Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

592 lines
16 KiB

  1. #include "priv.h"
  2. #include "zaxxon.h"
  3. #include "guids.h"
  4. #include "shlwapip.h"
  5. #include "resource.h"
  6. #include "commdlg.h"
  7. #include "varutil.h"
  8. #define ID_TOOLBAR 100
  9. #define ID_LISTVIEW 101
  10. #define EDIT_NEW 0
  11. #define EDIT_ADD 1
  12. #define EDIT_REMOVE 2
  13. #define EDIT_LOAD 3
  14. #define EDIT_SAVE 4
  15. #define EDIT_SORT 5
  16. CSong::CSong():_cRef(1)
  17. {
  18. }
  19. void CSong::AddRef()
  20. {
  21. InterlockedIncrement((LONG*)&_cRef);
  22. }
  23. void CSong::Release()
  24. {
  25. InterlockedDecrement((LONG*)&_cRef);
  26. if (_cRef == 0)
  27. delete this;
  28. }
  29. CZaxxonEditor::CZaxxonEditor(CZaxxon* pz):_pzax(pz)
  30. {
  31. WNDCLASS wc = {0};
  32. wc.lpszClassName = TEXT("ZaxxonEditor");
  33. wc.lpfnWndProc = CZaxxonEditor::s_WndProc;
  34. wc.hInstance = HINST_THISDLL;
  35. wc.hbrBackground = HBRUSH(COLOR_ACTIVECAPTION + 1);
  36. RegisterClass(&wc);
  37. hSongList = DPA_Create(10);
  38. }
  39. int CALLBACK SongDestroyCallback(void* p, void*)
  40. {
  41. CSong* psong = (CSong*)p;
  42. psong->Release();
  43. return 1;
  44. }
  45. CZaxxonEditor::~CZaxxonEditor()
  46. {
  47. if (_hwnd)
  48. {
  49. DestroyWindow(_hwnd);
  50. _hwnd = NULL;
  51. }
  52. DPA_DestroyCallback(hSongList, SongDestroyCallback, NULL);
  53. }
  54. int ReadLineFromStream(IStream* pstm, LPTSTR pszLine, DWORD cch)
  55. {
  56. // Assume this is an ANSI string
  57. char szLine[MAX_PATH];
  58. DWORD dwRead;
  59. int iLine = 0;
  60. ULARGE_INTEGER UliStartingFrom;
  61. LARGE_INTEGER liSeek = {0};
  62. pstm->Seek(liSeek, STREAM_SEEK_CUR, &UliStartingFrom);
  63. pstm->Read(szLine, ARRAYSIZE(szLine), &dwRead);
  64. if (dwRead > 0)
  65. {
  66. // Now convert to String
  67. SHAnsiToUnicode(szLine, pszLine, dwRead);
  68. pszLine[MAX_PATH - 1] = '\0'; //Null terminate the string
  69. // Get the number of characters in the line
  70. LPTSTR pszNewLine = StrStr(pszLine, TEXT("\r"));
  71. if (pszNewLine)
  72. {
  73. *pszNewLine++ = TEXT('\0'); // Nuke \r
  74. *pszNewLine = TEXT('\0'); // Nuke \n
  75. }
  76. else
  77. {
  78. pszNewLine = StrStr(pszLine, TEXT("\n"));
  79. if (pszNewLine)
  80. *pszNewLine = TEXT('\0'); // Nuke \n
  81. }
  82. iLine = lstrlen(pszLine);
  83. // Now take that, and set the seek position to 2 more than that (One for the \r and \n
  84. liSeek.QuadPart = UliStartingFrom.QuadPart + iLine + 1 + 1;
  85. pstm->Seek(liSeek, STREAM_SEEK_SET, NULL);
  86. }
  87. return iLine;
  88. }
  89. void CZaxxonEditor::UpdateSong(CSong* psong)
  90. {
  91. int i = ListView_MapIDToIndex(_hwndList, psong->_id);
  92. if (i >= 0)
  93. {
  94. TCHAR szTitle[MAX_PATH];
  95. wsprintf(szTitle, TEXT("%s - %s"), psong->szArtist, psong->szTitle);
  96. LVITEM lv;
  97. lv.mask = LVIF_TEXT;
  98. lv.iItem = i;
  99. lv.iSubItem = 0;
  100. lv.pszText = szTitle;
  101. ListView_SetItem(_hwndList,&lv);
  102. lv.iSubItem = 1;
  103. lv.pszText = psong->szDuration;
  104. ListView_SetItem(_hwndList,&lv);
  105. }
  106. }
  107. void CZaxxonEditor::LoadPlaylist()
  108. {
  109. TCHAR sz[MAX_PATH];
  110. TCHAR szInit[MAX_PATH];
  111. OPENFILENAME of = {0};
  112. of.lStructSize = sizeof(of);
  113. of.hwndOwner = _hwnd;
  114. of.lpstrFilter = TEXT("Playlist\0*.m3u\0\0");
  115. of.lpstrFile = sz;
  116. of.nMaxFile = MAX_PATH;
  117. of.lpstrDefExt = TEXT(".m3u");
  118. if (FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, szInit)))
  119. SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, szInit);
  120. of.lpstrInitialDir = szInit;
  121. of.Flags = OFN_ENABLESIZING | OFN_EXPLORER | OFN_FILEMUSTEXIST;
  122. if (GetOpenFileName(&of))
  123. {
  124. ClearPlaylist();
  125. IStream* pstm;
  126. if (SUCCEEDED(SHCreateStreamOnFile(sz, STGM_READ, &pstm)))
  127. {
  128. do
  129. {
  130. TCHAR szFilename[MAX_PATH];
  131. if (ReadLineFromStream(pstm, szFilename, ARRAYSIZE(szFilename)) > 0)
  132. {
  133. AddFilename(szFilename);
  134. }
  135. else
  136. {
  137. break;
  138. }
  139. }
  140. while (1);
  141. pstm->Release();
  142. }
  143. }
  144. }
  145. void CZaxxonEditor::SavePlaylist()
  146. {
  147. TCHAR sz[MAX_PATH];
  148. TCHAR szInit[MAX_PATH];
  149. OPENFILENAME of = {0};
  150. of.lStructSize = sizeof(of);
  151. of.hwndOwner = _hwnd;
  152. of.lpstrFilter = TEXT("Playlist\0*.m3u\0\0");
  153. of.lpstrFile = sz;
  154. of.nMaxFile = MAX_PATH;
  155. of.lpstrDefExt = TEXT(".m3u");
  156. if (FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, szInit)))
  157. SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, szInit);
  158. of.lpstrInitialDir = szInit;
  159. of.Flags = OFN_ENABLESIZING | OFN_EXPLORER;
  160. if (GetSaveFileName(&of))
  161. {
  162. IStream* pstm;
  163. if (SUCCEEDED(SHCreateStreamOnFile(sz, STGM_CREATE | STGM_WRITE, &pstm)))
  164. {
  165. char szLine[MAX_PATH];
  166. int cCount = DPA_GetPtrCount(hSongList);
  167. for (int i=0; i < cCount; i++)
  168. {
  169. CSong* psong = (CSong*)DPA_FastGetPtr(hSongList, i);
  170. SHUnicodeToAnsi(psong->szSong, szLine, MAX_PATH);
  171. pstm->Write(szLine, lstrlenA(szLine), NULL);
  172. pstm->Write("\r\n", 2, NULL);
  173. }
  174. pstm->Write("\r\n", 2, NULL);
  175. pstm->Release();
  176. }
  177. }
  178. }
  179. void CZaxxonEditor::RemoveFromPlaylist()
  180. {
  181. int iItem = ListView_GetNextItem(_hwndList, -1, MAKELPARAM(LVNI_SELECTED, 0));
  182. if (iItem >= 0 && iItem < DPA_GetPtrCount(hSongList))
  183. {
  184. _pzax->_pzax->RemoveSong(iItem);
  185. ListView_DeleteItem(_hwndList, iItem);
  186. CSong* psong = (CSong*)DPA_DeletePtr(hSongList, iItem);
  187. psong->Release();
  188. }
  189. }
  190. void CZaxxonEditor::ClearPlaylist()
  191. {
  192. _pzax->_pzax->ClearPlaylist();
  193. DPA_DestroyCallback(hSongList, SongDestroyCallback, NULL);
  194. hSongList = DPA_Create(10);
  195. ListView_DeleteAllItems(_hwndList);
  196. }
  197. void CZaxxonEditor::InsertFilename(int i, PTSTR psz)
  198. {
  199. CSong* pzs = new CSong;
  200. if (pzs)
  201. {
  202. StrCpy(pzs->szSong, psz);
  203. int iIndex = DPA_InsertPtr(hSongList, i, pzs);
  204. if (iIndex != -1)
  205. {
  206. LVITEM lv;
  207. lv.mask = LVIF_TEXT;
  208. lv.iItem = iIndex;
  209. lv.iSubItem = 0;
  210. lv.pszText = PathFindFileName(psz);
  211. ListView_InsertItem(_hwndList, &lv);
  212. pzs->_id = ListView_MapIndexToID(_hwndList, iIndex);
  213. _pzax->_pzax->AddSong(psz);
  214. CSongExtractionTask* pset = new CSongExtractionTask(_hwnd, pzs);
  215. if (pset)
  216. {
  217. if (_pzax->_pScheduler)
  218. _pzax->_pScheduler->AddTask(pset, CLSID_Zaxxon, 0, ITSAT_MIN_PRIORITY);
  219. pset->Release();
  220. }
  221. }
  222. }
  223. }
  224. void CZaxxonEditor::AddFilename(PTSTR psz)
  225. {
  226. InsertFilename(DSA_APPEND, psz);
  227. }
  228. void CZaxxonEditor::AddToPlaylist()
  229. {
  230. OPENFILENAME of = {0};
  231. PTSTR psz = (PTSTR)LocalAlloc(LPTR, 4096);
  232. if (psz)
  233. {
  234. of.lStructSize = sizeof(of);
  235. of.hwndOwner = _hwnd;
  236. of.lpstrFilter = TEXT("Music\0*.mp3;*.wma\0\0");
  237. of.lpstrFile = psz;
  238. of.nMaxFile = 4096;
  239. of.Flags = OFN_ALLOWMULTISELECT | OFN_ENABLESIZING | OFN_EXPLORER | OFN_FILEMUSTEXIST;
  240. if (GetOpenFileName(&of))
  241. {
  242. TCHAR szName[MAX_PATH];
  243. PTSTR pszFilename = psz;
  244. int iLen = lstrlen(pszFilename);
  245. do
  246. {
  247. pszFilename += iLen + 1; // Skip the last filename
  248. iLen = lstrlen(pszFilename);
  249. if (iLen > 0)
  250. {
  251. StrCpy(szName, psz); // Copy the path;
  252. PathAppend(szName, pszFilename); // Append the new name
  253. AddFilename(szName); // Append to list.
  254. }
  255. }
  256. while (iLen != 0);
  257. }
  258. LocalFree(psz);
  259. }
  260. }
  261. int FindSong(void* p1, void* p2, LPARAM lParam)
  262. {
  263. PTSTR pszSong = (PTSTR)p1;
  264. CSong* psong = (CSong*)p2;
  265. return StrCmpI(pszSong, psong->szSong);
  266. }
  267. void CZaxxonEditor::HighlightSong(PTSTR psz)
  268. {
  269. int iItem = DPA_Search(hSongList, psz, 0, FindSong, 0, NULL);
  270. if (iItem >= 0)
  271. {
  272. ListView_SetItemState(_hwndList, iItem, LVIS_SELECTED, LVIS_SELECTED);
  273. }
  274. }
  275. static const TCHAR* g_EditStrings = TEXT("New\0Add\0Delete\0Load\0Save\0Sort\0\0");
  276. static const TBBUTTON g_crgEditButtons[] =
  277. {
  278. {I_IMAGENONE, EDIT_NEW, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 0},
  279. {I_IMAGENONE, EDIT_ADD, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 1},
  280. {I_IMAGENONE, EDIT_REMOVE, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 2},
  281. {I_IMAGENONE, EDIT_LOAD, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 3},
  282. {I_IMAGENONE, EDIT_SAVE, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 4},
  283. {I_IMAGENONE, EDIT_SORT, TBSTATE_ENABLED, BTNS_BUTTON, 0, 0, 0, 5},
  284. };
  285. BOOL CZaxxonEditor::Initialize()
  286. {
  287. int cxInitial = 300;
  288. int cyInitial = 500;
  289. _hwnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
  290. TEXT("ZaxxonEditor"), TEXT("Zaxxon Playlist Editor"),
  291. WS_CAPTION | WS_POPUPWINDOW | WS_THICKFRAME | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
  292. 0, 0, 0, 0,
  293. _pzax->GetHWND(), NULL, HINST_THISDLL, this);
  294. if (_hwnd)
  295. {
  296. _pzax->_pzax->Register(_hwnd);
  297. _hwndList = CreateWindow(
  298. TEXT("SysListView32"), TEXT(""),
  299. WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS,
  300. 0, 0, 0, 0,
  301. _hwnd, (HMENU)ID_LISTVIEW, NULL, NULL);
  302. if (_hwndList)
  303. {
  304. RECT rc;
  305. GetClientRect(_hwnd, &rc);
  306. LVCOLUMN lvc = {0};
  307. lvc.mask = LVCF_TEXT | LVCF_WIDTH;
  308. lvc.cx = 4 * cxInitial / 5 - 10;
  309. lvc.pszText = TEXT("Song");
  310. ListView_InsertColumn(_hwndList, 0, &lvc);
  311. lvc.mask = LVCF_TEXT | LVCF_WIDTH;
  312. lvc.cx = cxInitial / 5;
  313. lvc.pszText = TEXT("Length");
  314. ListView_InsertColumn(_hwndList, 1, &lvc);
  315. ListView_SetExtendedListViewStyle(_hwndList, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
  316. }
  317. _hwndToolbar = CreateWindowEx(WS_EX_TOOLWINDOW, TOOLBARCLASSNAME, NULL,
  318. WS_VISIBLE | WS_CHILD | TBSTYLE_FLAT | TBSTYLE_LIST |
  319. WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
  320. CCS_NODIVIDER | CCS_NOPARENTALIGN |
  321. CCS_NORESIZE | TBSTYLE_REGISTERDROP,
  322. 0, 0, 0, 0, _hwnd, (HMENU) ID_TOOLBAR, HINST_THISDLL, NULL);
  323. if (_hwndToolbar)
  324. {
  325. // Set the format to ANSI or UNICODE as appropriate.
  326. ToolBar_SetUnicodeFormat(_hwndToolbar, TRUE);
  327. SetWindowTheme(_hwndToolbar, L"TaskBar", NULL);
  328. SendMessage(_hwndToolbar, CCM_SETVERSION, COMCTL32_VERSION, 0);
  329. SendMessage(_hwndToolbar, TB_BUTTONSTRUCTSIZE, SIZEOF(TBBUTTON), 0);
  330. SendMessage(_hwndToolbar, TB_ADDSTRING, NULL, (LPARAM)g_EditStrings);
  331. SendMessage(_hwndToolbar, TB_ADDBUTTONS, ARRAYSIZE(g_crgEditButtons), (LPARAM)&g_crgEditButtons);
  332. ToolBar_SetExtendedStyle(_hwndToolbar, TBSTYLE_EX_DOUBLEBUFFER, TBSTYLE_EX_DOUBLEBUFFER);
  333. }
  334. SetWindowPos(_hwnd, NULL, 0, 0, cxInitial, cyInitial, SWP_NOACTIVATE);
  335. }
  336. return _hwnd != NULL;
  337. }
  338. BOOL CZaxxonEditor::Show(BOOL fShow)
  339. {
  340. ShowWindow(_hwnd, fShow?SW_SHOW:SW_HIDE);
  341. return TRUE;
  342. }
  343. LRESULT CZaxxonEditor::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  344. {
  345. switch (uMsg)
  346. {
  347. case WM_CREATE:
  348. break;
  349. case WM_SONGCHANGE:
  350. {
  351. _fIgnoreChange = TRUE;
  352. HighlightSong((PTSTR)wParam);
  353. _fIgnoreChange = FALSE;
  354. }
  355. break;
  356. case WM_UPDATESONG:
  357. {
  358. UpdateSong((CSong*)wParam);
  359. }
  360. break;
  361. case WM_NOTIFY:
  362. {
  363. LPNMHDR pnm = (LPNMHDR)lParam;
  364. if (pnm->idFrom == ID_TOOLBAR)
  365. {
  366. if (pnm->code == NM_CLICK)
  367. {
  368. {
  369. int idCmd = (int)((LPNMCLICK)pnm)->dwItemSpec;
  370. switch (idCmd)
  371. {
  372. case EDIT_NEW:
  373. ClearPlaylist();
  374. break;
  375. case EDIT_ADD:
  376. AddToPlaylist();
  377. break;
  378. case EDIT_REMOVE:
  379. RemoveFromPlaylist();
  380. break;
  381. case EDIT_LOAD:
  382. LoadPlaylist();
  383. break;
  384. case EDIT_SAVE:
  385. SavePlaylist();
  386. break;
  387. case EDIT_SORT:
  388. break;
  389. }
  390. }
  391. }
  392. }
  393. else if (pnm->idFrom == ID_LISTVIEW)
  394. {
  395. if (pnm->code == LVN_ITEMCHANGED && !_fIgnoreChange)
  396. {
  397. NMLISTVIEW* pnml = (NMLISTVIEW*)pnm;
  398. int iItem = pnml->iItem;
  399. if (iItem < DPA_GetPtrCount(hSongList) &&
  400. pnml->uNewState != pnml->uOldState &&
  401. pnml->uNewState & LVIS_SELECTED)
  402. {
  403. _pzax->_pzax->SetSong(iItem);
  404. }
  405. }
  406. }
  407. }
  408. break;
  409. case WM_CLOSE:
  410. ShowWindow(hwnd, SW_HIDE);
  411. return 1;
  412. case WM_SIZE:
  413. {
  414. RECT rc;
  415. GetClientRect(hwnd, &rc);
  416. LONG lButton = SendMessage(_hwndToolbar, TB_GETBUTTONSIZE, 0, 0L);
  417. SetWindowPos(_hwndList, NULL, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc) - HIWORD(lButton), SWP_NOZORDER | SWP_NOACTIVATE);
  418. SetWindowPos(_hwndToolbar, NULL, rc.left, RECTHEIGHT(rc) - HIWORD(lButton), RECTWIDTH(rc), HIWORD(lButton), SWP_NOZORDER | SWP_NOACTIVATE);
  419. }
  420. break;
  421. }
  422. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  423. }
  424. LRESULT CZaxxonEditor::s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  425. {
  426. if (uMsg == WM_CREATE)
  427. {
  428. SetWindowLongPtr(hwnd, GWLP_USERDATA, (ULONG_PTR)((CREATESTRUCT*)lParam)->lpCreateParams);
  429. }
  430. CZaxxonEditor* pedit = (CZaxxonEditor*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  431. if (pedit)
  432. return pedit->WndProc(hwnd, uMsg, wParam, lParam);
  433. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  434. }
  435. CSongExtractionTask::CSongExtractionTask(HWND hwnd, CSong* psong)
  436. : CRunnableTask(RTF_DEFAULT), _hwnd(hwnd)
  437. {
  438. _psong = psong;
  439. _psong->AddRef();
  440. }
  441. CSongExtractionTask::~CSongExtractionTask()
  442. {
  443. _psong->Release();
  444. }
  445. STDMETHODIMP CSongExtractionTask::RunInitRT()
  446. {
  447. LPITEMIDLIST pidl = ILCreateFromPath(_psong->szSong);
  448. if (pidl)
  449. {
  450. LPCITEMIDLIST pidlChild;
  451. IShellFolder2* psf;
  452. HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder2, &psf), &pidlChild);
  453. if (SUCCEEDED(hr))
  454. {
  455. VARIANT v;
  456. hr = psf->GetDetailsEx(pidlChild, &SCID_MUSIC_Artist, &v);
  457. if (SUCCEEDED(hr))
  458. {
  459. VariantToStr(&v, _psong->szArtist, ARRAYSIZE(_psong->szArtist));
  460. }
  461. hr = psf->GetDetailsEx(pidlChild, &SCID_MUSIC_Album, &v);
  462. if (SUCCEEDED(hr))
  463. {
  464. VariantToStr(&v, _psong->szAlbum, ARRAYSIZE(_psong->szAlbum));
  465. }
  466. hr = psf->GetDetailsEx(pidlChild, &SCID_Title, &v);
  467. if (SUCCEEDED(hr))
  468. {
  469. VariantToStr(&v, _psong->szTitle, ARRAYSIZE(_psong->szTitle));
  470. }
  471. hr = psf->GetDetailsEx(pidlChild, &SCID_AUDIO_Duration, &v);
  472. if (SUCCEEDED(hr))
  473. {
  474. PROPVARIANT pv = *(PROPVARIANT*)&v;
  475. FILETIME ft = {pv.uhVal.LowPart, pv.uhVal.HighPart};
  476. SYSTEMTIME st;
  477. FileTimeToSystemTime(&ft, &st);
  478. GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER,
  479. &st, NULL, _psong->szDuration, ARRAYSIZE(_psong->szDuration));
  480. }
  481. SendMessage(_hwnd, WM_UPDATESONG, (WPARAM)_psong, 0);
  482. psf->Release();
  483. }
  484. ILFree(pidl);
  485. }
  486. return S_OK;
  487. }