winamp/Src/Plugins/Library/ml_pmp/ArtistAlbumLists.cpp

1173 lines
38 KiB
C++
Raw Normal View History

2024-09-24 12:54:57 +00:00
#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; }