michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMathMLmactionFrame.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "prprf.h" // For PR_snprintf() michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "nsIWebBrowserChrome.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: // michael@0: // -- bind actions to a subexpression - implementation michael@0: // michael@0: michael@0: enum nsMactionActionTypes { michael@0: NS_MATHML_ACTION_TYPE_CLASS_ERROR = 0x10, michael@0: NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION = 0x20, michael@0: NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION = 0x40, michael@0: NS_MATHML_ACTION_TYPE_CLASS_BITMASK = 0xF0, michael@0: michael@0: NS_MATHML_ACTION_TYPE_NONE = NS_MATHML_ACTION_TYPE_CLASS_ERROR|0x01, michael@0: michael@0: NS_MATHML_ACTION_TYPE_TOGGLE = NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION|0x01, michael@0: NS_MATHML_ACTION_TYPE_UNKNOWN = NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION|0x02, michael@0: michael@0: NS_MATHML_ACTION_TYPE_STATUSLINE = NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION|0x01, michael@0: NS_MATHML_ACTION_TYPE_TOOLTIP = NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION|0x02 michael@0: }; michael@0: michael@0: michael@0: // helper function to parse actiontype attribute michael@0: static int32_t michael@0: GetActionType(nsIContent* aContent) michael@0: { michael@0: nsAutoString value; michael@0: michael@0: if (aContent) { michael@0: if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::actiontype_, value)) michael@0: return NS_MATHML_ACTION_TYPE_NONE; michael@0: } michael@0: michael@0: if (value.EqualsLiteral("toggle")) michael@0: return NS_MATHML_ACTION_TYPE_TOGGLE; michael@0: if (value.EqualsLiteral("statusline")) michael@0: return NS_MATHML_ACTION_TYPE_STATUSLINE; michael@0: if (value.EqualsLiteral("tooltip")) michael@0: return NS_MATHML_ACTION_TYPE_TOOLTIP; michael@0: michael@0: return NS_MATHML_ACTION_TYPE_UNKNOWN; michael@0: } michael@0: michael@0: nsIFrame* michael@0: NS_NewMathMLmactionFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsMathMLmactionFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmactionFrame) michael@0: michael@0: nsMathMLmactionFrame::~nsMathMLmactionFrame() michael@0: { michael@0: // unregister us as a mouse event listener ... michael@0: // printf("maction:%p unregistering as mouse event listener ...\n", this); michael@0: if (mListener) { michael@0: mContent->RemoveSystemEventListener(NS_LITERAL_STRING("click"), mListener, michael@0: false); michael@0: mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener, michael@0: false); michael@0: mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener, michael@0: false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMathMLmactionFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: // Init our local attributes michael@0: michael@0: mChildCount = -1; // these will be updated in GetSelectedFrame() michael@0: mActionType = GetActionType(aContent); michael@0: michael@0: // Let the base class do the rest michael@0: return nsMathMLSelectedFrame::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLmactionFrame::ChildListChanged(int32_t aModType) michael@0: { michael@0: // update cached values michael@0: mChildCount = -1; michael@0: mSelectedFrame = nullptr; michael@0: michael@0: return nsMathMLSelectedFrame::ChildListChanged(aModType); michael@0: } michael@0: michael@0: // return the frame whose number is given by the attribute selection="number" michael@0: nsIFrame* michael@0: nsMathMLmactionFrame::GetSelectedFrame() michael@0: { michael@0: nsAutoString value; michael@0: int32_t selection; michael@0: michael@0: if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) == michael@0: NS_MATHML_ACTION_TYPE_CLASS_ERROR) { michael@0: mSelection = -1; michael@0: mInvalidMarkup = true; michael@0: mSelectedFrame = nullptr; michael@0: return mSelectedFrame; michael@0: } michael@0: michael@0: // Selection is not applied to tooltip and statusline. michael@0: // Thereby return the first child. michael@0: if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) == michael@0: NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION) { michael@0: // We don't touch mChildCount here. It's incorrect to assign it 1, michael@0: // and it's inefficient to count the children. It's fine to leave michael@0: // it be equal -1 because it's not used with other actiontypes. michael@0: mSelection = 1; michael@0: mInvalidMarkup = false; michael@0: mSelectedFrame = mFrames.FirstChild(); michael@0: return mSelectedFrame; michael@0: } michael@0: michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::selection_, value); michael@0: if (!value.IsEmpty()) { michael@0: nsresult errorCode; michael@0: selection = value.ToInteger(&errorCode); michael@0: if (NS_FAILED(errorCode)) michael@0: selection = 1; michael@0: } michael@0: else selection = 1; // default is first frame michael@0: michael@0: if (-1 != mChildCount) { // we have been in this function before... michael@0: // cater for invalid user-supplied selection michael@0: if (selection > mChildCount || selection < 1) michael@0: selection = -1; michael@0: // quick return if it is identical with our cache michael@0: if (selection == mSelection) michael@0: return mSelectedFrame; michael@0: } michael@0: michael@0: // get the selected child and cache new values... michael@0: int32_t count = 0; michael@0: nsIFrame* childFrame = mFrames.FirstChild(); michael@0: while (childFrame) { michael@0: if (!mSelectedFrame) michael@0: mSelectedFrame = childFrame; // default is first child michael@0: if (++count == selection) michael@0: mSelectedFrame = childFrame; michael@0: michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: // cater for invalid user-supplied selection michael@0: if (selection > count || selection < 1) michael@0: selection = -1; michael@0: michael@0: mChildCount = count; michael@0: mSelection = selection; michael@0: mInvalidMarkup = (mSelection == -1); michael@0: TransmitAutomaticData(); michael@0: michael@0: return mSelectedFrame; michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLmactionFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: nsresult rv = nsMathMLSelectedFrame::SetInitialChildList(aListID, aChildList); michael@0: michael@0: if (!mSelectedFrame) { michael@0: mActionType = NS_MATHML_ACTION_TYPE_NONE; michael@0: } michael@0: else { michael@0: // create mouse event listener and register it michael@0: mListener = new nsMathMLmactionFrame::MouseListener(this); michael@0: // printf("maction:%p registering as mouse event listener ...\n", this); michael@0: mContent->AddSystemEventListener(NS_LITERAL_STRING("click"), mListener, michael@0: false, false); michael@0: mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener, michael@0: false, false); michael@0: mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener, michael@0: false, false); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsMathMLmactionFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: bool needsReflow = false; michael@0: michael@0: if (aAttribute == nsGkAtoms::actiontype_) { michael@0: // updating mActionType ... michael@0: int32_t oldActionType = mActionType; michael@0: mActionType = GetActionType(mContent); michael@0: michael@0: // Initiate a reflow when actiontype classes are different. michael@0: if ((oldActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) != michael@0: (mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK)) { michael@0: needsReflow = true; michael@0: } michael@0: } else if (aAttribute == nsGkAtoms::selection_) { michael@0: if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) == michael@0: NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION) { michael@0: needsReflow = true; michael@0: } michael@0: } else { michael@0: // let the base class handle other attribute changes michael@0: return michael@0: nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, michael@0: aAttribute, aModType); michael@0: } michael@0: michael@0: if (needsReflow) { michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // ################################################################ michael@0: // Event handlers michael@0: // ################################################################ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsMathMLmactionFrame::MouseListener, michael@0: nsIDOMEventListener) michael@0: michael@0: michael@0: // helper to show a msg on the status bar michael@0: // curled from nsObjectFrame.cpp ... michael@0: void michael@0: ShowStatus(nsPresContext* aPresContext, nsString& aStatusMsg) michael@0: { michael@0: nsCOMPtr docShellItem(aPresContext->GetDocShell()); michael@0: if (docShellItem) { michael@0: nsCOMPtr treeOwner; michael@0: docShellItem->GetTreeOwner(getter_AddRefs(treeOwner)); michael@0: if (treeOwner) { michael@0: nsCOMPtr browserChrome(do_GetInterface(treeOwner)); michael@0: if (browserChrome) { michael@0: browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, aStatusMsg.get()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMathMLmactionFrame::MouseListener::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: if (eventType.EqualsLiteral("mouseover")) { michael@0: mOwner->MouseOver(); michael@0: } michael@0: else if (eventType.EqualsLiteral("click")) { michael@0: mOwner->MouseClick(); michael@0: } michael@0: else if (eventType.EqualsLiteral("mouseout")) { michael@0: mOwner->MouseOut(); michael@0: } michael@0: else { michael@0: NS_ABORT(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsMathMLmactionFrame::MouseOver() michael@0: { michael@0: // see if we should display a status message michael@0: if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) { michael@0: // retrieve content from a second child if it exists michael@0: nsIFrame* childFrame = mFrames.FrameAt(1); michael@0: if (!childFrame) return; michael@0: michael@0: nsIContent* content = childFrame->GetContent(); michael@0: if (!content) return; michael@0: michael@0: // check whether the content is mtext or not michael@0: if (content->GetNameSpaceID() == kNameSpaceID_MathML && michael@0: content->Tag() == nsGkAtoms::mtext_) { michael@0: // get the text to be displayed michael@0: content = content->GetFirstChild(); michael@0: if (!content) return; michael@0: michael@0: const nsTextFragment* textFrg = content->GetText(); michael@0: if (!textFrg) return; michael@0: michael@0: nsAutoString text; michael@0: textFrg->AppendTo(text); michael@0: // collapse whitespaces as listed in REC, section 3.2.6.1 michael@0: text.CompressWhitespace(); michael@0: ShowStatus(PresContext(), text); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMathMLmactionFrame::MouseOut() michael@0: { michael@0: // see if we should remove the status message michael@0: if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) { michael@0: nsAutoString value; michael@0: value.SetLength(0); michael@0: ShowStatus(PresContext(), value); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMathMLmactionFrame::MouseClick() michael@0: { michael@0: if (NS_MATHML_ACTION_TYPE_TOGGLE == mActionType) { michael@0: if (mChildCount > 1) { michael@0: int32_t selection = (mSelection == mChildCount)? 1 : mSelection + 1; michael@0: nsAutoString value; michael@0: char cbuf[10]; michael@0: PR_snprintf(cbuf, sizeof(cbuf), "%d", selection); michael@0: value.AssignASCII(cbuf); michael@0: bool notify = false; // don't yet notify the document michael@0: mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::selection_, value, notify); michael@0: michael@0: // Now trigger a content-changed reflow... michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(mSelectedFrame, nsIPresShell::eTreeChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: }