subscriptionView.cpp 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691
  1. #include "main.h"
  2. #include "./subscriptionView.h"
  3. #include "api__ml_wire.h"
  4. #include "./util.h"
  5. #include "Feeds.h"
  6. #include "RFCDate.h"
  7. #include "Cloud.h"
  8. #include "./channelEditor.h"
  9. #include "BackgroundDownloader.h"
  10. #include "./service.h"
  11. #include "./layout.h"
  12. #include "../nu/menushortcuts.h"
  13. #include "..\..\General\gen_ml/ml_ipc.h"
  14. #include "..\..\General\gen_ml/ml_ipc_0313.h"
  15. #include "../omBrowser/browserView.h"
  16. #include "./navigation.h"
  17. #include <strsafe.h>
  18. #include "../../../WAT/WAT.h"
  19. #ifndef HDF_SORTUP
  20. #define HDF_SORTUP 0x0400
  21. #define HDF_SORTDOWN 0x0200
  22. #endif // !HDF_SORTUP
  23. using namespace Nullsoft::Utility;
  24. int itemTitleWidth = 200;
  25. int itemDateWidth = 120;
  26. int itemMediaWidth = 100;
  27. int itemSizeWidth = 100;
  28. int currentItemSort = 1; // -1 means no sort active
  29. bool itemSortAscending = false;
  30. bool channelSortAscending = true;
  31. int channelLastSelection = -1;
  32. float htmlDividerPercent = 0.666f;
  33. float channelDividerPercent = 0.333f;
  34. extern int IPC_LIBRARY_SENDTOMENU;
  35. extern librarySendToMenuStruct s;
  36. static int episode_info_cy;
  37. HMENU g_context_menus3 = NULL;
  38. extern Cloud cloud;
  39. extern wchar_t serviceUrl[1024];
  40. #define SUBSCRIPTIONVIEW_NAME L"SubscriptionView"
  41. typedef enum
  42. {
  43. COLUMN_TITLE = 0,
  44. COLUMN_DATEADDED,
  45. //COLUMN_MEDIA,
  46. COLUMN_MEDIA_TIME,
  47. COLUMN_MEDIA_SIZE,
  48. } ListColumns;
  49. typedef enum
  50. {
  51. LI_CHANNEL = 0,
  52. LI_ITEM,
  53. LI_VERT,
  54. LI_FINDNEW,
  55. LI_ADD,
  56. LI_EDIT,
  57. LI_DELETE,
  58. LI_REFRESH,
  59. LI_HORZ,
  60. LI_EPISODE_INFO,
  61. LI_INFO,
  62. LI_PLAY,
  63. LI_ENQUEUE,
  64. LI_CUSTOM,
  65. LI_DOWNLOAD,
  66. LI_VISIT,
  67. LI_STATUS,
  68. LI_LAST
  69. } LayoutItems;
  70. typedef struct __PODCAST
  71. {
  72. LONG vertDivider;
  73. LONG horzDivider;
  74. LONG bottomRowSpace;
  75. LONG middleRowSpace;
  76. HRGN updateRegion;
  77. POINTS updateOffset;
  78. size_t channelActive;
  79. BOOL channelAscending;
  80. BOOL itemAscending;
  81. LPWSTR infoUrl;
  82. LPWSTR description;
  83. } PODCAST;
  84. #define GetPodcast(__hwnd) ((PODCAST*)GetPropW((__hwnd), MAKEINTATOM(VIEWPROP)))
  85. #define LAYOUTREASON_RESIZE 0
  86. #define LAYOUTREASON_DIV_LEFT 1
  87. #define LAYOUTREASON_DIV_RIGHT 2
  88. #define LAYOUTREASON_DIV_TOP 3
  89. #define LAYOUTREASON_DIV_BOTTOM 4
  90. #define UPDATE_TIMER 12
  91. #define UPDATE_DELAY 0
  92. static void CALLBACK PodcastChannel_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed);
  93. static void CALLBACK PodcastItem_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed);
  94. static size_t PodcastChannel_GetActive(HWND hwnd);
  95. ptrdiff_t inline CopyCharW(wchar_t *dest, const wchar_t *src)
  96. {
  97. wchar_t *end = CharNextW(src);
  98. ptrdiff_t count = end-src;
  99. for (ptrdiff_t i=0;i<count;i++)
  100. {
  101. *dest++=*src++;
  102. }
  103. return count;
  104. }
  105. // TODO for the moment, this is just to cleanup bad
  106. // urls which use spaces instead of %20 in the feed
  107. wchar_t *urlencode( wchar_t *p )
  108. {
  109. if ( p )
  110. {
  111. wchar_t buf[ MAX_PATH * 4 ], *i = buf;
  112. StringCchCopyW( buf, MAX_PATH * 4, p );
  113. while ( p && *p )
  114. {
  115. if ( !StrCmpNW( i, L" ", 1 ) )
  116. {
  117. *i++ = L'%';
  118. *i++ = L'2';
  119. *i = L'0';
  120. p += 1;
  121. }
  122. else
  123. {
  124. CopyCharW( i, p );
  125. p = CharNextW( p );
  126. }
  127. i = CharNextW( i );
  128. }
  129. *i = 0;
  130. return _wcsdup( buf );
  131. }
  132. return NULL;
  133. }
  134. static void SubscriptionView_EnableMenuCommands(HMENU hMenu, const INT *commandList, INT commandCount, BOOL fEnable)
  135. {
  136. UINT uEnable = MF_BYCOMMAND | MF_ENABLED;
  137. if (FALSE == fEnable) uEnable |= (MF_GRAYED | MF_DISABLED);
  138. for (INT i = 0; i < commandCount; i++)
  139. {
  140. EnableMenuItem(hMenu, commandList[i], uEnable);
  141. }
  142. }
  143. static void PodcastItem_EnableButtons(HWND hwnd, BOOL fEnable)
  144. {
  145. static const INT szButtons[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_DOWNLOAD, };
  146. for(INT i = 0; i < ARRAYSIZE(szButtons); i++)
  147. {
  148. HWND hControl = GetDlgItem(hwnd, szButtons[i]);
  149. if (NULL != hControl)
  150. {
  151. EnableWindow(hControl, fEnable);
  152. }
  153. }
  154. }
  155. static void PodcastChannel_EnableButtons(HWND hwnd, BOOL fEnable)
  156. {
  157. static const INT szButtons[] = { IDC_EDIT, IDC_REFRESH, IDC_DELETE, };
  158. HWND hControl;
  159. for(INT i = 0; i < ARRAYSIZE(szButtons); i++)
  160. {
  161. hControl = GetDlgItem(hwnd, szButtons[i]);
  162. if (NULL != hControl)
  163. {
  164. EnableWindow(hControl, fEnable);
  165. }
  166. }
  167. }
  168. static HRESULT SubscriptionView_FormatFont(LPWSTR pszBuffer, INT cchBufferMax, HFONT hFont)
  169. {
  170. const WCHAR szTemplate[] = L"font: %s %d %dpx \"%s\";";
  171. if (NULL == pszBuffer)
  172. return E_POINTER;
  173. pszBuffer[0] = L'\0';
  174. if (NULL == hFont) return E_POINTER;
  175. LOGFONT lf = {0};
  176. if (0 == GetObject(hFont, sizeof(LOGFONT), &lf))
  177. return E_FAIL;
  178. if (lf.lfHeight < 0)
  179. lf.lfHeight = -lf.lfHeight;
  180. return StringCchPrintfEx(pszBuffer, cchBufferMax, NULL, NULL, STRSAFE_IGNORE_NULLS,
  181. szTemplate, ((0 == lf.lfItalic) ? L"normal" : L"italic"),
  182. lf.lfWeight, lf.lfHeight, L"Arial"/*lf.lfFaceName*/);
  183. }
  184. static BOOL SubscriptionView_SetDescription(HWND hwnd, LPCWSTR pszInfo)
  185. {
  186. HWND hBrowser = GetDlgItem(hwnd, IDC_DESCRIPTION);
  187. if (NULL == hBrowser) return FALSE;
  188. const WCHAR szTemplate[] = L"<html>"
  189. L"<base target=\"_blank\">"
  190. L"<style type=\"text/css\"> body { %s overflow-y: auto; overflow-x: auto;}</style>"
  191. L"<body>%s</body>"
  192. L"</html>";
  193. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  194. WCHAR szFont[128] = {0};
  195. HFONT hFont = (HFONT)SendMessage(hItems, WM_GETFONT, 0, 0L);
  196. if (FAILED(SubscriptionView_FormatFont(szFont, ARRAYSIZE(szFont), hFont)))
  197. szFont[0] = L'\0';
  198. if (NULL == pszInfo) pszInfo = L"";
  199. INT cchLen = lstrlen(pszInfo) + 1;
  200. cchLen += lstrlen(szFont);
  201. cchLen += ARRAYSIZE(szTemplate);
  202. BSTR documentData = SysAllocStringLen(NULL, cchLen);
  203. if (NULL == documentData) return FALSE;
  204. BOOL result;
  205. if ( SUCCEEDED( StringCchPrintfEx( documentData, cchLen, NULL, NULL, STRSAFE_IGNORE_NULLS, szTemplate, szFont, pszInfo ) ) )
  206. result = BrowserView_WriteDocument( hBrowser, documentData, TRUE );
  207. else
  208. result = FALSE;
  209. if (FALSE == result)
  210. SysFreeString(documentData);
  211. return result;
  212. }
  213. static void SubscriptionView_UpdateInfo( HWND hwnd )
  214. {
  215. PODCAST *podcast = GetPodcast( hwnd );
  216. if ( NULL == podcast )
  217. return;
  218. AutoLock channelLock( channels LOCKNAME( "UpdateInfo" ) );
  219. LPCWSTR infoText = NULL;
  220. BOOL itemEnabled = FALSE;
  221. size_t iChannel = PodcastChannel_GetActive( hwnd );
  222. if ( BAD_CHANNEL != iChannel )
  223. {
  224. Channel *channel = &channels[ iChannel ];
  225. HWND hItems = GetDlgItem( hwnd, IDC_ITEMLIST );
  226. INT selectedCount = ( NULL != hItems ) ? (INT)SNDMSG( hItems, LVM_GETSELECTEDCOUNT, 0, 0L ) : 0;
  227. size_t iItem = ( 1 == selectedCount ) ? (size_t)SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_SELECTED ) : BAD_ITEM;
  228. if ( iItem < channel->items.size() )
  229. {
  230. if ( FALSE == podcast->itemAscending )
  231. iItem = channel->items.size() - iItem - 1;
  232. infoText = channel->items[ iItem ].description;
  233. }
  234. if ( NULL == infoText || L'\0' == *infoText )
  235. infoText = channel->description;
  236. if ( selectedCount > 0 )
  237. {
  238. INT iSelected = -1;
  239. while ( -1 != ( iSelected = SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED ) ) )
  240. {
  241. size_t t = iSelected;
  242. if ( t < channel->items.size() )
  243. {
  244. if ( FALSE == podcast->itemAscending )
  245. t = channel->items.size() - t - 1;
  246. LPCWSTR url = channel->items[ t ].url;
  247. if ( NULL != url && L'\0' != *url )
  248. {
  249. itemEnabled = TRUE;
  250. break;
  251. }
  252. }
  253. }
  254. }
  255. }
  256. if ( podcast->description != infoText && CSTR_EQUAL != CompareString( CSTR_INVARIANT, 0, podcast->description, -1, infoText, -1 ) )
  257. {
  258. Plugin_FreeString( podcast->description );
  259. podcast->description = Plugin_CopyString( infoText );
  260. SubscriptionView_SetDescription( hwnd, podcast->description );
  261. }
  262. PodcastChannel_EnableButtons( hwnd, ( BAD_CHANNEL != iChannel ) );
  263. PodcastItem_EnableButtons( hwnd, itemEnabled );
  264. }
  265. static BOOL SubscriptionView_SortItems(size_t iChannel, int sortColumn)
  266. {
  267. if (iChannel >= channels.size())
  268. return FALSE;
  269. AutoLock lock (channels LOCKNAME("SortItems"));
  270. Channel &channel = channels[iChannel];
  271. switch (sortColumn)
  272. {
  273. case COLUMN_TITLE: channel.SortByTitle(); return TRUE;
  274. case COLUMN_MEDIA_TIME: channel.SortByMediaTime(); return TRUE;
  275. case COLUMN_MEDIA_SIZE: channel.SortByMediaSize(); return TRUE;
  276. //case COLUMN_MEDIA: channel.SortByMedia(); return TRUE;
  277. case COLUMN_DATEADDED: channel.SortByDate(); return TRUE;
  278. }
  279. return FALSE;
  280. }
  281. static BOOL SubscriptionView_SortChannels(int sortColumn)
  282. {
  283. AutoLock lock (channels LOCKNAME("SortItems"));
  284. switch (sortColumn)
  285. {
  286. case COLUMN_TITLE: channels.SortByTitle(); return TRUE;
  287. }
  288. return FALSE;
  289. }
  290. static INT SubscriptionView_GetListSortColumn(HWND hwnd, INT listId, BOOL *fAscending)
  291. {
  292. HWND hItems = GetDlgItem(hwnd, listId);
  293. if (NULL != hItems)
  294. {
  295. HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
  296. if (NULL != hHeader)
  297. {
  298. HDITEM item;
  299. item.mask = HDI_FORMAT;
  300. INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
  301. for (INT i = 0; i < count; i++)
  302. {
  303. if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item) &&
  304. 0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
  305. {
  306. if (NULL != fAscending)
  307. {
  308. *fAscending = (0 != (HDF_SORTUP & item.fmt));
  309. }
  310. return i;
  311. }
  312. }
  313. }
  314. }
  315. return -1;
  316. }
  317. static void SubscriptionView_SetListSortColumn(HWND hwnd, INT listId, INT index, BOOL fAscending)
  318. {
  319. HWND hItems = GetDlgItem(hwnd, listId);
  320. if (NULL == hItems) return;
  321. HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L);
  322. if (NULL == hHeader) return;
  323. HDITEM item;
  324. item.mask = HDI_FORMAT;
  325. // reset first (ml req)
  326. INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L);
  327. for (INT i = 0; i < count; i++)
  328. {
  329. if (index != i && FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item))
  330. {
  331. if (0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt))
  332. {
  333. item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
  334. SNDMSG(hHeader, HDM_SETITEM, i, (LPARAM)&item);
  335. }
  336. }
  337. }
  338. if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, index, (LPARAM)&item))
  339. {
  340. INT fmt = item.fmt & ~(HDF_SORTUP | HDF_SORTDOWN);
  341. fmt |= (FALSE == fAscending) ? HDF_SORTDOWN : HDF_SORTUP;
  342. if (fmt != item.fmt)
  343. {
  344. item.fmt = fmt;
  345. SNDMSG(hHeader, HDM_SETITEM, index, (LPARAM)&item);
  346. }
  347. }
  348. }
  349. static BOOL SubscriptionView_UpdateInfoUrl(HWND hwnd)
  350. {
  351. AutoLock lock (channels LOCKNAME("UpdateInfoUrl"));
  352. BOOL result = FALSE;
  353. BOOL buttonEnable = FALSE;
  354. PODCAST *podcast = GetPodcast(hwnd);
  355. if (NULL != podcast)
  356. {
  357. Plugin_FreeString(podcast->infoUrl);
  358. podcast->infoUrl = NULL;
  359. LPCWSTR pszUrl = NULL;
  360. size_t iChannel = PodcastChannel_GetActive(hwnd);
  361. if (BAD_CHANNEL != iChannel)
  362. {
  363. Channel *channel = &channels[iChannel];
  364. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  365. INT selectedCount = (NULL != hItems ) ? (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L) : 0;
  366. size_t iItem = (1 == selectedCount) ? (size_t)SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_SELECTED) : BAD_ITEM;
  367. if (iItem < channel->items.size())
  368. {
  369. if (FALSE == podcast->itemAscending)
  370. iItem = channel->items.size() - iItem - 1;
  371. pszUrl = channel->items[iItem].link;
  372. }
  373. if (NULL == pszUrl || L'\0' == *pszUrl)
  374. pszUrl = channel->link;
  375. }
  376. if (NULL != pszUrl && L'\0' != *pszUrl)
  377. {
  378. podcast->infoUrl = Plugin_CopyString(pszUrl);
  379. if (NULL != podcast->infoUrl)
  380. {
  381. buttonEnable = TRUE;
  382. result = TRUE;
  383. }
  384. }
  385. }
  386. HWND hButton = GetDlgItem(hwnd, IDC_VISIT);
  387. if (NULL != hButton) EnableWindow(hButton, buttonEnable);
  388. return result;
  389. }
  390. static INT SubscriptionView_Play(HWND hwnd, size_t iChannel, size_t *indexList, size_t indexCount, BOOL fEnqueue, BOOL fForce)
  391. {
  392. AutoLock channelLock (channels LOCKNAME("Play"));
  393. if (BAD_CHANNEL == iChannel || iChannel >= channels.size())
  394. return 0;
  395. Channel *channel = &channels[iChannel];
  396. INT cchBuffer = 0;
  397. for(size_t i = 0; i < indexCount; i++)
  398. {
  399. size_t iItem = indexList[i];
  400. if (iItem < channel->items.size())
  401. {
  402. WCHAR szPath[MAX_PATH * 2] = {0};
  403. RSS::Item *downloadItem = &channel->items[iItem];
  404. if ((downloadItem->url && downloadItem->url[0]) &&
  405. SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
  406. PathFileExists(szPath))
  407. {
  408. cchBuffer += (lstrlen(szPath) + 1);
  409. }
  410. else
  411. {
  412. wchar_t* url = urlencode(channel->items[iItem].url);
  413. LPCWSTR p = url;
  414. if (NULL != p)
  415. {
  416. cchBuffer += (lstrlen(p) + 1);
  417. free(url);
  418. }
  419. }
  420. }
  421. }
  422. if (0 == cchBuffer)
  423. return 0;
  424. cchBuffer++;
  425. LPWSTR pszBuffer = (LPWSTR)calloc(cchBuffer, sizeof(WCHAR));
  426. if (NULL == pszBuffer) return 0;
  427. LPWSTR c = pszBuffer;
  428. size_t r = cchBuffer;
  429. INT playCount = 0;
  430. for(size_t i = 0; i < indexCount; i++)
  431. {
  432. size_t iItem = indexList[i];
  433. if (iItem < channel->items.size())
  434. {
  435. WCHAR szPath[MAX_PATH * 2] = {0};
  436. RSS::Item *downloadItem = &channel->items[iItem];
  437. if ((downloadItem->url && downloadItem->url[0]) &&
  438. SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
  439. PathFileExists(szPath))
  440. {
  441. LPCWSTR p = szPath;
  442. if (NULL != p && L'\0' != *p)
  443. {
  444. if (FAILED(StringCchCopyExW(c, r, p, &c, &r, STRSAFE_NULL_ON_FAILURE)) || 0 == r)
  445. break;
  446. c++;
  447. r--;
  448. channel->items[iItem].listened = true;
  449. playCount++;
  450. }
  451. }
  452. else
  453. {
  454. wchar_t* url = urlencode(channel->items[iItem].url);
  455. LPCWSTR p = url;
  456. if (NULL != p && L'\0' != *p)
  457. {
  458. if (FAILED(StringCchCopyExW(c, r, p, &c, &r, STRSAFE_NULL_ON_FAILURE)) || 0 == r)
  459. {
  460. free(url);
  461. break;
  462. }
  463. free(url);
  464. c++;
  465. r--;
  466. channel->items[iItem].listened = true;
  467. playCount++;
  468. }
  469. }
  470. }
  471. }
  472. if (c != pszBuffer)
  473. {
  474. *c = L'\0';
  475. // make sure this is initialised as default handler requires this being zeroed
  476. mlSendToWinampStruct send = {ML_TYPE_STREAMNAMESW,pszBuffer,0};
  477. // otherwise we've a specific action and need to tell ML to do as we want
  478. if (TRUE == fForce)
  479. send.enqueue = ((FALSE == fEnqueue) ? 0 : 1) | 2;
  480. SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SENDTOWINAMP, (WPARAM)&send);
  481. }
  482. free(pszBuffer);
  483. return playCount;
  484. }
  485. static INT SubscriptionView_PlayChannel(HWND hwnd, size_t iChannel, BOOL fEnqueue, BOOL fForce)
  486. {
  487. AutoLock channelLock (channels LOCKNAME("PlayChannel"));
  488. if (BAD_CHANNEL == iChannel || iChannel >= channels.size())
  489. return 0;
  490. size_t count = channels.size();
  491. size_t *list = NULL;
  492. if (0 != count)
  493. {
  494. list = (size_t*)calloc(count, sizeof(size_t));
  495. if (NULL == list) return 0;
  496. for (size_t i = 0; i < count; i++) list[i] = i;
  497. }
  498. INT result = SubscriptionView_Play(hwnd, iChannel, list, count, fEnqueue, fForce);
  499. if (NULL != list)
  500. free(list);
  501. return result;
  502. }
  503. static void PodcastItem_InitializeList(HWND hwnd)
  504. {
  505. HWND hControl = GetDlgItem(hwnd, IDC_ITEMLIST);
  506. if (NULL == hControl)
  507. return;
  508. UINT styleEx = (UINT)GetWindowLongPtr(hControl, GWL_EXSTYLE);
  509. SetWindowLongPtr(hControl, GWL_EXSTYLE, styleEx & ~WS_EX_NOPARENTNOTIFY);
  510. styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | /*LVS_EX_HEADERDRAGDROP |*/ LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
  511. SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
  512. SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
  513. LVCOLUMN lvc = {0};
  514. WCHAR szBuffer[128] = {0};
  515. lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
  516. lvc.pszText = szBuffer;
  517. lvc.fmt = LVCFMT_LEFT;
  518. lvc.cx = itemTitleWidth;
  519. WASABI_API_LNGSTRINGW_BUF( IDS_ITEM, szBuffer, ARRAYSIZE( szBuffer ) );
  520. SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc);
  521. lvc.fmt = LVCFMT_RIGHT;
  522. lvc.cx = itemDateWidth;
  523. WASABI_API_LNGSTRINGW_BUF( IDS_DATE_ADDED, szBuffer, ARRAYSIZE( szBuffer ) );
  524. SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)1, (LPARAM)&lvc);
  525. lvc.fmt = LVCFMT_RIGHT;
  526. lvc.cx = itemMediaWidth;
  527. WASABI_API_LNGSTRINGW_BUF( IDS_MEDIA_TIME, szBuffer, ARRAYSIZE( szBuffer ) );
  528. SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)2, (LPARAM)&lvc);
  529. lvc.fmt = LVCFMT_RIGHT;
  530. lvc.cx = itemSizeWidth;
  531. WASABI_API_LNGSTRINGW_BUF( IDS_MEDIA_SIZE, szBuffer, ARRAYSIZE( szBuffer ) );
  532. SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)3, (LPARAM)&lvc);
  533. HIMAGELIST imageList = ImageList_Create( 15, 15, ILC_COLOR24, 3, 0 );
  534. if ( imageList != NULL )
  535. {
  536. HIMAGELIST prevList = (HIMAGELIST)SNDMSG( hControl, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)imageList );
  537. if ( prevList != NULL )
  538. ImageList_Destroy( prevList );
  539. }
  540. MLSKINWINDOW skinWindow;
  541. skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
  542. skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
  543. skinWindow.hwndToSkin = hControl;
  544. MLSkinWindow( plugin.hwndLibraryParent, &skinWindow );
  545. }
  546. static void PodcastItem_SelectionChanged(HWND hwnd, BOOL fImmediate)
  547. {
  548. KillTimer(hwnd, UPDATE_TIMER);
  549. if (FALSE == fImmediate)
  550. {
  551. SetTimer(hwnd, UPDATE_TIMER, UPDATE_DELAY, PodcastItem_UpdateTimer);
  552. return;
  553. }
  554. SubscriptionView_UpdateInfoUrl(hwnd);
  555. SubscriptionView_UpdateInfo(hwnd);
  556. }
  557. static HMENU PodcastItem_GetMenu(HWND hwnd, HMENU baseMenu, INT iItem)
  558. {
  559. HMENU menu = GetSubMenu(baseMenu, 1);
  560. if (NULL == menu)
  561. return NULL;
  562. PodcastItem_SelectionChanged(hwnd, TRUE);
  563. // update the explore media menu item from the download button state (hence the two IDC_DOWNLOAD)
  564. const INT szExtras[] = { IDC_PLAY, IDC_ENQUEUE, IDC_DOWNLOAD, IDC_DOWNLOAD, IDC_VISIT, };
  565. for (INT i = 0; i < ARRAYSIZE(szExtras); i++)
  566. {
  567. HWND hButton = GetDlgItem(hwnd, szExtras[i]);
  568. UINT uEnable = MF_BYCOMMAND | MF_ENABLED;
  569. if (NULL == hButton || (-1 == iItem) || 0 != (WS_DISABLED & GetWindowLongPtr(hButton, GWL_STYLE)))
  570. uEnable |= (MF_GRAYED | MF_DISABLED);
  571. EnableMenuItem(menu, (i == 3 ? IDC_EXPLORERITEMFOLDER : szExtras[i]), uEnable);
  572. }
  573. EnableMenuItem(menu, 2, MF_BYPOSITION | ((-1 == iItem) ? (MF_GRAYED | MF_DISABLED) : MF_ENABLED));
  574. { // send-to menu shit...
  575. ZeroMemory(&s, sizeof(s));
  576. IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
  577. if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
  578. {
  579. s.mode = 1;
  580. s.hwnd = hwnd;
  581. s.data_type = ML_TYPE_FILENAMESW;
  582. s.ctx[1] = 1;
  583. s.build_hMenu = GetSubMenu(menu, 2);
  584. }
  585. }
  586. UpdateMenuItems(hwnd, menu);
  587. // check if the menu itsm is shown as having been download and
  588. // if it hasn't then is nicer to hide 'explore media folder'
  589. {
  590. AutoLock channelLock (channels LOCKNAME("ItemMenu"));
  591. PODCAST *podcast = GetPodcast(hwnd);
  592. if (NULL != podcast)
  593. {
  594. size_t iChannel = PodcastChannel_GetActive(hwnd);
  595. if (BAD_CHANNEL != iChannel)
  596. {
  597. Channel *channel = &channels[iChannel];
  598. if (iItem < (INT)channel->items.size())
  599. {
  600. if (FALSE == podcast->itemAscending)
  601. iItem = (INT)channel->items.size() - iItem - 1;
  602. RSS::Item *downloadItem = &channel->items[iItem];
  603. if(downloadItem->downloaded == false)
  604. {
  605. DeleteMenu(menu, IDC_EXPLORERITEMFOLDER, MF_BYCOMMAND);
  606. }
  607. }
  608. }
  609. }
  610. }
  611. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  612. INT iCount = (NULL != hItems) ? (INT)SNDMSG(hItems, LVM_GETITEMCOUNT, 0, 0L) : 0;
  613. INT command = IDC_REFRESH;
  614. SubscriptionView_EnableMenuCommands(menu, &command, 1, (0 != iCount));
  615. return menu;
  616. }
  617. static BOOL PodcastItem_Sort(HWND hwnd, INT iColumn, BOOL fAscending)
  618. {
  619. PODCAST *podcast = GetPodcast(hwnd);
  620. if (NULL == podcast) return FALSE;
  621. BOOL result = FALSE;
  622. size_t iChannel = PodcastChannel_GetActive(hwnd);
  623. if (BAD_CHANNEL != iChannel)
  624. {
  625. result = SubscriptionView_SortItems(iChannel, iColumn);
  626. }
  627. podcast->itemAscending = fAscending;
  628. SubscriptionView_SetListSortColumn(hwnd, IDC_ITEMLIST, iColumn, fAscending);
  629. if (FALSE != result)
  630. {
  631. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  632. if (NULL != hItems)
  633. InvalidateRect(hItems, NULL, TRUE);
  634. }
  635. return TRUE;
  636. }
  637. static size_t PodcastChannel_GetActive(HWND hwnd)
  638. {
  639. AutoLock lock (channels LOCKNAME("GetActive"));
  640. PODCAST *podcast = GetPodcast(hwnd);
  641. if (NULL != podcast)
  642. {
  643. size_t iChannel = podcast->channelActive;
  644. if (iChannel < channels.size())
  645. {
  646. return (size_t)iChannel;
  647. }
  648. }
  649. return BAD_CHANNEL;
  650. }
  651. static void PodcastInfo_InitializeList(HWND hwnd)
  652. {
  653. HWND hControl = GetDlgItem(hwnd, IDC_EPISODE_INFO);
  654. if ( hControl == NULL )
  655. return;
  656. MLSKINWINDOW skinWindow;
  657. skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
  658. skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
  659. skinWindow.hwndToSkin = hControl;
  660. MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
  661. MLSkinnedScrollWnd_ShowHorzBar(hControl, FALSE);
  662. MLSkinnedScrollWnd_ShowVertBar(hControl, FALSE);
  663. UINT styleEx;
  664. styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT;
  665. SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
  666. SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
  667. LVCOLUMN lvc = {0};
  668. WCHAR szBuffer[128] = {0};
  669. lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
  670. lvc.fmt = LVCFMT_LEFT;
  671. lvc.pszText = szBuffer;
  672. lvc.cx = 9999;
  673. WASABI_API_LNGSTRINGW_BUF(IDS_EPISODE_INFO, szBuffer, ARRAYSIZE(szBuffer));
  674. SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc);
  675. HWND hHeader = ListView_GetHeader(hControl);
  676. RECT rect;
  677. GetClientRect(hHeader, &rect);
  678. SetWindowPos(hControl, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
  679. }
  680. static void PodcastChannel_InitializeList(HWND hwnd)
  681. {
  682. HWND hControl = GetDlgItem(hwnd, IDC_CHANNELLIST);
  683. if (NULL == hControl)
  684. return;
  685. MLSKINWINDOW skinWindow = {0};
  686. skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS;
  687. skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW;
  688. skinWindow.hwndToSkin = hControl;
  689. MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
  690. UINT skinStyle = MLSkinnedWnd_GetStyle(hControl);
  691. skinStyle |= SWLVS_SELALWAYS;
  692. MLSkinnedWnd_SetStyle(hControl, skinStyle);
  693. MLSkinnedScrollWnd_ShowHorzBar(hControl, FALSE);
  694. UINT styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT;
  695. SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx);
  696. SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L);
  697. LVCOLUMN lvc = {0};
  698. WCHAR szBuffer[128] = {0};
  699. lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
  700. lvc.fmt = LVCFMT_LEFT;
  701. lvc.pszText = szBuffer;
  702. lvc.cx = 200;
  703. WASABI_API_LNGSTRINGW_BUF(IDS_CHANNEL, szBuffer, ARRAYSIZE(szBuffer));
  704. SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc);
  705. }
  706. static void PodcastChannel_SelectionChanged(HWND hwnd, BOOL fImmediate)
  707. {
  708. KillTimer(hwnd, UPDATE_TIMER);
  709. if (FALSE == fImmediate)
  710. {
  711. SetTimer(hwnd, UPDATE_TIMER, UPDATE_DELAY, PodcastChannel_UpdateTimer);
  712. return;
  713. }
  714. PODCAST *podcast = GetPodcast(hwnd);
  715. if (NULL == podcast)
  716. return;
  717. HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
  718. INT iSelected = (NULL != hChannel) ? (INT)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, LVNI_SELECTED) : -1;
  719. AutoLock channelLock (channels LOCKNAME("Channel Changed"));
  720. if (-1 != iSelected && ((size_t)iSelected) > channels.size())
  721. iSelected = -1;
  722. if (-1 != iSelected && FALSE == podcast->channelAscending)
  723. iSelected = (INT)channels.size() - iSelected - 1;
  724. podcast->channelActive = iSelected;
  725. if (-1 != iSelected)
  726. {
  727. INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, NULL);
  728. SubscriptionView_SortItems(iSelected, iSort);
  729. }
  730. size_t itemsCount = (-1 != iSelected) ? channels[iSelected].items.size() : 0;
  731. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  732. if (NULL != hItems)
  733. {
  734. SNDMSG(hItems, WM_SETREDRAW, FALSE, 0L);
  735. LVITEM lvi;
  736. lvi.state = 0;
  737. lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
  738. SNDMSG(hItems, LVM_SETSELECTIONMARK, 0, (LPARAM)-1);
  739. SNDMSG(hItems, LVM_SETITEMSTATE, -1, (LPARAM)&lvi);
  740. SNDMSG(hItems, LVM_SETITEMCOUNT, (WPARAM)itemsCount, 0L);
  741. SNDMSG(hItems, WM_SETREDRAW, TRUE, 0L);
  742. }
  743. SubscriptionView_UpdateInfoUrl(hwnd);
  744. SubscriptionView_UpdateInfo(hwnd);
  745. channelLastSelection = iSelected;
  746. }
  747. static HMENU PodcastChannel_GetMenu(HWND hwnd, HMENU baseMenu, INT iItem)
  748. {
  749. HMENU menu = GetSubMenu(baseMenu, 0);
  750. if (NULL == menu) return NULL;
  751. if (iItem != -1) PodcastChannel_SelectionChanged(hwnd, TRUE);
  752. if (iItem != -1)
  753. {
  754. DeleteMenu(menu, IDC_ADD, MF_BYCOMMAND);
  755. const INT szExtras[] = { IDC_REFRESH, IDC_EDIT, IDC_DELETE, };
  756. SubscriptionView_EnableMenuCommands(menu, szExtras, ARRAYSIZE(szExtras), (-1 != iItem));
  757. }
  758. else
  759. {
  760. DeleteMenu(menu, IDC_REFRESH, MF_BYCOMMAND);
  761. DeleteMenu(menu, IDC_EDIT, MF_BYCOMMAND);
  762. DeleteMenu(menu, IDC_DELETE, MF_BYCOMMAND);
  763. DeleteMenu(menu, IDC_VISIT, MF_BYCOMMAND);
  764. DeleteMenu(menu, 0, MF_BYPOSITION);
  765. DeleteMenu(menu, 0, MF_BYPOSITION);
  766. }
  767. return menu;
  768. }
  769. static BOOL PodcastChannel_SyncHeaderSize(HWND hwnd, BOOL fRedraw)
  770. {
  771. HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
  772. if (NULL == hChannel) return FALSE;
  773. RECT channelRect;
  774. HDITEM item;
  775. item.mask = HDI_WIDTH;
  776. HWND hHeader = (HWND)SNDMSG(hChannel, LVM_GETHEADER, 0, 0L);
  777. if (NULL == hHeader ||
  778. FALSE == GetClientRect(hChannel, &channelRect) ||
  779. FALSE == SNDMSG(hHeader, HDM_GETITEM, COLUMN_TITLE, (LPARAM)&item))
  780. {
  781. return FALSE;
  782. }
  783. LONG columnWidth = channelRect.right - channelRect.left;
  784. if (item.cxy == columnWidth) return TRUE;
  785. item.cxy = columnWidth;
  786. UINT windowStyle = GetWindowLongPtr(hHeader, GWL_STYLE);
  787. if (FALSE == fRedraw && 0 != (WS_VISIBLE & windowStyle))
  788. SetWindowLongPtr(hHeader, GWL_STYLE, windowStyle & ~WS_VISIBLE);
  789. SNDMSG(hHeader, HDM_SETITEM, COLUMN_TITLE, (LPARAM)&item);
  790. if (FALSE == fRedraw && 0 != (WS_VISIBLE & windowStyle))
  791. SetWindowLongPtr(hHeader, GWL_STYLE, windowStyle);
  792. return TRUE;
  793. }
  794. static BOOL PodcastChannel_Sort(HWND hwnd, INT iColumn, BOOL fAscending)
  795. {
  796. PODCAST *podcast = GetPodcast(hwnd);
  797. if (NULL == podcast) return FALSE;
  798. BOOL result = SubscriptionView_SortChannels(iColumn);
  799. podcast->channelAscending = fAscending;
  800. SubscriptionView_SetListSortColumn(hwnd, IDC_CHANNELLIST, iColumn, fAscending);
  801. if (FALSE != result)
  802. {
  803. HWND hItems = GetDlgItem(hwnd, IDC_CHANNELLIST);
  804. if (NULL != hItems)
  805. InvalidateRect(hItems, NULL, TRUE);
  806. }
  807. return result;
  808. }
  809. static BOOL PodcastChannel_SelectNext(HWND hwnd, BOOL fForward)
  810. {
  811. HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
  812. if (NULL == hChannel) return FALSE;
  813. INT iFocus = (INT)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, (LPARAM)LVNI_FOCUSED);
  814. if (FALSE != fForward)
  815. {
  816. iFocus++;
  817. INT iCount = (INT)SNDMSG(hChannel, LVM_GETITEMCOUNT, 0, 0L);
  818. if (iFocus >= iCount) return TRUE;
  819. }
  820. else
  821. {
  822. if (iFocus <= 0) return TRUE;
  823. iFocus--;
  824. }
  825. LVITEM lvi = {0};
  826. lvi.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
  827. SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
  828. lvi.state = lvi.stateMask;
  829. if (FALSE != SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)iFocus, (LPARAM)&lvi))
  830. {
  831. SNDMSG(hChannel, LVM_ENSUREVISIBLE, (WPARAM)iFocus, (LPARAM)FALSE);
  832. MLSkinnedScrollWnd_UpdateBars(hChannel, TRUE);
  833. }
  834. return TRUE;
  835. }
  836. static void CALLBACK PodcastChannel_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
  837. {
  838. KillTimer(hwnd, eventId);
  839. PodcastChannel_SelectionChanged(hwnd, TRUE);
  840. }
  841. static void CALLBACK PodcastItem_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
  842. {
  843. KillTimer(hwnd, eventId);
  844. PodcastItem_SelectionChanged(hwnd, TRUE);
  845. }
  846. wchar_t* PodcastCommand_OnSendToSelection(HWND hwnd)
  847. {
  848. AutoLock channelLock (channels LOCKNAME("SendTo"));
  849. PODCAST *podcast = GetPodcast(hwnd);
  850. if (NULL == podcast) return 0;
  851. size_t iChannel = PodcastChannel_GetActive(hwnd);
  852. if (BAD_CHANNEL == iChannel) return 0;
  853. Channel *channel = &channels[iChannel];
  854. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  855. if (NULL == hItems) return 0;
  856. INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
  857. if (0 == selectedCount) return 0;
  858. INT iSelected = -1;
  859. WCHAR szPath[MAX_PATH * 2] = {0}, *path = NULL;
  860. int buf_pos = 0, buf_size = 0;
  861. while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED)))
  862. {
  863. size_t iItem = iSelected;
  864. if (iItem < channel->items.size())
  865. {
  866. if (FALSE == podcast->itemAscending)
  867. iItem = channel->items.size() - iItem - 1;
  868. RSS::Item *downloadItem = &channel->items[iItem];
  869. if (!((downloadItem->url && downloadItem->url[0]) &&
  870. SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
  871. PathFileExists(szPath)))
  872. {
  873. lstrcpyn(szPath, downloadItem->url, ARRAYSIZE(szPath));
  874. }
  875. listbuild(&path, buf_size, buf_pos, szPath);
  876. }
  877. }
  878. if (path) path[buf_pos] = 0;
  879. return path;
  880. }
  881. static void SubscriptionView_ListContextMenu(HWND hwnd, INT controlId, POINTS pts)
  882. {
  883. HWND hControl = GetDlgItem(hwnd, controlId);
  884. if (NULL == hControl) return;
  885. POINT pt;
  886. POINTSTOPOINT(pt, pts);
  887. RECT controlRect, headerRect;
  888. if (FALSE == GetClientRect(hControl, &controlRect))
  889. SetRectEmpty(&controlRect);
  890. else
  891. MapWindowPoints(hControl, HWND_DESKTOP, (POINT*)&controlRect, 2);
  892. HWND hHeader = (HWND)SNDMSG(hControl, LVM_GETHEADER, 0, 0L);
  893. if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect))
  894. {
  895. SetRectEmpty(&headerRect);
  896. }
  897. if (-1 == pt.x && -1 == pt.y)
  898. {
  899. RECT rect;
  900. rect.left = LVIR_BOUNDS;
  901. INT iMark = SNDMSG(hControl, LVM_GETNEXTITEM, -1, LVNI_SELECTED | LVNI_FOCUSED);
  902. if (-1 == iMark) iMark = SNDMSG(hControl, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
  903. if (-1 != iMark && FALSE != SNDMSG(hControl, LVM_GETITEMRECT, (WPARAM)iMark, (LPARAM)&rect))
  904. {
  905. pt.x = rect.left + 4;
  906. if(pt.x > rect.right) pt.x = rect.right;
  907. pt.y = rect.bottom - (rect.bottom - rect.top)/3;
  908. MapWindowPoints(hControl, HWND_DESKTOP, &pt, 1);
  909. }
  910. }
  911. INT iItem = -1;
  912. if ((-1 != pt.x || -1 != pt.y) && FALSE != PtInRect(&controlRect, pt))
  913. {
  914. if (FALSE != PtInRect(&headerRect, pt))
  915. {
  916. return;
  917. }
  918. else
  919. {
  920. LVHITTESTINFO hitTest;
  921. hitTest.pt = pt;
  922. MapWindowPoints(HWND_DESKTOP, hControl, &hitTest.pt, 1);
  923. iItem = (INT)SNDMSG(hControl, LVM_HITTEST, 0, (LPARAM)&hitTest);
  924. if (0 == (LVHT_ONITEM & hitTest.flags)) iItem = -1;
  925. }
  926. }
  927. else
  928. {
  929. pt.x = controlRect.left + 2;
  930. pt.y = controlRect.top + 2;
  931. if (headerRect.top == controlRect.top && headerRect.bottom > controlRect.top)
  932. pt.y = headerRect.bottom + 2;
  933. }
  934. HMENU baseMenu = WASABI_API_LOADMENU(IDR_MENU1);
  935. if (NULL == baseMenu) return;
  936. HMENU menu = NULL;
  937. switch(controlId)
  938. {
  939. case IDC_ITEMLIST: menu = PodcastItem_GetMenu(hwnd, baseMenu, iItem); break;
  940. case IDC_CHANNELLIST: menu = PodcastChannel_GetMenu(hwnd, baseMenu, iItem); break;
  941. }
  942. if (NULL != menu)
  943. {
  944. int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN | 0x1000/*TPM_VERPOSANIMATION*/, pt.x, pt.y, hwnd, NULL);
  945. if (!SendMessage(hwnd, WM_COMMAND, r, 0))
  946. {
  947. s.menu_id = r;
  948. if (s.mode == 2 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
  949. {
  950. s.mode = 3;
  951. wchar_t* path = PodcastCommand_OnSendToSelection(hwnd);
  952. if (path && *path)
  953. {
  954. s.data = path;
  955. SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s, IPC_LIBRARY_SENDTOMENU);
  956. free(path);
  957. }
  958. }
  959. }
  960. if (s.mode)
  961. {
  962. s.mode=4;
  963. SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU); // cleanup
  964. }
  965. }
  966. DestroyMenu(baseMenu);
  967. }
  968. static void SubscriptionView_UpdateLayout(HWND hwnd, BOOL fRedraw, UINT uReason, HRGN validRegion, POINTS layoutOffset)
  969. {
  970. PODCAST *podcast = GetPodcast(hwnd);
  971. if (NULL == podcast) return;
  972. RECT clientRect;
  973. if (FALSE == GetClientRect(hwnd, &clientRect)) return;
  974. LONG clientWidth = clientRect.right - clientRect.left - WASABI_API_APP->getScaleX(2);
  975. LONG clientHeight = clientRect.bottom - clientRect.top;
  976. const INT szItems[LI_LAST] = { IDC_CHANNELLIST, IDC_ITEMLIST, IDC_VDIV,
  977. IDC_FINDNEW, IDC_ADD, IDC_EDIT, IDC_DELETE, IDC_REFRESH,
  978. IDC_HDIV,
  979. IDC_EPISODE_INFO, IDC_DESCRIPTION,
  980. IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_DOWNLOAD, IDC_VISIT, IDC_STATUS};
  981. LAYOUTITEM *layout = (LAYOUTITEM*)calloc(ARRAYSIZE(szItems), sizeof(LAYOUTITEM));
  982. if (NULL == layout) return;
  983. Layout_Initialize(hwnd, szItems, ARRAYSIZE(szItems), layout);
  984. if (episode_info_cy == 0 || (layout[LI_EPISODE_INFO].cy && episode_info_cy != layout[LI_EPISODE_INFO].cy))
  985. {
  986. HWND hControl = GetDlgItem(hwnd, IDC_EPISODE_INFO);
  987. if (IsWindow(hControl))
  988. {
  989. HWND hHeader = ListView_GetHeader(hControl);
  990. if (IsWindow(hHeader))
  991. {
  992. RECT rect;
  993. GetClientRect(hHeader, &rect);
  994. SetWindowPos(hControl, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER);
  995. episode_info_cy = rect.bottom-rect.top;
  996. }
  997. else
  998. episode_info_cy = layout[LI_EPISODE_INFO].cy;
  999. }
  1000. else
  1001. episode_info_cy = layout[LI_EPISODE_INFO].cy;
  1002. }
  1003. LONG t = (clientWidth - layout[LI_VERT].cx) * podcast->vertDivider / 10000;
  1004. LONG t2 = clientRect.right - layout[LI_VERT].cx;
  1005. if (LAYOUTREASON_DIV_LEFT == uReason && t < clientRect.left + WASABI_API_APP->getScaleX(36))
  1006. t = clientRect.left;
  1007. if (LAYOUTREASON_DIV_RIGHT == uReason && t > (t2 - WASABI_API_APP->getScaleX(36)))
  1008. t = t2;
  1009. if (t < clientRect.left + WASABI_API_APP->getScaleX(36)) t = clientRect.left;
  1010. else if (t > t2 - WASABI_API_APP->getScaleX(36)) t = t2;
  1011. layout[LI_CHANNEL].x = WASABI_API_APP->getScaleX(1);
  1012. layout[LI_CHANNEL].y = WASABI_API_APP->getScaleX(1);
  1013. layout[LI_CHANNEL].cx = t;
  1014. layout[LI_VERT].x = LI_GET_R(layout[LI_CHANNEL]);
  1015. layout[LI_VERT].y = WASABI_API_APP->getScaleX(1);
  1016. layout[LI_ITEM].x = LI_GET_R(layout[LI_VERT]);
  1017. layout[LI_ITEM].y = WASABI_API_APP->getScaleX(1);
  1018. LI_SET_R(layout[LI_ITEM], clientWidth);
  1019. layout[LI_HORZ].cx = clientWidth - layout[LI_HORZ].x*WASABI_API_APP->getScaleX(2);
  1020. layout[LI_EPISODE_INFO].x = WASABI_API_APP->getScaleX(1);
  1021. layout[LI_EPISODE_INFO].cx = clientWidth - layout[LI_EPISODE_INFO].x;
  1022. layout[LI_INFO].cx = clientWidth - layout[LI_INFO].x*WASABI_API_APP->getScaleX(2) - 1;
  1023. LI_SET_R(layout[LI_STATUS], clientWidth);
  1024. if (layout[LI_STATUS].cx < WASABI_API_APP->getScaleX(20)) layout[LI_STATUS].cx = 0;
  1025. t = clientHeight - layout[LI_PLAY].cy;
  1026. int cum_width = 0, inc = 0, btnY = 0;
  1027. const INT szBottomRow[] = { LI_PLAY, LI_ENQUEUE, LI_CUSTOM, LI_DOWNLOAD, LI_VISIT, LI_STATUS, };
  1028. for (INT i = 0; i < ARRAYSIZE(szBottomRow); i++)
  1029. {
  1030. LAYOUTITEM *p = &layout[szBottomRow[i]];
  1031. if (IsWindow(p->hwnd))
  1032. {
  1033. if (!inc) p->x = 1;
  1034. else p->x = 1 + cum_width + WASABI_API_APP->getScaleX(4)*inc;
  1035. wchar_t buffer[128] = {0};
  1036. GetWindowTextW(p->hwnd, buffer, ARRAYSIZE(buffer));
  1037. LRESULT idealSize = MLSkinnedButton_GetIdealSize(p->hwnd, buffer);
  1038. btnY = p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize));
  1039. p->y = clientHeight - p->cy;
  1040. if (szBottomRow[i] != LI_STATUS) // exclude the last one so it'll span the remainder of the space
  1041. {
  1042. if (LI_CUSTOM != szBottomRow[i] || customAllowed)
  1043. {
  1044. if (groupBtn && szBottomRow[i] == LI_PLAY && enqueuedef == 1)
  1045. {
  1046. p->flags |= SWP_HIDEWINDOW;
  1047. p->cy = 0;
  1048. continue;
  1049. }
  1050. if (groupBtn && szBottomRow[i] == LI_ENQUEUE && enqueuedef != 1)
  1051. {
  1052. p->flags |= SWP_HIDEWINDOW;
  1053. p->cy = 0;
  1054. continue;
  1055. }
  1056. if (groupBtn && (szBottomRow[i] == LI_PLAY || szBottomRow[i] == LI_ENQUEUE) && customAllowed)
  1057. {
  1058. p->flags |= SWP_HIDEWINDOW;
  1059. p->cy = 0;
  1060. continue;
  1061. }
  1062. LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6);
  1063. p->cx = width;
  1064. p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize));
  1065. cum_width += width;
  1066. inc++;
  1067. }
  1068. else
  1069. {
  1070. p->flags |= SWP_HIDEWINDOW;
  1071. p->cy = 0;
  1072. continue;
  1073. }
  1074. }
  1075. else
  1076. {
  1077. LRESULT idealSize = MLSkinnedStatic_GetIdealSize(p->hwnd, buffer);
  1078. btnY = p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize));
  1079. p->y = clientHeight - p->cy;
  1080. p->cx = clientWidth - cum_width - (WASABI_API_APP->getScaleX(6) * 2);
  1081. }
  1082. }
  1083. }
  1084. t = (clientHeight - layout[LI_HORZ].cy) * podcast->horzDivider / 10000;
  1085. t2 = layout[LI_PLAY].y - layout[LI_HORZ].cy;
  1086. if (LAYOUTREASON_DIV_TOP == uReason && t < clientRect.top + WASABI_API_APP->getScaleY(36 + btnY))
  1087. t = clientRect.top;
  1088. if (LAYOUTREASON_DIV_BOTTOM == uReason && t > (t2 - WASABI_API_APP->getScaleY(36 + btnY)))
  1089. t = t2;
  1090. if (t < clientRect.top + WASABI_API_APP->getScaleY(36 + btnY)) t = clientRect.top;
  1091. else if (t > t2 - WASABI_API_APP->getScaleY(36 + btnY)) t = t2;
  1092. cum_width = 0;
  1093. const INT szMiddleRow[] = { LI_FINDNEW, LI_ADD, LI_EDIT, LI_DELETE, LI_REFRESH, };
  1094. for (INT i = 0; i < ARRAYSIZE(szMiddleRow); i++)
  1095. {
  1096. LAYOUTITEM *p = &layout[szMiddleRow[i]];
  1097. p->x = 1 + (i ? cum_width + WASABI_API_APP->getScaleX(4)*i : 0);
  1098. wchar_t buffer[128] = {0};
  1099. GetWindowTextW(p->hwnd, buffer, ARRAYSIZE(buffer));
  1100. LRESULT idealSize = MLSkinnedButton_GetIdealSize(p->hwnd, buffer);
  1101. // used to hide the 'directory' button if not using one
  1102. LONG width = (!i && !serviceUrl[0] ? WASABI_API_APP->getScaleX(-4) : LOWORD(idealSize) + WASABI_API_APP->getScaleX(6));
  1103. p->cx = width;
  1104. cum_width += width;
  1105. p->cy = (t < WASABI_API_APP->getScaleY(48)) ? 0 : WASABI_API_APP->getScaleY(HIWORD(idealSize));
  1106. p->y = t - p->cy;
  1107. }
  1108. layout[LI_HORZ].y = LI_GET_B(layout[LI_FINDNEW]);
  1109. layout[LI_EPISODE_INFO].y = LI_GET_B(layout[LI_HORZ]);
  1110. if (clientRect.bottom - layout[LI_EPISODE_INFO].y < WASABI_API_APP->getScaleY(episode_info_cy))
  1111. layout[LI_EPISODE_INFO].cy = 0;
  1112. else
  1113. layout[LI_EPISODE_INFO].cy = episode_info_cy;
  1114. layout[LI_INFO].y = LI_GET_B(layout[LI_EPISODE_INFO]);
  1115. LI_SET_B(layout[LI_INFO], layout[LI_PLAY].y - WASABI_API_APP->getScaleY(4));
  1116. t = layout[LI_FINDNEW].y;
  1117. if (layout[LI_FINDNEW].cy > 0) t -= podcast->middleRowSpace;
  1118. layout[LI_CHANNEL].cy = t;
  1119. layout[LI_VERT].cy = t;
  1120. layout[LI_ITEM].cy = t;
  1121. layout[LI_INFO].flags |= SWP_NOREDRAW;
  1122. Layout_SetVisibility(&clientRect, layout, ARRAYSIZE(szItems));
  1123. Layout_Perform(hwnd, layout, ARRAYSIZE(szItems), fRedraw);
  1124. PodcastChannel_SyncHeaderSize(hwnd, fRedraw);
  1125. if (NULL != validRegion)
  1126. {
  1127. RECT validRect;
  1128. CopyRect(&validRect, &clientRect);
  1129. validRect.right += (layout[LI_INFO].rect.right - LI_GET_R(layout[LI_INFO]));
  1130. validRect.bottom += (layout[LI_PLAY].rect.bottom - LI_GET_B(layout[LI_PLAY]));
  1131. Layout_GetValidRgn(validRegion, layoutOffset, &validRect, layout, ARRAYSIZE(szItems));
  1132. }
  1133. free(layout);
  1134. }
  1135. static BOOL SubscriptionView_UpdateImageList(HIMAGELIST imageList, COLORREF rgbBk, COLORREF rgbFg)
  1136. {
  1137. if(NULL == imageList)
  1138. return FALSE;
  1139. INT imageCount = ImageList_GetImageCount(imageList);
  1140. MLIMAGESOURCE source;
  1141. ZeroMemory(&source, sizeof(source));
  1142. source.cbSize = sizeof(source);
  1143. source.hInst = plugin.hDllInstance;
  1144. source.type = SRC_TYPE_PNG;
  1145. MLIMAGEFILTERAPPLYEX filter;
  1146. ZeroMemory(&filter, sizeof(filter));
  1147. filter.cbSize = sizeof(filter);
  1148. filter.filterUID = MLIF_FILTER3_UID;
  1149. filter.rgbBk = rgbBk;
  1150. filter.rgbFg = rgbFg;
  1151. const INT szImages[] = { IDR_TEXT_ICON, IDR_DOWNLOAD_ICON, IDR_MEDIA_ICON, IDR_MEDIA_ICON, };
  1152. for (INT i = 0; i < ARRAYSIZE(szImages); i++)
  1153. {
  1154. source.lpszName = MAKEINTRESOURCE(szImages[i]);
  1155. HBITMAP bitmap = MLImageLoader_LoadDib(plugin.hwndLibraryParent, &source);
  1156. if (NULL != bitmap)
  1157. {
  1158. DIBSECTION dib;
  1159. if (sizeof(dib) == GetObject(bitmap, sizeof(dib), &dib))
  1160. {
  1161. filter.pData = (BYTE*)dib.dsBm.bmBits;
  1162. filter.bpp = dib.dsBm.bmBitsPixel;
  1163. filter.cx = dib.dsBm.bmWidth;
  1164. filter.cy = dib.dsBm.bmHeight;
  1165. if (filter.cy < 0)
  1166. filter.cy = -filter.cy;
  1167. if (MLImageFilter_ApplyEx(plugin.hwndLibraryParent, &filter))
  1168. {
  1169. INT result = (i < imageCount) ?
  1170. ImageList_Replace(imageList, i, bitmap, NULL) :
  1171. ImageList_Add(imageList, bitmap, NULL);
  1172. }
  1173. }
  1174. DeleteObject(bitmap);
  1175. }
  1176. }
  1177. return TRUE;
  1178. }
  1179. static void SubscriptionView_UpdateSkin(HWND hwnd)
  1180. {
  1181. UINT windowStyle = GetWindowLongPtr(hwnd, GWL_STYLE);
  1182. if (0 != (WS_VISIBLE & windowStyle))
  1183. SetWindowLongPtr(hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE);
  1184. HWND hControl;
  1185. COLORREF rgbBk = dialogSkinner.Color(WADLG_ITEMBG);
  1186. COLORREF rgbText = dialogSkinner.Color(WADLG_ITEMFG);
  1187. HFONT hFont = dialogSkinner.GetFont();
  1188. episode_info_cy = 0;
  1189. static const INT szLists[] = {IDC_CHANNELLIST, IDC_ITEMLIST, IDC_EPISODE_INFO};
  1190. for (INT i = 0; i < ARRAYSIZE(szLists); i++)
  1191. {
  1192. if (NULL != (hControl = GetDlgItem(hwnd, szLists[i])))
  1193. {
  1194. ListView_SetBkColor(hControl, rgbBk);
  1195. ListView_SetTextBkColor(hControl, rgbBk);
  1196. ListView_SetTextColor(hControl, rgbText);
  1197. if (NULL != hFont)
  1198. {
  1199. SendMessage(hControl, WM_SETFONT, (WPARAM)hFont, 0L);
  1200. }
  1201. HIMAGELIST imageList = (HIMAGELIST)SendMessage(hControl, LVM_GETIMAGELIST, LVSIL_SMALL, 0L);
  1202. if (NULL != imageList)
  1203. {
  1204. SubscriptionView_UpdateImageList(imageList, rgbBk, rgbText);
  1205. }
  1206. }
  1207. }
  1208. if (0 != (WS_VISIBLE & windowStyle))
  1209. {
  1210. windowStyle = GetWindowLongPtr(hwnd, GWL_STYLE);
  1211. if (0 == (WS_VISIBLE & windowStyle))
  1212. SetWindowLongPtr(hwnd, GWL_STYLE, windowStyle | WS_VISIBLE);
  1213. RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
  1214. SubscriptionView_UpdateInfo(hwnd);
  1215. }
  1216. PODCAST *podcast = GetPodcast(hwnd);
  1217. if (NULL == podcast) return;
  1218. SubscriptionView_SetDescription(hwnd, podcast->description);
  1219. HRGN validRegion = (NULL != podcast->updateRegion) ? CreateRectRgn(0, 0, 0, 0) : NULL;
  1220. SubscriptionView_UpdateLayout(hwnd, TRUE, LAYOUTREASON_RESIZE, validRegion, podcast->updateOffset);
  1221. if (NULL != validRegion)
  1222. {
  1223. CombineRgn(podcast->updateRegion, podcast->updateRegion, validRegion, RGN_DIFF);
  1224. DeleteObject(validRegion);
  1225. }
  1226. }
  1227. static void SubscriptionView_SkinControls(HWND hwnd, const INT *itemList, INT itemCount, UINT skinType, UINT skinStyle)
  1228. {
  1229. FLICKERFIX ff = {0, FFM_ERASEINPAINT};
  1230. MLSKINWINDOW skinWindow = {0};
  1231. skinWindow.style = skinStyle;
  1232. skinWindow.skinType = skinType;
  1233. for(INT i = 0; i < itemCount; i++)
  1234. {
  1235. ff.hwnd = skinWindow.hwndToSkin = GetDlgItem(hwnd, itemList[i]);
  1236. if (IsWindow(skinWindow.hwndToSkin))
  1237. {
  1238. MLSkinWindow(plugin.hwndLibraryParent, &skinWindow);
  1239. SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff);
  1240. }
  1241. }
  1242. }
  1243. static void SubscriptionView_InitMetrics(HWND hwnd)
  1244. {
  1245. PODCAST *podcast = GetPodcast(hwnd);
  1246. if (NULL == podcast) return;
  1247. HWND hControl;
  1248. RECT rect;
  1249. LONG t;
  1250. podcast->vertDivider = (LONG)(channelDividerPercent * 10000);
  1251. podcast->horzDivider = (LONG)(htmlDividerPercent * 10000);
  1252. if (NULL != (hControl = GetDlgItem(hwnd, IDC_ADD)) && FALSE != GetWindowRect(hControl, &rect))
  1253. {
  1254. t = rect.top + WASABI_API_APP->getScaleY(2);
  1255. if (NULL != (hControl = GetDlgItem(hwnd, IDC_CHANNELLIST)) && FALSE != GetWindowRect(hControl, &rect))
  1256. podcast->middleRowSpace = t - rect.bottom;
  1257. }
  1258. if (NULL != (hControl = GetDlgItem(hwnd, IDC_PLAY)) && FALSE != GetWindowRect(hControl, &rect))
  1259. {
  1260. t = rect.top + WASABI_API_APP->getScaleY(2);
  1261. if (NULL != (hControl = GetDlgItem(hwnd, IDC_DESCRIPTION)) && FALSE != GetWindowRect(hControl, &rect))
  1262. podcast->bottomRowSpace = t - rect.bottom;
  1263. }
  1264. }
  1265. static void PodcastChannel_OnKeyDown(HWND hwnd, NMLVKEYDOWN *pkd)
  1266. {
  1267. bool ctrl = (0 != (0x8000 & GetAsyncKeyState(VK_CONTROL))),
  1268. shift = (0 != (0x8000 & GetAsyncKeyState(VK_SHIFT)));
  1269. switch(pkd->wVKey)
  1270. {
  1271. case VK_DELETE:
  1272. if(!shift && !ctrl)
  1273. {
  1274. SENDCMD(hwnd, IDC_DELETE, 0, 0);
  1275. }
  1276. break;
  1277. case VK_F5:
  1278. if(!ctrl)
  1279. {
  1280. SENDCMD(hwnd, (!shift ? IDC_REFRESH : IDC_REFRESHALL), 0, 0);
  1281. }
  1282. break;
  1283. case VK_F7:
  1284. if (!shift && !ctrl)
  1285. {
  1286. SENDCMD(hwnd, IDC_VISIT, 0, 0);
  1287. }
  1288. break;
  1289. case VK_F2:
  1290. if(!shift && !ctrl)
  1291. {
  1292. SENDCMD(hwnd, IDC_EDIT, 0, 0);
  1293. }
  1294. break;
  1295. case VK_INSERT:
  1296. if(!shift && !ctrl)
  1297. {
  1298. SENDCMD(hwnd, IDC_ADD, 0, 0);
  1299. }
  1300. break;
  1301. case VK_LEFT:
  1302. if(!shift && !ctrl)
  1303. {
  1304. PodcastChannel_SelectNext(hwnd, FALSE);
  1305. }
  1306. break;
  1307. case VK_RIGHT:
  1308. if(!shift && !ctrl)
  1309. {
  1310. PodcastChannel_SelectNext(hwnd, TRUE);
  1311. }
  1312. break;
  1313. }
  1314. }
  1315. static void PodcastChannel_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
  1316. {
  1317. BOOL fAscending;
  1318. INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, &fAscending);
  1319. fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : TRUE;
  1320. PodcastChannel_Sort(hwnd, plv->iSubItem, fAscending);
  1321. }
  1322. static void PodcastChannel_OnDoubleClick(HWND hwnd, NMITEMACTIVATE *pia)
  1323. {
  1324. if(pia->iItem != -1)
  1325. {
  1326. LVITEM lvi;
  1327. lvi.state = LVIS_SELECTED;
  1328. lvi.stateMask = LVIS_SELECTED;
  1329. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1330. if (NULL != hItems &&
  1331. FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi))
  1332. {
  1333. SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
  1334. lvi.state = 0;
  1335. SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
  1336. }
  1337. }
  1338. }
  1339. static void PodcastChannel_OnReturn(HWND hwnd, NMHDR *pnmh)
  1340. {
  1341. LVITEM lvi;
  1342. lvi.state = LVIS_SELECTED;
  1343. lvi.stateMask = LVIS_SELECTED;
  1344. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1345. if (NULL != hItems &&
  1346. FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi))
  1347. {
  1348. SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
  1349. lvi.state = 0;
  1350. SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
  1351. }
  1352. }
  1353. static void PodcastChannel_OnGetDispInfo(HWND hwnd, NMLVDISPINFO *pdi)
  1354. {
  1355. PODCAST *podcast = GetPodcast(hwnd);
  1356. if (NULL == podcast) return;
  1357. if (0 != (LVIF_TEXT & pdi->item.mask))
  1358. {
  1359. switch (pdi->item.iSubItem)
  1360. {
  1361. case COLUMN_TITLE:
  1362. {
  1363. pdi->item.pszText[0] = 0; // turn into an empty string as a safety precaution
  1364. AutoLock channelLock(channels LOCKNAME("FillChannelTitle"));
  1365. size_t iChannel = (size_t)pdi->item.iItem;
  1366. if (iChannel < channels.size())
  1367. {
  1368. if (FALSE == podcast->channelAscending)
  1369. iChannel = channels.size() - iChannel - 1;
  1370. const wchar_t *title = channels[iChannel].title;
  1371. if (title)
  1372. StringCchCopy(pdi->item.pszText, pdi->item.cchTextMax, title);
  1373. }
  1374. }
  1375. break;
  1376. }
  1377. }
  1378. }
  1379. static void PodcastChannel_OnItemChanged(HWND hwnd, NMLISTVIEW *plv)
  1380. {
  1381. if ((plv->iItem != -1) && (LVIS_SELECTED & plv->uNewState) != (LVIS_SELECTED & plv->uOldState))
  1382. PodcastChannel_SelectionChanged(hwnd, FALSE);
  1383. }
  1384. static void PodcastChannel_OnSetFocus(HWND hwnd, NMHDR *pnmh)
  1385. {
  1386. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1387. if (NULL == hItems) return;
  1388. LVITEM lvi;
  1389. lvi.state = 0;
  1390. lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
  1391. SNDMSG(hItems, LVM_SETITEMSTATE, -1, (LPARAM)&lvi);
  1392. SNDMSG(hItems, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)-1);
  1393. }
  1394. static LRESULT PodcastChannel_OnNotify(HWND hwnd, NMHDR *pnmh)
  1395. {
  1396. switch (pnmh->code)
  1397. {
  1398. case LVN_KEYDOWN: PodcastChannel_OnKeyDown(hwnd, (NMLVKEYDOWN *)pnmh); break;
  1399. case LVN_COLUMNCLICK: PodcastChannel_OnColumnClick(hwnd, (NMLISTVIEW*)pnmh); break;
  1400. case NM_DBLCLK: PodcastChannel_OnDoubleClick(hwnd, (NMITEMACTIVATE*)pnmh); break;
  1401. case LVN_GETDISPINFO: PodcastChannel_OnGetDispInfo(hwnd, (NMLVDISPINFO*)pnmh); break;
  1402. case LVN_ITEMCHANGED: PodcastChannel_OnItemChanged(hwnd, (NMLISTVIEW*)pnmh); break;
  1403. case NM_SETFOCUS: PodcastChannel_OnSetFocus(hwnd, pnmh); break;
  1404. case NM_RETURN: PodcastChannel_OnReturn(hwnd, pnmh); break;
  1405. }
  1406. return 0;
  1407. }
  1408. bool ParseDuration(const wchar_t *duration, int *out_hours, int *out_minutes, int *out_seconds)
  1409. {
  1410. if (duration && duration[0])
  1411. {
  1412. const wchar_t *colon_position = wcschr(duration, L':');
  1413. if (colon_position == 0)
  1414. {
  1415. int v = _wtoi(duration);
  1416. *out_hours = v / 3600;
  1417. *out_minutes = (v/60)%60;
  1418. *out_seconds = v % 60;
  1419. return true;
  1420. }
  1421. else
  1422. {
  1423. int first_time = _wtoi(duration);
  1424. duration = colon_position+1;
  1425. colon_position = wcschr(duration, L':');
  1426. if (colon_position == 0) // only have MM:SS
  1427. {
  1428. *out_hours=0;
  1429. *out_minutes = first_time;
  1430. *out_seconds = _wtoi(duration);
  1431. }
  1432. else
  1433. {
  1434. *out_hours = first_time;
  1435. *out_minutes = _wtoi(duration);
  1436. *out_seconds = _wtoi(colon_position+1);
  1437. }
  1438. return true;
  1439. }
  1440. }
  1441. else
  1442. {
  1443. return false;
  1444. }
  1445. }
  1446. static void PodcastItem_OnColumnClick(HWND hwnd, NMLISTVIEW *plv)
  1447. {
  1448. BOOL fAscending;
  1449. INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, &fAscending);
  1450. fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : TRUE;
  1451. PodcastItem_Sort(hwnd, plv->iSubItem, fAscending);
  1452. }
  1453. static void PodcastItem_OnGetDispInfo(HWND hwnd, NMLVDISPINFO *pdi)
  1454. {
  1455. AutoLock channelLock (channels LOCKNAME("Item LVN_GETDISPINFO"));
  1456. PODCAST *podcast = GetPodcast(hwnd);
  1457. if (NULL == podcast) return;
  1458. size_t iChannel = podcast->channelActive;
  1459. if (iChannel >= channels.size()) return;
  1460. size_t iItem = pdi->item.iItem;
  1461. if (iItem >= channels[iChannel].items.size()) return;
  1462. if (FALSE == podcast->itemAscending) iItem = channels[iChannel].items.size() - iItem - 1;
  1463. RSS::Item *item = &channels[iChannel].items[iItem];
  1464. if (0 != (LVIF_IMAGE & pdi->item.mask))
  1465. {
  1466. switch (pdi->item.iSubItem)
  1467. {
  1468. case COLUMN_TITLE:
  1469. if (!item->url || !item->url[0])
  1470. pdi->item.iImage = 0;
  1471. else if (item->downloaded)
  1472. pdi->item.iImage = 1;
  1473. else if (item->listened)
  1474. pdi->item.iImage = 2;
  1475. else
  1476. pdi->item.iImage = 3;
  1477. break;
  1478. }
  1479. }
  1480. if (0 != (LVIF_TEXT &pdi->item.mask))
  1481. {
  1482. pdi->item.pszText[0] = L'\0';
  1483. switch (pdi->item.iSubItem)
  1484. {
  1485. case COLUMN_TITLE:
  1486. lstrcpyn(pdi->item.pszText, item->itemName, pdi->item.cchTextMax);
  1487. break;
  1488. case COLUMN_MEDIA_TIME:
  1489. {
  1490. int hours, minutes, seconds;
  1491. if (ParseDuration(item->duration, &hours, &minutes, &seconds))
  1492. {
  1493. if (hours)
  1494. {
  1495. StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"%d:%02d:%02d", hours, minutes, seconds);
  1496. }
  1497. else if (minutes)
  1498. {
  1499. StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"%d:%02d", minutes, seconds);
  1500. }
  1501. else
  1502. {
  1503. StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"0:%02d", seconds);
  1504. }
  1505. }
  1506. }
  1507. break;
  1508. case COLUMN_MEDIA_SIZE:
  1509. if (item->size)
  1510. {
  1511. WASABI_API_LNG->FormattedSizeString(pdi->item.pszText, pdi->item.cchTextMax, item->size);
  1512. }
  1513. break;
  1514. //case COLUMN_MEDIA:
  1515. // WASABI_API_LNGSTRINGW_BUF((item->url && item->url[0]) ? IDS_ARTICLE_WITH_MEDIA : IDS_TEXT_ARTICLE,
  1516. // pdi->item.pszText, pdi->item.cchTextMax);
  1517. // break;
  1518. case COLUMN_DATEADDED:
  1519. MakeDateString(item->publishDate, pdi->item.pszText, pdi->item.cchTextMax);
  1520. break;
  1521. }
  1522. }
  1523. }
  1524. static void PodcastItem_OnItemChanged(HWND hwnd, NMLISTVIEW *plv)
  1525. {
  1526. if ((LVIS_SELECTED & plv->uNewState) != (LVIS_SELECTED & plv->uOldState))
  1527. PodcastItem_SelectionChanged(hwnd, FALSE);
  1528. }
  1529. static void PodcastItem_OnSetFocus(HWND hwnd, NMHDR *pnmh)
  1530. {
  1531. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1532. if (NULL == hItems) return;
  1533. INT iMark = (INT)SNDMSG(hItems, LVM_GETNEXTITEM, -1, LVNI_FOCUSED | LVNI_SELECTED);
  1534. if (-1 == iMark)
  1535. {
  1536. POINT pt;
  1537. RECT rect;
  1538. if (GetCursorPos(&pt) && GetClientRect(hwnd, &rect))
  1539. {
  1540. MapWindowPoints(hwnd, HWND_DESKTOP, (POINT*)&rect, 2);
  1541. if (PtInRect(&rect, pt))
  1542. {
  1543. const INT szKey[] = { VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, /*VK_XBUTTON1*/0x05, /*VK_XBUTTON2*/0x06, };
  1544. for (INT i = 0; i < ARRAYSIZE(szKey); i++)
  1545. {
  1546. if (0 != (0x80000000 & GetAsyncKeyState(szKey[i])))
  1547. return;
  1548. }
  1549. }
  1550. }
  1551. LVITEM lvi;
  1552. lvi.state = LVIS_SELECTED | LVIS_FOCUSED;
  1553. lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
  1554. if (FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, 0, (LPARAM)&lvi))
  1555. {
  1556. SNDMSG(hItems, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)0);
  1557. SNDMSG(hItems, LVM_ENSUREVISIBLE, 0, FALSE);
  1558. }
  1559. }
  1560. }
  1561. static void PodcastItem_OnDoubleClick(HWND hwnd, NMITEMACTIVATE *pia)
  1562. {
  1563. SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
  1564. }
  1565. static void PodcastItem_OnKeyDown(HWND hwnd, NMLVKEYDOWN *pkd)
  1566. {
  1567. bool ctrl = (0 != (0x8000 & GetAsyncKeyState(VK_CONTROL))),
  1568. shift = (0 != (0x8000 & GetAsyncKeyState(VK_SHIFT)));
  1569. switch(pkd->wVKey)
  1570. {
  1571. case VK_F5:
  1572. if (!shift && !ctrl)
  1573. {
  1574. SENDCMD(hwnd, IDC_REFRESH, 0, 0);
  1575. }
  1576. break;
  1577. case VK_F7:
  1578. if (!shift && !ctrl)
  1579. {
  1580. SENDCMD(hwnd, IDC_VISIT, 0, 0);
  1581. }
  1582. break;
  1583. case 'A':
  1584. if (!shift && ctrl)
  1585. {
  1586. SENDCMD(hwnd, IDC_SELECTALL, 0, 0);
  1587. }
  1588. break;
  1589. case 'F':
  1590. if (!shift && ctrl)
  1591. {
  1592. SENDCMD(hwnd, IDC_EXPLORERITEMFOLDER, 0, 0);
  1593. }
  1594. break;
  1595. case 'D':
  1596. if (!shift && ctrl)
  1597. {
  1598. SENDCMD(hwnd, IDC_DOWNLOAD, 0, 0);
  1599. }
  1600. break;
  1601. }
  1602. }
  1603. static void PodcastItem_OnReturn(HWND hwnd, NMHDR *pnmh)
  1604. {
  1605. SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0);
  1606. }
  1607. static LRESULT PodcastItem_OnNotify(HWND hwnd, NMHDR *pnmh)
  1608. {
  1609. switch (pnmh->code)
  1610. {
  1611. case NM_SETFOCUS: PodcastItem_OnSetFocus(hwnd, pnmh); break;
  1612. case LVN_COLUMNCLICK: PodcastItem_OnColumnClick(hwnd, (NMLISTVIEW*)pnmh); break;
  1613. case NM_DBLCLK: PodcastItem_OnDoubleClick(hwnd, (NMITEMACTIVATE*)pnmh); break;
  1614. case LVN_GETDISPINFO: PodcastItem_OnGetDispInfo(hwnd, (NMLVDISPINFO*)pnmh); break;
  1615. case LVN_ITEMCHANGED: PodcastItem_OnItemChanged(hwnd, (NMLISTVIEW*)pnmh); break;
  1616. case LVN_KEYDOWN: PodcastItem_OnKeyDown(hwnd, (NMLVKEYDOWN *)pnmh); break;
  1617. case NM_RETURN: PodcastItem_OnReturn(hwnd, pnmh); break;
  1618. }
  1619. return 0;
  1620. }
  1621. static void CALLBACK SubscriptionView_OnDividerMoved(HWND hDivider, INT position, LPARAM param)
  1622. {
  1623. HWND hwnd = GetParent(hDivider);
  1624. if(NULL == hwnd) return;
  1625. PODCAST *podcast = GetPodcast(hwnd);
  1626. if (NULL == podcast) return;
  1627. RECT clientRect, dividerRect;
  1628. if (FALSE == GetClientRect(hwnd, &clientRect) ||
  1629. FALSE == GetWindowRect(hDivider, &dividerRect))
  1630. {
  1631. return;
  1632. }
  1633. LONG t;
  1634. UINT reason = 0;
  1635. switch(param)
  1636. {
  1637. case IDC_HDIV:
  1638. t = (clientRect.bottom - clientRect.top) - (dividerRect.bottom - dividerRect.top);
  1639. t = 10000 * position / t;
  1640. if (podcast->horzDivider != t)
  1641. {
  1642. reason = (podcast->horzDivider > t) ? LAYOUTREASON_DIV_TOP : LAYOUTREASON_DIV_BOTTOM;
  1643. podcast->horzDivider = t;
  1644. }
  1645. break;
  1646. case IDC_VDIV:
  1647. t = (clientRect.right - clientRect.left) - (dividerRect.right - dividerRect.left);
  1648. t = 10000 * position / t;
  1649. if (podcast->vertDivider != t)
  1650. {
  1651. reason = (podcast->vertDivider > t) ? LAYOUTREASON_DIV_LEFT : LAYOUTREASON_DIV_RIGHT;
  1652. podcast->vertDivider = t;
  1653. }
  1654. break;
  1655. }
  1656. if (0 != reason)
  1657. {
  1658. RECT rc;
  1659. GetClientRect(hwnd, &rc);
  1660. POINTS pts = {0,0};
  1661. HRGN updateRegion = CreateRectRgnIndirect(&rc);
  1662. HRGN validRegion = CreateRectRgn(0, 0, 0, 0);
  1663. SubscriptionView_UpdateLayout(hwnd, FALSE, reason, validRegion, pts);
  1664. CombineRgn(updateRegion, updateRegion, validRegion, RGN_DIFF);
  1665. RedrawWindow(hwnd, NULL ,updateRegion, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN);
  1666. DeleteObject(updateRegion);
  1667. DeleteObject(validRegion);
  1668. }
  1669. }
  1670. static INT_PTR SubscriptionView_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
  1671. {
  1672. SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)SUBSCRIPTIONVIEW_NAME);
  1673. HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_DOWNLOAD_ACCELERATORS);
  1674. if (accel)
  1675. WASABI_API_APP->app_addAccelerators(hwnd, &accel, 1, TRANSLATE_MODE_CHILD);
  1676. PODCAST *podcast = (PODCAST*)calloc(1, sizeof(PODCAST));
  1677. if (NULL == podcast || FALSE == SetProp(hwnd, MAKEINTATOM(VIEWPROP), podcast))
  1678. {
  1679. if (NULL != podcast) free(podcast);
  1680. return 0;
  1681. }
  1682. current_window = hwnd;
  1683. if (!view.play)
  1684. {
  1685. SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view);
  1686. }
  1687. g_context_menus3 = WASABI_API_LOADMENU(IDR_MENU1);
  1688. groupBtn = ML_GROUPBTN_VAL();
  1689. enqueuedef = (ML_ENQDEF_VAL() == 1);
  1690. // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay
  1691. // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed
  1692. pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwnd, (INT_PTR)MAKELONG(IDC_CUSTOM, IDC_ENQUEUE), (INT_PTR)L"ml_wire"};
  1693. wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p);
  1694. if (pszTextW && pszTextW[0] != 0)
  1695. {
  1696. // set this to be a bit different so we can just use one button and not the
  1697. // mixable one as well (leaving that to prevent messing with the resources)
  1698. customAllowed = TRUE;
  1699. SetDlgItemTextW(hwnd, IDC_CUSTOM, pszTextW);
  1700. }
  1701. else
  1702. customAllowed = FALSE;
  1703. SubscriptionView_InitMetrics(hwnd);
  1704. podcast->channelActive = -1;
  1705. HWND hLibrary = plugin.hwndLibraryParent;
  1706. MLSkinWindow2(hLibrary, hwnd, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
  1707. PodcastChannel_InitializeList(hwnd);
  1708. PodcastItem_InitializeList(hwnd);
  1709. PodcastInfo_InitializeList(hwnd);
  1710. const INT szControls[] = { IDC_FINDNEW, IDC_ADD, IDC_EDIT, IDC_DELETE, IDC_REFRESH, IDC_DOWNLOAD, IDC_VISIT, IDC_STATUS, };
  1711. SubscriptionView_SkinControls(hwnd, szControls, ARRAYSIZE(szControls), SKINNEDWND_TYPE_AUTO,
  1712. SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
  1713. const INT szControlz[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, };
  1714. SubscriptionView_SkinControls(hwnd, szControlz, ARRAYSIZE(szControlz), SKINNEDWND_TYPE_AUTO,
  1715. SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0));
  1716. HWND hControl = GetDlgItem(hwnd, IDC_HDIV);
  1717. MLSkinWindow2(hLibrary, hControl, SKINNEDWND_TYPE_DIVIDER, SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT | SWDIV_HORZ /*| SWDIV_NOHILITE*/);
  1718. MLSkinnedDivider_SetCallback(hControl, SubscriptionView_OnDividerMoved, IDC_HDIV);
  1719. hControl = GetDlgItem(hwnd, IDC_VDIV);
  1720. MLSkinWindow2(hLibrary, hControl, SKINNEDWND_TYPE_DIVIDER, SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT | SWDIV_VERT /*| SWDIV_NOHILITE*/);
  1721. MLSkinnedDivider_SetCallback(hControl, SubscriptionView_OnDividerMoved, IDC_VDIV);
  1722. PodcastChannel_Sort(hwnd, 0, channelSortAscending);
  1723. PodcastItem_Sort(hwnd, currentItemSort, itemSortAscending);
  1724. OmService *service = (OmService*)lParam;
  1725. HWND hBrowser = NULL;
  1726. if (NULL != OMBROWSERMNGR &&
  1727. SUCCEEDED(OMBROWSERMNGR->Initialize(NULL, plugin.hwndWinampParent)) &&
  1728. SUCCEEDED(OMBROWSERMNGR->CreateView(service, hwnd, NAVIGATE_BLANK, NBCS_NOTOOLBAR | NBCS_NOSTATUSBAR, &hBrowser)))
  1729. {
  1730. HWND hTarget = GetDlgItem(hwnd, IDC_DESCRIPTION);
  1731. if (NULL != hTarget)
  1732. {
  1733. RECT rect;
  1734. if (GetWindowRect(hTarget, &rect))
  1735. {
  1736. MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rect, 2);
  1737. SetWindowPos(hBrowser, hTarget, rect.left, rect.top, rect.right -rect.left, rect.bottom - rect.top,
  1738. SWP_NOACTIVATE | SWP_NOOWNERZORDER);
  1739. }
  1740. DestroyWindow(hTarget);
  1741. }
  1742. SetWindowLongPtr(hBrowser, GWLP_ID, IDC_DESCRIPTION);
  1743. ShowWindow(hBrowser, SW_SHOWNA);
  1744. }
  1745. Downloads_UpdateButtonText(hwnd, enqueuedef);
  1746. SendMessage(hwnd, WM_DISPLAYCHANGE, 0, 0L);
  1747. wchar_t status[256] = {0};
  1748. cloud.GetStatus(status, 256);
  1749. SubscriptionView_SetStatus(hwnd, status);
  1750. SubscriptionView_RefreshChannels(hwnd, TRUE);
  1751. // evil to do but as the data is known and kept by us then this should be ok
  1752. if (channelLastSelection != -1)
  1753. {
  1754. static LV_ITEM _macro_lvi;
  1755. _macro_lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
  1756. _macro_lvi.state = LVIS_SELECTED | LVIS_FOCUSED;
  1757. PostMessage(GetDlgItem(hwnd, IDC_CHANNELLIST), LVM_SETITEMSTATE,
  1758. (WPARAM)channelLastSelection, (LPARAM)(LV_ITEM *)&_macro_lvi);
  1759. }
  1760. return 0;
  1761. }
  1762. static void SubscriptionView_OnDestroy(HWND hwnd)
  1763. {
  1764. PODCAST *podcast = GetPodcast(hwnd);
  1765. RemoveProp(hwnd, MAKEINTATOM(VIEWPROP));
  1766. if (NULL != podcast)
  1767. {
  1768. htmlDividerPercent = podcast->horzDivider / 10000.0f;
  1769. channelDividerPercent = podcast->vertDivider / 10000.0f;
  1770. Plugin_FreeString(podcast->infoUrl);
  1771. Plugin_FreeString(podcast->description);
  1772. free(podcast);
  1773. }
  1774. BOOL fAscending;
  1775. currentItemSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, &fAscending);
  1776. itemSortAscending = (-1 != currentItemSort) ? (FALSE != fAscending) : true;
  1777. INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, &fAscending);
  1778. channelSortAscending = (-1 != iSort) ? (FALSE != fAscending) : true;
  1779. INT iSelected = (INT)SNDMSG(GetDlgItem(hwnd, IDC_CHANNELLIST), LVM_GETNEXTITEM, -1, LVNI_SELECTED);
  1780. HWND hControl = GetDlgItem(hwnd, IDC_ITEMLIST);
  1781. if (NULL != hControl)
  1782. {
  1783. itemTitleWidth = ListView_GetColumnWidth(hControl, COLUMN_TITLE);
  1784. itemDateWidth = ListView_GetColumnWidth(hControl, COLUMN_DATEADDED);
  1785. //itemMediaWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA);
  1786. itemMediaWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA_TIME);
  1787. itemSizeWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA_SIZE);
  1788. HIMAGELIST imageList = (HIMAGELIST)SendMessage(hControl, LVM_GETIMAGELIST, LVSIL_SMALL, 0L);
  1789. if (NULL != imageList)
  1790. ImageList_Destroy(imageList);
  1791. }
  1792. // ensure view settings are saved...
  1793. SaveAll(true);
  1794. }
  1795. static void SubscriptionView_OnWindowPosChanged(HWND hwnd, WINDOWPOS *pwp)
  1796. {
  1797. if (SWP_NOSIZE == ((SWP_NOSIZE | SWP_FRAMECHANGED) & pwp->flags)) return;
  1798. PODCAST *podcast = GetPodcast(hwnd);
  1799. if (NULL == podcast) return;
  1800. HRGN validRegion = (NULL != podcast->updateRegion) ? CreateRectRgn(0, 0, 0, 0) : NULL;
  1801. SubscriptionView_UpdateLayout(hwnd, (0 == (SWP_NOREDRAW & pwp->flags)),
  1802. LAYOUTREASON_RESIZE, validRegion, podcast->updateOffset);
  1803. if (NULL != validRegion)
  1804. {
  1805. CombineRgn(podcast->updateRegion, podcast->updateRegion, validRegion, RGN_DIFF);
  1806. DeleteObject(validRegion);
  1807. }
  1808. }
  1809. static void SubscriptionView_OnContextMenu(HWND hwnd, HWND hTarget, POINTS pts)
  1810. {
  1811. INT controlId = (NULL != hTarget) ? (INT)GetWindowLongPtr(hTarget, GWLP_ID) : 0;
  1812. switch(controlId)
  1813. {
  1814. case IDC_ITEMLIST:
  1815. case IDC_CHANNELLIST:
  1816. SubscriptionView_ListContextMenu(hwnd, controlId, pts);
  1817. break;
  1818. }
  1819. }
  1820. static void SubscriptionView_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS updateOffset)
  1821. {
  1822. PODCAST *podcast = GetPodcast(hwnd);
  1823. if (NULL == podcast) return;
  1824. podcast->updateRegion = updateRegion;
  1825. podcast->updateOffset = updateOffset;
  1826. }
  1827. static void PodcastCommand_OnDeleteChannel(HWND hwnd)
  1828. {
  1829. PODCAST *podcast = GetPodcast(hwnd);
  1830. if (NULL == podcast) return;
  1831. AutoLock lock (channels LOCKNAME("DeleteChannel"));
  1832. size_t iChannel = PodcastChannel_GetActive(hwnd);
  1833. if (BAD_CHANNEL == iChannel) return;
  1834. WCHAR szText[1024] = {0}, szBuffer[1024] = {0};
  1835. WASABI_API_LNGSTRINGW_BUF(IDS_SURE_YOU_WANT_TO_REMOVE_THIS, szBuffer, ARRAYSIZE(szBuffer));
  1836. StringCchPrintf(szText, ARRAYSIZE(szText), szBuffer, channels[iChannel].title, channels[iChannel].url);
  1837. WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRM, szBuffer, ARRAYSIZE(szBuffer));
  1838. if (IDYES == MessageBox(hwnd, szText, szBuffer, MB_YESNO | MB_ICONWARNING))
  1839. {
  1840. channels.RemoveChannel((int)iChannel);
  1841. SubscriptionView_RefreshChannels(hwnd, FALSE);
  1842. SaveAll();
  1843. }
  1844. }
  1845. static void PodcastCommand_OnVisitSite(HWND hwnd)
  1846. {
  1847. PODCAST *podcast = GetPodcast(hwnd);
  1848. if (NULL == podcast || NULL == podcast->infoUrl)
  1849. return;
  1850. SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, podcast->infoUrl);
  1851. }
  1852. static void PodcastCommand_OnRefreshChannel(HWND hwnd)
  1853. {
  1854. size_t iChannel = PodcastChannel_GetActive(hwnd);
  1855. if (BAD_CHANNEL != iChannel)
  1856. {
  1857. channels[iChannel].needsRefresh = true;
  1858. cloud.Pulse();
  1859. }
  1860. }
  1861. static void PodcastCommand_OnEditChannel(HWND hwnd)
  1862. {
  1863. size_t iChannel = PodcastChannel_GetActive(hwnd);
  1864. if (BAD_CHANNEL == iChannel) return;
  1865. ChannelEditor_Show(hwnd, iChannel, CEF_CENTEROWNER);
  1866. HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
  1867. if (NULL != hChannel)
  1868. PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hChannel, TRUE);
  1869. }
  1870. static void PodcastCommand_OnAddChannel(HWND hwnd)
  1871. {
  1872. ChannelEditor_Show(hwnd, 0, CEF_CENTEROWNER | CEF_CREATENEW);
  1873. HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
  1874. if (NULL != hChannel)
  1875. PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hChannel, TRUE);
  1876. }
  1877. static void PodcastCommand_OnFindNewChannel(HWND hwnd)
  1878. {
  1879. HNAVITEM hItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL);
  1880. MLNavItem_Select(plugin.hwndLibraryParent, hItem);
  1881. }
  1882. static void PodcastCommand_OnRefreshAll(HWND hwnd)
  1883. {
  1884. cloud.RefreshAll();
  1885. cloud.Pulse();
  1886. }
  1887. static void PodcastCommand_OnPlaySelection(HWND hwnd, BOOL fEnqueue, BOOL fForce)
  1888. {
  1889. AutoLock channelLock (channels LOCKNAME("PlaySelection"));
  1890. PODCAST *podcast = GetPodcast(hwnd);
  1891. if (NULL == podcast) return;
  1892. size_t iChannel = PodcastChannel_GetActive(hwnd);
  1893. if (BAD_CHANNEL == iChannel) return;
  1894. Channel *channel = &channels[iChannel];
  1895. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1896. if (NULL == hItems) return;
  1897. INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
  1898. if (0 == selectedCount) return;
  1899. size_t indexCount = 0;
  1900. size_t *indexList = (size_t*)calloc(selectedCount, sizeof(size_t));
  1901. if (NULL == indexList) return;
  1902. INT iSelected = -1;
  1903. while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED)))
  1904. {
  1905. size_t iItem = iSelected;
  1906. if (iItem < channel->items.size())
  1907. {
  1908. if (FALSE == podcast->itemAscending)
  1909. iItem = channel->items.size() - iItem - 1;
  1910. indexList[indexCount++] = iItem;
  1911. }
  1912. }
  1913. if (0 != indexCount)
  1914. {
  1915. SubscriptionView_Play(hwnd, iChannel, indexList, indexCount, fEnqueue, fForce);
  1916. }
  1917. free(indexList);
  1918. }
  1919. static void PodcastCommand_OnDownloadSelection( HWND hwnd )
  1920. {
  1921. AutoLock channelLock( channels LOCKNAME( "DownloadSelection" ) );
  1922. PODCAST *podcast = GetPodcast( hwnd );
  1923. if ( NULL == podcast ) return;
  1924. size_t iChannel = PodcastChannel_GetActive( hwnd );
  1925. if ( BAD_CHANNEL == iChannel ) return;
  1926. Channel *channel = &channels[ iChannel ];
  1927. HWND hItems = GetDlgItem( hwnd, IDC_ITEMLIST );
  1928. if ( NULL == hItems ) return;
  1929. INT selectedCount = (INT)SNDMSG( hItems, LVM_GETSELECTEDCOUNT, 0, 0L );
  1930. if ( 0 == selectedCount ) return;
  1931. INT iSelected = -1;
  1932. WCHAR szPath[ MAX_PATH * 2 ] = { 0 };
  1933. size_t scheduled = 0;
  1934. while ( -1 != ( iSelected = SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED ) ) )
  1935. {
  1936. size_t iItem = iSelected;
  1937. if ( iItem < channel->items.size() )
  1938. {
  1939. if ( FALSE == podcast->itemAscending )
  1940. iItem = channel->items.size() - iItem - 1;
  1941. RSS::Item *downloadItem = &channel->items[ iItem ];
  1942. if ( ( downloadItem->url && downloadItem->url[ 0 ] ) && SUCCEEDED( downloadItem->GetDownloadFileName( channel->title, szPath, ARRAYSIZE( szPath ), TRUE ) ) )
  1943. {
  1944. if ( !wa::files::file_exists( szPath ) )
  1945. {
  1946. wchar_t *url = urlencode( downloadItem->url );
  1947. downloader.Download( url, szPath, channel->title, downloadItem->itemName, downloadItem->publishDate );
  1948. downloadItem->downloaded = true;
  1949. if ( 0 == scheduled )
  1950. {
  1951. SendMessage( hwnd, SVM_SETSTATUS, 0, (LPARAM)IDS_ADD_TO_DOWNLOADS );
  1952. }
  1953. free( url );
  1954. scheduled++;
  1955. }
  1956. }
  1957. }
  1958. }
  1959. if ( 0 == scheduled )
  1960. SendMessage( hwnd, SVM_SETSTATUS, 0, (LPARAM)IDS_NO_MEDIA_TO_DOWNLOAD );
  1961. }
  1962. static void PodcastCommand_OnExploreItemFolder(HWND hwnd)
  1963. {
  1964. AutoLock channelLock (channels LOCKNAME("Explore"));
  1965. PODCAST *podcast = GetPodcast(hwnd);
  1966. if (NULL == podcast) return;
  1967. size_t iChannel = PodcastChannel_GetActive(hwnd);
  1968. if (BAD_CHANNEL == iChannel) return;
  1969. Channel *channel = &channels[iChannel];
  1970. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1971. if (NULL == hItems) return;
  1972. INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
  1973. if (0 == selectedCount) return;
  1974. INT iSelected = -1;
  1975. WCHAR szPath[MAX_PATH * 2] = {0};
  1976. WASABI_API_EXPLORERFINDFILE->Reset();
  1977. while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED)))
  1978. {
  1979. size_t iItem = iSelected;
  1980. if (iItem < channel->items.size())
  1981. {
  1982. if (FALSE == podcast->itemAscending)
  1983. iItem = channel->items.size() - iItem - 1;
  1984. RSS::Item *downloadItem = &channel->items[iItem];
  1985. if ((downloadItem->url && downloadItem->url[0]) &&
  1986. SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) &&
  1987. PathFileExists(szPath))
  1988. {
  1989. WASABI_API_EXPLORERFINDFILE->AddFile(szPath);
  1990. }
  1991. }
  1992. }
  1993. WASABI_API_EXPLORERFINDFILE->ShowFiles();
  1994. }
  1995. static void PodcastCommand_OnSelectAll(HWND hwnd)
  1996. {
  1997. HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST);
  1998. if (NULL == hItems) return;
  1999. INT iCount = (INT)SNDMSG(hItems, LVM_GETITEMCOUNT, 0, 0L);
  2000. if (0 != iCount)
  2001. {
  2002. INT iSelected = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L);
  2003. if (iSelected != iCount)
  2004. {
  2005. LVITEM lvi;
  2006. lvi.state = LVIS_SELECTED;
  2007. lvi.stateMask = LVIS_SELECTED;
  2008. SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi);
  2009. }
  2010. }
  2011. }
  2012. static void PodcastCommand_PlayEnqueue(HWND hwndDlg, HWND from, UINT idFrom)
  2013. {
  2014. HMENU listMenu = GetSubMenu(g_context_menus3, 1);
  2015. int count = GetMenuItemCount(listMenu);
  2016. if (count > 2)
  2017. {
  2018. for (int i = 2; i < count; i++)
  2019. {
  2020. DeleteMenu(listMenu, 2, MF_BYPOSITION);
  2021. }
  2022. }
  2023. Downloads_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND);
  2024. }
  2025. static void SubscriptionView_OnCommand(HWND hwnd, INT controlId, INT eventId, HWND hControl)
  2026. {
  2027. switch (controlId)
  2028. {
  2029. case IDC_CUSTOM:
  2030. case IDC_PLAY:
  2031. case IDC_ENQUEUE:
  2032. case IDC_PLAYACTION:
  2033. case IDC_ENQUEUEACTION:
  2034. if (eventId == MLBN_DROPDOWN)
  2035. {
  2036. PodcastCommand_PlayEnqueue(hwnd, hControl, controlId);
  2037. }
  2038. else
  2039. {
  2040. int action;
  2041. if (controlId == IDC_PLAY || controlId == IDC_PLAYACTION)
  2042. action = (eventId == 1) ? enqueuedef == 1 : 0;
  2043. else if (controlId == IDC_ENQUEUE || controlId == IDC_ENQUEUEACTION)
  2044. action = (eventId == 1) ? (enqueuedef != 1) : 1;
  2045. else
  2046. // so custom can work with the menu item part
  2047. break;
  2048. PodcastCommand_OnPlaySelection(hwnd, action, (controlId == IDC_PLAY || controlId == IDC_ENQUEUE));
  2049. }
  2050. break;
  2051. case IDC_VISIT: PodcastCommand_OnVisitSite(hwnd); break;
  2052. case IDC_DOWNLOAD: PodcastCommand_OnDownloadSelection(hwnd); break;
  2053. case IDC_EXPLORERITEMFOLDER: PodcastCommand_OnExploreItemFolder(hwnd); break;
  2054. case IDC_SELECTALL: PodcastCommand_OnSelectAll(hwnd); break;
  2055. case IDC_REFRESH: PodcastCommand_OnRefreshChannel(hwnd); break;
  2056. case IDC_EDIT: PodcastCommand_OnEditChannel(hwnd); break;
  2057. case IDC_ADD: PodcastCommand_OnAddChannel(hwnd); break;
  2058. case IDC_FINDNEW: PodcastCommand_OnFindNewChannel(hwnd); break;
  2059. case IDC_DELETE: PodcastCommand_OnDeleteChannel(hwnd); break;
  2060. case IDC_REFRESHALL: PodcastCommand_OnRefreshAll(hwnd); break;
  2061. }
  2062. }
  2063. static LRESULT SubscriptionView_OnNotify(HWND hwnd, INT controlId, NMHDR *pnmh)
  2064. {
  2065. LRESULT result = 0;
  2066. switch (controlId)
  2067. {
  2068. case IDC_CHANNELLIST: result = PodcastChannel_OnNotify(hwnd, pnmh); break;
  2069. case IDC_ITEMLIST: result = PodcastItem_OnNotify(hwnd, pnmh); break;
  2070. }
  2071. return result;
  2072. }
  2073. static void SubscriptionView_OnChar(HWND hwnd, UINT vKey, UINT flags)
  2074. {
  2075. switch(vKey)
  2076. {
  2077. case VK_DELETE:
  2078. if (GetFocus() == GetDlgItem(hwnd, IDC_CHANNELLIST))
  2079. SENDCMD(hwnd, IDC_DELETE, 0, 0L);
  2080. break;
  2081. }
  2082. }
  2083. static BOOL SubscriptionView_OnRefreshChannels(HWND hwnd, BOOL fSort)
  2084. {
  2085. AutoLock lock (channels LOCKNAME("All_ChannelsUpdated"));
  2086. HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST);
  2087. if (NULL == hChannel) return FALSE;
  2088. size_t channelSize = channels.size();
  2089. size_t iSelected = (size_t)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
  2090. if (FALSE != fSort)
  2091. {
  2092. INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, NULL);
  2093. SubscriptionView_SortChannels(iSort);
  2094. }
  2095. SNDMSG(hChannel, LVM_SETITEMCOUNT, channelSize, 0L);
  2096. PodcastChannel_SyncHeaderSize(hwnd, TRUE);
  2097. BOOL selectChannel = FALSE;
  2098. if(BAD_CHANNEL != iSelected && iSelected >= channelSize)
  2099. {
  2100. iSelected = (channelSize - 1);
  2101. selectChannel = TRUE;
  2102. }
  2103. if (BAD_CHANNEL == iSelected && channelSize > 0)
  2104. {
  2105. iSelected = 0;
  2106. selectChannel = TRUE;
  2107. }
  2108. if (FALSE != selectChannel)
  2109. {
  2110. LVITEM lvi;
  2111. lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
  2112. lvi.state = lvi.stateMask;
  2113. if (FALSE == SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)iSelected, (LPARAM)&lvi))
  2114. iSelected = -1;
  2115. else
  2116. {
  2117. SNDMSG(hChannel, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)iSelected);
  2118. SNDMSG(hChannel, LVM_ENSUREVISIBLE, (WPARAM)iSelected, (LPARAM)FALSE);
  2119. }
  2120. }
  2121. if (FALSE == selectChannel || 0 == channelSize)
  2122. {
  2123. PodcastChannel_SelectionChanged(hwnd, FALSE);
  2124. }
  2125. return TRUE;
  2126. }
  2127. static void SubscriptionView_OnParentNotify(HWND hwnd, UINT uMsg, HWND childId, LPARAM lParam)
  2128. {
  2129. switch(uMsg)
  2130. {
  2131. case WM_LBUTTONDOWN:
  2132. case WM_RBUTTONDOWN:
  2133. case WM_MBUTTONDOWN:
  2134. case /*WM_XBUTTONDOWN*/0x020B:
  2135. {
  2136. POINT pt;
  2137. GetCursorPos(&pt);
  2138. RECT rect;
  2139. HWND hItem = GetDlgItem(hwnd, IDC_ITEMLIST);
  2140. if (NULL != hItem && hItem != GetFocus() && FALSE != GetClientRect(hItem, &rect))
  2141. {
  2142. MapWindowPoints(hItem, HWND_DESKTOP, (POINT*)&rect, 2);
  2143. if (PtInRect(&rect, pt))
  2144. {
  2145. SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hItem, MAKELPARAM(TRUE, 0));
  2146. }
  2147. }
  2148. }
  2149. break;
  2150. }
  2151. }
  2152. static LRESULT SubscriptionView_OnSetStatus(HWND hwnd, LPCWSTR pszStatus)
  2153. {
  2154. HWND hStatus = GetDlgItem(hwnd, IDC_STATUS);
  2155. if (NULL == hStatus) return FALSE;
  2156. WCHAR szBuffer[512] = {0};
  2157. if (IS_INTRESOURCE(pszStatus))
  2158. {
  2159. pszStatus = WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszStatus, szBuffer, ARRAYSIZE(szBuffer));
  2160. }
  2161. return SetWindowText(hStatus, pszStatus);
  2162. }
  2163. static INT_PTR CALLBACK SubscriptionView_DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  2164. {
  2165. switch (uMsg)
  2166. {
  2167. case WM_INITDIALOG: return SubscriptionView_OnInitDialog(hwnd, (HWND)wParam, lParam);
  2168. case WM_DESTROY: SubscriptionView_OnDestroy(hwnd); return TRUE;
  2169. case WM_WINDOWPOSCHANGED: SubscriptionView_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return TRUE;
  2170. case WM_DISPLAYCHANGE: SubscriptionView_UpdateSkin(hwnd); return TRUE;
  2171. case WM_CONTEXTMENU: SubscriptionView_OnContextMenu(hwnd, (HWND)wParam, MAKEPOINTS(lParam)); return TRUE;
  2172. case WM_NOTIFYFORMAT: MSGRESULT(hwnd, NFR_UNICODE);
  2173. case WM_COMMAND: SubscriptionView_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
  2174. case WM_NOTIFY: MSGRESULT(hwnd, SubscriptionView_OnNotify(hwnd, (INT)wParam, (NMHDR*)lParam));
  2175. case WM_CHAR: SubscriptionView_OnChar(hwnd, (UINT)wParam, (UINT)lParam); return TRUE;
  2176. case WM_PARENTNOTIFY: SubscriptionView_OnParentNotify(hwnd, LOWORD(wParam), (HWND)HIWORD(wParam), lParam); return TRUE;
  2177. case WM_USER + 0x200: MSGRESULT(hwnd, TRUE);
  2178. case WM_USER + 0x201: SubscriptionView_OnSetUpdateRegion(hwnd, (HRGN)lParam, MAKEPOINTS(wParam)); return TRUE;
  2179. case SVM_REFRESHCHANNELS: MSGRESULT(hwnd, SubscriptionView_OnRefreshChannels(hwnd, (BOOL)wParam));
  2180. case SVM_SETSTATUS: MSGRESULT(hwnd, SubscriptionView_OnSetStatus(hwnd, (LPCWSTR)lParam));
  2181. case WM_INITMENUPOPUP:
  2182. if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1)
  2183. {
  2184. if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff)
  2185. s.mode = 2;
  2186. }
  2187. return 0;
  2188. case WM_APP + 104:
  2189. {
  2190. Downloads_UpdateButtonText(hwnd, enqueuedef);
  2191. SendMessage(hwnd, WM_DISPLAYCHANGE, 0, 0);
  2192. return 0;
  2193. }
  2194. case WM_PAINT:
  2195. if (IsWindowVisible(GetDlgItem(hwnd, IDC_DESCRIPTION)))
  2196. {
  2197. int tab[] = { IDC_DESCRIPTION|DCW_SUNKENBORDER};
  2198. dialogSkinner.Draw(hwnd, tab, sizeof(tab) / sizeof(tab[0]));
  2199. }
  2200. return 0;
  2201. }
  2202. return 0;
  2203. }
  2204. HWND CALLBACK SubscriptionView_Create(HWND hParent, OmService *service)
  2205. {
  2206. return WASABI_API_CREATEDIALOGPARAMW(IDD_PODCAST, hParent, SubscriptionView_DlgProc, (LPARAM)service);
  2207. }
  2208. HWND CALLBACK SubscriptionView_FindWindow()
  2209. {
  2210. HWND hView = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0);
  2211. if (NULL == hView) return NULL;
  2212. WCHAR szBuffer[128] = {0};
  2213. if (0 == GetClassName(hView, szBuffer, ARRAYSIZE(szBuffer)) ||
  2214. CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, 0, szBuffer, -1, L"#32770", -1) ||
  2215. 0 == GetWindowText(hView, szBuffer, ARRAYSIZE(szBuffer)) ||
  2216. CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, 0, szBuffer, -1, SUBSCRIPTIONVIEW_NAME, -1))
  2217. {
  2218. hView = NULL;
  2219. }
  2220. return hView;
  2221. }