aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_pmp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library/ml_pmp')
-rw-r--r--Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp848
-rw-r--r--Src/Plugins/Library/ml_pmp/AlbumArtListView.h44
-rw-r--r--Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp1173
-rw-r--r--Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h64
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceCommands.cpp192
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceCommands.h65
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceView.cpp2714
-rw-r--r--Src/Plugins/Library/ml_pmp/DeviceView.h257
-rw-r--r--Src/Plugins/Library/ml_pmp/Filters.cpp829
-rw-r--r--Src/Plugins/Library/ml_pmp/Filters.h58
-rw-r--r--Src/Plugins/Library/ml_pmp/IconStore.cpp237
-rw-r--r--Src/Plugins/Library/ml_pmp/IconStore.h39
-rw-r--r--Src/Plugins/Library/ml_pmp/LinkedQueue.cpp125
-rw-r--r--Src/Plugins/Library/ml_pmp/LinkedQueue.h41
-rw-r--r--Src/Plugins/Library/ml_pmp/PmpDevice.cpp557
-rw-r--r--Src/Plugins/Library/ml_pmp/PmpDevice.h1
-rw-r--r--Src/Plugins/Library/ml_pmp/SkinnedListView.cpp856
-rw-r--r--Src/Plugins/Library/ml_pmp/SkinnedListView.h86
-rw-r--r--Src/Plugins/Library/ml_pmp/api__ml_pmp.h56
-rw-r--r--Src/Plugins/Library/ml_pmp/autofill.cpp432
-rw-r--r--Src/Plugins/Library/ml_pmp/banner.cpp216
-rw-r--r--Src/Plugins/Library/ml_pmp/banner.h44
-rw-r--r--Src/Plugins/Library/ml_pmp/config.cpp50
-rw-r--r--Src/Plugins/Library/ml_pmp/config.h21
-rw-r--r--Src/Plugins/Library/ml_pmp/editinfo.cpp790
-rw-r--r--Src/Plugins/Library/ml_pmp/graphics.cpp316
-rw-r--r--Src/Plugins/Library/ml_pmp/graphics.h71
-rw-r--r--Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp313
-rw-r--r--Src/Plugins/Library/ml_pmp/local_menu.cpp189
-rw-r--r--Src/Plugins/Library/ml_pmp/local_menu.h15
-rw-r--r--Src/Plugins/Library/ml_pmp/main.cpp1454
-rw-r--r--Src/Plugins/Library/ml_pmp/main.h91
-rw-r--r--Src/Plugins/Library/ml_pmp/metadata_utils.cpp516
-rw-r--r--Src/Plugins/Library/ml_pmp/metadata_utils.h48
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.rc1228
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.sln141
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj444
-rw-r--r--Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters298
-rw-r--r--Src/Plugins/Library/ml_pmp/mt19937ar.cpp3
-rw-r--r--Src/Plugins/Library/ml_pmp/mt19937ar.h6
-rw-r--r--Src/Plugins/Library/ml_pmp/pluginloader.cpp187
-rw-r--r--Src/Plugins/Library/ml_pmp/pluginloader.h14
-rw-r--r--Src/Plugins/Library/ml_pmp/pmp.h321
-rw-r--r--Src/Plugins/Library/ml_pmp/prefs.cpp1541
-rw-r--r--Src/Plugins/Library/ml_pmp/replaceVars.cpp213
-rw-r--r--Src/Plugins/Library/ml_pmp/resource1.h475
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/albumArt.pngbin0 -> 192 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/columns.pngbin0 -> 133 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/deviceIcon.pngbin0 -> 222 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/notfound.pngbin0 -> 3645 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/playlistIcon.pngbin0 -> 194 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/sync-command-small.pngbin0 -> 532 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.pngbin0 -> 215 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.pngbin0 -> 217 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.pngbin0 -> 211 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.pngbin0 -> 216 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/usb.pngbin0 -> 441 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/videoIcon.pngbin0 -> 197 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/resources/viewMode.pngbin0 -> 146 bytes
-rw-r--r--Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp675
-rw-r--r--Src/Plugins/Library/ml_pmp/syncCloudDialog.h17
-rw-r--r--Src/Plugins/Library/ml_pmp/syncDialog.cpp443
-rw-r--r--Src/Plugins/Library/ml_pmp/syncDialog.h20
-rw-r--r--Src/Plugins/Library/ml_pmp/transcoder.h38
-rw-r--r--Src/Plugins/Library/ml_pmp/transcoder_imp.cpp539
-rw-r--r--Src/Plugins/Library/ml_pmp/transcoder_imp.h122
-rw-r--r--Src/Plugins/Library/ml_pmp/transfer_thread.cpp503
-rw-r--r--Src/Plugins/Library/ml_pmp/transfer_thread.h94
-rw-r--r--Src/Plugins/Library/ml_pmp/version.rc239
-rw-r--r--Src/Plugins/Library/ml_pmp/view_pmp_devices.cpp764
-rw-r--r--Src/Plugins/Library/ml_pmp/view_pmp_media.cpp3205
-rw-r--r--Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp1181
72 files changed, 25319 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp b/Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp
new file mode 100644
index 00000000..42566ba8
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/AlbumArtListView.cpp
@@ -0,0 +1,848 @@
+#include "AlbumArtListView.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "../tataki/export.h"
+#include <api/service/waServiceFactory.h>
+#include <api/service/svcs/svc_imgload.h>
+#include "./local_menu.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+#define WM_EX_GETREALLIST (WM_USER + 0x01)
+
+#define HORZ_SPACING 4
+#define VERT_SPACING 4
+
+AlbumArtListView::AlbumArtListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu)
+ : SkinnedListView(lc,dlgitem,libraryParent,parent,enableHeaderMenu), hbmpNames(NULL),
+ classicnotfoundW(0), classicnotfoundH(0), ratingrow(-1), itemHeight(0), textHeight(0), ratingTop(0),
+ notfound(L"winamp.cover.notfound"), notfound60(L"winamp.cover.notfound.60"), notfound90(L"winamp.cover.notfound.90")
+{
+ this->hwndDlg = parent;
+ this->dlgitem = dlgitem;
+ mode = lc->config->ReadInt(L"albumartviewmode",0);
+ lc->SetMode(mode);
+
+ ZeroMemory(classicnotfound, sizeof(classicnotfound));
+}
+
+AlbumArtListView::~AlbumArtListView() {
+ if (hbmpNames) DeleteObject(hbmpNames);
+}
+
+int AlbumArtListView::GetFindItemColumn() {
+ if(mode > 1) return 1;
+ return contents->GetSortColumn();
+}
+
+static COLORREF GetWAColor(INT index)
+{
+ static int (*wad_getColor)(int idx) = NULL;
+ if (!wad_getColor) *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ return (wad_getColor) ? wad_getColor(index) : 0xFF00FF;
+}
+
+static void getImgSize(int mode, int &w, int &h) {
+ switch(mode) {
+ case 0: w=h=60; break;
+ case 1: w=h=90; break;
+ case 2: w=h=120; break;
+ case 3: w=h=60; break;
+ case 4: w=h=90; break;
+ case 5: w=h=120; break;
+ }
+}
+
+static bool isDetailsMode(int mode) {
+ return mode == 0 || mode == 1 || mode == 2;
+}
+
+void DrawRect(HDC dc, int x, int y, int w, int h) {
+ w-=1;
+ h-=1;
+ MoveToEx(dc,x,y,NULL);
+ LineTo(dc,x,y+h);
+ MoveToEx(dc,x,y+h,NULL);
+ LineTo(dc,x+w,y+h);
+ MoveToEx(dc,x+w,y+h,NULL);
+ LineTo(dc,x+w,y);
+ MoveToEx(dc,x+w,y,NULL);
+ LineTo(dc,x,y);
+}
+
+static int getStrExtent(HDC dc, const wchar_t * s) {
+ int ret=0;
+ while(s && *s) {
+ int f=0;
+ GetCharWidth32(dc,*s,*s,&f);
+ s++;
+ ret+=f;
+ }
+ return int(ret);
+}
+
+ARGB32 * loadImg(const void * data, int len, int *w, int *h, bool ldata=false) {
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for(int i=0; i<n; i++) {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if(sf) {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if(l) {
+ if(l->testData(data,len)) {
+ ARGB32* ret;
+ if(ldata) ret = l->loadImageData(data,len,w,h);
+ else ret = l->loadImage(data,len,w,h);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+ARGB32 * loadRrc(int id, char * sec, int *w, int *h, bool data=false)
+{
+ DWORD size = 0;
+ // as a nice little treat, allow lang packs to contain a custom IDR_IMAGE_NOTFOUND file
+ HGLOBAL resourceHandle = WASABI_API_LOADRESFROMFILEA((LPCSTR)sec, (LPCSTR)MAKEINTRESOURCEA(id), &size);
+ if(resourceHandle)
+ {
+ ARGB32* ret = loadImg(resourceHandle,size,w,h,data);
+ UnlockResource(resourceHandle);
+ return ret;
+ }
+ return NULL;
+}
+
+void adjustbmp(ARGB32 * p, int len, COLORREF fg)
+{
+ ARGB32 * end = p+len;
+ while (p < end)
+ {
+ int a = (*p>>24)&0xff ;
+ int b = a*((*p&0xff) * (fg&0xff)) / (0xff*0xff);
+ int g = a*(((*p>>8)&0xff) * ((fg>>8)&0xff)) / (0xff*0xff);
+ int r = a*(((*p>>16)&0xff) * ((fg>>16)&0xff)) / (0xff*0xff);
+ *p = (a<<24) | (r&0xff) | ((g&0xff)<<8) | ((b&0xff)<<16);
+ p++;
+ }
+}
+
+void AlbumArtListView::drawArt(pmpart_t art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex)
+{
+ int x = prcDst->left;
+ int y = prcDst->top;
+ int w = prcDst->right - prcDst->left;
+ int h = prcDst->bottom - prcDst->top;
+ HDC dc = pCanvas->getHDC();
+ if(art) contents->dev->setArtNaturalSize(art,w,h);
+ // draw image 4,4,w,h
+ if(art && contents->dev->drawArt(art,pCanvas->getHDC(),x,y,w,h)) {
+ // drawn by plugin!
+ }
+ else
+ {
+ SkinBitmap *noart;
+
+ int h = prcDst->right - prcDst->left;
+ if (h == 60)
+ noart = notfound60.getBitmap();
+ else if (h == 90)
+ noart = notfound90.getBitmap();
+ else
+ noart = notfound.getBitmap();
+
+ if (!noart || noart->isInvalid())
+ {
+ if(classicnotfound[imageIndex])
+ SkinBitmap(classicnotfound[imageIndex],classicnotfoundW,classicnotfoundH).stretchToRectAlpha(pCanvas, prcDst);
+ else
+ {
+ DrawRect(dc,x,y,w,h);
+ wchar_t str1[32] = {0}, str2[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_IMAGE,str1,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_AVAILABLE,str2,32);
+ ExtTextOutW(dc, w/2 - 22 + x, w/2 - 14 + y, 0,NULL,str1,wcslen(str1),0);
+ ExtTextOutW(dc, w/2 - 22 + x, w/2 + 1 + y, 0,NULL,str2,wcslen(str2),0);
+ }
+ }
+ else
+ noart->stretch(pCanvas,x,y,w,h);
+ }
+}
+
+// icon view
+BOOL AlbumArtListView::DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive)
+{
+ HDC hdc = plvcd->nmcd.hdc;
+ RECT ri, re, rcText;
+ int w=0,h=0, imageIndex;
+ getImgSize(mode,w,h);
+
+ SetBkColor(hdc, plvcd->clrTextBk);
+ SetTextColor(hdc, plvcd->clrText);
+ SetRect(&rcText, plvcd->nmcd.rc.left, plvcd->nmcd.rc.bottom - (textHeight + 2), plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom);
+ imageIndex = 0;
+ if ((LVIS_SELECTED | LVIS_FOCUSED) & itemState)
+ {
+ SetRect(&re, plvcd->nmcd.rc.left, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, rcText.top - 4);
+ if (IntersectRect(&ri, &re, prcClip)) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+ if (LVIS_SELECTED & itemState) imageIndex = (bWndActive) ? 1 : 2;
+ }
+
+ SetRect(&re, plvcd->nmcd.rc.left + 2, plvcd->nmcd.rc.top + 2, plvcd->nmcd.rc.left + 2 + w, plvcd->nmcd.rc.top + 2 + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ pmpart_t art = contents->GetArt(plvcd->nmcd.dwItemSpec);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ if (IntersectRect(&ri, &rcText, prcClip))
+ {
+ if ((LVIS_SELECTED | LVIS_FOCUSED) & itemState) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ wchar_t buf[104] = {0};
+ contents->GetCellText(plvcd->nmcd.dwItemSpec,1,buf,100);
+ const wchar_t *p = buf;
+ if (p && *p)
+ {
+ SetRect(&ri, rcText.left + 2, rcText.top + 1, rcText.right - 2, rcText.bottom -1);
+ DrawTextW(hdc, p, -1, &ri, DT_CENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_NOPREFIX);
+ }
+
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ HWND hwndRealList;
+ hwndRealList = (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L);
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
+ DrawFocusRect(hdc, &rcText);
+ }
+ }
+ }
+ return TRUE;
+}
+
+//detail view
+BOOL AlbumArtListView::PrepareDetails(HDC hdc)
+{
+ INT width(0), height, len;
+ HFONT hFont,hFontBold, hOldFont;
+ HDC hdcTmp;
+ HBITMAP hbmpOld;
+ LOGFONT l={0};
+ RECT ri = {0};
+ wchar_t ratingstr[100] = {0}, buf[100] = {0};
+
+ hdcTmp = CreateCompatibleDC(hdc);
+ if (!hdcTmp) return FALSE;
+
+ hFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
+ GetObject(hFont, sizeof(LOGFONT), &l);
+ l.lfWeight = FW_BOLD;
+ hFontBold = CreateFontIndirect(&l);
+
+ hOldFont = (HFONT)SelectObject(hdcTmp, hFontBold);
+
+ for (int i=0; i < contents->GetNumColumns(); i++)
+ {
+ int of = getStrExtent(hdcTmp, contents->GetColumnTitle(i));
+ if (of > width) width = of;
+ }
+ if(width) width += 20;
+ height = contents->GetNumColumns() * textHeight;
+ hbmpNames = CreateCompatibleBitmap(hdc, width * 3, height);
+ hbmpOld = (HBITMAP)SelectObject(hdcTmp, hbmpNames);
+
+ SetRect(&ri, 0, 0, width, height);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_RATING,ratingstr,100);
+ INT clrText[3] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, };
+ INT clrBk[3] = { WADLG_ITEMBG, WADLG_SELBAR_BGCOLOR, WADLG_INACT_SELBAR_BGCOLOR, };
+ for (int j = 0; j < 3; j++)
+ {
+ SetTextColor(hdcTmp, GetWAColor(clrText[j]));
+ SetBkColor(hdcTmp, GetWAColor(clrBk[j]));
+
+ ExtTextOutW(hdcTmp,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+
+ for (int i=0, top = 0; i < contents->GetNumColumns(); i++, top += textHeight)
+ {
+ if (-1 == ratingrow && 0 == lstrcmpW(contents->GetColumnTitle(i), ratingstr)) ratingrow = i;
+ StringCchCopyW(buf, 100, contents->GetColumnTitle(i));
+ len = wcslen(buf);
+ if (len > 0 && len < 99) { buf[len] = L':'; len ++; }
+ ExtTextOutW(hdcTmp, ri.left + 1, top, ETO_CLIPPED, &ri, buf, len, NULL);
+ }
+ OffsetRect(&ri, width, 0);
+ }
+
+ SelectObject(hdcTmp, hbmpOld);
+ SelectObject(hdcTmp, hOldFont);
+ DeleteObject(hFontBold);
+ DeleteDC(hdcTmp);
+ return TRUE;
+}
+
+BOOL AlbumArtListView::DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNames, INT namesWidth)
+{
+ RECT ri, re;
+ HDC hdc;
+
+ INT imageIndex;
+
+ hdc = plvcd->nmcd.hdc;
+
+ SetTextColor(hdc, plvcd->clrText);
+ SetBkColor(hdc, plvcd->clrTextBk);
+
+ if (LVIS_SELECTED & itemState)
+ {
+ // background
+ SetRect(&re, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if (IntersectRect(&ri, &re, prcClip)) ExtTextOutW(hdc,0, 0, ETO_OPAQUE, &ri, L"", 0, 0);
+ imageIndex = (bWndActive) ? 1 : 2;
+ }
+ else imageIndex = 0;
+
+ int w, h;
+ getImgSize(mode, w, h);
+ SetRect(&re, 6+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, 6+ plvcd->nmcd.rc.left + w, 3+ plvcd->nmcd.rc.top + h);
+ if (IntersectRect(&ri, &re, prcClip))
+ {
+ pmpart_t art = contents->GetArt(plvcd->nmcd.dwItemSpec);
+ drawArt(art, pCanvas, &re, plvcd->nmcd.dwItemSpec, imageIndex);
+ }
+
+ // text
+ int limCY, limCX;
+ wchar_t buf[100] = {0};
+
+ limCY = plvcd->nmcd.rc.bottom;
+ if (prcClip->bottom < plvcd->nmcd.rc.bottom) limCY = prcClip->bottom;
+ limCX = plvcd->nmcd.rc.right -1;
+ if (prcClip->right < plvcd->nmcd.rc.right) limCX = prcClip->right;
+
+ SetRect(&ri, w+16+plvcd->nmcd.rc.left, 3+plvcd->nmcd.rc.top, limCX, limCY);
+
+ if (hdcNames && ri.left < ri.right)
+ {
+ BitBlt(hdc, ri.left, ri.top, min(ri.right - ri.left, namesWidth), ri.bottom - ri.top, hdcNames, namesWidth*imageIndex, 0, SRCCOPY);
+ ri.left += namesWidth;
+ }
+
+ ri.bottom = ri.top;
+
+ if (ri.left < ri.right)
+ {
+ for (int i=0; i < contents->GetNumColumns() && ri.top < limCY; i++, ri.top += textHeight)
+ {
+ contents->GetCellText(plvcd->nmcd.dwItemSpec,i,buf,100);
+ const wchar_t *p = buf;
+
+ ri.bottom += textHeight;
+ if (ri.bottom > limCY) ri.bottom = limCY;
+
+ if ((INT)i == ratingrow) // this is the ratings column, so draw graphical stars
+ {
+ int rating = wcslen(buf);
+ RATINGDRAWPARAMS p = {sizeof(RATINGDRAWPARAMS),hdc,
+ {ri.left, ri.top + ratingTop, ri.right,ri.bottom},
+ rating,5,0,RDS_SHOWEMPTY,NULL,0};
+ MLRating_Draw(plugin.hwndLibraryParent,&p);
+ }
+ else ExtTextOutW(hdc, ri.left, ri.top, ETO_CLIPPED ,&ri,p,wcslen(p),NULL);
+ }
+ }
+
+ // bottom line
+ MoveToEx(hdc,plvcd->nmcd.rc.left + 4,plvcd->nmcd.rc.bottom - 3,NULL);
+ LineTo(hdc,plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 3);
+
+ // focus rect
+ SetRect(&ri, plvcd->nmcd.rc.left + 4, plvcd->nmcd.rc.top, plvcd->nmcd.rc.right, plvcd->nmcd.rc.bottom - 5);
+ if ((LVIS_FOCUSED & itemState) && bWndActive)
+ {
+ HWND hwndRealList;
+ hwndRealList = (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L);
+ if (hwndRealList && 0 == (0x01/*UISF_HIDEFOCUS*/ & SendMessageW(hwndRealList, 0x0129/*WM_QUERYUISTATE*/, 0, 0L)))
+ {
+ SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+ SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
+ DrawFocusRect(hdc, &ri);
+ }
+ }
+
+ return TRUE;
+}
+
+static HWND CreateSmoothScrollList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"SmoothScrollList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+static HWND CreateHeaderIconList(HWND parent, int x, int y, int cx, int cy, int dlgid) {
+ DWORD flags = WS_CHILD | WS_VSCROLL | DS_CONTROL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+ HWND h = CreateWindowExW(WS_EX_CONTROLPARENT, L"HeaderIconList", L"",flags, x, y, cx, cy, parent,(HMENU)dlgid, NULL, (LPVOID)0);
+ SendMessage(h,WM_INITDIALOG,0,0);
+ return h;
+}
+
+BOOL AlbumArtListView::OnKeyDown(NMLVKEYDOWN *plvkd)
+{
+ switch (plvkd->wVKey)
+ {
+ case 'A':
+ if (GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT))
+ {
+ LVITEM item;
+ item.state = LVIS_SELECTED;
+ item.stateMask = LVIS_SELECTED;
+ SendMessageW(plvkd->hdr.hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item);
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtListView::OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ bActive = (GetFocus() == (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L));
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState;
+ itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+ plvcd->nmcd.rc.right -= HORZ_SPACING;
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemIcon(plvcd, &activeCanvas, itemState, &rcClip, bActive);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtListView::OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static RECT rcClip;
+ static BOOL bActive;
+ static HDC hdcNames;
+ static INT namesWidth;
+ static HBITMAP hbmpOld;
+ static HPEN penOld;
+ static DCCanvas activeCanvas;
+
+ *pResult = CDRF_DODEFAULT;
+ switch(plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ if (0 == plvcd->nmcd.rc.bottom && 0 == plvcd->nmcd.rc.right)
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ CopyRect(&rcClip, &plvcd->nmcd.rc);
+ if (!hbmpNames) PrepareDetails(plvcd->nmcd.hdc);
+ if (hbmpNames)
+ {
+ BITMAP bi;
+ GetObject(hbmpNames, sizeof(BITMAP), &bi);
+ namesWidth = bi.bmWidth/3;
+ hdcNames = CreateCompatibleDC(plvcd->nmcd.hdc);
+ hbmpOld = (hdcNames) ? (HBITMAP)SelectObject(hdcNames, hbmpNames) : NULL;
+ }
+ else
+ {
+ hdcNames = NULL;
+ namesWidth = 0;
+ }
+ bActive = (GetFocus() == (HWND)SendMessageW(plvcd->nmcd.hdr.hwndFrom, WM_EX_GETREALLIST, 0, 0L));
+
+ penOld = (HPEN)SelectObject(plvcd->nmcd.hdc, CreatePen(PS_SOLID,1, GetWAColor(WADLG_HILITE)));
+ activeCanvas.cloneDC(plvcd->nmcd.hdc, NULL);
+ *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ UINT itemState;
+ itemState = SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMSTATE, plvcd->nmcd.dwItemSpec,
+ LVIS_FOCUSED | LVIS_SELECTED | LVIS_DROPHILITED | LVIS_CUT);
+
+ plvcd->nmcd.rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&plvcd->nmcd.rc);
+
+ if (rcClip.left < plvcd->nmcd.rc.right)
+ {
+ DrawItemDetail(plvcd, &activeCanvas, itemState, &rcClip, bActive, hdcNames, namesWidth);
+ *pResult = CDRF_SKIPDEFAULT;
+ }
+ }
+ return TRUE;
+
+ case CDDS_POSTPAINT:
+ if (hdcNames)
+ {
+ SelectObject(hdcNames, hbmpOld);
+ DeleteDC(hdcNames);
+ }
+ if (penOld)
+ {
+ HPEN pen;
+ pen = (HPEN)SelectObject(plvcd->nmcd.hdc, penOld);
+ if (pen) DeleteObject(pen);
+ penOld = NULL;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL AlbumArtListView::CalcuateItemHeight(void)
+{
+ int w, h;
+ HWND hwndList = GetDlgItem(hwndDlg, dlgitem);
+ TEXTMETRIC tm = {0};
+
+ getImgSize(mode, w, h);
+
+ textHeight = 0;
+
+ HDC hdc = GetDC(hwndDlg);
+ if (hdc)
+ {
+ HFONT hFont = (HFONT)SendMessageW(hwndList, WM_GETFONT, 0, 0L);
+ if (!hFont) hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ HFONT hFontOld = (HFONT)SelectObject(hdc, hFont);
+
+ GetTextMetrics(hdc, &tm);
+ textHeight = tm.tmHeight;
+ SelectObject(hdc, hFontOld);
+ ReleaseDC(hwndDlg, hdc);
+ }
+
+ if (isDetailsMode(mode))
+ {
+ if (textHeight < 14) textHeight = 14;
+ RECT r;
+ MLRating_CalcRect(plugin.hwndLibraryParent, NULL, 5, &r);
+ r.bottom -= r.top;
+
+ if ( r.bottom >= textHeight ) ratingTop = 0;
+ else
+ {
+ if (tm.tmAscent > (r.bottom + (r.bottom/2))) ratingTop = tm.tmAscent - r.bottom;
+ else ratingTop = (textHeight - r.bottom)/2 + 1;
+ }
+
+ int newHeight = max(h, textHeight * (INT)contents->GetNumColumns()) + 12;
+ if (newHeight != itemHeight)
+ {
+ itemHeight = newHeight;
+ RECT rw;
+ GetWindowRect(hwndList, &rw);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left - 1, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW);
+ SetWindowPos(hwndList, NULL, 0, 0, rw.right - rw.left, rw.bottom - rw.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
+ }
+ }
+ else
+ {
+ HIMAGELIST hIL = (HIMAGELIST)SendMessageW(hwndList, LVM_GETIMAGELIST, 0, 0L);
+ if (!hIL || !ImageList_GetIconSize(hIL, &w, &h))
+ { h += 4; w+= 4; }
+ SendMessageW(hwndList, LVM_SETICONSPACING, 0, MAKELPARAM(w + HORZ_SPACING, h + textHeight + 6 + VERT_SPACING));
+ if (!tm.tmAveCharWidth) tm.tmAveCharWidth = 2;
+ itemHeight = w / tm.tmAveCharWidth;
+ }
+ return TRUE;
+}
+
+BOOL AlbumArtListView::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ HWND hwnd = GetDlgItem(hwndDlg,dlgitem);
+
+ RECT r;
+ if (hwnd)
+ {
+ GetWindowRect(hwnd,&r);
+ MapWindowPoints(HWND_DESKTOP, hwndDlg, (POINT*)&r, 2);
+ }
+ else SetRect(&r, 0, 0, 1, 1);
+
+ if (isDetailsMode(mode))
+ {
+ if (hwnd) DestroyWindow(hwnd);
+ hwnd = CreateSmoothScrollList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ SendMessage(hwnd,WM_USER+6,0,0);
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+ }
+ else
+ {
+ if (hwnd) DestroyWindow(hwnd);
+ hwnd = CreateHeaderIconList(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, dlgitem);
+ ShowWindow(hwnd,SW_SHOWNORMAL);
+ int w=0,h=0;
+ getImgSize(mode,w,h);
+ HIMAGELIST il = ImageList_Create(w + 4,h + 4,ILC_COLOR24,0,1); // add borders
+ ListView_SetImageList(hwnd,il,LVSIL_NORMAL);
+ }
+ }
+ break;
+
+ case WM_DISPLAYCHANGE:
+ {
+ if (hbmpNames) DeleteObject(hbmpNames);
+ hbmpNames = NULL;
+ ratingrow = -1;
+ CalcuateItemHeight();
+
+ int rw,rh;
+ ARGB32 * bmp = loadRrc(IDR_IMAGE_NOTFOUND,"PNG",&rw,&rh,true);
+ classicnotfoundW = rw;
+ classicnotfoundH = rh;
+ INT color[] = { WADLG_ITEMFG, WADLG_SELBAR_FGCOLOR, WADLG_INACT_SELBAR_FGCOLOR, };
+
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i]) WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+ classicnotfound[i] = NULL;
+
+ if(bmp)
+ {
+ if (0 != i)
+ {
+ classicnotfound[i] = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*rw*rh);
+ CopyMemory(classicnotfound[i], bmp, sizeof(ARGB32)*rw*rh);
+ }
+ else classicnotfound[i] = bmp;
+ adjustbmp(classicnotfound[i],rw*rh, GetWAColor(color[i]));
+ }
+ }
+
+ if(uMsg == WM_DISPLAYCHANGE) PostMessageW(GetDlgItem(hwndDlg,dlgitem),uMsg,wParam,lParam);
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i = sizeof(classicnotfound)/sizeof(classicnotfound[0]) -1; i > -1; i--)
+ {
+ if(classicnotfound[i]) WASABI_API_MEMMGR->sysFree(classicnotfound[i]);
+ classicnotfound[i] = NULL;
+ }
+ break;
+
+ case WM_MEASUREITEM:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ ((MEASUREITEMSTRUCT*)lParam)->itemHeight = itemHeight;
+ return TRUE;
+ }
+ break;
+ case WM_NOTIFY:
+ if (wParam == (WPARAM)dlgitem)
+ {
+ BOOL bProcessed(FALSE);
+ LRESULT result(0);
+ switch (((NMHDR*)lParam)->code)
+ {
+ case LVN_KEYDOWN: bProcessed = OnKeyDown((NMLVKEYDOWN*)lParam); break;
+ case NM_CUSTOMDRAW:
+ bProcessed = (isDetailsMode(mode)) ? OnCustomDrawDetails((NMLVCUSTOMDRAW*)lParam, &result) : OnCustomDrawIcon((NMLVCUSTOMDRAW*)lParam, &result);
+ break;
+ case LVN_GETDISPINFOW:
+ return TRUE;
+ case LVN_GETINFOTIPW:
+ {
+ wchar_t buf[256]=L"";
+ contents->GetCellText(((NMLVGETINFOTIPW*)lParam)->iItem,1,buf,256);
+ const wchar_t *p = buf;
+ if (p && *p && lstrlenW(p) > itemHeight) // we use itemHeight to write number of average characters that fits label in icon mode
+ {
+ StringCchCopyW(((NMLVGETINFOTIPW*)lParam)->pszText, ((NMLVGETINFOTIPW*)lParam)->cchTextMax, p);
+ }
+ }
+ return TRUE;
+ }
+
+ if (bProcessed) SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return bProcessed;
+ }
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->code == NM_RCLICK && l->hwndFrom == ListView_GetHeader(listview.getwnd())) {
+ extern HMENU m_context_menus;
+ HMENU menu = GetSubMenu(m_context_menus, 10);
+ POINT p;
+ GetCursorPos(&p);
+ int checked=0;
+ switch(mode) {
+ case 0: checked=ID_ARTHEADERMENU_SMALLDETAILS; break;
+ case 1: checked=ID_ARTHEADERMENU_MEDIUMDETAILS; break;
+ case 2: checked=ID_ARTHEADERMENU_LARGEDETAILS; break;
+ case 3: checked=ID_ARTHEADERMENU_SMALLICON; break;
+ case 4: checked=ID_ARTHEADERMENU_MEDIUMICON; break;
+ case 5: checked=ID_ARTHEADERMENU_LARGEICON; break;
+ }
+ CheckMenuItem(menu,checked,MF_CHECKED | MF_BYCOMMAND);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, l->hwndFrom, NULL);
+ CheckMenuItem(menu,checked,MF_UNCHECKED | MF_BYCOMMAND);
+ if(!r) return TRUE;
+ ProcessMenuResult(r,false,0,NULL,hwndDlg);
+ return TRUE;
+ } else if(l->code == LVN_GETINFOTIP && l->idFrom == dlgitem) {
+ NMLVGETINFOTIP *n = (NMLVGETINFOTIP*)l;
+ wchar_t artist[100] = {0}, album[100] = {0}, tracks[10] = {0}, year[10] = {0};
+ contents->GetCellText(n->iItem,0,artist,100);
+ contents->GetCellText(n->iItem,1,album,100);
+ contents->GetCellText(n->iItem,2,tracks,10);
+ contents->GetCellText(n->iItem,3,year,10);
+ wchar_t trackstr[100] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRACKS,trackstr,100);
+ CharLower(trackstr);
+ if(year[0]) StringCchPrintf(n->pszText,n->cchTextMax,L"%s - %s (%s): %s %s",artist,album,year,tracks,trackstr);
+ else StringCchPrintf(n->pszText,n->cchTextMax,L"%s - %s: %s %s",artist,album,tracks,trackstr);
+ }
+ }
+ break;
+
+ case LVM_REDRAWITEMS:
+ {
+ RECT ri;
+ HWND hwndList = GetDlgItem(hwndDlg, dlgitem);
+ if (hwndList)
+ {
+ HWND hwndRealList = (HWND)SendMessageW(hwndList, WM_EX_GETREALLIST, 0, 0L);
+ if (hwndRealList)
+ {
+ int w, h;
+ getImgSize(mode, w, h);
+ if (isDetailsMode(mode))
+ {
+ for(int i = (INT)wParam; i <= (INT)lParam; i++)
+ {
+ ri.left = LVIR_BOUNDS;
+ if (SendMessageW(hwndRealList, LVM_GETITEMRECT, i, (LPARAM)&ri))
+ {
+ ri.left += 6;
+ ri.top += 3;
+ ri.right = ri.left + w;
+ ri.bottom = ri.top + h;
+ InvalidateRect(hwndRealList, &ri, FALSE);
+ }
+ }
+ }
+ else
+ {
+ for(int i = (INT)wParam; i <= (INT)lParam; i++)
+ {
+ ri.left = LVIR_ICON;
+ if (SendMessageW(hwndRealList, LVM_GETITEMRECT, i, (LPARAM)&ri))
+ {
+ ri.left += 2;
+ ri.top += 2;
+ ri.right = ri.left + w;
+ ri.bottom = ri.top + h;
+ InvalidateRect(hwndRealList, &ri, FALSE);
+ }
+ }
+ }
+ }
+ else SendMessageW(hwndList, LVM_REDRAWITEMS, wParam, lParam);
+ }
+ }
+ return TRUE;
+ }
+ return SkinnedListView::DialogProc(hwndDlg,uMsg,wParam,lParam);
+}
+
+HMENU AlbumArtListView::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu) {
+ HMENU menu = GetSubMenu(themenu, 10);
+
+ int checked=0;
+ switch(mode) {
+ case 0: checked=ID_ARTHEADERMENU_SMALLDETAILS; break;
+ case 1: checked=ID_ARTHEADERMENU_MEDIUMDETAILS; break;
+ case 2: checked=ID_ARTHEADERMENU_LARGEDETAILS; break;
+ case 3: checked=ID_ARTHEADERMENU_SMALLICON; break;
+ case 4: checked=ID_ARTHEADERMENU_MEDIUMICON; break;
+ case 5: checked=ID_ARTHEADERMENU_LARGEICON; break;
+ }
+ CheckMenuItem(menu,checked,MF_CHECKED | MF_BYCOMMAND);
+
+ if(isFilter) {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i=0;
+ while(GetMenuItemInfo(menu,i,TRUE,&m)) {
+ m.wID |= (1+filterNum) << 16;
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ i++;
+ }
+ }
+ return menu;
+}
+
+void AlbumArtListView::ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent) {
+ int mid = (r >> 16);
+ if(!isFilter && mid) return;
+ if(isFilter && mid-1 != filterNum) return;
+ r &= 0xFFFF;
+
+ switch(r) {
+ case ID_ARTHEADERMENU_SMALLDETAILS: mode=0; break;
+ case ID_ARTHEADERMENU_MEDIUMDETAILS: mode=1; break;
+ case ID_ARTHEADERMENU_LARGEDETAILS: mode=2; break;
+ case ID_ARTHEADERMENU_SMALLICON: mode=3; break;
+ case ID_ARTHEADERMENU_MEDIUMICON: mode=4; break;
+ case ID_ARTHEADERMENU_LARGEICON: mode=5; break;
+ default: return;
+ }
+
+ contents->config->WriteInt(L"albumartviewmode",mode);
+ contents->SetMode(mode);
+ while (ListView_DeleteColumn(listview.getwnd(), 0));
+ DialogProc(parent,WM_INITDIALOG,0,0);
+ HWND hwndList = GetDlgItem(parent,dlgitem);
+ if (hwndList)
+ {
+ MLSkinnedWnd_SkinChanged(hwndList, TRUE, TRUE);
+ ListView_SetItemCount(hwndList, contents->GetNumRows());
+ CalcuateItemHeight();
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/AlbumArtListView.h b/Src/Plugins/Library/ml_pmp/AlbumArtListView.h
new file mode 100644
index 00000000..d6335849
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/AlbumArtListView.h
@@ -0,0 +1,44 @@
+#ifndef _ALBUMARTLISTVIEW_H_
+#define _ALBUMARTLISTVIEW_H_
+
+#include "SkinnedListView.h"
+#include <tataki/bitmap/autobitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+
+class AlbumArtListView : public SkinnedListView {
+public:
+ AlbumArtListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu=true);
+
+ virtual ~AlbumArtListView();
+ virtual int GetFindItemColumn();
+ virtual BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+
+ BOOL DrawItemIcon(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive);
+ BOOL PrepareDetails(HDC hdc);
+ BOOL DrawItemDetail(NMLVCUSTOMDRAW *plvcd, DCCanvas *pCanvas, UINT itemState, RECT *prcClip, BOOL bWndActive, HDC hdcNaes, INT namesWidth);
+ void drawArt(pmpart_t art, DCCanvas *pCanvas, RECT *prcDst, int itemid, int imageIndex);
+
+protected:
+ BOOL OnCustomDrawDetails(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnCustomDrawIcon(NMLVCUSTOMDRAW *plvcd, LRESULT *pResult);
+ BOOL OnKeyDown(NMLVKEYDOWN *plvkd);
+ BOOL CalcuateItemHeight(void);
+
+ int dlgitem;
+ HWND hwndDlg;
+ int mode;
+ WNDPROC oldproc;
+ AutoSkinBitmap notfound, notfound60, notfound90;
+ ARGB32 * classicnotfound[3];
+ int classicnotfoundW,classicnotfoundH;
+ INT ratingrow;
+ HBITMAP hbmpNames;
+ INT itemHeight;
+ INT textHeight;
+ INT ratingTop;
+};
+
+#endif // _ALBUMARTLISTVIEW_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp
new file mode 100644
index 00000000..33cf8a9d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp
@@ -0,0 +1,1173 @@
+#include "ArtistAlbumLists.h"
+#include "Filters.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "metadata_utils.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
+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 lstrcmpi(pa,pb);
+}
+#undef SKIP_THE_AND_WHITESPACE
+
+Filter * getFilter(wchar_t *name);
+
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+
+typedef struct
+{
+ songid_t songid;
+ Device * dev;
+} SortSongItem;
+
+int thread_killed = 0;
+static int useby, usedir, usecloud;
+static Device * currentDev;
+
+static int sortFunc(const void *elem1, const void *elem2)
+{
+ int use_by=useby;
+ int use_dir=usedir;
+ songid_t a=(songid_t)*(void **)elem1;
+ songid_t b=(songid_t)*(void **)elem2;
+ wchar_t bufa[2048] = {0}, bufb[2048] = {0};
+
+ // this might be too slow, but it'd be nice
+ for (int x = 0; x < 5; x ++)
+ {
+ if (thread_killed) break;
+
+ bufa[0]=bufb[0]=0;
+ if (use_by == (7+usecloud)) // year -> artist -> album -> track
+ {
+ int v1=currentDev->getTrackYear(a);
+ int v2=currentDev->getTrackYear(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == 4 && usecloud) // cloud -> artist -> album -> track
+ {
+ currentDev->getTrackExtraInfo(a,L"cloud",bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackExtraInfo(b,L"cloud",bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=0;
+ }
+ else if (use_by == 1) // title -> artist -> album -> disc -> track
+ {
+ currentDev->getTrackTitle(a,bufa,2048);
+ currentDev->getTrackTitle(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=0;
+ }
+ else if (use_by == 0) // artist -> album -> disc -> track -> title
+ {
+ currentDev->getTrackArtist(a,bufa,2048);
+ currentDev->getTrackArtist(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == 2) // album -> disc -> track -> title -> artist
+ {
+ currentDev->getTrackAlbum(a,bufa,2048);
+ currentDev->getTrackAlbum(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_dir=0;
+ use_by=5;
+ }
+ else if (use_by == 5+usecloud) // disc -> track -> title -> artist -> album
+ {
+ int v1=currentDev->getTrackDiscNum(a);
+ int v2=currentDev->getTrackDiscNum(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=4;
+ }
+ else if (use_by == 4+usecloud) // track -> title -> artist -> album -> disc
+ {
+ int v1=currentDev->getTrackTrackNum(a);
+ int v2=currentDev->getTrackTrackNum(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=1;
+ }
+ else if (use_by == 6+usecloud) // genre -> artist -> album -> disc -> track
+ {
+ currentDev->getTrackGenre(a,bufa,2048);
+ currentDev->getTrackGenre(b,bufb,2048);
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=0;
+ }
+ else if (use_by == 3) // length -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackLength(a);
+ int v2=currentDev->getTrackLength(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (8+usecloud)) // bitrate -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackBitrate(a);
+ int v2=currentDev->getTrackBitrate(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (9+usecloud)) // size -> artist -> album -> disc -> track
+ {
+ __int64 v1=currentDev->getTrackSize(a);
+ __int64 v2=currentDev->getTrackSize(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (10+usecloud)) // playcount -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackPlayCount(a);
+ int v2=currentDev->getTrackPlayCount(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (11+usecloud)) // rating -> artist -> album -> disc -> track
+ {
+ int v1=currentDev->getTrackRating(a);
+ int v2=currentDev->getTrackRating(b);
+ if (v1<0)v1=0;
+ if (v2<0)v2=0;
+ RETIFNZ(v1-v2)
+ use_by=0;
+ }
+ else if (use_by == (12+usecloud))
+ {
+ double v = difftime((time_t)currentDev->getTrackLastPlayed(a),(time_t)currentDev->getTrackLastPlayed(b));
+ RETIFNZ(v);
+ use_by=0;
+ }
+ else if (use_by == (13+usecloud)) // album artist -> album
+ {
+ currentDev->getTrackAlbumArtist(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackAlbumArtist(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (14+usecloud)) // publisher -> album
+ {
+ currentDev->getTrackPublisher(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackPublisher(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (15+usecloud)) // composer -> album
+ {
+ currentDev->getTrackComposer(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackComposer(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (16+usecloud)) // mime -> artist -> album -> disc -> track
+ {
+ currentDev->getTrackMimeType(a,bufa,ARRAYSIZE(bufa));
+ currentDev->getTrackMimeType(b,bufb,ARRAYSIZE(bufb));
+ int v=STRCMP_NULLOK(bufa,bufb);
+ RETIFNZ(v)
+ use_by=2;
+ }
+ else if (use_by == (17+usecloud)) // date added -> artist -> album -> disc -> track
+ {
+ double v = difftime((time_t)currentDev->getTrackDateAdded(a),(time_t)currentDev->getTrackDateAdded(b));
+ RETIFNZ(v);
+ use_by=0;
+ }
+ else break; // no sort order?
+
+ if (thread_killed) break;
+ }
+ return 0;
+}
+
+static int sortFunc_filteritems(const void *elem1, const void *elem2) {
+ FilterItem *a=(FilterItem *)*(void **)elem1;
+ FilterItem *b=(FilterItem *)*(void **)elem2;
+ return a->compareTo2(b,useby,usedir);
+}
+
+class FilterList : public ListContents {
+public:
+ Filter * filter;
+ C_ItemList * items;
+ ArtistAlbumLists * aaList;
+
+ wchar_t topString[128];
+ int nextFilterNum;
+ int tracks;
+
+ int sortcol;
+ int sortdir;
+ int id;
+
+ FilterList(int id, Device * dev0, C_Config * config, Filter * filter, ArtistAlbumLists * aaList, bool cloud) :
+ id(id), filter(filter), sortcol(0), sortdir(0), aaList(aaList), tracks(0), nextFilterNum(0), items(0) {
+ this->topString[0] = 0;
+ this->config = config;
+ this->dev = dev0;
+ this->cloud = cloud;
+ this->cloudcol = -1;
+ filter->AddColumns(dev, &fields, config, !!this->cloud);
+
+ for(int i = 0; i < fields.GetSize(); i++) {
+ if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud"))
+ {
+ this->cloudcol = ((ListField *)fields.Get(i))->pos;
+ break;
+ }
+ }
+
+ wchar_t temp[16] = {0};
+ wsprintf(temp, L"filter%d_sortcol", id);
+ this->sortcol = config->ReadInt(temp, 0);
+ wsprintf(temp, L"filter%d_sortdir", id);
+ this->sortdir = config->ReadInt(temp, 0);
+
+ this->SortColumns();
+ }
+ virtual ~FilterList() {
+ wchar_t temp[16] = {0};
+ wsprintf(temp, L"filter%d_sortcol", id);
+ config->WriteInt(temp, sortcol);
+ wsprintf(temp, L"filter%d_sortdir", id);
+ config->WriteInt(temp, sortdir);
+ }
+ virtual int GetNumColumns() { return fields.GetSize(); }
+ virtual int GetNumRows() { return items->GetSize() + (filter->HaveTopItem()?1:0); }
+ virtual wchar_t * GetColumnTitle(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->name;
+ return L"";
+ }
+ virtual int GetColumnWidth(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->width;
+ return 0;
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) {
+ buf[0]=0;
+ if(col >= fields.GetSize() || aaList->bgThread_Handle) return;
+ int colid = ((ListField*)fields.Get(col))->field;
+ if(filter->HaveTopItem()) {
+ if(row) ((FilterItem*)items->Get(row-1))->GetCellText(colid,buf,buflen);
+ else {
+ if(colid%100 == 0) lstrcpyn(buf,topString,buflen);
+ else if(colid%100 == 41) wsprintf(buf,L"%d",tracks);
+ else if(colid%100 == 40 && filter->nextFilter) wsprintf(buf,L"%d",nextFilterNum);
+ }
+ } else ((FilterItem*)items->Get(row))->GetCellText(colid,buf,buflen);
+ }
+ virtual void SortList() {
+ if (sortcol > fields.GetSize()) return;
+ useby=((ListField*)fields.Get(sortcol))->field;
+ usedir=sortdir;
+ qsort(items->GetAll(),items->GetSize(),sizeof(void*),sortFunc_filteritems);
+ }
+ virtual int GetSortDirection() { return sortdir; }
+ virtual int GetSortColumn() { return sortcol; }
+ virtual void ColumnClicked(int col) {
+ if(col == sortcol)
+ sortdir = sortdir?0:1;
+ else {
+ sortdir=0;
+ sortcol=col;
+ }
+ SortList();
+ }
+ virtual void ColumnResize(int col, int newWidth) {
+ if(col >=0 && col < fields.GetSize()) {
+ ListField * lf = (ListField *)fields.Get(col);
+ lf->width = newWidth;
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"colWidth_%d",lf->field);
+ config->WriteInt(buf,newWidth);
+ }
+ }
+ virtual pmpart_t GetArt(int row) { return ((FilterItem*)items->Get(row))->GetArt(); }
+ virtual void SetMode(int mode) {
+ filter->SetMode(mode);
+ int i=fields.GetSize();
+ while(i>=0) { i--; delete (ListField*)fields.Get(i); fields.Del(i); }
+ filter->AddColumns(dev, &fields, config, !!this->cloud);
+ SortColumns();
+ }
+ virtual songid_t GetTrack(int pos) { return 0; }
+};
+
+static void getStars(int stars, wchar_t * buf, int buflen) {
+ wchar_t * r=L"";
+ switch(stars) {
+ case 1: r=L"\u2605"; break;
+ case 2: r=L"\u2605\u2605"; break;
+ case 3: r=L"\u2605\u2605\u2605"; break;
+ case 4: r=L"\u2605\u2605\u2605\u2605"; break;
+ case 5: r=L"\u2605\u2605\u2605\u2605\u2605"; break;
+ }
+ lstrcpyn(buf,r,buflen);
+}
+
+extern void timeToString(__time64_t time, wchar_t * buf, int buflen);
+
+static void timeValue(int totalsecs, wchar_t *dest)
+{
+ int secs=totalsecs%60;
+ int mins=(totalsecs/60)%60;
+ int hours=(totalsecs/3600)%24;
+ int days=(totalsecs/86400);
+ if(days==0) {
+ wsprintf(dest,L"%d:%02d:%02d",hours,mins,secs);
+ } else if(days==1) {
+ wsprintf(dest,L"%d day+%d:%02d:%02d",days,hours,mins,secs);
+ } else {
+ wsprintf(dest,L"%d days+%d:%02d:%02d",days,hours,mins,secs);
+ }
+}
+
+void GetInfoString(wchar_t * buf, Device * dev, int numTracks, __int64 totalSize, int totalPlayLength, int cloud) {
+ wchar_t lengthStr[100]=L"";
+ wchar_t sizeStr[100]=L"";
+ wchar_t availStr[100]=L"";
+ wchar_t devCapacityStr[100]=L"";
+ int usedPercent;
+
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits || (fieldsBits & SUPPORTS_LENGTH)) {
+ lengthStr[0] = L'[';
+ timeValue(totalPlayLength,&lengthStr[1]);
+ wcscat_s(lengthStr,100,L"] ");
+ }
+
+ __int64 available = dev->getDeviceCapacityAvailable();
+ WASABI_API_LNG->FormattedSizeString(sizeStr, ARRAYSIZE(sizeStr), totalSize);
+ WASABI_API_LNG->FormattedSizeString(availStr, ARRAYSIZE(availStr), available);
+ __int64 capacity = dev->getDeviceCapacityTotal();
+ WASABI_API_LNG->FormattedSizeString(devCapacityStr, ARRAYSIZE(devCapacityStr), capacity);
+
+ if(capacity > 0) usedPercent = (int)((((__int64)100)*available) / capacity);
+ else usedPercent = 0;
+ if (!cloud || cloud && available > 0)
+ wsprintf(buf, WASABI_API_LNGSTRINGW(IDS_X_ITEMS_X_AVAILABLE), numTracks, lengthStr, sizeStr, availStr, usedPercent, devCapacityStr);
+ else
+ wsprintf(buf, WASABI_API_LNGSTRINGW(IDS_X_ITEMS_X_AVAILABLE_SLIM), numTracks, lengthStr, sizeStr);
+}
+
+class TracksList : public PrimaryListContents {
+public:
+ __int64 totalSize;
+ int totalPlayLength;
+ int sortcol;
+ int sortdir;
+ C_ItemList * tracks;
+ Device * dev;
+ ArtistAlbumLists * aaList;
+ TracksList(Device * dev,C_Config * config, ArtistAlbumLists * aaList, bool cloud) :
+ dev(dev), aaList(aaList), totalSize(0), totalPlayLength(0), tracks(0) {
+ this->config = config;
+ this->cloud = cloud;
+ this->cloudcol = -1;
+
+ this->sortcol = config->ReadInt(L"sortcol", 0);
+ this->sortdir = config->ReadInt(L"sortdir", 0);
+
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if (!fieldsBits) fieldsBits = -1;
+ if (fieldsBits & SUPPORTS_ARTIST) fields.Add(new ListField(0, 200, WASABI_API_LNGSTRINGW(IDS_ARTIST), config));
+ if (fieldsBits & SUPPORTS_TITLE) fields.Add(new ListField(1, 200, WASABI_API_LNGSTRINGW(IDS_TITLE), config));
+ if (fieldsBits & SUPPORTS_ALBUM) fields.Add(new ListField(2, 200, WASABI_API_LNGSTRINGW(IDS_ALBUM), config));
+ if (fieldsBits & SUPPORTS_LENGTH) fields.Add(new ListField(3, 64, WASABI_API_LNGSTRINGW(IDS_LENGTH), config));
+ if (cloud) fields.Add(new ListField(3+cloud, 27, WASABI_API_LNGSTRINGW(IDS_CLOUD), config));
+ if (fieldsBits & SUPPORTS_TRACKNUM) fields.Add(new ListField(4+cloud, 50, WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER), config));
+ if (fieldsBits & SUPPORTS_DISCNUM) fields.Add(new ListField(5+cloud, 38, WASABI_API_LNGSTRINGW(IDS_DISC), config));
+ if (fieldsBits & SUPPORTS_GENRE) fields.Add(new ListField(6+cloud, 100, WASABI_API_LNGSTRINGW(IDS_GENRE), config));
+ if (fieldsBits & SUPPORTS_YEAR) fields.Add(new ListField(7+cloud, 38, WASABI_API_LNGSTRINGW(IDS_YEAR), config));
+ if (fieldsBits & SUPPORTS_BITRATE) fields.Add(new ListField(8+cloud, 45, WASABI_API_LNGSTRINGW(IDS_BITRATE), config));
+ if (fieldsBits & SUPPORTS_SIZE) fields.Add(new ListField(9+cloud, 90, WASABI_API_LNGSTRINGW(IDS_SIZE), config));
+ if (fieldsBits & SUPPORTS_PLAYCOUNT) fields.Add(new ListField(10+cloud, 64, WASABI_API_LNGSTRINGW(IDS_PLAY_COUNT), config));
+ if (fieldsBits & SUPPORTS_RATING) fields.Add(new ListField(11+cloud, 64, WASABI_API_LNGSTRINGW(IDS_RATING), config));
+ if (fieldsBits & SUPPORTS_LASTPLAYED) fields.Add(new ListField(12+cloud, 120, WASABI_API_LNGSTRINGW(IDS_LAST_PLAYED), config));
+ if (fieldsBits & SUPPORTS_ALBUMARTIST) fields.Add(new ListField(13+cloud, 200, WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST), config, true));
+ if (fieldsBits & SUPPORTS_PUBLISHER) fields.Add(new ListField(14+cloud, 200, WASABI_API_LNGSTRINGW(IDS_PUBLISHER), config, true));
+ if (fieldsBits & SUPPORTS_COMPOSER) fields.Add(new ListField(15+cloud, 200, WASABI_API_LNGSTRINGW(IDS_COMPOSER), config, true));
+ if (fieldsBits & SUPPORTS_MIMETYPE) fields.Add(new ListField(16+cloud, 100, WASABI_API_LNGSTRINGW(IDS_MIME_TYPE), config, true));
+ if (fieldsBits & SUPPORTS_DATEADDED) fields.Add(new ListField(17+cloud, 120, WASABI_API_LNGSTRINGW(IDS_DATE_ADDED), config, true));
+ this->SortColumns();
+
+ if (cloud)
+ {
+ // not pretty but it'll allow us to know the current
+ // position of the cloud column for drawing purposes
+ for(int i = 0; i < fields.GetSize(); i++) {
+ if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud"))
+ {
+ this->cloudcol = ((ListField *)fields.Get(i))->pos;
+ break;
+ }
+ }
+ }
+ }
+ virtual ~TracksList() {
+ config->WriteInt(L"sortcol", sortcol);
+ config->WriteInt(L"sortdir", sortdir);
+ }
+ virtual int GetNumColumns() { return fields.GetSize(); }
+ virtual int GetNumRows() { return (tracks ? tracks->GetSize() : 0); }
+ virtual int GetColumnWidth(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->width;
+ return 0;
+ }
+ virtual wchar_t * GetColumnTitle(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->name;
+ return L"";
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) {
+ buf[0]=0;
+ if(row >= tracks->GetSize() || aaList->bgThread_Handle) return;
+ songid_t s = (songid_t)tracks->Get(row);
+ if(col >=0 && col < fields.GetSize()) {
+ if (cloud)
+ {
+ switch(((ListField *)fields.Get(col))->field) {
+ case 0: dev->getTrackArtist(s,buf,buflen); return;
+ case 1: dev->getTrackTitle(s,buf,buflen); return;
+ case 2: dev->getTrackAlbum(s,buf,buflen); return;
+ case 3: { int l=dev->getTrackLength(s); if (l>=0) wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; }
+ case 4: { dev->getTrackExtraInfo(s,L"cloud",buf,buflen); return; }
+ case 5: { int t=dev->getTrackTrackNum(s); if (t>0) wsprintf(buf,L"%d",t); return; }
+ case 6: { int d = dev->getTrackDiscNum(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 7: dev->getTrackGenre(s,buf,buflen); return;
+ case 8: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 9: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; }
+ case 10: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return;
+ case 11: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 12: getStars(dev->getTrackRating(s),buf,buflen); return;
+ case 13: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return;
+ case 14: dev->getTrackAlbumArtist(s,buf,buflen); return;
+ case 15: dev->getTrackPublisher(s,buf,buflen); return;
+ case 16: dev->getTrackComposer(s,buf,buflen); return;
+ case 17: dev->getTrackMimeType(s,buf,buflen); return;
+ case 18: timeToString(dev->getTrackDateAdded(s),buf,buflen); return;
+ }
+ }
+ else
+ {
+ switch(((ListField *)fields.Get(col))->field) {
+ case 0: dev->getTrackArtist(s,buf,buflen); return;
+ case 1: dev->getTrackTitle(s,buf,buflen); return;
+ case 2: dev->getTrackAlbum(s,buf,buflen); return;
+ case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; }
+ case 4: { int t=dev->getTrackTrackNum(s); if (t>0) wsprintf(buf,L"%d",t); return; }
+ case 5: { int d = dev->getTrackDiscNum(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 6: dev->getTrackGenre(s,buf,buflen); return;
+ case 7: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 8: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; }
+ case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return;
+ case 10: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 11: getStars(dev->getTrackRating(s),buf,buflen); return;
+ case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return;
+ case 13: dev->getTrackAlbumArtist(s,buf,buflen); return;
+ case 14: dev->getTrackPublisher(s,buf,buflen); return;
+ case 15: dev->getTrackComposer(s,buf,buflen); return;
+ case 16: dev->getTrackMimeType(s,buf,buflen); return;
+ case 17: timeToString(dev->getTrackDateAdded(s),buf,buflen); return;
+ }
+ }
+ }
+ }
+ virtual void SortList() {
+ useby = ((ListField*)fields.Get(sortcol))->field;
+ usedir = sortdir;
+ // if a cloud item then adjust things as needed
+ // since we're inserting between genre and year
+ usecloud = cloud;
+ thread_killed = 0;
+ currentDev = dev;
+ qsort(tracks->GetAll(),tracks->GetSize(),sizeof(void*),sortFunc);
+ }
+ virtual void ColumnResize(int col, int newWidth) {
+ if(col >=0 && col < fields.GetSize()) {
+ ListField * lf = (ListField *)fields.Get(col);
+ lf->width = newWidth;
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"colWidth_%d",lf->field);
+ config->WriteInt(buf,newWidth);
+ }
+ }
+ virtual int GetSortColumn() { return sortcol; }
+ virtual int GetSortDirection() { return sortdir; }
+ virtual void ColumnClicked(int col) {
+ if(col == sortcol)
+ sortdir = sortdir?0:1;
+ else {
+ sortdir=0;
+ sortcol=col;
+ }
+ SortList();
+ }
+ virtual void GetInfoString(wchar_t * buf) {
+ ::GetInfoString(buf, dev, tracks->GetSize(), totalSize, totalPlayLength, cloud);
+ }
+ virtual songid_t GetTrack(int pos) { return (songid_t)tracks->Get(pos); }
+ virtual void RemoveTrack(songid_t song) {
+ for(int i=0; i<tracks->GetSize(); i++) {
+ if((songid_t)tracks->Get(i) == song) tracks->Del(i--);
+ }
+ }
+};
+
+static void FreeFilterItemList(C_ItemList * list) {
+ if(!list) return;
+ for(int i=0; i < list->GetSize(); i++) {
+ FilterItem * a = (FilterItem*)list->Get(i);
+ if(a->nextFilter) FreeFilterItemList(a->nextFilter); a->nextFilter=0;
+ delete a;
+ }
+ delete list;
+}
+
+static DWORD WINAPI bgThreadSearchProc(void *tmp)
+{
+ ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp;
+ return (aacList ? aacList->bgSearchThreadProc(tmp) : 0);
+}
+
+static DWORD WINAPI bgThreadLoadProc(void *tmp)
+{
+ ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp;
+ return (aacList ? aacList->bgLoadThreadProc(tmp) : 0);
+}
+
+static DWORD WINAPI bgThreadRefineProc(void *tmp)
+{
+ ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp;
+ return (aacList ? aacList->bgRefineThreadProc(tmp) : 0);
+}
+
+extern HWND hwndMediaView;
+DWORD WINAPI ArtistAlbumLists::bgLoadThreadProc(void *tmp)
+{
+ int l = dev->getPlaylistLength(playlistId);
+
+ for(int i=0; i<l; i++) {
+ songid_t x=dev->getPlaylistTrack(playlistId,i);
+ int t = dev->getTrackType(x);
+ if(type != -1 && t != type) continue;
+ searchedTracks->Add((void*)x);
+ trackList->Add((void*)x);
+ unrefinedTracks->Add((void*)x);
+ }
+
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) {
+ if (filters[0]) FreeFilterItemList(filters[0]->items);
+ filters[0]->items = CompilePrimaryList(searchedTracks);
+ }
+ else {
+ delete filters[i]->items;
+ filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ }
+ filters[i]->SortList();
+ }
+
+ SetRefine(L"");
+
+ if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0);
+ return 0;
+}
+
+DWORD WINAPI ArtistAlbumLists::bgSearchThreadProc(void *tmp)
+{
+ int l = dev->getPlaylistLength(playlistId);
+ C_ItemList allTracks;
+
+ for(int i=0; i<l; i++) {
+ songid_t x=dev->getPlaylistTrack(playlistId,i);
+ int t = dev->getTrackType(x);
+ if(type != -1 && t != type) continue;
+ allTracks.Add((void*)x);
+ }
+
+ searchedTracks = FilterSongs(lastSearch, &allTracks);
+ delete unrefinedTracks;
+ unrefinedTracks = new C_ItemList;
+ l=searchedTracks->GetSize();
+ for(int i=0; i<l; i++) unrefinedTracks->Add(searchedTracks->Get(i));
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) {
+ FreeFilterItemList(filters[0]->items);
+ filters[0]->items = CompilePrimaryList(searchedTracks);
+ }
+ else {
+ delete filters[i]->items;
+ filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ }
+ }
+ SetRefine(L"");
+
+ for(int i=0; i<numFilters; i++) filters[i]->SortList();
+
+ if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0);
+ return 0;
+}
+
+DWORD WINAPI ArtistAlbumLists::bgRefineThreadProc(void *tmp)
+{
+ SetRefine(lastRefine);
+
+ if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0);
+ return 0;
+}
+
+void ArtistAlbumLists::bgQuery_Stop() // exported for other people to call since it is useful (eventually
+{
+ KillTimer(hwndMediaView, 123);
+ if (bgThread_Handle)
+ {
+ thread_killed = bgThread_Kill = 1;
+ WaitForSingleObject(bgThread_Handle, INFINITE);
+ CloseHandle(bgThread_Handle);
+ bgThread_Handle = 0;
+ }
+}
+
+void ArtistAlbumLists::bgQuery(int mode) // only internal used
+{
+ bgQuery_Stop();
+
+ // TODO cache the HWND to avoid confusion
+ SetTimer(hwndMediaView, 123, 200, NULL);
+ DWORD id;
+ bgThread_Kill = 0;
+ bgThread_Handle = CreateThread(NULL, 0, (!mode ? bgThreadLoadProc : (mode == 1 ? bgThreadSearchProc : bgThreadRefineProc)), (LPVOID)this, 0, &id);
+}
+
+ArtistAlbumLists::ArtistAlbumLists(Device * dev, int playlistId, C_Config * config, wchar_t ** filterNames, int numFilters0, int type, bool async) {
+ this->type = type;
+ this->dev = dev;
+ this->playlistId = playlistId;
+ this->numFilters = numFilters0;
+ this->bgThread_Handle = 0;
+ this->async = async;
+ this->lastSearch = 0;
+ this->lastRefine = 0;
+ ZeroMemory(&filters, sizeof(filters));
+
+ if (config->ReadInt(L"savefilter", 1))
+ {
+ lastSearch = wcsdup(config->ReadString(L"savedfilter", L""));
+ lastRefine = wcsdup(config->ReadString(L"savedrefinefilter", L""));
+ }
+
+ Filter * f = NULL;
+ for(int i = 0; i < numFilters; i++) {
+ if(!i) { f = firstFilter = getFilter(filterNames[i]); }
+ else { f->nextFilter = getFilter(filterNames[i]); f = f->nextFilter; }
+ }
+
+ f = firstFilter;
+ for(int i=0; i<numFilters; i++) {
+ filters[i] = new FilterList(i,dev,config,f,this,async);
+ f = f->nextFilter;
+ }
+
+ tracksLC = new TracksList(dev,config,this,async);
+
+ searchedTracks = new C_ItemList;
+ trackList = new C_ItemList;
+ unrefinedTracks = new C_ItemList;
+ if (!async && (!lastSearch || lastSearch && !*lastSearch))
+ {
+ int l = dev->getPlaylistLength(playlistId);
+ for(int i=0; i<l; i++) {
+ songid_t x=dev->getPlaylistTrack(playlistId,i);
+ int t = dev->getTrackType(x);
+ if(type != -1 && t != type) continue;
+ searchedTracks->Add((void*)x);
+ trackList->Add((void*)x);
+ unrefinedTracks->Add((void*)x);
+ }
+ }
+
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) filters[i]->items = CompilePrimaryList(searchedTracks);
+ else filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ filters[i]->SortList();
+ }
+
+ tracksLC->tracks = trackList;
+ tracksLC->dev=dev;
+
+ if (async) bgQuery();
+ else SetRefine((lastRefine ? lastRefine : L""));
+}
+
+ArtistAlbumLists::~ArtistAlbumLists() {
+ if(numFilters && filters[0]) FreeFilterItemList(filters[0]->items);
+ for(int i=0; i<numFilters; i++) {
+ delete filters[i]->filter;
+ if(i!=0) delete filters[i]->items;
+ delete filters[i];
+ }
+ delete trackList;
+ delete searchedTracks;
+ delete unrefinedTracks;
+ delete tracksLC;
+
+ if (lastSearch) free(lastSearch);
+ if (lastRefine) free(lastRefine);
+}
+
+static void parsequicksearch(wchar_t *out, wchar_t *in) // parses a list into a list of terms that we are searching for
+{
+ int inquotes=0, neednull=0;
+ while (in && *in)
+ {
+ wchar_t c=*in++;
+ if (c != L' ' && c != L'\t' && c != L'\"')
+ {
+ neednull=1;
+ *out++=c;
+ }
+ else if (c == L'\"')
+ {
+ inquotes=!inquotes;
+ if (!inquotes)
+ {
+ *out++=0;
+ neednull=0;
+ }
+ }
+ else
+ {
+ if (inquotes) *out++=c;
+ else if (neednull)
+ {
+ *out++=0;
+ neednull=0;
+ }
+ }
+ }
+ *out++=0;
+ *out++=0;
+}
+
+static int in_string(wchar_t *string, wchar_t *substring)
+{
+ if (!string) return 0;
+ if (!*substring) return 1;
+ int l=lstrlen(substring);
+ while (string[0]) if (!_wcsnicmp(string++,substring,l)) return 1;
+ return 0;
+}
+
+C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs, Device * dev, bool cloud)
+{
+ wchar_t filterstr[256] = {0}, filteritems[300] = {0};
+ lstrcpyn(filterstr,filter,256);
+ parsequicksearch(filteritems,filterstr);
+
+ C_ItemList * filtered = new C_ItemList;
+ int l = songs->GetSize();
+ for(int i=0; i<l; i++) {
+ songid_t s = (songid_t)songs->Get(i);
+ wchar_t *p=filteritems;
+ if(p && *p) {
+ while(p && *p) {
+ bool in=false;
+ for(int j=0; j<15; j++) {
+ wchar_t buf[2048] = {0};
+ int buflen=2048;
+ if (cloud)
+ {
+ switch(j) {
+ case 0: dev->getTrackArtist(s,buf,buflen); break;
+ case 1: dev->getTrackTitle(s,buf,buflen); break;
+ case 2: dev->getTrackAlbum(s,buf,buflen); break;
+ case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); break; }
+ case 4: { dev->getTrackExtraInfo(s,L"cloud",buf,buflen); break; }
+ case 5: wsprintf(buf,L"%d",dev->getTrackTrackNum(s)); break;
+ case 6: wsprintf(buf,L"%d",dev->getTrackDiscNum(s)); break;
+ case 7: dev->getTrackGenre(s,buf,buflen); break;
+ case 8: wsprintf(buf,L"%d",dev->getTrackYear(s)); break;
+ case 9: wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),dev->getTrackBitrate(s)); break;
+ case 10: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); break;
+ case 11: wsprintf(buf,L"%d",dev->getTrackPlayCount(s)); break;
+ case 12: getStars(dev->getTrackRating(s),buf,buflen); break;
+ case 13: timeToString(dev->getTrackLastPlayed(s),buf,buflen); break;
+ case 14: dev->getTrackAlbumArtist(s,buf,buflen); break;
+ case 15: dev->getTrackPublisher(s,buf,buflen); break;
+ case 16: dev->getTrackComposer(s,buf,buflen); break;
+ case 17: dev->getTrackMimeType(s,buf,buflen); break;
+ case 18: timeToString(dev->getTrackDateAdded(s),buf,buflen); break;
+ default: lstrcpyn(buf,L"",buflen); break;
+ }
+ }
+ else
+ {
+ switch(j) {
+ case 0: dev->getTrackArtist(s,buf,buflen); break;
+ case 1: dev->getTrackTitle(s,buf,buflen); break;
+ case 2: dev->getTrackAlbum(s,buf,buflen); break;
+ case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); break; }
+ case 4: wsprintf(buf,L"%d",dev->getTrackTrackNum(s)); break;
+ case 5: wsprintf(buf,L"%d",dev->getTrackDiscNum(s)); break;
+ case 6: dev->getTrackGenre(s,buf,buflen); break;
+ case 7: wsprintf(buf,L"%d",dev->getTrackYear(s)); break;
+ case 8: wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),dev->getTrackBitrate(s)); break;
+ case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); break;
+ case 10: wsprintf(buf,L"%d",dev->getTrackPlayCount(s)); break;
+ case 11: getStars(dev->getTrackRating(s),buf,buflen); break;
+ case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); break;
+ case 13: dev->getTrackAlbumArtist(s,buf,buflen); break;
+ case 14: dev->getTrackPublisher(s,buf,buflen); break;
+ case 15: dev->getTrackComposer(s,buf,buflen); break;
+ case 16: dev->getTrackMimeType(s,buf,buflen); break;
+ case 17: timeToString(dev->getTrackDateAdded(s),buf,buflen); break;
+ default: lstrcpyn(buf,L"",buflen); break;
+ }
+ }
+ if(in_string(buf,p)) { in=true; break;}
+ }
+ if(in) p+=lstrlen(p)+1;
+ else break;
+ }
+ }
+ if(p && *p) continue;
+ filtered->Add((void*)s);
+ }
+ return filtered;
+}
+
+C_ItemList * ArtistAlbumLists::FilterSongs(const wchar_t * filter, const C_ItemList * songs)
+{
+ return ::FilterSongs(filter,songs,dev,this->async);
+}
+
+static Filter * firstFil;
+
+static int sortFunc_ssi(const void *elem1, const void *elem2)
+{
+ SortSongItem * a = (SortSongItem *)elem1;
+ SortSongItem * b = (SortSongItem *)elem2;
+ Filter * f = firstFil;
+ while(f) {
+ int r = f->sortFunc(a->dev,a->songid,b->songid);
+ if(r) return r;
+ f = f->nextFilter;
+ }
+ return 0;
+}
+
+C_ItemList * ArtistAlbumLists::CompilePrimaryList(const C_ItemList * songs) {
+ C_ItemList * list = new C_ItemList;
+
+ SortSongItem *songList = (SortSongItem*)calloc(songs->GetSize(), sizeof(SortSongItem));
+ int l=songs->GetSize();
+ for(int i=0; i<l; i++) { songList[i].songid = (songid_t)songs->Get(i); songList[i].dev = dev; }
+
+ firstFil = firstFilter;
+ qsort(songList,l,sizeof(SortSongItem),sortFunc_ssi); //sort it
+
+ FilterItem * items[MAX_FILTERS]={0};
+ Filter * filters[MAX_FILTERS]={0};
+
+ Filter * f = firstFilter;
+ int numFilters=0;
+ while(f) {filters[numFilters++] = f; f=f->nextFilter;}
+
+ for(int i=0; i<l; i++) {
+ songid_t s = songList[i].songid;
+ for(int j=0; j<numFilters; j++) {
+ if(items[j] && filters[j]->isInGroup(dev,s,items[j])) filters[j]->addToGroup(dev,s,items[j]);
+ else {
+ for(int k=j; k<numFilters; k++) {
+ items[k] = filters[k]->newGroup(dev,s);
+ items[k]->nextFilter = new C_ItemList;
+ if(k==0) list->Add(items[k]);
+ else {
+ items[k-1]->nextFilter->Add(items[k]);
+ items[k-1]->numNextFilter++;
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (songList)
+ {
+ free(songList);
+ songList = 0;
+ }
+
+ if(list->GetSize() && ((FilterItem*)list->Get(0))->isWithoutGroup()) {
+ wsprintf(this->filters[0]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X_WITHOUT_X),
+ list->GetSize()-1,(list->GetSize()==2)?this->filters[0]->filter->name:this->filters[0]->filter->namePlural,((FilterItem*)list->Get(0))->numTracks,this->filters[0]->filter->name);
+ }
+ else
+ wsprintf(this->filters[0]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X),
+ list->GetSize(),(list->GetSize()==1)?this->filters[0]->filter->name:this->filters[0]->filter->namePlural);
+ CharLower(this->filters[0]->topString+3);
+ this->filters[0]->tracks = songs->GetSize();
+ return list;
+}
+
+static int sortFunc_filters(const void *elem1, const void *elem2) {
+ FilterItem *a=(FilterItem *)*(void **)elem1;
+ FilterItem *b=(FilterItem *)*(void **)elem2;
+ return a->compareTo(b);
+}
+
+C_ItemList * ArtistAlbumLists::CompileSecondaryList(const C_ItemList * selectedItems, int level, bool updateTopArtist) {
+ int totalTracks=0;
+ C_ItemList * list = new C_ItemList;
+ C_ItemList * collatedlist = new C_ItemList;
+
+ for(int i=0; i < selectedItems->GetSize(); i++) {
+ FilterItem * item = (FilterItem*)selectedItems->Get(i);
+ if (item) {
+ C_ItemList *nf = item->independentNextFilter?item->independentNextFilter:item->nextFilter;
+ for(int j=0; j < nf->GetSize(); j++) list->Add(nf->Get(j));
+ }
+ }
+ qsort(list->GetAll(),list->GetSize(),sizeof(void*),sortFunc_filters);
+
+ FilterItem * curItem=0;
+ for(int i=0; i < list->GetSize(); i++) {
+ FilterItem * item = (FilterItem*)list->Get(i);
+ if(curItem && !curItem->compareTo(item)) {
+ curItem->independentTracks += item->numTracks;
+ curItem->independentSize += item->size;
+ curItem->independentLength += item->length;
+ curItem->independentCloudState += item->cloudState;
+ } else {
+ curItem = item;
+ collatedlist->Add(item);
+ item->independentTracks = item->numTracks;
+ curItem->independentSize = item->size;
+ curItem->independentLength = item->length;
+ curItem->independentCloudState = item->cloudState;
+ if(item->independentNextFilter) delete item->independentNextFilter;
+ item->independentNextFilter = new C_ItemList;
+ }
+ totalTracks+=item->numTracks;
+ for(int k=0; k<item->nextFilter->GetSize(); k++) curItem->independentNextFilter->Add(item->nextFilter->Get(k));
+ }
+ delete list;
+ if(updateTopArtist) this->filters[level-1]->nextFilterNum = collatedlist->GetSize();
+
+ if(collatedlist->GetSize() && ((FilterItem*)collatedlist->Get(0))->isWithoutGroup()) {
+ wsprintf(this->filters[level]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X_WITHOUT_X),
+ collatedlist->GetSize(),(collatedlist->GetSize()==1)?this->filters[level]->filter->name:this->filters[level]->filter->namePlural,((FilterItem*)collatedlist->Get(0))->numTracks,this->filters[level]->filter->name);
+ }
+ else {
+ wsprintf(this->filters[level]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X),
+ collatedlist->GetSize(),(collatedlist->GetSize()==1)?this->filters[level]->filter->name:this->filters[level]->filter->namePlural);
+ }
+ CharLower(this->filters[level]->topString+3);
+ this->filters[level]->tracks=totalTracks;
+ return collatedlist;
+}
+
+// removes song from all relevant lists
+void ArtistAlbumLists::RemoveTrack(songid_t song)
+{
+ if (searchedTracks)
+ {
+ for (int i=0;i<searchedTracks->GetSize();i++)
+ {
+ if (searchedTracks->Get(i) == (void *) song)
+ {
+ searchedTracks->Del(i--);
+ }
+ }
+ }
+}
+
+void ArtistAlbumLists::SelectionChanged(int filterNum, SkinnedListView **listview) {
+ for(int i=filterNum; i<numFilters-1; i++) {
+ int l = listview[i]->listview.GetCount();
+ bool all = (i != filterNum || (ListView_GetSelectedCount(listview[i]->listview.getwnd())==0));
+ if(listview[i]->listview.GetSelected(0) && filters[i]->filter->HaveTopItem()) all=true;
+ C_ItemList selectedItems;
+ int j=0,f=0;
+ if(filters[i]->filter->HaveTopItem()) { j=1; f=1; }
+ for(; j<l; j++) {
+ if(all || listview[i]->listview.GetSelected(j))
+ selectedItems.Add(filters[i]->items->Get(j-f));
+ }
+ delete filters[i+1]->items;
+ filters[i+1]->items = CompileSecondaryList(&selectedItems,i+1,false);
+ filters[i+1]->SortList();
+ listview[i+1]->UpdateList();
+ }
+
+ C_ItemList * tracks = new C_ItemList;
+
+ C_ItemList * selectedItems[MAX_FILTERS]={0};
+
+ for(int i=0; i<=filterNum; i++) {
+ bool all = (ListView_GetSelectedCount(listview[i]->listview.getwnd())==0);
+ if(listview[i]->listview.GetSelected(0) && filters[i]->filter->HaveTopItem()) all=true;
+ if(all) selectedItems[i] = NULL;
+ else {
+ selectedItems[i] = new C_ItemList;
+ int m = filters[i]->items->GetSize();
+ int offset = filters[i]->filter->HaveTopItem()?1:0;
+ for(int k=0; k<m; k++) if(listview[i]->listview.GetSelected(k+offset)) selectedItems[i]->Add(filters[i]->items->Get(k));
+ }
+ }
+
+ int l=searchedTracks->GetSize();
+ for(int j=0; j<l; j++) {
+ songid_t track = (songid_t)searchedTracks->Get(j);
+ bool matches=true;
+ for(int i=0; i<=filterNum; i++) {
+ matches=false;
+ if(selectedItems[i]) {
+ for(int k=0; k<selectedItems[i]->GetSize(); k++)
+ if(filters[i]->filter->isInGroup(dev,track,(FilterItem*)selectedItems[i]->Get(k))) { matches=true; break; }
+ }
+ else matches=true;
+ if(!matches) break;
+ }
+ if(matches) //woo hoo, its in!
+ tracks->Add((void*)track);
+ }
+ delete unrefinedTracks;
+ unrefinedTracks = tracks;
+ SetRefine(L"");
+ for(int i=0; i<=filterNum; i++)
+ {
+ delete selectedItems[i];
+ }
+}
+
+void ArtistAlbumLists::SetRefine(const wchar_t * str, bool async) {
+ if (!async)
+ {
+ C_ItemList * refinedTracks = FilterSongs(str,unrefinedTracks);
+ C_ItemList * oldTrackList = trackList;
+ trackList = refinedTracks;
+ tracksLC->tracks = trackList;
+ delete oldTrackList;
+ tracksLC->SortList();
+
+ // get stats
+ __int64 fileSize=0;
+ int playLength=0;
+ int millis=0;
+ for(int i=0; i < trackList->GetSize(); i++) {
+ songid_t s = (songid_t)trackList->Get(i);
+ fileSize += (__int64)dev->getTrackSize(s);
+ playLength += dev->getTrackLength(s)/1000;
+ millis += dev->getTrackLength(s)%1000;
+ }
+ playLength += millis/1000;
+ tracksLC->totalPlayLength=playLength;
+ tracksLC->totalSize=fileSize;
+ }
+ else
+ {
+ if (lastRefine)
+ {
+ free(lastRefine);
+ lastRefine = 0;
+ }
+ lastRefine = wcsdup(str);
+
+ bgQuery(2);
+ }
+}
+
+void ArtistAlbumLists::SetSearch(const wchar_t * str, bool async) {
+ bgQuery_Stop();
+
+ if (!async)
+ {
+ delete searchedTracks; searchedTracks = NULL;
+ C_ItemList allTracks;
+
+ int l = dev->getPlaylistLength(playlistId);
+ for(int i=0; i<l; i++) {
+ songid_t x = dev->getPlaylistTrack(playlistId,i);
+ if(type != -1 && dev->getTrackType(x) != type) continue;
+ allTracks.Add((void*)x);
+ }
+
+ searchedTracks = FilterSongs(str,&allTracks);
+ delete unrefinedTracks;
+ unrefinedTracks = new C_ItemList;
+ l=searchedTracks->GetSize();
+ for(int i=0; i<l; i++) unrefinedTracks->Add(searchedTracks->Get(i));
+
+ for(int i=0; i<numFilters; i++) {
+ if(i==0) {
+ FreeFilterItemList(filters[0]->items);
+ filters[0]->items = CompilePrimaryList(searchedTracks);
+ }
+ else {
+ delete filters[i]->items;
+ filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true);
+ }
+ }
+
+ SetRefine(L"");
+
+ for(int i=0; i<numFilters; i++) filters[i]->SortList();
+ }
+ else
+ {
+ if (lastSearch)
+ {
+ free(lastSearch);
+ lastSearch = 0;
+ }
+
+ if (!(str && *str))
+ {
+ if (unrefinedTracks) delete unrefinedTracks;
+ if (searchedTracks) delete searchedTracks;
+ if (trackList) delete trackList;
+ searchedTracks = new C_ItemList;
+ trackList = new C_ItemList;
+ unrefinedTracks = new C_ItemList;
+ }
+ else
+ {
+ lastSearch = wcsdup(str);
+ }
+ bgQuery((str && *str));
+ }
+}
+
+ListContents * ArtistAlbumLists::GetFilterList(int i) { return filters[i]; }
+PrimaryListContents * ArtistAlbumLists::GetTracksList() { return this->tracksLC; } \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h
new file mode 100644
index 00000000..be868d0c
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.h
@@ -0,0 +1,64 @@
+#ifndef _ARTISTALBUMLISTS_H_
+#define _ARTISTALBUMLISTS_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <time.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "pmp.h"
+#include "SkinnedListView.h"
+#include "config.h"
+
+class Filter;
+class FilterList;
+class TracksList;
+
+#define MAX_FILTERS 3
+extern int thread_killed;
+
+class ArtistAlbumLists {
+protected:
+ Device * dev;
+ int playlistId;
+ C_ItemList * searchedTracks;
+ C_ItemList * unrefinedTracks;
+ C_ItemList * trackList;
+ C_ItemList * CompilePrimaryList(const C_ItemList * songs);
+ C_ItemList * CompileSecondaryList(const C_ItemList * selectedArtists, int level, bool updateTopArtist);
+ C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs);
+ int numFilters;
+ FilterList *filters[MAX_FILTERS];
+ Filter * firstFilter;
+ TracksList *tracksLC;
+ int type;
+ wchar_t * lastSearch;
+ wchar_t * lastRefine;
+public:
+ ArtistAlbumLists(Device * dev, int playlistId, C_Config * config, wchar_t ** filterNames, int numFilters, int type=-1, bool async=false);
+ ~ArtistAlbumLists();
+ void SetRefine(const wchar_t * str, bool async=false);
+ void SetSearch(const wchar_t * str, bool async=false);
+ void SelectionChanged(int filterNum, SkinnedListView **listview);
+ ListContents * GetFilterList(int i);
+ PrimaryListContents * GetTracksList();
+ void RemoveTrack(songid_t song); // removes song from all relevant lists
+
+ // used for threaded background scans (mainly aimed for cloud support but could be used for other devices if needed)
+ DWORD WINAPI bgLoadThreadProc(void *tmp);
+ DWORD WINAPI bgSearchThreadProc(void *tmp);
+ DWORD WINAPI bgRefineThreadProc(void *tmp);
+ void bgQuery_Stop();
+ int bgThread_Kill;
+ HANDLE bgThread_Handle;
+ bool async;
+ void bgQuery(int mode=0);
+};
+
+#endif //_ARTISTALBUMLISTS_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/DeviceCommands.cpp b/Src/Plugins/Library/ml_pmp/DeviceCommands.cpp
new file mode 100644
index 00000000..5c34a84a
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceCommands.cpp
@@ -0,0 +1,192 @@
+#include "main.h"
+#include "DeviceCommands.h"
+
+#include <strsafe.h>
+PortableCommand::PortableCommand(const char *name, int title, int description)
+ : name(name), title(title), description(description)
+{
+}
+
+const char *PortableCommand::GetName()
+{
+ return name;
+}
+
+HRESULT PortableCommand::GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT PortableCommand::GetDisplayName(wchar_t *buffer, size_t bufferSize)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(title, buffer, bufferSize);
+ return S_OK;
+}
+
+HRESULT PortableCommand::GetDescription(wchar_t *buffer, size_t bufferSize)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(description, buffer, bufferSize);
+ return S_OK;
+}
+
+#define CBCLASS PortableCommand
+START_DISPATCH;
+CB(API_GETNAME, GetName);
+CB(API_GETICON, GetIcon);
+CB(API_GETDISPLAYNAME, GetDisplayName);
+CB(API_GETDESCRIPTION, GetDescription);
+END_DISPATCH;
+#undef CBCLASS
+
+
+BOOL SetDeviceCommandInfo(DeviceCommandInfo *info, const char *name, DeviceCommandFlags flags)
+{
+ if (NULL == info)
+ return FALSE;
+
+ info->name = name;
+ info->flags = flags;
+ return TRUE;
+}
+
+/* -------------- */
+DeviceCommand::DeviceCommand(const char *name, DeviceCommandFlags flags)
+: name(name), flags(flags)
+{
+}
+
+DeviceCommand::DeviceCommand(const DeviceCommandInfo *commandInfo)
+: name(commandInfo->name), flags(commandInfo->flags)
+{
+}
+
+const char *DeviceCommand::GetName()
+{
+ return name;
+}
+
+HRESULT DeviceCommand::GetFlags(DeviceCommandFlags *flags)
+{
+ *flags = this->flags;
+ return 0;
+}
+
+
+#define CBCLASS DeviceCommand
+START_DISPATCH;
+REFERENCE_COUNTED;
+CB(API_GETNAME, GetName);
+CB(API_GETFLAGS, GetFlags);
+END_DISPATCH;
+#undef CBCLASS
+
+
+
+DeviceCommandEnumerator::DeviceCommandEnumerator(const DeviceCommandInfo *commandInfoList, size_t listSize)
+ : position(0), commands(NULL), count(0)
+{
+ if (NULL != commandInfoList &&
+ 0 != listSize)
+ {
+ commands = (DeviceCommand**)calloc(listSize, sizeof(DeviceCommand*));
+ if (NULL != commands)
+ {
+ for(count = 0; count < listSize; count++)
+ {
+ commands[count] = new DeviceCommand(&commandInfoList[count]);
+ }
+ }
+ }
+}
+
+DeviceCommandEnumerator::~DeviceCommandEnumerator()
+{
+ if (NULL != commands)
+ {
+ while(count--)
+ commands[count]->Release();
+
+ free(commands);
+ }
+
+}
+
+HRESULT DeviceCommandEnumerator::Next(ifc_devicesupportedcommand **buffer, size_t bufferMax, size_t *fetched)
+{
+ size_t available, copied, index;
+ DeviceCommand **source;
+
+ if (NULL == buffer)
+ return E_POINTER;
+
+ if (0 == bufferMax)
+ return E_INVALIDARG;
+
+ if (position >= count)
+ {
+ if (NULL != fetched)
+ *fetched = 0;
+
+ return S_FALSE;
+ }
+
+ available = count - position;
+ copied = ((available > bufferMax) ? bufferMax : available);
+
+ source = commands + position;
+ CopyMemory(buffer, source, copied * sizeof(ifc_devicesupportedcommand*));
+
+ for(index = 0; index < copied; index++)
+ buffer[index]->AddRef();
+
+ position += copied;
+
+ if (NULL != fetched)
+ *fetched = copied;
+
+ return (bufferMax == copied) ? S_OK : S_FALSE;
+}
+
+HRESULT DeviceCommandEnumerator::Reset(void)
+{
+ position=0;
+ return S_OK;
+}
+
+HRESULT DeviceCommandEnumerator::Skip(size_t count)
+{
+ position += count;
+ if (position > this->count)
+ position = this->count;
+ return (position < this->count) ? S_OK : S_FALSE;
+}
+
+HRESULT DeviceCommandEnumerator::GetCount(size_t *count)
+{
+ if (NULL == count)
+ return E_POINTER;
+
+ *count = this->count;
+
+ return S_OK;
+}
+
+
+#define CBCLASS DeviceCommandEnumerator
+START_DISPATCH;
+CB(API_NEXT, Next);
+CB(API_RESET, Reset);
+CB(API_SKIP, Skip);
+CB(API_GETCOUNT, GetCount);
+REFERENCE_COUNTED;
+END_DISPATCH;
+#undef CBCLASS
+
+
+
diff --git a/Src/Plugins/Library/ml_pmp/DeviceCommands.h b/Src/Plugins/Library/ml_pmp/DeviceCommands.h
new file mode 100644
index 00000000..3aa237d2
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceCommands.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "../nu/refcount.h"
+#include "../devices/ifc_devicecommand.h"
+#include "../devices/ifc_devicesupportedcommand.h"
+#include "../devices/ifc_devicesupportedcommandenum.h"
+class PortableCommand : public ifc_devicecommand
+{
+public:
+ PortableCommand(const char *name, int title, int description);
+ const char *name;
+ int title;
+ int description;
+
+ const char *GetName();
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height);
+
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize);
+
+ HRESULT GetDescription(wchar_t *buffer, size_t bufferSize);
+RECVS_DISPATCH;
+};
+
+typedef struct DeviceCommandInfo
+{
+ const char *name;
+ DeviceCommandFlags flags;
+} DeviceCommandInfo;
+
+BOOL SetDeviceCommandInfo(DeviceCommandInfo *info, const char *name, DeviceCommandFlags flags);
+
+class DeviceCommand : public Countable<ifc_devicesupportedcommand>
+{
+public:
+ DeviceCommand(const char *name, DeviceCommandFlags flags);
+ DeviceCommand(const DeviceCommandInfo *commandInfo);
+
+public:
+ const char *GetName();
+ HRESULT GetFlags(DeviceCommandFlags *flags);
+ REFERENCE_COUNT_IMPLEMENTATION;
+
+public:
+ const char *name;
+ DeviceCommandFlags flags;
+RECVS_DISPATCH;
+};
+
+class DeviceCommandEnumerator : public Countable<ifc_devicesupportedcommandenum>
+{
+public:
+ DeviceCommandEnumerator(const DeviceCommandInfo *commandInfoList, size_t listSize);
+ ~DeviceCommandEnumerator();
+
+ HRESULT Next(ifc_devicesupportedcommand **buffer, size_t bufferMax, size_t *count);
+ HRESULT Reset(void);
+ HRESULT Skip(size_t count);
+ HRESULT GetCount(size_t *count);
+ REFERENCE_COUNT_IMPLEMENTATION;
+
+private:
+ size_t position;
+ DeviceCommand **commands;
+ size_t count;
+ RECVS_DISPATCH;
+};
diff --git a/Src/Plugins/Library/ml_pmp/DeviceView.cpp b/Src/Plugins/Library/ml_pmp/DeviceView.cpp
new file mode 100644
index 00000000..6f2b8644
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceView.cpp
@@ -0,0 +1,2714 @@
+//#define _WIN32_WINNT 0x0400
+#include "../Winamp/buildType.h"
+#include "main.h"
+#include "DeviceView.h"
+
+//#include <commctrl.h>
+#include "nu/AutoWide.h"
+#include "nu/AutoChar.h"
+#include "../nu/AutoUrl.h"
+#include "SkinnedListView.h"
+#include "../playlist/api_playlistmanager.h"
+#include "../playlist/ifc_playlistdirectorycallback.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+#include "api__ml_pmp.h"
+#include <shlwapi.h>
+#include <time.h>
+#include "metadata_utils.h"
+#include "../ml_wire/ifc_podcast.h"
+#include "./local_menu.h"
+#include "IconStore.h"
+#include "../devices/ifc_deviceevent.h"
+#include "metadata_utils.h"
+#include "../nu/sort.h"
+#include "resource1.h"
+#include <strsafe.h>
+#include "../nu/MediaLibraryInterface.h"
+
+extern C_ItemList devices;
+extern C_Config * global_config;
+
+extern INT_PTR CALLBACK pmp_artistalbum_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern INT_PTR CALLBACK pmp_playlist_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int IPC_GET_CLOUD_HINST = -1, IPC_LIBRARY_PLAYLISTS_REFRESH = -1;
+HINSTANCE cloud_hinst = 0;
+int currentViewedPlaylist=0;
+HNAVITEM cloudQueueTreeItem=NULL;
+LinkedQueue cloudTransferQueue, cloudFinishedTransfers;
+int cloudTransferProgress = 0;
+DeviceView * currentViewedDevice=NULL;
+
+volatile size_t TransferContext::paused_all = 0;
+
+extern void UpdateTransfersListView(bool softUpdate, CopyInst * item=NULL);
+extern void UpdateDevicesListView(bool softUpdate);
+extern INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern HWND mainMessageWindow;
+extern prefsDlgRecW prefsPage;
+extern int prefsPageLoaded;
+static int thread_id;
+
+static bool copySettings(wchar_t * ssrc, wchar_t * sdest);
+static __int64 fileSize(wchar_t * filename);
+static void removebadchars(wchar_t *s);
+
+extern ThreadID *transfer_thread;
+int TransferThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id);
+
+INT_PTR CALLBACK pmp_queue_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK pmp_video_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+DeviceView::DeviceView(Device *dev)
+ : activityRunning(FALSE), navigationItemCreated(FALSE), usedSpace(0), totalSpace(0)
+{
+ memset(name, 0, sizeof(name));
+ queueActiveIcon = isCloudDevice = 0;
+ treeItem = videoTreeItem = queueTreeItem = 0;
+ connection_type = "USB";
+ display_type = "Portable Media Player";
+ metadata_fields = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!metadata_fields)
+ metadata_fields = -1;
+ dev->extraActions(DEVICE_GET_CONNECTION_TYPE, (intptr_t)&connection_type, 0, 0);
+ isCloudDevice = (!lstrcmpiA(connection_type, "cloud"));
+ dev->extraActions(DEVICE_GET_DISPLAY_TYPE, (intptr_t)&display_type, 0, 0);
+ ref_count = 1;
+ if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0) == 0)
+ {
+ // fallback
+ GUID name_guid;
+ CoCreateGuid(&name_guid);
+ StringCbPrintfA(name, sizeof(name), "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ (int)name_guid.Data1, (int)name_guid.Data2, (int)name_guid.Data3,
+ (int)name_guid.Data4[0], (int)name_guid.Data4[1],
+ (int)name_guid.Data4[2], (int)name_guid.Data4[3],
+ (int)name_guid.Data4[4], (int)name_guid.Data4[5],
+ (int)name_guid.Data4[6], (int)name_guid.Data4[7] );
+ }
+
+ wchar_t inifile[MAX_PATH] = {0};
+ dev->extraActions(DEVICE_GET_INI_FILE,(intptr_t)inifile,0,0);
+ if(!inifile[0])
+ {
+ wchar_t name[256] = {0};
+ dev->getPlaylistName(0,name,256);
+ removebadchars(name);
+ // build this slow so we make sure each directory exists
+ PathCombine(inifile, WASABI_API_APP->path_getUserSettingsPath(), L"Plugins");
+ CreateDirectory(inifile, NULL);
+ PathAppend(inifile, L"ml");
+ CreateDirectory(inifile, NULL);
+ wchar_t ini_filespec[MAX_PATH] = {0};
+ StringCchPrintf(ini_filespec, MAX_PATH, L"ml_pmp_device_%s.ini",name);
+ PathAppend(inifile, ini_filespec);
+ }
+
+ if(fileSize(inifile) <= 0) copySettings(global_config->GetIniFile(),inifile); // import old settings
+ config = new C_Config(inifile,L"ml_pmp",global_config);
+
+ currentTransferProgress = 0;
+ transferRate=0;
+ commitNeeded=false;
+ this->dev = dev;
+ wchar_t deviceName[256]=L"";
+ dev->getPlaylistName(0,deviceName,sizeof(deviceName)/sizeof(wchar_t));
+
+ if (!isCloudDevice) videoView = config->ReadInt(L"showVideoView",dev->extraActions(DEVICE_SUPPORTS_VIDEO,0,0,0));
+ else videoView = 0;
+
+ prefsDlgRecW *parentPrefs = (prefsDlgRecW *)dev->extraActions(DEVICE_GET_PREFS_PARENT, 0, 0, 0);
+ if (!parentPrefs)
+ {
+ // only add it when we're using our own root page, otherwise skip this
+ if (!prefsPageLoaded)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ prefsPageLoaded+=1;
+ }
+
+ if (lstrcmpi(deviceName, L"all_sources"))
+ {
+ devPrefsPage.hInst=WASABI_API_LNG_HINST;
+ devPrefsPage.where=(parentPrefs ? (intptr_t)parentPrefs : (intptr_t)&prefsPage);
+ devPrefsPage.dlgID=IDD_CONFIG;
+ devPrefsPage.name=_wcsdup(deviceName);
+ devPrefsPage.proc=config_dlgproc;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&devPrefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ else
+ {
+ memset(&devPrefsPage, 0, sizeof(prefsDlgRecW));
+ }
+
+ UpdateSpaceInfo(TRUE, FALSE);
+
+ threadKillswitch = 0;
+ transferContext.transfer_thread = WASABI_API_THREADPOOL->ReserveThread(api_threadpool::FLAG_LONG_EXECUTION);
+ transferContext.dev = this;
+ WASABI_API_THREADPOOL->AddHandle(transferContext.transfer_thread, transferContext.notifier, TransferThreadPoolFunc, &transferContext, thread_id, api_threadpool::FLAG_LONG_EXECUTION);
+ thread_id++;
+
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ ifc_device *registered_device = this;
+ AGAVE_API_DEVICEMANAGER->DeviceRegister(&registered_device, 1);
+ }
+ //hTransferThread = CreateThread(NULL, 0, ThreadFunc_Transfer, (LPVOID)this, 0, &dwThreadId);
+ /*
+ if(dev->getDeviceCapacityTotal() > L3000000000) SyncConnectionDefault=1;
+ else SyncConnectionDefault=2;
+ */
+ SyncConnectionDefault=0; // default off for now.
+ if (!isCloudDevice)
+ {
+ // ok all started. Now do any "on connect" actions...
+ time_t lastSync = (time_t)config->ReadInt(L"syncOnConnect_time",0);
+ time_t now = time(NULL);
+ //int diff = now - lastSync;
+ double diff = difftime(now,lastSync);
+ config->WriteInt(L"syncOnConnect_time",(int)now);
+ if(diff > config->ReadInt(L"syncOnConnect_hours",12)*3600)
+ {
+ switch(config->ReadInt(L"syncOnConnect",SyncConnectionDefault))
+ {
+ case 1:
+ {
+ if (!isCloudDevice) Sync(true);
+ //else CloudSync(true);
+ }
+ break;
+ case 2: Autofill(); break;
+ }
+ }
+ }
+ if (!AGAVE_API_DEVICEMANAGER)
+ RegisterViews(0);
+}
+HNAVITEM GetNavigationRoot(BOOL forceCreate);
+HNAVITEM NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0);
+
+void DeviceView::RegisterViews(HNAVITEM parent)
+{
+ NAVINSERTSTRUCT nis = {0};
+ NAVITEM *item = 0;
+ wchar_t buffer[128] = {0};
+
+ item = &nis.item;
+
+ if(!parent)
+ {
+ MLTREEIMAGE devIcon;
+ wchar_t deviceName[256]=L"";
+
+ devIcon.resourceId = IDR_DEVICE_ICON;
+ devIcon.hinst = plugin.hDllInstance;
+ dev->extraActions(DEVICE_SET_ICON,(intptr_t)&devIcon,0,0);
+
+ dev->getPlaylistName(0,deviceName,sizeof(deviceName)/sizeof(wchar_t));
+
+ nis.hParent = GetNavigationRoot(TRUE);
+ nis.hInsertAfter = NCI_LAST;
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_IMAGE | NIMF_IMAGESEL;
+
+ item->pszText = deviceName;
+ item->pszInvariant = nis.item.pszText;
+ item->style = NIS_HASCHILDREN;
+ item->styleMask = item->style,
+ item->iImage = icon_store.GetResourceIcon(devIcon.hinst, MAKEINTRESOURCE(devIcon.resourceId));
+ item->iSelectedImage = item->iImage;
+
+ treeItem = parent = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+
+ navigationItemCreated = TRUE;
+ }
+ else
+ {
+ treeItem = parent;
+ navigationItemCreated = FALSE;
+
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_STYLE;
+ item->hItem = treeItem;
+ item->style = NIS_HASCHILDREN;
+ item->styleMask = NIS_HASCHILDREN;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, item);
+
+ /* create transfer view */
+ // TODO: create this view dynamically
+ HNAVITEM cloud = 0;
+ if (isCloudDevice)
+ {
+ cloud = NavigationItem_Find(0, L"cloud_sources", TRUE);
+ if (cloud != NULL) parent = cloud;
+ }
+
+#if 0
+ int mode = gen_mlconfig->ReadInt(L"txviewpos", 0);
+ if (mode == 1)
+ {
+ nis.hParent = cloud;//parent;
+ nis.hInsertAfter = NCI_FIRST;
+ }
+ else if (mode == 2)
+ {
+ nis.hParent = NavigationItem_Find(0, L"ml_devices_root", TRUE);
+ nis.hInsertAfter = NCI_FIRST;
+ }
+#else
+ nis.hParent = parent;
+#endif
+
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ item->pszText = WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERS, buffer, 128);
+ item->pszInvariant = (isCloudDevice ? L"cloud_transfers" : L"transfers");
+ item->iImage = icon_store.GetQueueIcon();
+ item->iSelectedImage = nis.item.iImage;
+
+#if 0
+ if (!cloudQueueTreeItem && isCloudDevice)
+ {
+ NAVINSERTSTRUCT nis2 = {0};
+ nis2.item.cbSize = sizeof(NAVITEM);
+ nis2.item.pszText = WASABI_API_LNGSTRINGW(IDS_ADD_SOURCE);
+ nis2.item.pszInvariant = L"cloud_add_sources";
+ nis2.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
+ nis2.item.iImage = nis2.item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD_ADD_SOURCE);
+ nis2.hParent = parent;
+ nis2.hInsertAfter = NCI_FIRST;
+ HNAVITEM item = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis2);
+ if (mode == 1) nis.hInsertAfter = item;
+ queueTreeItem = cloudQueueTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+
+#if 0
+ nis2.item.pszText = L"BYOS";//WASABI_API_LNGSTRINGW(IDS_ADD_SOURCE);
+ nis2.item.pszInvariant = L"cloud_byos";
+ nis2.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
+ nis2.item.iImage = nis2.item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD_ADD_BYOS);
+ nis2.hParent = parent;
+ nis2.hInsertAfter = nis.hInsertAfter;
+ MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis2);
+#endif
+ }
+ else
+ queueTreeItem = cloudQueueTreeItem;
+#endif
+ queueTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+
+ /* create video view */
+ if (videoView)
+ {
+ nis.hParent = parent;
+ nis.hInsertAfter = NCI_LAST;
+ item->cbSize = sizeof(NAVITEM);
+ item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ item->pszText = WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO, buffer, 128);
+ item->pszInvariant = L"video";
+ item->iImage = icon_store.GetVideoIcon();
+ item->iSelectedImage = nis.item.iImage;
+
+ videoTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+ else
+ videoTreeItem = 0;
+
+ /* create playlists */
+ int l = (dev ? dev->getPlaylistCount() : 0);
+ for(int i=1; i<l; i++)
+ {
+ AddPlaylistNode(i);
+ }
+}
+
+DeviceView::~DeviceView()
+{
+ if(configDevice == this) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_OPENPREFSTOPAGE);
+
+ // remove it when we're removed what we added
+ int lastPrefsPageLoaded = prefsPageLoaded;
+ prefsPageLoaded-=1;
+ if(lastPrefsPageLoaded == 1)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_REMOVE_PREFS_DLG);
+ }
+
+ //OutputDebugString(L"device unloading started");
+ // get rid of the transfer thread
+ threadKillswitch=1;
+ transferContext.WaitForKill();
+ if(threadKillswitch != 100)
+ {
+ /*OutputDebugString(L"FUCKO");*/
+ }
+
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&devPrefsPage,IPC_REMOVE_PREFS_DLG);
+ free(devPrefsPage.name);
+ //OutputDebugString(L"device unloading finished");
+ delete config;
+}
+
+void DeviceView::SetVideoView(BOOL enabled)
+{
+ videoView=enabled;
+ config->WriteInt(L"showVideoView",videoView);
+ if(videoView)
+ {
+ /* add video before the playlists */
+ wchar_t buffer[128] = {0};
+ NAVINSERTSTRUCT nis = {0};
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO, buffer, 128);
+ nis.item.pszInvariant = L"video";
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.hParent = treeItem;
+ nis.hInsertAfter = NCI_FIRST;
+ nis.item.iImage = icon_store.GetVideoIcon();
+ nis.item.iSelectedImage = nis.item.iImage;
+ videoTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ }
+ else
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, videoTreeItem);
+ videoTreeItem = 0;
+ }
+}
+
+static void removebadchars(wchar_t *s)
+{
+ while (s && *s)
+ {
+ if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|')
+ *s = L'_';
+ s = CharNextW(s);
+ }
+}
+
+static __int64 fileSize(wchar_t * filename)
+{
+ WIN32_FIND_DATA f= {0};
+ HANDLE h = FindFirstFileW(filename,&f);
+ if(h == INVALID_HANDLE_VALUE) return -1;
+ FindClose(h);
+ ULARGE_INTEGER i;
+ i.HighPart = f.nFileSizeHigh;
+ i.LowPart = f.nFileSizeLow;
+ return i.QuadPart;
+}
+
+static bool copySettings(wchar_t * ssrc, wchar_t * sdest)
+{
+ FILE * src, * dest;
+ src=_wfopen(ssrc,L"rt");
+ if(!src) return false;
+ dest=_wfopen(sdest,L"wt");
+ if(!dest)
+ {
+ fclose(src); return false;
+ }
+ wchar_t buf[1024]=L"";
+ bool insection=false;
+ while(fgetws(buf,1024,src))
+ {
+ if(buf[0]==L'[' && wcslen(buf)>1) if(buf[wcslen(buf)-2]==L']') insection=false;
+ if(wcscmp(&buf[0],L"[ml_pmp]\n")==0) insection=true;
+ if(insection) fputws(&buf[0],dest);
+ }
+ fclose(src);
+ fclose(dest);
+ return true;
+}
+
+HNAVITEM DeviceView::AddPlaylistNode(int id)
+{
+ NAVINSERTSTRUCT nis = {0};
+ wchar_t title[256] = {0}, name[128] = {0};
+
+ dev->getPlaylistName(id, title , ARRAYSIZE(title));
+
+ StringCchPrintf(name, ARRAYSIZE(name), L"ml_pmp_playlist_%d", id);
+
+ nis.hParent = treeItem;
+ nis.hInsertAfter = NCI_LAST;
+
+ memset(&nis.item, 0, sizeof(nis.item));
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.pszText = title;
+ nis.item.pszInvariant = name;
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE;
+ nis.item.iImage = icon_store.GetPlaylistIcon();
+ nis.item.iSelectedImage = nis.item.iImage;
+ nis.item.style = NIS_ALLOWEDIT;
+ nis.item.styleMask = nis.item.style;
+
+ HNAVITEM item = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ playlistTreeItems.push_back(item);
+
+ return item;
+}
+
+int DeviceView::CreatePlaylist(wchar_t * name, bool silent)
+{
+ HNAVITEM item;
+ int playlistId;
+
+ if (NULL == name)
+ {
+ int count, slot, length;
+ wchar_t buffer[512] = {0}, buffer2[ARRAYSIZE(buffer)] = {0};
+ BOOL foundMatch;
+
+ name = WASABI_API_LNGSTRINGW_BUF(IDS_NEW_PLAYLIST, buffer, ARRAYSIZE(buffer));
+
+ count = dev->getPlaylistCount();
+ slot = 1;
+ length = -1;
+
+ do
+ {
+ foundMatch = FALSE;
+ for (int i = 1; i < count; i++)
+ {
+
+ dev->getPlaylistName(i, buffer2, ARRAYSIZE(buffer2));
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, buffer2, -1, name, -1))
+ {
+ foundMatch = TRUE;
+
+ if(name != buffer)
+ {
+ if (FAILED(StringCchCopy(buffer, ARRAYSIZE(buffer), name)))
+ return -1;
+ }
+
+ if (-1 == length)
+ {
+ wchar_t *end;
+ length = lstrlen(buffer);
+ end = buffer + length;
+
+ if(length > 2 && L')' == *(--end))
+ {
+ unsigned short charType;
+
+ for(wchar_t *begin = --end; begin != buffer; begin--)
+ {
+ if (L'(' == *begin)
+ {
+ if (begin > buffer && L' ' == *(--begin))
+ {
+ length = (int)(intptr_t)(begin - buffer);
+ slot = 0;
+ }
+ break;
+ }
+ else if (FALSE == GetStringTypeW(CT_CTYPE1, begin, 1, &charType) ||
+ 0 == (C1_DIGIT & charType))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ slot++;
+
+ if (1 == slot)
+ buffer[length] = L'\0';
+ else if (FAILED(StringCchPrintf(buffer + length, ARRAYSIZE(buffer) - length, L" (%d)", slot)))
+ return false;
+
+ break;
+ }
+ }
+ } while(FALSE != foundMatch);
+ }
+
+ playlistId = dev->newPlaylist(name);
+ if(playlistId == -1)
+ return -1; // failed
+
+ item = AddPlaylistNode(playlistId);
+ if (NULL == item)
+ {
+ dev->deletePlaylist(playlistId);
+ return -1;
+ }
+
+ DevicePropertiesChanges();
+
+ if(!silent)
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+
+ return playlistId;
+}
+
+void DeviceView::RenamePlaylist(int playlistId)
+{
+ HNAVITEM item;
+
+ item = NULL;
+
+ if (0 == playlistId)
+ {
+ if (0 != dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ item = treeItem;
+ }
+ else
+ {
+ if (playlistId > 0 && playlistId <= (int)playlistTreeItems.size())
+ item = playlistTreeItems[playlistId - 1];
+ }
+
+ if (NULL != item)
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+}
+
+size_t DeviceView::GetPlaylistName(wchar_t *buffer, size_t bufferSize, int playlistId,
+ const wchar_t *defaultName, BOOL quoteSpaces)
+{
+ size_t length;
+
+ if (NULL == buffer || 0 == bufferSize)
+ return 0;
+
+ buffer[0] = L'\0';
+ if (NULL != dev)
+ dev->getPlaylistName(playlistId, buffer, bufferSize);
+
+ if (FAILED(StringCchLength(buffer, bufferSize, &length)))
+ return 0;
+
+ if (0 == length)
+ {
+ if (NULL != defaultName)
+ {
+ if (FALSE != IS_INTRESOURCE(defaultName))
+ WASABI_API_LNGSTRINGW_BUF((int)(intptr_t)defaultName, buffer, bufferSize);
+ else
+ {
+ if (FAILED(StringCchCopy(buffer, bufferSize, defaultName)))
+ return 0;
+ }
+
+ if (FAILED(StringCchLength(buffer, bufferSize, &length)))
+ return 0;
+ }
+ }
+ else
+ {
+ if (FALSE != quoteSpaces &&
+ (L' ' == buffer[0] || L' ' == buffer[length-1]) &&
+ (bufferSize - length) > 2)
+ {
+ memmove(buffer + 1, buffer, sizeof(wchar_t) * length);
+ buffer[0] = L'\"';
+ buffer[length++] = L'\"';
+ buffer[length] = L'\0';
+ }
+ }
+ return length;
+}
+
+bool DeviceView::DeletePlaylist(int playlistId, bool deleteFiles, bool verbal)
+{
+ int index;
+ C_ItemList delList;
+
+ if(playlistId < 1)
+ return false;
+
+ index = playlistId - 1;
+
+ if (false != deleteFiles)
+ {
+ int length;
+
+ length = dev->getPlaylistLength(playlistId);
+ for(int i = 0; i < length; i++)
+ {
+ delList.Add((void*)dev->getPlaylistTrack(playlistId, i));
+ }
+ }
+
+ if (false != verbal)
+ {
+ wchar_t message[1024] = {0}, title[1024] = {0}, playlistName[256] = {0}, deviceName[256] = {0};
+
+ GetPlaylistName(playlistName, ARRAYSIZE(playlistName), playlistId, NULL, FALSE);
+ GetPlaylistName(deviceName, ARRAYSIZE(deviceName), 0, MAKEINTRESOURCE(IDS_DEVICE_LOWERCASE), TRUE);
+
+ if (0 != delList.GetSize())
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_PHYSICALLY_REMOVE_X_TRACKS, title, ARRAYSIZE(title));
+ StringCchPrintf(message, ARRAYSIZE(message), title, delList.GetSize(), playlistName);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST_TITLE, title, ARRAYSIZE(title));
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST, title, ARRAYSIZE(title));
+ StringCchPrintf(message, ARRAYSIZE(message), title, playlistName, deviceName);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST_TITLE, title, ARRAYSIZE(title));
+ }
+
+ if(IDYES != MessageBox(plugin.hwndLibraryParent, message, title,
+ MB_YESNO | MB_ICONWARNING))
+ {
+ return false;
+ }
+ }
+
+ if (0 != delList.GetSize())
+ {
+ int result;
+ result = DeleteTracks(&delList, CENTER_OVER_ML_VIEW);
+ if (IDABORT == result) /* user abort */
+ return false;
+
+ if (IDOK != result) /* error */
+ {
+
+ }
+
+ }
+
+ HNAVITEM item = playlistTreeItems[index];
+ playlistTreeItems.erase(playlistTreeItems.begin() + index);
+
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, item);
+
+ dev->deletePlaylist(playlistId);
+ DevicePropertiesChanges();
+
+ return true;
+}
+
+bool DeviceView::GetTransferFromMlSupported(int dataType)
+{
+ switch(dataType)
+ {
+ case ML_TYPE_ITEMRECORDLISTW:
+ case ML_TYPE_ITEMRECORDLIST:
+ case ML_TYPE_PLAYLIST:
+ case ML_TYPE_PLAYLISTS:
+ case ML_TYPE_FILENAMES:
+ case ML_TYPE_FILENAMESW:
+ return true;
+ }
+ return false;
+}
+intptr_t DeviceView::TransferFromML(int type, void* data, int unsupportedReturn, int supportedReturn, int playlist)
+{
+ int r;
+
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ if(type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ r=AddItemListToTransferQueue((itemRecordListW*)data,playlist);
+ }
+ else if(type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordListW list= {0};
+ convertRecordList(&list,(itemRecordList*)data);
+ r=AddItemListToTransferQueue(&list,playlist);
+ freeRecordList(&list);
+ }
+ else if(type == ML_TYPE_FILENAMES)
+ {
+ C_ItemList fileList;
+ char * filenames = (char *)data;
+ while(filenames && *filenames)
+ {
+ fileList.Add(filenames);
+ filenames+=strlen(filenames)+1;
+ }
+ r=AddFileListToTransferQueue((char**)fileList.GetAll(),fileList.GetSize(),playlist);
+ }
+ else if(type == ML_TYPE_FILENAMESW)
+ {
+ C_ItemList fileList;
+ wchar_t * filenames = (wchar_t *)data;
+ while(filenames && *filenames)
+ {
+ fileList.Add(filenames);
+ filenames+=wcslen(filenames)+1;
+ }
+ r=AddFileListToTransferQueue((wchar_t**)fileList.GetAll(),fileList.GetSize(),playlist);
+ }
+ else if(type == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist * pl = (mlPlaylist *)data;
+ TransferPlaylist((wchar_t*)pl->filename,(wchar_t*)pl->title);
+ r=0;
+ }
+ else if(type == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)data;
+ while(playlists && *playlists)
+ {
+ mlPlaylist *pl = *playlists;
+ TransferPlaylist((wchar_t*)pl->filename,(wchar_t*)pl->title);
+ playlists++;
+ }
+ r=0;
+ }
+ else return unsupportedReturn;
+
+ wchar_t errStr[32] = {0};
+ if(r==-1)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_DEVICE_OUT_OF_SPACE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+ else if(r==-2)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_FORMAT_NO_TX),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+
+ return supportedReturn;
+}
+
+
+class ItemListRefLoader : public ifc_playlistloadercallback
+{
+public:
+ ItemListRefLoader(C_ItemList &itemList, C_ItemList &playlistItemList) : fileList(itemList), playlistList(playlistItemList)
+ {
+ }
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ if(playlistManager->CanLoad(filename))
+ playlistList.Add(_wcsdup(filename));
+ else
+ fileList.Add(_wcsdup(filename));
+ }
+
+ C_ItemList &fileList, &playlistList;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS ItemListRefLoader
+START_DISPATCH;
+VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile )
+END_DISPATCH;
+#undef CBCLASS
+
+
+class PlaylistDirectoryCallback : public ifc_playlistdirectorycallback
+{
+public:
+ PlaylistDirectoryCallback(const wchar_t *_extlist) : extlist(_extlist)
+ {
+ }
+
+ bool ShouldRecurse(const wchar_t *path)
+ {
+ // TODO: check for recursion?
+ return true;
+ }
+
+ bool ShouldLoad(const wchar_t *filename)
+ {
+ if(playlistManager->CanLoad(filename))
+ return true;
+ const wchar_t *ext = PathFindExtensionW(filename);
+ if(!*ext)
+ return false;
+
+ ext++;
+
+ const wchar_t *a = extlist;
+ while(a && *a)
+ {
+ if(!_wcsicmp(a, ext))
+ return true;
+ a += wcslen(a) + 1;
+ }
+ return false;
+ }
+
+ const wchar_t *extlist;
+
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS PlaylistDirectoryCallback
+START_DISPATCH;
+CB(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse)
+CB(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad)
+END_DISPATCH;
+#undef CBCLASS
+
+intptr_t DeviceView::TransferFromDrop(HDROP hDrop, int playlist)
+{
+ // benski> ugh. memory allocation hell. oh well
+ C_ItemList fileList;
+ C_ItemList playlistList;
+ const wchar_t *extListW = (const wchar_t *)SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_EXTLISTW);
+ PlaylistDirectoryCallback dirCB(extListW);
+ wchar_t temp[2048] = {0};
+ int y = DragQueryFileW(hDrop, 0xffffffff, temp, 2048);
+
+ for(int x = 0; x < y; x ++)
+ {
+ DragQueryFileW(hDrop, x, temp, 2048);
+ // see if it's a directory
+ bool isDir=false;
+ if(!PathIsURLW(temp) && !PathIsNetworkPathW(temp))
+ {
+ HANDLE h;
+ WIN32_FIND_DATAW d;
+
+ h = FindFirstFileW(temp, &d);
+ if(h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+ if(d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ ItemListRefLoader fileListCB(fileList, playlistList);
+ playlistManager->LoadDirectory(temp, &fileListCB, &dirCB);
+ isDir=true;
+ }
+ }
+ }
+
+ if(!isDir)
+ {
+ if(playlistManager->CanLoad(temp))
+ playlistList.Add(_wcsdup(temp));
+ else
+ fileList.Add(_wcsdup(temp));
+ }
+ }
+
+ int r=0, r2=0;
+ if(fileList.GetSize())
+ r=AddFileListToTransferQueue((wchar_t**)fileList.GetAll(),fileList.GetSize(),playlist);
+#if 0
+ if(playlistList.GetSize())
+ r2=AddFileListToTransferQueue((wchar_t**)playlistList.GetAll(), playlistList.GetSize(),1/*playlists*/);
+#endif
+ wchar_t errStr[32] = {0};
+ if(r==-1 || r2==-1)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_DEVICE_OUT_OF_SPACE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+ if(r==-2 || r2 == -2)
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_FORMAT_NO_TX),
+ WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0);
+
+ // benski> my CS301 professor would be proud!
+ fileList.for_all(free);
+ playlistList.for_all(free);
+
+ GlobalFree((HGLOBAL)extListW);
+ return 0;
+}
+
+HWND DeviceView::CreateView(HWND parent)
+{
+ currentViewedDevice=this;
+ currentViewedPlaylist=0;
+
+ if (currentViewedDevice->config->ReadInt(L"media_numfilters", 2) == 1)
+ return WASABI_API_CREATEDIALOGPARAMW((isCloudDevice ? IDD_VIEW_CLOUD_SIMPLE : IDD_VIEW_PMP_VIDEO),
+ parent, pmp_video_dlgproc, (currentViewedDevice->isCloudDevice ? 1 : 2));
+ else
+ return WASABI_API_CREATEDIALOGPARAMW((isCloudDevice ? IDD_VIEW_CLOUD_ARTISTALBUM : IDD_VIEW_PMP_ARTISTALBUM),
+ parent, pmp_artistalbum_dlgproc, 0);
+}
+
+BOOL DeviceView::DisplayDeviceContextMenu(HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ HMENU menu = GetSubMenu(m_context_menus,3);
+ if (NULL == menu)
+ return FALSE;
+
+ if(dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ AppendMenu(menu,0,ID_TREEPLAYLIST_RENAMEPLAYLIST,WASABI_API_LNGSTRINGW(IDS_RENAME_DEVICE));
+
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,
+ pt.x, pt.y, plugin.hwndLibraryParent, NULL);
+
+ if(dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ DeleteMenu(menu,ID_TREEPLAYLIST_RENAMEPLAYLIST,MF_BYCOMMAND);
+
+ switch(r)
+ {
+ case ID_TREEDEVICE_NEWPLAYLIST:
+ CreatePlaylist();
+ break;
+ case ID_TREEDEVICE_EJECTDEVICE:
+ Eject();
+ break;
+ case ID_TREEPLAYLIST_RENAMEPLAYLIST:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ //RenamePlaylist(0);
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL DeviceView::DisplayPlaylistContextMenu(int playlistId, HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ HMENU menu = GetSubMenu(m_context_menus,4);
+ if (NULL == menu)
+ return FALSE;
+
+ EnableMenuItem(menu,ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA,
+ MF_BYCOMMAND | (dev->copyToHardDriveSupported()? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
+
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,
+ pt.x, pt.y, plugin.hwndLibraryParent, NULL);
+ switch(r)
+ {
+ case ID_TREEPLAYLIST_RENAMEPLAYLIST:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ break;
+ case ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES:
+ DeletePlaylist(playlistId, true, true);
+ break;
+ case ID_TREEPLAYLIST_REMOVEPLAYLIST:
+ DeletePlaylist(playlistId, false, true);
+ break;
+ case ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA:
+ CopyPlaylistToLibrary(playlistId);
+ break;
+ }
+
+ return TRUE;
+}
+static HNAVITEM Navigation_GetItemFromMessage(INT msg, INT_PTR param)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param) :
+ (HNAVITEM)param;
+}
+
+BOOL DeviceView::Navigation_IsPlaylistItem(HNAVITEM item, int *playlistId)
+{
+ for(size_t i=0; i < playlistTreeItems.size(); i++)
+ {
+ if(item == playlistTreeItems[i])
+ {
+ if (NULL != playlistId)
+ *playlistId = (i + 1);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+HWND DeviceView::Navigation_CreateViewCb(HNAVITEM item, HWND parentWindow)
+{
+ if(item == treeItem)
+ {
+ if (FALSE != navigationItemCreated)
+ {
+ currentViewedDevice = this;
+ currentViewedPlaylist = 0;
+ return WASABI_API_CREATEDIALOGW((currentViewedDevice->isCloudDevice ? IDD_VIEW_CLOUD_ARTISTALBUM : IDD_VIEW_PMP_ARTISTALBUM), parentWindow, pmp_artistalbum_dlgproc);
+ }
+ }
+ else if (item == queueTreeItem)
+ {
+ currentViewedDevice = this;
+ currentViewedPlaylist = 0;
+ return WASABI_API_CREATEDIALOGPARAMW((currentViewedDevice->isCloudDevice ? IDD_VIEW_CLOUD_QUEUE : IDD_VIEW_PMP_QUEUE), parentWindow, pmp_queue_dlgproc, (LPARAM)this);
+ }
+ else if(item == videoTreeItem)
+ {
+ currentViewedDevice = this;
+ currentViewedPlaylist = 0;
+ return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_PMP_VIDEO, parentWindow, pmp_video_dlgproc, 0);
+ }
+ else
+ {
+ if (FALSE != Navigation_IsPlaylistItem(item, &currentViewedPlaylist))
+ {
+ currentViewedDevice = this;
+ return WASABI_API_CREATEDIALOGW(IDD_VIEW_PMP_PLAYLIST, parentWindow, pmp_playlist_dlgproc);
+ }
+ }
+
+ return NULL;
+}
+
+BOOL DeviceView::Navigation_ShowContextMenuCb(HNAVITEM item, HWND hostWindow, POINT pt)
+{
+ if (item == treeItem)
+ {
+ if (FALSE != navigationItemCreated)
+ return DisplayDeviceContextMenu(item, hostWindow, pt);
+ }
+ else
+ {
+ int playlistId;
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ return DisplayPlaylistContextMenu(playlistId, item, hostWindow, pt);
+ }
+
+ return FALSE;
+}
+BOOL DeviceView::Navigation_ClickCb(HNAVITEM item, int actionType, HWND hostWindow)
+{
+ int playlistId;
+
+ switch(actionType)
+ {
+ case ML_ACTION_DBLCLICK:
+ case ML_ACTION_ENTER:
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ {
+ PlayPlaylist(playlistId, false, true, hostWindow);
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+int DeviceView::Navigation_DropTargetCb(HNAVITEM item, unsigned int dataType, void *data)
+{
+ if (item == treeItem)
+ {
+ if (FALSE != navigationItemCreated)
+ {
+ if(NULL == data)
+ return (false != GetTransferFromMlSupported(dataType)) ? 1 : -1;
+
+ return TransferFromML(dataType, data, -1, 1);
+ }
+ }
+ else
+ {
+ int playlistId;
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ {
+ if(NULL == data)
+ return (false != GetTransferFromMlSupported(dataType)) ? 1 : -1;
+
+ return TransferFromML(dataType, data, -1, 1, playlistId);
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL DeviceView::Navigation_TitleEditBeginCb(HNAVITEM item)
+{
+ if (item == treeItem)
+ {
+ if (FALSE != navigationItemCreated &&
+ FALSE == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+BOOL DeviceView::Navigation_TitleEditEndCb(HNAVITEM item, const wchar_t *title)
+{
+ int playlistId = 0;
+ wchar_t buffer[512] = {0};
+
+ if (item == treeItem)
+ {
+ if (FALSE == navigationItemCreated)
+ return FALSE;
+
+ playlistId = 0;
+ }
+ else
+ {
+ if (FALSE == Navigation_IsPlaylistItem(item, &playlistId))
+ return FALSE;
+ }
+
+ if (NULL == title)
+ return TRUE;
+
+ buffer[0] = L'\0';
+ dev->getPlaylistName(playlistId, buffer, ARRAYSIZE(buffer));
+
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, title, -1))
+ return TRUE;
+
+ dev->setPlaylistName(playlistId, title);
+ DevicePropertiesChanges();
+
+ buffer[0] = L'\0';
+ dev->getPlaylistName(playlistId, buffer, ARRAYSIZE(buffer));
+
+ if (0 == playlistId)
+ {
+ free(devPrefsPage.name);
+ devPrefsPage.name = _wcsdup(buffer);
+
+ UpdateDevicesListView(false);
+ OnNameChanged(buffer);
+ }
+
+ if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, title, -1))
+ return TRUE;
+
+ NAVITEM itemInfo;
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.pszText = buffer;
+ itemInfo.hItem = item;
+ itemInfo.mask = NIMF_TEXT;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+
+ return FALSE;
+}
+
+BOOL DeviceView::Navigation_KeyDownCb(HNAVITEM item, NMTVKEYDOWN *keyData, HWND hwnd)
+{
+ int playlistId;
+ if (item == treeItem)
+ {
+ if (FALSE == navigationItemCreated)
+ return FALSE;
+
+ switch(keyData->wVKey)
+ {
+ case VK_F2:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ break;
+ }
+ return TRUE;
+ }
+
+ if (FALSE != Navigation_IsPlaylistItem(item, &playlistId))
+ {
+ switch(keyData->wVKey)
+ {
+ case VK_F2:
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, item);
+ break;
+
+ case VK_DELETE:
+ DeletePlaylist(playlistId, false, true);
+ break;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+intptr_t DeviceView::MessageProc(int message_type, intptr_t param1, intptr_t param2, intptr_t param3)
+{
+ if(message_type >= ML_MSG_TREE_BEGIN && message_type <= ML_MSG_TREE_END)
+ {
+ HNAVITEM item;
+
+ switch(message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_CreateViewCb(item, (HWND)param2);
+ case ML_MSG_NAVIGATION_CONTEXTMENU:
+ {
+ POINT pt;
+ POINTSTOPOINT(pt, MAKEPOINTS(param3));
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_ShowContextMenuCb(item, (HWND)param2, pt);
+ }
+ case ML_MSG_TREE_ONCLICK:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_ClickCb(item, (int)param2, (HWND)param3);
+ case ML_MSG_TREE_ONDROPTARGET:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_DropTargetCb(item, (unsigned int)param2, (void*)param3);
+ case ML_MSG_NAVIGATION_ONBEGINTITLEEDIT:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_TitleEditBeginCb(item);
+ case ML_MSG_NAVIGATION_ONENDTITLEEDIT:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_TitleEditEndCb(item, (const wchar_t*)param2);
+ case ML_MSG_TREE_ONKEYDOWN:
+ item = Navigation_GetItemFromMessage(message_type, param1);
+ return (INT_PTR)Navigation_KeyDownCb(item, (NMTVKEYDOWN*)param2, (HWND)param3);
+ }
+ }
+ else if(message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (!gen_mlconfig->ReadInt(L"pmp_send_to", DEFAULT_PMP_SEND_TO))
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_ITEMRECORDLISTW ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW ||
+ param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)
+ {
+ if (dev->extraActions(DEVICE_SENDTO_UNSUPPORTED, 0, 0, 0) == 0)
+ {
+ wchar_t buffer[128] = {0};
+ dev->getPlaylistName(0, buffer, 128);
+ mediaLibrary.AddToSendTo(buffer, param2, reinterpret_cast<INT_PTR>(this));
+ }
+ }
+ }
+ }
+ else if(message_type == ML_MSG_ONSENDTOSELECT && param2 && param3 == (intptr_t)this)
+ {
+ // TODO!!!
+ // if we get a playlist or playlist list and we can match it to a cloud device then
+ // we check for 'hss' and if so then process as a cloud playlist else do as before
+ if (this->isCloudDevice && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS))
+ {
+ char name[128] = {0};
+ if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0))
+ {
+ if (!strcmp(name, "hss"/*HSS_CLIENT*/))
+ {
+ if(param1 == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist * pl = (mlPlaylist *)param2;
+ TransferAddCloudPlaylist((wchar_t*)pl->filename, (wchar_t*)pl->title);
+ }
+ else if(param1 == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)param2;
+ while(playlists && *playlists)
+ {
+ mlPlaylist *pl = *playlists;
+ TransferAddCloudPlaylist((wchar_t*)pl->filename, (wchar_t*)pl->title);
+ playlists++;
+ }
+ }
+ return 1;
+ }
+ }
+ }
+
+ UpdateActivityState();
+ return TransferFromML(param1,(void*)param2,0,1);
+ }
+ return 0;
+}
+
+void DeviceView::DevicePropertiesChanges()
+{
+ commitNeeded=true;
+ SetEvent(transferContext.notifier);
+}
+
+void DeviceView::Eject()
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if(txQueue && txQueue->GetSize() == 0)
+ {
+ dev->Eject();
+ }
+ else
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_SYNC_IS_IN_PROGRESS),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CANNOT_EJECT,titleStr,32),0);
+ }
+}
+
+Device * deleteTrackDev;
+C_ItemList * deleteTracks;
+extern HWND hwndMediaView;
+
+static INT_PTR CALLBACK pmp_delete_progress_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static int i;
+ static songid_t s;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ i=0;
+ s=0;
+ SetWindowText(hwndDlg,WASABI_API_LNGSTRINGW((!currentViewedDevice || currentViewedDevice && !currentViewedDevice->isCloudDevice ? IDS_DELETING_TRACKS : IDS_REMOVING_TRACKS)));
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETRANGE,0,MAKELPARAM(0, (!deleteTracks ? 0 : deleteTracks->GetSize())));
+ SetTimer(hwndDlg,0,5,NULL);
+
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+
+ break;
+ case WM_TIMER:
+ if(wParam == 1)
+ {
+ KillTimer(hwndDlg,wParam);
+ SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETPOS,i,0);
+ if(i < deleteTracks->GetSize())
+ {
+ songid_t s2 = (songid_t)deleteTracks->Get(i++);
+ if(s != s2)
+ {
+ if(hwndMediaView) SendMessage(hwndMediaView,WM_USER+1,(WPARAM)s2,0);
+ deleteTrackDev->deleteTrack(s2);
+ s=s2;
+ }
+ SetTimer(hwndDlg,1,5,NULL);
+ }
+ else EndDialog(hwndDlg, IDOK);
+ }
+ else if(wParam == 0)
+ {
+ KillTimer(hwndDlg,0);
+ int s = deleteTracks->GetSize();
+ for(int i=0; i<s; i++)
+ {
+ void * p = deleteTracks->Get(i);
+ for(int j=i+1; j<s; j++)
+ {
+ if(p == deleteTracks->Get(j))
+ {
+ deleteTracks->Del(j--); s--;
+ }
+ }
+ }
+ SetTimer(hwndDlg,1,5,NULL);
+ }
+ break;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_ABORT)
+ EndDialog(hwndDlg, IDABORT);
+ break;
+ }
+ return 0;
+}
+int DeviceView::DeleteTracks(C_ItemList * tracks, HWND centerWindow)
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if(txQueue && txQueue->GetSize() > 0)
+ {
+ wchar_t sorry[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SORRY,sorry,32),0);
+ return -1;
+ }
+ if (dev && tracks)
+ {
+ deleteTrackDev = dev;
+ deleteTracks = tracks;
+ return WASABI_API_DIALOGBOXPARAMW(IDD_PROGRESS, plugin.hwndLibraryParent, pmp_delete_progress_dlgproc, (LPARAM)centerWindow);
+ }
+ return IDABORT;
+}
+
+int DeviceView::AddFileListToTransferQueue(char ** files, int num, int playlist)
+{
+ wchar_t ** filesW = (wchar_t**)calloc(num, sizeof(wchar_t*));
+ for(int i=0; i<num; i++)
+ {
+ filesW[i] = AutoWideDup(files[i]);
+ }
+ int r = AddFileListToTransferQueue(filesW,num,playlist);
+ for(int i=0; i<num; i++)
+ {
+ free(filesW[i]);
+ }
+ free(filesW);
+ return r;
+}
+
+int DeviceView::AddFileListToTransferQueue(wchar_t ** files, int num, int playlist)
+{
+ C_ItemList * irs = fileListToItemRecords(files,num, CENTER_OVER_ML_VIEW);
+ int r = AddItemListToTransferQueue(irs,playlist);
+ for(int i=0; i < irs->GetSize(); i++)
+ {
+ itemRecordW * it = (itemRecordW *)irs->Get(i);
+ freeRecord(it);
+ free(it);
+ }
+ delete irs;
+ return r;
+}
+
+void TransfersListPushPopItem(CopyInst * item, DeviceView *view);
+int DeviceView::AddItemListToTransferQueue(C_ItemList * items, int playlist)
+{
+ if(playlist == 0)
+ {
+ if (!isCloudDevice)
+ {
+ int r=0;
+ C_ItemList toSend, haveSent;
+ ProcessDatabaseDifferences(dev,items,&haveSent,&toSend,NULL,NULL);
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+
+ for(int i = 0; i < toSend.GetSize(); i++)
+ {
+ if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)toSend.Get(i), true)) == -1) break;
+ }
+
+ txQueue->unlock();
+ }
+ return r;
+ }
+ else
+ {
+ int r=0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+
+ for(int i = 0; i < items->GetSize(); i++)
+ {
+ if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)items->Get(i), true)) == -1) break;
+ }
+
+ txQueue->unlock();
+ }
+ return r;
+ }
+ }
+ else
+ {
+ return TransferTracksToPlaylist(items,playlist);
+ }
+}
+
+int DeviceView::AddItemListToTransferQueue(itemRecordListW * items, int playlist)
+{
+ if(playlist == 0)
+ {
+ if (!isCloudDevice)
+ {
+ int r=0;
+ C_ItemList toSend;
+ ProcessDatabaseDifferences(dev,items,NULL,&toSend,NULL,NULL);
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int i = 0; i < toSend.GetSize(); i++)
+ if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)toSend.Get(i), true)) == -1) break;
+ txQueue->unlock();
+ }
+ return r;
+ }
+ else
+ {
+ int r=0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int i = 0; i < items->Size; i++)
+ if((r = this->AddTrackToTransferQueue(this, &items->Items[i], true)) == -1) break;
+ txQueue->unlock();
+ }
+ return r;
+ }
+ }
+ else
+ {
+ C_ItemList itemRecords;
+ for (int i = 0; i < items->Size; i++) itemRecords.Add(&items->Items[i]);
+ return TransferTracksToPlaylist(&itemRecords,playlist);
+ }
+}
+
+class ItemListLoader : public ifc_playlistloadercallback
+{
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ fileList.Add(_wcsdup(filename));
+ }
+
+ void FreeAll()
+ {
+ fileList.for_all(free);
+ }
+ C_ItemList fileList;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS ItemListLoader
+START_DISPATCH;
+VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile )
+END_DISPATCH;
+#undef CBCLASS
+
+void DeviceView::TransferPlaylist(wchar_t * file, wchar_t * name0)
+{
+ // first sort the name out
+ if(!file) return;
+ wchar_t name[256] = {0};
+ if(!name0)
+ {
+ wchar_t * s = wcsrchr(file,L'\\');
+ if(!s) s = wcsrchr(file,L'/');
+ if(!s) s = file;
+ else s++;
+ if(wcslen(s) >= 255) s[255]=0;
+ StringCchCopy(name,256, s);
+ wchar_t * e = wcsrchr(name,L'.');
+ if(e) *e=0;
+ }
+ else lstrcpyn(name,name0,255);
+ name[255]=0;
+ // name sorted, parse m3u
+
+ ItemListLoader fileList;
+ playlistManager->Load(file, &fileList);
+ C_ItemList *itemRecords = fileListToItemRecords(&fileList.fileList, CENTER_OVER_ML_VIEW);
+ fileList.FreeAll();
+
+ // now we have a list of itemRecords, lets try and add this playlist to the device!
+ int plid = CreatePlaylist(name,true);
+ if(plid != -1)
+ TransferTracksToPlaylist(itemRecords,plid);
+ delete itemRecords;
+}
+
+int DeviceView::TransferTracksToPlaylist(C_ItemList *itemRecords, int plid)
+{
+ wchar_t name[256] = {0};
+ dev->getPlaylistName(plid,name,256);
+ int i;
+ for(i=0; i<itemRecords->GetSize(); i++)
+ {
+ itemRecordW * ice = (itemRecordW *)itemRecords->Get(i);
+ wchar_t num[12] = {0};
+ StringCchPrintf(num, 12, L"%x",i);
+ setRecordExtendedItem(ice,L"PLN",num);
+ }
+ C_ItemList irAlreadyOn, siAlreadyOn;
+ ProcessDatabaseDifferences(dev,itemRecords,&irAlreadyOn,NULL,&siAlreadyOn,NULL);
+ // itemRecords_sort, irAlreadyOn, irTransfer and siAlreadyOn will NOT be in playlist order
+ // we must get them into playlist order. In O(n) :/
+ int l = itemRecords->GetSize();
+ PlaylistAddItem * pl = (PlaylistAddItem*)calloc(l,sizeof(PlaylistAddItem));
+ int on=0;
+ for(i=0; i < l; i++)
+ {
+ itemRecordW * ice = (itemRecordW *)itemRecords->Get(i);
+ int n;
+ swscanf(getRecordExtendedItem(ice,L"PLN"),L"%x",&n);
+ if(n >= l)
+ {
+ continue;
+ }
+ pl[n].item = ice;
+ if(on < irAlreadyOn.GetSize()) if((itemRecordW*)irAlreadyOn.Get(on) == ice) // this track is on the device!
+ {
+ pl[n].songid = (songid_t)siAlreadyOn.Get(on);
+ on++;
+ }
+ }
+
+ // awesome! pl now contains our playlist in proper order with the "songid" fields set if the track is on the device.
+ C_ItemList * directAdd = new C_ItemList;
+ int m = 0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ PlaylistCopyInst * inst = NULL;
+ txQueue->lock();
+ for(i=0; i < l; i++)
+ {
+ if(pl[i].songid)
+ {
+ directAdd->Add((void*)pl[i].songid);
+ }
+ else
+ {
+ int r = dev->trackAddedToTransferQueue(pl[i].item);
+ if(r)
+ {
+ m |= (-r);
+ freeRecord(pl[i].item);
+ continue;
+ }
+ if(!inst)
+ {
+ if(plid != -1) for(int i=0; i<directAdd->GetSize(); i++)
+ dev->addTrackToPlaylist(plid,(songid_t)directAdd->Get(i));
+ delete directAdd;
+ }
+ else
+ {
+ inst->plAddSongs = directAdd;
+ AddTrackToTransferQueue(inst);
+ }
+ directAdd = new C_ItemList;
+ inst = new PlaylistCopyInst(this,pl[i].item,name,plid);
+ }
+ freeRecord(pl[i].item);
+ }
+ if(inst)
+ {
+ inst->plAddSongs = directAdd;
+ AddTrackToTransferQueue(inst);
+ }
+ else // NULL inst means no transfers!
+ {
+ if(plid != -1) for(int i=0; i<directAdd->GetSize(); i++)
+ dev->addTrackToPlaylist(plid,(songid_t)directAdd->Get(i));
+ delete directAdd;
+ }
+ txQueue->unlock();
+ }
+ if (pl) free(pl);
+
+ wchar_t warnStr[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_WARNING,warnStr,32);
+ if(m == 1) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_DEVICE_MAYBE_FULL),warnStr,0);
+ else if(m == 2) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT),warnStr,0);
+ else if(m == 3) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT),warnStr,0);
+ return 0;
+}
+
+void DeviceView::TransferAddCloudPlaylist(wchar_t * file, wchar_t * name0)
+{
+ if (!file) return;
+
+ AGAVE_API_PLAYLISTS->Lock();
+ for (size_t index = 0; index < AGAVE_API_PLAYLISTS->GetCount(); index++)
+ {
+ const wchar_t* filename = AGAVE_API_PLAYLISTS->GetFilename(index);
+ if (!lstrcmpiW(filename, file))
+ {
+ int cloud = 1;
+ if (AGAVE_API_PLAYLISTS->GetInfo(index, api_playlists_cloud, &cloud, sizeof(cloud)) == API_PLAYLISTS_SUCCESS)
+ {
+ // not set as a cloud playlist so we need to set and then announce
+ if (!cloud)
+ {
+ cloud = 1;
+ AGAVE_API_PLAYLISTS->SetInfo(index, api_playlists_cloud, &cloud, sizeof(cloud));
+ AGAVE_API_PLAYLISTS->Flush();
+ }
+
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ mlplugin->MessageProc(0x406, index, 0, 0);
+ }
+ }
+ }
+ PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_LIBRARY_PLAYLISTS_REFRESH);
+ }
+ else
+ {
+ }
+ break;
+ }
+ }
+ AGAVE_API_PLAYLISTS->Unlock();
+}
+
+extern MLTREEITEMW mainTreeItem;
+
+HWND hwndToolTips=NULL;
+
+int DeviceView::AddTrackToTransferQueue(CopyInst * inst)
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->Offer(inst);
+ if(txQueue->GetSize() == 1)
+ SetEvent(transferContext.notifier);
+ device_update_map[0] = true;
+ device_update_map[inst->dev] = true;
+ }
+ return 0;
+}
+
+int DeviceView::AddTrackToTransferQueue(DeviceView * device, itemRecordW * item, bool dupeCheck, bool forceDupe)
+{
+ SongCopyInst * inst = new SongCopyInst(device, item);
+
+ if (dupeCheck)
+ {
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ // current queue dupe check
+ for(int i = 0; i < txQueue->GetSize(); i++)
+ {
+ if(((CopyInst *)txQueue->Get(i))->Equals(inst))
+ {
+ delete inst;
+ txQueue->unlock();
+ return 0;
+ }
+ }
+ txQueue->unlock();
+ }
+ }
+
+ if (!forceDupe)
+ {
+ int r = dev->trackAddedToTransferQueue(&inst->song);
+ if (r)
+ {
+ if (r == 2)
+ {
+ inst->status = STATUS_DONE;
+ inst->res = 2;
+ AddTrackToTransferQueue(inst);
+ return 0;
+ }
+ else
+ {
+ delete inst;
+ }
+ }
+ else AddTrackToTransferQueue(inst);
+ return r;
+ }
+ else
+ {
+ inst->res = 2;
+ AddTrackToTransferQueue(inst);
+ return 0;
+ }
+}
+
+class SyncItemListLoader : public ifc_playlistloadercallback
+{
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ if(pos < len)
+ {
+ songs[pos].filename = _wcsdup(filename);
+ metaToGet->Add(&songs[pos].map);
+ songMaps->Add(&songs[pos].pladd);
+ }
+ pos++;
+ }
+ int pos,len;
+ songMapping * songs;
+ C_ItemList * metaToGet, * songMaps;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS SyncItemListLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+typedef struct
+{
+ mlPlaylistInfo info;
+ songMapping * songs;
+} SyncPlaylist;
+
+void TransfersListUpdateItem(CopyInst * item);
+void TransfersListUpdateItem(CopyInst * item, DeviceView *view);
+/*
+static int setPlaylistTrack(Device * dev, int pl, int n, int len, songid_t song) {
+if(n >= len) { while(n >= len) { dev->addTrackToPlaylist(pl,song); len++; } return len; }
+else {
+dev->addTrackToPlaylist(pl,song);
+dev->playlistSwapItems(pl,len,n);
+dev->removeTrackFromPlaylist(pl,len);
+}
+return len;
+}
+*/
+class PlaylistSyncCopyInst : public CopyInst
+{
+public:
+ bool memFreed;
+ C_ItemList *songMaps;
+ C_ItemList * playlists;
+ PlaylistSyncCopyInst(DeviceView *dev, C_ItemList *songMaps, C_ItemList * playlists) : songMaps(songMaps), playlists(playlists)
+ {
+ usesPreCopy = false;
+ usesPostCopy = true;
+ this->dev = dev;
+ equalsType = -1;
+ status=STATUS_WAITING;
+ // status caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING,statusCaption,sizeof(statusCaption)/sizeof(wchar_t));
+ // track caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_PLAYLIST_SYNCRONIZATION,trackCaption,sizeof(trackCaption)/sizeof(wchar_t));
+ // type caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_OTHER,typeCaption,sizeof(typeCaption)/sizeof(wchar_t));
+ memFreed=false;
+ }
+
+ virtual ~PlaylistSyncCopyInst()
+ {
+ freeMemory();
+ }
+
+ virtual bool CopyAction()
+ {
+ return false;
+ }
+
+ virtual void PostCopyAction()
+ {
+ SyncPlaylists(); freeMemory();
+ }
+ virtual void Cancelled()
+ {
+ freeMemory();
+ }
+ virtual bool Equals(CopyInst * b)
+ {
+ return false;
+ }
+ void freeMemory()
+ {
+ if(memFreed) return;
+ memFreed=true;
+ if(songMaps) delete songMaps;
+ songMaps = NULL;
+ if(playlists)
+ {
+ int l = playlists->GetSize();
+ for(int i=0; i<l; i++)
+ {
+ SyncPlaylist * playlist = (SyncPlaylist *)playlists->Get(i);
+ if(playlist)
+ {
+ if(playlist->songs)
+ {
+ for(int j=0; j<playlist->info.numItems; j++)
+ {
+ if(playlist->songs[j].ice)
+ {
+ freeRecord(playlist->songs[j].ice);
+ free(playlist->songs[j].ice);
+ }
+ if(playlist->songs[j].filename)
+ free(playlist->songs[j].filename);
+ }
+ free(playlist->songs);
+ }
+ free(playlist);
+ }
+ }
+ delete playlists;
+ playlists = NULL;
+ }
+ }
+ void SyncPlaylists()
+ {
+ if(memFreed)
+ return;
+ WASABI_API_LNGSTRINGW_BUF(IDS_WORKING,statusCaption,sizeof(statusCaption)/sizeof(wchar_t));
+ TransfersListUpdateItem(this);
+ TransfersListUpdateItem(this, dev);
+ MapItemRecordsToSongs(dev->dev,(PlaylistAddItem **)songMaps->GetAll(),songMaps->GetSize());
+ int numPlaylists = playlists->GetSize();
+ for(int i=0; i<numPlaylists; i++)
+ {
+ SyncPlaylist * playlist = (SyncPlaylist *)playlists->Get(i);
+ int plnum = -1;
+ bool done = false;
+ int l = dev->dev->getPlaylistCount();
+ int j;
+ for(j=0; j < l; j++)
+ {
+ wchar_t buf[128] = {0};
+ dev->dev->getPlaylistName(j,buf,128);
+ if(wcscmp(buf,playlist->info.playlistName)) continue;
+ int plen = dev->dev->getPlaylistLength(j);
+ if(plen != playlist->info.numItems)
+ {
+ plnum = j;
+ break;
+ }
+ for(int k=0; k<plen; k++)
+ {
+ if(playlist->songs[k].song != dev->dev->getPlaylistTrack(j,k))
+ {
+ plnum = j;
+ break;
+ }
+ }
+ if(plnum == -1)
+ {
+ done = true;
+ break;
+ }
+ }
+ if(done) continue;
+ if(plnum == -1)
+ {
+ plnum = dev->CreatePlaylist(playlist->info.playlistName,true);
+ if(plnum == -1) continue;
+ }
+ int plen = dev->dev->getPlaylistLength(plnum);
+ while(plen && ((plen % 4) != 1)) dev->dev->removeTrackFromPlaylist(plnum,--plen); // avoid granulation boundarys
+ int n=0;
+ for(j=0; j<playlist->info.numItems; j++)
+ {
+ songid_t s = playlist->songs[j].song;
+ if(s && (n>=plen || s != dev->dev->getPlaylistTrack(plnum,n)))
+ {
+ // begin set item code...
+ if(n >= plen) while(n >= plen)
+ {
+ dev->dev->addTrackToPlaylist(plnum,s);
+ plen++;
+ }
+ else
+ {
+ dev->dev->addTrackToPlaylist(plnum,s);
+ dev->dev->playlistSwapItems(plnum,plen,n);
+ dev->dev->removeTrackFromPlaylist(plnum,plen);
+ }
+ // end set item code
+ }
+ if(s) n++;
+ }
+ plen = dev->dev->getPlaylistLength(plnum);
+ while(plen > n) dev->dev->removeTrackFromPlaylist(plnum,--plen);
+
+ if(_wcsicmp(playlist->info.playlistName,L"Podcasts")==0)
+ {
+ wchar_t *name=NULL;
+ for(int j=playlist->info.numItems-1; j>=0; j--)
+ {
+ wchar_t *n = getRecordExtendedItem(playlist->songs[j].ice,L"podcastchannel");
+ if(!name) name=n;
+ if(name && n)
+ {
+ if(_wcsicmp(name,n))
+ {
+ dev->dev->extraActions(DEVICE_ADDPODCASTGROUP,plnum,j+1,(intptr_t)name);
+ name=n;
+ }
+ if(j==0) dev->dev->extraActions(DEVICE_ADDPODCASTGROUP,plnum,0,(intptr_t)name);
+ }
+ }
+ dev->dev->extraActions(DEVICE_ADDPODCASTGROUP_FINISH,plnum,0,0);
+ }
+ }
+ dev->DevicePropertiesChanges();
+ freeMemory();
+ WASABI_API_LNGSTRINGW_BUF(IDS_DONE,statusCaption,sizeof(statusCaption)/sizeof(wchar_t));
+ TransfersListUpdateItem(this);
+ TransfersListUpdateItem(this, dev);
+ }
+};
+
+static bool shouldSyncPlaylist(wchar_t * name, C_Config * config)
+{
+ wchar_t buf[150] = {0};
+ StringCchPrintf(buf,150, L"sync-%s",name);
+ return config->ReadInt(buf,0) == config->ReadInt(L"plsyncwhitelist",1);
+}
+
+static int sortfunc_podcastpubdate(const void *elem1, const void *elem2)
+{
+ itemRecordW *ar = (itemRecordW *)elem1;
+ itemRecordW *br = (itemRecordW *)elem2;
+ wchar_t *a = getRecordExtendedItem(ar,L"podcastpubdate");
+ wchar_t *b = getRecordExtendedItem(br,L"podcastpubdate");
+ if(!a) a = L"0";
+ if(!b) b = L"0";
+ return _wtoi(b) - _wtoi(a);
+}
+
+void DeviceView::OnActivityStarted()
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->ActivityStarted( this, this );
+}
+
+void DeviceView::OnActivityChanged()
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->ActivityChanged( this, this );
+}
+
+void DeviceView::OnActivityFinished()
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->ActivityFinished( this, this );
+}
+
+void DeviceView::UpdateActivityState()
+{
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue && FALSE == activityRunning)
+ {
+ if (0 != txQueue->GetSize())
+ {
+ activityRunning = TRUE;
+
+ if (FAILED(GetProgress(&currentProgress)))
+ currentProgress = 0;
+
+ OnActivityStarted();
+ }
+ }
+ else
+ {
+ if (txQueue && 0 == txQueue->GetSize())
+ {
+ activityRunning = FALSE;
+ OnActivityFinished();
+ }
+ else
+ {
+ unsigned int percent;
+ if (FAILED(GetProgress(&percent)) ||
+ percent != currentProgress)
+ {
+ currentProgress = percent;
+ OnActivityChanged();
+ }
+ }
+ }
+}
+
+void DeviceView::UpdateSpaceInfo(BOOL updateUsedSpace, BOOL notifyChanges)
+{
+ uint64_t total, used;
+ unsigned int changes;
+
+ changes = 0;
+
+ total = dev->getDeviceCapacityTotal();
+ if (total != totalSpace)
+ {
+ totalSpace = total;
+ changes |= (1 << 0);
+ }
+
+ if (FALSE != updateUsedSpace)
+ {
+ used = dev->getDeviceCapacityAvailable();
+ if (used > total)
+ used = total;
+
+ used = total - used;
+
+ if (used != usedSpace)
+ {
+ usedSpace = used;
+ changes |= (1 << 1);
+ }
+ }
+
+ if (0 != changes && FALSE != notifyChanges)
+ {
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ {
+ if (0 != ((1 << 0) & changes))
+ l_event_handler->TotalSpaceChanged(this, totalSpace);
+ if (0 != ((1 << 1) & changes))
+ l_event_handler->TotalSpaceChanged(this, usedSpace);
+ }
+ }
+}
+
+
+void DeviceView::OnNameChanged(const wchar_t *new_name)
+{
+ for ( ifc_deviceevent *l_event_handler : event_handlers )
+ l_event_handler->DisplayNameChanged( this, new_name );
+}
+
+void DeviceView::Sync(bool silent)
+{
+ // sync configuration settings....
+ bool syncAllLibrary = config->ReadInt(L"syncAllLibrary",1)!=0;
+
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ device_name[0] = 0;
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ HWND centerWindow = CENTER_OVER_ML_VIEW;
+ UpdateActivityState();
+
+ C_ItemList mllist;
+ wchar_t * querystring=0;
+ itemRecordListW *results = 0;
+ if(syncAllLibrary)
+ {
+ querystring = _wcsdup(config->ReadString(L"SyncQuery",L"type=0"));
+ results = (AGAVE_API_MLDB ? AGAVE_API_MLDB->Query(querystring) : NULL);
+ if (results)
+ for(int i = 0; i < results->Size; i++) mllist.Add(&results->Items[i]);
+ }
+
+ // read playlists/views and find out what else needs to be added
+ PlaylistSyncCopyInst * sync = NULL;
+ C_ItemList filenameMaps;
+ C_ItemList *songMaps = new C_ItemList;
+ C_ItemList * playlists = new C_ItemList;
+
+ // first collect playlists without metadata
+ SyncItemListLoader list;
+ list.metaToGet = &filenameMaps;
+ list.songMaps = songMaps;
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_PLAYLIST_COUNT);
+ for(int i=0; i<playlistsnum; i++)
+ {
+ SyncPlaylist* playlist = (SyncPlaylist*)calloc(sizeof(SyncPlaylist),1);
+ playlist->info.size = sizeof(mlPlaylistInfo);
+ playlist->info.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&playlist->info, ML_IPC_PLAYLIST_INFO);
+ if(shouldSyncPlaylist(playlist->info.playlistName, config))
+ {
+ playlists->Add(playlist);
+ }
+ else
+ {
+ free(playlist);
+ playlist = 0;
+ continue;
+ }
+ //if(playlist->info.numItems <= 1)
+ {
+ list.pos = list.len = 0;
+ playlistManager->Load(playlist->info.filename, &list);
+ playlist->info.numItems = list.pos;
+ }
+ list.pos = 0;
+ list.len=playlist->info.numItems;
+ list.songs = playlist->songs = (songMapping*)calloc(sizeof(songMapping), list.len);
+ playlistManager->Load(playlist->info.filename, &list);
+ }
+ mapFilesToItemRecords((filenameMap **)filenameMaps.GetAll(), filenameMaps.GetSize(), centerWindow); // get metadata
+
+ // now sync podcasts...
+ if (dev->extraActions(DEVICE_SUPPORTS_PODCASTS, 0, 0, 0) == 0)
+ {
+ int podcasteps = config->ReadInt(L"podcast-sync_episodes",0);
+ int podcastsnum = AGAVE_API_PODCASTS ? AGAVE_API_PODCASTS->GetNumPodcasts() : 0;
+ if(podcasteps && podcastsnum > 0)
+ {
+ // if we want to sync podcasts and we have podcasts to sync
+ bool all = !!config->ReadInt(L"podcast-sync_all", 1);
+ SyncPlaylist * s = (SyncPlaylist *)calloc(sizeof(SyncPlaylist),1);
+ lstrcpyn(s->info.playlistName, L"Podcasts", 128); //set the name of the playlist containing our podcasts
+ int n = 0, alloc = 512;
+ s->songs = (songMapping*)calloc(alloc, sizeof(songMapping));
+ for(int i = 0; i < podcastsnum; i++)
+ {
+ ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i);
+ if(podcast)
+ {
+ wchar_t podcast_name[256] = {0};
+ if(podcast->GetTitle(podcast_name, 256) == 0)
+ {
+ wchar_t buf[300] = {0};
+ StringCchPrintf(buf, 300, L"podcast-sync-%s", podcast_name);
+ if(podcast_name[0] && (all || config->ReadInt(buf,0))) // if we have a podcast and we want to sync it
+ {
+ wchar_t query[300] = {0};
+ StringCchPrintf(query, 300, L"podcastchannel = \"%s\"", podcast_name);
+ itemRecordListW *podcasts = AGAVE_API_MLDB->Query(query);
+ if(podcasts)
+ {
+ qsort(podcasts->Items,podcasts->Size,sizeof(itemRecordW),sortfunc_podcastpubdate); // sort the podcasts into publish date order
+ for(int j=0; j<podcasts->Size && (podcasteps == -1 || j < podcasteps); j++)
+ {
+ // add podcast to playlist
+ if(n >= alloc)
+ {
+ size_t old_alloc = alloc;
+ alloc += 512;
+ songMapping* new_songs = (songMapping*)realloc(s->songs,sizeof(songMapping) * alloc);
+ if (new_songs)
+ {
+ s->songs = new_songs;
+ }
+ else
+ {
+ new_songs = (songMapping*)malloc(sizeof(songMapping) * alloc);
+ if (new_songs)
+ {
+ memcpy(new_songs, s->songs, sizeof(songMapping) * old_alloc);
+ free(s->songs);
+ s->songs = new_songs;
+ }
+ else
+ {
+ alloc = old_alloc;
+ continue;
+ }
+ }
+ }
+ ZeroMemory(&s->songs[n],sizeof(songMapping));
+ s->songs[n].ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1);
+ copyRecord(s->songs[n].ice,&podcasts->Items[j]);
+ mllist.Add(s->songs[n].ice);
+ songMaps->Add(&s->songs[n].pladd);
+ n++;
+ }
+ if(podcasts)
+ AGAVE_API_MLDB->FreeRecordList(podcasts);
+ }
+ }
+ }
+ }
+ }
+ s->info.numItems = n;
+ if(n)
+ playlists->Add(s);
+ else
+ {
+ free(s->songs);
+ free(s);
+ }
+ }
+ }
+ // now collect playlists with metadata (i.e, smart views)
+ // except the new ml_local isn't ready.
+ // calloc a new SyncPlaylist, fill in playlist->info.numItems, playlist->info.playlistName and playlist->songs[].ice then add to playlists.
+
+ // add tracks to be sync'd
+ for(int i=0; i<filenameMaps.GetSize(); i++)
+ {
+ filenameMap* f = (filenameMap*)filenameMaps.Get(i);
+ if(f->ice)
+ mllist.Add(f->ice);
+ }
+ // prepare sync
+ if(playlists->GetSize())
+ sync = new PlaylistSyncCopyInst(this, songMaps, playlists);
+ else
+ {
+ delete playlists;
+ delete songMaps;
+ }
+
+ // work out the tracks to be sent and deleted...
+ C_ItemList synclist,dellist;
+
+ ProcessDatabaseDifferences(dev, &mllist, NULL, &synclist, NULL, &dellist);
+
+ if(!synclist.GetSize() && !dellist.GetSize())
+ {
+ // nothing to do
+ if(sync)
+ {
+ sync->SyncPlaylists();
+ delete sync;
+ }
+ if(!silent)
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_NOTHING_TO_SYNC_UP_TO_DATE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SYNC, titleStr, 32),0);
+ }
+ }
+ else
+ {
+ // need to sync some tracks
+ if(IDOK == SyncDialog_Show(centerWindow, this, &synclist, &dellist, FALSE))
+ {
+ config->WriteInt(L"syncOnConnect_time",(int)time(NULL));
+ if(dellist.GetSize())
+ {
+ switch(config->ReadInt(L"TrueSync",0))
+ {
+ case 1: this->DeleteTracks(&dellist, centerWindow); break;
+ case 2: this->CopyTracksToHardDrive(&dellist); break;
+ }
+ }
+
+ int i = 0, l = 0;
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ l = synclist.GetSize();
+ txQueue->lock();
+ for(i = 0; i < l; i++) if(AddTrackToTransferQueue(this, (itemRecordW*)synclist.Get(i), false) == -1) break;
+ if(sync) AddTrackToTransferQueue(sync);
+ txQueue->unlock();
+ }
+
+ if(i != l)
+ {
+ wchar_t titleStr[128] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_NOT_ENOUGH_SPACE, titleStr, ARRAYSIZE(titleStr)),
+ MB_OK | MB_ICONWARNING);
+ }
+ }
+ else
+ {
+ if(sync) delete sync;
+ }
+ }
+
+ if(syncAllLibrary)
+ {
+ if(results)
+ AGAVE_API_MLDB->FreeRecordList(results);
+ free(querystring);
+ }
+}
+
+void DeviceView::CloudSync(bool silent)
+{
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ UpdateActivityState();
+
+ // work out the tracks to be sent...
+ C_ItemList *filenameMaps2 = new C_ItemList, synclist;
+ DeviceView * hss = 0, * local = 0;
+
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ // determine the cloud device and alter the device
+ // to be checked with as needed by the action done
+ for(int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+
+ if (d->isCloudDevice)
+ {
+ char name[128] = {0};
+ if (d->dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0))
+ {
+ if (!strcmp(name, "hss"/*HSS_CLIENT*/))
+ hss = d;
+ else if (!strcmp(name, "local_desktop"))
+ local = d;
+ }
+ }
+ }
+
+ if (local && hss && local->dev == dev)
+ {
+ // just use the local library as the source to compare against
+ mlplugin->MessageProc(0x403, /*source*/-1, /*dest*/hss->dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID,0,0,0), (INT_PTR)filenameMaps2);
+ }
+ else
+ {
+ // just use the local library as the source to compare against
+ mlplugin->MessageProc(0x403, /*source*/-1, /*dest*/dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID,0,0,0), (INT_PTR)filenameMaps2);
+ }
+ }
+ }
+ }
+
+ synclist = *fileListToItemRecords(filenameMaps2, CENTER_OVER_ML_VIEW);
+ nu::qsort(synclist.GetAll(), synclist.GetSize(), sizeof(void*), dev, compareSongs);
+
+ if(!synclist.GetSize())
+ {
+ if(!silent)
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_NOTHING_TO_SYNC_UP_TO_DATE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_SYNC,titleStr,32),0);
+ }
+ }
+ else
+ {
+ DeviceView * destDevice = (local && hss && local->dev == dev ? hss : this);
+ // need to sync some tracks
+ if(IDOK == SyncCloudDialog_Show(CENTER_OVER_ML_VIEW, destDevice, &synclist))
+ {
+ int l = synclist.GetSize();
+ cloudTransferQueue.lock();
+ int i = 0;
+ for (; i < l; i++) if (AddTrackToTransferQueue(destDevice, (itemRecordW*)synclist.Get(i), false) == -1) break;
+ cloudTransferQueue.unlock();
+
+ if(i != l)
+ {
+ wchar_t titleStr[128] = {0};
+ MessageBox(plugin.hwndLibraryParent,
+ WASABI_API_LNGSTRINGW(IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE),
+ WASABI_API_LNGSTRINGW_BUF(IDS_NOT_ENOUGH_SPACE, titleStr, ARRAYSIZE(titleStr)),
+ MB_OK | MB_ICONWARNING);
+ }
+ }
+ }
+}
+
+extern itemRecordListW * generateAutoFillList(DeviceView * dev, C_Config * config); // from autofill.cpp
+
+void DeviceView::Autofill()
+{
+ HWND centerWindow;
+
+ centerWindow = CENTER_OVER_ML_VIEW;
+
+ if (AGAVE_API_STATS)
+ {
+ wchar_t device_name[128] = {0};
+ if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0])
+ {
+ AGAVE_API_STATS->SetString("pmp", device_name);
+ }
+ }
+
+ UpdateActivityState();
+
+ C_ItemList delList,sendList;
+
+ itemRecordListW * autofillList = generateAutoFillList(this,config);
+ ProcessDatabaseDifferences(dev,autofillList,NULL,&sendList,NULL,&delList);
+
+ if(IDOK == SyncDialog_Show(centerWindow, this, &sendList, &delList, TRUE))
+ {
+ config->WriteInt(L"syncOnConnect_time", (int)time(NULL));
+ // delete all tracks in delList
+ if(IDOK == DeleteTracks(&delList, centerWindow))
+ {
+ // not aborted
+ // send all tracks in sendList
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for(int i = 0; i < sendList.GetSize(); i++) AddTrackToTransferQueue(this, (itemRecordW*)sendList.Get(i), false);
+ txQueue->unlock();
+ }
+ }
+ }
+ if(autofillList)
+ freeRecordList(autofillList);
+}
+
+extern int serverPort;
+
+bool DeviceView::PlayTracks(C_ItemList * tracks, int startPlaybackAt, bool enqueue, bool msgIfImpossible, HWND parent)
+{
+ if(tracks->GetSize() == 0) return true;
+ // direct playback?
+ if(dev->playTracks((songid_t*)tracks->GetAll(),tracks->GetSize(),startPlaybackAt,enqueue))
+ return true;
+ if(serverPort>0 && dev->copyToHardDriveSupported())
+ {
+ // indirect playback?
+ if(!enqueue)
+ {
+ //clear playlist
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
+ }
+
+ wchar_t buf[2048] = {0};
+ dev->getPlaylistName(0,buf,128);
+ AutoUrl device(buf);
+ for(int i=0; i<tracks->GetSize(); i++)
+ {
+ songid_t s = (songid_t)tracks->Get(i);
+ //encode fields to url format
+ wchar_t metadata[2048] = {0};
+ dev->getTrackArtist(s,metadata,2048);
+ AutoUrl artist(metadata);
+ dev->getTrackAlbum(s,metadata,2048);
+ AutoUrl album(metadata);
+ dev->getTrackTitle(s,metadata,2048);
+ AutoUrl title(metadata);
+
+ // construct URL
+ wchar_t ext[10]=L"";
+ dev->getTrackExtraInfo(s,L"ext",ext,10);
+ char buf[8192] = {0};
+ StringCchPrintfA(buf,8192, "http://127.0.0.1:%d/?a=%s&l=%s&t=%s&d=%s%s%s",serverPort,artist,album,title,device,*ext?";.":"",(char*)AutoChar(ext));
+ // get title
+ AutoWide wideUrl(buf);
+
+ wchar_t buf2[4096] = {0};
+ getTitle(dev,s,wideUrl,buf2,4096);
+ // enqueue file
+ enqueueFileWithMetaStructW ef = { wideUrl, buf2, NULL, dev->getTrackLength( s ) / 1000 };
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&ef, IPC_PLAYFILEW);
+ }
+ if(!enqueue) //play item startPlaybackAt
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startPlaybackAt,IPC_SETPLAYLISTPOS);
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
+ SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play
+ }
+ return true;
+ }
+ if(msgIfImpossible)
+ {
+ wchar_t titleStr[32] = {0};
+ MessageBox(parent,WASABI_API_LNGSTRINGW(IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK),
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNSUPPORTED,titleStr,32),0);
+ }
+ return false;
+}
+
+bool DeviceView::PlayPlaylist(int playlistId, bool enqueue, bool msgIfImpossible, HWND parent)
+{
+ int l = dev->getPlaylistLength(playlistId);
+ C_ItemList tracks;
+ for(int j=0; j<l; j++)
+ tracks.Add((void*)dev->getPlaylistTrack(playlistId,j));
+ return PlayTracks(&tracks, 0, enqueue, msgIfImpossible, parent);
+}
+
+void DeviceView::CopyTracksToHardDrive(C_ItemList * tracks)
+{
+ CopyTracksToHardDrive((songid_t*)tracks->GetAll(),tracks->GetSize());
+}
+
+static void getReverseCopyFilenameFormat(wchar_t* filepath, wchar_t* format, int len, BOOL * uppercaseext)
+{
+ wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music";
+ wchar_t m_def_filename_fmt[MAX_PATH] = L"<Artist> - <Album>\\## - <Trackartist> - <Title>";
+ GetDefaultSaveToFolder(m_def_extract_path);
+ bool cdrip = !!global_config->ReadInt(L"extractusecdrip", 1);
+ const wchar_t *mlinifile = (const wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW);
+
+ wchar_t buf[2048] = {0};
+ if(cdrip) GetPrivateProfileString(L"gen_ml_config",L"extractpath",m_def_extract_path,buf,2048,mlinifile);
+ else lstrcpyn(buf,global_config->ReadString(L"extractpath",m_def_extract_path),2048);
+ lstrcpyn(filepath,buf,len);
+ int l = wcslen(filepath);
+ if(*(filepath+l-1) != L'\\')
+ {
+ *(filepath+l) = L'\\';
+ *(filepath+l+1)=0;
+ l++;
+ }
+ if(cdrip) GetPrivateProfileString(L"gen_ml_config",L"extractfmt2",m_def_filename_fmt,buf,2048,mlinifile);
+ else lstrcpyn(buf,global_config->ReadString(L"extractfmt2",m_def_filename_fmt),2048);
+ if(l < len) lstrcpyn(format/*+l*/,buf,len - l);
+ if(cdrip) *uppercaseext = GetPrivateProfileInt(L"gen_ml_config",L"extractucext",0,mlinifile);
+ else *uppercaseext = global_config->ReadInt(L"extractucext",0);
+}
+
+void DeviceView::CopyTracksToHardDrive(songid_t * tracks, int numTracks)
+{
+ if(!dev->copyToHardDriveSupported()) return;
+ BOOL uppercaseext=FALSE;
+ wchar_t filepath[MAX_PATH] = {0}, format[2048] = {0};
+ getReverseCopyFilenameFormat(filepath,format,2048,&uppercaseext);
+
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for(int i=0; i<numTracks; i++)
+ {
+ AddTrackToTransferQueue(new ReverseCopyInst(this,filepath,format,tracks[i],true,!!uppercaseext));
+ }
+ txQueue->unlock();
+ }
+}
+
+void DeviceView::CopyPlaylistToLibrary(int plnum)
+{
+ if(plnum==0) return;
+ wchar_t name[128] = {0};
+ dev->getPlaylistName(plnum,name,128);
+ wchar_t filename[MAX_PATH] = {0};
+ wchar_t dir[MAX_PATH] = {0};
+
+ GetTempPath(MAX_PATH,dir);
+ GetTempFileName(dir,L"pmppl",0,filename);
+ _wunlink(filename);
+ {
+ wchar_t * ext = wcsrchr(filename,L'.');
+ if(ext) *ext=0;
+ StringCchCat(filename,MAX_PATH,L".m3u");
+ }
+ FILE * f = _wfopen(filename,L"wt"); if(f)
+ {
+ fputws(L"#EXTM3U\n",f);
+ fclose(f);
+ }
+ /*
+ mlMakePlaylistV2 a = {sizeof(mlMakePlaylistV2),name,ML_TYPE_FILENAMES,"\0\0",PL_FLAG_SHOW | PL_FLAG_FILL_FILENAME};
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&a,ML_IPC_PLAYLIST_MAKE);
+ */
+
+ wchar_t filepath[MAX_PATH] = {0}, format[2048] = {0};
+ BOOL uppercaseext=FALSE;
+ getReverseCopyFilenameFormat(filepath,format,2048,&uppercaseext);
+ int l = dev->getPlaylistLength(plnum);
+
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for(int i=0; i<l; i++)
+ AddTrackToTransferQueue(new ReversePlaylistCopyInst(this,filepath,format,dev->getPlaylistTrack(plnum,i),filename,name,i==l-1,true));
+ txQueue->unlock();
+ }
+}
+
+void DeviceView::Unregister()
+{
+ for(size_t i=0; i < playlistTreeItems.size(); i++)
+ {
+ HNAVITEM item = playlistTreeItems[i];
+ // TODO: free memory associated with the text for item
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, item);
+ }
+ playlistTreeItems.clear();
+
+ if (videoTreeItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, videoTreeItem);
+ videoTreeItem=0;
+
+ if (AGAVE_API_DEVICEMANAGER)
+ AGAVE_API_DEVICEMANAGER->DeviceUnregister(name);
+ if (treeItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, treeItem);
+ treeItem=0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/DeviceView.h b/Src/Plugins/Library/ml_pmp/DeviceView.h
new file mode 100644
index 00000000..a930834e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/DeviceView.h
@@ -0,0 +1,257 @@
+#ifndef __DEVICEVIEW_H_
+#define __DEVICEVIEW_H_
+
+//#define _WIN32_WINNT 0x0400
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <shellapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/ipc_pe.h"
+#include "LinkedQueue.h"
+#include "pmp.h"
+#include "resource1.h"
+#include "config.h"
+#include "transfer_thread.h"
+#include "api__ml_pmp.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+
+#include <bfc/platform/types.h>
+#include "../devices/ifc_device.h"
+#include "../devices/ifc_deviceactivity.h"
+#include "pmp.h"
+#include <bfc/multipatch.h>
+#include <vector>
+
+#define COMMITTIMERID 0x2345
+
+extern C_Config * gen_mlconfig;
+extern LinkedQueue cloudTransferQueue, cloudFinishedTransfers;
+extern int cloudTransferProgress;
+extern winampMediaLibraryPlugin plugin;
+
+wchar_t *guessTitles(const wchar_t *filename, int *tracknum,wchar_t **artist, wchar_t **album,wchar_t **title); // free result after using artist, etc
+wchar_t* GetDefaultSaveToFolder(wchar_t* path_to_store);
+
+#define AVERAGEBASIS 3
+class TransferContext
+{
+public:
+ TransferContext() : dev(0), transfer_thread(NULL)
+ {
+ numTransfers=0;
+ start=0;
+ end=0;
+ InitializeCriticalSection(&transfer_lock);
+ killer = CreateEvent(NULL, TRUE, FALSE, 0);
+ notifier = CreateEvent(NULL, FALSE, FALSE, 0);
+ paused = 0;
+ ZeroMemory(&times,sizeof(times));
+ }
+
+ ~TransferContext()
+ {
+ DeleteCriticalSection(&transfer_lock);
+ CloseHandle(killer);
+ CloseHandle(notifier);
+ }
+
+ void WaitForKill()
+ {
+ SetEvent(notifier);
+ WaitForSingleObject(killer, INFINITE);
+ }
+ void DoOneTransfer(HANDLE handle);
+ bool IsPaused();
+ void Pause();
+ void Resume();
+
+ static bool IsAllPaused();
+ static void PauseAll();
+ static void ResumeAll();
+
+ int numTransfers;
+ int times[AVERAGEBASIS];
+ time_t start, end;
+ DeviceView *dev;
+ volatile size_t paused;
+ HANDLE killer, notifier;
+ CRITICAL_SECTION transfer_lock;
+ ThreadID *transfer_thread;
+
+ static volatile size_t paused_all;
+};
+
+enum
+{
+ PATCH_IFC_DEVICE,
+ PATCH_IFC_DEVICEACTIVITY,
+};
+
+class DeviceView : public MultiPatch<PATCH_IFC_DEVICE, ifc_device>,
+ public MultiPatch<PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity>
+{
+public://protected:
+ HNAVITEM treeItem, videoTreeItem, queueTreeItem;
+ int queueActiveIcon;
+ int isCloudDevice;
+ std::vector<HNAVITEM> playlistTreeItems;
+ LinkedQueue finishedTransfers;
+
+ HNAVITEM AddPlaylistNode(int id);
+ int AddTrackToTransferQueue(DeviceView * device, itemRecordW * item, bool dupeCheck, bool forceDupe=false); // true on success
+ int AddTrackToTransferQueue(CopyInst * inst);
+public:
+ TransferContext transferContext;
+ int videoView;
+ int SyncConnectionDefault;
+ prefsDlgRecW devPrefsPage;
+ int currentTransferProgress; // percentage
+ double transferRate;
+ int threadKillswitch;
+ LinkedQueue transferQueue;
+ bool commitNeeded;
+ C_Config * config;
+ Device * dev;
+ int metadata_fields;
+ DeviceView(Device *dev);
+
+ void SetVideoView(BOOL enabled);
+ bool GetTransferFromMlSupported(int dataType);
+ intptr_t TransferFromML(int type,void* data, int unsupportedReturn, int supportedReturn, int playlist=0);
+ intptr_t TransferFromDrop(HDROP hDrop, int playlist=0);
+ intptr_t MessageProc(int message_type, intptr_t param1, intptr_t param2, intptr_t param3);
+ void DevicePropertiesChanges();
+ int CreatePlaylist(wchar_t * name=NULL, bool silent=false);
+ void RenamePlaylist(int id);
+ bool DeletePlaylist(int playlistId, bool deleteFiles, bool verbal);
+ int AddFileListToTransferQueue(char ** files, int num, int playlist=0);
+ int AddFileListToTransferQueue(wchar_t ** files, int num, int playlist=0);
+ int AddItemListToTransferQueue(itemRecordListW * items, int playlist=0);
+ int AddItemListToTransferQueue(C_ItemList * items, int playlist=0);
+ void TransferPlaylist(wchar_t * file, wchar_t * name=NULL); // name=NULL when its from the ML and we must find out ourself...
+ int TransferTracksToPlaylist(C_ItemList *itemRecords, int plid);
+ void TransferAddCloudPlaylist(wchar_t * file, wchar_t * name0);
+ int DeleteTracks(C_ItemList * tracks, HWND centerWindow);
+ void Sync(bool silent = false);
+ void CloudSync(bool silent = false);
+ void Autofill();
+ void Eject();
+ bool PlayTracks(C_ItemList * tracks, int startPlaybackAt, bool enqueue, bool msgIfImpossible, HWND parent=NULL); // returns false if failed/unsupported
+ bool PlayPlaylist(int playlistId, bool enqueue, bool msgIfImpossible, HWND parent=NULL);
+ void CopyTracksToHardDrive(songid_t * tracks, int numTracks);
+ void CopyTracksToHardDrive(C_ItemList * tracks);
+ void CopyPlaylistToLibrary(int plnum);
+ void OnActivityStarted();
+ void OnActivityChanged();
+ void OnActivityFinished();
+ void OnNameChanged(const wchar_t *new_name);
+//private:
+ void RegisterViews(HNAVITEM parent);
+ void Unregister();
+
+ BOOL DisplayDeviceContextMenu(HNAVITEM item, HWND hostWindow, POINT pt);
+ BOOL DisplayPlaylistContextMenu(int playlistId, HNAVITEM item, HWND hostWindow, POINT pt);
+
+// navigation
+ BOOL Navigation_IsPlaylistItem(HNAVITEM item, int *playlistId);
+ HWND Navigation_CreateViewCb(HNAVITEM item, HWND parentWindow);
+ BOOL Navigation_ShowContextMenuCb(HNAVITEM item, HWND hostWindow, POINT pt);
+ BOOL Navigation_ClickCb(HNAVITEM item, int actionType, HWND hostWindow);
+ int Navigation_DropTargetCb(HNAVITEM item, unsigned int dataType, void *data);
+ BOOL Navigation_TitleEditBeginCb(HNAVITEM item);
+ BOOL Navigation_TitleEditEndCb(HNAVITEM item, const wchar_t *title);
+ BOOL Navigation_KeyDownCb(HNAVITEM item, NMTVKEYDOWN *keyData, HWND hwnd);
+
+ size_t GetPlaylistName(wchar_t *buffer, size_t bufferSize, int playlistId, const wchar_t *defaultName, BOOL quoteSpaces);
+
+public:
+ /* ifc_device */
+ int QueryInterface(GUID interface_guid, void **object);
+ const char *GetName();
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height);
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize);
+
+ const char *GetType();
+ const char *GetDisplayType();
+ const char *GetConnection();
+
+ BOOL GetHidden();
+
+ HRESULT GetTotalSpace(uint64_t *size);
+ HRESULT GetUsedSpace(uint64_t *size);
+
+ BOOL GetAttached();
+ HRESULT Attach(HWND hostWindow);
+ HRESULT Detach(HWND hostWindow);
+
+ HRESULT EnumerateCommands(ifc_devicesupportedcommandenum **enumerator, DeviceCommandContext context);
+ HRESULT SendCommand(const char *command, HWND hostWindow, ULONG_PTR param);
+ HRESULT GetCommandFlags(const char *command, DeviceCommandFlags *flags);
+
+ HRESULT GetActivity(ifc_deviceactivity **activity);
+
+ HRESULT Advise(ifc_deviceevent *handler);
+ HRESULT Unadvise(ifc_deviceevent *handler);
+
+ HWND CreateView(HWND parentWindow);
+ void SetNavigationItem(void *navigationItem);
+
+ HRESULT GetDropSupported(unsigned int dataType);
+ HRESULT Drop(void *data, unsigned int dataType);
+
+ HRESULT SetDisplayName(const wchar_t *displayName, bool force);
+
+ HRESULT GetModel(wchar_t *buffer, size_t bufferSize);
+ HRESULT GetStatus(wchar_t *buffer, size_t bufferSize);
+
+ void UpdateActivityState();
+ void UpdateSpaceInfo(BOOL updateUsedSpace, BOOL notifyChanges);
+
+ /* ifc_deviceactivity */
+ BOOL GetActive();
+ BOOL GetCancelable();
+ HRESULT GetProgress(unsigned int *percentCompleted);
+ HRESULT Activity_GetDisplayName(wchar_t *buffer, size_t bufferMax);
+ HRESULT Activity_GetStatus(wchar_t *buffer, size_t bufferMax);
+ HRESULT Cancel(HWND hostWindow);
+
+ /* Dispatchable */
+ size_t AddRef()
+ {
+ return InterlockedIncrement((LONG*)&ref_count);
+ }
+
+ size_t Release()
+ {
+ if (0 == ref_count)
+ return ref_count;
+
+ LONG r = InterlockedDecrement((LONG*)&ref_count);
+ if (0 == r)
+ delete(this);
+
+ return r;
+ }
+
+private:
+ RECVS_MULTIPATCH;
+ std::vector<ifc_deviceevent*> event_handlers;
+ char name[128];
+ size_t ref_count;
+ ~DeviceView();
+ const char *connection_type;
+ const char *display_type;
+ BOOL navigationItemCreated;
+ BOOL activityRunning;
+ unsigned int currentProgress;
+ uint64_t usedSpace;
+ uint64_t totalSpace;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/Filters.cpp b/Src/Plugins/Library/ml_pmp/Filters.cpp
new file mode 100644
index 00000000..19e56861
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/Filters.cpp
@@ -0,0 +1,829 @@
+#include "ArtistAlbumLists.h"
+#include "Filters.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "metadata_utils.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+#define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1;
+
+extern int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+
+#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
+static wchar_t GetIndex(wchar_t *name) {
+ SKIP_THE_AND_WHITESPACE(name);
+ return towupper(name[0]);
+}
+
+void Filter::addToGroup(Device *dev, songid_t song, FilterItem * group) {
+ group->length += dev->getTrackLength(song);
+ group->size += dev->getTrackSize(song);
+ group->numTracks++;
+
+ wchar_t buf[16] = {0};
+ dev->getTrackExtraInfo(song, L"cloud", buf, 16);
+ int status = _wtoi(buf);
+ // local and to be uploaded (4) are the same here
+ if (status == 4)
+ {
+ status = 0;
+ }
+
+ group->cloudState += status;
+}
+
+void Filter::AddDefaultColumns(Device * dev, C_ItemList * fields, C_Config * config, int columnStart, bool cloud) {
+ if(nextFilter) fields->Add(new ListField(columnStart+40, 50 ,nextFilter->namePlural, config));
+ fields->Add(new ListField(columnStart+41, 50 , WASABI_API_LNGSTRINGW(IDS_TRACKS), config));
+ if (cloud) fields->Add(new ListField(columnStart+52, 27, WASABI_API_LNGSTRINGW(IDS_CLOUD), config));
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if (!fieldsBits) fieldsBits = -1;
+ if (fieldsBits & SUPPORTS_SIZE) fields->Add(new ListField(columnStart+50, 50, WASABI_API_LNGSTRINGW(IDS_SIZE), config, true));
+ if (fieldsBits & SUPPORTS_LENGTH) fields->Add(new ListField(columnStart+51, 50 ,WASABI_API_LNGSTRINGW(IDS_LENGTH), config, true));
+}
+
+void FilterItem::GetDefaultColumnsCellText(int col, wchar_t*buf, int buflen) {
+ switch (col)
+ {
+ case 40: wsprintf(buf, L"%d", numNextFilter); break;
+ case 41: wsprintf(buf, L"%d", (independentTracks ? independentTracks : numTracks)); break;
+ case 50: WASABI_API_LNG->FormattedSizeString(buf, buflen, (independentSize ? independentSize : size)); break;
+ case 51:
+ {
+ __int64 l = (independentLength ? independentLength : length) / 1000;
+ int x = (int)l / 60;
+ int y = (int)l % 60;
+ wsprintf(buf, L"%d:%02d", x, y);
+ }
+ break;
+ case 52:
+ {
+ int state = (independentCloudState ? independentCloudState : cloudState);
+ if (state % (independentTracks ? independentTracks : numTracks))
+ {
+ wsprintf(buf, L"%d", 2);
+ }
+ else
+ {
+ wsprintf(buf, L"%d", state / (independentTracks ? independentTracks : numTracks));
+ }
+ }
+ break;
+ }
+}
+
+int FilterItem::DefaultSortAction(FilterItem *that, int use_by, int use_dir) {
+ int v = 0;
+ if(use_by==50) { RETIFNZ(this->size - that->size); }
+ if(use_by==51) { RETIFNZ(this->length - that->length); }
+ if(use_by==52) {
+ if (numTracks > 0)
+ {
+ int state = (independentCloudState ? independentCloudState : cloudState);
+ int thatState = (that->independentCloudState ? that->independentCloudState : that->cloudState);
+ if (state % (independentTracks ? independentTracks : numTracks))
+ {
+ if (thatState % (that->independentTracks ? that->independentTracks : that->numTracks))
+ {
+ v = 0;
+ }
+ else
+ {
+ v = (2) - (thatState / (that->independentTracks ? that->independentTracks : that->numNextFilter));
+ }
+ }
+ else
+ {
+ if (thatState % (that->independentTracks ? that->independentTracks : that->numTracks))
+ {
+ v = (state / (independentTracks ? independentTracks : numTracks)) - (2);
+ }
+ else
+ {
+ v = (state / (independentTracks ? independentTracks : numTracks)) - (thatState / (that->independentTracks ? that->independentTracks : that->numNextFilter));
+ }
+ }
+ }
+ RETIFNZ(v);
+ }
+ if(use_by==40) {
+ v=this->numNextFilter - that->numNextFilter;
+ RETIFNZ(v)
+ }
+ if(use_by==41) {
+ if(this->independentTracks || that->independentTracks) v = this->independentTracks - that->independentTracks;
+ else v=this->numTracks - that->numTracks;
+ RETIFNZ(v)
+ }
+ return 0;
+}
+
+FilterItem::~FilterItem() {
+ if(independentNextFilter) delete independentNextFilter; independentNextFilter=0;
+ if(nextFilter) delete nextFilter; nextFilter=0;
+}
+
+class ArtistFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-100,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 100: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_ARTIST),buflen); break;
+ default: GetDefaultColumnsCellText(col-100,buf,buflen); break;
+ }
+ }
+ };
+ ArtistFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTISTS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(100, 170,WASABI_API_LNGSTRINGW(IDS_ARTIST),config));
+ AddDefaultColumns(dev,fields,config,100,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackArtist(a,astr,256);
+ dev->getTrackArtist(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackArtist(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ //ArtistFI * group = (ArtistFI*)group0;
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackArtist(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumFilter : public Filter {
+public:
+ class AlbumFI : public FilterItem {
+ public:
+ AlbumFI(wchar_t *name0) : FilterItem(), yearHigh(0), yearLow(0) { name = _wcsdup(name0); }
+ virtual ~AlbumFI() { free(name); }
+ wchar_t *name;
+ int yearHigh, yearLow;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((AlbumFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-200,use_dir); if(v) return v; }
+ AlbumFI *that = (AlbumFI*)that0;
+ int v;
+ if(use_by==201) { //year
+ v=this->yearHigh - that->yearHigh;
+ RETIFNZ(v)
+ }
+ //by name
+ v = STRCMP_NULLOK(this->name,that->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 200: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_ALBUM),buflen); break;
+ case 201:
+ if(yearHigh>0 && yearLow>0) {
+ if(yearHigh == yearLow) wsprintf(buf,L"%d",yearHigh);
+ else if(yearHigh/100 == yearLow/100) wsprintf(buf,L"%d-%02d",yearLow,yearHigh%100);
+ else wsprintf(buf,L"%d-%d",yearLow,yearHigh);
+ }
+ break;
+ default: GetDefaultColumnsCellText(col-200,buf,buflen); break;
+ }
+ }
+ };
+
+ AlbumFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUMS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ fields->Add(new ListField(200, 170,WASABI_API_LNGSTRINGW(IDS_ALBUM),config));
+ if(!fieldsBits || (fieldsBits & SUPPORTS_YEAR))
+ fields->Add(new ListField(201, 50 ,WASABI_API_LNGSTRINGW(IDS_YEAR),config));
+ AddDefaultColumns(dev,fields,config,200,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbum(a,astr,256);
+ dev->getTrackAlbum(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbum(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ Filter::addToGroup(dev,song,group0);
+ int y = dev->getTrackYear(song);
+ if(y>0) {
+ if(y > group->yearHigh || group->yearHigh<=0) group->yearHigh=y;
+ if(y < group->yearLow || group->yearLow<=0) group->yearLow=y;
+ }
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbum(song,buf,256);
+ AlbumFI * group = new AlbumFI(buf);
+ group->yearHigh = group->yearLow = dev->getTrackYear(song);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+
+class GenreFilter : public Filter {
+public:
+ class GenreFI : public FilterItem {
+ public:
+ GenreFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~GenreFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((GenreFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-300,use_dir); if(v) return v; }
+ GenreFI *a=this,*b = (GenreFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 300: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_GENRE),buflen); break;
+ default: GetDefaultColumnsCellText(col-300,buf,buflen); break;
+ }
+ }
+ };
+ GenreFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_GENRE));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_GENRES)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(300, 170,WASABI_API_LNGSTRINGW(IDS_GENRE),config));
+ AddDefaultColumns(dev,fields,config,300,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackGenre(a,astr,256);
+ dev->getTrackGenre(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ GenreFI * group = (GenreFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackGenre(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ //GenreFI * group = (GenreFI*)group0;
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackGenre(song,buf,256);
+ GenreFI * group = new GenreFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class ArtistIndexFilter : public Filter {
+public:
+ class ArtistIndexFI : public FilterItem {
+ public:
+ ArtistIndexFI(wchar_t *name0) : FilterItem() { name = GetIndex(name0); }
+ virtual ~ArtistIndexFI() { }
+ wchar_t name;
+ virtual bool isWithoutGroup() { return !name; }
+ virtual int compareTo(FilterItem *that) { return name - ((ArtistIndexFI*)that)->name; }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-400,use_dir); if(v) return v; }
+ ArtistIndexFI *that = (ArtistIndexFI*)that0;
+ int v;
+ //by name
+ v = this->name - that->name;
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 400:
+ {
+ if(name) { buf[0] = name; buf[1]=0; }
+ else WASABI_API_LNGSTRINGW_BUF(IDS_NO_ARTIST,buf,buflen);
+ }
+ break;
+ default: GetDefaultColumnsCellText(col-400,buf,buflen); break;
+ }
+ }
+ };
+
+ ArtistIndexFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEX));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEXES)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(400, 170,WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEX),config));
+ AddDefaultColumns(dev,fields,config,400,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackArtist(a,astr,256);
+ dev->getTrackArtist(b,bstr,256);
+ return GetIndex(astr) - GetIndex(bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistIndexFI * group = (ArtistIndexFI*)group0;
+ wchar_t astr[256]=L"";
+ dev->getTrackArtist(song,astr,256);
+ return GetIndex(astr) == group->name;
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackArtist(song,buf,256);
+ ArtistIndexFI * group = new ArtistIndexFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumArtistIndexFilter : public Filter {
+public:
+ class ArtistIndexFI : public FilterItem {
+ public:
+ ArtistIndexFI(wchar_t *name0) : FilterItem() { name = GetIndex(name0); }
+ virtual ~ArtistIndexFI() { }
+ wchar_t name;
+ virtual bool isWithoutGroup() { return !name; }
+ virtual int compareTo(FilterItem *that) { return name - ((ArtistIndexFI*)that)->name; }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-900,use_dir); if(v) return v; }
+ ArtistIndexFI *that = (ArtistIndexFI*)that0;
+ int v;
+ //by name
+ v = this->name - that->name;
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 900:
+ {
+ if(name) { buf[0] = name; buf[1]=0; }
+ else WASABI_API_LNGSTRINGW_BUF(IDS_NO_ALBUM_ARTIST,buf,buflen);
+ }
+ break;
+ default: GetDefaultColumnsCellText(col-900,buf,buflen); break;
+ }
+ }
+ };
+
+ AlbumArtistIndexFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST_INDEX));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ARTIST_INDEXES)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(900, 170,WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST_INDEX),config));
+ AddDefaultColumns(dev,fields,config,900,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbumArtist(a,astr,256);
+ dev->getTrackAlbumArtist(b,bstr,256);
+ return GetIndex(astr) - GetIndex(bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistIndexFI * group = (ArtistIndexFI*)group0;
+ wchar_t astr[256]=L"";
+ dev->getTrackAlbumArtist(song,astr,256);
+ return GetIndex(astr) == group->name;
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbumArtist(song,buf,256);
+ ArtistIndexFI * group = new ArtistIndexFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class YearFilter : public Filter {
+public:
+ class YearFI : public FilterItem {
+ public:
+ int year;
+ YearFI(int _year) : FilterItem(), year(_year) { if(year<0) year=0; }
+ virtual ~YearFI() { }
+ virtual bool isWithoutGroup() { return year<=0; }
+ virtual int compareTo(FilterItem *that) { return year - ((YearFI*)that)->year; }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-500,use_dir); if(v) return v; }
+ YearFI *a=this,*b = (YearFI*)that0;
+ int v;
+ v = a->year - b->year;
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 500: if(year>0) wsprintf(buf,L"%d",year); else WASABI_API_LNGSTRINGW_BUF(IDS_NO_YEAR,buf,buflen); break;
+ default: GetDefaultColumnsCellText(col-500,buf,buflen); break;
+ }
+ }
+ };
+ YearFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_YEAR));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_YEARS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(500, 170,WASABI_API_LNGSTRINGW(IDS_YEAR),config));
+ AddDefaultColumns(dev,fields,config,500,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ int w = dev->getTrackYear(a);
+ int e = dev->getTrackYear(b);
+ if(w<=0) w=0;
+ if(e<=0) e=0;
+ return w - e;
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ YearFI * group = (YearFI*)group0;
+ int y = dev->getTrackYear(song);
+ if(group->isWithoutGroup() && y<=0) return true;
+ return group->year == y;
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ YearFI * group = new YearFI(dev->getTrackYear(song));
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumArtistFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-600,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 600: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_ALBUM_ARTIST),buflen); break;
+ default: GetDefaultColumnsCellText(col-600,buf,buflen); break;
+ }
+ }
+ };
+ AlbumArtistFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTISTS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(600, 170,WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST),config));
+ AddDefaultColumns(dev,fields,config,600,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbumArtist(a,astr,256);
+ dev->getTrackAlbumArtist(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbumArtist(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackAlbumArtist(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class PublisherFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-700,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 700: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_PUBLISHER),buflen); break;
+ default: GetDefaultColumnsCellText(col-700,buf,buflen); break;
+ }
+ }
+ };
+ PublisherFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_PUBLISHER));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_PUBLISHERS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(700, 170,WASABI_API_LNGSTRINGW(IDS_PUBLISHER),config));
+ AddDefaultColumns(dev,fields,config,700,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackPublisher(a,astr,256);
+ dev->getTrackPublisher(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackPublisher(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackPublisher(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class ComposerFilter : public Filter {
+public:
+ class ArtistFI : public FilterItem {
+ public:
+ ArtistFI(wchar_t *name0) : FilterItem() { name = _wcsdup(name0); }
+ virtual ~ArtistFI() { free(name); }
+ wchar_t *name;
+ virtual bool isWithoutGroup() { return !name[0]; }
+ virtual int compareTo(FilterItem *that) { return STRCMP_NULLOK(this->name,((ArtistFI*)that)->name); }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-800,use_dir); if(v) return v; }
+ ArtistFI *a=this,*b = (ArtistFI*)that0;
+ int v;
+ v = STRCMP_NULLOK(a->name,b->name);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 800: lstrcpyn(buf,name[0]?name:WASABI_API_LNGSTRINGW(IDS_NO_COMPOSER),buflen); break;
+ default: GetDefaultColumnsCellText(col-800,buf,buflen); break;
+ }
+ }
+ };
+ ComposerFilter() : Filter() { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_COMPOSER));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_COMPOSERS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ fields->Add(new ListField(800, 170,WASABI_API_LNGSTRINGW(IDS_COMPOSER),config));
+ AddDefaultColumns(dev,fields,config,800,cloud);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackComposer(a,astr,256);
+ dev->getTrackComposer(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ ArtistFI * group = (ArtistFI*)group0;
+ wchar_t buf[256]=L"";
+ dev->getTrackComposer(song,buf,256);
+ return !STRCMP_NULLOK(buf,group->name);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ Filter::addToGroup(dev,song,group0);
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t buf[256]=L"";
+ dev->getTrackComposer(song,buf,256);
+ ArtistFI * group = new ArtistFI(buf);
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+};
+
+class AlbumArtFilter : public Filter {
+public:
+ class AlbumFI : public FilterItem {
+ public:
+ AlbumFI(Device* dev, wchar_t *artist0, wchar_t *album0, int year, wchar_t *genre0, int rating0, pmpart_t art) :
+ FilterItem(), dev(dev), yearHigh(year), yearLow(year), art(art)
+ {
+ artist = _wcsdup(artist0); album = _wcsdup(album0); genre = _wcsdup(genre0);
+ if(rating0 > 0) rating = rating0;
+ }
+ virtual ~AlbumFI() { free(artist); free(album); free(genre); if(art) dev->releaseArt(art); }
+ Device *dev;
+ wchar_t *artist;
+ wchar_t *album;
+ int yearHigh, yearLow;
+ wchar_t *genre;
+ int rating;
+ pmpart_t art;
+ virtual bool isWithoutGroup() { return !artist[0] && !album[0]; }
+ virtual int compareTo(FilterItem *that) {
+ int x = STRCMP_NULLOK(this->artist,((AlbumFI*)that)->artist);
+ if(x) return x;
+ return STRCMP_NULLOK(this->album,((AlbumFI*)that)->album);
+ }
+ virtual int compareTo2(FilterItem *that0, int use_by, int use_dir) {
+ {int v=DefaultSortAction(that0,use_by-1000,use_dir); if(v) return v; }
+ AlbumFI *that = (AlbumFI*)that0;
+ __int64 v;
+ if(use_by==1086) {
+ v=this->size - that->size;
+ RETIFNZ(v)
+ }
+ if(use_by==1085) {
+ v=this->length - that->length;
+ RETIFNZ(v)
+ }
+ if(use_by==1084) {
+ v=this->rating - that->rating;
+ RETIFNZ(v)
+ }
+ if(use_by==1083) {
+ v=STRCMP_NULLOK(this->genre,that->genre);
+ RETIFNZ(v)
+ }
+ if(use_by==1082) { //year
+ v=this->yearHigh - that->yearHigh;
+ RETIFNZ(v)
+ }
+ if(use_by==1001) {
+ v = STRCMP_NULLOK(this->album,that->album);
+ RETIFNZ(v)
+ }
+ //by name
+ v = STRCMP_NULLOK(this->artist,that->artist);
+ RETIFNZ(v)
+ return 0;
+ }
+ virtual void GetCellText(int col, wchar_t * buf, int buflen) {
+ switch(col) {
+ case 1000: lstrcpyn(buf,artist[0]?artist:WASABI_API_LNGSTRINGW(IDS_NO_ARTIST),buflen); break;
+ case 1001: lstrcpyn(buf,album[0]?album:WASABI_API_LNGSTRINGW(IDS_NO_ALBUM),buflen); break;
+ case 1082:
+ if(yearHigh>0 && yearLow>0) {
+ if(yearHigh == yearLow) wsprintf(buf,L"%d",yearHigh);
+ else if(yearHigh/100 == yearLow/100) wsprintf(buf,L"%d-%02d",yearLow,yearHigh%100);
+ else wsprintf(buf,L"%d-%d",yearLow,yearHigh);
+ }
+ break;
+ case 1083: lstrcpyn(buf,genre[0]?genre:WASABI_API_LNGSTRINGW(IDS_NO_GENRE),buflen); break;
+ case 1084:
+ if(numTracks > 0) {
+ wchar_t r[] = L"\u2605\u2605\u2605\u2605\u2605";
+ double d = double(rating) / double(numTracks);
+ int rat = int(d);
+ if(d - double(rat) >= 0.5 && rat<5) rat++;
+ if(rat>0 && rat<=5) r[rat]=0;
+ else r[0]=0;
+ lstrcpyn(buf,r,buflen);
+ }
+ break;
+ case 1085: {__int64 l=length/1000; int x = (int)l/60; int y = (int)l%60; wsprintf(buf,L"%d:%02d",x,y); } break;
+ case 1086: WASABI_API_LNG->FormattedSizeString(buf, buflen, size); break;
+ default: GetDefaultColumnsCellText(col-1000,buf,buflen); break;
+ }
+ }
+ virtual pmpart_t GetArt() {return art;}
+ };
+ int mode;
+ AlbumArtFilter() : Filter(),mode(0) { name=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUM));
+ namePlural=_wcsdup(WASABI_API_LNGSTRINGW(IDS_ALBUMS)); }
+ virtual void AddColumns(Device * dev, C_ItemList * fields,C_Config * config, bool cloud) {
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits=-1;
+ fields->Add(new ListField(1000, 90,WASABI_API_LNGSTRINGW(IDS_ARTIST),config,0));
+ fields->Add(new ListField(1001, 90,WASABI_API_LNGSTRINGW(IDS_ALBUM),config,0));
+ if(fieldsBits & SUPPORTS_YEAR) fields->Add(new ListField(1082, 50 ,WASABI_API_LNGSTRINGW(IDS_YEAR),config,0));
+ if(fieldsBits & SUPPORTS_GENRE) fields->Add(new ListField(1083, 50 ,WASABI_API_LNGSTRINGW(IDS_GENRE),config,!(mode==1 || mode==2)));
+ if(fieldsBits & SUPPORTS_RATING) fields->Add(new ListField(1084, 50 ,WASABI_API_LNGSTRINGW(IDS_RATING),config,!(mode==1 || mode==2)));
+ if(fieldsBits & SUPPORTS_LENGTH) fields->Add(new ListField(1085, 50 ,WASABI_API_LNGSTRINGW(IDS_LENGTH),config,mode!=2));
+ if(fieldsBits & SUPPORTS_SIZE) fields->Add(new ListField(1086, 50 ,WASABI_API_LNGSTRINGW(IDS_SIZE),config,mode!=2));
+ fields->Add(new ListField(1041, 50 ,WASABI_API_LNGSTRINGW(IDS_TRACKS),config));
+ //AddDefaultColumns(dev,fields,config,1000);
+ }
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b) {
+ wchar_t astr[256]=L"", bstr[256]=L"";
+ dev->getTrackAlbumArtist(a,astr,256);
+ if(!astr[0]) dev->getTrackArtist(a,astr,256);
+ dev->getTrackAlbumArtist(b,bstr,256);
+ if(!bstr[0]) dev->getTrackArtist(b,bstr,256);
+ int x = STRCMP_NULLOK(astr,bstr);
+ if(x) return x;
+ dev->getTrackAlbum(a,astr,256);
+ dev->getTrackAlbum(b,bstr,256);
+ return STRCMP_NULLOK(astr,bstr);
+ }
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ wchar_t bufa[256]=L"",bufb[256]=L"";
+ dev->getTrackAlbumArtist(song,bufa,256);
+ if(!bufa[0]) dev->getTrackArtist(song,bufa,256);
+ dev->getTrackAlbum(song,bufb,256);
+ return !STRCMP_NULLOK(bufa,group->artist) && !STRCMP_NULLOK(bufb,group->album);
+ }
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group0) {
+ AlbumFI * group = (AlbumFI*)group0;
+ Filter::addToGroup(dev,song,group0);
+ int y = dev->getTrackYear(song);
+ if(y>0) {
+ if(y > group->yearHigh || group->yearHigh<=0) group->yearHigh=y;
+ if(y < group->yearLow || group->yearLow<=0) group->yearLow=y;
+ }
+ if(!group->art) group->art = dev->getArt(song);
+ else {
+ int w=0,h=0;
+ dev->getArtNaturalSize(group->art,&w,&h);
+ pmpart_t newart = dev->getArt(song);
+ int nw=0,nh=0;
+ dev->getArtNaturalSize(newart,&nw,&nh);
+ if(nw > w && nh > h) {
+ dev->releaseArt(group->art);
+ group->art = newart;
+ } else dev->releaseArt(newart);
+ }
+ int r = dev->getTrackRating(song);
+ if(r > 0) group->rating+=r;
+ }
+ virtual FilterItem * newGroup(Device *dev, songid_t song) {
+ wchar_t bufa[256]=L"";
+ wchar_t bufb[256]=L"";
+ wchar_t bufc[256]=L"";
+ dev->getTrackAlbumArtist(song,bufa,256);
+ if(!bufa[0]) dev->getTrackArtist(song,bufa,256);
+ dev->getTrackAlbum(song,bufb,256);
+ dev->getTrackGenre(song,bufc,256);
+ AlbumFI * group = new AlbumFI(dev,bufa,bufb,dev->getTrackYear(song),bufc,dev->getTrackRating(song),dev->getArt(song));
+ Filter::addToGroup(dev,song,group);
+ return group;
+ }
+ virtual bool HaveTopItem() {return false;}
+ virtual void SetMode(int m) {mode=m;}
+};
+
+Filter * getFilter(wchar_t *name) {
+ if(name) {
+ if(!_wcsicmp(L"Artist",name)) return new ArtistFilter(); // 100
+ if(!_wcsicmp(L"Album",name)) return new AlbumFilter(); //200
+ if(!_wcsicmp(L"Genre",name)) return new GenreFilter(); //300
+ if(!_wcsicmp(L"Artist Index",name)) return new ArtistIndexFilter(); //400
+ if(!_wcsicmp(L"Year",name)) return new YearFilter(); //500
+ if(!_wcsicmp(L"Album Artist",name)) return new AlbumArtistFilter(); //600
+ if(!_wcsicmp(L"Publisher",name)) return new PublisherFilter(); //700
+ if(!_wcsicmp(L"Composer",name)) return new ComposerFilter(); //800
+ if(!_wcsicmp(L"Album Artist Index",name)) return new AlbumArtistIndexFilter(); //900
+ if(!_wcsicmp(L"Album Art",name)) return new AlbumArtFilter(); //1000
+ }
+ return new ArtistFilter();
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/Filters.h b/Src/Plugins/Library/ml_pmp/Filters.h
new file mode 100644
index 00000000..c6be1f54
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/Filters.h
@@ -0,0 +1,58 @@
+#ifndef _FILTERS_H
+#define _FILTERS_H
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <time.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "pmp.h"
+#include "SkinnedListView.h"
+#include "config.h"
+
+class FilterItem {
+public:
+ FilterItem() : numTracks(0), independentTracks(0), nextFilter(0), independentNextFilter(0),
+ numNextFilter(0), size(0), length(0), cloudState(0), independentCloudState(0),
+ independentSize(0), independentLength(0) {}
+ virtual ~FilterItem();
+ int numTracks, cloudState;
+ __int64 size, length;
+ int independentTracks, independentCloudState;
+ __int64 independentSize, independentLength;
+ C_ItemList * independentNextFilter;
+ C_ItemList * nextFilter;
+ int numNextFilter;
+ virtual int compareTo(FilterItem *that)=0;
+ virtual int compareTo2(FilterItem *that, int use_by, int use_dir)=0;
+ virtual void GetCellText(int col, wchar_t * buf, int buflen)=0;
+ void GetDefaultColumnsCellText(int col, wchar_t*buf, int buflen);
+ int DefaultSortAction(FilterItem *that, int use_by, int use_dir);
+ virtual bool isWithoutGroup()=0;
+ virtual pmpart_t GetArt(){return NULL;}
+};
+
+class Filter {
+public:
+ Filter() : nextFilter(0), namePlural(0), name(0) {}
+ virtual ~Filter() { free(name); free(namePlural); }
+ virtual void AddColumns(Device * dev,C_ItemList * fields,C_Config * config, bool cloud = false)=0;
+ void AddDefaultColumns(Device * dev,C_ItemList * fields,C_Config * config, int columnStart, bool cloud = false);
+ virtual int sortFunc(Device * dev, songid_t a, songid_t b)=0;
+ virtual bool isInGroup(Device *dev, songid_t song, FilterItem * group)=0;
+ virtual void addToGroup(Device *dev, songid_t song, FilterItem * group);
+ virtual FilterItem * newGroup(Device *dev, songid_t song)=0;
+ virtual bool HaveTopItem(){return true;}
+ virtual void SetMode(int mode){}
+ Filter * nextFilter;
+ wchar_t * namePlural;
+ wchar_t * name;
+};
+
+#endif //_FILTERS_H \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/IconStore.cpp b/Src/Plugins/Library/ml_pmp/IconStore.cpp
new file mode 100644
index 00000000..c5ef3eea
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/IconStore.cpp
@@ -0,0 +1,237 @@
+#include "IconStore.h"
+#include "resource1.h"
+#include "..\..\General\gen_ml/ml.h"
+
+extern winampMediaLibraryPlugin plugin;
+IconStore icon_store;
+
+static int IconStore_RegisterBitmap(HMLIMGLST imageList, int index, HINSTANCE module, const wchar_t *iconName)
+{
+ MLIMAGESOURCE imageSource;
+ MLIMAGELISTITEM listItem;
+
+ imageSource.cbSize = sizeof(imageSource);
+ imageSource.hInst = module;
+ imageSource.lpszName = iconName;
+ imageSource.type = SRC_TYPE_PNG;
+ imageSource.bpp = 32;
+ imageSource.flags = ISF_FORCE_BPP;
+
+ if (NULL == module && FALSE == IS_INTRESOURCE(iconName))
+ imageSource.flags |= ISF_LOADFROMFILE;
+
+ listItem.cbSize = sizeof(listItem);
+ listItem.hmlil = imageList;
+ listItem.filterUID = MLIF_FILTER3_UID;
+ listItem.pmlImgSource = &imageSource;
+ listItem.mlilIndex = index;
+
+ if (listItem.mlilIndex >= 0)
+ {
+ if (FALSE == MLImageList_Replace(plugin.hwndLibraryParent, &listItem))
+ return -1;
+ return listItem.mlilIndex;
+ }
+
+ return MLImageList_Add(plugin.hwndLibraryParent, &listItem);
+}
+
+static BOOL IconStore_IsResourceNameEqual(const wchar_t *name1, const wchar_t *name2)
+{
+ if (FALSE != IS_INTRESOURCE(name1) || FALSE != IS_INTRESOURCE(name2))
+ {
+ return (name1 == name2);
+ }
+
+ return (CSTR_EQUAL == CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT),
+ NORM_IGNORECASE, name1, -1, name2, -1));
+}
+
+static void IconStore_FreeResourceName(wchar_t *name)
+{
+ if (FALSE == IS_INTRESOURCE(name))
+ free(name);
+}
+
+IconStore::IconStore()
+{
+ playlist_icon_index = -1;
+ video_icon_index = -1;
+ device_icon_index = -1;
+ for (int i = 0; i < 4; i++)
+ {
+ active_queue_icon[i] = queue_icon_index[i] = 0;
+ }
+}
+
+IconStore::~IconStore()
+{
+ size_t index;
+
+ index = iconList.size();
+ while(index--)
+ {
+ IconStore_FreeResourceName(iconList[index].name);
+ }
+}
+
+int IconStore::GetPlaylistIcon()
+{
+ if (-1 == playlist_icon_index)
+ {
+ playlist_icon_index = IconStore_RegisterBitmap(MLNavCtrl_GetImageList(plugin.hwndLibraryParent), -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDR_PLAYLIST_ICON));
+ }
+
+ return playlist_icon_index;
+}
+
+int IconStore::GetVideoIcon()
+{
+ if (-1 == video_icon_index)
+ {
+ MLIMAGELISTTAG imageTag;
+
+ imageTag.hmlil = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ imageTag.nTag = 102; // video node image tag registered by ml_local
+
+ if (FALSE != MLImageList_GetIndexFromTag(plugin.hwndLibraryParent, &imageTag))
+ video_icon_index = imageTag.mlilIndex;
+ else
+ video_icon_index = IconStore_RegisterBitmap(imageTag.hmlil, -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDR_VIDEO_ICON));
+ }
+
+ return video_icon_index;
+}
+
+int IconStore::GetDeviceIcon()
+{
+ if (-1 == device_icon_index)
+ {
+ HMLIMGLST imageList;
+
+ imageList = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ if (NULL != imageList)
+ {
+ device_icon_index = IconStore_RegisterBitmap(imageList, -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDR_DEVICE_ICON));
+ }
+ }
+
+ return device_icon_index;
+}
+
+int IconStore::GetQueueIcon(int iconIndex)
+{
+ if (!queue_icon_index[iconIndex])
+ {
+ HMLIMGLST imageList;
+
+ imageList = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ if (NULL != imageList)
+ {
+ queue_icon_index[iconIndex] = IconStore_RegisterBitmap(imageList, -1,
+ plugin.hDllInstance, MAKEINTRESOURCE(IDB_XFER_QUEUE_16 + iconIndex));
+ }
+ }
+
+ return queue_icon_index[iconIndex];
+}
+
+void IconStore::ReleaseResourceIcon(int iconIndex)
+{
+ if (-1 == iconIndex)
+ return;
+
+ size_t index = iconList.size();
+ while(index--)
+ {
+ ResourceIcon *icon = &iconList[index];
+ if (icon->index == iconIndex)
+ {
+ if (0 != icon->ref)
+ {
+ icon->ref--;
+ if (0 == icon->ref)
+ {
+ IconStore_FreeResourceName(icon->name);
+ icon->name = NULL;
+ icon->module = NULL;
+ }
+ }
+ break;
+ }
+ }
+}
+
+int IconStore::GetResourceIcon(HINSTANCE module, const wchar_t *name)
+{
+ ResourceIcon *icon;
+ size_t index;
+
+ if (module == plugin.hDllInstance &&
+ FALSE != IS_INTRESOURCE(name) &&
+ name == MAKEINTRESOURCE(IDR_DEVICE_ICON))
+ {
+ return GetDeviceIcon();
+ }
+
+ index = iconList.size();
+ while(index--)
+ {
+ icon = &iconList[index];
+ if (icon->module == module &&
+ FALSE != IconStore_IsResourceNameEqual(icon->name, name))
+ {
+ icon->ref++;
+ return icon->index;
+ }
+ }
+
+ return RegisterResourceIcon(module, name);
+}
+
+int IconStore::RegisterResourceIcon(HINSTANCE module, const wchar_t *name)
+{
+ ResourceIcon *icon, iconData;
+ HMLIMGLST imageList;
+ size_t index;
+
+ imageList = MLNavCtrl_GetImageList(plugin.hwndLibraryParent);
+ if (NULL == imageList)
+ return -1;
+
+ index = iconList.size();
+ while(index--)
+ {
+ icon = &iconList[index];
+ if (0 == icon->ref)
+ {
+ break;
+ }
+ }
+ if ((size_t)-1 == index)
+ {
+ icon = &iconData;
+ icon->index = -1;
+ }
+
+ if (FALSE != IS_INTRESOURCE(name))
+ icon->name = (wchar_t*)name;
+ else
+ {
+ icon->name = _wcsdup(name);
+ if (NULL == icon->name)
+ return -1;
+ }
+
+ icon->ref = 1;
+ icon->module = module;
+ icon->index = IconStore_RegisterBitmap(imageList, icon->index, icon->module, icon->name);
+
+ if (-1 != icon->index && &iconData == icon)
+ iconList.push_back(iconData);
+
+ return icon->index;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/IconStore.h b/Src/Plugins/Library/ml_pmp/IconStore.h
new file mode 100644
index 00000000..4e3b379f
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/IconStore.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include <vector>
+
+class IconStore
+{
+public:
+ IconStore();
+ ~IconStore();
+
+ int GetPlaylistIcon();
+ int GetVideoIcon();
+ int GetDeviceIcon();
+ int GetQueueIcon(int iconIndex = 0);
+ int GetResourceIcon(HINSTANCE module, const wchar_t *name);
+ void ReleaseResourceIcon(int iconIndex);
+
+private:
+ int RegisterResourceIcon(HINSTANCE module, const wchar_t *name);
+
+private:
+ typedef struct ResourceIcon
+ {
+ size_t ref;
+ int index;
+ wchar_t *name;
+ HINSTANCE module;
+ } ResourceIcon;
+
+ int playlist_icon_index;
+ int video_icon_index;
+ int device_icon_index;
+ int queue_icon_index[4];
+ int active_queue_icon[4];
+
+ std::vector<ResourceIcon> iconList;
+};
+
+extern IconStore icon_store; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/LinkedQueue.cpp b/Src/Plugins/Library/ml_pmp/LinkedQueue.cpp
new file mode 100644
index 00000000..17e9251f
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/LinkedQueue.cpp
@@ -0,0 +1,125 @@
+#include "LinkedQueue.h"
+
+
+LinkedQueue::LinkedQueue() {
+ size=0;
+ head=NULL;
+ tail=NULL;
+ bm=NULL;
+ bmpos=0;
+ InitializeCriticalSection(&cs);
+}
+
+void LinkedQueue::lock() {
+ EnterCriticalSection(&cs);
+ //wchar_t buf[100]; wsprintf(buf,L"Lock taken by %x",GetCurrentThreadId()); OutputDebugString(buf);
+}
+void LinkedQueue::unlock() {
+ LeaveCriticalSection(&cs);
+ //wchar_t buf[100]; wsprintf(buf,L"Lock released by %x",GetCurrentThreadId()); OutputDebugString(buf);
+}
+
+LinkedQueue::~LinkedQueue() {
+ lock();
+ QueueElement * q=head;
+ while(q) { QueueElement *p=q; q=q->next; delete p; }
+ unlock();
+ DeleteCriticalSection(&cs);
+}
+
+void LinkedQueue::Offer(void * e) {
+ lock();
+ if(size==0) { size++; head=tail=new QueueElement(e); unlock(); return; }
+ tail->next=new QueueElement(e);
+ tail->next->prev=tail;
+ tail=tail->next;
+ size++;
+ bm=NULL;
+ unlock();
+}
+
+void * LinkedQueue::Poll() {
+ lock();
+ if(size == 0) { unlock(); return NULL; }
+ size--;
+ void * r = head->elem;
+ QueueElement * q = head;
+ head=head->next;
+ if(head!=NULL) head->prev=NULL;
+ else tail=NULL;
+ delete q;
+ bm=NULL;
+ unlock();
+ return r;
+}
+
+void * LinkedQueue::Peek() {
+ lock();
+ void * ret=head?head->elem:NULL;
+ unlock();
+ return ret;
+}
+
+QueueElement * LinkedQueue::Find(int x) {
+ if(x>=size || x<0) return NULL;
+ if(x == 0) return head;
+ if(x == size-1) return tail;
+ if(!bm) { bm=head; bmpos=0; }
+ int diffh = x;
+ int difft = (size-1) - x;
+ int diffbm = x - bmpos;
+ diffbm>0?diffbm:-diffbm;
+ if(diffh < difft && diffh < diffbm) { bm=head; bmpos=0; }
+ else if(diffh >= difft && diffbm >= difft) { bm=tail; bmpos=size-1; }
+ while(bmpos > x && bm) { bm=bm->prev; bmpos--; }
+ while(bmpos < x && bm) { bm=bm->next; bmpos++; }
+ return bm;
+}
+
+void * LinkedQueue::Get(int pos) {
+ lock();
+ QueueElement * e = Find(pos);
+ unlock();
+ return e?e->elem:NULL;
+}
+
+void LinkedQueue::Set(int pos, void * val) {
+ lock();
+ QueueElement * e = Find(pos);
+ if(e) e->elem=val;
+ unlock();
+}
+
+void* LinkedQueue::Del(int pos) {
+ lock();
+ QueueElement * e = Find(pos);
+ if(!e) { unlock(); return NULL; }
+ else if(size == 1) head=tail=NULL;
+ else if(e==head) {
+ head=head->next;
+ head->prev=NULL;
+ }
+ else if(e==tail) {
+ tail=tail->prev;
+ tail->next=NULL;
+ }
+ else {
+ e->prev->next = e->next;
+ e->next->prev = e->prev;
+ }
+ size--;
+ bm=NULL;
+ unlock();
+ void * ret = e->elem;
+ delete e;
+ return ret;
+}
+
+int LinkedQueue::GetSize() {
+ return size;
+ /*
+ lock();
+ int s = size;
+ unlock();
+ return s;*/
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/LinkedQueue.h b/Src/Plugins/Library/ml_pmp/LinkedQueue.h
new file mode 100644
index 00000000..84310efc
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/LinkedQueue.h
@@ -0,0 +1,41 @@
+#ifndef _LINKEDQUEUE_H_
+#define _LINKEDQUEUE_H_
+
+#include <windows.h>
+
+class LinkedQueue;
+class QueueElement;
+
+class QueueElement {
+public:
+ QueueElement * next;
+ QueueElement * prev;
+ void * elem;
+ QueueElement(void * e) { next=NULL; prev=NULL; elem=e; }
+};
+
+
+class LinkedQueue {
+protected:
+ QueueElement * head;
+ QueueElement * tail;
+ QueueElement * bm;
+ int bmpos;
+ int size;
+ QueueElement * Find(int pos);
+ CRITICAL_SECTION cs;
+public:
+ LinkedQueue();
+ ~LinkedQueue();
+ int GetSize();
+ void Offer(void * e);
+ void *Poll();
+ void *Peek();
+ void *Get(int pos);
+ void Set(int pos, void * val);
+ void *Del(int pos);
+ void lock();
+ void unlock();
+};
+
+#endif //_LINKEDQUEUE_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/PmpDevice.cpp b/Src/Plugins/Library/ml_pmp/PmpDevice.cpp
new file mode 100644
index 00000000..3ef7eae6
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/PmpDevice.cpp
@@ -0,0 +1,557 @@
+#include "main.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include "../nu/refcount.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "DeviceCommands.h"
+#include "resource1.h"
+#include <strsafe.h>
+
+// known commands
+static const char *DEVICE_CMD_VIEW_OPEN = "view_open";
+static const char *DEVICE_CMD_SYNC = "sync";
+static const char *DEVICE_CMD_AUTOFILL = "autofill";
+static const char *DEVICE_CMD_PLAYLIST_CREATE = "playlist_create";
+static const char *DEVICE_CMD_RENAME = "rename";
+static const char *DEVICE_CMD_PREFERENCES = "preferences";
+static const char *DEVICE_CMD_EJECT = "eject";
+static const char *DEVICE_CMD_REMOVE = "remove";
+static const char *DEVICE_CMD_TRANSFER = "transfer";
+static const char *DEVICE_CMD_HIDE = "hide";
+
+extern void UpdateDevicesListView(bool softUpdate);
+
+// we're going to share the command enum stuff for all devices
+
+class PortableDeviceType : public ifc_devicetype
+{
+public:
+ PortableDeviceType()
+ {
+ }
+ const char *GetName()
+ {
+ return "portable";
+ }
+
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize)
+ {
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLE_DEVICE_TYPE, buffer, bufferSize);
+ return S_OK;
+ }
+protected:
+
+#define CBCLASS PortableDeviceType
+ START_DISPATCH_INLINE;
+ CB(API_GETNAME, GetName);
+ CB(API_GETICON, GetIcon);
+ CB(API_GETDISPLAYNAME, GetDisplayName);
+ END_DISPATCH;
+#undef CBCLASS
+};
+
+class USBDeviceConnection : public ifc_deviceconnection
+{
+public:
+ USBDeviceConnection()
+ {
+ }
+ const char *GetName()
+ {
+ return "usb";
+ }
+
+ HRESULT GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+ {
+ if (FALSE == FormatResProtocol(MAKEINTRESOURCE(IDB_USB),
+ L"PNG", buffer, bufferSize))
+ {
+ return E_FAIL;
+ }
+
+ return S_OK;
+ }
+
+ HRESULT GetDisplayName(wchar_t *buffer, size_t bufferSize)
+ {
+ if (NULL == buffer)
+ return E_POINTER;
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_CONNECTION_USB, buffer, bufferSize);
+ return S_OK;
+ }
+protected:
+
+#define CBCLASS USBDeviceConnection
+ START_DISPATCH_INLINE;
+ CB(API_GETNAME, GetName);
+ CB(API_GETICON, GetIcon);
+ CB(API_GETDISPLAYNAME, GetDisplayName);
+ END_DISPATCH;
+#undef CBCLASS
+};
+
+static PortableDeviceType portable_device_type;
+static USBDeviceConnection usb_connection;
+static PortableCommand registered_commands[] =
+{
+ PortableCommand(DEVICE_CMD_VIEW_OPEN, IDS_DEVICE_CMD_VIEW_OPEN, IDS_DEVICE_CMD_VIEW_OPEN_DESC),
+ PortableCommand(DEVICE_CMD_SYNC, IDS_DEVICE_CMD_SYNC, IDS_DEVICE_CMD_SYNC_DESC),
+ PortableCommand(DEVICE_CMD_TRANSFER, IDS_DEVICE_CMD_TRANSFER, IDS_DEVICE_CMD_TRANSFER_DESC),
+ PortableCommand(DEVICE_CMD_EJECT, IDS_DEVICE_CMD_EJECT, IDS_DEVICE_CMD_EJECT_DESC),
+ PortableCommand(DEVICE_CMD_REMOVE, IDS_DEVICE_CMD_REMOVE, IDS_DEVICE_CMD_REMOVE_DESC),
+ PortableCommand(DEVICE_CMD_RENAME, IDS_DEVICE_CMD_RENAME, IDS_DEVICE_CMD_RENAME_DESC),
+ PortableCommand(DEVICE_CMD_AUTOFILL, IDS_DEVICE_CMD_AUTOFILL, IDS_DEVICE_CMD_AUTOFILL_DESC),
+ PortableCommand(DEVICE_CMD_PLAYLIST_CREATE, IDS_DEVICE_CMD_PLAYLIST_CREATE, IDS_DEVICE_CMD_PLAYLIST_CREATE_DESC),
+ PortableCommand(DEVICE_CMD_PREFERENCES, IDS_DEVICE_CMD_PREFERENCES, IDS_DEVICE_CMD_PREFERENCES_DESC),
+ PortableCommand(DEVICE_CMD_HIDE, IDS_DEVICE_CMD_HIDE, IDS_DEVICE_CMD_HIDE),
+};
+
+static ifc_devicecommand * _cdecl
+Devices_RegisterCommand(const char *name, void *user)
+{
+ for(size_t i = 0; i < sizeof(registered_commands)/sizeof(*registered_commands); i++)
+ {
+ if (name == registered_commands[i].GetName())
+ {
+ return &registered_commands[i];
+ }
+ }
+ return NULL;
+}
+
+void Devices_Init()
+{
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ /* register 'portable' device type */
+ ifc_devicetype *type = &portable_device_type;
+ AGAVE_API_DEVICEMANAGER->TypeRegister(&type, 1);
+
+ /* register 'usb' connection type */
+ ifc_deviceconnection *connection = &usb_connection;
+ AGAVE_API_DEVICEMANAGER->ConnectionRegister(&connection, 1);
+
+
+ /* register commands */
+ const char *commands[sizeof(registered_commands)/sizeof(*registered_commands)];
+ for(size_t i = 0; i < sizeof(registered_commands)/sizeof(*registered_commands); i++)
+ {
+ commands[i] = registered_commands[i].GetName();
+ }
+ AGAVE_API_DEVICEMANAGER->CommandRegisterIndirect(commands, sizeof(registered_commands)/sizeof(*registered_commands), Devices_RegisterCommand, NULL);
+ }
+}
+
+int DeviceView::QueryInterface(GUID interface_guid, void **object)
+{
+ if (interface_guid == IFC_Device)
+ {
+ AddRef();
+ *object = (ifc_device *)this;
+ return 0;
+ }
+ return 1;
+}
+
+const char *DeviceView::GetName()
+{
+ return name;
+}
+
+HRESULT DeviceView::GetIcon(wchar_t *buffer, size_t bufferSize, int width, int height)
+{
+ buffer[0]=0;
+ dev->extraActions(DEVICE_GET_ICON, width, height, (intptr_t)buffer);
+ if (buffer[0] == 0)
+ return E_NOTIMPL;
+ else
+ return S_OK;
+
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::GetDisplayName(wchar_t *buffer, size_t bufferSize)
+{
+ // TODO sometimes this is erroring on loading
+ dev->getPlaylistName(0, buffer, bufferSize);
+ return S_OK;
+}
+
+const char *DeviceView::GetType()
+{
+ return "portable";
+}
+
+const char *DeviceView::GetDisplayType()
+{
+ return display_type;
+}
+
+const char *DeviceView::GetConnection()
+{
+ return connection_type;
+}
+
+BOOL DeviceView::GetHidden()
+{
+ return FALSE;
+}
+
+HRESULT DeviceView::GetTotalSpace(uint64_t *size)
+{
+ UpdateSpaceInfo(FALSE, TRUE);
+ *size = dev->getDeviceCapacityTotal();
+ return S_OK;
+}
+
+HRESULT DeviceView::GetUsedSpace(uint64_t *size)
+{
+ if (NULL == size)
+ return E_POINTER;
+
+ UpdateSpaceInfo(TRUE, TRUE);
+ *size = usedSpace;
+
+ return S_OK;
+}
+
+BOOL DeviceView::GetAttached()
+{
+ return TRUE; // ml_pmp devices are by default attached
+}
+
+HRESULT DeviceView::Attach(HWND hostWindow)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::Detach(HWND hostWindow)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::EnumerateCommands(ifc_devicesupportedcommandenum **enumerator, DeviceCommandContext context)
+{
+ DeviceCommandInfo commands[32];
+ size_t count;
+
+ if (NULL == enumerator)
+ return E_POINTER;
+
+ count = 0;
+
+ LinkedQueue * txQueue = getTransferQueue(this);
+ if (txQueue == NULL)
+ return E_POINTER;
+
+ // return E_NOTIMPL;
+ if (context == DeviceCommandContext_View)
+ {
+ if (0 == txQueue->GetSize())
+ {
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_SYNC, DeviceCommandFlag_Primary);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_AUTOFILL, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PLAYLIST_CREATE, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PREFERENCES, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_EJECT, DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_REMOVE, DeviceCommandFlag_None);
+ }
+ }
+ else
+ {
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_VIEW_OPEN, DeviceCommandFlag_Primary);
+
+ DeviceCommandFlags flags = DeviceCommandFlag_None;
+ if (0 != txQueue->GetSize())
+ flags |= DeviceCommandFlag_Disabled;
+
+ if (0 != dev->extraActions(DEVICE_SYNC_UNSUPPORTED,0,0,0))
+ flags |= DeviceCommandFlag_Disabled;
+ SetDeviceCommandInfo(&commands[count++], (!isCloudDevice ? DEVICE_CMD_SYNC : DEVICE_CMD_TRANSFER), flags | DeviceCommandFlag_Group);
+ if (!isCloudDevice) SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_AUTOFILL, flags);
+
+ flags = DeviceCommandFlag_None;
+ if (0 == dev->extraActions(DEVICE_PLAYLISTS_UNSUPPORTED,0,0,0))
+ {
+ // TODO remove once we've got cloud playlists implemented
+ if (!isCloudDevice)
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PLAYLIST_CREATE, flags | DeviceCommandFlag_Group);
+ }
+
+ // adds a specific menu item to hide the 'local library' source
+ if (isCloudDevice)
+ {
+ char name[128] = {0};
+ if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0))
+ {
+ if (!strcmp(name, "local_desktop"))
+ {
+ flags = DeviceCommandFlag_None | DeviceCommandFlag_Group;
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_HIDE, flags);
+ }
+ }
+ }
+
+ bool has_rename = true;
+ flags = DeviceCommandFlag_None;
+ if (0 == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0))
+ has_rename = false;
+ else
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_RENAME, flags | DeviceCommandFlag_Group);
+
+ flags = (!has_rename ? DeviceCommandFlag_Group : DeviceCommandFlag_None);
+ SetDeviceCommandInfo(&commands[count++], DEVICE_CMD_PREFERENCES, flags);
+ if (!dev->extraActions(DEVICE_DOES_NOT_SUPPORT_REMOVE,0,0,0))
+ SetDeviceCommandInfo(&commands[count++], (!isCloudDevice ? DEVICE_CMD_EJECT : DEVICE_CMD_REMOVE), flags | DeviceCommandFlag_Group);
+ }
+
+ *enumerator = new DeviceCommandEnumerator(commands, count);
+ if (NULL == *enumerator)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+}
+
+HRESULT DeviceView::SendCommand(const char *command, HWND hostWindow, ULONG_PTR param)
+{
+ if (!strcmp(command, DEVICE_CMD_EJECT) || !strcmp(command, DEVICE_CMD_REMOVE))
+ {
+ Eject();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_SYNC) || !strcmp(command, DEVICE_CMD_TRANSFER))
+ {
+ if (!this->isCloudDevice) Sync();
+ else CloudSync();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_AUTOFILL))
+ {
+ Autofill();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_RENAME))
+ {
+ if (NULL != treeItem)
+ MLNavItem_EditTitle(plugin.hwndLibraryParent, treeItem);
+ else
+ RenamePlaylist(0);
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_PLAYLIST_CREATE))
+ {
+ CreatePlaylist();
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_PREFERENCES))
+ {
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE,(WPARAM)&devPrefsPage);
+ return S_OK;
+ }
+ else if (!strcmp(command, DEVICE_CMD_HIDE))
+ {
+ static int IPC_CLOUD_HIDE_LOCAL = -1;
+ if (IPC_CLOUD_HIDE_LOCAL == -1)
+ IPC_CLOUD_HIDE_LOCAL = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloudLocal", IPC_REGISTER_WINAMP_IPCMESSAGE);
+
+ SENDWAIPC(plugin.hwndWinampParent, IPC_CLOUD_HIDE_LOCAL, 0);
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::GetCommandFlags(const char *command, DeviceCommandFlags *flags)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DeviceView::GetActivity(ifc_deviceactivity **activity)
+{
+ LinkedQueue * txQueue = getTransferQueue();
+ if (txQueue == NULL || txQueue->GetSize() == 0)
+ {
+ *activity = 0;
+ return S_FALSE;
+ }
+ AddRef();
+ *activity = this;
+ return S_OK;
+}
+
+HRESULT DeviceView::Advise(ifc_deviceevent *handler)
+{
+ event_handlers.push_back(handler);
+ return S_OK;
+}
+
+HRESULT DeviceView::Unadvise(ifc_deviceevent *handler)
+{
+ //event_handlers.eraseObject(handler);
+ auto it = std::find(event_handlers.begin(), event_handlers.end(), handler);
+ if (it != event_handlers.end())
+ {
+ event_handlers.erase(it);
+ }
+
+ return S_OK;
+}
+
+extern C_ItemList devices;
+extern void UpdateDevicesListView(bool softupdate);
+void DeviceView::SetNavigationItem(void *navigationItem)
+{
+ if (navigationItem)
+ RegisterViews((HNAVITEM)navigationItem);
+}
+
+BOOL DeviceView::GetActive()
+{
+ LinkedQueue * txQueue = getTransferQueue();
+ if (txQueue == NULL || txQueue->GetSize() == 0)
+ return FALSE;
+ return TRUE;
+}
+
+BOOL DeviceView::GetCancelable()
+{
+ return FALSE;
+}
+
+HRESULT DeviceView::GetProgress(unsigned int *percentCompleted)
+{
+ LinkedQueue * txQueue = getTransferQueue();
+ LinkedQueue * finishedTX = getFinishedTransferQueue();
+ int txProgress = getTransferProgress();
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ double num = (100.0 * (double)size) - (double)txProgress;
+ double total = (double)100 * size + 100 * (finishedTX ? finishedTX->GetSize() : 0);
+
+ double percent = (0 != total) ? (((total - num) * 100) / total) : 0;
+
+ *percentCompleted = (unsigned int)percent;
+ return S_OK;
+}
+
+HRESULT DeviceView::Activity_GetDisplayName(wchar_t *buffer, size_t bufferMax)
+{
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERRING, buffer, bufferMax);
+ return S_OK;
+}
+
+HRESULT DeviceView::Activity_GetStatus(wchar_t *buffer, size_t bufferMax)
+{
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERRING_DESC, buffer, bufferMax);
+ return S_OK;
+}
+
+HRESULT DeviceView::Cancel(HWND hostWindow)
+{
+// threadKillswitch = 1; // TODO: i think this is how to do it
+ //transferContext.WaitForKill();
+ return S_OK;
+}
+
+HRESULT DeviceView::GetDropSupported(unsigned int dataType)
+{
+ if (dataType == ML_TYPE_ITEMRECORDLISTW
+ || dataType == ML_TYPE_ITEMRECORDLIST
+ || dataType == ML_TYPE_PLAYLIST
+ || dataType == ML_TYPE_PLAYLISTS
+ || dataType == ML_TYPE_FILENAMES
+ || dataType == ML_TYPE_FILENAMESW)
+ return S_OK;
+ return E_FAIL;
+}
+
+HRESULT DeviceView::Drop(void *data, unsigned int dataType)
+{
+ return (HRESULT)TransferFromML(dataType,data,E_FAIL,S_OK);
+}
+
+HRESULT DeviceView::SetDisplayName(const wchar_t *displayName, bool force = 0)
+{
+ if((0 == force && 0 == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0)))
+ return E_FAIL;
+
+ dev->setPlaylistName(0, displayName);
+ free(devPrefsPage.name);
+ devPrefsPage.name = _wcsdup(displayName);
+ SENDWAIPC(plugin.hwndWinampParent, IPC_UPDATE_PREFS_DLGW, (WPARAM)&devPrefsPage);
+
+ DevicePropertiesChanges();
+ UpdateDevicesListView(false);
+
+ OnNameChanged(displayName);
+
+ return S_OK;
+}
+
+HRESULT DeviceView::GetModel(wchar_t *buffer, size_t bufferSize)
+{
+ if (NULL == buffer)
+ return E_POINTER;
+
+ buffer[0] = L'\0';
+
+ if(0 == dev->extraActions(DEVICE_GET_MODEL, (intptr_t)buffer, bufferSize, 0))
+ return E_NOTIMPL;
+
+ return S_OK;
+}
+
+HRESULT DeviceView::GetStatus(wchar_t *buffer, size_t bufferSize)
+{
+ return E_NOTIMPL;
+}
+
+#define CBCLASS DeviceView
+START_MULTIPATCH;
+START_PATCH(PATCH_IFC_DEVICE)
+M_CB(PATCH_IFC_DEVICE, ifc_device, QUERYINTERFACE, QueryInterface);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETNAME, GetName);
+M_CB(PATCH_IFC_DEVICE, ifc_device, ifc_deviceobject::API_GETDISPLAYNAME, GetDisplayName);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETICON, GetIcon);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETTYPE, GetType);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETDISPLAYTYPE, GetDisplayType);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETCONNECTION, GetConnection);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETTOTALSPACE, GetTotalSpace);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETUSEDSPACE, GetUsedSpace);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_ENUMERATECOMMANDS, EnumerateCommands);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_SENDCOMMAND, SendCommand);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETATTACHED, GetAttached);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_ATTACH, Attach);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_DETACH, Detach);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_ADVISE, Advise);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_UNADVISE, Unadvise);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_CREATEVIEW, CreateView);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETACTIVITY, GetActivity);
+M_VCB(PATCH_IFC_DEVICE, ifc_device, API_SETNAVIGATIONITEM, SetNavigationItem);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETDROPSUPPORTED, GetDropSupported);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_DROP, Drop);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_SETDISPLAYNAME, SetDisplayName);
+M_CB(PATCH_IFC_DEVICE, ifc_device, API_GETMODEL, GetModel);
+M_CB(PATCH_IFC_DEVICE, ifc_device, ifc_device::API_GETSTATUS, GetStatus);
+M_CB(PATCH_IFC_DEVICE, ifc_device, ADDREF, AddRef);
+M_CB(PATCH_IFC_DEVICE, ifc_device, RELEASE, Release);
+NEXT_PATCH(PATCH_IFC_DEVICEACTIVITY)
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_GETACTIVE, GetActive);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_GETCANCELABLE, GetCancelable);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_GETPROGRESS, GetProgress);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, ifc_deviceactivity::API_GETDISPLAYNAME, Activity_GetDisplayName);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, ifc_deviceactivity::API_GETSTATUS, Activity_GetStatus);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, API_CANCEL, Cancel);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, ADDREF, AddRef);
+M_CB(PATCH_IFC_DEVICEACTIVITY, ifc_deviceactivity, RELEASE, Release);
+END_PATCH
+END_MULTIPATCH;
+#undef CBCLASS \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/PmpDevice.h b/Src/Plugins/Library/ml_pmp/PmpDevice.h
new file mode 100644
index 00000000..6f70f09b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/PmpDevice.h
@@ -0,0 +1 @@
+#pragma once
diff --git a/Src/Plugins/Library/ml_pmp/SkinnedListView.cpp b/Src/Plugins/Library/ml_pmp/SkinnedListView.cpp
new file mode 100644
index 00000000..348bbd67
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/SkinnedListView.cpp
@@ -0,0 +1,856 @@
+#include "main.h"
+#include "SkinnedListView.h"
+#include "resource1.h"
+#include "api__ml_pmp.h"
+#include "./local_menu.h"
+#include "../replicant/nx/nxstring.h"
+#include <strsafe.h>
+
+extern DeviceView * currentViewedDevice;
+extern winampMediaLibraryPlugin plugin;
+extern HMENU m_context_menus;
+extern HINSTANCE cloud_hinst;
+extern int IPC_GET_CLOUD_HINST;
+
+static bool doneFirstInit=false;
+int (*wad_getColor)(int idx);
+
+#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
+extern int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb);
+
+SkinnedListView::SkinnedListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu)
+ : enableHeaderMenu(enableHeaderMenu), skinlistview_handle(0), contents(0), headerWindow(0)
+{
+ if(!doneFirstInit) {
+ *(void **)&wad_getColor=(void*)SendMessage(libraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ doneFirstInit=true;
+ }
+ this->dlgitem = dlgitem;
+ this->contents = lc;
+ this->libraryParent = libraryParent;
+}
+
+void SkinnedListView::UpdateList(bool softUpdate) {
+ if(!softUpdate) {
+ ListView_SetItemCount(listview.getwnd(),0);
+ ListView_SetItemCount(listview.getwnd(),(contents ? contents->GetNumRows() : 0));
+ }
+ ListView_RedrawItems(listview.getwnd(),0,(contents ? contents->GetNumRows() - 1 : 0));
+}
+
+int SkinnedListView::GetFindItemColumn() {
+ return contents->GetSortColumn();
+}
+
+HMENU SkinnedListView::GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu) {
+ HMENU menu;
+
+ menu = GetSubMenu(themenu, (FALSE != isFilter) ? 9 : 8);
+ if (NULL == menu)
+ return NULL;
+
+ if(isFilter)
+ {
+ MENUITEMINFO m={sizeof(m),MIIM_ID,0};
+ int i, count;
+ unsigned int filterMarker;
+
+ filterMarker = (((unsigned char)(1+filterNum)) << 24);
+ count = GetMenuItemCount(menu);
+ for(i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(menu,i,TRUE,&m))
+ {
+ m.wID = filterMarker | (m.wID & 0x00FFFFFF);
+ SetMenuItemInfo(menu,i,TRUE,&m);
+ }
+ }
+
+ wchar_t conf[100] = {0};
+ StringCchPrintf(conf, ARRAYSIZE(conf), L"media_scroll_%d",filterNum);
+ bool enablescroll = c->ReadInt(conf,0)!=0;
+
+ CheckMenuItem(menu,
+ filterMarker | (ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR & 0x00FFFFFF),
+ MF_BYCOMMAND | (enablescroll ? MF_CHECKED : MF_UNCHECKED));
+ }
+
+ return menu;
+}
+
+void SkinnedListView::ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent) {
+ int mid = (r >> 24);
+ if(!isFilter && mid) return;
+ if(isFilter && mid-1 != filterNum) return;
+ r &= 0xFFFF;
+ switch(r) {
+ case ID_HEADERWND_CUSTOMIZECOLUMNS:
+ {
+ contents->CustomizeColumns(listview.getwnd(),FALSE);
+ while(ListView_DeleteColumn(listview.getwnd(),0));
+ for(int i=0; i < contents->GetNumColumns(); i++)
+ listview.AddCol(contents->GetColumnTitle(i),contents->GetColumnWidth(i));
+ }
+ break;
+
+ case ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR:
+ {
+ wchar_t conf[100] = {0};
+ StringCchPrintf(conf, ARRAYSIZE(conf), L"media_scroll_%d",filterNum);
+ bool enablescroll = !c->ReadInt(conf,0);
+ c->WriteInt(conf,enablescroll?1:0);
+
+ if (FALSE != MLSkinnedScrollWnd_ShowHorzBar(listview.getwnd(), enablescroll))
+ {
+ RECT rect;
+ if(FALSE != GetWindowRect(listview.getwnd(), &rect))
+ {
+ OffsetRect(&rect, -rect.left, -rect.top);
+
+ SetWindowPos(listview.getwnd(), NULL, 0, 0, rect.right - 1, rect.bottom,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ SetWindowPos(listview.getwnd(), NULL, 0, 0, rect.right, rect.bottom,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+
+ RedrawWindow(listview.getwnd(), NULL, NULL,
+ RDW_INVALIDATE | RDW_ERASE | RDW_FRAME |
+ RDW_ERASENOW | RDW_UPDATENOW);
+ }
+ }
+ }
+ break;
+ }
+}
+
+void SkinnedListView::InitializeFilterData(int filterNum, C_Config *config)
+{
+ wchar_t buffer[64] = {0};
+ BOOL enableHorzScrollbar;
+
+ if (NULL == config)
+ return;
+
+ if (filterNum < 0)
+ return;
+
+ if(FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), L"media_scroll_%d",filterNum)))
+ return;
+
+ enableHorzScrollbar = (FALSE != config->ReadInt(buffer, FALSE));
+ if (FALSE != MLSkinnedScrollWnd_ShowHorzBar(listview.getwnd(), enableHorzScrollbar))
+ {
+ SetWindowPos(listview.getwnd(), NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
+ }
+}
+
+LRESULT SkinnedListView::pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (uMsg == WM_NOTIFY)
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ switch (l->code)
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
+ RECT r = {0};
+ if (lvh.iSubItem)
+ ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r);
+ else
+ {
+ ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS);
+ r.right = r.left + ListView_GetColumnWidth(hwnd, contents->cloudcol);
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2);
+ SetWindowPos(tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ static wchar_t tt_buf[256] = {L""};
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem != -1 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
+ static int last_item = -1;
+
+ if (last_item == lvh.iItem)
+ {
+ lpnmtdi->lpszText = tt_buf;
+ return 0;
+ }
+
+ if (contents->cloud_cache[lvh.iItem] == 4)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else
+ {
+ if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+ if (cloud_hinst && cloud_hinst != (HINSTANCE)1)
+ {
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRACK_AVAILABLE, tt_buf, ARRAYSIZE(tt_buf));
+
+ int message = 0x405;
+ wchar_t value[1024] = {0};
+ songid_t s = contents->GetTrack(lvh.iItem);
+ currentViewedDevice->dev->getTrackExtraInfo(s, L"filepath", value, ARRAYSIZE(value));
+ if (!value[0])
+ {
+ message = 0x407;
+ currentViewedDevice->dev->getTrackExtraInfo(s, L"metahash", value, ARRAYSIZE(value));
+ }
+
+ nx_string_t *out_devicenames = 0;
+ size_t num_names = mlplugin->MessageProc(message, (INT_PTR)&value, (INT_PTR)&out_devicenames, 0);
+ if (num_names > 0)
+ {
+ for (size_t i = 0; i < num_names; i++)
+ {
+ if (i > 0) StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), L", ");
+ StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), out_devicenames[i]->string);
+ }
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ if (out_devicenames)
+ NXStringRelease(*out_devicenames);
+ }
+ }
+ }
+ }
+ last_item = lvh.iItem;
+ lpnmtdi->lpszText = tt_buf;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+
+LRESULT SkinnedListView::pmp_listview_alt(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ if (uMsg == WM_NOTIFY)
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ switch (l->code)
+ {
+ case TTN_SHOW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem > 0 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
+ RECT r = {0};
+ if (lvh.iSubItem)
+ ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r);
+ else
+ {
+ ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS);
+ r.right = r.left + ListView_GetColumnWidth(hwnd, contents->cloudcol);
+ }
+
+ MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2);
+ SetWindowPos(tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE);
+ return 1;
+ }
+ }
+ break;
+
+ case TTN_NEEDTEXTW:
+ {
+ LVHITTESTINFO lvh = {0};
+ GetCursorPos(&lvh.pt);
+ ScreenToClient(hwnd, &lvh.pt);
+ ListView_SubItemHitTest(hwnd, &lvh);
+
+ static wchar_t tt_buf[256] = {L""};
+ ListContents * contents = (ListContents *)GetPropW(hwnd, L"pmp_list_info");
+ if (lvh.iItem > 0 && lvh.iSubItem == contents->cloudcol)
+ {
+ LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
+ static int last_item = -1;
+
+ if (last_item == lvh.iItem)
+ {
+ lpnmtdi->lpszText = tt_buf;
+ return 0;
+ }
+
+ wchar_t temp[8] = {0};
+ contents->GetCellText(lvh.iItem, lvh.iSubItem, temp, 8);
+ int status = _wtoi(temp);
+ if (status == 0 || status == 4)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_ALL_TRACKS_PLAYABLE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (status == 1)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_ALL_TRACKS_PLAYABLE_HERE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (status == 2)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_SOME_TRACKS_PLAYABLE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ else if (status == 3)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_NO_TRACKS_PLAYABLE, tt_buf, ARRAYSIZE(tt_buf));
+ }
+ last_item = lvh.iItem;
+ lpnmtdi->lpszText = tt_buf;
+
+ // bit of a fiddle but it allows for multi-line tooltips
+ //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
+ }
+ else
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+ }
+ return 0;
+ }
+ }
+
+ return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam);
+}
+
+BOOL SkinnedListView::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ #if defined(_UNICODE) || defined(UNICODE)
+ SendMessage(hwndDlg,CCM_SETUNICODEFORMAT,TRUE,0);
+ #endif
+
+ HWND list = GetDlgItem(hwndDlg, dlgitem);
+ headerWindow = (HWND)SendMessageW(list, LVM_GETHEADER, 0, 0L);
+ listview.setwnd(list);
+
+ // setup tooltip handling as needed
+ if (dlgitem == IDC_LIST_ARTIST || dlgitem == IDC_LIST_ALBUM || dlgitem == IDC_LIST_ALBUM2)
+ {
+ if (!GetPropW(list, L"pmp_list_proc")) {
+ SetPropW(list, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(list, GWLP_WNDPROC, (LONG_PTR)this->pmp_listview_alt));
+ SetPropW(list, L"pmp_list_info", (HANDLE)this->contents);
+ }
+ }
+ else if (dlgitem != IDC_LIST_TRANSFERS)
+ {
+ if (!GetPropW(list, L"pmp_list_proc")) {
+ SetPropW(list, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(list, GWLP_WNDPROC, (LONG_PTR)this->pmp_listview));
+ SetPropW(list, L"pmp_list_info", (HANDLE)this->contents);
+ }
+ }
+
+ if(!wParam)
+ {
+ MLSKINWINDOW m = {0};
+ m.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ m.hwndToSkin = listview.getwnd();
+ MLSkinWindow(libraryParent, &m);
+ }
+
+ if (contents)
+ {
+ for(int i=0; i < contents->GetNumColumns(); i++)
+ {
+ if (contents->cloud)
+ {
+ listview.AddCol((i == contents->cloudcol ? L"" : contents->GetColumnTitle(i)),contents->GetColumnWidth(i));
+ }
+ else
+ {
+ listview.AddCol(contents->GetColumnTitle(i),contents->GetColumnWidth(i));
+ }
+ }
+ if(contents->GetSortColumn() != -1) // display sort arrow
+ SendMessage(headerWindow,WM_ML_IPC,MAKEWPARAM(contents->GetSortColumn(),!contents->GetSortDirection()),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ UpdateList();
+ }
+ }
+ break;
+
+ case WM_DISPLAYCHANGE:
+ ListView_SetTextColor(listview.getwnd(),wad_getColor?wad_getColor(WADLG_ITEMFG):RGB(0xff,0xff,0xff));
+ ListView_SetBkColor(listview.getwnd(),wad_getColor?wad_getColor(WADLG_ITEMBG):RGB(0x00,0x00,0x00));
+ ListView_SetTextBkColor(listview.getwnd(),wad_getColor?wad_getColor(WADLG_ITEMBG):RGB(0x00,0x00,0x00));
+ listview.SetFont((HFONT)SendMessage(libraryParent, WM_ML_IPC, 66, ML_IPC_SKIN_WADLG_GETFUNC));
+ break;
+
+ case WM_DESTROY:
+ if (contents)
+ {
+ for(int i=0; i<contents->GetNumColumns(); i++)
+ if(contents->GetColumnWidth(i) != listview.GetColumnWidth(i))
+ contents->ColumnResize(i,listview.GetColumnWidth(i));
+ }
+ break;
+
+ case WM_NOTIFYFORMAT:
+ return NFR_UNICODE;
+
+ case WM_CONTEXTMENU:
+ {
+ POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
+ HWND hwndFromChild = WindowFromPoint(pt);
+ if (enableHeaderMenu && hwndFromChild == ListView_GetHeader(listview.getwnd())) {
+ if(contents->CustomizeColumns(listview.getwnd(), TRUE)) {
+ while (ListView_DeleteColumn(listview.getwnd(), 0));
+ for(int i=0; i < contents->GetNumColumns(); i++)
+ listview.AddCol(contents->GetColumnTitle(i),contents->GetColumnWidth(i));
+ }
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==dlgitem) {
+ switch(l->code) {
+ case NM_DBLCLK:
+ break;
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case 0x41: //A
+ if(GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ int num=listview.GetCount();
+ for(int x = 0; x < num; x ++) listview.SetSelected(x);
+ }
+ break;
+ case 0x2E: //Delete
+ break;
+ }
+ break;
+
+ case LVN_ODFINDITEM:
+ {
+ NMLVFINDITEM *t = (NMLVFINDITEM *)lParam;
+ int i=t->iStart;
+ if (i >= contents->GetNumRows()) i=0;
+
+ int cnt=contents->GetNumRows()-i;
+ if (t->lvfi.flags & LVFI_WRAP) cnt+=i;
+
+ while (cnt-->0) {
+ wchar_t tmp[128]=L"";
+ wchar_t *name=0;
+
+ contents->GetCellText(i,GetFindItemColumn(),tmp,sizeof(tmp)/sizeof(wchar_t));
+ name = tmp;
+
+ if (!name) name=L"";
+ else SKIP_THE_AND_WHITESPACE(name)
+
+ if (t->lvfi.flags & (4|LVFI_PARTIAL)) {
+ if (!_wcsnicmp(name,t->lvfi.psz,lstrlen(t->lvfi.psz))) {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else if (t->lvfi.flags & LVFI_STRING) {
+ if (!STRCMP_NULLOK(name,t->lvfi.psz)) {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,i);
+ return 1;
+ }
+ }
+ else {
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ if (++i == contents->GetNumRows()) i=0;
+ }
+ SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,-1);
+ return 1;
+ }
+ break;
+
+ case LVN_GETDISPINFOW:
+ {
+ NMLVDISPINFO *lpdi = (NMLVDISPINFO*) lParam;
+ int item=lpdi->item.iItem;
+ if (item < 0 || contents && item >= contents->GetNumRows()) return 0;
+ if (lpdi->item.mask & LVIF_TEXT) {
+ lpdi->item.pszText[0]=0;
+ contents->GetCellText(item,lpdi->item.iSubItem,lpdi->item.pszText,lpdi->item.cchTextMax);
+ // will cache the cloud status for use in drawing later on (not ideal but it'll do for now)
+ if (lpdi->item.iSubItem == contents->cloudcol) contents->cloud_cache[item] = _wtoi(lpdi->item.pszText);
+ }
+ }
+ break;
+
+ case LVN_COLUMNCLICK:
+ {
+ NMLISTVIEW *p=(NMLISTVIEW*)lParam;
+ contents->ColumnClicked(p->iSubItem);
+ if(contents->GetSortColumn() != -1) {
+ SendMessage(headerWindow,WM_ML_IPC,MAKEWPARAM(contents->GetSortColumn(),!contents->GetSortDirection()),ML_IPC_SKINNEDHEADER_DISPLAYSORT);
+ }
+ UpdateList();
+ }
+ break;
+ }
+ }
+
+ switch(l->code) {
+ case HDN_ITEMCHANGING:
+ {
+ if (headerWindow == l->hwndFrom)
+ {
+ LPNMHEADERW phdr = (LPNMHEADERW)lParam;
+ if (phdr->pitem && (HDI_WIDTH & phdr->pitem->mask) && phdr->iItem == contents->cloudcol)
+ {
+ INT width = phdr->pitem->cxy;
+ if (MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &width))
+ {
+ phdr->pitem->cxy = width;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+ListContents::~ListContents() {
+ for(int i=0; i<fields.GetSize(); i++) delete ((ListField*)fields.Get(i));
+ for(int i=0; i<hiddenfields.GetSize(); i++) delete ((ListField*)hiddenfields.Get(i));
+}
+
+static int sortFunc_cols(const void *elem1, const void *elem2) {
+ ListField * a = *(ListField **)elem1;
+ ListField * b = *(ListField **)elem2;
+ return a->pos - b->pos;
+}
+
+void ListContents::SortColumns() {
+ for(int i=0; i<fields.GetSize(); i++){
+ ListField *l = (ListField*)fields.Get(i);
+ if(l->hidden) {
+ hiddenfields.Add(l);
+ fields.Del(i--);
+ }
+ }
+ qsort(fields.GetAll(),fields.GetSize(),sizeof(void*),sortFunc_cols);
+}
+
+typedef struct CustomizeColumnsCreateParam
+{
+ ListContents *list;
+ HWND ownerWindow;
+} CustomizeColumnsCreateParam;
+
+static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND m_curlistbox_hwnd, m_availlistbox_hwnd;
+ static ListContents *list;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ CustomizeColumnsCreateParam *param;
+ HWND centerWindow;
+
+ m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1);
+ m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2);
+
+ param = (CustomizeColumnsCreateParam*)lParam;
+ if (NULL != param)
+ {
+ list = param->list;
+ centerWindow = param->ownerWindow;
+ if (NULL == centerWindow)
+ centerWindow = CENTER_OVER_ML_VIEW;
+ }
+ else
+ {
+ list = NULL;
+ centerWindow = CENTER_OVER_ML_VIEW;
+ }
+
+ if (FALSE != CenterWindow(hwndDlg, centerWindow))
+ {
+ if (FALSE == IS_INTRESOURCE(centerWindow))
+ {
+ wchar_t buffer[64] = {0};
+
+ if (FALSE != GetClassName(centerWindow, buffer, ARRAYSIZE(buffer)) &&
+ CSTR_EQUAL == CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT),
+ NORM_IGNORECASE, buffer, -1, L"SysListView32", -1))
+ {
+ RECT rect;
+ long top = 0;
+
+ if (FALSE != GetClientRect(centerWindow, &rect))
+ {
+ MapWindowPoints(centerWindow, HWND_DESKTOP, (POINT*)&rect, 2);
+ top = rect.top;
+
+ HWND headerWindow = (HWND)SendMessage(centerWindow, LVM_GETHEADER, 0, 0L);
+ if (NULL != headerWindow &&
+ (0 != (WS_VISIBLE & GetWindowLongPtr(headerWindow, GWL_STYLE))) &&
+ GetWindowRect(headerWindow, &rect))
+ {
+ if (rect.top == top)
+ top = rect.bottom;
+ }
+ }
+
+ top += 12;
+ if (FALSE != GetWindowRect(hwndDlg, &rect) &&
+ rect.top != top)
+ {
+ SetWindowPos(hwndDlg, NULL, rect.left, top, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
+ }
+ }
+ }
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+ }
+ }
+ case WM_USER + 32:
+ {
+ int i;
+ for (i=0; i<list->fields.GetSize(); i++) {
+ ListField * l = (ListField *)list->fields.Get(i);
+ int r = SendMessage(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l);
+ }
+ for (i=0; i<list->hiddenfields.GetSize(); i++) {
+ ListField * l = (ListField *)list->hiddenfields.Get(i);
+ int r = SendMessage(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)l->name);
+ SendMessage(m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)l);
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_DEFS:
+ SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0);
+ list->ResetColumns();
+ SendMessage(hwndDlg, WM_USER + 32, 0, 0);
+ break;
+ case IDC_LIST2:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON2:
+ //add column
+ {
+ for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i--, 0);
+ int r = SendMessage(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0);
+ }
+ break;
+ case IDC_LIST1:
+ if (HIWORD(wParam) != LBN_DBLCLK) {
+ if (HIWORD(wParam) == LBN_SELCHANGE) {
+ int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r);
+ }
+ return 0;
+ }
+ case IDC_BUTTON3:
+ //remove column
+ {
+ for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ if(!c) continue;
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0);
+ i--;
+ int r = SendMessage(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)c->name);
+ SendMessage(m_availlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0);
+ }
+ break;
+ case IDC_BUTTON4:
+ //move column up
+ {
+ for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++)
+ {
+ if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i - 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0);
+ int r = (INT)SendMessage(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDC_BUTTON5:
+ //move column down
+ {
+ int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (int i = l - 2;i >= 0;i--)
+ {
+ if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0))
+ {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i + 1, 0);
+ SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0);
+ int r = (INT)SendMessage(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)c->name);
+ SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, (LPARAM)c);
+ }
+ }
+ }
+ break;
+ case IDOK:
+ // read and apply changes...
+ {
+ while(list->fields.GetSize()) list->fields.Del(0);
+ while(list->hiddenfields.GetSize()) list->hiddenfields.Del(0);
+ wchar_t buf[100] = {0};
+ int i;
+ int l = (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_curlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ list->fields.Add(c);
+ c->pos=i;
+ c->hidden=false;
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colPos_%d",c->field);
+ list->config->WriteInt(buf,i);
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colHidden_%d",c->field);
+ list->config->WriteInt(buf,0);
+ }
+ l = (INT)SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);
+ for (i = 0;i < l;i++) {
+ ListField* c = (ListField*)SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0);
+ list->hiddenfields.Add(c);
+ c->hidden=true;
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colHidden_%d",c->field);
+ list->config->WriteInt(buf,1);
+ }
+ list->SortColumns();
+ }
+ EndDialog(hwndDlg, 1);
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+bool ListContents::CustomizeColumns(HWND parent, BOOL showmenu) {
+ if(!fields.GetSize()) return false;
+ if(showmenu) {
+ HMENU menu = GetSubMenu(m_context_menus, 8);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, parent, NULL);
+ if(r != ID_HEADERWND_CUSTOMIZECOLUMNS) return false;
+ }
+
+ CustomizeColumnsCreateParam param;
+ param.list = this;
+ param.ownerWindow = parent;
+
+ bool r = !!WASABI_API_DIALOGBOXPARAMW(IDD_CUSTCOLUMNS, parent, custColumns_dialogProc,(LPARAM)&param);
+ MSG msg;
+ while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return
+
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(parent), (this->cloudcol = -1)); // reset the cloud status column so it'll be correctly removed
+ if (cloud)
+ {
+ // not pretty but it'll allow us to know the current
+ // position of the cloud column for drawing purposes
+ for(int i = 0; i < fields.GetSize(); i++)
+ {
+ if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud"))
+ {
+ this->cloudcol = ((ListField *)fields.Get(i))->pos;
+ // update the cloud column
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(parent), this->cloudcol);
+ break;
+ }
+ }
+ }
+
+ return r;
+}
+
+ListField::ListField(int field, int defwidth, wchar_t * name, C_Config * config, bool hidden):field(field),name(name),hiddenDefault(hidden) {
+ wchar_t buf[100] = {0};
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colWidth_%d",field);
+ width = config->ReadInt(buf,defwidth);
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colPos_%d",field);
+ pos = config->ReadInt(buf,field==-1?-1:(field%100));
+ StringCchPrintf(buf, ARRAYSIZE(buf), L"colHidden_%d",field);
+ this->hidden = config->ReadInt(buf,hidden?1:0)!=0;
+ this->name = _wcsdup(name);
+}
+
+void ListField::ResetPos() { pos = ((field==-1)?-1:(pos%100)); hidden = hiddenDefault;}
+
+void ListContents::ResetColumns() {
+ while(hiddenfields.GetSize()) { fields.Add(hiddenfields.Get(0)); hiddenfields.Del(0); }
+ for (int i=0; i<fields.GetSize(); i++) ((ListField*)fields.Get(i))->ResetPos();
+ SortColumns();
+}
+
+int ListContents::GetSortDirection() { return TRUE; }
+int ListContents::GetSortColumn() { return -1; }
+void ListContents::ColumnClicked(int col) {}
+void ListContents::ColumnResize(int col, int newWidth) {}
+void ListContents::GetInfoString(wchar_t * buf) { buf[0]=0; } \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/SkinnedListView.h b/Src/Plugins/Library/ml_pmp/SkinnedListView.h
new file mode 100644
index 00000000..3682f9c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/SkinnedListView.h
@@ -0,0 +1,86 @@
+#ifndef _SKINNEDLISTVIEW_H_
+#define _SKINNEDLISTVIEW_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "pmp.h"
+#include "../nu/listview.h"
+#include <map>
+#include "..\..\General\gen_ml/itemlist.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "config.h"
+
+class ListField {
+public:
+ int field;
+ int width;
+ int pos;
+ wchar_t * name;
+ bool hidden, hiddenDefault;
+ ListField() : field(0), width(0), pos(0), name(0), hidden(0), hiddenDefault(0) {};
+ ListField(int field, int defwidth, wchar_t * name, C_Config * config, bool hidden=false);
+ ~ListField() { free(name); }
+ void ResetPos();
+};
+
+class ListContents {
+public:
+ C_ItemList fields, hiddenfields;
+ C_Config * config;
+ int cloud, cloudcol;
+ typedef std::map<size_t, int> CloudCache;
+ CloudCache cloud_cache;
+ Device *dev;
+ ListContents() : config(NULL), cloud(0), cloudcol(-1), dev(0) {};
+ virtual ~ListContents();
+ virtual int GetNumColumns()=0;
+ virtual int GetNumRows()=0;
+ virtual wchar_t * GetColumnTitle(int num)=0;
+ virtual int GetColumnWidth(int num)=0;
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)=0;
+ virtual int GetSortColumn();
+ virtual int GetSortDirection(); // return true for acending.
+ virtual void ColumnClicked(int col); // sort contents and update list
+ virtual void ColumnResize(int col, int newWidth); // called once only just before deconstuctor if changed
+ virtual void GetInfoString(wchar_t * buf);
+ void SortColumns();
+ virtual bool CustomizeColumns(HWND parent, BOOL showmenu);
+ virtual void ResetColumns();
+ virtual pmpart_t GetArt(int row){return NULL;}
+ virtual void SetMode(int mode){}
+ virtual songid_t GetTrack(int pos)=0;
+};
+
+class PrimaryListContents : public ListContents {
+public:
+ virtual void RemoveTrack(songid_t song){}
+};
+
+class SkinnedListView {
+protected:
+ HWND libraryParent, headerWindow;
+ int dlgitem;
+ bool enableHeaderMenu;
+public:
+ int skinlistview_handle;
+ ListContents * contents;
+ W_ListView listview;
+ SkinnedListView(ListContents * lc, int dlgitem, HWND libraryParent, HWND parent, bool enableHeaderMenu=true);
+ virtual ~SkinnedListView(){}
+ virtual BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ static LRESULT pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ static LRESULT pmp_listview_alt(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ virtual void UpdateList(bool softUpdate=false);
+ virtual int GetFindItemColumn();
+ virtual HMENU GetMenu(bool isFilter, int filterNum, C_Config *c, HMENU themenu);
+ virtual void ProcessMenuResult(int r, bool isFilter, int filterNum, C_Config *c, HWND parent);
+ virtual void InitializeFilterData(int filterNum, C_Config *config);
+};
+
+#endif //_SKINNEDLISTVIEW_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/api__ml_pmp.h b/Src/Plugins/Library/ml_pmp/api__ml_pmp.h
new file mode 100644
index 00000000..44cc638d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/api__ml_pmp.h
@@ -0,0 +1,56 @@
+#ifndef NULLSOFT_ML_PMP_API_H
+#define NULLSOFT_ML_PMP_API_H
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../playlist/api_playlists.h"
+extern api_playlists *playlistsApi;
+#define AGAVE_API_PLAYLISTS playlistsApi
+
+#include "../ml_local/api_mldb.h"
+extern api_mldb *mldbApi;
+#define AGAVE_API_MLDB mldbApi
+
+#include <api/syscb/api_syscb.h>
+extern api_syscb *sysCallbackApi;
+#define WASABI_API_SYSCB sysCallbackApi
+
+#include <api/application/api_application.h>
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../Agave/Language/api_language.h"
+
+#include <api/memmgr/api_memmgr.h>
+extern api_memmgr *memoryManager;
+#define WASABI_API_MEMMGR memoryManager
+
+#include "../ml_wire/api_podcasts.h"
+extern api_podcasts *podcastsApi;
+#define AGAVE_API_PODCASTS podcastsApi
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include "../nu/threadpool/api_threadpool.h"
+extern api_threadpool *threadPoolApi;
+#define WASABI_API_THREADPOOL threadPoolApi
+
+#include "../devices/api_devicemanager.h"
+extern api_devicemanager *deviceManagerApi;
+#define AGAVE_API_DEVICEMANAGER deviceManagerApi
+
+#include "../Agave/AlbumArt/api_albumart.h"
+extern api_albumart *albumArtApi;
+#define AGAVE_API_ALBUMART albumArtApi
+
+#include "../Agave/Metadata/api_metadata.h"
+extern api_metadata *metadataApi;
+#define AGAVE_API_METADATA metadataApi
+
+#include <api/service/svcs/svc_imgload.h>
+
+#endif // !NULLSOFT_ML_PMP_API_H
diff --git a/Src/Plugins/Library/ml_pmp/autofill.cpp b/Src/Plugins/Library/ml_pmp/autofill.cpp
new file mode 100644
index 00000000..f8eb47a4
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/autofill.cpp
@@ -0,0 +1,432 @@
+/*
+** This is adapted from the autofill code in ml_ipod. It was originally written by and is
+** Copyright (C) Will Fisher and Justin Frankel. It remains under the following licence:
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include <windows.h>
+#include <time.h>
+#include "../../General/gen_ml/ml.h"
+#include "pmp.h"
+#include "../../General/gen_ml/itemlist.h"
+#include "DeviceView.h"
+#include "mt19937ar.h"
+#include "config.h"
+#include "../nu/AutoChar.h"
+#include "metadata_utils.h"
+#include "api__ml_pmp.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+extern C_ItemList * getSelectedItems(bool all=false);
+
+typedef struct {
+ wchar_t * albumName;
+ int rating;
+ float ratingf;
+ int lastplayed;
+ int size;
+ bool album;
+ itemRecordW * ice;
+} AutoFillItem;
+
+static void randomizeList(void *list, int elems, int elemsize) {
+ if (elems < 2) return;
+ if (elemsize < 1) return;
+ char *plist=(char*)list;
+ int p;
+ char *tmp=(char*)calloc(elemsize, sizeof(char));
+ if (!tmp) return;
+ for (p = 0; p < elems; p ++) {
+ int np=genrand_int31()%elems;
+ if (p != np) {
+ np*=elemsize;
+ int pp=p*elemsize;
+ memcpy(tmp,plist+pp,elemsize);
+ memcpy(plist+pp,plist+np,elemsize);
+ memcpy(plist+np,tmp,elemsize);
+ }
+ }
+ free(tmp);
+}
+
+#define atoi_NULLOK(s) ((s)?atoi(s):0)
+#define STRCMP_NULLOK(a,b) _wcsicmp(a?a:L"",b?b:L"")
+
+static int sortFunc_icealbums(const void *elem1, const void *elem2) {
+ itemRecordW * a = (itemRecordW *)elem1;
+ itemRecordW * b = (itemRecordW *)elem2;
+ return STRCMP_NULLOK(a->album,b->album);
+}
+
+
+static void autoFill_songfilter(Device * dev, C_ItemList * list, itemRecordListW * results) {
+ if (results) {
+ for(int i=0; i < results->Size; i++) {
+ AutoFillItem * a = (AutoFillItem *)calloc(sizeof(AutoFillItem),1);
+ a->rating = results->Items[i].rating;
+ a->lastplayed = (int)results->Items[i].lastplay;
+ a->size = (int)dev->getTrackSizeOnDevice(&results->Items[i]);
+ a->album = false;
+ a->ice = &results->Items[i];
+ a->albumName=L"arse";
+ list->Add(a);
+ }
+ }
+}
+
+static void autoFill_songfilter(Device * dev, C_ItemList * list, C_ItemList * results) {
+ for(int i=0; i < results->GetSize(); i++) {
+ itemRecordW *r = (itemRecordW*)results->Get(i);
+ AutoFillItem * a = (AutoFillItem *)calloc(sizeof(AutoFillItem),1);
+ a->rating = r->rating;
+ a->lastplayed = (int)r->lastplay;
+ a->size = (int)dev->getTrackSizeOnDevice(r);
+ a->album = false;
+ a->ice = r;
+ a->albumName=L"arse";
+ list->Add(a);
+ }
+}
+
+static void autoFill_albumfilter(Device * dev, C_ItemList * list, itemRecordListW * results, C_ItemList * res = NULL) {
+ if (results)
+ qsort(results->Items,results->Size,sizeof(itemRecordW),sortFunc_icealbums);
+ AutoFillItem * curalbum = NULL;
+ wchar_t * albumName=NULL;
+ int albumSize=0;
+ __int64 lastplayacc=0;
+ int size = res?res->GetSize():(results?results->Size:0);
+ int i;
+ for(i=0; i < size; i++) {
+ itemRecordW * item = res?(itemRecordW*)res->Get(i):&results->Items[i];
+ if(!curalbum || STRCMP_NULLOK(item->album,albumName) != 0) {
+ if(curalbum && albumSize) {
+ curalbum->lastplayed = (int)(lastplayacc / (__int64)albumSize);
+ curalbum->ratingf /= (float)albumSize;
+ list->Add(curalbum);
+ }
+ AutoFillItem * a = (AutoFillItem *)calloc(sizeof(AutoFillItem),1);
+ a->ratingf = (float)item->rating;
+ lastplayacc = item->lastplay;
+ a->size = (int)dev->getTrackSizeOnDevice(item);
+ a->album = true;
+ a->albumName = item->album;
+ curalbum = a;
+ albumName = a->albumName;
+ albumSize++;
+ } else {
+ albumSize++;
+ curalbum->size += (int)dev->getTrackSizeOnDevice(item);
+ curalbum->ratingf += (float)item->rating;
+ lastplayacc += item->lastplay;
+ }
+ }
+ if(curalbum && albumSize) {
+ curalbum->lastplayed = (int)(lastplayacc / (__int64)albumSize);
+ curalbum->ratingf /= (float)albumSize;
+ list->Add(curalbum);
+ }
+
+ //FUCKO: think of something clever to make the ratings relevant
+ for(i=0; i < list->GetSize(); i++) {
+ AutoFillItem * a = (AutoFillItem *)list->Get(i);
+ a->rating = (int)(a->ratingf + 0.5);
+ if(a->rating > 5) a->rating = 5;
+ }
+}
+
+// FUCKO lame O(n^2) gayness. Make this better.
+static void autoFill_addAlbums(C_ItemList * albums, itemRecordListW * songs, C_ItemList * dest) {
+ if (songs) {
+ for(int i=0; i < songs->Size; i++)
+ for(int j=0; j < albums->GetSize(); j++)
+ if(STRCMP_NULLOK(songs->Items[i].album,(wchar_t *)albums->Get(j))==0)
+ dest->Add(&songs->Items[i]);
+ }
+}
+
+static int sortFunc_autofill(const void *elem1, const void *elem2)
+{
+ AutoFillItem *a=(AutoFillItem *)*(void **)elem1;
+ AutoFillItem *b=(AutoFillItem *)*(void **)elem2;
+ return a->lastplayed - b->lastplayed;
+}
+
+itemRecordListW * generateAutoFillList(DeviceView * dev, C_Config * config)
+{
+ // Settings for an autofill
+ int lastAutofill=config->ReadInt(L"LastAutoFill",(int)time(NULL));
+ int fillpc = config->ReadInt(L"FillPercent",90);; // fill device 90% full.
+ bool byAlbum = dev->dev->getDeviceCapacityTotal() > (__int64)1500000000;
+ byAlbum = config->ReadInt(L"AlbumAutoFill",byAlbum?1:0)==1;
+ wchar_t * afquery = _wcsdup(config->ReadString(L"AutoFillQuery",L"length > 30"));
+ wchar_t * squery = _wcsdup(config->ReadString(L"SyncQuery",L"type = 0"));
+ char * tmp2 = AutoCharDup(config->ReadString(L"AutoFillRatings",L"")); // ratings ratio string (eg "3:1:1:1:0:0")
+ int len = 2*strlen(tmp2)+2;
+ char * tmp = (char*)calloc(len,sizeof(char));
+ strncpy(tmp,tmp2,len); free(tmp2);
+
+ int ratingsRatios[6]={0,0,0,0,0,0,};
+ bool useratings=true;
+ if(tmp[0]==0) useratings=false;
+ int i=0;
+ int ratingsRatiosTotal=0;
+ if(useratings) {
+ int len = strlen(tmp);
+ while(tmp[++i]!=0) if(tmp[i]==':') tmp[i]=0;
+ tmp[i+1]=0;
+ char * p = &tmp[0];
+ for(i=0; i<6; i++) {
+ if(p > len + tmp) break;
+ if(*p == 0) break;
+ ratingsRatios[i] = atoi_NULLOK(p);
+ ratingsRatiosTotal+=ratingsRatios[i];
+ p+=strlen(p)+1;
+ }
+ }
+ if(ratingsRatiosTotal==0) {
+ ratingsRatiosTotal=6;
+ for(i=0; i<6; i++) ratingsRatios[i]=1;
+ }
+
+ //construct query
+ wchar_t query[2048]=L"";
+ if(afquery[0]==0) wsprintf(query,L"%s",squery);
+ else wsprintf(query,L"(%s) AND (%s)",squery,afquery);
+
+ //run query
+ itemRecordListW *items = 0;
+ itemRecordListW *results = AGAVE_API_MLDB?AGAVE_API_MLDB->Query(query):0;
+ if (results)
+ {
+ C_ItemList * songList = new C_ItemList;
+
+ if(!byAlbum) autoFill_songfilter(dev->dev,songList, results);
+ else autoFill_albumfilter(dev->dev,songList, results);
+
+ //dump into ratings "bins"
+ C_ItemList * songs[7];
+ for(i=0; i<7; i++) songs[i] = new C_ItemList;
+ for(i=0; i<songList->GetSize(); i++) {
+ if(useratings) {
+ int rating = ((AutoFillItem*)songList->Get(i))->rating;
+ switch (rating) {
+ case 1: songs[1]->Add(songList->Get(i)); break;
+ case 2: songs[2]->Add(songList->Get(i)); break;
+ case 3: songs[3]->Add(songList->Get(i)); break;
+ case 4: songs[4]->Add(songList->Get(i)); break;
+ case 5: songs[5]->Add(songList->Get(i)); break;
+ default: songs[0]->Add(songList->Get(i)); break;
+ }
+ } else songs[0]->Add(songList->Get(i));
+ }
+
+ for(i=0; i<6; i++)
+ {
+ randomizeList(songs[i]->GetAll(),songs[i]->GetSize(),sizeof(void*)); // randomize
+ qsort(songs[i]->GetAll(),songs[i]->GetSize(),sizeof(void*),sortFunc_autofill); // sort by date
+ }
+
+
+ __int64 sizeToFill, totalSize=0;
+
+ sizeToFill = (((__int64)fillpc) * dev->dev->getDeviceCapacityTotal()) / ((__int64)100);
+ //sizeToFill = dev->dev->getDeviceCapacityTotal();
+
+ C_ItemList * alreadyIn = new C_ItemList;
+ C_ItemList * notGoingIn = new C_ItemList;
+ C_ItemList * songsToSend = new C_ItemList;
+
+ if(!byAlbum) {
+
+ int numTracks = dev->dev->getPlaylistLength(0);
+ /* Hmm. This should make autofill just replace selected songs
+ ** gonna leave it out for now, until i have good ipod shuffle (et al) support.
+ C_ItemList * selected = getSelectedItems(false);
+ int j=0;
+ if(selected) {
+ if(selected->GetSize() > 0 && selected->GetSize() < numTracks) {
+ for(i=0; i<numTracks; i++) {
+ if(j < selected->GetSize()) if((songid_t)selected->Get(j) == dev->dev->getPlaylistTrack(0,i)) {
+ j++;
+ notGoingIn->Add(selected->Get(j));
+ } else alreadyIn->Add(dev->dev->getPlaylistTrack(0,i));
+ }
+ }
+ delete selected;
+ }
+ */
+ // otherwise replace ones played since last autofill, unless none played, in which case replace all
+ if(notGoingIn->GetSize() == 0) {
+ delete alreadyIn;
+ alreadyIn = new C_ItemList;
+ if(lastAutofill > 0)
+ {
+ for(i=0; i<numTracks; ++i)
+ if(dev->dev->getTrackLastPlayed(dev->dev->getPlaylistTrack(0,i)) >= lastAutofill) break;
+
+ if (i >= numTracks) // this means nothing has been played, so in this case we set everything to be replaced
+ {
+ for(i=0; i<numTracks; ++i) notGoingIn->Add((void*)dev->dev->getPlaylistTrack(0,i));
+ }
+ else
+ {
+ for(i=0; i < numTracks; ++i)
+ {
+ songid_t s = dev->dev->getPlaylistTrack(0,i);
+ if(dev->dev->getTrackLastPlayed(s) <= lastAutofill) alreadyIn->Add((void*)s);
+ }
+ }
+ }
+ }
+
+ //remove our already selected songs from the bins
+ for(i=0; i<notGoingIn->GetSize(); ++i) {
+ songid_t s = (songid_t)notGoingIn->Get(i);
+ for (int b=0; b < 6; b ++)
+ {
+ for(int j=0; j<songs[b]->GetSize(); ++j)
+ {
+ if(!compareItemRecordAndSongId(((AutoFillItem *)songs[b]->Get(j))->ice,s, dev->dev))
+ { // FUCKO
+ songs[6]->Add(songs[b]->Get(j));
+ songs[b]->Del(j);
+ b=6;
+ break;
+ }
+ }
+ }
+ }
+ for(i=0; i<alreadyIn->GetSize(); ++i) {
+ songid_t s = (songid_t)alreadyIn->Get(i);
+ for (int b= 0; b < 6; b ++)
+ {
+ for(int j=0; j<songs[b]->GetSize(); ++j)
+ {
+ if(!compareItemRecordAndSongId(((AutoFillItem *)songs[b]->Get(j))->ice,s, dev->dev))
+ {
+ songsToSend->Add(((AutoFillItem *)songs[b]->Get(j))->ice);
+ songs[b]->Del(j);
+ b=6;
+ break;
+ }
+ }
+ }
+
+ __int64 size = (__int64)dev->dev->getTrackSize(s);
+
+ if(totalSize + size >= sizeToFill) break;
+ totalSize += size;
+ }
+ } // end if(!byAlbum)
+
+ //select the rest!
+ C_ItemList * albumsToSend = new C_ItemList;
+
+ while(true) {
+ int bin=0;
+ if (useratings)
+ {
+ int val = genrand_int31() % ratingsRatiosTotal;
+ bin=-1;
+ while(val>=0 && bin < 5) val -= ratingsRatios[++bin];
+
+ if (bin > 5) bin=5;
+ else if(bin<0) bin=0;
+
+ bin = 5-bin; // work in reverse mapped bin now (since ratingsRatio is 5-0)
+
+ int sbin=bin;
+ while(bin<6&&songs[bin]->GetSize()<=0) { bin++; } // if our bin is empty, go to a higher bin
+
+ if (bin > 5) // out of higher rated bins, go lower
+ {
+ bin=sbin-1; // start at one lower than where we started
+ while (bin >= 0 && songs[bin]->GetSize()<=0) bin--; // if our bin is still empty, go to a lower bin
+ }
+
+ if (bin < 0) bin = 6;
+ } else bin = songs[0]->GetSize()?0:6;
+ if(!songs[bin])
+ break;
+ int bsize=songs[bin]->GetSize();
+ if (bsize<1)
+ break;
+
+ // JF> will had a %(bsize/2) here effectively, which I think is too major.
+ // I propose a nice simple weighted thing, where stuff near the beginning is more likely
+ // to be picked.
+ int snum = genrand_int31() % bsize;
+ if (genrand_int31()&1) snum/=2; // half the time, just use the first half (less recently played items)
+
+ int size = ((AutoFillItem *)songs[bin]->Get(snum))->size;
+ if(size <= 0)
+ {
+ songs[bin]->Del(snum);
+ continue;
+ }
+ totalSize += (__int64)size;
+
+ /*{
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"not full. must fill: %d, so far: %d, this track: %d",(int)sizeToFill,(int)totalSize,(int)size);
+ OutputDebugString(buf);
+ }*/
+
+ if(totalSize >= sizeToFill)
+ {
+ /*OutputDebugString(L"full");*/
+ break;
+ }
+
+ if(byAlbum) {
+ albumsToSend->Add(((AutoFillItem *)songs[bin]->Get(snum))->albumName);
+ } else {
+ songsToSend->Add(((AutoFillItem *)songs[bin]->Get(snum))->ice);
+ }
+ songs[bin]->Del(snum);
+ }
+
+ // dump our albums into songsToSend
+ if(albumsToSend->GetSize() > 0)
+ autoFill_addAlbums(albumsToSend, results, songsToSend);
+
+ items = new itemRecordListW;
+ items->Alloc = songsToSend->GetSize();
+ items->Size = songsToSend->GetSize();
+ items->Items = (itemRecordW*)calloc(items->Alloc, sizeof(itemRecordW));
+
+ for(i=0; i<songsToSend->GetSize(); ++i) copyRecord(&items->Items[i],(itemRecordW*)songsToSend->Get(i));
+
+ // clear stuff up
+ for(i=0; i < songList->GetSize(); i++) free(songList->Get(i));
+ for(i=0; i<7; ++i) delete songs[i];
+ delete songsToSend;
+ delete albumsToSend;
+ delete songList;
+
+ delete notGoingIn;
+ delete alreadyIn;
+
+ AGAVE_API_MLDB->FreeRecordList(results); //free memory
+ }
+
+ free(afquery);
+ free(squery);
+ free(tmp);
+
+ return items;
+}
diff --git a/Src/Plugins/Library/ml_pmp/banner.cpp b/Src/Plugins/Library/ml_pmp/banner.cpp
new file mode 100644
index 00000000..9e3861c5
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/banner.cpp
@@ -0,0 +1,216 @@
+#include ".\banner.h"
+#include "..\gen_ml\graphics.h"
+
+
+MLBanner::MLBanner(void)
+{
+ bmpBck = NULL;
+ bmpLogo = NULL;
+ bmpLogoMask = NULL;
+ bmpBanner = NULL;
+
+ oldWndProc = NULL;
+
+ color1 = RGB(0,0,0);
+ color2 = RGB(255,255,255);
+
+ hInstance = NULL;
+ logoResId = 0;
+ bgndResId = 0;
+ m_hwnd = 0;
+
+ SetRect(&rcBanner, 0,0,0,0);
+}
+MLBanner::~MLBanner(void)
+{
+ DestroyImages();
+ SetWindowLongPtr(m_hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc);
+ oldWndProc = NULL;
+}
+
+void MLBanner::SetColors(int color1, int color2)
+{
+ this->color1 = color1;
+ this->color2 = color2;
+ ReloadImages();
+}
+
+void MLBanner::SetImages(HINSTANCE hInstance, int bgndResId, int logoResId)
+{
+ this->hInstance = hInstance;
+ this->logoResId = logoResId;
+ this->bgndResId = bgndResId;
+ ReloadImages();
+}
+
+void MLBanner::ReloadImages(void)
+{
+ DestroyImages();
+ if (hInstance)
+ {
+ if (bgndResId)
+ {
+ bmpBck = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(bgndResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpBck) bmpBck = PatchBitmapColors24(bmpBck, color1, color2, Filter1);
+ }
+ if (logoResId)
+ {
+ bmpLogo = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(logoResId), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
+ if (bmpLogo)
+ {
+ bmpLogoMask = CreateBitmapMask(bmpLogo, 1,1);
+ }
+ }
+ }
+
+}
+
+void MLBanner::DestroyImages(void)
+{
+ if (bmpBck) DeleteObject(bmpBck);
+ bmpBck = NULL;
+
+ if (bmpLogo) DeleteObject(bmpLogo);
+ bmpLogo = NULL;
+
+ if (bmpLogoMask) DeleteObject(bmpLogoMask);
+ bmpLogoMask = NULL;
+
+ if (bmpBanner) DeleteObject(bmpBanner);
+ bmpBanner = NULL;
+}
+
+
+
+void MLBanner::UpdateBunnerBmp(void)
+{
+ if (bmpBanner) DeleteObject(bmpBanner);
+
+ HDC hdc = GetDC(m_hwnd);
+
+ bmpBanner = CreateCompatibleBitmap(hdc, rcBanner.right, rcBanner.bottom);
+ HDC memDstDC = CreateCompatibleDC (hdc);
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ SelectObject(memDstDC, bmpBanner);
+ SelectObject(memSrcDC, bmpBck);
+
+ for (int i = 0; i < rcBanner.right; i++)
+ {
+ BitBlt(memDstDC,
+ i,0,
+ 1, rcBanner.bottom,
+ memSrcDC,
+ 0,0,
+ SRCCOPY);
+
+ }
+
+ BITMAP bm;
+ GetObject(bmpLogo, sizeof(BITMAP), &bm);
+
+ SelectObject(memSrcDC, bmpLogoMask);
+ BitBlt(memDstDC,
+ 6,
+ max(2, (rcBanner.bottom - bm.bmHeight) / 2),
+ min(rcBanner.right - 4, bm.bmWidth),
+ min(rcBanner.bottom - 2, bm.bmHeight),
+ memSrcDC,
+ 0,0,
+ SRCAND);
+
+ SelectObject(memSrcDC, bmpLogo);
+ BitBlt(memDstDC,
+ 6,
+ max(2, (rcBanner.bottom - bm.bmHeight) / 2),
+ min(rcBanner.right - 4, bm.bmWidth),
+ min(rcBanner.bottom - 2, bm.bmHeight),
+ memSrcDC,
+ 0,0,
+ SRCPAINT);
+
+ ReleaseDC(m_hwnd, hdc);
+ DeleteDC(memDstDC);
+ DeleteDC(memSrcDC);
+
+}
+
+void MLBanner::Init(HWND hwnd)
+{
+ m_hwnd = hwnd;
+ SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)this);
+ oldWndProc= (WNDPROC) SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newWndProc);
+ UpdateBunnerBmp();
+}
+
+INT_PTR CALLBACK MLBanner::newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ MLBanner *banner = (MLBanner*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(uMsg)
+ {
+ case WM_SIZE:
+ if (SIZE_MINIMIZED != wParam)
+ {
+ SetRect(&banner->rcBanner, 0,0,LOWORD(lParam),HIWORD(lParam));
+ banner->UpdateBunnerBmp();
+ }
+ break;
+ case WM_ERASEBKGND:
+ {
+ HDC hdc = GetDC(hwndDlg);
+ if (banner->bmpBanner)
+ {
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ SelectObject(memSrcDC, banner->bmpBanner);
+ StretchBlt( hdc,
+ banner->rcBanner.left,
+ banner->rcBanner.top,
+ banner->rcBanner.right - banner->rcBanner.left,
+ banner->rcBanner.bottom - banner->rcBanner.top,
+ memSrcDC,
+ banner->rcBanner.left,
+ banner->rcBanner.top,
+ banner->rcBanner.right - banner->rcBanner.left,
+ banner->rcBanner.bottom - banner->rcBanner.top,
+ SRCCOPY);
+ DeleteDC(memSrcDC);
+ }
+ ReleaseDC(hwndDlg, hdc);
+ }
+ return TRUE;
+ case WM_PAINT:
+ {
+ PAINTSTRUCT pt;
+ HDC hdc = BeginPaint(hwndDlg, &pt);
+ if (!banner->bmpBanner)
+ {
+ SetRect(&banner->rcBanner, 0,0,pt.rcPaint.right - pt.rcPaint.left, pt.rcPaint.bottom - pt.rcPaint.top);
+ banner->UpdateBunnerBmp();
+ }
+ if (banner->bmpBanner)
+ {
+ HDC memSrcDC = CreateCompatibleDC (hdc);
+ SelectObject(memSrcDC, banner->bmpBanner);
+ StretchBlt( hdc,
+ pt.rcPaint.left,
+ pt.rcPaint.top,
+ pt.rcPaint.right - pt.rcPaint.left,
+ pt.rcPaint.bottom - pt.rcPaint.top,
+ memSrcDC,
+ pt.rcPaint.left,
+ pt.rcPaint.top,
+ pt.rcPaint.right - pt.rcPaint.left,
+ pt.rcPaint.bottom - pt.rcPaint.top,
+ SRCCOPY);
+ DeleteDC(memSrcDC);
+ ValidateRect(hwndDlg, &pt.rcPaint);
+ }
+ EndPaint(hwndDlg, &pt);
+ }
+ break;
+ }
+
+ return CallWindowProc(banner->oldWndProc, hwndDlg, uMsg, wParam, lParam);
+}
+
+ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/banner.h b/Src/Plugins/Library/ml_pmp/banner.h
new file mode 100644
index 00000000..85ce0fc1
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/banner.h
@@ -0,0 +1,44 @@
+#ifndef NULLSOFT_ML_BANNER_HEADER
+#define NULLSOFT_ML_BANNER_HEADER
+
+#include <windows.h>
+
+class MLBanner
+{
+public:
+ MLBanner(void);
+ ~MLBanner(void);
+
+public:
+
+ void SetColors(int color1, int color2);
+ void SetImages(HINSTANCE hInstance, int bgndResId, int logoResId);
+ void Init(HWND hwnd);
+ void ReloadImages(void);
+
+protected:
+ void DestroyImages(void);
+ void UpdateBunnerBmp(void);
+ static INT_PTR CALLBACK newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+private:
+
+ HWND m_hwnd;
+ HBITMAP bmpBck;
+ HBITMAP bmpLogo;
+ HBITMAP bmpLogoMask;
+ HBITMAP bmpBanner;
+
+ WNDPROC oldWndProc;
+
+ HINSTANCE hInstance;
+ int logoResId;
+ int bgndResId;
+
+ int color1;
+ int color2;
+
+ RECT rcBanner;
+};
+
+#endif // NULLSOFT_ML_BANNER_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/config.cpp b/Src/Plugins/Library/ml_pmp/config.cpp
new file mode 100644
index 00000000..c14f110d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/config.cpp
@@ -0,0 +1,50 @@
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+
+#include "config.h"
+
+C_Config::~C_Config()
+{
+ free(m_inifile);
+ free(m_section);
+}
+
+C_Config::C_Config(wchar_t *ini, wchar_t *section, C_Config * globalWrite) : globalWrite(globalWrite)
+{
+ m_strbuf[0]=0;
+ m_inifile=_wcsdup(ini);
+ m_section=_wcsdup(section);
+}
+
+void C_Config::WriteInt(wchar_t *name, int value, wchar_t *section)
+{
+ wchar_t buf[32] = {0};
+ wsprintf(buf,L"%d",value);
+ WriteString(name,buf,section);
+}
+
+int C_Config::ReadInt(wchar_t *name, int defvalue, wchar_t *section)
+{
+ return GetPrivateProfileInt(section?section:m_section,name,defvalue,m_inifile);
+}
+
+wchar_t *C_Config::WriteString(wchar_t *name, wchar_t *string, wchar_t *section)
+{
+ if(globalWrite && !section) globalWrite->WriteString(name,string,m_section);
+ WritePrivateProfileString(section?section:m_section,name,string,m_inifile);
+ return name;
+}
+
+wchar_t *C_Config::ReadString(wchar_t *name, wchar_t *defstr, wchar_t *section)
+{
+ static wchar_t foobuf[] = L"___________config_lameness___________";
+ m_strbuf[0]=0;
+ GetPrivateProfileString(section?section:m_section,name,foobuf,m_strbuf,sizeof(m_strbuf)/sizeof(wchar_t),m_inifile);
+ if (!lstrcmp(foobuf,m_strbuf)) return defstr;
+
+ m_strbuf[sizeof(m_strbuf)/sizeof(wchar_t)-1]=0;
+ return m_strbuf;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/config.h b/Src/Plugins/Library/ml_pmp/config.h
new file mode 100644
index 00000000..1343fe6f
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/config.h
@@ -0,0 +1,21 @@
+#ifndef _C_CONFIG_H_
+#define _C_CONFIG_H_
+
+class C_Config
+{
+ public:
+ C_Config(wchar_t *ini,wchar_t *section=L"ml_pmp", C_Config * globalWrite=NULL);
+ ~C_Config();
+ void WriteInt(wchar_t *name, int value, wchar_t *section=NULL);
+ wchar_t *WriteString(wchar_t *name, wchar_t *string, wchar_t *section=NULL);
+ int ReadInt(wchar_t *name, int defvalue, wchar_t *section=NULL);
+ wchar_t *ReadString(wchar_t *name, wchar_t *defvalue, wchar_t *section=NULL);
+ wchar_t *GetIniFile(){return m_inifile;}
+ private:
+ wchar_t m_strbuf[8192];
+ wchar_t *m_inifile;
+ wchar_t *m_section;
+ C_Config * globalWrite;
+};
+
+#endif//_C_CONFIG_H_
diff --git a/Src/Plugins/Library/ml_pmp/editinfo.cpp b/Src/Plugins/Library/ml_pmp/editinfo.cpp
new file mode 100644
index 00000000..cbd40630
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/editinfo.cpp
@@ -0,0 +1,790 @@
+#include "main.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include <api/service/waServiceFactory.h>
+#include <api/service/svcs/svc_imgload.h>
+#include <api/service/svcs/svc_imgwrite.h>
+#include <api/memmgr/api_memmgr.h>
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+
+extern C_ItemList devices;
+extern HWND hwndMediaView;
+
+static C_ItemList * editItems;
+static Device * editDevice;
+
+typedef struct {
+ int w;
+ int h;
+ ARGB32 * data;
+} editinfo_image;
+
+static INT_PTR CALLBACK editInfo_commit_dialogProc(HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static int i;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ i=0;
+ SetWindowText(hwnd,WASABI_API_LNGSTRINGW(IDS_SETTING_METADATA));
+ SendDlgItemMessage(hwnd,IDC_PROGRESS,PBM_SETRANGE,0,MAKELPARAM(0, editItems->GetSize()));
+ SetTimer(hwnd,1,5,NULL);
+
+ if (FALSE != CenterWindow(hwnd, (HWND)lParam))
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+
+ break;
+ case WM_TIMER:
+ if(wParam == 1) {
+ KillTimer(hwnd,1);
+ HWND hwndDlg = GetParent(hwnd);
+ editinfo_image * image = (editinfo_image *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ SendDlgItemMessage(hwnd,IDC_PROGRESS,PBM_SETPOS,i,0);
+ if(i < editItems->GetSize()) {
+ int metadata_edited=0;
+ songid_t song=(songid_t)editItems->Get(i);
+ time_t t; time(&t);
+ editDevice->setTrackLastUpdated(song,t);
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ARTIST)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_ARTIST,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackArtist(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_TITLE)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_TITLE,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackTitle(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ALBUM)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUM,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackAlbum(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_GENRE)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_GENRE,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackGenre(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_TRACK)) {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_TRACK,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ int n=_wtoi(blah);
+ if (n <= 0) n=-1;
+ editDevice->setTrackTrackNum(song,n);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_DISC)) {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_DISC,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ int n=_wtoi(blah);
+ if (n <= 0) n=-1;
+ editDevice->setTrackDiscNum(song,n);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_YEAR)) {
+ wchar_t blah[64] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_YEAR,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ int n=_wtoi(blah);
+ if (n <= 0) n=-1;
+ editDevice->setTrackYear(song,n);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ALBUMARTIST)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUMARTIST,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackAlbumArtist(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_PUBLISHER)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_PUBLISHER,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackPublisher(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_COMPOSER)) {
+ wchar_t blah[512] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT_COMPOSER,blah,sizeof(blah)/sizeof(wchar_t)-1);
+ blah[sizeof(blah)/sizeof(wchar_t)-1]=0;
+ editDevice->setTrackComposer(song,blah);
+ metadata_edited++;
+ }
+ if(IsDlgButtonChecked(hwndDlg,IDC_CHECK_ALBUMART)) {
+ if(!image) editDevice->setArt(song,NULL,0,0);
+ else editDevice->setArt(song,image->data,image->w,image->h);
+ }
+
+ if (metadata_edited)
+ {
+ editDevice->extraActions(DEVICE_DONE_SETTING, (intptr_t)song,0,0);
+ }
+ SetTimer(hwnd,1,5,NULL);
+ }
+ else {
+ editDevice->commitChanges();
+ EndDialog(hwnd,0);
+ }
+ i++;
+ }
+ break;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_ABORT) {
+ editDevice->commitChanges();
+ EndDialog(hwnd,-1);
+ }
+ break;
+ }
+ return 0;
+}
+
+static ARGB32 * loadImg(const void * data, int len, int *w, int *h) {
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for(int i=0; i<n; i++) {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if(sf) {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if(l) {
+ if(l->testData(data,len)) {
+ ARGB32* ret = l->loadImage(data,len,w,h);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+static void * loadFile(const wchar_t * file, int &len) {
+ len=0;
+ FILE * f = _wfopen(file,L"rb");
+ if(!f) return 0;
+ fseek(f,0,2);
+ len = ftell(f);
+ if(!len) {fclose(f); return 0;}
+ fseek(f,0,0);
+ void * data = calloc(len, sizeof(void*));
+ fread(data,len,1,f);
+ fclose(f);
+ return data;
+}
+
+static ARGB32 * loadImgFromFile(const wchar_t * file, int *w, int *h) {
+ int len;
+ void * d = loadFile(file,len);
+ if(!d) return 0;
+ ARGB32 * im = loadImg(d,len,w,h);
+ free(d);
+ return im;
+}
+
+static void * writeImg(const ARGB32 *data, int w, int h, int *length, const wchar_t *ext) {
+ if(!ext || !*ext) return NULL;
+ if(*ext == L'.') ext++;
+ FOURCC imgwrite = svc_imageWriter::getServiceType();
+ int n = plugin.service->service_getNumServices(imgwrite);
+ for(int i=0; i<n; i++) {
+ waServiceFactory *sf = plugin.service->service_enumService(imgwrite,i);
+ if(sf) {
+ svc_imageWriter * l = (svc_imageWriter*)sf->getInterface();
+ if(l) {
+ if(wcsstr(l->getExtensions(),ext)) {
+ void* ret = l->convert(data,32,w,h,length);
+ sf->releaseInterface(l);
+ return ret;
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ return NULL;
+}
+
+static void writeFile(const wchar_t *file, void * data, int length) {
+ FILE *f = _wfopen(file,L"wb");
+ if(!f) return;
+ fwrite(data,length,1,f);
+ fclose(f);
+}
+
+static void writeImageToFile(ARGB32 * img, int w, int h, const wchar_t *file) {
+ int length=0;
+ void * data = writeImg(img,w,h,&length,wcsrchr(file,L'.'));
+ if(data) {
+ writeFile(file,data,length);
+ WASABI_API_MEMMGR->sysFree(data);
+ }
+}
+
+static void enableArt(HWND hwndDlg, bool combo, BOOL enable) {
+ if(combo) EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUMART),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_ARTINFO),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PICTUREHOLDER),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_ART_CHANGE),enable);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_ART_CLEAR),enable);
+}
+
+static int checkEditInfoClick(HWND hwndDlg, POINT p, int item, int check, bool art=false) {
+ if(!IsWindowEnabled(GetDlgItem(hwndDlg,check))) return 0;
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg, item), &r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r.right);
+ if (PtInRect(&r, p) && !IsDlgButtonChecked(hwndDlg, check)) {
+ CheckDlgButton(hwndDlg, check, TRUE);
+ if(art) enableArt(hwndDlg,false,TRUE);
+ else EnableWindow(GetDlgItem(hwndDlg, item), TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);
+ PostMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, item), TRUE);
+ return 1;
+ }
+ return 0;
+}
+
+static HBITMAP getBitmap(const editinfo_image * image, HWND parent) {
+ BITMAPINFO info={0};
+ info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ info.bmiHeader.biWidth = image->w;
+ info.bmiHeader.biHeight = -image->h;
+ info.bmiHeader.biPlanes = 1;
+ info.bmiHeader.biBitCount = 32;
+ info.bmiHeader.biCompression = BI_RGB;
+ HDC dc = GetDC(parent);
+ HBITMAP bm = CreateCompatibleBitmap(dc,image->w,image->h);
+ SetDIBits(dc,bm,0,image->h,image->data,&info,DIB_RGB_COLORS);
+ ReleaseDC(parent,dc);
+ return bm;
+}
+
+static HBITMAP getBitmap(pmpart_t art, Device * dev, HWND parent) {
+ int w,h;
+ editDevice->getArtNaturalSize(art,&w,&h);
+ if(w == 0 || h == 0) return NULL;
+ ARGB32 * bits = (ARGB32 *)calloc(w * h, sizeof(ARGB32));
+ if(!bits) return NULL;
+ editDevice->getArtData(art,bits);
+ editinfo_image im={w,h,bits};
+ HBITMAP bm = getBitmap(&im,parent);
+ free(bits);
+ return bm;
+}
+
+static void setBitmap(const editinfo_image * image, HWND hwndDlg, bool init=false) {
+ if(!init) {
+ CheckDlgButton(hwndDlg,IDC_CHECK_ALBUMART,BST_CHECKED);
+ enableArt(hwndDlg,false,TRUE);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_ART_CLEAR,0),0);
+ }
+ HQSkinBitmap temp(image->data, image->w, image->h); // wrap into a SkinBitmap (no copying involved)
+ BltCanvas newImage(90,90);
+ temp.stretch(&newImage, 0, 0, 90, 90);
+ editinfo_image i = {90,90,(ARGB32*)newImage.getBits()};
+ HBITMAP bm = getBitmap(&i,hwndDlg);
+ HBITMAP bmold = (HBITMAP)SendDlgItemMessage(hwndDlg,IDC_PICTUREHOLDER,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)bm);
+ if(bmold) DeleteObject(bmold);
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)image);
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"%dx%d",image->w,image->h);
+ SetDlgItemText(hwndDlg,IDC_ARTINFO,buf);
+}
+
+static void GetSize(HBITMAP bm,int &w, int &h,HWND hwndDlg) {
+ HDC dc = GetDC(hwndDlg);
+ BITMAPINFO info={0};
+ info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ GetDIBits(dc,bm,0,0,NULL,&info,DIB_RGB_COLORS);
+ w = abs(info.bmiHeader.biWidth);
+ h = abs(info.bmiHeader.biHeight);
+ ReleaseDC(hwndDlg,dc);
+}
+
+static void setBitmap(HBITMAP bm, HWND hwndDlg) {
+ editinfo_image* image = (editinfo_image*)calloc(1, sizeof(editinfo_image));
+ GetSize(bm,image->w,image->h,hwndDlg);
+ image->data = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*image->w*image->h);
+ BITMAPINFO info={0};
+ info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ info.bmiHeader.biWidth = image->w;
+ info.bmiHeader.biHeight = -image->h;
+ info.bmiHeader.biPlanes = 1;
+ info.bmiHeader.biBitCount = 32;
+ info.bmiHeader.biCompression = BI_RGB;
+ HDC dc = GetDC(hwndDlg);
+ GetDIBits(dc,bm,0,image->h,image->data,&info,DIB_RGB_COLORS);
+ ReleaseDC(hwndDlg,dc);
+ setBitmap(image,hwndDlg);
+}
+
+static INT_PTR CALLBACK editInfo_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ wchar_t last_artist[2048]=L"", last_title[2048]=L"", last_album[2048]=L"", last_genre[2048]=L"", last_albumartist[2048]=L"", last_publisher[2048]=L"", last_composer[2048]=L"";
+ pmpart_t last_albumart=NULL;
+ int last_year=-1, last_track=-1, last_disc=-1;
+ bool disable_artist=0, disable_title=0, disable_album=0, disable_genre=0, disable_year=0, disable_track=0, disable_disc=0, disable_albumartist=0, disable_publisher=0, disable_composer=0, disable_albumart=0;
+ int fieldBits = (int)editDevice->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldBits) fieldBits = -1;
+ disable_artist = (fieldBits & SUPPORTS_ARTIST)==0;
+ disable_title = (fieldBits & SUPPORTS_TITLE)==0;
+ disable_album = (fieldBits & SUPPORTS_ALBUM)==0;
+ disable_genre = (fieldBits & SUPPORTS_GENRE)==0;
+ disable_year = (fieldBits & SUPPORTS_YEAR)==0;
+ disable_track = (fieldBits & SUPPORTS_TRACKNUM)==0;
+ disable_disc = (fieldBits & SUPPORTS_DISCNUM)==0;
+ disable_albumartist = (fieldBits & SUPPORTS_ALBUMARTIST)==0;
+ disable_publisher = (fieldBits & SUPPORTS_PUBLISHER)==0;
+ disable_composer = (fieldBits & SUPPORTS_COMPOSER)==0;
+ disable_albumart = (fieldBits & SUPPORTS_ALBUMART)==0;
+
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
+
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ARTIST),!disable_artist);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_TITLE),!disable_title);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUM),!disable_album);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_GENRE),!disable_genre);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_YEAR),!disable_year);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_TRACK),!disable_track);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_DISC),!disable_disc);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUMARTIST),!disable_albumartist);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_PUBLISHER),!disable_publisher);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_COMPOSER),!disable_composer);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_CHECK_ALBUMART),!disable_albumart);
+ //enableArt(hwndDlg,true,!disable_albumart);
+
+ int l=editItems->GetSize();
+ for(int i=0;i<l;i++) {
+ wchar_t buf[2048]=L"";
+ songid_t song=(songid_t)editItems->Get(i);
+ if(!disable_artist)
+ {
+ buf[0]=0;
+ editDevice->getTrackArtist(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_artist[0]) lstrcpyn(last_artist,buf,2048);
+ else if(lstrcmp(buf,last_artist)) disable_artist=true;
+ }
+ if(!disable_albumartist)
+ {
+ buf[0]=0;
+ editDevice->getTrackAlbumArtist(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_albumartist[0]) lstrcpyn(last_albumartist,buf,2048);
+ else if(lstrcmp(buf,last_albumartist)) disable_albumartist=true;
+ }
+ if(!disable_publisher)
+ {
+ buf[0]=0;
+ editDevice->getTrackPublisher(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_publisher[0]) lstrcpyn(last_publisher,buf,2048);
+ else if(lstrcmp(buf,last_publisher)) disable_publisher=true;
+ }
+ if(!disable_composer)
+ {
+ buf[0]=0;
+ editDevice->getTrackComposer(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_composer[0]) lstrcpyn(last_composer,buf,2048);
+ else if(lstrcmp(buf,last_composer)) disable_composer=true;
+ }
+ if(!disable_title)
+ {
+ buf[0]=0;
+ editDevice->getTrackTitle(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_title[0])
+ lstrcpyn(last_title,buf,2048);
+ else if(lstrcmp(buf,last_title)) disable_title=true;
+ }
+ if(!disable_album)
+ {
+ buf[0]=0;
+ editDevice->getTrackAlbum(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_album[0]) lstrcpyn(last_album,buf,2048);
+ else if(lstrcmp(buf,last_album)) disable_album=true;
+ }
+ if(!disable_genre)
+ {
+ buf[0]=0;
+ editDevice->getTrackGenre(song,buf,sizeof(buf)/sizeof(wchar_t));
+ if(!buf[0]);
+ else if(!last_genre[0]) lstrcpyn(last_genre,buf,2048);
+ else if(lstrcmp(buf,last_genre)) disable_genre=true;
+ }
+ if(!disable_year)
+ {
+ int val=editDevice->getTrackYear(song);
+ if(val <= 0);
+ else if(last_year==-1) last_year=val;
+ else if(last_year!=val) disable_year=true;
+ }
+ if(!disable_track)
+ {
+ int val=editDevice->getTrackTrackNum(song);
+ if(val <= 0);
+ else if(last_track==-1) last_track=val;
+ else if(last_track!=val) disable_track=true;
+ }
+ if(!disable_disc)
+ {
+ int val=editDevice->getTrackDiscNum(song);
+ if(val <= 0);
+ else if(last_disc==-1) last_disc=val;
+ else if(last_disc!=val) disable_disc=true;
+ }
+ if(!disable_albumart)
+ {
+ pmpart_t a = editDevice->getArt(song);
+ if(!a);
+ else if(!last_albumart) { editDevice->releaseArt(last_albumart); last_albumart = a; }
+ else if(!editDevice->artIsEqual(a,last_albumart)) { disable_albumart=true; editDevice->releaseArt(a); editDevice->releaseArt(last_albumart); last_albumart=0; }
+ }
+ }
+ if(!disable_artist && last_artist) SetDlgItemText(hwndDlg,IDC_EDIT_ARTIST,last_artist);
+ if(!disable_albumartist && last_albumartist) SetDlgItemText(hwndDlg,IDC_EDIT_ALBUMARTIST,last_albumartist);
+ if(!disable_publisher && last_publisher) SetDlgItemText(hwndDlg,IDC_EDIT_PUBLISHER,last_publisher);
+ if(!disable_composer && last_composer) SetDlgItemText(hwndDlg,IDC_EDIT_COMPOSER,last_composer);
+ if(!disable_title && last_title) SetDlgItemText(hwndDlg,IDC_EDIT_TITLE,last_title);
+ if(!disable_album && last_album) SetDlgItemText(hwndDlg,IDC_EDIT_ALBUM,last_album);
+ if(!disable_genre && last_genre) SetDlgItemText(hwndDlg,IDC_EDIT_GENRE,last_genre);
+ if(!disable_year && last_year>0) {
+ wchar_t tmp[64] = {0};
+ wsprintf(tmp,L"%d",last_year);
+ SetDlgItemText(hwndDlg,IDC_EDIT_YEAR,tmp);
+ }
+ if(!disable_track && last_track>0) {
+ wchar_t tmp[64] = {0};
+ wsprintf(tmp,L"%d",last_track);
+ SetDlgItemText(hwndDlg,IDC_EDIT_TRACK,tmp);
+ }
+ if(!disable_disc && last_disc>0) {
+ wchar_t tmp[64] = {0};
+ wsprintf(tmp,L"%d",last_disc);
+ SetDlgItemText(hwndDlg,IDC_EDIT_DISC,tmp);
+ }
+ if(!disable_albumart && last_albumart) {
+ // save copy of image
+ editinfo_image* image = (editinfo_image*)calloc(1, sizeof(editinfo_image));
+ editDevice->getArtNaturalSize(last_albumart,&image->w,&image->h);
+ image->data = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(sizeof(ARGB32)*image->w*image->h);
+ editDevice->getArtData(last_albumart,image->data);
+ setBitmap(image,hwndDlg,true);
+ editDevice->releaseArt(last_albumart);
+ }
+ else SetWindowLongPtr(hwndDlg,GWLP_USERDATA,0);
+
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+
+ }
+ break;
+ case WM_LBUTTONDOWN:
+ {
+ POINTS p = MAKEPOINTS(lParam);
+ POINT p2 = {p.x, p.y};
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ARTIST, IDC_CHECK_ARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TITLE, IDC_CHECK_TITLE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUM, IDC_CHECK_ALBUM)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_TRACK, IDC_CHECK_TRACK)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_DISC, IDC_CHECK_DISC)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_GENRE, IDC_CHECK_GENRE)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_YEAR, IDC_CHECK_YEAR)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_PICTUREHOLDER, IDC_CHECK_ALBUMART,true)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_ART_CLEAR, IDC_CHECK_ALBUMART,true)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_ART_CHANGE, IDC_CHECK_ALBUMART,true)) break;
+ if (checkEditInfoClick(hwndDlg, p2, IDC_ARTINFO, IDC_CHECK_ALBUMART,true)) break;
+ }
+ break;
+ case WM_RBUTTONDOWN:
+ {
+ POINTS pts = MAKEPOINTS(lParam);
+ POINT p = {pts.x, pts.y};
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_PICTUREHOLDER), &r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r);
+ ScreenToClient(hwndDlg, (LPPOINT)&r.right);
+ if(PtInRect(&r, p)) { // right click on picture holder
+ extern HMENU m_context_menus;
+ HMENU menu = GetSubMenu(m_context_menus, 11);
+ POINT p;
+ GetCursorPos(&p);
+ wchar_t artist[256]=L"";
+ wchar_t album[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUMARTIST,artist,256);
+ if(!artist[0])
+ GetDlgItemText(hwndDlg,IDC_EDIT_ARTIST,artist,256);
+ GetDlgItemText(hwndDlg,IDC_EDIT_ALBUM,album,256);
+
+ bool canpaste=(!!IsClipboardFormatAvailable(CF_DIB));
+ bool hasimage= (GetWindowLongPtr(hwndDlg,GWLP_USERDATA) != 0);
+ EnableMenuItem(menu, ID_ARTEDITMENU_PASTE, MF_BYCOMMAND | (canpaste?MF_ENABLED:MF_GRAYED));
+ EnableMenuItem(menu, ID_ARTEDITMENU_COPY, MF_BYCOMMAND | (hasimage?MF_ENABLED:MF_GRAYED));
+ EnableMenuItem(menu, ID_ARTEDITMENU_DELETE, MF_BYCOMMAND | (hasimage?MF_ENABLED:MF_GRAYED));
+ EnableMenuItem(menu, ID_ARTEDITMENU_SAVEAS, MF_BYCOMMAND | (hasimage?MF_ENABLED:MF_GRAYED));
+
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ #if 0
+ bool candownload = (album[0] || artist[0]);
+ EnableMenuItem(menu, ID_ARTEDITMENU_DOWNLOAD, MF_BYCOMMAND | (candownload?MF_ENABLED:MF_GRAYED));
+ #else
+ DeleteMenu(menu, ID_ARTEDITMENU_DOWNLOAD, MF_BYCOMMAND);
+ #endif
+
+ int r = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, 0, hwndDlg, NULL);
+ switch(r) {
+ // disabled 30 May 2012 as per email from Tejas w.r.t. to Rovi deal ending
+ #if 0
+ case ID_ARTEDITMENU_DOWNLOAD:
+ {
+ artFetchData d = {sizeof(d),hwndDlg,artist,album,0};
+ int r = (int)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(LPARAM)&d,IPC_FETCH_ALBUMART);
+ if(r == 0 && d.imgData && d.imgDataLen) // success, save art in correct location
+ {
+ int w=0,h=0;
+ ARGB32* bytes = loadImg(d.imgData,d.imgDataLen,&w,&h);
+ if(bytes)
+ {
+ editinfo_image *image = (editinfo_image *)calloc(1, sizeof(editinfo_image));
+ image->data = bytes;
+ image->w = w;
+ image->h = h;
+ setBitmap(image,hwndDlg);
+ }
+ WASABI_API_MEMMGR->sysFree(d.imgData);
+ }
+ }
+ break;
+ #endif
+ case ID_ARTEDITMENU_COPY:
+ {
+ if (!OpenClipboard(hwndDlg)) break;
+ EmptyClipboard();
+ HBITMAP bm = getBitmap((editinfo_image*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA),hwndDlg);
+ SetClipboardData(CF_BITMAP,bm);
+ CloseClipboard();
+ }
+ break;
+ case ID_ARTEDITMENU_PASTE:
+ {
+ if (!OpenClipboard(hwndDlg)) break;
+ HBITMAP bm = (HBITMAP)GetClipboardData(CF_BITMAP);
+ if(bm) setBitmap(bm,hwndDlg);
+ CloseClipboard();
+ }
+ break;
+ case ID_ARTEDITMENU_DELETE:
+ CheckDlgButton(hwndDlg,IDC_CHECK_ALBUMART,BST_CHECKED);
+ enableArt(hwndDlg,false,TRUE);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_ART_CLEAR,0),0);
+ break;
+ case ID_ARTEDITMENU_SAVEAS:
+ {
+
+ static int tests_run = 0;
+ wchar_t file[1024] = {0};
+ static wchar_t filter[1024] = {0}, *sff = filter;
+ OPENFILENAME fn = {0};
+ fn.lStructSize = sizeof(fn);
+ fn.hwndOwner = hwndDlg;
+ fn.lpstrFile = file;
+ fn.nMaxFile = 1020;
+
+ if(!tests_run)
+ {
+ tests_run = 1;
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ size_t size = 1024;
+ for (int i = 0, j = 0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if (sf)
+ {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if (l)
+ {
+ wchar_t *tests[] = {L"*.jpg",L"*.png",L"*.gif",L"*.bmp"};
+ for(int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ {
+ if (l->isMine(tests[i]))
+ {
+ j++;
+ int len = 0, tests_str[] = {IDS_JPEG_FILE,IDS_PNG_FILE,IDS_GIF_FILE,IDS_BMP_FILE};
+ WASABI_API_LNGSTRINGW_BUF(tests_str[i],sff,size);
+ size-=(len = lstrlenW(sff)+1);
+ sff+=len;
+ lstrcpynW(sff,tests[i],size);
+ size-=(len = lstrlenW(sff)+1);
+ sff+=len;
+ }
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ }
+
+ fn.lpstrFilter = filter;
+ fn.Flags = OFN_OVERWRITEPROMPT;
+ GetSaveFileName(&fn);
+ int l = wcslen(file);
+ if(l>4 && file[l-4]==L'.'); // we have an extention
+ else switch(fn.nFilterIndex) {
+ case 1: StringCchCat(file, ARRAYSIZE(file), L".jpg"); break;
+ case 2: StringCchCat(file, ARRAYSIZE(file), L".png"); break;
+ case 3: StringCchCat(file, ARRAYSIZE(file), L".gif"); break;
+ case 4: StringCchCat(file, ARRAYSIZE(file), L".bmp"); break;
+ }
+ editinfo_image *image = (editinfo_image *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ writeImageToFile(image->data,image->w,image->h,file);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+#define HANDLE_CONTROL(item, check) { int enabled = IsDlgButtonChecked(hwndDlg, check); EnableWindow(GetDlgItem(hwndDlg, item), enabled); EnableWindow(GetDlgItem(hwndDlg, IDOK), enabled); }
+ switch(LOWORD(wParam)) {
+
+ case IDC_CHECK_ARTIST: HANDLE_CONTROL(IDC_EDIT_ARTIST, IDC_CHECK_ARTIST); break;
+ case IDC_CHECK_TITLE: HANDLE_CONTROL(IDC_EDIT_TITLE, IDC_CHECK_TITLE); break;
+ case IDC_CHECK_ALBUM: HANDLE_CONTROL(IDC_EDIT_ALBUM, IDC_CHECK_ALBUM); break;
+ case IDC_CHECK_ALBUMARTIST: HANDLE_CONTROL(IDC_EDIT_ALBUMARTIST, IDC_CHECK_ALBUMARTIST); break;
+ case IDC_CHECK_COMPOSER: HANDLE_CONTROL(IDC_EDIT_COMPOSER, IDC_CHECK_COMPOSER); break;
+ case IDC_CHECK_PUBLISHER: HANDLE_CONTROL(IDC_EDIT_PUBLISHER, IDC_CHECK_PUBLISHER); break;
+ case IDC_CHECK_TRACK: HANDLE_CONTROL(IDC_EDIT_TRACK, IDC_CHECK_TRACK); break;
+ case IDC_CHECK_DISC: HANDLE_CONTROL(IDC_EDIT_DISC, IDC_CHECK_DISC); break;
+ case IDC_CHECK_GENRE: HANDLE_CONTROL(IDC_EDIT_GENRE, IDC_CHECK_GENRE); break;
+ case IDC_CHECK_YEAR: HANDLE_CONTROL(IDC_EDIT_YEAR, IDC_CHECK_YEAR); break;
+ //case IDC_CHECK_COMMENT: HANDLE_CONTROL(IDC_EDIT_COMMENT, IDC_CHECK_COMMENT); break;
+ //case IDC_CHECK_CATEGORY: HANDLE_CONTROL(IDC_EDIT_CATEGORY, IDC_CHECK_CATEGORY); break;
+ //case IDC_CHECK_DIRECTOR: HANDLE_CONTROL(IDC_EDIT_DIRECTOR, IDC_CHECK_DIRECTOR); break;
+ //case IDC_CHECK_PRODUCER: HANDLE_CONTROL(IDC_EDIT_PRODUCER, IDC_CHECK_PRODUCER); break;
+ //case IDC_CHECK_BPM: HANDLE_CONTROL(IDC_EDIT_BPM, IDC_CHECK_BPM); break;
+ //case IDC_CHECK_RATING: HANDLE_CONTROL(IDC_COMBO_RATING, IDC_CHECK_RATING); break;
+ case IDC_CHECK_ALBUMART:
+ {
+ int enabled = IsDlgButtonChecked(hwndDlg, IDC_CHECK_ALBUMART);
+ enableArt(hwndDlg, false, enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDOK), enabled);
+ break;
+ }
+ case IDC_ART_CLEAR:
+ {
+ editinfo_image* image = (editinfo_image*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ if(image) {
+ if(image->data) WASABI_API_MEMMGR->sysFree(image->data);
+ free(image);
+ }
+ SetDlgItemText(hwndDlg,IDC_ARTINFO,WASABI_API_LNGSTRINGW(IDS_NO_IMAGE));
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,0);
+ HBITMAP old = (HBITMAP)SendDlgItemMessage(hwndDlg,IDC_PICTUREHOLDER,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)0);
+ DeleteObject(old);
+ }
+ break;
+ case IDC_ART_CHANGE:
+ {
+ static wchar_t fileExtensionsString[MAX_PATH] = {0};
+ wchar_t file[1024]=L"";
+ OPENFILENAME fn = {0};
+ fn.lStructSize = sizeof(fn);
+ fn.hwndOwner = hwndDlg;
+ fn.lpstrFile = file;
+ fn.nMaxFile = 1024;
+
+ if(!fileExtensionsString[0])
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_IMAGE_FILES,fileExtensionsString,MAX_PATH);
+ wchar_t *temp=fileExtensionsString+lstrlenW(fileExtensionsString) + 1;
+
+ // query the available image loaders and build it against the supported formats
+ FOURCC imgload = svc_imageLoader::getServiceType();
+ int n = plugin.service->service_getNumServices(imgload);
+ for (int i=0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(imgload,i);
+ if (sf)
+ {
+ svc_imageLoader * l = (svc_imageLoader*)sf->getInterface();
+ if (l)
+ {
+ wchar_t *tests[] = {L"*.jpg",L"*.png",L"*.gif",L"*.bmp"};
+ for(int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ {
+ if (l->isMine(tests[i]))
+ {
+ StringCchCatW(temp,MAX_PATH,tests[i]);
+ StringCchCatW(temp,MAX_PATH,L";");
+ }
+ }
+ sf->releaseInterface(l);
+ }
+ }
+ }
+ *(temp = temp + lstrlenW(temp) + 1) = 0;
+ }
+
+ fn.lpstrFilter = fileExtensionsString;
+ fn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
+ GetOpenFileName(&fn);
+ editinfo_image *image = (editinfo_image *)calloc(1, sizeof(editinfo_image));
+ image->data = loadImgFromFile(file,&image->w,&image->h);
+ if(!image->data) { free(image); break; }
+ setBitmap(image,hwndDlg);
+ }
+ break;
+ case IDOK:
+ {
+ if(WASABI_API_DIALOGBOXPARAMW(IDD_PROGRESS,hwndDlg,editInfo_commit_dialogProc, (LPARAM)hwndDlg) == 0)
+ EndDialog(hwndDlg,0);
+ DeviceView * editDeviceView=NULL;
+ for(int i=0; i < devices.GetSize(); i++) if(((DeviceView*)devices.Get(i))->dev == editDevice) editDeviceView = (DeviceView*)devices.Get(i);
+ if(editDeviceView) editDeviceView->DevicePropertiesChanges();
+ SendMessage(hwndMediaView,WM_USER+2,1,0);
+ }
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ editinfo_image* image = (editinfo_image*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA);
+ if(image) {
+ if(image->data) WASABI_API_MEMMGR->sysFree(image->data);
+ free(image);
+ }
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,0);
+ HBITMAP bmold = (HBITMAP)SendDlgItemMessage(hwndDlg,IDC_PICTUREHOLDER,STM_GETIMAGE,IMAGE_BITMAP,0);
+ if(bmold) DeleteObject(bmold);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void editInfo(C_ItemList * items, Device * dev, HWND centerWindow) {
+ editItems = items;
+ editDevice = dev;
+ WASABI_API_DIALOGBOXPARAMW(IDD_EDIT_INFO, plugin.hwndLibraryParent, editInfo_dialogProc, (LPARAM)centerWindow);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/graphics.cpp b/Src/Plugins/Library/ml_pmp/graphics.cpp
new file mode 100644
index 00000000..8fbfaecb
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/graphics.cpp
@@ -0,0 +1,316 @@
+#include "main.h"
+#include "./graphics.h"
+
+BYTE
+Graphics_GetSysFontQuality()
+{
+ BOOL smoothingEnabled;
+ if (FALSE == SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &smoothingEnabled, 0) ||
+ FALSE == smoothingEnabled)
+ {
+ return DEFAULT_QUALITY;
+ }
+
+ OSVERSIONINFOW vi;
+ vi.dwOSVersionInfoSize = sizeof(vi);
+ if (FALSE == GetVersionExW(&vi))
+ return DEFAULT_QUALITY;
+
+ if (vi.dwMajorVersion > 5 || (vi.dwMajorVersion == 5 && vi.dwMinorVersion >= 1))
+ {
+ UINT smootingType;
+ if (FALSE == SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &smootingType, 0))
+ return DEFAULT_QUALITY;
+
+ if (FE_FONTSMOOTHINGCLEARTYPE == smootingType)
+ return CLEARTYPE_NATURAL_QUALITY/*CLEARTYPE_QUALITY*/;
+ }
+
+ return ANTIALIASED_QUALITY;
+}
+
+HFONT
+Graphics_CreateSysFont()
+{
+ LOGFONTW lf;
+ HFONT font;
+
+ if (FALSE == SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0))
+ return NULL;
+
+ lf.lfQuality = Graphics_GetSysFontQuality();
+ font = CreateFontIndirectW(&lf);
+
+ return font;
+}
+
+HFONT
+Graphics_DuplicateFont(HFONT sourceFont, INT heightDeltaPt, BOOL forceBold, BOOL systemQuality)
+{
+ LOGFONTW lf;
+
+ if (NULL == sourceFont)
+ return NULL;
+
+
+ if (sizeof(lf) != GetObjectW(sourceFont, sizeof(lf), &lf))
+ return NULL;
+
+ if (0 != heightDeltaPt)
+ {
+ HDC hdc, hdcTmp;
+
+ hdc = GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE | DCX_NORESETATTRS);
+ hdcTmp = NULL;
+
+ if (NULL != hdc)
+ {
+ hdcTmp = CreateCompatibleDC(hdc);
+ ReleaseDC(NULL, hdc);
+ }
+
+ if (NULL == hdcTmp)
+ return NULL;
+
+ LONG pixelsY = GetDeviceCaps(hdcTmp, LOGPIXELSY);
+ HFONT prevFont = SelectFont(hdcTmp, sourceFont);
+
+ TEXTMETRICW tm;
+ if (FALSE != GetTextMetricsW(hdcTmp, &tm))
+ {
+ INT basePt = MulDiv(tm.tmHeight - tm.tmInternalLeading, 72, pixelsY);
+ lf.lfHeight = -MulDiv((basePt + heightDeltaPt), pixelsY, 72);
+
+ }
+
+ SelectObject(hdcTmp, prevFont);
+ DeleteDC(hdcTmp);
+ }
+
+ if (FALSE != systemQuality)
+ lf.lfQuality = Graphics_GetSysFontQuality();
+ if (FALSE != forceBold && lf.lfWeight < FW_BOLD)
+ lf.lfWeight = FW_BOLD;
+
+ return CreateFontIndirectW(&lf);
+}
+
+long
+Graphics_GetFontHeight(HDC hdc)
+{
+ TEXTMETRICW tm;
+ if (FALSE == GetTextMetricsW(hdc, &tm))
+ return 0;
+
+ return tm.tmHeight;
+}
+
+long
+Graphics_GetAveStrWidth(HDC hdc, UINT cchLen)
+{
+ const char szTest[] =
+ {
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l', 'm','n','o','p','q','r','s','t','u','v','w','x','y','z'
+ };
+
+ SIZE textSize;
+ if (FALSE == GetTextExtentPointA(hdc, szTest, ARRAYSIZE(szTest) -1, &textSize))
+ return 0;
+
+ LONG result;
+ if (1 == cchLen)
+ {
+ result = (textSize.cx + ARRAYSIZE(szTest)/2)/ARRAYSIZE(szTest);
+ }
+ else
+ {
+ result = MulDiv(cchLen, textSize.cx + ARRAYSIZE(szTest)/2, ARRAYSIZE(szTest));
+ if (0 != result)
+ {
+ TEXTMETRICW tm;
+ if (FALSE != GetTextMetricsW(hdc, &tm))
+ result += tm.tmOverhang;
+ }
+ }
+ return result;
+}
+
+BOOL
+Graphics_GetWindowBaseUnits(HWND hwnd, LONG *baseUnitX, LONG *baseUnitY)
+{
+ BOOL result;
+ result = FALSE;
+
+ if (NULL != hwnd)
+ {
+ HDC hdc = GetDCEx(hwnd, NULL, DCX_CACHE | DCX_NORESETATTRS);
+ if (NULL != hdc)
+ {
+ TEXTMETRICW tm;
+ HFONT font, prevFont;
+
+ font = (HFONT)SNDMSG(hwnd, WM_GETFONT, 0, 0L);
+ prevFont = SelectFont(hdc, font);
+
+ if (FALSE != GetTextMetricsW(hdc, &tm))
+ {
+ if (NULL != baseUnitX)
+ *baseUnitX = Graphics_GetAveStrWidth(hdc, 1);
+
+ if (NULL != baseUnitY)
+ *baseUnitY = tm.tmHeight;
+
+ result = TRUE;
+ }
+
+ SelectFont(hdc, prevFont);
+ ReleaseDC(hwnd, hdc);
+ }
+ }
+ return result;
+}
+
+
+typedef int (*SkinColorFunc)(int idx);
+
+COLORREF Graphics_GetSkinColor(unsigned int colorIndex)
+{
+ static SkinColorFunc GetSkinColor = NULL;
+
+ if (NULL == GetSkinColor)
+ {
+ GetSkinColor = (SkinColorFunc)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SKIN_WADLG_GETFUNC, 1);
+ if (NULL == GetSkinColor)
+ return RGB(255, 0, 255);
+ }
+
+ return GetSkinColor(colorIndex);
+}
+
+COLORREF
+Graphics_BlendColors(COLORREF rgbTop, COLORREF rgbBottom, INT alpha)
+{
+ if (alpha > 254) return rgbTop;
+ if (alpha < 0) return rgbBottom;
+
+ WORD k = (WORD)(((255 - alpha)*255 + 127)/255);
+
+ return RGB( (GetRValue(rgbTop)*alpha + k*GetRValue(rgbBottom) + 127)/255,
+ (GetGValue(rgbTop)*alpha + k*GetGValue(rgbBottom) + 127)/255,
+ (GetBValue(rgbTop)*alpha + k*GetBValue(rgbBottom) + 127)/255);
+}
+
+INT
+Graphics_GetColorDistance(COLORREF rgb1, COLORREF rgb2)
+{
+ return (1000 * ((GetRValue(rgb1) - GetRValue(rgb2)) +
+ (GetGValue(rgb1) - GetGValue(rgb2)) +
+ (GetBValue(rgb1) - GetBValue(rgb2))))/ (3 * 255);
+}
+
+void
+Graphics_ClampRect(RECT *rect, const RECT *boxRect)
+{
+ if (rect->left < boxRect->left)
+ rect->left = boxRect->left;
+
+ if (rect->top < boxRect->top)
+ rect->top = boxRect->top;
+
+ if (rect->right > boxRect->right)
+ rect->right = boxRect->right;
+
+ if (rect->bottom > boxRect->bottom)
+ rect->bottom = boxRect->bottom;
+}
+
+void
+Graphics_NormalizeRect(RECT *rect)
+{
+ if (rect->top > rect->bottom)
+ rect->bottom = rect->top;
+
+ if (rect->left > rect->right)
+ rect->right = rect->left;
+}
+
+void
+Graphics_GetRectSizeNormalized(const RECT *rect, SIZE *size)
+{
+ size->cx = rect->right - rect->left;
+ if (size->cx < 0)
+ size->cx = 0;
+
+ size->cy = rect->bottom - rect->top;
+ if (size->cy < 0)
+ size->cy = 0;
+}
+
+BOOL
+Graphics_IsRectFit(const RECT *rect, const RECT *boxRect)
+{
+ if (rect->left < boxRect->left ||
+ rect->top < boxRect->top ||
+ rect->right > boxRect->right ||
+ rect->bottom > boxRect->bottom)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+BOOL SetSizeEmpty(SIZE *size)
+{
+ if (NULL == size)
+ return FALSE;
+
+ ZeroMemory(size, sizeof(SIZE));
+ return TRUE;
+}
+
+BOOL IsSizeEmpty(SIZE *size)
+{
+ return (NULL == size || 0 == size->cx || 0 == size->cy);
+}
+
+BOOL SetSize(SIZE *size, long width, long height)
+{
+ if (NULL == size)
+ return FALSE;
+
+ size->cx = width;
+ size->cy = height;
+
+ return TRUE;
+}
+
+BOOL SetPoint(POINT *pt, long x, long y)
+{
+ if (NULL == pt)
+ return FALSE;
+
+ pt->x = x;
+ pt->y = y;
+
+ return TRUE;
+}
+
+BOOL MakeRectPolygon(POINT vertices[4], long left, long top, long right, long bottom)
+{
+ if (NULL == vertices)
+ return FALSE;
+
+ vertices[0].x = left;
+ vertices[0].y = top;
+ vertices[1].x = right;
+ vertices[1].y = top;
+ vertices[2].x = right;
+ vertices[2].y = bottom;
+ vertices[3].x = left;
+ vertices[3].y = bottom;
+
+ return TRUE;
+}
diff --git a/Src/Plugins/Library/ml_pmp/graphics.h b/Src/Plugins/Library/ml_pmp/graphics.h
new file mode 100644
index 00000000..097dd83d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/graphics.h
@@ -0,0 +1,71 @@
+#ifndef _NULLSOFT_WINAMP_ML_PORTABLES_GRAPHICS_HEADER
+#define _NULLSOFT_WINAMP_ML_PORTABLES_GRAPHICS_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#ifndef SelectBitmap
+ #define SelectBitmap(_hdc, _bitmap) ((HBITMAP)SelectObject(_hdc, _bitmap))
+#endif
+
+#ifndef SelectFont
+ #define SelectFont(_hdc, _font) ((HFONT)SelectObject(_hdc, _font))
+#endif
+
+#ifndef SelectBrush
+ #define SelectBrush(_hdc, _brush) ((HBRUSH)SelectObject(_hdc, _brush))
+#endif
+
+#ifndef GetCurrentBitmap
+ #define GetCurrentBitmap(_hdc) ((HBITMAP)GetCurrentObject(_hdc, OBJ_BITMAP))
+#endif
+
+#ifndef GetCurrentFont
+ #define GetCurrentFont(_hdc) ((HFONT)GetCurrentObject(_hdc, OBJ_FONT))
+#endif
+
+#ifndef GetCurrentBrush
+ #define GetCurrentBrush(_hdc) ((HBRUSH)GetCurrentObject(_hdc, OBJ_BRUSH))
+#endif
+
+
+#ifndef SET_GRADIENT_VERTEX
+ #define SET_GRADIENT_VERTEX(_vertex, _x, _y, _rgb)\
+ {(_vertex).x = (_x); (_vertex).y = (_y);\
+ (_vertex).Red = GetRValue(_rgb) << 8; (_vertex).Green = GetGValue(_rgb) << 8;\
+ (_vertex).Blue = GetBValue(_rgb) << 8; (_vertex).Alpha = 0x0000;}
+#endif
+
+#ifndef SET_GRADIENT_RECT_MESH
+ #define SET_GRADIENT_RECT_MESH(_mesh, _upperLeft, _bottomRight)\
+ {(_mesh).UpperLeft = (_upperLeft); (_mesh).LowerRight = (_bottomRight);}
+#endif
+
+
+BYTE Graphics_GetSysFontQuality();
+HFONT Graphics_CreateSysFont();
+HFONT Graphics_DuplicateFont(HFONT sourceFont, INT heightDeltaPt, BOOL forceBold, BOOL systemQuality);
+
+long Graphics_GetFontHeight(HDC hdc);
+long Graphics_GetAveStrWidth(HDC hdc, UINT cchLen);
+BOOL Graphics_GetWindowBaseUnits(HWND hwnd, LONG *baseUnitX, LONG *baseUnitY);
+
+COLORREF Graphics_GetSkinColor(unsigned int colorIndex);
+COLORREF Graphics_BlendColors(COLORREF rgbTop, COLORREF rgbBottom, INT alpha);
+INT Graphics_GetColorDistance(COLORREF rgb1, COLORREF rgb2);
+
+void Graphics_ClampRect(RECT *rect, const RECT *boxRect);
+void Graphics_NormalizeRect(RECT *rect);
+void Graphics_GetRectSizeNormalized(const RECT *rect, SIZE *size);
+BOOL Graphics_IsRectFit(const RECT *rect, const RECT *boxRect);
+
+BOOL SetSizeEmpty(SIZE *size);
+BOOL IsSizeEmpty(SIZE *size);
+BOOL SetSize(SIZE *size, long width, long height);
+
+BOOL SetPoint(POINT *pt, long x, long y);
+BOOL MakeRectPolygon(POINT vertices[4], long left, long top, long right, long bottom);
+
+
+#endif //_NULLSOFT_WINAMP_ML_PORTABLES_GRAPHICS_HEADER
diff --git a/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp b/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp
new file mode 100644
index 00000000..13536436
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp
@@ -0,0 +1,313 @@
+#include "main.h"
+
+#include "api/service/waservicefactory.h"
+#include "bfc/dispatch.h"
+
+#include "../Components/wac_network/wac_network_web_server_api.h"
+#include "../Components/wac_network/wac_network_onconncb_api.h"
+#include "../Components/wac_network/wac_network_connection_api.h"
+#include "../Components/wac_network/wac_network_http_server_api.h"
+#include "../Components/wac_network/wac_network_page_generator_api.h"
+
+#include "api__ml_pmp.h"
+
+#include "DeviceView.h"
+
+#include <wchar.h>
+
+#include "metadata_utils.h"
+
+#include "nu/AutoWide.h"
+#include "nu/AutoChar.h"
+
+#include <strsafe.h>
+
+void url_decode(char *in, char *out, int maxlen)
+{
+ while (in && *in && maxlen>1)
+ {
+ if (*in == '+')
+ {
+ in++;
+ *out++=' ';
+ }
+ else if (*in == '%' && in[1] != '%' && in[1])
+ {
+ int a=0;
+ int b=0;
+ for ( b = 0; b < 2; b ++)
+ {
+ int r=in[1+b];
+ if (r>='0'&&r<='9') r-='0';
+ else if (r>='a'&&r<='z') r-='a'-10;
+ else if (r>='A'&&r<='Z') r-='A'-10;
+ else break;
+ a*=16;
+ a+=r;
+ }
+ if (b < 2) *out++=*in++;
+ else { *out++=a; in += 3;}
+ }
+ else *out++=*in++;
+ maxlen--;
+ }
+ *out=0;
+}
+
+static void callbackThunk(void * callBackContext, wchar_t * status) {
+}
+
+typedef struct {
+ int status;
+ wchar_t * device; wchar_t * artist; wchar_t * album; wchar_t * title;
+ DeviceView * d; songid_t s;
+} FindTrackStruct;
+
+static VOID CALLBACK APC_FindTrack(ULONG_PTR dwParam) {
+ FindTrackStruct * f = (FindTrackStruct *)dwParam;
+ for(int i=0; i<devices.GetSize(); i++) {
+ f->d = (DeviceView*)devices.Get(i);
+ wchar_t buf[2048] = {0}; int len = 2047;
+ f->d->dev->getPlaylistName(0,buf,128);
+ if(wcscmp(buf,f->device)) continue;
+ int l = f->d->dev->getPlaylistLength(0);
+ for(int j=0; j<l; j++) {
+ f->s = f->d->dev->getPlaylistTrack(0,j);
+ f->d->dev->getTrackArtist(f->s,buf,len);
+ if(lstrcmpi(buf,f->artist)) continue;
+ f->d->dev->getTrackAlbum(f->s,buf,len);
+ if(lstrcmpi(buf,f->album)) continue;
+ f->d->dev->getTrackTitle(f->s,buf,len);
+ if(lstrcmpi(buf,f->title)) continue;
+ f->status = 2;
+ return;
+ }
+ }
+ f->status = 1;
+}
+
+bool findTrack(Device **dev, songid_t *song,wchar_t * device,wchar_t * artist,wchar_t * album,wchar_t * title) {
+ FindTrackStruct f = {0,device,artist,album,title,NULL,0};
+ SynchronousProcedureCall(APC_FindTrack,(ULONG_PTR)&f);
+ *dev = f.d->dev;
+ *song = f.s;
+ return f.status == 2;
+}
+
+bool copyFileFromDevice(Device * dev, songid_t song,wchar_t * fn) {
+ int k=0;
+ if(dev->copyToHardDriveSupported()) {
+ if(!dev->copyToHardDrive(song,fn,NULL,callbackThunk,&k))
+ return true;
+ }
+ return false;
+}
+
+class FilePageGenerator : public api_pagegenerator // public IPageGenerator
+{
+public:
+ virtual ~FilePageGenerator();
+ FilePageGenerator(wchar_t *fn, api_httpserv *serv);
+ size_t GetData(char *buf, int size); // return 0 when done
+ int is_error() { return !m_fp; }
+
+private:
+ FILE *m_fp;
+ int m_file_pos;
+ int m_file_len;
+ wchar_t *m_fn;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS FilePageGenerator
+START_DISPATCH;
+CB(API_PAGEGENERATOR_GETDATA, GetData);
+END_DISPATCH;
+#undef CBCLASS
+
+FilePageGenerator::FilePageGenerator(wchar_t *fn, api_httpserv *serv)
+{
+ m_fn = _wcsdup(fn);
+ m_file_pos=m_file_len=0;
+ m_fp=_wfopen(fn,L"rb");
+ if (m_fp)
+ {
+ int resume_end=0;
+ int resume_pos=0;
+ char *range = serv->getheader("Range");
+ if (range)
+ {
+ if (!_strnicmp(range,"bytes=",6))
+ {
+ range+=6;
+ char *t=range;
+ while (t && *t && (*t < '0' || *t > '9')) t++;
+ while (t && *t && *t >= '0' && *t <= '9')
+ {
+ resume_pos*=10;
+ resume_pos+=*t-'0';
+ t++;
+ }
+ if (*t != '-') resume_pos=0;
+ else if (t[1])
+ {
+ resume_end=atoi(t+1);
+ }
+ }
+ }
+
+ fseek(m_fp,0,SEEK_END);
+ m_file_len=ftell(m_fp);
+ char buf[512] = {0};
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Length: %d",m_file_len);
+ serv->set_reply_header(buf);
+
+ int m_file_len_orig=m_file_len;
+
+ if (resume_end && resume_end < m_file_len) m_file_len=resume_end;
+ if (resume_pos > 0 && resume_pos < m_file_len) m_file_pos = resume_pos;
+ fseek(m_fp,m_file_pos,SEEK_SET);
+
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Range: bytes=%d-%d/%d",resume_pos,resume_end,m_file_len_orig);
+ serv->set_reply_header(buf);
+ if (resume_pos != 0) serv->set_reply_string("HTTP/1.1 206 Partial Content");
+ }
+}
+
+FilePageGenerator::~FilePageGenerator()
+{
+ if (m_fp) fclose(m_fp);
+ if (m_fn) { _wunlink(m_fn); free(m_fn); }
+}
+
+size_t FilePageGenerator::GetData(char *buf, int size) // return 0 when done
+{
+ if (!m_fp) return 0;
+ if (m_file_pos+size > m_file_len) size=m_file_len - m_file_pos;
+ int l=fread(buf,1,size,m_fp);
+ m_file_pos+=l;
+ return l;
+}
+
+class onConnCB : public api_onconncb {
+public:
+ api_pagegenerator* onConnection(api_httpserv *serv, int port)
+ {
+ api_connection *c = serv->get_con();
+ if(c && c->GetRemoteAddress() != 0x0100007f) return 0; // if it's not from localhost, disregard.
+ serv->set_reply_header("Server:ml_pmp/1.0");
+ if (!strcmp(serv->get_request_file(),"/"))
+ {
+ // find temporary filename
+ wchar_t fn[MAX_PATH]={0};
+ wchar_t dir[MAX_PATH]={0};
+ GetTempPath(MAX_PATH,dir);
+ GetTempFileName(dir,L"pmp",0,fn);
+ _wunlink(fn);
+ {wchar_t * ext = wcsrchr(fn,L'.'); if(ext) *ext=0;}
+ // decode the parameters needed to find the track from the URL
+ char device[128]={0};
+ char artist[2048]={0};
+ char album[2048]={0};
+ char title[2048]={0};
+ char * p;
+ p = serv->get_request_parm("d");
+ if(p) url_decode(p,device,128);
+ p = serv->get_request_parm("a");
+ if(p) url_decode(p,artist,2048);
+ p = serv->get_request_parm("l");
+ if(p) url_decode(p,album,2048);
+ p = serv->get_request_parm("t");
+ if(p) url_decode(p,title,2048);
+ char * sc = strrchr(device,';');
+ char * ext = "mp3";
+ if(sc) { *sc=0; ext = sc+2; }
+ // find the track based on this info
+ Device * dev;
+ songid_t song;
+ if(findTrack(&dev,&song,AutoWide(device,CP_UTF8),AutoWide(artist,CP_UTF8),AutoWide(album,CP_UTF8),AutoWide(title,CP_UTF8))) {
+ if(dev->copyToHardDriveSupported()) {
+ // track found, can be copied back. Lets reply to the user
+ serv->set_reply_string("HTTP/1.1 200 OK");
+ char buf[150]="";
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Type:audio/%s",ext);
+ serv->set_reply_header(buf);
+ wchar_t title[128]={0};
+ getTitle(dev,song,fn,title,128);
+ StringCchPrintfA(buf, ARRAYSIZE(buf), "icy-name:%s",(char*)AutoChar(title,CP_UTF8));
+ serv->set_reply_header(buf);
+ serv->send_reply();
+ // do the actual copy, and start streaming
+ if(copyFileFromDevice(dev,song,fn)) return new FilePageGenerator(fn,serv);
+ }
+ }
+ }
+ serv->set_reply_string("HTTP/1.1 404 NOT FOUND");
+ serv->send_reply();
+ return 0; // no data
+ }
+ void destroyConnection(api_pagegenerator *conn)
+ {
+ FilePageGenerator *realObject = static_cast<FilePageGenerator *>(conn);
+ delete realObject;
+ }
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS onConnCB
+START_DISPATCH;
+CB(API_ONCONNCB_ONCONNECTION,onConnection);
+VCB(API_ONCONNCB_DESTROYCONNECTION,destroyConnection);
+END_DISPATCH;
+#undef CBCLASS
+
+int serverPort = -1;
+
+static DWORD WINAPI ThreadFunc_Server(LPVOID lpParam) {
+ extern bool quitting;
+ if(quitting) return 0;
+ onConnCB *pOnConnCB;
+ int * killswitch = (int*)lpParam;
+ api_webserv *server=0;
+ waServiceFactory *sf = plugin.service->service_getServiceByGuid(webservGUID);
+ if (sf) server = (api_webserv *)sf->getInterface();
+ if(!server) return NULL;
+ pOnConnCB = new onConnCB;
+ server->SetConnectionCallback(pOnConnCB);
+ serverPort = 54387;
+ while(server->addListenPort(serverPort,0x0100007f) && serverPort < 54397) serverPort++;
+ if(serverPort >= 54397) { serverPort=-1; return NULL; }
+ while (killswitch && *killswitch == 0)
+ {
+ server->run();
+ server->run();
+ Sleep(20);
+ }
+ server->removeListenPort(serverPort);
+ sf->releaseInterface(server);
+ serverPort=-1;
+ delete pOnConnCB;
+ return NULL;
+}
+
+static int killswitch = 0;
+static HANDLE serverThread = NULL;
+
+void startServer() {
+ if(serverPort == -1) {
+ killswitch = 0;
+ DWORD dwThreadId;
+ serverThread = CreateThread(NULL, 0, ThreadFunc_Server, (LPVOID)&killswitch, 0, &dwThreadId);
+ }
+}
+
+void stopServer() {
+ if(serverThread) {
+ killswitch = 1;
+ WaitForSingleObject(serverThread,INFINITE);
+ CloseHandle(serverThread);
+ serverThread = NULL;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/local_menu.cpp b/Src/Plugins/Library/ml_pmp/local_menu.cpp
new file mode 100644
index 00000000..b299195c
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/local_menu.cpp
@@ -0,0 +1,189 @@
+#include "./local_menu.h"
+#include "../../General/gen_ml/ml.h"
+#include "../../General/gen_ml/menu.h"
+#include "./resource1.h"
+#include "../nu/menushortcuts.h"
+#include "api__ml_pmp.h"
+#include "main.h"
+
+extern winampMediaLibraryPlugin plugin;
+extern HWND hwndMediaView;
+
+#define RATING_MARKER MAKELONG(MAKEWORD('R','A'),MAKEWORD('T','E'))
+
+#define RATING_MINSPACECX 16
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x)/sizeof((x)))
+#endif
+
+static HMENU Menu_FindRatingByChild(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii, UINT childId)
+{
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii))
+ {
+ if (childId == pmii->wID) return hMenu;
+ if (NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingByChild(pmii->hSubMenu, pmi, pmii, childId);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ }
+ return NULL;
+}
+
+static HMENU Menu_FindRatingByMarker(HMENU hMenu, MENUINFO *pmi, MENUITEMINFO *pmii)
+{
+ if (GetMenuInfo(hMenu, pmi) && RATING_MARKER == pmi->dwMenuData)
+ return hMenu;
+
+ INT count = GetMenuItemCount(hMenu);
+ for(INT i = 0; i < count; i++)
+ {
+ if (GetMenuItemInfo(hMenu, i, TRUE, pmii) && NULL != pmii->hSubMenu)
+ {
+ HMENU hRating = Menu_FindRatingByMarker(pmii->hSubMenu, pmi, pmii);
+ if (NULL != hRating)
+ return hRating;
+ }
+ }
+ return NULL;
+}
+
+HMENU Menu_FindRatingMenu(HMENU hMenu, BOOL fUseMarker)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_SUBMENU;
+
+ if (FALSE == fUseMarker)
+ mii.fMask |= MIIM_ID;
+
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+
+ return (FALSE == fUseMarker) ?
+ Menu_FindRatingByChild(hMenu, &mi, &mii, ID_RATE_5) :
+ Menu_FindRatingByMarker(hMenu, &mi, &mii);
+}
+
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue)
+{
+ if (NULL == ratingMenu) return FALSE;
+
+ INT ratingList[] = { ID_RATE_0, ID_RATE_1, ID_RATE_2,
+ ID_RATE_3, ID_RATE_4, ID_RATE_5};
+
+ /// set rating marker
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(MENUINFO);
+ mi.fMask = MIM_MENUDATA;
+ mi.dwMenuData = RATING_MARKER;
+ if (!SetMenuInfo(ratingMenu, &mi))
+ return FALSE;
+
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(MENUITEMINFO);
+
+ UINT type, state;
+ for (INT i = 0; i < ARRAYSIZE(ratingList); i++)
+ {
+ mii.fMask = MIIM_STATE | MIIM_FTYPE;
+ if (GetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii))
+ {
+ if (ratingValue == i)
+ {
+ type = mii.fType | MFT_RADIOCHECK;
+ state = mii.fState | MFS_CHECKED;
+ }
+ else
+ {
+ type = mii.fType & ~MFT_RADIOCHECK;
+ state = mii.fState & ~MFS_CHECKED;
+ }
+
+ mii.fMask = 0;
+ if (type != mii.fType)
+ {
+ mii.fType = type;
+ mii.fMask |= MIIM_FTYPE;
+ }
+
+ if (state != mii.fState)
+ {
+ mii.fState = state;
+ mii.fMask |= MIIM_STATE;
+ }
+
+ if (0 != mii.fMask)
+ SetMenuItemInfo(ratingMenu, ratingList[i], FALSE, &mii);
+ }
+ }
+ return TRUE;
+}
+
+void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu)
+{
+ HACCEL szAccel[24] = {0};
+ INT c = WASABI_API_APP->app_getAccelerators(hwndDlg, szAccel, sizeof(szAccel)/sizeof(szAccel[0]), FALSE);
+ AppendMenuShortcuts(menu, szAccel, c, MSF_REPLACE);
+}
+
+void SwapPlayEnqueueInMenu(HMENU listMenu)
+{
+ int playPos=-1, enqueuePos=-1;
+ MENUITEMINFOW playItem={sizeof(MENUITEMINFOW), 0,}, enqueueItem={sizeof(MENUITEMINFOW), 0,};
+
+ int numItems = GetMenuItemCount(listMenu);
+
+ for (int i=0;i<numItems;i++)
+ {
+ UINT id = GetMenuItemID(listMenu, i);
+ if (id == ID_TRACKSLIST_PLAYSELECTION)
+ {
+ playItem.fMask = MIIM_ID;
+ playPos = i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &playItem);
+ }
+ else if (id == ID_TRACKSLIST_ENQUEUESELECTION)
+ {
+ enqueueItem.fMask = MIIM_ID;
+ enqueuePos= i;
+ GetMenuItemInfoW(listMenu, i, TRUE, &enqueueItem);
+ }
+ }
+
+ playItem.wID = ID_TRACKSLIST_ENQUEUESELECTION;
+ enqueueItem.wID = ID_TRACKSLIST_PLAYSELECTION;
+ SetMenuItemInfoW(listMenu, playPos, TRUE, &playItem);
+ SetMenuItemInfoW(listMenu, enqueuePos, TRUE, &enqueueItem);
+}
+
+INT Menu_TrackSkinnedPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm)
+{
+ if (NULL == hMenu)
+ return NULL;
+
+ bool swapPlayEnqueue=false;
+ if (gen_mlconfig->ReadInt(L"enqueuedef", 0) == 1)
+ {
+ SwapPlayEnqueueInMenu(hMenu);
+ swapPlayEnqueue = true;
+ }
+
+ SyncMenuWithAccelerators(hwndMediaView, hMenu);
+
+ if (swapPlayEnqueue)
+ SwapPlayEnqueueInMenu(hMenu);
+
+ return Menu_TrackPopupParam(plugin.hwndLibraryParent, hMenu, fuFlags, x, y,
+ hwnd, lptpm, (ULONG_PTR)Menu_FindRatingMenu(hMenu, TRUE));
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/local_menu.h b/Src/Plugins/Library/ml_pmp/local_menu.h
new file mode 100644
index 00000000..d7b7dad0
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/local_menu.h
@@ -0,0 +1,15 @@
+#ifndef NULLOSFT_PORTABLES_PLUGIN_MENU_HEADER
+#define NULLOSFT_PORTABLES_PLUGIN_MENU_HEADER
+
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+INT Menu_TrackSkinnedPopup(HMENU hMenu, UINT fuFlags, INT x, INT y, HWND hwnd, LPTPMPARAMS lptpm);
+BOOL Menu_SetRatingValue(HMENU ratingMenu, INT ratingValue);
+HMENU Menu_FindRatingMenu(HMENU hMenu, BOOL fUseMarker);
+
+#endif //NULLOSFT_PORTABLES_PLUGIN_MENU_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/main.cpp b/Src/Plugins/Library/ml_pmp/main.cpp
new file mode 100644
index 00000000..09bd908d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/main.cpp
@@ -0,0 +1,1454 @@
+#define PLUGIN_NAME "Nullsoft Portable Music Player Support"
+#define PLUGIN_VERSION L"2.25"
+
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../winamp/wa_ipc.h"
+#include "nu/ns_wc.h"
+#include "..\..\General\gen_hotkeys/wa_hotkeys.h"
+#include "resource1.h"
+#include "pmp.h"
+#include "DeviceView.h"
+#include "pluginloader.h"
+#include "nu/AutoWide.h"
+#include "api__ml_pmp.h"
+#include "transcoder_imp.h"
+#include <api/service/waservicefactory.h>
+#include "config.h"
+#include "tataki/export.h"
+#include "nu/ServiceWatcher.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "mt19937ar.h"
+#include "./local_menu.h"
+#include "pmpdevice.h"
+#include "IconStore.h"
+#include "../replicant/nx/nxstring.h"
+#include <strsafe.h>
+#include "../nu/MediaLibraryInterface.h"
+#include <vector>
+
+#define MAINTREEID 5002
+#define PROGRESSTIMERID 1
+
+static int init();
+static void quit();
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+extern INT_PTR CALLBACK pmp_devices_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+extern void UpdateDevicesListView(bool softupdate);
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+void UpdateTransfersListView(bool softUpdate, CopyInst * item=NULL);
+
+static void CALLBACK ProgressTimerTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, unsigned long elapsed);
+
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_pmp.dll)",
+ init,
+ quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+C_ItemList devices;
+C_ItemList loadingDevices;
+extern HNAVITEM cloudQueueTreeItem;
+static HNAVITEM navigationRoot = NULL;
+static ATOM viewAtom = 0;
+int groupBtn = 1, customAllowed = 0, enqueuedef = 0;
+extern HWND hwndMediaView;
+extern DeviceView * currentViewedDevice;
+HMENU m_context_menus = NULL, m_context_menus2 = NULL;
+int prefsPageLoaded = 0, profile = 0;
+prefsDlgRecW prefsPage;
+prefsDlgRecW pluginsPrefsPage;
+C_Config * global_config;
+C_Config * gen_mlconfig;
+UINT genhotkeys_add_ipc;
+HANDLE hMainThread;
+UINT_PTR mainTreeHandle;
+extern HINSTANCE cloud_hinst;
+extern int IPC_GET_CLOUD_HINST, IPC_LIBRARY_PLAYLISTS_REFRESH;
+void deviceConnected(Device * dev);
+void deviceDisconnected(Device * dev);
+void deviceLoading(pmpDeviceLoading * load);
+void deviceNameChanged(Device * dev);
+
+//extern CRITICAL_SECTION listTransfersLock;
+CRITICAL_SECTION csenumdrives;
+
+api_playlists *AGAVE_API_PLAYLISTS = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_mldb *AGAVE_API_MLDB = 0;
+api_memmgr *WASABI_API_MEMMGR = 0;
+api_syscb *WASABI_API_SYSCB = 0;
+api_application *WASABI_API_APP = 0;
+api_podcasts *AGAVE_API_PODCASTS = 0;
+api_albumart *AGAVE_API_ALBUMART = 0;
+api_stats *AGAVE_API_STATS = 0;
+api_threadpool *WASABI_API_THREADPOOL = 0;
+api_devicemanager *AGAVE_API_DEVICEMANAGER = 0;
+api_metadata *AGAVE_API_METADATA = 0;
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0;
+HINSTANCE WASABI_API_ORIG_HINST = 0;
+
+static const GUID pngLoaderGUID =
+ { 0x5e04fb28, 0x53f5, 0x4032, { 0xbd, 0x29, 0x3, 0x2b, 0x87, 0xec, 0x37, 0x25 } };
+
+static svc_imageLoader *wasabiPngLoader = NULL;
+
+HWND mainMessageWindow = 0;
+static bool classRegistered=0;
+HWND CreateDummyWindow()
+{
+ if (!classRegistered)
+ {
+ WNDCLASSW wc = {0, };
+
+ wc.style = 0;
+ wc.lpfnWndProc = DeviceMsgProc;
+ wc.hInstance = plugin.hDllInstance;
+ wc.hIcon = 0;
+ wc.hCursor = NULL;
+ wc.lpszClassName = L"ml_pmp_window";
+
+ if (!RegisterClassW(&wc))
+ return 0;
+
+ classRegistered = true;
+ }
+ HWND dummy = CreateWindowW(L"ml_pmp_window", L"ml_pmp_window", 0, 0, 0, 0, 0, NULL, NULL, plugin.hDllInstance, NULL);
+
+ return dummy;
+}
+
+genHotkeysAddStruct hksync = { 0, HKF_UNICODE_NAME, WM_USER, 0, 0, "ml_pmp_sync", 0 };
+genHotkeysAddStruct hkautofill = { 0, HKF_UNICODE_NAME, WM_USER + 1, 0, 0, "ml_pmp_autofill", 0 };
+genHotkeysAddStruct hkeject = { 0, HKF_UNICODE_NAME, WM_USER + 2, 0, 0, "ml_pmp_eject", 0 };
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+HNAVITEM NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0)
+{
+ NAVCTRLFINDPARAMS find;
+ HNAVITEM item;
+
+ if (NULL == name)
+ return NULL;
+
+ if (NULL == plugin.hwndLibraryParent)
+ return NULL;
+
+ find.pszName = (wchar_t*)name;
+ find.cchLength = -1;
+ find.compFlags = NICF_INVARIANT;
+ find.fFullNameSearch = FALSE;
+
+ item = MLNavCtrl_FindItemByName(plugin.hwndLibraryParent, &find);
+ if (NULL == item)
+ return NULL;
+
+ if (!allow_root)
+ {
+ // if allowed then we can look for root level items which
+ // is really for getting 'cloud' devices to another group
+ if (NULL != root &&
+ root != MLNavItem_GetParent(plugin.hwndLibraryParent, item))
+ {
+ item = NULL;
+ }
+ }
+
+ return item;
+}
+
+HNAVITEM GetNavigationRoot(BOOL forceCreate)
+{
+ if (NULL == navigationRoot &&
+ FALSE != forceCreate)
+ {
+ NAVINSERTSTRUCT nis = {0};
+ wchar_t buffer[512] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLES, buffer, ARRAYSIZE(buffer));
+
+ nis.hParent = NULL;
+ nis.hInsertAfter = NCI_LAST;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_ITEMID | NIMF_PARAM;
+ nis.item.id = MAINTREEID;
+ nis.item.pszInvariant = L"Portables";
+ nis.item.style = NIS_HASCHILDREN;
+ nis.item.pszText = buffer;
+ nis.item.lParam = -1;
+
+ navigationRoot = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ if (NULL != navigationRoot)
+ {
+ SetTimer(mainMessageWindow, PROGRESSTIMERID, 250, ProgressTimerTickCb);
+ }
+ }
+
+ return navigationRoot;
+}
+
+
+static HNAVITEM GetNavigationItemFromMessage(int msg, INT_PTR param)
+{
+ return (msg < ML_MSG_NAVIGATION_FIRST) ?
+ MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param) :
+ (HNAVITEM)param;
+}
+
+void Menu_ConvertRatingMenuStar(HMENU menu, UINT menu_id)
+{
+MENUITEMINFOW mi = {sizeof(mi), MIIM_DATA | MIIM_TYPE, MFT_STRING};
+wchar_t rateBuf[32], *rateStr = rateBuf;
+ mi.dwTypeData = rateBuf;
+ mi.cch = 32;
+ if(GetMenuItemInfoW(menu, menu_id, FALSE, &mi))
+ {
+ while(rateStr && *rateStr)
+ {
+ if(*rateStr == L'*') *rateStr = L'\u2605';
+ rateStr=CharNextW(rateStr);
+ }
+ SetMenuItemInfoW(menu, menu_id, FALSE, &mi);
+ }
+}
+
+static ServiceWatcher serviceWatcher;
+
+static int init()
+{
+ // if there are no pmp_*.dll then no reason to load
+ if (!testForDevPlugins())
+ return ML_INIT_FAILURE;
+
+ genrand_int31 = (int (*)())SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_RANDFUNC);
+
+ if (0 == viewAtom)
+ {
+ viewAtom = GlobalAddAtomW(L"WinampPortableMediaView");
+ if (0 == viewAtom)
+ return 2;
+ }
+
+ TranscoderImp::init();
+ InitializeCriticalSection(&csenumdrives);
+
+ Tataki::Init(plugin.service);
+ ServiceBuild( AGAVE_API_PLAYLISTS, api_playlistsGUID );
+ ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceBuild( WASABI_API_SYSCB, syscbApiServiceGuid );
+ ServiceBuild( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceBuild( AGAVE_API_STATS, AnonymousStatsGUID );
+ ServiceBuild( WASABI_API_THREADPOOL, ThreadPoolGUID );
+ ServiceBuild( AGAVE_API_DEVICEMANAGER, DeviceManagerGUID );
+ ServiceBuild( AGAVE_API_ALBUMART, albumArtGUID );
+ ServiceBuild( AGAVE_API_METADATA, api_metadataGUID );
+
+ // loader so that we can get the localisation service api for use
+ ServiceBuild( WASABI_API_LNG, languageApiGUID );
+ ServiceBuild( WASABI_API_MEMMGR, memMgrApiServiceGuid );
+
+ // no guarantee that AGAVE_API_MLDB will be available yet, so we'll start a watcher for it
+ serviceWatcher.WatchWith( plugin.service );
+ serviceWatcher.WatchFor( &AGAVE_API_MLDB, mldbApiGuid );
+ serviceWatcher.WatchFor( &AGAVE_API_PODCASTS, api_podcastsGUID );
+
+ WASABI_API_SYSCB->syscb_registerCallback( &serviceWatcher );
+
+
+ mediaLibrary.library = plugin.hwndLibraryParent;
+ mediaLibrary.winamp = plugin.hwndWinampParent;
+ mediaLibrary.instance = plugin.hDllInstance;
+
+ mediaLibrary.GetIniDirectory();
+ mediaLibrary.GetIniDirectoryW();
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,MlPMPLangGUID);
+
+ static wchar_t szDescription[256];
+ StringCbPrintfW(szDescription, ARRAYSIZE(szDescription), WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PMP_SUPPORT), PLUGIN_VERSION);
+ plugin.description = (char*)szDescription;
+
+ hMainThread = GetCurrentThread();
+ //InitializeCriticalSection(&listTransfersLock);
+ global_config = new C_Config( (wchar_t *)mediaLibrary.GetWinampIniW() );
+ profile = global_config->ReadInt( L"profile", 0, L"Winamp" );
+
+ gen_mlconfig = new C_Config( (wchar_t *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW ), L"gen_ml_config" );
+
+ m_context_menus = WASABI_API_LOADMENU( IDR_CONTEXTMENUS );
+ m_context_menus2 = WASABI_API_LOADMENU( IDR_CONTEXTMENUS );
+
+
+ HMENU rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,0),7);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,1),4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,2),7);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ rate_hmenu = GetSubMenu(GetSubMenu(m_context_menus,7),5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_5);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_4);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_3);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_2);
+ Menu_ConvertRatingMenuStar(rate_hmenu, ID_RATE_1);
+
+ //subclass winamp window
+ mainMessageWindow = CreateDummyWindow();
+
+ prefsPage.hInst = WASABI_API_LNG_HINST;
+ prefsPage.dlgID = IDD_CONFIG_GLOBAL;
+ prefsPage.name = _wcsdup( WASABI_API_LNGSTRINGW( IDS_PORTABLES ) );
+ prefsPage.where = 6;
+ prefsPage.proc = global_config_dlgproc;
+
+ pluginsPrefsPage.hInst = WASABI_API_LNG_HINST;
+ pluginsPrefsPage.dlgID = IDD_CONFIG_PLUGINS;
+ pluginsPrefsPage.name = prefsPage.name;
+ pluginsPrefsPage.where = 1;
+ pluginsPrefsPage.proc = config_dlgproc_plugins;
+
+ // only insert the portables page if we've actually loaded a pmp_*.dll
+ int count = 0;
+ if(loadDevPlugins(&count) && count > 0)
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&pluginsPrefsPage,IPC_ADD_PREFS_DLGW);
+ }
+ else if (!count)
+ {
+ // and if there are none, then cleanup and also notify ml_devices.dll to
+ // shut-down as no need for it to keep running if there's not going to be
+ // anything else running (as unlikely we'd have use for it without ml_pmp
+ quit();
+
+ winampMediaLibraryPlugin *(*gp)();
+ gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(GetModuleHandleW(L"ml_devices.dll"), "winampGetMediaLibraryPlugin");
+ if (gp)
+ {
+ winampMediaLibraryPlugin *mlplugin = gp();
+ if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER))
+ {
+ mlplugin->quit();
+ }
+ }
+
+ return ML_INIT_FAILURE;
+ }
+
+ // we've got pmp_*.dll to load, so lets start the fun...
+ Devices_Init();
+ if (AGAVE_API_DEVICEMANAGER)
+ {
+ SetTimer(mainMessageWindow, PROGRESSTIMERID, 250, ProgressTimerTickCb);
+ }
+ else if (!global_config->ReadInt(L"HideRoot",0))
+ {
+ GetNavigationRoot(TRUE);
+ }
+
+ SetTimer(mainMessageWindow,COMMITTIMERID,5000,NULL);
+
+ IPC_LIBRARY_PLAYLISTS_REFRESH = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"ml_playlist_refresh", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ IPC_GET_CLOUD_HINST = (INT)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"WinampCloud", IPC_REGISTER_WINAMP_IPCMESSAGE);
+ cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST);
+
+ // set up hotkeys...
+ genhotkeys_add_ipc = SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&"GenHotkeysAdd",IPC_REGISTER_WINAMP_IPCMESSAGE);
+ hksync.wnd = hkautofill.wnd = hkeject.wnd = mainMessageWindow;
+ hksync.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_SYNC_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hksync,genhotkeys_add_ipc);
+ hkautofill.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_AUTOFILL_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hkautofill,genhotkeys_add_ipc);
+ hkeject.name = (char*)_wcsdup(WASABI_API_LNGSTRINGW(IDS_GHK_EJECT_PORTABLE_DEVICE));
+ PostMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&hkeject,genhotkeys_add_ipc);
+ return ML_INIT_SUCCESS;
+}
+
+bool quitting=false;
+static void quit()
+{
+ // trigger transfer kill
+ int i = devices.GetSize();
+ if (i > 0)
+ {
+ while(i-- > 0)
+ {
+ DeviceView * device = ((DeviceView*)devices.Get(i));
+ if (device) device->threadKillswitch = 1;
+ }
+ }
+
+ quitting = true;
+ KillTimer(mainMessageWindow, COMMITTIMERID);
+ DeviceMsgProc(NULL, WM_TIMER, COMMITTIMERID, 0);
+
+ i = devices.GetSize();
+ if (i > 0)
+ {
+ while(i-- > 0)
+ {
+ DeviceView * device = ((DeviceView*)devices.Get(i));
+ if (device) device->dev->Close();
+ }
+ }
+ unloadDevPlugins();
+
+ stopServer();
+ HWND f = mainMessageWindow;
+ mainMessageWindow = 0;
+ DestroyWindow(f);
+ delete global_config;
+
+ WASABI_API_SYSCB->syscb_deregisterCallback(&serviceWatcher);
+ serviceWatcher.Clear();
+
+ ServiceRelease( AGAVE_API_PLAYLISTS, api_playlistsGUID );
+ ServiceRelease( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceRelease( WASABI_API_SYSCB, syscbApiServiceGuid );
+ ServiceRelease( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceRelease( WASABI_API_LNG, languageApiGUID );
+ ServiceRelease( WASABI_API_MEMMGR, memMgrApiServiceGuid );
+ ServiceRelease( AGAVE_API_MLDB, mldbApiGuid );
+ ServiceRelease( AGAVE_API_PODCASTS, api_podcastsGUID );
+ ServiceRelease( AGAVE_API_STATS, AnonymousStatsGUID );
+ ServiceRelease( AGAVE_API_DEVICEMANAGER, DeviceManagerGUID );
+ ServiceRelease( AGAVE_API_ALBUMART, albumArtGUID );
+ ServiceRelease( AGAVE_API_METADATA, api_metadataGUID );
+
+ if (NULL != wasabiPngLoader)
+ {
+ ServiceRelease(wasabiPngLoader, pngLoaderGUID);
+ wasabiPngLoader = NULL;
+ }
+
+ DeleteCriticalSection(&csenumdrives);
+ TranscoderImp::quit();
+
+ delete gen_mlconfig;
+ Tataki::Quit();
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+
+ if (0 != viewAtom)
+ {
+ GlobalDeleteAtom(viewAtom);
+ viewAtom = 0;
+ }
+}
+
+typedef struct { ULONG_PTR param; void * proc; int state; CRITICAL_SECTION lock;} spc ;
+
+static VOID CALLBACK spc_caller(ULONG_PTR dwParam) {
+ spc * s = (spc*)dwParam;
+ if(!s) return;
+ EnterCriticalSection(&s->lock);
+ if(s->state == -1) { LeaveCriticalSection(&s->lock); DeleteCriticalSection(&s->lock); free(s); return; }
+ s->state = 2;
+ void (CALLBACK *p)(ULONG_PTR dwParam);
+ p = (void (CALLBACK *)(ULONG_PTR dwParam))s->proc;
+ if(p) p(s->param);
+ s->state=1;
+ LeaveCriticalSection(&s->lock);
+}
+
+static int spc_GetState(spc *s)
+{
+ EnterCriticalSection(&s->lock);
+ int state = s->state;
+ LeaveCriticalSection(&s->lock);
+ return state;
+}
+
+// p must be of type void (CALLBACK *)(ULONG_PTR dwParam). Returns 0 for success.
+int SynchronousProcedureCall(void * p, ULONG_PTR dwParam) {
+ if(!p) return 1;
+ spc * s = (spc*)calloc(1, sizeof(spc));
+ InitializeCriticalSection(&s->lock);
+ s->param = dwParam;
+ s->proc = p;
+ s->state = 0;
+#if 1 // _WIN32_WINNT >= 0x0400
+ if(!QueueUserAPC(spc_caller,hMainThread,(ULONG_PTR)s)) { DeleteCriticalSection(&s->lock); free(s); return 1; } //failed
+#else
+ if(!mainMessageWindow) { DeleteCriticalSection(&s->lock); free(s); return 1; } else PostMessage(mainMessageWindow,WM_USER+3,(WPARAM)spc_caller,(LPARAM)s);
+#endif
+ int i=0;
+ while (spc_GetState(s) != 1)
+ {
+ if (SleepEx(10,TRUE) == 0)
+ i++;
+ if(i >= 100)
+ {
+ EnterCriticalSection(&s->lock);
+ s->state = -1;
+ LeaveCriticalSection(&s->lock);
+ return 1;
+ }
+ }
+ DeleteCriticalSection(&s->lock);
+ free(s);
+ return 0;
+}
+
+static VOID CALLBACK getTranscoder(ULONG_PTR dwParam)
+{
+ C_Config * conf = global_config;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == *(Device **)dwParam) { conf = d->config; break; }
+ }
+ Transcoder ** t = (Transcoder**)dwParam;
+ *t = new TranscoderImp(plugin.hwndWinampParent,plugin.hDllInstance,conf, *(Device **)dwParam);
+}
+
+static void CALLBACK ProgressTimerTickCb(HWND hwnd, UINT uMsg, UINT_PTR eventId, unsigned long elapsed)
+{
+ wchar_t *buf = (wchar_t*)calloc(sizeof(wchar_t), 256);
+ NAVITEM itemInfo = {0};
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if (!AGAVE_API_DEVICEMANAGER && NULL == rootItem)
+ {
+ KillTimer(hwnd, eventId);
+ if (buf) free(buf);
+ return;
+ }
+
+ // TODO need to get this working for a merged instance...
+ int num = 0,total = 0, percent = 0;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(dev);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(dev);
+ int txProgress = getTransferProgress(dev);
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ int device_num = (100 * size) - txProgress;
+ num += device_num;
+ int device_total = 100 * size + 100 * (finishedTX ? finishedTX->GetSize() : 0);
+ total += device_total;
+ percent = (0 != device_total) ? (((device_total - device_num) * 100) / device_total) : -1;
+ // TODO need to do something to handle it sticking on 100%
+ if (dev->queueTreeItem)
+ {
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = dev->queueTreeItem;
+ itemInfo.mask = NIMF_PARAM;
+
+ if (MLNavItem_GetInfo(plugin.hwndLibraryParent, &itemInfo) && itemInfo.lParam != percent)
+ {
+ if (-1 == percent)
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERS,buf, 256);
+ else
+ StringCbPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_TRANSFERS_PERCENT), percent);
+
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = buf;
+ itemInfo.lParam = percent;
+ itemInfo.cchTextMax = -1;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+ }
+ }
+
+ dev->UpdateActivityState();
+ }
+
+ if (!rootItem)
+ {
+ if (buf) free(buf);
+ return;
+ }
+
+ percent = (0 != total) ? (((total-num)*100)/total) : -1;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.hItem = rootItem;
+ itemInfo.mask = NIMF_PARAM;
+
+ if (FALSE == MLNavItem_GetInfo(plugin.hwndLibraryParent, &itemInfo))
+ {
+ if (buf) free(buf);
+ return;
+ }
+
+ if (itemInfo.lParam != percent)
+ {
+ if (-1 == percent)
+ WASABI_API_LNGSTRINGW_BUF(IDS_PORTABLES,buf,256);
+ else
+ StringCbPrintfW(buf, sizeof(buf), WASABI_API_LNGSTRINGW(IDS_PORTABLES_PERCENT),percent);
+
+ itemInfo.mask |= NIMF_TEXT;
+ itemInfo.pszText = buf;
+ itemInfo.lParam = percent;
+ itemInfo.cchTextMax = -1;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+ }
+ if (buf) free(buf);
+}
+
+extern LRESULT CALLBACK TranscodeMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void EnumDrives(ENUM_DRIVES_CALLBACK callback)
+{
+ DWORD drives=GetLogicalDrives();
+ wchar_t drivestr[4] = L"X:\\";
+ for(int i=2;i<25;i++) if(drives&(1<<i))
+ {
+ EnterCriticalSection(&csenumdrives);
+ UINT olderrmode=SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ drivestr[0] = L'A'+i;
+ callback(drivestr[0],GetDriveTypeW(drivestr));
+ SetErrorMode(olderrmode);
+ LeaveCriticalSection(&csenumdrives);
+ }
+}
+
+LRESULT CALLBACK DeviceMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if(uMsg == WM_WA_IPC && hwnd != mainMessageWindow) return TranscodeMsgProc(hwnd,uMsg,wParam,lParam);
+ switch(uMsg)
+ {
+ case WM_USER: // hotkey: sync
+ if(devices.GetSize() > 0)
+ {
+ if (!((DeviceView *)devices.Get(0))->isCloudDevice)
+ ((DeviceView *)devices.Get(0))->Sync();
+ else
+ ((DeviceView *)devices.Get(0))->CloudSync();
+ }
+ break;
+ case WM_USER+1: // hotkey: autofill
+ if(devices.GetSize() > 0)
+ ((DeviceView *)devices.Get(0))->Autofill();
+ break;
+ case WM_USER+2: // hotkey: eject
+ if(devices.GetSize() > 0)
+ ((DeviceView *)devices.Get(0))->Eject();
+ break;
+ case WM_USER+3:
+ {
+ void (CALLBACK *p)(ULONG_PTR dwParam);
+ p = (void (CALLBACK *)(ULONG_PTR dwParam))wParam;
+ p((ULONG_PTR)lParam);
+ }
+ break;
+ case WM_USER+4: // refreshes cloud views (re-checks if we've moved away)
+ if (IsWindow(hwndMediaView) && currentViewedDevice && currentViewedDevice->isCloudDevice)
+ {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ break;
+ case WM_USER+5: // removes cloud devices from a cloud sources logout...
+ {
+ for (int i = devices.GetSize() - 1; i > -1; --i)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (dev->isCloudDevice)
+ dev->dev->Close();
+ }
+ break;
+ }
+ case WM_PMP_IPC:
+ {
+ switch(lParam)
+ {
+ case PMP_IPC_DEVICECONNECTED:
+ deviceConnected((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICEDISCONNECTED:
+ deviceDisconnected((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICELOADING:
+ deviceLoading((pmpDeviceLoading *)wParam);
+ break;
+ case PMP_IPC_DEVICENAMECHANGED:
+ deviceNameChanged((Device*)wParam);
+ break;
+ case PMP_IPC_DEVICECLOUDTRANSFER:
+ {
+ int ret = 0;
+ cloudDeviceTransfer * transfer = (cloudDeviceTransfer *)wParam;
+ if (transfer)
+ {
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (dev->dev->extraActions(DEVICE_IS_CLOUD_TX_DEVICE, (intptr_t)transfer->device_token, 0, 0))
+ {
+ ret = dev->TransferFromML(ML_TYPE_FILENAMESW, (void *)transfer->filenames, 0, 1);
+ }
+ }
+ }
+ return ret;
+ }
+ case PMP_IPC_GETCLOUDTRANSFERS:
+ {
+ typedef std::vector<wchar_t*> CloudFiles;
+ CloudFiles *pending = (CloudFiles *)wParam;
+ if (pending)
+ {
+ cloudTransferQueue.lock();
+ // TODO de-dupe incase going to multiple devices...
+ for(int i = 0; i < cloudTransferQueue.GetSize(); i++)
+ {
+ pending->push_back(((CopyInst *)cloudTransferQueue.Get(i))->sourceFile);
+ }
+ cloudTransferQueue.unlock();
+
+ return pending->size();
+ }
+ return 0;
+ }
+ case PMP_IPC_GET_TRANSCODER:
+ {
+ void * t = (void*)wParam;
+ getTranscoder((ULONG_PTR)&t);
+ if (t == (void*)wParam) return 0;
+ return (LRESULT)t;
+ }
+ case PMP_IPC_RELEASE_TRANSCODER:
+ delete ((TranscoderImp *)wParam);
+ break;
+ case PMP_IPC_ENUM_ACTIVE_DRIVES:
+ {
+ if (wParam == 0)
+ return (LRESULT)EnumDrives;
+ else
+ EnumDrives((ENUM_DRIVES_CALLBACK)wParam);
+ }
+ break;
+ case PMP_IPC_GET_INI_FILE:
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if(dev->dev == (Device*)wParam) return (intptr_t)dev->config->GetIniFile();
+ }
+ break;
+ case PMP_IPC_GET_PREFS_VIEW:
+ {
+ pmpDevicePrefsView *view = (pmpDevicePrefsView *)wParam;
+ if (view)
+ {
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * dev = (DeviceView *)devices.Get(i);
+ if (!lstrcmpA(dev->GetName(), view->dev_name))
+ {
+ return (LRESULT)OnSelChanged(view->parent, view->parent, dev);
+ }
+ }
+ }
+ return 0;
+ }
+ }
+ }
+ break;
+ case WM_DEVICECHANGE:
+ {
+ int r = wmDeviceChange(wParam,lParam);
+ if (r) return r;
+ }
+ break;
+ case WM_TIMER:
+ switch(wParam)
+ {
+ case COMMITTIMERID:
+ {
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if (txQueue)
+ {
+ if(d->commitNeeded && !txQueue->GetSize())
+ {
+ d->commitNeeded = false;
+ d->dev->commitChanges();
+ }
+ }
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+void UpdateLoadingCaption(wchar_t * caption, void * context)
+{
+ NAVITEM itemInfo = {0};
+
+ itemInfo.hItem = (HNAVITEM)context;
+ if (NULL == itemInfo.hItem)
+ return;
+
+ itemInfo.cbSize = sizeof(itemInfo);
+ itemInfo.pszText = caption;
+ itemInfo.cchTextMax = -1;
+ itemInfo.mask = NIMF_TEXT;
+
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo);
+}
+
+void deviceLoading(pmpDeviceLoading * load)
+{
+ if (!AGAVE_API_DEVICEMANAGER)
+ {
+ wchar_t buffer[256] = {0};
+ NAVINSERTSTRUCT nis = {0};
+ MLTREEIMAGE devIcon;
+
+ devIcon.resourceId = IDR_DEVICE_ICON;
+ devIcon.hinst = plugin.hDllInstance;
+ if(load->dev)
+ load->dev->extraActions(DEVICE_SET_ICON,(intptr_t)&devIcon,0,0);
+
+ nis.hParent = GetNavigationRoot(TRUE);
+ nis.hInsertAfter = NCI_LAST;
+ nis.item.cbSize = sizeof(NAVITEM);
+ nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_IMAGE | NIMF_IMAGESEL;
+ nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_LOADING, buffer, ARRAYSIZE(buffer));
+ nis.item.pszInvariant = L"device_loading";
+ nis.item.style = NIS_ITALIC;
+ nis.item.iImage = icon_store.GetResourceIcon(devIcon.hinst, (LPCWSTR)MAKEINTRESOURCE(devIcon.resourceId));
+ nis.item.iSelectedImage = nis.item.iImage;
+ load->context = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis);
+ load->UpdateCaption = UpdateLoadingCaption;
+ loadingDevices.Add(load);
+ }
+}
+
+void finishDeviceLoading(Device * dev)
+{
+ for(int i=0; i < loadingDevices.GetSize(); i++)
+ {
+ pmpDeviceLoading * l = (pmpDeviceLoading *)loadingDevices.Get(i);
+ if(l->dev == dev) {
+ loadingDevices.Del(i);
+ HNAVITEM treeItem = (HNAVITEM)l->context;
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent,treeItem);
+ return;
+ }
+ }
+}
+
+void deviceConnected(Device * dev)
+{
+ if (!AGAVE_API_DEVICEMANAGER)
+ GetNavigationRoot(TRUE);
+
+ Device * oldDev = dev;
+
+ finishDeviceLoading(oldDev);
+ if(!devices.GetSize()) startServer();
+
+ DeviceView *pmp_device = new DeviceView(dev);
+ devices.Add(pmp_device);
+ UpdateDevicesListView(false);
+}
+
+void deviceDisconnected(Device * dev)
+{
+ finishDeviceLoading(dev);
+ int cloud_count = 0;
+ for(int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == dev)
+ {
+ d->threadKillswitch = 1;
+ d->transferContext.WaitForKill();
+ devices.Del(i);
+ d->Unregister();
+ d->Release();
+ }
+ else
+ {
+ // to keep the 'cloud library' node on the correct expanded state
+ // we need to adjust the cloud sources count due to 'all sources'
+ if (_strnicmp(d->GetName(), "all_sources", 11) && d->isCloudDevice)
+ {
+ cloud_count += 1;
+ }
+ }
+ }
+
+ // if we're only showing a single device, then we can just disable everything else
+ if (cloud_count == 0)
+ {
+ HNAVITEM cloud_transfers = NavigationItem_Find(0, L"cloud_transfers", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ cloudQueueTreeItem = NULL;
+ }
+
+ cloud_transfers = NavigationItem_Find(0, L"cloud_add_sources", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ }
+
+ cloud_transfers = NavigationItem_Find(0, L"cloud_byos", TRUE);
+ if (cloud_transfers != NULL)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, cloud_transfers);
+ }
+
+ HNAVITEM cloud = NavigationItem_Find(0, L"cloud_sources", TRUE);
+ if (cloud != NULL)
+ {
+ NAVITEM item = {0};
+ item.cbSize = sizeof(NAVITEM);
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ item.hItem = cloud;
+ item.iImage = item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD);
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+ }
+
+ UpdateDevicesListView(false);
+ if(!devices.GetSize() && !loadingDevices.GetSize() && global_config->ReadInt(L"HideRoot",0))
+ {
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if (NULL != rootItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+ }
+ if(!devices.GetSize()) stopServer();
+}
+
+void deviceNameChanged(Device * dev)
+{
+ finishDeviceLoading(dev);
+ for(int i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if(d->dev == dev)
+ {
+ wchar_t *buffer = (wchar_t*)calloc(sizeof(wchar_t),128);
+ if (buffer)
+ {
+ dev->getPlaylistName(0, buffer, 128);
+ UpdateLoadingCaption(buffer, d->treeItem);
+ d->SetDisplayName(buffer, 1);
+ free(buffer);
+ }
+ break;
+ }
+ }
+}
+
+svc_imageLoader *GetPngLoaderService()
+{
+ if (NULL == wasabiPngLoader)
+ {
+ if (NULL == WASABI_API_MEMMGR)
+ return NULL;
+
+ ServiceBuild(wasabiPngLoader, pngLoaderGUID);
+ }
+
+ return wasabiPngLoader;
+}
+
+BOOL FormatResProtocol(const wchar_t *resourceName, const wchar_t *resourceType, wchar_t *buffer, size_t bufferMax)
+{
+ unsigned long filenameLength;
+
+ if (NULL == resourceName)
+ return FALSE;
+
+ if (FAILED(StringCchCopyExW(buffer, bufferMax, L"res://", &buffer, &bufferMax, 0)))
+ return FALSE;
+
+ filenameLength = GetModuleFileNameW(plugin.hDllInstance, buffer, bufferMax);
+ if (0 == filenameLength || bufferMax == filenameLength)
+ return FALSE;
+
+ buffer += filenameLength;
+ bufferMax -= filenameLength;
+
+ if (NULL != resourceType)
+ {
+ if (FALSE != IS_INTRESOURCE(resourceType))
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceType)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceType)))
+ return FALSE;
+ }
+ }
+
+ if (FALSE != IS_INTRESOURCE(resourceName))
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/#%d", (int)(INT_PTR)resourceName)))
+ return FALSE;
+ }
+ else
+ {
+ if (FAILED(StringCchPrintfExW(buffer, bufferMax, &buffer, &bufferMax, 0, L"/%s", resourceName)))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL
+CenterWindow(HWND window, HWND centerWindow)
+{
+ RECT centerRect, windowRect;
+ long x, y;
+
+ if (NULL == window ||
+ FALSE == GetWindowRect(window, &windowRect))
+ {
+ return FALSE;
+ }
+
+ if (CENTER_OVER_ML_VIEW == centerWindow)
+ {
+ centerWindow = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0);
+ if (NULL == centerWindow || FALSE == IsWindowVisible(centerWindow))
+ centerWindow = CENTER_OVER_ML;
+ }
+
+ if (CENTER_OVER_ML == centerWindow)
+ {
+ centerWindow = plugin.hwndLibraryParent;
+ if (NULL == centerWindow || FALSE == IsWindowVisible(centerWindow))
+ centerWindow = CENTER_OVER_WINAMP;
+ }
+
+ if (CENTER_OVER_WINAMP == centerWindow)
+ {
+ centerWindow = (HWND)SENDWAIPC(plugin.hwndWinampParent, IPC_GETDIALOGBOXPARENT, 0);
+ if (FALSE == IsChild(centerWindow, plugin.hwndLibraryParent))
+ centerWindow = NULL;
+ }
+
+ if (NULL == centerWindow ||
+ FALSE == IsWindowVisible(centerWindow) ||
+ FALSE == GetWindowRect(centerWindow, &centerRect))
+ {
+ HMONITOR monitor;
+ MONITORINFO monitorInfo;
+
+ monitor = MonitorFromWindow(plugin.hwndWinampParent, MONITOR_DEFAULTTONEAREST);
+
+ monitorInfo.cbSize = sizeof(monitorInfo);
+
+ if (NULL == monitor ||
+ FALSE == GetMonitorInfo(monitor, &monitorInfo) ||
+ FALSE == CopyRect(&centerRect, &monitorInfo.rcWork))
+ {
+ CopyRect(&centerRect, &windowRect);
+ }
+ }
+
+ x = centerRect.left + ((centerRect.right - centerRect.left)- (windowRect.right - windowRect.left))/2;
+ y = centerRect.top + ((centerRect.bottom - centerRect.top)- (windowRect.bottom - windowRect.top))/2;
+
+ if (x == windowRect.left && y == windowRect.top)
+ return FALSE;
+
+ return SetWindowPos(window, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
+}
+ATOM
+GetViewAtom()
+{
+ return viewAtom;
+}
+
+void *
+GetViewData(HWND hwnd)
+{
+ return GetPropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom));
+}
+
+BOOL
+SetViewData(HWND hwnd, void *data)
+{
+ return SetPropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom), data);
+}
+
+void *
+RemoveViewData(HWND hwnd)
+{
+ return RemovePropW(hwnd, (const wchar_t*)MAKEINTATOM(viewAtom));
+}
+
+static void URL_GetParameter(const wchar_t *param_start, char *dest, size_t dest_len)
+{
+ if (!param_start)
+ {
+ dest[0]=0;
+ return;
+ }
+
+ while (param_start && *param_start++ != '=');
+
+ while (param_start && *param_start && dest_len > 1 && *param_start != L'&')
+ {
+ if (*param_start == '+')
+ {
+ param_start++;
+ *dest++=' ';
+ }
+ else if (*param_start == L'%' && param_start[1] != L'%' && param_start[1])
+ {
+ int a=0;
+ int b=0;
+ for ( b = 0; b < 2; b ++)
+ {
+ int r=param_start[1+b];
+ if (r>='0'&&r<='9') r-='0';
+ else if (r>='a'&&r<='z') r-='a'-10;
+ else if (r>='A'&&r<='Z') r-='A'-10;
+ else break;
+ a*=16;
+ a+=r;
+ }
+ if (b < 2)
+ {
+ *dest++=(char)*param_start++;
+ }
+ else
+ {
+ *dest++=a;
+ param_start += 3;}
+ }
+ else
+ {
+ *dest++=(char)*param_start++;
+ }
+ dest_len--;
+ }
+ *dest = 0;
+}
+
+// this only works for one-character parameters
+static const wchar_t *GetParameterStart(const wchar_t *url, wchar_t param)
+{
+ wchar_t lookup[4] = { L'&', param, L'=', 0 };
+ lookup[1] = param;
+ const wchar_t *val = wcsstr(url, lookup);
+ if (!val)
+ {
+ lookup[0] = L'?';
+ val = wcsstr(url, lookup);
+ }
+ return val;
+}
+extern int serverPort;
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ int i;
+ if (message_type == ML_IPC_HOOKEXTINFOW)
+ {
+ extendedFileInfoStructW *hookMetadata = (extendedFileInfoStructW *)param1;
+ if (hookMetadata->filename && !wcsncmp(hookMetadata->filename, L"http://127.0.0.1:", 17) && _wtoi(hookMetadata->filename + 17) == serverPort)
+ {
+ if (!_wcsicmp(hookMetadata->metadata, L"artist"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 'a'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ else if (!_wcsicmp(hookMetadata->metadata, L"album"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 'l'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ else if (!_wcsicmp(hookMetadata->metadata, L"title"))
+ {
+ char metadata[1024] = {0};
+ URL_GetParameter(GetParameterStart(hookMetadata->filename, 't'), metadata, 1024);
+ MultiByteToWideCharSZ(CP_UTF8, 0, metadata, -1, hookMetadata->ret, hookMetadata->retlen);
+ return 1;
+ }
+ }
+ }
+
+ for(i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView *deviceView;
+ deviceView = (DeviceView *)devices.Get(i);
+ if (NULL != deviceView)
+ {
+ INT_PTR a= deviceView->MessageProc(message_type,param1,param2,param3);
+ if(0 != a)
+ return a;
+ }
+ }
+
+
+ if (message_type >= ML_MSG_TREE_BEGIN &&
+ message_type <= ML_MSG_TREE_END)
+ {
+ HNAVITEM item, rootItem;
+ item = GetNavigationItemFromMessage(message_type, param1);
+ rootItem = GetNavigationRoot(FALSE);
+
+ if(message_type == ML_MSG_TREE_ONCREATEVIEW)
+ {
+ for(i=0; i < loadingDevices.GetSize(); i++)
+ {
+ pmpDeviceLoading * l = (pmpDeviceLoading *)loadingDevices.Get(i);
+ if(NULL != l->context)
+ {
+ if (((HNAVITEM)l->context) == item)
+ {
+ HNAVITEM parentItem;
+ parentItem = MLNavItem_GetParent(plugin.hwndLibraryParent, item);
+ if (NULL == parentItem)
+ parentItem = rootItem;
+
+ PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)parentItem, ML_IPC_NAVITEM_SELECT);
+ return 0;
+ }
+ }
+ }
+ }
+
+ if(NULL != item && item == rootItem)
+ {
+ switch (message_type)
+ {
+ case ML_MSG_TREE_ONCREATEVIEW:
+ return (INT_PTR)WASABI_API_CREATEDIALOGW(IDD_VIEW_PMP_DEVICES,(HWND)param2,pmp_devices_dlgproc);
+ case ML_MSG_TREE_ONCLICK:
+ switch(param2)
+ {
+ case ML_ACTION_RCLICK:
+ {
+ HMENU menu = GetSubMenu(m_context_menus,6);
+ int hideRoot = global_config->ReadInt(L"HideRoot",0);
+ CheckMenuItem(menu,ID_MAINTREEROOT_AUTOHIDEROOT,hideRoot?MF_CHECKED:MF_UNCHECKED);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,p.x,p.y,plugin.hwndLibraryParent,NULL);
+ switch(r)
+ {
+ case ID_MAINTREEROOT_AUTOHIDEROOT:
+ hideRoot = hideRoot?0:1;
+ global_config->WriteInt(L"HideRoot",hideRoot);
+ if(hideRoot && devices.GetSize() == 0)
+ {
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+ }
+ break;
+ case ID_MAINTREEROOT_PREFERENCES:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &pluginsPrefsPage);
+ break;
+ case ID_MAINTREEROOT_HELP:
+ SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8106455294612-Winamp-Portables-Guide");
+ break;
+ }
+ }
+ break;
+ case ML_ACTION_DBLCLICK:
+ break;
+ case ML_ACTION_ENTER:
+ break;
+ }
+ break;
+ case ML_MSG_TREE_ONDROPTARGET:
+ break;
+ case ML_MSG_TREE_ONDRAG:
+ break;
+ case ML_MSG_TREE_ONDROP:
+ break;
+ case ML_MSG_NAVIGATION_ONDELETE:
+ navigationRoot = NULL;
+ KillTimer(mainMessageWindow, PROGRESSTIMERID);
+ return TRUE;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_NO_CONFIG)
+ {
+ if(prefsPage._id == 0)
+ return TRUE;
+ }
+ else if (message_type == ML_MSG_CONFIG)
+ {
+ if(prefsPage._id == 0) return 0;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, prefsPage._id, IPC_OPENPREFSTOPAGE);
+ }
+ else if (message_type == ML_MSG_NOTOKTOQUIT)
+ {
+ // see if we have any transfers in progress and if so then prompt on what to do...
+ bool transfers = false;
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if(txQueue && txQueue->GetSize() > 0)
+ {
+ transfers = true;
+ break;
+ }
+ }
+
+ if (transfers)
+ {
+ wchar_t titleStr[32] = {0};
+ if (MessageBoxW(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_CANCEL_TRANSFERS_AND_QUIT),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRM_QUIT,titleStr,32), MB_YESNO | MB_ICONQUESTION) == IDNO)
+ return TRUE;
+ }
+ return FALSE;
+ }
+ else if(message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_ITEMRECORDLISTW ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW ||
+ param1 == ML_TYPE_PLAYLISTS || param1 == ML_TYPE_PLAYLIST)
+ {
+ if (gen_mlconfig->ReadInt(L"pmp_send_to", DEFAULT_PMP_SEND_TO))
+ {
+ for(int m = 0, mode = 0; m < 2; m++, mode++)
+ {
+ int added = 0;
+ for(i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView *deviceView;
+ deviceView = (DeviceView *)devices.Get(i);
+ if (NULL != deviceView)
+ {
+ if (deviceView->dev->extraActions(DEVICE_SENDTO_UNSUPPORTED, 0, 0, 0) == 0)
+ {
+ wchar_t buffer[128] = {0};
+ deviceView->dev->getPlaylistName(0, buffer, 128);
+ if (buffer[0])
+ {
+ // TODO - this is to block true playlists from appearing on the sendto
+ // for cloud playlists handling - remove this when we can do more
+ // than just uploading the playlist blob without the actual files
+ if (deviceView->isCloudDevice && param3 == ML_TYPE_PLAYLIST) continue;
+
+ if (!deviceView->isCloudDevice == mode)
+ {
+ if (!added)
+ {
+ mediaLibrary.BranchSendTo(param2);
+ added = 1;
+ }
+ mediaLibrary.AddToBranchSendTo(buffer, param2, reinterpret_cast<INT_PTR>(deviceView));
+ }
+ }
+ }
+ }
+ }
+
+ if (added)
+ {
+ mediaLibrary.EndBranchSendTo(WASABI_API_LNGSTRINGW((!m ? IDS_SENDTO_CLOUD : IDS_SENDTO_DEVICES)), param2);
+ }
+ }
+ }
+ }
+ }
+
+ else if (message_type == ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE)
+ {
+ enqueuedef = param1;
+ groupBtn = param2;
+ if (IsWindow(hwndMediaView))
+ PostMessage(hwndMediaView, WM_APP + 104, param1, param2);
+ return 0;
+ }
+
+ return 0;
+}
+
+extern "C" {
+ __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ // cleanup the ml tree so the portables root isn't left
+
+ HNAVITEM rootItem = GetNavigationRoot(FALSE);
+ if(NULL != rootItem)
+ MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, rootItem);
+
+ // prompt to remove our settings with default as no (just incase)
+ wchar_t title[256] = {0};
+ StringCbPrintfW(title, ARRAYSIZE(title), WASABI_API_LNGSTRINGW(IDS_NULLSOFT_PMP_SUPPORT), PLUGIN_VERSION);
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ title,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ global_config->WriteString(0,0);
+ }
+
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&pluginsPrefsPage,IPC_REMOVE_PREFS_DLG);
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/main.h b/Src/Plugins/Library/ml_pmp/main.h
new file mode 100644
index 00000000..53d2767e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/main.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <wtypes.h>
+#include <windowsx.h>
+
+#include "./graphics.h"
+#include "DeviceView.h"
+#include "../ml_pmp/pmp.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include <map>
+#include "./syncDialog.h"
+#include "./syncCloudDialog.h"
+
+#ifndef LONGX86
+#ifdef _WIN64
+ #define LONGX86 LONG_PTR
+#else /*_WIN64*/
+ #define LONGX86 LONG
+#endif /*_WIN64*/
+#endif // LONGX86
+
+#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
+
+#ifdef __cplusplus
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) ::SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#else
+ #define SENDMSG(__hwnd, __msgId, __wParam, __lParam) SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
+#endif // __cplusplus
+
+#define SENDMLIPC(__hwndML, __ipcMsgId, __param) SENDMSG((__hwndML), WM_ML_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define SENDWAIPC(__hwndWA, __ipcMsgId, __param) SENDMSG((__hwndWA), WM_WA_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
+
+#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
+
+#define DIALOG_RESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
+
+#ifndef RECTWIDTH
+ #define RECTWIDTH(__r) ((__r).right - (__r).left)
+#endif
+
+#ifndef RECTHEIGHT
+ #define RECTHEIGHT(__r) ((__r).bottom - (__r).top)
+#endif
+
+#undef CLAMP
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+#ifndef ARRAYSIZE
+ #define ARRAYSIZE(_a) (sizeof(_a)/sizeof((_a)[0]))
+#endif
+
+#define DEFAULT_PMP_SEND_TO 1
+extern DeviceView *configDevice;
+extern winampMediaLibraryPlugin plugin;
+extern C_ItemList devices;
+extern HWND mainMessageWindow;
+extern std::map<DeviceView *, bool> device_update_map;
+extern C_Config * global_config;
+extern void Devices_Init();
+extern HMENU m_context_menus, m_context_menus2;
+
+/* indirectplaybackserver.cpp - for localhost HTTP-based playback (for devices that don't support direct playback */
+void startServer();
+void stopServer();
+
+ATOM GetViewAtom();
+
+void *GetViewData(HWND hwnd);
+
+BOOL SetViewData(HWND hwnd, void *data);
+
+void *RemoveViewData(HWND hwnd);
+
+BOOL FormatResProtocol(const wchar_t *resourceName,
+ const wchar_t *resourceType,
+ wchar_t *buffer,
+ size_t bufferMax);
+
+#define CENTER_OVER_WINAMP ((HWND)0)
+#define CENTER_OVER_ML ((HWND)1)
+#define CENTER_OVER_ML_VIEW ((HWND)2)
+
+BOOL CenterWindow(HWND window, HWND centerWindow);
+HWND OnSelChanged(HWND hwndDlg, HWND external, DeviceView *dev);
+
+LinkedQueue *getTransferQueue(DeviceView *deviceView = NULL);
+LinkedQueue *getFinishedTransferQueue(DeviceView *deviceView = NULL);
+int getTransferProgress(DeviceView *deviceView = NULL);
+
+extern int groupBtn, customAllowed, enqueuedef; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/metadata_utils.cpp b/Src/Plugins/Library/ml_pmp/metadata_utils.cpp
new file mode 100644
index 00000000..4a238612
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/metadata_utils.cpp
@@ -0,0 +1,516 @@
+#include "main.h"
+#include "DeviceView.h"
+#include "metadata_utils.h"
+#include "../nu/sort.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+#include "api__ml_pmp.h"
+
+#define METADATASTRATAGYSWITCH 500
+
+filenameMap **filenameMapping;
+int filenameMapLen;
+static INT_PTR CALLBACK findingMetadata2_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static int i;
+ static int added;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ SendDlgItemMessage(hwndDlg, IDC_METADATAPROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, filenameMapLen));
+ i=0;
+ added=0;
+ SetTimer(hwndDlg, 1, 10, NULL);
+
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+
+ SetForegroundWindow(hwndDlg);
+ return 0;
+
+ case WM_TIMER:
+ if(wParam == 1) {
+ KillTimer(hwndDlg, 1);
+ filenameMap **map = filenameMapping;
+ int len = filenameMapLen;
+ for(; i < len; i++) {
+ if (i % 25 == 0) SendDlgItemMessage(hwndDlg, IDC_METADATAPROGRESS, PBM_SETPOS, i, 0);
+
+ itemRecordW *result = AGAVE_API_MLDB->GetFile(map[i]->fn);
+ map[i]->ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1);
+ if (result) {
+ copyRecord(map[i]->ice, result);
+ AGAVE_API_MLDB->FreeRecord(result);
+
+ if(i % 200 == 0) {
+ i++;
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ return 0;
+ }
+ } else {
+ filenameToItemRecord(map[i]->fn, map[i]->ice); // ugh. Disk intensive.
+ SendMessage(plugin.hwndWinampParent, WM_ML_IPC, (WPARAM)map[i]->ice, ML_IPC_DB_ADDORUPDATEITEMW);
+ added++;
+ i++;
+ PostMessage(hwndDlg, WM_TIMER, 1, 0);
+ return 0;
+ }
+ }
+ if (added) SendMessage(plugin.hwndWinampParent,WM_ML_IPC,0,ML_IPC_DB_SYNCDB);
+ EndDialog(hwndDlg,0);
+ }
+ break;
+ }
+ return 0;
+}
+
+void mapFilesToItemRecords(filenameMap ** map0, int len, HWND centerWindow) {
+ filenameMapping = map0;
+ filenameMapLen = len;
+ if (filenameMapLen > 0)
+ WASABI_API_DIALOGBOXPARAMW(IDD_GETTINGMETADATA,plugin.hwndWinampParent, findingMetadata2_dlgproc, (LPARAM)centerWindow);
+}
+
+C_ItemList * fileListToItemRecords(wchar_t** files,int l, HWND centerWindow) {
+ filenameMap ** map = (filenameMap **)calloc(l, sizeof(void*));
+ filenameMap * m = (filenameMap *)calloc(l, sizeof(filenameMap));
+ for(int i=0; i<l; i++) {
+ map[i] = &m[i];
+ map[i]->fn = files[i];
+ }
+
+ mapFilesToItemRecords(map, l, centerWindow);
+
+ C_ItemList * out = new C_ItemList;
+ for(int i=0; i<l; i++)
+ out->Add(map[i]->ice);
+ free(m);
+ free(map);
+ return out;
+}
+
+C_ItemList * fileListToItemRecords(C_ItemList * fileList, HWND centerWindow) {
+ return fileListToItemRecords((wchar_t**)fileList->GetAll(),fileList->GetSize(), centerWindow);
+}
+
+typedef struct
+{
+ songid_t songid;
+ Device * dev;
+} SortSongItem;
+
+#define RETIFNZ(v) { int zz = (v); if(zz) return zz; }
+#define CMPFIELDS(x) { x(a,bufa,256); x(b,bufb,256); int v = lstrcmpiW(bufa,bufb); if(v) return v; }
+
+int __fastcall compareSongs(const void *elem1, const void *elem2, const void *context) {
+ songid_t a = *(songid_t*)elem1;
+ songid_t b = *(songid_t*)elem2;
+ if(a == b) return 0;
+ Device * dev = (Device *)context;
+ wchar_t bufa[256] = {0};
+ wchar_t bufb[256] = {0};
+ CMPFIELDS(dev->getTrackArtist)
+ CMPFIELDS(dev->getTrackAlbum)
+ CMPFIELDS(dev->getTrackTitle)
+ int t1 = dev->getTrackTrackNum(a);
+ int t2 = dev->getTrackTrackNum(b);
+ if(t1>0 && t2>0) RETIFNZ(t1 - t2)
+ return 0;
+}
+
+#undef CMPFIELDS
+
+static __forceinline int strcmp_nullok(wchar_t * x,wchar_t * y) {
+ if(!x) x=L"";
+ if(!y) y=L"";
+ return lstrcmpiW(x,y);
+}
+
+int compareItemRecordAndSongId(itemRecordW * item, songid_t song, Device *dev)
+{
+ wchar_t buf[2048] = {0};
+ dev->getTrackArtist(song,buf,sizeof(buf)/sizeof(wchar_t));
+ RETIFNZ(strcmp_nullok(buf,item->artist));
+ dev->getTrackAlbum(song,buf,sizeof(buf)/sizeof(wchar_t));
+ RETIFNZ(strcmp_nullok(buf,item->album));
+ dev->getTrackTitle(song,buf,sizeof(buf)/sizeof(wchar_t));
+ RETIFNZ(strcmp_nullok(buf,item->title));
+ int t = dev->getTrackTrackNum(song);
+ if(item->track>0 && t>0) RETIFNZ(t - item->track);
+ return 0;
+}
+
+int compareItemRecords(itemRecordW * a, itemRecordW * b) {
+ if(a == b) return 0;
+ RETIFNZ(lstrcmpiW(a->artist?a->artist:L"",b->artist?b->artist:L""));
+ RETIFNZ(lstrcmpiW(a->album?a->album:L"",b->album?b->album:L""));
+ RETIFNZ(lstrcmpiW(a->title?a->title:L"",b->title?b->title:L""));
+ if(a->track>0 && b->track>0) RETIFNZ(a->track - b->track);
+ return 0;
+}
+
+static int sortfunc_ItemRecords_map(const void *elem1, const void *elem2) {
+ PlaylistAddItem *a = *(PlaylistAddItem **)elem1;
+ PlaylistAddItem *b = *(PlaylistAddItem **)elem2;
+ return compareItemRecords(a->item,b->item);
+}
+
+static int sortfunc_ItemRecords(const void *elem1, const void *elem2) {
+ itemRecordW *a = *(itemRecordW **)elem1;
+ itemRecordW *b = *(itemRecordW **)elem2;
+ return compareItemRecords(a,b);
+}
+
+#undef RETIFNZ
+
+/* Gay Venn Diagram(tm) explaining ProcessDatabaseDifferences. Leave arguments NULL if not required
+ ml device
+ /------\ /------\
+ / X \
+ / / \<-------\---songsInML and itemRecordsOnDevice
+ \ \ / /
+ \ X /
+ \------/ \------/
+ ^ ^--------songsNotInML
+ |--itemRecordsNotOnDevice
+*/
+// Runs in O(nlogn) of largest list
+void ProcessDatabaseDifferences(Device * dev, C_ItemList * ml0,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML) {
+ C_ItemList device2;
+ C_ItemList *device0=&device2;
+
+ int l = dev->getPlaylistLength(0);
+ for(int i=0; i<l; i++) device0->Add((void*)dev->getPlaylistTrack(0,i));
+
+ qsort(ml0->GetAll(),ml0->GetSize(),sizeof(void*),sortfunc_ItemRecords);
+ nu::qsort(device0->GetAll(), device0->GetSize(), sizeof(void*), dev, compareSongs);
+
+ C_ItemList *ml = new C_ItemList;
+ C_ItemList *device = new C_ItemList;
+
+ int i,j;
+ {
+ itemRecordW * lastice = NULL;
+ songid_t lastsong = NULL;
+ for(i=0; i<ml0->GetSize(); i++) {
+ itemRecordW * it = (itemRecordW*)ml0->Get(i);
+ if(lastice) if(compareItemRecords(lastice,it)==0) continue;
+ ml->Add(it);
+ lastice = it;
+ }
+ for(i=0; i<device0->GetSize(); i++) {
+ songid_t song = (songid_t)device0->Get(i);
+ if(lastsong) if(compareSongs((void*)&song,(void*)&lastsong, dev)==0) continue;
+ device->Add((void*)song);
+ lastsong = song;
+ }
+ }
+
+ i=0,j=0;
+ int li = device->GetSize();
+ int lj = ml->GetSize();
+ while(i<li && j<lj) {
+ itemRecordW * it = (itemRecordW*)ml->Get(j);
+ songid_t song = (songid_t)device->Get(i);
+
+ int cmp = compareItemRecordAndSongId(it,song, dev);
+ if(cmp == 0) { // song on both
+ if(itemRecordsOnDevice) itemRecordsOnDevice->Add(it);
+ if(songsInML) songsInML->Add((void*)song);
+ i++;
+ j++;
+ }
+ else if(cmp > 0) { //song in ml and not on device
+ if(itemRecordsNotOnDevice) itemRecordsNotOnDevice->Add(it);
+ j++;
+ }
+ else { // song on device but not in ML
+ if(songsNotInML) songsNotInML->Add((void*)song);
+ i++;
+ }
+ }
+
+ // any leftovers?
+ if(songsNotInML) while(i<li) {
+ songid_t song = (songid_t)device->Get(i++);
+ songsNotInML->Add((void*)song);
+ }
+
+ if(itemRecordsNotOnDevice) while(j<lj) {
+ itemRecordW * it = (itemRecordW *)ml->Get(j++);
+ itemRecordsNotOnDevice->Add(it);
+ }
+
+ delete ml; delete device;
+}
+
+void MapItemRecordsToSongs(Device * dev, PlaylistAddItem ** map, int len, C_ItemList * itemRecordsNotOnDevice) {
+ C_ItemList device;
+ int l = dev->getPlaylistLength(0);
+ int i;
+ for(i=0; i<l; i++) device.Add((void*)dev->getPlaylistTrack(0,i));
+
+ qsort(map,len,sizeof(void*),sortfunc_ItemRecords_map);
+ nu::qsort(device.GetAll(),device.GetSize(),sizeof(void*),dev,compareSongs);
+
+ int j=0;
+ i=0;
+ int li = device.GetSize();
+ int lj = len;
+ while(i<li && j<lj) {
+ PlaylistAddItem* p = map[j];
+ songid_t s = (songid_t)device.Get(i);
+ int cmp = compareItemRecordAndSongId(p->item,s, dev);
+ if(cmp == 0) {
+ p->songid = s;
+ j++;
+ }
+ else if(cmp > 0) { j++; if(itemRecordsNotOnDevice) itemRecordsNotOnDevice->Add(p->item); }
+ else i++;
+ }
+}
+
+void ProcessDatabaseDifferences(Device * dev, itemRecordListW * ml,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML) {
+ if(!ml) return;
+ C_ItemList ml_list;
+ for(int i=0; i < ml->Size; i++) ml_list.Add(&ml->Items[i]);
+ ProcessDatabaseDifferences(dev,&ml_list,itemRecordsOnDevice,itemRecordsNotOnDevice,songsInML,songsNotInML);
+}
+
+typedef struct { songid_t song; Device * dev; const wchar_t * filename; } tagItem;
+
+static wchar_t * tagFunc(const wchar_t * tag, void * p) { //return 0 if not found, -1 for empty tag
+ tagItem * s = (tagItem *)p;
+ int len = 2048;
+ wchar_t * buf = (wchar_t *)calloc(len, sizeof(wchar_t));
+ if (buf)
+ {
+ if (!_wcsicmp(tag, L"artist")) s->dev->getTrackArtist(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"album")) s->dev->getTrackAlbum(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"title")) s->dev->getTrackTitle(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"genre")) s->dev->getTrackGenre(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"year")) wsprintf(buf,L"%d",s->dev->getTrackYear(s->song));
+ else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) wsprintf(buf,L"%d",s->dev->getTrackTrackNum(s->song));
+ else if (!_wcsicmp(tag, L"discnumber")) wsprintf(buf,L"%d",s->dev->getTrackDiscNum(s->song));
+ else if (!_wcsicmp(tag, L"bitrate")) wsprintf(buf,L"%d",s->dev->getTrackBitrate(s->song));
+ else if (!_wcsicmp(tag, L"filename")) lstrcpyn(buf,s->filename,len);
+ else if (!_wcsicmp(tag, L"albumartist")) s->dev->getTrackAlbumArtist(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"composer")) s->dev->getTrackComposer(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"publisher")) s->dev->getTrackPublisher(s->song,buf,len);
+ else if (!_wcsicmp(tag, L"mime")) s->dev->getTrackMimeType(s->song,buf,len);
+ }
+ return buf;
+}
+
+static void tagFreeFunc(wchar_t *tag, void *p) { if(tag) free(tag); }
+
+static time_t FileTimeToUnixTime(FILETIME *ft)
+{
+ ULARGE_INTEGER end;
+ memcpy(&end,ft,sizeof(end));
+ end.QuadPart -= 116444736000000000;
+ end.QuadPart /= 10000000; // 100ns -> seconds
+ return (time_t)end.QuadPart;
+}
+
+static __int64 FileSize64(HANDLE file)
+{
+ LARGE_INTEGER position;
+ position.QuadPart=0;
+ position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
+
+ if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
+ return INVALID_FILE_SIZE;
+ else
+ return position.QuadPart;
+}
+
+void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time)
+{
+ WIN32_FILE_ATTRIBUTE_DATA file_data;
+ if (GetFileAttributesExW(filename, GetFileExInfoStandard, &file_data) == FALSE)
+ {
+ // GetFileAttributesEx failed. that sucks, let's try something else
+ HANDLE hFile=CreateFileW(filename,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ FILETIME lt;
+ if (GetFileTime(hFile,NULL,NULL,&lt))
+ {
+ *file_time=FileTimeToUnixTime(&lt);
+ }
+ *file_size=FileSize64(hFile);
+ CloseHandle(hFile);
+ }
+ }
+ else
+ {
+ // success
+ *file_time = FileTimeToUnixTime(&file_data.ftLastWriteTime);
+ LARGE_INTEGER size64;
+ size64.LowPart = file_data.nFileSizeLow;
+ size64.HighPart = file_data.nFileSizeHigh;
+ *file_size = size64.QuadPart;
+ }
+}
+
+void getTitle(Device * dev, songid_t song, const wchar_t * filename,wchar_t * buf, int len) {
+ buf[0]=0; buf[len-1]=0;
+ tagItem item = {song,dev,filename};
+ waFormatTitleExtended fmt={filename,0,NULL,&item,buf,len,tagFunc,tagFreeFunc};
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
+}
+
+#define atoi_NULLOK(s) ((s)?_wtoi(s):0)
+
+void filenameToItemRecord(wchar_t * file, itemRecordW * ice)
+{
+ int gtrack=0;
+ wchar_t *gartist=NULL,*galbum=NULL,*gtitle=NULL;
+ wchar_t *guessbuf = guessTitles(file,&gtrack,&gartist,&galbum,&gtitle);
+ if(!gartist) gartist=L"";
+ if(!galbum) galbum=L"";
+ if(!gtitle) gtitle=L"";
+
+ wchar_t buf[512]=L"";
+ extendedFileInfoStructW efs={file,NULL,buf,512};
+
+ efs.metadata=L"title"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) { ice->title=_wcsdup(buf); gartist=L""; galbum=L""; gtrack=-1;}
+ else ice->title=_wcsdup(gtitle);
+
+ efs.metadata=L"album";
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) ice->album=_wcsdup(buf);
+ else ice->album=_wcsdup(galbum);
+
+ efs.metadata=L"artist"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) ice->artist=_wcsdup(buf);
+ else ice->artist=_wcsdup(gartist);
+
+ efs.metadata=L"comment"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->comment=_wcsdup(buf);
+
+ efs.metadata=L"genre"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->genre=_wcsdup(buf);
+
+ efs.metadata=L"albumartist"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->albumartist=_wcsdup(buf);
+
+ efs.metadata=L"replaygain_album_gain"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->replaygain_album_gain=_wcsdup(buf);
+
+ efs.metadata=L"replaygain_track_gain"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->replaygain_track_gain=_wcsdup(buf);
+
+ efs.metadata=L"publisher"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->publisher=_wcsdup(buf);
+
+ efs.metadata=L"composer"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->composer=_wcsdup(buf);
+
+ efs.metadata=L"year"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->year=atoi_NULLOK(buf);
+
+ efs.metadata=L"track"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) ice->track=atoi_NULLOK(buf);
+ else ice->track=gtrack;
+
+ efs.metadata=L"tracks"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->tracks=atoi_NULLOK(buf);
+
+ efs.metadata=L"rating"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->rating=atoi_NULLOK(buf);
+
+ __int64 file_size=INVALID_FILE_SIZE;
+ time_t file_time=0;
+ GetFileSizeAndTime(file, &file_size, &file_time);
+ if (!(file_size == INVALID_FILE_SIZE || file_size == 0))
+ {
+ ice->filetime=file_time;
+ // scales to the kb value this uses
+ ice->filesize=(int)(file_size/1024);
+ // and since 5.64+ we can also return this as a true value
+ StringCchPrintf(buf, sizeof(buf), L"%d", file_size);
+ setRecordExtendedItem(ice,L"realsize",buf);
+ }
+
+ ice->lastupd=time(NULL);
+
+ efs.metadata=L"bitrate"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->bitrate=atoi_NULLOK(buf);
+
+ efs.metadata=L"type"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->type=atoi_NULLOK(buf);
+
+ efs.metadata=L"disc"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->disc=atoi_NULLOK(buf);
+
+ efs.metadata=L"discs"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->discs=atoi_NULLOK(buf);
+
+ efs.metadata=L"bpm"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ ice->bpm=atoi_NULLOK(buf);
+
+ basicFileInfoStructW b={efs.filename,0};
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
+ ice->length=b.length;
+
+ // additional fields to match (if available) with a full library
+ // response this is mainly for improving the cloud compatibility
+ efs.metadata=L"lossless"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"lossless",buf);
+
+ efs.metadata=L"director"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"director",buf);
+
+ efs.metadata=L"producer"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"producer",buf);
+
+ efs.metadata=L"width"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"width",buf);
+
+ efs.metadata=L"height"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"height",buf);
+
+ efs.metadata=L"mime"; buf[0]=0;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&efs,IPC_GET_EXTENDED_FILE_INFOW);
+ if(buf[0]) setRecordExtendedItem(ice,L"mime",buf);
+
+ // Not filled in are: playcount, lastplay
+ ice->filename = _wcsdup(file);
+ free(guessbuf);
+}
+
+void copyTags(itemRecordW * in, wchar_t * out) {
+ // check if the old file still exists - if it does, we will let Winamp copy metadata for us
+ if (wcscmp(in->filename, out) && PathFileExists(in->filename))
+ {
+ copyFileInfoStructW copy;
+ copy.dest = out;
+ copy.source = in->filename;
+ if(SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&copy, IPC_COPY_EXTENDED_FILE_INFOW) == 0) // 0 means success
+ return;
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/metadata_utils.h b/Src/Plugins/Library/ml_pmp/metadata_utils.h
new file mode 100644
index 00000000..7b6ddfc7
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/metadata_utils.h
@@ -0,0 +1,48 @@
+#ifndef __METADATA_UTILS_H_
+#define __METADATA_UTILS_H_
+
+#include "pmp.h"
+
+typedef struct {
+ wchar_t * fn;
+ itemRecordW * ice;
+} filenameMap;
+
+typedef struct {
+ itemRecordW * item;
+ songid_t songid;
+} PlaylistAddItem;
+
+typedef union {
+ struct {
+ wchar_t * filename;
+ itemRecordW * ice;
+ songid_t song;
+ };
+ struct {
+ filenameMap map;
+ songid_t song;
+ };
+ struct {
+ wchar_t * filename;
+ PlaylistAddItem pladd;
+ };
+} songMapping;
+
+void MapItemRecordsToSongs(Device * dev, PlaylistAddItem ** map, int len, C_ItemList * itemRecordsNotOnDevice=NULL);
+void mapFilesToItemRecords(filenameMap ** map0, int len, HWND centerWindow);
+
+void ProcessDatabaseDifferences(Device * dev, itemRecordListW * ml,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML);
+void ProcessDatabaseDifferences(Device * dev, C_ItemList * ml,C_ItemList * itemRecordsOnDevice, C_ItemList * itemRecordsNotOnDevice, C_ItemList * songsInML, C_ItemList * songsNotInML);
+void getTitle(Device * dev, songid_t song, const wchar_t * filename,wchar_t * buf, int len);
+C_ItemList * fileListToItemRecords(wchar_t** files,int l, HWND centerWindow);
+C_ItemList * fileListToItemRecords(C_ItemList * fileList, HWND centerWindow);
+void filenameToItemRecord(wchar_t * file, itemRecordW * ice);
+
+int __fastcall compareSongs(const void *elem1, const void *elem2, const void *context);
+int compareItemRecords(itemRecordW * a, itemRecordW * b);
+int compareItemRecordAndSongId(itemRecordW * item, songid_t song, Device *dev);
+
+void GetFileSizeAndTime(const wchar_t *filename, __int64 *file_size, time_t *file_time);
+
+#endif // __METADATA_UTILS_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ml_pmp.rc b/Src/Plugins/Library/ml_pmp/ml_pmp.rc
new file mode 100644
index 00000000..f249a586
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.rc
@@ -0,0 +1,1228 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource1.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
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_CONFIG DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_TAB1,"SysTabControl32",WS_TABSTOP,0,0,271,246
+END
+
+IDD_CONFIG_MEDIAVIEW DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Media Display Settings",IDC_STATIC,4,3,256,74
+ CONTROL "Remember search filters",IDC_REMEMBER_SEARCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,16,93,10
+ CONTROL "Show Video files in a separate view",IDC_CHECK_VIDEOVIEW,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,30,128,10
+ CONTROL "Simple",IDC_RADIO_FILTERS1,"Button",BS_AUTORADIOBUTTON,10,44,36,10
+ CONTROL "Two Filters",IDC_RADIO_FILTERS2,"Button",BS_AUTORADIOBUTTON,50,44,50,10
+ CONTROL "Three Filters",IDC_RADIO_FILTERS3,"Button",BS_AUTORADIOBUTTON,104,44,55,10
+ LTEXT "Filters",IDC_STATIC,11,59,20,8
+ COMBOBOX IDC_COMBO_FILTER1,35,57,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC,93,59,8,8
+ COMBOBOX IDC_COMBO_FILTER2,98,57,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER3,156,59,8,8
+ COMBOBOX IDC_COMBO_FILTER3,161,57,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+END
+
+IDD_CONFIG_GLOBAL DIALOGEX 0, 0, 272, 247
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Copy to Local Media Filenames",IDC_STATIC,0,0,272,132
+ LTEXT "This determines the path and filename when using the Copy To Local Media feature for copying songs from devices back to your computer's hard drive.",IDC_STATIC,6,12,259,16
+ CONTROL "Use CD Rip Settings",IDC_CHECK_USECDRIP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,34,192,10
+ LTEXT "Specify the destination folder for copied tracks:",IDC_STATIC_1,6,47,204,8
+ EDITTEXT IDC_DESTPATH,6,58,214,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_BUTTON1,221,58,45,13
+ LTEXT "Specify the naming convention for copied tracks:",IDC_STATIC_2,6,77,203,8
+ EDITTEXT IDC_FILENAMEFMT,6,88,214,13,ES_AUTOHSCROLL
+ PUSHBUTTON "Format Help",IDC_BUTTON2,221,88,45,13
+ LTEXT "",IDC_FMTOUT,8,106,258,20
+END
+
+IDD_VIEW_PMP_VIDEO DIALOGEX 0, 0, 291, 272
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Search:",IDC_SEARCH_TEXT,0,0,26,8
+ EDITTEXT IDC_QUICKSEARCH,29,0,208,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,239,0,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,12,289,247
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+ CONTROL "Sync",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,77,261,34,11
+ CONTROL "AutoFill",IDC_BUTTON_AUTOFILL,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,114,261,40,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,158,262,98,9
+ CONTROL "Eject",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,260,261,30,11
+END
+
+IDD_CUSTCOLUMNS DIALOGEX 0, 0, 342, 154
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Customize columns"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+ GROUPBOX "Hidden columns",IDC_STATIC,7,7,126,120
+ LISTBOX IDC_LIST2,13,17,114,104,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Add -->",IDC_BUTTON2,138,41,66,14,WS_DISABLED
+ PUSHBUTTON "<-- Remove",IDC_BUTTON3,138,59,66,14,WS_DISABLED
+ PUSHBUTTON "Restore Defaults",IDC_DEFS,138,107,66,14
+ GROUPBOX "Visible columns",IDC_STATIC,208,7,126,140
+ LISTBOX IDC_LIST1,214,17,114,110,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ DEFPUSHBUTTON "OK",IDOK,7,133,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,62,133,50,14
+ PUSHBUTTON "Move up",IDC_BUTTON4,239,130,43,12,WS_DISABLED
+ PUSHBUTTON "Move down",IDC_BUTTON5,285,130,43,12,WS_DISABLED
+END
+
+IDD_VIEW_PMP_PLAYLIST DIALOGEX 0, 0, 291, 178
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,0,289,155
+ LTEXT "",IDC_STATUS,115,168,142,9,SS_CENTERIMAGE | SS_ENDELLIPSIS
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,167,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,167,44,11
+ CONTROL "Sort",IDC_BUTTON_SORT,"Button",BS_OWNERDRAW | WS_TABSTOP,77,167,34,11
+ CONTROL "Eject",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW,261,167,30,11
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_VIEW_CLOUD_QUEUE$(DISABLED) DIALOGEX 0, 0, 294, 204
+#else
+IDD_VIEW_CLOUD_QUEUE DIALOGEX 0, 0, 294, 204
+#endif
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_TRANSFERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,0,292,191,WS_EX_CLIENTEDGE
+ PUSHBUTTON "Cancel Transfers",IDC_BUTTONCANCELSELECTED,0,193,70,11
+ PUSHBUTTON "Clear Finished",IDC_BUTTON_CLEARFINISHED,73,193,60,11
+ PUSHBUTTON "Remove Selected",IDC_BUTTON_REMOVESELECTED,136,193,72,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_ENDELLIPSIS,247,195,47,9
+ PUSHBUTTON "Retry Selected",IDC_BUTTON_RETRYSELECTED,210,193,34,11
+END
+#endif
+
+IDD_VIEW_PMP_ARTISTALBUM DIALOGEX 0, 0, 291, 272
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Search:",IDC_SEARCH_TEXT,64,1,26,8
+ EDITTEXT IDC_QUICKSEARCH,92,2,145,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,240,1,49,11
+ CONTROL "",IDC_LIST_ARTIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,13,109,83
+ CONTROL "",IDC_VDELIM,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,109,14,6,82
+ CONTROL "",IDC_LIST_ALBUM,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,115,13,95,83
+ CONTROL "",IDC_VDELIM2,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,210,14,6,82
+ CONTROL "",IDC_LIST_ALBUM2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,216,13,75,83
+ CONTROL "",IDC_HDELIM,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,96,291,6
+ LTEXT "Refine:",IDC_REFINE_TEXT,1,104,24,8
+ EDITTEXT IDC_REFINE,29,104,208,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Refine",IDC_BUTTON_CLEARREFINE,"Button",BS_OWNERDRAW | WS_TABSTOP,240,104,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,115,291,144
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,161,262,96,9
+ CONTROL "Sync",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | WS_TABSTOP,79,261,34,11
+ CONTROL "AutoFill",IDC_BUTTON_AUTOFILL,"Button",BS_OWNERDRAW | WS_TABSTOP,117,261,40,11
+ CONTROL "Eject",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,261,261,30,11
+ CONTROL "",IDC_BUTTON_ARTMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,0,1,14,11
+ CONTROL "",IDC_BUTTON_VIEWMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,16,1,20,11
+ CONTROL "",IDC_BUTTON_COLUMNS,"Button",BS_OWNERDRAW | WS_TABSTOP,37,1,20,11
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+END
+
+IDD_VIEW_PMP_DEVICES DIALOGEX 0, 0, 294, 204
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Portable Media Players Currently Connected:",IDC_STATIC,2,1,145,8
+ CONTROL "",IDC_LIST_DEVICES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,11,292,78
+ CONTROL "",IDC_HDELIM,"Static",SS_ETCHEDHORZ | SS_NOTIFY | NOT WS_VISIBLE,0,91,292,1
+ LTEXT "Transfer Queue:",IDC_TQ_STATIC,2,95,54,8
+ CONTROL "",IDC_LIST_TRANSFERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,106,292,85
+ CONTROL "Pause",IDC_BUTTON_PAUSETRANSFERS,"Button",BS_OWNERDRAW | WS_TABSTOP,0,193,35,11
+ CONTROL "Clear Finished",IDC_BUTTON_CLEARFINISHED,"Button",BS_OWNERDRAW | WS_TABSTOP,38,193,54,11
+ CONTROL "Remove Selected",IDC_BUTTON_REMOVESELECTED,"Button",BS_OWNERDRAW | WS_TABSTOP,95,193,64,11
+ LTEXT "",IDC_STATUS,162,195,130,9
+END
+
+IDD_EDIT_INFO DIALOGEX 0, 0, 295, 215
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Edit item info"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Artist",IDC_CHECK_ARTIST,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,8,32,10
+ EDITTEXT IDC_EDIT_ARTIST,52,7,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Title",IDC_CHECK_TITLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,26,29,10
+ EDITTEXT IDC_EDIT_TITLE,52,25,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Album",IDC_CHECK_ALBUM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,44,35,10
+ EDITTEXT IDC_EDIT_ALBUM,52,43,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Track #",IDC_CHECK_TRACK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,62,41,10
+ EDITTEXT IDC_EDIT_TRACK,52,61,54,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Disc #",IDC_CHECK_DISC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,119,62,36,10
+ EDITTEXT IDC_EDIT_DISC,163,61,54,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Genre",IDC_CHECK_GENRE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,80,35,10
+ EDITTEXT IDC_EDIT_GENRE,52,79,236,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Year",IDC_CHECK_YEAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,98,31,10
+ EDITTEXT IDC_EDIT_YEAR,52,97,54,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Album Artist",IDC_CHECK_ALBUMARTIST,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,117,54,10
+ EDITTEXT IDC_EDIT_ALBUMARTIST,65,116,142,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Publisher",IDC_CHECK_PUBLISHER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,135,54,10
+ EDITTEXT IDC_EDIT_PUBLISHER,65,133,142,13,ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Composer",IDC_CHECK_COMPOSER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,152,54,10
+ EDITTEXT IDC_EDIT_COMPOSER,65,151,142,13,ES_AUTOHSCROLL | WS_DISABLED
+ DEFPUSHBUTTON "Update",IDOK,184,194,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,238,194,50,14
+ GROUPBOX "A",IDC_STATIC,213,99,75,92
+ CONTROL "",IDC_PICTUREHOLDER,"Static",SS_BITMAP | SS_REALSIZEIMAGE | WS_DISABLED,221,109,60,55
+ PUSHBUTTON "Change...",IDC_ART_CHANGE,217,174,40,12,WS_DISABLED
+ PUSHBUTTON "Clear",IDC_ART_CLEAR,260,174,26,12,WS_DISABLED
+ CTEXT "No image",IDC_ARTINFO,217,165,67,8,WS_DISABLED
+ CONTROL "Album Art",IDC_CHECK_ALBUMART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,219,98,45,10
+END
+
+IDD_PROGRESS DIALOGEX 0, 0, 186, 44
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Dialog"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,7,172,14
+ PUSHBUTTON "Abort",IDC_ABORT,68,27,50,13
+END
+
+IDD_GETTINGMETADATA DIALOGEX 0, 0, 186, 26
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION
+CAPTION "Finding Metadata..."
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_METADATAPROGRESS,"msctls_progress32",WS_BORDER,7,7,172,12
+END
+
+IDD_FIND DIALOGEX 0, 0, 185, 39
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Find in Playlist"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ EDITTEXT IDC_EDIT,5,5,175,12,ES_AUTOHSCROLL
+ DEFPUSHBUTTON "Find",IDOK,76,22,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,130,22,50,13
+END
+
+IDD_SYNC DIALOGEX 0, 0, 361, 217
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Sync"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Leave them",IDC_TRUESYNC_LEAVE,"Button",BS_AUTORADIOBUTTON,7,26,53,10
+ CONTROL "Delete them",IDC_TRUESYNC_DELETE,"Button",BS_AUTORADIOBUTTON,61,26,53,10
+ CONTROL "Copy them to the Library",IDC_TRUESYNC_COPY,"Button",BS_AUTORADIOBUTTON,116,26,136,10
+ CONTROL "Automatically Sync upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,38,131,10
+ PUSHBUTTON "More >>",IDC_MORE,7,51,50,14
+ DEFPUSHBUTTON "OK",IDOK,153,51,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,209,51,50,14
+ LISTBOX IDC_LIST_ADD,11,66,163,105,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Remove Selected",IDC_ADD_REMSEL,11,175,61,14
+ PUSHBUTTON "Crop to Selected",IDC_ADD_CROPSEL,77,175,61,14
+ LISTBOX IDC_LIST_REMOVE,186,66,163,105,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Remove Selected",IDC_REM_REMSEL,186,175,61,14
+ PUSHBUTTON "Crop to Selected",IDC_REM_CROPSEL,252,175,61,14
+ PUSHBUTTON "Less <<",IDC_LESS,7,196,50,14
+ DEFPUSHBUTTON "OK",IDOK2,248,196,50,14
+ PUSHBUTTON "Cancel",IDCANCEL2,304,196,50,14
+ LTEXT "%d songs will be transferred to the device. \nThere are %d songs on the device which are not in the Local Media Library. ",1020,7,7,250,18
+ GROUPBOX "Songs to be transferred",IDC_ADDLABEL,7,55,172,138,0,WS_EX_TRANSPARENT
+ GROUPBOX "Songs to be deleted",IDC_REMOVELABEL,182,55,172,138,0,WS_EX_TRANSPARENT
+END
+
+IDD_CONFIG_TRANSCODE DIALOGEX 0, 0, 260, 227
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Transcoding Formats",IDC_STATIC,4,3,255,49
+ CONTROL "Enable Transcoding of incompatible tracks",IDC_ENABLETRANSCODER,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,16,149,10
+ PUSHBUTTON "Advanced Settings",IDC_ADVANCED,178,14,75,13
+ LTEXT "Format:",IDC_STATIC,11,35,25,8
+ COMBOBOX IDC_ENCFORMAT,40,33,213,116,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_ENC_CONFIG,"Static",SS_BLACKRECT | NOT WS_VISIBLE,4,56,255,167
+END
+
+IDD_CONFIG_PLUGINS DIALOGEX 0, 0, 272, 246
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Portable Device plug-ins",IDC_STATIC,0,0,272,246
+ LTEXT "Installed Portable Device plug-ins (loaded at startup):",IDC_STATIC,6,12,253,8
+ LISTBOX IDC_PLUGINSLIST,5,26,259,198,LBS_SORT | LBS_USETABSTOPS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "&Configure selected plug-in",IDC_CONFIGPLUGIN,5,228,96,13,WS_DISABLED
+ PUSHBUTTON "Uninstall selected plug-in",IDC_UNINSTALLPLUGIN,105,228,90,13,WS_DISABLED
+ CONTROL "Get plug-ins",IDC_PLUGINVERS,"Button",BS_OWNERDRAW | WS_TABSTOP,224,230,41,9
+END
+
+IDD_CONFIG_TRANSCODING_ADVANCED DIALOGEX 0, 0, 263, 56
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Advanced Transcoding Settings"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Force transcoding of compatible tracks if bitrate is over",IDC_CHECK_FORCE,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,6,190,10
+ EDITTEXT IDC_FORCE_BITRATE,196,4,40,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "kbps",IDC_STATIC,240,6,16,10,SS_CENTERIMAGE
+ CONTROL "Force transcoding of compatible lossless tracks",IDC_CHECK_FORCE_LOSSLESS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,21,190,10
+ DEFPUSHBUTTON "OK",IDOK,155,39,50,13
+ PUSHBUTTON "Cancel",IDCANCEL,209,39,50,13
+END
+
+IDD_AUTOFILL DIALOGEX 0, 0, 361, 199
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "AutoFill"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "This will add %d tracks and delete %d tracks.\nContinue?",1020,7,7,154,18
+ CONTROL "AutoFill my device immediately upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,24,168,10
+ PUSHBUTTON "More >>",IDC_MORE,7,38,50,14
+ DEFPUSHBUTTON "OK",IDOK,63,38,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,119,38,50,14
+ GROUPBOX "Songs to be deleted",IDC_REMOVELABEL,182,38,172,138,0,WS_EX_TRANSPARENT
+ GROUPBOX "Songs to be transferred",IDC_ADDLABEL,7,38,172,138,0,WS_EX_TRANSPARENT
+ LISTBOX IDC_LIST_ADD,11,49,163,122,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ LISTBOX IDC_LIST_REMOVE,186,49,163,122,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Less <<",IDC_LESS,7,179,50,14
+ DEFPUSHBUTTON "OK",IDOK2,248,179,50,14
+ PUSHBUTTON "Cancel",IDCANCEL2,304,179,50,14
+END
+
+IDD_CONFIG_SYNC DIALOGEX 0, 0, 260, 228
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Here you can configure what is transferred when you hit the ""Sync"" button. Podcasts will be synchronised based on the options on the 'Podcast Sync' tab.",IDC_STATIC,4,3,251,17
+ GROUPBOX "Playlist Sync",IDC_STATIC,4,24,254,100
+ CONTROL "Update selected playlists only",IDC_PL_WHITELIST,"Button",BS_AUTORADIOBUTTON,11,36,111,8
+ CONTROL "Update all playlists except those selected",IDC_PL_BLACKLIST,
+ "Button",BS_AUTORADIOBUTTON,11,47,148,8
+ CONTROL "",IDC_PL_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,11,60,240,58
+ GROUPBOX "Library Sync",IDC_STATIC,4,128,254,54
+ CONTROL "Update all songs from my Local Media Library",IDC_LIBRARYSYNC,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,139,157,10
+ LTEXT "Use the following query to specify media types which should be included:",IDC_STATIC,11,152,238,8
+ EDITTEXT IDC_SYNC_QUERY_STRING,11,164,187,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit Query...",IDC_SYNC_QUERY_EDIT,199,164,53,12
+ GROUPBOX "Auto-Sync",IDC_STATIC,4,186,254,40
+ CONTROL "Automatically Sync my device upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,198,165,10
+ CTEXT "if I haven't done a Sync in",IDC_STATIC,23,210,85,10,SS_CENTERIMAGE
+ EDITTEXT IDC_SYNCONCONNECT_TIME,111,209,27,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "hours",IDC_STATIC,143,210,19,10,SS_CENTERIMAGE
+END
+
+IDD_CONFIG_AUTOFILL DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Here you can configure what is transferred when you hit the ""AutoFill"" button.",IDC_STATIC,4,3,253,8
+ GROUPBOX "AutoFill Settings",IDC_STATIC,4,14,256,85
+ CONTROL "Make higher rated songs more likely to be included in the AutoFill",IDC_BOOSTRATINGS,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,27,239,10
+ LTEXT "Aim to AutoFill the device 90% full",IDC_FILLCAPTION,11,41,112,8
+ CONTROL "",IDC_SPACESLIDER,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,125,38,100,12
+ LTEXT "Use this query to fine-tune what songs should be included in the AutoFill",IDC_STATIC,11,56,242,8
+ EDITTEXT IDC_AUTOFILL_QUERY_STRING,11,68,195,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Edit Query",IDC_AUTOFILL_QUERY_EDIT,207,68,44,12
+ CONTROL "AutoFill full albums only",IDC_AUTOFILLALBUMS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,84,100,10
+ GROUPBOX "Super AutoFill",IDC_STATIC,4,103,256,41
+ CONTROL "AutoFill my device immediately upon connection",IDC_SYNCONCONNECT,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,115,167,10
+ CTEXT "if I haven't done an AutoFill in",IDC_STATIC,22,128,98,10,SS_CENTERIMAGE
+ EDITTEXT IDC_SYNCONCONNECT_TIME,123,126,27,12,ES_AUTOHSCROLL | ES_NUMBER
+ LTEXT "hours",IDC_STATIC,155,126,19,12,SS_CENTERIMAGE
+ LTEXT "AutoFill will select enough songs to fit on your device based on rating and when they were last played. It is perfect for devices with low capactiy (eg, 2GB or less). Unlike Sync, it will select a different set of songs each time.",IDC_STATIC,4,148,250,25
+END
+
+IDD_CONFIG_PODCAST_SYNC DIALOGEX 0, 0, 264, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Here you can configure what podcasts will be transferred when using the ""Sync"" button. See the 'Sync' tab for other options when synchronising device media.",-1,4,3,258,17
+ GROUPBOX "Podcast Sync Settings",IDC_STATIC_PODCASTS,4,24,258,157
+ CONTROL "Sync",IDC_CHECK_PC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,37,28,10,WS_EX_TRANSPARENT
+ COMBOBOX IDC_COMBO_PC_NUM,43,36,32,107,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "most recent episodes of:",IDC_STATIC_PODTXT,79,38,80,8
+ CONTROL "All podcasts",IDC_PC_ALL,"Button",BS_AUTORADIOBUTTON | WS_GROUP,11,52,54,8
+ CONTROL "Selected podcasts:",IDC_PC_SEL,"Button",BS_AUTORADIOBUTTON,11,64,77,8
+ LTEXT "Tip: For best results, set your podcasts to update regularly and download automatically.",IDC_STATIC_PODTIP,11,76,84,42,0,WS_EX_TRANSPARENT
+ CONTROL "",IDC_PC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,99,62,156,112
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_VIEW_CLOUD_ARTISTALBUM$(DISABLED) DIALOGEX 0, 0, 291, 272
+#else
+IDD_VIEW_CLOUD_ARTISTALBUM DIALOGEX 0, 0, 291, 272
+#endif
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_HEADER_DEVICE_ICON,"Static",SS_BITMAP | SS_CENTERIMAGE | NOT WS_VISIBLE,0,0,20,20
+ LTEXT "",IDC_HEADER_DEVICE_NAME,0,0,70,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HEADER_DEVICE_BAR,"msctls_progress32",NOT WS_VISIBLE,75,0,110,8
+ LTEXT "",IDC_HEADER_DEVICE_SIZE,203,0,60,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HDELIM2,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,10,291,6
+ CONTROL "",IDC_HEADER_DEVICE_TRANSFER,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,269,0,22,11
+ CONTROL "",IDC_BUTTON_ARTMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,0,12,14,11
+ CONTROL "",IDC_BUTTON_VIEWMODE,"Button",BS_OWNERDRAW | WS_TABSTOP,16,12,20,11
+ CONTROL "",IDC_BUTTON_COLUMNS,"Button",BS_OWNERDRAW | WS_TABSTOP,37,12,20,11
+ LTEXT "Search:",IDC_SEARCH_TEXT,64,12,26,8,SS_NOTIFY
+ EDITTEXT IDC_QUICKSEARCH,92,13,145,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,240,12,49,11
+ CONTROL "",IDC_LIST_ARTIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,24,109,71
+ CONTROL "",IDC_VDELIM,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,109,25,6,71
+ CONTROL "",IDC_LIST_ALBUM,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,115,24,95,71
+ CONTROL "",IDC_VDELIM2,"Static",SS_ETCHEDFRAME | SS_NOTIFY | NOT WS_VISIBLE,210,25,6,71
+ CONTROL "",IDC_LIST_ALBUM2,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,216,25,75,71
+ CONTROL "",IDC_HDELIM,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,96,291,6
+ LTEXT "Refine:",IDC_REFINE_TEXT,1,104,24,8
+ EDITTEXT IDC_REFINE,29,104,208,12,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Refine",IDC_BUTTON_CLEARREFINE,"Button",BS_OWNERDRAW | WS_TABSTOP,240,104,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,115,291,146
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+ CONTROL "Transfer",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | WS_TABSTOP,79,261,42,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,125,262,122,9
+ CONTROL "Remove",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,251,261,40,11
+END
+#endif
+
+IDD_CLOUD_SYNC DIALOGEX 0, 0, 379, 215
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Cloud Transfer"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Transfer songs to the device by:",IDC_STATIC,5,7,106,8
+ COMBOBOX IDC_TX_MODE,115,5,72,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "Songs to be transferred",IDC_ADDLABEL,5,19,182,191,0,WS_EX_TRANSPARENT
+ CONTROL "Transfer all playlists",IDC_CLOUDSYNC_ALL,"Button",BS_AUTORADIOBUTTON | WS_GROUP,11,32,79,10
+ CONTROL "Transfer selected playlists only",IDC_CLOUDSYNC_SEL,
+ "Button",BS_AUTORADIOBUTTON,11,44,115,10
+ CONTROL "Transfer all playlists except those selected",IDC_CLOUDSYNC_NOTSEL,
+ "Button",BS_AUTORADIOBUTTON,11,56,152,10
+ LISTBOX IDC_LIST_ADD_PL,11,70,170,134,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_HSCROLL | WS_GROUP | WS_TABSTOP
+ LISTBOX IDC_LIST_ADD,11,70,170,134,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_HSCROLL | WS_GROUP | WS_TABSTOP
+ GROUPBOX "Transfer Summary",IDC_DETAILSLABEL,193,5,182,172,0,WS_EX_TRANSPARENT
+ LTEXT "",IDC_DETAILS,199,16,170,154
+ DEFPUSHBUTTON "Begin Transfer",IDOK,253,196,68,14
+ PUSHBUTTON "Cancel",IDCANCEL,325,196,50,14
+END
+
+IDD_CONFIG_CLOUD_MEDIAVIEW DIALOGEX 0, 0, 260, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Cloud Source Display Settings",IDC_STATIC,4,3,256,62
+ CONTROL "Remember search filters",IDC_REMEMBER_SEARCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,16,93,10
+ CONTROL "Simple",IDC_RADIO_FILTERS1,"Button",BS_AUTORADIOBUTTON,10,32,36,10
+ CONTROL "Two Filters",IDC_RADIO_FILTERS2,"Button",BS_AUTORADIOBUTTON,50,32,50,10
+ CONTROL "Three Filters",IDC_RADIO_FILTERS3,"Button",BS_AUTORADIOBUTTON,104,32,55,10
+ LTEXT "Filters",IDC_STATIC,11,47,20,8
+ COMBOBOX IDC_COMBO_FILTER1,35,45,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER2,93,47,8,8
+ COMBOBOX IDC_COMBO_FILTER2,98,45,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "/",IDC_STATIC_FILTER3,156,47,8,8
+ COMBOBOX IDC_COMBO_FILTER3,161,45,56,87,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+END
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDD_VIEW_CLOUD_SIMPLE$(DISABLED) DIALOGEX 0, 0, 291, 272
+#else
+IDD_VIEW_CLOUD_SIMPLE DIALOGEX 0, 0, 291, 272
+#endif
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_HEADER_DEVICE_ICON,"Static",SS_BITMAP | SS_CENTERIMAGE | NOT WS_VISIBLE,0,0,20,20
+ LTEXT "",IDC_HEADER_DEVICE_NAME,34,0,70,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HEADER_DEVICE_BAR,"msctls_progress32",NOT WS_VISIBLE,112,0,110,8
+ LTEXT "",IDC_HEADER_DEVICE_SIZE,203,0,60,9,SS_ENDELLIPSIS | NOT WS_VISIBLE
+ CONTROL "",IDC_HEADER_DEVICE_TRANSFER,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,269,0,22,11
+ CONTROL "",IDC_HDELIM2,"Static",SS_BLACKFRAME | SS_NOTIFY | NOT WS_VISIBLE,0,9,291,6
+ LTEXT "Search:",IDC_SEARCH_TEXT,0,13,26,8,SS_NOTIFY
+ EDITTEXT IDC_QUICKSEARCH,29,13,208,10,ES_AUTOHSCROLL | NOT WS_BORDER
+ CONTROL "Clear Search",IDC_BUTTON_CLEARSEARCH,"Button",BS_OWNERDRAW | WS_TABSTOP,239,13,49,11
+ CONTROL "",IDC_LIST_TRACKS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,0,28,289,231
+ CONTROL "Play",IDC_BUTTON_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,261,28,11
+ CONTROL "Enqueue",IDC_BUTTON_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,31,261,44,11
+ CONTROL "Transfer",IDC_BUTTON_SYNC,"Button",BS_OWNERDRAW | NOT WS_VISIBLE | WS_TABSTOP,77,261,42,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS,123,262,124,9
+ CONTROL "Remove",IDC_BUTTON_EJECT,"Button",BS_OWNERDRAW | WS_TABSTOP,251,261,40,11
+END
+#endif
+
+IDD_VIEW_PMP_QUEUE DIALOGEX 0, 0, 294, 204
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST_TRANSFERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_TABSTOP,0,0,292,191,WS_EX_CLIENTEDGE
+ PUSHBUTTON "Pause",IDC_BUTTON_PAUSETRANSFERS,0,193,38,11
+ PUSHBUTTON "Clear Finished",IDC_BUTTON_CLEARFINISHED,41,193,60,11
+ PUSHBUTTON "Remove Selected",IDC_BUTTON_REMOVESELECTED,104,193,72,11
+ CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_NOPREFIX | SS_ENDELLIPSIS,180,195,112,9
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_CONFIG, DIALOG
+ BEGIN
+ RIGHTMARGIN, 265
+ BOTTOMMARGIN, 240
+ END
+
+ IDD_CUSTCOLUMNS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 335
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 147
+ END
+
+ IDD_VIEW_PMP_PLAYLIST, DIALOG
+ BEGIN
+ RIGHTMARGIN, 289
+ END
+
+ IDD_VIEW_PMP_DEVICES, DIALOG
+ BEGIN
+ RIGHTMARGIN, 292
+ END
+
+ IDD_EDIT_INFO, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 288
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 208
+ END
+
+ IDD_PROGRESS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 40
+ END
+
+ IDD_GETTINGMETADATA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 19
+ END
+
+ IDD_FIND, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 180
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 35
+ END
+
+ IDD_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 354
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 210
+ END
+
+ IDD_CONFIG_TRANSCODE, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 226
+ END
+
+ IDD_CONFIG_TRANSCODING_ADVANCED, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 259
+ TOPMARGIN, 3
+ BOTTOMMARGIN, 52
+ END
+
+ IDD_AUTOFILL, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 354
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 192
+ END
+
+ IDD_CONFIG_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 258
+ END
+
+ IDD_CONFIG_AUTOFILL, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ END
+
+ IDD_CONFIG_PODCAST_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 262
+ BOTTOMMARGIN, 222
+ END
+
+ IDD_CLOUD_SYNC, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 375
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 210
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource1.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
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// PNG
+//
+
+IDB_XFER_QUEUE_16 PNG ".\\resources\\transfer_queue_16x16.png"
+IDB_USB PNG ".\\resources\\usb.png"
+IDR_IMAGE_NOTFOUND PNG ".\\resources\\notfound.png"
+IDB_XFER_QUEUE_16_3 PNG ".\\resources\\transfer_queue_16x16_3.png"
+IDB_XFER_QUEUE_16_2 PNG ".\\resources\\transfer_queue_16x16_2.png"
+IDB_XFER_QUEUE_16_1 PNG ".\\resources\\transfer_queue_16x16_1.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CONTEXTMENUS MENU
+BEGIN
+ POPUP "TracksList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select &all\tCtrl+A", ID_TRACKSLIST_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &metadata...\tCtrl+E", ID_TRACKSLIST_EDITSELECTEDITEMS
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete...\tDelete", ID_TRACKSLIST_DELETE
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TRACKSLIST_COPYTOLIBRARY
+ END
+ POPUP "ArtistAlbumList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete...\tDelete", ID_TRACKSLIST_DELETE
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TRACKSLIST_COPYTOLIBRARY
+ END
+ POPUP "PlayList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Select &all\tCtrl+A", ID_TRACKSLIST_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &metadata...\tCtrl+E", ID_TRACKSLIST_EDITSELECTEDITEMS
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete...\tDelete", ID_TRACKSLIST_DELETE
+ MENUITEM "Rem&ove from playlist\tShift+Delete", ID_TRACKSLIST_REMOVEFROMPLAYLIST
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TRACKSLIST_COPYTOLIBRARY
+ END
+ POPUP "TreeDevice"
+ BEGIN
+ MENUITEM "&New Playlist...", ID_TREEDEVICE_NEWPLAYLIST
+ MENUITEM SEPARATOR
+ MENUITEM "&Eject Device", ID_TREEDEVICE_EJECTDEVICE
+ END
+ POPUP "TreePlaylist"
+ BEGIN
+ MENUITEM "&Rename\tF2", ID_TREEPLAYLIST_RENAMEPLAYLIST
+ MENUITEM "&Delete\tDelete", ID_TREEPLAYLIST_REMOVEPLAYLIST
+ MENUITEM "Delete with &files", ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES
+ MENUITEM SEPARATOR
+ MENUITEM "&Copy to Local Media\tCtrl+C", ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA
+ END
+ POPUP "SortPlaylist"
+ BEGIN
+ MENUITEM "Artist", ID_SORTPLAYLIST_ARTIST
+ MENUITEM "Album", ID_SORTPLAYLIST_ALBUM
+ MENUITEM "Title", ID_SORTPLAYLIST_TITLE
+ MENUITEM "Track", ID_SORTPLAYLIST_TRACK
+ MENUITEM "Disc", ID_SORTPLAYLIST_DISC
+ MENUITEM "Genre", ID_SORTPLAYLIST_GENRE
+ MENUITEM "Rating", ID_SORTPLAYLIST_RATING
+ MENUITEM "Play Count", ID_SORTPLAYLIST_PLAYCOUNT
+ MENUITEM "Last Played", ID_SORTPLAYLIST_LASTPLAYED
+ MENUITEM SEPARATOR
+ MENUITEM "Randomize", ID_SORTPLAYLIST_RANDOMIZE
+ MENUITEM SEPARATOR
+ MENUITEM "Reverse Playlist", ID_SORTPLAYLIST_REVERSEPLAYLIST
+ END
+ POPUP "MainTreeRoot"
+ BEGIN
+ MENUITEM "H&ide this icon when no portables are attached", ID_MAINTREEROOT_AUTOHIDEROOT
+ MENUITEM SEPARATOR
+ MENUITEM "&Preferences", ID_MAINTREEROOT_PREFERENCES
+ MENUITEM SEPARATOR
+ MENUITEM "&Help", ID_MAINTREEROOT_HELP
+ END
+ POPUP "TransfersList"
+ BEGIN
+ MENUITEM "&Play\tEnter", ID_TRACKSLIST_PLAYSELECTION
+ MENUITEM "&Enqueue\tShift+Enter", ID_TRACKSLIST_ENQUEUESELECTION
+ POPUP "&Send to playlist:"
+ BEGIN
+ MENUITEM "New Playlist", ID_ADDTOPLAYLIST_NEWPLAYLIST
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "Edit &metadata...\tCtrl+E", ID_TRACKSLIST_EDITSELECTEDITEMS
+ POPUP "&Rate"
+ BEGIN
+ MENUITEM "*****", ID_RATE_5
+ MENUITEM "****", ID_RATE_4
+ MENUITEM "***", ID_RATE_3
+ MENUITEM "**", ID_RATE_2
+ MENUITEM "*", ID_RATE_1
+ MENUITEM "No rating", ID_RATE_0
+ END
+ MENUITEM SEPARATOR
+ MENUITEM "&Delete\tDelete...", ID_TRACKSLIST_DELETE
+ END
+ POPUP "HeaderWnd"
+ BEGIN
+ MENUITEM "&Customize columns...", 40045
+ END
+ POPUP "FilterHeaderWnd"
+ BEGIN
+ MENUITEM "&Customize columns...", 40045
+ MENUITEM "Show &Horizontal Scrollbar", ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR
+ END
+ POPUP "ArtHeaderMenu"
+ BEGIN
+ MENUITEM "&Small Icon", ID_ARTHEADERMENU_SMALLICON
+ MENUITEM "&Medium Icon", ID_ARTHEADERMENU_MEDIUMICON
+ MENUITEM "&Large Icon", ID_ARTHEADERMENU_LARGEICON
+ MENUITEM SEPARATOR
+ MENUITEM "Sm&all Details", ID_ARTHEADERMENU_SMALLDETAILS
+ MENUITEM "M&edium Details", ID_ARTHEADERMENU_MEDIUMDETAILS
+ MENUITEM "La&rge Details", ID_ARTHEADERMENU_LARGEDETAILS
+ END
+ POPUP "ArtEditMenu"
+ BEGIN
+ MENUITEM "Copy", ID_ARTEDITMENU_COPY
+ MENUITEM "Paste", ID_ARTEDITMENU_PASTE
+ MENUITEM "Delete", ID_ARTEDITMENU_DELETE
+ MENUITEM "Save As...", ID_ARTEDITMENU_SAVEAS
+ MENUITEM "Download...", ID_ARTEDITMENU_DOWNLOAD
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RCDATA
+//
+
+IDR_DEVICE_ICON RCDATA ".\\resources\\deviceIcon.png"
+IDR_PLAYLIST_ICON RCDATA ".\\resources\\playlistIcon.png"
+IDR_VIDEO_ICON RCDATA ".\\resources\\videoIcon.png"
+IDR_TOOL_VIEWMODE_ICON RCDATA ".\\resources\\viewMode.png"
+IDR_TOOL_ALBUMART_ICON RCDATA ".\\resources\\albumArt.png"
+IDR_TOOL_COLUMNS_ICON RCDATA ".\\resources\\columns.png"
+IDR_TRANSFER_SMALL_ICON RCDATA ".\\resources\\sync-command-small.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Bitmap
+//
+
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD$(DISABLED) BITMAP "..\\ml_cloud\\resources\\ti_cloud_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD BITMAP "..\\ml_cloud\\resources\\ti_cloud_16x16x16.bmp"
+#endif
+#endif
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD_ADD_SOURCE$(DISABLED) BITMAP "..\\ml_cloud\\resources\\ti_add_cloud_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD_ADD_SOURCE BITMAP "..\\ml_cloud\\resources\\ti_add_cloud_16x16x16.bmp"
+#endif
+#endif
+#if defined(APSTUDIO_INVOKED) || defined(DISABLED)
+#if defined(APSTUDIO_INVOKED)
+IDB_TREEITEM_CLOUD_ADD_BYOS$(DISABLED) BITMAP "..\\ml_cloud\\resources\\ti_byos_16x16x16.bmp"
+#else
+IDB_TREEITEM_CLOUD_ADD_BYOS BITMAP "..\\ml_cloud\\resources\\ti_byos_16x16x16.bmp"
+#endif
+#endif
+
+//
+// Accelerator
+//
+
+IDR_ACCELERATORS ACCELERATORS
+BEGIN
+ "A", ID_TRACKSLIST_SELECTALL, VIRTKEY, CONTROL, NOINVERT
+ "C", ID_TRACKSLIST_COPYTOLIBRARY, VIRTKEY, CONTROL, NOINVERT
+ "E", ID_TRACKSLIST_EDITSELECTEDITEMS, VIRTKEY, CONTROL, NOINVERT
+ VK_DELETE, ID_TRACKSLIST_DELETE, VIRTKEY, NOINVERT
+ VK_DELETE, ID_TRACKSLIST_REMOVEFROMPLAYLIST, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, ID_TRACKSLIST_PLAYSELECTION, VIRTKEY, NOINVERT
+ VK_RETURN, IDC_BUTTON_PLAY, VIRTKEY, NOINVERT
+ VK_RETURN, ID_TRACKSLIST_ENQUEUESELECTION, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
+ VK_RETURN, IDC_BUTTON_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_PMP_SUPPORT "Nullsoft Portable Music Player Support v%s"
+ 65535 "{04C986EE-9CE3-4369-820D-A64394C63D60}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_VIDEO "Video"
+ IDS_RENAME_DEVICE "Rename Device"
+ IDS_LAST_CHANGED "Last Changed"
+ IDS_DEVICE_OUT_OF_SPACE "Not all of these tracks can be transferred, the device is out of space"
+ IDS_INCOMPATABLE_FORMAT_NO_TX
+ "Some of these tracks were of an incompatible format and will not be transferred"
+ IDS_ERROR "Error"
+ IDS_PHYSICALLY_REMOVE_X_TRACKS
+ "This will physically remove these %d tracks from this device.\nProceed?"
+ IDS_ARE_YOU_SURE "Are you sure?"
+ IDS_SYNC_IS_IN_PROGRESS "Synchronization is in progress!"
+ IDS_CANNOT_EJECT "Cannot Eject"
+ IDS_DELETING_TRACKS "Deleting Tracks..."
+ IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING
+ "You cannot remove tracks while transfers are in progress!"
+ IDS_SORRY "Sorry"
+ IDS_NATS_DEVICE_MAYBE_FULL
+ "Unfortunately not all of the tracks from the playlist could be transferred.\nThe device may be full."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT
+ "Unfortunately not all of the tracks from the playlist could be transferred.\nSome tracks are of an incompatible format."
+ IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT
+ "Unfortunately not all of the tracks from the playlist could be transferred.\nThe device may be full and some tracks are of an incompatible format."
+ IDS_WARNING "Warning"
+ IDS_SONGS_TO_BE_DELETED "Songs to be deleted"
+ IDS_SONGS_TO_BE_COPIED "Songs to be copied"
+ IDS_SONGS_NOT_IN_MEDIA_LIBRARY "Songs not in Library"
+ IDS_WAITING "Waiting"
+ IDS_PLAYLIST_SYNCRONIZATION "Playlist Synchronization"
+ IDS_OTHER "Other"
+ IDS_WORKING "Working..."
+ IDS_DONE "Done"
+ IDS_NOTHING_TO_SYNC_UP_TO_DATE
+ "Nothing to sync, the device is up to date."
+ IDS_SYNC "Sync"
+ IDS_X_SONGS_WILL_BE_TRANSFERRED_TO_THE_DEVICE
+ "%d songs will be transferred to the device.\nThere are %d songs on the device which are not in the Library."
+ IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE
+ "There is not enough space on this device to sync selected media.\r\n\r\nTry the AutoFill option to sync media with the available capacity on your device."
+ IDS_NOT_ENOUGH_SPACE "Limited Device Capacity"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_THIS_WILL_ADD_X_SONGS_AND_DELETE_X_SONGS
+ "This will add %d songs and delete %d songs.\nContinue?"
+ IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK
+ "This device does not support direct playback."
+ IDS_UNSUPPORTED "Unsupported"
+ IDS_PREFS_SYNC "Sync"
+ IDS_PREFS_AUTOFILL "AutoFill"
+ IDS_PREFS_TRANSCODING "Transcoding"
+ IDS_PREFS_VIEW "View"
+ IDS_EXAMPLE_FORMATTING_STRING "Example copied file filename: \n"
+ IDS_CHOOSE_A_FOLDER "Choose a folder"
+ IDS_COPIED_FILE_FORMAT_INFO
+ "You may enter a filename format string for your ripped files.\nIt can contain \\ or / to delimit a path, and the following keywords:\n\n <Artist> - inserts the album artist with the default capitalization\n <ARTIST> - inserts the album artist in all uppercase\n <artist> - inserts the album artist in all lowercase\n <Trackartist>/<TRACKARTIST>/<trackartist> - inserts the track artist\n <Album>/<ALBUM>/<album> - inserts the album\n <year> - inserts the album year\n <Genre>/<GENRE>/<genre> - inserts the album genre\n <Title>/<TITLE>/<title> - inserts the track title\n #, ##, or ### - inserts the track number, with leading 0s if ## or ###\n <filename> - uses the old filename of the file (not the full path)\n <disc> - inserts the disc number\n\n For Example: E:\\Music\\<Artist>\\<Album>\\## - <Title>\n"
+ IDS_COPIED_FILE_FORMAT_HELP "Copied Filename Format Help"
+ IDS_ALL "All"
+ IDS_AIM_TO_AUTOFILL_DEVICE "Aim to Autofill the device %d%% full"
+ IDS_ARTIST "Artist"
+ IDS_ALBUM "Album"
+ IDS_GENRE "Genre"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_YEAR "Year"
+ IDS_ALBUM_ARTIST "Album Artist"
+ IDS_PUBLISHER "Publisher"
+ IDS_COMPOSER "Composer"
+ IDS_ARTIST_INDEX "Artist Index"
+ IDS_ALBUM_ARTIST_INDEX "Album Artist Index"
+ IDS_PLUGIN_HAS_NO_CONFIG_IMPLEMENTED
+ "This plug-in has no configuration implemented"
+ IDS_DEVICE_PLUGINS "Device Plug-ins"
+ IDS_PERMANENTLY_UNINSTALL_THIS_PLUGIN
+ "Permanently uninstall this plug-in?\n(This may require a restart of Winamp)"
+ IDS_CONFIRMATION "Confirmation"
+ IDS_CLICK_OK_TO_CONTINUE "Click Ok to continue"
+ IDS_RELOADING_PLUGIN "Reloading plug-in"
+ IDS_UNKNOWN_ARTIST "Unknown Artist"
+ IDS_UNKKNOWN_ALBUM "Unknown Album"
+ IDS_UNKNOWN "Unknown"
+ IDS_TRANSFER "Transfer"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COPY_TO_LIBRARY "Copy to Library"
+ IDS_INVALID_PATH "Invalid Path"
+ IDS_STARTING_TRANSFER "Starting transfer..."
+ IDS_DUPLICATE "Duplicate"
+ IDS_TITLE "Title"
+ IDS_LENGTH "Length"
+ IDS_TRACK_NUMBER "Track #"
+ IDS_DISC "Disc"
+ IDS_BITRATE "Bitrate"
+ IDS_SIZE "Size"
+ IDS_PLAY_COUNT "Play Count"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_RATING "Rating"
+ IDS_LAST_PLAYED "Last Played"
+ IDS_TRACKS "Tracks"
+ IDS_ARTISTS "Artists"
+ IDS_NO_ARTIST "(no artist)"
+ IDS_ALBUMS "Albums"
+ IDS_GENRES "Genres"
+ IDS_ARTIST_INDEXES "Artist Indexes"
+ IDS_NO_ALBUM_ARTIST "(no album artist)"
+ IDS_ALBUM_ARTIST_INDEXES "Album Artist Indexes"
+ IDS_NO_YEAR "(no year)"
+ IDS_YEARS "Years"
+ IDS_ALBUM_ARTISTS "Album Artists"
+ IDS_NO_PUBLISHER "(no publisher)"
+ IDS_PUBLISHERS "Publishers"
+ IDS_NO_COMPOSER "(no composer)"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_COMPOSERS "Composers"
+ IDS_GHK_SYNC_PORTABLE_DEVICE "Portables: Sync Portable Device"
+ IDS_GHK_AUTOFILL_PORTABLE_DEVICE "Portables: AutoFill Portable Device"
+ IDS_GHK_EJECT_PORTABLE_DEVICE "Portables: Eject Portable Device"
+ IDS_PORTABLES "Portables"
+ IDS_PORTABLES_PERCENT "Portables (%d%%)"
+ IDS_LOADING "Loading..."
+ IDS_NAME "Name"
+ IDS_CAPACITY_FREE "Capacity (Free)"
+ IDS_TYPE "Type"
+ IDS_STATUS "Status"
+ IDS_DEVICE "Device"
+ IDS_TRANFERS_PERCENT_REMAINING "%d Transfers (%d%%), %02d:%02d Remaining"
+ IDS_RESUME "Resume"
+ IDS_PAUSE "Pause"
+ IDS_SETTING_METADATA "Setting Metadata..."
+END
+
+STRINGTABLE
+BEGIN
+ IDS_TRANSFER_PERCENT "Transferring %d%%"
+ IDS_ALL_X_WITHOUT_X "All (%d %s, %d without %s)"
+ IDS_ALL_X "All (%d %s)"
+ IDS_NO_ALBUM "(no album)"
+ IDS_NO_GENRE "(no genre)"
+ IDS_TRACK "Track"
+ IDS_X_ITEMS_X_AVAILABLE "%d items %s[%s] [%s available (%d%%) / %s]"
+ IDS_AUTOFILL_QUERY "Autofill Query"
+ IDS_SYNC_QUERY "Sync Query"
+ IDS_ALBUM_ART "Album Art"
+ IDS_NO_IMAGE "No image"
+ IDS_AVAILABLE "available"
+ IDS_OTHER2 "Other..."
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings for this plug-in?"
+ IDS_AUDIO_BUTTON_TT1 "Toggle Album Art View"
+ IDS_AUDIO_BUTTON_TT2 "Select Filters & Panes"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AUDIO_BUTTON_TT3 "Pane Options"
+ IDS_IMAGE_FILES "Image Files"
+ IDS_JPEG_FILE "JPEG File"
+ IDS_PNG_FILE "PNG File"
+ IDS_GIF_FILE "GIF File"
+ IDS_BMP_FILE "BMP File"
+ IDS_DELETE_PLAYLIST "Are you sure you want to delete playlist ""%s"" from the %s?"
+ IDS_NEW_PLAYLIST "Unknown playlist"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_TRANSFERRING "Transferring..."
+ IDS_DEVICE_CMD_PLAYLIST_CREATE "&New Playlist"
+ IDS_DEVICE_CMD_AUTOFILL "&Auto Fill"
+ IDS_DEVICE_CMD_EJECT "&Eject"
+ IDS_TRANSFERS "Transfers"
+ IDS_TRANSFERS_PERCENT "Transfers (%d%%)"
+ IDS_DEVICE_CMD_PREFERENCES "&Preferences"
+ IDS_DEVICE_CMD_VIEW_OPEN "&Open"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEVICE_CMD_VIEW_OPEN_DESC "Show contents of this device."
+ IDS_DEVICE_CMD_SYNC_DESC "Synchronize your Library with this device."
+ IDS_DEVICE_CMD_EJECT_DESC
+ "Save changes to device and prepare for removal."
+ IDS_DEVICE_CMD_RENAME_DESC "Change the name of this device."
+ IDS_DEVICE_CMD_AUTOFILL_DESC "AutoFill this device."
+ IDS_DEVICE_CMD_PLAYLIST_CREATE_DESC "Make a new playlist on this device."
+ IDS_DEVICE_CMD_PREFERENCES_DESC "Change settings for this device."
+ IDS_DEVICE_CMD_RENAME "&Rename\tF2"
+ IDS_DEVICE_CMD_SYNC "&Synchronize"
+ IDS_PORTABLE_DEVICE_TYPE "Portable Media Player"
+ IDS_DEVICE_CONNECTION_USB "USB"
+ IDS_PODCAST_SYNC "Podcast Sync"
+ IDS_TRANSFERRING_DESC "Performing synchronization. Select Transfer view for details."
+ IDS_UNKNOWN_TRACK "Unknown Track"
+ IDS_NUMBER "#"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DELETE_PLAYLIST_TITLE "Delete Playlist"
+ IDS_DEVICE_LOWERCASE "device"
+ IDS_KBPS "%d kbps"
+ IDS_SENDTO_DEVICES "Devices"
+ IDS_SENDTO_CLOUD "Cloud Sources"
+ IDS_MIME_TYPE "Mime"
+ IDS_ALREADY_UPLOADED "Already Uploaded"
+ IDS_SOURCE "Source"
+ IDS_DESTINATION "Destination"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLOUD_SYNC_PL_EMPTY "%s [No transferable files]"
+ IDS_CLOUD_SYNC_PL_SOME "%s [%d files already transferred]"
+ IDS_ALL_TRACKS_PLAYABLE "All tracks playable locally"
+ IDS_NO_TRACKS_PLAYABLE "No tracks playable locally"
+ IDS_SOME_TRACKS_PLAYABLE "Some tracks playable locally"
+ IDS_ALL_TRACKS_PLAYABLE_HERE "All tracks playable from here"
+ IDS_DATE_ADDED "Date Added"
+ IDS_CLIENT_TYPE "Client"
+ IDS_NOT_LOADED "NOT LOADED"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_DEVICE_CMD_REMOVE "Remove"
+ IDS_DEVICE_CMD_REMOVE_DESC "Remove"
+ IDS_LOCAL_MACHINE "Local Machine"
+ IDS_UPLOAD_CANCELLED "Upload Cancelled"
+ IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME "%d Transfers (%d%%)"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLOUD_SOURCES "Cloud Sources:"
+ IDS_CLOUD "Cloud"
+ IDS_X_ITEMS_X_AVAILABLE_SLIM "%d items %s[%s]"
+ IDS_CLOUD_REMOVE_X_TRACKS
+ "This will remove these %d tracks from this Cloud device only.\nProceed?"
+ IDS_DELETE "Delete...\tDel"
+ IDS_REMOVE "Remove...\tDel"
+ IDS_REMOVING_TRACKS "Removing Tracks..."
+ IDS_DEVICE_CMD_TRANSFER "Transfer"
+ IDS_DEVICE_CMD_TRANSFER_DESC "Transfer"
+ IDS_SOURCE_FILE "Source File"
+ IDS_UPLOADED "Uploaded"
+ IDS_PLAYLISTS "Playlists"
+ IDS_SONGS "Songs"
+ IDS_CLOUD_TX_X_TO_Y "Cloud Transfer: '%s' -> '%s'"
+ IDS_CLOUD_TX_HAVE_SELECTED_X
+ "Transferring from '%s' to '%s'\n\nYou have selected %lld songs (%s) to be transferred to '%s' (%s available space)"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CLOUD_TX_OVER_LIMIT "\n\n\nThe current selection puts you %s over your limit on '%s'.\n\nChange the number of selected songs to reduce the number to transfer or consider upgrading or choosing a device with more available space."
+ IDS_CLOUD_TX_NO_SEL "Transferring from '%s' to '%s'\n\nThere are no selected songs to transfer"
+ IDS_CLOUD_LOCAL_LIBRARY "Local Library"
+ IDS_CLOUD_SYNC_ALL_SONGS "Transfer all songs"
+ IDS_CLOUD_SYNC_ALL_PL "Transfer all playlists"
+ IDS_CLOUD_SYNC_SEL_SONGS "Transfer selected songs only"
+ IDS_CLOUD_SYNC_SEL_PL "Transfer selected playlists only"
+ IDS_CLOUD_SYNC_NONSEL_SONGS "Transfer all songs except those selected"
+ IDS_CLOUD_SYNC_NONSEL_PL "Transfer all playlists except those selected"
+ IDS_SEND_TO_PL "&Send to playlist:"
+ IDS_DEVICE_CMD_HIDE "Hide Source"
+ IDS_ADD_SOURCE "Add Source"
+ IDS_TRACK_AVAILABLE "Track available in "
+ IDS_UPLOAD_TO_SOURCE "Upload track to source"
+ IDS_CONFIRM_QUIT "Confirm Quit"
+ IDS_CANCEL_TRANSFERS_AND_QUIT
+ "There are transfers currently in progress.\nAre you sure you want to cancel these transfers and quit?"
+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_pmp/ml_pmp.sln b/Src/Plugins/Library/ml_pmp/ml_pmp.sln
new file mode 100644
index 00000000..c05670fa
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.sln
@@ -0,0 +1,141 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29424.173
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_pmp", "ml_pmp.vcxproj", "{C524F141-87CA-491B-91D8-920248C9066E}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F} = {A929EC04-302E-4B4F-B2A3-65AF63BB088F}
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}
+ {44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA} = {977153BF-8420-4C8D-AA25-592FAEDB6CBA}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tataki", "..\tataki\tataki.vcxproj", "{255B68B5-7EF8-45EF-A675-2D6B88147909}"
+ ProjectSection(ProjectDependencies) = postProject
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "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
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "elevator", "..\Elevator\elevator.vcxproj", "{977153BF-8420-4C8D-AA25-592FAEDB6CBA}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F} = {A929EC04-302E-4B4F-B2A3-65AF63BB088F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ElevatorPS", "..\Elevator\ElevatorPS.vcxproj", "{A929EC04-302E-4B4F-B2A3-65AF63BB088F}"
+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
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|Win32.Build.0 = Debug|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|x64.ActiveCfg = Debug|x64
+ {C524F141-87CA-491B-91D8-920248C9066E}.Debug|x64.Build.0 = Debug|x64
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|Win32.ActiveCfg = Release|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|Win32.Build.0 = Release|Win32
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|x64.ActiveCfg = Release|x64
+ {C524F141-87CA-491B-91D8-920248C9066E}.Release|x64.Build.0 = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.ActiveCfg = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.Build.0 = Debug|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.ActiveCfg = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.Build.0 = Debug|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.ActiveCfg = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.Build.0 = Release|Win32
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.ActiveCfg = Release|x64
+ {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.Build.0 = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.Build.0 = Debug|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.Build.0 = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64
+ {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
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|Win32.ActiveCfg = Debug|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|Win32.Build.0 = Debug|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|x64.ActiveCfg = Debug|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Debug|x64.Build.0 = Debug|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|Win32.ActiveCfg = Release|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|Win32.Build.0 = Release|Win32
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|x64.ActiveCfg = Release|x64
+ {977153BF-8420-4C8D-AA25-592FAEDB6CBA}.Release|x64.Build.0 = Release|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|Win32.Build.0 = Debug|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|x64.ActiveCfg = Debug|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Debug|x64.Build.0 = Debug|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|Win32.ActiveCfg = Release|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|Win32.Build.0 = Release|Win32
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|x64.ActiveCfg = Release|x64
+ {A929EC04-302E-4B4F-B2A3-65AF63BB088F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C138FA71-FE1F-4840-B678-526B21CE935D}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj
new file mode 100644
index 00000000..3ccbf638
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj
@@ -0,0 +1,444 @@
+<?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>{C524F141-87CA-491B-91D8-920248C9066E}</ProjectGuid>
+ <RootNamespace>ml_pmp</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;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4838;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN64;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4100;4302;4311;4312;4838;4244;4267;4090;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4838;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;IGNORE_API_GRACENOTE;WIN64;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4302;4311;4312;4838;4244;4267;4090;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ModuleOutputFile>$(IntDir)</ModuleOutputFile>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x0409</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalDependencies>odbc32.lib;odbccp32.lib;wsock32.lib;shlwapi.lib;comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <ModuleDefinitionFile>
+ </ModuleDefinitionFile>
+ <SubSystem>Windows</SubSystem>
+ </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>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\replicant\nx\nx.vcxproj">
+ <Project>{57c90706-b25d-4aca-9b33-95cdb2427c27}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\tataki\tataki.vcxproj">
+ <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</Project>
+ <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
+ <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\General\gen_ml\itemlist.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp" />
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
+ <ClCompile Include="..\ml_local\guess.cpp" />
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp" />
+ <ClCompile Include="..\..\..\nu\sort.cpp" />
+ <ClCompile Include="AlbumArtListView.cpp" />
+ <ClCompile Include="ArtistAlbumLists.cpp" />
+ <ClCompile Include="autofill.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="DeviceCommands.cpp" />
+ <ClCompile Include="DeviceView.cpp" />
+ <ClCompile Include="editinfo.cpp" />
+ <ClCompile Include="Filters.cpp" />
+ <ClCompile Include="graphics.cpp" />
+ <ClCompile Include="IconStore.cpp" />
+ <ClCompile Include="indirectplaybackserver.cpp" />
+ <ClCompile Include="LinkedQueue.cpp" />
+ <ClCompile Include="local_menu.cpp" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="metadata_utils.cpp" />
+ <ClCompile Include="mt19937ar.cpp" />
+ <ClCompile Include="pluginloader.cpp" />
+ <ClCompile Include="PmpDevice.cpp" />
+ <ClCompile Include="prefs.cpp" />
+ <ClCompile Include="replaceVars.cpp" />
+ <ClCompile Include="SkinnedListView.cpp" />
+ <ClCompile Include="syncCloudDialog.cpp" />
+ <ClCompile Include="syncDialog.cpp" />
+ <ClCompile Include="transcoder_imp.cpp" />
+ <ClCompile Include="transfer_thread.cpp" />
+ <ClCompile Include="view_pmp_devices.cpp" />
+ <ClCompile Include="view_pmp_media.cpp" />
+ <ClCompile Include="view_pmp_queue.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_hotkeys\wa_hotkeys.h" />
+ <ClInclude Include="..\..\General\gen_ml\itemlist.h" />
+ <ClInclude Include="..\..\General\gen_ml\menu.h" />
+ <ClInclude Include="..\..\General\gen_ml\ml.h" />
+ <ClInclude Include="..\..\..\nu\listview.h" />
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
+ <ClInclude Include="..\..\..\nu\menushortcuts.h" />
+ <ClInclude Include="..\..\..\nu\ServiceWatcher.h" />
+ <ClInclude Include="..\..\..\Winamp\ipc_pe.h" />
+ <ClInclude Include="..\..\..\Winamp\wa_dlg.h" />
+ <ClInclude Include="..\..\..\Winamp\wa_ipc.h" />
+ <ClInclude Include="AlbumArtListView.h" />
+ <ClInclude Include="api__ml_pmp.h" />
+ <ClInclude Include="ArtistAlbumLists.h" />
+ <ClInclude Include="config.h" />
+ <ClInclude Include="DeviceCommands.h" />
+ <ClInclude Include="DeviceView.h" />
+ <ClInclude Include="Filters.h" />
+ <ClInclude Include="graphics.h" />
+ <ClInclude Include="IconStore.h" />
+ <ClInclude Include="LinkedQueue.h" />
+ <ClInclude Include="local_menu.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="metadata_utils.h" />
+ <ClInclude Include="mt19937ar.h" />
+ <ClInclude Include="pluginloader.h" />
+ <ClInclude Include="pmp.h" />
+ <ClInclude Include="PmpDevice.h" />
+ <ClInclude Include="resource1.h" />
+ <ClInclude Include="SkinnedListView.h" />
+ <ClInclude Include="syncCloudDialog.h" />
+ <ClInclude Include="syncDialog.h" />
+ <ClInclude Include="transcoder.h" />
+ <ClInclude Include="transcoder_imp.h" />
+ <ClInclude Include="transfer_thread.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="..\ml_cloud\resources\ti_add_cloud_16x16x16.bmp" />
+ <Image Include="..\ml_cloud\resources\ti_byos_16x16x16.bmp" />
+ <Image Include="..\ml_cloud\resources\ti_cloud_16x16x16.bmp" />
+ <Image Include="resources\albumArt.png" />
+ <Image Include="resources\columns.png" />
+ <Image Include="resources\deviceIcon.png" />
+ <Image Include="resources\notfound.png" />
+ <Image Include="resources\playlistIcon.png" />
+ <Image Include="resources\sync-command-large.png" />
+ <Image Include="resources\sync-command-small.png" />
+ <Image Include="resources\transfer_queue_16x16.png" />
+ <Image Include="resources\transfer_queue_16x16_1.png" />
+ <Image Include="resources\transfer_queue_16x16_2.png" />
+ <Image Include="resources\transfer_queue_16x16_3.png" />
+ <Image Include="resources\usb.png" />
+ <Image Include="resources\videoIcon.png" />
+ <Image Include="resources\viewMode.png" />
+ <Image Include="transfer_queue_16x16.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_pmp.rc" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+ <ProjectExtensions>
+ <VisualStudio>
+ <UserProperties RESOURCE_FILE="ml_pmp.rc" />
+ </VisualStudio>
+ </ProjectExtensions>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters
new file mode 100644
index 00000000..61358d7b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/ml_pmp.vcxproj.filters
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="AlbumArtListView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ArtistAlbumLists.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="autofill.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DeviceCommands.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="DeviceView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="editinfo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Filters.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="graphics.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\ml_local\guess.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="IconStore.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="indirectplaybackserver.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="LinkedQueue.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="local_menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="metadata_utils.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="mt19937ar.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="pluginloader.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="PmpDevice.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="prefs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="replaceVars.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="SkinnedListView.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="syncCloudDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="syncDialog.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="transcoder_imp.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="transfer_thread.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pmp_devices.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pmp_media.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="view_pmp_queue.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\itemlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\menu.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\menushortcuts.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\ServiceWatcher.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\sort.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="AlbumArtListView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="api__ml_pmp.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ArtistAlbumLists.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="config.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DeviceCommands.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="DeviceView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Filters.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="graphics.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="IconStore.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="LinkedQueue.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="local_menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="metadata_utils.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="mt19937ar.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="pluginloader.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="pmp.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="PmpDevice.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="transfer_thread.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="transcoder_imp.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="transcoder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="syncDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="syncCloudDialog.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="SkinnedListView.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource1.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\ipc_pe.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\itemlist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\listview.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\menu.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\menushortcuts.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_ml\ml.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\nu\ServiceWatcher.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\wa_dlg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\General\gen_hotkeys\wa_hotkeys.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\Winamp\wa_ipc.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\columns.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\albumArt.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\deviceIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\notfound.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\playlistIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\sync-command-large.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\sync-command-small.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="..\ml_cloud\resources\ti_add_cloud_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="..\ml_cloud\resources\ti_byos_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="..\ml_cloud\resources\ti_cloud_16x16x16.bmp">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="transfer_queue_16x16.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16_1.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16_2.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\transfer_queue_16x16_3.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\usb.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\videoIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\viewMode.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{2260cdd9-16ed-4c23-8fb3-a13ecce513d3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{460ed29f-85d9-44b8-964a-8a55c6556b88}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{b08c2218-ed5e-46a8-9564-75c07f6f2a85}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{0c486deb-c516-4f17-acd5-94651b7a19cc}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_pmp.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/mt19937ar.cpp b/Src/Plugins/Library/ml_pmp/mt19937ar.cpp
new file mode 100644
index 00000000..7d086488
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/mt19937ar.cpp
@@ -0,0 +1,3 @@
+#include "mt19937ar.h"
+
+int (*genrand_int31)()=0;
diff --git a/Src/Plugins/Library/ml_pmp/mt19937ar.h b/Src/Plugins/Library/ml_pmp/mt19937ar.h
new file mode 100644
index 00000000..175d95e1
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/mt19937ar.h
@@ -0,0 +1,6 @@
+#ifndef _MT19937AR_H
+#define _MT19937AR_H
+
+extern int (*genrand_int31)();
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/pluginloader.cpp b/Src/Plugins/Library/ml_pmp/pluginloader.cpp
new file mode 100644
index 00000000..6045e995
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/pluginloader.cpp
@@ -0,0 +1,187 @@
+#include "pluginloader.h"
+#include "../winamp/wa_ipc.h"
+#include "nu/AutoWide.h"
+#include <shlwapi.h>
+#include "../nu/MediaLibraryInterface.h"
+
+extern winampMediaLibraryPlugin plugin;
+
+C_ItemList m_plugins;
+
+extern HWND mainMessageWindow;
+
+int wmDeviceChange(WPARAM wParam, LPARAM lParam) {
+ int ret=0;
+ for(int i=0; i < m_plugins.GetSize(); i++) {
+ PMPDevicePlugin * plugin = (PMPDevicePlugin *)m_plugins.Get(i);
+ /*
+ if(plugin->wmDeviceChange)
+ {
+ if(plugin->wmDeviceChange(wParam, lParam) == BROADCAST_QUERY_DENY)
+ ret = BROADCAST_QUERY_DENY;
+ }
+ */
+ if(plugin->MessageProc)
+ {
+ if(plugin->MessageProc(PMP_DEVICECHANGE,wParam,lParam,0) == BROADCAST_QUERY_DENY)
+ ret = BROADCAST_QUERY_DENY;
+ }
+ }
+ return ret;
+}
+
+PMPDevicePlugin * loadPlugin(wchar_t * file)
+{
+ HINSTANCE m=LoadLibrary(file);
+ if(m)
+ {
+ PMPDevicePlugin *(*gp)();
+ gp=(PMPDevicePlugin *(__cdecl *)(void))GetProcAddress(m,"winampGetPMPDevicePlugin");
+ if(!gp)
+ {
+ FreeLibrary(m);
+ return NULL;
+ }
+
+ PMPDevicePlugin *devplugin=gp();
+ if(!devplugin || devplugin->version != PMPHDR_VER)
+ {
+ FreeLibrary(m);
+ return NULL;
+ }
+
+ devplugin->hDllInstance=m;
+ devplugin->hwndLibraryParent=plugin.hwndLibraryParent;
+ devplugin->hwndWinampParent=plugin.hwndWinampParent;
+ devplugin->hwndPortablesParent=mainMessageWindow;
+ devplugin->service = plugin.service;
+
+ if(devplugin->init())
+ {
+ FreeLibrary(m);
+ }
+ else
+ {
+ m_plugins.Add((void *)devplugin);
+ return devplugin;
+ }
+ }
+ return NULL;
+}
+
+BOOL loadDevPlugins(int *count)
+{
+ BOOL loaded = FALSE;
+ wchar_t tofind[MAX_PATH] = {0};
+ LPCWSTR dir = (LPCWSTR)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORYW);
+ PathCombine(tofind, dir, L"pmp_*.dll");
+
+ WIN32_FIND_DATA d = {0};
+ HANDLE h = FindFirstFile(tofind,&d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ wchar_t file[MAX_PATH] = {0};
+ PathCombine(file, dir, d.cFileName);
+ loaded += (!!loadPlugin(file));
+ }
+ while(FindNextFile(h,&d));
+ FindClose(h);
+ }
+
+ if (count) *count = m_plugins.GetSize();
+ return loaded;
+}
+
+BOOL testForDevPlugins()
+{
+ BOOL found = FALSE;
+ wchar_t tofind[MAX_PATH] = {0};
+ LPCWSTR dir = (LPCWSTR)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORYW);
+ PathCombine(tofind, dir, L"pmp_*.dll");
+
+ WIN32_FIND_DATA d = {0};
+ HANDLE h = FindFirstFile(tofind,&d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ found = TRUE;
+ FindClose(h);
+ }
+ return found;
+}
+
+extern int profile;
+static HANDLE hProfile = INVALID_HANDLE_VALUE;
+HANDLE GetProfileFileHandle()
+{
+ if (profile)
+ {
+ if (hProfile == INVALID_HANDLE_VALUE)
+ {
+ wchar_t profileFile[MAX_PATH] = {0};
+ PathCombineW(profileFile, mediaLibrary.GetIniDirectoryW(), L"profile.txt");
+ hProfile = CreateFileW(profileFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ // just to make sure we don't over-write things
+ SetFilePointer(hProfile, NULL, NULL, FILE_END);
+ }
+ }
+ return hProfile;
+ }
+ return INVALID_HANDLE_VALUE;
+}
+
+void unloadPlugin(PMPDevicePlugin *devplugin, int n=-1)
+{
+ if(n == -1) for(int i=0; i<m_plugins.GetSize(); i++) if(m_plugins.Get(i) == (void*)devplugin) n=i;
+ devplugin->quit();
+ //if (devplugin->hDllInstance) FreeLibrary(devplugin->hDllInstance);
+ m_plugins.Del(n);
+}
+
+void unloadDevPlugins()
+{
+ int i=m_plugins.GetSize();
+ HANDLE hProfile = GetProfileFileHandle();
+ LARGE_INTEGER freq;
+ QueryPerformanceFrequency(&freq);
+
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ DWORD written = 0;
+ WriteFile(hProfile, L"\r\n", 2, &written, NULL);
+ }
+
+ while (i-->0) // reverse order to aid in not fucking up subclassing shit
+ {
+ PMPDevicePlugin *devplugin=(PMPDevicePlugin *)m_plugins.Get(i);
+ wchar_t profile[MAX_PATH*2] = {0}, filename[MAX_PATH] = {0};
+ LARGE_INTEGER starttime, endtime;
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ GetModuleFileNameW(devplugin->hDllInstance, filename, MAX_PATH);
+ QueryPerformanceCounter(&starttime);
+ }
+
+ unloadPlugin(devplugin,i);
+
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ QueryPerformanceCounter(&endtime);
+
+ DWORD written = 0;
+ unsigned int ms = (UINT)((endtime.QuadPart - starttime.QuadPart) * 1000 / freq.QuadPart);
+ int len = swprintf(profile, L"Portable\t%s\t[%s]\t%dms\r\n", filename, devplugin->description, ms);
+ WriteFile(hProfile, profile, len*sizeof(wchar_t), &written, NULL);
+ }
+ }
+
+ if (hProfile != INVALID_HANDLE_VALUE)
+ {
+ DWORD written = 0;
+ WriteFile(hProfile, L"\r\n", 2, &written, NULL);
+ CloseHandle(hProfile);
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/pluginloader.h b/Src/Plugins/Library/ml_pmp/pluginloader.h
new file mode 100644
index 00000000..efb3cc1e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/pluginloader.h
@@ -0,0 +1,14 @@
+#ifndef __PLUGINLOADER_H_
+#define __PLUGINLOADER_H_
+
+#include <windows.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "pmp.h"
+#include "..\..\General\gen_ml/itemlist.h"
+
+BOOL testForDevPlugins();
+BOOL loadDevPlugins(int *count);
+void unloadDevPlugins();
+int wmDeviceChange(WPARAM wParam, LPARAM lParam);
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/pmp.h b/Src/Plugins/Library/ml_pmp/pmp.h
new file mode 100644
index 00000000..1b24036b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/pmp.h
@@ -0,0 +1,321 @@
+#ifndef __PMP_H_
+#define __PMP_H_
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h> // needed for HDC and stuff
+#include <stddef.h>
+#include "..\..\General\gen_ml/ml.h" // for itemRecordW
+// make sure you include ml.h before you include this, wherever you include it.
+
+#ifdef __cplusplus
+class api_service;
+#endif
+
+typedef intptr_t songid_t;
+typedef intptr_t pmpart_t;
+
+// What metadata the device supports
+#define SUPPORTS_ARTIST 0x00000001
+#define SUPPORTS_ALBUM 0x00000002
+#define SUPPORTS_TITLE 0x00000004
+#define SUPPORTS_TRACKNUM 0x00000008
+#define SUPPORTS_DISCNUM 0x00000010
+#define SUPPORTS_GENRE 0x00000020
+#define SUPPORTS_YEAR 0x00000040
+#define SUPPORTS_SIZE 0x00000080
+#define SUPPORTS_LENGTH 0x00000100
+#define SUPPORTS_BITRATE 0x00000200
+#define SUPPORTS_PLAYCOUNT 0x00000400
+#define SUPPORTS_RATING 0x00000800
+#define SUPPORTS_LASTPLAYED 0x00001000
+#define SUPPORTS_LASTUPDATED 0x00002000
+#define SUPPORTS_ALBUMARTIST 0x00004000
+#define SUPPORTS_COMPOSER 0x00008000
+#define SUPPORTS_PUBLISHER 0x00010000
+#define SUPPORTS_ALBUMART 0x00020000
+#define SUPPORTS_MIMETYPE 0x00040000
+#define SUPPORTS_DATEADDED 0x00080000
+
+// constants for sorting playlists
+#define SORTBY_ARTIST 0
+#define SORTBY_ALBUM 1
+#define SORTBY_TITLE 2
+#define SORTBY_TRACKNUM 3
+#define SORTBY_DISCNUM 4
+#define SORTBY_GENRE 5
+#define SORTBY_RATING 6
+#define SORTBY_PLAYCOUNT 7
+#define SORTBY_LASTPLAYED 8
+#define SORTBY_DATEADDED 9
+
+// for get/setTrackExtraInfo, FIELD_* will be passed in as the "field" parameter. check using "if(!wcscmp(field,FIELD_*))" or similar
+#define FIELD_EXTENSION L"ext" // return the file extention, eg L"mp3". Only needed for indirect playback, will never be set.
+
+// firstly a little clarification between what a playlistnumber is and a songid is.
+// playlist numbers are always 0,1,2,...,getPlaylistCount() (thus, if playlist 1 is deleted,
+// the number of all playlists except 0 change)
+// songids are unique identifiers which persist even when other songs are removed.
+// Feel free to use a pointer OR an integer as a songid.
+
+// NOTE: wherever stated, len means number of characters NOT bytes available in the buffer
+
+/* benski> All calls will be made on the 'main thread', with the following exceptions
+transferTrackToDevice() on the transfer thread
+trackRemovedFromTransferQueue() on the transfer thread
+copyToHardDrive() on the transfer thread
+*/
+class Device {
+ protected:
+ Device() {}
+ ~Device() {}
+ public:
+ virtual __int64 getDeviceCapacityAvailable()=0; // in bytes
+ virtual __int64 getDeviceCapacityTotal()=0; // in bytes
+
+ virtual void Eject()=0; // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this;
+ virtual void Close()=0; // save any changes, and call PMP_IPC_DEVICEDISCONNECTED AND delete this;
+
+ // return 0 for success, -1 for failed or cancelled
+ virtual int transferTrackToDevice(const itemRecordW * track, // the track to transfer
+ void * callbackContext, //pass this to the callback
+ void (*callback)(void *callbackContext, wchar_t *status), // call this every so often so the GUI can be updated. Including when finished!
+ songid_t * songid, // fill in the songid when you are finished
+ int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
+ )=0;
+ virtual int trackAddedToTransferQueue(const itemRecordW *track)=0; // return 0 to accept, -1 for "not enough space", -2 for "incorrect format"
+ virtual void trackRemovedFromTransferQueue(const itemRecordW *track)=0;
+
+ // return the amount of space that will be taken up on the device by the track (once it has been tranferred)
+ // or 0 for incompatable. This is usually the filesize, unless you are transcoding. An estimate is acceptable.
+ virtual __int64 getTrackSizeOnDevice(const itemRecordW *track)=0;
+
+ virtual void deleteTrack(songid_t songid)=0; // physically remove from device. Be sure to remove it from all the playlists!
+
+ virtual void commitChanges(){} // optional. Will be called at a good time to save changes
+
+ virtual int getPlaylistCount()=0; // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks.
+ // PlaylistName(0) should return the name of the device.
+ virtual void getPlaylistName(int playlistnumber, wchar_t *buf, int len)=0;
+ virtual int getPlaylistLength(int playlistnumber)=0;
+ virtual songid_t getPlaylistTrack(int playlistnumber,int songnum)=0; // returns a songid
+
+ virtual void setPlaylistName(int playlistnumber, const wchar_t *buf)=0; // with playlistnumber==0, set the name of the device.
+ virtual void playlistSwapItems(int playlistnumber, int posA, int posB)=0; // swap the songs at position posA and posB
+ virtual void sortPlaylist(int playlistnumber, int sortBy)=0;
+ virtual void addTrackToPlaylist(int playlistnumber, songid_t songid)=0; // adds songid to the end of the playlist
+ virtual void removeTrackFromPlaylist(int playlistnumber, int songnum)=0; //where songnum is the position of the track in the playlist
+
+ virtual void deletePlaylist(int playlistnumber)=0;
+ virtual int newPlaylist(const wchar_t *name)=0; // create empty playlist, returns playlistnumber. -1 for failed.
+
+ virtual void getTrackArtist(songid_t songid, wchar_t *buf, int len)=0;
+ virtual void getTrackAlbum(songid_t songid, wchar_t *buf, int len)=0;
+ virtual void getTrackTitle(songid_t songid, wchar_t *buf, int len)=0;
+ virtual int getTrackTrackNum(songid_t songid)=0;
+ virtual int getTrackDiscNum(songid_t songid)=0;
+ virtual void getTrackGenre(songid_t songid, wchar_t * buf, int len)=0;
+ virtual int getTrackYear(songid_t songid)=0;
+ virtual __int64 getTrackSize(songid_t songid)=0; // in bytes
+ virtual int getTrackLength(songid_t songid)=0; // in millisecs
+ virtual int getTrackBitrate(songid_t songid)=0; // in kbps
+ virtual int getTrackPlayCount(songid_t songid)=0;
+ virtual int getTrackRating(songid_t songid)=0; //0-5
+ virtual __time64_t getTrackLastPlayed(songid_t songid)=0; // in unix time format
+ virtual __time64_t getTrackLastUpdated(songid_t songid)=0; // in unix time format
+ virtual void getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len){};
+ virtual void getTrackPublisher(songid_t songid, wchar_t *buf, int len){};
+ virtual void getTrackComposer(songid_t songid, wchar_t *buf, int len){};
+ virtual void getTrackMimeType(songid_t songid, wchar_t *buf, int len){};
+ virtual __time64_t getTrackDateAdded(songid_t songid){ return -1; }; // in unix time format
+ virtual int getTrackType(songid_t songid) { return 0; }
+ virtual void getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len) {}; //optional
+
+ // feel free to ignore any you don't support
+ virtual void setTrackArtist(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackAlbum(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackTitle(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackTrackNum(songid_t songid, int value)=0;
+ virtual void setTrackDiscNum(songid_t songid, int value)=0;
+ virtual void setTrackGenre(songid_t songid, const wchar_t *value)=0;
+ virtual void setTrackYear(songid_t songid, int year)=0;
+ virtual void setTrackPlayCount(songid_t songid, int value)=0;
+ virtual void setTrackRating(songid_t songid, int value)=0;
+ virtual void setTrackLastPlayed(songid_t songid, __time64_t value)=0; // in unix time format
+ virtual void setTrackLastUpdated(songid_t songid, __time64_t value)=0; // in unix time format
+ virtual void setTrackAlbumArtist(songid_t songid, const wchar_t *value){};
+ virtual void setTrackPublisher(songid_t songid, const wchar_t *value){};
+ virtual void setTrackComposer(songid_t songid, const wchar_t *value){};
+ virtual void setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value) {}; //optional
+
+ virtual bool playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue)=0; // return false if unsupported
+
+ virtual intptr_t extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4){return 0;}
+
+ virtual bool copyToHardDriveSupported() {return false;}
+
+ virtual __int64 songSizeOnHardDrive(songid_t song) {return -1;} // how big a song will be when copied back. Return -1 for not supported.
+
+ virtual int copyToHardDrive(songid_t song, // the song to copy
+ wchar_t * path, // path to copy to, in the form "c:\directory\song". The directory will already be created, you must append ".mp3" or whatever to this string! (there is space for at least 10 new characters).
+ void * callbackContext, //pass this to the callback
+ void (*callback)(void * callbackContext, wchar_t * status), // call this every so often so the GUI can be updated. Including when finished!
+ int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
+ ) {return -1;} // -1 for failed/not supported. 0 for success.
+
+ // art functions
+ virtual void setArt(songid_t songid, void *buf, int w, int h){} //buf is in format ARGB32*
+ virtual pmpart_t getArt(songid_t songid){return NULL;}
+ virtual void releaseArt(pmpart_t art){}
+ virtual int drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h) {return 0;}
+ virtual void getArtNaturalSize(pmpart_t art, int *w, int *h){*w=*h=0;}
+ virtual void setArtNaturalSize(pmpart_t art, int w, int h){}
+ virtual void getArtData(pmpart_t art, void* data){} // data ARGB32* is at natural size
+ virtual bool artIsEqual(pmpart_t a, pmpart_t b){return false;}
+};
+
+#define PMPHDR_VER 0x10
+/*
+0x10 is for Winamp 5.66+
+- it adds passing a api_service *service
+
+0x9 is for Winamp 5.63+
+- it now requires that plugins handle addTrackToPlaylist(0, ...)
+ so plugins that don't want to directly add to their database via transferTrackToDevice() [which happens off-thread]
+ can do it during addTrackToPlaylist(0, ...) [which happens on the main thread]
+- changes description from char* to wchar_t* (as there's no 3rd party pmp_* we can make such a breaking change)
+*/
+
+// The MessageProc could recieve any of the following..
+#define PMP_DEVICECHANGE 0x100 // param1=WPARAM, param2=LPARAM. See http://msdn.microsoft.com/library/en-us/devio/base/wm_devicechange.asp
+#define PMP_CONFIG 0x101 // param1 will be the parent HWND. return 1 if you implement this
+#define PMP_NO_CONFIG 0x102 // return TRUE to allow the plug-in config button to be disabled as applicable
+
+// use SendMessage(hwndPortablesParent,WM_PMP_IPC,param,PMP_IPC_*); on any of the following
+#define WM_PMP_IPC WM_USER+10
+#define PMP_IPC_DEVICECONNECTED 0x100 // pass a Device *
+#define PMP_IPC_DEVICEDISCONNECTED 0x101 // pass a Device *
+
+#define PMP_IPC_DEVICELOADING 0x102 // pass a pmpDeviceLoading *.
+ // This is optional, call PMP_IPC_DEVICECONNECTED when loading is finished
+ // or PMP_IPC_DEVICEDISCONNECTED to cancel.
+ // while a device is being loaded, DEVICE_SET_ICON will be called, nothing else.
+
+#define PMP_IPC_DEVICENAMECHANGED 0x103 // pass a Device *
+ // added 5.64+
+
+#define PMP_IPC_DEVICECLOUDTRANSFER 0x104 // pass a cloudDeviceTransfer *
+ // added 5.64+
+
+#define PMP_IPC_GETCLOUDTRANSFERS 0x105 // pass a Cloudfiles * (defined as typedef nu::PtrList<wchar_t>)
+ // added 5.64+
+
+typedef struct {
+ wchar_t filenames[MAX_PATH + 1]; // list of filename(s) to transfer to the cloud device
+ void * device_token; // token identifier of the cloud device to transfer to
+} cloudDeviceTransfer; // added 5.64+
+
+typedef struct {
+ Device * dev;
+ // filled in by ml_pmp
+ void (*UpdateCaption)(wchar_t * caption, void * context); // call this with the context to update the caption
+ void * context;
+} pmpDeviceLoading;
+
+typedef struct {
+ HWND parent;
+ const char * dev_name;
+} pmpDevicePrefsView;
+
+#define PMP_IPC_GET_TRANSCODER 0x200 // returns a Transcoder*, pass your Device* (you must have previously called PMP_IPC_DEVICECONNECTED)
+#define PMP_IPC_RELEASE_TRANSCODER 0x201 // pass your Transcoder*, no return value
+#define PMP_IPC_ENUM_ACTIVE_DRIVES 0x300 // pass your function in wParam, or if you pass wParam = 0, it will return a function pointer (type ENUMDRIVES) you can call directly
+typedef void (*ENUM_DRIVES_CALLBACK)(wchar_t, UINT);
+typedef void (*ENUMDRIVES)(ENUM_DRIVES_CALLBACK callback);
+#define PMP_IPC_GET_INI_FILE 0x301 // pass a Device*, returns a wchar_t*
+#define PMP_IPC_GET_PREFS_VIEW 0x302 // pass a pmpDevicePrefsView*, returns a HWND
+
+// The following may be recieved by Device::extraActions (as param1)
+#define DEVICE_SET_ICON 0x0 // param2 is of type MLTREEIMAGE *, modify it to use your own icon for the device in the ML tree
+#define DEVICE_SUPPORTED_METADATA 0x1 // return a load of SUPPORTS_* ORed together. If you return 0, all is assumed.
+#define DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA 0x3 // return 1 if metadata cannot be edited, 0 otherwise.
+#define DEVICE_CAN_RENAME_DEVICE 0x4 // return 1 if the device can be renamed. setPlaylistName(0,name) will be used to rename the device
+#define DEVICE_GET_INI_FILE 0x5 // param2 is wchar_t * of length MAX_PATH. Fill it with the location of the inifile that should be used.
+#define DEVICE_GET_PREFS_DIALOG 0x6 // param2 is a pref_tab*, fill it in if you want to put your own config in. On WM_INITDIALOG lParam will be a prefsParam *
+#define DEVICE_REFRESH 0x7 // F5 was pressed. return 1 to do an in-place update
+#define DEVICE_ADDPODCASTGROUP 0x8 // for ipod. param2=int playlistid, param3=int position, param4=wchar_t* channelname
+#define DEVICE_ADDPODCASTGROUP_FINISH 0x9 // for ipod. param2=int playlistid
+#define DEVICE_SUPPORTS_VIDEO 0xA // return 1 if you support video
+#define DEVICE_DONE_SETTING 0xB // param2=songid_t, tells your plugin that a series of setTrack*() functions are done being called for a particular track
+#define DEVICE_VETO_ENCODER 0xC // param2==fourcc. return 1 to remove the encoder from the transcoding preferences
+#define DEVICE_GET_ICON 0xD // param2=width, param3=height, param4 = wchar_t[260] to put your path into (possibly res:// protocol)
+#define DEVICE_SUPPORTS_PODCASTS 0xE // return 0 if you support podcasts. return 1 if you don't want them transferred
+#define DEVICE_GET_CONNECTION_TYPE 0xF // return 1 if you support. param2 is a const char ** that you should set to a static connect type string.
+#define DEVICE_GET_UNIQUE_ID 0x10 // return 1 if you support. copy a unique name into param2 (char *), param3 will be the allocated size (# of characters)
+#define DEVICE_GET_MODEL 0x11 // return 1 if you support, param2 = (wchar_t*)buffer - model name; param3 = (unsigned int)bufferSize - buffer size; param4 - not used.
+#define DEVICE_SENDTO_UNSUPPORTED 0x12 // return 1 if you don't support send-to
+#define DEVICE_GET_DISPLAY_TYPE 0x13 // return 1 if you support, param2 = (wchar_t*)buffer - model name; param3 = (unsigned int)bufferSize - buffer size; param4 - not used.
+#define DEVICE_VETO_TRANSCODING 0x14 // return 1 if you don't support transcoding and don't want to show the 'Transcoding' preference tab (also see DEVICE_VETO_ENCODER)
+#define DEVICE_GET_PREFS_PARENT 0x15 // return prefsDlgRecW * of the parent preference page you want the device preference page to be a child off. return 0 for default placing
+#define DEVICE_GET_CLOUD_SOURCES_MENU 0x16 // return HMENU if you support cloud source menus and have one to provide (only called as needed). param2=(int*)num_cloud_devices. param4=(int)songid i.e. clicked item or -1 for selection
+#define DEVICE_DO_CLOUD_SOURCES_MENU 0x17 // a cloud sources menu item was clicked. param2=(int)menu_id from the menu provided via DEVICE_GET_CLOUD_SOURCES_MENU.
+ // param3=(int)mode where 0 is via submenu and 1 is the menu only i.e. single-selection.
+ // param4=(CItemList*) of songid_t so it is possible to do multiple selection handling
+ // return 1 if needing the item to be removed from the view
+#define DEVICE_IS_CLOUD_TX_DEVICE 0x18 // param1=(nx_string_t) return 1 if device token matches ours
+#define DEVICE_SYNC_UNSUPPORTED 0x19 // return 1 if you don't support sync
+#define DEVICE_GET_CLOUD_DEVICE_ID 0x20 // return the device id
+#define DEVICE_NOT_READY_TO_VIEW 0x21 // return 1 if view is not ready to be shown
+#define DEVICE_GET_NODE_ICON_ID 0x22 // return the resource id of the node icon
+#define DEVICE_PLAYLISTS_UNSUPPORTED 0x23 // return 1 if you don't support playlists
+#define DEVICE_DOES_NOT_SUPPORT_REMOVE 0x24 // return 1 if you don't support remove / eject
+
+#define CLOUD_SOURCE_MENUS 60000
+#define CLOUD_SOURCE_MENUS_UPPER 60000 + 20
+#define CLOUD_SOURCE_MENUS_PL_UPPER 60000 + 200
+
+typedef struct {
+ wchar_t title[98];
+ int res_id;
+ DLGPROC dlg_proc;
+ HINSTANCE hinst;
+} pref_tab;
+
+typedef struct {
+ HWND parent;
+ Device * dev;
+ void (*config_tab_init)(HWND tab,HWND m_hwndDlg); // call this on WM_INITDIALOG
+} prefsParam;
+
+typedef struct {
+ int version; // should be PMPHDR_VER
+ wchar_t *description; // a textual desciption (including version info)
+ int ( __cdecl *init)(); // called when winamp is loaded, for any one-time init
+ void ( __cdecl *quit)(); // called when winamp is unloaded, for any one-time deinit
+
+ INT_PTR ( __cdecl *MessageProc)(int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+ // All the following data is filled in by ml_pmp
+ HWND hwndWinampParent; // send this any of the WM_WA_IPC messages
+ HWND hwndLibraryParent; // send this any of the WM_ML_IPC messages
+ HWND hwndPortablesParent; // send this any of the WM_PMP_IPC messages
+ HINSTANCE hDllInstance; // this plugins instance
+
+ // filled in by Winamp (added 5.66+ to replace need to call IPC_GET_API_SERVICE on loading)
+ #ifdef __cplusplus
+ api_service *service;
+ #else
+ void * service;
+ #endif
+} PMPDevicePlugin;
+
+// return values from the init(..) which determines if Winamp will continue loading
+// and handling the plugin or if it will disregard the load attempt. If PMP_INIT_FAILURE
+// is returned then the plugin will be listed as [NOT LOADED] on the plug-in prefs page.
+#define PMP_INIT_SUCCESS 0
+#define PMP_INIT_FAILURE 1
+
+// return values from the winampUninstallPlugin(HINSTANCE hdll, HWND parent, int param)
+// which determine if we can uninstall the plugin immediately or on winamp restart
+#define PMP_PLUGIN_UNINSTALL_NOW 0x0
+#define PMP_PLUGIN_UNINSTALL_REBOOT 0x1
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/prefs.cpp b/Src/Plugins/Library/ml_pmp/prefs.cpp
new file mode 100644
index 00000000..65c649e9
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/prefs.cpp
@@ -0,0 +1,1541 @@
+#include "../Winamp/buildType.h"
+#include "api__ml_pmp.h"
+#include "main.h"
+#include "../ml_wire/ifc_podcast.h"
+#include "DeviceView.h"
+#include "nu/AutoWide.h"
+#include "transcoder_imp.h"
+#include "nu/listview.h"
+#include "nu/AutoLock.h"
+#include <Commctrl.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <api/syscb/callbacks/browsercb.h>
+#if defined (_WIN64)
+ #include "../Elevator/IFileTypeRegistrar_64.h"
+#else
+ #include "../Elevator/IFileTypeRegistrar_32.h"
+#endif
+
+extern winampMediaLibraryPlugin plugin;
+extern DeviceView * currentViewedDevice;
+extern C_ItemList m_plugins;
+extern C_Config * global_config;
+extern C_ItemList devices;
+extern HWND hwndMediaView;
+
+static void link_handledraw(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+void link_startsubclass(HWND hwndDlg, UINT id);
+
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_cloud_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_podcast_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_autofill(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_transcode(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static INT_PTR CALLBACK config_dlgproc_cloud_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+void myOpenURLWithFallback(HWND hwnd, wchar_t *loc, wchar_t *fallbackLoc);
+
+HWND m_hwndTab = NULL, m_hwndTabDisplay = NULL;
+int g_prefs_openpage=0;
+static C_Config * config;
+DeviceView * configDevice;
+
+enum {
+ SYNC_PREF_IDX = 0,
+ PODSYNC_PREF_IDX,
+ AUTOFILL_PREF_IDX,
+ TRANS_PREF_IDX,
+ MEDIAVIEW_PREF_IDX
+};
+static pref_tab tabs[] = {
+ {L"",IDD_CONFIG_SYNC,config_dlgproc_sync,0},
+ {L"",IDD_CONFIG_PODCAST_SYNC,config_dlgproc_podcast_sync,0},
+ {L"",IDD_CONFIG_AUTOFILL,config_dlgproc_autofill,0},
+ {L"",IDD_CONFIG_TRANSCODE,config_dlgproc_transcode,0},
+ {L"",IDD_CONFIG_MEDIAVIEW,config_dlgproc_mediaview,0},
+ {L"",0,0,0}, // extra config for plugins
+};
+
+static void config_tab_init(HWND tab,HWND m_hwndDlg)
+{
+ RECT r;
+ GetWindowRect(m_hwndTab,&r);
+ TabCtrl_AdjustRect(m_hwndTab,FALSE,&r);
+ MapWindowPoints(NULL,m_hwndDlg,(LPPOINT)&r,2);
+ SetWindowPos(tab,HWND_TOP,r.left,r.top,r.right-r.left,r.bottom-r.top,SWP_NOACTIVATE);
+ if(!SendMessage(plugin.hwndWinampParent,WM_WA_IPC,IPC_ISWINTHEMEPRESENT,IPC_USE_UXTHEME_FUNC))
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)tab,IPC_USE_UXTHEME_FUNC);
+}
+
+HWND OnSelChanged(HWND hwndDlg, HWND external = NULL, DeviceView *dev = NULL)
+{
+ static prefsParam p;
+ int sel=(!external ? TabCtrl_GetCurSel(m_hwndTab) : MEDIAVIEW_PREF_IDX);
+ if (!external)
+ {
+ g_prefs_openpage=sel;
+ if(m_hwndTabDisplay!=NULL)
+ DestroyWindow(m_hwndTabDisplay);
+ p.config_tab_init = config_tab_init;
+ p.dev = configDevice->dev;
+ p.parent = hwndDlg;
+
+ // copes with the Transcoding tab being hidden
+ if (sel >= TRANS_PREF_IDX && tabs[TRANS_PREF_IDX].title[0] == 0) sel++;
+ }
+ else
+ {
+ configDevice = dev;
+ config = dev->config;
+ p.config_tab_init = config_tab_init;
+ p.dev = dev->dev;
+ p.parent = hwndDlg;
+ }
+
+ // copes with a cloud device which doesn't need podcast sync and autofill
+ // as well as factoring in the transcoding page needing to be hidden, etc
+ if (configDevice->isCloudDevice)
+ {
+ if (!external && (sel >= SYNC_PREF_IDX)) sel += (3 + (tabs[TRANS_PREF_IDX].title[0] == 0));
+ if (sel == MEDIAVIEW_PREF_IDX)
+ {
+ tabs[sel].res_id = IDD_CONFIG_CLOUD_MEDIAVIEW;
+ tabs[sel].dlg_proc = config_dlgproc_cloud_mediaview;
+ }
+ }
+ else
+ {
+ if (sel == SYNC_PREF_IDX)
+ {
+ tabs[sel].res_id = IDD_CONFIG_SYNC;
+ tabs[sel].dlg_proc = config_dlgproc_sync;
+ }
+ if (sel == MEDIAVIEW_PREF_IDX)
+ {
+ tabs[sel].res_id = IDD_CONFIG_MEDIAVIEW;
+ tabs[sel].dlg_proc = config_dlgproc_mediaview;
+ }
+ }
+
+ if(!tabs[sel].hinst)
+ m_hwndTabDisplay=WASABI_API_CREATEDIALOGPARAMW(tabs[sel].res_id, hwndDlg, tabs[sel].dlg_proc, (LPARAM)&p);
+ else
+ m_hwndTabDisplay=WASABI_API_LNG->CreateLDialogParamW(tabs[sel].hinst, tabs[sel].hinst, tabs[sel].res_id, hwndDlg, tabs[sel].dlg_proc, (LPARAM)&p);
+
+ if (!external)
+ {
+ RECT r;
+ GetClientRect(m_hwndTab, &r);
+ TabCtrl_AdjustRect(m_hwndTab, FALSE, &r);
+ SetWindowPos(m_hwndTabDisplay, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE | SWP_SHOWWINDOW);
+ }
+ return m_hwndTabDisplay;
+}
+
+void Shell_Free(void *p) {
+ IMalloc *m = NULL;
+ if (SUCCEEDED(SHGetMalloc(&m)) && m)
+ {
+ m->Free(p);
+ }
+}
+
+wchar_t* GetDefaultSaveToFolder(wchar_t* path_to_store)
+{
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, path_to_store)))
+ {
+ if(FAILED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path_to_store)))
+ {
+ // and if that all fails then do a reasonable default
+ lstrcpyn(path_to_store, L"C:\\My Music", MAX_PATH);
+ }
+ // if there's no valid My Music folder (typically win2k) then default to %my_documents%\my music
+ else
+ {
+ PathCombine(path_to_store, path_to_store, L"My Music");
+ }
+ }
+ return path_to_store;
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music";
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)global_config->ReadString(L"extractpath", GetDefaultSaveToFolder(m_def_extract_path)));
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+extern void doFormatFileName(wchar_t out[MAX_PATH], wchar_t *fmt, int trackno, wchar_t *artist, wchar_t *album, wchar_t *title, wchar_t *genre, wchar_t *year, wchar_t *trackartist);
+
+INT_PTR CALLBACK global_config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music";
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ wchar_t m_def_filename_fmt[] = L"<Artist> - <Album>\\## - <Trackartist> - <Title>";
+ GetDefaultSaveToFolder(m_def_extract_path);
+ if (global_config->ReadInt(L"extractusecdrip", 1)) CheckDlgButton(hwndDlg, IDC_CHECK_USECDRIP, BST_CHECKED);
+ SendDlgItemMessage(hwndDlg, IDC_DESTPATH, EM_SETLIMITTEXT, MAX_PATH, 0);
+ SetDlgItemText(hwndDlg, IDC_DESTPATH, global_config->ReadString(L"extractpath", m_def_extract_path));
+ SetDlgItemText(hwndDlg, IDC_FILENAMEFMT, global_config->ReadString(L"extractfmt2", m_def_filename_fmt));
+ SendMessage(hwndDlg,WM_USER,0,0);
+ }
+ break;
+ case WM_USER:
+ {
+ BOOL enabled = !IsDlgButtonChecked(hwndDlg, IDC_CHECK_USECDRIP);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_1),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_DESTPATH),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON1),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_2),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_BUTTON2),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_FILENAMEFMT),enabled);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_FMTOUT),enabled);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_DESTPATH:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg, IDC_DESTPATH, buf, 1024);
+ global_config->WriteString(L"extractpath", buf);
+ }
+ return 0;
+ case IDC_CHECK_USECDRIP:
+ {
+ global_config->WriteInt(L"extractusecdrip", !!IsDlgButtonChecked(hwndDlg, IDC_CHECK_USECDRIP));
+ SendMessage(hwndDlg,WM_USER,0,0);
+ }
+ break;
+ // run through...
+ case IDC_FILENAMEFMT:
+ if (HIWORD(wParam) == EN_CHANGE)
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg, IDC_FILENAMEFMT, buf, 1024);
+ if(LOWORD(wParam) == IDC_FILENAMEFMT) global_config->WriteString(L"extractfmt2", buf);
+ wchar_t str[MAX_PATH] = {0};
+ doFormatFileName(WASABI_API_LNGSTRINGW_BUF(IDS_EXAMPLE_FORMATTING_STRING,str,MAX_PATH),
+ buf, 10, L"U2", L"The Joshua Tree", L"Exit", L"Rock", L"1987", L"U2");
+
+ wchar_t fmt[5]=L"mp3";
+
+ StringCchCat(str, MAX_PATH, L".");
+ StringCchCat(str, MAX_PATH, fmt);
+
+ SetDlgItemText(hwndDlg, IDC_FMTOUT, str);
+ }
+ return 0;
+ case IDC_BUTTON1:
+ {
+ //browse for folder
+ BROWSEINFO bi = {0};
+ wchar_t name[MAX_PATH] = {0};
+ bi.hwndOwner = hwndDlg;
+ bi.pidlRoot = NULL;
+ bi.pszDisplayName = name;
+ bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_A_FOLDER);
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ ITEMIDLIST *idlist = SHBrowseForFolder(&bi);
+ if (idlist)
+ {
+ wchar_t path[MAX_PATH] = {0};
+ SHGetPathFromIDList( idlist, path );
+ Shell_Free(idlist);
+ global_config->WriteString(L"extractpath", path);
+ SetDlgItemText(hwndDlg, IDC_DESTPATH, global_config->ReadString(L"extractpath", m_def_extract_path));
+ }
+ }
+ break;
+ case IDC_BUTTON2:
+ wchar_t titleStr[64] = {0};
+ MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_COPIED_FILE_FORMAT_INFO),
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPIED_FILE_FORMAT_HELP,titleStr,64),
+ MB_OK);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ config = global_config;
+ DeviceView * dev = NULL;
+ for(int i=0; i<devices.GetSize(); i++)
+ {
+ dev = (DeviceView *)devices.Get(i);
+ if(&dev->devPrefsPage == (prefsDlgRecW *)lParam) { configDevice = dev; config = dev->config; break; }
+ }
+ if(config == global_config)
+ {
+ EndDialog(hwndDlg,0);
+ return 0;
+ }
+
+ // set the prefs page titles at this stage so that we can have localised versions
+ int title_ids[] = {IDS_PREFS_SYNC,IDS_PODCAST_SYNC,IDS_PREFS_AUTOFILL,IDS_PREFS_TRANSCODING,IDS_PREFS_VIEW};
+ for(int i=0; i<sizeof(title_ids)/sizeof(title_ids[0]); i++)
+ WASABI_API_LNGSTRINGW_BUF(title_ids[i],tabs[i].title,(sizeof(tabs[i].title)/sizeof(wchar_t)));
+
+ // set the last item's title to zero otherwise it can cause a dialog to appear incorrectly
+ tabs[sizeof(tabs)/sizeof(tabs[0])-1].title[0] = 0;
+
+ m_hwndTab = GetDlgItem(hwndDlg,IDC_TAB1);
+ TCITEM tie;
+ tie.mask = TCIF_TEXT;
+ int num = 0;
+ for(int i=0; i<sizeof(tabs)/sizeof(pref_tab); i++)
+ {
+ if (dev)
+ {
+ if(tabs[i].title[0] == 0)
+ {
+ dev->dev->extraActions(DEVICE_GET_PREFS_DIALOG,(intptr_t)&tabs[i],num++,0);
+ }
+
+ // check if we need to show the Transcoding page or not
+ if (i == TRANS_PREF_IDX && dev->dev->extraActions(DEVICE_VETO_TRANSCODING,0,0,0))
+ {
+ tabs[i].title[0] = 0;
+ }
+ else
+ {
+ // check if a cloud device and drop sync, podcast sync and autofill as appropriate
+ if (configDevice->isCloudDevice && (i == SYNC_PREF_IDX || i == PODSYNC_PREF_IDX || i == AUTOFILL_PREF_IDX))
+ {
+ tabs[i].title[0] = 0;
+ }
+ }
+ }
+
+ if(tabs[i].title[0] != 0)
+ {
+ tie.pszText=tabs[i].title;
+ TabCtrl_InsertItem(m_hwndTab,i,&tie);
+ }
+ }
+ TabCtrl_SetCurSel(m_hwndTab,g_prefs_openpage);
+ OnSelChanged(hwndDlg);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR lpn = (LPNMHDR) lParam;
+ if(lpn) if(lpn->code==TCN_SELCHANGE) OnSelChanged(hwndDlg);
+ }
+ break;
+
+ case WM_DESTROY:
+ tabs[4].title[0]=0;
+ configDevice = NULL;
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+static W_ListView m_list;
+static int nonotif;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ nonotif=1;
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ if(config->ReadInt(L"syncOnConnect",configDevice->SyncConnectionDefault)==1) CheckDlgButton(hwndDlg,IDC_SYNCONCONNECT,BST_CHECKED);
+ else EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),FALSE);
+ SetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,config->ReadString(L"syncOnConnect_hours",L"12"));
+ SetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,config->ReadString(L"SyncQuery",L"type=0"));
+ if(config->ReadInt(L"plsyncwhitelist",1)) CheckDlgButton(hwndDlg,IDC_PL_WHITELIST,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PL_BLACKLIST,BST_CHECKED);
+ if(config->ReadInt(L"syncAllLibrary",1)) CheckDlgButton(hwndDlg,IDC_LIBRARYSYNC,BST_CHECKED);
+
+ m_list.setwnd(GetDlgItem(hwndDlg,IDC_PL_LIST));
+ ListView_SetExtendedListViewStyle(m_list.getwnd(), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_list.AddCol(L"",353);
+ m_list.SetColumnWidth(0, 200);
+ mlPlaylistInfo playlist = {0};
+ playlist.size = sizeof(mlPlaylistInfo);
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,0,ML_IPC_PLAYLIST_COUNT);
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ m_list.InsertItem(i,playlist.playlistName,0);
+ }
+ ListView_SetColumnWidth(m_list.getwnd(),0,LVSCW_AUTOSIZE);
+
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ wchar_t buf[150] = {0};
+ StringCchPrintf(buf,150,L"sync-%s",playlist.playlistName);
+ BOOL state = config->ReadInt(buf,0)?TRUE:FALSE;
+ ListView_SetCheckState(m_list.getwnd(),i,state);
+ }
+ nonotif=0;
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_list.getwnd(), TRUE);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ HWND listWindow;
+ listWindow = GetDlgItem(hwndDlg,IDC_PL_LIST);
+ if (NULL != listWindow)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_PL_LIST && l->code == LVN_ITEMCHANGED && !nonotif) {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if(lv->iItem == -1) break;
+ wchar_t buf[150] = {0}, buf1[125] = {0};
+ m_list.GetText(lv->iItem,0,buf1,125);
+ StringCchPrintf(buf,150,L"sync-%s",buf1);
+ BOOL state = ListView_GetCheckState(m_list.getwnd(),lv->iItem);
+ config->WriteInt(buf,state?1:0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_SYNCONCONNECT:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT)?1:0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT));
+ break;
+
+ case IDC_SYNCONCONNECT_TIME:
+ if(HIWORD(wParam) == EN_CHANGE) {
+ wchar_t buf[100]=L"";
+ GetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,buf,100);
+ config->WriteInt(L"syncOnConnect_hours",_wtoi(buf));
+ }
+ break;
+
+ case IDC_PL_WHITELIST:
+ case IDC_PL_BLACKLIST:
+ config->WriteInt(L"plsyncwhitelist",IsDlgButtonChecked(hwndDlg,IDC_PL_WHITELIST)?1:0);
+ break;
+
+ case IDC_LIBRARYSYNC:
+ config->WriteInt(L"syncAllLibrary",IsDlgButtonChecked(hwndDlg,IDC_LIBRARYSYNC)?1:0);
+ break;
+
+ case IDC_SYNC_QUERY_STRING:
+ if (HIWORD(wParam) == EN_KILLFOCUS) {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,buf,1024);
+ config->WriteString(L"SyncQuery",buf);
+ }
+ break;
+
+ case IDC_SYNC_QUERY_EDIT:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, temp, sizeof(temp) - 1);
+ ml_editview meq = {hwndDlg,temp,"Sync Query",-1};
+ meq.name = WASABI_API_LNGSTRING(IDS_SYNC_QUERY);
+ if(!(int)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(LPARAM)&meq,ML_IPC_EDITVIEW)) return 0;
+ SetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, meq.query);
+ config->WriteString(L"SyncQuery",AutoWide(meq.query));
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_cloud_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+static W_ListView m_list;
+static int nonotif;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ nonotif=1;
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ if(config->ReadInt(L"syncOnConnect",configDevice->SyncConnectionDefault)==1) CheckDlgButton(hwndDlg,IDC_SYNCONCONNECT,BST_CHECKED);
+ else EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),FALSE);
+ SetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,config->ReadString(L"syncOnConnect_hours",L"12"));
+ SetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,config->ReadString(L"SyncQuery",L"type=0"));
+ if(config->ReadInt(L"plsyncwhitelist",1)) CheckDlgButton(hwndDlg,IDC_PL_WHITELIST,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PL_BLACKLIST,BST_CHECKED);
+ if(config->ReadInt(L"syncAllLibrary",1)) CheckDlgButton(hwndDlg,IDC_LIBRARYSYNC,BST_CHECKED);
+
+ m_list.setwnd(GetDlgItem(hwndDlg,IDC_PL_LIST));
+ ListView_SetExtendedListViewStyle(m_list.getwnd(), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_list.AddCol(L"",353);
+ mlPlaylistInfo playlist = {0};
+ playlist.size = sizeof(mlPlaylistInfo);
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,0,ML_IPC_PLAYLIST_COUNT);
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ m_list.InsertItem(i,playlist.playlistName,0);
+ }
+ ListView_SetColumnWidth(m_list.getwnd(),0,LVSCW_AUTOSIZE);
+
+ for(int i=0; i<playlistsnum; i++) {
+ playlist.playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playlist,ML_IPC_PLAYLIST_INFO);
+ wchar_t buf[150] = {0};
+ StringCchPrintf(buf,150,L"sync-%s",playlist.playlistName);
+ BOOL state = config->ReadInt(buf,0)?TRUE:FALSE;
+ ListView_SetCheckState(m_list.getwnd(),i,state);
+ }
+ nonotif=0;
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_list.getwnd(), TRUE);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ HWND listWindow;
+ listWindow = GetDlgItem(hwndDlg,IDC_PL_LIST);
+ if (NULL != listWindow)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_PL_LIST && l->code == LVN_ITEMCHANGED && !nonotif) {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if(lv->iItem == -1) break;
+ wchar_t buf[150] = {0}, buf1[125] = {0};
+ m_list.GetText(lv->iItem,0,buf1,125);
+ StringCchPrintf(buf,150,L"sync-%s",buf1);
+ BOOL state = ListView_GetCheckState(m_list.getwnd(),lv->iItem);
+ config->WriteInt(buf,state?1:0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_SYNCONCONNECT:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT)?1:0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT));
+ break;
+
+ case IDC_SYNCONCONNECT_TIME:
+ if(HIWORD(wParam) == EN_CHANGE) {
+ wchar_t buf[100]=L"";
+ GetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,buf,100);
+ config->WriteInt(L"syncOnConnect_hours",_wtoi(buf));
+ }
+ break;
+
+ case IDC_PL_WHITELIST:
+ case IDC_PL_BLACKLIST:
+ config->WriteInt(L"plsyncwhitelist",IsDlgButtonChecked(hwndDlg,IDC_PL_WHITELIST)?1:0);
+ break;
+
+ case IDC_LIBRARYSYNC:
+ config->WriteInt(L"syncAllLibrary",IsDlgButtonChecked(hwndDlg,IDC_LIBRARYSYNC)?1:0);
+ break;
+
+ case IDC_SYNC_QUERY_STRING:
+ if (HIWORD(wParam) == EN_KILLFOCUS) {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg,IDC_SYNC_QUERY_STRING,buf,1024);
+ config->WriteString(L"SyncQuery",buf);
+ }
+ break;
+
+ case IDC_SYNC_QUERY_EDIT:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, temp, sizeof(temp) - 1);
+ ml_editview meq = {hwndDlg,temp,"Sync Query",-1};
+ meq.name = WASABI_API_LNGSTRING(IDS_SYNC_QUERY);
+ if(!(int)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(LPARAM)&meq,ML_IPC_EDITVIEW)) return 0;
+ SetDlgItemTextA(hwndDlg, IDC_SYNC_QUERY_STRING, meq.query);
+ config->WriteString(L"SyncQuery",AutoWide(meq.query));
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_podcast_sync(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+static W_ListView m_list2;
+static int nonotif;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ nonotif=1;
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+
+ m_list2.setwnd(GetDlgItem(hwndDlg,IDC_PC_LIST));
+ ListView_SetExtendedListViewStyle(m_list2.getwnd(), LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_list2.AddCol(L"",215);
+
+ if (configDevice->dev->extraActions(DEVICE_SUPPORTS_PODCASTS, 0,0, 0) == 0) {
+ int podcastsnum = AGAVE_API_PODCASTS?AGAVE_API_PODCASTS->GetNumPodcasts():0;
+ for(int i=0; i<podcastsnum; i++) {
+ ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i);
+ if (podcast) {
+ wchar_t podcast_name[256] = {0};
+ if (podcast->GetTitle(podcast_name, 256) == 0 && podcast_name[0])
+ {
+ m_list2.InsertItem(i,podcast_name,0);
+ }
+ }
+ }
+ ListView_SetColumnWidth(m_list2.getwnd(),0,LVSCW_AUTOSIZE);
+
+ for(int i=0; i<podcastsnum; i++) {
+ ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i);
+ if (podcast) {
+ wchar_t podcast_name[256] = {0};
+ if (podcast->GetTitle(podcast_name, 256) == 0 && podcast_name[0]) {
+ wchar_t buf[300] = {0};
+ StringCchPrintf(buf,300,L"podcast-sync-%s",podcast_name);
+ BOOL state = config->ReadInt(buf,0)?TRUE:FALSE;
+ ListView_SetCheckState(m_list2.getwnd(),i,state);
+ }
+ }
+ }
+
+ int podcast_eps = config->ReadInt(L"podcast-sync_episodes",0);
+ if(podcast_eps == 0) {
+ podcast_eps=3;
+ } else
+ CheckDlgButton(hwndDlg,IDC_CHECK_PC,BST_CHECKED);
+
+ for(int i=0; i<6; i++) {
+ int a,d;
+ if(i==0) {
+ a = SendDlgItemMessage(hwndDlg,IDC_COMBO_PC_NUM,CB_ADDSTRING,0,
+ (LPARAM)WASABI_API_LNGSTRINGW(IDS_ALL));
+ d = -1;
+ } else {
+ wchar_t buf[10] = {0};
+ StringCchPrintf(buf,100,L"%d",i);
+ a = SendDlgItemMessage(hwndDlg,IDC_COMBO_PC_NUM,CB_ADDSTRING,0,(LPARAM)buf);
+ d = i;
+ }
+ SendDlgItemMessage(hwndDlg,IDC_COMBO_PC_NUM,CB_SETITEMDATA,a,(LPARAM)d);
+ if(d == podcast_eps) SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_SETCURSEL, (WPARAM)a, 0);
+ }
+
+ if(config->ReadInt(L"podcast-sync_all",1)) CheckDlgButton(hwndDlg,IDC_PC_ALL,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PC_SEL,BST_CHECKED);
+ }
+ else {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK_PC), FALSE);
+ }
+ SendMessage(hwndDlg,WM_COMMAND,IDC_CHECK_PC,0xdeadbeef);
+ nonotif=0;
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_list2.getwnd(), TRUE);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ {
+ HWND listWindow;
+ listWindow = GetDlgItem(hwndDlg,IDC_PL_LIST);
+ if (NULL != listWindow)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_PC_LIST && l->code == LVN_ITEMCHANGED && !nonotif) {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if(lv->iItem == -1) break;
+ wchar_t buf[150] = {0}, buf1[125] = {0};
+ m_list2.GetText(lv->iItem,0,buf1,125);
+ StringCchPrintf(buf,150,L"podcast-sync-%s",buf1);
+ BOOL state = ListView_GetCheckState(m_list2.getwnd(),lv->iItem);
+ config->WriteInt(buf,state?1:0);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_CHECK_PC:
+ {
+ BOOL e = IsDlgButtonChecked(hwndDlg,IDC_CHECK_PC);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COMBO_PC_NUM),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_PODTXT),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_ALL),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_SEL),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC_PODTIP),e);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_LIST),e && IsDlgButtonChecked(hwndDlg,IDC_PC_SEL));
+ if(lParam != 0xdeadbeef) {
+ if(e) {
+ int b = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETCURSEL, 0, 0);
+ if(b>=0) {
+ int x = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETITEMDATA, b, 0);
+ config->WriteInt(L"podcast-sync_episodes", x);
+ }
+ }
+ else config->WriteInt(L"podcast-sync_episodes",0);
+ }
+ }
+ break;
+
+ case IDC_PC_SEL:
+ case IDC_PC_ALL:
+ {
+ BOOL e = IsDlgButtonChecked(hwndDlg,IDC_PC_SEL);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_PC_LIST),e);
+ config->WriteInt(L"podcast-sync_all",!e);
+ }
+ break;
+
+ case IDC_COMBO_PC_NUM:
+ if(HIWORD(wParam) == CBN_SELCHANGE) {
+ int b = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETCURSEL, 0, 0);
+ if (b >= 0) {
+ int x = SendDlgItemMessage(hwndDlg, IDC_COMBO_PC_NUM, CB_GETITEMDATA, b, 0);
+ config->WriteInt(L"podcast-sync_episodes", x);
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_autofill(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ if(config->ReadInt(L"syncOnConnect",configDevice->SyncConnectionDefault)==2) CheckDlgButton(hwndDlg,IDC_SYNCONCONNECT,BST_CHECKED);
+ else EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),FALSE);
+ SetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,config->ReadString(L"syncOnConnect_hours",L"12"));
+ if(lstrlen(config->ReadString(L"AutoFillRatings",L""))!=0) CheckDlgButton(hwndDlg,IDC_BOOSTRATINGS,BST_CHECKED);
+ if(config->ReadInt(L"AlbumAutoFill",0)) CheckDlgButton(hwndDlg,IDC_AUTOFILLALBUMS,BST_CHECKED);
+ SetDlgItemText(hwndDlg,IDC_AUTOFILL_QUERY_STRING,config->ReadString(L"AutoFillQuery",L"length > 30"));
+ SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_SETRANGEMAX, TRUE, 100);
+ SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_SETRANGEMIN, TRUE, 0);
+ SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_SETPOS, TRUE, config->ReadInt(L"FillPercent",90));
+ break;
+ case WM_NOTIFY:
+ switch (LOWORD(wParam))
+ {
+ case IDC_SPACESLIDER:
+ {
+ int spaceToAutofill = SendMessage(GetDlgItem(hwndDlg,IDC_SPACESLIDER),TBM_GETPOS,0,0);
+ wchar_t tmp[100]=L"";
+ StringCchPrintf(tmp,100,WASABI_API_LNGSTRINGW(IDS_AIM_TO_AUTOFILL_DEVICE),spaceToAutofill);
+ SetDlgItemText(hwndDlg, IDC_FILLCAPTION, tmp);
+ config->WriteInt(L"FillPercent",spaceToAutofill);
+ }
+ break;
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_SYNCONCONNECT:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT)?2:0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_SYNCONCONNECT_TIME),IsDlgButtonChecked(hwndDlg,IDC_SYNCONCONNECT));
+ break;
+ case IDC_SYNCONCONNECT_TIME:
+ if(HIWORD(wParam) == EN_CHANGE)
+ {
+ wchar_t buf[100]=L"";
+ GetDlgItemText(hwndDlg,IDC_SYNCONCONNECT_TIME,buf,100);
+ config->WriteInt(L"syncOnConnect_hours",_wtoi(buf));
+ }
+ break;
+ case IDC_AUTOFILL_QUERY_STRING:
+ if (HIWORD(wParam) == EN_KILLFOCUS)
+ {
+ wchar_t buf[1024] = {0};
+ GetDlgItemText(hwndDlg,IDC_AUTOFILL_QUERY_STRING,buf,1024);
+ config->WriteString(L"AutoFillQuery",buf);
+ }
+ break;
+ case IDC_AUTOFILL_QUERY_EDIT:
+ {
+ char temp[1024] = {0};
+ GetDlgItemTextA(hwndDlg, IDC_AUTOFILL_QUERY_STRING, temp, sizeof(temp) - 1);
+ ml_editview meq = {hwndDlg,temp,0,-1};
+ meq.name = WASABI_API_LNGSTRING(IDS_AUTOFILL_QUERY);
+ if(!(int)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(LPARAM)&meq,ML_IPC_EDITVIEW)) return 0;
+ SetDlgItemTextA(hwndDlg, IDC_AUTOFILL_QUERY_STRING, meq.query);
+ config->WriteString(L"AutoFillQuery",AutoWide(meq.query));
+ }
+ break;
+ case IDC_BOOSTRATINGS:
+ if(IsDlgButtonChecked(hwndDlg,IDC_BOOSTRATINGS)) config->WriteString(L"AutoFillRatings",L"20:18:15:13:11:10");
+ else config->WriteString(L"AutoFillRatings",L"");
+ break;
+ case IDC_AUTOFILLALBUMS:
+ config->WriteInt(L"AlbumAutoFill",IsDlgButtonChecked(hwndDlg,IDC_AUTOFILLALBUMS)?1:0);
+ break;
+ }
+ break;
+ }
+
+ const int controls[] =
+ {
+ IDC_SPACESLIDER,
+ };
+ if (FALSE != WASABI_API_APP->DirectMouseWheel_ProcessDialogMessage(hwndDlg, uMsg, wParam, lParam, controls, ARRAYSIZE(controls)))
+ return TRUE;
+
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_transcode(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ Device *dev = 0;
+ if(lParam)
+ {
+ config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ dev = ((prefsParam*)lParam)->dev;
+
+ }
+ lParam = (LPARAM)TranscoderImp::ConfigureTranscoder(L"ml_pmp",plugin.hwndWinampParent,config, dev);
+ }
+ break;
+ }
+ return TranscoderImp::transcodeconfig_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+static INT_PTR CALLBACK config_dlgproc_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ int fieldsBits = (int)configDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits=-1;
+ // used to ensure that the filters will work irrespective of language in use
+ wchar_t * filters[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist":0,
+ (fieldsBits&SUPPORTS_ALBUM)?L"Album":0,
+ (fieldsBits&SUPPORTS_GENRE)?L"Genre":0,
+ (fieldsBits&SUPPORTS_YEAR)?L"Year":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist":0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?L"Publisher":0,
+ (fieldsBits&SUPPORTS_COMPOSER)?L"Composer":0,
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMART)?L"Album Art":0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?L"Mime":0,
+ (fieldsBits&SUPPORTS_DATEADDED)?L"Date Added":0,
+ };
+ // used for displayed items - localised crazyness, heh
+ int filters_idx[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST:0,
+ (fieldsBits&SUPPORTS_ALBUM)?IDS_ALBUM:0,
+ (fieldsBits&SUPPORTS_GENRE)?IDS_GENRE:0,
+ (fieldsBits&SUPPORTS_YEAR)?IDS_YEAR:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST:0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?IDS_PUBLISHER:0,
+ (fieldsBits&SUPPORTS_COMPOSER)?IDS_COMPOSER:0,
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMART)?IDS_ALBUM_ART:0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?IDS_MIME_TYPE:0,
+ (fieldsBits&SUPPORTS_DATEADDED)?IDS_DATE_ADDED:0,
+ };
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+ CheckRadioButton(hwndDlg, IDC_RADIO_FILTERS1, IDC_RADIO_FILTERS3, (IDC_RADIO_FILTERS1 + numFilters - 1));
+
+ CheckDlgButton(hwndDlg, IDC_REMEMBER_SEARCH, config->ReadInt(L"savefilter", 1));
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ for(int j=0; j<(sizeof(filters)/sizeof(wchar_t*)); j++) {
+ if(filters[j]) {
+ int a = SendDlgItemMessage(hwndDlg,id,CB_ADDSTRING,0,
+ (LPARAM)WASABI_API_LNGSTRINGW(filters_idx[j]));
+ SendDlgItemMessage(hwndDlg,id,CB_SETITEMDATA,a,(LPARAM)filters[j]);
+ }
+ }
+
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,numFilters));
+ for(int l = 0; l < (sizeof(filters)/sizeof(wchar_t*)); l++) {
+ wchar_t* x = (wchar_t*)SendDlgItemMessage(hwndDlg,id,CB_GETITEMDATA,l,0);
+ if(x && x != (wchar_t*)-1 && !_wcsicmp(filterStr,x)) {
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,(WPARAM)l,0);
+ break;
+ }
+ }
+ }
+
+ if(configDevice->videoView) CheckDlgButton(hwndDlg,IDC_CHECK_VIDEOVIEW,TRUE);
+ }
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case WM_USER:
+ {
+ BOOL full_enable = (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1) != BST_CHECKED);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER1), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER2), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER2), full_enable);
+
+ BOOL enable = (IsDlgButtonChecked(hwndDlg, IDC_RADIO_FILTERS3) == BST_CHECKED) && full_enable;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER3), enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER3), enable);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_RADIO_FILTERS3:
+ case IDC_RADIO_FILTERS2:
+ case IDC_RADIO_FILTERS1:
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case IDC_CHECK_VIDEOVIEW:
+ configDevice->SetVideoView(IsDlgButtonChecked(hwndDlg,IDC_CHECK_VIDEOVIEW));
+ break;
+ case IDC_REMEMBER_SEARCH:
+ config->WriteInt(L"savefilter", IsDlgButtonChecked(hwndDlg,IDC_REMEMBER_SEARCH));
+ config->WriteString(L"savedfilter", L"");
+ config->WriteString(L"savedrefinefilter", L"");
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ int update_needed = false;
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+
+ int new_numFilters = 1;
+ if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1)) new_numFilters = 1;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS2)) new_numFilters = 2;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS3)) new_numFilters = 3;
+
+ if (new_numFilters != numFilters) update_needed++;
+ config->WriteInt(L"media_numfilters",new_numFilters);
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ int sel = SendDlgItemMessage(hwndDlg, id, CB_GETCURSEL, 0, 0);
+ wchar_t * x = (wchar_t*)SendDlgItemMessage(hwndDlg, id, CB_GETITEMDATA, sel, 0);
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,new_numFilters));
+
+ if(x && x != (wchar_t*)-1)
+ {
+ config->WriteString(name,x);
+ update_needed += (wcscmp(filterStr, x));
+ }
+ }
+
+ // only refresh the view if it is one of ours (is a bit silly
+ // otherwise) and also not refresh unless there was a change
+ // with the configDevice check to cope with going elsewhere
+ if (update_needed && ((configDevice && configDevice == currentViewedDevice) || IsWindow(hwndMediaView))) {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK config_dlgproc_cloud_mediaview(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ if(lParam) config_tab_init(hwndDlg,((prefsParam*)lParam)->parent);
+ int fieldsBits = (int)configDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits=-1;
+ // used to ensure that the filters will work irrespective of language in use
+ wchar_t * filters[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist":0,
+ (fieldsBits&SUPPORTS_ALBUM)?L"Album":0,
+ (fieldsBits&SUPPORTS_GENRE)?L"Genre":0,
+ (fieldsBits&SUPPORTS_YEAR)?L"Year":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist":0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?L"Publisher":0,
+ (fieldsBits&SUPPORTS_COMPOSER)?L"Composer":0,
+ (fieldsBits&SUPPORTS_ARTIST)?L"Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?L"Album Artist Index":0,
+ (fieldsBits&SUPPORTS_ALBUMART)?L"Album Art":0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?L"Mime":0,
+ (fieldsBits&SUPPORTS_DATEADDED)?L"Date Added":0,
+ };
+ // used for displayed items - localised crazyness, heh
+ int filters_idx[] = {
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST:0,
+ (fieldsBits&SUPPORTS_ALBUM)?IDS_ALBUM:0,
+ (fieldsBits&SUPPORTS_GENRE)?IDS_GENRE:0,
+ (fieldsBits&SUPPORTS_YEAR)?IDS_YEAR:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST:0,
+ (fieldsBits&SUPPORTS_PUBLISHER)?IDS_PUBLISHER:0,
+ (fieldsBits&SUPPORTS_COMPOSER)?IDS_COMPOSER:0,
+ (fieldsBits&SUPPORTS_ARTIST)?IDS_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMARTIST)?IDS_ALBUM_ARTIST_INDEX:0,
+ (fieldsBits&SUPPORTS_ALBUMART)?IDS_ALBUM_ART:0,
+ (fieldsBits&SUPPORTS_MIMETYPE)?IDS_MIME_TYPE:0,
+ (fieldsBits&SUPPORTS_DATEADDED)?IDS_DATE_ADDED:0,
+ };
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+ CheckRadioButton(hwndDlg, IDC_RADIO_FILTERS1, IDC_RADIO_FILTERS3, (IDC_RADIO_FILTERS1 + numFilters - 1));
+
+ CheckDlgButton(hwndDlg, IDC_REMEMBER_SEARCH, config->ReadInt(L"savefilter", 1));
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ for(int j=0; j<(sizeof(filters)/sizeof(wchar_t*)); j++) {
+ if(filters[j]) {
+ int a = SendDlgItemMessage(hwndDlg,id,CB_ADDSTRING,0,
+ (LPARAM)WASABI_API_LNGSTRINGW(filters_idx[j]));
+ SendDlgItemMessage(hwndDlg,id,CB_SETITEMDATA,a,(LPARAM)filters[j]);
+ }
+ }
+
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,numFilters));
+ for(int l = 0; l < (sizeof(filters)/sizeof(wchar_t*)); l++) {
+ wchar_t* x = (wchar_t*)SendDlgItemMessage(hwndDlg,id,CB_GETITEMDATA,l,0);
+ if(x && x != (wchar_t*)-1 && !_wcsicmp(filterStr,x)) {
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,(WPARAM)l,0);
+ break;
+ }
+ }
+ }
+ }
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case WM_USER:
+ {
+ BOOL full_enable = (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1) != BST_CHECKED);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER1), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER2), full_enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER2), full_enable);
+
+ BOOL enable = (IsDlgButtonChecked(hwndDlg, IDC_RADIO_FILTERS3) == BST_CHECKED) && full_enable;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC_FILTER3), enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_COMBO_FILTER3), enable);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_RADIO_FILTERS3:
+ case IDC_RADIO_FILTERS2:
+ case IDC_RADIO_FILTERS1:
+ SendMessage(hwndDlg,WM_USER,0,0);
+ break;
+ case IDC_REMEMBER_SEARCH:
+ config->WriteInt(L"savefilter", IsDlgButtonChecked(hwndDlg,IDC_REMEMBER_SEARCH));
+ config->WriteString(L"savedfilter", L"");
+ config->WriteString(L"savedrefinefilter", L"");
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ int update_needed = false;
+ int numFilters = config->ReadInt(L"media_numfilters",2);
+
+ int new_numFilters = 1;
+ if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS1)) new_numFilters = 1;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS2)) new_numFilters = 2;
+ else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO_FILTERS3)) new_numFilters = 3;
+
+ if (new_numFilters != numFilters) update_needed++;
+ config->WriteInt(L"media_numfilters",new_numFilters);
+
+ for(int i=0; i<3; i++) {
+ int id = (i==0)?IDC_COMBO_FILTER1:((i==1)?IDC_COMBO_FILTER2:IDC_COMBO_FILTER3);
+ int sel = SendDlgItemMessage(hwndDlg, id, CB_GETCURSEL, 0, 0);
+ wchar_t * x = (wchar_t*)SendDlgItemMessage(hwndDlg, id, CB_GETITEMDATA, sel, 0);
+ wchar_t name[20] = {0};
+ StringCchPrintf(name,20,L"media_filter%d",i);
+
+ extern wchar_t *GetDefFilter(int i,int n);
+ SendDlgItemMessage(hwndDlg,id,CB_SETCURSEL,0,0);
+ wchar_t* filterStr = config->ReadString(name,GetDefFilter(i,new_numFilters));
+
+ if(x && x != (wchar_t*)-1)
+ {
+ config->WriteString(name,x);
+ update_needed += (wcscmp(filterStr, x));
+ }
+ }
+
+ // only refresh the view if it is one of ours (is a bit silly
+ // otherwise) and also not refresh unless there was a change
+ // with the configDevice check to cope with going elsewhere
+ if (update_needed && ((configDevice && configDevice == currentViewedDevice) || IsWindow(hwndMediaView))) {
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+extern void unloadPlugin(PMPDevicePlugin *devplugin, int n=-1);
+extern PMPDevicePlugin * loadPlugin(wchar_t * file);
+
+HRESULT RemovePMPPlugin(LPCWSTR file, HINSTANCE hDllInstance) {
+ if(!hDllInstance) {
+ SHFILEOPSTRUCT op = {0};
+ wchar_t srcFile[MAX_PATH+1], *end;
+ op.wFunc = FO_DELETE;
+ StringCchCopyExW(srcFile, MAX_PATH, file, &end, 0, 0);
+ if (end) end[1]=0; // double null terminate
+ op.pFrom = srcFile;
+ op.fFlags=FOF_NOCONFIRMATION|FOF_NOCONFIRMMKDIR|FOF_SIMPLEPROGRESS|FOF_NORECURSION|FOF_NOERRORUI|FOF_SILENT;
+ return (!SHFileOperation(&op)? S_OK : E_FAIL);
+ }
+ else {
+ wchar_t buf[1024],
+ *ini = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETINIFILEW);
+ GetModuleFileName(hDllInstance, buf, ARRAYSIZE(buf));
+ WritePrivateProfileString(L"winamp", L"remove_genplug", buf, ini);
+ WritePrivateProfileString(L"winamp", L"show_prefs", L"-1", ini);
+ PostMessage(plugin.hwndWinampParent, WM_USER, 0, IPC_RESTARTWINAMP);
+ return S_OK;
+ }
+}
+
+static bool pluginsLoaded;
+INT_PTR CALLBACK config_dlgproc_plugins(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ pluginsLoaded = false;
+ link_startsubclass(hwndDlg, IDC_PLUGINVERS);
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ if (NULL != listWindow)
+ {
+ RECT r = {0}, rc = {0};
+ GetWindowRect(listWindow, &r);
+ GetClientRect(listWindow, &r);
+ MapWindowPoints(listWindow, hwndDlg, (LPPOINT)&r, 2);
+ InflateRect(&r, 2, 2);
+ DestroyWindow(listWindow);
+ listWindow = CreateWindowEx(WS_EX_NOPARENTNOTIFY | WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL |
+ LVS_SHOWSELALWAYS | LVS_SORTASCENDING | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER,
+ r.left, r.top, r.right - r.left, r.bottom - r.top,
+ hwndDlg, (HMENU)IDC_PLUGINSLIST, NULL, NULL);
+ SetWindowPos(listWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
+ ListView_SetExtendedListViewStyleEx(listWindow, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ SendMessage(listWindow, WM_SETFONT, SendMessage(hwndDlg, WM_GETFONT, 0, 0), FALSE);
+
+ LVCOLUMNW lvc = {0};
+ ListView_InsertColumn(listWindow, 0, &lvc);
+ ListView_InsertColumn(listWindow, 1, &lvc);
+
+ wchar_t buf[1024] = {0}, fn[MAX_PATH] = {0};
+ for (int x = 0; x < m_plugins.GetSize(); x ++)
+ {
+ PMPDevicePlugin * devplugin=(PMPDevicePlugin *)m_plugins.Get(x);
+ if (devplugin)
+ {
+ GetModuleFileNameW(devplugin->hDllInstance, fn, MAX_PATH);
+ PathStripPath(fn);
+
+ LVITEMW lvi = {LVIF_TEXT | LVIF_PARAM, x, 0};
+ lvi.pszText = devplugin->description;
+ lvi.lParam = x;
+ lvi.iItem = ListView_InsertItem(listWindow, &lvi);
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ lvi.pszText = fn;
+ ListView_SetItem(listWindow, &lvi);
+ }
+ }
+
+ WIN32_FIND_DATA d = {0};
+ wchar_t *pluginPath = (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
+ wchar_t dirstr[MAX_PATH] = {0};
+ PathCombine(dirstr, pluginPath, L"PMP_*.DLL");
+ HANDLE h = FindFirstFile(dirstr, &d);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ PathCombine(dirstr, pluginPath, d.cFileName);
+ HMODULE b = LoadLibraryEx(dirstr, NULL, LOAD_LIBRARY_AS_DATAFILE);
+ int x = 0;
+ for (; b && (x != m_plugins.GetSize()); x ++)
+ {
+ PMPDevicePlugin *devplugin = (PMPDevicePlugin *)m_plugins.Get(x);
+ if (devplugin->hDllInstance == b)
+ {
+ break;
+ }
+ }
+
+ if (x == m_plugins.GetSize() || !b)
+ {
+ LVITEMW lvi = {LVIF_TEXT | LVIF_PARAM, x, 0};
+ lvi.pszText = d.cFileName;
+ lvi.lParam = -2;
+ lvi.iItem = ListView_InsertItem(listWindow, &lvi);
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ lvi.pszText = WASABI_API_LNGSTRINGW(IDS_NOT_LOADED);
+ ListView_SetItem(listWindow, &lvi);
+ }
+ FreeLibrary(b);
+ }
+ while (FindNextFile(h, &d));
+ FindClose(h);
+ }
+
+ GetClientRect(listWindow, &r);
+ ListView_SetColumnWidth(listWindow, 1, LVSCW_AUTOSIZE);
+ ListView_SetColumnWidth(listWindow, 0, (r.right - r.left) - ListView_GetColumnWidth(listWindow, 1));
+
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, TRUE);
+
+ pluginsLoaded = true;
+ }
+ }
+ break;
+ }
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR p = (LPNMHDR)lParam;
+ if (p->idFrom == IDC_PLUGINSLIST)
+ {
+ if (p->code == LVN_ITEMCHANGED)
+ {
+ LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
+ LVITEM lvi = {LVIF_PARAM, pnmv->iItem};
+ if (ListView_GetItem(p->hwndFrom, &lvi) && (pnmv->uNewState & LVIS_SELECTED))
+ {
+ int loaded = (lvi.lParam != -2);
+ if (loaded)
+ {
+ PMPDevicePlugin *devplugin;
+ if (lvi.lParam >= 0 && lvi.lParam < m_plugins.GetSize() &&
+ (devplugin = (PMPDevicePlugin *)m_plugins.Get(lvi.lParam)))
+ {
+ // enables / disables the config button as applicable instead of the
+ // "This plug-in has no configuration implemented" message (opt-in)
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN), (!devplugin->MessageProc(PMP_NO_CONFIG, 0, 0, 0)));
+ }
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN), 0);
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_UNINSTALLPLUGIN), 1);
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN), 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_UNINSTALLPLUGIN), 0);
+ }
+ }
+ else if (p->code == NM_DBLCLK)
+ {
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_CONFIGPLUGIN, 0), (LPARAM)GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN));
+ }
+ }
+ else if (p->code == HDN_ITEMCHANGINGW)
+ {
+ if (pluginsLoaded)
+ {
+#if defined(_WIN64)
+ SetWindowLong(hwndDlg, DWLP_MSGRESULT, TRUE);
+#else
+ SetWindowLong(hwndDlg, DWL_MSGRESULT, TRUE);
+#endif
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ case WM_DESTROY:
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ if (IsWindow(listWindow) && (NULL != WASABI_API_APP))
+ WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(listWindow, FALSE);
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_CONFIGPLUGIN:
+ {
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_CONFIGPLUGIN)))
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ LVITEM lvi = {LVIF_PARAM, ListView_GetSelectionMark(listWindow)};
+ if (ListView_GetItem(listWindow, &lvi))
+ {
+ PMPDevicePlugin *devplugin;
+ if(lvi.lParam >= 0 && lvi.lParam < m_plugins.GetSize() && (devplugin=(PMPDevicePlugin *)m_plugins.Get(lvi.lParam)))
+ {
+ if(devplugin->MessageProc(PMP_CONFIG,(intptr_t)hwndDlg,0,0) == 0)
+ {
+ wchar_t titleStr[64] = {0};
+ MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_PLUGIN_HAS_NO_CONFIG_IMPLEMENTED),
+ WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_PLUGINS,titleStr,64),0);
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case IDC_UNINSTALLPLUGIN:
+ {
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_UNINSTALLPLUGIN)))
+ {
+ HWND listWindow = GetDlgItem(hwndDlg, IDC_PLUGINSLIST);
+ int which_sel = ListView_GetSelectionMark(listWindow);
+ LVITEM lvi = {LVIF_PARAM, which_sel};
+ if (ListView_GetItem(listWindow, &lvi))
+ {
+ PMPDevicePlugin *devplugin = 0;
+ wchar_t titleStr[32] = {0};
+ int msgBox = MessageBox(hwndDlg, WASABI_API_LNGSTRINGW(IDS_PERMANENTLY_UNINSTALL_THIS_PLUGIN),
+ WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION, titleStr, 32), MB_YESNO | MB_ICONEXCLAMATION);
+
+ if (lvi.lParam >= 0 && lvi.lParam <= m_plugins.GetSize() && (devplugin=(PMPDevicePlugin *)m_plugins.Get(lvi.lParam)) && msgBox == IDYES)
+ {
+ wchar_t buf[1024] = {0};
+ GetModuleFileName(devplugin->hDllInstance,buf,sizeof(buf)/sizeof(wchar_t));
+ int ret = PMP_PLUGIN_UNINSTALL_NOW;
+ int (*pr)(HINSTANCE hDllInst, HWND hwndDlg, int param);
+ *(void**)&pr = (void*)GetProcAddress(devplugin->hDllInstance,"winampUninstallPlugin");
+ if(pr) ret = pr(devplugin->hDllInstance,hwndDlg,0);
+ if(pr && ret == PMP_PLUGIN_UNINSTALL_NOW) { // dynamic unload
+ ListView_DeleteItem(listWindow, lvi.lParam);
+ unloadPlugin(devplugin,lvi.lParam);
+
+ // removing the plugin (bit convoluted to hopefully not cause crashes with dynamic removal)
+ // try to use the elevator to do this
+ IFileTypeRegistrar *registrar = (IFileTypeRegistrar*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_FILEREGISTRAR_OBJECT);
+ if(registrar && (registrar != (IFileTypeRegistrar*)1)) {
+ if(registrar->DeleteItem(buf) != S_OK) {
+ // we don't always free by default as it can cause some crashes
+ FreeLibrary(devplugin->hDllInstance);
+ if(registrar->DeleteItem(buf) != S_OK) {
+ // all gone wrong so non-dynamic unload (restart winamp)
+ RemovePMPPlugin(buf, devplugin->hDllInstance);
+ }
+ }
+ registrar->Release();
+ }
+ // otherwise revert to a standard method
+ else {
+ if(RemovePMPPlugin(buf, 0) != S_OK){
+ // we don't always free by default as it can cause some crashes
+ FreeLibrary(devplugin->hDllInstance);
+ if(RemovePMPPlugin(buf, 0) != S_OK) {
+ // all gone wrong so non-dynamic unload (restart winamp)
+ RemovePMPPlugin(buf, devplugin->hDllInstance);
+ }
+ }
+ }
+ }
+ else if(!pr)
+ { // non-dynamic unload (restart winamp)
+ RemovePMPPlugin(buf,devplugin->hDllInstance);
+ }
+ }
+ // will cope with not loaded plug-ins so we can still remove them, etc
+ else if (lvi.lParam == -2 && msgBox == IDYES)
+ {
+ wchar_t buf[1024] = {0}, base[1024] = {0};
+ GetModuleFileName(plugin.hDllInstance,base,sizeof(base)/sizeof(wchar_t));
+
+ LVITEM lvi = {LVIF_TEXT, which_sel};
+ lvi.pszText = buf;
+ lvi.cchTextMax = ARRAYSIZE(buf);
+ ListView_GetItem(listWindow, &lvi);
+
+ wchar_t *p = wcschr(buf, L'.');
+ if (p && *p == L'.')
+ {
+ p += 4;
+ *p = 0;
+ PathRemoveFileSpec(base);
+ PathAppend(base, buf);
+ }
+
+ // try to use the elevator to do this
+ IFileTypeRegistrar *registrar = (IFileTypeRegistrar*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_FILEREGISTRAR_OBJECT);
+ if(registrar && (registrar != (IFileTypeRegistrar*)1)) {
+ if(registrar->DeleteItem(base) != S_OK){
+ RemovePMPPlugin(base, 0);
+ }
+ else
+ ListView_DeleteItem(listWindow, which_sel);
+ registrar->Release();
+ }
+ // otherwise revert to a standard method
+ else {
+ RemovePMPPlugin(base, 0);
+ }
+ }
+
+ // resets the focus to the listbox so it'll keep ui response working
+ SetFocus(GetDlgItem(hwndDlg, IDC_PLUGINSLIST));
+ }
+ }
+ }
+ break;
+
+ case IDC_PLUGINVERS:
+ myOpenURLWithFallback(hwndDlg, L"http://www.google.com/search?q=Winamp+Portable+Plugins",L"http://www.google.com/search?q=Winamp+Portable+Plugins");
+ break;
+ }
+ break;
+ }
+ link_handledraw(hwndDlg,uMsg,wParam,lParam);
+ return 0;
+}
+
+void myOpenURLWithFallback(HWND hwnd, wchar_t *loc, wchar_t *fallbackLoc)
+{
+ bool override=false;
+ if (loc)
+ {
+ WASABI_API_SYSCB->syscb_issueCallback(SysCallback::BROWSER, BrowserCallback::ONOPENURL, reinterpret_cast<intptr_t>(loc), reinterpret_cast<intptr_t>(&override));
+ }
+ if (!override && fallbackLoc)
+ ShellExecuteW(hwnd, L"open", fallbackLoc, NULL, NULL, SW_SHOWNORMAL);
+}
+
+static HCURSOR link_hand_cursor;
+LRESULT link_handlecursor(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT ret = CallWindowProcW((WNDPROC)GetPropW(hwndDlg, L"link_proc"), hwndDlg, uMsg, wParam, lParam);
+ // override the normal cursor behaviour so we have a hand to show it is a link
+ if(uMsg == WM_SETCURSOR)
+ {
+ if((HWND)wParam == hwndDlg)
+ {
+ if(!link_hand_cursor)
+ {
+ link_hand_cursor = LoadCursor(NULL, IDC_HAND);
+ }
+ SetCursor(link_hand_cursor);
+ return TRUE;
+ }
+ }
+
+ return ret;
+}
+
+void link_startsubclass(HWND hwndDlg, UINT id){
+HWND ctrl = GetDlgItem(hwndDlg, id);
+ SetPropW(ctrl, L"link_proc",
+ (HANDLE)SetWindowLongPtrW(ctrl, GWLP_WNDPROC, (LONG_PTR)link_handlecursor));
+}
+
+static void link_handledraw(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == WM_DRAWITEM)
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON)
+ {
+ wchar_t wt[123] = {0};
+ int y;
+ RECT r;
+ HPEN hPen, hOldPen;
+ GetDlgItemText(hwndDlg, wParam, wt, sizeof(wt)/sizeof(wchar_t));
+
+ // draw text
+ SetTextColor(di->hDC, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ r = di->rcItem;
+ r.left += 2;
+ DrawText(di->hDC, wt, -1, &r, DT_VCENTER | DT_SINGLELINE);
+
+ memset(&r, 0, sizeof(r));
+ DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_CALCRECT);
+
+ // draw underline
+ y = di->rcItem.bottom - ((di->rcItem.bottom - di->rcItem.top) - (r.bottom - r.top)) / 2 - 1;
+ hPen = CreatePen(PS_SOLID, 0, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220));
+ hOldPen = (HPEN) SelectObject(di->hDC, hPen);
+ MoveToEx(di->hDC, di->rcItem.left + 2, y, NULL);
+ LineTo(di->hDC, di->rcItem.right + 2 - ((di->rcItem.right - di->rcItem.left) - (r.right - r.left)), y);
+ SelectObject(di->hDC, hOldPen);
+ DeleteObject(hPen);
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/replaceVars.cpp b/Src/Plugins/Library/ml_pmp/replaceVars.cpp
new file mode 100644
index 00000000..325442fb
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/replaceVars.cpp
@@ -0,0 +1,213 @@
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include "pmp.h"
+#include <strsafe.h>
+#include "api__ml_pmp.h"
+#include "resource1.h"
+extern winampMediaLibraryPlugin plugin;
+
+wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song);
+BOOL RecursiveCreateDirectory(wchar_t* buf1);
+void doFormatFileName(wchar_t out[MAX_PATH], wchar_t *fmt, int trackno, wchar_t *artist, wchar_t *album, wchar_t *title, wchar_t *genre, wchar_t *year, wchar_t *trackartist);
+
+static void removebadchars(wchar_t *s) {
+ while (s && *s)
+ {
+ if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|')
+ *s = L'_';
+ s = CharNextW(s);
+ }
+}
+
+// Skip_Root: removes drive/host/share name in a path
+wchar_t * Skip_Root(wchar_t *path) {
+ wchar_t *p = CharNext(path);
+ wchar_t *p2 = CharNext(p);
+ if (*path && *p == L':' && *p2 == L'\\') return CharNext(p2);
+ else if (*path == L'\\' && *p == L'\\') {
+ // skip host and share name
+ int x = 2;
+ while (x--) {
+ while (p2 && *p2 != L'\\') {
+ if (!p2 || !*p2) return NULL;
+ p2 = CharNext(p2);
+ }
+ p2 = CharNext(p2);
+ }
+ return p2;
+ }
+ return NULL;
+}
+
+// RecursiveCreateDirectory: creates all non-existent folders in a path
+BOOL RecursiveCreateDirectory(wchar_t* buf1) {
+ wchar_t *p=buf1;
+ int errors = 0;
+ if (*p) {
+ p = Skip_Root(buf1);
+ if (!p) return true ;
+
+ wchar_t ch='c';
+ while (ch) {
+ while (p && *p != '\\' && *p) p=CharNext(p);
+ ch=*p;
+ if (p) *p=0;
+ int pp = wcslen(buf1)-1;
+
+ while(buf1[pp] == '.' ||
+ buf1[pp] == ' ' ||
+ (buf1[pp] == '\\' && (buf1[pp-1] == '.' || buf1[pp-1] == ' ' || buf1[pp-1] == '/'))
+ || buf1[pp] == '/' && buf1)
+ {
+ if(buf1[pp] == '\\')
+ {
+ buf1[pp-1] = '_';
+ pp -= 2;
+ }else{
+ buf1[pp] = '_';
+ pp--;
+ }
+ }
+
+ HANDLE h;
+ WIN32_FIND_DATA fd;
+ // Avoid a "There is no disk in the drive" error box on empty removable drives
+ UINT prevErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ h = FindFirstFile(buf1,&fd);
+ SetErrorMode(prevErrorMode);
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ if (!CreateDirectory(buf1,NULL)) errors++;
+ } else {
+ FindClose(h);
+ if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) errors++;
+ }
+ *p++ = ch;
+ }
+ }
+ return errors != 0;
+}
+
+// FixReplacementVars: replaces <Artist>, <Title>, <Album>, and #, ##, ##, with appropriate data
+// DOES NOT add a file extention!!
+wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song)
+{
+ wchar_t artist[256]=L"",album[256]=L"",albumartist[256]=L"",title[256]=L"",genre[256]=L"",year[10]=L"",dest[MAX_PATH]=L"";
+ dev->getTrackArtist(song,artist,256);
+ dev->getTrackAlbum(song,album,256);
+ dev->getTrackAlbumArtist(song,albumartist,256);
+ if(!albumartist[0]) lstrcpyn(albumartist,artist,256);
+ dev->getTrackTitle(song,title,256);
+ dev->getTrackGenre(song,genre,256);
+ int y = dev->getTrackYear(song);
+ if(y>0) StringCchPrintfW(year,10,L"%d",y);
+ wchar_t unknown[32] = {0}, unknownartist[32] = {0}, unknownalbum[32] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN,unknown,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_ARTIST,unknownartist,32);
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKKNOWN_ALBUM,unknownalbum,32);
+ doFormatFileName(dest,str,
+ dev->getTrackTrackNum(song),
+ *albumartist ? albumartist : unknownartist,
+ *album ? album : unknownalbum,
+ *title ? title : L"0",
+ *genre ? genre : unknown,
+ *year ? year : unknown,
+ *artist ? artist : unknown
+ );
+ bool c = (str[1]==':');
+ lstrcpyn(str,dest,str_size);
+ if(c) str[1]=L':';
+ return str;
+}
+
+static void CleanDirectory(wchar_t *str)
+{
+ if (!str)
+ return ;
+ int l = wcslen(str);
+
+ while (l--)
+ {
+ if (str[l] == L' '
+ || str[l] == L'.')
+ str[l] = 0;
+ else
+ break;
+ }
+}
+
+void doFormatFileName(wchar_t out[MAX_PATH], wchar_t *fmt, int trackno, wchar_t *artist, wchar_t *album, wchar_t *title, wchar_t *genre, wchar_t *year, wchar_t *trackartist)
+{
+ CleanDirectory(artist);
+ CleanDirectory(album);
+ CleanDirectory(title);
+ CleanDirectory(genre);
+ CleanDirectory(year);
+ while (fmt && *fmt)
+ {
+ int whichstr = 0;
+ if (*fmt == L'#' && trackno != 0xdeadbeef)
+ {
+ int cnt = 0;
+ while (fmt && *fmt == L'#')
+ {
+ fmt++;
+ cnt++;
+ }
+ if (cnt > 8) cnt = 8;
+ wchar_t specstr[32] = {0};
+ StringCchPrintf(specstr, 32, L"%%%02dd", cnt);
+ wchar_t tracknostr[32] = {0};
+ StringCchPrintf(tracknostr, 32, specstr, trackno);
+ StringCchCat(out, MAX_PATH, tracknostr);
+ }
+ else if (artist && !_wcsnicmp(fmt, L"<artist>", 8)) whichstr = 1;
+ else if (album && !_wcsnicmp(fmt, L"<album>", 7)) whichstr = 2;
+ else if (title && !_wcsnicmp(fmt, L"<title>", 7)) whichstr = 3;
+ else if (genre && !_wcsnicmp(fmt, L"<genre>", 7)) whichstr = 4;
+ else if (year && !_wcsnicmp(fmt, L"<year>", 6)) whichstr = 5;
+ else if (year && !_wcsnicmp(fmt, L"<trackartist>", 13)) whichstr=6;
+ else
+ {
+ wchar_t p[2] = {0};
+ p[0] = *fmt++;
+ if (p[0] == L'?' || p[0] == L'*' || p[0] == L'|') p[0] = L'_';
+ else if (p[0] == L':') p[0] = L'-';
+ else if (p[0] == L'\"') p[0] = L'\'';
+ else if (p[0] == L'<') p[0] = L'(';
+ else if (p[0] == L'>') p[0] = L')';
+ p[1] = 0;
+ StringCchCat(out, MAX_PATH, p);
+ }
+ if (whichstr > 0)
+ {
+ int islow = IsCharLowerW(fmt[1]) && IsCharLowerW(fmt[2]);
+ int ishi = IsCharUpperW(fmt[1]) && IsCharUpperW(fmt[2]);
+ wchar_t *src;
+ if (whichstr == 1) { src = artist; fmt += 8; }
+ else if (whichstr == 2) { src = album; fmt += 7; }
+ else if (whichstr == 3) { src = title; fmt += 7; }
+ else if (whichstr == 4) { src = genre; fmt += 7; }
+ else if (whichstr == 5) { src = year; fmt += 6; }
+ else if (whichstr == 6) { src= trackartist; fmt+=13; }
+ else break;
+
+ while (src && *src)
+ {
+ wchar_t p[2] = {src[0], 0};
+ if (ishi) CharUpperBuffW(p, 1);
+ else if (islow) CharLowerBuffW(p, 1);
+
+ if (p[0] == L'?' || p[0] == L'*' || p[0] == L'|') p[0] = L'_';
+ else if (p[0] == L'/' || p[0] == L'\\' || p[0] == L':') p[0] = L'-';
+ else if (p[0] == L'\"') p[0] = L'\'';
+ else if (p[0] == L'<') p[0] = L'(';
+ else if (p[0] == L'>') p[0] = L')';
+
+ src++;
+ p[1] = 0;
+ StringCchCat(out, MAX_PATH, p);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/resource1.h b/Src/Plugins/Library/ml_pmp/resource1.h
new file mode 100644
index 00000000..54dbd4ac
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resource1.h
@@ -0,0 +1,475 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_pmp.rc
+//
+#define IDS_VIDEO 1
+#define IDS_RENAME_DEVICE 2
+#define IDS_RENAME_PLAYLIST 3
+#define IDS_STRING3 3
+#define IDS_LAST_CHANGED 3
+#define IDS_CREATE_PLAYLIST 4
+#define IDCANCEL2 5
+#define IDS_DEVICE_OUT_OF_SPACE 5
+#define IDOK2 6
+#define IDS_INCOMPATABLE_FORMAT_NO_TX 6
+#define IDS_ERROR 7
+#define IDS_PHYSICALLY_REMOVE_X_TRACKS 8
+#define IDS_ARE_YOU_SURE 9
+#define IDD_VIEW_PMP_DEVICES 9
+#define IDS_SYNC_IS_IN_PROGRESS 10
+#define IDS_CANNOT_EJECT 11
+#define IDS_DELETING_TRACKS 12
+#define IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING 13
+#define IDS_SORRY 14
+#define IDS_NATS_DEVICE_MAYBE_FULL 15
+#define IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT 16
+#define IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT 17
+#define IDS_WARNING 18
+#define IDS_SONGS_TO_BE_DELETED 19
+#define IDS_SONGS_TO_BE_COPIED 20
+#define IDS_SONGS_NOT_IN_MEDIA_LIBRARY 21
+#define IDS_WAITING 22
+#define IDS_PLAYLIST_SYNCRONIZATION 23
+#define IDS_OTHER 24
+#define IDS_WORKING 25
+#define IDS_DONE 26
+#define IDS_NOTHING_TO_SYNC_UP_TO_DATE 27
+#define IDS_SYNC 28
+#define IDS_X_SONGS_WILL_BE_TRANSFERRED_TO_THE_DEVICE 29
+#define IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE 30
+#define IDS_NOT_ENOUGH_SPACE 31
+#define IDS_THIS_WILL_ADD_X_SONGS_AND_DELETE_X_SONGS 32
+#define IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK 33
+#define IDS_UNSUPPORTED 34
+#define IDS_PREFS_SYNC 35
+#define IDS_PREFS_AUTOFILL 36
+#define IDS_PREFS_TRANSCODING 37
+#define IDS_PREFS_VIEW 38
+#define IDS_EXAMPLE_FORMATTING_STRING 39
+#define IDS_CHOOSE_A_FOLDER 40
+#define IDS_COPIED_FILE_FORMAT_INFO 41
+#define IDS_COPIED_FILE_FORMAT_HELP 42
+#define IDS_ALL 43
+#define IDS_AIM_TO_AUTOFILL_DEVICE 44
+#define IDS_ARTIST 45
+#define IDS_ALBUM 46
+#define IDS_GENRE 47
+#define IDS_YEAR 48
+#define IDS_ALBUM_ARTIST 49
+#define IDS_PUBLISHER 50
+#define IDS_COMPOSER 51
+#define IDS_ARTIST_INDEX 52
+#define IDS_ALBUM_ARTIST_INDEX 53
+#define IDS_PLUGIN_HAS_NO_CONFIG_IMPLEMENTED 54
+#define IDS_DEVICE_PLUGINS 55
+#define IDS_PERMANENTLY_UNINSTALL_THIS_PLUGIN 56
+#define IDS_CONFIRMATION 57
+#define IDS_CLICK_OK_TO_CONTINUE 58
+#define IDS_RELOADING_PLUGIN 59
+#define IDS_UNKNOWN_ARTIST 60
+#define IDS_UNKKNOWN_ALBUM 61
+#define IDS_UNKNOWN 62
+#define IDS_TRANSFER 63
+#define IDS_COPY_TO_LIBRARY 64
+#define IDS_INVALID_PATH 65
+#define IDS_STARTING_TRANSFER 66
+#define IDS_DUPLICATE 67
+#define IDS_TITLE 73
+#define IDS_LENGTH 74
+#define IDS_TRACK_NUMBER 75
+#define IDS_DISC 76
+#define IDS_BITRATE 77
+#define IDS_SIZE 78
+#define IDS_PLAY_COUNT 79
+#define IDS_RATING 80
+#define IDS_LAST_PLAYED 81
+#define IDS_TRACKS 82
+#define IDS_ARTISTS 83
+#define IDS_NO_ARTIST 84
+#define IDS_ALBUMS 85
+#define IDS_GENRES 86
+#define IDS_ARTIST_INDEXES 87
+#define IDS_NO_ALBUM_ARTIST 88
+#define IDS_ALBUM_ARTIST_INDEXES 89
+#define IDS_NO_YEAR 90
+#define IDS_YEARS 91
+#define IDS_ALBUM_ARTISTS 92
+#define IDS_NO_PUBLISHER 93
+#define IDS_PUBLISHERS 94
+#define IDS_NO_COMPOSER 95
+#define IDS_COMPOSERS 96
+#define IDS_GHK_SYNC_PORTABLE_DEVICE 97
+#define IDS_GHK_AUTOFILL_PORTABLE_DEVICE 98
+#define IDS_GHK_EJECT_PORTABLE_DEVICE 99
+#define IDS_PORTABLES 100
+#define IDS_PORTABLES_PERCENT 101
+#define IDD_VIEW_PMP_PLAYLIST 102
+#define IDS_LOADING 102
+#define IDS_NAME 103
+#define IDD_VIEW_PMP_ARTISTALBUM 104
+#define IDS_CAPACITY_FREE 104
+#define IDR_CONTEXTMENUS 105
+#define IDS_TYPE 105
+#define IDS_STATUS 106
+#define IDD_EDIT_INFO 107
+#define IDS_DEVICE 107
+#define IDD_PROGRESS 108
+#define IDS_TRANFERS_PERCENT_REMAINING 108
+#define IDD_CONFIG 109
+#define IDS_RESUME 109
+#define IDS_PAUSE 110
+#define IDS_SETTING_METADATA 111
+#define IDS_TRANSFER_PERCENT 112
+#define IDS_ALL_X_WITHOUT_X 113
+#define IDS_ALL_X 114
+#define IDS_NO_ALBUM 115
+#define IDS_NO_GENRE 116
+#define IDS_TRACK 117
+#define IDS_X_ITEMS_X_AVAILABLE 118
+#define IDS_AUTOFILL_QUERY 119
+#define IDD_GETTINGMETADATA 120
+#define IDS_SYNC_QUERY 120
+#define IDD_FIND 121
+#define IDS_ALBUM_ART 121
+#define IDS_NO_IMAGE 122
+#define IDS_AVAILABLE 123
+#define IDS_OTHER2 124
+#define IDD_SYNC 125
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 125
+#define IDS_AUDIO_BUTTON_TT1 126
+#define IDS_AUDIO_BUTTON_TT2 127
+#define IDD_CONFIG_TRANSCODE 128
+#define IDS_AUDIO_BUTTON_TT3 128
+#define IDD_CONFIG_PLUGINS 129
+#define IDS_IMAGE_FILES 129
+#define IDD_CONFIG_TRANSCODING_ADVANCED 130
+#define IDS_JPEG_FILE 130
+#define IDD_AUTOFILL 131
+#define IDS_PNG_FILE 131
+#define IDS_GIF_FILE 132
+#define IDS_BMP_FILE 133
+#define IDD_CONFIG_AUTOFILL 134
+#define IDS_DELETE_PLAYLIST 134
+#define IDD_CONFIG_GLOBAL 135
+#define IDS_NEW_PLAYLIST 135
+#define IDD_VIEW_PMP_VIDEO 141
+#define IDD_CONFIG_MEDIAVIEW 142
+#define IDR_TOOL_VIEWMODE_ICON 144
+#define IDR_TOOL_ALBUMART_ICON 145
+#define IDR_TOOL_COLUMNS_ICON 146
+#define IDR_IMAGE_NOTFOUND 147
+#define IDR_DEVICE_ICON 148
+#define IDR_PLAYLIST_ICON 149
+#define IDR_VIDEO_ICON 150
+#define IDB_USB 151
+#define IDS_TRANSFERRING 151
+#define IDB_XFER_QUEUE_16 152
+#define IDS_DEVICE_CMD_PLAYLIST_NEW 152
+#define IDS_DEVICE_CMD_PLAYLIST_CREATE 152
+#define IDB_XFER_QUEUE_16_1 153
+#define IDS_DEVICE_CMD_AUTOFILL 153
+#define IDB_XFER_QUEUE_16_2 154
+#define IDS_DEVICE_CMD_EJECT 154
+#define IDB_XFER_QUEUE_16_3 155
+#define IDS_TRANSFERS 155
+#define IDS_TRANSFERS_PERCENT 156
+#define IDS_DEVICE_CMD_PREFERENCES 158
+#define IDS_DEVICE_CMD_VIEW_OPEN 159
+#define IDS_DEVICE_CMD_VIEW_OPEN_DESC 160
+#define IDS_DEVICE_CMD_SYNC_DESC 161
+#define IDS_DEVICE_CMD_EJECT_DESC 162
+#define IDS_DEVICE_CMD_RENAME_DESC 163
+#define IDS_DEVICE_CMD_AUTOFILL_DESC 164
+#define IDS_DEVICE_CMD_PLAYLIST_NEW_DESC 165
+#define IDS_DEVICE_CMD_PLAYLIST_CREATE_DESC 165
+#define IDS_DEVICE_CMD_PREFERENCES_DESC 166
+#define IDS_DEVICE_CMD_RENAME 167
+#define IDS_DEVICE_CMD_SYNC 168
+#define IDS_PORTABLE_DEVICE_TYPE 170
+#define IDS_DEVICE_CONNECTION_USB 171
+#define IDS_PODCAST_SYNC 172
+#define IDS_TRANSFERRING_DESC 173
+#define IDS_UNKNOWN_TRACK 174
+#define IDS_NUMBER 175
+#define IDS_DELETE_PLAYLIST_TITLE 176
+#define IDS_DEVICE_LOWERCASE 177
+#define IDS_KBPS 178
+#define IDS_SENDTO_DEVICES 180
+#define IDS_STRING181 181
+#define IDS_SENDTO_CLOUD 182
+#define IDS_MIME_TYPE 183
+#define IDS_ALREADY_UPLOADED 184
+#define IDS_SOURCE 185
+#define IDS_DESTINATION 186
+#define IDD_VIEW_CLOUD_ARTISTALBUM 187
+#define IDD_CLOUD_SYNC 188
+#define IDD_CONFIG_CLOUD_MEDIAVIEW 190
+#define IDD_VIEW_CLOUD_SIMPLE 191
+#define IDD_CUSTCOLUMNS 247
+#define IDD_VIEW_PMP_QUEUE 248
+#define IDD_CONFIG_PODCAST_SYNC 249
+#define IDD_CONFIG_SYNC 250
+#define IDS_DEVICE_CMD_REMOVE 251
+#define IDS_DEVICE_CMD_REMOVE_DESC 252
+#define IDD_VIEW_CLOUD_QUEUE 253
+#define IDS_LOCAL_MACHINE 253
+#define IDS_UPLOAD_CANCELLED 254
+#define IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME 255
+#define IDS_CLOUD_SOURCES 256
+#define IDS_CLOUD 257
+#define IDS_X_ITEMS_X_AVAILABLE_SLIM 258
+#define IDS_CLOUD_REMOVE_X_TRACKS 259
+#define IDS_DELETE 260
+#define IDS_REMOVE 261
+#define IDS_REMOVING_TRACKS 262
+#define IDS_DEVICE_CMD_TRANSFER 263
+#define IDS_DEVICE_CMD_TRANSFER_DESC 264
+#define IDR_TRANSFER_SMALL_ICON 265
+#define IDS_SOURCE_FILE 266
+#define IDB_TREEITEM_CLOUD 267
+#define IDS_UPLOADED 267
+#define IDB_TREEITEM_CLOUD_ADD_SOURCE 268
+#define IDS_PLAYLISTS 268
+#define IDS_SONGS 269
+#define IDS_CLOUD_TX_X_TO_Y 270
+#define IDS_CLOUD_TX_HAVE_SELECTED_X 271
+#define IDS_CLOUD_TX_OVER_LIMIT 272
+#define IDS_CLOUD_TX_NO_SEL 273
+#define IDS_CLOUD_LOCAL_LIBRARY 274
+#define IDS_CLOUD_SYNC_ALL_SONGS 275
+#define IDS_CLOUD_SYNC_ALL_PL 276
+#define IDS_CLOUD_SYNC_SEL_SONGS 277
+#define IDS_CLOUD_SYNC_SEL_PL 278
+#define IDS_CLOUD_SYNC_NONSEL_SONGS 279
+#define IDS_CLOUD_SYNC_NONSEL_PL 280
+#define IDS_SEND_TO_PL 281
+#define IDS_DEVICE_CMD_HIDE 282
+#define IDS_ADD_SOURCE 283
+#define IDS_TRACK_AVAILABLE 284
+#define IDS_UPLOAD_TO_SOURCE 285
+#define IDS_CONFIRM_QUIT 286
+#define IDS_CANCEL_TRANSFERS_AND_QUIT 287
+#define IDS_CLOUD_SYNC_PL_EMPTY 288
+#define IDS_CLOUD_SYNC_PL_SOME 289
+#define IDS_ALL_TRACKS_PLAYABLE 290
+#define IDS_NO_TRACKS_PLAYABLE 291
+#define IDS_SOME_TRACKS_PLAYABLE 292
+#define IDS_ALL_TRACKS_PLAYABLE_HERE 293
+#define IDB_TREEITEM_CLOUD_ADD_BYOS 294
+#define IDS_DATE_ADDED 294
+#define IDS_CLIENT_TYPE 295
+#define IDR_ACCELERATORS 295
+#define IDS_NOT_LOADED 296
+#define IDC_BUTTON_CUSTOM 1000
+#define IDC_PL_LIST 1000
+#define IDC_LIST1 1000
+#define IDC_LIST2 1001
+#define IDC_LIST_TRANSFERS 1002
+#define IDC_EDIT_ARTIST 1002
+#define IDC_PC_LIST 1002
+#define IDC_LIST_DEVICES 1003
+#define IDC_REFINE_TEXT 1004
+#define IDC_CHECK_ARTIST 1004
+#define IDC_CHECK_TITLE 1005
+#define IDC_CHECK_ALBUM 1006
+#define IDC_TQ_STATIC 1006
+#define IDC_QUICKSEARCH 1007
+#define IDC_BUTTON_CLEARFINISHED 1007
+#define IDC_REFINE 1008
+#define IDC_CHECK_TRACK 1008
+#define IDC_BUTTON_REMOVESELECTED 1008
+#define IDC_CHECK_GENRE 1009
+#define IDC_PROGRESS 1009
+#define IDC_DESTPATH 1009
+#define IDC_BUTTON_RETRYSELECTED 1009
+#define IDC_CHECK_DISC 1010
+#define IDC_BUTTON_PAUSETRANSFERS 1010
+#define IDC_ABORT 1010
+#define IDC_BUTTON1 1010
+#define IDC_CHECK_YEAR 1011
+#define IDC_BOOSTRATINGS 1011
+#define IDC_FILENAMEFMT 1011
+#define IDC_BUTTON_VIEWMODE 1011
+#define IDC_BUTTON_COLUMNS 1012
+#define IDC_EDIT_TITLE 1013
+#define IDC_PLUGINSLIST 1013
+#define IDC_EDIT_ALBUM 1014
+#define IDC_CONFIGPLUGIN 1014
+#define IDC_UNINSTALLPLUGIN 1015
+#define IDC_EDIT_TRACK 1016
+#define IDC_PLUGINVERS 1016
+#define IDC_EDIT_DISC 1017
+#define IDC_METADATAPROGRESS 1018
+#define IDC_CHECK_ALBUMARTIST 1018
+#define IDC_EDIT_GENRE 1019
+#define IDC_EDIT 1019
+#define IDC_FMTOUT 1019
+#define IDC_CAPTION 1020
+#define IDC_ENCFORMAT 1020
+#define IDC_EDIT_ALBUMARTIST 1020
+#define IDC_EDIT_YEAR 1021
+#define IDC_TRUESYNC_LEAVE 1021
+#define IDC_ENC_CONFIG 1021
+#define IDC_TRUESYNC_DELETE 1022
+#define IDC_CHECK_PUBLISHER 1022
+#define IDC_BUTTON_PLAY 1023
+#define IDC_TRUESYNC_COPY 1023
+#define IDC_EDIT_PUBLISHER 1023
+#define IDC_BUTTON_ENQUEUE 1024
+#define IDC_TAB1 1024
+#define IDC_CHECK_COMPOSER 1024
+#define IDC_EDIT_COMPOSER 1025
+#define IDC_LIST_ARTIST 1027
+#define IDC_ENABLETRANSCODER 1027
+#define IDC_BUTTON_EJECT 1028
+#define IDC_ADVANCED 1028
+#define IDC_CHECK_FORCE 1029
+#define IDC_LIST_ALBUM 1030
+#define IDC_FORCE_BITRATE 1030
+#define IDC_LIST_ALBUM2 1031
+#define IDC_CHECK_FORCE2 1031
+#define IDC_CHECK_FORCE_LOSSLESS 1031
+#define IDC_PL_WHITELIST 1032
+#define IDC_HDELIM 1033
+#define IDC_PL_BLACKLIST 1033
+#define IDC_LIBRARYSYNC 1034
+#define IDC_VDELIM 1035
+#define IDC_SYNCONCONNECT 1035
+#define IDC_VDELIM2 1036
+#define IDC_STATUS 1039
+#define IDC_SYNC_QUERY_STRING 1040
+#define IDC_LIST_TRACKS 1041
+#define IDC_AUTOFILL_QUERY_STRING 1041
+#define IDC_SYNCONCONNECT_TIME 1042
+#define IDC_BUTTON_SYNC 1044
+#define IDC_SEARCH_TEXT 1045
+#define IDC_COMBO_FILTER1 1045
+#define IDC_COMBO_FILTER2 1046
+#define IDC_COMBO_FILTER3 1047
+#define IDC_RADIO_FILTERS1 1048
+#define IDC_RADIO_FILTERS2 1049
+#define IDC_RADIO_FILTERS3 1050
+#define IDC_STATIC_FILTER3 1051
+#define IDC_CHECK_USECDRIP 1052
+#define IDC_CHECK_PC 1052
+#define IDC_CHECK_ALBUMART 1052
+#define IDC_STATIC_1 1053
+#define IDC_REMEMBER_SEARCH 1053
+#define IDC_STATIC_2 1054
+#define IDC_PC_ALL 1058
+#define IDC_PC_SEL 1059
+#define IDC_COMBO_PC_NUM 1060
+#define IDC_STATIC_PODTXT 1061
+#define IDC_STATIC_PODTIP 1062
+#define IDC_BUTTON3 1063
+#define IDC_CHECK_VIDEOVIEW 1063
+#define IDC_BUTTON4 1064
+#define IDC_PICTUREHOLDER 1064
+#define IDC_BUTTON_AUTOFILL 1065
+#define IDC_BUTTON5 1065
+#define IDC_ARTINFO 1065
+#define IDC_BUTTON2 1066
+#define IDC_ART_CHANGE 1067
+#define IDC_ART_CLEAR 1068
+#define IDC_BUTTON_ARTMODE 1069
+#define IDC_STATIC_PODCASTS 1070
+#define IDC_BUTTON_CLEARSEARCH 1071
+#define IDC_BUTTON_CLEARREFINE 1072
+#define IDC_DEVICE_HEADER 1074
+#define IDC_DEVICE_HEADER_SIZE 1075
+#define IDC_STATIC_FILTER2 1076
+#define IDC_SYNC_QUERY_EDIT 1077
+#define IDC_BUTTONCANCELSELECTED 1077
+#define IDC_AUTOFILL_QUERY_EDIT 1078
+#define IDC_BUTTON_SORT 1079
+#define IDC_DETAILS 1080
+#define IDC_CLOUDSYNC_ALL 1081
+#define IDC_CLOUDSYNC_SEL 1082
+#define IDC_CLOUDSYNC_NOTSEL 1083
+#define IDC_HEADER_DEVICE_NAME 1083
+#define IDC_HEADER_DEVICE_SIZE 1084
+#define IDC_HEADER_DEVICE_TRANSFER 1085
+#define IDC_HEADER_DEVICE_BAR 1086
+#define IDC_HDELIM2 1088
+#define IDC_HEADER_DEVICE_ICON 1089
+#define IDC_TX_MODE 1090
+#define IDC_SPACESLIDER 1109
+#define IDC_FILLCAPTION 1110
+#define IDC_LIST_ADD 1116
+#define IDC_LIST_REMOVE 1117
+#define IDC_LIST_ADD_PL 1118
+#define IDC_LESS 1119
+#define IDC_MORE 1120
+#define IDC_ADDLABEL 1124
+#define IDC_REMOVELABEL 1125
+#define IDC_ADD_REMSEL 1126
+#define IDC_ADD_CROPSEL 1127
+#define IDC_REM_REMSEL 1128
+#define IDC_REM_CROPSEL 1129
+#define IDC_DETAILSLABEL 1130
+#define IDC_AUTOFILLALBUMS 1131
+#define IDC_DEFS 1210
+#define ID_TRACKSLIST_PLAYSELECTION 40001
+#define ID_TRACKSLIST_ENQUEUESELECTION 40002
+#define ID_ADDTOPLAYLIST_NEWPLAYLIST 40004
+#define ID_RATE_5 40013
+#define ID_RATE_4 40014
+#define ID_RATE_3 40015
+#define ID_RATE_2 40016
+#define ID_RATE_1 40017
+#define ID_RATE_0 40018
+#define ID_TRACKSLIST_EDITSELECTEDITEMS 40019
+#define ID_TRACKSLIST_DELETE 40020
+#define ID_TRACKSLIST_SELECTALL 40021
+#define ID_TRACKSLIST_REMOVEFROMPLAYLIST 40022
+#define ID_TREEDEVICE_EJECTDEVICE 40023
+#define ID_TREEPLAYLIST_REMOVEPLAYLIST 40024
+#define ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES 40026
+#define ID_SORTPLAYLIST_ARTIST 40027
+#define ID_SORTPLAYLIST_ALBUM 40028
+#define ID_SORTPLAYLIST_TITLE 40029
+#define ID_SORTPLAYLIST_TRACK 40030
+#define ID_SORTPLAYLIST_DISC 40031
+#define ID_SORTPLAYLIST_GENRE 40032
+#define ID_SORTPLAYLIST_RATING 40033
+#define ID_SORTPLAYLIST_PLAYCOUNT 40034
+#define ID_SORTPLAYLIST_LASTPLAYED 40035
+#define ID_SORTPLAYLIST_RANDOMIZE 40036
+#define ID_SORTPLAYLIST_REVERSEPLAYLIST 40037
+#define ID_TREEPLAYLIST_RENAMEPLAYLIST 40038
+#define ID_TREEDEVICE_NEWPLAYLIST 40039
+#define ID_MAINTREEROOT_AUTOHIDEROOT 40041
+#define ID_TRACKSLIST_COPYTOLIBRARY 40042
+#define ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA 40043
+#define ID_HEADERWND_CUSTOMIZECOLUMNS 40045
+#define ID_FILTERHEADERWND_SHOWHORIZONTALSCROLLBAR 40046
+#define ID_ARTHEADERMENU_SMALLICON 40049
+#define ID_ARTHEADERMENU_MEDIUMICON 40050
+#define ID_ARTHEADERMENU_LARGEICON 40051
+#define ID_ARTHEADERMENU_SMALLDETAILS 40052
+#define ID_ARTHEADERMENU_LARGEDETAILS 40053
+#define ID_ARTHEADERMENU_MEDIUMDETAILS 40054
+#define ID_ARTEDITMENU_COPY 40055
+#define ID_ARTEDITMENU_PASTE 40056
+#define ID_ARTEDITMENU_DELETE 40057
+#define ID_ARTEDITMENU_SAVEAS 40058
+#define ID_ARTEDITMENU_DOWNLOAD 40059
+#define ID_MAINTREEROOT_PREFERENCES 40060
+#define ID_MAINTREEROOT_HELP 40061
+#define ID_CLOUDTRACKSLIST_CLOUDSOURCES 40062
+#define ID_Menu 40063
+#define ID_Menu40064 40064
+#define ID_CLOUDTRACKSLIST_CLOUDSOURCES40065 40065
+#define ID_CLOUDARTISTALBUMLIST_CLOUDSOURCES 40066
+#define IDS_NULLSOFT_PMP_SUPPORT 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 295
+#define _APS_NEXT_COMMAND_VALUE 40067
+#define _APS_NEXT_CONTROL_VALUE 1091
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_pmp/resources/albumArt.png b/Src/Plugins/Library/ml_pmp/resources/albumArt.png
new file mode 100644
index 00000000..9c23f46a
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/albumArt.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/columns.png b/Src/Plugins/Library/ml_pmp/resources/columns.png
new file mode 100644
index 00000000..14aace4b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/columns.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/deviceIcon.png b/Src/Plugins/Library/ml_pmp/resources/deviceIcon.png
new file mode 100644
index 00000000..3e477593
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/deviceIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/notfound.png b/Src/Plugins/Library/ml_pmp/resources/notfound.png
new file mode 100644
index 00000000..f76c0516
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/notfound.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/playlistIcon.png b/Src/Plugins/Library/ml_pmp/resources/playlistIcon.png
new file mode 100644
index 00000000..748a7aea
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/playlistIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/sync-command-small.png b/Src/Plugins/Library/ml_pmp/resources/sync-command-small.png
new file mode 100644
index 00000000..b161fc33
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/sync-command-small.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.png
new file mode 100644
index 00000000..4268c56b
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.png
new file mode 100644
index 00000000..6bc33d38
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_1.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.png
new file mode 100644
index 00000000..75a16f38
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_2.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.png b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.png
new file mode 100644
index 00000000..3bb90d10
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/transfer_queue_16x16_3.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/usb.png b/Src/Plugins/Library/ml_pmp/resources/usb.png
new file mode 100644
index 00000000..3896d1b3
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/usb.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/videoIcon.png b/Src/Plugins/Library/ml_pmp/resources/videoIcon.png
new file mode 100644
index 00000000..d1d043dc
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/videoIcon.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/resources/viewMode.png b/Src/Plugins/Library/ml_pmp/resources/viewMode.png
new file mode 100644
index 00000000..809bc1b7
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/resources/viewMode.png
Binary files differ
diff --git a/Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp b/Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp
new file mode 100644
index 00000000..aca58562
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncCloudDialog.cpp
@@ -0,0 +1,675 @@
+#include "main.h"
+#include "metadata_utils.h"
+#include <strsafe.h>
+#include "../playlist/ifc_playlistloadercallback.h"
+#include "resource1.h"
+#include <shlwapi.h>
+
+#define SYNCCLOUDDIALOG_PROP L"SyncCloudDialogProp"
+
+typedef struct SyncCloudDialog
+{
+ HWND centerWindow;
+ DeviceView *device;
+ C_ItemList *libraryList;
+ C_ItemList *playlistsList;
+} SyncCloudDialog;
+
+#define SYNCCLOUDDIALOG(_hwnd) ((SyncCloudDialog*)GetPropW((_hwnd), SYNCCLOUDDIALOG_PROP))
+
+static INT_PTR CALLBACK SyncCloudDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+INT_PTR SyncCloudDialog_Show(HWND centerWindow, DeviceView *device, C_ItemList *libraryList)
+{
+ SyncCloudDialog param;
+
+ param.centerWindow = centerWindow;
+ param.device = device;
+ param.libraryList = libraryList;
+ param.playlistsList = new C_ItemList;
+
+ return WASABI_API_DIALOGBOXPARAMW(IDD_CLOUD_SYNC, plugin.hwndLibraryParent, SyncCloudDialog_DialogProc, (LPARAM)&param);
+}
+
+static BOOL SyncCloudDialog_FormatSongString(wchar_t *buffer, size_t bufferSize, const wchar_t *artist,
+ const wchar_t *title, const wchar_t *fileName)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return FALSE;
+
+ if (NULL == title || L'\0' == *title)
+ {
+ if (NULL == fileName || L'\0' == *fileName)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_TRACK, buffer, bufferSize);
+ hr = S_OK;
+ }
+ else
+ hr = StringCchCopyEx(buffer, bufferSize, fileName, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else if (NULL == artist || L'\0' == *artist)
+ {
+ hr = StringCchCopyEx(buffer, bufferSize, title, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else
+ {
+ hr = StringCchPrintfEx(buffer, bufferSize, NULL, NULL, STRSAFE_IGNORE_NULLS,
+ L"%s - %s", artist, title);
+ }
+
+ return SUCCEEDED(hr);
+}
+
+WORD WGetListboxStringExtent(HWND hList, wchar_t* psz){
+ WORD wExtent = 0;
+ HDC hDC = GetDC(hList);
+ int tab[] = {0};
+ SelectObject(hDC,(HFONT)SendMessage(hList,WM_GETFONT,0,0));
+ wExtent = LOWORD(GetTabbedTextExtent(hDC,psz,lstrlen(psz)+1,0,tab));
+ ReleaseDC(hList,hDC);
+ return wExtent;
+}
+
+DWORD fill_listbox(HWND list, wchar_t* string, DWORD width){
+ DWORD prev = width;
+ DWORD dwExtent = WGetListboxStringExtent(list,string);
+ if(prev < dwExtent){prev = dwExtent;}
+ return prev;
+}
+
+class SyncCloudItemListLoader : public ifc_playlistloadercallback
+{
+public:
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+ {
+ if(pos < len)
+ {
+ if (info)
+ {
+ // look at the devices we've already uploaded to vs the destination
+ const wchar_t * devices = info->GetExtendedInfo(L"cloud_devices");
+ if (devices)
+ {
+ wchar_t* pt = wcstok((wchar_t*)devices, L"*");
+ while (pt)
+ {
+ if (_wtoi(pt) == device_id)
+ {
+ done++;
+ return;
+ }
+ pt = wcstok(NULL, L"*");
+ }
+ }
+ }
+
+ // check if the file exists, skipping if not
+ if (PathFileExistsW(filename))
+ {
+ __int64 file_size = INVALID_FILE_SIZE;
+ time_t file_time = 0;
+ GetFileSizeAndTime(filename, &file_size, &file_time);
+ if (!(file_size == INVALID_FILE_SIZE || file_size == 0))
+ {
+ total_size += file_size;
+ }
+ songs[pos].filename = _wcsdup(filename);
+ pos++;
+ }
+ else
+ {
+ done++;
+ }
+ }
+ }
+ int64_t total_size;
+ int pos, len, done, device_id;
+ songMapping * songs;
+protected:
+ RECVS_DISPATCH;
+};
+
+#define CBCLASS SyncCloudItemListLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+static BOOL SyncCloudDialog_PopulatePlaylistLists(HWND hwnd)
+{
+ SyncCloudDialog *self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ HWND hList = GetDlgItem(hwnd, IDC_LIST_ADD_PL);
+ if(NULL != hList)
+ {
+ DWORD list_width = 0;
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ // first collect playlists without metadata
+ int playlistsnum = SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_PLAYLIST_COUNT);
+ for(int i = 0, count = 0; i < playlistsnum; i++)
+ {
+ SyncCloudItemListLoader* list = new SyncCloudItemListLoader();
+ mlPlaylistInfo* playlist = (mlPlaylistInfo*)calloc(sizeof(mlPlaylistInfo),1);
+ playlist->size = sizeof(mlPlaylistInfo);
+ playlist->playlistNum = i;
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)playlist, ML_IPC_PLAYLIST_INFO);
+
+ if (playlist->numItems > 0)
+ {
+ wchar_t buf[256] = {0};
+ list->device_id = self->device->dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID, 0, 0, 0);
+ list->total_size = list->pos = list->done = 0;
+ list->len = playlist->numItems;
+ list->songs = (songMapping*)calloc(sizeof(songMapping), list->len);
+ playlistManager->Load(playlist->filename, list);
+ list->len -= list->done;
+ if (list->len > 0)
+ {
+ if (list->done > 0)
+ {
+ StringCchPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_CLOUD_SYNC_PL_SOME), playlist->playlistName, list->done);
+ }
+ int pos = SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)(buf[0] ? buf : playlist->playlistName));
+ SendMessage(hList, LB_SETITEMDATA, pos, count);
+ self->playlistsList->Add(list);
+ count++;
+ }
+ else
+ {
+ StringCchPrintfW(buf, 256, WASABI_API_LNGSTRINGW(IDS_CLOUD_SYNC_PL_EMPTY), playlist->playlistName);
+ int pos = SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)buf);
+ SendMessage(hList, LB_SETITEMDATA, pos, -1);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ SendMessage(hList, LB_SETHORIZONTALEXTENT, list_width, 0L);
+ }
+
+ return TRUE;
+}
+
+static BOOL SyncCloudDialog_PopulateTrackLists(HWND hwnd)
+{
+ SyncCloudDialog *self;
+ HWND hList;
+ wchar_t buffer[1024] = {0};
+ int index, count;
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ hList = GetDlgItem(hwnd, IDC_LIST_ADD);
+ if(NULL != hList)
+ {
+ DWORD list_width = 0;
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ count = (NULL != self->libraryList) ? self->libraryList->GetSize() : 0;
+ if (0 != count)
+ {
+ itemRecordW *record;
+
+ SendMessage(hList, LB_SETCOUNT , (WPARAM)count, 0L);
+
+ for(index = 0; index < count; index++)
+ {
+ record = (itemRecordW*)self->libraryList->Get(index);
+ if (NULL != record &&
+ FALSE != SyncCloudDialog_FormatSongString(buffer, ARRAYSIZE(buffer),
+ record->artist, record->title, record->filename))
+ {
+ SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)buffer);
+ list_width = fill_listbox(hList,buffer,list_width);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ SendMessage(hList, LB_SETHORIZONTALEXTENT, list_width, 0L);
+ }
+
+ return TRUE;
+}
+
+static int SyncCloudDialog_ReadTransferOptions(SyncCloudDialog *self)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ return config->ReadInt(L"CloudSyncMode", 0);
+ }
+
+ return 0;
+}
+
+static BOOL SyncCloudDialog_WriteTransferOptions(SyncCloudDialog *self, int mode)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ config->WriteInt(L"CloudSyncMode", mode);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void SyncCloudDialog_UpdateTransferOptions(HWND hwnd, int mode)
+{
+ const int control[] =
+ {
+ IDC_CLOUDSYNC_ALL,
+ IDC_CLOUDSYNC_SEL,
+ IDC_CLOUDSYNC_NOTSEL
+ };
+
+ const int controls[] =
+ {
+ IDS_CLOUD_SYNC_ALL_SONGS,
+ IDS_CLOUD_SYNC_SEL_SONGS,
+ IDS_CLOUD_SYNC_NONSEL_SONGS
+ };
+ const int controls_pl[] =
+ {
+ IDS_CLOUD_SYNC_ALL_PL,
+ IDS_CLOUD_SYNC_SEL_PL,
+ IDS_CLOUD_SYNC_NONSEL_PL
+ };
+
+ switch (mode)
+ {
+ case 0:
+ for (int i = 0; i < sizeof(controls_pl)/sizeof(controls_pl[0]); i++)
+ {
+ SetDlgItemText(hwnd, control[i], WASABI_API_LNGSTRINGW(controls_pl[i]));
+ }
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD_PL), TRUE);
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD), FALSE);
+ break;
+
+ case 1:
+ for (int i = 0; i < sizeof(controls)/sizeof(controls[0]); i++)
+ {
+ SetDlgItemText(hwnd, control[i], WASABI_API_LNGSTRINGW(controls[i]));
+ }
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD_PL), FALSE);
+ ShowWindow(GetDlgItem(hwnd, IDC_LIST_ADD), TRUE);
+ break;
+ }
+}
+
+static BOOL SyncCloudDialog_UpdateCaption(HWND hwnd)
+{
+ SyncCloudDialog *self;
+ HWND captionWindow, hList;
+ wchar_t buffer[1024] = {0};
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ captionWindow = GetDlgItem(hwnd, IDC_DETAILS);
+ if (NULL == captionWindow)
+ return FALSE;
+
+ int mode = SyncCloudDialog_ReadTransferOptions(self);
+ hList = GetDlgItem(hwnd, (mode ? IDC_LIST_ADD : IDC_LIST_ADD_PL));
+ if (NULL == hList)
+ return FALSE;
+
+ int64_t total_size = 0, sel_size = 0,
+ device_size = self->device->dev->getDeviceCapacityAvailable();
+
+ int all = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_ALL) == BST_CHECKED);
+ int sel = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_SEL) == BST_CHECKED);
+ if (mode)
+ {
+ for (int i = 0; i < self->libraryList->GetSize(); i++)
+ {
+ if (all || SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ itemRecordW *record = (itemRecordW *)self->libraryList->Get(i);
+ total_size += record->filesize * 1024;
+ sel_size += 1;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < SendMessage(hList, LB_GETCOUNT, 0, 0); i++)
+ {
+ if (all || SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ int item = SendMessage(hList, LB_GETITEMDATA, i, 0);
+ if (item != -1)
+ {
+ SyncCloudItemListLoader *list = (SyncCloudItemListLoader *)self->playlistsList->Get(item);
+ total_size += list->total_size;
+ sel_size += list->len;
+ }
+ }
+ }
+ }
+
+ wchar_t buf[64] = {0}, buf2[64] = {0}, buf3[128] = {0}, buf4[128] = {0},
+ buffer2[256] = {0}, local_lib[128] = {0};
+ WASABI_API_LNG->FormattedSizeString(buf, ARRAYSIZE(buf), total_size);
+ WASABI_API_LNG->FormattedSizeString(buf2, ARRAYSIZE(buf2), device_size);
+ int64_t over_limit = (total_size - device_size);
+ if (over_limit > 0)
+ {
+ WASABI_API_LNG->FormattedSizeString(buf4, ARRAYSIZE(buf4), over_limit);
+ }
+ self->device->GetDisplayName(buf3, ARRAYSIZE(buf3));
+
+ int over_size = (device_size > 0 && total_size <= device_size);
+
+ // enable the transfer button as applicable
+ EnableWindow(GetDlgItem(hwnd, IDOK), sel_size && over_size);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_LOCAL_LIBRARY, local_lib, 128);
+
+ StringCchPrintf(buffer, ARRAYSIZE(buffer),
+ WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_X_TO_Y),
+ local_lib, buf3);
+ SetWindowText(hwnd, buffer);
+
+ StringCchPrintf(buffer, ARRAYSIZE(buffer), WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_HAVE_SELECTED_X), local_lib, buf3, sel_size, buf, buf3, buf2);
+
+ if (sel_size && over_limit > 0)
+ {
+ StringCchPrintf(buffer2, ARRAYSIZE(buffer2), WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_OVER_LIMIT), buf4, buf3);
+ StringCchCat(buffer, ARRAYSIZE(buffer), buffer2);
+ }
+ else if (!sel_size)
+ {
+ StringCchPrintf(buffer, ARRAYSIZE(buffer), WASABI_API_LNGSTRINGW(IDS_CLOUD_TX_NO_SEL), local_lib, buf3);
+ }
+
+ SendMessage(captionWindow, WM_SETTEXT, 0, (LPARAM)buffer);
+ return TRUE;
+}
+
+static void SyncCloudDialog_GenerateList(HWND hwnd)
+{
+ SyncCloudDialog *self;
+ HWND hList;
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return;
+
+ int mode = SyncCloudDialog_ReadTransferOptions(self);
+ hList = GetDlgItem(hwnd, (mode ? IDC_LIST_ADD : IDC_LIST_ADD_PL));
+ if (NULL == hList)
+ return;
+
+ if (mode)
+ {
+ if ((IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_ALL) == BST_UNCHECKED))
+ {
+ int sel = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_SEL) == BST_CHECKED),
+ current = 0, l = self->libraryList->GetSize();
+ for (int i = 0; i < l; i++)
+ {
+ if (SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ current++;
+ }
+ else
+ {
+ self->libraryList->Del(current);
+ }
+ }
+ }
+ }
+ else
+ {
+ // no need for this so clear out
+ int l = self->libraryList->GetSize();
+ for (int i = 0; i < l; i++)
+ {
+ self->libraryList->Del(0);
+ }
+
+ int all = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_ALL) == BST_CHECKED);
+ int sel = (IsDlgButtonChecked(hwnd, IDC_CLOUDSYNC_SEL) == BST_CHECKED);
+ for (int i = 0; i < SendMessage(hList, LB_GETCOUNT, 0, 0); i++)
+ {
+ if (all || SendMessage(hList, LB_GETSEL, i, 0) != !sel)
+ {
+ int item = SendMessage(hList, LB_GETITEMDATA, i, 0);
+ if (item != -1)
+ {
+ SyncCloudItemListLoader *list = (SyncCloudItemListLoader *)self->playlistsList->Get(item);
+ if (list)
+ {
+ for (int j = 0; j < list->len; j++)
+ {
+ songMapping* song = &list->songs[j];
+ if (song)
+ {
+ itemRecordW *result = AGAVE_API_MLDB->GetFile(song->filename);
+ song->ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1);
+ if (result)
+ {
+ copyRecord(song->ice, result);
+ AGAVE_API_MLDB->FreeRecord(result);
+ }
+ else
+ {
+ filenameToItemRecord(song->filename, song->ice); // ugh. Disk intensive.
+ }
+ if (song->ice)
+ {
+ self->libraryList->Add(song->ice);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+typedef enum DeviceSyncPolicy
+{
+ DeviceSyncPolicy_CopyAllTracks = 0,
+ DeviceSyncPolicy_CopySelTracks = 1,
+ DeviceSyncPolicy_CopyNotSelTracks = 2,
+} DeviceSyncPolicy;
+
+static DeviceSyncPolicy SyncCloudDialog_ReadDeviceSyncPolicy(SyncCloudDialog *self)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ return (DeviceSyncPolicy)config->ReadInt(L"CloudSync", (int)DeviceSyncPolicy_CopyAllTracks);
+ }
+
+ return DeviceSyncPolicy_CopyAllTracks;
+}
+
+static BOOL SyncCloudDialog_WriteDeviceSyncPolicy(SyncCloudDialog *self, DeviceSyncPolicy policy)
+{
+ if (NULL != self && NULL != self->device && NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ config->WriteInt(L"CloudSync", (int)policy);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL SyncCloudDialog_SelectDeviceSyncPolicy(HWND hwnd, DeviceSyncPolicy policy)
+{
+ SyncCloudDialog *self;
+ int policyControl;
+
+ const int controls[] =
+ {
+ IDC_CLOUDSYNC_ALL,
+ IDC_CLOUDSYNC_SEL,
+ IDC_CLOUDSYNC_NOTSEL
+ };
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ switch(policy)
+ {
+ case DeviceSyncPolicy_CopyAllTracks:
+ policyControl = IDC_CLOUDSYNC_ALL;
+ break;
+ case DeviceSyncPolicy_CopySelTracks:
+ policyControl = IDC_CLOUDSYNC_SEL;
+ break;
+ case DeviceSyncPolicy_CopyNotSelTracks:
+ policyControl = IDC_CLOUDSYNC_NOTSEL;
+ break;
+ default:
+ return FALSE;
+ }
+
+ CheckRadioButton(hwnd, controls[0], controls[2], policyControl);
+
+ return TRUE;
+}
+
+static INT_PTR SyncCloudDialog_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ SyncCloudDialog *self;
+ C_Config *config;
+
+ self = (SyncCloudDialog*)param;
+
+ if (NULL == self ||
+ FALSE == SetProp(hwnd, SYNCCLOUDDIALOG_PROP, self))
+ {
+ EndDialog(hwnd, -1);
+ return 0;
+ }
+
+ config = self->device->config;
+
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, SyncCloudDialog_ReadDeviceSyncPolicy(self));
+
+ if (FALSE != CenterWindow(hwnd, self->centerWindow))
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+
+ int mode = SyncCloudDialog_ReadTransferOptions(self);
+ SendDlgItemMessageW(hwnd, IDC_TX_MODE, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(IDS_PLAYLISTS));
+ SendDlgItemMessageW(hwnd, IDC_TX_MODE, CB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(IDS_SONGS));
+ SendDlgItemMessageW(hwnd, IDC_TX_MODE, CB_SETCURSEL, (WPARAM)mode, 0);
+
+ SyncCloudDialog_UpdateTransferOptions(hwnd, mode);
+ SyncCloudDialog_PopulateTrackLists(hwnd);
+ SyncCloudDialog_PopulatePlaylistLists(hwnd);
+ SyncCloudDialog_UpdateCaption(hwnd);
+
+ return 0;
+}
+
+static void SyncCloudDialog_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, SYNCCLOUDDIALOG_PROP);
+}
+
+static void SyncCloudDialog_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ SyncCloudDialog *self;
+ C_Config *config;
+
+ self = SYNCCLOUDDIALOG(hwnd);
+ if (NULL == self)
+ return;
+
+ config = self->device->config;
+
+ switch(commandId)
+ {
+ case IDC_TX_MODE:
+ if (CBN_SELCHANGE == eventId)
+ {
+ int mode = SendDlgItemMessage(hwnd, commandId, CB_GETCURSEL, 0, 0);
+ SyncCloudDialog_WriteTransferOptions(self, mode);
+ SyncCloudDialog_UpdateTransferOptions(hwnd, mode);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_CLOUDSYNC_ALL:
+ if (BN_CLICKED == eventId)
+ {
+ SyncCloudDialog_WriteDeviceSyncPolicy(self, DeviceSyncPolicy_CopyAllTracks);
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopyAllTracks);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_CLOUDSYNC_SEL:
+ if (BN_CLICKED == eventId)
+ {
+ SyncCloudDialog_WriteDeviceSyncPolicy(self, DeviceSyncPolicy_CopySelTracks);
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopySelTracks);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_CLOUDSYNC_NOTSEL:
+ if (BN_CLICKED == eventId)
+ {
+ SyncCloudDialog_WriteDeviceSyncPolicy(self, DeviceSyncPolicy_CopyNotSelTracks);
+ SyncCloudDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopyNotSelTracks);
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDOK:
+ SyncCloudDialog_GenerateList(hwnd);
+ EndDialog(hwnd, IDOK);
+ break;
+
+ case IDCANCEL:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+
+ case IDC_LIST_ADD:
+ case IDC_LIST_ADD_PL:
+ if (eventId == LBN_SELCHANGE)
+ {
+ SyncCloudDialog_UpdateCaption(hwnd);
+ }
+ break;
+ }
+}
+
+static INT_PTR CALLBACK SyncCloudDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return SyncCloudDialog_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: SyncCloudDialog_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: SyncCloudDialog_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/syncCloudDialog.h b/Src/Plugins/Library/ml_pmp/syncCloudDialog.h
new file mode 100644
index 00000000..8c93c569
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncCloudDialog.h
@@ -0,0 +1,17 @@
+#ifndef _NULLSOFT_WINAMP_ML_PMP_SYNC_CLOUD_DIALOG_HEADER
+#define _NULLSOFT_WINAMP_ML_PMP_SYNC_CLOUD_DIALOG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class DeviceView;
+class C_ItemList;
+
+INT_PTR SyncCloudDialog_Show(HWND centerWindow, DeviceView *device,
+ C_ItemList *libraryList/*, C_ItemList *deviceList,
+ BOOL autofillMode*/);
+
+#endif //_NULLSOFT_WINAMP_ML_PMP_SYNC_CLOUD_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/syncDialog.cpp b/Src/Plugins/Library/ml_pmp/syncDialog.cpp
new file mode 100644
index 00000000..08badf7c
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncDialog.cpp
@@ -0,0 +1,443 @@
+#include "main.h"
+#include <strsafe.h>
+
+#define SYNCDIALOG_PROP L"SyncDialogProp"
+
+typedef struct SyncDialog
+{
+ HWND centerWindow;
+ DeviceView *device;
+ C_ItemList *libraryList;
+ C_ItemList *deviceList;
+ BOOL autofillMode;
+} SyncDialog;
+
+#define SYNCDIALOG(_hwnd) ((SyncDialog*)GetPropW((_hwnd), SYNCDIALOG_PROP))
+
+static INT_PTR CALLBACK
+SyncDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+
+INT_PTR
+SyncDialog_Show(HWND centerWindow, DeviceView *device,
+ C_ItemList *libraryList, C_ItemList *deviceList, BOOL autofillMode)
+{
+ SyncDialog param;
+
+ param.centerWindow = centerWindow;
+ param.device = device;
+ param.libraryList = libraryList;
+ param.deviceList = deviceList;
+ param.autofillMode = autofillMode;
+
+ return WASABI_API_DIALOGBOXPARAMW(IDD_SYNC, plugin.hwndLibraryParent, SyncDialog_DialogProc, (LPARAM)&param);
+}
+
+static BOOL
+SyncDialog_FormatSongString(wchar_t *buffer, size_t bufferSize,
+ const wchar_t *artist, const wchar_t *title, const wchar_t *fileName)
+{
+ HRESULT hr;
+
+ if (NULL == buffer)
+ return FALSE;
+
+ if (NULL == title || L'\0' == *title)
+ {
+ if (NULL == fileName || L'\0' == *fileName)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_TRACK, buffer, bufferSize);
+ hr = S_OK;
+ }
+ else
+ hr = StringCchCopyEx(buffer, bufferSize, fileName, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else if (NULL == artist || L'\0' == *artist)
+ {
+ hr = StringCchCopyEx(buffer, bufferSize, title, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ }
+ else
+ {
+ hr = StringCchPrintfEx(buffer, bufferSize, NULL, NULL, STRSAFE_IGNORE_NULLS,
+ L"%s - %s", artist, title);
+ }
+
+ return SUCCEEDED(hr);
+}
+
+static BOOL
+SyncDialog_PopulateTrackLists(HWND hwnd)
+{
+ SyncDialog *self;
+ HWND hList;
+ wchar_t buffer[1024] = {0};
+ int index, count;
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ hList = GetDlgItem(hwnd, IDC_LIST_ADD);
+ if(NULL != hList)
+ {
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ count = (NULL != self->libraryList) ? self->libraryList->GetSize() : 0;
+ if (0 != count)
+ {
+ SendMessage(hList, LB_SETCOUNT , (WPARAM)count, 0L);
+
+ for(index = 0; index < count; index++)
+ {
+ itemRecordW *record = (itemRecordW*)self->libraryList->Get(index);
+ if (NULL != record &&
+ FALSE != SyncDialog_FormatSongString(buffer, ARRAYSIZE(buffer),
+ record->artist, record->title, record->filename))
+ {
+ SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)buffer);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ }
+
+ hList = GetDlgItem(hwnd, IDC_LIST_REMOVE);
+ if(NULL != hList)
+ {
+ SendMessage(hList, WM_SETREDRAW, FALSE, 0L);
+ SendMessage(hList, LB_RESETCONTENT, FALSE, 0L);
+
+ count = (NULL != self->deviceList) ? self->deviceList->GetSize() : 0;
+ if (0 != count)
+ {
+ Device *device;
+ wchar_t artist[256] = {0}, title[256] = {0};
+
+ SendMessage(hList, LB_SETCOUNT , (WPARAM)count, 0L);
+
+ device = self->device->dev;
+
+ for(index = 0; index < count; index++)
+ {
+ songid_t songId = (songid_t)self->deviceList->Get(index);
+
+ device->getTrackArtist(songId, artist,ARRAYSIZE(artist));
+ device->getTrackTitle(songId, title, ARRAYSIZE(title));
+
+ if (FALSE != SyncDialog_FormatSongString(buffer, ARRAYSIZE(buffer),
+ artist, title, NULL))
+ {
+ SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)buffer);
+ }
+ }
+ }
+
+ SendMessage(hList, WM_SETREDRAW, TRUE, 0L);
+ }
+
+ return TRUE;
+}
+
+static BOOL
+SyncDialog_UpdateCaption(HWND hwnd)
+{
+ SyncDialog *self;
+ HWND captionWindow;
+ int resourceId;
+ wchar_t buffer[1024] = {0}, format[1024] = {0};
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ captionWindow = GetDlgItem(hwnd, IDC_CAPTION);
+ if (NULL == captionWindow)
+ return FALSE;
+
+ resourceId = (FALSE == self->autofillMode) ?
+ IDS_X_SONGS_WILL_BE_TRANSFERRED_TO_THE_DEVICE :
+ IDS_THIS_WILL_ADD_X_SONGS_AND_DELETE_X_SONGS;
+
+
+ WASABI_API_LNGSTRINGW_BUF(resourceId, format, ARRAYSIZE(format));
+
+
+ if (FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), format,
+ self->libraryList->GetSize(), self->deviceList->GetSize())))
+ {
+ return FALSE;
+ }
+
+ SendMessage(captionWindow, WM_SETTEXT, 0, (LPARAM)buffer);
+ return TRUE;
+}
+
+typedef enum DeviceSyncPolicy
+{
+ DeviceSyncPolicy_LeaveTracks = 0,
+ DeviceSyncPolicy_DeleteTracks = 1,
+ DeviceSyncPolicy_CopyTracks = 2,
+} DeviceSyncPolicy;
+
+static BOOL
+SyncDialog_GetDeviceSyncPolicyEnabled(SyncDialog *self)
+{
+ if (NULL != self &&
+ FALSE == self->autofillMode &&
+ 0 != self->deviceList->GetSize())
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static DeviceSyncPolicy
+SyncDialog_ReadDeviceSyncPolicy(SyncDialog *self)
+{
+ if (FALSE != SyncDialog_GetDeviceSyncPolicyEnabled(self) &&
+ NULL != self->device &&
+ NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ return (DeviceSyncPolicy)config->ReadInt(L"TrueSync", (int)DeviceSyncPolicy_LeaveTracks);
+ }
+
+ return DeviceSyncPolicy_LeaveTracks;
+}
+
+static BOOL
+SyncDialog_WriteDeviceSyncPolicy(SyncDialog *self, DeviceSyncPolicy policy)
+{
+ if (FALSE != SyncDialog_GetDeviceSyncPolicyEnabled(self) &&
+ NULL != self->device &&
+ NULL != self->device->config)
+ {
+ C_Config *config;
+ config = self->device->config;
+ config->WriteInt(L"TrueSync", (int)policy);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL
+SyncDialog_SelectDeviceSyncPolicy(HWND hwnd, DeviceSyncPolicy policy)
+{
+ SyncDialog *self;
+ HWND hControl;
+ size_t index;
+ int policyControl;
+ int checkMode;
+ int labelText;
+ BOOL enableControls;
+
+ const int controls[] =
+ {
+ IDC_TRUESYNC_LEAVE,
+ IDC_TRUESYNC_DELETE,
+ IDC_TRUESYNC_COPY
+ };
+
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return FALSE;
+
+ switch(policy)
+ {
+ case DeviceSyncPolicy_LeaveTracks:
+ policyControl = IDC_TRUESYNC_LEAVE;
+ labelText = IDS_SONGS_NOT_IN_MEDIA_LIBRARY;
+ break;
+ case DeviceSyncPolicy_DeleteTracks:
+ policyControl = IDC_TRUESYNC_DELETE;
+ labelText = IDS_SONGS_TO_BE_DELETED;
+ break;
+ case DeviceSyncPolicy_CopyTracks:
+ policyControl = IDC_TRUESYNC_COPY;
+ labelText = IDS_SONGS_TO_BE_COPIED;
+ break;
+ default:
+ return FALSE;
+ }
+
+ enableControls = SyncDialog_GetDeviceSyncPolicyEnabled(self);
+
+ for (index = 0; index < ARRAYSIZE(controls); index++)
+ {
+ hControl = GetDlgItem(hwnd, controls[index]);
+ if (NULL == hControl)
+ continue;
+
+ checkMode = (controls[index] == policyControl) ?
+ BST_CHECKED :
+ BST_UNCHECKED;
+
+ SendMessage(hControl, BM_SETCHECK, (WPARAM)checkMode, 0L);
+ EnableWindow(hControl, enableControls);
+ }
+
+ hControl = GetDlgItem(hwnd, IDC_REMOVELABEL);
+ if (NULL != hControl)
+ {
+ wchar_t buffer[256] = {0};
+ WASABI_API_LNGSTRINGW_BUF(labelText, buffer, ARRAYSIZE(buffer));
+ SendMessage(hControl, WM_SETTEXT, 0, (LPARAM)buffer);
+ }
+
+ return TRUE;
+}
+
+static INT_PTR
+SyncDialog_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ SyncDialog *self;
+ C_Config *config;
+
+ self = (SyncDialog*)param;
+
+ if (NULL == self ||
+ FALSE == SetProp(hwnd, SYNCDIALOG_PROP, self))
+ {
+ EndDialog(hwnd, -1);
+ return 0;
+ }
+
+ config = self->device->config;
+
+ SyncDialog_UpdateCaption(hwnd);
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, SyncDialog_ReadDeviceSyncPolicy(self));
+
+
+ if(config->ReadInt(L"syncOnConnect", self->device->SyncConnectionDefault) == (self->autofillMode ? 2:1))
+ CheckDlgButton(hwnd, IDC_SYNCONCONNECT, BST_CHECKED);
+
+ SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_LESS,0), NULL);
+
+ if (FALSE != CenterWindow(hwnd, self->centerWindow))
+ SendMessage(hwnd, DM_REPOSITION, 0, 0L);
+
+ SyncDialog_PopulateTrackLists(hwnd);
+
+ return 0;
+}
+
+static void
+SyncDialog_OnDestroy(HWND hwnd)
+{
+ RemoveProp(hwnd, SYNCDIALOG_PROP);
+}
+
+static void
+SyncDialog_OnCommand(HWND hwnd, INT commandId, INT eventId, HWND controlWindow)
+{
+ SyncDialog *self;
+ C_Config *config;
+ BOOL enableRadios;
+
+ self = SYNCDIALOG(hwnd);
+ if (NULL == self)
+ return;
+
+ config = self->device->config;
+
+ enableRadios = ((!self->autofillMode) && self->deviceList->GetSize());
+
+ switch(commandId)
+ {
+ case IDC_ADD_REMSEL:
+ case IDC_REM_REMSEL:
+ case IDC_ADD_CROPSEL:
+ case IDC_REM_CROPSEL:
+ {
+ bool rem = (commandId == IDC_ADD_REMSEL || commandId == IDC_REM_REMSEL);
+ bool addlist = (commandId == IDC_ADD_REMSEL || commandId == IDC_ADD_CROPSEL);
+ HWND box = GetDlgItem(hwnd,addlist?IDC_LIST_ADD:IDC_LIST_REMOVE);
+ C_ItemList * list = addlist?self->libraryList:self->deviceList;
+ int l=SendMessage(box,LB_GETCOUNT,0,0);
+ int current=0;
+ for(int i=0; i<l; ++i)
+ {
+ if((SendMessage(box,LB_GETSEL,current,0)!=0) == rem)
+ {
+ SendMessage(box,LB_DELETESTRING,current,0);
+ list->Del(current);
+ }
+ else current++;
+ }
+ SyncDialog_UpdateCaption(hwnd);
+ }
+ break;
+
+ case IDC_TRUESYNC_DELETE:
+ if (BN_CLICKED == eventId)
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_DeleteTracks);
+ break;
+ case IDC_TRUESYNC_COPY:
+ if (BN_CLICKED == eventId)
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_CopyTracks);
+ break;
+ case IDC_TRUESYNC_LEAVE:
+ if (BN_CLICKED == eventId)
+ SyncDialog_SelectDeviceSyncPolicy(hwnd, DeviceSyncPolicy_LeaveTracks);
+ break;
+
+ case IDC_MORE:
+ case IDC_LESS:
+ {
+ bool more = commandId == IDC_MORE;
+ int show = more?SW_SHOW:SW_HIDE;
+ int hide = more?SW_HIDE:SW_SHOW;
+ ShowWindow(GetDlgItem(hwnd,IDCANCEL),hide);
+ ShowWindow(GetDlgItem(hwnd,IDOK),hide);
+ ShowWindow(GetDlgItem(hwnd,IDC_MORE),hide);
+ ShowWindow(GetDlgItem(hwnd,IDC_LESS),show);
+ ShowWindow(GetDlgItem(hwnd,IDCANCEL2),show);
+ ShowWindow(GetDlgItem(hwnd,IDOK2),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_ADDLABEL),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_REMOVELABEL),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_LIST_ADD),show);
+ ShowWindow(GetDlgItem(hwnd,IDC_LIST_REMOVE),show);
+ RECT r, r1, r2;
+ GetWindowRect(hwnd,&r);
+ GetWindowRect(GetDlgItem(hwnd,more?IDC_MORE:IDC_LESS),&r1);
+ GetWindowRect(GetDlgItem(hwnd,more?IDCANCEL2:IDCANCEL),&r2);
+ SetWindowPos(hwnd,HWND_TOP,r.left,r.top,
+ r2.right - r.left + (r1.left - r.left),
+ r2.bottom - r.top + (r1.left - r.left), 0);
+ }
+ break;
+ case IDOK2:
+ case IDOK:
+ config->WriteInt(L"syncOnConnect",IsDlgButtonChecked(hwnd,IDC_SYNCONCONNECT)?(self->autofillMode?2:1):0);
+
+ if(enableRadios)
+ config->WriteInt(L"TrueSync",IsDlgButtonChecked(hwnd,IDC_TRUESYNC_LEAVE)?0:(IsDlgButtonChecked(hwnd,IDC_TRUESYNC_DELETE)?1:2));
+
+ EndDialog(hwnd, IDOK);
+ break;
+
+ case IDCANCEL2:
+ case IDCANCEL:
+ EndDialog(hwnd, IDCANCEL);
+ break;
+ }
+}
+
+static INT_PTR CALLBACK
+SyncDialog_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch(uMsg)
+ {
+ case WM_INITDIALOG: return SyncDialog_OnInitDialog(hwnd, (HWND)wParam, lParam);
+ case WM_DESTROY: SyncDialog_OnDestroy(hwnd); return TRUE;
+ case WM_COMMAND: SyncDialog_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
+ }
+
+ return FALSE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/syncDialog.h b/Src/Plugins/Library/ml_pmp/syncDialog.h
new file mode 100644
index 00000000..82ce32a0
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/syncDialog.h
@@ -0,0 +1,20 @@
+#ifndef _NULLSOFT_WINAMP_ML_PMP_SYNC_DIALOG_HEADER
+#define _NULLSOFT_WINAMP_ML_PMP_SYNC_DIALOG_HEADER
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+#pragma once
+#endif
+
+#include <wtypes.h>
+
+class DeviceView;
+class C_ItemList;
+
+INT_PTR
+SyncDialog_Show(HWND centerWindow,
+ DeviceView *device,
+ C_ItemList *libraryList,
+ C_ItemList *deviceList,
+ BOOL autofillMode);
+
+#endif //_NULLSOFT_WINAMP_ML_PMP_SYNC_DIALOG_HEADER \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transcoder.h b/Src/Plugins/Library/ml_pmp/transcoder.h
new file mode 100644
index 00000000..2aad4ac7
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transcoder.h
@@ -0,0 +1,38 @@
+#ifndef _TRANSCODER_H_
+#define _TRANSCODER_H_
+
+class Transcoder {
+protected:
+ Transcoder(){}
+ virtual ~Transcoder(){}
+public:
+ // done at init()
+ virtual void LoadConfigProfile(wchar_t *profile)=0; // deprecated, do not call
+ virtual void AddAcceptableFormat(wchar_t *format)=0; // eg, L"mp3" or L"wma"
+ virtual void AddAcceptableFormat(unsigned int format)=0; // eg, mmioFOURCC('M','4','A',' ')
+
+ // done when file is added to transfer queue
+ // returns:
+ // -1 for can't transcode
+ // output file size estimate if can transcode
+ // if ext is supplied, it should be a buffer with space for 5 characters, and will be filled with
+ // the output file type file extention, eg, L".mp3"
+ virtual int CanTranscode(wchar_t *file, wchar_t *ext = NULL, int length = -1)=0;
+
+ // false if no transcoding needed
+ virtual bool ShouldTranscode(wchar_t *file)=0;
+
+ // done just before transfer OR in background after file is added to queue
+ // extention is added to outputFile, allow 5 extra chars
+ // callback, callbackcontext and killswitch should be similar to those passed by ml_pmp
+ // return 0 for success, -1 for failed or cancelled
+ virtual int TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callback)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption=L"Transcoding %d%%")=0;
+
+
+ // get a filename which can be used as a staging area.
+ // ext should be for example L".mp3"
+ // make sure filename is a buffer at least MAX_PATH characters long.
+ virtual void GetTempFilePath(const wchar_t *ext, wchar_t *filename)=0;
+};
+
+#endif //_TRANSCODER_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp b/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp
new file mode 100644
index 00000000..571c0289
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp
@@ -0,0 +1,539 @@
+#include "api__ml_pmp.h"
+#include "transcoder_imp.h"
+#include "nu/ns_wc.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+#include <mmiscapi.h>
+
+extern HWND CreateDummyWindow();
+
+static std::vector<TranscoderImp*> transcoders;
+
+LRESULT CALLBACK TranscodeMsgProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
+{
+ if ( uMsg == WM_WA_IPC && ( lParam == IPC_CB_CONVERT_STATUS || lParam == IPC_CB_CONVERT_DONE ) )
+ {
+ for ( TranscoderImp *t : transcoders )
+ {
+ if ( t->cfs.callbackhwnd == hwnd )
+ {
+ t->TranscodeProgress( (int)wParam, lParam == IPC_CB_CONVERT_DONE );
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void fourccToString(unsigned int f, wchar_t * str) {
+ wchar_t s[4] = {(wchar_t)(f&0xFF),(wchar_t)((f>>8)&0xFF),(wchar_t)((f>>16)&0xFF),0};
+ wcsncpy(str,s,4);
+ CharLower(str);
+}
+
+static unsigned int stringToFourcc(const wchar_t * str) {
+ FOURCC cc = 0;
+ char *ccc = (char *)&cc;
+ // unrolled loop (this function gets called a lot on sync and autofill)
+ if (str[0])
+ {
+ ccc[0] = (char)str[0];
+ if (str[1])
+ {
+ ccc[1] = (char)str[1];
+ if (str[2])
+ {
+ ccc[2] = (char)str[2];
+ if (str[3])
+ {
+ ccc[3] = (char)str[3];
+ }
+ }
+ }
+ }
+ CharUpperBuffA(ccc, 4);
+ return cc;
+}
+
+static bool fourccEqual(unsigned int a, unsigned int b) {
+ if((a & 0xFF000000) == 0 || (b & 0xFF000000) == 0)
+ return (a & 0x00FFFFFF) == (b & 0x00FFFFFF);
+ return a == b;
+}
+
+class TranscodeProfileCache {
+public:
+ unsigned int inputformat;
+ unsigned int outputformat;
+ int outputbitrate;
+ TranscodeProfileCache(unsigned int inputformat,unsigned int outputformat,int outputbitrate) :
+ inputformat(inputformat), outputformat(outputformat),outputbitrate(outputbitrate){}
+};
+
+static void enumProc(intptr_t user_data, const char *desc, int fourcc)
+{
+ ((FormatList *)user_data)->push_back(new EncodableFormat((unsigned int)fourcc,AutoWide(desc)));
+}
+
+static void BuildEncodableFormatsList(FormatList &list, HWND winampWindow, Device *device)
+{
+ converterEnumFmtStruct e = {enumProc,(intptr_t)&list};
+ SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS);
+
+ // filter out unacceptable formats
+ int i = list.size();
+ while (i--)
+ {
+ if (device && device->extraActions(DEVICE_VETO_ENCODER, list[i]->fourcc, 0, 0) == 1)
+ {
+ list.erase(list.begin() + i);
+ }
+ }
+}
+
+static CRITICAL_SECTION csTranscoder;
+
+void TranscoderImp::init() {
+ InitializeCriticalSection(&csTranscoder);
+}
+
+void TranscoderImp::quit() {
+ DeleteCriticalSection(&csTranscoder);
+}
+
+TranscoderImp::TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device)
+: device(device), config(config), winampParent(winampParent), hInst(hInst)
+{
+ EnterCriticalSection(&csTranscoder);
+
+ transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
+ transrate = !!config->ReadInt(L"transrate",0);
+ translossless = !!config->ReadInt(L"translossless",0);
+ TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
+
+ int current_fourcc = config->ReadInt(L"lastusedencoder", 0);
+ if (current_fourcc == 0)
+ {
+ // TODO: ask for default from plugin
+ config->WriteInt(L"lastusedencoder", ' A4M');
+ }
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_PERCENT,caption,100);
+ ZeroMemory(&cfs,sizeof(convertFileStruct));
+ this->callback = NULL;
+ transcoders.push_back(this);
+ callbackhwnd = CreateDummyWindow();
+ StringCchCopy(inifile, ARRAYSIZE(inifile), config->GetIniFile());
+ WideCharToMultiByteSZ(CP_ACP, 0, inifile, -1, inifileA, MAX_PATH, 0, 0);
+ BuildEncodableFormatsList(formats, winampParent, device);
+ LeaveCriticalSection(&csTranscoder);
+}
+
+TranscoderImp::~TranscoderImp()
+{
+ EnterCriticalSection(&csTranscoder);
+
+ //transcoders.eraseObject(this);
+ auto it = std::find(transcoders.begin(), transcoders.end(), this);
+ if (it != transcoders.end())
+ {
+ transcoders.erase(it);
+ }
+
+ //formats.deleteAll();
+ for (auto format : formats)
+ {
+ delete format;
+ }
+ formats.clear();
+
+ DestroyWindow(callbackhwnd);
+ LeaveCriticalSection(&csTranscoder);
+}
+
+void TranscoderImp::LoadConfigProfile(wchar_t *profile) {
+}
+
+void TranscoderImp::ReloadConfig()
+{
+ //formats.deleteAll();
+ for (auto format : formats)
+ {
+ delete format;
+ }
+ formats.clear();
+
+ BuildEncodableFormatsList(formats, winampParent, device);
+
+ transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
+ transrate = !!config->ReadInt(L"transrate",0);
+ translossless = !!config->ReadInt(L"translossless",0);
+ TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
+}
+
+void TranscoderImp::AddAcceptableFormat(unsigned int format)
+{
+ outformats.push_back(format);
+}
+
+void TranscoderImp::AddAcceptableFormat(wchar_t *format)
+{
+ outformats.push_back(stringToFourcc(format));
+}
+
+static bool FileExists(const wchar_t *file)
+{
+ return GetFileAttributesW(file) != INVALID_FILE_ATTRIBUTES;
+}
+
+static int getFileLength(const wchar_t * file, HWND winampParent) { // returns length in seconds
+ basicFileInfoStructW b={0};
+ b.filename=file;
+ SendMessage(winampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
+ return b.length;
+}
+
+static bool isFileLossless(const wchar_t * file) {
+ wchar_t ret[64] = {0};
+ if (AGAVE_API_METADATA && AGAVE_API_METADATA->GetExtendedFileInfo(file, L"lossless", ret, 64) && ret[0] == '1')
+ return true;
+ return false;
+}
+
+static int getFileBitrate(const wchar_t * file, HWND winampParent) { // returns bitrate in bits per second.
+ int secs = getFileLength(file,winampParent);
+ if(!secs) return 0;
+ FILE * f = _wfopen(file,L"rb");
+ int len = 0;
+ if(f) { fseek(f,0,2); len=ftell(f); fclose(f); }
+ return (len/secs)*8;
+}
+
+void TranscoderImp::GetTempFilePath(const wchar_t *ext, wchar_t *path) {
+ wchar_t dir[MAX_PATH] = {0};
+ GetTempPath(MAX_PATH,dir);
+ GetTempFileName(dir,L"transcode",0,path);
+ _wunlink(path);
+ wchar_t *e = wcsrchr(path,L'.');
+ if(e) *e=0;
+ wcscat(path,ext);
+ _wunlink(path);
+}
+
+void TranscoderImp::TranscodeProgress(int pc, bool done) {
+ if(!done) {
+ wchar_t buf[128] = {0};
+ StringCchPrintf(buf, ARRAYSIZE(buf), caption,pc);
+ if(callback) callback(callbackContext,buf);
+ }
+ else convertDone = done;
+}
+
+bool TranscoderImp::StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputFile, bool test) {
+ cfs.callbackhwnd = callbackhwnd;
+ cfs.sourcefile = _wcsdup(inputFile);
+ cfs.destfile = _wcsdup(outputFile);
+ cfs.destformat[0] = destformat;
+ cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
+ cfs.destformat[7] = (intptr_t)inifileA;
+ cfs.error = L"";
+ if(!SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILEW))
+ {
+ if(cfs.error && callback)
+ callback(callbackContext,cfs.error);
+ return false;
+ }
+ if(!test)
+ {
+ convertSetPriorityW csp = {&cfs,THREAD_PRIORITY_NORMAL};
+ SendMessage(winampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW);
+ TranscodeProgress(0,false);
+ }
+ return true;
+}
+
+void TranscoderImp::EndTranscode()
+{
+ cfs.callbackhwnd = NULL;
+ SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILE_END);
+ free(cfs.sourcefile);
+ free(cfs.destfile);
+ ZeroMemory(&cfs,sizeof(convertFileStruct));
+}
+
+bool TranscoderImp::TestTranscode(wchar_t * file, unsigned int destformat)
+{
+ wchar_t tempfn[MAX_PATH], ext[5]=L".";
+ fourccToString(destformat,&ext[1]);
+ GetTempFilePath(ext,tempfn);
+
+ convertFileStructW cfs;
+ cfs.callbackhwnd = callbackhwnd;
+ cfs.sourcefile = file;
+ cfs.destfile = tempfn;
+ cfs.destformat[0] = destformat;
+ cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
+ cfs.destformat[7] = (intptr_t)inifileA;
+ cfs.error = L"";
+
+ int v = SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERT_TEST);
+ _wunlink(tempfn);
+ return !!v;
+}
+
+bool TranscoderImp::FormatAcceptable(unsigned int format)
+{
+ int l = outformats.size();
+ for(int i=0; i<l; i++)
+ {
+ if(fourccEqual(outformats[i], format))
+ return true;
+ }
+ return false;
+}
+
+bool TranscoderImp::FormatAcceptable(wchar_t * format)
+{
+ return FormatAcceptable(stringToFourcc(format));
+}
+
+int TranscoderImp::GetOutputFormat(wchar_t * file, int *bitrate)
+{
+ if (!FileExists(file))
+ return 0;
+ int fourcc = config->ReadInt(L"lastusedencoder",' A4M');
+
+ if (TestTranscode(file,fourcc))
+ {
+ char buf[100]="128";
+ convertConfigItem ccs={fourcc,"bitrate",buf,100, inifileA};
+ SendMessage(winampParent,WM_WA_IPC,(WPARAM)&ccs,IPC_CONVERT_CONFIG_GET_ITEM);
+ int br = atoi(buf)*1000;
+ if(bitrate) *bitrate = br;
+ return fourcc;
+ }
+ return 0;
+}
+
+bool TranscoderImp::ShouldTranscode(wchar_t * file)
+{
+ if(TranscoderDisabled) return false;
+ wchar_t * ext = wcsrchr(file,L'.');
+ if(ext && FormatAcceptable(&ext[1])) {
+ if(transrate && getFileBitrate(file,winampParent) > 1000*transratethresh)
+ return true;
+ else if (translossless && isFileLossless(file))
+ return true;
+ else return false;
+ }
+ return true;
+}
+
+int TranscoderImp::CanTranscode(wchar_t * file, wchar_t * ext, int length)
+{
+ if(TranscoderDisabled) return -1;
+ int bitrate;
+ unsigned int fmt = GetOutputFormat(file,&bitrate);
+ if(fmt) {
+ if(ext) {
+ ext[0]=L'.'; ext[1]=0;
+ char extA[8]=".";
+ convertConfigItem c = {fmt,"extension",&extA[1],6,AutoCharDup(config->GetIniFile())};
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM);
+ if(extA[1]) wcsncpy(ext,AutoWide(extA), 10);
+ else fourccToString(fmt,&ext[1]);
+ free(c.configfile);
+ }
+ if (length <= 0)
+ length = getFileLength(file,winampParent);
+ return (bitrate/8) * length; // should transcode
+ }
+ return -1; // transcoding impossible
+}
+
+extern void filenameToItemRecord(wchar_t * file, itemRecordW * ice);
+extern void copyTags(itemRecordW * in, wchar_t * out);
+
+int TranscoderImp::TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callbackFunc)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption)
+{
+ if(caption) lstrcpyn(this->caption,caption,100);
+ this->callback = callbackFunc;
+ this->callbackContext = callbackContext;
+ convertDone = false;
+ int format = GetOutputFormat(inputFile);
+ if(!format) return -1;
+ if(!StartTranscode(format,inputFile,outputFile))
+ return -1;
+ while(!convertDone && !(*killswitch))
+ Sleep(50);
+ EndTranscode();
+
+ // copy the tags over
+ itemRecordW ice={0};
+ filenameToItemRecord(inputFile,&ice);
+ copyTags(&ice,outputFile);
+ freeRecord(&ice);
+
+ if(convertDone && callback)
+ callback(callbackContext,L"Done");
+ this->callback = NULL;
+ return convertDone?0:-1;
+}
+
+static void doConfigResizeChild(HWND parent, HWND child)
+{
+ if (child)
+ {
+ RECT r;
+ GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r);
+ ScreenToClient(parent, (LPPOINT)&r);
+ SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ ShowWindow(child, SW_SHOWNA);
+ }
+}
+
+struct ConfigTranscoderParam
+{
+ ConfigTranscoderParam()
+ {
+ winampParent=0;
+ configfile=0;
+ memset(&ccs, 0, sizeof(ccs));
+ config=0;
+ dev=0;
+ }
+
+ ~ConfigTranscoderParam()
+ {
+ //list.deleteAll();
+ for (auto l : list)
+ {
+ delete l;
+ }
+ list.clear();
+
+
+ free((char*)ccs.extra_data[7]);
+ free(configfile);
+ }
+ HWND winampParent;
+ wchar_t *configfile;
+ FormatList list;
+ convertConfigStruct ccs;
+ C_Config * config;
+ Device *dev;
+};
+
+static INT_PTR CALLBACK config_dlgproc_transcode_advanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static ConfigTranscoderParam *p;
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ p = (ConfigTranscoderParam *)lParam;
+ SetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,p->config->ReadString(L"forcetranscodingbitrate",L"250"));
+ if(p->config->ReadInt(L"transrate",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE,BST_CHECKED);
+ if(p->config->ReadInt(L"translossless",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE_LOSSLESS,BST_CHECKED);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ {
+ wchar_t buf[10]=L"";
+ GetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,buf,10);
+ p->config->WriteString(L"forcetranscodingbitrate",buf);
+ p->config->WriteInt(L"transrate",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE)?1:0);
+ p->config->WriteInt(L"translossless",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE_LOSSLESS)?1:0);
+ }
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+BOOL TranscoderImp::transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ static ConfigTranscoderParam *p;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ p = (ConfigTranscoderParam *)lParam;
+ if(p->config->ReadInt(L"enableTranscoder",1)) CheckDlgButton(hwndDlg,IDC_ENABLETRANSCODER,BST_CHECKED);
+
+ BuildEncodableFormatsList(p->list, p->winampParent, p->dev);
+ p->ccs.hwndParent = hwndDlg;
+
+ int encdef = p->config->ReadInt(L"lastusedencoder",0);
+ for(size_t i=0; i < p->list.size(); i++)
+ {
+ EncodableFormat * f = p->list[i];
+ int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc);
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f);
+ if(i==0 && encdef == 0) encdef = f->fourcc;
+ if(f->fourcc == encdef)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0);
+ p->ccs.format = f->fourcc;
+ }
+ }
+
+ p->ccs.hwndParent = hwndDlg;
+ HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_ENABLETRANSCODER:
+ p->config->WriteInt(L"enableTranscoder",IsDlgButtonChecked(hwndDlg,IDC_ENABLETRANSCODER)?1:0);
+ break;
+ case IDC_ADVANCED:
+ return WASABI_API_DIALOGBOXPARAMW(IDD_CONFIG_TRANSCODING_ADVANCED,hwndDlg,config_dlgproc_transcode_advanced,(LPARAM)p);
+ case IDC_ENCFORMAT:
+ if (HIWORD(wParam) != CBN_SELCHANGE) return 0;
+ {
+ int sel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
+ if (sel != CB_ERR)
+ {
+ SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
+ EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0);
+ p->ccs.format = f->fourcc;
+
+ HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
+ doConfigResizeChild(hwndDlg, h);
+ p->config->WriteInt(L"lastusedencoder",p->ccs.format);
+ }
+ }
+ break;
+ }
+ break;
+ case WM_DESTROY:
+ {
+ p->config->WriteInt(L"lastusedencoder",p->ccs.format);
+ SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
+ delete p;
+
+ for( TranscoderImp *l_transcoder : transcoders )
+ l_transcoder->ReloadConfig();
+ }
+ break;
+ }
+ return 0;
+}
+
+void* TranscoderImp::ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev)
+{
+ ConfigTranscoderParam * p = new ConfigTranscoderParam;
+ p->config = config;
+ p->winampParent=winampParent;
+ p->configfile=_wcsdup(config->GetIniFile());
+ p->ccs.extra_data[6] = mmioFOURCC('I','N','I',' ');
+ p->ccs.extra_data[7] = (int)AutoCharDup(p->configfile);
+ p->dev = dev;
+ return p;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transcoder_imp.h b/Src/Plugins/Library/ml_pmp/transcoder_imp.h
new file mode 100644
index 00000000..bb41a366
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transcoder_imp.h
@@ -0,0 +1,122 @@
+#ifndef _TRANSCODER_IMP_H_
+#define _TRANSCODER_IMP_H_
+
+#include <windows.h>
+#include <windowsx.h>
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../winamp/wa_ipc.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "DeviceView.h"
+#include "nu/AutoWide.h"
+#include "nu/AutoChar.h"
+#include "transcoder.h"
+#include "resource1.h"
+#include <wchar.h>
+#include <stdio.h>
+#include <vector>
+
+#define ENCODER_HIGHLY_PREFERRED 2
+#define ENCODER_PREFERRED 1
+#define ENCODER_NO_PREFERENCE 0
+#define ENCODER_NOT_PREFERRED -1
+#define ENCODER_DO_NOT_USE -2
+
+class EncodableFormat
+{
+public:
+ unsigned int fourcc;
+ wchar_t *desc;
+ EncodableFormat(unsigned int fourcc,wchar_t *desc) :
+ fourcc(fourcc)
+ {
+ this->desc = _wcsdup(desc);
+ }
+
+ ~EncodableFormat()
+ {
+ free(desc);
+ }
+};
+
+typedef std::vector<EncodableFormat*> FormatList;
+
+
+class TranscoderImp : public Transcoder
+{
+protected:
+ bool TranscoderDisabled;
+ bool transrate;
+ int transratethresh;
+ bool translossless;
+ wchar_t caption[100];
+ wchar_t inifile[MAX_PATH];
+ char inifileA[MAX_PATH];
+ HINSTANCE hInst;
+ HWND winampParent;
+ HWND callbackhwnd;
+ std::vector<unsigned int> outformats;
+ FormatList formats;
+ C_Config * config;
+ convertFileStructW cfs;
+ void (*callback)(void * callbackContext, wchar_t * status);
+ void* callbackContext;
+ bool convertDone;
+ Device *device;
+
+ void TranscodeProgress(int pc, bool done);
+ bool StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputfile, bool test=false); // returns true if started ok.
+ void EndTranscode();
+ bool TestTranscode(wchar_t * file, unsigned int destformat);
+ bool FormatAcceptable(wchar_t * format);
+ bool FormatAcceptable(unsigned int format);
+ int GetOutputFormat(wchar_t * file, int *bitrate=NULL);
+ void AddEncodableFormat(const char *desc, unsigned int fourcc);
+ void ReloadConfig();
+
+public:
+ TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device);
+ virtual ~TranscoderImp();
+
+ virtual void LoadConfigProfile(wchar_t *profile);
+ virtual void AddAcceptableFormat(wchar_t *format);
+ virtual void AddAcceptableFormat(unsigned int format);
+
+ // done when file is added to transfer queue
+ // returns:
+ // -1 for can't transcode
+ // output file size estimate if can transcode
+ // if ext is supplied, it should be a buffer with space for 5 characters, and will be filled with
+ // the output file type file extention, eg, L".mp3"
+ virtual int CanTranscode(wchar_t *file, wchar_t *ext = NULL, int length = -1);
+
+ virtual bool ShouldTranscode(wchar_t * file); // false if no transcoding needed
+
+ virtual int TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callback)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption=L"Transcoding %d%%");
+ // done just before transfer OR in background after file is added to queue
+ // extention is added to outputFile, allow 5 extra chars
+ // callback, callbackcontext and killswitch should be similar to those passed by ml_pmp
+
+ //virtual int TranscodeFileASync(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callback)(void * callbackContext, wchar_t * status), void* callbackContext){return 0;};
+ //asynchronous version of the above, details obvious
+
+ virtual void GetTempFilePath(const wchar_t *ext, wchar_t *filename); // get a filename which can be used as a staging area. ext should be, i.e L".mp3"
+
+ /* remember to call DestroyWindow on the return value when parent recieves the WM_DESTROY message.
+ use like this:
+ transcoderConfig = TranscoderImp::ConfigureTranscoder(hwndDlg,L"ml_pmp");
+ RECT r;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_PLACEHOLDER),&r);
+ ScreenToClient(hwndDlg,(LPPOINT)&r);
+ SetWindowPos(transcoderConfig,NULL,r.left,r.top,0,0,SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
+ ShowWindow(transcoderConfig,SW_SHOWNA);
+ where IDC_PLACEHOLDER is an invisible group box of size 259x176 (in dialog units)
+ */
+ static void* ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev);
+ static BOOL transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+ static void init();
+ static void quit();
+
+ friend LRESULT CALLBACK TranscodeMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+};
+
+#endif //_TRANSCODER_IMP_H_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transfer_thread.cpp b/Src/Plugins/Library/ml_pmp/transfer_thread.cpp
new file mode 100644
index 00000000..0399562e
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transfer_thread.cpp
@@ -0,0 +1,503 @@
+#include "DeviceView.h"
+#include <time.h>
+#include <shlwapi.h>
+#include "SkinnedListView.h"
+#include "metadata_utils.h"
+#include "IconStore.h"
+#include "api__ml_pmp.h"
+#include "resource1.h"
+#include "main.h"
+
+static void TransferCallback(void * callBackContext, wchar_t * status);
+extern void TransfersListUpdateItem(CopyInst * item);
+void TransfersListUpdateItem(CopyInst * item, DeviceView *view);
+extern void TransfersListPushPopItem(CopyInst * item);
+void TransfersListPushPopItem(CopyInst * item, DeviceView *view);
+
+extern HWND mainMessageWindow;
+extern HANDLE hMainThread;
+
+/*
+How to add new ways of copying files.
+Subclass CopyInst, over-ride CopyAction and Equals
+Optionally over-ride PreCopyAction, PostCopyAction and Cancelled
+
+Add to transfer queue as normal.
+*/
+
+SongCopyInst::SongCopyInst(DeviceView * dev, itemRecordW * song0)
+{
+ usesPreCopy = false;
+ usesPostCopy = true;
+ this->dev = dev;
+ equalsType = 0;
+ res = 0;
+ copyRecord(&song, song0);
+ songid = NULL;
+ status = STATUS_WAITING;
+ // status caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+
+ SYSTEMTIME system_time;
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, lastChanged, sizeof(lastChanged)/sizeof(wchar_t));
+
+ // make the itemRecord a little safer
+ if(!song.album) song.album = _wcsdup(L"");
+ if(!song.artist) song.artist = _wcsdup(L"");
+ if(!song.title) song.title = _wcsdup(L"");
+ if(!song.genre) song.genre = _wcsdup(L"");
+ if(!song.filename) song.filename = _wcsdup(L"");
+ if(!song.comment) song.comment = _wcsdup(L"");
+ if(!song.albumartist) song.albumartist = _wcsdup(L"");
+ if(!song.publisher) song.publisher = _wcsdup(L"");
+ if(!song.composer) song.composer = _wcsdup(L"");
+
+ // track caption
+ lstrcpyn(trackCaption, song.artist, 128);
+
+ int l = lstrlen(trackCaption);
+ if(128 - l > 1) lstrcpyn(trackCaption + l, L" - ", 128-l);
+ l = lstrlen(trackCaption);
+ if(128 - l > 1) lstrcpyn(trackCaption + l, song.title, 128 - l);
+
+ // type caption
+ WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER, typeCaption, sizeof(typeCaption)/sizeof(wchar_t));
+
+ // TODO fill out when other actions become done
+ // source and destination details
+ //this->dev->GetDisplayName(sourceDevice, sizeof(sourceDevice)/sizeof(wchar_t));
+ WASABI_API_LNGSTRINGW_BUF(IDS_LOCAL_MACHINE, sourceDevice, ARRAYSIZE(sourceDevice));
+ this->dev->GetDisplayName(destDevice, ARRAYSIZE(destDevice));
+
+ lstrcpynW(sourceFile, song.filename, sizeof(sourceFile)/sizeof(wchar_t));
+}
+
+SongCopyInst::~SongCopyInst()
+{
+ freeRecord(&song);
+}
+
+void SongCopyInst::Cancelled() {
+ // helps us to do appropriate handling
+ if (status == STATUS_TRANSFERRING)
+ {
+ dev->threadKillswitch = -2;
+ }
+ status = STATUS_CANCELLED;
+ WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_CANCELLED, statusCaption, ARRAYSIZE(statusCaption));
+
+ SYSTEMTIME system_time = {0};
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, lastChanged, ARRAYSIZE(lastChanged));
+
+ dev->dev->trackRemovedFromTransferQueue(&song);
+}
+
+static void TransferCallback(void * callBackContext, wchar_t * status)
+{
+ CopyInst * c = (CopyInst *)callBackContext;
+ if(!wcscmp(status, c->statusCaption)) return;
+
+ if (status && *status) lstrcpyn(c->statusCaption, status, sizeof(c->statusCaption)/sizeof(wchar_t));
+ TransfersListUpdateItem(c);
+ TransfersListUpdateItem(c, c->dev);
+ int pc=0;
+ // copes with 'transferring %'
+ if(swscanf(status,L"%*s %d%%",&pc))
+ {
+ if (c->dev->isCloudDevice) cloudTransferProgress = pc;
+ else c->dev->currentTransferProgress = pc;
+ }
+ // copes with 'transferring (%)'
+ else if(swscanf(status,L"%*s %*1c %d%%",&pc))
+ {
+ if (c->dev->isCloudDevice) cloudTransferProgress = pc;
+ else c->dev->currentTransferProgress = pc;
+ }
+ c->dev->UpdateSpaceInfo(TRUE, TRUE);
+}
+
+bool SongCopyInst::CopyAction()
+{
+ int r = dev->dev->transferTrackToDevice(&song,this,TransferCallback,&songid,&dev->threadKillswitch);
+ if (r==0 && AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::PMP_TRANSFER_COUNT);
+
+ dev->dev->trackRemovedFromTransferQueue(&song);
+ return r!=0;
+}
+
+void SongCopyInst::PostCopyAction()
+{
+ if(status == STATUS_DONE && songid)
+ {
+ dev->dev->addTrackToPlaylist(0, songid);
+
+ if (dev->metadata_fields & SUPPORTS_ALBUMART)
+ {
+ int w,h;
+ ARGB32 *bits;
+ if (AGAVE_API_ALBUMART->GetAlbumArt_NoAMG(song.filename, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS)
+ {
+ dev->dev->setArt(songid,bits,w,h);
+ WASABI_API_MEMMGR->sysFree(bits);
+ }
+ }
+ }
+}
+
+bool SongCopyInst::Equals(CopyInst * b) {
+ if(this->equalsType == b->equalsType) {
+ SongCopyInst * c = (SongCopyInst*)b;
+ bool ret = (compareItemRecords(&this->song,&c->song) == 0);
+
+ // for cloud then we do some extra checks to allow different formats
+ // and also for sending the same file to a different cloud device...
+ if (c->dev->isCloudDevice)
+ {
+ const wchar_t * mime_1 = getRecordExtendedItem(&c->song, L"mime");
+ const wchar_t * mime_2 = getRecordExtendedItem(&this->song, L"mime");
+ int mime_match = lstrcmpiW(mime_1 ? mime_1 : L"", mime_2 ? mime_2 : L"");
+ int device_match = lstrcmpiW(c->destDevice ? c->destDevice : L"", this->destDevice ? this->destDevice : L"");
+
+ if (!device_match)
+ {
+ if (!mime_match)
+ {
+ return ret;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return ret;
+ }
+ return false;
+}
+
+PlaylistCopyInst::PlaylistCopyInst(DeviceView * dev, itemRecordW * song, wchar_t * plName0, int plid0) : SongCopyInst(dev,song)
+{
+ lstrcpyn(plName,plName0,255);
+ plid=plid0;
+ plAddSongs = NULL;
+ usesPreCopy = false;
+ usesPostCopy = true;
+}
+
+PlaylistCopyInst::~PlaylistCopyInst()
+{
+ SongCopyInst::~SongCopyInst();
+ if(plAddSongs) delete plAddSongs;
+}
+
+bool PlaylistCopyInst::PreCopyAction() {return false;}
+
+void PlaylistCopyInst::PostCopyAction() {
+ SongCopyInst::PostCopyAction();
+ if(!plAddSongs || plid == -1) return;
+ if(plid >= dev->dev->getPlaylistCount()) return;
+ int l = plAddSongs->GetSize();
+ wchar_t pln[256] = {0};
+ dev->dev->getPlaylistName(plid,pln,255);
+ if(wcscmp(pln,plName) == 0) {
+ if(status == STATUS_DONE && songid) dev->dev->addTrackToPlaylist(plid,songid);
+ for(int i=0; i < l; i++) {
+ songid_t s = (songid_t)plAddSongs->Get(i);
+ if(s) dev->dev->addTrackToPlaylist(plid,s);
+ }
+ }
+}
+
+ReverseCopyInst::ReverseCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, bool addToLibrary, bool uppercaseext) : uppercaseext(uppercaseext) {
+ usesPreCopy = false;
+ usesPostCopy = addToLibrary;
+ this->dev = dev;
+ equalsType = 1;
+ this->songid = song;
+ status = STATUS_WAITING;
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+ WASABI_API_LNGSTRINGW_BUF(IDS_COPY_TO_LIBRARY, typeCaption, sizeof(typeCaption)/sizeof(wchar_t));
+ dev->dev->getTrackArtist(song,trackCaption,128);
+ int l = lstrlen(trackCaption);
+ if(128 - l > 1) lstrcpyn(trackCaption + l,L" - ",128-l);
+ l = lstrlen(trackCaption);
+ if(128 - l > 1) dev->dev->getTrackTitle(song,trackCaption + l,128 - l);
+
+ // find path for song
+ lstrcpyn(path,format,2036);
+ FixReplacementVars(path,2036,dev->dev,song);
+ PathCombine(path,filepath,path);
+}
+
+bool ReverseCopyInst::Equals(CopyInst *b) {
+ if(this->equalsType == b->equalsType) {
+ ReverseCopyInst* c = (ReverseCopyInst*)b;
+ return (c->dev == this->dev) && (c->songid == this->songid);
+ }
+ return false;
+}
+
+bool ReverseCopyInst::CopyAction() { //Return true if failed.
+ wchar_t * lastslash = wcsrchr(path,L'\\');
+ if(!lastslash) {
+ WASABI_API_LNGSTRINGW_BUF(IDS_INVALID_PATH, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+ return true;
+ }
+ *lastslash=0;
+ if(RecursiveCreateDirectory(path)) {
+ WASABI_API_LNGSTRINGW_BUF(IDS_INVALID_PATH, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
+ return true;
+ }
+ *lastslash=L'\\';
+ // path created, copy file over.
+ int r = dev->dev->copyToHardDrive(songid, path, this, TransferCallback, &dev->threadKillswitch);
+ return r!=0;
+}
+
+void ReverseCopyInst::PostCopyAction()
+{
+ itemRecordW ice={0};
+ filenameToItemRecord(path,&ice);
+
+ ice.rating = dev->dev->getTrackRating(songid);
+ ice.playcount = dev->dev->getTrackPlayCount(songid);
+ ice.lastplay = dev->dev->getTrackLastPlayed(songid);
+ ice.lastupd = dev->dev->getTrackLastUpdated(songid);
+ ice.type = dev->dev->getTrackType(songid);
+
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&ice,ML_IPC_DB_ADDORUPDATEITEMW);
+ freeRecord(&ice);
+}
+
+ReversePlaylistCopyInst::ReversePlaylistCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, wchar_t * playlistFile0, wchar_t * playlistName0, bool last,bool addToLibrary)
+ : ReverseCopyInst(dev,filepath,format,song,addToLibrary,false), last(last) {
+ lstrcpyn(playlistFile,playlistFile0,MAX_PATH);
+ lstrcpyn(playlistName,playlistName0,128);
+}
+
+bool ReversePlaylistCopyInst::CopyAction()
+{
+ bool r = ReverseCopyInst::CopyAction();
+ return r;
+}
+
+void ReversePlaylistCopyInst::PostCopyAction()
+{
+ ReverseCopyInst::PostCopyAction();
+ FILE * f = _wfopen(playlistFile,L"at");
+ if(f) {
+ fputws(L"#EXTINF:",f);
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"%d",dev->dev->getTrackLength(songid)/1000);
+ fputws(buf,f);
+ fputws(L",",f);
+ wchar_t title[2048] = {0};
+ getTitle(dev->dev,songid,path,title,2048);
+ fputws(title,f);
+ fputws(L"\n",f);
+ fputws(path,f);
+ fputws(L"\n",f);
+ fclose(f);
+ }
+ if(last) {
+ mlAddPlaylist a = {sizeof(mlAddPlaylist),playlistName,playlistFile,PL_FLAG_SHOW | PL_FLAGS_IMPORT,-1,-1};
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&a,ML_IPC_PLAYLIST_ADD);
+ _wunlink(playlistFile);
+ }
+}
+
+// HERE BE DRAGONS
+static VOID CALLBACK APC_PreCopy(ULONG_PTR dwParam) {
+ CopyInst * a = (CopyInst *)dwParam;
+ a->res = a->PreCopyAction()?2:1;
+}
+
+static VOID CALLBACK APC_PostCopy(ULONG_PTR dwParam) {
+ CopyInst * a = (CopyInst *)dwParam;
+ a->PostCopyAction();
+ a->res = 1;
+}
+
+void CALLBACK TransferNavTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
+{
+ TransferContext *context = (TransferContext *)eventId;
+ if (context && context->dev->queueTreeItem)
+ {
+ NAVITEM item;
+ item.cbSize = sizeof(NAVITEM);
+ item.hItem = context->dev->queueTreeItem;
+ item.iSelectedImage = item.iImage = icon_store.GetQueueIcon(context->dev->queueActiveIcon);
+ context->dev->queueActiveIcon = (context->dev->queueActiveIcon + 1) % 4;
+ item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
+ MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
+ }
+}
+
+void TransferContext::DoOneTransfer(HANDLE handle)
+{
+ if (TryEnterCriticalSection(&transfer_lock))
+ {
+ if(dev->threadKillswitch == 1)
+ {
+ WASABI_API_THREADPOOL->RemoveHandle(transfer_thread, handle);
+ dev->threadKillswitch = 100;
+ SetEvent(killer);
+ LeaveCriticalSection(&transfer_lock);
+ return;
+ }
+
+ if (IsPaused())
+ {
+ LeaveCriticalSection(&transfer_lock);
+ return;
+ }
+
+ LinkedQueue * txQueue = getTransferQueue(this->dev);
+ CopyInst * c = (txQueue ? (CopyInst *)txQueue->Peek() : NULL);
+ if (c)
+ {
+ if (c->res != 2 && c->status != STATUS_CANCELLED)
+ {
+ c->status = STATUS_TRANSFERRING;
+ start = time(NULL);
+ TransferCallback(c, WASABI_API_LNGSTRINGW_BUF(IDS_STARTING_TRANSFER, c->statusCaption, sizeof(c->statusCaption)/sizeof(wchar_t)));
+ c->res = 0;
+ if(c->usesPreCopy)
+ SynchronousProcedureCall(APC_PreCopy,(ULONG_PTR)c);
+ }
+ if(dev->threadKillswitch)
+ {
+ WASABI_API_THREADPOOL->RemoveHandle(transfer_thread, handle);
+ dev->threadKillswitch = 100;
+ SetEvent(killer);
+ LeaveCriticalSection(&transfer_lock);
+ return;
+ }
+ if(c->res == 2)
+ { // dupe
+ WASABI_API_LNGSTRINGW_BUF((dev->isCloudDevice ? IDS_ALREADY_UPLOADED : IDS_DUPLICATE), c->statusCaption, sizeof(c->statusCaption)/sizeof(wchar_t));
+ c->status = STATUS_DONE;
+ }
+ else if (c->status != STATUS_CANCELLED)
+ {
+ // do the transfer
+ int r = c->CopyAction();
+ c->status = (r == -1 ? STATUS_ERROR : STATUS_DONE);
+
+ SYSTEMTIME system_time = {0};
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, c->lastChanged, sizeof(c->lastChanged)/sizeof(wchar_t));
+
+ // Now do whatever needs to be done post-copy (add to playlist or whatever)
+ c->res = 0;
+ if(c->usesPostCopy && c->status == STATUS_DONE)
+ SynchronousProcedureCall(APC_PostCopy,(ULONG_PTR)c);
+ // now work out the moving average time per transfer
+ end = time(NULL);
+ if(c->status == STATUS_DONE)
+ {
+ times[numTransfers % AVERAGEBASIS] = (int)((long)end - (long)start);
+ numTransfers++;
+ }
+ int n = min(AVERAGEBASIS,numTransfers);
+ if(n > 0)
+ {
+ int t = 0;
+ for(int i = 0; i < n; i++) t += times[i];
+ dev->transferRate = ((double)t) / ((double)n);
+ }
+ }
+ if(dev->threadKillswitch == 2)
+ { // a transfer has been cancelled part way through
+ dev->threadKillswitch = 0;
+ delete txQueue->Poll();
+ }
+ else
+ {
+ LinkedQueue * finishedTX = getFinishedTransferQueue(this->dev);
+ if (finishedTX)
+ {
+ txQueue->lock();
+ finishedTX->lock();
+ finishedTX->Offer(txQueue->Poll());
+ finishedTX->unlock();
+ txQueue->unlock();
+ TransfersListPushPopItem(c);
+ TransfersListPushPopItem(c, dev);
+ }
+ }
+ dev->commitNeeded = true;
+ if (dev->isCloudDevice) cloudTransferProgress = 0;
+ else dev->currentTransferProgress = 0;
+
+ LeaveCriticalSection(&transfer_lock);
+ SetEvent(handle);
+ }
+ else
+ {
+ if(dev->commitNeeded)
+ PostMessage(mainMessageWindow,WM_TIMER,COMMITTIMERID,0);
+ if (dev->isCloudDevice) cloudTransferProgress = 0;
+ else dev->currentTransferProgress = 0;
+ dev->UpdateActivityState();
+ LeaveCriticalSection(&transfer_lock);
+ }
+ }
+}
+
+int TransferThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
+{
+ TransferContext *context = (TransferContext *)user_data;
+ SetTimer(plugin.hwndLibraryParent, (UINT_PTR)user_data, 1000, TransferNavTimer);
+ // allows cancels to continue even if a cloud device upload had been cancelled
+ if (context->dev->threadKillswitch == -2) context->dev->threadKillswitch = 0;
+ context->DoOneTransfer(handle);
+ KillTimer(plugin.hwndLibraryParent, (UINT_PTR)user_data);
+ return 0;
+}
+
+bool TransferContext::IsPaused()
+{
+ return (paused_all || paused);
+}
+
+void TransferContext::Pause()
+{
+ if (1 == InterlockedIncrement(&paused))
+ {
+ if(dev->commitNeeded)
+ PostMessage(mainMessageWindow,WM_TIMER,COMMITTIMERID,0);
+
+ SetEvent(notifier);
+ }
+}
+
+void TransferContext::Resume()
+{
+ if (0 == InterlockedDecrement(&paused))
+ {
+ SetEvent(notifier);
+ }
+}
+
+bool TransferContext::IsAllPaused()
+{
+ return paused_all?true:false;
+}
+
+void TransferContext::PauseAll()
+{
+ InterlockedIncrement(&paused_all);
+}
+
+void TransferContext::ResumeAll()
+{
+ InterlockedDecrement(&paused_all);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/transfer_thread.h b/Src/Plugins/Library/ml_pmp/transfer_thread.h
new file mode 100644
index 00000000..0e317041
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/transfer_thread.h
@@ -0,0 +1,94 @@
+#ifndef __TRANSFER_THREAD_
+#define __TRANSFER_THREAD_
+#include <wchar.h>
+
+class DeviceView;
+
+#define STATUS_ERROR -1
+#define STATUS_WAITING 0
+#define STATUS_TRANSFERRING 1
+#define STATUS_DONE 3
+#define STATUS_CANCELLED 4
+
+extern int SynchronousProcedureCall(void * p, ULONG_PTR dwParam);
+extern BOOL RecursiveCreateDirectory(wchar_t* buf1); // from replaceVars.cpp
+extern wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song); // from replaceVars.cpp
+
+class CopyInst {
+public:
+ CopyInst() : status(STATUS_WAITING), res(0), dev(NULL), equalsType(-1), usesPreCopy(false), usesPostCopy(true), songid(NULL) {
+ typeCaption[0] = 0;
+ statusCaption[0] = 0;
+ trackCaption[0] = 0;
+ sourceDevice[0] = 0;
+ destDevice[0] = 0;
+ lastChanged[0] = 0;
+ sourceFile[0] = 0;
+ }
+ virtual bool PreCopyAction() {return false;} // called in main window thread. Return true to skip.
+ virtual bool CopyAction()=0; // Do the actual transfer, called in transfer thread. Return true if failed.
+ virtual void PostCopyAction() {} // called in main window thread
+ virtual void Cancelled() {} // the transfer has been cancelled
+ virtual bool Equals(CopyInst * b)=0; // use equalsType to check if it is safe to cast b to your own type. Return true if equal.
+ int status; // one of STATUS_*
+ int res; // don't mess with this!
+ DeviceView * dev;
+ wchar_t typeCaption[128];
+ wchar_t statusCaption[128];
+ wchar_t trackCaption[128];
+ wchar_t sourceDevice[128];
+ wchar_t destDevice[128];
+ wchar_t lastChanged[128];
+ wchar_t sourceFile[MAX_PATH];
+ int equalsType; // 0 for SongCopyInst, 1 for ReverseCopyInst, -1 for unknown
+ bool usesPreCopy;
+ bool usesPostCopy;
+ songid_t songid;
+};
+
+class SongCopyInst : public CopyInst {
+public:
+ SongCopyInst(DeviceView * dev,itemRecordW * song);
+ virtual ~SongCopyInst();
+ itemRecordW song;
+ virtual bool CopyAction();
+ virtual void PostCopyAction();
+ virtual void Cancelled();
+ virtual bool Equals(CopyInst * b);
+};
+
+class PlaylistCopyInst : public SongCopyInst {
+public:
+ wchar_t plName[256];
+ int plid;
+ C_ItemList * plAddSongs;
+ PlaylistCopyInst(DeviceView * dev, itemRecordW * song, wchar_t * plName0, int plid0);
+ virtual ~PlaylistCopyInst();
+ virtual bool PreCopyAction();
+ virtual void PostCopyAction();
+};
+
+class ReverseCopyInst : public CopyInst {
+public:
+ ReverseCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, bool addToLibrary, bool uppercaseext);
+ virtual bool CopyAction(); // Do the actual transfer, called in transfer thread. Return true if failed.
+ virtual void PostCopyAction();
+ virtual bool Equals(CopyInst * b); // use equalsType to check if it is safe to cast b to your own type. Return true if equal.
+ wchar_t path[2048];
+ bool uppercaseext;
+};
+
+// simple subclass which appends the copied filename to an m3u playlist file after copy.
+class ReversePlaylistCopyInst : public ReverseCopyInst {
+public:
+ ReversePlaylistCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, wchar_t * playlistFile, wchar_t * playlistName, bool last,bool addToLibrary=true);
+ virtual bool CopyAction();
+ virtual void PostCopyAction();
+ wchar_t playlistFile[MAX_PATH];
+ wchar_t playlistName[128];
+ bool last;
+};
+
+//DWORD WINAPI ThreadFunc_Transfer(LPVOID lpParam);
+
+#endif //__TRANSFER_THREAD_ \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/version.rc2 b/Src/Plugins/Library/ml_pmp/version.rc2
new file mode 100644
index 00000000..af678fba
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 2,25,0,0
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp Media Library Plug-in"
+ VALUE "FileVersion", "2,25,0,0"
+ VALUE "InternalName", "Nullsoft Portable Music Player Support"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_pmp.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_pmp/view_pmp_devices.cpp b/Src/Plugins/Library/ml_pmp/view_pmp_devices.cpp
new file mode 100644
index 00000000..42937088
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/view_pmp_devices.cpp
@@ -0,0 +1,764 @@
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "resource1.h"
+#include "SkinnedListView.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include "metadata_utils.h"
+
+static int (*wad_handleDialogMsgs)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void (*wad_DrawChildWindowBorders)(HWND hwndDlg, int *tab, int tabsize);
+static void (*cr_init)(HWND hwndDlg, ChildWndResizeItem *list, int num);
+static void (*cr_resize)(HWND hwndDlg, ChildWndResizeItem *list, int num);
+
+
+static HWND m_hwnd;
+
+static ChildWndResizeItem resize_rlist[]=
+{
+ {IDC_LIST_DEVICES, 0x0010},
+ {IDC_LIST_TRANSFERS, 0x0011},
+ {IDC_TQ_STATIC, 0x0000},
+ {IDC_HDELIM, 0x0010},
+ {IDC_BUTTON_PAUSETRANSFERS, 0x0101},
+ {IDC_BUTTON_CLEARFINISHED, 0x0101},
+ {IDC_BUTTON_REMOVESELECTED, 0x0101},
+ {IDC_STATUS,0x0111},
+};
+static int m_nodrawtopborders=0,adiv_clickoffs,adivpos=-1;
+SkinnedListView * listDevices=NULL;
+static SkinnedListView * listTransfers=NULL;
+//CRITICAL_SECTION listTransfersLock;
+
+static INT_PTR CALLBACK adiv_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+static void adiv_UpdPos(int xp);
+static WNDPROC adiv_oldWndProc;
+
+class DeviceContents : public ListContents
+{
+public:
+ virtual int GetNumColumns()
+ {
+ return 2;
+ }
+ virtual int GetNumRows()
+ {
+ return devices.GetSize();
+ }
+ virtual wchar_t * GetColumnTitle(int num)
+ {
+ switch (num)
+ {
+ case 0: return WASABI_API_LNGSTRINGW(IDS_NAME);
+ case 1: return WASABI_API_LNGSTRINGW(IDS_CAPACITY_FREE);
+ }
+ return L"";
+ }
+ virtual int GetColumnWidth(int num)
+ {
+ switch (num)
+ {
+ case 0: return global_config->ReadInt(L"devices_col0_width",150);
+ case 1: return global_config->ReadInt(L"devices_col1_width",100);
+ default: return 0;
+ }
+ }
+ virtual void ColumnResize(int col, int newWidth)
+ {
+ if (col==0) global_config->WriteInt(L"devices_col0_width",newWidth);
+ else if (col==1) global_config->WriteInt(L"devices_col1_width",newWidth);
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)
+ {
+ switch (col)
+ {
+ case 0:
+ ((DeviceView *)devices.Get(row))->dev->getPlaylistName(0,buf,buflen);
+ return;
+ case 1:
+ {
+ wchar_t capacity[20]=L"";
+ Device * dev = ((DeviceView *)devices.Get(row))->dev;
+ WASABI_API_LNG->FormattedSizeString(capacity, ARRAYSIZE(capacity), dev->getDeviceCapacityTotal());
+ __int64 cap = dev->getDeviceCapacityTotal();
+ int pc;
+ if (cap) pc = (int)((((__int64)100)*dev->getDeviceCapacityAvailable()) / cap);
+ else pc = 0;
+ wsprintf(buf,L"%s (%d%%)",capacity,pc);
+ }
+ return;
+ }
+ }
+ virtual songid_t GetTrack(int pos) { return 0; }
+};
+
+static DeviceContents deviceListContents;
+
+class TransferItemShadow
+{
+public:
+ CopyInst * c;
+ wchar_t * status, * type, * track;
+ wchar_t device[128];
+ bool changed;
+ TransferItemShadow(CopyInst * c)
+ {
+ changed = false;
+ this->c = c;
+ status = _wcsdup(c->statusCaption);
+ type = _wcsdup(c->typeCaption);
+ track = _wcsdup(c->trackCaption);
+ device[0] = 0;
+ c->dev->dev->getPlaylistName(0,device,128);
+ }
+ ~TransferItemShadow()
+ {
+ free(status);
+ free(type);
+ free(track);
+ }
+ bool Equals(TransferItemShadow * a)
+ {
+ if (!a) return false;
+ return (c == a->c) && !wcscmp(track,a->track) && !wcscmp(status,a->status) && !wcscmp(device,a->device) && !wcscmp(type,a->type);
+ }
+};
+
+static C_ItemList *getTransferListShadow()
+{
+ C_ItemList * list = new C_ItemList;
+
+ for (int i=0; i < devices.GetSize(); i++)
+ {
+ DeviceView *device = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(device);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ if (txQueue)
+ {
+ txQueue->lock();
+ int l = txQueue->GetSize();
+
+ for (int j=0; j<l; j++)
+ list->Add(new TransferItemShadow((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ int l = finishedTX->GetSize();
+
+ for (int j=0; j<l; j++)
+ list->Add(new TransferItemShadow((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+ }
+ return list;
+}
+
+class TransferContents2 : public ListContents
+{
+public:
+ CRITICAL_SECTION cs;
+ C_ItemList * listShadow;
+ TransferContents2() : listShadow(0)
+ {
+ oldSize = 0;
+ listShadow = getTransferListShadow();
+ InitializeCriticalSection(&cs);
+ }
+ virtual ~TransferContents2()
+ {
+ DeleteCriticalSection(&cs); delete listShadow;
+ }
+ virtual int GetNumColumns()
+ {
+ return 4;
+ }
+ virtual int GetNumRows()
+ {
+ return (listShadow ? listShadow->GetSize() : 0);
+ }
+ virtual wchar_t * GetColumnTitle(int num)
+ {
+ switch (num)
+ {
+ case 0: return WASABI_API_LNGSTRINGW(IDS_TYPE);
+ case 1: return WASABI_API_LNGSTRINGW(IDS_TRACK);
+ case 2: return WASABI_API_LNGSTRINGW(IDS_STATUS);
+ case 3: return WASABI_API_LNGSTRINGW(IDS_DEVICE);
+ }
+ return L"";
+ }
+ virtual int GetColumnWidth(int num)
+ {
+ switch (num)
+ {
+ case 0: return global_config->ReadInt(L"transfers_col0_width",100);
+ case 1: return global_config->ReadInt(L"transfers_col1_width",300);
+ case 2: return global_config->ReadInt(L"transfers_col2_width",150);
+ case 3: return global_config->ReadInt(L"transfers_col3_width",150);
+ default: return 0;
+ }
+ }
+ virtual void ColumnResize(int col, int newWidth)
+ {
+ if (col==0) global_config->WriteInt(L"transfers_col0_width",newWidth);
+ else if (col==1) global_config->WriteInt(L"transfers_col1_width",newWidth);
+ else if (col==2) global_config->WriteInt(L"transfers_col2_width",newWidth);
+ else if (col==3) global_config->WriteInt(L"transfers_col3_width",newWidth);
+ }
+ void lock()
+ {
+ EnterCriticalSection(&cs);
+ }
+ void unlock()
+ {
+ LeaveCriticalSection(&cs);
+ }
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)
+ {
+ lock();
+ if (row >= listShadow->GetSize())
+ {
+ unlock(); return;
+ }
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(row);
+ switch (col)
+ {
+ case 0: lstrcpyn(buf,t->type,buflen); break;
+ case 1: lstrcpyn(buf,t->track,buflen); break;
+ case 2: lstrcpyn(buf,t->status,buflen); break;
+ case 3: lstrcpyn(buf,t->device,buflen); break;
+ }
+ unlock();
+ }
+
+ void PushPopItem(CopyInst *c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ t->changed=true;
+ listShadow->Del(i);
+ listShadow->Add(t);
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd,LVM_DELETEITEM,i,0);
+ PostMessage(hwnd,LVM_SETITEMCOUNT,size,LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+
+ void ItemUpdate(CopyInst * c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ TransferItemShadow * n = new TransferItemShadow(c);
+ n->changed=true;
+ listShadow->Set(i,n);
+ delete t;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+ int oldSize;
+ void FullUpdate()
+ {
+ C_ItemList * newListShadow = getTransferListShadow();
+ int newSize = newListShadow->GetSize();
+ lock();
+ oldSize = listShadow->GetSize();
+ for (int i=0; i<newSize; i++)
+ {
+ TransferItemShadow * newt = (TransferItemShadow *)newListShadow->Get(i);
+ TransferItemShadow * oldt = i<oldSize?(TransferItemShadow *)listShadow->Get(i):NULL;
+ newt->changed = !newt->Equals(oldt);
+ }
+
+ C_ItemList * oldListShadow = listShadow;
+ listShadow = newListShadow;
+ for (int i=0; i<oldListShadow->GetSize(); i++) delete(TransferItemShadow *)oldListShadow->Get(i);
+ delete oldListShadow;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ if (newSize != oldSize) PostMessage(hwnd,LVM_SETITEMCOUNT,newSize, 0);
+ for (int i=0; i<newSize; i++)
+ {
+ TransferItemShadow * t = (TransferItemShadow *)listShadow->Get(i);
+ if (t->changed) PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ }
+ unlock();
+ }
+ virtual songid_t GetTrack(int pos) { return 0; }
+};
+
+static void updateStatus()
+{
+ int pcnum=0,num=0,time=0,total=0;
+
+ for (int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView* device = (DeviceView*)devices.Get(i);
+ LinkedQueue * txQueue = getTransferQueue(device);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ int txProgress = getTransferProgress(device);
+ int s = (txQueue ? txQueue->GetSize() : 0);
+ int n = (s * 100) - txProgress;
+ total += s * 100;
+ total += 100 * (finishedTX ? finishedTX->GetSize() : 0);
+ pcnum += n;
+ num += s;
+ int t = (int)(device->transferRate * (((double)n) / 100.0));
+ if (time < t) time = t;
+ }
+ if (total)
+ {
+ wchar_t caption[256] = {0};
+ int pc = ((total-pcnum)*100)/total;
+ wsprintf(caption,WASABI_API_LNGSTRINGW((time > 0 ? IDS_TRANFERS_PERCENT_REMAINING : IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME)),num,pc,time/60,time%60);
+ SetDlgItemText(m_hwnd,IDC_STATUS,caption);
+ }
+ else SetDlgItemText(m_hwnd,IDC_STATUS,L"");
+}
+
+static TransferContents2 transferListContents;
+
+void TransfersListUpdateItem(CopyInst * item)
+{
+ transferListContents.ItemUpdate(item);
+}
+
+void TransfersListPushPopItem(CopyInst * item)
+{
+ transferListContents.PushPopItem(item);
+}
+
+void UpdateDevicesListView(bool softUpdate)
+{
+ if (listDevices) listDevices->UpdateList(softUpdate);
+}
+
+static bool AddSelectedItems(C_ItemList *items, W_ListView *listview, LinkedQueue *transfer_queue, int &row, DeviceView *&dev)
+{
+ transfer_queue->lock();
+ int l = transfer_queue->GetSize();
+ for (int j=0; j<l; j++)
+ {
+ if (listview->GetSelected(row++))
+ {
+ CopyInst * c = (CopyInst *)transfer_queue->Get(j);
+ if (c->songid)
+ {
+ if (!dev && c->dev) dev = c->dev;
+ if (dev)
+ {
+ if (c->dev != dev)
+ {
+ transfer_queue->unlock();
+ return false;
+ }
+ else
+ items->Add((void*)c->songid);
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+ return true;
+}
+
+static void RemoveSelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, int &row, bool finished_queue)
+{
+ transfer_queue->lock();
+ int j = transfer_queue->GetSize();
+ while (j-- > 0)
+ {
+ if (listview->GetSelected(--row))
+ {
+ if (j == 0 && !finished_queue)
+ {
+ CopyInst * d = (CopyInst *)transfer_queue->Get(j);
+ if (d && d->status == STATUS_WAITING && device->transferContext.IsPaused())
+ {
+ transfer_queue->Del(j);
+ d->Cancelled();
+ delete d;
+ } // otherwise don't bother
+ }
+ else
+ {
+ CopyInst * d = (CopyInst*)transfer_queue->Del(j);
+ if (d)
+ {
+ if (d->status == STATUS_WAITING && !finished_queue)
+ d->Cancelled();
+ delete d;
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+}
+
+extern void handleContextMenuResult(int r, C_ItemList * items=NULL, DeviceView * dev=NULL);
+extern int showContextMenu(int context,HWND hwndDlg, Device * dev, POINT pt);
+extern int (*wad_getColor)(int idx);
+
+INT_PTR CALLBACK pmp_devices_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if (wad_handleDialogMsgs)
+ {
+ BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (listDevices)
+ {
+ BOOL a=listDevices->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ if (listTransfers)
+ {
+ BOOL a=listTransfers->DialogProc(hwndDlg,uMsg,wParam,lParam); if (a) return a;
+ }
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ m_hwnd=hwndDlg;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&cr_init=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,32,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&cr_resize=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,33,ML_IPC_SKIN_WADLG_GETFUNC);
+ if (cr_init) cr_init(hwndDlg,resize_rlist,sizeof(resize_rlist)/sizeof(resize_rlist[0]));
+ listDevices = new SkinnedListView(&deviceListContents,IDC_LIST_DEVICES,plugin.hwndLibraryParent, hwndDlg);
+ listDevices->DialogProc(hwndDlg,uMsg,0,0);
+ transferListContents.lock();
+ listTransfers = new SkinnedListView(&transferListContents,IDC_LIST_TRANSFERS,plugin.hwndLibraryParent, hwndDlg);
+ listTransfers->DialogProc(hwndDlg,uMsg,0,0);
+ transferListContents.unlock();
+
+ adiv_oldWndProc=(WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_HDELIM),GWLP_WNDPROC,(LONG_PTR)adiv_newWndProc);
+
+ SetDlgItemText(hwndDlg,IDC_BUTTON_PAUSETRANSFERS,WASABI_API_LNGSTRINGW((TransferContext::IsAllPaused()?IDS_RESUME:IDS_PAUSE)));
+ SetTimer(hwndDlg,1,250,NULL);
+ updateStatus();
+ break;
+ case WM_TIMER:
+ if (wParam == 1)
+ {
+ updateStatus();
+ if (device_update_map[0])
+ {
+ device_update_map[0]=0;
+ transferListContents.FullUpdate();
+ }
+ }
+ break;
+ case WM_DISPLAYCHANGE:
+ break;
+ case WM_SETCURSOR: // set cursor when near the dividers
+ {
+ RECT r;
+ POINT p;
+ GetCursorPos(&p);
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_HDELIM),&r);
+ r.top-=3;
+ r.bottom+=3;
+ if (PtInRect(&r,p))
+ {
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ return uMsg == WM_SETCURSOR;
+ }
+
+ break;
+ }
+ case WM_LBUTTONDOWN:
+ {
+ // forward dialog clicks to the dividers if they get near them
+ POINT p;
+ RECT r3;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_VDELIM),&r3);
+ GetCursorPos(&p);
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_HDELIM),&r3);
+ if (p.x >= r3.left && p.x <= r3.right)
+ {
+ int d=p.y-r3.bottom;
+ int d2=p.y-r3.top;
+ if (d<0)d=-d;
+ if (d2<0)d2=-d2;
+ if (d < 6 || d2 < 6) SendDlgItemMessage(hwndDlg,IDC_HDELIM,uMsg,0,0);
+ }
+ }
+ break;
+ case WM_SIZE:
+ if (wParam != SIZE_MINIMIZED)
+ {
+ if (cr_resize) cr_resize(hwndDlg,resize_rlist,sizeof(resize_rlist)/sizeof(resize_rlist[0]));
+ }
+ break;
+ case WM_PAINT:
+ {
+ if (wad_DrawChildWindowBorders)
+ {
+ int tab[] = {m_nodrawtopborders==1?0:(IDC_LIST_DEVICES|DCW_SUNKENBORDER),
+ m_nodrawtopborders==2?0:(IDC_LIST_TRANSFERS|DCW_SUNKENBORDER),
+ IDC_HDELIM|DCW_DIVIDER
+ };
+ wad_DrawChildWindowBorders(hwndDlg,tab,3);
+ }
+ }
+ break;
+ case WM_DESTROY:
+ {
+ KillTimer(hwndDlg, 1);
+ m_hwnd=NULL;
+ SkinnedListView * ld = listDevices; listDevices=NULL; delete ld;
+ SkinnedListView * lt = listTransfers;
+ transferListContents.lock();
+ listTransfers=NULL;
+ transferListContents.unlock();
+ delete lt;
+ }
+ break;
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_LIST_TRANSFERS)
+ switch (l->code)
+ {
+ case NM_RETURN: // enter!
+ case NM_RCLICK: // right click!
+ case LVN_KEYDOWN:
+ {
+ DeviceView * dev = NULL;
+ C_ItemList items;
+ int row = 0;
+ for (int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView *device = (DeviceView*)devices.Get(i);
+ if (!AddSelectedItems(&items, &listTransfers->listview, getTransferQueue(device), row, dev))
+ return 0;
+ if (!AddSelectedItems(&items, &listTransfers->listview, getFinishedTransferQueue(device), row, dev))
+ return 0;
+ }
+ bool foundDev=false;
+ for (int k=0; k<devices.GetSize(); k++) if (dev == (DeviceView*)devices.Get(k)) foundDev=true;
+ if (dev && foundDev && items.GetSize())
+ {
+ if (l->code == NM_RCLICK)
+ {
+ LPNMITEMACTIVATE lva=(LPNMITEMACTIVATE)lParam;
+ handleContextMenuResult(showContextMenu(7,l->hwndFrom,dev->dev,lva->ptAction),&items,dev);
+ }
+ else if (l->code == NM_RETURN)
+ {
+ handleContextMenuResult((!GetAsyncKeyState(VK_SHIFT)?ID_TRACKSLIST_PLAYSELECTION:ID_TRACKSLIST_ENQUEUESELECTION),&items,dev);
+ }
+ else if (l->code == LVN_KEYDOWN)
+ {
+ switch (((LPNMLVKEYDOWN)lParam)->wVKey)
+ {
+ case VK_DELETE:
+ {
+ if (!(GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_DELETE,&items,dev);
+ }
+ }
+ break;
+ case 0x45: //E
+ if ((GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_EDITSELECTEDITEMS,&items,dev);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON_CLEARFINISHED:
+ {
+ for (int i=0; i<devices.GetSize(); i++)
+ {
+ DeviceView *device = (DeviceView*)devices.Get(i);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ int j=finishedTX->GetSize();
+ while (j-- > 0) delete(CopyInst*)finishedTX->Del(j);
+ finishedTX->unlock();
+ }
+ }
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_REMOVESELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ int i = devices.GetSize();
+ while (i-- > 0)
+ {
+ DeviceView *device = (DeviceView *)devices.Get(i);
+ RemoveSelectedItems(device, &listTransfers->listview, getTransferQueue(device), row, false);
+ RemoveSelectedItems(device, &listTransfers->listview, getFinishedTransferQueue(device), row, true);
+ }
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_PAUSETRANSFERS:
+
+ if (false != TransferContext::IsAllPaused())
+ TransferContext::ResumeAll();
+ else
+ TransferContext::PauseAll();
+
+ for (int i=0;i<devices.GetSize();i++)
+ {
+ DeviceView *device_view = (DeviceView*)devices.Get(i);
+ SetEvent(device_view->transferContext.notifier);
+ }
+
+ SetDlgItemText(hwndDlg,IDC_BUTTON_PAUSETRANSFERS,WASABI_API_LNGSTRINGW((TransferContext::IsAllPaused()?IDS_RESUME:IDS_PAUSE)));
+ break;
+ }
+ break;
+ case WM_MOUSEMOVE:
+ if (wParam==MK_LBUTTON)
+ {
+ if (GetCapture() == hwndDlg) ReleaseCapture();
+ }
+ break;
+ }
+ return 0;
+}
+
+// deals with the horizontal (IDC_HDELIM) divider
+
+static void adiv_UpdPos(int yp)
+{
+ RECT r,old_divider_rect;
+ GetClientRect(m_hwnd,&r);
+
+ GetWindowRect(GetDlgItem(m_hwnd,IDC_HDELIM),&old_divider_rect);
+ ScreenToClient(m_hwnd,(LPPOINT)&old_divider_rect);
+ ScreenToClient(m_hwnd,((LPPOINT)&old_divider_rect)+1);
+ if (yp < 50)
+ {
+ m_nodrawtopborders=1;
+ yp=20;
+ }
+ else m_nodrawtopborders=0;
+ if (yp > r.bottom-42-30)
+ {
+ yp=r.bottom-42;
+ m_nodrawtopborders=2;
+ }
+
+ int x;
+ for (x = 0; x < 4; x ++)
+ {
+ RECT myoldr;
+ GetWindowRect(GetDlgItem(m_hwnd,resize_rlist[x].id),&myoldr);
+
+ ScreenToClient(m_hwnd,(LPPOINT)&myoldr);
+ ScreenToClient(m_hwnd,((LPPOINT)&myoldr)+1);
+ switch (x)
+ {
+ case 0:
+ resize_rlist[x].rinfo.bottom=yp - old_divider_rect.top + myoldr.bottom;
+ break;
+ case 1:
+ resize_rlist[x].rinfo.top=yp - old_divider_rect.top + myoldr.top;
+ break;
+ case 2:
+ case 3:
+ {
+ int h=resize_rlist[x].rinfo.bottom - resize_rlist[x].rinfo.top;
+ resize_rlist[x].rinfo.top = yp - old_divider_rect.top + resize_rlist[x].rinfo.top;
+ resize_rlist[x].rinfo.bottom = resize_rlist[x].rinfo.top + h;
+ }
+ break;
+ }
+ }
+ cr_resize(m_hwnd,resize_rlist,4);
+}
+
+static INT_PTR CALLBACK adiv_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if (uMsg == WM_LBUTTONDOWN)
+ {
+ SetForegroundWindow(hwndDlg);
+ SetCapture(hwndDlg);
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ POINT p;
+ GetCursorPos(&p);
+ ScreenToClient(hwndDlg,&p);
+ adiv_clickoffs=p.y;
+ }
+ else if (uMsg == WM_SETCURSOR)
+ {
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ return TRUE;
+ }
+ else if (uMsg == WM_MOUSEMOVE && GetCapture()==hwndDlg)
+ {
+ POINT p;
+ GetCursorPos(&p);
+ ScreenToClient(GetParent(hwndDlg),&p);
+ adiv_UpdPos(p.y-adiv_clickoffs);
+ RECT r;
+ GetClientRect(GetParent(hwndDlg),&r);
+ if (r.bottom > r.top)
+ {
+ int percent = ((p.y-adiv_clickoffs) * 100000) / (r.bottom-r.top);
+ adivpos = percent;
+ }
+ }
+ else if (uMsg == WM_MOUSEMOVE)
+ {
+ SetCursor(LoadCursor(NULL,IDC_SIZENS));
+ }
+ else if (uMsg == WM_LBUTTONUP)
+ {
+ ReleaseCapture();
+ }
+ return CallWindowProc(adiv_oldWndProc,hwndDlg,uMsg,wParam,lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/view_pmp_media.cpp b/Src/Plugins/Library/ml_pmp/view_pmp_media.cpp
new file mode 100644
index 00000000..ade8f61d
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/view_pmp_media.cpp
@@ -0,0 +1,3205 @@
+#include "main.h"
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/ml_ipc_0313.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "resource1.h"
+#include "SkinnedListView.h"
+#include "DeviceView.h"
+#include "ArtistAlbumLists.h"
+#include "mt19937ar.h" // random number generator
+#include "api__ml_pmp.h"
+#include "..\..\General\gen_ml/graphics.h"
+#include <tataki/export.h>
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+#include "AlbumArtListView.h"
+#include "./local_menu.h"
+#include "metadata_utils.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+
+extern winampMediaLibraryPlugin plugin;
+extern int currentViewedPlaylist;
+extern DeviceView * currentViewedDevice;
+extern HMENU m_context_menus;
+extern C_ItemList devices;
+
+extern void editInfo(C_ItemList * items, Device * dev, HWND centerWindow); // from editinfo.cpp
+
+static INT_PTR CALLBACK pmp_common_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+
+int (*wad_handleDialogMsgs)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void (*wad_DrawChildWindowBorders)(HWND hwndDlg, int *tab, int tabsize);
+
+HWND hwndMediaView;
+static SkinnedListView *artistList=NULL, *albumList=NULL, *albumList2=NULL, *tracksList=NULL;
+
+static int adiv1_nodraw=0,adiv3_nodraw=0;
+static int m_nodrawtopborders=0;
+static int numFilters;
+
+static int adiv1pos=-1, adiv2pos=-1, adiv3pos=-1;
+static int refineHidden=0;
+
+static BOOL g_displaysearch = TRUE;
+static BOOL g_displayrefine = TRUE;
+static BOOL g_displaystatus = TRUE;
+
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX = 0, offsetY = 0, header = 0;
+viewButtons view = {0};
+static bool noSearchTimer=false;
+
+static ArtistAlbumLists *aacontents=NULL;
+static PrimaryListContents *tracks=NULL;
+
+static void UpdateStatus(HWND hwndDlg, bool full = false) {
+ wchar_t buf[1024]=L"";
+ if (tracks)
+ {
+ tracks->GetInfoString(buf);
+ SetDlgItemText(hwndDlg,IDC_STATUS,buf);
+ }
+
+ if (full && currentViewedDevice && currentViewedDevice->isCloudDevice)
+ {
+ int usedPercent = 0;
+
+ __int64 available = currentViewedDevice->dev->getDeviceCapacityAvailable();
+ __int64 capacity = currentViewedDevice->dev->getDeviceCapacityTotal();
+
+ if(capacity > 0) usedPercent = (int)((((__int64)100)*available) / capacity);
+
+ if (!header)
+ {
+ wchar_t buf[128], status[128], availStr[100]=L"";
+ currentViewedDevice->dev->getTrackExtraInfo(0, L"cloud_status", status, ARRAYSIZE(status));
+
+ WASABI_API_LNG->FormattedSizeString(availStr, ARRAYSIZE(availStr), (available > 0 ? available : capacity));
+ wsprintf(buf, L"%s %s %s", availStr, (available > 0 ? L"free" : L"used"), status);
+ SetDlgItemText(hwndDlg, IDC_HEADER_DEVICE_SIZE, buf);
+ SendDlgItemMessage(hwndDlg, IDC_HEADER_DEVICE_BAR, PBM_SETPOS, (100 - usedPercent), 0);
+ }
+ }
+}
+
+static wchar_t *playmode = L"viewplaymode";
+
+typedef void (WINAPI *DIVIDERMOVED)(HWND, INT, LPARAM);
+typedef struct _DIVIDER
+{
+ BOOL fVertical;
+ DIVIDERMOVED callback;
+ LPARAM param;
+ WNDPROC fnOldProc;
+ BOOL fUnicode;
+ INT clickoffs;
+} DIVIDER;
+
+#define GET_DIVIDER(hwnd) (DIVIDER*)GetPropW(hwnd, L"DIVDATA")
+
+static BOOL AttachDivider(HWND hwnd, BOOL fVertical, DIVIDERMOVED callback, LPARAM param);
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static void WINAPI OnDividerMoved(HWND hwnd, INT nPos, LPARAM param);
+void LayoutWindows(HWND hwnd, BOOL fRedraw, INT simple);
+
+static HBITMAP ConvertTo24bpp(HBITMAP bmp, int bpp)
+{
+ HDC hdcMem, hdcMem2;
+ HBITMAP hbm24;
+ BITMAP bm;
+
+ GetObjectW(bmp, sizeof(BITMAP), &bm);
+
+ hdcMem = CreateCompatibleDC(0);
+ hdcMem2 = CreateCompatibleDC(0);
+
+ void *bits;
+ BITMAPINFOHEADER bi;
+
+ ZeroMemory (&bi, sizeof (bi));
+ bi.biSize = sizeof (bi);
+ bi.biWidth= bm.bmWidth;
+ bi.biHeight = -bm.bmHeight;
+ bi.biPlanes = 1;
+ bi.biBitCount= bpp;
+
+ hbm24 = CreateDIBSection(hdcMem2, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, NULL);
+
+ HBITMAP oBmp = (HBITMAP)SelectObject(hdcMem, bmp);
+ HBITMAP oBmp24 = (HBITMAP)SelectObject(hdcMem2, hbm24);
+
+ BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
+
+ SelectObject(hdcMem, oBmp);
+ SelectObject(hdcMem2, oBmp24);
+
+ DeleteDC(hdcMem);
+ DeleteDC(hdcMem2);
+
+
+ return hbm24;
+}
+
+C_ItemList * getSelectedItems(bool all=false) {
+ if(!currentViewedDevice) return NULL;
+ C_ItemList * selected = new C_ItemList;
+ int l = tracks->GetNumRows();
+ if(all || tracksList->listview.GetSelectedCount()==0) for(int i=0; i<l; i++) selected->Add((void*)tracks->GetTrack(i));
+ else for(int i=0; i<l; i++) if(tracksList->listview.GetSelected(i)) selected->Add((void*)tracks->GetTrack(i));
+ return selected;
+}
+
+int showContextMenu(int context, HWND hwndDlg, Device * dev, POINT pt) {
+
+ // does cloud specific menu hacks
+ int cloud_devices = 0;
+ HMENU cloud_menu = 0;
+ if (context < 2)
+ {
+ int mark = tracksList->listview.GetSelectionMark();
+ if (mark != -1)
+ {
+ // if wanting to do on the selection then use getSelectedItems()
+ //C_ItemList * items = getSelectedItems();
+ // otherwise only work on the selection mark for speed (and like how ml_local does things)
+ C_ItemList * items = new C_ItemList;
+ items->Add((void*)tracks->GetTrack(mark));
+ cloud_menu = (HMENU)dev->extraActions(DEVICE_GET_CLOUD_SOURCES_MENU, (intptr_t)&cloud_devices, 0, (intptr_t)items);
+ delete items;
+ }
+ }
+
+ HMENU menu = GetSubMenu(m_context_menus, context);
+ HMENU sendto = GetSubMenu(menu, 2);
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA, 0, 0, 0);
+
+ bool noRatings = !(!fieldsBits || (fieldsBits & SUPPORTS_RATING));
+ bool noEdit = dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA, 0, 0, 0) != 0;
+
+ HMENU ratingMenu = Menu_FindRatingMenu(menu, FALSE);
+ if (NULL != ratingMenu)
+ {
+ Menu_SetRatingValue(ratingMenu, -1);
+ }
+
+ // toggle text of the delete menu item as needed
+ MENUITEMINFOW mii = {sizeof(MENUITEMINFOW), 0};
+ mii.fMask = MIIM_STRING;
+ mii.dwTypeData = WASABI_API_LNGSTRINGW(!currentViewedDevice->isCloudDevice ? IDS_DELETE : IDS_REMOVE);
+ mii.cch = wcslen(mii.dwTypeData);
+ // just make sure we've got a string to use here
+ if (mii.cch > 0)
+ {
+ SetMenuItemInfoW(menu, ID_TRACKSLIST_DELETE, FALSE, &mii);
+ }
+ EnableMenuItem(menu, ID_TRACKSLIST_DELETE, MF_BYCOMMAND |
+ (!currentViewedDevice->isCloudDevice ||
+ (currentViewedDevice->isCloudDevice &&
+ !currentViewedDevice->dev->extraActions(DEVICE_NOT_READY_TO_VIEW, 0, 0, 0))) ? MF_ENABLED : MF_GRAYED);
+
+ EnableMenuItem(menu, ID_TRACKSLIST_EDITSELECTEDITEMS, MF_BYCOMMAND | noEdit ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_0, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_1, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_2, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_3, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_4, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(menu, ID_RATE_5, MF_BYCOMMAND | noRatings ? MF_GRAYED : MF_ENABLED);
+
+ EnableMenuItem(menu, ID_TRACKSLIST_COPYTOLIBRARY, MF_BYCOMMAND | dev->copyToHardDriveSupported() ? MF_ENABLED : MF_GRAYED);
+ int num = 0;
+ // TODO remove once we've got cloud playlist implemented
+ if (0 == dev->extraActions(DEVICE_PLAYLISTS_UNSUPPORTED,0,0,0))
+ {
+ if (EnableMenuItem(menu, ID_ADDTOPLAYLIST_NEWPLAYLIST, MF_BYCOMMAND | (!cloud_menu ? MF_ENABLED : MF_GRAYED)) == -1)
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_STRING, 0};
+ wchar_t a[100] = {0};
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_SEND_TO_PL, a, 100);
+ m.wID = 2;
+ sendto = m.hSubMenu = CreatePopupMenu();
+ InsertMenuItemW(menu, 2, TRUE, &m);
+
+ m.fMask -= MIIM_SUBMENU;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_DEVICE_CMD_PLAYLIST_CREATE, a, 100);
+ m.wID = ID_ADDTOPLAYLIST_NEWPLAYLIST;
+ m.fState = (!cloud_menu ? MF_ENABLED : MF_GRAYED);
+ InsertMenuItemW(m.hSubMenu, 0, FALSE, &m);
+ }
+
+ num = dev->getPlaylistCount();
+ if(num > 1) AppendMenu(sendto, MF_SEPARATOR, 0, L"");
+ for(int i=1; i<num; i++) {
+ wchar_t buf[100] = {0};
+ dev->getPlaylistName(i, buf, sizeof(buf)/sizeof(wchar_t));
+ AppendMenu(sendto, 0, 100000 + i, buf);
+ }
+ }
+ else
+ {
+ if (DeleteMenu(menu, ID_ADDTOPLAYLIST_NEWPLAYLIST, MF_BYCOMMAND))
+ {
+ DeleteMenu(menu, 2, MF_BYPOSITION);
+ sendto = NULL;
+ }
+ }
+
+ if (cloud_menu)
+ {
+ MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0};
+ m.wID = CLOUD_SOURCE_MENUS - 1;
+ m.fType = MFT_SEPARATOR;
+ InsertMenuItemW(menu, (sendto ? 3 : 2), TRUE, &m);
+
+ wchar_t a[100] = {0};
+ m.fType = MFT_STRING;
+ m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100);
+ m.wID = CLOUD_SOURCE_MENUS;
+ m.hSubMenu = cloud_menu;
+ InsertMenuItemW(menu, (sendto ? 4 : 3), TRUE, &m);
+ }
+
+ if (-1 == pt.x && -1 == pt.y)
+ {
+ RECT itemRect;
+ int selected = ListView_GetNextItem(hwndDlg, -1, LVNI_ALL | LVNI_SELECTED);
+ ListView_GetItemRect(hwndDlg, (selected != -1 ? selected : 0), &itemRect, LVIR_BOUNDS);
+ pt.x = itemRect.left;
+ pt.y = itemRect.top;
+ MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&pt, 1);
+ }
+
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY, pt.x, pt.y, hwndDlg, NULL);
+ if(num > 1) DeleteMenu(sendto,1,MF_BYPOSITION);
+ for(int i = 1; i < num; i++) DeleteMenu(sendto, 100000+i, MF_BYCOMMAND);
+ if (cloud_menu)
+ {
+ DeleteMenu(menu, (sendto ? 4 : 3), MF_BYPOSITION);
+ DeleteMenu(menu, (sendto ? 3 : 2), MF_BYPOSITION);
+ DestroyMenu(cloud_menu);
+ }
+ return r;
+}
+
+void handleContextMenuResult(int r, C_ItemList * items0=NULL, DeviceView * dev=NULL) {
+ if(!dev) dev = currentViewedDevice;
+ if(!dev) return;
+ C_ItemList * items = items0;
+ switch(r) {
+ case ID_TRACKSLIST_PLAYSELECTION:
+ case ID_TRACKSLIST_ENQUEUESELECTION:
+ {
+ if (!items) items = getSelectedItems();
+ dev->PlayTracks(items, 0, r==ID_TRACKSLIST_ENQUEUESELECTION, true, hwndMediaView);
+ if (!items0) delete items;
+ }
+ break;
+ case ID_ADDTOPLAYLIST_NEWPLAYLIST:
+ {
+ int n = dev->CreatePlaylist();
+ if (n == -1) return;
+ r = 100000 + n;
+ // the selected tracks will be added by the code at the end of this function
+ }
+ break;
+ case ID_TRACKSLIST_SELECTALL:
+ {
+ int num = tracksList->listview.GetCount();
+ for (int x = 0; x < num; x ++) tracksList->listview.SetSelected(x);
+ }
+ break;
+ case ID_TRACKSLIST_EDITSELECTEDITEMS:
+ {
+ if (!items) items = getSelectedItems();
+ editInfo(items, dev->dev, CENTER_OVER_ML_VIEW);
+ }
+ break;
+ case ID_RATE_5:
+ case ID_RATE_4:
+ case ID_RATE_3:
+ case ID_RATE_2:
+ case ID_RATE_1:
+ case ID_RATE_0:
+ {
+ if (!items) items = getSelectedItems();
+ for(int i = 0; i<items->GetSize(); i++)
+ dev->dev->setTrackRating((songid_t)items->Get(i), ID_RATE_0 - r);
+ if (tracksList) tracksList->UpdateList(true);
+ dev->DevicePropertiesChanges();
+ }
+ break;
+ case ID_TRACKSLIST_DELETE:
+ {
+ wchar_t buf[256] = {0};
+ if (!items) items = getSelectedItems();
+ wsprintf(buf,WASABI_API_LNGSTRINGW((!currentViewedDevice->isCloudDevice ? IDS_PHYSICALLY_REMOVE_X_TRACKS : IDS_CLOUD_REMOVE_X_TRACKS)),items->GetSize());
+ bool ckdev = (dev == currentViewedDevice);
+ wchar_t titleStr[32] = {0};
+ if (MessageBox(hwndMediaView, buf, WASABI_API_LNGSTRINGW_BUF(IDS_ARE_YOU_SURE, titleStr, 32), MB_YESNO | MB_ICONQUESTION) == IDYES) {
+ if (ckdev && !currentViewedDevice) return;
+ dev->DeleteTracks(items, CENTER_OVER_ML_VIEW);
+ if (aacontents && dev == currentViewedDevice) { // full artist-album refresh
+ GetDlgItemText(hwndMediaView, IDC_QUICKSEARCH, buf, sizeof(buf)/sizeof(wchar_t));
+ // TODO async
+ if (aacontents) aacontents->SetSearch(buf);
+ GetDlgItemText(hwndMediaView, IDC_REFINE, buf, sizeof(buf)/sizeof(wchar_t));
+ if (aacontents) aacontents->SetRefine(buf);
+ if (artistList) artistList->UpdateList();
+ if (albumList) albumList->UpdateList();
+ if (albumList2) albumList2->UpdateList();
+ }
+ if (tracksList) tracksList->UpdateList();
+ dev->DevicePropertiesChanges();
+ UpdateStatus(hwndMediaView, true);
+ }
+ }
+ break;
+ case ID_TRACKSLIST_COPYTOLIBRARY:
+ {
+ if (!items) items = getSelectedItems();
+ dev->CopyTracksToHardDrive(items);
+ }
+ break;
+ }
+
+ if(r > 100000) { // add songs to existing playlist
+ int num = dev->dev->getPlaylistCount();
+ if(r < num + 100000) {
+ r-=100000;
+ if(!items) items = getSelectedItems();
+ for(int i = 0; i < items->GetSize(); i++) dev->dev->addTrackToPlaylist(r,(songid_t)items->Get(i));
+ dev->DevicePropertiesChanges();
+ }
+ }
+ else if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER) { // deals with cloud specific menus
+ if (!items) items = getSelectedItems();
+ int ret = dev->dev->extraActions(DEVICE_DO_CLOUD_SOURCES_MENU, (intptr_t)r, items->GetSize(), (intptr_t)items);
+ // only send a removal from the view if plug-in says so
+ if (ret) SendMessage(hwndMediaView, WM_USER+1, (WPARAM)items->Get(0), ret);
+ }
+
+ if (!items0) delete items;
+}
+
+void localizeFilter(const wchar_t *f, wchar_t *buf, int len) {
+ int r=0;
+ if(!_wcsicmp(f,L"Artist")) r = IDS_ARTIST;
+ else if(!_wcsicmp(f,L"Album")) r = IDS_ALBUM;
+ else if(!_wcsicmp(f,L"Genre")) r = IDS_GENRE;
+ else if(!_wcsicmp(f,L"Artist Index")) r = IDS_ARTIST_INDEX;
+ else if(!_wcsicmp(f,L"Year")) r = IDS_YEAR;
+ else if(!_wcsicmp(f,L"Album Artist")) r = IDS_ALBUM_ARTIST;
+ else if(!_wcsicmp(f,L"Publisher")) r = IDS_PUBLISHER;
+ else if(!_wcsicmp(f,L"Composer")) r = IDS_COMPOSER;
+ else if(!_wcsicmp(f,L"Album Artist Index")) r = IDS_ALBUM_ARTIST_INDEX;
+ else if(!_wcsicmp(f,L"Album Art")) r = IDS_ALBUM_ART;
+ else {buf[0]=0; return;}
+ WASABI_API_LNGSTRINGW_BUF(r,buf,len);
+}
+
+wchar_t *GetDefFilter(int i,int n) {
+ if(n==2) i++;
+ if(i==0) return L"Genre";
+ if(i==1) return L"Artist";
+ return L"Album";
+}
+
+static __forceinline BYTE pm(int a, int b) {
+ return (BYTE) ((a * b) / 0xff);
+}
+
+extern svc_imageLoader *GetPngLoaderService();
+
+static ARGB32 *LoadPngResource(HINSTANCE module, const wchar_t *name, const wchar_t *type,
+ BOOL premultily, int *width, int *height)
+{
+ svc_imageLoader *wasabiPngLoader;
+ HRSRC resource;
+ HANDLE resourceHandle;
+ ARGB32 *result;
+
+ if (NULL == WASABI_API_MEMMGR)
+ return NULL;
+
+ wasabiPngLoader = GetPngLoaderService();
+ if (NULL == wasabiPngLoader)
+ return NULL;
+
+ resource = FindResourceW(module, name, type);
+ if (NULL == resource)
+ return NULL;
+
+ result = NULL;
+
+ resourceHandle = LoadResource(module, resource);
+ if (NULL != resourceHandle)
+ {
+ unsigned long resourceSize = SizeofResource(module, resource);
+ if (0 != resourceSize)
+ {
+ void *resourceData = LockResource(resourceHandle);
+ if (NULL != resourceData)
+ {
+ result = (FALSE != premultily) ?
+ wasabiPngLoader->loadImage(resourceData, resourceSize, width, height) :
+ wasabiPngLoader->loadImageData(resourceData, resourceSize, width, height);
+ }
+ }
+ }
+
+ return result;
+}
+
+void DeleteSkinBitmap(SkinBitmap *skinBitmap)
+{
+ void *bits;
+ if (NULL == skinBitmap)
+ return;
+
+ bits = skinBitmap->getBits();
+ if (NULL != bits)
+ {
+ if (NULL != WASABI_API_MEMMGR)
+ WASABI_API_MEMMGR->sysFree(bits);
+ }
+
+ delete skinBitmap;
+}
+
+BOOL SetToolbarButtonBitmap(HWND hwnd, int controlId, const wchar_t *resourceName, ARGB32 fc)
+{
+ int width, height;
+ ARGB32 *data, *x, *end;
+ BYTE r, g, b;
+ SkinBitmap *sbm, *old;
+ HWND controlWindow;
+
+ controlWindow = GetDlgItem(hwnd, controlId);
+ if (NULL == controlWindow)
+ return FALSE;
+
+ data = LoadPngResource(plugin.hDllInstance, resourceName, RT_RCDATA, FALSE, &width, &height);
+ if (NULL == data)
+ return FALSE;
+
+ r = (BYTE)(fc & 0x00ff0000 >> 16);
+ g = (BYTE)(fc & 0x0000ff00 >> 8);
+ b = (BYTE)(fc & 0x000000ff);
+
+ x = data;
+ end = data + width*height;
+
+ while(x < end)
+ {
+ BYTE a = (BYTE)(~(*x))&0xff;
+ *(x++) = (a<<24) | (pm(r,a)<<16) | (pm(g,a)<<8) | pm(b,a);
+ }
+
+ sbm = new SkinBitmap(data, width, height);
+ old = (SkinBitmap*)SetWindowLongPtr(controlWindow, GWLP_USERDATA,(LONG_PTR)sbm);
+ DeleteSkinBitmap(old);
+ InvalidateRect(controlWindow, NULL, TRUE);
+
+ return TRUE;
+}
+
+typedef struct { int id, id2; } hi;
+
+void do_help(HWND hwnd, UINT id, HWND hTooltipWnd)
+{
+ RECT r;
+ POINT p;
+ GetWindowRect(GetDlgItem(hwnd, id), &r);
+ GetCursorPos(&p);
+ if (p.x >= r.left && p.x < r.right && p.y >= r.top && p.y < r.bottom)
+ {}
+ else
+ {
+ r.left += r.right;
+ r.left /= 2;
+ r.top += r.bottom;
+ r.top /= 2;
+ SetCursorPos(r.left, r.top);
+ }
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_INITIAL, 0);
+ SendMessage(hTooltipWnd, TTM_SETDELAYTIME, TTDT_RESHOW, 0);
+}
+
+#define C_BLAH
+#define DO_HELP() \
+static HWND hTooltipWnd; \
+C_BLAH \
+if (uMsg == WM_HELP) { \
+ HELPINFO *hi=(HELPINFO *)(lParam); \
+ if (hi->iContextType == HELPINFO_WINDOW) { do_help(hwndDlg,hi->iCtrlId,hTooltipWnd);} \
+ return TRUE; \
+} \
+if (uMsg == WM_NOTIFY) { LPNMHDR t=(LPNMHDR)lParam; if (t->code == TTN_POP) { SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,1000); SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,1000); } } \
+if (uMsg == WM_DESTROY && IsWindow(hTooltipWnd)) { DestroyWindow(hTooltipWnd); hTooltipWnd=NULL; } \
+if (uMsg == WM_INITDIALOG) { \
+ int x; \
+ hTooltipWnd = CreateWindow(TOOLTIPS_CLASS,(LPCWSTR)NULL,TTS_ALWAYSTIP|TTS_NOPREFIX, \
+ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, hwndDlg,NULL,GetModuleHandle(NULL),NULL); \
+ SendMessage(hTooltipWnd,TTM_SETMAXTIPWIDTH,0,587); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_INITIAL,250); \
+ SendMessage(hTooltipWnd,TTM_SETDELAYTIME,TTDT_RESHOW,500); \
+ for (x = 0; x < sizeof(helpinfo)/sizeof(helpinfo[0]); x ++) { \
+ TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_SUBCLASS|TTF_IDISHWND; \
+ ti.uId=(UINT_PTR)GetDlgItem(hwndDlg,helpinfo[x].id); ti.hwnd=hwndDlg; ti.lpszText=WASABI_API_LNGSTRINGW(helpinfo[x].id2); \
+ SendMessage(hTooltipWnd,TTM_ADDTOOL,0,(LPARAM) &ti); \
+ } \
+}
+
+static BOOL ListView_OnCustomDraw(HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult)
+{
+ static BOOL bDrawFocus;
+ static RECT rcView;
+ static CLOUDCOLUMNPAINT cloudColumnPaint;
+
+ *pResult = CDRF_DODEFAULT;
+
+ switch (plvcd->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ *pResult |= CDRF_NOTIFYITEMDRAW;
+ CopyRect(&rcView, &plvcd->nmcd.rc);
+
+ cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
+ cloudColumnPaint.hdc = plvcd->nmcd.hdc;
+ cloudColumnPaint.prcView = &rcView;
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ *pResult |= CDRF_NOTIFYSUBITEMDRAW;
+ bDrawFocus = (CDIS_FOCUS & plvcd->nmcd.uItemState);
+ if (bDrawFocus)
+ {
+ plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
+ *pResult |= CDRF_NOTIFYPOSTPAINT;
+ }
+ return TRUE;
+
+ case CDDS_ITEMPOSTPAINT:
+ if (bDrawFocus)
+ {
+ RECT rc;
+ rc.left = LVIR_BOUNDS;
+ SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc);
+ rc.left += 3;
+ DrawFocusRect(plvcd->nmcd.hdc, &rc);
+ plvcd->nmcd.uItemState |= CDIS_FOCUS;
+ bDrawFocus = FALSE;
+ }
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+
+ case(CDDS_SUBITEM | CDDS_ITEMPREPAINT):
+ {
+ if (aacontents && aacontents->bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break;
+ cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
+ cloudColumnPaint.iSubItem = plvcd->iSubItem;
+
+ if (plvcd->nmcd.hdr.idFrom == IDC_LIST_TRACKS)
+ {
+ if (plvcd->iSubItem == tracks->cloudcol)
+ {
+ cloudColumnPaint.value = tracks->cloud_cache[plvcd->nmcd.dwItemSpec];
+ }
+ else
+ break;
+ }
+ else if (plvcd->nmcd.hdr.idFrom == IDC_LIST_ARTIST)
+ {
+ if (plvcd->iSubItem == aacontents->GetFilterList(0)->cloudcol)
+ {
+ wchar_t buf[16] = {0};
+ aacontents->GetFilterList(0)->GetCellText(plvcd->nmcd.dwItemSpec, plvcd->iSubItem, buf, 16);
+ cloudColumnPaint.value = _wtoi(buf);
+ }
+ else
+ break;
+ }
+ else if (plvcd->nmcd.hdr.idFrom == IDC_LIST_ALBUM)
+ {
+ if (plvcd->iSubItem == aacontents->GetFilterList(1)->cloudcol)
+ {
+ wchar_t buf[16] = {0};
+ aacontents->GetFilterList(1)->GetCellText(plvcd->nmcd.dwItemSpec, plvcd->iSubItem, buf, 16);
+ cloudColumnPaint.value = _wtoi(buf);
+ }
+ else
+ break;
+ }
+ else if (plvcd->nmcd.hdr.idFrom == IDC_LIST_ALBUM2)
+ {
+ if (plvcd->iSubItem == aacontents->GetFilterList(2)->cloudcol)
+ {
+ wchar_t buf[16] = {0};
+ aacontents->GetFilterList(2)->GetCellText(plvcd->nmcd.dwItemSpec, plvcd->iSubItem, buf, 16);
+ cloudColumnPaint.value = _wtoi(buf);
+ }
+ else
+ break;
+ }
+
+ cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
+ cloudColumnPaint.rgbBk = plvcd->clrTextBk;
+ cloudColumnPaint.rgbFg = plvcd->clrText;
+
+ if (MLCloudColumn_Paint(plugin.hwndLibraryParent, &cloudColumnPaint))
+ {
+ *pResult = CDRF_SKIPDEFAULT;
+ return TRUE;
+ }
+ }
+ break;
+ }
+ return FALSE;
+}
+
+void pmp_common_UpdateButtonText(HWND hwndDlg, int _enqueuedef)
+{
+ if (groupBtn)
+ {
+ switch(_enqueuedef)
+ {
+ case 1:
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue);
+ customAllowed = FALSE;
+ break;
+
+ default:
+ // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
+ // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
+ pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0};
+
+ wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
+ if (pszTextW && pszTextW[0] != 0)
+ {
+ // set this to be a bit different so we can just use one button and not the
+ // mixable one as well (leaving that to prevent messing with the resources)
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW);
+ customAllowed = TRUE;
+ }
+ else
+ {
+ SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play);
+ customAllowed = FALSE;
+ }
+ break;
+ }
+ }
+}
+
+
+enum
+{
+ BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
+ BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
+};
+
+BOOL pmp_common_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0)
+{
+ RECT r;
+ HWND buttonHWND = GetDlgItem(hwndDlg, buttonId);
+ GetWindowRect(buttonHWND, &r);
+ 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_TrackSkinnedPopup(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 pmp_common_PlayEnqueue(HWND hwndDlg, HWND from, UINT idFrom)
+{
+ HMENU listMenu = GetSubMenu(m_context_menus2, 0);
+ int count = GetMenuItemCount(listMenu);
+ if (count > 2)
+ {
+ for (int i = 2; i < count; i++)
+ {
+ DeleteMenu(listMenu, 2, MF_BYPOSITION);
+ }
+ }
+
+ pmp_common_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
+}
+
+static BOOL restoreDone;
+INT_PTR CALLBACK pmp_artistalbum_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ hi helpinfo[]={
+ {IDC_BUTTON_ARTMODE,IDS_AUDIO_BUTTON_TT1},
+ {IDC_BUTTON_VIEWMODE,IDS_AUDIO_BUTTON_TT2},
+ {IDC_BUTTON_COLUMNS,IDS_AUDIO_BUTTON_TT3},
+ };
+ DO_HELP();
+
+ if(hwndMediaView != hwndDlg && uMsg != WM_INITDIALOG) return 0;
+ if (wad_handleDialogMsgs) { BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a; }
+ if (artistList) { BOOL a=artistList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+ if (albumList) { BOOL a=albumList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+ if (albumList2) { BOOL a=albumList2->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+ if (tracksList) { BOOL a=tracksList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ groupBtn = gen_mlconfig->ReadInt(L"groupbtn", 1);
+ enqueuedef = (gen_mlconfig->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_pmp"};
+ 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;
+ }
+
+ restoreDone = FALSE;
+ playmode = L"viewplaymode";
+ hwndMediaView = hwndDlg;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+
+ if (!lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ header = 1;
+ else
+ header = currentViewedDevice->config->ReadInt(L"header",0);
+
+ numFilters = currentViewedDevice->config->ReadInt(L"media_numfilters",2);
+ if(numFilters != 3 && numFilters != 2) numFilters=2;
+
+ wchar_t filters[300] = {0}, *filtersp[3] = {0};
+ bool artfilter[3]={false,false,false};
+ for(int i = 0; i < numFilters; i++) {
+ filtersp[i]=&filters[i*100];
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",i);
+ lstrcpyn(filtersp[i],currentViewedDevice->config->ReadString(name,GetDefFilter(i,numFilters)),100);
+ if(!_wcsicmp(filtersp[i],L"Album Art")) artfilter[i]=true;
+ }
+ aacontents = new ArtistAlbumLists(currentViewedDevice->dev, currentViewedPlaylist, currentViewedDevice->config, filtersp,
+ numFilters, (currentViewedDevice->videoView ? 0 : -1), (!!currentViewedDevice->isCloudDevice));
+
+ if (!artfilter[0]) artistList = new SkinnedListView(aacontents->GetFilterList(0),IDC_LIST_ARTIST,plugin.hwndLibraryParent, hwndDlg, false);
+ else artistList = new AlbumArtListView(aacontents->GetFilterList(0),IDC_LIST_ARTIST,plugin.hwndLibraryParent, hwndDlg, false);
+ artistList->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ artistList->InitializeFilterData(0, currentViewedDevice->config);
+
+ if (!artfilter[1]) albumList = new SkinnedListView(aacontents->GetFilterList(1),IDC_LIST_ALBUM,plugin.hwndLibraryParent, hwndDlg, false);
+ else albumList = new AlbumArtListView(aacontents->GetFilterList(1),IDC_LIST_ALBUM,plugin.hwndLibraryParent, hwndDlg, false);
+ albumList->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ albumList->InitializeFilterData(1, currentViewedDevice->config);
+
+ if (numFilters == 3) {
+ if(!artfilter[2]) albumList2 = new SkinnedListView(aacontents->GetFilterList(2),IDC_LIST_ALBUM2,plugin.hwndLibraryParent, hwndDlg, false);
+ else albumList2 = new AlbumArtListView(aacontents->GetFilterList(2),IDC_LIST_ALBUM2,plugin.hwndLibraryParent, hwndDlg, false);
+ albumList2->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ albumList2->InitializeFilterData(2, currentViewedDevice->config);
+ }
+
+ if (currentViewedDevice->config->ReadInt(L"savefilter", 1))
+ {
+ SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, currentViewedDevice->config->ReadString(L"savedfilter", L""));
+ SetDlgItemTextW(hwndDlg, IDC_REFINE, currentViewedDevice->config->ReadString(L"savedrefinefilter", L""));
+ }
+ else
+ restoreDone = TRUE;
+
+ HWND list = GetDlgItem(hwndDlg, IDC_LIST_TRACKS);
+ // TODO need to be able to change the order of the tracks items (so cloud is in a more appropriate place)
+ //ListView_SetExtendedListViewStyleEx(list, LVS_EX_HEADERDRAGDROP, LVS_EX_HEADERDRAGDROP);
+ ListView_SetExtendedListViewStyle(list, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP);
+
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = list;
+ skin.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ skin.hwndToSkin = artistList->listview.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ skin.hwndToSkin = albumList->listview.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ if (numFilters == 3) {
+ skin.hwndToSkin = albumList2->listview.getwnd();
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+
+ if(!currentViewedDevice->config->ReadInt(L"media_scroll_0",0))
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,artistList->skinlistview_handle,ML_IPC_LISTVIEW_DISABLEHSCROLL);
+ if(!currentViewedDevice->config->ReadInt(L"media_scroll_1",0))
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,albumList->skinlistview_handle,ML_IPC_LISTVIEW_DISABLEHSCROLL);
+ if(numFilters == 3 && !currentViewedDevice->config->ReadInt(L"media_scroll_2",0))
+ SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,albumList2->skinlistview_handle,ML_IPC_LISTVIEW_DISABLEHSCROLL);
+
+ if (aacontents)
+ {
+ artistList->UpdateList();
+ tracks = aacontents->GetTracksList();
+ }
+
+ adiv1pos = numFilters == 3?33333:50000;
+ adiv3pos = numFilters == 3?66667:0;
+ adiv2pos = 50000;
+ adiv1pos = currentViewedDevice->config->ReadInt(L"adiv1pos",adiv1pos);
+ if(numFilters == 3) adiv3pos = currentViewedDevice->config->ReadInt(L"adiv3pos",adiv3pos);
+ adiv2pos = currentViewedDevice->config->ReadInt(L"adiv2pos",adiv2pos);
+ if(numFilters == 3 && adiv1pos>adiv3pos) {
+ adiv1pos=33333;
+ adiv3pos=66667;
+ }
+ AttachDivider(GetDlgItem(hwndDlg, IDC_VDELIM), TRUE, OnDividerMoved, IDC_VDELIM);
+ if(numFilters == 3) AttachDivider(GetDlgItem(hwndDlg, IDC_VDELIM2), TRUE, OnDividerMoved, IDC_VDELIM2);
+ AttachDivider(GetDlgItem(hwndDlg, IDC_HDELIM), FALSE, OnDividerMoved, IDC_HDELIM);
+
+ int fieldsBits = (int)currentViewedDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits = -1;
+ if(!(fieldsBits & SUPPORTS_ALBUMART))
+ SetWindowPos(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),0,0,0,0,0,0); // get rid of art mode button if we don't support albumart
+
+ FLICKERFIX ff = {0};
+ ff.mode = FFM_ERASEINPAINT;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ INT ffcl[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM,
+ IDC_BUTTON_CLEARSEARCH, IDC_BUTTON_CLEARREFINE,
+ IDC_BUTTON_EJECT, IDC_BUTTON_SYNC, IDC_BUTTON_AUTOFILL,
+ IDC_STATUS, IDC_SEARCH_TEXT, IDC_QUICKSEARCH, IDC_REFINE,
+ IDC_REFINE_TEXT, IDC_BUTTON_ARTMODE, IDC_BUTTON_VIEWMODE,
+ IDC_BUTTON_COLUMNS,
+ // disabled cloud parts
+ /*IDC_HEADER_DEVICE_ICON, IDC_HEADER_DEVICE_NAME, IDC_HEADER_DEVICE_BAR,
+ IDC_HEADER_DEVICE_SIZE, IDC_HEADER_DEVICE_TRANSFER,*/
+ };
+ for (int i = 0; i < ARRAYSIZE(ffcl); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+
+ // skip the mode buttons
+ if (i < 13)
+ {
+ if (i < 3)
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0);
+ else
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ skin.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+ }
+
+ if (0 != currentViewedDevice->dev->extraActions(DEVICE_SYNC_UNSUPPORTED,0,0,0))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC), FALSE);
+
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ // do this now to get the cloud columns correctly known (doesn't work correctly if done before the skinning is setup)
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_TRACKS)), tracks->cloudcol);
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_ARTIST)), artistList->contents->cloudcol);
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_ALBUM)), albumList->contents->cloudcol);
+ if (numFilters == 3) MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(GetDlgItem(hwndDlg, IDC_LIST_ALBUM2)), albumList2->contents->cloudcol);
+
+ g_displayrefine = !!(gen_mlconfig->ReadInt(L"audiorefine", 0));
+ adiv1_nodraw=0;
+ adiv3_nodraw=0;
+ m_nodrawtopborders=0;
+ PostMessage(hwndDlg, WM_DISPLAYCHANGE, 0, 0);
+ break;
+ }
+
+ case WM_DISPLAYCHANGE:
+ {
+ int (*wad_getColor)(int idx);
+ *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ ARGB32 fc = (wad_getColor?wad_getColor(WADLG_BUTTONFG):RGB(0xFF,0xFF,0xFF)) & 0x00FFFFFF;
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_ARTMODE, MAKEINTRESOURCE(IDR_TOOL_ALBUMART_ICON),fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_VIEWMODE, MAKEINTRESOURCE(IDR_TOOL_VIEWMODE_ICON),fc);
+ SetToolbarButtonBitmap(hwndDlg,IDC_BUTTON_COLUMNS, MAKEINTRESOURCE(IDR_TOOL_COLUMNS_ICON),fc);
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE, 0);
+ }
+ break;
+
+ case WM_DROPFILES:
+ return currentViewedDevice->TransferFromDrop((HDROP)wParam);
+
+ case WM_DESTROY:
+ if (aacontents) aacontents->bgQuery_Stop();
+ currentViewedDevice->config->WriteInt(L"adiv1pos",adiv1pos);
+ if(numFilters == 3) currentViewedDevice->config->WriteInt(L"adiv3pos",adiv3pos);
+ currentViewedDevice->config->WriteInt(L"adiv2pos",adiv2pos);
+ tracks=NULL;
+ hwndMediaView=NULL;
+ if (albumList) delete albumList; albumList=NULL;
+ if (artistList) delete artistList; artistList=NULL;
+ if (albumList2) delete albumList2; albumList2=NULL;
+ if (aacontents) delete aacontents; aacontents=NULL;
+ {
+ SkinBitmap *s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_ARTMODE),GWLP_USERDATA);
+ DeleteSkinBitmap(s);
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),GWLP_USERDATA);
+ DeleteSkinBitmap(s);
+ s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),GWLP_USERDATA);
+ DeleteSkinBitmap(s);
+ }
+ break;
+
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON) {
+ if(di->CtlID == IDC_BUTTON_ARTMODE || di->CtlID == IDC_BUTTON_VIEWMODE || di->CtlID == IDC_BUTTON_COLUMNS)
+ { // draw the toolbar buttons!
+ SkinBitmap* hbm = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,di->CtlID),GWLP_USERDATA);
+ if(hbm && di->rcItem.left != di->rcItem.right && di->rcItem.top != di->rcItem.bottom) {
+ DCCanvas dc(di->hDC);
+ if (di->itemState & ODS_SELECTED) hbm->blitAlpha(&dc,di->rcItem.left+6,di->rcItem.top+5);
+ else hbm->blitAlpha(&dc,di->rcItem.left+4,di->rcItem.top+3);
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_SETCURSOR:
+ case WM_LBUTTONDOWN:
+ {
+ static INT id[] = { IDC_VDELIM, IDC_HDELIM, IDC_VDELIM2 };
+ RECT rw;
+ POINT pt;
+
+ GetCursorPos(&pt);
+ for (INT i = 0; i < sizeof(id)/sizeof(INT); i++)
+ {
+ HWND hwndDiv = GetDlgItem(hwndDlg, id[i]);
+ if (!hwndDiv) continue;
+
+ GetWindowRect(hwndDiv, &rw);
+ if (PtInRect(&rw, pt))
+ {
+ if (WM_SETCURSOR == uMsg)
+ {
+ SetCursor(LoadCursor(NULL, (IDC_VDELIM == id[i] || IDC_VDELIM2 == id[i]) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+ }
+ else
+ {
+ SendMessage(hwndDiv, uMsg, wParam, MAKELPARAM(pt.x - rw.left, pt.y - rw.top));
+ return TRUE;
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_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), 0);
+ }
+ return TRUE;
+
+ case WM_PAINT:
+ {
+ int tab[] = {(m_nodrawtopborders==2)?0:(IDC_LIST_TRACKS|DCW_SUNKENBORDER),
+ IDC_QUICKSEARCH|DCW_SUNKENBORDER,
+ (refineHidden!=0)?0:(IDC_REFINE|DCW_SUNKENBORDER),
+ IDC_HDELIM|DCW_DIVIDER,
+ (!header?IDC_HDELIM2|DCW_DIVIDER:0),
+ IDC_VDELIM|DCW_DIVIDER,
+ numFilters == 3?IDC_VDELIM2|DCW_DIVIDER:0,
+ adiv1_nodraw==1?0:(IDC_LIST_ARTIST|DCW_SUNKENBORDER),
+ (adiv1_nodraw==2 || adiv3_nodraw==1)?0:(IDC_LIST_ALBUM|DCW_SUNKENBORDER),
+ (numFilters != 3 || adiv3_nodraw==2)?0:(IDC_LIST_ALBUM2|DCW_SUNKENBORDER)};
+ int size = sizeof(tab) / sizeof(tab[0]);
+ // do this to prevent drawing parts when the views are collapsed
+ if (m_nodrawtopborders==1) size -= 6;
+ if (wad_DrawChildWindowBorders) wad_DrawChildWindowBorders(hwndDlg,tab,size);
+ }
+ return TRUE;
+
+ case WM_ERASEBKGND:
+ return 1;
+
+ case WM_USER:
+ {
+ if (aacontents && aacontents->bgThread_Handle) break;
+
+ if (aacontents)
+ {
+ SkinnedListView * lists[3]={artistList,albumList,albumList2};
+ aacontents->SelectionChanged(wParam==IDC_LIST_ARTIST?0:(wParam==IDC_LIST_ALBUM?1:2),lists);
+ }
+ noSearchTimer=true;
+ SetDlgItemText(hwndDlg,IDC_REFINE,L"");
+ noSearchTimer=false;
+ UpdateStatus(hwndDlg);
+ wParam=0;
+ }
+ // run through
+ case WM_USER+2:
+ if (tracksList) tracksList->UpdateList(wParam==1);
+ break;
+
+ case WM_USER+1:
+ if (tracks) tracks->RemoveTrack((songid_t)wParam);
+ if (aacontents) aacontents->RemoveTrack((songid_t)wParam);
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->idFrom==IDC_LIST_ARTIST || l->idFrom==IDC_LIST_ALBUM || l->idFrom==IDC_LIST_ALBUM2)
+ {
+ switch(l->code) {
+ case LVN_ITEMCHANGED:
+ {
+ LPNMLISTVIEW lv=(LPNMLISTVIEW)lParam;
+ if ((lv->uNewState ^ lv->uOldState) & LVIS_SELECTED) {
+ PostMessage(hwndDlg,WM_USER,l->idFrom,0);
+ }
+ }
+ break;
+ case NM_RETURN:
+ case NM_DBLCLK: // play some songs!
+ {
+ C_ItemList * items = getSelectedItems(true);
+ currentViewedDevice->PlayTracks(items, 0, (!(GetAsyncKeyState(VK_SHIFT)&0x8000) ? (enqueuedef == 1) : (enqueuedef != 1)), false);
+ delete items;
+ break;
+ }
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case VK_DELETE:
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ }
+ break;
+ case 0x43: //C
+ if(GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_COPYTOLIBRARY);
+ }
+ break;
+ }
+ break;
+ }
+ }
+ else if(l->idFrom == IDC_LIST_TRACKS)
+ {
+ switch(l->code) {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case VK_DELETE:
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ }
+ break;
+ case 0x45: //E
+ bool noEdit = currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA,0,0,0)!=0;
+ if(!noEdit && GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ C_ItemList * items = getSelectedItems();
+ editInfo(items,currentViewedDevice->dev, CENTER_OVER_ML_VIEW);
+ delete items;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_APP + 3: // send by bgthread
+ if (wParam == 0x69)
+ {
+ if (aacontents)
+ {
+ aacontents->bgQuery_Stop();
+ tracks = aacontents->GetTracksList();
+
+ if (artistList) artistList->UpdateList();
+ if (albumList) albumList->UpdateList();
+ if (albumList2) albumList2->UpdateList();
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg, true);
+ }
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ pmp_common_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE, 0);
+ return 0;
+ }
+
+ case WM_TIMER:
+ switch(wParam) {
+ case 123:
+ {
+ if (aacontents && aacontents->bgThread_Handle)
+ {
+ HWND hwndList;
+ hwndList = tracksList->listview.getwnd();
+ if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+ }
+ break;
+
+ case 400:
+ KillTimer(hwndDlg,400);
+ currentViewedDevice->config->WriteInt(L"adiv1pos",adiv1pos);
+ if(numFilters == 3) currentViewedDevice->config->WriteInt(L"adiv3pos",adiv3pos);
+ currentViewedDevice->config->WriteInt(L"adiv2pos",adiv2pos);
+ break;
+
+ case 500:
+ {
+ KillTimer(hwndDlg,500);
+ wchar_t buf[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_QUICKSEARCH,buf,sizeof(buf)/sizeof(wchar_t));
+ noSearchTimer=true;
+ if (restoreDone) SetDlgItemText(hwndDlg,IDC_REFINE,L"");
+ noSearchTimer=false;
+ aacontents->SetSearch(buf, (!!currentViewedDevice->isCloudDevice));
+ if (!aacontents->bgThread_Handle)
+ {
+ if (artistList) artistList->UpdateList();
+ if (albumList) albumList->UpdateList();
+ if (albumList2) albumList2->UpdateList();
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+
+ if (!restoreDone)
+ {
+ restoreDone = TRUE;
+ KillTimer(hwndDlg,501);
+ SetTimer(hwndDlg,501,250,NULL);
+ }
+ }
+ break;
+
+ case 501:
+ {
+ KillTimer(hwndDlg,501);
+ wchar_t buf[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_REFINE,buf,sizeof(buf)/sizeof(wchar_t));
+ aacontents->SetRefine(buf, (!!currentViewedDevice->isCloudDevice));
+ if (!aacontents->bgThread_Handle)
+ {
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+ }
+ break;
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ if(wParam==MK_LBUTTON) {
+ if(GetCapture() == hwndDlg) ReleaseCapture();
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_BUTTON_ARTMODE:
+ {
+ int changed=0;
+ for(int i=0; i<3; i++) {
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",i);
+ wchar_t * str = currentViewedDevice->config->ReadString(name,GetDefFilter(i,numFilters));
+ if(!_wcsicmp(str,L"Album")) { currentViewedDevice->config->WriteString(name,L"Album Art"); changed=1; }
+ else if(!_wcsicmp(str,L"Album Art")) { currentViewedDevice->config->WriteString(name,L"Album"); changed=1; }
+ }
+ if (changed) PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ break;
+ case IDC_BUTTON_VIEWMODE:
+ {
+ struct {
+ const wchar_t *name;
+ int numfilters;
+ wchar_t* f[3];
+ int requiredFields;
+ } presets[] = {
+ {0,2,{L"Artist",L"Album"},0},
+ {0,2,{L"Artist",L"Album Art"},SUPPORTS_ALBUMART},
+ {0,2,{L"Album Artist",L"Album"},SUPPORTS_ALBUMARTIST},
+ {0,2,{L"Album Artist",L"Album Art"},SUPPORTS_ALBUMARTIST | SUPPORTS_ALBUMART},
+ {0,3,{L"Genre",L"Artist",L"Album"},SUPPORTS_GENRE | SUPPORTS_ALBUMART},
+ {0,2,{L"Genre",L"Album Art"},SUPPORTS_GENRE},
+ {0,3,{L"Year",L"Artist",L"Album"},SUPPORTS_YEAR},
+ {0,2,{L"Composer",L"Album"},SUPPORTS_COMPOSER},
+ {0,3,{L"Publisher",L"Artist",L"Album"},SUPPORTS_PUBLISHER},
+ };
+ HMENU menu = CreatePopupMenu();
+ int fieldsBits = (int)currentViewedDevice->dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits = -1;
+ bool checked=false;
+
+ wchar_t filters[300] = {0};
+ wchar_t *filtersp[3] = {0};
+ for(int i=0; i<numFilters; i++) {
+ filtersp[i]=&filters[i*100];
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",i);
+ lstrcpyn(filtersp[i],currentViewedDevice->config->ReadString(name,GetDefFilter(i,numFilters)),100);
+ }
+
+ for(int i=0; i < sizeof(presets)/sizeof(presets[0]); i++) {
+ if(!presets[i].requiredFields || (presets[i].requiredFields & fieldsBits) == presets[i].requiredFields) {
+ wchar_t buf[350] = {0};
+ if(!presets[i].name) {
+ wchar_t a[100] = {0}, b[100] = {0}, c[100] = {0};
+ localizeFilter(presets[i].f[0],a,100);
+ localizeFilter(presets[i].f[1],b,100);
+ if(presets[i].numfilters == 3) localizeFilter(presets[i].f[2],c,100);
+ if(presets[i].numfilters == 3) wsprintf(buf,L"%s/%s/%s",a,b,c);
+ else wsprintf(buf,L"%s/%s",a,b);
+ }
+ AppendMenu(menu,MF_STRING,i+1,presets[i].name?presets[i].name:buf);
+ if(numFilters == presets[i].numfilters && !_wcsicmp(presets[i].f[0],filtersp[0]) && !_wcsicmp(presets[i].f[1],filtersp[1]) && (numFilters == 2 || !_wcsicmp(presets[i].f[2],filtersp[2])))
+ { // this is our view...
+ CheckMenuItem(menu,i+1,MF_CHECKED);
+ checked=true;
+ }
+ }
+ }
+ AppendMenu(menu,MF_STRING,0x4000,WASABI_API_LNGSTRINGW(IDS_OTHER2));
+ if(!checked)
+ CheckMenuItem(menu,0x4000,MF_CHECKED);
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_VIEWMODE),&rc);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,rc.left,rc.bottom,hwndDlg,NULL);
+ DestroyMenu(menu);
+ if(r==0) break;
+ else if(r == 0x4000) {
+ extern int g_prefs_openpage;
+ g_prefs_openpage = (!currentViewedDevice->isCloudDevice ? 4 : 0);
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&currentViewedDevice->devPrefsPage,IPC_OPENPREFSTOPAGE);
+
+ extern HWND m_hwndTab;
+ if (IsWindow(m_hwndTab))
+ {
+ TabCtrl_SetCurSel(m_hwndTab, g_prefs_openpage);
+ extern HWND OnSelChanged(HWND hwndDlg, HWND external = NULL, DeviceView *dev = NULL);
+ OnSelChanged(GetParent(m_hwndTab));
+ }
+ }
+ else {
+ r--;
+ for(int j=0; j<presets[r].numfilters; j++) {
+ wchar_t name[20] = {0};
+ wsprintf(name,L"media_filter%d",j);
+ currentViewedDevice->config->WriteString(name,presets[r].f[j]);
+ }
+ currentViewedDevice->config->WriteInt(L"media_numfilters",presets[r].numfilters);
+ PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
+ }
+ }
+ break;
+ case IDC_BUTTON_COLUMNS:
+ {
+ HMENU themenu1 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU themenu2 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU themenu3 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+ HMENU themenu4 = WASABI_API_LOADMENU(IDR_CONTEXTMENUS);
+
+ HMENU menu = CreatePopupMenu();
+ MENUITEMINFO m={sizeof(m),MIIM_TYPE | MIIM_ID | MIIM_SUBMENU,MFT_STRING,0};
+ wchar_t a[100] = {0}, b[100] = {0}, c[100] = {0};
+ localizeFilter(currentViewedDevice->config->ReadString(L"media_filter0",GetDefFilter(0,numFilters)),a,100);
+ localizeFilter(currentViewedDevice->config->ReadString(L"media_filter1",GetDefFilter(1,numFilters)),b,100);
+ localizeFilter(currentViewedDevice->config->ReadString(L"media_filter2",GetDefFilter(2,numFilters)),c,100);
+ wchar_t * d = WASABI_API_LNGSTRINGW(IDS_TRACKS);
+ m.wID = 0;
+ m.dwTypeData = a;
+ m.hSubMenu = artistList->GetMenu(true,0,currentViewedDevice->config,themenu1);
+ InsertMenuItem(menu,0,FALSE,&m);
+ m.wID = 1;
+ m.dwTypeData = b;
+ m.hSubMenu = albumList->GetMenu(true,1,currentViewedDevice->config,themenu2);
+ InsertMenuItem(menu,1,FALSE,&m);
+ if(numFilters == 3) {
+ m.wID = 2;
+ m.dwTypeData = c;
+ m.hSubMenu = albumList2->GetMenu(true,2,currentViewedDevice->config,themenu3);
+ InsertMenuItem(menu,2,FALSE,&m);
+ }
+ m.wID = 3;
+ m.dwTypeData = d;
+ m.hSubMenu = tracksList->GetMenu(false,0,currentViewedDevice->config,themenu4);
+ InsertMenuItem(menu,3,FALSE,&m);
+
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg,IDC_BUTTON_COLUMNS),&rc);
+ int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,rc.left,rc.bottom,hwndDlg,NULL);
+
+ artistList->ProcessMenuResult(r,true,0,currentViewedDevice->config,hwndDlg);
+ albumList->ProcessMenuResult(r,true,1,currentViewedDevice->config,hwndDlg);
+ if(numFilters == 3) albumList2->ProcessMenuResult(r,true,2,currentViewedDevice->config,hwndDlg);
+ tracksList->ProcessMenuResult(r,false,0,currentViewedDevice->config,hwndDlg);
+
+ DestroyMenu(menu);
+ DestroyMenu(themenu1);
+ DestroyMenu(themenu2);
+ DestroyMenu(themenu3);
+ DestroyMenu(themenu4);
+ }
+ break;
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE && !noSearchTimer) {
+ KillTimer(hwndDlg, 500);
+ SetTimer(hwndDlg, 500, 350, NULL);
+
+ HWND hwndList = artistList->listview.getwnd();
+ if (IsWindow(hwndList))
+ {
+ ListView_SetItemCountEx(hwndList, 0, 0);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+
+ hwndList = albumList->listview.getwnd();
+ if (IsWindow(hwndList))
+ {
+ ListView_SetItemCountEx(hwndList, 0, 0);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+
+ if (numFilters == 3)
+ {
+ hwndList = albumList2->listview.getwnd();
+ if (IsWindow(hwndList))
+ {
+ ListView_SetItemCountEx(hwndList, 0, 0);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+ }
+ }
+ break;
+ case IDC_REFINE:
+ if (HIWORD(wParam) == EN_CHANGE && !noSearchTimer) {
+ KillTimer(hwndDlg,501);
+ SetTimer(hwndDlg,501,250,NULL);
+ }
+ break;
+ case IDC_BUTTON_CLEARSEARCH:
+ {
+ SetDlgItemText(hwndDlg,IDC_QUICKSEARCH,L"");
+ break;
+ }
+ case IDC_BUTTON_CLEARREFINE:
+ SetDlgItemText(hwndDlg,IDC_REFINE,L"");
+ break;
+ case IDC_HEADER_DEVICE_TRANSFER:
+ {
+ if (!IsWindowEnabled(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC)))
+ {
+ MessageBox(hwndDlg, L"Transferring files from this device to another is not currently supported.",
+ L"Cloud Transfer Not Supported", MB_ICONINFORMATION);
+ break;
+ }
+ }
+ case IDC_BUTTON_SYNC:
+ if (!currentViewedDevice->isCloudDevice)
+ currentViewedDevice->Sync();
+ else
+ currentViewedDevice->CloudSync();
+ break;
+ case IDC_BUTTON_AUTOFILL:
+ currentViewedDevice->Autofill();
+ break;
+ case IDC_SEARCH_TEXT:
+ if (HIWORD(wParam) == STN_DBLCLK)
+ {
+ // TODO decide what to do for the 'all_sources'
+ if (currentViewedDevice->isCloudDevice &&
+ lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ {
+ header = !header;
+ currentViewedDevice->config->WriteInt(L"header",header);
+ LayoutWindows(hwndMediaView, TRUE, 0);
+ UpdateStatus(hwndDlg, true);
+ ShowWindow(hwndDlg, 0);
+ ShowWindow(hwndDlg, 1);
+ }
+ }
+ break;
+ }
+ break;
+
+ case WM_USER + 0x200:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 1); // yes, we support no - redraw resize
+ return TRUE;
+
+ case WM_USER + 0x201:
+ offsetX = (short)LOWORD(wParam);
+ offsetY = (short)HIWORD(wParam);
+ g_rgnUpdate = (HRGN)lParam;
+ return TRUE;
+ }
+ return pmp_common_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+static void getStars(int stars, wchar_t * buf, int buflen) {
+ wchar_t * r=L"";
+ switch(stars) {
+ case 1: r=L"\u2605"; break;
+ case 2: r=L"\u2605\u2605"; break;
+ case 3: r=L"\u2605\u2605\u2605"; break;
+ case 4: r=L"\u2605\u2605\u2605\u2605"; break;
+ case 5: r=L"\u2605\u2605\u2605\u2605\u2605"; break;
+ }
+ lstrcpyn(buf,r,buflen);
+}
+
+__inline void TimetToFileTime(time_t t, LPFILETIME pft)
+{
+ LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
+ pft->dwLowDateTime = (DWORD) ll;
+ pft->dwHighDateTime = ll >>32;
+}
+
+void timeToString(__time64_t time, wchar_t * buf, int buflen) {
+ if((__int64)time<=0) { lstrcpyn(buf,L"",buflen); return; }
+
+ FILETIME ft = {0};
+ SYSTEMTIME st = {0};
+ TimetToFileTime(time, &ft);
+ FileTimeToSystemTime(&ft, &st);
+
+ int adjust = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, buf, buflen);
+ buf[adjust-1] = ' ';
+ GetTimeFormat(LOCALE_USER_DEFAULT, NULL, &st, NULL, &buf[adjust], buflen - adjust);
+}
+
+extern void GetInfoString(wchar_t * buf, Device * dev, int numTracks, __int64 totalSize, int totalPlayLength, int cloud);
+
+class PlaylistContents : public PrimaryListContents {
+public:
+ int playlistId;
+ Device * dev;
+ PlaylistContents(int playlistId, Device * dev) {
+ this->playlistId = playlistId;
+ this->dev = dev;
+ int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0);
+ if(!fieldsBits) fieldsBits = -1;
+ C_Config *c = currentViewedDevice->config;
+ this->config = c;
+ fields.Add(new ListField(-1,20, WASABI_API_LNGSTRINGW(IDS_NUMBER),c));
+ if(fieldsBits & SUPPORTS_ARTIST) fields.Add(new ListField(1000, 200,WASABI_API_LNGSTRINGW(IDS_ARTIST),c));
+ if(fieldsBits & SUPPORTS_TITLE) fields.Add(new ListField(1001, 200,WASABI_API_LNGSTRINGW(IDS_TITLE),c));
+ if(fieldsBits & SUPPORTS_ALBUM) fields.Add(new ListField(1002, 200,WASABI_API_LNGSTRINGW(IDS_ALBUM),c));
+ if(fieldsBits & SUPPORTS_LENGTH) fields.Add(new ListField(1003, 64, WASABI_API_LNGSTRINGW(IDS_LENGTH),c));
+ if(fieldsBits & SUPPORTS_TRACKNUM) fields.Add(new ListField(1004, 50, WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER),c));
+ if(fieldsBits & SUPPORTS_DISCNUM) fields.Add(new ListField(1005, 38, WASABI_API_LNGSTRINGW(IDS_DISC),c));
+ if(fieldsBits & SUPPORTS_GENRE) fields.Add(new ListField(1006, 100,WASABI_API_LNGSTRINGW(IDS_GENRE),c));
+ if(fieldsBits & SUPPORTS_YEAR) fields.Add(new ListField(1007, 38, WASABI_API_LNGSTRINGW(IDS_YEAR),c));
+ if(fieldsBits & SUPPORTS_BITRATE) fields.Add(new ListField(1008, 45, WASABI_API_LNGSTRINGW(IDS_BITRATE),c));
+ if(fieldsBits & SUPPORTS_SIZE) fields.Add(new ListField(1009, 90, WASABI_API_LNGSTRINGW(IDS_SIZE),c));
+ if(fieldsBits & SUPPORTS_PLAYCOUNT) fields.Add(new ListField(1010, 64, WASABI_API_LNGSTRINGW(IDS_PLAY_COUNT),c));
+ if(fieldsBits & SUPPORTS_RATING) fields.Add(new ListField(1011, 64, WASABI_API_LNGSTRINGW(IDS_RATING),c));
+ if(fieldsBits & SUPPORTS_LASTPLAYED) fields.Add(new ListField(1012, 100,WASABI_API_LNGSTRINGW(IDS_LAST_PLAYED),c));
+ if(fieldsBits & SUPPORTS_ALBUMARTIST) fields.Add(new ListField(1013, 200,WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST),c,true));
+ if(fieldsBits & SUPPORTS_PUBLISHER) fields.Add(new ListField(1014, 200,WASABI_API_LNGSTRINGW(IDS_PUBLISHER),c,true));
+ if(fieldsBits & SUPPORTS_COMPOSER) fields.Add(new ListField(1015, 200,WASABI_API_LNGSTRINGW(IDS_COMPOSER),c,true));
+ if(fieldsBits & SUPPORTS_MIMETYPE) fields.Add(new ListField(1016, 100,WASABI_API_LNGSTRINGW(IDS_MIME_TYPE),c,true));
+ if(fieldsBits & SUPPORTS_DATEADDED) fields.Add(new ListField(1017, 100,WASABI_API_LNGSTRINGW(IDS_DATE_ADDED),c,true));
+ this->SortColumns();
+ }
+
+ //virtual ~PlaylistContents() { }
+ virtual int GetNumColumns() { return fields.GetSize(); }
+
+ virtual int GetNumRows() { return dev->getPlaylistLength(playlistId); }
+
+ virtual int GetColumnWidth(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->width;
+ return 0;
+ }
+
+ virtual wchar_t * GetColumnTitle(int num) {
+ if(num >=0 && num < fields.GetSize())
+ return ((ListField *)fields.Get(num))->name;
+ return L"";
+ }
+
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) {
+ if(col >=0 && col < fields.GetSize()) col = ((ListField *)fields.Get(col))->field;
+ if(col != -1) col -= 1000;
+ songid_t s = dev->getPlaylistTrack(playlistId,row);
+ buf[0]=0;
+ switch(col) {
+ case -1: wsprintf(buf,L"%d",row+1); return;
+ case 0: dev->getTrackArtist(s,buf,buflen); return;
+ case 1: dev->getTrackTitle(s,buf,buflen); return;
+ case 2: dev->getTrackAlbum(s,buf,buflen); return;
+ case 3: { int l=dev->getTrackLength(s); if (l>=0) wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; }
+ case 4: { int d = dev->getTrackTrackNum(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 5: { int d = dev->getTrackDiscNum(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 6: dev->getTrackGenre(s,buf,buflen); return;
+ case 7: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; }
+ case 8: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; }
+ case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return;
+ case 10: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; }
+ case 11: getStars(dev->getTrackRating(s),buf,buflen); return;
+ case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return;
+ case 13: dev->getTrackAlbumArtist(s,buf,buflen); return;
+ case 14: dev->getTrackPublisher(s,buf,buflen); return;
+ case 15: dev->getTrackComposer(s,buf,buflen); return;
+ }
+ }
+
+ virtual void GetInfoString(wchar_t * buf) {
+ __int64 totalSize=0;
+ int totalPlayLength=0;
+ int millis=0;
+ int l = dev->getPlaylistLength(playlistId);
+ for(int i=0; i < l; i++) {
+ songid_t s = dev->getPlaylistTrack(playlistId,i);
+ totalSize += (__int64)dev->getTrackSize(s);
+ int len=dev->getTrackLength(s);
+ totalPlayLength += len/1000;
+ millis += len%1000;
+ }
+ totalPlayLength += millis/1000;
+ ::GetInfoString(buf, dev, l, totalSize, totalPlayLength, 0);
+ }
+
+ virtual songid_t GetTrack(int pos) { return dev->getPlaylistTrack(playlistId,pos); }
+
+ virtual void ColumnResize(int col, int newWidth) {
+ if(col >=0 && col < fields.GetSize()) {
+ ListField * lf = (ListField *)fields.Get(col);
+ lf->width = newWidth;
+ wchar_t buf[100] = {0};
+ wsprintf(buf,L"colWidth_%d",lf->field);
+ currentViewedDevice->config->WriteInt(buf,newWidth);
+ }
+ }
+};
+
+static INT_PTR CALLBACK find_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ SetDlgItemText(hwndDlg,IDC_EDIT,currentViewedDevice->config->ReadString(L"plfind",L""));
+ if (FALSE != CenterWindow(hwndDlg, (HWND)lParam))
+ SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ {
+ wchar_t buf[256] = {0};
+ GetDlgItemText(hwndDlg,IDC_EDIT,buf,sizeof(buf)/sizeof(wchar_t));
+ buf[255]=0;
+ currentViewedDevice->config->WriteString(L"plfind",buf);
+ EndDialog(hwndDlg,(INT_PTR)_wcsdup(buf));
+ }
+ break;
+ case IDCANCEL:
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+extern C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs, Device * dev, bool cloud);
+
+static int m_pldrag;
+
+INT_PTR CALLBACK pmp_playlist_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static bool pldrag_changed;
+ if (wad_handleDialogMsgs) { BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a; }
+ if (tracksList) { BOOL a=tracksList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ groupBtn = gen_mlconfig->ReadInt(L"groupbtn", 1);
+ enqueuedef = (gen_mlconfig->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_pmp"};
+ 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;
+ }
+
+ playmode = L"plplaymode";
+ hwndMediaView = hwndDlg;
+ pldrag_changed=false;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+ tracks = new PlaylistContents(currentViewedPlaylist,currentViewedDevice->dev);
+ m_pldrag=-1;
+
+ {
+ MLSKINWINDOW skin = {0};
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ FLICKERFIX ff = {0};
+ ff.mode = FFM_ERASEINPAINT;
+ INT ffcl[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_BUTTON_SORT, IDC_BUTTON_EJECT, IDC_STATUS};
+ for (int i = 0; i < sizeof(ffcl) / sizeof(INT); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn && (i < 3) ? SWBS_SPLITBUTTON : 0);
+ skin.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ if(tracks) delete tracks; tracks=0;
+ break;
+
+ case WM_DROPFILES:
+ return currentViewedDevice->TransferFromDrop((HDROP)wParam, currentViewedPlaylist);
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->idFrom==IDC_LIST_TRACKS)
+ switch(l->code) {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case 0x46: //F
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ wchar_t * findstr = (wchar_t*)WASABI_API_DIALOGBOXPARAMW(IDD_FIND,hwndDlg,find_dlgproc, (LPARAM)hwndDlg);
+ if(!findstr) break;
+ C_ItemList songs;
+ int l = currentViewedDevice->dev->getPlaylistLength(currentViewedPlaylist);
+ int i;
+ for(i=0; i<l; i++) songs.Add((void*)currentViewedDevice->dev->getPlaylistTrack(currentViewedPlaylist,i));
+ C_ItemList * found = FilterSongs(findstr,&songs,currentViewedDevice->dev,0);
+ free(findstr);
+ int j=0;
+ HWND wnd = tracksList->listview.getwnd();
+ for(i=0; i<l; i++) {
+ if(found->Get(j) == songs.Get(i)) {
+ ListView_SetItemState(wnd,i,LVIS_SELECTED,LVIS_SELECTED);
+ if(j++ == 0) ListView_EnsureVisible(wnd,i,TRUE);
+ }
+ else ListView_SetItemState(wnd,i,0,LVIS_SELECTED);
+ }
+ delete found;
+ }
+ }
+ break;
+ case VK_DELETE:
+ if(!GetAsyncKeyState(VK_CONTROL)){
+ if(!GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ else{
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(ID_TRACKSLIST_REMOVEFROMPLAYLIST,0),0);
+ }
+ }
+ break;
+ case 0x43: //C
+ if(GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_COPYTOLIBRARY);
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags), 2);
+ }
+ return TRUE;
+
+ case WM_DISPLAYCHANGE:
+ {
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE, 2);
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ pmp_common_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE, 2);
+ return 0;
+ }
+
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_LIST_TRACKS|DCW_SUNKENBORDER};
+ int size = sizeof(tab) / sizeof(tab[0]);
+ if (wad_DrawChildWindowBorders) wad_DrawChildWindowBorders(hwndDlg,tab,size);
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ m_pldrag=-1;
+ //m_pldrag = tracksList->listview.FindItemByPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
+ break;
+
+ case WM_LBUTTONUP:
+ m_pldrag=-1;
+ if(GetCapture() == hwndDlg) ReleaseCapture();
+ if(pldrag_changed) { currentViewedDevice->DevicePropertiesChanges(); pldrag_changed=false;}
+ break;
+
+ case WM_MOUSEMOVE:
+ if(wParam==MK_LBUTTON) {
+ if(m_pldrag==-1) { m_pldrag=tracksList->listview.FindItemByPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)); break; } // m_pldrag=GET_Y_LPARAM(lParam);
+ int p = tracksList->listview.FindItemByPoint(GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)); // new point
+ if(p==-1) break;
+ //work out how much to move and how
+ int h=1; //height of one item
+ HWND listWnd = tracksList->listview.getwnd();
+ Device * dev = currentViewedDevice->dev;
+ if(p - m_pldrag >= h) {
+ //moved down
+ int start=-1,end=-1;
+ int i;
+ int l = dev->getPlaylistLength(currentViewedPlaylist);
+ for(i=l-1; i>=0; i--) if(ListView_GetItemState(listWnd, i, LVIS_SELECTED) == LVIS_SELECTED) {
+ if(i == l-1) break;
+ if(end == -1) end = i+1;
+ start = i;
+ //change playlist
+ dev->playlistSwapItems(currentViewedPlaylist,i,i+1);
+ pldrag_changed=true;
+ //set selection correctly
+ ListView_SetItemState(listWnd,i,0,LVIS_SELECTED);
+ ListView_SetItemState(listWnd,i+1,LVIS_SELECTED,LVIS_SELECTED);
+ if(ListView_GetItemState(listWnd, i, LVIS_FOCUSED)==LVIS_FOCUSED) {
+ ListView_SetItemState(listWnd,i,0,LVIS_FOCUSED);
+ ListView_SetItemState(listWnd,i+1,LVIS_FOCUSED,LVIS_FOCUSED);
+ }
+ }
+ if(start != -1) {
+ ListView_RedrawItems(listWnd,start,end);
+ m_pldrag += h;
+ }
+ }
+ else if(m_pldrag - p >= h){
+ //moved up
+ int start=-1,end=-1;
+ int i;
+ int l = dev->getPlaylistLength(currentViewedPlaylist);
+ for(i=0; i<l; i++) if(ListView_GetItemState(listWnd, i, LVIS_SELECTED) == LVIS_SELECTED) {
+ if(i == 0) break;
+ if(start == -1) start = i-1;
+ end = i;
+ //change playlist
+ dev->playlistSwapItems(currentViewedPlaylist,i,i-1);
+ pldrag_changed=true;
+ //set selection correctly
+ ListView_SetItemState(listWnd,i,0,LVIS_SELECTED);
+ ListView_SetItemState(listWnd,i-1,LVIS_SELECTED,LVIS_SELECTED);
+ if(ListView_GetItemState(listWnd, i, LVIS_FOCUSED)==LVIS_FOCUSED) {
+ ListView_SetItemState(listWnd,i,0,LVIS_FOCUSED);
+ ListView_SetItemState(listWnd,i-1,LVIS_FOCUSED,LVIS_FOCUSED);
+ }
+ }
+ if(start != -1) {
+ ListView_RedrawItems(listWnd,start,end);
+ m_pldrag -= h;
+ }
+ }
+ return 0;
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case ID_TRACKSLIST_REMOVEFROMPLAYLIST:
+ {
+ int l = tracksList->listview.GetCount();
+ while(l-- > 0) if(tracksList->listview.GetSelected(l))
+ currentViewedDevice->dev->removeTrackFromPlaylist(currentViewedPlaylist,l);
+ tracksList->UpdateList();
+ currentViewedDevice->DevicePropertiesChanges();
+ }
+ break;
+ case IDC_BUTTON_SORT:
+ {
+ HMENU menu = GetSubMenu(m_context_menus,5);
+ POINT p;
+ GetCursorPos(&p);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY, p.x, p.y, hwndDlg, NULL);
+ if(r >= ID_SORTPLAYLIST_ARTIST && r <= ID_SORTPLAYLIST_LASTPLAYED) {
+ currentViewedDevice->dev->sortPlaylist(currentViewedPlaylist,r - ID_SORTPLAYLIST_ARTIST);
+ }
+ else if(r == ID_SORTPLAYLIST_RANDOMIZE) {
+ int elems = currentViewedDevice->dev->getPlaylistLength(currentViewedPlaylist);
+ if (elems > 1) {
+ for (int p = 0; p < elems; p ++) {
+ int np=genrand_int31()%elems;
+ if (p != np) // swap pp and np
+ currentViewedDevice->dev->playlistSwapItems(currentViewedPlaylist,p,np);
+ }
+ }
+ }
+ else if(r == ID_SORTPLAYLIST_REVERSEPLAYLIST) {
+ int i=0;
+ int j = currentViewedDevice->dev->getPlaylistLength(currentViewedPlaylist) - 1;
+ while(i < j)
+ currentViewedDevice->dev->playlistSwapItems(currentViewedPlaylist,i++,j--);
+ }
+ if(r > 0) {
+ tracksList->UpdateList(true);
+ currentViewedDevice->DevicePropertiesChanges();
+ }
+ }
+ break;
+ }
+ break; // WM_COMMAND
+ }
+ return pmp_common_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+INT_PTR CALLBACK pmp_video_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+
+ if (wad_handleDialogMsgs) { BOOL a=wad_handleDialogMsgs(hwndDlg,uMsg,wParam,lParam); if (a) return a; }
+ if (tracksList) { BOOL a=tracksList->DialogProc(hwndDlg,uMsg,wParam,lParam); if(a) return a; }
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ if (!view.play)
+ {
+ SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
+ }
+
+ groupBtn = gen_mlconfig->ReadInt(L"groupbtn", 1);
+ enqueuedef = (gen_mlconfig->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_pmp"};
+ 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;
+ }
+
+ playmode = L"viewplaymode";
+ hwndMediaView = hwndDlg;
+ *(void **)&wad_handleDialogMsgs=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,2,ML_IPC_SKIN_WADLG_GETFUNC);
+ *(void **)&wad_DrawChildWindowBorders=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,3,ML_IPC_SKIN_WADLG_GETFUNC);
+
+ if (!lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ header = 1;
+ else
+ header = currentViewedDevice->config->ReadInt(L"header",0);
+
+ HWND list = GetDlgItem(hwndDlg, IDC_LIST_TRACKS);
+ // TODO need to be able to change the order of the tracks items (so cloud is in a more appropriate place)
+ //ListView_SetExtendedListViewStyleEx(list, LVS_EX_HEADERDRAGDROP, LVS_EX_HEADERDRAGDROP);
+ ListView_SetExtendedListViewStyle(list, LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP);
+
+ if (currentViewedDevice->config->ReadInt(L"savefilter", 1))
+ {
+ SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, currentViewedDevice->config->ReadString(L"savedfilter", L""));
+ }
+
+ // depending on how this is called, it will either do a video only view or will be re-purposed as a 'simple' view
+ aacontents = new ArtistAlbumLists(currentViewedDevice->dev, currentViewedPlaylist, currentViewedDevice->config,
+ NULL, 0, (lParam ? -1 : (currentViewedDevice->videoView ? 1 : -1)), (lParam == 1));
+
+ if (aacontents)
+ {
+ tracks = aacontents->GetTracksList();
+ }
+
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = list;
+ skin.skinType = SKINNEDWND_TYPE_LISTVIEW;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(list), tracks->cloudcol);
+
+ FLICKERFIX ff = {0};
+ ff.mode = FFM_ERASEINPAINT;
+ INT ffcl[] = { IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM,
+ IDC_BUTTON_CLEARSEARCH, IDC_BUTTON_CLEARREFINE,
+ IDC_BUTTON_EJECT, IDC_BUTTON_SYNC, IDC_BUTTON_AUTOFILL,
+ IDC_STATUS, IDC_SEARCH_TEXT, IDC_QUICKSEARCH, IDC_REFINE,
+ IDC_REFINE_TEXT,
+ // disabled cloud parts
+ /*IDC_HEADER_DEVICE_ICON, IDC_HEADER_DEVICE_NAME,
+ IDC_HEADER_DEVICE_BAR, IDC_HEADER_DEVICE_SIZE,
+ IDC_HEADER_DEVICE_TRANSFER,*/
+ };
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ for (int i = 0; i < sizeof(ffcl) / sizeof(INT); i++)
+ {
+ ff.hwnd = GetDlgItem(hwndDlg, ffcl[i]);
+ if (IsWindow(ff.hwnd))
+ {
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&ff, ML_IPC_FLICKERFIX);
+
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn && (i < 3) ? SWBS_SPLITBUTTON : 0);
+ skin.hwndToSkin = ff.hwnd;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+
+ skin.hwndToSkin = hwndDlg;
+ skin.skinType = SKINNEDWND_TYPE_AUTO;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+
+ if (0 != currentViewedDevice->dev->extraActions(DEVICE_SYNC_UNSUPPORTED,0,0,0))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC), FALSE);
+ }
+ break;
+
+ case WM_USER+1:
+ if (tracks)
+ {
+ if (lParam)
+ {
+ tracks->RemoveTrack((songid_t)wParam);
+ }
+ tracksList->UpdateList();
+ }
+ break;
+
+ case WM_DESTROY:
+ if (aacontents) aacontents->bgQuery_Stop();
+ tracks = NULL;
+ if (aacontents) delete aacontents; aacontents=NULL;
+ break;
+
+ case WM_DROPFILES:
+ return currentViewedDevice->TransferFromDrop((HDROP)wParam);
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom == IDC_LIST_TRACKS)
+ {
+ switch (l->code)
+ {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case VK_DELETE:
+ {
+ if(!GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ handleContextMenuResult(ID_TRACKSLIST_DELETE);
+ }
+ }
+ break;
+ case 0x45: //E
+ bool noEdit = currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA,0,0,0)!=0;
+ if(!noEdit && GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ C_ItemList * items = getSelectedItems();
+ editInfo(items,currentViewedDevice->dev, CENTER_OVER_ML_VIEW);
+ delete items;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & ((WINDOWPOS*)lParam)->flags) ||
+ (SWP_FRAMECHANGED & ((WINDOWPOS*)lParam)->flags))
+ {
+ LayoutWindows(hwndDlg, !(SWP_NOREDRAW & ((WINDOWPOS*)lParam)->flags), 1);
+ }
+ return TRUE;
+
+ case WM_DISPLAYCHANGE:
+ {
+ UpdateWindow(hwndDlg);
+ LayoutWindows(hwndDlg, TRUE, 1);
+ }
+ break;
+
+ case WM_APP + 104:
+ {
+ pmp_common_UpdateButtonText(hwndDlg, wParam);
+ LayoutWindows(hwndDlg, TRUE, 1);
+ return 0;
+ }
+
+ case WM_PAINT:
+ {
+ int tab[] = {IDC_LIST_TRACKS|DCW_SUNKENBORDER,
+ IDC_QUICKSEARCH|DCW_SUNKENBORDER,
+ (!header?IDC_HDELIM2|DCW_DIVIDER:0),
+ };
+ int size = sizeof(tab) / sizeof(tab[0]);
+ // do this to prevent drawing parts when the views are collapsed
+ if (!currentViewedDevice->isCloudDevice) size -= 1;
+ if (wad_DrawChildWindowBorders) wad_DrawChildWindowBorders(hwndDlg,tab,size);
+ }
+ return TRUE;
+
+ case WM_APP + 3: // send by bgthread
+ if (wParam == 0x69)
+ {
+ if (aacontents)
+ {
+ aacontents->bgQuery_Stop();
+ tracks = aacontents->GetTracksList();
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg, true);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_QUICKSEARCH:
+ if (HIWORD(wParam) == EN_CHANGE && !noSearchTimer) {
+ KillTimer(hwndDlg,500);
+ SetTimer(hwndDlg,500,250,NULL);
+ }
+ break;
+ case IDC_HEADER_DEVICE_TRANSFER:
+ {
+ if (!IsWindowEnabled(GetDlgItem(hwndDlg, IDC_BUTTON_SYNC)))
+ {
+ MessageBox(hwndDlg, L"Transferring files from this device to another is not currently supported.",
+ L"Cloud Transfer Not Supported", MB_ICONINFORMATION);
+ break;
+ }
+ }
+ case IDC_BUTTON_SYNC:
+ if (!currentViewedDevice->isCloudDevice)
+ currentViewedDevice->Sync();
+ else
+ currentViewedDevice->CloudSync();
+ break;
+ case IDC_SEARCH_TEXT:
+ if (HIWORD(wParam) == STN_DBLCLK)
+ {
+ // TODO decide what to do for the 'all_sources'
+ if (currentViewedDevice->isCloudDevice &&
+ lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ {
+ header = !header;
+ currentViewedDevice->config->WriteInt(L"header",header);
+ LayoutWindows(hwndMediaView, TRUE, 1);
+ UpdateStatus(hwndDlg);
+ ShowWindow(hwndDlg, 0);
+ ShowWindow(hwndDlg, 1);
+ }
+ }
+ break;
+ case IDC_BUTTON_CLEARSEARCH:
+ SetDlgItemText(hwndDlg,IDC_QUICKSEARCH,L"");
+ break;
+ }
+ break; //WM_COMMAND
+
+ case WM_TIMER:
+ switch(wParam) {
+ case 123:
+ {
+ if (aacontents && aacontents->bgThread_Handle && tracksList)
+ {
+ HWND hwndList;
+ hwndList = tracksList->listview.getwnd();
+ if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
+ ListView_RedrawItems(hwndList, 0, 0);
+ }
+ }
+ break;
+
+ case 500:
+ {
+ KillTimer(hwndDlg,500);
+ if (currentViewedDevice && aacontents)
+ {
+ wchar_t buf[256]=L"";
+ GetDlgItemText(hwndDlg,IDC_QUICKSEARCH,buf,sizeof(buf)/sizeof(wchar_t));
+ aacontents->SetSearch(buf, (!!currentViewedDevice->isCloudDevice));
+ if (!aacontents->bgThread_Handle)
+ {
+ if (tracksList) tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ return pmp_common_dlgproc(hwndDlg,uMsg,wParam,lParam);
+}
+
+static INT_PTR CALLBACK pmp_common_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ {
+ hwndMediaView = hwndDlg;
+
+ HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_ACCELERATORS);
+ if (accel)
+ WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD);
+
+ if (currentViewedDevice->isCloudDevice)
+ {
+ wchar_t buf[256] = {0};
+ currentViewedDevice->GetDisplayName(buf, 128);
+ SetDlgItemText(hwndDlg, IDC_HEADER_DEVICE_NAME, buf);
+
+ int icon_id = currentViewedDevice->dev->extraActions(0x22, 0, 0, 0);
+ HBITMAP bm = (HBITMAP)LoadImage(GetModuleHandle(L"pmp_cloud.dll"),
+ MAKEINTRESOURCE(icon_id > 0 ? icon_id : 103), IMAGE_BITMAP, 16, 16,
+ LR_SHARED | LR_LOADTRANSPARENT | LR_CREATEDIBSECTION);
+
+ SendDlgItemMessage(hwndDlg, IDC_HEADER_DEVICE_ICON, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bm);
+
+ HWND progress = GetDlgItem(hwndDlg, IDC_HEADER_DEVICE_BAR);
+ if (IsWindow(progress))
+ {
+ MLSKINWINDOW skin = {0};
+ skin.hwndToSkin = progress;
+ skin.skinType = SKINNEDWND_TYPE_PROGRESSBAR;
+ skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
+ MLSkinWindow(plugin.hwndLibraryParent, &skin);
+ }
+ }
+
+ tracksList = new SkinnedListView(tracks,IDC_LIST_TRACKS,plugin.hwndLibraryParent, hwndDlg);
+ tracksList->DialogProc(hwndDlg,WM_INITDIALOG,0,0);
+ if (tracksList->contents && !tracksList->contents->cloud)
+ {
+ tracksList->UpdateList();
+ UpdateStatus(hwndDlg);
+ }
+
+ pmp_common_UpdateButtonText(hwndDlg, enqueuedef == 1);
+ }
+
+ case WM_DISPLAYCHANGE:
+ {
+ if (currentViewedDevice->isCloudDevice)
+ {
+ int (*wad_getColor)(int idx);
+ *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ ARGB32 fc = (!wad_getColor?wad_getColor(WADLG_WNDBG):RGB(0xFF,0xFF,0xFF)) & 0x00FFFFFF;
+ SetToolbarButtonBitmap(hwndDlg,IDC_HEADER_DEVICE_TRANSFER, MAKEINTRESOURCE(IDR_TRANSFER_SMALL_ICON),fc);
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ if (currentViewedDevice)
+ {
+ SkinBitmap *s = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_HEADER_DEVICE_TRANSFER),GWLP_USERDATA);
+ if (s) DeleteSkinBitmap(s);
+
+ if (currentViewedDevice->config->ReadInt(L"savefilter", 1))
+ {
+ wchar_t buf[256] = {0};
+ GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, 256);
+ currentViewedDevice->config->WriteString(L"savedfilter", buf);
+ buf[0] = 0;
+ GetDlgItemTextW(hwndDlg, IDC_REFINE, buf, 256);
+ currentViewedDevice->config->WriteString(L"savedrefinefilter", buf);
+ }
+ }
+
+ WASABI_API_APP->app_removeAccelerators(hwndDlg);
+
+ hwndMediaView = NULL;
+ currentViewedDevice = NULL;
+ if (tracksList)
+ {
+ delete tracksList;
+ tracksList = NULL;
+ }
+ }
+ break;
+
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+ if (di->CtlType == ODT_BUTTON || di->CtlType == ODT_STATIC) {
+ if(di->CtlID == IDC_HEADER_DEVICE_TRANSFER)
+ { // draw the toolbar buttons!
+ int (*wad_getColor)(int idx);
+ *(void **)&wad_getColor=(void*)SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,1,ML_IPC_SKIN_WADLG_GETFUNC);
+ HBRUSH hbr = CreateSolidBrush((wad_getColor?wad_getColor(WADLG_WNDBG):RGB(0xFF,0xFF,0xFF)));
+ FillRect(di->hDC, &di->rcItem, hbr);
+ DeleteBrush(hbr);
+
+ SkinBitmap* hbm = (SkinBitmap*)GetWindowLongPtr(GetDlgItem(hwndDlg,di->CtlID),GWLP_USERDATA);
+ if(hbm && di->rcItem.left != di->rcItem.right && di->rcItem.top != di->rcItem.bottom) {
+ DCCanvas dc(di->hDC);
+ if (di->itemState & ODS_SELECTED) hbm->blitAlpha(&dc,di->rcItem.left+1,di->rcItem.top+1);
+ else hbm->blitAlpha(&dc,di->rcItem.left,di->rcItem.top);
+ dc.Release();
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if(l->code == LVN_KEYDOWN && ((LPNMLVKEYDOWN)lParam)->wVKey == VK_F5)
+ {
+ if (currentViewedDevice->dev->extraActions(DEVICE_REFRESH,0,0,0))
+ {
+ SetTimer(hwndDlg, (artistList ? 501 : 500), 250, NULL);
+ }
+ }
+ if(l->idFrom == IDC_LIST_TRACKS)
+ {
+ switch(l->code) {
+ case LVN_KEYDOWN:
+ switch(((LPNMLVKEYDOWN)lParam)->wVKey) {
+ case 0x45: //E
+ {
+ bool noEdit = currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA,0,0,0)!=0;
+ if(!noEdit && GetAsyncKeyState(VK_CONTROL) && !GetAsyncKeyState(VK_SHIFT)){
+ C_ItemList * items = getSelectedItems();
+ editInfo(items,currentViewedDevice->dev, CENTER_OVER_ML_VIEW);
+ delete items;
+ }
+ }
+ break;
+ }
+ break;
+ case NM_RETURN:
+ case NM_DBLCLK:
+ {
+ C_ItemList * items = getSelectedItems(true);
+ int start=0;
+ int l=tracksList->listview.GetCount();
+
+ int i = 0;
+ for (; i < l; i++)
+ if (tracksList->listview.GetSelected(i))
+ {
+ start = i;
+ break;
+ }
+ if (items->GetSize() && (!!(GetAsyncKeyState(VK_SHIFT)&0x8000) || !gen_mlconfig->ReadInt(playmode,1))) {
+ void* x = items->Get(i);
+ delete items;
+ items = new C_ItemList;
+ items->Add(x);
+ start = 0;
+ }
+ currentViewedDevice->PlayTracks(items, start, (!(GetAsyncKeyState(VK_SHIFT)&0x8000) ? (enqueuedef == 1) : (enqueuedef != 1)), false);
+ delete items;
+ }
+ break;
+
+ case NM_CUSTOMDRAW:
+ {
+ LRESULT result = 0;
+ if (ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)lParam, &result))
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return 1;
+ }
+ }
+ break;
+
+ case LVN_GETDISPINFOW:
+ {
+ NMLVDISPINFOW* pdi = (NMLVDISPINFOW*)lParam;
+ if (aacontents && aacontents->bgThread_Handle)
+ {
+ LVITEMW *pItem = &pdi->item;
+ if (0 == pItem->iItem && (LVIF_TEXT & pItem->mask))
+ {
+ if (0 == pItem->iSubItem)
+ {
+ static char bufpos = 0;
+ static int buflen = 17;
+ static wchar_t buffer[64];//L"Scanning _\0/-\\|";
+ if (!buffer[0])
+ {
+ //WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_PLAIN,buffer,54);
+ StringCchCopyW(buffer,64,L"Scanning");
+ StringCchCatW(buffer,64,L" _");
+ StringCchCatW(buffer+(buflen=lstrlenW(buffer)+1),64,L"/-\\|");
+ buflen+=4;
+ }
+ int pos = buflen - 5;;
+ buffer[pos - 1] = buffer[pos + (bufpos++&3) + 1];
+ pItem->pszText = buffer;
+ SetDlgItemText(hwndDlg, IDC_STATUS, buffer);
+ }
+ else
+ {
+ pItem->pszText = L"";
+ }
+ }
+ return 1;
+ }
+ }
+ break;
+
+ case NM_CLICK:
+ {
+ // prevent the right-click menus appearing when scanning
+ if (aacontents && aacontents->bgThread_Handle) break;
+
+ LPNMITEMACTIVATE lpnmitem = (LPNMITEMACTIVATE)lParam;
+ if (lpnmitem->iItem != -1 && lpnmitem->iSubItem == tracks->cloudcol)
+ {
+ RECT itemRect = {0};
+ if (lpnmitem->iSubItem)
+ ListView_GetSubItemRect(l->hwndFrom, lpnmitem->iItem, lpnmitem->iSubItem, LVIR_BOUNDS, &itemRect);
+ else
+ {
+ ListView_GetItemRect(l->hwndFrom, lpnmitem->iItem, &itemRect, LVIR_BOUNDS);
+ itemRect.right = itemRect.left + ListView_GetColumnWidth(l->hwndFrom, tracks->cloudcol);
+ }
+ MapWindowPoints(l->hwndFrom, HWND_DESKTOP, (POINT*)&itemRect, 2);
+
+ int cloud_devices = 0;
+ int mark = tracksList->listview.GetSelectionMark();
+ if (mark != -1)
+ {
+ // if wanting to do on the selection then use getSelectedItems()
+ //C_ItemList * items = getSelectedItems();
+ // otherwise only work on the selection mark for speed (and like how ml_local does things)
+ C_ItemList * items = new C_ItemList;
+ items->Add((void*)tracks->GetTrack(mark));
+ HMENU cloud_menu = (HMENU)currentViewedDevice->dev->extraActions(DEVICE_GET_CLOUD_SOURCES_MENU, (intptr_t)&cloud_devices, 0, (intptr_t)items);
+ if (cloud_menu)
+ {
+ int r = Menu_TrackSkinnedPopup(cloud_menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY,
+ itemRect.right, itemRect.top, hwndDlg, NULL);
+ if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER) { // deals with cloud specific menus
+ int ret = currentViewedDevice->dev->extraActions(DEVICE_DO_CLOUD_SOURCES_MENU, (intptr_t)r, 1, (intptr_t)items);
+ // only send a removal from the view if plug-in says so
+ if (ret) SendMessage(hwndDlg, WM_USER+1, (WPARAM)items->Get(0), ret);
+ }
+ DestroyMenu(cloud_menu);
+ }
+ delete items;
+ }
+ }
+ }
+ break;
+ }
+ }
+ else if(l->idFrom==IDC_LIST_ARTIST || l->idFrom==IDC_LIST_ALBUM || l->idFrom==IDC_LIST_ALBUM2)
+ {
+ switch(l->code) {
+ case NM_CUSTOMDRAW:
+ {
+ LRESULT result = 0;
+ if (ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)lParam, &result))
+ {
+ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result);
+ return 1;
+ }
+ }
+ break;
+ }
+ }
+ }
+ break; //WM_NOTIFY
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_BUTTON_EJECT:
+ currentViewedDevice->Eject();
+ break;
+
+ case IDC_BUTTON_PLAY:
+ case ID_TRACKSLIST_PLAYSELECTION:
+ case IDC_BUTTON_ENQUEUE:
+ case ID_TRACKSLIST_ENQUEUESELECTION:
+ case IDC_BUTTON_CUSTOM:
+ {
+ if (HIWORD(wParam) == MLBN_DROPDOWN)
+ {
+ pmp_common_PlayEnqueue(hwndDlg, (HWND)lParam, LOWORD(wParam));
+ }
+ else
+ {
+ bool action;
+ if (LOWORD(wParam) == IDC_BUTTON_PLAY || LOWORD(wParam) == ID_TRACKSLIST_PLAYSELECTION)
+ action = (HIWORD(wParam) == 1) ? enqueuedef == 1 : 0;
+ else if (LOWORD(wParam) == IDC_BUTTON_ENQUEUE || LOWORD(wParam) == ID_TRACKSLIST_ENQUEUESELECTION)
+ action = (HIWORD(wParam) == 1) ? (enqueuedef != 1) : 1;
+ else
+ // so custom can work with the menu item part
+ break;
+
+ C_ItemList * selected = getSelectedItems();
+ currentViewedDevice->PlayTracks(selected, 0, action, true);
+ delete selected;
+ }
+ }
+ break;
+
+ default:
+ handleContextMenuResult(LOWORD(wParam));
+ break;
+ }
+ break; //WM_COMMAND
+
+ case WM_CONTEXTMENU:
+ {
+ // prevent the right-click menus appearing when scanning
+ if (aacontents && aacontents->bgThread_Handle) break;
+
+ POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
+ UINT_PTR idFrom = GetDlgCtrlID((HWND)wParam);
+ HWND hwndFrom = (HWND)wParam;
+ HWND hwndFromChild = WindowFromPoint(pt);
+
+ // deal with the column headers first
+ SkinnedListView * list=NULL;
+ int n = 0;
+ if (artistList && hwndFromChild == ListView_GetHeader(artistList->listview.getwnd())) { n=0; list=artistList; }
+ if (albumList && hwndFromChild == ListView_GetHeader(albumList->listview.getwnd())) { n=1; list=albumList; }
+ if (albumList2 && hwndFromChild == ListView_GetHeader(albumList2->listview.getwnd())) { n=2; list=albumList2; }
+ if (list)
+ {
+ HMENU menu = list->GetMenu(true,n,currentViewedDevice->config,m_context_menus);
+ int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hwndDlg, NULL);
+ list->ProcessMenuResult(r,true,n,currentViewedDevice->config,hwndDlg);
+ return 0;
+ }
+
+ // and then do the list menus
+ if(idFrom==IDC_LIST_ARTIST || idFrom==IDC_LIST_ALBUM || idFrom==IDC_LIST_ALBUM2)
+ {
+ handleContextMenuResult(showContextMenu(1,hwndFrom,currentViewedDevice->dev,pt));
+ }
+ else if(idFrom == IDC_LIST_TRACKS)
+ {
+ // prevents funkiness if having shown the 'customize columns' menu before
+ // as well as alloing the shift+f10 key to work within the track listview
+ if ((hwndFrom == hwndFromChild) || (pt.x == -1 && pt.y == -1))
+ {
+ int r = showContextMenu((GetDlgItem(hwndDlg, IDC_BUTTON_SORT) ? 2 : 0), hwndFrom, currentViewedDevice->dev, pt);
+ switch(r) {
+ case ID_TRACKSLIST_REMOVEFROMPLAYLIST:
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(ID_TRACKSLIST_REMOVEFROMPLAYLIST,0),0);
+ break;
+ default:
+ handleContextMenuResult(r);
+ break;
+ }
+ }
+ }
+ 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)
+ {
+ restoreDone = FALSE;
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH));
+ }
+ break;
+ }
+ return 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))
+
+// disabled cloud parts
+/*#define GROUP_MIN 0x1
+#define GROUP_MAX 0x7
+#define GROUP_HEADER 0x1
+#define GROUP_SEARCH 0x2
+#define GROUP_FILTER 0x3
+#define GROUP_HDELIM 0x4
+#define GROUP_REFINE 0x5
+#define GROUP_TRACKS 0x6
+#define GROUP_STATUS 0x7*/
+#define GROUP_MIN 0x1
+#define GROUP_MAX 0x6
+#define GROUP_SEARCH 0x1
+#define GROUP_FILTER 0x2
+#define GROUP_HDELIM 0x3
+#define GROUP_REFINE 0x4
+#define GROUP_TRACKS 0x5
+#define GROUP_STATUS 0x6
+
+void LayoutWindows(HWND hwnd, BOOL fRedraw, int simple)
+{
+ static INT controls[] =
+ {
+ // disabled cloud parts
+ //GROUP_HEADER, IDC_HEADER_DEVICE_TRANSFER, IDC_HEADER_DEVICE_ICON, IDC_HEADER_DEVICE_NAME, IDC_HEADER_DEVICE_BAR, IDC_HEADER_DEVICE_SIZE, IDC_HDELIM2,
+ GROUP_SEARCH, IDC_BUTTON_ARTMODE, IDC_BUTTON_VIEWMODE, IDC_BUTTON_COLUMNS, IDC_SEARCH_TEXT, IDC_BUTTON_CLEARSEARCH, IDC_QUICKSEARCH,
+ GROUP_STATUS, IDC_BUTTON_EJECT, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_CUSTOM, IDC_BUTTON_SYNC, IDC_BUTTON_AUTOFILL, IDC_BUTTON_SORT, IDC_STATUS,
+ GROUP_HDELIM, IDC_HDELIM,
+ GROUP_REFINE, IDC_REFINE_TEXT, IDC_BUTTON_CLEARREFINE, IDC_REFINE,
+ GROUP_TRACKS, IDC_LIST_TRACKS,
+ GROUP_FILTER, IDC_LIST_ARTIST, IDC_VDELIM, IDC_LIST_ALBUM, IDC_VDELIM2, IDC_LIST_ALBUM2,
+ };
+
+ INT index, divY, divX, divX2, divCY;
+ RECT rc, rg, ri;
+ LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl;
+ BOOL skipgroup;
+ HRGN rgn;
+
+ rgn = NULL;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.right == rc.left || rc.bottom == rc.top) return;
+ if (rc.right > 4) rc.right -= 4;
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.top);
+
+ pl = layout;
+ skipgroup = FALSE;
+
+ divX = (adiv1pos * (rc.right- rc.left)) / 100000;
+ divX2 = numFilters == 3 ? ((adiv3pos * (rc.right- rc.left)) / 100000) : rc.right;
+ divY = (((-1 == adiv2pos) ? 50000 : adiv2pos) * (rc.bottom- rc.top)) / 100000;
+ divCY = 0;
+
+ InvalidateRect(hwnd, NULL, TRUE);
+
+ for (index = 0; index < sizeof(controls) / sizeof(INT); index++)
+ {
+ if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id
+ {
+ skipgroup = FALSE;
+ switch(controls[index])
+ {
+ // disabled cloud parts
+ /*case GROUP_HEADER:
+ if (currentViewedDevice->isCloudDevice && simple != 2)
+ {
+ SetRect(&rg, rc.left, rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right + WASABI_API_APP->getScaleX(1),
+ (!header ? rc.top + WASABI_API_APP->getScaleY(18) : rc.top));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ else
+ skipgroup = 1;
+ break;*/
+ case GROUP_SEARCH:
+ if (g_displaysearch && simple != 2)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_CLEARSEARCH);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.top + WASABI_API_APP->getScaleY(2),
+ rc.right - WASABI_API_APP->getScaleX(2),
+ rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1));
+ rc.top = rg.bottom + WASABI_API_APP->getScaleY(3);
+ }
+ else
+ skipgroup = 1;
+ break;
+ case GROUP_STATUS:
+ if (g_displaystatus)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_EJECT);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)),
+ rc.right, rc.bottom);
+ rc.bottom = rg.top - WASABI_API_APP->getScaleY(3);
+ }
+ skipgroup = !g_displaystatus;
+ break;
+ case GROUP_HDELIM:
+ SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom);
+ break;
+ case GROUP_REFINE:
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_CLEARREFINE);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ rg.top = divY + divCY;
+ refineHidden = (g_displayrefine) ? ((rg.top + WASABI_API_APP->getScaleY(HIWORD(idealSize))) >= rc.bottom) : TRUE;
+ SetRect(&rg, rc.left, rg.top, rc.right, (refineHidden) ? rg.top : (rg.top + WASABI_API_APP->getScaleY(HIWORD(idealSize))));
+ break;
+ }
+ case GROUP_TRACKS:
+ if (!simple)
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_CLEARREFINE);
+ GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer);
+
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1),
+ (divY + divCY) + ((!refineHidden) ? WASABI_API_APP->getScaleY(HIWORD(idealSize) + 3) : 0),
+ rc.right, rc.bottom);
+ }
+ else
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom);
+ break;
+ case GROUP_FILTER:
+ if (!simple)
+ {
+ if (divX < (rc.left + 15))
+ {
+ divX = rc.left;
+ adiv1_nodraw = 1;
+ }
+ else if (divX > (rc.right - (numFilters == 3 ? 16 * 2 : 24)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_VDELIM), &rw);
+ divX = rc.right - (rw.right - rw.left) - (numFilters == 3 ? 6 : 0) + (numFilters == 2 ? 1 : -1);
+ adiv1_nodraw = 2;
+ }
+ else adiv1_nodraw = 0;
+
+ if (divX2 < (rc.left + 16 * (numFilters == 3 ? 2 : 1)))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_VDELIM), &rw);
+ divX2 = rc.left;
+ adiv3_nodraw = 1;
+ }
+ else if (divX2 > (rc.right - 16 * 2))
+ {
+ RECT rw;
+ GetWindowRect(GetDlgItem(hwnd, IDC_VDELIM2), &rw);
+ divX2 = rc.right - ((rw.right - rw.left) - 1) * 2;
+ adiv3_nodraw = 2;
+ }
+ else adiv3_nodraw = 0;
+ SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, divY);
+ }
+ else
+ skipgroup = 1;
+ 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)
+ {
+ // disabled cloud parts
+ /*case IDC_HEADER_DEVICE_TRANSFER:
+ SETLAYOUTPOS(pl, rg.right - 18, rg.top - WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(16), WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (rg.right - rg.left) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(3+16));
+ break;
+ case IDC_HEADER_DEVICE_ICON:
+ SETLAYOUTPOS(pl, rg.left, rg.top - WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(16), WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (rg.right - rg.left + WASABI_API_APP->getScaleX(16)) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ case IDC_HEADER_DEVICE_NAME:
+ {
+ SIZE s = {0};
+ wchar_t buf[128] = {0};
+ HDC dc = GetDC(pl->hwnd);
+ HFONT font = (HFONT)SendMessage(pl->hwnd, WM_GETFONT, 0, 0), oldfont = (HFONT)SelectObject(dc, font);
+ int len = GetWindowText(pl->hwnd, buf, sizeof(buf));
+ GetTextExtentPoint32(dc, buf, len, &s);
+ SelectObject(dc, oldfont);
+ ReleaseDC(pl->hwnd, dc);
+
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(4), rg.top, s.cx + ((s.cx / len)), WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (rg.right - rg.left + WASABI_API_APP->getScaleX(12)) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_HEADER_DEVICE_BAR:
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(4), rg.top + WASABI_API_APP->getScaleY(2),
+ WASABI_API_APP->getScaleX(150), WASABI_API_APP->getScaleY(11));
+ pl->flags |= (!header && (rg.right - rg.left) > (ri.right - ri.left)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(8));
+ break;
+ case IDC_HEADER_DEVICE_SIZE:
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(4), rg.top, rg.right - rg.left, WASABI_API_APP->getScaleY(16));
+ pl->flags |= (!header && (ri.right - ri.left) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ case IDC_HDELIM2:
+ SETLAYOUTPOS(pl, 0, rg.bottom + WASABI_API_APP->getScaleY(1), rg.right + WASABI_API_APP->getScaleX(35), 0);
+ pl->flags |= (!header ? SWP_SHOWWINDOW : SWP_HIDEWINDOW);
+ break;*/
+ case IDC_BUTTON_ARTMODE:
+ case IDC_BUTTON_VIEWMODE:
+ case IDC_BUTTON_COLUMNS:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top, (ri.right - ri.left), (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(1));
+ break;
+ case IDC_SEARCH_TEXT:
+ {
+ wchar_t buffer[128] = {0};
+ GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer));
+ LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer);
+
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+ SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(6),
+ rg.top + WASABI_API_APP->getScaleY(1),
+ WASABI_API_APP->getScaleX(LOWORD(idealSize)),
+ (rg.bottom - rg.top));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_REFINE_TEXT:
+ {
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ 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));
+ if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4));
+ break;
+ }
+ case IDC_BUTTON_CLEARSEARCH:
+ {
+ 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_BUTTON_CLEARREFINE:
+ {
+ 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 |= SWP_SHOWWINDOW;
+ 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_REFINE:
+ pl->flags |= ((rg.right > rg.left) && (rg.top < rg.bottom)) ? 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_EJECT:
+ {
+ if (currentViewedDevice->isCloudDevice)
+ {
+ if (currentViewedDevice->dev->extraActions(DEVICE_DOES_NOT_SUPPORT_REMOVE,0,0,0))
+ {
+ 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.right - width + WASABI_API_APP->getScaleX(2),
+ 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.right -= (pl->cx + WASABI_API_APP->getScaleX(3));
+ break;
+ }
+ case IDC_BUTTON_AUTOFILL:
+ if (currentViewedDevice->isCloudDevice)
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+ case IDC_BUTTON_SYNC:
+ if (currentViewedDevice->isCloudDevice)
+ {
+ if (!lstrcmpiA(currentViewedDevice->GetName(), "all_sources"))
+ {
+ pl->flags |= SWP_HIDEWINDOW;
+ break;
+ }
+ }
+ case IDC_BUTTON_PLAY:
+ case IDC_BUTTON_ENQUEUE:
+ case IDC_BUTTON_CUSTOM:
+ case IDC_BUTTON_SORT:
+ {
+ 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_STATUS:
+ SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(18), rg.right - rg.left, WASABI_API_APP->getScaleY(17));
+ if (SWP_SHOWWINDOW & pl->flags) rg.top = (pl->y + pl->cy + WASABI_API_APP->getScaleY(1));
+ break;
+ case IDC_HDELIM:
+ divCY = ri.bottom - ri.top;
+ if (divY > (rg.bottom - WASABI_API_APP->getScaleY(70))) { divY = rg.bottom - divCY; m_nodrawtopborders = 2; }
+ else if (divY < (rg.top + WASABI_API_APP->getScaleY(36))) { divY = rg.top; m_nodrawtopborders = 1; }
+ else m_nodrawtopborders = 0;
+ SETLAYOUTPOS(pl, rg.left, divY, rg.right - rg.left + WASABI_API_APP->getScaleX(2), (ri.bottom - ri.top));
+ break;
+ case IDC_LIST_TRACKS:
+ pl->flags |= (rg.top < rg.bottom) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
+
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1),
+ (rg.right - rg.left) + WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2));
+
+ break;
+ case IDC_LIST_ARTIST:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), divX, (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ rg.left += pl->cx;
+ break;
+ case IDC_VDELIM:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (ri.right - ri.left), (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST_ALBUM:
+ if(numFilters == 3)
+ {
+ BOOL hide = ((divX2 - divX - 1) < WASABI_API_APP->getScaleX(15));
+ pl->flags |= hide ? SWP_HIDEWINDOW : SWP_SHOWWINDOW;
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (hide ? 0 : divX2 - divX - WASABI_API_APP->getScaleX(1)),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ }
+ else { SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) + WASABI_API_APP->getScaleX(1),
+ (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1)); }
+ rg.left += pl->cx;
+ break;
+ case IDC_VDELIM2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (numFilters == 2 ? 0 : ri.right - ri.left), (rg.bottom - rg.top));
+ rg.left += pl->cx;
+ break;
+ case IDC_LIST_ALBUM2:
+ SETLAYOUTPOS(pl, rg.left, rg.top + WASABI_API_APP->getScaleY(1), (rg.right - rg.left) + WASABI_API_APP->getScaleX(1),
+ (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) && IsWindowVisible(pl->hwnd))
+ {
+ SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy);
+ ValidateRect(hwnd, &ri);
+ }
+ pl++;
+ }
+ else if ((fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd))
+ {
+ ValidateRect(hwnd, &ri);
+ if (GetUpdateRect(pl->hwnd, NULL, FALSE))
+ {
+ if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0);
+ GetUpdateRgn(pl->hwnd, rgn, FALSE);
+ OffsetRgn(rgn, pl->x, pl->y);
+ InvalidateRgn(hwnd, rgn, FALSE);
+ }
+ }
+ }
+
+ if (pl != layout)
+ {
+ LAYOUT *pc;
+ HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout));
+ for(pc = layout; pc < pl && hdwp; pc++)
+ {
+ 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 WINAPI OnDividerMoved(HWND hwnd, INT nPos, LPARAM param)
+{
+ RECT rc;
+ HWND hwndParent;
+ hwndParent = GetParent(hwnd);
+ KillTimer(hwndParent,400);
+ SetTimer(hwndParent,400,1000,NULL);
+
+ if (hwndParent)
+ {
+ GetClientRect(hwndParent, &rc);
+ switch((INT)param)
+ {
+ case IDC_VDELIM:
+ {
+ adiv1pos = (nPos * 100000) / (rc.right - rc.left + 10);
+ if(adiv1pos + 500 >= adiv3pos) adiv3pos = adiv1pos + 500;
+ }
+ break;
+ case IDC_HDELIM:
+ adiv2pos = (nPos * 100000) / (rc.bottom - rc.top);
+ break;
+ case IDC_VDELIM2:
+ {
+ adiv3pos = (nPos * 100000) / (rc.right - rc.left + 10);
+ if(adiv3pos - 500 < adiv1pos) adiv1pos = adiv3pos - 500;
+ }
+ break;
+ }
+ LayoutWindows(hwndParent, TRUE, 0);
+ }
+}
+
+static BOOL AttachDivider(HWND hwnd, BOOL fVertical, DIVIDERMOVED callback, LPARAM param)
+{
+ if (!hwnd) return FALSE;
+
+ DIVIDER *pd = (DIVIDER*)calloc(1, sizeof(DIVIDER));
+ if (!pd) return FALSE;
+
+ pd->fUnicode = IsWindowUnicode(hwnd);
+ pd->fnOldProc = (WNDPROC) ((pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc) :
+ SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)div_newWndProc));
+ if (!pd->fnOldProc || !SetPropW(hwnd, L"DIVDATA", pd))
+ {
+ free(pd);
+ return FALSE;
+ }
+ pd->fVertical = fVertical;
+ pd->param = param;
+ pd->callback = callback;
+
+ return TRUE;
+}
+
+static LRESULT CALLBACK div_newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DIVIDER *pd;
+ pd = GET_DIVIDER(hwnd);
+ if (!pd) return (IsWindowUnicode(hwnd)) ? DefWindowProcW(hwnd, uMsg, wParam, lParam) : DefWindowProcA(hwnd, uMsg, wParam, lParam);
+
+ switch(uMsg)
+ {
+ case WM_DESTROY:
+ RemovePropW(hwnd, L"DIVDATA");
+ (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+ (pd->fUnicode) ? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc) : SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)pd->fnOldProc);
+ free(pd);
+ return 0;
+ case WM_LBUTTONDOWN:
+ pd->clickoffs = (pd->fVertical) ? LOWORD(lParam) : HIWORD(lParam);
+ SetCapture(hwnd);
+ break;
+ case WM_LBUTTONUP:
+ ReleaseCapture();
+ break;
+ case WM_SETCURSOR:
+ SetCursor(LoadCursor(NULL, (pd->fVertical) ? IDC_SIZEWE : IDC_SIZENS));
+ return TRUE;
+ case WM_MOUSEMOVE:
+ {
+ RECT rw;
+ GetWindowRect(hwnd, &rw);
+ GetCursorPos(((LPPOINT)&rw) + 1);
+ (pd->fVertical) ? rw.right -= pd->clickoffs : rw.bottom -= pd->clickoffs;
+
+ if ((pd->fVertical && rw.left != rw.right) || (!pd->fVertical && rw.top != rw.bottom))
+ {
+ MapWindowPoints(HWND_DESKTOP, GetParent(hwnd), ((LPPOINT)&rw) + 1, 1);
+ if (pd->callback) pd->callback(hwnd, (pd->fVertical) ? rw.right : rw.bottom, pd->param);
+ }
+ }
+ break;
+ }
+
+ return (pd->fUnicode) ? CallWindowProcW(pd->fnOldProc, hwnd, uMsg, wParam, lParam) : CallWindowProcA(pd->fnOldProc, hwnd, uMsg, wParam, lParam);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp b/Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp
new file mode 100644
index 00000000..aa01c0ce
--- /dev/null
+++ b/Src/Plugins/Library/ml_pmp/view_pmp_queue.cpp
@@ -0,0 +1,1181 @@
+/* almost the same as view_pmp_devices, but only one device*/
+#include "main.h"
+#include <windows.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <shlobj.h>
+#include "..\..\General\gen_ml/ml.h"
+#include "..\..\General\gen_ml/itemlist.h"
+#include "../nu/listview.h"
+#include "..\..\General\gen_ml/childwnd.h"
+#include "../winamp/wa_ipc.h"
+#include "../winamp/wa_dlg.h"
+#include "resource1.h"
+#include "SkinnedListView.h"
+#include "DeviceView.h"
+#include "api__ml_pmp.h"
+#include "./graphics.h"
+#include <strsafe.h>
+#include "../nu/AutoWide.h"
+
+static HRGN g_rgnUpdate = NULL;
+static int offsetX, offsetY, customAllowed;
+static DeviceView *device;
+static SkinnedListView * listTransfers=NULL;
+std::map<DeviceView *, bool> device_update_map;
+
+typedef struct TransferView
+{
+ HFONT font;
+ SIZE unitSize;
+ HRGN updateRegion;
+ POINT updateOffset;
+} TransferView;
+
+#define TRANSFERVIEW(_hwnd) ((TransferView*)GetViewData(_hwnd))
+#define TRANSFERVIEW_RET_VOID(_self, _hwnd) { (_self) = TRANSFERVIEW((_hwnd)); if (NULL == (_self)) return; }
+#define TRANSFERVIEW_RET_VAL(_self, _hwnd, _error) { (_self) = TRANSFERVIEW((_hwnd)); if (NULL == (_self)) return (_error); }
+
+#define TRANSFERVIEW_DLU_TO_HORZ_PX(_self, _dlu) MulDiv((_dlu), (_self)->unitSize.cx, 4)
+#define TRANSFERVIEW_DLU_TO_VERT_PX(_self, _dlu) MulDiv((_dlu), (_self)->unitSize.cy, 8)
+
+void handleContextMenuResult(int r, C_ItemList * items=NULL, DeviceView * dev=NULL);
+int showContextMenu(int context,HWND hwndDlg, Device * dev, POINT pt);
+
+class TransferItemShadowShort
+{
+public:
+ CopyInst * c;
+ wchar_t * status, * type, * track, * sourceDevice, * destDevice, * lastChanged, * sourceFile, * clientType;
+ bool changed;
+ TransferItemShadowShort(CopyInst * c)
+ {
+ changed = false;
+ this->c = c;
+ status = _wcsdup(c->statusCaption);
+ type = _wcsdup(c->typeCaption);
+ track = _wcsdup(c->trackCaption);
+ lastChanged = _wcsdup(c->lastChanged);
+ sourceDevice = _wcsdup(c->sourceDevice);
+ destDevice = _wcsdup(c->destDevice);
+ sourceFile = _wcsdup(c->sourceFile);
+ clientType = AutoWideDup(c->dev->GetConnection());
+ }
+ ~TransferItemShadowShort()
+ {
+ if (status) free(status);
+ if (type) free(type);
+ if (track) free(track);
+ if (lastChanged) free(lastChanged);
+ if (sourceDevice) free(sourceDevice);
+ if (destDevice) free(destDevice);
+ if (sourceFile) free(sourceFile);
+ if (clientType) free(clientType);
+ }
+ bool Equals(TransferItemShadowShort * a)
+ {
+ if (!a) return false;
+ return (c == a->c) && !wcscmp(track,a->track) && !wcscmp(status,a->status) && !wcscmp(type,a->type);
+ }
+};
+
+LinkedQueue *getTransferQueue(DeviceView *deviceView)
+{
+ if (deviceView)
+ {
+ return (deviceView->isCloudDevice ? &cloudTransferQueue : &deviceView->transferQueue);
+ }
+ else if (device)
+ {
+ return (device->isCloudDevice ? &cloudTransferQueue : &device->transferQueue);
+ }
+ return NULL;
+}
+
+LinkedQueue *getFinishedTransferQueue(DeviceView *deviceView)
+{
+ if (deviceView)
+ {
+ return (deviceView->isCloudDevice ? &cloudFinishedTransfers : &deviceView->finishedTransfers);
+ }
+ else if (device)
+ {
+ return (device->isCloudDevice ? &cloudFinishedTransfers : &device->finishedTransfers);
+ }
+ return NULL;
+}
+
+int getTransferProgress(DeviceView *deviceView)
+{
+ if(deviceView)
+ {
+ return (deviceView->isCloudDevice ? cloudTransferProgress : deviceView->currentTransferProgress);
+ }
+ else if(device)
+ {
+ return (device->isCloudDevice ? cloudTransferProgress : device->currentTransferProgress);
+ }
+ return 0;
+}
+
+// TODO hook up to a config (would need to work after a reset to be 100% safe...?)
+int sharedQueue = 1;
+static C_ItemList *getTransferListShadow()
+{
+ C_ItemList * list = new C_ItemList;
+ if (!sharedQueue)
+ {
+ LinkedQueue * txQueue = getTransferQueue();
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int j = 0; j < txQueue->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ LinkedQueue * finishedTX = getFinishedTransferQueue();
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ for (int j = 0; j < finishedTX->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+ }
+ else
+ {
+ // TODO should probably review this when we're more intelligent on multi-handling
+ // we do the cloud transfer queue specifically so as not to duplicate by devices
+ LinkedQueue * txQueue = &cloudTransferQueue;
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int j = 0; j < txQueue->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ LinkedQueue * finishedTX = &cloudFinishedTransfers;
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ for (int j = 0; j < finishedTX->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+
+ for (int i = 0; i < devices.GetSize(); i++)
+ {
+ DeviceView * d = (DeviceView *)devices.Get(i);
+ if (!d || d->isCloudDevice) continue;
+
+ LinkedQueue * txQueue = getTransferQueue(d);
+ if (txQueue)
+ {
+ txQueue->lock();
+ for (int j = 0; j < txQueue->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)txQueue->Get(j)));
+ txQueue->unlock();
+ }
+
+ LinkedQueue * finishedTX = getFinishedTransferQueue(d);
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ for (int j = 0; j < finishedTX->GetSize(); j++)
+ list->Add(new TransferItemShadowShort((CopyInst*)finishedTX->Get(j)));
+ finishedTX->unlock();
+ }
+ }
+ }
+ return list;
+}
+
+class TransferContents : public ListContents
+{
+public:
+ TransferContents()
+ {
+ oldSize = 0;
+ listShadow = 0;
+ InitializeCriticalSection(&cs);
+ }
+
+ void Init()
+ {
+ lock();
+
+ if (!listShadow)
+ listShadow = getTransferListShadow();
+
+ unlock();
+ }
+
+ virtual ~TransferContents()
+ {
+ DeleteCriticalSection(&cs);
+ delete listShadow;
+ }
+
+ virtual int GetNumColumns()
+ {
+ // TODO check the number of columns are ok, etc
+ return 7;//(device->isCloudDevice ? 7 : 3);
+ }
+
+ virtual int GetNumRows()
+ {
+ return (listShadow ? listShadow->GetSize() : 0);
+ }
+
+ virtual wchar_t * GetColumnTitle(int num)
+ {
+ // TODO need to clean this up as needed
+ switch (num + 1)
+ {
+ case 0: return WASABI_API_LNGSTRINGW(IDS_TYPE);
+ case 1: return WASABI_API_LNGSTRINGW(IDS_TRACK);
+ case 2: return WASABI_API_LNGSTRINGW(IDS_STATUS);
+ case 3: return WASABI_API_LNGSTRINGW(IDS_LAST_CHANGED);
+ case 4: return WASABI_API_LNGSTRINGW(IDS_SOURCE);
+ case 5: return WASABI_API_LNGSTRINGW(IDS_DESTINATION);
+ case 6: return WASABI_API_LNGSTRINGW(IDS_SOURCE_FILE);
+ case 7: return WASABI_API_LNGSTRINGW(IDS_CLIENT_TYPE);
+ }
+ return L"";
+ }
+
+ virtual int GetColumnWidth(int num)
+ {
+ switch (num)
+ {
+ case 0: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col0_width" : L"cloud_col0_width"), 300);
+ case 1: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col1_width" : L"cloud_col1_width"), 150);
+ case 2: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col2_width" : L"cloud_col2_width"), 100);
+ case 3: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col3_width" : L"cloud_col3_width"), 100);
+ case 4: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col4_width" : L"cloud_col4_width"), 100);
+ case 5: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col5_width" : L"cloud_col5_width"), 100);
+ case 6: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col6_width" : L"cloud_col6_width"), 300);
+ case 7: return global_config->ReadInt((!device->isCloudDevice ? L"transfers_col7_width" : L"cloud_col7_width"), 100);
+ default: return 0;
+ }
+ }
+
+ virtual void ColumnResize(int col, int newWidth)
+ {
+ if (NULL != global_config &&
+ col >= 0 &&
+ col < GetNumColumns())
+ {
+ wchar_t buffer[64] = {0};
+
+ if (FAILED(StringCchPrintf(buffer, ARRAYSIZE(buffer), (!device->isCloudDevice ? L"transfers_col%d_width" : L"cloud_col%d_width"), col)))
+ return;
+
+ global_config->WriteInt(buffer, newWidth);
+ }
+ }
+
+ void lock()
+ {
+ EnterCriticalSection(&cs);
+ }
+
+ void unlock()
+ {
+ LeaveCriticalSection(&cs);
+ }
+
+ virtual void GetCellText(int row, int col, wchar_t * buf, int buflen)
+ {
+ if (NULL == buf)
+ return;
+
+ buf[0] = L'\0';
+
+ lock();
+
+ if (row < listShadow->GetSize())
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(row);
+ if (NULL != t)
+ {
+ // TODO need to clean this up as needed
+ switch (col + 1)
+ {
+ case 0: /*StringCchCopy(buf, buflen, t->type);*/ break;
+ case 1: StringCchCopy(buf, buflen, t->track); break;
+ case 2: StringCchCopy(buf, buflen, t->status); break;
+ case 3: StringCchCopy(buf, buflen, t->lastChanged); break;
+ case 4: StringCchCopy(buf, buflen, t->sourceDevice); break;
+ case 5: StringCchCopy(buf, buflen, t->destDevice); break;
+ case 6: StringCchCopy(buf, buflen, t->sourceFile); break;
+ case 7: StringCchCopy(buf, buflen, t->clientType); break;
+ }
+ }
+ }
+ unlock();
+ }
+
+ void PushPopItem(CopyInst *c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ t->changed=true;
+ listShadow->Del(i);
+ listShadow->Add(t);
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd, LVM_REDRAWITEMS, i, size);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+
+ void ItemUpdate(CopyInst * c)
+ {
+ lock();
+ int size = listShadow->GetSize();
+ for (int i=0; i<size; i++)
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(i);
+ if (c == t->c)
+ {
+ TransferItemShadowShort * n = new TransferItemShadowShort(c);
+ n->changed=true;
+ listShadow->Set(i,n);
+ delete t;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ unlock();
+ return;
+ }
+ }
+ unlock();
+ }
+
+ void FullUpdate()
+ {
+ C_ItemList * newListShadow = getTransferListShadow();
+ if (newListShadow)
+ {
+ int newSize = newListShadow->GetSize();
+ lock();
+ oldSize = listShadow->GetSize();
+ for (int i = 0; i < newSize; i++)
+ {
+ TransferItemShadowShort * newt = (TransferItemShadowShort *)newListShadow->Get(i);
+ TransferItemShadowShort * oldt = i < oldSize ? (TransferItemShadowShort *)listShadow->Get(i) : NULL;
+ newt->changed = !newt->Equals(oldt);
+ }
+
+ C_ItemList * oldListShadow = listShadow;
+ listShadow = newListShadow;
+ for (int i = 0; i < oldListShadow->GetSize(); i++) delete(TransferItemShadowShort *)oldListShadow->Get(i);
+ delete oldListShadow;
+ if (listTransfers)
+ {
+ HWND hwnd = listTransfers->listview.getwnd();
+ if (newSize != oldSize) PostMessage(hwnd,LVM_SETITEMCOUNT,newSize, 0);
+ for (int i=0; i<newSize; i++)
+ {
+ TransferItemShadowShort * t = (TransferItemShadowShort *)listShadow->Get(i);
+ if (t->changed) PostMessage(hwnd,LVM_REDRAWITEMS,i,i);
+ }
+ }
+ unlock();
+ }
+ }
+
+ virtual songid_t GetTrack(int pos) { return 0; }
+
+private:
+ CRITICAL_SECTION cs;
+ C_ItemList * listShadow;
+ int oldSize;
+};
+
+static TransferContents transferListContents;
+
+static void updateStatus(HWND hwnd)
+{
+ HWND statusWindow = GetDlgItem(hwnd, IDC_STATUS);
+ if (NULL == statusWindow)
+ return;
+
+ int txProgress = getTransferProgress(device);
+ LinkedQueue * txQueue = getTransferQueue(device);
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ int size = (txQueue ? txQueue->GetSize() : 0);
+ if (size > 0)
+ {
+ wchar_t buffer[256] = {0}, format[256] = {0};
+ int pcnum, time, pc, total;
+
+ pcnum = (size * 100) - txProgress;
+ total = size * 100;
+ total += 100 * (finishedTX ? finishedTX->GetSize() : 0);
+
+ time = (int)(device->transferRate * (((double)pcnum)/100.0));
+ pc = ((total-pcnum)*100)/total;
+
+ WASABI_API_LNGSTRINGW_BUF((time > 0 ? IDS_TRANFERS_PERCENT_REMAINING : IDS_TRANFERS_PERCENT_REMAINING_NOT_TIME), format, ARRAYSIZE(format));
+ if (SUCCEEDED(StringCchPrintf(buffer, ARRAYSIZE(buffer), format, size, pc, time/60, time%60)))
+ {
+ if (0 == SendMessage(statusWindow, WM_GETTEXT, (WPARAM)ARRAYSIZE(format), (LPARAM)format) ||
+ CSTR_EQUAL != CompareString(LOCALE_USER_DEFAULT, 0, format, -1, buffer, -1))
+ {
+ SendMessage(statusWindow, WM_SETTEXT, 0, (LPARAM)buffer);
+ }
+ }
+ }
+ else
+ {
+ int length = (int)SendMessage(statusWindow, WM_GETTEXTLENGTH, 0, 0L);
+ if (0 != length)
+ SendMessage(statusWindow, WM_SETTEXT, 0, 0L);
+ }
+}
+
+void TransfersListUpdateItem(CopyInst * item, DeviceView *view)
+{
+ if (view == device)
+ transferListContents.ItemUpdate(item);
+}
+
+void TransfersListPushPopItem(CopyInst * item, DeviceView *view)
+{
+ if (view == device)
+ transferListContents.PushPopItem(item);
+}
+
+static bool AddSelectedItems(C_ItemList *items, W_ListView *listview, LinkedQueue *transfer_queue, int &row, DeviceView *&dev)
+{
+ transfer_queue->lock();
+ int l = transfer_queue->GetSize();
+ for (int j=0; j<l; j++)
+ {
+ if (listview->GetSelected(row++))
+ {
+ CopyInst * c = (CopyInst *)transfer_queue->Get(j);
+ if (c->songid)
+ {
+ if (!dev && c->dev)
+ dev = c->dev;
+ if (dev)
+ {
+ if (c->dev != dev)
+ {
+ transfer_queue->unlock();
+ return false;
+ }
+ else
+ items->Add((void*)c->songid);
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+ return true;
+}
+
+static void RemoveSelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, int &row, bool finished_queue)
+{
+ transfer_queue->lock();
+ int j = transfer_queue->GetSize();
+ while (j-- > 0)
+ {
+ if (listview->GetSelected(--row))
+ {
+ if (j == 0 && !finished_queue)
+ {
+ CopyInst * d = (CopyInst *)transfer_queue->Get(j);
+ if (d && (d->status == STATUS_WAITING || d->status == STATUS_CANCELLED) && device->transferContext.IsPaused())
+ {
+ transfer_queue->Del(j);
+ d->Cancelled();
+ delete d;
+ } // otherwise don't bother
+ }
+ else
+ {
+ CopyInst * d = (CopyInst*)transfer_queue->Del(j);
+ if (d)
+ {
+ if ((d->status == STATUS_WAITING || d->status == STATUS_CANCELLED) && !finished_queue)
+ d->Cancelled();
+ delete d;
+ }
+ }
+ }
+ }
+ transfer_queue->unlock();
+}
+
+static void CancelSelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, int &row)
+{
+ transfer_queue->lock();
+ int j = transfer_queue->GetSize();
+ int sel = listview->GetSelectedCount();
+
+ for (int i = 0, q = 0; i <= j; i++)
+ {
+ if (listview->GetSelected(i) || !sel)
+ {
+ CopyInst * d = (CopyInst *)transfer_queue->Get(q);
+ if (d && d->status == STATUS_WAITING)
+ {
+ transfer_queue->Del(q);
+ d->Cancelled();
+ delete d;
+ }
+ else if (d && d->status == STATUS_TRANSFERRING)
+ {
+ d->Cancelled();
+ }
+ else
+ {
+ q++;
+ }
+ }
+ }
+
+ transfer_queue->unlock();
+}
+
+static void RetrySelectedItems(DeviceView *device, W_ListView *listview, LinkedQueue *transfer_queue, LinkedQueue *finished_transfer_queue, int &row)
+{
+ transfer_queue->lock();
+ finished_transfer_queue->lock();
+ int j = finished_transfer_queue->GetSize();
+ int sel = listview->GetSelectedCount();
+
+ LinkedQueue retryTransferQueue;
+ int i = 0;
+ for (int q = 0; i <= j; i++)
+ {
+ if (listview->GetSelected(i) || !sel)
+ {
+ CopyInst * d = (CopyInst *)finished_transfer_queue->Get(q);
+ if (d && (d->status == STATUS_DONE || d->status == STATUS_CANCELLED || d->status == STATUS_ERROR))
+ {
+ // due to STATUS_DONE being applied in most cases, have to look at the
+ // status message and use as the basis on how to proceed with the item
+ if (lstrcmpi(d->statusCaption, WASABI_API_LNGSTRINGW(IDS_UPLOADED)))
+ {
+ retryTransferQueue.Offer(d);
+ finished_transfer_queue->Del(q);
+ }
+ else
+ {
+ q++;
+ }
+ }
+ else
+ {
+ q++;
+ }
+ }
+ }
+
+ i = 0;
+ for (; i <= retryTransferQueue.GetSize(); i++)
+ {
+ CopyInst * d = (CopyInst *)retryTransferQueue.Get(i);
+ if (d)
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, d->statusCaption, sizeof(d->statusCaption)/sizeof(wchar_t));
+
+ SYSTEMTIME system_time;
+ GetLocalTime(&system_time);
+ GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, d->lastChanged, sizeof(d->lastChanged)/sizeof(wchar_t));
+
+ d->dev->AddTrackToTransferQueue(d);
+ }
+ }
+
+ transfer_queue->unlock();
+ finished_transfer_queue->unlock();
+}
+
+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 0x2
+#define GROUP_STATUSBAR 0x1
+#define GROUP_MAIN 0x2
+
+/*
+IDC_BUTTON_PAUSETRANSFERS,
+IDC_BUTTONCANCELSELECTED,
+IDC_BUTTON_CLEARFINISHED,
+IDC_BUTTON_REMOVESELECTED,
+IDC_BUTTON_RETRYSELECTED,
+IDC_STATUS,
+IDC_LIST_TRANSFERS,
+*/
+
+static void TransferView_UpdateLayout(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE)
+{
+ static INT controls[] =
+ {
+ GROUP_STATUSBAR, IDC_BUTTON_PAUSETRANSFERS, IDC_BUTTON_CLEARFINISHED, IDC_BUTTON_REMOVESELECTED, IDC_BUTTON_RETRYSELECTED, IDC_STATUS,
+ GROUP_MAIN, IDC_LIST_TRANSFERS
+ };
+
+ 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_STATUSBAR:
+ {
+ wchar_t buffer[128] = {0};
+ HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_PAUSETRANSFERS);
+ 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_BUTTON_PAUSETRANSFERS:
+ case IDC_BUTTON_CLEARFINISHED:
+ case IDC_BUTTON_REMOVESELECTED:
+ case IDC_BUTTON_RETRYSELECTED:
+ {
+ 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));
+ break;
+ }
+ case IDC_STATUS:
+ 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_LIST_TRANSFERS:
+ SETLAYOUTPOS(pl, rg.left, rg.top + 1, (rg.right - rg.left) - WASABI_API_APP->getScaleX(2), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1));
+ 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
+TransferView_UpdateFont(HWND hwnd, BOOL redraw)
+{
+ TransferView *self;
+ HWND elementWindow;
+ HDWP hdwp;
+
+ const int buttonList[] =
+ {
+ IDC_BUTTON_PAUSETRANSFERS,
+ IDC_BUTTONCANCELSELECTED,
+ IDC_BUTTON_CLEARFINISHED,
+ IDC_BUTTON_REMOVESELECTED,
+ IDC_BUTTON_RETRYSELECTED
+ };
+
+ TRANSFERVIEW_RET_VOID(self, hwnd);
+
+ if (FALSE == Graphics_GetWindowBaseUnits(hwnd, &self->unitSize.cx, &self->unitSize.cy))
+ {
+ self->unitSize.cx = 6;
+ self->unitSize.cy = 13;
+ }
+
+ elementWindow = GetDlgItem(hwnd, IDC_LIST_TRANSFERS);
+ if (NULL != elementWindow)
+ {
+ elementWindow = (HWND)SendMessage(elementWindow, LVM_GETHEADER, 0, 0L);
+ if (NULL != elementWindow)
+ MLSkinnedHeader_SetHeight(elementWindow, -1);
+ }
+
+ hdwp = BeginDeferWindowPos(ARRAYSIZE(buttonList) + 1);
+ if (NULL != hdwp)
+ {
+ LRESULT idealSize;
+ SIZE buttonSize;
+
+ elementWindow = GetDlgItem(hwnd, IDC_STATUS);
+ if (NULL != elementWindow)
+ {
+ hdwp = DeferWindowPos(hdwp, elementWindow, NULL, 0, 0, 100, self->unitSize.cy,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ }
+
+ for(size_t index = 0; index < ARRAYSIZE(buttonList) && NULL != hdwp; index++)
+ {
+ elementWindow = GetDlgItem(hwnd, buttonList[index]);
+ if (NULL == elementWindow)
+ continue;
+
+ if (IDC_BUTTON_PAUSETRANSFERS == buttonList[index])
+ {
+ wchar_t buffer[128] = {0};
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_RESUME, buffer, ARRAYSIZE(buffer));
+ idealSize = MLSkinnedButton_GetIdealSize(elementWindow, buffer);
+ buttonSize.cx = LOWORD(idealSize);
+ buttonSize.cy = HIWORD(idealSize);
+
+ WASABI_API_LNGSTRINGW_BUF(IDS_PAUSE, buffer, ARRAYSIZE(buffer));
+ idealSize = MLSkinnedButton_GetIdealSize(elementWindow, buffer);
+
+ if (buttonSize.cx < LOWORD(idealSize))
+ buttonSize.cx = LOWORD(idealSize);
+
+ if (buttonSize.cy < HIWORD(idealSize))
+ buttonSize.cy = HIWORD(idealSize);
+ }
+ else
+ {
+ idealSize = MLSkinnedButton_GetIdealSize(elementWindow, NULL);
+ buttonSize.cx = LOWORD(idealSize);
+ buttonSize.cy = HIWORD(idealSize);
+ }
+
+ hdwp = DeferWindowPos(hdwp, elementWindow, NULL, 0, 0, buttonSize.cx, buttonSize.cy,
+ SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
+ }
+
+ if (NULL != hdwp)
+ EndDeferWindowPos(hdwp);
+ }
+
+ TransferView_UpdateLayout(hwnd, redraw);
+}
+
+static int
+TransferView_OnInitDialog(HWND hwnd, HWND focusWindow, LPARAM param)
+{
+ HWND controlWindow;
+
+ const int skinList[] =
+ {
+ IDC_BUTTON_PAUSETRANSFERS,
+ IDC_BUTTONCANCELSELECTED,
+ IDC_BUTTON_CLEARFINISHED,
+ IDC_BUTTON_REMOVESELECTED,
+ IDC_BUTTON_RETRYSELECTED,
+ IDC_STATUS,
+ };
+
+ TransferView *self = (TransferView*)calloc(1, sizeof(TransferView));
+ if (NULL != self)
+ {
+ if (FALSE == SetViewData(hwnd, self))
+ {
+ free(self);
+ self = NULL;
+ }
+ }
+
+ if (NULL == self)
+ {
+ DestroyWindow(hwnd);
+ return 0;
+ }
+
+ device = (DeviceView *)param;
+
+ transferListContents.Init();
+ transferListContents.lock();
+
+ transferListContents.FullUpdate();
+
+ listTransfers = new SkinnedListView(&transferListContents,IDC_LIST_TRANSFERS,plugin.hwndLibraryParent, hwnd);
+ listTransfers->DialogProc(hwnd,WM_INITDIALOG, (WPARAM)focusWindow, param);
+
+ transferListContents.unlock();
+ SetDlgItemText(hwnd,IDC_BUTTON_PAUSETRANSFERS,
+ WASABI_API_LNGSTRINGW((device->transferContext.IsPaused()?IDS_RESUME:IDS_PAUSE)));
+
+ MLSkinWindow2(plugin.hwndLibraryParent, hwnd, SKINNEDWND_TYPE_DIALOG,
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+
+ for(size_t index = 0; index < ARRAYSIZE(skinList); index++)
+ {
+ controlWindow = GetDlgItem(hwnd, skinList[index]);
+ if (NULL != controlWindow)
+ {
+ MLSkinWindow2(plugin.hwndLibraryParent, controlWindow,
+ (skinList[index] != IDC_STATUS ? SKINNEDWND_TYPE_BUTTON : SKINNEDWND_TYPE_STATIC),
+ SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
+ }
+ }
+
+ TransferView_UpdateFont(hwnd, FALSE);
+
+ SetTimer(hwnd,1,500,NULL);
+ updateStatus(hwnd);
+
+ return 0;
+}
+
+static void
+TransferView_OnDestroy(HWND hwnd)
+{
+ TransferView *self;
+
+ self = (TransferView *)RemoveViewData(hwnd);
+ if (NULL == self)
+ return;
+
+ KillTimer(hwnd, 1);
+ device = 0;
+
+ SkinnedListView * lt = listTransfers;
+ if (NULL != lt)
+ {
+ transferListContents.lock();
+ listTransfers=NULL;
+ transferListContents.unlock();
+ delete lt;
+ }
+
+ free(self);
+}
+
+static void
+TransferView_OnWindowPosChanged(HWND hwnd, WINDOWPOS *windowPos)
+{
+ if (NULL == windowPos)
+ return;
+
+ if (SWP_NOSIZE != ((SWP_NOSIZE | SWP_FRAMECHANGED) & windowPos->flags))
+ {
+ TransferView_UpdateLayout(hwnd, 0 == (SWP_NOREDRAW & windowPos->flags));
+ }
+}
+
+static void
+TransferView_OnDisplayChanged(HWND hwnd, INT bpp, INT dpi_x, INT dpi_y)
+{
+ UpdateWindow(hwnd);
+ TransferView_UpdateFont(hwnd, TRUE);
+}
+
+static void
+TransferView_OnSetFont(HWND hwnd, HFONT font, BOOL redraw)
+{
+ TransferView *self;
+ TRANSFERVIEW_RET_VOID(self, hwnd);
+
+ self->font = font;
+}
+
+static HFONT
+TransferView_OnGetFont(HWND hwnd)
+{
+ TransferView *self;
+ TRANSFERVIEW_RET_VAL(self, hwnd, NULL);
+
+ return self->font;
+}
+
+static void
+TransferView_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS regionOffset)
+{
+ TransferView *self;
+ TRANSFERVIEW_RET_VOID(self, hwnd);
+
+ self->updateRegion = updateRegion;
+ self->updateOffset.x = regionOffset.x;
+ self->updateOffset.y = regionOffset.y;
+}
+
+INT_PTR CALLBACK pmp_queue_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+ if (NULL != listTransfers)
+ {
+ INT_PTR processed = listTransfers->DialogProc(hwndDlg,uMsg,wParam,lParam);
+ if (0 != processed)
+ return processed;
+ }
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG: return TransferView_OnInitDialog(hwndDlg, (HWND)wParam, lParam);
+ case WM_DESTROY: TransferView_OnDestroy(hwndDlg); break;
+ case WM_WINDOWPOSCHANGED: TransferView_OnWindowPosChanged(hwndDlg, (WINDOWPOS*)lParam); return TRUE;
+ case WM_DISPLAYCHANGE: TransferView_OnDisplayChanged(hwndDlg, (INT)wParam, LOWORD(lParam), HIWORD(lParam)); return TRUE;
+ case WM_GETFONT: DIALOG_RESULT(hwndDlg, TransferView_OnGetFont(hwndDlg));
+ case WM_SETFONT: TransferView_OnSetFont(hwndDlg, (HFONT)wParam, LOWORD(lParam)); return TRUE;
+ case WM_TIMER:
+ if (wParam == 1)
+ {
+ updateStatus(hwndDlg);
+ /*if (device_update_map[device] == true)
+ {
+ device_update_map[device] = false;*/
+ transferListContents.FullUpdate();
+ /*}*/
+ }
+ break;
+ case WM_NOTIFY:
+ {
+ LPNMHDR l=(LPNMHDR)lParam;
+ if (l->idFrom==IDC_LIST_TRANSFERS)
+ {
+ switch (l->code)
+ {
+ case NM_RETURN: // enter!
+ //case NM_RCLICK: // right click!
+ case LVN_KEYDOWN:
+ {
+ int row = 0;
+ C_ItemList items;
+ if (!AddSelectedItems(&items, &listTransfers->listview, getTransferQueue(device), row, device))
+ return 0;
+ if (!AddSelectedItems(&items, &listTransfers->listview, getFinishedTransferQueue(device), row, device))
+ return 0;
+
+ if (items.GetSize())
+ {
+ // TODO need to check the handling of this...
+ /*if (l->code == NM_RCLICK)
+ {
+ LPNMITEMACTIVATE lva=(LPNMITEMACTIVATE)lParam;
+ handleContextMenuResult(showContextMenu(7,l->hwndFrom,device->dev,lva->ptAction),&items,device);
+ }
+ else*/ if (l->code == NM_RETURN)
+ {
+ handleContextMenuResult((!GetAsyncKeyState(VK_SHIFT)?ID_TRACKSLIST_PLAYSELECTION:ID_TRACKSLIST_ENQUEUESELECTION),&items,device);
+ }
+ else if (l->code == LVN_KEYDOWN)
+ {
+ switch (((LPNMLVKEYDOWN)lParam)->wVKey)
+ {
+ case VK_DELETE:
+ {
+ if (!(GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_DELETE,&items,device);
+ }
+ }
+ break;
+ case 0x45: //E
+ if ((GetAsyncKeyState(VK_CONTROL)&0x8000) && !(GetAsyncKeyState(VK_SHIFT)&0x8000))
+ {
+ handleContextMenuResult(ID_TRACKSLIST_EDITSELECTEDITEMS,&items,device);
+ }
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_BUTTON_CLEARFINISHED:
+ {
+ LinkedQueue * finishedTX = getFinishedTransferQueue(device);
+ if (finishedTX)
+ {
+ finishedTX->lock();
+ int j=finishedTX->GetSize();
+ while (j-- > 0) delete(CopyInst*)finishedTX->Del(j);
+ finishedTX->unlock();
+ }
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_REMOVESELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ RemoveSelectedItems(device, &listTransfers->listview, getTransferQueue(device), row, false);
+ RemoveSelectedItems(device, &listTransfers->listview, getFinishedTransferQueue(device), row, true);
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_PAUSETRANSFERS:
+ {
+ if (false != device->transferContext.IsPaused())
+ device->transferContext.Resume();
+ else
+ device->transferContext.Pause();
+
+ SetDlgItemText(hwndDlg,IDC_BUTTON_PAUSETRANSFERS,WASABI_API_LNGSTRINGW((device->transferContext.IsPaused()?IDS_RESUME:IDS_PAUSE)));
+ TransferView_UpdateLayout(hwndDlg, TRUE);
+ }
+ break;
+ case IDC_BUTTONCANCELSELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ CancelSelectedItems(device, &listTransfers->listview, &cloudTransferQueue, row);
+ transferListContents.FullUpdate();
+ }
+ break;
+ case IDC_BUTTON_RETRYSELECTED:
+ {
+ int row = transferListContents.GetNumRows();
+ RetrySelectedItems(device, &listTransfers->listview, &cloudTransferQueue, &cloudFinishedTransfers, row);
+ transferListContents.FullUpdate();
+ }
+ break;
+ }
+ break;
+
+ // gen_ml flickerless drawing
+ case WM_USER + 0x200: DIALOG_RESULT(hwndDlg, 1);
+ case WM_USER + 0x201: TransferView_OnSetUpdateRegion(hwndDlg, (HRGN)lParam, MAKEPOINTS(wParam)); return TRUE;
+ }
+
+ return 0;
+} \ No newline at end of file