// window_chat.cpp
#include "../common.h"
// WARNING: OLE-enabled RichEdit control needs a C++ compiler!
#include <richole.h>
// window parameters
#define WINDOW_CLASSNAME PROGRAM_NAME L" Chat WndClass"
#define WINDOW_DEFAULT_WIDTH 520
#define WINDOW_DEFAULT_HEIGHT 400
#define WINDOW_MIN_WIDTH 480
#define WINDOW_MIN_HEIGHT 330
// local definitions
#define WINDOW_TEXT_QUESTION 1
#define WINDOW_EDITBOX_DISCUSSION 2
#define WINDOW_EDITBOX_MESSAGETOSEND 3
#define WINDOW_BITMAP_OPPONENTSTATUS 4
#define WINDOW_BUTTON_INVITE 5
#define WINDOW_BUTTON_PLAYERINFO 6
#define WINDOW_BUTTON_SEND 7
#define WINDOW_TEXT_STATUSBAR 8
#define WINDOW_TIMER_REFRESH 1
// RichEdit OLE callback interface structure definition
struct RichEditOLECallback : IRichEditOleCallback
{
// this structure derives from the IRichEditOleCallback structure, which itself derives
// from the IUnknown structure. So we define IN THIS VERY ORDER the members of the
// IUnknown structure first, THEN the members of the IRichEditOleCallback structure,
// THEN our own members.
// constructor/destructor
RichEditOLECallback () { reference_count = 0; }
~RichEditOLECallback () { }
// members of the IUnknown structure
ULONG CALLBACK AddRef (void) { reference_count++; return (reference_count); }
ULONG CALLBACK Release (void) { reference_count--; return (reference_count); }
HRESULT CALLBACK QueryInterface (REFIID iid, void **ppvObject)
{
if ((iid != IID_IUnknown) && (iid != IID_IRichEditOleCallback))
return (E_NOINTERFACE);
*ppvObject = this;
reference_count++;
return (S_OK);
}
// members of the IRichEditOleCallback structure
HRESULT CALLBACK ContextSensitiveHelp (BOOL fEnterMode) { return (E_NOTIMPL); }
HRESULT CALLBACK DeleteObject (IOleObject *lpoleobj) { return (E_NOTIMPL); }
HRESULT CALLBACK GetClipboardData (CHARRANGE *lpchrg, DWORD reco, IDataObject **lplpdataobj) { return (E_NOTIMPL); }
HRESULT CALLBACK GetContextMenu (WORD seltype, IOleObject *lpoleobj, CHARRANGE *lpchrg, HMENU *lphmenu) { return (E_NOTIMPL); }
HRESULT CALLBACK GetDragDropEffect (BOOL fDrag, DWORD grfKeyState, DWORD *pdwEffect) { return (E_NOTIMPL); }
HRESULT CALLBACK GetInPlaceContext (IOleInPlaceFrame **lplpFrame, IOleInPlaceUIWindow **lplpDoc, tagOIFI *lpFrameInfo) { return (E_NOTIMPL); }
HRESULT CALLBACK GetNewStorage (IStorage **storage)
{
// initialize a storage object in memory
ILockBytes *lock_bytes;
HRESULT ret;
if ((ret = CreateILockBytesOnHGlobal (NULL, TRUE, &lock_bytes)) != S_OK)
return (ret);
if ((ret = StgCreateDocfileOnILockBytes (lock_bytes, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, storage)) != S_OK)
lock_bytes->Release ();
return (ret);
}
HRESULT CALLBACK QueryAcceptData (IDataObject *lpdataobj, CLIPFORMAT *lpcfFormat, DWORD reco, BOOL fReally, HANDLE hMetaPict) { return (E_NOTIMPL); }
HRESULT CALLBACK QueryInsertObject (LPCLSID lpclsid, IStorage *storage, LONG cp) { return (S_OK); }
HRESULT CALLBACK ShowContainerUI (BOOL fShow) { return (E_NOTIMPL); }
// our own data
ULONG reference_count;
};
// global variables used in this module only
static bool is_classregistered = false;
static PROC WindowProc_MessageToSend_DefaultProc;
static RichEditOLECallback *richedit_oleinterface = NULL;
// prototypes of local functions
static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK WindowProc_MessageToSend (HWND hDlg, unsigned int message, WPARAM wParam, LPARAM lParam);
void Window_Chat (int interlocutor_index)
{
// helper function to fire up the child window
WNDCLASSEX wc;
player_t *network_player;
// if the REOLECallback class instance hasn't been instantiated yet, do it
if (richedit_oleinterface == NULL)
richedit_oleinterface = new RichEditOLECallback;
network_player = Player_FindByType (PLAYER_INTERNET); // quick access to network player
if (network_player == NULL)
return; // consistency check
if (lastonlineplayers_time + 5.0f < current_time)
Player_SendBuffer_Add (network_player, 1000, L"who\n"); // request a players list update
if (!interlocutors[interlocutor_index].is_active)
return; // consistency check
// 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);
is_classregistered = true; // remember this window class is registered
}
// create the window as an application-level window (not child)
interlocutors[interlocutor_index].hWnd = CreateWindowEx (WS_EX_CLIENTEDGE, WINDOW_CLASSNAME, L"", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT,
NULL, NULL, hAppInstance, NULL);
interlocutors[interlocutor_index].update_dialog = true; // and update this interlocutor's dialog
return; // finished, the window is fired up
}
void Window_Chat_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_chat_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;
wchar_t temp_string[256];
wchar_t *text_buffer;
char *cstring_buffer;
interlocutor_t *interlocutor;
onlineplayer_t *onlineplayer;
challenge_t *challenge;
player_t *remote_player;
player_t *local_player;
MINMAXINFO *minmax;
FLASHWINFO flashinfo;
RECT client_rect;
HWND hEditWnd;
int display_picture;
int player_index;
int char_index;
int length;
bool is_invitable;
// 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_QUESTION, hAppInstance, NULL);
LoadLibrary (L"riched20.dll"); // ensure the RichEdit controls DLL is loaded
hEditWnd = CreateWindowEx (0, L"RichEdit20A", L"", // for some reason, only the ANSI version of this control accepts RTF data :(
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_SELECTIONBAR | ES_SUNKEN,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_EDITBOX_DISCUSSION, hAppInstance, NULL);
SendMessage (hEditWnd, EM_SETOLECALLBACK, 0, (LPARAM) richedit_oleinterface);
hEditWnd = CreateWindowEx (WS_EX_CLIENTEDGE, L"edit", L"",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_EDITBOX_MESSAGETOSEND, hAppInstance, NULL);
WindowProc_MessageToSend_DefaultProc = (PROC) GetWindowLong (hEditWnd, GWL_WNDPROC); // save this edit box's window proc
SetWindowLong (hEditWnd, GWL_WNDPROC, (long) WindowProc_MessageToSend); // replace this edit box's window proc by ours
CreateWindowEx (0, L"static", L"",
WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_SUNKEN,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_BITMAP_OPPONENTSTATUS, hAppInstance, NULL);
CreateWindowEx (0, L"button", L"",
WS_CHILD | WS_VISIBLE | WS_DISABLED, // initially disabled
0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_INVITE, hAppInstance, NULL);
CreateWindowEx (0, L"button", L"",
WS_CHILD | WS_VISIBLE | WS_DISABLED, // initially disabled
0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_PLAYERINFO, hAppInstance, NULL);
CreateWindowEx (0, L"button", L"",
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_SEND, hAppInstance, NULL);
CreateWindowEx (WS_EX_RIGHT, L"static", L"",
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL);
// set the chat text font
SendMessage (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND), WM_SETFONT, (WPARAM) hFontChat, false);
// associate the default GUI font with the leading caption
SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_QUESTION), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
// associate the default GUI font with the invite button and set its text
SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_INVITE), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
SetDlgItemText (hWnd, WINDOW_BUTTON_INVITE, LOCALIZE (L"Chat_Invite"));
// associate the default GUI font with the player info button and set its text
SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_PLAYERINFO), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
SetDlgItemText (hWnd, WINDOW_BUTTON_PLAYERINFO, LOCALIZE (L"Chat_GetInfo"));
// associate the default GUI font with the send button and set its text
SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_SEND), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
SetDlgItemText (hWnd, WINDOW_BUTTON_SEND, LOCALIZE (L"Button_Send"));
// 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);
SetDlgItemText (hWnd, WINDOW_TEXT_STATUSBAR, LOCALIZE (L"Chat_StatusBar"));
// refresh the server message area every second
SetTimer (hWnd, WINDOW_TIMER_REFRESH, 1000, NULL);
SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, 0); // but call it now
// 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 are we destroying this window ?
else if (message == WM_DESTROY)
{
KillTimer (hWnd, WINDOW_TIMER_REFRESH); // destroy the timer we used to refresh the dialog text
interlocutor = Interlocutor_FindByWindowHandle (hWnd); // find the interlocutor it is
if (interlocutor != NULL)
interlocutor->is_active = false; // remember interlocutor has gone away
is_window_chat_validated = true;
the_board.reevaluate = true; // refresh the GUI buttons if needed
}
// 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_TEXT_QUESTION), NULL, 16, 16, client_rect.right - 148, 16, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_EDITBOX_DISCUSSION), NULL, 16, 40, client_rect.right - 148, client_rect.bottom - 136, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND), NULL, 16, client_rect.bottom - 80, client_rect.right - 148, 64, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_BITMAP_OPPONENTSTATUS), NULL, client_rect.right - 116, 16, 100, 100, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_INVITE), NULL, client_rect.right - 116, 124, 100, 32, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_PLAYERINFO), NULL, client_rect.right - 116, 162, 100, 32, SWP_NOZORDER);
SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_SEND), NULL, client_rect.right - 116, client_rect.bottom - 80, 100, 64, 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 did we take action on one of the controls ?
else if (message == WM_COMMAND)
{
// was it the "invite" button ?
if (wParam_loword == WINDOW_BUTTON_INVITE)
{
interlocutor = Interlocutor_FindByWindowHandle (hWnd); // find the interlocutor it is
if (interlocutor != NULL)
{
// player is available for an invitation. Is he already inviting us ?
challenge = Challenge_Find (interlocutor->nickname);
if ((challenge != NULL) && challenge->is_active && IsWindow (challenge->hWnd))
EndDialog (challenge->hWnd, 0); // if so, close the challenge dialog box (this will also make us decline it properly)
DialogBox_SendChallenge (interlocutor->nickname); // fire up the dialog box
}
}
// else did we click the "get player info" button ?
else if (wParam_loword == WINDOW_BUTTON_PLAYERINFO)
{
interlocutor = Interlocutor_FindByWindowHandle (hWnd); // find the interlocutor it is
if (interlocutor != NULL)
PlayerCard_FindOrCreate (interlocutor->nickname); // fire up the dialog box
}
// else was it the "send" button ?
else if (wParam_loword == WINDOW_BUTTON_SEND)
{
// grab the text to send from the edit box and empty the box
length = SendMessage (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND), WM_GETTEXTLENGTH, 0, 0); // get text length
text_buffer = (wchar_t *) SAFE_malloc (length + 1, sizeof (wchar_t), false); // allocate space
GetDlgItemText (hWnd, WINDOW_EDITBOX_MESSAGETOSEND, text_buffer, length + 1); // copy text
// is there actually something to send ?
if (text_buffer[0] != 0)
{
// drop all accents by converting to 7-bit US-ASCII and then back to wide char
cstring_buffer = (char *) SAFE_malloc (2 * (length + 1), sizeof (char), false);
ConvertTo7BitASCII (cstring_buffer, 2 * (length + 1), text_buffer);
ConvertToWideChar (text_buffer, length + 1, cstring_buffer);
SAFE_free ((void **) &cstring_buffer);
// convert some dangerous characters
length = wcslen (text_buffer);
for (char_index = 0; char_index < length; char_index++)
if ((text_buffer[char_index] == L'\r') || (text_buffer[char_index] == L'\n'))
text_buffer[char_index] = L' ';
else if (text_buffer[char_index] == L'%')
text_buffer[char_index] = L'ยค'; // percent signs are nasty for the varargs, so remove them
// find the local and the network players
if (((remote_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
&& ((local_player = Player_FindByType (PLAYER_HUMAN)) != NULL))
{
interlocutor = Interlocutor_FindByWindowHandle (hWnd); // find the interlocutor it is
if (interlocutor != NULL)
{
// fill the send buffer and append chat text to interlocutor's chat window
Player_SendBuffer_Add (remote_player, 1000, L"tell %s %s\n", interlocutor->nickname, text_buffer);
Interlocutor_Chat (interlocutor, local_player->name, true, text_buffer);
}
}
}
SetDlgItemText (hWnd, WINDOW_EDITBOX_MESSAGETOSEND, L""); // reset text in the window
SAFE_free ((void **) &text_buffer); // free the memory space we used
}
// else was it the status bar hyperlink ?
else 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
}
// else is it a timer event AND is it our refresh timer ?
else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH))
{
interlocutor = Interlocutor_FindByWindowHandle (hWnd); // find the interlocutor it is
if (interlocutor != NULL)
{
// find interlocutor in online players list
for (player_index = 0; player_index < onlineplayer_count; player_index++)
if (wcscmp (interlocutor->nickname, onlineplayers[player_index].nickname) == 0)
break; // break as soon as we find it
// have we found it ?
if (player_index < onlineplayer_count)
{
onlineplayer = &onlineplayers[player_index]; // quick access to online player
remote_player = Player_FindByType (PLAYER_INTERNET); // find the remote player
// is this talkee our opponent AND are we playing a game AND does its status not reflect it yet ?
if ((remote_player != NULL) && remote_player->is_in_game
&& (wcscmp (onlineplayer->nickname, remote_player->name) == 0)
&& ((onlineplayer->handlestatus == HANDLESTATUS_UNDEFINED)
|| (onlineplayer->handlestatus == HANDLESTATUS_AVAILABLE)
|| (onlineplayer->handlestatus == HANDLESTATUS_EXAMININGAGAME)
|| (onlineplayer->handlestatus == HANDLESTATUS_NOTOPENFORAMATCH)
|| (onlineplayer->handlestatus == HANDLESTATUS_INACTIVEORBUSY)))
onlineplayer->handlestatus = HANDLESTATUS_INGAME; // if so, remember we're playing against it
display_picture = onlineplayer->handlestatus; // if so, use its current status
}
else
display_picture = HANDLESTATUS_OFFLINE; // else flag it as offline
// do we need to change its status ?
if (display_picture != interlocutor->current_displaypicture)
{
// set window icons (small one for title bar & big one for task manager) and avatar
SendMessage (hWnd, WM_SETICON, ICON_SMALL, (LPARAM) handlestatus[display_picture].icon);
SendMessage (hWnd, WM_SETICON, ICON_BIG, (LPARAM) handlestatus[display_picture].icon);
SendMessage (GetDlgItem (hWnd, WINDOW_BITMAP_OPPONENTSTATUS), STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) handlestatus[display_picture].bitmap);
// set window text
swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%s (%s)", interlocutor->nickname, handlestatus[display_picture].text);
SetWindowText (hWnd, temp_string); // set window title
// set window title and parameters
if (display_picture == HANDLESTATUS_OFFLINE)
{
EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_PLAYERINFO), false); // if so, disable buttons
EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_INVITE), false);
EnableWindow (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND), false);
EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_SEND), false);
}
else
{
if ((display_picture == HANDLESTATUS_OFFLINE) || (display_picture == HANDLESTATUS_INGAME) || (display_picture == HANDLESTATUS_NOTOPENFORAMATCH))
is_invitable = false; // this player is NOT invitable
else
is_invitable = true; // all other players are invitable
EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_PLAYERINFO), true); // else enable them
EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_INVITE), is_invitable);
EnableWindow (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND), true);
EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_SEND), true);
if (GetForegroundWindow () == hWnd)
SetFocus (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND)); // set keyboard focus to the message editbox
}
interlocutor->current_displaypicture = display_picture; // remember we've done it
}
// do we need to update dialog ?
if (interlocutor->update_dialog)
{
// set the leading caption
swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), LOCALIZE (L"Chat_Question"), interlocutor->nickname);
SetDlgItemText (hWnd, WINDOW_TEXT_QUESTION, temp_string);
// set discussion text and scroll the edit box down
hEditWnd = GetDlgItem (hWnd, WINDOW_EDITBOX_DISCUSSION);
SetWindowText (hEditWnd, interlocutor->dialogtext);
length = GetWindowTextLength (hEditWnd);
//SetFocus (hEditWnd);
Edit_SetSel (hEditWnd, length, length);
Edit_ScrollCaret (hEditWnd);
SetFocus (GetDlgItem (hWnd, WINDOW_EDITBOX_MESSAGETOSEND));
// if we are not the top level window, flash window
if (GetForegroundWindow () != hWnd)
{
memset (&flashinfo, 0, sizeof (flashinfo));
flashinfo.cbSize = sizeof (flashinfo);
flashinfo.hwnd = hWnd;
flashinfo.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; // flash until we bring window to foreground
FlashWindowEx (&flashinfo);
}
interlocutor->update_dialog = false; // remember we refreshed dialog text
}
}
}
// call the default dialog message processing function to keep things going
return (DefWindowProc (hWnd, message, wParam, lParam));
}
static LRESULT CALLBACK WindowProc_MessageToSend (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
// this is the callback function for the edit box control where the user types its message
// was the enter key pressed ?
if ((message == WM_CHAR) && (wParam == VK_RETURN))
{
SendMessage (GetParent (hWnd), WM_COMMAND, WINDOW_BUTTON_SEND, 0); // if so, forward it to our parent
return (true); // and prevent this keystroke from being treated
}
// for all other messages, call the default window message processing function
return ((LRESULT) CallWindowProc ((WNDPROC) WindowProc_MessageToSend_DefaultProc, hWnd, message, wParam, lParam));
}