CMDEXT.CPP

/////////////////////////////////////////////////////////////////////////////// 
//
// File Name
// CMDEXT.CPP
//
// Description
// Implementation of interface objects
//
// IExchExt interface methods:
// MyExchExt::QueryInterface()
// MyExchExt::AddRef()
// MyExchExt::Release()
//
// MyExchExt::Install()
//
// IExchExtCommands interface methods:
// MyExchExtCommands::QueryInterface()
// MyExchExtCommands::AddRef()
// MyExchExtCommands::Release()
//
// MyExchExtCommands::InstallCommands()
// MyExchExtCommands::DoCommand()
// MyExchExtCommands::InitMenu()
// MyExchExtCommands::Help()
// MyExchExtCommands::QueryHelpText()
// MyExchExtCommands::QueryButtonInfo()
// MyExchExtCommands::ResetToolbar()
//
// MyExchExtCommands::SetContext()
// MyExchExtCommands::GetCmdID()
//
// IExchExtUserEvents interface methods:
// MyExchExtUserEvents::QueryInterface()
// MyExchExtUserEvents::AddRef()
// MyExchExtUserEvents::Release()
//
// MyExchExtUserEvents::OnSelectionChange()
// MyExchExtUserEvents::OnObjectChange()
//
// MyExchExtUserEvents::SetContext()
// MyExchExtUserEvents::SetIExchExt()
//
// Author
// Gary Peluso
//
// Revision: 1.00
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1992-1995 Microsoft Corporation. All rights reserved.
//
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMAPIFolder

#include "CMDEXT.H"

///////////////////////////////////////////////////////////////////////////////
// data
static HINSTANCE ghInstDLL = NULL; // instance handle of DLL

///////////////////////////////////////////////////////////////////////////////
// FUNCTION: DLLMain()
//
// Purpose
// Do initilization processesing
//
// Return Value
// TRUE - DLL successfully loads and LoadLibrary will succeed.
// FALSE - will cause an Exchange error message saying it cannot locate
// the extension DLL.
//
// Comments
// We only need to get a copy of the DLL's HINSTANCE
//
BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{
if (DLL_PROCESS_ATTACH == fdwReason)
{
ghInstDLL = hinstDLL;
}
return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// FUNCTION: ExchEntryPoint
//
// Parameters - none
//
// Purpose
// The entry point which Exchange calls.
//
// Return Value
// Pointer to Exchange Extension Object
//
// Comments
// This is called for each context entry. Create a new MyExchExt object
// every time so each context will get its own MyExchExt interface.
//
LPEXCHEXT CALLBACK ExchEntryPoint(void)
{
return new MyExchExt;
}


///////////////////////////////////////////////////////////////////////////////
// MyExchExt::MyExchExt()
//
// Parameters - none
//
// Purpose
// Constructor. Initialize members and create supporting interface objects
//
// Comments
// Each context of Exchange gets its own set of interface objects.
// Furthermore, interface objects per context are kept track of by Exchange
// and the interface methods are called in the proper context.
//
MyExchExt::MyExchExt ()
{
m_cRef = 1;
m_context = 0;

m_pExchExtCommands = new MyExchExtCommands;
m_pExchExtUserEvents = new MyExchExtUserEvents;

// in MyExchExtUserEvents methods I need a reference to MyExchExt
m_pExchExtUserEvents->SetIExchExt(this);

}


///////////////////////////////////////////////////////////////////////////////
// IExchExt virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
// MyExchExt::QueryInterface()
//
// Parameters
// riid -- Interface ID.
// ppvObj -- address of interface object pointer.
//
// Purpose
// Called by Exchage to request for interfaces
//
// Return Value
// S_OK -- interface is supported and returned in ppvObj pointer
// E_NOINTERFACE -- interface is not supported and ppvObj is NULL
//
// Comments
// Exchange client calls QueryInterface for each object. Only
// Need to support objects that apply to the extension. QueryInterface
// is called onces for each IID for each context. We support two
// contexts in this example so QueryInterface is called twice for
// each IID.
//
STDMETHODIMP MyExchExt::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
HRESULT hr = S_OK;

*ppvObj = NULL;

if ( (IID_IUnknown == riid) || (IID_IExchExt == riid) )
{
*ppvObj = (LPUNKNOWN)this;
}
else if ( IID_IExchExtCommands == riid)
{
*ppvObj = (LPUNKNOWN)m_pExchExtCommands;
m_pExchExtCommands->SetContext(m_context);
}
else if ( IID_IExchExtUserEvents == riid)
{
*ppvObj = (LPUNKNOWN)m_pExchExtUserEvents;
m_pExchExtUserEvents->SetContext(m_context);
}
else
hr = E_NOINTERFACE;

if (NULL != *ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();

return hr;
}



///////////////////////////////////////////////////////////////////////////////
// MyExchExt::Install()
//
// Parameters
// peecb -- pointer to Exchange Extension callback function
// eecontext -- context code at time of being called.
// ulFlags -- flag to say if install is for modal or not
//
// Purpose
// Called once for each new contexted that is entered. Proper version
// number is checked here.
//
// Return Value
// S_OK -- object supported in the requested context
// S_FALSE -- object is not supported in teh requested context
//
// Comments
//
STDMETHODIMP MyExchExt::Install (LPEXCHEXTCALLBACK pmecb,
ULONG mecontext, ULONG ulFlags)
{
ULONG ulBuildVersion;
HRESULT hr;

m_context = mecontext;

// make sure this is the right version
pmecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion & EECBGV_BUILDVERSION_MAJOR_MASK))
return S_FALSE;

