//----------------------------------------------------------------------------- // 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 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_lpDID(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); }