pluginproc.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. #include "main.h"
  2. #include "resource.h"
  3. #include <windows.h>
  4. #include <commctrl.h>
  5. #include "SendTo.h"
  6. #include "ml_local/api_mldb.h"
  7. #include "ml_pmp/pmp.h"
  8. #include "replicant/nswasabi/ReferenceCounted.h"
  9. #include "replicant/nx/win/nxstring.h"
  10. #include "replicant/nu/AutoChar.h"
  11. #include "nu/AutoCharFn.h"
  12. #include "nu/menushortcuts.h"
  13. #include <api/syscb/callbacks/syscb.h>
  14. #include <api/syscb/callbacks/browsercb.h>
  15. using namespace Nullsoft::Utility;
  16. INT_PTR lastActiveID = 0;
  17. SendToMenu treeViewSendTo;
  18. HWND currentView = 0;
  19. int playlists_ContextMenu(INT_PTR param1, HWND hHost, POINTS pts);
  20. LRESULT pluginHandleIpcMessage(int msg, WPARAM param)
  21. {
  22. return SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, param, msg);
  23. }
  24. INT_PTR pluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
  25. {
  26. switch (message_type)
  27. {
  28. case ML_MSG_NO_CONFIG:
  29. return TRUE;
  30. case ML_MSG_TREE_ONCREATEVIEW:
  31. if (param1 == playlistsTreeId)
  32. {
  33. return (INT_PTR)(currentView = WASABI_API_CREATEDIALOGW(IDD_VIEW_PLAYLISTS, (HWND)param2, view_playlistsDialogProc));
  34. }
  35. else // if param1 is a valid playlist
  36. {
  37. if (FindTreeItem(param1))
  38. {
  39. lastActiveID = param1;
  40. // TODO: what if it's an ifc_playlist provided from elsewhere instead of just a filename?
  41. return (INT_PTR)(currentView = WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_PLAYLIST, (HWND)param2, view_playlistDialogProc, lastActiveID));
  42. }
  43. }
  44. break;
  45. case ML_MSG_ONSENDTOBUILD:
  46. return playlists_BuildSendTo(param1, param2);
  47. case ML_MSG_ONSENDTOSELECT:
  48. return playlists_OnSendTo(param1, param2, param3);
  49. case ML_MSG_TREE_ONCLICK:
  50. return playlists_OnClick(param1, param2, (HWND)param3);
  51. case ML_MSG_NAVIGATION_CONTEXTMENU:
  52. {
  53. HNAVITEM hItem = (HNAVITEM)param1;
  54. HNAVITEM myItem = MLNavCtrl_FindItemById(plugin.hwndLibraryParent, playlistsTreeId);
  55. if (hItem == myItem)
  56. {
  57. return playlists_ContextMenu(param1, (HWND)param2, MAKEPOINTS(param3));
  58. }
  59. else
  60. {
  61. NAVITEM nvItem = {sizeof(NAVITEM),hItem,NIMF_ITEMID,};
  62. MLNavItem_GetInfo(plugin.hwndLibraryParent, &nvItem);
  63. if (FindTreeItem(nvItem.id))
  64. {
  65. HWND wnd = (HWND)param2;
  66. sendToIgnoreID = nvItem.id;
  67. HMENU menu = GetSubMenu(g_context_menus, 0),
  68. sendToMenu = GetSubMenu(menu, 2);
  69. treeViewSendTo.AddHere(wnd, sendToMenu, ML_TYPE_FILENAMES, 1, (ML_TYPE_PLAYLIST+1)); // we're going to lie about the type for now
  70. // make sure that we call init on this otherwise the sendto menu will most likely fail
  71. treeViewSendTo.InitPopupMenu((WPARAM)sendToMenu);
  72. // tweaked to not fudge on the ml tree
  73. EnableMenuItem(menu, IDC_PLAY, MF_BYCOMMAND | MF_ENABLED);
  74. EnableMenuItem(menu, IDC_ENQUEUE, MF_BYCOMMAND | MF_ENABLED);
  75. EnableMenuItem(menu, IDC_DELETE, MF_BYCOMMAND | MF_ENABLED);
  76. EnableMenuItem(menu, ID_QUERYMENU_ADDNEWQUERY, MF_BYCOMMAND | MF_ENABLED);
  77. EnableMenuItem(menu, IDC_RENAME, MF_BYCOMMAND | MF_ENABLED);
  78. EnableMenuItem(menu, IDC_ENQUEUE, MF_BYCOMMAND | MF_ENABLED);
  79. EnableMenuItem(menu, 2, MF_BYPOSITION | MF_ENABLED);
  80. EnableMenuItem(menu, IDC_VIEWLIST, MF_BYCOMMAND | (nvItem.id != lastActiveID ? MF_ENABLED : MF_DISABLED));
  81. HMENU cloud_hmenu = (HMENU)0x666;
  82. size_t index = 0;
  83. if (playlists_CloudAvailable())
  84. {
  85. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  86. PlaylistInfo info;
  87. if (info.Associate(nvItem.id))
  88. {
  89. ReferenceCountedNXString uid;
  90. NXStringCreateWithFormatting(&uid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
  91. (int)info.playlist_guid.Data1, (int)info.playlist_guid.Data2,
  92. (int)info.playlist_guid.Data3, (int)info.playlist_guid.Data4[0],
  93. (int)info.playlist_guid.Data4[1], (int)info.playlist_guid.Data4[2],
  94. (int)info.playlist_guid.Data4[3], (int)info.playlist_guid.Data4[4],
  95. (int)info.playlist_guid.Data4[5], (int)info.playlist_guid.Data4[6],
  96. (int)info.playlist_guid.Data4[7]);
  97. index = info.GetIndex();
  98. WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_GET_CLOUD_STATUS, (intptr_t)uid->string, (intptr_t)&cloud_hmenu);
  99. if (cloud_hmenu && cloud_hmenu != (HMENU)0x666)
  100. {
  101. MENUITEMINFOW m = {sizeof(m), MIIM_TYPE | MIIM_ID | MIIM_SUBMENU, MFT_SEPARATOR, 0};
  102. m.wID = CLOUD_SOURCE_MENUS - 1;
  103. InsertMenuItemW(menu, 3, TRUE, &m);
  104. wchar_t a[100] = {0};
  105. m.fType = MFT_STRING;
  106. m.dwTypeData = WASABI_API_LNGSTRINGW_BUF(IDS_CLOUD_SOURCES, a, 100);
  107. m.wID = CLOUD_SOURCE_MENUS;
  108. m.hSubMenu = cloud_hmenu;
  109. InsertMenuItemW(menu, 4, TRUE, &m);
  110. }
  111. }
  112. }
  113. bool swapPlayEnqueue=false;
  114. if (g_config->ReadInt(L"enqueuedef", 0) == 1)
  115. {
  116. SwapPlayEnqueueInMenu(menu);
  117. swapPlayEnqueue=true;
  118. }
  119. {
  120. HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_PL_ACCELERATORS);
  121. int size = CopyAcceleratorTable(accel,0,0);
  122. AppendMenuShortcuts(menu, &accel, size, MSF_REPLACE);
  123. }
  124. if (swapPlayEnqueue)
  125. SwapPlayEnqueueInMenu(menu);
  126. POINT pt;
  127. POINTSTOPOINT(pt, MAKEPOINTS(param3));
  128. if (-1 == pt.x || -1 == pt.y)
  129. {
  130. NAVITEMGETRECT itemRect;
  131. itemRect.fItem = FALSE;
  132. itemRect.hItem = hItem;
  133. if (MLNavItem_GetRect(plugin.hwndLibraryParent, &itemRect))
  134. {
  135. MapWindowPoints(wnd, HWND_DESKTOP, (POINT*)&itemRect.rc, 2);
  136. pt.x = itemRect.rc.left + 2;
  137. pt.y = itemRect.rc.top + 2;
  138. }
  139. }
  140. int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, wnd, NULL);
  141. switch (r)
  142. {
  143. case IDC_PLAY:
  144. {
  145. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  146. PlaylistInfo info;
  147. if (info.Associate(nvItem.id))
  148. {
  149. playlist_SaveGUID(info.playlist_guid);
  150. mediaLibrary.PlayFile(info.GetFilename());
  151. }
  152. }
  153. break;
  154. case IDC_ENQUEUE:
  155. {
  156. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  157. PlaylistInfo info;
  158. if (info.Associate(nvItem.id))
  159. {
  160. playlist_SaveGUID(info.playlist_guid);
  161. mediaLibrary.EnqueueFile(info.GetFilename());
  162. }
  163. }
  164. break;
  165. case IDC_NEWPLAYLIST:
  166. playlists_Add(wnd);
  167. break;
  168. case IDC_DELETE:
  169. DeletePlaylist(tree_to_guid_map[nvItem.id], wnd, true);
  170. break;
  171. case IDC_RENAME:
  172. RenamePlaylist(tree_to_guid_map[nvItem.id], wnd);
  173. break;
  174. case IDC_VIEWLIST:
  175. mediaLibrary.SelectTreeItem(nvItem.id);
  176. break;
  177. default:
  178. {
  179. if (treeViewSendTo.WasClicked(r))
  180. {
  181. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  182. PlaylistInfo info;
  183. if (info.Associate(nvItem.id))
  184. {
  185. playlist_SaveGUID(info.playlist_guid);
  186. info.Refresh();
  187. mlPlaylist sendToPlaylist;
  188. sendToPlaylist.filename = info.GetFilename();
  189. sendToPlaylist.title = info.GetName();
  190. sendToPlaylist.numItems = info.GetSize();
  191. sendToPlaylist.length = info.GetLength();
  192. if (treeViewSendTo.SendPlaylist(&sendToPlaylist) != 1)
  193. {
  194. // didn't like that
  195. // let's try this way
  196. wchar_t filenames[MAX_PATH + 1] = {0};
  197. lstrcpyn(filenames, info.GetFilename(), MAX_PATH);
  198. filenames[lstrlen(filenames) + 1] = 0;
  199. treeViewSendTo.SendFilenames(filenames);
  200. }
  201. }
  202. }
  203. else
  204. {
  205. if (r >= CLOUD_SOURCE_MENUS && r < CLOUD_SOURCE_MENUS_PL_UPPER) // deals with cloud specific menus
  206. {
  207. // 0 = no change
  208. // 1 = adding to cloud
  209. // 2 = added locally
  210. // 4 = removed
  211. int mode = -(int)index;
  212. WASABI_API_SYSCB->syscb_issueCallback(api_mldb::SYSCALLBACK, api_mldb::MLDB_FILE_PROCESS_CLOUD_STATUS, (intptr_t)r, (intptr_t)&mode);
  213. if (mode > 0)
  214. {
  215. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  216. PlaylistInfo info;
  217. if (info.Associate(nvItem.id))
  218. {
  219. info.SetCloud((mode == 1 ? 1 : 0));
  220. AGAVE_API_PLAYLISTS->Flush();
  221. UpdatePlaylists();
  222. }
  223. }
  224. }
  225. }
  226. }
  227. }
  228. treeViewSendTo.Cleanup();
  229. sendToIgnoreID = 0;
  230. if (cloud_hmenu && cloud_hmenu != (HMENU)0x666)
  231. {
  232. DeleteMenu(menu, CLOUD_SOURCE_MENUS - 1, MF_BYCOMMAND);
  233. DeleteMenu(menu, CLOUD_SOURCE_MENUS, MF_BYCOMMAND);
  234. DestroyMenu(cloud_hmenu);
  235. }
  236. return TRUE;
  237. }
  238. }
  239. }
  240. return FALSE;
  241. case ML_MSG_TREE_ONKEYDOWN:
  242. return playlists_OnKeyDown(param1, (NMTVKEYDOWN *)param2, (HWND)param3);
  243. case ML_MSG_TREE_ONDRAG:
  244. return playlists_OnDrag(param1, (POINT *)param2, (int *)param3);
  245. case ML_MSG_TREE_ONDROP:
  246. return playlists_OnDrop(param1, (POINT *)param2, param3);
  247. case ML_MSG_TREE_ONDROPTARGET:
  248. return playlists_OnDropTarget(param1, param2, param3);
  249. case ML_MSG_PLAYING_FILE:
  250. lstrcpynW(current_playing, (wchar_t*)param1, FILENAME_SIZE);
  251. if (IsWindow(currentView)) PostMessage(currentView, WM_APP + 103, (WPARAM)current_playing, 0);
  252. return FALSE;
  253. case ML_MSG_WRITE_CONFIG:
  254. if (param1)
  255. {
  256. // only save the ml playlists if saving winamp.m3u/m3u8
  257. // else this will happen everytime the prefs are closed
  258. AGAVE_API_PLAYLISTS->Flush();
  259. }
  260. return FALSE;
  261. case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
  262. {
  263. enqueuedef = param1;
  264. groupBtn = param2;
  265. PostMessage(currentView, WM_APP + 104, param1, param2);
  266. return 0;
  267. }
  268. }
  269. return 0;
  270. }
  271. void myOpenURL(HWND hwnd, wchar_t *loc)
  272. {
  273. if (loc)
  274. {
  275. bool override=false;
  276. WASABI_API_SYSCB->syscb_issueCallback(SysCallback::BROWSER, BrowserCallback::ONOPENURL, reinterpret_cast<intptr_t>(loc), reinterpret_cast<intptr_t>(&override));
  277. if (!override)
  278. ShellExecuteW(hwnd, L"open", loc, NULL, NULL, SW_SHOWNORMAL);
  279. }
  280. }
  281. int playlists_ContextMenu( INT_PTR param1, HWND hHost, POINTS pts )
  282. {
  283. POINT pt;
  284. POINTSTOPOINT( pt, pts );
  285. if ( -1 == pt.x || -1 == pt.y )
  286. {
  287. HNAVITEM hItem = (HNAVITEM)param1;
  288. NAVITEMGETRECT itemRect;
  289. itemRect.fItem = FALSE;
  290. itemRect.hItem = hItem;
  291. if ( MLNavItem_GetRect( plugin.hwndLibraryParent, &itemRect ) )
  292. {
  293. MapWindowPoints( hHost, HWND_DESKTOP, (POINT *)&itemRect.rc, 2 );
  294. pt.x = itemRect.rc.left + 2;
  295. pt.y = itemRect.rc.top + 2;
  296. }
  297. }
  298. HMENU menu = GetSubMenu( g_context_menus, 1 );
  299. int r = Menu_TrackPopup( plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_NONOTIFY, pt.x, pt.y, hHost, NULL );
  300. switch ( r )
  301. {
  302. case IDC_NEWPLAYLIST:
  303. playlists_Add( hHost );
  304. break;
  305. case IDC_IMPORT_PLAYLIST_FROM_FILE:
  306. Playlist_importFromFile( hHost );
  307. break;
  308. case IDC_IMPORT_WINAMP_PLAYLIST:
  309. Playlist_importFromWinamp();
  310. break;
  311. case ID_PLAYLISTSMENU_IMPORTPLAYLISTFROMFOLDERS:
  312. Playlist_importFromFolders( hHost );
  313. break;
  314. case ID_SORTPLAYLIST_TITLE_A_Z:
  315. playlists_Sort( SORT_TITLE_ASCENDING );
  316. break;
  317. case ID_SORTPLAYLIST_TITLE_Z_A:
  318. playlists_Sort( SORT_TITLE_DESCENDING );
  319. break;
  320. case ID_SORTPLAYLIST_NUMBEROFITEMSASCENDING:
  321. playlists_Sort( SORT_NUMBER_ASCENDING );
  322. break;
  323. case ID_SORTPLAYLIST_NUMBEROFITEMSDESCENDING:
  324. playlists_Sort( SORT_NUMBER_DESCENDING );
  325. break;
  326. case ID_PLAYLISTS_HELP:
  327. myOpenURL( hHost, L"https://help.winamp.com/hc/articles/8109547717268-Winamp-Playlists" );
  328. break;
  329. }
  330. Sleep( 100 );
  331. MSG msg;
  332. while ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ); //eat return
  333. return TRUE;
  334. }
  335. // param1 = param of tree item, param2 = action type (below), param3 = HWND of main window
  336. INT_PTR playlists_OnClick(INT_PTR treeId, int clickType, HWND wnd)
  337. {
  338. switch (clickType)
  339. {
  340. case ML_ACTION_DBLCLICK:
  341. case ML_ACTION_ENTER:
  342. {
  343. if (FindTreeItem(treeId))
  344. {
  345. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  346. PlaylistInfo info;
  347. if (info.Associate(treeId))
  348. {
  349. playlist_SaveGUID(info.playlist_guid);
  350. bool flip = (clickType==ML_ACTION_ENTER) && (GetAsyncKeyState(VK_SHIFT)&0x8000);
  351. if ((!!(g_config->ReadInt(L"enqueuedef", 0) == 1)) ^ flip)
  352. mediaLibrary.EnqueueFile(info.GetFilename());
  353. else
  354. mediaLibrary.PlayFile(info.GetFilename());
  355. }
  356. return 1;
  357. }
  358. }
  359. break;
  360. }
  361. return 0;
  362. }
  363. int playlists_OnKeyDown(int treeId, NMTVKEYDOWN *p, HWND hwndDlg)
  364. {
  365. int ctrl = (GetAsyncKeyState(VK_CONTROL)&0x8000);
  366. int shift = (GetAsyncKeyState(VK_SHIFT)&0x8000);
  367. if (treeId == playlistsTreeId)
  368. {
  369. switch (p->wVKey)
  370. {
  371. case VK_INSERT:
  372. {
  373. if (shift && !ctrl) playlists_Add(plugin.hwndLibraryParent); return 1;
  374. }
  375. }
  376. }
  377. else if (FindTreeItem(treeId))
  378. {
  379. switch (p->wVKey)
  380. {
  381. case VK_F2: if (!shift && !ctrl) RenamePlaylist(tree_to_guid_map[treeId], plugin.hwndLibraryParent); return 1;
  382. case VK_INSERT: if (shift && !ctrl) playlists_Add(plugin.hwndLibraryParent); return 1;
  383. case VK_DELETE: if (!shift && !ctrl) DeletePlaylist(tree_to_guid_map[treeId], hwndDlg, true); return 1;
  384. }
  385. }
  386. return 0;
  387. }
  388. int playlists_OnDrag(int treeId, POINT *pt, int *type)
  389. {
  390. if (FindTreeItem(treeId))
  391. {
  392. *type = ML_TYPE_FILENAMES;
  393. return 1;
  394. }
  395. return 0;
  396. }
  397. int playlists_OnDrop(int treeId, POINT *pt, int destTreeId)
  398. {
  399. if (FindTreeItem(treeId))
  400. {
  401. AutoLockT<api_playlists> lock (AGAVE_API_PLAYLISTS);
  402. PlaylistInfo info;
  403. if (info.Associate(treeId))
  404. {
  405. if (destTreeId == playlistsTreeId)
  406. {
  407. mediaLibrary.RemoveTreeItem(info.treeId);
  408. tree_to_guid_map.erase(info.treeId);
  409. AGAVE_API_PLAYLISTS->MoveBefore(info.GetIndex(), 0);
  410. // TODO: move most of this to PlaylistsCB
  411. // TODO use the more native code like in MakeTree(..)
  412. MLTREEITEMW src = {sizeof(MLTREEITEMW), };
  413. src.title = const_cast<wchar_t *>(info.GetName());
  414. src.hasChildren = 0;
  415. src.parentId = playlistsTreeId;
  416. src.id = destTreeId;
  417. src.imageIndex = (!info.GetCloud() ? imgPL : imgCloudPL);
  418. mediaLibrary.InsertTreeItem(src);
  419. info.treeId = src.id;
  420. tree_to_guid_map[info.treeId] = info.playlist_guid;
  421. mediaLibrary.SelectTreeItem(info.treeId);
  422. }
  423. else if (FindTreeItem(destTreeId))
  424. {
  425. PlaylistInfo dest;
  426. if (dest.Associate(destTreeId))
  427. {
  428. mediaLibrary.RemoveTreeItem(info.treeId);
  429. tree_to_guid_map.erase(info.treeId);
  430. AGAVE_API_PLAYLISTS->MoveBefore(info.GetIndex(), dest.GetIndex()+1);
  431. // TODO: move most of this to PlaylistsCB
  432. // TODO use the more native code like in MakeTree(..)
  433. MLTREEITEMW src = {sizeof(MLTREEITEMW), };
  434. src.title = const_cast<wchar_t *>(info.GetName());
  435. src.hasChildren = 0;
  436. src.parentId = playlistsTreeId;
  437. src.id = destTreeId;
  438. src.imageIndex = (!info.GetCloud() ? imgPL : imgCloudPL);
  439. mediaLibrary.InsertTreeItem(src);
  440. info.treeId = src.id;
  441. tree_to_guid_map[info.treeId] = info.playlist_guid;
  442. mediaLibrary.SelectTreeItem(info.treeId);
  443. }
  444. return 1;
  445. }
  446. else
  447. {
  448. playlist_SaveGUID(info.playlist_guid);
  449. info.Refresh();
  450. mlPlaylist pl;
  451. pl.filename = info.GetFilename();
  452. pl.length = info.GetLength();
  453. pl.numItems = info.GetSize();
  454. pl.title = info.GetName();
  455. mlDropItemStruct m = {0};
  456. m.p = *pt;
  457. m.flags = 0;
  458. m.type = ML_TYPE_PLAYLIST;
  459. m.result = 0;
  460. m.data = (void *) & pl;
  461. m.name = 0;
  462. pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
  463. /* TODO: fall back to this is result fails?
  464. mlDropItemStruct m = {0};
  465. m.p = *pt;
  466. m.flags = ML_HANDLEDRAG_FLAG_NAME;
  467. m.type=ML_TYPE_FILENAMES;
  468. m.result = 0;
  469. char filename[1025] = {0};
  470. lstrcpynA(filename, AutoCharFn(playlists[index].filename), 1024);
  471. filename[lstrlenA(filename)+1]=0;
  472. m.data=(void *)filename;
  473. AutoChar charTitle(playlists[index].title);
  474. m.name = charTitle;
  475. pluginHandleIpcMessage(ML_IPC_HANDLEDROP, (WPARAM)&m);
  476. */
  477. }
  478. }
  479. }
  480. return 0;
  481. }