view_playlists.cpp 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953
  1. #include <windows.h>
  2. #include <shlwapi.h>
  3. #include <shlobj.h>
  4. #include <shellapi.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include "main.h"
  8. #include "nu/listview.h"
  9. #include "resource.h"
  10. #include "Playlist.h"
  11. #include "replicant/nu/AutoChar.h"
  12. #include "../../General/gen_ml/ml_ipc.h"
  13. #include "SendTo.h"
  14. #include "api__ml_playlists.h"
  15. #include "../../General/gen_ml/ml_ipc_0313.h"
  16. #include "nu/menushortcuts.h"
  17. #include "ml_local/api_mldb.h"
  18. #include "ml_pmp/pmp.h"
  19. #include "replicant/nswasabi/ReferenceCounted.h"
  20. #include "replicant/nx/win/nxstring.h"
  21. void playlist_UpdateButtonText( HWND hwndDlg, int enqueuedef );
  22. BOOL playlist_ButtonPopupMenu( HWND hwndDlg, int buttonId, HMENU menu, int flags = 0 );
  23. static std::vector<GUID> playlistGUIDs;
  24. using namespace Nullsoft::Utility;
  25. SendToMenu sendTo;
  26. static W_ListView m_playlistslist;
  27. int root_is_drag_and_dropping = 0;
  28. HINSTANCE cloud_hinst = 0;
  29. static int last_item1 = -1;
  30. int IPC_GET_CLOUD_HINST = -1;
  31. int IPC_GET_CLOUD_ACTIVE = -1;
  32. int cloud_avail = 0;
  33. int normalimage = 0;
  34. int cloudImage = 0;
  35. static void AutoSizePlaylistColumns()
  36. {
  37. m_playlistslist.AutoSizeColumn( 2 );
  38. m_playlistslist.AutoSizeColumn( 3 );
  39. RECT channelRect;
  40. GetClientRect( m_playlistslist.getwnd(), &channelRect );
  41. ListView_SetColumnWidth( m_playlistslist.getwnd(), 0, channelRect.right - m_playlistslist.GetColumnWidth( 1 ) - m_playlistslist.GetColumnWidth( 2 ) - m_playlistslist.GetColumnWidth( 3 ) );
  42. }
  43. static bool opened = false, loaded = false;
  44. void RefreshPlaylistsList()
  45. {
  46. if ( opened )
  47. {
  48. playlistGUIDs.clear();
  49. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  50. size_t count = AGAVE_API_PLAYLISTS->GetCount();
  51. playlistGUIDs.reserve( count );
  52. for ( size_t i = 0; i < count; i++ )
  53. playlistGUIDs.push_back( AGAVE_API_PLAYLISTS->GetGUID( i ) );
  54. ListView_SetItemCount( m_playlistslist.getwnd(), playlistGUIDs.size() );
  55. ListView_RedrawItems( m_playlistslist.getwnd(), 0, playlistGUIDs.size() - 1 );
  56. }
  57. }
  58. void ImportPlaylist( const wchar_t *srcFilename, bool callback = false )
  59. {
  60. wchar_t l_src_filename[ MAX_PATH ] = { 0 };
  61. lstrcpynW( l_src_filename, srcFilename, MAX_PATH );
  62. wchar_t filename[ MAX_PATH ] = { 0 };
  63. wchar_t *filenameptr = ( !g_config->ReadInt( L"external", 0 ) ? createPlayListDBFileName( filename ) : 0 );
  64. size_t numItems = AGAVE_API_PLAYLISTMANAGER->Copy( filename, l_src_filename );
  65. // get the filename of the imported playlist
  66. PathRemoveExtensionW( l_src_filename );
  67. PathStripPathW( l_src_filename );
  68. // just incase we've had external playlists added / imported
  69. // we spin through and abort trying to re-add any that match
  70. if ( g_config->ReadInt( L"external", 0 ) )
  71. {
  72. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  73. size_t count = AGAVE_API_PLAYLISTS->GetCount();
  74. for ( size_t i = 0; i != count; i++ )
  75. {
  76. PlaylistInfo info( i );
  77. if ( info.Valid() )
  78. {
  79. if ( !lstrcmpiW( srcFilename, info.GetFilename() ) )
  80. {
  81. wchar_t titleStr[ 96 ] = { 0 };
  82. MessageBox( currentView, WASABI_API_LNGSTRINGW( IDS_EXTERNAL_ALREADY_ADDED ), WASABI_API_LNGSTRINGW_BUF( IDS_PL_FILE_MNGT, titleStr, 96 ), MB_OK | MB_ICONWARNING );
  83. return;
  84. }
  85. }
  86. }
  87. }
  88. if ( l_src_filename[ 0 ] )
  89. AddPlaylist( ( !callback ? 1 : 2 ), l_src_filename, ( !g_config->ReadInt( L"external", 0 ) ? filenameptr : srcFilename ), 1, g_config->ReadInt( L"cloud", 1 ), numItems );
  90. else
  91. AddPlaylist( ( !callback ? 1 : 2 ), WASABI_API_LNGSTRINGW( IDS_IMPORTED_PLAYLIST ), ( !g_config->ReadInt( L"external", 0 ) ? filenameptr : srcFilename ), 1, g_config->ReadInt( L"cloud", 1 ), numItems );
  92. }
  93. void playlists_ImportExternalPrompt( HWND hwndDlg )
  94. {
  95. // TODO decide if better to show the message on all changes or only once and
  96. // then just leave the user to it in the future otherwise leave as it is
  97. //if (!g_config->ReadInt("external_prompt", 0) && g_config->ReadInt("external", 0))
  98. if ( !g_config->ReadInt( L"external", 0 ) )
  99. {
  100. wchar_t titleStr[ 96 ] = { 0 };
  101. if ( MessageBox( hwndDlg, WASABI_API_LNGSTRINGW( IDS_EXTERNAL_CHECKED ), WASABI_API_LNGSTRINGW_BUF( IDS_PL_FILE_MNGT, titleStr, 96 ), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 ) == IDYES )
  102. {
  103. g_config->WriteInt( L"external", ( IsDlgButtonChecked( hwndDlg, IDC_EXTERNAL ) == BST_CHECKED ) );
  104. }
  105. else
  106. {
  107. CheckDlgButton( hwndDlg, IDC_EXTERNAL, g_config->ReadInt( L"external", 0 ) );
  108. }
  109. g_config->WriteInt( L"external_prompt", 1 );
  110. }
  111. else
  112. {
  113. g_config->WriteInt( L"external", ( IsDlgButtonChecked( hwndDlg, IDC_EXTERNAL ) == BST_CHECKED ) );
  114. }
  115. }
  116. UINT_PTR CALLBACK Playlist_OFNHookProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
  117. {
  118. if ( uMsg == WM_INITDIALOG )
  119. {
  120. int cloud = playlists_CloudAvailable();
  121. HWND cloudWnd = GetDlgItem( hwndDlg, IDC_CLOUD );
  122. if ( IsWindow( cloudWnd ) )
  123. {
  124. ShowWindow( cloudWnd, cloud );
  125. CheckDlgButton( hwndDlg, IDC_CLOUD, AddToCloud() );
  126. }
  127. CheckDlgButton( hwndDlg, IDC_EXTERNAL, g_config->ReadInt( L"external", 0 ) );
  128. if ( !cloud )
  129. {
  130. HWND external = GetDlgItem( hwndDlg, IDC_EXTERNAL );
  131. if ( IsWindow( external ) && IsWindow( cloudWnd ) )
  132. {
  133. RECT r = { 0 }, cl = { 0 };
  134. GetWindowRect( external, &r );
  135. GetWindowRect( cloudWnd, &cl );
  136. ScreenToClient( hwndDlg, (LPPOINT)&r );
  137. ScreenToClient( hwndDlg, (LPPOINT)&cl );
  138. SetWindowPos( external, NULL, cl.left, r.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_SHOWWINDOW );
  139. }
  140. }
  141. }
  142. else if ( uMsg == WM_COMMAND )
  143. {
  144. switch ( LOWORD( wParam ) )
  145. {
  146. case IDC_CLOUD:
  147. playlists_AddToCloudPrompt( hwndDlg );
  148. return 1;
  149. case IDC_EXTERNAL:
  150. playlists_ImportExternalPrompt( hwndDlg );
  151. return 1;
  152. }
  153. }
  154. return 0;
  155. }
  156. void Playlist_importFromFile( HWND dlgparent )
  157. {
  158. wchar_t oldCurPath[ MAX_PATH ] = { 0 };
  159. wchar_t newCurPath[ MAX_PATH ] = { 0 };
  160. bool skipRes = false;
  161. GetCurrentDirectoryW( MAX_PATH, oldCurPath );
  162. retry:
  163. wchar_t temp[ 1024 ] = { 0 };
  164. wchar_t filter[ 1024 ] = { 0 };
  165. AGAVE_API_PLAYLISTMANAGER->GetFilterList( filter, 1024 );
  166. OPENFILENAMEW l = { sizeof( l ), 0 };
  167. l.hwndOwner = dlgparent;
  168. l.lpstrFilter = filter;
  169. l.lpstrFile = temp;
  170. l.nMaxFile = 1024;
  171. l.lpstrTitle = WASABI_API_LNGSTRINGW( IDS_IMPORT_PLAYLIST );
  172. l.lpstrDefExt = L"m3u";
  173. l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
  174. l.lpfnHook = Playlist_OFNHookProc;
  175. l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLESIZING | OFN_ENABLETEMPLATE;
  176. l.lpTemplateName = MAKEINTRESOURCE( IDD_IMPORT_PLFLD );
  177. l.hInstance = ( !skipRes ? WASABI_API_LNG_HINST : WASABI_API_ORIG_HINST );
  178. if ( GetOpenFileNameW( &l ) )
  179. {
  180. GetCurrentDirectoryW( MAX_PATH, newCurPath );
  181. WASABI_API_APP->path_setWorkingPath( newCurPath );
  182. ImportPlaylist( temp );
  183. }
  184. else
  185. {
  186. // deals with the extra child dialog not being present in language packs
  187. // so we re-spin and try to load the native version before just failing
  188. DWORD res = CommDlgExtendedError();
  189. if ( res == CDERR_NOTEMPLATE || res == CDERR_FINDRESFAILURE )
  190. {
  191. if ( !skipRes )
  192. {
  193. skipRes = true;
  194. goto retry;
  195. }
  196. }
  197. }
  198. SetCurrentDirectoryW( oldCurPath );
  199. }
  200. void Playlists_ReplaceBadPathChars( LPWSTR pszPath )
  201. {
  202. if ( NULL == pszPath )
  203. return;
  204. while ( L'\0' != *pszPath )
  205. {
  206. switch ( *pszPath )
  207. {
  208. case L'?':
  209. case L'/':
  210. case L'\\':
  211. case L':':
  212. case L'*':
  213. case L'\"':
  214. case L'<':
  215. case L'>':
  216. case L'|':
  217. *pszPath = L'_';
  218. break;
  219. default:
  220. if ( *pszPath < 32 )
  221. *pszPath = L'_';
  222. break;
  223. }
  224. pszPath = CharNextW( pszPath );
  225. }
  226. }
  227. void Playlist_export( HWND dlgparent, const wchar_t *name, const wchar_t *srcm3u )
  228. {
  229. wchar_t oldCurPath[ MAX_PATH ] = { 0 };
  230. GetCurrentDirectoryW( MAX_PATH, oldCurPath );
  231. wchar_t temp[ MAX_PATH ] = { 0 };
  232. OPENFILENAMEW l = { sizeof( OPENFILENAMEW ), 0 };
  233. l.hwndOwner = dlgparent;
  234. l.hInstance = plugin.hDllInstance;
  235. lstrcpynW( temp, name, MAX_PATH );
  236. Playlists_ReplaceBadPathChars( temp );
  237. l.nFilterIndex = g_config->ReadInt( L"filter", 3 );
  238. l.lpstrFilter = (LPCWSTR)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 3, IPC_GET_PLAYLIST_EXTLISTW );
  239. l.lpstrFile = temp;
  240. l.nMaxFile = MAX_PATH;
  241. l.lpstrTitle = WASABI_API_LNGSTRINGW( IDS_EXPORT_PLAYLIST );
  242. l.lpstrDefExt = L"m3u";
  243. l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();
  244. l.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_OVERWRITEPROMPT;
  245. if ( GetSaveFileNameW( &l ) )
  246. {
  247. wchar_t newCurPath[ MAX_PATH ] = { 0 };
  248. GetCurrentDirectoryW( MAX_PATH, newCurPath );
  249. WASABI_API_APP->path_setWorkingPath( newCurPath );
  250. AGAVE_API_PLAYLISTMANAGER->Copy( temp, srcm3u );
  251. }
  252. g_config->WriteInt( L"filter", l.nFilterIndex );
  253. SetCurrentDirectoryW( oldCurPath );
  254. }
  255. void importPlaylistFolder( const wchar_t *path, int dorecurs )
  256. {
  257. wchar_t tmppath[ MAX_PATH ] = { 0 };
  258. PathCombineW( tmppath, path, L"*" );
  259. WIN32_FIND_DATAW d;
  260. HANDLE h = FindFirstFileW( tmppath, &d );
  261. if ( h == INVALID_HANDLE_VALUE )
  262. return;
  263. do
  264. {
  265. wchar_t l_playlist_folder[ MAX_PATH ] = { 0 };
  266. PathCombineW( l_playlist_folder, path, d.cFileName );
  267. if ( d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && lstrcmpW( d.cFileName, L"." ) && lstrcmpW( d.cFileName, L".." ) && dorecurs )
  268. {
  269. importPlaylistFolder( l_playlist_folder, dorecurs );
  270. continue;
  271. }
  272. if ( AGAVE_API_PLAYLISTMANAGER->CanLoad( l_playlist_folder ) )
  273. {
  274. ImportPlaylist( l_playlist_folder, true );
  275. }
  276. } while ( FindNextFileW( h, &d ) != 0 );
  277. if ( h != INVALID_HANDLE_VALUE )
  278. FindClose( h );
  279. }
  280. void Shell_Free( void *p )
  281. {
  282. IMalloc *m;
  283. SHGetMalloc( &m );
  284. m->Free( p );
  285. }
  286. static INT_PTR CALLBACK browseCheckBoxProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
  287. {
  288. if ( uMsg == WM_INITDIALOG )
  289. {
  290. int cloud = playlists_CloudAvailable();
  291. HWND cloudWnd = GetDlgItem( hwndDlg, IDC_CLOUD );
  292. if ( IsWindow( cloudWnd ) )
  293. {
  294. ShowWindow( cloudWnd, cloud );
  295. CheckDlgButton( hwndDlg, IDC_CLOUD, AddToCloud() );
  296. }
  297. if ( g_config->ReadInt( L"importplfoldrecurs", 1 ) )
  298. CheckDlgButton( hwndDlg, IDC_CHECK1, BST_CHECKED );
  299. CheckDlgButton( hwndDlg, IDC_CLOUD, AddToCloud() );
  300. CheckDlgButton( hwndDlg, IDC_EXTERNAL, g_config->ReadInt( L"external", 0 ) );
  301. }
  302. if ( uMsg == WM_COMMAND )
  303. {
  304. if ( LOWORD( wParam ) == IDC_CHECK1 )
  305. {
  306. g_config->WriteInt( L"importplfoldrecurs", !!IsDlgButtonChecked( hwndDlg, IDC_CHECK1 ) );
  307. }
  308. else if ( LOWORD( wParam ) == IDC_CLOUD )
  309. {
  310. playlists_AddToCloudPrompt( hwndDlg );
  311. }
  312. else if ( LOWORD( wParam ) == IDC_EXTERNAL )
  313. {
  314. playlists_ImportExternalPrompt( hwndDlg );
  315. }
  316. }
  317. return 0;
  318. }
  319. BOOL CALLBACK browseEnumProc( HWND hwnd, LPARAM lParam )
  320. {
  321. wchar_t cl[ 32 ] = { 0 };
  322. GetClassNameW( hwnd, cl, ARRAYSIZE( cl ) );
  323. if ( !lstrcmpiW( cl, WC_TREEVIEW ) )
  324. {
  325. PostMessage( hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection( hwnd ) );
  326. return FALSE;
  327. }
  328. return TRUE;
  329. }
  330. int CALLBACK WINAPI _bcp( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData )
  331. {
  332. switch ( uMsg )
  333. {
  334. case BFFM_INITIALIZED:
  335. {
  336. SetWindowText( hwnd, WASABI_API_LNGSTRINGW( IDS_IMPORT_PLAYLIST_FROM_FOLDER ) );
  337. SendMessageW( hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)WASABI_API_APP->path_getWorkingPath() );
  338. HWND h2 = FindWindowEx( hwnd, NULL, NULL, L"__foo" );
  339. if ( h2 )
  340. ShowWindow( h2, SW_HIDE );
  341. HWND h = WASABI_API_CREATEDIALOGW( IDD_BROWSE_PLFLD, hwnd, browseCheckBoxProc );
  342. SetWindowPos( h, 0, 4, 4, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
  343. ShowWindow( h, SW_SHOWNA );
  344. // this is not nice but it fixes the selection not working correctly on all OSes
  345. EnumChildWindows( hwnd, browseEnumProc, 0 );
  346. }
  347. }
  348. return 0;
  349. }
  350. void Playlist_importFromFolders( HWND dlgparent )
  351. {
  352. BROWSEINFOW bi = { 0 };
  353. wchar_t name[ MAX_PATH ] = { 0 };
  354. bi.hwndOwner = dlgparent;
  355. bi.pszDisplayName = name;
  356. bi.lpszTitle = L"__foo";
  357. bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
  358. bi.lpfn = _bcp;
  359. ITEMIDLIST *idlist = SHBrowseForFolderW( &bi );
  360. if ( idlist )
  361. {
  362. wchar_t path[ MAX_PATH ] = { 0 };
  363. SHGetPathFromIDListW( idlist, path );
  364. WASABI_API_APP->path_setWorkingPath( path );
  365. Shell_Free( idlist );
  366. MLNavCtrl_BeginUpdate( plugin.hwndLibraryParent, 0 );
  367. importPlaylistFolder( path, g_config->ReadInt( L"importplfoldrecurs", 1 ) );
  368. AGAVE_API_PLAYLISTS->Flush(); // REVIEW: save immediately? or only at the end?
  369. MLNavCtrl_EndUpdate( plugin.hwndLibraryParent );
  370. }
  371. }
  372. static void playlists_Save( HWND parent )
  373. {
  374. for ( size_t i = 0; i < playlistGUIDs.size(); i++ )
  375. {
  376. if ( !m_playlistslist.GetSelected( i ) )
  377. continue;
  378. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  379. PlaylistInfo info( playlistGUIDs[ i ] );
  380. wchar_t str[ MAX_PATH ] = { 0 };
  381. if ( PathIsFileSpecW( info.GetFilename() ) )
  382. PathCombineW( str, g_path, info.GetFilename() );
  383. else
  384. lstrcpynW( str, info.GetFilename(), MAX_PATH );
  385. Playlist_export( parent, info.GetName(), str );
  386. }
  387. }
  388. void playlists_Import( HWND hwndDlg, LPARAM lParam )
  389. {
  390. RECT r;
  391. HMENU menu = GetSubMenu( g_context_menus, 2 );
  392. GetWindowRect( (HWND)lParam, &r );
  393. int x = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_BOTTOMALIGN | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RETURNCMD, r.left, r.top, hwndDlg, NULL );
  394. switch ( x )
  395. {
  396. case IDC_IMPORT_PLAYLIST_FROM_FILE:
  397. Playlist_importFromFile( hwndDlg );
  398. RefreshPlaylistsList();
  399. break;
  400. case IDC_IMPORT_WINAMP_PLAYLIST:
  401. Playlist_importFromWinamp();
  402. RefreshPlaylistsList();
  403. break;
  404. case ID_PLAYLISTSIMPORT_IMPORTPLAYLISTSFROMFOLDER:
  405. Playlist_importFromFolders( hwndDlg );
  406. RefreshPlaylistsList();
  407. break;
  408. }
  409. }
  410. void playlists_Add( HWND parent, bool callback )
  411. {
  412. WASABI_API_DIALOGBOXPARAMW( ( playlists_CloudAvailable() ? IDD_ADD_CLOUD_PLAYLIST : IDD_ADD_PLAYLIST ), parent, AddPlaylistDialogProc, callback );
  413. }
  414. void DeletePlaylist( GUID _guid, HWND parent, bool confirm )
  415. {
  416. wchar_t titleStr[ 32 ] = { 0 };
  417. if ( confirm && MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_CONFIRM_DELETION ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRMATION, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) != IDYES )
  418. {
  419. SetFocus( parent );
  420. return;
  421. }
  422. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  423. PlaylistInfo info( _guid );
  424. wchar_t gs[ MAX_PATH + 1 ] = { 0 }, gs2[ MAX_PATH ] = { 0 };
  425. if ( PathIsFileSpecW( info.GetFilename() ) )
  426. PathCombineW( gs, g_path, info.GetFilename() );
  427. else
  428. lstrcpynW( gs, info.GetFilename(), MAX_PATH );
  429. wchar_t l_node[ MAX_PATH ];
  430. wchar_t l_dir[ MAX_PATH ];
  431. wchar_t l_fname[ MAX_PATH ];
  432. wchar_t l_ext[ MAX_PATH ];
  433. lstrcpynW( gs2, gs, MAX_PATH );
  434. _wsplitpath( gs2, l_node, l_dir, l_fname, l_ext );
  435. _wmakepath( gs2, l_node, l_dir, L"", L"" );
  436. AGAVE_API_PLAYLISTS->RemovePlaylist( info.GetIndex() );
  437. // changed in 5.58 to resolve the issue reported at
  438. // http://forums.winamp.com/showthread.php?l_plugin_message=2652001#post2652001
  439. // delete the file after the removal and not before which
  440. // fixes issues if removing the currently viewed playlist
  441. //DeleteFileW(gs);
  442. // changed in 5.64 to use SHFileOperation(..) instead of DeleteFile(..)
  443. // so we're able to recover external playlists incase people messup...
  444. SHFILEOPSTRUCTW fileOp = { 0 };
  445. fileOp.hwnd = parent;
  446. fileOp.wFunc = FO_DELETE;
  447. fileOp.pFrom = gs;
  448. fileOp.fFlags = ( lstrcmpi( g_path, gs2 ) ? FOF_ALLOWUNDO : 0 ) | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_SIMPLEPROGRESS | FOF_NORECURSION | FOF_NOERRORUI | FOF_SILENT;
  449. SHFileOperationW( &fileOp );
  450. SetFocus( parent );
  451. }
  452. static void playlists_Delete( HWND parent )
  453. {
  454. if ( !m_playlistslist.GetSelectedCount() || m_playlistslist.GetSelectionMark() == -1 )
  455. return;
  456. wchar_t titleStr[ 32 ] = { 0 };
  457. if ( MessageBox( parent, WASABI_API_LNGSTRINGW( IDS_CONFIRM_DELETION ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRMATION, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) != IDYES )
  458. return;
  459. MLNavCtrl_BeginUpdate( plugin.hwndLibraryParent, 0 );
  460. for ( int i = playlistGUIDs.size() - 1; i >= 0; i-- )
  461. {
  462. if ( !m_playlistslist.GetSelected( i ) )
  463. continue;
  464. DeletePlaylist( playlistGUIDs[ i ], parent, false );
  465. }
  466. AGAVE_API_PLAYLISTS->Flush(); // REVIEW: save immediately? or only at the end?
  467. MLNavCtrl_EndUpdate( plugin.hwndLibraryParent );
  468. }
  469. static void playlists_Play( int enqueue )
  470. {
  471. int deleted = 0;
  472. for ( size_t i = 0; i < playlistGUIDs.size(); i++ )
  473. {
  474. if ( !m_playlistslist.GetSelected( i ) )
  475. continue;
  476. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  477. PlaylistInfo info( playlistGUIDs[ i ] );
  478. wchar_t str[ MAX_PATH ] = { 0 };
  479. const wchar_t *fn;
  480. if ( PathIsFileSpecW( info.GetFilename() ) )
  481. {
  482. PathCombineW( str, g_path, info.GetFilename() );
  483. fn = str;
  484. }
  485. else
  486. {
  487. fn = info.GetFilename();
  488. }
  489. if ( !enqueue && !deleted )
  490. {
  491. SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_DELETE );
  492. deleted = 1;
  493. }
  494. enqueueFileWithMetaStructW s = { 0 };
  495. s.filename = fn;
  496. s.ext = NULL;
  497. s.length = -1;
  498. SendMessage( plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW );
  499. }
  500. if ( !enqueue )
  501. SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_STARTPLAY );
  502. }
  503. static void playlists_ManageButtons( HWND hwndDlg )
  504. {
  505. int has_selection = m_playlistslist.GetSelectedCount();
  506. const int buttonids[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_VIEWLIST, IDC_SAVE };
  507. for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
  508. {
  509. HWND controlHWND = GetDlgItem( hwndDlg, buttonids[ i ] );
  510. EnableWindow( controlHWND, has_selection );
  511. }
  512. }
  513. static void playlists_ViewList()
  514. {
  515. int t = m_playlistslist.GetSelectionMark();
  516. if ( t >= 0 )
  517. {
  518. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  519. PlaylistInfo info( playlistGUIDs[ t ] );
  520. //if ( info.treeId == 0 ) // not created yet
  521. //{
  522. // // TODO: make a treeid for it
  523. //}
  524. mediaLibrary.SelectTreeItem( info.treeId );
  525. }
  526. }
  527. static void playlists_Paint( HWND hwndDlg )
  528. {
  529. int tab[] = { IDC_PLAYLIST_LIST | DCW_SUNKENBORDER, };
  530. dialogSkinner.Draw( hwndDlg, tab, 1 );
  531. }
  532. LRESULT playlists_cloud_listview( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
  533. {
  534. if ( uMsg == WM_NOTIFY )
  535. {
  536. LPNMHDR l = (LPNMHDR)lParam;
  537. switch ( l->code )
  538. {
  539. case TTN_SHOW:
  540. {
  541. LVHITTESTINFO lvh = { 0 };
  542. GetCursorPos( &lvh.pt );
  543. ScreenToClient( hwnd, &lvh.pt );
  544. ListView_SubItemHitTest( hwnd, &lvh );
  545. if ( cloud_avail && lvh.iItem != -1 && lvh.iSubItem == 1 )
  546. {
  547. LPTOOLTIPTEXTW tt = (LPTOOLTIPTEXTW)lParam;
  548. RECT r = { 0 };
  549. if ( lvh.iSubItem )
  550. ListView_GetSubItemRect( hwnd, lvh.iItem, lvh.iSubItem, LVIR_BOUNDS, &r );
  551. else
  552. {
  553. ListView_GetItemRect( hwnd, lvh.iItem, &r, LVIR_BOUNDS );
  554. r.right = r.left + ListView_GetColumnWidth( hwnd, 1 );
  555. }
  556. MapWindowPoints( hwnd, HWND_DESKTOP, (LPPOINT)&r, 2 );
  557. SetWindowPos( tt->hdr.hwndFrom, HWND_TOPMOST, r.right, r.top + 2, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE );
  558. return 1;
  559. }
  560. }
  561. break;
  562. case TTN_NEEDTEXTW:
  563. {
  564. LVHITTESTINFO lvh = { 0 };
  565. GetCursorPos( &lvh.pt );
  566. ScreenToClient( hwnd, &lvh.pt );
  567. ListView_SubItemHitTest( hwnd, &lvh );
  568. static wchar_t tt_buf1[ 256 ] = { L"" };
  569. if ( cloud_avail && lvh.iItem != -1 && lvh.iSubItem == 1 )
  570. {
  571. LPNMTTDISPINFO lpnmtdi = (LPNMTTDISPINFO)lParam;
  572. if ( last_item1 == lvh.iItem )
  573. {
  574. lpnmtdi->lpszText = tt_buf1;
  575. return 0;
  576. }
  577. if ( lvh.iItem < 0 || lvh.iItem >= (int)playlistGUIDs.size() )
  578. return 0;
  579. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  580. PlaylistInfo info( playlistGUIDs[ lvh.iItem ] );
  581. if ( info.Valid() )
  582. {
  583. WASABI_API_LNGSTRINGW_BUF( ( !info.GetCloud() ? IDS_UPLOAD_TO_CLOUD : IDS_AVAILABLE_IN_CLOUD ), tt_buf1, ARRAYSIZE( tt_buf1 ) );
  584. }
  585. else
  586. {
  587. WASABI_API_LNGSTRINGW_BUF( IDS_UPLOAD_TO_CLOUD, tt_buf1, ARRAYSIZE( tt_buf1 ) );
  588. }
  589. last_item1 = lvh.iItem;
  590. lpnmtdi->lpszText = tt_buf1;
  591. // bit of a fiddle but it allows for multi-line tooltips
  592. //SendMessage(l->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 0);
  593. }
  594. else
  595. return CallWindowProcW( (WNDPROC)GetPropW( hwnd, L"cloud_list_proc" ), hwnd, uMsg, wParam, lParam );
  596. }
  597. return 0;
  598. }
  599. }
  600. return CallWindowProcW( (WNDPROC)GetPropW( hwnd, L"cloud_list_proc" ), hwnd, uMsg, wParam, lParam );
  601. }
  602. static void playlists_InitDialog( HWND hwndDlg )
  603. {
  604. HACCEL accel = WASABI_API_LOADACCELERATORSW( IDR_VIEW_PLS_ACCELERATORS );
  605. if ( accel )
  606. WASABI_API_APP->app_addAccelerators( hwndDlg, &accel, 1, TRANSLATE_MODE_CHILD );
  607. if ( !view.play )
  608. SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view );
  609. opened = true;
  610. loaded = false;
  611. cloud_avail = playlists_CloudAvailable();
  612. groupBtn = g_config->ReadInt( L"groupbtn", 1 );
  613. enqueuedef = ( g_config->ReadInt( L"enqueuedef", 0 ) == 1 );
  614. // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
  615. // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
  616. pluginMessage l_plugin_message = { ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwndDlg, (INT_PTR)MAKELONG( IDC_CUSTOM, IDC_ENQUEUE ), (INT_PTR)L"ml_playlists_root" };
  617. wchar_t *pszTextW = (wchar_t *)SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&l_plugin_message );
  618. if ( pszTextW && pszTextW[ 0 ] != 0 )
  619. {
  620. // set this to be a bit different so we can just use one button and not the
  621. // mixable one as well (leaving that to prevent messing with the resources)
  622. customAllowed = TRUE;
  623. SetDlgItemTextW( hwndDlg, IDC_CUSTOM, pszTextW );
  624. }
  625. else
  626. customAllowed = FALSE;
  627. /* skin dialog */
  628. MLSKINWINDOW skinWindow = { 0 };
  629. skinWindow.skinType = SKINNEDWND_TYPE_DIALOG;
  630. skinWindow.style = SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT;
  631. skinWindow.hwndToSkin = hwndDlg;
  632. MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
  633. /* skin listview */
  634. HWND hwndList = skinWindow.hwndToSkin = GetDlgItem( hwndDlg, IDC_PLAYLIST_LIST );
  635. skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
  636. skinWindow.style = SWLVS_FULLROWSELECT | SWLVS_DOUBLEBUFFER | SWLVS_ALTERNATEITEMS | SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
  637. MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
  638. MLSkinnedScrollWnd_ShowHorzBar( hwndList, FALSE );
  639. /* skin buttons */
  640. skinWindow.skinType = SKINNEDWND_TYPE_BUTTON;
  641. skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | ( groupBtn ? SWBS_SPLITBUTTON : 0 );
  642. const int buttonidz[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM };
  643. for ( size_t i = 0; i != sizeof( buttonidz ) / sizeof( buttonidz[ 0 ] ); i++ )
  644. {
  645. skinWindow.hwndToSkin = GetDlgItem( hwndDlg, buttonidz[ i ] );
  646. if ( IsWindow( skinWindow.hwndToSkin ) )
  647. MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
  648. }
  649. /* skin buttons */
  650. skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS;
  651. const int buttonids[] = { IDC_VIEWLIST, IDC_CREATENEWPL, IDC_SAVE };
  652. for ( size_t i = 0; i != sizeof( buttonids ) / sizeof( buttonids[ 0 ] ); i++ )
  653. {
  654. skinWindow.hwndToSkin = GetDlgItem( hwndDlg, buttonids[ i ] );
  655. if ( IsWindow( skinWindow.hwndToSkin ) )
  656. MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
  657. }
  658. /* skin dropdown buttons */
  659. skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWBS_DROPDOWNBUTTON;
  660. skinWindow.hwndToSkin = GetDlgItem( hwndDlg, IDC_IMPORT );
  661. MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
  662. HIMAGELIST imageList = ImageList_Create( 15, 15, ILC_COLOR24, 3, 0 );
  663. if ( imageList != NULL )
  664. {
  665. HIMAGELIST prevList = (HIMAGELIST)SNDMSG( hwndList, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)imageList );
  666. if ( prevList != NULL )
  667. ImageList_Destroy( prevList );
  668. }
  669. m_playlistslist.setwnd( hwndList );
  670. m_playlistslist.AddCol( WASABI_API_LNGSTRINGW( IDS_PLAYLIST_TITLE ), 400 );
  671. int width = 27;
  672. MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width );
  673. m_playlistslist.AddCol( L"", ( cloud_avail ? width : 0 ) );
  674. m_playlistslist.AddCol( WASABI_API_LNGSTRINGW( IDS_ITEMS ), 50 );
  675. m_playlistslist.AutoColumnWidth( 3 );
  676. m_playlistslist.JustifyColumn( 3, LVCFMT_RIGHT );
  677. m_playlistslist.AddCol( WASABI_API_LNGSTRINGW( IDS_TIME ), 75 );
  678. m_playlistslist.AutoSizeColumn( 4 );
  679. m_playlistslist.JustifyColumn( 4, LVCFMT_RIGHT );
  680. MLSkinnedHeader_SetCloudColumn( ListView_GetHeader( hwndList ), ( cloud_avail ? 1 : -1 ) );
  681. if ( !GetPropW( hwndList, L"cloud_list_proc" ) )
  682. SetPropW( hwndList, L"cloud_list_proc", (HANDLE)SetWindowLongPtrW( hwndList, GWLP_WNDPROC, (LONG_PTR)playlists_cloud_listview ) );
  683. playlist_UpdateButtonText( hwndDlg, enqueuedef == 1 );
  684. playlists_ManageButtons( hwndDlg );
  685. RefreshPlaylistsList();
  686. SetWindowRedraw( m_playlistslist.getwnd(), FALSE );
  687. }
  688. void playlists_Destroy( HWND hwndDlg )
  689. {
  690. opened = false;
  691. WASABI_API_APP->app_removeAccelerators( hwndDlg );
  692. m_playlistslist.setwnd( NULL );
  693. playlistGUIDs.clear();
  694. }
  695. BOOL playlists_GetDisplayInfo( NMLVDISPINFO *lpdi )
  696. {
  697. size_t item = lpdi->item.iItem;
  698. if ( item < 0 || item >= playlistGUIDs.size() )
  699. return 0;
  700. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  701. PlaylistInfo info( playlistGUIDs[ item ] );
  702. if ( lpdi->item.mask & LVIF_TEXT && info.Valid() )
  703. {
  704. switch ( lpdi->item.iSubItem )
  705. {
  706. case 0:
  707. {
  708. // TODO: this is going to be slow, we should investigate caching the title
  709. lstrcpyn( lpdi->item.pszText, info.GetName(), lpdi->item.cchTextMax );
  710. break;
  711. }
  712. case 1:
  713. {
  714. StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d", info.GetCloud() );
  715. break;
  716. }
  717. case 2:
  718. {
  719. StringCchPrintf( lpdi->item.pszText, lpdi->item.cchTextMax, L"%d", info.GetSize() );
  720. break;
  721. }
  722. case 3:
  723. {
  724. wchar_t str[ 64 ] = { 0 };
  725. FormatLength( str, info.GetLength(), 64 );
  726. lstrcpyn( lpdi->item.pszText, str, lpdi->item.cchTextMax );
  727. }
  728. break;
  729. }
  730. }
  731. return 0;
  732. }
  733. void Playlists_RenameSelected( HWND hwndDlg )
  734. {
  735. // TOOD: loop through selections
  736. int s = m_playlistslist.GetSelectionMark();
  737. if ( s != -1 )
  738. RenamePlaylist( playlistGUIDs[ s ], hwndDlg );
  739. }
  740. BOOL playlists_OnCustomDraw( HWND hwndDlg, NMLVCUSTOMDRAW *plvcd, LRESULT *pResult )
  741. {
  742. static BOOL bDrawFocus;
  743. static RECT rcView;
  744. static CLOUDCOLUMNPAINT cloudColumnPaint;
  745. *pResult = CDRF_DODEFAULT;
  746. switch ( plvcd->nmcd.dwDrawStage )
  747. {
  748. case CDDS_PREPAINT:
  749. *pResult |= CDRF_NOTIFYITEMDRAW;
  750. CopyRect( &rcView, &plvcd->nmcd.rc );
  751. cloudColumnPaint.hwndList = plvcd->nmcd.hdr.hwndFrom;
  752. cloudColumnPaint.hdc = plvcd->nmcd.hdc;
  753. cloudColumnPaint.prcView = &rcView;
  754. return TRUE;
  755. case CDDS_ITEMPREPAINT:
  756. *pResult |= CDRF_NOTIFYSUBITEMDRAW;
  757. bDrawFocus = ( CDIS_FOCUS & plvcd->nmcd.uItemState );
  758. if ( bDrawFocus )
  759. {
  760. plvcd->nmcd.uItemState &= ~CDIS_FOCUS;
  761. *pResult |= CDRF_NOTIFYPOSTPAINT;
  762. }
  763. return TRUE;
  764. case CDDS_ITEMPOSTPAINT:
  765. if ( bDrawFocus )
  766. {
  767. RECT rc;
  768. rc.left = LVIR_BOUNDS;
  769. SendMessageW( plvcd->nmcd.hdr.hwndFrom, LVM_GETITEMRECT, plvcd->nmcd.dwItemSpec, (LPARAM)&rc );
  770. rc.left += 3;
  771. DrawFocusRect( plvcd->nmcd.hdc, &rc );
  772. plvcd->nmcd.uItemState |= CDIS_FOCUS;
  773. bDrawFocus = FALSE;
  774. }
  775. *pResult = CDRF_SKIPDEFAULT;
  776. return TRUE;
  777. case( CDDS_SUBITEM | CDDS_ITEMPREPAINT ):
  778. // TODO need to have a map between column ids so we do this correctly
  779. if ( plvcd->iSubItem == 1 )
  780. {
  781. if ( 0 == plvcd->iSubItem && 0 == plvcd->nmcd.rc.right || playlistGUIDs.empty() )
  782. break;
  783. cloudColumnPaint.iItem = plvcd->nmcd.dwItemSpec;
  784. cloudColumnPaint.iSubItem = plvcd->iSubItem;
  785. int cloud_icon = 0;
  786. size_t item = plvcd->nmcd.dwItemSpec;
  787. if ( item >= 0 || item < playlistGUIDs.size() )
  788. {
  789. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  790. PlaylistInfo info( playlistGUIDs[ item ] );
  791. if ( info.Valid() )
  792. cloud_icon = info.GetCloud();
  793. }
  794. // TODO have this show an appropriate cloud icon for the playlist
  795. // currently all we have is cloud or nothing as we'll only
  796. // have files locally for this for the moment (need todo!!!)
  797. cloudColumnPaint.value = cloud_icon;
  798. cloudColumnPaint.prcItem = &plvcd->nmcd.rc;
  799. cloudColumnPaint.rgbBk = plvcd->clrTextBk;
  800. cloudColumnPaint.rgbFg = plvcd->clrText;
  801. if ( MLCloudColumn_Paint( plugin.hwndLibraryParent, &cloudColumnPaint ) )
  802. {
  803. *pResult = CDRF_SKIPDEFAULT;
  804. return TRUE;
  805. }
  806. }
  807. break;
  808. }
  809. return FALSE;
  810. }
  811. BOOL playlists_Notify( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
  812. {
  813. LPNMHDR l = (LPNMHDR)lParam;
  814. if ( l->idFrom == IDC_PLAYLIST_LIST )
  815. {
  816. switch ( l->code )
  817. {
  818. case LVN_ITEMCHANGED:
  819. playlists_ManageButtons( hwndDlg );
  820. break;
  821. case LVN_BEGINDRAG:
  822. root_is_drag_and_dropping = 1; SetCapture( hwndDlg );
  823. break;
  824. case LVN_GETDISPINFO:
  825. return playlists_GetDisplayInfo( (NMLVDISPINFO *)lParam );
  826. case NM_DBLCLK:
  827. playlists_Play( enqueuedef == 1 );
  828. break;
  829. case NM_CLICK:
  830. {
  831. LPNMITEMACTIVATE pnmitem = (LPNMITEMACTIVATE)lParam;
  832. if ( cloud_avail && pnmitem->iItem != -1 && pnmitem->iSubItem == 1 )
  833. {
  834. RECT itemRect = { 0 };
  835. if ( pnmitem->iSubItem )
  836. ListView_GetSubItemRect( pnmitem->hdr.hwndFrom, pnmitem->iItem, pnmitem->iSubItem, LVIR_BOUNDS, &itemRect );
  837. else
  838. {
  839. ListView_GetItemRect( pnmitem->hdr.hwndFrom, pnmitem->iItem, &itemRect, LVIR_BOUNDS );
  840. itemRect.right = itemRect.left + ListView_GetColumnWidth( pnmitem->hdr.hwndFrom, pnmitem->iSubItem );
  841. }
  842. MapWindowPoints( pnmitem->hdr.hwndFrom, HWND_DESKTOP, (POINT *)&itemRect, 2 );
  843. size_t item = pnmitem->iItem;
  844. if ( item < 0 || item >= playlistGUIDs.size() )
  845. return 0;
  846. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  847. PlaylistInfo info( playlistGUIDs[ item ] );
  848. HMENU cloud_menu = (HMENU)0x666;
  849. ReferenceCountedNXString uid;
  850. NXStringCreateWithFormatting( &uid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
  851. (int)info.playlist_guid.Data1, (int)info.playlist_guid.Data2,
  852. (int)info.playlist_guid.Data3, (int)info.playlist_guid.Data4[ 0 ],
  853. (int)info.playlist_guid.Data4[ 1 ], (int)info.playlist_guid.Data4[ 2 ],
  854. (int)info.playlist_guid.Data4[ 3 ], (int)info.playlist_guid.Data4[ 4 ],
  855. (int)info.playlist_guid.Data4[ 5 ], (int)info.playlist_guid.Data4[ 6 ],
  856. (int)info.playlist_guid.Data4[ 7 ] );
  857. WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)uid->string, (intptr_t)&cloud_menu );
  858. if ( cloud_menu )
  859. {
  860. int r = Menu_TrackPopup( plugin.hwndLibraryParent, cloud_menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY, itemRect.right, itemRect.top, pnmitem->hdr.hwndFrom, NULL );
  861. if ( r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER ) // deals with cloud specific menus
  862. {
  863. // 0 = no change
  864. // 1 = adding to cloud
  865. // 2 = added locally
  866. // 4 = removed
  867. int mode = -(int)info.GetIndex();
  868. WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode );
  869. if ( mode > 0 )
  870. {
  871. info.SetCloud( ( mode == 1 ? 1 : 0 ) );
  872. AGAVE_API_PLAYLISTS->Flush();
  873. UpdatePlaylists();
  874. last_item1 = -1;
  875. }
  876. }
  877. DestroyMenu( cloud_menu );
  878. }
  879. }
  880. }
  881. break;
  882. case LVN_KEYDOWN:
  883. {
  884. LPNMLVKEYDOWN pnkd = (LPNMLVKEYDOWN)lParam;
  885. switch ( pnkd->wVKey )
  886. {
  887. case 0x2E: //Delete
  888. playlists_Delete( hwndDlg );
  889. break;
  890. case VK_F2:
  891. Playlists_RenameSelected( hwndDlg );
  892. SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)l->hwndFrom, (LPARAM)TRUE );
  893. break;
  894. case 'A':
  895. if ( !( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) && ( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) )
  896. m_playlistslist.SelectAll();
  897. break;
  898. }
  899. }
  900. break;
  901. case NM_CUSTOMDRAW:
  902. {
  903. LRESULT result = 0;
  904. if ( cloud_avail && playlists_OnCustomDraw( hwndDlg, (NMLVCUSTOMDRAW *)lParam, &result ) )
  905. {
  906. SetWindowLongPtrW( hwndDlg, DWLP_MSGRESULT, (LONG_PTR)result );
  907. return 1;
  908. }
  909. break;
  910. }
  911. }
  912. }
  913. switch ( l->code )
  914. {
  915. case HDN_ITEMCHANGING:
  916. {
  917. LPNMHEADERW phdr = (LPNMHEADERW)lParam;
  918. if ( phdr->pitem && ( HDI_WIDTH & phdr->pitem->mask ) && phdr->iItem == 1 )
  919. {
  920. if ( !cloud_avail )
  921. phdr->pitem->cxy = 0;
  922. else
  923. {
  924. INT width = phdr->pitem->cxy;
  925. if ( MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width ) )
  926. phdr->pitem->cxy = width;
  927. }
  928. }
  929. break;
  930. }
  931. }
  932. return 0;
  933. }
  934. void playlists_MouseMove( HWND hwndDlg, LPARAM lParam )
  935. {
  936. if ( root_is_drag_and_dropping && GetCapture() == hwndDlg )
  937. {
  938. POINT p = { GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) };
  939. ClientToScreen( hwndDlg, &p );
  940. mlDropItemStruct m = { 0 };
  941. m.type = ML_TYPE_FILENAMES;
  942. m.p = p;
  943. pluginHandleIpcMessage( ML_IPC_HANDLEDRAG, (WPARAM)&m );
  944. }
  945. }
  946. void playlists_LeftButtonUp( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
  947. {
  948. if ( root_is_drag_and_dropping && GetCapture() == hwndDlg )
  949. {
  950. ReleaseCapture();
  951. POINT p = { GET_X_LPARAM( lParam ),GET_Y_LPARAM( lParam ) };
  952. ClientToScreen( hwndDlg, &p );
  953. mlDropItemStruct m = { 0 };
  954. m.type = ML_TYPE_FILENAMES;
  955. m.p = p;
  956. pluginHandleIpcMessage( ML_IPC_HANDLEDRAG, (WPARAM)&m );
  957. if ( m.result > 0 )
  958. {
  959. //std::vector<char> data;
  960. std::string data;
  961. for ( size_t i = 0; i < playlistGUIDs.size(); i++ )
  962. {
  963. if ( !m_playlistslist.GetSelected( i ) )
  964. continue;
  965. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  966. PlaylistInfo info( playlistGUIDs[ i ] );
  967. wchar_t str[ MAX_PATH ] = { 0 };
  968. if ( PathIsFileSpecW( info.GetFilename() ) )
  969. PathCombineW( str, g_path, info.GetFilename() );
  970. else
  971. lstrcpynW( str, info.GetFilename(), MAX_PATH );
  972. AutoChar charStr( str );
  973. // HAKAN: why (len + 1) ?
  974. //data.append(charStr, lstrlenA( charStr + 1));
  975. data.append(charStr, lstrlenA(charStr));
  976. }
  977. // HAKAN: No need to add trailing zero
  978. //data.push_back( 0 );
  979. m.flags = 0;
  980. m.result = 0;
  981. m.data = (void *)data.c_str();
  982. pluginHandleIpcMessage( ML_IPC_HANDLEDROP, (WPARAM)&m );
  983. RefreshPlaylistsList();
  984. }
  985. root_is_drag_and_dropping = 0;
  986. }
  987. }
  988. enum
  989. {
  990. BPM_ECHO_WM_COMMAND = 0x1, // send WM_COMMAND and return value
  991. BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
  992. };
  993. static void playlists_PlayEnqueue( HWND hwndDlg, HWND from, UINT idFrom )
  994. {
  995. HMENU listMenu = GetSubMenu( g_context_menus3, 0 );
  996. int count = GetMenuItemCount( listMenu );
  997. if ( count > 2 )
  998. {
  999. for ( int i = 2; i < count; i++ )
  1000. DeleteMenu( listMenu, 2, MF_BYPOSITION );
  1001. }
  1002. UINT menuStatus;
  1003. if ( m_playlistslist.GetNextSelected( -1 ) == -1 )
  1004. menuStatus = MF_BYCOMMAND | MF_GRAYED;
  1005. else
  1006. menuStatus = MF_BYCOMMAND | MF_ENABLED;
  1007. EnableMenuItem( listMenu, IDC_PLAYLIST_INVERT_SELECTION, menuStatus );
  1008. if ( m_playlistslist.GetCount() > 0 )
  1009. menuStatus = MF_BYCOMMAND | MF_ENABLED;
  1010. else
  1011. menuStatus = MF_BYCOMMAND | MF_GRAYED;
  1012. EnableMenuItem( listMenu, IDC_PLAYLIST_SELECT_ALL, menuStatus );
  1013. playlist_ButtonPopupMenu( hwndDlg, idFrom, listMenu, BPM_WM_COMMAND );
  1014. }
  1015. void playlists_Command( HWND hwndDlg, WPARAM wParam, LPARAM lParam )
  1016. {
  1017. switch ( LOWORD( wParam ) )
  1018. {
  1019. case IDC_VIEWLIST:
  1020. playlists_ViewList();
  1021. break;
  1022. case IDC_IMPORT:
  1023. playlists_Import( hwndDlg, lParam );
  1024. break;
  1025. case IDC_CREATENEWPL:
  1026. case IDC_NEWPLAYLIST:
  1027. playlists_Add( hwndDlg );
  1028. break;
  1029. case IDC_PLAY:
  1030. case IDC_ENQUEUE:
  1031. case IDC_CUSTOM:
  1032. {
  1033. if ( HIWORD( wParam ) == MLBN_DROPDOWN )
  1034. {
  1035. playlists_PlayEnqueue( hwndDlg, (HWND)lParam, LOWORD( wParam ) );
  1036. }
  1037. else
  1038. {
  1039. int action;
  1040. if ( LOWORD( wParam ) == IDC_PLAY )
  1041. action = ( HIWORD( wParam ) == 1 ) ? enqueuedef == 1 : 0;
  1042. else if ( LOWORD( wParam ) == IDC_ENQUEUE )
  1043. action = ( HIWORD( wParam ) == 1 ) ? ( enqueuedef != 1 ) : 1;
  1044. else
  1045. // so custom can work with the menu item part
  1046. break;
  1047. playlists_Play( action );
  1048. }
  1049. break;
  1050. }
  1051. case IDC_SAVE:
  1052. playlists_Save( hwndDlg );
  1053. break;
  1054. case IDC_DELETE:
  1055. playlists_Delete( hwndDlg );
  1056. break;
  1057. case IDC_RENAME:
  1058. Playlists_RenameSelected( hwndDlg );
  1059. break;
  1060. }
  1061. }
  1062. void playlists_DropFiles( HDROP hDrop )
  1063. {
  1064. wchar_t l_playlist_filename[ 2048 ] = { 0 };
  1065. int y = DragQueryFileW( hDrop, 0xffffffff, l_playlist_filename, 2048 );
  1066. for ( int x = 0; x < y; x++ )
  1067. {
  1068. Playlist currentPlaylist2;
  1069. DragQueryFileW( hDrop, x, l_playlist_filename, 2048 );
  1070. // make sure that we only add valid playlists and not normal files
  1071. if ( AGAVE_API_PLAYLISTMANAGER->CanLoad( l_playlist_filename ) )
  1072. {
  1073. ImportPlaylist( l_playlist_filename );
  1074. }
  1075. }
  1076. }
  1077. void playlists_Sort( size_t sort_type )
  1078. {
  1079. int cur_sel = mediaLibrary.GetSelectedTreeItem();
  1080. GUID cur_guid = tree_to_guid_map[ cur_sel ];
  1081. // keep the old tree ids before sorting so we can then re-map
  1082. // without having to remove and re-add all of the tree items
  1083. std::vector<int> tree_ids;
  1084. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  1085. size_t count = AGAVE_API_PLAYLISTS->GetCount();
  1086. for ( size_t i = 0; i != count; i++ )
  1087. {
  1088. PlaylistInfo info( i );
  1089. if ( info.Valid() )
  1090. tree_ids.push_back( info.treeId );
  1091. }
  1092. if ( AGAVE_API_PLAYLISTS->Sort( sort_type ) )
  1093. {
  1094. for ( size_t i = 0; i != count; i++ )
  1095. {
  1096. PlaylistInfo info( i );
  1097. UpdateTree( info, tree_ids[ i ] );
  1098. }
  1099. for ( size_t i = 0; i != count; i++ )
  1100. {
  1101. PlaylistInfo info( i );
  1102. if ( cur_guid == info.playlist_guid )
  1103. {
  1104. mediaLibrary.SelectTreeItem( info.treeId );
  1105. }
  1106. }
  1107. RefreshPlaylistsList();
  1108. }
  1109. }
  1110. void playlists_ContextMenu( HWND hwndDlg, HWND from, int x, int y )
  1111. {
  1112. if ( from != m_playlistslist.getwnd() )
  1113. return;
  1114. POINT pt = { x,y };
  1115. if ( x == -1 || y == -1 ) // x and y are -1 if the user invoked a shift-f10 popup menu
  1116. {
  1117. RECT channelRect = { 0 };
  1118. int selected = m_playlistslist.GetNextSelected();
  1119. if ( selected != -1 ) // if something is selected we'll drop the menu from there
  1120. {
  1121. m_playlistslist.GetItemRect( selected, &channelRect );
  1122. ClientToScreen( hwndDlg, (POINT *)&channelRect );
  1123. }
  1124. else // otherwise we'll drop it from the top-left corner of the listview, adjusting for the header location
  1125. {
  1126. GetWindowRect( hwndDlg, &channelRect );
  1127. HWND hHeader = (HWND)SNDMSG( from, LVM_GETHEADER, 0, 0L );
  1128. RECT headerRect;
  1129. if ( ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) && GetWindowRect( hHeader, &headerRect ) )
  1130. {
  1131. channelRect.top += ( headerRect.bottom - headerRect.top );
  1132. }
  1133. }
  1134. x = channelRect.left;
  1135. y = channelRect.top;
  1136. }
  1137. HWND hHeader = (HWND)SNDMSG( from, LVM_GETHEADER, 0, 0L );
  1138. RECT headerRect;
  1139. if ( 0 == ( WS_VISIBLE & GetWindowLongPtr( hHeader, GWL_STYLE ) ) || FALSE == GetWindowRect( hHeader, &headerRect ) )
  1140. SetRectEmpty( &headerRect );
  1141. if ( FALSE != PtInRect( &headerRect, pt ) )
  1142. return;
  1143. HMENU menu = GetSubMenu( g_context_menus, 0 );
  1144. sendTo.AddHere( hwndDlg, GetSubMenu( menu, 2 ), ML_TYPE_FILENAMES, 1, ( ML_TYPE_PLAYLIST + 1 ) );
  1145. HMENU cloud_hmenu = (HMENU)0x666;
  1146. size_t index = 0, i = 0;
  1147. if ( playlists_CloudAvailable() )
  1148. {
  1149. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  1150. for ( ; i < playlistGUIDs.size(); i++ )
  1151. {
  1152. if ( !m_playlistslist.GetSelected( i ) )
  1153. continue;
  1154. PlaylistInfo info( playlistGUIDs[ i ] );
  1155. ReferenceCountedNXString uid;
  1156. NXStringCreateWithFormatting( &uid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
  1157. (int)info.playlist_guid.Data1, (int)info.playlist_guid.Data2,
  1158. (int)info.playlist_guid.Data3, (int)info.playlist_guid.Data4[ 0 ],
  1159. (int)info.playlist_guid.Data4[ 1 ], (int)info.playlist_guid.Data4[ 2 ],
  1160. (int)info.playlist_guid.Data4[ 3 ], (int)info.playlist_guid.Data4[ 4 ],
  1161. (int)info.playlist_guid.Data4[ 5 ], (int)info.playlist_guid.Data4[ 6 ],
  1162. (int)info.playlist_guid.Data4[ 7 ] );
  1163. index = info.GetIndex();
  1164. WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)uid->string, (intptr_t)&cloud_hmenu );
  1165. if ( cloud_hmenu && cloud_hmenu != (HMENU)0x666 )
  1166. {
  1167. MENUITEMINFOW m = { sizeof( m ), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0 };
  1168. m.wID = CLOUD_SOURCE_MENUS - 1;
  1169. InsertMenuItemW( menu, 3, TRUE, &m );
  1170. wchar_t a[ 100 ] = { 0 };
  1171. m.fType = MFT_STRING;
  1172. m.dwTypeData = WASABI_API_LNGSTRINGW_BUF( IDS_CLOUD_SOURCES, a, 100 );
  1173. m.wID = CLOUD_SOURCE_MENUS;
  1174. m.hSubMenu = cloud_hmenu;
  1175. InsertMenuItemW( menu, 4, TRUE, &m );
  1176. }
  1177. break;
  1178. }
  1179. }
  1180. UpdateMenuItems( hwndDlg, menu );
  1181. UINT menuStatus;
  1182. if ( m_playlistslist.GetNextSelected( -1 ) == -1 )
  1183. {
  1184. menuStatus = MF_BYCOMMAND | MF_GRAYED;
  1185. EnableMenuItem( menu, 2, MF_BYPOSITION | MF_GRAYED );
  1186. EnableMenuItem( menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND | MF_GRAYED );
  1187. }
  1188. else
  1189. {
  1190. menuStatus = MF_BYCOMMAND | MF_ENABLED;
  1191. EnableMenuItem( menu, 2, MF_BYPOSITION | MF_ENABLED );
  1192. EnableMenuItem( menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND | MF_ENABLED );
  1193. }
  1194. EnableMenuItem( menu, IDC_PLAY, menuStatus );
  1195. EnableMenuItem( menu, IDC_ENQUEUE, menuStatus );
  1196. EnableMenuItem( menu, IDC_DELETE, menuStatus );
  1197. EnableMenuItem( menu, ID_QUERYMENU_ADDNEWQUERY, menuStatus );
  1198. EnableMenuItem( menu, IDC_RENAME, menuStatus );
  1199. EnableMenuItem( menu, IDC_ENQUEUE, menuStatus );
  1200. EnableMenuItem( menu, IDC_VIEWLIST, menuStatus );
  1201. int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, x, y, hwndDlg, NULL );
  1202. switch ( r )
  1203. {
  1204. case IDC_VIEWLIST:
  1205. playlists_ViewList();
  1206. break;
  1207. case IDC_NEWPLAYLIST:
  1208. playlists_Add( hwndDlg );
  1209. break;
  1210. case IDC_PLAY:
  1211. playlists_Play( 0 );
  1212. break;
  1213. case IDC_ENQUEUE:
  1214. playlists_Play( 1 );
  1215. break;
  1216. case IDC_DELETE:
  1217. playlists_Delete( hwndDlg );
  1218. SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
  1219. break;
  1220. case ID_QUERYMENU_ADDNEWQUERY:
  1221. playlists_Add( hwndDlg );
  1222. SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
  1223. break;
  1224. case IDC_RENAME:
  1225. Playlists_RenameSelected( hwndDlg );
  1226. SendMessage( hwndDlg, WM_NEXTDLGCTL, (WPARAM)from, (LPARAM)TRUE );
  1227. break;
  1228. default:
  1229. if ( sendTo.WasClicked( r ) )
  1230. {
  1231. bool playlist_type_worked = true;
  1232. int numPlaylists = m_playlistslist.GetSelectedCount();
  1233. if ( !numPlaylists )
  1234. break;
  1235. mlPlaylist **playlists = new mlPlaylist * [ numPlaylists + 1 ];
  1236. playlists[ numPlaylists ] = 0; // null terminate
  1237. // TODO: m_playlistslist.GetNextSelected()
  1238. for ( int i = 0, pl = 0; i < m_playlistslist.GetCount(); i++ )
  1239. {
  1240. if ( !m_playlistslist.GetSelected( i ) )
  1241. continue;
  1242. playlists[ pl ] = new mlPlaylist;
  1243. memset( playlists[ pl ], 0, sizeof( mlPlaylist ) );
  1244. { // scope for lock
  1245. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  1246. PlaylistInfo info( playlistGUIDs[ i ] );
  1247. playlists[ pl ]->filename = _wcsdup( info.GetFilename() );
  1248. playlists[ pl ]->length = info.GetLength();
  1249. playlists[ pl ]->numItems = info.GetSize();
  1250. playlists[ pl ]->title = _wcsdup( info.GetName() );
  1251. }
  1252. pl++;
  1253. }
  1254. if ( sendTo.SendPlaylists( playlists ) != 1 )
  1255. {
  1256. for ( int i = 0; i < numPlaylists; i++ )
  1257. {
  1258. if ( sendTo.SendPlaylist( playlists[ i ] ) != 1 )
  1259. {
  1260. playlist_type_worked = false;
  1261. break;
  1262. }
  1263. }
  1264. }
  1265. for ( int i = 0; i < numPlaylists; i++ )
  1266. {
  1267. free( (void *)playlists[ i ]->filename );
  1268. free( (void *)playlists[ i ]->title );
  1269. delete playlists[ i ];
  1270. }
  1271. delete[] playlists;
  1272. if ( !playlist_type_worked )
  1273. {
  1274. //std::vector<wchar_t> data;
  1275. std::wstring data;
  1276. { // scope for lock
  1277. AutoLockT<api_playlists> lock( AGAVE_API_PLAYLISTS );
  1278. for ( size_t ipl = 0; ipl < playlistGUIDs.size(); ipl++ )
  1279. {
  1280. if ( !m_playlistslist.GetSelected( ipl ) )
  1281. continue;
  1282. PlaylistInfo info( playlistGUIDs[ ipl ] );
  1283. wchar_t str[ MAX_PATH ] = { 0 };
  1284. if ( PathIsFileSpecW( info.GetFilename() ) )
  1285. PathCombineW( str, g_path, info.GetFilename() );
  1286. else
  1287. lstrcpynW( str, info.GetFilename(), MAX_PATH );
  1288. // HAKAN: why (len + 1) ?
  1289. //data.append( str, lstrlen(str) + 1);
  1290. data.append(str, lstrlen(str));
  1291. }
  1292. }
  1293. // HAKAN: No need to add trailing zero
  1294. //data.push_back( 0 );
  1295. //
  1296. // build my data.
  1297. sendTo.SendFilenames( data.c_str() );
  1298. }
  1299. }
  1300. else
  1301. {
  1302. if ( r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER ) // deals with cloud specific menus
  1303. {
  1304. // 0 = no change
  1305. // 1 = adding to cloud
  1306. // 2 = added locally
  1307. // 4 = removed
  1308. int mode = -(int)index;
  1309. WASABI_API_SYSCB->syscb_issueCallback( api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode );
  1310. if ( mode > 0 )
  1311. {
  1312. PlaylistInfo info( playlistGUIDs[ i ] );
  1313. info.SetCloud( ( mode == 1 ? 1 : 0 ) );
  1314. AGAVE_API_PLAYLISTS->Flush();
  1315. UpdatePlaylists();
  1316. last_item1 = -1;
  1317. }
  1318. }
  1319. }
  1320. break;
  1321. }
  1322. sendTo.Cleanup();
  1323. if ( cloud_hmenu && cloud_hmenu != (HMENU)0x666 )
  1324. {
  1325. DeleteMenu( menu, CLOUD_SOURCE_MENUS - 1, MF_BYCOMMAND );
  1326. DeleteMenu( menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND );
  1327. DestroyMenu( cloud_hmenu );
  1328. }
  1329. }
  1330. static HRGN g_rgnUpdate = NULL;
  1331. static int offsetX = 0;
  1332. static int offsetY = 0;
  1333. typedef struct _LAYOUT
  1334. {
  1335. INT id;
  1336. HWND hwnd;
  1337. INT x;
  1338. INT y;
  1339. INT cx;
  1340. INT cy;
  1341. DWORD flags;
  1342. HRGN rgn;
  1343. }
  1344. LAYOUT, PLAYOUT;
  1345. #define SETLAYOUTPOS(_layout, _x, _y, _cx, _cy) { _layout->x=_x; _layout->y=_y;_layout->cx=_cx;_layout->cy=_cy;_layout->rgn=NULL; }
  1346. #define SETLAYOUTFLAGS(_layout, _r) \
  1347. { \
  1348. BOOL fVis; \
  1349. fVis = (WS_VISIBLE & (LONG)GetWindowLongPtr(_layout->hwnd, GWL_STYLE)); \
  1350. if (_layout->x == _r.left && _layout->y == _r.top) _layout->flags |= SWP_NOMOVE; \
  1351. if (_layout->cx == (_r.right - _r.left) && _layout->cy == (_r.bottom - _r.top)) _layout->flags |= SWP_NOSIZE; \
  1352. if ((SWP_HIDEWINDOW & _layout->flags) && !fVis) _layout->flags &= ~SWP_HIDEWINDOW; \
  1353. if ((SWP_SHOWWINDOW & _layout->flags) && fVis) _layout->flags &= ~SWP_SHOWWINDOW; \
  1354. }
  1355. #define LAYOUTNEEEDUPDATE(_layout) ((SWP_NOMOVE | SWP_NOSIZE) != ((SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_SHOWWINDOW) & _layout->flags))
  1356. #define GROUP_MIN 0x1
  1357. #define GROUP_MAX 0x2
  1358. #define GROUP_STATUSBAR 0x1
  1359. #define GROUP_MAIN 0x2
  1360. static void LayoutWindows( HWND hwnd, BOOL fRedraw, BOOL fUpdateAll = FALSE )
  1361. {
  1362. static INT controls[] =
  1363. {
  1364. GROUP_STATUSBAR, IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_VIEWLIST, IDC_CREATENEWPL, IDC_IMPORT, IDC_SAVE,
  1365. GROUP_MAIN, IDC_PLAYLIST_LIST
  1366. };
  1367. INT index;
  1368. RECT rc, rg, ri;
  1369. LAYOUT layout[ sizeof( controls ) / sizeof( controls[ 0 ] ) ], *pl;
  1370. BOOL skipgroup;
  1371. HRGN rgn = NULL;
  1372. GetClientRect( hwnd, &rc );
  1373. if ( rc.right == rc.left || rc.bottom == rc.top )
  1374. return;
  1375. if ( rc.right > WASABI_API_APP->getScaleX( 4 ) )
  1376. rc.right -= WASABI_API_APP->getScaleX( 4 );
  1377. SetRect( &rg, rc.left, rc.top, rc.right, rc.top );
  1378. pl = layout;
  1379. skipgroup = FALSE;
  1380. InvalidateRect( hwnd, NULL, TRUE );
  1381. for ( index = 0; index < sizeof( controls ) / sizeof( *controls ); index++ )
  1382. {
  1383. if ( controls[ index ] >= GROUP_MIN && controls[ index ] <= GROUP_MAX ) // group id
  1384. {
  1385. skipgroup = FALSE;
  1386. switch ( controls[ index ] )
  1387. {
  1388. case GROUP_STATUSBAR:
  1389. {
  1390. wchar_t buffer[ 128 ] = { 0 };
  1391. GetDlgItemTextW( hwnd, IDC_PLAY, buffer, ARRAYSIZE( buffer ) );
  1392. LRESULT idealSize = MLSkinnedButton_GetIdealSize( GetDlgItem( hwnd, IDC_PLAY ), buffer );
  1393. SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ),
  1394. rc.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ),
  1395. rc.right, rc.bottom );
  1396. rc.bottom = rg.top - WASABI_API_APP->getScaleY( 3 );
  1397. break;
  1398. }
  1399. case GROUP_MAIN:
  1400. SetRect( &rg, rc.left + WASABI_API_APP->getScaleX( 1 ), rc.top, rc.right, rc.bottom );
  1401. break;
  1402. }
  1403. continue;
  1404. }
  1405. if ( skipgroup )
  1406. continue;
  1407. pl->id = controls[ index ];
  1408. pl->hwnd = GetDlgItem( hwnd, pl->id );
  1409. if ( !pl->hwnd )
  1410. continue;
  1411. GetWindowRect( pl->hwnd, &ri );
  1412. MapWindowPoints( HWND_DESKTOP, hwnd, (LPPOINT)&ri, 2 );
  1413. pl->flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOCOPYBITS;
  1414. switch ( pl->id )
  1415. {
  1416. case IDC_PLAY:
  1417. case IDC_ENQUEUE:
  1418. case IDC_CUSTOM:
  1419. case IDC_VIEWLIST:
  1420. case IDC_CREATENEWPL:
  1421. case IDC_IMPORT:
  1422. case IDC_SAVE:
  1423. if ( IDC_CUSTOM != pl->id || customAllowed )
  1424. {
  1425. if ( groupBtn && ( pl->id == IDC_PLAY ) && ( enqueuedef == 1 ) )
  1426. {
  1427. pl->flags |= SWP_HIDEWINDOW;
  1428. break;
  1429. }
  1430. if ( groupBtn && ( pl->id == IDC_ENQUEUE ) && ( enqueuedef != 1 ) )
  1431. {
  1432. pl->flags |= SWP_HIDEWINDOW;
  1433. break;
  1434. }
  1435. if ( groupBtn && ( pl->id == IDC_PLAY || pl->id == IDC_ENQUEUE ) && customAllowed )
  1436. {
  1437. pl->flags |= SWP_HIDEWINDOW;
  1438. break;
  1439. }
  1440. wchar_t buffer[ 128 ] = { 0 };
  1441. GetWindowText( pl->hwnd, buffer, ARRAYSIZE( buffer ) );
  1442. LRESULT idealSize = MLSkinnedButton_GetIdealSize( pl->hwnd, buffer );
  1443. LONG width = LOWORD( idealSize ) + ( pl->id != IDC_IMPORT ? WASABI_API_APP->getScaleX( 6 ) : 0 );
  1444. SETLAYOUTPOS( pl, rg.left, rg.bottom - WASABI_API_APP->getScaleY( HIWORD( idealSize ) ), width, WASABI_API_APP->getScaleY( HIWORD( idealSize ) ) );
  1445. pl->flags |= ( ( rg.right - rg.left ) > width ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
  1446. if ( SWP_SHOWWINDOW & pl->flags )
  1447. rg.left += ( pl->cx + WASABI_API_APP->getScaleX( 4 ) );
  1448. }
  1449. else
  1450. pl->flags |= SWP_HIDEWINDOW;
  1451. break;
  1452. case IDC_PLAYLIST_LIST:
  1453. pl->flags |= ( rg.top < rg.bottom ) ? SWP_SHOWWINDOW : SWP_HIDEWINDOW;
  1454. SETLAYOUTPOS( pl, rg.left, rg.top + WASABI_API_APP->getScaleY( 1 ), rg.right - rg.left + WASABI_API_APP->getScaleX( 1 ), ( rg.bottom - rg.top ) - WASABI_API_APP->getScaleY( 2 ) );
  1455. break;
  1456. }
  1457. SETLAYOUTFLAGS( pl, ri );
  1458. if ( LAYOUTNEEEDUPDATE( pl ) )
  1459. {
  1460. if ( SWP_NOSIZE == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW | SWP_NOSIZE ) & pl->flags ) && ri.left == ( pl->x + offsetX ) && ri.top == ( pl->y + offsetY ) && IsWindowVisible( pl->hwnd ) )
  1461. {
  1462. SetRect( &ri, pl->x, pl->y, pl->cx + pl->x, pl->y + pl->cy );
  1463. ValidateRect( hwnd, &ri );
  1464. }
  1465. pl++;
  1466. }
  1467. else if ( ( fRedraw || ( !offsetX && !offsetY ) ) && IsWindowVisible( pl->hwnd ) )
  1468. {
  1469. ValidateRect( hwnd, &ri );
  1470. if ( GetUpdateRect( pl->hwnd, NULL, FALSE ) )
  1471. {
  1472. if ( !rgn )
  1473. rgn = CreateRectRgn( 0, 0, 0, 0 );
  1474. GetUpdateRgn( pl->hwnd, rgn, FALSE );
  1475. OffsetRgn( rgn, pl->x, pl->y );
  1476. InvalidateRgn( hwnd, rgn, FALSE );
  1477. }
  1478. }
  1479. }
  1480. if ( pl != layout )
  1481. {
  1482. LAYOUT *pc;
  1483. HDWP hdwp = BeginDeferWindowPos( (INT)( pl - layout ) );
  1484. for ( pc = layout; pc < pl && hdwp; pc++ )
  1485. hdwp = DeferWindowPos( hdwp, pc->hwnd, NULL, pc->x, pc->y, pc->cx, pc->cy, pc->flags );
  1486. if ( hdwp )
  1487. EndDeferWindowPos( hdwp );
  1488. if ( !rgn )
  1489. rgn = CreateRectRgn( 0, 0, 0, 0 );
  1490. for ( pc = layout; pc < pl && hdwp; pc++ )
  1491. {
  1492. switch ( pc->id )
  1493. {
  1494. case IDC_PLAYLIST_LIST:
  1495. PostMessage( hwnd, WM_APP + 100, 0, 0 );
  1496. break;
  1497. }
  1498. }
  1499. if ( fRedraw )
  1500. {
  1501. GetUpdateRgn( hwnd, rgn, FALSE );
  1502. for ( pc = layout; pc < pl && hdwp; pc++ )
  1503. {
  1504. if ( pc->rgn )
  1505. {
  1506. OffsetRgn( pc->rgn, pc->x, pc->y );
  1507. CombineRgn( rgn, rgn, pc->rgn, RGN_OR );
  1508. }
  1509. }
  1510. RedrawWindow( hwnd, NULL, rgn, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN );
  1511. }
  1512. if ( g_rgnUpdate )
  1513. {
  1514. GetUpdateRgn( hwnd, g_rgnUpdate, FALSE );
  1515. for ( pc = layout; pc < pl && hdwp; pc++ )
  1516. {
  1517. if ( pc->rgn )
  1518. {
  1519. OffsetRgn( pc->rgn, pc->x, pc->y );
  1520. CombineRgn( g_rgnUpdate, g_rgnUpdate, pc->rgn, RGN_OR );
  1521. }
  1522. }
  1523. }
  1524. for ( pc = layout; pc < pl && hdwp; pc++ )
  1525. if ( pc->rgn )
  1526. DeleteObject( pc->rgn );
  1527. }
  1528. if ( rgn )
  1529. DeleteObject( rgn );
  1530. ValidateRgn( hwnd, NULL );
  1531. }
  1532. INT_PTR CALLBACK view_playlistsDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
  1533. {
  1534. INT_PTR a = dialogSkinner.Handle( hwndDlg, uMsg, wParam, lParam ); if ( a ) return a;
  1535. switch ( uMsg )
  1536. {
  1537. case WM_INITMENUPOPUP:
  1538. sendTo.InitPopupMenu( wParam );
  1539. return 0;
  1540. case WM_INITDIALOG:
  1541. playlists_InitDialog( hwndDlg );
  1542. return TRUE;
  1543. case WM_NOTIFY:
  1544. return playlists_Notify( hwndDlg, wParam, lParam );
  1545. case WM_MOUSEMOVE:
  1546. playlists_MouseMove( hwndDlg, lParam );
  1547. return 0;
  1548. case WM_LBUTTONUP:
  1549. playlists_LeftButtonUp( hwndDlg, wParam, lParam );
  1550. return 0;
  1551. case WM_COMMAND:
  1552. playlists_Command( hwndDlg, wParam, lParam );
  1553. break;
  1554. case WM_PAINT:
  1555. playlists_Paint( hwndDlg );
  1556. return 0;
  1557. case WM_ERASEBKGND:
  1558. return 1; //handled by WADlg_DrawChildWindowBorders in WM_PAINT
  1559. case WM_DESTROY:
  1560. playlists_Destroy( hwndDlg );
  1561. break;
  1562. case WM_DROPFILES:
  1563. playlists_DropFiles( (HDROP)wParam );
  1564. return 0;
  1565. case WM_CONTEXTMENU:
  1566. playlists_ContextMenu( hwndDlg, (HWND)wParam, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
  1567. return 0;
  1568. case WM_DISPLAYCHANGE:
  1569. LayoutWindows( hwndDlg, TRUE );
  1570. return 0;
  1571. case WM_APP + 104:
  1572. {
  1573. playlist_UpdateButtonText( hwndDlg, wParam );
  1574. LayoutWindows( hwndDlg, TRUE );
  1575. return 0;
  1576. }
  1577. case WM_APP + 102:
  1578. {
  1579. if ( cloud_avail )
  1580. {
  1581. int width = 27;
  1582. MLCloudColumn_GetWidth( plugin.hwndLibraryParent, &width );
  1583. m_playlistslist.SetColumnWidth( 1, width );
  1584. MLSkinnedHeader_SetCloudColumn( ListView_GetHeader( m_playlistslist.getwnd() ), 1 );
  1585. }
  1586. }
  1587. case WM_APP + 101:
  1588. m_playlistslist.RefreshAll(); UpdateWindow( m_playlistslist.getwnd() );
  1589. case WM_APP + 100:
  1590. AutoSizePlaylistColumns();
  1591. if ( !loaded )
  1592. {
  1593. loaded = true;
  1594. SetWindowRedraw( m_playlistslist.getwnd(), TRUE );
  1595. }
  1596. return 0;
  1597. case WM_WINDOWPOSCHANGED:
  1598. if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE ) & ( (WINDOWPOS *)lParam )->flags ) ||
  1599. ( SWP_FRAMECHANGED & ( (WINDOWPOS *)lParam )->flags ) )
  1600. {
  1601. LayoutWindows( hwndDlg, !( SWP_NOREDRAW & ( (WINDOWPOS *)lParam )->flags ) );
  1602. }
  1603. return 0;
  1604. case WM_USER + 0x200:
  1605. SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, 1 ); // yes, we support no - redraw resize
  1606. return TRUE;
  1607. case WM_USER + 0x201:
  1608. offsetX = (short)LOWORD( wParam );
  1609. offsetY = (short)HIWORD( wParam );
  1610. g_rgnUpdate = (HRGN)lParam;
  1611. return TRUE;
  1612. case WM_ML_CHILDIPC:
  1613. {
  1614. if ( lParam == ML_CHILDIPC_DROPITEM && wParam )
  1615. {
  1616. mlDropItemStruct *dis = (mlDropItemStruct *)wParam;
  1617. if ( dis )
  1618. {
  1619. switch ( dis->type )
  1620. {
  1621. case ML_TYPE_FILENAMES:
  1622. case ML_TYPE_STREAMNAMES:
  1623. case ML_TYPE_FILENAMESW:
  1624. case ML_TYPE_STREAMNAMESW:
  1625. case ML_TYPE_ITEMRECORDLIST:
  1626. case ML_TYPE_ITEMRECORDLISTW:
  1627. case ML_TYPE_CDTRACKS:
  1628. // check we're not dropping back on ourself - not
  1629. // pretty but it prevents the new playlist prompt
  1630. if ( root_is_drag_and_dropping )
  1631. {
  1632. RECT r;
  1633. GetWindowRect( hwndDlg, &r );
  1634. dis->result = ( !PtInRect( &r, dis->p ) ? 1 : -1 );
  1635. }
  1636. // otherwise allow through as from external
  1637. else
  1638. dis->result = 1;
  1639. break;
  1640. default:
  1641. dis->result = -1;
  1642. break;
  1643. }
  1644. }
  1645. return 0;
  1646. }
  1647. }
  1648. }
  1649. return 0;
  1650. }