///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
// Band.cpp : Implementation of CBand
//
// © JetBrains Inc, 2005
// Written by (H) Serge Baltic
#include "StdAfx.h"
#include "Band.h"
// Initialize static variables
CString CBand::m_sWindowClassName = CBand::GetWindowClassName();
UINT CBand::m_nUpdateControlsMessage = RegisterWindowMessage(_T("WM_JetIe_") + CJetIe::LoadStringT(IDS_PLUGIN_NAME) + _T("_BandUpdateControls"));
#define CHEVRON_WIDTH 0x10
// CBand
CBand::CBand()
{
m_dwBandId = NULL;
m_dwViewMode = DBIF_VIEWMODE_NORMAL;
m_hwndParent = NULL;
m_hwndTopmostParent = NULL;
m_bMouseCaptured = false;
m_nChevronState = csNormal;
ASSERT(!(m_guidToolbar.Data1 = 0));
m_oActionManager = CJetIe::GetActionManager();
m_dwLastUpdateControls = GetTickCount();
// Load the icons
m_iconChevron = LoadIcon(CJetIe::GetModuleInstanceHandle(), MAKEINTRESOURCE(IDI_CHEVRON));
}
CBand::~CBand()
{
ASSERT(m_guidToolbar.Data1 != 0);
}
STDMETHODIMP CBand::SetSite(IUnknown* pUnkSite)
{
IObjectWithSiteImpl::SetSite(pUnkSite);
if(pUnkSite == NULL)
{
m_oBrowser = NULL;
return S_OK; // We're detaching, not attaching
}
try
{
// To retrieve the top-level IWebBrowser2 reference, get IServiceProvider from the client site and perform a QueryService for IID_IServiceProvider under the service SID_STopLevelBrowser (defined in Shlguid.h). From this second IServiceProvider, perform a QueryService for IID_IWebBrowser2 in the SID_SWebBrowserApp service.
// The best place to perform this work is in the SetClientSite() method of IOleObject.
IServiceProviderPtr oSiteServiceProvider = (IUnknown*)m_spUnkSite; // Site's Service Provider
IServiceProviderPtr oTopLevelWebBrowserServiceProvider; // Service Provider of the Web browser object for the top-level frame window
oSiteServiceProvider->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, reinterpret_cast(&oTopLevelWebBrowserServiceProvider)); // Get it
oTopLevelWebBrowserServiceProvider->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast(&m_oBrowser)); // Get the Web browser object interface of the top-level frame window
// Get the parent window
IOleWindowPtr oWindow = pUnkSite;
COM_CHECK(oWindow, GetWindow(&m_hwndParent));
// Get the topmost parent window
m_hwndTopmostParent = m_hwndParent;
while(::GetParent(m_hwndTopmostParent) != NULL)
m_hwndTopmostParent = ::GetParent(m_hwndTopmostParent);
// Now create our window (if not created yet)
TRACE(L"Creating the band window.");
if(!::IsWindow(m_hWnd))
{
CRect rc;
::GetClientRect(m_hwndParent, &rc);
//TRACE(L"Assigning window style to %#010X.", WS_CHILD | WS_CLIPSIBLINGS | TBSTYLE_ALTDRAG | TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_TRANSPARENT | TBSTYLE_REGISTERDROP | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | CCS_ADJUSTABLE | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE);
//if(Create(m_hwndParent, &rc, _T("JetIe Rebar Band"), WS_CHILD | WS_CLIPSIBLINGS | TBSTYLE_ALTDRAG | TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_TRANSPARENT | TBSTYLE_REGISTERDROP | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | CCS_ADJUSTABLE | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE) == NULL)
/*HWND hwnd = CreateWindow(TOOLBARCLASSNAME, _T("JetIe Rebar Band"), 0x5600994D, rc.left, rc.top, rc.Width(), rc.Height(), m_hwndParent, NULL, CJetIe::GetModuleInstanceHandle(), NULL);
ASSERT(hwnd != NULL);
SubclassWindow(hwnd);
BOOL bDummy;
OnCreate(WM_CREATE, 0, 0, bDummy);*/
//if(Create(m_hwndParent, &rc, _T("JetIe Rebar Band"), 0x5600994D) == NULL)
if(Create(m_hwndParent, &rc, _T("JetIe Rebar Band"),
WS_CHILDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | /*TBSTYLE_TRANSPARENT | TBSTYLE_LIST | */TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | CCS_TOP/*,
WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR | WS_EX_TOOLWINDOW*/) == NULL)
{
TRACE(L"The band window could not be created.");
return E_FAIL;
}
}
}
COM_CATCH();
return S_OK;
}
STDMETHODIMP CBand::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
{
m_dwBandId = dwBandID;
m_dwViewMode = dwViewMode;
pdbi->dwMask |= DBIM_ACTUAL;
// Calculate the toolbar height
int nIconHeight = GetSystemMetrics(SM_CYSMICON);
int nToolbarHeight = nIconHeight + 3 * 2; // Add the gaps
if(pdbi->dwMask & DBIM_MINSIZE)
{
pdbi->ptMinSize.x = 100;
pdbi->ptMinSize.y = nToolbarHeight;
}
if(pdbi->dwMask & DBIM_MAXSIZE)
{
pdbi->ptMaxSize.x = -1; // Unlimited
pdbi->ptMaxSize.y = -1;
}
if(pdbi->dwMask & DBIM_INTEGRAL)
{
pdbi->ptIntegral.x = 1;
pdbi->ptIntegral.y = nToolbarHeight;
}
if(pdbi->dwMask & DBIM_ACTUAL)
{
pdbi->ptActual.x = 1024; // TODO: calculate what we actually need
pdbi->ptActual.y = nToolbarHeight;
}
if(pdbi->dwMask & DBIM_TITLE)
{
CStringW sTitle;
// Get the toolbar title if it should be shown, otherwise, leave it blank
if((m_xmlControls != NULL) && (m_xmlControls->selectSingleNode(L"@ShowTitle") != NULL) && ((long)m_xmlControls->getAttribute(L"ShowTitle")))
sTitle = (LPCWSTR)(_bstr_t)m_xmlControls->getAttribute(L"Title");
StringCchCopyW(pdbi->wszTitle, 0x10, (LPCWSTR)sTitle); // Copy the title
}
if(pdbi->dwMask & DBIM_MODEFLAGS)
{
pdbi->dwModeFlags = DBIMF_NORMAL;
pdbi->dwModeFlags |= DBIMF_VARIABLEHEIGHT;
}
if(pdbi->dwMask & DBIM_BKCOLOR)
{
//Use the default background color by removing this flag.
pdbi->dwMask &= ~DBIM_BKCOLOR;
}
return S_OK;
}
STDMETHODIMP CBand::HasFocusIO()
{
HWND hwnd = ::GetFocus();
while(hwnd != NULL)
{
if(hwnd == m_hWnd) // We're an (indirect) parent of the focused window
break;
hwnd = ::GetParent(hwnd);
}
if(hwnd == NULL) // Reached the topmost window, did not find ourselves
return S_FALSE;
return S_OK; // We're above the focus
}
STDMETHODIMP CBand::TranslateAcceleratorIO(LPMSG lpMsg)
{
return S_FALSE;
}
STDMETHODIMP CBand::UIActivateIO(BOOL fActivate, LPMSG lpMsg)
{
if(fActivate)
SetFocus();
return S_OK;
}
STDMETHODIMP CBand::GetClassID(CLSID *pClassID)
{
*pClassID = __uuidof(CBand);
return S_OK;
}
STDMETHODIMP CBand::IsDirty()
{
return S_FALSE;
}
STDMETHODIMP CBand::Load(LPSTREAM)
{
return S_OK;
}
STDMETHODIMP CBand::Save(LPSTREAM, BOOL)
{
return S_OK;
}
STDMETHODIMP CBand::GetSizeMax(ULARGE_INTEGER*)
{
return E_NOTIMPL;
}
STDMETHODIMP CBand::GetWindow(HWND *phwnd)
{
if(m_hWnd == NULL) // Not created yet
return E_UNEXPECTED;
*phwnd = m_hWnd;
return S_OK;
}
STDMETHODIMP CBand::ContextSensitiveHelp(BOOL fEnterMode)
{
// TODO: implement the context-sensitive help mode
return E_NOTIMPL;
}
STDMETHODIMP CBand::CloseDW(DWORD)
{
TRACE(L"The band docking window is being closed.");
ASSERT(m_hWnd != NULL); // Must still exist
// Destroy the window
if(m_hWnd != NULL) // This may happen if the window failed to be created
DestroyWindow();
return S_OK;
}
STDMETHODIMP CBand::ResizeBorderDW(LPCRECT prcBorder, IUnknown *punkToolbarSite, BOOL)
{
// This method is never called for the band objects. He-heh :)
TRACE(L"Warning! An unexpected thus unhandled call to IDockingWindow::ResizeBorderDW.");
return E_NOTIMPL;
}
STDMETHODIMP CBand::ShowDW(BOOL bShow)
{
// Note: we do not call the IDockingWindowSite::SetBorderSpaceDW because it's not needed for the tool bands.
if(!::IsWindow(m_hWnd))
return E_UNEXPECTED;
ShowWindow(bShow ? SW_SHOW : SW_HIDE);
return S_OK;
}
LRESULT CBand::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRACE(L"OnCreateEnter: The window style is %#010X.", GetWindowLong(GWL_STYLE));
if(DefWindowProc() == -1) // Allow the default handler to execute
return -1;
bHandled = TRUE; // Handled by both us and the default handler
TRACE(L"OnCreateCalledSuper: The window style is %#010X.", GetWindowLong(GWL_STYLE));
// Initialize the toolbar
TRACE(L"OnCreate: Initializing the toolbar.");
SendMessage(TB_BUTTONSTRUCTSIZE, (WPARAM)(int)sizeof(TBBUTTON), 0);
// Set the window that will receive the toolbar notification messages
SendMessage(TB_SETPARENT, (WPARAM)m_hWnd, 0);
// Update the window style
SetWindowLong(GWL_STYLE, 0x5600994D);
SetWindowLong(GWL_EXSTYLE, 0x00000080);
//SetWindowPos(0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_NOZORDER);
// Set the toolbar's extended style
SendMessage(TB_SETEXTENDEDSTYLE, 0, (LPARAM)(TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_HIDECLIPPEDBUTTONS | TBSTYLE_EX_MIXEDBUTTONS | TBSTYLE_EX_DOUBLEBUFFER));
SendMessage(TB_SETWINDOWTHEME, 0, (LPARAM)(LPCWSTR)L"TOOLBAR");
SetWindowPos(0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOZORDER);
if(!CreateControls())
{
TRACE(L"The toolbar has failed to self-initialize. Destroying the window.");
return -1; // Suicide
}
// Start the updater timer
SetTimer(timerUpdateUI, timerUpdateUIInterval, NULL);
TRACE(L"OnCreateExit: The window style is %#010X.", GetWindowLong(GWL_STYLE));
return 0;
}
BOOL CBand::CreateControls()
{
try
{
// Remove the old toolbar controls (if any)
for(int a = 0; (a < 0x1000) && (SendMessage(TB_DELETEBUTTON, 0, 0)); a++)
; // Delete the first button while there are more buttons
TBBUTTON btn;
ZeroMemory(&btn, sizeof(btn));
// Load or reload the XML control layout data
// Obtain a copy of this toolbar's controls
XmlElement xmlControlsLive = m_oActionManager->ControlFamilyFromGuid(m_guidToolbar, L"Toolbar");
XmlDocument xmlCopyDoc = CJetIe::CreateXmlDocument();
xmlCopyDoc->loadXML(xmlControlsLive->xml); // Create a copy starting from that element
m_xmlControls = xmlCopyDoc->selectSingleNode(L"/Controls");
XmlNodeList xmlControls = m_xmlControls->selectNodes(L"*"); // All the nodes
XmlElement xmlControl; // Each control on the toolbar
XmlElement xmlAction; // Action that corresponds to the current control
int nIndex = 0;
CString sButtonText; // Holds the buffer while the button is being assigned
// Create the image list and attach to the toolbar
m_ilAllNormal.Attach(ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, xmlControls->length, xmlControls->length)); // Create or re-create each one
m_ilAllHot.Attach(ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, xmlControls->length, xmlControls->length));
m_ilAllDisabled.Attach(ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, xmlControls->length, xmlControls->length));
// Enable multiple image lists
if(SendMessage(CCM_GETVERSION, 0, 0) <= 5) // Determine the current CommonControls Version. Raise if below 5
SendMessage(CCM_SETVERSION, (WPARAM)(DWORD)5, 0);
SendMessage(TB_SETIMAGELIST, IDIL_ALL, (LPARAM)m_ilAllNormal); // Submit
SendMessage(TB_SETHOTIMAGELIST, IDIL_ALL, (LPARAM)m_ilAllHot);
SendMessage(TB_SETDISABLEDIMAGELIST, IDIL_ALL, (LPARAM)m_ilAllDisabled);
// Enumerate and create all the controls
while((xmlControl = xmlControls->nextNode()) != NULL)
{
try // Per-control failures do not cause the global toolbar failures
{
// Preinit
btn.iBitmap = I_IMAGENONE; // TODO: control's setting should take precedence, if present
btn.idCommand = IDC_CONTROLBASE + nIndex;
btn.fsState = TBSTATE_HIDDEN | TBSTATE_INDETERMINATE; // Disabled and hidden by default
btn.dwData = nIndex; // Index in the controls list
// Further processing depends on the control type
if(xmlControl->baseName == (_bstr_t)L"Control") // Just an ordinary control
{
// Get the corresponding action
xmlAction = m_oActionManager->GetAction2(xmlControl);
// Fill in the btn structure that will create the button
PrepareButtonControl(xmlAction, btn, sButtonText);
// Actually add the button onto a toolbar
if(!SendMessage(TB_INSERTBUTTON, (WPARAM)-1, (LPARAM)&btn))
ThrowError(CJetIe::GetSystemError());
} // "Control"
else if(xmlControl->baseName == (_bstr_t)L"DropDownButton") // A drop-down button with a drop-arrow and default action, both clickable: [button|v]
{
// Get an entry id of the default control dropping out of this button, retrieve that control using this entry-id, and then take its action
xmlAction = m_oActionManager->GetAction2(m_oActionManager->ControlFromEntryID(xmlControl->getAttribute(L"Default"), xmlControl, false));
// Fill in the btn structure that will create the button
PrepareButtonControl(xmlAction, btn, sButtonText);
// Update to turn it into a dropdown button
btn.fsStyle |= BTNS_DROPDOWN;
if(!SendMessage(TB_INSERTBUTTON, (WPARAM)-1, (LPARAM)&btn))
ThrowError(CJetIe::GetSystemError());
}
else if(xmlControl->baseName == (_bstr_t)L"Separator") // Add a separator to the toolbar
{
btn.fsStyle = BTNS_SEP;
if(!SendMessage(TB_INSERTBUTTON, (WPARAM)-1, (LPARAM)&btn))
ThrowError(CJetIe::GetSystemError());
}
else // Yet another control type
{
ASSERT(FALSE);
ThrowError(CJetIe::LoadString(IDS_E_CONTROLTYPE));
}
// Increase the button index only in case the button has succeeded in being created
nIndex++;
}
catch(_com_error e)
{
COM_TRACE();
TRACE(L"Failed to create a toolbar button for %s.", (LPCWSTR)(_bstr_t)xmlControl->xml);
}
}
}
catch(_com_error e)
{
COM_TRACE();
// TODO: report the error
return FALSE; // Indicates failure
}
// Enable/disable, as needed
UpdateControls();
return TRUE; // More or less successful
}
LRESULT CBand::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DefWindowProc(); // Let the subclass make its cleanup
return 0;
}
HICON CBand::LoadResourceIcon(CString sOrigin)
{
if(sOrigin.Left(8) == _T("%JETIE%,")) // Denotes an icon in this DLL's resources
return LoadIcon(CJetIe::GetModuleInstanceHandle(), MAKEINTRESOURCE(_tstoi(sOrigin.Right(sOrigin.GetLength() - 8))));
//return (HICON)LoadImage(CJetIe::GetModuleInstanceHandle(), MAKEINTRESOURCE(_tstoi(sOrigin.Right(sOrigin.GetLength() - 8))), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION | LR_SHARED);
else // Icon in a file or other module's resourcess
return (HICON)LoadImage(NULL, sOrigin, IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_SHARED);
}
void CBand::UpdateControls()
{
//TRACE(L"Updating the toolbar controls state.");
try
{
XmlNodeList xmlControls = m_xmlControls->selectNodes(L"*");
XmlElement xmlControl; // Each control on the toolbar
// Enumerate and poll all the controls
for(int nIndex = 0; ((xmlControl = xmlControls->nextNode()) != NULL); nIndex++)
UpdateControl(xmlControl, nIndex); // If some control fails, that should not prevent from processing the rest of controls
}
COM_CATCH();
m_dwLastUpdateControls = GetTickCount();
}
void CBand::UpdateControl(XmlElement xmlControl, int nIndex)
{
try
{
///////////////////////////////////
// Query for the new control style
// Variables that describe the control state and should be gotten from the control callbacks
DWORD dwOleCmdF;
CStringW sTitle;
if(xmlControl->baseName == (_bstr_t)L"Control") // A control
{
m_oActionManager->QueryStatus((_bstr_t)xmlControl->getAttribute(L"Action"), (_variant_t)(IDispatch*)m_oBrowser, &dwOleCmdF, &sTitle, NULL, NULL, true);
if(!m_oActionManager->HasText((_bstr_t)xmlControl->getAttribute(L"Action")))
sTitle.Empty(); // Do not submit text to the button if it's not allowed for this control
}
else if(xmlControl->baseName == (_bstr_t)L"DropDownButton") // A drop-down with button
m_oActionManager->QueryStatus((_bstr_t)m_oActionManager->ControlFromEntryID(xmlControl->getAttribute(L"Default"), xmlControl, false)->getAttribute(L"Action"), (_variant_t)(IDispatch*)m_oBrowser, &dwOleCmdF, &sTitle, NULL, NULL, true);
else if(xmlControl->baseName == (_bstr_t)L"Separator") // A separator
dwOleCmdF = OLECMDF_ENABLED | OLECMDF_SUPPORTED; // Display it
else // Unknown control type
{
ASSERT(FALSE);
ThrowError(CJetIe::LoadString(IDS_E_CONTROLTYPE));
}
///////////////////////////////
// Apply the new control state
bool bChanged = false; // ORs the changes to the button state, submit the new state only if there's a change really
// Request the old state to merge with
// Also it's used to test whether there were any changes to the button state actually, and if no, the setter is not invoked (as it causes flickering)
TBBUTTONINFO tbi = {0};
ZeroMemory(&tbi, sizeof(tbi));
tbi.cbSize = sizeof(tbi);
tbi.dwMask = TBIF_STATE | TBIF_TEXT | TBIF_BYINDEX;
CString sOldButtonTitle;
int nBufferLen = 0x400;
tbi.pszText = sOldButtonTitle.GetBuffer(nBufferLen);
tbi.cchText = nBufferLen;
SendMessage(TB_GETBUTTONINFO, (WPARAM)nIndex, (LPARAM)&tbi);
sOldButtonTitle.ReleaseBuffer();
// Do the merge
BYTE fsStateOld = tbi.fsState;
tbi.fsState = 0;
if(!(dwOleCmdF & OLECMDF_SUPPORTED))
tbi.fsState |= TBSTATE_HIDDEN;
if(dwOleCmdF & OLECMDF_ENABLED)
tbi.fsState |= TBSTATE_ENABLED;
if(dwOleCmdF & OLECMDF_LATCHED)
tbi.fsState |= TBSTATE_CHECKED;
tbi.fsState |= fsStateOld & TBSTATE_PRESSED; // Maintain the pressed-by-mouse button state
bChanged = bChanged || (tbi.fsState != fsStateOld); // Test for the change
// Provide title, if such info is available
CString sTitleT;
if(!sTitle.IsEmpty())
{
sTitleT = (LPCTSTR)CW2T((LPCWSTR)sTitle);
tbi.pszText = (LPTSTR)(LPCTSTR)sTitleT;
tbi.dwMask |= TBIF_TEXT;
}
bChanged = bChanged || (sTitleT != sOldButtonTitle); // Test for the change
// Apply the new state
if(bChanged)
SendMessage(TB_SETBUTTONINFO, (WPARAM)nIndex, (LPARAM)&tbi);
}
COM_CATCH();
}
LRESULT CBand::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
TRACE(L"OnCommand!");
bHandled = FALSE; // Will be set to Handled by an individual handler
if((HWND)lParam == m_hWnd) // Messages from the toolbar we're handling
{
if((LOWORD(wParam) >= IDC_CONTROLBASE) && (LOWORD(wParam) <= IDC_CONTROLLIMIT)) // A message from the toolbar button
{
// TODO: parse the notification code
bHandled = TRUE;
try
{
// Get the control's element
long nIndex = LOWORD(wParam) - IDC_CONTROLBASE;
XmlElement xmlControl = m_xmlControls->selectNodes(L"*")->item[nIndex];
// React depending on the control type
if(xmlControl->baseName == (_bstr_t)L"Control") // TODO: check the base type of the action
m_oActionManager->Execute2(xmlControl, (_variant_t)(IDispatch*)(IDispatchPtr)m_oBrowser); // Execute the action associated with the control
else if(xmlControl->baseName == (_bstr_t)L"DropDownButton") // A button part of the drop-down button has been clicked
m_oActionManager->Execute2(m_oActionManager->ControlFromEntryID(xmlControl->getAttribute(L"Default"), xmlControl, false), (_variant_t)(IDispatch*)(IDispatchPtr)m_oBrowser); // Execute an action associated with the active button of this dropdown
else
TRACE(L"Received a command for an unknown control type (index=%d, type=%s).", nIndex, (LPCWSTR)xmlControl->baseName);
UpdateControl(xmlControl, nIndex); // Update the control state and appearance
}
catch(_com_error e)
{
CStringW sError = COM_REASON(e);
COM_TRACE();
CJetIe::ShowPopupNotification(CJetIe::LoadString(IDS_E_CANNOTEXECUTEACTION) + L'\n' + sError, NULL, CPopupNotification::pmStop);
}
}
}
return 0;
}
LRESULT CBand::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
//TRACE(L"OnNotify (%#010X)/(%#010X,a%#010X,w%#010X,%#010X)!", ((NMHDR*)lParam)->code, TBN_GETINFOTIP, TBN_GETINFOTIPA, TBN_GETINFOTIPW, NM_HOVER);
bHandled = TRUE; // Prevent the base class (commctl toolbar 32) from processing any of the notification messages because it will re-emit them to our class and get into an almost infinite loop
return 0;
}
LRESULT CBand::OnGetInfoTip(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
bHandled = FALSE;
TRACE(L"OnNotify::GetInfoTip!");
try
{
NMTBGETINFOTIP *pTip = (NMTBGETINFOTIP*)pnmh;
// Select action of the control with an index appropriate
XmlElement xmlControl = m_xmlControls->selectNodes(L"*")->item[(long)pTip->lParam];
_bstr_t bsAction = m_oActionManager->GetAction2(xmlControl)->getAttribute(L"ID");
CStringW sInfoTip;
m_oActionManager->QueryStatus(bsAction, (_variant_t)(IDispatch*)m_oBrowser, NULL, NULL, &sInfoTip, NULL, true);
// Submit
StringCchCopy(pTip->pszText, pTip->cchTextMax, CW2T((LPCWSTR)sInfoTip));
bHandled = TRUE;
}
COM_CATCH();
return 0;
}
LRESULT CBand::OnNeedText(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
bHandled = FALSE;
//TRACE(L"OnNotify::TtnNeedText!");
try
{
NMTTDISPINFO *pDI = (NMTTDISPINFO*)pnmh;
// Select action of the control with an index appropriate
//TRACE(L"ONT-00");
//TRACE(L"ONT-00-0 Index: %d.", (long)(pDI->hdr.idFrom - IDC_CONTROLBASE));
//TRACE(L"ONT-00-1 Controls count: %d.", m_xmlControls->selectNodes(L"*")->length);
XmlElement xmlControl = m_xmlControls->selectNodes(L"*")->item[(long)(pDI->hdr.idFrom - IDC_CONTROLBASE)];
_bstr_t bsAction = m_oActionManager->GetAction2(xmlControl)->getAttribute(L"ID");
//TRACE(L"ONT-01");
CStringW sInfoTip;
m_oActionManager->QueryStatus(bsAction, (_variant_t)(IDispatch*)m_oBrowser, NULL, NULL, &sInfoTip, NULL, true);
//TRACE(L"ONT-02");
// Submit
pDI->hinst = NULL;
pDI->lpszText = NULL;
StringCchCopy(pDI->szText, 80, CW2T((LPCWSTR)sInfoTip));
bHandled = TRUE;
}
COM_CATCH();
return 0;
}
LRESULT CBand::OnNotifyFormat(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = TRUE;
if(lParam == NF_QUERY)
#ifdef _UNICODE
return NFR_UNICODE;
#else
return NFR_ANSI;
#endif
return 0; // An unknown command
}
void CBand::SetToolbarGuid(GUID guid)
{
ASSERT(m_guidToolbar.Data1 == 0);
m_guidToolbar = guid;
}
LRESULT CBand::OnDropDown(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
NMTOOLBAR *pNM = (NMTOOLBAR*)pnmh;
if((pNM->iItem >= IDC_CONTROLBASE) && (pNM->iItem <= IDC_CONTROLLIMIT)) // A message from the toolbar button
{
bHandled = TRUE;
try
{
// Get the control's element
long nIndex = pNM->iItem - IDC_CONTROLBASE;
XmlElement xmlControl = m_xmlControls->selectNodes(L"*")->item[nIndex];
// React depending on the control type
if(xmlControl->baseName == (_bstr_t)L"DropDownButton") // An arrow part of the drop-down button has been clicked
{
// Button location in screen coordinates (as needed to show the context menu)
CRect rcButtonScreen = pNM->rcButton;
ClientToScreen(&rcButtonScreen);
// Show the context menu and achieve the result
XmlElement xmlClicked = m_oActionManager->ShowPopupMenu(xmlControl, m_hWnd, CPoint(rcButtonScreen.left, rcButtonScreen.bottom), (IDispatch*)m_oBrowser);
///////////
// Switch the button's default control (if allowed for the button)
bool bSwitch = ((xmlControl->selectSingleNode(L"@Switch") != NULL) && ((long)xmlControl->getAttribute(L"Switch")));
// If switching is allowed, there was some user's choice, the choice is an immediate child to the drop-down button, and it's not the currently-default item
if((bSwitch) && (xmlClicked != NULL) && (xmlControl->selectSingleNode((_bstr_t)L"*[@EntryID='" + (_bstr_t)xmlClicked->getAttribute(L"EntryID") + L"']") != NULL) && (xmlControl->getAttribute(L"Default") != xmlClicked->getAttribute(L"EntryID")))
{ // Set the executed action as a default
// Refresh the ActionManager contents
m_oActionManager->Load();
// Do the necessary updates
XmlElement xmlControlsLive = m_oActionManager->ControlFamilyFromGuid(m_guidToolbar, L"Toolbar");
xmlControlsLive->xml;
XmlElement xmlControlNew = m_oActionManager->ControlFromEntryID(xmlControl->getAttribute(L"EntryID"), xmlControlsLive, true);
xmlControlNew->setAttribute(L"Default", xmlClicked->getAttribute(L"EntryID"));
// Persist the changes
m_oActionManager->Save();
// Force updating of all the toolbars
UpdateAllToolbars();
}
}
else
ThrowError(CJetIe::LoadString(IDS_E_CONTROLTYPE));
UpdateControl(xmlControl, nIndex); // Update the control state and appearance
}
catch(_com_error e)
{
CStringW sError = COM_REASON(e);
COM_TRACE();
CJetIe::ShowPopupNotification(CJetIe::LoadString(IDS_E_CANNOTEXECUTEACTION) + L'\n' + sError, NULL, CPopupNotification::pmStop);
}
}
return 0;
}
void CBand::PrepareButtonControl(XmlElement xmlAction, TBBUTTON &btn, CString &sCache)
{
// Note that some general init is already done outside this function, uniformly for all the controls
int nIconIndex;
HICON hNormalIcon;
// Control appearance (image/text)
if(xmlAction->selectSingleNode(L"Style") == NULL) // Style not specified, "image only" for the toolbar by default
{
btn.fsStyle = 0;
btn.iBitmap = I_IMAGECALLBACK; // This is a temporary value, means that an image should be loaded later
}
else // TODO: local settings in the Control element should take precedence, if present
{
// Show image?
if((long)(xmlAction->selectSingleNode(L"Style/@Image")->nodeValue))
btn.iBitmap = I_IMAGECALLBACK; // This is a temporary value, means that an image should be loaded later
else
btn.iBitmap = I_IMAGENONE; // Image should not be loaded
// Show text?
if((long)(xmlAction->selectSingleNode(L"Style/@Text")->nodeValue))
btn.fsStyle = BTNS_AUTOSIZE | BTNS_SHOWTEXT;
else
btn.fsStyle = BTNS_AUTOSIZE;
}
// Load image, if needed
if(btn.iBitmap == I_IMAGECALLBACK) // Our flag, means need-to-load
{
// TODO: fill the imagelit, load the icon appropriate, control's setting should take precedence, if present
if(xmlAction->selectSingleNode(L"Image") == NULL) // No icon specified for this action
btn.iBitmap = I_IMAGENONE;
else
{
// Add the normal image (it must exist anyway)
nIconIndex = ImageList_AddIcon(m_ilAllNormal, hNormalIcon = LoadResourceIcon((LPCTSTR)(_bstr_t)xmlAction->selectSingleNode(L"Image/@Normal")->nodeValue)); // Load and add to the image list
btn.iBitmap = MAKELONG(nIconIndex, IDIL_ALL); // Assign to the button
// Add the hot image
if(xmlAction->selectSingleNode(L"Image/@Hot") != NULL)
ImageList_AddIcon(m_ilAllHot, LoadResourceIcon((LPCTSTR)(_bstr_t)xmlAction->selectSingleNode(L"Image/@Hot")->nodeValue));
else
ImageList_AddIcon(m_ilAllHot, hNormalIcon); // If not specified, use a normal icon for that
// Add the disabled image
if(xmlAction->selectSingleNode(L"Image/@Disabled") != NULL)
ImageList_AddIcon(m_ilAllDisabled, LoadResourceIcon((LPCTSTR)(_bstr_t)xmlAction->selectSingleNode(L"Image/@Disabled")->nodeValue));
else
ImageList_AddIcon(m_ilAllDisabled, hNormalIcon); // If not specified, use a normal icon for that
}
}
if((long)(xmlAction->selectSingleNode(L"Style/@Text")->nodeValue))
{
sCache = (LPCTSTR)(_bstr_t)xmlAction->selectSingleNode(L"Title/@Text")->nodeValue; // Hold the buffer until we submit it
btn.iString = (INT_PTR)(LPCTSTR)sCache;
}
else
btn.iString = NULL;
}
void CBand::UpdateAllToolbars()
{
TRACE(L"CBand is starting issuing an OnUpdateControls message.");
// Find all the windows
EnumWindows(EnumWindowsProc, m_nUpdateControlsMessage);
}
BOOL CALLBACK CBand::EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
// Send a message, if this is a suitable window
CString sClassName;
if((GetClassName(hwnd, sClassName.GetBuffer(0x100), 0x100)) && (sClassName == m_sWindowClassName)) // Succeeded in getting the window class name and it conicides with the desired band window class name
{
TRACE(L"CBand is issuing an OnUpdateControls message to HWND %#010X.", (DWORD)(INT_PTR)hwnd);
::PostMessage(hwnd, (UINT)lParam, 0, 0);
}
// Recurse to the child windows
EnumChildWindows(hwnd, EnumChildWindowsProc, lParam);
// Go on with enumeration
return TRUE;
}
BOOL CALLBACK CBand::EnumChildWindowsProc(HWND hwnd, LPARAM lParam)
{
// Send a message, if this is a suitable window
CString sClassName;
if((GetClassName(hwnd, sClassName.GetBuffer(0x100), 0x100)) && (sClassName == m_sWindowClassName)) // Succeeded in getting the window class name and it conicides with the desired band window class name
{
TRACE(L"CBand is issuing an OnUpdateControls message to HWND %#010X.", (DWORD)(INT_PTR)hwnd);
::PostMessage(hwnd, (UINT)lParam, 0, 0);
}
// We do not recurse to the child windows because they are already about to be handled by the parent search
// Go on with enumeration
return TRUE;
}
CString CBand::GetWindowClassName()
{
return _T("JetIe.") + CJetIe::LoadStringT(IDS_PLUGIN_NAME) + _T(".RebarBand");
}
LRESULT CBand::OnUpdateControls(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = TRUE;
TRACE(L"CBand has received an OnUpdateControls message via HWND %#010X.", m_hWnd);
// First, reload the possible changes to the Action Manager
m_oActionManager->Load();
// Then, re-create the toolbar controls according to the new scheme
CreateControls();
return 0;
}
LRESULT CBand::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE; // Allow base impl to process the mouse message
// Update the controls on mouse-enter
DWORD dwTimeLimit = (::IsChild(m_hwndTopmostParent, ::GetFocus())) ? 500 : 5000; // Minimum time between two subsequent updates, 500 ms for active window and 5000 for inactive
if(GetTickCount() - m_dwLastUpdateControls >= dwTimeLimit)
UpdateControls(); // m_dwLastUpdateControls will be updated within this function
CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
// Monitor the chevron state
BOOL bMouseOnChevron = ChevronBounds.PtInRect(pt); // This also checks for the visibility
if((bMouseOnChevron) && (m_nChevronState == csNormal))
{
m_nChevronState = csHovered;
InvalidateRect(ChevronBounds, false);
}
else if((!bMouseOnChevron) && (m_nChevronState == csHovered))
{
m_nChevronState = csNormal;
InvalidateRect(ChevronBounds, false);
}
bHandled = bMouseOnChevron;
// Capture or uncapture the mouse
// NOTE: capture causes the base button-pressing algorithm to fail …
/*
if(m_bMouseCaptured)
{
CRect rc;
GetClientRect(&rc);
if(!rc.PtInRect(pt))
{
m_bMouseCaptured = false;
ReleaseCapture();
}
}
else
{
m_bMouseCaptured = true;
SetCapture();
}
*/
return 0;
}
LRESULT CBand::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = TRUE; // Assume handled by default
// TODO: improve?..
// If the mouse is no more over the chevron, remove the hover state cues
if(m_nChevronState == csHovered)
{
CPoint pt;
GetCursorPos(&pt);
if(!ChevronBounds.PtInRect(pt))
{
m_nChevronState = csNormal;
InvalidateRect(ChevronBounds, FALSE);
}
}
// Dispatch the timer-handling
switch(wParam)
{
case timerUpdateUI:
OnTimerUpdateUI();
break;
default: // No handler …
bHandled = FALSE;
}
return 0;
}
void CBand::OnTimerUpdateUI()
{
// Check if the current window has focus in one of its descendant childs
if(::IsChild(m_hwndTopmostParent, ::GetFocus()))
UpdateControls();
}
#ifndef IInputObjectSitePtr
_COM_SMARTPTR_TYPEDEF(IInputObjectSite, IID_IInputObjectSite);
#endif
LRESULT CBand::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = TRUE;
// Notify the site about the focus loss
IInputObjectSitePtr oSite = (IUnknown*)m_spUnkSite;
if(oSite != NULL)
oSite->OnFocusChangeIS((IBand*)this, FALSE);
return S_OK;
}
LRESULT CBand::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = TRUE;
// Notify the site about the focus gain
IInputObjectSitePtr oSite = (IUnknown*)m_spUnkSite;
if(oSite != NULL)
oSite->OnFocusChangeIS((IBand*)this, TRUE);
return S_OK;
}
bool CBand::GetChevronVisible()
{
// Check if the last button fits within the window's client rectangle.
CRect rcClient;
GetClientRect(&rcClient);
CRect rcLastButton;
if(SendMessage(TB_GETITEMRECT, SendMessage(TB_BUTTONCOUNT, NULL, NULL) - 1, (LPARAM)(LPRECT)&rcLastButton))
return !((rcClient.PtInRect(CPoint(rcLastButton.left, rcLastButton.top))) && (rcClient.PtInRect(CPoint(rcLastButton.right - 1, rcLastButton.bottom - 1))));
return true;
}
LRESULT CBand::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DefWindowProc();
//PAINTSTRUCT ps;
////////////
// Chevron
if(ChevronVisible)
{
HDC hdc = GetDC();//BeginPaint(&ps);
CRect rcChevron = ChevronBounds;
// Background
FillRect(hdc, &rcChevron, (HBRUSH)GetSysColorBrush(COLOR_BTNFACE));
// Chevron Icon
DrawIconEx(hdc, rcChevron.left + (rcChevron.Width() - 8) / 2, rcChevron.top + (rcChevron.Height() - 16) / 2, m_iconChevron, 8, 16, NULL, NULL, DI_IMAGE | DI_MASK);
// Special decoration?
HBRUSH brushTopLeft = NULL, brushBottomRight = NULL;
switch(m_nChevronState)
{
case csNormal:
break;
case csHovered:
brushTopLeft = GetSysColorBrush(COLOR_3DHIGHLIGHT);
brushBottomRight = GetSysColorBrush(COLOR_3DSHADOW);
break;
case csPressed:
brushTopLeft = GetSysColorBrush(COLOR_3DSHADOW);
brushBottomRight = GetSysColorBrush(COLOR_3DHIGHLIGHT);
break;
}
if((brushTopLeft != 0) || (brushBottomRight != 0))
{
CRect rc;
rc.SetRect(rcChevron.left, rcChevron.top, rcChevron.right - 1, rcChevron.top + 1);
FillRect(hdc, &rc, brushTopLeft);
rc.SetRect(rcChevron.left, rcChevron.top, rcChevron.left + 1, rcChevron.bottom - 1);
FillRect(hdc, &rc, brushTopLeft);
rc.SetRect(rcChevron.right - 1, rcChevron.top, rcChevron.right, rcChevron.bottom);
FillRect(hdc, &rc, brushBottomRight);
rc.SetRect(rcChevron.left, rcChevron.bottom - 1, rcChevron.right, rcChevron.bottom);
FillRect(hdc, &rc, brushBottomRight);
}
ReleaseDC(hdc);//EndPaint(&ps);
}
bHandled = TRUE; // Called the underlying handler manually
return 0;
}
CRect CBand::GetChevronBounds()
{
if(!ChevronVisible)
return CRect(0, 0, 0, 0);
return GetDefaultChevronBounds();
}
CRect CBand::GetDefaultChevronBounds()
{
CRect rc;
GetClientRect(&rc);
rc.left = rc.right - CHEVRON_WIDTH >= 0 ? rc.right - CHEVRON_WIDTH : 0;
return rc;
}
LRESULT CBand::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
CRect rcChevron = ChevronBounds;
if(rcChevron.PtInRect(CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))) // This also checks for the visibility
{
bHandled = TRUE;
// Paint the chevron as pressed
m_nChevronState = csPressed;
InvalidateRect(ChevronBounds, false);
// Prepare the rects to check if a particular button is visible or not
CRect rcButton, rcClient;
GetClientRect(&rcClient);
// Filter out those controls that should be displayed under the chevron (those that do not fit into the view)
XmlElement xmlTempRoot = m_xmlControls->ownerDocument->createElement(L"Controls");
XmlNodeList xmlControls = m_xmlControls->selectNodes(L"*");
XmlElement xmlControl;
for(int nIndex = 0; ((xmlControl = xmlControls->nextNode()) != NULL); nIndex++)
{
// Get the rectangle for each button and check whether it fits or not, if not (or on rect-failure), add it to the chevron
if((!SendMessage(TB_GETITEMRECT, nIndex, (LPARAM)(LPRECT)&rcButton)) || (!((rcClient.PtInRect(CPoint(rcButton.left, rcButton.top))) && (rcClient.PtInRect(CPoint(rcButton.right - 1, rcButton.bottom - 1))))))
xmlTempRoot->appendChild(xmlControl->cloneNode(true));
}
// Chevron location in screen coordinates (as needed to show the context menu)
CRect rcChevronScreen = rcChevron;
ClientToScreen(&rcChevronScreen);
// Show the popup menu for the chevron
m_oActionManager->ShowPopupMenu(xmlTempRoot, m_hWnd, CPoint(rcChevronScreen.left, rcChevronScreen.bottom), (IDispatch*)m_oBrowser);
// Paint the chevron normally
m_nChevronState = csNormal;
InvalidateRect(ChevronBounds, false);
}
return 0;
}
LRESULT CBand::OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
// Catch all the mouse actions over the chevron, although there's no special action for that
CRect rcChevron = ChevronBounds;
if(rcChevron.PtInRect(CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))) // This also checks for chevron visibility
bHandled = TRUE;
return 0;
}
LRESULT CBand::OnSizeOrSizing(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// Force repaint of the chevron, as it's not handled by the toolbar's repainting code
//CRect rcChevron = GetDefaultChevronBounds();
//rcChevron.left -= CHEVRON_WIDTH;
//InvalidateRect(&rcChevron, false);
InvalidateRect(NULL);
bHandled = FALSE; // Let toolbar play with it
return 0;
}