From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- Src/Plugins/Library/ml_wire/subscriptionView.cpp | 2691 ++++++++++++++++++++++ 1 file changed, 2691 insertions(+) create mode 100644 Src/Plugins/Library/ml_wire/subscriptionView.cpp (limited to 'Src/Plugins/Library/ml_wire/subscriptionView.cpp') diff --git a/Src/Plugins/Library/ml_wire/subscriptionView.cpp b/Src/Plugins/Library/ml_wire/subscriptionView.cpp new file mode 100644 index 00000000..4bff7ebc --- /dev/null +++ b/Src/Plugins/Library/ml_wire/subscriptionView.cpp @@ -0,0 +1,2691 @@ +#include "main.h" +#include "./subscriptionView.h" + +#include "api__ml_wire.h" +#include "./util.h" +#include "Feeds.h" +#include "RFCDate.h" +#include "Cloud.h" +#include "./channelEditor.h" +#include "BackgroundDownloader.h" +#include "./service.h" +#include "./layout.h" +#include "../nu/menushortcuts.h" +#include "..\..\General\gen_ml/ml_ipc.h" +#include "..\..\General\gen_ml/ml_ipc_0313.h" +#include "../omBrowser/browserView.h" +#include "./navigation.h" + +#include + +#include "../../../WAT/WAT.h" + +#ifndef HDF_SORTUP +#define HDF_SORTUP 0x0400 +#define HDF_SORTDOWN 0x0200 +#endif // !HDF_SORTUP + +using namespace Nullsoft::Utility; + +int itemTitleWidth = 200; +int itemDateWidth = 120; +int itemMediaWidth = 100; +int itemSizeWidth = 100; + +int currentItemSort = 1; // -1 means no sort active +bool itemSortAscending = false; +bool channelSortAscending = true; +int channelLastSelection = -1; + +float htmlDividerPercent = 0.666f; +float channelDividerPercent = 0.333f; + +extern int IPC_LIBRARY_SENDTOMENU; +extern librarySendToMenuStruct s; + +static int episode_info_cy; +HMENU g_context_menus3 = NULL; + +extern Cloud cloud; +extern wchar_t serviceUrl[1024]; + +#define SUBSCRIPTIONVIEW_NAME L"SubscriptionView" + +typedef enum +{ + COLUMN_TITLE = 0, + COLUMN_DATEADDED, + //COLUMN_MEDIA, + COLUMN_MEDIA_TIME, + COLUMN_MEDIA_SIZE, +} ListColumns; + +typedef enum +{ + LI_CHANNEL = 0, + LI_ITEM, + LI_VERT, + LI_FINDNEW, + LI_ADD, + LI_EDIT, + LI_DELETE, + LI_REFRESH, + LI_HORZ, + LI_EPISODE_INFO, + LI_INFO, + LI_PLAY, + LI_ENQUEUE, + LI_CUSTOM, + LI_DOWNLOAD, + LI_VISIT, + LI_STATUS, + LI_LAST +} LayoutItems; + +typedef struct __PODCAST +{ + LONG vertDivider; + LONG horzDivider; + LONG bottomRowSpace; + LONG middleRowSpace; + HRGN updateRegion; + POINTS updateOffset; + size_t channelActive; + BOOL channelAscending; + BOOL itemAscending; + LPWSTR infoUrl; + LPWSTR description; +} PODCAST; + +#define GetPodcast(__hwnd) ((PODCAST*)GetPropW((__hwnd), MAKEINTATOM(VIEWPROP))) + +#define LAYOUTREASON_RESIZE 0 +#define LAYOUTREASON_DIV_LEFT 1 +#define LAYOUTREASON_DIV_RIGHT 2 +#define LAYOUTREASON_DIV_TOP 3 +#define LAYOUTREASON_DIV_BOTTOM 4 + +#define UPDATE_TIMER 12 +#define UPDATE_DELAY 0 + +static void CALLBACK PodcastChannel_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed); +static void CALLBACK PodcastItem_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed); +static size_t PodcastChannel_GetActive(HWND hwnd); + +ptrdiff_t inline CopyCharW(wchar_t *dest, const wchar_t *src) +{ + wchar_t *end = CharNextW(src); + ptrdiff_t count = end-src; + for (ptrdiff_t i=0;i" + L"" + L"" + L"%s" + L""; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + WCHAR szFont[128] = {0}; + HFONT hFont = (HFONT)SendMessage(hItems, WM_GETFONT, 0, 0L); + if (FAILED(SubscriptionView_FormatFont(szFont, ARRAYSIZE(szFont), hFont))) + szFont[0] = L'\0'; + + if (NULL == pszInfo) pszInfo = L""; + + INT cchLen = lstrlen(pszInfo) + 1; + cchLen += lstrlen(szFont); + cchLen += ARRAYSIZE(szTemplate); + + BSTR documentData = SysAllocStringLen(NULL, cchLen); + if (NULL == documentData) return FALSE; + + BOOL result; + + if ( SUCCEEDED( StringCchPrintfEx( documentData, cchLen, NULL, NULL, STRSAFE_IGNORE_NULLS, szTemplate, szFont, pszInfo ) ) ) + result = BrowserView_WriteDocument( hBrowser, documentData, TRUE ); + else + result = FALSE; + + if (FALSE == result) + SysFreeString(documentData); + + return result; +} + +static void SubscriptionView_UpdateInfo( HWND hwnd ) +{ + PODCAST *podcast = GetPodcast( hwnd ); + if ( NULL == podcast ) + return; + + AutoLock channelLock( channels LOCKNAME( "UpdateInfo" ) ); + + LPCWSTR infoText = NULL; + BOOL itemEnabled = FALSE; + + size_t iChannel = PodcastChannel_GetActive( hwnd ); + if ( BAD_CHANNEL != iChannel ) + { + Channel *channel = &channels[ iChannel ]; + + HWND hItems = GetDlgItem( hwnd, IDC_ITEMLIST ); + INT selectedCount = ( NULL != hItems ) ? (INT)SNDMSG( hItems, LVM_GETSELECTEDCOUNT, 0, 0L ) : 0; + size_t iItem = ( 1 == selectedCount ) ? (size_t)SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_SELECTED ) : BAD_ITEM; + if ( iItem < channel->items.size() ) + { + if ( FALSE == podcast->itemAscending ) + iItem = channel->items.size() - iItem - 1; + + infoText = channel->items[ iItem ].description; + } + + if ( NULL == infoText || L'\0' == *infoText ) + infoText = channel->description; + + if ( selectedCount > 0 ) + { + INT iSelected = -1; + while ( -1 != ( iSelected = SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED ) ) ) + { + size_t t = iSelected; + if ( t < channel->items.size() ) + { + if ( FALSE == podcast->itemAscending ) + t = channel->items.size() - t - 1; + + LPCWSTR url = channel->items[ t ].url; + if ( NULL != url && L'\0' != *url ) + { + itemEnabled = TRUE; + break; + } + } + } + } + } + + if ( podcast->description != infoText && CSTR_EQUAL != CompareString( CSTR_INVARIANT, 0, podcast->description, -1, infoText, -1 ) ) + { + Plugin_FreeString( podcast->description ); + podcast->description = Plugin_CopyString( infoText ); + SubscriptionView_SetDescription( hwnd, podcast->description ); + } + + PodcastChannel_EnableButtons( hwnd, ( BAD_CHANNEL != iChannel ) ); + PodcastItem_EnableButtons( hwnd, itemEnabled ); +} + +static BOOL SubscriptionView_SortItems(size_t iChannel, int sortColumn) +{ + if (iChannel >= channels.size()) + return FALSE; + + AutoLock lock (channels LOCKNAME("SortItems")); + Channel &channel = channels[iChannel]; + + switch (sortColumn) + { + case COLUMN_TITLE: channel.SortByTitle(); return TRUE; + case COLUMN_MEDIA_TIME: channel.SortByMediaTime(); return TRUE; + case COLUMN_MEDIA_SIZE: channel.SortByMediaSize(); return TRUE; + //case COLUMN_MEDIA: channel.SortByMedia(); return TRUE; + case COLUMN_DATEADDED: channel.SortByDate(); return TRUE; + } + + return FALSE; +} + +static BOOL SubscriptionView_SortChannels(int sortColumn) +{ + AutoLock lock (channels LOCKNAME("SortItems")); + + switch (sortColumn) + { + case COLUMN_TITLE: channels.SortByTitle(); return TRUE; + } + + return FALSE; +} + +static INT SubscriptionView_GetListSortColumn(HWND hwnd, INT listId, BOOL *fAscending) +{ + HWND hItems = GetDlgItem(hwnd, listId); + if (NULL != hItems) + { + HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L); + if (NULL != hHeader) + { + HDITEM item; + item.mask = HDI_FORMAT; + + INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L); + for (INT i = 0; i < count; i++) + { + if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item) && + 0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt)) + { + if (NULL != fAscending) + { + *fAscending = (0 != (HDF_SORTUP & item.fmt)); + } + return i; + } + } + + } + } + return -1; +} + +static void SubscriptionView_SetListSortColumn(HWND hwnd, INT listId, INT index, BOOL fAscending) +{ + HWND hItems = GetDlgItem(hwnd, listId); + if (NULL == hItems) return; + + HWND hHeader = (HWND)SNDMSG(hItems, LVM_GETHEADER, 0, 0L); + if (NULL == hHeader) return; + + HDITEM item; + item.mask = HDI_FORMAT; + // reset first (ml req) + INT count = (INT)SNDMSG(hHeader, HDM_GETITEMCOUNT, 0, 0L); + for (INT i = 0; i < count; i++) + { + if (index != i && FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, i, (LPARAM)&item)) + { + if (0 != ((HDF_SORTUP | HDF_SORTDOWN) & item.fmt)) + { + item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN); + SNDMSG(hHeader, HDM_SETITEM, i, (LPARAM)&item); + } + } + } + + if (FALSE != (BOOL)SNDMSG(hHeader, HDM_GETITEM, index, (LPARAM)&item)) + { + INT fmt = item.fmt & ~(HDF_SORTUP | HDF_SORTDOWN); + fmt |= (FALSE == fAscending) ? HDF_SORTDOWN : HDF_SORTUP; + if (fmt != item.fmt) + { + item.fmt = fmt; + SNDMSG(hHeader, HDM_SETITEM, index, (LPARAM)&item); + } + } +} + +static BOOL SubscriptionView_UpdateInfoUrl(HWND hwnd) +{ + AutoLock lock (channels LOCKNAME("UpdateInfoUrl")); + + BOOL result = FALSE; + BOOL buttonEnable = FALSE; + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL != podcast) + { + Plugin_FreeString(podcast->infoUrl); + podcast->infoUrl = NULL; + + LPCWSTR pszUrl = NULL; + + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL != iChannel) + { + Channel *channel = &channels[iChannel]; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + INT selectedCount = (NULL != hItems ) ? (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L) : 0; + size_t iItem = (1 == selectedCount) ? (size_t)SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_SELECTED) : BAD_ITEM; + if (iItem < channel->items.size()) + { + if (FALSE == podcast->itemAscending) + iItem = channel->items.size() - iItem - 1; + pszUrl = channel->items[iItem].link; + } + + if (NULL == pszUrl || L'\0' == *pszUrl) + pszUrl = channel->link; + } + + if (NULL != pszUrl && L'\0' != *pszUrl) + { + podcast->infoUrl = Plugin_CopyString(pszUrl); + if (NULL != podcast->infoUrl) + { + buttonEnable = TRUE; + result = TRUE; + } + } + } + + HWND hButton = GetDlgItem(hwnd, IDC_VISIT); + if (NULL != hButton) EnableWindow(hButton, buttonEnable); + + return result; +} + +static INT SubscriptionView_Play(HWND hwnd, size_t iChannel, size_t *indexList, size_t indexCount, BOOL fEnqueue, BOOL fForce) +{ + AutoLock channelLock (channels LOCKNAME("Play")); + + if (BAD_CHANNEL == iChannel || iChannel >= channels.size()) + return 0; + + Channel *channel = &channels[iChannel]; + + INT cchBuffer = 0; + + for(size_t i = 0; i < indexCount; i++) + { + size_t iItem = indexList[i]; + if (iItem < channel->items.size()) + { + WCHAR szPath[MAX_PATH * 2] = {0}; + RSS::Item *downloadItem = &channel->items[iItem]; + if ((downloadItem->url && downloadItem->url[0]) && + SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) && + PathFileExists(szPath)) + { + cchBuffer += (lstrlen(szPath) + 1); + } + else + { + wchar_t* url = urlencode(channel->items[iItem].url); + LPCWSTR p = url; + if (NULL != p) + { + cchBuffer += (lstrlen(p) + 1); + free(url); + } + } + } + } + + if (0 == cchBuffer) + return 0; + + cchBuffer++; + LPWSTR pszBuffer = (LPWSTR)calloc(cchBuffer, sizeof(WCHAR)); + if (NULL == pszBuffer) return 0; + + LPWSTR c = pszBuffer; + size_t r = cchBuffer; + + INT playCount = 0; + for(size_t i = 0; i < indexCount; i++) + { + size_t iItem = indexList[i]; + if (iItem < channel->items.size()) + { + WCHAR szPath[MAX_PATH * 2] = {0}; + RSS::Item *downloadItem = &channel->items[iItem]; + if ((downloadItem->url && downloadItem->url[0]) && + SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) && + PathFileExists(szPath)) + { + LPCWSTR p = szPath; + if (NULL != p && L'\0' != *p) + { + if (FAILED(StringCchCopyExW(c, r, p, &c, &r, STRSAFE_NULL_ON_FAILURE)) || 0 == r) + break; + + c++; + r--; + + channel->items[iItem].listened = true; + playCount++; + } + } + else + { + wchar_t* url = urlencode(channel->items[iItem].url); + LPCWSTR p = url; + if (NULL != p && L'\0' != *p) + { + if (FAILED(StringCchCopyExW(c, r, p, &c, &r, STRSAFE_NULL_ON_FAILURE)) || 0 == r) + { + free(url); + break; + } + free(url); + c++; + r--; + + channel->items[iItem].listened = true; + playCount++; + } + } + } + } + + if (c != pszBuffer) + { + *c = L'\0'; + // make sure this is initialised as default handler requires this being zeroed + mlSendToWinampStruct send = {ML_TYPE_STREAMNAMESW,pszBuffer,0}; + // otherwise we've a specific action and need to tell ML to do as we want + if (TRUE == fForce) + send.enqueue = ((FALSE == fEnqueue) ? 0 : 1) | 2; + SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SENDTOWINAMP, (WPARAM)&send); + } + + free(pszBuffer); + return playCount; +} + +static INT SubscriptionView_PlayChannel(HWND hwnd, size_t iChannel, BOOL fEnqueue, BOOL fForce) +{ + AutoLock channelLock (channels LOCKNAME("PlayChannel")); + + if (BAD_CHANNEL == iChannel || iChannel >= channels.size()) + return 0; + + size_t count = channels.size(); + size_t *list = NULL; + if (0 != count) + { + list = (size_t*)calloc(count, sizeof(size_t)); + if (NULL == list) return 0; + for (size_t i = 0; i < count; i++) list[i] = i; + } + + INT result = SubscriptionView_Play(hwnd, iChannel, list, count, fEnqueue, fForce); + + if (NULL != list) + free(list); + + return result; +} + +static void PodcastItem_InitializeList(HWND hwnd) +{ + HWND hControl = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hControl) + return; + + UINT styleEx = (UINT)GetWindowLongPtr(hControl, GWL_EXSTYLE); + SetWindowLongPtr(hControl, GWL_EXSTYLE, styleEx & ~WS_EX_NOPARENTNOTIFY); + + styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | /*LVS_EX_HEADERDRAGDROP |*/ LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES; + SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx); + SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L); + + LVCOLUMN lvc = {0}; + WCHAR szBuffer[128] = {0}; + + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + lvc.pszText = szBuffer; + + lvc.fmt = LVCFMT_LEFT; + lvc.cx = itemTitleWidth; + WASABI_API_LNGSTRINGW_BUF( IDS_ITEM, szBuffer, ARRAYSIZE( szBuffer ) ); + SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc); + + lvc.fmt = LVCFMT_RIGHT; + lvc.cx = itemDateWidth; + WASABI_API_LNGSTRINGW_BUF( IDS_DATE_ADDED, szBuffer, ARRAYSIZE( szBuffer ) ); + SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)1, (LPARAM)&lvc); + + lvc.fmt = LVCFMT_RIGHT; + lvc.cx = itemMediaWidth; + WASABI_API_LNGSTRINGW_BUF( IDS_MEDIA_TIME, szBuffer, ARRAYSIZE( szBuffer ) ); + SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)2, (LPARAM)&lvc); + + lvc.fmt = LVCFMT_RIGHT; + lvc.cx = itemSizeWidth; + WASABI_API_LNGSTRINGW_BUF( IDS_MEDIA_SIZE, szBuffer, ARRAYSIZE( szBuffer ) ); + SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)3, (LPARAM)&lvc); + + HIMAGELIST imageList = ImageList_Create( 15, 15, ILC_COLOR24, 3, 0 ); + if ( imageList != NULL ) + { + HIMAGELIST prevList = (HIMAGELIST)SNDMSG( hControl, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)imageList ); + if ( prevList != NULL ) + ImageList_Destroy( prevList ); + } + + MLSKINWINDOW skinWindow; + skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS; + skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW; + skinWindow.hwndToSkin = hControl; + + MLSkinWindow( plugin.hwndLibraryParent, &skinWindow ); +} + +static void PodcastItem_SelectionChanged(HWND hwnd, BOOL fImmediate) +{ + KillTimer(hwnd, UPDATE_TIMER); + + if (FALSE == fImmediate) + { + SetTimer(hwnd, UPDATE_TIMER, UPDATE_DELAY, PodcastItem_UpdateTimer); + return; + } + + SubscriptionView_UpdateInfoUrl(hwnd); + SubscriptionView_UpdateInfo(hwnd); +} + +static HMENU PodcastItem_GetMenu(HWND hwnd, HMENU baseMenu, INT iItem) +{ + HMENU menu = GetSubMenu(baseMenu, 1); + if (NULL == menu) + return NULL; + + PodcastItem_SelectionChanged(hwnd, TRUE); + + // update the explore media menu item from the download button state (hence the two IDC_DOWNLOAD) + const INT szExtras[] = { IDC_PLAY, IDC_ENQUEUE, IDC_DOWNLOAD, IDC_DOWNLOAD, IDC_VISIT, }; + + for (INT i = 0; i < ARRAYSIZE(szExtras); i++) + { + HWND hButton = GetDlgItem(hwnd, szExtras[i]); + + UINT uEnable = MF_BYCOMMAND | MF_ENABLED; + if (NULL == hButton || (-1 == iItem) || 0 != (WS_DISABLED & GetWindowLongPtr(hButton, GWL_STYLE))) + uEnable |= (MF_GRAYED | MF_DISABLED); + + EnableMenuItem(menu, (i == 3 ? IDC_EXPLORERITEMFOLDER : szExtras[i]), uEnable); + } + + EnableMenuItem(menu, 2, MF_BYPOSITION | ((-1 == iItem) ? (MF_GRAYED | MF_DISABLED) : MF_ENABLED)); + + { // send-to menu shit... + ZeroMemory(&s, sizeof(s)); + IPC_LIBRARY_SENDTOMENU = SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibrarySendToMenu", IPC_REGISTER_WINAMP_IPCMESSAGE); + if (IPC_LIBRARY_SENDTOMENU > 65536 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)0, IPC_LIBRARY_SENDTOMENU) == 0xffffffff) + { + s.mode = 1; + s.hwnd = hwnd; + s.data_type = ML_TYPE_FILENAMESW; + s.ctx[1] = 1; + s.build_hMenu = GetSubMenu(menu, 2); + } + } + + UpdateMenuItems(hwnd, menu); + + // check if the menu itsm is shown as having been download and + // if it hasn't then is nicer to hide 'explore media folder' + { + AutoLock channelLock (channels LOCKNAME("ItemMenu")); + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL != podcast) + { + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL != iChannel) + { + Channel *channel = &channels[iChannel]; + + if (iItem < (INT)channel->items.size()) + { + if (FALSE == podcast->itemAscending) + iItem = (INT)channel->items.size() - iItem - 1; + + RSS::Item *downloadItem = &channel->items[iItem]; + if(downloadItem->downloaded == false) + { + DeleteMenu(menu, IDC_EXPLORERITEMFOLDER, MF_BYCOMMAND); + } + } + } + } + } + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + INT iCount = (NULL != hItems) ? (INT)SNDMSG(hItems, LVM_GETITEMCOUNT, 0, 0L) : 0; + INT command = IDC_REFRESH; + SubscriptionView_EnableMenuCommands(menu, &command, 1, (0 != iCount)); + + return menu; +} + +static BOOL PodcastItem_Sort(HWND hwnd, INT iColumn, BOOL fAscending) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return FALSE; + + BOOL result = FALSE; + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL != iChannel) + { + result = SubscriptionView_SortItems(iChannel, iColumn); + } + + podcast->itemAscending = fAscending; + SubscriptionView_SetListSortColumn(hwnd, IDC_ITEMLIST, iColumn, fAscending); + + if (FALSE != result) + { + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL != hItems) + InvalidateRect(hItems, NULL, TRUE); + } + + return TRUE; +} + +static size_t PodcastChannel_GetActive(HWND hwnd) +{ + AutoLock lock (channels LOCKNAME("GetActive")); + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL != podcast) + { + size_t iChannel = podcast->channelActive; + if (iChannel < channels.size()) + { + return (size_t)iChannel; + } + } + return BAD_CHANNEL; +} +static void PodcastInfo_InitializeList(HWND hwnd) +{ + HWND hControl = GetDlgItem(hwnd, IDC_EPISODE_INFO); + if ( hControl == NULL ) + return; + + MLSKINWINDOW skinWindow; + skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS; + skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW; + skinWindow.hwndToSkin = hControl; + MLSkinWindow(plugin.hwndLibraryParent, &skinWindow); + + MLSkinnedScrollWnd_ShowHorzBar(hControl, FALSE); + MLSkinnedScrollWnd_ShowVertBar(hControl, FALSE); + + UINT styleEx; + + styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT; + SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx); + SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L); + + LVCOLUMN lvc = {0}; + WCHAR szBuffer[128] = {0}; + + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + lvc.fmt = LVCFMT_LEFT; + lvc.pszText = szBuffer; + lvc.cx = 9999; + + WASABI_API_LNGSTRINGW_BUF(IDS_EPISODE_INFO, szBuffer, ARRAYSIZE(szBuffer)); + SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc); + + HWND hHeader = ListView_GetHeader(hControl); + + RECT rect; + GetClientRect(hHeader, &rect); + SetWindowPos(hControl, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER); +} + +static void PodcastChannel_InitializeList(HWND hwnd) +{ + HWND hControl = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL == hControl) + return; + + MLSKINWINDOW skinWindow = {0}; + skinWindow.style = SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWLVS_ALTERNATEITEMS; + skinWindow.skinType = SKINNEDWND_TYPE_LISTVIEW; + skinWindow.hwndToSkin = hControl; + + MLSkinWindow(plugin.hwndLibraryParent, &skinWindow); + + UINT skinStyle = MLSkinnedWnd_GetStyle(hControl); + skinStyle |= SWLVS_SELALWAYS; + MLSkinnedWnd_SetStyle(hControl, skinStyle); + MLSkinnedScrollWnd_ShowHorzBar(hControl, FALSE); + + UINT styleEx = LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT; + SendMessage(hControl, LVM_SETEXTENDEDLISTVIEWSTYLE, styleEx, styleEx); + SendMessage(hControl, LVM_SETUNICODEFORMAT, (WPARAM)TRUE, 0L); + + LVCOLUMN lvc = {0}; + WCHAR szBuffer[128] = {0}; + + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + lvc.fmt = LVCFMT_LEFT; + lvc.pszText = szBuffer; + lvc.cx = 200; + + WASABI_API_LNGSTRINGW_BUF(IDS_CHANNEL, szBuffer, ARRAYSIZE(szBuffer)); + SNDMSG(hControl, LVM_INSERTCOLUMN, (WPARAM)0, (LPARAM)&lvc); +} + +static void PodcastChannel_SelectionChanged(HWND hwnd, BOOL fImmediate) +{ + KillTimer(hwnd, UPDATE_TIMER); + + if (FALSE == fImmediate) + { + SetTimer(hwnd, UPDATE_TIMER, UPDATE_DELAY, PodcastChannel_UpdateTimer); + return; + } + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) + return; + + HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST); + INT iSelected = (NULL != hChannel) ? (INT)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, LVNI_SELECTED) : -1; + + AutoLock channelLock (channels LOCKNAME("Channel Changed")); + + if (-1 != iSelected && ((size_t)iSelected) > channels.size()) + iSelected = -1; + + if (-1 != iSelected && FALSE == podcast->channelAscending) + iSelected = (INT)channels.size() - iSelected - 1; + + podcast->channelActive = iSelected; + + if (-1 != iSelected) + { + INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, NULL); + SubscriptionView_SortItems(iSelected, iSort); + } + + size_t itemsCount = (-1 != iSelected) ? channels[iSelected].items.size() : 0; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL != hItems) + { + SNDMSG(hItems, WM_SETREDRAW, FALSE, 0L); + + LVITEM lvi; + lvi.state = 0; + lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + SNDMSG(hItems, LVM_SETSELECTIONMARK, 0, (LPARAM)-1); + SNDMSG(hItems, LVM_SETITEMSTATE, -1, (LPARAM)&lvi); + SNDMSG(hItems, LVM_SETITEMCOUNT, (WPARAM)itemsCount, 0L); + + SNDMSG(hItems, WM_SETREDRAW, TRUE, 0L); + } + + SubscriptionView_UpdateInfoUrl(hwnd); + SubscriptionView_UpdateInfo(hwnd); + channelLastSelection = iSelected; +} + +static HMENU PodcastChannel_GetMenu(HWND hwnd, HMENU baseMenu, INT iItem) +{ + HMENU menu = GetSubMenu(baseMenu, 0); + if (NULL == menu) return NULL; + + if (iItem != -1) PodcastChannel_SelectionChanged(hwnd, TRUE); + + if (iItem != -1) + { + DeleteMenu(menu, IDC_ADD, MF_BYCOMMAND); + const INT szExtras[] = { IDC_REFRESH, IDC_EDIT, IDC_DELETE, }; + SubscriptionView_EnableMenuCommands(menu, szExtras, ARRAYSIZE(szExtras), (-1 != iItem)); + } + else + { + DeleteMenu(menu, IDC_REFRESH, MF_BYCOMMAND); + DeleteMenu(menu, IDC_EDIT, MF_BYCOMMAND); + DeleteMenu(menu, IDC_DELETE, MF_BYCOMMAND); + DeleteMenu(menu, IDC_VISIT, MF_BYCOMMAND); + DeleteMenu(menu, 0, MF_BYPOSITION); + DeleteMenu(menu, 0, MF_BYPOSITION); + } + + return menu; +} + +static BOOL PodcastChannel_SyncHeaderSize(HWND hwnd, BOOL fRedraw) +{ + HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL == hChannel) return FALSE; + + RECT channelRect; + + HDITEM item; + item.mask = HDI_WIDTH; + + HWND hHeader = (HWND)SNDMSG(hChannel, LVM_GETHEADER, 0, 0L); + if (NULL == hHeader || + FALSE == GetClientRect(hChannel, &channelRect) || + FALSE == SNDMSG(hHeader, HDM_GETITEM, COLUMN_TITLE, (LPARAM)&item)) + { + return FALSE; + } + + LONG columnWidth = channelRect.right - channelRect.left; + + + if (item.cxy == columnWidth) return TRUE; + + item.cxy = columnWidth; + + UINT windowStyle = GetWindowLongPtr(hHeader, GWL_STYLE); + if (FALSE == fRedraw && 0 != (WS_VISIBLE & windowStyle)) + SetWindowLongPtr(hHeader, GWL_STYLE, windowStyle & ~WS_VISIBLE); + + SNDMSG(hHeader, HDM_SETITEM, COLUMN_TITLE, (LPARAM)&item); + + if (FALSE == fRedraw && 0 != (WS_VISIBLE & windowStyle)) + SetWindowLongPtr(hHeader, GWL_STYLE, windowStyle); + + return TRUE; +} + +static BOOL PodcastChannel_Sort(HWND hwnd, INT iColumn, BOOL fAscending) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return FALSE; + + BOOL result = SubscriptionView_SortChannels(iColumn); + + podcast->channelAscending = fAscending; + SubscriptionView_SetListSortColumn(hwnd, IDC_CHANNELLIST, iColumn, fAscending); + + if (FALSE != result) + { + HWND hItems = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL != hItems) + InvalidateRect(hItems, NULL, TRUE); + } + + return result; +} + +static BOOL PodcastChannel_SelectNext(HWND hwnd, BOOL fForward) +{ + HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL == hChannel) return FALSE; + + INT iFocus = (INT)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, (LPARAM)LVNI_FOCUSED); + + if (FALSE != fForward) + { + iFocus++; + INT iCount = (INT)SNDMSG(hChannel, LVM_GETITEMCOUNT, 0, 0L); + if (iFocus >= iCount) return TRUE; + } + else + { + if (iFocus <= 0) return TRUE; + iFocus--; + } + + LVITEM lvi = {0}; + lvi.stateMask = LVIS_FOCUSED | LVIS_SELECTED; + SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi); + + lvi.state = lvi.stateMask; + if (FALSE != SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)iFocus, (LPARAM)&lvi)) + { + SNDMSG(hChannel, LVM_ENSUREVISIBLE, (WPARAM)iFocus, (LPARAM)FALSE); + MLSkinnedScrollWnd_UpdateBars(hChannel, TRUE); + } + + return TRUE; +} +static void CALLBACK PodcastChannel_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed) +{ + KillTimer(hwnd, eventId); + PodcastChannel_SelectionChanged(hwnd, TRUE); +} + +static void CALLBACK PodcastItem_UpdateTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed) +{ + KillTimer(hwnd, eventId); + PodcastItem_SelectionChanged(hwnd, TRUE); +} + +wchar_t* PodcastCommand_OnSendToSelection(HWND hwnd) +{ + AutoLock channelLock (channels LOCKNAME("SendTo")); + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return 0; + + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL == iChannel) return 0; + Channel *channel = &channels[iChannel]; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hItems) return 0; + + INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L); + if (0 == selectedCount) return 0; + + INT iSelected = -1; + WCHAR szPath[MAX_PATH * 2] = {0}, *path = NULL; + int buf_pos = 0, buf_size = 0; + + while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED))) + { + size_t iItem = iSelected; + if (iItem < channel->items.size()) + { + if (FALSE == podcast->itemAscending) + iItem = channel->items.size() - iItem - 1; + + RSS::Item *downloadItem = &channel->items[iItem]; + if (!((downloadItem->url && downloadItem->url[0]) && + SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) && + PathFileExists(szPath))) + { + lstrcpyn(szPath, downloadItem->url, ARRAYSIZE(szPath)); + } + + listbuild(&path, buf_size, buf_pos, szPath); + } + } + + if (path) path[buf_pos] = 0; + return path; +} + +static void SubscriptionView_ListContextMenu(HWND hwnd, INT controlId, POINTS pts) +{ + HWND hControl = GetDlgItem(hwnd, controlId); + if (NULL == hControl) return; + + POINT pt; + POINTSTOPOINT(pt, pts); + + RECT controlRect, headerRect; + if (FALSE == GetClientRect(hControl, &controlRect)) + SetRectEmpty(&controlRect); + else + MapWindowPoints(hControl, HWND_DESKTOP, (POINT*)&controlRect, 2); + + HWND hHeader = (HWND)SNDMSG(hControl, LVM_GETHEADER, 0, 0L); + if (0 == (WS_VISIBLE & GetWindowLongPtr(hHeader, GWL_STYLE)) || FALSE == GetWindowRect(hHeader, &headerRect)) + { + SetRectEmpty(&headerRect); + } + + if (-1 == pt.x && -1 == pt.y) + { + RECT rect; + + rect.left = LVIR_BOUNDS; + INT iMark = SNDMSG(hControl, LVM_GETNEXTITEM, -1, LVNI_SELECTED | LVNI_FOCUSED); + if (-1 == iMark) iMark = SNDMSG(hControl, LVM_GETNEXTITEM, -1, LVNI_SELECTED); + if (-1 != iMark && FALSE != SNDMSG(hControl, LVM_GETITEMRECT, (WPARAM)iMark, (LPARAM)&rect)) + { + pt.x = rect.left + 4; + if(pt.x > rect.right) pt.x = rect.right; + pt.y = rect.bottom - (rect.bottom - rect.top)/3; + + MapWindowPoints(hControl, HWND_DESKTOP, &pt, 1); + } + } + + INT iItem = -1; + + if ((-1 != pt.x || -1 != pt.y) && FALSE != PtInRect(&controlRect, pt)) + { + if (FALSE != PtInRect(&headerRect, pt)) + { + return; + } + else + { + LVHITTESTINFO hitTest; + hitTest.pt = pt; + MapWindowPoints(HWND_DESKTOP, hControl, &hitTest.pt, 1); + + iItem = (INT)SNDMSG(hControl, LVM_HITTEST, 0, (LPARAM)&hitTest); + if (0 == (LVHT_ONITEM & hitTest.flags)) iItem = -1; + } + } + else + { + pt.x = controlRect.left + 2; + pt.y = controlRect.top + 2; + if (headerRect.top == controlRect.top && headerRect.bottom > controlRect.top) + pt.y = headerRect.bottom + 2; + } + + HMENU baseMenu = WASABI_API_LOADMENU(IDR_MENU1); + if (NULL == baseMenu) return; + + HMENU menu = NULL; + switch(controlId) + { + case IDC_ITEMLIST: menu = PodcastItem_GetMenu(hwnd, baseMenu, iItem); break; + case IDC_CHANNELLIST: menu = PodcastChannel_GetMenu(hwnd, baseMenu, iItem); break; + } + + if (NULL != menu) + { + int r = Menu_TrackPopup(plugin.hwndLibraryParent, menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN | 0x1000/*TPM_VERPOSANIMATION*/, pt.x, pt.y, hwnd, NULL); + if (!SendMessage(hwnd, WM_COMMAND, r, 0)) + { + s.menu_id = r; + if (s.mode == 2 && SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff) + { + s.mode = 3; + + wchar_t* path = PodcastCommand_OnSendToSelection(hwnd); + if (path && *path) + { + s.data = path; + SendMessage(plugin.hwndWinampParent, WM_WA_IPC,(WPARAM)&s, IPC_LIBRARY_SENDTOMENU); + free(path); + } + } + } + + if (s.mode) + { + s.mode=4; + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&s,IPC_LIBRARY_SENDTOMENU); // cleanup + } + } + + DestroyMenu(baseMenu); +} + +static void SubscriptionView_UpdateLayout(HWND hwnd, BOOL fRedraw, UINT uReason, HRGN validRegion, POINTS layoutOffset) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + RECT clientRect; + if (FALSE == GetClientRect(hwnd, &clientRect)) return; + + LONG clientWidth = clientRect.right - clientRect.left - WASABI_API_APP->getScaleX(2); + LONG clientHeight = clientRect.bottom - clientRect.top; + + const INT szItems[LI_LAST] = { IDC_CHANNELLIST, IDC_ITEMLIST, IDC_VDIV, + IDC_FINDNEW, IDC_ADD, IDC_EDIT, IDC_DELETE, IDC_REFRESH, + IDC_HDIV, + IDC_EPISODE_INFO, IDC_DESCRIPTION, + IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, IDC_DOWNLOAD, IDC_VISIT, IDC_STATUS}; + + LAYOUTITEM *layout = (LAYOUTITEM*)calloc(ARRAYSIZE(szItems), sizeof(LAYOUTITEM)); + if (NULL == layout) return; + + Layout_Initialize(hwnd, szItems, ARRAYSIZE(szItems), layout); + + if (episode_info_cy == 0 || (layout[LI_EPISODE_INFO].cy && episode_info_cy != layout[LI_EPISODE_INFO].cy)) + { + HWND hControl = GetDlgItem(hwnd, IDC_EPISODE_INFO); + if (IsWindow(hControl)) + { + HWND hHeader = ListView_GetHeader(hControl); + if (IsWindow(hHeader)) + { + RECT rect; + GetClientRect(hHeader, &rect); + SetWindowPos(hControl, 0, 0, 0, rect.right-rect.left, rect.bottom-rect.top, SWP_NOMOVE|SWP_NOZORDER); + episode_info_cy = rect.bottom-rect.top; + } + else + episode_info_cy = layout[LI_EPISODE_INFO].cy; + } + else + episode_info_cy = layout[LI_EPISODE_INFO].cy; + } + + LONG t = (clientWidth - layout[LI_VERT].cx) * podcast->vertDivider / 10000; + LONG t2 = clientRect.right - layout[LI_VERT].cx; + + if (LAYOUTREASON_DIV_LEFT == uReason && t < clientRect.left + WASABI_API_APP->getScaleX(36)) + t = clientRect.left; + + if (LAYOUTREASON_DIV_RIGHT == uReason && t > (t2 - WASABI_API_APP->getScaleX(36))) + t = t2; + + if (t < clientRect.left + WASABI_API_APP->getScaleX(36)) t = clientRect.left; + else if (t > t2 - WASABI_API_APP->getScaleX(36)) t = t2; + + layout[LI_CHANNEL].x = WASABI_API_APP->getScaleX(1); + layout[LI_CHANNEL].y = WASABI_API_APP->getScaleX(1); + layout[LI_CHANNEL].cx = t; + layout[LI_VERT].x = LI_GET_R(layout[LI_CHANNEL]); + layout[LI_VERT].y = WASABI_API_APP->getScaleX(1); + layout[LI_ITEM].x = LI_GET_R(layout[LI_VERT]); + layout[LI_ITEM].y = WASABI_API_APP->getScaleX(1); + LI_SET_R(layout[LI_ITEM], clientWidth); + + layout[LI_HORZ].cx = clientWidth - layout[LI_HORZ].x*WASABI_API_APP->getScaleX(2); + layout[LI_EPISODE_INFO].x = WASABI_API_APP->getScaleX(1); + layout[LI_EPISODE_INFO].cx = clientWidth - layout[LI_EPISODE_INFO].x; + layout[LI_INFO].cx = clientWidth - layout[LI_INFO].x*WASABI_API_APP->getScaleX(2) - 1; + LI_SET_R(layout[LI_STATUS], clientWidth); + if (layout[LI_STATUS].cx < WASABI_API_APP->getScaleX(20)) layout[LI_STATUS].cx = 0; + + t = clientHeight - layout[LI_PLAY].cy; + int cum_width = 0, inc = 0, btnY = 0; + const INT szBottomRow[] = { LI_PLAY, LI_ENQUEUE, LI_CUSTOM, LI_DOWNLOAD, LI_VISIT, LI_STATUS, }; + for (INT i = 0; i < ARRAYSIZE(szBottomRow); i++) + { + LAYOUTITEM *p = &layout[szBottomRow[i]]; + if (IsWindow(p->hwnd)) + { + if (!inc) p->x = 1; + else p->x = 1 + cum_width + WASABI_API_APP->getScaleX(4)*inc; + + wchar_t buffer[128] = {0}; + GetWindowTextW(p->hwnd, buffer, ARRAYSIZE(buffer)); + LRESULT idealSize = MLSkinnedButton_GetIdealSize(p->hwnd, buffer); + + btnY = p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize)); + p->y = clientHeight - p->cy; + + if (szBottomRow[i] != LI_STATUS) // exclude the last one so it'll span the remainder of the space + { + if (LI_CUSTOM != szBottomRow[i] || customAllowed) + { + if (groupBtn && szBottomRow[i] == LI_PLAY && enqueuedef == 1) + { + p->flags |= SWP_HIDEWINDOW; + p->cy = 0; + continue; + } + + if (groupBtn && szBottomRow[i] == LI_ENQUEUE && enqueuedef != 1) + { + p->flags |= SWP_HIDEWINDOW; + p->cy = 0; + continue; + } + + if (groupBtn && (szBottomRow[i] == LI_PLAY || szBottomRow[i] == LI_ENQUEUE) && customAllowed) + { + p->flags |= SWP_HIDEWINDOW; + p->cy = 0; + continue; + } + + LONG width = LOWORD(idealSize) + WASABI_API_APP->getScaleX(6); + p->cx = width; + p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize)); + cum_width += width; + inc++; + } + else + { + p->flags |= SWP_HIDEWINDOW; + p->cy = 0; + continue; + } + } + else + { + LRESULT idealSize = MLSkinnedStatic_GetIdealSize(p->hwnd, buffer); + btnY = p->cy = WASABI_API_APP->getScaleY(HIWORD(idealSize)); + p->y = clientHeight - p->cy; + p->cx = clientWidth - cum_width - (WASABI_API_APP->getScaleX(6) * 2); + } + } + } + + t = (clientHeight - layout[LI_HORZ].cy) * podcast->horzDivider / 10000; + t2 = layout[LI_PLAY].y - layout[LI_HORZ].cy; + + if (LAYOUTREASON_DIV_TOP == uReason && t < clientRect.top + WASABI_API_APP->getScaleY(36 + btnY)) + t = clientRect.top; + + if (LAYOUTREASON_DIV_BOTTOM == uReason && t > (t2 - WASABI_API_APP->getScaleY(36 + btnY))) + t = t2; + + if (t < clientRect.top + WASABI_API_APP->getScaleY(36 + btnY)) t = clientRect.top; + else if (t > t2 - WASABI_API_APP->getScaleY(36 + btnY)) t = t2; + + cum_width = 0; + const INT szMiddleRow[] = { LI_FINDNEW, LI_ADD, LI_EDIT, LI_DELETE, LI_REFRESH, }; + for (INT i = 0; i < ARRAYSIZE(szMiddleRow); i++) + { + LAYOUTITEM *p = &layout[szMiddleRow[i]]; + p->x = 1 + (i ? cum_width + WASABI_API_APP->getScaleX(4)*i : 0); + + wchar_t buffer[128] = {0}; + GetWindowTextW(p->hwnd, buffer, ARRAYSIZE(buffer)); + LRESULT idealSize = MLSkinnedButton_GetIdealSize(p->hwnd, buffer); + // used to hide the 'directory' button if not using one + LONG width = (!i && !serviceUrl[0] ? WASABI_API_APP->getScaleX(-4) : LOWORD(idealSize) + WASABI_API_APP->getScaleX(6)); + p->cx = width; + cum_width += width; + + p->cy = (t < WASABI_API_APP->getScaleY(48)) ? 0 : WASABI_API_APP->getScaleY(HIWORD(idealSize)); + p->y = t - p->cy; + } + + layout[LI_HORZ].y = LI_GET_B(layout[LI_FINDNEW]); + layout[LI_EPISODE_INFO].y = LI_GET_B(layout[LI_HORZ]); + if (clientRect.bottom - layout[LI_EPISODE_INFO].y < WASABI_API_APP->getScaleY(episode_info_cy)) + layout[LI_EPISODE_INFO].cy = 0; + else + layout[LI_EPISODE_INFO].cy = episode_info_cy; + + layout[LI_INFO].y = LI_GET_B(layout[LI_EPISODE_INFO]); + LI_SET_B(layout[LI_INFO], layout[LI_PLAY].y - WASABI_API_APP->getScaleY(4)); + + t = layout[LI_FINDNEW].y; + if (layout[LI_FINDNEW].cy > 0) t -= podcast->middleRowSpace; + + layout[LI_CHANNEL].cy = t; + layout[LI_VERT].cy = t; + layout[LI_ITEM].cy = t; + layout[LI_INFO].flags |= SWP_NOREDRAW; + + Layout_SetVisibility(&clientRect, layout, ARRAYSIZE(szItems)); + Layout_Perform(hwnd, layout, ARRAYSIZE(szItems), fRedraw); + PodcastChannel_SyncHeaderSize(hwnd, fRedraw); + + if (NULL != validRegion) + { + RECT validRect; + CopyRect(&validRect, &clientRect); + validRect.right += (layout[LI_INFO].rect.right - LI_GET_R(layout[LI_INFO])); + validRect.bottom += (layout[LI_PLAY].rect.bottom - LI_GET_B(layout[LI_PLAY])); + Layout_GetValidRgn(validRegion, layoutOffset, &validRect, layout, ARRAYSIZE(szItems)); + } + + free(layout); +} + +static BOOL SubscriptionView_UpdateImageList(HIMAGELIST imageList, COLORREF rgbBk, COLORREF rgbFg) +{ + if(NULL == imageList) + return FALSE; + + INT imageCount = ImageList_GetImageCount(imageList); + + MLIMAGESOURCE source; + ZeroMemory(&source, sizeof(source)); + source.cbSize = sizeof(source); + source.hInst = plugin.hDllInstance; + source.type = SRC_TYPE_PNG; + + MLIMAGEFILTERAPPLYEX filter; + ZeroMemory(&filter, sizeof(filter)); + filter.cbSize = sizeof(filter); + filter.filterUID = MLIF_FILTER3_UID; + filter.rgbBk = rgbBk; + filter.rgbFg = rgbFg; + + const INT szImages[] = { IDR_TEXT_ICON, IDR_DOWNLOAD_ICON, IDR_MEDIA_ICON, IDR_MEDIA_ICON, }; + + for (INT i = 0; i < ARRAYSIZE(szImages); i++) + { + source.lpszName = MAKEINTRESOURCE(szImages[i]); + HBITMAP bitmap = MLImageLoader_LoadDib(plugin.hwndLibraryParent, &source); + if (NULL != bitmap) + { + DIBSECTION dib; + if (sizeof(dib) == GetObject(bitmap, sizeof(dib), &dib)) + { + filter.pData = (BYTE*)dib.dsBm.bmBits; + filter.bpp = dib.dsBm.bmBitsPixel; + filter.cx = dib.dsBm.bmWidth; + filter.cy = dib.dsBm.bmHeight; + + if (filter.cy < 0) + filter.cy = -filter.cy; + + if (MLImageFilter_ApplyEx(plugin.hwndLibraryParent, &filter)) + { + INT result = (i < imageCount) ? + ImageList_Replace(imageList, i, bitmap, NULL) : + ImageList_Add(imageList, bitmap, NULL); + } + } + + DeleteObject(bitmap); + } + } + + return TRUE; +} + +static void SubscriptionView_UpdateSkin(HWND hwnd) +{ + UINT windowStyle = GetWindowLongPtr(hwnd, GWL_STYLE); + if (0 != (WS_VISIBLE & windowStyle)) + SetWindowLongPtr(hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE); + + HWND hControl; + COLORREF rgbBk = dialogSkinner.Color(WADLG_ITEMBG); + COLORREF rgbText = dialogSkinner.Color(WADLG_ITEMFG); + HFONT hFont = dialogSkinner.GetFont(); + + episode_info_cy = 0; + + static const INT szLists[] = {IDC_CHANNELLIST, IDC_ITEMLIST, IDC_EPISODE_INFO}; + for (INT i = 0; i < ARRAYSIZE(szLists); i++) + { + if (NULL != (hControl = GetDlgItem(hwnd, szLists[i]))) + { + ListView_SetBkColor(hControl, rgbBk); + ListView_SetTextBkColor(hControl, rgbBk); + ListView_SetTextColor(hControl, rgbText); + if (NULL != hFont) + { + SendMessage(hControl, WM_SETFONT, (WPARAM)hFont, 0L); + } + + HIMAGELIST imageList = (HIMAGELIST)SendMessage(hControl, LVM_GETIMAGELIST, LVSIL_SMALL, 0L); + if (NULL != imageList) + { + SubscriptionView_UpdateImageList(imageList, rgbBk, rgbText); + } + } + } + + if (0 != (WS_VISIBLE & windowStyle)) + { + windowStyle = GetWindowLongPtr(hwnd, GWL_STYLE); + if (0 == (WS_VISIBLE & windowStyle)) + SetWindowLongPtr(hwnd, GWL_STYLE, windowStyle | WS_VISIBLE); + + RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN); + SubscriptionView_UpdateInfo(hwnd); + } + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + SubscriptionView_SetDescription(hwnd, podcast->description); + + HRGN validRegion = (NULL != podcast->updateRegion) ? CreateRectRgn(0, 0, 0, 0) : NULL; + SubscriptionView_UpdateLayout(hwnd, TRUE, LAYOUTREASON_RESIZE, validRegion, podcast->updateOffset); + if (NULL != validRegion) + { + CombineRgn(podcast->updateRegion, podcast->updateRegion, validRegion, RGN_DIFF); + DeleteObject(validRegion); + } +} + +static void SubscriptionView_SkinControls(HWND hwnd, const INT *itemList, INT itemCount, UINT skinType, UINT skinStyle) +{ + FLICKERFIX ff = {0, FFM_ERASEINPAINT}; + MLSKINWINDOW skinWindow = {0}; + skinWindow.style = skinStyle; + skinWindow.skinType = skinType; + + for(INT i = 0; i < itemCount; i++) + { + ff.hwnd = skinWindow.hwndToSkin = GetDlgItem(hwnd, itemList[i]); + if (IsWindow(skinWindow.hwndToSkin)) + { + MLSkinWindow(plugin.hwndLibraryParent, &skinWindow); + SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_FLICKERFIX, (WPARAM)&ff); + } + } +} + +static void SubscriptionView_InitMetrics(HWND hwnd) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + HWND hControl; + RECT rect; + LONG t; + + podcast->vertDivider = (LONG)(channelDividerPercent * 10000); + podcast->horzDivider = (LONG)(htmlDividerPercent * 10000); + + if (NULL != (hControl = GetDlgItem(hwnd, IDC_ADD)) && FALSE != GetWindowRect(hControl, &rect)) + { + t = rect.top + WASABI_API_APP->getScaleY(2); + if (NULL != (hControl = GetDlgItem(hwnd, IDC_CHANNELLIST)) && FALSE != GetWindowRect(hControl, &rect)) + podcast->middleRowSpace = t - rect.bottom; + } + + if (NULL != (hControl = GetDlgItem(hwnd, IDC_PLAY)) && FALSE != GetWindowRect(hControl, &rect)) + { + t = rect.top + WASABI_API_APP->getScaleY(2); + if (NULL != (hControl = GetDlgItem(hwnd, IDC_DESCRIPTION)) && FALSE != GetWindowRect(hControl, &rect)) + podcast->bottomRowSpace = t - rect.bottom; + } +} + +static void PodcastChannel_OnKeyDown(HWND hwnd, NMLVKEYDOWN *pkd) +{ + bool ctrl = (0 != (0x8000 & GetAsyncKeyState(VK_CONTROL))), + shift = (0 != (0x8000 & GetAsyncKeyState(VK_SHIFT))); + switch(pkd->wVKey) + { + case VK_DELETE: + if(!shift && !ctrl) + { + SENDCMD(hwnd, IDC_DELETE, 0, 0); + } + break; + + case VK_F5: + if(!ctrl) + { + SENDCMD(hwnd, (!shift ? IDC_REFRESH : IDC_REFRESHALL), 0, 0); + } + break; + + case VK_F7: + if (!shift && !ctrl) + { + SENDCMD(hwnd, IDC_VISIT, 0, 0); + } + break; + + case VK_F2: + if(!shift && !ctrl) + { + SENDCMD(hwnd, IDC_EDIT, 0, 0); + } + break; + + case VK_INSERT: + if(!shift && !ctrl) + { + SENDCMD(hwnd, IDC_ADD, 0, 0); + } + break; + + case VK_LEFT: + if(!shift && !ctrl) + { + PodcastChannel_SelectNext(hwnd, FALSE); + } + break; + + case VK_RIGHT: + if(!shift && !ctrl) + { + PodcastChannel_SelectNext(hwnd, TRUE); + } + break; + } +} + +static void PodcastChannel_OnColumnClick(HWND hwnd, NMLISTVIEW *plv) +{ + BOOL fAscending; + INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, &fAscending); + fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : TRUE; + + PodcastChannel_Sort(hwnd, plv->iSubItem, fAscending); +} + +static void PodcastChannel_OnDoubleClick(HWND hwnd, NMITEMACTIVATE *pia) +{ + if(pia->iItem != -1) + { + LVITEM lvi; + lvi.state = LVIS_SELECTED; + lvi.stateMask = LVIS_SELECTED; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + + if (NULL != hItems && + FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi)) + { + SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0); + lvi.state = 0; + SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi); + } + } +} + +static void PodcastChannel_OnReturn(HWND hwnd, NMHDR *pnmh) +{ + LVITEM lvi; + lvi.state = LVIS_SELECTED; + lvi.stateMask = LVIS_SELECTED; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + + if (NULL != hItems && + FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi)) + { + SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0); + lvi.state = 0; + SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi); + } +} + +static void PodcastChannel_OnGetDispInfo(HWND hwnd, NMLVDISPINFO *pdi) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + if (0 != (LVIF_TEXT & pdi->item.mask)) + { + switch (pdi->item.iSubItem) + { + case COLUMN_TITLE: + { + pdi->item.pszText[0] = 0; // turn into an empty string as a safety precaution + AutoLock channelLock(channels LOCKNAME("FillChannelTitle")); + size_t iChannel = (size_t)pdi->item.iItem; + if (iChannel < channels.size()) + { + if (FALSE == podcast->channelAscending) + iChannel = channels.size() - iChannel - 1; + + const wchar_t *title = channels[iChannel].title; + if (title) + StringCchCopy(pdi->item.pszText, pdi->item.cchTextMax, title); + } + } + break; + } + } +} + +static void PodcastChannel_OnItemChanged(HWND hwnd, NMLISTVIEW *plv) +{ + if ((plv->iItem != -1) && (LVIS_SELECTED & plv->uNewState) != (LVIS_SELECTED & plv->uOldState)) + PodcastChannel_SelectionChanged(hwnd, FALSE); +} + +static void PodcastChannel_OnSetFocus(HWND hwnd, NMHDR *pnmh) +{ + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hItems) return; + + LVITEM lvi; + lvi.state = 0; + lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + SNDMSG(hItems, LVM_SETITEMSTATE, -1, (LPARAM)&lvi); + SNDMSG(hItems, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)-1); +} + +static LRESULT PodcastChannel_OnNotify(HWND hwnd, NMHDR *pnmh) +{ + switch (pnmh->code) + { + case LVN_KEYDOWN: PodcastChannel_OnKeyDown(hwnd, (NMLVKEYDOWN *)pnmh); break; + case LVN_COLUMNCLICK: PodcastChannel_OnColumnClick(hwnd, (NMLISTVIEW*)pnmh); break; + case NM_DBLCLK: PodcastChannel_OnDoubleClick(hwnd, (NMITEMACTIVATE*)pnmh); break; + case LVN_GETDISPINFO: PodcastChannel_OnGetDispInfo(hwnd, (NMLVDISPINFO*)pnmh); break; + case LVN_ITEMCHANGED: PodcastChannel_OnItemChanged(hwnd, (NMLISTVIEW*)pnmh); break; + case NM_SETFOCUS: PodcastChannel_OnSetFocus(hwnd, pnmh); break; + case NM_RETURN: PodcastChannel_OnReturn(hwnd, pnmh); break; + } + + return 0; +} + +bool ParseDuration(const wchar_t *duration, int *out_hours, int *out_minutes, int *out_seconds) +{ + if (duration && duration[0]) + { + const wchar_t *colon_position = wcschr(duration, L':'); + if (colon_position == 0) + { + int v = _wtoi(duration); + *out_hours = v / 3600; + *out_minutes = (v/60)%60; + *out_seconds = v % 60; + return true; + } + else + { + int first_time = _wtoi(duration); + duration = colon_position+1; + colon_position = wcschr(duration, L':'); + if (colon_position == 0) // only have MM:SS + { + *out_hours=0; + *out_minutes = first_time; + *out_seconds = _wtoi(duration); + } + else + { + *out_hours = first_time; + *out_minutes = _wtoi(duration); + *out_seconds = _wtoi(colon_position+1); + } + return true; + } + } + else + { +return false; + } +} + +static void PodcastItem_OnColumnClick(HWND hwnd, NMLISTVIEW *plv) +{ + BOOL fAscending; + INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, &fAscending); + fAscending = (-1 != iSort && iSort == plv->iSubItem) ? (!fAscending) : TRUE; + + PodcastItem_Sort(hwnd, plv->iSubItem, fAscending); +} + +static void PodcastItem_OnGetDispInfo(HWND hwnd, NMLVDISPINFO *pdi) +{ + AutoLock channelLock (channels LOCKNAME("Item LVN_GETDISPINFO")); + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + size_t iChannel = podcast->channelActive; + if (iChannel >= channels.size()) return; + + size_t iItem = pdi->item.iItem; + if (iItem >= channels[iChannel].items.size()) return; + if (FALSE == podcast->itemAscending) iItem = channels[iChannel].items.size() - iItem - 1; + + RSS::Item *item = &channels[iChannel].items[iItem]; + + if (0 != (LVIF_IMAGE & pdi->item.mask)) + { + switch (pdi->item.iSubItem) + { + case COLUMN_TITLE: + if (!item->url || !item->url[0]) + pdi->item.iImage = 0; + else if (item->downloaded) + pdi->item.iImage = 1; + else if (item->listened) + pdi->item.iImage = 2; + else + pdi->item.iImage = 3; + break; + } + } + + if (0 != (LVIF_TEXT &pdi->item.mask)) + { + pdi->item.pszText[0] = L'\0'; + switch (pdi->item.iSubItem) + { + case COLUMN_TITLE: + lstrcpyn(pdi->item.pszText, item->itemName, pdi->item.cchTextMax); + break; + case COLUMN_MEDIA_TIME: + { + int hours, minutes, seconds; + if (ParseDuration(item->duration, &hours, &minutes, &seconds)) + { + if (hours) + { + StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"%d:%02d:%02d", hours, minutes, seconds); + } + else if (minutes) + { + StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"%d:%02d", minutes, seconds); + } + else + { + StringCchPrintfW(pdi->item.pszText, pdi->item.cchTextMax, L"0:%02d", seconds); + } + } + } + + break; + case COLUMN_MEDIA_SIZE: + if (item->size) + { + WASABI_API_LNG->FormattedSizeString(pdi->item.pszText, pdi->item.cchTextMax, item->size); + } + break; + //case COLUMN_MEDIA: + // WASABI_API_LNGSTRINGW_BUF((item->url && item->url[0]) ? IDS_ARTICLE_WITH_MEDIA : IDS_TEXT_ARTICLE, + // pdi->item.pszText, pdi->item.cchTextMax); + // break; + case COLUMN_DATEADDED: + MakeDateString(item->publishDate, pdi->item.pszText, pdi->item.cchTextMax); + break; + } + } +} + +static void PodcastItem_OnItemChanged(HWND hwnd, NMLISTVIEW *plv) +{ + if ((LVIS_SELECTED & plv->uNewState) != (LVIS_SELECTED & plv->uOldState)) + PodcastItem_SelectionChanged(hwnd, FALSE); +} + +static void PodcastItem_OnSetFocus(HWND hwnd, NMHDR *pnmh) +{ + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hItems) return; + + INT iMark = (INT)SNDMSG(hItems, LVM_GETNEXTITEM, -1, LVNI_FOCUSED | LVNI_SELECTED); + if (-1 == iMark) + { + POINT pt; + RECT rect; + if (GetCursorPos(&pt) && GetClientRect(hwnd, &rect)) + { + MapWindowPoints(hwnd, HWND_DESKTOP, (POINT*)&rect, 2); + if (PtInRect(&rect, pt)) + { + const INT szKey[] = { VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, /*VK_XBUTTON1*/0x05, /*VK_XBUTTON2*/0x06, }; + for (INT i = 0; i < ARRAYSIZE(szKey); i++) + { + if (0 != (0x80000000 & GetAsyncKeyState(szKey[i]))) + return; + } + } + } + + LVITEM lvi; + lvi.state = LVIS_SELECTED | LVIS_FOCUSED; + lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + if (FALSE != SNDMSG(hItems, LVM_SETITEMSTATE, 0, (LPARAM)&lvi)) + { + SNDMSG(hItems, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)0); + SNDMSG(hItems, LVM_ENSUREVISIBLE, 0, FALSE); + } + + } +} + +static void PodcastItem_OnDoubleClick(HWND hwnd, NMITEMACTIVATE *pia) +{ + SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0); +} + +static void PodcastItem_OnKeyDown(HWND hwnd, NMLVKEYDOWN *pkd) +{ + bool ctrl = (0 != (0x8000 & GetAsyncKeyState(VK_CONTROL))), + shift = (0 != (0x8000 & GetAsyncKeyState(VK_SHIFT))); + switch(pkd->wVKey) + { + case VK_F5: + if (!shift && !ctrl) + { + SENDCMD(hwnd, IDC_REFRESH, 0, 0); + } + break; + + case VK_F7: + if (!shift && !ctrl) + { + SENDCMD(hwnd, IDC_VISIT, 0, 0); + } + break; + + case 'A': + if (!shift && ctrl) + { + SENDCMD(hwnd, IDC_SELECTALL, 0, 0); + } + break; + + case 'F': + if (!shift && ctrl) + { + SENDCMD(hwnd, IDC_EXPLORERITEMFOLDER, 0, 0); + } + break; + + case 'D': + if (!shift && ctrl) + { + SENDCMD(hwnd, IDC_DOWNLOAD, 0, 0); + } + break; + } +} + +static void PodcastItem_OnReturn(HWND hwnd, NMHDR *pnmh) +{ + SENDCMD(hwnd, (((!!(GetAsyncKeyState(VK_SHIFT)&0x8000)) ^ ML_ENQDEF_VAL()) ? IDC_ENQUEUEACTION : IDC_PLAYACTION), 0, 0); +} + +static LRESULT PodcastItem_OnNotify(HWND hwnd, NMHDR *pnmh) +{ + switch (pnmh->code) + { + case NM_SETFOCUS: PodcastItem_OnSetFocus(hwnd, pnmh); break; + case LVN_COLUMNCLICK: PodcastItem_OnColumnClick(hwnd, (NMLISTVIEW*)pnmh); break; + case NM_DBLCLK: PodcastItem_OnDoubleClick(hwnd, (NMITEMACTIVATE*)pnmh); break; + case LVN_GETDISPINFO: PodcastItem_OnGetDispInfo(hwnd, (NMLVDISPINFO*)pnmh); break; + case LVN_ITEMCHANGED: PodcastItem_OnItemChanged(hwnd, (NMLISTVIEW*)pnmh); break; + case LVN_KEYDOWN: PodcastItem_OnKeyDown(hwnd, (NMLVKEYDOWN *)pnmh); break; + case NM_RETURN: PodcastItem_OnReturn(hwnd, pnmh); break; + } + + return 0; +} + +static void CALLBACK SubscriptionView_OnDividerMoved(HWND hDivider, INT position, LPARAM param) +{ + HWND hwnd = GetParent(hDivider); + if(NULL == hwnd) return; + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + RECT clientRect, dividerRect; + if (FALSE == GetClientRect(hwnd, &clientRect) || + FALSE == GetWindowRect(hDivider, ÷rRect)) + { + return; + } + + LONG t; + + UINT reason = 0; + + switch(param) + { + case IDC_HDIV: + t = (clientRect.bottom - clientRect.top) - (dividerRect.bottom - dividerRect.top); + t = 10000 * position / t; + if (podcast->horzDivider != t) + { + reason = (podcast->horzDivider > t) ? LAYOUTREASON_DIV_TOP : LAYOUTREASON_DIV_BOTTOM; + podcast->horzDivider = t; + } + break; + case IDC_VDIV: + t = (clientRect.right - clientRect.left) - (dividerRect.right - dividerRect.left); + t = 10000 * position / t; + if (podcast->vertDivider != t) + { + reason = (podcast->vertDivider > t) ? LAYOUTREASON_DIV_LEFT : LAYOUTREASON_DIV_RIGHT; + podcast->vertDivider = t; + } + break; + } + + if (0 != reason) + { + RECT rc; + GetClientRect(hwnd, &rc); + + POINTS pts = {0,0}; + + HRGN updateRegion = CreateRectRgnIndirect(&rc); + HRGN validRegion = CreateRectRgn(0, 0, 0, 0); + + SubscriptionView_UpdateLayout(hwnd, FALSE, reason, validRegion, pts); + CombineRgn(updateRegion, updateRegion, validRegion, RGN_DIFF); + RedrawWindow(hwnd, NULL ,updateRegion, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN); + + DeleteObject(updateRegion); + DeleteObject(validRegion); + } +} + +static INT_PTR SubscriptionView_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam) +{ + SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)SUBSCRIPTIONVIEW_NAME); + + HACCEL accel = WASABI_API_LOADACCELERATORSW(IDR_VIEW_DOWNLOAD_ACCELERATORS); + if (accel) + WASABI_API_APP->app_addAccelerators(hwnd, &accel, 1, TRANSLATE_MODE_CHILD); + + PODCAST *podcast = (PODCAST*)calloc(1, sizeof(PODCAST)); + if (NULL == podcast || FALSE == SetProp(hwnd, MAKEINTATOM(VIEWPROP), podcast)) + { + if (NULL != podcast) free(podcast); + return 0; + } + + current_window = hwnd; + + if (!view.play) + { + SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GET_VIEW_BUTTON_TEXT, (WPARAM)&view); + } + + g_context_menus3 = WASABI_API_LOADMENU(IDR_MENU1); + groupBtn = ML_GROUPBTN_VAL(); + enqueuedef = (ML_ENQDEF_VAL() == 1); + + // v5.66+ - re-use the old predixis parts so the button can be used functionally via ml_enqplay + // pass the hwnd, button id and plug-in id so the ml plug-in can check things as needed + pluginMessage p = {ML_MSG_VIEW_BUTTON_HOOK, (INT_PTR)hwnd, (INT_PTR)MAKELONG(IDC_CUSTOM, IDC_ENQUEUE), (INT_PTR)L"ml_wire"}; + wchar_t *pszTextW = (wchar_t *)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p); + if (pszTextW && pszTextW[0] != 0) + { + // set this to be a bit different so we can just use one button and not the + // mixable one as well (leaving that to prevent messing with the resources) + customAllowed = TRUE; + SetDlgItemTextW(hwnd, IDC_CUSTOM, pszTextW); + } + else + customAllowed = FALSE; + + SubscriptionView_InitMetrics(hwnd); + + podcast->channelActive = -1; + + HWND hLibrary = plugin.hwndLibraryParent; + MLSkinWindow2(hLibrary, hwnd, SKINNEDWND_TYPE_AUTO, SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS); + + PodcastChannel_InitializeList(hwnd); + PodcastItem_InitializeList(hwnd); + PodcastInfo_InitializeList(hwnd); + + const INT szControls[] = { IDC_FINDNEW, IDC_ADD, IDC_EDIT, IDC_DELETE, IDC_REFRESH, IDC_DOWNLOAD, IDC_VISIT, IDC_STATUS, }; + SubscriptionView_SkinControls(hwnd, szControls, ARRAYSIZE(szControls), SKINNEDWND_TYPE_AUTO, + SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS); + + const INT szControlz[] = { IDC_PLAY, IDC_ENQUEUE, IDC_CUSTOM, }; + SubscriptionView_SkinControls(hwnd, szControlz, ARRAYSIZE(szControlz), SKINNEDWND_TYPE_AUTO, + SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS | (groupBtn ? SWBS_SPLITBUTTON : 0)); + + HWND hControl = GetDlgItem(hwnd, IDC_HDIV); + MLSkinWindow2(hLibrary, hControl, SKINNEDWND_TYPE_DIVIDER, SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT | SWDIV_HORZ /*| SWDIV_NOHILITE*/); + MLSkinnedDivider_SetCallback(hControl, SubscriptionView_OnDividerMoved, IDC_HDIV); + + hControl = GetDlgItem(hwnd, IDC_VDIV); + MLSkinWindow2(hLibrary, hControl, SKINNEDWND_TYPE_DIVIDER, SWS_USESKINCOLORS | SWS_USESKINCURSORS | SWS_USESKINFONT | SWDIV_VERT /*| SWDIV_NOHILITE*/); + MLSkinnedDivider_SetCallback(hControl, SubscriptionView_OnDividerMoved, IDC_VDIV); + + PodcastChannel_Sort(hwnd, 0, channelSortAscending); + PodcastItem_Sort(hwnd, currentItemSort, itemSortAscending); + + OmService *service = (OmService*)lParam; + HWND hBrowser = NULL; + if (NULL != OMBROWSERMNGR && + SUCCEEDED(OMBROWSERMNGR->Initialize(NULL, plugin.hwndWinampParent)) && + SUCCEEDED(OMBROWSERMNGR->CreateView(service, hwnd, NAVIGATE_BLANK, NBCS_NOTOOLBAR | NBCS_NOSTATUSBAR, &hBrowser))) + { + HWND hTarget = GetDlgItem(hwnd, IDC_DESCRIPTION); + if (NULL != hTarget) + { + RECT rect; + if (GetWindowRect(hTarget, &rect)) + { + MapWindowPoints(HWND_DESKTOP, hwnd, (POINT*)&rect, 2); + SetWindowPos(hBrowser, hTarget, rect.left, rect.top, rect.right -rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER); + } + DestroyWindow(hTarget); + } + + SetWindowLongPtr(hBrowser, GWLP_ID, IDC_DESCRIPTION); + ShowWindow(hBrowser, SW_SHOWNA); + } + + Downloads_UpdateButtonText(hwnd, enqueuedef); + SendMessage(hwnd, WM_DISPLAYCHANGE, 0, 0L); + + wchar_t status[256] = {0}; + cloud.GetStatus(status, 256); + SubscriptionView_SetStatus(hwnd, status); + SubscriptionView_RefreshChannels(hwnd, TRUE); + + // evil to do but as the data is known and kept by us then this should be ok + if (channelLastSelection != -1) + { + static LV_ITEM _macro_lvi; + _macro_lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + _macro_lvi.state = LVIS_SELECTED | LVIS_FOCUSED; + PostMessage(GetDlgItem(hwnd, IDC_CHANNELLIST), LVM_SETITEMSTATE, + (WPARAM)channelLastSelection, (LPARAM)(LV_ITEM *)&_macro_lvi); + } + + return 0; +} + +static void SubscriptionView_OnDestroy(HWND hwnd) +{ + PODCAST *podcast = GetPodcast(hwnd); + RemoveProp(hwnd, MAKEINTATOM(VIEWPROP)); + + if (NULL != podcast) + { + htmlDividerPercent = podcast->horzDivider / 10000.0f; + channelDividerPercent = podcast->vertDivider / 10000.0f; + + Plugin_FreeString(podcast->infoUrl); + Plugin_FreeString(podcast->description); + free(podcast); + } + + BOOL fAscending; + + currentItemSort = SubscriptionView_GetListSortColumn(hwnd, IDC_ITEMLIST, &fAscending); + itemSortAscending = (-1 != currentItemSort) ? (FALSE != fAscending) : true; + + INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, &fAscending); + channelSortAscending = (-1 != iSort) ? (FALSE != fAscending) : true; + + INT iSelected = (INT)SNDMSG(GetDlgItem(hwnd, IDC_CHANNELLIST), LVM_GETNEXTITEM, -1, LVNI_SELECTED); + + HWND hControl = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL != hControl) + { + itemTitleWidth = ListView_GetColumnWidth(hControl, COLUMN_TITLE); + itemDateWidth = ListView_GetColumnWidth(hControl, COLUMN_DATEADDED); + //itemMediaWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA); + itemMediaWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA_TIME); + itemSizeWidth = ListView_GetColumnWidth(hControl, COLUMN_MEDIA_SIZE); + + HIMAGELIST imageList = (HIMAGELIST)SendMessage(hControl, LVM_GETIMAGELIST, LVSIL_SMALL, 0L); + if (NULL != imageList) + ImageList_Destroy(imageList); + } + + // ensure view settings are saved... + SaveAll(true); +} + +static void SubscriptionView_OnWindowPosChanged(HWND hwnd, WINDOWPOS *pwp) +{ + if (SWP_NOSIZE == ((SWP_NOSIZE | SWP_FRAMECHANGED) & pwp->flags)) return; + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + HRGN validRegion = (NULL != podcast->updateRegion) ? CreateRectRgn(0, 0, 0, 0) : NULL; + + SubscriptionView_UpdateLayout(hwnd, (0 == (SWP_NOREDRAW & pwp->flags)), + LAYOUTREASON_RESIZE, validRegion, podcast->updateOffset); + + if (NULL != validRegion) + { + CombineRgn(podcast->updateRegion, podcast->updateRegion, validRegion, RGN_DIFF); + DeleteObject(validRegion); + } +} + +static void SubscriptionView_OnContextMenu(HWND hwnd, HWND hTarget, POINTS pts) +{ + INT controlId = (NULL != hTarget) ? (INT)GetWindowLongPtr(hTarget, GWLP_ID) : 0; + + switch(controlId) + { + case IDC_ITEMLIST: + case IDC_CHANNELLIST: + SubscriptionView_ListContextMenu(hwnd, controlId, pts); + break; + } +} + +static void SubscriptionView_OnSetUpdateRegion(HWND hwnd, HRGN updateRegion, POINTS updateOffset) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + podcast->updateRegion = updateRegion; + podcast->updateOffset = updateOffset; +} + +static void PodcastCommand_OnDeleteChannel(HWND hwnd) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + AutoLock lock (channels LOCKNAME("DeleteChannel")); + + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL == iChannel) return; + + WCHAR szText[1024] = {0}, szBuffer[1024] = {0}; + WASABI_API_LNGSTRINGW_BUF(IDS_SURE_YOU_WANT_TO_REMOVE_THIS, szBuffer, ARRAYSIZE(szBuffer)); + StringCchPrintf(szText, ARRAYSIZE(szText), szBuffer, channels[iChannel].title, channels[iChannel].url); + + WASABI_API_LNGSTRINGW_BUF(IDS_CONFIRM, szBuffer, ARRAYSIZE(szBuffer)); + + if (IDYES == MessageBox(hwnd, szText, szBuffer, MB_YESNO | MB_ICONWARNING)) + { + channels.RemoveChannel((int)iChannel); + SubscriptionView_RefreshChannels(hwnd, FALSE); + SaveAll(); + } +} + +static void PodcastCommand_OnVisitSite(HWND hwnd) +{ + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast || NULL == podcast->infoUrl) + return; + + SENDWAIPC(plugin.hwndWinampParent, IPC_OPEN_URL, podcast->infoUrl); +} + +static void PodcastCommand_OnRefreshChannel(HWND hwnd) +{ + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL != iChannel) + { + channels[iChannel].needsRefresh = true; + cloud.Pulse(); + } +} + +static void PodcastCommand_OnEditChannel(HWND hwnd) +{ + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL == iChannel) return; + + ChannelEditor_Show(hwnd, iChannel, CEF_CENTEROWNER); + + HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL != hChannel) + PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hChannel, TRUE); +} + +static void PodcastCommand_OnAddChannel(HWND hwnd) +{ + ChannelEditor_Show(hwnd, 0, CEF_CENTEROWNER | CEF_CREATENEW); + + HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL != hChannel) + PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hChannel, TRUE); +} + +static void PodcastCommand_OnFindNewChannel(HWND hwnd) +{ + HNAVITEM hItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL); + MLNavItem_Select(plugin.hwndLibraryParent, hItem); +} + +static void PodcastCommand_OnRefreshAll(HWND hwnd) +{ + cloud.RefreshAll(); + cloud.Pulse(); +} + +static void PodcastCommand_OnPlaySelection(HWND hwnd, BOOL fEnqueue, BOOL fForce) +{ + AutoLock channelLock (channels LOCKNAME("PlaySelection")); + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL == iChannel) return; + Channel *channel = &channels[iChannel]; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hItems) return; + + INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L); + if (0 == selectedCount) return; + + size_t indexCount = 0; + size_t *indexList = (size_t*)calloc(selectedCount, sizeof(size_t)); + if (NULL == indexList) return; + + INT iSelected = -1; + + while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED))) + { + size_t iItem = iSelected; + if (iItem < channel->items.size()) + { + if (FALSE == podcast->itemAscending) + iItem = channel->items.size() - iItem - 1; + + indexList[indexCount++] = iItem; + } + } + + if (0 != indexCount) + { + SubscriptionView_Play(hwnd, iChannel, indexList, indexCount, fEnqueue, fForce); + } + + free(indexList); +} + +static void PodcastCommand_OnDownloadSelection( HWND hwnd ) +{ + AutoLock channelLock( channels LOCKNAME( "DownloadSelection" ) ); + + PODCAST *podcast = GetPodcast( hwnd ); + if ( NULL == podcast ) return; + + size_t iChannel = PodcastChannel_GetActive( hwnd ); + if ( BAD_CHANNEL == iChannel ) return; + Channel *channel = &channels[ iChannel ]; + + HWND hItems = GetDlgItem( hwnd, IDC_ITEMLIST ); + if ( NULL == hItems ) return; + + INT selectedCount = (INT)SNDMSG( hItems, LVM_GETSELECTEDCOUNT, 0, 0L ); + if ( 0 == selectedCount ) return; + + INT iSelected = -1; + + WCHAR szPath[ MAX_PATH * 2 ] = { 0 }; + + size_t scheduled = 0; + + while ( -1 != ( iSelected = SNDMSG( hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED ) ) ) + { + size_t iItem = iSelected; + if ( iItem < channel->items.size() ) + { + if ( FALSE == podcast->itemAscending ) + iItem = channel->items.size() - iItem - 1; + + RSS::Item *downloadItem = &channel->items[ iItem ]; + if ( ( downloadItem->url && downloadItem->url[ 0 ] ) && SUCCEEDED( downloadItem->GetDownloadFileName( channel->title, szPath, ARRAYSIZE( szPath ), TRUE ) ) ) + { + if ( !wa::files::file_exists( szPath ) ) + { + wchar_t *url = urlencode( downloadItem->url ); + downloader.Download( url, szPath, channel->title, downloadItem->itemName, downloadItem->publishDate ); + downloadItem->downloaded = true; + if ( 0 == scheduled ) + { + SendMessage( hwnd, SVM_SETSTATUS, 0, (LPARAM)IDS_ADD_TO_DOWNLOADS ); + } + free( url ); + scheduled++; + } + } + } + } + + if ( 0 == scheduled ) + SendMessage( hwnd, SVM_SETSTATUS, 0, (LPARAM)IDS_NO_MEDIA_TO_DOWNLOAD ); +} + +static void PodcastCommand_OnExploreItemFolder(HWND hwnd) +{ + AutoLock channelLock (channels LOCKNAME("Explore")); + + PODCAST *podcast = GetPodcast(hwnd); + if (NULL == podcast) return; + + size_t iChannel = PodcastChannel_GetActive(hwnd); + if (BAD_CHANNEL == iChannel) return; + Channel *channel = &channels[iChannel]; + + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hItems) return; + + INT selectedCount = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L); + if (0 == selectedCount) return; + + INT iSelected = -1; + + WCHAR szPath[MAX_PATH * 2] = {0}; + WASABI_API_EXPLORERFINDFILE->Reset(); + + while(-1 != (iSelected = SNDMSG(hItems, LVM_GETNEXTITEM, (WPARAM)iSelected, LVNI_SELECTED))) + { + size_t iItem = iSelected; + if (iItem < channel->items.size()) + { + if (FALSE == podcast->itemAscending) + iItem = channel->items.size() - iItem - 1; + + RSS::Item *downloadItem = &channel->items[iItem]; + if ((downloadItem->url && downloadItem->url[0]) && + SUCCEEDED(downloadItem->GetDownloadFileName(channel->title, szPath, ARRAYSIZE(szPath), TRUE)) && + PathFileExists(szPath)) + { + WASABI_API_EXPLORERFINDFILE->AddFile(szPath); + } + } + } + WASABI_API_EXPLORERFINDFILE->ShowFiles(); +} + +static void PodcastCommand_OnSelectAll(HWND hwnd) +{ + HWND hItems = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL == hItems) return; + + INT iCount = (INT)SNDMSG(hItems, LVM_GETITEMCOUNT, 0, 0L); + if (0 != iCount) + { + INT iSelected = (INT)SNDMSG(hItems, LVM_GETSELECTEDCOUNT, 0, 0L); + if (iSelected != iCount) + { + LVITEM lvi; + lvi.state = LVIS_SELECTED; + lvi.stateMask = LVIS_SELECTED; + SNDMSG(hItems, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lvi); + } + } +} + +static void PodcastCommand_PlayEnqueue(HWND hwndDlg, HWND from, UINT idFrom) +{ + HMENU listMenu = GetSubMenu(g_context_menus3, 1); + int count = GetMenuItemCount(listMenu); + if (count > 2) + { + for (int i = 2; i < count; i++) + { + DeleteMenu(listMenu, 2, MF_BYPOSITION); + } + } + + Downloads_ButtonPopupMenu(hwndDlg, idFrom, listMenu, BPM_WM_COMMAND); +} + +static void SubscriptionView_OnCommand(HWND hwnd, INT controlId, INT eventId, HWND hControl) +{ + switch (controlId) + { + case IDC_CUSTOM: + case IDC_PLAY: + case IDC_ENQUEUE: + case IDC_PLAYACTION: + case IDC_ENQUEUEACTION: + + if (eventId == MLBN_DROPDOWN) + { + PodcastCommand_PlayEnqueue(hwnd, hControl, controlId); + } + else + { + int action; + if (controlId == IDC_PLAY || controlId == IDC_PLAYACTION) + action = (eventId == 1) ? enqueuedef == 1 : 0; + else if (controlId == IDC_ENQUEUE || controlId == IDC_ENQUEUEACTION) + action = (eventId == 1) ? (enqueuedef != 1) : 1; + else + // so custom can work with the menu item part + break; + + PodcastCommand_OnPlaySelection(hwnd, action, (controlId == IDC_PLAY || controlId == IDC_ENQUEUE)); + } + break; + + case IDC_VISIT: PodcastCommand_OnVisitSite(hwnd); break; + case IDC_DOWNLOAD: PodcastCommand_OnDownloadSelection(hwnd); break; + case IDC_EXPLORERITEMFOLDER: PodcastCommand_OnExploreItemFolder(hwnd); break; + case IDC_SELECTALL: PodcastCommand_OnSelectAll(hwnd); break; + case IDC_REFRESH: PodcastCommand_OnRefreshChannel(hwnd); break; + case IDC_EDIT: PodcastCommand_OnEditChannel(hwnd); break; + case IDC_ADD: PodcastCommand_OnAddChannel(hwnd); break; + case IDC_FINDNEW: PodcastCommand_OnFindNewChannel(hwnd); break; + case IDC_DELETE: PodcastCommand_OnDeleteChannel(hwnd); break; + case IDC_REFRESHALL: PodcastCommand_OnRefreshAll(hwnd); break; + } +} + +static LRESULT SubscriptionView_OnNotify(HWND hwnd, INT controlId, NMHDR *pnmh) +{ + LRESULT result = 0; + switch (controlId) + { + case IDC_CHANNELLIST: result = PodcastChannel_OnNotify(hwnd, pnmh); break; + case IDC_ITEMLIST: result = PodcastItem_OnNotify(hwnd, pnmh); break; + } + + return result; +} + +static void SubscriptionView_OnChar(HWND hwnd, UINT vKey, UINT flags) +{ + switch(vKey) + { + case VK_DELETE: + if (GetFocus() == GetDlgItem(hwnd, IDC_CHANNELLIST)) + SENDCMD(hwnd, IDC_DELETE, 0, 0L); + break; + } +} + +static BOOL SubscriptionView_OnRefreshChannels(HWND hwnd, BOOL fSort) +{ + AutoLock lock (channels LOCKNAME("All_ChannelsUpdated")); + + HWND hChannel = GetDlgItem(hwnd, IDC_CHANNELLIST); + if (NULL == hChannel) return FALSE; + + size_t channelSize = channels.size(); + size_t iSelected = (size_t)SNDMSG(hChannel, LVM_GETNEXTITEM, -1, LVNI_SELECTED); + + if (FALSE != fSort) + { + INT iSort = SubscriptionView_GetListSortColumn(hwnd, IDC_CHANNELLIST, NULL); + SubscriptionView_SortChannels(iSort); + } + + SNDMSG(hChannel, LVM_SETITEMCOUNT, channelSize, 0L); + PodcastChannel_SyncHeaderSize(hwnd, TRUE); + + BOOL selectChannel = FALSE; + if(BAD_CHANNEL != iSelected && iSelected >= channelSize) + { + iSelected = (channelSize - 1); + selectChannel = TRUE; + } + + if (BAD_CHANNEL == iSelected && channelSize > 0) + { + iSelected = 0; + selectChannel = TRUE; + } + + if (FALSE != selectChannel) + { + LVITEM lvi; + lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + lvi.state = lvi.stateMask; + + if (FALSE == SNDMSG(hChannel, LVM_SETITEMSTATE, (WPARAM)iSelected, (LPARAM)&lvi)) + iSelected = -1; + else + { + SNDMSG(hChannel, LVM_SETSELECTIONMARK, (WPARAM)0, (LPARAM)iSelected); + SNDMSG(hChannel, LVM_ENSUREVISIBLE, (WPARAM)iSelected, (LPARAM)FALSE); + } + } + + if (FALSE == selectChannel || 0 == channelSize) + { + PodcastChannel_SelectionChanged(hwnd, FALSE); + } + + return TRUE; +} + +static void SubscriptionView_OnParentNotify(HWND hwnd, UINT uMsg, HWND childId, LPARAM lParam) +{ + switch(uMsg) + { + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case /*WM_XBUTTONDOWN*/0x020B: + { + POINT pt; + GetCursorPos(&pt); + + RECT rect; + HWND hItem = GetDlgItem(hwnd, IDC_ITEMLIST); + if (NULL != hItem && hItem != GetFocus() && FALSE != GetClientRect(hItem, &rect)) + { + MapWindowPoints(hItem, HWND_DESKTOP, (POINT*)&rect, 2); + if (PtInRect(&rect, pt)) + { + SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hItem, MAKELPARAM(TRUE, 0)); + } + } + } + break; + } +} + +static LRESULT SubscriptionView_OnSetStatus(HWND hwnd, LPCWSTR pszStatus) +{ + HWND hStatus = GetDlgItem(hwnd, IDC_STATUS); + if (NULL == hStatus) return FALSE; + + WCHAR szBuffer[512] = {0}; + if (IS_INTRESOURCE(pszStatus)) + { + pszStatus = WASABI_API_LNGSTRINGW_BUF((INT)(INT_PTR)pszStatus, szBuffer, ARRAYSIZE(szBuffer)); + } + + return SetWindowText(hStatus, pszStatus); +} + +static INT_PTR CALLBACK SubscriptionView_DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: return SubscriptionView_OnInitDialog(hwnd, (HWND)wParam, lParam); + case WM_DESTROY: SubscriptionView_OnDestroy(hwnd); return TRUE; + case WM_WINDOWPOSCHANGED: SubscriptionView_OnWindowPosChanged(hwnd, (WINDOWPOS*)lParam); return TRUE; + case WM_DISPLAYCHANGE: SubscriptionView_UpdateSkin(hwnd); return TRUE; + case WM_CONTEXTMENU: SubscriptionView_OnContextMenu(hwnd, (HWND)wParam, MAKEPOINTS(lParam)); return TRUE; + case WM_NOTIFYFORMAT: MSGRESULT(hwnd, NFR_UNICODE); + case WM_COMMAND: SubscriptionView_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE; + case WM_NOTIFY: MSGRESULT(hwnd, SubscriptionView_OnNotify(hwnd, (INT)wParam, (NMHDR*)lParam)); + case WM_CHAR: SubscriptionView_OnChar(hwnd, (UINT)wParam, (UINT)lParam); return TRUE; + case WM_PARENTNOTIFY: SubscriptionView_OnParentNotify(hwnd, LOWORD(wParam), (HWND)HIWORD(wParam), lParam); return TRUE; + + case WM_USER + 0x200: MSGRESULT(hwnd, TRUE); + case WM_USER + 0x201: SubscriptionView_OnSetUpdateRegion(hwnd, (HRGN)lParam, MAKEPOINTS(wParam)); return TRUE; + + case SVM_REFRESHCHANNELS: MSGRESULT(hwnd, SubscriptionView_OnRefreshChannels(hwnd, (BOOL)wParam)); + case SVM_SETSTATUS: MSGRESULT(hwnd, SubscriptionView_OnSetStatus(hwnd, (LPCWSTR)lParam)); + + case WM_INITMENUPOPUP: + if (wParam && (HMENU)wParam == s.build_hMenu && s.mode==1) + { + if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_LIBRARY_SENDTOMENU) == 0xffffffff) + s.mode = 2; + } + return 0; + + case WM_APP + 104: + { + Downloads_UpdateButtonText(hwnd, enqueuedef); + SendMessage(hwnd, WM_DISPLAYCHANGE, 0, 0); + return 0; + } + + case WM_PAINT: + if (IsWindowVisible(GetDlgItem(hwnd, IDC_DESCRIPTION))) + { + int tab[] = { IDC_DESCRIPTION|DCW_SUNKENBORDER}; + dialogSkinner.Draw(hwnd, tab, sizeof(tab) / sizeof(tab[0])); + } + return 0; + } + return 0; +} + +HWND CALLBACK SubscriptionView_Create(HWND hParent, OmService *service) +{ + return WASABI_API_CREATEDIALOGPARAMW(IDD_PODCAST, hParent, SubscriptionView_DlgProc, (LPARAM)service); +} + +HWND CALLBACK SubscriptionView_FindWindow() +{ + HWND hView = (HWND)SENDMLIPC(plugin.hwndLibraryParent, ML_IPC_GETCURRENTVIEW, 0); + if (NULL == hView) return NULL; + + WCHAR szBuffer[128] = {0}; + if (0 == GetClassName(hView, szBuffer, ARRAYSIZE(szBuffer)) || + CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, 0, szBuffer, -1, L"#32770", -1) || + 0 == GetWindowText(hView, szBuffer, ARRAYSIZE(szBuffer)) || + CSTR_EQUAL != CompareStringW(CSTR_INVARIANT, 0, szBuffer, -1, SUBSCRIPTIONVIEW_NAME, -1)) + { + hView = NULL; + } + + return hView; +} \ No newline at end of file -- cgit