1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Original Author: David W. Hyatt (hyatt@netscape.com)
24 : * Michael Lowe <michael.lowe@bigfoot.com>
25 : * Pierre Phaneuf <pp@ludusdesign.com>
26 : * Dean Tessman <dean_tessman@hotmail.com>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either of the GNU General Public License Version 2 or later (the "GPL"),
30 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 : #include "nsGkAtoms.h"
43 : #include "nsHTMLParts.h"
44 : #include "nsMenuFrame.h"
45 : #include "nsBoxFrame.h"
46 : #include "nsIContent.h"
47 : #include "prtypes.h"
48 : #include "nsIAtom.h"
49 : #include "nsPresContext.h"
50 : #include "nsIPresShell.h"
51 : #include "nsStyleContext.h"
52 : #include "nsCSSRendering.h"
53 : #include "nsINameSpaceManager.h"
54 : #include "nsMenuPopupFrame.h"
55 : #include "nsMenuBarFrame.h"
56 : #include "nsIDocument.h"
57 : #include "nsIDOMElement.h"
58 : #include "nsIComponentManager.h"
59 : #include "nsBoxLayoutState.h"
60 : #include "nsIScrollableFrame.h"
61 : #include "nsBindingManager.h"
62 : #include "nsIServiceManager.h"
63 : #include "nsCSSFrameConstructor.h"
64 : #include "nsIDOMKeyEvent.h"
65 : #include "nsEventDispatcher.h"
66 : #include "nsIPrivateDOMEvent.h"
67 : #include "nsXPIDLString.h"
68 : #include "nsReadableUtils.h"
69 : #include "nsUnicharUtils.h"
70 : #include "nsIStringBundle.h"
71 : #include "nsGUIEvent.h"
72 : #include "nsContentUtils.h"
73 : #include "nsDisplayList.h"
74 : #include "nsIReflowCallback.h"
75 : #include "nsISound.h"
76 : #include "nsEventStateManager.h"
77 : #include "nsIDOMXULMenuListElement.h"
78 : #include "mozilla/Services.h"
79 : #include "mozilla/Preferences.h"
80 : #include "mozilla/LookAndFeel.h"
81 :
82 : using namespace mozilla;
83 :
84 : #define NS_MENU_POPUP_LIST_INDEX 0
85 :
86 : #if defined(XP_WIN) || defined(XP_OS2)
87 : #define NSCONTEXTMENUISMOUSEUP 1
88 : #endif
89 :
90 : static void
91 0 : AssertNotCalled(void* aPropertyValue)
92 : {
93 0 : NS_ERROR("popup list should never be destroyed by the FramePropertyTable");
94 0 : }
95 0 : NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled)
96 :
97 : static PRInt32 gEatMouseMove = false;
98 :
99 : const PRInt32 kBlinkDelay = 67; // milliseconds
100 :
101 : // this class is used for dispatching menu activation events asynchronously.
102 : class nsMenuActivateEvent : public nsRunnable
103 0 : {
104 : public:
105 0 : nsMenuActivateEvent(nsIContent *aMenu,
106 : nsPresContext* aPresContext,
107 : bool aIsActivate)
108 0 : : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate)
109 : {
110 0 : }
111 :
112 0 : NS_IMETHOD Run()
113 : {
114 0 : nsAutoString domEventToFire;
115 :
116 0 : if (mIsActivate) {
117 : // Highlight the menu.
118 : mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
119 0 : NS_LITERAL_STRING("true"), true);
120 : // The menuactivated event is used by accessibility to track the user's
121 : // movements through menus
122 0 : domEventToFire.AssignLiteral("DOMMenuItemActive");
123 : }
124 : else {
125 : // Unhighlight the menu.
126 0 : mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
127 0 : domEventToFire.AssignLiteral("DOMMenuItemInactive");
128 : }
129 :
130 0 : nsCOMPtr<nsIDOMEvent> event;
131 0 : if (NS_SUCCEEDED(nsEventDispatcher::CreateEvent(mPresContext, nsnull,
132 : NS_LITERAL_STRING("Events"),
133 : getter_AddRefs(event)))) {
134 0 : event->InitEvent(domEventToFire, true, true);
135 :
136 0 : nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(event));
137 0 : privateEvent->SetTrusted(true);
138 :
139 : nsEventDispatcher::DispatchDOMEvent(mMenu, nsnull, event,
140 0 : mPresContext, nsnull);
141 : }
142 :
143 0 : return NS_OK;
144 : }
145 :
146 : private:
147 : nsCOMPtr<nsIContent> mMenu;
148 : nsRefPtr<nsPresContext> mPresContext;
149 : bool mIsActivate;
150 : };
151 :
152 : class nsMenuAttributeChangedEvent : public nsRunnable
153 0 : {
154 : public:
155 0 : nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr)
156 0 : : mFrame(aFrame), mAttr(aAttr)
157 : {
158 0 : }
159 :
160 0 : NS_IMETHOD Run()
161 : {
162 0 : nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame());
163 0 : NS_ENSURE_STATE(frame);
164 0 : if (mAttr == nsGkAtoms::checked) {
165 0 : frame->UpdateMenuSpecialState(frame->PresContext());
166 0 : } else if (mAttr == nsGkAtoms::acceltext) {
167 : // someone reset the accelText attribute,
168 : // so clear the bit that says *we* set it
169 0 : frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
170 0 : frame->BuildAcceleratorText(true);
171 : }
172 0 : else if (mAttr == nsGkAtoms::key) {
173 0 : frame->BuildAcceleratorText(true);
174 0 : } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) {
175 0 : frame->UpdateMenuType(frame->PresContext());
176 : }
177 0 : return NS_OK;
178 : }
179 : protected:
180 : nsWeakFrame mFrame;
181 : nsCOMPtr<nsIAtom> mAttr;
182 : };
183 :
184 : //
185 : // NS_NewMenuFrame and NS_NewMenuItemFrame
186 : //
187 : // Wrappers for creating a new menu popup container
188 : //
189 : nsIFrame*
190 0 : NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
191 : {
192 0 : nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext);
193 :
194 0 : if (it)
195 0 : it->SetIsMenu(true);
196 :
197 0 : return it;
198 : }
199 :
200 : nsIFrame*
201 0 : NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
202 : {
203 0 : nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext);
204 :
205 0 : if (it)
206 0 : it->SetIsMenu(false);
207 :
208 0 : return it;
209 : }
210 :
211 0 : NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame)
212 :
213 0 : NS_QUERYFRAME_HEAD(nsMenuFrame)
214 0 : NS_QUERYFRAME_ENTRY(nsMenuFrame)
215 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
216 :
217 0 : nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext):
218 : nsBoxFrame(aShell, aContext),
219 : mIsMenu(false),
220 : mChecked(false),
221 : mIgnoreAccelTextChange(false),
222 : mType(eMenuType_Normal),
223 : mMenuParent(nsnull),
224 0 : mBlinkState(0)
225 : {
226 0 : }
227 :
228 : void
229 0 : nsMenuFrame::SetParent(nsIFrame* aParent)
230 : {
231 0 : nsBoxFrame::SetParent(aParent);
232 0 : InitMenuParent(aParent);
233 0 : }
234 :
235 : void
236 0 : nsMenuFrame::InitMenuParent(nsIFrame* aParent)
237 : {
238 0 : while (aParent) {
239 0 : nsIAtom* type = aParent->GetType();
240 0 : if (type == nsGkAtoms::menuPopupFrame) {
241 0 : mMenuParent = static_cast<nsMenuPopupFrame *>(aParent);
242 0 : break;
243 : }
244 0 : else if (type == nsGkAtoms::menuBarFrame) {
245 0 : mMenuParent = static_cast<nsMenuBarFrame *>(aParent);
246 0 : break;
247 : }
248 0 : aParent = aParent->GetParent();
249 : }
250 0 : }
251 :
252 : class nsASyncMenuInitialization : public nsIReflowCallback
253 0 : {
254 : public:
255 0 : nsASyncMenuInitialization(nsIFrame* aFrame)
256 0 : : mWeakFrame(aFrame)
257 : {
258 0 : }
259 :
260 0 : virtual bool ReflowFinished()
261 : {
262 0 : bool shouldFlush = false;
263 0 : if (mWeakFrame.IsAlive()) {
264 0 : if (mWeakFrame.GetFrame()->GetType() == nsGkAtoms::menuFrame) {
265 0 : nsMenuFrame* menu = static_cast<nsMenuFrame*>(mWeakFrame.GetFrame());
266 0 : menu->UpdateMenuType(menu->PresContext());
267 0 : shouldFlush = true;
268 : }
269 : }
270 0 : delete this;
271 0 : return shouldFlush;
272 : }
273 :
274 0 : virtual void ReflowCallbackCanceled()
275 : {
276 0 : delete this;
277 0 : }
278 :
279 : nsWeakFrame mWeakFrame;
280 : };
281 :
282 : NS_IMETHODIMP
283 0 : nsMenuFrame::Init(nsIContent* aContent,
284 : nsIFrame* aParent,
285 : nsIFrame* aPrevInFlow)
286 : {
287 0 : nsresult rv = nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
288 :
289 : // Set up a mediator which can be used for callbacks on this frame.
290 0 : mTimerMediator = new nsMenuTimerMediator(this);
291 0 : if (NS_UNLIKELY(!mTimerMediator))
292 0 : return NS_ERROR_OUT_OF_MEMORY;
293 :
294 0 : InitMenuParent(aParent);
295 :
296 0 : BuildAcceleratorText(false);
297 0 : nsIReflowCallback* cb = new nsASyncMenuInitialization(this);
298 0 : NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY);
299 0 : PresContext()->PresShell()->PostReflowCallback(cb);
300 0 : return rv;
301 : }
302 :
303 : const nsFrameList&
304 0 : nsMenuFrame::GetChildList(ChildListID aListID) const
305 : {
306 0 : if (kPopupList == aListID) {
307 0 : nsFrameList* list = GetPopupList();
308 0 : return list ? *list : nsFrameList::EmptyList();
309 : }
310 0 : return nsBoxFrame::GetChildList(aListID);
311 : }
312 :
313 : void
314 0 : nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const
315 : {
316 0 : nsBoxFrame::GetChildLists(aLists);
317 0 : nsFrameList* list = GetPopupList();
318 0 : if (list) {
319 0 : list->AppendIfNonempty(aLists, kPopupList);
320 : }
321 0 : }
322 :
323 : nsMenuPopupFrame*
324 0 : nsMenuFrame::GetPopup()
325 : {
326 0 : nsFrameList* popupList = GetPopupList();
327 0 : return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) :
328 0 : nsnull;
329 : }
330 :
331 : nsFrameList*
332 0 : nsMenuFrame::GetPopupList() const
333 : {
334 0 : if (!HasPopup()) {
335 0 : return nsnull;
336 : }
337 : nsFrameList* prop =
338 0 : static_cast<nsFrameList*>(Properties().Get(PopupListProperty()));
339 0 : NS_ASSERTION(prop && prop->GetLength() == 1 &&
340 : prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame,
341 : "popup list should have exactly one nsMenuPopupFrame");
342 0 : return prop;
343 : }
344 :
345 : void
346 0 : nsMenuFrame::DestroyPopupList()
347 : {
348 0 : NS_ASSERTION(HasPopup(), "huh?");
349 : nsFrameList* prop =
350 0 : static_cast<nsFrameList*>(Properties().Remove(PopupListProperty()));
351 0 : NS_ASSERTION(prop && prop->IsEmpty(),
352 : "popup list must exist and be empty when destroying");
353 0 : RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
354 0 : delete prop;
355 0 : }
356 :
357 : void
358 0 : nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList)
359 : {
360 0 : for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
361 0 : if (e.get()->GetType() == nsGkAtoms::menuPopupFrame) {
362 : // Remove the frame from the list and store it in a nsFrameList* property.
363 0 : nsIFrame* popupFrame = e.get();
364 0 : aFrameList.RemoveFrame(popupFrame);
365 0 : nsFrameList* popupList = new nsFrameList(popupFrame, popupFrame);
366 0 : Properties().Set(PopupListProperty(), popupList);
367 0 : AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
368 0 : break;
369 : }
370 : }
371 0 : }
372 :
373 : NS_IMETHODIMP
374 0 : nsMenuFrame::SetInitialChildList(ChildListID aListID,
375 : nsFrameList& aChildList)
376 : {
377 0 : NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?");
378 0 : if (aListID == kPrincipalList || aListID == kPopupList) {
379 0 : SetPopupFrame(aChildList);
380 : }
381 0 : return nsBoxFrame::SetInitialChildList(aListID, aChildList);
382 : }
383 :
384 : void
385 0 : nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot)
386 : {
387 : // Kill our timer if one is active. This is not strictly necessary as
388 : // the pointer to this frame will be cleared from the mediator, but
389 : // this is done for added safety.
390 0 : if (mOpenTimer) {
391 0 : mOpenTimer->Cancel();
392 : }
393 :
394 0 : StopBlinking();
395 :
396 : // Null out the pointer to this frame in the mediator wrapper so that it
397 : // doesn't try to interact with a deallocated frame.
398 0 : mTimerMediator->ClearFrame();
399 :
400 : // if the menu content is just being hidden, it may be made visible again
401 : // later, so make sure to clear the highlighting.
402 0 : mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false);
403 :
404 : // are we our menu parent's current menu item?
405 0 : if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) {
406 : // yes; tell it that we're going away
407 0 : mMenuParent->CurrentMenuIsBeingDestroyed();
408 : }
409 :
410 0 : nsFrameList* popupList = GetPopupList();
411 0 : if (popupList) {
412 0 : popupList->DestroyFramesFrom(aDestructRoot);
413 0 : DestroyPopupList();
414 : }
415 :
416 0 : nsBoxFrame::DestroyFrom(aDestructRoot);
417 0 : }
418 :
419 : NS_IMETHODIMP
420 0 : nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
421 : const nsRect& aDirtyRect,
422 : const nsDisplayListSet& aLists)
423 : {
424 0 : if (!aBuilder->IsForEventDelivery())
425 0 : return nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
426 :
427 0 : nsDisplayListCollection set;
428 0 : nsresult rv = nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
429 0 : NS_ENSURE_SUCCESS(rv, rv);
430 :
431 0 : return WrapListsInRedirector(aBuilder, set, aLists);
432 : }
433 :
434 : NS_IMETHODIMP
435 0 : nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
436 : nsGUIEvent* aEvent,
437 : nsEventStatus* aEventStatus)
438 : {
439 0 : NS_ENSURE_ARG_POINTER(aEventStatus);
440 0 : if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
441 0 : (mMenuParent && mMenuParent->IsMenuLocked())) {
442 0 : return NS_OK;
443 : }
444 :
445 0 : nsWeakFrame weakFrame(this);
446 0 : if (*aEventStatus == nsEventStatus_eIgnore)
447 0 : *aEventStatus = nsEventStatus_eConsumeDoDefault;
448 :
449 0 : bool onmenu = IsOnMenu();
450 :
451 0 : if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) {
452 0 : nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent;
453 0 : PRUint32 keyCode = keyEvent->keyCode;
454 : #ifdef XP_MACOSX
455 : // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
456 : if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->isMeta) ||
457 : (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
458 : *aEventStatus = nsEventStatus_eConsumeNoDefault;
459 : OpenMenu(false);
460 : }
461 : #else
462 : // On other platforms, toggle menulist on unmodified F4 or Alt arrow
463 0 : if ((keyCode == NS_VK_F4 && !keyEvent->isAlt) ||
464 : ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->isAlt)) {
465 0 : *aEventStatus = nsEventStatus_eConsumeNoDefault;
466 0 : ToggleMenuState();
467 : }
468 : #endif
469 : }
470 0 : else if (aEvent->eventStructType == NS_MOUSE_EVENT &&
471 : aEvent->message == NS_MOUSE_BUTTON_DOWN &&
472 : static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton &&
473 0 : !IsDisabled() && IsMenu()) {
474 : // The menu item was selected. Bring up the menu.
475 : // We have children.
476 : // Don't prevent the default action here, since that will also cancel
477 : // potential drag starts.
478 0 : if (!mMenuParent || mMenuParent->IsMenuBar()) {
479 0 : ToggleMenuState();
480 : }
481 : else {
482 0 : if (!IsOpen()) {
483 0 : OpenMenu(false);
484 : }
485 : }
486 : }
487 0 : else if (
488 : #ifndef NSCONTEXTMENUISMOUSEUP
489 : (aEvent->eventStructType == NS_MOUSE_EVENT &&
490 : aEvent->message == NS_MOUSE_BUTTON_UP &&
491 : static_cast<nsMouseEvent*>(aEvent)->button ==
492 : nsMouseEvent::eRightButton) &&
493 : #else
494 : aEvent->message == NS_CONTEXTMENU &&
495 : #endif
496 0 : onmenu && !IsMenu() && !IsDisabled()) {
497 : // if this menu is a context menu it accepts right-clicks...fire away!
498 : // Make sure we cancel default processing of the context menu event so
499 : // that it doesn't bubble and get seen again by the popuplistener and show
500 : // another context menu.
501 : //
502 : // Furthermore (there's always more, isn't there?), on some platforms (win32
503 : // being one of them) we get the context menu event on a mouse up while
504 : // on others we get it on a mouse down. For the ones where we get it on a
505 : // mouse down, we must continue listening for the right button up event to
506 : // dismiss the menu.
507 0 : if (mMenuParent->IsContextMenu()) {
508 0 : *aEventStatus = nsEventStatus_eConsumeNoDefault;
509 0 : Execute(aEvent);
510 : }
511 : }
512 0 : else if (aEvent->eventStructType == NS_MOUSE_EVENT &&
513 : aEvent->message == NS_MOUSE_BUTTON_UP &&
514 : static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton &&
515 0 : !IsMenu() && !IsDisabled()) {
516 : // Execute the execute event handler.
517 0 : *aEventStatus = nsEventStatus_eConsumeNoDefault;
518 0 : Execute(aEvent);
519 : }
520 0 : else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) {
521 : // Kill our timer if one is active.
522 0 : if (mOpenTimer) {
523 0 : mOpenTimer->Cancel();
524 0 : mOpenTimer = nsnull;
525 : }
526 :
527 : // Deactivate the menu.
528 0 : if (mMenuParent) {
529 0 : bool onmenubar = mMenuParent->IsMenuBar();
530 0 : if (!(onmenubar && mMenuParent->IsActive())) {
531 0 : if (IsMenu() && !onmenubar && IsOpen()) {
532 : // Submenus don't get closed up immediately.
533 : }
534 0 : else if (this == mMenuParent->GetCurrentMenuItem()) {
535 0 : mMenuParent->ChangeMenuItem(nsnull, false);
536 : }
537 : }
538 : }
539 : }
540 0 : else if (aEvent->message == NS_MOUSE_MOVE &&
541 0 : (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) {
542 0 : if (gEatMouseMove) {
543 0 : gEatMouseMove = false;
544 0 : return NS_OK;
545 : }
546 :
547 : // Let the menu parent know we're the new item.
548 0 : mMenuParent->ChangeMenuItem(this, false);
549 0 : NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
550 0 : NS_ENSURE_TRUE(mMenuParent, NS_OK);
551 :
552 : // we need to check if we really became the current menu
553 : // item or not
554 0 : nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem();
555 0 : if (realCurrentItem != this) {
556 : // we didn't (presumably because a context menu was active)
557 0 : return NS_OK;
558 : }
559 :
560 : // Hovering over a menu in a popup should open it without a need for a click.
561 : // A timer is used so that it doesn't open if the user moves the mouse quickly
562 : // past the menu. This conditional check ensures that only menus have this
563 : // behaviour
564 0 : if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) {
565 : PRInt32 menuDelay =
566 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
567 :
568 : // We're a menu, we're built, we're closed, and no timer has been kicked off.
569 0 : mOpenTimer = do_CreateInstance("@mozilla.org/timer;1");
570 0 : mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT);
571 : }
572 : }
573 :
574 0 : return NS_OK;
575 : }
576 :
577 : void
578 0 : nsMenuFrame::ToggleMenuState()
579 : {
580 0 : if (IsOpen())
581 0 : CloseMenu(false);
582 : else
583 0 : OpenMenu(false);
584 0 : }
585 :
586 : void
587 0 : nsMenuFrame::PopupOpened()
588 : {
589 0 : nsWeakFrame weakFrame(this);
590 : mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
591 0 : NS_LITERAL_STRING("true"), true);
592 0 : if (!weakFrame.IsAlive())
593 : return;
594 :
595 0 : if (mMenuParent) {
596 0 : mMenuParent->SetActive(true);
597 : // Make sure the current menu which is being toggled on
598 : // the menubar is highlighted
599 0 : mMenuParent->SetCurrentMenuItem(this);
600 : }
601 : }
602 :
603 : void
604 0 : nsMenuFrame::PopupClosed(bool aDeselectMenu)
605 : {
606 0 : nsWeakFrame weakFrame(this);
607 : nsContentUtils::AddScriptRunner(
608 0 : new nsUnsetAttrRunnable(mContent, nsGkAtoms::open));
609 0 : if (!weakFrame.IsAlive())
610 : return;
611 :
612 : // if the popup is for a menu on a menubar, inform menubar to deactivate
613 0 : if (mMenuParent && mMenuParent->MenuClosed()) {
614 0 : if (aDeselectMenu) {
615 0 : SelectMenu(false);
616 : } else {
617 : // We are not deselecting the parent menu while closing the popup, so send
618 : // a DOMMenuItemActive event to the menu to indicate that the menu is
619 : // becoming active again.
620 0 : nsMenuFrame *current = mMenuParent->GetCurrentMenuItem();
621 0 : if (current) {
622 : nsCOMPtr<nsIRunnable> event =
623 0 : new nsMenuActivateEvent(current->GetContent(),
624 0 : PresContext(), true);
625 0 : NS_DispatchToCurrentThread(event);
626 : }
627 : }
628 : }
629 : }
630 :
631 : NS_IMETHODIMP
632 0 : nsMenuFrame::SelectMenu(bool aActivateFlag)
633 : {
634 0 : if (mContent) {
635 : // When a menu opens a submenu, the mouse will often be moved onto a
636 : // sibling before moving onto an item within the submenu, causing the
637 : // parent to become deselected. We need to ensure that the parent menu
638 : // is reselected when an item in the submenu is selected, so navigate up
639 : // from the item to its popup, and then to the popup above that.
640 0 : if (aActivateFlag) {
641 0 : nsIFrame* parent = GetParent();
642 0 : while (parent) {
643 0 : if (parent->GetType() == nsGkAtoms::menuPopupFrame) {
644 : // a menu is always the direct parent of a menupopup
645 0 : parent = parent->GetParent();
646 0 : if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
647 : // a popup however is not necessarily the direct parent of a menu
648 0 : nsIFrame* popupParent = parent->GetParent();
649 0 : while (popupParent) {
650 0 : if (popupParent->GetType() == nsGkAtoms::menuPopupFrame) {
651 0 : nsMenuPopupFrame* popup = static_cast<nsMenuPopupFrame *>(popupParent);
652 0 : popup->SetCurrentMenuItem(static_cast<nsMenuFrame *>(parent));
653 0 : break;
654 : }
655 0 : popupParent = popupParent->GetParent();
656 : }
657 : }
658 0 : break;
659 : }
660 0 : parent = parent->GetParent();
661 : }
662 : }
663 :
664 : // cancel the close timer if selecting a menu within the popup to be closed
665 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
666 0 : if (pm)
667 0 : pm->CancelMenuTimer(mMenuParent);
668 :
669 : nsCOMPtr<nsIRunnable> event =
670 0 : new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag);
671 0 : NS_DispatchToCurrentThread(event);
672 : }
673 :
674 0 : return NS_OK;
675 : }
676 :
677 : NS_IMETHODIMP
678 0 : nsMenuFrame::AttributeChanged(PRInt32 aNameSpaceID,
679 : nsIAtom* aAttribute,
680 : PRInt32 aModType)
681 : {
682 0 : if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) {
683 : // Reset the flag so that only one change is ignored.
684 0 : mIgnoreAccelTextChange = false;
685 0 : return NS_OK;
686 : }
687 :
688 0 : if (aAttribute == nsGkAtoms::checked ||
689 : aAttribute == nsGkAtoms::acceltext ||
690 : aAttribute == nsGkAtoms::key ||
691 : aAttribute == nsGkAtoms::type ||
692 : aAttribute == nsGkAtoms::name) {
693 : nsCOMPtr<nsIRunnable> event =
694 0 : new nsMenuAttributeChangedEvent(this, aAttribute);
695 0 : nsContentUtils::AddScriptRunner(event);
696 : }
697 0 : return NS_OK;
698 : }
699 :
700 : void
701 0 : nsMenuFrame::OpenMenu(bool aSelectFirstItem)
702 : {
703 0 : if (!mContent)
704 0 : return;
705 :
706 0 : gEatMouseMove = true;
707 :
708 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
709 0 : if (pm) {
710 0 : pm->KillMenuTimer();
711 : // This opens the menu asynchronously
712 0 : pm->ShowMenu(mContent, aSelectFirstItem, true);
713 : }
714 : }
715 :
716 : void
717 0 : nsMenuFrame::CloseMenu(bool aDeselectMenu)
718 : {
719 0 : gEatMouseMove = true;
720 :
721 : // Close the menu asynchronously
722 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
723 0 : if (pm && HasPopup())
724 0 : pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true);
725 0 : }
726 :
727 : bool
728 0 : nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways)
729 : {
730 : bool sizeToPopup;
731 0 : if (aContent->Tag() == nsGkAtoms::select)
732 0 : sizeToPopup = true;
733 : else {
734 0 : nsAutoString sizedToPopup;
735 0 : aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup);
736 0 : sizeToPopup = sizedToPopup.EqualsLiteral("always") ||
737 0 : (!aRequireAlways && sizedToPopup.EqualsLiteral("pref"));
738 : }
739 :
740 0 : return sizeToPopup;
741 : }
742 :
743 : nsSize
744 0 : nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
745 : {
746 0 : nsSize size = nsBoxFrame::GetMinSize(aBoxLayoutState);
747 0 : DISPLAY_MIN_SIZE(this, size);
748 :
749 0 : if (IsSizedToPopup(mContent, true))
750 0 : SizeToPopup(aBoxLayoutState, size);
751 :
752 : return size;
753 : }
754 :
755 : NS_IMETHODIMP
756 0 : nsMenuFrame::DoLayout(nsBoxLayoutState& aState)
757 : {
758 : // lay us out
759 0 : nsresult rv = nsBoxFrame::DoLayout(aState);
760 :
761 0 : nsMenuPopupFrame* popupFrame = GetPopup();
762 0 : if (popupFrame) {
763 0 : bool sizeToPopup = IsSizedToPopup(mContent, false);
764 0 : popupFrame->LayoutPopup(aState, this, sizeToPopup);
765 : }
766 :
767 0 : return rv;
768 : }
769 :
770 : #ifdef DEBUG_LAYOUT
771 : NS_IMETHODIMP
772 : nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug)
773 : {
774 : // see if our state matches the given debug state
775 : bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
776 : bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
777 :
778 : // if it doesn't then tell each child below us the new debug state
779 : if (debugChanged)
780 : {
781 : nsBoxFrame::SetDebug(aState, aDebug);
782 : nsMenuPopupFrame* popupFrame = GetPopup();
783 : if (popupFrame)
784 : SetDebug(aState, popupFrame, aDebug);
785 : }
786 :
787 : return NS_OK;
788 : }
789 :
790 : nsresult
791 : nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug)
792 : {
793 : if (!aList)
794 : return NS_OK;
795 :
796 : while (aList) {
797 : if (aList->IsBoxFrame())
798 : aList->SetDebug(aState, aDebug);
799 :
800 : aList = aList->GetNextSibling();
801 : }
802 :
803 : return NS_OK;
804 : }
805 : #endif
806 :
807 : //
808 : // Enter
809 : //
810 : // Called when the user hits the <Enter>/<Return> keys or presses the
811 : // shortcut key. If this is a leaf item, the item's action will be executed.
812 : // In either case, do nothing if the item is disabled.
813 : //
814 : nsMenuFrame*
815 0 : nsMenuFrame::Enter(nsGUIEvent *aEvent)
816 : {
817 0 : if (IsDisabled()) {
818 : #ifdef XP_WIN
819 : // behavior on Windows - close the popup chain
820 : if (mMenuParent) {
821 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
822 : if (pm) {
823 : nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
824 : if (popup)
825 : pm->HidePopup(popup->GetContent(), true, true, true);
826 : }
827 : }
828 : #endif // #ifdef XP_WIN
829 : // this menu item was disabled - exit
830 0 : return nsnull;
831 : }
832 :
833 0 : if (!IsOpen()) {
834 : // The enter key press applies to us.
835 0 : if (!IsMenu() && mMenuParent)
836 0 : Execute(aEvent); // Execute our event handler
837 : else
838 0 : return this;
839 : }
840 :
841 0 : return nsnull;
842 : }
843 :
844 : bool
845 0 : nsMenuFrame::IsOpen()
846 : {
847 0 : nsMenuPopupFrame* popupFrame = GetPopup();
848 0 : return popupFrame && popupFrame->IsOpen();
849 : }
850 :
851 : bool
852 0 : nsMenuFrame::IsMenu()
853 : {
854 0 : return mIsMenu;
855 : }
856 :
857 : nsMenuListType
858 0 : nsMenuFrame::GetParentMenuListType()
859 : {
860 0 : if (mMenuParent && mMenuParent->IsMenu()) {
861 0 : nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(mMenuParent);
862 0 : nsIFrame* parentMenu = popupFrame->GetParent();
863 0 : if (parentMenu) {
864 0 : nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
865 0 : if (menulist) {
866 0 : bool isEditable = false;
867 0 : menulist->GetEditable(&isEditable);
868 0 : return isEditable ? eEditableMenuList : eReadonlyMenuList;
869 : }
870 : }
871 : }
872 0 : return eNotMenuList;
873 : }
874 :
875 : nsresult
876 0 : nsMenuFrame::Notify(nsITimer* aTimer)
877 : {
878 : // Our timer has fired.
879 0 : if (aTimer == mOpenTimer.get()) {
880 0 : mOpenTimer = nsnull;
881 :
882 0 : if (!IsOpen() && mMenuParent) {
883 : // make sure we didn't open a context menu in the meantime
884 : // (i.e. the user right-clicked while hovering over a submenu).
885 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
886 0 : if (pm) {
887 0 : if ((!pm->HasContextMenu(nsnull) || mMenuParent->IsContextMenu()) &&
888 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
889 0 : nsGkAtoms::_true, eCaseMatters)) {
890 0 : OpenMenu(false);
891 : }
892 : }
893 : }
894 0 : } else if (aTimer == mBlinkTimer) {
895 0 : switch (mBlinkState++) {
896 : case 0:
897 0 : NS_ASSERTION(false, "Blink timer fired while not blinking");
898 0 : StopBlinking();
899 0 : break;
900 : case 1:
901 : {
902 : // Turn the highlight back on and wait for a while before closing the menu.
903 0 : nsWeakFrame weakFrame(this);
904 : mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
905 0 : NS_LITERAL_STRING("true"), true);
906 0 : if (weakFrame.IsAlive()) {
907 0 : aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
908 : }
909 : }
910 0 : break;
911 : default:
912 0 : if (mMenuParent) {
913 0 : mMenuParent->LockMenuUntilClosed(false);
914 : }
915 0 : PassMenuCommandEventToPopupManager();
916 0 : StopBlinking();
917 0 : break;
918 : }
919 : }
920 :
921 0 : return NS_OK;
922 : }
923 :
924 : bool
925 0 : nsMenuFrame::IsDisabled()
926 : {
927 : return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
928 0 : nsGkAtoms::_true, eCaseMatters);
929 : }
930 :
931 : void
932 0 : nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext)
933 : {
934 : static nsIContent::AttrValuesArray strings[] =
935 : {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nsnull};
936 0 : switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
937 0 : strings, eCaseMatters)) {
938 0 : case 0: mType = eMenuType_Checkbox; break;
939 : case 1:
940 0 : mType = eMenuType_Radio;
941 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName);
942 0 : break;
943 :
944 : default:
945 0 : if (mType != eMenuType_Normal) {
946 0 : nsWeakFrame weakFrame(this);
947 : mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
948 0 : true);
949 0 : ENSURE_TRUE(weakFrame.IsAlive());
950 : }
951 0 : mType = eMenuType_Normal;
952 0 : break;
953 : }
954 0 : UpdateMenuSpecialState(aPresContext);
955 : }
956 :
957 : /* update checked-ness for type="checkbox" and type="radio" */
958 : void
959 0 : nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext)
960 : {
961 : bool newChecked =
962 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
963 0 : nsGkAtoms::_true, eCaseMatters);
964 0 : if (newChecked == mChecked) {
965 : /* checked state didn't change */
966 :
967 0 : if (mType != eMenuType_Radio)
968 0 : return; // only Radio possibly cares about other kinds of change
969 :
970 0 : if (!mChecked || mGroupName.IsEmpty())
971 0 : return; // no interesting change
972 : } else {
973 0 : mChecked = newChecked;
974 0 : if (mType != eMenuType_Radio || !mChecked)
975 : /*
976 : * Unchecking something requires no further changes, and only
977 : * menuRadio has to do additional work when checked.
978 : */
979 0 : return;
980 : }
981 :
982 : /*
983 : * If we get this far, we're type=radio, and:
984 : * - our name= changed, or
985 : * - we went from checked="false" to checked="true"
986 : */
987 :
988 : /*
989 : * Behavioural note:
990 : * If we're checked and renamed _into_ an existing radio group, we are
991 : * made the new checked item, and we unselect the previous one.
992 : *
993 : * The only other reasonable behaviour would be to check for another selected
994 : * item in that group. If found, unselect ourselves, otherwise we're the
995 : * selected item. That, however, would be a lot more work, and I don't think
996 : * it's better at all.
997 : */
998 :
999 : /* walk siblings, looking for the other checked item with the same name */
1000 : // get the first sibling in this menu popup. This frame may be it, and if we're
1001 : // being called at creation time, this frame isn't yet in the parent's child list.
1002 : // All I'm saying is that this may fail, but it's most likely alright.
1003 0 : nsIFrame* sib = GetParent()->GetFirstPrincipalChild();
1004 :
1005 0 : while (sib) {
1006 0 : if (sib != this && sib->GetType() == nsGkAtoms::menuFrame) {
1007 0 : nsMenuFrame* menu = static_cast<nsMenuFrame*>(sib);
1008 0 : if (menu->GetMenuType() == eMenuType_Radio &&
1009 0 : menu->IsChecked() &&
1010 0 : (menu->GetRadioGroupName() == mGroupName)) {
1011 : /* uncheck the old item */
1012 0 : sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
1013 0 : true);
1014 : /* XXX in DEBUG, check to make sure that there aren't two checked items */
1015 0 : return;
1016 : }
1017 : }
1018 :
1019 0 : sib = sib->GetNextSibling();
1020 : }
1021 : }
1022 :
1023 : void
1024 0 : nsMenuFrame::BuildAcceleratorText(bool aNotify)
1025 : {
1026 0 : nsAutoString accelText;
1027 :
1028 0 : if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) {
1029 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText);
1030 0 : if (!accelText.IsEmpty())
1031 : return;
1032 : }
1033 : // accelText is definitely empty here.
1034 :
1035 : // Now we're going to compute the accelerator text, so remember that we did.
1036 0 : AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
1037 :
1038 : // If anything below fails, just leave the accelerator text blank.
1039 0 : nsWeakFrame weakFrame(this);
1040 0 : mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify);
1041 0 : ENSURE_TRUE(weakFrame.IsAlive());
1042 :
1043 : // See if we have a key node and use that instead.
1044 0 : nsAutoString keyValue;
1045 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
1046 0 : if (keyValue.IsEmpty())
1047 : return;
1048 :
1049 : // Turn the document into a DOM document so we can use getElementById
1050 0 : nsIDocument *document = mContent->GetDocument();
1051 0 : if (!document)
1052 : return;
1053 :
1054 0 : nsIContent *keyElement = document->GetElementById(keyValue);
1055 0 : if (!keyElement) {
1056 : #ifdef DEBUG
1057 0 : nsAutoString label;
1058 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
1059 0 : nsAutoString msg = NS_LITERAL_STRING("Key '") +
1060 0 : keyValue +
1061 0 : NS_LITERAL_STRING("' of menu item '") +
1062 0 : label +
1063 0 : NS_LITERAL_STRING("' could not be found");
1064 0 : NS_WARNING(NS_ConvertUTF16toUTF8(msg).get());
1065 : #endif
1066 : return;
1067 : }
1068 :
1069 : // get the string to display as accelerator text
1070 : // check the key element's attributes in this order:
1071 : // |keytext|, |key|, |keycode|
1072 0 : nsAutoString accelString;
1073 0 : keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString);
1074 :
1075 0 : if (accelString.IsEmpty()) {
1076 0 : keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString);
1077 :
1078 0 : if (!accelString.IsEmpty()) {
1079 0 : ToUpperCase(accelString);
1080 : } else {
1081 0 : nsAutoString keyCode;
1082 0 : keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode);
1083 0 : ToUpperCase(keyCode);
1084 :
1085 : nsresult rv;
1086 : nsCOMPtr<nsIStringBundleService> bundleService =
1087 0 : mozilla::services::GetStringBundleService();
1088 0 : if (bundleService) {
1089 0 : nsCOMPtr<nsIStringBundle> bundle;
1090 0 : rv = bundleService->CreateBundle("chrome://global/locale/keys.properties",
1091 0 : getter_AddRefs(bundle));
1092 :
1093 0 : if (NS_SUCCEEDED(rv) && bundle) {
1094 0 : nsXPIDLString keyName;
1095 0 : rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName));
1096 0 : if (keyName)
1097 0 : accelString = keyName;
1098 : }
1099 : }
1100 :
1101 : // nothing usable found, bail
1102 0 : if (accelString.IsEmpty())
1103 : return;
1104 : }
1105 : }
1106 :
1107 : static PRInt32 accelKey = 0;
1108 :
1109 0 : if (!accelKey)
1110 : {
1111 : // Compiled-in defaults, in case we can't get LookAndFeel --
1112 : // command for mac, control for all other platforms.
1113 : #ifdef XP_MACOSX
1114 : accelKey = nsIDOMKeyEvent::DOM_VK_META;
1115 : #else
1116 0 : accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL;
1117 : #endif
1118 :
1119 : // Get the accelerator key value from prefs, overriding the default:
1120 0 : accelKey = Preferences::GetInt("ui.key.accelKey", accelKey);
1121 : }
1122 :
1123 0 : nsAutoString modifiers;
1124 0 : keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
1125 :
1126 0 : char* str = ToNewCString(modifiers);
1127 : char* newStr;
1128 0 : char* token = nsCRT::strtok(str, ", \t", &newStr);
1129 :
1130 0 : nsAutoString shiftText;
1131 0 : nsAutoString altText;
1132 0 : nsAutoString metaText;
1133 0 : nsAutoString controlText;
1134 0 : nsAutoString modifierSeparator;
1135 :
1136 0 : nsContentUtils::GetShiftText(shiftText);
1137 0 : nsContentUtils::GetAltText(altText);
1138 0 : nsContentUtils::GetMetaText(metaText);
1139 0 : nsContentUtils::GetControlText(controlText);
1140 0 : nsContentUtils::GetModifierSeparatorText(modifierSeparator);
1141 :
1142 0 : while (token) {
1143 :
1144 0 : if (PL_strcmp(token, "shift") == 0)
1145 0 : accelText += shiftText;
1146 0 : else if (PL_strcmp(token, "alt") == 0)
1147 0 : accelText += altText;
1148 0 : else if (PL_strcmp(token, "meta") == 0)
1149 0 : accelText += metaText;
1150 0 : else if (PL_strcmp(token, "control") == 0)
1151 0 : accelText += controlText;
1152 0 : else if (PL_strcmp(token, "accel") == 0) {
1153 0 : switch (accelKey)
1154 : {
1155 : case nsIDOMKeyEvent::DOM_VK_META:
1156 0 : accelText += metaText;
1157 0 : break;
1158 :
1159 : case nsIDOMKeyEvent::DOM_VK_ALT:
1160 0 : accelText += altText;
1161 0 : break;
1162 :
1163 : case nsIDOMKeyEvent::DOM_VK_CONTROL:
1164 : default:
1165 0 : accelText += controlText;
1166 0 : break;
1167 : }
1168 : }
1169 :
1170 0 : accelText += modifierSeparator;
1171 :
1172 0 : token = nsCRT::strtok(newStr, ", \t", &newStr);
1173 : }
1174 :
1175 0 : nsMemory::Free(str);
1176 :
1177 0 : accelText += accelString;
1178 :
1179 0 : mIgnoreAccelTextChange = true;
1180 0 : mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify);
1181 0 : ENSURE_TRUE(weakFrame.IsAlive());
1182 :
1183 0 : mIgnoreAccelTextChange = false;
1184 : }
1185 :
1186 : void
1187 0 : nsMenuFrame::Execute(nsGUIEvent *aEvent)
1188 : {
1189 : // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
1190 0 : bool needToFlipChecked = false;
1191 0 : if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
1192 : needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
1193 0 : nsGkAtoms::_false, eCaseMatters);
1194 : }
1195 :
1196 0 : nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
1197 0 : if (sound)
1198 0 : sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
1199 :
1200 0 : StartBlinking(aEvent, needToFlipChecked);
1201 0 : }
1202 :
1203 : bool
1204 0 : nsMenuFrame::ShouldBlink()
1205 : {
1206 : PRInt32 shouldBlink =
1207 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0);
1208 0 : if (!shouldBlink)
1209 0 : return false;
1210 :
1211 : // Don't blink in editable menulists.
1212 0 : if (GetParentMenuListType() == eEditableMenuList)
1213 0 : return false;
1214 :
1215 0 : return true;
1216 : }
1217 :
1218 : void
1219 0 : nsMenuFrame::StartBlinking(nsGUIEvent *aEvent, bool aFlipChecked)
1220 : {
1221 0 : StopBlinking();
1222 0 : CreateMenuCommandEvent(aEvent, aFlipChecked);
1223 :
1224 0 : if (!ShouldBlink()) {
1225 0 : PassMenuCommandEventToPopupManager();
1226 0 : return;
1227 : }
1228 :
1229 : // Blink off.
1230 0 : nsWeakFrame weakFrame(this);
1231 0 : mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
1232 0 : if (!weakFrame.IsAlive())
1233 : return;
1234 :
1235 0 : if (mMenuParent) {
1236 : // Make this menu ignore events from now on.
1237 0 : mMenuParent->LockMenuUntilClosed(true);
1238 : }
1239 :
1240 : // Set up a timer to blink back on.
1241 0 : mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1");
1242 0 : mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
1243 0 : mBlinkState = 1;
1244 : }
1245 :
1246 : void
1247 0 : nsMenuFrame::StopBlinking()
1248 : {
1249 0 : mBlinkState = 0;
1250 0 : if (mBlinkTimer) {
1251 0 : mBlinkTimer->Cancel();
1252 0 : mBlinkTimer = nsnull;
1253 : }
1254 0 : mDelayedMenuCommandEvent = nsnull;
1255 0 : }
1256 :
1257 : void
1258 0 : nsMenuFrame::CreateMenuCommandEvent(nsGUIEvent *aEvent, bool aFlipChecked)
1259 : {
1260 : // Create a trusted event if the triggering event was trusted, or if
1261 : // we're called from chrome code (since at least one of our caller
1262 : // passes in a null event).
1263 : bool isTrusted = aEvent ? NS_IS_TRUSTED_EVENT(aEvent) :
1264 0 : nsContentUtils::IsCallerChrome();
1265 :
1266 0 : bool shift = false, control = false, alt = false, meta = false;
1267 0 : if (aEvent && (aEvent->eventStructType == NS_MOUSE_EVENT ||
1268 : aEvent->eventStructType == NS_KEY_EVENT)) {
1269 0 : shift = static_cast<nsInputEvent *>(aEvent)->isShift;
1270 0 : control = static_cast<nsInputEvent *>(aEvent)->isControl;
1271 0 : alt = static_cast<nsInputEvent *>(aEvent)->isAlt;
1272 0 : meta = static_cast<nsInputEvent *>(aEvent)->isMeta;
1273 : }
1274 :
1275 : // Because the command event is firing asynchronously, a flag is needed to
1276 : // indicate whether user input is being handled. This ensures that a popup
1277 : // window won't get blocked.
1278 0 : bool userinput = nsEventStateManager::IsHandlingUserInput();
1279 :
1280 : mDelayedMenuCommandEvent =
1281 : new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta,
1282 0 : userinput, aFlipChecked);
1283 0 : }
1284 :
1285 : void
1286 0 : nsMenuFrame::PassMenuCommandEventToPopupManager()
1287 : {
1288 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1289 0 : if (pm && mMenuParent && mDelayedMenuCommandEvent) {
1290 0 : pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent);
1291 : }
1292 0 : mDelayedMenuCommandEvent = nsnull;
1293 0 : }
1294 :
1295 : NS_IMETHODIMP
1296 0 : nsMenuFrame::RemoveFrame(ChildListID aListID,
1297 : nsIFrame* aOldFrame)
1298 : {
1299 0 : nsFrameList* popupList = GetPopupList();
1300 0 : if (popupList && popupList->DestroyFrameIfPresent(aOldFrame)) {
1301 0 : DestroyPopupList();
1302 0 : PresContext()->PresShell()->
1303 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1304 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1305 0 : return NS_OK;
1306 : }
1307 0 : return nsBoxFrame::RemoveFrame(aListID, aOldFrame);
1308 : }
1309 :
1310 : NS_IMETHODIMP
1311 0 : nsMenuFrame::InsertFrames(ChildListID aListID,
1312 : nsIFrame* aPrevFrame,
1313 : nsFrameList& aFrameList)
1314 : {
1315 0 : if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
1316 0 : SetPopupFrame(aFrameList);
1317 0 : if (HasPopup()) {
1318 : #ifdef DEBUG_LAYOUT
1319 : nsBoxLayoutState state(PresContext());
1320 : SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
1321 : #endif
1322 :
1323 0 : PresContext()->PresShell()->
1324 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1325 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1326 : }
1327 : }
1328 :
1329 0 : if (aFrameList.IsEmpty())
1330 0 : return NS_OK;
1331 :
1332 0 : if (NS_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) {
1333 0 : aPrevFrame = nsnull;
1334 : }
1335 :
1336 0 : return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1337 : }
1338 :
1339 : NS_IMETHODIMP
1340 0 : nsMenuFrame::AppendFrames(ChildListID aListID,
1341 : nsFrameList& aFrameList)
1342 : {
1343 0 : if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
1344 0 : SetPopupFrame(aFrameList);
1345 0 : if (HasPopup()) {
1346 :
1347 : #ifdef DEBUG_LAYOUT
1348 : nsBoxLayoutState state(PresContext());
1349 : SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
1350 : #endif
1351 0 : PresContext()->PresShell()->
1352 : FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1353 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
1354 : }
1355 : }
1356 :
1357 0 : if (aFrameList.IsEmpty())
1358 0 : return NS_OK;
1359 :
1360 0 : return nsBoxFrame::AppendFrames(aListID, aFrameList);
1361 : }
1362 :
1363 : bool
1364 0 : nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize)
1365 : {
1366 0 : if (!IsCollapsed()) {
1367 : bool widthSet, heightSet;
1368 0 : nsSize tmpSize(-1, 0);
1369 0 : nsIBox::AddCSSPrefSize(this, tmpSize, widthSet, heightSet);
1370 0 : if (!widthSet && GetFlex(aState) == 0) {
1371 0 : nsMenuPopupFrame* popupFrame = GetPopup();
1372 0 : if (!popupFrame)
1373 0 : return false;
1374 0 : tmpSize = popupFrame->GetPrefSize(aState);
1375 :
1376 : // Produce a size such that:
1377 : // (1) the menu and its popup can be the same width
1378 : // (2) there's enough room in the menu for the content and its
1379 : // border-padding
1380 : // (3) there's enough room in the popup for the content and its
1381 : // scrollbar
1382 0 : nsMargin borderPadding;
1383 0 : GetBorderAndPadding(borderPadding);
1384 :
1385 : // if there is a scroll frame, add the desired width of the scrollbar as well
1386 0 : nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild());
1387 0 : nscoord scrollbarWidth = 0;
1388 0 : if (scrollFrame) {
1389 : scrollbarWidth =
1390 0 : scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight();
1391 : }
1392 :
1393 : aSize.width =
1394 0 : tmpSize.width + NS_MAX(borderPadding.LeftRight(), scrollbarWidth);
1395 :
1396 0 : return true;
1397 : }
1398 : }
1399 :
1400 0 : return false;
1401 : }
1402 :
1403 : nsSize
1404 0 : nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState)
1405 : {
1406 0 : nsSize size = nsBoxFrame::GetPrefSize(aState);
1407 0 : DISPLAY_PREF_SIZE(this, size);
1408 :
1409 : // If we are using sizetopopup="always" then
1410 : // nsBoxFrame will already have enforced the minimum size
1411 0 : if (!IsSizedToPopup(mContent, true) &&
1412 0 : IsSizedToPopup(mContent, false) &&
1413 0 : SizeToPopup(aState, size)) {
1414 : // We now need to ensure that size is within the min - max range.
1415 0 : nsSize minSize = nsBoxFrame::GetMinSize(aState);
1416 0 : nsSize maxSize = GetMaxSize(aState);
1417 0 : size = BoundsCheck(minSize, size, maxSize);
1418 : }
1419 :
1420 : return size;
1421 : }
1422 :
1423 : NS_IMETHODIMP
1424 0 : nsMenuFrame::GetActiveChild(nsIDOMElement** aResult)
1425 : {
1426 0 : nsMenuPopupFrame* popupFrame = GetPopup();
1427 0 : if (!popupFrame)
1428 0 : return NS_ERROR_FAILURE;
1429 :
1430 0 : nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem();
1431 0 : if (!menuFrame) {
1432 0 : *aResult = nsnull;
1433 : }
1434 : else {
1435 0 : nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent()));
1436 0 : *aResult = elt;
1437 0 : NS_IF_ADDREF(*aResult);
1438 : }
1439 :
1440 0 : return NS_OK;
1441 : }
1442 :
1443 : NS_IMETHODIMP
1444 0 : nsMenuFrame::SetActiveChild(nsIDOMElement* aChild)
1445 : {
1446 0 : nsMenuPopupFrame* popupFrame = GetPopup();
1447 0 : if (!popupFrame)
1448 0 : return NS_ERROR_FAILURE;
1449 :
1450 0 : if (!aChild) {
1451 : // Remove the current selection
1452 0 : popupFrame->ChangeMenuItem(nsnull, false);
1453 0 : return NS_OK;
1454 : }
1455 :
1456 0 : nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
1457 :
1458 0 : nsIFrame* kid = child->GetPrimaryFrame();
1459 0 : if (kid && kid->GetType() == nsGkAtoms::menuFrame)
1460 0 : popupFrame->ChangeMenuItem(static_cast<nsMenuFrame *>(kid), false);
1461 0 : return NS_OK;
1462 : }
1463 :
1464 0 : nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame()
1465 : {
1466 0 : nsMenuPopupFrame* popupFrame = GetPopup();
1467 0 : if (!popupFrame)
1468 0 : return nsnull;
1469 0 : nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild();
1470 0 : if (childFrame)
1471 0 : return popupFrame->GetScrollFrame(childFrame);
1472 0 : return nsnull;
1473 : }
1474 :
1475 : // nsMenuTimerMediator implementation.
1476 0 : NS_IMPL_ISUPPORTS1(nsMenuTimerMediator, nsITimerCallback)
1477 :
1478 : /**
1479 : * Constructs a wrapper around an nsMenuFrame.
1480 : * @param aFrame nsMenuFrame to create a wrapper around.
1481 : */
1482 0 : nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) :
1483 0 : mFrame(aFrame)
1484 : {
1485 0 : NS_ASSERTION(mFrame, "Must have frame");
1486 0 : }
1487 :
1488 0 : nsMenuTimerMediator::~nsMenuTimerMediator()
1489 : {
1490 0 : }
1491 :
1492 : /**
1493 : * Delegates the notification to the contained frame if it has not been destroyed.
1494 : * @param aTimer Timer which initiated the callback.
1495 : * @return NS_ERROR_FAILURE if the frame has been destroyed.
1496 : */
1497 0 : NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer)
1498 : {
1499 0 : if (!mFrame)
1500 0 : return NS_ERROR_FAILURE;
1501 :
1502 0 : return mFrame->Notify(aTimer);
1503 : }
1504 :
1505 : /**
1506 : * Clear the pointer to the contained nsMenuFrame. This should be called
1507 : * when the contained nsMenuFrame is destroyed.
1508 : */
1509 0 : void nsMenuTimerMediator::ClearFrame()
1510 : {
1511 0 : mFrame = nsnull;
1512 0 : }
|