Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6487 lines
191 KiB

// ARP.cpp : Add Remove Programs
//
#include "priv.h"
#define GADGET_ENABLE_TRANSITIONS
// Related services
#include <duser.h>
#include <directui.h>
#include "stdafx.h"
#include "appwizid.h"
#include "resource.h"
#include <winable.h> // BlockInput
#include <process.h> // Multi-threaded routines
#include "setupenum.h"
#include <tsappcmp.h> // for TermsrvAppInstallMod
#include <comctrlp.h> // for DPA stuff
#include "util.h"
#include <shstyle.h>
using namespace DirectUI;
UsingDUIClass(Element);
UsingDUIClass(Button);
UsingDUIClass(RepeatButton); // used by ScrollBar
UsingDUIClass(Thumb); // used by ScrollBar
UsingDUIClass(ScrollBar); // used by ScrollViewer
UsingDUIClass(Selector);
UsingDUIClass(HWNDElement);
UsingDUIClass(ScrollViewer);
UsingDUIClass(Combobox);
#include "shappmgrp.h"
#include "arp.h"
#define HRCHK(r) if (FAILED(r)) goto Cleanup;
// Primary thread run flag
bool g_fRun = true;
// Appliction shutting down after run flag goes false
bool g_fAppShuttingDown = false;
void CALLBACK ARPParseError(LPCWSTR pszError, LPCWSTR pszToken, int dLine);
inline void StrFree(LPWSTR psz)
{
CoTaskMemFree(psz); // CoTaskMemFree handles NULL parameter
}
// Need this weirdo helper function to avoid compiler complaining that
// "bool is smaller than LPARAM, you're truncating!" Do this only if
// you know that the LPARAM came from a bool originally.
bool UNCASTLPARAMTOBOOL(LPARAM lParam)
{
return (bool&)lParam;
}
extern "C" void inline SetElementAccessability(Element* pe, bool bAccessible, int iRole, LPCWSTR pszAccName);
// Client names are compared in English to avoid weirdness
// with collation rules of certain languages.
inline bool AreEnglishStringsEqual(LPCTSTR psz1, LPCTSTR psz2)
{
return CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, psz1, -1, psz2, -1) == CSTR_EQUAL;
}
//
// Set the default action based on a resource ID in oleacc.
//
HRESULT SetDefAction(Element* pe, UINT oleacc)
{
WCHAR szBuf[80];
if (!GetRoleTextW(oleacc, szBuf, DUIARRAYSIZE(szBuf)))
{
szBuf[0] = TEXT('\0');
}
return pe->SetAccDefAction(szBuf);
}
////////////////////////////////////////////////////////
// Tree traversal upwards
//
Element* _FindAncestorElement(Element* pe, IClassInfo* Class)
{
while (pe && !pe->GetClassInfo()->IsSubclassOf(Class))
{
pe = pe->GetParent();
}
return pe;
}
template<class T>
T* FindAncestorElement(Element *pe)
{
return (T*)_FindAncestorElement(pe, T::Class);
}
////////////////////////////////////////////////////////
// Tree traversal downwards
//
typedef HRESULT (CALLBACK *_TRAVERSETREECB)(Element*, LPARAM);
//
// _TraverseTree is the worker function for TraverseTree<T>.
HRESULT _TraverseTree(Element* pe, IClassInfo* Class, _TRAVERSETREECB lpfnCB, LPARAM lParam)
{
HRESULT hr = S_OK;
Value* pv;
if (pe->GetClassInfo()->IsSubclassOf(Class)) {
hr = lpfnCB(pe, lParam);
}
if (SUCCEEDED(hr))
{
ElementList* peList = pe->GetChildren(&pv);
if (peList)
{
Element* peChild;
for (UINT i = 0; SUCCEEDED(hr) && i < peList->GetSize(); i++)
{
peChild = peList->GetItem(i);
hr = _TraverseTree(peChild, Class, lpfnCB, lParam);
}
pv->Release();
}
}
return hr;
}
// TraverseTree<T> walks the tree starting at pe and calls the callback
// for each element of type T. T is inferred from the callback function,
// but for readability, it is suggested that you state it explicitly.
//
// Callback should return S_OK to continue enumeration or a COM error
// to stop enumeration, in which case the COM error code is returned as
// the return value from TraverseTree.
//
template <class T>
HRESULT TraverseTree(Element* pe,
HRESULT (CALLBACK *lpfnCB)(T*, LPARAM), LPARAM lParam = 0)
{
return _TraverseTree(pe, T::Class, (_TRAVERSETREECB)lpfnCB, lParam);
}
////////////////////////////////////////////////////////
//
// When chunks of the tree go UI-inactive, you must manually
// enable and disable accessibility on them.
HRESULT DisableElementAccessibilityCB(Element* pe, LPARAM)
{
pe->SetAccessible(false);
return S_OK;
}
HRESULT CheckAndEnableElementAccessibilityCB(Element* pe, LPARAM)
{
if ( 0 != pe->GetAccRole())
{
pe->SetAccessible(true);
}
return S_OK;
}
void DisableElementTreeAccessibility(Element* pe)
{
TraverseTree(pe, DisableElementAccessibilityCB);
}
void EnableElementTreeAccessibility(Element* pe)
{
TraverseTree(pe, CheckAndEnableElementAccessibilityCB);
}
HRESULT DisableElementShortcutCB(Element* pe, LPARAM)
{
pe->SetShortcut(0);
return S_OK;
}
// When a tree is hidden or removed from layout permanently (due to
// restriction), we also have to remove all keyboard shortcuts so the
// user doesn't have a backdoor.
//
void DisableElementTreeShortcut(Element* pe)
{
pe->SetVisible(false);
TraverseTree(pe, DisableElementShortcutCB);
}
////////////////////////////////////////////////////////
// ARPFrame class
////////////////////////////////////////////////////////
// ARPFrame IDs (for identifying targets of events)
ATOM ARPFrame::_idChange = 0;
ATOM ARPFrame::_idAddNew = 0;
ATOM ARPFrame::_idAddRmWin = 0;
ATOM ARPFrame::_idClose = 0;
ATOM ARPFrame::_idAddFromDisk = 0;
ATOM ARPFrame::_idAddFromMsft = 0;
ATOM ARPFrame::_idComponents = 0;
ATOM ARPFrame::_idSortCombo = 0;
ATOM ARPFrame::_idCategoryCombo = 0;
ATOM ARPFrame::_idAddFromCDPane = 0;
ATOM ARPFrame::_idAddFromMSPane = 0;
ATOM ARPFrame::_idAddFromNetworkPane = 0;
ATOM ARPFrame::_idAddWinComponent = 0;
ATOM ARPFrame::_idPickApps = 0;
ATOM ARPFrame::_idOptionList = 0;
HANDLE ARPFrame::htPopulateInstalledItemList = NULL;
HANDLE ARPFrame::htPopulateAndRenderOCSetupItemList = NULL;
HANDLE ARPFrame::htPopulateAndRenderPublishedItemList = NULL;
ARPFrame::~ARPFrame()
{
UINT i;
if (_psacl)
{
for (i = 0; i < _psacl->cCategories; i++)
{
if (_psacl->pCategory[i].pszCategory)
{
StrFree(_psacl->pCategory[i].pszCategory);
}
}
delete _psacl;
}
if (_pParserStyle)
_pParserStyle->Destroy();
// Close theme handles (if applicable)
for (i = FIRSTHTHEME; i <= LASTHTHEME; i++)
{
if (_arH[i])
CloseThemeData(_arH[i]);
}
if (_arH[SHELLSTYLEHINSTANCE])
{
FreeLibrary((HMODULE)_arH[SHELLSTYLEHINSTANCE]);
}
EndProgressDialog();
}
HRESULT ARPFrame::Create(OUT Element** ppElement)
{
UNREFERENCED_PARAMETER(ppElement);
DUIAssertForce("Cannot instantiate an HWND host derived Element via parser. Must use substitution.");
return E_NOTIMPL;
}
HRESULT ARPFrame::Create(NativeHWNDHost* pnhh, bool fDblBuffer, OUT Element** ppElement)
{
*ppElement = NULL;
ARPFrame* paf = HNewAndZero<ARPFrame>();
if (!paf)
return E_OUTOFMEMORY;
HRESULT hr = paf->Initialize(pnhh, fDblBuffer);
if (FAILED(hr))
{
paf->Destroy();
return hr;
}
*ppElement = paf;
return S_OK;
}
HRESULT ARPFrame::Initialize(NativeHWNDHost* pnhh, bool fDblBuffer)
{
// Initialize
_pnhh = pnhh;
_bDoubleBuffer = fDblBuffer;
_pParserStyle = NULL;
ZeroMemory(_arH, sizeof(_arH));
_fThemedStyle = FALSE;
_pParserStyle = NULL;
_hdsaInstalledItems = NULL;
_hdsaPublishedItems = NULL;
_bAnimationEnabled = true;
if (IsOS(OS_TERMINALSERVER))
{
_bTerminalServer = true;
}
else
{
_bTerminalServer = false;
}
// Do base class initialization
HRESULT hr = HWNDElement::Initialize(pnhh->GetHWND(), fDblBuffer, 0);
if (FAILED(hr))
return hr;
CurrentSortType = SORT_NAME;
hr = CreateStyleParser(&_pParserStyle);
if (FAILED(hr) || !_pParserStyle || _pParserStyle->WasParseError())
return hr;
ManageAnimations();
return S_OK;
}
HRESULT ARPFrame::CreateStyleParser(Parser** ppParser)
{
HRESULT hr;
// We always need these two
_arH[THISDLLHINSTANCE] = g_hinst; // Default HINSTANCE
_arH[XPSP1HINSTANCE] = g_hinst; // same HINSTANCE
// And this one
if (_arH[SHELLSTYLEHINSTANCE])
{
FreeLibrary((HMODULE)_arH[SHELLSTYLEHINSTANCE]);
}
_arH[SHELLSTYLEHINSTANCE] = SHGetShellStyleHInstance();
// Store style and theme information
// We cannot trust IsAppThemed() or IsThemeActive() because WindowBlinds
// patches them to return hard-coded TRUE. If we trusted it, then
// we would think that we're using a theme-enabled shellstyle.dll and
// fail when we try to load resources out of it. Instead, sniff
// the DLL to see if it has a control panel watermark bitmap.
if (FindResource((HMODULE)_arH[SHELLSTYLEHINSTANCE],
MAKEINTRESOURCE(IDB_CPANEL_WATERMARK), RT_BITMAP))
{
_fThemedStyle = TRUE;
// Populate handle list for theme style parsing
_arH[BUTTONHTHEME] = OpenThemeData(GetHWND(), L"Button");
_arH[SCROLLBARHTHEME] = OpenThemeData(GetHWND(), L"Scrollbar");
_arH[TOOLBARHTHEME] = OpenThemeData(GetHWND(), L"Toolbar");
hr = Parser::Create(IDR_ARPSTYLETHEME, _arH, ARPParseError, ppParser);
}
else
{
_fThemedStyle = FALSE;
hr = Parser::Create(IDR_ARPSTYLESTD, _arH, ARPParseError, ppParser);
}
return hr;
}
extern "C" DWORD _cdecl ARPIsRestricted(LPCWSTR pszPolicy);
extern "C" bool _cdecl ARPIsOnDomain();
// Handy helper functions.
// Finds a descendent and asserts if not found.
Element* FindDescendentByName(Element* peRoot, LPCWSTR pszName)
{
Element* pe = peRoot->FindDescendent(StrToID(pszName));
DUIAssertNoMsg(pe);
return pe;
}
// Finds a descendent but doesn't complain if not found.
Element* MaybeFindDescendentByName(Element* peRoot, LPCWSTR pszName)
{
Element* pe = peRoot->FindDescendent(StrToID(pszName));
return pe;
}
HRESULT _SendParseCompleted(ClientBlock* pcb, LPARAM lParam)
{
return pcb->ParseCompleted((ARPFrame*)lParam);
}
// Initialize IDs and hold parser, called after contents are filled
bool ARPFrame::Setup(Parser* pParser, int uiStartPane)
{
WCHAR szTemp[1024];
_pParser = pParser;
if (uiStartPane >= 0 && uiStartPane <= 3)
{
_uiStartPane = uiStartPane;
}
// Initialize ID cache
_idChange = StrToID(L"change");
_idAddNew = StrToID(L"addnew");
_idAddRmWin = StrToID(L"addrmwin");
_idClose = StrToID(L"close");
_idAddFromDisk = StrToID(L"addfromdisk");
_idAddFromMsft = StrToID(L"addfrommsft");
_idComponents = StrToID(L"components");
_idSortCombo = StrToID(L"sortcombo");
_idCategoryCombo = StrToID(L"categorycombo");
_idAddFromCDPane = StrToID(L"addfromCDPane");
_idAddFromMSPane = StrToID(L"addfromMSpane");
_idAddFromNetworkPane = StrToID(L"addfromNetworkpane");
_idAddWinComponent = StrToID(L"addwincomponent");
_idPickApps = StrToID(L"pickapps");
_idOptionList = StrToID(L"optionlist");
// Find children
_peOptionList = (ARPSelector*) FindDescendentByName(this, L"optionlist");
_peInstalledItemList = (Selector*) FindDescendentByName(this, L"installeditemlist");
_pePublishedItemList = (Selector*) FindDescendentByName(this, L"publisheditemlist");
_peOCSetupItemList = (Selector*) FindDescendentByName(this, L"ocsetupitemlist");
_peSortCombo = (Combobox*) FindDescendentByName(this, L"sortcombo");
_pePublishedCategory = (Combobox*) FindDescendentByName(this, L"categorycombo");
_pePublishedCategoryLabel = (Element*) FindDescendentByName(this, L"categorylabel");
_peClientTypeList = (ARPSelector*) FindDescendentByName(this, L"clienttypelist");
_peOEMClients = (Expando*) FindDescendentByName(_peClientTypeList, L"oemclients");
_peMSClients = (Expando*) FindDescendentByName(_peClientTypeList, L"msclients");
_peNonMSClients = (Expando*) FindDescendentByName(_peClientTypeList, L"nonmsclients");
_peCustomClients = (Expando*) FindDescendentByName(_peClientTypeList, L"customclients");
_peOK = FindDescendentByName(this, L"ok");
_peCancel = FindDescendentByName(this, L"cancel");
_peCurrentItemList = NULL;
_peChangePane = FindDescendentByName(this, L"changepane");
_peAddNewPane = FindDescendentByName(this, L"addnewpane");
_peAddRmWinPane = FindDescendentByName(this, L"addrmwinpane");
_pePickAppPane = FindDescendentByName(this, L"pickapppane");
if (NULL != _peSortCombo)
{
LoadStringW(_pParser->GetHInstance(), IDS_APPNAME, szTemp, DUIARRAYSIZE(szTemp));
_peSortCombo->AddString(szTemp);
LoadStringW(_pParser->GetHInstance(), IDS_SIZE, szTemp, DUIARRAYSIZE(szTemp));
_peSortCombo->AddString(szTemp);
LoadStringW(_pParser->GetHInstance(), IDS_FREQUENCY, szTemp, DUIARRAYSIZE(szTemp));
_peSortCombo->AddString(szTemp);
LoadStringW(_pParser->GetHInstance(), IDS_DATELASTUSED, szTemp, DUIARRAYSIZE(szTemp));
_peSortCombo->AddString(szTemp);
_peSortCombo->SetSelection(0);
}
_bInDomain = ARPIsOnDomain();
_bOCSetupNeeded = !!COCSetupEnum::s_OCSetupNeeded();
// Apply polices as needed
ApplyPolices();
if(!_bOCSetupNeeded)
{
Element* pe = FindDescendentByName(this, L"addrmwinoc");
DUIAssertNoMsg(pe);
pe->SetLayoutPos(LP_None);
}
// Set initial selection of option list
Element* peSel;
switch(_uiStartPane)
{
case 3:
peSel = FindDescendent(_idPickApps);
break;
case 2:
peSel = FindDescendent(_idAddRmWin);
break;
case 1:
peSel = FindDescendent(_idAddNew);
break;
case 0:
default:
peSel = FindDescendent(_idChange);
break;
}
// Set initial selection of style list
DUIAssertNoMsg(peSel);
_peOptionList->SetSelection(peSel);
// initialize focus-following floater window
peLastFocused = NULL;
Element::Create(0, &peFloater);
peFloater->SetLayoutPos(LP_Absolute);
Add(peFloater);
peFloater->SetBackgroundColor(ARGB(64, 255, 255, 0));
///////////////////////////////////////////////////////////////
// Initialize the Pick Apps pane
// Tell the clientblock elements that it's safe to initialize now
if (FAILED(TraverseTree<ClientBlock>(this, _SendParseCompleted, (LPARAM)this)))
{
return false;
}
// Fill the client type lists
InitClientCombos(_peOEMClients, CLIENTFILTER_OEM);
InitClientCombos(_peMSClients, CLIENTFILTER_MS);
InitClientCombos(_peNonMSClients, CLIENTFILTER_NONMS);
_peClientTypeList->SetSelection(_peCustomClients);
_peCustomClients->SetExpanded(false);
return true;
}
struct SETFILTERINFO {
CLIENTFILTER _cf;
BOOL _fHasApp;
ARPFrame* _paf;
};
HRESULT SetFilterCB(ClientPicker *pe, LPARAM lParam)
{
SETFILTERINFO* pfi = (SETFILTERINFO*)lParam;
HRESULT hr = pe->SetFilter(pfi->_cf, pfi->_paf);
if (SUCCEEDED(hr) && !pe->IsEmpty())
{
pfi->_fHasApp = TRUE;
}
return hr;
}
HRESULT ARPFrame::InitClientCombos(Expando* pexParent, CLIENTFILTER cf)
{
HRESULT hr;
Element* pe;
hr = _pParser->CreateElement(L"clientcategoryblock", NULL, &pe);
if (SUCCEEDED(hr))
{
//
// The client combos appear as the last element in the
// clipped block. The clipped block is the first (only)
// child of the clipper.
//
GetNthChild(pexParent->GetClipper(), 0)->Add(pe);
SETFILTERINFO sfi = { cf, FALSE, this };
hr = TraverseTree<ClientPicker>(pe, SetFilterCB, (LPARAM)&sfi);
if (sfi._cf == CLIENTFILTER_OEM && !sfi._fHasApp)
{
pexParent->SetLayoutPos(LP_None);
}
}
pexParent->SetExpanded(false);
return hr;
}
//
// ARPFrame::FindClientBlock locates a ClientBlock by client type.
//
struct FINDCLIENTBLOCKINFO {
LPCWSTR _pwszType;
ClientBlock* _pcb;
};
HRESULT FindClientBlockCB(ClientBlock* pcb, LPARAM lParam)
{
FINDCLIENTBLOCKINFO* pfcbi = (FINDCLIENTBLOCKINFO*)lParam;
Value* pv;
LPWSTR pwszType = pcb->GetClientTypeString(&pv);
// Use LOCALE_INVARIANT so we aren't bitten by locales that do not
// collate the same way as English.
if (pwszType &&
AreEnglishStringsEqual(pwszType, pfcbi->_pwszType))
{
pfcbi->_pcb = pcb; // found it!
}
pv->Release();
return S_OK;
}
ClientBlock* ARPFrame::FindClientBlock(LPCWSTR pwszType)
{
FINDCLIENTBLOCKINFO fcbi = { pwszType, NULL };
TraverseTree<ClientBlock>(this, FindClientBlockCB, (LPARAM)&fcbi);
return fcbi._pcb;
}
/*
* You must be a member of the Administrators group in order to
* configure programs. Being Power User isn't good enough because
* Power Users can't write to %ALLUSERSPROFILE%.
*/
BOOL CanConfigurePrograms()
{
return IsUserAnAdmin();
}
void ARPFrame::ApplyPolices()
{
Element* pe;
if (ARPIsRestricted(L"NoSupportInfo"))
{
_bSupportInfoRestricted = true;
}
if (ARPIsRestricted(L"ShowPostSetup"))
{
_bOCSetupNeeded = true;
}
else if (ARPIsRestricted(L"NoServices"))
{
_bOCSetupNeeded = false;
}
pe = FindDescendent(_idChange);
DUIAssertNoMsg(pe);
if (ARPIsRestricted(L"NoRemovePage"))
{
pe->SetLayoutPos(LP_None);
if (0 == _uiStartPane)
{
_uiStartPane++;
}
}
pe = FindDescendent(_idAddNew);
DUIAssertNoMsg(pe);
if (ARPIsRestricted(L"NoAddPage"))
{
pe->SetLayoutPos(LP_None);
if (1 == _uiStartPane)
{
_uiStartPane++;
}
}
else
{
if (ARPIsRestricted(L"NoAddFromCDorFloppy"))
{
pe = FindDescendent(_idAddFromCDPane);
DUIAssertNoMsg(pe);
pe->SetVisible(false);
DisableElementTreeShortcut(pe);
}
if (ARPIsRestricted(L"NoAddFromInternet"))
{
pe = FindDescendent(_idAddFromMSPane);
DUIAssertNoMsg(pe);
pe->SetVisible(false);
DisableElementTreeShortcut(pe);
}
if (!_bInDomain || ARPIsRestricted(L"NoAddFromNetwork"))
{
pe = FindDescendent(_idAddFromNetworkPane);
DUIAssertNoMsg(pe);
pe->SetVisible(false);
DisableElementTreeShortcut(pe);
}
}
pe = FindDescendent(_idAddRmWin);
DUIAssertNoMsg(pe);
// Note that in real ARP, we will never end up here with all thre panes disabled since we check for that before doing anything elese.
if (ARPIsRestricted(L"NoWindowsSetupPage"))
{
pe->SetLayoutPos(LP_None);
DisableElementTreeShortcut(pe);
if (2 == _uiStartPane)
{
_uiStartPane++;
}
}
pe = FindDescendent(_idAddWinComponent);
DUIAssertNoMsg(pe);
if (ARPIsRestricted(L"NoComponents"))
{
pe->SetVisible(false);
DisableElementTreeShortcut(pe);
}
// Remove the "pick apps" page entirely if we are on Server or embedded
// ("Choose Programs" is a workstation-only feature)
// or it is restricted
// (
pe = FindDescendent(_idPickApps);
DUIAssertNoMsg(pe);
if (IsOS(OS_ANYSERVER) ||
IsOS(OS_EMBEDDED) ||
ARPIsRestricted(L"NoChooseProgramsPage"))
{
pe->SetLayoutPos(LP_None);
DisableElementTreeShortcut(pe);
if (3 == _uiStartPane)
{
_uiStartPane++;
}
}
else
{
// DUI doesn't support content=rcicon so we have to set it manually
HICON hico = (HICON)LoadImage(g_hinst, MAKEINTRESOURCE(IDI_DEFPROGS), IMAGE_ICON, 32, 32, 0);
if (hico)
{
Value *pv = Value::CreateGraphic(hico);
if (pv)
{
GetNthChild(pe, 0)->SetValue(ContentProp, PI_Local, pv);
pv->Release();
}
}
// Neuter the "pick apps" page if the user can't configure apps
if (!CanConfigurePrograms()) {
pe = FindDescendentByName(_pePickAppPane, L"pickappadmin");
pe->SetVisible(false);
DisableElementTreeShortcut(pe);
pe = FindDescendentByName(_pePickAppPane, L"pickappnonadmin");
pe->SetVisible(true);
}
}
}
bool ARPFrame::IsChangeRestricted()
{
return ARPIsRestricted(L"NoRemovePage")? true : false;
}
void ARPFrame::RunOCManager()
{
// Invoke Add/Remove Windows components
// Command to invoke and OCMgr: "sysocmgr /y /i:%systemroot%\system32\sysoc.inf"
WCHAR szInf[MAX_PATH];
WCHAR szPath[MAX_PATH];
if ( GetSystemDirectoryW(szPath, MAX_PATH) && PathCombineW(szInf, szPath, L"sysoc.inf") &&
PathCombineW(szPath, szPath, L"sysocmgr.exe") )
{
WCHAR szParam[MAX_PATH];
StringCchPrintf(szParam, ARRAYSIZE(szParam), L"/y /i:%s", szInf);
ShellExecuteW(NULL, NULL, szPath, szParam, NULL, SW_SHOWDEFAULT);
}
}
DWORD WINAPI PopulateInstalledItemList(void* paf);
void ARPFrame::UpdateInstalledItems()
{
if (!IsChangeRestricted())
{
_peInstalledItemList->RemoveAll();
_bInstalledListFilled = false;
// Start second thread for item population
//_beginthread(PopulateInstalledItemList, 0, (void*)this);
if (!htPopulateInstalledItemList && g_fRun)
htPopulateInstalledItemList = CreateThread(NULL, 0, PopulateInstalledItemList, (void*)this, 0, NULL);
}
}
////////////////////////////////////////////////////////
// Generic eventing
// Helper
inline void _SetElementSheet(Element* peTarget, ATOM atomID, Value* pvSheet, bool bSheetRelease = true)
{
if (pvSheet)
{
Element* pe = peTarget->FindDescendent(atomID);
DUIAssertNoMsg(pe);
pe->SetValue(Element::SheetProp, PI_Local, pvSheet);
if (bSheetRelease)
pvSheet->Release();
}
}
BOOL IsValidFileTime(FILETIME ft)
{
return ft.dwHighDateTime || ft.dwLowDateTime;
}
BOOL IsValidSize(ULONGLONG ull)
{
return ull != (ULONGLONG)-1;
}
BOOL IsValidFrequency(int iTimesUsed)
{
return iTimesUsed >= 0;
}
void CALLBACK
_UnblockInputCallback(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR idEvent, DWORD /*dwTime*/)
{
BlockInput(FALSE);
KillTimer(NULL, idEvent);
}
void _BlockDoubleClickInput(void)
{
if (SetTimer(NULL, 0, GetDoubleClickTime(), _UnblockInputCallback))
{
BlockInput(TRUE);
}
}
//#ifdef NEVER
//
// NTRAID#NTBUG9-314154-2001/3/12-brianau Handle Refresh
//
// Need to finish this for Whistler.
//
DWORD WINAPI PopulateAndRenderPublishedItemList(void* paf);
DWORD WINAPI PopulateAndRenderOCSetupItemList(void* paf);
void EnablePane(Element* pePane, bool fEnable);
void ARPFrame::OnInput(InputEvent *pEvent)
{
if (GMF_BUBBLED == pEvent->nStage)
{
if (GINPUT_KEYBOARD == pEvent->nCode)
{
KeyboardEvent *pke = (KeyboardEvent *)pEvent;
if (VK_F5 == pke->ch)
{
if (_peCurrentItemList)
{
if (_peCurrentItemList == _peInstalledItemList)
{
UpdateInstalledItems();
}
else if (_peCurrentItemList == _pePublishedItemList)
{
RePopulatePublishedItemList();
}
else if (_peCurrentItemList == _peOCSetupItemList)
{
RePopulateOCSetupItemList();
}
}
}
}
}
HWNDElement::OnInput(pEvent);
}
//#endif
HRESULT SetVisibleClientCB(ClientPicker *pe, LPARAM lParam)
{
pe->SetVisible(UNCASTLPARAMTOBOOL(lParam));
return TRUE;
}
//
// This hides the controls that belong to the old pane and shows the
// controls that belong to the new pane.
//
void ARPFrame::ChangePane(Element *pePane)
{
bool fEnable;
// Show/hide elements that belong to _peChangePane...
fEnable = pePane == _peChangePane;
// TODO: Zero size ancestors need to cause adaptors (HWNDHosts) to hide
_peSortCombo->SetVisible(fEnable);
EnablePane(_peChangePane, fEnable);
// Show/hide elements that belong to _peAddNewPane
fEnable = pePane == _peAddNewPane;
_pePublishedCategory->SetVisible(fEnable);
_pePublishedCategoryLabel->SetVisible(fEnable);
EnablePane(_peAddNewPane, fEnable);
// Show/hide elements that belong to _peAddRmWinPane
fEnable = pePane == _peAddRmWinPane;
EnablePane(_peAddRmWinPane, fEnable);
// Show/hide elements that belong to _pePickAppPane
fEnable = pePane == _pePickAppPane;
TraverseTree<ClientPicker>(_pePickAppPane, SetVisibleClientCB, fEnable);
EnablePane(_pePickAppPane, fEnable);
}
// If we can't put focus on the list, it will go to the fallback location
void ARPFrame::PutFocusOnList(Selector* peList)
{
Element* pe;
if (pe = peList->GetSelection())
{
pe->SetKeyFocus();
}
else
{
pe = FallbackFocus();
if (pe)
{
pe->SetKeyFocus();
}
}
}
void ARPFrame::OnEvent(Event* pEvent)
{
// Handle only bubbled generic events
if (pEvent->nStage == GMF_BUBBLED)
{
if (pEvent->uidType == Button::Click)
{
ButtonClickEvent* pbce = (ButtonClickEvent*)pEvent;
if (pbce->peTarget->GetID() == _idClose ||
pbce->peTarget == _peOK)
{
// Close button
if (OnClose())
{
_pnhh->DestroyWindow();
}
pEvent->fHandled = true;
return;
}
else if (pbce->peTarget == _peCancel)
{
// Do not call OnClose; nothing will be applied
_pnhh->DestroyWindow();
pEvent->fHandled = true;
return;
}
else if (pbce->peTarget->GetID() == _idAddFromDisk)
{
// Add from disk button
HRESULT hr;
IShellAppManager* pisam = NULL;
hr = CoCreateInstance(__uuidof(ShellAppManager), NULL, CLSCTX_INPROC_SERVER, __uuidof(IShellAppManager), (void**)&pisam);
if (SUCCEEDED(hr))
{
pisam->InstallFromFloppyOrCDROM(GetHWND());
}
if (pisam)
{
pisam->Release();
}
pEvent->fHandled = true;
return;
}
else if (pbce->peTarget->GetID() == _idAddFromMsft)
{
// Windows update button
WCHAR szPath[MAX_PATH];
if ( GetSystemDirectory(szPath, MAX_PATH) && PathCombineW(szPath, szPath, L"wupdmgr.exe") )
{
ShellExecuteW(NULL, NULL, szPath, NULL, NULL, SW_SHOWDEFAULT);
pEvent->fHandled = true;
}
return;
}
else if (pbce->peTarget->GetID() == _idComponents)
{
RunOCManager();
}
else if (pbce->peTarget->GetID() == ARPItem::_idSize ||
pbce->peTarget->GetID() == ARPItem::_idFreq ||
pbce->peTarget->GetID() == ARPItem::_idSupInfo)
{
// Help requests
ARPHelp* peHelp;
NativeHWNDHost* pnhh = NULL;
Element* pe = NULL;
WCHAR szTitle[1024];
if (pbce->peTarget->GetID() == ARPItem::_idSize)
{
LoadStringW(_pParser->GetHInstance(), IDS_SIZETITLE, szTitle, DUIARRAYSIZE(szTitle));
if (SUCCEEDED(NativeHWNDHost::Create(szTitle, GetHWND(), NULL, CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, 0, WS_POPUPWINDOW | WS_OVERLAPPED | WS_DLGFRAME, NHHO_NoSendQuitMessage | NHHO_HostControlsSize | NHHO_ScreenCenter, &pnhh)))
{
ARPHelp::Create(pnhh, this, _bDoubleBuffer, (Element**)&peHelp);
_pParser->CreateElement(L"sizehelp", peHelp, &pe);
}
else
{
DUITrace(">> Failed to create NativeHWNDHost for size info window.\n");
}
}
else if (pbce->peTarget->GetID() == ARPItem::_idFreq)
{
LoadStringW(_pParser->GetHInstance(), IDS_FREQUENCYTITLE, szTitle, DUIARRAYSIZE(szTitle));
if (SUCCEEDED(NativeHWNDHost::Create(szTitle, GetHWND(), NULL, CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, 0, WS_POPUPWINDOW | WS_OVERLAPPED | WS_DLGFRAME, NHHO_NoSendQuitMessage | NHHO_HostControlsSize | NHHO_ScreenCenter, &pnhh)))
{
ARPHelp::Create(pnhh, this, _bDoubleBuffer, (Element**)&peHelp);
_pParser->CreateElement(L"freqhelp", peHelp, &pe);
}
else
{
DUITrace(">> Failed to create NativeHWNDHost for frequency info window.\n");
}
}
else
{
// Support information, add additional fields
LoadStringW(_pParser->GetHInstance(), IDS_SUPPORTTITLE, szTitle, DUIARRAYSIZE(szTitle));
if (SUCCEEDED(NativeHWNDHost::Create(szTitle, GetHWND(), NULL, CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, 0, WS_POPUPWINDOW | WS_OVERLAPPED | WS_DLGFRAME, NHHO_NoSendQuitMessage | NHHO_HostControlsSize | NHHO_ScreenCenter, &pnhh)))
{
ARPHelp::Create(pnhh, this, _bDoubleBuffer, (Element**)&peHelp);
_pParser->CreateElement(L"suphelp", peHelp, &pe);
// Get application info
APPINFODATA aid = {0};
// Query
aid.cbSize = sizeof(APPINFODATA);
aid.dwMask = AIM_DISPLAYNAME | AIM_VERSION | AIM_PUBLISHER | AIM_PRODUCTID |
AIM_REGISTEREDOWNER | AIM_REGISTEREDCOMPANY | AIM_SUPPORTURL |
AIM_SUPPORTTELEPHONE | AIM_HELPLINK | AIM_INSTALLLOCATION | AIM_INSTALLDATE |
AIM_COMMENTS | AIM_IMAGE | AIM_READMEURL | AIM_CONTACT | AIM_UPDATEINFOURL;
// There must be a selection
ARPItem* peSel = (ARPItem*)_peInstalledItemList->GetSelection();
peSel->_piia->GetAppInfo(&aid);
((ARPHelp*)peHelp)->_piia = peSel->_piia;
PrepareSupportInfo(peHelp, &aid);
// Clean up
ClearAppInfoData(&aid);
}
else
{
DUITrace(">> Failed to create NativeHWNDHost for support info window.\n");
}
}
if (pe && pnhh) // Fill contents using substitution
{
// Set visible and host
_pah = peHelp;
_bInModalMode = true;
EnableWindow(GetHWND(), FALSE);
pnhh->Host(peHelp);
peHelp->SetVisible(true);
peHelp->SetDefaultFocus();
// Do initial show
pnhh->ShowWindow();
}
pEvent->fHandled = true;
return;
}
}
else if (pEvent->uidType == Selector::SelectionChange)
{
SelectionChangeEvent* sce = (SelectionChangeEvent*)pEvent;
//
// NTRAID#NTBUG9-294015-2001/02/08-jeffreys
//
// If the user double-clicks, weird things can happen.
//
//
// NTRAID#NTBUG9-313888-2001/2/14-brianau
//
// This fix for 294015 caused more strange things to happen. The most notable
// is that sometimes you click a button and it remains depressed
// but nothing happens. Disabling this call to block double
// click input fixes this problem. We need to devise a better way
// of handling double-click input in DUI.
//
// _BlockDoubleClickInput();
if (sce->peTarget == _peOptionList)
{
// ARP options
StartDefer();
Element* peAddContentHeader = FindDescendentByName(this, L"addcontentheader");
ASSERT(peAddContentHeader != NULL);
if (sce->peNew->GetID() == _idChange)
{
if (!_bInstalledListFilled)
{
UpdateInstalledItems();
}
ChangePane(_peChangePane);
_peCurrentItemList = _peInstalledItemList;
_peInstalledItemList->SetContentString(L"");
PutFocusOnList(_peInstalledItemList);
}
else if (sce->peNew->GetID() == _idAddNew)
{
if (!_bPublishedListFilled)
{
WCHAR szTemp[1024];
LoadStringW(_pParser->GetHInstance(), IDS_WAITFEEDBACK, szTemp, DUIARRAYSIZE(szTemp));
_pePublishedItemList->SetContentString(szTemp);
SetElementAccessability(_pePublishedItemList, true, ROLE_SYSTEM_STATICTEXT, szTemp);
RePopulatePublishedItemList();
}
ChangePane(_peAddNewPane);
_peCurrentItemList = _pePublishedItemList;
PutFocusOnList(_pePublishedItemList);
}
else if (sce->peNew->GetID() == _idAddRmWin)
{
ChangePane(_peAddRmWinPane);
_peCurrentItemList = _peOCSetupItemList;
if (!_bOCSetupNeeded)
{
RunOCManager();
if (sce->peOld) {
_peOptionList->SetSelection(sce->peOld);
}
}
else
{
if (!_bOCSetupListFilled)
{
//_beginthread(PopulateAndRenderOCSetupItemList, 0, (void*)this);
if (!htPopulateAndRenderOCSetupItemList && g_fRun)
htPopulateAndRenderOCSetupItemList = CreateThread(NULL, 0, PopulateAndRenderOCSetupItemList, (void*)this, 0, NULL);
_bOCSetupListFilled = true;
}
PutFocusOnList(_peOCSetupItemList);
}
}
else if (sce->peNew->GetID() == _idPickApps)
{
ChangePane(_pePickAppPane);
_peCurrentItemList = _peClientTypeList;
PutFocusOnList(_peClientTypeList);
}
EndDefer();
}
else if (sce->peTarget == _peInstalledItemList)
{
if (sce->peOld)
{
sce->peOld->FindDescendent(ARPItem::_idRow[0])->SetEnabled(false);
}
if (sce->peNew)
{
sce->peNew->FindDescendent(ARPItem::_idRow[0])->RemoveLocalValue(EnabledProp);
}
}
pEvent->fHandled = true;
return;
}
else if (pEvent->uidType == Combobox::SelectionChange)
{
SelectionIndexChangeEvent* psice = (SelectionIndexChangeEvent*)pEvent;
if (psice->peTarget->GetID() == _idSortCombo)
{
SortList(psice->iNew, psice->iOld);
}
else if (psice->peTarget->GetID() == _idCategoryCombo)
{
_curCategory = psice->iNew;
if (_bPublishedComboFilled)
{
if (_bPublishedListFilled)
{
RePopulatePublishedItemList();
}
}
}
}
}
HWNDElement::OnEvent(pEvent);
}
void ARPFrame::OnKeyFocusMoved(Element* peFrom, Element* peTo)
{
if(peTo && IsDescendent(peTo))
{
peLastFocused = peTo;
}
Element::OnKeyFocusMoved(peFrom, peTo);
/* uncomment when JStall's message fixing is done
if (peTo != peLastFocused)
{
// transition focus-following floater element from old to new
if (!peTo)
peFloater->SetVisible(false);
else
{
Value* pvSize;
const SIZE* psize = peTo->GetExtent(&pvSize);
peFloater->SetWidth(psize->cx);
peFloater->SetHeight(psize->cy);
pvSize->Release();
POINT pt = { 0, 0 };
MapElementPoint(peTo, &pt, &pt);
peFloater->SetX(pt.x);
peFloater->SetY(pt.y);
if (!peLastFocused)
peFloater->SetVisible(true);
}
peLastFocused = peTo;
}
*/
}
void ARPFrame::OnPublishedListComplete()
{
Invoke(ARP_PUBLISHEDLISTCOMPLETE, NULL);
}
void ARPFrame::RePopulatePublishedItemList()
{
//_beginthread(::PopulateAndRenderPublishedItemList, 0, (void*)this);
if (!htPopulateAndRenderPublishedItemList && g_fRun)
{
// Disable the category combo until we are done populating the list
_pePublishedCategory->SetEnabled(false);
_bPublishedListFilled = false;
_pePublishedItemList->DestroyAll();
htPopulateAndRenderPublishedItemList = CreateThread(NULL, 0, PopulateAndRenderPublishedItemList, (void*)this, 0, NULL);
}
}
void ARPFrame::RePopulateOCSetupItemList()
{
if (!htPopulateAndRenderOCSetupItemList && g_fRun)
{
_peOCSetupItemList->DestroyAll();
_bOCSetupListFilled = false;
htPopulateAndRenderOCSetupItemList = CreateThread(NULL, 0, PopulateAndRenderOCSetupItemList, (void*)this, 0, NULL);
_bOCSetupListFilled = true;
}
}
bool ARPFrame::CanSetFocus()
{
if (_bInModalMode)
{
HWND hWnd = _pah->GetHost()->GetHWND();
FLASHWINFO fwi = {
sizeof(FLASHWINFO), // cbSize
hWnd, // hwnd
FLASHW_CAPTION, // flags
5, // uCount
75 // dwTimeout
};
FlashWindowEx(&fwi);
SetFocus(hWnd);
return false;
}
return true;
}
HRESULT TransferToCustomCB(ClientPicker *pe, LPARAM)
{
return pe->TransferToCustom();
}
HRESULT ApplyClientBlockCB(ClientBlock* pcb, LPARAM lParam)
{
return pcb->Apply((ARPFrame*)lParam);
}
bool ARPFrame::OnClose()
{
if (_peClientTypeList)
{
Element *peSelected = _peClientTypeList->GetSelection();
if (peSelected)
{
// Get all the client pickers in the user's selection
// to transfer their settings to the Custom pane.
// (This is a NOP if the current selection is itself the custom pane.)
TraverseTree<ClientPicker>(peSelected, TransferToCustomCB);
InitProgressDialog();
// To get the progress bar right, we apply in two passes.
// The first pass is "fake mode" where all we do is count up
// how much work we are going to do.
SetProgressFakeMode(true);
TraverseTree<ClientBlock>(this, ApplyClientBlockCB, (LPARAM)this);
// Okay now we know what the progress bar limit should be.
_dwProgressTotal = _dwProgressSoFar;
_dwProgressSoFar = 0;
// The second pass is "real mode" where we do the actualy work.
SetProgressFakeMode(false);
TraverseTree<ClientBlock>(this, ApplyClientBlockCB, (LPARAM)this);
EndProgressDialog();
}
}
return true;
}
void ARPFrame::InitProgressDialog()
{
TCHAR szBuf[MAX_PATH];
EndProgressDialog();
_dwProgressTotal = _dwProgressSoFar = 0;
if (SUCCEEDED(CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IProgressDialog, &_ppd))))
{
_ppd->SetAnimation(GetModuleHandle(TEXT("SHELL32")), 165);
LoadString(g_hinst, IDS_APPLYINGCLIENT, szBuf, SIZECHARS(szBuf));
_ppd->SetTitle(szBuf);
_ppd->StartProgressDialog(GetHostWindow(), NULL, PROGDLG_MODAL | PROGDLG_NOTIME | PROGDLG_NOMINIMIZE, NULL);
}
}
void ARPFrame::SetProgressDialogText(UINT ids, LPCTSTR pszName)
{
TCHAR szBuf[MAX_PATH];
TCHAR szFormat[MAX_PATH];
if (_ppd)
{
LoadString(g_hinst, ids, szFormat, SIZECHARS(szFormat));
wnsprintf(szBuf, SIZECHARS(szBuf), szFormat, pszName);
_ppd->SetLine(1, szBuf, FALSE, NULL);
_ppd->SetProgress(_dwProgressSoFar, _dwProgressTotal);
}
}
void ARPFrame::EndProgressDialog()
{
if (_ppd)
{
_ppd->StopProgressDialog();
_ppd->Release();
_ppd = NULL;
}
}
HRESULT ARPFrame::LaunchClientCommandAndWait(UINT ids, LPCTSTR pszName, LPTSTR pszCommand)
{
HRESULT hr = S_OK;
if (!_bFakeProgress)
{
if (_ppd && _ppd->HasUserCancelled())
{
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
}
else
{
SetProgressDialogText(ids, pszName);
PROCESS_INFORMATION pi;
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL;
if (CreateProcess(NULL, pszCommand, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
while (SHWaitForSendMessageThread(pi.hProcess, 1000) == WAIT_TIMEOUT)
{
if (_ppd && _ppd->HasUserCancelled())
{
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
break;
}
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
}
_dwProgressSoFar++;
return hr;
}
////////////////////////////////////////////////////////
// Caller thread-safe APIs (do any additional work on callers thread and then marshal)
// Sets the range for the total number of installed items
void ARPFrame::SetInstalledItemCount(UINT cItems)
{
Invoke(ARP_SETINSTALLEDITEMCOUNT, (void*)(UINT_PTR)cItems);
}
void ARPFrame::DecrementInstalledItemCount()
{
Invoke(ARP_DECREMENTINSTALLEDITEMCOUNT, NULL);
}
// Sets the range for the total number of installed items
void ARPFrame::SetPublishedItemCount(UINT cItems)
{
Invoke(ARP_SETPUBLISHEDITEMCOUNT, (void*)(UINT_PTR)cItems);
}
void ARPFrame::DecrementPublishedItemCount()
{
Invoke(ARP_DECREMENTPUBLISHEDITEMCOUNT, NULL);
}
// Inserts in items, sorted into the ARP list
void ARPFrame::InsertInstalledItem(IInstalledApp* piia)
{
if (piia == NULL)
{
Invoke(ARP_DONEINSERTINSTALLEDITEM, NULL);
}
else
{
// Setup marshalled call, do as much work as possible on caller thread
InsertItemData iid;
APPINFODATA aid = {0};
SLOWAPPINFO sai = {0};
// Query only for display name and support URL
aid.cbSize = sizeof(APPINFODATA);
aid.dwMask = AIM_DISPLAYNAME | AIM_VERSION | AIM_PUBLISHER | AIM_PRODUCTID |
AIM_REGISTEREDOWNER | AIM_REGISTEREDCOMPANY | AIM_SUPPORTURL |
AIM_SUPPORTTELEPHONE | AIM_HELPLINK | AIM_INSTALLLOCATION | AIM_INSTALLDATE |
AIM_COMMENTS | AIM_IMAGE | AIM_READMEURL | AIM_CONTACT | AIM_UPDATEINFOURL;
piia->GetAppInfo(&aid);
if(FAILED(piia->GetCachedSlowAppInfo(&sai)))
{
piia->GetSlowAppInfo(&sai);
}
// Set data
iid.piia = piia;
if (aid.pszDisplayName && aid.pszDisplayName[0])
{
// Title
CopyMemory(iid.pszTitle, aid.pszDisplayName, min(sizeof(iid.pszTitle), (wcslen(aid.pszDisplayName) + 1) * sizeof(WCHAR)));
// Image
if (aid.pszImage && aid.pszImage[0])
{
iid.iIconIndex = PathParseIconLocationW(aid.pszImage);
CopyMemory(iid.pszImage, aid.pszImage, min(sizeof(iid.pszImage), (wcslen(aid.pszImage) + 1) * sizeof(WCHAR)));
}
else if (sai.pszImage && sai.pszImage[0])
{
iid.iIconIndex = PathParseIconLocationW(sai.pszImage);
CopyMemory(iid.pszImage, sai.pszImage, min(sizeof(iid.pszImage), (wcslen(sai.pszImage) + 1) * sizeof(WCHAR)));
}
else
{
*iid.pszImage = NULL;
}
// Size, Frequency, and Last Used On
iid.ullSize = sai.ullSize;
iid.iTimesUsed = sai.iTimesUsed;
iid.ftLastUsed = sai.ftLastUsed;
// Possible actions (change, remove, etc.)
piia->GetPossibleActions(&iid.dwActions);
// Flag if support information is available
iid.bSupportInfo = ShowSupportInfo(&aid);
Invoke(ARP_INSERTINSTALLEDITEM, &iid);
}
else
// Adjust Status bar size.
{
DecrementInstalledItemCount();
}
// Free query memory
ClearAppInfoData(&aid);
}
}
void ARPFrame::InsertPublishedItem(IPublishedApp* pipa, bool bDuplicateName)
{
PUBAPPINFO* ppai;
APPINFODATA aid = {0};
InsertItemData iid= {0};
ppai = new PUBAPPINFO;
if (ppai == NULL)
{
return;
}
ppai->cbSize = sizeof(PUBAPPINFO);
ppai->dwMask = PAI_SOURCE | PAI_ASSIGNEDTIME | PAI_PUBLISHEDTIME | PAI_EXPIRETIME | PAI_SCHEDULEDTIME;
aid.cbSize = sizeof(APPINFODATA);
aid.dwMask = AIM_DISPLAYNAME | AIM_VERSION | AIM_PUBLISHER | AIM_PRODUCTID |
AIM_REGISTEREDOWNER | AIM_REGISTEREDCOMPANY | AIM_SUPPORTURL |
AIM_SUPPORTTELEPHONE | AIM_HELPLINK | AIM_INSTALLLOCATION | AIM_INSTALLDATE |
AIM_COMMENTS | AIM_IMAGE | AIM_READMEURL | AIM_CONTACT | AIM_UPDATEINFOURL;
pipa->GetAppInfo(&aid);
pipa->GetPublishedAppInfo(ppai);
// Title
if (bDuplicateName)
{
//
// Duplicate entries have their publisher name appended
// to the application name so that they can be differentiated
// from one another in the UI.
//
StringCchPrintf(iid.pszTitle,
ARRAYSIZE(iid.pszTitle),
L"%ls: %ls",
aid.pszDisplayName,
ppai->pszSource);
}
else
{
//
// iid.pszTitle, despite the name is a character buffer, not a pointer.
//
lstrcpyn(iid.pszTitle, aid.pszDisplayName, ARRAYSIZE(iid.pszTitle));
}
iid.pipa = pipa;
iid.ppai = ppai;
Invoke(ARP_INSERTPUBLISHEDITEM, &iid);
// Free query memory
ClearAppInfoData(&aid);
}
void ARPFrame::InsertOCSetupItem(COCSetupApp* pocsa)
{
APPINFODATA aid = {0};
InsertItemData iid= {0};
aid.cbSize = sizeof(APPINFODATA);
aid.dwMask = AIM_DISPLAYNAME;
pocsa->GetAppInfo(&aid);
iid.pocsa = pocsa;
// Title
CopyMemory(iid.pszTitle, aid.pszDisplayName, min(sizeof(iid.pszTitle), (wcslen(aid.pszDisplayName) + 1) * sizeof(WCHAR)));
Invoke(ARP_INSERTOCSETUPITEM, &iid);
// Free query memory
ClearAppInfoData(&aid);
}
void ARPFrame::FeedbackEmptyPublishedList()
{
Invoke(ARP_SETPUBLISHEDFEEDBACKEMPTY, 0);
}
void ARPFrame::DirtyInstalledListFlag()
{
_bInstalledListFilled=false;
// Refresh if we are on the published list
if (_peCurrentItemList == _peInstalledItemList)
{
UpdateInstalledItems();
}
}
void ARPFrame::DirtyPublishedListFlag()
{
_bPublishedListFilled=false;
// Refresh if we are on the published list
if (_peCurrentItemList == _pePublishedItemList)
{
RePopulatePublishedItemList();
}
}
void ARPFrame::PopulateCategoryCombobox()
{
Invoke(ARP_POPULATECATEGORYCOMBO, NULL);
}
LPCWSTR ARPFrame::GetCurrentPublishedCategory()
{
int iCurrentCategory = _curCategory;
if (iCurrentCategory == 0 || iCurrentCategory == CB_ERR || _psacl == NULL)
{
return NULL;
}
return _psacl->pCategory[iCurrentCategory - 1].pszCategory;
}
inline bool ARPFrame::ShowSupportInfo(APPINFODATA *paid)
{
if (_bSupportInfoRestricted)
{
return false;
}
if (paid->pszVersion && paid->pszVersion ||
paid->pszPublisher && paid->pszPublisher ||
paid->pszProductID && paid->pszProductID ||
paid->pszRegisteredOwner && paid->pszRegisteredOwner ||
paid->pszRegisteredCompany && paid->pszRegisteredCompany ||
paid->pszSupportUrl && paid->pszSupportUrl ||
paid->pszHelpLink && paid->pszHelpLink ||
paid->pszContact && paid->pszContact ||
paid->pszReadmeUrl && paid->pszReadmeUrl ||
paid->pszComments && paid->pszComments)
{
return TRUE;
}
return FALSE;
}
void ARPFrame::PrepareSupportInfo(Element* peHelp, APPINFODATA *paid)
{
DWORD dwAction = 0;
Element* pe;
pe = FindDescendentByName(peHelp, L"title");
pe->SetContentString(paid->pszDisplayName);
SetElementAccessability(pe, true, ROLE_SYSTEM_STATICTEXT, paid->pszDisplayName);
pe = FindDescendentByName(peHelp, L"prodname");
pe->SetContentString(paid->pszDisplayName);
SetElementAccessability(pe, true, ROLE_SYSTEM_STATICTEXT, paid->pszDisplayName);
ARPSupportItem* pasi;
pasi = (ARPSupportItem*) FindDescendentByName(peHelp, L"publisher");
pasi->SetAccValue(paid->pszPublisher);
pasi->SetURL(paid->pszSupportUrl);
FindDescendentByName(peHelp, L"version")->SetAccValue(paid->pszVersion);
FindDescendentByName(peHelp, L"contact")->SetAccValue(paid->pszContact);
pasi = (ARPSupportItem*) FindDescendentByName(peHelp, L"support");
pasi->SetAccValue(paid->pszHelpLink);
pasi->SetURL(paid->pszHelpLink);
FindDescendentByName(peHelp, L"telephone")->SetAccValue(paid->pszSupportTelephone);
pasi = (ARPSupportItem*) FindDescendentByName(peHelp, L"readme");
pasi->SetAccValue(paid->pszReadmeUrl);
pasi->SetURL(paid->pszReadmeUrl);
pasi = (ARPSupportItem*) FindDescendentByName(peHelp, L"update");
pasi->SetAccValue(paid->pszUpdateInfoUrl);
pasi->SetURL(paid->pszUpdateInfoUrl);
FindDescendentByName(peHelp, L"productID")->SetAccValue(paid->pszProductID);
FindDescendentByName(peHelp, L"regCompany")->SetAccValue(paid->pszRegisteredCompany);
FindDescendentByName(peHelp, L"regOwner")->SetAccValue(paid->pszRegisteredOwner);
FindDescendentByName(peHelp, L"comments")->SetAccValue(paid->pszComments);
((ARPHelp*)peHelp)->_piia->GetPossibleActions(&dwAction);
if (!(dwAction & APPACTION_REPAIR))
FindDescendentByName(peHelp, L"repairblock")->SetLayoutPos(LP_None);
}
extern "C" int __cdecl CompareElementDataName(const void* pA, const void* pB);
extern "C" int __cdecl CompareElementDataSize(const void* pA, const void* pB);
extern "C" int __cdecl CompareElementDataFreq(const void* pA, const void* pB);
extern "C" int __cdecl CompareElementDataLast(const void* pA, const void* pB);
CompareCallback ARPFrame::GetCompareFunction()
{
switch(CurrentSortType)
{
case SORT_SIZE: return CompareElementDataSize;
case SORT_TIMESUSED: return CompareElementDataFreq;
case SORT_LASTUSED: return CompareElementDataLast;
default: return CompareElementDataName;
}
}
void ARPFrame::SortList(int iNew, int iOld)
{
if ((iNew >= 0) && (iNew != CurrentSortType))
{
CurrentSortType = (SortType) iNew;
StartDefer();
if (((iNew != SORT_NAME) || (iOld != SORT_SIZE)) &&
((iNew != SORT_SIZE) || (iOld != SORT_NAME)))
{
Value* pvChildren;
ElementList* pel = _peInstalledItemList->GetChildren(&pvChildren);
if (NULL == pel)
{
EndDefer();
return;
}
for (UINT i = 0; i < pel->GetSize(); i++)
((ARPItem*) pel->GetItem(i))->SortBy(iNew, iOld);
pvChildren->Release();
}
_peInstalledItemList->SortChildren(GetCompareFunction());
if (!_peInstalledItemList->GetSelection())
{
Value* pv;
ElementList* peList = _peInstalledItemList->GetChildren(&pv);
if (NULL == peList)
{
EndDefer();
return;
}
_peInstalledItemList->SetSelection(peList->GetItem(0));
pv->Release();
}
EndDefer();
}
}
void ARPFrame::SelectInstalledApp(IInstalledApp* piia)
{
Value* pv;
ElementList* peList = _peInstalledItemList->GetChildren(&pv);
for (UINT i = 0; i < peList->GetSize(); i++)
{
ARPItem* pai = (ARPItem*) peList->GetItem(i);
if (pai->_piia == piia)
{
pai->SetKeyFocus();
break;
}
}
pv->Release();
}
// Selects an app adjacent in the list to piia if possible, or to the fallback otherwise.
// First preference is for the app immediately following piia, if available.
void ARPFrame::SelectClosestApp(IInstalledApp* piia)
{
Value* pv;
ElementList* peList = _peInstalledItemList->GetChildren(&pv);
for (UINT i = 0; i < peList->GetSize(); i++)
{
ARPItem* pai = (ARPItem*) peList->GetItem(i);
if (pai->_piia == piia)
{
Element* peFocus = FallbackFocus();
// If there is an app after piia, select it.
if ((i + 1) < peList->GetSize())
{
peFocus = (Element*) peList->GetItem(i + 1);
}
// else if there is an app before piia, select it
else if (i != 0)
{
peFocus = (Element*) peList->GetItem(i - 1);
}
peFocus->SetKeyFocus();
break;
}
}
pv->Release();
}
////////////////////////////////////////////////////////
// Callee thread-safe invoke (override)
void ARPFrame::OnInvoke(UINT nType, void* pData)
{
// We are shutting down, ignore any requests from other threads
if (!g_fRun)
return;
// Initialize ID cache if first pass
if (!ARPItem::_idTitle)
{
ARPItem::_idTitle = StrToID(L"title");
ARPItem::_idIcon = StrToID(L"icon");
ARPItem::_idSize = StrToID(L"size");
ARPItem::_idFreq = StrToID(L"freq");
ARPItem::_idLastUsed = StrToID(L"lastused");
ARPItem::_idInstalled = StrToID(L"installed");
ARPItem::_idExInfo = StrToID(L"exinfo");
ARPItem::_idSupInfo = StrToID(L"supinfo");
ARPItem::_idItemAction = StrToID(L"itemaction");
ARPItem::_idRow[0] = StrToID(L"row1");
ARPItem::_idRow[1] = StrToID(L"row2");
ARPItem::_idRow[2] = StrToID(L"row3");
}
switch (nType)
{
case ARP_SETINSTALLEDITEMCOUNT:
// pData is item count
_cMaxInstalledItems = (int)(INT_PTR)pData;
break;
case ARP_DECREMENTINSTALLEDITEMCOUNT:
_cMaxInstalledItems--;
break;
case ARP_SETPUBLISHEDITEMCOUNT:
// pData is item count
_cMaxPublishedItems = (int)(INT_PTR)pData;
break;
case ARP_DECREMENTPUBLISHEDITEMCOUNT:
_cMaxPublishedItems--;
break;
case ARP_SETPUBLISHEDFEEDBACKEMPTY:
{
WCHAR szTemp[1024];
if (_bTerminalServer)
{
// We are running terminal server
// This means no applications are displayed by design (not because there aren't any available)
LoadStringW(_pParser->GetHInstance(), IDS_TERMSERVFEEDBACK, szTemp, DUIARRAYSIZE(szTemp));
}
else
{
LoadStringW(_pParser->GetHInstance(), IDS_EMPTYFEEDBACK, szTemp, DUIARRAYSIZE(szTemp));
}
_pePublishedItemList->SetContentString(szTemp);
SetElementAccessability(_pePublishedItemList, true, ROLE_SYSTEM_STATICTEXT, szTemp);
}
break;
case ARP_INSERTINSTALLEDITEM:
{
WCHAR szTemp[1024] = {0};
// pData is InsertItemData struct
InsertItemData* piid = (InsertItemData*)pData;
StartDefer();
// Create ARP item
DUIAssertNoMsg(_pParser);
ARPItem* peItem;
Element* pe;
if (_hdsaInstalledItems == NULL)
{
LoadStringW(_pParser->GetHInstance(), IDS_PLEASEWAIT, szTemp, DUIARRAYSIZE(szTemp));
_hdsaInstalledItems = DSA_Create(sizeof(ARPItem*), _cMaxInstalledItems);
_peInstalledItemList->SetContentString(szTemp);
}
_pParser->CreateElement(L"installeditem", NULL, (Element**)&peItem);
peItem->_paf = this;
// Add appropriate change, remove buttons
Element* peAction = NULL;
if (!(piid->dwActions & APPACTION_MODIFYREMOVE))
{
// It isn't marked with modify/remove (the default)
// Somebody gave us some special instructions from the registry
if (!(piid->dwActions & APPACTION_UNINSTALL))
{
// NoRemove is set to 1
if (piid->dwActions & APPACTION_MODIFY)
{
// NoModify is not set so we can show the change button
_pParser->CreateElement(L"installeditemchangeonlyaction", NULL, &peAction);
if (!ARPItem::_idChg)
{
ARPItem::_idChg = StrToID(L"chg");
}
LoadStringW(_pParser->GetHInstance(), IDS_HELPCHANGE, szTemp, DUIARRAYSIZE(szTemp));
}
}
else if (!(piid->dwActions & APPACTION_MODIFY))
{
// NoModify is set to 1
// The only way we get here is if NoRemove is not set
// so we don't have to check it again
_pParser->CreateElement(L"installeditemremoveonlyaction", NULL, &peAction);
if (!ARPItem::_idRm)
{
ARPItem::_idRm = StrToID(L"rm");
}
LoadStringW(_pParser->GetHInstance(), IDS_HELPREMOVE, szTemp, DUIARRAYSIZE(szTemp));
}
else
{
// Just display both Change and Remove buttons
_pParser->CreateElement(L"installeditemdoubleaction", NULL, &peAction);
if (!ARPItem::_idChg)
{
ARPItem::_idChg = StrToID(L"chg");
ARPItem::_idRm = StrToID(L"rm");
}
LoadStringW(_pParser->GetHInstance(), IDS_HELPCHANGEORREMOVE, szTemp, DUIARRAYSIZE(szTemp));
}
}
else
{
// Display the default "Change/Remove" button
_pParser->CreateElement(L"installeditemsingleaction", NULL, &peAction);
if (!ARPItem::_idChgRm)
ARPItem::_idChgRm = StrToID(L"chgrm");
LoadStringW(_pParser->GetHInstance(), IDS_HELPCHANGEREMOVE, szTemp, DUIARRAYSIZE(szTemp));
}
// Common steps for all cases above
if (peAction)
{
// If peAction is not set, we are not displaying any buttons...
pe = FindDescendentByName(peItem, L"instruct");
pe->SetContentString(szTemp);
SetElementAccessability(pe, true, ROLE_SYSTEM_STATICTEXT, szTemp);
peItem->FindDescendent(ARPItem::_idItemAction)->Add(peAction);
}
// Support information
if (!piid->bSupportInfo)
peItem->FindDescendent(ARPItem::_idSupInfo)->SetLayoutPos(LP_None);
// Set fields
// Installed app interface pointer
peItem->_piia = piid->piia;
peItem->_piia->AddRef();
// should just be call into the peItem: peItem->SetTimesUsed(piid->iTimesUsed); etc.
peItem->_iTimesUsed = piid->iTimesUsed;
peItem->_ftLastUsed = piid->ftLastUsed;
peItem->_ullSize = piid->ullSize;
// Title
Element* peField = peItem->FindDescendent(ARPItem::_idTitle);
DUIAssertNoMsg(peField);
peField->SetContentString(piid->pszTitle);
SetElementAccessability(peField, true, ROLE_SYSTEM_STATICTEXT, piid->pszTitle);
SetElementAccessability(peItem, true, ROLE_SYSTEM_LISTITEM, piid->pszTitle);
// Icon
if (piid->pszImage)
{
HICON hIcon;
ExtractIconExW(piid->pszImage, piid->iIconIndex, NULL, &hIcon, 1);
if (hIcon)
{
peField = peItem->FindDescendent(ARPItem::_idIcon);
DUIAssertNoMsg(peField);
Value* pvIcon = Value::CreateGraphic(hIcon);
if (NULL != pvIcon)
{
peField->SetValue(Element::ContentProp, PI_Local, pvIcon); // Element takes ownership (will destroy)
pvIcon->Release();
}
}
}
*szTemp = NULL;
// Size
peField = peItem->FindDescendent(ARPItem::_idSize);
DUIAssertNoMsg(peField);
if (IsValidSize(piid->ullSize))
{
WCHAR szMBLabel[5] = L"MB";
WCHAR szSize[15] = {0};
double fSize = (double)(__int64)piid->ullSize;
fSize /= 1048576.; // 1MB
LoadStringW(_pParser->GetHInstance(), IDS_SIZEUNIT, szMBLabel, DUIARRAYSIZE(szMBLabel));
if (fSize > 100.)
{
StringCchPrintfW(szTemp, ARRAYSIZE(szTemp), L"%d", (__int64)fSize); // Clip
}
else
{
StringCchPrintfW(szTemp, ARRAYSIZE(szTemp), L"%.2f", fSize);
}
// Format the number for the current user's locale
if (GetNumberFormat(LOCALE_USER_DEFAULT, 0, szTemp, NULL, szSize, DUIARRAYSIZE(szSize)) == 0)
{
lstrcpyn(szSize, szTemp, DUIARRAYSIZE(szSize));
}
if (SUCCEEDED(StringCchCat(szSize, ARRAYSIZE(szSize), szMBLabel)))
{
peField->SetContentString(szSize);
SetElementAccessability(peField, true, ROLE_SYSTEM_STATICTEXT, szTemp);
}
}
else
{
peField->SetVisible(false);
FindDescendentByName(peItem, L"sizelabel")->SetVisible(false);
}
// Frequency
peField = peItem->FindDescendent(ARPItem::_idFreq);
DUIAssertNoMsg(peField);
if (IsValidFrequency(piid->iTimesUsed))
{
if (piid->iTimesUsed <= 2)
LoadStringW(_pParser->GetHInstance(), IDS_USEDREARELY, szTemp, DUIARRAYSIZE(szTemp));
else if (piid->iTimesUsed <= 10)
LoadStringW(_pParser->GetHInstance(), IDS_USEDOCCASIONALLY, szTemp, DUIARRAYSIZE(szTemp));
else
LoadStringW(_pParser->GetHInstance(), IDS_USEDFREQUENTLY, szTemp, DUIARRAYSIZE(szTemp));
peField->SetContentString(szTemp);
SetElementAccessability(peField, true, ROLE_SYSTEM_STATICTEXT, szTemp);
}
else
{
peField->SetVisible(false);
FindDescendentByName(peItem, L"freqlabel")->SetVisible(false);
}
// Last used on
peField = peItem->FindDescendent(ARPItem::_idLastUsed);
DUIAssertNoMsg(peField);
if (IsValidFileTime(piid->ftLastUsed))
{
LPWSTR szDate;
SYSTEMTIME stLastUsed;
DWORD dwDateSize = 0;
BOOL bFailed=FALSE;
// Get the date it was last used on
FileTimeToSystemTime(&piid->ftLastUsed, &stLastUsed);
dwDateSize = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastUsed, NULL, NULL, dwDateSize);
if (dwDateSize)
{
szDate = new WCHAR[dwDateSize];
if (szDate)
{
dwDateSize = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastUsed, NULL, szDate, dwDateSize);
if (dwDateSize)
{
peField->SetContentString(szDate);
SetElementAccessability(peField, true, ROLE_SYSTEM_STATICTEXT, szDate);
}
else
{
bFailed=TRUE;
}
delete szDate;
}
else
{
bFailed=TRUE;
}
}
else
{
bFailed=TRUE;
}
if (bFailed)
{
peField->SetVisible(false);
FindDescendentByName(peItem, L"lastlabel")->SetVisible(false);
}
}
else
{
peField->SetVisible(false);
FindDescendentByName(peItem, L"lastlabel")->SetVisible(false);
}
// Insert item into DSA
int cNum = DSA_InsertItem(_hdsaInstalledItems, INT_MAX, &peItem);
// Insert failed
if (cNum < 0)
{
_cMaxInstalledItems--;
// We're out of items to insert so remove the wait string
if (!_cMaxInstalledItems)
{
_peInstalledItemList->SetContentString(L"");
}
}
EndDefer();
}
break;
case ARP_DONEINSERTINSTALLEDITEM:
{
DUITrace(">> ARP_DONEINSERTINSTALLEDITEM STARTED.\n");
StartDefer();
if (_hdsaInstalledItems != NULL)
{
int iMax = DSA_GetItemCount(_hdsaInstalledItems);
// Just to be safe so if all items get removed we won't be
// stuck with the please wait string.
_peInstalledItemList->SetContentString(L"");
for (int i=0; i < iMax; i++)
{
ARPItem* aItem;
if (DSA_GetItem(_hdsaInstalledItems, i, &aItem))
{
_peInstalledItemList->Add(aItem, GetCompareFunction());
}
}
DSA_Destroy(_hdsaInstalledItems);
_hdsaInstalledItems = NULL;
// Set focus to first item
// once list is populated, move focus to list
GetNthChild(_peInstalledItemList, 0)->SetKeyFocus();
_bInstalledListFilled = true;
}
EndDefer();
DUITrace(">> ARP_DONEINSERTINSTALLEDITEM DONE.\n");
}
break;
case ARP_INSERTPUBLISHEDITEM:
{
WCHAR szTemp[MAX_PATH] = {0};
InsertItemData* piid = (InsertItemData*)pData;
StartDefer();
// Need a DSA so we can add them all to the list at one time to avoid
// having lots of redrawing of the layout. This method is much much faster.
if (_hdsaPublishedItems == NULL)
{
LoadStringW(_pParser->GetHInstance(), IDS_PLEASEWAIT, szTemp, DUIARRAYSIZE(szTemp));
_hdsaPublishedItems = DSA_Create(sizeof(ARPItem*), _cMaxPublishedItems);
_pePublishedItemList->SetContentString(szTemp);
}
// Create ARP item
DUIAssertNoMsg(_pParser);
ARPItem* peItem;
Element* pe;
_pParser->CreateElement(L"publisheditem", NULL, (Element**)&peItem);
peItem->_paf = this;
// Add appropriate change, remove buttons
Element* peAction = NULL;
_pParser->CreateElement(L"publisheditemsingleaction", NULL, &peAction);
if (!ARPItem::_idAdd)
ARPItem::_idAdd = StrToID(L"add");
peItem->FindDescendent(ARPItem::_idItemAction)->Add(peAction);
if (S_OK == piid->pipa->IsInstalled())
{
peItem->ShowInstalledString(TRUE);
}
// Published app interface pointer
peItem->_pipa = piid->pipa;
peItem->_pipa->AddRef();
peItem->_ppai = piid->ppai;
// Title
Element* peField = peItem->FindDescendent(ARPItem::_idTitle);
DUIAssertNoMsg(peField);
peField->SetContentString(piid->pszTitle);
SetElementAccessability(peField, true, ROLE_SYSTEM_STATICTEXT, piid->pszTitle);
SetElementAccessability(peItem, true, ROLE_SYSTEM_LISTITEM, piid->pszTitle);
// Icon
if (piid->pszImage)
{
HICON hIcon;
ExtractIconExW(piid->pszImage, NULL, NULL, &hIcon, 1);
if (hIcon)
{
peField = peItem->FindDescendent(ARPItem::_idIcon);
DUIAssertNoMsg(peField);
Value* pvIcon = Value::CreateGraphic(hIcon);
peField->SetValue(Element::ContentProp, PI_Local, pvIcon); // Element takes ownership (will destroy)
pvIcon->Release();
}
}
// Insert into DSA, alphabetically
if (_hdsaPublishedItems != NULL)
{
int iInsert;
int cNum = DSA_GetItemCount(_hdsaPublishedItems);
// Search for place to insert
for (iInsert = 0; iInsert < cNum; iInsert++)
{
ARPItem* fItem;
if (DSA_GetItem(_hdsaPublishedItems, iInsert, &fItem))
{
Value* pvTitle;
pe = fItem->FindDescendent(ARPItem::_idTitle);
DUIAssertNoMsg(pe);
if (wcscmp(pe->GetContentString(&pvTitle), piid->pszTitle) > 0)
{
pvTitle->Release();
break;
}
pvTitle->Release();
}
}
// Insert item into DSA
if (DSA_InsertItem(_hdsaPublishedItems, iInsert, &peItem) < 0)
{
// Failed to insert the item
// Bring the total down by 1
_cMaxPublishedItems--;
}
}
// We only want to start actually adding the items to the list
// when we reach our last item. If we insert each item into the list
// as we process these messages, it can take upwards of 4 minutes to populate
// if there are a lot of items.
if (_hdsaPublishedItems != NULL &&
DSA_GetItemCount(_hdsaPublishedItems) == _cMaxPublishedItems)
{
for (int i=0; i < _cMaxPublishedItems; i++)
{
ARPItem* aItem;
if (DSA_GetItem(_hdsaPublishedItems, i, &aItem))
{
_pePublishedItemList->Insert(aItem, i);
}
}
DSA_Destroy(_hdsaPublishedItems);
_hdsaPublishedItems = NULL;
_pePublishedItemList->SetSelection(GetNthChild(_pePublishedItemList, 0));
}
EndDefer();
}
break;
case ARP_INSERTOCSETUPITEM:
{
WCHAR szTemp[MAX_PATH] = {0};
InsertItemData* piid = (InsertItemData*)pData;
StartDefer();
// Create ARP item
DUIAssertNoMsg(_pParser);
ARPItem* peItem;
if (SUCCEEDED(_pParser->CreateElement(L"ocsetupitem", NULL, (Element**)&peItem)))
{
peItem->_paf = this;
if (!ARPItem::_idConfigure)
ARPItem::_idConfigure = StrToID(L"configure");
// Add appropriate change, remove buttons
Element* peAction = NULL;
if (SUCCEEDED(_pParser->CreateElement(L"ocsetupitemsingleaction", NULL, &peAction)))
{
Element *peItemAction = peItem->FindDescendent(ARPItem::_idItemAction);
if (NULL != peItemAction && SUCCEEDED(peItemAction->Add(peAction)))
{
peAction = NULL; // Action successfully added.
// OCSetup pointer
peItem->_pocsa = piid->pocsa;
// Title
Element* peField = peItem->FindDescendent(ARPItem::_idTitle);
DUIAssertNoMsg(peField);
peField->SetContentString(piid->pszTitle);
SetElementAccessability(peField, true, ROLE_SYSTEM_STATICTEXT, piid->pszTitle);
SetElementAccessability(peItem, true, ROLE_SYSTEM_LISTITEM, piid->pszTitle);
// Insert into list, alphabetically
Value* pvElList;
ElementList* peElList = _peOCSetupItemList->GetChildren(&pvElList);
Value* pvTitle;
Element* pe;
UINT iInsert = 0;
if (peElList)
{
for (; iInsert < peElList->GetSize(); iInsert++)
{
pe = peElList->GetItem(iInsert)->FindDescendent(ARPItem::_idTitle);
DUIAssertNoMsg(pe);
if (wcscmp(pe->GetContentString(&pvTitle), piid->pszTitle) > 0)
{
pvTitle->Release();
break;
}
pvTitle->Release();
}
}
pvElList->Release();
// Insert item into list
if (FAILED(_peOCSetupItemList->Insert(peItem, iInsert)))
{
//
// Failed to insert item into list. Need to delete
// the OCSetupApp object.
//
delete peItem->_pocsa;
peItem->_pocsa = NULL;
}
else
{
peItem = NULL; // Successfully added to list.
_peOCSetupItemList->SetSelection(GetNthChild(_peOCSetupItemList, 0));
}
}
if (NULL != peAction)
{
peAction->Destroy();
peAction = NULL;
}
}
if (NULL != peItem)
{
peItem->Destroy();
peItem = NULL;
}
}
EndDefer();
}
break;
case ARP_POPULATECATEGORYCOMBO:
{
UINT i;
WCHAR szTemp[1024];
UINT iSelection = 0; // Default to "All Categories"
SHELLAPPCATEGORY *psac = _psacl->pCategory;
LoadStringW(_pParser->GetHInstance(), IDS_ALLCATEGORIES, szTemp, DUIARRAYSIZE(szTemp));
_pePublishedCategory->AddString(szTemp);
szTemp[0] = 0;
ARPGetPolicyString(L"DefaultCategory", szTemp, ARRAYSIZE(szTemp));
StartDefer();
for (i = 0; i < _psacl->cCategories; i++, psac++)
{
if (psac->pszCategory)
{
_pePublishedCategory->AddString(psac->pszCategory);
if (0 == lstrcmpi(psac->pszCategory, szTemp))
{
//
// Policy says default to this category.
// i + 1 is required since element 0 is "All Categories"
// and is ALWAYS present at element 0.
//
iSelection = i + 1;
}
}
}
_pePublishedCategory->SetSelection(iSelection);
EndDefer();
}
break;
case ARP_PUBLISHEDLISTCOMPLETE:
{
_pePublishedCategory->SetEnabled(true);
break;
}
}
}
void ARPFrame::ManageAnimations()
{
BOOL fAnimate = TRUE;
SystemParametersInfo(SPI_GETMENUANIMATION, 0, &fAnimate, 0);
if (fAnimate)
{
if (!IsFrameAnimationEnabled())
{
_bAnimationEnabled = true;
EnableAnimations();
}
}
else
{
if (IsFrameAnimationEnabled())
{
_bAnimationEnabled = false;
DisableAnimations();
}
}
DUIAssertNoMsg((fAnimate != FALSE) == IsFrameAnimationEnabled());
}
HRESULT CalculateWidthCB(ClientPicker* pcp, LPARAM)
{
pcp->CalculateWidth();
return S_OK;
}
LRESULT ARPFrame::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_THEMECHANGED:
case WM_SETTINGCHANGE:
{
LockWindowUpdate(_pnhh->GetHWND());
Parser* pOldStyle = _pParserStyle;
Parser* pNewStyle = NULL;
if (!pOldStyle)
break;
// System parameter changing, reload style sheets so to sync
// up with changes
if (_fThemedStyle)
{
for (int i = FIRSTHTHEME; i <= LASTHTHEME; i++)
{
if (_arH[i])
{
CloseThemeData(_arH[i]);
_arH[i] = NULL;
}
}
}
CreateStyleParser(&pNewStyle);
// Replace all style sheets
if (pNewStyle)
{
Parser::ReplaceSheets(this, pOldStyle, pNewStyle);
}
// New style parser
_pParserStyle = pNewStyle;
// Destroy old
pOldStyle->Destroy();
// Animation setting may have changed
ManageAnimations();
TraverseTree<ClientPicker>(this, CalculateWidthCB);
LockWindowUpdate(NULL);
}
break;
}
return HWNDElement::WndProc(hWnd, uMsg, wParam, lParam);
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* ARPFrame::Class = NULL;
HRESULT ARPFrame::Register()
{
return ClassInfo<ARPFrame,HWNDElement>::Register(L"ARPFrame", NULL, 0);
}
////////////////////////////////////////////////////////
// ARPItem class
////////////////////////////////////////////////////////
// ARP item IDs
ATOM ARPItem::_idTitle = 0;
ATOM ARPItem::_idIcon = 0;
ATOM ARPItem::_idSize = 0;
ATOM ARPItem::_idFreq = 0;
ATOM ARPItem::_idLastUsed = 0;
ATOM ARPItem::_idExInfo = 0;
ATOM ARPItem::_idInstalled = 0;
ATOM ARPItem::_idChgRm = 0;
ATOM ARPItem::_idChg = 0;
ATOM ARPItem::_idRm = 0;
ATOM ARPItem::_idAdd = 0;
ATOM ARPItem::_idConfigure = 0;
ATOM ARPItem::_idSupInfo = 0;
ATOM ARPItem::_idItemAction = 0;
ATOM ARPItem::_idRow[3] = { 0, 0, 0 };
////////////////////////////////////////////////////////
// ARPItem
HRESULT ARPItem::Create(OUT Element** ppElement)
{
*ppElement = NULL;
ARPItem* pai = HNew<ARPItem>();
if (!pai)
return E_OUTOFMEMORY;
HRESULT hr = pai->Initialize();
if (FAILED(hr))
{
pai->Destroy();
return hr;
}
*ppElement = pai;
return S_OK;
}
HRESULT ARPItem::Initialize()
{
_piia = NULL; // Init before base in event of failure (invokes desstructor)
_pipa = NULL; // Init before base in event of failure (invokes desstructor)
// Do base class initialization
HRESULT hr = Button::Initialize(AE_MouseAndKeyboard);
if (FAILED(hr))
return hr;
return S_OK;
}
ARPItem::~ARPItem()
{
if (_piia)
_piia->Release();
if (_pipa)
_pipa->Release();
if (_pocsa)
delete _pocsa;
if (_ppai)
{
ClearPubAppInfo(_ppai);
delete _ppai;
}
}
void ARPItem::ShowInstalledString(BOOL bInstalled)
{
WCHAR szTemp[MAX_PATH] = L"";
Element* pe = FindDescendent(ARPItem::_idInstalled);
if (pe != NULL)
{
if (bInstalled)
{
LoadStringW(g_hinst, IDS_INSTALLED, szTemp, DUIARRAYSIZE(szTemp));
}
pe->SetContentString(szTemp);
SetElementAccessability(pe, true, ROLE_SYSTEM_STATICTEXT, szTemp);
}
}
HWND _CreateTransparentStubWindow(HWND hwndParent);
////////////////////////////////////////////////////////
// Generic eventing
void ARPItem::OnEvent(Event* pEvent)
{
// Handle only bubbled generic events
if (pEvent->uidType == Element::KeyboardNavigate)
{
KeyboardNavigateEvent* pkne = (KeyboardNavigateEvent*)pEvent;
if (pkne->iNavDir & NAV_LOGICAL)
{
if (pEvent->nStage == GMF_DIRECT)
{
}
}
else
{
if (pEvent->nStage == GMF_ROUTED)
{
pEvent->fHandled = true;
KeyboardNavigateEvent kne;
kne.uidType = Element::KeyboardNavigate;
kne.peTarget = this;
kne.iNavDir = pkne->iNavDir;
FireEvent(&kne); // Will route and bubble
}
return;
}
}
if (pEvent->nStage == GMF_BUBBLED)
{
if (pEvent->uidType == Button::Click)
{
ButtonClickEvent* pbce = (ButtonClickEvent*)pEvent;
ATOM id = pbce->peTarget->GetID();
if (id == _idChgRm || id == _idRm || id == _idChg || id == _idAdd || id == _idConfigure)
{
HWND hwndStub = NULL;
HWND hwndHost = NULL;
DUIAssertNoMsg(_paf);
if (_paf)
{
hwndHost = _paf->GetHostWindow();
}
if (hwndHost)
{
hwndStub = _CreateTransparentStubWindow(hwndHost);
EnableWindow(hwndHost, FALSE);
SetActiveWindow(hwndStub);
}
if (id == _idAdd)
{
HRESULT hres = S_OK;
// Does the app have an expired publishing time?
if (_ppai->dwMask & PAI_EXPIRETIME)
{
// Yes, it does. Let's compare the expired time with our current time
SYSTEMTIME stCur = {0};
GetLocalTime(&stCur);
// Is "now" later than the expired time?
if (CompareSystemTime(&stCur, &_ppai->stExpire) > 0)
{
// Yes, warn the user and return failure
ShellMessageBox(g_hinst, hwndHost, MAKEINTRESOURCE(IDS_EXPIRED),
MAKEINTRESOURCE(IDS_ARPTITLE), MB_OK | MB_ICONEXCLAMATION);
hres = E_FAIL;
}
}
// if hres is not set by the above code, preceed with installation
if (hres == S_OK)
{
HCURSOR hcur = ::SetCursor(LoadCursor(NULL, IDC_WAIT));
// On NT, let Terminal Services know that we are about to install an application.
// NOTE: This function should be called no matter the Terminal Services
// is running or not.
BOOL bPrevMode = TermsrvAppInstallMode();
SetTermsrvAppInstallMode(TRUE);
if (SUCCEEDED(_pipa->Install(NULL)))
{
// Show this item as installed
ShowInstalledString(TRUE);
// update installed items list
_paf->DirtyInstalledListFlag();
}
SetTermsrvAppInstallMode(bPrevMode);
::SetCursor(hcur);
}
}
else
{
HRESULT hr = E_FAIL;
if ((id == _idChgRm) || (id == _idRm))
hr = _piia->Uninstall(hwndHost);
else if (id == _idChg)
hr = _piia->Modify(hwndHost);
if (SUCCEEDED(hr))
{
if (S_FALSE == _piia->IsInstalled())
{
_paf->DirtyPublishedListFlag();
}
}
}
if (id == _idConfigure)
{
_pocsa->Run();
_paf->RePopulateOCSetupItemList();
}
if (hwndHost)
{
if (!_piia)
{
EnableWindow(hwndHost, TRUE);
SetForegroundWindow(hwndHost);
}
if (hwndStub)
{
DestroyWindow(hwndStub);
}
EnableWindow(hwndHost, TRUE);
}
if (_piia)
{
if (S_OK == _piia->IsInstalled())
{
SetKeyFocus();
}
else
{
// remove from installed items list
_paf->SelectClosestApp(_piia);
Destroy();
}
}
pEvent->fHandled = true;
return;
}
}
}
Button::OnEvent(pEvent);
}
////////////////////////////////////////////////////////
// System events
void ARPItem::OnPropertyChanged(PropertyInfo* ppi, int iIndex, Value* pvOld, Value* pvNew)
{
if (IsProp(Selected))
{
// Display of extended information
Element* peExInfo = FindDescendent(_idExInfo);
DUIAssertNoMsg(peExInfo);
peExInfo->SetLayoutPos(pvNew->GetBool() ? BLP_Top : LP_None);
// Do default processing in this case
}
Button::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
}
void GetOrder(int iSortBy, int* iOrder)
{
switch (iSortBy)
{
case SORT_NAME:
case SORT_SIZE: iOrder[0] = 0; iOrder[1] = 1; iOrder[2] = 2; break;
case SORT_TIMESUSED: iOrder[0] = 1; iOrder[1] = 0; iOrder[2] = 2; break;
case SORT_LASTUSED: iOrder[0] = 2; iOrder[1] = 0; iOrder[2] = 1; break;
}
}
void ARPItem::SortBy(int iNew, int iOld)
{
Element* pe[3][2]; // size, timesused, lastused
int iOrderOld[3]; // size, timesused, lastused
int iOrderNew[3]; // size, timesused, lastused
GetOrder(iOld, iOrderOld);
GetOrder(iNew, iOrderNew);
//
// First get all the DUI elements to be sorted. If we
// can't get all of them, this sort fails.
//
bool bAllFound = true;
int i;
Element* peRow[3]; // row1, row2, row3
for (i = 0; i < ARRAYSIZE(peRow); i++)
{
if (iOrderOld[i] != iOrderNew[i])
{
peRow[i] = FindDescendent(ARPItem::_idRow[i]);
if (NULL == peRow[i])
{
bAllFound = false;
}
}
}
if (bAllFound)
{
for (i = 0; i < ARRAYSIZE(iOrderOld); i++) // loop through rows
{
int row = iOrderOld[i];
if (row == iOrderNew[i])
iOrderNew[i] = -1;
else
{
DUIAssertNoMsg(NULL != peRow[i]);
Value* pvChildren;
ElementList* pel;
pel = peRow[i]->GetChildren(&pvChildren);
pe[row][0] = pel->GetItem(0);
pe[row][1] = pel->GetItem(1);
pvChildren->Release();
}
}
for (i = 0; i < 3; i++)
{
int row = iOrderNew[i];
if (row != -1) // meaning that this row doesn't change
peRow[i]->Add(pe[row], 2);
}
}
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* ARPItem::Class = NULL;
HRESULT ARPItem::Register()
{
return ClassInfo<ARPItem,Button>::Register(L"ARPItem", NULL, 0);
}
////////////////////////////////////////////////////////
// ARPHelp
////////////////////////////////////////////////////////
HRESULT ARPHelp::Create(OUT Element** ppElement)
{
UNREFERENCED_PARAMETER(ppElement);
DUIAssertForce("Cannot instantiate an HWND host derived Element via parser. Must use substitution.");
return E_NOTIMPL;
}
HRESULT ARPHelp::Create(NativeHWNDHost* pnhh, ARPFrame* paf, bool bDblBuffer, OUT Element** ppElement)
{
*ppElement = NULL;
ARPHelp* pah = HNew<ARPHelp>();
if (!pah)
return E_OUTOFMEMORY;
HRESULT hr = pah->Initialize(pnhh, paf, bDblBuffer);
if (FAILED(hr))
{
pah->Destroy();
return hr;
}
*ppElement = pah;
return S_OK;
}
HRESULT ARPHelp::Initialize(NativeHWNDHost* pnhh, ARPFrame* paf, bool bDblBuffer)
{
// Do base class initialization
HRESULT hr = HWNDElement::Initialize(pnhh->GetHWND(), bDblBuffer, 0);
if (FAILED(hr))
return hr;
// Initialize
// SetActive(AE_MouseAndKeyboard);
_pnhh = pnhh;
_paf = paf;
return S_OK;
}
void ARPHelp::SetDefaultFocus()
{
Element* pe = FindDescendentByName(this, L"close");
if (pe)
{
pe->SetKeyFocus();
}
}
////////////////////////////////////////////////////////
// Generic eventing
void ARPHelp::OnEvent(Event* pEvent)
{
// Handle only bubbled generic events
if (pEvent->nStage == GMF_BUBBLED)
{
if (pEvent->uidType == Button::Click)
{
ATOM id = pEvent->peTarget->GetID();
if (id == StrToID(L"repair"))
_piia->Repair(NULL);
if (pEvent->peTarget->GetID() == StrToID(L"close"))
{
_pnhh->DestroyWindow();
}
pEvent->fHandled = true;
return;
}
}
HWNDElement::OnEvent(pEvent);
}
void ARPHelp::OnDestroy()
{
HWNDElement::OnDestroy();
if (_paf)
{
_paf->SetModalMode(false);
}
}
ARPHelp::~ARPHelp()
{
if (_paf)
{
EnableWindow(_paf->GetHWND(), TRUE);
SetFocus(_paf->GetHWND());
_paf->RestoreKeyFocus();
}
if (_pnhh)
{
_pnhh->Destroy();
}
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* ARPHelp::Class = NULL;
HRESULT ARPHelp::Register()
{
return ClassInfo<ARPHelp,HWNDElement>::Register(L"ARPHelp", NULL, 0);
}
////////////////////////////////////////////////////////
// ARPSupportItem
////////////////////////////////////////////////////////
HRESULT ARPSupportItem::Create(OUT Element** ppElement)
{
*ppElement = NULL;
ARPSupportItem* pasi = HNew<ARPSupportItem>();
if (!pasi)
return E_OUTOFMEMORY;
HRESULT hr = pasi->Initialize();
if (FAILED(hr))
{
pasi->Destroy();
return hr;
}
*ppElement = pasi;
return S_OK;
}
Value* _pvRowLayout = NULL;
HRESULT ARPSupportItem::Initialize()
{
// Do base class initialization
HRESULT hr = Element::Initialize(0);
if (FAILED(hr))
return hr;
// Initialize
bool fCreateLayout = !_pvRowLayout;
if (fCreateLayout)
{
int ari[3] = { -1, 0, 3 };
hr = RowLayout::Create(3, ari, &_pvRowLayout);
if (FAILED(hr))
return hr;
}
Element* peName;
hr = Element::Create(AE_Inactive, &peName);
if (FAILED(hr))
return hr;
Button* peValue;
hr = Button::Create((Element**) &peValue);
if (FAILED(hr))
{
peName->Destroy();
return hr;
}
peValue->SetEnabled(false);
Add(peName);
Add(peValue);
SetValue(LayoutProp, PI_Local, _pvRowLayout);
SetLayoutPos(LP_None);
if (fCreateLayout)
{
// todo: need to track in propertychanged to know when it reaches null, which is
// when we need to set it to NULL
}
return S_OK;
}
////////////////////////////////////////////////////////
// System events
#define ASI_Name 0
#define ASI_Value 1
Element* GetNthChild(Element *peRoot, UINT index)
{
Value* pvChildren;
ElementList* pel = peRoot->GetChildren(&pvChildren);
Element* pe = NULL;
if (pel && (pel->GetSize() > index))
pe = pel->GetItem(index);
pvChildren->Release();
return pe;
}
Element* ARPSupportItem::GetChild(UINT index)
{
return GetNthChild(this, index);
}
void ARPSupportItem::OnPropertyChanged(PropertyInfo* ppi, int iIndex, Value* pvOld, Value* pvNew)
{
int index = -1;
if (IsProp(AccName))
index = ASI_Name;
else if (IsProp(AccValue))
index = ASI_Value;
else if (IsProp(URL))
{
Element* pe = GetChild(ASI_Value);
if (pe)
{
if (pvNew && pvNew->GetString() && *(pvNew->GetString()))
pe->RemoveLocalValue(EnabledProp);
else
pe->SetEnabled(false);
}
}
if (index != -1)
{
Element* pe = GetChild(index);
if (index == ASI_Value)
{
// WARNING -- this code assumes you will not put a layoutpos on this element
// as this code toggles between LP_None and unset, ignoring any previous setting
// to the property -- verify this with Mark -- could be that this is local
// and the markup is specified? then there wouldn't be a problem
if (pvNew && pvNew->GetString() && *(pvNew->GetString()))
RemoveLocalValue(LayoutPosProp);
else
SetLayoutPos(LP_None);
}
if (pe)
pe->SetValue(ContentProp, PI_Local, pvNew);
}
Element::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
}
////////////////////////////////////////////////////////
// Generic eventing
void ARPSupportItem::OnEvent(Event* pEvent)
{
// Handle only bubbled generic events
if (pEvent->nStage == GMF_BUBBLED)
{
if (pEvent->uidType == Button::Click)
{
Value* pvURL;
LPCWSTR lpszURL = GetURL(&pvURL);
if (*lpszURL)
{
ShellExecuteW(NULL, NULL, lpszURL, NULL, NULL, SW_SHOWDEFAULT);
}
pvURL->Release();
pEvent->fHandled = true;
return;
}
}
Element::OnEvent(pEvent);
}
// URL property
static int vvURL[] = { DUIV_STRING, -1 }; StaticValuePtr(svDefaultURL, DUIV_STRING, (void*)L"");
static PropertyInfo impURLProp = { L"URL", PF_Normal|PF_Cascade, 0, vvURL, NULL, (Value*)&svDefaultURL };
PropertyInfo* ARPSupportItem::URLProp = &impURLProp;
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Class properties
static PropertyInfo* _aPI[] = {
ARPSupportItem::URLProp,
};
// Define class info with type and base type, set static class pointer
IClassInfo* ARPSupportItem::Class = NULL;
HRESULT ARPSupportItem::Register()
{
return ClassInfo<ARPSupportItem,Element>::Register(L"ARPSupportItem", _aPI, DUIARRAYSIZE(_aPI));
}
////////////////////////////////////////////////////////
//
// ARPSelector
//
// A Selector whose children are all buttons. If the user clicks
// any of the buttons, that button automatically becomes the new
// selection.
// Define class info with type and base type, set static class pointer
HRESULT ARPSelector::Create(OUT Element** ppElement)
{
*ppElement = NULL;
ARPSelector* ps = HNew<ARPSelector>();
if (!ps)
return E_OUTOFMEMORY;
HRESULT hr = ps->Initialize();
if (FAILED(hr))
{
ps->Destroy();
return hr;
}
*ppElement = ps;
return S_OK;
}
////////////////////////////////////////////////////////
// Generic eventing
HRESULT CALLBACK CollapseExpandosExceptCB(Expando* pex, LPARAM lParam)
{
if (pex != (Expando*)lParam)
{
pex->SetExpanded(false);
}
return S_OK;
}
void CALLBACK s_Repaint(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
KillTimer(hwnd, idEvent);
ARPSelector* self = (ARPSelector*)idEvent;
Element* pe;
if (SUCCEEDED(Element::Create(0, &pe)))
{
pe->SetLayoutPos(BLP_Client);
if (SUCCEEDED(self->Add(pe)))
{
self->Remove(pe);
}
pe->Destroy();
}
}
void ARPSelector::OnEvent(Event* pEvent)
{
// Handle only bubbled generic events
if (pEvent->nStage == GMF_BUBBLED)
{
// Selection occurs only for Button::Click or Expando::Click events
if (pEvent->uidType == Button::Click ||
pEvent->uidType == Expando::Click)
{
pEvent->fHandled = true;
SetSelection(pEvent->peTarget);
// If it was a Click from an Expando, then unexpand all the
// other Expandos and expand this expando
if (pEvent->uidType == Expando::Click)
{
TraverseTree<Expando>(this, CollapseExpandosExceptCB, (LPARAM)pEvent->peTarget);
Expando* pex = (Expando*)pEvent->peTarget;
pex->SetExpanded(true);
// Hack for DUI painting weirdness
// After the animation is over, repaint ourselves
// to get rid of the detritus.
ARPFrame* paf = FindAncestorElement<ARPFrame>(this);
if (paf->GetHostWindow())
{
SetTimer(paf->GetHostWindow(),
(UINT_PTR)this,
paf->GetAnimationTime(), s_Repaint);
}
}
return;
}
}
Selector::OnEvent(pEvent);
}
// If we are not the option list, bypass Selector::GetAdjacent because
// Selector navigates from the selected element but we want to navigate
// from the focus element because the focus element has interesting
// subelements...
Element *ARPSelector::GetAdjacent(Element *peFrom, int iNavDir, NavReference const *pnr, bool bKeyable)
{
if (GetID() == ARPFrame::_idOptionList)
{
// Let the option list navigate normally
return Selector::GetAdjacent(peFrom, iNavDir, pnr, bKeyable);
}
else
{
// All other selectors navigate from selection
return Element::GetAdjacent(peFrom, iNavDir, pnr, bKeyable);
}
}
IClassInfo* ARPSelector::Class = NULL;
HRESULT ARPSelector::Register()
{
return ClassInfo<ARPSelector,Selector>::Register(L"ARPSelector", NULL, 0);
}
////////////////////////////////////////////////////////
//
// CLIENTINFO
//
// Tracks information about a specific client.
//
bool CLIENTINFO::GetInstallFile(HKEY hkInfo, LPCTSTR pszValue, LPTSTR pszBuf, UINT cchBuf, bool fFile)
{
DWORD dwType;
DWORD cb = cchBuf * sizeof(TCHAR);
if (SHQueryValueEx(hkInfo, pszValue, NULL, &dwType, pszBuf, &cb) != ERROR_SUCCESS ||
dwType != REG_SZ)
{
// If a file, then failure is okay (it means nothing to verify)
return fFile;
}
TCHAR szBuf[MAX_PATH];
lstrcpyn(szBuf, pszBuf, DUIARRAYSIZE(szBuf));
if (!fFile)
{
// Now validate that the program exists
PathRemoveArgs(szBuf);
PathUnquoteSpaces(szBuf);
}
// Must be fully-qualified
if (PathIsRelative(szBuf))
{
return false;
}
// File must exist, but don't hit the network to validate it
if (!PathIsNetworkPath(szBuf) &&
!PathFileExists(szBuf))
{
return false;
}
return true;
}
bool CLIENTINFO::GetInstallCommand(HKEY hkInfo, LPCTSTR pszValue, LPTSTR pszBuf, UINT cchBuf)
{
return GetInstallFile(hkInfo, pszValue, pszBuf, cchBuf, FALSE);
}
LONG RegQueryDWORD(HKEY hk, LPCTSTR pszValue, DWORD* pdwOut)
{
DWORD dwType;
DWORD cb = sizeof(*pdwOut);
LONG lRc = RegQueryValueEx(hk, pszValue, NULL, &dwType, (LPBYTE)pdwOut, &cb);
if (lRc == ERROR_SUCCESS && dwType != REG_DWORD)
{
lRc = ERROR_INVALID_DATA;
}
return lRc;
}
//
// hkInfo = NULL means that pzsKey is actually the friendlyname for
// "keep this item"
//
bool CLIENTINFO::Initialize(HKEY hkApp, HKEY hkInfo, LPCWSTR pszKey)
{
LPCWSTR pszName;
WCHAR szBuf[MAX_PATH];
DUIAssertNoMsg(_tOEMShown == TRIBIT_UNDEFINED);
if (hkInfo)
{
_pszKey = StrDupW(pszKey);
if (!_pszKey) return false;
// Program must have properly registered IconsVisible status
DWORD dwValue;
if (RegQueryDWORD(hkInfo, TEXT("IconsVisible"), &dwValue) != ERROR_SUCCESS)
{
return false;
}
// If there is a VerifyFile, the file must exist
if (!GetInstallFile(hkInfo, TEXT("VerifyFile"), szBuf, DUIARRAYSIZE(szBuf), TRUE))
{
return false;
}
_bShown = BOOLIFY(dwValue);
// Program must have properly registered Reinstall, HideIcons and ShowIcons commands
if (!GetInstallCommand(hkInfo, TEXT("ReinstallCommand"), szBuf, DUIARRAYSIZE(szBuf)) ||
!GetInstallCommand(hkInfo, TEXT("HideIconsCommand"), szBuf, DUIARRAYSIZE(szBuf)) ||
!GetInstallCommand(hkInfo, TEXT("ShowIconsCommand"), szBuf, DUIARRAYSIZE(szBuf)))
{
return false;
}
// Get the OEM's desired hide/show setting for this app, if any
if (RegQueryDWORD(hkInfo, TEXT("OEMShowIcons"), &dwValue) == ERROR_SUCCESS)
{
_tOEMShown = dwValue ? TRIBIT_TRUE : TRIBIT_FALSE;
}
// See if this is the OEM's default client
if (RegQueryDWORD(hkInfo, TEXT("OEMDefault"), &dwValue) == ERROR_SUCCESS &&
dwValue != 0)
{
_bOEMDefault = BOOLIFY(dwValue);
}
SHLoadLegacyRegUIStringW(hkApp, NULL, szBuf, ARRAYSIZE(szBuf));
if (!szBuf[0]) return false;
pszName = szBuf;
}
else
{
pszName = pszKey;
}
_pszName = StrDupW(pszName);
if (!_pszName) return false;
return true;
}
CLIENTINFO* CLIENTINFO::Create(HKEY hkApp, HKEY hkInfo, LPCWSTR pszKey)
{
CLIENTINFO* pci = HNewAndZero<CLIENTINFO>();
if (pci)
{
if (!pci->Initialize(hkApp, hkInfo, pszKey))
{
pci->Delete();
pci = NULL;
}
}
return pci;
}
CLIENTINFO::~CLIENTINFO()
{
LocalFree(_pszKey);
LocalFree(_pszName);
if (_pvMSName)
{
_pvMSName->Release();
}
}
int CLIENTINFO::QSortCMP(const void* p1, const void* p2)
{
CLIENTINFO* pci1 = *(CLIENTINFO**)p1;
CLIENTINFO* pci2 = *(CLIENTINFO**)p2;
return lstrcmpi(pci1->_pszName, pci2->_pszName);
}
////////////////////////////////////////////////////////
//
// StringList
//
// A list of strings. The buffer for all the strings is allocated
// in _pszBuf; the DynamicArray contains pointers into that buffer.
//
void StringList::Reset()
{
if (_pdaStrings)
{
_pdaStrings->Destroy();
_pdaStrings = NULL;
}
LocalFree(_pszBuf);
_pszBuf = NULL;
}
// pszInit is a semicolon-separated list
HRESULT StringList::SetStringList(LPCTSTR pszInit)
{
HRESULT hr;
Reset();
if (!pszInit)
{
hr = S_OK; // empty list
}
else if (SUCCEEDED(hr = DynamicArray<LPTSTR>::Create(0, false, &_pdaStrings)))
{
_pszBuf = StrDup(pszInit);
if (_pszBuf)
{
LPTSTR psz = _pszBuf;
hr = S_OK;
while (SUCCEEDED(hr) && psz && *psz)
{
LPTSTR pszT = StrChr(psz, L';');
if (pszT)
{
*pszT++ = L'\0';
}
hr = _pdaStrings->Add(psz);
psz = pszT;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
bool StringList::IsStringInList(LPCTSTR pszFind)
{
if (_pdaStrings)
{
for (UINT i = 0; i < _pdaStrings->GetSize(); i++)
{
if (AreEnglishStringsEqual(_pdaStrings->GetItem(i), pszFind))
{
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////
//
// ClientPicker
//
// An element which manages a list of registered clients.
//
// If there is only one item in the list, then the element is static.
// Otherwise, the element hosts a combo box.
//
// The clienttype attribute is the name of the registry key under Clients.
//
HRESULT ClientPicker::Create(OUT Element** ppElement)
{
*ppElement = NULL;
ClientPicker* pcc = HNewAndZero<ClientPicker>();
if (!pcc)
return E_OUTOFMEMORY;
HRESULT hr = pcc->Initialize();
if (FAILED(hr))
{
pcc->Destroy();
return hr;
}
*ppElement = pcc;
return S_OK;
};
HRESULT ClientPicker::Initialize()
{
HRESULT hr;
// Initialize base
hr = super::Initialize(0); // Normal display node creation
if (FAILED(hr))
return hr;
// Initialize members
hr = DynamicArray<CLIENTINFO*>::Create(0, false, &_pdaClients);
if (FAILED(hr))
return hr;
hr = Element::Create(0, &_peStatic);
if (FAILED(hr))
return hr;
if (FAILED(hr = _peStatic->SetClass(L"clientstatic")) ||
FAILED(hr = Add(_peStatic)))
{
_peStatic->Destroy();
return hr;
}
_peStatic->SetAccessible(true);
_peStatic->SetAccRole(ROLE_SYSTEM_STATICTEXT);
hr = Combobox::Create((Element**)&_peCombo);
if (FAILED(hr))
return hr;
if (FAILED(hr = Add(_peCombo)) ||
FAILED(hr = _peCombo->SetVisible(false)))
{
_peCombo->Destroy();
return hr;
}
// JeffBog says I should mess with the width here
SetWidth(10);
return S_OK;
}
ClientPicker::~ClientPicker()
{
_CancelDelayShowCombo();
if (_pdaClients)
{
_pdaClients->Destroy();
}
}
void ClientPicker::OnPropertyChanged(PropertyInfo* ppi, int iIndex, Value* pvOld, Value* pvNew)
{
super::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
// Since UIActive = Selected && ParentEnabled, we need to call
// _SyncUIActive if either property changes.
if (IsProp(Selected))
{
// Change in selection may require us to block or unblock the OK button.
_CheckBlockOK(pvNew->GetBool());
_SyncUIActive();
}
else if (IsProp(ParentExpanded))
{
_SyncUIActive();
}
}
// To keep accessibility happy, we reflect content in the AccName.
void _SetStaticTextAndAccName(Element* pe, Value* pv)
{
pe->SetValue(Element::ContentProp, PI_Local, pv);
pe->SetValue(Element::AccNameProp, PI_Local, pv);
}
void _SetStaticTextAndAccName(Element* pe, LPCWSTR pszText)
{
Value* pv = Value::CreateString(pszText);
_SetStaticTextAndAccName(pe, pv);
pv->Release();
}
//
// When UI Active, show the combo box.
// When not UI Active, hide our combo box so animation doesn't tube it.
//
void ClientPicker::_SyncUIActive()
{
ARPFrame* paf = FindAncestorElement<ARPFrame>(this);
bool bUIActive = GetSelected() && GetParentExpanded();
if (_bUIActive != bUIActive)
{
_bUIActive = bUIActive;
if (_bUIActive)
{
// Normally we would just _peCombo->SetVisible(_NeedsCombo())
// and go home. Unfortunately, DirectUI gets confused if a
// combo box moves around, so we have to change the visibility
// after the world has gone quiet
_hwndHost = paf->GetHostWindow();
if (_hwndHost)
{
SetTimer(_hwndHost,
(UINT_PTR)this,
paf->GetAnimationTime(), s_DelayShowCombo);
}
}
else
{
// Inactive - copy current combo selection to static
// and hide the combo
UINT iSel = _peCombo->GetSelection();
if (iSel < GetClientList()->GetSize())
{
_SetStaticTextAndAccName(_peStatic, GetClientList()->GetItem(iSel)->GetFilteredName(GetFilter()));
}
_peCombo->SetVisible(false);
_peStatic->SetVisible(true);
_CancelDelayShowCombo();
}
}
}
void ClientPicker::_DelayShowCombo()
{
// Tell DirectUI to let the combo participate in layout again
bool bNeedsCombo = _NeedsCombo();
_peCombo->SetVisible(bNeedsCombo);
_peStatic->SetVisible(!bNeedsCombo);
// Force a relayout by shrinking the combo box a teensy bit, then
// returning it to normal size. This cannot be done inside a
// Defer because that ends up optimizing out the relayout.
_peCombo->SetWidth(_peCombo->GetWidth()-1);
_peCombo->RemoveLocalValue(WidthProp);
if (!_bFilledCombo)
{
_bFilledCombo = true;
SendMessage(_peCombo->GetHWND(), CB_RESETCONTENT, 0, 0);
for (UINT i = 0; i < GetClientList()->GetSize(); i++)
{
_peCombo->AddString(GetClientList()->GetItem(i)->GetFilteredName(GetFilter()));
}
_peCombo->SetSelection(0);
}
}
// If the user picked "Choose from list" and we are selected,
// then block OK since the user actually needs to choose something.
void ClientPicker::_CheckBlockOK(bool bSelected)
{
ARPFrame* paf = FindAncestorElement<ARPFrame>(this);
CLIENTINFO* pci = GetSelectedClient();
if (pci)
{
if (bSelected && pci->IsPickFromList())
{
if (!_bBlockedOK)
{
_bBlockedOK = true;
paf->BlockOKButton();
}
}
else
{
if (_bBlockedOK)
{
_bBlockedOK = false;
paf->UnblockOKButton();
}
}
}
}
void ClientPicker::s_DelayShowCombo(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
KillTimer(hwnd, idEvent);
ClientPicker* self = (ClientPicker*)idEvent;
self->_DelayShowCombo();
}
void ClientPicker::_CancelDelayShowCombo()
{
if (_hwndHost)
{
KillTimer(_hwndHost, (UINT_PTR)this);
_hwndHost = NULL;
}
}
void ClientPicker::OnEvent(Event* pEvent)
{
// Handle only bubbled generic events
if (pEvent->nStage == GMF_BUBBLED)
{
// If the selection changed, then see if it's a change
// that should block the OK button.
if (pEvent->uidType == Combobox::SelectionChange)
{
_CheckBlockOK(GetSelected());
}
}
super::OnEvent(pEvent);
}
//
// CLIENTFILTER_OEM - add one if marked OEM, else "Keep unchanged"
// CLIENTFILTER_MS - add any that are marked MS, else "Keep unchanged"
// CLIENTFILTER_NONMS - add any that are not marked MS, else "Keep unchanged"
// furthermore, if more than one non-MS, then
// add and select "Choose from list"
//
// On success, returns the number of items added
// (not counting "Keep unchanged" / "Choose from list")
//
HRESULT ClientPicker::SetFilter(CLIENTFILTER cf, ARPFrame* paf)
{
HRESULT hr = E_FAIL;
DUIAssert(_cf == 0, "SetFilter called more than once");
_cf = cf;
_bEmpty = true;
_bFilledCombo = false;
Value* pv;
LPWSTR pszType = GetClientTypeString(&pv);
if (pszType)
{
_pcb = paf->FindClientBlock(pszType);
if (_pcb)
{
hr = _pcb->InitializeClientPicker(this);
}
}
pv->Release();
// The static element gets the first item in the list
if (SUCCEEDED(hr) && GetClientList()->GetSize())
{
_SetStaticTextAndAccName(_peStatic, GetClientList()->GetItem(0)->_pszName);
}
if (SUCCEEDED(hr))
{
CalculateWidth();
_SyncUIActive();
}
return hr;
}
// Set our width to the width of the longest string in our combo box.
// Combo boxes don't do this themselves, so they need our help. We have
// to set the width on ourselves and not on the combobox because
// RowLayout will change the width of the combobox and HWNDHost will
// treat the HWND width as authoritative, overwriting the combobox width
// we had set.
void ClientPicker::CalculateWidth()
{
HWND hwndCombo = _peCombo->GetHWND();
HDC hdc = GetDC(hwndCombo);
if (hdc)
{
HFONT hfPrev = SelectFont(hdc, GetWindowFont(hwndCombo));
int cxMax = 0;
SIZE siz;
for (UINT i = 0; i < GetClientList()->GetSize(); i++)
{
LPCTSTR pszName = GetClientList()->GetItem(i)->GetFilteredName(GetFilter());
if (GetTextExtentPoint(hdc, pszName, lstrlen(pszName), &siz) &&
cxMax < siz.cx)
{
cxMax = siz.cx;
}
}
SelectFont(hdc, hfPrev);
ReleaseDC(hwndCombo, hdc);
// Add in the borders that USER adds to the combo box.
// Unfortunately, we get called when the combo box has been
// squished to zero width, so GetComboBoxInfo is of no use.
// We have to replicate the computations.
//
// The client space is arranged horizontally like so:
//
// SM_CXFIXEDFRAME
// v v
// | | edit | | |
// ^
// SM_CXVSCROLL
RECT rc = { 0, 0, cxMax, 0 };
rc.right += 2 * GetSystemMetrics(SM_CXFIXEDFRAME) + GetSystemMetrics(SM_CXVSCROLL);
rc.right += GetSystemMetrics(SM_CXEDGE); // extra edge for Hebrew/Arabic
AdjustWindowRect(&rc, GetWindowStyle(hwndCombo), FALSE);
SetWidth(rc.right - rc.left);
}
}
HRESULT ClientPicker::TransferToCustom()
{
HRESULT hr = E_FAIL;
if (_pcb)
{
hr = _pcb->TransferFromClientPicker(this);
}
return hr;
}
CLIENTINFO* ClientPicker::GetSelectedClient()
{
if (_peCombo)
{
UINT iSel = _peCombo->GetSelection();
if (iSel < GetClientList()->GetSize())
{
return GetClientList()->GetItem(iSel);
}
}
return NULL;
}
////////////////////////////////////////////////////////
// Property definitions
/** Property template (replace !!!), also update private PropertyInfo* parray and class header (element.h)
// !!! property
static int vv!!![] = { DUIV_INT, -1 }; StaticValue(svDefault!!!, DUIV_INT, 0);
static PropertyInfo imp!!!Prop = { L"!!!", PF_Normal, 0, vv!!!, (Value*)&svDefault!!! };
PropertyInfo* Element::!!!Prop = &imp!!!Prop;
**/
// ClientType property
static int vvCCClientType[] = { DUIV_STRING, -1 };
static PropertyInfo impCCClientTypeProp = { L"ClientType", PF_Normal, 0, vvCCClientType, NULL, Value::pvStringNull };
PropertyInfo* ClientPicker::ClientTypeProp = &impCCClientTypeProp;
// ParentExpanded property
static int vvParentExpanded[] = { DUIV_BOOL, -1 };
static PropertyInfo impParentExpandedProp = { L"parentexpanded", PF_Normal, 0, vvParentExpanded, NULL, Value::pvBoolFalse };
PropertyInfo* ClientPicker::ParentExpandedProp = &impParentExpandedProp;
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Class properties
PropertyInfo* _aClientPickerPI[] = {
ClientPicker::ClientTypeProp,
ClientPicker::ParentExpandedProp,
};
// Define class info with type and base type, set static class pointer
IClassInfo* ClientPicker::Class = NULL;
HRESULT ClientPicker::Register()
{
return ClassInfo<ClientPicker,super>::Register(L"clientpicker", _aClientPickerPI, DUIARRAYSIZE(_aClientPickerPI));
}
////////////////////////////////////////////////////////
// ARP Parser
HRESULT ARPParser::Create(ARPFrame* paf, UINT uRCID, HINSTANCE hInst, PPARSEERRORCB pfnErrorCB, OUT Parser** ppParser)
{
*ppParser = NULL;
ARPParser* ap = HNew<ARPParser>();
if (!ap)
return E_OUTOFMEMORY;
HRESULT hr = ap->Initialize(paf, uRCID, hInst, pfnErrorCB);
if (FAILED(hr))
{
ap->Destroy();
return hr;
}
*ppParser = ap;
return S_OK;
}
HRESULT ARPParser::Initialize(ARPFrame* paf, UINT uRCID, HINSTANCE hInst, PPARSEERRORCB pfnErrorCB)
{
_paf = paf;
return Parser::Initialize(uRCID, hInst, pfnErrorCB);
}
Value* ARPParser::GetSheet(LPCWSTR pszResID)
{
// All style sheet mappings go through here. Redirect sheet queries to appropriate
// style sheets (i.e. themed or standard look). _pParserStyle points to the
// appropriate stylesheet-only Parser instance
return _paf->GetStyleParser()->GetSheet(pszResID);
}
////////////////////////////////////////////////////////
//
// AutoButton
//
// A button that does a bunch of stuff that USER does automagically,
// if it were a regular button control.
//
// - Automatically updates its own accessibility state and action
// - If a checkbox, autotoggles on click
HRESULT AutoButton::Create(OUT Element** ppElement)
{
*ppElement = NULL;
AutoButton* pb = HNew<AutoButton>();
if (!pb)
return E_OUTOFMEMORY;
HRESULT hr = pb->Initialize(AE_MouseAndKeyboard);
if (FAILED(hr))
{
pb->Destroy();
return hr;
}
*ppElement = pb;
return S_OK;
}
void AutoButton::OnEvent(Event* pev)
{
// Checkboxes auto-toggle on click
if (pev->nStage == GMF_DIRECT &&
pev->uidType == Button::Click &&
GetAccRole() == ROLE_SYSTEM_CHECKBUTTON)
{
pev->fHandled = true;
// Toggle the selected state
SetSelected(!GetSelected());
}
super::OnEvent(pev);
}
//
// Reflect the selected state to accessibility.
//
void AutoButton::OnPropertyChanged(PropertyInfo* ppi, int iIndex, Value* pvOld, Value* pvNew)
{
super::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
if (IsProp(Selected))
{
int state = GetAccState();
if (GetAccRole() == ROLE_SYSTEM_OUTLINEBUTTON)
{
// Outline buttons expose Selection as expanded/collapsed
state &= ~(STATE_SYSTEM_EXPANDED | STATE_SYSTEM_COLLAPSED);
if (pvNew->GetBool())
{
state |= STATE_SYSTEM_EXPANDED;
}
else
{
state |= STATE_SYSTEM_COLLAPSED;
}
}
else
{
// Radio buttons and checkboxes expose Selection as checked/unchecked
if (pvNew->GetBool())
{
state |= STATE_SYSTEM_CHECKED;
}
else
{
state &= ~STATE_SYSTEM_CHECKED;
}
}
SetAccState(state);
SyncDefAction();
}
else if (IsProp(AccRole))
{
SyncDefAction();
}
}
//
// Role strings from oleacc. They are biased by 1100 since that is
// where roles begin.
//
#define OLEACCROLE_EXPAND (305-1100)
#define OLEACCROLE_COLLAPSE (306-1100)
#define OLEACCROLE_CHECK (309-1100)
#define OLEACCROLE_UNCHECK (310-1100)
// Default action is "Check" if a radio button or an unchecked
// checkbox. Default action is "Uncheck" if an unchecked checkbox.
void AutoButton::SyncDefAction()
{
UINT idsAction;
switch (GetAccRole())
{
// Checkbuttons will check or uncheck depending on state
case ROLE_SYSTEM_CHECKBUTTON:
idsAction = (GetAccState() & STATE_SYSTEM_CHECKED) ?
OLEACCROLE_UNCHECK :
OLEACCROLE_CHECK;
break;
// Radiobutton always checks.
case ROLE_SYSTEM_RADIOBUTTON:
idsAction = OLEACCROLE_CHECK;
break;
// Expando button expands or collapses.
case ROLE_SYSTEM_OUTLINEBUTTON:
idsAction = (GetAccState() & STATE_SYSTEM_EXPANDED) ?
OLEACCROLE_COLLAPSE :
OLEACCROLE_EXPAND;
break;
default:
DUIAssert(0, "Unknown AccRole");
return;
}
SetDefAction(this, idsAction);
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* AutoButton::Class = NULL;
HRESULT AutoButton::Register()
{
return ClassInfo<AutoButton,super>::Register(L"AutoButton", NULL, 0);
}
////////////////////////////////////////////////////////
// ClientBlock class
//
// Manages a block of elements which expose all the clients registered
// to a particular client category.
HRESULT ClientBlock::Create(OUT Element** ppElement)
{
*ppElement = NULL;
ClientBlock* pcb = HNewAndZero<ClientBlock>();
if (!pcb)
return E_OUTOFMEMORY;
HRESULT hr = pcb->Initialize();
if (FAILED(hr))
{
pcb->Destroy();
return hr;
}
*ppElement = pcb;
return S_OK;
}
HRESULT ClientBlock::Initialize()
{
HRESULT hr;
// Initialize base
hr = super::Initialize(0); // Normal display node creation
if (FAILED(hr))
return hr;
// Initialize members
hr = DynamicArray<CLIENTINFO*>::Create(0, false, &_pdaClients);
if (FAILED(hr))
return hr;
return S_OK;
}
ClientBlock::~ClientBlock()
{
if (_pdaClients)
{
for (UINT i = 0; i < _pdaClients->GetSize(); i++)
{
_pdaClients->GetItem(i)->Delete();
}
_pdaClients->Destroy();
}
}
//
// If the user clicks a new default application, force it to be checked
// and disable it so it cannot be unchecked. Also re-enable the old one.
//
void ClientBlock::OnEvent(Event* pev)
{
if (pev->nStage == GMF_BUBBLED &&
pev->uidType == Selector::SelectionChange)
{
SelectionChangeEvent* sce = (SelectionChangeEvent*)pev;
// Re-enable the previous guy, if any
_EnableShowCheckbox(sce->peOld, true);
// Disable the new guy, if any
_EnableShowCheckbox(sce->peNew, false);
}
super::OnEvent(pev);
}
void ClientBlock::_EnableShowCheckbox(Element* peRadio, bool fEnable)
{
if (peRadio)
{
Element* peRow = peRadio->GetParent();
if (peRow)
{
Element* peShow = MaybeFindDescendentByName(peRow, L"show");
if (peShow)
{
peShow->SetEnabled(fEnable);
peShow->SetSelected(true); // force checked
// HACKHACK - DUI doesn't realize that the checkbox needs
// to be repainted so I have to kick it.
InvalidateGadget(peShow->GetDisplayNode());
}
}
}
}
//
// ClientBlock initialization / apply methods...
//
HKEY ClientBlock::_OpenClientKey(HKEY hkRoot, DWORD dwAccess)
{
HKEY hkClient = NULL;
Value *pv;
LPCWSTR pszClient = GetClientTypeString(&pv);
if (pszClient)
{
WCHAR szBuf[MAX_PATH];
wnsprintfW(szBuf, ARRAYSIZE(szBuf), TEXT("Software\\Clients\\%s"),
pszClient);
RegOpenKeyExW(hkRoot, szBuf, 0, dwAccess, &hkClient);
pv->Release();
}
return hkClient;
}
bool ClientBlock::_GetDefaultClient(HKEY hkClient, HKEY hkRoot, LPTSTR pszBuf, LONG cchBuf)
{
bool bResult = false;
HKEY hk = _OpenClientKey(hkRoot);
if (hk)
{
DWORD cbSize = cchBuf * sizeof(*pszBuf);
DWORD dwType;
// Client must be defined, be of type REG_SZ, be non-NULL, and have
// a corresponding entry in HKLM\Software\Clients. RegQueryValue
// is a handy abbreviatio for RegQueryKeyExists.
LONG l;
if (SHGetValue(hk, NULL, NULL, &dwType, pszBuf, &cbSize) == ERROR_SUCCESS &&
dwType == REG_SZ && pszBuf[0] &&
RegQueryValue(hkClient, pszBuf, NULL, &l) == ERROR_SUCCESS)
{
bResult = true;
}
RegCloseKey(hk);
}
return bResult;
}
// Determines whether the current client is a Microsoft client different
// from the Windows default client. Usually, this is when the current
// client is Outlook but the Windows default client is Outlook Express.
bool ClientBlock::_IsCurrentClientNonWindowsMS()
{
bool bResult = false;
HKEY hkClient = _OpenClientKey();
if (hkClient)
{
TCHAR szClient[MAX_PATH];
if (_GetDefaultClient(hkClient, HKEY_CURRENT_USER, szClient, ARRAYSIZE(szClient)) ||
_GetDefaultClient(hkClient, HKEY_LOCAL_MACHINE, szClient, ARRAYSIZE(szClient)))
{
// Is it a Microsoft client that isn't the Windows default?
if (_GetClientTier(szClient) == CBT_MS)
{
bResult = true;
}
}
RegCloseKey(hkClient);
}
return bResult;
}
//
// Called after the entire tree has been parsed and hosted.
// (Sort of like readystatecomplete.)
//
HRESULT ClientBlock::ParseCompleted(ARPFrame *paf)
{
HRESULT hr = S_OK;
Value* pv;
hr = _slOtherMSClients.SetStringList(GetOtherMSClientsString(&pv));
pv->Release();
if (SUCCEEDED(hr))
{
hr = paf->CreateElement(L"clientblockselector", NULL, (Element**)&_peSel);
if (SUCCEEDED(hr))
{
hr = Add(_peSel);
if (SUCCEEDED(hr))
{
// Failure to open the client key is not fatal; it just means that
// there are vacuously no clients.
HKEY hkClient = _OpenClientKey();
if (hkClient)
{
// Enumerate each app under the client key and look for those which
// have a "InstallInfo" subkey.
TCHAR szKey[MAX_PATH];
for (DWORD dwIndex = 0;
SUCCEEDED(hr) &&
RegEnumKey(hkClient, dwIndex, szKey, ARRAYSIZE(szKey)) == ERROR_SUCCESS;
dwIndex++)
{
HKEY hkApp;
if (RegOpenKeyEx(hkClient, szKey, 0, KEY_READ, &hkApp) == ERROR_SUCCESS)
{
HKEY hkInfo;
if (RegOpenKeyEx(hkApp, TEXT("InstallInfo"), 0, KEY_READ, &hkInfo) == ERROR_SUCCESS)
{
// Woo-hoo, this client provided install info
// Let's see if it's complete.
CLIENTINFO* pci = CLIENTINFO::Create(hkApp, hkInfo, szKey);
if (pci)
{
if (SUCCEEDED(hr = _pdaClients->Add(pci)))
{
// success
}
else
{
pci->Delete();
}
}
RegCloseKey(hkInfo);
}
RegCloseKey(hkApp);
}
}
RegCloseKey(hkClient);
//
// Sort the clients alphabetically to look nice.
// (Otherwise they show up alphabetical by registry key name,
// which is not very useful to an end-user.)
//
_pdaClients->Sort(CLIENTINFO::QSortCMP);
}
//
// Insert "Keep unchanged" and "Pick from list".
// Do this after sorting because we want those two
// to be at the top. Since we are adding to the top,
// we add them in the reverse order so
// "Keep unchanged" = 1, "Pick from list" = 0.
hr = AddStaticClientInfoToTop(KeepTextProp);
if (SUCCEEDED(hr))
{
hr = AddStaticClientInfoToTop(PickTextProp);
}
// Now create one row for each client we found
// Start at i=1 to skip over "Pick from list"
for (UINT i = 1; SUCCEEDED(hr) && i < _pdaClients->GetSize(); i++)
{
CLIENTINFO* pci = _pdaClients->GetItem(i);
Element* pe;
hr = paf->CreateElement(L"clientitem", NULL, &pe);
if (SUCCEEDED(hr))
{
hr = _peSel->Add(pe);
if (SUCCEEDED(hr))
{
pci->_pe = pe;
// Set friendly name
pci->SetFriendlyName(pci->_pszName);
if (pci->IsSentinel())
{
// "Keep Unchanged" loses the checkboxes and defaults selected
// Merely hide the checkboxes instead of destroying them;
// this keeps RowLayout happy.
FindDescendentByName(pe, L"show")->SetVisible(false);
_peSel->SetSelection(pe);
}
else
{
// Others initialize the checkbox and default unselected
pci->SetShowCheckbox(pci->_bShown);
}
}
else // _peSel->Add(pe) failed
{
pe->Destroy();
}
}
}
}
else // Add(_peSel) failed
{
_peSel->Destroy();
_peSel = NULL;
}
}
}
return hr;
}
HRESULT ClientBlock::AddStaticClientInfoToTop(PropertyInfo* ppi)
{
HRESULT hr;
Value* pv;
pv = GetValue(ppi, PI_Specified);
CLIENTINFO* pci = CLIENTINFO::Create(NULL, NULL, pv->GetString());
pv->Release();
if (pci)
{
if (SUCCEEDED(hr = _pdaClients->Insert(0, pci)))
{
// maybe this block has a custom replacement text for the
// Microsoft section if the current app is a Microsoft app.
GetKeepMSTextString(&pci->_pvMSName);
}
else
{
pci->Delete();
}
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
ClientBlock::CBTIER ClientBlock::_GetClientTier(LPCTSTR pszClient)
{
Value* pv;
LPWSTR pwsz;
// Highest tier is "Windows default client"
pwsz = GetWindowsClientString(&pv);
bool bRet = pwsz && AreEnglishStringsEqual(pwsz, pszClient);
pv->Release();
if (bRet)
{
return CBT_WINDOWSDEFAULT;
}
// next best is "Microsoft client"
if (_slOtherMSClients.IsStringInList(pszClient))
{
return CBT_MS;
}
// otherwise, it's a thirdparty app
return CBT_NONMS;
}
//
// Based on the filter, determine whether the specified item should
// be shown, hidden, or left alone (returned as a TRIBIT), and optionally
// determine whether the item should be added to the client picker.
//
TRIBIT ClientBlock::_GetFilterShowAdd(CLIENTINFO* pci, ClientPicker* pcp, bool* pbAdd)
{
bool bAdd = false;
TRIBIT tShow = TRIBIT_UNDEFINED;
CBTIER cbt = _GetClientTier(pci->_pszKey);
switch (pcp->GetFilter())
{
case CLIENTFILTER_OEM:
//
// Add the one that is marked "OEM Default".
// (Caller will catch the "more than one" scenario.)
// Set show/hide state according to OEM preference.
//
bAdd = pci->_bOEMDefault;
if (bAdd) {
tShow = TRIBIT_TRUE;
} else {
tShow = pci->_tOEMShown;
}
break;
case CLIENTFILTER_MS:
//
// Add the Windows preferred client.
// Show all applications except for "keep unchanged" (which
// isn't really an application anyway).
//
bAdd = IsWindowsDefaultClient(cbt);
tShow = TRIBIT_TRUE;
break;
case CLIENTFILTER_NONMS:
//
// Hide all Microsoft clients.
// Add all thirdparty clients and show them.
//
if (IsMicrosoftClient(cbt))
{
bAdd = false;
tShow = TRIBIT_FALSE;
}
else
{
bAdd = true;
tShow = TRIBIT_TRUE;
}
break;
default:
DUIAssert(0, "Invalid client filter category");
break;
}
if (pbAdd)
{
*pbAdd = bAdd;
}
if (pci->IsSentinel())
{
tShow = TRIBIT_UNDEFINED;
}
return tShow;
}
//
// On success, returns the number of items added
// (not counting "Keep unchanged")
//
HRESULT ClientBlock::InitializeClientPicker(ClientPicker* pcp)
{
HRESULT hr = S_OK;
ARPFrame* paf = FindAncestorElement<ARPFrame>(this);
// Walk our children looking for ones that match the filter.
HKEY hkClient = _OpenClientKey();
if (hkClient)
{
if (SUCCEEDED(paf->CreateElement(L"oemclientshowhide", NULL, &pcp->_peShowHide)))
{
// Insert the template after our parent
Element* peParent = pcp->GetParent();
peParent->GetParent()->Insert(pcp->_peShowHide, peParent->GetIndex() + 1);
}
// Note! Start loop with 2 because we don't care about
// "Pick from list" or "Keep Unchanged" yet
DUIAssert(_pdaClients->GetItem(0)->IsPickFromList(), "GetItem(0) must be 'Pick from list'");
DUIAssert(_pdaClients->GetItem(1)->IsKeepUnchanged(), "GetItem(1) must be 'Keep unchanged'");
for (UINT i = 2; SUCCEEDED(hr) && i < _pdaClients->GetSize(); i++)
{
CLIENTINFO* pci = _pdaClients->GetItem(i);
bool bAdd;
TRIBIT tShow = _GetFilterShowAdd(pci, pcp, &bAdd);
if (pcp->_peShowHide)
{
switch (tShow)
{
case TRIBIT_TRUE:
pcp->AddClientToOEMRow(L"show", pci);
pcp->SetNotEmpty();
break;
case TRIBIT_FALSE:
pcp->AddClientToOEMRow(L"hide", pci);
pcp->SetNotEmpty();
break;
}
}
if (bAdd)
{
hr = pcp->GetClientList()->Add(pci);
pcp->SetNotEmpty();
}
}
RegCloseKey(hkClient);
}
if (SUCCEEDED(hr))
{
// Now some wacko cleanup rules.
switch (pcp->GetFilter())
{
case CLIENTFILTER_OEM:
// There can be only one OEM default item.
// If there's more than one (OEM or app trying to cheat),
// then throw them all away.
if (pcp->GetClientList()->GetSize() != 1)
{
pcp->GetClientList()->Reset(); // throw away everything
}
break;
case CLIENTFILTER_MS:
// If the current client is not the default client but
// does belong to Microsoft, then add "Keep unchanged"
// and select it. What's more, save the current string
// to be used if the user picks the Windows client,
// then append the Windows app to the "Also Show" string
// and save that too.
if (_IsCurrentClientNonWindowsMS())
{
hr = pcp->AddKeepUnchanged(_pdaClients->GetItem(1));
}
break;
case CLIENTFILTER_NONMS:
// If there is more than one available, then insert
// "Pick an app"
if (pcp->GetClientList()->GetSize() > 1)
{
hr = pcp->GetClientList()->Insert(0, _pdaClients->GetItem(0)); // insert "pick an app"
}
break;
}
// If there are no items, then add "Keep unchanged"
if (pcp->GetClientList()->GetSize() == 0)
{
hr = pcp->GetClientList()->Add(_pdaClients->GetItem(1)); // add "keep unchanged"
}
}
if (pcp->_peShowHide)
{
_RemoveEmptyOEMRow(pcp->_peShowHide, L"show");
_RemoveEmptyOEMRow(pcp->_peShowHide, L"hide");
}
return hr;
}
HRESULT ClientPicker::AddKeepUnchanged(CLIENTINFO* pciKeepUnchanged)
{
HRESULT hr = GetClientList()->Insert(0, pciKeepUnchanged); // insert "keep unchanged"
return hr;
}
void ClientPicker::AddClientToOEMRow(LPCWSTR pszName, CLIENTINFO* pci)
{
Element* peRow = FindDescendentByName(_peShowHide, pszName);
Element* peList = FindDescendentByName(peRow, L"list");
Value* pv;
LPCWSTR pszContent = peList->GetContentString(&pv);
if (!pszContent)
{
_SetStaticTextAndAccName(peList, pci->_pszName);
}
else
{
TCHAR szFormat[20];
LPCWSTR rgpszInsert[2] = { pszContent, pci->_pszName };
LoadString(g_hinst, IDS_ADDITIONALCLIENTFORMAT, szFormat, SIZECHARS(szFormat));
LPWSTR pszFormatted;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
szFormat, 0, 0, (LPWSTR)&pszFormatted, 0, (va_list*)rgpszInsert))
{
_SetStaticTextAndAccName(peList, pszFormatted);
LocalFree(pszFormatted);
}
}
pv->Release();
}
void ClientBlock::_RemoveEmptyOEMRow(Element* peShowHide, LPCWSTR pszName)
{
Element* peRow = FindDescendentByName(peShowHide, pszName);
Element* peList = FindDescendentByName(peRow, L"list");
Value* pv;
LPCWSTR pszContent = peList->GetContentString(&pv);
if (!pszContent || !pszContent[0])
{
peRow->Destroy();
}
pv->Release();
}
// Take the setting from the ClientPicker and copy it to the Custom item
// This is done in preparation for Apply()ing the custom item to make the
// changes stick.
HRESULT ClientBlock::TransferFromClientPicker(ClientPicker* pcp)
{
HRESULT hr = S_OK;
CLIENTINFO* pciSel = pcp->GetSelectedClient();
for (UINT i = 0; SUCCEEDED(hr) && i < _pdaClients->GetSize(); i++)
{
CLIENTINFO* pci = _pdaClients->GetItem(i);
// If this is the one the guy selected, then select it here too
if (pci == pciSel && _peSel)
{
if (pci->IsPickFromList())
{
// "Pick from list" -> "Keep unchanged"
_peSel->SetSelection(_pdaClients->GetItem(1)->GetSetDefault());
}
else
{
_peSel->SetSelection(pci->GetSetDefault());
}
}
// Transfer the hide/show setting into the element
TRIBIT tShow = _GetFilterShowAdd(pci, pcp, NULL);
if (tShow != TRIBIT_UNDEFINED)
{
pci->SetShowCheckbox(tShow == TRIBIT_TRUE);
}
}
return hr;
}
//
// Okay, here it is, the whole reason we're here. Apply the user's
// choices.
//
HRESULT ClientBlock::Apply(ARPFrame* paf)
{
HRESULT hr = S_OK;
HKEY hkClient = _OpenClientKey(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WRITE);
if (hkClient)
{
// Note! Start loop with 2 because we don't care about applying "Keep Unchanged"
// or "Pick an app"
DUIAssert(_pdaClients->GetItem(0)->IsPickFromList(), "GetItem(0) must be 'Pick from list'");
DUIAssert(_pdaClients->GetItem(1)->IsKeepUnchanged(), "GetItem(1) must be 'Keep unchanged'");
for (UINT i = 2; SUCCEEDED(hr) && i < _pdaClients->GetSize(); i++)
{
CLIENTINFO* pci = _pdaClients->GetItem(i);
TCHAR szBuf[MAX_PATH];
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s\\InstallInfo"), pci->_pszKey);
HKEY hkInfo;
if (RegOpenKeyEx(hkClient, szBuf, 0, KEY_READ, &hkInfo) == ERROR_SUCCESS)
{
// Always do hide/show first. That way, an application being
// asked to set itself as the default always does so while its
// icons are shown.
bool bShow = pci->IsShowChecked();
if (bShow != pci->_bShown)
{
if (pci->GetInstallCommand(hkInfo, bShow ? TEXT("ShowIconsCommand") : TEXT("HideIconsCommand"),
szBuf, DUIARRAYSIZE(szBuf)))
{
hr = paf->LaunchClientCommandAndWait(bShow ? IDS_SHOWINGICONS : IDS_HIDINGICONS, pci->_pszName, szBuf);
}
}
if (pci->GetSetDefault()->GetSelected())
{
if (pci->GetInstallCommand(hkInfo, TEXT("ReinstallCommand"),
szBuf, DUIARRAYSIZE(szBuf)))
{
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
SHSetValue(hkClient, NULL, TEXT("LastUserInitiatedDefaultChange"),
REG_BINARY, &ft, sizeof(ft));
hr = paf->LaunchClientCommandAndWait(IDS_SETTINGDEFAULT, pci->_pszName, szBuf);
}
}
RegCloseKey(hkInfo);
}
}
RegCloseKey(hkClient);
}
return hr;
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// ClientType property
static int vvClientType[] = { DUIV_STRING, -1 };
static PropertyInfo impClientTypeProp = { L"ClientType", PF_Normal, 0, vvClientType, NULL, Value::pvStringNull };
PropertyInfo* ClientBlock::ClientTypeProp = &impClientTypeProp;
// WindowsClient property
static int vvWindowsClient[] = { DUIV_STRING, -1 };
static PropertyInfo impWindowsClientProp = { L"WindowsClient", PF_Normal, 0, vvWindowsClient, NULL, Value::pvStringNull };
PropertyInfo* ClientBlock::WindowsClientProp = &impWindowsClientProp;
// OtherMSClients property
static int vvOtherMSClients[] = { DUIV_STRING, -1 };
static PropertyInfo impOtherMSClientsProp = { L"OtherMSClients", PF_Normal, 0, vvOtherMSClients, NULL, Value::pvStringNull };
PropertyInfo* ClientBlock::OtherMSClientsProp = &impOtherMSClientsProp;
// KeepText property
static int vvKeepText[] = { DUIV_STRING, -1 };
static PropertyInfo impKeepTextProp = { L"KeepText", PF_Normal, 0, vvKeepText, NULL, Value::pvStringNull };
PropertyInfo* ClientBlock::KeepTextProp = &impKeepTextProp;
// KeepMSText property
static int vvKeepMSText[] = { DUIV_STRING, -1 };
static PropertyInfo impKeepMSTextProp = { L"KeepMSText", PF_Normal, 0, vvKeepMSText, NULL, Value::pvStringNull };
PropertyInfo* ClientBlock::KeepMSTextProp = &impKeepMSTextProp;
// PickText property
static int vvPickText[] = { DUIV_STRING, -1 };
static PropertyInfo impPickTextProp = { L"PickText", PF_Normal, 0, vvPickText, NULL, Value::pvStringNull };
PropertyInfo* ClientBlock::PickTextProp = &impPickTextProp;
// Class properties
PropertyInfo* _aClientBlockPI[] = {
ClientBlock::ClientTypeProp,
ClientBlock::WindowsClientProp,
ClientBlock::OtherMSClientsProp,
ClientBlock::KeepTextProp,
ClientBlock::KeepMSTextProp,
ClientBlock::PickTextProp,
};
// Define class info with type and base type, set static class pointer
IClassInfo* ClientBlock::Class = NULL;
HRESULT ClientBlock::Register()
{
return ClassInfo<ClientBlock,super>::Register(L"clientblock", _aClientBlockPI, DUIARRAYSIZE(_aClientBlockPI));
}
////////////////////////////////////////////////////////
// Expandable class
//
// Base class for Expando and Clipper. It is just an element
// with an "expanded" property. This property inherits from parent
// to child. This is used so Clipper can inherit (and therefore
// react to) the expanded state of its parent Expando.
//
HRESULT Expandable::Create(OUT Element** ppElement)
{
*ppElement = NULL;
Expandable* pe = HNew<Expandable>();
if (!pe)
return E_OUTOFMEMORY;
HRESULT hr = pe->Initialize(0);
if (FAILED(hr))
{
pe->Destroy();
return hr;
}
*ppElement = pe;
return S_OK;
}
////////////////////////////////////////////////////////
// Property definitions
/** Property template (replace !!!), also update private PropertyInfo* parray and class header (element.h)
// !!! property
static int vv!!![] = { DUIV_INT, -1 }; StaticValue(svDefault!!!, DUIV_INT, 0);
static PropertyInfo imp!!!Prop = { L"!!!", PF_Normal, 0, vv!!!, (Value*)&svDefault!!! };
PropertyInfo* Element::!!!Prop = &imp!!!Prop;
**/
// Expanded property
static int vvExpanded[] = { DUIV_BOOL, -1 };
static PropertyInfo impExpandedProp = { L"Expanded", PF_Normal|PF_Inherit, 0, vvExpanded, NULL, Value::pvBoolTrue };
PropertyInfo* Expandable::ExpandedProp = &impExpandedProp;
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Class properties
PropertyInfo* _aExpandablePI[] = { Expandable::ExpandedProp };
// Define class info with type and base type, set static class pointer
IClassInfo* Expandable::Class = NULL;
HRESULT Expandable::Register()
{
return ClassInfo<Expandable,super>::Register(L"Expandable", _aExpandablePI, DUIARRAYSIZE(_aExpandablePI));
}
////////////////////////////////////////////////////////
// Expando class
//
// An Expando element works in conjunction with a Clipper element
// to provide expand/collapse functionality.
//
// The Expando element manages the expanded/contracted state.
// The Expando element has two child elements:
//
// The first element is a button (the "header").
// The second element is a Clipper.
//
// The Clipper vanishes when contracted and is shown when expanded.
// The header is always shown.
//
// One of the elements in the header must be a button of type "arrow".
// Clicking this button causes the Expando to expand/collapse.
//
// A click on any other element causes an Expando::Click event
// to fire (to be caught by an ancestor element.)
//
// The "selected" property on the "arrow" tracks the "expanded"
// property on the Expando.
//
DefineClassUniqueID(Expando, Click)
HRESULT Expando::Create(OUT Element** ppElement)
{
*ppElement = NULL;
Expando* pex = HNewAndZero<Expando>();
if (!pex)
return E_OUTOFMEMORY;
HRESULT hr = pex->Initialize();
if (FAILED(hr))
{
pex->Destroy();
return hr;
}
*ppElement = pex;
return S_OK;
}
HRESULT Expando::Initialize()
{
HRESULT hr;
// Initialize base
hr = super::Initialize(0); // Normal display node creation
if (FAILED(hr))
return hr;
// Initialize
_fExpanding = false;
return S_OK;
}
Clipper* Expando::GetClipper()
{
Element* pe = GetNthChild(this, 1);
DUIAssertNoMsg(pe->GetClassInfo()->IsSubclassOf(Clipper::Class));
return (Clipper*)pe;
}
//
// Do this so ARPSelector will select us and deselect our siblings
//
void Expando::FireClickEvent()
{
Event e;
e.uidType = Expando::Click;
FireEvent(&e); // Will route and bubble
}
void Expando::OnEvent(Event* pev)
{
if (pev->nStage == GMF_BUBBLED)
{
if (pev->uidType == Button::Click)
{
pev->fHandled = true;
// Clicking the arrow toggles the expanded state
if (pev->peTarget->GetID() == StrToID(L"arrow"))
{
SetExpanded(!GetExpanded());
}
else
{
// Clicking anything else activates our section
FireClickEvent();
}
}
}
Element::OnEvent(pev);
}
////////////////////////////////////////////////////////
// System events
HRESULT _SetParentExpandedProp(ClientPicker* pcp, LPARAM lParam)
{
Value* pv = (Value*)lParam;
pcp->SetValue(ClientPicker::ParentExpandedProp, PI_Local, pv);
return S_OK;
}
void Expando::OnPropertyChanged(PropertyInfo* ppi, int iIndex, Value* pvOld, Value* pvNew)
{
// Do default processing
Element::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
if (IsProp(Selected))
{
// BUGBUG something goes here?
}
else if (IsProp(Expanded))
{
// Update height of clipper based on expanded state
Element* pe = GetClipper();
if (pe)
{
// The following will cause a relayout, mark object so that
// when the expando's Extent changes, it'll go through
// with the EnsureVisible. Otherwise, it's being resized
// as a result of something else. In which case, do nothing.
_fExpanding = true;
// To achieve "pulldown" animation, we use a clipper control that will
// size it's child based on it's unconstrained desired size in its Y direction.
// We also push the Expanded property into all child ClientPicker
// elements as the Selected property so they can turn static when
// collapsed.
if (pvNew->GetBool())
{
pe->RemoveLocalValue(HeightProp);
}
else
{
pe->SetHeight(0);
}
TraverseTree<ClientPicker>(pe, _SetParentExpandedProp, (LPARAM)pvNew);
}
// child Clipper object inherits the Expanded state
// Push the Expanded state as the arrow's Selected state
FindDescendentByName(this, L"arrow")->SetValue(SelectedProp, PI_Local, pvNew);
}
else if (IsProp(Extent))
{
if (_fExpanding && GetExpanded())
{
_fExpanding = false;
// On extent, we want to ensure that not just the client area but
// also the bottom margin of the expando is visible. Why? Simply
// because it looks better to scroll the expando plus its margin
// into view versus just the expando.
//
Value* pvSize;
Value* pvMargin;
const SIZE* psize = GetExtent(&pvSize);
const RECT* prect = GetMargin(&pvMargin);
EnsureVisible(0, 0, psize->cx, psize->cy + prect->bottom);
pvSize->Release();
pvMargin->Release();
}
}
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* Expando::Class = NULL;
HRESULT Expando::Register()
{
return ClassInfo<Expando,super>::Register(L"Expando", NULL, 0);
}
////////////////////////////////////////////////////////
//
// Clipper class
//
// Used to do the smooth hide/show animation.
//
// The Clipper element animates away its one child, typically
// an <element> with layout and inner child elements.
//
HRESULT Clipper::Create(OUT Element** ppElement)
{
*ppElement = NULL;
Clipper* pc = HNewAndZero<Clipper>();
if (!pc)
return E_OUTOFMEMORY;
HRESULT hr = pc->Initialize();
if (FAILED(hr))
{
pc->Destroy();
return hr;
}
*ppElement = pc;
return S_OK;
}
HRESULT Clipper::Initialize()
{
// Initialize base
HRESULT hr = super::Initialize(EC_SelfLayout); // Normal display node creation, self layout
if (FAILED(hr))
return hr;
// Children can exist outside of Element bounds
SetGadgetStyle(GetDisplayNode(), GS_CLIPINSIDE, GS_CLIPINSIDE);
return S_OK;
}
////////////////////////////////////////////////////////
// Self-layout methods
SIZE Clipper::_SelfLayoutUpdateDesiredSize(int cxConstraint, int cyConstraint, Surface* psrf)
{
UNREFERENCED_PARAMETER(cyConstraint);
SIZE size = { 0, 0 };
// Desired size of this is based solely on it's first child.
// Width is child's width, height is unconstrained height of child.
Element* pec = GetNthChild(this, 0);
if (pec)
{
size = pec->_UpdateDesiredSize(cxConstraint, INT_MAX, psrf);
if (size.cx > cxConstraint)
size.cx = cxConstraint;
if (size.cy > cyConstraint)
size.cy = cyConstraint;
}
return size;
}
void Clipper::_SelfLayoutDoLayout(int cx, int cy)
{
// Layout first child giving it's desired height and aligning
// it with the clipper's bottom edge
Element* pec = GetNthChild(this, 0);
if (pec)
{
const SIZE* pds = pec->GetDesiredSize();
pec->_UpdateLayoutPosition(0, cy - pds->cy);
pec->_UpdateLayoutSize(cx, pds->cy);
}
}
////////////////////////////////////////////////////////
// Property definitions
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* Clipper::Class = NULL;
HRESULT Clipper::Register()
{
return ClassInfo<Clipper,super>::Register(L"Clipper", NULL, 0);
}
////////////////////////////////////////////////////////
// GradientLine class
//
// This is necessary for two reasons.
//
// 1. gradient(...) doesn't support FILLTYPE_TriHGradient.
// The code to implement tri-gradients exists only in
// the GdiPlus version. We can fake it by putting two
// FILLTYPE_HGradient elements next to each other, except
// for the second problem...
// 2. gradient(...) doesn't support system colors like "buttonface".
//
HRESULT GradientLine::Create(OUT Element** ppElement)
{
*ppElement = NULL;
GradientLine* pe = HNew<GradientLine>();
if (!pe)
return E_OUTOFMEMORY;
HRESULT hr = pe->Initialize(0);
if (FAILED(hr))
{
pe->Destroy();
return hr;
}
*ppElement = pe;
return S_OK;
}
COLORREF GradientLine::GetColorProperty(PropertyInfo* ppi)
{
// on failure, use transparent color (i.e., nothing happens)
COLORREF cr = ARGB(0xFF, 0, 0, 0);
Value* pv = GetValue(ppi, PI_Specified);
switch (pv->GetType())
{
case DUIV_INT:
cr = ColorFromEnumI(pv->GetInt());
break;
case DUIV_FILL:
{
const Fill* pf = pv->GetFill();
if (pf->dType == FILLTYPE_Solid)
{
cr = pf->ref.cr;
}
else
{
DUIAssert(0, "GradientLine supports only solid colors");
}
}
break;
default:
DUIAssert(0, "GradientLine supports only solid colors");
}
pv->Release();
return cr;
}
void GradientLine::Paint(HDC hDC, const RECT* prcBounds, const RECT* prcInvalid, RECT* prcSkipBorder, RECT* prcSkipContent)
{
// Paint default except content
RECT rcContent;
Element::Paint(hDC, prcBounds, prcInvalid, prcSkipBorder, &rcContent);
// Render gradient content if requested
if (!prcSkipContent)
{
//
// Vertices are as indicated. The two rectangles are (0-1) and (1-2).
//
// 0(bgcolor) 2(bgcolor)
// +-----------------+----------------+
// | |
// | |
// | |
// +-----------------+----------------+
// 1(fgcolor)
TRIVERTEX rgvert[3];
GRADIENT_RECT rggr[2];
COLORREF cr;
cr = GetColorProperty(BackgroundProp);
rgvert[0].x = rcContent.left;
rgvert[0].y = rcContent.top;
rgvert[0].Red = GetRValue(cr) << 8;
rgvert[0].Green = GetGValue(cr) << 8;
rgvert[0].Blue = GetBValue(cr) << 8;
rgvert[0].Alpha = GetAValue(cr) << 8;
rgvert[2] = rgvert[0];
rgvert[2].x = rcContent.right;
cr = GetColorProperty(ForegroundProp);
rgvert[1].x = (rcContent.left + rcContent.right) / 2;
rgvert[1].y = rcContent.bottom;
rgvert[1].Red = GetRValue(cr) << 8;
rgvert[1].Green = GetGValue(cr) << 8;
rgvert[1].Blue = GetBValue(cr) << 8;
rgvert[1].Alpha = GetAValue(cr) << 8;
rggr[0].UpperLeft = 0;
rggr[0].LowerRight = 1;
rggr[1].UpperLeft = 1;
rggr[1].LowerRight = 2;
GradientFill(hDC, rgvert, DUIARRAYSIZE(rgvert), rggr, DUIARRAYSIZE(rggr), GRADIENT_FILL_RECT_H);
}
else
{
*prcSkipContent = rcContent;
}
}
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Define class info with type and base type, set static class pointer
IClassInfo* GradientLine::Class = NULL;
HRESULT GradientLine::Register()
{
return ClassInfo<GradientLine,super>::Register(L"GradientLine", NULL, 0);
}
////////////////////////////////////////////////////////
// BigElement class
//
// This is necessary because the DUI parser limits rcstr() to 256
// characters and we have strings that are dangerously close to that
// limit. (So localization will likely push them over the limit.)
//
HRESULT BigElement::Create(OUT Element** ppElement)
{
*ppElement = NULL;
BigElement* pe = HNew<BigElement>();
if (!pe)
return E_OUTOFMEMORY;
HRESULT hr = pe->Initialize(0);
if (FAILED(hr))
{
pe->Destroy();
return hr;
}
*ppElement = pe;
return S_OK;
}
void BigElement::OnPropertyChanged(PropertyInfo* ppi, int iIndex, Value* pvOld, Value* pvNew)
{
// Do default processing
Element::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
if (IsProp(StringResID))
{
UINT uID = pvNew->GetInt();
HRSRC hrsrc = FindResource(g_hinst, (LPTSTR)(LONG_PTR)(1 + uID / 16), RT_STRING);
if (hrsrc)
{
PWCHAR pwch = (PWCHAR)LoadResource(g_hinst, hrsrc);
if (pwch)
{
// Now skip over strings until we hit the one we want.
for (uID %= 16; uID; uID--)
{
pwch += *pwch + 1;
}
// Found it -- load the entire string and set it
LPWSTR pszString = new WCHAR[*pwch + 1];
if (pszString)
{
memcpy(pszString, pwch+1, *pwch * sizeof(WCHAR));
pszString[*pwch] = L'\0';
SetContentString(pszString);
SetAccName(pszString);
delete[] pszString;
}
}
}
}
}
////////////////////////////////////////////////////////
// Property definitions
// StringResID property
static int vvStringResID[] = { DUIV_INT, -1 };
static PropertyInfo impStringResIDProp = { L"StringResID", PF_Normal, 0, vvStringResID, NULL, Value::pvIntZero };
PropertyInfo* BigElement::StringResIDProp = &impStringResIDProp;
////////////////////////////////////////////////////////
// ClassInfo (must appear after property definitions)
// Class properties
PropertyInfo* _aBigElementPI[] = { BigElement::StringResIDProp };
// Define class info with type and base type, set static class pointer
IClassInfo* BigElement::Class = NULL;
HRESULT BigElement::Register()
{
return ClassInfo<BigElement,super>::Register(L"BigElement", _aBigElementPI, DUIARRAYSIZE(_aBigElementPI));
}
////////////////////////////////////////////////////////
// ARP Parser callback
void CALLBACK ARPParseError(LPCWSTR pszError, LPCWSTR pszToken, int dLine)
{
WCHAR buf[201];
if (dLine != -1)
{
StringCchPrintfW(buf, ARRAYSIZE(buf), L"%s '%s' at line %d", pszError, pszToken, dLine);
}
else
{
StringCchPrintfW(buf, ARRAYSIZE(buf), L"%s '%s'", pszError, pszToken);
}
MessageBoxW(NULL, buf, L"Parser Message", MB_OK);
}
void inline SetElementAccessability(Element* pe, bool bAccessible, int iRole, LPCWSTR pszAccName)
{
if (pe)
{
pe->SetAccessible(bAccessible);
pe->SetAccRole(iRole);
pe->SetAccName(pszAccName);
}
}
void EnablePane(Element* pePane, bool fEnable)
{
if (fEnable)
{
pePane->SetLayoutPos(BLP_Client);
EnableElementTreeAccessibility(pePane);
}
else
{
pePane->SetLayoutPos(LP_None);
DisableElementTreeAccessibility(pePane);
}
}
void BestFitOnDesktop(RECT* r)
{
ASSERT(r != NULL);
RECT wr; // Rect to hold size of work area
if (SystemParametersInfo(SPI_GETWORKAREA, 0, &wr, 0))
{
if ((wr.right-wr.left) < ARP_DEFAULT_WIDTH)
{
// Default width is too large, use the entire width of the desktop area
r->left = wr.left;
r->right = wr.right - wr.left;
}
else
{
// Center on screen using default width
r->left = wr.left + (((wr.right-wr.left) - ARP_DEFAULT_WIDTH) / 2);
r->right = ARP_DEFAULT_WIDTH;
}
if ((wr.bottom-wr.top) < ARP_DEFAULT_HEIGHT)
{
// Default height is too large, use the entire height of the desktop area
r->top = wr.top;
r->bottom = wr.bottom - wr.top;
}
else
{
// Center on screen using default height
r->top = wr.top + (((wr.bottom-wr.top) - ARP_DEFAULT_HEIGHT) / 2);
r->bottom = ARP_DEFAULT_HEIGHT;
}
}
else
{
// Don't know why the function would fail, but if it does just use the default size
// and position
SetRect(r,
ARP_DEFAULT_POS_X,
ARP_DEFAULT_POS_Y,
ARP_DEFAULT_WIDTH,
ARP_DEFAULT_HEIGHT);
}
}
////////////////////////////////////////////////////////
// ARP entry point
DWORD WINAPI PopulateInstalledItemList(void* paf);
STDAPI ARP(HWND hWnd, int nPage)
{
HRESULT hr;
Parser* pParser = NULL;
NativeHWNDHost* pnhh = NULL;
ARPFrame* paf = NULL;
Element* pe = NULL;
RECT rect;
WCHAR szTemp[1024];
// DirectUI init process
hr = InitProcess();
if (FAILED(hr))
goto Failure;
// Register classes
hr = ARPFrame::Register();
if (FAILED(hr))
goto Failure;
hr = ARPItem::Register();
if (FAILED(hr))
goto Failure;
hr = ARPHelp::Register();
if (FAILED(hr))
goto Failure;
hr = ARPSupportItem::Register();
if (FAILED(hr))
goto Failure;
hr = ARPSelector::Register();
if (FAILED(hr))
goto Failure;
hr = ClientPicker::Register();
if (FAILED(hr))
goto Failure;
hr = AutoButton::Register();
if (FAILED(hr))
goto Failure;
hr = ClientBlock::Register();
if (FAILED(hr))
goto Failure;
hr = Expandable::Register();
if (FAILED(hr))
goto Failure;
hr = Expando::Register();
if (FAILED(hr))
goto Failure;
hr = Clipper::Register();
if (FAILED(hr))
goto Failure;
hr = GradientLine::Register();
if (FAILED(hr))
goto Failure;
hr = BigElement::Register();
if (FAILED(hr))
goto Failure;
// DirectUI init thread
hr = InitThread();
if (FAILED(hr))
goto Failure;
hr = CoInitialize(NULL);
if (FAILED(hr))
goto Failure;
Element::StartDefer();
// Create host
LoadStringW(g_hinst, IDS_ARPTITLE, szTemp, DUIARRAYSIZE(szTemp));
BestFitOnDesktop(&rect);
hr = NativeHWNDHost::Create(szTemp, hWnd, LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_CPLICON)), rect.left, rect.top, rect.right, rect.bottom, WS_EX_APPWINDOW, WS_OVERLAPPEDWINDOW, 0, &pnhh);
if (FAILED(hr))
goto Failure;
hr = ARPFrame::Create(pnhh, true, (Element**)&paf);
if (FAILED(hr))
goto Failure;
// Load resources
ARPParser::Create(paf, IDR_ARP, g_hinst, ARPParseError, &pParser);
if (!pParser || pParser->WasParseError())
goto Failure;
pParser->CreateElement(L"main", paf, &pe);
if (pe && // Fill contents using substitution
paf->Setup(pParser, nPage)) // Set ARPFrame state (incluing ID initialization)
{
// Set visible and host
paf->SetVisible(true);
pnhh->Host(paf);
Element::EndDefer();
// Do initial show
pnhh->ShowWindow();
Element* peClose = ((ARPFrame*)pe)->FallbackFocus();
if (peClose)
{
peClose->SetKeyFocus();
}
if (!paf->IsChangeRestricted())
{
paf->UpdateInstalledItems();
}
// Pump messages
MSG msg;
bool fDispatch = true;
while (GetMessageW(&msg, 0, 0, 0) != 0)
{
// Check for destruction of top-level window (always async)
if (msg.hwnd == pnhh->GetHWND() && msg.message == NHHM_ASYNCDESTROY)
{
// Async destroy requested, clean up secondary threads
// Signal that secondary threads should complete as soon as possible
// Any requests from secondary threads will be ignored
// No more secondary threads will be allowed to start
g_fRun = false;
// Hide window, some threads may need more time to exit normally
pnhh->HideWindow();
// Don't dispatch this one
if (!g_fAppShuttingDown)
fDispatch = false;
}
// Check for pending threads
if (!g_fRun)
{
if (!ARPFrame::htPopulateInstalledItemList &&
!ARPFrame::htPopulateAndRenderOCSetupItemList &&
!ARPFrame::htPopulateAndRenderPublishedItemList)
{
if (!g_fAppShuttingDown)
{
// Done, reissue async destroy
DUITrace(">> App shutting down, async destroying main window\n");
g_fAppShuttingDown = true;
pnhh->DestroyWindow();
}
}
}
if (fDispatch)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
fDispatch = true;
}
// paf will be deleted by native HWND host when destroyed
}
else
Element::EndDefer();
Failure:
if (pnhh)
{
if (pnhh->GetHWND())
{
// In the error case we didn't destroy the window cleanly, so
// we need to do it viciously. Cannot use pnhh->DestroyWindow()
// because that defers the destroy but we need it to happen now.
DestroyWindow(pnhh->GetHWND());
}
pnhh->Destroy();
}
if (pParser)
pParser->Destroy();
CoUninitialize();
UnInitThread();
UnInitProcess();
return 0;
}
DWORD _cdecl ARPIsRestricted(LPCWSTR pszPolicy)
{
return SHGetRestriction(NULL, L"Uninstall", pszPolicy);
}
bool _cdecl ARPIsOnDomain()
{
// NOTE: assume it's on the domain
bool bRet = true;
LPWSTR pszDomain;
NETSETUP_JOIN_STATUS nsjs;
if (NERR_Success == NetGetJoinInformation(NULL, &pszDomain, &nsjs))
{
if (nsjs != NetSetupDomainName)
bRet = FALSE;
NetApiBufferFree(pszDomain);
}
return bRet;
}
////////////////////////////////////////////////////////
// Async ARP item population thread
////////////////////////////////////////////////////////
// Query system and enumerate installed apps
HRESULT BuildPublishedAppArray(IEnumPublishedApps *penum, HDSA *phdsaPubApps);
HRESULT InstallPublishedAppArray(ARPFrame *paf, HDSA hdsaPubApps, UINT *piCount);
HRESULT InsertPubAppInPubAppArray(HDSA hdsa, IPublishedApp *ppa);
HRESULT GetPubAppName(IPublishedApp *ppa, LPWSTR *ppszName);
int CALLBACK DestroyPublishedAppArrayEntry(void *p, void *pData);
DWORD WINAPI PopulateAndRenderPublishedItemList(void* paf)
{
DUITrace(">> Thread 'htPopulateAndRenderPublishedItemList' STARTED.\n");
HRESULT hr;
UINT iCount = 0;
IShellAppManager* pisam = NULL;
IEnumPublishedApps* piepa = NULL;
IPublishedApp* pipa = NULL;
HDCONTEXT hctx = NULL;
// Initialize
HRESULT hrOle = CoInitialize(NULL);
INITGADGET ig;
ZeroMemory(&ig, sizeof(ig));
ig.cbSize = sizeof(ig);
ig.nThreadMode = IGTM_MULTIPLE;
ig.nMsgMode = IGMM_ADVANCED;
ig.hctxShare = NULL;
hctx = InitGadgets(&ig);
if (hctx == NULL) {
goto Cleanup;
}
// Create shell manager
hr = CoCreateInstance(__uuidof(ShellAppManager), NULL, CLSCTX_INPROC_SERVER, __uuidof(IShellAppManager), (void**)&pisam);
HRCHK(hr);
if (!((ARPFrame*)paf)->GetPublishedComboFilled())
{
// Get the list of categories
SHELLAPPCATEGORYLIST* psacl = ((ARPFrame*)paf)->GetShellAppCategoryList();
if (psacl == NULL)
{
psacl = new SHELLAPPCATEGORYLIST;
}
if (psacl == NULL)
{
goto Cleanup;
}
else
{
((ARPFrame*)paf)->SetShellAppCategoryList(psacl);
}
hr = pisam->GetPublishedAppCategories(psacl);
((ARPFrame*)paf)->PopulateCategoryCombobox();
((ARPFrame*)paf)->SetPublishedComboFilled(true);
}
hr = pisam->EnumPublishedApps(((ARPFrame*)paf)->GetCurrentPublishedCategory(), &piepa);
HRCHK(hr);
HDSA hdsaPubApps = NULL;
hr = BuildPublishedAppArray(piepa, &hdsaPubApps);
HRCHK(hr);
hr = InstallPublishedAppArray((ARPFrame *)paf, hdsaPubApps, &iCount);
HRCHK(hr);
if (iCount == 0)
{
((ARPFrame*)paf)->FeedbackEmptyPublishedList();
}
Cleanup:
if (NULL != hdsaPubApps)
{
DSA_DestroyCallback(hdsaPubApps, DestroyPublishedAppArrayEntry, NULL);
hdsaPubApps = NULL;
}
if (paf)
{
((ARPFrame*)paf)->OnPublishedListComplete();
((ARPFrame*)paf)->SetPublishedListFilled(true);
}
if (pisam)
pisam->Release();
if (piepa)
piepa->Release();
if (hctx)
DeleteHandle(hctx);
if (SUCCEEDED(hrOle))
{
CoUninitialize();
}
// Thread is done
ARPFrame::htPopulateAndRenderPublishedItemList = NULL;
// Information primary thread that this worker is complete
PostMessage(((ARPFrame*)paf)->GetHWND(), WM_ARPWORKERCOMPLETE, 0, 0);
DUITrace(">> Thread 'htPopulateAndRenderPublishedItemList' DONE.\n");
return 0;
}
// ----------------------------------------------------------------------------
// Handling published apps with duplicate names
// ----------------------------------------------------------------------------
//
// Entry in dynamic array of published app items.
// Entries with duplicate application names must be identifed
// in the UI by appending the applicable publishing source name
// to the display name of the application. In order to do this,
// we need to assemble all of the published entries in a sorted
// array then mark as such those that have duplicate names.
// When the array items are added to the ARP frame, the items
// marked 'duplicate' have their publisher's name appended to
// their application name.
//
struct PubItemListEntry
{
IPublishedApp *ppa; // The published app object.
bool bDuplicateName; // Does it have a duplicate name?
};
//
// Build the dynamic array of app/duplicate information.
// One entry for each published app. If this function succeeds,
// the caller is responsible for destroying the returnd DSA.
//
HRESULT
BuildPublishedAppArray(
IEnumPublishedApps *penum,
HDSA *phdsaPubApps
)
{
ASSERT(NULL != penum);
ASSERT(NULL != phdsaPubApps);
HRESULT hr = S_OK;
//
// Create a large DSA so that we minimize resizing.
//
HDSA hdsa = DSA_Create(sizeof(PubItemListEntry), 512);
if (NULL != hdsa)
{
IPublishedApp *ppa;
while(g_fRun)
{
hr = THR(penum->Next(&ppa));
if (S_OK == hr)
{
//
// Ignore any errors related to a specific published app.
//
THR(InsertPubAppInPubAppArray(hdsa, ppa));
ppa->Release();
}
else
{
break;
}
}
if (FAILED(hr))
{
DSA_DestroyCallback(hdsa, DestroyPublishedAppArrayEntry, NULL);
hdsa = NULL;
}
}
else
{
hr = E_OUTOFMEMORY;
}
ASSERT(FAILED(hr) || NULL != hdsa);
*phdsaPubApps = hdsa;
return THR(hr);
}
//
// Retrieve the application name string for a given published app.
// If this function succeeds, the caller is responsible for freeing
// the name string by using SHFree.
//
HRESULT
GetPubAppName(
IPublishedApp *ppa,
LPWSTR *ppszName
)
{
ASSERT(NULL != ppa);
ASSERT(NULL != ppszName);
APPINFODATA aid;
aid.cbSize = sizeof(aid);
aid.dwMask = AIM_DISPLAYNAME;
*ppszName = NULL;
HRESULT hr = THR(ppa->GetAppInfo(&aid));
if (SUCCEEDED(hr))
{
if (AIM_DISPLAYNAME & aid.dwMask)
{
*ppszName = aid.pszDisplayName;
}
else
{
hr = E_FAIL;
}
}
return THR(hr);
}
//
// Insert a published app into the published app array.
// Upon return, the dynamic array is sorted by published app name
// and all duplicate entries are marked with their bDuplicateName
// member set to 'true'.
//
HRESULT
InsertPubAppInPubAppArray(
HDSA hdsa,
IPublishedApp *ppa
)
{
ASSERT(NULL != hdsa);
ASSERT(NULL != ppa);
LPWSTR pszAppName;
HRESULT hr = THR(GetPubAppName(ppa, &pszAppName));
if (SUCCEEDED(hr))
{
//
// Create the new entry. We'll addref the COM pointer
// only after the item is successfully inserted into the array.
//
PubItemListEntry entryNew = { ppa, false };
//
// Find the insertion point so that the array is
// sorted by app name.
//
const int cEntries = DSA_GetItemCount(hdsa);
int iInsertHere = 0; // Insertion point.
PubItemListEntry *pEntry = NULL;
for (iInsertHere = 0; iInsertHere < cEntries; iInsertHere++)
{
pEntry = (PubItemListEntry *)DSA_GetItemPtr(hdsa, iInsertHere);
TBOOL(NULL != pEntry);
if (NULL != pEntry)
{
LPWSTR psz;
hr = THR(GetPubAppName(pEntry->ppa, &psz));
if (SUCCEEDED(hr))
{
int iCompare = lstrcmpi(psz, pszAppName);
SHFree(psz);
psz = NULL;
if (0 <= iCompare)
{
//
// This is the insertion point.
//
if (0 == iCompare)
{
//
// This entry has the same name.
//
entryNew.bDuplicateName = true;
pEntry->bDuplicateName = true;
}
break;
}
}
}
}
//
// Now mark all other duplicates. Note that if the entry
// currently at the insertion point is a duplicate of the
// entry we're inserting, we've already marked it as a duplicate
// above. Therefore, we can start with the next entry.
//
for (int i = iInsertHere + 1; i < cEntries; i++)
{
pEntry = (PubItemListEntry *)DSA_GetItemPtr(hdsa, i);
TBOOL(NULL != pEntry);
if (NULL != pEntry)
{
LPWSTR psz;
hr = THR(GetPubAppName(pEntry->ppa, &psz));
if (SUCCEEDED(hr))
{
int iCompare = lstrcmpi(psz, pszAppName);
SHFree(psz);
psz = NULL;
//
// Assert that the array is sorted alphabetically.
//
ASSERT(0 <= iCompare);
if (0 == iCompare)
{
//
// Yep, another duplicate.
//
pEntry->bDuplicateName = true;
}
else
{
break; // No need to look further.
}
}
}
}
//
// Insert the new item.
//
if (-1 != DSA_InsertItem(hdsa, iInsertHere, &entryNew))
{
entryNew.ppa->AddRef();
}
else
{
hr = E_OUTOFMEMORY;
}
SHFree(pszAppName);
}
return THR(hr);
}
//
// Given a DSA of application/duplicate-flag pairs, install
// the items in the ARP frame.
//
HRESULT
InstallPublishedAppArray(
ARPFrame *paf,
HDSA hdsaPubApps,
UINT *piCount // optional. Can be NULL.
)
{
ASSERT(NULL != paf);
ASSERT(NULL != hdsaPubApps);
int cEntries = DSA_GetItemCount(hdsaPubApps);
paf->SetPublishedItemCount(cEntries);
UINT iCount = 0;
for (int i = 0; i < cEntries && g_fRun; i++)
{
PubItemListEntry *pEntry = (PubItemListEntry *)DSA_GetItemPtr(hdsaPubApps, i);
TBOOL(NULL != pEntry);
if (NULL != pEntry)
{
//
// Unfortunately, InsertPublishedItem() doesn't return a value.
//
paf->InsertPublishedItem(pEntry->ppa, pEntry->bDuplicateName);
iCount++;
}
}
if (NULL != piCount)
{
*piCount = iCount;
}
return S_OK;
}
//
// Callback for destroying the DSA of application/duplicate-flag pairs.
// Need to release the IPublishedApp ptr for each entry.
//
int CALLBACK
DestroyPublishedAppArrayEntry(
void *p,
void *pData
)
{
PubItemListEntry *pEntry = (PubItemListEntry *)p;
ASSERT(NULL != pEntry && NULL != pEntry->ppa);
ATOMICRELEASE(pEntry->ppa);
return 1;
}
DWORD WINAPI PopulateAndRenderOCSetupItemList(void* paf)
{
DUITrace(">> Thread 'htPopulateAndRenderOCSetupItemList' STARTED.\n");
HDCONTEXT hctx = NULL;
INITGADGET ig;
ZeroMemory(&ig, sizeof(ig));
ig.cbSize = sizeof(ig);
ig.nThreadMode = IGTM_MULTIPLE;
ig.nMsgMode = IGMM_ADVANCED;
ig.hctxShare = NULL;
hctx = InitGadgets(&ig);
if (hctx == NULL) {
goto Cleanup;
}
// Create an object that enums the OCSetup items
COCSetupEnum * pocse = new COCSetupEnum;
if (pocse)
{
if (pocse->EnumOCSetupItems())
{
COCSetupApp* pocsa;
while (g_fRun && pocse->Next(&pocsa))
{
APPINFODATA ai = {0};
ai.cbSize = sizeof(ai);
ai.dwMask = AIM_DISPLAYNAME;
if ( pocsa->GetAppInfo(&ai) && (lstrlen(ai.pszDisplayName) > 0) )
{
//
// InsertOCSetupItem doesn't return a status value
// so we have no way of knowing if the item was
// added to ARP or not. So... we have no way of knowing
// if we should delete it to prevent a leak.
// I've added code to ARPFrame::OnInvoke to delete
// the pocsa object if it cannot be added to ARP.
// [brianau - 2/27/01]
//
// Insert item
((ARPFrame*)paf)->InsertOCSetupItem(pocsa);
}
else
{
delete pocsa;
pocsa = NULL;
}
}
}
delete pocse;
pocse = NULL;
}
Cleanup:
if (hctx)
DeleteHandle(hctx);
// Thread is done
ARPFrame::htPopulateAndRenderOCSetupItemList = NULL;
// Information primary thread that this worker is complete
PostMessage(((ARPFrame*)paf)->GetHWND(), WM_ARPWORKERCOMPLETE, 0, 0);
DUITrace(">> Thread 'htPopulateAndRenderOCSetupItemList' DONE.\n");
return 0;
}
DWORD WINAPI PopulateInstalledItemList(void* paf)
{
DUITrace(">> Thread 'htPopulateInstalledItemList' STARTED.\n");
HRESULT hr;
IShellAppManager* pisam = NULL;
IEnumInstalledApps* pieia = NULL;
IInstalledApp* piia = NULL;
DWORD dwAppCount = 0;
APPINFODATA aid = {0};
HDCONTEXT hctx = NULL;
// Initialize
CoInitialize(NULL);
INITGADGET ig;
ZeroMemory(&ig, sizeof(ig));
ig.cbSize = sizeof(ig);
ig.nThreadMode = IGTM_MULTIPLE;
ig.nMsgMode = IGMM_ADVANCED;
ig.hctxShare = NULL;
hctx = InitGadgets(&ig);
if (hctx == NULL) {
goto Cleanup;
}
aid.cbSize = sizeof(APPINFODATA);
aid.dwMask = AIM_DISPLAYNAME | AIM_VERSION | AIM_PUBLISHER | AIM_PRODUCTID |
AIM_REGISTEREDOWNER | AIM_REGISTEREDCOMPANY | AIM_SUPPORTURL |
AIM_SUPPORTTELEPHONE | AIM_HELPLINK | AIM_INSTALLLOCATION | AIM_INSTALLDATE |
AIM_COMMENTS | AIM_IMAGE | AIM_READMEURL | AIM_CONTACT | AIM_UPDATEINFOURL;
// Create shell manager
hr = CoCreateInstance(__uuidof(ShellAppManager), NULL, CLSCTX_INPROC_SERVER, __uuidof(IShellAppManager), (void**)&pisam);
HRCHK(hr);
hr = pisam->EnumInstalledApps(&pieia);
HRCHK(hr);
// Count installed apps, IShellAppManager::GetNumberofInstalledApps() not impl
while (g_fRun)
{
hr = pieia->Next(&piia);
if (hr == S_FALSE) // Done with enumeration
break;
dwAppCount++;
}
// IEnumInstalledApps::Reset() doesn't work
pieia->Release();
pieia = NULL;
hr = pisam->EnumInstalledApps(&pieia);
HRCHK(hr);
// Set app count in frame
((ARPFrame*)paf)->SetInstalledItemCount(dwAppCount);
// Enumerate apps
while (g_fRun)
{
hr = pieia->Next(&piia);
if (hr == S_FALSE) // Done with enumeration
break;
// Insert item
if (piia != NULL)
{
((ARPFrame*)paf)->InsertInstalledItem(piia);
}
}
// Passing NULL to InsertInstalledItem signals ARP that it is finished
// inserting items and should now display the list.
if (dwAppCount > 0)
{
((ARPFrame*)paf)->InsertInstalledItem(NULL);
}
Cleanup:
if (pisam)
pisam->Release();
if (pieia)
pieia->Release();
if (hctx)
DeleteHandle(hctx);
CoUninitialize();
if (g_fRun)
((ARPFrame*)paf)->FlushWorkingSet();
// Thread is done
ARPFrame::htPopulateInstalledItemList = NULL;
// Information primary thread that this worker is complete
PostMessage(((ARPFrame*)paf)->GetHWND(), WM_ARPWORKERCOMPLETE, 0, 0);
DUITrace(">> Thread 'htPopulateInstalledItemList' DONE.\n");
return 0;
}
// Sorting
int __cdecl CompareElementDataName(const void* pA, const void* pB)
{
Value* pvName1 = NULL;
Value* pvName2 = NULL;
LPCWSTR pszName1 = NULL;
LPCWSTR pszName2 = NULL;
Element *pe;
if (NULL != pA)
{
pe = (*(ARPItem**)pA)->FindDescendent(ARPItem::_idTitle);
if (NULL != pe)
{
pszName1 = pe->GetContentString(&pvName1);
}
}
if (NULL != pB)
{
pe = (*(ARPItem**)pB)->FindDescendent(ARPItem::_idTitle);
if (NULL != pe)
{
pszName2 = pe->GetContentString(&pvName2);
}
}
static const int rgResults[2][2] = {
/* pszName2 == NULL, pszName2 != NULL */
/* pszName1 == NULL */ { 0, 1 },
/* pszName1 != NULL */ { -1, 2 }
};
int iResult = rgResults[int(NULL != pszName1)][int(NULL != pszName2)];
if (2 == iResult)
{
iResult = StrCmpW(pszName1, pszName2);
}
if (NULL != pvName1)
{
pvName1->Release();
}
if (NULL != pvName2)
{
pvName2->Release();
}
return iResult;
}
int __cdecl CompareElementDataSize(const void* pA, const void* pB)
{
ULONGLONG ull1 = (*(ARPItem**)pA)->_ullSize;
ULONGLONG ull2 = (*(ARPItem**)pB)->_ullSize;
if (!IsValidSize(ull1))
ull1 = 0;
if (!IsValidSize(ull2))
ull2 = 0;
// Big apps come before smaller apps
if (ull1 > ull2)
return -1;
else if (ull1 < ull2)
return 1;
return CompareElementDataName(pA, pB);
}
int __cdecl CompareElementDataFreq(const void* pA, const void* pB)
{
// Rarely used apps come before frequently used apps. Blank
// (unknown) apps go last. Unknown apps are -1, so those sort
// to the bottom if we simply compare unsigned values.
UINT u1 = (UINT)(*(ARPItem**)pA)->_iTimesUsed;
UINT u2 = (UINT)(*(ARPItem**)pB)->_iTimesUsed;
if (u1 < u2)
return -1;
else if (u1 > u2)
return 1;
return CompareElementDataName(pA, pB);
}
int __cdecl CompareElementDataLast(const void* pA, const void* pB)
{
FILETIME ft1 = (*(ARPItem**)pA)->_ftLastUsed;
FILETIME ft2 = (*(ARPItem**)pB)->_ftLastUsed;
BOOL bTime1 = IsValidFileTime(ft1);
BOOL bTime2 = IsValidFileTime(ft2);
if (!bTime1 || !bTime2)
{
if (bTime1)
return -1;
if (bTime2)
return 1;
// else they're both not set -- use name
}
else
{
LONG diff = CompareFileTime(&ft1, &ft2);
if (diff)
return diff;
}
return CompareElementDataName(pA, pB);
}
//
// Stuff imported from other modules which have been deleted.
//
const TCHAR c_szStubWindowClass[] = TEXT("Add/Remove Stub Window");
HWND _CreateTransparentStubWindow(HWND hwndParent)
{
WNDCLASS wc;
RECT rc = {0};
if (hwndParent)
{
RECT rcParent = {0};
GetWindowRect(hwndParent, &rcParent);
rc.left = (rcParent.left + RECTWIDTH(rcParent)) / 2;
rc.top = (rcParent.top + RECTHEIGHT(rcParent)) / 2;
}
else
{
rc.left = CW_USEDEFAULT;
rc.top = CW_USEDEFAULT;
}
if (!GetClassInfo(HINST_THISDLL, c_szStubWindowClass, &wc))
{
wc.style = 0;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = SIZEOF(DWORD) * 2;
wc.hInstance = HINST_THISDLL;
wc.hIcon = NULL;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = c_szStubWindowClass;
RegisterClass(&wc);
}
// WS_EX_APPWINDOW makes this show up in ALT+TAB, but not the tray.
return CreateWindowEx(WS_EX_TRANSPARENT, c_szStubWindowClass, TEXT(""), WS_POPUP | WS_VISIBLE, rc.left,
rc.top, 1, 1, hwndParent, NULL, HINST_THISDLL, NULL);
}