diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Library/ml_local | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Library/ml_local')
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"&", fp); + break; + case L'>': + fputws(L">", fp); + break; + case L'<': + fputws(L"<", fp); + break; + case L'\'': + fputws(L"'", fp); + break; + case L'\"': + fputws(L""", 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 ©) : 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 ©); + + +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,<)) + { + *file_time=FileTimeToUnixTime(<); + } + *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, ¶ms->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, ¶ms->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, ¶ms->found, ¶ms->total, ¶ms->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 Binary files differnew file mode 100644 index 00000000..2688e2e4 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/icn_alb_art.bmp diff --git a/Src/Plugins/Library/ml_local/resources/icn_columns.bmp b/Src/Plugins/Library/ml_local/resources/icn_columns.bmp Binary files differnew file mode 100644 index 00000000..46be6b46 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/icn_columns.bmp diff --git a/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp b/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp Binary files differnew file mode 100644 index 00000000..60f52169 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/icn_view_mode.bmp diff --git a/Src/Plugins/Library/ml_local/resources/nf_simple.bmp b/Src/Plugins/Library/ml_local/resources/nf_simple.bmp Binary files differnew file mode 100644 index 00000000..eaf5b9aa --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/nf_simple.bmp diff --git a/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp b/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp Binary files differnew file mode 100644 index 00000000..1d94f00a --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/nf_simplealbum.bmp diff --git a/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp b/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp Binary files differnew file mode 100644 index 00000000..4af1ba08 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/nf_threefilters.bmp diff --git a/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp b/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp Binary files differnew file mode 100644 index 00000000..2aed6d4f --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/nf_twofilters.bmp diff --git a/Src/Plugins/Library/ml_local/resources/notfound.png b/Src/Plugins/Library/ml_local/resources/notfound.png Binary files differnew file mode 100644 index 00000000..f76c0516 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/notfound.png diff --git a/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp Binary files differnew file mode 100644 index 00000000..0fb45741 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_audio_16x16x16.bmp 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 Binary files differnew file mode 100644 index 00000000..0d235d66 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_most_played_16x16x16.bmp 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 Binary files differnew file mode 100644 index 00000000..d299fda8 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_never_played_16x16x16.bmp diff --git a/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp Binary files differnew file mode 100644 index 00000000..428d5b50 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_podcasts_16x16x16.bmp 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 Binary files differnew file mode 100644 index 00000000..225e45cc --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_recently_added_16x16x16.bmp 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 Binary files differnew file mode 100644 index 00000000..4ec00ef0 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_recently_modified_16x16x16.bmp 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 Binary files differnew file mode 100644 index 00000000..476bde34 --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_recently_played_16x16x16.bmp 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 Binary files differnew file mode 100644 index 00000000..63b5036f --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_top_rated_16x16x16.bmp diff --git a/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp b/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp Binary files differnew file mode 100644 index 00000000..8fccdeef --- /dev/null +++ b/Src/Plugins/Library/ml_local/resources/ti_video_16x16x16.bmp 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 |