// window_chatterchannels.cpp
#include "../common.h"
// window parameters
#define WINDOW_CLASSNAME PROGRAM_NAME L" ChatterChannels WndClass"
#define WINDOW_DEFAULT_WIDTH 800
#define WINDOW_DEFAULT_HEIGHT 600
#define WINDOW_MIN_WIDTH 480
#define WINDOW_MIN_HEIGHT 240
// local definitions
#define WINDOW_TEXT_DOUBLECLICKTOTOGGLE 1
#define WINDOW_LIST_CHANNELS 2
#define WINDOW_LIST_CHANNELMEMBERS 3
#define WINDOW_TEXT_STATUSBAR 4
#define WINDOW_TIMER_REFRESH 1
// list view column definition
typedef struct listviewcolumn_s
{
int width;
int alignment;
bool sort_descending;
wchar_t *text;
HWND hToolTipWnd;
} listviewcolumn_t;
// list view icon definition
typedef struct listviewicon_s
{
HICON icon;
int load_index;
} listviewicon_t;
// global variables used in this module only
static bool is_classregistered = false;
static int listviewicons[sizeof (handlestatus) / sizeof (handlestatus_t)]; // as big as the handlestatus global array
static listviewcolumn_t listviewcolumns[] =
{
{ 50, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelNumber")*/, NULL }, // text address needs to be set at runtime, because it's mallocated
{ 170, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelTheme")*/, NULL },
{ 55, LVCFMT_RIGHT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelMembers")*/, NULL },
};
static wchar_t temp_string[256];
static bool update_selection = false;
static bool update_sortorder = false;
static int channellist_checksum = 0;
static listviewicon_t channelicons[2] = { { NULL, 0 }, { NULL, 0 } };
//static HWND hThisWnd = NULL;
#define hThisWnd hChatterChannelsWnd // shared variable
// prototypes of local functions
static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column);
static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
static void ListView_ScrollTilItem (HWND hListViewWnd, void *item);
// prototypes of local functions
static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
void Window_ChatterChannels (void)
{
// helper function to fire up the child window
WNDCLASSEX wc;
player_t *network_player;
// is the window we want to display already displayed ?
if (IsWindow (hThisWnd))
SetForegroundWindow (hThisWnd); // if so, just bring it to front
// else the way is clear
else
{
// find the network player and make him ask an update from the server
if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request
// is the window class NOT registered yet ?
if (!is_classregistered)
{
// if so, register the window class once and for all
memset (&wc, 0, sizeof (wc));
wc.cbSize = sizeof (wc);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc_ThisWindow;
wc.hInstance = hAppInstance;
wc.hIcon = LoadIcon (hAppInstance, (wchar_t *) ICON_MAIN);
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = GetSysColorBrush (COLOR_3DFACE);
wc.lpszClassName = WINDOW_CLASSNAME;
RegisterClassEx (&wc);
// also load the icons we need
channelicons[0].icon = (HICON) LoadImage (NULL, L"data/icons/led-red.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
channelicons[1].icon = (HICON) LoadImage (NULL, L"data/icons/led-green.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
is_classregistered = true; // remember this window class is registered
}
// create the child window
hThisWnd = CreateWindowEx (WS_EX_CLIENTEDGE, WINDOW_CLASSNAME, LOCALIZE (L"ChatterChannels_TitleBuildingList"), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT,
hMainWnd, NULL, hAppInstance, NULL);
}
return; // return as soon as the thread is fired up
}
void Window_ChatterChannels_Validated (void)
{
// callback function called by the main game thread when the window is validated
// remember this callback is no longer to be called
is_window_chatterchannels_validated = false;
return; // finished
}
static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
// message handler for the child window
unsigned short wParam_hiword;
unsigned short wParam_loword;
HWND hListChannelsWnd;
HWND hListMembersWnd;
LVCOLUMN lvc;
LVITEM lvi;
HDITEM hdi;
NMLISTVIEW *lv;
HIMAGELIST channels_imagelist;
HIMAGELIST members_imagelist;
MINMAXINFO *minmax;
RECT client_rect;
chatterchannel_t *chatterchannel;
chatterchannelmember_t *chatterchannelmember;
player_t *network_player;
player_t *local_player;
int column_index;
int insert_index;
int channel_index;
int member_index;
int new_checksum;
// filter out the commonly used message values
wParam_hiword = HIWORD (wParam);
wParam_loword = LOWORD (wParam);
// have we just fired up this window ?
if (message == WM_CREATE)
{
// center the window
CenterWindow (hWnd, hMainWnd);
// populate the window
CreateWindowEx (0, L"static", L"",
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_DOUBLECLICKTOTOGGLE, hAppInstance, NULL);
CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_CHANNELS, hAppInstance, NULL);
CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_LIST | LVS_SINGLESEL | LVS_ALIGNLEFT,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_CHANNELMEMBERS, hAppInstance, NULL);
CreateWindowEx (WS_EX_RIGHT, L"static", L"",
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL);
// prepare the list view : do it before anything that could trigger a fill
// get a quick access to the list controls
hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS);
// add full row select and header columns rearranging
ListView_SetExtendedListViewStyle (hListChannelsWnd, ListView_GetExtendedListViewStyle (hListChannelsWnd)
| LVS_EX_GRIDLINES | LVS_EX_LABELTIP
| LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
ListView_SetExtendedListViewStyle (hListMembersWnd, ListView_GetExtendedListViewStyle (hListMembersWnd) | LVS_EX_LABELTIP);
// subclass the list view procedure to handle header tooltips and save the old procedure as one of the window's property
SetProp (hListChannelsWnd, L"BaseWndProc", (HANDLE) SetWindowLongPtr (hListChannelsWnd, GWL_WNDPROC, (long) ListView_WndProc));
// tell Windows which members of the LVCOLUMN structure we're filling
memset (&lvc, 0, sizeof (lvc));
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
{
lvc.iSubItem = column_index;
if (column_index == 0) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelNumber");
else if (column_index == 1) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelTheme");
else if (column_index == 2) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelMembers");
lvc.cx = listviewcolumns[column_index].width;
lvc.fmt = listviewcolumns[column_index].alignment;
ListView_InsertColumn (hListChannelsWnd, column_index, &lvc); // add each column to list view
}
// create the listviews image lists
channels_imagelist = ImageList_Create (16, 16, ILC_COLOR32, 2, 1); // create an imagelist holding 2 32-bit images
for (insert_index = 0; insert_index < 2; insert_index++)
channelicons[insert_index].load_index = ImageList_AddIcon (channels_imagelist, channelicons[insert_index].icon); // add our icons in the image list
ListView_SetImageList (hListChannelsWnd, channels_imagelist, LVSIL_SMALL); // associate it with the channels listview
members_imagelist = ImageList_Create (16, 16, ILC_COLOR32, sizeof (handlestatus) / sizeof (handlestatus_t), 1); // create an imagelist holding N 32-bit images
for (insert_index = 1; insert_index < sizeof (handlestatus) / sizeof (handlestatus_t); insert_index++)
listviewicons[insert_index] = ImageList_AddIcon (members_imagelist, handlestatus[insert_index].icon); // add our icons in the image list
ListView_SetImageList (hListMembersWnd, members_imagelist, LVSIL_SMALL); // associate it with the members listviews too
// refresh the window every second
SetTimer (hWnd, WINDOW_TIMER_REFRESH, 1000, NULL);
update_selection = false; // we aren't aware yet of any selected channel
update_sortorder = false; // we don't need to sort anything yet
// associate the default GUI font with the help text and set its text
SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), LOCALIZE (L"ChatterChannels_DoubleClickToToggle"));
// associate the default GUI font with the status bar and set its text
SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), LOCALIZE (L"ChatterChannels_StatusBar"));
// convert the status bar message to a hyperlink
ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR));
// now show the window
ShowWindow (hWnd, SW_SHOW);
}
// else did we click the close button on the title bar ?
else if (message == WM_CLOSE)
DestroyWindow (hWnd); // close the window
// else is the window being destroyed ?
else if (message == WM_DESTROY)
{
KillTimer (hWnd, WINDOW_TIMER_REFRESH); // destroy the timer we used to refresh the window
hThisWnd = NULL; // window is closed
SetForegroundWindow (hMainWnd); // restore focus on the main window
is_window_chatterchannels_validated = true; // remember we closed this window
}
// else are we resizing the window ?
else if (message == WM_SIZE)
{
// get the new window size
GetClientRect (hWnd, &client_rect);
// position the window elements
SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELS), NULL, 16, 64, 296, client_rect.bottom - 80, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS), NULL, 316, 64, client_rect.right - 332, client_rect.bottom - 80, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), NULL, 16, 16, client_rect.right - 16, 48, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
}
// else are we asking how big/small we can resize ?
else if (message == WM_GETMINMAXINFO)
{
minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
}
// else is it a timer event AND is it our refresh timer ?
else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH))
{
//////////////////////////////////////////////////
// do we need to update the chatter channel list ?
if (chatterchannels_updated)
{
// get a quick access to the list control
hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
// compute the new checksum and see if it differs from the one we know
new_checksum = 0;
for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
new_checksum += chatterchannels[channel_index].is_open + wcslen (chatterchannels[channel_index].theme) + chatterchannels[channel_index].member_count;
// does it differ ?
if (new_checksum != channellist_checksum)
{
// populate the list control and compute its new checksum on the fly
ListView_DeleteAllItems (hListChannelsWnd); // start by emptying it first
// tell Windows which members of the LVCOLUMN structure we're filling
memset (&lvi, 0, sizeof (lvi));
lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
{
// first, attach the right structure to this item
lvi.lParam = (LPARAM) &chatterchannels[channel_index];
// set item's image and name
lvi.iItem = channel_index;
if (chatterchannels[channel_index].is_open)
lvi.iImage = channelicons[1].load_index; // this channel is open
else
lvi.iImage = channelicons[0].load_index; // this channel is silenced
insert_index = ListView_InsertItem (hListChannelsWnd, &lvi); // add each item to list view
// set item's substrings
// channel number, theme and member count
swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].id);
ListView_SetItemText (hListChannelsWnd, insert_index, 0, temp_string); // channel number
ListView_SetItemText (hListChannelsWnd, insert_index, 1, chatterchannels[channel_index].theme); // theme
swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].member_count);
ListView_SetItemText (hListChannelsWnd, insert_index, 2, temp_string); // member count
// is it the selected chatter channel ? if so, mark it as selected
if (&chatterchannels[channel_index] == selected_chatterchannel)
ListView_SetItemState (hListChannelsWnd, channel_index, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
}
channellist_checksum = new_checksum; // remember the new checksum
}
// now that the display is finished, IF the reply is arrived, set the totals in the window title
if (chatterchannel_count >= 0)
SetWindowText (hWnd, LOCALIZE (L"ChatterChannels_Title"));
update_selection = true; // update the selected chatter channel members display
update_sortorder = true; // update the sort order
chatterchannels_updated = false; // and remember we updated the list (don't do it twice)
}
/////////////////////////////////////////////////////////////
// do we need to update the chatter channels member display ?
if (update_selection)
{
// get a quick access to the list controls
hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS);
// if so, populate the channel members list view
ListView_DeleteAllItems (hListMembersWnd); // start by emptying it first
if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL))
{
// select the selected chatter channel in the channel list
ListView_SetItemState (hListChannelsWnd, selected_chatterchannel, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
memset (&lvi, 0, sizeof (lvi));
lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
for (member_index = 0; member_index < selected_chatterchannel->member_count; member_index++)
{
// first, attach the right structure to this item
lvi.lParam = (LPARAM) &selected_chatterchannel->members[member_index];
// set item's image and name
lvi.iItem = member_index;
if (selected_chatterchannel->members[member_index].is_silenced)
lvi.iImage = listviewicons[HANDLESTATUS_NOTOPENFORAMATCH]; // this player is silenced
else
lvi.iImage = listviewicons[HANDLESTATUS_AVAILABLE]; // this player is available
insert_index = ListView_InsertItem (hListMembersWnd, &lvi); // add each item to list view
ListView_SetItemText (hListMembersWnd, insert_index, 0, selected_chatterchannel->members[member_index].nickname); // member name
}
}
update_selection = false; // don't do it twice
}
//////////////////////////////////////////////////////
// do we need to sort the chatter channels list view ?
if (update_sortorder)
{
// get a quick access to the list control
hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
// cycle through all columns and see which one is the sort criteria
memset (&hdi, 0, sizeof (hdi));
hdi.mask = HDI_FORMAT;
for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
{
Header_GetItem (ListView_GetHeader (hListChannelsWnd), column_index, &hdi); // get the column's sort arrow state
if ((hdi.fmt & HDF_SORTDOWN) || (hdi.fmt & HDF_SORTUP))
break; // break as soon as we find it
}
// have we NOT found it ?
if (column_index == sizeof (listviewcolumns) / sizeof (listviewcolumn_t))
column_index = 0; // if so, sort the list view according to the first column
// now sort the list view
ListView_SortItems (hListChannelsWnd, CompareProc_ListChannels, column_index);
// is a channel selected ? if so, scroll to the selected item
if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL))
ListView_ScrollTilItem (hListChannelsWnd, selected_chatterchannel);
update_sortorder = false; // don't do this twice
}
}
/////////////////////////////////////////////////////////////////
// else is it a list view message from the chatter channel list ?
else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELS))
{
lv = (NMLISTVIEW *) lParam; // quick access to list view
/////////////////////////////////////////////////
// is it a click on one of the headers' columns ?
if (lv->hdr.code == LVN_COLUMNCLICK)
{
// cycle through all columns and reset their sort order
memset (&hdi, 0, sizeof (hdi));
hdi.mask = HDI_FORMAT;
for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
{
Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
if (column_index == lv->iSubItem)
{
listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
}
else
listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
}
update_sortorder = true; // update sort order
SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now
}
/////////////////////////////////////////////////////////////
// else is it a single click on one of the chatter channels ?
else if ((lv->hdr.code == NM_CLICK) && (((NMITEMACTIVATE *) lParam)->iItem != -1))
{
// get which item it is in the listview data
memset (&lvi, 0, sizeof (lvi));
lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
lvi.mask = LVIF_PARAM;
ListView_GetItem (lv->hdr.hwndFrom, &lvi);
chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
// is it valid ?
if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1]))
{
selected_chatterchannel = chatterchannel; // save selected chatter channel
update_selection = true; // update selection
the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed)
SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now
}
}
/////////////////////////////////////////////////////////////
// else is it a double-click on one of the chatter channels ?
else if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1))
{
// get which item it is in the listview data
memset (&lvi, 0, sizeof (lvi));
lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
lvi.mask = LVIF_PARAM;
ListView_GetItem (lv->hdr.hwndFrom, &lvi);
chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
// find the network player and make him ask to toggle channel on/off to the server
if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
{
Player_SendBuffer_Add (network_player, 1000, L"%schannel %d\n", (chatterchannel->is_open ? L"-" : L"+"), chatterchannel->id);
Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request
}
chatterchannels_updated = true; // and update display
SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
}
}
/////////////////////////////////////////////////////////////////////////
// else is it a list view message from the chatter channel members list ?
else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELMEMBERS))
{
lv = (NMLISTVIEW *) lParam; // quick access to list view
///////////////////////////////////////////////////////
// is it a double-click on one of the channel members ?
if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1) && (chatterchannel_count > 0))
{
// get which item it is in the listview data
memset (&lvi, 0, sizeof (lvi));
lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
lvi.mask = LVIF_PARAM;
ListView_GetItem (lv->hdr.hwndFrom, &lvi);
chatterchannelmember = (chatterchannelmember_t *) lvi.lParam; // get the chatter channel it is
local_player = Player_FindByType (PLAYER_HUMAN); // find the local player
// is it open AND is it NOT ourselves ? if so, find or create an interlocutor structure for this cc member
if (!chatterchannelmember->is_silenced && (local_player != NULL) && (wcscmp (chatterchannelmember->nickname, local_player->name) != 0))
Interlocutor_FindOrCreate (chatterchannelmember->nickname);
else
PlayerCard_FindOrCreate (chatterchannelmember->nickname); // else just display his player card
}
}
// else did we take action on one of the controls ?
else if (message == WM_COMMAND)
{
// was it the status bar hyperlink ?
if (wParam_loword == WINDOW_TEXT_STATUSBAR)
ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
}
// call the default window message processing function to keep things going
return (DefWindowProc (hWnd, message, wParam, lParam));
}
static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column)
{
// callback function that tells whether the lParam1 listview element comes before lParam2 in the
// sort order of the specified column
// channel ID
if (column == 0)
{
if (listviewcolumns[column].sort_descending)
return (((chatterchannel_t *) lParam1)->id <= ((chatterchannel_t *) lParam2)->id);
else
return (((chatterchannel_t *) lParam1)->id >= ((chatterchannel_t *) lParam2)->id);
}
// channel topic
else if (column == 1)
{
if (listviewcolumns[column].sort_descending)
return (_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme));
else
return (-_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme));
}
// member count
else if (column == 2)
{
if (listviewcolumns[column].sort_descending)
return (((chatterchannel_t *) lParam1)->member_count <= ((chatterchannel_t *) lParam2)->member_count);
else
return (((chatterchannel_t *) lParam1)->member_count >= ((chatterchannel_t *) lParam2)->member_count);
}
return (0); // should neever reach here
}
static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
// callback that subclasses the original ListView window procedure, so that we can hook
// some of its messages
static bool tooltips_initialized = false;
static bool update_tooltips = false;
WNDPROC BaseWndProc;
TOOLINFO toolinfo;
HWND hHeaderWnd;
LVITEM lvi;
chatterchannel_t *chatterchannel;
int column_index;
// get a pointer to the base window procedure (it was stored as a window property)
BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
if (BaseWndProc == NULL)
return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
// is the mouse moving around ?
if (message == WM_MOUSEMOVE)
{
// do the tooltips need to be created ?
if (!tooltips_initialized)
{
hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
// add a tooltip for each column
for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
{
// create the tooltip and set its window topmost
listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hHeaderWnd, NULL, hAppInstance, NULL);
// associate the tooltip with the tool
memset (&toolinfo, 0, sizeof (toolinfo));
toolinfo.cbSize = sizeof (toolinfo);
toolinfo.uFlags = TTF_SUBCLASS;
toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
toolinfo.uId = column_index; // tooltip ID will be column ID
toolinfo.hinst = hAppInstance;
toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
}
tooltips_initialized = true; // do this only once
}
// else do the tooltips need to be updated ?
else if (update_tooltips)
{
hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
// cycle through all columns
for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
{
// update the tooltip rectangle
memset (&toolinfo, 0, sizeof (toolinfo));
toolinfo.cbSize = sizeof (toolinfo);
toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
toolinfo.uId = column_index; // tooltip ID is column ID
Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
}
update_tooltips = false; // do this only once
}
}
// else is it a directional key release ?
else if ((message == WM_KEYUP) && ((wParam == VK_UP) || (wParam == VK_DOWN)
|| (wParam == VK_PRIOR) || (wParam == VK_NEXT) || (wParam == VK_END) || (wParam == VK_HOME)))
{
// get which item is selected in the listview data
memset (&lvi, 0, sizeof (lvi));
lvi.iItem = ListView_GetNextItem (hWnd, -1, LVNI_SELECTED);
lvi.mask = LVIF_PARAM;
ListView_GetItem (hWnd, &lvi);
chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
// is it valid ?
if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1]))
{
selected_chatterchannel = chatterchannel; // save selected chatter channel
update_selection = true; // update selection
the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed)
SendMessage (GetParent (hWnd), WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
}
}
// else has the user finished dragging/resizing a column header ?
else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
update_tooltips = true; // if so, remember to update tooltips on the next mouse move
// in any case, forward all messages to the original ListView hook procedure
return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
}
static void ListView_ScrollTilItem (HWND hListViewWnd, void *item)
{
// helper function to scroll the given listview til the given item appears
LVFINDINFO lvfi;
POINT point;
RECT rect;
int item_index;
int top_index;
int count_per_page;
int total_count;
int scroll_pixels;
// scroll to the selected chatter channel index
memset (&lvfi, 0, sizeof (lvfi));
lvfi.flags = LVFI_PARAM;
lvfi.lParam = (LPARAM) item;
item_index = ListView_FindItem (hListViewWnd, -1, &lvfi); // get the position of the selected chatter channel in the list
if (item_index > 0)
{
// figure out how many items in the listview, how many fit in one page and which index is at the top of the page
total_count = ListView_GetItemCount (hListViewWnd);
count_per_page = ListView_GetCountPerPage (hListViewWnd);
top_index = ListView_GetTopIndex (hListViewWnd);
// is index already visible ?
if ((item_index >= top_index) && (item_index <= top_index + count_per_page))
return; // if so, no need to scroll
ListView_GetItemRect (hListViewWnd, 0, &rect, LVIR_BOUNDS); // get the size of a standard item
ListView_GetOrigin (hListViewWnd, &point); // get the list view origin
scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // compute how many pixels we should scroll
// do we need to scroll upwards or downwards ?
if (scroll_pixels < 0)
item_index -= 1 * count_per_page / 3; // scroll up one third of the window
else
item_index -= 2 * count_per_page / 3; // scroll up two thirds of the window
if (item_index < 0)
item_index = 0; // don't go beyond the beginning
if (item_index > total_count - count_per_page)
item_index = total_count - count_per_page; // don't go beyond the end
scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // recompute how many pixels we should scroll
ListView_Scroll (hListViewWnd, 0, scroll_pixels); // and scroll to the desired position
}
return; // finished
}