|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsMathMLmactionFrame.h" |
|
7 #include "nsCOMPtr.h" |
|
8 #include "nsPresContext.h" |
|
9 #include "nsNameSpaceManager.h" |
|
10 #include "prprf.h" // For PR_snprintf() |
|
11 #include "nsIDocShell.h" |
|
12 #include "nsIDocShellTreeOwner.h" |
|
13 #include "nsIWebBrowserChrome.h" |
|
14 #include "nsIInterfaceRequestorUtils.h" |
|
15 #include "nsTextFragment.h" |
|
16 #include "nsIDOMEvent.h" |
|
17 #include "mozilla/gfx/2D.h" |
|
18 |
|
19 // |
|
20 // <maction> -- bind actions to a subexpression - implementation |
|
21 // |
|
22 |
|
23 enum nsMactionActionTypes { |
|
24 NS_MATHML_ACTION_TYPE_CLASS_ERROR = 0x10, |
|
25 NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION = 0x20, |
|
26 NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION = 0x40, |
|
27 NS_MATHML_ACTION_TYPE_CLASS_BITMASK = 0xF0, |
|
28 |
|
29 NS_MATHML_ACTION_TYPE_NONE = NS_MATHML_ACTION_TYPE_CLASS_ERROR|0x01, |
|
30 |
|
31 NS_MATHML_ACTION_TYPE_TOGGLE = NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION|0x01, |
|
32 NS_MATHML_ACTION_TYPE_UNKNOWN = NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION|0x02, |
|
33 |
|
34 NS_MATHML_ACTION_TYPE_STATUSLINE = NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION|0x01, |
|
35 NS_MATHML_ACTION_TYPE_TOOLTIP = NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION|0x02 |
|
36 }; |
|
37 |
|
38 |
|
39 // helper function to parse actiontype attribute |
|
40 static int32_t |
|
41 GetActionType(nsIContent* aContent) |
|
42 { |
|
43 nsAutoString value; |
|
44 |
|
45 if (aContent) { |
|
46 if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::actiontype_, value)) |
|
47 return NS_MATHML_ACTION_TYPE_NONE; |
|
48 } |
|
49 |
|
50 if (value.EqualsLiteral("toggle")) |
|
51 return NS_MATHML_ACTION_TYPE_TOGGLE; |
|
52 if (value.EqualsLiteral("statusline")) |
|
53 return NS_MATHML_ACTION_TYPE_STATUSLINE; |
|
54 if (value.EqualsLiteral("tooltip")) |
|
55 return NS_MATHML_ACTION_TYPE_TOOLTIP; |
|
56 |
|
57 return NS_MATHML_ACTION_TYPE_UNKNOWN; |
|
58 } |
|
59 |
|
60 nsIFrame* |
|
61 NS_NewMathMLmactionFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
62 { |
|
63 return new (aPresShell) nsMathMLmactionFrame(aContext); |
|
64 } |
|
65 |
|
66 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmactionFrame) |
|
67 |
|
68 nsMathMLmactionFrame::~nsMathMLmactionFrame() |
|
69 { |
|
70 // unregister us as a mouse event listener ... |
|
71 // printf("maction:%p unregistering as mouse event listener ...\n", this); |
|
72 if (mListener) { |
|
73 mContent->RemoveSystemEventListener(NS_LITERAL_STRING("click"), mListener, |
|
74 false); |
|
75 mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener, |
|
76 false); |
|
77 mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener, |
|
78 false); |
|
79 } |
|
80 } |
|
81 |
|
82 void |
|
83 nsMathMLmactionFrame::Init(nsIContent* aContent, |
|
84 nsIFrame* aParent, |
|
85 nsIFrame* aPrevInFlow) |
|
86 { |
|
87 // Init our local attributes |
|
88 |
|
89 mChildCount = -1; // these will be updated in GetSelectedFrame() |
|
90 mActionType = GetActionType(aContent); |
|
91 |
|
92 // Let the base class do the rest |
|
93 return nsMathMLSelectedFrame::Init(aContent, aParent, aPrevInFlow); |
|
94 } |
|
95 |
|
96 nsresult |
|
97 nsMathMLmactionFrame::ChildListChanged(int32_t aModType) |
|
98 { |
|
99 // update cached values |
|
100 mChildCount = -1; |
|
101 mSelectedFrame = nullptr; |
|
102 |
|
103 return nsMathMLSelectedFrame::ChildListChanged(aModType); |
|
104 } |
|
105 |
|
106 // return the frame whose number is given by the attribute selection="number" |
|
107 nsIFrame* |
|
108 nsMathMLmactionFrame::GetSelectedFrame() |
|
109 { |
|
110 nsAutoString value; |
|
111 int32_t selection; |
|
112 |
|
113 if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) == |
|
114 NS_MATHML_ACTION_TYPE_CLASS_ERROR) { |
|
115 mSelection = -1; |
|
116 mInvalidMarkup = true; |
|
117 mSelectedFrame = nullptr; |
|
118 return mSelectedFrame; |
|
119 } |
|
120 |
|
121 // Selection is not applied to tooltip and statusline. |
|
122 // Thereby return the first child. |
|
123 if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) == |
|
124 NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION) { |
|
125 // We don't touch mChildCount here. It's incorrect to assign it 1, |
|
126 // and it's inefficient to count the children. It's fine to leave |
|
127 // it be equal -1 because it's not used with other actiontypes. |
|
128 mSelection = 1; |
|
129 mInvalidMarkup = false; |
|
130 mSelectedFrame = mFrames.FirstChild(); |
|
131 return mSelectedFrame; |
|
132 } |
|
133 |
|
134 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::selection_, value); |
|
135 if (!value.IsEmpty()) { |
|
136 nsresult errorCode; |
|
137 selection = value.ToInteger(&errorCode); |
|
138 if (NS_FAILED(errorCode)) |
|
139 selection = 1; |
|
140 } |
|
141 else selection = 1; // default is first frame |
|
142 |
|
143 if (-1 != mChildCount) { // we have been in this function before... |
|
144 // cater for invalid user-supplied selection |
|
145 if (selection > mChildCount || selection < 1) |
|
146 selection = -1; |
|
147 // quick return if it is identical with our cache |
|
148 if (selection == mSelection) |
|
149 return mSelectedFrame; |
|
150 } |
|
151 |
|
152 // get the selected child and cache new values... |
|
153 int32_t count = 0; |
|
154 nsIFrame* childFrame = mFrames.FirstChild(); |
|
155 while (childFrame) { |
|
156 if (!mSelectedFrame) |
|
157 mSelectedFrame = childFrame; // default is first child |
|
158 if (++count == selection) |
|
159 mSelectedFrame = childFrame; |
|
160 |
|
161 childFrame = childFrame->GetNextSibling(); |
|
162 } |
|
163 // cater for invalid user-supplied selection |
|
164 if (selection > count || selection < 1) |
|
165 selection = -1; |
|
166 |
|
167 mChildCount = count; |
|
168 mSelection = selection; |
|
169 mInvalidMarkup = (mSelection == -1); |
|
170 TransmitAutomaticData(); |
|
171 |
|
172 return mSelectedFrame; |
|
173 } |
|
174 |
|
175 nsresult |
|
176 nsMathMLmactionFrame::SetInitialChildList(ChildListID aListID, |
|
177 nsFrameList& aChildList) |
|
178 { |
|
179 nsresult rv = nsMathMLSelectedFrame::SetInitialChildList(aListID, aChildList); |
|
180 |
|
181 if (!mSelectedFrame) { |
|
182 mActionType = NS_MATHML_ACTION_TYPE_NONE; |
|
183 } |
|
184 else { |
|
185 // create mouse event listener and register it |
|
186 mListener = new nsMathMLmactionFrame::MouseListener(this); |
|
187 // printf("maction:%p registering as mouse event listener ...\n", this); |
|
188 mContent->AddSystemEventListener(NS_LITERAL_STRING("click"), mListener, |
|
189 false, false); |
|
190 mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener, |
|
191 false, false); |
|
192 mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener, |
|
193 false, false); |
|
194 } |
|
195 return rv; |
|
196 } |
|
197 |
|
198 nsresult |
|
199 nsMathMLmactionFrame::AttributeChanged(int32_t aNameSpaceID, |
|
200 nsIAtom* aAttribute, |
|
201 int32_t aModType) |
|
202 { |
|
203 bool needsReflow = false; |
|
204 |
|
205 if (aAttribute == nsGkAtoms::actiontype_) { |
|
206 // updating mActionType ... |
|
207 int32_t oldActionType = mActionType; |
|
208 mActionType = GetActionType(mContent); |
|
209 |
|
210 // Initiate a reflow when actiontype classes are different. |
|
211 if ((oldActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) != |
|
212 (mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK)) { |
|
213 needsReflow = true; |
|
214 } |
|
215 } else if (aAttribute == nsGkAtoms::selection_) { |
|
216 if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) == |
|
217 NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION) { |
|
218 needsReflow = true; |
|
219 } |
|
220 } else { |
|
221 // let the base class handle other attribute changes |
|
222 return |
|
223 nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, |
|
224 aAttribute, aModType); |
|
225 } |
|
226 |
|
227 if (needsReflow) { |
|
228 PresContext()->PresShell()-> |
|
229 FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); |
|
230 } |
|
231 |
|
232 return NS_OK; |
|
233 } |
|
234 |
|
235 // ################################################################ |
|
236 // Event handlers |
|
237 // ################################################################ |
|
238 |
|
239 NS_IMPL_ISUPPORTS(nsMathMLmactionFrame::MouseListener, |
|
240 nsIDOMEventListener) |
|
241 |
|
242 |
|
243 // helper to show a msg on the status bar |
|
244 // curled from nsObjectFrame.cpp ... |
|
245 void |
|
246 ShowStatus(nsPresContext* aPresContext, nsString& aStatusMsg) |
|
247 { |
|
248 nsCOMPtr<nsIDocShellTreeItem> docShellItem(aPresContext->GetDocShell()); |
|
249 if (docShellItem) { |
|
250 nsCOMPtr<nsIDocShellTreeOwner> treeOwner; |
|
251 docShellItem->GetTreeOwner(getter_AddRefs(treeOwner)); |
|
252 if (treeOwner) { |
|
253 nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(treeOwner)); |
|
254 if (browserChrome) { |
|
255 browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, aStatusMsg.get()); |
|
256 } |
|
257 } |
|
258 } |
|
259 } |
|
260 |
|
261 NS_IMETHODIMP |
|
262 nsMathMLmactionFrame::MouseListener::HandleEvent(nsIDOMEvent* aEvent) |
|
263 { |
|
264 nsAutoString eventType; |
|
265 aEvent->GetType(eventType); |
|
266 if (eventType.EqualsLiteral("mouseover")) { |
|
267 mOwner->MouseOver(); |
|
268 } |
|
269 else if (eventType.EqualsLiteral("click")) { |
|
270 mOwner->MouseClick(); |
|
271 } |
|
272 else if (eventType.EqualsLiteral("mouseout")) { |
|
273 mOwner->MouseOut(); |
|
274 } |
|
275 else { |
|
276 NS_ABORT(); |
|
277 } |
|
278 |
|
279 return NS_OK; |
|
280 } |
|
281 |
|
282 void |
|
283 nsMathMLmactionFrame::MouseOver() |
|
284 { |
|
285 // see if we should display a status message |
|
286 if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) { |
|
287 // retrieve content from a second child if it exists |
|
288 nsIFrame* childFrame = mFrames.FrameAt(1); |
|
289 if (!childFrame) return; |
|
290 |
|
291 nsIContent* content = childFrame->GetContent(); |
|
292 if (!content) return; |
|
293 |
|
294 // check whether the content is mtext or not |
|
295 if (content->GetNameSpaceID() == kNameSpaceID_MathML && |
|
296 content->Tag() == nsGkAtoms::mtext_) { |
|
297 // get the text to be displayed |
|
298 content = content->GetFirstChild(); |
|
299 if (!content) return; |
|
300 |
|
301 const nsTextFragment* textFrg = content->GetText(); |
|
302 if (!textFrg) return; |
|
303 |
|
304 nsAutoString text; |
|
305 textFrg->AppendTo(text); |
|
306 // collapse whitespaces as listed in REC, section 3.2.6.1 |
|
307 text.CompressWhitespace(); |
|
308 ShowStatus(PresContext(), text); |
|
309 } |
|
310 } |
|
311 } |
|
312 |
|
313 void |
|
314 nsMathMLmactionFrame::MouseOut() |
|
315 { |
|
316 // see if we should remove the status message |
|
317 if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) { |
|
318 nsAutoString value; |
|
319 value.SetLength(0); |
|
320 ShowStatus(PresContext(), value); |
|
321 } |
|
322 } |
|
323 |
|
324 void |
|
325 nsMathMLmactionFrame::MouseClick() |
|
326 { |
|
327 if (NS_MATHML_ACTION_TYPE_TOGGLE == mActionType) { |
|
328 if (mChildCount > 1) { |
|
329 int32_t selection = (mSelection == mChildCount)? 1 : mSelection + 1; |
|
330 nsAutoString value; |
|
331 char cbuf[10]; |
|
332 PR_snprintf(cbuf, sizeof(cbuf), "%d", selection); |
|
333 value.AssignASCII(cbuf); |
|
334 bool notify = false; // don't yet notify the document |
|
335 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::selection_, value, notify); |
|
336 |
|
337 // Now trigger a content-changed reflow... |
|
338 PresContext()->PresShell()-> |
|
339 FrameNeedsReflow(mSelectedFrame, nsIPresShell::eTreeChange, |
|
340 NS_FRAME_IS_DIRTY); |
|
341 } |
|
342 } |
|
343 } |