diff --git a/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.cpp b/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.cpp index 313316328..4c6d52e05 100644 --- a/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.cpp +++ b/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.cpp @@ -135,6 +135,8 @@ int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpstrC FARPROC proc=GetProcAddress(dll,"ShowExplorerSettings"); if (!proc) return 2; + HRESULT comResult=CoInitialize(NULL); proc(); + if (SUCCEEDED(comResult)) CoUninitialize(); return 0; } diff --git a/Src/ClassicIE/ClassicIE.cpp b/Src/ClassicIE/ClassicIE.cpp index 9850aaef1..c45eed7ff 100644 --- a/Src/ClassicIE/ClassicIE.cpp +++ b/Src/ClassicIE/ClassicIE.cpp @@ -252,6 +252,8 @@ int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdL return 0; } + HRESULT comResult=CoInitialize(NULL); ShowIESettings(); + if (SUCCEEDED(comResult)) CoUninitialize(); return 0; } diff --git a/Src/Lib/Lib.rc b/Src/Lib/Lib.rc index d34dbaf46..44432a16a 100644 --- a/Src/Lib/Lib.rc +++ b/Src/Lib/Lib.rc @@ -70,7 +70,7 @@ BEGIN EDITTEXT IDC_EDITSEARCH,22,7,120,12,ES_AUTOHSCROLL CONTROL "Show all settings",IDC_CHECKALL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,7,79,12 CONTROL "Help...",IDC_LINKHELP,"SysLink",WS_TABSTOP,348,9,26,10,WS_EX_TRANSPARENT - CONTROL "",IDC_TABSETTINGS,"SysTabControl32",TCS_MULTILINE | TCS_FOCUSNEVER,7,20,367,169 + CONTROL "",IDC_TABSETTINGS,"SysTabControl32",TCS_MULTILINE | WS_TABSTOP,7,20,367,169 CONTROL "Open-Shell Homepage",IDC_LINKWEB,"SysLink",WS_TABSTOP,7,195,75,10,WS_EX_TRANSPARENT CONTROL "Name of translator goes here",IDC_SYSLINKLOC, "SysLink",NOT WS_VISIBLE | WS_TABSTOP,80,195,111,10 @@ -282,7 +282,9 @@ END STRINGTABLE BEGIN - IDS_INITIATE_FAIL "Failed to initiate the download." + IDS_SETTINGS_PAGES "Settings pages" + IDS_SETTING_MODIFIED "modified" + IDS_INITIATE_FAIL "Failed to initiate the download." IDS_UPDATE_TITLE "Open-Shell Update" IDS_INST_DOWNLOAD_FAIL "Failed to download the new version." IDS_INST_SAVE_FAIL "Failed to save file '%s'." diff --git a/Src/Lib/Settings.cpp b/Src/Lib/Settings.cpp index 599cb9fed..b7205c78e 100644 --- a/Src/Lib/Settings.cpp +++ b/Src/Lib/Settings.cpp @@ -1119,6 +1119,7 @@ class CSettingsDlg: public CResizeableDlg MESSAGE_HANDLER( WM_KEYDOWN, OnKeyDown ) MESSAGE_HANDLER( WM_SYSCOMMAND, OnSysCommand ) MESSAGE_HANDLER( WM_CLEAR, OnResetUI ) + MESSAGE_HANDLER( WM_APP+37, OnAccessibleTabOrder ) COMMAND_HANDLER( IDOK, BN_CLICKED, OnOK ) COMMAND_HANDLER( IDCANCEL, BN_CLICKED, OnCancel ) COMMAND_HANDLER( IDC_BUTTONBACKUP, BN_CLICKED, OnBackup ) @@ -1159,6 +1160,7 @@ class CSettingsDlg: public CResizeableDlg LRESULT OnKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnSysCommand( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnResetUI( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); + LRESULT OnAccessibleTabOrder( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ); LRESULT OnOK( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled ); LRESULT OnCancel( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled ); LRESULT OnBackup( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled ); @@ -1186,7 +1188,8 @@ class CSettingsDlg: public CResizeableDlg const wchar_t* m_AppId; void AddTabs( int name, const CSetting *pSelect=NULL ); - void SetCurTab( int index, bool bReset, const CSetting *pSelect=NULL ); + void SetCurTab( int index, bool bReset, const CSetting *pSelect=NULL, bool bFocusPanel=true ); + void SetAccessibleTabOrder( void ); bool IsTabValid( void ); void StorePlacement( void ); @@ -1312,6 +1315,7 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO icon=(HICON)LoadImage(GetModuleHandle(L"shell32.dll"),MAKEINTRESOURCE(323),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),LR_DEFAULTCOLOR); SendDlgItemMessage(IDC_ICONSEARCH,STM_SETICON,(WPARAM)icon); SendDlgItemMessage(IDC_EDITSEARCH,EM_SETCUEBANNER,FALSE,(LPARAM)(const wchar_t*)LoadStringEx(IDS_SEARCH_PROMPT)); + SetControlAccessibleName(GetDlgItem(IDC_EDITSEARCH),LoadStringEx(IDS_SEARCH_PROMPT)); SetWindowSubclass(GetDlgItem(IDC_EDITSEARCH),SubclassSearchBoxProc,'CLSH',0); CWindow tooltip; @@ -1364,6 +1368,7 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO SIZE maxSize={0,0}; m_Tabs=GetDlgItem(IDC_TABSETTINGS); + m_Tabs.SetWindowText(LoadStringEx(IDS_SETTINGS_PAGES)); m_Panel=NULL; int idx=0; for (const CSetting *pSetting=m_pSettings;pSetting->name;pSetting++) @@ -1411,6 +1416,7 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO if (m_InitialTab) pos.tab=m_InitialTab; AddTabs(pos.tab); + PostMessage(WM_APP+37); if (pos.tab && bPosValid) { SetStoreRect(pos.rc); @@ -1418,7 +1424,8 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO ShowWindow(SW_MAXIMIZE); } - return TRUE; + GetDlgItem(IDC_BUTTONBACKUP).SetFocus(); + return FALSE; } LRESULT CSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) @@ -1477,6 +1484,14 @@ void CSettingsDlg::AddTabs( int name, const CSetting *pSelect ) if (pSetting->nameID==name) idx=i; } + int tabCount=TabCtrl_GetItemCount(m_Tabs); + LONG_PTR tabStyle=m_Tabs.GetWindowLongPtr(GWL_STYLE); + if (tabCount<=1) + tabStyle=(tabStyle&~WS_TABSTOP)|TCS_FOCUSNEVER; + else + tabStyle=(tabStyle|WS_TABSTOP)&~TCS_FOCUSNEVER; + m_Tabs.SetWindowLongPtr(GWL_STYLE,tabStyle); + m_Tabs.SetWindowPos(NULL,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_FRAMECHANGED); m_Index=-1; TabCtrl_SetCurSel(m_Tabs,idx); m_Tabs.InvalidateRect(NULL); @@ -1499,20 +1514,23 @@ LRESULT CSettingsDlg::OnKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& if (wParam==VK_TAB && GetKeyState(VK_CONTROL)<0) { int sel=TabCtrl_GetCurSel(m_Tabs); + int count=TabCtrl_GetItemCount(m_Tabs); if (GetKeyState(VK_SHIFT)<0) { - if (sel>0) + if (count>0) { - TabCtrl_SetCurSel(m_Tabs,sel-1); - SetCurTab(sel-1,false); + int next=(sel+count-1)%count; + TabCtrl_SetCurSel(m_Tabs,next); + SetCurTab(next,false); } } else { - if (sel0) { - TabCtrl_SetCurSel(m_Tabs,sel+1); - SetCurTab(sel+1,false); + int next=(sel+1)%count; + TabCtrl_SetCurSel(m_Tabs,next); + SetCurTab(next,false); } } } @@ -1740,6 +1758,7 @@ LRESULT CSettingsDlg::OnCheckAll( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL { m_bBasic=bBasic; AddTabs(-1); + ::SetFocus(hWndCtl); } return 0; } @@ -1758,7 +1777,7 @@ LRESULT CSettingsDlg::OnSearchChange( WORD wNotifyCode, WORD wID, HWND hWndCtl, return 0; } -void CSettingsDlg::SetCurTab( int index, bool bReset, const CSetting *pSelect ) +void CSettingsDlg::SetCurTab( int index, bool bReset, const CSetting *pSelect, bool bFocusPanel ) { if (m_Index==index && !bReset) return; m_Index=index; @@ -1778,11 +1797,46 @@ void CSettingsDlg::SetCurTab( int index, bool bReset, const CSetting *pSelect ) ::MapWindowPoints(NULL,m_hWnd,(POINT*)&rc,2); TabCtrl_AdjustRect(m_Tabs,FALSE,&rc); HWND hwnd=pPanel->Activate(pGroup,rc,bReset); + CString pageTitle=LoadStringEx(pGroup->nameID); + ::SetWindowText(hwnd,pageTitle); if (hwnd!=m_Panel) { if (m_Panel) ::ShowWindow(m_Panel,SW_HIDE); m_Panel=hwnd; - ::SetFocus(m_Panel); + } + PostMessage(WM_APP+37); + if (bFocusPanel) ::SetFocus(m_Panel); +} + +LRESULT CSettingsDlg::OnAccessibleTabOrder( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) +{ + SetAccessibleTabOrder(); + return 0; +} + +void CSettingsDlg::SetAccessibleTabOrder( void ) +{ + // Tab order: Backup, Show all, Search, tabs, page, OK, Cancel, + // Help, Homepage. Reapply after changing pages because panels are dynamic. + HWND previous=HWND_TOP; + const int controls[]={IDC_BUTTONBACKUP,IDC_CHECKALL,IDC_EDITSEARCH,IDC_TABSETTINGS}; + for (int i=0;i<_countof(controls);i++) + { + HWND control=GetDlgItem(controls[i]); + ::SetWindowPos(control,previous,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); + previous=control; + } + if (m_Panel) + { + ::SetWindowPos(m_Panel,previous,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); + previous=m_Panel; + } + const int trailing[]={IDOK,IDCANCEL,IDC_LINKHELP,IDC_LINKWEB}; + for (int i=0;i<_countof(trailing);i++) + { + HWND control=GetDlgItem(trailing[i]); + ::SetWindowPos(control,previous,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); + previous=control; } } @@ -1793,7 +1847,9 @@ LRESULT CSettingsDlg::OnSelChanging( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ) LRESULT CSettingsDlg::OnSelChange( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ) { - SetCurTab(TabCtrl_GetCurSel(m_Tabs),false); + // Mouse clicks and Left/Right navigation keep focus on the tab strip. + // Ctrl+Tab is handled separately and moves focus into the new page. + SetCurTab(TabCtrl_GetCurSel(m_Tabs),false,NULL,false); return 0; } diff --git a/Src/Lib/SettingsUIHelper.cpp b/Src/Lib/SettingsUIHelper.cpp index 9b988e282..66d10ab95 100644 --- a/Src/Lib/SettingsUIHelper.cpp +++ b/Src/Lib/SettingsUIHelper.cpp @@ -16,6 +16,183 @@ #include #include #include +#include + +// Local constants avoid forcing every executable that links the Lib static +// library to define the oleacc GUID symbols. +static const GUID CLSID_SettingsAccPropServices={0xb5f8350b,0x0548,0x48b1,{0xa6,0xee,0x88,0xbd,0x00,0xb4,0xa5,0xe7}}; +static const GUID PROPID_SettingsAccState={0xa8d4d5b0,0x0a21,0x42d0,{0xa5,0xc0,0x51,0x4e,0x98,0x4f,0x45,0x7b}}; +static const GUID PROPID_SettingsAccName={0x608d3df8,0x8128,0x4aa7,{0xa4,0x28,0xf5,0x5e,0x49,0x26,0x72,0x91}}; +static const GUID PROPID_SettingsAccRole={0xcb905ff2,0x7bd1,0x4c05,{0xb3,0xc8,0xe6,0xc2,0x41,0x36,0x4d,0x70}}; + +class CSettingsTreeAccServer: public IAccPropServer +{ +public: + CSettingsTreeAccServer( void ): m_RefCount(1) + { + CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&m_Props)); + } + + virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID iid, void **object ) + { + if (!object) return E_POINTER; + *object=NULL; + if (iid==IID_IUnknown || iid==IID_IAccPropServer) + { + *object=static_cast(this); + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + virtual ULONG STDMETHODCALLTYPE AddRef( void ) { return InterlockedIncrement(&m_RefCount); } + virtual ULONG STDMETHODCALLTYPE Release( void ) + { + ULONG count=InterlockedDecrement(&m_RefCount); + if (!count) delete this; + return count; + } + virtual HRESULT STDMETHODCALLTYPE GetPropValue( const BYTE *identity, DWORD identitySize, MSAAPROPID prop, VARIANT *value, BOOL *hasProp ) + { + if (!value || !hasProp) return E_POINTER; + VariantInit(value); + *hasProp=FALSE; + if (!m_Props) return S_OK; + + HWND tree=NULL; + DWORD objectId=0, childId=0; + if (FAILED(m_Props->DecomposeHwndIdentityString(identity,identitySize,&tree,&objectId,&childId)) || objectId!=OBJID_CLIENT || childId==CHILDID_SELF) + return S_OK; + HTREEITEM hItem=TreeView_MapAccIDToHTREEITEM(tree,childId); + if (!hItem) return S_OK; + + wchar_t text[256]; + TVITEM item={TVIF_IMAGE|TVIF_STATE|TVIF_TEXT,hItem,0,TVIS_SELECTED|TVIS_EXPANDED|TVIS_CUT,text,_countof(text)}; + if (!TreeView_GetItem(tree,&item)) return S_OK; + if (prop==PROPID_SettingsAccRole) + { + if (item.iImage&SETTING_STATE_CHECKBOX) + value->lVal=ROLE_SYSTEM_CHECKBUTTON; + else if (item.iImage&SETTING_STATE_RADIO) + value->lVal=ROLE_SYSTEM_RADIOBUTTON; + else + return S_OK; + value->vt=VT_I4; + *hasProp=TRUE; + return S_OK; + } + if (prop==PROPID_SettingsAccName) + { + // A sighted user can see which child choice is checked without + // opening the group. Give screen-reader users the same summary. + for (HTREEITEM child=TreeView_GetChild(tree,hItem);child;child=TreeView_GetNextSibling(tree,child)) + { + wchar_t childText[256]; + TVITEM childItem={TVIF_IMAGE|TVIF_TEXT,child,0,0,childText,_countof(childText)}; + if (TreeView_GetItem(tree,&childItem) && (childItem.iImage&SETTING_STATE_CHECKED)) + { + CString name; + name.Format(L"%s: %s",text,childText); + value->vt=VT_BSTR; + value->bstrVal=SysAllocString(name); + if (!value->bstrVal) return E_OUTOFMEMORY; + *hasProp=TRUE; + return S_OK; + } + } + return S_OK; + } + if (prop!=PROPID_SettingsAccState) return S_OK; + LONG state=STATE_SYSTEM_SELECTABLE|STATE_SYSTEM_FOCUSABLE; + if (item.state&TVIS_SELECTED) + { + state|=STATE_SYSTEM_SELECTED; + if (GetFocus()==tree) state|=STATE_SYSTEM_FOCUSED; + } + if (TreeView_GetChild(tree,hItem)) + state|=(item.state&TVIS_EXPANDED)?STATE_SYSTEM_EXPANDED:STATE_SYSTEM_COLLAPSED; + if (item.state&TVIS_CUT || (item.iImage&SETTING_STATE_DISABLED)) + state|=STATE_SYSTEM_UNAVAILABLE; + if ((item.iImage&(SETTING_STATE_CHECKBOX|SETTING_STATE_RADIO)) && (item.iImage&SETTING_STATE_CHECKED)) + state|=STATE_SYSTEM_CHECKED; + RECT rect; + if (!TreeView_GetItemRect(tree,hItem,&rect,FALSE)) + state|=STATE_SYSTEM_INVISIBLE; + + value->vt=VT_I4; + value->lVal=state; + *hasProp=TRUE; + return S_OK; + } + +private: + LONG m_RefCount; + CComPtr m_Props; +}; + +void EnableSettingsTreeAccessibility( HWND tree ) +{ + CComPtr props; + if (FAILED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) return; + CSettingsTreeAccServer *server=new CSettingsTreeAccServer; + MSAAPROPID property=PROPID_SettingsAccState; + props->SetHwndPropServer(tree,OBJID_CLIENT,CHILDID_SELF,&property,1,server,ANNO_CONTAINER); + server->Release(); +} + +void ClearSettingsTreeItemAccessibility( HWND tree ) +{ + CComPtr props; + if (FAILED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) return; + MSAAPROPID properties[]={PROPID_SettingsAccState,PROPID_SettingsAccName,PROPID_SettingsAccRole}; + for (HTREEITEM item=TreeView_GetRoot(tree);item;) + { + props->ClearHwndProps(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),properties,_countof(properties)); + HTREEITEM next=TreeView_GetChild(tree,item); + if (!next) + { + next=TreeView_GetNextSibling(tree,item); + while (!next && (item=TreeView_GetParent(tree,item))!=NULL) + next=TreeView_GetNextSibling(tree,item); + } + item=next; + } +} + +void SetControlAccessibleName( HWND control, const wchar_t *name ) +{ + CComPtr props; + if (SUCCEEDED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) + props->SetHwndPropStr(control,OBJID_CLIENT,CHILDID_SELF,PROPID_SettingsAccName,name); +} + +void SetSettingsTreeAccessibleName( HWND tree, const wchar_t *name ) +{ + SetControlAccessibleName(tree,name); +} + +void SetSettingsTreeItemAccessibleName( HWND tree, HTREEITEM item, const wchar_t *name ) +{ + CComPtr props; + if (SUCCEEDED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) + props->SetHwndPropStr(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),PROPID_SettingsAccName,name); +} + +void SetSettingsTreeItemAccessibleRole( HWND tree, HTREEITEM item, LONG role ) +{ + CComPtr props; + if (SUCCEEDED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) + { + // Some settings hosts do not ask a container annotation server for + // descendant state. Bind the dynamic state provider to the item too. + CSettingsTreeAccServer *server=new CSettingsTreeAccServer; + MSAAPROPID stateProperty=PROPID_SettingsAccState; + props->SetHwndPropServer(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),&stateProperty,1,server,ANNO_THIS); + server->Release(); + CComVariant value(role); + props->SetHwndProp(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),PROPID_SettingsAccRole,value); + } +} const KNOWNFOLDERID FOLDERID_DesktopRoot={'DESK', 'TO', 'P', {'D', 'E', 'S', 'K', 'T', 'O', 'P', 0x00}}; @@ -2334,7 +2511,6 @@ class CTreeSettingsDlg: public CResizeableDlg NOTIFY_HANDLER( IDC_SETTINGS, TVN_KEYDOWN, OnKeyDown ) NOTIFY_HANDLER( IDC_SETTINGS, TVN_GETINFOTIP, OnGetInfoTip ) NOTIFY_HANDLER( IDC_SETTINGS, TVN_SELCHANGED, OnSelChanged ) - NOTIFY_HANDLER( IDC_SETTINGS, TVN_ITEMEXPANDING, OnExpanding ) END_MSG_MAP() BEGIN_RESIZE_MAP @@ -2364,7 +2540,6 @@ class CTreeSettingsDlg: public CResizeableDlg LRESULT OnClick( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ); LRESULT OnGetInfoTip( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ); LRESULT OnSelChanged( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ); - LRESULT OnExpanding( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ) { return TRUE; } // prevent collapsing private: CWindow m_Tree; @@ -2398,6 +2573,32 @@ static LRESULT CALLBACK SubclassTreeProc( HWND hWnd, UINT uMsg, WPARAM wParam, L { if (uMsg==WM_COMMAND) return SendMessage(GetParent(hWnd),uMsg,wParam,lParam); + if (uMsg==WM_KEYDOWN && (wParam==VK_LEFT || wParam==VK_RIGHT) && GetKeyState(VK_MENU)>=0) + { + HTREEITEM item=TreeView_GetSelection(hWnd); + if (!item) return 0; + HTREEITEM child=TreeView_GetChild(hWnd,item); + TVITEM state={TVIF_STATE,item,0,TVIS_EXPANDED}; + TreeView_GetItem(hWnd,&state); + if (wParam==VK_LEFT) + { + if (child && (state.state&TVIS_EXPANDED)) + TreeView_Expand(hWnd,item,TVE_COLLAPSE); + else + { + HTREEITEM parent=TreeView_GetParent(hWnd,item); + if (parent) TreeView_SelectItem(hWnd,parent); + } + } + else if (child) + { + if (state.state&TVIS_EXPANDED) + TreeView_SelectItem(hWnd,child); + else + TreeView_Expand(hWnd,item,TVE_EXPAND); + } + return 0; + } return DefSubclassProc(hWnd,uMsg,wParam,lParam); } @@ -2534,6 +2735,16 @@ LRESULT CTreeSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, SetWindowTheme(m_Tree,L"Explorer",NULL); } m_Tree.SendMessage(TVM_SETEXTENDEDSTYLE,TVS_EX_DOUBLEBUFFER,TVS_EX_DOUBLEBUFFER); + EnableSettingsTreeAccessibility(m_Tree); + CString tip; + GetDlgItemText(IDC_STATICTIP,tip); + int line=tip.Find(L'\n'); + if (line>=0) + { + tip=tip.Mid(line+1); + tip.Trim(); + SetControlAccessibleName(GetDlgItem(IDC_STATICTIP),tip); + } SetWindowSubclass(m_Tree,SubclassTreeProc,'CLSH',0); HINSTANCE hInstance=_AtlBaseModule.GetResourceInstance(); @@ -3201,6 +3412,7 @@ void CTreeSettingsDlg::ItemSelected( HTREEITEM hItem, CSetting *pSetting, bool b if (mode!=EDIT_NONE) { CString str=LoadStringEx(pSetting->nameID); + SetControlAccessibleName(m_EditBox,str); TVITEM item={TVIF_TEXT,hItem,0,0,(LPWSTR)(LPCWSTR)str}; TreeView_SetItem(m_Tree,&item); m_Tree.GetClientRect(&rc); @@ -3242,6 +3454,12 @@ void CTreeSettingsDlg::ItemSelected( HTREEITEM hItem, CSetting *pSetting, bool b m_EditBox.SetWindowPos(NULL,&rc,SWP_NOZORDER|SWP_SHOWWINDOW); SendMessage(WM_NEXTDLGCTL,(LPARAM)m_EditBox.m_hWnd,TRUE); SetWindowSubclass(m_EditBox,SubclassEditProc,'CLSH',(mode==EDIT_HOTKEY)?1:((mode==EDIT_HOTKEY_ANY)?2:0)); + // The same overlay edit is reused while Up/Down moves through the + // tree. Its HWND and keyboard focus do not change, so explicitly + // announce that its accessible name has changed. + NotifyWinEvent(EVENT_OBJECT_NAMECHANGE,m_EditBox,OBJID_CLIENT,CHILDID_SELF); + if (::GetFocus()==m_EditBox) + NotifyWinEvent(EVENT_OBJECT_FOCUS,m_EditBox,OBJID_CLIENT,CHILDID_SELF); } else m_EditBox.ShowWindow(SW_HIDE); @@ -3385,11 +3603,14 @@ HTREEITEM CTreeSettingsDlg::FindSettingsItem( const wchar_t *name ) void CTreeSettingsDlg::SetGroup( CSetting *pGroup, const CString &filter, const CSetting *pSelect ) { + CString groupName=LoadStringEx(pGroup->nameID); + SetSettingsTreeAccessibleName(m_Tree,groupName); ItemSelected(NULL,NULL,false); m_pGroup=pGroup; // fill tree control m_Tree.SendMessage(WM_SETREDRAW,FALSE); TreeView_SelectItem(m_Tree,NULL); + ClearSettingsTreeItemAccessibility(m_Tree); TreeView_DeleteAllItems(m_Tree); int level=0; HTREEITEM hRadioParent=NULL; @@ -3566,9 +3787,14 @@ void CTreeSettingsDlg::UpdateGroup( const CSetting *pModified ) RECT rc; TreeView_GetItemRect(m_Tree,hItem,&rc,FALSE); m_Tree.InvalidateRect(&rc); + NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hItem)); } + if (pSetting->type==CSetting::TYPE_BOOL) + SetSettingsTreeItemAccessibleRole(m_Tree,hItem,ROLE_SYSTEM_CHECKBUTTON); // update radio buttons + wchar_t selectedText[256]; + selectedText[0]=0; if (pSetting->type==CSetting::TYPE_INT && pSetting[1].type==CSetting::TYPE_RADIO) { int val=0; @@ -3598,9 +3824,37 @@ void CTreeSettingsDlg::UpdateGroup( const CSetting *pModified ) RECT rc; TreeView_GetItemRect(m_Tree,hRadio,&rc,FALSE); m_Tree.InvalidateRect(&rc); + NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hRadio)); } + SetSettingsTreeItemAccessibleRole(m_Tree,hRadio,ROLE_SYSTEM_RADIOBUTTON); } + for (HTREEITEM hRadio=TreeView_GetChild(m_Tree,hItem);hRadio;hRadio=TreeView_GetNextSibling(m_Tree,hRadio)) + { + TVITEM radioName={TVIF_IMAGE|TVIF_TEXT,hRadio,0,0,selectedText,_countof(selectedText)}; + if (TreeView_GetItem(m_Tree,&radioName) && (radioName.iImage&SETTING_STATE_CHECKED)) break; + selectedText[0]=0; + } + } + + // Bold is the visual indication that a setting differs from its + // default. Expose the same information in the accessible name. + wchar_t parentText[256]; + TVITEM parentName={TVIF_TEXT,hItem,0,0,parentText,_countof(parentText)}; + TreeView_GetItem(m_Tree,&parentName); + CString accessibleName=parentText; + if (selectedText[0]) + { + accessibleName+=L": "; + accessibleName+=selectedText; + } + if (!bDefault) + { + accessibleName+=L", "; + accessibleName+=LoadStringEx(IDS_SETTING_MODIFIED); } + SetSettingsTreeItemAccessibleName(m_Tree,hItem,accessibleName); + if (pSetting==pModified) + NotifyWinEvent(EVENT_OBJECT_NAMECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hItem)); } } diff --git a/Src/Lib/SettingsUIHelper.h b/Src/Lib/SettingsUIHelper.h index 8a66f72c3..0bdfa11be 100644 --- a/Src/Lib/SettingsUIHelper.h +++ b/Src/Lib/SettingsUIHelper.h @@ -14,6 +14,16 @@ class CSettingsTree; class ISettingsPanel; struct CSetting; +// Adds the checked/unchecked and unavailable states used by the settings +// trees to their MSAA/UIA representation. The visual state remains provided +// by Open-Shell's existing image list. +void EnableSettingsTreeAccessibility( HWND tree ); +void ClearSettingsTreeItemAccessibility( HWND tree ); +void SetControlAccessibleName( HWND control, const wchar_t *name ); +void SetSettingsTreeAccessibleName( HWND tree, const wchar_t *name ); +void SetSettingsTreeItemAccessibleName( HWND tree, HTREEITEM item, const wchar_t *name ); +void SetSettingsTreeItemAccessibleRole( HWND tree, HTREEITEM item, LONG role ); + /////////////////////////////////////////////////////////////////////////////// // CResizeableDlg - a dialog that rearranges its controls when it gets resized diff --git a/Src/Lib/resource.h b/Src/Lib/resource.h index 718e4f2c4..00587bd3c 100644 --- a/Src/Lib/resource.h +++ b/Src/Lib/resource.h @@ -139,6 +139,8 @@ #define IDS_BMP_FILTERS2 4077 #define IDS_UNSAVED_CHANGES 4078 #define IDS_UNSAVED_TITLE 4079 +#define IDS_SETTINGS_PAGES 4080 +#define IDS_SETTING_MODIFIED 4081 // Next default values for new objects // diff --git a/Src/StartMenu/StartMenuDLL/Accessibility.cpp b/Src/StartMenu/StartMenuDLL/Accessibility.cpp index aa8529543..d413af338 100644 --- a/Src/StartMenu/StartMenuDLL/Accessibility.cpp +++ b/Src/StartMenu/StartMenuDLL/Accessibility.cpp @@ -53,7 +53,14 @@ HRESULT STDMETHODCALLTYPE CMenuAccessible::get_accName( VARIANT varChild, BSTR * if (!m_pOwner) return RPC_E_DISCONNECTED; *pszName=NULL; if (varChild.vt!=VT_I4) return S_FALSE; - if (varChild.lVal==CHILDID_SELF) return S_FALSE; + if (varChild.lVal==CHILDID_SELF) + { + if (m_pOwner->m_pParent && m_pOwner->m_ParentIndex>=0 && m_pOwner->m_ParentIndex<(int)m_pOwner->m_pParent->m_Items.size()) + *pszName=SysAllocString(m_pOwner->m_pParent->m_Items[m_pOwner->m_ParentIndex].name); + else + *pszName=SysAllocString(L"Start"); + return *pszName?S_OK:E_OUTOFMEMORY; + } int index=varChild.lVal-1; if (index<0 || index>=(int)m_pOwner->m_Items.size()) return S_FALSE; if (m_pOwner->m_Items[index].id==MENU_SEPARATOR) return S_FALSE; @@ -105,7 +112,10 @@ HRESULT STDMETHODCALLTYPE CMenuAccessible::get_accState( VARIANT varChild, VARIA if (m_pOwner->m_HotItem==index) flags|=STATE_SYSTEM_FOCUSED; if (item.bFolder) + { flags|=STATE_SYSTEM_HASPOPUP; + flags|=(m_pOwner->m_Submenu==index)?STATE_SYSTEM_EXPANDED:STATE_SYSTEM_COLLAPSED; + } if (item.id==MENU_SEPARATOR) flags=0; RECT rc; @@ -167,7 +177,12 @@ HRESULT STDMETHODCALLTYPE CMenuAccessible::get_accDefaultAction( VARIANT varChil return S_FALSE; const CMenuContainer::MenuItem &item=m_pOwner->m_Items[index]; if (item.id!=MENU_SEPARATOR && item.id!=MENU_EMPTY && item.id!=MENU_EMPTY_TOP) - *pszDefaultAction=SysAllocString(item.bFolder?FindTranslation(L"Menu.ActionOpen",L"Open"):FindTranslation(L"Menu.ActionExecute",L"Execute")); + { + if (item.bFolder) + *pszDefaultAction=SysAllocString(m_pOwner->m_Submenu==index?FindTranslation(L"Menu.ActionClose",L"Close"):FindTranslation(L"Menu.ActionOpen",L"Open")); + else + *pszDefaultAction=SysAllocString(FindTranslation(L"Menu.ActionExecute",L"Execute")); + } return S_OK; } diff --git a/Src/StartMenu/StartMenuDLL/SettingsUI.cpp b/Src/StartMenu/StartMenuDLL/SettingsUI.cpp index 49383601b..310864c18 100644 --- a/Src/StartMenu/StartMenuDLL/SettingsUI.cpp +++ b/Src/StartMenu/StartMenuDLL/SettingsUI.cpp @@ -207,6 +207,10 @@ LRESULT CSkinSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, EnableThemeDialogTexture(m_hWnd,ETDT_ENABLETAB); m_Tree=GetDlgItem(IDC_SKINOPTIONS); + EnableSettingsTreeAccessibility(m_Tree); + CString optionsName; + GetDlgItemText(IDC_STATICOPT,optionsName); + SetSettingsTreeAccessibleName(m_Tree,optionsName); SetWindowSubclass(m_Tree,SubclassTreeProc,'CLSH',0); TreeView_SetImageList(m_Tree,GetSettingsImageList(m_Tree),TVSIL_NORMAL); m_Tree.SendMessage(TVM_SETEXTENDEDSTYLE,TVS_EX_DOUBLEBUFFER,TVS_EX_DOUBLEBUFFER); @@ -290,6 +294,7 @@ LRESULT CSkinSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BO void CSkinSettingsDlg::InitSkinUI( void ) { m_Tree.SetRedraw(FALSE); + ClearSettingsTreeItemAccessibility(m_Tree); TreeView_DeleteAllItems(m_Tree); m_VariationIndex=-1; m_EditItemIndex=-1; @@ -396,10 +401,12 @@ void CSkinSettingsDlg::UpdateSkinSettings( void ) { item.iImage=item.iSelectedImage=image; TreeView_SetItem(m_Tree,&item); + NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hVar)); RECT rc; TreeView_GetItemRect(m_Tree,hVar,&rc,FALSE); m_Tree.InvalidateRect(&rc); } + SetSettingsTreeItemAccessibleRole(m_Tree,hVar,ROLE_SYSTEM_RADIOBUTTON); } hItem=TreeView_GetNextSibling(m_Tree,hItem); } @@ -442,6 +449,8 @@ void CSkinSettingsDlg::UpdateSkinSettings( void ) item.iImage=item.iSelectedImage=image; item.mask|=TVIF_TEXT; TreeView_SetItem(m_Tree,&item); + NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hItem)); + SetSettingsTreeItemAccessibleRole(m_Tree,hItem,option.groupId>=0?ROLE_SYSTEM_RADIOBUTTON:ROLE_SYSTEM_CHECKBUTTON); item.mask&=~TVIF_TEXT; RECT rc; TreeView_GetItemRect(m_Tree,hItem,&rc,FALSE); diff --git a/Src/StartMenu/StartMenuDLL/SkinManager.cpp b/Src/StartMenu/StartMenuDLL/SkinManager.cpp index 5bf2d301a..a9f882059 100644 --- a/Src/StartMenu/StartMenuDLL/SkinManager.cpp +++ b/Src/StartMenu/StartMenuDLL/SkinManager.cpp @@ -2918,6 +2918,28 @@ bool MenuSkin::LoadSkin( HMODULE hMod, const wchar_t *variation, const wchar_t * ItemSettings[COLUMN2_INLINE].bmpIconFrame.bIsOwned=false; ItemSettings[COLUMN2_INLINE].bmpSeparator.bIsOwned=false; + // A skin's "new item" colors must not override the user's High Contrast + // palette. This only changes the highlighted-new variants while High + // Contrast is active; normal themed rendering remains untouched. + HIGHCONTRAST contrast={sizeof(contrast)}; + if (SystemParametersInfo(SPI_GETHIGHCONTRAST,sizeof(contrast),&contrast,0) && (contrast.dwFlags&HCF_HIGHCONTRASTON)) + { + const int newItems[]={COLUMN1_NEW,COLUMN2_NEW,SUBMENU_NEW,PROGRAMS_TREE_NEW,PROGRAMS_BUTTON_NEW,PROGRAMS_CASCADING_NEW}; + for (int i=0;i<_countof(newItems);i++) + { + ItemDrawSettings &settings=ItemSettings[newItems[i]]; + settings.bmpSelection.Reset(true); + settings.bmpSelection=GetSysColor(COLOR_HIGHLIGHT); + settings.glowSize=0; + for (int j=0;j<_countof(settings.textColors);j++) + { + settings.textColors[j]=GetSysColor(COLOR_HIGHLIGHTTEXT); + settings.textShadowColors[j]=GetSysColor(COLOR_HIGHLIGHT); + } + settings.arrColors[0]=settings.arrColors[1]=GetSysColor(COLOR_HIGHLIGHTTEXT); + } + } + HDC hdc=CreateCompatibleDC(NULL); HGDIOBJ font0=GetCurrentObject(hdc,OBJ_FONT); for (int i=0;i<_countof(ItemSettings);i++)