windows-nt/Source/XPSP1/NT/multimedia/directx/dinput/diconfig/cdiacpage.cpp

1908 lines
50 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//-----------------------------------------------------------------------------
// File: cdiacpage.cpp
//
// Desc: CDIDeviceActionConfigPage implements the page object used by the UI.
// A page covers the entire UI minus the device tabs and the bottons at
// the bottom. The information window, player combo-box, genre combo-
// box, action list tree, and device view window are all managed by
// the page.
//
// Copyright (C) 1999-2000 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------
#include "common.hpp"
#include <initguid.h>
DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
// {D0B5C9AE-966F-4510-B955-4D2482C5EB1B}
DEFINE_GUID(GUID_ActionItem,
0xd0b5c9ae, 0x966f, 0x4510, 0xb9, 0x55, 0x4d, 0x24, 0x82, 0xc5, 0xeb, 0x1b);
#define DISEM_TYPE_MASK ( 0x00000600 )
#define DISEM_REL_MASK ( 0x00000100 )
#define DISEM_REL_SHIFT ( 8 )
#define DISEM_TYPE_AXIS 0x00000200
#define DISEM_TYPE_BUTTON 0x00000400
#define DISEM_TYPE_POV 0x00000600
#define DEVICE_POLLING_INTERVAL 10
#define DEVICE_POLLING_AXIS_MIN 0
#define DEVICE_POLLING_AXIS_MAX 100
#define DEVICE_POLLING_AXIS_MINDELTA 3
#define DEVICE_POLLING_AXIS_SIGNIFICANT 40
#define DEVICE_POLLING_AXIS_ACCUMULATION 20
#define DEVICE_POLLING_ACBUF_START_INDEX 3
#define DEVICE_POLLING_WHEEL_SCALE_FACTOR 3
// For WINMM.DLL
HINSTANCE g_hWinMmDLL = NULL;
FUNCTYPE_timeSetEvent g_fptimeSetEvent = NULL;
//QueryInterface
STDMETHODIMP CDIDeviceActionConfigPage::QueryInterface(REFIID iid, LPVOID* ppv)
{
//null the out param
*ppv = NULL;
if ((iid == IID_IUnknown) || (iid == IID_IDIDeviceActionConfigPage))
{
*ppv = this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
//AddRef
STDMETHODIMP_(ULONG) CDIDeviceActionConfigPage::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
//Release
STDMETHODIMP_(ULONG) CDIDeviceActionConfigPage::Release()
{
if (InterlockedDecrement(&m_cRef) == 0)
{
delete this;
return 0;
}
return m_cRef;
}
//constructor
CDIDeviceActionConfigPage::CDIDeviceActionConfigPage() :
m_pDeviceUI(NULL), m_puig(NULL), m_pUIFrame(NULL),
m_cRef(1), m_lpDiac(NULL), m_State(CFGSTATE_NORMAL),
m_pCurControl(NULL),
m_tszIBText(NULL), m_pbmIB(NULL), m_pbmIB2(NULL),
m_pbmRelAxesGlyph(NULL), m_pbmAbsAxesGlyph(NULL), m_pbmButtonGlyph(NULL),
m_pbmHatGlyph(NULL), m_pbmCheckGlyph(NULL), m_pbmCheckGlyphDark(NULL),
m_pRelAxesParent(NULL), m_pAbsAxesParent(NULL), m_pButtonParent(NULL),
m_pHatParent(NULL), m_pUnknownParent(NULL),
m_bFirstDeviceData(TRUE), m_cbDeviceDataSize(0), m_nOnDeviceData(0),
m_dwLastControlType(0),
m_nPageIndex(-1)
{
tracescope(__ts, _T("CDIDeviceActionConfigPage::CDIDeviceActionConfigPage()\n"));
m_pDeviceData[0] = NULL;
m_pDeviceData[1] = NULL;
}
//destructor
CDIDeviceActionConfigPage::~CDIDeviceActionConfigPage()
{
tracescope(__ts, _T("CDIDeviceActionConfigPage::~CDIDeviceActionConfigPage()\n"));
// Unattach the parent from the tooltip window so it won't get destroyed.
SetParent(CFlexWnd::s_ToolTip.m_hWnd, NULL);
if (m_hWnd != NULL)
Destroy();
FreeResources();
delete m_pDeviceUI;
for (int c = 0; c < 2; c++)
if (m_pDeviceData[c] != NULL)
free(m_pDeviceData[c]);
if (m_lpDID != NULL)
{
m_lpDID->Unacquire();
m_lpDID->Release();
}
m_lpDID = NULL;
}
STDMETHODIMP CDIDeviceActionConfigPage::Create(DICFGPAGECREATESTRUCT *pcs)
{
tracescope(__ts, _T("CDIDeviceActionConfigPage::Create()\n"));
if (pcs == NULL)
return E_INVALIDARG;
DICFGPAGECREATESTRUCT &cs = *pcs;
// validate/save uig and uif
m_puig = pcs->pUIGlobals;
m_pUIFrame = pcs->pUIFrame;
if (m_puig == NULL || m_pUIFrame == NULL)
return E_INVALIDARG;
// save page index
m_nPageIndex = pcs->nPage;
assert(m_nPageIndex >= 0);
// create deviceui with uig, or fail
m_pDeviceUI = new CDeviceUI(*m_puig, *m_pUIFrame);
if (m_pDeviceUI == NULL)
return E_FAIL;
// save the device instance
m_didi = cs.didi;
m_lpDID = cs.lpDID;
if (m_lpDID != NULL)
m_lpDID->AddRef();
// create the window
HWND hWnd = NULL;
assert(m_puig != NULL);
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
BOOL bAllowEditLayout = m_puig->QueryAllowEditLayout();
#endif
//@@END_MSINTERNAL
RECT rect = {0, 0, 1, 1};
hWnd = CFlexWnd::Create(cs.hParentWnd, rect, FALSE);
// return the handle
cs.hPageWnd = hWnd;
assert(m_puig != NULL);
// Create the information box
m_InfoBox.Create(m_hWnd, g_InfoWndRect, TRUE);
m_InfoBox.SetFont((HFONT)m_puig->GetFont(UIE_USERNAMES));
m_InfoBox.SetColors(m_puig->GetTextColor(UIE_USERNAMES),
m_puig->GetBkColor(UIE_USERNAMES),
m_puig->GetTextColor(UIE_USERNAMESEL),
m_puig->GetBkColor(UIE_USERNAMESEL),
m_puig->GetBrushColor(UIE_USERNAMES),
m_puig->GetPenColor(UIE_USERNAMES));
SetAppropriateDefaultText();
// Create the check box only if this is a keyboard device.
if (LOBYTE(LOWORD(m_didi.dwDevType)) == DI8DEVTYPE_KEYBOARD)
{
m_CheckBox.Create(m_hWnd, g_CheckBoxRect, FALSE);
m_CheckBox.SetNotify(m_hWnd);
m_CheckBox.SetFont((HFONT)m_puig->GetFont(UIE_USERNAMES));
m_CheckBox.SetColors(m_puig->GetTextColor(UIE_USERNAMES),
m_puig->GetBkColor(UIE_USERNAMES),
m_puig->GetTextColor(UIE_USERNAMESEL),
m_puig->GetBkColor(UIE_USERNAMESEL),
m_puig->GetBrushColor(UIE_USERNAMES),
m_puig->GetPenColor(UIE_USERNAMES));
TCHAR tszResourceString[MAX_PATH];
LoadString(g_hModule, IDS_SORTASSIGNED, tszResourceString, MAX_PATH);
m_CheckBox.SetText(tszResourceString);
m_CheckBox.SetCheck(TRUE);
::ShowWindow(m_CheckBox.m_hWnd, SW_SHOW);
}
// create the username dropdown if necessary
FLEXCOMBOBOXCREATESTRUCT cbcs;
cbcs.dwSize = sizeof(FLEXCOMBOBOXCREATESTRUCT);
cbcs.dwFlags = FCBF_DEFAULT;
cbcs.dwListBoxFlags = FCBF_DEFAULT|FLBF_INTEGRALHEIGHT;
cbcs.hWndParent = m_hWnd;
cbcs.hWndNotify = m_hWnd;
cbcs.bVisible = TRUE;
cbcs.rect = g_UserNamesRect;
cbcs.hFont = (HFONT)m_puig->GetFont(UIE_USERNAMES);
cbcs.rgbText = m_puig->GetTextColor(UIE_USERNAMES);
cbcs.rgbBk = m_puig->GetBkColor(UIE_USERNAMES);
cbcs.rgbSelText = m_puig->GetTextColor(UIE_USERNAMESEL);
cbcs.rgbSelBk = m_puig->GetBkColor(UIE_USERNAMESEL);
cbcs.rgbFill = m_puig->GetBrushColor(UIE_USERNAMES);
cbcs.rgbLine = m_puig->GetPenColor(UIE_USERNAMES);
cbcs.nSBWidth = 11;
if (m_puig->GetNumUserNames() > 0 && m_hWnd != NULL
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
&& !m_puig->QueryAllowEditLayout()
#endif
//@@END_MSINTERNAL
)
{
for (int i = 0, n = m_puig->GetNumUserNames(); i < n; i++)
m_UserNames.AddString(SAFESTR(m_puig->GetUserName(i)));
m_UserNames.AddString(SAFESTR(_T("(unassigned)")));
m_UserNames.Create(&cbcs);
int nUser = m_pUIFrame->GetCurUser(m_nPageIndex);
if (nUser == -1)
nUser = m_puig->GetNumUserNames();
m_UserNames.SetSel(nUser);
} else
if (m_hWnd != NULL)
m_UserNames.SetSel(0); // If only 1 user, still must set selection to 0 or we get error later.
// If we are in view mode, set username combobox to read only so user can't change its value.
if (!m_puig->InEditMode())
m_UserNames.SetReadOnly(TRUE);
if (m_puig->GetNumMasterAcFors() > 1 && m_hWnd != NULL)
{
for (int i = 0, n = m_puig->GetNumMasterAcFors(); i < n; i++)
m_Genres.AddString(SAFESTR(m_puig->RefMasterAcFor(i).tszActionMap));
cbcs.rect = g_GenresRect;
m_Genres.Create(&cbcs);
m_Genres.SetSel(m_pUIFrame->GetCurGenre());
}
// return success/fail
return hWnd != NULL ? S_OK : E_FAIL;
}
STDMETHODIMP CDIDeviceActionConfigPage::Show(LPDIACTIONFORMATW lpDiActFor)
{
// save the format pointer
m_lpDiac = lpDiActFor;
// force tree init
InitTree(TRUE);
// show the assignments for the controls
SetControlAssignments();
// show the assignment for the current control
ShowCurrentControlAssignment();
// Sort the list if check box is checked.
if (m_CheckBox.GetCheck())
m_pDeviceUI->GetCurView()->SortAssigned(TRUE);
// show the window
if (m_hWnd != NULL)
ShowWindow(m_hWnd, SW_SHOW);
SetFocus(m_hWnd);
CFlexWnd::s_CurrPageHwnd = m_hWnd;
return S_OK;
}
STDMETHODIMP CDIDeviceActionConfigPage::Hide()
{
// clear the tree
ClearTree();
// null the format pointer
m_lpDiac = NULL;
// hide the window
if (m_hWnd != NULL)
ShowWindow(m_hWnd, SW_HIDE);
// If we are in the assign state, exit it.
if (m_State == CFGSTATE_ASSIGN)
ExitAssignState();
return S_OK;
}
void CDIDeviceActionConfigPage::InitIB()
{
RECT z = {0,0,0,0};
SIZE bsize = {0,0};
m_rectIB = z;
if (m_pbmIB != NULL)
{
if (m_pbmIB->GetSize(&bsize))
{
m_rectIB.right = bsize.cx * 2;
m_rectIB.bottom = bsize.cy;
}
}
const int IBORIGINX = 200, IBORIGINY = 394,
IBTEXTMARGINLEFT = 5;
POINT ptIBOrigin = {IBORIGINX, IBORIGINY};
m_tszIBText = _T("Click here to see different views of your controller.");
SIZE tsize = GetTextSize(m_tszIBText, (HFONT)m_puig->GetFont(UIE_VIEWSEL));
m_ptIBOffset.x = 0;
m_ptIBOffset.y = 0;
int tofs = 0;
if (m_rectIB.bottom < tsize.cy)
{
m_rectIB.bottom = tsize.cy;
m_ptIBOffset.y = (tsize.cy - bsize.cy) / 2;
}
else if (tsize.cy < m_rectIB.bottom)
tofs = (bsize.cy - tsize.cy) / 2;
m_rectIB.right += tsize.cx;
if (m_pbmIB != NULL)
m_rectIB.right += IBTEXTMARGINLEFT * 2;
OffsetRect(&m_rectIB, ptIBOrigin.x, ptIBOrigin.y);
m_ptIBOffset.x += ptIBOrigin.x;
m_ptIBOffset.y += ptIBOrigin.y;
m_ptIBOffset2.x = m_rectIB.right - bsize.cx;
m_ptIBOffset2.y = m_ptIBOffset.y;
m_rectIBText = m_rectIB;
if (m_pbmIB != NULL)
m_rectIBText.left += IBTEXTMARGINLEFT + bsize.cx;
if (m_pbmIB2 != NULL)
m_rectIBText.right -= IBTEXTMARGINLEFT + bsize.cx;
m_rectIBText.top += tofs;
// Inialize the two RECTs representing the two arrow bitmaps
m_rectIBLeft = m_rectIBRight = m_rectIB;
m_rectIBLeft.right = m_rectIBText.left;
m_rectIBRight.left = m_rectIBText.right;
}
void CDIDeviceActionConfigPage::OnInit()
{
tracescope(__ts, _T("CDIDeviceActionConfigPage::OnInit()\n"));
// init resources
InitResources();
// init IB
InitIB();
// initialize the device UI
m_pDeviceUI->Init(m_didi, m_lpDID, m_hWnd, this);
// initialize the device
InitDevice();
// Start a one-shot timer for click to pick
if (g_fptimeSetEvent)
g_fptimeSetEvent(DEVICE_POLLING_INTERVAL, DEVICE_POLLING_INTERVAL,
CDIDeviceActionConfigPage::DeviceTimerProc, (DWORD_PTR)m_hWnd, TIME_ONESHOT);
// create the tree
CAPTIONLOOK cl;
cl.dwMask = CLMF_TEXTCOLOR | CLMF_FONT | CLMF_LINECOLOR;
cl.rgbTextColor = m_puig->GetTextColor(UIE_CALLOUT);
cl.rgbLineColor = m_puig->GetPenColor(UIE_BORDER);
cl.hFont = (HFONT)m_puig->GetFont(UIE_ACTION);
m_Tree.SetDefCaptionLook(cl);
cl.rgbTextColor = m_puig->GetTextColor(UIE_CALLOUTHIGH);
m_Tree.SetDefCaptionLook(cl, TRUE);
m_Tree.SetBkColor(RGB(0,0,0));
if (m_puig->InEditMode())
{
m_Tree.Create(m_hWnd, g_TreeRect, TRUE, TRUE);
m_Tree.SetScrollBarColors(
m_puig->GetBrushColor(UIE_SBTRACK),
m_puig->GetBrushColor(UIE_SBTHUMB),
m_puig->GetPenColor(UIE_SBBUTTON));
}
}
void CDIDeviceActionConfigPage::InitResources()
{
// create glyphs
if (!m_pbmRelAxesGlyph)
m_pbmRelAxesGlyph = CBitmap::CreateFromResource(g_hModule, IDB_AXESGLYPH);
if (!m_pbmAbsAxesGlyph)
m_pbmAbsAxesGlyph = CBitmap::CreateFromResource(g_hModule, IDB_AXESGLYPH);
if (!m_pbmButtonGlyph)
m_pbmButtonGlyph = CBitmap::CreateFromResource(g_hModule, IDB_BUTTONGLYPH);
if (!m_pbmHatGlyph)
m_pbmHatGlyph = CBitmap::CreateFromResource(g_hModule, IDB_HATGLYPH);
if (!m_pbmCheckGlyph)
m_pbmCheckGlyph = CBitmap::CreateFromResource(g_hModule, IDB_CHECKGLYPH);
if (!m_pbmCheckGlyphDark)
m_pbmCheckGlyphDark = CBitmap::CreateFromResource(g_hModule, IDB_CHECKGLYPHDARK);
// create IB bitmaps
if (!m_pbmIB)
m_pbmIB = CBitmap::CreateFromResource(g_hModule, IDB_IB);
if (!m_pbmIB2)
m_pbmIB2 = CBitmap::CreateFromResource(g_hModule, IDB_IB2);
}
void CDIDeviceActionConfigPage::FreeResources()
{
if (m_pbmRelAxesGlyph)
delete m_pbmRelAxesGlyph;
if (m_pbmAbsAxesGlyph)
delete m_pbmAbsAxesGlyph;
if (m_pbmButtonGlyph)
delete m_pbmButtonGlyph;
if (m_pbmHatGlyph)
delete m_pbmHatGlyph;
if (m_pbmCheckGlyph)
delete m_pbmCheckGlyph;
if (m_pbmCheckGlyphDark)
delete m_pbmCheckGlyphDark;
if (m_pbmIB)
delete m_pbmIB;
if (m_pbmIB2)
delete m_pbmIB2;
m_pbmRelAxesGlyph = NULL;
m_pbmAbsAxesGlyph = NULL;
m_pbmButtonGlyph = NULL;
m_pbmHatGlyph = NULL;
m_pbmCheckGlyph = NULL;
m_pbmCheckGlyphDark = NULL;
m_pbmIB = NULL;
m_pbmIB2 = NULL;
}
void CDIDeviceActionConfigPage::ClearTree()
{
m_Tree.FreeAll();
m_pRelAxesParent = NULL;
m_pAbsAxesParent = NULL;
m_pButtonParent = NULL;
m_pHatParent = NULL;
m_pUnknownParent = NULL;
m_dwLastControlType = 0;
}
void CDIDeviceActionConfigPage::InitTree(BOOL bForceInit)
{
// get type of control
DWORD dwControlType = 0;
if (m_pCurControl && m_pCurControl->IsOffsetAssigned())
{
DWORD dwObjId = m_pCurControl->GetOffset();
if (dwObjId & DIDFT_RELAXIS)
dwControlType = DIDFT_RELAXIS;
else if (dwObjId & DIDFT_ABSAXIS)
dwControlType = DIDFT_ABSAXIS;
else if (dwObjId & DIDFT_BUTTON)
dwControlType = DIDFT_BUTTON;
else if (dwObjId & DIDFT_POV)
dwControlType = DIDFT_POV;
}
// Turn off the tree's readonly flag if we are in the assign state.
// We will turn it on later if current control's action has DIA_APPFIXED.
if (m_State == CFGSTATE_NORMAL)
m_Tree.SetReadOnly(TRUE);
else
m_Tree.SetReadOnly(FALSE);
// if this control type is the same as the last, do nothing,
// unless we're force init
if (m_dwLastControlType == dwControlType && !bForceInit && m_State)
return;
// delete the whole tree
ClearTree();
// can't use tree if there is no diac or action array
if (m_lpDiac == NULL || m_lpDiac->rgoAction == NULL)
return;
// also can't use if we don't have a control type
if (dwControlType == 0)
return;
// prepare margin rects
RECT labelmargin = {14, 6, 3, 3};
RECT itemmargin = {14, 1, 3, 2};
// set default indents
m_Tree.SetRootChildIndent(5);
m_Tree.SetDefChildIndent(12);
// add the control type sections
m_Tree.SetDefMargin(labelmargin);
TCHAR tszResourceString[MAX_PATH];
switch (dwControlType)
{
case DIDFT_RELAXIS:
LoadString(g_hModule, IDS_AXISACTIONS, tszResourceString, MAX_PATH);
m_pRelAxesParent = m_Tree.DefAddItem(tszResourceString);
break;
case DIDFT_ABSAXIS:
LoadString(g_hModule, IDS_AXISACTIONS, tszResourceString, MAX_PATH);
m_pAbsAxesParent = m_Tree.DefAddItem(tszResourceString);
break;
case DIDFT_BUTTON:
LoadString(g_hModule, IDS_BUTTONACTIONS, tszResourceString, MAX_PATH);
m_pButtonParent = m_Tree.DefAddItem(tszResourceString);
break;
case DIDFT_POV:
LoadString(g_hModule, IDS_POVACTIONS, tszResourceString, MAX_PATH);
m_pHatParent = m_Tree.DefAddItem(tszResourceString);
break;
default:
return;
}
// populate the tree
m_Tree.SetDefMargin(itemmargin);
for (unsigned int i = 0; i < m_lpDiac->dwNumActions; i++)
{
DIACTIONW *pAction = m_lpDiac->rgoAction + i;
CFTItem *pItem = NULL;
if (pAction == NULL)
continue;
switch (pAction->dwSemantic & DISEM_TYPE_MASK)
{
case DISEM_TYPE_AXIS:
// Must distinguish between relative and absolute
switch((pAction->dwSemantic & DISEM_REL_MASK) >> DISEM_REL_SHIFT)
{
case 0: pItem = m_pAbsAxesParent; break;
case 1: pItem = m_pRelAxesParent; break;
}
break;
case DISEM_TYPE_BUTTON: pItem = m_pButtonParent; break;
case DISEM_TYPE_POV: pItem = m_pHatParent; break;
}
if (pItem == NULL)
continue;
// Add action with this name
CFTItem *pAlready = GetItemWithActionNameAndSemType(pAction->lptszActionName, pAction->dwSemantic);
if (!pAlready)
{
LPTSTR acname = AllocLPTSTR(pAction->lptszActionName);
pItem = m_Tree.DefAddItem(acname, pItem, ATTACH_LASTCHILD); // This might return NULL.
free(acname);
if (pItem)
pItem->SetUserData((LPVOID)(new RGLPDIACW));
}
else
{
pItem = pAlready;
}
if (pItem == NULL)
continue;
pItem->SetUserGUID(GUID_ActionItem);
RGLPDIACW *pacs = (RGLPDIACW *)pItem->GetUserData();
if (pacs)
pacs->SetAtGrow(pacs->GetSize(), pAction);
if (pAlready)
{
// The tree already has an action with this name. Check the DIA_APPFIXED flag for each DIACTION
// that this item holds.
DWORD dwNumActions = GetNumItemLpacs(pItem);
for (DWORD i = 0; i < dwNumActions; ++i)
{
LPDIACTIONW lpExistingAc = GetItemLpac(pItem, i);
// If the DIACTION that is assigned to this device has DIA_APPFIXED flag, then
// the other must have it too.
if (lpExistingAc && IsEqualGUID(lpExistingAc->guidInstance, m_didi.guidInstance))
{
if (lpExistingAc->dwFlags & DIA_APPFIXED)
{
// If this DIACTION has DIA_APPFIXED, then all DIACTIONs must have it too.
for (DWORD j = 0; j < dwNumActions; ++j)
{
LPDIACTIONW lpChangeAc = GetItemLpac(pItem, j);
if (lpChangeAc)
lpChangeAc->dwFlags |= DIA_APPFIXED;
}
}
break; // Break the loop since we already found the DIACTION that is assigned.
}
}
} // if (pAlready)
}
// show all
m_Tree.GetRoot()->ExpandAll();
m_dwLastControlType = dwControlType;
}
int CompareActionNames(LPCWSTR acname1, LPCWSTR acname2)
{
#ifdef CFGUI__COMPAREACTIONNAMES_CASE_INSENSITIVE
return _wcsicmp(acname1, acname2);
#else
return wcscmp(acname1, acname2);
#endif
}
CFTItem *CDIDeviceActionConfigPage::GetItemWithActionNameAndSemType(LPCWSTR acname, DWORD dwSemantic)
{
CFTItem *pItem = m_Tree.GetFirstItem();
for (; pItem != NULL; pItem = pItem->GetNext())
{
if (!pItem->IsUserGUID(GUID_ActionItem))
continue;
LPDIACTIONW lpac = GetItemLpac(pItem);
if (!lpac)
continue;
// Check semantic type
if ((lpac->dwSemantic & DISEM_TYPE_MASK) != (dwSemantic & DISEM_TYPE_MASK))
continue;
// If both are axis, check for relative/absolute
if ((lpac->dwSemantic & DISEM_TYPE_MASK) == DISEM_TYPE_AXIS)
if ((lpac->dwSemantic & DISEM_REL_MASK) != (dwSemantic & DISEM_REL_MASK))
continue;
// Check name
if (CompareActionNames(lpac->lptszActionName, acname) == 0)
return pItem;
}
return NULL;
}
void CDIDeviceActionConfigPage::OnPaint(HDC hDC)
{
TCHAR tszResourceString[MAX_PATH];
CPaintHelper ph(*m_puig, hDC);
ph.SetBrush(UIB_BLACK);
RECT rect;
GetClientRect(&rect);
ph.Rectangle(rect, UIR_SOLID);
ph.SetText(UIC_BORDER, UIC_BLACK);
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
if (!m_puig->QueryAllowEditLayout())
#endif
//@@END_MSINTERNAL
{
rect = g_UserNamesTitleRect;
LoadString(g_hModule, IDS_PLAYER_TITLE, tszResourceString, MAX_PATH);
DrawText(hDC, tszResourceString, -1, &rect, DT_CENTER|DT_NOCLIP|DT_NOPREFIX);
}
if (m_puig->GetNumMasterAcFors() > 1)
{
rect = g_GenresTitleRect;
LoadString(g_hModule, IDS_GENRE_TITLE, tszResourceString, MAX_PATH);
DrawText(hDC, tszResourceString, -1, &rect, DT_CENTER|DT_NOCLIP|DT_NOPREFIX);
}
// Draw tree window title and outline if we are in edit mode.
if (m_puig->InEditMode())
{
COLORREF BorderColor = m_puig->GetColor(UIC_BORDER);
if (m_Tree.GetReadOnly())
BorderColor = RGB(GetRValue(BorderColor)>>1, GetGValue(BorderColor)>>1, GetBValue(BorderColor)>>1);
::SetTextColor(hDC, BorderColor); // Use the muted color if tree is read only.
// Draw tree window title (Available Actions)
rect = g_TreeTitleRect;
LoadString(g_hModule, IDS_AVAILABLEACTIONS_TITLE, tszResourceString, MAX_PATH);
DrawText(hDC, tszResourceString, -1, &rect, DT_CENTER|DT_NOCLIP|DT_NOPREFIX);
// Draw tree window outline
HGDIOBJ hPen, hOldPen;
if (m_Tree.GetReadOnly())
{
hPen = CreatePen(PS_SOLID, 0, BorderColor);
hOldPen = ::SelectObject(hDC, hPen);
}
else
ph.SetPen(UIP_BORDER);
RECT rc = g_TreeRect;
InflateRect(&rc, 1, 1);
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
if (m_Tree.GetReadOnly())
{
::SelectObject(hDC, hOldPen);
DeleteObject(hPen);
}
}
if (m_pDeviceUI->GetNumViews() < 2)
return;
if (m_pbmIB != NULL)
m_pbmIB->Draw(hDC, m_ptIBOffset);
if (m_pbmIB2 != NULL)
m_pbmIB2->Draw(hDC, m_ptIBOffset2);
if (m_tszIBText != NULL)
{
ph.SetElement(UIE_VIEWSEL);
RECT rect = m_rectIBText;
DrawText(hDC, m_tszIBText, -1, &rect, DT_NOCLIP | DT_NOPREFIX);
}
}
void CDIDeviceActionConfigPage::SetCurrentControl(CDeviceControl *pControl)
{
// If the new control is the same as the old, no need to do anything.
if (m_pCurControl == pControl)
return;
if (m_pCurControl != NULL)
{
m_pCurControl->Unhighlight();
// If we don't have a current control, then invalidate the view so that the old callout can be repainted.
// If there is a current control, the view will be invalidated by Highlight().
if (!pControl)
m_pCurControl->Invalidate();
}
m_pCurControl = pControl;
if (m_pCurControl != NULL)
m_pCurControl->Highlight();
ShowCurrentControlAssignment();
}
CFTItem *CDIDeviceActionConfigPage::GetItemForActionAssignedToControl(CDeviceControl *pControl)
{
if (!pControl)
return NULL;
// find the item for the action assigned to this control, if any
CFTItem *pItem = m_Tree.GetFirstItem();
for (; pItem != NULL; pItem = pItem->GetNext())
{
if (!pItem->IsUserGUID(GUID_ActionItem))
continue;
for (int i = 0, n = GetNumItemLpacs(pItem); i < n; i++)
{
LPDIACTIONW lpac = GetItemLpac(pItem, i);
if (!lpac)
continue;
if (IsEqualGUID(lpac->guidInstance, m_didi.guidInstance) &&
GetOffset(lpac) == pControl->GetOffset())
return pItem;
}
}
return NULL;
}
int CDIDeviceActionConfigPage::GetNumItemLpacs(CFTItem *pItem)
{
if (pItem == NULL)
return 0;
RGLPDIACW *pacs = (RGLPDIACW *)pItem->GetUserData();
if (!pacs)
return 0;
else
return pacs->GetSize();
}
LPDIACTIONW CDIDeviceActionConfigPage::GetItemLpac(CFTItem *pItem, int i)
{
if (pItem == NULL)
return NULL;
RGLPDIACW *pacs = (RGLPDIACW *)pItem->GetUserData();
if (!pacs || i < 0 || i >= pacs->GetSize())
return NULL;
else
return pacs->GetAt(i);
}
void CDIDeviceActionConfigPage::ShowCurrentControlAssignment()
{
// init the tree
InitTree();
// if we don't have a control...
if (m_pCurControl == NULL)
{
// select nothing
m_Tree.SetCurSel(NULL);
return;
}
// find the item for the action assigned to this control, if any
CFTItem *pItem = GetItemForActionAssignedToControl(m_pCurControl);
// if we didn't find a match...
if (!pItem)
{
// select nothing
m_Tree.SetCurSel(NULL);
return;
}
// We need to check if the action this control is assigned to has DIA_APPFIXED flag.
// If it does, this control cannot be remapped to another action.
// We prevent this by setting the tree control to read-only, so it can't receive any clicks.
LPDIACTIONW lpAc = GetItemLpac(pItem); // Get the action
if (lpAc && (lpAc->dwFlags & DIA_APPFIXED))
m_Tree.SetReadOnly(TRUE);
// otherwise, show item and select it
pItem->EnsureVisible();
m_Tree.SetCurSel(pItem);
}
void CDIDeviceActionConfigPage::DeviceUINotify(const DEVICEUINOTIFY &uin)
{
switch (uin.msg)
{
case DEVUINM_NUMVIEWSCHANGED:
Invalidate();
break;
case DEVUINM_SELVIEW:
// set the view
m_pDeviceUI->SetView(uin.selview.nView);
// show the assignments for the controls
SetControlAssignments();
// select nothing
SetCurrentControl(NULL);
break;
case DEVUINM_ONCONTROLDESTROY:
if (uin.control.pControl == m_pCurControl)
m_pCurControl = NULL;
break;
case DEVUINM_CLICK:
ExitAssignState();
switch (uin.from)
{
case DEVUINFROM_CONTROL:
SetCurrentControl(uin.control.pControl);
SetAppropriateDefaultText();
break;
case DEVUINFROM_VIEWWND:
break;
}
break;
case DEVUINM_DOUBLECLICK:
switch (uin.from)
{
case DEVUINFROM_CONTROL:
EnterAssignState();
break;
}
break;
case DEVUINM_MOUSEOVER:
SetAppropriateDefaultText();
break;
case DEVUINM_RENEWDEVICE:
HWND hParent = GetParent(m_hWnd);
CConfigWnd *pCfgWnd = (CConfigWnd *)GetFlexWnd(hParent);
if (pCfgWnd)
{
LPDIRECTINPUTDEVICE8W lpDID = pCfgWnd->RenewDevice(m_didi.guidInstance);
if (lpDID)
{
// Destroy the device instance we have
if (m_lpDID) m_lpDID->Release();
lpDID->AddRef();
m_lpDID = lpDID;
}
m_pDeviceUI->SetDevice(lpDID); // Sets the device pointer in CDeviceUI (no need to AddRef)
}
}
}
void CDIDeviceActionConfigPage::UnassignCallout()
{
// find the item for the action assigned to this control, if any
CFTItem *pItem = GetItemForActionAssignedToControl(m_pCurControl);
if (pItem)
{
LPDIACTIONW lpac = GetItemLpac(pItem);
// Only unassign if the action doesn't have DIA_APPFIXED flag.
if (lpac && !(lpac->dwFlags & DIA_APPFIXED))
{
ActionClick(NULL);
m_Tree.Invalidate();
}
}
// Sort the list if the check box is checked.
if (m_CheckBox.GetCheck())
m_pDeviceUI->GetCurView()->SortAssigned(TRUE);
}
void CDIDeviceActionConfigPage::NullAction(LPDIACTIONW lpac)
{
if (lpac == NULL)
return;
SetInvalid(lpac);
//@@BEGIN_MSINTERNAL
// TODO: find tree view item with this action and indicate unassignment
//@@END_MSINTERNAL
}
void CDIDeviceActionConfigPage::UnassignActionsAssignedTo(const GUID &guidInstance, DWORD dwOffset)
{
if (m_lpDiac == NULL || m_lpDiac->rgoAction == NULL)
return;
if (IsEqualGUID(guidInstance, GUID_NULL))
return;
// assign any actions assigned to this control to nothing
DWORD i;
LPDIACTIONW lpac;
for (i = 0, lpac = m_lpDiac->rgoAction; i < m_lpDiac->dwNumActions; i++, lpac++)
if (IsEqualGUID(guidInstance, lpac->guidInstance) && dwOffset == GetOffset(lpac)/*->dwInternalOffset*/)
{
GlobalUnassignControlAt(guidInstance, dwOffset);
NullAction(lpac);
}
}
void CDIDeviceActionConfigPage::UnassignControl(CDeviceControl *pControl)
{
if (pControl == NULL)
return;
// make sure the control itself indicates unassignment
pControl->SetCaption(g_tszUnassignedControlCaption);
}
void CallUnassignControl(CDeviceControl *pControl, LPVOID pVoid, BOOL bFixed)
{
CDIDeviceActionConfigPage *pThis = (CDIDeviceActionConfigPage *)pVoid;
pThis->UnassignControl(pControl);
}
void CDIDeviceActionConfigPage::GlobalUnassignControlAt(const GUID &guidInstance, DWORD dwOffset)
{
if (IsEqualGUID(guidInstance, GUID_NULL))
return;
if (IsEqualGUID(guidInstance, m_didi.guidInstance))
m_pDeviceUI->DoForAllControlsAtOffset(dwOffset, CallUnassignControl, this);
}
// this function must find whatever control is assigned to this action and unassign it
void CDIDeviceActionConfigPage::UnassignAction(LPDIACTIONW slpac)
{
// call UnassignSpecificAction for each action with the same name
// as this one, including this one
if (slpac == NULL)
return;
CFTItem *pItem = GetItemWithActionNameAndSemType(slpac->lptszActionName, slpac->dwSemantic);
if (!pItem)
return;
RGLPDIACW *pacs = (RGLPDIACW *)pItem->GetUserData();
if (!pacs)
return;
for (int i = 0; i < pacs->GetSize(); i++)
UnassignSpecificAction(pacs->GetAt(i));
}
void CDIDeviceActionConfigPage::UnassignSpecificAction(LPDIACTIONW lpac)
{
if (lpac == NULL)
return;
if (IsEqualGUID(lpac->guidInstance, GUID_NULL))
return;
// if there's a control with this instance/offset, unassign it
UnassignActionsAssignedTo(lpac->guidInstance, GetOffset(lpac)/*->dwInternalOffset*/);
GlobalUnassignControlAt(lpac->guidInstance, GetOffset(lpac)/*->dwInternalOffset*/);
// now actually null the action
NullAction(lpac);
}
void CDIDeviceActionConfigPage::AssignCurrentControlToAction(LPDIACTIONW lpac)
{
// if there is a control, unassign it
if (m_pCurControl != NULL)
{
UnassignControl(m_pCurControl);
GUID guidInstance;
DWORD dwOffset;
m_pCurControl->GetInfo(guidInstance, dwOffset);
UnassignActionsAssignedTo(guidInstance, dwOffset);
}
// if there is an action, unassign it
if (lpac != NULL)
UnassignAction(lpac);
// can only continue if we have both
if (lpac == NULL || m_pCurControl == NULL)
return;
// here we should have a control and an action
assert(lpac != NULL);
assert(m_pCurControl != NULL);
// because an action can only be assigned to one control,
// make sure this action is unassigned first
UnassignAction(lpac);
// now actually assign
DWORD ofs;
m_pCurControl->GetInfo(lpac->guidInstance, ofs/*lpac->dwInternalOffset*/);
SetOffset(lpac, ofs);
LPTSTR acname = AllocLPTSTR(lpac->lptszActionName);
m_pCurControl->SetCaption(acname, lpac->dwFlags & DIA_APPFIXED);
free(acname);
// Sort the action list if check box is checked
if (m_CheckBox.GetCheck())
{
m_pDeviceUI->GetCurView()->SortAssigned(TRUE);
// Scroll so that we scroll to make this visible since it might be displaced by sorting.
m_pDeviceUI->GetCurView()->ScrollToMakeControlVisible(m_pCurControl->GetCalloutMaxRect());
}
}
void CDIDeviceActionConfigPage::ActionClick(LPDIACTIONW lpac)
{
if (m_pCurControl != NULL)
{
AssignCurrentControlToAction(lpac);
// Set assignment since other views may have the same callout and
// they need to be updated too.
SetControlAssignments();
}
// Change the state back to normal
ExitAssignState();
}
void CDIDeviceActionConfigPage::SetControlAssignments()
{
assert(!IsEqualGUID(m_didi.guidInstance, GUID_NULL));
m_pDeviceUI->SetAllControlCaptionsTo(g_tszUnassignedControlCaption);
if (m_lpDiac == NULL || m_lpDiac->rgoAction == NULL)
return;
DWORD i;
LPDIACTIONW lpac;
for (i = 0, lpac = m_lpDiac->rgoAction; i < m_lpDiac->dwNumActions; i++)
{
lpac = m_lpDiac->rgoAction + i;
if (IsEqualGUID(lpac->guidInstance, GUID_NULL))
continue;
if (!IsEqualGUID(lpac->guidInstance, m_didi.guidInstance))
continue;
LPTSTR acname = AllocLPTSTR(lpac->lptszActionName);
m_pDeviceUI->SetCaptionForControlsAtOffset(GetOffset(lpac)/*->dwInternalOffset*/, acname, lpac->dwFlags & DIA_APPFIXED);
free(acname);
}
}
void CDIDeviceActionConfigPage::DoViewSel()
{
m_ViewSelWnd.Go(m_hWnd, m_rectIB.left, m_rectIB.top, m_pDeviceUI);
}
void CDIDeviceActionConfigPage::OnClick(POINT point, WPARAM, BOOL bLeft)
{
if (!bLeft)
return;
// Unhighlight current callout
ExitAssignState();
if (m_pDeviceUI->GetNumViews() > 1)
{
int iCurView = m_pDeviceUI->GetCurViewIndex();
if (PtInRect(&m_rectIBLeft, point))
m_pDeviceUI->SetView(iCurView == 0 ? m_pDeviceUI->GetNumViews() - 1 : iCurView - 1);
if (PtInRect(&m_rectIBRight, point))
m_pDeviceUI->SetView(iCurView == m_pDeviceUI->GetNumViews() - 1 ? 0 : iCurView + 1);
if (PtInRect(&m_rectIBText, point))
DoViewSel();
}
}
void CDIDeviceActionConfigPage::OnMouseOver(POINT point, WPARAM fwKeys)
{
CFlexWnd::s_ToolTip.SetEnable(FALSE);
// Check view selection area so we can display text in info box.
if (m_pDeviceUI->GetNumViews() > 1)
{
if (PtInRect(&m_rectIB, point))
{
SetInfoText(IDS_INFOMSG_VIEW_VIEWSEL);
return;
}
}
SetAppropriateDefaultText();
}
int GetActionIndexFromPointer(LPDIACTIONW p, LPDIACTIONFORMATW paf)
{
if (!p || !paf || !paf->rgoAction)
return -1;
int index = int((((LPBYTE)p) - ((LPBYTE)paf->rgoAction)) / (DWORD)sizeof(DIACTIONW));
assert(&(paf->rgoAction[index]) == p);
return index;
}
BOOL CDIDeviceActionConfigPage::IsActionAssignedHere(int index)
{
if (!m_lpDiac)
return FALSE;
if (index < 0 || index >= (int)m_lpDiac->dwNumActions)
return FALSE;
return IsEqualGUID(m_didi.guidInstance, m_lpDiac->rgoAction[index].guidInstance);
}
LRESULT CDIDeviceActionConfigPage::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_UNHIGHLIGHT:
// Unhighlight current callout
ExitAssignState();
break;
case WM_KEYDOWN:
#ifdef DBG
// In debug version, shift-escape exits the UI.
if (wParam == VK_ESCAPE && GetAsyncKeyState(VK_SHIFT) < 0)
{
PostMessage(GetParent(m_hWnd), WM_KEYDOWN, wParam, lParam);
break;
}
#endif
// If this is a keyboard device, then click-to-pick will take care of the functionalities below.
// Process WM_KEYDOWN only for non-keyboard devices.
if (LOBYTE(m_didi.dwDevType) == DI8DEVTYPE_KEYBOARD) return 0;
switch(wParam)
{
case VK_RETURN:
// If we are not in assign state, enter it.
if (m_State == CFGSTATE_NORMAL && m_pCurControl)
EnterAssignState();
break;
case VK_DELETE:
// If we are in assign state and there is a control, unassign it.
if (m_State == CFGSTATE_ASSIGN && m_pCurControl)
UnassignCallout();
break;
case VK_ESCAPE:
if (m_State == CFGSTATE_ASSIGN)
ExitAssignState();
break;
}
return 0;
case WM_FLEXCHECKBOX:
switch(wParam)
{
case CHKNOTIFY_UNCHECK:
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
if (!m_pDeviceUI->InEditMode()) // Ignore sort assigned checkbox if in DDK tool
{
#endif
//@@END_MSINTERNAL
m_pDeviceUI->GetCurView()->SortAssigned(FALSE);
if (m_pCurControl)
{
// Scroll so that we scroll to make this visible since it might be displaced by sorting.
m_pDeviceUI->GetCurView()->ScrollToMakeControlVisible(m_pCurControl->GetCalloutMaxRect());
}
Invalidate();
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
}
#endif
//@@END_MSINTERNAL
break;
case CHKNOTIFY_CHECK:
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
if (!m_pDeviceUI->InEditMode()) // Ignore sort assigned checkbox if in DDK tool
{
#endif
//@@END_MSINTERNAL
m_pDeviceUI->GetCurView()->SortAssigned(TRUE);
if (m_pCurControl)
{
// Scroll so that we scroll to make this visible since it might be displaced by sorting.
m_pDeviceUI->GetCurView()->ScrollToMakeControlVisible(m_pCurControl->GetCalloutMaxRect());
}
Invalidate();
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
}
#endif
//@@END_MSINTERNAL
break;
case CHKNOTIFY_MOUSEOVER:
SetInfoText(m_CheckBox.GetCheck() ? IDS_INFOMSG_VIEW_SORTENABLED : IDS_INFOMSG_VIEW_SORTDISABLED);
break;
}
break;
case WM_FLEXCOMBOBOX:
switch (wParam)
{
case FCBN_MOUSEOVER:
if (lParam)
{
CFlexComboBox *pCombo = (CFlexComboBox*)lParam;
if (pCombo->m_hWnd == m_UserNames.m_hWnd)
SetInfoText(m_puig->InEditMode() ? IDS_INFOMSG_EDIT_USERNAME : IDS_INFOMSG_VIEW_USERNAME);
else if (pCombo->m_hWnd == m_Genres.m_hWnd)
SetInfoText(m_puig->InEditMode() ? IDS_INFOMSG_EDIT_GAMEMODE : IDS_INFOMSG_VIEW_GAMEMODE);
}
break;
case FCBN_SELCHANGE:
// Clear the tool tip as the combo-box has closed
CFlexWnd::s_ToolTip.SetEnable(FALSE);
CFlexWnd::s_ToolTip.SetToolTipParent(NULL);
if (m_pUIFrame && m_puig)
{
ExitAssignState();
m_pUIFrame->SetCurGenre(m_Genres.GetSel());
int nUser = m_UserNames.GetSel();
if (m_puig->GetNumUserNames() > 0 && nUser >= m_puig->GetNumUserNames())
nUser = -1;
m_pUIFrame->SetCurUser(m_nPageIndex, nUser);
}
break;
}
return 0;
case WM_FLEXTREENOTIFY:
{
// Check if this is a mouse over message (just for info box update)
if (wParam == FTN_MOUSEOVER)
{
SetAppropriateDefaultText();
return FALSE;
}
if (!lParam)
return FALSE;
FLEXTREENOTIFY &n = *((FLEXTREENOTIFY *)(LPVOID)lParam);
if (!n.pItem)
return FALSE;
switch (wParam)
{
case FTN_OWNERDRAW:
{
POINT ofs = {0, 0};
CBitmap *pbmGlyph = NULL;
BOOL bAssigned = FALSE, bAssignedHere = FALSE;
if (n.pItem->IsUserGUID(GUID_ActionItem))
{
LPDIACTIONW lpac = GetItemLpac(n.pItem, 0);
if (lpac)
// We now walk through each DIACTION and find those with action name match, then see if
// they are assigned anywhere.
for (DWORD i = 0; i < m_lpDiac->dwNumActions; ++i)
{
if (wcscmp(lpac->lptszActionName, m_lpDiac->rgoAction[i].lptszActionName))
continue;
if (bAssignedHere = IsActionAssignedHere(i))
{
bAssigned = TRUE;
break;
}
if (m_pUIFrame && m_pUIFrame->QueryActionAssignedAnywhere(m_didi.guidInstance, i) == S_OK)
bAssigned = TRUE;
}
if (bAssigned || bAssignedHere)
{
pbmGlyph = bAssignedHere ? m_pbmCheckGlyph :
m_pbmCheckGlyphDark;
pbmGlyph->FigureSize();
ofs.x = 2;
ofs.y = 4;
}
}
else
{
if (n.pItem == m_pRelAxesParent)
pbmGlyph = m_pbmRelAxesGlyph;
if (n.pItem == m_pAbsAxesParent)
pbmGlyph = m_pbmAbsAxesGlyph;
if (n.pItem == m_pButtonParent)
pbmGlyph = m_pbmButtonGlyph;
if (n.pItem == m_pHatParent)
pbmGlyph = m_pbmHatGlyph;
ofs.y = 2;
}
if (!pbmGlyph)
return FALSE;
n.pItem->PaintInto(n.hDC);
RECT rect;
CPaintHelper ph(*m_puig, n.hDC);
ph.SetElement(UIE_GLYPH);
n.pItem->GetMargin(rect);
pbmGlyph->Draw(n.hDC, ofs.x, rect.top + ofs.y);
return TRUE;
}
case FTN_CLICK:
// We cannot assign a different control to this action if it has the DIA_APPFIXED flag.
if (n.pItem->IsUserGUID(GUID_ActionItem) && GetItemLpac(n.pItem) && !(GetItemLpac(n.pItem)->dwFlags & DIA_APPFIXED))
{
m_Tree.SetCurSel(n.pItem);
ActionClick(GetItemLpac(n.pItem));
}
else
{
#ifdef CFGUI__ALLOW_USER_ACTION_TREE_BRANCH_MANIPULATION
if (!n.pItem->IsExpanded())
n.pItem->Expand();
else
n.pItem->Collapse();
#endif
}
break;
}
break;
}
}
return CFlexWnd::WndProc(hWnd, msg, wParam, lParam);
}
void CDIDeviceActionConfigPage::SetInvalid(LPDIACTIONW lpac)
{
lpac->guidInstance = GUID_NULL;
lpac->dwObjID = (DWORD)-1;
}
DWORD CDIDeviceActionConfigPage::GetOffset(LPDIACTIONW lpac)
{
return lpac ? lpac->dwObjID : (DWORD)-1;
}
void CDIDeviceActionConfigPage::SetOffset(LPDIACTIONW lpac, DWORD ofs)
{
assert(lpac != NULL);
if (!lpac)
return;
lpac->dwObjID = ofs;
}
HRESULT CDIDeviceActionConfigPage::InitLookup()
{
DIDEVOBJSTRUCT os;
HRESULT hresult = FillDIDeviceObjectStruct(os, m_lpDID);
if (FAILED(hresult))
return hresult;
for (int i = 0; i < os.nObjects; i++)
{
DIDEVICEOBJECTINSTANCEW &doi = os.pdoi[i];
offset_objid.add(doi.dwOfs, doi.dwType);
}
return S_OK;
}
HRESULT CDIDeviceActionConfigPage::SetEditLayout(BOOL bEditLayout)
{
m_pDeviceUI->SetEditMode(bEditLayout);
return S_OK;
}
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
HRESULT CDIDeviceActionConfigPage::WriteIHVSetting()
{
m_pDeviceUI->WriteToINI();
return S_OK;
}
#endif
//@@END_MSINTERNAL
BOOL CDIDeviceActionConfigPage::IsControlMapped(CDeviceControl *pControl)
{
if (pControl == NULL)
return FALSE;
if (!pControl->IsOffsetAssigned())
return FALSE;
if (m_lpDiac == NULL)
return FALSE;
for (DWORD i = 0; i < m_lpDiac->dwNumActions; i++)
if (GetOffset(&(m_lpDiac->rgoAction[i])) == pControl->GetOffset())
return TRUE;
return FALSE;
}
void CDIDeviceActionConfigPage::InitDevice()
{
if (m_lpDID == NULL || m_pDeviceUI == NULL || m_pUIFrame == NULL)
return;
HWND hWndMain = m_pUIFrame->GetMainHWND();
if (!hWndMain)
return;
// don't do anything if this is a mouse
switch ((DWORD)(LOBYTE(LOWORD(m_pDeviceUI->m_didi.dwDevType))))
{
case DI8DEVTYPE_MOUSE:
return;
}
// init/prepare...
int i;
const DIDEVOBJSTRUCT &os = m_pDeviceUI->m_os;
int nObjects = os.nObjects;
DIDATAFORMAT df;
df.dwSize = sizeof(DIDATAFORMAT);
df.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
df.dwFlags = DIDF_ABSAXIS;
df.dwDataSize = sizeof(DWORD) * (DWORD)nObjects;
df.dwNumObjs = (DWORD)nObjects;
df.rgodf = (DIOBJECTDATAFORMAT *)malloc(sizeof(DIOBJECTDATAFORMAT) * nObjects);
if (df.rgodf == NULL)
{
etrace1(_T("Could not allocate DIOBJECTDATAFORMAT array of %d elements\n"), nObjects);
return;
}
m_cbDeviceDataSize = df.dwDataSize;
for (int c = 0; c < 2; c++)
{
if (m_pDeviceData[c] != NULL)
free(m_pDeviceData[c]);
m_pDeviceData[c] = (DWORD *)malloc(m_cbDeviceDataSize);
if (m_pDeviceData[c] == NULL)
etrace2(_T("Could not allocate device data buffer %d of %d bytes\n"), c, m_cbDeviceDataSize);
}
m_nOnDeviceData = 0;
m_bFirstDeviceData = TRUE;
for (i = 0; i < nObjects; i++)
{
DIOBJECTDATAFORMAT *podf = &(df.rgodf[i]);
podf->pguid = NULL;
podf->dwOfs = i * sizeof(DWORD);
podf->dwType = os.pdoi[i].dwType;
podf->dwFlags = 0;
}
if (df.rgodf != NULL)
{
HRESULT hr = m_lpDID->SetDataFormat(&df);
free(df.rgodf);
df.rgodf = NULL;
if (FAILED(hr))
{
etrace1(_T("SetDataFormat() failed, returning 0x%08x\n"), hr);
}
else
{
hr = m_lpDID->SetCooperativeLevel(hWndMain, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
if (FAILED(hr))
etrace1(_T("SetCooperativeLevel() failed, returning 0x%08x\n"), hr);
DIPROPRANGE range;
range.diph.dwSize = sizeof(DIPROPRANGE);
range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
range.diph.dwObj = 0;
range.diph.dwHow = DIPH_DEVICE;
range.lMin = DEVICE_POLLING_AXIS_MIN;
range.lMax = DEVICE_POLLING_AXIS_MAX;
hr = m_lpDID->SetProperty(DIPROP_RANGE, (LPCDIPROPHEADER)&range);
if (FAILED(hr))
etrace1(_T("SetProperty(DIPROP_RANGE, ...) failed, returning 0x%08x\n"), hr);
hr = m_lpDID->Acquire();
if (FAILED(hr))
etrace1(_T("Acquire() failed, returning 0x%08x\n"), hr);
}
}
}
void CALLBACK CDIDeviceActionConfigPage::DeviceTimerProc(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
if (!IsWindow((HWND)dwUser)) return; // Verify that dwUser is a valid window handle
CDIDeviceActionConfigPage *pPage = (CDIDeviceActionConfigPage *)GetFlexWnd((HWND)dwUser); // Get flex object
if (pPage)
pPage->DeviceTimer();
}
void CDIDeviceActionConfigPage::DeviceTimer()
{
DWORD *pOldData = m_pDeviceData[m_nOnDeviceData];
m_nOnDeviceData = (m_nOnDeviceData + 1) & 1;
DWORD *pData = m_pDeviceData[m_nOnDeviceData];
if (m_lpDID == NULL || pData == NULL || pOldData == NULL)
{
// Required data not available. Return and there'll be no more timer callbacks.
etrace(_T("DeviceTimer() failed\n"));
return;
}
// Get device data only if this page is visible.
if (m_lpDiac)
{
HRESULT hr = m_lpDID->Poll();
if (SUCCEEDED(hr))
{
hr = m_lpDID->GetDeviceState(m_cbDeviceDataSize, pData);
if (SUCCEEDED(hr))
{
if (!m_bFirstDeviceData)
{
DeviceDelta(pData, pOldData);
} else
{
m_bFirstDeviceData = FALSE;
}
} else
{
etrace1(_T("GetDeviceState() failed, returning 0x%08x\n"), hr);
}
} else
{
etrace1(_T("Poll() failed, returning 0x%08x\n"), hr);
}
}
// Set the next timer event.
if (g_fptimeSetEvent)
g_fptimeSetEvent(DEVICE_POLLING_INTERVAL, DEVICE_POLLING_INTERVAL,
CDIDeviceActionConfigPage::DeviceTimerProc, (DWORD_PTR)m_hWnd, TIME_ONESHOT);
}
void CDIDeviceActionConfigPage::DeviceDelta(DWORD *pData, DWORD *pOldData)
{
if (pData == NULL || pOldData == NULL || m_pDeviceUI == NULL)
return;
const DIDEVOBJSTRUCT &os = m_pDeviceUI->m_os;
// see which objects changed
for (int i = 0; i < os.nObjects; i++)
{
// for axes, we need to do special processing
if (os.pdoi[i].dwType & DIDFT_AXIS)
{
BOOL bSig = FALSE, bOldSig = FALSE;
StoreAxisDeltaAndCalcSignificance(os.pdoi[i],
pData[i], pOldData[i], bSig, bOldSig);
AxisDelta(os.pdoi[i], bSig, bOldSig);
continue;
}
// for all others, skip that which didn't change
if (pData[i] == pOldData[i])
continue;
// pass to appropriate delta function
DWORD dwObjId = os.pdoi[i].dwType;
if (dwObjId & DIDFT_BUTTON)
ButtonDelta(os.pdoi[i], pData[i], pOldData[i]);
else if (dwObjId & DIDFT_POV)
PovDelta(os.pdoi[i], pData[i], pOldData[i]);
}
}
void CDIDeviceActionConfigPage::StoreAxisDeltaAndCalcSignificance(const DIDEVICEOBJECTINSTANCEW &doi, DWORD data, DWORD olddata, BOOL &bSig, BOOL &bOldSig)
{
// see if this object has an axis value array
int i;
if (objid_avai.getright(doi.dwType, i))
{
AxisValueArray &ar = m_AxisValueArray[i];
int on = ar[0] + 1;
if (on >= ar.GetSize())
on = DEVICE_POLLING_ACBUF_START_INDEX;
ar[0] = on;
int delta = abs(int(data) - int(olddata));
// Scale up the delta if this is a wheel axis as wheels are harder to move generally.
if (LOBYTE(m_didi.dwDevType) == DI8DEVTYPE_DRIVING && doi.guidType == GUID_XAxis)
delta = delta * DEVICE_POLLING_WHEEL_SCALE_FACTOR;
if (delta < DEVICE_POLLING_AXIS_MINDELTA)
delta = 0;
int cumul = ar[1]; // Retrieve cumulative value for easier processing
cumul -= ar[on]; // Subtract value in current slot from cumul since it's being thrown away.
cumul += delta; // Add current delta to cumul
ar[on] = delta; // Store the delta at current slot
ar[1] = cumul; // Save cumulative value
bOldSig = (BOOL)ar[2];
ar[2] = int(bSig = cumul > DEVICE_POLLING_AXIS_SIGNIFICANT);
if (bSig)
{
// This axis is about to be activated. We now reset the history and cumulative movement since we don't need them any more.
ar[0] = DEVICE_POLLING_ACBUF_START_INDEX;
ar[1] = 0;
ar[2] = FALSE;
for (int c = DEVICE_POLLING_ACBUF_START_INDEX;
c < DEVICE_POLLING_ACBUF_START_INDEX + DEVICE_POLLING_AXIS_ACCUMULATION; c++)
ar[c] = 0;
}
}
else
{
i = m_AxisValueArray.GetSize();
m_AxisValueArray.SetSize(i + 1);
objid_avai.add(doi.dwType, i);
AxisValueArray &ar = m_AxisValueArray[i];
ar.SetSize(DEVICE_POLLING_ACBUF_START_INDEX + DEVICE_POLLING_AXIS_ACCUMULATION);
ar[0] = DEVICE_POLLING_ACBUF_START_INDEX;
ar[1] = 0;
ar[2] = FALSE;
for (int c = DEVICE_POLLING_ACBUF_START_INDEX;
c < DEVICE_POLLING_ACBUF_START_INDEX + DEVICE_POLLING_AXIS_ACCUMULATION; c++)
ar[c] = 0;
bOldSig = bSig = FALSE;
}
}
void CDIDeviceActionConfigPage::AxisDelta(const DIDEVICEOBJECTINSTANCEW &doi, BOOL data, BOOL old)
{
if (data && !old)
{
if (m_State == CFGSTATE_NORMAL)
ActivateObject(doi);
}
if (old && !data)
DeactivateObject(doi);
}
void CDIDeviceActionConfigPage::ButtonDelta(const DIDEVICEOBJECTINSTANCEW &doi, DWORD data, DWORD old)
{
static DWORD dwLastOfs;
static DWORD dwLastTimeStamp;
if (data && !old)
{
// Do special processing for keyboard
if (LOBYTE(m_didi.dwDevType) == DI8DEVTYPE_KEYBOARD)
{
// If this is an ENTER key, we enter the assign state if not already in it.
if (doi.dwOfs == DIK_RETURN || doi.dwOfs == DIK_NUMPADENTER)
{
if (m_State == CFGSTATE_NORMAL && m_pCurControl)
EnterAssignState();
return; // Do nothing other than entering the assign state. No highlighting
}
// DELETE key case
// If we are in assign state and there is a control, unassign it.
if (doi.dwOfs == DIK_DELETE && m_State == CFGSTATE_ASSIGN && m_pCurControl)
{
UnassignCallout();
return; // Don't highlight or do pick to click for delete if this press happens during assign state.
}
// ESCAPE key case
if (doi.dwOfs == DIK_ESCAPE && m_State == CFGSTATE_ASSIGN)
{
ExitAssignState();
return;
}
// For all other keys, still process click-to-pick or highlighting.
}
// Enter assign state if this is a double activation
if (m_State == CFGSTATE_NORMAL)
{
ActivateObject(doi);
if (doi.dwOfs == dwLastOfs && dwLastTimeStamp + GetDoubleClickTime() > GetTickCount())
{
// We check if a callout for this control exists. If not, do not enter assign state.
CDeviceView *pCurView = m_pDeviceUI->GetCurView();
CDeviceControl *pControl = pCurView->GetControlFromOfs(doi.dwType);
if (pControl)
EnterAssignState();
}
dwLastOfs = doi.dwOfs;
dwLastTimeStamp = GetTickCount();
}
}
if (old && !data)
DeactivateObject(doi);
}
void CDIDeviceActionConfigPage::PovDelta(const DIDEVICEOBJECTINSTANCEW &doi, DWORD data, DWORD old)
{
BOOL d = data != -1, o = old != -1;
if (d && !o)
{
if (m_State == CFGSTATE_NORMAL)
ActivateObject(doi);
}
if (o && !d)
DeactivateObject(doi);
}
void CDIDeviceActionConfigPage::ActivateObject(const DIDEVICEOBJECTINSTANCEW &doi)
{
if (m_pDeviceUI == NULL)
return;
//@@BEGIN_MSINTERNAL
#ifdef DDKBUILD
if (m_pDeviceUI->GetCurView()->InEditState())
return;
#endif
//@@END_MSINTERNAL
CDeviceView *pCurView = m_pDeviceUI->GetCurView(), *pView = pCurView;
if (pView == NULL)
return;
CDeviceControl *pControl = pView->GetControlFromOfs(doi.dwType);
if (pControl == NULL)
{
for (int i = 0; i < m_pDeviceUI->GetNumViews(); i++)
{
pView = m_pDeviceUI->GetView(i);
if (pView == NULL)
continue;
pControl = pView->GetControlFromOfs(doi.dwType);
if (pControl != NULL)
break;
}
if (pControl != NULL && pView != NULL && pView != pCurView)
{
// switch to view
m_pDeviceUI->SetView(pView);
SetControlAssignments();
SetCurrentControl(NULL);
}
}
if (pControl != NULL)
SetCurrentControl(pControl);
SetAppropriateDefaultText();
}
void CDIDeviceActionConfigPage::DeactivateObject(const DIDEVICEOBJECTINSTANCEW &doi)
{
// Add code that needs to be run when deactivating here.
}
HRESULT CDIDeviceActionConfigPage::Unacquire()
{
if (m_lpDID != NULL)
m_lpDID->Unacquire();
return S_OK;
}
HRESULT CDIDeviceActionConfigPage::Reacquire()
{
InitDevice();
return S_OK;
}
void CDIDeviceActionConfigPage::EnterAssignState()
{
if (!m_puig->InEditMode())
return;
if (!m_pCurControl || m_pCurControl->IsFixed())
return;
SetInfoText(IDS_INFOMSG_EDIT_EDITMODEENABLED);
m_State = CFGSTATE_ASSIGN; // Into the assign state.
ShowCurrentControlAssignment(); // Show the tree
m_Tree.Invalidate();
Invalidate();
}
void CDIDeviceActionConfigPage::ExitAssignState()
{
m_State = CFGSTATE_NORMAL; // Out of the assign state.
SetCurrentControl(NULL); // Unselect the control
ShowCurrentControlAssignment(); // Show the tree
m_Tree.Invalidate();
Invalidate();
SetAppropriateDefaultText();
}
HRESULT CDIDeviceActionConfigPage::SetInfoText(int iCode)
{
// We check for special code -1 here. This is only called by CConfigWnd, and means that we should
// call SetAppropriateDefaultText to display proper text.
if (iCode == -1)
SetAppropriateDefaultText();
else
m_InfoBox.SetText(iCode);
return S_OK;
}
void CDIDeviceActionConfigPage::SetAppropriateDefaultText()
{
if (m_puig->InEditMode())
{
if (m_State == CFGSTATE_ASSIGN)
SetInfoText(IDS_INFOMSG_EDIT_EDITMODEENABLED);
else if (m_pCurControl)
{
if (m_pCurControl->IsFixed())
SetInfoText(IDS_INFOMSG_APPFIXEDSELECT);
else
SetInfoText(IDS_INFOMSG_EDIT_CTRLSELECTED);
}
else
{
if (LOBYTE(m_didi.dwDevType) == DI8DEVTYPE_KEYBOARD)
SetInfoText(IDS_INFOMSG_EDIT_KEYBOARD);
else
if (LOBYTE(m_didi.dwDevType) == DI8DEVTYPE_MOUSE)
SetInfoText(IDS_INFOMSG_EDIT_MOUSE);
else
SetInfoText(IDS_INFOMSG_EDIT_DEVICE);
}
} else
SetInfoText(IDS_INFOMSG_VIEW_DEVICE);
}