#include "main.h" #include "ml_local.h" #include #include "../nu/listview.h" #include "..\..\General\gen_ml/config.h" #include "resource.h" #include #include "..\..\General\gen_ml/ml_ipc.h" #include "../ml_pmp/pmp.h" #include "..\..\General\gen_ml/gaystring.h" #include "../nde/nde.h" #include "../replicant/nu/AutoWide.h" #include "../replicant/nu/AutoChar.h" #include "..\..\General\gen_ml/ml_ipc_0313.h" #include #include #include #include "..\..\General\gen_ml/menufucker.h" #include "api_mldb.h" #include "../replicant/foundation/error.h" static wchar_t oldText[4096]; static int IPC_LIBRARY_SENDTOMENU; static HINSTANCE cloud_hinst; const int ML_MSG_PDXS_STATUS = 0x1001; const int ML_MSG_PDXS_MIX = 0x1002; void RefreshMetadata(HWND parent); static HRGN g_rgnUpdate = NULL; static int offsetX, offsetY, customAllowed; int groupBtn = 1, enqueuedef = 0; static viewButtons view; HWND hwndSearchGlobal = 0; //timers #define TIMER_RATINGAUTOUNHOVER_ID 65520 #define TIMER_RATINGAUTOUNHOVER_DELAY 200 void FixAmps(wchar_t *str, size_t len) { size_t realSize = 0; size_t extra = 0; wchar_t *itr = str; while (itr && *itr) { if (itr && *itr == L'&') extra++; itr++; realSize++; } extra = min(len - (realSize + 1), extra); while (extra) { str[extra+realSize] = str[realSize]; if (str[realSize] == L'&') { extra--; str[extra+realSize] = L'&'; } realSize--; } } void MakeDateString(__time64_t convertTime, wchar_t *dest, size_t destlen) { SYSTEMTIME sysTime; tm *newtime = _localtime64(&convertTime); if (newtime) { sysTime.wYear = (WORD)(newtime->tm_year + 1900); sysTime.wMonth = (WORD)(newtime->tm_mon + 1); sysTime.wDayOfWeek = (WORD)newtime->tm_wday; sysTime.wDay = (WORD)newtime->tm_mday; sysTime.wHour = (WORD)newtime->tm_hour; sysTime.wMinute = (WORD)newtime->tm_min; sysTime.wSecond = (WORD)newtime->tm_sec; sysTime.wMilliseconds = 0; GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, destlen); size_t dateSize = lstrlenW(dest); dest += dateSize; destlen -= dateSize; if (destlen) { *dest++ = L' '; destlen--; } GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, destlen); //wcsftime(expire_time, 63, L"%b %d, %Y", _localtime64(&convertTime)); } else dest[0] = 0; } #define MAINTABLE_ID_CLOUD (unsigned char)-1 const unsigned char extra_idsW[] = { MAINTABLE_ID_ISPODCAST, MAINTABLE_ID_PODCASTCHANNEL, MAINTABLE_ID_PODCASTPUBDATE, MAINTABLE_ID_GRACENOTEFILEID, MAINTABLE_ID_GRACENOTEEXTDATA, MAINTABLE_ID_LOSSLESS, MAINTABLE_ID_CODEC, MAINTABLE_ID_DIRECTOR, MAINTABLE_ID_PRODUCER, MAINTABLE_ID_WIDTH, MAINTABLE_ID_HEIGHT, MAINTABLE_ID_MIMETYPE, 0, MAINTABLE_ID_DATEADDED, MAINTABLE_ID_CLOUD, }; const ExtendedFields extended_fields = { L"ispodcast", L"podcastchannel", L"podcastpubdate", L"GracenoteFileID", L"GracenoteExtData", L"lossless", L"codec", L"director", L"producer", L"width", L"height", L"mime", L"realsize", L"dateadded", L"cloud", }; const wchar_t *extra_strsW[] = { extended_fields.ispodcast, extended_fields.podcastchannel, extended_fields.podcastpubdate, extended_fields.GracenoteFileID, extended_fields.GracenoteExtData, extended_fields.lossless, extended_fields.codec, extended_fields.director, extended_fields.producer, extended_fields.width, extended_fields.height, extended_fields.mimetype, extended_fields.realsize, extended_fields.dateadded, extended_fields.cloud, }; const int NUM_EXTRA_COLSW = sizeof(extra_idsW) / sizeof(*extra_idsW); bool isMixable(itemRecordW &song); static int predixisExist; static BOOL g_displaysearch = TRUE; static BOOL g_displaycontrols = TRUE; nde_scanner_t m_media_scanner = 0; W_ListView resultlist; static int resultSkin; void fileInfoDialogs(HWND hwndParent); void editInfo(HWND hwndParent); void customizeColumnsDialog(HWND hwndParent); static HWND m_hwnd; itemRecordListW itemCache; static int bgThread_Kill = 0; static HANDLE bgThread_Handle; static bool isMixablePresent = true; CloudFiles cloudFiles, cloudUploading; typedef struct { UINT column_id; char *config_name; WORD defWidth; WORD minWidth; WORD maxWidth; } headerColumn; #define UNLIMITED_WIDTH ((WORD)-1) #define COLUMN_DEFMINWIDTH 12 #define COLUMN_DEFMAXWIDTH UNLIMITED_WIDTH #define MAX_COLUMN_ORDER (MEDIAVIEW_COL_NUMS+1) static headerColumn columnList[MAX_COLUMN_ORDER - 1] = { {IDS_ARTIST, "mv_col_artist", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_TITLE, "mv_col_title", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_ALBUM, "mv_col_album", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_LENGTH, "mv_col_length", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_TRACK_NUMBER, "mv_col_track", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_GENRE, "mv_col_genre", 75, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_YEAR, "mv_col_year", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_FILENAME, "mv_col_fn", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_RATING, "mv_col_rating", 65, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_PLAY_COUNT, "mv_col_playcount", 70, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_PLAYED_LAST, "mv_col_lastplay", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_LAST_UPDATED, "mv_col_lastupd", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_FILE_TIME, "mv_col_filetime", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_COMMENT, "mv_col_comment", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_FILE_SIZE, "mb_col_filesize", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_BITRATE, "mb_col_bitrate", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_TYPE, "mb_col_type", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_DISC, "mb_col_disc", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_ALBUM_ARTIST, "mb_col_albumartist", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_FILE_PATH, "mb_col_filepath", 140, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_ALBUM_GAIN, "mb_col_albumgain", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_TRACK_GAIN, "mb_col_trackgain", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_PUBLISHER, "mb_col_publisher", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_COMPOSER, "mb_col_composer", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_EXTENSION, "mb_col_extension", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_IS_PODCAST, "mb_col_ispodcast", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_PODCAST_CHANNEL, "mb_col_podcastchannel", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_PODCAST_PUBLISH_DATE, "mb_col_podcastpubdate", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_BPM, "mb_col_bpm", 55, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_CATEGORY, "mb_col_category", 75, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_DIRECTOR, "mb_col_director", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_PRODUCER, "mb_col_producer", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_DIMENSION, "mb_col_dimension", 100, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_DATE_ADDED, "mb_col_dateadded", 115, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, {IDS_CLOUD, "mv_col_cloud", 27, COLUMN_DEFMINWIDTH, COLUMN_DEFMAXWIDTH}, }; //default column order static signed char defColumnOrderCloud[MAX_COLUMN_ORDER] = { MEDIAVIEW_COL_ARTIST, MEDIAVIEW_COL_ALBUM, MEDIAVIEW_COL_TRACK, MEDIAVIEW_COL_CLOUD, MEDIAVIEW_COL_TITLE, MEDIAVIEW_COL_LENGTH, MEDIAVIEW_COL_GENRE, MEDIAVIEW_COL_RATING, MEDIAVIEW_COL_PLAYCOUNT, MEDIAVIEW_COL_LASTPLAY, MEDIAVIEW_COL_YEAR, -1 }; static signed char defColumnOrder[MAX_COLUMN_ORDER] = { MEDIAVIEW_COL_ARTIST, MEDIAVIEW_COL_ALBUM, MEDIAVIEW_COL_TRACK, MEDIAVIEW_COL_TITLE, MEDIAVIEW_COL_LENGTH, MEDIAVIEW_COL_GENRE, MEDIAVIEW_COL_RATING, MEDIAVIEW_COL_PLAYCOUNT, MEDIAVIEW_COL_LASTPLAY, MEDIAVIEW_COL_YEAR, -1 }; static signed char columnOrder[MAX_COLUMN_ORDER]; int WCSCMP_NULLOK(const wchar_t *pa, const wchar_t *pb) { if (!pa) pa = L""; else SKIP_THE_AND_WHITESPACEW(pa) if (!pb) pb = L""; else SKIP_THE_AND_WHITESPACEW(pb) return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE /*| NORM_IGNORENONSPACE*/, pa, -1, pb, -1) - 2; // return lstrcmpi(pa, pb); } int FLOATWCMP_NULLOK(const wchar_t *pa, const wchar_t *pb) { if (pa) SKIP_THE_AND_WHITESPACEW(pa) if (pb) SKIP_THE_AND_WHITESPACEW(pb) if ((!pa || !*pa) && (!pb || !*pb)) return 0; if (!pa || !*pa) return 1; if (!pb || !*pb) return -1; _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); float a = (float)_wtof_l(pa,C_locale); float b = (float)_wtof_l(pb,C_locale); if (a > b) return 1; else if (a < b) return -1; else return 0; } typedef struct { resultsniff_funcW cb; int user32; } bgThreadParms; static int bg_total_len_s; static __int64 bg_total_len_bytes; static LARGE_INTEGER querytime; static HMENU rate_hmenu = NULL; static HMENU cloud_hmenu = NULL; static HMENU sendto_hmenu = NULL; static librarySendToMenuStruct s; static RATINGCOLUMN ratingColumn; static WCHAR ratingBackText[128]; #define IDC_LIST2HEADER 2001 // WM_INITDIALOG assign this is to the IDC_LIST2 header // internal messages #define IM_SYNCHEADERORDER (WM_USER + 0xFFF0) static DWORD WINAPI bgThreadQueryProc(void *tmp) { bgThreadParms *p = (bgThreadParms*)tmp; bg_total_len_s = 0; bg_total_len_bytes = 0; LARGE_INTEGER starttime, endtime; QueryPerformanceCounter(&starttime); bg_total_len_s = saveQueryToListW(g_view_metaconf, m_media_scanner, &itemCache, &cloudFiles, &cloudUploading, p->cb, p->user32, &bgThread_Kill, &bg_total_len_bytes); QueryPerformanceCounter(&endtime); querytime.QuadPart = endtime.QuadPart - starttime.QuadPart; if (!bgThread_Kill) PostMessage(m_hwnd, WM_APP + 3, 0x69, 0); return 0; } void bgQuery_Stop() // exported for other people to call since it is useful (eventually // we should have bgQuery pass the new query info along but I'll do that soon) { if (bgThread_Handle) { bgThread_Kill = 1; WaitForSingleObject(bgThread_Handle, INFINITE); CloseHandle(bgThread_Handle); bgThread_Handle = 0; } KillTimer(m_hwnd, 123); } static void bgQuery(resultsniff_funcW cb = 0, int user32 = 0) // only internal used { bgQuery_Stop(); SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, WASABI_API_LNGSTRINGW(IDS_SCANNING)); StringCchCopyW(oldText, 4096, WASABI_API_LNGSTRINGW(IDS_SCANNING)); SetTimer(m_hwnd, 123, 200, NULL); DWORD id; static bgThreadParms parms; parms.cb = cb; parms.user32 = user32; bgThread_Kill = 0; bgThread_Handle = CreateThread(NULL, 0, bgThreadQueryProc, (LPVOID) & parms, 0, &id); } // this thing does not produce a fully valid itemRecordList. be afraid. static void copyFilesToItemCacheW(itemRecordListW *obj) { if (bgThread_Handle) return ; int cnt = itemCache.Size; int i, l = cnt; cnt = 0; for (i = 0;i < l;i++) { if (resultlist.GetSelected(i)) cnt++; } obj->Alloc = obj->Size = 0; if (!cnt) return ; allocRecordList(obj, cnt, 0); if (!obj->Items) { obj->Size = obj->Alloc = 0; return ; } for (i = 0; i < itemCache.Size; i ++) { if (resultlist.GetSelected(i)) { obj->Items[obj->Size++] = itemCache.Items[i]; // makes sure that we are providing filesize in kb as // per spec even if we store it as __int64 internally obj->Items[obj->Size-1].filesize /= 1024; } } } void playFiles(int enqueue, int all) { if (bgThread_Handle) return ; int cnt = 0; int l = itemCache.Size; int foo_all = 0; // all but play the only selected int foo_selected = -1; if (!enqueue && !all && g_config->ReadInt(L"viewplaymode", 1)) { int selcnt = 0; for (int i = 0;i < l;i++) { if (resultlist.GetSelected(i)) selcnt++; } if (selcnt == 1) { foo_all = -1; } } for (int i = 0;i < l;i++) { if (foo_all || all || resultlist.GetSelected(i)) { if (foo_all && foo_selected < 0 && resultlist.GetSelected(i)) foo_selected = i; if (!cnt) { if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE); cnt++; } wchar_t title[2048] = {0}; TAG_FMT_EXT(itemCache.Items[i].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&itemCache.Items[i], title, 2048, 0); enqueueFileWithMetaStructW s; s.filename = itemCache.Items[i].filename; s.title = title; s.ext = NULL; s.length = itemCache.Items[i].length; #ifndef _DEBUG ndestring_retain(itemCache.Items[i].filename); SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW_NDE); #else SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW); #endif } } if (cnt) { if (foo_selected >= 0) { SendMessage(plugin.hwndWinampParent, WM_WA_IPC, foo_selected, IPC_SETPLAYLISTPOS); SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40047, 0); // stop button, literally SendMessage(plugin.hwndWinampParent, WM_COMMAND, 40045, 0); // play button, literally } else if (!enqueue) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY); } } // out can never be bigger than in+1 static void parsequicksearch(wchar_t *out, wchar_t *in) // parses a list into a list of terms that we are searching for { int inquotes = 0, neednull = 0; while (in && *in) { wchar_t c = *in++; if (c != ' ' && c != '\t' && c != '\"') { neednull = 1; *out++ = c; } else if (c == '\"') { inquotes = !inquotes; if (!inquotes) { *out++ = 0; neednull = 0; } } else { if (inquotes) *out++ = c; else if (neednull) { *out++ = 0; neednull = 0; } } } *out++ = 0; *out++ = 0; } void makeQueryStringFromText(GayStringW *query, wchar_t *text, int nf) { int ispar = 0; if (query->Get()[0]) { ispar = 1; query->Append(L"&("); } if (!_wcsnicmp(text, L"query:", 6)) query->Append(text + 6); // copy the query as is else if (text[0] == L'?') query->Append(text + 1); else { int isAny = 0; if (*text == L'*' && text[1] == L' ') { isAny = 1; text += 2; } wchar_t tmpbuf[2048 + 32] = {0}; parsequicksearch(tmpbuf, text); int x; wchar_t *fields[] = { L"filename", L"title", L"artist", L"album", L"genre", L"albumartist", L"publisher", L"composer", }; wchar_t *p = tmpbuf; while (p && *p) { size_t lenp = wcslen(p); if (p == tmpbuf) query->Append(L"("); else if (isAny) query->Append(L")|("); else query->Append(L")&("); if (p[0] == L'<' && p[wcslen(p) - 1] == L'>' && wcslen(p) > 2) { wchar_t *op = p; while (op && *op) { if (*op == L'\'') *op = L'\"'; op++; } p[lenp - 1] = 0; // remove > query->Append(p + 1); } else { for (x = 0; x < (int)min(sizeof(fields) / sizeof(fields[0]), nf); x ++) { wchar_t *field = fields[x]; if (x) query->Append(L"|"); query->Append(field); query->Append(L" HAS \""); GayStringW escaped; queryStrEscape(p, escaped); query->Append(escaped.Get()); query->Append(L"\""); } } p += lenp + 1; } query->Append(L")"); } if (ispar) query->Append(L")"); } static void doQuery(HWND hwndDlg, wchar_t *text, int dobg = 1) { bgQuery_Stop(); GayStringW query; if (text[0]) makeQueryStringFromText(&query, text); wchar_t *parent_query = NULL; extern wchar_t* m_query; parent_query = m_query; SendMessage(GetParent(hwndDlg), WM_APP + 2, 0, (LPARAM)&parent_query); GayStringW q; if (parent_query && parent_query[0]) { q.Set(L"("); q.Append(parent_query); q.Append(L")"); } if (query.Get() && query.Get()[0]) { if (q.Get()[0]) { q.Append(L" & ("); q.Append(query.Get()); q.Append(L")"); } else q.Set(query.Get()); } EnterCriticalSection(&g_db_cs); NDE_Scanner_Query(m_media_scanner, q.Get()); LeaveCriticalSection(&g_db_cs); if (dobg) bgQuery(); } static void RecycleSelectedItems() { int totalItems = resultlist.GetSelectedCount(); if (!totalItems) return ; SHFILEOPSTRUCTW fileOp; fileOp.hwnd = m_hwnd; fileOp.wFunc = FO_DELETE; fileOp.pFrom = 0; fileOp.pTo = 0; fileOp.fFlags = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_USES_RECYCLEBIN) ? FOF_ALLOWUNDO : 0; fileOp.fAnyOperationsAborted = 0; fileOp.hNameMappings = 0; fileOp.lpszProgressTitle = 0; EnterCriticalSection(&g_db_cs); nde_scanner_t s = NDE_Table_CreateScanner(g_table); int cchLen = totalItems * (MAX_PATH + 1) + 1; wchar_t *files = new wchar_t[cchLen]; // need room for each file name, null terminated. then have to null terminate the whole list if (files) // if malloc succeeded { wchar_t *curFile = files; for (int i = 0; i < itemCache.Size; i++) { if (resultlist.GetSelected(i)) { if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename)) { StringCchCopyW(curFile, cchLen, itemCache.Items[i].filename); curFile += wcslen(itemCache.Items[i].filename) + 1; } } } if (curFile != files) { curFile[0] = 0; // null terminate fileOp.pFrom = files; fileOp.fAnyOperationsAborted = 0; if (SHFileOperationW(&fileOp)) { wchar_t title[64] = {0}; MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_ERROR_DELETING_FILES), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,title,64), MB_OK); } else { // only remove items if deletion was allowed if (!fileOp.fAnyOperationsAborted) { for (int j = 0; j < itemCache.Size; j++) { if (resultlist.GetSelected(j)) { if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[j].filename)) { // Wasabi callback event for pre remove WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[j].filename, 0); NDE_Scanner_Delete(s); NDE_Scanner_Post(s); g_table_dirty++; // Wasabi callback event for post remove // ToDo: (BigG) Move outside of critical section somehow WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[j].filename, 0); } } } } } } delete [] files; } else // if malloc failed ... maybe because there's too many items. { files = new wchar_t[MAX_PATH + 1 + 1]; // double null termination if (!files) // if this malloc failed, just bail out { NDE_Table_DestroyScanner(g_table, s); LeaveCriticalSection(&g_db_cs); return ; } fileOp.pFrom = files; for (int i = 0;i < itemCache.Size;i++) { if (resultlist.GetSelected(i)) { if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename)) { StringCchCopyW(files, MAX_PATH + 1 + 1, itemCache.Items[i].filename); files[wcslen(itemCache.Items[i].filename) + 1] = 0; // double null terminate fileOp.fAnyOperationsAborted = 0; if (SHFileOperationW(&fileOp)) { wchar_t mes[4096] = {0}; StringCchPrintfW(mes, 4096, WASABI_API_LNGSTRINGW(IDS_ERROR_DELETING_X), files); MessageBoxW(m_hwnd, mes, WASABI_API_LNGSTRINGW(IDS_ERROR), MB_OK); continue; } if (!fileOp.fAnyOperationsAborted) { // Wasabi callback event for pre remove WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[i].filename, 0); NDE_Scanner_Delete(s); NDE_Scanner_Post(s); g_table_dirty++; // Wasabi callback event for post remove // ToDo: (BigG) Move outside of critical section somehow WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[i].filename, 0); } } } delete files; } } NDE_Table_DestroyScanner(g_table, s); if (g_table_dirty) NDE_Table_Sync(g_table); g_table_dirty = 0; LeaveCriticalSection(&g_db_cs); resultlist.Clear(); emptyRecordList(&itemCache); // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc), // but really it is necessary for the view to be accurate. SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view } static void removeSelectedItems(int physical) { if (physical) { RecycleSelectedItems(); return ; } int hasdel = 0; EnterCriticalSection(&g_db_cs); nde_scanner_t s = NDE_Table_CreateScanner(g_table); for (int i = 0;i < itemCache.Size;i++) { if (resultlist.GetSelected(i)) { if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[i].filename)) { wchar_t conf[32] = {0}; if (!hasdel && MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_SURE_YOU_WANT_TO_REMOVE_SELECTED_FROM_LIBRARY), WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRMATION,conf,32), MB_YESNO | MB_ICONQUESTION) != IDYES) { NDE_Table_DestroyScanner(g_table, s); LeaveCriticalSection(&g_db_cs); return ; //FUCKO> need to eat the RETURN msg //MSG msg; //while(PeekMessage(&msg,m_hwnd,WM_COMMAND,WM_COMMAND,1)); } if (!hasdel) // stop any background queries { bgQuery_Stop(); } // Wasabi callback event for pre remove WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_PRE, (size_t)itemCache.Items[i].filename, 0); hasdel = 1; NDE_Scanner_Delete(s); NDE_Scanner_Post(s); g_table_dirty++; // Wasabi callback event for post remove // ToDo: (BigG) Move this outside of the critical section WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_REMOVED_POST, (size_t)itemCache.Items[i].filename, 0); } } } NDE_Table_DestroyScanner(g_table, s); if (g_table_dirty) NDE_Table_Sync(g_table); g_table_dirty = 0; LeaveCriticalSection(&g_db_cs); if (!hasdel) return ; resultlist.Clear(); emptyRecordList(&itemCache); // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc), // but really it is necessary for the view to be accurate. SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view } static void exploreItemFolder(HWND hwndDlg) { if (resultlist.GetSelectionMark() >= 0) { int l=resultlist.GetCount(); for(int i=0;iAddFile(itemCache.Items[i].filename); } } WASABI_API_EXPLORERFINDFILE->ShowFiles(); } } static void removeDeadFiles(HWND hwndDlg) { Scan_RemoveFiles(hwndDlg); // this might be gay, refreshing it completely (i.e. the cursor pos gets put back to normal, etc), // but really it is necessary for the view to be accurate. SendMessage(m_hwnd, WM_APP + 1, 0, 0); //refresh current view } static WNDPROC search_oldWndProc; static DWORD WINAPI search_newWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_KEYDOWN && wParam == VK_DOWN) { PostMessageW(GetParent(hwndDlg), WM_NEXTDLGCTL, (WPARAM)resultlist.getwnd(), (LPARAM)TRUE); ListView_SetItemState(resultlist.getwnd(), 0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); } return CallWindowProcW(search_oldWndProc, hwndDlg, uMsg, wParam, lParam); } typedef struct _LAYOUT { INT id; HWND hwnd; INT x; INT y; INT cx; INT cy; DWORD flags; HRGN rgn; } LAYOUT, PLAYOUT; #define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; } #define SETLAYOUTFLAGS(_layout, _r) \ { \ BOOL fVis; \ fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \ if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \ if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \ if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \ if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \ } #define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags)) #define GROUP_MIN 0x1 #define GROUP_MAX 0x3 #define GROUP_SEARCH 0x1 #define GROUP_STATUSBAR 0x2 #define GROUP_MAIN 0x3 static void LayoutWindows(HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE) { static INT controls[] = { GROUP_SEARCH, IDC_SEARCHCAPTION, IDC_CLEAR, IDC_QUICKSEARCH, GROUP_STATUSBAR, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX, IDC_BUTTON_CREATEPLAYLIST, IDC_BUTTON_INFOTOGGLE, IDC_MIXABLE, IDC_MEDIASTATUS, GROUP_MAIN, IDC_LIST2 }; INT index; RECT rc, rg, ri; LAYOUT layout[sizeof(controls)/sizeof(controls[0])], *pl; BOOL skipgroup; HRGN rgn = NULL; GetClientRect(hwnd, &rc); if (rc.bottom == rc.top || rc.right == rc.left) return; SetRect(&rg, rc.left, rc.top, rc.right, rc.bottom); pl = layout; skipgroup = FALSE; InvalidateRect(hwnd, NULL, TRUE); for (index = 0; index < sizeof(controls) / sizeof(*controls); index++) { if (controls[index] >= GROUP_MIN && controls[index] <= GROUP_MAX) // group id { skipgroup = FALSE; switch (controls[index]) { case GROUP_SEARCH: if (g_displaysearch) { wchar_t buffer[128] = {0}; HWND ctrl = GetDlgItem(hwnd, IDC_CLEAR); GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer)); LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer); SetRect(&rg, rc.left, rc.top + WASABI_API_APP->getScaleY(2), rc.right - WASABI_API_APP->getScaleX(2), rc.top + WASABI_API_APP->getScaleY(HIWORD(idealSize)+1)); rc.top = rg.bottom + WASABI_API_APP->getScaleY(3); } skipgroup = !g_displaysearch; break; case GROUP_STATUSBAR: if (g_displaycontrols) { wchar_t buffer[128] = {0}; HWND ctrl = GetDlgItem(hwnd, IDC_BUTTON_PLAY); GetWindowTextW(ctrl, buffer, ARRAYSIZE(buffer)); LRESULT idealSize = MLSkinnedButton_GetIdealSize(ctrl, buffer); SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)), rc.right, rc.bottom); rc.bottom = rg.top - WASABI_API_APP->getScaleY(3); } skipgroup = !g_displaycontrols; break; case GROUP_MAIN: SetRect(&rg, rc.left + WASABI_API_APP->getScaleX(1), rc.top, rc.right, rc.bottom); break; } continue; } if (skipgroup) continue; pl->id = controls[index]; pl->hwnd = GetDlgItem(hwnd, pl->id); if (!pl->hwnd) continue; GetWindowRect(pl->hwnd, &ri); MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2); pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS; switch (pl->id) { case IDC_SEARCHCAPTION: { wchar_t buffer[128] = {0}; GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer)); LRESULT idealSize = MLSkinnedStatic_GetIdealSize(pl->hwnd, buffer); SETLAYOUTPOS(pl, rg.left + WASABI_API_APP->getScaleX(2), rg.top + WASABI_API_APP->getScaleY(1), WASABI_API_APP->getScaleX(LOWORD(idealSize)), (rg.bottom - rg.top)); rg.left += (pl->cx + WASABI_API_APP->getScaleX(4)); break; } case IDC_CLEAR: { wchar_t buffer[128] = {0}; GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer)); LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer); LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6); pl->flags |= (((rg.right - rg.left) - width) > WASABI_API_APP->getScaleX(40)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ; SETLAYOUTPOS(pl, rg.right - width, rg.top, width, rg.bottom - rg.top); if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4)); break; } case IDC_QUICKSEARCH: pl->flags |= (rg.right > rg.left) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW; SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left - WASABI_API_APP->getScaleX(1), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(1)); break; case IDC_BUTTON_PLAY: case IDC_BUTTON_ENQUEUE: case IDC_BUTTON_MIX: case IDC_BUTTON_CREATEPLAYLIST: if (IDC_BUTTON_MIX != pl->id || customAllowed) { if (groupBtn && pl->id == IDC_BUTTON_PLAY && enqueuedef == 1) { pl->flags |= SWP_HIDEWINDOW; break; } if (groupBtn && pl->id == IDC_BUTTON_ENQUEUE && enqueuedef != 1) { pl->flags |= SWP_HIDEWINDOW; break; } if (groupBtn && (pl->id == IDC_BUTTON_PLAY || pl->id == IDC_BUTTON_ENQUEUE) && customAllowed) { pl->flags |= SWP_HIDEWINDOW; break; } if (pl->id == IDC_BUTTON_CREATEPLAYLIST && !AGAVE_API_PLAYLIST_GENERATOR) { pl->flags |= SWP_HIDEWINDOW; break; } wchar_t buffer[128] = {0}; GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer)); LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer); LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6); SETLAYOUTPOS(pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY(HIWORD(idealSize)), width, WASABI_API_APP->getScaleY(HIWORD(idealSize))); pl->flags |= ((rg.right - rg.left) > width) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW; if (SWP_SHOWWINDOW & pl->flags) rg.left += (pl->cx + WASABI_API_APP->getScaleX(4)); } else pl->flags |= SWP_HIDEWINDOW; break; case IDC_BUTTON_INFOTOGGLE: switch (SendMessage(GetParent(hwnd), WM_USER + 66, 0, 0)) { case 0xFF: case 0xF0: { wchar_t buffer[128] = {0}; GetWindowTextW(pl->hwnd, buffer, ARRAYSIZE(buffer)); LRESULT idealSize = MLSkinnedButton_GetIdealSize(pl->hwnd, buffer); LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6); pl->flags |= (((rg.right - rg.left) - (ri.right - ri.left)) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW ; SETLAYOUTPOS(pl, rg.right - width - WASABI_API_APP->getScaleX(2), rg.top, width, (rg.bottom - rg.top)); if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4)); break; } } break; case IDC_MIXABLE: if (predixisExist & 1) { SETLAYOUTPOS(pl, rg.right - (ri.right - ri.left), rg.top, (ri.right - ri.left), (rg.bottom - rg.top)); pl->flags |= ((rg.right - rg.left) - (ri.right - ri.left) > WASABI_API_APP->getScaleX(60)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW; if (SWP_SHOWWINDOW & pl->flags) rg.right -= (pl->cx + WASABI_API_APP->getScaleX(4)); } break; case IDC_MEDIASTATUS: SETLAYOUTPOS(pl, rg.left, rg.top, rg.right - rg.left, (rg.bottom - rg.top)); pl->flags |= (pl->cx > WASABI_API_APP->getScaleX(16)) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW; break; case IDC_LIST2: SETLAYOUTPOS(pl, rg.left, rg.top + 1, (rg.right - rg.left) - WASABI_API_APP->getScaleX(3), (rg.bottom - rg.top) - WASABI_API_APP->getScaleY(2)); break; } SETLAYOUTFLAGS(pl, ri); if (LAYOUTNEEEDUPDATE(pl)) { if (SWP_NOSIZE == ((SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE) & pl->flags) && ri.left == (pl->x + offsetX) && ri.top == (pl->y + offsetY) && !fUpdateAll && IsWindowVisible(pl->hwnd)) { SetRect(&ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy); ValidateRect(hwnd, &ri); } pl++; } else if (!fUpdateAll && (fRedraw || (!offsetX && !offsetY)) && IsWindowVisible(pl->hwnd)) { ValidateRect(hwnd, &ri); if (GetUpdateRect(pl->hwnd, NULL, FALSE)) { if (!rgn) rgn = CreateRectRgn(0,0,0,0); GetUpdateRgn(pl->hwnd, rgn, FALSE); OffsetRgn(rgn, pl->x, pl->y); InvalidateRgn(hwnd, rgn, FALSE); } } } if (pl != layout) { LAYOUT *pc; HDWP hdwp = BeginDeferWindowPos((INT)(pl - layout)); for (pc = layout; pc < pl && hdwp; pc++) { hdwp = DeferWindowPos(hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags); } if (hdwp) EndDeferWindowPos(hdwp); if (!rgn) rgn = CreateRectRgn(0, 0, 0, 0); if (fRedraw) { GetUpdateRgn(hwnd, rgn, FALSE); for (pc = layout; pc < pl && hdwp; pc++) { if (pc->rgn) { OffsetRgn(pc->rgn, pc->x, pc->y); CombineRgn(rgn, rgn, pc->rgn, RGN_OR); } } RedrawWindow(hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN); } if (g_rgnUpdate) { GetUpdateRgn(hwnd, g_rgnUpdate, FALSE); for (pc = layout; pc < pl && hdwp; pc++) { if (pc->rgn) { OffsetRgn(pc->rgn, pc->x, pc->y); CombineRgn(g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR); } } } for (pc = layout; pc < pl && hdwp; pc++) if (pc->rgn) DeleteObject(pc->rgn); } if (rgn) DeleteObject(rgn); ValidateRgn(hwnd, NULL); } static void updateInfoText(HWND hwndDlg, bool x = false) { int a = SendMessage(GetParent(hwndDlg), WM_USER + 66, x ? -1 : 0, 0); if (a == 0xff) SetDlgItemTextW(hwndDlg, IDC_BUTTON_INFOTOGGLE, WASABI_API_LNGSTRINGW(IDS_HIDE_INFO)); else if (a == 0xf0) SetDlgItemTextW(hwndDlg, IDC_BUTTON_INFOTOGGLE, WASABI_API_LNGSTRINGW(IDS_SHOW_INFO)); else ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_INFOTOGGLE), SW_HIDE); } static void initColumnsHeader(HWND hwndList) { INT index, sortby; LVCOLUMNW lvc = {0, }; if (!hwndList || !IsWindow(hwndList)) return; SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L); while (SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L)); sortby = g_view_metaconf->ReadInt(L"mv_sort_by", 1); lvc.mask = LVCF_TEXT | LVCF_WIDTH; lvc.pszText = L""; lvc.cx = 30; SendMessageW(hwndList, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc); // create dummy column // TODO set to a zero width if not available MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1); // reset the cloud status column so it'll be correctly removed SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1); for (index = 0; columnOrder[index] != -1; index++) { headerColumn *cl = &columnList[columnOrder[index]]; lvc.pszText = WASABI_API_LNGSTRINGW(cl->column_id); lvc.cx = g_view_metaconf->ReadInt(AutoWide(cl->config_name), cl->defWidth); if (lvc.cx < cl->minWidth) lvc.cx = cl->minWidth; // update position of the cloud column icon if (columnOrder[index] == MEDIAVIEW_COL_CLOUD) { if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) { MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1); SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1); lvc.cx = 0; } else { MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), index); SetPropW(hwndList, L"pmp_list_info", (HANDLE)index); lvc.cx = 27; MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &lvc.cx); } } SendMessageW(hwndList, LVM_INSERTCOLUMNW, 0xFFFF, (LPARAM)&lvc); if (sortby == columnOrder[index]) MLSkinnedListView_DisplaySort(hwndList, index, !g_view_metaconf->ReadInt(L"mv_sort_dir", 0)); } SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L); // Delete dummy column for (index = 0; -1 != columnOrder[index] && MEDIAVIEW_COL_RATING != columnOrder[index]/* && MEDIAVIEW_COL_CLOUD != columnOrder[index]*/; index++); if (-1 != index) SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, index, (LPARAM)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, index, 0L)); SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L); } static int m_last_selitem = -1; static int m_bgupdinfoviewerflag; extern void add_to_library(HWND wndparent); static INT_PTR CALLBACK needAddFilesProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: if (AGAVE_API_ITUNES_IMPORTER && AGAVE_API_ITUNES_IMPORTER->iTunesExists()) EnableWindow(GetDlgItem(hwndDlg, IDC_IMPORT_ITUNES), TRUE); else EnableWindow(GetDlgItem(hwndDlg, IDC_IMPORT_ITUNES), FALSE); SetTimer(hwndDlg, 1, 1000, NULL); return 1; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: case IDCANCEL: if (BN_CLICKED == HIWORD(wParam)) { if (IsDlgButtonChecked(hwndDlg, IDC_CHECK1)) g_config->WriteInt(L"noshowadddlg", 1); EndDialog(hwndDlg, 0); } break; case ID_ADD_FILES: if (BN_CLICKED == HIWORD(wParam)) { add_to_library(hwndDlg); PostMessage(hwndDlg, WM_TIMER, 1, 0); } break; case IDC_IMPORT_ITUNES: if (BN_CLICKED == HIWORD(wParam)) { if (AGAVE_API_ITUNES_IMPORTER) AGAVE_API_ITUNES_IMPORTER->ImportFromiTunes(hwndDlg); PostMessage(hwndDlg, WM_TIMER, 1, 0); if (m_curview_hwnd) PostMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view } break; case IDC_BTN_LINK_PROMO: if (BN_CLICKED == HIWORD(wParam)) ShellExecuteA(plugin.hwndWinampParent, "open", "https://help.winamp.com/hc/articles/8105244490772-Player-Overview", NULL, ".", 0); break; } break; case WM_TIMER: if (g_table && NDE_Table_GetRecordsCount(g_table)) { wchar_t buf[512] = {0}; StringCchPrintfW(buf, 512, WASABI_API_LNGSTRINGW(IDS_THERE_ARE_NOW_X_ITEMS_IN_THE_LIBRARY), NDE_Table_GetRecordsCount(g_table)); SetDlgItemTextW(hwndDlg, IDC_TEXT, buf); SetDlgItemTextW(hwndDlg, ID_ADD_FILES, WASABI_API_LNGSTRINGW(IDS_ADD_MORE)); } break; case WM_DRAWITEM: { DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam; if (di->CtlType == ODT_BUTTON) { wchar_t wt[123] = {0}; int y; RECT r; HPEN hPen, hOldPen; DWORD style; GetDlgItemText(hwndDlg, (INT)wParam, wt, ARRAYSIZE(wt)); style = (DWORD)GetWindowLongPtrW(di->hwndItem, GWL_STYLE); // draw text SetTextColor(di->hDC, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220)); memset(&r, 0, sizeof(r)); DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_CALCRECT); if (BS_RIGHT & style) r.left = max(di->rcItem.left+ 2, di->rcItem.right - r.right - 2); else if (BS_LEFT & style) r.left = di->rcItem.left+ 2; else r.left = ((di->rcItem.right - di->rcItem.left - 4) - r.right) / 2; if (r.left < di->rcItem.left + 2) { r.left = di->rcItem.left + 2; r.right = di->rcItem.right - 2; } else r.right += r.left; if (r.right > di->rcItem.right - 2) r.right = di->rcItem.right - 2; if (BS_TOP & style) r.top = di->rcItem.top; else if(BS_VCENTER & style) r.top = ((di->rcItem.bottom - di->rcItem.top - 2) - r.bottom) /2; else r.top = di->rcItem.bottom - 2 - r.bottom; if (r.top < di->rcItem.top) { r.top = di->rcItem.top; r.bottom = di->rcItem.bottom - 2; } else r.bottom += r.top; if (r.bottom > di->rcItem.bottom - 2) r.bottom = di->rcItem.bottom - 2; DrawText(di->hDC, wt, -1, &r, DT_SINGLELINE | DT_WORD_ELLIPSIS); // draw underline y = min(di->rcItem.bottom, r.bottom + 1); hPen = CreatePen(PS_SOLID, 0, (di->itemState & ODS_SELECTED) ? RGB(220, 0, 0) : RGB(0, 0, 220)); hOldPen = (HPEN) SelectObject(di->hDC, hPen); MoveToEx(di->hDC, r.left, y, NULL); LineTo(di->hDC, r.right, y); SelectObject(di->hDC, hOldPen); DeleteObject(hPen); } } } return 0; }; static void SetStatusText(HWND hwndStatus, LPCWSTR *ppsz, INT count) { WCHAR buffer[4096] = {0}; if (0 == count || !ppsz) { SetWindowText(hwndStatus, L""); return; } buffer[0] = 0x00; for (int i = 0; i < count; i++) { StringCchCatW(buffer, 4096, ppsz[i]); StringCchCatW(buffer, 4096, L" "); } SetWindowTextW(hwndStatus, buffer); StringCchCopyW(oldText, 4096, buffer); } static void SetRating(UINT iItem, INT newRating, HWND hwndList) { if (0 == newRating) newRating = -1; if (iItem < (UINT)itemCache.Size) { if (g_table && newRating != itemCache.Items[iItem].rating) { EnterCriticalSection(&g_db_cs); nde_scanner_t s = NDE_Table_CreateScanner(g_table); if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[iItem].filename)) { NDE_Scanner_Edit(s); db_setFieldInt(s, MAINTABLE_ID_RATING, newRating); NDE_Scanner_Post(s); itemCache.Items[iItem].rating = newRating; if (g_config->ReadInt(L"writeratings", 0)) { wchar_t buf[64] = {0}; if (newRating > 0) { wsprintfW(buf, L"%d", newRating); } else buf[0] = 0; updateFileInfo(itemCache.Items[iItem].filename, DB_FIELDNAME_rating, buf); SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO); } } NDE_Table_DestroyScanner(g_table, s); if (g_table_dirty) { g_table_dirty = 0; NDE_Table_Sync(g_table); } LeaveCriticalSection(&g_db_cs); } if (newRating == itemCache.Items[iItem].rating && hwndList) { ratingColumn.hwndList = hwndList; ratingColumn.iItem = iItem; ratingColumn.iSubItem = 600; MLRatingColumn_Animate(plugin.hwndLibraryParent, &ratingColumn); ListView_RedrawItems(resultlist.getwnd(), iItem, iItem); // TODO: benski> update the top panes w/o refreshing, if possible // CUT: PostMessage(GetParent(GetParent(hwndList)), WM_APP + 4, (WPARAM)newRating, (LPARAM)1); } } } /////////// Header Messages / Notifications static BOOL Header_OnItemChanging(HWND hwndDlg, NMHEADERW *phdr, LRESULT *pResult, UINT uMsg) { if (phdr->pitem && (HDI_WIDTH & phdr->pitem->mask)) { INT test; test = columnList[columnOrder[phdr->iItem]].minWidth; if (phdr->pitem->cxy < test) phdr->pitem->cxy = test; test = columnList[columnOrder[phdr->iItem]].maxWidth; if (test != UNLIMITED_WIDTH && phdr->pitem->cxy > test) phdr->pitem->cxy = test; if (MEDIAVIEW_COL_RATING == columnOrder[phdr->iItem]) { RATINGWIDTH rw; rw.fStyle = RCS_DEFAULT; rw.width = phdr->pitem->cxy; if (MLRatingColumn_GetWidth(plugin.hwndLibraryParent, &rw)) phdr->pitem->cxy = rw.width; if (0 == phdr->iItem) { RATINGBACKTEXT rbt; rbt.pszText = ratingBackText; rbt.cchTextMax = sizeof(ratingBackText)/sizeof(WCHAR); rbt.nColumnWidth = phdr->pitem->cxy; rbt.fStyle = RCS_DEFAULT; MLRatingColumn_FillBackString(plugin.hwndLibraryParent, &rbt); } else ratingBackText[0] = 0x00; } else if (MEDIAVIEW_COL_CLOUD == columnOrder[phdr->iItem]) { if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) phdr->pitem->cxy = 0; else { INT width = phdr->pitem->cxy; if (MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &width)) { phdr->pitem->cxy = width; } } } } return FALSE; } static BOOL Header_OnEndDrag(HWND hwndDlg, NMHEADERW *phdr, LRESULT *pResult) { PostMessageW(hwndDlg, IM_SYNCHEADERORDER, 0, (LPARAM)phdr->hdr.hwndFrom); return FALSE; } static BOOL Header_OnRightClick(HWND hwndDlg, NMHDR *pnmh, LRESULT *pResult) { HMENU menu = GetSubMenu(g_context_menus, 4); POINT p; GetCursorPos(&p); int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, p.x, p.y, hwndDlg, NULL); switch (r) { case ID_HEADERWND_CUSTOMIZECOLUMNS: customizeColumnsDialog(hwndDlg); break; } return FALSE; } /////////// ListView Messages / Notifications static BOOL ListView_OnItemChanged(HWND hwndDlg, NMLISTVIEW *pnmv) { if (pnmv->uNewState & LVIS_SELECTED) { //if (GetFocus()==resultlist.getwnd()) { m_last_selitem = pnmv->iItem; KillTimer(hwndDlg, 6600); SetTimer(hwndDlg, 6600, 250, NULL); } } else { if (isMixablePresent) { SetDlgItemText(hwndDlg, IDC_MIXABLE, L""); isMixablePresent = false; } } return FALSE; } static BOOL ListView_OnDoubleClick(HWND hwndDlg, NMITEMACTIVATE *pnmitem) { playFiles((!!g_config->ReadInt(L"enqueuedef", 0)) ^(!!(GetAsyncKeyState(VK_SHIFT)&0x8000)), 0); return FALSE; } void EatKeyboard() { Sleep(100); MSG msg; while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); //eat return } static void Dialog_OnContextMenu(HWND hwndDlg, HWND hwndFrom, int x, int y) { if (hwndFrom != resultlist.getwnd()) return; POINT pt = {x,y}; if (x == -1 || y == -1) // x and y are -1 if the user invoked a shift-f10 popup menu { RECT itemRect = {0}; int selected = resultlist.GetNextSelected(); if (selected != -1) // if something is selected we'll drop the menu from there { resultlist.GetItemRect(selected, &itemRect); ClientToScreen(resultlist.getwnd(), (POINT *)&itemRect); } else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location { GetWindowRect(resultlist.getwnd(), &itemRect); HWND hHeader = (HWND)SNDMSG(resultlist.getwnd(), LVM_GETHEADER, 0, 0L); RECT headerRect; if ((WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) && GetWindowRect(hHeader, &headerRect)) { itemRect.top += (headerRect.bottom - headerRect.top); } } x = itemRect.left; y = itemRect.top; } HWND hHeader = (HWND)SNDMSG(resultlist.getwnd(), LVM_GETHEADER, 0, 0L); RECT headerRect; if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect)) { SetRectEmpty(&headerRect); } if (FALSE != PtInRect(&headerRect, pt)) { return; } HMENU globmenu = WASABI_API_LOADMENU(IDR_CONTEXTMENUS); HMENU menu = GetSubMenu(globmenu, 0); int rate_idx = 9; sendto_hmenu = GetSubMenu(menu, 2); rate_hmenu = GetSubMenu(menu, rate_idx); ConvertRatingMenuStar(rate_hmenu, ID_RATE_5); ConvertRatingMenuStar(rate_hmenu, ID_RATE_4); ConvertRatingMenuStar(rate_hmenu, ID_RATE_3); ConvertRatingMenuStar(rate_hmenu, ID_RATE_2); ConvertRatingMenuStar(rate_hmenu, ID_RATE_1); s.mode = 0; s.hwnd = 0; s.build_hMenu = 0; IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE); if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1) { s.mode = 1; s.hwnd = hwndDlg; s.data_type = ML_TYPE_ITEMRECORDLIST; s.ctx[1] = 1; s.build_hMenu = sendto_hmenu; } wchar_t *artist = NULL; wchar_t *album = NULL; int n = resultlist.GetSelectionMark(); if (n != -1) { artist = itemCache.Items[n].artist; album = itemCache.Items[n].album; wchar_t str[2048] = {0}, str2[128] = {0}; // (BigG): Check the ini settings for viewing vs playing and create the menu accordingly // Keeping this here in case we want to use the ini setting: //StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW( (g_viewnotplay != 0) ? IDS_VIEW_ALL_FILES_BY : IDS_PLAY_ALL_FILES_BY), artist ? artist : L""); int len = lstrlenW(artist); if (len > 39) { StringCchPrintfW(str2, 40, L"%.36s...", artist);//WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), artist ? artist : L""); StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), str2); } else { StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_BY), artist ? artist : L""); } FixAmps(str, 2048); MENUITEMINFOW mii = { sizeof(MENUITEMINFOW), MIIM_TYPE | MIIM_ID, MFT_STRING, MFS_ENABLED, 0x1234, NULL, NULL, NULL, 0, str, 0, }; if (!(!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE))) { MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0}; m.wID = CLOUD_SOURCE_MENUS - 1; InsertMenuItemW(menu, 0, FALSE, &m); wchar_t a[100] = {0}; m.fType = MFT_STRING; m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100); m.wID = CLOUD_SOURCE_MENUS; m.hSubMenu = cloud_hmenu = CreatePopupMenu(); InsertMenuItemW(menu, 0, FALSE, &m); } if (artist && artist[0]) { InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii); } // (BigG): Check the ini settings for viewing vs playing and create the menu accordingly // Keeping this here in case we want to use the ini setting: len = lstrlenW(album); if (len > 39) { StringCchPrintfW(str2, 40, L"%.36s...", album); StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_FROM), str2); } else { StringCchPrintfW(str, 2048, WASABI_API_LNGSTRINGW(IDS_VIEW_ALL_FILES_FROM), album ? album : L""); } FixAmps(str, 2048); mii.cch = wcslen(str); mii.wID = 0x1235; if (album && album[0]) { InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii); } { mii.wID = 0xdeadbeef; mii.fType = MFT_SEPARATOR; InsertMenuItemW(menu, ID_EDITITEMINFOS, FALSE, &mii); } } else { EnableMenuItem(menu, ID_MEDIAWND_PLAYSELECTEDFILES, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, ID_MEDIAWND_ENQUEUESELECTEDFILES, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, IDC_REFRESH_METADATA, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, ID_MEDIAWND_REMOVEFROMLIBRARY, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, ID_EDITITEMINFOS, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, ID_MEDIAWND_EXPLOREFOLDER, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, ID_PE_ID3, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(menu, 2, MF_BYPOSITION | MF_GRAYED); //grays the "Add to playlist..." menu EnableMenuItem(menu, rate_idx, MF_BYPOSITION | MF_GRAYED); //grays the "Rate..." menu EnableMenuItem(menu, 13, MF_BYPOSITION | MF_GRAYED); //grays the "Remove..." menu } menufucker_t mf = {sizeof(mf),MENU_MEDIAVIEW,menu,0x3000,0x4000,0}; mf.extinf.mediaview.list = resultlist.getwnd(); mf.extinf.mediaview.items = &itemCache; pluginMessage message_build = {ML_IPC_MENUFUCKER_BUILD,(intptr_t)&mf,0}; SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_build, ML_IPC_SEND_PLUGIN_MESSAGE); Menu_SetRatingValue(rate_hmenu, 0); UpdateMenuItems(hwndDlg, menu, IDR_VIEW_ACCELERATORS); int r = DoTrackPopup(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL); pluginMessage message_result = {ML_IPC_MENUFUCKER_RESULT,(intptr_t)&mf,r,0}; SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&message_result, ML_IPC_SEND_PLUGIN_MESSAGE); switch (r) { case ID_MEDIAWND_PLAYSELECTEDFILES: case ID_MEDIAWND_ENQUEUESELECTEDFILES: playFiles((r == ID_MEDIAWND_ENQUEUESELECTEDFILES), 0); break; case ID_MEDIAWND_SELECTALL: { LVITEM item = {0}; item.state = LVIS_SELECTED; item.stateMask = LVIS_SELECTED; SendMessageW(hwndFrom, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item); } break; case ID_MEDIAWND_REMOVEFROMLIBRARY: removeSelectedItems(0); break; case ID_EDITITEMINFOS: if (resultlist.GetSelectedCount() > 0) editInfo(hwndDlg); break; case ID_PE_ID3: fileInfoDialogs(hwndDlg); PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)hwndFrom, (LPARAM)TRUE); break; case IDC_REFRESH_METADATA: RefreshMetadata(hwndDlg); break; case 0x1234: // all files from selected artist { wchar_t tmp[2048] = {0}; GayStringW escaped; queryStrEscape(artist, escaped); // Keeping this here in case we want to use the ini setting later: //if ( g_viewnotplay ) // (BigG): Check to see if we should play or view the current tracks //{ StringCchPrintfW(tmp, 2048, L"?artist = \"%s\"", escaped.Get()); SetWindowTextW(hwndSearchGlobal, tmp); //} //else // Otherwise do the old behavior and just play it back //{ // StringCchPrintfW(tmp, 2048, L"artist = \"%s\"", escaped.Get()); // main_playQuery(g_view_metaconf, tmp, 0); //} SetWindowTextW(hwndSearchGlobal, tmp); } break; case 0x1235: // all files from selected album { wchar_t tmp[2048] = {0}; GayStringW escaped; queryStrEscape(album, escaped); // Keeping this here in case we want to use the ini setting later: //if ( g_viewnotplay ) // (BigG): Check to see if we should play or view the current tracks //{ StringCchPrintfW(tmp, 2048, L"?album = \"%s\"", escaped.Get()); SetWindowTextW(hwndSearchGlobal, tmp); //} //else // Otherwise do the old behavior and just play it back //{ // StringCchPrintfW(tmp, 2048, L"album = \"%s\"", escaped.Get()); // main_playQuery(g_view_metaconf, tmp, 0); //} } break; case ID_RATE_1: case ID_RATE_2: case ID_RATE_3: case ID_RATE_4: case ID_RATE_5: case ID_RATE_0: { int rate = r - ID_RATE_1 + 1; if (r == ID_RATE_0) rate = 0; int x; int has = 0; EnterCriticalSection(&g_db_cs); nde_scanner_t s = NDE_Table_CreateScanner(g_table); for (x = 0; x < itemCache.Size; x ++) { if (resultlist.GetSelected(x)) { if (NDE_Scanner_LocateNDEFilename(s, MAINTABLE_ID_FILENAME, FIRST_RECORD, itemCache.Items[x].filename)) { has++; NDE_Scanner_Edit(s); db_setFieldInt(s, MAINTABLE_ID_RATING, rate); NDE_Scanner_Post(s); itemCache.Items[x].rating = rate; if (g_config->ReadInt(L"writeratings", 0)) { wchar_t buf[64] = {0}; if (rate > 0) { wsprintfW(buf, L"%d", rate); } else buf[0] = 0; updateFileInfo(itemCache.Items[x].filename, DB_FIELDNAME_rating, buf); SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO); } } } } NDE_Table_DestroyScanner(g_table, s); if (g_table_dirty) { g_table_dirty = 0; NDE_Table_Sync(g_table); } LeaveCriticalSection(&g_db_cs); if (has) { ListView_RedrawItems(resultlist.getwnd(), 0, itemCache.Size - 1); PostMessage(GetParent(hwndDlg), WM_APP + 4, (WPARAM)rate, (LPARAM)0); } } break; case ID_MEDIAWND_EXPLOREFOLDER: exploreItemFolder(hwndDlg); break; case ID_MEDIAWND_REMOVE_REMOVEALLDEADFILES: removeDeadFiles(hwndDlg); break; case ID_MEDIAWND_REMOVE_PHYSICALLYREMOVESELECTEDITEMS: RecycleSelectedItems(); break; default: { if (cloud_hmenu && (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER)) // deals with cloud specific menus { // 0 = no change // 1 = add cloud // 2 = add local // 4 = removed int mode = 0; WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode); int n = resultlist.GetSelectionMark(); if (n != -1) { switch (mode) { case 1: setCloudValue(&itemCache.Items[n], L"5"); break; case 2: setCloudValue(&itemCache.Items[n], L"0"); break; case 4: setCloudValue(&itemCache.Items[n], L"4"); break; } InvalidateRect(resultlist.getwnd(), NULL, TRUE); } break; } if (s.mode == 2) { s.menu_id = r; if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1) { // build my data. s.mode = 3; s.data_type = ML_TYPE_ITEMRECORDLISTW; itemRecordListW myObj = {0, }; copyFilesToItemCacheW(&myObj); // does not dupe strings s.data = (void*) & myObj; LRESULT result = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM) & s, IPC_LIBRARY_SENDTOMENU); if (result != 1) { s.mode = 3; s.data_type = ML_TYPE_ITEMRECORDLIST; itemRecordList objA = {0, }; convertRecordList(&objA, &myObj); s.data = (void*) & objA; SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU); freeRecordList(&objA); } _aligned_free(myObj.Items); } } break; } } if (s.mode) { s.mode = 4; SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU); // cleanup } sendto_hmenu = 0; DestroyMenu(cloud_hmenu); cloud_hmenu = 0; DestroyMenu(globmenu); UpdateWindow(hwndFrom); EatKeyboard(); } static BOOL ListView_OnFindItem(HWND hwndDlg, NMLVFINDITEMW *pfi, LRESULT *pResult, UINT uMsg) { if (bgThread_Handle) return FALSE; int i = pfi->iStart; if (i >= itemCache.Size) i = 0; int cnt = itemCache.Size - i; if (pfi->lvfi.flags & LVFI_WRAP) cnt += i; int by = g_view_metaconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST); while (cnt-- > 0) { itemRecordW *thisitem = itemCache.Items + i; wchar_t tmp[128] = {0}; wchar_t *name = 0; switch (by) { case MEDIAVIEW_COL_FILENAME: name = thisitem->filename + wcslen(thisitem->filename); while (name >= thisitem->filename && *name != L'/' && *name != L'\\') name--; break; case MEDIAVIEW_COL_FULLPATH: name = thisitem->filename; break; case MEDIAVIEW_COL_EXTENSION: name = PathFindExtensionW(thisitem->filename); if (name && *name) name++; break; case MEDIAVIEW_COL_TITLE: name = thisitem->title; break; case MEDIAVIEW_COL_COMMENT: name = thisitem->comment; break; case MEDIAVIEW_COL_ARTIST: name = thisitem->artist; break; case MEDIAVIEW_COL_ALBUM: name = thisitem->album; break; case MEDIAVIEW_COL_GENRE: name = thisitem->genre; break; case MEDIAVIEW_COL_YEAR: tmp[0] = 0; if (thisitem->year >= 0) StringCchPrintfW(tmp, 128, L"%04d", thisitem->year); name = tmp; break; case MEDIAVIEW_COL_TRACK: tmp[0] = 0; if (thisitem->track > 0) { if (thisitem->tracks > 0) StringCchPrintfW(tmp, 128, L"%d/%d", thisitem->track, thisitem->tracks); else StringCchPrintfW(tmp, 128, L"%d", thisitem->track); } name = tmp; break; case MEDIAVIEW_COL_LENGTH: tmp[0] = 0; if (thisitem->length >= 0) StringCchPrintfW(tmp, 128, L"%d:%02d", thisitem->length / 60, thisitem->length % 60); name = tmp; break; case MEDIAVIEW_COL_RATING: tmp[0] = 0; if (thisitem->rating > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->rating); name = tmp; break; case MEDIAVIEW_COL_CLOUD: { tmp[0] = 0; wchar_t *x = getRecordExtendedItem_fast(thisitem, extended_fields.cloud); if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x); name = tmp; break; } case MEDIAVIEW_COL_PLAYCOUNT: tmp[0] = 0; if (thisitem->playcount > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->playcount); name = tmp; break; case MEDIAVIEW_COL_BITRATE: if (thisitem->bitrate > 0) StringCchPrintfW(name = tmp, 128, L"%d%s", thisitem->bitrate, WASABI_API_LNGSTRINGW(IDS_KBPS)); break; case MEDIAVIEW_COL_BPM: if (thisitem->bpm > 0) StringCchPrintfW(name = tmp, 128, L"%d", thisitem->bpm); break; case MEDIAVIEW_COL_TYPE: name = WASABI_API_LNGSTRINGW(thisitem->type ? IDS_VIDEO : IDS_AUDIO); break; case MEDIAVIEW_COL_DISC: tmp[0] = 0; if (thisitem->disc > 0) { if (thisitem->discs > 0) StringCchPrintfW(tmp, 128, L"%d/%d", thisitem->disc, thisitem->discs); else StringCchPrintfW(tmp, 128, L"%d", thisitem->disc); } name = tmp; break; case MEDIAVIEW_COL_ALBUMARTIST: name = thisitem->albumartist; break; case MEDIAVIEW_COL_PUBLISHER: name = thisitem->publisher; break; case MEDIAVIEW_COL_COMPOSER: name = thisitem->composer; break; case MEDIAVIEW_COL_ALBUMGAIN: name = thisitem->replaygain_album_gain; break; case MEDIAVIEW_COL_TRACKGAIN: name = thisitem->replaygain_track_gain; break; case MEDIAVIEW_COL_FILESIZE: tmp[0] = 0; if (thisitem->filesize > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->filesize); name = tmp; break; case MEDIAVIEW_COL_FILETIME: tmp[0] = 0; if (thisitem->filetime > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->filetime); name = tmp; break; case MEDIAVIEW_COL_LASTUPD: tmp[0] = 0; if (thisitem->lastupd > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->lastupd); name = tmp; break; case MEDIAVIEW_COL_DATEADDED: { tmp[0] = 0; wchar_t *x = getRecordExtendedItem_fast(thisitem,extended_fields.dateadded); if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x); name = tmp; break; } case MEDIAVIEW_COL_LASTPLAY: tmp[0] = 0; if (thisitem->lastplay > 0) StringCchPrintfW(tmp, 128, L"%d", thisitem->lastplay); name = tmp; break; case MEDIAVIEW_COL_ISPODCAST: { wchar_t * t = getRecordExtendedItem_fast(thisitem, extended_fields.ispodcast); name = WASABI_API_LNGSTRINGW(t && wcscmp(t,L"1") ? IDS_PODCAST : IDS_NON_PODCAST); } break; case MEDIAVIEW_COL_PODCASTCHANNEL: name = getRecordExtendedItem_fast(thisitem,extended_fields.podcastchannel); break; case MEDIAVIEW_COL_PODCASTPUBDATE: { tmp[0] = 0; wchar_t *x = getRecordExtendedItem_fast(thisitem,extended_fields.podcastpubdate); if (x && *x) StringCchPrintfW(tmp, 128, L"%d", x); name = tmp; } break; case MEDIAVIEW_COL_CATEGORY: name = thisitem->category; break; case MEDIAVIEW_COL_DIRECTOR: name = getRecordExtendedItem_fast(thisitem,extended_fields.director); break; case MEDIAVIEW_COL_PRODUCER: name = getRecordExtendedItem_fast(thisitem,extended_fields.producer); break; case MEDIAVIEW_COL_DIMENSION: { tmp[0] = 0; wchar_t *w = getRecordExtendedItem_fast(thisitem,extended_fields.width); wchar_t *h = getRecordExtendedItem_fast(thisitem,extended_fields.height); if (w && *w && h && *h) StringCchPrintfW(tmp, 128, L"%dx%d", _wtoi(w), _wtoi(h)); name = tmp; break; } } if (!name) name = L""; else SKIP_THE_AND_WHITESPACEW(name) if (pfi->lvfi.flags & (4 | LVFI_PARTIAL)) { if (!StrCmpNIW(name, pfi->lvfi.psz, wcslen(pfi->lvfi.psz))) { *pResult = i; return TRUE; } } else if (pfi->lvfi.flags & LVFI_STRING) { if (!lstrcmpiW(name, pfi->lvfi.psz)) { *pResult = i; return TRUE; } } else { *pResult = i; return TRUE; } if (++i == itemCache.Size) i = 0; } *pResult = i; return TRUE; } static BOOL ListView_OnGetDispInfo(HWND hwndDlg, NMLVDISPINFOW* pdi, UINT uMsg) { LVITEMW *pItem; itemRecordW *pRec; pItem = &pdi->item; if (bgThread_Handle) { if (0 == pItem->iItem && 0 == pItem->iSubItem && (LVIF_TEXT & pItem->mask)) { static char bufpos = 0; static int buflen = 17; static wchar_t buffer[64];//L"Scanning _\0/-\\|"; if (!buffer[0]) { WASABI_API_LNGSTRINGW_BUF(IDS_SCANNING_PLAIN,buffer,54); StringCchCatW(buffer,64,L" _"); StringCchCatW(buffer+(buflen=lstrlenW(buffer)+1),64,L"/-\\|"); buflen+=4; } int pos = buflen - 5;; buffer[pos - 1] = buffer[pos + (bufpos++&3) + 1]; pItem->pszText = buffer; } return FALSE; } if (pItem->iItem < 0 || pItem->iItem >= itemCache.Size) return FALSE; pRec = itemCache.Items + pItem->iItem; if (LVIF_TEXT & pItem->mask) { switch (columnOrder[pItem->iSubItem]) { case MEDIAVIEW_COL_FILENAME: // show filename (Tho we'll show without the path cause paths are gay) pItem->pszText = PathFindFileNameW(pRec->filename); break; case MEDIAVIEW_COL_FULLPATH: // show filename (Tho we'll show without the path cause paths are gay) pItem->pszText = pRec->filename; break; case MEDIAVIEW_COL_EXTENSION: pItem->pszText = PathFindExtensionW(pRec->filename); if (pItem->pszText && *pItem->pszText) pItem->pszText++; break; case MEDIAVIEW_COL_TITLE: pItem->pszText = pRec->title; break; case MEDIAVIEW_COL_COMMENT: pItem->pszText = pRec->comment; break; case MEDIAVIEW_COL_ALBUM: pItem->pszText = pRec->album; break; case MEDIAVIEW_COL_ARTIST: pItem->pszText = pRec->artist; break; case MEDIAVIEW_COL_TRACK: if (pRec->track > 0) { if (pRec->tracks > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d/%d", pRec->track, pRec->tracks); else StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->track); } break; case MEDIAVIEW_COL_GENRE: pItem->pszText = pRec->genre; break; case MEDIAVIEW_COL_YEAR: if (pRec->year >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%04d", pRec->year); break; case MEDIAVIEW_COL_LENGTH: if (pRec->length >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d:%02d", pRec->length / 60, pRec->length % 60); break; case MEDIAVIEW_COL_RATING: if (0 == pItem->iSubItem && ratingBackText[0]) pItem->pszText = ratingBackText; else if (pRec->rating > 0 && pRec->rating <= 5) _itow(pRec->rating, pItem->pszText, 10); break; case MEDIAVIEW_COL_CLOUD: { wchar_t *t = getRecordExtendedItem_fast(pRec, extended_fields.cloud); if (t && *t) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", _wtoi(t)); break; } case MEDIAVIEW_COL_PLAYCOUNT: if (pRec->playcount >= 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->playcount); else pItem->pszText = L"0"; break; case MEDIAVIEW_COL_DISC: if (pRec->disc > 0) { if (pRec->discs > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d/%d", pRec->disc, pRec->discs); else StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->disc); } break; case MEDIAVIEW_COL_ALBUMARTIST: pItem->pszText = pRec->albumartist; break; case MEDIAVIEW_COL_PUBLISHER: pItem->pszText = pRec->publisher; break; case MEDIAVIEW_COL_COMPOSER: pItem->pszText = pRec->composer; break; case MEDIAVIEW_COL_ALBUMGAIN: pItem->pszText = pRec->replaygain_album_gain; break; case MEDIAVIEW_COL_TRACKGAIN: pItem->pszText = pRec->replaygain_track_gain; break; case MEDIAVIEW_COL_TYPE: pItem->pszText = WASABI_API_LNGSTRINGW((pRec->type) ? IDS_VIDEO : IDS_AUDIO); break; case MEDIAVIEW_COL_BITRATE: if (pRec->bitrate > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d%s", pRec->bitrate, WASABI_API_LNGSTRINGW(IDS_KBPS)); break; case MEDIAVIEW_COL_BPM: if (pRec->bpm > 0) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%d", pRec->bpm); break; case MEDIAVIEW_COL_FILESIZE: if (pRec->filesize != -1) WASABI_API_LNG->FormattedSizeString(pItem->pszText, pItem->cchTextMax, pRec->filesize); break; case MEDIAVIEW_COL_FILETIME: MakeDateString(pRec->filetime, pItem->pszText, pItem->cchTextMax); break; case MEDIAVIEW_COL_LASTUPD: MakeDateString(pRec->lastupd, pItem->pszText, pItem->cchTextMax); break; case MEDIAVIEW_COL_DATEADDED: { wchar_t * t = getRecordExtendedItem_fast(pRec,extended_fields.dateadded); if (t && *t) MakeDateString(_wtoi64(t), pItem->pszText, pItem->cchTextMax); break; } case MEDIAVIEW_COL_LASTPLAY: if (pRec->lastplay > 0) MakeDateString(pRec->lastplay, pItem->pszText, pItem->cchTextMax); break; case MEDIAVIEW_COL_ISPODCAST: { wchar_t *t = getRecordExtendedItem_fast(pRec, extended_fields.ispodcast); pItem->pszText = WASABI_API_LNGSTRINGW((t && t[0] == L'1' && !t[1]) ? IDS_PODCAST : IDS_NON_PODCAST); break; } case MEDIAVIEW_COL_PODCASTCHANNEL: pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.podcastchannel); break; case MEDIAVIEW_COL_PODCASTPUBDATE: { wchar_t * t = getRecordExtendedItem_fast(pRec,extended_fields.podcastpubdate); if (t && *t) MakeDateString(_wtoi64(t), pItem->pszText, pItem->cchTextMax); break; } case MEDIAVIEW_COL_CATEGORY: pItem->pszText = pRec->category; break; case MEDIAVIEW_COL_DIRECTOR: pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.director); break; case MEDIAVIEW_COL_PRODUCER: pItem->pszText = getRecordExtendedItem_fast(pRec, extended_fields.producer); break; case MEDIAVIEW_COL_DIMENSION: { wchar_t *w = getRecordExtendedItem_fast(pRec,extended_fields.width); wchar_t *h = getRecordExtendedItem_fast(pRec,extended_fields.height); if (w && *w && h && *h) StringCchPrintfW(pItem->pszText, pItem->cchTextMax, L"%dx%d", _wtoi(w), _wtoi(h)); break; } } if (!pItem->pszText) pItem->pszText = L""; } return FALSE; } static BOOL ListView_OnColumnClick(HWND hwndDlg, NMLISTVIEW *pnmv) { int l_sc = g_view_metaconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST); int l_sd = g_view_metaconf->ReadInt(L"mv_sort_dir", 0); if (columnOrder[pnmv->iSubItem] == l_sc) l_sd = !l_sd; else { /* JF> I like this better when the direction doesnt get reset every time you choose a new column. To revert to old behavior, uncomment this line: l_sd=0; */ l_sc = columnOrder[pnmv->iSubItem]; } g_view_metaconf->WriteInt(L"mv_sort_by", l_sc); g_view_metaconf->WriteInt(L"mv_sort_dir", l_sd); MLSkinnedListView_DisplaySort(pnmv->hdr.hwndFrom, pnmv->iSubItem, !l_sd); sortResults(g_view_metaconf, &itemCache); resultlist.SetVirtualCount(0); resultlist.SetVirtualCount(itemCache.Size); // TODO: we could set a limit here ListView_RedrawItems(pnmv->hdr.hwndFrom, 0, itemCache.Size - 1); return FALSE; } static BOOL ListView_OnBeginDrag(HWND hwndDlg, NMLISTVIEW *pnmv) { switch (columnOrder[pnmv->iSubItem]) { case MEDIAVIEW_COL_RATING: ratingColumn.hwndList = pnmv->hdr.hwndFrom; ratingColumn.fStyle = RCS_DEFAULT; ratingColumn.iItem = pnmv->iItem; ratingColumn.iSubItem = pnmv->iSubItem; ratingColumn.value = ((UINT)pnmv->iItem < (UINT)itemCache.Size) ? itemCache.Items[pnmv->iItem].rating : 0; MLRatingColumn_BeginDrag(plugin.hwndLibraryParent, &ratingColumn); break; } SetCapture(hwndDlg); return FALSE; } static BOOL ListView_OnReturn(HWND hwndDlg, NMHDR *pnmh) { SendMessage(hwndDlg, WM_COMMAND, ((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^(!!g_config->ReadInt(L"enqueuedef", 0))) ? IDC_BUTTON_ENQUEUE : IDC_BUTTON_PLAY, 0); return FALSE; } static BOOL ListView_OnCustomDraw(HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult) { static BOOL bDrawFocus; static RECT rcView; static RATINGCOLUMNPAINT ratingColumnPaint; static CLOUDCOLUMNPAINT cloudColumnPaint; *pResult = CDRF_DODEFAULT; switch (plvcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: *pResult |= CDRF_NOTIFYITEMDRAW; CopyRect(&rcView, &plvcd->nmcd.rc); ratingColumnPaint.fStyle = RCS_DEFAULT; ratingColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom; ratingColumnPaint.hdc = plvcd->nmcd.hdc; ratingColumnPaint.prcView = &rcView; cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom; cloudColumnPaint.hdc = plvcd->nmcd.hdc; cloudColumnPaint.prcView = &rcView; return TRUE; case CDDS_ITEMPREPAINT: *pResult |= CDRF_NOTIFYSUBITEMDRAW; bDrawFocus = (CDIS_FOCUS & plvcd->nmcd.uItemState); if (bDrawFocus) { plvcd->nmcd.uItemState &= ~CDIS_FOCUS; *pResult |= CDRF_NOTIFYPOSTPAINT; } return TRUE; case CDDS_ITEMPOSTPAINT: if (bDrawFocus) { RECT rc; rc.left = LVIR_BOUNDS; SendMessageW(plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc); rc.left += 3; DrawFocusRect(plvcd->nmcd.hdc, &rc); plvcd->nmcd.uItemState |= CDIS_FOCUS; bDrawFocus = FALSE; } *pResult = CDRF_SKIPDEFAULT; return TRUE; case(CDDS_SUBITEM | CDDS_ITEMPREPAINT): switch (columnOrder[plvcd->iSubItem]) { case MEDIAVIEW_COL_RATING: if (bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break; ratingColumnPaint.iItem = plvcd->nmcd.dwItemSpec; ratingColumnPaint.iSubItem = plvcd->iSubItem; ratingColumnPaint.value = (plvcd->nmcd.dwItemSpec >= 0 && plvcd->nmcd.dwItemSpec < (UINT)itemCache.Size) ? itemCache.Items[plvcd->nmcd.dwItemSpec].rating : 0; ratingColumnPaint.prcItem = &plvcd->nmcd.rc; ratingColumnPaint.rgbBk = plvcd->clrTextBk; ratingColumnPaint.rgbFg = plvcd->clrText; if (MLRatingColumn_Paint(plugin.hwndLibraryParent, &ratingColumnPaint)) { *pResult = CDRF_SKIPDEFAULT; return TRUE; } break; case MEDIAVIEW_COL_CLOUD: if (bgThread_Handle || (0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right)) break; int icon = 4; wchar_t *t = getRecordExtendedItem_fast(&itemCache.Items[plvcd->nmcd.dwItemSpec], extended_fields.cloud); if (t && *t) icon = _wtoi(t); cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec; cloudColumnPaint.iSubItem = plvcd->iSubItem; cloudColumnPaint.value = icon; cloudColumnPaint.prcItem = &plvcd->nmcd.rc; cloudColumnPaint.rgbBk = plvcd->clrTextBk; cloudColumnPaint.rgbFg = plvcd->clrText; if (MLCloudColumn_Paint(plugin.hwndLibraryParent, &cloudColumnPaint)) { *pResult = CDRF_SKIPDEFAULT; return TRUE; } break; } break; } return FALSE; } static BOOL ListView_OnClick(HWND hwnDlg, NMITEMACTIVATE *pnmitem) { if (bgThread_Handle) return FALSE; if (pnmitem->iItem != -1) { switch (columnOrder[pnmitem->iSubItem]) { case MEDIAVIEW_COL_RATING: ratingColumn.hwndList = pnmitem->hdr.hwndFrom; ratingColumn.ptAction = pnmitem->ptAction; ratingColumn.bRedrawNow = TRUE; ratingColumn.fStyle = RCS_DEFAULT; if (!MLRatingColumn_Click(plugin.hwndLibraryParent, &ratingColumn)) return FALSE; SetRating(ratingColumn.iItem, ratingColumn.value, ratingColumn.hwndList); break; case MEDIAVIEW_COL_CLOUD: { RECT itemRect = {0}; if (pnmitem->iSubItem) ListView_GetSubItemRect(pnmitem->hdr.hwndFrom, pnmitem->iItem, pnmitem->iSubItem, LVIR_BOUNDS, &itemRect); else { ListView_GetItemRect(pnmitem->hdr.hwndFrom, pnmitem->iItem, &itemRect, LVIR_BOUNDS); itemRect.right = itemRect.left + ListView_GetColumnWidth(pnmitem->hdr.hwndFrom, pnmitem->iSubItem); } MapWindowPoints(pnmitem->hdr.hwndFrom, HWND_DESKTOP, (POINT*)&itemRect, 2); HMENU cloud_menu = CreatePopupMenu(); WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)itemCache.Items[pnmitem->iItem].filename, (intptr_t)&cloud_menu); if (cloud_menu) { int r = DoTrackPopup(cloud_menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, itemRect.right, itemRect.top, pnmitem->hdr.hwndFrom, NULL); if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_UPPER) { // 0 = no change // 1 = adding to cloud // 2 = added locally // 4 = removed int mode = 0; // deals with cloud specific menus WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode); switch (mode) { case 1: setCloudValue(&itemCache.Items[pnmitem->iItem], L"5"); break; case 2: setCloudValue(&itemCache.Items[pnmitem->iItem], L"4"); break; case 4: setCloudValue(&itemCache.Items[pnmitem->iItem], L"4"); break; } InvalidateRect(resultlist.getwnd(), NULL, TRUE); } DestroyMenu(cloud_menu); } } break; } } return FALSE; } static BOOL ListView_OnHotTrack(HWND hwndDlg, NMLISTVIEW *pnmlv, LRESULT *pResult) { UINT iItem; if (bgThread_Handle) { pnmlv->iItem = -1; *pResult = TRUE; return TRUE; } if (-1 == pnmlv->iItem && 0 == pnmlv->iSubItem) { LVHITTESTINFO lvhit; lvhit.pt = pnmlv->ptAction; SendMessageW(pnmlv->hdr.hwndFrom, LVM_HITTEST, 0, (LPARAM)&lvhit); iItem = lvhit.iItem; } else iItem = pnmlv->iItem; switch (columnOrder[pnmlv->iSubItem]) { case MEDIAVIEW_COL_RATING: ratingColumn.hwndList = pnmlv->hdr.hwndFrom; ratingColumn.fStyle = RCS_DEFAULT; ratingColumn.iItem = iItem; ratingColumn.iSubItem = pnmlv->iSubItem; ratingColumn.value = (iItem < (UINT)itemCache.Size) ? itemCache.Items[iItem].rating : 0; ratingColumn.ptAction = pnmlv->ptAction; ratingColumn.bRedrawNow = TRUE; MLRatingColumn_Track(plugin.hwndLibraryParent, &ratingColumn); break; } // LVS_EX_ONECLICKACTIVATE enabled - make listview select nothing pnmlv->iItem = -1; *pResult = TRUE; return TRUE; } /////////// Dialog Messages / Notifications static void Dialog_OnDisplayChange(HWND hwndDlg) { INT i; HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2); for (i = 0; -1 != columnOrder[i] && MEDIAVIEW_COL_RATING != columnOrder[i] && MEDIAVIEW_COL_CLOUD != columnOrder[i]; i++); if (-1 != columnOrder[i]) { if (hwndList) { INT w = (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L); SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, i, (LPARAM)w); } } LayoutWindows(hwndDlg, TRUE); } static wchar_t tt_buf[256] = {L""}; static int last_item = -1, last_icon = -1; LRESULT pmp_listview(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_NOTIFY) { LPNMHDR l=(LPNMHDR)lParam; switch (l->code) { case TTN_SHOW: { LVHITTESTINFO lvh = {0}; GetCursorPos(&lvh.pt); ScreenToClient(hwnd, &lvh.pt); ListView_SubItemHitTest(hwnd, &lvh); int cloudcol = (int)GetPropW(hwnd, L"pmp_list_info"); if (lvh.iItem != -1 && lvh.iSubItem == cloudcol) { RECT r = {0}; if (lvh.iSubItem) ListView_GetSubItemRect(hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r); else { ListView_GetItemRect(hwnd, lvh.iItem, &r, LVIR_BOUNDS); r.right = r.left + ListView_GetColumnWidth(hwnd, cloudcol); } MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&r, 2); SetWindowPos(l->hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE); return 1; } } break; case TTN_NEEDTEXTW: { LVHITTESTINFO lvh = {0}; GetCursorPos(&lvh.pt); ScreenToClient(hwnd, &lvh.pt); ListView_SubItemHitTest(hwnd, &lvh); int cloudcol = (int)GetPropW(hwnd, L"pmp_list_info"); if (lvh.iItem != -1 && lvh.iSubItem == cloudcol) { LPNMTTDISPINFOW lpnmtdi = (LPNMTTDISPINFOW)lParam; int icon = 4; wchar_t *t = getRecordExtendedItem_fast(&itemCache.Items[lvh.iItem], extended_fields.cloud); if (t && *t) icon = _wtoi(t); if (last_item == lvh.iItem && last_icon == icon) { lpnmtdi->lpszText = tt_buf; return 0; } if (icon == 4) { WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf)); } else if (icon == 5) { WASABI_API_LNGSTRINGW_BUF(IDS_UPLOADING_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf)); } else { if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST); if (cloud_hinst && cloud_hinst != (HINSTANCE)1) { winampMediaLibraryPlugin *(*gp)(); gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin"); if (gp) { winampMediaLibraryPlugin *mlplugin = gp(); if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER)) { WASABI_API_LNGSTRINGW_BUF(IDS_TRACK_AVAILABLE, tt_buf, ARRAYSIZE(tt_buf)); nx_string_t *out_devicenames = 0; size_t num_names = mlplugin->MessageProc(0x405, (INT_PTR)itemCache.Items[lvh.iItem].filename, (INT_PTR)&out_devicenames, 0); if (num_names > 0) { for (size_t i = 0; i < num_names; i++) { if (i > 0) StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), L", "); StringCchCatW(tt_buf, ARRAYSIZE(tt_buf), out_devicenames[i]->string); } } else { WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_TO_SOURCE, tt_buf, ARRAYSIZE(tt_buf)); } if (out_devicenames) free(out_devicenames); } } } } last_item = lvh.iItem; last_icon = icon; lpnmtdi->lpszText = tt_buf; // bit of a fiddle but it allows for multi-line tooltips //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0); } else return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam); } return 0; } } return CallWindowProcW((WNDPROC)GetPropW(hwnd, L"pmp_list_proc"), hwnd, uMsg, wParam, lParam); } void Dialog_UpdateButtonText(HWND hwndDlg, int _enqueuedef) { if (groupBtn) { switch(_enqueuedef) { case 1: SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.enqueue); customAllowed = FALSE; break; default: // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK_IN_USE, (INT_PTR)_enqueuedef, 0, 0}; wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p); if (pszTextW && pszTextW[0] != 0) { // set this to be a bit different so we can just use one button and not the // mixable one as well (leaving that to prevent messing with the resources) SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, pszTextW); customAllowed = TRUE; } else { SetDlgItemTextW(hwndDlg, IDC_BUTTON_PLAY, view.play); customAllowed = FALSE; } break; } } } enum { BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value BPM_WM_COMMAND = 0x2, // just send WM_COMMAND }; BOOL Dialog_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0) { RECT r; HWND buttonHWND = GetDlgItem(hwndDlg, buttonId); GetWindowRect(buttonHWND, &r); UpdateMenuItems(hwndDlg, menu, IDR_VIEW_ACCELERATORS); MLSkinnedButton_SetDropDownState(buttonHWND, TRUE); UINT tpmFlags = TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN; if (!(flags & BPM_WM_COMMAND)) tpmFlags |= TPM_RETURNCMD; int x = DoTrackPopup(menu, tpmFlags, r.left, r.top, hwndDlg, NULL); if ((flags & BPM_ECHO_WM_COMMAND) && x) SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(x, 0), 0); MLSkinnedButton_SetDropDownState(buttonHWND, FALSE); return x; } static void Dialog_Play(HWND hwndDlg, HWND from, UINT idFrom) { HMENU listMenu = GetSubMenu(g_context_menus2, 0); int count = GetMenuItemCount(listMenu); if (count > 2) { for (int i = 2; i < count; i++) { DeleteMenu(listMenu, 2, MF_BYPOSITION); } } Dialog_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND); } static INT_PTR Dialog_OnInit(HWND hwndDlg, HWND hwndFocus, LPARAM lParam) { // benski> this is just going here because it's very likely to get called. will only get compiled in debug mode. assert(sizeof(extra_idsW) / sizeof(*extra_idsW) == sizeof(extra_strsW) / sizeof(*extra_strsW)); g_displaysearch = !(BOOL)lParam; // Set the hwnd for the search to a global only if the multipane view hasnt set it yet, as it takes precedence to which box gets populated with the query //if (!IsWindow(hwndSearchGlobal)) hwndSearchGlobal = GetDlgItem(hwndDlg, IDC_QUICKSEARCH); HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_ACCELERATORS); if (accel) WASABI_API_APP->app_addAccelerators(hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD); if (!view.play) { SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view); } HWND hwndList; FLICKERFIX ff; INT index; INT ffcl[] = { IDC_CLEAR, IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX, IDC_BUTTON_INFOTOGGLE, IDC_BUTTON_CREATEPLAYLIST, IDC_MIXABLE, IDC_MEDIASTATUS, }; m_hwnd = hwndDlg; m_bgupdinfoviewerflag = 0; columnOrder[0] = -1; last_item = -1; tt_buf[0] = 0; if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST); if (cloud_hinst && cloud_hinst != (HINSTANCE)1) { winampMediaLibraryPlugin *(*gp)(); gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin"); if (gp) { winampMediaLibraryPlugin *mlplugin = gp(); if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER)) { int64_t *out_ids = 0; nx_string_t *out_filenames = 0; size_t num_files = mlplugin->MessageProc(0x404, (INT_PTR)&out_filenames, (INT_PTR)&out_ids, 0xDEADBEEF); for (size_t i = 0; i < num_files; i++) { cloudFiles.push_back((wchar_t *)out_filenames[i]->string); } if (out_filenames) { free(out_filenames); out_filenames = 0; } if (out_ids) { free(out_ids); out_ids = 0; } HWND ml_pmp_window = FindWindowW(L"ml_pmp_window", NULL); if (IsWindow(ml_pmp_window)) { SendMessage(ml_pmp_window, WM_PMP_IPC, (WPARAM)&cloudUploading, PMP_IPC_GETCLOUDTRANSFERS); wchar_t a[32] = {0}; StringCchPrintfW(a, 32, L"%d", cloudUploading.size()); } } } } EnterCriticalSection(&g_db_cs); if (!m_media_scanner) m_media_scanner = NDE_Table_CreateScanner(g_table); LeaveCriticalSection(&g_db_cs); itemCache.Items = 0; itemCache.Alloc = 0; itemCache.Size = 0; hwndList = GetDlgItem(hwndDlg, IDC_LIST2); if (IsWindow(hwndList)) { resultlist.setwnd(hwndList); resultlist.ForceUnicode(); DWORD styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_ONECLICKACTIVATE; /*LVS_EX_ONECLICKACTIVATE - needed to hottracking work prior WinXp */ SendMessageW(hwndList, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx); HWND hwndHeader = (HWND)SendMessage(hwndList, LVM_GETHEADER, 0, 0L); if (IsWindow(hwndHeader)) SetWindowLongPtrW(hwndHeader, GWLP_ID, IDC_LIST2HEADER); MLSKINWINDOW skin = {0}; skin.hwndToSkin = hwndList; skin.skinType = SKINNEDWND_TYPE_LISTVIEW; skin.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS; MLSkinWindow(plugin.hwndLibraryParent, &skin); } else resultlist.setwnd(NULL); ff.mode = FFM_ERASEINPAINT; for (index = 0; index < sizeof(ffcl) / sizeof(INT); index++) { ff.hwnd = GetDlgItem(hwndDlg, ffcl[index]); SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff); } if (!g_displaysearch) // disable search box { ShowWindow(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_CLEAR), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_SEARCHCAPTION), SW_HIDE); } if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) memcpy(columnOrder, defColumnOrder, sizeof(defColumnOrder)); else memcpy(columnOrder, defColumnOrderCloud, sizeof(defColumnOrderCloud)); //Read the column order Dialog_OnDisplayChange(hwndDlg); int cloudcol = -1; int l = g_view_metaconf->ReadInt(L"nbcolumns", 0); if (l) { if (l > MAX_COLUMN_ORDER - 1) l = MAX_COLUMN_ORDER - 1; index = 0; for (; index < l; index++) { wchar_t tmp[128] = {0}; StringCchPrintfW(tmp, 128, L"column%d", index); int v = g_view_metaconf->ReadInt(tmp, 0); if (v == MEDIAVIEW_COL_CLOUD) cloudcol = index; if (v < 0 || v >= MEDIAVIEW_COL_NUMS) v = 0; columnOrder[index] = (BYTE)v; } columnOrder[index] = -1; } if (cloudcol == -1 && !g_view_metaconf->ReadInt(L"cloud", 1)) { g_view_metaconf->WriteInt(L"cloud", 1); for(int i = l; i != 0; i--) { columnOrder[i+1] = columnOrder[i]; if (i == 3) { columnOrder[i] = MEDIAVIEW_COL_CLOUD; break; } } } if (!GetPropW(hwndList, L"pmp_list_proc")) { SetPropW(hwndList, L"pmp_list_proc", (HANDLE)SetWindowLongPtrW(hwndList, GWLP_WNDPROC, (LONG_PTR)pmp_listview)); } initColumnsHeader(hwndList); char *pszTextA = (g_config->ReadInt(L"remembersearch", 0)) ? g_view_metaconf->ReadString("lastquery_utf8", "") : ""; AutoWide queryUnicode(pszTextA, CP_UTF8); SetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, queryUnicode); KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID); doQuery(hwndDlg, queryUnicode, 0); updateInfoText(hwndDlg); search_oldWndProc = (WNDPROC)SetWindowLongPtrW(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), GWLP_WNDPROC, (LONG_PTR)search_newWndProc); if (g_table && !NDE_Table_GetRecordsCount(g_table) && !g_config->ReadInt(L"noshowadddlg", 0)) { SetTimer(hwndDlg, 5050, 1000, NULL); } groupBtn = g_config->ReadInt(L"groupbtn", 1); enqueuedef = (g_config->ReadInt(L"enqueuedef", 0) == 1); /// detect predixis predixisExist = FALSE; //predixis out - begin //pluginMessage p = {ML_MSG_PDXS_STATUS, (INT_PTR)"test", 0, 0}; //pszTextA = (char *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p); //predixisExist = (pszTextA != NULL && pszTextA[0] != 0); //predixis out - end // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG(IDC_BUTTON_MIX, IDC_BUTTON_ENQUEUE), (INT_PTR)L"ml_local"}; wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p); if (pszTextW && pszTextW[0] != 0) { // set this to be a bit different so we can just use one button and not the // mixable one as well (leaving that to prevent messing with the resources) customAllowed = TRUE; SetDlgItemTextW(hwndDlg, IDC_BUTTON_MIX, pszTextW); } else customAllowed = FALSE; ShowWindow(GetDlgItem(hwndDlg, IDC_BUTTON_MIX), (!customAllowed ? SW_HIDE : SW_SHOW)); ShowWindow(GetDlgItem(hwndDlg, IDC_MIXABLE), (!(predixisExist & 1) ? SW_HIDE : SW_SHOW)); MLSKINWINDOW m = {0}; m.hwndToSkin = hwndDlg; m.skinType = SKINNEDWND_TYPE_AUTO; m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS; MLSkinWindow(plugin.hwndLibraryParent, &m); m.skinType = SKINNEDWND_TYPE_BUTTON; m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0); const int buttonids[] = {IDC_BUTTON_PLAY, IDC_BUTTON_ENQUEUE, IDC_BUTTON_MIX}; for (size_t i=0;i!=sizeof(buttonids)/sizeof(buttonids[0]);i++) { m.hwndToSkin = GetDlgItem(hwndDlg, buttonids[i]); if (IsWindow(m.hwndToSkin)) MLSkinWindow(plugin.hwndLibraryParent, &m); } m.skinType = SKINNEDWND_TYPE_AUTO; m.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS; const int buttonidz[] = {IDC_SEARCHCAPTION, IDC_QUICKSEARCH, IDC_MEDIASTATUS, IDC_CLEAR, IDC_BUTTON_INFOTOGGLE, IDC_BUTTON_CREATEPLAYLIST, IDC_MIXABLE}; for (size_t i=0;i!=sizeof(buttonidz)/sizeof(buttonidz[0]);i++) { m.hwndToSkin = GetDlgItem(hwndDlg, buttonidz[i]); if (IsWindow(m.hwndToSkin)) MLSkinWindow(plugin.hwndLibraryParent, &m); } Dialog_UpdateButtonText(hwndDlg, enqueuedef); return FALSE; } static BOOL Dialog_OnNotify(HWND hwndDlg, INT idCtrl, NMHDR* pnmh, LRESULT *pResult) { switch (pnmh->idFrom) { case IDC_LIST2: switch (pnmh->code) { case LVN_ITEMCHANGED: return ListView_OnItemChanged(hwndDlg, (NMLISTVIEW*)pnmh); case NM_DBLCLK: return ListView_OnDoubleClick(hwndDlg, (NMITEMACTIVATE*)pnmh); case LVN_ODFINDITEMA: case LVN_ODFINDITEMW: return ListView_OnFindItem(hwndDlg, (NMLVFINDITEMW*)pnmh, pResult, pnmh->code); case LVN_GETDISPINFOA: case LVN_GETDISPINFOW: return ListView_OnGetDispInfo(hwndDlg, (NMLVDISPINFOW*)pnmh, pnmh->code); case LVN_COLUMNCLICK: return ListView_OnColumnClick(hwndDlg, (NMLISTVIEW*)pnmh); case LVN_BEGINDRAG: return ListView_OnBeginDrag(hwndDlg, (NMLISTVIEW*)pnmh); case NM_RETURN: return ListView_OnReturn(hwndDlg, pnmh); case NM_CUSTOMDRAW: return ListView_OnCustomDraw(hwndDlg, (NMLVCUSTOMDRAW*)pnmh, pResult); case LVN_HOTTRACK: return ListView_OnHotTrack(hwndDlg, (NMLISTVIEW*)pnmh, pResult); case NM_CLICK: return ListView_OnClick(hwndDlg, (NMITEMACTIVATE*)pnmh); } break; case IDC_LIST2HEADER: switch (pnmh->code) { case NM_RCLICK: return Header_OnRightClick(hwndDlg, pnmh, pResult); case HDN_ENDDRAG: return Header_OnEndDrag(hwndDlg, (NMHEADERW*)pnmh, pResult); case HDN_ITEMCHANGINGA: case HDN_ITEMCHANGINGW: return Header_OnItemChanging(hwndDlg, (NMHEADERW*)pnmh, pResult, pnmh->code); } break; } return FALSE; } static void Dialog_OnInitMenuPopup(HWND hwndDlg, HMENU hMenu, UINT nIndex, BOOL bSysMenu) { if (hMenu && hMenu == s.build_hMenu && s.mode == 1) { myMenu = TRUE; if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == (LRESULT)-1) s.mode = 2; myMenu = FALSE; } if (rate_hmenu && hMenu == rate_hmenu) { int x; int sel = 0; for (x = 0; x < itemCache.Size; x ++) { if (resultlist.GetSelected(x)) { int s = itemCache.Items[x].rating; if (s == sel || !sel) sel = s; if (s != sel) break; } } if (-1 == sel) sel = 0; Menu_SetRatingValue(rate_hmenu, sel); } if (cloud_hmenu && hMenu == cloud_hmenu) { int n = resultlist.GetSelectionMark(); if (n != -1 && !GetMenuItemCount(hMenu)) { WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)itemCache.Items[n].filename, (intptr_t)&cloud_hmenu); } } } static void Dialog_OnMouseMove(HWND hwndDlg, UINT nFlags, POINTS pts) { if (GetCapture() == hwndDlg) { mlDropItemStruct m = {0}; POINTSTOPOINT(m.p, pts); MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&m.p, 1); if (MLRatingColumn_Drag(plugin.hwndLibraryParent, &m.p)) return; m.type = ML_TYPE_ITEMRECORDLIST; pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m); } } static void Dialog_OnLButtonUp(HWND hwndDlg, UINT nFlags, POINTS pts) { if (GetCapture() == hwndDlg) { mlDropItemStruct m = {0}; ReleaseCapture(); POINTSTOPOINT(m.p, pts); MapWindowPoints(hwndDlg, HWND_DESKTOP, (POINT*)&m.p, 1); ratingColumn.bCanceled = FALSE; ratingColumn.ptAction = m.p; ratingColumn.bRedrawNow = TRUE; if (MLRatingColumn_EndDrag(plugin.hwndLibraryParent, &ratingColumn)) { SetRating(ratingColumn.iItem, ratingColumn.value, ratingColumn.hwndList); return; } m.type = ML_TYPE_ITEMRECORDLISTW; m.flags = ML_HANDLEDRAG_FLAG_NOCURSOR; pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m); if (m.result > 0) // try itemRecordListW { itemRecordListW myObj = {0, }; copyFilesToItemCacheW(&myObj); m.flags = 0; m.result = 0; m.data = (void*) & myObj; pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m); _aligned_free(myObj.Items); // DO NOT empty this object, cause it doesnt own its data } else // if it didn't work, fall back to itemRecordList { m.type = ML_TYPE_ITEMRECORDLIST; m.result = 0; pluginHandleIpcMessage(ML_IPC_HANDLEDRAG, (WPARAM)&m); if (m.result > 0) { itemRecordListW myObj = {0, }; copyFilesToItemCacheW(&myObj); itemRecordList objA = {0, }; convertRecordList(&objA, &myObj); m.flags = 0; m.result = 0; m.data = (void*) & objA; pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m); emptyRecordList(&objA); freeRecordList(&objA); _aligned_free(myObj.Items); // DO NOT empty this object, cause it doesnt own its data } } } } class ItemRecordPlaylist : public ifc_playlist { public: ItemRecordPlaylist(const itemRecordListW *_list) { list = _list; } private: size_t GetNumItems() { return list->Size; } size_t GetItem(size_t item, wchar_t *filename, size_t filenameCch) { if (item < (size_t)list->Size && list->Items[item].filename) { StringCchCopyW(filename, filenameCch, list->Items[item].filename); return 1; } return 0; } size_t GetItemTitle(size_t item, wchar_t *title, size_t titleCch) { if (item < (size_t)list->Size && list->Items[item].filename) { TAG_FMT_EXT(list->Items[item].filename, itemrecordWTagFunc, ndeTagFuncFree, (void*)&list->Items[item], title, titleCch, 0); return 1; } return 0; } int GetItemLengthMilliseconds(size_t item) { if (item < (size_t)list->Size && list->Items[item].length>=0) { return list->Items[item].length * 1000; } return -1000; } private: const itemRecordListW *list; protected: RECVS_DISPATCH; }; #define CBCLASS ItemRecordPlaylist START_DISPATCH; CB(IFC_PLAYLIST_GETNUMITEMS, GetNumItems) CB(IFC_PLAYLIST_GETITEM, GetItem) CB(IFC_PLAYLIST_GETITEMTITLE, GetItemTitle) CB(IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds) END_DISPATCH; #undef CBCLASS static void Dialog_OnCommand(HWND hwndDlg, UINT idCtrl, INT nCode, HWND hwndCtrl) { if (GetFocus() != hwndSearchGlobal) { switch (idCtrl) { case IDC_CLEAR: SetDlgItemText(hwndDlg, IDC_QUICKSEARCH, L""); break; case IDC_BUTTON_INFOTOGGLE: updateInfoText(hwndDlg, TRUE); UpdateWindow(hwndDlg); LayoutWindows(hwndDlg, TRUE); break; case IDC_QUICKSEARCH: if (nCode == EN_CHANGE) { KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID); SetTimer(hwndDlg, UPDATE_QUERY_TIMER_ID, g_querydelay, NULL); } break; case ID_AUDIOWND_PLAYSELECTION: case ID_QUERYWND_PLAYQUERY: case IDC_BUTTON_PLAY: case ID_MEDIAWND_PLAYSELECTEDFILES: case ID_AUDIOWND_ENQUEUESELECTION: case IDC_BUTTON_ENQUEUE: case ID_MEDIAWND_ENQUEUESELECTEDFILES: case IDC_BUTTON_MIX: { if (nCode == MLBN_DROPDOWN) { Dialog_Play(hwndDlg, hwndCtrl, idCtrl); } else { int action; if (idCtrl == IDC_BUTTON_PLAY || idCtrl == ID_MEDIAWND_PLAYSELECTEDFILES || idCtrl == ID_AUDIOWND_PLAYSELECTION) { action = (nCode == 1) ? g_config->ReadInt(L"enqueuedef", 0) == 1 : 0; } else if (idCtrl == IDC_BUTTON_ENQUEUE || idCtrl == ID_MEDIAWND_ENQUEUESELECTEDFILES || idCtrl == ID_AUDIOWND_ENQUEUESELECTION) { action = (nCode == 1) ? g_config->ReadInt(L"enqueuedef", 0) != 1 : 1; } else break; int i, l = itemCache.Size; for (i = 0; i < l; i++) if (resultlist.GetSelected(i)) break; playFiles(action/*idCtrl == IDC_BUTTON_ENQUEUE*/, i == l); } } break; case IDC_BUTTON_CREATEPLAYLIST: #if 0 // TODO consider exposing this option somehow... if (AGAVE_API_PLAYLISTMANAGER) // This is the old Create Playlist button code { wchar_t fn[MAX_PATH] = {0}; wchar_t dir[MAX_PATH] = {0}; GetTempPathW(MAX_PATH,dir); GetTempFileNameW(dir,L"ml_playlist",0,fn); wcscat(fn,L".m3u8"); ItemRecordPlaylist playlist(&itemCache); if (AGAVE_API_PLAYLISTMANAGER->Save(fn, &playlist) == PLAYLISTMANAGER_SUCCESS) { mlAddPlaylist p={sizeof(p),NULL,fn,PL_FLAGS_IMPORT,-1,-1}; SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&p,ML_IPC_PLAYLIST_ADD); DeleteFileW(fn); } } #endif if (AGAVE_API_PLAYLIST_GENERATOR) { const int number_selected = resultlist.GetSelectedCount(); // Total selected if (number_selected > 0) { itemRecordListW recordList; // Record list itemRecordW *records = new itemRecordW[number_selected]; // Array of records int selectedRecordcounter = 0; for (int i = 0; i < itemCache.Size; i++) { if (resultlist.GetSelected(i)) // See if the current item is selected or not { records[selectedRecordcounter] = itemCache.Items[i]; // If its selected then add it to our itemlist selectedRecordcounter++; } } recordList.Size = selectedRecordcounter; // Set the correct size of the record list recordList.Items = records; // Set the array of records to the record list AGAVE_API_PLAYLIST_GENERATOR->GeneratePlaylist(hwndDlg, &recordList); // Call the playlist API with the list of selected records delete [] records; // Free up the array of records } else { wchar_t title[64] = {0}; MessageBoxW(m_hwnd, WASABI_API_LNGSTRINGW(IDS_ERROR_PLG_SELECT_TRACKS), WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_PLAYLIST_GENERATOR,title,64), MB_OK | MB_ICONINFORMATION); } } break; #if 0 case IDC_BUTTON_MIX: { itemRecordList list; int i; int ct = 0; for (i = 0; i < itemCache.Size; i++) { if (resultlist.GetSelected(i)) { ct++; } } ZeroMemory(&list, sizeof(itemRecordList)); allocRecordList(&list, ct, 1); list.Size = ct; ct = 0; for (i = 0; i < itemCache.Size; i++) { if (resultlist.GetSelected(i)) { convertRecord(&list.Items[ct], &itemCache.Items[i]); ct++; } } pluginMessage p = {ML_MSG_PDXS_MIX, (INT_PTR) &list, 0, 0}; SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&p, ML_IPC_SEND_PLUGIN_MESSAGE); emptyRecordList(&list); freeRecordList(&list); } break; #endif case ID_MEDIAWND_SELECTALL: { LVITEM item = {0}; item.state = LVIS_SELECTED; item.stateMask = LVIS_SELECTED; SendMessageW(resultlist.getwnd(), LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&item); } break; case ID_MEDIAWND_REMOVEFROMLIBRARY: removeSelectedItems(0); break; case ID_EDITITEMINFOS: if (resultlist.GetSelectedCount() > 0) editInfo(hwndDlg); break; case ID_PE_ID3: fileInfoDialogs(hwndDlg); PostMessageW(hwndDlg, WM_NEXTDLGCTL, (WPARAM)resultlist.getwnd(), (LPARAM)TRUE); break; case IDC_REFRESH_METADATA: RefreshMetadata(hwndDlg); break; case ID_MEDIAWND_EXPLOREFOLDER: exploreItemFolder(hwndDlg); break; } } else { switch (idCtrl) { case IDC_QUICKSEARCH: if (nCode == EN_CHANGE) { KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID); SetTimer(hwndDlg, UPDATE_QUERY_TIMER_ID, g_querydelay, NULL); } break; case ID_MEDIAWND_SELECTALL: SendMessageW(hwndSearchGlobal, EM_SETSEL, 0, -1); break; case ID_MEDIAWND_REMOVEFROMLIBRARY: { DWORD start = -1, end = -1; SendMessageW(hwndSearchGlobal, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); if (start != -1) { if (start == end) { SendMessageW(hwndSearchGlobal, EM_SETSEL, start, end + 1); } SendMessageW(hwndSearchGlobal, EM_REPLACESEL, TRUE, (LPARAM)""); SendMessageW(hwndSearchGlobal, EM_SETSEL, start, start); } } break; } } } static void Dialog_OnTimer(HWND hwndDlg, UINT_PTR idEvent, TIMERPROC fnTimer) { switch (idEvent) { case 5050: KillTimer(hwndDlg, 5050); WASABI_API_DIALOGBOXW(IDD_NEEDADDFILES, hwndDlg, needAddFilesProc); PostMessage(GetParent(hwndDlg), WM_APP + 1, (WPARAM)0, (LPARAM)0); break; case 6600: KillTimer(hwndDlg, idEvent); if (m_last_selitem >= 0 && m_last_selitem < itemCache.Size) { if (predixisExist & 1) { // Only single seeds are supported currently if (resultlist.GetSelectedCount() == 1 && isMixable(itemCache.Items[m_last_selitem])) { SetDlgItemTextW(hwndDlg, IDC_MIXABLE, WASABI_API_LNGSTRINGW(IDS_MIXABLE)); isMixablePresent = true; } else SetDlgItemText(hwndDlg, IDC_MIXABLE, L""); } SendMessageW(GetParent(hwndDlg), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)itemCache.Items[m_last_selitem].filename); m_last_selitem = -1; } break; case 123: if (bgThread_Handle) { HWND hwndList; hwndList = resultlist.getwnd(); if (1 != ListView_GetItemCount(hwndList)) ListView_SetItemCountEx(hwndList, 1, LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL); ListView_RedrawItems(hwndList, 0, 0); //UpdateWindow(hwndList); } break; case UPDATE_QUERY_TIMER_ID: { KillTimer(hwndDlg, UPDATE_QUERY_TIMER_ID); wchar_t buf[2048] = {0}; GetWindowTextW(GetDlgItem(hwndDlg, IDC_QUICKSEARCH), buf, ARRAYSIZE(buf)); doQuery(hwndDlg, buf); } break; case UPDATE_RESULT_LIST_TIMER_ID: { ListView_RedrawItems(resultlist.getwnd(), 0, resultlist.GetCount() - 1); } break; } } static void Dialog_OnDestroy(HWND hwndDlg) { HWND hwndList; INT i, j; wchar_t buf[2048] = {0}; bgQuery_Stop(); GetDlgItemTextW(hwndDlg, IDC_QUICKSEARCH, buf, ARRAYSIZE(buf)); g_view_metaconf->WriteString("lastquery_utf8", AutoChar(buf, CP_UTF8)); hwndList = GetDlgItem(hwndDlg, IDC_LIST2); if (hwndList && IsWindow(hwndList)) { for (i = 0; columnOrder[i] != -1; i++) { headerColumn *cl = &columnList[columnOrder[i]]; g_view_metaconf->WriteInt(AutoWide(cl->config_name), SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, i, 0L)); } } //Save the column order for (i = 0; columnOrder[i] != -1; i++); g_view_metaconf->WriteInt(L"nbcolumns", i); for (j = 0; j < i; j++) { wchar_t tmp[128] = {0}; StringCchPrintfW(tmp, 128, L"column%d", j); g_view_metaconf->WriteInt(tmp, columnOrder[j]); } freeRecordList(&itemCache); itemCache.Items = 0; itemCache.Alloc = 0; itemCache.Size = 0; cloudFiles.clear(); cloudUploading.clear(); hwndSearchGlobal = 0; // Set the hwnd for the search to a global to null so we know we are not in the single pane view } static void Dialog_OnWindowPosChanged(HWND hwndDlg, WINDOWPOS *pwp) { if ((SWP_NOSIZE | SWP_NOMOVE) != ((SWP_NOSIZE | SWP_NOMOVE) & pwp->flags) || (SWP_FRAMECHANGED & pwp->flags)) { LayoutWindows(hwndDlg, !(SWP_NOREDRAW & pwp->flags), 0 != (SWP_SHOWWINDOW & pwp->flags)); } } static void Dialog_OnSyncHeaderOrder(HWND hwndDlg, HWND hwndHeader) { LVCOLUMNW column = {0}; wchar_t buffer[128] = {0}; signed char tempOrder[MAX_COLUMN_ORDER] = {0}; HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST2); if (!hwndList) return; CopyMemory(tempOrder, columnOrder, sizeof(tempOrder)/sizeof(signed char)); column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_IMAGE; column.cchTextMax = sizeof(buffer) /sizeof(wchar_t); SendMessageW(hwndList, WM_SETREDRAW, FALSE, 0L); INT sort = MLSkinnedListView_GetSort(hwndList); INT count = (INT)SendMessageW(hwndHeader, HDM_GETITEMCOUNT, 0, 0L); if (count > 0) { INT index = count + 1, *pOrder = (INT*)calloc(1, sizeof(INT)*count); if (pOrder && SendMessageW(hwndList, LVM_GETCOLUMNORDERARRAY, count, (LPARAM)pOrder)) { INT order; for (order = 0; order < count; order++) { column.pszText = buffer; if (!SendMessageW(hwndList, LVM_GETCOLUMNW, pOrder[order], (LPARAM)&column)) continue; column.iOrder = order; // update position of the cloud column icon if (tempOrder[pOrder[order]] == MEDIAVIEW_COL_CLOUD) { if (!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) { MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), -1); SetPropW(hwndList, L"pmp_list_info", (HANDLE)-1); column.cx = 0; } else { MLSkinnedHeader_SetCloudColumn(ListView_GetHeader(hwndList), order); SetPropW(hwndList, L"pmp_list_info", (HANDLE)order); column.cx = 27; MLCloudColumn_GetWidth(plugin.hwndLibraryParent, &column.cx); } } SendMessageW(hwndList, LVM_INSERTCOLUMNW, index++, (LPARAM)&column); columnOrder[order] = tempOrder[pOrder[order]]; if (LOWORD(sort) == pOrder[order]) MLSkinnedListView_DisplaySort(hwndList, order, HIWORD(sort)); } for (order = 0; order < count; order++) SendMessageW(hwndList, LVM_DELETECOLUMN, 0, 0L); } for (index = 0; -1 != columnOrder[index] && MEDIAVIEW_COL_RATING != columnOrder[index] && MEDIAVIEW_COL_CLOUD != columnOrder[index]; index++); if (-1 != columnOrder[index]) { INT w = (INT)SendMessageW(hwndList, LVM_GETCOLUMNWIDTH, index, 0L); SendMessageW(hwndList, LVM_SETCOLUMNWIDTH, index, (LPARAM)w); } if (pOrder) free(pOrder); } SendMessageW(hwndList, WM_SETREDRAW, TRUE, 0L); } static void Window_OnQueryFileInfo(HWND hwnd) { INT index; HWND hwndList = GetDlgItem(hwnd, IDC_LIST2); index = (hwndList) ? (INT)SendMessage(hwndList, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_FOCUSED) : -1; SendMessageW(GetParent(hwnd), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)((-1 != index && index < itemCache.Size) ? itemCache.Items[index].filename : L"")); } void Window_OnDropFiles(HWND hwndDlg, HDROP hdrop) { wchar_t temp[1024] = {0}; int y = DragQueryFileW(hdrop, 0xffffffff, temp, 1024); if (y > 0) { wchar_t **paths = (wchar_t **)calloc(y, sizeof(wchar_t *)); int *guesses = (int *)calloc(y, sizeof(int)); int *metas = (int *)calloc(y, sizeof(int)); int *recs= (int *)calloc(y, sizeof(int)); if (paths && guesses && metas && recs) { size_t count=0; for (int x = 0; x < y; x ++) { DragQueryFileW(hdrop, x, temp, 1024); int guess = -1, meta = -1, rec = 1; // do this for normal media drops PLCallBackW plCB; if (AGAVE_API_PLAYLISTMANAGER && PLAYLISTMANAGER_SUCCESS != AGAVE_API_PLAYLISTMANAGER->Load(temp, &plCB)) { autoscan_add_directory(temp, &guess, &meta, &rec, 0); if (guess == -1) guess = g_config->ReadInt(L"guessmode", 0); if (meta == -1) meta = g_config->ReadInt(L"usemetadata", 1); paths[count] = _wcsdup(temp); guesses[count]=guess; metas[count]=meta; recs[count]=rec; count++; } } DragFinish(hdrop); Scan_ScanFolders(hwndDlg, count, paths, guesses, metas, recs); if (IsWindow(m_curview_hwnd)) SendMessage(m_curview_hwnd, WM_APP + 1, 0, 0); //update current view } else { free(paths); free(guesses); free(metas); free(recs); } } else DragFinish(hdrop); } INT_PTR CALLBACK view_mediaDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { BOOL a = dialogSkinner.Handle(hwndDlg, uMsg, wParam, lParam); if (a) return a; switch (uMsg) { case WM_INITMENUPOPUP: Dialog_OnInitMenuPopup(hwndDlg, (HMENU)wParam, LOWORD(lParam), HIWORD(lParam)); break; case WM_DISPLAYCHANGE: Dialog_OnDisplayChange(hwndDlg); break; case WM_INITDIALOG: return Dialog_OnInit(hwndDlg, (HWND)wParam, lParam); case WM_MOUSEMOVE: Dialog_OnMouseMove(hwndDlg, (UINT)wParam, MAKEPOINTS(lParam)); break; case WM_LBUTTONUP: Dialog_OnLButtonUp(hwndDlg, (UINT)wParam, MAKEPOINTS(lParam)); break; case WM_DROPFILES: Window_OnDropFiles(hwndDlg, (HDROP)wParam); case WM_COMMAND: Dialog_OnCommand(hwndDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); break; case WM_TIMER: Dialog_OnTimer(hwndDlg, (UINT_PTR) wParam, (TIMERPROC)lParam); break; case WM_DESTROY: Dialog_OnDestroy(hwndDlg); break; case WM_WINDOWPOSCHANGED: Dialog_OnWindowPosChanged(hwndDlg, (WINDOWPOS*)lParam); break; case WM_ERASEBKGND: return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT case WM_CONTEXTMENU: Dialog_OnContextMenu(hwndDlg, (HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); return 0; case WM_NOTIFY: { LRESULT result; result = 0L; if (Dialog_OnNotify(hwndDlg, (INT)wParam, (NMHDR*)lParam, &result)) { SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result); return TRUE; } } break; case IM_SYNCHEADERORDER: Dialog_OnSyncHeaderOrder(hwndDlg, (HWND)lParam); break; case WM_APP + 3: // send by bgthread if (wParam == 0x666) m_bgupdinfoviewerflag = 1; else if (wParam == 0x69) { bgQuery_Stop(); resultlist.SetVirtualCount(0); resultlist.SetVirtualCount(itemCache.Size); // TODO: we could set a limit here ListView_RedrawItems(resultlist.getwnd(), 0, itemCache.Size - 1); UpdateWindow(resultlist.getwnd()); __int64 total_len_bytes = bg_total_len_bytes; int total_length_s = (int)bg_total_len_s & 0x7FFFFFFF; wchar_t buffer[4*64] = {0}; wchar_t *pb[4] = {0}; for (int i = 0; i < 4; i++) pb[i] = buffer + i * 64; int index(0); StringCchPrintfW(pb[index], 64, L"%d %s", itemCache.Size, WASABI_API_LNGSTRINGW(itemCache.Size == 1 ? IDS_ITEM : IDS_ITEMS)); index++; if (itemCache.Size) { int uncert = 0; //bg_total_len_s>>31; if (total_length_s < 60*60) StringCchPrintfW(pb[index], 64, L"[%s%u:%02u]", uncert ? L"~" : L"", total_length_s / 60, total_length_s % 60); else if (total_length_s < 60*60*24) StringCchPrintfW(pb[index], 64, L"[%s%u:%02u:%02u]", uncert ? L"~" : L"", total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60); else { wchar_t days[16] = {0}; int total_days = total_length_s / (60 * 60 * 24); total_length_s -= total_days * 60 * 60 * 24; StringCchPrintfW(pb[index], 64, WASABI_API_LNGSTRINGW(IDS_LENGTH_DURATION_STRING), uncert ? L"~" : L"", total_days, WASABI_API_LNGSTRINGW_BUF(total_days == 1 ? IDS_DAY : IDS_DAYS, days, 16), total_length_s / 60 / 60, (total_length_s / 60) % 60, total_length_s % 60); } index++; } if (total_len_bytes) { StringCchCopyW(pb[index], 64, L"["); WASABI_API_LNG->FormattedSizeString(pb[index] + 1, 64, total_len_bytes); StringCchCatW(pb[index], 64, L"]"); index++; } unsigned int ms = (UINT)(querytime.QuadPart * 1000 / freq.QuadPart); StringCchPrintfW(pb[index], 64, WASABI_API_LNGSTRINGW(IDS_IN_X_SEC), ms / 1000.0f); index++; SetStatusText(GetDlgItem(hwndDlg, IDC_MEDIASTATUS), (LPCWSTR*)pb, index); if (m_bgupdinfoviewerflag) { m_bgupdinfoviewerflag = 0; if (itemCache.Size > 0) { if (predixisExist) { if (resultlist.GetSelectedCount() == 1 && isMixable(itemCache.Items[0])) { SetDlgItemText(hwndDlg, IDC_MIXABLE, L""); isMixablePresent = true; } else { SetDlgItemText(hwndDlg, IDC_MIXABLE, L""); } } SendMessageW(GetParent(hwndDlg), WM_SHOWFILEINFO, (WPARAM)FALSE, (LPARAM)itemCache.Items[0].filename); } } } break; case WM_APP + 1: bgQuery((resultsniff_funcW)wParam, (int)lParam); break; case WM_APP + 5: { // TODO int done = (HIWORD(wParam) == 1); int code = (LOWORD(wParam)); for (int i = 0; i < itemCache.Size; i ++) { // 0 = no change // 1 = add cloud // 2 = add local // 4 = removed if (!lstrcmpiW(itemCache.Items[i].filename, (wchar_t *)lParam)) { if (!done) { setCloudValue(&itemCache.Items[i], L"5"); } else { if (code == NErr_Success) { // uploaded ok // TODO if going to another device, will need to alter this setCloudValue(&itemCache.Items[i], L"0"); } else { // re-query state setCloudValue(&itemCache.Items[i], L"0"); } } InvalidateRect(resultlist.getwnd(), NULL, TRUE); break; } } break; } case WM_APP + 6: // handles the ml_cloud 'first pull' announces so we { // can then show the cloud column & update the cache initColumnsHeader(resultlist.getwnd()); bgQuery();//(resultsniff_funcW)wParam, (int)lParam); break; } case WM_APP + 104: { Dialog_UpdateButtonText(hwndDlg, wParam); LayoutWindows(hwndDlg, TRUE); return 0; } case WM_PAINT: { int tab[] = { IDC_LIST2 | DCW_SUNKENBORDER, IDC_QUICKSEARCH | DCW_SUNKENBORDER}; dialogSkinner.Draw(hwndDlg, tab, 1 + !!IsWindowVisible(GetDlgItem(hwndDlg, IDC_QUICKSEARCH))); } return 0; case WM_ML_CHILDIPC: switch (lParam) { case ML_CHILDIPC_GO_TO_SEARCHBAR: SendDlgItemMessage(hwndDlg, IDC_QUICKSEARCH, EM_SETSEL, 0, -1); SetFocus(GetDlgItem(hwndDlg, IDC_QUICKSEARCH)); break; case ML_CHILDIPC_REFRESH_SEARCH: PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_QUICKSEARCH, EN_CHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_QUICKSEARCH)); break; } break; case WM_USER + 0x201: offsetX = (short)LOWORD(wParam); offsetY = (short)HIWORD(wParam); g_rgnUpdate = (HRGN)lParam; return TRUE; case WM_QUERYFILEINFO: Window_OnQueryFileInfo(hwndDlg); break; } return FALSE; } //////////////////////////////// Customize columns dialog static signed char edit_columnOrder[MAX_COLUMN_ORDER]; static INT_PTR CALLBACK custColumns_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static HWND m_curlistbox_hwnd, m_availlistbox_hwnd; switch (uMsg) { case WM_INITDIALOG: memcpy(edit_columnOrder, columnOrder, sizeof(edit_columnOrder)); m_curlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST1); m_availlistbox_hwnd = GetDlgItem(hwndDlg, IDC_LIST2); if (NULL != WASABI_API_APP) { if (NULL != m_curlistbox_hwnd) WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, TRUE); if (NULL != m_availlistbox_hwnd) WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, TRUE); } case WM_USER + 32: { for (int i = 0; edit_columnOrder[i] != -1; i++) { int c = edit_columnOrder[i]; headerColumn *cl = &columnList[c]; int column_id = cl->column_id; if (column_id == IDS_CLOUD || column_id == IDS_CLOUD_HIDDEN) { // if no cloud support at all then we hide everything if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) continue; column_id = ((!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) ? IDS_CLOUD_HIDDEN : IDS_CLOUD); } int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(column_id)); SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, c); } for (int i = 0; i < sizeof(columnList) / sizeof(headerColumn); i++) { headerColumn *cl = &columnList[i]; int j; for (j = 0; edit_columnOrder[j] != -1 && edit_columnOrder[j] != i; j++); if (edit_columnOrder[j] == -1) { int column_id = cl->column_id; if (column_id == IDS_CLOUD || column_id == IDS_CLOUD_HIDDEN) { // if no cloud support at all then we hide everything if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) continue; column_id = ((!cloud_hinst || cloud_hinst == (HINSTANCE)1 || !SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_ACTIVE)) ? IDS_CLOUD_HIDDEN : IDS_CLOUD); } int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(column_id)); SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, i); } } } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_DEFS: if (!cloud_hinst || cloud_hinst == (HINSTANCE)1) memcpy(edit_columnOrder, defColumnOrder, sizeof(defColumnOrder)); else memcpy(edit_columnOrder, defColumnOrderCloud, sizeof(defColumnOrderCloud)); SendMessage(m_curlistbox_hwnd, LB_RESETCONTENT, 0, 0); SendMessage(m_availlistbox_hwnd, LB_RESETCONTENT, 0, 0); SendMessage(hwndDlg, WM_USER + 32, 0, 0); break; case IDC_LIST2: if (HIWORD(wParam) != LBN_DBLCLK) { if (HIWORD(wParam) == LBN_SELCHANGE) { int r = SendMessage(m_availlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0; EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), r); } return 0; } case IDC_BUTTON2: //add column { for (int i = 0;i < SendMessage(m_availlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) { if (SendMessage(m_availlistbox_hwnd, LB_GETSEL, i, 0)) { int c = SendMessage(m_availlistbox_hwnd, LB_GETITEMDATA, i, 0); int j; for (j = 0;edit_columnOrder[j] != -1;j++); edit_columnOrder[j] = (BYTE)c; edit_columnOrder[j + 1] = -1; SendMessage(m_availlistbox_hwnd, LB_DELETESTRING, i, 0); i--; int r = SendMessageW(m_curlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id)); SendMessageW(m_curlistbox_hwnd, LB_SETITEMDATA, r, c); } } EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON2), 0); } break; case IDC_LIST1: if (HIWORD(wParam) != LBN_DBLCLK) { if (HIWORD(wParam) == LBN_SELCHANGE) { int r = SendMessage(m_curlistbox_hwnd, LB_GETSELCOUNT, 0, 0) > 0; EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), r); EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), r); EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), r); } return 0; } case IDC_BUTTON3: //remove column { for (int i = 0;i < SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) { if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) { int c = edit_columnOrder[i]; for (int j = i;edit_columnOrder[j] != -1;j++) { edit_columnOrder[j] = edit_columnOrder[j + 1]; } SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i, 0); i--; int r = SendMessageW(m_availlistbox_hwnd, LB_ADDSTRING, 0, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id)); SendMessageW(m_availlistbox_hwnd, LB_SETITEMDATA, r, c); } } EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON3), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON4), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_BUTTON5), 0); } break; case IDC_BUTTON4: //move column up { for (int i = 0;i < (INT)SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0);i++) { if (i != 0 && (INT)SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) { BYTE c = edit_columnOrder[i - 1]; edit_columnOrder[i - 1] = edit_columnOrder[i]; edit_columnOrder[i] = c; SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i - 1, 0); int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id)); SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, c); } } } break; case IDC_BUTTON5: //move column down { int l = SendMessage(m_curlistbox_hwnd, LB_GETCOUNT, 0, 0); for (int i = l - 2;i >= 0;i--) { if (SendMessage(m_curlistbox_hwnd, LB_GETSEL, i, 0)) { BYTE c = edit_columnOrder[i + 1]; edit_columnOrder[i + 1] = edit_columnOrder[i]; edit_columnOrder[i] = c; SendMessage(m_curlistbox_hwnd, LB_DELETESTRING, i + 1, 0); int r = (INT)SendMessageW(m_curlistbox_hwnd, LB_INSERTSTRING, i, (LPARAM)WASABI_API_LNGSTRINGW(columnList[c].column_id)); SendMessage(m_curlistbox_hwnd, LB_SETITEMDATA, r, c); } } } break; case IDOK: { HWND hwndList = resultlist.getwnd(); memcpy(columnOrder, edit_columnOrder, sizeof(edit_columnOrder)); if (hwndList) { initColumnsHeader(hwndList); InvalidateRect(hwndList, NULL, TRUE); UpdateWindow(hwndList); } } case IDCANCEL: EndDialog(hwndDlg, 0); break; } break; case WM_DESTROY: if (NULL != WASABI_API_APP) { if (NULL != m_curlistbox_hwnd) WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_curlistbox_hwnd, FALSE); if (NULL != m_availlistbox_hwnd) WASABI_API_APP->DirectMouseWheel_EnableConvertToMouseWheel(m_availlistbox_hwnd, FALSE); WASABI_API_APP->app_removeAccelerators(hwndDlg); } break; } return FALSE; } void customizeColumnsDialog(HWND hwndParent) { WASABI_API_DIALOGBOXW(IDD_CUSTCOLUMNS, hwndParent, custColumns_dialogProc); EatKeyboard(); } bool isMixable(itemRecordW &song) { if (!song.filename) return false; AutoChar charFn(song.filename); pluginMessage p = {ML_MSG_PDXS_STATUS, (INT_PTR)(char *)charFn, 0, 0}; char *text = (char *)SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM) & p, ML_IPC_SEND_PLUGIN_MESSAGE); // Analyzed/Identified = mixable return text && (text[0] == 'A' || text[0] == 'I'); } void AccessingGracenoteHack(int p) { if (p == 0) { GetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, oldText, 4096); SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, L"Accessing Gracenote Database"); } else { SetDlgItemTextW(m_hwnd, IDC_MEDIASTATUS, oldText); } }