switch (mecontext)
{
case EECONTEXT_VIEWER:
case EECONTEXT_SEARCHVIEWER:
hr = S_OK;
break;
default:
hr = S_FALSE;
break;
}
return hr;
}

///////////////////////////////////////////////////////////////////////////////
// IExchExtCommands virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::QueryInterface()
//
// Parameters
// riid -- Interface ID.
// ppvObj -- address of interface object pointer.
//
// Purpose
// Exchange Client does not call IExchExtCommands::QueryInterface().
// So return nothing.
//
// Return Value - none
//

STDMETHODIMP MyExchExtCommands::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
*ppvObj = NULL;
if ( (riid == IID_IExchExtCommands) || (riid == IID_IUnknown) )
{
*ppvObj = (LPVOID)this;
// Increase usage count of this object
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::InstallCommands()
//
// Parameters
// pmecb -- Exchange Callback Interface
// hWnd -- window handle to main window of context
// hMenu -- menu handle to main menu of context
// lptbeArray -- array of toolbar button entries
// ctbe -- count of button entries in array
// ulFlags -- reserved
//
// Purpose
// This function is called when commands are installed for each context
// the extension services.
//
// Return Value
// S_FALSE means the commands have been handled.
//
// Comments
// The hWnd and hMenu are in context. If the context is for the SENDNOTE
// dialog, then the hWnd is the window handle to the dialog and the hMenu is
// the main menu of the dialog.
//
// Call ResetToolbar so that Exchange will show it's toolbar
//


STDMETHODIMP MyExchExtCommands::InstallCommands(LPEXCHEXTCALLBACK pmecb,
HWND hWnd, HMENU hMenu,
UINT FAR * pcmdidBase, LPTBENTRY lptbeArray,
UINT ctbe, ULONG ulFlags)
{
HRESULT hr = S_FALSE;
HMENU hMenuTools;

if ((EECONTEXT_SEARCHVIEWER == m_context) || (EECONTEXT_VIEWER == m_context))
{


// ------- install the new menu command, append to end of Tools menu. ----
m_hWnd = hWnd;

pmecb->GetMenuPos(EECMDID_ToolsCustomizeToolbar, &hMenuTools, NULL, NULL, 0);


// add a menu separator
AppendMenu(hMenuTools, MF_SEPARATOR, 0, NULL);

// add our extension command
AppendMenu(hMenuTools,
MF_BYPOSITION | MF_STRING,
*pcmdidBase,
"Folder Stats");

m_cmdid = *pcmdidBase;

(*pcmdidBase)++;

}

if (EECONTEXT_VIEWER == m_context)
{

// -------------- install the new toolbar button --------------

// walk through the toolbars and find the standard toolbar

int tbindx;
HWND hwndToolbar = NULL;
for (tbindx = ctbe-1; (int) tbindx > -1; --tbindx)
{
if (EETBID_STANDARD == lptbeArray[tbindx].tbid)
{
hwndToolbar = lptbeArray[tbindx].hwnd;
m_itbb = lptbeArray[tbindx].itbbBase++;
break;
}
}

// add out button's bitmap to the toolbar's set of buttons
if (hwndToolbar)
{
TBADDBITMAP tbab;

tbab.hInst = ghInstDLL;
tbab.nID = IDB_EXTBTN;
m_itbm = SendMessage(hwndToolbar, TB_ADDBITMAP, 1, (LPARAM)&tbab);
}


}

return hr;

}


///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::DoCommand()
//
// Parameters
// pmecb -- pointer to Exchange Callback Interface
//
// Purpose
// This method is called by Exchange for each WM_COMMAND is sent to the
// window in context.
//
// Return Value
// S_OK if command is handled
// S_FALSE if command is not handled
//
// Comments
// Use this function to either respond to the command item (menu or toolbar)
// added or modify an existing command in Exchange. Return S_OK to let
// Exchange know the command was handled. Return S_OK on commands you are
// taking over from Exchange. Return S_FALSE to let Exchange know you want
// it to carry out its command, even if you modify its action.
//

STDMETHODIMP MyExchExtCommands::DoCommand(LPEXCHEXTCALLBACK pmecb, UINT cmdid)
{
static char szBuffer[200];
ULONG uNumSelected = 0;
ULONG ulSubFolders = 0;
ULONG ulReadMsgs = 0;
ULONG ulUnReadMsgs = 0;
LPMDB lpMDB = NULL;
LPMAPIFOLDER lpFolder = NULL;
LPMAPISESSION lpSession = NULL;
LPADRBOOK lpAddrBook = NULL;
ULONG cbeid;
LPENTRYID lpeid = NULL;
ULONG ulType;
char szMsgClass[50];
ULONG cbMsgClass;
// ULONG ulMsgFlags;

HRESULT hr = S_FALSE; // assume it is not our command

if (m_cmdid != cmdid)
return hr; // not our command

if (m_context == EECONTEXT_VIEWER)
{

cbMsgClass = 49;
szMsgClass[0] = '\0';


hr = pmecb->GetSession(&lpSession, &lpAddrBook);
if (FAILED(hr))
{
hr = S_OK; // we still handled the command
goto error_return;
}

hr = pmecb->GetSelectionItem(0, &cbeid, &lpeid, &ulType, NULL,
0, NULL, 0);
if (FAILED(hr))
{
hr = S_OK; // we still handled the command
goto error_return;
}

if (ulType != MAPI_FOLDER) // should never get this
{
hr = S_OK; // we still handled the command
goto error_return;
}


hr = lpSession->OpenEntry(cbeid, lpeid, NULL, 0, &ulType,
(LPUNKNOWN FAR *)&lpFolder);
if (FAILED(hr))
{
hr = S_OK; // we still handled the command
goto error_return;
}


if (NULL == lpFolder)
{
hr = S_OK; // we still handled the command
goto error_return; // no folder, not going to continue
}

GetFolderStats(lpFolder, &ulSubFolders, &ulReadMsgs, &ulUnReadMsgs);
wsprintf(szBuffer, "Number of subfolders: %ld\n"
"Number of read messages: %ld\n"
"Number of unread messages: %ld",
ulSubFolders, ulReadMsgs, ulUnReadMsgs);

MessageBox(m_hWnd, szBuffer, "Sample Extension - Folder Stats", MB_OK);

hr = S_OK;

}

// must use a different technique in Find window
if (m_context == EECONTEXT_SEARCHVIEWER)
{


pmecb->GetObject(&lpMDB, (LPMAPIPROP FAR *)&lpFolder);

if (lpFolder == NULL)
{
hr = S_OK; // we still handled the command
goto error_return; // no folder, not going to continue
}

GetFolderStats(lpFolder, &ulSubFolders, &ulReadMsgs, &ulUnReadMsgs);
wsprintf(szBuffer, "Number of subfolders: %d\n"
"Number of read messages: %d\n"
"Number of unread messages: %d",
ulSubFolders, ulReadMsgs, ulUnReadMsgs);


MessageBox(m_hWnd, szBuffer, "Sample Extension - Folder Stats", MB_OK);

hr = S_OK;

}

error_return:

MAPIFreeBuffer(lpeid);

if (NULL != lpSession)
lpSession->Release();

if (NULL != lpAddrBook)
lpAddrBook->Release();

if (NULL != lpMDB)
lpMDB->Release();

if (NULL != lpFolder)
lpFolder->Release();

return hr;
}


///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::InitMenu()
//
// Parameters
// pmecb -- pointer to Exchange Callback Interface
//
// Purpose
// This method is called by Exchange when the menu of context is about to
// be activated. See WM_INITMENU in the Windows API Reference for more
// information.
//
// Return Value - none
//

STDMETHODIMP_(VOID) MyExchExtCommands::InitMenu(LPEXCHEXTCALLBACK pmecb)
{
// do nothing

}

///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::Help()
//
// Parameters
// pmecb -- pointer to Exchange Callback Interface
// cmdid -- command id
//
// Purpose
// Respond when user presses F1 while custom menu item is selected.
//
// Return Value
// S_OK -- recognized the command and provided help
// S_FALSE -- not our command and we didn't provide help
//

STDMETHODIMP MyExchExtCommands::Help(LPEXCHEXTCALLBACK pmecb, UINT cmdid)
{
HRESULT hr;


if (cmdid == m_cmdid)
{

MessageBox(m_hWnd, "Called through IExchExtCommands::Help()\n"
"Contained in cmdext32.dll\n\n"
"Copyright (c) 1995 Microsoft Corporation.\n"
"All rights reserved.",
"About Folder Stats Sample Extension", MB_OK);

hr = S_OK;
}
else
hr = S_FALSE;

return hr;
}


///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::QueryHelpText()
//
// Parameters
// cmdid -- command id corresponding to menu item activated
// ulFlags -- identifies either EECQHT_STATUS or EECQHT_TOOLTIP
// psz -- pointer to buffer to be populated with text to display
// cch -- count of characters available in psz buffer
//
// Purpose
// Exchange calls this function each time it requires to update the status
// bar text or if it is to display a tooltip on the toolbar.
//
// Return Value
// S_OK to indicate our command was handled
// S_FALSE to tell Exchange it can continue with its function
//

STDMETHODIMP MyExchExtCommands::QueryHelpText(UINT cmdid, ULONG ulFlags,
LPTSTR psz, UINT cch)
{

HRESULT hr;

if (cmdid == m_cmdid)
{
if (ulFlags == EECQHT_STATUS)
lstrcpyn(psz, "Display Stats on Current Folder", cch);

if (ulFlags == EECQHT_TOOLTIP)
lstrcpyn(psz, "Folder Stats", cch);

hr = S_OK;

}
else
hr = S_FALSE;

return hr;
}

///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::QueryButtonInfo()
//
// Parameters
// tbid -- toolbar identifier
// itbb -- toolbar button index
// ptbb -- pointer to toolbar button structure -- see TBBUTTON structure
// lpsz -- point to string describing button
// cch -- maximum size of lpsz buffer
// ulFlags -- EXCHEXT_UNICODE may be specified
//
// Purpose
// For Exchange to find out about toolbar button information.
//
// Return Value
// S_FALSE - not our button
// S_OK - we filled information about our button
//
// Comments
// Called for every button installed for toolbars when IExchExtCommands
// is installed for each context. The lpsz text is used when the Customize
// Toolbar dialog is displayed. The text will be displayed next to the
// button.
//

STDMETHODIMP MyExchExtCommands::QueryButtonInfo (ULONG tbid, UINT itbb,
LPTBBUTTON ptbb, LPTSTR lpsz, UINT cch,
ULONG ulFlags)
{
HRESULT hr = S_FALSE;

if (m_itbb == itbb)
{
ptbb->iBitmap = m_itbm; // see InstallCommands in this source file
ptbb->idCommand = m_cmdid;
ptbb->fsState = TBSTATE_ENABLED;
ptbb->fsStyle = TBSTYLE_BUTTON;
ptbb->dwData = 0;
ptbb->iString = -1;
lstrcpyn(lpsz, "Folder Stats Toolbar Button", cch);

hr = S_OK;
}
return hr;
}


///////////////////////////////////////////////////////////////////////////////
// MyExchExtCommands::ResetToolbar()
//
// Parameters
// tbid
// ulFlags
//
// Purpose
//
// Return Value S_OK always
//
STDMETHODIMP MyExchExtCommands::ResetToolbar(ULONG tbid, ULONG ulFlags)
{
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// IExchExtUserEvents virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
// MyExchExtUserEvents::QueryInterface()
//
// Parameters
// riid -- Interface ID.
// ppvObj -- address of interface object pointer.
//
// Purpose
// Exchange Client does not call IExchExtUserEvents::QueryInterface().
// So return nothing.
//
// Return Value - none
//

STDMETHODIMP MyExchExtUserEvents::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
*ppvObj = NULL;
if (( riid == IID_IExchExtUserEvents) || (riid == IID_IUnknown) )
{
*ppvObj = (LPVOID)this;
// Increase usage count of this object
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
// MyExchExtUserEvents::OnSelectionChange()
//
// Parameters
// pmecb -- pointer to Exchange Callback Object
//
//
// Purpose
// This function is called when the selection in the UI is changed.
//
// Return Value - none
//
// Comments
// OnSelectionChange is called whenever the selection changes either within
// a pane or is changed between panes.
//

STDMETHODIMP_(VOID) MyExchExtUserEvents::OnSelectionChange(LPEXCHEXTCALLBACK pmecb)
{
static ULONG cbeid;
static LPENTRYID lpeid = NULL;
static ULONG ulType;
static char szMsgClass[50];
ULONG cbMsgClass;
ULONG ulMsgFlags;
HMENU hMenu;
HRESULT hr;
UINT cmdid;
MyExchExtCommands * pExchExtCommands = NULL;

// do not care about enabling or disabling menu item if in the search viewer
if (m_context == EECONTEXT_SEARCHVIEWER)
return;

cbMsgClass = 49;
szMsgClass[0] = '\0';


hr = pmecb->GetSelectionItem(0, &cbeid, &lpeid, &ulType, szMsgClass,
cbMsgClass, &ulMsgFlags, 0);

if (FAILED(hr))
{
goto error_return;
}


pmecb->GetMenu(&hMenu);

hr = m_pExchExt->QueryInterface(IID_IExchExtCommands, (LPVOID *)&pExchExtCommands);
if (FAILED(hr))
{
goto error_return;
}

cmdid = pExchExtCommands->GetCmdID();
pExchExtCommands->Release();
pExchExtCommands = NULL;


// enable or disable the command depending if the selected object is
// a folder or a "non-folder" Enable if object is a folder.
if (ulType == MAPI_FOLDER)
{
EnableMenuItem(hMenu, cmdid, MF_BYCOMMAND | MF_ENABLED);
}
else
{
EnableMenuItem(hMenu, cmdid, MF_BYCOMMAND | MF_GRAYED);
}


error_return:

if (lpeid != NULL)
MAPIFreeBuffer(lpeid);

}


///////////////////////////////////////////////////////////////////////////////
// MyExchExtUserEvents::OnObjectChange()
//
// Parameters
// pmecb -- pointer to Exchange Callback Object
//
//
// Purpose
// This function is called when the selection in the UI is to a different
// of object on the left pane.
//
// Return Value - none
//
// Comments
// OnObjectChange is called whenever the selection is changed between
// objects in the left pane only. Change in selection between folders,
// subfolders or container object in the left pane will be reflected with a
// call to OnObjectChange. Change in selection between objects (messages,
// subfolders) in the right pane will not call OnObjectChange, only
// OnSelectionChange.
//

STDMETHODIMP_(VOID) MyExchExtUserEvents::OnObjectChange(LPEXCHEXTCALLBACK pmecb)
{
// no need to handle this one
}



///////////////////////////////////////////////////////////////////////////////
// Helper functions to accomplish this extension's task
//


///////////////////////////////////////////////////////////////////////////////
// GetFolderStats()
//
// Parameters
// lpFolder -- pointer to Folder of which to get statistics
// pulFolder -- pointer to buffer to be filled with number of subfolders
// pulReadMsgs -- pointer to buffer to be filled with number of read
// messages
// pulUnReadMsgs -- pointer to buffer to be filled with number of unread
// messages
//
// Purpose
// This function gathers information information about the given message
// folder. It calculates the number of subfolders, read messages, and
// unread messages.
//
// Return Value - none
//
// Comments
// The Find window results folder only contains messages and not subfolders
// so it sets the number of subfolders to zero
//

void GetFolderStats(LPMAPIFOLDER lpFolder, ULONG FAR * pulSubFolders,
ULONG FAR * pulReadMsgs, ULONG FAR * pulUnReadMsgs)
{
HRESULT hr = 0;
LPMAPITABLE lpTable = NULL;
ULONG ulRows = 0;
UINT u;
LPSRowSet lpRows = NULL;

enum { MESSAGE_FLAGS, STATTAGS };

SizedSPropTagArray(STATTAGS, MsgTags) =
{ STATTAGS,
{
PR_MESSAGE_FLAGS // contains read/unread flags
}
};


hr = lpFolder->GetHierarchyTable(0, &lpTable); // get table of subfolders
if (MAPI_E_NO_SUPPORT == hr) // Find window folder doesn't
*pulSubFolders = 0; // support GetHierarchyTable

else if (S_OK != hr) // some other error
goto error_return;

if (lpTable != NULL)
{
lpTable->GetRowCount(0, pulSubFolders);
lpTable->Release();
lpTable =NULL;
}

hr = lpFolder->GetContentsTable(0, &lpTable); // get table of messages
if (S_OK != hr)
goto error_return;

hr = lpTable->SetColumns((LPSPropTagArray)&MsgTags, 0);
if (S_OK != hr)
goto error_return;

lpTable->GetRowCount(0, &ulRows);
hr = lpTable->QueryRows(ulRows, 0, &lpRows);
if (S_OK != hr)
goto error_return;

*pulReadMsgs = 0;
*pulUnReadMsgs = 0;

for (u=0; u<ulRows; u++)
{

if (MSGFLAG_READ & lpRows->aRow[u].lpProps[MESSAGE_FLAGS].Value.ul)
(*pulReadMsgs)++;
else
(*pulUnReadMsgs)++;

}


error_return:

if (NULL != lpTable)
lpTable->Release();

if (NULL != lpRows)
FreeProws(lpRows);

}