aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_local
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library/ml_local')
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtCache.cpp508
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtCache.h14
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtContainer.cpp80
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtContainer.h43
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtFilter.cpp1258
-rw-r--r--Src/Plugins/Library/ml_local/AlbumArtFilter.h64
-rw-r--r--Src/Plugins/Library/ml_local/AlbumFilter.cpp496
-rw-r--r--Src/Plugins/Library/ml_local/AlbumFilter.h49
-rw-r--r--Src/Plugins/Library/ml_local/DBitemrecord.cpp173
-rw-r--r--Src/Plugins/Library/ml_local/FolderBrowseEx.cpp239
-rw-r--r--Src/Plugins/Library/ml_local/FolderBrowseEx.h97
-rw-r--r--Src/Plugins/Library/ml_local/LocalMediaCOM.cpp307
-rw-r--r--Src/Plugins/Library/ml_local/LocalMediaCOM.h21
-rw-r--r--Src/Plugins/Library/ml_local/MD5.cpp294
-rw-r--r--Src/Plugins/Library/ml_local/MD5.h18
-rw-r--r--Src/Plugins/Library/ml_local/MLDBCallback.h76
-rw-r--r--Src/Plugins/Library/ml_local/MLString.cpp116
-rw-r--r--Src/Plugins/Library/ml_local/MLString.h54
-rw-r--r--Src/Plugins/Library/ml_local/Main.cpp586
-rw-r--r--Src/Plugins/Library/ml_local/Main.h71
-rw-r--r--Src/Plugins/Library/ml_local/ReIndexUI.cpp333
-rw-r--r--Src/Plugins/Library/ml_local/SaveQuery.cpp406
-rw-r--r--Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp475
-rw-r--r--Src/Plugins/Library/ml_local/ScanFolderBrowser.h53
-rw-r--r--Src/Plugins/Library/ml_local/SimpleFilter.cpp278
-rw-r--r--Src/Plugins/Library/ml_local/SimpleFilter.h169
-rw-r--r--Src/Plugins/Library/ml_local/TitleInfo.cpp321
-rw-r--r--Src/Plugins/Library/ml_local/ViewFilter.cpp431
-rw-r--r--Src/Plugins/Library/ml_local/ViewFilter.h89
-rw-r--r--Src/Plugins/Library/ml_local/add.cpp478
-rw-r--r--Src/Plugins/Library/ml_local/api__ml_local.h15
-rw-r--r--Src/Plugins/Library/ml_local/api_mldb.cpp3
-rw-r--r--Src/Plugins/Library/ml_local/api_mldb.h166
-rw-r--r--Src/Plugins/Library/ml_local/bgscan.cpp940
-rw-r--r--Src/Plugins/Library/ml_local/contnr.cpp979
-rw-r--r--Src/Plugins/Library/ml_local/contnr.h153
-rw-r--r--Src/Plugins/Library/ml_local/db.h64
-rw-r--r--Src/Plugins/Library/ml_local/db_error.txt6
-rw-r--r--Src/Plugins/Library/ml_local/editinfo.cpp816
-rw-r--r--Src/Plugins/Library/ml_local/editquery.cpp1013
-rw-r--r--Src/Plugins/Library/ml_local/editquery.h7
-rw-r--r--Src/Plugins/Library/ml_local/evntsink.cpp239
-rw-r--r--Src/Plugins/Library/ml_local/evntsink.h25
-rw-r--r--Src/Plugins/Library/ml_local/guess.cpp122
-rw-r--r--Src/Plugins/Library/ml_local/handleMessage.cpp906
-rw-r--r--Src/Plugins/Library/ml_local/local_menu.cpp272
-rw-r--r--Src/Plugins/Library/ml_local/local_menu.h15
-rw-r--r--Src/Plugins/Library/ml_local/metaRecord.h44
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.cpp1394
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.h403
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.rc1244
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.sln128
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.vcxproj416
-rw-r--r--Src/Plugins/Library/ml_local/ml_local.vcxproj.filters328
-rw-r--r--Src/Plugins/Library/ml_local/ml_subclass.cpp80
-rw-r--r--Src/Plugins/Library/ml_local/mldbApi.cpp399
-rw-r--r--Src/Plugins/Library/ml_local/mldbApi.h34
-rw-r--r--Src/Plugins/Library/ml_local/mldbApiFactory.cpp61
-rw-r--r--Src/Plugins/Library/ml_local/mldbApiFactory.h23
-rw-r--r--Src/Plugins/Library/ml_local/nde_error.txt4
-rw-r--r--Src/Plugins/Library/ml_local/nde_itemRecord.cpp976
-rw-r--r--Src/Plugins/Library/ml_local/pe_subclass.cpp76
-rw-r--r--Src/Plugins/Library/ml_local/prefs.cpp936
-rw-r--r--Src/Plugins/Library/ml_local/queries.cpp1639
-rw-r--r--Src/Plugins/Library/ml_local/queries.txt77
-rw-r--r--Src/Plugins/Library/ml_local/remove.cpp31
-rw-r--r--Src/Plugins/Library/ml_local/resource.h606
-rw-r--r--Src/Plugins/Library/ml_local/resources/icn_alb_art.bmpbin0 -> 1210 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/icn_columns.bmpbin0 -> 1342 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/icn_view_mode.bmpbin0 -> 1298 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_simple.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_threefilters.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/nf_twofilters.bmpbin0 -> 822 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/notfound.pngbin0 -> 3645 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmpbin0 -> 578 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_local/util.cpp77
-rw-r--r--Src/Plugins/Library/ml_local/version.rc239
-rw-r--r--Src/Plugins/Library/ml_local/view_audio.cpp2403
-rw-r--r--Src/Plugins/Library/ml_local/view_errorinfo.cpp119
-rw-r--r--Src/Plugins/Library/ml_local/view_media.cpp4051
-rw-r--r--Src/Plugins/Library/ml_local/view_miniinfo.cpp201
-rw-r--r--Src/Plugins/Library/ml_local/wa_subclass.cpp314
91 files changed, 29441 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_local/AlbumArtCache.cpp b/Src/Plugins/Library/ml_local/AlbumArtCache.cpp
new file mode 100644
index 00000000..6478c009
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtCache.cpp
@@ -0,0 +1,508 @@
+#include "main.h"
+#include <nde/nde_c.h>
+#include "../nu/threadname.h"
+#include "AlbumArtContainer.h"
+#include <tataki/bitmap/bitmap.h>
+#include "MD5.h"
+#include "../replicant/nu/AutoLock.h"
+#include <strsafe.h>
+#include <map>
+#include <deque>
+
+static bool GetArtFromCache(const wchar_t *filename, int size, ARGB32 **bits);
+static bool SetArtCache(const wchar_t *filename, int size, const ARGB32 *bits, uint8_t hash[16]);
+static void CloseArtCache();
+static HANDLE artWake=0;
+static HANDLE mainThread=0;
+
+struct CreateCacheParameters
+{
+ AlbumArtContainer *container;
+ int w;
+ int h;
+ SkinBitmap *cache;
+ AlbumArtContainer::CacheStatus status;
+};
+
+static std::deque<CreateCacheParameters*> artQueue;
+static nu::LockGuard queueGuard;
+
+static void Adjust(int bmpw, int bmph, int &x, int &y, int &w, int &h)
+{
+ // maintain 'square' stretching
+ double aspX = (double)(w)/(double)bmpw;
+ double aspY = (double)(h)/(double)bmph;
+ double asp = min(aspX, aspY);
+ int newW = (int)(bmpw*asp);
+ int newH = (int)(bmph*asp);
+ x = (w - newW)/2;
+ y = (h - newH)/2;
+ w = newW;
+ h = newH;
+}
+
+static void CALLBACK CreateCacheCallbackAPC(ULONG_PTR parameter)
+{
+ CreateCacheParameters *parameters = (CreateCacheParameters *)parameter;
+ parameters->container->SetCache(parameters->cache, parameters->status);
+ parameters->container->Release();
+ WASABI_API_MEMMGR->sysFree(parameters);
+}
+
+static void CreateCache(CreateCacheParameters *parameters)
+{
+ // let's hope this doesn't overflow the stack
+ const wchar_t *filename = parameters->container->filename;
+
+ if ((unsigned int)(ULONG_PTR)filename < 65536)
+ {
+ parameters->cache = 0;
+ parameters->status = AlbumArtContainer::CACHE_NOTFOUND;
+ QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)parameters);
+ return;
+ }
+
+ int w = parameters->w, h = parameters->h;
+
+ ARGB32 *cacheBits = 0;
+ if (GetArtFromCache(filename, w, &cacheBits))
+ {
+ SkinBitmap *cache = new SkinBitmap(cacheBits, w, h, true);
+ parameters->cache = cache;
+ parameters->status = AlbumArtContainer::CACHE_CACHED;
+ }
+ else
+ {
+ int artsize = w;
+ int bmp_w = 0, bmp_h = 0;
+ ARGB32 *bits = 0;
+
+ if (AGAVE_API_ALBUMART && AGAVE_API_ALBUMART->GetAlbumArt(filename, L"Cover", &bmp_w, &bmp_h, &bits) == ALBUMART_SUCCESS)
+ {
+ uint8_t hash[16] = {0};
+ MD5_CTX hashCtx;
+ MD5Init(&hashCtx);
+ MD5Update(&hashCtx, (uint8_t *)bits, bmp_w*bmp_h*sizeof(ARGB32));
+ MD5Final(hash, &hashCtx);
+
+ BltCanvas canvas(w,h);
+ HQSkinBitmap temp(bits, bmp_w,bmp_h); // wrap into a SkinBitmap (no copying involved)
+ int x = 0, y = 0;
+ Adjust(bmp_w, bmp_h, x,y,w,h);
+ temp.stretch(&canvas,x,y,w,h);
+ SkinBitmap *cache = new SkinBitmap(&canvas);
+ parameters->cache = cache;
+ SetArtCache(filename, artsize, (ARGB32 *)canvas.getBits(), hash);
+ WASABI_API_MEMMGR->sysFree(bits);
+ parameters->status = AlbumArtContainer::CACHE_CACHED;
+ }
+ else
+ {
+ parameters->cache = 0;
+ parameters->status = AlbumArtContainer::CACHE_NOTFOUND;
+ }
+ }
+ QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)parameters);
+ return;
+}
+
+static int ArtLoadThread(HANDLE handle, void *user_data, intptr_t id)
+{
+ // TODO? SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
+
+ queueGuard.Lock();
+ if (artQueue.empty())
+ {
+ queueGuard.Unlock();
+ return 0;
+ }
+
+ CreateCacheParameters *parameters = artQueue.front();
+ artQueue.pop_front();
+ queueGuard.Unlock();
+ CreateCache(parameters);
+ return 0;
+}
+
+enum
+{
+ ARTHASH_FILENAME = 0,
+ ARTHASH_HASH = 1,
+
+ ARTTABLE_HASH = 0,
+ ARTTABLE_ARGB32 = 1,
+};
+
+typedef std::map<int, nde_table_t> ArtCache;
+static ArtCache artcache;
+static nde_database_t art_db = 0;
+static nde_table_t artHashes = 0;
+
+static ThreadID *artThread=0;
+static void InitArtThread()
+{
+ if (!artThread)
+ {
+ artWake = CreateSemaphore(0, 0, 65536, 0);
+ mainThread = WASABI_API_APP->main_getMainThreadHandle();
+ artThread = WASABI_API_THREADPOOL->ReserveThread(0);
+ WASABI_API_THREADPOOL->AddHandle(artThread, artWake, ArtLoadThread, 0, 0, 0);
+ }
+}
+
+static int ArtKillThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ WASABI_API_THREADPOOL->RemoveHandle(artThread, artWake);
+ CloseArtCache();
+ SetEvent((HANDLE)user_data);
+ return 0;
+}
+
+void KillArtThread()
+{
+ if (artThread)
+ {
+ HANDLE artKill = CreateEvent(0, FALSE, FALSE, 0);
+ WASABI_API_THREADPOOL->RunFunction(artThread, ArtKillThreadPoolFunc, (void *)artKill, 0, 0);
+ WaitForSingleObject(artKill, INFINITE);
+ CloseHandle(mainThread); mainThread = 0;
+ CloseHandle(artKill); artKill = 0;
+ CloseHandle(artWake); artWake = 0;
+ WASABI_API_THREADPOOL->ReleaseThread(artThread);
+ }
+}
+
+void MigrateArtCache()
+{
+ int size[] = {0, 60, 90, 120, 180};
+
+ for (int i = 0; i < sizeof(size)/sizeof(size[0]); i++)
+ {
+ if (!size[i])
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ art_db = NDE_CreateDatabase(plugin.hDllInstance);
+ wchar_t tableName[MAX_PATH] = {0}, oldTableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0}, oldIndexName[MAX_PATH] = {0};
+ PathCombineW(oldIndexName, inidir, L"Plugins\\ml");
+ PathCombineW(indexName, inidir, L"Plugins\\ml\\art");
+ CreateDirectoryW(indexName, NULL);
+ PathCombineW(tableName, indexName, L"art.dat");
+ PathCombineW(oldTableName, oldIndexName, L"art.dat");
+ PathAppendW(indexName, L"art.idx");
+ PathAppendW(oldIndexName, L"art.idx");
+
+ // migrate files to their own 'art' sub-folder
+ if (PathFileExistsW(oldIndexName) && !PathFileExistsW(indexName))
+ {
+ MoveFileW(oldIndexName, indexName);
+ MoveFileW(oldTableName, tableName);
+ }
+ }
+ else if (size[i])
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t temp[MAX_PATH] = {0}, tableName[MAX_PATH] = {0}, oldTableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0}, oldIndexName[MAX_PATH] = {0};
+ PathCombineW(temp, inidir, L"Plugins\\ml");
+ StringCchPrintfW(tableName, MAX_PATH, L"%s\\art\\art_%d.dat", temp, size[i]);
+ StringCchPrintfW(oldTableName, MAX_PATH, L"%s\\art_%d.dat", temp, size[i]);
+ StringCchPrintfW(indexName, MAX_PATH, L"%s\\art\\art_%d.idx", temp, size[i]);
+ StringCchPrintfW(oldIndexName, MAX_PATH, L"%s\\art_%d.idx", temp, size[i]);
+
+ // migrate files to their own 'art' sub-folder
+ if (PathFileExistsW(oldIndexName) && !PathFileExistsW(indexName))
+ {
+ MoveFileW(oldIndexName, indexName);
+ MoveFileW(oldTableName, tableName);
+ }
+ }
+ }
+}
+
+static bool InitArtCache(int size)
+{
+ if (!art_db)
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ art_db = NDE_CreateDatabase(plugin.hDllInstance);
+ wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(indexName, inidir, L"Plugins\\ml\\art");
+ PathCombineW(tableName, indexName, L"art.dat");
+ PathAppendW(indexName, L"art.idx");
+
+ artHashes = NDE_Database_OpenTable(art_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
+ NDE_Table_NewColumnW(artHashes, ARTHASH_FILENAME, DB_FIELDNAME_filename, FIELD_FILENAME);
+ NDE_Table_NewColumnW(artHashes, ARTHASH_HASH, L"hash", FIELD_INT128);
+ NDE_Table_PostColumns(artHashes);
+ NDE_Table_AddIndexByIDW( artHashes, ARTHASH_FILENAME, DB_FIELDNAME_filename );
+ }
+
+ if (size && !artcache[size])
+ {
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t temp[MAX_PATH] = {0}, tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(temp, inidir, L"Plugins\\ml");
+ StringCchPrintfW(tableName, MAX_PATH, L"%s\\art\\art_%d.dat", temp, size);
+ StringCchPrintfW(indexName, MAX_PATH, L"%s\\art\\art_%d.idx", temp, size);
+
+ nde_table_t artTable = NDE_Database_OpenTable(art_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_NOCACHE);
+ NDE_Table_NewColumnW(artTable, ARTTABLE_HASH, L"hash", FIELD_INT128);
+ NDE_Table_NewColumnW(artTable, ARTTABLE_ARGB32, L"art", FIELD_BINARY32);
+ NDE_Table_PostColumns(artTable);
+ NDE_Table_AddIndexByIDW(artTable, ARTTABLE_HASH, L"hash");
+ artcache[size] = artTable;
+ }
+ else
+ return artHashes != 0;
+
+ return artcache[size] != 0;
+}
+static void CloseArtHashes()
+{
+ __try
+ {
+ if (artHashes)
+ {
+ NDE_Database_CloseTable(art_db, artHashes);
+ }
+ artHashes = 0;
+
+ NDE_DestroyDatabase(art_db);
+ art_db = 0;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ artHashes = 0;
+ art_db = 0;
+ }
+}
+static void CloseArtCache()
+{
+ ArtCache::iterator itr;
+ for (itr = artcache.begin(); itr != artcache.end(); itr++)
+ {
+ if (itr->second)
+ NDE_Database_CloseTable(art_db, itr->second);
+ itr->second = 0;
+ }
+ artcache.clear();
+
+ CloseArtHashes();
+}
+/*
+@param size = art dimensions. e.g. size==120 is for 120x120 album art
+@param bits better be allocated to size*size*sizeof(ARGB32) or you're in for a world of hurt
+*/
+static bool GetArtFromCache(const wchar_t *filename, int size, ARGB32 **bits)
+{
+ if (InitArtCache(size))
+ {
+ nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
+ if (NDE_Scanner_LocateFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
+ if (field)
+ {
+ nde_scanner_t artscanner = NDE_Table_CreateScanner(artcache[size]);
+ if (NDE_Scanner_LocateField(artscanner, ARTTABLE_HASH, FIRST_RECORD, field))
+ {
+ nde_field_t field = NDE_Scanner_GetFieldByID(artscanner, ARTTABLE_ARGB32);
+ if (field)
+ {
+ size_t len = 0;
+ void *data = NDE_BinaryField_GetData(field, &len);
+ if (data && len == size*size*sizeof(ARGB32))
+ {
+ *bits = (ARGB32 *)WASABI_API_MEMMGR->sysMalloc(len);
+ memcpy(*bits, data, len);
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ return true;
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ }
+ }
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ }
+ return false;
+}
+
+static bool SetArtCache(const wchar_t *filename, int size, const ARGB32 *bits, uint8_t hash[16])
+{
+ if (InitArtCache(size))
+ {
+ nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
+ if (!NDE_Scanner_LocateFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
+ {
+ NDE_Scanner_New(hashscanner);
+ db_setFieldStringW(hashscanner, ARTHASH_FILENAME, filename);
+ }
+ else
+ NDE_Scanner_Edit(hashscanner);
+
+ nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
+ if (!field)
+ field = NDE_Scanner_NewFieldByID(hashscanner, ARTHASH_HASH);
+ NDE_Int128Field_SetValue(field, hash);
+
+ NDE_Scanner_Post(hashscanner);
+
+ nde_scanner_t artscanner = NDE_Table_CreateScanner(artcache[size]);
+ if (!NDE_Scanner_LocateField(artscanner, ARTTABLE_HASH, FIRST_RECORD, field))
+ {
+ NDE_Scanner_New(artscanner);
+ field = NDE_Scanner_NewFieldByID(artscanner, ARTTABLE_HASH);
+ NDE_Int128Field_SetValue(field, hash);
+ field = NDE_Scanner_NewFieldByID(artscanner, ARTTABLE_ARGB32);
+ // TODO when size is possibly zero, this is causing a crash in nde.dll
+ if (size > 0) NDE_BinaryField_SetData(field, bits, size*size*sizeof(ARGB32));
+ NDE_Scanner_Post(artscanner);
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ NDE_Table_Sync(artHashes);
+ return true;
+ }
+ NDE_Table_DestroyScanner(artcache[size], artscanner);
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ NDE_Table_Sync(artHashes);
+ }
+
+ return false;
+}
+
+size_t maxCache = 65536/*100*/;
+void HintCacheSize(int _cachesize)
+{
+ maxCache = _cachesize;
+}
+
+void CreateCache(AlbumArtContainer *container, int w, int h)
+{
+ InitArtThread();
+ assert(artThread);
+ container->AddRef();
+ CreateCacheParameters *parameters = (CreateCacheParameters *)WASABI_API_MEMMGR->sysMalloc(sizeof(CreateCacheParameters));
+ parameters->container = container;
+ parameters->w = w;
+ parameters->h = h;
+ parameters->status = AlbumArtContainer::CACHE_UNKNOWN;
+ nu::AutoLock lock(queueGuard);
+ if (artQueue.size() > maxCache)
+ {
+ CreateCacheParameters *kill = artQueue.back();
+ artQueue.pop_back();
+ kill->cache = 0;
+ kill->status = AlbumArtContainer::CACHE_UNKNOWN;
+ QueueUserAPC(CreateCacheCallbackAPC, mainThread, (ULONG_PTR)kill);
+ }
+ artQueue.push_front(parameters);
+ ReleaseSemaphore(artWake, 1, 0);
+}
+
+void FlushCache()
+{
+ nu::AutoLock lock(queueGuard);
+ while (!artQueue.empty())
+ {
+ CreateCacheParameters *kill = artQueue.front();
+ kill->container->SetCache(0, AlbumArtContainer::CACHE_UNKNOWN);
+ artQueue.pop_front();
+ WASABI_API_MEMMGR->sysFree(kill);
+ }
+}
+
+void ResumeCache()
+{
+}
+
+static int ClearFilenameCacheAPC(HANDLE handle, void *param, intptr_t id)
+{
+ wchar_t *filename = (wchar_t *)param;
+
+ if (InitArtCache(0))
+ {
+ nde_scanner_t hashscanner = NDE_Table_CreateScanner(artHashes);
+ if (NDE_Scanner_LocateNDEFilename(hashscanner, ARTHASH_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t field = NDE_Scanner_GetFieldByID(hashscanner, ARTHASH_HASH);
+ if (field)
+ {
+ nde_scanner_t deleteAllScanner = NDE_Table_CreateScanner(artHashes);
+ while (NDE_Scanner_LocateField(deleteAllScanner, ARTHASH_HASH, FIRST_RECORD, field))
+ {
+ NDE_Scanner_Delete(deleteAllScanner);
+ NDE_Scanner_Post(deleteAllScanner);
+ }
+ NDE_Table_DestroyScanner(artHashes, deleteAllScanner);
+ NDE_Table_Sync(artHashes);
+
+ /* delete it from the art table as well, but we'll just
+ use the already-opened ones */
+ for (ArtCache::iterator itr=artcache.begin();itr!=artcache.end();itr++)
+ {
+ nde_table_t table = itr->second;
+ if (table)
+ {
+ nde_scanner_t s= NDE_Table_CreateScanner(table);
+ while (NDE_Scanner_LocateField(s, ARTTABLE_HASH, FIRST_RECORD, field))
+ {
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ }
+ NDE_Table_DestroyScanner(table, s);
+ NDE_Table_Sync(table);
+ }
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(artHashes, hashscanner);
+ }
+ ndestring_release(filename);
+ return 0;
+}
+
+static int DeleteDatabaseAPC(HANDLE handle, void *user_data, intptr_t id)
+{
+ CloseArtCache();
+
+ wchar_t search_mask[MAX_PATH] = {0};
+ StringCchPrintfW(search_mask, MAX_PATH, L"%s\\art_*.*", g_tableDir);
+
+ wchar_t fn[MAX_PATH] = {0};
+ StringCchPrintfW(fn, MAX_PATH, L"%s\\art.idx", g_tableDir);
+ DeleteFileW(fn);
+ StringCchPrintfW(fn, MAX_PATH, L"%s\\art.dat", g_tableDir);
+ DeleteFileW(fn);
+
+ WIN32_FIND_DATAW findData;
+ HANDLE hFind = FindFirstFileW(search_mask, &findData);
+ if (hFind != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ if (!_wcsnicmp(findData.cFileName, L"art_", 4))
+ {
+ StringCchPrintfW(fn, MAX_PATH, L"%s\\%s", g_tableDir, findData.cFileName);
+ DeleteFileW(fn);
+ }
+ }
+ while (FindNextFileW(hFind, &findData));
+ FindClose(hFind);
+ }
+ return 0;
+}
+
+void DumpArtCache()
+{
+ InitArtThread();
+ assert(artThread);
+ WASABI_API_THREADPOOL->RunFunction(artThread, DeleteDatabaseAPC, 0, 0, 0);
+}
+
+void ClearCache(const wchar_t *filename)
+{
+ InitArtThread();
+ assert(artThread);
+ WASABI_API_THREADPOOL->RunFunction(artThread, ClearFilenameCacheAPC, ndestring_wcsdup(filename), 0, 0);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtCache.h b/Src/Plugins/Library/ml_local/AlbumArtCache.h
new file mode 100644
index 00000000..c413e0ee
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtCache.h
@@ -0,0 +1,14 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMARTCACHE_H
+#define NULLSOFT_ML_LOCAL_ALBUMARTCACHE_H
+
+#include "AlbumArtContainer.h"
+void KillArtThread();
+void CreateCache(AlbumArtContainer *container, int w, int h);
+void FlushCache();
+void ResumeCache();
+
+void HintCacheSize(int _cachesize);
+
+void ClearCache(const wchar_t *filename);
+void DumpArtCache();
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtContainer.cpp b/Src/Plugins/Library/ml_local/AlbumArtContainer.cpp
new file mode 100644
index 00000000..9b06b288
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtContainer.cpp
@@ -0,0 +1,80 @@
+#include "AlbumArtContainer.h"
+#include "api__ml_local.h"
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/canvas.h>
+#include <assert.h>
+#include "AlbumArtCache.h"
+#include "../nde/nde.h"
+#include <commctrl.h>
+
+AlbumArtContainer::AlbumArtContainer() :filename(0)
+{
+ references = 1;
+ cached = CACHE_UNKNOWN;
+ cache = 0;
+ updateMsg.hwnd = NULL;
+}
+
+void AlbumArtContainer::AddRef()
+{
+ references++;
+}
+
+void AlbumArtContainer::Release()
+{
+ if (--references == 0)
+ delete this;
+}
+
+AlbumArtContainer::~AlbumArtContainer()
+{
+ ndestring_release(filename);
+ if (cache) cache->Release();
+}
+
+void AlbumArtContainer::Reset()
+{
+ if (cache)
+ cache->Release();
+ cache = 0;
+ cached = AlbumArtContainer::CACHE_UNKNOWN;
+}
+
+void AlbumArtContainer::SetCache(SkinBitmap *bitmap, CacheStatus status)
+{
+ if (cache)
+ cache->Release();
+ cache = bitmap;
+ cached = status;
+
+ // lets post the update message
+ if(updateMsg.hwnd && (CACHE_CACHED == cached || CACHE_UNKNOWN == cached)) PostMessageW(updateMsg.hwnd,updateMsg.message,updateMsg.wParam,updateMsg.lParam);
+}
+
+int AlbumArtContainer::drawArt(DCCanvas *pCanvas, RECT *prcDst)
+{
+ switch (cached)
+ {
+ case CACHE_NOTFOUND:
+ return DRAW_NOART;
+ case CACHE_LOADING:
+ return DRAW_LOADING;
+ case CACHE_UNKNOWN:
+ cached = CACHE_LOADING;
+ CreateCache(this, prcDst->right - prcDst->left, prcDst->bottom - prcDst->top);
+ return DRAW_LOADING;
+ case CACHE_CACHED:
+ {
+ if (cache->getWidth() != (prcDst->right - prcDst->left) || cache->getHeight() != (prcDst->bottom - prcDst->top))
+ {
+ cached = CACHE_LOADING;
+ CreateCache(this, prcDst->right - prcDst->left, prcDst->bottom - prcDst->top);
+ return DRAW_LOADING;
+ }
+ if (pCanvas) cache->blitAlpha(pCanvas, prcDst->left, prcDst->top);
+ return DRAW_SUCCESS;
+ }
+ default:
+ return DRAW_NOART; // shouldn't reach this;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtContainer.h b/Src/Plugins/Library/ml_local/AlbumArtContainer.h
new file mode 100644
index 00000000..988721ff
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtContainer.h
@@ -0,0 +1,43 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMARTCONTAINER_H
+#define NULLSOFT_ML_LOCAL_ALBUMARTCONTAINER_H
+
+#include <windows.h> // for HDC
+#include <tataki/canvas/bltcanvas.h>
+
+class AlbumArtContainer
+{
+public:
+ enum CacheStatus
+ {
+ CACHE_UNKNOWN,
+ CACHE_CACHED,
+ CACHE_NOTFOUND,
+ CACHE_LOADING,
+ };
+
+ AlbumArtContainer();
+ enum
+ {
+ DRAW_SUCCESS,
+ DRAW_NOART,
+ DRAW_LOADING,
+ };
+ int drawArt(DCCanvas *pCanvas, RECT *prcDst);
+ // benski> this definition is just temporary to get things going
+
+ void AddRef();
+ void Release();
+ wchar_t *filename; // actually an NDE reference counted string
+ MSG updateMsg;
+ void SetCache(SkinBitmap *bitmap, CacheStatus status);
+ void Reset();
+private:
+ ~AlbumArtContainer();
+ SkinBitmap * volatile cache;
+
+ volatile CacheStatus cached;
+ size_t references;
+
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtFilter.cpp b/Src/Plugins/Library/ml_local/AlbumArtFilter.cpp
new file mode 100644
index 00000000..d7295571
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtFilter.cpp
@@ -0,0 +1,1258 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "../nu/sort.h"
+#include "AlbumArtFilter.h"
+#include "resource.h"
+#include "../nu/AutoUrl.h"
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "AlbumArtContainer.h"
+#include <tataki/canvas/canvas.h>
+#include <tataki/bitmap/autobitmap.h>
+#include <api/service/waServiceFactory.h>
+#include <api/service/svcs/svc_imgload.h>
+
+static size_t m_sort_by, m_sort_dir, m_sort_which;
+
+void emptyQueryListObject(queryListObject *obj);
+int reallocQueryListObject(queryListObject *obj);
+void freeQueryListObject(queryListObject *obj);
+extern C_Config *g_config;
+int config_use_alternate_colors=1;
+
+#define WM_EX_GETREALLIST (WM_USER + 0x01)
+#define WM_EX_GETCOUNTPERPAGE (WM_USER + 0x04)
+#define LVN_EX_SIZECHANGED (LVN_LAST)
+
+AlbumArtFilter::AlbumArtFilter(HWND hwndDlg, int dlgitem, C_Config *c) : hwndDlg(hwndDlg), dlgitem(dlgitem),
+ notfound(L"winamp.cover.notfound"), notfound60(L"winamp.cover.notfound.60"), notfound90(L"winamp.cover.notfound.90"),
+ hbmpNames(NULL), ratingHotItem((DWORD)-1), bgBrush(NULL)
+{
+ mode = c->ReadInt(L"albumartviewmode",1);
+ icons_only = c->ReadInt(L"albumarticonmode",0);
+ ZeroMemory(classicnotfound, sizeof(classicnotfound));
+ DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+}
+
+AlbumArtFilter::~AlbumArtFilter()
+{
+ if (hbmpNames) DeleteObject(hbmpNames);
+ if (bgBrush) DeleteBrush(bgBrush);
+}
+
+static COLORREF GetWAColor(INT index)
+{
+ static int (*wad_getColor)(int idx) = NULL;
+ if (!wad_getColor) *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ return (wad_getColor) ? wad_getColor(index) : 0xFF00FF;
+}
+
+static void getImgSize(int mode, int &w, int &h)
+{
+ switch (mode)
+ {
+ case 0: w=h=60; break;
+ case 1: w=h=90; break;
+ case 2: w=h=120; break;
+ case 3: w=h=60; break;
+ case 4: w=h=90; break;
+ case 5: w=h=120; break;
+ case 6: w=h=180; break;
+ }
+}
+
+static bool isDetailsMode(int mode)
+{
+ return mode == 0 || mode == 1 || mode == 2;
+}
+
+void DrawRect(HDC dc, int x, int y, int w, int h)
+{
+ w-=1;
+ h-=1;
+ MoveToEx(dc,x,y,NULL);
+ LineTo(dc,x,y+h);
+ MoveToEx(dc,x,y+h,NULL);
+ LineTo(dc,x+w,y+h);
+ MoveToEx(dc,x+w,y+h,NULL);
+ LineTo(dc,x+w,y);
+ MoveToEx(dc,x+w,y,NULL);
+ LineTo(dc,x,y);
+}
+
+static int getStrExtent(HDC dc, const wchar_t * s)
+{
+ int ret=0;
+ while (s && *s)
+ {
+ int f;
+ GetCharWidth32(dc,*s,*s,&f);
+ s++;
+ ret+=f;
+ }
+ return int(ret);
+}
+
+ARGB32 * loadImg(const void * data, int len, int *w, int *h, bool ldata=false)
+{
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for (int i=0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if (sf)
+ {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if (l)
+ {
+ if (l->testData(data,len))
+ {
+ ARGB32* ret;
+ if (ldata) ret = l->loadImageData(data,len,w,h);
+ else ret = l->loadImage(data,len,w,h);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+ARGB32 * loadRrc(int id, char * sec, int *w, int *h, bool data=false)
+{
+ DWORD size = 0;
+ // as a nice little treat, allow lang packs to contain a custom IDR_IMAGE_NOTFOUND file
+ HGLOBAL resourceHandle = WASABI_API_LOADRESFROMFILEA((LPCSTR)sec, MAKEINTRESOURCEA(id), &size);
+ if(resourceHandle)
+ {
+ ARGB32* ret = loadImg(resourceHandle,size,w,h,data);
+ UnlockResource(resourceHandle);
+ return ret;
+ }
+ return NULL;
+}
+
+void adjustbmp(ARGB32 * p, int len, COLORREF fg)
+{
+ ARGB32 * end = p+len;
+ while (p < end)
+ {
+ int a = (*p>>24)&0xff ;
+ int b = a*((*p&0xff) * (fg&0xff)) / (0xff*0xff);
+ int g = a*(((*p>>8)&0xff) * ((fg>>8)&0xff)) / (0xff*0xff);
+ int r = a*(((*p>>16)&0xff) * ((fg>>16)&0xff)) / (0xff*0xff);
+ *p = (a<<24) | (r&0xff) | ((g&0xff)<<8) | ((b&0xff)<<16);
+ p++;
+ }
+}
+
+void AlbumArtFilter::drawArt(AlbumArtContainer *art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex)
+{
+ int res = AlbumArtContainer::DRAW_NOART;
+ if (art)
+ {
+ art->updateMsg.hwnd = hwndRealList;
+ art->updateMsg.message = LVM_REDRAWITEMS;
+ art->updateMsg.lParam = itemid;
+ art->updateMsg.wParam = itemid;
+ res = art->drawArt(pCanvas, prcDst);
+ }
+
+ if (res != AlbumArtContainer::DRAW_SUCCESS)
+ {
+ SkinBitmap *noart;
+
+ // Martin> This will just work for Modern Skins, but looks definitly better
+ int h = prcDst->right - prcDst->left;
+ if (h == 60)
+ noart = notfound60.getBitmap();
+ else if (h == 90)
+ noart = notfound90.getBitmap();
+ else
+ noart = notfound.getBitmap();
+
+ if (!noart || noart->isInvalid())
+ {
+ if(classicnotfound[imageIndex])
+ SkinBitmap(classicnotfound[imageIndex],classicnotfoundW,classicnotfoundH).stretchToRectAlpha(pCanvas, prcDst);
+ else
+ {
+ int w = prcDst->right - prcDst->left;
+ int h = prcDst->bottom - prcDst->top;
+ DrawRect(pCanvas->getHDC(), prcDst->left, prcDst->top, w, h);
+ wchar_t str1[32] = {0}, str2[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_IMAGE,str1,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_AVAILABLE,str2,32);
+ ExtTextOutW(pCanvas->getHDC(), w/2 - 22 + prcDst->left, w/2 - 14 + prcDst->top, 0,NULL,str1,wcslen(str1),0);
+ ExtTextOutW(pCanvas->getHDC(), w/2 - 22 + prcDst->left, w/2 + 1 + prcDst->top, 0,NULL,str2,wcslen(str2),0);
+ }
+ }
+ else
+ noart->stretchToRectAlpha(pCanvas, prcDst);
+ }
+}
+
+// icon view
+BOOL AlbumArtFilter::DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive)
+{
+ HDC hdc = plvcd->nmcd.hdc;
+ RECT ri, re, rcText;
+ int w=0, h=0, imageIndex=0;
+ getImgSize(mode,w,h);
+
+ SetBkColor(hdc, plvcd->clrTextBk);
+ SetTextColor(hdc, plvcd->clrText);
+ SetRect(&rcText, plvcd->nmcd.rc.left, (plvcd->nmcd.rc.bottom - textHeight),
+ plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - (icons_only ? textHeight : 4));
+
+ // background
+ SetRect(&re, plvcd->nmcd.rc.left, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, (icons_only ? (plvcd->nmcd.rc.bottom - textHeight - 5) : rcText.top));
+
+ if (IntersectRect(&ri, &re, prcClip))
+ ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ if (LVIS_SELECTED & itemState)
+ imageIndex = (bWndActive) ? 1 : 2;
+
+ INT of = ((plvcd->nmcd.rc.right - plvcd->nmcd.rc.left) - w)/2;
+ SetRect(&re, plvcd->nmcd.rc.left + of, plvcd->nmcd.rc.top + 2, plvcd->nmcd.rc.left + of + w, plvcd->nmcd.rc.top + 2 + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ AlbumArtContainer *art = GetArt(plvcd->nmcd.dwItemSpec+1);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ if (IntersectRect(&ri, &rcText, prcClip))
+ {
+ ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ const wchar_t *p = GetText(plvcd->nmcd.dwItemSpec);
+ if (p && *p)
+ {
+ SetRect(&ri, rcText.left + of, rcText.top - (textHeight/2) - 1, rcText.right - of, rcText.top + textHeight);
+ DrawTextW(hdc, p, -1, &ri, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_NOPREFIX);
+ }
+ }
+
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ /*SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));*/
+ SetBkColor(hdc, GetWAColor(WADLG_ITEMBG));
+
+ if (icons_only)
+ {
+ InflateRect(&re, 2, 2);
+ }
+ else
+ plvcd->nmcd.rc.bottom -= 4;
+
+ DrawFocusRect(hdc, (icons_only ? &re : &plvcd->nmcd.rc));
+ }
+ }
+
+ return TRUE;
+}
+
+//detail view
+BOOL AlbumArtFilter::PrepareDetails(HDC hdc)
+{
+ INT width(0), height, len;
+ HFONT hFont,hFontBold, hOldFont;
+ HDC hdcTmp;
+ HBITMAP hbmpOld;
+ LOGFONT l={0};
+ RECT ri;
+ wchar_t ratingstr[100] = {0}, buf[100] = {0};
+
+ hdcTmp = CreateCompatibleDC(hdc);
+ if (!hdcTmp) return FALSE;
+
+ hFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
+ GetObject(hFont, sizeof(LOGFONT), &l);
+ l.lfWeight = FW_BOLD;
+ hFontBold = CreateFontIndirect(&l);
+
+ hOldFont = (HFONT)SelectObject(hdcTmp, hFontBold);
+
+ int bypassColumnCount = 0;
+
+ for ( ListField *l_showcolumn : showncolumns )
+ {
+ if ( l_showcolumn->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ bypassColumnCount++;
+ continue;
+ }
+ int of = getStrExtent(hdcTmp, l_showcolumn->name);
+ if (of > width) width = of;
+ }
+ if (width) width += 20;
+
+ height = (showncolumns.size()-bypassColumnCount) * textHeight;
+ hbmpNames = CreateCompatibleBitmap(hdc, width * 4, height);
+ hbmpOld = (HBITMAP)SelectObject(hdcTmp, hbmpNames);
+
+ SetRect(&ri, 0, 0, width, height);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_RATING,ratingstr,100);
+ INT clrText[4] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, WADLG_ITEMFG2, };
+ INT clrBk[4] = { WADLG_ITEMBG, WADLG_SELBAR_BGCOLOR, WADLG_INACT_SELBAR_BGCOLOR, WADLG_ITEMBG2, };
+
+ for (int j = 0; j < 4; j++)
+ {
+ SetTextColor(hdcTmp, GetWAColor(clrText[j]));
+ SetBkColor(hdcTmp, GetWAColor(clrBk[j]));
+
+ ExtTextOutW(hdcTmp,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ for (size_t i=0, top = 0; i < showncolumns.size(); i++, top += textHeight)
+ {
+ if (showncolumns[i]->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ top -= textHeight;
+ continue;
+ }
+ if (-1 == ratingrow && 0 == lstrcmpW(showncolumns[i]->name, ratingstr)) ratingrow = i;
+ StringCchCopyW(buf, 100, (showncolumns[i]->name) ? showncolumns[i]->name : L"");
+ len = wcslen(buf);
+ if (len > 0 && len < 99) { buf[len] = L':'; len ++; }
+ ExtTextOutW(hdcTmp, ri.left + 1, top, ETO_CLIPPED, &ri, buf, len, NULL);
+ }
+ OffsetRect(&ri, width, 0);
+ }
+
+ SelectObject(hdcTmp, hbmpOld);
+ SelectObject(hdcTmp, hOldFont);
+ DeleteObject(hFontBold);
+ DeleteDC(hdcTmp);
+ return TRUE;
+}
+
+BOOL AlbumArtFilter::DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNames, INT namesWidth)
+{
+ RECT ri, re;
+ INT imageIndex = 0;
+ HDC hdc = plvcd->nmcd.hdc;
+
+ SetTextColor(hdc, plvcd->clrText);
+ SetBkColor(hdc, plvcd->clrTextBk);
+
+ // background
+ SetRect(&re, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if (IntersectRect(&ri, &re, prcClip)) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+ if (LVIS_SELECTED & itemState) { imageIndex = (bWndActive) ? 1 : 2; }
+
+ int w, h;
+ getImgSize(mode, w, h);
+ SetRect(&re, 6+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, 6+ plvcd->nmcd.rc.left + w, 3+ plvcd->nmcd.rc.top + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ AlbumArtContainer *art = GetArt(plvcd->nmcd.dwItemSpec + 1);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ // text
+ int limCY, limCX;
+
+ //select 4th bitmap for alternate coloring
+ if (g_config->ReadInt(L"alternate_items", config_use_alternate_colors) && imageIndex==0 && plvcd->nmcd.dwItemSpec%2)
+ imageIndex=3;
+
+ limCY = plvcd->nmcd.rc.bottom;
+ if (prcClip->bottom < plvcd->nmcd.rc.bottom) limCY = prcClip->bottom;
+ limCX = plvcd->nmcd.rc.right -1;
+ if (prcClip->right < plvcd->nmcd.rc.right) limCX = prcClip->right;
+
+ SetRect(&ri, w+16+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, limCX, limCY);
+
+ if (hdcNames && ri.left < ri.right)
+ {
+ BitBlt(hdc, ri.left, ri.top, min(ri.right - ri.left, namesWidth), ri.bottom - ri.top, hdcNames, namesWidth*imageIndex, 0, SRCCOPY);
+ ri.left += namesWidth;
+ }
+
+ ri.bottom = ri.top;
+
+ if (ri.left < ri.right)
+ {
+ for (size_t i=0; i < showncolumns.size() && ri.top < limCY; i++, ri.top += textHeight)
+ {
+ if (showncolumns[i]->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ ri.top -= textHeight;
+ continue;
+ }
+
+ wchar_t buf[100] = {0};
+ const wchar_t *p = CopyText2(plvcd->nmcd.dwItemSpec,i,buf,100);
+ ri.bottom += textHeight;
+ if (ri.bottom > limCY) ri.bottom = limCY;
+
+ if ((INT)i == ratingrow) // this is the ratings column, so draw graphical stars
+ {
+ int rating = wcslen(buf);
+ RATINGDRAWPARAMS p = {sizeof(RATINGDRAWPARAMS),hdc,
+ {ri.left, ri.top + ratingTop, ri.right,ri.bottom},
+ rating,5,0,RDS_SHOWEMPTY,NULL,0};
+ if(ratingHotItem == plvcd->nmcd.dwItemSpec) { p.fStyle |= RDS_HOT; p.trackingValue = ratingHotValue; }
+ MLRating_Draw(plugin.hwndLibraryParent,&p);
+ }
+ else {
+ //ri.left = re.left;
+ ri.right = plvcd->nmcd.rc.right;
+ DrawTextW(hdc, p, wcslen(p), &ri, DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_NOCLIP | DT_NOPREFIX);
+ }
+ }
+ }
+
+ // bottom line
+ MoveToEx(hdc,plvcd->nmcd.rc.left + 4,plvcd->nmcd.rc.bottom - 3,NULL);
+ LineTo(hdc,plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 3);
+
+ // focus rect
+ SetRect(&ri, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ /*SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));*/
+ SetBkColor(hdc, GetWAColor(WADLG_ITEMBG));
+ DrawFocusRect(hdc, &ri);
+ }
+ }
+
+ return TRUE;
+}
+
+static HWND CreateSmoothScrollList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"SmoothScrollList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+static HWND CreateHeaderIconList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"HeaderIconList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+BOOL AlbumArtFilter::OnKeyDown(NMLVKEYDOWN *plvkd)
+{
+ switch (plvkd->wVKey)
+ {
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL))
+ {
+ LVITEM item;
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(plvkd->hdr.hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtFilter::OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ bActive = (GetFocus() == hwndRealList);
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemIcon(plvcd, &activeCanvas, itemState, &rcClip, bActive);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtFilter::OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static HDC hdcNames;
+ static HBITMAP hbmpOld;
+ static HPEN penOld;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ {
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ FillRect(plvcd->nmcd.hdc,&plvcd->nmcd.rc,bgBrush);
+
+ if (!hbmpNames) PrepareDetails(plvcd->nmcd.hdc);
+ if (hbmpNames)
+ {
+ BITMAP bi;
+ GetObject(hbmpNames, sizeof(BITMAP), &bi);
+ namesWidth = bi.bmWidth/4;
+ hdcNames = CreateCompatibleDC(plvcd->nmcd.hdc);
+ hbmpOld = (hdcNames) ? (HBITMAP)SelectObject(hdcNames, hbmpNames) : NULL;
+ }
+ else
+ {
+ hdcNames = NULL;
+ namesWidth = 0;
+ }
+ bActive = (GetFocus() == hwndRealList);
+
+ penOld = (HPEN)SelectObject(plvcd->nmcd.hdc, CreatePen(PS_SOLID,1, GetWAColor(WADLG_HILITE)));
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+ }
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemDetail(plvcd, &activeCanvas, itemState, &rcClip, bActive, hdcNames, namesWidth);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ if (hdcNames)
+ {
+ SelectObject(hdcNames, hbmpOld);
+ DeleteDC(hdcNames);
+ }
+ if (penOld)
+ {
+ HPEN pen = (HPEN)SelectObject(plvcd->nmcd.hdc, penOld);
+ if (pen) DeleteObject(pen);
+ penOld = NULL;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtFilter::CalcuateItemHeight(void)
+{
+ HDC hdc;
+ int w, h;
+ HWND hwndList;
+ TEXTMETRIC tm = {0};
+
+ getImgSize(mode, w, h);
+
+ hwndList = GetDlgItem(hwndDlg, dlgitem);
+
+ textHeight = 0;
+
+ hdc = GetDC(hwndDlg);
+ if (hdc)
+ {
+ HFONT hFont = (HFONT)SendMessageW(hwndList, WM_GETFONT, 0, 0L);
+ if (!hFont) hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ HFONT hFontOld = (HFONT)SelectObject(hdc, hFont);
+
+ GetTextMetrics(hdc, &tm);
+ textHeight = tm.tmHeight;
+ SelectObject(hdc, hFontOld);
+ ReleaseDC(hwndDlg, hdc);
+ }
+
+ if (isDetailsMode(mode))
+ {
+ if (textHeight < 14) textHeight = 14;
+ RECT r;
+ MLRating_CalcRect(plugin.hwndLibraryParent, NULL, 5, &r);
+ r.bottom -= r.top;
+
+ if ( r.bottom >= textHeight ) ratingTop = 0;
+ else
+ {
+ if (tm.tmAscent > (r.bottom + (r.bottom/2))) ratingTop = tm.tmAscent - r.bottom;
+ else ratingTop = (textHeight - r.bottom)/2 + 1;
+ }
+
+ int bypassColumnCount = 0;
+ for ( ListField *l_showcolumn : showncolumns )
+ {
+ if ( l_showcolumn->field == ALBUMFILTER_COLUMN_LASTUPD) // let's hide last updated from details view
+ {
+ bypassColumnCount++;
+ continue;
+ }
+ }
+
+ int newHeight = max(h, textHeight * ((INT)showncolumns.size() - bypassColumnCount)) + 12;
+ if (newHeight != itemHeight)
+ {
+ itemHeight = newHeight;
+ RECT rw;
+ GetWindowRect(hwndList, &rw);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left - 1, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
+ }
+ }
+ else
+ {
+ HIMAGELIST hIL = (HIMAGELIST)SendMessageW(hwndList, LVM_GETIMAGELIST, 0, 0L);
+ if (!hIL || !ImageList_GetIconSize(hIL, &w, &h)) { h += 4; w+= 4; }
+ SendMessageW(hwndList, LVM_SETICONSPACING, 0, MAKELPARAM(w, h + (icons_only ? 0 : textHeight)));
+ if (!tm.tmAveCharWidth) tm.tmAveCharWidth = 2;
+ itemHeight = w / tm.tmAveCharWidth;
+ }
+ return TRUE;
+}
+
+INT_PTR AlbumArtFilter::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ HWND hwnd = GetDlgItem(hwndDlg,dlgitem);
+ RECT r;
+ if (hwnd)
+ {
+ GetWindowRect(hwnd,&r);
+ MapWindowPoints(HWND_DESKTOP, hwndDlg, (POINT*)&r, 2);
+ DestroyWindow(hwnd);
+ }
+ else SetRect(&r, 0, 0, 1, 1);
+
+ if (isDetailsMode(mode))
+ {
+ hwnd = CreateSmoothScrollList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ }
+ else
+ {
+ hwnd = CreateHeaderIconList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ int w=0,h=0;
+ getImgSize(mode,w,h);
+ HIMAGELIST il = ImageList_Create(w + 4,h + 4,ILC_COLOR24,0,1); // add borders
+ ListView_SetImageList(hwnd,il,LVSIL_NORMAL);
+ }
+
+ hwndRealList = (HWND)SendMessageW(hwnd, WM_EX_GETREALLIST, 0, 0L);
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ if (isDetailsMode(mode) && NULL != hwndRealList)
+ MLSkinnedWnd_SetStyle(hwndRealList, MLSkinnedWnd_GetStyle(hwndRealList) | SWLVS_ALTERNATEITEMS);
+ }
+
+ case WM_DISPLAYCHANGE:
+ {
+ if (hbmpNames)
+ DeleteObject(hbmpNames);
+
+ hbmpNames = NULL;
+ ratingrow = -1;
+
+ if(bgBrush)
+ DeleteBrush(bgBrush);
+
+ bgBrush = CreateSolidBrush(GetWAColor(WADLG_ITEMBG));
+ CalcuateItemHeight();
+
+ //TODO
+ //config_use_alternate_colors = g_config->ReadInt("alternate_items", 1);
+
+ int rw,rh;
+ ARGB32 * bmp = loadRrc(IDR_IMAGE_NOTFOUND,"PNG",&rw,&rh,true);
+ classicnotfoundW = rw;
+ classicnotfoundH = rh;
+ INT color[] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, };
+
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i]) WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+ classicnotfound[i] = NULL;
+
+ if(bmp)
+ {
+ if (0 != i)
+ {
+ classicnotfound[i] = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*rw*rh);
+ CopyMemory(classicnotfound[i], bmp, sizeof(ARGB32)*rw*rh);
+ }
+ else
+ classicnotfound[i] = bmp;
+
+ adjustbmp(classicnotfound[i],rw*rh, GetWAColor(color[i]));
+ }
+ }
+
+ if(WM_DISPLAYCHANGE == uMsg)
+ PostMessageW(GetDlgItem(hwndDlg,dlgitem),uMsg,wParam,lParam);
+ else
+ ShowWindow(GetDlgItem(hwndDlg,dlgitem), SW_SHOWNORMAL);
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i])
+ WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+
+ classicnotfound[i] = NULL;
+ }
+ break;
+
+ case WM_MEASUREITEM:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ ((MEASUREITEMSTRUCT*)lParam)->itemHeight = itemHeight;
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+ break;
+
+ case WM_USER+600:
+ AppendMenuW((HMENU)wParam,MF_SEPARATOR,lParam++,0);
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ //AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_GET_ALBUM_ART));
+ AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_REFRESH_ALBUM_ART));
+ AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_OPEN_FOLDER));
+ AppendMenuW((HMENU)wParam,MF_STRING,lParam++,WASABI_API_LNGSTRINGW(IDS_MORE_ARTIST_INFO));
+ break;
+
+ case WM_USER+601:
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ //RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ RemoveMenu((HMENU)wParam,lParam++,MF_BYCOMMAND);
+ break;
+
+ case WM_USER+602:
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ #if 0
+ if (lParam == 1)
+ {
+ int c = list->GetCount();
+ for (int x = 0; x < c; x ++)
+ {
+ if(list->GetSelected(x) && albumList.Items[x+1].art)
+ {
+ artFetchData d = {sizeof(d),hwndDlg,albumList.Items[x+1].artist,albumList.Items[x+1].name,0};
+ d.gracenoteFileId = albumList.Items[x+1].gracenoteFileId;
+ d.showCancelAll = 1;
+ int r = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(LPARAM)&d,IPC_FETCH_ALBUMART);
+ if(r == -2) break; // cancel all was pressed
+ if(r == 0 && d.imgData && d.imgDataLen) // success, save art in correct location
+ {
+ AGAVE_API_ALBUMART->SetAlbumArt(albumList.Items[x+1].art->filename,L"cover",0,0,d.imgData,d.imgDataLen,d.type);
+ WASABI_API_MEMMGR->sysFree(d.imgData);
+ // clear the cache...
+ ClearCache(albumList.Items[x+1].art->filename);
+ albumList.Items[x+1].art->updateMsg.hwnd=0;
+ albumList.Items[x+1].art->Reset();
+ SendMessage(GetDlgItem(hwndDlg,dlgitem),LVM_REDRAWITEMS,x,x);
+ }
+ }
+ }
+ }
+ else
+ #endif
+ if(lParam == 1)
+ {
+ int c = list->GetCount();
+ for (int x = 0; x < c; x ++)
+ {
+ if(list->GetSelected(x) && albumList.Items[x+1].art)
+ {
+ ClearCache(albumList.Items[x+1].art->filename);
+ albumList.Items[x+1].art->updateMsg.hwnd=0;
+ albumList.Items[x+1].art->Reset();
+ SendMessage(GetDlgItem(hwndDlg,dlgitem),LVM_REDRAWITEMS,x,x);
+ }
+ }
+ }
+ else if(lParam == 2)
+ {
+ int opened=0;
+ int c = list->GetCount();
+ for (int x = 0; x < c; x ++)
+ {
+ if((ListView_GetItemState(list->getwnd(), x, LVIS_FOCUSED)&LVIS_FOCUSED) && albumList.Items[x+1].art)
+ {
+ // TODO change to use the explorer api
+ if(opened++ >= 10) break; // that's enough! Opening 400 exploerer windows may seem like fun, but windows _hates_ it.
+ wchar_t fn[MAX_PATH] = {0};
+ lstrcpynW(fn,albumList.Items[x+1].art->filename,MAX_PATH);
+ PathRemoveFileSpecW(fn);
+ ShellExecuteW(NULL,L"open",fn,NULL,NULL,SW_SHOW);
+ }
+ }
+ }
+ else if (lParam == 3)
+ {
+ int sel = list->GetNextSelected();
+ if (sel != LB_ERR)
+ {
+ char url[1024] = {0};
+ StringCchPrintfA(url, 1024, "http://client.winamp.com/nowplaying/artist?artistName=%s&icid=localmediagetartistinfo", AutoUrl(albumList.Items[sel+1].artist));
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(LPARAM)url,IPC_OPEN_URL);
+ }
+ }
+ break;
+
+ case WM_USER+700: // hit-info
+ {
+ DWORD hotold = ratingHotItem;
+ int hotoldval = ratingHotValue;
+ ratingHotItem = (DWORD)-1;
+
+ typedef struct {
+ int x,y,item;
+ HWND hwnd;
+ UINT msg;
+ } hitinfo;
+
+ hitinfo * info = (hitinfo *)wParam;
+ if(mode && info->hwnd == hwndRealList && info->item >= 0 && info->item < albumList.Size)
+ {
+ int w = 0, h = 0;
+ getImgSize(mode,w,h);
+ RATINGHITTESTPARAMS ratingHitTest = {sizeof(ratingHitTest),{info->x,info->y},{16+w+namesWidth,3+textHeight*ratingrow,0,0},5,RDS_NORMAL,NULL,-1,(UINT)-1};
+ ratingHitTest.rc.bottom = ratingHitTest.rc.top + textHeight;
+ ratingHitTest.rc.right = ratingHitTest.rc.left + 200;
+
+ LONG hitVal = MLRating_HitTest(plugin.hwndLibraryParent,&ratingHitTest);
+
+ ratingHitTest.rc.left -= 10; //this 10px is the area in which you can click to set a zero rating (see the PtInRect call below)
+
+ if(!hitVal && !PtInRect(&ratingHitTest.rc, ratingHitTest.pt))
+ {
+ if(hotold >=0) { ReleaseCapture(); ListView_RedrawItems(hwndRealList,hotold,hotold); }
+ break;
+ }
+
+ if(info->msg == WM_MOUSEMOVE)
+ {
+ ratingHotValue = hitVal;
+ ratingHotItem = info->item;
+ if(hotold != ratingHotItem || hotoldval != ratingHotValue)
+ {
+ ListView_RedrawItems(hwndRealList,info->item,info->item);
+ if(hotold >=0 && hotold != ratingHotItem)
+ ListView_RedrawItems(hwndRealList,hotold,hotold);
+
+ SetCapture(hwndRealList);
+ }
+ }
+ else if(info->msg == WM_LBUTTONDOWN)
+ {
+ /*
+ This is a slight race condition. We are part of the WM_MOUSEMOVE message in the listview. Selection state hasn't changed yet.
+ By doing a PostMessage, we're betting that by the time that the message gets processed, selections state HAS changed.
+ Haven't managed to make it misbehave yet though.
+ (Passing the this pointer is not dangerous, because we check it on the other side :)
+ */
+ PostMessage(hwndDlg,WM_USER+710,hitVal,(LPARAM)this);
+ ReleaseCapture();
+ }
+ else if(hotold >=0) { ReleaseCapture(); ListView_RedrawItems(hwndRealList,hotold,hotold); }
+ }
+ else if(hotold >=0) { ReleaseCapture(); ListView_RedrawItems(hwndRealList,hotold,hotold); }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ BOOL bProcessed(FALSE);
+ LRESULT result(0);
+ switch (((NMHDR*)lParam)->code)
+ {
+ case LVN_KEYDOWN: bProcessed = OnKeyDown((NMLVKEYDOWN*)lParam); break;
+ case NM_CUSTOMDRAW:
+ bProcessed = (isDetailsMode(mode)) ? OnCustomDrawDetails((NMLVCUSTOMDRAW*)lParam, &result) : OnCustomDrawIcon((NMLVCUSTOMDRAW*)lParam, &result);
+ break;
+ case LVN_GETDISPINFOW:
+ return TRUE;
+ case LVN_GETINFOTIPW:
+ {
+ const wchar_t *p = GetText(((NMLVGETINFOTIPW*)lParam)->iItem);
+ if (p && *p && lstrlenW(p) > itemHeight) // we use itemHeight to write number of average characters that fits label in icon mode
+ {
+ StringCchCopyW(((NMLVGETINFOTIPW*)lParam)->pszText, ((NMLVGETINFOTIPW*)lParam)->cchTextMax, p);
+ }
+ return TRUE;
+ }
+ case LVN_EX_SIZECHANGED:
+ {
+ INT hint = (INT)SendMessageW(((NMHDR*)lParam)->hwndFrom, WM_EX_GETCOUNTPERPAGE, 0, 0L);
+ if (hint > 0) HintCacheSize(hint);
+ }
+ return TRUE;
+ }
+
+ if (bProcessed) SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return bProcessed;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int getWidth(int def, int col)
+{
+ wchar_t buf[100] = {0};
+ char * name = AlbumFilter::getColConfig(col);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"av_art_col_%hs", name);
+ return g_view_metaconf->ReadInt(buf, def);
+}
+
+void AlbumArtFilter::AddColumns2()
+{
+ showncolumns.push_back(new ListField(7,getWidth(90,7),IDS_ARTIST,g_view_metaconf,"",false,false,false,0));
+ showncolumns.push_back(new ListField(0,getWidth(90,0),IDS_ALBUM,g_view_metaconf,"",false,false,false,1));
+ showncolumns.push_back(new ListField(3,getWidth(50,3),IDS_TRACKS_MENU,g_view_metaconf,"",false,false,false,2));
+ showncolumns.push_back(new ListField(1,getWidth(50,1),IDS_YEAR,g_view_metaconf,"",false,false,false,3));
+ if (mode != 0 && mode != 3)
+ {
+ showncolumns.push_back(new ListField(8,getWidth(50,8),IDS_GENRE,g_view_metaconf,"",false,false,false,4));
+ showncolumns.push_back(new ListField(9,getWidth(50,9),IDS_RATING,g_view_metaconf,"",false,false,false,5));
+ if (mode != 1 && mode != 4)
+ {
+ showncolumns.push_back(new ListField(6,getWidth(50,6),IDS_LENGTH,g_view_metaconf,"",false,false,false,6));
+ showncolumns.push_back(new ListField(5,getWidth(50,5),IDS_SIZE,g_view_metaconf,"",false,false,false,7));
+ }
+ }
+ showncolumns.push_back(new ListField(10,getWidth(90,10),IDS_LAST_UPDATED,g_view_metaconf,"",false,false,false,8));
+}
+
+static const wchar_t* getArtist(const itemRecordW *p)
+{
+ if (p->albumartist) return p->albumartist;
+ else if (p->artist) return p->artist;
+ return L"";
+}
+
+static wchar_t* retainArtist(itemRecordW *p)
+{
+ if (p->albumartist)
+ {
+ ndestring_retain(p->albumartist);
+ return p->albumartist;
+ }
+ else if (p->artist)
+ {
+ ndestring_retain(p->artist);
+ return p->artist;
+ }
+ return emptyQueryListString;
+}
+
+int AlbumArtFilter::MyBuildSortFunc(const void *elem1, const void *elem2, const void *context)
+{
+ AlbumArtFilter *sortFilter = (AlbumArtFilter *)context;
+ itemRecordW *a = (itemRecordW *)elem1;
+ itemRecordW *b = (itemRecordW *)elem2;
+ int v=WCSCMP_NULLOK(getArtist(a),getArtist(b));
+ if (v)
+ return v;
+ v=WCSCMP_NULLOK(a->album,b->album);
+ if (v)
+ return v;
+ if (sortFilter->nextFilter)
+ {
+ wchar_t ab[100] = {0}, bb[100] = {0};
+ v = WCSCMP_NULLOK(sortFilter->nextFilter->GroupText(a,ab,100),sortFilter->nextFilter->GroupText(b,bb,100));
+ if (v)
+ return v;
+ }
+ v = a->track - b->track;
+ if (v)
+ return v;
+ return WCSCMP_NULLOK(a->filename,b->filename);
+}
+
+void AlbumArtFilter::Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)
+{
+ if (numitems > 1)
+ qsort_itemRecord(items,numitems,this, MyBuildSortFunc);
+
+ if (killswitch && *killswitch) return;
+
+ emptyQueryListObject(&albumList);
+ reallocQueryListObject(&albumList);
+
+ itemRecordW *p = items;
+ int n = numitems;
+ int numalbumstotal = 0;
+ wchar_t *lastartist = 0;
+ wchar_t *lastalbum = 0;
+ const wchar_t *lastalb=0;
+ wchar_t albbuf[100] = {0}, albbuf2[100] = {0};
+ ZeroMemory(&albumList.Items[0],sizeof(queryListItem));
+ int isbl = 0;
+ while (n--)
+ {
+ if (killswitch && *killswitch) return;
+ if ((!lastartist || WCSCMP_NULLOK(lastartist, getArtist(p))) || (!lastalbum || WCSCMP_NULLOK(lastalbum, p->album)))
+ {
+ albumList.Size++;
+ if (reallocQueryListObject(&albumList)) break;
+ wchar_t *albumGain = p->replaygain_album_gain;
+ ndestring_retain(albumGain);
+ wchar_t *gracenoteFileId = getRecordExtendedItem_fast(p, extended_fields.GracenoteFileID);
+ ZeroMemory(&albumList.Items[albumList.Size],sizeof(queryListItem));
+ albumList.Items[albumList.Size].albumGain = albumGain;
+ albumList.Items[albumList.Size].gracenoteFileId = gracenoteFileId;
+ ndestring_retain(gracenoteFileId);
+
+ lastartist = albumList.Items[albumList.Size].artist = retainArtist(p);
+ if (p->album)
+ {
+ ndestring_retain(p->album);
+ lastalbum = albumList.Items[albumList.Size].name = p->album;
+ }
+ else
+ lastalbum = albumList.Items[albumList.Size].name = emptyQueryListString;
+
+ lastalb=0;
+ SKIP_THE_AND_WHITESPACEW(lastalbum) // optimization technique
+ SKIP_THE_AND_WHITESPACEW(lastartist) // optimization technique
+
+ albumList.Items[albumList.Size].art = new AlbumArtContainer();
+ ndestring_retain(p->filename);
+ albumList.Items[albumList.Size].art->filename = p->filename;
+
+ if (*lastalbum) numalbumstotal++;
+ }
+ if (p->year>0)
+ {
+ int y = albumList.Items[albumList.Size].ifields[2];
+ if (y == 0) y = MAKELONG((short)p->year,(short)p->year);
+ else if (p->year > (short)LOWORD(y)) y = MAKELONG((short)p->year,(short)HIWORD(y));
+ else if (p->year < (short)HIWORD(y)) y = MAKELONG((short)LOWORD(y),(short)p->year);
+ albumList.Items[albumList.Size].ifields[2] = y;
+ }
+
+ if (!albumList.Items[albumList.Size].genre && p->genre)
+ {
+ wchar_t *genre = p->genre;
+ ndestring_retain(genre);
+ albumList.Items[albumList.Size].genre = genre;
+ }
+
+ if (p->rating > 0) albumList.Items[albumList.Size].rating += p->rating;
+
+ if (p->lastupd > albumList.Items[albumList.Size].lastupd) albumList.Items[albumList.Size].lastupd = p->lastupd;
+
+ if (!p->album || !*p->album) isbl++;
+ if (albumList.Size)
+ {
+ albumList.Items[albumList.Size].ifields[1]++;
+ if (p->length>0) albumList.Items[albumList.Size].length += p->length;
+ if (p->filesize>0) albumList.Items[albumList.Size].size += p->filesize;
+ }
+ if (nextFilter && (!lastalb || WCSCMP_NULLOK(lastalb,nextFilter->GroupText(p,albbuf2,100))))
+ {
+ lastalb = nextFilter->GroupText(p,albbuf,100);
+ if (lastalb && *lastalb) albumList.Items[albumList.Size].ifields[0]++;
+ if (lastalb) SKIP_THE_AND_WHITESPACEW(lastalb) // optimization technique
+ }
+ p++;
+ }
+ wchar_t buf[64] = {0}, sStr[16] = {0}, langBuf[64] = {0};
+ if (isbl)
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM, langBuf, 64), albumList.Size - 1, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 2 ? IDS_ALBUM : IDS_ALBUMS,sStr,16), isbl);
+ }
+ else
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS, langBuf, 64), albumList.Size, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 1 ? IDS_ALBUM : IDS_ALBUMS,sStr,16));
+ }
+ albumList.Items[0].name = ndestring_wcsdup(buf);
+ albumList.Items[0].ifields[1] = numitems;
+ albumList.Items[0].ifields[0] = numitems2;
+ albumList.Size++;
+ numGroups = numalbumstotal;
+}
+
+bool AlbumArtFilter::MakeFilterQuery(int x, GayStringW *query)
+{
+ queryListItem * l = &albumList.Items[x+1];
+ // this mess appends this query:
+ // (album="l->album" && ((albumartist isempty && artist="l->artist") || (albumartist isnotempty && albumartist="l->artist")))
+ query->Append(L"(album=\"");
+ GayStringW escaped;
+ queryStrEscape(l->name, escaped);
+ query->Append(escaped.Get());
+ escaped.Set(L"");
+ query->Append(L"\" && ((albumartist isempty && artist LIKE \"");
+ queryStrEscape(l->artist, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\") || (albumartist isnotempty && albumartist LIKE \"");
+ query->Append(escaped.Get());
+ query->Append(L"\")))");
+ return true;
+}
+
+HMENU AlbumArtFilter::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu)
+{
+ HMENU menu = GetSubMenu(themenu, 6);
+
+ int checked=0;
+ switch (mode)
+ {
+ case 0: checked=ID_ARTHEADERWND_SMALLDETAILS; break;
+ case 1: checked=ID_ARTHEADERWND_MEDIUMDETAILS; break;
+ case 2: checked=ID_ARTHEADERWND_LARGEDETAILS; break;
+ case 3: checked=ID_ARTHEADERWND_SMALLICON; break;
+ case 4: checked=ID_ARTHEADERWND_MEDIUMICON; break;
+ case 5: checked=ID_ARTHEADERWND_LARGEICON; break;
+ case 6: checked=ID_ARTHEADERWND_EXTRALARGEICON; break;
+ }
+ CheckMenuItem(menu,checked,MF_CHECKED | MF_BYCOMMAND);
+ CheckMenuItem(menu,ID_ARTHEADERWND_SHOWTEXT,(icons_only ? MF_UNCHECKED : MF_CHECKED) | MF_BYCOMMAND);
+
+ if (isFilter)
+ {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i=0;
+ while (GetMenuItemInfo(menu,i,TRUE,&m))
+ {
+ m.wID |= (1+filterNum) << 16;
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ i++;
+ }
+ }
+ return menu;
+}
+
+void AlbumArtFilter::ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent)
+{
+ int mid = (r >> 16), newMode;
+ BOOL updateChache = FALSE;
+ BOOL iconsOnly = FALSE;
+ if (!isFilter && mid) return;
+ if (isFilter && mid-1 != filterNum) return;
+
+ switch (LOWORD(r))
+ {
+ case ID_ARTHEADERWND_SMALLDETAILS: newMode=0; break;
+ case ID_ARTHEADERWND_MEDIUMDETAILS: newMode=1; break;
+ case ID_ARTHEADERWND_LARGEDETAILS: newMode=2; break;
+ case ID_ARTHEADERWND_SMALLICON: newMode=3; break;
+ case ID_ARTHEADERWND_MEDIUMICON: newMode=4; break;
+ case ID_ARTHEADERWND_LARGEICON: newMode=5; break;
+ case ID_ARTHEADERWND_EXTRALARGEICON:newMode=6; break;
+ case ID_ARTHEADERWND_SHOWTEXT:
+ {
+ newMode = mode;
+ icons_only = !icons_only;
+ // only update view if in icon view
+ if (!isDetailsMode(mode)) {
+ iconsOnly = TRUE;
+ }
+ }
+ break;
+ default: return;
+ }
+
+ if (mode == newMode && iconsOnly == FALSE) return;
+
+ if (!iconsOnly) {
+ int w1, w2, h1, h2;
+ getImgSize(mode, w1, h1);
+ getImgSize(newMode, w2, h2);
+ updateChache = (w1 != w2 || h1 != h2);
+ mode = newMode;
+ }
+
+ c->WriteInt(L"albumartviewmode",mode);
+ c->WriteInt(L"albumarticonmode",icons_only);
+ SaveColumnWidths();
+ while (ListView_DeleteColumn(list->getwnd(), 0));
+ if (updateChache) FlushCache();
+
+ for (int i=0;i!=albumList.Size;i++)
+ {
+ if (albumList.Items[i].art)
+ {
+ albumList.Items[i].art->Reset();
+ albumList.Items[i].art->updateMsg.hwnd=0;
+ }
+ }
+
+ DialogProc(parent,WM_INITDIALOG,0,0);
+ if (updateChache) ResumeCache();
+ HWND hwndList = GetDlgItem(parent,dlgitem);
+ if (hwndList)
+ {
+ MLSkinnedWnd_SkinChanged(hwndList, TRUE, TRUE);
+ list->setwnd(hwndList);
+ AddColumns();
+ CalcuateItemHeight();
+ ListView_SetItemCount(hwndList, Size());
+ }
+}
+
+void AlbumArtFilter::MetaUpdate(int r, const wchar_t *metaItem, const wchar_t *value)
+{
+ if(_wcsicmp(metaItem, DB_FIELDNAME_rating )==0)
+ albumList.Items[r+1].rating = _wtoi(value) * albumList.Items[r+1].ifields[1];
+
+ SendMessage(GetDlgItem(hwndDlg,dlgitem),LVM_REDRAWITEMS,r,r);
+ UpdateWindow(GetDlgItem(hwndDlg,dlgitem));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumArtFilter.h b/Src/Plugins/Library/ml_local/AlbumArtFilter.h
new file mode 100644
index 00000000..b3a1439c
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumArtFilter.h
@@ -0,0 +1,64 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMART_FILTER_H_
+#define NULLSOFT_ML_LOCAL_ALBUMART_FILTER_H_
+
+#include "AlbumFilter.h"
+#include <tataki/bitmap/autobitmap.h>
+
+class AlbumArtFilter : public AlbumFilter
+{
+public:
+ AlbumArtFilter(HWND hwndDlg, int dlgitem, C_Config *c);
+ virtual ~AlbumArtFilter();
+ virtual char * GetConfigId(){return "av_art";}
+ virtual int Size() { return albumList.Size - 1; }
+ virtual const wchar_t *GetText(int row) { return AlbumFilter::GetText(row+1); }
+ virtual void CopyText(int row, size_t column, wchar_t *dest, int destCch) { return AlbumFilter::CopyText(row+1,column,dest,destCch); }
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch) { return AlbumFilter::CopyText2(row+1,column,dest,destCch); }
+
+ virtual INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ AlbumArtContainer * GetArt(int row) { return albumList.Items[row].art; }
+
+ BOOL DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive);
+ BOOL PrepareDetails(HDC hdc);
+ BOOL DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNaes, INT namesWidth);
+ void drawArt(AlbumArtContainer *art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex);
+
+ void AddColumns2();
+ void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2);
+ virtual bool HasTopItem() {return false;}
+ static int __fastcall MyBuildSortFunc(const void *elem1, const void *elem2, const void *context);
+ virtual bool MakeFilterQuery(int x, GayStringW *query);
+
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+
+ virtual void MetaUpdate(int r, const wchar_t *metaItem, const wchar_t *value);
+
+protected:
+ BOOL OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnKeyDown(NMLVKEYDOWN *plvkd);
+ BOOL CalcuateItemHeight(void);
+protected:
+ DWORD ratingHotItem;
+ int ratingHotValue;
+ int dlgitem;
+ HWND hwndDlg;
+ HWND hwndRealList;
+ int mode;
+ int icons_only;
+ AutoSkinBitmap notfound;
+ AutoSkinBitmap notfound60;
+ AutoSkinBitmap notfound90;
+ ARGB32 * classicnotfound[3];
+ int classicnotfoundW,classicnotfoundH;
+ INT ratingrow;
+ HBITMAP hbmpNames;
+ INT itemHeight;
+ INT textHeight;
+ INT ratingTop;
+ INT namesWidth;
+ HBRUSH bgBrush;
+};
+
+#endif // NULLSOFT_ML_LOCAL_ALBUMART_FILTER_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumFilter.cpp b/Src/Plugins/Library/ml_local/AlbumFilter.cpp
new file mode 100644
index 00000000..af414d2d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumFilter.cpp
@@ -0,0 +1,496 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "../nu/sort.h"
+#include "AlbumFilter.h"
+#include "resource.h"
+#include <shlwapi.h>
+
+static size_t m_sort_by, m_sort_dir, m_sort_which;
+
+void emptyQueryListObject(queryListObject *obj);
+int reallocQueryListObject(queryListObject *obj);
+void freeQueryListObject(queryListObject *obj);
+
+
+int AlbumFilter::AlbumSortFunc(const void *elem1, const void *elem2)
+{
+ const queryListItem *a = (const queryListItem*)elem1;
+ const queryListItem *b = (const queryListItem*)elem2;
+ int use_by = m_sort_by;
+ int use_dir = m_sort_dir;
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+ if (use_by == ALBUMFILTER_COLUMN_NAME)
+ {
+ int v = WCSCMP_NULLOK(a->name, b->name);
+ RETIFNZ(v)
+ v = b->ifields[2] - a->ifields[2];
+ RETIFNZ(v)
+ v = b->ifields[1] - a->ifields[1];
+ return v;
+ }
+ if (use_by == ALBUMFILTER_COLUMN_YEAR)
+ {
+ int v = a->ifields[2] - b->ifields[2];
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+ if (use_by == ALBUMFILTER_COLUMN_ALBUMS)
+ {
+ int v = a->ifields[0] - b->ifields[0];
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+ if (use_by == ALBUMFILTER_COLUMN_TRACKS)
+ {
+ int v = a->ifields[1] - b->ifields[1];
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if (use_by == ALBUMFILTER_COLUMN_REPLAYGAIN)
+ {
+ int v = FLOATWCMP_NULLOK(a->albumGain, b->albumGain);
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if(use_by == ALBUMFILTER_COLUMN_SIZE)
+ {
+ __int64 v = a->size - b->size;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if(use_by == ALBUMFILTER_COLUMN_LENGTH)
+ {
+ int v = a->length - b->length;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+ if(use_by == ALBUMFILTER_COLUMN_RATING) {
+ int ar = 0;
+ if(a->ifields[1]) ar = a->rating/a->ifields[1];
+ int br = 0;
+ if(b->ifields[1]) br = b->rating/b->ifields[1];
+ int v = ar - br;
+ RETIFNZ(v)
+ }
+ if(use_by == ALBUMFILTER_COLUMN_ARTIST || use_by == ALBUMFILTER_COLUMN_RATING) {
+ int v = WCSCMP_NULLOK(a->artist, b->artist);
+ RETIFNZ(v)
+ v = WCSCMP_NULLOK(a->name, b->name);
+ RETIFNZ(v)
+ }
+ else if(use_by == ALBUMFILTER_COLUMN_GENRE) {
+ int v = WCSCMP_NULLOK(a->genre, b->genre);
+ RETIFNZ(v)
+ v = WCSCMP_NULLOK(a->artist, b->artist);
+ RETIFNZ(v)
+ v = WCSCMP_NULLOK(a->name, b->name);
+ RETIFNZ(v)
+ }
+ if(use_by == ALBUMFILTER_COLUMN_LASTUPD)
+ {
+ int v = (int)(b->lastupd - a->lastupd);
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+#undef RETIFNZ
+ return 0;
+}
+
+void AlbumFilter::SortResults(C_Config *viewconf, int which, int isfirst) // sorts the results based on the current sort mode
+{
+ if (viewconf)
+ {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d", GetConfigId(), which);
+ m_sort_by = viewconf->ReadInt(buf, 0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_dir_%d", GetConfigId(), which);
+ m_sort_dir = viewconf->ReadInt(buf, 0);
+ m_sort_which = which;
+
+ if(showncolumns.size()>m_sort_by && m_sort_by>=0) m_sort_by = showncolumns[m_sort_by]->field;
+
+ if (m_sort_dir == 0 && m_sort_by == 0 && isfirst) return;
+
+ if (albumList.Size > 2) qsort(albumList.Items+1,albumList.Size-1,sizeof(queryListItem),AlbumSortFunc);
+ }
+}
+
+void AlbumFilter::Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)
+{
+ if (numitems > 1)
+ qsort_itemRecord(items,numitems, this, BuildSortFunc);
+
+ if (killswitch && *killswitch) return;
+
+ emptyQueryListObject(&albumList);
+ reallocQueryListObject(&albumList);
+
+ itemRecordW *p = items;
+ int n = numitems;
+ int numalbumstotal = 0;
+ wchar_t *lastname = 0;
+ const wchar_t *lastalb=0;
+ wchar_t albbuf[100] = {0}, albbuf2[100] = {0};
+ ZeroMemory(&albumList.Items[0],sizeof(queryListItem));
+ int isbl = 0;
+ while (n--)
+ {
+ if (killswitch && *killswitch) return;
+ if (!lastname || WCSCMP_NULLOK(lastname, p->album))
+ {
+ albumList.Size++;
+ if (reallocQueryListObject(&albumList)) break;
+ wchar_t *albumGain = p->replaygain_album_gain;
+ ndestring_retain(albumGain);
+ wchar_t *gracenoteFileId = getRecordExtendedItem_fast(p, extended_fields.GracenoteFileID);
+ ZeroMemory(&albumList.Items[albumList.Size],sizeof(queryListItem));
+ albumList.Items[albumList.Size].albumGain = albumGain;
+ albumList.Items[albumList.Size].gracenoteFileId = gracenoteFileId;
+ ndestring_retain(gracenoteFileId);
+
+ if (p->album)
+ {
+ ndestring_retain(p->album);
+ lastname = albumList.Items[albumList.Size].name = p->album;
+ }
+ else
+ lastname = albumList.Items[albumList.Size].name = emptyQueryListString;
+
+ lastalb=0;
+ SKIP_THE_AND_WHITESPACEW(lastname) // optimization technique
+ if(*lastname) numalbumstotal++;
+ }
+ if(p->year>0) {
+ int y = albumList.Items[albumList.Size].ifields[2];
+ if(y == 0) y = MAKELONG((short)p->year,(short)p->year);
+ else if(p->year > (short)LOWORD(y)) y = MAKELONG((short)p->year,(short)HIWORD(y));
+ else if(p->year < (short)HIWORD(y)) y = MAKELONG((short)LOWORD(y),(short)p->year);
+ albumList.Items[albumList.Size].ifields[2] = y;
+ }
+
+ if (!p->album || !*p->album) isbl++;
+ if (albumList.Size) {
+ albumList.Items[albumList.Size].ifields[1]++;
+ if(p->length>0) albumList.Items[albumList.Size].length += p->length;
+ if(p->filesize>0) albumList.Items[albumList.Size].size += p->filesize;
+ }
+ if (nextFilter && (!lastalb || WCSCMP_NULLOK(lastalb,nextFilter->GroupText(p,albbuf2,100))))
+ {
+ lastalb = nextFilter->GroupText(p,albbuf,100);
+ if(lastalb && *lastalb) albumList.Items[albumList.Size].ifields[0]++;
+ if (lastalb) SKIP_THE_AND_WHITESPACEW(lastalb) // optimization technique
+ }
+ p++;
+ }
+ wchar_t buf[64] = {0}, sStr[16] = {0}, langBuf[64] = {0};
+ if (isbl)
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM, langBuf, 64), albumList.Size - 1, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 2 ? IDS_ALBUM : IDS_ALBUMS,sStr,16), isbl);
+ }
+ else
+ {
+ wsprintfW(buf, WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_ALBUMS, langBuf, 64), albumList.Size, WASABI_API_LNGSTRINGW_BUF(albumList.Size == 1 ? IDS_ALBUM : IDS_ALBUMS,sStr,16));
+ }
+
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(buf);
+
+ albumList.Items[0].name = ndestring_wcsdup(buf);
+ albumList.Items[0].ifields[1] = numitems;
+ albumList.Items[0].ifields[0] = numitems2;
+ albumList.Size++;
+ numGroups = numalbumstotal;
+}
+
+int AlbumFilter::Size()
+{
+ return albumList.Size;
+}
+
+const wchar_t *AlbumFilter::GetText(int row) // gets main text (first column)
+{
+ return (row < albumList.Size) ? albumList.Items[row].name : NULL;
+}
+
+void AlbumFilter::Empty()
+{
+ freeQueryListObject(&albumList);
+}
+
+
+void AlbumFilter::CopyText(int row, size_t column, wchar_t *dest, int destCch)
+{
+ if(column >= showncolumns.size()) return;
+ column = showncolumns[column]->field;
+ if (row>=albumList.Size)
+ return ;
+ switch(column)
+ {
+ case ALBUMFILTER_COLUMN_NAME: // name
+ if (albumList.Items[row].name && *albumList.Items[row].name)
+ lstrcpynW(dest,albumList.Items[row].name,destCch);
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ALBUM,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_YEAR: // year
+ if (HIWORD(albumList.Items[row].ifields[2]) < 1)
+ dest[0] = 0;
+ else {
+ int y = albumList.Items[row].ifields[2];
+ if(HIWORD(y) == LOWORD(y)) StringCchPrintfW(dest, destCch, L"%d", HIWORD(y));
+ else if(HIWORD(y)/100 == LOWORD(y)/100) StringCchPrintfW(dest, destCch, L"%d-%02d", HIWORD(y),LOWORD(y)%100);
+ else StringCchPrintfW(dest, destCch, L"%d-%d", HIWORD(y),LOWORD(y));
+ }
+ break;
+ case ALBUMFILTER_COLUMN_ALBUMS: // albums
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[0]);
+ break;
+ case ALBUMFILTER_COLUMN_TRACKS: // tracks
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[1]);
+ break;
+ case ALBUMFILTER_COLUMN_REPLAYGAIN: // album replay gain
+ if (albumList.Items[row].albumGain)
+ lstrcpynW(dest, albumList.Items[row].albumGain, destCch);
+ else
+ dest[0] = 0;
+ break;
+ case ALBUMFILTER_COLUMN_SIZE: // size
+ if(row && albumList.Items[row].size) {
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, albumList.Items[row].size);
+ } else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_LENGTH: // length
+ if(row && albumList.Items[row].length)
+ StringCchPrintfW(dest, destCch,L"%d:%02d", albumList.Items[row].length / 60, albumList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_ARTIST: // artist
+ if(row && albumList.Items[row].artist)
+ lstrcpynW(dest,albumList.Items[row].artist,destCch);
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ARTIST,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_GENRE: // genre
+ if(row && albumList.Items[row].genre)
+ lstrcpynW(dest,albumList.Items[row].genre,destCch);
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_GENRE,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_RATING: // rating
+ dest[0]=0;
+ if(row && albumList.Items[row].rating > 0 && albumList.Items[row].ifields[1]) {
+ int r = albumList.Items[row].rating / albumList.Items[row].ifields[1];
+ if(r >=0 && r<=5) {
+ wchar_t ra[]=L"*****";
+ ra[r]=0;
+ StringCchPrintfW(dest, destCch,L"%d",ra);
+ }
+ }
+ break;
+ case ALBUMFILTER_COLUMN_LASTUPD: // last updated
+ dest[0]=0;
+ if(row && albumList.Items[row].lastupd)
+ {
+ StringCchPrintfW(dest, destCch,L"%d", albumList.Items[row].lastupd);
+ }
+ else dest[0]=0;
+ break;
+ }
+}
+const wchar_t *AlbumFilter::CopyText2(int row, size_t column, wchar_t *dest, int destCch)
+{
+ const wchar_t *cdest=dest;
+ if(column >= showncolumns.size()) return NULL;
+ column = showncolumns[column]->field;
+ if (row>=albumList.Size)
+ return NULL;
+ switch(column)
+ {
+ case ALBUMFILTER_COLUMN_NAME: // name
+ if (albumList.Items[row].name && *albumList.Items[row].name)
+ cdest = albumList.Items[row].name;
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ALBUM,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_YEAR: // year
+ if (HIWORD(albumList.Items[row].ifields[2]) < 1)
+ dest[0] = 0;
+ else {
+ int y = albumList.Items[row].ifields[2];
+ if(HIWORD(y) == LOWORD(y)) StringCchPrintfW(dest, destCch, L"%d", HIWORD(y));
+ else if(HIWORD(y)/100 == LOWORD(y)/100) StringCchPrintfW(dest, destCch, L"%d-%02d", HIWORD(y),LOWORD(y)%100);
+ else StringCchPrintfW(dest, destCch, L"%d-%d", HIWORD(y),LOWORD(y));
+ }
+ break;
+ case ALBUMFILTER_COLUMN_ALBUMS: // albums
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[0]);
+ break;
+ case ALBUMFILTER_COLUMN_TRACKS: // tracks
+ StringCchPrintfW(dest, destCch,L"%d",albumList.Items[row].ifields[1]);
+ break;
+ case ALBUMFILTER_COLUMN_REPLAYGAIN: // album replay gain
+ if (albumList.Items[row].albumGain)
+ cdest = albumList.Items[row].albumGain;
+ else
+ dest[0] = 0;
+ break;
+ case ALBUMFILTER_COLUMN_SIZE: // size
+ if(row && albumList.Items[row].size) {
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, albumList.Items[row].size);
+ } else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_LENGTH: // length
+ if(row && albumList.Items[row].length)
+ StringCchPrintfW(dest, destCch,L"%d:%02d", albumList.Items[row].length / 60, albumList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ case ALBUMFILTER_COLUMN_ARTIST: // artist
+ if(row && albumList.Items[row].artist)
+ cdest = albumList.Items[row].artist;
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_ARTIST,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_GENRE: // genre
+ if(row && albumList.Items[row].genre)
+ cdest = albumList.Items[row].genre;
+ else
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_GENRE,dest,destCch);
+ break;
+ case ALBUMFILTER_COLUMN_RATING: // rating
+ dest[0]=0;
+ if(row && albumList.Items[row].rating > 0 && albumList.Items[row].ifields[1]) {
+ double d = double(albumList.Items[row].rating) / double(albumList.Items[row].ifields[1]);
+ int r = int(d);
+ if(d - double(r) >= 0.5 && r<5) r++;
+ if(r >=0 && r<=5) {
+ wchar_t ra[]=L"*****";
+ ra[r]=0;
+ StringCchPrintfW(dest, destCch,L"%s",ra);
+ }
+ }
+ break;
+ case ALBUMFILTER_COLUMN_LASTUPD: // last updated
+ dest[0]=0;
+ if(row && albumList.Items[row].lastupd)
+ {
+ StringCchPrintfW(dest, destCch,L"%d", albumList.Items[row].lastupd);
+ }
+ else dest[0]=0;
+ break;
+ }
+ return cdest;
+}
+const wchar_t *AlbumFilter::GetField()
+{
+ return DB_FIELDNAME_album;
+}
+
+const wchar_t *AlbumFilter::GetName()
+{
+ return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUMS,name,64);
+}
+
+const wchar_t *AlbumFilter::GetNameSingular()
+{
+ return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM,sing_name,64);
+}
+
+const wchar_t *AlbumFilter::GetNameSingularAlt(int mode)
+{
+ return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ALBUM_ALT:(mode==1?2064:2080)),sing_name_alt,64);
+}
+
+char * AlbumFilter::getColConfig(int i) {
+ switch (i) {
+ case 0: return "album";
+ case 1: return "year";
+ case 2: return "nf";
+ case 3: return "tracks";
+ case 4: return "albumgain";
+ case 5: return "size";
+ case 6: return "length";
+ case 7: return "artist";
+ case 8: return "genre";
+ case 9: return "rating";
+ case 10: return "lastupd";
+ }
+ return "";
+}
+
+static void readConf(C_Config * c, int col, int * width, int * pos, bool * hidden, int defwidth, int defpos, bool defhidden, char * confid) {
+ char * name = AlbumFilter::getColConfig(col);
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs", confid, name);
+ *width = c->ReadInt(buf, defwidth);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_pos", confid, name);
+ *pos = c->ReadInt(buf, defpos);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_hidden", confid, name);
+ *hidden = !!c->ReadInt(buf, defhidden);
+}
+
+static void saveConf(C_Config * c, int col, int defwidth, int defpos, bool defhidden, bool exists, char * confid) {
+ char * name = AlbumFilter::getColConfig(col);
+ wchar_t buf[100] = {0};
+ if(exists) {
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs", confid, name);
+ c->WriteInt(buf, defwidth);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_pos", confid, name);
+ c->WriteInt(buf, defpos);
+ }
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%hs_hidden", confid, name);
+ c->WriteInt(buf, defhidden);
+}
+
+void AlbumFilter::AddColumns2() {
+ int width, pos;
+ bool hidden;
+ char * config = GetConfigId();
+ readConf(g_view_metaconf,0,&width,&pos,&hidden,155,0,false,config);
+ showncolumns.push_back(new ListField(0,width,IDS_ALBUM,g_view_metaconf,"",hidden,false,false,pos));
+ readConf(g_view_metaconf,1,&width,&pos,&hidden,47,1,false,config);
+ showncolumns.push_back(new ListField(1,width,IDS_YEAR,g_view_metaconf,"",hidden,false,false,pos));
+ readConf(g_view_metaconf,2,&width,&pos,&hidden,48,2,false,config);
+ if(nextFilter)
+ showncolumns.push_back(new ListField(2,width,nextFilter->GetName(),g_view_metaconf,"",hidden,false,false,pos));
+ readConf(g_view_metaconf,3,&width,&pos,&hidden,47,3,false,config);
+ showncolumns.push_back(new ListField(3,width,IDS_TRACKS_MENU,g_view_metaconf,"",hidden,false,false,pos));
+
+ readConf(g_view_metaconf,4,&width,&pos,&hidden,77,4,!g_config->ReadInt(L"show_albumgain", 0),config);
+ showncolumns.push_back(new ListField(4,width,IDS_ALBUM_GAIN,g_view_metaconf,"",hidden,true,false,pos));
+
+ readConf(g_view_metaconf,5,&width,&pos,&hidden,45,5,true,config);
+ showncolumns.push_back(new ListField(5,width,IDS_SIZE,g_view_metaconf,"",hidden,true,false,pos));
+ readConf(g_view_metaconf,6,&width,&pos,&hidden,45,6,true,config);
+ showncolumns.push_back(new ListField(6,width,IDS_LENGTH,g_view_metaconf,"",hidden,true,false,pos));
+}
+
+
+void AlbumFilter::SaveColumnWidths()
+{
+ char *config = GetConfigId();
+ for ( size_t i = 0; i < showncolumns.size(); i++ )
+ {
+ int field = showncolumns[ i ]->field;
+ saveConf( g_view_metaconf, field, list->GetColumnWidth( i ), i, 0, list->ColumnExists( i ), config );
+ }
+ for ( size_t i = 0; i < hiddencolumns.size(); i++ )
+ {
+ int field = hiddencolumns[ i ]->field;
+ saveConf( g_view_metaconf, field, 0, 0, true, false, config );
+ }
+}
+
+const wchar_t * AlbumFilter::GroupText(itemRecordW * item, wchar_t * buf, int len) {
+ return item->album;
+}
+
+void AlbumFilter::CustomizeColumns(HWND parent, BOOL showmenu) {
+ ViewFilter::CustomizeColumns(parent,showmenu);
+ int v = 0;
+ for (size_t i = 0;i < showncolumns.size();i++) if(showncolumns[i]->field == 4) { v=1; break; }
+ g_config->WriteInt(L"show_albumgain", v);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/AlbumFilter.h b/Src/Plugins/Library/ml_local/AlbumFilter.h
new file mode 100644
index 00000000..371ee111
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/AlbumFilter.h
@@ -0,0 +1,49 @@
+#ifndef NULLSOFT_ML_LOCAL_ALBUMFILTER_H
+#define NULLSOFT_ML_LOCAL_ALBUMFILTER_H
+
+#include "ViewFilter.h"
+
+class AlbumFilter : public ViewFilter
+{
+public:
+ static int AlbumSortFunc(const void *elem1, const void *elem2);
+ void SortResults(C_Config *viewconf, int which=0, int isfirst=0);
+ void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2);
+ virtual int Size();
+ virtual const wchar_t *GetText(int row);
+ virtual void CopyText(int row, size_t column, wchar_t *dest, int destCch);
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch);
+ void Empty();
+ const wchar_t *GetField();
+ const wchar_t *GetName();
+ const wchar_t *GetNameSingular();
+ const wchar_t *GetNameSingularAlt(int mode);
+ void AddColumns2();
+ virtual const wchar_t * GroupText(itemRecordW * item, wchar_t * buf, int len);
+ virtual void SaveColumnWidths();
+ virtual void CustomizeColumns(HWND parent, BOOL showmenu);
+ virtual char * GetConfigId(){return "av2";}
+ static char * getColConfig(int i);
+ wchar_t name[64];
+ wchar_t sing_name[64];
+ wchar_t sing_name_alt[64];
+protected:
+ queryListObject albumList;
+
+ enum
+ {
+ ALBUMFILTER_COLUMN_NAME = 0,
+ ALBUMFILTER_COLUMN_YEAR = 1,
+ ALBUMFILTER_COLUMN_ALBUMS = 2,
+ ALBUMFILTER_COLUMN_TRACKS = 3,
+ ALBUMFILTER_COLUMN_REPLAYGAIN = 4,
+ ALBUMFILTER_COLUMN_SIZE = 5,
+ ALBUMFILTER_COLUMN_LENGTH = 6,
+ ALBUMFILTER_COLUMN_ARTIST = 7,
+ ALBUMFILTER_COLUMN_GENRE = 8,
+ ALBUMFILTER_COLUMN_RATING = 9,
+ ALBUMFILTER_COLUMN_LASTUPD = 10,
+ };
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/DBitemrecord.cpp b/Src/Plugins/Library/ml_local/DBitemrecord.cpp
new file mode 100644
index 00000000..d301ec14
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/DBitemrecord.cpp
@@ -0,0 +1,173 @@
+#include "main.h"
+#include "ml_local.h"
+
+#ifdef _M_IX86
+const size_t convert_max_characters = 16; // it's actually 11, but this probably causes less memory fragmentation
+#elif defined(_M_X64)
+const size_t convert_max_characters = 20;
+#endif
+
+bool compat_mode = false;
+
+typedef void (__fastcall *FieldFunc)(itemRecordW *obj, nde_field_t f);
+#define FIELD_FUNC(field) field ## Func
+#define STRINGFIELD_FUNC(item) static void __fastcall FIELD_FUNC(item)(itemRecordW *obj, nde_field_t f) { obj-> ## item = NDE_StringField_GetString(f); ndestring_retain(obj-> ## item); }
+#define INTFIELD_FUNC(item) static void __fastcall FIELD_FUNC(item)(itemRecordW *obj, nde_field_t f) { obj-> ## item = NDE_IntegerField_GetValue(f); }
+#define EXT_STRINGFIELD_FUNC(field) static void __fastcall FIELD_FUNC(field)(itemRecordW *obj, nde_field_t f) { setRecordExtendedItem_fast(obj, extended_fields.field, NDE_StringField_GetString(f)); }
+#define EXT_INTFIELD_FUNC(field) static void __fastcall FIELD_FUNC(field)(itemRecordW *obj, nde_field_t f) { wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t)); unsigned int v = NDE_IntegerField_GetValue(f); wsprintfW(temp, L"%u", v); setRecordExtendedItem_fast(obj, extended_fields.field, temp); ndestring_release(temp); }
+#define REALSIZE_INTFIELD_FUNC(field) static void __fastcall FIELD_FUNC(field)(itemRecordW *obj, nde_field_t f) {\
+ wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t));\
+ __int64 v = NDE_Int64Field_GetValue(f);\
+ obj-> ## field = (v > 0 ? ((int)(compat_mode ? v >> 10 : v)) : 0);\
+ wsprintfW(temp, L"%ld", v);\
+ setRecordExtendedItem_fast(obj, extended_fields.realsize, temp);\
+ ndestring_release(temp);\
+}
+STRINGFIELD_FUNC(title);
+STRINGFIELD_FUNC(artist);
+STRINGFIELD_FUNC(album);
+INTFIELD_FUNC(year);
+STRINGFIELD_FUNC(genre);
+STRINGFIELD_FUNC(comment);
+INTFIELD_FUNC(track);
+INTFIELD_FUNC(length);
+INTFIELD_FUNC(type);
+INTFIELD_FUNC(lastupd);
+INTFIELD_FUNC(lastplay);
+INTFIELD_FUNC(rating);
+INTFIELD_FUNC(playcount);
+INTFIELD_FUNC(filetime);
+// use a custom version to set 'filesize' and 'realsize' to ensure compatibility
+REALSIZE_INTFIELD_FUNC(filesize);
+INTFIELD_FUNC(bitrate);
+INTFIELD_FUNC(disc);
+STRINGFIELD_FUNC(albumartist);
+STRINGFIELD_FUNC(replaygain_album_gain);
+STRINGFIELD_FUNC(replaygain_track_gain);
+STRINGFIELD_FUNC(publisher);
+STRINGFIELD_FUNC(composer);
+INTFIELD_FUNC(bpm);
+INTFIELD_FUNC(discs);
+INTFIELD_FUNC(tracks);
+EXT_INTFIELD_FUNC(ispodcast);
+EXT_STRINGFIELD_FUNC(podcastchannel);
+EXT_INTFIELD_FUNC(podcastpubdate);
+EXT_STRINGFIELD_FUNC(GracenoteFileID);
+EXT_STRINGFIELD_FUNC(GracenoteExtData);
+EXT_INTFIELD_FUNC(lossless);
+STRINGFIELD_FUNC(category);
+EXT_STRINGFIELD_FUNC(codec);
+EXT_STRINGFIELD_FUNC(director);
+EXT_STRINGFIELD_FUNC(producer);
+EXT_INTFIELD_FUNC(width);
+EXT_INTFIELD_FUNC(height);
+EXT_STRINGFIELD_FUNC(mimetype);
+EXT_INTFIELD_FUNC(dateadded);
+
+static void __fastcall NullFieldFunction(itemRecordW *obj, nde_field_t f) {}
+static FieldFunc field_functions[] =
+{
+ NullFieldFunction, // filename
+ FIELD_FUNC(title),
+ FIELD_FUNC(artist),
+ FIELD_FUNC(album),
+ FIELD_FUNC(year),
+ FIELD_FUNC(genre),
+ FIELD_FUNC(comment),
+ FIELD_FUNC(track),
+ FIELD_FUNC(length),
+ FIELD_FUNC(type),
+ FIELD_FUNC(lastupd),
+ FIELD_FUNC(lastplay),
+ FIELD_FUNC(rating),
+ NullFieldFunction, // skip lucky number 13
+ NullFieldFunction, // gracenote ID
+ FIELD_FUNC(playcount),
+ FIELD_FUNC(filetime),
+ FIELD_FUNC(filesize),
+ FIELD_FUNC(bitrate),
+ FIELD_FUNC(disc),
+ FIELD_FUNC(albumartist),
+ FIELD_FUNC(replaygain_album_gain),
+ FIELD_FUNC(replaygain_track_gain),
+ FIELD_FUNC(publisher),
+ FIELD_FUNC(composer),
+ FIELD_FUNC(bpm),
+ FIELD_FUNC(discs),
+ FIELD_FUNC(tracks),
+ FIELD_FUNC(ispodcast),
+ FIELD_FUNC(podcastchannel),
+ FIELD_FUNC(podcastpubdate),
+ FIELD_FUNC(GracenoteFileID),
+ FIELD_FUNC(GracenoteExtData),
+ FIELD_FUNC(lossless),
+ FIELD_FUNC(category),
+ FIELD_FUNC(codec),
+ FIELD_FUNC(director),
+ FIELD_FUNC(producer),
+ FIELD_FUNC(width),
+ FIELD_FUNC(height),
+ FIELD_FUNC(mimetype),
+ FIELD_FUNC(dateadded),
+};
+
+static int StoreField(void *record, nde_field_t field, void *context)
+{
+ unsigned char id = NDE_Field_GetID(field);
+ if (id < sizeof(field_functions)/sizeof(*field_functions))
+ {
+ FieldFunc field_function = field_functions[id];
+ field_function((itemRecordW *)context, field);
+ }
+ return 1;
+}
+
+static void initRecord(itemRecordW *p)
+{
+ if (p)
+ {
+ p->title=0;
+ p->album=0;
+ p->artist=0;
+ p->comment=0;
+ p->genre=0;
+ p->albumartist=0;
+ p->replaygain_album_gain=0;
+ p->replaygain_track_gain=0;
+ p->publisher=0;
+ p->composer=0;
+ p->year=-1;
+ p->track=-1;
+ p->tracks=-1;
+ p->length=-1;
+ p->rating=-1;
+ p->playcount=-1;
+ p->lastplay=-1;
+ p->lastupd=-1;
+ p->filetime=-1;
+ p->filesize=-1;
+ p->bitrate=-1;
+ p->type=-1;
+ p->disc=-1;
+ p->discs=-1;
+ p->bpm=-1;
+ p->extended_info=0;
+ p->category=0;
+ }
+}
+
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordW *obj, bool compat)
+{
+ initRecord(obj);
+ compat_mode = compat;
+ NDE_Scanner_WalkFields(s, StoreField, obj);
+ return obj->filesize;
+}
+
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordListW *obj, bool compat)
+{
+ compat_mode = compat;
+ __int64 retval = ScannerRefToObjCacheNFNW(s, &obj->Items[obj->Size], compat);
+ obj->Size++;
+ return retval;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/FolderBrowseEx.cpp b/Src/Plugins/Library/ml_local/FolderBrowseEx.cpp
new file mode 100644
index 00000000..2c1b0de1
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/FolderBrowseEx.cpp
@@ -0,0 +1,239 @@
+#include "main.h"
+#include "./folderbrowseex.h"
+#include "../nu/CGlobalAtom.h"
+#include <shobjidl.h>
+#include "../replicant/nu/AutoWide.h"
+
+static CGlobalAtom CLSPROP(L"FBEXDLG");
+
+#ifdef _WIN64
+#define LONGPTR_CAST LONG_PTR
+#else
+#define LONGPTR_CAST LONG
+#endif
+
+#define PATHTYPE_PIDL FALSE
+#define PATHTYPE_STRING TRUE
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassName(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpi(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int WINAPI BrowseCallback_Helper(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ FolderBrowseEx *lpfbex = reinterpret_cast<FolderBrowseEx*>(lpData);
+ if (!lpfbex) return 0;
+
+ switch (uMsg)
+ {
+ case BFFM_INITIALIZED:
+ lpfbex->hwnd = hwnd;
+ if(SetPropW(hwnd, CLSPROP, (void*)lpData))
+ {
+ lpfbex->oldProc = (LONG_PTR) ((IsWindowUnicode(hwnd)) ? SetWindowLongPtrW(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)WindowProc_Helper) : SetWindowLongPtrA(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)WindowProc_Helper));
+ if (NULL == lpfbex->oldProc) RemovePropW(hwnd, CLSPROP);
+ }
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ break;
+ }
+ return lpfbex->BrowseCallback(uMsg, lParam);
+}
+
+static LRESULT WINAPI WindowProc_Helper(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ FolderBrowseEx *lpfbex = static_cast<FolderBrowseEx*>(GetPropW(hwnd, CLSPROP));
+ if (!lpfbex) return 0;
+
+ switch(uMsg)
+ {
+ case WM_NCDESTROY:
+ lpfbex->DialogProc(uMsg, wParam, lParam);
+ if (IsWindowUnicode(hwnd)) SetWindowLongPtrW(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)lpfbex->oldProc);
+ else SetWindowLongPtrA(hwnd, DWLP_DLGPROC, (LONGPTR_CAST)lpfbex->oldProc);
+ RemovePropW(hwnd, CLSPROP);
+ lpfbex->oldProc = NULL;
+ lpfbex->hwnd = NULL;
+ return 0;
+ }
+ return lpfbex->DialogProc(uMsg, wParam, lParam);
+}
+
+static void Initialize(FolderBrowseEx *lpfbx, LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle)
+{
+ lpfbx->pidlRoot = pidlRoot;
+ lpfbx->ulFlags = ulFlags;
+
+ lpfbx->lpszCaption = NULL;
+ lpfbx->lpszTitle = NULL;
+ lpfbx->image = -1;
+ lpfbx->pidl = NULL;
+ lpfbx->hwnd = NULL;
+ lpfbx->oldProc = NULL;
+
+ CoInitialize(NULL);
+
+ lpfbx->pathExpanded.empty = TRUE;
+ lpfbx->pathSelection.empty = TRUE;
+
+ if (lpszCaption) lpfbx->SetCaption(lpszCaption);
+ if (lpszTitle) lpfbx->SetTitle(lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle)
+{
+ Initialize(this, pidlRoot, ulFlags, lpszCaption, lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle)
+{
+ Initialize(this, NULL, ulFlags, lpszCaption, lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(UINT ulFlags, LPCWSTR lpszTitle)
+{
+ Initialize(this, NULL, ulFlags, L"", lpszTitle);
+}
+
+FolderBrowseEx::FolderBrowseEx(void)
+{
+ Initialize(this, NULL, 0, L"", L"");
+}
+
+FolderBrowseEx::~FolderBrowseEx(void)
+{
+ if (pidl) CoTaskMemFree(pidl);
+ if (lpszTitle) free(lpszTitle);
+ if (lpszCaption) free(lpszCaption);
+ CoUninitialize();
+}
+
+HRESULT FolderBrowseEx::ParseDisplayName(LPCWSTR lpszPath, IBindCtx *pbc, LPITEMIDLIST *ppidl, SFGAOF sfgaoIn, SFGAOF *psfgaoOut)
+{
+ IShellFolder *isf = NULL;
+ HRESULT result;
+ SFGAOF attrib;
+
+ if (NOERROR != (result = SHGetDesktopFolder(&isf))) return result;
+ attrib = sfgaoIn;
+ result = isf->ParseDisplayName(NULL, pbc, (LPWSTR)lpszPath, NULL, ppidl, &attrib);
+ isf->Release();
+
+ if (S_OK != result) *ppidl = NULL;
+ else if (psfgaoOut) *psfgaoOut = attrib;
+ return result;
+}
+
+void FolderBrowseEx::SetExpanded(LPCITEMIDLIST pidlExpand)
+{
+ pathExpanded.empty = FALSE;
+ pathExpanded.type = PATHTYPE_PIDL;
+ pathExpanded.value = (void*)pidlExpand;
+ if (hwnd) SendMessage(BFFM_SETEXPANDED, pathExpanded.type, (LPARAM)pathExpanded.value);
+}
+
+void FolderBrowseEx::SetExpanded(LPCWSTR lpszExpand)
+{
+ pathExpanded.empty = FALSE;
+ pathExpanded.type = PATHTYPE_STRING;
+ pathExpanded.value = (void*)lpszExpand;
+ if (hwnd) SendMessage(BFFM_SETEXPANDED, pathExpanded.type, (LPARAM)pathExpanded.value);
+}
+
+void FolderBrowseEx::SetSelection(LPCITEMIDLIST pidlSelect)
+{
+ pathSelection.empty = FALSE;
+ pathSelection.type = PATHTYPE_PIDL;
+ pathSelection.value = (void*)pidlSelect;
+ if (hwnd) SendMessage(BFFM_SETSELECTIONW, pathSelection.type, (LPARAM)pathSelection.value);
+}
+
+void FolderBrowseEx::SetSelection(LPCWSTR lpszSelect)
+{
+ pathSelection.empty = FALSE;
+ pathSelection.type = PATHTYPE_STRING;
+ pathSelection.value = (void*)lpszSelect;
+ if (hwnd) SendMessage(BFFM_SETSELECTIONW, pathSelection.type, (LPARAM)pathSelection.value);
+}
+
+void FolderBrowseEx::SetCaption(LPCWSTR lpszCaption)
+{
+ if (this->lpszCaption) free(this->lpszCaption);
+ this->lpszCaption = _wcsdup((lpszCaption) ? lpszCaption : L"");
+ if (hwnd) SetWindowText(lpszCaption);
+}
+
+void FolderBrowseEx::SetTitle(LPCWSTR lpszTitle)
+{
+ if (this->lpszTitle) free(this->lpszTitle);
+ this->lpszTitle = _wcsdup((lpszTitle) ? lpszTitle : L"");
+}
+
+LPITEMIDLIST FolderBrowseEx::Browse(HWND hwndOwner)
+{
+ BROWSEINFOW bi = {0};
+ bi.hwndOwner = hwndOwner;
+ bi.pidlRoot = pidlRoot;
+ bi.pszDisplayName = pszDisplayName;
+ bi.lpszTitle = lpszTitle;
+ bi.ulFlags = ulFlags;
+ bi.lpfn = BrowseCallback_Helper;
+ bi.lParam = (LPARAM)this;
+
+ pidl = SHBrowseForFolderW(&bi);
+ if (pidl) OnSelectionDone(pidl);
+
+ image = (pidl) ? bi.iImage : -1;
+
+ return pidl;
+}
+
+INT FolderBrowseEx::BrowseCallback(UINT uMsg, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case BFFM_INITIALIZED: OnInitialized(); break;
+ case BFFM_IUNKNOWN: OnIUnknown((IUnknown*)lParam); break;
+ case BFFM_SELCHANGED: OnSelectionChanged((LPCITEMIDLIST)lParam); break;
+ case BFFM_VALIDATEFAILEDA: return OnValidateFailed(AutoWide((LPCSTR)lParam));
+ case BFFM_VALIDATEFAILEDW: return OnValidateFailed((LPCWSTR)lParam);
+ }
+ return 0;
+}
+
+void FolderBrowseEx::OnInitialized(void)
+{
+ if (!pathSelection.empty) SendMessage(BFFM_SETSELECTIONW, pathSelection.type, (LPARAM)pathSelection.value);
+ if (!pathExpanded.empty) SendMessage(BFFM_SETEXPANDED, pathExpanded.type, (LPARAM)pathExpanded.value);
+ SetWindowText(lpszCaption);
+}
+
+void FolderBrowseEx::OnSelectionChanged(LPCITEMIDLIST pidl)
+{
+}
+
+INT_PTR FolderBrowseEx::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return CallWindowProc(uMsg, wParam, lParam);
+}
+
+INT_PTR FolderBrowseEx::CallWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ return (IsWindowUnicode(hwnd)) ? ::CallWindowProcW((WNDPROC)oldProc, hwnd, uMsg, wParam, lParam)
+ : ::CallWindowProcA((WNDPROC)oldProc, hwnd, uMsg, wParam, lParam);
+}
+
+void FolderBrowseEx::SetDialogResult(LRESULT result)
+{
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, (LONG)(LONG_PTR)result);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/FolderBrowseEx.h b/Src/Plugins/Library/ml_local/FolderBrowseEx.h
new file mode 100644
index 00000000..746d186d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/FolderBrowseEx.h
@@ -0,0 +1,97 @@
+#ifndef NULLSOFT_FOLDERBROWSE_EXTENDED_DIALOG_HEADER
+#define NULLSOFT_FOLDERBROWSE_EXTENDED_DIALOG_HEADER
+
+#include <windows.h>
+#include <shlobj.h>
+
+/// Standart controls
+#define IDC_TV_FOLDERS 0x3741
+#define IDC_SB_GRIPPER 0x3747
+#define IDC_LBL_FOLDER 0x3748
+#define IDC_LBL_CAPTION 0x3742
+#define IDC_EDT_PATH 0x3744
+
+
+typedef struct _BFPATH
+{
+ int empty;
+ int type;
+ void *value;
+}BFPATH;
+
+class FolderBrowseEx
+{
+public:
+ FolderBrowseEx(LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle);
+ FolderBrowseEx(UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle);
+ FolderBrowseEx(UINT ulFlags, LPCWSTR lpszTitle);
+ FolderBrowseEx(void);
+ virtual ~FolderBrowseEx(void);
+
+public:
+ virtual LPITEMIDLIST Browse(HWND hwndOwner);
+
+ LPITEMIDLIST GetPIDL(void) { return pidl; }
+ INT GetImage(void) { return image; }
+ LPCWSTR GetDislpayName(void) { return pszDisplayName; }
+ HRESULT ParseDisplayName(LPCWSTR lpszPath, IBindCtx *pbc, LPITEMIDLIST *ppidl, SFGAOF sfgaoIn, SFGAOF *psfgaoOut);
+ HWND GetDlgItem(int nIDDlgItem) { return ::GetDlgItem(hwnd, nIDDlgItem); }
+
+ void SetRoot(LPCITEMIDLIST pidlRoot) { this->pidlRoot = pidlRoot; }
+ void SetFlags(UINT ulFlags) { this->ulFlags = ulFlags; }
+ void SetSelection(LPCITEMIDLIST pidlSelect);
+ void SetSelection(LPCWSTR lpszSelect);
+ void SetExpanded(LPCITEMIDLIST pidlExpand);
+ void SetExpanded(LPCWSTR lpszExpand);
+ void SetCaption(LPCWSTR lpszCaption);
+ void SetTitle(LPCWSTR lpszTitle);
+
+
+protected:
+ HWND GetHandle(void) { return hwnd; }
+ INT_PTR CallWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+ void SetDialogResult(LRESULT result);
+ LRESULT SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { return ::SendMessageW(hwnd, uMsg, wParam, lParam); }
+ void SetWindowText(LPCWSTR lpText) { ::SetWindowTextW(hwnd, lpText); }
+
+ void EnableOK(BOOL enable) { SendMessage(BFFM_ENABLEOK, 0, (LPARAM)enable); }
+ void SetOKText(LPCWSTR lpText) { SendMessage(BFFM_SETOKTEXT, 0, (LPARAM)lpText); }
+ void SetStatusText(LPCWSTR lpText) { SendMessage(BFFM_SETSTATUSTEXTW, 0, (LPARAM)lpText); }
+
+ virtual void OnInitialized(void);
+ virtual void OnIUnknown(IUnknown *lpiu) {}
+ virtual void OnSelectionChanged(LPCITEMIDLIST pidl);
+ virtual BOOL OnValidateFailed(LPCWSTR lpName) { return FALSE; }
+ virtual void OnSelectionDone(LPCITEMIDLIST pidl) { }
+
+ virtual INT BrowseCallback(UINT uMsg, LPARAM lParam);
+ virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+protected:
+ LPCITEMIDLIST pidlRoot;
+
+ BFPATH pathSelection;
+ BFPATH pathExpanded;
+
+ LPWSTR lpszCaption;
+ LPWSTR lpszTitle;
+ UINT ulFlags;
+ INT image;
+ WCHAR pszDisplayName[MAX_PATH];
+ BOOL expand;
+ LPITEMIDLIST pidl;
+
+
+private:
+ HWND hwnd;
+ LONG_PTR oldProc;
+
+
+
+ friend static int WINAPI BrowseCallback_Helper(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);
+ friend static LRESULT WINAPI WindowProc_Helper(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ friend static void Initialize(FolderBrowseEx *lpfbx, LPCITEMIDLIST pidlRoot, UINT ulFlags, LPCWSTR lpszCaption, LPCWSTR lpszTitle);
+
+};
+
+#endif //NULLSOFT_FOLDERBROWSE_EXTENDED_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/LocalMediaCOM.cpp b/Src/Plugins/Library/ml_local/LocalMediaCOM.cpp
new file mode 100644
index 00000000..3600b349
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/LocalMediaCOM.cpp
@@ -0,0 +1,307 @@
+#include "main.h"
+#include "LocalMediaCOM.h"
+
+
+void sortResults(int column, int dir, itemRecordListW *obj);
+
+static void saveQueryToList(nde_scanner_t s, itemRecordListW *obj, int sortColumn, int sortDir)
+{
+ emptyRecordList(obj);
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_First(s);
+
+ int r;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(obj->Items[obj->Size].filename);
+ ScannerRefToObjCacheNFNW(s, obj, true);
+ }
+
+ r = NDE_Scanner_Next(s);
+ }
+ while (r && !NDE_Scanner_EOF(s));
+
+ if (((Table *)g_table)->HasErrors()) // TODO: don't use C++ NDE API
+ {
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Compact(g_table);
+ if (m_media_scanner)
+ {
+ m_media_scanner = NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ compactRecordList(obj);
+
+ sortResults(sortColumn, sortDir, obj);
+}
+
+void WriteEscaped(FILE *fp, const wchar_t *str)
+{
+ // TODO: for speed optimization,
+ // we should wait until we hit a special character
+ // and write out everything else so before it,
+ // like how ASX loader does it
+ while (str && *str)
+ {
+ switch(*str)
+ {
+ case L'&':
+ fputws(L"&amp;", fp);
+ break;
+ case L'>':
+ fputws(L"&gt;", fp);
+ break;
+ case L'<':
+ fputws(L"&lt;", fp);
+ break;
+ case L'\'':
+ fputws(L"&apos;", fp);
+ break;
+ case L'\"':
+ fputws(L"&quot;", fp);
+ break;
+ default:
+ fputwc(*str, fp);
+ break;
+ }
+ // write out the whole character
+ wchar_t *next = CharNextW(str);
+ while (++str != next)
+ fputwc(*str, fp);
+
+ }
+}
+
+bool SaveListToXML(const itemRecordListW *const obj, const wchar_t *filename, int limit)
+{
+ int i=0;
+ FILE *fp = _wfopen(filename, L"wb");
+ if (!fp)
+ return false;
+ wchar_t BOM = 0xFEFF;
+ fwrite(&BOM, sizeof(BOM), 1, fp);
+ fwprintf(fp, L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>");
+ fputws(L"<itemlist>\r\n", fp);
+
+ while (i < obj->Size)
+ {
+ fputws(L"<item ", fp);
+
+ if (obj->Items[i].filename)
+ {
+ fputws(L"filename=\"", fp);
+ WriteEscaped(fp, obj->Items[i].filename);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].title)
+ {
+ fputws(L"title=\"", fp);
+ WriteEscaped(fp, obj->Items[i].title);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].album)
+ {
+ fputws(L"album=\"", fp);
+ WriteEscaped(fp, obj->Items[i].album);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].artist)
+ {
+ fputws(L"artist=\"", fp);
+ WriteEscaped(fp, obj->Items[i].artist);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].comment)
+ {
+ fputws(L"comment=\"", fp);
+ WriteEscaped(fp, obj->Items[i].comment);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].genre)
+ {
+ fputws(L"genre=\"", fp);
+ WriteEscaped(fp, obj->Items[i].genre);
+ fputws(L"\" ", fp);
+ }
+
+ if (obj->Items[i].year > 0)
+ fwprintf(fp, L"year=\"%d\" ",obj->Items[i].year);
+
+ if (obj->Items[i].track > 0)
+ fwprintf(fp, L"track=\"%d\" ",obj->Items[i].track);
+
+ if (obj->Items[i].length > 0)
+ fwprintf(fp, L"length=\"%d\" ",obj->Items[i].length);
+
+ // TODO: extended info fields
+ fputws(L"/>", fp);
+ if (++i == limit)
+ break;
+ }
+ fputws(L"</itemlist>", fp);
+ fclose(fp);
+
+ return true;
+}
+
+enum
+{
+ DISP_LOCALMEDIA_XMLQUERY = 777,
+
+};
+
+#define CHECK_ID(str, id) if (_wcsicmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
+HRESULT LocalMediaCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ bool unknowns = false;
+ for (unsigned int i = 0;i != cNames;i++)
+ {
+ CHECK_ID("XMLQuery", DISP_LOCALMEDIA_XMLQUERY)
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT LocalMediaCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT LocalMediaCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+void Bookmark_WriteAsXML(const wchar_t *filename, int max);
+
+HRESULT LocalMediaCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ case DISP_LOCALMEDIA_XMLQUERY:
+ if (pdispparams->cArgs == 4)
+ {
+ openDb();
+ int max,dir,column;
+
+ // Strict-ish type checking
+ if ( pdispparams->rgvarg[0].vt == VT_BSTR )
+ max = _wtoi(pdispparams->rgvarg[0].bstrVal);
+ else
+ max = pdispparams->rgvarg[0].uiVal;
+
+ if ( pdispparams->rgvarg[1].vt == VT_BSTR )
+ dir = _wtoi(pdispparams->rgvarg[2].bstrVal);
+ else
+ dir = pdispparams->rgvarg[1].uiVal;
+
+ if ( pdispparams->rgvarg[2].vt == VT_BSTR )
+ column = _wtoi(pdispparams->rgvarg[2].bstrVal);
+ else
+ column = pdispparams->rgvarg[2].uiVal;
+
+ // run query
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, pdispparams->rgvarg[3].bstrVal);
+
+// create itemRecordList (necessary because NDE doesn't sort)
+ itemRecordListW obj;
+ obj.Alloc = 0;
+ obj.Items = NULL;
+ obj.Size = 0;
+ saveQueryToList(s, &obj, column, dir);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ // write to a temporary XML file
+ wchar_t tempPath[MAX_PATH] = {0};
+ GetTempPathW(MAX_PATH, tempPath);
+ wchar_t tempFile[MAX_PATH] = {0};
+ GetTempFileNameW(tempPath, L"lmx", 0, tempFile);
+ SaveListToXML(&obj, tempFile, max);
+ freeRecordList(&obj);
+
+ // open the resultant file to read into a buffer
+ // (we're basically using the filesystem as an automatically growing buffer)
+ HANDLE plFile = CreateFileW(tempFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
+ size_t flen = SetFilePointer(plFile, 0, NULL, FILE_END);
+ SetFilePointer(plFile, 0, NULL, FILE_BEGIN);
+
+ SAFEARRAY *bufferArray=SafeArrayCreateVector(VT_UI1, 0, flen);
+ void *data;
+ SafeArrayAccessData(bufferArray, &data);
+ DWORD bytesRead = 0;
+ ReadFile(plFile, data, flen, &bytesRead, 0);
+ SafeArrayUnaccessData(bufferArray);
+ CloseHandle(plFile);
+ VariantInit(pvarResult);
+ V_VT(pvarResult) = VT_ARRAY|VT_UI1;
+ V_ARRAY(pvarResult) = bufferArray;
+ DeleteFileW(tempFile);
+ }
+ return S_OK;
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+
+STDMETHODIMP LocalMediaCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG LocalMediaCOM::AddRef(void)
+{
+ return 0;
+}
+
+
+ULONG LocalMediaCOM::Release(void)
+{
+ return 0;
+}
diff --git a/Src/Plugins/Library/ml_local/LocalMediaCOM.h b/Src/Plugins/Library/ml_local/LocalMediaCOM.h
new file mode 100644
index 00000000..02138a06
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/LocalMediaCOM.h
@@ -0,0 +1,21 @@
+#ifndef NULLSOFT_BOOKMARKSCOM_H
+#define NULLSOFT_BOOKMARKSCOM_H
+
+#include <ocidl.h>
+
+class LocalMediaCOM : public IDispatch
+{
+public:
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MD5.cpp b/Src/Plugins/Library/ml_local/MD5.cpp
new file mode 100644
index 00000000..4ce5396d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MD5.cpp
@@ -0,0 +1,294 @@
+#include "MD5.h"
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+
+
+/* Constants for MD5Transform routine.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform(uint32_t [4], unsigned char [64]);
+static void Encode(unsigned char *, uint32_t *, unsigned int);
+static void Decode(uint32_t *, unsigned char *, unsigned int);
+
+static unsigned char PADDING[64] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void MD5Init(MD5_CTX *context)
+{
+ context->count[0] = context->count[1] = 0;
+
+ /* Load magic initialization constants.
+ */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen)
+{
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((uint32_t)inputLen << 3))
+ < ((uint32_t)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((uint32_t)inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible.
+ */
+ if (inputLen >= partLen)
+ {
+ memcpy
+ ((void *)&context->buffer[index], (void *)input, partLen);
+ MD5Transform(context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform(context->state, &input[i]);
+
+ index = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ memcpy
+ ((void *)&context->buffer[index], (void *)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+void MD5Final(uint8_t digest[16], MD5_CTX *context)
+{
+ uint8_t bits[8] = {0};
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode(bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+ */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ MD5Update(context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update(context, bits, 8);
+
+ /* Store state in digest */
+ Encode(digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+ */
+ memset((void *)context, 0, sizeof(*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform(uint32_t state[4], uint8_t block[64])
+{
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16] = {0};
+
+ Decode(x, block, 64);
+
+ /* Round 1 */
+ FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+ */
+ memset((void *)x, 0, sizeof(x));
+}
+
+/* Encodes input (uint32_t) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode(unsigned char *output, uint32_t *input, unsigned int len)
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (uint32_t). Assumes len is
+ a multiple of 4.
+ */
+static void Decode(uint32_t *output, unsigned char *input, unsigned int len)
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((uint32_t)input[j]) | (((uint32_t)input[j+1]) << 8) |
+ (((uint32_t)input[j+2]) << 16) | (((uint32_t)input[j+3]) << 24);
+}
diff --git a/Src/Plugins/Library/ml_local/MD5.h b/Src/Plugins/Library/ml_local/MD5.h
new file mode 100644
index 00000000..74f8f675
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MD5.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_ML_LOCAL_MD5_H
+#define NULLSOFT_ML_LOCAL_MD5_H
+
+#include <bfc/platform/types.h>
+/* MD5 context. */
+typedef struct
+{
+ uint32_t state[4]; /* state (ABCD) */
+ uint32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ uint8_t buffer[64]; /* input buffer */
+} MD5_CTX;
+
+void MD5Init(MD5_CTX *);
+void MD5Update(MD5_CTX *, uint8_t *, unsigned int);
+void MD5Final(uint8_t [16], MD5_CTX *);
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MLDBCallback.h b/Src/Plugins/Library/ml_local/MLDBCallback.h
new file mode 100644
index 00000000..4fe48286
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MLDBCallback.h
@@ -0,0 +1,76 @@
+#pragma once
+#include <api/syscb/callbacks/svccb.h>
+#include <api/syscb/api_syscb.h>
+#include "../ml_local/api_mldb.h"
+
+class MLDBCallback : public SysCallback
+{
+private:
+ FOURCC GetEventType()
+ {
+ return api_mldb::SYSCALLBACK;
+ }
+
+ int notify(int msg, intptr_t param1, intptr_t param2)
+ {
+ const wchar_t *filename = (const wchar_t *)param1;
+
+ switch (msg)
+ {
+ case api_mldb::MLDB_FILE_ADDED:
+ OnFileAdded(filename);
+ return 1;
+
+ case api_mldb::MLDB_FILE_REMOVED_PRE:
+ OnFileRemove_Pre(filename);
+ return 1;
+
+ case api_mldb::MLDB_FILE_REMOVED_POST:
+ OnFileRemove_Post(filename);
+ return 1;
+
+ case api_mldb::MLDB_FILE_UPDATED:
+ case api_mldb::MLDB_FILE_UPDATED_EXTERNAL:
+ OnFileUpdated(filename, (msg == api_mldb::MLDB_FILE_UPDATED));
+ return 1;
+
+ case api_mldb::MLDB_FILE_PLAYED:
+ OnFilePlayed(filename,
+ ((api_mldb::played_info *)param2)->played,
+ ((api_mldb::played_info *)param2)->count);
+ return 1;
+
+ case api_mldb::MLDB_CLEARED:
+ OnCleared((const wchar_t **)param1, param2);
+ return 1;
+
+ case api_mldb::MLDB_FILE_GET_CLOUD_STATUS:
+ {
+ OnGetCloudStatus((const wchar_t *)param1, (HMENU *)param2);
+ }
+ return 1;
+
+ case api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS:
+ OnProcessCloudStatus(param1, (int *)param2);
+ return 1;
+
+ default:
+ return 0;
+ }
+ }
+ virtual void OnFileAdded(const wchar_t *filename) {}
+ virtual void OnFileRemove_Pre(const wchar_t *filename) {}
+ virtual void OnFileRemove_Post(const wchar_t *filename) {}
+ virtual void OnFileUpdated(const wchar_t *filename, bool from_library) {}
+ virtual void OnFilePlayed(const wchar_t *filename, time_t played, int count) {}
+ virtual void OnCleared(const wchar_t **filenames, int count) {}
+ virtual void OnGetCloudStatus(const wchar_t *filename, HMENU *menu) {}
+ virtual void OnProcessCloudStatus(int menu_item, int *result) {}
+
+#define CBCLASS MLDBCallback
+ START_DISPATCH_INLINE;
+ CB(SYSCALLBACK_GETEVENTTYPE, GetEventType);
+ CB(SYSCALLBACK_NOTIFY, notify);
+ END_DISPATCH;
+#undef CBCLASS
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MLString.cpp b/Src/Plugins/Library/ml_local/MLString.cpp
new file mode 100644
index 00000000..32b8e229
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MLString.cpp
@@ -0,0 +1,116 @@
+#include "main.h"
+#include ".\MLString.h"
+#include <strsafe.h>
+
+#define ALLOCATION_STEP 16
+
+void* MLString::heap = GetProcessHeap();
+
+MLString::MLString(void) : buffer(NULL), allocated(0), cchLen(0)
+{
+}
+
+MLString::MLString(const wchar_t* string) : buffer(NULL), allocated(0), cchLen(0)
+{
+ if (string) Append(string, lstrlenW(string));
+}
+MLString::MLString(unsigned int cchBuffer) : buffer(NULL), allocated(0), cchLen(0)
+{
+ Allocate(cchBuffer);
+}
+
+MLString::MLString(const MLString &copy) : buffer(NULL), allocated(0)
+{
+ cchLen = copy.cchLen;
+ Allocate(cchLen + 1);
+ CopyMemory(buffer, copy.buffer, cchLen*sizeof(wchar_t));
+}
+
+MLString::~MLString(void)
+{
+ if (buffer)
+ {
+ HeapFree(heap, NULL, buffer);
+ allocated = 0;
+ cchLen = 0;
+ buffer = NULL;
+ }
+}
+
+HRESULT MLString::Append(const wchar_t* string, unsigned int cchLength)
+{
+ if (allocated <= cchLen + cchLength)
+ {
+ do { allocated += ALLOCATION_STEP; } while(allocated < cchLen + cchLength + 1);
+ buffer = (wchar_t*) ((!buffer) ? HeapAlloc(heap, NULL, allocated*sizeof(wchar_t)) :
+ HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t)));
+ }
+ if (!buffer) return ERROR_OUTOFMEMORY;
+ CopyMemory(buffer + cchLen, string, cchLength*sizeof(wchar_t));
+ cchLen += cchLength;
+ buffer[cchLen] = 0x0000;
+ return S_OK;
+}
+
+HRESULT MLString::Set(const wchar_t* string, unsigned int cchLength)
+{
+ cchLen = 0;
+ return Append(string, cchLength);
+}
+
+HRESULT MLString::Allocate(unsigned int cchNewSize)
+{
+ if (cchNewSize <= cchLen) return ERROR_BAD_LENGTH;
+ if (allocated >= cchNewSize) return S_OK;
+ allocated = cchNewSize;
+ buffer = (wchar_t*) ((!buffer) ? HeapAlloc(heap, NULL, allocated*sizeof(wchar_t)) :
+ HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t)));
+ return (buffer) ? S_OK : ERROR_OUTOFMEMORY;
+}
+
+void MLString::Compact(void)
+{
+ if (!buffer) return;
+ if (0 == cchLen)
+ {
+ HeapFree(heap, NULL, buffer);
+ allocated = 0;
+ cchLen = 0;
+ buffer = NULL;
+ }
+ else
+ {
+ allocated = cchLen + 1;
+ buffer = (wchar_t*)HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t));
+ }
+}
+
+HRESULT MLString::Format(const wchar_t *format, ...)
+{
+ va_list argList;
+ va_start(argList, format);
+ HRESULT retCode;
+ size_t remaining = 0;
+ retCode = (allocated) ? StringCchVPrintfExW(buffer, allocated, NULL, &remaining, STRSAFE_NULL_ON_FAILURE, format, argList) : STRSAFE_E_INSUFFICIENT_BUFFER;
+ while (STRSAFE_E_INSUFFICIENT_BUFFER == retCode)
+ {
+ int attempt = 1;
+ allocated += ALLOCATION_STEP*attempt;
+ attempt++;
+ buffer = (wchar_t*) ((!buffer) ? HeapAlloc(heap, NULL, allocated*sizeof(wchar_t)) :
+ HeapReAlloc(heap, NULL, buffer, allocated*sizeof(wchar_t)));
+ retCode = StringCchVPrintfExW(buffer, allocated, NULL, &remaining, STRSAFE_NULL_ON_FAILURE, format, argList);
+ }
+ va_end(argList);
+ cchLen = (S_OK == retCode) ? allocated - (unsigned int)remaining : 0;
+ return retCode;
+}
+
+HRESULT MLString::CopyTo(MLString *destination)
+{
+ HRESULT retCode = destination->Allocate(allocated);
+ if (S_OK != retCode) return retCode;
+ destination->cchLen = cchLen;
+ CopyMemory(destination->buffer, buffer, cchLen*sizeof(wchar_t));
+ return S_OK;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/MLString.h b/Src/Plugins/Library/ml_local/MLString.h
new file mode 100644
index 00000000..ddf98e58
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/MLString.h
@@ -0,0 +1,54 @@
+#ifndef NULLOSFT_MLSTRING_HEADER
+#define NULLOSFT_MLSTRING_HEADER
+
+#include <windows.h>
+
+class MLString
+{
+public:
+ MLString(void);
+ ~MLString(void);
+ MLString(const wchar_t* string);
+ MLString(unsigned int cchBuffer);
+protected:
+ MLString(const MLString &copy);
+
+
+public:
+ HRESULT Set(const wchar_t* string, unsigned int cchLength);
+ HRESULT Append(const wchar_t* string, unsigned int cchLength);
+ const wchar_t* Get(void) { return (cchLen) ? buffer : NULL; }
+ void Clear(void) { cchLen = 0; }
+ unsigned int GetLength(void) { return cchLen; }
+ HRESULT Format(const wchar_t *format, ...);
+ HRESULT CopyTo(MLString *destination);
+
+ HRESULT Set(const wchar_t* string) { return Set(string, lstrlenW(string)); }
+ HRESULT Append(const wchar_t* string) { return Append(string, lstrlenW(string)); }
+
+ // buffer
+ HRESULT Allocate(unsigned int cchNewSize);
+ void Compact(void);
+ wchar_t* GetBuffer(void) { return buffer; }
+ unsigned int GetBufferLength(void) { return allocated; }
+ void UpdateBuffer(void) { cchLen = lstrlenW(buffer); }
+
+ operator const wchar_t *() { return buffer; }
+ operator wchar_t *() { return buffer; }
+ wchar_t& operator [](unsigned int index) { return buffer[index]; }
+ MLString& operator = (const wchar_t *source) { (source) ? Set(source, lstrlenW(source)) : Clear(); return *this;}
+ MLString& operator + (const wchar_t *source) { if (source) Append(source, lstrlenW(source)); return *this;}
+ MLString& operator += (const wchar_t *source) { if (source) Append(source, lstrlenW(source)); return *this;}
+ MLString& operator = (MLString *source) { (source) ? source->CopyTo(this) : Clear(); return *this;}
+ MLString& operator + (MLString *source) { if (source) Append(source->GetBuffer(), source->GetLength()); return *this;}
+ MLString& operator += (MLString *source) { if (source) Append(source->GetBuffer(), source->GetLength()); return *this;}
+
+protected:
+ wchar_t *buffer;
+ unsigned int cchLen;
+ unsigned int allocated;
+
+ static void *heap;
+};
+
+#endif // NULLOSFT_STRING_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/Main.cpp b/Src/Plugins/Library/ml_local/Main.cpp
new file mode 100644
index 00000000..a8aefced
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/Main.cpp
@@ -0,0 +1,586 @@
+#define PLUGIN_VERSION L"3.36"
+#include "main.h"
+#include "resource.h"
+#include "api__ml_local.h"
+#include "..\..\General\gen_ml/config.h"
+#include <commctrl.h>
+#include ".\ml_local.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../replicant/nu/AutoChar.h"
+#include <api/service/waServiceFactory.h>
+#include "../playlist/api_playlistmanager.h"
+#include "mldbApiFactory.h"
+#include "../nu/ServiceWatcher.h"
+#include "LocalMediaCOM.h"
+#include <tataki/export.h>
+#include <strsafe.h>
+#if 0
+// disabled since not building cloud dlls
+#include "../ml_cloud/CloudCallback.h"
+#endif
+
+static ServiceWatcher serviceWatcher;
+static LocalMediaCOM localMediaCOM;
+MLDBAPIFactory mldbApiFactory;
+mlAddTreeItemStruct newTree;
+
+#if 0
+// disabled since not building cloud dlls
+static class CloudCallbacks : public CloudCallback
+{
+ void OnCloudUploadStart(const wchar_t *filename) {
+ SendMessage(m_curview_hwnd, WM_APP + 5, 0, (LPARAM)filename);
+ }
+
+ void OnCloudUploadDone(const wchar_t *filename, int code) {
+ SendMessage(m_curview_hwnd, WM_APP + 5, MAKEWPARAM(code, 1), (LPARAM)filename);
+ }
+} cloudCallback;
+#endif
+
+LRESULT ML_IPC_MENUFUCKER_BUILD = -1, ML_IPC_MENUFUCKER_RESULT = -1;
+int IPC_CLOUD_ENABLED = -1;
+
+int CreateView(int treeItem, HWND parent);
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+void SaveAll();
+
+prefsDlgRecW preferences;
+
+static wchar_t preferencesName[64] = {0};
+
+int winampVersion = 0;
+int substantives = 0;
+int play_enq_rnd_alt = 0;
+
+// Delay load library control << begin >>
+#include <delayimp.h>
+#pragma comment(lib, "delayimp")
+
+bool nde_error = false;
+
+FARPROC WINAPI FailHook(unsigned dliNotify, DelayLoadInfo *dli)
+{
+ nde_error = true;
+ return 0;
+}
+
+/*
+extern "C"
+{
+ PfnDliHook __pfnDliFailureHook2 = FailHook;
+}
+// Delay load library control << end >>
+*/
+
+#define CBCLASS PLCallBackW
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+extern WORD waMenuID;
+DEFINE_EXTERNAL_SERVICE(api_application, WASABI_API_APP);
+DEFINE_EXTERNAL_SERVICE(api_explorerfindfile, WASABI_API_EXPLORERFINDFILE);
+DEFINE_EXTERNAL_SERVICE(api_language, WASABI_API_LNG);
+DEFINE_EXTERNAL_SERVICE(api_syscb, WASABI_API_SYSCB);
+DEFINE_EXTERNAL_SERVICE(api_memmgr, WASABI_API_MEMMGR);
+DEFINE_EXTERNAL_SERVICE(api_albumart, AGAVE_API_ALBUMART);
+DEFINE_EXTERNAL_SERVICE(api_metadata, AGAVE_API_METADATA);
+DEFINE_EXTERNAL_SERVICE(api_playlistmanager, AGAVE_API_PLAYLISTMANAGER);
+DEFINE_EXTERNAL_SERVICE(api_itunes_importer, AGAVE_API_ITUNES_IMPORTER);
+DEFINE_EXTERNAL_SERVICE(api_playlist_generator, AGAVE_API_PLAYLIST_GENERATOR);
+DEFINE_EXTERNAL_SERVICE(api_threadpool, WASABI_API_THREADPOOL);
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+int sse_flag;
+int Init()
+{
+#ifdef _M_IX86
+ int flags_edx;
+ _asm
+ {
+ mov eax, 1
+ cpuid
+ mov flags_edx, edx
+ }
+
+ sse_flag = flags_edx & 0x02000000;
+#else
+ sse_flag=1; // always supported on amd64
+#endif
+ InitializeCriticalSection(&g_db_cs);
+
+ waMenuID = (WORD)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_REGISTER_LOWORD_COMMAND);
+
+ Tataki::Init(plugin.service);
+
+ plugin.service->service_register(&mldbApiFactory);
+
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ ServiceBuild(WASABI_API_EXPLORERFINDFILE, ExplorerFindFileApiGUID);
+ ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceBuild(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid);
+ ServiceBuild(WASABI_API_SYSCB, syscbApiServiceGuid);
+ ServiceBuild(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID);
+ //ServiceBuild(AGAVE_API_PLAYLIST_GENERATOR, api_playlist_generator::getServiceGuid());
+
+ serviceWatcher.WatchWith(plugin.service);
+ serviceWatcher.WatchFor(&AGAVE_API_ITUNES_IMPORTER, api_itunes_importer::getServiceGuid());
+ serviceWatcher.WatchFor(&AGAVE_API_PLAYLIST_GENERATOR, api_playlist_generator::getServiceGuid());
+ WASABI_API_SYSCB->syscb_registerCallback(&serviceWatcher);
+ #if 0
+ // disabled since not building cloud dlls
+ WASABI_API_SYSCB->syscb_registerCallback(&cloudCallback);
+ #endif
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlLocalLangGUID);
+
+ wchar_t buf[2] = {0};
+ if(LoadString(WASABI_API_LNG_HINST,IDS_SUBSTANTIVES,buf,2)){
+ substantives = 1;
+ }
+
+ // this is used to load alternative play/enqueue random strings where
+ // the default implementation will cause pluralisation issue eg de-de
+ buf[0] = 0;
+ if(LoadString(WASABI_API_LNG_HINST,IDS_PLAY_ENQ_RND_ALTERNATIVE,buf,2)){
+ play_enq_rnd_alt = 1;
+ }
+
+ static wchar_t szDescription[256];
+ StringCchPrintfW(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_LOCAL_MEDIA), PLUGIN_VERSION);
+ plugin.description = (char*)szDescription;
+
+ ML_IPC_MENUFUCKER_BUILD = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_build", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ ML_IPC_MENUFUCKER_RESULT = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"menufucker_result", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_CLOUD_ENABLED = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudEnabled", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+ winampVersion = mediaLibrary.GetWinampVersion();
+
+ mediaLibrary.AddDispatch(L"LocalMedia", &localMediaCOM);
+
+ // this may look unused, but we want to get this here since mediaLibrary will cache the inidir
+ // and then we don't run into weird multithreaded SendMessage issues
+ mediaLibrary.GetIniDirectory();
+ mediaLibrary.GetIniDirectoryW();
+
+ preferences.hInst = WASABI_API_LNG_HINST;
+ preferences.dlgID = IDD_PREFSFR;
+ preferences.proc = (void *)PrefsProc;
+ preferences.name = WASABI_API_LNGSTRINGW_BUF(IDS_LOCAL_MEDIA,preferencesName,64);
+ preferences.where = 6; // 0;
+ mediaLibrary.AddPreferences(preferences);
+
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_AUDIO, TREE_IMAGE_LOCAL_AUDIO, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_MOSTPLAYED, TREE_IMAGE_LOCAL_MOSTPLAYED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_NEVERPLAYED, TREE_IMAGE_LOCAL_NEVERPLAYED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_RECENTLYPLAYED, TREE_IMAGE_LOCAL_RECENTLYPLAYED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_RECENTLYADDED, TREE_IMAGE_LOCAL_RECENTLYADDED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_TOPRATED, TREE_IMAGE_LOCAL_TOPRATED, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_VIDEO, TREE_IMAGE_LOCAL_VIDEO, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_PODCASTS, TREE_IMAGE_LOCAL_PODCASTS, (BMPFILTERPROC)FILTER_DEFAULT1);
+ mediaLibrary.AddTreeImage(IDB_TREEITEM_RECENTLYMODIFIED, TREE_IMAGE_LOCAL_RECENTLYMODIFIED, (BMPFILTERPROC)FILTER_DEFAULT1);
+
+ int ret = init();
+ if (ret) return ret;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW(IDS_LOCAL_MEDIA);
+ nis.item.pszInvariant = L"Local Media";
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.id = 1000; // benski> use the old ID for backwards compatability
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_ITEMID;
+
+ // map to item id (will probably have to change but is a quick port to support invariant item naming)
+ NAVITEM nvItem = {sizeof(NAVITEM),0,NIMF_ITEMID,};
+ nvItem.hItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ m_query_tree = nvItem.id;
+
+ loadQueryTree();
+
+ m_query_mode = 0;
+ m_query_metafile = L"default.vmd";
+
+ return ret;
+}
+
+void Quit()
+{
+ serviceWatcher.StopWatching();
+ serviceWatcher.Clear();
+
+ // deregister this first, otherwise people might try to use it after we shut down the database!
+ plugin.service->service_deregister(&mldbApiFactory);
+
+ UnhookPlaylistEditor();
+
+ Scan_Kill();
+
+ closeDb();
+
+ delete(g_view_metaconf);
+ g_view_metaconf = 0;
+
+ delete g_config;
+ g_config = NULL;
+
+ KillArtThread();
+
+ for (QueryList::iterator i = m_query_list.begin();i != m_query_list.end();i++)
+ {
+ queryItem *item = i->second;
+ if (item)
+ {
+ free(item->metafn);
+ free(item->name);
+ free(item->query);
+ }
+ free(item);
+ }
+ m_query_list.clear();
+ DeleteCriticalSection(&g_db_cs);
+
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid);
+ ServiceRelease(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceRelease(WASABI_API_SYSCB, syscbApiServiceGuid);
+ ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_ITUNES_IMPORTER, api_itunes_importer::getServiceGuid());
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+ ServiceRelease(AGAVE_API_PLAYLIST_GENERATOR, api_playlist_generator::getServiceGuid());
+
+ Tataki::Quit();
+}
+
+INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ switch (message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW: // param1 = param of tree item, param2 is HWND of parent. return HWND if it is us
+ if (param1 == m_query_tree || m_query_list[param1])
+ return (INT_PTR)onTreeViewSelectChange((HWND)param2);
+ else
+ return 0;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, m_query_tree);
+ if (hItem == myItem)
+ {
+ queriesContextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+ return 1;
+ }
+ else
+ {
+ NAVITEM nvItem = {sizeof(NAVITEM),hItem,NIMF_ITEMID,};
+ MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
+ if (m_query_list[nvItem.id])
+ {
+ view_queryContextMenu(param1, (HWND)param2, MAKEPOINTS(param3), nvItem.id);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ case ML_MSG_TREE_ONCLICK:
+ if (param1 == m_query_tree)
+ return OnLocalMediaClick(param2, (HWND)param3);
+ else if (m_query_list[param1])
+ return OnLocalMediaItemClick(param2, param1, (HWND)param3);
+ else
+ return 0;
+
+ case ML_MSG_TREE_ONDRAG:
+ if (m_query_list[param1])
+ {
+ int *type = reinterpret_cast<int *>(param3);
+ *type = ML_TYPE_ITEMRECORDLIST;
+ return 1;
+ }
+ return 0;
+
+ case ML_MSG_TREE_ONDROP:
+ if (param3 != NULL && param1 != NULL) // here we go - finishing moving view
+ {
+ if (m_query_list[param1])
+ {
+ if (param3 == m_query_tree || m_query_list[param3])
+ {
+ QueryList::iterator src = m_query_list.find(param1);
+ mediaLibrary.RemoveTreeItem(src->first);
+ MLTREEITEMW srcItem = {sizeof(MLTREEITEMW), };
+
+ srcItem.title = src->second->name;
+ srcItem.hasChildren = 0;
+ srcItem.parentId = m_query_tree;
+ srcItem.id = param3;
+ srcItem.imageIndex = src->second->imgIndex;
+ mediaLibrary.InsertTreeItem(srcItem);
+
+ auto item = src->second;
+ m_query_list.erase(param1);
+ m_query_list.insert({ srcItem.id, item });
+
+ saveQueryTree();
+ mediaLibrary.SelectTreeItem(srcItem.id);
+ return 1;
+ }
+ }
+ }
+ else if (m_query_list[param1])
+ {
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_ITEMRECORDLISTW;
+ m.p = *(POINT *)param2;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR | ML_HANDLEDRAG_FLAG_NAME;
+
+ // build an itemRecordList
+ queryItem *item = m_query_list[param1];
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, item->query);
+ itemRecordListW obj = {0, };
+ saveQueryToListW(&viewconf, s, &obj, 0, 0, (resultsniff_funcW)-1);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ m.data = (void *) & obj;
+ AutoChar whatsThisNameUsedForAnyway(item->name);
+ m.name = whatsThisNameUsedForAnyway;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ if (m.result < 1)
+ {
+ m.result = 0;
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA={0,};
+ convertRecordList(&objA, &obj);
+ m.data = (void*)&objA;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ freeRecordList(&objA);
+ }
+ freeRecordList(&obj);
+ }
+ return 0;
+
+ case ML_MSG_CONFIG:
+ mediaLibrary.GoToPreferences(preferences._id);
+ return TRUE;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = param1;
+ groupBtn = param2;
+ PostMessage(m_curview_hwnd, WM_APP + 104, param1, param2);
+ return 0;
+
+ case ML_MSG_ONSENDTOBUILD:
+ if (param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_ITEMRECORDLIST ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW)
+ {
+ if (!myMenu) mediaLibrary.AddToSendTo(WASABI_API_LNGSTRINGW(IDS_ADD_TO_LOCAL_MEDIA), param2, (INT_PTR)MessageProc);
+ }
+ break;
+
+ case ML_MSG_ONSENDTOSELECT:
+ case ML_MSG_TREE_ONDROPTARGET: // return -1 if not allowed, 1 if allowed, or 0 if not our tree item
+ // set with droptarget defaults =)
+ INT_PTR type, data;
+
+ if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)MessageProc) return 0;
+
+ type = param1;
+ data = param2;
+ }
+ else
+ {
+ if (param1 != m_query_tree && !m_query_list[param1]) return 0;
+
+ type = param2;
+ data = param3;
+
+ if (!data)
+ {
+ return (type == ML_TYPE_ITEMRECORDLISTW || type == ML_TYPE_ITEMRECORDLIST ||
+ type == ML_TYPE_FILENAMES || type == ML_TYPE_FILENAMESW ||
+ type == ML_TYPE_PLAYLIST) ? 1 : -1;
+ }
+ }
+
+ if (data)
+ {
+ if (type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordList *p = (itemRecordList*)data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddToMediaLibrary(p->Items[x].filename);
+
+ return 1;
+ }
+ else if (type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *p = (itemRecordListW*)data;
+ for (int x = 0; x < p->Size; x ++)
+ mediaLibrary.AddToMediaLibrary(p->Items[x].filename);
+
+ return 1;
+ }
+ else if (type == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist * pl = (mlPlaylist *)data;
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(pl->filename, &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(pl->filename);
+ }
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMES)
+ {
+ const char *p = (const char*)data;
+ while (p && *p)
+ {
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(AutoWide(p), &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(p);
+ }
+ p += strlen(p) + 1;
+ }
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMESW)
+ {
+ const wchar_t *p = (const wchar_t*)data;
+ while (p && *p)
+ {
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(p, &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(p);
+ }
+ p += wcslen(p) + 1;
+ }
+ return 1;
+ }
+ }
+ break;
+
+ case ML_MSG_TREE_ONKEYDOWN:
+ {
+ NMTVKEYDOWN *p = (NMTVKEYDOWN*)param2;
+ int ctrl = (GetAsyncKeyState(VK_CONTROL)&0x8000);
+ int shift = (GetAsyncKeyState(VK_SHIFT)&0x8000);
+
+ if (p->wVKey == VK_INSERT && !shift)
+ {
+ if (!ctrl)
+ addNewQuery(plugin.hwndLibraryParent);
+ else
+ if (!g_bgscan_scanning) SendMessage(plugin.hwndLibraryParent, WM_USER + 575, 0xffff00dd, 0);
+
+ PostMessageW(plugin.hwndLibraryParent, WM_NEXTDLGCTL, (WPARAM)param3, (LPARAM)TRUE);
+ return 1;
+ }
+ else if (m_query_list[param1])
+ {
+ if (p->wVKey == VK_F2 && !shift && !ctrl)
+ {
+ queryEditItem(param1);
+ }
+ else if (p->wVKey == VK_DELETE && !shift && !ctrl)
+ {
+ queryDeleteItem(plugin.hwndLibraryParent, param1);
+ }
+ else
+ break;
+
+ PostMessageW(plugin.hwndLibraryParent, WM_NEXTDLGCTL, (WPARAM)param3, (LPARAM)TRUE);
+ return 1;
+ }
+ return 0;
+ }
+
+ case ML_IPC_HOOKEXTINFO:
+ if (IPC_HookExtInfo(param1)) return 1;
+ break;
+
+ case ML_IPC_HOOKEXTINFOW:
+ if (IPC_HookExtInfoW(param1)) return 1;
+ break;
+
+ case ML_IPC_HOOKTITLEW:
+ if (IPC_HookTitleInfo(param1)) return 1;
+ break;
+
+ case ML_MSG_PLAYING_FILE:
+ if (param1) onStartPlayFileTrack((const wchar_t *)param1, false);
+ break;
+ }
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_local.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/Main.h b/Src/Plugins/Library/ml_local/Main.h
new file mode 100644
index 00000000..bde0bc52
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/Main.h
@@ -0,0 +1,71 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include <windows.h>
+#include <windowsx.h>
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "../nu/DialogSkinner.h"
+#include "../winamp/wa_ipc.h"
+#include "ml_local.h"
+#include <shlobj.h>
+#include "../nde/nde.h"
+#include "../nde/nde_c.h"
+#include <time.h>
+#include "../Agave/Language/api_language.h"
+#include "..\..\General\gen_ml/config.h"
+#include "api__ml_local.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../replicant/nu/AutoChar.h"
+#include "AlbumArtCache.h"
+#include "./local_menu.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+
+#define WM_QUERYFILEINFO (WM_USER + 65)
+#define WM_SHOWFILEINFO (WM_USER + 64) // wParam - bForceUpdate, lParam - pszFileName
+
+extern winampMediaLibraryPlugin plugin;
+extern int winampVersion;
+extern int substantives;
+extern int play_enq_rnd_alt;
+extern bool nde_error;
+extern HMENU wa_playlists_cmdmenu;
+extern prefsDlgRecW preferences;
+extern HWND hwndSearchGlobal;
+
+void EatKeyboard();
+void HookPlaylistEditor();
+void UnhookPlaylistEditor();
+extern bool skipTitleInfo;
+extern wchar_t *recent_fn;
+
+extern int IPC_GET_CLOUD_HINST, IPC_GET_CLOUD_ACTIVE, IPC_CLOUD_ENABLED;
+extern LRESULT ML_IPC_MENUFUCKER_BUILD;
+extern LRESULT ML_IPC_MENUFUCKER_RESULT;
+
+extern int groupBtn, enqueuedef;
+extern LARGE_INTEGER freq;
+
+class PLCallBackW : public ifc_playlistloadercallback
+{
+public:
+ PLCallBackW(void){};
+ ~PLCallBackW(void){};
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ mediaLibrary.AddToMediaLibrary(filename);
+ }
+ RECVS_DISPATCH;
+};
+
+extern "C" void qsort_itemRecord(void *base, size_t num, const void *context,
+int (__fastcall *comp)(const void *, const void *, const void *));
+
+void MigrateArtCache();
+void setCloudValue(itemRecordW *item, const wchar_t* value);
+
+void onStartPlayFileTrack(const wchar_t *filename, bool resume);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ReIndexUI.cpp b/Src/Plugins/Library/ml_local/ReIndexUI.cpp
new file mode 100644
index 00000000..83e0c968
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ReIndexUI.cpp
@@ -0,0 +1,333 @@
+#include "Main.h"
+#include "ml_local.h"
+#include "resource.h"
+
+static INT_PTR CALLBACK CompactWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, 400));
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, 0, 0);
+
+ int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (progress[1] == -102)
+ {
+ progress[1] = -101;
+ SetWindowTextW(hwndDlg, WASABI_API_LNGSTRINGW(IDS_REFRESH_FILESIZE_DATEADDED));
+ }
+
+ SetTimer(hwndDlg, 666, 500, 0);
+ break;
+ }
+ case WM_TIMER:
+ if (wParam == 666)
+ {
+ int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (progress[0] == 666)
+ {
+ KillTimer(hwndDlg, 666);
+ EndDialog(hwndDlg, 0);
+ }
+ else if (progress[1] != -101)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, progress[1] + 300, 0);
+ }
+ else
+ {
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, progress[0] + 100, 0);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static DWORD CALLBACK CompactThread(LPVOID param)
+{
+ WASABI_API_DIALOGBOXPARAMW(IDD_REINDEX, NULL, CompactWndProc, (LPARAM)param);
+ return 0;
+}
+
+static int sortFunc(const void *a, const void *b)
+{
+ const wchar_t **fn1 = (const wchar_t **)a;
+ const wchar_t **fn2 = (const wchar_t **)b;
+ return _wcsicmp(*fn1, *fn2);
+}
+
+void RetypeFilename(nde_table_t table)
+{
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+
+ int progress[2] = {-100, -101};
+ DWORD threadId = 0;
+ HANDLE compactThread = 0;
+
+ nde_scanner_t pruneScanner = NDE_Table_CreateScanner(table);
+ if (pruneScanner)
+ {
+ bool first=true;
+ int recordNum = 0;
+ NDE_Scanner_First(pruneScanner);
+ while (!NDE_Scanner_EOF(pruneScanner))
+ {
+ progress[0] = MulDiv(recordNum, 100, totalRecords)-100;
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, MAINTABLE_ID_FILENAME);
+ if (f && NDE_Field_GetType(f) == FIELD_STRING)
+ {
+ wchar_t *s = NDE_StringField_GetString(f);
+ ndestring_retain(s);
+
+ NDE_Scanner_DeleteField(pruneScanner, f);
+
+ nde_field_t new_f = NDE_Scanner_NewFieldByID(pruneScanner, MAINTABLE_ID_FILENAME);
+ NDE_StringField_SetString(new_f, s);
+
+ ndestring_release(s);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ else if (f)
+ {
+ first = false; // skips creating the thread
+ break;
+ }
+
+ recordNum++;
+ NDE_Scanner_Next(pruneScanner);
+ if (first)
+ {
+ compactThread = CreateThread(0, 0, CompactThread, progress, 0, &threadId);
+ DumpArtCache(); // go ahead and dump the album art cache if we had to rebuild this table. ideally we could perform the same logic but this is easier and it's 1 in the morning and i don't feel like doing it :)
+ }
+ first=false;
+ }
+
+ NDE_Table_DestroyScanner(table, pruneScanner);
+ if (compactThread)
+ NDE_Table_Sync(table);
+ }
+ progress[0] = 666;
+ if (compactThread)
+ {
+ WaitForSingleObject(compactThread, INFINITE);
+ CloseHandle(compactThread);
+ }
+}
+
+void RefreshFileSizeAndDateAddedTable(nde_table_t table)
+{
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+
+ int progress[2] = {-100, -102};
+ DWORD threadId = 0;
+ HANDLE compactThread = 0;
+
+ nde_scanner_t scanner = NDE_Table_CreateScanner(table);
+ if (scanner)
+ {
+ bool first=true;
+ int recordNum = 0;
+ NDE_Scanner_First(scanner);
+ while (!NDE_Scanner_EOF(scanner))
+ {
+ progress[0] = MulDiv(recordNum, 400, totalRecords);
+
+ // converts filesize from a int and kb scaled value to the actual filesize as a int64
+ nde_field_t f = NDE_Scanner_GetFieldByID(scanner, MAINTABLE_ID_FILESIZE);
+ if (f && NDE_Field_GetType(f) == FIELD_INTEGER)
+ {
+ __int64 size = NDE_IntegerField_GetValue(f);
+ if (size) size *= 1024;
+
+ NDE_Scanner_DeleteField(scanner, f);
+
+ nde_field_t new_f = NDE_Scanner_NewFieldByType(scanner, FIELD_INT64, MAINTABLE_ID_FILESIZE);
+ NDE_Int64Field_SetValue(new_f, size);
+
+ NDE_Scanner_Post(scanner);
+ }
+
+ // takes the lastupd value and applies it to dateadded so we've got something to use
+ f = NDE_Scanner_GetFieldByID(scanner, MAINTABLE_ID_LASTUPDTIME);
+ if (f && NDE_Field_GetType(f) == FIELD_DATETIME)
+ {
+ int lastupd = NDE_IntegerField_GetValue(f);
+ nde_field_t new_f = NDE_Scanner_NewFieldByID(scanner, MAINTABLE_ID_DATEADDED);
+ NDE_IntegerField_SetValue(new_f, lastupd);
+
+ NDE_Scanner_Post(scanner);
+ }
+
+ NDE_Scanner_Next(scanner);
+ recordNum++;
+ if (first)
+ {
+ compactThread = CreateThread(0, 0, CompactThread, progress, 0, &threadId);
+ }
+ first=false;
+ }
+
+ NDE_Table_DestroyScanner(table, scanner);
+ if (compactThread)
+ NDE_Table_Sync(table);
+ }
+ progress[0] = 666;
+ if (compactThread)
+ {
+ WaitForSingleObject(compactThread, INFINITE);
+ CloseHandle(compactThread);
+ }
+}
+
+void ReindexTable(nde_table_t table)
+{
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+
+ int progress[2] = {-100, -101};
+ DWORD threadId;
+ HANDLE compactThread = CreateThread(0, 0, CompactThread, progress, 0, &threadId);
+
+ nde_scanner_t pruneScanner = NDE_Table_CreateScanner(table);
+ if (pruneScanner)
+ {
+ int recordNum = 0;
+ NDE_Scanner_First(pruneScanner);
+ while (!NDE_Scanner_EOF(pruneScanner))
+ {
+ int total = MulDiv(recordNum, 100, totalRecords);
+ progress[0] = total - 100;
+ #ifndef elementsof
+ #define elementsof(x) (sizeof(x)/sizeof(*x))
+ #endif
+ unsigned char STR_IDS[] = {MAINTABLE_ID_TITLE, MAINTABLE_ID_ARTIST, MAINTABLE_ID_ALBUM, MAINTABLE_ID_GENRE,
+ MAINTABLE_ID_COMMENT, MAINTABLE_ID_GRACENOTE_ID, MAINTABLE_ID_ALBUMARTIST,
+ MAINTABLE_ID_TRACKGAIN, MAINTABLE_ID_PUBLISHER, MAINTABLE_ID_COMPOSER,
+ MAINTABLE_ID_PODCASTCHANNEL, MAINTABLE_ID_GRACENOTEFILEID, MAINTABLE_ID_GRACENOTEEXTDATA,
+ MAINTABLE_ID_CATEGORY, MAINTABLE_ID_CODEC, MAINTABLE_ID_DIRECTOR, MAINTABLE_ID_PRODUCER
+ };
+ for (size_t i = 0;i != elementsof(STR_IDS);i++)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, STR_IDS[i]);
+ if (f)
+ {
+ const wchar_t *s = NDE_StringField_GetString(f);
+ if (!s || !*s)
+ {
+ NDE_Scanner_DeleteField(pruneScanner, f);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ }
+ }
+
+ unsigned char INT_IDS_ZEROOK[] = {MAINTABLE_ID_LENGTH, MAINTABLE_ID_PLAYCOUNT, MAINTABLE_ID_FILESIZE,
+ MAINTABLE_ID_TYPE, MAINTABLE_ID_ISPODCAST, MAINTABLE_ID_LOSSLESS
+ };
+ for (size_t i = 0;i != elementsof(INT_IDS_ZEROOK);i++)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, INT_IDS_ZEROOK[i]);
+ if (f)
+ {
+ int s = NDE_IntegerField_GetValue(f);
+ if (s < 0)
+ {
+ NDE_Scanner_DeleteField(pruneScanner, f);
+ NDE_Scanner_Post(pruneScanner);
+
+ }
+ }
+ }
+
+ unsigned char INT_IDS[] = {MAINTABLE_ID_TRACKNB, MAINTABLE_ID_LASTUPDTIME, MAINTABLE_ID_LASTPLAY, MAINTABLE_ID_RATING,
+ MAINTABLE_ID_FILETIME, MAINTABLE_ID_BITRATE, MAINTABLE_ID_DISC, MAINTABLE_ID_BPM, MAINTABLE_ID_DISCS,
+ MAINTABLE_ID_TRACKS, MAINTABLE_ID_PODCASTPUBDATE, MAINTABLE_ID_FILESIZE, MAINTABLE_ID_DATEADDED
+ };
+ for (size_t i = 0;i != elementsof(INT_IDS);i++)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, INT_IDS[i]);
+ if (f)
+ {
+ int s = NDE_IntegerField_GetValue(f);
+ if (s <= 0)
+ {
+ NDE_Scanner_DeleteField(pruneScanner, f);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ }
+ }
+ NDE_Scanner_Next(pruneScanner);
+ recordNum++;
+ }
+
+ NDE_Table_DestroyScanner(table, pruneScanner);
+ NDE_Table_Sync(table);
+ }
+
+ NDE_Table_Compact(table, &progress[0]);
+ assert(((Table *)table)->CheckIndexing());
+ // now remove duplicates
+ nde_scanner_t dupscanner = NDE_Table_CreateScanner(table);
+ if (dupscanner)
+ {
+ int count = NDE_Scanner_GetRecordsCount(dupscanner);
+ wchar_t **filenames = new wchar_t *[count];
+ int i = 0;
+ for (NDE_Scanner_First(dupscanner);!NDE_Scanner_EOF(dupscanner);NDE_Scanner_Next(dupscanner))
+ {
+ nde_field_t fileName = NDE_Scanner_GetFieldByID(dupscanner, MAINTABLE_ID_FILENAME);
+ if (fileName)
+ {
+ filenames[i] = NDE_StringField_GetString(fileName);
+ ndestring_retain(filenames[i]);
+ i++;
+ }
+ }
+ count = i;
+ if (count)
+ {
+ qsort(filenames, count, sizeof(wchar_t *), sortFunc);
+ for (int x = 0;x < (count - 1);x++)
+ {
+ int total = MulDiv(x, 100, count);
+ progress[1] = total - 100;
+
+ if (!_wcsicmp(filenames[x], filenames[x+1]))
+ {
+ wchar_t query[1024] = {0};
+ wnsprintfW(query, 1024, L"filename == \"%s\"", filenames[x]);
+
+ nde_scanner_t scanner = NDE_Table_CreateScanner(table);
+ NDE_Scanner_Query(scanner, query);
+
+ NDE_Scanner_First(scanner);
+ NDE_Scanner_Next(scanner);
+ while (!NDE_Scanner_EOF(scanner))
+ {
+ NDE_Scanner_Delete(scanner);
+ NDE_Scanner_Post(scanner);
+ NDE_Scanner_Next(scanner);
+ }
+ NDE_Table_DestroyScanner(table, scanner);
+ }
+ ndestring_release(filenames[x]);
+ filenames[x]=0;
+ }
+ }
+ delete[] filenames;
+ }
+ NDE_Table_DestroyScanner(table, dupscanner);
+ NDE_Table_Sync(table);
+ NDE_Table_Compact(table, &progress[1]);
+
+ progress[0] = 666;
+ WaitForSingleObject(compactThread, INFINITE);
+ CloseHandle(compactThread);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/SaveQuery.cpp b/Src/Plugins/Library/ml_local/SaveQuery.cpp
new file mode 100644
index 00000000..35cac30c
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/SaveQuery.cpp
@@ -0,0 +1,406 @@
+#include "main.h"
+#include "ml_local.h"
+#include <shlwapi.h>
+#include <assert.h>
+#include "../nu/sort.h"
+
+/*
+#ifdef _M_IX86
+#undef min
+static inline int min(int x, int y)
+{
+ return y+((x-y)>>31)&(x-y);
+}
+#undef max
+static inline int max(int x, int y)
+{
+ return x-(((x-y)>>(31))&(x-y));
+}
+
+#elif defined(_WIN64)
+#undef min
+static inline int min(int x, int y)
+{
+ return y+((x-y)>>63)&(x-y);
+}
+#undef max
+static inline int max(int x, int y)
+{
+ return x-(((x-y)>>(63))&(x-y));
+}
+#endif
+*/
+
+#ifdef _M_IX86
+const size_t convert_max_characters = 16; // it's actually 11, but this probably causes less memory fragmentation
+#elif defined(_M_X64)
+const size_t convert_max_characters = 20;
+#endif
+
+static inline int Compare_Int_NegativeIsNull(int v1, int v2)
+{
+ v1 = max(v1,0);
+ v2 = max(v2,0);
+ return(v1 - v2);
+}
+
+typedef int (__fastcall *SortFunction)(const itemRecordW *a, const itemRecordW *b);
+#define SORT(field) SortBy ## field
+#define STRING_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return WCSCMP_NULLOK(a->field, b->field); }
+#define EXT_STRING_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.field); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.field); return WCSCMP_NULLOK(a_field, b_field);}
+#define TIME_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { time_t v1 = (time_t)a->field; time_t v2 = (time_t)b->field; if (v1 == -1) v1 = 0; if (v2 == -1) v2 = 0; return (int)(v1 - v2);}
+#define EXT_TIME_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.dateadded); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.dateadded); time_t v1 = a_field?_wtoi(a_field):0; time_t v2 = b_field?_wtoi(b_field):0; if (v1 == -1) v1 = 0; if (v2 == -1) v2 = 0; return (int)(v1 - v2);}
+#define INT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return Compare_Int_NegativeIsNull(a->field, b->field); }
+#define EXT_INT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.field); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.field); int v1 = a_field?_wtoi(a_field):0; int v2 = b_field?_wtoi(b_field):0; return (v1-v2);}
+#define FLOAT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return FLOATWCMP_NULLOK(a->field, b->field); }
+STRING_SORT(artist);
+STRING_SORT(title);
+STRING_SORT(album);
+INT_SORT(length);
+INT_SORT(track);
+STRING_SORT(genre);
+INT_SORT(year);
+static int __fastcall SORT(filespec)(const itemRecordW *a, const itemRecordW *b)
+{
+ // remove path before compare...
+ wchar_t * af = L"";
+ if (a->filename)
+ af = PathFindFileNameW(a->filename);
+
+ wchar_t * bf = L"";
+ if (b->filename)
+ bf = PathFindFileNameW(b->filename);
+
+ return _wcsicmp(af, bf);
+}
+
+INT_SORT(rating);
+INT_SORT(playcount);
+TIME_SORT(lastplay);
+TIME_SORT(lastupd);
+TIME_SORT(filetime);
+STRING_SORT(comment);
+INT_SORT(filesize);
+INT_SORT(bitrate);
+INT_SORT(type);
+INT_SORT(disc);
+STRING_SORT(albumartist);
+STRING_SORT(filename);
+FLOAT_SORT(replaygain_album_gain);
+FLOAT_SORT(replaygain_track_gain);
+STRING_SORT(publisher);
+STRING_SORT(composer);
+static int __fastcall SORT(extension)(const itemRecordW *a, const itemRecordW *b)
+{
+ wchar_t *extA = PathFindExtensionW(a->filename);
+ wchar_t *extB = PathFindExtensionW(b->filename);
+ if (extA && *extA)
+ extA++;
+ if (extB && *extB)
+ extB++;
+ return WCSCMP_NULLOK(extA, extB);
+}
+EXT_INT_SORT(ispodcast);
+EXT_STRING_SORT(podcastchannel);
+EXT_STRING_SORT(podcastpubdate);
+INT_SORT(bpm);
+STRING_SORT(category);
+EXT_STRING_SORT(director);
+EXT_STRING_SORT(producer);
+EXT_TIME_SORT(dateadded);
+EXT_STRING_SORT(cloud);
+
+static int __fastcall SORT(dimension)(const itemRecordW *a, const itemRecordW *b)
+{
+ wchar_t *a_width = getRecordExtendedItem_fast(a, extended_fields.width);
+ wchar_t *b_width = getRecordExtendedItem_fast(b, extended_fields.width);
+ wchar_t *a_height = getRecordExtendedItem_fast(a, extended_fields.height);
+ wchar_t *b_height = getRecordExtendedItem_fast(b, extended_fields.height);
+ int w1 = a_width?_wtoi(a_width):0;
+ int w2 = b_width?_wtoi(b_width):0;
+ int h1 = a_height?_wtoi(a_height):0;
+ int h2 = b_height?_wtoi(b_height):0;
+ if (w1 != w2)
+ return (w1-w2);
+ else
+ return (h1-h2);
+}
+// DISABLED FOR 5.62 RELEASE - DRO
+//EXT_STRING_SORT(codec);
+
+#define FORCE_ASCENDING ((SortFunction)-2)
+// this is where we define sort orders!
+static const SortFunction sortOrder[MEDIAVIEW_COL_NUMS][MEDIAVIEW_COL_NUMS+2] =
+{
+ {SORT(artist), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), SORT(title), 0}, // Artist
+ {SORT(title), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(album), FORCE_ASCENDING, SORT(albumartist), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(length), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(track), FORCE_ASCENDING, SORT(title), SORT(artist), SORT(album), SORT(disc), 0},
+ {SORT(genre), FORCE_ASCENDING, SORT(albumartist), SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(year), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filespec), SORT(filename),0},
+ {SORT(rating), SORT(playcount), SORT(lastplay), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(playcount), SORT(lastplay), FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(lastplay), FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(lastupd), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filetime), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(comment), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filesize),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(bitrate), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(type), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(disc), FORCE_ASCENDING, SORT(track), SORT(title), SORT(artist), SORT(album), SORT(disc), 0},
+ {SORT(albumartist), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(filename), 0},
+ {SORT(replaygain_album_gain), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(replaygain_track_gain), FORCE_ASCENDING, SORT(title), 0},
+ {SORT(publisher),FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(composer), FORCE_ASCENDING,SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(extension), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(ispodcast), FORCE_ASCENDING, SORT(podcastchannel), SORT(title), 0},
+ {SORT(podcastchannel), SORT(title), 0},
+ {SORT(podcastpubdate), 0},
+ {SORT(bpm), 0}, // TODO
+ {SORT(category),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(director),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(producer),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
+ {SORT(dimension), 0},
+ // DISABLED FOR 5.62 RELEASE - DRO
+ //{SORT(codec), 0},
+ {SORT(dateadded), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+ {SORT(cloud), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
+};
+
+/* ---- */
+struct SortRules
+{
+ int by;
+ int dir;
+};
+static int __fastcall sortFuncW(const void *elem1, const void *elem2, const void *context)
+{
+ assert(sizeof(sortOrder) / sizeof(*sortOrder) == MEDIAVIEW_COL_NUMS);
+
+ const itemRecordW *a = (const itemRecordW*)elem1;
+ const itemRecordW *b = (const itemRecordW*)elem2;
+
+ const SortRules *rules = (SortRules *)context;
+ int use_by = rules->by;
+ int use_dir = !!rules->dir;
+
+ int dir_values[] = {-1, 1};
+
+#define RETIFNZ(v) if ((v)<0) return dir_values[use_dir]; if ((v)>0) return -dir_values[use_dir];
+
+ const SortFunction *order =sortOrder[use_by];
+ int i=0;
+ while (order[i])
+ {
+ if (order[i]==FORCE_ASCENDING)
+ {
+ use_dir=0;
+ i++;
+ continue;
+ }
+ int v = order[i](a, b);
+ RETIFNZ(v);
+ i++;
+ }
+ return 0;
+}
+
+void sortResults(int column, int dir, itemRecordListW *obj) // sorts the results based on the current sort mode
+{
+ if (obj->Size > 1)
+ {
+ SortRules rules = {column, dir};
+ qsort_itemRecord(obj->Items, obj->Size, &rules, sortFuncW);
+ }
+}
+
+void sortResults(C_Config *viewconf, itemRecordListW *obj) // sorts the results based on the current sort mode
+{
+ if (viewconf)
+ {
+ SortRules rules = {viewconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST), viewconf->ReadInt(L"mv_sort_dir", 0)};
+ if (obj->Size > 1)
+ qsort_itemRecord(obj->Items, obj->Size, &rules, sortFuncW);
+ }
+}
+
+void setCloudValue(itemRecordW *item, const wchar_t* value)
+{
+ wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t));
+ lstrcpynW(temp, value, convert_max_characters);
+ setRecordExtendedItem(item, extended_fields.cloud, temp);
+ ndestring_release(temp);
+}
+
+int saveQueryToListW(C_Config *viewconf, nde_scanner_t s, itemRecordListW *obj,
+ CloudFiles *uploaded, CloudFiles *uploading,
+ resultsniff_funcW cb, int user32, int *killswitch, __int64 *total_bytes)
+{
+ __int64 total_kb = 0;
+
+ emptyRecordList(obj);
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_First(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+
+ int r = 0;
+ unsigned int total_length_s = 0;
+ unsigned int uncert_cnt = 0;
+ unsigned int cert_cnt = 0;
+ int recordCount = NDE_Scanner_GetRecordsCount(s);
+ allocRecordList(obj, recordCount);
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(obj->Items[obj->Size].filename);
+ total_kb += ScannerRefToObjCacheNFNW(s, obj, (cb == (resultsniff_funcW)-1 ? true : false));
+
+ // look for any files known to the cloud so the icon can be set accordingly
+ // TODO possibly need to do more here to better cope with transient states??
+ if (uploaded && uploaded->size() > 0)
+ {
+ bool found = false;
+ for(CloudFiles::iterator iter = uploaded->begin(); iter != uploaded->end(); iter++)
+ {
+ if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter)) == 0)
+ {
+ bool pending = false;
+ found = true;
+
+ // catches files being uploaded but not fully known to be in the cloud, etc
+ if (uploading && uploading->size() > 0)
+ {
+ for(CloudFiles::iterator iter2 = uploading->begin(); iter2 != uploading->end(); iter2++)
+ {
+ if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter2)) == 0)
+ {
+ pending = true;
+ setCloudValue(&obj->Items[obj->Size - 1], L"5");
+ break;
+ }
+ }
+ }
+
+ if (!pending)
+ {
+ setCloudValue(&obj->Items[obj->Size - 1], L"0");
+ }
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ // catches files being uploaded but not fully known to be in the cloud, etc
+ if (uploading && uploading->size() > 0)
+ {
+ for(CloudFiles::iterator iter = uploading->begin(); iter != uploading->end(); iter++)
+ {
+ if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter)) == 0)
+ {
+ found = true;
+ setCloudValue(&obj->Items[obj->Size - 1], L"5");
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ setCloudValue(&obj->Items[obj->Size - 1], L"4");
+ }
+ }
+ }
+
+ int thisl = obj->Items[obj->Size - 1].length;
+
+ if (thisl > 0)
+ {
+ total_length_s += thisl;
+ cert_cnt++;
+ }
+ else
+ {
+ uncert_cnt++;
+ }
+ }
+
+ r = NDE_Scanner_Next(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ }
+ while (r && !NDE_Scanner_EOF(s));
+
+ if (((Table *)g_table)->HasErrors()) // TODO: don't use C++ NDE API
+ {
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Compact(g_table);
+ if (m_media_scanner)
+ {
+ m_media_scanner = NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (total_bytes)
+ {
+ // maintain compatibility as needed
+ if (cb == (resultsniff_funcW)-1)
+ *total_bytes = total_kb * 1024;
+ else
+ *total_bytes = total_kb;
+ }
+ compactRecordList(obj);
+
+ if (cb && cb != (resultsniff_funcW)-1)
+ {
+ cb(obj->Items, obj->Size, user32, killswitch);
+ }
+
+ if (killswitch && *killswitch) return 0;
+
+ sortResults(viewconf, obj);
+
+ if (killswitch && *killswitch) return 0;
+
+ if (uncert_cnt)
+ {
+ if (cert_cnt)
+ {
+ __int64 avg_len = (__int64)total_length_s / cert_cnt;
+ total_length_s += (DWORD)(avg_len * uncert_cnt);
+ }
+ total_length_s |= (1 << 31);
+ }
+
+ return total_length_s;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp b/Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp
new file mode 100644
index 00000000..ae49d0a0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ScanFolderBrowser.cpp
@@ -0,0 +1,475 @@
+#include "main.h"
+#include "./scanfolderbrowser.h"
+#include "resource.h"
+
+/// New controls
+#define IDC_CHK_BGSCAN 0x1980
+#define IDC_TLB_BOOKMARKS 0x1978
+#define TOOLBAR_BTN_STARTID 0x1000
+
+#define PATHTYPE_CSIDL 0
+#define PATHTYPE_STRING 1
+#define PATHTYPE_PIDL 2
+
+typedef struct __FOLDER
+{
+ int pathtype;
+ INT_PTR path;
+ LPCWSTR caption; // if NULL display name will be used
+ LPCWSTR tooltip; // if NULL display name will be used
+} FOLDER;
+
+typedef struct _FBUTTON
+{
+ LPITEMIDLIST pidl;
+ LPWSTR caption;
+ LPWSTR tooltip;
+ INT iImage;
+} FBUTTON;
+
+static const FOLDER BOOKMARKS[] =
+{
+ {PATHTYPE_STRING, (INT_PTR)L"C:\\Program Files\\Winamp", NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_MYMUSIC, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_MYVIDEO, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_PERSONAL, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_DESKTOP, NULL, NULL },
+ {PATHTYPE_CSIDL, CSIDL_DRIVES, NULL, NULL},
+ {PATHTYPE_CSIDL, CSIDL_NETWORK, NULL, NULL },
+};
+
+static HIMAGELIST hSysImageList;
+
+//// XP THEME - BEGIN
+typedef HANDLE HTHEME;
+
+typedef HRESULT (WINAPI *XPT_CLOSETHEMEDATA)(HTHEME);
+typedef BOOL (WINAPI *XPT_ISAPPTHEMED)(void);
+typedef HTHEME (WINAPI *XPT_OPENTHEMEDATA)(HWND, LPCWSTR);
+typedef HRESULT (WINAPI *XPT_GETTHEMECOLOR)(HTHEME, INT, INT, INT, COLORREF*);
+
+#define GP_BORDER 1
+#define BSS_FLAT 1
+#define TMT_BORDERCOLOR 3801
+
+static XPT_CLOSETHEMEDATA xpCloseThemeData;
+static XPT_ISAPPTHEMED xpIsAppThemed;
+static XPT_OPENTHEMEDATA xpOpenThemeData;
+static XPT_GETTHEMECOLOR xpGetThemeColor;
+
+static HINSTANCE xpThemeDll = NULL;
+
+static BOOL LoadXPTheme(void);
+static void UnloadXPTheme(void);
+//// XP THEME - END
+
+static HBRUSH GetBorderBrush(HWND hwnd);
+
+static void Initialize(ScanFolderBrowser *browser, BOOL showBckScan, BOOL checkBckScan)
+{
+ browser->buttonsCount = 0;
+ browser->buttons = NULL;
+ browser->bkScanChecked = checkBckScan;
+ browser->bkScanShow = showBckScan;
+ browser->pac = NULL;
+ browser->pacl2 = NULL;
+
+ HRESULT result = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete, (LPVOID*)&browser->pac);
+ if (S_OK == result)
+ {
+ IAutoComplete2 *pac2;
+ if (SUCCEEDED(browser->pac->QueryInterface(IID_IAutoComplete2, (LPVOID*)&pac2)))
+ {
+ pac2->SetOptions(ACO_AUTOSUGGEST | ACO_AUTOAPPEND | ACO_USETAB | 0x00000020/*ACF_UPDOWNKEYDROPSLIST*/);
+ }
+ result = CoCreateInstance(CLSID_ACListISF, NULL, CLSCTX_INPROC_SERVER, IID_IACList2, (LPVOID*)&browser->pacl2);
+ if (S_OK == result) browser->pacl2->SetOptions(ACLO_FILESYSDIRS);
+ else { browser->pac->Release(); browser->pac = NULL; }
+ }
+
+ lstrcpynW(browser->selectionPath, WASABI_API_APP->path_getWorkingPath(), MAX_PATH);
+
+ browser->SetCaption(WASABI_API_LNGSTRINGW(IDS_ADD_MEDIA_TO_LIBRARY_));
+ browser->SetTitle(WASABI_API_LNGSTRINGW(IDS_SELECT_FOLDER_TO_ADD_TO_WINAMP_MEDIA_LIBRARY));
+ browser->SetSelection(browser->selectionPath);
+ browser->SetFlags(BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_VALIDATE | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON | BIF_NEWDIALOGSTYLE);
+ browser->LoadBookmarks();
+}
+
+ScanFolderBrowser::ScanFolderBrowser(BOOL showBckScanOption)
+{
+ Initialize(this, showBckScanOption, FALSE);
+}
+
+ScanFolderBrowser::ScanFolderBrowser(void)
+{
+ Initialize(this, TRUE, FALSE);
+}
+
+ScanFolderBrowser::~ScanFolderBrowser(void)
+{
+ if (pacl2) pacl2->Release();
+ if (pac) pac->Release();
+ FreeBookmarks();
+}
+
+void ScanFolderBrowser::LoadBookmarks(void)
+{
+ FreeBookmarks();
+ INT count = sizeof(BOOKMARKS)/sizeof(FOLDER);
+ if (!count) return;
+
+ buttons = (FBUTTON*)calloc(count, sizeof(FBUTTON));
+ if (!buttons) return;
+ buttonsCount = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ const FOLDER *pfolder = &BOOKMARKS[i];
+ FBUTTON *pfb = &buttons[buttonsCount];
+ switch(BOOKMARKS[i].pathtype)
+ {
+ case PATHTYPE_PIDL:
+ pfb->pidl = ILClone((LPITEMIDLIST)pfolder->path); // need to copy it
+ break;
+ case PATHTYPE_CSIDL:
+ if (S_OK != SHGetSpecialFolderLocation(NULL, (INT)pfolder->path, &pfb->pidl)) pfb->pidl = NULL;
+ break;
+ case PATHTYPE_STRING:
+ if (S_OK != ParseDisplayName((LPCWSTR)pfolder->path, NULL, &pfb->pidl, 0, NULL))pfb->pidl = NULL;
+ break;
+ }
+ if (!buttons[buttonsCount].pidl)
+ {
+ continue;
+ }
+
+ SHFILEINFOW shfi = {0};
+ hSysImageList = (HIMAGELIST)SHGetFileInfoW((LPCWSTR)pfb->pidl, 0, &shfi, sizeof(SHFILEINFO), SHGFI_DISPLAYNAME | SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SYSICONINDEX | SHGFI_PIDL);
+ pfb->caption = _wcsdup((pfolder->caption) ? pfolder->caption : shfi.szDisplayName);
+ pfb->tooltip = _wcsdup((pfolder->tooltip) ? pfolder->tooltip : shfi.szDisplayName);
+ pfb->iImage = shfi.iIcon;
+ buttonsCount++;
+ }
+}
+
+void ScanFolderBrowser::FreeBookmarks(void)
+{
+ if (buttons)
+ {
+ FBUTTON *pbtn = buttons;
+ while(buttonsCount--)
+ {
+ if (pbtn->caption) { free(pbtn->caption); pbtn->caption = NULL; }
+ if (pbtn->tooltip) { free(pbtn->tooltip); pbtn->tooltip = NULL; }
+ if (pbtn->pidl) { ILFree(pbtn->pidl); pbtn->pidl = NULL; }
+ pbtn++;
+ }
+ free(buttons);
+ }
+ buttonsCount = 0;
+}
+
+void ScanFolderBrowser::OnInitialized(void)
+{
+ HWND ctrlWnd;
+
+ if (!FindWindowExW(GetHandle(), NULL,L"SHBrowseForFolder ShellNameSpace Control",NULL))
+ {
+ RECT rw;
+ int cx;
+ GetWindowRect(GetHandle(), &rw);
+ cx = rw.right - rw.left;
+ SetWindowPos(GetHandle(), NULL, 0, 0, cx, rw.bottom - rw.top, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
+ GetWindowRect(GetHandle(), &rw);
+ ShrinkWindows((rw.right - rw.left) - cx);
+ }
+ SetOKText(WASABI_API_LNGSTRINGW(IDS_ADD));
+
+ ShiftWindows(88);
+
+ ctrlWnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY , TOOLBARCLASSNAMEW, NULL,
+ WS_CHILD | WS_TABSTOP |
+ CCS_TOP | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE |
+ TBSTYLE_FLAT | TBSTYLE_TRANSPARENT | TBSTYLE_WRAPABLE | TBSTYLE_CUSTOMERASE | TBSTYLE_TOOLTIPS,
+ 12, 10, 84, 272, GetHandle(), (HMENU)IDC_TLB_BOOKMARKS, NULL, NULL);
+ if (ctrlWnd)
+ {
+ ::SendMessage(ctrlWnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0L);
+ ::SendMessage(ctrlWnd, TB_SETBUTTONSIZE, (WPARAM)0, MAKELPARAM(84, 50));
+ ::SendMessage(ctrlWnd, TB_SETBITMAPSIZE, (WPARAM)0, MAKELPARAM(32, 32));
+ ::SendMessage(ctrlWnd, TB_SETPADDING, (WPARAM)0, MAKELPARAM(8, 8));
+ ::SendMessage(ctrlWnd, TB_SETBUTTONWIDTH, (WPARAM)0, MAKELPARAM(84, 84));
+ ::SendMessage(ctrlWnd, TB_SETEXTENDEDSTYLE, (WPARAM)0, 0x0000080 /*TBSTYLE_EX_DOUBLEBUFFER*/);
+ ::SendMessage(ctrlWnd, TB_SETMAXTEXTROWS, (WPARAM)2, 0L);
+ ::SendMessage(ctrlWnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)hSysImageList);
+
+ LPTBBUTTON ptbb = (LPTBBUTTON)calloc(buttonsCount, sizeof(TBBUTTON));
+ for (int i = 0; i < buttonsCount; i++)
+ {
+ ptbb[i].fsState = TBSTATE_ENABLED | TBSTATE_WRAP;
+ ptbb[i].idCommand = TOOLBAR_BTN_STARTID + i;
+ ptbb[i].fsStyle = TBSTYLE_CHECKGROUP;
+ ptbb[i].iString = (INT_PTR)buttons[i].caption;
+ ptbb[i].iBitmap = (INT_PTR) buttons[i].iImage;
+ }
+ ::SendMessage(ctrlWnd, TB_ADDBUTTONSW, (WPARAM)buttonsCount,(LPARAM)ptbb);
+ }
+
+ ctrlWnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY, L"BUTTON",
+ WASABI_API_LNGSTRINGW(IDS_SCAN_FOLDER_IN_BACKGROUND),
+ BS_AUTOCHECKBOX | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_TABSTOP,
+ 0, 0, 100, 16, GetHandle(), (HMENU)IDC_CHK_BGSCAN, NULL, NULL);
+ if (ctrlWnd)
+ {
+ ::SendMessage(ctrlWnd, WM_SETFONT, (WPARAM)SendMessage(WM_GETFONT, 0, 0), FALSE);
+ ::SendMessage(ctrlWnd, BM_SETCHECK, (WPARAM) (bkScanChecked) ? BST_CHECKED : BST_UNCHECKED, 0L);
+ }
+
+ hbBorder = GetBorderBrush(GetHandle());
+ RepositionWindows();
+
+ ShowWindow(GetDlgItem(IDC_TLB_BOOKMARKS), SW_SHOWNORMAL);
+ if (bkScanShow) ShowWindow(GetDlgItem(IDC_CHK_BGSCAN), SW_SHOWNORMAL);
+
+ // edit box autocomplete chnages
+ if(pac) pac->Init(GetDlgItem(IDC_EDT_PATH), pacl2, NULL, NULL);
+
+ FolderBrowseEx::OnInitialized();
+}
+
+void ScanFolderBrowser::OnSelectionChanged(LPCITEMIDLIST pidl)
+{
+ for(int i =0; i < buttonsCount; i++)
+ {
+ ::SendMessage(GetDlgItem(IDC_TLB_BOOKMARKS), TB_CHECKBUTTON, (WPARAM)TOOLBAR_BTN_STARTID + i, ILIsEqual(pidl, buttons[i].pidl));
+ }
+
+ FolderBrowseEx::OnSelectionChanged(pidl);
+}
+
+BOOL ScanFolderBrowser::OnValidateFailed(LPCWSTR lpName)
+{
+ wchar_t buffer[2048] = {0}, titleStr[64] = {0};
+ FolderBrowseEx::OnValidateFailed(lpName);
+ wsprintfW(buffer, WASABI_API_LNGSTRINGW(IDS_FOLDER_NAME_IS_INCORRECT), lpName);
+ MessageBoxW(GetHandle(), buffer, WASABI_API_LNGSTRINGW_BUF(IDS_INCORRECT_FOLDER_NAME,titleStr,64), MB_OK | MB_ICONERROR | MB_SETFOREGROUND);
+ return TRUE;
+}
+
+void ScanFolderBrowser::OnSelectionDone(LPCITEMIDLIST pidl)
+{
+ WCHAR pszPath[MAX_PATH] = {0};
+ SHGetPathFromIDListW(pidl, pszPath);
+ WASABI_API_APP->path_setWorkingPath(pszPath);
+}
+
+void ScanFolderBrowser::ShiftWindows(int cx)
+{
+ HWND hwnd = GetWindow(GetHandle(), GW_CHILD);
+ while(hwnd)
+ {
+ RECT rw;
+ UINT ctrlID = GetDlgCtrlID(hwnd);
+ if (ctrlID != IDOK && ctrlID != IDCANCEL && ctrlID != IDC_SB_GRIPPER)
+ {
+ GetWindowRect(hwnd, &rw);
+ MapWindowPoints(HWND_DESKTOP, GetHandle(), (LPPOINT)&rw, 2);
+ SetWindowPos(hwnd, NULL, rw.left + cx, rw.top, (rw.right - (rw.left + cx)), rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOZORDER | ((ctrlID == IDC_LBL_FOLDER) ? SWP_NOSIZE : 0));
+ }
+ hwnd = GetWindow(hwnd, GW_HWNDNEXT);
+ }
+}
+
+void ScanFolderBrowser::ShrinkWindows(int cx)
+{
+ HWND hwnd = GetWindow(GetHandle(), GW_CHILD);
+ while(hwnd)
+ {
+ UINT ctrlID = GetDlgCtrlID(hwnd);
+ if (ctrlID != IDC_LBL_FOLDER)
+ {
+ RECT rw;
+ GetWindowRect(hwnd, &rw);
+ MapWindowPoints(HWND_DESKTOP, GetHandle(), (LPPOINT)&rw, 2);
+ SetWindowPos(hwnd, NULL,
+ rw.left + cx, rw.top, (rw.right - rw.left) + cx, rw.bottom - rw.top,
+ SWP_NOACTIVATE | SWP_NOZORDER | ((ctrlID == IDOK || ctrlID == IDCANCEL) ? SWP_NOSIZE : SWP_NOMOVE));
+ }
+ hwnd = GetWindow(hwnd, GW_HWNDNEXT);
+ }
+}
+
+void ScanFolderBrowser::RepositionWindows(void)
+{
+ RECT rwBtn, rwLV, rwCtrl;
+ HWND pwnd, ctrlWnd;
+
+ GetWindowRect(GetDlgItem(IDOK), &rwBtn);
+ pwnd = FindWindowExW(GetHandle(), NULL,L"SHBrowseForFolder ShellNameSpace Control",NULL);
+
+ GetWindowRect((pwnd) ? pwnd : GetDlgItem(IDC_TV_FOLDERS), &rwLV);
+ ctrlWnd = GetDlgItem(IDC_CHK_BGSCAN);
+ GetWindowRect(ctrlWnd, &rwCtrl);
+ SetRect(&rwCtrl, rwLV.left, rwBtn.bottom - (rwCtrl.bottom - rwCtrl.top), rwBtn.left - 4 - rwLV.left, rwCtrl.bottom - rwCtrl.top);
+ MapWindowPoints(HWND_DESKTOP, GetHandle(), (LPPOINT)&rwCtrl, 1);
+ SetWindowPos(ctrlWnd, NULL, rwCtrl.left, rwCtrl.top, rwCtrl.right, rwCtrl.bottom, SWP_NOACTIVATE | SWP_NOZORDER);
+
+ ctrlWnd = GetDlgItem(IDC_TLB_BOOKMARKS);
+ GetWindowRect(ctrlWnd, &rwCtrl);
+ GetWindowRect(GetDlgItem(IDC_LBL_CAPTION), &rwLV);
+ SetWindowPos(ctrlWnd, NULL, 0, 0, rwCtrl.right - rwCtrl.left, rwBtn.bottom - rwLV.top, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE);
+}
+
+void ScanFolderBrowser::OnSize(UINT nType, int cx, int cy)
+{
+ FolderBrowseEx::DialogProc(WM_SIZE, nType, MAKELPARAM(cx, cy));
+
+ RepositionWindows();
+}
+
+void ScanFolderBrowser::OnWindowPosChanging(WINDOWPOS *lpwp)
+{
+ if (lpwp->cx < 346 + 88) lpwp->cx = 346 + 88;
+}
+
+BOOL ScanFolderBrowser::OnNotify(UINT idCtrl, LPNMHDR pnmh, LRESULT *result)
+{
+ switch(pnmh->code)
+ {
+ case NM_CUSTOMDRAW:
+ switch(pnmh->idFrom)
+ {
+ case IDC_TLB_BOOKMARKS:
+ *result = OnToolBarCustomDraw((LPNMTBCUSTOMDRAW)pnmh);
+ return TRUE;
+ }
+ break;
+ case TTN_GETDISPINFOW:
+ OnToolTipGetDispInfo((LPNMTTDISPINFOW)pnmh);
+ break;
+ }
+ return FALSE;
+}
+
+BOOL ScanFolderBrowser::OnCommand(UINT idCtrl, UINT idEvnt, HWND hwndCtrl)
+{
+ int tbid;
+ tbid = idCtrl - TOOLBAR_BTN_STARTID;
+ if (tbid >= 0 && tbid < buttonsCount)
+ {
+ LPITEMIDLIST pidl1 = buttons[tbid].pidl;
+ SetExpanded(pidl1);
+ SetSelection(pidl1);
+
+ wchar_t test_path[MAX_PATH] = {0};
+ EnableOK(SHGetPathFromIDListW(pidl1, test_path));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+LRESULT ScanFolderBrowser::OnToolBarCustomDraw(LPNMTBCUSTOMDRAW pnmcd)
+{
+ switch(pnmcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ FrameRect(pnmcd->nmcd.hdc, &pnmcd->nmcd.rc, hbBorder);
+ InflateRect(&pnmcd->nmcd.rc, -1, -1);
+ IntersectClipRect(pnmcd->nmcd.hdc, pnmcd->nmcd.rc.left, pnmcd->nmcd.rc.top, pnmcd->nmcd.rc.right, pnmcd->nmcd.rc.bottom);
+ return CDRF_NOTIFYITEMDRAW;
+ case CDDS_ITEMPREPAINT:
+ InflateRect(&pnmcd->nmcd.rc, -1, 0);
+ if (0 == pnmcd->nmcd.rc.top) pnmcd->nmcd.rc.top++;
+ return CDRF_DODEFAULT;
+ }
+ return CDRF_DODEFAULT;
+}
+
+void ScanFolderBrowser::OnToolTipGetDispInfo(LPNMTTDISPINFOW lpnmtdi)
+{
+ int tbid;
+ tbid = lpnmtdi->hdr.idFrom - TOOLBAR_BTN_STARTID;
+ lpnmtdi->lpszText = (tbid >= 0 && tbid < buttonsCount) ? buttons[tbid].tooltip : L"";
+}
+
+INT_PTR ScanFolderBrowser::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT result;
+ switch(uMsg)
+ {
+ case WM_WINDOWPOSCHANGING: OnWindowPosChanging((WINDOWPOS*)lParam);
+ case WM_SIZE: OnSize(wParam, LOWORD(lParam), HIWORD(lParam)); return 0;
+ case WM_NOTIFY:
+ if (OnNotify(wParam, (LPNMHDR)lParam, &result))
+ {
+ SetDialogResult(result);
+ return TRUE;
+ }
+ break;
+ case WM_COMMAND:
+ if (OnCommand(LOWORD(wParam), HIWORD(wParam), (HWND)lParam)) return 0;
+ break;
+ case WM_DESTROY:
+ if (hbBorder) DeleteObject(hbBorder);
+ hbBorder = NULL;
+ bkScanChecked = (BST_CHECKED == IsDlgButtonChecked(GetHandle(), IDC_CHK_BGSCAN));
+ break;
+ }
+ return FolderBrowseEx::DialogProc(uMsg, wParam, lParam);
+}
+
+static BOOL LoadXPTheme(void)
+{
+ xpThemeDll = LoadLibraryW(L"UxTheme.dll");
+ if (!xpThemeDll) return FALSE;
+
+ xpCloseThemeData = (XPT_CLOSETHEMEDATA) GetProcAddress(xpThemeDll, "CloseThemeData");
+ xpIsAppThemed = (XPT_ISAPPTHEMED) GetProcAddress(xpThemeDll, "IsAppThemed");
+ xpOpenThemeData = (XPT_OPENTHEMEDATA) GetProcAddress(xpThemeDll, "OpenThemeData");
+ xpGetThemeColor = (XPT_GETTHEMECOLOR) GetProcAddress(xpThemeDll, "GetThemeColor");
+
+ if (!xpCloseThemeData || !xpIsAppThemed || !xpOpenThemeData || !xpGetThemeColor) UnloadXPTheme();
+ return (NULL != xpThemeDll);
+}
+
+static void UnloadXPTheme(void)
+{
+ xpCloseThemeData = NULL;
+ xpIsAppThemed = NULL;
+ xpOpenThemeData = NULL;
+ xpGetThemeColor = NULL;
+ if (xpThemeDll)
+ {
+ FreeLibrary(xpThemeDll);
+ xpThemeDll = NULL;
+ }
+}
+
+static HBRUSH GetBorderBrush(HWND hwnd)
+{
+ HBRUSH hb;
+ COLORREF clr;
+ HRESULT result;
+
+ clr = 0x00000;
+ result = S_FALSE;
+ if(LoadXPTheme())
+ {
+ if(xpIsAppThemed())
+ {
+ HTHEME ht;
+ ht = xpOpenThemeData(GetDlgItem(hwnd, IDC_EDT_PATH), L"Edit");
+ if (ht)
+ {
+ result = xpGetThemeColor(ht, GP_BORDER, BSS_FLAT, TMT_BORDERCOLOR, &clr);
+ xpCloseThemeData(ht);
+ }
+ }
+ UnloadXPTheme();
+ }
+
+ hb = CreateSolidBrush((S_OK == result) ? clr : GetSysColor(COLOR_WINDOWFRAME));
+
+ return hb;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ScanFolderBrowser.h b/Src/Plugins/Library/ml_local/ScanFolderBrowser.h
new file mode 100644
index 00000000..0849779b
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ScanFolderBrowser.h
@@ -0,0 +1,53 @@
+#ifndef NULLSOFT_FOLDERBROWSE_SCANFILES_DIALOG_HEADER
+#define NULLSOFT_FOLDERBROWSE_SCANFILES_DIALOG_HEADER
+
+#include "./folderbrowseex.h"
+
+typedef struct _FBUTTON FBUTTON;
+class ScanFolderBrowser : public FolderBrowseEx
+{
+public:
+ ScanFolderBrowser(void);
+ ScanFolderBrowser(BOOL showBckScanOption);
+ virtual ~ScanFolderBrowser(void);
+
+ void ShowBckScanOption(BOOL show) { bkScanShow = show; }
+ void SetBckScanChecked(BOOL checked) { bkScanChecked = checked; }
+ BOOL GetBckScanChecked(void) { return bkScanChecked; }
+protected:
+ virtual void OnInitialized(void);
+ virtual void OnSelectionChanged(LPCITEMIDLIST pidl);
+ virtual BOOL OnValidateFailed(LPCWSTR lpName);
+ virtual void OnSelectionDone(LPCITEMIDLIST pidl);
+ virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+ void OnWindowPosChanging(WINDOWPOS *lpwp);
+ void OnSize(UINT nType, int cx, int cy);
+ BOOL OnNotify(UINT idCtrl, LPNMHDR pnmh, LRESULT *result);
+ BOOL OnCommand(UINT idCtrl, UINT idEvnt, HWND hwndCtrl);
+ LRESULT OnToolBarCustomDraw(LPNMTBCUSTOMDRAW pnmcd);
+ void OnToolTipGetDispInfo(LPNMTTDISPINFOW lpnmtdi);
+
+private:
+ void LoadBookmarks(void);
+ void FreeBookmarks(void);
+ void ShiftWindows(int cx);
+ void ShrinkWindows(int cx);
+ void RepositionWindows(void);
+
+private:
+ FBUTTON *buttons;
+ int buttonsCount;
+ HBRUSH hbBorder;
+ BOOL bkScanChecked;
+ BOOL bkScanShow;
+
+ IAutoComplete *pac;
+ IACList2 *pacl2;
+
+ wchar_t selectionPath[MAX_PATH]; // this is here only because i'm lazy
+
+ friend static void Initialize(ScanFolderBrowser *browser, BOOL showBckScan, BOOL checkBckScan);
+};
+
+#endif //NULLSOFT_FOLDERBROWSE_SCANFILES_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/SimpleFilter.cpp b/Src/Plugins/Library/ml_local/SimpleFilter.cpp
new file mode 100644
index 00000000..64dd1819
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/SimpleFilter.cpp
@@ -0,0 +1,278 @@
+#include "main.h"
+#include "SimpleFilter.h"
+#include "../nu/sort.h"
+#include "resource.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+static size_t m_sort_by, m_sort_dir, m_sort_which;
+
+void emptyQueryListObject(queryListObject *obj);
+int reallocQueryListObject(queryListObject *obj);
+void freeQueryListObject(queryListObject *obj);
+
+
+int SimpleFilter::SimpleSortFunc(const void *elem1, const void *elem2)
+{
+ queryListItem *a=(queryListItem*)elem1;
+ queryListItem *b=(queryListItem*)elem2;
+ int use_by=m_sort_by;
+ int use_dir=m_sort_dir;
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+ if (use_by == 0)
+ {
+ int v=WCSCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ v=b->ifields[0]-a->ifields[0];
+ RETIFNZ(v)
+ v=b->ifields[1]-a->ifields[1];
+ return v;
+ }
+ if (use_by == 1)
+ {
+ int v=a->ifields[0]-b->ifields[0];
+ RETIFNZ(v)
+
+ if (m_sort_which == 0)
+ {
+ v=b->ifields[1]-a->ifields[1];
+ RETIFNZ(v)
+ }
+
+ return WCSCMP_NULLOK(a->name,b->name);
+ }
+ if (use_by == 2)
+ {
+ int v=a->ifields[1]-b->ifields[1];
+ RETIFNZ(v)
+ if (m_sort_which == 0)
+ {
+ v=b->ifields[0]-a->ifields[0];
+ RETIFNZ(v)
+ }
+ return WCSCMP_NULLOK(a->name,b->name);
+ }
+
+ if (use_by == 5)
+ {
+ __int64 v = a->size - b->size;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+ if (use_by == 6)
+ {
+ int v = a->length - b->length;
+ RETIFNZ(v)
+ return WCSCMP_NULLOK(a->name, b->name);
+ }
+
+#undef RETIFNZ
+ return 0;
+}
+void SimpleFilter::SortResults(C_Config *viewconf, int which, int isfirst) // sorts the results based on the current sort mode
+{
+ if (viewconf)
+ {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d", GetConfigId(), which);
+ m_sort_by = viewconf->ReadInt(buf, 0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_dir_%d", GetConfigId(), which);
+ m_sort_dir = viewconf->ReadInt(buf, 0);
+ m_sort_which = which;
+
+ if (showncolumns.size()>m_sort_by && m_sort_by>=0) m_sort_by = showncolumns[m_sort_by]->field;
+
+ if (m_sort_dir == 0 && m_sort_by == 0 && isfirst) return;
+
+ if (artistList.Size > 2) qsort(artistList.Items+1,artistList.Size-1,sizeof(queryListItem),SimpleSortFunc);
+ }
+}
+
+void SimpleFilter::Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)
+{
+ if (numitems > 1)
+ qsort_itemRecord(items,numitems, this, BuildSortFunc);
+
+ if (killswitch && *killswitch) return;
+
+ emptyQueryListObject(&artistList);
+ reallocQueryListObject(&artistList);
+ ZeroMemory(&artistList.Items[0],sizeof(queryListItem));
+
+ wchar_t *lastname=0;
+ const wchar_t *lastalb=0;
+ wchar_t albbuf[100] = {0}, albbuf2[100] = {0}, albbuf3[100] = {0};
+
+ itemRecordW *p=items;
+ int n=numitems;
+
+ int isbl=0;
+ numGroups = 0;
+ bool do_wcsdup = !this->uses_nde_strings();
+ while (n--)
+ {
+ if (killswitch && *killswitch) return;
+ const wchar_t *name = this->GroupText(p,albbuf3,100);
+ if (!lastname || WCSCMP_NULLOK(lastname, name))
+ {
+ artistList.Size++;
+ if (reallocQueryListObject(&artistList)) break;
+ ZeroMemory(&artistList.Items[artistList.Size],sizeof(queryListItem));
+ lastalb=0;
+ if (name == albbuf3)
+ lastname = artistList.Items[artistList.Size].name = ndestring_wcsdup(name);
+ else if (name)
+ {
+ if (do_wcsdup)
+ {
+ lastname = artistList.Items[artistList.Size].name = ndestring_wcsdup(name);
+ }
+ else
+ {
+ wchar_t *ndename = (wchar_t *)name;
+ ndestring_retain(ndename);
+ lastname = artistList.Items[artistList.Size].name = ndename;
+ }
+ }
+ else
+ {
+ lastname = artistList.Items[artistList.Size].name = emptyQueryListString;
+ }
+
+ SKIP_THE_AND_WHITESPACEW(lastname) // optimization technique
+ if (*lastname) numGroups++;
+ }
+ if (!name || !*name) isbl++;
+ if (artistList.Size)
+ {
+ artistList.Items[artistList.Size].ifields[1]++;
+ if (p->length>0) artistList.Items[artistList.Size].length += p->length;
+ if (p->filesize>0) artistList.Items[artistList.Size].size += p->filesize;
+ }
+ if (nextFilter && (!lastalb || WCSCMP_NULLOK(lastalb,nextFilter->GroupText(p,albbuf2,100))))
+ {
+ lastalb = nextFilter->GroupText(p,albbuf,100);
+ if (lastalb && *lastalb) artistList.Items[artistList.Size].ifields[0]++;
+ if (lastalb) SKIP_THE_AND_WHITESPACEW(lastalb) // optimization technique
+ }
+ p++;
+ }
+
+ if (killswitch && *killswitch) return;
+
+ wchar_t langbuf[2048] = {0}, buf[64] = {0};
+ if (isbl)
+ {
+ StringCchPrintfW(buf,64,WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_S, langbuf, 2048),artistList.Size-1,artistList.Size==2?GetNameSingular():GetName());
+ }
+ else
+ {
+ StringCchPrintfW(buf,64,WASABI_API_LNGSTRINGW_BUF(IDS_ALL_X_S, langbuf, 2048),artistList.Size,artistList.Size==1?GetNameSingular():GetName());
+ }
+
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(buf);
+
+ artistList.Items[0].name=ndestring_wcsdup(buf);
+ artistList.Items[0].ifields[0]=numitems2;
+ artistList.Items[0].ifields[1]=numitems;
+ artistList.Size++;
+}
+
+int SimpleFilter::Size()
+{
+ return artistList.Size;
+}
+
+const wchar_t *SimpleFilter::GetText(int row) // gets main text (first column)
+{
+ return artistList.Items[row].name;
+}
+
+void SimpleFilter::Empty()
+{
+ freeQueryListObject(&artistList);
+}
+
+void SimpleFilter::CopyText(int row, size_t column, wchar_t *dest, int destCch)
+{
+ if (column >= showncolumns.size()) return;
+ column = showncolumns[column]->field;
+ if (row>=artistList.Size)
+ return ;
+ switch (column)
+ {
+ case 0: // artist name
+ if (artistList.Items[row].name && *artistList.Items[row].name)
+ lstrcpynW(dest,artistList.Items[row].name,destCch);
+ else
+ {
+ StringCchPrintfW(dest, destCch, WASABI_API_LNGSTRINGW(IDS_NO_S), GetNameSingular());
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(dest);
+ }
+ break;
+ case 1: // albums
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[0]);
+ break;
+ case 2: // tracks
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[1]);
+ break;
+ case 5:
+ if (row && artistList.Items[row].size)
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, artistList.Items[row].size);
+ else dest[0]=0;
+ break;
+ case 6:
+ if (row && artistList.Items[row].length)
+ StringCchPrintfW(dest,destCch,L"%d:%02d", artistList.Items[row].length / 60, artistList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ }
+}
+
+const wchar_t * SimpleFilter::CopyText2(int row, size_t column, wchar_t *dest, int destCch)
+{
+ if (column >= showncolumns.size()) return NULL;
+ column = showncolumns[column]->field;
+ if (row>=artistList.Size)
+ return NULL;
+ switch (column)
+ {
+ case 0: // artist name
+ if (artistList.Items[row].name && *artistList.Items[row].name)
+ dest = artistList.Items[row].name;
+ else
+ {
+ StringCchPrintfW(dest, destCch, WASABI_API_LNGSTRINGW(IDS_NO_S), GetNameSingular());
+ // for some languages a lowercased first word is invalid so need to prevent this from happening
+ process_substantives(dest);
+ }
+ break;
+ case 1: // albums
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[0]);
+ break;
+ case 2: // tracks
+ StringCchPrintfW(dest,destCch,L"%d",artistList.Items[row].ifields[1]);
+ break;
+ case 5:
+ if (row && artistList.Items[row].size)
+ WASABI_API_LNG->FormattedSizeString(dest, destCch, artistList.Items[row].size);
+ else dest[0]=0;
+ break;
+ case 6:
+ if (row && artistList.Items[row].length)
+ StringCchPrintfW(dest,destCch,L"%d:%02d", artistList.Items[row].length / 60, artistList.Items[row].length % 60);
+ else dest[0]=0;
+ break;
+ }
+ return dest;
+}
+void SimpleFilter::AddColumns2()
+{
+ showncolumns.push_back(new ListField(0,155,GetNameSingular(),g_view_metaconf,GetConfigId()));
+ if (nextFilter) showncolumns.push_back(new ListField(1,48,nextFilter->GetName(),g_view_metaconf,GetConfigId()));
+ showncolumns.push_back(new ListField(2,45,IDS_TRACKS,g_view_metaconf,GetConfigId()));
+ showncolumns.push_back(new ListField(5,45,IDS_SIZE,g_view_metaconf,GetConfigId(),true,true));
+ showncolumns.push_back(new ListField(6,45,IDS_LENGTH,g_view_metaconf,GetConfigId(),true,true));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/SimpleFilter.h b/Src/Plugins/Library/ml_local/SimpleFilter.h
new file mode 100644
index 00000000..aaad01a9
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/SimpleFilter.h
@@ -0,0 +1,169 @@
+#ifndef NULLSOFT_ML_LOCAL_SIMPLEFILTER_H
+#define NULLSOFT_ML_LOCAL_SIMPLEFILTER_H
+
+#include "ViewFilter.h"
+#include "resource.h"
+
+class SimpleFilter : public ViewFilter
+{
+public:
+ static int SimpleSortFunc(const void *elem1, const void *elem2);
+ void SortResults(C_Config *viewconf, int which=0, int isfirst=0);
+ void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2);
+ int Size();
+ const wchar_t *GetText(int row);
+ void CopyText(int row, size_t column, wchar_t *dest, int destCch);
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch);
+ void Empty();
+ void AddColumns2();
+ // override these 4 to make a simple filter
+ virtual const wchar_t *GetField()=0;
+ virtual const wchar_t *GetName()=0;
+ virtual const wchar_t *GetNameSingular()=0;
+ virtual const wchar_t *GetNameSingularAlt(int mode)=0;
+ virtual const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len)=0;
+ wchar_t name[64];
+ wchar_t sname[64];
+ wchar_t saname[64];
+ virtual bool uses_nde_strings() { return true; }
+private:
+ queryListObject artistList;
+};
+
+class AlbumArtistFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_albumartist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTISTS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTIST,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ALBUM_ARTIST_ALT:(mode==1?2065:2081)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->albumartist;}
+ char * GetConfigId(){return "av_aa";}
+};
+
+class ArtistFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_artist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST_S,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ARTIST_ALT:(mode==1?2066:2082)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->artist;}
+ char * GetConfigId(){return "av_ar";}
+};
+
+class ComposerFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_composer;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_COMPOSERS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_COMPOSER,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_COMPOSER_ALT:(mode==1?2067:2083)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->composer;}
+ char * GetConfigId(){return "av_c";}
+};
+
+class GenreFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_genre;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_GENRES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_GENRE,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_GENRE_ALT:(mode==1?2068:2084)),saname,64);}
+ const wchar_t * GroupText(itemRecordW * item, wchar_t * buf, int len){return item->genre;}
+ char * GetConfigId(){return "av_g";}
+};
+
+class PublisherFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_publisher;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_PUBLISHERS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_PUBLISHER,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_PUBLISHER_ALT:(mode==1?2069:2085)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->publisher;}
+ char * GetConfigId(){return "av_p";}
+};
+
+class YearFilter : public SimpleFilter
+{
+ const wchar_t *GetComparisonOperator() {return L"==";}
+ const wchar_t *GetField(){return DB_FIELDNAME_year;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_YEARS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_YEAR,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_YEAR_ALT:(mode==1?2070:2086)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){if(item->year>0) wsprintfW(buf,L"%d",item->year); else buf[0]=0; return buf;}
+ char * GetConfigId(){return "av_y";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+static wchar_t getIndex(const wchar_t* str) {
+ if(!str) return 0;
+ SKIP_THE_AND_WHITESPACEW(str);
+ return towupper(*str);
+}
+
+class AlbumArtistIndexFilter : public SimpleFilter
+{
+ const wchar_t *GetComparisonOperator() {return L"BEGINSLIKE";}
+ const wchar_t *GetField(){return DB_FIELDNAME_albumartist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTIST_INDEXES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ALBUM_ARTIST_INDEX,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ALBUM_ARTIST_INDEX_ALT:(mode==1?2071:2087)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len) {buf[0]=getIndex(item->albumartist); buf[1]=0; return buf;}
+ char * GetConfigId(){return "av_aai";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class ArtistIndexFilter : public SimpleFilter
+{
+ const wchar_t *GetComparisonOperator() {return L"BEGINSLIKE";}
+ const wchar_t *GetField(){return DB_FIELDNAME_artist;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST_INDEXES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_ARTIST_INDEX,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_ARTIST_INDEX_ALT:(mode==1?2072:2088)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len) {buf[0]=getIndex(item->artist); buf[1]=0; return buf;}
+ char * GetConfigId(){return "av_ai";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class PodcastChannelFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_podcastchannel;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_CHANNELS,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_CHANNEL,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_PODCAST_CHANNEL_ALT:(mode==1?2073:2089)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return getRecordExtendedItem_fast(item,extended_fields.podcastchannel);}
+ char * GetConfigId(){return "av_pc";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class CategoryFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return L"category";}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_CATEGORIES,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_CATEGORY,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_CATEGORY_ALT:(mode==1?2074:2090)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return item->category;}
+ char * GetConfigId(){return "av_cat";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class DirectorFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return DB_FIELDNAME_director;}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_DIRECTOR,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_DIRECTOR,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_DIRECTOR_ALT:(mode==1?2075:2091)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return getRecordExtendedItem_fast(item,extended_fields.director);}
+ char * GetConfigId(){return "av_drt";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+class ProducerFilter : public SimpleFilter
+{
+ const wchar_t *GetField(){return L"producer";}
+ const wchar_t *GetName(){return WASABI_API_LNGSTRINGW_BUF(IDS_PRODUCER,name,64);}
+ const wchar_t *GetNameSingular(){return WASABI_API_LNGSTRINGW_BUF(IDS_PRODUCER,sname,64);}
+ const wchar_t *GetNameSingularAlt(int mode){return WASABI_API_LNGSTRINGW_BUF((!mode?IDS_PRODUCER_ALT:(mode==1?2076:2092)),saname,64);}
+ const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len){return getRecordExtendedItem_fast(item,extended_fields.producer);}
+ char * GetConfigId(){return "av_pdc";}
+ virtual bool uses_nde_strings() { return false; }
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/TitleInfo.cpp b/Src/Plugins/Library/ml_local/TitleInfo.cpp
new file mode 100644
index 00000000..3d52a4c1
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/TitleInfo.cpp
@@ -0,0 +1,321 @@
+#include "main.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/ns_wc.h"
+#include <shlwapi.h>
+#include <malloc.h> // for alloca
+
+static wchar_t m_lastfn[2048];
+static wchar_t m_lastartist[256], m_lasttitle[256], m_lastalbum[256], m_gracenotefileid[128];
+static int m_lasttrack;
+static int m_playcount;
+static int m_ispodcast;
+static int m_rating;
+static int m_db_found;
+
+void ClearTitleHookCache()
+{
+ memset(m_lastfn, 0, sizeof(m_lastfn));
+}
+
+BOOL IPC_HookExtInfoW(INT_PTR param)
+{
+ if (skipTitleInfo) // we're reading metadata and being asked to skip title hooking (so we can get a valid title for guessing, if need be)
+ return false;
+
+ extendedFileInfoStructW *p = (extendedFileInfoStructW *)param;
+ // fill in our own titles from db? not for now, just let it default to hitting the tags?
+
+ if (!g_config->ReadInt(L"newtitle", 1))
+ return FALSE;
+
+ if (NULL == p->filename || L'\0' == *p->filename ||
+ NULL == p->metadata || L'\0' == *p->metadata)
+ {
+ return FALSE;
+ }
+
+ int which = 0;
+ if (!_wcsicmp(p->metadata, DB_FIELDNAME_artist )) which = 1;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_title )) which = 2;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_album )) which = 3;
+ //else if (!_wcsicmp(p->metadata, L"tuid")) which = 4;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_track )) which = 5;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_rating )) which = 6;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_playcount )) which = 7;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_GracenoteFileID )) which=8;
+ else if (!_wcsicmp(p->metadata, DB_FIELDNAME_ispodcast )) which=9;
+
+ if (which)
+ {
+ if (_wcsicmp(m_lastfn, p->filename))
+ {
+ if (!g_table) openDb();
+ if (!g_table) return 0;
+
+ m_lastartist[0] = 0;
+ m_lasttitle[0] = 0;
+ m_lastalbum[0] = 0;
+ m_gracenotefileid[0]=0;
+ m_lasttrack = 0;
+ m_rating=-1;
+ m_playcount=-1;
+ m_ispodcast=-1;
+ lstrcpynW(m_lastfn, p->filename, sizeof(m_lastfn)/sizeof(wchar_t));
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found = FindFileInDatabase(s, MAINTABLE_ID_FILENAME, m_lastfn, filename2);
+
+ if (found == 2)
+ lstrcpynW(m_lastfn, filename2, sizeof(m_lastfn)/sizeof(m_lastfn));
+
+ if (found)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ARTIST);
+ if (f) lstrcpynW(m_lastartist, NDE_StringField_GetString(f), sizeof(m_lastartist)/sizeof(wchar_t));
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ALBUM);
+ if (f) lstrcpynW(m_lastalbum, NDE_StringField_GetString(f), sizeof(m_lastalbum)/sizeof(wchar_t));
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TITLE);
+ if (f) lstrcpynW(m_lasttitle, NDE_StringField_GetString(f), sizeof(m_lasttitle)/sizeof(wchar_t));
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TRACKNB);
+ if (f) m_lasttrack = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
+ if (f) m_playcount = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_RATING);
+ if (f) m_rating = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ISPODCAST);
+ if (f) m_ispodcast = NDE_IntegerField_GetValue(f);
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_GRACENOTEFILEID);
+ if (f) lstrcpynW(m_gracenotefileid, NDE_StringField_GetString(f), sizeof(m_gracenotefileid)/sizeof(wchar_t));
+ m_db_found = 1;
+ }
+ else m_db_found = 0;
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ }
+ if (m_db_found == 1)
+ {
+ switch(which)
+ {
+ case 1:
+ if (m_lastartist[0])
+ {
+ lstrcpynW(p->ret, m_lastartist, p->retlen);
+ return 1;
+ }
+ break;
+ case 2:
+ if (m_lasttitle[0])
+ {
+ lstrcpynW(p->ret, m_lasttitle, p->retlen);
+ return 1;
+ }
+ break;
+ case 3:
+ if (m_lastalbum[0])
+ {
+ lstrcpynW(p->ret, m_lastalbum, p->retlen);
+ return 1;
+ }
+ break;
+ case 5:
+ if (m_lasttrack && m_lasttrack != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_lasttrack);
+ return 1;
+ }
+ break;
+ case 6:
+ if (m_rating != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_rating);
+ return 1;
+ }
+ break;
+ case 7:
+ if (m_playcount != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_playcount);
+ return 1;
+ }
+ break;
+ case 8:
+ if (m_gracenotefileid[0])
+ {
+ lstrcpynW(p->ret, m_gracenotefileid, p->retlen);
+ return 1;
+ }
+ break;
+ case 9:
+ if (m_ispodcast != -1)
+ {
+ _snwprintf(p->ret, p->retlen, L"%d", m_ispodcast);
+ return 1;
+ }
+ break;
+ }
+ }
+ }
+ return FALSE;
+}
+
+BOOL IPC_HookExtInfo(INT_PTR param)
+{
+ extendedFileInfoStruct *p = (extendedFileInfoStruct*)param;
+ // fill in our own titles from db? not for now, just let it default to hitting the tags?
+ if (!g_config->ReadInt(L"newtitle", 1))
+ return FALSE;
+
+ if (NULL == p->filename || '\0' == *p->filename ||
+ NULL == p->metadata || '\0' == *p->metadata)
+ {
+ return FALSE;
+ }
+
+ extendedFileInfoStructW pW = {0};
+ AutoWide wideFn(p->filename), wideMetadata(p->metadata);
+ pW.filename = wideFn;
+ pW.metadata = wideMetadata;
+ pW.retlen = p->retlen;
+ pW.ret = (wchar_t *)alloca(pW.retlen * sizeof(wchar_t));
+
+ if (IPC_HookExtInfoW((INT_PTR)&pW))
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, pW.ret, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ return 0;
+
+}
+
+BOOL IPC_HookTitleInfo(INT_PTR param)
+{
+ if (skipTitleInfo) // we're reading metadata and being asked to skip title hooking (so we can get a valid title for guessing, if need be)
+ return false;
+
+ waHookTitleStructW *hts = (waHookTitleStructW*)param;
+
+ if (NULL != hts->filename &&
+ !StrStrW(hts->filename, L"://") &&
+ g_config->ReadInt(L"newtitle", 1))
+ {
+ if (!g_table) openDb();
+ if (!g_table) return 0;
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, hts->filename, filename2);
+
+ if (found)
+ {
+ nde_field_t length = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LENGTH);
+ int l = -1;
+ if (length)
+ l = NDE_IntegerField_GetValue(length);
+ if (l > 0)
+ hts->length = l;
+ else hts->length = -1;
+
+ if (hts->title)
+ {
+ TAG_FMT_EXT(hts->filename, fieldTagFunc, ndeTagFuncFree, (void*)s, hts->title, 2048, 1);
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (found) return 1;
+ } // not http://
+ return FALSE;
+}
+
+DWORD doGuessProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ // call the old wndproc, and if it produces empty results or no results, try to guess
+ extendedFileInfoStruct *p = (extendedFileInfoStruct*)wParam;
+
+ LRESULT ret = CallWindowProc(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+ if (NULL != p &&
+ (!ret || !p->ret[0]) &&
+ g_config->ReadInt(L"guessmode", 0) != 2)
+ {
+ int which = 0;
+
+ if (NULL != p->metadata)
+ {
+ if (!_stricmp(p->metadata, "artist")) which = 1;
+ else if (!_stricmp(p->metadata, "title")) which = 2;
+ else if (!_stricmp(p->metadata, "album")) which = 3;
+ else if (!_stricmp(p->metadata, "track")) which = 5;
+ else which = 0;
+ }
+ else
+ which = 0;
+
+ if (which)
+ {
+ static char m_lastfn[2048];
+ if (NULL != p->filename && _strnicmp(m_lastfn, p->filename, sizeof(m_lastfn)))
+ {
+ m_db_found = 2;
+ m_lastartist[0] = 0;
+ m_lasttitle[0] = 0;
+ m_lastalbum[0] = 0;
+ m_lasttrack = 0;
+ StringCbCopyA(m_lastfn, sizeof(m_lastfn), p->filename);
+
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0;
+ wchar_t *guessbuf = guessTitles(AutoWide(m_lastfn), &tn, &artist, &album, &title);
+
+ if (guessbuf)
+ {
+ if (artist) StringCbCopyW(m_lastartist, sizeof(m_lastartist), artist);
+ if (album) StringCbCopyW(m_lastalbum, sizeof(m_lastalbum), album);
+ if (title) StringCbCopyW(m_lasttitle, sizeof(m_lasttitle), title);
+ m_lasttrack = tn;
+ free(guessbuf);
+ }
+ }
+ if (m_db_found == 2)
+ {
+ if (which == 1 && m_lastartist[0])
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, m_lastartist, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ if (which == 2 && m_lasttitle[0])
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, m_lasttitle, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ if (which == 3 && m_lastalbum[0])
+ {
+ WideCharToMultiByteSZ(CP_ACP, 0, m_lastalbum, -1, p->ret, p->retlen, 0, 0);
+ return 1;
+ }
+ if (which == 5 && m_lasttrack)
+ {
+ _snprintf(p->ret, p->retlen, "%d", m_lasttrack);
+ return 1;
+ }
+ if (which == 6 && m_rating != -1)
+ {
+ _snprintf(p->ret, p->retlen, "%d", m_rating);
+ return 1;
+ }
+ if (which == 7 && m_playcount != -1)
+ {
+ _snprintf(p->ret, p->retlen, "%d", m_playcount);
+ return 1;
+ }
+ }
+ }
+ }
+ return (int)ret;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ViewFilter.cpp b/Src/Plugins/Library/ml_local/ViewFilter.cpp
new file mode 100644
index 00000000..4a0def1d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ViewFilter.cpp
@@ -0,0 +1,431 @@
+#include "main.h"
+#include "ViewFilter.h"
+#include "resource.h"
+#include "AlbumArtContainer.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <algorithm>
+
+wchar_t *emptyQueryListString = L"";
+void emptyQueryListObject(queryListObject *obj)
+{
+ queryListItem *p = obj->Items;
+ while (obj->Size-- > 0)
+ {
+ if (p->name && p->name != emptyQueryListString)
+ ndestring_release(p->name);
+ ndestring_release(p->albumGain);
+ ndestring_release(p->gracenoteFileId);
+ ndestring_release(p->genre);
+ if (p->artist && p->artist != emptyQueryListString)
+ ndestring_release(p->artist);
+
+ if(p->art) { p->art->updateMsg.hwnd = 0; p->art->Release(); }
+ p++;
+ }
+ obj->Size = 0;
+}
+
+void freeQueryListObject(queryListObject *obj)
+{
+ emptyQueryListObject(obj);
+ free(obj->Items);
+ obj->Items = 0;
+ obj->Alloc = obj->Size = 0;
+}
+
+int reallocQueryListObject(queryListObject *obj) // 0 on success
+{
+ if (obj->Size >= obj->Alloc)
+ {
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc = obj->Size + 32;
+ queryListItem *data = (queryListItem*)realloc(obj->Items, sizeof(queryListItem) * obj->Alloc);
+ if (data)
+ {
+ obj->Items = data;
+ }
+ else
+ {
+ data = (queryListItem*)malloc(sizeof(queryListItem) * obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(queryListItem) * old_Alloc);
+ free(obj->Items);
+ obj->Items = data;
+ }
+ else obj->Alloc = old_Alloc;
+ }
+
+ if (!obj->Items)
+ {
+ obj->Alloc = 0;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int ViewFilter::BuildSortFunc(const void *elem1, const void *elem2, const void *context)
+{
+ ViewFilter *sortFilter = (ViewFilter *)context;
+ itemRecordW *a = (itemRecordW *)elem1;
+ itemRecordW *b = (itemRecordW *)elem2;
+ wchar_t ab[100] = {0}, bb[100] = {0};
+ int v=WCSCMP_NULLOK(sortFilter->GroupText(a,ab,100),sortFilter->GroupText(b,bb,100));
+ if (v)
+ return v;
+ if(sortFilter->nextFilter)
+ return WCSCMP_NULLOK(sortFilter->nextFilter->GroupText(a,ab,100),sortFilter->nextFilter->GroupText(b,bb,100));
+ return 0;
+}
+
+static int sortFunc_cols(const void *elem1, const void *elem2)
+{
+ ListField * a = (ListField *)elem1;
+ ListField * b = (ListField *)elem2;
+ return a->pos - b->pos;
+}
+static bool sortFunc_cols_V2(const void* elem1, const void* elem2)
+{
+ return sortFunc_cols(elem1, elem2) < 0;
+}
+void ViewFilter::AddColumns() {
+ ClearColumns();
+
+ AddColumns2();
+
+ for(unsigned int i=0; i<showncolumns.size(); i++){
+ ListField *l = showncolumns[i];
+ if(l->hidden) {
+ hiddencolumns.push_back(l);
+ showncolumns.erase(showncolumns.begin() + i);
+ i--;
+ }
+ }
+ //qsort(showncolumns.first(),showncolumns.size(),sizeof(void*),sortFunc_cols);
+ std::sort(showncolumns.begin(), showncolumns.end(), sortFunc_cols_V2);
+
+ for ( ListField *l_column : showncolumns )
+ list->AddCol( l_column->name, l_column->width );
+}
+
+void ViewFilter::ClearColumns()
+{
+ for ( ListField *l_shown_column : showncolumns )
+ delete l_shown_column;
+
+ showncolumns.clear();
+
+
+ for ( ListField *l_hidden_column : hiddencolumns )
+ delete l_hidden_column;
+
+ hiddencolumns.clear();
+}
+
+void ViewFilter::SaveColumnWidths()
+{
+ char *av = GetConfigId();
+ for ( size_t i = 0; i < showncolumns.size(); i++ )
+ {
+ wchar_t configname[ 256 ] = { 0 };
+ int field = showncolumns[ i ]->field;
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d", av, field );
+ g_view_metaconf->WriteInt( configname, list->GetColumnWidth( i ) );
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d_pos", av, field );
+ g_view_metaconf->WriteInt( configname, i );
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d_hidden", av, field );
+ g_view_metaconf->WriteInt( configname, 0 );
+ }
+
+ for ( ListField *l_hidden_column : hiddencolumns )
+ {
+ wchar_t configname[ 256 ] = { 0 };
+ StringCchPrintfW( configname, ARRAYSIZE( configname ), L"%hs_col_%d_hidden", av, l_hidden_column->field );
+ g_view_metaconf->WriteInt( configname, 1 );
+ }
+}
+
+void ViewFilter::ResetColumns()
+{
+ for ( unsigned int i = 0; i < hiddencolumns.size(); i++ )
+ {
+ showncolumns.push_back( hiddencolumns[ i ] );
+ hiddencolumns.erase( hiddencolumns.begin() + i );
+ i--;
+ }
+
+ for ( unsigned int i = 0; i < showncolumns.size(); i++ )
+ {
+ ListField *l = showncolumns[ i ];
+ l->ResetPos();
+ if ( l->hidden )
+ {
+ hiddencolumns.push_back( l );
+ showncolumns.erase( showncolumns.begin() + i );
+ i--;
+ }
+ }
+ //qsort(showncolumns.first(),showncolumns.size(),sizeof(void*),sortFunc_cols);
+ std::sort( showncolumns.begin(), showncolumns.end(), sortFunc_cols_V2 );
+
+ for ( ListField *l_show_column : showncolumns )
+ list->AddCol( l_show_column->name, l_show_column->width );
+}
+
+ListField::ListField(int field, int width0, const wchar_t * name, C_Config * config,char * av, bool hidden0, bool hiddenDefault, bool readini,int pos):field(field),name(name),hiddenDefault(hiddenDefault),hidden(hidden0),pos(pos),width(width0) {
+ this->name = _wcsdup(name);
+ if(readini) {
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d", av, field);
+ this->width = config->ReadInt(buf, width0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_pos", av, field);
+ this->pos = config->ReadInt(buf, field);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_hidden", av, field);
+ this->hidden = config->ReadInt(buf, hidden0 ? 1: 0 ) != 0;
+ }
+}
+
+ListField::ListField(int field, int width0, int name0, C_Config * config,char * av, bool hidden0, bool hiddenDefault, bool readini,int pos):field(field),name(name),hiddenDefault(hiddenDefault),hidden(hidden0),pos(pos),width(width0) {
+ this->name = _wcsdup(WASABI_API_LNGSTRINGW(name0));
+ if(readini) {
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d", av, field);
+ this->width = config->ReadInt(buf,width0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_pos", av, field);
+ this->pos = config->ReadInt(buf,field);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_col_%d_hidden", av, field);
+ this->hidden = config->ReadInt(buf,hidden0?1:0)!=0;
+ }
+}
+
+void ListField::ResetPos() { pos = field; hidden = hiddenDefault;}
+
+static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND m_curlistbox_hwnd, m_availlistbox_hwnd;
+ static ViewFilter *filter;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1);
+ m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2);
+ filter = (ViewFilter*)lParam;
+
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, TRUE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, TRUE);
+ }
+ case WM_USER + 32:
+ {
+ for ( ListField *l : filter->showncolumns )
+ {
+ int r = SendMessageW( m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name );
+ SendMessageW( m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l );
+ }
+
+ for ( ListField *l : filter->hiddencolumns )
+ {
+ int r = SendMessageW( m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name );
+ SendMessageW( m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l );
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_DEFS:
+ SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ filter->ResetColumns();
+ SendMessage(hwndDlg, WM_USER + 32, 0, 0);
+ break;
+ case IDC_LIST2:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON2:
+ //add column
+ {
+ for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i--, 0);
+ int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0);
+ }
+ break;
+ case IDC_LIST1:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON3:
+ //remove column
+ {
+ for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+ int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0);
+ }
+ break;
+ case IDC_BUTTON4:
+ //move column up
+ {
+ for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i - 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON5:
+ //move column down
+ {
+ int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (int i = l - 2;i >= 0;i--)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i + 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDOK:
+ // read and apply changes...
+ {
+ filter->showncolumns.clear();
+ filter->hiddencolumns.clear();
+ int i;
+ int l = (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ filter->showncolumns.push_back(c);
+ c->pos=i;
+ c->hidden=false;
+ }
+ l = (INT)SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ filter->hiddencolumns.push_back(c);
+ c->hidden=true;
+ }
+ filter->SaveColumnWidths();
+ }
+ EndDialog(hwndDlg, 1);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ }
+ break;
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, FALSE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, FALSE);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+
+void ViewFilter::CustomizeColumns( HWND parent, BOOL showmenu )
+{
+ if ( showmenu )
+ {
+ HMENU menu = GetSubMenu( g_context_menus, 4 );
+ POINT p;
+ GetCursorPos( &p );
+ int r = TrackPopupMenu( menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, 0, parent, NULL );
+ if ( r != ID_HEADERWND_CUSTOMIZECOLUMNS ) return;
+ }
+
+ bool r = !!WASABI_API_DIALOGBOXPARAMW( IDD_CUSTCOLUMNS, parent, custColumns_dialogProc, (LPARAM)this );
+ MSG msg;
+ while ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ); //eat return
+ if ( r )
+ {
+ while ( ListView_DeleteColumn( list->getwnd(), 0 ) );
+ for ( ListField *l_show_column : showncolumns )
+ list->AddCol( l_show_column->name, l_show_column->width );
+ }
+}
+
+HMENU ViewFilter::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu) {
+ HMENU menu = GetSubMenu(themenu, 5);
+ wchar_t scrollConf[] = L"av0_hscroll";
+ scrollConf[2] = (wchar_t)('0'+filterNum);
+ BOOL enablescroll = c->ReadInt(scrollConf, 0);
+ CheckMenuItem(menu,ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR,enablescroll?MF_CHECKED:MF_UNCHECKED);
+
+ if(isFilter) {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i=0;
+ while(GetMenuItemInfo(menu,i,TRUE,&m)) {
+ m.wID |= (1+filterNum) << 16;
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ i++;
+ }
+ }
+ return menu;
+}
+
+void ViewFilter::ProcessMenuResult(int r, bool isFilter, int editFilter, C_Config *c, HWND hwndDlg) {
+ int mid = (r >> 16);
+ if(!isFilter && mid) return;
+ if(isFilter && mid-1 != editFilter) return;
+ r &= 0xFFFF;
+
+ switch(r) {
+ case ID_HEADERWND_CUSTOMIZECOLUMNS:
+ CustomizeColumns(hwndDlg, FALSE);
+ break;
+ case ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR:
+ {
+ wchar_t scrollConf[] = L"av0_hscroll";
+ scrollConf[2]=(wchar_t)('0'+editFilter);
+ BOOL enablescroll = !c->ReadInt(scrollConf,0);
+ g_view_metaconf->WriteInt(scrollConf,enablescroll);
+ MLSkinnedScrollWnd_ShowHorzBar(list->getwnd(),enablescroll);
+ }
+ break;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ViewFilter.h b/Src/Plugins/Library/ml_local/ViewFilter.h
new file mode 100644
index 00000000..b3c6c610
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ViewFilter.h
@@ -0,0 +1,89 @@
+#ifndef NULLSOFT_ML_LOCAL_VIEWFILTER_H
+#define NULLSOFT_ML_LOCAL_VIEWFILTER_H
+
+#include "main.h"
+#include "../nu/listview.h"
+#include <vector>
+class AlbumArtContainer;
+
+extern wchar_t *emptyQueryListString;
+struct queryListItem
+{
+ queryListItem() : name(0), albumGain(0), length(0), art(0), artist(0), genre(0),
+ gracenoteFileId(0), rating(0), lastupd(0), size(0) {
+ ZeroMemory(&ifields,sizeof(ifields));
+ }
+ wchar_t *name;
+ wchar_t *albumGain;
+ int length, ifields[3];
+ AlbumArtContainer *art;
+ wchar_t *artist;
+ wchar_t *genre;
+ wchar_t *gracenoteFileId;
+ int rating;
+ __time64_t lastupd;
+ __int64 size;
+} ;
+
+struct queryListObject
+{
+ queryListObject() : Items(0), Size(0), Alloc(0){}
+ queryListItem *Items;
+ int Size, Alloc;
+} ;
+
+class ListField {
+public:
+ int field;
+ int width;
+ int pos;
+ const wchar_t * name;
+ bool hidden, hiddenDefault;
+ ListField(int field, int width, const wchar_t * name, C_Config * config, char * av, bool hidden=false, bool hiddenDefault=false, bool readini=true, int pos=0);
+ ListField(int field, int width, int name, C_Config * config, char * av, bool hidden=false, bool hiddenDefault=false, bool readini=true, int pos=0);
+ ~ListField() { free((void*)name); }
+ void ResetPos();
+};
+
+class ViewFilter
+{
+public:
+ virtual ~ViewFilter(){}
+ virtual void SortResults(C_Config *viewconf, int which=0, int isfirst=0)=0;
+ virtual void Fill(itemRecordW *items, int numitems, int *killswitch, int numitems2)=0;
+ virtual int Size()=0;
+ virtual const wchar_t *GetText(int row)=0;
+ virtual void CopyText(int row, size_t column, wchar_t *dest, int destCch)=0;
+ virtual const wchar_t *CopyText2(int row, size_t column, wchar_t *dest, int destCch)=0;
+ virtual void Empty()=0;
+ virtual const wchar_t *GetField()=0;
+ virtual const wchar_t *GetName()=0;
+ virtual const wchar_t *GetNameSingular()=0;
+ virtual const wchar_t *GetNameSingularAlt(int mode)=0;
+
+ virtual const wchar_t *GroupText(itemRecordW * item, wchar_t * buf, int len)=0;
+ virtual void AddColumns2()=0;
+ void AddColumns();
+ virtual void SaveColumnWidths();
+ static int __fastcall BuildSortFunc(const void *elem1, const void *elem2, const void *context);
+ virtual void CustomizeColumns(HWND parent, BOOL showmenu);
+ void ResetColumns();
+ virtual const wchar_t *GetComparisonOperator() {return L"LIKE";}
+ virtual char * GetConfigId(){return "av";}
+ void ClearColumns();
+ virtual INT_PTR DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam){return 0;}
+ virtual bool HasTopItem() {return true;}
+ virtual bool MakeFilterQuery(int n, GayStringW *str){return false;}
+
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+ virtual void MetaUpdate(int r, const wchar_t *metaItem, const wchar_t *value) {}
+
+ std::vector<ListField*> showncolumns;
+ std::vector<ListField*> hiddencolumns;
+ ViewFilter *nextFilter;
+ W_ListView * list;
+ int numGroups;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/add.cpp b/Src/Plugins/Library/ml_local/add.cpp
new file mode 100644
index 00000000..d99c93b8
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/add.cpp
@@ -0,0 +1,478 @@
+#include "main.h"
+#include "ml_local.h"
+#include "api_mldb.h"
+#include <commctrl.h>
+#include "resource.h"
+#include "../replicant/nu/ns_wc.h"
+#include "../nde/nde.h"
+#include "../Agave/Language/api_language.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "time.h"
+#include "../winamp/in2.h"
+#include "../Winamp/strutil.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+
+bool skipTitleInfo=false;
+
+static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len)
+{
+ dest[0]=0;
+ return AGAVE_API_METADATA->GetExtendedFileInfo(filename, metadata, dest, len);
+}
+
+static time_t FileTimeToUnixTime(FILETIME *ft)
+{
+ ULARGE_INTEGER end;
+ memcpy(&end,ft,sizeof(end));
+ end.QuadPart -= 116444736000000000;
+ end.QuadPart /= 10000000; // 100ns -> seconds
+ return (time_t)end.QuadPart;
+}
+
+void makeFilename2W(const wchar_t *filename, wchar_t *filename2, int filename2_len)
+{
+ filename2[0]=0;
+ GetLongPathNameW(filename, filename2, filename2_len);
+ if (!_wcsicmp(filename,filename2)) filename2[0]=0;
+}
+
+void makeFilename2(const char *filename, char *filename2, int filename2_len)
+{
+ filename2[0]=0;
+ GetLongPathNameA(filename, filename2, filename2_len);
+ if (!stricmp(filename,filename2)) filename2[0]=0;
+}
+
+static __int64 FileSize64(HANDLE file)
+{
+ LARGE_INTEGER position;
+ position.QuadPart=0;
+ position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
+
+ if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
+ return INVALID_FILE_SIZE;
+ else
+ return position.QuadPart;
+}
+
+static void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time)
+{
+ WIN32_FILE_ATTRIBUTE_DATA file_data;
+ if (GetFileAttributesExW(filename, GetFileExInfoStandard, &file_data) == FALSE)
+ {
+ // GetFileAttributesEx failed. that sucks, let's try something else
+ HANDLE hFile=CreateFileW(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ FILETIME lt = {0};
+ if (GetFileTime(hFile,NULL,NULL,&lt))
+ {
+ *file_time=FileTimeToUnixTime(&lt);
+ }
+ *file_size=FileSize64(hFile);
+ CloseHandle(hFile);
+ }
+ }
+ else
+ {
+ // success
+ *file_time = FileTimeToUnixTime(&file_data.ftLastWriteTime);
+ LARGE_INTEGER size64;
+ size64.LowPart = file_data.nFileSizeLow;
+ size64.HighPart = file_data.nFileSizeHigh;
+ *file_size = size64.QuadPart;
+ }
+}
+
+int FindFileInDatabase(nde_scanner_t s, int fieldId, const wchar_t *filename, wchar_t alternate[MAX_PATH])
+{
+ alternate[0]=0;
+
+ makeFilename2W(filename,alternate,MAX_PATH);
+ if (alternate[0])
+ {
+ if (NDE_Scanner_LocateFilename(s, fieldId, FIRST_RECORD, alternate))
+ {
+ return 2;
+ }
+ }
+ if (NDE_Scanner_LocateFilename(s, fieldId, FIRST_RECORD, filename))
+ return 1;
+
+ return 0;
+}
+
+static inline void GetOptionalField(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
+{
+ wchar_t tmp[1024]={0};
+ if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ tmp[1023]=0; // just in case
+ db_setFieldStringW(s, field_id,tmp);
+ }
+ else
+ {
+ db_removeField(s, field_id);
+ }
+ }
+}
+
+static inline void GetOptionalFieldInt(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
+{
+ wchar_t tmp[128]={0};
+ if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ tmp[127]=0; // just in case
+ db_setFieldInt(s,field_id,_wtoi(tmp));
+ }
+ else
+ {
+ db_removeField(s, field_id);
+ }
+ }
+}
+
+static inline void GetNonBlankFieldInt(nde_scanner_t s, const wchar_t *filename, const wchar_t *field, unsigned char field_id)
+{
+ wchar_t tmp[128]={0};
+ if (getFileInfoW(filename,field,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ tmp[127]=0; // just in case
+ db_setFieldInt(s,field_id,_wtoi(tmp));
+ }
+ }
+}
+
+
+// return values
+// 0 - error
+// 1 - success
+// -2 - record not found (in update only mode)
+int addFileToDb(const wchar_t *filename, int onlyupdate, int use_metadata, int guess_mode, int playcnt, int lastplay, bool force)
+{
+ if (!_wcsicmp(PathFindExtensionW(filename), L".cda"))
+ return 0;
+
+ __int64 file_size=INVALID_FILE_SIZE;
+ time_t file_time=0;
+ GetFileSizeAndTime(filename, &file_size, &file_time);
+ if (file_size == INVALID_FILE_SIZE || file_size == 0)
+ return 0;
+
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ int found = FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+
+ if (found) // For updating
+ {
+ // if an update wasn't forced, see if the file's timestamp or filesize have changed
+ if (!force && NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILESIZE))
+ {
+ nde_field_t f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILETIME);
+ if (f && file_time <= NDE_IntegerField_GetValue(f))
+ {
+ f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LASTUPDTIME);
+ if (f && file_time <= NDE_IntegerField_GetValue(f))
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+ }
+ }
+ NDE_Scanner_Edit(s);
+ if (found == 1 && filename2[0]) db_setFieldStringW(s,MAINTABLE_ID_FILENAME,filename2); // if we have a better filename, update it
+ nde_field_t f=NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
+ int cnt = f?NDE_IntegerField_GetValue(f):0;
+ if (!cnt)
+ db_setFieldInt(s,MAINTABLE_ID_PLAYCOUNT,0);
+ }
+ else // Adding an entry from scratch
+ {
+ if (onlyupdate)
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ // Issue a wasabi system callback after we have successfully updated a file in the ml database
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED_EXTERNAL, (size_t)filename, 0);
+ return -2;
+ }
+ // new file
+ NDE_Scanner_New(s);
+ db_setFieldStringW(s,MAINTABLE_ID_FILENAME,filename2[0]?filename2:filename);
+ db_setFieldInt(s,MAINTABLE_ID_PLAYCOUNT,playcnt);
+ if (lastplay)
+ db_setFieldInt(s,MAINTABLE_ID_LASTPLAY,lastplay);
+ db_setFieldInt(s,MAINTABLE_ID_DATEADDED, (int)time(NULL));
+ }
+
+ int hasttitle=0, hastartist=0, hastalbum=0, hasttrack=0;
+ int hastyear=0, hastlength=0;
+ int hasdisc=0, hasalbumartist=0;
+ int hasttype=0, hasdiscs=0, hastracks=0, hasbitrate=0;
+ int the_length_sec=0;
+ wchar_t m_artist[1024] = {0}, tmp[1024] = {0};
+
+ if(getFileInfoW(filename, DB_FIELDNAME_type, tmp, sizeof(tmp) / sizeof(wchar_t)) )
+ {
+ if(tmp[0]) { int type=_wtoi(tmp); db_setFieldInt(s,MAINTABLE_ID_TYPE,type); hasttype++; }
+ }
+
+ if (getFileInfoW(filename, DB_FIELDNAME_length,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0]) { db_setFieldInt(s,MAINTABLE_ID_LENGTH,the_length_sec=(_wtoi(tmp)/1000)); hastlength++; }
+ }
+
+ if (getFileInfoW(filename, DB_FIELDNAME_bitrate,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0]) { db_setFieldInt(s,MAINTABLE_ID_BITRATE,_wtoi(tmp)); hasbitrate++; }
+ }
+
+ if(use_metadata && getFileInfoW(filename, DB_FIELDNAME_title,tmp,sizeof(tmp)/sizeof(wchar_t)))
+ {
+ if(tmp[0])
+ {
+ db_setFieldStringW(s,MAINTABLE_ID_TITLE,tmp);
+ hasttitle++;
+ }
+
+ getFileInfoW( filename, DB_FIELDNAME_artist, tmp, sizeof( tmp ) / sizeof( wchar_t ) );
+
+ if(tmp[0])
+ {
+ StringCchCopyW(m_artist, 1024, tmp);
+ db_setFieldStringW(s,MAINTABLE_ID_ARTIST,tmp);
+ hastartist++;
+ }
+
+ getFileInfoW(filename, DB_FIELDNAME_album,tmp,sizeof(tmp)/sizeof(wchar_t));
+
+ if(tmp[0])
+ {
+ db_setFieldStringW(s,MAINTABLE_ID_ALBUM,tmp);
+ hastalbum++;
+ }
+
+ GetOptionalField(s, filename, DB_FIELDNAME_comment, MAINTABLE_ID_COMMENT);
+
+ getFileInfoW(filename, DB_FIELDNAME_year,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0] && !wcsstr(tmp,L"__") && !wcsstr(tmp,L"/") && !wcsstr(tmp,L"\\") && !wcsstr(tmp,L".")) {
+ wchar_t *p=tmp;
+ while (p && *p)
+ {
+ if (*p == L'_') *p=L'0';
+ p++;
+ }
+ int y=_wtoi(tmp);
+ if(y!=0) { db_setFieldInt(s,MAINTABLE_ID_YEAR,_wtoi(tmp)); hastyear++; }
+ }
+ GetOptionalField(s, filename, DB_FIELDNAME_genre, MAINTABLE_ID_GENRE);
+
+ getFileInfoW(filename, DB_FIELDNAME_track,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0])
+ {
+ int track, tracks;
+ ParseIntSlashInt(tmp, &track, &tracks);
+ if (track > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_TRACKNB,track);
+ hasttrack++;
+ }
+ if (tracks > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_TRACKS,tracks);
+ hastracks++;
+ }
+ }
+ getFileInfoW(filename, DB_FIELDNAME_disc,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0])
+ {
+ int disc, discs;
+ ParseIntSlashInt(tmp, &disc, &discs);
+ if (disc > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_DISC,disc);
+ hasdisc++;
+ }
+ if (discs > 0)
+ {
+ db_setFieldInt(s,MAINTABLE_ID_DISCS,discs);
+ hasdiscs++;
+ }
+ }
+ getFileInfoW(filename, DB_FIELDNAME_albumartist,tmp,sizeof(tmp)/sizeof(wchar_t));
+ if(tmp[0]) { db_setFieldStringW(s,MAINTABLE_ID_ALBUMARTIST,tmp); hasalbumartist++; }
+ GetOptionalField(s, filename, DB_FIELDNAME_publisher, MAINTABLE_ID_PUBLISHER);
+ GetOptionalField(s, filename, DB_FIELDNAME_composer, MAINTABLE_ID_COMPOSER);
+ GetOptionalField(s, filename, DB_FIELDNAME_replaygain_album_gain, MAINTABLE_ID_ALBUMGAIN);
+ GetOptionalField(s, filename, DB_FIELDNAME_replaygain_track_gain, MAINTABLE_ID_TRACKGAIN);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_bpm, MAINTABLE_ID_BPM);
+ GetOptionalField(s, filename, DB_FIELDNAME_GracenoteFileID, MAINTABLE_ID_GRACENOTEFILEID);
+ GetOptionalField(s, filename, DB_FIELDNAME_GracenoteExtData, MAINTABLE_ID_GRACENOTEEXTDATA);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_lossless, MAINTABLE_ID_LOSSLESS);
+ GetOptionalField(s, filename, DB_FIELDNAME_category, MAINTABLE_ID_CATEGORY);
+ GetOptionalField(s, filename, DB_FIELDNAME_codec, MAINTABLE_ID_CODEC);
+ GetOptionalField(s, filename, DB_FIELDNAME_director, MAINTABLE_ID_DIRECTOR);
+ GetOptionalField(s, filename, DB_FIELDNAME_producer, MAINTABLE_ID_PRODUCER);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_width, MAINTABLE_ID_WIDTH);
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_height, MAINTABLE_ID_HEIGHT);
+ if (g_config->ReadInt(L"writeratings", 0))
+ GetOptionalFieldInt(s, filename, DB_FIELDNAME_rating, MAINTABLE_ID_RATING);
+ else
+ GetNonBlankFieldInt(s, filename, DB_FIELDNAME_rating, MAINTABLE_ID_RATING);
+ GetOptionalField(s, filename, L"mime", MAINTABLE_ID_MIMETYPE);
+ }
+
+ int guessmode = guess_mode;
+ if (guessmode != 2 && ((!hasttitle) + (!hastartist) + (!hastalbum) + (!hasttrack) >= (g_guessifany ? 1 : 4)))
+ {
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0, *guessbuf = 0;
+
+ if (guessmode==1)
+ {
+ guessbuf = _wcsdup(filename2[0] ? filename2 : filename);
+
+ wchar_t *p=scanstr_backW(guessbuf, L"\\/.", guessbuf);
+ if (*p == '.')
+ {
+ *p = 0;
+ p = scanstr_backW(guessbuf, L"\\/", guessbuf);
+ }
+
+ if (p > guessbuf)
+ {
+ *p = 0;
+ title = p+1;
+ p=scanstr_backW(guessbuf, L"\\/", guessbuf);
+ if (p > guessbuf)
+ {
+ *p = 0;
+ album = p+1;
+ p=scanstr_backW(guessbuf,L"\\/", guessbuf);
+ if (p > guessbuf)
+ {
+ *p = 0;
+ artist = p+1;
+ }
+ }
+ }
+ }
+ else
+ guessbuf = guessTitles(filename2[0] ? filename2 : filename, &tn, &artist, &album, &title);
+
+ if (guessbuf)
+ {
+ if (!hasttitle && title) { hasttitle++; db_setFieldStringW(s,MAINTABLE_ID_TITLE,title); }
+ if (!hastartist && artist) { hastartist++; db_setFieldStringW(s,MAINTABLE_ID_ARTIST,artist); StringCbCopyW(m_artist, sizeof(m_artist), artist); }
+ if (!hastalbum && album) { hastalbum++; db_setFieldStringW(s,MAINTABLE_ID_ALBUM,album); }
+ if (!hasttrack && tn) { hasttrack++; db_setFieldInt(s,MAINTABLE_ID_TRACKNB,tn); }
+ free(guessbuf);
+ }
+ }
+
+ if (!hastlength || !hasttitle)
+ {
+ // try to query length and title using older GetFileInfo input plugin API
+ wchar_t ft[1024] = {0};
+ basicFileInfoStructW bi={0};
+ bi.filename=filename2[0]?filename2:filename;
+ bi.length=-1;
+ bi.title=ft;
+ bi.titlelen=1024;
+ skipTitleInfo=true;
+ LeaveCriticalSection(&g_db_cs); // benski> not actually sure if this is safe, but it prevents a deadlock
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&bi,IPC_GET_BASIC_FILE_INFOW);
+ EnterCriticalSection(&g_db_cs);
+ skipTitleInfo=false;
+ if (!hastlength && bi.length >= 0)
+ {
+ hastlength=1;
+ db_setFieldInt(s,MAINTABLE_ID_LENGTH,the_length_sec=bi.length);
+ }
+ if (!hasttitle && ft[0])
+ {
+ hasttitle=1;
+ db_setFieldStringW(s,MAINTABLE_ID_TITLE,ft);
+ }
+ }
+
+ // set up default (empty) strings/values
+ if (!hasttitle) // title=filename
+ {
+ wchar_t *p = PathFindFileNameW(filename), *dup = _wcsdup(p);
+ if (dup)
+ {
+ PathRemoveExtensionW(dup);
+ db_setFieldStringW(s,MAINTABLE_ID_TITLE,dup);
+ free(dup);
+ }
+ }
+
+ if (!hastartist) db_removeField(s,MAINTABLE_ID_ARTIST);
+ if (!hastalbum) db_removeField(s,MAINTABLE_ID_ALBUM);
+ if (!hasttrack) db_removeField(s,MAINTABLE_ID_TRACKNB);
+ if (!hastracks) db_removeField(s,MAINTABLE_ID_TRACKS);
+ if (!hastyear) db_removeField(s,MAINTABLE_ID_YEAR);
+ if (!hastlength) db_removeField(s,MAINTABLE_ID_LENGTH);
+ if (!hasttype) db_setFieldInt(s,MAINTABLE_ID_TYPE,0); //audio
+ if (!hasdisc) db_removeField(s, MAINTABLE_ID_DISC);
+ if (!hasdiscs) db_removeField(s, MAINTABLE_ID_DISCS);
+ if (!hasalbumartist)
+ {
+ if (hastartist && g_config->ReadInt(L"artist_as_albumartist", 1))
+ db_setFieldStringW(s, MAINTABLE_ID_ALBUMARTIST, m_artist);
+ else
+ db_removeField(s, MAINTABLE_ID_ALBUMARTIST);
+ }
+
+ if (file_size != INVALID_FILE_SIZE)
+ {
+ db_setFieldInt64(s,MAINTABLE_ID_FILESIZE, file_size);
+ }
+ else db_removeField(s,MAINTABLE_ID_FILESIZE);
+
+ db_setFieldInt(s,MAINTABLE_ID_LASTUPDTIME, (int)time(NULL));
+ db_setFieldInt(s,MAINTABLE_ID_FILETIME, (int)file_time);
+
+ if (!hasbitrate && the_length_sec)
+ {
+ __int64 br =(file_size*8LL) / (__int64)the_length_sec;
+ br /= 1000;
+ db_setFieldInt(s,MAINTABLE_ID_BITRATE,(int)br);
+ }
+
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+
+ if (found)
+ {
+ // Issue a wasabi system callback after we have successfully updated a file in the ml database
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED, (size_t)filename, 0);
+ }
+ else
+ {
+ // Issue a wasabi system callback after we have successfully added a file in the ml database
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_ADDED, (size_t)filename, 0);
+ }
+
+ return 1;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/api__ml_local.h b/Src/Plugins/Library/ml_local/api__ml_local.h
new file mode 100644
index 00000000..7c602232
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/api__ml_local.h
@@ -0,0 +1,15 @@
+#pragma once
+#include "../Agave/Agave.h"
+
+DECLARE_EXTERNAL_SERVICE(api_application, WASABI_API_APP);
+DECLARE_EXTERNAL_SERVICE(api_explorerfindfile, WASABI_API_EXPLORERFINDFILE);
+DECLARE_EXTERNAL_SERVICE(api_language, WASABI_API_LNG);
+DECLARE_EXTERNAL_SERVICE(api_syscb, WASABI_API_SYSCB);
+DECLARE_EXTERNAL_SERVICE(api_memmgr, WASABI_API_MEMMGR);
+DECLARE_EXTERNAL_SERVICE(api_albumart, AGAVE_API_ALBUMART);
+DECLARE_EXTERNAL_SERVICE(api_metadata, AGAVE_API_METADATA);
+DECLARE_EXTERNAL_SERVICE(api_playlistmanager, AGAVE_API_PLAYLISTMANAGER);
+DECLARE_EXTERNAL_SERVICE(api_itunes_importer, AGAVE_API_ITUNES_IMPORTER);
+DECLARE_EXTERNAL_SERVICE(api_playlist_generator, AGAVE_API_PLAYLIST_GENERATOR);
+DECLARE_EXTERNAL_SERVICE(api_threadpool, WASABI_API_THREADPOOL);
+
diff --git a/Src/Plugins/Library/ml_local/api_mldb.cpp b/Src/Plugins/Library/ml_local/api_mldb.cpp
new file mode 100644
index 00000000..67220738
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/api_mldb.cpp
@@ -0,0 +1,3 @@
+#include "api_mldb.h"
+
+// this file exists solely to ensure that api_mldb.h can be #include'd by itself \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/api_mldb.h b/Src/Plugins/Library/ml_local/api_mldb.h
new file mode 100644
index 00000000..ed792573
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/api_mldb.h
@@ -0,0 +1,166 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include "..\..\General\gen_ml/ml.h"
+
+class api_mldb : public Dispatchable
+{
+protected:
+ api_mldb() {}
+ ~api_mldb() {}
+public:
+ itemRecordW *GetFile(const wchar_t *filename);
+ itemRecordW *GetFileIf(const wchar_t *filename, const wchar_t *query); // returns the item record for a filename, but also checks against the passed query
+ itemRecordListW *GetAlbum(const wchar_t *albumname, const wchar_t *albumartist);
+ itemRecordListW *Query(const wchar_t *query);
+ itemRecordListW *QueryLimit(const wchar_t *query, unsigned int limit);
+
+ void SetField(const wchar_t *filename, const char *field, const wchar_t *value);
+ void SetFieldInteger(const wchar_t *filename, const char *field, int value);
+ void SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16]);
+ void Sync();
+
+ void FreeRecord(itemRecordW *record);
+ void FreeRecordList(itemRecordListW *recordList);
+
+ int AddFile(const wchar_t *filename);
+ int RemoveFile(const wchar_t *filename);
+
+ /* wrappers around ndestring */
+ void RetainString(wchar_t *str);
+ void ReleaseString(wchar_t *str);
+ wchar_t *DuplicateString(const wchar_t *str);
+
+ int GetMaxInteger(const char *field, int *max);
+
+ DISPATCH_CODES
+ {
+ API_MLDB_GETFILE = 10,
+ API_MLDB_GETFILEIF = 11,
+ API_MLDB_GETALBUM = 20,
+ API_MLDB_QUERY = 30,
+ API_MLDB_QUERYLIMIT = 31,
+ API_MLDB_FREERECORD = 40,
+ API_MLDB_FREERECORDLIST = 50,
+ API_MLDB_SETFIELD = 60,
+ API_MLDB_SETFIELDINT = 61,
+ API_MLDB_SETFIELDINT128 = 62,
+ API_MLDB_SYNC = 70,
+ API_MLDB_ADDFILE = 80,
+ API_MLDB_REMOVEFILE = 90,
+ API_MLDB_RETAINSTRING = 100,
+ API_MLDB_RELEASESTRING = 110,
+ API_MLDB_DUPLICATESTRING = 120,
+ API_MLDB_GETMAXINTEGER = 130,
+ };
+
+ typedef struct
+ {
+ time_t played;
+ int count;
+ } played_info;
+
+ enum
+ {
+ // System callbacks for api_mldb
+ SYSCALLBACK = MK4CC('m','l','d','b'), // Unique identifier for mldb_api callbacks
+ MLDB_FILE_ADDED = 10, // param1 = filename, param2 = (not used), Callback event for when a new file is added to the local mldb
+ MLDB_FILE_REMOVED_PRE = 20, // param1 = filename, param2 = (not used), Callback event for when a file is removed from the local mldb (before it happens)
+ MLDB_FILE_REMOVED_POST = 25, // param1 = filename, param2 = (not used), Callback event for when a file is removed from the local mldb (after it happens)
+ MLDB_FILE_UPDATED = 30, // param1 = filename, param2 = (not used), Callback event for when a file is modified in the local mldb
+ MLDB_FILE_UPDATED_EXTERNAL = 35, // param1 = filename, param2 = (not used), Callback event for when a file is modified and is not in the local mldb
+ MLDB_CLEARED = 40, // param1 = filenames, param2 = count, Callback event for when the local mldb is cleared (useful so removed is not triggered for all files)
+ MLDB_FILE_PLAYED = 50, // param1 = filename, param2 = played_info, Callback event for when a file is tracked as playing in the local mldb
+ MLDB_FILE_GET_CLOUD_STATUS = 60, // param1 = filename, param2 = HMENU*, Callback event for when ml_local needs to show a cloud status menu (returned by the handler in param2)
+ MLDB_FILE_PROCESS_CLOUD_STATUS = 65, // param1 = menu_item, param2 = int*, Callback event for when ml_local needs to process the result of a cloud status menu
+ };
+};
+
+inline itemRecordW *api_mldb::GetFile(const wchar_t *filename)
+{
+ return _call(API_MLDB_GETFILE, (itemRecordW *)0, filename);
+}
+
+inline itemRecordW *api_mldb::GetFileIf(const wchar_t *filename, const wchar_t *query)
+{
+ return _call(API_MLDB_GETFILEIF, (itemRecordW *)0, filename, query);
+}
+
+inline itemRecordListW *api_mldb::GetAlbum(const wchar_t *albumname, const wchar_t *albumartist)
+{
+ return _call(API_MLDB_GETALBUM, (itemRecordListW *)0, albumname, albumartist);
+}
+
+inline itemRecordListW *api_mldb::Query(const wchar_t *query)
+{
+ return _call(API_MLDB_QUERY, (itemRecordListW *)0, query);
+}
+
+inline itemRecordListW *api_mldb::QueryLimit(const wchar_t *query, unsigned int limit)
+{
+ return _call(API_MLDB_QUERYLIMIT, (itemRecordListW *)0, query, limit);
+}
+
+inline void api_mldb::FreeRecord(itemRecordW *record)
+{
+ _voidcall(API_MLDB_FREERECORD, record);
+}
+
+inline void api_mldb::FreeRecordList(itemRecordListW *recordList)
+{
+ _voidcall(API_MLDB_FREERECORDLIST, recordList);
+}
+
+inline void api_mldb::SetField(const wchar_t *filename, const char *field, const wchar_t *value)
+{
+ _voidcall(API_MLDB_SETFIELD, filename, field, value);
+}
+
+inline void api_mldb::SetFieldInteger(const wchar_t *filename, const char *field, int value)
+{
+ _voidcall(API_MLDB_SETFIELDINT, filename, field, value);
+}
+
+inline void api_mldb::SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16])
+{
+ _voidcall(API_MLDB_SETFIELDINT128, filename, field, value);
+}
+
+inline void api_mldb::Sync()
+{
+ _voidcall(API_MLDB_SYNC);
+}
+
+inline int api_mldb::AddFile(const wchar_t *filename)
+{
+ return _call(API_MLDB_ADDFILE, (int)0, filename);
+}
+
+inline int api_mldb::RemoveFile(const wchar_t *filename)
+{
+ return _call(API_MLDB_REMOVEFILE, (int)0, filename);
+}
+
+inline void api_mldb::RetainString(wchar_t *str)
+{
+ _voidcall(API_MLDB_RETAINSTRING, str);
+}
+
+inline void api_mldb::ReleaseString(wchar_t *str)
+{
+ _voidcall(API_MLDB_RELEASESTRING, str);
+}
+
+inline wchar_t *api_mldb::DuplicateString(const wchar_t *str)
+{
+ return _call(API_MLDB_DUPLICATESTRING, (wchar_t *)0, str);
+}
+
+inline int api_mldb::GetMaxInteger(const char *field, int *max)
+{
+ return _call(API_MLDB_GETMAXINTEGER, (int)1, field, max);
+}
+
+// {5A94DABC-E19A-4a12-9AA8-852D8BF06532}
+static const GUID mldbApiGuid =
+{ 0x5a94dabc, 0xe19a, 0x4a12, { 0x9a, 0xa8, 0x85, 0x2d, 0x8b, 0xf0, 0x65, 0x32 } };
diff --git a/Src/Plugins/Library/ml_local/bgscan.cpp b/Src/Plugins/Library/ml_local/bgscan.cpp
new file mode 100644
index 00000000..8b4d9bdc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/bgscan.cpp
@@ -0,0 +1,940 @@
+#include "main.h"
+#include "resource.h"
+#include "api_mldb.h"
+#include "../Winamp/strutil.h"
+
+enum {
+ STATUS_SEARCHING,
+ STATUS_GETINFO,
+ STATUS_DONE,
+};
+
+extern HWND g_bgrescan_status_hwnd;
+extern nde_scanner_t m_media_scanner;
+
+#define MAX_RECURSE_DEPTH 32
+/* Event handles */
+static HANDLE scan_killswitch=0;
+static HANDLE scan_cancel=0;
+static HANDLE scan_cancel_complete=0;
+/* Thread handle */
+static HANDLE scan_thread=0;
+/* extension list */
+static wchar_t *scan_extlist=0;
+
+static void SyncTable()
+{
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+}
+
+static bool ScanCancelled(HANDLE *events, int count)
+{
+ // make sure no one cancelled us
+ DWORD eventFired=WaitForMultipleObjectsEx(count, events, FALSE, 0, TRUE);
+ if (eventFired >= WAIT_OBJECT_0 && eventFired < (WAIT_OBJECT_0+count))
+ return true;
+
+ return false;
+}
+
+static bool SupportedType(const wchar_t *ext)
+{
+ if (!scan_extlist)
+ scan_extlist=(wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_EXTLISTW);
+
+ // dunno how this would happen but should verify
+ if (!scan_extlist || scan_extlist == (wchar_t *)1)
+ return false;
+
+ const wchar_t *a = scan_extlist;
+ while (a && *a)
+ {
+ if (CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, a, -1, ext, -1) == CSTR_EQUAL)
+ {
+ return true;
+ }
+ a+=wcslen(a)+1;
+ }
+ return false;
+}
+
+static void CountFolder(const wchar_t *folder, int recurse, HANDLE cancelswitch, volatile int *found)
+{
+ wchar_t filespec[MAX_PATH] = {0};
+ PathCombineW(filespec, folder, L"*.*");
+
+ WIN32_FIND_DATAW findData = {0};
+
+ HANDLE h = FindFirstFileW(filespec, &findData);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t status[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_DIR, status, 150);
+
+ const wchar_t *p=folder+wcslen(folder);
+ while (p > folder && *p != '\\') p--;
+ p--;
+ while (p >= folder && *p != '\\') p--;
+
+ StringCbCatW(status, sizeof(status), ++p);
+
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+
+ HANDLE events[2] = { scan_killswitch, cancelswitch};
+ do
+ {
+ // make sure no one cancelled us
+ if (ScanCancelled(events, 2))
+ break;
+
+ /* if it's a directory (And not either of the two special dirs */
+ if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ && lstrcmpiW(findData.cFileName, L".")
+ && lstrcmpiW(findData.cFileName, L".."))
+ {
+ if (recurse && recurse < MAX_RECURSE_DEPTH)
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ CountFolder(filespec, recurse+1, cancelswitch, found); // add 1 so we can verify recurse depth
+ }
+ }
+
+ if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ wchar_t *ext=extensionW(findData.cFileName);
+ if (ext && ext[0] && SupportedType(ext))
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, b, 150);
+ StringCbCatW(b, sizeof(b), filespec);
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ if (found)
+ (*found)++;
+ }
+ }
+
+ } while (FindNextFileW(h, &findData));
+ FindClose(h);
+ }
+ else if (!(GetFileAttributesW(folder) & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, b, 150);
+ StringCbCatW(b, sizeof(b), folder);
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ if (found)
+ (*found)++;
+ }
+}
+
+static void ScanFolder(const wchar_t *folder, int recurse, int metadata, int guessmode, HANDLE cancelswitch, volatile int *scanned)
+{
+ if ((unsigned long)folder < 65536) return;
+
+ wchar_t filespec[MAX_PATH] = {0};
+ wchar_t status[150+MAX_PATH] = {0};
+ PathCombineW(filespec, folder, L"*.*");
+
+ WIN32_FIND_DATAW findData = {0};
+
+ HANDLE h = FindFirstFileW(filespec, &findData);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_DIR, status, 150);
+
+ const wchar_t *p=folder+wcslen(folder);
+ while (p > folder && *p != '\\') p--;
+ p--;
+ while (p >= folder && *p != '\\') p--;
+
+ StringCbCatW(status, sizeof(status), ++p);
+
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+
+ HANDLE events[2] = { scan_killswitch, cancelswitch};
+ do
+ {
+ // make sure no one cancelled us
+ if (ScanCancelled(events, 2))
+ break;
+
+ /* if it's a directory (And not either of the two special dirs */
+ if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ && lstrcmpiW(findData.cFileName, L".")
+ && lstrcmpiW(findData.cFileName, L".."))
+ {
+ if (recurse && recurse < MAX_RECURSE_DEPTH)
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ ScanFolder(filespec, recurse+1, metadata, guessmode, cancelswitch, scanned); // add 1 so we can verify recurse depth
+ }
+ }
+
+ if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ wchar_t *ext=extensionW(findData.cFileName);
+ if (ext && ext[0] && SupportedType(ext))
+ {
+ PathCombineW(filespec, folder, findData.cFileName);
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, status, 150);
+ StringCbCatW(status, sizeof(status), filespec);
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+ addFileToDb(filespec, 0, metadata, guessmode);
+ if (scanned)
+ (*scanned)++;
+ }
+ }
+ } while (FindNextFileW(h, &findData));
+ FindClose(h);
+ }
+ else if (!(GetFileAttributesW(folder) & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_FILE, status, 150);
+ StringCbCatW(status, sizeof(status), folder);
+ SetWindowTextW(g_bgrescan_status_hwnd,status);
+ }
+ addFileToDb(folder, 0, metadata, guessmode);
+ if (scanned)
+ (*scanned)++;
+ }
+}
+
+static DWORD CALLBACK ScanThreadProc(LPVOID param)
+{
+ /* sit and run APCs until we get signalled to die */
+ HANDLE events[2] = { scan_killswitch, scan_cancel};
+ int eventFired;
+ do
+ {
+ eventFired=WaitForMultipleObjectsEx(2, events, FALSE, INFINITE, TRUE);
+ switch(eventFired)
+ {
+ case WAIT_OBJECT_0+1: // cancel event
+ ResetEvent(scan_cancel);
+ SetEvent(scan_cancel_complete);
+ break;
+ }
+ }
+ while (eventFired != WAIT_OBJECT_0);
+
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist=0;
+ return 0;
+}
+
+static bool ScanCreateThread()
+{
+ if (!scan_thread)
+ {
+ /* create events */
+ scan_killswitch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ scan_cancel = CreateEvent(NULL, TRUE, FALSE, NULL);
+ scan_cancel_complete = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset event
+
+ /* start thread */
+ scan_thread = CreateThread(NULL, 0, ScanThreadProc, 0, 0, 0);
+ }
+
+ return !!scan_thread;
+}
+
+void Scan_Cancel()
+{
+ HWND old = g_bgrescan_status_hwnd; // clear g_bgrescan_status_hwnd so that we don't deadlock when the BG thread calls SetWindowText
+ g_bgrescan_status_hwnd = 0;
+ if (scan_cancel)
+ SignalObjectAndWait(scan_cancel, scan_cancel_complete, INFINITE, FALSE);
+ g_bgrescan_status_hwnd = old;
+}
+
+void Scan_Kill()
+{
+ HWND old = g_bgrescan_status_hwnd; // clear g_bgrescan_status_hwnd so that we don't deadlock when the BG thread calls SetWindowText
+ g_bgrescan_status_hwnd = 0;
+ if (scan_thread)
+ SignalObjectAndWait(scan_killswitch, scan_thread, INFINITE, FALSE);
+ g_bgrescan_status_hwnd = old;
+}
+
+/* ---------------
+* Scan_ScanFolder
+* ---------------
+*/
+
+struct ScanFolderParams
+{
+ ScanFolderParams(const wchar_t *_path, int _guess, int _meta, int _recurse)
+ {
+ path = _wcsdup(_path);
+ guess = _guess >= 0 ? _guess : g_config->ReadInt(L"guessmode",0);;
+ meta = _meta >= 0 ? _meta : g_config->ReadInt(L"usemetadata",1);
+ recurse = _recurse;
+ }
+ ~ScanFolderParams()
+ {
+ free(path);
+ }
+ wchar_t *path;
+ int guess;
+ int meta;
+ int recurse;
+};
+
+static VOID CALLBACK ScanFolderAPC(ULONG_PTR param)
+{
+ // clear extension list to get latest config
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist = 0;
+
+ ScanFolderParams *params = (ScanFolderParams *)param;
+ ScanFolder(params->path, params->recurse, params->meta, params->guess, scan_cancel, 0);
+ SyncTable();
+ delete params;
+}
+
+void Scan_ScanFolderBackground(const wchar_t *path, int guess, int meta, int recurse)
+{
+ if (ScanCreateThread())
+ {
+ ScanFolderParams *params = new ScanFolderParams(path, guess, meta, recurse);
+ if (QueueUserAPC(ScanFolderAPC, scan_thread, (ULONG_PTR)params) == 0)
+ delete params;
+ }
+}
+
+/* ---------------
+* Scan_ScanFolders
+* ---------------
+*/
+
+struct ScanFoldersParams
+{
+ ScanFoldersParams(wchar_t **_path, size_t _count, int *_guess, int *_meta, int *_recurse)
+ {
+ path = _path;
+ count = _count;
+ guess = _guess;
+ meta = _meta;
+ recurse = _recurse;
+ found = 0;
+ scanned = 0;
+ cancel_switch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ status = STATUS_SEARCHING;
+ ui = 0;
+ in_timer = 0;
+
+ }
+ ~ScanFoldersParams()
+ {
+ for (size_t i=0;i!=count;i++)
+ free(path[i]);
+ free(path);
+ free(guess);
+ free(meta);
+ free(recurse);
+ CloseHandle(cancel_switch);
+ }
+ wchar_t **path;
+ size_t count;
+ int *guess;
+ int *meta;
+ int *recurse;
+ volatile int found;
+ volatile int scanned;
+ volatile int status;
+ int in_timer;
+ HANDLE cancel_switch;
+ HWND ui;
+};
+
+static VOID CALLBACK ScanFoldersAPC(ULONG_PTR param)
+{
+ // clear extension list to get latest config
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist = 0;
+
+ ScanFoldersParams *params = (ScanFoldersParams *)param;
+ HANDLE events[2] = { scan_killswitch, params->cancel_switch};
+
+ for (size_t i=0;i!=params->count;i++)
+ {
+ if (ScanCancelled(events, 2))
+ break;
+
+ CountFolder(params->path[i], params->recurse[i], params->cancel_switch, &params->found);
+ }
+
+ params->status = STATUS_GETINFO;
+
+ for (size_t i=0;i!=params->count;i++)
+ {
+ if (ScanCancelled(events, 2))
+ break;
+
+ int guess = params->guess[i] >= 0 ? params->guess[i] : g_config->ReadInt(L"guessmode",0);;
+ int meta = params->meta[i] >= 0 ? params->meta[i] : g_config->ReadInt(L"usemetadata",1);
+ ScanFolder(params->path[i], params->recurse[i], meta, guess, params->cancel_switch, &params->scanned);
+ }
+
+ params->status = STATUS_DONE;
+ PostMessage(params->ui, WM_APP, 0, 0);
+}
+
+static INT_PTR CALLBACK ScanFileUI(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,WASABI_API_LNGSTRINGW(IDS_INITIALIZING));
+
+ ScanFoldersParams *params = (ScanFoldersParams *)lParam;
+ params->ui = hwndDlg;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ if (QueueUserAPC(ScanFoldersAPC, scan_thread, (ULONG_PTR)lParam) == 0)
+ EndDialog(hwndDlg, 0);
+ else
+ {
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETRANGE,0,MAKELPARAM(0, 100));
+ SetTimer(hwndDlg,0x123,300,NULL);
+ }
+
+ // show window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"scan_x", -1), g_config->ReadInt(L"scan_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+
+ case WM_TIMER:
+ {
+ ScanFoldersParams *params = (ScanFoldersParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (params->in_timer) break;
+ params->in_timer++;
+
+ if(params->status==STATUS_SEARCHING)
+ {
+ wchar_t tmp[512] = {0};
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_SEARCHING_X_FILES_FOUND), params->found);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ }
+ else if(params->status==STATUS_GETINFO)
+ {
+ wchar_t tmp[512] = {0};
+ int perc=params->found?(params->scanned*100/params->found):0;
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_GETTING_INFO_FROM_FILES_PERCENT),perc);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,perc,0);
+ }
+ params->in_timer--;
+ }
+ break;
+
+ case WM_APP:
+ {
+ KillTimer(hwndDlg,0x123);
+ SyncTable();
+ EndDialog(hwndDlg,0);
+ }
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam)==IDCANCEL)
+ {
+ ScanFoldersParams *params = (ScanFoldersParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetEvent(params->cancel_switch);
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ RECT scan_rect = {0};
+ GetWindowRect(hwndDlg, &scan_rect);
+ g_config->WriteInt(L"scan_x", scan_rect.left);
+ g_config->WriteInt(L"scan_y", scan_rect.top);
+
+ KillTimer(hwndDlg,0x123);
+ ScanFoldersParams *params = (ScanFoldersParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
+ delete params;
+ return FALSE;
+ }
+ }
+ return FALSE;
+}
+
+/* When you call this function, it will own the memory and release it with free() */
+void Scan_ScanFolders(HWND parent, size_t count, wchar_t **paths, int *guess, int *meta, int *recurse)
+{
+ openDb();
+
+ if (g_table && ScanCreateThread())
+ {
+ ScanFoldersParams *params = new ScanFoldersParams(paths, count, guess, meta, recurse);
+ WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_ADDSTUFF,
+ parent, (DLGPROC)ScanFileUI, (LPARAM)params);
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,NDE_Table_GetRecordsCount(g_table),IPC_STATS_LIBRARY_ITEMCNT);
+ }
+ else
+ {
+ for (size_t i=0;i!=count;i++)
+ free(paths[i]);
+ free(paths);
+ }
+}
+
+void Scan_ScanFolder(HWND parent, const wchar_t *path, int guess, int meta, int recurse)
+{
+ // kind of a hack ...
+ if (ScanCreateThread())
+ {
+ wchar_t **paths = (wchar_t **)calloc(1, sizeof(wchar_t*));
+ int *guesses = (int *)calloc(1, sizeof(int));
+ int *metas = (int *)calloc(1, sizeof(int));
+ int *recs = (int *)calloc(1, sizeof(int));
+ *guesses = guess;
+ *metas = meta;
+ *recs = recurse;
+ paths[0] = _wcsdup(path);
+ Scan_ScanFolders(parent, 1, paths, guesses, metas, recs);
+ }
+}
+
+static VOID CALLBACK BackgroundScanAPC(ULONG_PTR param)
+{
+ openDb();
+ if (!g_table)
+ return;
+
+ HANDLE events[2] = { scan_killswitch, scan_cancel};
+
+ // clear extension list to get latest config
+ if (scan_extlist && scan_extlist != (wchar_t *)1)
+ GlobalFree((HGLOBAL)scan_extlist);
+ scan_extlist = 0;
+
+ // read list from config
+ UINT codePage = CP_ACP;
+ char scandirlist[65536] = {0};
+ if (!g_config->ReadString("scandirlist", 0, scandirlist, 65536))
+ {
+ g_config->ReadString("scandirlist_utf8","", scandirlist, 65536);
+ codePage = CP_UTF8;
+ }
+
+ AutoWide s1(scandirlist, codePage);
+ size_t len = wcslen(s1)+2;
+ wchar_t *s =(wchar_t*)calloc(len, sizeof(wchar_t));
+ if (s)
+ {
+ lstrcpynW(s, s1, len);
+ s[wcslen(s)+1]=0;
+
+ wchar_t *p=s;
+ while (p && *p == L'|') p++;
+
+ while ((p=wcsstr(p,L"|")))
+ {
+ *p++=0;
+ while (p && *p == L'|') p++;
+ }
+ p=s;
+
+ // iterate through list
+ while (p && *p && !ScanCancelled(events, 2))
+ {
+ while (p && *p == L'|') p++;
+
+ int use_metadata=g_config->ReadInt(L"usemetadata",1);
+ int guess_mode=g_config->ReadInt(L"guessmode",0);
+ int recurse=1;
+ if (*p == L'<' && wcsstr(p,L">"))
+ {
+ p++;
+ while (p && *p != L'>')
+ {
+ // <MmSs>can prefix directory
+ // M=metadata use override
+ // m=no metadata
+ // S=smart guessing
+ // s=stupid guessing
+ if (*p == L'M') use_metadata=1;
+ else if (*p == L'm') use_metadata=0;
+ else if (*p == L'S') guess_mode=0;
+ else if (*p == L's') guess_mode=1;
+ else if (*p == L'r') recurse=0;
+ else if (*p == L'g') guess_mode=2;
+ p++;
+ }
+ p++;
+ }
+ ScanFolder(p, recurse, use_metadata, guess_mode, scan_cancel, 0);
+ p+=wcslen(p)+1;
+ }
+
+ free(s);
+ }
+
+ /* Remove missing files */
+ if (!ScanCancelled(events, 2) && g_config->ReadInt(L"bgrescan_compact",1))
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t scanner = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(scanner, L"");
+ NDE_Scanner_First(scanner);
+again:
+ nde_field_t f=NDE_Scanner_GetFieldByID(scanner, MAINTABLE_ID_FILENAME);
+ wchar_t *gs=0;
+ if (f)
+ {
+ gs = NDE_StringField_GetString(f);
+ ndestring_retain(gs);
+ if (GetFileAttributesW(gs) != INVALID_FILE_ATTRIBUTES)
+ {
+ NDE_Scanner_Next(scanner);
+ }
+ else
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)gs, 0);
+
+ NDE_Scanner_Delete(scanner);
+ NDE_Scanner_Post(scanner);
+
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)gs, 0);
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (f) // done checking for unused files
+ {
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150+MAX_PATH] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_CHECKING_FOR_FILE, b, 150);
+ StringCbCatW(b, sizeof(b), PathFindFileNameW(gs));
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ ndestring_release(gs);
+ gs=0;
+ if (!ScanCancelled(events, 2))
+ {
+ EnterCriticalSection(&g_db_cs);
+ goto again;
+ }
+ }
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_DestroyScanner(g_table, scanner);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (IsWindow(g_bgrescan_status_hwnd))
+ {
+ wchar_t b[150] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_COMPACTING, b, 150);
+ SetWindowTextW(g_bgrescan_status_hwnd,b);
+ }
+ }
+
+ // TODO: hmmm, safe to do on separate thread?
+ EnterCriticalSection(&g_db_cs);
+ if (!ScanCancelled(events, 2))
+ {
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Sync(g_table); // this is currently b0rk3d -- fucko :)
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ if (m_media_scanner)
+ {
+ m_media_scanner=NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,NDE_Table_GetRecordsCount(g_table),IPC_STATS_LIBRARY_ITEMCNT);
+ }
+ else
+ {
+ NDE_Table_Sync(g_table); // this is currently b0rk3d -- fucko :)
+ g_table_dirty=0;
+ }
+ LeaveCriticalSection(&g_db_cs);
+ if (IsWindow(g_bgrescan_status_hwnd))
+ SetWindowTextW(g_bgrescan_status_hwnd,L"");
+ g_bgscan_last_rescan = time(NULL);
+ g_bgscan_scanning = 0;
+}
+
+void Scan_BackgroundScan()
+{
+ if (ScanCreateThread())
+ {
+ g_bgrescan_force = 0;
+ g_bgscan_last_rescan = time(NULL);
+ g_bgscan_scanning = 1;
+
+ QueueUserAPC(BackgroundScanAPC, scan_thread, (ULONG_PTR)0);
+
+ }
+}
+
+static void RemoveFiles(HANDLE cancelswitch, volatile int *found, volatile int *count, volatile int *scanned)
+{
+ // TODO: benski> we might need to keep the database lock the whole time. need to think it thru
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t myscanner=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(myscanner, L"");
+ NDE_Scanner_First(myscanner);
+ *found=0;
+ *scanned=0;
+ *count=NDE_Table_GetRecordsCount(g_table);
+ LeaveCriticalSection(&g_db_cs);
+
+ HANDLE events[2] = { scan_killswitch, cancelswitch};
+
+ bool fileRemoved = false;
+ wchar_t *filename;
+ while (!ScanCancelled(events, 2))
+ {
+ EnterCriticalSection(&g_db_cs);
+ if (!NDE_Scanner_BOF(myscanner) && !NDE_Scanner_EOF(myscanner))
+ {
+ nde_field_t f= NDE_Scanner_GetFieldByID(myscanner, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ (*scanned)++;
+
+ filename = (NDE_StringField_GetString(f));
+
+ if (GetFileAttributesW(NDE_StringField_GetString(f)) != INVALID_FILE_ATTRIBUTES)
+ {
+ NDE_Scanner_Next(myscanner);
+ }
+ else
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)filename, 0);
+
+ //remove file
+ NDE_Scanner_Delete(myscanner);
+ NDE_Scanner_Post(myscanner);
+ (*found)++;
+ fileRemoved = true;
+ }
+ }
+ else
+ {
+ //remove file
+ NDE_Scanner_Delete(myscanner);
+ NDE_Scanner_Post(myscanner);
+ (*found)++;
+ fileRemoved = false;
+ }
+ }
+ else // last file
+ {
+ LeaveCriticalSection(&g_db_cs);
+ break;
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (fileRemoved)
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)filename, 0);
+ fileRemoved = false;
+ }
+ }
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_DestroyScanner(g_table, myscanner); // important that we delete the scanner BEFORE
+ myscanner=0;
+
+ wchar_t *last_query = NULL;
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ NDE_Table_Sync(g_table); // this is currently b0rk3d -- fucko :)
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ if (m_media_scanner)
+ {
+ m_media_scanner=NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+}
+
+struct RemoveFilesParams
+{
+ RemoveFilesParams()
+ {
+ found = 0;
+ scanned = 0;
+ total = 0;
+ cancel_switch = CreateEvent(NULL, TRUE, FALSE, NULL);
+ ui = 0;
+ in_timer = 0;
+ }
+ ~RemoveFilesParams()
+ {
+
+ CloseHandle(cancel_switch);
+ }
+ volatile int found;
+ volatile int scanned;
+ volatile int total;
+
+ int in_timer;
+ HANDLE cancel_switch;
+ HWND ui;
+};
+
+static VOID CALLBACK RemoveFilesAPC(ULONG_PTR param)
+{
+ RemoveFilesParams *params = (RemoveFilesParams *)param;
+
+ RemoveFiles(params->cancel_switch, &params->found, &params->total, &params->scanned);
+ PostMessage(params->ui, WM_APP, 0, 0);
+}
+
+static INT_PTR CALLBACK RemoveFilesUI(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowTextW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_REMOVING_FILES_NOT_EXISTING));
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,WASABI_API_LNGSTRINGW(IDS_INITIALIZING));
+
+ RemoveFilesParams *params = (RemoveFilesParams *)lParam;
+ params->ui = hwndDlg;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ if (QueueUserAPC(RemoveFilesAPC, scan_thread, (ULONG_PTR)lParam) == 0)
+ EndDialog(hwndDlg, 0);
+ else
+ {
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETRANGE,0,MAKELPARAM(0, 100));
+ SetTimer(hwndDlg,0x123,300,NULL);
+ }
+
+ // show window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"scan_x", -1), g_config->ReadInt(L"scan_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+
+ case WM_TIMER:
+ {
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (params->in_timer) break;
+ params->in_timer++;
+
+ if(params->total)
+ {
+ wchar_t tmp[512] = {0};
+ int perc=(params->scanned*100/(params->total?params->total:1));
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_SCANNING_X_OF_X_X_REMOVED),params->scanned,params->total,params->found);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,perc,0);
+ }
+
+ params->in_timer--;
+ }
+ break;
+
+ case WM_APP:
+ {
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ KillTimer(hwndDlg,0x123);
+
+ wchar_t tmp[512] = {0};
+ int perc=(params->scanned*100/(params->total?params->total:1));
+ StringCchPrintfW(tmp, 512, WASABI_API_LNGSTRINGW(IDS_SCANNED_X_FILES_X_REMOVED),params->total,params->found);
+ SetDlgItemTextW(hwndDlg,IDC_STATUS,tmp);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,perc,0);
+
+ SyncTable();
+ EndDialog(hwndDlg,0);
+ }
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam)==IDCANCEL)
+ {
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetEvent(params->cancel_switch);
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ RECT scan_rect = {0};
+ GetWindowRect(hwndDlg, &scan_rect);
+ g_config->WriteInt(L"scan_x", scan_rect.left);
+ g_config->WriteInt(L"scan_y", scan_rect.top);
+
+ KillTimer(hwndDlg,0x123);
+ RemoveFilesParams *params = (RemoveFilesParams *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
+ delete params;
+ }
+ return FALSE;
+ }
+ return FALSE;
+}
+
+void Scan_RemoveFiles(HWND parent)
+{
+ openDb();
+ if (g_table && ScanCreateThread())
+ {
+ RemoveFilesParams *params = new RemoveFilesParams();
+ WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_ADDSTUFF,
+ parent, (DLGPROC)RemoveFilesUI, (LPARAM)params);
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,NDE_Table_GetRecordsCount(g_table),IPC_STATS_LIBRARY_ITEMCNT);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/contnr.cpp b/Src/Plugins/Library/ml_local/contnr.cpp
new file mode 100644
index 00000000..6306f3f2
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/contnr.cpp
@@ -0,0 +1,979 @@
+/**************************************************************************
+ THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ PARTICULAR PURPOSE.
+
+ Copyright 1998 Microsoft Corporation. All Rights Reserved.
+**************************************************************************/
+
+/**************************************************************************
+
+ File: contnr.cpp
+
+ Description: This file contains the complete implementation of an
+ ActiveX control container. This purpose of this container
+ is to test a single control being hosted.
+
+**************************************************************************/
+
+/**************************************************************************
+ #include statements
+**************************************************************************/
+#include "main.h"
+#include <windows.h>
+#include <commctrl.h>
+#include "contnr.h"
+
+#include <mshtmdid.h>
+#include <shlobj.h>
+#include "ml_local.h"
+
+/**************************************************************************
+
+ CContainer::CContainer()
+
+**************************************************************************/
+extern IDispatch *winampExternal;
+
+CContainer::CContainer()
+{
+ m_cRefs = 1;
+ m_hwnd = NULL;
+ m_punk = NULL;
+ m_scrollbars= 1;
+ m_allowScripts=1;
+ m_restrictAlreadySet=0;
+ m_hwndStatus = 0;
+
+}
+
+/**************************************************************************
+
+ CContainer::~CContainer()
+
+**************************************************************************/
+
+CContainer::~CContainer()
+{
+ if (m_punk)
+ {
+ m_punk->Release();
+ m_punk=NULL;
+ }
+}
+
+/**************************************************************************
+
+ CContainer::QueryInterface()
+
+**************************************************************************/
+
+STDMETHODIMP CContainer::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IOleClientSite))
+ *ppvObject = (IOleClientSite *)this;
+ else if (IsEqualIID(riid, IID_IOleInPlaceSite))
+ *ppvObject = (IOleInPlaceSite *)this;
+ else if (IsEqualIID(riid, IID_IOleInPlaceFrame))
+ *ppvObject = (IOleInPlaceFrame *)this;
+ else if (IsEqualIID(riid, IID_IOleInPlaceUIWindow))
+ *ppvObject = (IOleInPlaceUIWindow *)this;
+ else if (IsEqualIID(riid, IID_IOleControlSite))
+ *ppvObject = (IOleControlSite *)this;
+ else if (IsEqualIID(riid, IID_IOleWindow))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else if (IsEqualIID(riid, IID_IDocHostUIHandler))
+ *ppvObject = (IDocHostUIHandler *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::AddRef()
+
+**************************************************************************/
+
+ULONG CContainer::AddRef(void)
+{
+ return ++m_cRefs;
+}
+
+/**************************************************************************
+
+ CContainer::Release()
+
+**************************************************************************/
+
+ULONG CContainer::Release(void)
+{
+ if (--m_cRefs)
+ return m_cRefs;
+
+ delete this;
+ return 0;
+}
+
+/**************************************************************************
+
+ CContainer::SaveObject()
+
+**************************************************************************/
+
+HRESULT CContainer::SaveObject()
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetMoniker()
+
+**************************************************************************/
+
+HRESULT CContainer::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER * ppMk)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetContainer()
+
+**************************************************************************/
+
+HRESULT CContainer::GetContainer(LPOLECONTAINER * ppContainer)
+{
+ return E_NOINTERFACE;
+}
+
+/**************************************************************************
+
+ CContainer::ShowObject()
+
+**************************************************************************/
+
+HRESULT CContainer::ShowObject()
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnShowWindow()
+
+**************************************************************************/
+
+HRESULT CContainer::OnShowWindow(BOOL fShow)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::RequestNewObjectLayout()
+
+**************************************************************************/
+
+HRESULT CContainer::RequestNewObjectLayout()
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IOleWindow
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::GetWindow()
+
+**************************************************************************/
+
+HRESULT CContainer::GetWindow(HWND * lphwnd)
+{
+ if (!IsWindow(m_hwnd))
+ return S_FALSE;
+
+ *lphwnd = m_hwnd;
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::ContextSensitiveHelp()
+
+**************************************************************************/
+
+HRESULT CContainer::ContextSensitiveHelp(BOOL fEnterMode)
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IOleInPlaceSite
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::CanInPlaceActivate()
+
+**************************************************************************/
+
+HRESULT CContainer::CanInPlaceActivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnInPlaceActivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnInPlaceActivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnUIActivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnUIActivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::GetWindowContext()
+
+**************************************************************************/
+
+HRESULT CContainer::GetWindowContext (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppIIPUIWin,
+ LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
+{
+ *ppFrame = (IOleInPlaceFrame *)this;
+ *ppIIPUIWin = NULL;
+
+ RECT rect;
+ GetClientRect(m_hwnd, &rect);
+ lprcPosRect->left = 0;
+ lprcPosRect->top = 0;
+ lprcPosRect->right = rect.right;
+ lprcPosRect->bottom = rect.bottom;
+
+ CopyRect(lprcClipRect, lprcPosRect);
+
+ lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);
+ lpFrameInfo->fMDIApp = FALSE;
+ lpFrameInfo->hwndFrame = m_hwnd;
+ lpFrameInfo->haccel = 0;
+ lpFrameInfo->cAccelEntries = 0;
+
+ (*ppFrame)->AddRef();
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::Scroll()
+
+**************************************************************************/
+
+HRESULT CContainer::Scroll(SIZE scrollExtent)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::OnUIDeactivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnUIDeactivate(BOOL fUndoable)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::OnInPlaceDeactivate()
+
+**************************************************************************/
+
+HRESULT CContainer::OnInPlaceDeactivate(void)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::DiscardUndoState()
+
+**************************************************************************/
+
+HRESULT CContainer::DiscardUndoState(void)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::DeactivateAndUndo()
+
+**************************************************************************/
+
+HRESULT CContainer::DeactivateAndUndo(void)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::OnPosRectChange()
+
+**************************************************************************/
+
+HRESULT CContainer::OnPosRectChange(LPCRECT lprcPosRect)
+{
+ HRESULT hr;
+ IOleInPlaceObject *pipo;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (SUCCEEDED(hr))
+ {
+ pipo->SetObjectRects(lprcPosRect, lprcPosRect);
+ pipo->Release();
+ }
+ return(S_OK);
+}
+
+// ***********************************************************************
+// IOleInPlaceUIWindow
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::GetBorder()
+
+**************************************************************************/
+
+HRESULT CContainer::GetBorder(LPRECT lprectBorder)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::RequestBorderSpace()
+
+**************************************************************************/
+
+HRESULT CContainer::RequestBorderSpace(LPCBORDERWIDTHS lpborderwidths)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetBorderSpace()
+
+**************************************************************************/
+
+HRESULT CContainer::SetBorderSpace(LPCBORDERWIDTHS lpborderwidths)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetActiveObject()
+
+**************************************************************************/
+
+HRESULT CContainer::SetActiveObject(IOleInPlaceActiveObject * pActiveObject, LPCOLESTR lpszObjName)
+{
+ return S_OK;
+}
+
+// ***********************************************************************
+// IOleInPlaceFrame
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::InsertMenus()
+
+**************************************************************************/
+
+HRESULT CContainer::InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetMenu()
+
+**************************************************************************/
+
+HRESULT CContainer::SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::RemoveMenus()
+
+**************************************************************************/
+
+HRESULT CContainer::RemoveMenus(HMENU hmenuShared)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::SetStatusText()
+
+**************************************************************************/
+
+HRESULT CContainer::SetStatusText(LPCOLESTR pszStatusText)
+{
+ char status[MAX_PATH]; // ansi version of status text
+
+ if (NULL == pszStatusText)
+ return E_POINTER;
+
+ WideCharToMultiByte(CP_ACP, 0, pszStatusText, -1, status, MAX_PATH, NULL, NULL);
+
+ if (IsWindow(m_hwndStatus))
+ SendMessage(m_hwndStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)status);
+
+ return (S_OK);
+}
+
+/**************************************************************************
+
+ CContainer::EnableModeless()
+
+**************************************************************************/
+
+HRESULT CContainer::EnableModeless(BOOL fEnable)
+{
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::TranslateAccelerator()
+
+**************************************************************************/
+
+HRESULT CContainer::TranslateAccelerator(LPMSG lpmsg, WORD wID)
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IOleControlSite
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::OnControlInfoChanged()
+
+**************************************************************************/
+
+HRESULT CContainer::OnControlInfoChanged()
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::LockInPlaceActive()
+
+**************************************************************************/
+
+HRESULT CContainer::LockInPlaceActive(BOOL fLock)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetExtendedControl()
+
+**************************************************************************/
+
+HRESULT CContainer::GetExtendedControl(IDispatch **ppDisp)
+{
+ if (ppDisp == NULL)
+ return E_INVALIDARG;
+
+ *ppDisp = (IDispatch *)this;
+ (*ppDisp)->AddRef();
+
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CContainer::TransformCoords()
+
+**************************************************************************/
+
+HRESULT CContainer::TransformCoords(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::TranslateAccelerator()
+
+**************************************************************************/
+
+HRESULT CContainer::TranslateAccelerator(LPMSG pMsg, DWORD grfModifiers)
+{
+ return S_FALSE;
+}
+
+/**************************************************************************
+
+ CContainer::OnFocus()
+
+**************************************************************************/
+
+HRESULT CContainer::OnFocus(BOOL fGotFocus)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::ShowPropertyFrame()
+
+**************************************************************************/
+
+HRESULT CContainer::ShowPropertyFrame(void)
+{
+ return E_NOTIMPL;
+}
+
+// ***********************************************************************
+// IDispatch
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::GetIDsOfNames()
+
+**************************************************************************/
+
+HRESULT CContainer::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+}
+
+/**************************************************************************
+
+ CContainer::GetTypeInfo()
+
+**************************************************************************/
+
+HRESULT CContainer::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::GetTypeInfoCount()
+
+**************************************************************************/
+
+HRESULT CContainer::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CContainer::Invoke()
+
+**************************************************************************/
+
+HRESULT CContainer::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ if(dispid==DISPID_AMBIENT_DLCONTROL)
+ {
+ m_restrictAlreadySet=1;
+ if(!m_allowScripts)
+ {
+ pvarResult->vt = VT_I4;
+ pvarResult->lVal = DLCTL_DLIMAGES|DLCTL_NO_SCRIPTS|DLCTL_NO_DLACTIVEXCTLS|DLCTL_NO_RUNACTIVEXCTLS|DLCTL_NO_JAVA;
+ return S_OK;
+ }
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+// ***********************************************************************
+// Public (non-interface) Methods
+// ***********************************************************************
+
+/**************************************************************************
+
+ CContainer::add()
+
+**************************************************************************/
+
+void CContainer::add(BSTR bstrClsid)
+{
+ CLSID clsid; // CLSID of the control object
+ HRESULT hr; // return code
+
+ CLSIDFromString(bstrClsid, &clsid);
+ CoCreateInstance(clsid,
+ NULL,
+ CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
+ IID_IUnknown,
+ (PVOID *)&m_punk);
+
+ if (!m_punk)
+ return;
+
+ IOleObject *pioo;
+ hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (FAILED(hr))
+ return;
+
+ pioo->SetClientSite(this);
+ pioo->Release();
+
+ IPersistStreamInit *ppsi;
+ hr = m_punk->QueryInterface(IID_IPersistStreamInit, (PVOID *)&ppsi);
+ if (SUCCEEDED(hr))
+ {
+ ppsi->InitNew();
+ ppsi->Release();
+ }
+}
+
+/**************************************************************************
+
+ CContainer::remove()
+
+**************************************************************************/
+
+void CContainer::remove()
+{
+ if (!m_punk)
+ return;
+
+ HRESULT hr;
+ IOleObject *pioo;
+ IOleInPlaceObject *pipo;
+
+ hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (SUCCEEDED(hr))
+ {
+ pioo->Close(OLECLOSE_NOSAVE);
+ pioo->SetClientSite(NULL);
+ pioo->Release();
+ }
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (SUCCEEDED(hr))
+ {
+ pipo->UIDeactivate();
+ pipo->InPlaceDeactivate();
+ pipo->Release();
+ }
+
+ m_punk->Release();
+ m_punk = NULL;
+}
+
+/**************************************************************************
+
+ CContainer::setParent()
+
+**************************************************************************/
+
+void CContainer::setParent(HWND hwndParent)
+{
+ m_hwnd = hwndParent;
+}
+
+/**************************************************************************
+
+ CContainer::setLocation()
+
+**************************************************************************/
+
+void CContainer::setLocation(int x, int y, int width, int height)
+{
+ RECT rc;
+ ::SetRect(&rc, x, y, x + width, y + height);
+
+ if (!m_punk) return;
+
+ HRESULT hr;
+ IOleInPlaceObject *pipo;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (FAILED(hr))
+ return;
+
+ pipo->SetObjectRects(&rc, &rc);
+ pipo->Release();
+}
+
+BOOL CContainer::SetRect(RECT *prc)
+{
+ HRESULT hr;
+ IOleInPlaceObject *pipo;
+
+
+ if (!m_punk) return FALSE;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceObject, (PVOID *)&pipo);
+ if (SUCCEEDED(hr))
+ {
+ hr = pipo->SetObjectRects(prc, prc);
+ pipo->Release();
+ }
+ return SUCCEEDED(hr);
+}
+/**************************************************************************
+
+ CContainer::setVisible()
+
+**************************************************************************/
+
+void CContainer::setVisible(BOOL fVisible)
+{
+ if (!m_punk)
+ return;
+
+ HRESULT hr;
+ IOleObject *pioo;
+
+ hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (FAILED(hr))
+ return;
+
+ if (fVisible)
+ {
+
+ pioo->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, m_hwnd, NULL);
+ pioo->DoVerb(OLEIVERB_SHOW, NULL, this, 0, m_hwnd, NULL);
+ }
+ else
+ pioo->DoVerb(OLEIVERB_HIDE, NULL, this, 0, m_hwnd, NULL);
+
+ pioo->Release();
+}
+
+/**************************************************************************
+
+ CContainer::setFocus()
+
+**************************************************************************/
+
+void CContainer::setFocus(BOOL fFocus)
+{
+ if (!m_punk)
+ return;
+
+ if (fFocus)
+ {
+ IOleObject *pioo;
+ HRESULT hr = m_punk->QueryInterface(IID_IOleObject, (PVOID *)&pioo);
+ if (FAILED(hr))
+ return;
+
+ pioo->DoVerb(OLEIVERB_UIACTIVATE, NULL, this, 0, m_hwnd, NULL);
+ pioo->Release();
+ }
+}
+
+/**************************************************************************
+
+ CContainer::setStatusWindow()
+
+**************************************************************************/
+
+void CContainer::setStatusWindow(HWND hwndStatus)
+{
+ m_hwndStatus = hwndStatus;
+}
+
+/**************************************************************************
+
+ CContainer::translateKey()
+
+**************************************************************************/
+
+void CContainer::translateKey(MSG msg)
+{
+ if (!m_punk)
+ return;
+
+ HRESULT hr;
+ IOleInPlaceActiveObject *pao;
+
+ hr = m_punk->QueryInterface(IID_IOleInPlaceActiveObject, (PVOID *)&pao);
+ if (FAILED(hr))
+ return;
+
+ pao->TranslateAccelerator(&msg);
+ pao->Release();
+}
+
+/**************************************************************************
+
+ * CContainer::getDispatch()
+
+**************************************************************************/
+
+IDispatch * CContainer::getDispatch()
+{
+ if (!m_punk)
+ return NULL;
+
+ IDispatch *pdisp;
+ m_punk->QueryInterface(IID_IDispatch, (PVOID *)&pdisp);
+ return pdisp;
+}
+
+/**************************************************************************
+
+ * CContainer::getUnknown()
+
+**************************************************************************/
+
+IUnknown * CContainer::getUnknown()
+{
+ if (!m_punk)
+ return NULL;
+
+ m_punk->AddRef();
+ return m_punk;
+}
+
+void CContainer::setScrollbars(int scroll)
+{
+ m_scrollbars=scroll;
+}
+
+// ***********************************************************************
+// IDocHostUIHandler
+// ***********************************************************************
+
+HRESULT CContainer::ShowContextMenu(DWORD dwID, POINT __RPC_FAR *ppt, IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CContainer::GetHostInfo(DOCHOSTUIINFO __RPC_FAR *pInfo)
+{
+ pInfo->dwFlags=0x00200000|(m_scrollbars?0:8);
+ return S_OK;
+}
+
+HRESULT CContainer::ShowUI(DWORD dwID, IOleInPlaceActiveObject __RPC_FAR *pActiveObject, IOleCommandTarget __RPC_FAR *pCommandTarget, IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CContainer::HideUI(void)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::UpdateUI(void)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::OnDocWindowActivate(BOOL fActivate)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::OnFrameWindowActivate(BOOL fActivate)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::TranslateAccelerator(LPMSG lpMsg, const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::GetOptionKeyPath(LPOLESTR __RPC_FAR *pchKey, DWORD dw)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::GetDropTarget(IDropTarget __RPC_FAR *pDropTarget, IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::GetExternal(IDispatch __RPC_FAR *__RPC_FAR *ppDispatch)
+{
+ *ppDispatch = winampExternal;
+ return S_OK; //E_NOTIMPL;
+}
+
+
+HRESULT CContainer::TranslateUrl(DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn, OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut)
+{
+ return E_NOTIMPL;
+}
+
+
+HRESULT CContainer::FilterDataObject(IDataObject __RPC_FAR *pDO, IDataObject __RPC_FAR *__RPC_FAR *ppDORet)
+{
+ return E_NOTIMPL;
+}
+
diff --git a/Src/Plugins/Library/ml_local/contnr.h b/Src/Plugins/Library/ml_local/contnr.h
new file mode 100644
index 00000000..aa969591
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/contnr.h
@@ -0,0 +1,153 @@
+/**************************************************************************
+ THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ PARTICULAR PURPOSE.
+
+ Copyright 1998 Microsoft Corporation. All Rights Reserved.
+**************************************************************************/
+
+/**************************************************************************
+
+ File: contnr.h
+
+ Description: This file contains the complete class specification of an
+ ActiveX control container. This purpose of this container
+ is to test a single control being hosted.
+
+**************************************************************************/
+
+#ifndef _CONTAINER_H_
+#define _CONTAINER_H_
+
+/**************************************************************************
+ #include statements
+**************************************************************************/
+
+#include <ocidl.h>
+#include <mshtmhst.h>
+
+/**************************************************************************
+ class definitions
+**************************************************************************/
+
+class CContainer : public IOleClientSite,
+ public IOleInPlaceSite,
+ public IOleInPlaceFrame,
+ public IOleControlSite,
+ public IDocHostUIHandler,
+ public IDispatch
+{
+ private:
+ ULONG m_cRefs; // ref count
+ HWND m_hwnd; // window handle of the container
+ HWND m_hwndStatus; // status window handle
+ IUnknown *m_punk; // IUnknown of contained object
+ int m_scrollbars;
+
+ int m_allowScripts, m_restrictAlreadySet;
+
+ public:
+ CContainer();
+ ~CContainer();
+
+ public:
+ // *** IUnknown Methods ***
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IOleClientSite Methods ***
+ STDMETHOD (SaveObject)();
+ STDMETHOD (GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER *ppMk);
+ STDMETHOD (GetContainer)(LPOLECONTAINER *ppContainer);
+ STDMETHOD (ShowObject)();
+ STDMETHOD (OnShowWindow)(BOOL fShow);
+ STDMETHOD (RequestNewObjectLayout)();
+
+ // *** IOleWindow Methods ***
+ STDMETHOD (GetWindow) (HWND * phwnd);
+ STDMETHOD (ContextSensitiveHelp) (BOOL fEnterMode);
+
+ // *** IOleInPlaceSite Methods ***
+ STDMETHOD (CanInPlaceActivate) (void);
+ STDMETHOD (OnInPlaceActivate) (void);
+ STDMETHOD (OnUIActivate) (void);
+ STDMETHOD (GetWindowContext) (IOleInPlaceFrame ** ppFrame, IOleInPlaceUIWindow ** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo);
+ STDMETHOD (Scroll) (SIZE scrollExtent);
+ STDMETHOD (OnUIDeactivate) (BOOL fUndoable);
+ STDMETHOD (OnInPlaceDeactivate) (void);
+ STDMETHOD (DiscardUndoState) (void);
+ STDMETHOD (DeactivateAndUndo) (void);
+ STDMETHOD (OnPosRectChange) (LPCRECT lprcPosRect);
+
+ // *** IOleInPlaceUIWindow Methods ***
+ STDMETHOD (GetBorder)(LPRECT lprectBorder);
+ STDMETHOD (RequestBorderSpace)(LPCBORDERWIDTHS lpborderwidths);
+ STDMETHOD (SetBorderSpace)(LPCBORDERWIDTHS lpborderwidths);
+ STDMETHOD (SetActiveObject)(IOleInPlaceActiveObject * pActiveObject,
+ LPCOLESTR lpszObjName);
+
+ // *** IOleInPlaceFrame Methods ***
+ STDMETHOD (InsertMenus)(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
+ STDMETHOD (SetMenu)(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject);
+ STDMETHOD (RemoveMenus)(HMENU hmenuShared);
+ STDMETHOD (SetStatusText)(LPCOLESTR pszStatusText);
+ STDMETHOD (EnableModeless)(BOOL fEnable);
+ STDMETHOD (TranslateAccelerator)(LPMSG lpmsg, WORD wID);
+
+ // *** IOleControlSite Methods ***
+ STDMETHOD (OnControlInfoChanged)(void);
+ STDMETHOD (LockInPlaceActive)(BOOL fLock);
+ STDMETHOD (GetExtendedControl)(IDispatch **ppDisp);
+ STDMETHOD (TransformCoords)(POINTL *pptlHimetric, POINTF *pptfContainer, DWORD dwFlags);
+ STDMETHOD (TranslateAccelerator)(LPMSG pMsg, DWORD grfModifiers);
+ STDMETHOD (OnFocus)(BOOL fGotFocus);
+ STDMETHOD (ShowPropertyFrame)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+
+ // *** IDocHostUIHandler Methods ***
+ STDMETHOD (ShowContextMenu)(DWORD dwID, POINT __RPC_FAR *ppt, IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved);
+ STDMETHOD (GetHostInfo)(DOCHOSTUIINFO __RPC_FAR *pInfo);
+ STDMETHOD (ShowUI)(DWORD dwID, IOleInPlaceActiveObject __RPC_FAR *pActiveObject, IOleCommandTarget __RPC_FAR *pCommandTarget, IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc);
+ STDMETHOD (HideUI)(void);
+ STDMETHOD (UpdateUI)(void);
+ STDMETHOD (OnDocWindowActivate)(BOOL fActivate);
+ STDMETHOD (OnFrameWindowActivate)(BOOL fActivate);
+ STDMETHOD (ResizeBorder)(LPCRECT prcBorder, IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow);
+ STDMETHOD (TranslateAccelerator)(LPMSG lpMsg, const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID);
+ STDMETHOD (GetOptionKeyPath)(LPOLESTR __RPC_FAR *pchKey, DWORD dw);
+ STDMETHOD (GetDropTarget)(IDropTarget __RPC_FAR *pDropTarget, IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget);
+ STDMETHOD (GetExternal)(IDispatch __RPC_FAR *__RPC_FAR *ppDispatch);
+ STDMETHOD (TranslateUrl)(DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn, OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut);
+ STDMETHOD (FilterDataObject)(IDataObject __RPC_FAR *pDO, IDataObject __RPC_FAR *__RPC_FAR *ppDORet);
+
+ public:
+ void add(BSTR clsid);
+ void remove();
+ void setParent(HWND hwndParent);
+ void setLocation(int x, int y, int width, int height);
+
+ void setVisible(BOOL fVisible);
+ void setFocus(BOOL fFocus);
+ void setStatusWindow(HWND hwndStatus);
+ void translateKey(MSG msg);
+ void setScrollbars(int scroll);
+
+ void setAllowScripts(int s) { m_allowScripts=s; }
+ int getAllowScripts() { return m_allowScripts; }
+
+ int getRestrictAlreadySet() { return m_restrictAlreadySet; }
+
+ IDispatch *getDispatch();
+ IUnknown * getUnknown();
+
+ BOOL SetRect(RECT *prc);
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/db.h b/Src/Plugins/Library/ml_local/db.h
new file mode 100644
index 00000000..76b2eca0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/db.h
@@ -0,0 +1,64 @@
+#ifndef NULLSOT_LOCALMEDIA_DB_H
+#define NULLSOT_LOCALMEDIA_DB_H
+
+#define MAINTABLE_ID_FILENAME 0
+#define MAINTABLE_ID_TITLE 1
+#define MAINTABLE_ID_ARTIST 2
+#define MAINTABLE_ID_ALBUM 3
+#define MAINTABLE_ID_YEAR 4
+#define MAINTABLE_ID_GENRE 5
+#define MAINTABLE_ID_COMMENT 6
+#define MAINTABLE_ID_TRACKNB 7
+#define MAINTABLE_ID_LENGTH 8 //in seconds
+#define MAINTABLE_ID_TYPE 9 //0=audio, 1=video
+#define MAINTABLE_ID_LASTUPDTIME 10 // last time (seconds since 1970) of db update of this item
+#define MAINTABLE_ID_LASTPLAY 11 // last time (seconds since 1970) of last play
+#define MAINTABLE_ID_RATING 12
+#define MAINTABLE_ID_GRACENOTE_ID 14
+#define MAINTABLE_ID_PLAYCOUNT 15 // play count
+#define MAINTABLE_ID_FILETIME 16 // file time
+#define MAINTABLE_ID_FILESIZE 17 // file size, kilobytes
+#define MAINTABLE_ID_BITRATE 18 // file bitratea, kbps
+
+#include "../nde/nde.h"
+#include <map>
+#include <string>
+
+// DataBase manipulations
+class DB
+{
+// construcotrs
+public:
+ DB();
+ ~DB();
+
+// methods
+public:
+ int Open();
+ int Close();
+ int Nuke();
+ int AddColumn(char* metaKey, int type); // returns index of the new column or -1 on error
+private:
+ BOOL Discover(void);
+ void ClearMap(void);
+
+
+// properties
+public:
+ void SetTableDir(const char* tableDir);
+ const char* GetTableDir();
+ int GetColumnsCount();
+ int GetColumnId(char *metaKey); // returns index of the column or -1 if can't find
+
+// fields
+private:
+ char * tableDir;
+ Database db;
+ Table *table;
+ Scanner *sc;
+
+ std::map< std::string, int> columnsMap;
+
+};
+
+#endif //NULLSOT_LOCALMEDIA_DB_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/db_error.txt b/Src/Plugins/Library/ml_local/db_error.txt
new file mode 100644
index 00000000..055cc656
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/db_error.txt
@@ -0,0 +1,6 @@
+
+There was an error reading the local database. This could be due to the database becoming corrupted or having been removed.
+
+If the database files (main.dat and main.idx) have become corrupted then unless you have a back up, you will need to reset the database and then re-add any media files required back into the database.
+
+By resetting the database, any custom ratings and play counts not already stored in the media file(s) will be lost which also will have happened if the database was corrupted. \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/editinfo.cpp b/Src/Plugins/Library/ml_local/editinfo.cpp
new file mode 100644
index 00000000..4242cbfe
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/editinfo.cpp
@@ -0,0 +1,816 @@
+#include "main.h"
+#include "../Agave/Language/api_language.h"
+#include "../nu/ListView.h"
+#include "../Winamp/strutil.h"
+#include "resource.h"
+#include <time.h>
+#define MAKESAFE(x) ((x)?(x):L"")
+
+extern W_ListView resultlist;
+extern itemRecordListW itemCache;
+volatile int no_lv_update = 0;
+//////////////// info editor fun
+
+
+// must be one of OUR item records (since we free it)
+static void UpdateItemRecordFromDB(itemRecordW *song)
+{
+ // look in the database for the updated song info
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, song->filename))
+ {
+ // now we can actually update the itemCache itemRecordW from the value in the db
+ itemRecordW item = {0};
+ itemRecordListW obj = {&item, 0, 1};
+ ScannerRefToObjCacheNFNW(s, &obj, false);
+
+ item.filename = song->filename;
+ song->filename = NULL; // set to NULL so freeRecord doesn't delete the filename string
+
+ freeRecord(song); // delete old item
+ *song = item; // replace with our new (BETTER :) item
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+}
+
+//physically update metadata in a given file
+int updateFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *data)
+{
+ extendedFileInfoStructW efis = {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? wcslen(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+static int m_upd_nb, m_stopped, m_upd_nb_all, m_upd_nb_cur;
+static nde_scanner_t m_scanner;
+
+// sets part and parts to -1 or 0 on fail/missing (e.g. parts will be -1 on "1", but 0 on "1/")
+void ParseIntSlashInt(wchar_t *string, int *part, int *parts)
+{
+ *part = -1;
+ *parts = -1;
+
+ if (string && string[0])
+ {
+ *part = _wtoi(string);
+ while (string && *string && *string != '/')
+ {
+ string++;
+ }
+ if (string && *string == '/')
+ {
+ string++;
+ *parts = _wtoi(string);
+ }
+ }
+}
+
+// TODO: benski> can this copy-and-paste code be factored?
+static INT_PTR CALLBACK updateFiles_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowTextW(hwndDlg, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_UPDATING_FILES));
+ SetWindowLong(GetDlgItem(hwndDlg, IDC_STATUS), GWL_STYLE, (GetWindowLong(GetDlgItem(hwndDlg, IDC_STATUS), GWL_STYLE)&~SS_CENTER) | SS_LEFTNOWORDWRAP);
+ SetTimer(hwndDlg, 0x123, 30, NULL);
+ m_upd_nb = 0;
+ EnterCriticalSection(&g_db_cs);
+ m_scanner = NDE_Table_CreateScanner(g_table);
+ m_stopped = 0;
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETRANGE, 0, MAKELPARAM(0, m_upd_nb_all));
+ m_upd_nb_cur = 0;
+
+ // show window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"scan_x", -1), g_config->ReadInt(L"scan_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+
+ break;
+ }
+ case WM_TIMER:
+ if (wParam == 0x123 && !m_stopped)
+ {
+ unsigned int start_t = GetTickCount();
+again:
+ {
+ int l = resultlist.GetCount();
+
+ while (m_upd_nb < l && !resultlist.GetSelected(m_upd_nb))
+ m_upd_nb++;
+
+ if (m_upd_nb >= l)
+ {
+ //done
+ EndDialog(hwndDlg, 1);
+ break;
+ }
+
+ int i = m_upd_nb++;
+
+ itemRecordW *song = itemCache.Items + i;
+ HWND hwndParent = GetParent(hwndDlg);
+
+ wchar_t stattmp[512] = {0};
+ wchar_t *p = scanstr_backW(song->filename, L"\\", song->filename - 1) + 1;
+ wsprintfW(stattmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_UPDATING_X), p);
+ SetDlgItemTextW(hwndDlg, IDC_STATUS, stattmp);
+
+ SendDlgItemMessage(hwndDlg, IDC_PROGRESS1, PBM_SETPOS, m_upd_nb_cur, 0);
+ m_upd_nb_cur++;
+
+ int updtagz = !!IsDlgButtonChecked(hwndParent, IDC_CHECK1);
+ if (!NDE_Scanner_LocateNDEFilename(m_scanner, MAINTABLE_ID_FILENAME, FIRST_RECORD, song->filename))
+ {
+ break;
+ }
+
+ NDE_Scanner_Edit(m_scanner);
+
+#define CHECK_AND_COPY(IDCHECK, ID, field, item) if (IsDlgButtonChecked(hwndParent, IDCHECK)) {\
+ wchar_t blah[2048] = {0}; GetDlgItemTextW(hwndParent, ID, blah, 2048);\
+ if (wcscmp(MAKESAFE(item), blah))\
+ { if (blah[0]) db_setFieldStringW(m_scanner, field, blah);\
+ else db_removeField(m_scanner, field);\
+ ndestring_release(item);\
+ item = ndestring_wcsdup(blah);}}
+
+ CHECK_AND_COPY(IDC_CHECK_ARTIST, IDC_EDIT_ARTIST, MAINTABLE_ID_ARTIST, song->artist);
+ CHECK_AND_COPY(IDC_CHECK_TITLE, IDC_EDIT_TITLE, MAINTABLE_ID_TITLE, song->title);
+ CHECK_AND_COPY(IDC_CHECK_ALBUM, IDC_EDIT_ALBUM, MAINTABLE_ID_ALBUM, song->album);
+ CHECK_AND_COPY(IDC_CHECK_COMMENT, IDC_EDIT_COMMENT, MAINTABLE_ID_COMMENT, song->comment);
+ CHECK_AND_COPY(IDC_CHECK_GENRE, IDC_EDIT_GENRE, MAINTABLE_ID_GENRE, song->genre);
+ CHECK_AND_COPY(IDC_CHECK_ALBUMARTIST, IDC_EDIT_ALBUMARTIST, MAINTABLE_ID_ALBUMARTIST, song->albumartist);
+ CHECK_AND_COPY(IDC_CHECK_PUBLISHER, IDC_EDIT_PUBLISHER, MAINTABLE_ID_PUBLISHER, song->publisher);
+ CHECK_AND_COPY(IDC_CHECK_COMPOSER, IDC_EDIT_COMPOSER, MAINTABLE_ID_COMPOSER, song->composer);
+ CHECK_AND_COPY(IDC_CHECK_CATEGORY, IDC_EDIT_CATEGORY, MAINTABLE_ID_CATEGORY, song->category);
+
+#define CHECK_AND_COPY_EXTENDED(IDCHECK, ID, field, name) if (IsDlgButtonChecked(hwndParent, IDCHECK)) {\
+ wchar_t blah[2048] = {0}; GetDlgItemTextW(hwndParent, ID, blah, 2048);\
+ wchar_t *oldData = getRecordExtendedItem_fast(song, name);\
+ if (wcscmp(MAKESAFE(oldData), blah))\
+ { if (blah[0]) db_setFieldStringW(m_scanner, field, blah);\
+ else db_removeField(m_scanner, field);\
+ wchar_t *nde_blah = ndestring_wcsdup(blah);\
+ setRecordExtendedItem(song, name, nde_blah);\
+ ndestring_release(nde_blah);}}
+
+ CHECK_AND_COPY_EXTENDED(IDC_CHECK_DIRECTOR, IDC_EDIT_DIRECTOR, MAINTABLE_ID_DIRECTOR, extended_fields.director);
+ CHECK_AND_COPY_EXTENDED(IDC_CHECK_PRODUCER, IDC_EDIT_PRODUCER, MAINTABLE_ID_PRODUCER, extended_fields.producer);
+ CHECK_AND_COPY_EXTENDED(IDC_CHECK_PODCAST_CHANNEL, IDC_EDIT_PODCAST_CHANNEL, MAINTABLE_ID_PODCASTCHANNEL, extended_fields.podcastchannel);
+
+#define CHECK_AND_COPY_EXTENDED_PC(IDCHECK, field, name) \
+ wchar_t blah[2048] = {0}; StringCchPrintfW(blah, 2048, L"%d", (IsDlgButtonChecked(hwndParent, IDCHECK) == BST_CHECKED));\
+ wchar_t *oldData = getRecordExtendedItem_fast(song, name);\
+ if (wcscmp(MAKESAFE(oldData), blah))\
+ { if (blah[0]) db_setFieldInt(m_scanner, field, _wtoi(blah));\
+ else db_removeField(m_scanner, field);\
+ wchar_t *nde_blah = ndestring_wcsdup(blah);\
+ setRecordExtendedItem(song, name, nde_blah);\
+ ndestring_release(nde_blah);}
+
+ CHECK_AND_COPY_EXTENDED_PC(IDC_CHECK_PODCAST, MAINTABLE_ID_ISPODCAST, extended_fields.ispodcast);
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_TRACK))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemTextW(hwndParent, IDC_EDIT_TRACK, blah, 64);
+ int track, tracks;
+ ParseIntSlashInt(blah, &track, &tracks);
+ if (tracks <= 0) tracks = -1;
+ if (track <= 0) track = -1;
+
+ if (song->track != track || song->tracks != tracks)
+ {
+ if (track>0) db_setFieldInt(m_scanner, MAINTABLE_ID_TRACKNB, track);
+ else db_removeField(m_scanner, MAINTABLE_ID_TRACKNB);
+ if (tracks>0) db_setFieldInt(m_scanner, MAINTABLE_ID_TRACKS, tracks);
+ else db_removeField(m_scanner, MAINTABLE_ID_TRACKS);
+ song->track = track;
+ song->tracks = tracks;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_DISC))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemTextW(hwndParent, IDC_EDIT_DISC, blah, 64);
+ int disc, discs;
+ ParseIntSlashInt(blah, &disc, &discs);
+ if (discs <= 0) discs = -1;
+ if (disc <= 0) disc = -1;
+
+ if (song->disc != disc || song->discs != discs)
+ {
+ if (disc>0) db_setFieldInt(m_scanner, MAINTABLE_ID_DISC, disc);
+ else db_removeField(m_scanner, MAINTABLE_ID_DISC);
+ if (discs>0) db_setFieldInt(m_scanner, MAINTABLE_ID_DISCS, discs);
+ else db_removeField(m_scanner, MAINTABLE_ID_DISCS);
+ song->disc = disc;
+ song->discs = discs;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_YEAR))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndParent, IDC_EDIT_YEAR, blah, 64);
+ int n = _wtoi(blah);
+ if (n <= 0) n = -1;
+ if (song->year != n)
+ {
+ if (n > 0) db_setFieldInt(m_scanner, MAINTABLE_ID_YEAR, n);
+ else db_removeField(m_scanner, MAINTABLE_ID_YEAR);
+ song->year = n;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_BPM))
+ {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndParent, IDC_EDIT_BPM, blah, 64);
+ int n = _wtoi(blah);
+ if (n <= 0) n = -1;
+ if (song->bpm != n)
+ {
+ if (n > 0) db_setFieldInt(m_scanner, MAINTABLE_ID_BPM, n);
+ else db_removeField(m_scanner, MAINTABLE_ID_BPM);
+ song->bpm = n;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndParent, IDC_CHECK_RATING))
+ {
+ int n = SendDlgItemMessage(hwndParent, IDC_COMBO_RATING, CB_GETCURSEL, 0, 0), rating = -1;
+ if (n != CB_ERR && (n >= 0 && n < 5))
+ {
+ rating = 5 - n;
+ }
+ /*int n = atoi(blah);
+ if (n < 0 || n > 5) n = -1;*/
+ if (song->rating != rating)
+ {
+ if (rating > 0) db_setFieldInt(m_scanner, MAINTABLE_ID_RATING, n);
+ else db_removeField(m_scanner, MAINTABLE_ID_RATING);
+ song->rating = rating;
+ }
+ }
+
+ if (updtagz)
+ {
+ m_stopped = 1;
+retry:
+ if (updateFileInfo(song->filename, DB_FIELDNAME_title, song->title)) // if this returns 0, then this format doesnt even support extended
+ {
+ updateFileInfo(song->filename, DB_FIELDNAME_artist, song->artist);
+ updateFileInfo(song->filename, DB_FIELDNAME_album, song->album);
+ updateFileInfo(song->filename, DB_FIELDNAME_comment, song->comment);
+ updateFileInfo(song->filename, DB_FIELDNAME_genre, song->genre);
+
+ wchar_t buf[32] = {0};
+ if (song->track > 0)
+ {
+ if (song->tracks > 0)
+ wsprintfW(buf, L"%d/%d", song->track, song->tracks);
+ else
+ wsprintfW(buf, L"%d", song->track);
+ }
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_track, buf);
+
+ if (song->year > 0) wsprintfW(buf, L"%d", song->year);
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_year, buf);
+
+ if (song->disc > 0)
+ {
+ if (song->discs > 0)
+ wsprintfW(buf, L"%d/%d", song->disc, song->discs);
+ else
+ wsprintfW(buf, L"%d", song->disc);
+ }
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_disc, buf);
+
+ if (song->rating > 0) wsprintfW(buf, L"%d", song->rating);
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_rating, buf);
+
+ if (song->bpm > 0) wsprintfW(buf, L"%d", song->bpm);
+ else buf[0] = 0;
+ updateFileInfo(song->filename, DB_FIELDNAME_bpm, buf);
+
+ updateFileInfo(song->filename, DB_FIELDNAME_albumartist, song->albumartist);
+ updateFileInfo(song->filename, DB_FIELDNAME_publisher, song->publisher);
+ updateFileInfo(song->filename, DB_FIELDNAME_composer, song->composer);
+ updateFileInfo(song->filename, DB_FIELDNAME_category, song->category);
+ updateFileInfo(song->filename, DB_FIELDNAME_director, getRecordExtendedItem_fast(song, extended_fields.director));
+ updateFileInfo(song->filename, DB_FIELDNAME_producer, getRecordExtendedItem_fast(song, extended_fields.producer));
+
+ if (!SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO))
+ {
+ wchar_t tmp[1024] = {0};
+ wsprintfW(tmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_ERROR_UPDATING_FILE), song->filename);
+ int ret = MessageBoxW(hwndDlg, tmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_INFO_UPDATING_ERROR), MB_RETRYCANCEL);
+ if (ret == IDRETRY) goto retry;
+ if (ret == IDCANCEL)
+ {
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ }
+ }
+ m_stopped = 0;
+ }
+
+ db_setFieldInt(m_scanner, MAINTABLE_ID_LASTUPDTIME, (int)time(NULL));
+ db_setFieldInt(m_scanner, MAINTABLE_ID_FILETIME, (int)time(NULL));
+ NDE_Scanner_Post(m_scanner);
+
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_UPDATED, (size_t)song->filename, 0);
+ }
+ if (GetTickCount() - start_t < 30) goto again;
+ }
+ break;
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDCANCEL)
+ {
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case WM_DESTROY:
+ {
+ RECT scan_rect = {0};
+ GetWindowRect(hwndDlg, &scan_rect);
+ g_config->WriteInt(L"scan_x", scan_rect.left);
+ g_config->WriteInt(L"scan_y", scan_rect.top);
+
+ NDE_Table_DestroyScanner(g_table, m_scanner);
+ NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static int checkEditInfoClick(HWND hwndDlg, POINT p, int item, int check)
+{
+ RECT r = {0};
+ GetWindowRect(GetDlgItem(hwndDlg, item), &r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r.right);
+ if (PtInRect(&r, p) && !IsDlgButtonChecked(hwndDlg, check))
+ {
+ CheckDlgButton(hwndDlg, check, TRUE);
+ if (item == IDC_COMBO_RATING) SendDlgItemMessage(hwndDlg, IDC_COMBO_RATING, CB_SHOWDROPDOWN, TRUE, 0);
+ EnableWindow(GetDlgItem(hwndDlg, item), TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, item), (LPARAM)TRUE);
+ return 1;
+ }
+ return 0;
+}
+
+void getViewport(RECT *r, HWND wnd, int full, RECT *sr)
+{
+ POINT *p = NULL;
+ if (p || sr || wnd)
+ {
+ HMONITOR hm = NULL;
+
+ if (sr)
+ hm = MonitorFromRect(sr, MONITOR_DEFAULTTONEAREST);
+ else if (wnd)
+ hm = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
+ else if (p)
+ hm = MonitorFromPoint(*p, MONITOR_DEFAULTTONEAREST);
+
+ if (hm)
+ {
+ MONITORINFOEXW mi;
+ memset(&mi, 0, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+
+ if (GetMonitorInfoW(hm, &mi))
+ {
+ if (!full)
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+ return ;
+ }
+ }
+ }
+ if (full)
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics(SM_CXSCREEN);
+ r->bottom = GetSystemMetrics(SM_CYSCREEN);
+ }
+ else
+ {
+ SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0);
+ }
+}
+
+BOOL windowOffScreen(HWND hwnd, POINT pt)
+{
+ RECT r = {0}, wnd = {0}, sr = {0};
+ GetWindowRect(hwnd, &wnd);
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + (wnd.right - wnd.left);
+ sr.bottom = sr.top + (wnd.bottom - wnd.top);
+ getViewport(&r, hwnd, 0, &sr);
+ return !PtInRect(&r, pt);
+}
+
+// TODO: benski> can this copy-and-paste code be factored?
+static INT_PTR CALLBACK editInfo_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t *last_artist = NULL, *last_title = NULL, *last_album = NULL, *last_genre = NULL,
+ *last_comment = NULL, *last_albumartist = NULL, *last_composer = NULL,
+ *last_publisher = NULL, *last_ispodcast = NULL, *last_podcastchannel = NULL;
+ const wchar_t *last_category = NULL, *last_director = NULL, *last_producer = NULL;
+ int last_year = -1, last_track = -1, last_disc = -1, last_discs = -1, last_tracks = -1,
+ last_bpm = -1, last_rating = -1, disable_artist = 0, disable_title = 0,
+ disable_album = 0, disable_genre = 0, disable_year = 0, disable_track = 0,
+ disable_comment = 0, disable_disc = 0, disable_albumartist = 0, disable_composer = 0,
+ disable_publisher = 0, disable_discs = 0, disable_tracks = 0, disable_category = 0,
+ disable_director = 0, disable_producer = 0, disable_bpm = 0, disable_rating = 0,
+ disable_ispodcast = 0, disable_podcastchannel = 0, nb = 0;
+
+ if (g_config->ReadInt(L"upd_tagz", 1)) CheckDlgButton(hwndDlg, IDC_CHECK1, BST_CHECKED);
+
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
+
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605\u2605\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0, (LPARAM)L"\u2605");
+ SendDlgItemMessageW(hwndDlg, IDC_COMBO_RATING, CB_ADDSTRING, 0,
+ (LPARAM)WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDS_NO_RATING));
+
+ for (int i = 0; i < resultlist.GetCount(); i++)
+ {
+ if (!resultlist.GetSelected(i)) continue;
+
+ itemRecordW *song = itemCache.Items + i;
+
+#define SAVE_LAST_STR(last, check, disable) if (!disable && check && check[0]) { if (!last) last = check; else if (wcscmp(check, last)) disable = 1; }
+#define SAVE_LAST_INT(last, check, disable) if (!disable && check > 0) { if (last == -1) last = check; else if (last != check) disable = 1; }
+
+ SAVE_LAST_STR(last_artist, song->artist, disable_artist);
+ SAVE_LAST_STR(last_title, song->title, disable_title);
+ SAVE_LAST_STR(last_album, song->album, disable_album);
+ SAVE_LAST_STR(last_comment, song->comment, disable_comment);
+ SAVE_LAST_STR(last_genre, song->genre, disable_genre);
+
+ SAVE_LAST_INT(last_year, song->year, disable_year);
+ SAVE_LAST_INT(last_track, song->track, disable_track);
+ SAVE_LAST_INT(last_tracks, song->tracks, disable_tracks);
+ SAVE_LAST_INT(last_disc, song->disc, disable_disc);
+ SAVE_LAST_INT(last_discs, song->discs, disable_discs);
+ SAVE_LAST_INT(last_rating, song->rating, disable_rating);
+ SAVE_LAST_INT(last_bpm, song->bpm, disable_bpm);
+
+ SAVE_LAST_STR(last_albumartist, song->albumartist, disable_albumartist);
+ SAVE_LAST_STR(last_composer, song->composer, disable_composer);
+ SAVE_LAST_STR(last_publisher, song->publisher, disable_publisher);
+ SAVE_LAST_STR(last_category, song->category, disable_category);
+
+#define SAVE_LAST_STR_EXTENDED(last, name, disable) if (!disable) { wchar_t *check = getRecordExtendedItem_fast(song, name); if (check && check[0]) { if (!last) last = check; else if (wcscmp(check, last)) disable = 1; }};
+
+ SAVE_LAST_STR_EXTENDED(last_director, extended_fields.director, disable_director);
+ SAVE_LAST_STR_EXTENDED(last_producer, extended_fields.producer, disable_producer);
+ SAVE_LAST_STR_EXTENDED(last_podcastchannel, extended_fields.podcastchannel, disable_podcastchannel);
+ SAVE_LAST_STR_EXTENDED(last_ispodcast, extended_fields.ispodcast, disable_ispodcast);
+ nb++;
+ }
+
+ if (!disable_artist && last_artist) SetDlgItemTextW(hwndDlg, IDC_EDIT_ARTIST, last_artist);
+ if (!disable_title && last_title) SetDlgItemTextW(hwndDlg, IDC_EDIT_TITLE, last_title);
+ if (!disable_album && last_album) SetDlgItemTextW(hwndDlg, IDC_EDIT_ALBUM, last_album);
+ if (!disable_comment && last_comment) SetDlgItemTextW(hwndDlg, IDC_EDIT_COMMENT, last_comment);
+ if (!disable_albumartist && last_albumartist) SetDlgItemTextW(hwndDlg, IDC_EDIT_ALBUMARTIST, last_albumartist);
+ if (!disable_composer && last_composer) SetDlgItemTextW(hwndDlg, IDC_EDIT_COMPOSER, last_composer);
+ if (!disable_publisher && last_publisher) SetDlgItemTextW(hwndDlg, IDC_EDIT_PUBLISHER, last_publisher);
+ if (!disable_genre && last_genre) SetDlgItemTextW(hwndDlg, IDC_EDIT_GENRE, last_genre);
+ if (!disable_category && last_category) SetDlgItemTextW(hwndDlg, IDC_EDIT_CATEGORY, last_category);
+ if (!disable_director && last_director) SetDlgItemTextW(hwndDlg, IDC_EDIT_DIRECTOR, last_director);
+ if (!disable_producer && last_producer) SetDlgItemTextW(hwndDlg, IDC_EDIT_PRODUCER, last_producer);
+ if (!disable_podcastchannel && last_podcastchannel) SetDlgItemTextW(hwndDlg, IDC_EDIT_PODCAST_CHANNEL, last_podcastchannel);
+ if (!disable_ispodcast && last_ispodcast)
+ {
+ CheckDlgButton(hwndDlg, IDC_CHECK_PODCAST, (_wtoi(last_ispodcast) == 1 ? BST_CHECKED : BST_UNCHECKED));
+ }
+ if (!disable_year && last_year > 0)
+ {
+ wchar_t tmp[64] = {0};
+ wsprintfW(tmp, L"%d", last_year);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_YEAR, tmp);
+ }
+ if (!disable_bpm && last_bpm > 0)
+ {
+ wchar_t tmp[64] = {0};
+ wsprintfW(tmp, L"%d", last_bpm);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_BPM, tmp);
+ }
+ if (!disable_rating)
+ {
+ if (last_rating > 0 && last_rating <= 5)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_COMBO_RATING, CB_SETCURSEL, 5 - last_rating, 0);
+ }
+ else SendDlgItemMessage(hwndDlg, IDC_COMBO_RATING, CB_SETCURSEL, 5, 0);
+ }
+ if (!disable_track && last_track > 0 && !disable_tracks)
+ {
+ wchar_t tmp[64] = {0};
+ if (!disable_tracks && last_tracks > 0)
+ wsprintfW(tmp, L"%d/%d", last_track, last_tracks);
+ else
+ wsprintfW(tmp, L"%d", last_track);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_TRACK, tmp);
+ }
+ if (!disable_disc && last_disc > 0
+ && !disable_discs)
+ {
+ wchar_t tmp[64] = {0};
+ if (!disable_discs && last_discs > 0)
+ wsprintfW(tmp, L"%d/%d", last_disc, last_discs);
+ else
+ wsprintfW(tmp, L"%d", last_disc);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_DISC, tmp);
+ }
+ wchar_t tmp[512] = {0};
+ wsprintfW(tmp, WASABI_API_LNG->GetStringW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), (nb==1?IDS_X_ITEM_SELECTED:IDS_X_ITEMS_SELECTED)), nb);
+ SetDlgItemTextW(hwndDlg, IDC_HEADER, tmp);
+ m_upd_nb_all = nb;
+
+ // show edit info window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"edit_x", -1), g_config->ReadInt(L"edit_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ return 1;
+ case WM_COMMAND:
+#define HANDLE_CONTROL(item, check) { int enabled = IsDlgButtonChecked(hwndDlg, check); EnableWindow(GetDlgItem(hwndDlg, item), enabled); EnableWindow(GetDlgItem(hwndDlg, IDOK), enabled); }
+ switch (LOWORD(wParam))
+ {
+ case IDC_CHECK_ARTIST: HANDLE_CONTROL(IDC_EDIT_ARTIST, IDC_CHECK_ARTIST); break;
+ case IDC_CHECK_TITLE: HANDLE_CONTROL(IDC_EDIT_TITLE, IDC_CHECK_TITLE); break;
+ case IDC_CHECK_ALBUM: HANDLE_CONTROL(IDC_EDIT_ALBUM, IDC_CHECK_ALBUM); break;
+ case IDC_CHECK_COMMENT: HANDLE_CONTROL(IDC_EDIT_COMMENT, IDC_CHECK_COMMENT); break;
+ case IDC_CHECK_ALBUMARTIST: HANDLE_CONTROL(IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST); break;
+ case IDC_CHECK_COMPOSER: HANDLE_CONTROL(IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER); break;
+ case IDC_CHECK_PUBLISHER: HANDLE_CONTROL(IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER); break;
+ case IDC_CHECK_TRACK: HANDLE_CONTROL(IDC_EDIT_TRACK, IDC_CHECK_TRACK); break;
+ case IDC_CHECK_DISC: HANDLE_CONTROL(IDC_EDIT_DISC, IDC_CHECK_DISC); break;
+ case IDC_CHECK_GENRE: HANDLE_CONTROL(IDC_EDIT_GENRE, IDC_CHECK_GENRE); break;
+ case IDC_CHECK_YEAR: HANDLE_CONTROL(IDC_EDIT_YEAR, IDC_CHECK_YEAR); break;
+ case IDC_CHECK_CATEGORY: HANDLE_CONTROL(IDC_EDIT_CATEGORY, IDC_CHECK_CATEGORY); break;
+ case IDC_CHECK_DIRECTOR: HANDLE_CONTROL(IDC_EDIT_DIRECTOR, IDC_CHECK_DIRECTOR); break;
+ case IDC_CHECK_PRODUCER: HANDLE_CONTROL(IDC_EDIT_PRODUCER, IDC_CHECK_PRODUCER); break;
+ case IDC_CHECK_PODCAST_CHANNEL: HANDLE_CONTROL(IDC_EDIT_PODCAST_CHANNEL, IDC_CHECK_PODCAST_CHANNEL); break;
+ case IDC_CHECK_BPM: HANDLE_CONTROL(IDC_EDIT_BPM, IDC_CHECK_BPM); break;
+ case IDC_CHECK_RATING: HANDLE_CONTROL(IDC_COMBO_RATING, IDC_CHECK_RATING); break;
+ case IDOK:
+ {
+ int updtagz = !!IsDlgButtonChecked(hwndDlg, IDC_CHECK1);
+ g_config->WriteInt(L"upd_tagz", updtagz);
+ int ret = WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_ADDSTUFF,
+ hwndDlg, (DLGPROC)updateFiles_dialogProc, 0);
+ ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+ if (!ret) break;
+ }
+ case IDCANCEL:
+ RECT edit_rect = {0};
+ GetWindowRect(hwndDlg, &edit_rect);
+ g_config->WriteInt(L"edit_x", edit_rect.left);
+ g_config->WriteInt(L"edit_y", edit_rect.top);
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ case WM_LBUTTONDOWN:
+ {
+ POINTS p = MAKEPOINTS(lParam);
+ POINT p2 = {p.x, p.y};
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ARTIST, IDC_CHECK_ARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TITLE, IDC_CHECK_TITLE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUM, IDC_CHECK_ALBUM)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_COMMENT, IDC_CHECK_COMMENT)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TRACK, IDC_CHECK_TRACK)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_GENRE, IDC_CHECK_GENRE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_YEAR, IDC_CHECK_YEAR)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_DISC, IDC_CHECK_DISC)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_CATEGORY, IDC_CHECK_CATEGORY)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_DIRECTOR, IDC_CHECK_DIRECTOR)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PRODUCER, IDC_CHECK_PRODUCER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PODCAST_CHANNEL, IDC_CHECK_PODCAST_CHANNEL)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_BPM, IDC_CHECK_BPM)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_COMBO_RATING, IDC_CHECK_RATING)) break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void editInfo(HWND hwndParent)
+{
+ no_lv_update++;
+ bgQuery_Stop();
+
+ WASABI_API_LNG->LDialogBoxParamW(WASABI_API_LNG->FindDllHandleByGUID(WinampLangGUID),
+ GetModuleHandleW(L"winamp.exe"), IDD_EDIT_INFO,
+ hwndParent, (DLGPROC)editInfo_dialogProc, 0);
+
+ EatKeyboard();
+ no_lv_update--;
+}
+
+#define REFRESHCB_NUMITEMS (WM_USER)
+#define REFRESHCB_ITERATE (WM_USER+1)
+#define REFRESHCB_FINISH (WM_USER+2)
+static bool refreshKill;
+static int RefreshMetadataThread(HANDLE handle, void *_callback, intptr_t id)
+{
+ HWND callback = (HWND)_callback;
+ int l = resultlist.GetCount();
+ PostMessage(callback, REFRESHCB_NUMITEMS, 0, resultlist.GetSelectedCount());
+ for (int i = 0; i < l; i++)
+ {
+ if (refreshKill)
+ break;
+ if (!resultlist.GetSelected(i)) continue;
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+
+ EnterCriticalSection(&g_db_cs);
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(song->filename, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+
+ addFileToDb(song->filename, TRUE, meta, guess, 0, 0, true);
+ UpdateItemRecordFromDB(song);
+
+ PostMessage(callback, REFRESHCB_ITERATE, 0, 0);
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ PostMessage(callback, REFRESHCB_FINISH, 0, 0);
+ return 0;
+}
+
+static void WriteStatus(HWND hwndDlg, int dlgId, int numFiles, int totalFiles)
+{
+ wchar_t temp[1024] = {0};
+ if (numFiles + 1 > totalFiles)
+ WASABI_API_LNGSTRINGW_BUF(IDS_FINISHED, temp, 1024);
+ else
+ StringCchPrintfW(temp, 1024, WASABI_API_LNGSTRINGW(IDS_REFRESH_MESSAGE), numFiles + 1, totalFiles);
+ SetDlgItemTextW(hwndDlg, dlgId, temp);
+}
+
+static int numFiles, totalFiles;
+static INT_PTR WINAPI RefreshDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ numFiles = 0;
+ totalFiles = 0;
+ refreshKill = false;
+ WASABI_API_THREADPOOL->RunFunction(0, RefreshMetadataThread, (void *)hwndDlg, 0, api_threadpool::FLAG_LONG_EXECUTION);
+
+ // show refresh info window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"refresh_x", -1), g_config->ReadInt(L"refresh_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_DESTROY:
+ {
+ RECT refresh_rect = {0};
+ GetWindowRect(hwndDlg, &refresh_rect);
+ g_config->WriteInt(L"refresh_x", refresh_rect.left);
+ g_config->WriteInt(L"refresh_y", refresh_rect.top);
+ }
+ break;
+ case REFRESHCB_NUMITEMS:
+ totalFiles = lParam;
+ WriteStatus(hwndDlg, IDC_REFRESHMETADATA_STATUS, numFiles, totalFiles);
+ break;
+ case REFRESHCB_ITERATE:
+ numFiles++;
+ WriteStatus(hwndDlg, IDC_REFRESHMETADATA_STATUS, numFiles, totalFiles);
+ break;
+ case REFRESHCB_FINISH:
+ EndDialog(hwndDlg, 0);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCANCEL:
+ refreshKill = true;
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+void RefreshMetadata(HWND parent)
+{
+ bgQuery_Stop();
+
+ WASABI_API_DIALOGBOXW(IDD_REFRESH_METADATA, parent, RefreshDlgProc);
+ ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+}
+
+// when the rowcache is enabled, the filename pointers should be identical
+void UpdateRating_RowCache(const wchar_t *filename, int new_rating)
+{
+ int itemcount = itemCache.Size;
+ for (int i = 0; i < itemcount; i++)
+ {
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+ if (song->filename == filename || !nde_wcsicmp_fn(song->filename,filename))
+ {
+ song->rating = new_rating;
+ ListView_RedrawItems(resultlist.getwnd(), i, i);
+ break;
+ }
+ }
+}
+
+void UpdateLocalResultsCache(const wchar_t *filename) // perhaps just a itemRecordW parm
+{
+ // Search thru the itemCache looking for the file that was changed
+ int itemcount = itemCache.Size;
+ for (int i = 0; i < itemcount; i++)
+ {
+ // TODO: linear search, yuck, look at this later
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+ if (nde_wcsicmp_fn(song->filename,filename) == 0)
+ {
+ UpdateItemRecordFromDB(song);
+ SetTimer(GetParent(resultlist.getwnd()), UPDATE_RESULT_LIST_TIMER_ID, 500, 0);
+ break;
+ }
+ }
+}
+
+void fileInfoDialogs(HWND hwndParent)
+{
+ no_lv_update++; // this might block other attempts from going thru but that's OK
+ bgQuery_Stop();
+ int l = resultlist.GetCount(), i;
+ int needref = 0;
+ for (i = 0;i < l;i++)
+ {
+ if (!resultlist.GetSelected(i)) continue;
+ itemRecordW *song = itemCache.Items + i;
+ if (!song->filename || !song->filename[0]) continue;
+
+ infoBoxParamW p;
+ p.filename = song->filename;
+ p.parent = hwndParent;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&p, IPC_INFOBOXW)) break;
+
+ needref = 1;
+ UpdateItemRecordFromDB(song);
+ }
+ EatKeyboard();
+ if (needref) ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+ no_lv_update--;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/editquery.cpp b/Src/Plugins/Library/ml_local/editquery.cpp
new file mode 100644
index 00000000..e1d4ca23
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/editquery.cpp
@@ -0,0 +1,1013 @@
+#include "main.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include "editquery.h"
+#include "../nde/NDE.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "time.h"
+#include "../Agave/Language/api_language.h"
+#include "../replicant/nu/AutoChar.h"
+#define ListBox_GetTextLenW(hwndCtl, index) ((int)(DWORD)SendMessageW((hwndCtl), LB_GETTEXTLEN, (WPARAM)(int)(index), 0L))
+#define ListBox_GetTextW(hwndCtl, index, lpszBuffer) ((int)(DWORD)SendMessageW((hwndCtl), LB_GETTEXT, (WPARAM)(int)(index), (LPARAM)(LPCWSTR)(lpszBuffer)))
+
+int myatoi(wchar_t *p, int len) {
+ wchar_t *w = (wchar_t *)calloc((len+1), sizeof(wchar_t));
+ if (w)
+ {
+ wcsncpy(w, p, len);
+ w[len] = 0;
+ int a = _wtoi(w);
+ free(w);
+ return a;
+ }
+ return 0;
+}
+
+static wchar_t *monthtable[12] = {L"January", L"February", L"March", L"April", L"May", L"June", L"July", L"August", L"September", L"October", L"November", L"December" };
+INT_PTR CALLBACK editQueryDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK editDateTimeDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static wchar_t *qe_field = NULL;
+
+static GayStringW qe_curquery;
+static GayStringW qe_curexpr;
+static GayStringW qe_origquery;
+static GayStringW qe_curorigin;
+static GayStringW qe_curdate;
+
+void qe_freeStuff() {
+ qe_curquery.Set(L"");
+ qe_curquery.Compact();
+ qe_curexpr.Set(L"");
+ qe_curexpr.Compact();
+ qe_curorigin.Set(L"");
+ qe_curorigin.Compact();
+ qe_origquery.Set(L"");
+ qe_origquery.Compact();
+}
+
+static GayStringW te_ret;
+static GayStringW te_result;
+int te_cur_selected_month=0;
+
+const wchar_t *editTime(HWND wnd, const wchar_t *curfield) {
+ te_ret.Set(curfield);
+
+ TimeParse tp;
+ NDE_Time_ApplyConversion(0, curfield, &tp);
+
+ if (tp.relative_day == -2 || tp.relative_kwday != -1) {
+ wchar_t titleStr[64] = {0};
+ int r = MessageBoxW(wnd, WASABI_API_LNGSTRINGW(IDS_DATE_TIME_IS_TOO_COMPLEX),
+ WASABI_API_LNGSTRINGW_BUF(IDS_DATE_TIME_EDITOR_QUESTION,titleStr,64), MB_YESNO);
+ if (r == IDNO) {
+ return curfield;
+ }
+ }
+
+ INT_PTR r = WASABI_API_DIALOGBOXW(IDD_TIMEEDITOR, wnd, editDateTimeDialogProc);
+ if (r == IDOK) {
+ te_ret.Set(te_result.Get());
+ return te_ret.Get();
+ }
+
+ return NULL;
+}
+
+const wchar_t *editQuery(HWND wnd, const wchar_t *curquery, wchar_t *newQuery, size_t len)
+{
+ qe_field = NULL;
+
+ qe_curquery.Set(curquery);
+ qe_origquery.Set(curquery);
+
+ INT_PTR r = WASABI_API_DIALOGBOXW(IDD_EDIT_QUERY, wnd, editQueryDialogProc);
+
+ if (qe_field) free(qe_field);
+
+ if (r == IDOK)
+ {
+ lstrcpynW(newQuery, qe_curquery.Get(), len);
+ qe_freeStuff();
+ return newQuery;
+ }
+ qe_freeStuff();
+ return NULL;
+}
+
+static bool FillListProc(Record *record, Field *entry, void *context)
+{
+ HWND hwndDlg = (HWND)context;
+ SendMessage(hwndDlg, LB_ADDSTRING, 0, (LPARAM)NDE_ColumnField_GetFieldName((nde_field_t)entry));
+ return true;
+}
+
+void qe_fillFieldsList( HWND hwndDlg )
+{
+ ListBox_ResetContent( hwndDlg );
+
+ Record *cr = ( (Table *)g_table )->GetColumns(); // TODO: don't use C++ NDE API
+ if ( cr )
+ {
+ cr->WalkFields( FillListProc, (void *)hwndDlg );
+ }
+
+ int len = ListBox_GetTextLenW( hwndDlg, 0 );
+
+ if ( qe_field != NULL )
+ free( qe_field );
+
+ qe_field = (wchar_t *)calloc( ( len + 1 ), sizeof( wchar_t ) );
+ ListBox_GetTextW( hwndDlg, 0, qe_field );
+
+ qe_field[ len ] = 0;
+ ListBox_SetCurSel( hwndDlg, 0 );
+}
+
+void qe_doEnableDisable( HWND hwndDlg, int *n, int num, int how )
+{
+ for ( int i = 0; i < num; i++ )
+ EnableWindow( GetDlgItem( hwndDlg, n[ i ] ), how );
+}
+
+void qe_enableDisableItem( HWND hwndDlg, int id, int tf )
+{
+ EnableWindow( GetDlgItem( hwndDlg, id ), tf );
+}
+
+void qe_fallback( HWND hwndDlg, int disabling, int enabled )
+{
+ if ( IsDlgButtonChecked( hwndDlg, disabling ) )
+ {
+ CheckDlgButton( hwndDlg, disabling, BST_UNCHECKED );
+ CheckDlgButton( hwndDlg, enabled, BST_CHECKED );
+ }
+}
+
+int ctrls_datetime[] = {IDC_STATIC_DATETIME, IDC_EDIT_DATETIME, IDC_BUTTON_EDITDATETIME, IDC_STATIC_CURDATETIME};
+int ctrls_datetime_base[] = {IDC_STATIC_DATETIME, IDC_CHECK_ABSOLUTE, IDC_CHECK_RELATIVE};
+int ctrls_datetime_relative[] = {IDC_CHECK_SELECTIVE, IDC_CHECK_TIMEAGO, IDC_STATIC_QUERYTIME, IDC_STATIC_RESULT};
+int ctrls_datetime_relative_ago[] = {IDC_STATIC_TIMEAGO, IDC_EDIT_TIMEAGO, IDC_RADIO_TIMEAGO_Y,
+ IDC_RADIO_TIMEAGO_H, IDC_RADIO_TIMEAGO_M, IDC_RADIO_TIMEAGO_MIN, IDC_RADIO_TIMEAGO_W, IDC_RADIO_TIMEAGO_S,
+ IDC_RADIO_TIMEAGO_D};
+int ctrls_datetime_relative_ago_direction[] = {IDC_STATIC_DIRECTION, IDC_RADIO_AFTER, IDC_RADIO_BEFORE};
+int ctrls_datetime_relative_selective[] = {IDC_STATIC_SELECTIVE, IDC_STATIC_YEAR, IDC_RADIO_THISYEAR,
+ IDC_RADIO_YEAR, IDC_EDIT_YEAR, IDC_STATIC_MONTH, IDC_RADIO_THISMONTH, IDC_RADIO_MONTH, IDC_BUTTON_MONTH,
+ IDC_STATIC_DAY, IDC_RADIO_THISDAY, IDC_RADIO_DAY, IDC_EDIT_DAY, IDC_STATIC_TIME, IDC_RADIO_THISTIME,
+ IDC_RADIO_TIME, IDC_RADIO_NOON, IDC_RADIO_MIDNIGHT, IDC_BUTTON_NOW, IDC_BUTTON_PICK, IDC_DATETIMEPICKER2};
+int ctrls_datetime_absolute[] = {IDC_STATIC_ABSOLUTE, IDC_DATETIMEPICKER, IDC_DATETIMEPICKER1, IDC_STATIC_RELATIVE};
+int ctrls_string_ops[] = {IDC_RADIO_ISLIKE, IDC_RADIO_BEGINS, IDC_RADIO_ENDS, IDC_RADIO_CONTAINS};
+
+int ctrls_string[] = {IDC_STATIC_STRING, IDC_EDIT_STRING};
+
+void qe_showHideOperators(HWND hwndDlg) {
+ if (!qe_curquery.Get() || !*qe_curquery.Get()) {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_AND), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_OR), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, ID_BUTTON_SENDTOQUERY), SW_SHOW);
+ } else {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_AND), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_OR), SW_SHOW);
+ ShowWindow(GetDlgItem(hwndDlg, ID_BUTTON_SENDTOQUERY), SW_HIDE);
+ }
+}
+
+void qe_updateResultingDate(HWND hwndDlg) {
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_EDIT_DATETIME)) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY)) {
+ time_t t = NDE_Time_ApplyConversion(0, qe_curdate.Get(), 0);
+ struct tm *ts = localtime(&t);
+ wchar_t qe_tempstr[4096] = {0};
+ if (ts)
+ wsprintfW(qe_tempstr, L"%02d/%d/%04d %02d:%02d:%02d", ts->tm_mon+1, ts->tm_mday, ts->tm_year+1900, ts->tm_hour, ts->tm_min, ts->tm_sec);
+ else
+ wcsncpy(qe_tempstr, L"DATE_OUTOFRANGE", 4096);
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_CURDATETIME, qe_tempstr);
+ } else {
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_CURDATETIME, L"N/A");
+ }
+}
+
+void te_updateResultingDate(HWND hwndDlg) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE)) {
+ time_t t = NDE_Time_ApplyConversion(0, qe_curorigin.Get(), 0);
+ struct tm *ts = localtime(&t);
+ wchar_t qe_tempstr[4096] = {0};
+ if (ts)
+ wsprintfW(qe_tempstr, L"%02d/%d/%04d %02d:%02d:%02d", ts->tm_mon+1, ts->tm_mday, ts->tm_year+1900, ts->tm_hour, ts->tm_min, ts->tm_sec);
+ else
+ wcsncpy(qe_tempstr, L"DATE_OUTOFRANGE", 4096);
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_QUERYTIME, qe_tempstr);
+ } else {
+ SetDlgItemTextW(hwndDlg, IDC_STATIC_QUERYTIME, L"N/A");
+ }
+}
+
+void qe_enableDisable(HWND hwndDlg) {
+ if (!*qe_field) return;
+ nde_field_t field = NDE_Table_GetColumnByName(g_table, AutoChar(qe_field));
+ if (!field) return;
+ int type = NDE_ColumnField_GetDataType(field);
+
+ qe_showHideOperators(hwndDlg);
+
+ switch (type) {
+ case FIELD_DATETIME: {
+ qe_fallback(hwndDlg, IDC_RADIO_ISLIKE, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_BEGINS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_ENDS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_CONTAINS, IDC_RADIO_EQUAL);
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 0);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_STRING));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 1);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_AFTER));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BEFORE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_SINCE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_UNTIL));
+ break;
+ }
+ case FIELD_LENGTH: {
+ qe_fallback(hwndDlg, IDC_RADIO_ISLIKE, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_BEGINS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_ENDS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_CONTAINS, IDC_RADIO_EQUAL);
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 1);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_LENGTH));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_ABOVE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BELOW));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_ABOVE_OR_EQUAL));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_BELOW_OR_EQUAL));
+ break;
+ }
+ case FIELD_FILENAME:
+ case FIELD_STRING: {
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 1);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 1);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_STRING));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_ABOVE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BELOW));
+ SetDlgItemTextW(hwndDlg,IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_ABOVE_OR_EQUAL));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_BELOW_OR_EQUAL));
+ break;
+ }
+ case FIELD_INTEGER: {
+ qe_fallback(hwndDlg, IDC_RADIO_ISLIKE, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_BEGINS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_ENDS, IDC_RADIO_EQUAL);
+ qe_fallback(hwndDlg, IDC_RADIO_CONTAINS, IDC_RADIO_EQUAL);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_STRING,WASABI_API_LNGSTRINGW(IDS_COMPARE_TO_NUMBER));
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 1);
+ qe_doEnableDisable(hwndDlg, ctrls_string_ops, sizeof(ctrls_string_ops)/sizeof(int), 0);
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVE, WASABI_API_LNGSTRINGW(IDS_ABOVE));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOW, WASABI_API_LNGSTRINGW(IDS_BELOW));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_ABOVEOREQUAL, WASABI_API_LNGSTRINGW(IDS_ABOVE_OR_EQUAL));
+ SetDlgItemTextW(hwndDlg, IDC_RADIO_BELOWOREQUAL, WASABI_API_LNGSTRINGW(IDS_BELOW_OR_EQUAL));
+ break;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY)) {
+ qe_doEnableDisable(hwndDlg, ctrls_string, sizeof(ctrls_string)/sizeof(int), 0);
+ qe_doEnableDisable(hwndDlg, ctrls_datetime, sizeof(ctrls_datetime)/sizeof(int), 0);
+ return;
+ }
+}
+
+void te_enableDisable(HWND hwndDlg) {
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative, sizeof(ctrls_datetime_relative)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_absolute, sizeof(ctrls_datetime_absolute)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_ABSOLUTE));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative_ago, sizeof(ctrls_datetime_relative_ago)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative_ago_direction, sizeof(ctrls_datetime_relative_ago_direction)/sizeof(int), !IsDlgButtonChecked(hwndDlg, IDC_CHECK_ABSOLUTE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO));
+ qe_doEnableDisable(hwndDlg, ctrls_datetime_relative_selective, sizeof(ctrls_datetime_relative_selective)/sizeof(int), IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE));
+ int origin = IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE) && IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE);
+ qe_enableDisableItem(hwndDlg, IDC_EDIT_YEAR, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_YEAR));
+ qe_enableDisableItem(hwndDlg, IDC_BUTTON_MONTH, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_MONTH));
+ qe_enableDisableItem(hwndDlg, IDC_EDIT_DAY, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_DAY));
+ qe_enableDisableItem(hwndDlg, IDC_DATETIMEPICKER2, origin && IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIME));
+
+ SetDlgItemTextW(hwndDlg, IDC_CHECK_TIMEAGO, WASABI_API_LNGSTRINGW((IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE) ? IDS_OFFSET_BY : IDS_TIME_AGO)));
+ SetWindowTextW(GetDlgItem(hwndDlg, IDC_BUTTON_MONTH), monthtable[te_cur_selected_month]);
+
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_ABSOLUTE), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_RELATIVE), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_TIMEAGO), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_CHECK_SELECTIVE), NULL, TRUE);
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_STATIC_DIRECTION), NULL, TRUE);
+}
+
+const wchar_t *te_getDateTime(HWND hwndDlg) {
+ static GayStringW str;
+ wchar_t qe_tempstr[4096] = {0};
+ str.Set(L"");
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_ABSOLUTE)) {
+ GayStringW sd;
+ SYSTEMTIME st={0,0,0,0,0,0,0,0};
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), &st);
+ if (st.wYear != 0) {
+ wsprintfW(qe_tempstr, L"%02d/%d/%02d", st.wMonth, st.wDay, st.wYear);
+ sd.Append(qe_tempstr);
+ }
+ SYSTEMTIME stt={0,0,0,0,0,0,0,0};
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), &stt);
+ if (stt.wYear != 0) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ wsprintfW(qe_tempstr, L"%02d:%02d:%02d", stt.wHour, stt.wMinute, stt.wSecond);
+ sd.Append(qe_tempstr);
+ }
+ if (sd.Get() && *sd.Get()) {
+ str.Set(sd.Get());
+ return str.Get();
+ }
+ } else if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_RELATIVE)) {
+ GayStringW sd;
+ int gotshit = 0;
+ int gotmonth = 0;
+ int gotthismonth = 0;
+ int gotday = 0;
+ int lgotthistime = 0;
+ int lgotthisday = 0;
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO)) {
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_TIMEAGO, NULL, TRUE);
+ wsprintfW(qe_tempstr, L"%d", v);
+ sd.Append(qe_tempstr);
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_Y)) { sd.Append(L" year"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_M)) { sd.Append(L" month"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_W)) { sd.Append(L" week"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_D)) { sd.Append(L" day"); if (v > 1) sd.Append(L"s"); }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_H)) sd.Append(L" h");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_MIN)) sd.Append(L" mn");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIMEAGO_S)) sd.Append(L" s");
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE)) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO) && IsDlgButtonChecked(hwndDlg, IDC_RADIO_AFTER)) sd.Append(L" after");
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO) && IsDlgButtonChecked(hwndDlg, IDC_RADIO_BEFORE)) sd.Append(L" before");
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_DAY, NULL, TRUE);
+ wsprintfW(qe_tempstr, L"%s%d", !gotmonth ? L"the " : L"", v);
+ int u = (v - ((int)((double)v/10.0)) * 10);
+ if (v < 1 || v > 31) {
+ sd.Append(L"DAY_OUTOFRANGE");
+ gotshit = 1;
+ } else {
+ sd.Append(qe_tempstr);
+ switch (u) {
+ case 1: sd.Append(L"st"); gotshit = 1; break;
+ case 2: sd.Append(L"nd"); gotshit = 1; break;
+ case 3: sd.Append(L"rd"); gotshit = 1; break;
+ default: sd.Append(L"th"); gotshit = 1; break;
+ }
+ }
+ if (!gotthismonth && IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ sd.Append(L" of this month");
+ gotthismonth = 1;
+ }
+ gotshit = 1;
+ gotday = 1;
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"now");
+ gotshit = 1;
+ } else {
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH) &&
+ IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this date");
+ gotshit = 1;
+ } else {
+ if (!gotthismonth && IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this month");
+ gotthismonth = 1;
+ gotshit = 1;
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this day");
+ lgotthisday = 1;
+ gotshit = 1;
+ }
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME)) {
+ if (gotshit) sd.Append(L",");
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"this time");
+ lgotthistime = 1;
+ lgotthisday = 0;
+ gotshit = 1;
+ }
+ }
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (lgotthistime) {
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY))
+ sd.Append(L"on ");
+ else {
+ sd.Append(L"in ");
+ }
+ } else if (lgotthisday) sd.Append(L"of ");
+ switch (te_cur_selected_month) {
+ case 0: sd.Append(L"Jan"); gotshit = 1; gotmonth = 1; break;
+ case 1: sd.Append(L"Feb"); gotshit = 1; gotmonth = 1; break;
+ case 2: sd.Append(L"Mar"); gotshit = 1; gotmonth = 1; break;
+ case 3: sd.Append(L"Apr"); gotshit = 1; gotmonth = 1; break;
+ case 4: sd.Append(L"May"); gotshit = 1; gotmonth = 1; break;
+ case 5: sd.Append(L"Jun"); gotshit = 1; gotmonth = 1; break;
+ case 6: sd.Append(L"Jul"); gotshit = 1; gotmonth = 1; break;
+ case 7: sd.Append(L"Aug"); gotshit = 1; gotmonth = 1; break;
+ case 8: sd.Append(L"Sep"); gotshit = 1; gotmonth = 1; break;
+ case 9: sd.Append(L"Oct"); gotshit = 1; gotmonth = 1; break;
+ case 10: sd.Append(L"Nov"); gotshit = 1; gotmonth = 1; break;
+ case 11: sd.Append(L"Dec"); gotshit = 1; gotmonth = 1; break;
+ default: sd.Append(L"MONTH_OUTOFRANGE"); gotshit = 1; gotmonth = 1; break;
+ }
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISDAY) &&
+ !IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_DAY, NULL, TRUE);
+ wsprintfW(qe_tempstr, L"%s%d", !gotmonth ? L"the " : L"", v);
+ int u = (v - ((int)((double)v/10.0)) * 10);
+ if (v < 1 || v > 31) {
+ sd.Append(L"DAY_OUTOFRANGE");
+ gotshit = 1;
+ } else {
+ sd.Append(qe_tempstr);
+ switch (u) {
+ case 1: sd.Append(L"st"); gotshit = 1; break;
+ case 2: sd.Append(L"nd"); gotshit = 1; break;
+ case 3: sd.Append(L"rd"); gotshit = 1; break;
+ default: sd.Append(L"th"); gotshit = 1; break;
+ }
+ }
+ if (!gotthismonth && IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISMONTH)) {
+ sd.Append(L" of this month");
+ }
+ gotthismonth = 1;
+ gotshit = 1;
+ gotday = 1;
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISYEAR)) {
+
+ if ((gotshit || gotday) && !gotmonth) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ sd.Append(L"in ");
+ } else if (gotmonth && gotday) sd.Append(L", ");
+ else if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ int v = GetDlgItemInt(hwndDlg, IDC_EDIT_YEAR, NULL, TRUE);
+ if (v <= 69) v += 2000;
+ else if (v > 69 && v < 100) v += 1900;
+ if (v <= 1969 || v >= 2038)
+ wcsncpy(qe_tempstr, L"YEAR_OUTOFRANGE", 4096);
+ else
+ wsprintfW(qe_tempstr, L"%d", v);
+ sd.Append(qe_tempstr);
+ gotshit = 1;
+ }
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME)) {
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_NOON)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (gotshit) sd.Append(L"at ");
+ sd.Append(L"noon");
+ } else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_MIDNIGHT)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (gotshit) sd.Append(L"at ");
+ sd.Append(L"midnight");
+ } else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_TIME)) {
+ if (sd.Get() && *sd.Get()) sd.Append(L" ");
+ if (gotshit) sd.Append(L"at ");
+ SYSTEMTIME stt={0,0,0,0,0,0,0,0};
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER2), &stt);
+ wsprintfW(qe_tempstr, L"%02d:%02d:%02d", stt.wHour, stt.wMinute, stt.wSecond);
+ sd.Append(qe_tempstr);
+ gotshit = 1;
+ }
+ }
+ } else {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_TIMEAGO))
+ sd.Append(L" ago");
+ }
+ qe_curorigin.Set(sd.Get());
+ if (sd.Get() && *sd.Get()) {
+ str.Set(sd.Get());
+ return str.Get();
+ }
+ }
+ return NULL;
+}
+
+void qe_updateQuery(HWND hwndDlg) {
+ if (!*qe_field) return;
+ wchar_t qe_tempstr[4096] = {0};
+ nde_field_t field = NDE_Table_GetColumnByName(g_table, AutoChar(qe_field));
+ if (!field) return;
+ int type =NDE_ColumnField_GetDataType(field);
+
+ *qe_tempstr = NULL;
+ GayStringW str;
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_NOT))
+ str.Append(L"!(");
+ str.Append(qe_field);
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_EQUAL))
+ str.Append(L" ==");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ABOVE))
+ str.Append(L" >");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_BELOW))
+ str.Append(L" <");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ABOVEOREQUAL))
+ str.Append(L" >=");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_BELOWOREQUAL))
+ str.Append(L" <=");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISLIKE))
+ str.Append(L" like");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY))
+ str.Append(L" isempty");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_BEGINS))
+ str.Append(L" begins");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_ENDS))
+ str.Append(L" ends");
+ if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_CONTAINS))
+ str.Append(L" has");
+
+ if (!IsDlgButtonChecked(hwndDlg, IDC_RADIO_ISEMPTY)) {
+ switch(type) {
+ case FIELD_DATETIME: {
+ str.Append(L" [");
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_DATETIME, qe_tempstr, 4096);
+ qe_curdate.Set(qe_tempstr);
+ str.Append(qe_curdate.Get());
+ str.Append(L"]");
+ }
+ break;
+ case FIELD_INTEGER: {
+ wsprintfW(qe_tempstr, L" %d", GetDlgItemInt(hwndDlg, IDC_EDIT_STRING, NULL, 1));
+ str.Append(qe_tempstr);
+ }
+ break;
+ case FIELD_FILENAME:
+ case FIELD_STRING: {
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_STRING, qe_tempstr, 4096);
+ str.Append(L" \"");
+ GayStringW escaped;
+ queryStrEscape(qe_tempstr, escaped);
+ str.Append(escaped.Get());
+ str.Append(L"\"");
+ }
+ break;
+ case FIELD_LENGTH: {
+ GetDlgItemTextW(hwndDlg, IDC_EDIT_STRING, qe_tempstr, 4096);
+ wchar_t *z = wcschr(qe_tempstr, L':');
+ if (z) {
+ wchar_t *zz = wcschr(z+1, L':');
+ int a, b, c=0,v=0;
+ a = myatoi(qe_tempstr, z-qe_tempstr);
+ if (zz) { b = myatoi(z+1, zz-(z+1)); c = _wtoi(zz+1); v = a * 3600 + b * 60 + c; }
+ else { b = _wtoi(z+1); v = a * 60 + b; }
+
+ if (v < 60)
+ wsprintfW(qe_tempstr,L"%d",v);
+ else if (v < 60*60)
+ wsprintfW(qe_tempstr,L"%d:%02d",v/60,v%60);
+ else
+ wsprintfW(qe_tempstr,L"%d:%02d:%02d",v/60/60,(v/60)%60,v%60);
+
+ str.Append(qe_tempstr);
+ } else {
+ wsprintfW(qe_tempstr, L" %d", GetDlgItemInt(hwndDlg, IDC_EDIT_STRING, NULL, 1));
+ str.Append(qe_tempstr);
+ }
+ }
+ break;
+ }
+ }
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_NOT))
+ str.Append(L")");
+
+ qe_curexpr.Set(str.Get());
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_RESULT, str.Get());
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_QUERY, qe_curquery.Get());
+
+ qe_updateResultingDate(hwndDlg);
+}
+
+void qe_update(HWND hwndDlg) {
+ qe_enableDisable(hwndDlg);
+ qe_updateQuery(hwndDlg);
+}
+
+void te_updateResult(HWND hwndDlg) {
+ const wchar_t *s = te_getDateTime(hwndDlg);
+ te_result.Set(s == NULL ? L"":s);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_RESULT, te_result.Get());
+}
+
+void te_update(HWND hwndDlg) {
+ te_enableDisable(hwndDlg);
+ te_updateResult(hwndDlg);
+}
+
+void qe_pushExpression(HWND hwndDlg, const wchar_t *op) {
+ GayStringW newq;
+ if (op && qe_curquery.Get() && *qe_curquery.Get()) {
+ newq.Append(L"(");
+ newq.Append(qe_curquery.Get());
+ newq.Append(L") ");
+ newq.Append(op);
+ newq.Append(L" (");
+ newq.Append(qe_curexpr.Get());
+ newq.Append(L")");
+ qe_curquery.Set(newq.Get());
+ } else {
+ qe_curquery.Set(qe_curexpr.Get());
+ }
+ qe_update(hwndDlg);
+}
+
+SYSTEMTIME pickedtime;
+SYSTEMTIME pickeddate;
+
+INT_PTR CALLBACK pickDateTimeDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG: {
+ return TRUE;
+ }
+ case WM_COMMAND: {
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ memset(&pickedtime, 0, sizeof(pickedtime));
+ memset(&pickeddate, 0, sizeof(pickeddate));
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), &pickeddate);
+ DateTime_GetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), &pickedtime);
+ EndDialog(hwndDlg, IDOK);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+ }
+ break;
+ }
+ return FALSE;
+}
+
+INT_PTR CALLBACK editDateTimeDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+
+ switch(uMsg) {
+ case WM_INITDIALOG: {
+ const wchar_t *p = te_ret.Get();
+ while (p) {
+ if (*p != L' ') break;
+ p++;
+ }
+ int empty = (p && *p == 0);
+ TimeParse tp;
+ NDE_Time_ApplyConversion(0, te_ret.Get(), &tp);
+
+ if (!empty) {
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, tp.relative_year == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_YEAR, tp.relative_year != -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, tp.relative_month == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MONTH, tp.relative_month != -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, tp.relative_day == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_DAY, tp.relative_day != -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, tp.relative_hour == -1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_NOON, (tp.relative_hour == 12 && tp.relative_min == 0 && tp.relative_sec == 0) ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MIDNIGHT, (tp.relative_hour == 0 && tp.relative_min == 0 && tp.relative_sec == 0) ? BST_CHECKED : BST_UNCHECKED);
+ int timeyet = IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME);
+ timeyet |= IsDlgButtonChecked(hwndDlg, IDC_RADIO_NOON);
+ timeyet |= IsDlgButtonChecked(hwndDlg, IDC_RADIO_MIDNIGHT);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIME, !timeyet ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_CHECK_RELATIVE, tp.is_relative ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_CHECK_ABSOLUTE, !tp.is_relative ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_BEFORE, tp.offset_whence == 1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_AFTER, tp.offset_whence == 0 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_CHECK_TIMEAGO, tp.offset_used == 1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_Y, tp.offset_what == 0 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_M, tp.offset_what == 1 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_W, tp.offset_what == 2 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_D, tp.offset_what == 3 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_H, tp.offset_what == 4 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_MIN, tp.offset_what == 5 ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_S, tp.offset_what == 6 ? BST_CHECKED : BST_UNCHECKED);
+ if (tp.is_relative && (tp.relative_year != -1 || tp.relative_month != -1 || tp.relative_day != -1 || tp.relative_hour != -1 || tp.relative_min != -1 || tp.relative_sec != -1))
+ CheckDlgButton(hwndDlg, IDC_CHECK_SELECTIVE, BST_CHECKED);
+ else
+ CheckDlgButton(hwndDlg, IDC_CHECK_SELECTIVE, BST_UNCHECKED);
+ }
+ wchar_t t[64] = {0};
+ wsprintfW(t, L"%d", tp.offset_value);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_TIMEAGO, t);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_RESULT, te_ret.Get());
+ {
+ wchar_t qe_tempstr[4096] = {0};
+ time_t now;
+ time(&now);
+ struct tm *ts = localtime(&now);
+ wsprintfW(qe_tempstr, L"%d", tp.relative_year != -1 ? tp.relative_year : ts->tm_year+1900);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_YEAR, qe_tempstr);
+ wsprintfW(qe_tempstr, L"%d", tp.relative_day != -1 ? tp.relative_day : ts->tm_mday);
+ SetDlgItemTextW(hwndDlg, IDC_EDIT_DAY, qe_tempstr);
+ te_cur_selected_month = tp.relative_month != -1 ? tp.relative_month : ts->tm_mon;
+ }
+ if (!empty && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_THISTIME) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_NOON) && !IsDlgButtonChecked(hwndDlg, IDC_RADIO_MIDNIGHT)) {
+ SYSTEMTIME st={0};
+ st.wHour = (WORD)tp.relative_hour;
+ st.wMinute = (WORD)tp.relative_min;
+ st.wSecond = (WORD)tp.relative_sec;
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER2), GDT_VALID, &st);
+ }
+ if (!empty && !tp.is_relative) {
+ time_t o = tp.absolute_datetime;
+ struct tm *t = localtime(&o);
+ if (t) {
+ if (!tp.absolute_hasdate)
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), GDT_NONE, NULL);
+ else {
+ SYSTEMTIME st={0};
+ st.wYear = (WORD)t->tm_year;
+ st.wDay = (WORD)t->tm_mday;
+ st.wMonth = (WORD)t->tm_mon;
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER), GDT_VALID, &st);
+ }
+ if (!tp.absolute_hastime)
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), GDT_NONE, NULL);
+ else {
+ SYSTEMTIME st2={0};
+ GetSystemTime(&st2);
+ st2.wHour = (WORD)t->tm_hour;
+ st2.wMinute = (WORD)t->tm_min;
+ st2.wSecond = (WORD)t->tm_sec;
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER1), GDT_VALID, &st2);
+ }
+ }
+ CheckDlgButton(hwndDlg, IDC_CHECK_TIMEAGO, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_W, 1);
+ SetDlgItemText(hwndDlg, IDC_EDIT_TIMEAGO, L"1");
+ CheckDlgButton(hwndDlg, IDC_RADIO_BEFORE, 1);
+ } else if (!empty) {
+ if (!IsDlgButtonChecked(hwndDlg, IDC_CHECK_SELECTIVE)) {
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, 1);
+ }
+ } else if (empty) {
+ CheckDlgButton(hwndDlg, IDC_CHECK_TIMEAGO, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIMEAGO_W, 1);
+ SetDlgItemText(hwndDlg, IDC_EDIT_TIMEAGO, L"1");
+ CheckDlgButton(hwndDlg, IDC_RADIO_BEFORE, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, 1);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, 1);
+ CheckDlgButton(hwndDlg, IDC_CHECK_RELATIVE, 1);
+ }
+ te_enableDisable(hwndDlg);
+ SetTimer(hwndDlg, 0, 1000, NULL);
+ }
+ return 1;
+ case WM_COMMAND: {
+ switch (LOWORD(wParam)) {
+ case IDOK: EndDialog(hwndDlg, IDOK); break;
+ case IDCANCEL: EndDialog(hwndDlg, IDCANCEL); break;
+ case IDC_EDIT_TIMEAGO:
+ case IDC_EDIT_YEAR:
+ case IDC_EDIT_DAY:
+ if (HIWORD(wParam) != EN_CHANGE) break;
+ case IDC_CHECK_ABSOLUTE:
+ case IDC_CHECK_RELATIVE:
+ case IDC_CHECK_TIMEAGO:
+ case IDC_CHECK_SELECTIVE:
+ case IDC_RADIO_THISYEAR:
+ case IDC_RADIO_THISMONTH:
+ case IDC_RADIO_THISDAY:
+ case IDC_RADIO_THISTIME:
+ case IDC_RADIO_YEAR:
+ case IDC_RADIO_MONTH:
+ case IDC_RADIO_DAY:
+ case IDC_RADIO_TIME:
+ case IDC_RADIO_NOON:
+ case IDC_RADIO_MIDNIGHT:
+ case IDC_RADIO_EQUAL:
+ case IDC_RADIO_ABOVE:
+ case IDC_RADIO_ABOVEOREQUAL:
+ case IDC_RADIO_BELOW:
+ case IDC_RADIO_BELOWOREQUAL:
+ case IDC_RADIO_ISLIKE:
+ case IDC_RADIO_ISEMPTY:
+ case IDC_RADIO_BEGINS:
+ case IDC_RADIO_ENDS:
+ case IDC_RADIO_CONTAINS:
+ case IDC_CHECK_NOT:
+ case IDC_RADIO_TIMEAGO_Y:
+ case IDC_RADIO_TIMEAGO_M:
+ case IDC_RADIO_TIMEAGO_D:
+ case IDC_RADIO_TIMEAGO_H:
+ case IDC_RADIO_TIMEAGO_MIN:
+ case IDC_RADIO_TIMEAGO_S:
+ case IDC_RADIO_TIMEAGO_W:
+ case IDC_RADIO_AFTER:
+ case IDC_RADIO_BEFORE:
+ te_update(hwndDlg);
+ return 0;
+ case IDC_BUTTON_PICK: {
+ INT_PTR r = WASABI_API_DIALOGBOXW(IDD_EDIT_QUERY_PICK, hwndDlg, pickDateTimeDialogProc);
+ if (r == IDOK) {
+ if (pickeddate.wYear != 0) {
+ SetDlgItemInt(hwndDlg, IDC_EDIT_YEAR, pickeddate.wYear, 0);
+ te_cur_selected_month = pickeddate.wMonth-1;
+ SetDlgItemInt(hwndDlg, IDC_EDIT_DAY, pickeddate.wDay, 0);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_YEAR, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MONTH, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_DAY, BST_CHECKED);
+ }
+ if (pickedtime.wYear != 0) {
+ DateTime_SetSystemtime(GetDlgItem(hwndDlg, IDC_DATETIMEPICKER2), GDT_VALID, &pickedtime);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIME, BST_CHECKED);
+ }
+ te_update(hwndDlg);
+ }
+ }
+ break;
+ case IDC_BUTTON_NOW:
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISYEAR, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_YEAR, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISMONTH, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MONTH, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISDAY, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_DAY, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_THISTIME, BST_CHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_TIME, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_NOON, BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_RADIO_MIDNIGHT, BST_UNCHECKED);
+ te_update(hwndDlg);
+ return 0;
+ case IDC_BUTTON_MONTH: {
+ HMENU menu = CreatePopupMenu();
+ for (int i=0;i<12;i++)
+ AppendMenuW(menu, MF_STRING|(te_cur_selected_month==i)?MF_CHECKED:MF_UNCHECKED, i+1, monthtable[i]);
+ POINT pt;
+ GetCursorPos(&pt);
+ int r = TrackPopupMenuEx(menu, TPM_LEFTALIGN|TPM_BOTTOMALIGN|TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, pt.x, pt.y, hwndDlg, NULL);
+ if (r)
+ {
+ te_cur_selected_month = r-1;
+ te_update(hwndDlg);
+ }
+ break;
+ }
+ }
+ }
+ case WM_TIMER: {
+ te_updateResultingDate(hwndDlg);
+ return 0;
+ }
+ case WM_NOTIFY: {
+ int idCtrl = (int)wParam;
+ NMHDR *hdr = (NMHDR*)lParam;
+ switch (idCtrl) {
+ case IDC_DATETIMEPICKER:
+ case IDC_DATETIMEPICKER1:
+ case IDC_DATETIMEPICKER2:
+ if (hdr->code == DTN_DATETIMECHANGE)
+ te_update(hwndDlg);
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+INT_PTR CALLBACK editQueryDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ qe_fillFieldsList( GetDlgItem( hwndDlg, IDC_LIST_FIELDS ) );
+ SetDlgItemTextW( hwndDlg, IDC_EDIT_RESULT, qe_curquery.Get() );
+ CheckDlgButton( hwndDlg, IDC_RADIO_EQUAL, MF_CHECKED );
+ qe_update( hwndDlg );
+ SetTimer( hwndDlg, 0, 1000, NULL );
+ return 1;
+ }
+ case WM_TIMER:
+ {
+ qe_updateResultingDate( hwndDlg );
+ return 0;
+ }
+ case WM_COMMAND:
+ {
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ if ( !qe_curquery.Get() || !*qe_curquery.Get() && qe_curexpr.Get() && *qe_curexpr.Get() )
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ int r = MessageBoxW( hwndDlg, WASABI_API_LNGSTRINGW( IDS_QUERY_FIELD_IS_EMPTY ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_EMPTY_QUERY, titleStr, 32 ), MB_YESNOCANCEL );
+ if ( r == IDYES )
+ qe_curquery.Set( qe_curexpr.Get() );
+ else if ( r == IDCANCEL ) break;
+ }
+ if ( qe_origquery.Get() && *qe_origquery.Get() && qe_curquery.Get() || *qe_curquery.Get() && qe_curexpr.Get() && *qe_curexpr.Get() )
+ {
+ if ( !wcscmp( qe_curquery.Get(), qe_origquery.Get() ) )
+ {
+ wchar_t titleStr[ 32 ] = { 0 };
+ int r = MessageBoxW( hwndDlg, WASABI_API_LNGSTRINGW( IDS_NO_CHANGES_MADE_TO_QUERY ),
+ WASABI_API_LNGSTRINGW_BUF( IDS_QUERY_NOT_CHANGED, titleStr, 32 ), MB_YESNOCANCEL );
+ if ( r == IDYES )
+ qe_curquery.Set( qe_curexpr.Get() );
+ else if ( r == IDCANCEL ) break;
+ }
+ }
+ EndDialog( hwndDlg, IDOK );
+ break;
+ case IDCANCEL: EndDialog( hwndDlg, IDCANCEL ); break;
+
+ case IDC_EDIT_STRING:
+ case IDC_EDIT_DATETIME:
+ if ( HIWORD( wParam ) != EN_CHANGE ) break;
+ case IDC_RADIO_EQUAL:
+ case IDC_RADIO_ABOVE:
+ case IDC_RADIO_ABOVEOREQUAL:
+ case IDC_RADIO_BELOW:
+ case IDC_RADIO_BELOWOREQUAL:
+ case IDC_RADIO_ISLIKE:
+ case IDC_RADIO_ISEMPTY:
+ case IDC_RADIO_BEGINS:
+ case IDC_RADIO_ENDS:
+ case IDC_RADIO_CONTAINS:
+ case IDC_CHECK_NOT:
+ qe_update( hwndDlg );
+ return 0;
+ case IDC_LIST_FIELDS:
+ if ( HIWORD( wParam ) == CBN_SELCHANGE )
+ {
+ HWND wnd = GetDlgItem( hwndDlg, IDC_LIST_FIELDS );
+ int idx = ListBox_GetCurSel( wnd );
+ int len = ListBox_GetTextLenW( wnd, idx );
+ if ( qe_field != NULL ) free( qe_field );
+ qe_field = (wchar_t *)calloc( ( len + 1 ), sizeof( wchar_t ) );
+ ListBox_GetTextW( wnd, idx, qe_field );
+
+ qe_field[ len ] = 0;
+ qe_update( hwndDlg );
+ }
+ break;
+ case IDC_BUTTON_AND:
+ qe_pushExpression( hwndDlg, L"AND" );
+ break;
+ case IDC_BUTTON_OR:
+ qe_pushExpression( hwndDlg, L"OR" );
+ break;
+ case ID_BUTTON_SENDTOQUERY:
+ qe_pushExpression( hwndDlg, NULL );
+ break;
+ case IDC_BUTTON_EDITDATETIME:
+ {
+ const wchar_t *res = editTime( hwndDlg, qe_curdate.Get() );
+ if ( res != NULL )
+ {
+ SetDlgItemTextW( hwndDlg, IDC_EDIT_DATETIME, res );
+ qe_curdate.Set( res );
+ }
+ qe_update( hwndDlg );
+ break;
+ }
+ case IDC_EDIT_QUERY:
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ wchar_t qe_tempstr[ 4096 ] = { 0 };
+ GetDlgItemTextW( hwndDlg, IDC_EDIT_QUERY, qe_tempstr, 4096 );
+ qe_curquery.Set( qe_tempstr );
+ qe_showHideOperators( hwndDlg );
+ }
+ break;
+ }
+ }
+ break;
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/editquery.h b/Src/Plugins/Library/ml_local/editquery.h
new file mode 100644
index 00000000..c5e2a3c0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/editquery.h
@@ -0,0 +1,7 @@
+#ifndef _EDITQUERY_H
+#define _EDITQUERY_H
+
+const wchar_t *editQuery(HWND wnd, const wchar_t *curquery, wchar_t *newQuery, size_t len);
+extern const wchar_t *editTime(HWND wnd, const wchar_t *curfield);
+
+#endif
diff --git a/Src/Plugins/Library/ml_local/evntsink.cpp b/Src/Plugins/Library/ml_local/evntsink.cpp
new file mode 100644
index 00000000..e3301e39
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/evntsink.cpp
@@ -0,0 +1,239 @@
+/**************************************************************************
+ THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+ PARTICULAR PURPOSE.
+
+ Copyright 1998 Microsoft Corporation. All Rights Reserved.
+**************************************************************************/
+
+/**************************************************************************
+
+ File: evntsink.cpp
+
+ Description: This file contains the implementation of the event sink.
+
+**************************************************************************/
+
+/**************************************************************************
+ #include statements
+**************************************************************************/
+
+#include "main.h"
+#include <windows.h>
+#include "evntsink.h"
+
+/**************************************************************************
+ function prototypes
+**************************************************************************/
+
+/**************************************************************************
+ global variables and definitions
+**************************************************************************/
+
+/**************************************************************************
+
+ CEventSink::CEventSink()
+
+**************************************************************************/
+
+CEventSink::CEventSink()
+{
+ m_cRefs = 1;
+}
+
+/**************************************************************************
+
+ CEventSink::QueryInterface()
+
+**************************************************************************/
+
+STDMETHODIMP CEventSink::QueryInterface(REFIID riid, PVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IDispatch))
+ *ppvObject = (IDispatch *)this;
+ else if (IsEqualIID(riid, IID_IUnknown))
+ *ppvObject = this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+/**************************************************************************
+
+ CEventSink::AddRef()
+
+**************************************************************************/
+
+ULONG CEventSink::AddRef(void)
+{
+ return ++m_cRefs;
+}
+
+/**************************************************************************
+
+ CEventSink::Release()
+
+**************************************************************************/
+
+ULONG CEventSink::Release(void)
+{
+ if (--m_cRefs)
+ return m_cRefs;
+
+ delete this;
+ return 0;
+}
+
+/**************************************************************************
+
+ CEventSink::GetIDsOfNames()
+
+**************************************************************************/
+
+HRESULT CEventSink::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
+{
+ *rgdispid = DISPID_UNKNOWN;
+ return DISP_E_UNKNOWNNAME;
+}
+
+/**************************************************************************
+
+ CEventSink::GetTypeInfo()
+
+**************************************************************************/
+
+HRESULT CEventSink::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CEventSink::GetTypeInfoCount()
+
+**************************************************************************/
+
+HRESULT CEventSink::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+/**************************************************************************
+
+ CEventSink::Invoke()
+
+**************************************************************************/
+
+void main_setStatusText(LPCWSTR txt)
+{
+ char dest[512];
+ dest[0]=0;
+ WideCharToMultiByte(CP_ACP,0,txt,-1,dest,sizeof(dest),NULL,NULL);
+ //SetDlgItemText(m_hwnd,IDC_STATUS,dest);
+}
+
+void main_beforeNavigate(LPCWSTR txt)
+{
+ VARIANT *blah=(VARIANT *)txt;
+ char dest[512];
+ dest[0]=0;
+ WideCharToMultiByte(CP_ACP,0,blah->bstrVal,-1,dest,sizeof(dest),NULL,NULL);
+ //SetDlgItemText(m_hwnd,IDC_QUICKSEARCH,dest);
+}
+
+HRESULT CEventSink::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ // void StatusTextChange([in] BSTR Text);
+ case 0x66:
+ main_setStatusText(pdispparams->rgvarg[0].bstrVal);
+ //m_pApp->eventStatusTextChange(pdispparams->rgvarg[0].bstrVal);
+ break;
+
+ // void ProgressChange([in] long Progress, [in] long ProgressMax);
+ case 0x6c:
+ break;
+
+ // void CommandStateChange([in] long Command, [in] VARIANT_BOOL Enable);
+ case 0x69:
+ //m_pApp->eventCommandStateChange(pdispparams->rgvarg[1].lVal, pdispparams->rgvarg[0].boolVal);
+ break;
+
+ // void DownloadBegin();
+ case 0x6a:
+ //m_pApp->eventDownloadBegin();
+ break;
+
+ // void DownloadComplete();
+ case 0x68:
+ //m_pApp->eventDownloadComplete();
+ break;
+
+ // void TitleChange([in] BSTR Text);
+ case 0x071:
+ //m_pApp->eventTitleChange(pdispparams->rgvarg[0].bstrVal);
+ break;
+
+ // void PropertyChange([in] BSTR szProperty);
+ case 0x70:
+ //m_pApp->eventPropertyChange(pdispparams->rgvarg[0].bstrVal);
+ break;
+
+ // void BeforeNavigate2([in] IDispatch* pDisp, [in] VARIANT* URL, [in] VARIANT* Flags, [in] VARIANT* TargetFrameName, [in] VARIANT* PostData, [in] VARIANT* Headers, [in, out] VARIANT_BOOL* Cancel);
+ case 0xfa:
+ main_beforeNavigate(pdispparams->rgvarg[5].bstrVal);
+ break;
+
+ // void NewWindow2([in, out] IDispatch** ppDisp, [in, out] VARIANT_BOOL* Cancel);
+ case 0xfb:
+ break;
+
+ // void NavigateComplete2([in] IDispatch* pDisp, [in] VARIANT* URL);
+ case 0xfc:
+ break;
+
+ // void DocumentComplete([in] IDispatch* pDisp, [in] VARIANT* URL);
+ case 0x0103:
+ break;
+
+ // void OnQuit();
+ case 0xfd:
+ break;
+
+ // void OnVisible([in] VARIANT_BOOL Visible);
+ case 0xfe:
+ break;
+
+ // void OnToolBar([in] VARIANT_BOOL ToolBar);
+ case 0xff:
+ break;
+
+ // void OnMenuBar([in] VARIANT_BOOL MenuBar);
+ case 0x0100:
+ break;
+
+ // void OnStatusBar([in] VARIANT_BOOL StatusBar);
+ case 0x0101:
+ break;
+
+ // void OnFullScreen([in] VARIANT_BOOL FullScreen);
+ case 0x0102:
+ break;
+
+ // void OnTheaterMode([in] VARIANT_BOOL TheaterMode);
+ case 0x0104:
+ break;
+ }
+
+ return S_OK;
+}
diff --git a/Src/Plugins/Library/ml_local/evntsink.h b/Src/Plugins/Library/ml_local/evntsink.h
new file mode 100644
index 00000000..14eb2ab9
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/evntsink.h
@@ -0,0 +1,25 @@
+#ifndef _EVNTSINK_H
+#define _EVNTSINK_H
+
+class CEventSink : public IDispatch
+{
+ private:
+ ULONG m_cRefs; // ref count
+
+ public:
+ CEventSink();
+
+ public:
+ // *** IUnknown Methods ***
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ // *** IDispatch Methods ***
+ STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
+ STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
+ STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
+ STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/guess.cpp b/Src/Plugins/Library/ml_local/guess.cpp
new file mode 100644
index 00000000..24686ddc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/guess.cpp
@@ -0,0 +1,122 @@
+#include <string.h>
+#include <memory.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <windows.h>
+
+wchar_t *guessTitles(const wchar_t *filename,
+ int *tracknum,
+ wchar_t **artist,
+ wchar_t **album,
+ wchar_t **title) // should free the result of this function after using artist/album/title
+{
+ *tracknum=0;
+ *artist=0;
+ *album=0;
+ *title=0;
+
+ if (!filename[0]) return 0;
+
+ wchar_t *_artist=NULL;
+ wchar_t *_album=NULL;
+
+ const wchar_t *f=filename;
+ while (f && *f) f++;
+ while (f && (f > filename) && *f != L'/' && *f != L'\\') f--;
+
+ if (f == filename) return 0;
+
+ int dirlen = f-filename;
+
+ wchar_t *fullfn = (wchar_t*)_wcsdup(filename);
+ fullfn[dirlen]=0;
+
+ wchar_t *n=fullfn+dirlen;
+ while (n >= fullfn && *n != L'/' && *n != L'\\') n--;
+ n++;
+
+ // try to guess artist and album from the dirname
+ if (!wcsstr(n,L"-")) // assume dir name is album name, artist name unknown
+ {
+ *album=n;
+ _album=n;
+ }
+ else
+ {
+ *artist=_artist=n;
+ _album=wcsstr(n,L"-")+1;
+ wchar_t *t=_album-2;
+ while (t >= n && *t == L' ') t--;
+ t[1]=0;
+
+ while (_album && *_album == L' ') _album++;
+ *album=_album;
+ }
+
+ // get filename shizit
+ wcsncpy(fullfn+dirlen+1,filename+dirlen+1,wcslen(filename) - (dirlen + 1));
+
+ n=fullfn+dirlen+1;
+ while (n && *n) n++;
+ while (n > fullfn+dirlen+1 && *n != L'.') n--;
+ if (*n == L'.') *n=0;
+ n=fullfn+dirlen+1;
+
+ while (n && *n == L' ') n++;
+
+ // detect XX. filename
+ if (wcsstr(n,L".") && _wtoi(n) && _wtoi(n) < 130)
+ {
+ wchar_t *tmp=n;
+ while (tmp && *tmp >= L'0' && *tmp <= L'9') tmp++;
+ while (tmp && *tmp == L' ') tmp++;
+ if (tmp && *tmp == L'.')
+ {
+ *tracknum=_wtoi(n);
+ n=tmp+1;
+ while (n && *n == L'.') n++;
+ while (n && *n == L' ') n++;
+ }
+ }
+
+ // detect XX- filename
+ if (!*tracknum && wcsstr(n,L"-") && _wtoi(n) && _wtoi(n) < 130)
+ {
+ wchar_t *tmp=n;
+ while (tmp && *tmp >= L'0' && *tmp <= L'9') tmp++;
+ while (tmp && *tmp == L' ') tmp++;
+ if (tmp && *tmp == L'-')
+ {
+ *tracknum=_wtoi(n);
+ n=tmp+1;
+ while (n && *n == L'-') n++;
+ while (n && *n == L' ') n++;
+ }
+ }
+
+ while (wcsstr(n,L"-"))
+ {
+ wchar_t *a=n;
+ n=wcsstr(n,L"-");
+ {
+ wchar_t *t=n-1;
+ while (t >= a && *t == L' ') t--;
+ t[1]=0;
+ }
+ *n=0;
+ n++;
+ while (n && *n == L'-') n++;
+ while (n && *n == L' ') n++;
+
+ // a is the next token.
+ if (!*tracknum && !_wcsnicmp(a,L"Track ",6) && _wtoi(a+6)) *tracknum=_wtoi(a+6);
+ else if (*artist== _artist)
+ {
+ *artist=a;
+ }
+ if (*artist != _artist && *tracknum) break;
+ }
+ *title=n;
+
+ return fullfn;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/handleMessage.cpp b/Src/Plugins/Library/ml_local/handleMessage.cpp
new file mode 100644
index 00000000..f34aac4a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/handleMessage.cpp
@@ -0,0 +1,906 @@
+#include "main.h"
+#include "ml_local.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "../replicant/nu/ns_wc.h"
+#include "../nde/nde.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../nu/AutoWideFn.h"
+#include "editquery.h"
+#include <time.h>
+
+int queryEditOther(HWND hwnd, const char *query, const char *viewname, int mode);
+
+extern int m_item_mode;
+extern wchar_t m_item_name[256];
+extern wchar_t m_item_query[1024];
+
+LRESULT IPC_GetFileRatingW(INT_PTR fileName)
+{
+ int rating = 0;
+ const wchar_t *filename = (const wchar_t *)fileName;
+ if (!filename) return 0;
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+
+ if (found)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_RATING);
+ if (f && NDE_Field_GetType(f) == FIELD_INTEGER)
+ {
+ rating = min(5, max(0, NDE_IntegerField_GetValue(f)));
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+ }
+ return rating;
+}
+
+LRESULT IPC_SetFileRatingW(INT_PTR file_rating)
+{
+ const wchar_t *filename;
+ int rating;
+ int found = 0;
+
+ file_set_ratingW *data = (file_set_ratingW*)file_rating;
+ if (!data) return 0;
+
+ filename = data->fileName;
+ rating = min(5, max(0, data->newRating));
+
+ if (!filename) return 0;
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+ if (!found)
+ {
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(filename, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ addFileToDb(filename, 0, meta, guess, 1, (int)time(NULL));
+ }
+ if (filename2[0] && !found)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename2)) found = 2;
+ }
+ if (!found)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename)) found = 1;
+ }
+ if (found)
+ {
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, rating);
+ NDE_Scanner_Post(s);
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (rating > 0)
+ {
+ wsprintfW(buf, L"%d", rating);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+ }
+ return found != 0;
+}
+
+
+LRESULT IPC_GetFileRating(INT_PTR fileName)
+{
+ return IPC_GetFileRatingW((INT_PTR)(wchar_t *)AutoWide((const char *)fileName));
+}
+
+LRESULT IPC_SetFileRating(INT_PTR file_rating)
+{
+ file_set_rating *data = (file_set_rating*)file_rating;
+ if (!data) return 0;
+ file_set_ratingW dataW;
+ AutoWide wideFn(data->fileName);
+ dataW.fileName = wideFn;
+ dataW.newRating = data->newRating;
+ return IPC_SetFileRatingW((INT_PTR)&dataW);
+}
+
+int m_calling_getfileinfo;
+int getFileInfo(const char *filename, char *metadata, char *dest, int len)
+{
+ m_calling_getfileinfo=1;
+ dest[0]=0;
+ extendedFileInfoStruct efis={
+ filename,
+ metadata,
+ dest,
+ len,
+ };
+ int r = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFO); //will return 1 if wa2 supports this IPC call
+ m_calling_getfileinfo=0;
+ return r;
+}
+
+LRESULT IPC_GetFileInfo(INT_PTR param)
+{
+ if (!param) return 0;
+ itemRecord *ent = (itemRecord *)param;
+ if (!ent->filename || ent->filename && !*ent->filename) return 0;
+
+ char filename2[MAX_PATH] = {0}; // full lfn path if set
+ makeFilename2(ent->filename, filename2, ARRAYSIZE(filename2));
+
+ if (!g_table) openDb();
+ // first, check to see if it's in the db
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, (AutoWideFn)(ent->filename)) ||
+ (filename2[0] && NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, (AutoWideFn)(filename2))))
+ {
+ nde_field_t f;
+ // found it db, yay!
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TITLE);
+ if (!ent->title) ent->title = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ALBUM);
+ if (!ent->album) ent->album = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ARTIST);
+ if (!ent->artist) ent->artist = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_GENRE);
+ if (!ent->genre) ent->genre = f ? AutoCharDup(NDE_StringField_GetString(f)) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TRACKNB);
+ if (ent->track <= 0) ent->track = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_YEAR);
+ if (ent->year <= 0) ent->year = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LENGTH);
+ if (ent->length <= 0) ent->length = f ? NDE_IntegerField_GetValue(f) : -1;
+ //ent->extended_info=NULL;//for now
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ }
+ // if not, run the same shit as if we were adding it to the db!
+
+ char *fnp = filename2[0] ? filename2 : ent->filename; // fnp is the file we will run through the guesswork
+
+ char tmp[1024] = {0, };
+ if (getFileInfo(fnp, "title", tmp, ARRAYSIZE(tmp) - 1))
+ {
+ if (tmp[0] && !ent->title)
+ {
+ ent->title = _strdup(tmp);
+ }
+ if (!ent->artist)
+ {
+ getFileInfo(ent->filename, "artist", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->artist = _strdup(tmp);
+ }
+ }
+ if (!ent->album)
+ {
+ getFileInfo(ent->filename, "album", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->album = _strdup(tmp);
+ }
+ }
+ if (ent->year <= 0)
+ {
+ getFileInfo(ent->filename, "year", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0] && !strstr(tmp, "__") && !strstr(tmp, "/") && !strstr(tmp, "\\") && !strstr(tmp, "."))
+ {
+ char *p = tmp;
+ while (p && *p)
+ {
+ if (*p == '_') *p = '0';
+ p++;
+ }
+ int y = atoi(tmp);
+ if (y != 0) ent->year = y;
+ }
+ }
+ if (!ent->genre)
+ {
+ getFileInfo(ent->filename, "genre", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->genre = _strdup(tmp);
+ }
+ }
+ if (ent->track <= 0)
+ {
+ getFileInfo(ent->filename, "track", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->track = atoi(tmp);
+ }
+ }
+ if (ent->length <= 0)
+ {
+ getFileInfo(ent->filename, "length", tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->length = atoi(tmp) / 1000;
+ }
+ }
+ }
+ if (!ent->title && !ent->artist && !ent->album && !ent->track)
+ {
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0;
+ wchar_t *guessbuf = guessTitles(AutoWide(filename2[0] ? filename2 : ent->filename), &tn, &artist, &album, &title);
+ if (guessbuf)
+ {
+ if (!ent->title && title)
+ {
+ ent->title = AutoCharDup(title);
+ }
+ if (!ent->artist && artist)
+ {
+ ent->artist = AutoCharDup(artist);
+ }
+ if (!ent->album && album)
+ {
+ ent->album = AutoCharDup(album);
+ }
+ if (ent->track <= 0 && tn)
+ {
+ ent->track = tn;
+ }
+ free(guessbuf);
+ }
+ }
+ return 0;
+}
+
+int m_calling_getfileinfoW;
+static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len)
+{
+ m_calling_getfileinfoW=1;
+ dest[0]=0;
+ extendedFileInfoStructW efis={
+ filename,
+ metadata,
+ dest,
+ len,
+ };
+ int r = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFOW); //will return 1 if wa2 supports this IPC call
+ m_calling_getfileinfoW=0;
+ return r;
+}
+
+LRESULT IPC_GetFileInfoW(INT_PTR param)
+{
+ if (!param) return 0;
+ itemRecordW *ent = (itemRecordW *)param;
+ if (!ent->filename || ent->filename && !*ent->filename) return 0;
+
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ makeFilename2W(ent->filename, filename2, ARRAYSIZE(filename2));
+
+ if (!g_table) openDb();
+ // first, check to see if it's in the db
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, ent->filename) ||
+ (filename2[0] && NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename2)))
+ {
+ nde_field_t f;
+ // found it db, yay!
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TITLE);
+ if (!ent->title) ent->title = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ALBUM);
+ if (!ent->album) ent->album = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_ARTIST);
+ if (!ent->artist) ent->artist = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_GENRE);
+ if (!ent->genre) ent->genre = f ? NDE_StringField_GetString(f) : 0;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_TRACKNB);
+ if (ent->track <= 0) ent->track = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_YEAR);
+ if (ent->year <= 0) ent->year = f ? NDE_IntegerField_GetValue(f) : -1;
+ f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_LENGTH);
+ if (ent->length <= 0) ent->length = f ? NDE_IntegerField_GetValue(f) : -1;
+ //ent->extended_info=NULL;//for now
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ }
+ // if not, run the same shit as if we were adding it to the db!
+
+ wchar_t *fnp = filename2[0] ? filename2 : ent->filename; // fnp is the file we will run through the guesswork
+
+ wchar_t tmp[1024] = {0, };
+ if (getFileInfoW(fnp, DB_FIELDNAME_title, tmp, ARRAYSIZE(tmp) - 1))
+ {
+ if (tmp[0] && !ent->title)
+ {
+ ent->title = _wcsdup(tmp);
+ }
+ if (!ent->artist)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_artist, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->artist = _wcsdup(tmp);
+ }
+ }
+ if (!ent->album)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_album, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->album = _wcsdup(tmp);
+ }
+ }
+ if (ent->year <= 0)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_year, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0] && !wcsstr(tmp, L"__") && !wcsstr(tmp, L"/") && !wcsstr(tmp, L"\\") && !wcsstr(tmp, L"."))
+ {
+ wchar_t *p = tmp;
+ while (p && *p)
+ {
+ if (*p == L'_') *p = L'0';
+ p++;
+ }
+ int y = _wtoi(tmp);
+ if (y != 0) ent->year = y;
+ }
+ }
+ if (!ent->genre)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_genre, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->genre = _wcsdup(tmp);
+ }
+ }
+ if (ent->track <= 0)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_track, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->track = _wtoi(tmp);
+ }
+ }
+ if (ent->length <= 0)
+ {
+ getFileInfoW(ent->filename, DB_FIELDNAME_length, tmp, ARRAYSIZE(tmp) - 1);
+ if (tmp[0])
+ {
+ ent->length = _wtoi(tmp) / 1000;
+ }
+ }
+ }
+ if (!ent->title && !ent->artist && !ent->album && !ent->track)
+ {
+ int tn = 0;
+ wchar_t *artist = 0, *album = 0, *title = 0;
+ wchar_t *guessbuf = guessTitles(filename2[0] ? filename2 : ent->filename, &tn, &artist, &album, &title);
+ if (guessbuf)
+ {
+ if (!ent->title && title)
+ {
+ ent->title = _wcsdup(title);
+ }
+ if (!ent->artist && artist)
+ {
+ ent->artist = _wcsdup(artist);
+ }
+ if (!ent->album && album)
+ {
+ ent->album = _wcsdup(album);
+ }
+ if (ent->track <= 0 && tn)
+ {
+ ent->track = tn;
+ }
+ free(guessbuf);
+ }
+ }
+ return 0;
+}
+
+LRESULT IPC_FreeFileInfo(INT_PTR param)
+{
+ itemRecord *ent = (itemRecord *)param;
+ if (!param) return 0;
+ if (ent->album)
+ {
+ free(ent->album); ent->album = NULL;
+ }
+ if (ent->artist)
+ {
+ free(ent->artist); ent->artist = NULL;
+ }
+ if (ent->comment)
+ {
+ free(ent->comment); ent->comment = NULL;
+ }
+ if (ent->genre)
+ {
+ free(ent->genre); ent->genre = NULL;
+ }
+ if (ent->title)
+ {
+ free(ent->title); ent->title = NULL;
+ }
+ //if (ent->extended_info) { free(ent->extended_info); ent->extended_info= NULL; }
+
+ return 0;
+}
+
+LRESULT IPC_FreeFileInfoW(INT_PTR param)
+{
+ itemRecordW *ent = (itemRecordW *)param;
+ if (!param) return 0;
+ if (ent->album)
+ {
+ free(ent->album); ent->album = NULL;
+ }
+ if (ent->artist)
+ {
+ free(ent->artist); ent->artist = NULL;
+ }
+ if (ent->comment)
+ {
+ free(ent->comment); ent->comment = NULL;
+ }
+ if (ent->genre)
+ {
+ free(ent->genre); ent->genre = NULL;
+ }
+ if (ent->title)
+ {
+ free(ent->title); ent->title = NULL;
+ }
+ //if (ent->extended_info) { free(ent->extended_info); ent->extended_info= NULL; }
+
+ return 0;
+}
+
+LRESULT IPC_EditView(INT_PTR param)
+{
+ static char dummyNameChar[256];
+ static char dummyQueryChar[1024];
+ ml_editview *mev = (ml_editview *)param;
+ if (mev == NULL) return 0;
+ if (queryEditOther(mev->dialog_parent, mev->query, mev->name, mev->mode))
+ {
+ mev->mode = m_item_mode;
+ WideCharToMultiByteSZ(CP_ACP, 0, m_item_name, -1, dummyNameChar, 256, 0, 0);
+ mev->name = dummyNameChar;
+ WideCharToMultiByteSZ(CP_ACP, 0, m_item_query, -1, dummyQueryChar, 1024, 0, 0);
+ mev->query = dummyQueryChar;
+ }
+ else
+ {
+ mev->dialog_parent = NULL;
+ mev->mode = -1;
+ mev->name = NULL;
+ mev->query = NULL;
+ return 0;
+ }
+ return 1;
+}
+
+LRESULT IPC_EditQuery(INT_PTR param)
+{
+ static char staticQuery[4096];
+ ml_editquery *meq = (ml_editquery *)param;
+ if (meq == NULL) return 0;
+ wchar_t querybuf[4096] = {0};
+ const wchar_t *newQuery = editQuery(meq->dialog_parent, AutoWide(meq->query), querybuf, 4096);
+ WideCharToMultiByteSZ(CP_ACP, 0, newQuery, -1, staticQuery, 4096, 0, 0);
+ meq->query = staticQuery;
+ return meq->query != NULL;
+}
+
+bool FindFileInDB(nde_scanner_t s, const wchar_t *filename)
+{
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ return !!FindFileInDatabase(s, MAINTABLE_ID_FILENAME, filename, filename2);
+}
+
+INT_PTR HandleIpcMessage(INT_PTR msg, INT_PTR param)
+{
+ switch (msg)
+ {
+ /////////////////////// database API
+ case ML_IPC_DB_RUNQUERYW:
+ case ML_IPC_DB_RUNQUERY_SEARCHW:
+ case ML_IPC_DB_RUNQUERY_FILENAMEW:
+ case ML_IPC_DB_RUNQUERY_INDEXW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ mlQueryStructW *p = (mlQueryStructW *)param;
+ itemRecordListW *obj = &p->results;
+ int failed = 0;
+
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (msg == ML_IPC_DB_RUNQUERY_SEARCHW)
+ {
+ if (p->query)
+ {
+ GayStringW gs;
+ makeQueryStringFromText(&gs, p->query);
+ if (gs.Get() && gs.Get()[0]) NDE_Scanner_Query(s, gs.Get());
+ }
+ NDE_Scanner_First(s);
+ }
+ else if (msg == ML_IPC_DB_RUNQUERY_INDEXW)
+ {
+ failed=1;
+ }
+ else if (msg == ML_IPC_DB_RUNQUERY_FILENAMEW)
+ {
+ failed = 1;
+ if (p->query)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, p->query))
+ {
+ failed = 0;
+ }
+ }
+ }
+ else
+ {
+ if (p->query)
+ NDE_Scanner_Query(s, p->query);
+ NDE_Scanner_First(s);
+ }
+
+ if (!failed)
+ {
+ int cnt = 0;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(obj->Items[obj->Size].filename);
+ ScannerRefToObjCacheNFNW(s, obj, true);
+
+ // this makes 0 give unlimited results, 1 give 1, and so on.
+ if (++cnt == p->max_results || msg == ML_IPC_DB_RUNQUERY_FILENAMEW
+ /*|| msg == ML_IPC_DB_RUNQUERY_INDEX*/) break;
+
+ }
+ while (NDE_Scanner_Next(s));
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ compactRecordList(obj);
+
+ return failed ? -1 : 1;
+ }
+ return -1;
+
+ case ML_IPC_DB_RUNQUERY:
+ case ML_IPC_DB_RUNQUERY_SEARCH:
+ case ML_IPC_DB_RUNQUERY_FILENAME:
+ case ML_IPC_DB_RUNQUERY_INDEX:
+ {
+ mlQueryStruct *p = (mlQueryStruct*)param;
+ mlQueryStructW pW;
+ AutoWide wideQuery(p->query);
+ pW.query = wideQuery;
+ pW.max_results = p->max_results;
+ memset(&pW.results, 0, sizeof(pW.results));
+ INT_PTR res = HandleIpcMessage(msg+0x1000, (INT_PTR)&pW); // convienently the W messages are 0x1000 higher
+ itemRecordList *obj = &p->results;
+ // append new results to old results
+ for (int i=0;i!=pW.results.Size;i++)
+ {
+ allocRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+ convertRecord(&obj->Items[obj->Size++], &pW.results.Items[i]);
+ }
+ freeRecordList(&pW.results);
+ return res;
+ }
+ case ML_IPC_DB_FREEQUERYRESULTS:
+ if (param)
+ {
+ mlQueryStruct *p = (mlQueryStruct*)param;
+ freeRecordList(&p->results);
+ return 1;
+ }
+ return -1;
+ case ML_IPC_DB_FREEQUERYRESULTSW:
+ if (param)
+ {
+ mlQueryStructW *p = (mlQueryStructW*)param;
+ freeRecordList(&p->results);
+ return 1;
+ }
+ return -1;
+ case ML_IPC_DB_REMOVEITEM:
+ case ML_IPC_DB_REMOVEITEMW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ wchar_t *filename = (((msg == ML_IPC_DB_REMOVEITEMW) ? (wchar_t*)param : AutoWideFn((char*)param)));
+ int ret = ( RemoveFileFromDB(filename) == 0 ) ? 1 : -2; // Change the return value from the stadard 0 == success, > 0 == fail
+ return ret;
+ }
+ return -1;
+ case ML_IPC_DB_UPDATEITEMW:
+ case ML_IPC_DB_ADDORUPDATEITEMW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ int ret = 1;
+ itemRecordW *item = (itemRecordW*)param;
+ if (!item->filename) return -1;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (FindFileInDB(s, item->filename))
+ {
+ NDE_Scanner_Edit(s);
+ }
+ else
+ {
+ if (msg == ML_IPC_DB_UPDATEITEMW) ret = -2;
+ else
+ {
+ // new file
+ NDE_Scanner_New(s);
+ db_setFieldStringW(s, MAINTABLE_ID_FILENAME, item->filename);
+ db_setFieldInt(s, MAINTABLE_ID_DATEADDED, (int)time(NULL));
+ }
+ }
+ if (ret == 1)
+ {
+ // update applicable members
+ if (item->title) db_setFieldStringW(s, MAINTABLE_ID_TITLE, item->title);
+ if (item->album) db_setFieldStringW(s, MAINTABLE_ID_ALBUM, item->album);
+ if (item->artist) db_setFieldStringW(s, MAINTABLE_ID_ARTIST, item->artist);
+ if (item->comment) db_setFieldStringW(s, MAINTABLE_ID_COMMENT, item->comment);
+ if (item->genre) db_setFieldStringW(s, MAINTABLE_ID_GENRE, item->genre);
+ if (item->albumartist) db_setFieldStringW(s, MAINTABLE_ID_ALBUMARTIST, item->albumartist);
+ if (item->replaygain_album_gain) db_setFieldStringW(s, MAINTABLE_ID_ALBUMGAIN, item->replaygain_album_gain);
+ if (item->replaygain_track_gain) db_setFieldStringW(s, MAINTABLE_ID_TRACKGAIN, item->replaygain_track_gain);
+ if (item->publisher) db_setFieldStringW(s, MAINTABLE_ID_PUBLISHER, item->publisher);
+ if (item->composer) db_setFieldStringW(s, MAINTABLE_ID_COMPOSER, item->composer);
+ if (item->category) db_setFieldStringW(s, MAINTABLE_ID_CATEGORY, item->category);
+ if (item->year >= 0) db_setFieldInt(s, MAINTABLE_ID_YEAR, item->year);
+ if (item->track >= 0) db_setFieldInt(s, MAINTABLE_ID_TRACKNB, item->track);
+ if (item->tracks >= 0) db_setFieldInt(s, MAINTABLE_ID_TRACKS, item->tracks);
+ if (item->length >= 0) db_setFieldInt(s, MAINTABLE_ID_LENGTH, item->length);
+ if (item->rating >= 0)
+ {
+ db_setFieldInt(s, MAINTABLE_ID_RATING, item->rating);
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (item->rating > 0)
+ {
+ wsprintfW(buf, L"%d", item->rating);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(item->filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+ if (item->lastplay >= 0) db_setFieldInt(s, MAINTABLE_ID_LASTPLAY, (int)item->lastplay);
+ if (item->lastupd >= 0) db_setFieldInt(s, MAINTABLE_ID_LASTUPDTIME, (int)item->lastupd);
+ if (item->filetime >= 0) db_setFieldInt(s, MAINTABLE_ID_FILETIME, (int)item->filetime);
+ if (item->filesize >= 0)
+ {
+ // changed in 5.64 to use the 'realsize' if it's available, otherwise map to filesize scaled to bytes (was stored as kb previously)
+ const wchar_t *realsize = getRecordExtendedItem(item, L"realsize");
+ if (realsize) db_setFieldInt64(s, MAINTABLE_ID_FILESIZE, _wtoi64(realsize));
+ else db_setFieldInt(s, MAINTABLE_ID_FILESIZE, item->filesize * 1024);
+ }
+ if (item->bitrate >= 0) db_setFieldInt(s, MAINTABLE_ID_BITRATE, item->bitrate);
+ if (item->type >= 0) db_setFieldInt(s, MAINTABLE_ID_TYPE, item->type);
+ if (item->disc >= 0) db_setFieldInt(s, MAINTABLE_ID_DISC, item->disc);
+ if (item->discs >= 0) db_setFieldInt(s, MAINTABLE_ID_DISCS, item->discs);
+ if (item->bpm >= 0) db_setFieldInt(s, MAINTABLE_ID_BPM, item->bpm);
+ // give a default playcnt of 0 if we're creating a new one and the caller
+ // didn't specify a playcnt
+ if (item->playcount >= 0)
+ db_setFieldInt(s, MAINTABLE_ID_PLAYCOUNT, item->playcount);
+ else
+ db_setFieldInt(s, MAINTABLE_ID_PLAYCOUNT, 0);
+
+ for (int x = 0; x < NUM_EXTRA_COLSW; x ++)
+ {
+ wchar_t *at = getRecordExtendedItem(item, extra_strsW[x]); // can't use _fast here because it's not our itemRecordW
+ if (at)
+ {
+ switch (extra_idsW[x])
+ {
+ case MAINTABLE_ID_LOSSLESS:
+ case MAINTABLE_ID_PODCASTPUBDATE:
+ case MAINTABLE_ID_ISPODCAST:
+ case MAINTABLE_ID_WIDTH:
+ case MAINTABLE_ID_HEIGHT:
+ if (*at)
+ db_setFieldInt(s, extra_idsW[x], _wtoi(at));
+ break;
+ case MAINTABLE_ID_GRACENOTEFILEID:
+ case MAINTABLE_ID_GRACENOTEEXTDATA:
+ case MAINTABLE_ID_PODCASTCHANNEL:
+ case MAINTABLE_ID_CODEC:
+ case MAINTABLE_ID_DIRECTOR:
+ case MAINTABLE_ID_PRODUCER:
+ db_setFieldStringW(s, extra_idsW[x], at);
+ break;
+ }
+ }
+ }
+
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ return ret;
+ }
+ return -1;
+
+ case ML_IPC_DB_UPDATEITEM:
+ case ML_IPC_DB_ADDORUPDATEITEM:
+ {
+ itemRecord *item = (itemRecord*)param;
+ if (!item || !item->filename)
+ return -1;
+ itemRecordW wideRecord;
+ memset(&wideRecord, 0, sizeof(wideRecord));
+ convertRecord(&wideRecord, item);
+ INT_PTR res = HandleIpcMessage(msg+0x1000, (INT_PTR)&wideRecord); // unicode message values are convienantly 0x1000 higher here
+ freeRecord(&wideRecord);
+ return res;
+ }
+
+ case ML_IPC_DB_UPDATEFILEW:
+ case ML_IPC_DB_ADDORUPDATEFILEW:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ LMDB_FILE_ADD_INFOW *fi = (LMDB_FILE_ADD_INFOW *)param;
+ if (!fi || !fi->fileName) return -1;
+
+ int guess = (fi->gues_mode == -1) ? g_config->ReadInt(L"guessmode", 0) : fi->gues_mode;
+ int meta = (fi->meta_mode == -1) ? g_config->ReadInt(L"usemetadata", 1) : fi->meta_mode;
+ return addFileToDb(fi->fileName, msg == ML_IPC_DB_UPDATEFILEW ? 1 : 0, meta, guess);
+ }
+ return -1;
+ case ML_IPC_DB_UPDATEFILE:
+ case ML_IPC_DB_ADDORUPDATEFILE:
+ if (!g_table) openDb();
+ if (param && g_table)
+ {
+ LMDB_FILE_ADD_INFO *fi = (LMDB_FILE_ADD_INFO*)param;
+ if (!fi || !fi->fileName) return -1;
+
+ int guess = (fi->gues_mode == -1) ? g_config->ReadInt(L"guessmode", 0) : fi->gues_mode;
+ int meta = (fi->meta_mode == -1) ? g_config->ReadInt(L"usemetadata", 1) : fi->meta_mode;
+ return addFileToDb(AutoWide(fi->fileName), msg == ML_IPC_DB_UPDATEFILE ? 1 : 0, meta, guess);
+ }
+ return -1;
+ case ML_IPC_DB_SYNCDB:
+ if (!g_table) openDb();
+ if (g_table && g_table_dirty)
+ {
+ EnterCriticalSection(&g_db_cs);
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+ return -1;
+
+ case ML_IPC_GET_FILE_RATINGW: return IPC_GetFileRatingW(param);
+ case ML_IPC_SET_FILE_RATINGW: return IPC_SetFileRatingW(param);
+ case ML_IPC_GET_FILE_RATING: return IPC_GetFileRating(param);
+ case ML_IPC_SET_FILE_RATING: return IPC_SetFileRating(param);
+ case ML_IPC_FREEFILEINFO: return IPC_FreeFileInfo(param);
+ case ML_IPC_FREEFILEINFOW: return IPC_FreeFileInfoW(param);
+ case ML_IPC_GETFILEINFO: return IPC_GetFileInfo(param);
+ case ML_IPC_GETFILEINFOW: return IPC_GetFileInfoW(param);
+ case ML_IPC_PLAY_PLAYLIST:
+ case ML_IPC_LOAD_PLAYLIST:
+ {
+ // play/load the playlist passed as param
+ queryItem *item = m_query_list[param];
+ if (item)
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf, item->query, 0, msg == ML_IPC_PLAY_PLAYLIST);
+ }
+ }
+ break;
+ case ML_IPC_EDITVIEW: return IPC_EditView(param);
+ case ML_IPC_EDITQUERY: return IPC_EditQuery(param);
+ case ML_IPC_SMARTVIEW_COUNT:
+ return m_query_list.size();
+ case ML_IPC_SMARTVIEW_INFO:
+ if (param)
+ {
+ mlSmartViewInfo * v = (mlSmartViewInfo *)param;
+ if (v->size < sizeof(mlSmartViewInfo)) break;
+ if (v->smartViewNum >= m_query_list.size()) break;
+
+ auto it = m_query_list.begin();
+ while (v->smartViewNum--)
+ {
+ it++;
+ }
+ queryItem* q = it->second; //(m_query_list.begin()+v->smartViewNum)->second;
+
+ if (!q) break;
+ if (!q->name || !q->query) break;
+ v->treeItemId = it->first; //(m_query_list.begin()+v->smartViewNum)->first;
+ v->iconImgIndex = q->imgIndex;
+ v->mode = q->mode;
+ lstrcpynW(v->smartViewName,q->name,sizeof(v->smartViewName)/sizeof(wchar_t));
+ lstrcpynW(v->smartViewQuery,q->query,sizeof(v->smartViewQuery)/sizeof(wchar_t));
+ return 1;
+ }
+ break;
+ case ML_IPC_SMARTVIEW_ADD:
+ if (param)
+ {
+ mlSmartViewInfo * v = (mlSmartViewInfo *)param;
+ if (v->size < sizeof(mlSmartViewInfo)) break;
+ v->treeItemId = addQueryItem(v->smartViewName,v->smartViewQuery,v->mode,0,NULL,v->iconImgIndex, v->smartViewNum);
+ saveQueryTree();
+ return 1;
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/local_menu.cpp b/Src/Plugins/Library/ml_local/local_menu.cpp
new file mode 100644
index 00000000..0b55496a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/local_menu.cpp
@@ -0,0 +1,272 @@
+#include "main.h"
+#include "./local_menu.h"
+#include "./resource.h"
+#include "..\..\General\gen_ml/menu.h"
+#include "../nu/menushortcuts.h"
+
+#define RATING_MARKER MAKELONG(MAKEWORD('R','A'),MAKEWORD('T','E'))
+#define RATING_MINSPACECX 16
+
+#if 0
+static BOOL Menu_IsRatingStar(HMENU hMenu, INT itemId, INT *valueOut)
+{
+ WCHAR szBuffer[8] = {0};
+ INT cchBuffer = GetMenuStringW(hMenu, itemId, szBuffer, ARRAYSIZE(szBuffer), MF_BYCOMMAND);
+ if (cchBuffer < 1 || cchBuffer > 5)
+ return FALSE;
+
+ for (INT i = 1; i < cchBuffer; i++)
+ {
+ if (szBuffer[i -1] != szBuffer[i])
+ return FALSE;
+ }
+
+ if (NULL != valueOut)
+ *valueOut = cchBuffer;
+
+ return TRUE;
+}
+
+static BOOL Menu_MeasureRating(HMENU hMenu, HDC hdc, MEASUREITEMSTRUCT *pmis)
+{
+ if (NULL == hdc || !Menu_IsRatingStar(hMenu, pmis->itemID, NULL))
+ return FALSE;
+
+ RECT rect;
+ if (!MLRating_CalcRect(plugin.hwndLibraryParent, NULL, 5, &rect))
+ return FALSE;
+
+ pmis->itemHeight = rect.bottom - rect.top + 6;
+
+ TEXTMETRIC tm = {0};
+ if (GetTextMetrics(hdc, &tm) &&
+ (UINT)(tm.tmHeight + 2) > pmis->itemHeight)
+ {
+ pmis->itemHeight = tm.tmHeight + 2;
+ }
+
+ INT spaceCX = (pmis->itemHeight > RATING_MINSPACECX) ? pmis->itemHeight : RATING_MINSPACECX;
+ pmis->itemWidth = rect.right - rect.left + (2 * spaceCX) - (GetSystemMetrics(SM_CXMENUCHECK) - 1);
+ return TRUE;
+}
+
+static BOOL Menu_DrawRating(HMENU hMenu, HDC hdc, DRAWITEMSTRUCT *pdis)
+{
+ INT ratingValue;
+ if (NULL == hdc || !Menu_IsRatingStar(hMenu, pdis->itemID, &ratingValue))
+ return FALSE;
+
+ INT spaceCX = ((pdis->rcItem.bottom - pdis->rcItem.top) > RATING_MINSPACECX) ?
+ (pdis->rcItem.bottom - pdis->rcItem.top) :
+ RATING_MINSPACECX;
+
+ RATINGDRAWPARAMS rdp = {0};
+ rdp.cbSize = sizeof(RATINGDRAWPARAMS);
+ rdp.hdcDst = hdc;
+ rdp.rc = pdis->rcItem;
+ rdp.rc.left += spaceCX + WASABI_API_APP->getScaleX(13);
+ rdp.value = ratingValue;
+ rdp.maxValue = 5;
+
+ UINT menuState = GetMenuState(hMenu, pdis->itemID, MF_BYCOMMAND);
+ rdp.trackingValue = (0 != (ODS_SELECTED & pdis->itemState) &&
+ 0 == ((MF_DISABLED | MF_GRAYED) & menuState)) ?
+ rdp.value :
+ 0;
+
+ rdp.fStyle = RDS_LEFT | RDS_VCENTER | RDS_HOT;
+ rdp.hMLIL = NULL;
+ rdp.index = 0;
+
+ return MLRating_Draw(plugin.hwndLibraryParent, &rdp);
+}
+
+static BOOL CALLBACK Menu_CustomDrawProc(INT action, HMENU hMenu, HDC hdc, LPARAM param, ULONG_PTR user)
+{
+ switch(action)
+ {
+ case MLMENU_ACTION_MEASUREITEM:
+ if (hMenu == (HMENU)user)
+ return Menu_MeasureRating(hMenu, hdc, (MEASUREITEMSTRUCT*)param);
+ break;
+ case MLMENU_ACTION_DRAWITEM:
+ if (hMenu == (HMENU)user)
+ return MLMENU_WANT_DRAWPART;
+ break;
+ case MLMENU_ACTION_DRAWBACK:
+ break;
+ case MLMENU_ACTION_DRAWICON:
+ break;
+ case MLMENU_ACTION_DRAWTEXT:
+ if (hMenu == (HMENU)user)
+ return Menu_DrawRating(hMenu, hdc, (DRAWITEMSTRUCT*)param);
+ break;
+ }
+ return FALSE;
+}
+#endif
+
+static HMENU Menu_FindRatingMenuRecur(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii)
+{
+ if (GetMenuInfo(hMenu, pmi) && RATING_MARKER == pmi->dwMenuData)
+ return hMenu;
+
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii) && NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingMenuRecur(pmii->hSubMenu, pmi, pmii);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ return NULL;
+}
+
+HMENU Menu_FindRatingMenu(HMENU hMenu)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_SUBMENU;
+
+ MENUINFO mi;
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+
+ return Menu_FindRatingMenuRecur(hMenu, &mi, &mii);
+}
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue)
+{
+ if (NULL == ratingMenu) return FALSE;
+
+ INT ratingList[] = { ID_RATE_0, ID_RATE_1, ID_RATE_2,
+ ID_RATE_3, ID_RATE_4, ID_RATE_5};
+
+ /// set rating marker
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+ mi.dwMenuData = RATING_MARKER;
+ if (!SetMenuInfo(ratingMenu, &mi))
+ return FALSE;
+
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+
+ UINT type, state;
+ for (INT i = 0; i < ARRAYSIZE(ratingList); i++)
+ {
+ mii.fMask = MIIM_STATE | MIIM_FTYPE;
+ if (GetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii))
+ {
+ if (ratingValue == i)
+ {
+ type = mii.fType | MFT_RADIOCHECK;
+ state = mii.fState | MFS_CHECKED;
+ }
+ else
+ {
+ type = mii.fType & ~MFT_RADIOCHECK;
+ state = mii.fState & ~MFS_CHECKED;
+ }
+
+ mii.fMask = 0;
+ if (type != mii.fType)
+ {
+ mii.fType = type;
+ mii.fMask |= MIIM_FTYPE;
+ }
+
+ if (state != mii.fState)
+ {
+ mii.fState = state;
+ mii.fMask |= MIIM_STATE;
+ }
+
+ if (0 != mii.fMask)
+ SetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii);
+ }
+ }
+ return TRUE;
+}
+
+
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu)
+{
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(hwndDlg, szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendMenuShortcuts(menu, szAccel, c, MSF_REPLACE);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ int alt = 0;
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_MEDIAWND_PLAYSELECTEDFILES || id == ID_AUDIOWND_PLAYSELECTION || id == ID_QUERYWND_PLAYQUERY)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ if (id == ID_AUDIOWND_PLAYSELECTION) alt = 1;
+ else if (id == ID_QUERYWND_PLAYQUERY) alt = 2;
+ }
+ else if (id == ID_MEDIAWND_ENQUEUESELECTEDFILES || id == ID_AUDIOWND_ENQUEUESELECTION || id == ID_QUERYWND_ENQUEUEQUERY)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ if (id == ID_AUDIOWND_ENQUEUESELECTION) alt = 1;
+ else if (id == ID_QUERYWND_ENQUEUEQUERY) alt = 2;
+ }
+ }
+
+ playItem.wID = (alt == 1 ? ID_MEDIAWND_ENQUEUESELECTEDFILES : (alt == 2 ? ID_QUERYWND_ENQUEUEQUERY : ID_AUDIOWND_ENQUEUESELECTION));
+ enqueueItem.wID = (alt == 1 ? ID_MEDIAWND_PLAYSELECTEDFILES : (alt == 2 ? ID_QUERYWND_PLAYQUERY : ID_AUDIOWND_PLAYSELECTION));
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+void UpdateMenuItems(HWND hwndDlg, HMENU menu, UINT accel_id)
+{
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ if(!IsWindow(hwndDlg))
+ {
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(accel_id);
+ int size = CopyAcceleratorTable(accel,0,0);
+ AppendMenuShortcuts(menu, &accel, size, MSF_REPLACE);
+ }
+ else
+ {
+ SyncMenuWithAccelerators(hwndDlg, menu);
+ }
+
+ if (swapPlayEnqueue) SwapPlayEnqueueInMenu(menu);
+}
+
+INT DoTrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ return Menu_TrackPopupParam(plugin.hwndLibraryParent, hMenu, fuFlags, x, y,
+ hwnd, lptpm, (ULONG_PTR)Menu_FindRatingMenu(hMenu));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/local_menu.h b/Src/Plugins/Library/ml_local/local_menu.h
new file mode 100644
index 00000000..08907d70
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/local_menu.h
@@ -0,0 +1,15 @@
+#ifndef NULLOSFT_LOCALMEDIA_PLUGIN_MENU_HEADER
+#define NULLOSFT_LOCALMEDIA_PLUGIN_MENU_HEADER
+
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue);
+INT DoTrackPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm);
+void UpdateMenuItems(HWND hwndDlg, HMENU menu, UINT accel_id);
+
+#endif //NULLOSFT_LOCALMEDIA_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/metaRecord.h b/Src/Plugins/Library/ml_local/metaRecord.h
new file mode 100644
index 00000000..9dba63c2
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/metaRecord.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOT_LOCALMEDIA_METARECORD_H
+#define NULLSOT_LOCALMEDIA_METARECORD_H
+
+#include <map>
+#include <vector>
+#include <string>
+
+// Links db record to the metadata struct
+typedef struct
+{
+ int dbColumnId;
+ char *recordKey;
+} LM_RECORD_LINK;
+
+// cache size (records count)
+#define CACHE_SIZE 100;
+
+class MetaData
+{
+
+// construcotrs
+public:
+ MetaData();
+ ~MetaData();
+
+// methods
+private:
+ // gets record data from db to the cache
+ int GetDbColumnsCount();
+ void GetDBRecordToCache(int64 recordId);
+
+
+public:
+ // returns pointer to metadata
+ const char* GetMetaData(const char *metaKey);
+
+// fields
+private:
+
+
+}
+
+
+#endif //NULLSOT_LOCALMEDIA_METARECORD_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.cpp b/Src/Plugins/Library/ml_local/ml_local.cpp
new file mode 100644
index 00000000..b6e0dde7
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.cpp
@@ -0,0 +1,1394 @@
+// needed for handling the nde failed load error catching
+#pragma warning(disable:4530)
+#include "main.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include <time.h>
+#include <rpc.h>
+#include <assert.h>
+#include "resource.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/gaystring.h"
+
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+
+#include "../nde/nde.h"
+#include "../nde/nde_c.h"
+
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "..\..\General\gen_ml/MediaLibraryCOM.h"
+#include "api__ml_local.h"
+
+#include "../replicant/nu/AutoWide.h"
+
+#include "./scanfolderbrowser.h"
+#include "../replicant/nu/AutoChar.h"
+
+#include "api_mldb.h"
+
+
+int IPC_GET_ML_HMENU = -1, IPC_GET_CLOUD_HINST = -1, IPC_GET_CLOUD_ACTIVE = -1;
+
+HMENU wa_playlists_cmdmenu = NULL;
+HMENU wa_play_menu = 0;
+
+
+wchar_t *fieldTagFunc(wchar_t * tag, void * p); //return 0 if not found
+
+#define WA_MENUITEM_ID 23123
+
+
+WNDPROC wa_oldWndProc;
+BOOL myMenu = FALSE;
+
+HCURSOR hDragNDropCursor;
+
+C_Config *g_config;
+
+embedWindowState myWindowState;
+
+char g_burner_list[32] = "";
+
+int asked_for_playcount = 0;
+int g_bgrescan_int = 120, g_bgrescan_do = 0, g_bgrescan_force = 0, g_autochannel_do = 0;
+int g_guessifany = 0;
+int g_querydelay = 250;
+int g_viewnotplay = 0;
+
+wchar_t g_path[MAX_PATH] = {0};
+wchar_t g_tableDir[MAX_PATH] = {0};
+wchar_t g_viewsDir[MAX_PATH] = {0};
+
+//DB db;
+
+nde_database_t g_db=0;
+nde_table_t g_table=0;
+int g_table_dirty;
+
+CRITICAL_SECTION g_db_cs;
+
+HMENU g_context_menus = NULL, g_context_menus2 = NULL;
+HWND m_curview_hwnd = NULL;
+
+wchar_t *m_query = L"";
+int m_query_mode;
+
+C_Config *g_view_metaconf = NULL;
+
+static int m_query_moving;
+static HTREEITEM m_query_moving_dragplace;
+static HTREEITEM m_query_moving_item, m_query_moving_lastdest;
+static int m_query_moving_dragplaceisbelow;
+
+int m_query_tree;
+
+QueryList m_query_list;
+
+//xp theme disabling shit
+static HMODULE m_uxdll;
+HRESULT(__stdcall *SetWindowTheme)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList);
+HRESULT(__stdcall *IsAppThemed)(void);
+
+void db_setFieldStringW( nde_scanner_t s, unsigned char id, const wchar_t *data )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( !f )
+ f = NDE_Scanner_NewFieldByID( s, id );
+
+ NDE_StringField_SetString( f, data );
+}
+
+void db_setFieldInt( nde_scanner_t s, unsigned char id, int data )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( !f )
+ f = NDE_Scanner_NewFieldByID( s, id );
+
+ NDE_IntegerField_SetValue( f, data );
+}
+
+int db_getFieldInt( nde_scanner_t s, unsigned char id, int defaultVal )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( f )
+ return NDE_IntegerField_GetValue( f );
+ else
+ return defaultVal;
+}
+
+void db_setFieldInt64( nde_scanner_t s, unsigned char id, __int64 data )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( !f )
+ f = NDE_Scanner_NewFieldByID( s, id );
+
+ NDE_Int64Field_SetValue( f, data );
+}
+
+__int64 db_getFieldInt64( nde_scanner_t s, unsigned char id, __int64 defaultVal )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( f )
+ return NDE_Int64Field_GetValue( f );
+ else
+ return defaultVal;
+}
+
+void db_removeField( nde_scanner_t s, unsigned char id )
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID( s, id );
+ if ( f )
+ {
+ NDE_Scanner_DeleteField( s, f );
+ }
+}
+
+int pluginHandleIpcMessage(int msg, int param)
+{
+ return SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
+}
+
+void TAG_FMT_EXT(const wchar_t *filename, void *f, void *ff, void *p, wchar_t *out, int out_len, int extended)
+{
+ waFormatTitleExtended fmt;
+ fmt.filename = filename;
+ fmt.useExtendedInfo = extended;
+ fmt.out = out;
+ fmt.out_len = out_len;
+ fmt.p = p;
+ fmt.spec = 0;
+
+ *(void **)&fmt.TAGFUNC = f;
+ *(void **)&fmt.TAGFREEFUNC = ff;
+
+ *out = 0;
+
+ int oldCallingGetFileInfo = m_calling_getfileinfo;
+ m_calling_getfileinfo = 1;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+ m_calling_getfileinfo = oldCallingGetFileInfo;
+}
+
+void main_playItemRecordList(itemRecordListW *obj, int enqueue, int startplaying)
+{
+ assert(enqueue != -1); // benski> i'm pretty sure this isn't used anymore
+ if (obj->Size && !enqueue)
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+
+ int x;
+ for (x = 0; x < obj->Size; x ++)
+ {
+ if (obj->Items[x].filename && *obj->Items[x].filename)
+ {
+ wchar_t title[2048] = {0};
+ TAG_FMT_EXT(obj->Items[x].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&obj->Items[x], title, 2047, 0);
+
+ enqueueFileWithMetaStructW s;
+ s.filename = obj->Items[x].filename;
+ s.title = title;
+ s.ext = NULL;
+ s.length = obj->Items[x].length;
+#ifndef _DEBUG
+ ndestring_retain(obj->Items[x].filename);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW_NDE);
+#else
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+#endif
+ }
+ }
+
+ if (obj->Size && !enqueue && startplaying) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+}
+
+void main_playQuery(C_Config *viewconf, const wchar_t *query, int enqueue, int startplaying)
+{
+ // if enqueue is -1, we do it to the playlist
+ if (!g_table) return ;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query);
+ itemRecordListW obj = {0, };
+ // no need to have this provide compatible kb
+ // based filesizes as we never use the values
+ saveQueryToListW(viewconf, s, &obj, 0, 0, 0);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ main_playItemRecordList(&obj, enqueue, startplaying);
+
+ freeRecordList(&obj);
+}
+
+time_t g_bgscan_last_rescan;
+int g_bgscan_scanning;
+
+HWND updateCurrentView(HWND hwndDlg)
+{
+ if (m_curview_hwnd) DestroyWindow(m_curview_hwnd);
+ m_curview_hwnd = NULL;
+
+ delete g_view_metaconf;
+ g_view_metaconf = 0;
+
+ int id = -1;
+ DLGPROC proc = 0;
+
+ if (!g_table)
+ {
+ // try to show something better than a blank view
+ id = IDD_VIEW_DB_ERROR;
+ proc = view_errorinfoDialogProc;
+ }
+ else
+ {
+ switch (m_query_mode)
+ {
+ case 0:
+ id = IDD_VIEW_MEDIA; proc = view_mediaDialogProc;
+ break;
+ default:
+ id = IDD_VIEW_AUDIO; proc = view_audioDialogProc;
+ break;
+ }
+ }
+
+ if (id == -1)
+ proc = NULL;
+
+ if (id != -1)
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, m_query_metafile);
+ g_view_metaconf = new C_Config(configDir);
+
+ LPARAM lParam = 0;
+ INT_PTR parms[2] = {0};
+
+ if (g_config->ReadInt(L"useminiinfo2", 0) && (proc == view_audioDialogProc || proc == view_mediaDialogProc))
+ {
+ parms[0] = (INT_PTR)proc;
+ parms[1] = (INT_PTR)id;
+ lParam = (LPARAM) & parms;
+
+ id = IDD_VIEW_MINIINFO;
+ proc = view_miniinfoDialogProc;
+ }
+
+ if (proc)
+ m_curview_hwnd = WASABI_API_CREATEDIALOGPARAMW(id, hwndDlg, proc, lParam);
+ }
+
+ return m_curview_hwnd;
+}
+
+void makemetafn(wchar_t *filename, wchar_t **out)
+{
+ int x = 0;
+ for (;;)
+ {
+ GetTempFileNameW(g_viewsDir, L"meta", GetTickCount() + x*4050, filename);
+ if (wcslen(filename) > 4) wcscpy(filename + wcslen(filename) - 4, L".vmd");
+ HANDLE h = CreateFileW(filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW, 0, 0);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(h);
+ *out = _wcsdup(filename);
+ PathStripPathW(*out);
+ return ;
+ }
+ if (++x > 4096)
+ {
+ *out = _wcsdup(L"meta-error.vmd");
+ filename[0] = 0;
+ return ;
+ }
+ }
+}
+
+int ImageGuessFilter( int mode, const wchar_t *val, int index )
+{
+ if ( index != -1 )
+ return index;
+
+ if ( !lstrcmpiW( val, L"lastupd > [3 days ago]" ) )
+ return TREE_IMAGE_LOCAL_RECENTLYMODIFIED;
+ if ( !lstrcmpiW( val, L"dateadded > [3 days ago]" ) )
+ return TREE_IMAGE_LOCAL_RECENTLYADDED;
+ if ( !lstrcmpiW( val, L"playcount > 0" ) )
+ return TREE_IMAGE_LOCAL_MOSTPLAYED;
+ if ( !lstrcmpiW( val, L"rating >= 3" ) )
+ return TREE_IMAGE_LOCAL_TOPRATED;
+ if ( !lstrcmpiW( val, L"playcount = 0 | playcount isempty" ) )
+ return TREE_IMAGE_LOCAL_NEVERPLAYED;
+ if ( !lstrcmpiW( val, L"lastplay > [2 weeks ago]" ) )
+ return TREE_IMAGE_LOCAL_RECENTLYPLAYED;
+ if ( !lstrcmpiW( val, L"type = 0" ) )
+ return TREE_IMAGE_LOCAL_AUDIO;
+ if ( !lstrcmpiW( val, L"type = 1" ) )
+ return TREE_IMAGE_LOCAL_VIDEO;
+
+ return index;
+}
+
+int addQueryItem( const wchar_t *name, const wchar_t *val, int mode, int select, const wchar_t *metafn, int imageIndex, int num )
+{
+ MLTREEITEMW newItem = { 0 };
+ imageIndex = ImageGuessFilter( mode, val, imageIndex );
+
+ newItem.size = sizeof( newItem );
+ newItem.parentId = m_query_tree;
+ newItem.title = const_cast<wchar_t *>( name );
+ newItem.hasChildren = 0;
+ newItem.id = 0;
+ newItem.imageIndex = imageIndex;
+
+ if ( num <= 0 )
+ mediaLibrary.AddTreeItem( newItem );
+ else
+ {
+ for ( QueryList::iterator iter = m_query_list.begin(); iter != m_query_list.end(); iter++ )
+ {
+ if ( iter->second && iter->second->index == num - 1 )
+ newItem.id = iter->first; break;
+ }
+
+ mediaLibrary.InsertTreeItem( newItem );
+ }
+
+ queryItem *qi = (queryItem *)calloc( 1, sizeof( queryItem ) );
+ qi->name = _wcsdup( name );
+ qi->query = _wcsdup( val );
+ qi->mode = mode;
+ qi->imgIndex = imageIndex;
+ qi->index = m_query_list.size();
+
+ if ( !metafn || !metafn[ 0 ] )
+ {
+ wchar_t filename[ 1024 + 256 ] = { 0 };
+ makemetafn( filename, &qi->metafn );
+ }
+ else qi->metafn = _wcsdup( metafn );
+
+ m_query_list.insert({ newItem.id, qi });
+
+ return newItem.id;
+}
+
+void replaceQueryItem(int n, const wchar_t *name, const wchar_t *val, int mode, int imageIndex)
+{
+ queryItem *qi;
+
+ if ( mode == 32 )
+ return;
+
+ qi = m_query_list[n];
+ free(qi->name);
+ qi->name = _wcsdup(name);
+
+ if (val)
+ {
+ free(qi->query);
+ qi->query = _wcsdup(val);
+ }
+
+ qi->mode = mode;
+ qi->imgIndex = imageIndex;
+
+ MLTREEITEMW item;
+ item.hasChildren = 0;
+ item.id = n;
+ item.title = const_cast<wchar_t *>(name);
+ item.parentId = m_query_tree;
+ item.imageIndex = imageIndex;
+
+ mediaLibrary.SetTreeItem(item);
+ mediaLibrary.SelectTreeItem(n - 1);
+ mediaLibrary.SelectTreeItem(n);
+}
+
+wchar_t *def_names[] = { L"Audio",
+ L"Video",
+ L"Most Played",
+ L"Recently Added",
+ L"Recently Played",
+ L"Never Played",
+ L"Top Rated",
+ L"Recently Modified", };
+int def_str_ids[] = {IDS_AUDIO, IDS_VIDEO, IDS_MOST_PLAYED, IDS_RECENTLY_ADDED,
+ IDS_RECENTLY_PLAYED, IDS_NEVER_PLAYED, IDS_TOP_RATED,
+ IDS_RECENTLY_MODIFIED};
+
+void loadQueryTree()
+{
+ int meta_add_dirty = 0;
+ int nb = g_config->ReadInt(L"query_num", 0);
+ int fix = g_config->ReadInt(L"query_fix", 0);
+ int mig = g_config->ReadInt(L"query_mig", 0);
+
+ g_config->WriteInt(L"query_fix", 1);
+ g_config->WriteInt(L"query_mig", 1);
+
+ // helps to migrate existing vmd files to plugins\ml\views
+ wchar_t metafnold[MAX_PATH] = {0}, metafnnew[MAX_PATH] = {0};
+ if (!mig)
+ {
+ PathCombineW(metafnold, g_tableDir, L"default.vmd");
+ PathCombineW(metafnnew, g_viewsDir, L"default.vmd");
+ if (!PathFileExistsW(metafnnew) && PathFileExistsW(metafnold))
+ {
+ MoveFileW(metafnold, metafnnew);
+ }
+ }
+
+ for (int i = 0; i < nb; i++)
+ {
+ wchar_t qm[128] = {0}, qbmp[128] = {0}, qmet[128] = {0};
+ char qn[128] = {0}, qv[128] = {0}, name[1024] = {0}, val[1024] = {0};
+ wsprintfA(qn, "query%i_name", i + 1);
+ UINT codePage = CP_ACP;
+
+ if (!g_config->ReadString(qn, NULL, name, 1024) || !*name)
+ {
+ wsprintfA(qn, "query%i_name_utf8", i + 1);
+ g_config->ReadString(qn, NULL, name, 1024);
+ codePage = CP_UTF8;
+ if (!name)
+ continue;
+ }
+
+ wchar_t unicodeNameLoc[256] = {0};
+ AutoWide unicodeName(name, codePage);
+
+ wsprintfA(qv, "query%i_val", i + 1);
+ codePage = CP_ACP;
+ if (!g_config->ReadString(qv, NULL, val, 1024) || !*val)
+ {
+ wsprintfA(qv, "query%i_val_utf8", i + 1);
+ g_config->ReadString(qv, NULL, val, 1024);
+ codePage = CP_UTF8;
+ }
+
+ // this will convert 'lastupd > [3 days ago]' to 'dateadded > [3 days ago]'
+ // on older client installs so we're making use of the new dateadded column
+ if (!fix && val[0])
+ {
+ if (!_stricmp("lastupd > [3 days ago]", val))
+ {
+ lstrcpynA(val, "dateadded > [3 days ago]", sizeof(val));
+ g_config->WriteString(qv, val);
+ }
+ }
+
+ AutoWide unicodeVal(val, codePage);
+
+ wsprintfW(qm, L"query%i_mode", i + 1);
+ wsprintfW(qmet, L"query%i_meta", i + 1);
+ wsprintfW(qbmp, L"query%i_image", i + 1);
+
+ int mode = g_config->ReadInt(qm, -1);
+ if (mode == 32 || mode == -1)
+ continue; // old playlist or empty
+
+ wchar_t metafn[MAX_PATH] = {0};
+ g_config->ReadString(qmet, L"", metafn, MAX_PATH);
+
+ // helps to migrate existing vmd files to plugins\ml\views
+ if (!mig)
+ {
+ metafnold[0] = metafnnew[0] = 0;
+ PathCombineW(metafnold, g_tableDir, metafn);
+ PathCombineW(metafnnew, g_viewsDir, metafn);
+ if (!PathFileExistsW(metafnnew) && PathFileExistsW(metafnold))
+ {
+ MoveFileW(metafnold, metafnnew);
+ }
+ }
+
+ // see if we've got a name match to one of the defaults...
+ for(int j = 0; j < sizeof(def_names)/sizeof(def_names[0]); j++)
+ {
+ if(!lstrcmpiW(unicodeName, def_names[j]))
+ {
+ WASABI_API_LNGSTRINGW_BUF(def_str_ids[j], unicodeNameLoc, 256);
+ break;
+ }
+ }
+
+ addQueryItem((unicodeNameLoc[0]?unicodeNameLoc:unicodeName), unicodeVal, mode, 0, metafn, max(g_config->ReadInt(qbmp, -1), -1));
+ }
+
+ int aapos = g_config->ReadInt(L"view_autoadd_pos", 0);
+
+ if (aapos < 1)
+ {
+ if (!nb) // lame defaults added
+ {
+ meta_add_dirty = 1;
+ addQueryItem( WASABI_API_LNGSTRINGW( IDS_AUDIO ), L"type = 0", 1, 0, L"", TREE_IMAGE_LOCAL_AUDIO ); // new defaults
+ }
+
+ typedef struct
+ {
+ int title;
+ wchar_t *query;
+ char sort_by;
+ char sort_dir;
+ char *columns; //xff terminated list :)
+ int imageIndex;
+ }
+ addstruct;
+
+ addstruct m[] =
+ {
+ {IDS_VIDEO, L"type = 1", 10, 0, "\x7\1\5\x1E\6\3\x20\x8\x9\xA\xff", TREE_IMAGE_LOCAL_VIDEO},
+ {IDS_MOST_PLAYED, L"playcount > 0", 9, 0, "\x9\0\1\2\3\xA\xff", TREE_IMAGE_LOCAL_MOSTPLAYED},
+ {IDS_RECENTLY_ADDED, L"dateadded > [3 days ago]", 33, 0, "\x21\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYADDED},
+ {IDS_RECENTLY_MODIFIED, L"lastupd > [3 days ago]", 11, 0, "\xB\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYMODIFIED},
+ {IDS_RECENTLY_PLAYED, L"lastplay > [2 weeks ago]", 10, 0, "\xA\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYPLAYED},
+ {IDS_NEVER_PLAYED, L"playcount = 0 | playcount isempty", 0, 0, "\0\1\2\3\xff", TREE_IMAGE_LOCAL_NEVERPLAYED},
+ {IDS_TOP_RATED, L"rating >= 3", 8, 0, "\x8\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_TOPRATED},
+ };
+ if (aapos < 1)
+ {
+ int x;
+ for (x = 0; x < sizeof(m) / sizeof(m[0]); x++)
+ {
+ if (!x && nb) continue;
+
+ wchar_t filename[1024 + 256] = {0}, *ptr = 0;
+ makemetafn(filename, &ptr);
+
+ if (filename[0])
+ {
+ C_Config foo(filename);
+ foo.WriteInt(L"mv_sort_by", m[x].sort_by);
+ foo.WriteInt(L"mv_sort_dir", m[x].sort_dir);
+ int cnt = 0;
+ while ((unsigned char)m[x].columns[cnt] != 0xff)
+ {
+ wchar_t buf[32] = {0};
+ StringCchPrintfW(buf, 32, L"column%d", cnt);
+ foo.WriteInt(buf, (unsigned char)m[x].columns[cnt]);
+ cnt++;
+ }
+
+ foo.WriteInt(L"nbcolumns", cnt);
+ meta_add_dirty = 1;
+ addQueryItem(WASABI_API_LNGSTRINGW(m[x].title), m[x].query, 0, 0, ptr, m[x].imageIndex);
+ }
+
+ free(ptr);
+ }
+ }
+
+ g_config->WriteInt(L"view_autoadd_pos", 1);
+ }
+
+ if (meta_add_dirty)
+ saveQueryTree();
+}
+
+void saveQueryTree()
+{
+ int nb = g_config->ReadInt( L"query_num", 0 );
+ QueryList::iterator iter;
+ int i = 1;
+ wchar_t qm[ 128 ] = { 0 }, qmet[ 128 ] = { 0 }, qbmp[ 128 ] = { 0 };
+ char qn[ 128 ] = { 0 }, qv[ 128 ] = { 0 };
+
+ for ( int curId = mediaLibrary.GetChildId( m_query_tree ); curId != 0; curId = mediaLibrary.GetNextId( curId ), i++ )
+ {
+ iter = m_query_list.find( curId );
+ if ( iter == m_query_list.end() )
+ continue;
+
+ if ( i <= nb )
+ {
+ do
+ {
+ wsprintfW( qm, L"query%i_mode", i );
+ } while ( g_config->ReadInt( qm, -1 ) == 32 && i++ );
+ }
+
+ wsprintfA( qn, "query%i_name", i );
+ wsprintfA( qv, "query%i_val", i );
+ g_config->WriteString( qn, 0 ); // erase these old config items
+ g_config->WriteString( qv, 0 ); // erase these old config items
+
+ wsprintfA( qn, "query%i_name_utf8", i );
+ wsprintfA( qv, "query%i_val_utf8", i );
+ wsprintfW( qm, L"query%i_mode", i );
+ wsprintfW( qmet, L"query%i_meta", i );
+ wsprintfW( qbmp, L"query%i_image", i );
+
+ queryItem *thisitem = iter->second;
+ if ( thisitem == NULL ) continue;
+
+ wchar_t charNameLoc[ 256 ] = { 0 };
+ // see if we've got a name match to one of the defaults...
+ for ( int j = 0; j < sizeof( def_names ) / sizeof( def_names[ 0 ] ); j++ )
+ {
+ if ( !lstrcmpiW( thisitem->name, WASABI_API_LNGSTRINGW( def_str_ids[ j ] ) ) )
+ {
+ lstrcpyn( charNameLoc, def_names[ j ], ARRAYSIZE( charNameLoc ) );
+ break;
+ }
+ }
+
+ g_config->WriteString( qn, ( charNameLoc[ 0 ] ? charNameLoc : AutoChar( thisitem->name, CP_UTF8 ) ) );
+ g_config->WriteString( qv, AutoChar( thisitem->query, CP_UTF8 ) );
+ g_config->WriteInt( qm, thisitem->mode );
+ g_config->WriteString( qmet, thisitem->metafn );
+ g_config->WriteInt( qbmp, max( thisitem->imgIndex, -1 ) );
+ }
+
+ --i;
+ if ( i < nb )
+ {
+ for ( int k = i + 1; k <= nb; k++ )
+ {
+ wsprintfW( qm, L"query%i_mode", k );
+
+ int mode = g_config->ReadInt( qm, -1 );
+ if ( mode == 32 )
+ i++;
+ else
+ {
+ wsprintfA( qn, "query%i_name", k );
+ wsprintfA( qv, "query%i_val", k );
+ wsprintfW( qm, L"query%i_mode", k );
+ wsprintfW( qmet, L"query%i_meta", k );
+ wsprintfW( qbmp, L"query%i_image", k );
+
+ g_config->WriteString( qn, NULL );
+ g_config->WriteString( qv, NULL );
+ g_config->WriteString( qm, NULL );
+ g_config->WriteString( qmet, NULL );
+ g_config->WriteString( qbmp, NULL );
+ }
+ }
+ }
+
+ g_config->WriteInt( L"query_num", i );
+}
+
+HTREEITEM g_treedrag_lastSel;
+
+HWND onTreeViewSelectChange(HWND hwnd)
+{
+ if ( !g_table )
+ openDb();
+
+ bgQuery_Stop();
+
+ int par = mediaLibrary.GetSelectedTreeItem();
+
+ // defaults
+ m_query_mode = par;
+ m_query = L"";
+
+ m_query_metafile = L"";
+ m_query_mode = 0;
+
+ if (par == m_query_tree) // set up default media view
+ {
+ m_query_metafile = L"default.vmd";
+ }
+ else
+ {
+ QueryList::iterator iter;
+ iter = m_query_list.find(par);
+ if (iter != m_query_list.end())
+ {
+ m_query = iter->second->query;
+ m_query_mode = iter->second->mode;
+ m_query_metafile = iter->second->metafn;
+ }
+ else
+ {
+ m_query_metafile = L"default.vmd";
+ }
+ }
+
+ return updateCurrentView(hwnd);
+}
+
+void add_pledit_to_library()
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITEPLAYLIST);
+ wchar_t *m3udir = (wchar_t *) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETM3UDIRECTORYW);
+ wchar_t filename[MAX_PATH] = {0};
+ PathCombineW(filename, m3udir, L"winamp.m3u8");
+
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(filename, &plCB))
+ {
+ mediaLibrary.AddToMediaLibrary(filename);
+ }
+}
+
+void add_to_library(HWND wndparent)
+{
+ ScanFolderBrowser browser;
+ browser.SetBckScanChecked(g_config->ReadInt(L"addinbg", 0));
+
+ if (browser.Browse(wndparent))
+ {
+ wchar_t path[MAX_PATH] = {0};
+ g_config->WriteInt(L"addinbg", browser.GetBckScanChecked());
+ SHGetPathFromIDListW(browser.GetPIDL(), path);
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(path, &guess, &meta, &rec, 0);
+
+ if (guess == -1)
+ guess = g_config->ReadInt(L"guessmode", 0);
+
+ if (meta == -1)
+ meta = g_config->ReadInt(L"usemetadata", 1);
+
+ if (g_config->ReadInt(L"addinbg", 0))
+ {
+ Scan_ScanFolderBackground(path, guess, meta, rec); // add our dir to scan :)
+ }
+ else
+ {
+ Scan_ScanFolder(wndparent, path, guess, meta, rec);
+
+ int par = mediaLibrary.GetSelectedTreeItem();
+ mediaLibrary.SelectTreeItem(par - 1);
+ mediaLibrary.SelectTreeItem(par);
+
+ if (IsWindow(m_curview_hwnd))
+ SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ }
+ }
+}
+
+void nukeLibrary(HWND hwndDlg)
+{
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(hwndDlg, WASABI_API_LNGSTRINGW(IDS_REMOVE_ALL_ITEMS_IN_LIBRARY),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,titleStr,32),
+ MB_YESNO | MB_ICONQUESTION) == IDYES)
+ {
+ wchar_t *last_query = NULL;
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner)
+ {
+ const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
+ if (lq) last_query = _wcsdup(lq);
+ NDE_Table_DestroyScanner(g_table, m_media_scanner);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ bgQuery_Stop();
+ Scan_Cancel();
+
+ int count = 0;
+ wchar_t **filenames = 0;
+ nde_scanner_t clearedScanner = NDE_Table_CreateScanner(g_table);
+ if (clearedScanner)
+ {
+ filenames = new wchar_t * [(count = NDE_Scanner_GetRecordsCount(clearedScanner))];
+ int i = 0;
+ for (NDE_Scanner_First(clearedScanner); !NDE_Scanner_EOF(clearedScanner); NDE_Scanner_Next(clearedScanner))
+ {
+ nde_field_t fileName = NDE_Scanner_GetFieldByID(clearedScanner, MAINTABLE_ID_FILENAME);
+ if (fileName)
+ {
+ filenames[i] = NDE_StringField_GetString(fileName);
+ ndestring_retain(filenames[i]);
+ i++;
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, clearedScanner);
+
+ closeDb();
+
+ wchar_t tmp[MAX_PATH] = {0};
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\main.dat", g_tableDir);
+ DeleteFileW(tmp);
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\main.idx", g_tableDir);
+ DeleteFileW(tmp);
+
+ openDb();
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner)
+ {
+ m_media_scanner = NDE_Table_CreateScanner(g_table);
+ if (last_query != NULL)
+ {
+ NDE_Scanner_Query(m_media_scanner, last_query);
+ free(last_query);
+ }
+ }
+ LeaveCriticalSection(&g_db_cs);
+ DumpArtCache();
+ if (IsWindow(m_curview_hwnd))
+ SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+
+ // Wasabi event callback when the media library is cleared
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_CLEARED, (size_t)filenames, count);
+ if (filenames) delete[] filenames;
+
+ // trigger a refresh of the current view
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+}
+
+extern int main_sendto_mode;
+
+extern HMENU main_sendto_hmenu;
+
+
+wchar_t *itemrecordWTagFunc(wchar_t *tag, void * p) //return 0 if not found
+{
+ // TODO we can put more tags in here
+ itemRecordW *t = (itemRecordW *)p;
+ //bool copy=false;
+ wchar_t buf[128] = {0};
+ wchar_t *value = NULL;
+
+ if ( !_wcsicmp( tag, DB_FIELDNAME_artist ) )
+ value = t->artist;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_album ) )
+ value = t->album;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_filename ) )
+ value = t->filename;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_title ) )
+ value = t->title;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_year ) )
+ {
+ if ( t->year > 0 )
+ {
+ wsprintfW( buf, L"%04d", t->year );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_genre ) )
+ value = t->genre;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_comment ) )
+ value = t->comment;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_tracknumber ) || !_wcsicmp( tag, DB_FIELDNAME_track ) )
+ {
+ if ( t->track > 0 )
+ {
+ if ( t->tracks > 0 )
+ wsprintfW( buf, L"%02d/%02d", t->track, t->tracks );
+ else
+ wsprintfW( buf, L"%02d", t->track );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_disc ) )
+ {
+ if ( t->disc > 0 )
+ {
+ if ( t->discs > 0 )
+ wsprintfW( buf, L"%d/%d", t->disc, t->discs );
+ else
+ wsprintfW( buf, L"%d", t->disc );
+
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_rating ) )
+ {
+ if ( t->rating > 0 )
+ {
+ wsprintfW( buf, L"%d", t->rating );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_playcount ) )
+ {
+ if ( t->playcount > 0 )
+ {
+ wsprintfW( buf, L"%d", t->playcount );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bitrate ) )
+ {
+ if ( t->bitrate > 0 )
+ {
+ wsprintfW( buf, L"%d", t->bitrate );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bpm ) )
+ {
+ if ( t->bpm > 0 )
+ {
+ wsprintfW( buf, L"%d", t->bpm );
+ value = buf;
+ }
+ }
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_albumartist ) )
+ value = t->albumartist;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_publisher ) )
+ value = t->publisher;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_composer ) )
+ value = t->composer;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_album_gain ) )
+ value = t->replaygain_album_gain;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_track_gain ) )
+ value = t->replaygain_track_gain;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteFileID ) )
+ value = getRecordExtendedItem_fast( t, extended_fields.GracenoteFileID );
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteExtData ) )
+ value = getRecordExtendedItem_fast( t, extended_fields.GracenoteExtData );
+ else
+ return 0;
+
+ if (!value)
+ return reinterpret_cast<wchar_t *>(-1);
+ else
+ {
+ if (/*copy || */value == buf)
+ return ndestring_wcsdup(value);
+ else
+ {
+ ndestring_retain(value);
+ return value;
+ }
+ }
+}
+
+static bool TagNameToFieldID(const wchar_t *tag, int *id)
+{
+ if ( !_wcsicmp( tag, DB_FIELDNAME_artist ) )
+ *id = MAINTABLE_ID_ARTIST;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_album ) )
+ *id = MAINTABLE_ID_ALBUM;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_filename ) )
+ *id = MAINTABLE_ID_FILENAME;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_title ) )
+ *id = MAINTABLE_ID_TITLE;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_year ) )
+ *id = MAINTABLE_ID_YEAR;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_genre ) )
+ *id = MAINTABLE_ID_GENRE;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_comment ) )
+ *id = MAINTABLE_ID_COMMENT;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_tracknumber ) || !_wcsicmp( tag, DB_FIELDNAME_track ) )
+ *id = MAINTABLE_ID_TRACKNB;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_rating ) )
+ *id = MAINTABLE_ID_RATING;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_playcount ) )
+ *id = MAINTABLE_ID_PLAYCOUNT;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bitrate ) )
+ *id = MAINTABLE_ID_BITRATE;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_disc ) )
+ *id = MAINTABLE_ID_DISC;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_bpm ) )
+ *id = MAINTABLE_ID_BPM;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_albumartist ) )
+ *id = MAINTABLE_ID_ALBUMARTIST;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_publisher ) )
+ *id = MAINTABLE_ID_PUBLISHER;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_composer ) )
+ *id = MAINTABLE_ID_COMPOSER;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_album_gain ) )
+ *id = MAINTABLE_ID_ALBUMGAIN;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_replaygain_track_gain ) )
+ *id = MAINTABLE_ID_TRACKGAIN;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteFileID ) )
+ *id = MAINTABLE_ID_GRACENOTEFILEID;
+ else if ( !_wcsicmp( tag, DB_FIELDNAME_GracenoteExtData ) )
+ *id = MAINTABLE_ID_GRACENOTEEXTDATA;
+ //else if (!_wcsicmp(tag, DB_FIELDNAME_lossless)) *id = MAINTABLE_ID_LOSSLESS;
+ else
+ return false;
+
+ return true;
+}
+
+wchar_t *fieldTagFunc(wchar_t * tag, void * p) //return 0 if not found
+{
+ nde_scanner_t s = (nde_scanner_t)p;
+ int id = -1;
+
+ if (!TagNameToFieldID(tag, &id))
+ return 0;
+
+ if (id >= 0)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (f)
+ switch (id)
+ {
+ case MAINTABLE_ID_YEAR:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0) return reinterpret_cast<wchar_t *>(-1);
+ wsprintfW(buf, L"%04d", l);
+ return ndestring_wcsdup(buf);
+ }
+ case MAINTABLE_ID_TRACKNB:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0) return reinterpret_cast<wchar_t *>(-1);
+ int tracks = db_getFieldInt(s, MAINTABLE_ID_TRACKS, -1);
+ if (tracks > 0)
+ wsprintfW(buf, L"%02d/%02d", l, tracks);
+ else
+ wsprintfW(buf, L"%02d", l);
+ return ndestring_wcsdup(buf);
+ }
+ case MAINTABLE_ID_DISC:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0) return reinterpret_cast<wchar_t *>(-1);
+ int discs = db_getFieldInt(s, MAINTABLE_ID_DISCS, -1);
+ if (discs > 0)
+ wsprintfW(buf, L"%d/%d", l, discs);
+ else
+ wsprintfW(buf, L"%d", l);
+
+ return ndestring_wcsdup(buf);
+ }
+ case MAINTABLE_ID_PLAYCOUNT:
+ asked_for_playcount = 1;
+ // fall through :)
+ case MAINTABLE_ID_BPM:
+ case MAINTABLE_ID_RATING:
+ case MAINTABLE_ID_BITRATE:
+ {
+ wchar_t buf[32] = {0};
+ int l = NDE_IntegerField_GetValue(f);
+ if (l < 0)
+ return reinterpret_cast<wchar_t *>(-1);
+
+ wsprintfW(buf, L"%d", l);
+ return ndestring_wcsdup(buf);
+ }
+ default:
+ {
+ wchar_t *p = NDE_StringField_GetString(f);
+ if (!p || !*p)
+ return reinterpret_cast<wchar_t *>(-1);;
+
+ ndestring_retain(p);
+ return p;
+ }
+ }
+
+ }
+ return 0;
+}
+
+void ndeTagFuncFree(wchar_t * tag, void * p)
+{
+ ndestring_release(tag);
+}
+
+void RetypeFilename(nde_table_t table);
+void RefreshFileSizeAndDateAddedTable(nde_table_t table);
+void ReindexTable(nde_table_t table);
+
+static void CreateFields( nde_table_t table )
+{
+ // create defaults
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_FILENAME, DB_FIELDNAME_filename, FIELD_FILENAME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TITLE, DB_FIELDNAME_title, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ARTIST, DB_FIELDNAME_artist, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ALBUM, DB_FIELDNAME_album, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_YEAR, DB_FIELDNAME_year, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GENRE, DB_FIELDNAME_genre, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_COMMENT, DB_FIELDNAME_comment, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TRACKNB, DB_FIELDNAME_trackno, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LENGTH, DB_FIELDNAME_length, FIELD_LENGTH );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TYPE, DB_FIELDNAME_type, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LASTUPDTIME, DB_FIELDNAME_lastupd, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LASTPLAY, DB_FIELDNAME_lastplay, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_RATING, DB_FIELDNAME_rating, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GRACENOTE_ID, DB_FIELDNAME_tuid2, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PLAYCOUNT, DB_FIELDNAME_playcount, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_FILETIME, DB_FIELDNAME_filetime, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_FILESIZE, DB_FIELDNAME_filesize, FIELD_INT64 );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_BITRATE, DB_FIELDNAME_bitrate, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DISC, DB_FIELDNAME_disc, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ALBUMARTIST, DB_FIELDNAME_albumartist, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ALBUMGAIN, DB_FIELDNAME_replaygain_album_gain, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TRACKGAIN, DB_FIELDNAME_replaygain_track_gain, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PUBLISHER, DB_FIELDNAME_publisher, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_COMPOSER, DB_FIELDNAME_composer, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_BPM, DB_FIELDNAME_bpm, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DISCS, DB_FIELDNAME_discs, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_TRACKS, DB_FIELDNAME_tracks, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_ISPODCAST, DB_FIELDNAME_ispodcast, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PODCASTCHANNEL, DB_FIELDNAME_podcastchannel, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PODCASTPUBDATE, DB_FIELDNAME_podcastpubdate, FIELD_DATETIME );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GRACENOTEFILEID, DB_FIELDNAME_GracenoteFileID, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_GRACENOTEEXTDATA, DB_FIELDNAME_GracenoteExtData, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_LOSSLESS, DB_FIELDNAME_lossless, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_CATEGORY, DB_FIELDNAME_category, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_CODEC, DB_FIELDNAME_codec, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DIRECTOR, DB_FIELDNAME_director, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_PRODUCER, DB_FIELDNAME_producer, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_WIDTH, DB_FIELDNAME_width, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_HEIGHT, DB_FIELDNAME_height, FIELD_INTEGER );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_MIMETYPE, DB_FIELDNAME_mimetype, FIELD_STRING );
+ NDE_Table_NewColumnW( table, MAINTABLE_ID_DATEADDED, DB_FIELDNAME_dateadded, FIELD_DATETIME );
+
+ NDE_Table_PostColumns(table);
+ NDE_Table_AddIndexByIDW(table, MAINTABLE_ID_FILENAME, DB_FIELDNAME_filename );
+}
+
+int openDb()
+{
+ // TODO: fix!! this is a Double-Checked Lock Pattern and can have strange results
+ // in weird conditions because g_table is assigned before fully initialized
+ if ( g_table )
+ return 0;
+
+ EnterCriticalSection(&g_db_cs);
+
+ // benski> i know this looks redundant, but we might have sat and blocked at the above Critical Section for a while
+ if (g_table)
+ {
+ LeaveCriticalSection(&g_db_cs);
+
+ return 0;
+ }
+
+ if ( !g_db )
+ {
+ __try
+ {
+ g_db = NDE_CreateDatabase( plugin.hDllInstance );
+ }
+ __except ( EXCEPTION_EXECUTE_HANDLER )
+ {
+ g_db = NULL;
+ LeaveCriticalSection( &g_db_cs );
+
+ return 0;
+ }
+ }
+
+ const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
+ wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(indexName, inidir, L"Plugins\\ml");
+ PathCombineW(tableName, indexName, L"main.dat");
+ PathAppendW(indexName, L"main.idx");
+
+ g_table = NDE_Database_OpenTable(g_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
+ if (g_table)
+ {
+ ((Table *)g_table)->EnableRowCache(); // TODO: don't use c++ NDE API
+ CreateFields(g_table);
+ RetypeFilename(g_table);
+
+ #define REINDEX_KEY L"reindex_561"
+ if ( !g_config->ReadInt( REINDEX_KEY, 0 ) ) // do we need to reindex?
+ ReindexTable( g_table );
+
+ g_config->WriteInt(REINDEX_KEY, 1);
+
+ #undef REINDEX_KEY
+ #define REINDEX_KEY L"reindex_564"
+ if ( g_config->ReadInt( REINDEX_KEY, 0 ) != 2 ) // do we need to update the filesizes and date added?
+ RefreshFileSizeAndDateAddedTable( g_table );
+
+ g_config->WriteInt(REINDEX_KEY, 2);
+
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, NDE_Table_GetRecordsCount(g_table), IPC_STATS_LIBRARY_ITEMCNT);
+ }
+
+ LeaveCriticalSection(&g_db_cs);
+
+ return (g_table != 0);
+}
+
+// TODO make sure we're only ever saving if there was an actual change!!
+void closeDb()
+{
+ if ( g_db )
+ {
+ __try
+ {
+ if ( g_table )
+ {
+ if ( g_table_dirty )
+ NDE_Table_Sync( g_table );
+
+ NDE_Database_CloseTable( g_db, g_table );
+ }
+
+ NDE_DestroyDatabase( g_db );
+ }
+ __except ( EXCEPTION_EXECUTE_HANDLER )
+ {
+ }
+ }
+
+ g_db = NULL;
+ g_table = NULL;
+}
+
+LPCWSTR WINAMP_INI = NULL;
+WNDPROC ml_oldWndProc = NULL;
+LARGE_INTEGER freq;
+
+int init()
+{
+ QueryPerformanceFrequency(&freq);
+
+ g_table = NULL;
+ g_db = NULL;
+ g_bgscan_last_rescan = time( NULL );
+
+ LPCWSTR dir = (LPCWSTR )SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW);
+ if ( (INT_PTR)( dir ) < 65536 )
+ return 1;
+
+ PathCombineW(g_path, dir, L"Plugins");
+ CreateDirectoryW(g_path, NULL);
+
+ WINAMP_INI = (LPCWSTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIFILEW);
+
+ wchar_t configName[MAX_PATH] = {0};
+ PathCombineW(configName, g_path, L"gen_ml.ini");
+ g_config = new C_Config(configName);
+
+ g_bgrescan_int = g_config->ReadInt(L"bgrescan_int", g_bgrescan_int);
+ g_bgrescan_do = g_config->ReadInt(L"bgrescan_do", g_bgrescan_do);
+ g_bgrescan_force = g_config->ReadInt(L"bgrescan_startup", 0); // temporarily used
+ g_guessifany = g_config->ReadInt(L"guessifany", g_guessifany);
+ g_viewnotplay = g_config->ReadInt(L"viewnotplay", g_viewnotplay);
+
+ // this allows an override of the delay from making a change in the search box
+ // in the views to when the search will be run - sometimes needs tweaking for
+ // either older machines or some of the less powerful 'portable' type machines
+ g_querydelay = g_config->ReadInt(L"querydelay", g_querydelay);
+ if ( g_querydelay < 1 || g_querydelay > 5000 )
+ g_querydelay = 250;
+
+ PathCombineW(g_tableDir, g_path, L"ml");
+ PathCombineW(g_viewsDir, g_tableDir, L"views");
+
+ if (!g_config->ReadInt(L"artdbmig", 0))
+ {
+ MigrateArtCache();
+ g_config->WriteInt(L"artdbmig", 1);
+ }
+
+ wa_oldWndProc = (WNDPROC) SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc);
+
+ if ( g_bgrescan_force || g_config->ReadInt( L"dbloadatstart", 1 ) )
+ openDb();
+
+ HMENU wa_plcontext_menu = GetSubMenu((HMENU)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)-1, IPC_GET_HMENU), 2);
+ if ( wa_plcontext_menu )
+ wa_playlists_cmdmenu = GetSubMenu( wa_plcontext_menu, 4 );
+
+ wa_play_menu = GetSubMenu((HMENU)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_GET_HMENU), 2);
+
+ // lets extend menu that called on button press
+ IPC_GET_ML_HMENU = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibraryGetHmenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ g_context_menus = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ g_context_menus2 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+
+ HMENU rate_hmenu = GetSubMenu(GetSubMenu(g_context_menus,1),4);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ HMENU context_menu = (HMENU) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_ML_HMENU);
+
+ if (context_menu)
+ {
+ HMENU btnMenu = GetSubMenu(context_menu, 0);
+ if (btnMenu)
+ {
+ MENUITEMINFOW mii = {sizeof(MENUITEMINFOW)};
+
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ mii.fState = MFS_ENABLED;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.fMask = MIIM_TYPE | MIIM_ID;
+ mii.fType = MFT_STRING;
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_NEW_SMART_VIEW);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_DOSHITMENU_ADDNEWVIEW;
+ InsertMenuItemW(btnMenu, 1, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_RESCAN_WATCH_FOLDERS);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_RESCANFOLDERSNOW;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_ADD_PLEDIT_TO_LOCAL_MEDIA);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_ADD_PLEDIT;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_ADD_MEDIA_TO_LIBRARY);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_ADD_DIRS;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(IDS_REMOVE_MISSING_FILES_FROM_ML);
+ mii.cch = (unsigned int) wcslen(mii.dwTypeData);
+ mii.wID = IDM_REMOVE_UNUSED_FILES;
+ InsertMenuItemW(btnMenu, 0, TRUE, &mii);
+ }
+ }
+
+ IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_GET_CLOUD_ACTIVE = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudActive", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ ml_oldWndProc = (WNDPROC) SetWindowLongPtrW(plugin.hwndLibraryParent, GWLP_WNDPROC, (LONG_PTR)ml_newWndProc);
+
+ HookPlaylistEditor();
+ hDragNDropCursor = LoadCursor(GetModuleHandle(L"gen_ml.dll"), MAKEINTRESOURCE(ML_IDC_DRAGDROP));
+
+ // rescan timer
+ SetTimer( plugin.hwndLibraryParent, 200, 1000, NULL );
+
+ return 0;
+}
+
+int OnLocalMediaItemClick( int action, int item, HWND parent )
+{
+ switch ( action )
+ {
+ case ML_ACTION_ENTER:
+ case ML_ACTION_DBLCLICK:
+ {
+ queryItem *qitem = m_query_list[ item ];
+ if ( qitem != NULL )
+ {
+ wchar_t configDir[ MAX_PATH ] = { 0 };
+ PathCombineW( configDir, g_viewsDir, qitem->metafn );
+ C_Config viewconf( configDir );
+ main_playQuery( &viewconf, qitem->query, ( ( !!( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) ) ^ ( !!g_config->ReadInt( L"enqueuedef", 0 ) ) ) );
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+int OnLocalMediaClick(int action, HWND parent)
+{
+ switch (action)
+ {
+ case ML_ACTION_ENTER:
+ case ML_ACTION_DBLCLICK:
+ return 1;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.h b/Src/Plugins/Library/ml_local/ml_local.h
new file mode 100644
index 00000000..b607607c
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.h
@@ -0,0 +1,403 @@
+#ifndef ML_LOCAL_HEADER
+#define ML_LOCAL_HEADER
+
+#include <windows.h>
+#include <commctrl.h>
+
+#include <iostream> // for std::wstring
+
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/config.h"
+
+#include <map>
+#include "../nde/nde_c.h"
+#include <vector>
+
+struct ExtendedFields
+{
+ const wchar_t *ispodcast;
+ const wchar_t *podcastchannel;
+ const wchar_t *podcastpubdate;
+ const wchar_t *GracenoteFileID;
+ const wchar_t *GracenoteExtData;
+ const wchar_t *lossless;
+ const wchar_t *codec;
+ const wchar_t *director;
+ const wchar_t *producer;
+ const wchar_t *width;
+ const wchar_t *height;
+ const wchar_t *mimetype;
+ const wchar_t *realsize;
+ const wchar_t *dateadded;
+ const wchar_t *cloud;
+};
+extern const ExtendedFields extended_fields;
+
+extern const int NUM_EXTRA_COLSW;
+extern const unsigned char extra_idsW[]; // defined in view_media.cpp
+extern const wchar_t *extra_strsW[];
+
+extern HCURSOR hDragNDropCursor;
+wchar_t *getRecordExtendedItem_fast(const itemRecordW *item, const wchar_t *name);
+void setRecordExtendedItem_fast(itemRecordW *item, const wchar_t *name, const wchar_t *value);
+
+int OnLocalMediaItemClick(int action, int item, HWND parent);
+int OnLocalMediaClick(int action, HWND parent);
+BOOL IPC_HookExtInfo(INT_PTR param);
+BOOL IPC_HookExtInfoW(INT_PTR param);
+BOOL IPC_HookTitleInfo(INT_PTR param);
+
+#include "../winamp/wa_ipc.h"
+
+
+#define INT_ENTRY_MAX_NUM 20
+#define INT_ENTRY_MAX_PATHSIZE 512
+#define INT_ENTRY_MAX_TEXTSIZE 128
+
+#define TREE_IMAGE_LOCAL_AUDIO 101
+#define TREE_IMAGE_LOCAL_VIDEO 102
+#define TREE_IMAGE_LOCAL_MOSTPLAYED 103
+#define TREE_IMAGE_LOCAL_RECENTLYADDED 104
+#define TREE_IMAGE_LOCAL_RECENTLYPLAYED 105
+#define TREE_IMAGE_LOCAL_NEVERPLAYED 106
+#define TREE_IMAGE_LOCAL_TOPRATED 107
+#define TREE_IMAGE_LOCAL_PODCASTS 108
+#define TREE_IMAGE_LOCAL_RECENTLYMODIFIED 109
+
+
+#define MAINTABLE_ID_FILENAME 0
+#define MAINTABLE_ID_TITLE 1
+#define MAINTABLE_ID_ARTIST 2
+#define MAINTABLE_ID_ALBUM 3
+#define MAINTABLE_ID_YEAR 4
+#define MAINTABLE_ID_GENRE 5
+#define MAINTABLE_ID_COMMENT 6
+#define MAINTABLE_ID_TRACKNB 7
+#define MAINTABLE_ID_LENGTH 8 // in seconds
+#define MAINTABLE_ID_TYPE 9 // 0=audio, 1=video
+#define MAINTABLE_ID_LASTUPDTIME 10 // last time (seconds since 1970) of db update of this item
+#define MAINTABLE_ID_LASTPLAY 11 // last time (seconds since 1970) of last play
+#define MAINTABLE_ID_RATING 12
+#define MAINTABLE_ID_GRACENOTE_ID 14 // OLD Gracenote ID's. Don't use this anymore!!!
+#define MAINTABLE_ID_PLAYCOUNT 15 // play count
+#define MAINTABLE_ID_FILETIME 16 // file time
+#define MAINTABLE_ID_FILESIZE 17 // file size, bytes (was kilobytes until 5.7)
+#define MAINTABLE_ID_BITRATE 18 // file bitratea, kbps
+#define MAINTABLE_ID_DISC 19 // disc #
+#define MAINTABLE_ID_ALBUMARTIST 20 // album artist
+#define MAINTABLE_ID_ALBUMGAIN 21 // album gain (replaygain)
+#define MAINTABLE_ID_TRACKGAIN 22 // track gain (replaygain)
+#define MAINTABLE_ID_PUBLISHER 23 // publisher (record label)
+#define MAINTABLE_ID_COMPOSER 24 // composer
+#define MAINTABLE_ID_BPM 25 // beats per minute (tempo)
+#define MAINTABLE_ID_DISCS 26 // number of discs total
+#define MAINTABLE_ID_TRACKS 27 // number of tracks total
+#define MAINTABLE_ID_ISPODCAST 28
+#define MAINTABLE_ID_PODCASTCHANNEL 29
+#define MAINTABLE_ID_PODCASTPUBDATE 30
+#define MAINTABLE_ID_GRACENOTEFILEID 31
+#define MAINTABLE_ID_GRACENOTEEXTDATA 32
+#define MAINTABLE_ID_LOSSLESS 33
+#define MAINTABLE_ID_CATEGORY 34
+#define MAINTABLE_ID_CODEC 35
+#define MAINTABLE_ID_DIRECTOR 36
+#define MAINTABLE_ID_PRODUCER 37
+#define MAINTABLE_ID_WIDTH 38
+#define MAINTABLE_ID_HEIGHT 39
+#define MAINTABLE_ID_MIMETYPE 40
+#define MAINTABLE_ID_DATEADDED 41 // time file was added to the db
+
+// menu command id
+#define IDM_DOSHITMENU_ADDNEWVIEW 40030
+#define IDM_RESCANFOLDERSNOW 4066
+#define IDM_ADD_DIRS 4067
+#define IDM_REMOVE_UNUSED_FILES 4068
+#define IDM_ADD_PLEDIT 4069
+
+
+
+static const std::wstring _DB_FIELDNAME_tracknumber = L"tracknumber"; static const wchar_t *DB_FIELDNAME_tracknumber = _DB_FIELDNAME_tracknumber.c_str();
+static const std::wstring _DB_FIELDNAME_track = L"track"; static const wchar_t *DB_FIELDNAME_track = _DB_FIELDNAME_track.c_str();
+
+static const std::wstring _DB_FIELDNAME_filename = L"filename"; static const wchar_t *DB_FIELDNAME_filename = _DB_FIELDNAME_filename.c_str();
+static const std::wstring _DB_FIELDNAME_title = L"title"; static const wchar_t *DB_FIELDNAME_title = _DB_FIELDNAME_title.c_str();
+static const std::wstring _DB_FIELDNAME_artist = L"artist"; static const wchar_t *DB_FIELDNAME_artist = _DB_FIELDNAME_artist.c_str();
+static const std::wstring _DB_FIELDNAME_album = L"album"; static const wchar_t *DB_FIELDNAME_album = _DB_FIELDNAME_album.c_str();
+static const std::wstring _DB_FIELDNAME_year = L"year"; static const wchar_t *DB_FIELDNAME_year = _DB_FIELDNAME_year.c_str();
+static const std::wstring _DB_FIELDNAME_genre = L"genre"; static const wchar_t *DB_FIELDNAME_genre = _DB_FIELDNAME_genre.c_str();
+static const std::wstring _DB_FIELDNAME_comment = L"comment"; static const wchar_t *DB_FIELDNAME_comment = _DB_FIELDNAME_comment.c_str();
+static const std::wstring _DB_FIELDNAME_trackno = L"trackno"; static const wchar_t *DB_FIELDNAME_trackno = _DB_FIELDNAME_trackno.c_str();
+static const std::wstring _DB_FIELDNAME_length = L"length"; static const wchar_t *DB_FIELDNAME_length = _DB_FIELDNAME_length.c_str();
+static const std::wstring _DB_FIELDNAME_type = L"type"; static const wchar_t *DB_FIELDNAME_type = _DB_FIELDNAME_type.c_str();
+static const std::wstring _DB_FIELDNAME_lastupd = L"lastupd"; static const wchar_t *DB_FIELDNAME_lastupd = _DB_FIELDNAME_lastupd.c_str();
+static const std::wstring _DB_FIELDNAME_lastplay = L"lastplay"; static const wchar_t *DB_FIELDNAME_lastplay = _DB_FIELDNAME_lastplay.c_str();
+static const std::wstring _DB_FIELDNAME_rating = L"rating"; static const wchar_t *DB_FIELDNAME_rating = _DB_FIELDNAME_rating.c_str();
+static const std::wstring _DB_FIELDNAME_tuid2 = L"tuid2"; static const wchar_t *DB_FIELDNAME_tuid2 = _DB_FIELDNAME_tuid2.c_str();
+static const std::wstring _DB_FIELDNAME_playcount = L"playcount"; static const wchar_t *DB_FIELDNAME_playcount = _DB_FIELDNAME_playcount.c_str();
+static const std::wstring _DB_FIELDNAME_filetime = L"filetime"; static const wchar_t *DB_FIELDNAME_filetime = _DB_FIELDNAME_filetime.c_str();
+static const std::wstring _DB_FIELDNAME_filesize = L"filesize"; static const wchar_t *DB_FIELDNAME_filesize = _DB_FIELDNAME_filesize.c_str();
+static const std::wstring _DB_FIELDNAME_bitrate = L"bitrate"; static const wchar_t *DB_FIELDNAME_bitrate = _DB_FIELDNAME_bitrate.c_str();
+static const std::wstring _DB_FIELDNAME_disc = L"disc"; static const wchar_t *DB_FIELDNAME_disc = _DB_FIELDNAME_disc.c_str();
+static const std::wstring _DB_FIELDNAME_albumartist = L"albumartist"; static const wchar_t *DB_FIELDNAME_albumartist = _DB_FIELDNAME_albumartist.c_str();
+static const std::wstring _DB_FIELDNAME_replaygain_album_gain = L"replaygain_album_gain"; static const wchar_t *DB_FIELDNAME_replaygain_album_gain = _DB_FIELDNAME_replaygain_album_gain.c_str();
+static const std::wstring _DB_FIELDNAME_replaygain_track_gain = L"replaygain_track_gain"; static const wchar_t *DB_FIELDNAME_replaygain_track_gain = _DB_FIELDNAME_replaygain_track_gain.c_str();
+static const std::wstring _DB_FIELDNAME_publisher = L"publisher"; static const wchar_t *DB_FIELDNAME_publisher = _DB_FIELDNAME_publisher.c_str();
+static const std::wstring _DB_FIELDNAME_composer = L"composer"; static const wchar_t *DB_FIELDNAME_composer = _DB_FIELDNAME_composer.c_str();
+static const std::wstring _DB_FIELDNAME_bpm = L"bpm"; static const wchar_t *DB_FIELDNAME_bpm = _DB_FIELDNAME_bpm.c_str();
+static const std::wstring _DB_FIELDNAME_discs = L"discs"; static const wchar_t *DB_FIELDNAME_discs = _DB_FIELDNAME_discs.c_str();
+static const std::wstring _DB_FIELDNAME_tracks = L"tracks"; static const wchar_t *DB_FIELDNAME_tracks = _DB_FIELDNAME_tracks.c_str();
+static const std::wstring _DB_FIELDNAME_ispodcast = L"ispodcast"; static const wchar_t *DB_FIELDNAME_ispodcast = _DB_FIELDNAME_ispodcast.c_str();
+static const std::wstring _DB_FIELDNAME_podcastchannel = L"podcastchannel"; static const wchar_t *DB_FIELDNAME_podcastchannel = _DB_FIELDNAME_podcastchannel.c_str();
+static const std::wstring _DB_FIELDNAME_podcastpubdate = L"podcastpubdate"; static const wchar_t *DB_FIELDNAME_podcastpubdate = _DB_FIELDNAME_podcastpubdate.c_str();
+static const std::wstring _DB_FIELDNAME_GracenoteFileID = L"GracenoteFileID"; static const wchar_t *DB_FIELDNAME_GracenoteFileID = _DB_FIELDNAME_GracenoteFileID.c_str();
+static const std::wstring _DB_FIELDNAME_GracenoteExtData = L"GracenoteExtData"; static const wchar_t *DB_FIELDNAME_GracenoteExtData = _DB_FIELDNAME_GracenoteExtData.c_str();
+static const std::wstring _DB_FIELDNAME_lossless = L"lossless"; static const wchar_t *DB_FIELDNAME_lossless = _DB_FIELDNAME_lossless.c_str();
+static const std::wstring _DB_FIELDNAME_category = L"category"; static const wchar_t *DB_FIELDNAME_category = _DB_FIELDNAME_category.c_str();
+static const std::wstring _DB_FIELDNAME_codec = L"codec"; static const wchar_t *DB_FIELDNAME_codec = _DB_FIELDNAME_codec.c_str();
+static const std::wstring _DB_FIELDNAME_director = L"director"; static const wchar_t *DB_FIELDNAME_director = _DB_FIELDNAME_director.c_str();
+static const std::wstring _DB_FIELDNAME_producer = L"producer"; static const wchar_t *DB_FIELDNAME_producer = _DB_FIELDNAME_producer.c_str();
+static const std::wstring _DB_FIELDNAME_width = L"width"; static const wchar_t *DB_FIELDNAME_width = _DB_FIELDNAME_width.c_str();
+static const std::wstring _DB_FIELDNAME_height = L"height"; static const wchar_t *DB_FIELDNAME_height = _DB_FIELDNAME_height.c_str();
+static const std::wstring _DB_FIELDNAME_mimetype = L"mimetype"; static const wchar_t *DB_FIELDNAME_mimetype = _DB_FIELDNAME_mimetype.c_str();
+static const std::wstring _DB_FIELDNAME_dateadded = L"dateadded"; static const wchar_t *DB_FIELDNAME_dateadded = _DB_FIELDNAME_dateadded.c_str();
+
+
+extern BOOL myMenu;
+
+int init(void);
+void config(void);
+
+int treeGetParam(HTREEITEM h);
+
+class Table;
+class C_Config;
+extern CRITICAL_SECTION g_db_cs;
+extern nde_database_t g_db;
+extern nde_table_t g_table;
+extern int g_table_dirty;
+extern const wchar_t *WINAMP_INI;
+
+extern HWND m_curview_hwnd;
+
+extern wchar_t g_path[], g_tableDir[], g_viewsDir[];
+extern C_Config *g_config;
+
+extern HMENU g_context_menus, g_context_menus2;
+
+typedef struct
+{
+ wchar_t *name;
+ wchar_t *query;
+ wchar_t *metafn; //filename, without path, of meta file
+ int mode;
+ int imgIndex;
+ int index;
+} queryItem;
+
+typedef std::map <int, queryItem*> QueryList;
+extern QueryList m_query_list;
+extern C_Config *g_view_metaconf;
+extern int g_guessifany;
+extern int g_querydelay;
+extern int g_viewnotplay;
+
+void loadQueryTree();
+extern int m_query_tree;
+extern int m_query_mode;
+static wchar_t *m_query_metafile;
+HWND onTreeViewSelectChange(HWND hwnd);
+
+void db_setFieldStringW(nde_scanner_t s, unsigned char id, const wchar_t *data);
+void db_setFieldInt(nde_scanner_t s, unsigned char id, int data);
+void db_setFieldInt64(nde_scanner_t s, unsigned char id, __int64 data);
+int db_getFieldInt(nde_scanner_t s, unsigned char id, int defaultVal);
+void db_removeField(nde_scanner_t s, unsigned char id);
+
+void main_playQuery(C_Config *metaconf, const wchar_t *query, int enqueue, int startplaying=1); // enqueue =-1 sends it to the playlist
+void main_playItemRecordList (itemRecordListW *obj, int enqueue, int startplaying=1);
+int addQueryItem(const wchar_t *name, const wchar_t *val, int mode, int select, const wchar_t *metafn, int imageIndex, int num=-1);
+void replaceQueryItem(int n, const wchar_t *name, const wchar_t *val, int mode, int imageIndex);
+void saveQueryTree();
+
+int pluginHandleIpcMessage(int msg, int param);
+
+int openDb();
+void closeDb();
+
+void nukeLibrary(HWND hwndDlg);
+
+//add.cpp
+int addFileToDb(const wchar_t *filename, int onlyupdate, int use_metadata, int guess_mode, int playcnt=0, int lastplay=0, bool force=false); // adds a file to the db, gets info, etc.
+int RemoveFileFromDB(/*const Table *table, */const wchar_t *filename); // removes a file from the DB
+void makeFilename2(const char *filename, char *filename2, int filename2_len);
+void makeFilename2W(const wchar_t *filename, wchar_t *filename2, int filename2_len);
+
+//gracenote.cpp
+void gracenoteInit();
+int gracenoteQueryFile(const wchar_t *filename);
+void gracenoteCancelRequest();
+int gracenoteDoTimerStuff();
+void gracenoteSetValues(const wchar_t *artist, const wchar_t *album, const wchar_t *title);
+const wchar_t *gracenoteGetTuid();
+int gracenoteIsWorking();
+
+//guess.cpp
+wchar_t *guessTitles(const wchar_t *filename,
+ int *tracknum,
+ wchar_t **artist,
+ wchar_t **album,
+ wchar_t **title); // should free the result of this function after using artist/album/title
+
+//prefs.cpp
+INT_PTR CALLBACK PrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int autoscan_add_directory(const wchar_t *path, int *guess, int *meta, int *recurse, int noaddjustcheck);// if we return 1, guess and meta will be filled in
+void refreshPrefs(int screen);
+
+//util.cpp
+extern "C" {
+ void process_substantives(wchar_t* dest);
+ void ConvertRatingMenuStar(HMENU menu, UINT menu_id);
+};
+
+//view_audio.cpp
+INT_PTR CALLBACK view_audioDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+//view_miniinfo.cpp
+INT_PTR CALLBACK view_miniinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+//view_errorinfo.cpp
+INT_PTR CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+/* bgscan.cpp */
+void Scan_ScanFolderBackground(const wchar_t *path, int guess, int meta, int recurse);
+void Scan_ScanFolder(HWND parent, const wchar_t *path, int guess, int meta, int recurse);
+// When you call Scan_ScanFolders, it will own the memory and release it with free()
+void Scan_ScanFolders(HWND parent, size_t count, wchar_t **paths, int *guess, int *meta, int *recurse);
+void Scan_BackgroundScan();
+void Scan_Cancel();
+void Scan_Kill();
+// remove missing files
+void Scan_RemoveFiles(HWND parent);
+
+//view_media.cpp
+void makeQueryStringFromText(GayStringW *query, wchar_t *text, int nf=8);
+inline BOOL WINAPI IsCharSpaceA(char c) { return (c == ' ' || c == '\t'); }
+inline BOOL WINAPI IsCharSpaceW(wchar_t c) { return (c == L' ' || c == L'\t'); }
+inline bool IsThe(const char *str) { if (str && (str[0] == 't' || str[0] == 'T') && (str[1] == 'h' || str[1] == 'H') && (str[2] == 'e' || str[2] == 'E') && (str[3] == ' ')) return true; else return false; }
+__forceinline static bool IsTheW(const wchar_t *str)
+{
+ if ((str[0] & ~0x20) == L'T'
+ && (str[1] & ~0x20) == L'H'
+ && (str[2] & ~0x20) == L'E'
+ && str[3] == L' ')
+ return true;
+ else
+ return false;
+}
+#define SKIP_THE_AND_WHITESPACE(x) { char *save##x=(char*)x; while (IsCharSpaceA(*x) && *x) x++; if (IsThe(x)) x+=4; while (IsCharSpaceA(*x)) x++; if (!*x) x=save##x; }
+#define SKIP_THE_AND_WHITESPACEW(x) { wchar_t *save##x=(wchar_t*)x; while ((*x == L' ' || *x == L'\t') && *x) x++; if (IsTheW(x)) x+=4; while ((*x == L' ' || *x == L'\t')) x++; if (!*x) x=save##x; }
+//wherever this goes is fine
+
+#define UPDATE_QUERY_TIMER_ID 505
+#define UPDATE_RESULT_LIST_TIMER_ID 506
+INT_PTR CALLBACK view_mediaDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+int WCSCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+
+typedef void (*resultsniff_funcW)(itemRecordW *items, int numitems, int user32, int *killswitch);
+
+void bgQuery_Stop();
+extern nde_scanner_t m_media_scanner;
+typedef std::vector<wchar_t*> CloudFiles;
+
+// returns length in seconds (high bit on means could be much more), or -1 if killed
+int saveQueryToListW(C_Config *viewconf, nde_scanner_t s, itemRecordListW *obj,
+ CloudFiles *uploaded, CloudFiles *uploading,
+ resultsniff_funcW cb=0, int user32=0, int *killswitch=0, __int64 *total_bytes=0);
+
+// queries.cpp
+void view_queryContextMenu(INT_PTR param1, HWND hHost, POINTS pts, int item);
+void queriesContextMenu(INT_PTR param1, HWND hHost, POINTS pts);
+void queryEditItem(int n);
+void addNewQuery(HWND parent);
+void queryDeleteItem(HWND parent, int n);
+BOOL windowOffScreen(HWND hwnd, POINT pt);
+
+// handleMessage.cpp
+INT_PTR HandleIpcMessage(INT_PTR msg, INT_PTR param);
+extern "C" extern int (*warand)(void);
+
+extern WNDPROC wa_oldWndProc;
+
+wchar_t *itemrecordWTagFunc(wchar_t * tag, void * p);
+wchar_t *fieldTagFunc(wchar_t * tag, void * p); //return 0 if not found
+void ndeTagFuncFree(wchar_t * tag, void * p); // for NDE strings
+DWORD doGuessProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam );
+void TAG_FMT_EXT(const wchar_t *filename, void *f, void *ff, void *p, wchar_t *out, int out_len, int extended);
+extern int asked_for_playcount;
+LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+extern int m_calling_getfileinfo;
+extern HMENU wa_play_menu ;
+void add_pledit_to_library();
+void add_to_library(HWND wndparent);
+extern int g_bgscan_scanning, g_bgrescan_force, g_bgrescan_do, g_bgrescan_int;
+extern WNDPROC ml_oldWndProc;
+extern time_t g_bgscan_last_rescan;
+int runBGscan(int ms);
+void compactRecordList(itemRecordListW *obj);
+
+LRESULT APIENTRY ml_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+int FLOATCMP_NULLOK(const char *pa, const char *pb);
+int FLOATWCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+void ClearTitleHookCache();
+
+int FindFileInDatabase(nde_scanner_t s, int fieldId, const wchar_t *filename, wchar_t alternate[MAX_PATH]);
+
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordListW *obj, bool compat);
+__int64 ScannerRefToObjCacheNFNW(nde_scanner_t s, itemRecordW *obj, bool compat);
+int updateFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *data);
+void sortResults(C_Config *viewconf, itemRecordListW *obj);
+void queryStrEscape(const char *raw, GayString &str);
+void queryStrEscape(const wchar_t *raw, GayStringW &str);
+void ParseIntSlashInt(wchar_t *string, int *part, int *parts);
+
+HWND updateCurrentView(HWND hwndDlg);
+
+#define MEDIAVIEW_COL_ARTIST 0
+#define MEDIAVIEW_COL_TITLE 1
+#define MEDIAVIEW_COL_ALBUM 2
+#define MEDIAVIEW_COL_LENGTH 3
+#define MEDIAVIEW_COL_TRACK 4
+#define MEDIAVIEW_COL_GENRE 5
+#define MEDIAVIEW_COL_YEAR 6
+#define MEDIAVIEW_COL_FILENAME 7
+#define MEDIAVIEW_COL_RATING 8
+#define MEDIAVIEW_COL_PLAYCOUNT 9
+#define MEDIAVIEW_COL_LASTPLAY 10
+#define MEDIAVIEW_COL_LASTUPD 11
+#define MEDIAVIEW_COL_FILETIME 12
+#define MEDIAVIEW_COL_COMMENT 13
+#define MEDIAVIEW_COL_FILESIZE 14
+#define MEDIAVIEW_COL_BITRATE 15
+#define MEDIAVIEW_COL_TYPE 16
+#define MEDIAVIEW_COL_DISC 17
+#define MEDIAVIEW_COL_ALBUMARTIST 18
+#define MEDIAVIEW_COL_FULLPATH 19
+#define MEDIAVIEW_COL_ALBUMGAIN 20
+#define MEDIAVIEW_COL_TRACKGAIN 21
+#define MEDIAVIEW_COL_PUBLISHER 22
+#define MEDIAVIEW_COL_COMPOSER 23
+#define MEDIAVIEW_COL_EXTENSION 24
+#define MEDIAVIEW_COL_ISPODCAST 25
+#define MEDIAVIEW_COL_PODCASTCHANNEL 26
+#define MEDIAVIEW_COL_PODCASTPUBDATE 27
+#define MEDIAVIEW_COL_BPM 28
+#define MEDIAVIEW_COL_CATEGORY 29
+#define MEDIAVIEW_COL_DIRECTOR 30
+#define MEDIAVIEW_COL_PRODUCER 31
+#define MEDIAVIEW_COL_DIMENSION 32
+#define MEDIAVIEW_COL_DATEADDED 33
+#define MEDIAVIEW_COL_CLOUD 34
+
+#define MEDIAVIEW_COL_NUMS 35 // number of columns
+#endif // ML_LOCAL_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.rc b/Src/Plugins/Library/ml_local/ml_local.rc
new file mode 100644
index 00000000..0391a01a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.rc
@@ -0,0 +1,1244 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_VIEW_MEDIA DIALOGEX 0, 0, 289, 274
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Search:",IDC_SEARCHCAPTION,1,2,25,8,SS_CENTERIMAGE
+ EDITTEXT IDC_QUICKSEARCH,28,1,209,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_CLEAR,"Button",BS_OWNERDRAW | WS_TABSTOP,240,0,49,11
+ CONTROL "List4",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,14,289,246
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,35,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,38,261,35,11
+ CONTROL "MusicIP Mix",IDC_BUTTON_MIX,"Button",BS_OWNERDRAW | BS_CENTER | WS_TABSTOP,76,261,47,11
+ CONTROL "Generate Playlist",IDC_BUTTON_CREATEPLAYLIST,"Button",BS_OWNERDRAW | WS_TABSTOP,126,261,65,11
+ CONTROL "",IDC_MEDIASTATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,187,262,61,8
+ CONTROL "",IDC_MIXABLE,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,223,262,25,8
+ CONTROL "Show Info",IDC_BUTTON_INFOTOGGLE,"Button",BS_OWNERDRAW | WS_TABSTOP,249,261,40,11
+END
+
+IDD_VIEW_AUDIO DIALOGEX 0, 0, 291, 279
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "",IDC_BUTTON_ARTMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,0,1,14,11
+ CONTROL "",IDC_BUTTON_VIEWMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,16,1,20,11
+ CONTROL "",IDC_BUTTON_COLUMNS,"Button",BS_OWNERDRAW | WS_TABSTOP,37,1,20,11
+ EDITTEXT IDC_QUICKSEARCH,92,1,145,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_CLEAR,"Button",BS_OWNERDRAW | WS_TABSTOP,240,0,49,11
+ CONTROL "List1",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,14,83,89
+ CONTROL "List3",IDC_LIST2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,89,14,100,89
+ CONTROL "",IDC_LIST3,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,195,14,94,89
+ LTEXT "Search:",IDC_SEARCHCAPTION,64,2,25,8
+ CONTROL "",IDC_DIV1,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,82,14,6,91
+ CONTROL "",IDC_HDELIM,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,0,106,289,6
+ CONTROL "",IDC_DIV2,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,188,14,6,91
+END
+
+IDD_PREFS1 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Library Display Settings",IDC_STATIC,4,3,256,40
+ CONTROL "Display 'Show Info' in media and album views",IDC_CHECK3,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,15,162,10
+ CONTROL "Remember search filters in media and album views",IDC_REMEMBER_SEARCH,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,28,178,10
+ GROUPBOX "Recently Played",IDC_STATIC,4,46,256,57
+ CONTROL "Enable 'Recently Played' in the Library",IDC_CHECK2,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,58,159,10
+ PUSHBUTTON "Info...",IDC_BUTTON2,218,58,36,13
+ CONTROL "Wait",IDC_CHECK7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,72,30,8
+ EDITTEXT IDC_EDIT2,48,70,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "seconds before tracking items",IDC_STATIC4,76,72,101,8
+ CONTROL "Wait",IDC_CHECK8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,88,30,8
+ EDITTEXT IDC_EDIT3,48,86,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "percent of playback before tracking items",IDC_STATIC5,76,88,137,8
+ GROUPBOX "Advanced Library Preferences",IDC_STATIC,4,107,256,82
+ CONTROL "Use Library title information for Playlist Item Formatting",IDC_CHECK_ATF,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,120,193,10
+ CONTROL "Display 'Refine' search field in album views",IDC_CHECK5,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,132,151,10
+ CONTROL "Do not load the Library database at Winamp start-up",IDC_CHECK6,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,145,206,10
+ CONTROL "Use Artist as Album Artist if not available",IDC_ARTIST_AS_ALBUMARTIST,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,158,146,10
+ LTEXT "Search query delay:",IDC_STATIC_QUERYDELAY,10,173,71,10
+ EDITTEXT IDC_EDIT_QUERYDELAY,78,171,27,13,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "ms (default: 250)",IDC_STATIC,109,173,104,10
+ GROUPBOX "Clear Library",IDC_STATIC,4,190,256,35
+ LTEXT "Use this to remove all items from your Winamp Library. This will not physically remove any files from disk.",IDC_STATIC,10,202,176,18
+ PUSHBUTTON "Clear Library...",IDC_BUTTON1,190,202,64,18
+END
+
+IDD_VIEW_DB_ERROR DIALOGEX 0, 0, 194, 166
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CTEXT "",IDC_DB_ERROR,10,10,174,129,0x2000
+ CONTROL "Reset Database",IDC_RESET_DB_ON_ERROR,"Button",BS_OWNERDRAW | WS_TABSTOP,61,144,70,12
+END
+
+IDD_PREFSFR DIALOGEX 0, 0, 272, 246
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Tab1",IDC_TAB1,"SysTabControl32",WS_TABSTOP,0,0,271,246
+END
+
+IDD_VIEW_MINIINFO DIALOGEX 0, 0, 291, 279
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+END
+
+IDD_PREFS3 DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Watch Folder Settings",IDC_STATIC,4,3,256,187
+ LTEXT "New media found in the following folders will be automatically added to the Library:",IDC_STATIC,10,14,243,16
+ CONTROL "List1",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,10,34,242,74
+ PUSHBUTTON "Add folder...",IDC_BUTTON1,10,111,60,13
+ PUSHBUTTON "Edit selected",IDC_BUTTON4,74,111,53,13
+ PUSHBUTTON "Remove selected",IDC_BUTTON3,188,111,64,13
+ CONTROL "Automatically add played files",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,128,107,10
+ CONTROL "Rescan folders at startup",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,140,95,10
+ CONTROL "Automatically remove missing files",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,152,119,10
+ CONTROL "Rescan folders every",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,164,80,11
+ EDITTEXT IDC_EDIT3,92,164,32,12,ES_AUTOHSCROLL | ES_NUMBER
+ CONTROL "",IDC_SPIN1,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,116,164,7,12
+ LTEXT "minutes",IDC_MINUTES,127,165,26,9,SS_CENTERIMAGE
+ PUSHBUTTON "Rescan now",IDC_RESCAN,188,163,64,13
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_PATHELLIPSIS | WS_GROUP,9,177,244,8
+ GROUPBOX "Metadata Reading Settings",IDC_STATIC,4,192,256,33
+ LTEXT "Click ""Configure"" to modify how title information is read when importing media files",IDC_STATIC,11,203,171,16
+ PUSHBUTTON "Configure",IDC_CONFMETA,188,205,64,13
+END
+
+IDD_EDITDIR DIALOGEX 0, 0, 262, 146
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Edit folder properties"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Folder path:",IDC_STATIC,7,8,38,8
+ EDITTEXT IDC_EDIT1,7,17,198,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BUTTON1,208,17,43,14
+ CONTROL "Add subfolders",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,35,63,10
+ LTEXT "Metadata / filename detection override options:",IDC_STATIC,7,46,165,8
+ CONTROL "Read file metadata tags if available (grey is default)",IDC_CHECK1,
+ "Button",BS_AUTO3STATE | WS_TABSTOP,24,56,197,10
+ GROUPBOX "Filename metadata detection mode (when no tags available):",IDC_STATIC,24,66,231,54
+ CONTROL "Default",IDC_RADIO6,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,30,76,39,10
+ CONTROL "Smart (Detects ""Artist - Album\\TrackNum - Artist - Title"" etc)",IDC_RADIO1,
+ "Button",BS_AUTORADIOBUTTON,30,86,207,10
+ CONTROL "Simple (Detects ""Artist\\Album\\Title"")",IDC_RADIO2,
+ "Button",BS_AUTORADIOBUTTON,30,96,137,10
+ CONTROL "No guessing (title is filename, other fields empty)",IDC_RADIO8,
+ "Button",BS_AUTORADIOBUTTON,30,105,165,10
+ DEFPUSHBUTTON "OK",IDOK,7,125,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,64,125,50,14
+END
+
+IDD_EDIT_QUERY DIALOGEX 0, 0, 322, 151
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Query Builder"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Builder",IDC_STATIC_GENERAL,7,7,308,102
+ GROUPBOX "Field :",IDC_STATIC,13,16,181,70
+ LISTBOX IDC_LIST_FIELDS,20,26,52,55,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Equal",IDC_RADIO_EQUAL,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,75,26,34,8
+ CONTROL "Above",IDC_RADIO_ABOVE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,34,37,8
+ CONTROL "Below",IDC_RADIO_BELOW,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,42,35,8
+ CONTROL "Above or Equal",IDC_RADIO_ABOVEOREQUAL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,50,61,8
+ CONTROL "Below or Equal",IDC_RADIO_BELOWOREQUAL,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,75,58,61,8
+ CONTROL "Is not set",IDC_RADIO_ISEMPTY,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,141,25,44,8
+ CONTROL "Is like",IDC_RADIO_ISLIKE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,141,33,34,8
+ CONTROL "Begins with",IDC_RADIO_BEGINS,"Button",BS_AUTORADIOBUTTON,141,41,52,8
+ CONTROL "Ends in",IDC_RADIO_ENDS,"Button",BS_AUTORADIOBUTTON,141,49,39,8
+ CONTROL "Contains",IDC_RADIO_CONTAINS,"Button",BS_AUTORADIOBUTTON,141,57,39,8
+ CONTROL "Not",IDC_CHECK_NOT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,76,71,27,10
+ GROUPBOX "Compare to string",IDC_STATIC_STRING,198,16,112,29
+ EDITTEXT IDC_EDIT_STRING,202,27,104,13,ES_AUTOHSCROLL
+ GROUPBOX "Compare to date/time",IDC_STATIC_DATETIME,198,47,112,40
+ EDITTEXT IDC_EDIT_DATETIME,202,58,78,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit",IDC_BUTTON_EDITDATETIME,284,58,22,13
+ CTEXT "N/A",IDC_STATIC_CURDATETIME,202,74,104,9,SS_SUNKEN
+ LTEXT "Resulting expression :",IDC_STATIC,14,93,70,8
+ EDITTEXT IDC_EDIT_RESULT,85,90,180,14,ES_AUTOHSCROLL | ES_READONLY
+ PUSHBUTTON ">>",ID_BUTTON_SENDTOQUERY,268,90,41,14
+ PUSHBUTTON "AND",IDC_BUTTON_AND,268,90,19,14
+ PUSHBUTTON "OR",IDC_BUTTON_OR,290,90,19,14
+ LTEXT "Query :",IDC_STATIC_QUERY,7,115,24,8
+ EDITTEXT IDC_EDIT_QUERY,33,112,282,14,ES_AUTOHSCROLL
+ DEFPUSHBUTTON "OK",IDOK,211,130,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,265,130,50,14
+END
+
+IDD_EDIT_QUERY_PICK DIALOGEX 0, 0, 178, 78
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Pick date/time"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ CONTROL "DateTimePicker2",IDC_DATETIMEPICKER,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_SHOWNONE | DTS_LONGDATEFORMAT | WS_TABSTOP,20,13,145,15
+ CONTROL "DateTimePicker1",IDC_DATETIMEPICKER1,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_UPDOWN | DTS_SHOWNONE | WS_TABSTOP | 0x8,43,32,105,15
+ DEFPUSHBUTTON "OK",IDOK,67,57,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,121,57,50,14
+END
+
+IDD_CUSTCOLUMNS DIALOGEX 0, 0, 342, 154
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Customize columns"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Hidden Columns",IDC_STATIC,7,7,126,120
+ LISTBOX IDC_LIST2,13,17,114,104,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ DEFPUSHBUTTON "OK",IDOK,7,133,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,62,133,50,14
+ PUSHBUTTON "Add -->",IDC_BUTTON2,138,41,66,14,WS_DISABLED
+ PUSHBUTTON "<-- Remove",IDC_BUTTON3,138,59,66,14,WS_DISABLED
+ PUSHBUTTON "Restore Defaults",IDC_DEFS,138,107,66,14
+ GROUPBOX "Visible Columns",IDC_STATIC,208,7,126,140
+ LISTBOX IDC_LIST1,214,17,114,110,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Move up",IDC_BUTTON4,239,130,43,12,WS_DISABLED
+ PUSHBUTTON "Move down",IDC_BUTTON5,285,130,43,12,WS_DISABLED
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_NDE_RECOVERY$(DISABLED) DIALOGEX 0, 0, 261, 107
+#else
+IDD_NDE_RECOVERY DIALOGEX 0, 0, 261, 107
+#endif
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION
+CAPTION "Winamp5 Library Recovery"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "Inconsistencies were detected while loading your Library database, we are now trying to correct them.",IDC_STATIC,13,8,235,18
+ CONTROL "Progress1",IDC_PROGRESS_PERCENT,"msctls_progress32",WS_BORDER,23,32,214,14
+ CTEXT "0%",IDC_STATIC_PERCENT,116,47,30,8
+ LTEXT "Total number of records:",IDC_STATIC,23,61,78,8
+ LTEXT "0",IDC_STATIC_TOTAL,106,61,47,8
+ LTEXT "Records recovered:",IDC_STATIC,23,72,64,8
+ LTEXT "0",IDC_STATIC_RECOVERED,106,72,47,8
+ LTEXT "Records lost:",IDC_STATIC,24,83,42,8
+ LTEXT "0",IDC_STATIC_LOST,106,83,47,8
+ DEFPUSHBUTTON "OK",IDOK,204,86,50,14
+END
+#endif
+
+IDD_NEEDADDFILES DIALOGEX 0, 0, 201, 105
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Add Media to Library"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ LTEXT "You currently have nothing in your library. To add media, select from the options below.",IDC_TEXT,7,7,187,17
+ PUSHBUTTON "Add Media to Library",ID_ADD_FILES,52,31,96,14
+ PUSHBUTTON "Import from iTunes",IDC_IMPORT_ITUNES,52,49,96,14,WS_DISABLED
+ CONTROL "Do not show me this again",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,70,99,10
+ PUSHBUTTON "Close",IDOK,7,85,50,13
+ CONTROL "Learn More About Winamp",IDC_BTN_LINK_PROMO,"Button",BS_OWNERDRAW | BS_RIGHT | BS_BOTTOM | WS_TABSTOP,73,85,121,13
+END
+
+IDD_SCROLLCHILD DIALOGEX 0, 0, 310, 17
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ PUSHBUTTON "+",IDC_BUTTON1,1,2,13,11
+END
+
+IDD_SCROLLCHILDHOST DIALOGEX 0, 0, 310, 101
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+END
+
+IDD_SCROLLCHILDFILTER DIALOGEX 0, 0, 310, 24
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPCHILDREN
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ PUSHBUTTON "-",IDC_BUTTON1,1,2,13,12
+ COMBOBOX IDC_COMBO1,17,2,72,183,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ COMBOBOX IDC_COMBO2,92,2,80,172,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ EDITTEXT IDC_EDIT1,173,2,128,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Choose Time",IDC_BUTTON2,251,1,50,12,NOT WS_VISIBLE
+ PUSHBUTTON "Query Builder",IDC_BUTTON_QUERYBUILD,251,1,50,12,NOT WS_VISIBLE
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDHORZ,1,19,12,1
+ CONTROL "and",IDC_AND,"Button",BS_AUTORADIOBUTTON,14,15,27,10,WS_EX_TRANSPARENT
+ CONTROL "or",IDC_OR,"Button",BS_AUTORADIOBUTTON,41,15,23,10,WS_EX_TRANSPARENT
+ CONTROL "",IDC_STATIC,"Static",SS_ETCHEDHORZ,63,19,240,1
+END
+
+IDD_TIMEEDITOR DIALOGEX 0, 0, 281, 229
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Choose Time"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "",IDC_STATIC_ABSOLUTE,7,7,267,37
+ CONTROL "Absolute",IDC_CHECK_ABSOLUTE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,13,7,42,10,WS_EX_TRANSPARENT
+ CONTROL "DateTimePicker2",IDC_DATETIMEPICKER,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_SHOWNONE | WS_TABSTOP,13,20,145,15
+ CONTROL "DateTimePicker1",IDC_DATETIMEPICKER1,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_UPDOWN | DTS_SHOWNONE | WS_TABSTOP | 0x8,164,20,105,15
+ GROUPBOX "",IDC_STATIC_RELATIVE,7,46,267,141
+ CONTROL "Relative",IDC_CHECK_RELATIVE,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,13,46,40,8,WS_EX_TRANSPARENT
+ GROUPBOX "",IDC_STATIC_TIMEAGO,13,55,255,32,WS_GROUP
+ CONTROL "Time ago",IDC_CHECK_TIMEAGO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,55,43,10,WS_EX_TRANSPARENT
+ EDITTEXT IDC_EDIT_TIMEAGO,19,66,29,14,ES_AUTOHSCROLL
+ CONTROL "Year(s)",IDC_RADIO_TIMEAGO_Y,"Button",BS_AUTORADIOBUTTON | WS_GROUP,53,65,37,8
+ CONTROL "Hour(s)",IDC_RADIO_TIMEAGO_H,"Button",BS_AUTORADIOBUTTON,53,74,37,8
+ CONTROL "Month(s)",IDC_RADIO_TIMEAGO_M,"Button",BS_AUTORADIOBUTTON,92,65,45,8
+ CONTROL "Minute(s)",IDC_RADIO_TIMEAGO_MIN,"Button",BS_AUTORADIOBUTTON,92,74,46,8
+ CONTROL "Week(s)",IDC_RADIO_TIMEAGO_W,"Button",BS_AUTORADIOBUTTON,137,65,41,8
+ CONTROL "Second(s)",IDC_RADIO_TIMEAGO_S,"Button",BS_AUTORADIOBUTTON,137,74,52,8
+ CONTROL "Day(s)",IDC_RADIO_TIMEAGO_D,"Button",BS_AUTORADIOBUTTON,180,65,33,8
+ GROUPBOX "From Origin",IDC_STATIC_DIRECTION,215,55,53,32
+ CONTROL "After",IDC_RADIO_AFTER,"Button",BS_AUTORADIOBUTTON | WS_GROUP,221,65,31,8
+ CONTROL "Before",IDC_RADIO_BEFORE,"Button",BS_AUTORADIOBUTTON,221,74,34,8
+ GROUPBOX "",IDC_STATIC_SELECTIVE,13,90,255,79,WS_GROUP
+ CONTROL "Origin",IDC_CHECK_SELECTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,90,32,10,WS_EX_TRANSPARENT
+ GROUPBOX "",IDC_STATIC_YEAR,17,98,79,34,WS_GROUP
+ CONTROL "This year",IDC_RADIO_THISYEAR,"Button",BS_AUTORADIOBUTTON | WS_GROUP,23,106,45,9
+ CONTROL "Year :",IDC_RADIO_YEAR,"Button",BS_AUTORADIOBUTTON,23,116,30,10
+ EDITTEXT IDC_EDIT_YEAR,56,116,36,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_STATIC_MONTH,98,98,88,34
+ CONTROL "This month",IDC_RADIO_THISMONTH,"Button",BS_AUTORADIOBUTTON | WS_GROUP,104,106,51,9
+ CONTROL "Month :",IDC_RADIO_MONTH,"Button",BS_AUTORADIOBUTTON,104,116,35,10
+ PUSHBUTTON "September",IDC_BUTTON_MONTH,143,116,40,11
+ GROUPBOX "",IDC_STATIC_DAY,188,98,75,34,WS_GROUP
+ CONTROL "This day",IDC_RADIO_THISDAY,"Button",BS_AUTORADIOBUTTON | WS_GROUP,194,106,43,9
+ CONTROL "Day :",IDC_RADIO_DAY,"Button",BS_AUTORADIOBUTTON,194,116,28,10
+ EDITTEXT IDC_EDIT_DAY,224,116,36,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_STATIC_TIME,17,130,191,34,WS_GROUP
+ CONTROL "This time",IDC_RADIO_THISTIME,"Button",BS_AUTORADIOBUTTON | WS_GROUP,23,138,45,9
+ CONTROL "Noon",IDC_RADIO_NOON,"Button",BS_AUTORADIOBUTTON,23,148,33,10
+ CONTROL "Midnight",IDC_RADIO_MIDNIGHT,"Button",BS_AUTORADIOBUTTON,84,138,39,10
+ CONTROL "Time :",IDC_RADIO_TIME,"Button",BS_AUTORADIOBUTTON,84,148,32,10
+ CONTROL "DateTimePicker1",IDC_DATETIMEPICKER2,"SysDateTimePick32",DTS_RIGHTALIGN | DTS_UPDOWN | WS_TABSTOP | 0x8,131,146,72,13
+ PUSHBUTTON "Now",IDC_BUTTON_NOW,212,135,51,13
+ PUSHBUTTON "Pick",IDC_BUTTON_PICK,212,150,51,13
+ LTEXT "Resulting date/time :",IDC_STATIC_RESULT,14,172,66,8
+ CTEXT "N/A",IDC_STATIC_QUERYTIME,85,172,183,10,SS_SUNKEN
+ LTEXT "Formatted date/time :",IDC_STATIC,7,191,68,8
+ EDITTEXT IDC_EDIT_RESULT,77,189,197,12,ES_AUTOHSCROLL | ES_READONLY
+ DEFPUSHBUTTON "OK",IDOK,169,206,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,224,206,50,14
+END
+
+IDD_PREFS_METADATA DIALOGEX 0, 0, 278, 86
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Metadata Reading Settings"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Read media information on import if available",IDC_CHECK1,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,5,197,10
+ LTEXT "Use the following detection logic when",IDC_STATIC1,16,20,122,8
+ COMBOBOX IDC_COMBO1,140,18,31,142,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "media information is missing:",IDC_STATIC2,174,20,97,8
+ CONTROL "Smart (Detects ""Artist - Album\\TrackNum - Artist - Title"" etc)",IDC_RADIO1,
+ "Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,19,32,217,10
+ CONTROL "Simple (Detects ""Artist\\Album\\Title"")",IDC_RADIO2,
+ "Button",BS_AUTORADIOBUTTON,19,43,189,10
+ CONTROL "No guessing (title is filename, other fields empty)",IDC_RADIO3,
+ "Button",BS_AUTORADIOBUTTON,19,54,211,10
+ DEFPUSHBUTTON "OK",IDOK,167,68,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,221,68,50,13
+END
+
+IDD_MONITOR_SMALL DIALOGEX 0, 0, 146, 35
+STYLE DS_SETFONT | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP
+EXSTYLE WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+END
+
+IDD_MONITOR_HIDE DIALOGEX 0, 0, 285, 54
+STYLE DS_SETFONT | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Confirmation Dialog"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Are you sure you want to hide local media monitor window?\n This will not stop monitor.",IDC_STATIC,7,7,271,22
+ DEFPUSHBUTTON "Yes",IDOK,174,34,50,13
+ PUSHBUTTON "No",IDCANCEL,228,34,50,13
+END
+
+IDD_REFRESH_METADATA DIALOGEX 0, 0, 249, 28
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Reading Metadata"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Progress:",IDC_STATIC,7,7,32,8
+ LTEXT "",IDC_REFRESHMETADATA_STATUS,52,7,133,8
+ PUSHBUTTON "Cancel",IDCANCEL,192,7,50,14
+END
+
+IDD_REINDEX DIALOGEX 0, 0, 269, 30
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION
+CAPTION "Reindexing Library"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,7,7,255,16
+END
+
+IDD_ADD_VIEW_2 DIALOGEX 0, 0, 332, 255
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Smart View"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Choose a Preset:",IDC_STATIC,5,8,56,9,SS_CENTERIMAGE
+ COMBOBOX IDC_COMBO_PRESETS,70,7,151,162,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Advanced mode >>",IDC_BUTTON_MODE,247,7,80,13
+ GROUPBOX "",IDC_STATIC,5,21,322,154
+ CONTROL "",IDC_CHILDFRAME,"Static",SS_BLACKRECT | NOT WS_VISIBLE,11,30,310,139
+ CONTROL "Don't show me this again",IDC_CHECK_SHOWINFO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,111,95,10
+ GROUPBOX "",IDC_STATIC,5,176,322,43
+ LTEXT "Filters:",IDC_STATIC_FILTER,17,187,23,9,SS_CENTERIMAGE
+ CONTROL "",IDC_RADIO_SIMPLE,"Button",BS_AUTORADIOBUTTON | WS_GROUP,46,187,10,10,WS_EX_TRANSPARENT
+ CONTROL 157,IDC_IMAGE_SIMPLE,"Static",SS_BITMAP | SS_NOTIFY,56,187,11,10,WS_EX_TRANSPARENT
+ LTEXT "Simple",IDC_STATIC_SIMPLE,69,188,21,8,SS_NOTIFY
+ CONTROL "",IDC_RADIO_SIMPLEALBUM,"Button",BS_AUTORADIOBUTTON,105,187,10,10,WS_EX_TRANSPARENT
+ CONTROL 160,IDC_IMAGE_SIMPLEALBUM,"Static",SS_BITMAP | SS_NOTIFY,115,187,11,10
+ LTEXT "Simple Album",IDC_STATIC_SIMPLEALBUM,129,188,42,8,SS_NOTIFY
+ CONTROL "",IDC_RADIO_TWOFILTERS,"Button",BS_AUTORADIOBUTTON,181,187,10,8,WS_EX_TRANSPARENT
+ CONTROL 159,IDC_IMAGE_TWOFILTERS,"Static",SS_BITMAP | SS_NOTIFY,191,187,11,10
+ LTEXT "Two",IDC_STATIC_TWOFILTERS,205,188,14,8,SS_NOTIFY
+ CONTROL "",IDC_RADIO_THREEFILTERS,"Button",BS_AUTORADIOBUTTON,233,187,10,8,WS_EX_TRANSPARENT
+ CONTROL 158,IDC_IMAGE_THREEFILTERS,"Static",SS_BITMAP | SS_NOTIFY,243,187,11,10
+ LTEXT "Three",IDC_STATIC_THREEFILTERS,257,188,20,8,SS_NOTIFY
+ COMBOBOX IDC_COMBO_FILTER1,46,200,80,87,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER2,127,202,8,8
+ COMBOBOX IDC_COMBO_FILTER2,131,200,80,87,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER3,212,202,8,8
+ COMBOBOX IDC_COMBO_FILTER3,216,200,80,87,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Name:",IDC_STATIC,5,225,19,9,SS_CENTERIMAGE
+ EDITTEXT IDC_NAME,33,223,130,13,ES_AUTOHSCROLL
+ CONTROL "Hide extra info pane",IDC_HIDE_EXTINFO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,240,223,81,10
+ PUSHBUTTON "OK",IDOK,223,237,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,277,237,50,13
+ LTEXT "",IDC_STATIC_INFO,11,30,310,75
+END
+
+IDD_ADD_VIEW_2_NF DIALOGEX 0, 0, 332, 197
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "New Smart View"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Choose a Preset:",-1,5,8,56,9,SS_CENTERIMAGE
+ COMBOBOX IDC_COMBO_PRESETS,70,7,151,162,CBS_DROPDOWN | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "",-1,5,21,322,154
+ CONTROL "",IDC_CHILDFRAME,"Static",SS_BLACKRECT | NOT WS_VISIBLE,11,30,310,139
+ CONTROL "Don't show me this again",IDC_CHECK_SHOWINFO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,111,95,10
+ LTEXT "Name:",-1,5,181,25,9,SS_CENTERIMAGE
+ EDITTEXT IDC_NAME,33,179,130,13,ES_AUTOHSCROLL
+ PUSHBUTTON "OK",IDOK,223,179,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,277,179,50,13
+ LTEXT "",IDC_STATIC_INFO,11,30,310,75
+ PUSHBUTTON "Advanced mode >>",IDC_BUTTON_MODE,247,7,80,13
+END
+
+IDD_ADD_VIEW_CHILD_ADVANCED DIALOGEX 0, 0, 310, 138
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_VISIBLE | WS_SYSMENU
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Query:",IDC_STATIC,0,2,22,9,SS_CENTERIMAGE
+ EDITTEXT IDC_QUERY,25,1,236,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Query Builder",IDC_EDIT,262,1,47,12
+ GROUPBOX "Query Language Documentation",IDC_STATIC,0,17,310,121
+ EDITTEXT IDC_EDIT1,6,28,297,104,ES_MULTILINE | ES_READONLY | WS_VSCROLL
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_VIEW_AUDIO, DIALOG
+ BEGIN
+ RIGHTMARGIN, 289
+ END
+
+ IDD_PREFS1, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 259
+ END
+
+ IDD_VIEW_DB_ERROR, DIALOG
+ BEGIN
+ LEFTMARGIN, 10
+ RIGHTMARGIN, 184
+ TOPMARGIN, 10
+ BOTTOMMARGIN, 156
+ END
+
+ IDD_PREFSFR, DIALOG
+ BEGIN
+ RIGHTMARGIN, 191
+ BOTTOMMARGIN, 191
+ END
+
+ IDD_EDITDIR, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 255
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 139
+ END
+
+ IDD_EDIT_QUERY, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 315
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 144
+ END
+
+ IDD_EDIT_QUERY_PICK, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 171
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 71
+ END
+
+ IDD_CUSTCOLUMNS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 335
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 147
+ END
+
+ "IDD_NDE_RECOVERY$(DISABLED)", DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 254
+ TOPMARGIN, 8
+ BOTTOMMARGIN, 100
+ END
+
+ IDD_NEEDADDFILES, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 194
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 98
+ END
+
+ IDD_SCROLLCHILD, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 14
+ END
+
+ IDD_SCROLLCHILDHOST, DIALOG
+ BEGIN
+ RIGHTMARGIN, 295
+ END
+
+ IDD_TIMEEDITOR, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 274
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 220
+ END
+
+ IDD_PREFS_METADATA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 271
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 81
+ END
+
+ IDD_MONITOR_HIDE, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 278
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 47
+ END
+
+ IDD_REFRESH_METADATA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 242
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 21
+ END
+
+ IDD_REINDEX, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 262
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 23
+ END
+
+ IDD_ADD_VIEW_2, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 327
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 250
+ END
+
+ IDD_ADD_VIEW_2_NF, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 327
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 192
+ END
+
+ IDD_ADD_VIEW_CHILD_ADVANCED, DIALOG
+ BEGIN
+ RIGHTMARGIN, 309
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "MediaWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_MEDIAWND_PLAYSELECTEDFILES
+ MENUITEM "Enqueue selection\tShift+Enter", ID_MEDIAWND_ENQUEUESELECTEDFILES
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_MEDIAWND_ADDTOPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", ID_MEDIAWND_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Edit metadata for selection...\tCtrl+E", ID_EDITITEMINFOS
+ MENUITEM "View f&ile info...\tAlt+3", ID_PE_ID3
+ MENUITEM "Read metadata on selected items\tCtrl+R", IDC_REFRESH_METADATA
+ POPUP "Rate items"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Explore item folder\tCtrl+F", ID_MEDIAWND_EXPLOREFOLDER
+ MENUITEM SEPARATOR
+ POPUP "Remove..."
+ BEGIN
+ MENUITEM "Remove all dead files", ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES
+ MENUITEM "Physically remove selected item(s)", ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS
+ END
+ MENUITEM "Remove from library\tDel", ID_MEDIAWND_REMOVEFROMLIBRARY
+ END
+ POPUP "AudioWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_AUDIOWND_PLAYSELECTION
+ MENUITEM "Enqueue selection\tShift+Enter", ID_AUDIOWND_ENQUEUESELECTION
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_MEDIAWND_ADDTOPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ POPUP "Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Play random item", ID_AUDIOWND_PLAYRANDOMITEM
+ MENUITEM "Enqueue random item", ID_AUDIOWND_ENQUEUERANDOMITEM
+ END
+ POPUP "QueryWnd"
+ BEGIN
+ MENUITEM "Play\tEnter", ID_QUERYWND_PLAYQUERY
+ MENUITEM "Enqueue\tShift+Enter", ID_QUERYWND_ENQUEUEQUERY
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", ID_MEDIAWND_ADDTOPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Edit View...\tF2", ID_QUERYWND_EDIT
+ MENUITEM "Add Smart View...\tIns", ID_QUERYMENU_ADDNEWQUERY
+ MENUITEM SEPARATOR
+ MENUITEM "Delete View\tDel", ID_QUERYWND_DELETE
+ END
+ POPUP "QueryMenu"
+ BEGIN
+ MENUITEM "Add Smart View...\tIns", ID_QUERYMENU_ADDNEWQUERY
+ MENUITEM SEPARATOR
+ MENUITEM "&Preferences", ID_QUERYMENU_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_QUERYMENU_HELP
+ END
+ POPUP "HeaderWnd"
+ BEGIN
+ MENUITEM "Customize columns...", ID_HEADERWND_CUSTOMIZECOLUMNS
+ END
+ POPUP "FilterHeaderWnd"
+ BEGIN
+ MENUITEM "Customize columns...", ID_HEADERWND_CUSTOMIZECOLUMNS
+ MENUITEM "Show Horizontal Scrollbar", ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR
+ END
+ POPUP "ArtHeaderWnd"
+ BEGIN
+ MENUITEM "Small Icon", ID_ARTHEADERWND_SMALLICON
+ MENUITEM "Medium Icon", ID_ARTHEADERWND_MEDIUMICON
+ MENUITEM "Large Icon", ID_ARTHEADERWND_LARGEICON
+ MENUITEM "Extra Large Icon", ID_ARTHEADERWND_EXTRALARGEICON
+ MENUITEM "Show Album Title", ID_ARTHEADERWND_SHOWTEXT
+ MENUITEM SEPARATOR
+ MENUITEM "Small Details", ID_ARTHEADERWND_SMALLDETAILS
+ MENUITEM "Medium Details", ID_ARTHEADERWND_MEDIUMDETAILS
+ MENUITEM "Large Details", ID_ARTHEADERWND_LARGEDETAILS
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_AUDIO BITMAP "resources\\ti_audio_16x16x16.bmp"
+IDB_TREEITEM_MOSTPLAYED BITMAP "resources\\ti_most_played_16x16x16.bmp"
+IDB_TREEITEM_NEVERPLAYED BITMAP "resources\\ti_never_played_16x16x16.bmp"
+IDB_TREEITEM_RECENTLYADDED BITMAP "resources\\ti_recently_added_16x16x16.bmp"
+IDB_TREEITEM_RECENTLYPLAYED BITMAP "resources\\ti_recently_played_16x16x16.bmp"
+IDB_TREEITEM_TOPRATED BITMAP "resources\\ti_top_rated_16x16x16.bmp"
+IDB_TREEITEM_VIDEO BITMAP "resources\\ti_video_16x16x16.bmp"
+IDB_TREEITEM_PODCASTS BITMAP "resources\\ti_podcasts_16x16x16.bmp"
+IDB_NEWFILTER_SIMPLE BITMAP "resources\\nf_simple.bmp"
+IDB_NEWFILTER_THREEFILTERS BITMAP "resources\\nf_threefilters.bmp"
+IDB_NEWFILTER_TWOFILTERS BITMAP "resources\\nf_twofilters.bmp"
+IDB_NEWFILTER_SIMPLEALBUM BITMAP "resources\\nf_simplealbum.bmp"
+IDB_TOOL_MODE BITMAP "resources\\icn_view_mode.bmp"
+IDB_TOOL_ART BITMAP "resources\\icn_alb_art.bmp"
+IDB_TOOL_COLS BITMAP "resources\\icn_columns.bmp"
+IDB_TREEITEM_RECENTLYMODIFIED BITMAP "resources\\ti_recently_modified_16x16x16.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog Info
+//
+
+IDD_ADD_VIEW_2 DLGINIT
+BEGIN
+ IDC_COMBO_PRESETS, 0x403, 7, 0
+0x6473, 0x6466, 0x6673, "\000"
+ 0
+END
+
+IDD_ADD_VIEW_2_NF DLGINIT
+BEGIN
+ IDC_COMBO_PRESETS, 0x403, 7, 0
+0x6473, 0x6466, 0x6673, "\000"
+ 0
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXT
+//
+
+IDR_QUERIES_TEXT TEXT "queries.txt"
+IDR_DB_ERROR TEXT "db_error.txt"
+IDR_NDE_ERROR TEXT "nde_error.txt"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_ACCELERATORS ACCELERATORS
+BEGIN
+ "3", ID_PE_ID3, VIRTKEY, ALT, NOINVERT
+ "A", ID_MEDIAWND_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "E", ID_EDITITEMINFOS, VIRTKEY, CONTROL, NOINVERT
+ "F", ID_MEDIAWND_EXPLOREFOLDER, VIRTKEY, CONTROL, NOINVERT
+ "R", IDC_REFRESH_METADATA, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_MEDIAWND_REMOVEFROMLIBRARY, VIRTKEY, NOINVERT
+ VK_RETURN, ID_AUDIOWND_PLAYSELECTION, VIRTKEY, NOINVERT
+ VK_RETURN, ID_MEDIAWND_PLAYSELECTEDFILES, VIRTKEY, NOINVERT
+ VK_RETURN, IDC_BUTTON_PLAY, VIRTKEY, NOINVERT
+ VK_RETURN, ID_AUDIOWND_ENQUEUESELECTION, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_MEDIAWND_ENQUEUESELECTEDFILES, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_MIX, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+IDR_QUERY_ACCELERATORS ACCELERATORS
+BEGIN
+ VK_INSERT, ID_QUERYMENU_ADDNEWQUERY, VIRTKEY, NOINVERT
+ VK_DELETE, ID_QUERYWND_DELETE, VIRTKEY, NOINVERT
+ VK_F2, ID_QUERYWND_EDIT, VIRTKEY, NOINVERT
+ VK_RETURN, ID_QUERYWND_ENQUEUEQUERY, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_QUERYWND_PLAYQUERY, VIRTKEY, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// PNG
+//
+
+IDR_IMAGE_NOTFOUND PNG "resources\\notfound.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_LOCAL_MEDIA "Nullsoft Library v%s"
+ 65535 "{06A3F81D-043D-4b5c-B341-590ED7053492}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_LOCAL_MEDIA "Local Library"
+ IDS_SEARCHING_X_FILES_FOUND "Searching... (%i files found)"
+ IDS_GETTING_INFO_FROM_FILES_PERCENT
+ "Getting information from files... (%i%% done)"
+ IDS_SCANNING_DIR "Scanning dir: "
+ IDS_SCANNING_FILE "Scanning file: "
+ IDS_CHECKING_FOR_FILE "Checking for file: "
+ IDS_COMPACTING "Compacting"
+ IDS_REMOVING_FILES_NOT_EXISTING "Removing files that do not exist..."
+ IDS_INITIALIZING "Initializing..."
+ IDS_SCANNING_X_OF_X_X_REMOVED "Scanning %d of %d files (%d removed)"
+ IDS_SCANNED_X_FILES_X_REMOVED
+ "Scanned %d files (%d removed) - Cleaning up..."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DATE_TIME_IS_TOO_COMPLEX
+ "This date/time is too complex for the editor to handle, drop the unsupported keywords?"
+ IDS_DATE_TIME_EDITOR_QUESTION "Date/Time Editor Question"
+ IDS_COMPARE_TO_STRING "Compare to string"
+ IDS_AFTER "After"
+ IDS_BEFORE "Before"
+ IDS_SINCE "Since"
+ IDS_UNTIL "Until"
+ IDS_COMPARE_TO_LENGTH "Compare to length (HH:MM:SS)"
+ IDS_ABOVE "Above"
+ IDS_BELOW "Below"
+ IDS_ABOVE_OR_EQUAL "Above or Equal"
+ IDS_BELOW_OR_EQUAL "Below or Equal"
+ IDS_COMPARE_TO_NUMBER "Compare to number"
+ IDS_OFFSET_BY "Offset by"
+ IDS_TIME_AGO "Time ago"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_SPACE_AFTER " after"
+ IDS_SPACE_BEFORE " before"
+ IDS_THE "the "
+ IDS_1ST "st"
+ IDS_2ND "nd"
+ IDS_3RD "rd"
+ IDS_4TH "th"
+ IDS_OF_THIS_MONTH " of this month"
+ IDS_NOW "now"
+ IDS_THIS_DATE "this date"
+ IDS_THIS_MONTH "this month"
+ IDS_THIS_DAY "this day"
+ IDS_THIS_TIME "this time"
+ IDS_ON_SPACE "on "
+ IDS_IN_SPACE "in "
+ IDS_OF_SPACE "of "
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AT_SPACE "at "
+ IDS_NOON "noon"
+ IDS_MIDNIGHT "midnight"
+ IDS_AGO " ago"
+ IDS_QUERY_FIELD_IS_EMPTY
+ "The query field is empty, use expression field instead of resetting the query?"
+ IDS_EMPTY_QUERY "Empty query"
+ IDS_NO_CHANGES_MADE_TO_QUERY
+ "You have not made any modification to the query, use the expression field instead of the old query?"
+ IDS_QUERY_NOT_CHANGED "Query not changed"
+ IDS_REGISTERED "Registered:%s"
+ IDS_ADD_PLEDIT_TO_LOCAL_MEDIA "Add current &playlist to Library"
+ IDS_ERROR "Error"
+ IDS_ADD_TO_LOCAL_MEDIA "Add to Library"
+ IDS_AUDIO "Audio"
+ IDS_VIDEO "Video"
+ IDS_MOST_PLAYED "Most Played"
+ IDS_RECENTLY_ADDED "Recently Added"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RECENTLY_PLAYED "Recently Played"
+ IDS_NEVER_PLAYED "Never Played"
+ IDS_TOP_RATED "Top Rated"
+ IDS_REMOVE_ALL_ITEMS_IN_LIBRARY
+ "Are you sure you want to remove ALL the items in your Library?"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_NEW_SMART_VIEW "New Smart &View...\tInsert"
+ IDS_RESCAN_WATCH_FOLDERS
+ "&Rescan Watch Folders (in background)\tCtrl+Insert"
+ IDS_ADD_MEDIA_TO_LIBRARY "&Add media to Library..."
+ IDS_REMOVE_MISSING_FILES_FROM_ML "Remove missing files from Library..."
+ IDS_ANY "Any"
+ IDS_ALL "All"
+ IDS_OPTIONS "Options"
+ IDS_DIRECTORY "Directory"
+ IDS_STOP_SCAN "Stop scan"
+ IDS_RESCAN_NOW "Rescan now"
+ IDS_DEFAULT "Default"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RESCAN_ABORTED "Rescan aborted"
+ IDS_WATCH_FOLDERS "Watch Folders"
+ IDS_EDIT_VIEW "Edit View"
+ IDS_MY_NEW_VIEW "My New View"
+ IDS_SIMPLE_VIEW_EDITOR "Simple View Editor"
+ IDS_ADVANCED_EDITOR "Advanced Editor"
+ IDS_FILTERS "Filters"
+ IDS_MUST_ENTER_A_NAME "You must enter a name!"
+ IDS_DAY "day"
+ IDS_VIEW_QUERY_MAY_HAVE_ERRORS "This view query may have errors."
+ IDS_VIEW_QUERY_IS_TOO_COMPLEX "This view query is too complex."
+ IDS_TEXT_AFTER_THE_FIRST_ERROR "text after the first error"
+ IDS_SOME_OF_THE_QUERY_LOGIC "some of the query logic"
+ IDS_VIEW_EDITOR_QUESTION "View Editor Question"
+ IDS_DELETE_THIS_VIEW "Delete this view?"
+ IDS_REFINE "Refine:"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLEAR_REFINE "Clear refine"
+ IDS_ARTIST "Artist"
+ IDS_TITLE "Title"
+ IDS_ALBUM "Album"
+ IDS_LENGTH "Length"
+ IDS_TRACK_NUMBER "Track #"
+ IDS_GENRE "Genre"
+ IDS_YEAR "Year"
+ IDS_FILENAME "Filename"
+ IDS_RATING "Rating"
+ IDS_PLAY_COUNT "Play Count"
+ IDS_PLAYED_LAST "Played Last"
+ IDS_LAST_UPDATED "Last Updated"
+ IDS_FILE_TIME "File Time"
+ IDS_COMMENT "Comment"
+ IDS_FILE_SIZE "File Size"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_BITRATE "Bitrate"
+ IDS_TYPE "Type"
+ IDS_DISC "Disc"
+ IDS_ALBUM_ARTIST "Album Artist"
+ IDS_FILE_PATH "File Path"
+ IDS_ALBUM_GAIN "Album Gain"
+ IDS_TRACK_GAIN "Track Gain"
+ IDS_PUBLISHER "Publisher"
+ IDS_COMPOSER "Composer"
+ IDS_EXTENSION "Extension"
+ IDS_IS_PODCAST "Is Podcast"
+ IDS_PODCAST_CHANNEL "Podcast Channel"
+ IDS_PODCAST_PUBLISH_DATE "Podcast Publish Date"
+ IDS_SCANNING "Scanning..."
+ IDS_ERROR_DELETING_FILES "Error deleting files"
+ IDS_ERROR_DELETING_X "Error deleting %s"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY
+ "Are you sure you want to remove the selected item(s) from the library?"
+ IDS_HIDE_INFO "Hide Info"
+ IDS_SHOW_INFO "Show Info"
+ IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY
+ "There are now %d items in the library. Click 'Add More' to add more directories, or click Close to continue."
+ IDS_ADD_MORE "Add More..."
+ IDS_PLAY_ALL_FILES_BY "Play all files by %s"
+ IDS_PLAY_ALL_FILES_FROM "Play all files from %s"
+ IDS_PODCAST "Podcast"
+ IDS_NON_PODCAST "Non-Podcast"
+ IDS_MIXABLE "Mixable"
+ IDS_X_ITEM "%d item%s"
+ IDS_ALBUM_ART "Album Art"
+ IDS_LOOKING_UP_MEDIA_INFO "...looking up media info..."
+ IDS_CLICK_AN_ITEM_TO_GET_ITS_INFO "click an item to get its info"
+ IDS_PLEASE_CONNECT_TO_THE_INTERNET_TO_USE_THIS_FEATURE
+ "please connect to the internet to use this feature"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM "All (%d %s, %d without album)"
+ IDS_ALL_X_ALBUMS "All (%d %s)"
+ IDS_NO_ALBUM "(no album)"
+ IDS_OPEN_MEDIA_LIBRARY_VIEW_RESULTS "Open Library &view results"
+ IDS_MEDIA_LIBRARY_VIEW_RESULTS "Library &view results"
+ IDS_ADD_MEDIA_TO_LIBRARY_ "Add media to Library"
+ IDS_SELECT_FOLDER_TO_ADD_TO_WINAMP_MEDIA_LIBRARY
+ "Select folder to add to the Winamp Library."
+ IDS_ADD "Add"
+ IDS_SCAN_FOLDER_IN_BACKGROUND "Scan folder in background"
+ IDS_FOLDER_NAME_IS_INCORRECT
+ "Folder Name '%s' is incorrect. Please enter correct folder name or select 'Cancel'."
+ IDS_INCORRECT_FOLDER_NAME "Incorrect Folder Name"
+ IDS_WILLS_UBER_STRING "Welcome to the new Smart View creation dialog. It's totally super duper and overhauled to the limit, pretty much.\n\nTo get started, just choose a preset. If you know what you're doing choose the ""Custom"" preset. Even though it isn't really a ""preset"" per se, this is what we decided on! :)\n\nWhen the form appears (filled in with your preset info), tweak it to your liking and once you're done give your smart view a name. Choose to hide the extra info pane if you'd like and hit ""OK"" and you're all set!"
+ IDS_EDIT_SMART_VIEW "Edit Smart View"
+ IDS_ADVANCED_MODE "Advanced mode >>"
+ IDS_SIMPLE_MODE "<< Simple mode"
+ IDS_DAYS "days"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_LENGTH_DURATION_STRING "[%s%u %s+%u:%02u:%02u]"
+ IDS_ITEM "item"
+ IDS_ITEMS "items"
+ IDS_SCANNING_PLAIN "Scanning"
+ IDS_ALL_ARTISTS "All Artists"
+ IDS_ARTIST_INDEX "Artist Index"
+ IDS_ALBUM_ARTIST_INDEX "Album Artist Index"
+ IDS_CUSTOM "Custom"
+ IDS_IS_VIDEO "Is video"
+ IDS_FILE_SIZE_KB "File size (KB)"
+ IDS_BITRATE_KBPS "Bitrate (KBPS)"
+ IDS_DISC_NUMBER "Disc #"
+ IDS_DISCS "Discs"
+ IDS_TRACKS "Tracks"
+ IDS_EQUALS "Equals"
+ IDS_DOES_NOT_EQUAL "Does not equal"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CONTAINS "Contains"
+ IDS_DOES_NOT_CONTAIN "Does not contain"
+ IDS_IS_ABOVE "Is above"
+ IDS_IS_BELOW "Is below"
+ IDS_EQUALS_OR_IS_ABOVE "Equals or is above"
+ IDS_EQUALS_OR_IS_BELOW "Equals or is below"
+ IDS_IS_EMPTY "Is empty"
+ IDS_IS_NOT_EMPTY "Is not empty"
+ IDS_BEGINS_WITH "Begins with"
+ IDS_ENDS_WITH "Ends with"
+ IDS_IS_SIMILAR_TO "Is similar to"
+ IDS_AT "At"
+ IDS_NOT_AT "Not at"
+ IDS_STRING189 "After"
+ IDS_PODCASTS "Podcasts"
+ IDS_AUDIO_BY_GENRE "Audio by Genre"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AUDIO_BY_INDEX "Audio by Index"
+ IDS_60s_MUSIC "'60s Music"
+ IDS_70s_MUSIC "'70s Music"
+ IDS_80s_MUSIC "'80s Music"
+ IDS_90s_MUSIC "'90s Music"
+ IDS_00s_MUSIC "'00s Music"
+ IDS_ROCK_MUSIC "Rock Music"
+ IDS_CLASSICAL_MUSIC "Classical Music"
+ IDS_RECORD_LABELS "Record Labels"
+ IDS_ALBUMS "Albums"
+ IDS_ALBUM_ARTISTS "Album Artists"
+ IDS_ARTIST_S "Artists"
+ IDS_COMPOSERS "Composers"
+ IDS_GENRES "Genres"
+ IDS_PUBLISHERS "Publishers"
+ IDS_YEARS "Years"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_ALBUM_ARTIST_INDEXES "Album Artist Indexes"
+ IDS_ARTIST_INDEXES "Artist Indexes"
+ IDS_PODCAST_CHANNELS "Podcast Channels"
+ IDS_ALL_X_S "All (%d %s)"
+ IDS_NO_S "(no %s)"
+ IDS_SIZE "Size"
+ IDS_NO_IMAGE "No image"
+ IDS_AVAILABLE "available"
+ IDS_NO_ARTIST "No Artist"
+ IDS_NO_GENRE "No Genre"
+ IDS_OTHER2 "Other..."
+ IDS_SIMPLE_ALBUM "Simple Album"
+ IDS_AUDIO_BUTTON_TT1 "Toggle Album Art View"
+ IDS_AUDIO_BUTTON_TT2 "Select Filters & Panes"
+ IDS_AUDIO_BUTTON_TT3 "Pane Options"
+ IDS_GET_ALBUM_ART "Get Album Art"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_REFRESH_ALBUM_ART "Refresh Album Art"
+ IDS_OPEN_FOLDER "Open Folder"
+ IDS_BPM "BPM"
+ IDS_META_STR "Meta"
+ IDS_SMART_STR "Smart"
+ IDS_GUESS_STR "Guess"
+ IDS_RECURSE_STR "Recurse"
+ IDS_MORE_ARTIST_INFO "Get Artist Information"
+ IDS_PLAY_RANDOM_ITEM "Play random %s"
+ IDS_ENQUEUE_RANDOM_ITEM "Enqueue random %s"
+ IDS_LOSSLESS "Lossless"
+ IDS_CATEGORY "Category"
+ IDS_CATEGORIES "Categories"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_GENRE_ALT "genre"
+ IDS_YEAR_ALT "year"
+ IDS_ALBUM_ARTIST_ALT "album artist"
+ IDS_ALBUM_GAIN_ALT "album gain"
+ IDS_PUBLISHER_ALT "publisher"
+ IDS_COMPOSER_ALT "composer"
+ IDS_ARTIST_INDEX_ALT "artist index"
+ IDS_ALBUM_ARTIST_INDEX_ALT "album artist index"
+ IDS_PODCAST_CHANNEL_ALT "podcast channel"
+ IDS_CATEGORY_ALT "category"
+ IDS_ARTIST_ALT "artist"
+ IDS_ALBUM_ALT "album"
+ IDS_KBPS "kbps"
+ IDS_RECENTLY_PLAYED_TEXT
+ "When 'Recently Played' is enabled, Winamp will keep track of when\nand how many times items in the Library are played.\n\nNote: When the first match against any of the selected option(s)\nis met then the playing item will be tracked."
+ IDS_PRODUCER "Producer"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DIRECTOR "Director"
+ IDS_PRODUCER_ALT "producer"
+ IDS_DIRECTOR_ALT "director"
+ IDS_CODEC "Codec"
+ IDS_WIDTH "Width"
+ IDS_HEIGHT "Height"
+ IDS_DIMENSION "Dimension"
+ IDS_IN_X_SEC "in %.03f sec."
+ IDS_TRACKS_MENU "Tracks"
+ IDS_ERROR_PLG_SELECT_TRACKS
+ "Please select one or more tracks to use as a seed for the playlist generator."
+ IDS_NULLSOFT_PLAYLIST_GENERATOR "Nullsoft Playlist Generator"
+ IDS_VIEW_ALL_FILES_BY "All tracks by artist ""%s"""
+ IDS_VIEW_ALL_FILES_FROM "All tracks from album ""%s"""
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DATE_ADDED "Date Added"
+ IDS_REFRESH_FILESIZE_DATEADDED
+ "Refreshing 'Filesize' & Populating 'Date Added'"
+ IDS_RECENTLY_MODIFIED "Recently Modified"
+ IDS_FINISHED "Finished"
+ IDS_REFRESH_MESSAGE "%u of %u files"
+ IDS_CLOUD "Cloud"
+ IDS_CLOUD_SOURCES "Cloud Sources:"
+ IDS_CLOUD_HIDDEN "Cloud (Hidden)"
+ IDS_TRACK_AVAILABLE "Track available in "
+ IDS_UPLOAD_TO_SOURCE "Upload track to source"
+ IDS_UPLOADING_TO_SOURCE "Uploading track to source"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_local/ml_local.sln b/Src/Plugins/Library/ml_local/ml_local.sln
new file mode 100644
index 00000000..b7f892ca
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.sln
@@ -0,0 +1,128 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_local", "ml_local.vcxproj", "{D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tataki", "..\tataki\tataki.vcxproj", "{255B68B5-7EF8-45EF-A675-2D6B88147909}"
+ ProjectSection(ProjectDependencies) = postProject
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|Win32.Build.0 = Debug|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|x64.ActiveCfg = Debug|x64
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Debug|x64.Build.0 = Debug|x64
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|Win32.ActiveCfg = Release|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|Win32.Build.0 = Release|Win32
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|x64.ActiveCfg = Release|x64
+ {D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}.Release|x64.Build.0 = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.ActiveCfg = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.Build.0 = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.ActiveCfg = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.Build.0 = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.ActiveCfg = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.Build.0 = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.ActiveCfg = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.Build.0 = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.Build.0 = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.Build.0 = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_local/ml_local.vcxproj b/Src/Plugins/Library/ml_local/ml_local.vcxproj
new file mode 100644
index 00000000..847d5b6d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.vcxproj
@@ -0,0 +1,416 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{D9BD1AC1-9ECB-4399-BC33-E5E8B2A86884}</ProjectGuid>
+ <RootNamespace>ml_local</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>false</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4244;4267;4302;4311;4312;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ShowIncludes>false</ShowIncludes>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_LOCAL_EXPORTS;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4201;4244;4267;4302;4311;4312;4838;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ShowIncludes>false</ShowIncludes>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;Rpcrt4.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <MapFileName>$(IntDir)$(TargetName).map</MapFileName>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\tataki\tataki.vcxproj">
+ <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="add.cpp" />
+ <ClCompile Include="AlbumArtCache.cpp" />
+ <ClCompile Include="AlbumArtContainer.cpp" />
+ <ClCompile Include="AlbumArtFilter.cpp" />
+ <ClCompile Include="AlbumFilter.cpp" />
+ <ClCompile Include="api_mldb.cpp" />
+ <ClCompile Include="bgscan.cpp" />
+ <ClCompile Include="DBitemrecord.cpp" />
+ <ClCompile Include="editinfo.cpp" />
+ <ClCompile Include="editquery.cpp" />
+ <ClCompile Include="FolderBrowseEx.cpp" />
+ <ClCompile Include="guess.cpp" />
+ <ClCompile Include="handleMessage.cpp" />
+ <ClCompile Include="LocalMediaCOM.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="MD5.cpp" />
+ <ClCompile Include="mldbApi.cpp" />
+ <ClCompile Include="mldbApiFactory.cpp" />
+ <ClCompile Include="ml_local.cpp" />
+ <ClCompile Include="ml_subclass.cpp" />
+ <ClCompile Include="nde_itemRecord.cpp" />
+ <ClCompile Include="pe_subclass.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="queries.cpp" />
+ <ClCompile Include="ReIndexUI.cpp" />
+ <ClCompile Include="remove.cpp" />
+ <ClCompile Include="SaveQuery.cpp" />
+ <ClCompile Include="ScanFolderBrowser.cpp" />
+ <ClCompile Include="SimpleFilter.cpp" />
+ <ClCompile Include="TitleInfo.cpp" />
+ <ClCompile Include="util.cpp" />
+ <ClCompile Include="ViewFilter.cpp" />
+ <ClCompile Include="view_audio.cpp" />
+ <ClCompile Include="view_errorinfo.cpp" />
+ <ClCompile Include="view_media.cpp" />
+ <ClCompile Include="view_miniinfo.cpp" />
+ <ClCompile Include="wa_subclass.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\config.h" />
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\ml_cloud\CloudCallback.h" />
+ <ClInclude Include="..\..\..\replicant\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="AlbumArtCache.h" />
+ <ClInclude Include="AlbumArtContainer.h" />
+ <ClInclude Include="AlbumArtFilter.h" />
+ <ClInclude Include="AlbumFilter.h" />
+ <ClInclude Include="api__ml_local.h" />
+ <ClInclude Include="api_mldb.h" />
+ <ClInclude Include="editquery.h" />
+ <ClInclude Include="FolderBrowseEx.h" />
+ <ClInclude Include="LocalMediaCOM.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="MD5.h" />
+ <ClInclude Include="mldbApi.h" />
+ <ClInclude Include="mldbApiFactory.h" />
+ <ClInclude Include="MLString.h" />
+ <ClInclude Include="ml_local.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="ScanFolderBrowser.h" />
+ <ClInclude Include="SimpleFilter.h" />
+ <ClInclude Include="ViewFilter.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="db_error.txt" />
+ <Text Include="nde_error.txt" />
+ <Text Include="queries.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\icn_alb_art.bmp" />
+ <Image Include="resources\icn_columns.bmp" />
+ <Image Include="resources\icn_view_mode.bmp" />
+ <Image Include="resources\nf_simple.bmp" />
+ <Image Include="resources\nf_simplealbum.bmp" />
+ <Image Include="resources\nf_threefilters.bmp" />
+ <Image Include="resources\nf_twofilters.bmp" />
+ <Image Include="resources\notfound.png" />
+ <Image Include="resources\ti_audio_16x16x16.bmp" />
+ <Image Include="resources\ti_most_played_16x16x16.bmp" />
+ <Image Include="resources\ti_never_played_16x16x16.bmp" />
+ <Image Include="resources\ti_podcasts_16x16x16.bmp" />
+ <Image Include="resources\ti_recently_added_16x16x16.bmp" />
+ <Image Include="resources\ti_recently_modified_16x16x16.bmp" />
+ <Image Include="resources\ti_recently_played_16x16x16.bmp" />
+ <Image Include="resources\ti_top_rated_16x16x16.bmp" />
+ <Image Include="resources\ti_video_16x16x16.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_local.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_local.vcxproj.filters b/Src/Plugins/Library/ml_local/ml_local.vcxproj.filters
new file mode 100644
index 00000000..d5a1349e
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_local.vcxproj.filters
@@ -0,0 +1,328 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="add.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumArtCache.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumArtContainer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumArtFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="AlbumFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="api_mldb.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="bgscan.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DBitemrecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="editinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="editquery.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="FolderBrowseEx.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="guess.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="handleMessage.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="local_menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LocalMediaCOM.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="MD5.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_local.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="mldbApi.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="mldbApiFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="nde_itemRecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pe_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="queries.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ReIndexUI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="remove.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SaveQuery.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ScanFolderBrowser.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SimpleFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="wa_subclass.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ViewFilter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_miniinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_media.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_errorinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_audio.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="TitleInfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ChildSizer.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files\gen_ml</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="AlbumArtCache.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AlbumArtContainer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AlbumArtFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="AlbumFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_local.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_mldb.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="editquery.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="FolderBrowseEx.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="local_menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LocalMediaCOM.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MD5.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ml_local.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="mldbApi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="mldbApiFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="MLString.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ViewFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SimpleFilter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ScanFolderBrowser.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\ml_cloud\CloudCallback.h">
+ <Filter>Header Files\ml_cloud</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\config.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\gaystring.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files\gen_ml</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\replicant\nu\menushortcuts.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\icn_view_mode.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\icn_columns.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\icn_alb_art.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_simple.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_simplealbum.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_threefilters.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\nf_twofilters.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\notfound.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_audio_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_most_played_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_never_played_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_podcasts_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_recently_added_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_recently_modified_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_recently_played_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_top_rated_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\ti_video_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="db_error.txt" />
+ <Text Include="nde_error.txt" />
+ <Text Include="queries.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{f6d09751-2ba8-4112-b488-dc1f0ea05c94}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{3c287e55-bc08-4d03-824d-ee4d32647054}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{13873f78-eb7e-495e-beaa-a25de0940e15}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{35f126e7-3751-48fb-b7e5-f4aad512a831}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{d65e9d81-15db-4230-8fa3-a293d35217b3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{21c5748c-8e27-4395-80cf-865cb7d06a76}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{4dd5349f-8664-4615-8297-acc4d62c215f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\ml_cloud">
+ <UniqueIdentifier>{d3b5c0d1-2830-46e2-a1fe-d83606b83427}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{a811db7d-f845-4bb5-9437-1c7ac6b27f2e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{e9155744-9138-4cbe-84e6-413d3b07daa4}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{aaff6d40-e86b-4dc0-aa3c-02a2046a8d6a}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_local.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/ml_subclass.cpp b/Src/Plugins/Library/ml_local/ml_subclass.cpp
new file mode 100644
index 00000000..4020aeb1
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/ml_subclass.cpp
@@ -0,0 +1,80 @@
+#include "main.h"
+extern void AccessingGracenoteHack(int);
+extern HWND subWnd;
+
+// TODO: benski> a lot of things don't need to be part of gen_ml window - they could easily be done with a hidden window
+LRESULT APIENTRY ml_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_USER+641:
+ {
+ AccessingGracenoteHack(wParam);
+ break;
+ }
+ case WM_ML_IPC:
+ {
+ INT_PTR ret = HandleIpcMessage((INT_PTR)lParam, (INT_PTR)wParam);
+ if (ret != 0)
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, ret);
+ return ret; // supposed to return TRUE but thus is not working for me :(
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDM_DOSHITMENU_ADDNEWVIEW:
+ addNewQuery(hwndDlg);
+ return 0;
+ case IDM_ADD_PLEDIT:
+ add_pledit_to_library();
+ return 0;
+ case IDM_ADD_DIRS:
+ add_to_library(hwndDlg);
+ return 0;
+ case IDM_REMOVE_UNUSED_FILES:
+ Scan_RemoveFiles(hwndDlg);
+ if (m_curview_hwnd) SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ return 0;
+ case IDM_RESCANFOLDERSNOW:
+ if (!g_bgscan_scanning) SendMessage(hwndDlg, WM_USER + 575, 0xffff00dd, 0);
+ return 0;
+ }
+ break;
+ case WM_USER + 575: //sent by prefs to start scanning
+ if (wParam == 0xffff00dd && !lParam)
+ {
+ if (!g_bgscan_scanning)
+ {
+ Scan_BackgroundScan();
+ }
+ }
+ break;
+ case WM_TIMER:
+ {
+ static int in_timer;
+ if (in_timer) return 0;
+ in_timer = 1;
+ if (wParam == 200) // decide if it is time to scan yet
+ {
+ if (!g_bgscan_scanning)
+ {
+ if (g_bgrescan_force || (g_bgrescan_do && (time(NULL) - g_bgscan_last_rescan) > g_bgrescan_int*60))
+ {
+ // send to the prefs page so it'll show the status if it's open
+ // (makes it easier to see if things are working with the rescan every x option)
+ if (IsWindow(subWnd)) SendMessage(subWnd, WM_USER+101, 0, 0);
+ Scan_BackgroundScan();
+ }
+ }
+ in_timer = 0;
+ return 0;
+ }
+ in_timer = 0;
+ }
+ break;
+ }
+ return CallWindowProc(ml_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/mldbApi.cpp b/Src/Plugins/Library/ml_local/mldbApi.cpp
new file mode 100644
index 00000000..661c8631
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApi.cpp
@@ -0,0 +1,399 @@
+#include "mldbApi.h"
+#include "main.h"
+#include <strsafe.h>
+
+itemRecordW *MLDBAPI::GetFile(const wchar_t *filename)
+{
+ itemRecordW *result = 0;
+ if (filename)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (s)
+ {
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ result = (itemRecordW *)_aligned_malloc(sizeof(itemRecordW), 16);
+ if (result)
+ {
+ result->filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+itemRecordW *MLDBAPI::GetFileIf(const wchar_t *filename, const wchar_t *query)
+{
+ itemRecordW *result = 0;
+ if (filename)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (s)
+ {
+ NDE_Scanner_Query(s, query);
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename))
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (f)
+ {
+ result = (itemRecordW *)_aligned_malloc(sizeof(itemRecordW), 16);
+ if (result)
+ {
+ result->filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+itemRecordListW *MLDBAPI::GetAlbum(const wchar_t *albumname, const wchar_t *albumartist)
+{
+ wchar_t query[4096] = {0}; // hope it's big enough
+ if (albumartist && albumname)
+ {
+ StringCchPrintfW(query, 4096, L"((albumartist isempty and artist=\"%s\") or albumartist=\"%s\") and album=\"%s\"", albumartist, albumartist, albumname);
+ return Query(query);
+ }
+ else if (albumname)
+ {
+ StringCchPrintfW(query, 4096, L"album=\"%s\"", albumname);
+ return Query(query);
+ }
+ return 0;
+}
+
+itemRecordListW *MLDBAPI::Query(const wchar_t *query)
+{
+ itemRecordListW *result = 0;
+
+ if (query)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ NDE_Scanner_Query(s, query);
+ NDE_Scanner_First(s);
+
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+
+ if (!result)
+ result = (itemRecordListW *)calloc(1, sizeof(itemRecordListW));
+
+ if (!result)
+ break;
+
+ allocRecordList(result, result->Size + 1);
+ if (!result->Alloc) break;
+
+ result->Items[result->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->Items[result->Size].filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ while (NDE_Scanner_Next(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+itemRecordListW *MLDBAPI::QueryLimit(const wchar_t *query, unsigned int limit)
+{
+ itemRecordListW *result = 0;
+ if (limit == 0)
+ return 0;
+ if (query)
+ {
+ openDb(); // just in case it's not opened yet (this function will return immediately if it's already open)
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ NDE_Scanner_Query(s, query);
+ NDE_Scanner_First(s);
+
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+
+ if (!result)
+ {
+ result = (itemRecordListW *)calloc(1, sizeof(itemRecordListW));
+ if (!result)
+ break;
+
+ allocRecordList(result, limit);
+ if (!result->Alloc)
+ break;
+ }
+
+ result->Items[result->Size].filename = NDE_StringField_GetString(f);
+ ndestring_retain(result->Items[result->Size].filename);
+ ScannerRefToObjCacheNFNW(s, result, true);
+ }
+ while (result->Size < (int)limit && NDE_Scanner_Next(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ }
+ return result;
+}
+
+void MLDBAPI::FreeRecord(itemRecordW *record)
+{
+ freeRecord(record);
+}
+
+void MLDBAPI::FreeRecordList(itemRecordListW *recordList)
+{
+ freeRecordList(recordList);
+}
+
+bool FindFileInDB(nde_scanner_t s, const wchar_t *filename);
+void MLDBAPI::SetField(const wchar_t *filename, const char *field, const wchar_t *value)
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ if (FindFileInDB(s, filename))
+ {
+ if (value && value[0])
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (!f)
+ f=NDE_Scanner_NewFieldByName(s, field);
+
+ if (f)
+ NDE_StringField_SetString(f, value);
+ }
+ else
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (f)
+ NDE_Scanner_DeleteField(s, f);
+ }
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void MLDBAPI::SetFieldInteger(const wchar_t *filename, const char *field, int value)
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ if (FindFileInDB(s, filename))
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (!f)
+ f=NDE_Scanner_NewFieldByName(s, field);
+
+ if (f)
+ NDE_IntegerField_SetValue(f, value);
+
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void MLDBAPI::SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16])
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ if (FindFileInDB(s, filename))
+ {
+ if (value)
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (!f)
+ f=NDE_Scanner_NewFieldByName(s, field);
+
+ if (f)
+ NDE_Int128Field_SetValue(f, value);
+ }
+ else
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByName(s, field);
+ if (f)
+ NDE_Scanner_DeleteField(s, f);
+ }
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void MLDBAPI::Sync()
+{
+ openDb();
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+int MLDBAPI::AddFile(const wchar_t *filename)
+{
+ int guess = g_config->ReadInt(L"guessmode", 0);
+ int meta = g_config->ReadInt(L"usemetadata", 1);
+ return addFileToDb(filename, 0, meta, guess);
+}
+
+int MLDBAPI::RemoveFile(const wchar_t *filename)
+{
+ return RemoveFileFromDB(filename);
+}
+
+void MLDBAPI::RetainString(wchar_t *str)
+{
+ ndestring_retain(str);
+}
+
+void MLDBAPI::ReleaseString(wchar_t *str)
+{
+ ndestring_release(str);
+}
+
+wchar_t *MLDBAPI::DuplicateString(const wchar_t *str)
+{
+ return ndestring_wcsdup(str);
+}
+
+int MLDBAPI::GetMaxInteger(const char *field, int *max)
+{
+ openDb();
+ if (!g_table)
+ return 1;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_field_t f = NDE_Table_GetColumnByName(g_table, field);
+ if (!f)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+
+ unsigned char field_id = NDE_ColumnField_GetFieldID(f);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (s)
+ {
+ int maximum_so_far=0;
+ NDE_Scanner_Query(s, L"");
+ NDE_Scanner_First(s);
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, field_id);
+ if (f)
+ {
+ int this_value = NDE_IntegerField_GetValue(f);
+ if (this_value > maximum_so_far)
+ maximum_so_far = this_value;
+ }
+
+ } while (NDE_Scanner_Next(s) && !NDE_Scanner_EOF(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ *max=maximum_so_far;
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ else
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 1;
+ }
+}
+
+#define CBCLASS MLDBAPI
+START_DISPATCH;
+CB(API_MLDB_GETFILE, GetFile)
+CB(API_MLDB_GETFILEIF, GetFileIf)
+CB(API_MLDB_GETALBUM, GetAlbum)
+CB(API_MLDB_QUERY, Query)
+CB(API_MLDB_QUERYLIMIT, QueryLimit)
+VCB(API_MLDB_FREERECORD, FreeRecord)
+VCB(API_MLDB_FREERECORDLIST, FreeRecordList)
+VCB(API_MLDB_SETFIELD, SetField)
+VCB(API_MLDB_SETFIELDINT, SetFieldInteger)
+VCB(API_MLDB_SETFIELDINT128, SetFieldInt128)
+VCB(API_MLDB_SYNC, Sync)
+CB(API_MLDB_ADDFILE, AddFile)
+CB(API_MLDB_REMOVEFILE, RemoveFile)
+VCB(API_MLDB_RETAINSTRING, RetainString)
+VCB(API_MLDB_RELEASESTRING, ReleaseString)
+CB(API_MLDB_DUPLICATESTRING, DuplicateString)
+CB(API_MLDB_GETMAXINTEGER, GetMaxInteger)
+END_DISPATCH;
+#undef CBCLASS
+
diff --git a/Src/Plugins/Library/ml_local/mldbApi.h b/Src/Plugins/Library/ml_local/mldbApi.h
new file mode 100644
index 00000000..3ee0e2f7
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApi.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "api_mldb.h"
+
+class MLDBAPI : public api_mldb
+{
+public:
+ itemRecordW *GetFile(const wchar_t *filename);
+ itemRecordW *GetFileIf(const wchar_t *filename, const wchar_t *query);
+ itemRecordListW *GetAlbum(const wchar_t *albumname, const wchar_t *albumartist);
+ itemRecordListW *Query(const wchar_t *query);
+ itemRecordListW *QueryLimit(const wchar_t *query, unsigned int limit);
+
+ void SetField(const wchar_t *filename, const char *field, const wchar_t *value);
+ void SetFieldInteger(const wchar_t *filename, const char *field, int value);
+ void SetFieldInt128(const wchar_t *filename, const char *field, uint8_t value[16]);
+ void Sync();
+
+ int AddFile(const wchar_t *filename);
+
+ void FreeRecord(itemRecordW *record);
+ void FreeRecordList(itemRecordListW *recordList);
+ int RemoveFile(const wchar_t *filename);
+
+ /* wrappers around ndestring */
+ void RetainString(wchar_t *str);
+ void ReleaseString(wchar_t *str);
+ wchar_t *DuplicateString(const wchar_t *str);
+
+ int GetMaxInteger(const char *field, int *max);
+protected:
+ RECVS_DISPATCH;
+};
+
diff --git a/Src/Plugins/Library/ml_local/mldbApiFactory.cpp b/Src/Plugins/Library/ml_local/mldbApiFactory.cpp
new file mode 100644
index 00000000..a2cc2cc9
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApiFactory.cpp
@@ -0,0 +1,61 @@
+#include "api__ml_local.h"
+#include "mldbApiFactory.h"
+#include "mldbApi.h"
+MLDBAPI mldbApi;
+static const char serviceName[] = "Media Library Database API";
+
+FOURCC MLDBAPIFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *MLDBAPIFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID MLDBAPIFactory::GetGUID()
+{
+ return mldbApiGuid;
+}
+
+void *MLDBAPIFactory::GetInterface(int global_lock)
+{
+ return &mldbApi;
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+}
+
+int MLDBAPIFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int MLDBAPIFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *MLDBAPIFactory::GetTestString()
+{
+ return NULL;
+}
+
+int MLDBAPIFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS MLDBAPIFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_local/mldbApiFactory.h b/Src/Plugins/Library/ml_local/mldbApiFactory.h
new file mode 100644
index 00000000..620b961a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/mldbApiFactory.h
@@ -0,0 +1,23 @@
+#ifndef NULLSOFT_MLDBFACTORY_H
+#define NULLSOFT_MLDBFACTORY_H
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class MLDBAPIFactory: public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/nde_error.txt b/Src/Plugins/Library/ml_local/nde_error.txt
new file mode 100644
index 00000000..e36baa49
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/nde_error.txt
@@ -0,0 +1,4 @@
+
+There was an error loading 'nde.dll' which is required for all of the local database functions.
+
+This could be due to a failed Winamp update or 'nde.dll' having been removed by mistake. \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/nde_itemRecord.cpp b/Src/Plugins/Library/ml_local/nde_itemRecord.cpp
new file mode 100644
index 00000000..7af7f6fc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/nde_itemRecord.cpp
@@ -0,0 +1,976 @@
+/*
+** Copyright © 2003-2014 Winamp SA
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+*/
+
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include "../nde/nde.h"
+#include "../nu/sort.h"
+
+void freeRecord(itemRecord *p)
+{
+ if (p)
+ {
+ free(p->title);
+ free( p->artist );
+ free( p->ext );
+ free(p->comment);
+ free(p->album);
+ free(p->genre);
+ free(p->filename);
+ if (p->extended_info)
+ {
+ for (size_t x = 0; p->extended_info[x]; x ++)
+ free(p->extended_info[x]);
+ free(p->extended_info);
+ p->extended_info = 0;
+ }
+ }
+}
+
+void freeRecordList(itemRecordList *obj)
+{
+ if (obj)
+ {
+ emptyRecordList(obj);
+ _aligned_free(obj->Items);
+ obj->Items=0;
+ obj->Alloc=obj->Size=0;
+ }
+}
+
+void emptyRecordList(itemRecordList *obj)
+{
+ if (obj)
+ {
+ itemRecord *p=obj->Items;
+ while (obj->Size-->0)
+ {
+ freeRecord(p);
+ p++;
+ }
+ obj->Size=0;
+ }
+}
+
+void allocRecordList(itemRecordList *obj, int newsize, int granularity)
+{
+ if (newsize < obj->Alloc || newsize < obj->Size) return;
+
+ int old_Alloc = obj->Alloc;
+ obj->Alloc=newsize+granularity;
+
+ itemRecord *data = (itemRecord*)_aligned_realloc(obj->Items, sizeof(itemRecord) * obj->Alloc, 16);
+ if (!data)
+ {
+ data = (itemRecord*)_aligned_malloc(sizeof(itemRecord) * obj->Alloc, 16);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(itemRecord) * old_Alloc);
+ _aligned_free(obj->Items);
+ obj->Items = data;
+ }
+ else
+ obj->Alloc = old_Alloc;
+ }
+ else
+ obj->Items = data;
+
+ if (!obj->Items) obj->Alloc=0;
+}
+
+void compactRecordList(itemRecordListW *obj)
+{
+ if (obj->Size && obj->Size < obj->Alloc - 1024)
+ {
+ size_t old_Alloc = obj->Size;
+ obj->Alloc = obj->Size;
+
+ itemRecordW *data = (itemRecordW *)_aligned_realloc(obj->Items, sizeof(itemRecordW) * obj->Alloc, 16);
+ if (!data)
+ {
+ data = (itemRecordW *)_aligned_malloc(sizeof(itemRecordW) * obj->Alloc, 16);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(itemRecordW) * old_Alloc);
+ _aligned_free(obj->Items);
+ obj->Items = data;
+ }
+ else
+ obj->Alloc = old_Alloc;
+ }
+ else
+ obj->Items = data;
+ }
+}
+
+void copyRecord(itemRecord *out, const itemRecord *in)
+{
+#define COPYSTR(FOO) out->FOO = in->FOO ? _strdup(in->FOO) : 0;
+ COPYSTR(filename)
+ COPYSTR(title)
+ COPYSTR(ext)
+ COPYSTR(album)
+ COPYSTR(artist)
+ COPYSTR(comment)
+ COPYSTR(genre)
+ out->year=in->year;
+ out->track=in->track;
+ out->length=in->length;
+#undef COPYSTR
+ out->extended_info=0;
+
+ if (in->extended_info)
+ {
+ for (int y = 0; in->extended_info[y]; y ++)
+ {
+ char *p=in->extended_info[y];
+ if (*p) setRecordExtendedItem(out,p,p+strlen(p)+1);
+ }
+ }
+}
+
+void copyRecordList(itemRecordList *out, const itemRecordList *in)
+{
+ allocRecordList(out,out->Size+in->Size,0);
+ if (!out->Items) return;
+ for (int x = 0; x < in->Size; x ++)
+ {
+ copyRecord(&out->Items[out->Size++],&in->Items[x]);
+ }
+}
+
+char *getRecordExtendedItem(const itemRecord *item, const char *name)
+{
+ if (item->extended_info)
+ {
+ for (size_t x = 0; item->extended_info[x]; x ++)
+ {
+ if (!_stricmp(item->extended_info[x],name))
+ return item->extended_info[x]+strlen(name)+1;
+ }
+ }
+ return NULL;
+}
+
+void setRecordExtendedItem(itemRecord *item, const char *name, char *value)
+{
+ if (!item || !name) return;
+
+ size_t x = 0;
+ if (item->extended_info)
+ {
+ for (x = 0; item->extended_info[x]; x ++)
+ {
+ if (!_stricmp(item->extended_info[x],name))
+ {
+ size_t name_len = strlen(name), value_len = strlen(value);
+ if (value_len>strlen(item->extended_info[x]+name_len+1))
+ {
+ free(item->extended_info[x]);
+ item->extended_info[x]=(char*)malloc(name_len+value_len+2);
+ }
+ strncpy(item->extended_info[x], name, name_len);
+ strncpy(item->extended_info[x]+strlen(name)+1, value, value_len);
+ return;
+ }
+ }
+ }
+
+ // x=number of valid items.
+ char **data = (char**)realloc(item->extended_info,sizeof(char*) * (x+2));
+ if (data)
+ {
+ // if we could allocate then add, otherwise we'll have to skip
+ item->extended_info = data;
+
+ size_t name_len = strlen(name), value_len = strlen(value);
+ item->extended_info[x]=(char*)malloc(name_len+value_len+2);
+ strncpy(item->extended_info[x], name, name_len);
+ strncpy(item->extended_info[x]+name_len+1, value, value_len);
+
+ item->extended_info[x+1]=0;
+ }
+ else
+ {
+ data = (char**)malloc(sizeof(char*) * (x+2));
+ if (data)
+ {
+ memcpy(data, item->extended_info, sizeof(char*) * x);
+ free(item->extended_info);
+
+ // if we could allocate then add, otherwise we'll have to skip
+ item->extended_info = data;
+
+ size_t name_len = strlen(name), value_len = strlen(value);
+ item->extended_info[x]=(char*)malloc(name_len+value_len+2);
+ strncpy(item->extended_info[x], name, name_len);
+ strncpy(item->extended_info[x]+name_len+1, value, value_len);
+
+ item->extended_info[x+1]=0;
+ }
+ }
+}
+
+/*
+----------------------------------
+wide version starts here
+----------------------------------
+*/
+void freeRecord(itemRecordW *p)
+{
+ if (p)
+ {
+ ndestring_release(p->title);
+ ndestring_release(p->artist);
+ ndestring_release(p->comment);
+ ndestring_release(p->album);
+ ndestring_release(p->genre);
+ ndestring_release(p->filename);
+ ndestring_release(p->albumartist);
+ ndestring_release(p->replaygain_album_gain);
+ ndestring_release(p->replaygain_track_gain);
+ ndestring_release(p->publisher);
+ ndestring_release(p->composer);
+ if (p->extended_info)
+ {
+ for (size_t x = 0; p->extended_info[x].key; x ++)
+ {
+ // TODO release 'key' ?
+// ndestring_release(p->extended_info[x].key);
+ ndestring_release(p->extended_info[x].value);
+ }
+ free(p->extended_info);
+ p->extended_info = 0;
+ }
+ }
+}
+
+void freeRecordList(itemRecordListW *obj)
+{
+ if (obj)
+ {
+ emptyRecordList(obj);
+ _aligned_free(obj->Items);
+ obj->Items=0;
+ obj->Alloc=obj->Size=0;
+ }
+}
+
+void emptyRecordList(itemRecordListW *obj)
+{
+ if (obj)
+ {
+ itemRecordW *p=obj->Items;
+ while (obj->Size-->0)
+ {
+ freeRecord(p);
+ p++;
+ }
+ obj->Size=0;
+ }
+}
+
+void allocRecordList(itemRecordListW *obj, int newsize, int granularity)
+{
+ if (newsize < obj->Alloc || newsize < obj->Size) return;
+
+ int old_Alloc = obj->Alloc;
+ obj->Alloc=newsize+granularity;
+ itemRecordW *data = (itemRecordW*)_aligned_realloc(obj->Items,sizeof(itemRecordW)*obj->Alloc, 16);
+ if (!data)
+ {
+ data = (itemRecordW*)_aligned_malloc(sizeof(itemRecordW) * obj->Alloc, 16);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(itemRecordW) * obj->Alloc);
+ _aligned_free(obj->Items);
+ obj->Items = data;
+ }
+ else
+ obj->Alloc = old_Alloc;
+ }
+ else
+ obj->Items = data;
+
+ if (!obj->Items) obj->Alloc=0;
+}
+
+#if 0 // unused, don't re-enable until you verify the TODO below
+void copyRecord(itemRecordW *out, const itemRecordW *in)
+{
+#define COPYSTR(FOO) out->FOO = in->FOO ? _wcsdup(in->FOO) : 0;
+#define COPY(FOO) out->FOO = in->FOO;
+ /* this is only valid if 'in' is one of our item records! */
+ out->filename = in->filename;
+ NDEString *ndeString = WCHAR_TO_NDESTRING(out->filename);
+ if (ndeString) ndeString->Retain();
+ COPYSTR(title)
+ COPYSTR(album)
+ COPYSTR(artist)
+ COPYSTR(comment)
+ COPYSTR(genre)
+ COPYSTR(albumartist);
+ COPYSTR(replaygain_album_gain);
+ COPYSTR(replaygain_track_gain);
+ COPYSTR(publisher);
+ COPYSTR(composer);
+ COPY(year);
+ COPY(track);
+ COPY(tracks);
+ COPY(length);
+ COPY(rating);
+ COPY(playcount);
+ COPY(lastplay);
+ COPY(lastupd);
+ COPY(filetime);
+ COPY(filesize);
+ COPY(bitrate);
+ COPY(type);
+ COPY(disc);
+ COPY(discs);
+ COPY(bpm);
+ COPYSTR(category);
+#undef COPYSTR
+ out->extended_info=0;
+
+ if (in->extended_info)
+ {
+ for (int y = 0; in->extended_info[y].key; y ++)
+ {
+ setRecordExtendedItem(out,in->extended_info[y].key,in->extended_info[y].value);
+ }
+ }
+}
+
+void copyRecordList(itemRecordListW *out, const itemRecordListW *in)
+{
+ allocRecordList(out,out->Size+in->Size,0);
+ if (!out->Items) return;
+ for (size_t x = 0; x < in->Size; x ++)
+ {
+ copyRecord(&out->Items[out->Size++],&in->Items[x]);
+ }
+}
+#endif
+
+wchar_t *getRecordExtendedItem(const itemRecordW *item, const wchar_t *name)
+{
+ if (item && item->extended_info && name)
+ {
+ for (size_t x = 0; item->extended_info[x].key; x ++)
+ {
+ if (!_wcsicmp(item->extended_info[x].key,name))
+ return item->extended_info[x].value;
+ }
+ }
+ return NULL;
+}
+
+wchar_t *getRecordExtendedItem_fast(const itemRecordW *item, const wchar_t *name)
+{
+ if (item && item->extended_info && name)
+ {
+ for (size_t x = 0; item->extended_info[x].key; x ++)
+ {
+ if (item->extended_info[x].key == name)
+ return item->extended_info[x].value;
+ }
+ }
+ return NULL;
+}
+
+void setRecordExtendedItem(itemRecordW *item, const wchar_t *name, const wchar_t *value)
+{
+ if (!item || !name) return;
+
+ size_t x=0;
+ if (item->extended_info) for (x = 0; item->extended_info[x].key; x ++)
+ {
+ if (item->extended_info[x].key == name)
+ {
+ ndestring_retain(const_cast<wchar_t *>(value));
+ ndestring_release(item->extended_info[x].value);
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+ return;
+ }
+ }
+
+ // x=number of valid items.
+ extendedInfoW *data=(extendedInfoW *)realloc(item->extended_info,sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info=data;
+
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ else
+ {
+ data=(extendedInfoW *)malloc(sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info=data;
+
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ }
+}
+
+// this version assumes that the 'name' won't already be in the itemRecord
+void setRecordExtendedItem_fast(itemRecordW *item, const wchar_t *name, const wchar_t *value)
+{
+ if (!item || !name) return;
+
+ size_t x = 0;
+ if (item->extended_info)
+ {
+ for (x = 0; item->extended_info[x].key; x++)
+ {
+ }
+ }
+
+ // x=number of valid items.
+ extendedInfoW *data=(extendedInfoW *)realloc(item->extended_info,sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info = data;
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ else
+ {
+ data=(extendedInfoW *)malloc(sizeof(extendedInfoW) * (x+2));
+ if (data)
+ {
+ item->extended_info=data;
+
+ item->extended_info[x].key = const_cast<wchar_t *>(name);
+ ndestring_retain(const_cast<wchar_t *>(value));
+ item->extended_info[x].value = const_cast<wchar_t *>(value);
+
+ item->extended_info[x+1].key=0;
+ item->extended_info[x+1].value=0;
+ }
+ }
+}
+
+// TODO: redo this without AutoChar
+#include "../replicant/nu/AutoChar.h"
+#include "../nu/AutoCharFn.h"
+#define COPY_EXTENDED_STR(field) if (input-> ## field && input-> ## field ## [0]) setRecordExtendedItem(output, #field, AutoChar(input-> ## field));
+#define COPY_EXTENDED_INT(field) if (input->##field > 0) { char temp[64] = {0}; _itoa(input->##field, temp, 10); setRecordExtendedItem(output, #field, temp); }
+#define COPY_EXTENDED_INT64(field) if (input->##field > 0) { char temp[64] = {0}; _i64toa(input->##field, temp, 10); setRecordExtendedItem(output, #field, temp); }
+#define COPY_EXTENDED_INT0(field) if (input->##field >= 0) { char temp[64] = {0}; _itoa(input->##field, temp, 10); setRecordExtendedItem(output, #field, temp); }
+void convertRecord(itemRecord *output, const itemRecordW *input)
+{
+ output->filename=_strdup(AutoCharFn(input->filename));
+ output->title=AutoCharDup(input->title);
+ output->ext=AutoCharDup(input->ext);
+ output->album=AutoCharDup(input->album);
+ output->artist=AutoCharDup(input->artist);
+ output->comment=AutoCharDup(input->comment);
+ output->genre=AutoCharDup(input->genre);
+ output->year=input->year;
+ output->track=input->track;
+ output->length=input->length;
+ output->extended_info=0;
+ COPY_EXTENDED_STR(albumartist);
+ COPY_EXTENDED_STR(replaygain_album_gain);
+ COPY_EXTENDED_STR(replaygain_track_gain);
+ COPY_EXTENDED_STR(publisher);
+ COPY_EXTENDED_STR(composer);
+ COPY_EXTENDED_INT(tracks);
+ COPY_EXTENDED_INT(rating);
+ COPY_EXTENDED_INT(playcount);
+ COPY_EXTENDED_INT64(lastplay);
+ COPY_EXTENDED_INT64(lastupd);
+ COPY_EXTENDED_INT64(filetime);
+ COPY_EXTENDED_INT(filesize);
+ COPY_EXTENDED_INT(bitrate);
+ COPY_EXTENDED_INT0(type);
+ COPY_EXTENDED_INT(disc);
+ COPY_EXTENDED_INT(discs);
+ COPY_EXTENDED_INT(bpm);
+ COPY_EXTENDED_STR(category);
+
+ if (input->extended_info)
+ {
+ for (int y = 0; input->extended_info[y].key; y ++)
+ {
+ setRecordExtendedItem(output, AutoChar(input->extended_info[y].key), AutoChar(input->extended_info[y].value));
+ }
+ }
+}
+#undef COPY_EXTENDED_STR
+#undef COPY_EXTENDED_INT
+#undef COPY_EXTENDED_INT0
+
+#include "../replicant/nu/AutoWide.h"
+#define COPY_EXTENDED_STR(field) output->##field = ndestring_wcsdup(AutoWideDup(getRecordExtendedItem(input, #field)));
+#define COPY_EXTENDED_INT(field) { char *x = getRecordExtendedItem(input, #field); output->##field=x?atoi(x):-1; }
+
+void convertRecord(itemRecordW *output, const itemRecord *input)
+{
+ output->filename=ndestring_wcsdup(AutoWideDup(input->filename));
+ output->title=ndestring_wcsdup(AutoWideDup(input->title));
+ output->ext=ndestring_wcsdup(AutoWideDup(input->ext));
+ output->album=ndestring_wcsdup(AutoWideDup(input->album));
+ output->artist=ndestring_wcsdup(AutoWideDup(input->artist));
+ output->comment=ndestring_wcsdup(AutoWideDup(input->comment));
+ output->genre=ndestring_wcsdup(AutoWideDup(input->genre));
+ output->year=input->year;
+ output->track=input->track;
+ output->length=input->length;
+ output->extended_info=0;
+ COPY_EXTENDED_STR(albumartist);
+ COPY_EXTENDED_STR(replaygain_album_gain);
+ COPY_EXTENDED_STR(replaygain_track_gain);
+ COPY_EXTENDED_STR(publisher);
+ COPY_EXTENDED_STR(composer);
+ COPY_EXTENDED_INT(tracks);
+ COPY_EXTENDED_INT(rating);
+ COPY_EXTENDED_INT(playcount);
+ COPY_EXTENDED_INT(lastplay);
+ COPY_EXTENDED_INT(lastupd);
+ COPY_EXTENDED_INT(filetime);
+ COPY_EXTENDED_INT(filesize);
+ COPY_EXTENDED_INT(type);
+ COPY_EXTENDED_INT(disc);
+ COPY_EXTENDED_INT(discs);
+ COPY_EXTENDED_INT(bpm);
+ COPY_EXTENDED_INT(bitrate);
+ COPY_EXTENDED_STR(composer);
+ COPY_EXTENDED_STR(category);
+ // TODO: copy input's extended fields
+}
+#undef COPY_EXTENDED_STR
+#undef COPY_EXTENDED_INT
+
+void convertRecordList(itemRecordList *output, const itemRecordListW *input)
+{
+ output->Alloc = output->Size = input->Size;
+ output->Items = (itemRecord*)_aligned_malloc(sizeof(itemRecord)*input->Size, 16);
+ if (output->Items)
+ {
+ memset(output->Items, 0, sizeof(itemRecord)*input->Size);
+ for(int i=0; i < input->Size; i++)
+ {
+ convertRecord(&output->Items[i],&input->Items[i]);
+ }
+ }
+ else
+ output->Alloc = output->Size = 0;
+}
+
+void convertRecordList(itemRecordListW *output, const itemRecordList *input)
+{
+ output->Alloc = output->Size = input->Size;
+ output->Items = (itemRecordW*)malloc(sizeof(itemRecordW) * input->Size);
+ if (output->Items)
+ {
+ memset(output->Items, 0, sizeof(itemRecordW) * input->Size);
+ for(int i=0; i < input->Size; i++)
+ {
+ convertRecord(&output->Items[i],&input->Items[i]);
+ }
+ }
+}
+
+extern int sse_flag;
+
+// swaps two 16 byte aligned 128-byte values
+#ifdef _M_X64
+#include <emmintrin.h>
+#endif
+static void __fastcall swap128(uint8_t *_a, uint8_t *_b)
+{
+ // ECX = a
+ // EDX = b
+#ifdef _M_IX86
+ _asm
+ {
+ // load first 64 bytes of each
+ movaps xmm0, XMMWORD PTR [ecx+0]
+ movaps xmm1, XMMWORD PTR [ecx+16]
+ movaps xmm2, XMMWORD PTR [ecx+32]
+ movaps xmm3, XMMWORD PTR [ecx+48]
+ movaps xmm4, XMMWORD PTR [edx+0]
+ movaps xmm5, XMMWORD PTR [edx+16]
+ movaps xmm6, XMMWORD PTR [edx+32]
+ movaps xmm7, XMMWORD PTR [edx+48]
+
+ // store
+ movaps XMMWORD PTR [edx+0], xmm0
+ movaps XMMWORD PTR [edx+16], xmm1
+ movaps XMMWORD PTR [edx+32], xmm2
+ movaps XMMWORD PTR [edx+48], xmm3
+ movaps XMMWORD PTR [ecx+0], xmm4
+ movaps XMMWORD PTR [ecx+16], xmm5
+ movaps XMMWORD PTR [ecx+32], xmm6
+ movaps XMMWORD PTR [ecx+48], xmm7
+
+ // load second 64 bytes of each
+ movaps xmm0, XMMWORD PTR [ecx+64]
+ movaps xmm1, XMMWORD PTR [ecx+80]
+ movaps xmm2, XMMWORD PTR [ecx+96]
+ movaps xmm3, XMMWORD PTR [ecx+112]
+ movaps xmm4, XMMWORD PTR [edx+64]
+ movaps xmm5, XMMWORD PTR [edx+80]
+ movaps xmm6, XMMWORD PTR [edx+96]
+ movaps xmm7, XMMWORD PTR [edx+112]
+
+ // store
+ movaps XMMWORD PTR [edx+64], xmm0
+ movaps XMMWORD PTR [edx+80], xmm1
+ movaps XMMWORD PTR [edx+96], xmm2
+ movaps XMMWORD PTR [edx+112], xmm3
+ movaps XMMWORD PTR [ecx+64], xmm4
+ movaps XMMWORD PTR [ecx+80], xmm5
+ movaps XMMWORD PTR [ecx+96], xmm6
+ movaps XMMWORD PTR [ecx+112], xmm7
+ }
+#else
+ //sizeof(itemRecordW) is 176 on AMD64. that's 11 SSE registers
+ __m128i b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10;
+ __m128i a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10;
+ __m128i *a = (__m128i *)_a;
+ __m128i *b = (__m128i *)_b;
+
+ a0 = _mm_load_si128(&a[0]);
+ a1 = _mm_load_si128(&a[1]);
+ a2 = _mm_load_si128(&a[2]);
+ a3 = _mm_load_si128(&a[3]);
+ a4 = _mm_load_si128(&a[4]);
+ a5 = _mm_load_si128(&a[5]);
+ a6 = _mm_load_si128(&a[6]);
+ a7 = _mm_load_si128(&a[7]);
+ a8 = _mm_load_si128(&a[0]);
+ a9 = _mm_load_si128(&a[1]);
+ a10 = _mm_load_si128(&a[2]);
+
+ b0 = _mm_load_si128(&b[0]);
+ b1 = _mm_load_si128(&b[1]);
+ b2 = _mm_load_si128(&b[2]);
+ b3 = _mm_load_si128(&b[3]);
+ b4 = _mm_load_si128(&b[4]);
+ b5 = _mm_load_si128(&b[5]);
+ b6 = _mm_load_si128(&b[6]);
+ b7 = _mm_load_si128(&b[7]);
+ b8 = _mm_load_si128(&b[8]);
+ b9 = _mm_load_si128(&b[9]);
+ b10 = _mm_load_si128(&b[10]);
+
+ _mm_store_si128(&a[0], b0);
+ _mm_store_si128(&a[1], b1);
+ _mm_store_si128(&a[2], b2);
+ _mm_store_si128(&a[3], b3);
+ _mm_store_si128(&a[4], b4);
+ _mm_store_si128(&a[5], b5);
+ _mm_store_si128(&a[6], b6);
+ _mm_store_si128(&a[7], b7);
+ _mm_store_si128(&b[0], a0);
+ _mm_store_si128(&b[1], a1);
+ _mm_store_si128(&b[2], a2);
+ _mm_store_si128(&b[3], a3);
+ _mm_store_si128(&b[4], a4);
+ _mm_store_si128(&b[5], a5);
+ _mm_store_si128(&b[6], a6);
+ _mm_store_si128(&b[7], a7);
+
+
+#endif
+}
+
+/***
+*shortsort(hi, lo, width, comp) - insertion sort for sorting short arrays
+*
+*Purpose:
+* sorts the sub-array of elements between lo and hi (inclusive)
+* side effects: sorts in place
+* assumes that lo < hi
+*
+*Entry:
+* char *lo = pointer to low element to sort
+* char *hi = pointer to high element to sort
+* size_t width = width in bytes of each array element
+* int (*comp)() = pointer to function returning analog of strcmp for
+* strings, but supplied by user for comparing the array elements.
+* it accepts 2 pointers to elements and returns neg if 1<2, 0 if
+* 1=2, pos if 1>2.
+*
+*Exit:
+* returns void
+*
+*Exceptions:
+*
+*******************************************************************************/
+
+static void shortsort_sse(uint8_t *lo, uint8_t *hi, const void *context,
+ int (__fastcall *comp)(const void *, const void *, const void *))
+{
+ const size_t width=sizeof(itemRecordW);
+ uint8_t *p;
+
+ /* Note: in assertions below, i and j are alway inside original bound of
+ array to sort. */
+
+ while (hi > lo) {
+ /* A[i] <= A[j] for i <= j, j > hi */
+ uint8_t *max = lo;
+ for (p = lo+width; p <= hi; p += width) {
+ /* A[i] <= A[max] for lo <= i < p */
+ if (comp(p, max, context) > 0) {
+ max = p;
+ }
+ /* A[i] <= A[max] for lo <= i <= p */
+ }
+
+ /* A[i] <= A[max] for lo <= i <= hi */
+
+ swap128(max, hi);
+
+ /* A[i] <= A[hi] for i <= hi, so A[i] <= A[j] for i <= j, j >= hi */
+
+ hi -= width;
+
+ /* A[i] <= A[j] for i <= j, j > hi, loop top condition established */
+ }
+ /* A[i] <= A[j] for i <= j, j > lo, which implies A[i] <= A[j] for i < j,
+ so array is sorted */
+}
+
+#define CUTOFF 8
+#define STKSIZ (8*sizeof(void*) - 2)
+extern "C" void qsort_itemRecord(void *base, size_t num, const void *context,
+ int (__fastcall *comp)(const void *, const void *, const void *))
+{
+ /* Note: the number of stack entries required is no more than
+ 1 + log2(num), so 30 is sufficient for any array */
+ uint8_t *lo, *hi; /* ends of sub-array currently sorting */
+ uint8_t *mid; /* points to middle of subarray */
+ uint8_t *loguy, *higuy; /* traveling pointers for partition step */
+ size_t size; /* size of the sub-array */
+ uint8_t *lostk[STKSIZ], *histk[STKSIZ];
+ int stkptr; /* stack for saving sub-array to be processed */
+ const size_t width=sizeof(itemRecordW);
+
+#ifdef _M_IX86
+ assert(sizeof(itemRecordW) == 128);
+#elif defined (_M_X64)
+ assert(sizeof(itemRecordW) == 176);
+#else
+#error not implemented on this processor!
+#endif
+ if (!sse_flag)
+ {
+ nu::qsort(base, num, sizeof(itemRecordW), context, comp);
+ return;
+ }
+
+ if (num < 2)
+ return; /* nothing to do */
+
+ stkptr = 0; /* initialize stack */
+
+ lo = static_cast<uint8_t *>(base);
+ hi = (uint8_t *)base + width * (num-1); /* initialize limits */
+
+ /* this entry point is for pseudo-recursion calling: setting
+ lo and hi and jumping to here is like recursion, but stkptr is
+ preserved, locals aren't, so we preserve stuff on the stack */
+recurse:
+
+ size = (hi - lo) / width + 1; /* number of el's to sort */
+
+ /* below a certain size, it is faster to use a O(n^2) sorting method */
+ if (size <= CUTOFF) {
+ shortsort_sse(lo, hi, context, comp);
+ }
+ else {
+ /* First we pick a partitioning element. The efficiency of the
+ algorithm demands that we find one that is approximately the median
+ of the values, but also that we select one fast. We choose the
+ median of the first, middle, and last elements, to avoid bad
+ performance in the face of already sorted data, or data that is made
+ up of multiple sorted runs appended together. Testing shows that a
+ median-of-three algorithm provides better performance than simply
+ picking the middle element for the latter case. */
+
+ mid = lo + (size / 2) * width; /* find middle element */
+
+ /* Sort the first, middle, last elements into order */
+ if (comp(lo, mid, context) > 0) {
+ swap128(lo, mid);
+ }
+ if (comp(lo, hi, context) > 0) {
+ swap128(lo, hi);
+ }
+ if (comp(mid, hi, context) > 0) {
+ swap128(mid, hi);
+ }
+
+ /* We now wish to partition the array into three pieces, one consisting
+ of elements <= partition element, one of elements equal to the
+ partition element, and one of elements > than it. This is done
+ below; comments indicate conditions established at every step. */
+
+ loguy = lo;
+ higuy = hi;
+
+ /* Note that higuy decreases and loguy increases on every iteration,
+ so loop must terminate. */
+ for (;;) {
+ /* lo <= loguy < hi, lo < higuy <= hi,
+ A[i] <= A[mid] for lo <= i <= loguy,
+ A[i] > A[mid] for higuy <= i < hi,
+ A[hi] >= A[mid] */
+
+ /* The doubled loop is to avoid calling comp(mid,mid), since some
+ existing comparison funcs don't work when passed the same
+ value for both pointers. */
+
+ if (mid > loguy) {
+ do {
+ loguy += width;
+ } while (loguy < mid && comp(loguy, mid, context) <= 0);
+ }
+ if (mid <= loguy) {
+ do {
+ loguy += width;
+ } while (loguy <= hi && comp(loguy, mid, context) <= 0);
+ }
+
+ /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
+ either loguy > hi or A[loguy] > A[mid] */
+
+ do {
+ higuy -= width;
+ } while (higuy > mid && comp(higuy, mid, context) > 0);
+
+ /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi,
+ either higuy == lo or A[higuy] <= A[mid] */
+
+ if (higuy < loguy)
+ break;
+
+ /* if loguy > hi or higuy == lo, then we would have exited, so
+ A[loguy] > A[mid], A[higuy] <= A[mid],
+ loguy <= hi, higuy > lo */
+
+ swap128(loguy, higuy);
+
+ /* If the partition element was moved, follow it. Only need
+ to check for mid == higuy, since before the swap,
+ A[loguy] > A[mid] implies loguy != mid. */
+
+ if (mid == higuy)
+ mid = loguy;
+
+ /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
+ of loop is re-established */
+ }
+
+ /* A[i] <= A[mid] for lo <= i < loguy,
+ A[i] > A[mid] for higuy < i < hi,
+ A[hi] >= A[mid]
+ higuy < loguy
+ implying:
+ higuy == loguy-1
+ or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */
+
+ /* Find adjacent elements equal to the partition element. The
+ doubled loop is to avoid calling comp(mid,mid), since some
+ existing comparison funcs don't work when passed the same value
+ for both pointers. */
+
+ higuy += width;
+ if (mid < higuy) {
+ do {
+ higuy -= width;
+ } while (higuy > mid && comp(higuy, mid, context) == 0);
+ }
+ if (mid >= higuy) {
+ do {
+ higuy -= width;
+ } while (higuy > lo && comp(higuy, mid, context) == 0);
+ }
+
+ /* OK, now we have the following:
+ higuy < loguy
+ lo <= higuy <= hi
+ A[i] <= A[mid] for lo <= i <= higuy
+ A[i] == A[mid] for higuy < i < loguy
+ A[i] > A[mid] for loguy <= i < hi
+ A[hi] >= A[mid] */
+
+ /* We've finished the partition, now we want to sort the subarrays
+ [lo, higuy] and [loguy, hi].
+ We do the smaller one first to minimize stack usage.
+ We only sort arrays of length 2 or more.*/
+
+ if ( higuy - lo >= hi - loguy ) {
+ if (lo < higuy) {
+ lostk[stkptr] = lo;
+ histk[stkptr] = higuy;
+ ++stkptr;
+ } /* save big recursion for later */
+
+ if (loguy < hi) {
+ lo = loguy;
+ goto recurse; /* do small recursion */
+ }
+ }
+ else {
+ if (loguy < hi) {
+ lostk[stkptr] = loguy;
+ histk[stkptr] = hi;
+ ++stkptr; /* save big recursion for later */
+ }
+
+ if (lo < higuy) {
+ hi = higuy;
+ goto recurse; /* do small recursion */
+ }
+ }
+ }
+
+ /* We have sorted the array, except for any pending sorts on the stack.
+ Check if there are any, and do them. */
+
+ --stkptr;
+ if (stkptr >= 0) {
+ lo = lostk[stkptr];
+ hi = histk[stkptr];
+ goto recurse; /* pop subarray from stack */
+ }
+ else
+ return; /* all subarrays done */
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/pe_subclass.cpp b/Src/Plugins/Library/ml_local/pe_subclass.cpp
new file mode 100644
index 00000000..67de6b1f
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/pe_subclass.cpp
@@ -0,0 +1,76 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "resource.h"
+
+static WNDPROC PE_oldWndProc;
+static HMENU last_viewmenu = NULL;
+static WORD waCmdMenuID;
+
+static INT_PTR CALLBACK PE_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_COMMAND && wParam > 45000 && wParam < 57000)
+ {
+ int n = wParam - 45000;
+ if (m_query_list[n])
+ {
+ queryItem *item = m_query_list[n];
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf, item->query, 0, 0);
+
+ return 0;
+ }
+ }
+ else if (uMsg == WM_INITMENUPOPUP)
+ {
+ HMENU hmenuPopup = (HMENU) wParam;
+ if (hmenuPopup == wa_playlists_cmdmenu)
+ {
+ if (!waCmdMenuID)
+ {
+ waCmdMenuID = (WORD)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_REGISTER_LOWORD_COMMAND);
+ }
+ if (last_viewmenu)
+ {
+ RemoveMenu(wa_playlists_cmdmenu, waCmdMenuID, MF_BYCOMMAND);
+ DestroyMenu(last_viewmenu);
+ last_viewmenu = NULL;
+ }
+
+ mlGetTreeStruct mgts = { 1000, 45000, -1};
+ last_viewmenu = (HMENU)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & mgts, ML_IPC_GETTREE);
+ if (last_viewmenu)
+ {
+ MENUITEMINFOW menuItem = {sizeof(MENUITEMINFOW), MIIM_SUBMENU | MIIM_ID | MIIM_TYPE, MFT_STRING,
+ MFS_ENABLED, waCmdMenuID, last_viewmenu, NULL, NULL, NULL,
+ WASABI_API_LNGSTRINGW(IDS_OPEN_MEDIA_LIBRARY_VIEW_RESULTS), 0};
+
+ if (GetMenuItemCount(last_viewmenu) > 0)
+ {
+ InsertMenuItemW(wa_playlists_cmdmenu, 1, TRUE, &menuItem);
+ }
+ else
+ {
+ DestroyMenu(last_viewmenu);
+ last_viewmenu=0;
+ }
+ }
+ }
+ }
+ return CallWindowProc(PE_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+static HWND hwnd_pe = NULL;
+void HookPlaylistEditor()
+{
+ hwnd_pe = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, IPC_GETWND_PE, IPC_GETWND);
+
+ if (IsWindow(hwnd_pe))
+ PE_oldWndProc = (WNDPROC) SetWindowLongPtrW(hwnd_pe, GWLP_WNDPROC, (LONG_PTR)PE_newWndProc);
+}
+
+void UnhookPlaylistEditor()
+{
+ SetWindowLongPtrW(hwnd_pe, GWLP_WNDPROC, (LONG_PTR)PE_oldWndProc);
+}
diff --git a/Src/Plugins/Library/ml_local/prefs.cpp b/Src/Plugins/Library/ml_local/prefs.cpp
new file mode 100644
index 00000000..ffeef4da
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/prefs.cpp
@@ -0,0 +1,936 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "ml_local.h"
+#include <time.h>
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "./scanfolderbrowser.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/AutoWide.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+extern HWND subWnd = 0, prefsWnd = 0;
+extern int g_bgrescan_int, g_bgrescan_do, g_autochannel_do;
+HWND g_bgrescan_status_hwnd;
+static W_ListView *m_dir_lv;
+
+
+static void EnableDisableRecentPlayingControls( HWND hwndDlg, int tracking )
+{
+ if ( tracking == -1 ) tracking = !!g_config->ReadInt( L"trackplays", 1 );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_CHECK7 ), tracking );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_CHECK8 ), tracking );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT2 ), tracking && !!g_config->ReadInt( L"trackplays_wait_secs", 0 ) );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT3 ), tracking && !!g_config->ReadInt( L"trackplays_wait_percent", 0 ) );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC4 ), tracking );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC5 ), tracking );
+}
+
+// Recently Played
+// When 'Recently Played' is enabled, Winamp will keep track of\nwhen and how many times items in the Media Library are played.
+static INT_PTR CALLBACK Prefs1Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ CheckDlgButton( hwndDlg, IDC_REMEMBER_SEARCH, !!g_config->ReadInt( L"remembersearch", 0 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK3, !!g_config->ReadInt( L"useminiinfo2", 0 ) );
+
+ CheckDlgButton( hwndDlg, IDC_CHECK_ATF, !!g_config->ReadInt( L"newtitle", 1 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK5, !!g_config->ReadInt( L"audiorefine", 0 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK6, !g_config->ReadInt( L"dbloadatstart", 1 ) );
+
+ CheckDlgButton( hwndDlg, IDC_ARTIST_AS_ALBUMARTIST, g_config->ReadInt( L"artist_as_albumartist", 1 ) );
+
+ SetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, g_config->ReadInt( L"querydelay", 250 ), 0 );
+
+ int tracking = !!g_config->ReadInt( L"trackplays", 1 );
+ CheckDlgButton( hwndDlg, IDC_CHECK2, tracking );
+ CheckDlgButton( hwndDlg, IDC_CHECK7, !!g_config->ReadInt( L"trackplays_wait_secs", 0 ) );
+ CheckDlgButton( hwndDlg, IDC_CHECK8, !!g_config->ReadInt( L"trackplays_wait_percent", 0 ) );
+ SetDlgItemInt( hwndDlg, IDC_EDIT2, g_config->ReadInt( L"trackplays_wait_secs_lim", 5 ), 0 );
+ SetDlgItemInt( hwndDlg, IDC_EDIT3, g_config->ReadInt( L"trackplays_wait_percent_lim", 50 ), 0 );
+ EnableDisableRecentPlayingControls( hwndDlg, tracking );
+ }
+ break;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_CHECK3:
+ g_config->WriteInt( L"useminiinfo2", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK3 ) );
+ PostMessage( plugin.hwndLibraryParent, WM_USER + 30, 0, 0 );
+ break;
+ case IDC_REMEMBER_SEARCH:
+ g_config->WriteInt( L"remembersearch", !!IsDlgButtonChecked( hwndDlg, IDC_REMEMBER_SEARCH ) );
+ break;
+ case IDC_CHECK_ATF:
+ g_config->WriteInt( L"newtitle", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK_ATF ) );
+ break;
+ case IDC_CHECK5:
+ g_config->WriteInt( L"audiorefine", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK5 ) );
+ if ( plugin.hwndLibraryParent ) PostMessage( plugin.hwndLibraryParent, WM_USER + 30, 0, 0 );
+ break;
+ case IDC_CHECK6:
+ g_config->WriteInt( L"dbloadatstart", !IsDlgButtonChecked( hwndDlg, IDC_CHECK6 ) );
+ break;
+ case IDC_CHECK2:
+ {
+ int tracking = !!IsDlgButtonChecked( hwndDlg, IDC_CHECK2 );
+ g_config->WriteInt( L"trackplays", tracking );
+ EnableDisableRecentPlayingControls( hwndDlg, tracking );
+ }
+ break;
+ case IDC_CHECK7:
+ {
+ g_config->WriteInt( L"trackplays_wait_secs", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK7 ) );
+ EnableDisableRecentPlayingControls( hwndDlg, -1 );
+ }
+ break;
+ case IDC_CHECK8:
+ {
+ g_config->WriteInt( L"trackplays_wait_percent", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK8 ) );
+ EnableDisableRecentPlayingControls( hwndDlg, -1 );
+ }
+ break;
+ case IDC_BUTTON2:
+ {
+ wchar_t title[ 64 ] = { 0 };
+ WASABI_API_LNGSTRINGW_BUF( IDS_RECENTLY_PLAYED, title, 64 );
+ MessageBoxW( hwndDlg, WASABI_API_LNGSTRINGW( IDS_RECENTLY_PLAYED_TEXT ), title, 0 );
+ }
+ break;
+ case IDC_ARTIST_AS_ALBUMARTIST:
+ {
+ int oldValue = g_config->ReadInt( L"artist_as_albumartist", 1 );
+ int newValue = !!IsDlgButtonChecked( hwndDlg, IDC_ARTIST_AS_ALBUMARTIST );
+ if ( oldValue != newValue )
+ {
+ // TODO: prompt to re-read metadata on entire library
+ }
+ g_config->WriteInt( L"artist_as_albumartist", newValue );
+ }
+ break;
+ case IDC_EDIT_QUERYDELAY:
+ {
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ BOOL t;
+ int v = GetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, &t, FALSE );
+ if ( t )
+ {
+ if ( v < 1 )
+ {
+ v = 1;
+ SetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, v, 0 );
+ }
+ else if ( v > 5000 )
+ {
+ v = 5000;
+ SetDlgItemInt( hwndDlg, IDC_EDIT_QUERYDELAY, v, 0 );
+ }
+ g_config->WriteInt( L"querydelay", v );
+ g_querydelay = v;
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON1:
+ nukeLibrary( hwndDlg );
+ break;
+ case IDC_EDIT2:
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ BOOL t;
+ int v = GetDlgItemInt( hwndDlg, IDC_EDIT2, &t, FALSE );
+ if ( t )
+ {
+ if ( v < 0 )
+ {
+ v = 1;
+ SetDlgItemInt( hwndDlg, IDC_EDIT2, v, 0 );
+ }
+ g_config->WriteInt( L"trackplays_wait_secs_lim", v );
+ }
+ }
+ break;
+ case IDC_EDIT3:
+ if ( HIWORD( wParam ) == EN_CHANGE )
+ {
+ BOOL t;
+ int v = GetDlgItemInt( hwndDlg, IDC_EDIT3, &t, FALSE );
+ if ( t )
+ {
+ int tweaked = 0;
+ if ( v > 99 )
+ {
+ v = 99;
+ tweaked = 1;
+ }
+ else if ( v < 1 )
+ {
+ v = 1;
+ tweaked = 1;
+ }
+ if ( tweaked )
+ {
+ SetDlgItemInt( hwndDlg, IDC_EDIT3, v, 0 );
+ }
+
+ g_config->WriteInt( L"trackplays_wait_percent_lim", v );
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static void parseMetaStr( wchar_t *buf2, int *guess, int *meta, int *subdir )
+{
+ wchar_t metaPS[ 16 ] = { 0 }, metaMS[ 16 ] = { 0 }, smartPS[ 16 ] = { 0 }, smartMS[ 16 ] = { 0 }, guessS[ 16 ] = { 0 }, recurseS[ 16 ] = { 0 };
+ StringCchPrintfW( recurseS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( guessS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_GUESS_STR ) );
+ StringCchPrintfW( smartPS, 16, L"+%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ StringCchPrintfW( smartMS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ StringCchPrintfW( metaPS, 16, L"+%s", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+ StringCchPrintfW( metaMS, 16, L"-%s", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+
+ if ( wcsstr( buf2, metaPS ) )
+ {
+ *meta = 1;
+ }
+ else if ( wcsstr( buf2, metaMS ) )
+ {
+ *meta = 0;
+ }
+ else
+ {
+ *meta = -1;
+ }
+ if ( wcsstr( buf2, smartPS ) )
+ {
+ *guess = 0;
+ }
+ else if ( wcsstr( buf2, smartMS ) )
+ {
+ *guess = 1;
+ }
+ else if ( wcsstr( buf2, guessS ) )
+ {
+ *guess = 2;
+ }
+ else
+ {
+ *guess = -1;
+ }
+ if ( wcsstr( buf2, recurseS ) )
+ {
+ *subdir = 0;
+ }
+ else
+ {
+ *subdir = 1;
+ }
+}
+
+static void makeMetaStr( int guess_mode, int use_metadata, int subdir, wchar_t *buf, int bufLen )
+{
+ wchar_t guessstr[ 32 ] = { 0 }, recurseS[ 16 ] = { 0 };
+ if ( guess_mode >= 0 )
+ {
+ if ( guess_mode == 1 )
+ StringCchPrintfW( guessstr, 32, L"-%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ else if ( guess_mode == 2 )
+ StringCchPrintfW( guessstr, 32, L"-%s", WASABI_API_LNGSTRINGW( IDS_GUESS_STR ) );
+ else
+ StringCchPrintfW( guessstr, 32, L"+%s", WASABI_API_LNGSTRINGW( IDS_SMART_STR ) );
+ }
+
+ if ( use_metadata >= 0 && guess_mode >= 0 )
+ {
+ wchar_t bufS[ 64 ] = { 0 };
+ StringCchPrintfW( bufS, 64, L"%%c%s%%s%%s", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+ StringCchPrintfW( recurseS, 16, L"%s%s", subdir ? L"" : L"-", subdir ? L"" : WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( buf, bufLen, bufS, use_metadata ? L'+' : L'-', guessstr, recurseS );
+ }
+ else if ( use_metadata >= 0 )
+ {
+ wchar_t metaS[ 16 ] = { 0 };
+ StringCchPrintfW( metaS, 16, L"%s%s", use_metadata ? L"+" : L"-", WASABI_API_LNGSTRINGW( IDS_META_STR ) );
+ StringCchPrintfW( recurseS, 16, L"%s%s", subdir ? L"" : L"-", subdir ? L"" : WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( buf, bufLen, L"%s%s", metaS, recurseS );
+ }
+ else if ( guess_mode >= 0 )
+ {
+ StringCchPrintfW( recurseS, 16, L"%s%s", subdir ? L"" : L"-", subdir ? L"" : WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ StringCchPrintfW( buf, bufLen, L"%s%s", guessstr, recurseS );
+ }
+ else if ( !subdir )
+ {
+ StringCchPrintfW( buf, bufLen, L"-%s", WASABI_API_LNGSTRINGW( IDS_RECURSE_STR ) );
+ }
+ else
+ {
+ StringCchCopyW( buf, bufLen, WASABI_API_LNGSTRINGW( IDS_DEFAULT ) );
+ }
+}
+
+static void saveList()
+{
+ GayStringW gs;
+ int a = 0;
+ int x, l = m_dir_lv->GetCount();
+ for ( x = 0; x < l; x++ )
+ {
+ wchar_t buf[ 1024 ] = { 0 }, buf2[ 64 ] = { 0 };
+ m_dir_lv->GetText( x, 0, buf2, 64 );
+ m_dir_lv->GetText( x, 1, buf, 1024 );
+ if ( buf[ 0 ] )
+ {
+ if ( a )
+ gs.Append( L"|" );
+ else
+ a = 1;
+
+ int meta;
+ int guess;
+ int subdir;
+ parseMetaStr( buf2, &guess, &meta, &subdir );
+
+ if ( meta >= 0 || guess >= 0 || !subdir )
+ {
+ gs.Append( L"<" );
+ if ( guess >= 0 )
+ gs.Append( guess == 1 ? L"s" : ( guess == 2 ? L"g" : L"S" ) );
+
+ if ( meta >= 0 )
+ gs.Append( meta ? L"M" : L"m" );
+
+ if ( !subdir )
+ gs.Append( L"r" );
+
+ gs.Append( L">" );
+ }
+ gs.Append( buf );
+ }
+ }
+ g_config->WriteString( "scandirlist", 0 ); // erase the old ini value
+ g_config->WriteString( "scandirlist_utf8", AutoChar( gs.Get(), CP_UTF8 ) );
+}
+
+int autoscan_add_directory( const wchar_t *path, int *guess, int *meta, int *recurse, int noaddjustcheck )
+{
+ char *_s1 = g_config->ReadString( "scandirlist", "" );
+ UINT codePage = CP_ACP;
+ if ( !_s1 || !*_s1 )
+ {
+ _s1 = g_config->ReadString( "scandirlist_utf8", "" );
+ codePage = CP_UTF8;
+ }
+
+ wchar_t *s1 = AutoWideDup( _s1, codePage );
+
+ int bufLen = 0;
+ wchar_t *s = (wchar_t *)calloc( ( bufLen = ( wcslen( s1 ) + 2 ) ), sizeof( wchar_t ) );
+ if ( s )
+ {
+ wcsncpy( s, s1, bufLen );
+ s[ wcslen( s ) + 1 ] = 0;
+
+ wchar_t *p = s;
+ while ( p && *p == L'|' ) p++;
+
+ while ( ( p = wcsstr( p, L"|" ) ) )
+ {
+ *p++ = 0;
+ while ( p && *p == L'|' ) p++;
+ }
+ p = s;
+ while ( p && *p )
+ {
+ while ( p && *p == L'|' ) p++;
+
+ int use_meta = -1;
+ int do_guess = -1;
+ int do_recurse = 1;
+ if ( *p == L'<' && wcsstr( p, L">" ) )
+ {
+ p++;
+ while ( p && *p != L'>' )
+ {
+ // <MmSs>can prefix directory
+ // M=metadata use override
+ // m=no metadata
+ // S=smart guessing
+ // s=stupid guessing
+ // g=no guessing
+ if ( *p == L'M' )
+ use_meta = 1;
+ else if ( *p == L'm' )
+ use_meta = 0;
+ else if ( *p == L'S' )
+ do_guess = 0;
+ else if ( *p == L's' )
+ do_guess = 1;
+ else if ( *p == L'g' )
+ do_guess = 2;
+ else if ( *p == L'r' )
+ do_recurse = 0;
+
+ p++;
+ }
+ p++;
+ }
+
+ if ( !_wcsnicmp( p, path, wcslen( p ) ) && ( path[ wcslen( p ) ] == L'\\' || !path[ wcslen( p ) ] ) )
+ {
+ free( s ); // directory already in there
+
+ if ( meta )
+ *meta = use_meta;
+
+ if ( guess )
+ *guess = do_guess;
+
+ if ( recurse )
+ *recurse = do_recurse;
+
+ return 1;
+ }
+ p += wcslen( p ) + 1;
+ }
+ free( s );
+ }
+
+ if ( !noaddjustcheck )
+ {
+ WIN32_FIND_DATAW fd = { 0 };
+ int bufLen = 0;
+ wchar_t *s = (wchar_t *)calloc( ( bufLen = ( wcslen( path ) + 32 ) ), sizeof( wchar_t ) );
+ StringCchPrintfW( s, bufLen, L"%s\\*.*", path );
+ HANDLE h = FindFirstFileW( s, &fd );
+ free( s );
+ if ( h != INVALID_HANDLE_VALUE )
+ {
+ FindClose( h ); // we are a directory, yay
+ GayStringW gs;
+ gs.Set( s1 );
+
+ if ( s1[ 0 ] )
+ gs.Append( L"|" );
+
+ gs.Append( path );
+
+ g_config->WriteString( "scandirlist", 0 );
+ g_config->WriteString( "scandirlist_utf8", AutoCharDup( gs.Get(), codePage, 0 ) );
+
+ if ( m_dir_lv )
+ {
+ int x = m_dir_lv->GetCount();
+ m_dir_lv->InsertItem( x, WASABI_API_LNGSTRINGW( IDS_DEFAULT ), 0 );
+ m_dir_lv->SetItemText( x, 1, path );
+ }
+ }
+ }
+ return 0;
+}
+
+static int m_edit_idx;
+static INT_PTR CALLBACK editDirProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t buf[ 1024 ] = { 0 };
+ SHAutoComplete( GetDlgItem( hwndDlg, IDC_EDIT1 ), SHACF_AUTOAPPEND_FORCE_ON | SHACF_AUTOSUGGEST_FORCE_ON | /*SHACF_FILESYS_DIRS*/0x020 | 0x010 | SHACF_USETAB );
+ m_dir_lv->GetText( m_edit_idx, 1, buf, 1024 );
+ SetDlgItemTextW( hwndDlg, IDC_EDIT1, buf );
+ m_dir_lv->GetText( m_edit_idx, 0, buf, 1024 );
+ int guess, meta, subdir;
+ parseMetaStr( buf, &guess, &meta, &subdir );
+ if ( meta )
+ CheckDlgButton( hwndDlg, IDC_CHECK1, meta > 0 ? BST_CHECKED : BST_INDETERMINATE );
+
+ if ( guess < 0 )
+ CheckDlgButton( hwndDlg, IDC_RADIO6, BST_CHECKED );
+ else if ( !guess )
+ CheckDlgButton( hwndDlg, IDC_RADIO1, BST_CHECKED );
+ else if ( guess == 2 )
+ CheckDlgButton( hwndDlg, IDC_RADIO8, BST_CHECKED );
+ else if ( guess > 0 )
+ CheckDlgButton( hwndDlg, IDC_RADIO2, BST_CHECKED );
+
+ if ( subdir ) CheckDlgButton( hwndDlg, IDC_CHECK2, BST_CHECKED );
+ }
+ return 0;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ {
+ //save to m_edit_idx :)
+ wchar_t buf[ 2048 ] = { 0 };
+ GetDlgItemTextW( hwndDlg, IDC_EDIT1, buf, 2048 );
+ m_dir_lv->SetItemText( m_edit_idx, 1, buf );
+
+ int meta = IsDlgButtonChecked( hwndDlg, IDC_CHECK1 );
+ if ( meta == BST_CHECKED )
+ meta = 1;
+ else if ( meta == BST_INDETERMINATE )
+ meta = -1;
+ else
+ meta = 0;
+
+ int guess = -1;
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO1 ) )
+ guess = 0;
+ else if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO2 ) )
+ guess = 1;
+ else if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO8 ) )
+ guess = 2;
+
+ int subdir = 1;
+ if ( !IsDlgButtonChecked( hwndDlg, IDC_CHECK2 ) )
+ subdir = 0;
+
+ makeMetaStr( guess, meta, subdir, buf, 64 );
+ m_dir_lv->SetItemText( m_edit_idx, 0, buf );
+ EndDialog( hwndDlg, 1 );
+ }
+ return 0;
+ case IDCANCEL:
+ EndDialog( hwndDlg, 0 );
+ return 0;
+ case IDC_BUTTON1:
+ wchar_t path[ MAX_PATH ] = { 0 };
+ GetDlgItemTextW( hwndDlg, IDC_EDIT1, path, MAX_PATH );
+ ScanFolderBrowser browse( FALSE );
+ browse.SetSelection( path );
+ if ( browse.Browse( hwndDlg ) )
+ {
+ wchar_t path[ MAX_PATH ] = { 0 };
+ SHGetPathFromIDListW( browse.GetPIDL(), path );
+ SetDlgItemTextW( hwndDlg, IDC_EDIT1, path );
+ }
+ return 0;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+void hideShowMetadataRadioboxes( HWND hwndDlg )
+{
+ int enabled = IsDlgButtonChecked( hwndDlg, IDC_CHECK1 );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC1 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_STATIC2 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_COMBO1 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_RADIO1 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_RADIO2 ), enabled );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_RADIO3 ), enabled );
+}
+
+static INT_PTR CALLBACK confMetaProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ if ( g_config->ReadInt( L"usemetadata", 1 ) ) CheckDlgButton( hwndDlg, IDC_CHECK1, BST_CHECKED );
+ {
+ int gm = g_config->ReadInt( L"guessmode", 0 );
+ CheckDlgButton( hwndDlg, gm == 2 ? IDC_RADIO3 : ( gm == 1 ? IDC_RADIO2 : IDC_RADIO1 ), BST_CHECKED );
+ }
+
+ SendDlgItemMessageW( hwndDlg, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW( IDS_ANY ) );
+ SendDlgItemMessageW( hwndDlg, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW( IDS_ALL ) );
+ SendDlgItemMessage( hwndDlg, IDC_COMBO1, CB_SETCURSEL, g_guessifany ? 0 : 1, 0 );
+ hideShowMetadataRadioboxes( hwndDlg );
+ }
+ break;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDOK:
+ g_config->WriteInt( L"usemetadata", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK1 ) );
+ {
+ int a = SendDlgItemMessage( hwndDlg, IDC_COMBO1, CB_GETCURSEL, 0, 0 );
+ g_guessifany = ( a == 0 );
+ g_config->WriteInt( L"guessifany", g_guessifany );
+ }
+
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO1 ) )
+ g_config->WriteInt( L"guessmode", 0 );
+
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO2 ) )
+ g_config->WriteInt( L"guessmode", 1 );
+
+ if ( IsDlgButtonChecked( hwndDlg, IDC_RADIO3 ) )
+ g_config->WriteInt( L"guessmode", 2 );
+
+ case IDCANCEL:
+ EndDialog( hwndDlg, 0 );
+ break;
+ case IDC_CHECK1:
+ hideShowMetadataRadioboxes( hwndDlg );
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK Prefs3Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ static int m_last_isscanning, m_set_rescan_int;
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ m_last_isscanning = -1;
+ m_set_rescan_int = 0;
+
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ SetTimer( hwndDlg, 100, 1000, NULL );
+ g_bgrescan_status_hwnd = GetDlgItem( hwndDlg, IDC_STATUS );
+
+ CheckDlgButton( hwndDlg, IDC_CHECK4, g_bgrescan_do ? BST_CHECKED : 0 );
+ SendMessage( hwndDlg, WM_COMMAND, MAKEWPARAM( IDC_CHECK4, 0 ), 0 );
+ CheckDlgButton( hwndDlg, IDC_CHECK3, g_config->ReadInt( L"bgrescan_startup", 0 ) ? BST_CHECKED : 0 );
+ CheckDlgButton( hwndDlg, IDC_CHECK2, g_config->ReadInt( L"bgrescan_compact", 1 ) ? BST_CHECKED : 0 );
+ CheckDlgButton( hwndDlg, IDC_CHECK5, g_config->ReadInt( L"autoaddplays", 0 ) ? BST_CHECKED : 0 );
+
+ SendDlgItemMessage( hwndDlg, IDC_SPIN1, UDM_SETRANGE, 0, MAKELONG( 9999, 1 ) );
+ SetDlgItemInt( hwndDlg, IDC_EDIT3, g_bgrescan_int, FALSE );
+ m_set_rescan_int = 1;
+
+ delete m_dir_lv;
+ m_dir_lv = new W_ListView;
+ m_dir_lv->setwnd( GetDlgItem( hwndDlg, IDC_LIST1 ) );
+ m_dir_lv->AddCol( WASABI_API_LNGSTRINGW( IDS_OPTIONS ), 120 );
+ m_dir_lv->AddCol( WASABI_API_LNGSTRINGW( IDS_DIRECTORY ), 500 );
+ char *_s1 = g_config->ReadString( "scandirlist", "" );
+ UINT codePage = CP_ACP;
+ if ( !_s1 || !*_s1 )
+ {
+ _s1 = g_config->ReadString( "scandirlist_utf8", "" );
+ codePage = CP_UTF8;
+ }
+ wchar_t *s1 = AutoWideDup( _s1, codePage );
+ int bufLen = 0;
+ wchar_t *s = (wchar_t *)calloc( ( bufLen = ( wcslen( s1 ) + 2 ) ), sizeof( wchar_t ) );
+ if ( s )
+ {
+ wcsncpy( s, s1, bufLen );
+ s[ wcslen( s ) + 1 ] = 0;
+
+ wchar_t *p = s;
+ while ( p && *p == L'|' ) p++;
+
+ while ( ( p = wcsstr( p, L"|" ) ) )
+ {
+ *p++ = 0;
+ while ( p && *p == L'|' ) p++;
+ }
+ int x = 0;
+ p = s;
+ while ( p && *p )
+ {
+ while ( p && *p == L'|' ) p++;
+
+ int guess_mode = -1;
+ int use_metadata = -1;
+ int subdir = 1;
+ if ( *p == L'<' && wcsstr( p, L">" ) )
+ {
+ p++;
+ while ( p && *p != L'>' )
+ {
+ // <MmSs>can prefix directory
+ // M=metadata use override
+ // m=no metadata
+ // S=smart guessing
+ // s=stupid guessing
+ if ( *p == L'M' )
+ use_metadata = 1;
+ else if ( *p == L'm' )
+ use_metadata = 0;
+ else if ( *p == L'S' )
+ guess_mode = 0;
+ else if ( *p == L's' )
+ guess_mode = 1;
+ else if ( *p == L'r' )
+ subdir = 0;
+ else if ( *p == L'g' )
+ guess_mode = 2;
+
+ p++;
+ }
+ p++;
+ }
+ wchar_t buf[ 64 ] = { 0 };
+ makeMetaStr( guess_mode, use_metadata, subdir, buf, 64 );
+
+ m_dir_lv->InsertItem( x, buf, 0 );
+ m_dir_lv->SetItemText( x, 1, p );
+ x++;
+
+ p += wcslen( p ) + 1;
+ }
+ free( s );
+ }
+
+ if ( NULL != WASABI_API_APP )
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel( m_dir_lv->getwnd(), TRUE );
+ }
+ break;
+ // process this to update the ui state when a scan is running and we didn't manually run it
+ case WM_USER + 101:
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ SetTimer( hwndDlg, 100, 1000, NULL );
+ break;
+ case WM_TIMER:
+ if ( wParam == 100 )
+ {
+ extern int g_bgscan_scanning;
+ if ( !!g_bgscan_scanning != !!m_last_isscanning )
+ {
+ m_last_isscanning = !!g_bgscan_scanning;
+ SetDlgItemTextW( hwndDlg, IDC_RESCAN, WASABI_API_LNGSTRINGW( ( m_last_isscanning ? IDS_STOP_SCAN : IDS_RESCAN_NOW ) ) );
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch ( LOWORD( wParam ) )
+ {
+ case IDC_CHECK5:
+ g_config->WriteInt( L"autoaddplays", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK5 ) );
+ break;
+ case IDC_CHECK2:
+ g_config->WriteInt( L"bgrescan_compact", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK2 ) );
+ break;
+ case IDC_CHECK3:
+ g_config->WriteInt( L"bgrescan_startup", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK3 ) );
+ break;
+ case IDC_CHECK4:
+ g_config->WriteInt( L"bgrescan_do", g_bgrescan_do = !!IsDlgButtonChecked( hwndDlg, IDC_CHECK4 ) );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT3 ), g_bgrescan_do );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_SPIN1 ), g_bgrescan_do );
+ break;
+ case IDC_EDIT3:
+ {
+ if ( HIWORD( wParam ) == EN_CHANGE && m_set_rescan_int )
+ {
+ BOOL t;
+ int x = GetDlgItemInt( hwndDlg, IDC_EDIT3, &t, FALSE );
+ if ( t )
+ {
+ if ( x < 1 ) x = 1;
+ g_config->WriteInt( L"bgrescan_int", g_bgrescan_int = x );
+ extern time_t g_bgscan_last_rescan;
+ g_bgscan_last_rescan = time( NULL );
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON1:
+ {
+ ScanFolderBrowser browse( FALSE );
+ if ( browse.Browse( hwndDlg ) )
+ {
+ GayStringW n;
+ wchar_t path[ MAX_PATH ] = { 0 };
+
+ SHGetPathFromIDListW( browse.GetPIDL(), path );
+
+ char *_s1 = g_config->ReadString( "scandirlist", "" );
+ UINT codePage = CP_ACP;
+ if ( !_s1 || !*_s1 )
+ {
+ _s1 = g_config->ReadString( "scandirlist_utf8", "" );
+ codePage = CP_UTF8;
+ }
+
+ wchar_t *scanlist = AutoWide( _s1, codePage );
+ n.Set( scanlist );
+
+ if ( scanlist[ 0 ] )
+ n.Append( L"|" );
+
+ n.Append( path );
+ g_config->WriteString( "scandirlist", 0 );
+ g_config->WriteString( "scandirlist_utf8", AutoChar( n.Get(), CP_UTF8 ) );
+ int x = m_dir_lv->GetCount();
+ m_dir_lv->InsertItem( x, WASABI_API_LNGSTRINGW( IDS_DEFAULT ), 0 );
+ m_dir_lv->SetItemText( x, 1, path );
+ }
+ }
+ break;
+ case IDC_BUTTON4:
+ {
+ int x, l = m_dir_lv->GetCount(), s = 0;
+ for ( x = 0; x < l; x++ )
+ {
+ if ( m_dir_lv->GetSelected( x ) )
+ {
+ m_edit_idx = x;
+ if ( WASABI_API_DIALOGBOXW( IDD_EDITDIR, hwndDlg, editDirProc ) )
+ s = 1;
+ else
+ break;
+ }
+ }
+ if ( s )
+ {
+ saveList();
+ }
+ }
+ break;
+ case IDC_BUTTON3:
+ {
+ int x, l = m_dir_lv->GetCount();
+ int s = 0;
+ for ( x = 0; x < l; x++ )
+ {
+ if ( m_dir_lv->GetSelected( x ) )
+ {
+ s = 1;
+ m_dir_lv->DeleteItem( x-- );
+ l--;
+ }
+ }
+ if ( s )
+ {
+ saveList();
+ }
+ }
+ break;
+ case IDC_RESCAN:
+ extern int g_bgscan_scanning;
+ extern time_t g_bgscan_last_rescan;
+ if ( g_bgscan_scanning )
+ {
+ Scan_Cancel();
+ SetWindowTextW( g_bgrescan_status_hwnd, WASABI_API_LNGSTRINGW( IDS_RESCAN_ABORTED ) );
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ }
+ else
+ {
+ extern int openDb( void );
+ openDb();
+ if ( plugin.hwndLibraryParent ) SendMessage( plugin.hwndLibraryParent, WM_USER + 575, 0xffff00dd, 0 );
+ SendMessage( hwndDlg, WM_TIMER, 100, 0 );
+ }
+ break;
+ case IDC_CONFMETA:
+ WASABI_API_DIALOGBOXW( IDD_PREFS_METADATA, hwndDlg, confMetaProc );
+ break;
+ };
+ break;
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+ if ( l->idFrom == IDC_LIST1 && l->code == NM_DBLCLK )
+ {
+ SendMessage( hwndDlg, WM_COMMAND, IDC_BUTTON4, 0 );
+ }
+ }
+ break;
+ case WM_DESTROY:
+ m_set_rescan_int = 0;
+ g_bgrescan_status_hwnd = 0;
+ if ( NULL != WASABI_API_APP )
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel( m_dir_lv->getwnd(), FALSE );
+ delete m_dir_lv;
+ m_dir_lv = 0;
+ break;
+ }
+ return 0;
+}
+
+static void _dosetsel( HWND hwndDlg )
+{
+ HWND tabwnd = GetDlgItem( hwndDlg, IDC_TAB1 );
+ int sel = TabCtrl_GetCurSel( tabwnd );
+
+ if ( sel >= 0 && ( sel != g_config->ReadInt( L"lastprefp", 0 ) || !subWnd ) )
+ {
+ g_config->WriteInt( L"lastprefp", sel );
+ if ( subWnd )
+ DestroyWindow( subWnd );
+
+ subWnd = 0;
+
+ UINT t = 0;
+ DLGPROC p = NULL;
+ switch ( sel )
+ {
+ case 0: t = IDD_PREFS1; p = Prefs1Proc; break;
+ case 1: t = IDD_PREFS3; p = Prefs3Proc; break;
+ }
+
+ if ( t ) subWnd = WASABI_API_CREATEDIALOGW( t, hwndDlg, p );
+
+ if ( subWnd )
+ {
+ RECT r;
+ GetClientRect( tabwnd, &r );
+ TabCtrl_AdjustRect( tabwnd, FALSE, &r );
+ SetWindowPos( subWnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE );
+ ShowWindow( subWnd, SW_SHOWNA );
+ }
+
+ if ( !SendMessage( plugin.hwndWinampParent, WM_WA_IPC, IPC_ISWINTHEMEPRESENT, IPC_USE_UXTHEME_FUNC ) )
+ {
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)tabwnd, IPC_USE_UXTHEME_FUNC );
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)subWnd, IPC_USE_UXTHEME_FUNC );
+ }
+ }
+}
+
+// frame proc
+INT_PTR CALLBACK PrefsProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ switch ( uMsg )
+ {
+ case WM_INITDIALOG:
+ {
+ TCITEMW item;
+ HWND tabwnd = GetDlgItem( hwndDlg, IDC_TAB1 );
+ item.mask = TCIF_TEXT;
+ item.pszText = WASABI_API_LNGSTRINGW( IDS_OPTIONS );
+ SendMessageW( tabwnd, TCM_INSERTITEMW, 0, (LPARAM)&item );
+ item.pszText = WASABI_API_LNGSTRINGW( IDS_WATCH_FOLDERS );
+ SendMessageW( tabwnd, TCM_INSERTITEMW, 1, (LPARAM)&item );
+ TabCtrl_SetCurSel( tabwnd, g_config->ReadInt( L"lastprefp", 0 ) );
+ _dosetsel( hwndDlg );
+ prefsWnd = hwndDlg;
+ }
+ return 0;
+ case WM_NOTIFY:
+ {
+ LPNMHDR p = (LPNMHDR)lParam;
+ if ( p->idFrom == IDC_TAB1 && p->code == TCN_SELCHANGE ) _dosetsel( hwndDlg );
+ }
+ return 0;
+ case WM_DESTROY:
+ subWnd = NULL;
+ prefsWnd = NULL;
+ return 0;
+ }
+ return 0;
+}
+
+void refreshPrefs( int screen )
+{
+ if ( subWnd && g_config->ReadInt( L"lastprefp", -1 ) == screen )
+ {
+ if ( screen == 4 ) SendMessage( subWnd, WM_INITDIALOG, 0, 0 );
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/queries.cpp b/Src/Plugins/Library/ml_local/queries.cpp
new file mode 100644
index 00000000..6cfe338a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/queries.cpp
@@ -0,0 +1,1639 @@
+#include "main.h"
+#include "ml_local.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "../nde/nde.h"
+#include "editquery.h"
+#include "../nu/ComboBox.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/ns_wc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+const wchar_t *getFilterName(unsigned int filterId, wchar_t *buffer, size_t bufferSize);
+
+static int m_edit_item;
+
+void queryStrEscape(const wchar_t *p, GayStringW &str)
+{
+ if (!p || !*p) return;
+ size_t l = wcslen(p);
+ wchar_t *escaped = (wchar_t *)calloc((l*3+1), sizeof(wchar_t));
+ wchar_t *d = escaped;
+ while (p && *p) {
+ if (*p == L'%') { *d++ = L'%'; *d++ = L'%'; }
+ else if (*p == L'\"') { *d++ = L'%'; *d++ = L'2'; *d++ = L'2'; }
+ else if (*p == L'\'') { *d++ = L'%'; *d++ = L'2'; *d++ = L'7'; }
+ else if (*p == L'[') { *d++ = L'%'; *d++ = L'5'; *d++ = L'B'; }
+ else if (*p == L']') { *d++ = L'%'; *d++ = L'5'; *d++ = L'D'; }
+ else if (*p == L'(') { *d++ = L'%'; *d++ = L'2'; *d++ = L'8'; }
+ else if (*p == L')') { *d++ = L'%'; *d++ = L'2'; *d++ = L'9'; }
+ else if (*p == L'#') { *d++ = L'%'; *d++ = L'2'; *d++ = L'3'; }
+ else *d++ = *p;
+ p++;
+ }
+ *d = 0;
+ str.Set(escaped);
+ free(escaped);
+}
+
+static INT_PTR CALLBACK scrollChildHostProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK childAdvanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int m_item_mode=0,main_oninitdialog;
+int m_item_image = MLTREEIMAGE_DEFAULT;
+wchar_t m_item_name[256]=L"";
+wchar_t m_item_query[1024]=L"";
+
+typedef struct {
+ int title;
+ wchar_t *query;
+ char sort_by;
+ char sort_dir;
+ char *columns; //xff terminated list :). NULL means default columns
+ int imageIndex;
+ int mode;
+} smartViewPreset;
+
+#define ARTIST 0x01
+#define ALBUMARTIST 0x02
+#define GENRE 0x03
+#define PUBLISHER 0x04
+#define COMPOSER 0x05
+#define ALBUM 0x06
+#define YEAR 0x07
+#define ARTISTINDEX 0x08
+#define ALBUMARTISTINDEX 0x09
+#define PODCASTCHANNEL 0x0A
+#define ALBUMART 0x0B
+
+#define MAKEVIEW_3FILTER(a, b, c) (a | (b << 8) | (c << 16))
+#define MAKEVIEW_2FILTER(a, b) (a | (b << 8))
+
+static smartViewPreset presets[] = {
+ {IDS_AUDIO, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_VIDEO, L"type = 1", 10, 0, "\x7\1\5\x1E\6\3\x20\x8\x9\xA\xff", TREE_IMAGE_LOCAL_VIDEO,MAKEVIEW_2FILTER(GENRE,YEAR)},
+ {IDS_MOST_PLAYED, L"playcount > 0", 9, 0, "\x9\0\1\2\3\xA\xff", TREE_IMAGE_LOCAL_MOSTPLAYED,0},
+ {IDS_RECENTLY_ADDED, L"dateadded > [3 days ago]", 33, 0, "\x21\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYADDED,0},
+ {IDS_RECENTLY_MODIFIED, L"lastupd > [3 days ago]", 11, 0, "\xB\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYMODIFIED,0},
+ {IDS_RECENTLY_PLAYED, L"lastplay > [2 weeks ago]", 10, 0, "\xA\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_RECENTLYPLAYED,0},
+ {IDS_NEVER_PLAYED, L"playcount = 0 | playcount isempty", 0, 0, "\0\1\2\3\xff", TREE_IMAGE_LOCAL_NEVERPLAYED,0},
+ {IDS_TOP_RATED, L"rating >= 3", 8, 0, "\x8\x9\0\1\2\3\xff", TREE_IMAGE_LOCAL_TOPRATED,0},
+ {IDS_PODCASTS,L"ispodcast = 1", 0, 0, 0, TREE_IMAGE_LOCAL_PODCASTS, MAKEVIEW_3FILTER(GENRE,PODCASTCHANNEL,YEAR)},
+ {IDS_AUDIO_BY_GENRE, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_3FILTER(GENRE,ARTIST,ALBUM)},
+ {IDS_AUDIO_BY_INDEX, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_3FILTER(ARTISTINDEX,ARTIST,ALBUM)},
+ {IDS_SIMPLE_ALBUM, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 0x0100000B},
+ {IDS_60s_MUSIC, L"type = 0 and year >= 1960 and year < 1970", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_70s_MUSIC, L"type = 0 and year >= 1970 and year < 1980", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_80s_MUSIC, L"type = 0 and year >= 1980 and year < 1990", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_90s_MUSIC, L"type = 0 and year >= 1990 and year < 2000", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_00s_MUSIC, L"type = 0 and year >= 2000 and year < 2010", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_ROCK_MUSIC, L"type = 0 and genre has \"Rock\"", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, 1},
+ {IDS_CLASSICAL_MUSIC, L"type = 0 and genre has \"Classical\"", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_2FILTER(COMPOSER,ALBUM)},
+ {IDS_RECORD_LABELS, L"type = 0", 0, 0, NULL, TREE_IMAGE_LOCAL_AUDIO, MAKEVIEW_3FILTER(PUBLISHER,ALBUMARTIST,ALBUM)},
+ {IDS_CUSTOM, L"", 0, 0, NULL, MLTREEIMAGE_DEFAULT, 0},
+};
+
+#define NUM_PRESETS (sizeof(presets) / sizeof(smartViewPreset))
+
+void showdlgelements(HWND hwndDlg, int nCmdShow) {
+ const int dlgelems[] = {
+ IDC_STATIC_FILTER,
+ IDC_RADIO_SIMPLE,
+ IDC_IMAGE_SIMPLE,
+ IDC_STATIC_SIMPLE,
+ IDC_RADIO_SIMPLEALBUM,
+ IDC_IMAGE_SIMPLEALBUM,
+ IDC_STATIC_SIMPLEALBUM,
+ IDC_RADIO_TWOFILTERS,
+ IDC_IMAGE_TWOFILTERS,
+ IDC_STATIC_TWOFILTERS,
+ IDC_RADIO_THREEFILTERS,
+ IDC_IMAGE_THREEFILTERS,
+ IDC_STATIC_THREEFILTERS,
+ IDC_COMBO_FILTER1,
+ IDC_COMBO_FILTER2,
+ IDC_COMBO_FILTER3,
+ IDC_STATIC_FILTER2,
+ IDC_STATIC_FILTER3,
+ };
+ for(int i=0; i < (sizeof(dlgelems)/sizeof(int)); i++)
+ ShowWindow(GetDlgItem(hwndDlg,dlgelems[i]),nCmdShow);
+}
+
+#define SPL_RemoveAll(hwnd) SendMessage(hwnd,WM_USER+41,0,0)
+#define SPL_GetQueryString(hwnd) SendMessage(hwnd,WM_USER+40, 0 ,0)
+#define SPL_AddFilter(hwnd, filter) (HWND)scrollChildHostProc(hwnd,WM_USER+32, (WPARAM)filter, (LPARAM)1)
+#define SPL_AddBlankFilter(hwnd) (HWND)scrollChildHostProc(hwnd,WM_USER+32, 0, (LPARAM)2)
+#define SPL_RemoveFilter(hwnd, filterhwnd) SendMessage(hwnd,WM_USER+34,(WPARAM)filterhwnd,(LPARAM)-1)
+#define SPL_UpdateScroll(hwnd) SendMessage(hwnd,WM_USER+33,0,0)
+
+static int m_simple_dirty;
+
+void SetStaticItemImage(HWND hwndDlg, UINT ctrl_id, UINT ctrl_img)
+{
+ SendDlgItemMessage(hwndDlg,ctrl_id,STM_SETIMAGE,IMAGE_BITMAP,
+ (LPARAM)LoadImage(WASABI_API_ORIG_HINST,MAKEINTRESOURCE(ctrl_img),IMAGE_BITMAP,0,0,LR_SHARED));
+}
+
+int ResizeComboBoxDropDownW(HWND hwndDlg, UINT id, const wchar_t *str, int width){
+ SIZE size = {0};
+ HWND control = GetDlgItem(hwndDlg, id);
+ HDC hdc = GetDC(control);
+ // get and select parent dialog's font so that it'll calculate things correctly
+ HFONT font = (HFONT)SendMessage(hwndDlg,WM_GETFONT,0,0), oldfont = (HFONT)SelectObject(hdc,font);
+ GetTextExtentPoint32W(hdc, str, wcslen(str)+1, &size);
+
+ int ret = width;
+ if(size.cx > width)
+ {
+ SendDlgItemMessage(hwndDlg, id, CB_SETDROPPEDWIDTH, size.cx, 0);
+ ret = size.cx;
+ }
+
+ SelectObject(hdc, oldfont);
+ ReleaseDC(control, hdc);
+ return ret;
+}
+
+struct FiltersContext
+{
+ HWND last1, last2;
+ int mode;
+ HWND m_scrollchild;
+ int *error;
+};
+
+static bool EnumFilters(Scanner *scanner, nde_filter_t filter, void *context_in)
+{
+ FiltersContext *context = (FiltersContext *)context_in;
+
+ HWND x = SPL_AddFilter(context->m_scrollchild,filter);
+ if(x) { // we have a filter which talks about a field
+ context->last2=context->last1;
+ context->last1=x;
+ context->mode++;
+ if(context->mode > 2)
+ context->error[0]=1; // not in our strict form
+ } else { // we have an AND, OR or a NOT
+ int f=NDE_Filter_GetOp(filter);
+ if(f == FILTER_OR) CheckDlgButton(context->last2,IDC_OR,TRUE);
+ else if(f == FILTER_AND) CheckDlgButton(context->last2,IDC_AND,TRUE);
+ else context->error[0]=1; // we can't do FILTER_NOT
+ context->mode--;
+ if(context->mode != 1) context->error[0]=1; // not in our strict form
+ }
+ return !context->error[0];
+}
+
+static INT_PTR CALLBACK addQueryFrameDialogProc2(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static HWND m_scrollchild;
+ static int m_editting=0;
+ static C_Config *conf=0;
+ static wchar_t * m_item_meta;
+ static int mode;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ const int filters[] = {IDC_COMBO_FILTER1, IDC_COMBO_FILTER2,IDC_COMBO_FILTER3};
+ HWND controlWindow;
+ int iItem;
+ wchar_t buffer[512] = {0};
+
+ mode=0;
+ m_item_meta=0;
+ conf=0;
+ m_simple_dirty=0;
+
+ openDb();
+
+ CheckDlgButton(hwndDlg,IDC_RADIO_SIMPLE,TRUE);
+ SetDlgItemTextW(hwndDlg,IDC_STATIC_INFO, WASABI_API_LNGSTRINGW(IDS_WILLS_UBER_STRING));
+ EnableWindow(GetDlgItem(hwndDlg,IDC_HIDE_EXTINFO), g_config->ReadInt(L"useminiinfo2", 0));
+ // set up presets combo box
+ {
+ controlWindow = GetDlgItem(hwndDlg, IDC_COMBO_PRESETS);
+ if (NULL != controlWindow)
+ {
+ int width = 0;
+ for(size_t i=0; i < NUM_PRESETS; i++)
+ {
+ if (NULL != WASABI_API_LNGSTRINGW_BUF(presets[i].title, buffer, ARRAYSIZE(buffer)))
+ {
+ iItem = SendMessageW(controlWindow, CB_ADDSTRING, 0, (LPARAM)buffer);
+ if (CB_ERR != iItem)
+ {
+ width = ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO_PRESETS, buffer, width);
+ SendMessageW(controlWindow, CB_SETITEMDATA,(WPARAM)iItem, (LPARAM)(i + 1));
+ }
+ }
+ }
+ }
+ }
+
+ for(size_t i=0; i < ARRAYSIZE(filters); i++)
+ {
+ controlWindow = GetDlgItem(hwndDlg, filters[i]);
+ if (NULL != controlWindow)
+ {
+ int width = 0;
+ for(unsigned int filterId = 0;; filterId++)
+ {
+ if (NULL == getFilterName(filterId, buffer, ARRAYSIZE(buffer)))
+ break;
+
+ iItem = SendMessageW(controlWindow, CB_ADDSTRING, 0, (LPARAM)buffer);
+ if (CB_ERR != iItem)
+ {
+ width = ResizeComboBoxDropDownW(hwndDlg, filters[i], buffer, width);
+ SendMessageW(controlWindow, CB_SETITEMDATA,(WPARAM)iItem, (LPARAM)filterId);
+ }
+ }
+ }
+ }
+
+ if (lParam == 1)
+ {
+ if (m_edit_item == -1)
+ {
+ // nothing to do, m_item_name, m_item_query & m_item_mode have been set already
+ }
+ else
+ {
+ lstrcpynW(m_item_name,m_query_list[m_edit_item]->name,sizeof(m_item_name)/sizeof(wchar_t));
+ lstrcpynW(m_item_query,m_query_list[m_edit_item]->query,sizeof(m_item_query)/sizeof(wchar_t));
+ m_item_mode = m_query_list[m_edit_item]->mode;
+ m_item_image = m_query_list[m_edit_item]->imgIndex;
+
+ // re-select the preset name if we can get a match to a known preset (looks nicer and all that)
+ controlWindow = GetDlgItem(hwndDlg, IDC_COMBO_PRESETS);
+ if (NULL != controlWindow)
+ {
+ for(size_t i=0; i < NUM_PRESETS; i++)
+ {
+ if(m_item_query[0] && !_wcsicmp(presets[i].query, m_item_query)
+ && presets[i].mode == m_item_mode)
+ {
+ SendMessageW(controlWindow, CB_SETCURSEL, i, 0);
+ SendMessage(hwndDlg,WM_COMMAND,(WPARAM)MAKEWPARAM(IDC_COMBO_PRESETS,CBN_SELCHANGE),(LPARAM)GetDlgItem(hwndDlg,IDC_COMBO_PRESETS));
+ break;
+ }
+ }
+ }
+
+ // config
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, m_query_list[m_edit_item]->metafn);
+ conf = new C_Config(configDir);
+ CheckDlgButton(hwndDlg, IDC_HIDE_EXTINFO, conf->ReadInt(L"midivvis", 1) ? 0 : 1);
+ }
+ SetWindowTextW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_EDIT_SMART_VIEW));
+ m_editting=1;
+ }
+ else
+ {
+ m_editting=0;
+ if (m_item_mode != -1) m_item_mode=0;
+ m_item_name[0]=0;
+ m_item_query[0]=0;
+ m_item_image = MLTREEIMAGE_DEFAULT;
+
+ wchar_t filename[1024 + 256] = {0};
+ extern void makemetafn(wchar_t *filename, wchar_t **out);
+ makemetafn(filename, &m_item_meta);
+ conf = new C_Config(filename);
+ }
+
+ if(!mode) m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_SCROLLCHILDHOST,hwndDlg,scrollChildHostProc);
+ else m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_ADD_VIEW_CHILD_ADVANCED,hwndDlg,childAdvanced);
+
+ // this will make sure that we've got the little images shown in all languages (done for 5.51+)
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_SIMPLE,IDB_NEWFILTER_SIMPLE);
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_SIMPLEALBUM,IDB_NEWFILTER_SIMPLEALBUM);
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_TWOFILTERS,IDB_NEWFILTER_TWOFILTERS);
+ SetStaticItemImage(hwndDlg,IDC_IMAGE_THREEFILTERS,IDB_NEWFILTER_THREEFILTERS);
+
+ if(m_editting==0 && g_config->ReadInt(L"newfilter_showhelp",1))
+ {
+ showdlgelements(hwndDlg,SW_HIDE);
+ ShowWindow(hwndDlg,SW_SHOWNA);
+ EnableWindow(GetParent(hwndDlg),0);
+ break; // don't run through
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_INFO),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_SHOWINFO),SW_HIDE);
+ // run through
+ }
+ }
+ case WM_USER+9:
+ { // do stuff when we're shown or preset is changed
+ showdlgelements(hwndDlg,SW_SHOWNA);
+ ShowWindow(m_scrollchild,SW_SHOWNA);
+ // set up filter radio buttons and combo boxes
+ extern int GetFilter(int mode, int n);
+ extern int GetNumFilters(int mode);
+
+ int numFilters=GetNumFilters(m_item_mode);
+ int f[3] = {0};
+ f[0] = GetFilter(m_item_mode,0);
+ f[1] = GetFilter(m_item_mode,1);
+ f[2] = GetFilter(m_item_mode,2);
+
+ if(numFilters==0) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_SIMPLE);
+ else if(numFilters==1) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_SIMPLEALBUM);
+ else if(numFilters==2) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_TWOFILTERS);
+ else if(numFilters==3) CheckRadioButton(hwndDlg,IDC_RADIO_TWOFILTERS,IDC_RADIO_SIMPLEALBUM,IDC_RADIO_THREEFILTERS);
+
+ for(int i=0; i<3; i++) {
+ ComboBox comboBox(hwndDlg, (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3));
+ if(f[i]==0) comboBox.SelectItem(i==0?0:5);
+ else comboBox.SelectItem(f[i]-1);
+ }
+ // set up "name" edit control
+ SetDlgItemTextW(hwndDlg,IDC_NAME,m_item_name);
+
+ // do the query editor thing...
+ SendMessage(hwndDlg,WM_USER+11,0,0);
+ }
+ // run through
+ case WM_USER+10:
+ { // enable filter combo boxes based on radio buttons
+ int numFilters=0;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_SIMPLEALBUM)) numFilters=1;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_TWOFILTERS)) numFilters=2;
+ else if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_THREEFILTERS)) numFilters=3;
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_FILTER3),numFilters>2);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_FILTER2),numFilters>1);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_FILTER3),numFilters>2);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_FILTER2),numFilters>1);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_FILTER1),numFilters>1);
+
+ if (WM_INITDIALOG == uMsg)
+ {
+ // show edit info window and restore last position as applicable
+ POINT pt = {g_config->ReadInt(L"smart_x", -1), g_config->ReadInt(L"smart_y", -1)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOWNA);
+
+ HWND parentWindow = GetParent(hwndDlg);
+ if (NULL != parentWindow)
+ EnableWindow(parentWindow, 0);
+ }
+ /*else
+ ShowWindow(hwndDlg, SW_SHOW);*/
+ }
+ break;
+ case WM_USER+11:
+ if(!mode) {
+ SPL_RemoveAll(m_scrollchild);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t p=NDE_Table_CreateScanner(g_table);
+ ((Scanner *)p)->disable_date_resolution=1; // TODO: don't use C++ NDE api
+
+ int error=0; // not really an error, just we can't express it properly
+
+ if (m_item_query[0] && !NDE_Scanner_Query(p, m_item_query))
+ error=1; // ok, actually an error.
+
+ if (m_item_query[0] && !error)
+ {
+ FiltersContext context;
+ context.error = &error;
+ context.last1 = 0;
+ context.last2 = 0;
+ context.m_scrollchild = m_scrollchild;
+ context.mode = 0;
+ ((Scanner *)p)->WalkFilters((Scanner::FilterWalker)EnumFilters, &context); // TODO: don't use C++ NDE api
+ }
+
+ NDE_Table_DestroyScanner(g_table, p);
+ LeaveCriticalSection(&g_db_cs);
+ if(error) {
+ SPL_RemoveAll(m_scrollchild);
+ HWND x = SPL_AddBlankFilter(m_scrollchild);
+ ComboBox c(x,IDC_COMBO1);
+ int n = c.GetCount();
+ for(int i=0; i<n; i++) if(c.GetItemData(i) == 0x1ea7c0de) c.SelectItem(i);
+ SetDlgItemTextW(x,IDC_EDIT1,m_item_query);
+ SendMessage(x,WM_COMMAND,(WPARAM)MAKEWPARAM(IDC_COMBO1,CBN_SELCHANGE),(LPARAM)GetDlgItem(x,IDC_COMBO1));
+ }
+
+ m_simple_dirty=0;
+ SPL_UpdateScroll(m_scrollchild);
+ }
+ break;
+ case WM_DESTROY:
+ if(conf) delete conf; conf=0;
+ if(m_item_meta) free(m_item_meta); m_item_meta=0;
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_BUTTON_MODE:
+ if(m_simple_dirty)
+ SPL_GetQueryString(m_scrollchild);
+ DestroyWindow(m_scrollchild);
+ mode = !mode;
+ if(!mode) m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_SCROLLCHILDHOST,hwndDlg,scrollChildHostProc);
+ else m_scrollchild=WASABI_API_CREATEDIALOGW(IDD_ADD_VIEW_CHILD_ADVANCED,hwndDlg,childAdvanced);
+ SendMessage(hwndDlg,WM_USER+11,0,0);
+ SendMessage(hwndDlg,WM_USER+10,0,0);
+ ShowWindow(m_scrollchild,SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_INFO),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_SHOWINFO),SW_HIDE);
+
+ SetDlgItemTextW(hwndDlg,IDC_BUTTON_MODE,WASABI_API_LNGSTRINGW( !mode ? IDS_ADVANCED_MODE : IDS_SIMPLE_MODE));
+
+ showdlgelements(hwndDlg,SW_SHOWNA);
+ break;
+ case IDC_COMBO_PRESETS:
+ switch(HIWORD(wParam)) {
+ case CBN_SELCHANGE:
+ {
+ ComboBox combo(hwndDlg,IDC_COMBO_PRESETS);
+ int n = combo.GetItemData(combo.GetSelection());
+ if(n>0 && n<=NUM_PRESETS) {
+ wchar_t cusStr[16] = {0};
+ n--;
+ // populate with a preset
+ m_item_mode = presets[n].mode;
+ m_item_image = presets[n].imageIndex;
+ if(!_wcsicmp(WASABI_API_LNGSTRINGW_BUF(IDS_CUSTOM,cusStr,16),
+ WASABI_API_LNGSTRINGW(presets[n].title))) m_item_name[0]=0;
+ else WASABI_API_LNGSTRINGW_BUF(presets[n].title,m_item_name,256);
+ lstrcpynW(m_item_query,presets[n].query,sizeof(m_item_query)/sizeof(wchar_t));
+ showdlgelements(hwndDlg,SW_SHOWNA);
+ ShowWindow(m_scrollchild,SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_INFO),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_SHOWINFO),SW_HIDE);
+ SendMessage(hwndDlg,WM_USER+9,0,0);
+ }
+ }
+ break;
+ }
+ break;
+ // filter type radio buttons (simulate clicks on the radio button when the image or static text gets clicked)
+ case IDC_CHECK_SHOWINFO:
+ g_config->WriteInt(L"newfilter_showhelp",0);
+ break;
+ case IDC_STATIC_SIMPLE:
+ case IDC_IMAGE_SIMPLE:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_SIMPLE),BM_CLICK,0,0);
+ break;
+ case IDC_STATIC_SIMPLEALBUM:
+ case IDC_IMAGE_SIMPLEALBUM:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_SIMPLEALBUM),BM_CLICK,0,0);
+ break;
+ case IDC_STATIC_TWOFILTERS:
+ case IDC_IMAGE_TWOFILTERS:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_TWOFILTERS),BM_CLICK,0,0);
+ break;
+ case IDC_STATIC_THREEFILTERS:
+ case IDC_IMAGE_THREEFILTERS:
+ SendMessage(GetDlgItem(hwndDlg,IDC_RADIO_THREEFILTERS),BM_CLICK,0,0);
+ break;
+ // enable filter combo boxes based on radio buttons
+ case IDC_RADIO_SIMPLE:
+ case IDC_RADIO_SIMPLEALBUM:
+ case IDC_RADIO_TWOFILTERS:
+ case IDC_RADIO_THREEFILTERS:
+ CheckDlgButton(hwndDlg,IDC_RADIO_SIMPLE,0);
+ CheckDlgButton(hwndDlg,IDC_RADIO_SIMPLEALBUM,0);
+ CheckDlgButton(hwndDlg,IDC_RADIO_TWOFILTERS,0);
+ CheckDlgButton(hwndDlg,IDC_RADIO_THREEFILTERS,0);
+ CheckDlgButton(hwndDlg,LOWORD(wParam),1);
+ SendMessage(hwndDlg,WM_USER+10,0,0);
+ break;
+ // OK and Cancel buttons
+ case IDOK:
+ {
+ //name
+ GetDlgItemTextW(hwndDlg,IDC_NAME,m_item_name,255);
+ m_item_name[255]=0;
+ if(!m_item_name[0]) {
+ wchar_t title[64] = {0};
+ MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_MUST_ENTER_A_NAME),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,64),MB_OK);
+ return 0;
+ }
+ //query
+ if (m_simple_dirty)
+ SPL_GetQueryString(m_scrollchild);
+
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_SIMPLEALBUM))
+ {
+ if(conf)
+ {
+ conf->WriteInt(L"adiv2pos",100000);
+ conf->WriteInt(L"albumartviewmode",2);
+ }
+ m_item_mode = 0x0100000B;
+ }
+ else
+ { // mode
+ int numFilters=3;
+ if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_SIMPLE)) numFilters=0;
+ else if(IsDlgButtonChecked(hwndDlg,IDC_RADIO_TWOFILTERS)) numFilters=2;
+
+ int f[3] = {0};
+ for(int i=0; i<3; i++) {
+ ComboBox comboBox(hwndDlg, (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3));
+ f[i] = (comboBox.GetItemData(comboBox.GetSelection()) + 1) & 0xff;
+ }
+ if(f[1] == 6) f[1]=0;
+ if(numFilters == 2) f[2]=0;
+ if(numFilters == 0) f[0]=f[1]=f[2]=0;
+ m_item_mode = f[0] | (f[1] << 8) | (f[2] << 16);
+ }
+
+ if(conf) {
+ int v = IsDlgButtonChecked(hwndDlg,IDC_HIDE_EXTINFO)?0:1;
+ conf->WriteInt(L"midivvis",v);
+
+ ComboBox combo(hwndDlg,IDC_COMBO_PRESETS);
+ int n = combo.GetItemData(combo.GetSelection());
+ if(n>0 && n<=NUM_PRESETS) {
+ n--;
+ conf->WriteInt(L"mv_sort_by", presets[n].sort_by);
+ conf->WriteInt(L"mv_sort_dir", presets[n].sort_dir);
+
+ if(presets[n].columns) {
+ int cnt = 0;
+ while ((unsigned char)presets[n].columns[cnt] != 0xff)
+ {
+ wchar_t buf[32] = {0};
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"column%d", cnt);
+ conf->WriteInt(buf, (unsigned char)presets[n].columns[cnt]);
+ cnt++;
+ }
+ conf->WriteInt(L"nbcolumns", cnt);
+ }
+ }
+ }
+
+ if (m_edit_item == -1) {
+ // nothing to do, return values are m_item_name, m_item_query and m_item_mode
+ } else {
+ if(!m_editting) addQueryItem(m_item_name,m_item_query,m_item_mode,1,m_item_meta, m_item_image);
+ else replaceQueryItem(m_edit_item,m_item_name,m_item_query,m_item_mode, m_item_image);
+ saveQueryTree();
+ }
+ if(m_editting) PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ case IDCANCEL:
+ {
+ RECT smart_rect = {0};
+ GetWindowRect(hwndDlg, &smart_rect);
+ g_config->WriteInt(L"smart_x", smart_rect.left);
+ g_config->WriteInt(L"smart_y", smart_rect.top);
+
+ EndDialog(hwndDlg,(LOWORD(wParam) == IDOK));
+ EnableWindow(GetParent(hwndDlg),1);
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK childAdvanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ HWND parentWindow, controlWindow;
+ parentWindow = GetAncestor(hwndDlg, GA_PARENT);
+ if (NULL != parentWindow &&
+ FALSE != GetWindowRect(GetDlgItem(parentWindow,IDC_CHILDFRAME),&r))
+ {
+ MapWindowPoints(HWND_DESKTOP, parentWindow, (POINT*)&r, 2);
+ SetWindowPos(hwndDlg,NULL,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+
+ SetDlgItemTextW(hwndDlg,IDC_QUERY,m_item_query);
+
+ controlWindow = GetDlgItem(hwndDlg, IDC_EDIT1);
+ if (NULL != controlWindow)
+ {
+ SetWindowTextA(controlWindow, (char*)WASABI_API_LOADRESFROMFILEW(L"TEXT", MAKEINTRESOURCEW(IDR_QUERIES_TEXT), 0));
+ }
+ }
+ return 1;
+ case WM_USER+40: // get query str
+ GetDlgItemTextW(hwndDlg,IDC_QUERY,m_item_query,1024);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_EDIT:
+ {
+ wchar_t query[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_QUERY,query, 1024);
+ wchar_t querybuf[4096] = {0};
+ const wchar_t *newquery = editQuery(hwndDlg, query, querybuf, 4096);
+ if (newquery != NULL) {
+ SetDlgItemTextW(hwndDlg, IDC_QUERY, newquery);
+ m_simple_dirty=1;
+ }
+ }
+ break;
+ case IDC_QUERY:
+ m_simple_dirty=1;
+ break;
+ }
+ break;
+ }
+
+ const int controls[] =
+ {
+ IDC_EDIT1,
+ };
+ if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls)))
+ return TRUE;
+
+ return 0;
+}
+
+BOOL IsDirectMouseWheelMessage(const UINT uMsg)
+{
+ static UINT WINAMP_WM_DIRECT_MOUSE_WHEEL = WM_NULL;
+
+ if (WM_NULL == WINAMP_WM_DIRECT_MOUSE_WHEEL)
+ {
+ WINAMP_WM_DIRECT_MOUSE_WHEEL = RegisterWindowMessageW(L"WINAMP_WM_DIRECT_MOUSE_WHEEL");
+ if (WM_NULL == WINAMP_WM_DIRECT_MOUSE_WHEEL)
+ return FALSE;
+ }
+
+ return (WINAMP_WM_DIRECT_MOUSE_WHEEL == uMsg);
+}
+
+static INT_PTR CALLBACK filterProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ // populate comboboxes
+ {
+ HWND hwndCB=GetDlgItem(hwndDlg,IDC_COMBO1);
+ int strs[]=
+ {
+ IDS_FILENAME,
+ IDS_TITLE,
+ IDS_ARTIST,
+ IDS_ALBUM,
+ IDS_YEAR,
+ IDS_GENRE,
+ IDS_COMMENT,
+ IDS_TRACK_NUMBER,
+ IDS_LENGTH,
+ IDS_IS_VIDEO,
+ IDS_LAST_UPDATED,
+ IDS_PLAYED_LAST,
+ IDS_RATING,
+ NULL, // fear the 13
+ NULL, // gracenote
+ IDS_PLAY_COUNT,
+ IDS_FILE_TIME,
+ IDS_FILE_SIZE_KB,
+ IDS_BITRATE_KBPS,
+ IDS_DISC_NUMBER,
+ IDS_ALBUM_ARTIST,
+ IDS_ALBUM_GAIN,
+ IDS_TRACK_GAIN,
+ IDS_PUBLISHER,
+ IDS_COMPOSER,
+ IDS_BPM, // no bpm
+ IDS_DISCS,
+ IDS_TRACKS,
+ IDS_IS_PODCAST,
+ IDS_PODCAST_CHANNEL,
+ IDS_PODCAST_PUBLISH_DATE,
+ NULL,
+ NULL,
+ IDS_LOSSLESS,
+ IDS_CATEGORY,
+ IDS_CODEC,
+ IDS_DIRECTOR,
+ IDS_PRODUCER,
+ IDS_WIDTH,
+ IDS_HEIGHT,
+ NULL,
+ IDS_DATE_ADDED,
+ };
+ int x;
+ int cnt = 0, width = 0;
+ wchar_t * str;
+ for (x = 0; x < sizeof(strs)/sizeof(strs[0]); x ++)
+ {
+ if (strs[x])
+ {
+ int a=SendMessageW(hwndCB, CB_ADDSTRING,0,(LPARAM)(str = WASABI_API_LNGSTRINGW(strs[x])));
+ width = ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO1, str, width);
+ SendMessage(hwndCB,CB_SETITEMDATA,(WPARAM)a,(LPARAM)x);
+ cnt++;
+ }
+ }
+
+ int a=SendMessageW(hwndCB, CB_ADDSTRING,0,(LPARAM)(str = WASABI_API_LNGSTRINGW(IDS_CUSTOM)));
+ ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO1, str, width);
+ SendMessage(hwndCB,CB_SETITEMDATA,(WPARAM)a,(LPARAM)0x1ea7c0de);
+ cnt++;
+ if (lParam)
+ {
+ nde_filter_t filter=(nde_filter_t)lParam;
+ int a=NDE_Filter_GetID(filter);
+ if (a != -1)
+ {
+ for (x = 0; x < cnt; x ++)
+ {
+ int d = SendMessage(hwndCB,CB_GETITEMDATA,(WPARAM)x,0);
+ if (d == a)
+ break;
+ }
+ if (x < cnt)
+ {
+ SendMessage(hwndCB,CB_SETCURSEL,x,0);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO1,CBN_SELCHANGE),(LPARAM)hwndCB);
+ int filtop=NDE_Filter_GetOp(filter);
+ HWND hwndCB2=GetDlgItem(hwndDlg,IDC_COMBO2);
+ cnt=SendMessage(hwndCB2,CB_GETCOUNT,0,0);
+ for (x = 0; x < cnt; x ++)
+ {
+ if (SendMessage(hwndCB2,CB_GETITEMDATA,(WPARAM)x,0) == filtop)
+ break;
+ }
+ if (x < cnt)
+ {
+ SendMessage(hwndCB2,CB_SETCURSEL,x,0);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO2,CBN_SELCHANGE),(LPARAM)hwndCB2);
+ }
+ }
+ }
+ nde_field_t f=NDE_Filter_GetData(filter);
+ if (f)
+ {
+ int ft=NDE_Field_GetType(f);
+ //hack cause we never actualy get FIELD_LENGTH here.
+ if (ft==FIELD_INTEGER && a==MAINTABLE_ID_LENGTH) ft=FIELD_LENGTH;
+
+ switch (ft)
+ {
+ case FIELD_FILENAME:
+ case FIELD_STRING:
+ SetDlgItemTextW(hwndDlg,IDC_EDIT1,NDE_StringField_GetString(f));
+ break;
+ case FIELD_LENGTH:
+ {
+ int v=NDE_IntegerField_GetValue(f);
+ wchar_t buf[128] = {0};
+ if (v < 60)
+ wsprintf(buf,L"%d",v);
+ else if (v < 60*60)
+ wsprintf(buf,L"%d:%02d",v/60,v%60);
+ else
+ wsprintf(buf,L"%d:%02d:%02d",v/60/60,(v/60)%60,v%60);
+
+ SetDlgItemText(hwndDlg,IDC_EDIT1,buf);
+ }
+ break;
+ case FIELD_INTEGER:
+ SetDlgItemInt(hwndDlg,IDC_EDIT1,NDE_IntegerField_GetValue(f),TRUE);
+ break;
+ }
+ }
+ }
+ }
+ return 1;
+
+ case WM_USER+29:
+ if(IsDlgButtonChecked(hwndDlg,IDC_AND)) return 1;
+ if(IsDlgButtonChecked(hwndDlg,IDC_OR)) return 0;
+ CheckDlgButton(hwndDlg,wParam?IDC_AND:IDC_OR,TRUE);
+ return wParam;
+ case WM_USER+40:
+ if (wParam && lParam>0)
+ {
+ wchar_t *buf=(wchar_t *)wParam;
+ size_t buf_len=(size_t)lParam;
+ // produce expression here
+ HWND hwndCB=GetDlgItem(hwndDlg,IDC_COMBO1);
+ HWND hwndCB2=GetDlgItem(hwndDlg,IDC_COMBO2);
+ int x=SendMessage(hwndCB,CB_GETCURSEL,0,0);
+ if (x != CB_ERR)
+ {
+ int Id=SendMessage(hwndCB,CB_GETITEMDATA,x,0);
+ if(Id == 0x1ea7c0de) { // custom!
+ GetDlgItemTextW(hwndDlg,IDC_EDIT1,buf,buf_len);
+ return 0;
+ }
+ x = SendMessage(hwndCB2,CB_GETCURSEL,0,0);
+ if (x != CB_ERR)
+ {
+ int Op=SendMessage(hwndCB2,CB_GETITEMDATA,x,0);
+
+ if (Id != -1 && Op != -1)
+ {
+ wchar_t res[256] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_EDIT1,res,255);
+ res[255]=0;
+ nde_field_t p = NDE_Table_GetColumnByID(g_table, (unsigned char)Id);
+ if (p)
+ {
+ const wchar_t *fn=NDE_ColumnField_GetFieldName(p);
+
+
+ wchar_t *opstr=NULL;
+ switch (Op)
+ {
+ case FILTER_EQUALS: opstr=L"="; break;
+ case FILTER_NOTEQUALS: opstr=L"!="; break;
+ case FILTER_CONTAINS: opstr=L"HAS"; break;
+ case FILTER_NOTCONTAINS: opstr=L"NOTHAS"; break;
+ case FILTER_ABOVE: opstr=L">"; break;
+ case FILTER_BELOW: opstr=L"<"; break;
+ case FILTER_ABOVEOREQUAL: opstr=L">="; break;
+ case FILTER_BELOWOREQUAL: opstr=L"<="; break;
+ case FILTER_BEGINS: opstr=L"BEGINS"; break;
+ case FILTER_ENDS: opstr=L"ENDS"; break;
+ case FILTER_LIKE: opstr=L"LIKE"; break;
+ case FILTER_ISEMPTY: opstr=L"ISEMPTY"; break;
+ case FILTER_ISNOTEMPTY: opstr=L"ISNOTEMPTY"; break;
+ }
+ if (fn && fn[0] && opstr && opstr[0])
+ {
+ if (Op == FILTER_ISEMPTY || Op == FILTER_ISNOTEMPTY)
+ wsprintfW(buf, L"%s %s",fn,opstr);
+ else if (NDE_ColumnField_GetDataType(p) == FIELD_DATETIME)
+ wsprintfW(buf, L"%s %s [%s]",fn,opstr,res);
+ else
+ {
+ GayStringW escaped;
+ queryStrEscape(res, escaped);
+ wsprintfW(buf, L"%s %s \"%s\"",fn,opstr,escaped.Get());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON1:
+ SendMessage(GetParent(hwndDlg),WM_USER+34,(WPARAM)hwndDlg,-1);
+ m_simple_dirty=1;
+ return 0;
+ case IDC_OR:
+ case IDC_AND:
+ m_simple_dirty=1;
+ break;
+ case IDC_BUTTON_QUERYBUILD:
+ {
+ wchar_t query[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_EDIT,query, 1024);
+ wchar_t querybuf[4096] = {0};
+ const wchar_t *newquery = editQuery(hwndDlg, query, querybuf, 4096);
+ if (newquery != NULL) {
+ SetDlgItemTextW(hwndDlg, IDC_EDIT, newquery);
+ m_simple_dirty=1;
+ }
+ }
+ break;
+ case IDC_BUTTON2:
+ {
+ wchar_t query[1024] = {0};
+ GetDlgItemTextW(hwndDlg,IDC_EDIT1,query,1024);
+
+ const wchar_t *newquery = editTime(hwndDlg, query);
+ if (newquery != NULL) SetDlgItemTextW(hwndDlg, IDC_EDIT1, newquery);
+ }
+ // todo: date picker
+ return 0;
+ case IDC_EDIT1:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ m_simple_dirty=1;
+ }
+ return 0;
+ case IDC_COMBO2:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ m_simple_dirty=1;
+ HWND hwndCB2=(HWND)lParam;
+ int x=SendMessage(hwndCB2,CB_GETCURSEL,0,0);
+ if (x != CB_ERR)
+ {
+ int a=SendMessage(hwndCB2,CB_GETITEMDATA,(WPARAM)x,0);
+ if (a == FILTER_ISEMPTY || a == FILTER_ISNOTEMPTY)
+ {
+ EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT1),0);
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT1),1);
+ }
+ }
+ }
+ return 0;
+ case IDC_COMBO1:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ m_simple_dirty=1;
+ HWND hwndCB=(HWND)lParam;
+ HWND hwndCB2=GetDlgItem(hwndDlg,IDC_COMBO2);
+
+ int x=SendMessage(hwndCB,CB_GETCURSEL,0,0);
+#define GAP 2
+ if (x != CB_ERR)
+ {
+ int lastsel=SendMessage(hwndCB2,CB_GETCURSEL,0,0);
+ SendMessage(hwndCB2,CB_RESETCONTENT,0,0);
+ // populate with proper comparisons for type. give a default, too
+ int myId = SendMessage(hwndCB,CB_GETITEMDATA,(WPARAM)x,0);
+ if(myId == 0x1ea7c0de) { // custom!
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON_QUERYBUILD),SW_SHOWNA);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_COMBO2),SW_HIDE);
+
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON2),&r);
+ ScreenToClient(hwndDlg,((LPPOINT)&r));
+
+ RECT r1;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_COMBO1),&r1);
+ ScreenToClient(hwndDlg,((LPPOINT)&r1));
+ ScreenToClient(hwndDlg,((LPPOINT)&r1)+1);
+
+ RECT r2;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_EDIT1),&r2);
+ ScreenToClient(hwndDlg,((LPPOINT)&r2));
+ ScreenToClient(hwndDlg,((LPPOINT)&r2)+1);
+
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_EDIT1),0,r1.right+GAP,r1.top,r.left-r1.right-GAP-GAP,r2.bottom-r2.top,/*SWP_NOMOVE|*/SWP_NOACTIVATE|SWP_NOZORDER);
+ return 0;
+ }
+
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON_QUERYBUILD),SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg,IDC_COMBO2),SW_SHOWNA);
+
+ nde_field_t p = (-1 != myId) ? NDE_Table_GetColumnByID(g_table, (unsigned char)myId) : NULL;
+ if (p)
+ {
+ if (NDE_ColumnField_GetDataType(p)==FIELD_DATETIME)
+ {
+ //if (!IsWindowVisible(GetDlgItem(hwndDlg,IDC_BUTTON2)))
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON2),&r);
+ ScreenToClient(hwndDlg,((LPPOINT)&r));
+
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON2),SW_SHOWNA);
+
+ RECT r1;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_COMBO2),&r1);
+ ScreenToClient(hwndDlg,((LPPOINT)&r1));
+ ScreenToClient(hwndDlg,((LPPOINT)&r1)+1);
+
+ RECT r2;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_EDIT1),&r2);
+ ScreenToClient(hwndDlg,((LPPOINT)&r2));
+ ScreenToClient(hwndDlg,((LPPOINT)&r2)+1);
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_EDIT1),0,r1.right+GAP,r1.top,r.left-r1.right-GAP-GAP,r2.bottom-r2.top,/*SWP_NOMOVE|*/SWP_NOACTIVATE|SWP_NOZORDER);
+ }
+ }
+ else
+ {
+ //if (IsWindowVisible(GetDlgItem(hwndDlg,IDC_BUTTON2)))
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON2),&r);
+ ScreenToClient(hwndDlg,((LPPOINT)&r) + 1);
+
+ ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTON2),SW_HIDE);
+
+ RECT r1;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_COMBO2),&r1);
+ ScreenToClient(hwndDlg,((LPPOINT)&r1));
+ ScreenToClient(hwndDlg,((LPPOINT)&r1)+1);
+
+ RECT r2;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_EDIT1),&r2);
+ ScreenToClient(hwndDlg,((LPPOINT)&r2));
+ ScreenToClient(hwndDlg,((LPPOINT)&r2)+1);
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_EDIT1),0,r1.right+GAP,r1.top,r.right-r1.right-GAP,r2.bottom-r2.top,/*SWP_NOMOVE|*/SWP_NOACTIVATE|SWP_NOZORDER);
+ }
+ }
+#undef GAP
+ typedef struct
+ {
+ int str;
+ char id;
+ } fillT;
+
+ int myfillt_len=0;
+ fillT *myfillt=NULL;
+ static fillT foo1[]=
+ {
+ {IDS_EQUALS,FILTER_EQUALS},
+ {IDS_DOES_NOT_EQUAL,FILTER_NOTEQUALS},
+ {IDS_CONTAINS,FILTER_CONTAINS},
+ {IDS_DOES_NOT_CONTAIN,FILTER_NOTCONTAINS},
+ {IDS_IS_ABOVE,FILTER_ABOVE},
+ {IDS_IS_BELOW,FILTER_BELOW},
+ {IDS_EQUALS_OR_IS_ABOVE,FILTER_ABOVEOREQUAL},
+ {IDS_EQUALS_OR_IS_BELOW,FILTER_BELOWOREQUAL},
+ {IDS_IS_EMPTY,FILTER_ISEMPTY},
+ {IDS_IS_NOT_EMPTY,FILTER_ISNOTEMPTY},
+ {IDS_BEGINS_WITH,FILTER_BEGINS},
+ {IDS_ENDS_WITH,FILTER_ENDS},
+ {IDS_IS_SIMILAR_TO,FILTER_LIKE},
+ };
+ static fillT foo2[]=
+ {
+ {IDS_AT,FILTER_EQUALS},
+ {IDS_NOT_AT,FILTER_NOTEQUALS},
+ {IDS_AFTER,FILTER_ABOVE},
+ {IDS_BEFORE,FILTER_BELOW},
+ {IDS_SINCE,FILTER_ABOVEOREQUAL},
+ {IDS_UNTIL,FILTER_BELOWOREQUAL},
+ {IDS_IS_EMPTY,FILTER_ISEMPTY},
+ {IDS_IS_NOT_EMPTY,FILTER_ISNOTEMPTY},
+ };
+ switch (NDE_ColumnField_GetDataType(p))
+ {
+ case FIELD_DATETIME:
+ {
+ myfillt_len = sizeof(foo2)/sizeof(foo2[0]);
+ myfillt=foo2;
+ }
+ break;
+ case FIELD_LENGTH:
+ case FIELD_INTEGER:
+ {
+ myfillt_len = sizeof(foo1)/sizeof(foo1[0]) - 3;
+ myfillt=foo1;
+ }
+ break;
+ case FIELD_FILENAME:
+ case FIELD_STRING:
+ {
+ myfillt_len = sizeof(foo1)/sizeof(foo1[0]);
+ myfillt=foo1;
+ }
+ break;
+ default:
+ break;
+ }
+ if (myfillt)
+ {
+ wchar_t *str;
+ int width = 0;
+ while (myfillt_len--)
+ {
+ int a=SendMessageW(hwndCB2,CB_ADDSTRING,0,(LPARAM)(str = WASABI_API_LNGSTRINGW(myfillt->str)));
+ width = ResizeComboBoxDropDownW(hwndDlg, IDC_COMBO2, str, width);
+ SendMessage(hwndCB2,CB_SETITEMDATA,a,(LPARAM)myfillt->id);
+ myfillt++;
+ }
+ if (lastsel != CB_ERR)
+ SendMessage(hwndCB2,CB_SETCURSEL,lastsel,0);
+ }
+ }
+ }
+ }
+ return 0;
+ }
+ return 0;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(uMsg) ||
+ WM_MOUSEWHEEL == uMsg)
+ {
+ HWND parentWindow;
+ parentWindow = GetAncestor(hwndDlg, GA_PARENT);
+ if (NULL != parentWindow)
+ {
+ SendMessageW(parentWindow, WM_MOUSEWHEEL, wParam, lParam);
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (long)TRUE);
+ return TRUE;
+ }
+ }
+
+ return 0;
+}
+
+
+static INT_PTR CALLBACK scrollChildProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static int osize;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ GetClientRect(hwndDlg,&r);
+ osize=r.bottom;
+ }
+ return 1;
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_BUTTON1)
+ SPL_AddBlankFilter(hwndDlg);
+ return 0;
+ case WM_USER+40: // get query string
+ if (!lParam)
+ {
+ // now we go through our children, asking each one for a string, and combine them.
+ GayStringW str;
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ wchar_t * nextop=NULL;
+ while (h)
+ {
+ if (h != GetDlgItem(hwndDlg,IDC_BUTTON1))
+ {
+ wchar_t buf[512] = {0};
+ buf[0]=0;
+ SendMessage(h,WM_USER+40,(WPARAM)buf,(LPARAM)sizeof(buf)/sizeof(*buf));
+ if (buf[0])
+ {
+ if(nextop) str.Append(nextop);
+ nextop = IsDlgButtonChecked(h,IDC_AND) ? L" AND " : L" OR ";
+ //if (str.Get() && str.Get()[0]) str.Append(isOr ? L" OR " : L" AND ");
+ str.Append(buf);
+ }
+ }
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+ lstrcpynW(m_item_query,str.Get()?str.Get():L"",sizeof(m_item_query)/sizeof(*m_item_query));
+ }
+ return 0;
+ case WM_USER+41: // remove all
+ {
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ std::vector<void*> w;
+ while (h) {
+ if (h != GetDlgItem(hwndDlg,IDC_BUTTON1))
+ w.push_back((void*)h);
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+ for ( void *l_w : w )
+ SendMessage( hwndDlg, WM_USER + 34, (WPARAM)(HWND)l_w, -1 );
+ }
+ break;
+ case WM_USER+34: // remove filter by hwnd
+ if (wParam && lParam == -1)
+ {
+ HWND hwndRemove = (HWND) wParam;
+ RECT r;
+
+ GetClientRect(hwndRemove,&r);
+ int lh=r.bottom; // height to remove
+
+ GetWindowRect(hwndRemove,&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+ int ltop=r.top;
+
+ DestroyWindow(hwndRemove);
+
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ while (h)
+ {
+ RECT r;
+
+ GetWindowRect(h,&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+
+ if (r.top > ltop)
+ {
+ SetWindowPos(h,0,r.left,r.top - lh,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+
+ GetClientRect(hwndDlg,&r);
+ r.bottom -= lh;
+ SetWindowPos(hwndDlg,0,0,0,r.right,r.bottom,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
+
+ SendMessage(GetParent(hwndDlg),WM_USER+33,17,0);
+ }
+ return 0;
+ case WM_USER+32: // add filter. lParam is mode. 1 means add by filter, 2 means add blank
+ if(lParam == 2) {
+ BOOL b=0;
+ HWND h=GetWindow(hwndDlg,GW_CHILD);
+ while (h) {
+ if (h != GetDlgItem(hwndDlg,IDC_BUTTON1))
+ b = filterProc(h,WM_USER+29,(WPARAM)b,0);
+ h=GetWindow(h,GW_HWNDNEXT);
+ }
+ }
+ if ((lParam == 1 && wParam) || (lParam == 2 && !wParam))
+ {
+ HWND newChild=0;
+ nde_filter_t filter=(nde_filter_t)wParam;
+ if (lParam == 2 || (NULL != NDE_Table_GetColumnByID(g_table, NDE_Filter_GetID(filter))))
+ {
+ newChild=WASABI_API_CREATEDIALOGPARAMW(IDD_SCROLLCHILDFILTER,hwndDlg,filterProc,(LPARAM)filter);
+ RECT r,r2;
+ GetClientRect(hwndDlg,&r);
+ GetClientRect(newChild,&r2);
+ SetWindowPos(hwndDlg,0,0,0,r.right,r.bottom + r2.bottom,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
+ SetWindowPos(newChild,0,0,r.bottom - osize,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+
+ HWND h = GetDlgItem(hwndDlg,IDC_BUTTON1);
+ GetWindowRect(h,&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+ SetWindowPos(h,0,r.left,r.top + r2.bottom,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ ShowWindow(newChild,SW_SHOWNA);
+ }
+ if (lParam == 2) {
+ // update scroll, hugging bottom
+ SendMessage(GetParent(hwndDlg),WM_USER+33,16,0);
+ }
+ return (intptr_t)newChild;
+ }
+ return 0;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(uMsg) ||
+ WM_MOUSEWHEEL == uMsg)
+ {
+ HWND parentWindow;
+ parentWindow = GetAncestor(hwndDlg, GA_PARENT);
+ if (NULL != parentWindow)
+ {
+ SendMessageW(parentWindow, WM_MOUSEWHEEL, wParam, lParam);
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (long)TRUE);
+ return TRUE;
+ }
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK scrollChildHostProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static HWND m_child;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(GetParent(hwndDlg),IDC_CHILDFRAME),&r);
+ ScreenToClient(GetParent(hwndDlg),(LPPOINT)&r);
+ ScreenToClient(GetParent(hwndDlg),((LPPOINT)&r)+1);
+ SetWindowPos(hwndDlg,NULL,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ m_child=WASABI_API_CREATEDIALOGW(IDD_SCROLLCHILD,hwndDlg,scrollChildProc);
+ return 1;
+ case WM_USER+33:
+ if (m_child)
+ {
+ RECT r;
+ RECT r2;
+ GetClientRect(hwndDlg,&r2);
+ GetClientRect(m_child,&r);
+ if (r2.bottom < r.bottom)
+ {
+ SCROLLINFO si={sizeof(si),SIF_RANGE|SIF_PAGE|SIF_POS,0,r.bottom,r2.bottom,wParam == 16 ? r.bottom : 0,0};
+
+ if (wParam == 17)
+ si.fMask &= ~SIF_POS;
+
+ SetScrollInfo(hwndDlg,SB_VERT,&si,TRUE);
+
+ if (wParam != 17)
+ SetWindowPos(m_child,NULL,0,wParam == 16 ? r2.bottom-r.bottom : 0,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ } else {
+ //hide the scrollbar
+ SetWindowPos(m_child,NULL,0,0,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ ShowScrollBar(hwndDlg,SB_VERT,FALSE);
+ }
+ ShowWindow(m_child,SW_SHOWNA);
+ }
+ return 0;
+ case WM_USER+32:
+ case WM_USER+41:
+ case WM_USER+40:
+ if (m_child) return scrollChildProc(m_child,uMsg,wParam,lParam);
+ return 0;
+ case WM_VSCROLL:
+ {
+ RECT r;
+ RECT r2;
+ GetClientRect(hwndDlg,&r2);
+ GetClientRect(m_child,&r);
+
+ if (r2.bottom < r.bottom)
+ {
+ int v=0;
+ if (LOWORD(wParam) == SB_THUMBPOSITION || LOWORD(wParam) == SB_THUMBTRACK)
+ {
+ SCROLLINFO si={sizeof(si),SIF_TRACKPOS|SIF_POS};
+ GetScrollInfo(hwndDlg,SB_VERT,&si);
+ v=si.nTrackPos;
+ }
+ else if (LOWORD(wParam) == SB_TOP)
+ {
+ v=0;
+ }
+ else if (LOWORD(wParam) == SB_BOTTOM)
+ {
+ v=r.bottom-r2.bottom;
+ }
+ else if (LOWORD(wParam) == SB_PAGEDOWN || LOWORD(wParam) == SB_LINEDOWN)
+ {
+ SCROLLINFO si={sizeof(si),SIF_TRACKPOS|SIF_POS};
+ GetScrollInfo(hwndDlg,SB_VERT,&si);
+ v=si.nPos + r2.bottom;
+ if (v > r.bottom-r2.bottom) v=r.bottom-r2.bottom;
+ }
+ else if (LOWORD(wParam) == SB_PAGEUP || LOWORD(wParam) == SB_LINEUP)
+ {
+ SCROLLINFO si={sizeof(si),SIF_TRACKPOS|SIF_POS};
+ GetScrollInfo(hwndDlg,SB_VERT,&si);
+ v=si.nPos - r2.bottom;
+ if (v < 0) v=0;
+ }
+ else return 0;
+
+ SetScrollPos(hwndDlg,SB_VERT,v,!(LOWORD(wParam) == SB_THUMBPOSITION || LOWORD(wParam) == SB_THUMBTRACK));
+ SetWindowPos(m_child,NULL,0,0-v,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ else
+ {
+ SetScrollPos(hwndDlg,SB_VERT,0,!(LOWORD(wParam) == SB_THUMBPOSITION || LOWORD(wParam) == SB_THUMBTRACK));
+ SetWindowPos(m_child,NULL,0,0,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+ }
+ }
+ return 0;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(uMsg) ||
+ WM_MOUSEWHEEL == uMsg)
+ {
+ WORD scrollCommand;
+ short delta;
+
+ delta = HIWORD(wParam);
+
+ scrollCommand = (delta > 0) ? SB_LINEUP : SB_LINEDOWN;
+
+ SendMessageW(hwndDlg, WM_VSCROLL, MAKEWPARAM(scrollCommand, 0), 0L);
+ }
+ return 0;
+}
+
+void addNewQuery(HWND parent) {
+ HWND hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_ADD_VIEW_2, parent, addQueryFrameDialogProc2, 0);
+ SetActiveWindow(hwnd);
+}
+
+void queryEditItem(int n)
+{
+ m_edit_item=n;
+ HWND hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_ADD_VIEW_2, plugin.hwndLibraryParent, addQueryFrameDialogProc2, 1);
+ SetActiveWindow(hwnd);
+}
+
+// returns true if edition was validated, false if cancel was clicked
+// actual return values are in m_item_query, m_item_name and m_item_mode
+int queryEditOther(HWND hwnd, const char *query, const char *viewname, int mode)
+{
+ int notnew=1;
+ if (query == NULL || !*query) notnew = 0;
+ m_edit_item = -1;
+ if (notnew)
+ {
+ MultiByteToWideCharSZ(CP_ACP, 0, viewname, -1, m_item_name, 256);
+ MultiByteToWideCharSZ(CP_ACP, 0, query, -1, m_item_query, 1024);
+ }
+ m_item_mode = mode;
+ if(mode == -1)
+ return WASABI_API_DIALOGBOXPARAMW(IDD_ADD_VIEW_2_NF, hwnd, addQueryFrameDialogProc2, notnew);
+ else
+ return WASABI_API_DIALOGBOXPARAMW(IDD_ADD_VIEW_2, hwnd, addQueryFrameDialogProc2, notnew);
+}
+
+void queryDeleteItem(HWND parent, int n)
+{
+ QueryList::iterator iter;
+ iter = m_query_list.find(n);
+ if (iter == m_query_list.end()) return;
+
+ wchar_t title[64] = {0};
+ queryItem *item = iter->second;
+ if (MessageBoxW(parent,WASABI_API_LNGSTRINGW(IDS_DELETE_THIS_VIEW),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,title,64),
+ MB_YESNO|MB_ICONQUESTION) == IDYES)
+ {
+ mediaLibrary.RemoveTreeItem(iter->first);
+ m_query_list.erase(iter->first);
+ saveQueryTree();
+
+ // we deleted the item from the tree, which apparently is enough to close the current dialog if it was
+ // the current dialog. hot.
+
+ for(iter = m_query_list.begin(); iter != m_query_list.end(); iter++)
+ if(iter->second && iter->second->index > item->index) iter->second->index--;
+
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ DeleteFileW(configDir);
+
+ free(item->metafn);
+ free(item->name);
+ free(item->query);
+ free(item);
+ }
+}
+
+
+ int IPC_LIBRARY_SENDTOMENU;
+static librarySendToMenuStruct s_menu;
+
+static WNDPROC ml_oldWndProc2;
+static INT_PTR CALLBACK ml_newWndProc2(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s_menu.build_hMenu && s_menu.mode==1)
+ {
+ myMenu = TRUE;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s_menu, IPC_LIBRARY_SENDTOMENU)==(LRESULT)-1)
+ s_menu.mode=2;
+ myMenu = FALSE;
+ if(ml_oldWndProc2) SetWindowLongPtrW(hwndDlg,GWLP_WNDPROC,(LONG_PTR)ml_oldWndProc2);
+ ml_oldWndProc2 = NULL;
+
+ }
+ return 0;
+ }
+ if (ml_oldWndProc2) return CallWindowProc(ml_oldWndProc2,hwndDlg,uMsg,wParam,lParam);
+ return 0;
+}
+
+HMENU main_sendto_hmenu;
+int main_sendto_mode;
+
+void view_queryContextMenu(INT_PTR param1, HWND hHost, POINTS pts, int n)
+{
+ queryItem *item=m_query_list[n];
+ if (item == NULL) return;
+
+ ml_oldWndProc2 = (WNDPROC)SetWindowLongPtrW(hHost, GWLP_WNDPROC, (LONG_PTR)ml_newWndProc2);
+
+ HMENU menu=GetSubMenu(g_context_menus,2);
+ main_sendto_hmenu=GetSubMenu(menu,2);
+
+ s_menu.mode = 0;
+ s_menu.hwnd = 0;
+ s_menu.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU)==(LRESULT)-1)
+ {
+ s_menu.mode = 1;
+ s_menu.hwnd = hHost;
+ s_menu.data_type = ML_TYPE_ITEMRECORDLIST;
+ s_menu.ctx[1] = 1;
+ s_menu.build_hMenu = main_sendto_hmenu;
+ }
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ UpdateMenuItems(NULL, menu, IDR_QUERY_ACCELERATORS);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON,
+ pt.x, pt.y, hHost, NULL);
+ if(ml_oldWndProc2) SetWindowLongPtrW(hHost,GWLP_WNDPROC,(LONG_PTR)ml_oldWndProc2);
+ switch(r)
+ {
+ case ID_QUERYWND_EDIT:
+ queryEditItem(n);
+ break;
+ case ID_QUERYWND_DELETE:
+ queryDeleteItem(hHost,n);
+ break;
+ case ID_QUERYWND_PLAYQUERY:
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf,item->query,0);
+ }
+ break;
+ case ID_QUERYWND_ENQUEUEQUERY:
+ {
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf,item->query,1);
+ }
+ break;
+ case ID_QUERYMENU_ADDNEWQUERY:
+ addNewQuery(hHost);
+ break;
+ default:
+ if (s_menu.mode == 2)
+ {
+ s_menu.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s_menu, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ // build my data.
+ s_menu.mode=3;
+ s_menu.data_type = ML_TYPE_ITEMRECORDLISTW;
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, item->query);
+ itemRecordListW obj={0,};
+ saveQueryToListW(&viewconf, s, &obj, 0, 0, (resultsniff_funcW)-1);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ s_menu.data = (void*)&obj;
+
+ LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s_menu,IPC_LIBRARY_SENDTOMENU);
+ if (result != 1)
+ {
+ s_menu.mode=3;
+ s_menu.data_type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA={0,};
+ convertRecordList(&objA, &obj);
+ s_menu.data = (void*)&objA;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s_menu,IPC_LIBRARY_SENDTOMENU);
+ freeRecordList(&objA);
+ }
+ freeRecordList(&obj);
+
+ }
+ }
+ break;
+ }
+ if (s_menu.mode)
+ {
+ s_menu.mode=4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s_menu,IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ main_sendto_hmenu=0;
+ EatKeyboard();
+}
+
+void queriesContextMenu(INT_PTR param1, HWND hHost, POINTS pts) {
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ HNAVITEM hItem = (HNAVITEM)param1;
+ NAVITEMGETRECT itemRect;
+ itemRect.fItem = FALSE;
+ itemRect.hItem = hItem;
+ if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
+ {
+ MapWindowPoints(hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
+ pt.x = itemRect.rc.left + 2;
+ pt.y = itemRect.rc.top + 2;
+ }
+ }
+
+ HMENU menu=GetSubMenu(g_context_menus,3);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ pt.x, pt.y, hHost, NULL);
+ switch(r) {
+ case ID_QUERYMENU_ADDNEWQUERY:
+ addNewQuery(hHost);
+ break;
+ case ID_QUERYMENU_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &preferences);
+ break;
+ case ID_QUERYMENU_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8105304048660-The-Winamp-Media-Library");
+ break;
+ }
+ EatKeyboard();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/queries.txt b/Src/Plugins/Library/ml_local/queries.txt
new file mode 100644
index 00000000..64030f6e
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/queries.txt
@@ -0,0 +1,77 @@
+You can enable more powerful views using the custom query language.
+
+The basic format is:
+
+<field> <comparison> [value] [<logic operator> <field> <comparison> [value] [...]]
+
+Field names:
+ TYPE: 0 for audio files, 1 for video files
+ FILENAME: Full filename (including path)
+ LENGTH: Length, in seconds (or hh:mm:ss)
+ ARTIST: Artist
+ ALBUM: Album
+ ALBUMARTIST: Album Artist
+ TITLE: Title
+ TRACKNO: Track number of file
+ GENRE: Genre
+ YEAR: Year
+ COMMENT: Comment
+ COMPOSER: Composer
+ DISC: Disc number of a CD set
+ FILESIZE: File size, in kilobytes
+ FILETIME: Last known file date/time on disk
+ LASTUPD: Date/time of file imported to library or modified in library
+ LASTPLAY: Date/time of last play
+ RATING: Rating value (1-5, or 0 or empty for unrated)
+ PLAYCOUNT: Number of plays
+ PUBLISHER: Publisher or record label
+ DIRECTOR: Film director (for videos)
+ PRODUCER: Film or Record producer
+ CATEGORY: Category
+ REPLAYGAIN_ALBUM_GAIN: ReplayGain Album Gain
+ REPLAYGAIN_TRACK_GAIN: ReplayGain Track Gain
+ BITRATE: Bitrate (in KBPS)
+ TRACKS: Total number of tracks on the disc
+ DISCS: Total number of discs in the set
+ ISPODCAST: 1 for a podcast episode, 0 otherwise
+ PODCASTCHANNEL: The name of the channel for a podcast
+ PODCASTPUBDATE: Date/time when the podcast was published
+ LOSSLESS: Shows lossless audio formats, e.g. lossless=1
+
+Comparison operators:
+ '=': String or integer equals value
+ '!=': String or integer does not equal value
+ '<': String or integer is less than value
+ '>': String or integer is greater than value
+ '<=': String or integer is less than or equal to value
+ '>=': String or integer is greater than or equal to value
+ HAS: String contains value
+ NOTHAS: String does not contain value
+ LIKE: String is similar to value ("the" and whitespace are ignored)
+ BEGINS: String begins with value
+ BEGINSLIKE: String begins like value
+ ENDS: String ends with value
+ ISEMPTY: (no comparison value required) TRUE if <field> is empty
+ ISNOTEMPTY: (no comparison value required) TRUE if <field> is not empty
+
+Values:
+ "strings with spaces" or strings_without_spaces
+ integers can be "32" or just 32
+ integers for LENGTH can be a plain integer (seconds), or mm:ss or hh:mm:ss
+ date/timestamps should be [datetime data], which can be either an absolute
+ or relative time. i.e.: [3 weeks ago], [18:15], [05/30/2003],
+ [yesterday noon], [3 days ago 5 pm], [now], [5 mn before may 30th], etc.
+
+Logic operators:
+ &&, &, or AND: boolean AND two comparisons
+ ||, |, or OR: boolean OR two comparisons
+ !, or NOT: prefix this to an expression for the boolean NOT of that expression
+
+Examples:
+ all video files: type = 1
+ audio by Air longer than 4 minutes: type = 0 & artist = "air" & length > 4:00
+ all files on drive C: filename BEGINS C:
+ high rated items that haven't been played lately: rating > 3 & lastplay < [1 week ago]
+ newly added items that haven't been played yet: lastupd >= [yesterday] & playcount <= 0
+
+Note that you can also use this syntax in the search field of views. Simply prefix a '?' or 'query:' to your search string
diff --git a/Src/Plugins/Library/ml_local/remove.cpp b/Src/Plugins/Library/ml_local/remove.cpp
new file mode 100644
index 00000000..ff24848a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/remove.cpp
@@ -0,0 +1,31 @@
+#include "main.h"
+#include "api_mldb.h"
+
+// returns 0 on success
+// returns 1 on failure of either bad filename or invalid table
+int RemoveFileFromDB(const wchar_t *filename)
+{
+ // From mldbApi
+ int ret = 1;
+ if (!g_table) openDb();
+ if (filename && g_table)
+ {
+ // Issue wasabi callback for pre removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)filename, 0);
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ if (NDE_Scanner_LocateFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, filename))
+ {
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ ret = 0;
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ // Issue wasabi callback for post removal
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)filename, 0);
+ }
+ return ret;
+}
diff --git a/Src/Plugins/Library/ml_local/resource.h b/Src/Plugins/Library/ml_local/resource.h
new file mode 100644
index 00000000..44f6bb3a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resource.h
@@ -0,0 +1,606 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_local.rc
+//
+#define IDS_LOCAL_MEDIA 1
+#define IDS_SEARCHING_X_FILES_FOUND 2
+#define IDS_GETTING_INFO_FROM_FILES_PERCENT 3
+#define IDR_QUERIES_TEXT 4
+#define IDS_SCANNING_DIR 4
+#define IDS_SCANNING_FILE 5
+#define IDS_CHECKING_FOR_FILE 6
+#define IDS_COMPACTING 7
+#define IDS_REMOVING_FILES_NOT_EXISTING 8
+#define IDS_INITIALIZING 9
+#define IDS_SCANNING_X_OF_X_X_REMOVED 10
+#define IDS_SCANNED_X_FILES_X_REMOVED 11
+#define IDS_DATE_TIME_IS_TOO_COMPLEX 17
+#define IDS_DATE_TIME_EDITOR_QUESTION 18
+#define IDS_COMPARE_TO_STRING 19
+#define IDS_AFTER 20
+#define IDS_BEFORE 21
+#define IDS_SINCE 22
+#define IDS_UNTIL 23
+#define IDS_COMPARE_TO_LENGTH 24
+#define IDS_ABOVE 25
+#define IDS_BELOW 26
+#define IDS_ABOVE_OR_EQUAL 27
+#define IDS_BELOW_OR_EQUAL 28
+#define IDS_COMPARE_TO_NUMBER 29
+#define IDS_OFFSET_BY 30
+#define IDS_TIME_AGO 31
+#define IDS_SPACE_AFTER 32
+#define IDS_SPACE_BEFORE 33
+#define IDS_THE 34
+#define IDS_1ST 35
+#define IDS_2ND 36
+#define IDS_3RD 37
+#define IDS_4TH 38
+#define IDS_OF_THIS_MONTH 39
+#define IDS_NOW 40
+#define IDS_THIS_DATE 41
+#define IDS_THIS_MONTH 42
+#define IDS_THIS_DAY 43
+#define IDS_THIS_TIME 44
+#define IDS_ON_SPACE 45
+#define IDS_IN_SPACE 46
+#define IDS_OF_SPACE 47
+#define IDS_AT_SPACE 48
+#define IDS_NOON 49
+#define IDS_MIDNIGHT 50
+#define IDS_AGO 51
+#define IDS_QUERY_FIELD_IS_EMPTY 52
+#define IDS_EMPTY_QUERY 53
+#define IDS_NO_CHANGES_MADE_TO_QUERY 54
+#define IDS_QUERY_NOT_CHANGED 55
+#define IDS_REGISTERED 56
+#define IDS_ADD_PLEDIT_TO_LOCAL_MEDIA 57
+#define IDS_ERROR 58
+#define IDS_ADD_TO_LOCAL_MEDIA 59
+#define IDS_AUDIO 60
+#define IDS_VIDEO 61
+#define IDS_MOST_PLAYED 62
+#define IDS_RECENTLY_ADDED 63
+#define IDS_RECENTLY_PLAYED 64
+#define IDS_NEVER_PLAYED 65
+#define IDS_TOP_RATED 66
+#define IDS_REMOVE_ALL_ITEMS_IN_LIBRARY 67
+#define IDS_CONFIRMATION 68
+#define IDS_NEW_SMART_VIEW 69
+#define IDS_RESCAN_WATCH_FOLDERS 70
+#define IDS_ADD_MEDIA_TO_LIBRARY 71
+#define IDS_REMOVE_MISSING_FILES_FROM_ML 72
+#define IDS_ANY 73
+#define IDS_ALL 74
+#define IDS_OPTIONS 75
+#define IDS_DIRECTORY 76
+#define IDS_STOP_SCAN 77
+#define IDS_RESCAN_NOW 78
+#define IDS_DEFAULT 79
+#define IDS_RESCAN_ABORTED 80
+#define IDS_WATCH_FOLDERS 81
+#define IDS_EDIT_VIEW 82
+#define IDS_MY_NEW_VIEW 83
+#define IDS_SIMPLE_VIEW_EDITOR 84
+#define IDS_ADVANCED_EDITOR 85
+#define IDS_FILTERS 86
+#define IDS_MUST_ENTER_A_NAME 87
+#define IDS_DISCARD_CHANGES_TO_SIMPLE_EDITOR 88
+#define IDS_DAY 88
+#define IDS_VIEW_QUERY_MAY_HAVE_ERRORS 89
+#define IDS_VIEW_QUERY_IS_TOO_COMPLEX 90
+#define IDS_TEXT_AFTER_THE_FIRST_ERROR 91
+#define IDS_SOME_OF_THE_QUERY_LOGIC 92
+#define IDS_VIEW_EDITOR_QUESTION 93
+#define IDS_DELETE_THIS_VIEW 94
+#define IDS_REFINE 95
+#define IDS_CLEAR_REFINE 96
+#define IDS_ARTISTS 97
+#define IDS_ARTIST 97
+#define IDS_TITLE 98
+#define IDS_ALBUM 99
+#define IDS_LENGTH 100
+#define IDS_TRACK_LENGTH 101
+#define IDS_TRACK_NUMBER 101
+#define IDS_GENRE 102
+#define IDR_CONTEXTMENUS 103
+#define IDS_YEAR 103
+#define IDD_VIEW_MEDIA 104
+#define IDS_FILENAME 104
+#define IDD_VIEW_AUDIO 105
+#define IDS_RATING 105
+#define IDS_PLAY_COUNT 106
+#define IDS_PLAYED_LAST 107
+#define IDD_ADD_VIEW 108
+#define IDS_LAST_UPDATED 108
+#define IDS_FILE_TIME 109
+#define IDS_COMMENT 110
+#define IDS_FILE_SIZE 111
+#define IDS_BITRATE 112
+#define IDS_TYPE 113
+#define IDS_DISC 114
+#define IDS_ALBUM_ARTIST 115
+#define IDS_FILE_PATH 116
+#define IDD_EDITDIR 117
+#define IDS_ALBUM_GAIN 117
+#define IDS_TRACK_GAIN 118
+#define IDS_PUBLISHER 119
+#define IDS_COMPOSER 120
+#define IDS_EXTENSION 121
+#define IDS_IS_PODCAST 122
+#define IDD_EDIT_QUERY 123
+#define IDS_PODCAST_CHANNEL 123
+#define IDS_PODCAST_PUBLISH_DATE 124
+#define IDS_SCANNING 125
+#define IDD_EDIT_QUERY_PICK 126
+#define IDS_ERROR_DELETING_FILES 126
+#define IDD_NDE_RECOVERY 127
+#define IDS_ERROR_DELETING_X 127
+#define IDD_NEEDADDFILES 128
+#define IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY 128
+#define IDS_HIDE_INFO 129
+#define IDS_SHOW_INFO 130
+#define IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY 131
+#define IDS_ADD_MORE 132
+#define IDS_PLAY_ALL_FILES_BY 133
+#define IDB_TREEITEM_AUDIO 134
+#define IDS_PLAY_ALL_FILES_FROM 134
+#define IDB_TREEITEM_MOSTPLAYED 135
+#define IDS_PODCAST 135
+#define IDB_TREEITEM_NEVERPLAYED 136
+#define IDS_NON_PODCAST 136
+#define IDS_MIXABLE 137
+#define IDS_X_ITEM 138
+#define IDB_TREEITEM_RECENTLYADDED 139
+#define IDS_ALBUM_ART 139
+#define IDB_TREEITEM_RECENTLYPLAYED 140
+#define IDB_TREEITEM_TOPRATED 141
+#define IDS_LOOKING_UP_MEDIA_INFO 141
+#define IDB_TREEITEM_VIDEO 142
+#define IDS_CLICK_AN_ITEM_TO_GET_ITS_INFO 142
+#define IDD_MONITOR_SMALL 143
+#define IDS_PLEASE_CONNECT_TO_THE_INTERNET_TO_USE_THIS_FEATURE 143
+#define IDD_MONITOR_HIDE 144
+#define IDS_ALL_X_ALBUMS_X_WITHOUT_ALBUM 144
+#define IDD_REFRESH_METADATA 145
+#define IDS_ALL_X_ALBUMS 145
+#define IDD_REINDEX 146
+#define IDS_NO_ALBUM 146
+#define IDS_OPEN_MEDIA_LIBRARY_VIEW_RESULTS 147
+#define IDB_BITMAP1 148
+#define IDS_MEDIA_LIBRARY_VIEW_RESULTS 148
+#define IDB_TREEITEM_PODCASTS 149
+#define IDS_ADD_MEDIA_TO_LIBRARY_ 149
+#define IDS_SELECT_FOLDER_TO_ADD_TO_WINAMP_MEDIA_LIBRARY 150
+#define IDR_TEXT1 150
+#define IDB_TREEITEM_RECENTLYMODIFIED 150
+#define IDS_ADD 151
+#define IDD_ADD_VIEW_2 151
+#define IDS_SCAN_FOLDER_IN_BACKGROUND 152
+#define IDS_FOLDER_NAME_IS_INCORRECT 153
+#define IDS_INCORRECT_FOLDER_NAME 154
+#define IDS_WILLS_UBER_STRING 155
+#define IDS_EDIT_SMART_VIEW 156
+#define IDB_NEWFILTER_SIMPLE 157
+#define IDS_ADVANCED_MODE 157
+#define IDB_NEWFILTER_THREEFILTERS 158
+#define IDS_SIMPLE_MODE 158
+#define IDB_NEWFILTER_TWOFILTERS 159
+#define IDS_DAYS 159
+#define IDB_NEWFILTER_SIMPLEALBUM 160
+#define IDS_LENGTH_DURATION_STRING 160
+#define IDS_ITEM 161
+#define IDD_ADD_VIEW_2_NF 162
+#define IDS_ITEMS 162
+#define IDD_ADD_VIEW_CHILD_ADVANCED 163
+#define IDS_SCANNING_PLAIN 163
+#define IDS_ALL_ARTISTS 164
+#define IDS_ARTIST_INDEX 165
+#define IDB_TOOL_MODE 165
+#define IDS_ALBUM_ARTIST_INDEX 166
+#define IDB_TOOL_ART 166
+#define IDS_CUSTOM 167
+#define IDB_TOOL_COLS 167
+#define IDS_IS_VIDEO 168
+#define IDS_FILE_SIZE_KB 169
+#define IDR_IMAGE_NOTFOUND 169
+#define IDS_BITRATE_KBPS 170
+#define IDS_DISC_NUMBER 171
+#define IDS_DISCS 172
+#define IDS_TRACKS 173
+#define IDS_EQUALS 174
+#define IDS_DOES_NOT_EQUAL 175
+#define IDS_CONTAINS 176
+#define IDS_DOES_NOT_CONTAIN 177
+#define IDS_IS_ABOVE 178
+#define IDS_IS_BELOW 179
+#define IDS_EQUALS_OR_IS_ABOVE 180
+#define IDS_EQUALS_OR_IS_BELOW 181
+#define IDS_IS_EMPTY 182
+#define IDS_IS_NOT_EMPTY 183
+#define IDS_BEGINS_WITH 184
+#define IDS_ENDS_WITH 185
+#define IDS_IS_SIMILAR_TO 186
+#define IDS_AT 187
+#define IDS_NOT_AT 188
+#define IDS_STRING189 189
+#define IDS_PODCASTS 190
+#define IDS_AUDIO_BY_GENRE 191
+#define IDS_AUDIO_BY_INDEX 192
+#define IDS_60s_MUSIC 193
+#define IDS_70s_MUSIC 194
+#define IDS_80s_MUSIC 195
+#define IDS_90s_MUSIC 196
+#define IDS_00s_MUSIC 197
+#define IDS_ROCK_MUSIC 198
+#define IDS_CLASSICAL_MUSIC 199
+#define IDS_RECORD_LABELS 200
+#define IDS_ALBUMS 201
+#define IDS_ALBUM_ARTISTS 202
+#define IDS_ARTIST_S 203
+#define IDS_COMPOSERS 204
+#define IDS_GENRES 205
+#define IDS_PUBLISHERS 206
+#define IDS_YEARS 207
+#define IDS_ALBUM_ARTIST_INDEXES 208
+#define IDS_ARTIST_INDEXES 209
+#define IDS_PODCAST_CHANNELS 210
+#define IDS_ALL_X_S 211
+#define IDS_NO_S 212
+#define IDS_SIZE 213
+#define IDS_INFO_NAVIGATE_ERROR 214
+#define IDS_NO_IMAGE 214
+#define IDS_AVAILABLE 215
+#define IDS_NO_ARTIST 216
+#define IDS_NO_GENRE 217
+#define IDS_OTHER2 218
+#define IDD_PREFS1 219
+#define IDS_STRING219 219
+#define IDS_CREATE_NEW_SMART_VIEW 219
+#define IDS_SIMPLE_ALBUM 219
+#define IDD_VIEW_DB_ERROR 220
+#define IDS_AUDIO_BUTTON_TT1 220
+#define IDS_AUDIO_BUTTON_TT2 221
+#define IDS_AUDIO_BUTTON_TT3 222
+#define IDS_GET_ALBUM_ART 223
+#define IDS_REFRESH_ALBUM_ART 224
+#define IDS_OPEN_FOLDER 225
+#define IDS_BPM 226
+#define IDS_META_STR 227
+#define IDD_PREFSFR 228
+#define IDS_SMART_STR 228
+#define IDS_GUESS_STR 229
+#define IDS_RECURSE_STR 230
+#define IDS_MORE_ARTIST_INFO 234
+#define IDS_PLAY_RANDOM_ITEM 235
+#define IDS_ENQUEUE_RANDOM_ITEM 236
+#define IDS_LOSSLESS 237
+#define IDS_CATEGORY 238
+#define IDD_VIEW_MINIINFO 239
+#define IDS_CATEGORIES 239
+#define IDS_GENRE_ALT 240
+#define IDS_YEAR_ALT 241
+#define IDD_PREFS3 242
+#define IDS_ALBUM_ARTIST_ALT 242
+#define IDS_ALBUM_GAIN_ALT 243
+#define IDS_PUBLISHER_ALT 244
+#define IDS_COMPOSER_ALT 245
+#define IDS_ARTIST_INDEX_ALT 246
+#define IDD_CUSTCOLUMNS 247
+#define IDS_ALBUM_ARTIST_INDEX_ALT 247
+#define IDS_PODCAST_CHANNEL_ALT 248
+#define IDS_CATEGORY_ALT 249
+#define IDD_SCROLLCHILD 250
+#define IDS_ARTIST_ALT 250
+#define IDD_SCROLLCHILDHOST 251
+#define IDS_ALBUM_ALT 251
+#define IDD_SCROLLCHILDFILTER 252
+#define IDS_KBPS 252
+#define IDD_TIMEEDITOR 253
+#define IDS_RECENTLY_PLAYED_TEXT 253
+#define IDS_PRODUCER 255
+#define IDS_DIRECTOR 256
+#define IDS_PRODUCER_ALT 257
+#define IDS_DIRECTOR_ALT 258
+#define IDS_CODEC 259
+#define IDD_PREFS_METADATA 260
+#define IDS_WIDTH 260
+#define IDS_HEIGHT 261
+#define IDS_DIMENSION 262
+#define IDS_IN_X_SEC 263
+#define IDS_TRACKS_MENU 264
+#define IDS_ERROR_PLG_SELECT_TRACKS 267
+#define IDS_NULLSOFT_PLAYLIST_GENERATOR 268
+#define IDS_VIEW_ALL_FILES_BY 269
+#define IDS_VIEW_ALL_FILES_FROM 270
+#define IDR_DB_ERROR 270
+#define IDR_NDE_ERROR 273
+#define IDS_DATE_ADDED 274
+#define IDS_REFRESH_FILESIZE_DATEADDED 275
+#define IDS_RECENTLY_MODIFIED 276
+#define IDS_FINISHED 277
+#define IDS_REFRESH_MESSAGE 278
+#define IDS_CLOUD 279
+#define IDR_VIEW_ACCELERATORS 279
+#define IDS_CLOUD_SOURCES 280
+#define IDR_QUERY_ACCELERATORS 280
+#define IDS_CLOUD_HIDDEN 281
+#define IDS_TRACK_AVAILABLE 282
+#define IDS_UPLOAD_TO_SOURCE 283
+#define IDS_UPLOADING_TO_SOURCE 284
+#define IDS_STRING140 285
+#define IDD_EDIT_INFO 286
+#define IDD_ADDSTUFF 287
+#define IDS_NO_RATING 516
+#define IDS_X_ITEM_SELECTED 517
+#define IDS_X_ITEMS_SELECTED 518
+#define IDS_UPDATING_FILES 519
+#define IDS_UPDATING_X 520
+#define IDS_ERROR_UPDATING_FILE 521
+#define IDS_INFO_UPDATING_ERROR 522
+#define IDC_LIST1 1000
+#define IDC_LIST2 1001
+#define IDC_EDIT_ARTIST 1002
+#define IDC_LIST3 1002
+#define IDC_CHECK_ARTIST 1003
+#define IDC_CHECK_TITLE 1004
+#define IDC_CHECK_ALBUM 1005
+#define IDC_CLEAR 1005
+#define IDC_QUICKSEARCH 1006
+#define IDC_CHECK_TRACK 1006
+#define IDC_PROGRESS1 1007
+#define IDC_CHECK_GENRE 1007
+#define IDC_STATUS 1008
+#define IDC_CHECK_YEAR 1008
+#define IDC_RECT 1009
+#define IDC_EDIT_TITLE 1009
+#define IDC_EDIT_ALBUM 1010
+#define IDC_EDIT_TRACK 1011
+#define IDC_EDIT_GENRE 1012
+#define IDC_HDELIM 1013
+#define IDC_EDIT_YEAR 1013
+#define IDC_CHECK_COMMENT 1014
+#define IDC_MEDIASTATUS 1015
+#define IDC_EDIT_COMMENT 1015
+#define IDC_BUTTON_PLAY 1016
+#define IDC_CHECK_DISC 1016
+#define IDC_BUTTON_ENQUEUE 1017
+#define IDC_EDIT_DISC 1017
+#define IDC_BUTTON_INFOTOGGLE 1018
+#define IDC_CHECK_ALBUMARTIST 1018
+#define IDC_EDIT_ALBUMARTIST 1019
+#define IDC_BUTTON_CREATEPLAYLIST 1019
+#define IDC_SEARCHCAPTION 1020
+#define IDC_CHECK_PUBLISHER 1020
+#define IDC_EDIT_PUBLISHER 1021
+#define IDC_RESCAN 1022
+#define IDC_CHECK_COMPOSER 1022
+#define IDC_NAME 1023
+#define IDC_EDIT_COMPOSER 1023
+#define IDC_QUERY 1024
+#define IDC_CHECK_CATEGORY 1024
+#define IDC_DIV1 1025
+#define IDC_EDIT_COMPOSER2 1025
+#define IDC_EDIT_CATEGORY 1025
+#define IDC_DIV2 1026
+#define IDC_CHECK_BPM 1026
+#define IDC_EDIT_BPM 1027
+#define IDC_CHECK_RATING 1028
+#define IDC_RADIO1 1029
+#define IDC_RADIO2 1030
+#define IDC_RADIO6 1031
+#define IDC_RADIO_TWOFILTERS 1031
+#define IDC_COMBO1 1032
+#define IDC_RADIO8 1032
+#define IDC_COMBO2 1033
+#define IDC_RADIO_THREEFILTERS 1033
+#define IDC_RADIO3 1035
+#define IDC_RADIO_BELOW 1035
+#define IDC_RADIO_ABOVEOREQUAL 1036
+#define IDC_RADIO_BELOWOREQUAL 1037
+#define IDC_COMBO_FILTER1 1046
+#define IDC_COMBO_FILTER2 1047
+#define IDC_COMBO_FILTER3 1048
+#define IDC_RADIO_FILTERS2 1049
+#define IDC_RADIO_FILTERS3 1050
+#define IDC_HEADER 1051
+#define IDC_STATIC_FILTER3 1051
+#define IDC_CHECK1 1052
+#define IDC_REMEMBER_SEARCH 1053
+#define IDC_CHECK2 1054
+#define IDC_CHECK7 1055
+#define IDC_EDIT2 1056
+#define IDC_STATIC4 1060
+#define IDC_CHECK8 1061
+#define IDC_BUTTON1 1062
+#define IDC_BUTTON3 1063
+#define IDC_STATIC5 1063
+#define IDC_BUTTON4 1064
+#define IDC_BUTTON5 1065
+#define IDC_BUTTON_NOW 1065
+#define IDC_BUTTON2 1066
+#define IDC_BUTTON_QUERYBUILD 1067
+#define IDC_EDIT1 1071
+#define IDC_CHECK3 1076
+#define IDC_CHECK4 1077
+#define IDC_CHECK5 1078
+#define IDC_TAB1 1079
+#define IDC_CHECK6 1087
+#define IDC_TEXT 1090
+#define IDC_RADIO_ISEMPTY 1091
+#define IDC_RADIO_TIMEAGO_Y 1094
+#define IDC_RADIO_TIMEAGO_M 1095
+#define IDC_RADIO_TIMEAGO_W 1096
+#define IDC_RADIO_TIMEAGO_D 1097
+#define IDC_RADIO_TIMEAGO_H 1098
+#define IDC_RADIO_TIMEAGO_MIN 1099
+#define IDC_RADIO_TIMEAGO_S 1100
+#define IDC_EDIT3 1106
+#define IDC_MINUTES 1137
+#define IDC_CHECK_RELATIVE 1139
+#define IDC_CHECK_ABSOLUTE 1140
+#define IDC_DATETIMEPICKER2 1141
+#define IDC_CHECK_TIMEAGO 1143
+#define IDC_CHECK_SELECTIVE 1144
+#define IDC_RADIO_THISYEAR 1146
+#define IDC_RADIO_THISMONTH 1147
+#define IDC_RADIO_YEAR 1148
+#define IDC_RADIO_MONTH 1150
+#define IDC_RADIO_THISDAY 1151
+#define IDC_RADIO_DAY 1152
+#define IDC_EDIT_DAY 1153
+#define IDC_RADIO_THISTIME 1154
+#define IDC_RADIO_TIME 1155
+#define IDC_EDIT_RESULT 1164
+#define IDC_EDIT_QUERY 1165
+#define IDC_STATIC_YEAR 1166
+#define IDC_STATIC_MONTH 1167
+#define IDC_STATIC_DAY 1168
+#define IDC_STATIC_TIME 1169
+#define IDC_STATIC_STRING 1173
+#define IDC_STATIC_ABSOLUTE 1174
+#define IDC_STATIC_RELATIVE 1175
+#define IDC_STATIC_TIMEAGO 1176
+#define IDC_STATIC_SELECTIVE 1177
+#define IDC_EDIT 1178
+#define IDC_DATETIMEPICKER1 1179
+#define IDC_LIST_FIELDS 1180
+#define IDC_RADIO_EQUAL 1181
+#define IDC_RADIO_ABOVE 1182
+#define IDC_RADIO_ISLIKE 1183
+#define IDC_CHECK_NOT 1184
+#define IDC_EDIT_STRING 1185
+#define IDC_DATETIMEPICKER 1187
+#define IDC_EDIT_DATETIME 1187
+#define IDC_EDIT_TIMEAGO 1188
+#define IDC_BUTTON_PICK 1189
+#define IDC_BUTTON_MONTH 1190
+#define IDC_BUTTON_AND 1191
+#define IDC_BUTTON_OR 1192
+#define IDC_STATIC_DATETIME 1197
+#define IDC_STATIC_DIRECTION 1198
+#define IDC_RADIO_AFTER 1199
+#define IDC_RADIO_BEFORE 1200
+#define IDC_RADIO_NOON 1201
+#define IDC_RADIO_MIDNIGHT 1202
+#define IDC_STATIC_QUERYTIME 1203
+#define IDC_RADIO_BEGINS 1204
+#define IDC_RADIO_ENDS 1205
+#define IDC_STATIC_RESULT 1206
+#define IDC_STATIC_QUERY 1207
+#define IDC_STATIC_GENERAL 1208
+#define ID_BUTTON_SENDTOQUERY 1209
+#define IDC_RADIO_CONTAINS 1210
+#define IDC_DEFS 1210
+#define IDC_STATIC_TOTAL 1212
+#define IDC_STATIC_PERCENT 1213
+#define IDC_STATIC_RECOVERED 1214
+#define IDC_PROGRESS_PERCENT 1215
+#define IDC_STATIC_LOST 1216
+#define ID_ADD_FILES 1274
+#define IDC_OP 1275
+#define IDC_CHILDFRAME 1277
+#define IDC_BUTTON_EDITDATETIME 1278
+#define IDC_STATIC_CURDATETIME 1279
+#define IDC_CONFMETA 1288
+#define IDC_STATIC1 1293
+#define IDC_STATIC2 1294
+#define IDC_BUTTON_MIX 1304
+#define IDC_MIXABLE 1305
+#define IDC_CHECK_ATF 1311
+#define IDC_COMBO_FILTER 1312
+#define IDC_REFRESHMETADATA_STATUS 1314
+#define IDC_ALBUMGAIN 1315
+#define IDC_ARTIST_AS_ALBUMARTIST 1316
+#define IDC_FILTERS0 1317
+#define IDC_RADIO_FILTERS0 1317
+#define IDC_STATIC_FILTER2 1318
+#define IDC_RADIO_SIMPLE 1320
+#define IDC_IMAGE_SIMPLEALBUM 1321
+#define IDC_IMAGE_TWOFILTERS 1322
+#define IDC_IMAGE_THREEFILTERS 1323
+#define IDC_STATIC_SIMPLE 1324
+#define IDC_STATIC_SIMPLEALBUM 1325
+#define IDC_STATIC_TWOFILTERS 1326
+#define IDC_STATIC_THREEFILTERS 1327
+#define IDC_RADIO_SIMPLEALBUM 1328
+#define IDC_HIDE_EXTINFO 1329
+#define IDC_COMBO_PRESETS 1330
+#define IDC_STATIC_INFO 1331
+#define IDC_CHECK_SHOWINFO 1332
+#define IDC_STATIC_FILTER 1333
+#define IDC_IMAGE_SIMPLE 1334
+#define IDC_AND 1335
+#define IDC_OR 1336
+#define IDC_BUTTON_MODE 1337
+#define IDC_BUTTON_ARTMODE 1338
+#define IDC_BUTTON_VIEWMODE 1339
+#define IDC_BUTTON_COLUMNS 1340
+#define IDC_BTN_LINK_PROMO 1340
+#define IDC_SPIN1 1341
+#define IDC_IMPORT_ITUNES 1342
+#define IDC_CHECK_DIRECTOR 1343
+#define IDC_EDIT_DIRECTOR 1344
+#define IDC_CHECK_PRODUCER 1345
+#define IDC_EDIT_PRODUCER 1346
+#define IDC_STATIC_QUERYDELAY 1347
+#define IDC_CHECK_PODCAST 1347
+#define IDC_EDIT_QUERYDELAY 1348
+#define IDC_EDIT_PODCAST_CHANNEL 1348
+#define IDC_ERROR_1 1349
+#define IDC_CHECK_PODCAST_CHANNEL 1349
+#define IDC_RESET_DB_ON_ERROR 1352
+#define IDC_DB_ERROR 1353
+#define IDC_COMBO_RATING 1354
+#define IDS_SUBSTANTIVES 2048
+#define IDS_PLAY_ENQ_RND_ALTERNATIVE 2049
+#define ID_MEDIAWND_PLAYSELECTEDFILES 40001
+#define ID_MEDIAWND_ENQUEUESELECTEDFILES 40002
+#define ID_MEDIAWND_REMOVEFROMLIBRARY 40003
+#define ID_AUDIOWND_PLAYSELECTION 40004
+#define ID_AUDIOWND_ENQUEUESELECTION 40005
+#define ID_MEDIAWND_SELECTALL 40006
+#define ID_QUERYWND_EDIT 40007
+#define ID_QUERYWND_DELETE 40008
+#define ID_QUERYMENU_ADDNEWQUERY 40009
+#define ID_QUERYWND_PLAYQUERY 40010
+#define ID_QUERYWND_ENQUEUEQUERY 40011
+#define ID_EDITITEMINFOS 40012
+#define ID_MEDIAWND_ADDTOPLAYLIST 40033
+#define ID_MEDIAWND_EXPLOREFOLDER 40037
+#define ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES 40038
+#define ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS 40039
+#define ID_HEADERWND_CUSTOMIZECOLUMNS 40074
+#define ID_RATE_1 40075
+#define ID_RATE_2 40076
+#define ID_RATE_3 40077
+#define ID_RATE_4 40078
+#define ID_RATE_5 40079
+#define ID_RATE_0 40080
+#define IDC_REFRESH_METADATA 40098
+#define ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR 40099
+#define ID_ARTHEADERWND_SMALLICON 40100
+#define ID_ARTHEADERWND_MEDIUMICON 40101
+#define ID_ARTHEADERWND_LARGEICON 40102
+#define ID_ARTHEADERWND_MEDIUMDETAILS 40104
+#define ID_ARTHEADERWND_LARGEDETAILS 40105
+#define ID_ARTHEADERWND_SMALLDETAILS 40106
+#define ID_AUDIOWND_PLAYRANDOMITEM 40108
+#define ID_AUDIOWND_ENQUEUERANDOMITEM 40109
+#define ID_QUERYMENU_PREFERENCES 40110
+#define ID_QUERYMENU_HELP 40111
+#define ID_ARTHEADERWND_EXTRALARGEICON 40112
+#define ID_ARTHEADERWND_SHOWTEXT 40113
+#define ID_PE_ID3 40208
+#define IDS_NULLSOFT_LOCAL_MEDIA 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 281
+#define _APS_NEXT_COMMAND_VALUE 40129
+#define _APS_NEXT_CONTROL_VALUE 1355
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp b/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp
new file mode 100644
index 00000000..2688e2e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/icn_columns.bmp b/Src/Plugins/Library/ml_local/resources/icn_columns.bmp
new file mode 100644
index 00000000..46be6b46
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/icn_columns.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp b/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp
new file mode 100644
index 00000000..60f52169
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_simple.bmp b/Src/Plugins/Library/ml_local/resources/nf_simple.bmp
new file mode 100644
index 00000000..eaf5b9aa
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_simple.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp b/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp
new file mode 100644
index 00000000..1d94f00a
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp b/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp
new file mode 100644
index 00000000..4af1ba08
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp b/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp
new file mode 100644
index 00000000..2aed6d4f
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/notfound.png b/Src/Plugins/Library/ml_local/resources/notfound.png
new file mode 100644
index 00000000..f76c0516
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/notfound.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp
new file mode 100644
index 00000000..0fb45741
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp
new file mode 100644
index 00000000..0d235d66
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp
new file mode 100644
index 00000000..d299fda8
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp
new file mode 100644
index 00000000..428d5b50
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp
new file mode 100644
index 00000000..225e45cc
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp
new file mode 100644
index 00000000..4ec00ef0
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp
new file mode 100644
index 00000000..476bde34
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp
new file mode 100644
index 00000000..63b5036f
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp
new file mode 100644
index 00000000..8fccdeef
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_local/util.cpp b/Src/Plugins/Library/ml_local/util.cpp
new file mode 100644
index 00000000..8ac23b8d
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/util.cpp
@@ -0,0 +1,77 @@
+#include "main.h"
+#include "../replicant/nu/ns_wc.h"
+#include "ml_local.h"
+#include <string.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+
+extern "C" {
+
+void process_substantives(wchar_t* dest)
+{
+ if(!substantives)
+ {
+ wchar_t *b = dest;
+ while (!IsCharSpaceW(*b) && *b) b++;
+ while (IsCharSpaceW(*b) && *b) b++;
+ while (!IsCharSpaceW(*b) && *b) b++;
+ CharLowerW(b);
+ }
+}
+
+HRESULT ResolveShortCut(HWND hwnd, LPCWSTR pszShortcutFile, LPWSTR pszPath)
+{
+ IShellLink* psl = 0;
+ WIN32_FIND_DATA wfd = {0};
+
+ *pszPath = 0; // assume failure
+
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+ IID_IShellLink, (void **) &psl);
+ if (SUCCEEDED(hres))
+ {
+ IPersistFile* ppf;
+
+ hres = psl->QueryInterface(IID_IPersistFile, (void **) &ppf); // OLE 2! Yay! --YO
+ if (SUCCEEDED(hres))
+ {
+ wchar_t wsz[MAX_PATH] = {0};
+
+ hres = ppf->Load(wsz, STGM_READ);
+ if (SUCCEEDED(hres))
+ {
+ hres = psl->Resolve(hwnd, SLR_ANY_MATCH);
+ if (SUCCEEDED(hres))
+ {
+ wchar_t szGotPath[MAX_PATH] = {0};
+ wcsncpy(szGotPath, pszShortcutFile, MAX_PATH);
+ hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA *)&wfd, SLGP_SHORTPATH );
+ wcsncpy(pszPath, szGotPath, MAX_PATH);
+ }
+ }
+ ppf->Release();
+ }
+ psl->Release();
+ }
+ return SUCCEEDED(hres);
+}
+
+void ConvertRatingMenuStar(HMENU menu, UINT menu_id)
+{
+ MENUITEMINFOW mi = {sizeof(mi), MIIM_DATA | MIIM_TYPE, MFT_STRING};
+ wchar_t rateBuf[32], *rateStr = rateBuf;
+ mi.dwTypeData = rateBuf;
+ mi.cch = 32;
+ if(GetMenuItemInfoW(menu, menu_id, FALSE, &mi))
+ {
+ while(rateStr && *rateStr)
+ {
+ if(*rateStr == L'*') *rateStr = L'\u2605';
+ rateStr=CharNextW(rateStr);
+ }
+ SetMenuItemInfoW(menu, menu_id, FALSE, &mi);
+ }
+}
+
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/version.rc2 b/Src/Plugins/Library/ml_local/version.rc2
new file mode 100644
index 00000000..104d3186
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 3,35,0,0
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp Media Library Plug-in"
+ VALUE "FileVersion", "3,35,0,0"
+ VALUE "InternalName", "Nullsoft Library"
+ VALUE "LegalCopyright", "Copyright © 2002-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_local.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/Src/Plugins/Library/ml_local/view_audio.cpp b/Src/Plugins/Library/ml_local/view_audio.cpp
new file mode 100644
index 00000000..a2068ffa
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_audio.cpp
@@ -0,0 +1,2403 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include "../nu/listview.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "../nde/nde_c.h"
+#include <shlwapi.h>
+#include "SimpleFilter.h"
+#include "AlbumFilter.h"
+#include "AlbumArtFilter.h"
+#include "../replicant/nu/AutoChar.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../nu/CGlobalAtom.h"
+#include <tataki/export.h>
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+
+static CGlobalAtom PROPW_DIVDATA(L"DIVDATA");
+#define DELIMSTR "|"
+#define LDELIMSTR L"|"
+
+#define MAX_FILTERS 3
+
+ViewFilter *filter[MAX_FILTERS] = {0};
+
+int numFilters = 0;
+extern void queryStrEscape(const char *raw, GayString &str);
+extern void FixAmps(wchar_t *str, size_t size);
+static W_ListView m_list1;
+static W_ListView m_list2;
+static W_ListView m_list3;
+static HWND m_media_hwnd;
+static HWND m_hwnd;
+GayStringW l_query;
+static int IPC_LIBRARY_SENDTOMENU;
+static int m_sort_by, m_sort_dir, m_sort_which;
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+enum
+{
+ PLAY=0,
+ ENQUEUE=1,
+};
+
+#define ARTIST 0x01
+#define ALBUMARTIST 0x02
+#define GENRE 0x03
+#define PUBLISHER 0x04
+#define COMPOSER 0x05
+#define ALBUM 0x06
+#define YEAR 0x07
+#define ARTISTINDEX 0x08
+#define ALBUMARTISTINDEX 0x09
+#define PODCASTCHANNEL 0x0A
+#define ALBUMART 0x0B
+#define CATEGORY 0x0C
+#define DIRECTOR 0x0D
+#define PRODUCER 0x0E
+
+#define MAKEVIEW_3FILTER(a, b, c) (a | (b << 8) | (c << 16))
+#define MAKEVIEW_2FILTER(a, b) (a | (b << 8))
+
+static ViewFilter * getFilter(int n, HWND hwndDlg, int dlgitem, C_Config *c)
+{
+ switch (n)
+ {
+ case 0: return new ArtistFilter();
+ case 1: return new AlbumArtistFilter();
+ case 2: return new GenreFilter();
+ case 3: return new PublisherFilter();
+ case 4: return new ComposerFilter();
+ case 5: return new AlbumFilter();
+ case 6: return new YearFilter();
+ case 7: return new ArtistIndexFilter();
+ case 8: return new AlbumArtistIndexFilter();
+ case 9: return new PodcastChannelFilter();
+ case 10: return new AlbumArtFilter(hwndDlg,dlgitem,c);
+ case 11: return new CategoryFilter();
+ case 12: return new DirectorFilter();
+ case 13: return new ProducerFilter();
+ }
+ return new ArtistFilter();
+}
+
+const wchar_t *getFilterName(unsigned int filterId, wchar_t *buffer, size_t bufferSize) // for config
+{
+ const int filterNames[] =
+ {
+ IDS_ARTIST,
+ IDS_ALBUM_ARTIST,
+ IDS_GENRE,
+ IDS_PUBLISHER,
+ IDS_COMPOSER,
+ IDS_ALBUM,
+ IDS_YEAR,
+ IDS_ARTIST_INDEX,
+ IDS_ALBUM_ARTIST_INDEX,
+ IDS_PODCAST_CHANNEL,
+ IDS_ALBUM_ART,
+ IDS_CATEGORY,
+ IDS_DIRECTOR,
+ IDS_PRODUCER,
+ };
+
+ if (filterId >= ARRAYSIZE(filterNames))
+ return NULL;
+
+ return WASABI_API_LNGSTRINGW_BUF(filterNames[filterId], buffer, bufferSize);
+}
+
+static void mysearchCallbackAlbumUpdate(itemRecordW *items, int numitems, int user32, int *killswitch)
+{
+ if (killswitch && *killswitch) return;
+
+ // user32 specifies whether or not to bother with rebuilding artist list
+ if (user32 == 2 && numFilters == 2) return;
+
+ if (user32 <= 2 && numFilters >= 3)
+ filter[2]->Fill(items,numitems,killswitch,0);
+
+ if (user32 <= 1 && numFilters >= 2)
+ filter[1]->Fill(items,numitems,killswitch,numFilters >= 3 ? filter[2]->numGroups : 0);
+
+ if (user32 == 0 && numFilters >= 1)
+ filter[0]->Fill(items, numitems, killswitch,numFilters >= 2 ? filter[1]->numGroups : 0);
+
+ PostMessage(m_hwnd, WM_APP + 3, 0x69, user32);
+}
+
+static int makeFilterQuery(GayStringW *query, ViewFilter *f)
+{
+ int ret = 0;
+ if (!(f->list->GetSelected(0) && f->HasTopItem()))
+ {
+ int c = f->list->GetCount();
+ int selCount = SendMessageW(f->list->getwnd(), LVM_GETSELECTEDCOUNT, 0, 0L);
+
+ if (selCount && ((f->HasTopItem()) ? selCount < (c - 1) : selCount < c))
+ {
+ int needor = 0, needclose = 0;
+ for (int x = f->HasTopItem()?1:0; x < c; x ++)
+ {
+ if (f->list->GetSelected(x))
+ {
+ if (needor)
+ query->Append(L"|");
+ else
+ {
+ if (query->Get()[0])
+ query->Append(L"&(");
+ else
+ query->Set(L"(");
+ needclose = 1;
+ }
+ if (!f->MakeFilterQuery(x,query))
+ {
+ const wchar_t *val = f->GetText(x);
+ if (val && *val)
+ {
+ query->Append(f->GetField());
+ query->Append(L" ");
+ query->Append(f->GetComparisonOperator());
+ query->Append(L" \"");
+ GayStringW escaped;
+ queryStrEscape(val, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\"");
+ }
+ else
+ {
+ const wchar_t *field = f->GetField();
+ query->Append(field);
+ query->Append(L" = \"\" | ");
+ query->Append(field);
+ query->Append(L" ISEMPTY");
+ }
+ }
+ ret++;
+ needor = 1;
+ }
+ }
+ if (needclose) query->Append(L")");
+ }
+ }
+ return ret;
+}
+
+static void getParentPlusSearchQuery(GayStringW *gs)
+{
+ extern wchar_t* m_query;
+ wchar_t *parq = m_query;
+
+ if (parq && parq[0])
+ {
+ gs->Set(L"(");
+ gs->Append(parq);
+ gs->Append(L")");
+ }
+ else gs->Set(L"");
+
+ GayStringW query;
+ wchar_t buf[2048] = {0};
+ GetWindowTextW(GetDlgItem(m_hwnd, IDC_QUICKSEARCH), buf, ARRAYSIZE(buf));
+ if (buf[0]) makeQueryStringFromText(&query, buf);
+
+ if (query.Get() && query.Get()[0])
+ {
+ if (gs->Get()[0])
+ {
+ gs->Append(L" & (");
+ gs->Append(query.Get());
+ gs->Append(L")");
+ }
+ else gs->Set(query.Get());
+ }
+}
+
+
+static void playList(int enqueue, int pane)
+{
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+
+ for (int i=0; i<=pane; i++)
+ makeFilterQuery(&query,filter[i]);
+
+ if (!g_table) return;
+
+ main_playQuery(g_view_metaconf, query.Get(), enqueue);
+}
+
+static void playRandomList(int enqueue, int pane)
+{
+ static int inited = 0;
+ if (!inited)
+ srand((unsigned)(time(0)));
+ inited = 1;
+ int n;
+ n = filter[pane]->list->GetCount() * rand()/RAND_MAX;
+ // Martin> dunno if we should display the results in ML then? atm we are
+ filter[pane]->list->UnselectAll();
+ filter[pane]->list->SetSelected(n);
+ filter[pane]->list->ScrollTo(n);
+ playList(enqueue, pane);
+}
+
+static void buildRecordListW(itemRecordListW *obj, int pane)
+{
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+
+ for (int i=0; i<=pane; i++)
+ makeFilterQuery(&query,filter[i]);
+
+ if (!g_table) return;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query.Get());
+ saveQueryToListW(g_view_metaconf, s, obj, 0, 0, (resultsniff_funcW)-1);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+}
+
+void UpdateRating_RowCache(const wchar_t *filename, int new_rating);
+static bool rateList(int rate)
+{
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ int valid = 0;
+ for (int i=0; i<numFilters; i++)
+ valid += makeFilterQuery(&query,filter[i]);
+
+ if (valid)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query.Get());
+ NDE_Scanner_First(s);
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+ // get filename here as calling later fails due to the edit for some reason
+ // as was being called directly in the updateFileInfo(..) call previously..
+ wchar_t *filename = NDE_StringField_GetString(f);
+ if (!filename) break;
+ // update before, so we can take advantage of row cache pointer equality (sometimes)
+ UpdateRating_RowCache(filename, rate);
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, rate);
+ NDE_Scanner_Post(s);
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (rate > 0)
+ {
+ wsprintfW(buf, L"%d", rate);
+ }
+ else
+ buf[0] = 0;
+
+ updateFileInfo(filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+
+ g_table_dirty++;
+ }
+ while (NDE_Scanner_Next(s));
+
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty) NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+
+ // refresh media list
+// SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)0, (LPARAM)0);
+ return true;
+ }
+ return false;
+}
+
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+typedef void (WINAPI *DIVIDERMOVED)(HWND, INT, LPARAM);
+typedef struct _DIVIDER
+{
+ BOOL fVertical;
+ DIVIDERMOVED callback;
+ LPARAM param;
+ WNDPROC fnOldProc;
+ BOOL fUnicode;
+ INT clickoffs;
+
+} DIVIDER;
+
+BOOL AttachDivider(HWND hwnd, BOOL fVertical, DIVIDERMOVED callback, LPARAM param)
+{
+ if (!hwnd) return FALSE;
+
+ DIVIDER *pd = (DIVIDER*)calloc(1, sizeof(DIVIDER));
+ if (!pd) return FALSE;
+
+ pd->fUnicode = IsWindowUnicode(hwnd);
+ pd->fnOldProc = (WNDPROC)((pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc) :
+ SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc));
+ if (!pd->fnOldProc || !SetPropW(hwnd, PROPW_DIVDATA, pd))
+ {
+ free(pd);
+ return FALSE;
+ }
+ pd->fVertical = fVertical;
+ pd->param = param;
+ pd->callback = callback;
+
+ return TRUE;
+}
+#define GET_DIVIDER(hwnd) (DIVIDER*)GetPropW(hwnd, PROPW_DIVDATA)
+
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DIVIDER *pd;
+ pd = GET_DIVIDER(hwnd);
+ if (!pd) return (IsWindowUnicode(hwnd)) ? DefWindowProcW(hwnd, uMsg, wParam, lParam) : DefWindowProcA(hwnd, uMsg, wParam, lParam);
+
+ switch (uMsg)
+ {
+ case WM_DESTROY:
+ RemovePropW(hwnd, PROPW_DIVDATA);
+ (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+ (pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc) : SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc);
+ free(pd);
+ return 0;
+ case WM_LBUTTONDOWN:
+ pd->clickoffs = (pd->fVertical) ? LOWORD(lParam) : HIWORD(lParam);
+ SetCapture(hwnd);
+ break;
+ case WM_LBUTTONUP:
+ ReleaseCapture();
+ break;
+ case WM_SETCURSOR:
+ SetCursor(LoadCursor(NULL, (pd->fVertical) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+
+ case WM_MOUSEMOVE:
+ {
+ RECT rw;
+ GetWindowRect(hwnd, &rw);
+ GetCursorPos(((LPPOINT)&rw) + 1);
+ (pd->fVertical) ? rw.right -= pd->clickoffs : rw.bottom -= pd->clickoffs;
+
+ if ((pd->fVertical && rw.left != rw.right) || (!pd->fVertical && rw.top != rw.bottom))
+ {
+ MapWindowPoints(HWND_DESKTOP, GetParent(hwnd), ((LPPOINT)&rw) + 1, 1);
+ if (pd->callback) pd->callback(hwnd, (pd->fVertical) ? rw.right : rw.bottom, pd->param);
+ }
+ }
+ break;
+ }
+
+ return (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+}
+
+static int m_nodrawtopborders;
+static int adiv1_nodraw, adiv2_nodraw;
+
+#define DIVIDER_FILTER 0x1
+#define DIVIDER_FILTER2 0x3
+#define DIVIDER_MEDIA 0x2
+
+static int div_filterpos, div_filter2pos, div_mediapos;
+static BOOL g_displaysearch = TRUE;
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+ BOOL *pHidden;
+}LAYOUT, PLAYOUT;
+
+typedef struct _LCTRL
+{
+ INT id;
+ BOOL hidden;
+} LCTRL;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if (fVis && (_layout->cx < 1 || _layout->cy < 1)) { _layout->flags |= SWP_HIDEWINDOW; *_layout->pHidden = TRUE; } \
+ if (!fVis && *_layout->pHidden && _layout->cx > 0 && _layout->cy > 0) { _layout->flags |= SWP_SHOWWINDOW; *_layout->pHidden = FALSE; } \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x3
+#define GROUP_SEARCH 0x1
+#define GROUP_FILTER 0x2
+#define GROUP_MEDIA 0x3
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw)
+{
+ static LCTRL controls[] =
+ {
+ {GROUP_SEARCH, FALSE}, {IDC_BUTTON_ARTMODE, FALSE}, {IDC_BUTTON_VIEWMODE, FALSE}, {IDC_BUTTON_COLUMNS, FALSE}, {IDC_SEARCHCAPTION, FALSE}, {IDC_CLEAR, FALSE}, {IDC_QUICKSEARCH, FALSE},
+ {GROUP_MEDIA, FALSE}, {IDC_HDELIM, FALSE}, {IDD_VIEW_MEDIA, FALSE},
+ {GROUP_FILTER, FALSE}, {IDC_LIST1, FALSE}, {IDC_DIV1, FALSE}, {IDC_LIST2, FALSE}, {IDC_DIV2, FALSE}, {IDC_LIST3, FALSE}
+ };
+
+ INT index, divY, divX, divX2;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.bottom == rc.top || rc.right == rc.left) return;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ divY = (div_mediapos * (rc.bottom- rc.top)) / 100000;
+ divX = numFilters==1? rc.right : (div_filterpos * (rc.right- rc.left)) / 100000;
+ divX2 = numFilters==3? (div_filter2pos * (rc.right- rc.left)) / 100000 : rc.right;
+
+ if (divX > divX2 && numFilters==3)
+ {
+ div_filterpos=33333;
+ div_filter2pos=66667;
+ divX = (div_filterpos * (rc.right- rc.left)) / 100000;
+ divX2 = (div_filter2pos * (rc.right- rc.left)) / 100000;
+ }
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(LCTRL); index++)
+ {
+ if (controls[index].id >= GROUP_MIN && controls[index].id <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index].id)
+ {
+ case GROUP_SEARCH:
+ if (g_displaysearch)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_CLEAR);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaysearch;
+ break;
+
+ case GROUP_MEDIA:
+ m_nodrawtopborders = 0;
+ if (divY > (rc.bottom - WASABI_API_APP->getScaleY(85)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_HDELIM), &rw);
+ divY = rc.bottom - (rw.bottom - rw.top);
+ }
+ else if (divY < (rc.top + WASABI_API_APP->getScaleY(24)))
+ {
+ divY = rc.top; m_nodrawtopborders = 1;
+ }
+
+ SetRect(&rg, rc.left, divY, rc.right, rc.bottom);
+ rc.bottom = rg.top;
+ break;
+
+ case GROUP_FILTER:
+ if (divX < (rc.left + WASABI_API_APP->getScaleX(15)))
+ {
+ divX = rc.left;
+ adiv1_nodraw = 1;
+ }
+ // fixes moving the dividers over to the far right so that the
+ // 'docking' is consistant in both the two and three pain view
+ else if (divX > (rc.right - (WASABI_API_APP->getScaleX(17)*(numFilters-1)+(numFilters==2?WASABI_API_APP->getScaleX(10):0))))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_DIV1), &rw);
+ divX = rc.right - (rw.right - rw.left)*(numFilters-1);
+ adiv1_nodraw = 2;
+ }
+ else adiv1_nodraw = 0;
+
+ if (divX2 < (rc.left + WASABI_API_APP->getScaleX(15)*(numFilters-2)))
+ {
+ divX2 = rc.left;
+ adiv2_nodraw = 1;
+ }
+ else if (divX2 > (rc.right - WASABI_API_APP->getScaleX(18*2)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_DIV2), &rw);
+ divX2 = rc.right - (rw.right - rw.left);
+ adiv2_nodraw = 2;
+ }
+ else adiv2_nodraw = 0;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index].id;
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ pl->pHidden = &controls[index].hidden;
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_BUTTON_ARTMODE:
+ if(numFilters == 1)
+ {
+ SETLAYOUTPOS(pl, 0, 0, 0, 0);
+ break;
+ }
+ case IDC_BUTTON_VIEWMODE:
+ case IDC_BUTTON_COLUMNS:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, (ri.right - ri.left), (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(1));
+ break;
+ case IDC_SEARCHCAPTION:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(6),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ rg.left += (pl->cx + WASABI_API_APP->getScaleX(8));
+ break;
+ }
+ case IDC_CLEAR:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_QUICKSEARCH:
+ pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_LIST1:
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(1),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ (numFilters == 1 ? rg.right - rg.left - WASABI_API_APP->getScaleX(3) : divX),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ rg.left += pl->cx;
+ break;
+ case IDC_DIV1:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), ri.right - ri.left, (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST2:
+ if (numFilters == 3)
+ {
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), divX2 - rg.left, (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ }
+ else
+ {
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ }
+ rg.left += pl->cx;
+ break;
+ case IDC_DIV2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), ri.right - ri.left, (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST3:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_HDELIM:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(2), (ri.bottom - ri.top));
+ rg.top += pl->cy;
+ break;
+ case IDD_VIEW_MEDIA:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if ((SWP_HIDEWINDOW & pl->flags) && !IsWindowVisible(pl->hwnd)) continue;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (IDD_VIEW_MEDIA == pc->id)
+ {
+ SetRect(&ri, pc->x, pc->y, pc->x + pc->cx, pc->y + pc->cy);
+ ValidateRect(hwnd, &ri);
+ pc->rgn = CreateRectRgn(0, 0, pc->cx, pc->cy);
+ GetWindowRect(pc->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 1);
+ SendMessage(pc->hwnd, WM_USER + 0x201, MAKEWPARAM(offsetX + (pc->x - ri.left), offsetY + (pc->y - ri.top)), (LPARAM)pc->rgn);
+ }
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ switch (pc->id)
+ {
+ case IDD_VIEW_MEDIA:
+ SendMessage(pc->hwnd, WM_USER + 0x201, 0, 0L);
+ break;
+ }
+ }
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE | RDW_NOINTERNALPAINT | RDW_ALLCHILDREN);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ if (pc->rgn) DeleteObject(pc->rgn);
+ }
+
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void WINAPI OnDividerMoved(HWND hwnd, INT nPos, LPARAM param)
+{
+ RECT rc;
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ if (hwndParent)
+ {
+ GetClientRect(hwndParent, &rc);
+ switch ((INT)param)
+ {
+ case DIVIDER_FILTER:
+ div_filterpos = (nPos * 100000) / (rc.right - rc.left);
+ if (div_filterpos + 500 > div_filter2pos) div_filter2pos = div_filterpos+500;
+ break;
+ case DIVIDER_FILTER2:
+ div_filter2pos = (nPos * 100000) / (rc.right - rc.left);
+ if (div_filter2pos - 500 < div_filterpos) div_filterpos = div_filter2pos-500;
+ break;
+ case DIVIDER_MEDIA:
+ div_mediapos = (nPos * 100000) / (rc.bottom - rc.top);
+ break;
+ }
+ LayoutWindows(hwndParent, TRUE);
+ }
+}
+
+static int ignore_selections;
+
+//returns true if a selection has been set which needs to be propagated to the next pane
+static bool UpdateFilterPane(int pane, char* selconfigname)
+{
+ filter[pane]->SortResults(g_view_metaconf, pane, 0);
+
+ ListView_SetItemCount(filter[pane]->list->getwnd(), filter[pane]->Size());
+ InvalidateRect(filter[pane]->list->getwnd(), NULL, TRUE);
+
+ ignore_selections = 1;
+ char *_selstr = g_config->ReadInt(L"remembersearch", 0) ? g_view_metaconf->ReadString(selconfigname, "") : NULL;
+ wchar_t * selstr = AutoWide(_selstr, CP_UTF8);
+ int a = 0;
+ if (selstr && *selstr)
+ {
+ int len = (wcslen(selstr) + 2);
+ wchar_t *t = (wchar_t*)calloc(len, sizeof(wchar_t));
+ wcsncpy(t, selstr, len);
+ g_view_metaconf->WriteString(selconfigname, "");
+
+ t[wcslen(t)+1] = 0;
+ wchar_t *outp = t;
+ wchar_t *inp = t;
+ while (inp && *inp)
+ {
+ if (*inp == *LDELIMSTR)
+ {
+ if (inp[1] == *LDELIMSTR)
+ {
+ *outp++ = *LDELIMSTR; // unescape the output
+ inp += 2;
+ }
+ else
+ {
+ *outp++ = 0; inp++;
+ }
+ }
+ else *outp++ = *inp++;
+ }
+ *outp = 0;
+
+ int x;
+ for (x = 1; x < filter[pane]->Size(); x ++)
+ {
+ wchar_t *p = t;
+ while (p && *p)
+ {
+ if (!lstrcmpiW(filter[pane]->GetText(x), p)) break;
+ p += wcslen(p) + 1;
+ }
+ if (p && *p)
+ {
+ if (!a)
+ {
+ ListView_SetSelectionMark(filter[pane]->list->getwnd(), x);
+ ListView_SetItemState(filter[pane]->list->getwnd(), x, LVIS_FOCUSED, LVIS_FOCUSED);
+ ListView_EnsureVisible(filter[pane]->list->getwnd(), x, NULL);
+ }
+ a++;
+ filter[pane]->list->SetSelected(x);
+ }
+ }
+ free(t);
+ }
+ if (a)
+ {
+ ignore_selections = 0;
+ return true;
+ }
+ else
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, buf, sizeof(buf));
+ if (buf[0])
+ {
+ ignore_selections = 0;
+ l_query.Set(L""); // force refresh
+ }
+ if(filter[pane]->HasTopItem())
+ filter[pane]->list->SetSelected(0);
+ ignore_selections = 0;
+ return false;
+ }
+}
+
+HBITMAP ConvertTo24bpp(HBITMAP bmp, int bpp)
+{
+ HDC hdcMem, hdcMem2;
+ HBITMAP hbm24;
+ BITMAP bm;
+
+ GetObjectW(bmp, sizeof(BITMAP), &bm);
+
+ hdcMem = CreateCompatibleDC(0);
+ hdcMem2 = CreateCompatibleDC(0);
+
+ void *bits;
+ BITMAPINFOHEADER bi;
+
+ ZeroMemory(&bi, sizeof(bi));
+ bi.biSize = sizeof(bi);
+ bi.biWidth= bm.bmWidth;
+ bi.biHeight = -bm.bmHeight;
+ bi.biPlanes = 1;
+ bi.biBitCount= (WORD)bpp;
+
+ hbm24 = CreateDIBSection(hdcMem2, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, NULL);
+
+ HBITMAP oBmp = (HBITMAP)SelectObject(hdcMem, bmp);
+ HBITMAP oBmp24 = (HBITMAP)SelectObject(hdcMem2, hbm24);
+
+ BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
+
+ SelectObject(hdcMem, oBmp);
+ SelectObject(hdcMem2, oBmp24);
+
+ DeleteDC(hdcMem);
+ DeleteDC(hdcMem2);
+
+
+ return hbm24;
+}
+
+static __forceinline BYTE pm(int a, int b)
+{
+ return (BYTE)((a * b) / 0xff);
+}
+
+void SetToolbarButtonBitmap(HWND hwndDlg, int buttonrc, int bitmaprc, ARGB32 fc)
+{
+ // benski> we could use this if it wasn't for the lack of WASABI_API_IMGLDR on classic skin
+ // even though we have the resource loader ...
+ // SkinBitmap *sbm = new SkinBitmap( bitmaprc);
+ HBITMAP hbm1 = (HBITMAP)LoadImage(plugin.hDllInstance,MAKEINTRESOURCE(bitmaprc),IMAGE_BITMAP,0,0,LR_VGACOLOR);
+ HBITMAP hbm = ConvertTo24bpp(hbm1,32);
+ DeleteObject(hbm1);
+ BITMAP bm;
+ GetObjectW(hbm, sizeof(BITMAP), &bm);
+ int l = bm.bmWidth * bm.bmHeight;
+ ARGB32 *x = (ARGB32*)bm.bmBits;
+ ARGB32 *end = x+l;
+ BYTE r = (BYTE)(fc & 0x00ff0000 >> 16);
+ BYTE g = (BYTE)(fc & 0x0000ff00 >> 8);
+ BYTE b = (BYTE)(fc & 0x000000ff);
+ while (x < end)
+ {
+ BYTE a = (BYTE)((~(*x))&0xff);
+ *(x++) = (a<<24) | (pm(r,a)<<16) | (pm(g,a)<<8) | pm(b,a);
+ }
+
+ void * mem = malloc(bm.bmWidth*bm.bmHeight*sizeof(ARGB32));
+ memcpy(mem,bm.bmBits,bm.bmWidth*bm.bmHeight*sizeof(ARGB32));
+
+ SkinBitmap *sbm = new SkinBitmap((ARGB32*)mem,bm.bmWidth,bm.bmHeight);
+ DeleteObject(hbm);
+ HWND btn = GetDlgItem(hwndDlg,buttonrc);
+ if (IsWindow(btn))
+ {
+ SkinBitmap* old = (SkinBitmap*)SetWindowLongPtr(btn,GWLP_USERDATA,(LONG_PTR)sbm);
+ if (old)
+ {
+ if (old->getBits()) free(old->getBits()); delete old;
+ }
+ InvalidateRect(btn, NULL, TRUE);
+ }
+}
+
+int GetFilter(int mode, int n)
+{
+ if (n==0) return ((mode & 0x0000ff));
+ if (n==1)
+ {
+ int f=((mode & 0x00ff00) >> 8); if (f) return f; return 6;
+ } // album
+ if (n==2) return ((mode & 0xff0000) >> 16);
+ return 0;
+}
+
+int GetNumFilters(int mode)
+{
+ if (mode == 0) return 0;
+ if (mode > 0xffffff) return 1;
+ if (GetFilter(mode,2) == 0) return 2;
+ return 3;
+}
+
+typedef struct { int id, id2; } hi;
+
+//extern "C"
+//{
+//void __cdecl do_help(HWND hwnd, UINT id, HWND hTooltipWnd);
+
+void do_help(HWND hwnd, UINT id, HWND hTooltipWnd)
+{
+ RECT r;
+ POINT p;
+ GetWindowRect(GetDlgItem(hwnd, id), &r);
+ GetCursorPos(&p);
+ if (p.x >= r.left && p.x < r.right && p.y >= r.top && p.y < r.bottom)
+ {}
+ else
+ {
+ r.left += r.right;
+ r.left /= 2;
+ r.top += r.bottom;
+ r.top /= 2;
+ SetCursorPos(r.left, r.top);
+ }
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_INITIAL, 0);
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_RESHOW, 0);
+}
+
+#define C_BLAH
+#define DO_HELP() \
+ static HWND hTooltipWnd; \
+ C_BLAH \
+ if (uMsg == WM_HELP) { \
+ HELPINFO *hi=(HELPINFO *)(lParam); \
+ if (hi->iContextType == HELPINFO_WINDOW) { do_help(hwndDlg,hi->iCtrlId,hTooltipWnd);} \
+ return TRUE; \
+ } \
+ if (uMsg == WM_NOTIFY) { LPNMHDR t=(LPNMHDR)lParam; if (t->code == TTN_POP) { SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,1000); SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,1000); } } \
+ if (uMsg == WM_DESTROY && IsWindow(hTooltipWnd)) { DestroyWindow(hTooltipWnd); hTooltipWnd=NULL; } \
+ if (uMsg == WM_INITDIALOG) { \
+ int x; \
+ hTooltipWnd = CreateWindow(TOOLTIPS_CLASS,(LPCWSTR)NULL,TTS_ALWAYSTIP|TTS_NOPREFIX, \
+ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, hwndDlg,NULL,GetModuleHandle(NULL),NULL); \
+ SendMessage(hTooltipWnd,TTM_SETMAXTIPWIDTH,0,587); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,250); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,500); \
+ for (x = 0; x < sizeof(helpinfo)/sizeof(helpinfo[0]); x ++) { \
+ TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_SUBCLASS|TTF_IDISHWND; \
+ ti.uId=(UINT_PTR)GetDlgItem(hwndDlg,helpinfo[x].id); ti.hwnd=hwndDlg; ti.lpszText=WASABI_API_LNGSTRINGW(helpinfo[x].id2); \
+ SendMessage(hTooltipWnd,TTM_ADDTOOL,0,(LPARAM) &ti); \
+ } \
+ }
+//}
+
+static int refresh;
+INT_PTR CALLBACK view_audioDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static int we_are_drag_and_dropping;
+ static HMENU sendto_hmenu;
+ static librarySendToMenuStruct s;
+
+ hi helpinfo[]={
+ {IDC_BUTTON_ARTMODE,IDS_AUDIO_BUTTON_TT1},
+ {IDC_BUTTON_VIEWMODE,IDS_AUDIO_BUTTON_TT2},
+ {IDC_BUTTON_COLUMNS,IDS_AUDIO_BUTTON_TT3},
+ };
+ DO_HELP();
+
+ BOOL a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+ if (numFilters>=1)
+ {
+ a = filter[0]->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (numFilters>=2)
+ {
+ a = filter[1]->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (numFilters>=3)
+ {
+ a = filter[2]->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+
+ switch (uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (wParam)
+ {
+ if((HMENU)wParam == s.build_hMenu && s.mode == 1)
+ {
+ myMenu = TRUE;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ s.mode = 2;
+ myMenu = FALSE;
+ }
+ // only populate the average rating when needed otherwise it makes all of the menu slow
+ else if((HMENU)wParam == GetSubMenu(GetSubMenu(g_context_menus, 1),4))
+ {
+ int ave_rating = 0, valid = 0;
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ for (int i=0; i<numFilters; i++)
+ {
+ valid += makeFilterQuery(&query,filter[i]);
+ }
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t q = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(q, query.Get());
+ NDE_Scanner_First(q);
+ int count = 0;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(q, MAINTABLE_ID_FILENAME);
+ if (!f) break;
+ nde_field_t g = NDE_Scanner_GetFieldByID(q, MAINTABLE_ID_RATING);
+ int nde_rating = NDE_IntegerField_GetValue(g);
+ if(nde_rating > 0) ave_rating += nde_rating;
+ count++;
+ }
+ while (NDE_Scanner_Next(q));
+
+ NDE_Table_DestroyScanner(g_table, q);
+ LeaveCriticalSection(&g_db_cs);
+
+ if(ave_rating > 0)
+ {
+ float ave = ave_rating/(count*1.0f);
+ int diff = ave_rating/count;
+ int adjust = (ave-diff)>=0.5f;
+ ave_rating = (int)(ave+adjust);
+ }
+
+ Menu_SetRatingValue((HMENU)wParam, (valid?ave_rating:-1));
+ }
+ }
+ return 0;
+ case WM_DISPLAYCHANGE:
+ {
+ ARGB32 fc = dialogSkinner.Color(WADLG_BUTTONFG) & 0x00FFFFFF;
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_ARTMODE,IDB_TOOL_ART,fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_VIEWMODE,IDB_TOOL_MODE,fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_COLUMNS,IDB_TOOL_COLS,fc);
+ if (IsWindow(m_media_hwnd)) SendNotifyMessageW(m_media_hwnd, WM_DISPLAYCHANGE, wParam, lParam);
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE);
+ }
+ return 0;
+ case WM_INITDIALOG:
+ {
+ hwndSearchGlobal = GetDlgItem(hwndDlg, IDC_QUICKSEARCH); // Set the hwnd for the search to a global so we can access it from the media view dialog
+ ResumeCache();
+ int numFilters0=GetNumFilters(m_query_mode);
+ int f1 = GetFilter(m_query_mode,0);
+ int f2 = GetFilter(m_query_mode,1);
+ int f3 = GetFilter(m_query_mode,2);
+
+ filter[0] = getFilter(f1-1,hwndDlg,IDC_LIST1,g_view_metaconf);
+ filter[0]->list = &m_list1;
+ filter[0]->nextFilter = NULL;
+ filter[0]->list->setwnd(GetDlgItem(hwndDlg, IDC_LIST1));
+
+ if (numFilters0>=2)
+ {
+ filter[1] = getFilter(f2-1,hwndDlg,IDC_LIST2,g_view_metaconf);
+ filter[1]->list = &m_list2;
+ filter[1]->list->setwnd(GetDlgItem(hwndDlg, IDC_LIST2));
+ filter[1]->nextFilter = NULL;
+ filter[0]->nextFilter = filter[1];
+ }
+
+ if (numFilters0>=3)
+ {
+ filter[2] = getFilter(f3-1,hwndDlg,IDC_LIST3,g_view_metaconf);
+ filter[2]->list = &m_list3;
+ filter[2]->list->setwnd(GetDlgItem(hwndDlg, IDC_LIST3));
+ filter[2]->nextFilter = NULL;
+ filter[1]->nextFilter = filter[2];
+ }
+ numFilters=numFilters0;
+
+ m_hwnd = hwndDlg;
+ getParentPlusSearchQuery(&l_query);
+
+ for (int j=0; j<numFilters0; j++)
+ {
+ filter[j]->list->ForceUnicode();
+ filter[j]->AddColumns();
+ }
+
+ div_mediapos = g_view_metaconf->ReadInt(L"adiv2pos", 50000);
+
+ if (numFilters == 1)
+ {
+ div_filterpos = g_view_metaconf->ReadInt(L"adiv1pos", 100000);
+ div_filter2pos = -1;
+ }
+ else if (numFilters == 2)
+ {
+ div_filterpos = g_view_metaconf->ReadInt(L"adiv1pos", 50000);
+ div_filter2pos = -1;
+ }
+ else if (numFilters == 3)
+ {
+ div_filterpos = g_view_metaconf->ReadInt(L"adiv1pos", 33333);
+ div_filter2pos = g_view_metaconf->ReadInt(L"adiv3pos", 66667);
+ if (div_filterpos + div_filter2pos > 200000) // sanity check?
+ {
+ div_filterpos = 33333;
+ div_filter2pos = 66667;
+ }
+ }
+
+ if (numFilters >= 2) AttachDivider(GetDlgItem(hwndDlg, IDC_DIV1), TRUE, OnDividerMoved, DIVIDER_FILTER);
+ if (numFilters >= 3) AttachDivider(GetDlgItem(hwndDlg, IDC_DIV2), TRUE, OnDividerMoved, DIVIDER_FILTER2);
+ AttachDivider(GetDlgItem(hwndDlg, IDC_HDELIM), FALSE, OnDividerMoved, DIVIDER_MEDIA);
+
+ m_media_hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_MEDIA, hwndDlg, view_mediaDialogProc, (LPARAM)!g_config->ReadInt(L"audiorefine", 0));
+ SetWindowLongPtr(m_media_hwnd, GWLP_ID, IDD_VIEW_MEDIA);
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+
+ m.hwndToSkin = m_list1.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ m.hwndToSkin = m_list2.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ if (numFilters == 3)
+ {
+ m.hwndToSkin = m_list3.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+
+ if (!g_view_metaconf->ReadInt(L"av0_hscroll",0)) MLSkinnedScrollWnd_ShowHorzBar(m_list1.getwnd(), FALSE);
+ if (!g_view_metaconf->ReadInt(L"av1_hscroll",0)) MLSkinnedScrollWnd_ShowHorzBar(m_list2.getwnd(), FALSE);
+ if (!g_view_metaconf->ReadInt(L"av2_hscroll",0) && numFilters == 3) MLSkinnedScrollWnd_ShowHorzBar(m_list3.getwnd(), FALSE);
+
+ FLICKERFIX ff;
+ ff.mode = FFM_ERASEINPAINT;
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ INT ffcl[] = { IDC_QUICKSEARCH, IDC_SEARCHCAPTION, IDC_CLEAR, IDC_BUTTON_ARTMODE, IDC_BUTTON_VIEWMODE, IDC_BUTTON_COLUMNS};
+ for (int i = 0; i < sizeof(ffcl) / sizeof(INT); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(mediaLibrary.library, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+ if (i < 3)
+ {
+ m.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ }
+ }
+
+ SendMessageW(hwndDlg, WM_DISPLAYCHANGE, 0, 0L);
+
+ // clear the media windows refine shit
+ SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ SetDlgItemText(m_media_hwnd, IDC_SEARCHCAPTION, WASABI_API_LNGSTRINGW(IDS_REFINE));
+ SetDlgItemText(m_media_hwnd, IDC_CLEAR, WASABI_API_LNGSTRINGW(IDS_CLEAR_REFINE));
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ if (g_config->ReadInt(L"remembersearch", 0))
+ {
+ char *query = g_view_metaconf->ReadString("lastquery_a_utf8", "");
+ SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, AutoWide(query, CP_UTF8));
+ KillTimer(hwndDlg, 205);
+ }
+ for (int j=0; j<numFilters; j++)
+ filter[j]->SortResults(g_view_metaconf, j, 0);
+
+ {
+ wchar_t buf[32] = {0};
+ for (int i = 0; i < numFilters; i++)
+ {
+ int dlgitem = (i==0)?IDC_LIST1:((i==1)?IDC_LIST2:IDC_LIST3);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d", filter[i]->GetConfigId(), i);
+ int l_sc = g_view_metaconf->ReadInt(buf, 0);
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_dir_%d", filter[i]->GetConfigId(), i);
+ int l_sd = g_view_metaconf->ReadInt(buf, 0);
+ SendMessage((HWND)SendMessage(GetDlgItem(hwndDlg,dlgitem), LVM_GETHEADER, 0, 0L),WM_ML_IPC,MAKEWPARAM(l_sc,!l_sd),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ }
+ }
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ return TRUE;
+ }
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ if (di->CtlID == IDC_BUTTON_ARTMODE || di->CtlID == IDC_BUTTON_VIEWMODE || di->CtlID == IDC_BUTTON_COLUMNS)
+ {
+ // draw the toolbar buttons!
+ SkinBitmap* hbm = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,di->CtlID),GWLP_USERDATA);
+ if (hbm && di->rcItem.left != di->rcItem.right && di->rcItem.top != di->rcItem.bottom)
+ {
+ DCCanvas dc(di->hDC);
+ if (di->itemState & ODS_SELECTED) hbm->blitAlpha(&dc,di->rcItem.left+6,di->rcItem.top+5);
+ else hbm->blitAlpha(&dc,di->rcItem.left+4,di->rcItem.top+3);
+ }
+ return TRUE;
+ }
+ }
+ }
+ break;
+ case WM_TIMER:
+ if (wParam == 165) // list3 sel change
+ {
+ KillTimer(hwndDlg, 165);
+ if (numFilters < 3) break;
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ int r = makeFilterQuery(&query,filter[0]);
+ r += makeFilterQuery(&query,filter[1]);
+ r += makeFilterQuery(&query,filter[2]);
+
+ if (wcscmp(query.Get(), l_query.Get()))
+ {
+ bgQuery_Stop();
+ l_query.Set(query.Get());
+
+ wchar_t buf[2048] = {0};
+ GetDlgItemTextW(m_media_hwnd, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ if (buf[0]) makeQueryStringFromText(&query, buf);
+
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner) NDE_Scanner_Query(m_media_scanner, query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ if (r > 0) SendMessage(m_media_hwnd, WM_APP + 3, 0x666, 0); // notify the child to use the first item as a media lookup
+ SendMessage(m_media_hwnd, WM_APP + 1, 0, 0);
+ }
+ }
+ if (wParam == 166) // album sel change
+ {
+ KillTimer(hwndDlg, 166);
+
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ int r = makeFilterQuery(&query,filter[0]);
+ r += makeFilterQuery(&query,filter[1]);
+
+ if (wcscmp(query.Get(), l_query.Get()))
+ {
+ bgQuery_Stop();
+ l_query.Set(query.Get());
+
+ wchar_t buf[2048] = {0};
+ GetDlgItemTextW(m_media_hwnd, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ if (buf[0]) makeQueryStringFromText(&query, buf);
+
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ EnterCriticalSection(&g_db_cs);
+ if (m_media_scanner) NDE_Scanner_Query(m_media_scanner, query.Get());
+ LeaveCriticalSection(&g_db_cs);
+ if (r > 0) SendMessage(m_media_hwnd, WM_APP + 3, 0x666, 0); // notify the child to use the first item as a media lookup
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, 2);
+ }
+ }
+ if (wParam == 167) // artist sel change
+ {
+ KillTimer(hwndDlg, 167);
+
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+
+ int r = makeFilterQuery(&query,filter[0]);
+
+ if (wcscmp(query.Get(), l_query.Get()))
+ {
+ bgQuery_Stop();
+ l_query.Set(query.Get());
+
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ ListView_SetItemCount(m_list2.getwnd(), 0);
+ if (r > 0) SendMessage(m_media_hwnd, WM_APP + 3, 0x666, 0); // notify the child to use the first item as a media lookup
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)1); // pass a callback for sort
+ }
+ }
+ if (wParam == 205)
+ {
+ KillTimer(hwndDlg, 205);
+ bgQuery_Stop();
+ getParentPlusSearchQuery(&l_query);
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, l_query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ ignore_selections = 1;
+ m_list3.SetSelected(0);
+ m_list2.SetSelected(0);
+ m_list1.SetSelected(0);
+ ListView_SetItemCount(m_list1.getwnd(), 0);
+ ListView_SetItemCount(m_list2.getwnd(), 0);
+ ListView_SetItemCount(m_list3.getwnd(), 0);
+ ignore_selections = 0;
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)0);
+ refresh -= 1;
+ }
+ break;
+ case WM_MOUSEMOVE:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ POINT p;
+ p.x = GET_X_LPARAM(lParam);
+ p.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m;
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ m.p = p;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result <= 0)
+ {
+ ZeroMemory(&m, sizeof(mlDropItemStruct));
+ m.type = ML_TYPE_QUERYSTRING;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ }
+
+ break;
+ }
+ break;
+
+ case WM_SETCURSOR:
+ case WM_LBUTTONDOWN:
+ {
+ static INT id[] = { IDC_HDELIM, IDC_DIV1, IDC_DIV2 };
+ RECT rw;
+ POINT pt;
+ INT count;
+
+ count = numFilters;
+ if (count > sizeof(id)/sizeof(INT)) count = sizeof(id)/sizeof(INT);
+
+ GetCursorPos(&pt);
+ for (INT i = 0; i < count; i++)
+ {
+ HWND hwndDiv = GetDlgItem(hwndDlg, id[i]);
+ if (!hwndDiv) continue;
+
+ GetWindowRect(hwndDiv, &rw);
+ if (PtInRect(&rw, pt))
+ {
+ if (WM_SETCURSOR == uMsg)
+ {
+ SetCursor(LoadCursor(NULL, (IDC_HDELIM != id[i]) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+ }
+ else
+ {
+ SendMessage(hwndDiv, uMsg, wParam, MAKELPARAM(pt.x - rw.left, pt.y - rw.top));
+ return TRUE;
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (we_are_drag_and_dropping && GetCapture() == hwndDlg)
+ {
+ int whichlist = we_are_drag_and_dropping;
+ we_are_drag_and_dropping = 0;
+ ReleaseCapture();
+
+ POINT p;
+ p.x = GET_X_LPARAM(lParam);
+ p.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg, &p);
+ mlDropItemStruct m = {0};
+ m.type = ML_TYPE_ITEMRECORDLISTW;
+ m.p = p;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ itemRecordListW obj = {0, };
+ buildRecordListW(&obj, whichlist-1);
+
+ if (m.result > 0)
+ {
+ /*
+ if (whichlist == 1 || m_list2.GetSelectionMark() <= 0)
+ buildRecordListW(&obj, 0);
+ else if(whichlist == 2 || m_list3.GetSelectionMark() <= 0 || numFilters < 3)
+ buildRecordListW(&obj,1);
+ else
+ buildRecordListW(&obj,2);
+ */
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & obj;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ }
+ else
+ {
+ m.result = 0;
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result > 0)
+ {
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &obj);
+
+ /*
+ if (whichlist == 1 || m_list2.GetSelectionMark() <= 0)
+ buildRecordList(&obj, 0);
+ else if(whichlist == 2 || m_list3.GetSelectionMark() <= 0 || numFilters < 3)
+ buildRecordList(&obj, 1);
+ else
+ buildRecordList(&obj, 2);
+ */
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & objA;
+ GayString namebuf;
+ int wl = whichlist;
+ ViewFilter * filter1 = filter[wl-1];
+ {
+ int a = filter1->list->GetSelectionMark();
+ if (a < 0 || (filter1->HasTopItem() && filter1->list->GetSelected(0)))
+ {
+ filter1 = filter[0]; wl--;
+ }
+
+ if (wl > 0)
+ {
+ int x;
+ int cnt = 0;
+ for (x = filter1->HasTopItem()?1:0; x < filter1->Size(); x ++)
+ {
+ if (filter1->list->GetSelected(x))
+ {
+ if (cnt) namebuf.Append(", ");
+ if (cnt++ > 2)
+ {
+ namebuf.Append("..."); break;
+ }
+ namebuf.Append(AutoChar(filter1->GetText(x)));
+ }
+ }
+ }
+ }
+ if (namebuf.Get() && namebuf.Get()[0]) m.name = namebuf.Get();
+ if (m.name)
+ m.flags |= ML_HANDLEDRAG_FLAG_NAME;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ freeRecordList(&objA);
+ }
+
+ else
+ {
+ m.result = 0;
+ m.type = ML_TYPE_QUERYSTRING;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result > 0)
+ {
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ bool allSelected[3] = {false,false,false};
+ for (int k=0; k<numFilters; k++)
+ {
+ W_ListView &w = k==0?m_list1:(k==1?m_list2:m_list3);
+ if (filter[k]->HasTopItem()) allSelected[k] = (w.GetSelectionMark() <= 0);
+ else allSelected[k] = (w.GetSelectionMark() < 0);
+ }
+
+ if (whichlist == 1 || allSelected[1])
+ {
+ makeFilterQuery(&query,filter[0]);
+ }
+ else if (whichlist == 2 || allSelected[2] || numFilters < 3)
+ {
+ makeFilterQuery(&query,filter[0]);
+ makeFilterQuery(&query,filter[1]);
+ }
+ else
+ {
+ makeFilterQuery(&query,filter[0]);
+ makeFilterQuery(&query,filter[1]);
+ makeFilterQuery(&query,filter[2]);
+ }
+
+ m.data = (void*)query.Get();
+ m.result = 0;
+ GayString namebuf;
+ int wl = whichlist;
+ ViewFilter * filter1 = wl==2?filter[1]:filter[0];
+ {
+ int a = filter1->list->GetSelectionMark();
+ if (a < 0 || (filter1->HasTopItem() && filter1->list->GetSelected(0)))
+ {
+ if (wl==2)
+ {
+ filter1 = filter[0];
+ }
+ else m.name = WASABI_API_LNGSTRING(IDS_ALL_ARTISTS);
+ wl--;
+ }
+ if (wl > 0)
+ {
+ int x;
+ int cnt = 0;
+ for (x = filter1->HasTopItem()?1:0; x < filter1->Size(); x ++)
+ {
+ if (filter1->list->GetSelected(x))
+ {
+ if (cnt) namebuf.Append(", ");
+ if (cnt++ > 2)
+ {
+ namebuf.Append("..."); break;
+ }
+ namebuf.Append(AutoChar(filter1->GetText(x)));
+ }
+ }
+ }
+ }
+ if (namebuf.Get() && namebuf.Get()[0])
+ {
+ m.name = namebuf.Get();
+ m.flags |= ML_HANDLEDRAG_FLAG_NAME;
+ }
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ }
+ }
+ }
+ freeRecordList(&obj);
+ }
+ break;
+ case WM_CONTEXTMENU:
+ {
+ HWND hwndFrom = (HWND)wParam;
+ int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
+ POINT pt = {x,y};
+ int idFrom = GetDlgCtrlID(hwndFrom);
+ HWND hHeader;
+ W_ListView m_list;
+ int editFilter=-1;
+
+ if (idFrom == IDC_LIST1)
+ {
+ m_list = m_list1;
+ hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ editFilter = 0;
+ }
+ }
+ }
+ else if (idFrom == IDC_LIST2)
+ {
+ m_list = m_list2;
+ hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ editFilter = 1;
+ }
+ }
+ }
+ else if (idFrom == IDC_LIST3)
+ {
+ m_list = m_list3;
+ hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ editFilter = 2;
+ }
+ }
+ }
+ else
+ break;
+
+ if (editFilter != -1)
+ {
+ HMENU themenu = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU menu = filter[editFilter]->GetMenu(true,editFilter,g_view_metaconf,themenu);
+ POINT p;
+ GetCursorPos(&p);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, hwndDlg, NULL);
+ DestroyMenu(themenu);
+ filter[editFilter]->ProcessMenuResult(r,true,editFilter,g_view_metaconf,hwndDlg);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+ break;
+ }
+
+ int pane=0;
+ if (numFilters > 1)
+ {
+ if (idFrom == IDC_LIST2 && m_list2.GetSelectionMark() >= 0) pane=1;
+ }
+ if (numFilters > 2)
+ {
+ if (idFrom == IDC_LIST3 && m_list3.GetSelectionMark() >= 0) pane=2;
+ }
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = m_list.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ m_list.GetItemRect(selected, &itemRect);
+ ClientToScreen(m_list.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(m_list.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(m_list.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ break;
+ }
+
+ HMENU menu = GetSubMenu(g_context_menus, 1);
+ sendto_hmenu = GetSubMenu(menu, 2);
+
+ s.mode = 0;
+ s.hwnd = 0;
+ s.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ s.ctx[1] = 1;
+ s.build_hMenu = sendto_hmenu;
+ }
+
+ wchar_t str[128] = {0};
+ StringCchPrintfW(str, 128, (!play_enq_rnd_alt?WASABI_API_LNGSTRINGW(IDS_PLAY_RANDOM_ITEM):L"%s"),
+ filter[pane]->GetNameSingularAlt(play_enq_rnd_alt?1:0));
+ FixAmps(str, 128);
+ MENUITEMINFOW mii =
+ {
+ sizeof(MENUITEMINFOW),
+ MIIM_TYPE | MIIM_ID,
+ MFT_STRING,
+ MFS_ENABLED,
+ ID_AUDIOWND_PLAYRANDOMITEM,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ str,
+ 0,
+ };
+ SetMenuItemInfoW(menu, ID_AUDIOWND_PLAYRANDOMITEM, false, &mii);
+
+ StringCchPrintfW(str, 128, (!play_enq_rnd_alt?WASABI_API_LNGSTRINGW(IDS_ENQUEUE_RANDOM_ITEM):L"%s"),
+ filter[pane]->GetNameSingularAlt(play_enq_rnd_alt?2:0));
+ FixAmps(str, 128);
+ mii.wID = ID_AUDIOWND_ENQUEUERANDOMITEM;
+ SetMenuItemInfoW(menu, ID_AUDIOWND_ENQUEUERANDOMITEM, false, &mii);
+
+ filter[pane]->DialogProc(hwndDlg,WM_USER+600,(WPARAM)menu,0x7000);
+ UpdateMenuItems(NULL, menu, IDR_VIEW_ACCELERATORS);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL);
+
+ filter[pane]->DialogProc(hwndDlg,WM_USER+601,(WPARAM)menu,0x7000);
+
+ if(r >= 0x7000 && r <= 0x8000) {
+ GayStringW query;
+ getParentPlusSearchQuery(&query);
+ for (int i=0; i<=pane; i++)
+ makeFilterQuery(&query,filter[i]);
+ filter[pane]->DialogProc(hwndDlg,WM_USER+602,(WPARAM)query.Get(),r - 0x7000);
+ }
+
+ switch (r)
+ {
+ case ID_AUDIOWND_PLAYSELECTION:
+ playList(PLAY,pane);
+ break;
+ case ID_AUDIOWND_ENQUEUESELECTION:
+ playList(ENQUEUE,pane);
+ break;
+ case ID_AUDIOWND_PLAYRANDOMITEM:
+ case ID_AUDIOWND_ENQUEUERANDOMITEM:
+ playRandomList(r == ID_AUDIOWND_PLAYRANDOMITEM ? PLAY : ENQUEUE, pane);
+ break;
+ case ID_RATE_1:
+ case ID_RATE_2:
+ case ID_RATE_3:
+ case ID_RATE_4:
+ case ID_RATE_5:
+ case ID_RATE_0:
+ {
+ int rate = r - ID_RATE_1 + 1;
+ if (r == ID_RATE_0) rate = 0;
+ if(rateList(rate))
+ {
+ wchar_t buf[10] = {0};
+ wsprintfW(buf,L"%d",rate);
+ int s = -1;
+ while((s = filter[pane]->list->GetNextSelected(s)) != -1)
+ filter[pane]->MetaUpdate(s, DB_FIELDNAME_rating,buf);
+ }
+ PostMessage(hwndDlg, WM_APP + 4, (WPARAM)rate, (LPARAM)0);
+ }
+ break;
+ default:
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ // build my data.
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLISTW;
+ itemRecordListW myObj = {0, };
+ buildRecordListW(&myObj, pane);
+
+ s.data = (void*) & myObj;
+ LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+
+ if (result != 1)
+ {
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &myObj);
+
+ s.data = (void*) & objA;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+ freeRecordList(&objA);
+ }
+ freeRecordList(&myObj);
+ }
+ }
+ break;
+ }
+ if (s.mode)
+ {
+ s.mode = 4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ sendto_hmenu = 0;
+ EatKeyboard();
+ return 0;
+ }
+ case WM_NOTIFY:
+ {
+ LPNMHDR l = (LPNMHDR)lParam;
+
+ if (l->code == LVN_ODFINDITEMW)
+ {
+ ViewFilter * f;
+ if (l->idFrom == IDC_LIST2) f = filter[1];
+ else if (l->idFrom == IDC_LIST3) f = filter[2];
+ else f = filter[0];
+
+ NMLVFINDITEMW *t = (NMLVFINDITEMW *)lParam;
+ int i = t->iStart;
+ if (i >= f->Size()) i = 0;
+
+ int cnt = f->Size() - i;
+ if (t->lvfi.flags & LVFI_WRAP) cnt += i;
+
+ while (cnt-- > 0)
+ {
+ const wchar_t *name = f->GetText(i);
+ SKIP_THE_AND_WHITESPACEW(name)
+
+ if (t->lvfi.flags & (4 | LVFI_PARTIAL))
+ {
+ if (!StrCmpNIW(name, t->lvfi.psz, wcslen(t->lvfi.psz)))
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, i);
+ return 1;
+ }
+ }
+ else if (t->lvfi.flags & LVFI_STRING)
+ {
+ if (!lstrcmpiW(name, t->lvfi.psz))
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, i);
+ return 1;
+ }
+ }
+ else
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, -1);
+ return 1;
+ }
+ if (++i == f->Size()) i = 0;
+ }
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, -1);
+ return 1;
+ }
+ else if (l->code == LVN_GETDISPINFOW)
+ {
+ NMLVDISPINFOW *lpdi = (NMLVDISPINFOW*) lParam;
+ int item = lpdi->item.iItem;
+ if (item < 0) return 0;
+
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ if (l->idFrom == IDC_LIST3 && numFilters == 3)
+ lpdi->item.pszText = (wchar_t*)filter[2]->CopyText2(item,lpdi->item.iSubItem,lpdi->item.pszText,lpdi->item.cchTextMax);
+
+ if (l->idFrom == IDC_LIST2)
+ lpdi->item.pszText = (wchar_t*)filter[1]->CopyText2(item,lpdi->item.iSubItem,lpdi->item.pszText,lpdi->item.cchTextMax);
+
+ else if (l->idFrom == IDC_LIST1)
+ lpdi->item.pszText = (wchar_t*)filter[0]->CopyText2(item, lpdi->item.iSubItem, lpdi->item.pszText, lpdi->item.cchTextMax);
+ }
+ return 0;
+ }
+ else if (l->code == LVN_COLUMNCLICK)
+ {
+ NMLISTVIEW *p = (NMLISTVIEW*)lParam;
+ int which = l->idFrom == IDC_LIST3 ? 2 : (l->idFrom == IDC_LIST2 ? 1 : 0);
+ wchar_t buf[32] = {0}, buf2[32] = {0};
+
+ StringCchPrintfW(buf, ARRAYSIZE(buf), L"%hs_sort_by_%d",filter[which]->GetConfigId(), which);
+ int l_sc = g_view_metaconf->ReadInt(buf, 0);
+
+ StringCchPrintfW(buf2, ARRAYSIZE(buf2), L"%hs_sort_dir_%d",filter[which]->GetConfigId(), which);
+ int l_sd = g_view_metaconf->ReadInt(buf2, 0);
+ if (p->iSubItem == l_sc) l_sd = !l_sd;
+ else l_sc = p->iSubItem;
+
+ g_view_metaconf->WriteInt(buf, l_sc);
+ g_view_metaconf->WriteInt(buf2, l_sd);
+
+ int s;
+ s = filter[which]->Size();
+ int dlgitem = (which==0)?IDC_LIST1:((which==1)?IDC_LIST2:IDC_LIST3);
+ SendMessage((HWND)SendMessage(GetDlgItem(hwndDlg,dlgitem), LVM_GETHEADER, 0, 0L),WM_ML_IPC,MAKEWPARAM(l_sc,!l_sd/* : !l_sd/*!l_sd : l_sd*/),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ filter[which]->SortResults(g_view_metaconf, which);
+ ListView_SetItemCount(l->hwndFrom, s);
+ InvalidateRect(l->hwndFrom, NULL, TRUE);
+ }
+ else if (l->idFrom == IDC_LIST1 || l->idFrom == IDC_LIST2 || l->idFrom == IDC_LIST3)
+ {
+ int pane = 0;
+ if (l->idFrom == IDC_LIST2) pane = 1;
+ if (l->idFrom == IDC_LIST3) pane = 2;
+
+ if (l->code == NM_DBLCLK)
+ {
+ // allow doubleclick on all, yes
+ int c = filter[pane]->list->GetCount(), x=0;
+ for (x = 0; x < c; x ++) if (filter[pane]->list->GetSelected(x)) break;
+
+ if (!x && pane>0 && filter[pane]->HasTopItem())
+ playList((!!g_config->ReadInt(L"enqueuedef", PLAY)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)),pane-1);
+ else if (x < c)
+ playList((!!g_config->ReadInt(L"enqueuedef", PLAY)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)),pane);
+ }
+ else if (l->code == LVN_ITEMCHANGED)
+ {
+ LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam;
+ if (ignore_selections || !((lv->uNewState ^ lv->uOldState) & LVIS_SELECTED)) return 0;
+ int t;
+ if (pane==0) t=167;
+ else if (pane==1) t=166;
+ else t=165;
+
+ KillTimer(hwndDlg, t);
+ SetTimer(hwndDlg, t, 100, NULL);
+ }
+ else if (l->code == LVN_BEGINDRAG)
+ {
+ if (pane==0) we_are_drag_and_dropping = 1;
+ else if (pane==1) we_are_drag_and_dropping = 2;
+ else we_are_drag_and_dropping = 3;
+ SetCapture(hwndDlg);
+ }
+ }
+ if (l->code == NM_RETURN)
+ {
+ extern void playFiles(int enqueue, int all);
+ playFiles(((!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000))), 1);
+ }
+ }
+ break;
+ case WM_USER+710:
+ if(rateList(wParam))
+ {
+ ViewFilter *f = (ViewFilter *)lParam;
+
+ // first check the validity of lParam
+ bool found=false;
+ for(int i=0; i<numFilters; i++)
+ if(filter[i] == f) found=true;
+ if(!found) break;
+
+ wchar_t buf[10] = {0};
+ wsprintfW(buf,L"%d",wParam);
+ int s = -1;
+ while((s = f->list->GetNextSelected(s)) != -1)
+ f->MetaUpdate(s, DB_FIELDNAME_rating,buf);
+ }
+ break;
+ case WM_DROPFILES:
+ {
+ extern void Window_OnDropFiles(HWND hwndDlg, HDROP hdrop);
+ Window_OnDropFiles(hwndDlg, (HDROP)wParam);
+ break;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_CLEAR:
+ SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, L"");
+ break;
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ KillTimer(hwndDlg, 205);
+ SetTimer(hwndDlg, 205, g_querydelay, NULL);
+ }
+ break;
+ case IDC_BUTTON_ARTMODE:
+ {
+ int changed=0;
+ int f[3]={0,0,0};
+ for (int i=0; i<numFilters; i++)
+ {
+ f[i] = GetFilter(m_query_mode,i);
+ if (f[i] == 6)
+ {
+ f[i] = 11; changed=true;
+ }
+ else if (f[i] == 11)
+ {
+ f[i] = 6; changed=true;
+ }
+ }
+
+ if (changed)
+ {
+ if (f[1] == 6) f[1]=0;
+ if (numFilters == 2) f[2]=0;
+ int m = f[0] | (f[1] << 8) | (f[2] << 16);
+
+ int par = mediaLibrary.GetSelectedTreeItem();
+ QueryList::iterator iter;
+ iter = m_query_list.find(par);
+ if (iter != m_query_list.end())
+ iter->second->mode = m;
+ saveQueryTree();
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ case IDC_BUTTON_VIEWMODE:
+ {
+ int presets[] =
+ {
+ MAKEVIEW_2FILTER(ARTIST,ALBUM),
+ MAKEVIEW_2FILTER(ARTIST,ALBUMART),
+ MAKEVIEW_2FILTER(ALBUMARTIST,ALBUM),
+ MAKEVIEW_2FILTER(ALBUMARTIST,ALBUMART),
+ MAKEVIEW_3FILTER(GENRE,ARTIST,ALBUM),
+ MAKEVIEW_2FILTER(GENRE,ALBUMART),
+ MAKEVIEW_3FILTER(YEAR,ARTIST,ALBUM),
+ MAKEVIEW_2FILTER(COMPOSER,ALBUM),
+ MAKEVIEW_3FILTER(PUBLISHER,ARTIST,ALBUM),
+ };
+ HMENU menu = CreatePopupMenu();
+ bool hasCheck=false;
+ for (int i=0; i < sizeof(presets)/sizeof(presets[0]); i++)
+ {
+ wchar_t buf[350] = {0}, filterName[128] = {0};
+ buf[0] = L'\0';
+ int l = GetNumFilters(presets[i]);
+ bool check = (l == GetNumFilters(m_query_mode));
+ for (int j=0; j<l; j++)
+ {
+ int f = GetFilter(presets[i],j)-1;
+ wcscat(buf,getFilterName(f, filterName, ARRAYSIZE(filterName)));
+ if (GetFilter(presets[i],j+1)) wcscat(buf,L"\\");
+ if (GetFilter(presets[i],j) != GetFilter(m_query_mode,j)) check=false;
+ }
+ AppendMenuW(menu,MF_STRING,i+1,buf);
+ if(check)
+ {
+ CheckMenuItem(menu,i+1,MF_CHECKED);
+ hasCheck=true;
+ }
+ }
+ AppendMenuW(menu,MF_STRING,0x4000,WASABI_API_LNGSTRINGW(IDS_OTHER2));
+ if(!hasCheck)
+ CheckMenuItem(menu,0x4000,MF_CHECKED);
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),&rc);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY,
+ rc.left, rc.bottom, hwndDlg, NULL);
+ DestroyMenu(menu);
+ if (r==0) break;
+ else if (r == 0x4000)
+ queryEditItem(mediaLibrary.GetSelectedTreeItem());
+ else
+ {
+ int par = mediaLibrary.GetSelectedTreeItem();
+ QueryList::iterator iter;
+ iter = m_query_list.find(par);
+ if (iter != m_query_list.end())
+ iter->second->mode = presets[r-1];;
+ saveQueryTree();
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ case IDC_BUTTON_COLUMNS:
+ {
+ HMENU themenu[3] = {0};
+ wchar_t *name[3] = {0}, filterName[128] = {0};
+ HMENU menu = CreatePopupMenu();
+
+ MENUITEMINFOW m={sizeof(m),MIIM_TYPE | MIIM_ID | MIIM_SUBMENU,MFT_STRING,0};
+ for (int i=0; i<numFilters; i++)
+ {
+ m.wID = i;
+ m.dwTypeData = name[i] = _wcsdup(getFilterName(GetFilter(m_query_mode,i)-1, filterName, ARRAYSIZE(filterName)));
+ themenu[i] = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ m.hSubMenu = filter[i]->GetMenu(true,i,g_view_metaconf,themenu[i]);
+ InsertMenuItemW(menu,i,FALSE,&m);
+ }
+
+ m.wID = 3;
+ m.dwTypeData = WASABI_API_LNGSTRINGW(IDS_TRACKS_MENU);
+ m.hSubMenu = GetSubMenu(themenu[0], 4);
+ InsertMenuItemW(menu,3,FALSE,&m);
+
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),&rc);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, rc.left,rc.bottom, hwndDlg, NULL);
+
+ DestroyMenu(menu);
+
+ if (r == ID_HEADERWND_CUSTOMIZECOLUMNS)
+ {
+ void customizeColumnsDialog(HWND hwndParent);
+ customizeColumnsDialog(hwndDlg);
+ }
+
+ for (int i=0; i<numFilters; i++)
+ {
+ filter[i]->ProcessMenuResult(r,true,i,g_view_metaconf,hwndDlg);
+ DestroyMenu(themenu[i]);
+ free(name[i]);
+ }
+ }
+ break;
+ }
+ break;
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return TRUE;
+ case WM_APP + 1: //sent by parent for resizing window
+ bgQuery_Stop();
+ getParentPlusSearchQuery(&l_query);
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, l_query.Get());
+ LeaveCriticalSection(&g_db_cs);
+
+ ignore_selections = 1;
+ for (int i=0; i<numFilters; i++)
+ {
+ filter[i]->list->SetSelected(0);
+ ListView_SetItemCount(filter[i]->list->getwnd(), 0);
+ }
+ ignore_selections = 0;
+ if (!refresh) SetDlgItemText(m_media_hwnd, IDC_QUICKSEARCH, L"");
+ KillTimer(m_media_hwnd, UPDATE_QUERY_TIMER_ID); // keep it from researching :)
+ SendMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)0);
+ refresh -= 1;
+ break;
+ case WM_QUERYFILEINFO: if (m_media_hwnd) PostMessageW(m_media_hwnd, uMsg, wParam, lParam); break;
+ case WM_SHOWFILEINFO:
+ SendMessageW(GetParent(hwndDlg), uMsg, wParam, lParam); break;
+ case WM_USER + 66:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, SendMessage(GetParent(hwndDlg), uMsg, wParam, lParam));
+ return TRUE;
+ case WM_APP + 2: //sent by media child to get current query
+ if (lParam) *((const wchar_t **)(lParam)) = l_query.Get();
+ break;
+ case WM_APP + 3: // sent by bg thread to update our shit
+ if (wParam == 0x69)
+ {
+ if (lParam == 0 && numFilters >= 1)
+ {
+ if (UpdateFilterPane(0,"artist_sel_utf8"))
+ {
+ PostMessage(hwndDlg,WM_TIMER,167,0);
+ break;
+ }
+ }
+
+ if (lParam <= 1 && numFilters >= 2)
+ {
+ if (UpdateFilterPane(1,"album_sel_utf8"))
+ {
+ PostMessage(hwndDlg,WM_TIMER,166,0);
+ break;
+ }
+ }
+
+ if (numFilters >= 3)
+ {
+ if (UpdateFilterPane(2,"list3_sel_utf8"))
+ {
+ PostMessage(hwndDlg,WM_TIMER,165,0);
+ }
+ }
+ }
+ break;
+ // used for working around some quirks with the ratings so it'll update on-the-fly(ish)
+ case WM_APP + 4:
+ {
+ if(!lParam)
+ {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf,64,L"%d",wParam);
+ for(int i=0; i<numFilters; i++)
+ {
+ int s = -1;
+ while((s = filter[i]->list->GetNextSelected(s)) != -1)
+ {
+ filter[i]->MetaUpdate(s, DB_FIELDNAME_rating, buf);
+ SendMessage(filter[i]->list->getwnd(),LVM_REDRAWITEMS,s,s);
+ }
+ }
+ }
+ else
+ {
+ PostMessage(m_media_hwnd, WM_APP + 1, (WPARAM)mysearchCallbackAlbumUpdate, (LPARAM)1);
+ }
+ }
+ break;
+ case WM_APP + 5:
+ // TODO
+ break;
+ case WM_APP + 6: // handles the ml_cloud 'first pull' announces so we
+ { // can then show the cloud column & update the cache
+ SendMessage(m_media_hwnd, WM_APP + 6, wParam, lParam);
+ break;
+ }
+ case WM_DESTROY:
+ FlushCache();
+
+ if (numFilters >= 2) g_view_metaconf->WriteInt(L"adiv1pos", div_filterpos);
+ if (numFilters >= 3) g_view_metaconf->WriteInt(L"adiv3pos", div_filter2pos);
+
+ g_view_metaconf->WriteInt(L"adiv2pos", div_mediapos);
+ // save a list of selected artists and albums
+ for (int i=0; i<numFilters; i++)
+ {
+ GayStringW gs;
+ if (!(filter[i]->list->GetSelected(0) && filter[i]->HasTopItem()))
+ {
+ int c = filter[i]->list->GetCount(), delim = 0;
+ for (int x = filter[i]->HasTopItem()?1:0; x < c; x ++)
+ {
+ if (filter[i]->list->GetSelected(x))
+ {
+ if (delim) gs.Append(LDELIMSTR);
+ const wchar_t *p = filter[i]->GetText(x);
+ while (p && *p)
+ {
+ if (*p == *LDELIMSTR) gs.Append(LDELIMSTR LDELIMSTR);
+ else
+ {
+ wchar_t buf[2] = {*p, 0};
+ gs.Append(buf);
+ }
+ p++;
+ }
+ delim = 1;
+ }
+ }
+ }
+ char *confname = "artist_sel_utf8";
+ if (i==1) confname = "album_sel_utf8";
+ if (i==2) confname = "list3_sel_utf8";
+ g_view_metaconf->WriteString(confname, AutoChar(gs.Get(), CP_UTF8));
+ }
+ {
+ wchar_t buf[2048] = {0};
+ GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ g_view_metaconf->WriteString("lastquery_a_utf8", AutoChar(buf, CP_UTF8));
+ }
+
+ bgQuery_Stop();
+
+ for (int i=0; i<numFilters; i++)
+ {
+ filter[i]->SaveColumnWidths();
+ filter[i]->Empty();
+ filter[i]->ClearColumns();
+ delete filter[i];
+ }
+ numFilters=0;
+ {
+ SkinBitmap *s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),GWLP_USERDATA);
+ if (s)
+ {
+ void *bits = s->getBits();
+ if (bits) free(bits); delete s;
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),GWLP_USERDATA, 0);
+ }
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),GWLP_USERDATA);
+ if (s)
+ {
+ void *bits = s->getBits();
+ if (bits) free(bits); delete s;
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),GWLP_USERDATA, 0);
+ }
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),GWLP_USERDATA);
+ if (s)
+ {
+ void *bits = s->getBits();
+ if (bits) free(bits); delete s;
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),GWLP_USERDATA, 0);
+ }
+ }
+ break;
+ case WM_PAINT:
+ {
+ int tab[] =
+ {
+ IDC_HDELIM | DCW_DIVIDER, // 0
+ IDC_QUICKSEARCH | DCW_SUNKENBORDER, // 1
+ IDC_DIV1 | DCW_DIVIDER, // 2
+ IDC_LIST1 | DCW_SUNKENBORDER, //3
+ IDC_LIST2 | DCW_SUNKENBORDER, //4
+ IDC_LIST3 | DCW_SUNKENBORDER, //5
+ IDC_DIV2 | DCW_DIVIDER, //6
+ };
+ int size = 5;
+ if (numFilters == 3) size = 7;
+
+ if (m_nodrawtopborders) size = 2;
+ else
+ {
+ if (numFilters == 3)
+ {
+ if (adiv1_nodraw == 2) tab[4]=0;
+ if (adiv1_nodraw == 1) tab[3]=0;
+ if (adiv2_nodraw == 2) tab[5]=0;
+ if (adiv2_nodraw == 1) tab[4]=0;
+ }
+ else
+ {
+ if (adiv1_nodraw == 2) size = 4;
+ if (adiv1_nodraw == 1)
+ {
+ size = 4; tab[3] = tab[4];
+ }
+ }
+ }
+ dialogSkinner.Draw(hwndDlg, tab, size);
+ }
+ break;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_ML_CHILDIPC:
+ switch (lParam)
+ {
+ case ML_CHILDIPC_GO_TO_SEARCHBAR:
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ case ML_CHILDIPC_REFRESH_SEARCH:
+ refresh = (1 + numFilters);
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ }
+ break;
+
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/view_errorinfo.cpp b/Src/Plugins/Library/ml_local/view_errorinfo.cpp
new file mode 100644
index 00000000..5f7e71ec
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_errorinfo.cpp
@@ -0,0 +1,119 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw)
+{
+ RECT rc, rg;
+ HRGN rgn;
+
+ GetClientRect(hwnd, &rc);
+ SetRect(&rg, 0, 0, 0, 0);
+
+ rc.top += 2;
+ rc.right -=2;
+
+ if (rc.bottom <= rc.top || rc.right <= rc.left) return;
+
+ rgn = NULL;
+
+ HWND temp = GetDlgItem(hwnd, IDC_DB_ERROR);
+ GetWindowRect(temp, &rg);
+ SetWindowPos(temp, NULL, WASABI_API_APP->getScaleX(20), WASABI_API_APP->getScaleY(20),
+ rc.right - rc.left - WASABI_API_APP->getScaleX(40),
+ rc.bottom - rc.top - WASABI_API_APP->getScaleY(45),
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+
+ temp = GetDlgItem(hwnd, IDC_RESET_DB_ON_ERROR);
+ GetWindowRect(temp, &rg);
+ SetWindowPos(temp, NULL, ((rc.right - rc.left) - (rg.right - rg.left)) / 2,
+ rc.bottom - (rg.bottom - rg.top),
+ rg.right - rg.left, rg.bottom - rg.top,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ if (fRedraw)
+ {
+ UpdateWindow(hwnd);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ if (rgn)
+ {
+ OffsetRgn(rgn, rc.left, rc.top);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, rgn, RGN_OR);
+ }
+ }
+ ValidateRgn(hwnd, NULL);
+ if (rgn) DeleteObject(rgn);
+}
+
+INT_PTR CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a=dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DB_ERROR),(LPCWSTR)WASABI_API_LOADRESFROMFILEW(L"TEXT", MAKEINTRESOURCE((nde_error ? IDR_NDE_ERROR : IDR_DB_ERROR)), 0));
+ if (nde_error)
+ DestroyWindow(GetDlgItem(hwndDlg, IDC_RESET_DB_ON_ERROR));
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.hwndToSkin = hwndDlg;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_RESET_DB_ON_ERROR)
+ {
+ nukeLibrary(hwndDlg);
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER+66:
+ if (wParam == -1)
+ {
+ LayoutWindows(hwndDlg, TRUE);
+ }
+ return TRUE;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ dialogSkinner.Draw(hwndDlg, 0, 0);
+ }
+ return 0;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ }
+ return FALSE;
+}
diff --git a/Src/Plugins/Library/ml_local/view_media.cpp b/Src/Plugins/Library/ml_local/view_media.cpp
new file mode 100644
index 00000000..7c8aa07e
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_media.cpp
@@ -0,0 +1,4051 @@
+#include "main.h"
+#include "ml_local.h"
+#include <windowsx.h>
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include <time.h>
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "../ml_pmp/pmp.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "../nde/nde.h"
+#include "../replicant/nu/AutoWide.h"
+#include "../replicant/nu/AutoChar.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <math.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+#include "..\..\General\gen_ml/menufucker.h"
+#include "api_mldb.h"
+#include "../replicant/foundation/error.h"
+
+static wchar_t oldText[4096];
+
+static int IPC_LIBRARY_SENDTOMENU;
+static HINSTANCE cloud_hinst;
+const int ML_MSG_PDXS_STATUS = 0x1001;
+const int ML_MSG_PDXS_MIX = 0x1002;
+void RefreshMetadata(HWND parent);
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX, offsetY, customAllowed;
+int groupBtn = 1, enqueuedef = 0;
+static viewButtons view;
+HWND hwndSearchGlobal = 0;
+
+//timers
+#define TIMER_RATINGAUTOUNHOVER_ID 65520
+#define TIMER_RATINGAUTOUNHOVER_DELAY 200
+
+void FixAmps(wchar_t *str, size_t len)
+{
+ size_t realSize = 0;
+ size_t extra = 0;
+ wchar_t *itr = str;
+ while (itr && *itr)
+ {
+ if (itr && *itr == L'&')
+ extra++;
+ itr++;
+ realSize++;
+ }
+
+ extra = min(len - (realSize + 1), extra);
+
+ while (extra)
+ {
+ str[extra+realSize] = str[realSize];
+ if (str[realSize] == L'&')
+ {
+ extra--;
+ str[extra+realSize] = L'&';
+ }
+ realSize--;
+ }
+}
+
+void MakeDateString(__time64_t convertTime, wchar_t *dest, size_t destlen)
+{
+ SYSTEMTIME sysTime;
+ tm *newtime = _localtime64(&convertTime);
+ if (newtime)
+ {
+ sysTime.wYear = (WORD)(newtime->tm_year + 1900);
+ sysTime.wMonth = (WORD)(newtime->tm_mon + 1);
+ sysTime.wDayOfWeek = (WORD)newtime->tm_wday;
+ sysTime.wDay = (WORD)newtime->tm_mday;
+ sysTime.wHour = (WORD)newtime->tm_hour;
+ sysTime.wMinute = (WORD)newtime->tm_min;
+ sysTime.wSecond = (WORD)newtime->tm_sec;
+ sysTime.wMilliseconds = 0;
+
+ GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, destlen);
+
+ size_t dateSize = lstrlenW(dest);
+ dest += dateSize;
+ destlen -= dateSize;
+ if (destlen)
+ {
+ *dest++ = L' ';
+ destlen--;
+ }
+
+ GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, destlen);
+
+ //wcsftime(expire_time, 63, L"%b %d, %Y", _localtime64(&convertTime));
+ }
+ else
+ dest[0] = 0;
+}
+
+#define MAINTABLE_ID_CLOUD (unsigned char)-1
+const unsigned char extra_idsW[] =
+{
+ MAINTABLE_ID_ISPODCAST,
+ MAINTABLE_ID_PODCASTCHANNEL,
+ MAINTABLE_ID_PODCASTPUBDATE,
+ MAINTABLE_ID_GRACENOTEFILEID,
+ MAINTABLE_ID_GRACENOTEEXTDATA,
+ MAINTABLE_ID_LOSSLESS,
+ MAINTABLE_ID_CODEC,
+ MAINTABLE_ID_DIRECTOR,
+ MAINTABLE_ID_PRODUCER,
+ MAINTABLE_ID_WIDTH,
+ MAINTABLE_ID_HEIGHT,
+ MAINTABLE_ID_MIMETYPE,
+ 0,
+ MAINTABLE_ID_DATEADDED,
+ MAINTABLE_ID_CLOUD,
+};
+
+const ExtendedFields extended_fields =
+{
+ L"ispodcast",
+ L"podcastchannel",
+ L"podcastpubdate",
+ L"GracenoteFileID",
+ L"GracenoteExtData",
+ L"lossless",
+ L"codec",
+ L"director",
+ L"producer",
+ L"width",
+ L"height",
+ L"mime",
+ L"realsize",
+ L"dateadded",
+ L"cloud",
+};
+
+const wchar_t *extra_strsW[] =
+{
+ extended_fields.ispodcast,
+ extended_fields.podcastchannel,
+ extended_fields.podcastpubdate,
+ extended_fields.GracenoteFileID,
+ extended_fields.GracenoteExtData,
+ extended_fields.lossless,
+ extended_fields.codec,
+ extended_fields.director,
+ extended_fields.producer,
+ extended_fields.width,
+ extended_fields.height,
+ extended_fields.mimetype,
+ extended_fields.realsize,
+ extended_fields.dateadded,
+ extended_fields.cloud,
+};
+
+const int NUM_EXTRA_COLSW = sizeof(extra_idsW) / sizeof(*extra_idsW);
+
+bool isMixable(itemRecordW &song);
+static int predixisExist;
+
+static BOOL g_displaysearch = TRUE;
+static BOOL g_displaycontrols = TRUE;
+
+nde_scanner_t m_media_scanner = 0;
+
+W_ListView resultlist;
+static int resultSkin;
+
+void fileInfoDialogs(HWND hwndParent);
+void editInfo(HWND hwndParent);
+void customizeColumnsDialog(HWND hwndParent);
+
+static HWND m_hwnd;
+
+itemRecordListW itemCache;
+static int bgThread_Kill = 0;
+static HANDLE bgThread_Handle;
+static bool isMixablePresent = true;
+
+CloudFiles cloudFiles, cloudUploading;
+
+typedef struct
+{
+ UINT column_id;
+ char *config_name;
+ WORD defWidth;
+ WORD minWidth;
+ WORD maxWidth;
+}
+headerColumn;
+
+#define UNLIMITED_WIDTH ((WORD)-1)
+
+#define COLUMN_DEFMINWIDTH 12
+#define COLUMN_DEFMAXWIDTH UNLIMITED_WIDTH
+
+#define MAX_COLUMN_ORDER (MEDIAVIEW_COL_NUMS+1)
+
+static headerColumn columnList[MAX_COLUMN_ORDER - 1] =
+{
+ {IDS_ARTIST, "mv_col_artist", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TITLE, "mv_col_title", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_ALBUM, "mv_col_album", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_LENGTH, "mv_col_length", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TRACK_NUMBER, "mv_col_track", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_GENRE, "mv_col_genre", 75, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_YEAR, "mv_col_year", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILENAME, "mv_col_fn", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_RATING, "mv_col_rating", 65, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PLAY_COUNT, "mv_col_playcount", 70, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PLAYED_LAST, "mv_col_lastplay", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_LAST_UPDATED, "mv_col_lastupd", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILE_TIME, "mv_col_filetime", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_COMMENT, "mv_col_comment", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILE_SIZE, "mb_col_filesize", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_BITRATE, "mb_col_bitrate", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TYPE, "mb_col_type", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DISC, "mb_col_disc", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_ALBUM_ARTIST, "mb_col_albumartist", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_FILE_PATH, "mb_col_filepath", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_ALBUM_GAIN, "mb_col_albumgain", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_TRACK_GAIN, "mb_col_trackgain", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PUBLISHER, "mb_col_publisher", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_COMPOSER, "mb_col_composer", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_EXTENSION, "mb_col_extension", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_IS_PODCAST, "mb_col_ispodcast", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PODCAST_CHANNEL, "mb_col_podcastchannel", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PODCAST_PUBLISH_DATE, "mb_col_podcastpubdate", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_BPM, "mb_col_bpm", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_CATEGORY, "mb_col_category", 75, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DIRECTOR, "mb_col_director", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_PRODUCER, "mb_col_producer", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DIMENSION, "mb_col_dimension", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_DATE_ADDED, "mb_col_dateadded", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+ {IDS_CLOUD, "mv_col_cloud", 27, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH},
+};
+
+//default column order
+static signed char defColumnOrderCloud[MAX_COLUMN_ORDER] =
+{
+ MEDIAVIEW_COL_ARTIST,
+ MEDIAVIEW_COL_ALBUM,
+ MEDIAVIEW_COL_TRACK,
+ MEDIAVIEW_COL_CLOUD,
+ MEDIAVIEW_COL_TITLE,
+ MEDIAVIEW_COL_LENGTH,
+ MEDIAVIEW_COL_GENRE,
+ MEDIAVIEW_COL_RATING,
+ MEDIAVIEW_COL_PLAYCOUNT,
+ MEDIAVIEW_COL_LASTPLAY,
+ MEDIAVIEW_COL_YEAR,
+ -1
+};
+static signed char defColumnOrder[MAX_COLUMN_ORDER] =
+{
+ MEDIAVIEW_COL_ARTIST,
+ MEDIAVIEW_COL_ALBUM,
+ MEDIAVIEW_COL_TRACK,
+ MEDIAVIEW_COL_TITLE,
+ MEDIAVIEW_COL_LENGTH,
+ MEDIAVIEW_COL_GENRE,
+ MEDIAVIEW_COL_RATING,
+ MEDIAVIEW_COL_PLAYCOUNT,
+ MEDIAVIEW_COL_LASTPLAY,
+ MEDIAVIEW_COL_YEAR,
+ -1
+};
+static signed char columnOrder[MAX_COLUMN_ORDER];
+
+
+int WCSCMP_NULLOK(const wchar_t *pa, const wchar_t *pb)
+{
+ if (!pa) pa = L"";
+ else SKIP_THE_AND_WHITESPACEW(pa)
+
+ if (!pb) pb = L"";
+ else SKIP_THE_AND_WHITESPACEW(pb)
+
+ return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE /*| NORM_IGNORENONSPACE*/, pa, -1, pb, -1) - 2;
+// return lstrcmpi(pa, pb);
+}
+
+
+int FLOATWCMP_NULLOK(const wchar_t *pa, const wchar_t *pb)
+{
+ if (pa) SKIP_THE_AND_WHITESPACEW(pa)
+
+ if (pb) SKIP_THE_AND_WHITESPACEW(pb)
+
+ if ((!pa || !*pa) && (!pb || !*pb))
+ return 0;
+ if (!pa || !*pa)
+ return 1;
+ if (!pb || !*pb)
+ return -1;
+
+ _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
+ float a = (float)_wtof_l(pa,C_locale);
+ float b = (float)_wtof_l(pb,C_locale);
+
+ if (a > b)
+ return 1;
+ else if (a < b)
+ return -1;
+ else
+ return 0;
+}
+
+
+typedef struct
+{
+ resultsniff_funcW cb;
+ int user32;
+}
+bgThreadParms;
+
+static int bg_total_len_s;
+static __int64 bg_total_len_bytes;
+static LARGE_INTEGER querytime;
+
+static HMENU rate_hmenu = NULL;
+static HMENU cloud_hmenu = NULL;
+static HMENU sendto_hmenu = NULL;
+static librarySendToMenuStruct s;
+
+static RATINGCOLUMN ratingColumn;
+static WCHAR ratingBackText[128];
+
+#define IDC_LIST2HEADER 2001 // WM_INITDIALOG assign this is to the IDC_LIST2 header
+
+// internal messages
+#define IM_SYNCHEADERORDER (WM_USER + 0xFFF0)
+
+static DWORD WINAPI bgThreadQueryProc(void *tmp)
+{
+ bgThreadParms *p = (bgThreadParms*)tmp;
+ bg_total_len_s = 0;
+ bg_total_len_bytes = 0;
+ LARGE_INTEGER starttime, endtime;
+ QueryPerformanceCounter(&starttime);
+
+ bg_total_len_s = saveQueryToListW(g_view_metaconf, m_media_scanner, &itemCache,
+ &cloudFiles, &cloudUploading, p->cb, p->user32,
+ &bgThread_Kill, &bg_total_len_bytes);
+ QueryPerformanceCounter(&endtime);
+ querytime.QuadPart = endtime.QuadPart - starttime.QuadPart;
+
+ if (!bgThread_Kill) PostMessage(m_hwnd, WM_APP + 3, 0x69, 0);
+
+ return 0;
+}
+
+void bgQuery_Stop() // exported for other people to call since it is useful (eventually
+// we should have bgQuery pass the new query info along but I'll do that soon)
+{
+ if (bgThread_Handle)
+ {
+ bgThread_Kill = 1;
+ WaitForSingleObject(bgThread_Handle, INFINITE);
+ CloseHandle(bgThread_Handle);
+ bgThread_Handle = 0;
+ }
+ KillTimer(m_hwnd, 123);
+}
+
+static void bgQuery(resultsniff_funcW cb = 0, int user32 = 0) // only internal used
+{
+ bgQuery_Stop();
+ SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, WASABI_API_LNGSTRINGW(IDS_SCANNING));
+ StringCchCopyW(oldText, 4096, WASABI_API_LNGSTRINGW(IDS_SCANNING));
+
+ SetTimer(m_hwnd, 123, 200, NULL);
+
+ DWORD id;
+ static bgThreadParms parms;
+ parms.cb = cb;
+ parms.user32 = user32;
+ bgThread_Kill = 0;
+ bgThread_Handle = CreateThread(NULL, 0, bgThreadQueryProc, (LPVOID) & parms, 0, &id);
+}
+
+// this thing does not produce a fully valid itemRecordList. be afraid.
+static void copyFilesToItemCacheW(itemRecordListW *obj)
+{
+ if (bgThread_Handle) return ;
+
+ int cnt = itemCache.Size;
+ int i, l = cnt;
+ cnt = 0;
+ for (i = 0;i < l;i++)
+ {
+ if (resultlist.GetSelected(i)) cnt++;
+ }
+ obj->Alloc = obj->Size = 0;
+
+ if (!cnt) return ;
+
+ allocRecordList(obj, cnt, 0);
+ if (!obj->Items)
+ {
+ obj->Size = obj->Alloc = 0;
+ return ;
+ }
+
+ for (i = 0; i < itemCache.Size; i ++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ obj->Items[obj->Size++] = itemCache.Items[i];
+ // makes sure that we are providing filesize in kb as
+ // per spec even if we store it as __int64 internally
+ obj->Items[obj->Size-1].filesize /= 1024;
+ }
+ }
+}
+
+void playFiles(int enqueue, int all)
+{
+ if (bgThread_Handle) return ;
+
+ int cnt = 0;
+ int l = itemCache.Size;
+
+ int foo_all = 0; // all but play the only selected
+ int foo_selected = -1;
+
+ if (!enqueue && !all && g_config->ReadInt(L"viewplaymode", 1))
+ {
+ int selcnt = 0;
+ for (int i = 0;i < l;i++)
+ {
+ if (resultlist.GetSelected(i)) selcnt++;
+ }
+ if (selcnt == 1)
+ {
+ foo_all = -1;
+ }
+ }
+
+ for (int i = 0;i < l;i++)
+ {
+ if (foo_all || all || resultlist.GetSelected(i))
+ {
+ if (foo_all && foo_selected < 0 && resultlist.GetSelected(i)) foo_selected = i;
+
+ if (!cnt)
+ {
+ if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE);
+ cnt++;
+ }
+
+ wchar_t title[2048] = {0};
+ TAG_FMT_EXT(itemCache.Items[i].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&itemCache.Items[i], title, 2048, 0);
+
+ enqueueFileWithMetaStructW s;
+ s.filename = itemCache.Items[i].filename;
+ s.title = title;
+ s.ext = NULL;
+ s.length = itemCache.Items[i].length;
+#ifndef _DEBUG
+ ndestring_retain(itemCache.Items[i].filename);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW_NDE);
+#else
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+#endif
+ }
+ }
+ if (cnt)
+ {
+ if (foo_selected >= 0)
+ {
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, foo_selected, IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40047, 0); // stop button, literally
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40045, 0); // play button, literally
+ }
+ else if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY);
+ }
+}
+
+// out can never be bigger than in+1
+static void parsequicksearch(wchar_t *out, wchar_t *in) // parses a list into a list of terms that we are searching for
+{
+ int inquotes = 0, neednull = 0;
+ while (in && *in)
+ {
+ wchar_t c = *in++;
+ if (c != ' ' && c != '\t' && c != '\"')
+ {
+ neednull = 1;
+ *out++ = c;
+ }
+ else if (c == '\"')
+ {
+ inquotes = !inquotes;
+ if (!inquotes)
+ {
+ *out++ = 0;
+ neednull = 0;
+ }
+ }
+ else
+ {
+ if (inquotes) *out++ = c;
+ else if (neednull)
+ {
+ *out++ = 0;
+ neednull = 0;
+ }
+ }
+ }
+ *out++ = 0;
+ *out++ = 0;
+}
+
+void makeQueryStringFromText(GayStringW *query, wchar_t *text, int nf)
+{
+ int ispar = 0;
+ if (query->Get()[0])
+ {
+ ispar = 1;
+ query->Append(L"&(");
+ }
+
+ if (!_wcsnicmp(text, L"query:", 6))
+ query->Append(text + 6); // copy the query as is
+ else if (text[0] == L'?')
+ query->Append(text + 1);
+ else
+ {
+ int isAny = 0;
+ if (*text == L'*' && text[1] == L' ')
+ {
+ isAny = 1;
+ text += 2;
+ }
+ wchar_t tmpbuf[2048 + 32] = {0};
+ parsequicksearch(tmpbuf, text);
+
+ int x;
+ wchar_t *fields[] =
+ {
+ L"filename",
+ L"title",
+ L"artist",
+ L"album",
+ L"genre",
+ L"albumartist",
+ L"publisher",
+ L"composer",
+ };
+ wchar_t *p = tmpbuf;
+ while (p && *p)
+ {
+ size_t lenp = wcslen(p);
+
+ if (p == tmpbuf) query->Append(L"(");
+ else if (isAny) query->Append(L")|(");
+ else query->Append(L")&(");
+ if (p[0] == L'<' && p[wcslen(p) - 1] == L'>' && wcslen(p) > 2)
+ {
+ wchar_t *op = p;
+ while (op && *op)
+ {
+ if (*op == L'\'') *op = L'\"';
+ op++;
+ }
+
+ p[lenp - 1] = 0; // remove >
+ query->Append(p + 1);
+ }
+ else
+ {
+ for (x = 0; x < (int)min(sizeof(fields) / sizeof(fields[0]), nf); x ++)
+ {
+ wchar_t *field = fields[x];
+ if (x) query->Append(L"|");
+ query->Append(field);
+ query->Append(L" HAS \"");
+ GayStringW escaped;
+ queryStrEscape(p, escaped);
+ query->Append(escaped.Get());
+ query->Append(L"\"");
+ }
+ }
+ p += lenp + 1;
+ }
+ query->Append(L")");
+ }
+ if (ispar) query->Append(L")");
+}
+
+static void doQuery(HWND hwndDlg, wchar_t *text, int dobg = 1)
+{
+ bgQuery_Stop();
+
+ GayStringW query;
+ if (text[0]) makeQueryStringFromText(&query, text);
+
+ wchar_t *parent_query = NULL;
+ extern wchar_t* m_query;
+ parent_query = m_query;
+ SendMessage(GetParent(hwndDlg), WM_APP + 2, 0, (LPARAM)&parent_query);
+ GayStringW q;
+
+ if (parent_query && parent_query[0])
+ {
+ q.Set(L"(");
+ q.Append(parent_query);
+ q.Append(L")");
+ }
+ if (query.Get() && query.Get()[0])
+ {
+ if (q.Get()[0])
+ {
+ q.Append(L" & (");
+ q.Append(query.Get());
+ q.Append(L")");
+ }
+ else q.Set(query.Get());
+ }
+
+ EnterCriticalSection(&g_db_cs);
+ NDE_Scanner_Query(m_media_scanner, q.Get());
+ LeaveCriticalSection(&g_db_cs);
+ if (dobg) bgQuery();
+}
+
+static void RecycleSelectedItems()
+{
+ int totalItems = resultlist.GetSelectedCount();
+
+ if (!totalItems)
+ return ;
+
+ SHFILEOPSTRUCTW fileOp;
+ fileOp.hwnd = m_hwnd;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.pFrom = 0;
+ fileOp.pTo = 0;
+ fileOp.fFlags = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_USES_RECYCLEBIN) ? FOF_ALLOWUNDO : 0;
+ fileOp.fAnyOperationsAborted = 0;
+ fileOp.hNameMappings = 0;
+ fileOp.lpszProgressTitle = 0;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ int cchLen = totalItems * (MAX_PATH + 1) + 1;
+ wchar_t *files = new wchar_t[cchLen]; // need room for each file name, null terminated. then have to null terminate the whole list
+
+ if (files) // if malloc succeeded
+ {
+ wchar_t *curFile = files;
+ for (int i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename))
+ {
+ StringCchCopyW(curFile, cchLen, itemCache.Items[i].filename);
+ curFile += wcslen(itemCache.Items[i].filename) + 1;
+ }
+ }
+ }
+ if (curFile != files)
+ {
+ curFile[0] = 0; // null terminate
+
+ fileOp.pFrom = files;
+ fileOp.fAnyOperationsAborted = 0;
+ if (SHFileOperationW(&fileOp))
+ {
+ wchar_t title[64] = {0};
+ MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_ERROR_DELETING_FILES),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,64), MB_OK);
+ }
+ else
+ {
+ // only remove items if deletion was allowed
+ if (!fileOp.fAnyOperationsAborted)
+ {
+ for (int j = 0; j < itemCache.Size; j++)
+ {
+ if (resultlist.GetSelected(j))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[j].filename))
+ {
+ // Wasabi callback event for pre remove
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[j].filename, 0);
+
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ // Wasabi callback event for post remove
+ // ToDo: (BigG) Move outside of critical section somehow
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[j].filename, 0);
+
+ }
+ }
+ }
+ }
+ }
+ }
+
+ delete [] files;
+ }
+ else // if malloc failed ... maybe because there's too many items.
+ {
+ files = new wchar_t[MAX_PATH + 1 + 1]; // double null termination
+ if (!files) // if this malloc failed, just bail out
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return ;
+ }
+
+ fileOp.pFrom = files;
+
+ for (int i = 0;i < itemCache.Size;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename))
+ {
+ StringCchCopyW(files, MAX_PATH + 1 + 1, itemCache.Items[i].filename);
+ files[wcslen(itemCache.Items[i].filename) + 1] = 0; // double null terminate
+ fileOp.fAnyOperationsAborted = 0;
+ if (SHFileOperationW(&fileOp))
+ {
+ wchar_t mes[4096] = {0};
+ StringCchPrintfW(mes, 4096, WASABI_API_LNGSTRINGW(IDS_ERROR_DELETING_X), files);
+ MessageBoxW(m_hwnd, mes, WASABI_API_LNGSTRINGW(IDS_ERROR), MB_OK);
+ continue;
+ }
+ if (!fileOp.fAnyOperationsAborted)
+ {
+ // Wasabi callback event for pre remove
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[i].filename, 0);
+
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ // Wasabi callback event for post remove
+ // ToDo: (BigG) Move outside of critical section somehow
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[i].filename, 0);
+ }
+ }
+ }
+ delete files;
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty) NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+
+ resultlist.Clear();
+ emptyRecordList(&itemCache);
+
+ // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc),
+ // but really it is necessary for the view to be accurate.
+
+ SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view
+}
+
+static void removeSelectedItems(int physical)
+{
+ if (physical)
+ {
+ RecycleSelectedItems();
+ return ;
+ }
+
+ int hasdel = 0;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ for (int i = 0;i < itemCache.Size;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename))
+ {
+ wchar_t conf[32] = {0};
+ if (!hasdel && MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,conf,32), MB_YESNO | MB_ICONQUESTION) != IDYES)
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ return ;
+ //FUCKO> need to eat the RETURN msg
+ //MSG msg;
+ //while(PeekMessage(&msg,m_hwnd,WM_COMMAND,WM_COMMAND,1));
+ }
+ if (!hasdel) // stop any background queries
+ {
+ bgQuery_Stop();
+ }
+ // Wasabi callback event for pre remove
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[i].filename, 0);
+
+ hasdel = 1;
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+
+ // Wasabi callback event for post remove
+ // ToDo: (BigG) Move this outside of the critical section
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[i].filename, 0);
+ }
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty) NDE_Table_Sync(g_table);
+ g_table_dirty = 0;
+ LeaveCriticalSection(&g_db_cs);
+
+ if (!hasdel) return ;
+
+ resultlist.Clear();
+ emptyRecordList(&itemCache);
+
+ // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc),
+ // but really it is necessary for the view to be accurate.
+
+ SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view
+}
+
+static void exploreItemFolder(HWND hwndDlg)
+{
+ if (resultlist.GetSelectionMark() >= 0)
+ {
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ WASABI_API_EXPLORERFINDFILE->AddFile(itemCache.Items[i].filename);
+ }
+ }
+ WASABI_API_EXPLORERFINDFILE->ShowFiles();
+ }
+}
+
+static void removeDeadFiles(HWND hwndDlg)
+{
+ Scan_RemoveFiles(hwndDlg);
+
+ // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc),
+ // but really it is necessary for the view to be accurate.
+ SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view
+}
+
+static WNDPROC search_oldWndProc;
+static DWORD WINAPI search_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_KEYDOWN && wParam == VK_DOWN)
+ {
+ PostMessageW(GetParent(hwndDlg), WM_NEXTDLGCTL, (WPARAM)resultlist.getwnd(), (LPARAM)TRUE);
+ ListView_SetItemState(resultlist.getwnd(), 0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
+ }
+ return CallWindowProcW(search_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+typedef struct _LAYOUT
+{
+ INT id;
+ HWND hwnd;
+ INT x;
+ INT y;
+ INT cx;
+ INT cy;
+ DWORD flags;
+ HRGN rgn;
+}
+LAYOUT, PLAYOUT;
+
+#define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
+#define SETLAYOUTFLAGS(_layout, _r) \
+ { \
+ BOOL fVis; \
+ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
+ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
+ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
+ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
+ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
+ }
+
+#define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
+
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x3
+#define GROUP_SEARCH 0x1
+#define GROUP_STATUSBAR 0x2
+#define GROUP_MAIN 0x3
+
+
+static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_SEARCH, IDC_SEARCHCAPTION, IDC_CLEAR, IDC_QUICKSEARCH,
+ GROUP_STATUSBAR, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX, IDC_BUTTON_CREATEPLAYLIST, IDC_BUTTON_INFOTOGGLE, IDC_MIXABLE, IDC_MEDIASTATUS,
+ GROUP_MAIN, IDC_LIST2
+ };
+
+ INT index;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.bottom == rc.top || rc.right == rc.left) return;
+
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(*controls); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch (controls[index])
+ {
+ case GROUP_SEARCH:
+ if (g_displaysearch)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_CLEAR);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left,
+ rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaysearch;
+ break;
+ case GROUP_STATUSBAR:
+ if (g_displaycontrols)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_PLAY);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaycontrols;
+ break;
+ case GROUP_MAIN:
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ }
+ continue;
+ }
+ if (skipgroup) continue;
+
+ pl->id = controls[index];
+ pl->hwnd = GetDlgItem(hwnd, pl->id);
+ if (!pl->hwnd) continue;
+
+ GetWindowRect(pl->hwnd, &ri);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2);
+ pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
+
+ switch (pl->id)
+ {
+ case IDC_SEARCHCAPTION:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(2),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_CLEAR:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ;
+ SETLAYOUTPOS(pl, rg.right - width, rg.top, width, rg.bottom - rg.top);
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_QUICKSEARCH:
+ pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_BUTTON_PLAY:
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_MIX:
+ case IDC_BUTTON_CREATEPLAYLIST:
+ if (IDC_BUTTON_MIX != pl->id || customAllowed)
+ {
+ if (groupBtn && pl->id == IDC_BUTTON_PLAY && enqueuedef == 1)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && pl->id == IDC_BUTTON_ENQUEUE && enqueuedef != 1)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (groupBtn && (pl->id == IDC_BUTTON_PLAY || pl->id == IDC_BUTTON_ENQUEUE) && customAllowed)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ if (pl->id == IDC_BUTTON_CREATEPLAYLIST && !AGAVE_API_PLAYLIST_GENERATOR)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ width, WASABI_API_APP->getScaleY(HIWORD(idealSize)));
+ pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ }
+ else
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ case IDC_BUTTON_INFOTOGGLE:
+ switch (SendMessage(GetParent(hwnd), WM_USER + 66, 0, 0))
+ {
+ case 0xFF:
+ case 0xF0:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer);
+ LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
+
+ pl->flags |= (((rg.right - rg.left) - (ri.right - ri.left)) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ;
+ SETLAYOUTPOS(pl, rg.right - width - WASABI_API_APP->getScaleX(2),
+ rg.top, width, (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ }
+ break;
+ case IDC_MIXABLE:
+ if (predixisExist & 1)
+ {
+ SETLAYOUTPOS(pl, rg.right - (ri.right - ri.left), rg.top, (ri.right - ri.left), (rg.bottom - rg.top));
+ pl->flags |= ((rg.right - rg.left) - (ri.right - ri.left) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4));
+ }
+ break;
+ case IDC_MEDIASTATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top));
+ pl->flags |= (pl->cx > WASABI_API_APP->getScaleX(16)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ break;
+ case IDC_LIST2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + 1, (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+ break;
+ }
+
+ SETLAYOUTFLAGS(pl, ri);
+ if (LAYOUTNEEEDUPDATE(pl))
+ {
+ if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) &&
+ ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && !fUpdateAll && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+
+ pl++;
+ }
+ else if (!fUpdateAll && (fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0,0,0,0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags);
+ }
+ if (hdwp) EndDeferWindowPos(hdwp);
+
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+
+ if (fRedraw)
+ {
+ GetUpdateRgn(hwnd, rgn, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(rgn, rgn, pc->rgn, RGN_OR);
+ }
+ }
+ RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ for (pc = layout; pc < pl && hdwp; pc++)
+ {
+ if (pc->rgn)
+ {
+ OffsetRgn(pc->rgn, pc->x, pc->y);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR);
+ }
+ }
+ }
+
+ for (pc = layout; pc < pl && hdwp; pc++)
+ if (pc->rgn) DeleteObject(pc->rgn);
+ }
+ if (rgn) DeleteObject(rgn);
+ ValidateRgn(hwnd, NULL);
+}
+
+static void updateInfoText(HWND hwndDlg, bool x = false)
+{
+ int a = SendMessage(GetParent(hwndDlg), WM_USER + 66, x ? -1 : 0, 0);
+ if (a == 0xff)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_INFOTOGGLE, WASABI_API_LNGSTRINGW(IDS_HIDE_INFO));
+ else if (a == 0xf0)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_INFOTOGGLE, WASABI_API_LNGSTRINGW(IDS_SHOW_INFO));
+ else ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_INFOTOGGLE), SW_HIDE);
+}
+
+static void initColumnsHeader(HWND hwndList)
+{
+ INT index, sortby;
+ LVCOLUMNW lvc = {0, };
+ if (!hwndList || !IsWindow(hwndList)) return;
+
+ SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L);
+
+ while (SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L));
+
+ sortby = g_view_metaconf->ReadInt(L"mv_sort_by", 1);
+
+ lvc.mask = LVCF_TEXT | LVCF_WIDTH;
+ lvc.pszText = L"";
+ lvc.cx = 30;
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc); // create dummy column
+
+ // TODO set to a zero width if not available
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1); // reset the cloud status column so it'll be correctly removed
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1);
+
+ for (index = 0; columnOrder[index] != -1; index++)
+ {
+ headerColumn *cl = &columnList[columnOrder[index]];
+ lvc.pszText = WASABI_API_LNGSTRINGW(cl->column_id);
+
+ lvc.cx = g_view_metaconf->ReadInt(AutoWide(cl->config_name), cl->defWidth);
+ if (lvc.cx < cl->minWidth) lvc.cx = cl->minWidth;
+
+ // update position of the cloud column icon
+ if (columnOrder[index] == MEDIAVIEW_COL_CLOUD)
+ {
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1);
+ lvc.cx = 0;
+ }
+ else
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), index);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)index);
+ lvc.cx = 27;
+ MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &lvc.cx);
+ }
+ }
+
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, 0xFFFF, (LPARAM)&lvc);
+
+ if (sortby == columnOrder[index]) MLSkinnedListView_DisplaySort(hwndList, index, !g_view_metaconf->ReadInt(L"mv_sort_dir", 0));
+ }
+ SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L); // Delete dummy column
+
+ for (index = 0; -1 != columnOrder[index] && MEDIAVIEW_COL_RATING != columnOrder[index]/* && MEDIAVIEW_COL_CLOUD != columnOrder[index]*/; index++);
+ if (-1 != index) SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, index, (LPARAM)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, index, 0L));
+
+ SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L);
+}
+
+static int m_last_selitem = -1;
+static int m_bgupdinfoviewerflag;
+
+extern void add_to_library(HWND wndparent);
+
+static INT_PTR CALLBACK needAddFilesProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ if (AGAVE_API_ITUNES_IMPORTER && AGAVE_API_ITUNES_IMPORTER->iTunesExists())
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IMPORT_ITUNES), TRUE);
+ else
+ EnableWindow(GetDlgItem(hwndDlg, IDC_IMPORT_ITUNES), FALSE);
+
+ SetTimer(hwndDlg, 1, 1000, NULL);
+ return 1;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ case IDCANCEL:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ if (IsDlgButtonChecked(hwndDlg, IDC_CHECK1)) g_config->WriteInt(L"noshowadddlg", 1);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case ID_ADD_FILES:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ add_to_library(hwndDlg);
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ }
+ break;
+ case IDC_IMPORT_ITUNES:
+ if (BN_CLICKED == HIWORD(wParam))
+ {
+ if (AGAVE_API_ITUNES_IMPORTER)
+ AGAVE_API_ITUNES_IMPORTER->ImportFromiTunes(hwndDlg);
+
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ if (m_curview_hwnd) PostMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ }
+ break;
+ case IDC_BTN_LINK_PROMO:
+ if (BN_CLICKED == HIWORD(wParam)) ShellExecuteA(plugin.hwndWinampParent, "open", "https://help.winamp.com/hc/articles/8105244490772-Player-Overview", NULL, ".", 0);
+ break;
+ }
+ break;
+ case WM_TIMER:
+ if (g_table && NDE_Table_GetRecordsCount(g_table))
+ {
+ wchar_t buf[512] = {0};
+ StringCchPrintfW(buf, 512, WASABI_API_LNGSTRINGW(IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY), NDE_Table_GetRecordsCount(g_table));
+ SetDlgItemTextW(hwndDlg, IDC_TEXT, buf);
+ SetDlgItemTextW(hwndDlg, ID_ADD_FILES, WASABI_API_LNGSTRINGW(IDS_ADD_MORE));
+ }
+ break;
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ wchar_t wt[123] = {0};
+ int y;
+ RECT r;
+ HPEN hPen, hOldPen;
+ DWORD style;
+ GetDlgItemText(hwndDlg, (INT)wParam, wt, ARRAYSIZE(wt));
+
+ style = (DWORD)GetWindowLongPtrW(di->hwndItem, GWL_STYLE);
+ // draw text
+ SetTextColor(di->hDC, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+
+ memset(&r, 0, sizeof(r));
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_CALCRECT);
+
+
+ if (BS_RIGHT & style) r.left = max(di->rcItem.left+ 2, di->rcItem.right - r.right - 2);
+ else if (BS_LEFT & style) r.left = di->rcItem.left+ 2;
+ else r.left = ((di->rcItem.right - di->rcItem.left - 4) - r.right) / 2;
+
+ if (r.left < di->rcItem.left + 2)
+ {
+ r.left = di->rcItem.left + 2;
+ r.right = di->rcItem.right - 2;
+ }
+ else r.right += r.left;
+ if (r.right > di->rcItem.right - 2) r.right = di->rcItem.right - 2;
+
+
+ if (BS_TOP & style) r.top = di->rcItem.top;
+ else if(BS_VCENTER & style) r.top = ((di->rcItem.bottom - di->rcItem.top - 2) - r.bottom) /2;
+ else r.top = di->rcItem.bottom - 2 - r.bottom;
+
+ if (r.top < di->rcItem.top)
+ {
+ r.top = di->rcItem.top;
+ r.bottom = di->rcItem.bottom - 2;
+ }
+ else r.bottom += r.top;
+ if (r.bottom > di->rcItem.bottom - 2) r.bottom = di->rcItem.bottom - 2;
+
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_WORD_ELLIPSIS);
+
+ // draw underline
+ y = min(di->rcItem.bottom, r.bottom + 1);
+ hPen = CreatePen(PS_SOLID, 0, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ hOldPen = (HPEN) SelectObject(di->hDC, hPen);
+ MoveToEx(di->hDC, r.left, y, NULL);
+ LineTo(di->hDC, r.right, y);
+ SelectObject(di->hDC, hOldPen);
+ DeleteObject(hPen);
+ }
+ }
+ }
+
+ return 0;
+
+};
+
+static void SetStatusText(HWND hwndStatus, LPCWSTR *ppsz, INT count)
+{
+ WCHAR buffer[4096] = {0};
+ if (0 == count || !ppsz)
+ {
+ SetWindowText(hwndStatus, L"");
+ return;
+ }
+ buffer[0] = 0x00;
+ for (int i = 0; i < count; i++)
+ {
+ StringCchCatW(buffer, 4096, ppsz[i]);
+ StringCchCatW(buffer, 4096, L" ");
+ }
+ SetWindowTextW(hwndStatus, buffer);
+
+ StringCchCopyW(oldText, 4096, buffer);
+}
+
+static void SetRating(UINT iItem, INT newRating, HWND hwndList)
+{
+ if (0 == newRating) newRating = -1;
+ if (iItem < (UINT)itemCache.Size)
+ {
+ if (g_table && newRating != itemCache.Items[iItem].rating)
+ {
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[iItem].filename))
+ {
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, newRating);
+ NDE_Scanner_Post(s);
+ itemCache.Items[iItem].rating = newRating;
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (newRating > 0)
+ {
+ wsprintfW(buf, L"%d", newRating);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(itemCache.Items[iItem].filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (g_table_dirty)
+ {
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ }
+ if (newRating == itemCache.Items[iItem].rating && hwndList)
+ {
+ ratingColumn.hwndList = hwndList;
+ ratingColumn.iItem = iItem;
+ ratingColumn.iSubItem = 600;
+ MLRatingColumn_Animate(plugin.hwndLibraryParent, &ratingColumn);
+ ListView_RedrawItems(resultlist.getwnd(), iItem, iItem);
+ // TODO: benski> update the top panes w/o refreshing, if possible
+ // CUT: PostMessage(GetParent(GetParent(hwndList)), WM_APP + 4, (WPARAM)newRating, (LPARAM)1);
+ }
+ }
+}
+
+/////////// Header Messages / Notifications
+static BOOL Header_OnItemChanging(HWND hwndDlg, NMHEADERW *phdr, LRESULT *pResult, UINT uMsg)
+{
+ if (phdr->pitem && (HDI_WIDTH & phdr->pitem->mask))
+ {
+ INT test;
+ test = columnList[columnOrder[phdr->iItem]].minWidth;
+ if (phdr->pitem->cxy < test) phdr->pitem->cxy = test;
+ test = columnList[columnOrder[phdr->iItem]].maxWidth;
+ if (test != UNLIMITED_WIDTH && phdr->pitem->cxy > test) phdr->pitem->cxy = test;
+
+ if (MEDIAVIEW_COL_RATING == columnOrder[phdr->iItem])
+ {
+ RATINGWIDTH rw;
+
+ rw.fStyle = RCS_DEFAULT;
+ rw.width = phdr->pitem->cxy;
+ if (MLRatingColumn_GetWidth(plugin.hwndLibraryParent, &rw)) phdr->pitem->cxy = rw.width;
+ if (0 == phdr->iItem)
+ {
+ RATINGBACKTEXT rbt;
+ rbt.pszText = ratingBackText;
+ rbt.cchTextMax = sizeof(ratingBackText)/sizeof(WCHAR);
+ rbt.nColumnWidth = phdr->pitem->cxy;
+ rbt.fStyle = RCS_DEFAULT;
+ MLRatingColumn_FillBackString(plugin.hwndLibraryParent, &rbt);
+ }
+ else ratingBackText[0] = 0x00;
+ }
+ else if (MEDIAVIEW_COL_CLOUD == columnOrder[phdr->iItem])
+ {
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))
+ phdr->pitem->cxy = 0;
+ else
+ {
+ INT width = phdr->pitem->cxy;
+ if (MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &width))
+ {
+ phdr->pitem->cxy = width;
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+static BOOL Header_OnEndDrag(HWND hwndDlg, NMHEADERW *phdr, LRESULT *pResult)
+{
+ PostMessageW(hwndDlg, IM_SYNCHEADERORDER, 0, (LPARAM)phdr->hdr.hwndFrom);
+ return FALSE;
+}
+
+static BOOL Header_OnRightClick(HWND hwndDlg, NMHDR *pnmh, LRESULT *pResult)
+{
+ HMENU menu = GetSubMenu(g_context_menus, 4);
+ POINT p;
+ GetCursorPos(&p);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, hwndDlg, NULL);
+ switch (r)
+ {
+ case ID_HEADERWND_CUSTOMIZECOLUMNS:
+ customizeColumnsDialog(hwndDlg);
+ break;
+ }
+ return FALSE;
+}
+
+/////////// ListView Messages / Notifications
+static BOOL ListView_OnItemChanged(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ if (pnmv->uNewState & LVIS_SELECTED)
+ {
+ //if (GetFocus()==resultlist.getwnd())
+ {
+ m_last_selitem = pnmv->iItem;
+ KillTimer(hwndDlg, 6600);
+ SetTimer(hwndDlg, 6600, 250, NULL);
+ }
+ }
+ else
+ {
+ if (isMixablePresent)
+ {
+ SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ isMixablePresent = false;
+ }
+ }
+ return FALSE;
+}
+
+static BOOL ListView_OnDoubleClick(HWND hwndDlg, NMITEMACTIVATE *pnmitem)
+{
+ playFiles((!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)), 0);
+ return FALSE;
+}
+
+void EatKeyboard()
+{
+ Sleep(100);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+}
+
+static void Dialog_OnContextMenu(HWND hwndDlg, HWND hwndFrom, int x, int y)
+{
+ if (hwndFrom != resultlist.getwnd())
+ return;
+
+ POINT pt = {x,y};
+
+ if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu
+ {
+ RECT itemRect = {0};
+ int selected = resultlist.GetNextSelected();
+ if (selected != -1) // if something is selected we'll drop the menu from there
+ {
+ resultlist.GetItemRect(selected, &itemRect);
+ ClientToScreen(resultlist.getwnd(), (POINT *)&itemRect);
+ }
+ else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
+ {
+ GetWindowRect(resultlist.getwnd(), &itemRect);
+
+ HWND hHeader = (HWND)SNDMSG(resultlist.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect))
+ {
+ itemRect.top += (headerRect.bottom - headerRect.top);
+ }
+ }
+ x = itemRect.left;
+ y = itemRect.top;
+ }
+
+ HWND hHeader = (HWND)SNDMSG(resultlist.getwnd(), LVM_GETHEADER, 0, 0L);
+ RECT headerRect;
+ if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
+ {
+ SetRectEmpty(&headerRect);
+ }
+
+ if (FALSE != PtInRect(&headerRect, pt))
+ {
+ return;
+ }
+
+ HMENU globmenu = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU menu = GetSubMenu(globmenu, 0);
+ int rate_idx = 9;
+ sendto_hmenu = GetSubMenu(menu, 2);
+ rate_hmenu = GetSubMenu(menu, rate_idx);
+
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ s.mode = 0;
+ s.hwnd = 0;
+ s.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ s.ctx[1] = 1;
+ s.build_hMenu = sendto_hmenu;
+ }
+
+ wchar_t *artist = NULL;
+ wchar_t *album = NULL;
+ int n = resultlist.GetSelectionMark();
+ if (n != -1)
+ {
+ artist = itemCache.Items[n].artist;
+ album = itemCache.Items[n].album;
+ wchar_t str[2048] = {0}, str2[128] = {0};
+ // (BigG): Check the ini settings for viewing vs playing and create the menu accordingly
+ // Keeping this here in case we want to use the ini setting:
+ //StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW( (g_viewnotplay != 0) ? IDS_VIEW_ALL_FILES_BY : IDS_PLAY_ALL_FILES_BY), artist ? artist : L"");
+
+ int len = lstrlenW(artist);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", artist);//WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), artist ? artist : L"");
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), artist ? artist : L"");
+ }
+ FixAmps(str, 2048);
+ MENUITEMINFOW mii =
+ {
+ sizeof(MENUITEMINFOW),
+ MIIM_TYPE | MIIM_ID,
+ MFT_STRING,
+ MFS_ENABLED,
+ 0x1234,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ str,
+ 0,
+ };
+
+ if (!(!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)))
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0};
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ InsertMenuItemW(menu, 0, FALSE, &m);
+
+ wchar_t a[100] = {0};
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100);
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_hmenu = CreatePopupMenu();
+ InsertMenuItemW(menu, 0, FALSE, &m);
+ }
+
+ if (artist && artist[0])
+ {
+ InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii);
+ }
+ // (BigG): Check the ini settings for viewing vs playing and create the menu accordingly
+ // Keeping this here in case we want to use the ini setting:
+ len = lstrlenW(album);
+ if (len > 39)
+ {
+ StringCchPrintfW(str2, 40, L"%.36s...", album);
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_FROM), str2);
+ }
+ else
+ {
+ StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_FROM), album ? album : L"");
+ }
+ FixAmps(str, 2048);
+ mii.cch = wcslen(str);
+ mii.wID = 0x1235;
+ if (album && album[0])
+ {
+ InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii);
+ }
+
+ {
+ mii.wID = 0xdeadbeef;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii);
+ }
+ }
+ else
+ {
+ EnableMenuItem(menu, ID_MEDIAWND_PLAYSELECTEDFILES, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_MEDIAWND_ENQUEUESELECTEDFILES, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, IDC_REFRESH_METADATA, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_MEDIAWND_REMOVEFROMLIBRARY, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_EDITITEMINFOS, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_MEDIAWND_EXPLOREFOLDER, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, ID_PE_ID3, MF_BYCOMMAND | MF_GRAYED);
+ EnableMenuItem(menu, 2, MF_BYPOSITION | MF_GRAYED); //grays the "Add to playlist..." menu
+ EnableMenuItem(menu, rate_idx, MF_BYPOSITION | MF_GRAYED); //grays the "Rate..." menu
+ EnableMenuItem(menu, 13, MF_BYPOSITION | MF_GRAYED); //grays the "Remove..." menu
+ }
+
+ menufucker_t mf = {sizeof(mf),MENU_MEDIAVIEW,menu,0x3000,0x4000,0};
+ mf.extinf.mediaview.list = resultlist.getwnd();
+ mf.extinf.mediaview.items = &itemCache;
+ pluginMessage message_build = {ML_IPC_MENUFUCKER_BUILD,(intptr_t)&mf,0};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_build, ML_IPC_SEND_PLUGIN_MESSAGE);
+
+ Menu_SetRatingValue(rate_hmenu, 0);
+ UpdateMenuItems(hwndDlg, menu, IDR_VIEW_ACCELERATORS);
+ int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL);
+
+ pluginMessage message_result = {ML_IPC_MENUFUCKER_RESULT,(intptr_t)&mf,r,0};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_result, ML_IPC_SEND_PLUGIN_MESSAGE);
+
+ switch (r)
+ {
+ case ID_MEDIAWND_PLAYSELECTEDFILES:
+ case ID_MEDIAWND_ENQUEUESELECTEDFILES:
+ playFiles((r == ID_MEDIAWND_ENQUEUESELECTEDFILES), 0);
+ break;
+ case ID_MEDIAWND_SELECTALL:
+ {
+ LVITEM item = {0};
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ }
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ removeSelectedItems(0);
+ break;
+ case ID_EDITITEMINFOS:
+ if (resultlist.GetSelectedCount() > 0)
+ editInfo(hwndDlg);
+ break;
+ case ID_PE_ID3:
+ fileInfoDialogs(hwndDlg);
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)hwndFrom, (LPARAM)TRUE);
+ break;
+ case IDC_REFRESH_METADATA:
+ RefreshMetadata(hwndDlg);
+ break;
+ case 0x1234: // all files from selected artist
+ {
+ wchar_t tmp[2048] = {0};
+ GayStringW escaped;
+ queryStrEscape(artist, escaped);
+
+ // Keeping this here in case we want to use the ini setting later:
+ //if ( g_viewnotplay ) // (BigG): Check to see if we should play or view the current tracks
+ //{
+ StringCchPrintfW(tmp, 2048, L"?artist = \"%s\"", escaped.Get());
+ SetWindowTextW(hwndSearchGlobal, tmp);
+ //}
+ //else // Otherwise do the old behavior and just play it back
+ //{
+ // StringCchPrintfW(tmp, 2048, L"artist = \"%s\"", escaped.Get());
+ // main_playQuery(g_view_metaconf, tmp, 0);
+ //}
+
+ SetWindowTextW(hwndSearchGlobal, tmp);
+ }
+ break;
+ case 0x1235: // all files from selected album
+ {
+ wchar_t tmp[2048] = {0};
+ GayStringW escaped;
+ queryStrEscape(album, escaped);
+
+ // Keeping this here in case we want to use the ini setting later:
+ //if ( g_viewnotplay ) // (BigG): Check to see if we should play or view the current tracks
+ //{
+ StringCchPrintfW(tmp, 2048, L"?album = \"%s\"", escaped.Get());
+ SetWindowTextW(hwndSearchGlobal, tmp);
+ //}
+ //else // Otherwise do the old behavior and just play it back
+ //{
+ // StringCchPrintfW(tmp, 2048, L"album = \"%s\"", escaped.Get());
+ // main_playQuery(g_view_metaconf, tmp, 0);
+ //}
+ }
+ break;
+ case ID_RATE_1:
+ case ID_RATE_2:
+ case ID_RATE_3:
+ case ID_RATE_4:
+ case ID_RATE_5:
+ case ID_RATE_0:
+ {
+ int rate = r - ID_RATE_1 + 1;
+ if (r == ID_RATE_0) rate = 0;
+ int x;
+ int has = 0;
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ for (x = 0; x < itemCache.Size; x ++)
+ {
+ if (resultlist.GetSelected(x))
+ {
+ if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[x].filename))
+ {
+ has++;
+ NDE_Scanner_Edit(s);
+ db_setFieldInt(s, MAINTABLE_ID_RATING, rate);
+ NDE_Scanner_Post(s);
+ itemCache.Items[x].rating = rate;
+ if (g_config->ReadInt(L"writeratings", 0))
+ {
+ wchar_t buf[64] = {0};
+ if (rate > 0)
+ {
+ wsprintfW(buf, L"%d", rate);
+ }
+ else
+ buf[0] = 0;
+ updateFileInfo(itemCache.Items[x].filename, DB_FIELDNAME_rating, buf);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+ }
+ }
+ }
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (g_table_dirty)
+ {
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (has)
+ {
+ ListView_RedrawItems(resultlist.getwnd(), 0, itemCache.Size - 1);
+ PostMessage(GetParent(hwndDlg), WM_APP + 4, (WPARAM)rate, (LPARAM)0);
+ }
+ }
+ break;
+ case ID_MEDIAWND_EXPLOREFOLDER:
+ exploreItemFolder(hwndDlg);
+ break;
+ case ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES:
+ removeDeadFiles(hwndDlg);
+ break;
+ case ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS:
+ RecycleSelectedItems();
+ break;
+ default:
+ {
+ if (cloud_hmenu && (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER)) // deals with cloud specific menus
+ {
+ // 0 = no change
+ // 1 = add cloud
+ // 2 = add local
+ // 4 = removed
+ int mode = 0;
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode);
+ int n = resultlist.GetSelectionMark();
+ if (n != -1)
+ {
+ switch (mode)
+ {
+ case 1:
+ setCloudValue(&itemCache.Items[n], L"5");
+ break;
+
+ case 2:
+ setCloudValue(&itemCache.Items[n], L"0");
+ break;
+
+ case 4:
+ setCloudValue(&itemCache.Items[n], L"4");
+ break;
+ }
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);
+ }
+ break;
+ }
+
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ {
+ // build my data.
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLISTW;
+ itemRecordListW myObj = {0, };
+ copyFilesToItemCacheW(&myObj); // does not dupe strings
+ s.data = (void*) & myObj;
+ LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & s, IPC_LIBRARY_SENDTOMENU);
+ if (result != 1)
+ {
+ s.mode = 3;
+ s.data_type = ML_TYPE_ITEMRECORDLIST;
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &myObj);
+ s.data = (void*) & objA;
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
+ freeRecordList(&objA);
+ }
+ _aligned_free(myObj.Items);
+ }
+ }
+ break;
+ }
+ }
+
+ if (s.mode)
+ {
+ s.mode = 4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+ sendto_hmenu = 0;
+ DestroyMenu(cloud_hmenu);
+ cloud_hmenu = 0;
+ DestroyMenu(globmenu);
+ UpdateWindow(hwndFrom);
+ EatKeyboard();
+}
+
+static BOOL ListView_OnFindItem(HWND hwndDlg, NMLVFINDITEMW *pfi, LRESULT *pResult, UINT uMsg)
+{
+ if (bgThread_Handle) return FALSE;
+
+ int i = pfi->iStart;
+ if (i >= itemCache.Size) i = 0;
+
+ int cnt = itemCache.Size - i;
+ if (pfi->lvfi.flags & LVFI_WRAP) cnt += i;
+
+ int by = g_view_metaconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST);
+
+ while (cnt-- > 0)
+ {
+ itemRecordW *thisitem = itemCache.Items + i;
+ wchar_t tmp[128] = {0};
+ wchar_t *name = 0;
+
+ switch (by)
+ {
+ case MEDIAVIEW_COL_FILENAME:
+ name = thisitem->filename + wcslen(thisitem->filename);
+ while (name >= thisitem->filename && *name != L'/' && *name != L'\\') name--;
+ break;
+ case MEDIAVIEW_COL_FULLPATH:
+ name = thisitem->filename;
+ break;
+ case MEDIAVIEW_COL_EXTENSION:
+ name = PathFindExtensionW(thisitem->filename);
+ if (name && *name)
+ name++;
+ break;
+ case MEDIAVIEW_COL_TITLE: name = thisitem->title; break;
+ case MEDIAVIEW_COL_COMMENT: name = thisitem->comment; break;
+ case MEDIAVIEW_COL_ARTIST: name = thisitem->artist; break;
+ case MEDIAVIEW_COL_ALBUM: name = thisitem->album; break;
+ case MEDIAVIEW_COL_GENRE: name = thisitem->genre; break;
+ case MEDIAVIEW_COL_YEAR:
+ tmp[0] = 0;
+ if (thisitem->year >= 0) StringCchPrintfW(tmp, 128, L"%04d", thisitem->year);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_TRACK:
+ tmp[0] = 0;
+ if (thisitem->track > 0)
+ {
+ if (thisitem->tracks > 0) StringCchPrintfW(tmp, 128, L"%d/%d", thisitem->track, thisitem->tracks);
+ else StringCchPrintfW(tmp, 128, L"%d", thisitem->track);
+ }
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_LENGTH:
+ tmp[0] = 0;
+ if (thisitem->length >= 0) StringCchPrintfW(tmp, 128, L"%d:%02d", thisitem->length / 60, thisitem->length % 60);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_RATING:
+ tmp[0] = 0;
+ if (thisitem->rating > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->rating);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_CLOUD:
+ {
+ tmp[0] = 0;
+ wchar_t *x = getRecordExtendedItem_fast(thisitem, extended_fields.cloud);
+ if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x);
+ name = tmp;
+ break;
+ }
+ case MEDIAVIEW_COL_PLAYCOUNT:
+ tmp[0] = 0;
+ if (thisitem->playcount > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->playcount);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_BITRATE:
+ if (thisitem->bitrate > 0)
+ StringCchPrintfW(name = tmp, 128, L"%d%s", thisitem->bitrate, WASABI_API_LNGSTRINGW(IDS_KBPS));
+ break;
+ case MEDIAVIEW_COL_BPM:
+ if (thisitem->bpm > 0)
+ StringCchPrintfW(name = tmp, 128, L"%d", thisitem->bpm);
+ break;
+ case MEDIAVIEW_COL_TYPE:
+ name = WASABI_API_LNGSTRINGW(thisitem->type ? IDS_VIDEO : IDS_AUDIO);
+ break;
+ case MEDIAVIEW_COL_DISC:
+ tmp[0] = 0;
+ if (thisitem->disc > 0)
+ {
+ if (thisitem->discs > 0)
+ StringCchPrintfW(tmp, 128, L"%d/%d", thisitem->disc, thisitem->discs);
+ else
+ StringCchPrintfW(tmp, 128, L"%d", thisitem->disc);
+ }
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_ALBUMARTIST:
+ name = thisitem->albumartist;
+ break;
+ case MEDIAVIEW_COL_PUBLISHER:
+ name = thisitem->publisher;
+ break;
+ case MEDIAVIEW_COL_COMPOSER:
+ name = thisitem->composer;
+ break;
+ case MEDIAVIEW_COL_ALBUMGAIN:
+ name = thisitem->replaygain_album_gain;
+ break;
+ case MEDIAVIEW_COL_TRACKGAIN:
+ name = thisitem->replaygain_track_gain;
+ break;
+ case MEDIAVIEW_COL_FILESIZE:
+ tmp[0] = 0;
+ if (thisitem->filesize > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->filesize);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_FILETIME:
+ tmp[0] = 0;
+ if (thisitem->filetime > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->filetime);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_LASTUPD:
+ tmp[0] = 0;
+ if (thisitem->lastupd > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->lastupd);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_DATEADDED:
+ {
+ tmp[0] = 0;
+ wchar_t *x = getRecordExtendedItem_fast(thisitem,extended_fields.dateadded);
+ if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x);
+ name = tmp;
+ break;
+ }
+ case MEDIAVIEW_COL_LASTPLAY:
+ tmp[0] = 0;
+ if (thisitem->lastplay > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->lastplay);
+ name = tmp;
+ break;
+ case MEDIAVIEW_COL_ISPODCAST:
+ {
+ wchar_t * t = getRecordExtendedItem_fast(thisitem, extended_fields.ispodcast);
+ name = WASABI_API_LNGSTRINGW(t && wcscmp(t,L"1") ? IDS_PODCAST : IDS_NON_PODCAST);
+ }
+ break;
+ case MEDIAVIEW_COL_PODCASTCHANNEL:
+ name = getRecordExtendedItem_fast(thisitem,extended_fields.podcastchannel);
+ break;
+ case MEDIAVIEW_COL_PODCASTPUBDATE:
+ {
+ tmp[0] = 0;
+ wchar_t *x = getRecordExtendedItem_fast(thisitem,extended_fields.podcastpubdate);
+ if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x);
+ name = tmp;
+ }
+ break;
+ case MEDIAVIEW_COL_CATEGORY:
+ name = thisitem->category;
+ break;
+ case MEDIAVIEW_COL_DIRECTOR:
+ name = getRecordExtendedItem_fast(thisitem,extended_fields.director);
+ break;
+ case MEDIAVIEW_COL_PRODUCER:
+ name = getRecordExtendedItem_fast(thisitem,extended_fields.producer);
+ break;
+ case MEDIAVIEW_COL_DIMENSION:
+ {
+ tmp[0] = 0;
+ wchar_t *w = getRecordExtendedItem_fast(thisitem,extended_fields.width);
+ wchar_t *h = getRecordExtendedItem_fast(thisitem,extended_fields.height);
+ if (w && *w && h && *h) StringCchPrintfW(tmp, 128, L"%dx%d", _wtoi(w), _wtoi(h));
+ name = tmp;
+ break;
+ }
+ }
+
+ if (!name) name = L"";
+ else SKIP_THE_AND_WHITESPACEW(name)
+
+ if (pfi->lvfi.flags & (4 | LVFI_PARTIAL))
+ {
+ if (!StrCmpNIW(name, pfi->lvfi.psz, wcslen(pfi->lvfi.psz)))
+ {
+ *pResult = i;
+ return TRUE;
+ }
+ }
+ else if (pfi->lvfi.flags & LVFI_STRING)
+ {
+ if (!lstrcmpiW(name, pfi->lvfi.psz))
+ {
+ *pResult = i;
+ return TRUE;
+ }
+ }
+ else
+ {
+ *pResult = i;
+ return TRUE;
+ }
+ if (++i == itemCache.Size) i = 0;
+ }
+ *pResult = i;
+ return TRUE;
+}
+static BOOL ListView_OnGetDispInfo(HWND hwndDlg, NMLVDISPINFOW* pdi, UINT uMsg)
+{
+ LVITEMW *pItem;
+ itemRecordW *pRec;
+
+ pItem = &pdi->item;
+
+ if (bgThread_Handle)
+ {
+ if (0 == pItem->iItem && 0 == pItem->iSubItem && (LVIF_TEXT & pItem->mask))
+ {
+ static char bufpos = 0;
+ static int buflen = 17;
+ static wchar_t buffer[64];//L"Scanning _\0/-\\|";
+ if (!buffer[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_PLAIN,buffer,54);
+ StringCchCatW(buffer,64,L" _");
+ StringCchCatW(buffer+(buflen=lstrlenW(buffer)+1),64,L"/-\\|");
+ buflen+=4;
+ }
+ int pos = buflen - 5;;
+ buffer[pos - 1] = buffer[pos + (bufpos++&3) + 1];
+ pItem->pszText = buffer;
+ }
+ return FALSE;
+ }
+
+ if (pItem->iItem < 0 || pItem->iItem >= itemCache.Size) return FALSE;
+
+ pRec = itemCache.Items + pItem->iItem;
+
+ if (LVIF_TEXT & pItem->mask)
+ {
+ switch (columnOrder[pItem->iSubItem])
+ {
+ case MEDIAVIEW_COL_FILENAME: // show filename (Tho we'll show without the path cause paths are gay)
+ pItem->pszText = PathFindFileNameW(pRec->filename);
+ break;
+ case MEDIAVIEW_COL_FULLPATH: // show filename (Tho we'll show without the path cause paths are gay)
+ pItem->pszText = pRec->filename;
+ break;
+ case MEDIAVIEW_COL_EXTENSION:
+ pItem->pszText = PathFindExtensionW(pRec->filename);
+ if (pItem->pszText && *pItem->pszText) pItem->pszText++;
+ break;
+ case MEDIAVIEW_COL_TITLE:
+ pItem->pszText = pRec->title;
+ break;
+ case MEDIAVIEW_COL_COMMENT:
+ pItem->pszText = pRec->comment;
+ break;
+ case MEDIAVIEW_COL_ALBUM:
+ pItem->pszText = pRec->album;
+ break;
+ case MEDIAVIEW_COL_ARTIST:
+ pItem->pszText = pRec->artist;
+ break;
+ case MEDIAVIEW_COL_TRACK:
+ if (pRec->track > 0)
+ {
+ if (pRec->tracks > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d/%d", pRec->track, pRec->tracks);
+ else StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->track);
+ }
+ break;
+ case MEDIAVIEW_COL_GENRE:
+ pItem->pszText = pRec->genre;
+ break;
+ case MEDIAVIEW_COL_YEAR:
+ if (pRec->year >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%04d", pRec->year);
+ break;
+ case MEDIAVIEW_COL_LENGTH:
+ if (pRec->length >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d:%02d", pRec->length / 60, pRec->length % 60);
+ break;
+ case MEDIAVIEW_COL_RATING:
+ if (0 == pItem->iSubItem && ratingBackText[0]) pItem->pszText = ratingBackText;
+ else if (pRec->rating > 0 && pRec->rating <= 5) _itow(pRec->rating, pItem->pszText, 10);
+ break;
+ case MEDIAVIEW_COL_CLOUD:
+ {
+ wchar_t *t = getRecordExtendedItem_fast(pRec, extended_fields.cloud);
+ if (t && *t) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", _wtoi(t));
+ break;
+ }
+ case MEDIAVIEW_COL_PLAYCOUNT:
+ if (pRec->playcount >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->playcount);
+ else pItem->pszText = L"0";
+ break;
+ case MEDIAVIEW_COL_DISC:
+ if (pRec->disc > 0)
+ {
+ if (pRec->discs > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d/%d", pRec->disc, pRec->discs);
+ else StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->disc);
+ }
+ break;
+ case MEDIAVIEW_COL_ALBUMARTIST:
+ pItem->pszText = pRec->albumartist;
+ break;
+ case MEDIAVIEW_COL_PUBLISHER:
+ pItem->pszText = pRec->publisher;
+ break;
+ case MEDIAVIEW_COL_COMPOSER:
+ pItem->pszText = pRec->composer;
+ break;
+ case MEDIAVIEW_COL_ALBUMGAIN:
+ pItem->pszText = pRec->replaygain_album_gain;
+ break;
+ case MEDIAVIEW_COL_TRACKGAIN:
+ pItem->pszText = pRec->replaygain_track_gain;
+ break;
+ case MEDIAVIEW_COL_TYPE:
+ pItem->pszText = WASABI_API_LNGSTRINGW((pRec->type) ? IDS_VIDEO : IDS_AUDIO);
+ break;
+ case MEDIAVIEW_COL_BITRATE:
+ if (pRec->bitrate > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d%s", pRec->bitrate, WASABI_API_LNGSTRINGW(IDS_KBPS));
+ break;
+ case MEDIAVIEW_COL_BPM:
+ if (pRec->bpm > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->bpm);
+ break;
+ case MEDIAVIEW_COL_FILESIZE:
+ if (pRec->filesize != -1) WASABI_API_LNG->FormattedSizeString(pItem->pszText, pItem->cchTextMax, pRec->filesize);
+ break;
+ case MEDIAVIEW_COL_FILETIME:
+ MakeDateString(pRec->filetime, pItem->pszText, pItem->cchTextMax);
+ break;
+ case MEDIAVIEW_COL_LASTUPD:
+ MakeDateString(pRec->lastupd, pItem->pszText, pItem->cchTextMax);
+ break;
+ case MEDIAVIEW_COL_DATEADDED:
+ {
+ wchar_t * t = getRecordExtendedItem_fast(pRec,extended_fields.dateadded);
+ if (t && *t) MakeDateString(_wtoi64(t), pItem->pszText, pItem->cchTextMax);
+ break;
+ }
+ case MEDIAVIEW_COL_LASTPLAY:
+ if (pRec->lastplay > 0) MakeDateString(pRec->lastplay, pItem->pszText, pItem->cchTextMax);
+ break;
+ case MEDIAVIEW_COL_ISPODCAST:
+ {
+ wchar_t *t = getRecordExtendedItem_fast(pRec, extended_fields.ispodcast);
+ pItem->pszText = WASABI_API_LNGSTRINGW((t && t[0] == L'1' && !t[1]) ? IDS_PODCAST : IDS_NON_PODCAST);
+ break;
+ }
+ case MEDIAVIEW_COL_PODCASTCHANNEL:
+ pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.podcastchannel);
+ break;
+ case MEDIAVIEW_COL_PODCASTPUBDATE:
+ {
+ wchar_t * t = getRecordExtendedItem_fast(pRec,extended_fields.podcastpubdate);
+ if (t && *t) MakeDateString(_wtoi64(t), pItem->pszText, pItem->cchTextMax);
+ break;
+ }
+ case MEDIAVIEW_COL_CATEGORY:
+ pItem->pszText = pRec->category;
+ break;
+ case MEDIAVIEW_COL_DIRECTOR:
+ pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.director);
+ break;
+ case MEDIAVIEW_COL_PRODUCER:
+ pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.producer);
+ break;
+ case MEDIAVIEW_COL_DIMENSION:
+ {
+ wchar_t *w = getRecordExtendedItem_fast(pRec,extended_fields.width);
+ wchar_t *h = getRecordExtendedItem_fast(pRec,extended_fields.height);
+ if (w && *w && h && *h) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%dx%d", _wtoi(w), _wtoi(h));
+ break;
+ }
+ }
+ if (!pItem->pszText) pItem->pszText = L"";
+ }
+ return FALSE;
+}
+static BOOL ListView_OnColumnClick(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ int l_sc = g_view_metaconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST);
+ int l_sd = g_view_metaconf->ReadInt(L"mv_sort_dir", 0);
+ if (columnOrder[pnmv->iSubItem] == l_sc) l_sd = !l_sd;
+ else
+ {
+ /* JF> I like this better when the direction doesnt get reset every
+ time you choose a new column. To revert to old behavior, uncomment
+ this line:
+ l_sd=0;
+ */
+ l_sc = columnOrder[pnmv->iSubItem];
+ }
+
+ g_view_metaconf->WriteInt(L"mv_sort_by", l_sc);
+ g_view_metaconf->WriteInt(L"mv_sort_dir", l_sd);
+ MLSkinnedListView_DisplaySort(pnmv->hdr.hwndFrom, pnmv->iSubItem, !l_sd);
+ sortResults(g_view_metaconf, &itemCache);
+ resultlist.SetVirtualCount(0);
+ resultlist.SetVirtualCount(itemCache.Size); // TODO: we could set a limit here
+ ListView_RedrawItems(pnmv->hdr.hwndFrom, 0, itemCache.Size - 1);
+ return FALSE;
+}
+static BOOL ListView_OnBeginDrag(HWND hwndDlg, NMLISTVIEW *pnmv)
+{
+ switch (columnOrder[pnmv->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ ratingColumn.hwndList = pnmv->hdr.hwndFrom;
+ ratingColumn.fStyle = RCS_DEFAULT;
+ ratingColumn.iItem = pnmv->iItem;
+ ratingColumn.iSubItem = pnmv->iSubItem;
+ ratingColumn.value = ((UINT)pnmv->iItem < (UINT)itemCache.Size) ? itemCache.Items[pnmv->iItem].rating : 0;
+ MLRatingColumn_BeginDrag(plugin.hwndLibraryParent, &ratingColumn);
+ break;
+ }
+
+ SetCapture(hwndDlg);
+ return FALSE;
+}
+
+static BOOL ListView_OnReturn(HWND hwndDlg, NMHDR *pnmh)
+{
+ SendMessage(hwndDlg, WM_COMMAND, ((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^(!!g_config->ReadInt(L"enqueuedef", 0)))
+ ? IDC_BUTTON_ENQUEUE : IDC_BUTTON_PLAY, 0);
+ return FALSE;
+}
+
+static BOOL ListView_OnCustomDraw(HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static BOOL bDrawFocus;
+ static RECT rcView;
+ static RATINGCOLUMNPAINT ratingColumnPaint;
+ static CLOUDCOLUMNPAINT cloudColumnPaint;
+
+ *pResult = CDRF_DODEFAULT;
+
+ switch (plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ *pResult |= CDRF_NOTIFYITEMDRAW;
+ CopyRect(&rcView, &plvcd->nmcd.rc);
+
+ ratingColumnPaint.fStyle = RCS_DEFAULT;
+ ratingColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ ratingColumnPaint.hdc = plvcd->nmcd.hdc;
+ ratingColumnPaint.prcView = &rcView;
+
+ cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ cloudColumnPaint.hdc = plvcd->nmcd.hdc;
+ cloudColumnPaint.prcView = &rcView;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ *pResult |= CDRF_NOTIFYSUBITEMDRAW;
+ bDrawFocus = (CDIS_FOCUS & plvcd->nmcd.uItemState);
+ if (bDrawFocus)
+ {
+ plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
+ *pResult |= CDRF_NOTIFYPOSTPAINT;
+ }
+ return TRUE;
+ case CDDS_ITEMPOSTPAINT:
+ if (bDrawFocus)
+ {
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc);
+ rc.left += 3;
+ DrawFocusRect(plvcd->nmcd.hdc, &rc);
+ plvcd->nmcd.uItemState |= CDIS_FOCUS;
+ bDrawFocus = FALSE;
+ }
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+
+ case(CDDS_SUBITEM | CDDS_ITEMPREPAINT):
+ switch (columnOrder[plvcd->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ if (bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break;
+ ratingColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ ratingColumnPaint.iSubItem = plvcd->iSubItem;
+ ratingColumnPaint.value = (plvcd->nmcd.dwItemSpec >= 0 && plvcd->nmcd.dwItemSpec < (UINT)itemCache.Size) ? itemCache.Items[plvcd->nmcd.dwItemSpec].rating : 0;
+ ratingColumnPaint.prcItem = &plvcd->nmcd.rc;
+ ratingColumnPaint.rgbBk = plvcd->clrTextBk;
+ ratingColumnPaint.rgbFg = plvcd->clrText;
+
+ if (MLRatingColumn_Paint(plugin.hwndLibraryParent, &ratingColumnPaint))
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ break;
+
+ case MEDIAVIEW_COL_CLOUD:
+ if (bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break;
+
+ int icon = 4;
+ wchar_t *t = getRecordExtendedItem_fast(&itemCache.Items[plvcd->nmcd.dwItemSpec], extended_fields.cloud);
+ if (t && *t) icon = _wtoi(t);
+
+ cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ cloudColumnPaint.iSubItem = plvcd->iSubItem;
+ cloudColumnPaint.value = icon;
+ cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
+ cloudColumnPaint.rgbBk = plvcd->clrTextBk;
+ cloudColumnPaint.rgbFg = plvcd->clrText;
+
+ if (MLCloudColumn_Paint(plugin.hwndLibraryParent, &cloudColumnPaint))
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static BOOL ListView_OnClick(HWND hwnDlg, NMITEMACTIVATE *pnmitem)
+{
+ if (bgThread_Handle) return FALSE;
+
+ if (pnmitem->iItem != -1)
+ {
+ switch (columnOrder[pnmitem->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ ratingColumn.hwndList = pnmitem->hdr.hwndFrom;
+ ratingColumn.ptAction = pnmitem->ptAction;
+ ratingColumn.bRedrawNow = TRUE;
+ ratingColumn.fStyle = RCS_DEFAULT;
+ if (!MLRatingColumn_Click(plugin.hwndLibraryParent, &ratingColumn)) return FALSE;
+ SetRating(ratingColumn.iItem, ratingColumn.value, ratingColumn.hwndList);
+ break;
+
+ case MEDIAVIEW_COL_CLOUD:
+ {
+ RECT itemRect = {0};
+ if (pnmitem->iSubItem)
+ ListView_GetSubItemRect(pnmitem->hdr.hwndFrom, pnmitem->iItem, pnmitem->iSubItem, LVIR_BOUNDS, &itemRect);
+ else
+ {
+ ListView_GetItemRect(pnmitem->hdr.hwndFrom, pnmitem->iItem, &itemRect, LVIR_BOUNDS);
+ itemRect.right = itemRect.left + ListView_GetColumnWidth(pnmitem->hdr.hwndFrom, pnmitem->iSubItem);
+ }
+
+ MapWindowPoints(pnmitem->hdr.hwndFrom, HWND_DESKTOP, (POINT*)&itemRect, 2);
+
+ HMENU cloud_menu = CreatePopupMenu();
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)itemCache.Items[pnmitem->iItem].filename, (intptr_t)&cloud_menu);
+ if (cloud_menu)
+ {
+ int r = DoTrackPopup(cloud_menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, itemRect.right, itemRect.top, pnmitem->hdr.hwndFrom, NULL);
+ if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER)
+ {
+ // 0 = no change
+ // 1 = adding to cloud
+ // 2 = added locally
+ // 4 = removed
+ int mode = 0; // deals with cloud specific menus
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode);
+ switch (mode)
+ {
+ case 1:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"5");
+ break;
+
+ case 2:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+
+ case 4:
+ setCloudValue(&itemCache.Items[pnmitem->iItem], L"4");
+ break;
+ }
+
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);
+ }
+ DestroyMenu(cloud_menu);
+ }
+ }
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static BOOL ListView_OnHotTrack(HWND hwndDlg, NMLISTVIEW *pnmlv, LRESULT *pResult)
+{
+ UINT iItem;
+
+ if (bgThread_Handle)
+ {
+ pnmlv->iItem = -1;
+ *pResult = TRUE;
+ return TRUE;
+ }
+
+ if (-1 == pnmlv->iItem && 0 == pnmlv->iSubItem)
+ {
+ LVHITTESTINFO lvhit;
+ lvhit.pt = pnmlv->ptAction;
+ SendMessageW(pnmlv->hdr.hwndFrom, LVM_HITTEST, 0, (LPARAM)&lvhit);
+ iItem = lvhit.iItem;
+ }
+ else iItem = pnmlv->iItem;
+
+ switch (columnOrder[pnmlv->iSubItem])
+ {
+ case MEDIAVIEW_COL_RATING:
+ ratingColumn.hwndList = pnmlv->hdr.hwndFrom;
+ ratingColumn.fStyle = RCS_DEFAULT;
+ ratingColumn.iItem = iItem;
+ ratingColumn.iSubItem = pnmlv->iSubItem;
+ ratingColumn.value = (iItem < (UINT)itemCache.Size) ? itemCache.Items[iItem].rating : 0;
+ ratingColumn.ptAction = pnmlv->ptAction;
+ ratingColumn.bRedrawNow = TRUE;
+ MLRatingColumn_Track(plugin.hwndLibraryParent, &ratingColumn);
+ break;
+ }
+
+ // LVS_EX_ONECLICKACTIVATE enabled - make listview select nothing
+ pnmlv->iItem = -1;
+ *pResult = TRUE;
+ return TRUE;
+}
+
+/////////// Dialog Messages / Notifications
+static void Dialog_OnDisplayChange(HWND hwndDlg)
+{
+ INT i;
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ for (i = 0; -1 != columnOrder[i] && MEDIAVIEW_COL_RATING != columnOrder[i] && MEDIAVIEW_COL_CLOUD != columnOrder[i]; i++);
+ if (-1 != columnOrder[i])
+ {
+ if (hwndList)
+ {
+ INT w = (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L);
+ SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, i, (LPARAM)w);
+ }
+ }
+ LayoutWindows(hwndDlg, TRUE);
+}
+
+static wchar_t tt_buf[256] = {L""};
+static int last_item = -1, last_icon = -1;
+LRESULT pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (uMsg == WM_NOTIFY)
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ switch (l->code)
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ int cloudcol = (int)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == cloudcol)
+ {
+ RECT r = {0};
+ if (lvh.iSubItem)
+ ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r);
+ else
+ {
+ ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS);
+ r.right = r.left + ListView_GetColumnWidth(hwnd, cloudcol);
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2);
+ SetWindowPos(l->hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ int cloudcol = (int)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == cloudcol)
+ {
+ LPNMTTDISPINFOW lpnmtdi = (LPNMTTDISPINFOW)lParam;
+ int icon = 4;
+ wchar_t *t = getRecordExtendedItem_fast(&itemCache.Items[lvh.iItem], extended_fields.cloud);
+ if (t && *t) icon = _wtoi(t);
+
+ if (last_item == lvh.iItem && last_icon == icon)
+ {
+ lpnmtdi->lpszText = tt_buf;
+ return 0;
+ }
+
+ if (icon == 4)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (icon == 5)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOADING_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else
+ {
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRACK_AVAILABLE, tt_buf, ARRAYSIZE(tt_buf));
+
+ nx_string_t *out_devicenames = 0;
+ size_t num_names = mlplugin->MessageProc(0x405, (INT_PTR)itemCache.Items[lvh.iItem].filename, (INT_PTR)&out_devicenames, 0);
+ if (num_names > 0)
+ {
+ for (size_t i = 0; i < num_names; i++)
+ {
+ if (i > 0) StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), L", ");
+ StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), out_devicenames[i]->string);
+ }
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ if (out_devicenames)
+ free(out_devicenames);
+ }
+ }
+ }
+ }
+ last_item = lvh.iItem;
+ last_icon = icon;
+ lpnmtdi->lpszText = tt_buf;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+void Dialog_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL Dialog_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0)
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem(hwndDlg, buttonId);
+ GetWindowRect(buttonHWND, &r);
+ UpdateMenuItems(hwndDlg, menu, IDR_VIEW_ACCELERATORS);
+ MLSkinnedButton_SetDropDownState(buttonHWND, TRUE);
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if (!(flags & BPM_WM_COMMAND)) tpmFlags |= TPM_RETURNCMD;
+ int x = DoTrackPopup(menu, tpmFlags, r.left, r.top, hwndDlg, NULL);
+ if ((flags & BPM_ECHO_WM_COMMAND) && x)
+ SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(x, 0), 0);
+ MLSkinnedButton_SetDropDownState(buttonHWND, FALSE);
+ return x;
+}
+
+static void Dialog_Play(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(g_context_menus2, 0);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ Dialog_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+static INT_PTR Dialog_OnInit(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
+{
+ // benski> this is just going here because it's very likely to get called. will only get compiled in debug mode.
+ assert(sizeof(extra_idsW) / sizeof(*extra_idsW) == sizeof(extra_strsW) / sizeof(*extra_strsW));
+
+ g_displaysearch = !(BOOL)lParam;
+
+ // Set the hwnd for the search to a global only if the multipane view hasnt set it yet, as it takes precedence to which box gets populated with the query
+ //if (!IsWindow(hwndSearchGlobal))
+ hwndSearchGlobal = GetDlgItem(hwndDlg, IDC_QUICKSEARCH);
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ HWND hwndList;
+ FLICKERFIX ff;
+ INT index;
+ INT ffcl[] = { IDC_CLEAR,
+ IDC_BUTTON_PLAY,
+ IDC_BUTTON_ENQUEUE,
+ IDC_BUTTON_MIX,
+ IDC_BUTTON_INFOTOGGLE,
+ IDC_BUTTON_CREATEPLAYLIST,
+ IDC_MIXABLE,
+ IDC_MEDIASTATUS,
+ };
+
+ m_hwnd = hwndDlg;
+ m_bgupdinfoviewerflag = 0;
+ columnOrder[0] = -1;
+ last_item = -1;
+ tt_buf[0] = 0;
+
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ int64_t *out_ids = 0;
+ nx_string_t *out_filenames = 0;
+ size_t num_files = mlplugin->MessageProc(0x404, (INT_PTR)&out_filenames, (INT_PTR)&out_ids, 0xDEADBEEF);
+ for (size_t i = 0; i < num_files; i++)
+ {
+ cloudFiles.push_back((wchar_t *)out_filenames[i]->string);
+ }
+ if (out_filenames)
+ {
+ free(out_filenames);
+ out_filenames = 0;
+ }
+ if (out_ids)
+ {
+ free(out_ids);
+ out_ids = 0;
+ }
+
+ HWND ml_pmp_window = FindWindowW(L"ml_pmp_window", NULL);
+ if (IsWindow(ml_pmp_window))
+ {
+ SendMessage(ml_pmp_window, WM_PMP_IPC, (WPARAM)&cloudUploading, PMP_IPC_GETCLOUDTRANSFERS);
+ wchar_t a[32] = {0};
+ StringCchPrintfW(a, 32, L"%d", cloudUploading.size());
+ }
+ }
+ }
+ }
+
+ EnterCriticalSection(&g_db_cs);
+ if (!m_media_scanner) m_media_scanner = NDE_Table_CreateScanner(g_table);
+ LeaveCriticalSection(&g_db_cs);
+
+ itemCache.Items = 0;
+ itemCache.Alloc = 0;
+ itemCache.Size = 0;
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (IsWindow(hwndList))
+ {
+ resultlist.setwnd(hwndList);
+ resultlist.ForceUnicode();
+
+ DWORD styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE; /*LVS_EX_ONECLICKACTIVATE - needed to hottracking work prior WinXp */
+ SendMessageW(hwndList, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
+
+ HWND hwndHeader = (HWND)SendMessage(hwndList, LVM_GETHEADER, 0, 0L);
+ if (IsWindow(hwndHeader)) SetWindowLongPtrW(hwndHeader, GWLP_ID, IDC_LIST2HEADER);
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = hwndList;
+ skin.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ else resultlist.setwnd(NULL);
+
+ ff.mode = FFM_ERASEINPAINT;
+ for (index = 0; index < sizeof(ffcl) / sizeof(INT); index++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]);
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+
+ if (!g_displaysearch) // disable search box
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CLEAR), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_SEARCHCAPTION), SW_HIDE);
+ }
+
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1)
+ memcpy(columnOrder, defColumnOrder, sizeof(defColumnOrder));
+ else
+ memcpy(columnOrder, defColumnOrderCloud, sizeof(defColumnOrderCloud));
+ //Read the column order
+
+ Dialog_OnDisplayChange(hwndDlg);
+
+ int cloudcol = -1;
+ int l = g_view_metaconf->ReadInt(L"nbcolumns", 0);
+ if (l)
+ {
+ if (l > MAX_COLUMN_ORDER - 1) l = MAX_COLUMN_ORDER - 1;
+ index = 0;
+ for (; index < l; index++)
+ {
+ wchar_t tmp[128] = {0};
+ StringCchPrintfW(tmp, 128, L"column%d", index);
+ int v = g_view_metaconf->ReadInt(tmp, 0);
+ if (v == MEDIAVIEW_COL_CLOUD) cloudcol = index;
+ if (v < 0 || v >= MEDIAVIEW_COL_NUMS) v = 0;
+ columnOrder[index] = (BYTE)v;
+ }
+ columnOrder[index] = -1;
+ }
+
+ if (cloudcol == -1 && !g_view_metaconf->ReadInt(L"cloud", 1))
+ {
+ g_view_metaconf->WriteInt(L"cloud", 1);
+ for(int i = l; i != 0; i--)
+ {
+ columnOrder[i+1] = columnOrder[i];
+ if (i == 3)
+ {
+ columnOrder[i] = MEDIAVIEW_COL_CLOUD;
+ break;
+ }
+ }
+ }
+
+ if (!GetPropW(hwndList, L"pmp_list_proc")) {
+ SetPropW(hwndList, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(hwndList, GWLP_WNDPROC, (LONG_PTR)pmp_listview));
+ }
+ initColumnsHeader(hwndList);
+
+ char *pszTextA = (g_config->ReadInt(L"remembersearch", 0)) ? g_view_metaconf->ReadString("lastquery_utf8", "") : "";
+ AutoWide queryUnicode(pszTextA, CP_UTF8);
+ SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, queryUnicode);
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ doQuery(hwndDlg, queryUnicode, 0);
+
+ updateInfoText(hwndDlg);
+
+ search_oldWndProc = (WNDPROC)SetWindowLongPtrW(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), GWLP_WNDPROC, (LONG_PTR)search_newWndProc);
+
+ if (g_table && !NDE_Table_GetRecordsCount(g_table) && !g_config->ReadInt(L"noshowadddlg", 0))
+ {
+ SetTimer(hwndDlg, 5050, 1000, NULL);
+ }
+
+ groupBtn = g_config->ReadInt(L"groupbtn", 1);
+ enqueuedef = (g_config->ReadInt(L"enqueuedef", 0) == 1);
+
+ /// detect predixis
+ predixisExist = FALSE;
+ //predixis out - begin
+ //pluginMessage p = {ML_MSG_PDXS_STATUS, (INT_PTR)"test", 0, 0};
+ //pszTextA = (char *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ //predixisExist = (pszTextA != NULL && pszTextA[0] != 0);
+ //predixis out - end
+
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_MIX, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_local"};
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ customAllowed = TRUE;
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_MIX, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_MIX), (!customAllowed ? SW_HIDE : SW_SHOW));
+ ShowWindow(GetDlgItem(hwndDlg, IDC_MIXABLE), (!(predixisExist & 1) ? SW_HIDE : SW_SHOW));
+
+ MLSKINWINDOW m = {0};
+ m.hwndToSkin = hwndDlg;
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+
+ m.skinType = SKINNEDWND_TYPE_BUTTON;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0);
+
+ const int buttonids[] = {IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX};
+ for (size_t i=0;i!=sizeof(buttonids)/sizeof(buttonids[0]);i++)
+ {
+ m.hwndToSkin = GetDlgItem(hwndDlg, buttonids[i]);
+ if (IsWindow(m.hwndToSkin)) MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+
+ const int buttonidz[] = {IDC_SEARCHCAPTION, IDC_QUICKSEARCH, IDC_MEDIASTATUS, IDC_CLEAR, IDC_BUTTON_INFOTOGGLE, IDC_BUTTON_CREATEPLAYLIST, IDC_MIXABLE};
+ for (size_t i=0;i!=sizeof(buttonidz)/sizeof(buttonidz[0]);i++)
+ {
+ m.hwndToSkin = GetDlgItem(hwndDlg, buttonidz[i]);
+ if (IsWindow(m.hwndToSkin)) MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+
+ Dialog_UpdateButtonText(hwndDlg, enqueuedef);
+ return FALSE;
+}
+
+static BOOL Dialog_OnNotify(HWND hwndDlg, INT idCtrl, NMHDR* pnmh, LRESULT *pResult)
+{
+ switch (pnmh->idFrom)
+ {
+ case IDC_LIST2:
+ switch (pnmh->code)
+ {
+ case LVN_ITEMCHANGED: return ListView_OnItemChanged(hwndDlg, (NMLISTVIEW*)pnmh);
+ case NM_DBLCLK: return ListView_OnDoubleClick(hwndDlg, (NMITEMACTIVATE*)pnmh);
+ case LVN_ODFINDITEMA:
+ case LVN_ODFINDITEMW: return ListView_OnFindItem(hwndDlg, (NMLVFINDITEMW*)pnmh, pResult, pnmh->code);
+ case LVN_GETDISPINFOA:
+ case LVN_GETDISPINFOW: return ListView_OnGetDispInfo(hwndDlg, (NMLVDISPINFOW*)pnmh, pnmh->code);
+ case LVN_COLUMNCLICK: return ListView_OnColumnClick(hwndDlg, (NMLISTVIEW*)pnmh);
+ case LVN_BEGINDRAG: return ListView_OnBeginDrag(hwndDlg, (NMLISTVIEW*)pnmh);
+ case NM_RETURN: return ListView_OnReturn(hwndDlg, pnmh);
+ case NM_CUSTOMDRAW: return ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)pnmh, pResult);
+ case LVN_HOTTRACK: return ListView_OnHotTrack(hwndDlg, (NMLISTVIEW*)pnmh, pResult);
+ case NM_CLICK: return ListView_OnClick(hwndDlg, (NMITEMACTIVATE*)pnmh);
+ }
+ break;
+ case IDC_LIST2HEADER:
+ switch (pnmh->code)
+ {
+ case NM_RCLICK: return Header_OnRightClick(hwndDlg, pnmh, pResult);
+ case HDN_ENDDRAG: return Header_OnEndDrag(hwndDlg, (NMHEADERW*)pnmh, pResult);
+ case HDN_ITEMCHANGINGA:
+ case HDN_ITEMCHANGINGW: return Header_OnItemChanging(hwndDlg, (NMHEADERW*)pnmh, pResult, pnmh->code);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static void Dialog_OnInitMenuPopup(HWND hwndDlg, HMENU hMenu, UINT nIndex, BOOL bSysMenu)
+{
+ if (hMenu && hMenu == s.build_hMenu && s.mode == 1)
+ {
+ myMenu = TRUE;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1)
+ s.mode = 2;
+ myMenu = FALSE;
+ }
+ if (rate_hmenu && hMenu == rate_hmenu)
+ {
+ int x;
+ int sel = 0;
+ for (x = 0; x < itemCache.Size; x ++)
+ {
+ if (resultlist.GetSelected(x))
+ {
+ int s = itemCache.Items[x].rating;
+ if (s == sel || !sel) sel = s;
+ if (s != sel) break;
+ }
+ }
+ if (-1 == sel) sel = 0;
+ Menu_SetRatingValue(rate_hmenu, sel);
+ }
+ if (cloud_hmenu && hMenu == cloud_hmenu)
+ {
+ int n = resultlist.GetSelectionMark();
+ if (n != -1 && !GetMenuItemCount(hMenu))
+ {
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)itemCache.Items[n].filename, (intptr_t)&cloud_hmenu);
+ }
+ }
+}
+
+static void Dialog_OnMouseMove(HWND hwndDlg, UINT nFlags, POINTS pts)
+{
+ if (GetCapture() == hwndDlg)
+ {
+ mlDropItemStruct m = {0};
+
+ POINTSTOPOINT(m.p, pts);
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&m.p, 1);
+
+ if (MLRatingColumn_Drag(plugin.hwndLibraryParent, &m.p)) return;
+
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+
+ }
+}
+static void Dialog_OnLButtonUp(HWND hwndDlg, UINT nFlags, POINTS pts)
+{
+ if (GetCapture() == hwndDlg)
+ {
+ mlDropItemStruct m = {0};
+
+ ReleaseCapture();
+
+ POINTSTOPOINT(m.p, pts);
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&m.p, 1);
+
+ ratingColumn.bCanceled = FALSE;
+ ratingColumn.ptAction = m.p;
+ ratingColumn.bRedrawNow = TRUE;
+
+ if (MLRatingColumn_EndDrag(plugin.hwndLibraryParent, &ratingColumn))
+ {
+ SetRating(ratingColumn.iItem, ratingColumn.value, ratingColumn.hwndList);
+ return;
+ }
+
+ m.type = ML_TYPE_ITEMRECORDLISTW;
+ m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+
+ if (m.result > 0) // try itemRecordListW
+ {
+ itemRecordListW myObj = {0, };
+ copyFilesToItemCacheW(&myObj);
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & myObj;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ _aligned_free(myObj.Items); // DO NOT empty this object, cause it doesnt own its data
+ }
+ else // if it didn't work, fall back to itemRecordList
+ {
+ m.type = ML_TYPE_ITEMRECORDLIST;
+ m.result = 0;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m);
+ if (m.result > 0)
+ {
+ itemRecordListW myObj = {0, };
+ copyFilesToItemCacheW(&myObj);
+
+ itemRecordList objA = {0, };
+ convertRecordList(&objA, &myObj);
+ m.flags = 0;
+ m.result = 0;
+ m.data = (void*) & objA;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
+ emptyRecordList(&objA);
+ freeRecordList(&objA);
+ _aligned_free(myObj.Items); // DO NOT empty this object, cause it doesnt own its data
+ }
+ }
+ }
+}
+
+class ItemRecordPlaylist : public ifc_playlist
+{
+public:
+ ItemRecordPlaylist(const itemRecordListW *_list)
+ {
+ list = _list;
+ }
+
+private:
+ size_t GetNumItems()
+ {
+ return list->Size;
+ }
+
+ size_t GetItem(size_t item, wchar_t *filename, size_t filenameCch)
+ {
+ if (item < (size_t)list->Size && list->Items[item].filename)
+ {
+ StringCchCopyW(filename, filenameCch, list->Items[item].filename);
+ return 1;
+ }
+ return 0;
+ }
+
+ size_t GetItemTitle(size_t item, wchar_t *title, size_t titleCch)
+ {
+ if (item < (size_t)list->Size && list->Items[item].filename)
+ {
+ TAG_FMT_EXT(list->Items[item].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&list->Items[item], title, titleCch, 0);
+ return 1;
+ }
+ return 0;
+ }
+
+ int GetItemLengthMilliseconds(size_t item)
+ {
+ if (item < (size_t)list->Size && list->Items[item].length>=0)
+ {
+ return list->Items[item].length * 1000;
+ }
+ return -1000;
+ }
+
+private:
+ const itemRecordListW *list;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS ItemRecordPlaylist
+START_DISPATCH;
+CB(IFC_PLAYLIST_GETNUMITEMS, GetNumItems)
+CB(IFC_PLAYLIST_GETITEM, GetItem)
+CB(IFC_PLAYLIST_GETITEMTITLE, GetItemTitle)
+CB(IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds)
+END_DISPATCH;
+#undef CBCLASS
+
+
+static void Dialog_OnCommand(HWND hwndDlg, UINT idCtrl, INT nCode, HWND hwndCtrl)
+{
+ if (GetFocus() != hwndSearchGlobal)
+ {
+ switch (idCtrl)
+ {
+ case IDC_CLEAR:
+ SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, L"");
+ break;
+ case IDC_BUTTON_INFOTOGGLE:
+ updateInfoText(hwndDlg, TRUE);
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE);
+ break;
+ case IDC_QUICKSEARCH:
+ if (nCode == EN_CHANGE)
+ {
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg, UPDATE_QUERY_TIMER_ID, g_querydelay, NULL);
+ }
+ break;
+ case ID_AUDIOWND_PLAYSELECTION:
+ case ID_QUERYWND_PLAYQUERY:
+ case IDC_BUTTON_PLAY:
+ case ID_MEDIAWND_PLAYSELECTEDFILES:
+ case ID_AUDIOWND_ENQUEUESELECTION:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_MEDIAWND_ENQUEUESELECTEDFILES:
+ case IDC_BUTTON_MIX:
+ {
+ if (nCode == MLBN_DROPDOWN)
+ {
+ Dialog_Play(hwndDlg, hwndCtrl, idCtrl);
+ }
+ else
+ {
+ int action;
+ if (idCtrl == IDC_BUTTON_PLAY || idCtrl == ID_MEDIAWND_PLAYSELECTEDFILES || idCtrl == ID_AUDIOWND_PLAYSELECTION)
+ {
+ action = (nCode == 1) ? g_config->ReadInt(L"enqueuedef", 0) == 1 : 0;
+ }
+ else if (idCtrl == IDC_BUTTON_ENQUEUE || idCtrl == ID_MEDIAWND_ENQUEUESELECTEDFILES || idCtrl == ID_AUDIOWND_ENQUEUESELECTION)
+ {
+ action = (nCode == 1) ? g_config->ReadInt(L"enqueuedef", 0) != 1 : 1;
+ }
+ else
+ break;
+
+ int i, l = itemCache.Size;
+ for (i = 0; i < l; i++) if (resultlist.GetSelected(i)) break;
+ playFiles(action/*idCtrl == IDC_BUTTON_ENQUEUE*/, i == l);
+ }
+ }
+ break;
+ case IDC_BUTTON_CREATEPLAYLIST:
+#if 0
+ // TODO consider exposing this option somehow...
+ if (AGAVE_API_PLAYLISTMANAGER) // This is the old Create Playlist button code
+ {
+ wchar_t fn[MAX_PATH] = {0};
+ wchar_t dir[MAX_PATH] = {0};
+ GetTempPathW(MAX_PATH,dir);
+ GetTempFileNameW(dir,L"ml_playlist",0,fn);
+ wcscat(fn,L".m3u8");
+
+ ItemRecordPlaylist playlist(&itemCache);
+ if (AGAVE_API_PLAYLISTMANAGER->Save(fn, &playlist) == PLAYLISTMANAGER_SUCCESS)
+ {
+ mlAddPlaylist p={sizeof(p),NULL,fn,PL_FLAGS_IMPORT,-1,-1};
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&p,ML_IPC_PLAYLIST_ADD);
+ DeleteFileW(fn);
+ }
+ }
+#endif
+ if (AGAVE_API_PLAYLIST_GENERATOR)
+ {
+ const int number_selected = resultlist.GetSelectedCount(); // Total selected
+
+ if (number_selected > 0)
+ {
+ itemRecordListW recordList; // Record list
+ itemRecordW *records = new itemRecordW[number_selected]; // Array of records
+
+ int selectedRecordcounter = 0;
+ for (int i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i)) // See if the current item is selected or not
+ {
+ records[selectedRecordcounter] = itemCache.Items[i]; // If its selected then add it to our itemlist
+ selectedRecordcounter++;
+ }
+ }
+
+ recordList.Size = selectedRecordcounter; // Set the correct size of the record list
+ recordList.Items = records; // Set the array of records to the record list
+
+ AGAVE_API_PLAYLIST_GENERATOR->GeneratePlaylist(hwndDlg, &recordList); // Call the playlist API with the list of selected records
+
+ delete [] records; // Free up the array of records
+ }
+ else
+ {
+ wchar_t title[64] = {0};
+ MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_ERROR_PLG_SELECT_TRACKS), WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_PLAYLIST_GENERATOR,title,64), MB_OK | MB_ICONINFORMATION);
+ }
+ }
+ break;
+#if 0
+ case IDC_BUTTON_MIX:
+ {
+ itemRecordList list;
+ int i;
+ int ct = 0;
+ for (i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ ct++;
+ }
+ }
+ ZeroMemory(&list, sizeof(itemRecordList));
+ allocRecordList(&list, ct, 1);
+ list.Size = ct;
+ ct = 0;
+ for (i = 0; i < itemCache.Size; i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ convertRecord(&list.Items[ct], &itemCache.Items[i]);
+ ct++;
+ }
+ }
+ pluginMessage p = {ML_MSG_PDXS_MIX, (INT_PTR) &list, 0, 0};
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE);
+ emptyRecordList(&list);
+ freeRecordList(&list);
+ }
+ break;
+#endif
+ case ID_MEDIAWND_SELECTALL:
+ {
+ LVITEM item = {0};
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(resultlist.getwnd(), LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ }
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ removeSelectedItems(0);
+ break;
+ case ID_EDITITEMINFOS:
+ if (resultlist.GetSelectedCount() > 0)
+ editInfo(hwndDlg);
+ break;
+ case ID_PE_ID3:
+ fileInfoDialogs(hwndDlg);
+ PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)resultlist.getwnd(), (LPARAM)TRUE);
+ break;
+ case IDC_REFRESH_METADATA:
+ RefreshMetadata(hwndDlg);
+ break;
+ case ID_MEDIAWND_EXPLOREFOLDER:
+ exploreItemFolder(hwndDlg);
+ break;
+ }
+ }
+ else
+ {
+ switch (idCtrl)
+ {
+ case IDC_QUICKSEARCH:
+ if (nCode == EN_CHANGE)
+ {
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg, UPDATE_QUERY_TIMER_ID, g_querydelay, NULL);
+ }
+ break;
+ case ID_MEDIAWND_SELECTALL:
+ SendMessageW(hwndSearchGlobal, EM_SETSEL, 0, -1);
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ {
+ DWORD start = -1, end = -1;
+ SendMessageW(hwndSearchGlobal, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
+ if (start != -1)
+ {
+ if (start == end)
+ {
+ SendMessageW(hwndSearchGlobal, EM_SETSEL, start, end + 1);
+ }
+ SendMessageW(hwndSearchGlobal, EM_REPLACESEL, TRUE, (LPARAM)"");
+ SendMessageW(hwndSearchGlobal, EM_SETSEL, start, start);
+ }
+ }
+ break;
+ }
+ }
+}
+
+static void Dialog_OnTimer(HWND hwndDlg, UINT_PTR idEvent, TIMERPROC fnTimer)
+{
+ switch (idEvent)
+ {
+ case 5050:
+ KillTimer(hwndDlg, 5050);
+ WASABI_API_DIALOGBOXW(IDD_NEEDADDFILES, hwndDlg, needAddFilesProc);
+ PostMessage(GetParent(hwndDlg), WM_APP + 1, (WPARAM)0, (LPARAM)0);
+ break;
+ case 6600:
+ KillTimer(hwndDlg, idEvent);
+ if (m_last_selitem >= 0 && m_last_selitem < itemCache.Size)
+ {
+ if (predixisExist & 1)
+ {
+ // Only single seeds are supported currently
+ if (resultlist.GetSelectedCount() == 1 && isMixable(itemCache.Items[m_last_selitem]))
+ {
+ SetDlgItemTextW(hwndDlg, IDC_MIXABLE, WASABI_API_LNGSTRINGW(IDS_MIXABLE));
+ isMixablePresent = true;
+ }
+ else SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ }
+ SendMessageW(GetParent(hwndDlg), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)itemCache.Items[m_last_selitem].filename);
+ m_last_selitem = -1;
+ }
+ break;
+ case 123:
+ if (bgThread_Handle)
+ {
+ HWND hwndList;
+ hwndList = resultlist.getwnd();
+ if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ ListView_RedrawItems(hwndList, 0, 0);
+ //UpdateWindow(hwndList);
+ }
+ break;
+ case UPDATE_QUERY_TIMER_ID:
+ {
+ KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID);
+ wchar_t buf[2048] = {0};
+ GetWindowTextW(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), buf, ARRAYSIZE(buf));
+ doQuery(hwndDlg, buf);
+ }
+ break;
+ case UPDATE_RESULT_LIST_TIMER_ID:
+ {
+ ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1);
+ }
+ break;
+ }
+}
+
+static void Dialog_OnDestroy(HWND hwndDlg)
+{
+ HWND hwndList;
+ INT i, j;
+ wchar_t buf[2048] = {0};
+
+ bgQuery_Stop();
+
+ GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf));
+ g_view_metaconf->WriteString("lastquery_utf8", AutoChar(buf, CP_UTF8));
+
+ hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+ if (hwndList && IsWindow(hwndList))
+ {
+ for (i = 0; columnOrder[i] != -1; i++)
+ {
+ headerColumn *cl = &columnList[columnOrder[i]];
+ g_view_metaconf->WriteInt(AutoWide(cl->config_name), SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L));
+ }
+ }
+
+ //Save the column order
+ for (i = 0; columnOrder[i] != -1; i++);
+ g_view_metaconf->WriteInt(L"nbcolumns", i);
+ for (j = 0; j < i; j++)
+ {
+ wchar_t tmp[128] = {0};
+ StringCchPrintfW(tmp, 128, L"column%d", j);
+ g_view_metaconf->WriteInt(tmp, columnOrder[j]);
+ }
+
+ freeRecordList(&itemCache);
+ itemCache.Items = 0;
+ itemCache.Alloc = 0;
+ itemCache.Size = 0;
+
+ cloudFiles.clear();
+ cloudUploading.clear();
+
+ hwndSearchGlobal = 0; // Set the hwnd for the search to a global to null so we know we are not in the single pane view
+}
+
+static void Dialog_OnWindowPosChanged(HWND hwndDlg, WINDOWPOS *pwp)
+{
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & pwp->flags) || (SWP_FRAMECHANGED & pwp->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & pwp->flags), 0 != (SWP_SHOWWINDOW & pwp->flags));
+ }
+}
+
+static void Dialog_OnSyncHeaderOrder(HWND hwndDlg, HWND hwndHeader)
+{
+ LVCOLUMNW column = {0};
+ wchar_t buffer[128] = {0};
+ signed char tempOrder[MAX_COLUMN_ORDER] = {0};
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ if (!hwndList) return;
+
+ CopyMemory(tempOrder, columnOrder, sizeof(tempOrder)/sizeof(signed char));
+
+ column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_IMAGE;
+ column.cchTextMax = sizeof(buffer) /sizeof(wchar_t);
+
+ SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L);
+
+ INT sort = MLSkinnedListView_GetSort(hwndList);
+ INT count = (INT)SendMessageW(hwndHeader, HDM_GETITEMCOUNT, 0, 0L);
+ if (count > 0)
+ {
+ INT index = count + 1, *pOrder = (INT*)calloc(1, sizeof(INT)*count);
+ if (pOrder && SendMessageW(hwndList, LVM_GETCOLUMNORDERARRAY, count, (LPARAM)pOrder))
+ {
+ INT order;
+ for (order = 0; order < count; order++)
+ {
+ column.pszText = buffer;
+ if (!SendMessageW(hwndList, LVM_GETCOLUMNW, pOrder[order], (LPARAM)&column)) continue;
+ column.iOrder = order;
+
+ // update position of the cloud column icon
+ if (tempOrder[pOrder[order]] == MEDIAVIEW_COL_CLOUD)
+ {
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1);
+ column.cx = 0;
+ }
+ else
+ {
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), order);
+ SetPropW(hwndList, L"pmp_list_info", (HANDLE)order);
+ column.cx = 27;
+ MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &column.cx);
+ }
+ }
+
+ SendMessageW(hwndList, LVM_INSERTCOLUMNW, index++, (LPARAM)&column);
+ columnOrder[order] = tempOrder[pOrder[order]];
+
+ if (LOWORD(sort) == pOrder[order]) MLSkinnedListView_DisplaySort(hwndList, order, HIWORD(sort));
+ }
+ for (order = 0; order < count; order++) SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L);
+ }
+
+ for (index = 0; -1 != columnOrder[index] && MEDIAVIEW_COL_RATING != columnOrder[index] && MEDIAVIEW_COL_CLOUD != columnOrder[index]; index++);
+ if (-1 != columnOrder[index])
+ {
+ INT w = (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, index, 0L);
+ SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, index, (LPARAM)w);
+ }
+
+ if (pOrder) free(pOrder);
+ }
+
+ SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L);
+}
+
+static void Window_OnQueryFileInfo(HWND hwnd)
+{
+ INT index;
+ HWND hwndList = GetDlgItem(hwnd, IDC_LIST2);
+ index = (hwndList) ? (INT)SendMessage(hwndList, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_FOCUSED) : -1;
+ SendMessageW(GetParent(hwnd), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)((-1 != index && index < itemCache.Size) ? itemCache.Items[index].filename : L""));
+}
+
+void Window_OnDropFiles(HWND hwndDlg, HDROP hdrop)
+{
+ wchar_t temp[1024] = {0};
+ int y = DragQueryFileW(hdrop, 0xffffffff, temp, 1024);
+ if (y > 0)
+ {
+ wchar_t **paths = (wchar_t **)calloc(y, sizeof(wchar_t *));
+ int *guesses = (int *)calloc(y, sizeof(int));
+ int *metas = (int *)calloc(y, sizeof(int));
+ int *recs= (int *)calloc(y, sizeof(int));
+ if (paths && guesses && metas && recs)
+ {
+ size_t count=0;
+ for (int x = 0; x < y; x ++)
+ {
+ DragQueryFileW(hdrop, x, temp, 1024);
+ int guess = -1, meta = -1, rec = 1;
+ // do this for normal media drops
+ PLCallBackW plCB;
+ if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(temp, &plCB))
+ {
+ autoscan_add_directory(temp, &guess, &meta, &rec, 0);
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ paths[count] = _wcsdup(temp);
+ guesses[count]=guess;
+ metas[count]=meta;
+ recs[count]=rec;
+ count++;
+ }
+ }
+ DragFinish(hdrop);
+ Scan_ScanFolders(hwndDlg, count, paths, guesses, metas, recs);
+ if (IsWindow(m_curview_hwnd)) SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view
+ }
+ else
+ {
+ free(paths);
+ free(guesses);
+ free(metas);
+ free(recs);
+ }
+ }
+ else DragFinish(hdrop);
+}
+
+
+INT_PTR CALLBACK view_mediaDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ BOOL a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+
+ switch (uMsg)
+ {
+ case WM_INITMENUPOPUP: Dialog_OnInitMenuPopup(hwndDlg, (HMENU)wParam, LOWORD(lParam), HIWORD(lParam)); break;
+ case WM_DISPLAYCHANGE: Dialog_OnDisplayChange(hwndDlg); break;
+ case WM_INITDIALOG: return Dialog_OnInit(hwndDlg, (HWND)wParam, lParam);
+ case WM_MOUSEMOVE: Dialog_OnMouseMove(hwndDlg, (UINT)wParam, MAKEPOINTS(lParam)); break;
+ case WM_LBUTTONUP: Dialog_OnLButtonUp(hwndDlg, (UINT)wParam, MAKEPOINTS(lParam)); break;
+ case WM_DROPFILES: Window_OnDropFiles(hwndDlg, (HDROP)wParam);
+ case WM_COMMAND: Dialog_OnCommand(hwndDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break;
+ case WM_TIMER: Dialog_OnTimer(hwndDlg, (UINT_PTR) wParam, (TIMERPROC)lParam); break;
+ case WM_DESTROY: Dialog_OnDestroy(hwndDlg); break;
+ case WM_WINDOWPOSCHANGED: Dialog_OnWindowPosChanged(hwndDlg, (WINDOWPOS*)lParam); break;
+ case WM_ERASEBKGND: return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+ case WM_CONTEXTMENU:
+ Dialog_OnContextMenu(hwndDlg, (HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ return 0;
+ case WM_NOTIFY:
+ {
+ LRESULT result;
+ result = 0L;
+ if (Dialog_OnNotify(hwndDlg, (INT)wParam, (NMHDR*)lParam, &result))
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return TRUE;
+ }
+ }
+ break;
+ case IM_SYNCHEADERORDER: Dialog_OnSyncHeaderOrder(hwndDlg, (HWND)lParam); break;
+ case WM_APP + 3: // send by bgthread
+ if (wParam == 0x666) m_bgupdinfoviewerflag = 1;
+ else if (wParam == 0x69)
+ {
+ bgQuery_Stop();
+
+ resultlist.SetVirtualCount(0);
+ resultlist.SetVirtualCount(itemCache.Size); // TODO: we could set a limit here
+ ListView_RedrawItems(resultlist.getwnd(), 0, itemCache.Size - 1);
+ UpdateWindow(resultlist.getwnd());
+
+ __int64 total_len_bytes = bg_total_len_bytes;
+
+ int total_length_s = (int)bg_total_len_s & 0x7FFFFFFF;
+ wchar_t buffer[4*64] = {0};
+ wchar_t *pb[4] = {0};
+ for (int i = 0; i < 4; i++) pb[i] = buffer + i * 64;
+
+
+ int index(0);
+
+ StringCchPrintfW(pb[index], 64, L"%d %s", itemCache.Size,
+ WASABI_API_LNGSTRINGW(itemCache.Size == 1 ? IDS_ITEM : IDS_ITEMS));
+ index++;
+
+ if (itemCache.Size)
+ {
+ int uncert = 0; //bg_total_len_s>>31;
+ if (total_length_s < 60*60) StringCchPrintfW(pb[index], 64, L"[%s%u:%02u]", uncert ? L"~" : L"", total_length_s / 60, total_length_s % 60);
+ else if (total_length_s < 60*60*24) StringCchPrintfW(pb[index], 64, L"[%s%u:%02u:%02u]", uncert ? L"~" : L"", total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ else
+ {
+ wchar_t days[16] = {0};
+ int total_days = total_length_s / (60 * 60 * 24);
+ total_length_s -= total_days * 60 * 60 * 24;
+ StringCchPrintfW(pb[index], 64,
+ WASABI_API_LNGSTRINGW(IDS_LENGTH_DURATION_STRING),
+ uncert ? L"~" : L"", total_days,
+ WASABI_API_LNGSTRINGW_BUF(total_days == 1 ? IDS_DAY : IDS_DAYS, days, 16),
+ total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60);
+ }
+ index++;
+ }
+
+ if (total_len_bytes)
+ {
+ StringCchCopyW(pb[index], 64, L"[");
+ WASABI_API_LNG->FormattedSizeString(pb[index] + 1, 64, total_len_bytes);
+ StringCchCatW(pb[index], 64, L"]");
+ index++;
+ }
+
+ unsigned int ms = (UINT)(querytime.QuadPart * 1000 / freq.QuadPart);
+ StringCchPrintfW(pb[index], 64, WASABI_API_LNGSTRINGW(IDS_IN_X_SEC), ms / 1000.0f);
+ index++;
+
+ SetStatusText(GetDlgItem(hwndDlg, IDC_MEDIASTATUS), (LPCWSTR*)pb, index);
+
+ if (m_bgupdinfoviewerflag)
+ {
+ m_bgupdinfoviewerflag = 0;
+ if (itemCache.Size > 0)
+ {
+ if (predixisExist)
+ {
+ if (resultlist.GetSelectedCount() == 1 && isMixable(itemCache.Items[0]))
+ {
+ SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ isMixablePresent = true;
+ }
+ else
+ {
+ SetDlgItemText(hwndDlg, IDC_MIXABLE, L"");
+ }
+ }
+ SendMessageW(GetParent(hwndDlg), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)itemCache.Items[0].filename);
+ }
+ }
+ }
+ break;
+ case WM_APP + 1:
+ bgQuery((resultsniff_funcW)wParam, (int)lParam);
+ break;
+ case WM_APP + 5:
+ {
+ // TODO
+ int done = (HIWORD(wParam) == 1);
+ int code = (LOWORD(wParam));
+ for (int i = 0; i < itemCache.Size; i ++)
+ {
+ // 0 = no change
+ // 1 = add cloud
+ // 2 = add local
+ // 4 = removed
+ if (!lstrcmpiW(itemCache.Items[i].filename, (wchar_t *)lParam))
+ {
+ if (!done)
+ {
+ setCloudValue(&itemCache.Items[i], L"5");
+ }
+ else
+ {
+ if (code == NErr_Success)
+ {
+ // uploaded ok
+ // TODO if going to another device, will need to alter this
+ setCloudValue(&itemCache.Items[i], L"0");
+ }
+ else
+ {
+ // re-query state
+ setCloudValue(&itemCache.Items[i], L"0");
+ }
+ }
+ InvalidateRect(resultlist.getwnd(), NULL, TRUE);
+ break;
+ }
+ }
+ break;
+ }
+ case WM_APP + 6: // handles the ml_cloud 'first pull' announces so we
+ { // can then show the cloud column & update the cache
+ initColumnsHeader(resultlist.getwnd());
+ bgQuery();//(resultsniff_funcW)wParam, (int)lParam);
+ break;
+ }
+ case WM_APP + 104:
+ {
+ Dialog_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+ case WM_PAINT:
+ {
+ int tab[] = { IDC_LIST2 | DCW_SUNKENBORDER, IDC_QUICKSEARCH | DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 1 + !!IsWindowVisible(GetDlgItem(hwndDlg, IDC_QUICKSEARCH)));
+ }
+ return 0;
+
+ case WM_ML_CHILDIPC:
+ switch (lParam)
+ {
+ case ML_CHILDIPC_GO_TO_SEARCHBAR:
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ case ML_CHILDIPC_REFRESH_SEARCH:
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ break;
+ }
+ break;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+ case WM_QUERYFILEINFO: Window_OnQueryFileInfo(hwndDlg); break;
+ }
+ return FALSE;
+}
+
+//////////////////////////////// Customize columns dialog
+
+static signed char edit_columnOrder[MAX_COLUMN_ORDER];
+
+static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND m_curlistbox_hwnd, m_availlistbox_hwnd;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ memcpy(edit_columnOrder, columnOrder, sizeof(edit_columnOrder));
+ m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1);
+ m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, TRUE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, TRUE);
+ }
+
+ case WM_USER + 32:
+ {
+ for (int i = 0; edit_columnOrder[i] != -1; i++)
+ {
+ int c = edit_columnOrder[i];
+ headerColumn *cl = &columnList[c];
+ int column_id = cl->column_id;
+ if (column_id == IDS_CLOUD || column_id == IDS_CLOUD_HIDDEN)
+ {
+ // if no cloud support at all then we hide everything
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) continue;
+ column_id = ((!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) ? IDS_CLOUD_HIDDEN : IDS_CLOUD);
+ }
+ int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(column_id));
+ SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+
+ for (int i = 0; i < sizeof(columnList) / sizeof(headerColumn); i++)
+ {
+ headerColumn *cl = &columnList[i];
+ int j;
+ for (j = 0; edit_columnOrder[j] != -1 && edit_columnOrder[j] != i; j++);
+ if (edit_columnOrder[j] == -1)
+ {
+ int column_id = cl->column_id;
+ if (column_id == IDS_CLOUD || column_id == IDS_CLOUD_HIDDEN)
+ {
+ // if no cloud support at all then we hide everything
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) continue;
+ column_id = ((!cloud_hinst || cloud_hinst == (HINSTANCE)1 ||
+ !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) ? IDS_CLOUD_HIDDEN : IDS_CLOUD);
+ }
+ int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(column_id));
+ SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, i);
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_DEFS:
+ if (!cloud_hinst || cloud_hinst == (HINSTANCE)1)
+ memcpy(edit_columnOrder, defColumnOrder, sizeof(defColumnOrder));
+ else
+ memcpy(edit_columnOrder, defColumnOrderCloud, sizeof(defColumnOrderCloud));
+ SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(hwndDlg, WM_USER + 32, 0, 0);
+ break;
+ case IDC_LIST2:
+ if (HIWORD(wParam) != LBN_DBLCLK)
+ {
+ if (HIWORD(wParam) == LBN_SELCHANGE)
+ {
+ int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON2:
+ //add column
+ {
+ for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ int c = SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ int j;
+ for (j = 0;edit_columnOrder[j] != -1;j++);
+ edit_columnOrder[j] = (BYTE)c;
+ edit_columnOrder[j + 1] = -1;
+ SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+
+ int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0);
+ }
+ break;
+ case IDC_LIST1:
+ if (HIWORD(wParam) != LBN_DBLCLK)
+ {
+ if (HIWORD(wParam) == LBN_SELCHANGE)
+ {
+ int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON3:
+ //remove column
+ {
+ for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ int c = edit_columnOrder[i];
+ for (int j = i;edit_columnOrder[j] != -1;j++)
+ {
+ edit_columnOrder[j] = edit_columnOrder[j + 1];
+ }
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+
+ int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0);
+ }
+ break;
+ case IDC_BUTTON4:
+ //move column up
+ {
+ for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ BYTE c = edit_columnOrder[i - 1];
+ edit_columnOrder[i - 1] = edit_columnOrder[i];
+ edit_columnOrder[i] = c;
+
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON5:
+ //move column down
+ {
+ int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (int i = l - 2;i >= 0;i--)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ BYTE c = edit_columnOrder[i + 1];
+ edit_columnOrder[i + 1] = edit_columnOrder[i];
+ edit_columnOrder[i] = c;
+
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0);
+ int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id));
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, c);
+ }
+ }
+ }
+ break;
+ case IDOK:
+ {
+ HWND hwndList = resultlist.getwnd();
+ memcpy(columnOrder, edit_columnOrder, sizeof(edit_columnOrder));
+ if (hwndList)
+ {
+ initColumnsHeader(hwndList);
+ InvalidateRect(hwndList, NULL, TRUE);
+ UpdateWindow(hwndList);
+ }
+ }
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ if (NULL != m_curlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, FALSE);
+ if (NULL != m_availlistbox_hwnd)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, FALSE);
+
+ WASABI_API_APP->app_removeAccelerators(hwndDlg);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void customizeColumnsDialog(HWND hwndParent)
+{
+ WASABI_API_DIALOGBOXW(IDD_CUSTCOLUMNS, hwndParent, custColumns_dialogProc);
+ EatKeyboard();
+}
+
+bool isMixable(itemRecordW &song)
+{
+ if (!song.filename) return false;
+
+ AutoChar charFn(song.filename);
+ pluginMessage p = {ML_MSG_PDXS_STATUS, (INT_PTR)(char *)charFn, 0, 0};
+ char *text = (char *)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & p, ML_IPC_SEND_PLUGIN_MESSAGE);
+ // Analyzed/Identified = mixable
+ return text && (text[0] == 'A' || text[0] == 'I');
+}
+
+void AccessingGracenoteHack(int p)
+{
+ if (p == 0)
+ {
+ GetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, oldText, 4096);
+ SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, L"Accessing Gracenote Database");
+ }
+ else
+ {
+ SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, oldText);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/view_miniinfo.cpp b/Src/Plugins/Library/ml_local/view_miniinfo.cpp
new file mode 100644
index 00000000..997efe63
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/view_miniinfo.cpp
@@ -0,0 +1,201 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+static int g_displayinfo = TRUE, g_displayinfochanged=FALSE;
+
+static HWND m_media_hwnd = NULL;
+static HWND m_info_hwnd = NULL;
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+#define IDC_WEBINFO 0x1000
+
+static void LayoutWindows(HWND hwnd, BOOL fShowInfo, BOOL fRedraw)
+{
+ RECT rc, ri, rg;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ SetRect(&rg, 0, 0, 0, 0);
+
+ if (rc.bottom - rc.top < WASABI_API_APP->getScaleY(180)) fShowInfo = FALSE;
+ if (rc.bottom <= rc.top || rc.right <= rc.left) return;
+
+ if (fShowInfo)
+ {
+ if (!IsWindow(m_info_hwnd)) // create browser
+ {
+ WEBINFOCREATE wic = {0};
+ wic.hwndParent = hwnd;
+ wic.uMsgQuery = WM_QUERYFILEINFO;
+ wic.x = rc.left;
+ wic.y = rc.bottom - WASABI_API_APP->getScaleY(101);
+ wic.cx = (rc.right - rc.left) - WASABI_API_APP->getScaleX(3);
+ wic.cy = WASABI_API_APP->getScaleY(100);
+ wic.ctrlId = IDC_WEBINFO;
+
+ m_info_hwnd = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_CREATEWEBINFO, (WPARAM)&wic);
+ if (IsWindow(m_info_hwnd))
+ {
+ SetWindowPos(m_info_hwnd, HWND_BOTTOM, 0, 0, 0, 0,
+ SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_NOCOPYBITS);
+ ShowWindow(m_info_hwnd, SW_SHOWNORMAL);
+ }
+ }
+ else
+ {
+ SetWindowPos(m_info_hwnd, NULL, rc.left, rc.bottom - WASABI_API_APP->getScaleY(101),
+ rc.right - WASABI_API_APP->getScaleX(3), WASABI_API_APP->getScaleY(100),
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW);
+ }
+
+ if (IsWindow(m_info_hwnd))
+ {
+ rc.bottom -= WASABI_API_APP->getScaleY(104);
+ SetRect(&rg, rc.left, rc.bottom - WASABI_API_APP->getScaleY(101),
+ rc.right - WASABI_API_APP->getScaleX(10),
+ rc.bottom - WASABI_API_APP->getScaleY(1));
+ }
+ }
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ if (IsWindow(m_media_hwnd))
+ {
+ SetRect(&ri, rc.left, rc.top, rc.right, rc.bottom);
+ rgn = CreateRectRgn(0, 0, ri.right - ri.left, ri.bottom - ri.top);
+ SendMessage(m_media_hwnd, WM_USER + 0x201, MAKEWPARAM(offsetX, offsetY), (LPARAM)rgn);
+ SetWindowPos(m_media_hwnd, NULL, ri.left, ri.top, ri.right - ri.left, ri.bottom - ri.top,
+ SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOACTIVATE | ((!fRedraw) ? SWP_NOREDRAW : 0));
+ SendMessage(m_media_hwnd, WM_USER + 0x201, 0, 0L);
+ if (IsWindowVisible(m_media_hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(m_media_hwnd, NULL, FALSE))
+ {
+ GetUpdateRgn(m_media_hwnd, rgn, FALSE);
+ OffsetRgn(rgn, ri.left, ri.top);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (fRedraw)
+ {
+ UpdateWindow(hwnd);
+ if (IsWindow(m_media_hwnd)) UpdateWindow(m_media_hwnd);
+ }
+ if (g_rgnUpdate)
+ {
+ GetUpdateRgn(hwnd, g_rgnUpdate, FALSE);
+ if (rgn)
+ {
+ OffsetRgn(rgn, rc.left, rc.top);
+ CombineRgn(g_rgnUpdate, g_rgnUpdate, rgn, RGN_OR);
+ }
+ }
+ ValidateRgn(hwnd, NULL);
+ if (rgn) DeleteObject(rgn);
+}
+
+INT_PTR CALLBACK view_miniinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a=dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+
+ switch(uMsg)
+ {
+ case WM_DISPLAYCHANGE:
+ if (IsWindow(m_media_hwnd)) PostMessageW(m_media_hwnd, WM_DISPLAYCHANGE, wParam, lParam);
+ if (IsWindow(m_info_hwnd)) PostMessageW(m_info_hwnd, WM_DISPLAYCHANGE, wParam, lParam);
+ break;
+
+ case WM_INITDIALOG:
+ {
+ g_displayinfo = g_view_metaconf->ReadInt(L"midivvis", 1);
+ g_displayinfochanged = FALSE;
+ m_media_hwnd = (lParam) ? WASABI_API_CREATEDIALOGW(((INT_PTR*)lParam)[1], hwndDlg, (DLGPROC)((INT_PTR*)lParam)[0]) : NULL;
+ SetWindowPos(m_media_hwnd, NULL, 0,0,0,0, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_SHOWWINDOW);
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_DIALOG;
+ m.hwndToSkin = hwndDlg;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ return TRUE;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, g_displayinfo, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_APP+1: //sent by parent for resizing window
+ if (IsWindow(m_media_hwnd)) return SendMessage(m_media_hwnd,uMsg,wParam,lParam); // forward on
+ return 0;
+
+ case WM_APP+2: //sent by media child to get current query
+ return SendMessage(GetParent(hwndDlg),uMsg,wParam,lParam); // forward on
+
+ case WM_QUERYFILEINFO: if(IsWindow(m_media_hwnd)) PostMessageW(m_media_hwnd, uMsg, wParam, lParam); break;
+
+ case WM_SHOWFILEINFO:
+ if (IsWindow(m_info_hwnd))
+ {
+ WEBINFOSHOW wis = {0};
+ wis.pszFileName = (LPCWSTR)lParam;
+ wis.fFlags = (TRUE == wParam) ? WISF_FORCE : WISF_NORMAL;
+ SENDMLIPC(m_info_hwnd, ML_IPC_WEBINFO_SHOWINFO, (WPARAM)&wis);
+ }
+ case WM_USER+66:
+ if (wParam == -1)
+ {
+ g_displayinfo = !g_displayinfo;
+ g_displayinfochanged = !g_displayinfochanged;
+ if (IsWindow(m_info_hwnd)) ShowWindow(m_info_hwnd, (g_displayinfo) ? SW_SHOWNA : SW_HIDE);
+ LayoutWindows(hwndDlg, g_displayinfo, TRUE);
+ }
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT, (g_displayinfo) ? 0xff : 0xf0);
+ return TRUE;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ if (GetDlgItem(hwndDlg, IDC_WEBINFO))
+ {
+ static int tab[] = { IDC_WEBINFO | DCW_SUNKENBORDER };
+ dialogSkinner.Draw(hwndDlg, tab, sizeof(tab) / sizeof(tab[0]));
+ }
+ else
+ dialogSkinner.Draw(hwndDlg, 0, 0);
+ }
+ return 0;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+
+ case WM_DESTROY:
+ if(g_displayinfochanged) g_view_metaconf->WriteInt(L"midivvis", g_displayinfo);
+ if (IsWindow(m_info_hwnd)) SENDMLIPC(m_info_hwnd, ML_IPC_WEBINFO_RELEASE, 0);
+ m_media_hwnd = NULL;
+ m_info_hwnd = NULL;
+ break;
+ }
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_local/wa_subclass.cpp b/Src/Plugins/Library/ml_local/wa_subclass.cpp
new file mode 100644
index 00000000..f5f20a08
--- /dev/null
+++ b/Src/Plugins/Library/ml_local/wa_subclass.cpp
@@ -0,0 +1,314 @@
+#include "main.h"
+#include "api__ml_local.h"
+#include "resource.h"
+#include "../nu/listview.h"
+
+#define WINAMP_FILE_ADDTOLIBRARY 40344
+#define WINAMP_FILE_ADDCURRENTPLEDIT 40466
+#define WINAMP_SHOWLIBRARY 40379
+wchar_t *recent_fn = 0;
+static HMENU last_viewmenu = 0;
+WORD waMenuID = 0;
+
+extern W_ListView resultlist;
+extern volatile int no_lv_update;
+void UpdateLocalResultsCache(const wchar_t *filename);
+
+static void onPlayFileTrack()
+{
+ int autoaddplays = g_config->ReadInt(L"autoaddplays", 0);
+ int trackplays = g_config->ReadInt(L"trackplays", 1);
+ if (!trackplays && !autoaddplays)
+ return ;
+
+ if (g_table && recent_fn)
+ {
+ wchar_t filename2[MAX_PATH] = {0}; // full lfn path if set
+ EnterCriticalSection(&g_db_cs);
+
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ int found=FindFileInDatabase(s, MAINTABLE_ID_FILENAME, recent_fn, filename2);
+
+ if (found) // if it's in the table already
+ {
+ if (trackplays) // if we're tracking plays
+ {
+ NDE_Scanner_Edit(s);
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_PLAYCOUNT);
+ int cnt = f ? NDE_IntegerField_GetValue(f) : 0;
+ time_t t = time(NULL);
+
+ db_setFieldInt(s, MAINTABLE_ID_PLAYCOUNT, ++cnt);
+ db_setFieldInt(s, MAINTABLE_ID_LASTPLAY, (int)t);
+ if (asked_for_playcount)
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_UPDTITLE);
+
+ // Issue a wasabi system callback after we have successfully added a file in the ml database
+ api_mldb::played_info info = {t, cnt};
+ WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PLAYED, (size_t)recent_fn, (size_t)&info);
+
+ NDE_Scanner_Post(s);
+ // disabled in 5.65 (was added in 5.64?) as causing
+ // noticeable ui lockups on sync with large library
+ // so instead we'll risk it and flush on 50 instead
+ /*g_table_dirty = 0;
+ NDE_Table_Sync(g_table);*/
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ }
+ else // not found in main table, we are in the main table, time to add if set
+ {
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (autoaddplays)
+ {
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(recent_fn, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ addFileToDb(recent_fn, 0, meta, guess, 1, (int)time(NULL));
+ }
+ }
+
+ if (g_table_dirty > 50)
+ {
+ g_table_dirty = 0;
+ NDE_Table_Sync(g_table);
+ }
+
+ LeaveCriticalSection(&g_db_cs);
+ }
+}
+
+void onStartPlayFileTrack(const wchar_t *filename, bool resume)
+{
+ if (!(wcsstr(filename, L"://") && _wcsnicmp(filename, L"cda://", 6) && _wcsnicmp(filename, L"file://", 7)))
+ {
+ int timer = -1, timer1 = -1, timer2 = -1;
+
+ KillTimer(plugin.hwndWinampParent, 8081);
+ if (!resume)
+ {
+ free(recent_fn);
+ recent_fn = _wcsdup(filename);
+ }
+
+ // wait for x seconds
+ if(g_config->ReadInt(L"trackplays_wait_secs",0))
+ {
+ timer1 = g_config->ReadInt(L"trackplays_wait_secs_lim",5)*1000;
+ }
+
+ // wait for x percent of the song (approx to a second)
+ if(g_config->ReadInt(L"trackplays_wait_percent",0))
+ {
+ basicFileInfoStructW bfiW = {0};
+ bfiW.filename = recent_fn;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfiW, IPC_GET_BASIC_FILE_INFOW);
+ if(bfiW.length > 0)
+ {
+ bfiW.length=bfiW.length*1000;
+ timer2 = (bfiW.length*g_config->ReadInt(L"trackplays_wait_percent_lim",50))/100;
+ }
+ }
+
+ // decide on which playback option will be the prefered duration (smallest wins)
+ if(timer1 != -1 && timer2 != -1)
+ {
+ if(timer1 > timer2)
+ {
+ timer = timer2;
+ }
+ if(timer2 > timer1)
+ {
+ timer = timer1;
+ }
+ }
+ else if(timer1 == -1 && timer2 != -1)
+ {
+ timer = timer2;
+ }
+ else if(timer2 == -1 && timer1 != -1)
+ {
+ timer = timer1;
+ }
+
+ // if no match or something went wrong then try to ensure the default timer value is used
+ SetTimer(plugin.hwndWinampParent, 8081, ((timer > 0)? timer : 350), NULL);
+ }
+}
+
+static void FileUpdated(const wchar_t *filename)
+{
+ if (g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ int guess = -1, meta = -1, rec = 1;
+ autoscan_add_directory(filename, &guess, &meta, &rec, 1); // use this folder's guess/meta options
+ if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0);
+ if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1);
+ addFileToDb(filename, TRUE, meta, guess, 0, 0, true);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (g_table_dirty > 10)
+ {
+ g_table_dirty = 0;
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ if (!no_lv_update)
+ UpdateLocalResultsCache(filename);
+ }
+ ClearCache(filename);
+ ClearTitleHookCache();
+}
+
+LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_TIMER:
+ if (recent_fn && wParam == 8081)
+ {
+ if (SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_GETOUTPUTTIME) > 350)
+ {
+ KillTimer(hwndDlg, 8081);
+ if (SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_ISPLAYING) == 1)
+ {
+ onPlayFileTrack();
+ }
+ free(recent_fn);
+ recent_fn = 0;
+ }
+ }
+ break;
+ case WM_WA_IPC:
+ switch (lParam)
+ {
+ case IPC_CB_MISC:
+ if (wParam == IPC_CB_MISC_TITLE)
+ {
+ if(!SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_ISPLAYING))
+ {
+ KillTimer(hwndDlg, 8081);
+ }
+ }
+ else if (wParam == IPC_CB_MISC_PAUSE)
+ {
+ KillTimer(hwndDlg, 8081);
+ }
+ else if (wParam == IPC_CB_MISC_UNPAUSE)
+ {
+ if (recent_fn) onStartPlayFileTrack(recent_fn, true);
+ }
+ break;
+ case IPC_FILE_TAG_MAY_HAVE_UPDATEDW:
+ {
+ wchar_t *filename = (wchar_t *)wParam;
+ if (filename)
+ FileUpdated(filename);
+ }
+ break;
+ case IPC_FILE_TAG_MAY_HAVE_UPDATED:
+ {
+ char *filename = (char *)wParam;
+ if (filename)
+ FileUpdated(AutoWide(filename));
+ }
+ break;
+ case IPC_STOPPLAYING:
+ {
+ KillTimer(hwndDlg, 8081);
+ free(recent_fn);
+ recent_fn = 0;
+ }
+ break;
+ case IPC_GET_EXTENDED_FILE_INFO_HOOKABLE:
+ // guessing for metadata for when the library isn't open yet.
+ if (!g_table && wParam && !m_calling_getfileinfo) return doGuessProc(hwndDlg, uMsg, wParam, lParam);
+ break;
+ default:
+ {
+ if (lParam == IPC_CLOUD_ENABLED)
+ {
+ if (m_curview_hwnd) SendMessage(m_curview_hwnd, WM_APP + 6, 0, 0); //update current view
+ }
+ }
+ break;
+ }
+ break;
+ case WM_INITMENUPOPUP:
+ {
+ HMENU hmenuPopup = (HMENU) wParam;
+ if (hmenuPopup == wa_play_menu)
+ {
+ if (last_viewmenu)
+ {
+ RemoveMenu(wa_play_menu, waMenuID, MF_BYCOMMAND);
+ DestroyMenu(last_viewmenu);
+ last_viewmenu = NULL;
+ }
+
+ mlGetTreeStruct mgts = { 1000, 55000, -1};
+ last_viewmenu = (HMENU)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & mgts, ML_IPC_GETTREE);
+ if (last_viewmenu)
+ {
+ if (GetMenuItemCount(last_viewmenu) > 0)
+ {
+ int count = GetMenuItemCount(wa_play_menu);
+ MENUITEMINFOW menuItem = {sizeof(MENUITEMINFOW), MIIM_SUBMENU | MIIM_ID | MIIM_TYPE, MFT_STRING, MFS_ENABLED, waMenuID,
+ last_viewmenu, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_MEDIA_LIBRARY_VIEW_RESULTS), 0};
+ InsertMenuItemW(wa_play_menu, count, TRUE, &menuItem);
+ }
+ else
+ {
+ DestroyMenu(last_viewmenu);
+ last_viewmenu = 0;
+ }
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ case WM_SYSCOMMAND:
+ WORD lowP = LOWORD(wParam);
+ if (lowP == WINAMP_FILE_ADDTOLIBRARY)
+ {
+ if (!plugin.hwndLibraryParent || !IsWindowVisible(plugin.hwndLibraryParent))
+ {
+ SendMessage(plugin.hwndWinampParent, WM_COMMAND, MAKEWPARAM(WINAMP_SHOWLIBRARY, 0), 0L);
+ }
+ add_to_library(plugin.hwndLibraryParent);
+ }
+ else if (lowP == WINAMP_FILE_ADDCURRENTPLEDIT)
+ {
+ add_pledit_to_library();
+ }
+ else if (uMsg == WM_COMMAND && wParam > 45000 && wParam < 55000)
+ {
+ int n = wParam - 45000;
+ if (m_query_list[n])
+ {
+ mediaLibrary.SwitchToPluginView(n);
+ return 0;
+ }
+ }
+ else if (uMsg == WM_COMMAND && wParam > 55000 && wParam < 65000)
+ {
+ int n = wParam - 55000;
+ if (m_query_list[n])
+ {
+ queryItem *item = m_query_list[n];
+ wchar_t configDir[MAX_PATH] = {0};
+ PathCombineW(configDir, g_viewsDir, item->metafn);
+ C_Config viewconf(configDir);
+ main_playQuery(&viewconf, item->query, 0, 1);
+ return 0;
+ }
+ }
+ break;
+ }
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+} \ No newline at end of file