1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
24 * Original Author: David W. Hyatt (hyatt@netscape.com)
25 * Mike Pinkerton (pinkerton@netscape.com)
26 * Dean Tessman <dean_tessman@hotmail.com>
27 * Ben Goodger <ben@netscape.com>
29 * Alternatively, the contents of this file may be used under the terms of
30 * either of the GNU General Public License Version 2 or later (the "GPL"),
31 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
44 #include "nsMenuPopupFrame.h"
45 #include "nsGkAtoms.h"
46 #include "nsIContent.h"
49 #include "nsPresContext.h"
50 #include "nsStyleContext.h"
51 #include "nsCSSRendering.h"
52 #include "nsINameSpaceManager.h"
53 #include "nsIViewManager.h"
54 #include "nsWidgetsCID.h"
55 #include "nsMenuFrame.h"
56 #include "nsMenuBarFrame.h"
57 #include "nsPopupSetFrame.h"
58 #include "nsEventDispatcher.h"
59 #include "nsPIDOMWindow.h"
60 #include "nsIDOMScreen.h"
61 #include "nsIPresShell.h"
62 #include "nsFrameManager.h"
63 #include "nsIDocument.h"
64 #include "nsIDeviceContext.h"
66 #include "nsILookAndFeel.h"
67 #include "nsIComponentManager.h"
68 #include "nsBoxLayoutState.h"
69 #include "nsIScrollableView.h"
70 #include "nsIScrollableFrame.h"
71 #include "nsGUIEvent.h"
72 #include "nsIRootBox.h"
73 #include "nsIDocShellTreeItem.h"
74 #include "nsReadableUtils.h"
75 #include "nsUnicharUtils.h"
76 #include "nsLayoutUtils.h"
77 #include "nsContentUtils.h"
78 #include "nsCSSFrameConstructor.h"
79 #include "nsIEventStateManager.h"
80 #include "nsIBoxLayout.h"
81 #include "nsIPopupBoxObject.h"
82 #include "nsIReflowCallback.h"
83 #include "nsBindingManager.h"
84 #include "nsIDocShellTreeOwner.h"
85 #include "nsIBaseWindow.h"
87 #include "nsIRootBox.h"
89 PRInt8
nsMenuPopupFrame::sDefaultLevelParent
= -1;
91 // NS_NewMenuPopupFrame
93 // Wrapper for creating a new menu popup container
96 NS_NewMenuPopupFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
98 return new (aPresShell
) nsMenuPopupFrame (aPresShell
, aContext
);
102 // nsMenuPopupFrame ctor
104 nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell
* aShell
, nsStyleContext
* aContext
)
105 :nsBoxFrame(aShell
, aContext
),
106 mCurrentMenu(nsnull
),
107 mPopupAlignment(POPUPALIGNMENT_NONE
),
108 mPopupAnchor(POPUPALIGNMENT_NONE
),
109 mPopupType(ePopupTypePanel
),
110 mPopupState(ePopupClosed
),
111 mIsOpenChanged(PR_FALSE
),
112 mIsContextMenu(PR_FALSE
),
113 mAdjustOffsetForContextMenu(PR_FALSE
),
114 mGeneratedChildren(PR_FALSE
),
115 mMenuCanOverlapOSBar(PR_FALSE
),
116 mShouldAutoPosition(PR_TRUE
),
117 mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT
),
118 mInContentShell(PR_TRUE
),
121 if (sDefaultLevelParent
>= 0)
123 sDefaultLevelParent
=
124 nsContentUtils::GetBoolPref("ui.panel.default_level_parent", PR_FALSE
);
129 nsMenuPopupFrame::Init(nsIContent
* aContent
,
131 nsIFrame
* aPrevInFlow
)
133 nsresult rv
= nsBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
134 NS_ENSURE_SUCCESS(rv
, rv
);
136 nsPresContext
* presContext
= PresContext();
138 // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
141 presContext
->LookAndFeel()->
142 GetMetric(nsILookAndFeel::eMetric_MenusCanOverlapOSBar
, tempBool
);
143 mMenuCanOverlapOSBar
= tempBool
;
145 rv
= CreateViewForFrame(presContext
, this, GetStyleContext(), PR_TRUE
, PR_TRUE
);
146 NS_ENSURE_SUCCESS(rv
, rv
);
148 // XXX Hack. The popup's view should float above all other views,
149 // so we use the nsIView::SetFloating() to tell the view manager
150 // about that constraint.
151 nsIView
* ourView
= GetView();
152 nsIViewManager
* viewManager
= ourView
->GetViewManager();
153 viewManager
->SetViewFloating(ourView
, PR_TRUE
);
155 mPopupType
= ePopupTypePanel
;
156 nsIDocument
* doc
= aContent
->GetOwnerDoc();
159 nsCOMPtr
<nsIAtom
> tag
= doc
->BindingManager()->ResolveTag(aContent
, &namespaceID
);
160 if (namespaceID
== kNameSpaceID_XUL
) {
161 if (tag
== nsGkAtoms::menupopup
|| tag
== nsGkAtoms::popup
)
162 mPopupType
= ePopupTypeMenu
;
163 else if (tag
== nsGkAtoms::tooltip
)
164 mPopupType
= ePopupTypeTooltip
;
168 nsCOMPtr
<nsISupports
> cont
= PresContext()->GetContainer();
169 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= do_QueryInterface(cont
);
171 if (dsti
&& NS_SUCCEEDED(dsti
->GetItemType(&type
)) &&
172 type
== nsIDocShellTreeItem::typeChrome
)
173 mInContentShell
= PR_FALSE
;
175 // To improve performance, create the widget for the popup only if it is not
176 // a leaf. Leaf popups such as menus will create their widgets later when
178 if (!IsLeaf() && !ourView
->HasWidget()) {
179 CreateWidgetForView(ourView
);
182 if (aContent
->NodeInfo()->Equals(nsGkAtoms::tooltip
, kNameSpaceID_XUL
) &&
183 aContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::_default
,
184 nsGkAtoms::_true
, eIgnoreCase
)) {
185 nsIRootBox
* rootBox
=
186 nsIRootBox::GetRootBox(PresContext()->GetPresShell());
188 rootBox
->SetDefaultTooltip(aContent
);
196 nsMenuPopupFrame::IsNoAutoHide()
198 // Panels with noautohide="true" don't hide when the mouse is clicked
199 // outside of them, or when another application is made active. Non-autohide
200 // panels cannot be used in content windows.
201 return (!mInContentShell
&& mPopupType
== ePopupTypePanel
&&
202 mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::noautohide
,
203 nsGkAtoms::_true
, eIgnoreCase
));
207 nsMenuPopupFrame::IsTopMost()
209 // If this panel is not a panel, this is always a top-most popup
210 if (mPopupType
!= ePopupTypePanel
)
213 // If this panel is a noautohide panel, it should appear just above the parent
218 // Otherwise, check the topmost attribute.
219 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::level
,
220 nsGkAtoms::top
, eIgnoreCase
))
223 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::level
,
224 nsGkAtoms::parent
, eIgnoreCase
))
227 // Otherwise, the result depends on the platform.
228 return sDefaultLevelParent
? PR_TRUE
: PR_FALSE
;
232 nsMenuPopupFrame::EnsureWidget()
234 nsIView
* ourView
= GetView();
235 if (!ourView
->HasWidget()) {
236 NS_ASSERTION(!mGeneratedChildren
&& !GetFirstChild(nsnull
),
237 "Creating widget for MenuPopupFrame with children");
238 CreateWidgetForView(ourView
);
243 nsMenuPopupFrame::CreateWidgetForView(nsIView
* aView
)
245 // Create a widget for ourselves.
246 nsWidgetInitData widgetData
;
247 widgetData
.mWindowType
= eWindowType_popup
;
248 widgetData
.mBorderStyle
= eBorderStyle_default
;
249 widgetData
.clipSiblings
= PR_TRUE
;
250 widgetData
.mPopupHint
= mPopupType
;
252 nsTransparencyMode mode
= nsLayoutUtils::GetFrameTransparency(this);
253 PRBool viewHasTransparentContent
= !mInContentShell
&&
254 (eTransparencyTransparent
==
256 nsIContent
* parentContent
= GetContent()->GetParent();
257 nsIAtom
*tag
= nsnull
;
259 tag
= parentContent
->Tag();
260 widgetData
.mDropShadow
= !(viewHasTransparentContent
|| tag
== nsGkAtoms::menulist
);
262 // panels which are not topmost need a parent widget. This allows them to
263 // always appear in front of the parent window but behind other windows that
264 // should be in front of it.
265 nsCOMPtr
<nsIWidget
> parentWidget
;
267 nsCOMPtr
<nsISupports
> cont
= PresContext()->GetContainer();
268 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= do_QueryInterface(cont
);
270 return NS_ERROR_FAILURE
;
272 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
273 dsti
->GetTreeOwner(getter_AddRefs(treeOwner
));
274 if (!treeOwner
) return NS_ERROR_FAILURE
;
276 nsCOMPtr
<nsIBaseWindow
> baseWindow(do_QueryInterface(treeOwner
));
278 baseWindow
->GetMainWidget(getter_AddRefs(parentWidget
));
281 #if defined(XP_MACOSX) || defined(XP_BEOS)
282 static NS_DEFINE_IID(kCPopupCID
, NS_POPUP_CID
);
283 aView
->CreateWidget(kCPopupCID
, &widgetData
, nsnull
, PR_TRUE
, PR_TRUE
,
284 eContentTypeUI
, parentWidget
);
286 static NS_DEFINE_IID(kCChildCID
, NS_CHILD_CID
);
287 aView
->CreateWidget(kCChildCID
, &widgetData
, nsnull
, PR_TRUE
, PR_TRUE
,
288 eContentTypeInherit
, parentWidget
);
290 nsIWidget
* widget
= aView
->GetWidget();
291 widget
->SetTransparencyMode(mode
);
292 widget
->SetWindowShadowStyle(GetStyleUIReset()->mWindowShadow
);
296 // this class is used for dispatching popupshowing events asynchronously.
297 class nsXULPopupShownEvent
: public nsRunnable
300 nsXULPopupShownEvent(nsIContent
*aPopup
, nsPresContext
* aPresContext
)
301 : mPopup(aPopup
), mPresContext(aPresContext
)
307 nsMouseEvent
event(PR_TRUE
, NS_XUL_POPUP_SHOWN
, nsnull
, nsMouseEvent::eReal
);
308 return nsEventDispatcher::Dispatch(mPopup
, mPresContext
, &event
);
312 nsCOMPtr
<nsIContent
> mPopup
;
313 nsRefPtr
<nsPresContext
> mPresContext
;
317 nsMenuPopupFrame::SetInitialChildList(nsIAtom
* aListName
,
318 nsIFrame
* aChildList
)
320 // unless the list is empty, indicate that children have been generated.
322 mGeneratedChildren
= PR_TRUE
;
323 return nsBoxFrame::SetInitialChildList(aListName
, aChildList
);
327 nsMenuPopupFrame::IsLeaf() const
329 if (mGeneratedChildren
)
332 if (mPopupType
!= ePopupTypeMenu
) {
333 // any panel with a type attribute, such as the autocomplete popup,
334 // is always generated right away.
335 return !mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::type
);
338 // menu popups generate their child frames lazily only when opened, so
339 // behave like a leaf frame. However, generate child frames normally if
340 // the parent menu has a sizetopopup attribute. In this case the size of
341 // the parent menu is dependant on the size of the popup, so the frames
342 // need to exist in order to calculate this size.
343 nsIContent
* parentContent
= mContent
->GetParent();
344 return (parentContent
&&
345 !parentContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::sizetopopup
));
349 nsMenuPopupFrame::SetPreferredBounds(nsBoxLayoutState
& aState
,
352 nsBox::SetBounds(aState
, aRect
, PR_FALSE
);
353 mPrefSize
= aRect
.Size();
357 nsMenuPopupFrame::AdjustView()
359 if ((mPopupState
== ePopupOpen
|| mPopupState
== ePopupOpenAndVisible
) &&
360 mGeneratedChildren
) {
361 // if the popup has just opened, make sure the scrolled window is at 0,0
362 if (mIsOpenChanged
) {
363 nsIBox
* child
= GetChildBox();
364 nsIScrollableFrame
*scrollframe
= do_QueryFrame(child
);
366 scrollframe
->ScrollTo(nsPoint(0,0));
369 nsIView
* view
= GetView();
370 nsIViewManager
* viewManager
= view
->GetViewManager();
371 nsRect rect
= GetRect();
373 viewManager
->ResizeView(view
, rect
);
374 viewManager
->SetViewVisibility(view
, nsViewVisibility_kShow
);
375 mPopupState
= ePopupOpenAndVisible
;
377 nsPresContext
* pc
= PresContext();
378 nsContainerFrame::SyncFrameViewProperties(pc
, this, nsnull
, view
, 0);
380 // fire popupshown event when the state has changed
381 if (mIsOpenChanged
) {
382 mIsOpenChanged
= PR_FALSE
;
383 nsCOMPtr
<nsIRunnable
> event
= new nsXULPopupShownEvent(GetContent(), pc
);
384 NS_DispatchToCurrentThread(event
);
390 nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString
& aAnchor
,
391 const nsAString
& aAlign
)
393 if (aAnchor
.EqualsLiteral("topleft"))
394 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
395 else if (aAnchor
.EqualsLiteral("topright"))
396 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
397 else if (aAnchor
.EqualsLiteral("bottomleft"))
398 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
399 else if (aAnchor
.EqualsLiteral("bottomright"))
400 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
402 mPopupAnchor
= POPUPALIGNMENT_NONE
;
404 if (aAlign
.EqualsLiteral("topleft"))
405 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
406 else if (aAlign
.EqualsLiteral("topright"))
407 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
408 else if (aAlign
.EqualsLiteral("bottomleft"))
409 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
410 else if (aAlign
.EqualsLiteral("bottomright"))
411 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
413 mPopupAlignment
= POPUPALIGNMENT_NONE
;
417 nsMenuPopupFrame::InitializePopup(nsIContent
* aAnchorContent
,
418 const nsAString
& aPosition
,
419 PRInt32 aXPos
, PRInt32 aYPos
,
420 PRBool aAttributesOverride
)
424 mPopupState
= ePopupShowing
;
425 mAnchorContent
= aAnchorContent
;
428 mAdjustOffsetForContextMenu
= PR_FALSE
;
430 // if aAttributesOverride is true, then the popupanchor, popupalign and
431 // position attributes on the <popup> override those values passed in.
432 // If false, those attributes are only used if the values passed in are empty
433 if (aAnchorContent
) {
434 nsAutoString anchor
, align
, position
;
435 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::popupanchor
, anchor
);
436 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::popupalign
, align
);
437 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::position
, position
);
439 if (aAttributesOverride
) {
440 // if the attributes are set, clear the offset position. Otherwise,
441 // the offset is used to adjust the position from the anchor point
442 if (anchor
.IsEmpty() && align
.IsEmpty() && position
.IsEmpty())
443 position
.Assign(aPosition
);
447 else if (!aPosition
.IsEmpty()) {
448 position
.Assign(aPosition
);
451 if (position
.EqualsLiteral("before_start")) {
452 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
453 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
455 else if (position
.EqualsLiteral("before_end")) {
456 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
457 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
459 else if (position
.EqualsLiteral("after_start")) {
460 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
461 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
463 else if (position
.EqualsLiteral("after_end")) {
464 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
465 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
467 else if (position
.EqualsLiteral("start_before")) {
468 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
469 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
471 else if (position
.EqualsLiteral("start_after")) {
472 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
473 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
475 else if (position
.EqualsLiteral("end_before")) {
476 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
477 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
479 else if (position
.EqualsLiteral("end_after")) {
480 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
481 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
483 else if (position
.EqualsLiteral("overlap")) {
484 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
485 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
487 else if (position
.EqualsLiteral("after_pointer")) {
488 mPopupAnchor
= POPUPALIGNMENT_NONE
;
489 mPopupAlignment
= POPUPALIGNMENT_NONE
;
490 // XXXndeakin this is supposed to anchor vertically after, but with the
491 // horizontal position as the mouse pointer.
495 InitPositionFromAnchorAlign(anchor
, align
);
502 if (aAttributesOverride
) {
503 // Use |left| and |top| dimension attributes to position the popup if
504 // present, as they may have been persisted.
505 nsAutoString left
, top
;
506 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
);
507 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
);
510 if (!left
.IsEmpty()) {
511 PRInt32 x
= left
.ToInteger(&err
);
512 if (NS_SUCCEEDED(err
))
515 if (!top
.IsEmpty()) {
516 PRInt32 y
= top
.ToInteger(&err
);
517 if (NS_SUCCEEDED(err
))
524 nsMenuPopupFrame::InitializePopupAtScreen(PRInt32 aXPos
, PRInt32 aYPos
,
525 PRBool aIsContextMenu
)
529 mPopupState
= ePopupShowing
;
530 mAnchorContent
= nsnull
;
533 mPopupAnchor
= POPUPALIGNMENT_NONE
;
534 mPopupAlignment
= POPUPALIGNMENT_NONE
;
535 mIsContextMenu
= aIsContextMenu
;
536 mAdjustOffsetForContextMenu
= aIsContextMenu
;
540 nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent
* aAnchorContent
,
543 PRInt32 aXPos
, PRInt32 aYPos
)
547 mPopupState
= ePopupShowing
;
548 mAdjustOffsetForContextMenu
= PR_FALSE
;
550 // this popup opening function is provided for backwards compatibility
551 // only. It accepts either coordinates or an anchor and alignment value
552 // but doesn't use both together.
553 if (aXPos
== -1 && aYPos
== -1) {
554 mAnchorContent
= aAnchorContent
;
559 InitPositionFromAnchorAlign(aAnchor
, aAlign
);
562 mAnchorContent
= nsnull
;
563 mPopupAnchor
= POPUPALIGNMENT_NONE
;
564 mPopupAlignment
= POPUPALIGNMENT_NONE
;
573 LazyGeneratePopupDone(nsIContent
* aPopup
, nsIFrame
* aFrame
, void* aArg
)
575 // be safe and check the frame type
576 if (aFrame
->GetType() == nsGkAtoms::menuPopupFrame
) {
577 nsWeakFrame
weakFrame(aFrame
);
578 nsMenuPopupFrame
* popupFrame
= static_cast<nsMenuPopupFrame
*>(aFrame
);
580 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
581 if (pm
&& popupFrame
->IsMenu()) {
582 nsCOMPtr
<nsIContent
> popup
= aPopup
;
583 pm
->UpdateMenuItems(popup
);
585 if (!weakFrame
.IsAlive())
588 PRBool selectFirstItem
= (PRBool
)NS_PTR_TO_INT32(aArg
);
589 if (selectFirstItem
) {
590 nsMenuFrame
* next
= pm
->GetNextMenuItem(popupFrame
, nsnull
, PR_TRUE
);
591 popupFrame
->SetCurrentMenuItem(next
);
595 if (weakFrame
.IsAlive()) {
596 popupFrame
->PresContext()->PresShell()->
597 FrameNeedsReflow(popupFrame
, nsIPresShell::eTreeChange
,
598 NS_FRAME_HAS_DIRTY_CHILDREN
);
605 nsMenuPopupFrame::ShowPopup(PRBool aIsContextMenu
, PRBool aSelectFirstItem
)
607 mIsContextMenu
= aIsContextMenu
;
609 PRBool hasChildren
= PR_FALSE
;
611 if (mPopupState
== ePopupShowing
) {
612 mPopupState
= ePopupOpen
;
613 mIsOpenChanged
= PR_TRUE
;
615 nsIFrame
* parent
= GetParent();
616 if (parent
&& parent
->GetType() == nsGkAtoms::menuFrame
) {
617 nsWeakFrame
weakFrame(this);
618 (static_cast<nsMenuFrame
*>(parent
))->PopupOpened();
619 if (!weakFrame
.IsAlive())
623 // the frames for the child menus have not been created yet, so tell the
624 // frame constructor to build them
625 if (mFrames
.IsEmpty() && !mGeneratedChildren
) {
626 PresContext()->PresShell()->FrameConstructor()->
627 AddLazyChildren(mContent
, LazyGeneratePopupDone
, NS_INT32_TO_PTR(aSelectFirstItem
));
630 hasChildren
= PR_TRUE
;
631 PresContext()->PresShell()->
632 FrameNeedsReflow(this, nsIPresShell::eTreeChange
,
633 NS_FRAME_HAS_DIRTY_CHILDREN
);
635 if (mPopupType
== ePopupTypeMenu
) {
636 nsCOMPtr
<nsISound
> sound(do_CreateInstance("@mozilla.org/sound;1"));
638 sound
->PlaySystemSound(NS_SYSSOUND_MENU_POPUP
);
642 mShouldAutoPosition
= PR_TRUE
;
647 nsMenuPopupFrame::HidePopup(PRBool aDeselectMenu
, nsPopupState aNewState
)
649 NS_ASSERTION(aNewState
== ePopupClosed
|| aNewState
== ePopupInvisible
,
650 "popup being set to unexpected state");
652 // don't hide the popup when it isn't open
653 if (mPopupState
== ePopupClosed
|| mPopupState
== ePopupShowing
)
656 // when invisible and about to be closed, HidePopup has already been called,
657 // so just set the new state to closed and return
658 if (mPopupState
== ePopupInvisible
) {
659 if (aNewState
== ePopupClosed
)
660 mPopupState
= ePopupClosed
;
664 mPopupState
= aNewState
;
667 SetCurrentMenuItem(nsnull
);
669 mIncrementalString
.Truncate();
671 mIsOpenChanged
= PR_FALSE
;
672 mCurrentMenu
= nsnull
; // make sure no current menu is set
674 nsIView
* view
= GetView();
675 nsIViewManager
* viewManager
= view
->GetViewManager();
676 viewManager
->SetViewVisibility(view
, nsViewVisibility_kHide
);
677 viewManager
->ResizeView(view
, nsRect(0, 0, 0, 0));
679 FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent
);
681 // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
682 // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
683 // This code may not the best solution, but we can leave it here until we find the better approach.
684 nsIEventStateManager
*esm
= PresContext()->EventStateManager();
687 esm
->GetContentState(mContent
, state
);
689 if (state
& NS_EVENT_STATE_HOVER
)
690 esm
->SetContentState(nsnull
, NS_EVENT_STATE_HOVER
);
692 nsIFrame
* parent
= GetParent();
693 if (parent
&& parent
->GetType() == nsGkAtoms::menuFrame
) {
694 (static_cast<nsMenuFrame
*>(parent
))->PopupClosed(aDeselectMenu
);
699 nsMenuPopupFrame::InvalidateInternal(const nsRect
& aDamageRect
,
700 nscoord aX
, nscoord aY
, nsIFrame
* aForChild
,
703 InvalidateRoot(aDamageRect
+ nsPoint(aX
, aY
), aFlags
);
707 nsMenuPopupFrame::GetLayoutFlags(PRUint32
& aFlags
)
709 aFlags
= NS_FRAME_NO_SIZE_VIEW
| NS_FRAME_NO_MOVE_VIEW
| NS_FRAME_NO_VISIBILITY
;
712 ///////////////////////////////////////////////////////////////////////////////
713 // GetRootViewForPopup
714 // Retrieves the view for the popup widget that contains the given frame.
715 // If the given frame is not contained by a popup widget, return the
716 // root view of the root viewmanager.
718 nsMenuPopupFrame::GetRootViewForPopup(nsIFrame
* aStartFrame
)
720 nsIView
* view
= aStartFrame
->GetClosestView();
721 NS_ASSERTION(view
, "frame must have a closest view!");
723 // Walk up the view hierarchy looking for a view whose widget has a
724 // window type of eWindowType_popup - in other words a popup window
725 // widget. If we find one, this is the view we want.
726 nsIWidget
* widget
= view
->GetWidget();
729 widget
->GetWindowType(wtype
);
730 if (wtype
== eWindowType_popup
) {
735 nsIView
* temp
= view
->GetParent();
737 // Otherwise, we've walked all the way up to the root view and not
738 // found a view for a popup window widget. Just return the root view.
748 nsMenuPopupFrame::AdjustPositionForAnchorAlign(const nsRect
& anchorRect
,
749 PRBool
& aHFlip
, PRBool
& aVFlip
)
751 // flip the anchor and alignment for right-to-left
752 PRInt8
popupAnchor(mPopupAnchor
);
753 PRInt8
popupAlign(mPopupAlignment
);
754 if (GetStyleVisibility()->mDirection
== NS_STYLE_DIRECTION_RTL
) {
755 popupAnchor
= -popupAnchor
;
756 popupAlign
= -popupAlign
;
759 // first, determine at which corner of the anchor the popup should appear
761 switch (popupAnchor
) {
762 case POPUPALIGNMENT_TOPLEFT
:
763 pnt
= anchorRect
.TopLeft();
765 case POPUPALIGNMENT_TOPRIGHT
:
766 pnt
= anchorRect
.TopRight();
768 case POPUPALIGNMENT_BOTTOMLEFT
:
769 pnt
= anchorRect
.BottomLeft();
771 case POPUPALIGNMENT_BOTTOMRIGHT
:
772 pnt
= anchorRect
.BottomRight();
776 // If the alignment is on the right edge of the popup, move the popup left
777 // by the width. Similarly, if the alignment is on the bottom edge of the
778 // popup, move the popup up by the height. In addition, account for the
779 // margins of the popup on the edge on which it is aligned.
781 GetStyleMargin()->GetMargin(margin
);
782 switch (popupAlign
) {
783 case POPUPALIGNMENT_TOPLEFT
:
784 pnt
.MoveBy(margin
.left
, margin
.top
);
786 case POPUPALIGNMENT_TOPRIGHT
:
787 pnt
.MoveBy(-mRect
.width
- margin
.right
, margin
.top
);
789 case POPUPALIGNMENT_BOTTOMLEFT
:
790 pnt
.MoveBy(margin
.left
, -mRect
.height
- margin
.bottom
);
792 case POPUPALIGNMENT_BOTTOMRIGHT
:
793 pnt
.MoveBy(-mRect
.width
- margin
.right
, -mRect
.height
- margin
.bottom
);
797 // Flipping horizontally is allowed as long as the popup is above or below
798 // the anchor. This will happen if both the anchor and alignment are top or
799 // both are bottom, but different values. Similarly, flipping vertically is
800 // allowed if the popup is to the left or right of the anchor. In this case,
801 // the values of the constants are such that both must be positive or both
802 // must be negative. A special case, used for overlap, allows flipping
803 // vertically as well.
804 aHFlip
= (popupAnchor
== -popupAlign
);
805 aVFlip
= ((popupAnchor
> 0) == (popupAlign
> 0)) ||
806 (popupAnchor
== POPUPALIGNMENT_TOPLEFT
&& popupAlign
== POPUPALIGNMENT_TOPLEFT
);
812 nsMenuPopupFrame::FlipOrResize(nscoord
& aScreenPoint
, nscoord aSize
,
813 nscoord aScreenBegin
, nscoord aScreenEnd
,
814 nscoord aAnchorBegin
, nscoord aAnchorEnd
,
815 nscoord aMarginBegin
, nscoord aMarginEnd
,
816 nscoord aOffsetForContextMenu
, PRBool aFlip
)
818 // all of the coordinates used here are in app units relative to the screen
820 nscoord popupSize
= aSize
;
821 if (aScreenPoint
< aScreenBegin
) {
822 // at its current position, the popup would extend past the left or top
823 // edge of the screen, so it will have to be moved or resized.
825 // check whether there is more room to the left and right (or top and
826 // bottom) of the anchor and put the popup on the side with more room.
827 if (aAnchorBegin
- aScreenBegin
>= aScreenEnd
- aAnchorEnd
) {
828 aScreenPoint
= aScreenBegin
;
829 popupSize
= aAnchorBegin
- aScreenPoint
- aMarginEnd
;
832 // flip such that the popup is to the right or bottom of the anchor
833 // point instead. However, when flipping use the same margin size.
834 aScreenPoint
= aAnchorEnd
+ aMarginEnd
;
835 // check if the new position is still off the right or bottom edge of
836 // the screen. If so, resize the popup.
837 if (aScreenPoint
+ aSize
> aScreenEnd
) {
838 popupSize
= aScreenEnd
- aScreenPoint
;
843 aScreenPoint
= aScreenBegin
;
846 else if (aScreenPoint
+ aSize
> aScreenEnd
) {
847 // at its current position, the popup would extend past the right or
848 // bottom edge of the screen, so it will have to be moved or resized.
850 // check whether there is more room to the left and right (or top and
851 // bottom) of the anchor and put the popup on the side with more room.
852 if (aScreenEnd
- aAnchorEnd
>= aAnchorBegin
- aScreenBegin
) {
853 if (mIsContextMenu
) {
854 aScreenPoint
= aScreenEnd
- aSize
;
857 popupSize
= aScreenEnd
- aScreenPoint
;
861 // flip such that the popup is to the left or top of the anchor point
863 aScreenPoint
= aAnchorBegin
- aSize
- aMarginBegin
- aOffsetForContextMenu
;
864 // check if the new position is still off the left or top edge of the
865 // screen. If so, resize the popup.
866 if (aScreenPoint
< aScreenBegin
) {
867 aScreenPoint
= aScreenBegin
;
868 if (!mIsContextMenu
) {
869 popupSize
= aAnchorBegin
- aScreenPoint
- aMarginBegin
;
875 aScreenPoint
= aScreenEnd
- aSize
;
883 nsMenuPopupFrame::SetPopupPosition(nsIFrame
* aAnchorFrame
)
885 if (!mShouldAutoPosition
&& !mInContentShell
)
888 nsPresContext
* presContext
= PresContext();
889 nsIFrame
* rootFrame
= presContext
->PresShell()->FrameManager()->GetRootFrame();
890 NS_ASSERTION(rootFrame
->GetView() && GetView() &&
891 rootFrame
->GetView() == GetView()->GetParent(),
892 "rootFrame's view is not our view's parent???");
894 // if the frame is not specified, use the anchor node passed to OpenPopup. If
895 // that wasn't specified either, use the root frame. Note that mAnchorContent
896 // might be a different document so its presshell must be used.
898 if (mAnchorContent
) {
899 nsCOMPtr
<nsIDocument
> document
= mAnchorContent
->GetDocument();
901 nsIPresShell
*shell
= document
->GetPrimaryShell();
903 return NS_ERROR_FAILURE
;
905 aAnchorFrame
= shell
->GetPrimaryFrameFor(mAnchorContent
);
910 aAnchorFrame
= rootFrame
;
916 PRBool sizedToPopup
= PR_FALSE
;
917 if (aAnchorFrame
->GetContent()) {
918 // the popup should be the same size as the anchor menu, for example, a menulist.
919 sizedToPopup
= nsMenuFrame::IsSizedToPopup(aAnchorFrame
->GetContent(), PR_FALSE
);
922 // the dimensions of the anchor in its app units
923 nsRect parentRect
= aAnchorFrame
->GetScreenRectInAppUnits();
925 // the anchor may be in a different document with a different scale,
926 // so adjust the size so that it is in the app units of the popup instead
927 // of the anchor. This is done by converting to device pixels by dividing
928 // by the anchor's app units per device pixel and then converting back to
929 // app units by multiplying by the popup's app units per device pixel.
930 float adj
= float(presContext
->AppUnitsPerDevPixel()) /
931 aAnchorFrame
->PresContext()->AppUnitsPerDevPixel();
932 parentRect
.ScaleRoundOut(adj
);
934 // Set the popup's size to the preferred size. Below, this size will be
935 // adjusted to fit on the screen or within the content area. If the anchor
936 // is sized to the popup, use the anchor's width instead of the preferred
937 // width. The preferred size should already be set by the parent frame.
938 NS_ASSERTION(mPrefSize
.width
>= 0 || mPrefSize
.height
>= 0,
939 "preferred size of popup not set");
940 mRect
.width
= sizedToPopup
? parentRect
.width
: mPrefSize
.width
;
941 mRect
.height
= mPrefSize
.height
;
943 // the screen position in app units where the popup should appear
946 // For anchored popups, the anchor rectangle. For non-anchored popups, the
948 nsRect anchorRect
= parentRect
;
950 // indicators of whether the popup should be flipped or resized.
951 PRBool hFlip
= PR_FALSE
, vFlip
= PR_FALSE
;
954 GetStyleMargin()->GetMargin(margin
);
956 // the screen rectangle of the root frame, in dev pixels.
957 nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
959 nsIDeviceContext
* devContext
= presContext
->DeviceContext();
960 nscoord offsetForContextMenu
= 0;
961 // if mScreenXPos and mScreenYPos are -1, then we are anchored. If they
962 // have other values, then the popup appears unanchored at that screen
964 if (mScreenXPos
== -1 && mScreenYPos
== -1) {
965 // if we are anchored, there are certain things we don't want to do when
966 // repositioning the popup to fit on the screen, such as end up positioned
967 // over the anchor, for instance a popup appearing over the menu label.
968 // When doing this reposition, we want to move the popup to the side with
969 // the most room. The combination of anchor and alignment dictate if we
970 // readjust above/below or to the left/right.
971 if (mAnchorContent
) {
972 // move the popup according to the anchor and alignment. This will also
973 // tell us which axis the popup is flush against in case we have to move
974 // it around later. The AdjustPositionForAnchorAlign method accounts for
975 // the popup's margin.
976 screenPoint
= AdjustPositionForAnchorAlign(anchorRect
, hFlip
, vFlip
);
979 // with no anchor, the popup is positioned relative to the root frame
980 anchorRect
= rootScreenRect
;
981 screenPoint
= anchorRect
.TopLeft() + nsPoint(margin
.left
, margin
.top
);
984 // mXPos and mYPos specify an additonal offset passed to OpenPopup that
985 // should be added to the position
986 screenPoint
.x
+= presContext
->CSSPixelsToAppUnits(mXPos
);
987 screenPoint
.y
+= presContext
->CSSPixelsToAppUnits(mYPos
);
990 // the popup is positioned at a screen coordinate.
991 // first convert the screen position in mScreenXPos and mScreenYPos from
992 // CSS pixels into device pixels, ignoring any scaling as mScreenXPos and
993 // mScreenYPos are unscaled screen coordinates.
994 PRInt32 factor
= devContext
->UnscaledAppUnitsPerDevPixel();
996 // context menus should be offset by two pixels so that they don't appear
997 // directly where the cursor is. Otherwise, it is too easy to have the
998 // context menu close up again.
999 if (mAdjustOffsetForContextMenu
) {
1000 PRInt32 offsetForContextMenuDev
=
1001 nsPresContext::CSSPixelsToAppUnits(2) / factor
;
1002 offsetForContextMenu
= presContext
->DevPixelsToAppUnits(offsetForContextMenuDev
);
1005 // next, convert into app units accounting for the scaling
1006 screenPoint
.x
= presContext
->DevPixelsToAppUnits(
1007 nsPresContext::CSSPixelsToAppUnits(mScreenXPos
) / factor
);
1008 screenPoint
.y
= presContext
->DevPixelsToAppUnits(
1009 nsPresContext::CSSPixelsToAppUnits(mScreenYPos
) / factor
);
1010 anchorRect
= nsRect(screenPoint
, nsSize(0, 0));
1012 // add the margins on the popup
1013 screenPoint
.MoveBy(margin
.left
+ offsetForContextMenu
,
1014 margin
.top
+ offsetForContextMenu
);
1016 // screen positioned popups can be flipped vertically but never horizontally
1020 // screenRect will hold the rectangle of the available screen space. It
1021 // will be reduced by the OS chrome such as menubars. It addition, for
1022 // content shells, it will be the area of the content rather than the
1025 if (mMenuCanOverlapOSBar
)
1026 devContext
->GetRect(screenRect
);
1028 devContext
->GetClientRect(screenRect
);
1030 // keep a 3 pixel margin to the right and bottom of the screen for the WinXP dropshadow
1031 screenRect
.SizeBy(-nsPresContext::CSSPixelsToAppUnits(3),
1032 -nsPresContext::CSSPixelsToAppUnits(3));
1034 // for content shells, clip to the client area rather than the screen area
1035 if (mInContentShell
)
1036 screenRect
.IntersectRect(screenRect
, rootScreenRect
);
1038 // ensure that anchorRect is on screen
1039 if (!anchorRect
.IntersectRect(anchorRect
, screenRect
)) {
1040 // if the anchor isn't within the screen, move it to the edge of the screen.
1041 // IntersectRect will have set both the width and height of anchorRect to 0.
1042 if (anchorRect
.x
< screenRect
.x
)
1043 anchorRect
.x
= screenRect
.x
;
1044 if (anchorRect
.XMost() > screenRect
.XMost())
1045 anchorRect
.x
= screenRect
.XMost();
1046 if (anchorRect
.y
< screenRect
.y
)
1047 anchorRect
.y
= screenRect
.y
;
1048 if (anchorRect
.YMost() > screenRect
.YMost())
1049 anchorRect
.y
= screenRect
.YMost();
1052 // shrink the the popup down if it is larger than the screen size
1053 if (mRect
.width
> screenRect
.width
)
1054 mRect
.width
= screenRect
.width
;
1055 if (mRect
.height
> screenRect
.height
)
1056 mRect
.height
= screenRect
.height
;
1058 // at this point the anchor (anchorRect) is within the available screen
1059 // area (screenRect) and the popup is known to be no larger than the screen.
1060 // Next, check if there is enough space to show the popup at full size when
1061 // positioned at screenPoint. If not, flip the popups to the opposite side
1062 // of their anchor point, or resize them as necessary.
1063 mRect
.width
= FlipOrResize(screenPoint
.x
, mRect
.width
, screenRect
.x
,
1064 screenRect
.XMost(), anchorRect
.x
, anchorRect
.XMost(),
1065 margin
.left
, margin
.right
, offsetForContextMenu
, hFlip
);
1066 mRect
.height
= FlipOrResize(screenPoint
.y
, mRect
.height
, screenRect
.y
,
1067 screenRect
.YMost(), anchorRect
.y
, anchorRect
.YMost(),
1068 margin
.top
, margin
.bottom
, offsetForContextMenu
, vFlip
);
1070 NS_ASSERTION(screenPoint
.x
>= screenRect
.x
&& screenPoint
.y
>= screenRect
.y
&&
1071 screenPoint
.x
+ mRect
.width
<= screenRect
.XMost() &&
1072 screenPoint
.y
+ mRect
.height
<= screenRect
.YMost(),
1073 "Popup is offscreen");
1075 // determine the x and y position of the view by subtracting the desired
1076 // screen position from the screen position of the root frame.
1077 nsPoint viewPoint
= screenPoint
- rootScreenRect
.TopLeft();
1078 presContext
->GetViewManager()->MoveViewTo(GetView(), viewPoint
.x
, viewPoint
.y
);
1080 // Now that we've positioned the view, sync up the frame's origin.
1081 nsBoxFrame::SetPosition(viewPoint
- GetParent()->GetOffsetTo(rootFrame
));
1084 nsBoxLayoutState
state(PresContext());
1085 // XXXndeakin can parentSize.width still extend outside?
1086 SetBounds(state
, nsRect(mRect
.x
, mRect
.y
, parentRect
.width
, mRect
.height
));
1092 /* virtual */ nsMenuFrame
*
1093 nsMenuPopupFrame::GetCurrentMenuItem()
1095 return mCurrentMenu
;
1098 PRBool
nsMenuPopupFrame::ConsumeOutsideClicks()
1100 // If the popup has explicitly set a consume mode, honor that.
1101 if (mConsumeRollupEvent
!= nsIPopupBoxObject::ROLLUP_DEFAULT
)
1102 return (mConsumeRollupEvent
== nsIPopupBoxObject::ROLLUP_CONSUME
);
1104 nsCOMPtr
<nsIContent
> parentContent
= mContent
->GetParent();
1105 if (parentContent
) {
1106 nsINodeInfo
*ni
= parentContent
->NodeInfo();
1107 if (ni
->Equals(nsGkAtoms::menulist
, kNameSpaceID_XUL
))
1108 return PR_TRUE
; // Consume outside clicks for combo boxes on all platforms
1109 #if defined(XP_WIN) || defined(XP_OS2)
1110 // Don't consume outside clicks for menus in Windows
1111 if (ni
->Equals(nsGkAtoms::menu
, kNameSpaceID_XUL
) ||
1112 (ni
->Equals(nsGkAtoms::popupset
, kNameSpaceID_XUL
)))
1115 if (ni
->Equals(nsGkAtoms::textbox
, kNameSpaceID_XUL
)) {
1116 // Don't consume outside clicks for autocomplete widget
1117 if (parentContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
1118 nsGkAtoms::autocomplete
, eCaseMatters
))
1126 static nsIScrollableView
* GetScrollableViewForFrame(nsIFrame
* aFrame
)
1128 nsIScrollableFrame
* sf
= do_QueryFrame(aFrame
);
1131 return sf
->GetScrollableView();
1134 // XXXroc this is megalame. Fossicking around for a view of the right
1135 // type is a recipe for disaster in the long term.
1136 nsIScrollableView
* nsMenuPopupFrame::GetScrollableView(nsIFrame
* aStart
)
1141 nsIFrame
* currFrame
;
1142 nsIScrollableView
* scrollableView
=nsnull
;
1144 // try start frame and siblings
1147 scrollableView
= GetScrollableViewForFrame(currFrame
);
1148 if ( scrollableView
)
1149 return scrollableView
;
1150 currFrame
= currFrame
->GetNextSibling();
1151 } while ( currFrame
);
1154 nsIFrame
* childFrame
;
1157 childFrame
= currFrame
->GetFirstChild(nsnull
);
1158 scrollableView
=GetScrollableView(childFrame
);
1159 if ( scrollableView
)
1160 return scrollableView
;
1161 currFrame
= currFrame
->GetNextSibling();
1162 } while ( currFrame
);
1167 void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame
* aMenuItem
)
1170 nsIFrame
* childFrame
= GetFirstChild(nsnull
);
1171 nsIScrollableView
*scrollableView
;
1172 scrollableView
= GetScrollableView(childFrame
);
1173 if (scrollableView
) {
1174 nscoord scrollX
, scrollY
;
1176 nsRect viewRect
= scrollableView
->View()->GetBounds();
1177 nsRect itemRect
= aMenuItem
->GetRect();
1178 scrollableView
->GetScrollPosition(scrollX
, scrollY
);
1181 if ( itemRect
.y
+ itemRect
.height
> scrollY
+ viewRect
.height
)
1182 scrollableView
->ScrollTo(scrollX
, itemRect
.y
+ itemRect
.height
- viewRect
.height
, 0);
1185 else if ( itemRect
.y
< scrollY
)
1186 scrollableView
->ScrollTo(scrollX
, itemRect
.y
, 0);
1191 NS_IMETHODIMP
nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame
* aMenuItem
)
1193 if (mCurrentMenu
== aMenuItem
)
1197 mCurrentMenu
->SelectMenu(PR_FALSE
);
1201 EnsureMenuItemIsVisible(aMenuItem
);
1202 aMenuItem
->SelectMenu(PR_TRUE
);
1205 mCurrentMenu
= aMenuItem
;
1211 nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
1213 mCurrentMenu
= nsnull
;
1217 nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame
* aMenuItem
,
1218 PRBool aSelectFirstItem
)
1220 if (mCurrentMenu
== aMenuItem
)
1223 // When a context menu is open, the current menu is locked, and no change
1224 // to the menu is allowed.
1225 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
1226 if (!mIsContextMenu
&& pm
&& pm
->HasContextMenu(this))
1229 // Unset the current child.
1231 mCurrentMenu
->SelectMenu(PR_FALSE
);
1232 nsMenuPopupFrame
* popup
= mCurrentMenu
->GetPopup();
1234 if (mCurrentMenu
->IsOpen()) {
1236 pm
->HidePopupAfterDelay(popup
);
1241 // Set the new child.
1243 EnsureMenuItemIsVisible(aMenuItem
);
1244 aMenuItem
->SelectMenu(PR_TRUE
);
1247 mCurrentMenu
= aMenuItem
;
1253 nsMenuPopupFrame::Enter()
1255 mIncrementalString
.Truncate();
1257 // Give it to the child.
1259 return mCurrentMenu
->Enter();
1265 nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent
* aKeyEvent
, PRBool
& doAction
)
1267 PRUint32 charCode
, keyCode
;
1268 aKeyEvent
->GetCharCode(&charCode
);
1269 aKeyEvent
->GetKeyCode(&keyCode
);
1271 doAction
= PR_FALSE
;
1273 // Enumerate over our list of frames.
1274 nsIFrame
* immediateParent
= nsnull
;
1275 PresContext()->PresShell()->
1276 FrameConstructor()->GetInsertionPoint(this, nsnull
, &immediateParent
);
1277 if (!immediateParent
)
1278 immediateParent
= this;
1280 PRUint32 matchCount
= 0, matchShortcutCount
= 0;
1281 PRBool foundActive
= PR_FALSE
;
1283 nsMenuFrame
* frameBefore
= nsnull
;
1284 nsMenuFrame
* frameAfter
= nsnull
;
1285 nsMenuFrame
* frameShortcut
= nsnull
;
1287 nsIContent
* parentContent
= mContent
->GetParent();
1289 PRBool isMenu
= parentContent
&&
1290 !parentContent
->NodeInfo()->Equals(nsGkAtoms::menulist
, kNameSpaceID_XUL
);
1292 static DOMTimeStamp lastKeyTime
= 0;
1293 DOMTimeStamp keyTime
;
1294 aKeyEvent
->GetTimeStamp(&keyTime
);
1296 if (charCode
== 0) {
1297 if (keyCode
== NS_VK_BACK
) {
1298 if (!isMenu
&& !mIncrementalString
.IsEmpty()) {
1299 mIncrementalString
.SetLength(mIncrementalString
.Length() - 1);
1304 nsCOMPtr
<nsISound
> soundInterface
= do_CreateInstance("@mozilla.org/sound;1");
1306 soundInterface
->Beep();
1307 #endif // #ifdef XP_WIN
1313 PRUnichar uniChar
= ToLowerCase(static_cast<PRUnichar
>(charCode
));
1314 if (isMenu
|| // Menu supports only first-letter navigation
1315 keyTime
- lastKeyTime
> INC_TYP_INTERVAL
) // Interval too long, treat as new typing
1316 mIncrementalString
= uniChar
;
1318 mIncrementalString
.Append(uniChar
);
1322 // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one
1323 nsAutoString
incrementalString(mIncrementalString
);
1324 PRUint32 charIndex
= 1, stringLength
= incrementalString
.Length();
1325 while (charIndex
< stringLength
&& incrementalString
[charIndex
] == incrementalString
[charIndex
- 1]) {
1328 if (charIndex
== stringLength
) {
1329 incrementalString
.Truncate(1);
1333 lastKeyTime
= keyTime
;
1335 nsIFrame
* currFrame
;
1336 // NOTE: If you crashed here due to a bogus |immediateParent| it is
1337 // possible that the menu whose shortcut is being looked up has
1338 // been destroyed already. One strategy would be to
1339 // setTimeout(<func>,0) as detailed in:
1340 // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
1341 currFrame
= immediateParent
->GetFirstChild(nsnull
);
1343 PRInt32 menuAccessKey
= -1;
1344 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey
);
1346 // We start searching from first child. This process is divided into two parts
1347 // -- before current and after current -- by the current item
1349 nsIContent
* current
= currFrame
->GetContent();
1351 // See if it's a menu item.
1352 if (nsXULPopupManager::IsValidMenuItem(PresContext(), current
, PR_TRUE
)) {
1353 nsAutoString textKey
;
1354 if (menuAccessKey
>= 0) {
1355 // Get the shortcut attribute.
1356 current
->GetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
, textKey
);
1358 if (textKey
.IsEmpty()) { // No shortcut, try first letter
1359 isShortcut
= PR_FALSE
;
1360 current
->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
, textKey
);
1361 if (textKey
.IsEmpty()) // No label, try another attribute (value)
1362 current
->GetAttr(kNameSpaceID_None
, nsGkAtoms::value
, textKey
);
1365 isShortcut
= PR_TRUE
;
1367 if (StringBeginsWith(textKey
, incrementalString
,
1368 nsCaseInsensitiveStringComparator())) {
1369 // mIncrementalString is a prefix of textKey
1370 if (currFrame
->GetType() == nsGkAtoms::menuFrame
) {
1371 // There is one match
1374 // There is one shortcut-key match
1375 matchShortcutCount
++;
1376 // Record the matched item. If there is only one matched shortcut item, do it
1377 frameShortcut
= static_cast<nsMenuFrame
*>(currFrame
);
1380 // It's a first candidate item located before/on the current item
1382 frameBefore
= static_cast<nsMenuFrame
*>(currFrame
);
1385 // It's a first candidate item located after the current item
1387 frameAfter
= static_cast<nsMenuFrame
*>(currFrame
);
1394 // Get the active status
1395 if (current
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::menuactive
,
1396 nsGkAtoms::_true
, eCaseMatters
)) {
1397 foundActive
= PR_TRUE
;
1398 if (stringLength
> 1) {
1399 // If there is more than one char typed, the current item has highest priority,
1400 // otherwise the item next to current has highest priority
1401 if (currFrame
== frameBefore
)
1406 currFrame
= currFrame
->GetNextSibling();
1409 doAction
= (isMenu
&& (matchCount
== 1 || matchShortcutCount
== 1));
1411 if (matchShortcutCount
== 1) // We have one matched shortcut item
1412 return frameShortcut
;
1413 if (frameAfter
) // If we have matched item after the current, use it
1415 else if (frameBefore
) // If we haven't, use the item before the current
1418 // If we don't match anything, rollback the last typing
1419 mIncrementalString
.SetLength(mIncrementalString
.Length() - 1);
1421 // didn't find a matching menu item
1423 // behavior on Windows - this item is in a menu popup off of the
1424 // menu bar, so beep and do nothing else
1426 nsCOMPtr
<nsISound
> soundInterface
= do_CreateInstance("@mozilla.org/sound;1");
1428 soundInterface
->Beep();
1430 #endif // #ifdef XP_WIN
1436 nsMenuPopupFrame::GetWidget(nsIWidget
**aWidget
)
1438 nsIView
* view
= GetRootViewForPopup(this);
1442 *aWidget
= view
->GetWidget();
1443 NS_IF_ADDREF(*aWidget
);
1448 nsMenuPopupFrame::AttachedDismissalListener()
1450 mConsumeRollupEvent
= nsIPopupBoxObject::ROLLUP_DEFAULT
;
1453 // helpers /////////////////////////////////////////////////////////////
1456 nsMenuPopupFrame::AttributeChanged(PRInt32 aNameSpaceID
,
1457 nsIAtom
* aAttribute
,
1461 nsresult rv
= nsBoxFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
1464 if (aAttribute
== nsGkAtoms::left
|| aAttribute
== nsGkAtoms::top
)
1465 MoveToAttributePosition();
1467 // accessibility needs this to ensure the frames get constructed when the
1468 // menugenerated attribute is set, see bug 279703 comment 42 for discussion
1469 if (aAttribute
== nsGkAtoms::menugenerated
&&
1470 mFrames
.IsEmpty() && !mGeneratedChildren
) {
1472 PresContext()->PresShell()->FrameConstructor()->
1473 AddLazyChildren(mContent
, LazyGeneratePopupDone
, nsnull
, PR_TRUE
);
1480 nsMenuPopupFrame::MoveToAttributePosition()
1482 // Move the widget around when the user sets the |left| and |top| attributes.
1483 // Note that this is not the best way to move the widget, as it results in lots
1484 // of FE notifications and is likely to be slow as molasses. Use |moveTo| on
1485 // nsIPopupBoxObject if possible.
1486 nsAutoString left
, top
;
1487 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
);
1488 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
);
1490 mScreenXPos
= left
.ToInteger(&err1
);
1491 mScreenYPos
= top
.ToInteger(&err2
);
1493 if (NS_SUCCEEDED(err1
) && NS_SUCCEEDED(err2
))
1494 MoveToInternal(mScreenXPos
, mScreenYPos
);
1498 nsMenuPopupFrame::Destroy()
1500 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
1502 pm
->PopupDestroyed(this);
1504 nsIRootBox
* rootBox
=
1505 nsIRootBox::GetRootBox(PresContext()->GetPresShell());
1506 if (rootBox
&& rootBox
->GetDefaultTooltip() == mContent
) {
1507 rootBox
->SetDefaultTooltip(nsnull
);
1510 nsBoxFrame::Destroy();
1514 nsMenuPopupFrame::MoveTo(PRInt32 aLeft
, PRInt32 aTop
)
1516 // Set the 'left' and 'top' attributes
1517 nsAutoString left
, top
;
1518 left
.AppendInt(aLeft
);
1519 top
.AppendInt(aTop
);
1521 nsWeakFrame
weakFrame(this);
1522 mContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
, PR_FALSE
);
1523 if (!weakFrame
.IsAlive()) {
1526 mContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
, PR_FALSE
);
1527 if (!weakFrame
.IsAlive()) {
1531 MoveToInternal(aLeft
, aTop
);
1535 nsMenuPopupFrame::MoveToInternal(PRInt32 aLeft
, PRInt32 aTop
)
1537 // just don't support moving popups for content shells
1538 if (mInContentShell
)
1541 nsIView
* view
= GetView();
1542 NS_ASSERTION(view
->GetParent(), "Must have parent!");
1544 // Retrieve screen position of parent view
1545 nsIntPoint screenPos
= view
->GetParent()->GetScreenPosition();
1547 nsPresContext
* context
= PresContext();
1548 aLeft
= context
->AppUnitsToDevPixels(nsPresContext::CSSPixelsToAppUnits(aLeft
));
1549 aTop
= context
->AppUnitsToDevPixels(nsPresContext::CSSPixelsToAppUnits(aTop
));
1551 // Move the widget. The widget will be null if it hasn't been created yet,
1552 // but that's OK as the popup won't be open in this case.
1553 // XXXbz don't we want screenPos to be the parent _widget_'s position, then?
1554 nsIWidget
* widget
= view
->GetWidget();
1556 widget
->Move(aLeft
- screenPos
.x
, aTop
- screenPos
.y
);
1560 nsMenuPopupFrame::GetAutoPosition()
1562 return mShouldAutoPosition
;
1566 nsMenuPopupFrame::SetAutoPosition(PRBool aShouldAutoPosition
)
1568 mShouldAutoPosition
= aShouldAutoPosition
;
1572 nsMenuPopupFrame::SetConsumeRollupEvent(PRUint32 aConsumeMode
)
1574 mConsumeRollupEvent
= aConsumeMode
;