Source code of Windows XP (NT5)
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.

955 lines
20 KiB

  1. /*++
  2. Copyright (c) 1994-1998 Microsoft Corporation
  3. Module Name :
  4. menuex.cpp
  5. Abstract:
  6. Menu extension classes
  7. Author:
  8. Ronald Meijer (ronaldm)
  9. Project:
  10. Internet Services Manager
  11. Revision History:
  12. --*/
  13. #include "stdafx.h"
  14. #include "inetmgr.h"
  15. #include "menuex.h"
  16. #include "constr.h"
  17. #ifdef _DEBUG
  18. #undef THIS_FILE
  19. static char BASED_CODE THIS_FILE[] = __FILE__;
  20. #endif
  21. //
  22. // Static member initialization;
  23. //
  24. const TCHAR CISMShellExecutable::s_chField = _T(';');
  25. const TCHAR CISMShellExecutable::s_chEscape = _T('$');
  26. const TCHAR CISMShellExecutable::s_chService = _T('S');
  27. const TCHAR CISMShellExecutable::s_chComputer = _T('C');
  28. /* static */
  29. HICON
  30. CISMShellExecutable::GetShellIcon(
  31. IN LPCTSTR lpszShellExecutable
  32. )
  33. /*++
  34. Routine Description:
  35. Extract icon from the given shell executable
  36. Arguments:
  37. LPCTSTR lpszShellExecutable : Executable name (or shell document)
  38. Return Value:
  39. Handle to the icon or NULL
  40. --*/
  41. {
  42. SHFILEINFO shfi;
  43. ::SHGetFileInfo(
  44. lpszShellExecutable,
  45. 0L,
  46. &shfi,
  47. sizeof(shfi),
  48. SHGFI_ICON | SHGFI_SHELLICONSIZE | SHGFI_SMALLICON
  49. );
  50. //
  51. // Will be NULL if SHGetFileInfo failed
  52. //
  53. return shfi.hIcon;
  54. }
  55. /* static */
  56. HICON
  57. CISMShellExecutable::ExtractIcon(
  58. IN LPCTSTR lpszIconSource,
  59. IN UINT nIconOffset
  60. )
  61. /*++
  62. Routine Description:
  63. Extract icon specified by index from the given file
  64. Arguments:
  65. LPCTSTR lpszIconSource : Source of the icon file
  66. UINT nIconOffset : The 0-based icon index within the file
  67. Return Value:
  68. Handle to the icon or NULL
  69. --*/
  70. {
  71. HICON hIconSmall = NULL;
  72. ::ExtractIconEx(lpszIconSource, nIconOffset, NULL, &hIconSmall, 1);
  73. return hIconSmall;
  74. }
  75. /* static */
  76. LPTSTR
  77. CISMShellExecutable::GetField(
  78. IN LPTSTR pchLine OPTIONAL
  79. )
  80. /*++
  81. Routine Description:
  82. Similar to strtok, this function reads fields from the source string
  83. separated by semi-colons. When one is found, it is changed to a NULL,
  84. and the pointer to the current string is returned. Subsequent calls
  85. to this function may use pchLine as NULL, meaning continue where we
  86. left off. This function differs from strtok, in that only the first
  87. separator character is skipped. Having multiple ones in a row, e.g.
  88. ";;;", would return each field as an empty field, and not just skip
  89. past all of them. Also, quoted fields are returned in their entirety,
  90. and any separator characters in them are not treated as separators.
  91. Arguments:
  92. LPTSTR pchLine : Beginning string pointer or NULL
  93. Return Value:
  94. String pointer to the next field, or NULL if no further fields are
  95. available.
  96. --*/
  97. {
  98. static LPTSTR pchEnd = NULL;
  99. static BOOL fEOL = FALSE;
  100. LPTSTR pch;
  101. //
  102. // Initialize beginning line pointer
  103. //
  104. if (pchLine != NULL)
  105. {
  106. //
  107. // Starting with a new string
  108. //
  109. fEOL = FALSE;
  110. pch = pchLine;
  111. }
  112. else
  113. {
  114. //
  115. // Continue where we left off if there was any
  116. // thing more to read
  117. //
  118. if (fEOL)
  119. {
  120. return NULL;
  121. }
  122. //
  123. // Must have been called at least once with non-NULL
  124. // parameter
  125. //
  126. ASSERT(pchEnd != NULL);
  127. pch = ++pchEnd;
  128. }
  129. pchEnd = pch;
  130. //
  131. // Quotes are handled seperately
  132. //
  133. if (*pchEnd == _T('\"') )
  134. {
  135. //
  136. // Skip ahead to closing quote
  137. //
  138. ++pch;
  139. do
  140. {
  141. ++pchEnd;
  142. }
  143. while (*pchEnd && *pchEnd != _T('\"') );
  144. if (!*pchEnd)
  145. {
  146. fEOL = TRUE;
  147. }
  148. else
  149. {
  150. *(pchEnd++) = _T('\0');
  151. }
  152. return pch;
  153. }
  154. //
  155. // Else look for the field separator
  156. // Allowing for null fields
  157. //
  158. if (*pchEnd != CISMShellExecutable::s_chField)
  159. {
  160. do
  161. {
  162. ++pchEnd;
  163. }
  164. while (*pchEnd && * pchEnd != CISMShellExecutable::s_chField);
  165. }
  166. if (!*pchEnd)
  167. {
  168. fEOL = TRUE;
  169. }
  170. else
  171. {
  172. *pchEnd = _T('\0');
  173. }
  174. return pch;
  175. }
  176. /* static */
  177. DWORD
  178. CISMShellExecutable::ExpandEscapeCodes(
  179. OUT CString & strDest,
  180. IN LPCTSTR lpSrc,
  181. IN LPCTSTR lpszComputer OPTIONAL,
  182. IN LPCTSTR lpszService OPTIONAL
  183. )
  184. /*++
  185. Routine Description:
  186. Expand the escape codes in the string with computer or service
  187. strings. See note at constructor below
  188. Arguments:
  189. CString & strDest : Destination string
  190. LPCTSTR lpSrc : Original source string
  191. LPCTSTR lpszComputer : Computer name
  192. LPCTSTR lpszService : Service name
  193. Return Value:
  194. Error return code
  195. --*/
  196. {
  197. DWORD err = ERROR_SUCCESS;
  198. try
  199. {
  200. strDest.Empty();
  201. while(*lpSrc)
  202. {
  203. if (*lpSrc == CISMShellExecutable::s_chEscape)
  204. {
  205. TCHAR ch = *(++lpSrc);
  206. CharUpper((LPTSTR)ch);
  207. switch(ch)
  208. {
  209. case CISMShellExecutable::s_chComputer:
  210. if (lpszComputer)
  211. {
  212. strDest += PURE_COMPUTER_NAME(lpszComputer);
  213. }
  214. break;
  215. case CISMShellExecutable::s_chService:
  216. if (lpszService)
  217. {
  218. strDest += lpszService;
  219. }
  220. break;
  221. case CISMShellExecutable::s_chEscape:
  222. strDest += CISMShellExecutable::s_chEscape;
  223. break;
  224. case _T('\0'):
  225. default:
  226. //
  227. // Ignored
  228. //
  229. break;
  230. }
  231. }
  232. else
  233. {
  234. strDest += *lpSrc;
  235. }
  236. ++lpSrc;
  237. }
  238. }
  239. catch(CMemoryException * e)
  240. {
  241. err = ERROR_NOT_ENOUGH_MEMORY;
  242. e->Delete();
  243. }
  244. return err;
  245. }
  246. //
  247. // Helper macros to parse description line
  248. //
  249. #define PARSE_STR_FIELD(src, dest) \
  250. { \
  251. LPTSTR lp = CISMShellExecutable::GetField((LPTSTR)src);\
  252. if (lp != NULL) \
  253. { \
  254. dest = lp; \
  255. TRACEEOLID("Field is " << dest); \
  256. } \
  257. }
  258. #define PARSE_INT_FIELD(src, dest, def) \
  259. { \
  260. LPTSTR lp = CISMShellExecutable::GetField((LPTSTR)src);\
  261. dest = (lp != NULL ? ::_ttoi(lp) : def); \
  262. TRACEEOLID("Numeric field is " << dest); \
  263. }
  264. //
  265. // Helper macro for conditional expansion
  266. //
  267. #define SAFE_EXPAND(err, dest, src, server, service) \
  268. { \
  269. err = ExpandEscapeCodes(dest, src, server, service); \
  270. if (err != ERROR_SUCCESS) \
  271. { \
  272. break; \
  273. } \
  274. }
  275. CISMShellExecutable::CISMShellExecutable(
  276. IN LPCTSTR lpszRegistryValue,
  277. IN int nBitmapID,
  278. IN int nCmd
  279. )
  280. /*++
  281. Routine Description:
  282. Constructor. Read the description from the registry and initialize the
  283. fields.
  284. Arguments:
  285. LPCTSTR lpszRegistryValue : Registry value
  286. int nButton : Toolbar ID
  287. Return Value:
  288. N/A
  289. Notes:
  290. Fields
  291. =======
  292. The registry description consists of fields separated by semi-colons
  293. The fields are as follows:
  294. 1) executable name : Executable name or shell document
  295. 2) tool tips text : Text to appear in the toolbar
  296. 3) selection arguments : Command line options if there is a selection
  297. 4) non-selection arguments : Command line options if nothing is selected
  298. 5) working directory : CWD if not set
  299. 6) show in toolbar : If zero, not shown in toolbar, anything else
  300. will show in toolbar.
  301. CAVEAT: Empty is translated to mean 'yes'
  302. 7) icon path : Where to get the toolbar icon (if empty,
  303. the executable name will be used
  304. 8) icon index : If the above is specified, the icon index
  305. 0 otherwise
  306. 1) Is mandatory, all other fields are optional.
  307. Escape Codes
  308. ============
  309. Any field may contain escape codes that are filled in at runtime. These
  310. escape codes are as follows:
  311. $S : Replaced with service name (if selected -- skipped otherwise)
  312. $C : Replaced with computer name (if selected -- skipped otherwise)
  313. $$ : Replaced with a single $
  314. --*/
  315. : m_strCommand(),
  316. m_strParms(),
  317. m_strNoSelectionParms(),
  318. m_strToolTipsText(),
  319. m_hIcon(NULL),
  320. m_pBitmap(NULL),
  321. m_pmmcButton(NULL),
  322. m_nBitmapID(nBitmapID),
  323. m_nCmd(nCmd),
  324. m_fShowInToolbar(TRUE)
  325. {
  326. try
  327. {
  328. CString strIconFile;
  329. UINT nIconOffset;
  330. PARSE_STR_FIELD(lpszRegistryValue, m_strCommand);
  331. PARSE_STR_FIELD(NULL, m_strToolTipsText);
  332. PARSE_STR_FIELD(NULL, m_strParms);
  333. PARSE_STR_FIELD(NULL, m_strNoSelectionParms);
  334. PARSE_STR_FIELD(NULL, m_strDefaultDirectory);
  335. PARSE_INT_FIELD(NULL, m_fShowInToolbar, TRUE);
  336. PARSE_STR_FIELD(NULL, strIconFile);
  337. PARSE_INT_FIELD(NULL, nIconOffset, 0);
  338. //
  339. // If no tooltips text, give it the executable name
  340. //
  341. if (m_strToolTipsText.IsEmpty())
  342. {
  343. m_strToolTipsText = m_strCommand;
  344. }
  345. if (!m_strCommand.IsEmpty() && m_fShowInToolbar)
  346. {
  347. //
  348. // If no specific icon source file is specified,
  349. // just use what the shell would use.
  350. //
  351. if (strIconFile.IsEmpty())
  352. {
  353. m_hIcon = GetShellIcon(m_strCommand);
  354. }
  355. else
  356. {
  357. m_hIcon = ExtractIcon(strIconFile, nIconOffset);
  358. }
  359. //
  360. // Provide the ? icon if nothing is proviced in the binary
  361. //
  362. if (m_hIcon == NULL)
  363. {
  364. m_pBitmap = new CBitmap;
  365. if (!m_pBitmap->LoadMappedBitmap(IDB_UNKNOWN))
  366. {
  367. TRACEEOLID("Can't load unknown toolbar button bitmap");
  368. delete m_pBitmap;
  369. m_pBitmap = NULL;
  370. }
  371. }
  372. else
  373. {
  374. ExtractBitmapFromIcon(m_hIcon, m_pBitmap);
  375. }
  376. //
  377. // Now build the MMC button structure
  378. //
  379. if (HasBitmap())
  380. {
  381. m_pmmcButton = (MMCBUTTON *)AllocMem(sizeof(MMCBUTTON));
  382. if (m_pmmcButton != NULL)
  383. {
  384. m_pmmcButton->nBitmap = m_nBitmapID;
  385. m_pmmcButton->idCommand = m_nCmd;
  386. m_pmmcButton->fsState = TBSTATE_ENABLED;
  387. m_pmmcButton->fsType = TBSTYLE_BUTTON;
  388. m_pmmcButton->lpButtonText = _T(" ");
  389. m_pmmcButton->lpTooltipText = (LPTSTR)(LPCTSTR)m_strToolTipsText;
  390. }
  391. }
  392. }
  393. }
  394. catch(CMemoryException * e)
  395. {
  396. TRACEEOLID("!!!Exception initializing add-on-tool");
  397. e->ReportError();
  398. e->Delete();
  399. }
  400. }
  401. CISMShellExecutable::~CISMShellExecutable()
  402. /*++
  403. Routine Description:
  404. Destructor
  405. Arguments:
  406. N/A
  407. Return Value:
  408. N/A
  409. --*/
  410. {
  411. if (m_pBitmap)
  412. {
  413. //
  414. // I don't think I own this...
  415. //
  416. m_pBitmap->DeleteObject();
  417. delete m_pBitmap;
  418. }
  419. if (m_pmmcButton)
  420. {
  421. FreeMem(m_pmmcButton);
  422. }
  423. }
  424. void
  425. CISMShellExecutable::ExtractBitmapFromIcon(
  426. IN HICON hIcon,
  427. OUT CBitmap *& pBitmap
  428. )
  429. /*++
  430. Routine Description:
  431. Extract bitmap information from icon
  432. Arguments:
  433. HICON hIcon : Input icon handle
  434. CBitmap *& pBitmap : Returns bitmap info
  435. Return Value:
  436. None
  437. --*/
  438. {
  439. try
  440. {
  441. pBitmap = new CBitmap();
  442. //
  443. // Get bitmap info from icon
  444. //
  445. ICONINFO ii;
  446. ASSERT(hIcon != NULL);
  447. ::GetIconInfo(hIcon, &ii);
  448. BITMAP bm;
  449. //
  450. // Determine size of the image
  451. //
  452. ::GetObject(ii.hbmColor, sizeof(bm), &bm);
  453. //
  454. // Now create a new bitmap by drawing the icon on
  455. // a button face background colour
  456. //
  457. CDC dcMem;
  458. dcMem.CreateCompatibleDC(NULL);
  459. pBitmap->CreateBitmapIndirect(&bm);
  460. CBitmap * pOld = dcMem.SelectObject(pBitmap);
  461. COLORREF crOld = dcMem.SetBkColor(::GetSysColor(COLOR_BTNFACE));
  462. CRect rc(0, 0, bm.bmWidth, bm.bmHeight);
  463. CBrush br(::GetSysColor(COLOR_BTNFACE));
  464. dcMem.FillRect(&rc, &br);
  465. ::DrawIconEx(
  466. dcMem.m_hDC,
  467. 0,
  468. 0,
  469. hIcon,
  470. bm.bmWidth,
  471. bm.bmHeight,
  472. 0,
  473. NULL,
  474. DI_NORMAL
  475. );
  476. dcMem.SetBkColor(crOld);
  477. dcMem.SelectObject(pOld);
  478. }
  479. catch(CException * e)
  480. {
  481. TRACEEOLID("!!! Exception adding icon based button");
  482. e->ReportError();
  483. e->Delete();
  484. }
  485. }
  486. LPCTSTR
  487. CISMShellExecutable::GetToolTipsText(
  488. CString & str,
  489. IN LPCTSTR lpszServer OPTIONAL,
  490. IN LPCTSTR lpszService OPTIONAL
  491. )
  492. /*++
  493. Routine Description:
  494. Get the tooltips text. Optionally perform escape code
  495. expansion
  496. Arguments:
  497. LPCTSTR lpstrServer : Currently selected server (or NULL)
  498. LPCTSTR lpstrService : Currently selected service (or NULL)
  499. Return Value:
  500. Pointer to string
  501. --*/
  502. {
  503. ExpandEscapeCodes(str, m_strToolTipsText, lpszServer, lpszService);
  504. return (LPCTSTR)str;
  505. }
  506. DWORD
  507. CISMShellExecutable::Execute(
  508. IN LPCTSTR lpszServer OPTIONAL,
  509. IN LPCTSTR lpszService OPTIONAL
  510. )
  511. /*++
  512. Routine Description:
  513. Execute the current module
  514. Arguments:
  515. LPCTSTR lpszServer : Currently selected server (or NULL)
  516. LPCTSTR lpszService : Currently selected service (or NULL)
  517. Return Value:
  518. Error return code
  519. --*/
  520. {
  521. DWORD err = ERROR_SUCCESS;
  522. CString strCommand;
  523. CString strParms;
  524. CString strDefaultDirectory;
  525. do
  526. {
  527. //
  528. // Expand escape codes as appropriate
  529. //
  530. SAFE_EXPAND(err, strCommand, m_strCommand, lpszServer, lpszService);
  531. SAFE_EXPAND(err, strParms, lpszServer
  532. ? m_strParms : m_strNoSelectionParms, lpszServer, lpszService);
  533. SAFE_EXPAND(err, strDefaultDirectory, m_strDefaultDirectory,
  534. lpszServer, lpszService);
  535. if (::ShellExecute(
  536. NULL,
  537. _T("open"),
  538. strCommand,
  539. strParms,
  540. strDefaultDirectory,
  541. SW_SHOW
  542. ) <= (HINSTANCE)32)
  543. {
  544. err = ::GetLastError();
  545. }
  546. }
  547. while(FALSE);
  548. return err;
  549. }
  550. /* OBSOLETE
  551. //
  552. // Add Machine Page Procedure Name
  553. //
  554. #define ADDMACHINEPAGE_PROC "ISMAddMachinePages"
  555. BOOL
  556. AddISMPage(
  557. IN HPROPSHEETPAGE hPage,
  558. IN LPARAM lParam
  559. )
  560. /*++
  561. Routine Description:
  562. Callback function to be used for ISM machine property sheet
  563. extention modules to add pages to the property sheet.
  564. Arguments:
  565. HPROPSHEETPAGE hPage : Handle to page to be added
  566. LPARAM lParam : LParam given to the extention module.
  567. Privately, this is a cast to the property
  568. sheet structure.
  569. Return Value:
  570. TRUE for success, FALSE for failure. In case of failure, GetLastError
  571. will reveal the reason.
  572. --/
  573. {
  574. //
  575. // The extention module has been given the propsheet
  576. // cunningly disguised as an LPARAM. If they've messed
  577. // with the lparam, this will propably crash.
  578. //
  579. PROPSHEETHEADER * ppsh = (PROPSHEETHEADER *)lParam;
  580. HPROPSHEETPAGE * pOld = ppsh->phpage;
  581. ppsh->phpage = (HPROPSHEETPAGE *)AllocMem(ppsh->nPages + 1);
  582. if (ppsh->phpage == NULL)
  583. {
  584. //
  585. // Memory failure
  586. //
  587. ppsh->nPages = 0;
  588. TRACEEOLID("Memory failure building machine property sheet");
  589. ::SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  590. return FALSE;
  591. }
  592. //
  593. // Save the old handles
  594. //
  595. if (pOld)
  596. {
  597. for (UINT i = 0; i < ppsh->nPages; ++i)
  598. {
  599. ppsh->phpage[i] = pOld[i];
  600. }
  601. FreeMem(pOld);
  602. }
  603. //
  604. // Add the new page
  605. //
  606. ppsh->phpage[ppsh->nPages++] = hPage;
  607. TRACEEOLID("Succesfully built machine property sheet with "
  608. << ppsh->nPages << " pages.");
  609. return TRUE;
  610. }
  611. CISMMachinePageExt::CISMMachinePageExt(
  612. IN LPCTSTR lpstrDLLName
  613. )
  614. /*++
  615. Routine Description:
  616. Constructor for ISM Machine property sheet extender.
  617. Arguments:
  618. LPCTSTR lpstrDLLName : Name of the DLL.
  619. Return Value:
  620. N/A
  621. Notes:
  622. This will not attempt to load and resolve the DLL
  623. --/
  624. : m_strDLLName(lpstrDLLName),
  625. m_hDLL(NULL),
  626. m_lpfn(NULL)
  627. {
  628. }
  629. CISMMachinePageExt::~CISMMachinePageExt()
  630. /*++
  631. Routine Description:
  632. Destructor
  633. Arguments:
  634. N/A
  635. Return Value:
  636. N/A
  637. Notes:
  638. This will unload the DLL if it is still loaded, but this should
  639. have been done with the UnLoad method beforehand.
  640. --/
  641. {
  642. //
  643. // Should have been unloaded by now
  644. //
  645. ASSERT(m_hDLL == NULL);
  646. if (m_hDLL)
  647. {
  648. VERIFY(UnLoad());
  649. }
  650. }
  651. //
  652. // Prototype
  653. //
  654. DWORD
  655. ISMAddMachinePages(
  656. IN LPCTSTR lpstrMachineName,
  657. IN LPFNADDPROPSHEETPAGE lpfnAddPage,
  658. IN LPARAM lParam,
  659. IN HINSTANCE hInstance
  660. );
  661. DWORD
  662. CISMMachinePageExt::Load()
  663. /*++
  664. Routine Description:
  665. Load the DLL, and resolve the exposed interface
  666. Arguments:
  667. None
  668. Return Value:
  669. Error return code;
  670. --/
  671. {
  672. #ifdef _COMSTATIC
  673. m_lpfn = &ISMAddMachinePages;
  674. return ERROR_SUCCESS;
  675. #else
  676. //
  677. // Make sure it's not already loaded
  678. //
  679. ASSERT(m_hDLL == NULL);
  680. m_hDLL = ::AfxLoadLibrary(m_strDLLName);
  681. if (m_hDLL)
  682. {
  683. m_lpfn = (LPFNADDMACHINEPAGE)::GetProcAddress(
  684. m_hDLL, ADDMACHINEPAGE_PROC);
  685. if (m_lpfn)
  686. {
  687. return ERROR_SUCCESS;
  688. }
  689. }
  690. return ::GetLastError();
  691. #endif // _COMSTATIC
  692. }
  693. BOOL
  694. CISMMachinePageExt::UnLoad()
  695. /*++
  696. Routine Description:
  697. Unload the extention DLL
  698. Arguments:
  699. None
  700. Return Value:
  701. TRUE for success, FALSE for failure
  702. --/
  703. {
  704. #ifdef _COMSTATIC
  705. return TRUE;
  706. #else
  707. BOOL fResult = FALSE;
  708. if (m_hDLL)
  709. {
  710. fResult = ::AfxFreeLibrary(m_hDLL);
  711. m_hDLL = NULL;
  712. m_lpfn = NULL;
  713. }
  714. return fResult;
  715. #endif // _COMSTATIC
  716. }
  717. */