aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_history
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Library/ml_history
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Library/ml_history')
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPI.cpp93
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPI.h12
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp62
-rw-r--r--Src/Plugins/Library/ml_history/HistoryAPIFactory.h21
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp95
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_Creator.h31
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp123
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h27
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp260
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h46
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp198
-rw-r--r--Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h37
-rw-r--r--Src/Plugins/Library/ml_history/Main.cpp349
-rw-r--r--Src/Plugins/Library/ml_history/Main.h34
-rw-r--r--Src/Plugins/Library/ml_history/api__ml_history.h18
-rw-r--r--Src/Plugins/Library/ml_history/api_history.cpp1
-rw-r--r--Src/Plugins/Library/ml_history/api_history.h34
-rw-r--r--Src/Plugins/Library/ml_history/db_error.txt6
-rw-r--r--Src/Plugins/Library/ml_history/history.h35
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.cpp289
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.h64
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.rc264
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.sln94
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.vcxproj341
-rw-r--r--Src/Plugins/Library/ml_history/ml_history.vcxproj.filters164
-rw-r--r--Src/Plugins/Library/ml_history/prefs.cpp178
-rw-r--r--Src/Plugins/Library/ml_history/resource.h92
-rw-r--r--Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmpbin0 -> 568 bytes
-rw-r--r--Src/Plugins/Library/ml_history/version.rc239
-rw-r--r--Src/Plugins/Library/ml_history/view_history.cpp2349
30 files changed, 5356 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_history/HistoryAPI.cpp b/Src/Plugins/Library/ml_history/HistoryAPI.cpp
new file mode 100644
index 00000000..58301aef
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPI.cpp
@@ -0,0 +1,93 @@
+#include "HistoryAPI.h"
+#include "ml_history.h"
+
+static void saveQueryToList(nde_scanner_t s, historyRecordList *obj)
+{
+ emptyRecentRecordList(obj);
+
+ NDE_Scanner_First(s);
+
+ int r;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_FILENAME);
+ if (f)
+ {
+ allocRecentRecordList(obj, obj->Size + 1);
+ if (!obj->Alloc) break;
+
+ wchar_t *strval = NDE_StringField_GetString(f);
+ ndestring_retain(strval);
+ obj->Items[obj->Size].filename = strval;
+ recentScannerRefToObjCacheNFN(s, obj);
+ }
+
+ r = NDE_Scanner_Next(s);
+ }
+ while (r && !NDE_Scanner_EOF(s));
+
+ if (obj->Size && obj->Size < obj->Alloc - 1024)
+ {
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc = obj->Size;
+ historyRecord *data = (historyRecord*)realloc(obj->Items, sizeof(historyRecord) * obj->Alloc);
+ if (data)
+ {
+ obj->Items=data;
+ }
+ else
+ {
+ data=(historyRecord*)malloc(sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(historyRecord)*old_Alloc);
+ free(obj->Items);
+ obj->Items=data;
+ }
+ else obj->Alloc = (int)old_Alloc;
+ }
+ }
+}
+
+historyRecordList *HistoryAPI::Query(const wchar_t *query)
+{
+ if (!openDb())
+ return 0;
+
+ // run query
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s=NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, query);
+
+ historyRecordList obj;
+
+ obj.Alloc = 0;
+ obj.Items = NULL;
+ obj.Size = 0;
+ saveQueryToList(s, &obj);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+ if (obj.Size)
+ {
+ historyRecordList *result = (historyRecordList *)malloc(sizeof(historyRecordList));
+ memcpy(result, &obj, sizeof(historyRecordList));
+ return result;
+ }
+ else
+ {
+ freeRecentRecordList(&obj);
+ return 0;
+ }
+}
+
+void HistoryAPI::FreeHistoryList(historyRecordList *historyList)
+{
+ freeRecentRecordList(historyList);
+}
+
+#define CBCLASS HistoryAPI
+START_DISPATCH;
+CB(API_HISTORY_QUERY, Query)
+VCB(API_HISTORY_FREEHISTORYLIST, FreeHistoryList)
+END_DISPATCH;
+#undef CBCLASS
diff --git a/Src/Plugins/Library/ml_history/HistoryAPI.h b/Src/Plugins/Library/ml_history/HistoryAPI.h
new file mode 100644
index 00000000..cd5dfc8d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPI.h
@@ -0,0 +1,12 @@
+#pragma once
+#include "api_history.h"
+
+class HistoryAPI : public api_history
+{
+public:
+ historyRecordList *Query(const wchar_t *query);
+ void FreeHistoryList(historyRecordList *historyList);
+
+protected:
+ RECVS_DISPATCH;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp b/Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp
new file mode 100644
index 00000000..a938f6c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPIFactory.cpp
@@ -0,0 +1,62 @@
+#include "HistoryAPIFactory.h"
+#include "api__ml_history.h"
+#include "HistoryAPI.h"
+
+HistoryAPI historyAPI;
+static const char serviceName[] = "History API";
+
+FOURCC HistoryAPIFactory::GetServiceType()
+{
+ return WaSvc::UNIQUE;
+}
+
+const char *HistoryAPIFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID HistoryAPIFactory::GetGUID()
+{
+ return HistoryApiGuid;
+}
+
+void *HistoryAPIFactory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &historyAPI;
+}
+
+int HistoryAPIFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int HistoryAPIFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *HistoryAPIFactory::GetTestString()
+{
+ return 0;
+}
+
+int HistoryAPIFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS HistoryAPIFactory
+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 \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/HistoryAPIFactory.h b/Src/Plugins/Library/ml_history/HistoryAPIFactory.h
new file mode 100644
index 00000000..a07e063f
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/HistoryAPIFactory.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "api__ml_history.h"
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class HistoryAPIFactory : 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;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp b/Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp
new file mode 100644
index 00000000..d3619745
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_Creator.cpp
@@ -0,0 +1,95 @@
+#include "api__ml_history.h"
+#include "JSAPI2_Creator.h"
+#include "JSAPI2_HistoryAPI.h"
+#include "resource.h"
+
+
+IDispatch *JSAPI2_Creator::CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info)
+{
+ if (!_wcsicmp(name, L"History"))
+ return new JSAPI2::HistoryAPI(key, info);
+ else
+ return 0;
+}
+
+int JSAPI2_Creator::PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data)
+{
+ if (group && !_wcsicmp(group, L"history"))
+ {
+ const wchar_t *title_str = AGAVE_API_JSAPI2_SECURITY->GetAssociatedName(authorization_key);
+ return AGAVE_API_JSAPI2_SECURITY->SecurityPrompt(parent, title_str, L"This service is requesting access to your playback history.", JSAPI2::svc_apicreator::AUTHORIZATION_FLAG_GROUP_ONLY);
+ }
+ else
+ return JSAPI2::svc_apicreator::AUTHORIZATION_UNDEFINED;
+}
+
+#define CBCLASS JSAPI2_Creator
+START_DISPATCH;
+CB(JSAPI2_SVC_APICREATOR_CREATEAPI, CreateAPI);
+CB(JSAPI2_SVC_APICREATOR_PROMPTFORAUTHORIZATION, PromptForAuthorization);
+END_DISPATCH;
+#undef CBCLASS
+
+static JSAPI2_Creator jsapi2_svc;
+static const char serviceName[] = "History Javascript Objects";
+
+// {D9F59F89-82F3-446d-8CD8-6D4445094D50}
+static const GUID jsapi2_factory_guid =
+{ 0xd9f59f89, 0x82f3, 0x446d, { 0x8c, 0xd8, 0x6d, 0x44, 0x45, 0x9, 0x4d, 0x50 } };
+
+
+FOURCC JSAPI2Factory::GetServiceType()
+{
+ return jsapi2_svc.getServiceType();
+}
+
+const char *JSAPI2Factory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID JSAPI2Factory::GetGUID()
+{
+ return jsapi2_factory_guid;
+}
+
+void *JSAPI2Factory::GetInterface(int global_lock)
+{
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return &jsapi2_svc;
+}
+
+int JSAPI2Factory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int JSAPI2Factory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ return 1;
+}
+
+const char *JSAPI2Factory::GetTestString()
+{
+ return 0;
+}
+
+int JSAPI2Factory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#define CBCLASS JSAPI2Factory
+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 \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_Creator.h b/Src/Plugins/Library/ml_history/JSAPI2_Creator.h
new file mode 100644
index 00000000..ddcc9aee
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_Creator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class JSAPI2Factory : 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;
+};
+
+
+class JSAPI2_Creator : public JSAPI2::svc_apicreator
+{
+ IDispatch *CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info);
+ int PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data);
+protected:
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp
new file mode 100644
index 00000000..32bea983
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.cpp
@@ -0,0 +1,123 @@
+#include "api__ml_history.h"
+#include "JSAPI2_HistoryAPI.h"
+#include "../Winamp/JSAPI.h"
+#include "JSAPI2_HistoryRecordList.h"
+#include "HistoryAPI.h"
+
+extern HistoryAPI historyAPI;
+
+JSAPI2::HistoryAPI::HistoryAPI(const wchar_t *_key, JSAPI::ifc_info *_info)
+{
+ info = _info;
+ key = _key;
+ refCount = 1;
+}
+
+#define DISP_TABLE \
+ CHECK_ID(Query)\
+
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+};
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = JSAPI_DISP_ENUMIFY(str); continue; }
+HRESULT JSAPI2::HistoryAPI::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++)
+ {
+ DISP_TABLE
+
+ rgdispid[i] = DISPID_UNKNOWN;
+ unknowns = true;
+
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryAPI::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryAPI::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryAPI::Query(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
+{
+ JSAPI_VERIFY_METHOD(wFlags);
+ JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
+ JSAPI_VERIFY_PARAMTYPE(pdispparams, 1, VT_BSTR, puArgErr);
+ JSAPI_INIT_RESULT(pvarResult, VT_DISPATCH);
+
+ if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"history", L"query", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
+ {
+ if (pvarResult) // no sense in running the query if they don't care about the return value!
+ {
+ historyRecordList *query_results = historyAPI.Query(JSAPI_PARAM(pdispparams, 1).bstrVal);
+ if (query_results)
+ V_DISPATCH(pvarResult) = new JSAPI2::HistoryRecordList(query_results);
+ else
+ V_DISPATCH(pvarResult) = 0;
+ }
+ return S_OK;
+ }
+ else
+ {
+ V_DISPATCH(pvarResult) = 0;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdispparams, pvarResult, puArgErr);
+HRESULT JSAPI2::HistoryAPI::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ switch (dispid)
+ {
+ DISP_TABLE
+ }
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+STDMETHODIMP JSAPI2::HistoryAPI::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 JSAPI2::HistoryAPI::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+
+ULONG JSAPI2::HistoryAPI::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h
new file mode 100644
index 00000000..a880ef4d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryAPI.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <ocidl.h>
+#include "../Winamp/JSAPI_Info.h"
+
+namespace JSAPI2
+{
+ class HistoryAPI : public IDispatch
+ {
+ public:
+ HistoryAPI(const wchar_t *_key, JSAPI::ifc_info *info);
+ 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);
+ private:
+ const wchar_t *key;
+ volatile LONG refCount;
+ JSAPI::ifc_info *info;
+
+ STDMETHOD (Query)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
+ };
+}
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp
new file mode 100644
index 00000000..6b9a9ac6
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.cpp
@@ -0,0 +1,260 @@
+#include "JSAPI2_HistoryRecord.h"
+#include "../Winamp/JSAPI.h"
+
+#define DISP_TABLE \
+ CHECK_ID(filename)\
+ CHECK_ID(title)\
+ CHECK_ID(length)\
+ CHECK_ID(playcount)\
+ CHECK_ID(lastplay)\
+ CHECK_ID(offset)\
+
+
+#define CHECK_ID(str) JSAPI_DISP_ENUMIFY(str),
+enum {
+ DISP_TABLE
+ DISP_TABLE_NUM_ENTRIES,
+};
+
+JSAPI2::HistoryRecord::HistoryRecord(IUnknown *_parent, const historyRecord *_record)
+{
+ parent = _parent;
+ parent->AddRef();
+ record = _record;
+ refCount = 1;
+}
+
+JSAPI2::HistoryRecord::~HistoryRecord()
+{
+ if (parent)
+ parent->Release();
+}
+
+HRESULT JSAPI2::HistoryRecord::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++)
+ {
+ if (GetDispID(rgszNames[i], fdexNameCaseSensitive, &rgdispid[i]) == DISPID_UNKNOWN)
+ unknowns=true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryRecord::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ return InvokeEx(dispid, lcid, wFlags, pdispparams, pvarResult, pexecinfo, 0);
+}
+
+HRESULT JSAPI2::HistoryRecord::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP JSAPI2::HistoryRecord::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 if (IsEqualIID(riid, IID_IDispatchEx))
+ *ppvObject = (IDispatchEx *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::HistoryRecord::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+ULONG JSAPI2::HistoryRecord::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) if (wcscmp(bstrName, L## #str) == 0) { *pid = JSAPI_DISP_ENUMIFY(str); return S_OK; }
+HRESULT JSAPI2::HistoryRecord::GetDispID(BSTR bstrName, DWORD grfdex, DISPID *pid)
+{
+ DISP_TABLE
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): return str(wFlags, pdp, pvarRes);
+HRESULT JSAPI2::HistoryRecord::InvokeEx(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ switch(id)
+ {
+ DISP_TABLE
+ }
+ }
+
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::DeleteMemberByName(BSTR bstrName, DWORD grfdex)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::DeleteMemberByDispID(DISPID id)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::GetMemberProperties(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
+{
+ return E_NOTIMPL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): *pbstrName = SysAllocString(L ## #str); return S_OK;
+HRESULT JSAPI2::HistoryRecord::GetMemberName(DISPID id, BSTR *pbstrName)
+{
+ switch(id)
+ {
+ DISP_TABLE
+ }
+
+ return E_NOTIMPL;
+}
+
+#undef CHECK_ID
+#define CHECK_ID(str) case JSAPI_DISP_ENUMIFY(str): *pid = JSAPI_DISP_ENUMIFY(str) + 1; break;
+HRESULT JSAPI2::HistoryRecord::GetNextDispID(DWORD grfdex, DISPID id, DISPID *pid)
+{
+ if (grfdex == fdexEnumDefault)
+ {
+ switch(id)
+ {
+ case DISPID_UNKNOWN:
+ *pid = 0;
+ break;
+ DISP_TABLE
+
+ }
+ if (*pid == DISP_TABLE_NUM_ENTRIES)
+ return S_FALSE;
+ else
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::GetNameSpaceParent(IUnknown **ppunk)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecord::filename(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(record->filename));
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+
+HRESULT JSAPI2::HistoryRecord::title(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_BSTR);
+ JSAPI_SET_RESULT(pvarResult, bstrVal, SysAllocString(record->title));
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::length(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, record->length);
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::offset(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, record->offset);
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::playcount(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_I4);
+ JSAPI_SET_RESULT(pvarResult, lVal, record->playcnt);
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
+
+HRESULT JSAPI2::HistoryRecord::lastplay(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult)
+{
+ if (wFlags & DISPATCH_PROPERTYGET)
+ {
+ JSAPI_INIT_RESULT(pvarResult, VT_DATE);
+ if (pvarResult)
+ {
+ __time64_t convertTime = record->lastplayed;
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ SystemTimeToVariantTime(&sysTime, &pvarResult->date);
+ }
+ return S_OK;
+ }
+ else
+ return DISP_E_MEMBERNOTFOUND;
+}
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h
new file mode 100644
index 00000000..065b4edd
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecord.h
@@ -0,0 +1,46 @@
+#pragma once
+#include <dispex.h>
+#include "history.h"
+namespace JSAPI2
+{
+ class HistoryRecord : public IDispatchEx
+ {
+ public:
+ HistoryRecord(IUnknown *_parent, const historyRecord *_record);
+ ~HistoryRecord();
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ void AddObject(IDispatch *obj);
+ private:
+ // *** 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);
+
+ // *** IDispatchEx Methods ***
+ STDMETHOD (GetDispID)(BSTR bstrName, DWORD grfdex, DISPID *pid);
+ STDMETHOD (InvokeEx)(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller);
+ STDMETHOD (DeleteMemberByName)(BSTR bstrName, DWORD grfdex) ;
+ STDMETHOD (DeleteMemberByDispID)(DISPID id);
+ STDMETHOD (GetMemberProperties)(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex);
+ STDMETHOD (GetMemberName)(DISPID id, BSTR *pbstrName);
+ STDMETHOD (GetNextDispID)(DWORD grfdex, DISPID id, DISPID *pid);
+ STDMETHOD (GetNameSpaceParent)(IUnknown **ppunk);
+ private:
+ const historyRecord *record;
+ IUnknown *parent;
+ volatile LONG refCount;
+
+ STDMETHOD (filename)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (title)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (length)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (playcount)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (lastplay)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+ STDMETHOD (offset)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult);
+
+ };
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp
new file mode 100644
index 00000000..ee949c48
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.cpp
@@ -0,0 +1,198 @@
+#include "JSAPI2_HistoryRecordList.h"
+#include "JSAPI2_HistoryRecord.h"
+#include "ml_history.h"
+#include "../Winamp/JSAPI.h"
+#include <strsafe.h>
+
+JSAPI2::HistoryRecordList::HistoryRecordList(historyRecordList *_record)
+{
+ recordList = _record;
+ refCount = 1;
+}
+
+JSAPI2::HistoryRecordList::~HistoryRecordList()
+{
+ freeRecentRecordList(recordList);
+}
+
+enum
+{
+ OBJ_ARRAY_DISP_LENGTH,
+ OBJ_ARRAY_NUM_DISP,
+};
+
+HRESULT JSAPI2::HistoryRecordList::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++)
+ {
+ if (GetDispID(rgszNames[i], fdexNameCaseSensitive, &rgdispid[i]) == DISPID_UNKNOWN)
+ unknowns=true;
+ }
+ if (unknowns)
+ return DISP_E_UNKNOWNNAME;
+ else
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryRecordList::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
+{
+ return InvokeEx(dispid, lcid, wFlags, pdispparams, pvarResult, pexecinfo, 0);
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetTypeInfoCount(unsigned int FAR * pctinfo)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP JSAPI2::HistoryRecordList::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 if (IsEqualIID(riid, IID_IDispatchEx))
+ *ppvObject = (IDispatchEx *)this;
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+ULONG JSAPI2::HistoryRecordList::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+
+ULONG JSAPI2::HistoryRecordList::Release(void)
+{
+ LONG lRef = InterlockedDecrement(&refCount);
+ if (lRef == 0) delete this;
+ return lRef;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetDispID(BSTR bstrName, DWORD grfdex, DISPID *pid)
+{
+ if (!_wcsicmp(bstrName, L"length"))
+ *pid=OBJ_ARRAY_DISP_LENGTH;
+ else
+ {
+ if (bstrName[0] >= L'0' && bstrName[0] <= L'9')
+ *pid=_wtoi(bstrName) + OBJ_ARRAY_NUM_DISP;
+ else
+ return DISP_E_MEMBERNOTFOUND;
+
+ }
+ return S_OK;
+}
+
+HRESULT JSAPI2::HistoryRecordList::InvokeEx(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
+{
+ JSAPI_VERIFY_PARAMCOUNT(pdp, 0);
+
+ switch(id)
+ {
+ case OBJ_ARRAY_DISP_LENGTH:
+ {
+ JSAPI_INIT_RESULT(pvarRes, VT_I4);
+ JSAPI_SET_RESULT(pvarRes, lVal, recordList->Size);
+ return S_OK;
+ }
+ default:
+ {
+ if (id < OBJ_ARRAY_NUM_DISP)
+ return DISP_E_MEMBERNOTFOUND;
+
+ int index = id - OBJ_ARRAY_NUM_DISP;
+ if (index>=recordList->Size)
+ return DISP_E_MEMBERNOTFOUND;
+
+ JSAPI_INIT_RESULT(pvarRes, VT_DISPATCH);
+ JSAPI_SET_RESULT(pvarRes, pdispVal, new JSAPI2::HistoryRecord(this, &recordList->Items[index]));
+ return S_OK;
+ }
+ }
+
+}
+
+HRESULT JSAPI2::HistoryRecordList::DeleteMemberByName(BSTR bstrName, DWORD grfdex)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::DeleteMemberByDispID(DISPID id)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetMemberProperties(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetMemberName(DISPID id, BSTR *pbstrName)
+{
+ if (id >= OBJ_ARRAY_NUM_DISP)
+ {
+ wchar_t temp[64];
+ StringCbPrintfW(temp, sizeof(temp), L"%d", id-OBJ_ARRAY_NUM_DISP);
+ *pbstrName = SysAllocString(temp);
+ return S_OK;
+ }
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetNextDispID(DWORD grfdex, DISPID id, DISPID *pid)
+{
+ if (grfdex == fdexEnumDefault)
+ {
+ if (id == DISPID_UNKNOWN)
+ {
+ if (recordList->Size == 0)
+ return S_FALSE;
+ else
+ {
+ *pid = OBJ_ARRAY_NUM_DISP;
+ return S_OK;
+ }
+ }
+ else if (id < OBJ_ARRAY_NUM_DISP)
+ {
+ return S_FALSE;
+ }
+ else
+ {
+ int index = (id - OBJ_ARRAY_NUM_DISP) + 1;
+ if (index >= recordList->Size)
+ {
+ return S_FALSE;
+ }
+ else
+ {
+ *pid = OBJ_ARRAY_NUM_DISP + index;
+ return S_OK;
+ }
+
+ }
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT JSAPI2::HistoryRecordList::GetNameSpaceParent(IUnknown **ppunk)
+{
+ return E_NOTIMPL;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h
new file mode 100644
index 00000000..961614ab
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/JSAPI2_HistoryRecordList.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <dispex.h>
+#include "history.h"
+namespace JSAPI2
+{
+ class HistoryRecordList : public IDispatchEx
+ {
+ public:
+ HistoryRecordList(historyRecordList *_record);
+ ~HistoryRecordList();
+ STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
+ STDMETHOD_(ULONG, AddRef)(void);
+ STDMETHOD_(ULONG, Release)(void);
+
+ void AddObject(IDispatch *obj);
+ private:
+ // *** 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);
+
+ // *** IDispatchEx Methods ***
+ STDMETHOD (GetDispID)(BSTR bstrName, DWORD grfdex, DISPID *pid);
+ STDMETHOD (InvokeEx)(DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller);
+ STDMETHOD (DeleteMemberByName)(BSTR bstrName, DWORD grfdex) ;
+ STDMETHOD (DeleteMemberByDispID)(DISPID id);
+ STDMETHOD (GetMemberProperties)(DISPID id, DWORD grfdexFetch, DWORD *pgrfdex);
+ STDMETHOD (GetMemberName)(DISPID id, BSTR *pbstrName);
+ STDMETHOD (GetNextDispID)(DWORD grfdex, DISPID id, DISPID *pid);
+ STDMETHOD (GetNameSpaceParent)(IUnknown **ppunk);
+ private:
+ historyRecordList *recordList;
+ volatile LONG refCount;
+ };
+
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/Main.cpp b/Src/Plugins/Library/ml_history/Main.cpp
new file mode 100644
index 00000000..7f8391d4
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/Main.cpp
@@ -0,0 +1,349 @@
+#include "api__ml_history.h"
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "HistoryAPIFactory.h"
+#include "JSAPI2_Creator.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../nu/AutoCharFn.h"
+#include <strsafe.h>
+
+#define LOCAL_WRITE_VER L"2.03"
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
+JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = 0;
+api_application *WASABI_API_APP=0;
+
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+static HistoryAPIFactory historyAPIFactory;
+static JSAPI2Factory jsapi2Factory;
+
+static int Init();
+static void Quit();
+static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+prefsDlgRecW preferences = {0};
+wchar_t g_tableDir[MAX_PATH] = {0};
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_history.dll)",
+ Init,
+ Quit,
+ MessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+// Delay load library control << begin >>
+#include <delayimp.h>
+#pragma comment(lib, "delayimp")
+
+bool nde_error = false;
+
+FARPROC WINAPI FailHook(unsigned dliNotify, PDelayLoadInfo pdli)
+{
+ nde_error = true;
+ return 0;
+}
+/*
+extern "C"
+{
+ PfnDliHook __pfnDliFailureHook2 = FailHook;
+}
+*/
+// Delay load library control << end >>
+
+int Init()
+{
+ InitializeCriticalSection(&g_db_cs);
+
+ g_db = NULL;
+ g_table = NULL;
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ wchar_t configName[ MAX_PATH ] = { 0 };
+ const wchar_t *dir = mediaLibrary.GetIniDirectoryW();
+
+ if ( (INT_PTR)( dir ) < 65536 )
+ return ML_INIT_FAILURE;
+
+
+ PathCombineW( g_tableDir, dir, L"Plugins" );
+ PathCombineW( configName, g_tableDir, L"gen_ml.ini" );
+
+ g_config = new C_Config( configName );
+
+ CreateDirectoryW( g_tableDir, NULL );
+ PathCombineW( g_tableDir, g_tableDir, L"ml" );
+ CreateDirectoryW( g_tableDir, NULL );
+
+ // loader so that we can get the localisation service api for use
+
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
+ if ( sf )
+ WASABI_API_LNG = reinterpret_cast<api_language *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( JSAPI2::api_securityGUID );
+ if ( sf )
+ AGAVE_API_JSAPI2_SECURITY = reinterpret_cast<JSAPI2::api_security *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
+ if ( sf )
+ WASABI_API_APP = reinterpret_cast<api_application *>( sf->getInterface() );
+
+ sf = plugin.service->service_getServiceByGuid( ExplorerFindFileApiGUID );
+ if ( sf )
+ WASABI_API_EXPLORERFINDFILE = reinterpret_cast<api_explorerfindfile *>( sf->getInterface() );
+
+ plugin.service->service_register( &historyAPIFactory );
+ plugin.service->service_register( &jsapi2Factory );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlHistoryLangGUID );
+
+ g_context_menus2 = WASABI_API_LOADMENUW(IDR_CONTEXTMENUS);
+
+ static wchar_t szDescription[256];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_HISTORY ), LOCAL_WRITE_VER );
+
+
+ plugin.description = (char*)szDescription;
+
+ static wchar_t preferencesName[64] = {0};
+ preferences.hInst = WASABI_API_LNG_HINST;
+ preferences.dlgID = IDD_PREFS;
+ preferences.proc = (void *)PrefsProc;
+ preferences.name = WASABI_API_LNGSTRINGW_BUF( IDS_HISTORY, preferencesName, 64 );
+ preferences.where = 6; // media library
+
+ SENDWAIPC( plugin.hwndWinampParent, IPC_ADD_PREFS_DLGW, &preferences );
+
+ if ( !history_init() )
+ return ML_INIT_FAILURE;
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ plugin.service->service_deregister(&historyAPIFactory);
+ plugin.service->service_deregister(&jsapi2Factory);
+ history_quit();
+ delete g_config;
+ DeleteCriticalSection(&g_db_cs);
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(languageApiGUID);
+ if (sf) sf->releaseInterface(WASABI_API_LNG);
+
+ sf = plugin.service->service_getServiceByGuid(JSAPI2::api_securityGUID);
+ if (sf) sf->releaseInterface(AGAVE_API_JSAPI2_SECURITY);
+}
+
+static INT_PTR History_OnContextMenu(INT_PTR param1, HWND hHost, POINTS pts)
+{
+ HNAVITEM hItem = (HNAVITEM)param1;
+ HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, ml_history_tree);
+ if (hItem != myItem)
+ return FALSE;
+
+ POINT pt;
+ POINTSTOPOINT(pt, pts);
+ if (-1 == pt.x || -1 == pt.y)
+ {
+ 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 hMenu = WASABI_API_LOADMENUW(IDR_CONTEXTMENUS);
+ HMENU subMenu = (NULL != hMenu) ? GetSubMenu(hMenu, 1) : NULL;
+ if (NULL != subMenu)
+ {
+
+ INT r = Menu_TrackPopup(plugin.hwndLibraryParent, subMenu,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY |
+ TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON,
+ pt.x, pt.y, hHost, NULL);
+
+ switch(r)
+ {
+ case ID_NAVIGATION_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &preferences);
+ break;
+ case ID_NAVIGATION_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8105304048660-The-Winamp-Media-Library");
+ break;
+ }
+ }
+
+ if (NULL != hMenu)
+ DestroyMenu(hMenu);
+
+ return TRUE;
+}
+
+void History_StartTracking(const wchar_t *filename, bool resume)
+{
+ KillTimer(plugin.hwndWinampParent, 8082);
+ if (!resume)
+ {
+ free(history_fn);
+ history_fn = 0;
+ history_fn_mode = 0;
+ if (wcsstr(filename, L"://") && _wcsnicmp(filename, L"cda://", 6) && _wcsnicmp(filename, L"file://", 7))
+ {
+ history_fn_mode = 1;
+ }
+ history_fn = _wcsdup(filename);
+ }
+
+ int timer1 = -1, timer2 = -1, timer3 = -1;
+
+ // wait for x seconds
+ if(g_config->ReadInt(L"recent_wait_secs",0))
+ {
+ timer1 = g_config->ReadInt(L"recent_wait_secs_lim",5)*1000;
+ }
+
+ // wait for x percent of the song (approx to a second)
+ if(g_config->ReadInt(L"recent_wait_percent",0))
+ {
+ basicFileInfoStructW bfiW = {0};
+ bfiW.filename = history_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"recent_wait_percent_lim",50))/100;
+ }
+ }
+
+ // wait for the end of the item (within the last second of the track hopefully)
+ if(g_config->ReadInt(L"recent_wait_end",0))
+ {
+ basicFileInfoStructW bfiW = {0};
+ bfiW.filename = history_fn;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&bfiW, IPC_GET_BASIC_FILE_INFOW);
+ if(bfiW.length > 0)
+ {
+ timer3=(bfiW.length-1)*1000;
+ }
+ }
+
+ // 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;
+ }
+
+ // only track on end of file as very last method
+ if((timer <= 0) && (timer3 > 0)){ timer = timer3; }
+
+ // if no match or something went wrong then try to ensure the default timer value is used
+ SetTimer(plugin.hwndWinampParent, 8082, ((timer > 0)? timer : 350), NULL);
+}
+
+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
+ return (param1 == ml_history_tree) ? (INT_PTR)onTreeViewSelectChange((HWND)param2) : 0;
+
+ case ML_MSG_CONFIG:
+ mediaLibrary.GoToPreferences((int)preferences._id);
+ return TRUE;
+
+ case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
+ enqueuedef = (int)param1;
+ groupBtn = (int)param2;
+ PostMessage(m_curview_hwnd, WM_APP + 104, param1, param2);
+ return 0;
+
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ return History_OnContextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
+
+ case ML_MSG_PLAYING_FILE:
+ if (param1)
+ {
+ int resume = g_config->ReadInt(L"resumeplayback",0);
+ if(resume)
+ {
+ int is_playing = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_ISPLAYING);
+ //int play_pos = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETOUTPUTTIME);
+ if(is_playing == 1/* && !(play_pos/1000 > 0)*/) //playing, look up last play offset and send seek message
+ {
+ wchar_t genre[256]={0};
+ extendedFileInfoStructW efis={
+ (wchar_t*)param1,
+ L"genre",
+ genre,
+ ARRAYSIZE(genre),
+ };
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis,IPC_GET_EXTENDED_FILE_INFOW);
+
+ wchar_t ispodcast[8]={0};
+ extendedFileInfoStructW efis1={
+ (wchar_t*)param1,
+ L"ispodcast",
+ ispodcast,
+ ARRAYSIZE(ispodcast),
+ };
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efis1,IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE);
+
+ if (resume == 2 || (ispodcast[0] && _wtoi(ispodcast) > 0) || (genre[0] && !_wcsicmp(genre, L"podcast")))
+ {
+ int offset = retrieve_offset((wchar_t*)param1);
+ if (offset > 0 && (offset/1000 > 0)) PostMessage(plugin.hwndWinampParent,WM_WA_IPC,offset,IPC_JUMPTOTIME);
+ }
+ }
+ }
+
+ History_StartTracking((const wchar_t *)param1, false);
+ }
+ break;
+
+ case ML_MSG_WRITE_CONFIG:
+ if(param1)
+ {
+ closeDb();
+ openDb();
+ }
+ break;
+ }
+ return 0;
+}
+
+extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+{
+ return &plugin;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/Main.h b/Src/Plugins/Library/ml_history/Main.h
new file mode 100644
index 00000000..685b8ad1
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/Main.h
@@ -0,0 +1,34 @@
+#ifndef NULLSOFT_MAINH
+#define NULLSOFT_MAINH
+
+#include <windows.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "resource.h"
+#include "../nu/MediaLibraryInterface.h"
+#include "..\..\General\gen_ml/menu.h"
+#include <commctrl.h>
+#include <shlwapi.h>
+#include "ml_history.h"
+#include <windowsx.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include "../nde/nde_c.h"
+#include "api__ml_history.h"
+
+#include "../Winamp/JSAPI2_svc_apicreator.h"
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+extern winampMediaLibraryPlugin plugin;
+extern bool nde_error;
+extern int history_fn_mode;
+extern wchar_t *history_fn;
+extern int timer;
+extern HWND m_curview_hwnd;
+extern int groupBtn, enqueuedef;
+extern HMENU g_context_menus2;
+
+void History_StartTracking(const wchar_t *filename, bool resume);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/api__ml_history.h b/Src/Plugins/Library/ml_history/api__ml_history.h
new file mode 100644
index 00000000..ae410bf1
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/api__ml_history.h
@@ -0,0 +1,18 @@
+#ifndef NULLSOFT_ML_HISTORY_API_H
+#define NULLSOFT_ML_HISTORY_API_H
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../Winamp/JSAPI2_api_security.h"
+extern JSAPI2::api_security *jsapi2_security;
+#define AGAVE_API_JSAPI2_SECURITY jsapi2_security
+
+#include <api/service/waServiceFactory.h>
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
+
+#endif // !NULLSOFT_ML_HISTORY_API_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/api_history.cpp b/Src/Plugins/Library/ml_history/api_history.cpp
new file mode 100644
index 00000000..b13f171d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/api_history.cpp
@@ -0,0 +1 @@
+#include "api_history.h"
diff --git a/Src/Plugins/Library/ml_history/api_history.h b/Src/Plugins/Library/ml_history/api_history.h
new file mode 100644
index 00000000..3f987eb3
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/api_history.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <bfc/dispatch.h>
+#include "history.h"
+
+class api_history : public Dispatchable
+{
+protected:
+ api_history() {}
+ ~api_history() {}
+public:
+ historyRecordList *Query(const wchar_t *query);
+ void FreeHistoryList(historyRecordList *historyList);
+
+ enum
+ {
+ API_HISTORY_QUERY = 0,
+ API_HISTORY_FREEHISTORYLIST = 1,
+ };
+};
+
+inline historyRecordList *api_history::Query(const wchar_t *query)
+{
+ return _call(API_HISTORY_QUERY, (historyRecordList *)0, query);
+}
+
+inline void api_history::FreeHistoryList(historyRecordList *historyList)
+{
+ _voidcall(API_HISTORY_FREEHISTORYLIST, historyList);
+}
+
+// {F9BF9119-D163-4118-BEA7-5980869DBB2E}
+static const GUID HistoryApiGuid =
+{ 0xf9bf9119, 0xd163, 0x4118, { 0xbe, 0xa7, 0x59, 0x80, 0x86, 0x9d, 0xbb, 0x2e } };
diff --git a/Src/Plugins/Library/ml_history/db_error.txt b/Src/Plugins/Library/ml_history/db_error.txt
new file mode 100644
index 00000000..0705b100
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/db_error.txt
@@ -0,0 +1,6 @@
+
+There was an error reading the history database. This could be due to the database becoming corrupted or having been removed.
+
+If the database files (recent.dat and recent.idx) have become corrupted then unless you have a back up, you will need to reset the database.
+
+By resetting the database, any playing history 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_history/history.h b/Src/Plugins/Library/ml_history/history.h
new file mode 100644
index 00000000..6e1e8486
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/history.h
@@ -0,0 +1,35 @@
+#pragma once
+#ifndef NULLSOFT_ML_HISTORY_HISTORY_H
+#define NULLSOFT_ML_HISTORY_HISTORY_H
+
+#include <time.h>
+typedef struct
+{
+ wchar_t *filename;
+ wchar_t *title;
+ wchar_t *ext;
+ int length;
+ unsigned int playcnt;
+ __time64_t lastplayed;
+ int offset;
+} historyRecord;
+
+typedef struct
+{
+ historyRecord *Items;
+ int Size;
+ int Alloc;
+} historyRecordList;
+
+enum
+{
+ HISTORY_SORT_LASTPLAYED = 0,
+ HISTORY_SORT_PLAYCOUNT = 1,
+ HISTORY_SORT_TITLE = 2,
+ HISTORY_SORT_LENGTH = 3,
+ HISTORY_SORT_FILENAME = 4,
+ HISTORY_SORT_OFFSET = 5,
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/ml_history.cpp b/Src/Plugins/Library/ml_history/ml_history.cpp
new file mode 100644
index 00000000..24cb7aa1
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.cpp
@@ -0,0 +1,289 @@
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "..\..\General\gen_ml/MediaLibraryCOM.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <malloc.h>
+
+int ml_history_tree = -1;
+HWND m_curview_hwnd = NULL;
+static WNDPROC wa_oldWndProc=0;
+wchar_t *history_fn = 0;
+int timer = -1;
+static wchar_t *last_history_fn;
+static int last_timer=-1;
+static int last_play_pos=0;
+int history_fn_mode = 0;
+
+nde_database_t g_db = NULL;
+nde_table_t g_table = NULL;
+int g_table_dirty = 0;
+C_Config *g_config;
+CRITICAL_SECTION g_db_cs;
+
+HWND onTreeViewSelectChange(HWND hwnd)
+{
+ openDb();
+ if (m_curview_hwnd) DestroyWindow(m_curview_hwnd);
+
+ if (!g_table || nde_error)
+ {
+ m_curview_hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_DB_ERROR, hwnd, view_errorinfoDialogProc, 0);
+ }
+ else
+ {
+ m_curview_hwnd = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_RECENTITEMS, hwnd, view_historyDialogProc, 0);
+ }
+ return m_curview_hwnd;
+}
+
+static void history_cleanupifnecessary();
+static LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+int history_init()
+{
+ if ( !g_config->ReadInt( L"showrecentitems", 1 ) )
+ return 1;
+
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW(IDS_HISTORY);
+ nis.item.pszInvariant = L"History";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.iSelectedImage = nis.item.iImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_RECENT);
+
+ // 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);
+ ml_history_tree = nvItem.id;
+
+ if (!wa_oldWndProc) // don't double dip (we call history_init() dynamically if the user fiddles with prefs
+ {
+ wa_oldWndProc = (WNDPROC)SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)wa_newWndProc);
+ }
+
+
+ return 1;
+}
+
+void history_quit()
+{
+ if (last_history_fn)
+ {
+ if ((last_timer <= 0) || (last_play_pos > last_timer))
+ {
+ if (last_play_pos) history_onFile(last_history_fn, last_play_pos);
+ }
+ }
+ free(last_history_fn);
+ last_history_fn = 0;
+ free(history_fn);
+ history_fn = 0;
+ closeDb();
+ mediaLibrary.RemoveTreeItem(ml_history_tree);
+ ml_history_tree=0;
+}
+
+static void RetypeFilename(nde_table_t table)
+{
+ // TODO: UI
+ int totalRecords = NDE_Table_GetRecordsCount(g_table);
+ if (totalRecords == 0) // bail out early so we don't flash a dialog
+ return;
+ nde_scanner_t pruneScanner = NDE_Table_CreateScanner(table);
+ if (pruneScanner)
+ {
+ NDE_Scanner_First(pruneScanner);
+ while (!NDE_Scanner_EOF(pruneScanner))
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(pruneScanner, HISTORYVIEW_COL_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, HISTORYVIEW_COL_FILENAME);
+ NDE_StringField_SetNDEString(new_f, s);
+
+ ndestring_release(s);
+ NDE_Scanner_Post(pruneScanner);
+ }
+ else if (f)
+ break;
+
+ NDE_Scanner_Next(pruneScanner);
+ }
+
+ NDE_Table_DestroyScanner(table, pruneScanner);
+ NDE_Table_Sync(table);
+ }
+}
+
+static void CreateFields(nde_table_t table)
+{
+ // create defaults
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_LASTPLAYED, L"lastplay", FIELD_DATETIME);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_PLAYCOUNT, L"playcount", FIELD_INTEGER);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_TITLE, L"title", FIELD_STRING);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_LENGTH, L"length", FIELD_INTEGER);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_FILENAME, L"filename", FIELD_FILENAME);
+ NDE_Table_NewColumnW(g_table, HISTORYVIEW_COL_OFFSET, L"offset", FIELD_INTEGER);
+ NDE_Table_PostColumns(g_table);
+ NDE_Table_AddIndexByIDW(g_table, 0, L"filename");}
+
+int openDb()
+{
+ if (g_table) return 1; // need to close first
+
+ 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 1;
+ }
+
+ if (!g_db)
+ {
+ __try
+ {
+ g_db = NDE_CreateDatabase(plugin.hDllInstance);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ g_db = NULL;
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ }
+
+ wchar_t tableName[MAX_PATH] = {0}, indexName[MAX_PATH] = {0};
+ PathCombineW(tableName, g_tableDir, L"recent.dat");
+ PathCombineW(indexName, g_tableDir, L"recent.idx");
+
+ if (!g_db)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return 0;
+ }
+ g_table = NDE_Database_OpenTable(g_db, tableName, indexName, NDE_OPEN_ALWAYS, NDE_CACHE);
+ if (g_table)
+ {
+ CreateFields(g_table);
+ RetypeFilename(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+ return (g_table != NULL);
+}
+
+void closeDb(bool clear_dirty)
+{
+ if (g_table_dirty && g_table)
+ {
+ history_bgQuery_Stop();
+ NDE_Table_Sync(g_table);
+ if (clear_dirty) g_table_dirty=0;
+ }
+ if (g_db)
+ {
+ __try
+ {
+ if (g_table)
+ NDE_Database_CloseTable(g_db, g_table);
+
+ NDE_DestroyDatabase(g_db);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ }
+ }
+ g_db = NULL;
+ g_table = NULL;
+}
+
+INT_PTR pluginHandleIpcMessage(int msg, INT_PTR param)
+{
+ return (INT_PTR) SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
+}
+
+static LRESULT APIENTRY wa_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if ((uMsg == WM_SYSKEYDOWN || uMsg == WM_KEYDOWN) &&
+ wParam == 'H' &&
+ !(GetAsyncKeyState(VK_MENU)&0x8000) &&
+ !(GetAsyncKeyState(VK_SHIFT)&0x8000) &&
+ (GetAsyncKeyState(VK_CONTROL)&0x8000)
+ && ml_history_tree > 0)
+ {
+ mediaLibrary.ShowMediaLibrary();
+ mediaLibrary.SelectTreeItem(ml_history_tree);
+ }
+ else if (history_fn && uMsg == WM_TIMER && wParam == 8082)
+ {
+ if (!history_fn_mode || SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_GETOUTPUTTIME) > 350)
+ {
+ KillTimer(hwndDlg, 8082);
+ if (SendMessage(hwndDlg, WM_WA_IPC, 0, IPC_ISPLAYING) == 1)
+ {
+ history_onFile(history_fn, -1);
+ }
+ free(history_fn);
+ history_fn = 0;
+ }
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+ }
+ else if (last_history_fn && uMsg == WM_TIMER && wParam == 8083)
+ {
+ KillTimer(hwndDlg, 8083);
+ history_onFile(last_history_fn, last_play_pos);
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+ }
+ else if (uMsg == WM_WA_IPC)
+ {
+ if(lParam == IPC_STOPPLAYING && g_config->ReadInt(L"resumeplayback",0))
+ {
+ KillTimer(hwndDlg, 8082);
+ if (last_history_fn)
+ {
+ free(last_history_fn);
+ last_history_fn = 0;
+ }
+ if (history_fn) last_history_fn = _wcsdup(history_fn);
+ // copes with stopping after playback was tracked otherwise this aspect will fail!
+ else last_history_fn = _wcsdup((wchar_t*)SendMessage(hwndDlg,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME));
+ last_timer = timer;
+
+ stopPlayingInfoStruct *stopPlayingInfo = (stopPlayingInfoStruct *)wParam;
+ last_play_pos = stopPlayingInfo->last_time;
+
+ if (!stopPlayingInfo->g_fullstop)
+ {
+ if ((last_timer <= 0) || (last_play_pos > last_timer))
+ {
+ if (last_play_pos) SetTimer(hwndDlg, 8083, 150, NULL);
+ }
+ }
+ else // clean up play offset
+ {
+ if (last_history_fn) history_onFile(last_history_fn, 0);
+ }
+ }
+ else if(lParam == IPC_CB_MISC)
+ {
+ if (wParam == IPC_CB_MISC_PAUSE)
+ {
+ KillTimer(hwndDlg, 8082);
+ }
+ else if (wParam == IPC_CB_MISC_UNPAUSE)
+ {
+ if (history_fn) History_StartTracking(history_fn, true);
+ }
+ }
+ } // wm_wa_ipc
+ return CallWindowProcW(wa_oldWndProc, hwndDlg, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/ml_history.h b/Src/Plugins/Library/ml_history/ml_history.h
new file mode 100644
index 00000000..4e8e60f5
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.h
@@ -0,0 +1,64 @@
+#ifndef ML_HISTORY_MAIN_H
+#define ML_HISTORY_MAIN_H
+
+#include "main.h"
+#include <windows.h>
+#include <commctrl.h>
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/config.h"
+#include "../nde/nde_c.h"
+
+#define HISTORYVIEW_COL_LASTPLAYED 0
+#define HISTORYVIEW_COL_PLAYCOUNT 1
+#define HISTORYVIEW_COL_TITLE 2
+#define HISTORYVIEW_COL_LENGTH 3
+#define HISTORYVIEW_COL_FILENAME 4
+#define HISTORYVIEW_COL_OFFSET 5
+
+#define UPDATE_QUERY_TIMER_ID 505
+
+extern int ml_history_tree;
+HWND onTreeViewSelectChange(HWND hwnd);
+
+int history_init();
+void history_quit();
+
+int openDb();
+void closeDb(bool clear_dirty=true);
+extern wchar_t g_tableDir[];
+extern C_Config *g_config;
+
+extern CRITICAL_SECTION g_db_cs;
+extern nde_database_t g_db;
+extern nde_table_t g_table;
+extern int g_table_dirty;
+
+inline BOOL WINAPI IsCharSpaceW(wchar_t c) { return (c == L' ' || c == L'\t'); }
+inline bool IsTheW(const wchar_t *str) { if (str && (str[0] == L't' || str[0] == L'T') && (str[1] == L'h' || str[1] == L'H') && (str[2] == L'e' || str[2] == L'E') && (str[3] == L' ')) return true; else return false; }
+#define SKIP_THE_AND_WHITESPACE(x) { wchar_t *save##x=(wchar_t*)x; while (IsCharSpaceW(*x) && *x) x++; if (IsTheW(x)) x+=4; while (IsCharSpaceW(*x)) x++; if (!*x) x=save##x; }
+
+void history_bgQuery_Stop();
+void history_onFile(const wchar_t *fn, int offset);
+int retrieve_offset(const wchar_t *fn);
+BOOL CALLBACK view_historyDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+BOOL CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+void db_setFieldInt(nde_scanner_t s, unsigned char id, int data);
+void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data);
+void makeFilename2(const wchar_t *filename, wchar_t *filename2, int filename2_len);
+void queryStrEscape(const char *p, GayString &str);
+INT_PTR pluginHandleIpcMessage(int msg, INT_PTR param);
+
+//prefs.cpp
+BOOL CALLBACK PrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+#include "history.h"
+
+void allocRecentRecordList(historyRecordList *obj, int newsize, int granularity=512);
+void emptyRecentRecordList(historyRecordList *obj);
+void recentScannerRefToObjCacheNFN(nde_scanner_t s, historyRecordList *obj);
+void sortResults(historyRecordList *obj, int column, int dir);
+void freeRecentRecordList(historyRecordList *obj);
+void saveQueryToList(nde_scanner_t s, historyRecordList *obj);
+
+#endif ML_HISTORY_MAIN_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/ml_history.rc b/Src/Plugins/Library/ml_history/ml_history.rc
new file mode 100644
index 00000000..6627b89a
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.rc
@@ -0,0 +1,264 @@
+// 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_RECENTITEMS DIALOGEX 0, 0, 291, 248
+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
+ 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,220
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,237,35,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,38,237,35,11
+ CONTROL "Remove",IDC_REMOVEBOOK,"Button",BS_OWNERDRAW | WS_TABSTOP,76,237,36,11
+ CONTROL "",IDC_MEDIASTATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,113,238,178,10
+END
+
+IDD_PREFS DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "History",IDC_STATIC,0,0,272,246
+ LTEXT "When 'History' is enabled, Winamp will keep track of when and how many times any items are played through Winamp.",IDC_STATIC2,6,12,259,16
+ CONTROL "Enable 'History' view in Media Library",IDC_CHECK1,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,34,132,10
+ CONTROL "Track when and how many times all files are played",IDC_CHECK2,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,49,177,10
+ CONTROL "Track when and how many times all streams are played",IDC_CHECK3,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,62,189,10
+ CONTROL "Limit tracking to items played in the last",IDC_CHECK4,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,16,75,138,10
+ EDITTEXT IDC_EDIT1,157,74,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "days",IDC_STATIC1,183,76,16,8
+ GROUPBOX "Tracking Control",IDC_STATIC,16,89,249,89
+ CONTROL "Wait",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,103,30,8
+ EDITTEXT IDC_EDIT2,53,101,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "seconds before tracking items",IDC_STATIC4,81,103,174,8
+ CONTROL "Wait",IDC_CHECK6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,23,119,30,8
+ EDITTEXT IDC_EDIT3,53,117,24,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "percent of playback before tracking items",IDC_STATIC5,81,119,174,8
+ CONTROL "Wait until the end of complete playback\n(May count before the displayed end due to output buffer sizes)",IDC_CHECK7,
+ "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,23,133,232,16
+ LTEXT "Note: When the first match against any of the above selected option(s) is met then the currently playing item will be tracked.",IDC_STATIC6,23,156,235,16
+ GROUPBOX "Playback Resume",IDC_STATIC,16,184,249,52
+ COMBOBOX IDC_COMBO1,22,196,236,39,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Note: This will make Winamp resume playback of tracked files from the last played position (once the first Tracking Control target has been reached).",IDC_STATIC7,22,212,249,16
+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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "RecentWnd"
+ BEGIN
+ MENUITEM "Play selection\tEnter", ID_MEDIAWND_PLAYSELECTEDFILES
+ MENUITEM "Enqueue selection\tShift+Enter", ID_MEDIAWND_ENQUEUESELECTEDFILES
+ POPUP "Send to:"
+ BEGIN
+ MENUITEM "", 40003
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select all\tCtrl+A", ID_MEDIAWND_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "View f&ile info...\tAlt+3", ID_PE_ID3
+ MENUITEM "Remove from recent list\tDel", ID_MEDIAWND_REMOVEFROMLIBRARY
+ MENUITEM SEPARATOR
+ MENUITEM "&Clear playback offset\tShift+Del", ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY
+ MENUITEM SEPARATOR
+ MENUITEM "Explore item folder\tCtrl+F", 40007
+ END
+ POPUP "Navigation"
+ BEGIN
+ MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "Help", ID_NAVIGATION_HELP
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_PREFS, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 246
+ END
+
+ IDD_VIEW_DB_ERROR, DIALOG
+ BEGIN
+ LEFTMARGIN, 10
+ RIGHTMARGIN, 184
+ TOPMARGIN, 10
+ BOTTOMMARGIN, 156
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+IDB_TREEITEM_RECENT BITMAP "resources\\ti_history_items_16x16x16.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXT
+//
+
+IDR_DB_ERROR TEXT ".\\db_error.txt"
+IDR_NDE_ERROR TEXT "..\\ml_local\\nde_error.txt"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Accelerator
+//
+
+IDR_VIEW_ACCELERATORS ACCELERATORS
+BEGIN
+ "3", ID_PE_ID3, VIRTKEY, ALT, NOINVERT
+ "A", ID_MEDIAWND_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "F", ID_MEDIAWND_EXPLOREFOLDER, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_MEDIAWND_REMOVEFROMLIBRARY, VIRTKEY, NOINVERT
+ VK_DELETE, ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_MEDIAWND_PLAYSELECTEDFILES, VIRTKEY, NOINVERT
+ VK_RETURN, ID_MEDIAWND_ENQUEUESELECTEDFILES, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_HISTORY "Nullsoft History v%s"
+ 65535 "{F8756C00-11D2-4857-8C50-163AE4A57783}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_HISTORY "History"
+ IDS_DELAYLOAD_FAILURE "History was unable to load library ('%s') which is required. \nHistory cannot continue succesfully and will be turned off."
+ IDS_ERROR "Error"
+ IDS_SCANNING_ELLIPSE "Scanning..."
+ IDS_COL_LAST_PLAYED "Last Played"
+ IDS_COL_PLAY_COUNT "Play Count"
+ IDS_COL_TITLE "Title"
+ IDS_COL_LENGTH "Length"
+ IDS_COL_FILENAME "Filename"
+ IDS_EST_TIME_NO_SECS "%d %s, %u %s [%u:%02u estimated playtime]"
+ IDS_EST_TIME_HAS_SECS "%d %s, %u %s [%u:%02u:%02u estimated playtime]"
+ IDS_SCANNING "Scanning"
+ IDS_ITEM "item"
+ IDS_ITEMS "items"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_PLAY "play"
+ IDS_PLAYS "plays"
+ IDS_DAY "day"
+ IDS_DAYS "days"
+ IDS_REMOVE_ALL_HISTORY "Are you sure you want to reset your playing history?"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_DISABLED "Disabled"
+ IDS_PODCAST_ONLY "Enabled for Podcast files only"
+ IDS_ANY_APPLICABLE "Enabled for any applicable files"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COL_OFFSET "Play Offset"
+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_history/ml_history.sln b/Src/Plugins/Library/ml_history/ml_history.sln
new file mode 100644
index 00000000..e8f35db5
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.sln
@@ -0,0 +1,94 @@
+
+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_history", "ml_history.vcxproj", "{840EEC3D-03D7-4186-8FC7-763392D13309}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A} = {4D25C321-7F8B-424E-9899-D80A364BAF1A}
+ {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}") = "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
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|Win32.ActiveCfg = Debug|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|Win32.Build.0 = Debug|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|x64.ActiveCfg = Debug|x64
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Debug|x64.Build.0 = Debug|x64
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|Win32.ActiveCfg = Release|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|Win32.Build.0 = Release|Win32
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.Release|x64.ActiveCfg = Release|x64
+ {840EEC3D-03D7-4186-8FC7-763392D13309}.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_history/ml_history.vcxproj b/Src/Plugins/Library/ml_history/ml_history.vcxproj
new file mode 100644
index 00000000..0d6cf7cd
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.vcxproj
@@ -0,0 +1,341 @@
+<?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>{840EEC3D-03D7-4186-8FC7-763392D13309}</ProjectGuid>
+ <RootNamespace>ml_history</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_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.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>
+ </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_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.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>
+ </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_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <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_HISTORY_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <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>
+ <ClCompile Include="..\..\General\gen_ml\config.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\gaystring.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp" />
+ <ClCompile Include="api_history.cpp" />
+ <ClCompile Include="HistoryAPI.cpp" />
+ <ClCompile Include="HistoryAPIFactory.cpp" />
+ <ClCompile Include="JSAPI2_Creator.cpp" />
+ <ClCompile Include="JSAPI2_HistoryAPI.cpp" />
+ <ClCompile Include="JSAPI2_HistoryRecord.cpp" />
+ <ClCompile Include="JSAPI2_HistoryRecordList.cpp" />
+ <ClCompile Include="Main.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">main.h</PrecompiledHeaderFile>
+ </ClCompile>
+ <ClCompile Include="ml_history.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="view_history.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="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\Winamp\strutil.h" />
+ <ClInclude Include="api__ml_history.h" />
+ <ClInclude Include="api_history.h" />
+ <ClInclude Include="history.h" />
+ <ClInclude Include="HistoryAPI.h" />
+ <ClInclude Include="HistoryAPIFactory.h" />
+ <ClInclude Include="JSAPI2_Creator.h" />
+ <ClInclude Include="JSAPI2_HistoryAPI.h" />
+ <ClInclude Include="JSAPI2_HistoryRecord.h" />
+ <ClInclude Include="JSAPI2_HistoryRecordList.h" />
+ <ClInclude Include="Main.h" />
+ <ClInclude Include="ml_history.h" />
+ <ClInclude Include="resource.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="..\ml_local\nde_error.txt" />
+ <Text Include="db_error.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_history.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_history_items_16x16x16.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </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_history/ml_history.vcxproj.filters b/Src/Plugins/Library/ml_history/ml_history.vcxproj.filters
new file mode 100644
index 00000000..4912c232
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/ml_history.vcxproj.filters
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="api_history.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HistoryAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="HistoryAPIFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_Creator.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_HistoryAPI.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_HistoryRecord.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="JSAPI2_HistoryRecordList.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_history.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_history.cpp">
+ <Filter>Source Files</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\sort.cpp">
+ <Filter>Source Files\nu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\Winamp\strutil.cpp">
+ <Filter>Source Files\Winamp</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api_history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="HistoryAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="HistoryAPIFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_Creator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_HistoryAPI.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_HistoryRecord.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="JSAPI2_HistoryRecordList.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ml_history.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</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="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files\nu</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\strutil.h">
+ <Filter>Header Files\Winamp</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Text Include="..\ml_local\nde_error.txt" />
+ <Text Include="db_error.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{c2e8f027-5cef-4e02-911f-4719fc3ae234}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{a71fca4c-7b5a-4857-9d5d-ac9a8635df44}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{bd330ba0-d0e2-4ac8-b30d-b936c4549d0c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{b97ef514-3134-4462-b767-54f001121b05}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\gen_ml">
+ <UniqueIdentifier>{73085ff5-df6e-41f0-a474-a193e6007e23}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\nu">
+ <UniqueIdentifier>{6091f753-b15a-4842-b822-d511183811d7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files\Winamp">
+ <UniqueIdentifier>{02146aa6-27b8-4603-89ac-f219ea316867}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\gen_ml">
+ <UniqueIdentifier>{91facc1d-0b70-4a53-a029-b731c6a5fc00}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\nu">
+ <UniqueIdentifier>{a9cf0eec-25c4-45ad-a116-77cc32baf3fa}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files\Winamp">
+ <UniqueIdentifier>{b4d6ce84-5fe5-48af-a5b1-f0e01e83d9c5}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_history.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\ti_history_items_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/prefs.cpp b/Src/Plugins/Library/ml_history/prefs.cpp
new file mode 100644
index 00000000..8fa3832d
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/prefs.cpp
@@ -0,0 +1,178 @@
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+
+void hideShowRecentItemsCheckboxes(HWND hwndDlg)
+{
+ int enabled = IsDlgButtonChecked(hwndDlg, IDC_CHECK1);
+ int wait_secs = !!g_config->ReadInt(L"recent_wait_secs",0);
+ int wait_percent = !!g_config->ReadInt(L"recent_wait_percent",0);
+ int wait_end = !!g_config->ReadInt(L"recent_wait_end",0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK2), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK3), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK4), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK5), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK6), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK7), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT1), enabled && !!g_config->ReadInt(L"recent_limitd",1));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC1), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC3), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC4), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC5), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC6), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT2), enabled && wait_secs);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT3), enabled && wait_percent);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC7), enabled && !(wait_end && !wait_secs && !wait_percent));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO1), enabled && !(wait_end && !wait_secs && !wait_percent));
+}
+
+BOOL CALLBACK PrefsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static int need_ref;
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ CheckDlgButton(hwndDlg,IDC_CHECK2,(g_config->ReadInt(L"recent_track",1)&1)?BST_CHECKED:0);
+ CheckDlgButton(hwndDlg,IDC_CHECK3,(g_config->ReadInt(L"recent_track",1)&2)?0:BST_CHECKED);
+ CheckDlgButton(hwndDlg,IDC_CHECK1,!!g_config->ReadInt(L"showrecentitems",1));
+ CheckDlgButton(hwndDlg,IDC_CHECK4,!!g_config->ReadInt(L"recent_limitd",1));
+ CheckDlgButton(hwndDlg,IDC_CHECK5,!!g_config->ReadInt(L"recent_wait_secs",0));
+ CheckDlgButton(hwndDlg,IDC_CHECK6,!!g_config->ReadInt(L"recent_wait_percent",0));
+ CheckDlgButton(hwndDlg,IDC_CHECK7,!!g_config->ReadInt(L"recent_wait_end",0));
+
+ int item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_DISABLED));
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETITEMDATA,item,0);
+ item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_PODCAST_ONLY));
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETITEMDATA,item,1);
+ item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_ANY_APPLICABLE));
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETITEMDATA,item,2);
+ SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_SETCURSEL,g_config->ReadInt(L"resumeplayback",0),0);
+
+ SetDlgItemInt(hwndDlg,IDC_EDIT1,g_config->ReadInt(L"recent_limitnd",30),FALSE);
+ SetDlgItemInt(hwndDlg,IDC_EDIT2,g_config->ReadInt(L"recent_wait_secs_lim",5),FALSE);
+ SetDlgItemInt(hwndDlg,IDC_EDIT3,g_config->ReadInt(L"recent_wait_percent_lim",50),FALSE);
+ need_ref=0;
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+ }
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_CHECK1:
+ {
+ int a=!!IsDlgButtonChecked(hwndDlg,IDC_CHECK1);
+ g_config->WriteInt(L"showrecentitems",a);
+ if (a) history_init();
+ else history_quit();
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ }
+ break;
+
+ case IDC_EDIT1:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ BOOL t;
+ int v=GetDlgItemInt(hwndDlg,IDC_EDIT1,&t,FALSE);
+ if (t) g_config->WriteInt(L"recent_limitnd",v);
+ need_ref++;
+ }
+ break;
+
+ case IDC_CHECK4:
+ g_config->WriteInt(L"recent_limitd",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK4));
+ EnableWindow(GetDlgItem(hwndDlg,IDC_EDIT1),!!g_config->ReadInt(L"recent_limitd",1));
+ need_ref++;
+ break;
+
+ case IDC_CHECK2:
+ case IDC_CHECK3:
+ g_config->WriteInt(L"recent_track",(IsDlgButtonChecked(hwndDlg,IDC_CHECK2)?1:0) | (IsDlgButtonChecked(hwndDlg,IDC_CHECK3)?0:2));
+ break;
+
+ case IDC_CHECK5:
+ g_config->WriteInt(L"recent_wait_secs",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK5));
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+
+ case IDC_CHECK6:
+ g_config->WriteInt(L"recent_wait_percent",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK6));
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+
+ case IDC_CHECK7:
+ g_config->WriteInt(L"recent_wait_end",!!IsDlgButtonChecked(hwndDlg,IDC_CHECK7));
+ hideShowRecentItemsCheckboxes(hwndDlg);
+ break;
+
+ case IDC_COMBO1:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ int item = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_GETCURSEL,0,0);
+ if (item != CB_ERR)
+ {
+ g_config->WriteInt(L"resumeplayback", (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO1,CB_GETITEMDATA,item,0));
+ }
+ }
+ 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"recent_wait_secs_lim",v);
+ }
+ need_ref++;
+ }
+ 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"recent_wait_percent_lim",v);
+ }
+ need_ref++;
+ }
+ break;
+ };
+ break;
+
+ case WM_DESTROY:
+ if (need_ref)
+ {
+ g_config->WriteInt(L"recent_limitlt",0); // make sure it gets refreshed
+ // TODO: only do this if the history view is open
+ PostMessage(plugin.hwndLibraryParent,WM_USER+30,0,0);
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_history/resource.h b/Src/Plugins/Library/ml_history/resource.h
new file mode 100644
index 00000000..83a2c048
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/resource.h
@@ -0,0 +1,92 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_history.rc
+//
+#define IDS_HISTORY 1
+#define IDS_DELAYLOAD_FAILURE 2
+#define IDS_ERROR 3
+#define IDS_SCANNING_ELLIPSE 4
+#define IDS_COL_LAST_PLAYED 5
+#define IDS_COL_PLAY_COUNT 6
+#define IDS_COL_TITLE 7
+#define IDS_COL_LENGTH 8
+#define IDS_COL_FILENAME 9
+#define IDS_EST_TIME_NO_SECS 10
+#define IDS_EST_TIME_HAS_SECS 11
+#define IDS_SCANNING 13
+#define IDS_ITEM 14
+#define IDS_ITEMS 15
+#define IDS_PLAY 16
+#define IDS_PLAYS 17
+#define IDS_DAY 18
+#define IDS_DAYS 19
+#define IDS_REMOVE_ALL_HISTORY 20
+#define IDS_CONFIRMATION 21
+#define IDS_DISABLED 22
+#define IDS_PODCAST_ONLY 23
+#define IDS_ANY_APPLICABLE 24
+#define IDR_CONTEXTMENUS 101
+#define IDD_DIALOG1 102
+#define IDB_BITMAP1 103
+#define IDB_TREEITEM_RECENT 103
+#define IDD_PREFS 104
+#define IDS_COL_OFFSET 107
+#define IDR_ACCELERATOR1 109
+#define IDR_VIEW_ACCELERATORS 109
+#define IDR_DB_ERROR 110
+#define IDD_VIEW_DB_ERROR 111
+#define IDR_NDE_ERROR 111
+#define IDD_VIEW_RECENTITEMS 246
+#define IDC_BUTTON_CUSTOM 1000
+#define IDC_LIST2 1001
+#define IDC_STATIC6 1001
+#define IDC_CLEAR 1005
+#define IDC_QUICKSEARCH 1006
+#define IDC_COMBO1 1006
+#define IDC_MEDIASTATUS 1015
+#define IDC_BUTTON_PLAY 1016
+#define IDC_BUTTON_ENQUEUE 1017
+#define IDC_SEARCHCAPTION 1020
+#define IDC_CHECK1 1050
+#define IDC_CHECK2 1051
+#define IDC_CHECK3 1052
+#define IDC_CHECK4 1053
+#define IDC_EDIT1 1054
+#define IDC_CHECK5 1055
+#define IDC_EDIT2 1056
+#define IDC_STATIC1 1057
+#define IDC_STATIC2 1058
+#define IDC_STATIC3 1059
+#define IDC_STATIC4 1060
+#define IDC_CHECK6 1061
+#define IDC_EDIT3 1062
+#define IDC_STATIC5 1063
+#define IDC_CHECK7 1064
+#define IDC_CHECK8 1065
+#define IDC_STATIC7 1066
+#define IDC_REMOVEBOOK 1084
+#define IDC_DB_ERROR 1085
+#define IDC_RESET_DB_ON_ERROR 1086
+#define ID_MEDIAWND_PLAYSELECTEDFILES 40001
+#define ID_MEDIAWND_ENQUEUESELECTEDFILES 40002
+#define ID_MEDIAWND_ADDTOPLAYLIST 40003
+#define ID_MEDIAWND_SELECTALL 40004
+#define ID_PE_ID3 40005
+#define ID_MEDIAWND_REMOVEFROMLIBRARY 40006
+#define ID_MEDIAWND_EXPLOREFOLDER 40007
+#define ID_NAVIGATION_PREFERENCES 40008
+#define ID_NAVIGATION_HELP 40009
+#define ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY 40010
+#define ID_RECENTWND_CLEARPLAYBACKOFFSET 40011
+#define IDS_NULLSOFT_HISTORY 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 113
+#define _APS_NEXT_COMMAND_VALUE 40012
+#define _APS_NEXT_CONTROL_VALUE 1007
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmp b/Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmp
new file mode 100644
index 00000000..06714c39
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/resources/ti_history_items_16x16x16.bmp
Binary files differ
diff --git a/Src/Plugins/Library/ml_history/version.rc2 b/Src/Plugins/Library/ml_history/version.rc2
new file mode 100644
index 00000000..c0abd21a
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,0,3,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", "2,0,3,0"
+ VALUE "InternalName", "Nullsoft History"
+ VALUE "LegalCopyright", "Copyright © 2003-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_history.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_history/view_history.cpp b/Src/Plugins/Library/ml_history/view_history.cpp
new file mode 100644
index 00000000..010cf4e4
--- /dev/null
+++ b/Src/Plugins/Library/ml_history/view_history.cpp
@@ -0,0 +1,2349 @@
+#include "main.h"
+#include "..\..\General\gen_ml/config.h"
+#include "resource.h"
+#include "../nu/DialogSkinner.h"
+#include "../nu/listview.h"
+#include <time.h>
+#include "..\..\General\gen_ml/gaystring.h"
+#include "..\..\General\gen_ml/ml_ipc.h"
+#include <malloc.h>
+#include <string>
+#include "../nu/AutoWide.h"
+#include "../nu/AutoCharFn.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "../nu/sort.h"
+#include "../nu/menushortcuts.h"
+#include "../Winamp/strutil.h"
+#include "api__ml_history.h"
+#include <strsafe.h>
+
+static INT_PTR IPC_LIBRARY_SENDTOMENU;
+static void history_cleanupifnecessary();
+static GayStringW g_q;
+static W_ListView resultlist;
+static int resultSkin, customAllowed;
+static HWND m_hwnd, m_headerhwnd;
+static historyRecordList itemCache;
+static int history_bgThread_Kill;
+static HANDLE history_bgThread_Handle;
+static void fileInfoDialogs(HWND hwndParent);
+static int m_lv_last_topidx;
+int groupBtn = 1, enqueuedef = 0;
+HMENU g_context_menus2 = NULL;
+static viewButtons view;
+
+static void MakeDateStringW(__time64_t convertTime, wchar_t *dest, size_t destlen)
+{
+ SYSTEMTIME sysTime = {0};
+ tm *newtime = _localtime64(&convertTime);
+
+ sysTime.wYear = newtime->tm_year + 1900;
+ sysTime.wMonth = newtime->tm_mon + 1;
+ sysTime.wDayOfWeek = newtime->tm_wday;
+ sysTime.wDay = newtime->tm_mday;
+ sysTime.wHour = newtime->tm_hour;
+ sysTime.wMinute = newtime->tm_min;
+ sysTime.wSecond = newtime->tm_sec;
+
+ GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, (int)destlen);
+
+ size_t dateSize = lstrlenW(dest);
+ dest += dateSize;
+ destlen -= dateSize;
+ if (destlen)
+ {
+ *dest++ = ' ';
+ destlen--;
+ }
+
+ GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, (int)destlen);
+}
+
+void makeFilename2(const wchar_t *filename, wchar_t *filename2, int filename2_len)
+{
+ filename2[0]=0;
+ if (wcsstr(filename,L"~"))
+ {
+ WIN32_FIND_DATAW d = {0};
+ HANDLE h = FindFirstFileW(filename,&d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+ lstrcpynW(filename2,filename,filename2_len);
+ wchar_t *p=scanstr_backW(filename2,L"\\",filename2-1)+1;
+ int offs=(int)(p-filename2);
+ lstrcpynW(filename2+offs,d.cFileName,filename2_len - offs);
+ if (!_wcsicmp(filename,filename2)) filename2[0]=0;
+ }
+ }
+}
+
+void db_setFieldString(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);
+}
+
+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));
+ if (escaped)
+ {
+ 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);
+ }
+}
+
+int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb)
+{
+ if (!pa) pa = L"";
+ else SKIP_THE_AND_WHITESPACE(pa)
+
+ if (!pb) pb = L"";
+ else SKIP_THE_AND_WHITESPACE(pb)
+
+ return _wcsicmp(pa, pb);
+}
+
+struct SortRules
+{
+ int by;
+ int dir;
+};
+static int __fastcall sortFunc(const void *elem1, const void *elem2, const void *context)
+{
+ historyRecord *a=(historyRecord*)elem1;
+ historyRecord *b=(historyRecord*)elem2;
+
+ const SortRules *rules = (SortRules *)context;
+ int use_by = rules->by;
+ int use_dir = !!rules->dir;
+
+ #define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+
+ // this might be too slow, but it'd be nice
+ int x;
+ for (x = 0; x < 4; x ++)
+ {
+ if (use_by == HISTORY_SORT_FILENAME)
+ {
+ int v=STRCMP_NULLOK(a->filename,b->filename);
+ RETIFNZ(v)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_TITLE)
+ {
+ int v=STRCMP_NULLOK(a->title,b->title);
+ RETIFNZ(v)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_LASTPLAYED)
+ {
+ __time64_t v1=a->lastplayed;
+ __time64_t v2=b->lastplayed;
+ RETIFNZ(v2-v1)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_PLAYCOUNT)
+ {
+ int v1=a->playcnt;
+ int v2=b->playcnt;
+ RETIFNZ(v2-v1)
+ use_by=HISTORYVIEW_COL_LASTPLAYED;
+ }
+ else if (use_by == HISTORY_SORT_LENGTH) // length -> artist -> album -> track
+ {
+ int v1=a->length;
+ int v2=b->length;
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v2-v1)
+ return 0;
+ }
+ else if (use_by == HISTORY_SORT_OFFSET)
+ {
+ int v1=a->offset;
+ int v2=b->offset;
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v2-v1)
+ return 0;
+ }
+ else break; // no sort order?
+ }
+ #undef RETIFNZ
+ return 0;
+}
+
+void sortResults(historyRecordList *obj, int column, int dir) // sorts the results based on the current sort mode
+{
+ if (obj->Size > 1)
+ {
+ SortRules rules = {column, dir};
+ nu::qsort(obj->Items,obj->Size,sizeof(historyRecord),&rules, sortFunc);
+ }
+}
+
+// does not copy filename
+void recentScannerRefToObjCacheNFN(nde_scanner_t s, historyRecordList *obj)
+{
+ nde_field_t f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_TITLE);
+ if (f)
+ {
+ wchar_t *strval = NDE_StringField_GetString(f);
+ ndestring_retain(strval);
+ obj->Items[obj->Size].title = strval;
+ }
+ else
+ obj->Items[obj->Size].title = 0;
+
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_LENGTH);
+ obj->Items[obj->Size].length = f?NDE_IntegerField_GetValue(f):-1;
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_OFFSET);
+ obj->Items[obj->Size].offset = f?NDE_IntegerField_GetValue(f):-1;
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_PLAYCOUNT);
+ obj->Items[obj->Size].playcnt = f?NDE_IntegerField_GetValue(f):0;
+ f=NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_LASTPLAYED);
+ obj->Items[obj->Size].lastplayed = f?NDE_IntegerField_GetValue(f):0;
+ obj->Size++;
+}
+
+static void freeRecentRecord(historyRecord *p)
+{
+ ndestring_release(p->title);
+ ndestring_release(p->filename);
+}
+
+void emptyRecentRecordList(historyRecordList *obj)
+{
+ historyRecord *p=obj->Items;
+ while (obj->Size-->0)
+ {
+ freeRecentRecord(p);
+ p++;
+ }
+ obj->Size=0;
+}
+
+void freeRecentRecordList(historyRecordList *obj)
+{
+ emptyRecentRecordList(obj);
+ free(obj->Items);
+ obj->Items=0;
+ obj->Alloc=obj->Size=0;
+}
+
+void allocRecentRecordList(historyRecordList *obj, int newsize, int granularity)
+{
+ if (newsize < obj->Alloc || newsize < obj->Size) return;
+
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc=newsize+granularity;
+ historyRecord *data = (historyRecord*)realloc(obj->Items,sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ obj->Items=data;
+ }
+ else
+ {
+ data=(historyRecord*)malloc(sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(historyRecord)*old_Alloc);
+ free(obj->Items);
+ obj->Items=data;
+ }
+ else obj->Alloc = (int)old_Alloc;
+ }
+ if (!obj->Items) obj->Alloc=0;
+}
+
+static void playFiles(int enqueue, int all)
+{
+ if (history_bgThread_Handle) return;
+
+ int cnt=0;
+ int l=itemCache.Size;
+
+ for(int i=0;i<l;i++)
+ {
+ if ( all || resultlist.GetSelected( i ) )
+ {
+ if ( !cnt )
+ {
+ if ( !enqueue ) SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE );
+ cnt++;
+ }
+ enqueueFileWithMetaStructW s = { 0 };
+ s.filename = itemCache.Items[ i ].filename;
+ s.title = itemCache.Items[ i ].title;
+ s.ext = NULL;
+ s.length = itemCache.Items[ i ].length;
+ SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW );
+ }
+ }
+ if (cnt)
+ {
+ if(!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC,0,IPC_STARTPLAY);
+ }
+}
+
+static int history_saveQueryToList(nde_scanner_t s, historyRecordList *obj, int user32, int *killswitch) {
+
+ emptyRecentRecordList(obj);
+
+ NDE_Scanner_First(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ return 0;
+ }
+
+ int r;
+ unsigned int total_length_s=0;
+ do
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_FILENAME);
+ if (!f) break;
+
+ allocRecentRecordList(obj,obj->Size+1);
+ if (!obj->Alloc) break;
+
+ wchar_t *strval = NDE_StringField_GetString(f);
+ ndestring_retain(strval);
+ obj->Items[obj->Size].filename = strval;
+ recentScannerRefToObjCacheNFN(s,obj);
+
+ int thisl=obj->Items[obj->Size-1].length;
+
+ if (thisl > 0) total_length_s+=thisl * obj->Items[obj->Size-1].playcnt;
+ else total_length_s|=(1<<31);
+
+ r=NDE_Scanner_Next(s, killswitch);
+ if (killswitch && *killswitch)
+ {
+ return 0;
+ }
+ } while(r);
+
+ if (obj->Size && obj->Size < obj->Alloc - 1024)
+ {
+ size_t old_Alloc = obj->Alloc;
+ obj->Alloc = obj->Size;
+ historyRecord *data=(historyRecord*)realloc(obj->Items,sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ obj->Items=data;
+ }
+ else
+ {
+ data=(historyRecord*)malloc(sizeof(historyRecord)*obj->Alloc);
+ if (data)
+ {
+ memcpy(data, obj->Items, sizeof(historyRecord)*old_Alloc);
+ free(obj->Items);
+ obj->Items=data;
+ }
+ else obj->Alloc = (int)old_Alloc;
+ }
+ }
+
+ if (killswitch && *killswitch) return 0;
+
+ sortResults(obj,
+ g_config->ReadInt(L"recent_sort_by",HISTORY_SORT_LASTPLAYED),
+ g_config->ReadInt(L"recent_sort_dir",0));
+
+ if (killswitch && *killswitch) return 0;
+
+ return total_length_s;
+}
+
+typedef struct
+{
+ int user32;
+} history_bgThreadParms;
+
+// out can never be bigger than in+1
+static void parsequicksearch(wchar_t *out, const 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, const 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 // this is ubergay. no wait it isn't anymore. it rocks now due to the GayString
+ {
+ int isAny = 0;
+ if (text && (*text == L'*' && text[1] == L' '))
+ {
+ isAny = 1;
+ text += 2;
+ }
+ int cchText = lstrlenW(text);
+ wchar_t *tmpbuf = (wchar_t*)calloc((cchText + 2), sizeof(wchar_t));
+ parsequicksearch(tmpbuf, text);
+
+ int x;
+ const wchar_t *fields[5] =
+ {
+ L"filename",
+ L"title",
+ L"artist",
+ L"album",
+ L"genre",
+ };
+ 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 && *op == L'\'') *op = L'\"';
+ if (op) op++;
+ }
+ p[lenp - 1] = 0; // remove >
+ query->Append(p + 1);
+ }
+ else
+ {
+ for (x = 0; x < (int)min(sizeof(fields) / sizeof(fields[0]), nf); x ++)
+ {
+ const 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")");
+ free(tmpbuf);
+ }
+ if (ispar) query->Append(L")");
+}
+
+static DWORD WINAPI history_bgThreadQueryProc(void *tmp)
+{
+ history_bgThreadParms *p=(history_bgThreadParms*)tmp;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ NDE_Scanner_Query(s, g_q.Get());
+ int total_length_s=history_saveQueryToList(s,&itemCache, p->user32, &history_bgThread_Kill);
+ NDE_Table_DestroyScanner(g_table, s);
+ LeaveCriticalSection(&g_db_cs);
+
+ if (!history_bgThread_Kill) PostMessage(m_hwnd,WM_APP+3,0x69,total_length_s);
+ return 0;
+}
+
+void history_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 (history_bgThread_Handle)
+ {
+ history_bgThread_Kill=1;
+ WaitForSingleObject(history_bgThread_Handle,INFINITE);
+ CloseHandle(history_bgThread_Handle);
+ history_bgThread_Handle=0;
+ }
+ KillTimer(m_hwnd,123);
+}
+
+static void history_bgQuery(int user32=0) // only internal used
+{
+ history_bgQuery_Stop();
+
+ SetDlgItemTextW(m_hwnd,IDC_MEDIASTATUS,WASABI_API_LNGSTRINGW(IDS_SCANNING_ELLIPSE));
+ SetTimer(m_hwnd,123,200,NULL);
+
+ DWORD id;
+ static history_bgThreadParms parms;
+ parms.user32=user32;
+ history_bgThread_Kill=0;
+ history_bgThread_Handle=CreateThread(NULL,0,history_bgThreadQueryProc,(LPVOID)&parms,0,&id);
+}
+
+static void doQuery(HWND hwndDlg, wchar_t *text, int dobg=1) {
+ history_bgQuery_Stop();
+
+ GayStringW query;
+ if (text[0]) makeQueryStringFromText(&query,text,2);
+
+ wchar_t *parent_query=NULL;
+ SendMessage(GetParent(hwndDlg),WM_APP+2,0,(LONG_PTR)&parent_query);
+
+ g_q.Set(L"");
+
+ if(parent_query && parent_query[0])
+ {
+ g_q.Set(L"(");
+ g_q.Append(parent_query);
+ g_q.Append(L")");
+ }
+
+ if (query.Get() && query.Get()[0])
+ {
+ if(g_q.Get()[0])
+ {
+ g_q.Append(L" & (");
+ g_q.Append(query.Get());
+ g_q.Append(L")");
+ }
+ else g_q.Set(query.Get());
+ }
+
+ if (dobg) history_bgQuery();
+}
+
+static WNDPROC search_oldWndProc;
+static DWORD WINAPI search_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if(uMsg == WM_KEYDOWN && wParam == VK_DOWN)
+ {
+ HWND hwndList = resultlist.getwnd();
+ if (hwndList)
+ {
+ PostMessage(GetParent(hwndDlg), WM_NEXTDLGCTL, (WPARAM)hwndList, TRUE);
+ ListView_SetItemState(hwndList,0,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
+ }
+ }
+ return (DWORD)CallWindowProcW(search_oldWndProc,hwndDlg,uMsg,wParam,lParam);
+}
+
+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 removeSelectedItems(int isAll=0)
+{
+ int hasdel=0;
+ history_bgQuery_Stop();
+
+ 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) || isAll)
+ {
+ if(NDE_Scanner_LocateNDEFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,itemCache.Items[i].filename))
+ {
+ hasdel=1;
+ NDE_Scanner_Edit(s);
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ }
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (!hasdel)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return;
+ }
+
+ NDE_Table_Sync(g_table);
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+
+ resultlist.Clear();
+ emptyRecentRecordList(&itemCache);
+
+ SendMessage(m_hwnd,WM_APP+1,0,0); //refresh current view
+}
+
+static void removeSelectedItemOffsets(int isAll=0)
+{
+ int hasdel=0;
+ history_bgQuery_Stop();
+
+ 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) || isAll)
+ {
+ if(NDE_Scanner_LocateNDEFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,itemCache.Items[i].filename))
+ {
+ db_setFieldInt(s, HISTORYVIEW_COL_OFFSET, -1);
+ NDE_Scanner_Post(s);
+ hasdel=1;
+ itemCache.Items[i].offset = -1;
+ }
+ }
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ if (!hasdel)
+ {
+ LeaveCriticalSection(&g_db_cs);
+ return;
+ }
+
+ NDE_Table_Sync(g_table);
+ NDE_Table_Compact(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+
+ resultlist.RefreshAll();
+}
+
+static void History_SaveLastQuery(HWND hwnd)
+{
+ LPSTR pszQuery = NULL;
+ HWND hEditbox = GetDlgItem(hwnd, IDC_QUICKSEARCH);
+ if (NULL != hEditbox)
+ {
+ UINT cchTextMax = GetWindowTextLength(hEditbox);
+ if (0 != cchTextMax)
+ {
+ cchTextMax++;
+ LPWSTR pszText = (LPWSTR)calloc(cchTextMax, sizeof(WCHAR));
+ if (NULL != pszText)
+ {
+ UINT cchText = GetWindowTextW(hEditbox, pszText, cchTextMax);
+ if (0 != cchText)
+ {
+ UINT cchQuery = WideCharToMultiByte(CP_UTF8, 0, pszText, cchText, NULL, 0, NULL, NULL);
+ if (0 != cchQuery)
+ {
+ cchQuery++;
+ pszQuery = (LPSTR)calloc(cchQuery, sizeof(CHAR));
+ if (NULL != pszQuery)
+ {
+ cchQuery = WideCharToMultiByte(CP_UTF8, 0, pszText, cchText, pszQuery, cchQuery, NULL, NULL);
+ pszQuery[cchQuery] = '\0';
+ }
+ }
+ }
+ free(pszText);
+ }
+ }
+ }
+ g_config->WriteString("recent_lastquery", pszQuery);
+ if (NULL != pszQuery)
+ free(pszQuery);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_MEDIAWND_PLAYSELECTEDFILES)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ }
+ else if (id == ID_MEDIAWND_ENQUEUESELECTEDFILES)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos= i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ }
+ }
+
+ playItem.wID = ID_MEDIAWND_ENQUEUESELECTEDFILES;
+ enqueueItem.wID = ID_MEDIAWND_PLAYSELECTEDFILES;
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+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);
+}
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0;
+
+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_CUSTOM, IDC_REMOVEBOOK, 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:
+ {
+ wchar_t buffer[128] = {0};
+ GetDlgItemTextW(hwnd, IDC_BUTTON_PLAY, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(GetDlgItem(hwnd, IDC_BUTTON_PLAY), 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);
+ break;
+ }
+ case GROUP_STATUSBAR:
+ {
+ 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);
+ 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_CUSTOM:
+ case IDC_REMOVEBOOK:
+ if (IDC_BUTTON_CUSTOM != 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;
+ }
+
+ 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_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 + WASABI_API_APP->getScaleY(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 LayoutWindows2(HWND hwnd, BOOL fRedraw)
+{
+ RECT rc, rg;
+ HRGN rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ SetRect(&rg, 0, 0, 0, 0);
+
+ rc.top += WASABI_API_APP->getScaleY(2);
+ rc.right -= WASABI_API_APP->getScaleY(2);
+
+ if (rc.bottom <= rc.top || rc.right <= rc.left) return;
+
+ 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)) / WASABI_API_APP->getScaleX(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);
+}
+
+static void history_ManageButtons(HWND hwndDlg)
+{
+ int has_selection = resultlist.GetSelectedCount();
+
+ const int buttonids[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_REMOVEBOOK};
+ for (size_t i = 0; i != sizeof(buttonids)/sizeof(buttonids[0]); i++)
+ {
+ HWND controlHWND = GetDlgItem(hwndDlg, buttonids[i]);
+ EnableWindow(controlHWND, has_selection);
+ }
+}
+
+void history_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;
+ }
+ }
+}
+
+void UpdateMenuItems(HWND hwndDlg, HMENU menu)
+{
+ bool swapPlayEnqueue=false;
+ if (g_config->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(menu);
+ swapPlayEnqueue=true;
+ }
+
+ SyncMenuWithAccelerators(hwndDlg, menu);
+ if (swapPlayEnqueue) SwapPlayEnqueueInMenu(menu);
+}
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL history_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0)
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem(hwndDlg, buttonId);
+
+ GetWindowRect(buttonHWND, &r);
+ UpdateMenuItems(hwndDlg, menu);
+ MLSkinnedButton_SetDropDownState(buttonHWND, TRUE);
+
+ UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN;
+ if (!(flags & BPM_WM_COMMAND))
+ tpmFlags |= TPM_RETURNCMD;
+ int x = Menu_TrackPopup(plugin.hwndLibraryParent, 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 history_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);
+ }
+ }
+
+ history_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+BOOL CALLBACK view_historyDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a = (BOOL)dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a;
+
+ static HMENU sendto_hmenu;
+ static librarySendToMenuStruct s;
+
+ switch(uMsg)
+ {
+ case WM_INITMENUPOPUP:
+ if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
+ {
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU)==0xffffffff)
+ s.mode=2;
+ }
+ return 0;
+
+ case WM_DISPLAYCHANGE:
+ {
+ ListView_SetTextColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ resultlist.SetFont(dialogSkinner.GetFont());
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+
+ case WM_CONTEXTMENU:
+ {
+ HWND hwndFrom = (HWND)wParam;
+ if (hwndFrom != resultlist.getwnd())
+ return 0;
+
+ int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
+ 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(hwndFrom, 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(hwndFrom, 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 0;
+ }
+
+ HMENU g_context_menus=WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU menu=GetSubMenu(g_context_menus,0);
+ sendto_hmenu=GetSubMenu(menu,2);
+
+ UpdateMenuItems(hwndDlg, menu);
+
+ s.mode = 0;
+ s.hwnd = 0;
+ s.build_hMenu = 0;
+
+ IPC_LIBRARY_SENDTOMENU = (INT_PTR)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)==0xffffffff)
+ {
+ s.mode = 1;
+ s.hwnd = hwndDlg;
+ s.data_type = ML_TYPE_FILENAMESW;
+ s.ctx[1] = 1;
+ s.build_hMenu = sendto_hmenu;
+ }
+
+ UINT menustate = 0;
+ int n=resultlist.GetSelectedCount();
+ if(n == 0)
+ {
+ menustate = MF_BYCOMMAND|MF_GRAYED;
+ }
+ else
+ {
+ menustate = MF_BYCOMMAND|MF_ENABLED;
+ }
+
+ EnableMenuItem(menu,ID_PE_ID3,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_PLAYSELECTEDFILES,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_ENQUEUESELECTEDFILES,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_EXPLOREFOLDER,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_REMOVEFROMLIBRARY,menustate);
+ EnableMenuItem(menu,ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY,menustate);
+ EnableMenuItem(menu, 2, MF_BYPOSITION|(n==0?MF_GRAYED:MF_ENABLED));
+
+ int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL);
+ if(!SendMessage(hwndDlg,WM_COMMAND,r,0))
+ {
+ if (s.mode == 2)
+ {
+ s.menu_id = r;
+ if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
+ {
+ // build my data.
+ s.mode=3;
+ s.data_type=ML_TYPE_FILENAMESW;
+
+ //std::vector<wchar_t> sendStr;
+ std::wstring sendStr;
+
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ // HAKAN: why (len + 1) ?
+ //sendStr.append(itemCache.Items[i].filename, wcslen(itemCache.Items[i].filename)+1);
+ sendStr.append(itemCache.Items[i].filename, wcslen(itemCache.Items[i].filename));
+ }
+ }
+ // HAKAN: No need to add trailing zero
+ //sendStr.push_back(0);
+
+ s.data = (void*)sendStr.c_str();
+
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU)!=1)
+ {
+ s.mode=3;
+ s.data_type=ML_TYPE_FILENAMES;
+
+ //std::vector<char> sendStrA;
+ std::string sendStrA;
+
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ // HAKAN: why (len + 1) ?
+ //sendStrA.append(AutoCharFn(itemCache.Items[i].filename), strlen(AutoCharFn(itemCache.Items[i].filename))+1);
+ sendStrA.append(AutoCharFn(itemCache.Items[i].filename), strlen(AutoCharFn(itemCache.Items[i].filename)));
+ }
+ }
+ // HAKAN: No need to add trailing zero
+ //sendStrA.push_back(0);
+
+ s.data = (void*)sendStrA.c_str();
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU);
+ }
+ }
+ }
+ }
+
+ if (s.mode)
+ {
+ s.mode=4;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU); // cleanup
+ }
+
+ sendto_hmenu=0;
+ DestroyMenu(g_context_menus);
+ Sleep(100);
+ MSG msg = {0};
+ while(PeekMessage(&msg,NULL,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE)); //eat return
+ }
+ return 0;
+
+ case WM_INITDIALOG:
+ {
+ m_hwnd=hwndDlg;
+ g_q.Set(L"");
+
+ 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);
+ }
+
+ history_cleanupifnecessary();
+
+ itemCache.Items=0;
+ itemCache.Alloc=0;
+ itemCache.Size=0;
+
+ resultlist.setwnd(GetDlgItem(hwndDlg,IDC_LIST2));
+ resultlist.ForceUnicode();
+ resultSkin = (int)(INT_PTR)resultlist.getwnd(); //Might be unsafe
+
+ groupBtn = g_config->ReadInt(L"groupbtn", 1);
+ enqueuedef = (g_config->ReadInt(L"enqueuedef", 0) == 1);
+
+ // 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_CUSTOM, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_history"};
+ 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_CUSTOM, pszTextW);
+ }
+ else
+ customAllowed = FALSE;
+
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ m.hwndToSkin = resultlist.getwnd();
+ MLSkinWindow(mediaLibrary.library, &m);
+
+ m.skinType = SKINNEDWND_TYPE_BUTTON;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0);
+
+ FLICKERFIX ff = {0, FFM_ERASEINPAINT};
+ const int buttonids[] = {IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM};
+ for (size_t i=0;i!=sizeof(buttonids)/sizeof(buttonids[0]);i++)
+ {
+ m.hwndToSkin = ff.hwnd = GetDlgItem(hwndDlg, buttonids[i]);
+ if (IsWindow(m.hwndToSkin))
+ {
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+ }
+
+ INT ffcl[] = {IDC_REMOVEBOOK, IDC_CLEAR, IDC_MEDIASTATUS, IDC_QUICKSEARCH, IDC_SEARCHCAPTION};
+ m.skinType = SKINNEDWND_TYPE_AUTO;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ for (INT index = 0; index < sizeof(ffcl) / sizeof(INT); index++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]);
+ if (IsWindow(ff.hwnd))
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ m.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &m);
+ }
+ }
+
+ ListView_SetTextColor(resultlist.getwnd(),dialogSkinner.Color(WADLG_ITEMFG));
+ ListView_SetBkColor(resultlist.getwnd(), dialogSkinner.Color(WADLG_ITEMBG));
+ ListView_SetTextBkColor(resultlist.getwnd(),dialogSkinner.Color(WADLG_ITEMBG));
+
+ resultlist.SetFont(dialogSkinner.GetFont());
+
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_LAST_PLAYED), g_config->ReadInt(L"recent_col_lp", 111));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_PLAY_COUNT), g_config->ReadInt(L"recent_col_count", 70));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TITLE), g_config->ReadInt(L"recent_col_title", 238));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_LENGTH), g_config->ReadInt(L"recent_col_len", 50));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_FILENAME), g_config->ReadInt(L"recent_col_filename", 285));
+ resultlist.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_OFFSET), g_config->ReadInt(L"recent_col_offset", 80));
+
+ m_headerhwnd=ListView_GetHeader(resultlist.getwnd());
+
+ {
+ char *query="";
+ if (g_config->ReadInt(L"remembersearch",0)) query = g_config->ReadString("recent_lastquery", "");
+ AutoWide queryUnicode(query, CP_UTF8);
+ SetDlgItemTextW(hwndDlg,IDC_QUICKSEARCH,queryUnicode);
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ doQuery(hwndDlg,queryUnicode,0);
+ }
+
+ {
+ int l_sc=g_config->ReadInt(L"recent_sort_by", HISTORY_SORT_LASTPLAYED);
+ int l_sd=g_config->ReadInt(L"recent_sort_dir", 0);
+ mediaLibrary.ListViewSort(resultSkin, l_sc, l_sd);
+ mediaLibrary.ListViewShowSort(resultSkin, TRUE);
+ }
+
+ history_ManageButtons(hwndDlg);
+ history_UpdateButtonText(hwndDlg, enqueuedef == 1);
+
+ search_oldWndProc = (WNDPROC)(LONG_PTR)SetWindowLongPtrW(GetDlgItem(hwndDlg,IDC_QUICKSEARCH),GWLP_WNDPROC,(LONG_PTR)search_newWndProc);
+ 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 + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+
+ case WM_MOUSEMOVE:
+ if (GetCapture()==hwndDlg)
+ {
+ POINT p;
+ p.x=GET_X_LPARAM(lParam);
+ p.y=GET_Y_LPARAM(lParam);
+ ClientToScreen(hwndDlg,&p);
+ mlDropItemStruct m={0};
+ m.type=ML_TYPE_FILENAMESW;
+ m.p=p;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG,(WPARAM)&m);
+ break;
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (GetCapture()==hwndDlg)
+ {
+ 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_FILENAMESW;
+ m.p=p;
+ m.flags=ML_HANDLEDRAG_FLAG_NOCURSOR;
+
+ pluginHandleIpcMessage(ML_IPC_HANDLEDRAG,(WPARAM)&m);
+
+ if (m.result>0)
+ {
+ size_t buf_size=4096;
+ wchar_t *buf=(wchar_t*)calloc(buf_size, sizeof(wchar_t));
+ int buf_pos=0;
+
+ int l=resultlist.GetCount();
+ for(int i=0;i<l;i++)
+ {
+ if (resultlist.GetSelected(i))
+ {
+ const wchar_t *tbuf = itemCache.Items[i].filename;
+ size_t cchFilename = wcslen(tbuf) + 1;
+ size_t newsize=buf_pos + cchFilename;
+ if (newsize < buf_size)
+ {
+ size_t old_buf_size = buf_size;
+ buf_size=newsize+4096;
+ wchar_t *reallocated_buf = (wchar_t*)realloc(buf,buf_size*sizeof(wchar_t));
+ if (reallocated_buf)
+ {
+ buf = reallocated_buf;
+ }
+ else
+ {
+ wchar_t *newbuf = (wchar_t*)malloc(buf_size*sizeof(wchar_t));
+ if (!newbuf) // out of memory? well we can at least send what we've got
+ break;
+ memcpy(newbuf, buf, old_buf_size);
+ free(buf);
+ buf = newbuf;
+ }
+ }
+ StringCchCopyNW(buf+buf_pos,buf_size-buf_pos,tbuf,cchFilename);
+ buf_pos=(int)newsize;
+ }
+ }
+
+ if (buf_pos)
+ {
+ buf[buf_pos]=0;
+ m.flags=0;
+ m.result=0;
+ m.data=(void*)buf;
+ pluginHandleIpcMessage(ML_IPC_HANDLEDROP,(WPARAM)&m);
+ }
+
+ free(buf);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ if (GetFocus() != GetDlgItem(hwndDlg, IDC_QUICKSEARCH))
+ {
+ switch(LOWORD(wParam))
+ {
+ case IDC_REMOVEBOOK:
+ removeSelectedItems(0);
+ break;
+ case IDC_CLEAR:
+ SetDlgItemText(hwndDlg,IDC_QUICKSEARCH,TEXT(""));
+ break;
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg,UPDATE_QUERY_TIMER_ID,100,NULL);
+ }
+ break;
+ case IDC_BUTTON_PLAY:
+ case ID_MEDIAWND_PLAYSELECTEDFILES:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_MEDIAWND_ENQUEUESELECTEDFILES:
+ case IDC_BUTTON_CUSTOM:
+ {
+ if (HIWORD(wParam) == MLBN_DROPDOWN)
+ {
+ history_Play(hwndDlg, (HWND)lParam, LOWORD(wParam));
+ }
+ else
+ {
+ int action;
+ if (LOWORD(wParam) == IDC_BUTTON_PLAY || LOWORD(wParam) == ID_MEDIAWND_PLAYSELECTEDFILES)
+ {
+ action = (HIWORD(wParam) == 1) ? g_config->ReadInt(L"enqueuedef", 0) == 1 : 0;
+ }
+ else if (LOWORD(wParam) == IDC_BUTTON_ENQUEUE || LOWORD(wParam) == ID_MEDIAWND_ENQUEUESELECTEDFILES)
+ {
+ action = (HIWORD(wParam) == 1) ? g_config->ReadInt(L"enqueuedef", 0) != 1 : 1;
+ }
+ else
+ break;
+
+ playFiles(action, 0);
+ }
+ break;
+ }
+ case ID_MEDIAWND_SELECTALL:
+ ListView_SetItemState(resultlist.getwnd(), -1, LVIS_SELECTED, LVIS_SELECTED);
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ removeSelectedItems(0);
+ break;
+ case ID_MEDIAWND_REMOVEOFFSETFROMLIBRARY:
+ removeSelectedItemOffsets(0);
+ break;
+ case ID_MEDIAWND_EXPLOREFOLDER:
+ exploreItemFolder(hwndDlg);
+ break;
+ case ID_PE_ID3:
+ fileInfoDialogs(hwndDlg);
+ break;
+ }
+ }
+ else
+ {
+ switch(LOWORD(wParam))
+ {
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ SetTimer(hwndDlg,UPDATE_QUERY_TIMER_ID,100,NULL);
+ }
+ break;
+ case ID_MEDIAWND_SELECTALL:
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ break;
+ case ID_MEDIAWND_REMOVEFROMLIBRARY:
+ {
+ DWORD start = -1, end = -1;
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
+ if (start != -1)
+ {
+ if (start == end)
+ {
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, start, end + 1);
+ }
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_REPLACESEL, TRUE, (LPARAM)L"");
+ SendDlgItemMessageW(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, start, start);
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ case WM_TIMER:
+ if (wParam == 123)
+ {
+ if (history_bgThread_Handle)
+ {
+ ListView_SetItemCount(resultlist.getwnd(),1);
+ ListView_RedrawItems(resultlist.getwnd(),0,0);
+ }
+ }
+ else if (wParam == UPDATE_QUERY_TIMER_ID)
+ {
+ KillTimer(hwndDlg,UPDATE_QUERY_TIMER_ID);
+ wchar_t text[512] = {0};
+ GetWindowTextW(GetDlgItem(hwndDlg,IDC_QUICKSEARCH),text,511-1);
+ text[511]=0;
+ doQuery(hwndDlg,text);
+ }
+ return 0;
+
+ case WM_APP+3: // sent by bgthread
+ if (wParam == 0x69)
+ {
+ history_bgQuery_Stop();
+
+ ListView_SetItemCount(resultlist.getwnd(),0);
+ ListView_SetItemCount(resultlist.getwnd(),itemCache.Size);
+ if (itemCache.Size>0) ListView_RedrawItems(resultlist.getwnd(),0,itemCache.Size-1);
+
+ if (m_lv_last_topidx)
+ {
+ ListView_EnsureVisible(resultlist.getwnd(),m_lv_last_topidx,FALSE);
+ m_lv_last_topidx=0;
+ }
+
+ unsigned int total_plays=0;
+ int x;
+ for (x = 0; x < itemCache.Size; x ++)
+ {
+ total_plays += itemCache.Items[x].playcnt;
+ }
+
+ int total_length_s = (int)lParam & 0x7FFFFFFF;
+ int uncert=(int)(lParam>>31);
+ wchar_t buf[1024] = {0}, itemStr[16] = {0}, playStr[16] = {0};
+
+ StringCchPrintfW(buf, 1024,
+ L"%d %s, %u %s ",
+ itemCache.Size,
+ WASABI_API_LNGSTRINGW_BUF(itemCache.Size==1?IDS_ITEM:IDS_ITEMS,itemStr,16),
+ total_plays,
+ WASABI_API_LNGSTRINGW_BUF(total_plays==1?IDS_PLAY:IDS_PLAYS,playStr,16));
+
+ if (total_length_s < 60*60)
+ {
+ StringCchPrintfW(buf+wcslen(buf), 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(buf+wcslen(buf), 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(buf+wcslen(buf), 64,
+ L"[%s%u %s+%u:%02u:%02u]",
+ 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);
+ }
+
+ SetDlgItemTextW(hwndDlg,IDC_MEDIASTATUS,buf);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (resultlist.getwnd())
+ {
+ g_config->WriteInt(L"recent_col_lp", resultlist.GetColumnWidth(0));
+ g_config->WriteInt(L"recent_col_count", resultlist.GetColumnWidth(1));
+ g_config->WriteInt(L"recent_col_title", resultlist.GetColumnWidth(2));
+ g_config->WriteInt(L"recent_col_len", resultlist.GetColumnWidth(3));
+ g_config->WriteInt(L"recent_col_filename", resultlist.GetColumnWidth(4));
+ g_config->WriteInt(L"recent_col_offset", resultlist.GetColumnWidth(5));
+ }
+
+ History_SaveLastQuery(hwndDlg);
+
+ if (g_table_dirty && g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ emptyRecentRecordList(&itemCache);
+ free(itemCache.Items);
+ itemCache.Items=0;
+ itemCache.Alloc=0;
+ itemCache.Size=0;
+
+ m_hwnd=0;
+ g_q.Set(L"");
+ WASABI_API_APP->app_removeAccelerators(hwndDlg);
+ break;
+
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_QUICKSEARCH|DCW_SUNKENBORDER, IDC_LIST2|DCW_SUNKENBORDER};
+ dialogSkinner.Draw(hwndDlg, tab, 1 + !!IsWindowVisible(GetDlgItem(hwndDlg, IDC_QUICKSEARCH)));
+ }
+ return 0;
+
+ case WM_APP+1:
+ history_bgQuery((int)lParam);
+ break;
+
+ case WM_ML_CHILDIPC:
+ if(lParam == ML_CHILDIPC_GO_TO_SEARCHBAR)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1);
+ SetFocus(GetDlgItem(hwndDlg,IDC_QUICKSEARCH));
+ }
+ else if (lParam == ML_CHILDIPC_REFRESH_SEARCH)
+ {
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ }
+ break;
+
+ case WM_ERASEBKGND:
+ return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_LIST2) // media view
+ {
+ if (l->code == NM_DBLCLK)
+ {
+ playFiles((!!g_config->ReadInt(L"enqueuedef", 0)) ^ (!!(GetAsyncKeyState(VK_SHIFT)&0x8000)),0);
+ }
+ else if (l->code == LVN_ODFINDITEMW) // yay we find an item (for kb shortcuts)
+ {
+ if (history_bgThread_Handle) return 0;
+ NMLVFINDITEMW *t = (NMLVFINDITEMW *)lParam;
+ int i=t->iStart;
+ if (i >= itemCache.Size) i=0;
+
+ int cnt=itemCache.Size-i;
+ if (t->lvfi.flags & LVFI_WRAP) cnt+=i;
+
+ int by = g_config->ReadInt(L"recent_sort_by", HISTORY_SORT_LASTPLAYED);
+
+ while (cnt-->0)
+ {
+ historyRecord *thisitem=itemCache.Items + i;
+ wchar_t tmp[128] = {0};
+ const wchar_t *name=0;
+
+ switch (by)
+ {
+ case HISTORYVIEW_COL_FILENAME:
+ name=thisitem->filename;
+ if (!wcsstr(name,L"://"))
+ {
+ while (name && *name) name++;
+ while (name && name >= thisitem->filename && *name != '/' && *name != '\\') name--;
+ if (name) name++;
+ }
+ break;
+ case HISTORYVIEW_COL_TITLE: name=thisitem->title; break;
+ case HISTORYVIEW_COL_LASTPLAYED:
+ tmp[0]=0;
+ if (thisitem->lastplayed > 0)
+ {
+ __time64_t timev = thisitem->lastplayed;
+ MakeDateStringW(timev, tmp, ARRAYSIZE(tmp));
+ }
+ name=tmp;
+ break;
+ case HISTORYVIEW_COL_PLAYCOUNT:
+ StringCchPrintfW(tmp,128,L"%u",thisitem->playcnt);
+ name=tmp;
+ break;
+ case HISTORYVIEW_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 HISTORYVIEW_COL_OFFSET:
+ tmp[0]=0;
+ if (thisitem->offset > 0) StringCchPrintfW(tmp,128,L"%d:%02d",(thisitem->offset/1000)/60,(thisitem->offset/1000)%60);
+ name=tmp;
+ break;
+ }
+
+ if (!name) name=L"";
+ else SKIP_THE_AND_WHITESPACE(name)
+
+ if (t->lvfi.flags & (4|LVFI_PARTIAL))
+ {
+ if (!_wcsnicmp(name,t->lvfi.psz,lstrlenW(t->lvfi.psz)))
+ {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else if (t->lvfi.flags & LVFI_STRING)
+ {
+ if (!_wcsicmp(name,t->lvfi.psz))
+ {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else
+ {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ if (++i == itemCache.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 (history_bgThread_Handle)
+ {
+ if (!item && lpdi->item.iSubItem == 0 && lpdi->item.mask & LVIF_TEXT)
+ {
+ static char bufpos;
+ static char chars[4]={'/','-','\\','|'};
+ StringCchPrintfW(lpdi->item.pszText,lpdi->item.cchTextMax,
+ L"%s %c",WASABI_API_LNGSTRINGW(IDS_SCANNING),
+ chars[bufpos++&3]);
+ return 0;
+ }
+ }
+
+ if (item < 0 || item >= itemCache.Size) return 0;
+
+ historyRecord *thisitem = itemCache.Items + item;
+
+ if (lpdi->item.mask & (LVIF_TEXT|/*LVIF_IMAGE*/0)) // we can always do images too :)
+ {
+ if (lpdi->item.mask & LVIF_TEXT)
+ {
+ wchar_t tmpbuf[128] = {0};
+ const wchar_t *nameptr=0;
+ switch (lpdi->item.iSubItem)
+ {
+ case HISTORYVIEW_COL_FILENAME:
+ nameptr=thisitem->filename;
+ if (!wcsstr(nameptr,L"://"))
+ {
+ while (nameptr && *nameptr) nameptr++;
+ while (nameptr && nameptr >= thisitem->filename && *nameptr != L'/' && *nameptr != L'\\') nameptr--;
+ if (nameptr) nameptr++;
+ }
+ break;
+ case HISTORYVIEW_COL_TITLE: nameptr=thisitem->title; break;
+ case HISTORYVIEW_COL_LASTPLAYED:
+ if (thisitem->lastplayed > 0)
+ {
+ __time64_t timev = thisitem->lastplayed;
+ MakeDateStringW(timev, tmpbuf, ARRAYSIZE(tmpbuf));
+ }
+ nameptr=tmpbuf;
+ break;
+ case HISTORYVIEW_COL_PLAYCOUNT:
+ StringCchPrintfW(tmpbuf,128,L"%u",thisitem->playcnt);
+ nameptr=tmpbuf;
+ break;
+ case HISTORYVIEW_COL_LENGTH:
+ if (thisitem->length >= 0)
+ StringCchPrintfW(tmpbuf,128,L"%d:%02d",thisitem->length/60,thisitem->length%60);
+ nameptr=tmpbuf;
+ break;
+ case HISTORYVIEW_COL_OFFSET:
+ if (thisitem->offset > 0)
+ StringCchPrintfW(tmpbuf,128,L"%d:%02d",(thisitem->offset/1000)/60,(thisitem->offset/1000)%60);
+ nameptr=tmpbuf;
+ break;
+ }
+ if (nameptr) lstrcpynW(lpdi->item.pszText,nameptr,lpdi->item.cchTextMax);
+ else lpdi->item.pszText[0]=0;
+ }
+ // if(lpdi->item.mask & LVIF_IMAGE)
+ } // bother
+ return 0;
+ } // LVN_GETDISPINFO
+ else if (l->code == LVN_COLUMNCLICK)
+ {
+ NMLISTVIEW *p=(NMLISTVIEW*)lParam;
+ int l_sc=g_config->ReadInt(L"recent_sort_by",HISTORY_SORT_LASTPLAYED);
+ int l_sd=g_config->ReadInt(L"recent_sort_dir",0);
+ if (p->iSubItem == l_sc) l_sd=!l_sd;
+ else { l_sd=0; l_sc=p->iSubItem; }
+
+ g_config->WriteInt(L"recent_sort_by",l_sc);
+ g_config->WriteInt(L"recent_sort_dir",l_sd);
+
+ mediaLibrary.ListViewSort(resultSkin, l_sc, l_sd);
+ sortResults(&itemCache,
+ g_config->ReadInt(L"recent_sort_by",HISTORY_SORT_LASTPLAYED),
+ g_config->ReadInt(L"recent_sort_dir",0));
+ ListView_SetItemCount(resultlist.getwnd(),0);
+ ListView_SetItemCount(resultlist.getwnd(),itemCache.Size);
+ ListView_RedrawItems(resultlist.getwnd(),0,itemCache.Size-1);
+ }
+ else if (l->code == LVN_BEGINDRAG)
+ {
+ SetCapture(hwndDlg);
+ }
+ else if (l->code == LVN_ITEMCHANGED)
+ {
+ history_ManageButtons(hwndDlg);
+ }
+ }
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ history_UpdateButtonText(hwndDlg, (int)wParam);
+ LayoutWindows(hwndDlg, TRUE);
+ return 0;
+ }
+ }
+ return FALSE;
+}
+
+void nukeHistory(HWND hwndDlg)
+{
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(hwndDlg, WASABI_API_LNGSTRINGW(IDS_REMOVE_ALL_HISTORY),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,titleStr,32),
+ MB_YESNO | MB_ICONQUESTION) == IDYES)
+ {
+ closeDb();
+
+ wchar_t tmp[MAX_PATH] = {0};
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\recent.dat", g_tableDir);
+ DeleteFileW(tmp);
+ StringCchPrintfW(tmp, MAX_PATH, L"%s\\recent.idx", g_tableDir);
+ DeleteFileW(tmp);
+
+ openDb();
+
+ // trigger a refresh of the current view
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+}
+
+BOOL CALLBACK view_errorinfoDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ BOOL a= (BOOL)dialogSkinner.Handle(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowText(GetDlgItem(hwndDlg, IDC_DB_ERROR),
+ (wchar_t*)WASABI_API_LOADRESFROMFILEW(TEXT("TEXT"), MAKEINTRESOURCE((nde_error ? IDR_NDE_ERROR : IDR_DB_ERROR)), 0));
+
+ if (nde_error)
+ DestroyWindow(GetDlgItem(hwndDlg, IDC_RESET_DB_ON_ERROR));
+
+ FLICKERFIX ff;
+ INT index;
+ INT ffcl[] = { IDC_DB_ERROR,
+ IDC_RESET_DB_ON_ERROR,
+ };
+
+ ff.mode = FFM_ERASEINPAINT;
+ for (index = 0; index < (sizeof(ffcl) / sizeof(INT)); index++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]);
+ if (IsWindow(ff.hwnd))
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
+ }
+ }
+
+ 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)
+ {
+ nukeHistory(hwndDlg);
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows2(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags));
+ }
+ return 0;
+
+ case WM_USER+66:
+ if (wParam == -1)
+ {
+ LayoutWindows2(hwndDlg, TRUE);
+ }
+ 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;
+}
+
+void history_cleanupifnecessary()
+{
+ if (!g_table) return;
+
+ time_t now=time(NULL);
+
+ // if we've done it in the last 8 hours, don't do it again!
+ if (now < g_config->ReadInt(L"recent_limitlt",0) + 8*60*60) return;
+
+ // time to cleanup
+ int limit_d=g_config->ReadInt(L"recent_limitd",1);
+ int limit_dn=g_config->ReadInt(L"recent_limitnd",30);
+
+ if (!limit_d || limit_dn < 1) return;
+
+ g_config->WriteInt(L"recent_limitlt",(int)now);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+ wchar_t str[512] = {0};
+ StringCchPrintfW(str,512,L"lastplay < [%d days ago]",limit_dn);
+ NDE_Scanner_Query(s, str);
+ NDE_Scanner_First(s);
+ for (;;)
+ {
+ if (!NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_LASTPLAYED)) break;
+
+ NDE_Scanner_Edit(s);
+ NDE_Scanner_Delete(s);
+ NDE_Scanner_Post(s);
+ g_table_dirty++;
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+ if (g_table_dirty)
+ {
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ NDE_Table_Compact(g_table);
+ }
+ LeaveCriticalSection(&g_db_cs);
+}
+
+void history_onFile(const wchar_t *fn, int offset)
+{
+ if (!fn || fn && !*fn) return;
+
+ int isstream=!!wcsstr(fn,L"://");
+ if (isstream)
+ {
+ if (g_config->ReadInt(L"recent_track",1)&2) return;
+ }
+ else
+ {
+ if (!(g_config->ReadInt(L"recent_track",1)&1)) return;
+ }
+
+ if (!g_table && !openDb()) return;
+
+ const wchar_t *filename = fn;
+
+ int was_querying=0;
+ if (history_bgThread_Handle)
+ {
+ history_bgQuery_Stop();
+ was_querying=1;
+ }
+ KillTimer(m_hwnd,123);
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ wchar_t filename2[2048] = {0}; // full lfn path if set
+ makeFilename2(filename,filename2,ARRAYSIZE(filename2));
+ int found=0;
+
+ if (filename2[0])
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME, FIRST_RECORD, filename2)) found = 2;
+ }
+ if (!found)
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME, FIRST_RECORD, filename)) found = 1;
+ }
+
+ int cnt=0;
+ if (found)
+ {
+ NDE_Scanner_Edit(s);
+ if (found == 1 && filename2[0]) db_setFieldString(s,HISTORYVIEW_COL_FILENAME,filename2); // if we have a better filename, update it
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_PLAYCOUNT);
+ cnt = f?NDE_IntegerField_GetValue(f):0;
+ }
+ else
+ {
+ NDE_Scanner_New(s);
+ db_setFieldString(s, HISTORYVIEW_COL_FILENAME, filename2[0] ? filename2 : filename);
+
+ int plidx= (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETLISTPOS);
+ const wchar_t *ft=(const wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, plidx, IPC_GETPLAYLISTTITLEW);
+ if (!ft || (INT_PTR)ft == 1) ft=fn;
+ const wchar_t *ftp=ft;
+ int length= (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 1, IPC_GETOUTPUTTIME);
+
+ if (*ftp == '[' && (ftp=wcsstr(ftp,L"]")))
+ {
+ ftp++;
+ while (ftp && *ftp == ' ') ftp++;
+ if (ftp && !*ftp) ftp=ft;
+ }
+ else ftp=ft;
+
+ db_setFieldInt(s, HISTORYVIEW_COL_LENGTH, length);
+ db_setFieldString(s, HISTORYVIEW_COL_TITLE, ftp);
+ }
+
+ if (offset >= 0)
+ {
+ db_setFieldInt(s, HISTORYVIEW_COL_OFFSET, (offset > 0 ? offset : -1));
+ }
+ else
+ {
+ db_setFieldInt(s, HISTORYVIEW_COL_LASTPLAYED, (int)time(NULL));
+ db_setFieldInt(s, HISTORYVIEW_COL_PLAYCOUNT, cnt+1);
+ }
+
+ NDE_Scanner_Post(s);
+
+ NDE_Table_DestroyScanner(g_table, s);
+ NDE_Table_Sync(g_table);
+ g_table_dirty++;
+
+ // changed to save the history when updated to prevent it being
+ // lost but retains the 8hr cleanup, etc which is otherwise run
+ if (g_table_dirty > 100)
+ {
+ history_cleanupifnecessary();
+ }
+ if (g_table_dirty)
+ {
+ // and to keep existing behaviour for the dirty count, we
+ // ensure that even on saving we maintain the dirty count
+ closeDb(false);
+ openDb();
+ }
+ LeaveCriticalSection(&g_db_cs);
+
+ if (was_querying)
+ history_bgQuery();
+
+ if (IsWindow(m_hwnd))
+ {
+ m_lv_last_topidx=ListView_GetTopIndex(resultlist.getwnd());
+ SendMessage(m_hwnd,WM_APP+1,0,0);
+ }
+}
+
+int retrieve_offset(const wchar_t *fn)
+{
+ int offset = -1;
+
+ int isstream=!!wcsstr(fn,L"://");
+ if (isstream)
+ {
+ if (g_config->ReadInt(L"recent_track",1)&2) return offset;
+ }
+ else
+ {
+ if (!(g_config->ReadInt(L"recent_track",1)&1)) return offset;
+ }
+
+ if (!g_table && !openDb()) return offset;
+
+ const wchar_t *filename=fn;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s = NDE_Table_CreateScanner(g_table);
+
+ wchar_t filename2[2048] = {0}; // full lfn path if set
+ makeFilename2(filename,filename2,ARRAYSIZE(filename2));
+ int found=0;
+
+ if (filename2[0])
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,filename2)) found=2;
+ }
+ if (!found)
+ {
+ if (NDE_Scanner_LocateFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,filename)) found=1;
+ }
+
+ if (found)
+ {
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, HISTORYVIEW_COL_OFFSET);
+ offset = f?NDE_IntegerField_GetValue(f):-1;
+ }
+
+ NDE_Table_DestroyScanner(g_table, s);
+
+ LeaveCriticalSection(&g_db_cs);
+
+ return offset;
+}
+
+void fileInfoDialogs(HWND hwndParent)
+{
+ history_bgQuery_Stop();
+ int l=resultlist.GetCount(),i;
+ int needref=0;
+ for(i=0;i<l;i++)
+ {
+ if(!resultlist.GetSelected(i)) continue;
+ historyRecord *song=(historyRecord *)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;
+
+ EnterCriticalSection(&g_db_cs);
+ nde_scanner_t s= NDE_Table_CreateScanner(g_table);
+ if (NDE_Scanner_LocateNDEFilename(s, HISTORYVIEW_COL_FILENAME,FIRST_RECORD,song->filename))
+ {
+ wchar_t ft[1024]={0};
+ basicFileInfoStructW bi = {0};
+ bi.filename=p.filename;
+ bi.length=-1;
+ bi.title=ft;
+ bi.titlelen=ARRAYSIZE(ft);
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&bi,IPC_GET_BASIC_FILE_INFOW);
+
+ db_setFieldInt(s,HISTORYVIEW_COL_LENGTH,bi.length);
+ db_setFieldString(s,HISTORYVIEW_COL_TITLE,ft);
+
+ NDE_Scanner_Post(s);
+ }
+ NDE_Table_DestroyScanner(g_table, s);
+ g_table_dirty++;
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ if (g_table_dirty && g_table)
+ {
+ EnterCriticalSection(&g_db_cs);
+ NDE_Table_Sync(g_table);
+ g_table_dirty=0;
+ LeaveCriticalSection(&g_db_cs);
+ }
+
+ if (needref)
+ {
+ SendMessage(hwndParent,WM_TIMER,UPDATE_QUERY_TIMER_ID,0);
+ }
+
+ MSG msg;
+ while(PeekMessage(&msg,NULL,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE)); //eat return
+} \ No newline at end of file