Bug 473390 part 14. Eliminate the mRootBox member of the frame constructor state...
[mozilla-central.git] / layout / xul / base / src / nsMenuPopupFrame.cpp
blobf11fcc81af00cba56fa852e4ad2a583b51025f81
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
14 * License.
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.
23 * Contributor(s):
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"
47 #include "prtypes.h"
48 #include "nsIAtom.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"
65 #include "nsRect.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"
86 #include "nsISound.h"
87 #include "nsIRootBox.h"
89 PRInt8 nsMenuPopupFrame::sDefaultLevelParent = -1;
91 // NS_NewMenuPopupFrame
93 // Wrapper for creating a new menu popup container
95 nsIFrame*
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),
119 mPrefSize(-1, -1)
121 if (sDefaultLevelParent >= 0)
122 return;
123 sDefaultLevelParent =
124 nsContentUtils::GetBoolPref("ui.panel.default_level_parent", PR_FALSE);
125 } // ctor
128 NS_IMETHODIMP
129 nsMenuPopupFrame::Init(nsIContent* aContent,
130 nsIFrame* aParent,
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
139 // look&feel object
140 PRBool tempBool;
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();
157 if (doc) {
158 PRInt32 namespaceID;
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);
170 PRInt32 type = -1;
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
177 // the popup opens.
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());
187 if (rootBox) {
188 rootBox->SetDefaultTooltip(aContent);
192 return rv;
195 PRBool
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));
206 PRBool
207 nsMenuPopupFrame::IsTopMost()
209 // If this panel is not a panel, this is always a top-most popup
210 if (mPopupType != ePopupTypePanel)
211 return PR_TRUE;
213 // If this panel is a noautohide panel, it should appear just above the parent
214 // window.
215 if (IsNoAutoHide())
216 return PR_FALSE;
218 // Otherwise, check the topmost attribute.
219 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::level,
220 nsGkAtoms::top, eIgnoreCase))
221 return PR_TRUE;
223 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::level,
224 nsGkAtoms::parent, eIgnoreCase))
225 return PR_FALSE;
227 // Otherwise, the result depends on the platform.
228 return sDefaultLevelParent ? PR_TRUE : PR_FALSE;
231 void
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);
242 nsresult
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 ==
255 mode);
256 nsIContent* parentContent = GetContent()->GetParent();
257 nsIAtom *tag = nsnull;
258 if (parentContent)
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;
266 if (!IsTopMost()) {
267 nsCOMPtr<nsISupports> cont = PresContext()->GetContainer();
268 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont);
269 if (!dsti)
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));
277 if (baseWindow)
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);
285 #else
286 static NS_DEFINE_IID(kCChildCID, NS_CHILD_CID);
287 aView->CreateWidget(kCChildCID, &widgetData, nsnull, PR_TRUE, PR_TRUE,
288 eContentTypeInherit, parentWidget);
289 #endif
290 nsIWidget* widget = aView->GetWidget();
291 widget->SetTransparencyMode(mode);
292 widget->SetWindowShadowStyle(GetStyleUIReset()->mWindowShadow);
293 return NS_OK;
296 // this class is used for dispatching popupshowing events asynchronously.
297 class nsXULPopupShownEvent : public nsRunnable
299 public:
300 nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext)
301 : mPopup(aPopup), mPresContext(aPresContext)
305 NS_IMETHOD Run()
307 nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_SHOWN, nsnull, nsMouseEvent::eReal);
308 return nsEventDispatcher::Dispatch(mPopup, mPresContext, &event);
311 private:
312 nsCOMPtr<nsIContent> mPopup;
313 nsRefPtr<nsPresContext> mPresContext;
316 NS_IMETHODIMP
317 nsMenuPopupFrame::SetInitialChildList(nsIAtom* aListName,
318 nsIFrame* aChildList)
320 // unless the list is empty, indicate that children have been generated.
321 if (aChildList)
322 mGeneratedChildren = PR_TRUE;
323 return nsBoxFrame::SetInitialChildList(aListName, aChildList);
326 PRBool
327 nsMenuPopupFrame::IsLeaf() const
329 if (mGeneratedChildren)
330 return PR_FALSE;
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));
348 void
349 nsMenuPopupFrame::SetPreferredBounds(nsBoxLayoutState& aState,
350 const nsRect& aRect)
352 nsBox::SetBounds(aState, aRect, PR_FALSE);
353 mPrefSize = aRect.Size();
356 void
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);
365 if (scrollframe)
366 scrollframe->ScrollTo(nsPoint(0,0));
369 nsIView* view = GetView();
370 nsIViewManager* viewManager = view->GetViewManager();
371 nsRect rect = GetRect();
372 rect.x = rect.y = 0;
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);
389 void
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;
401 else
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;
412 else
413 mPopupAlignment = POPUPALIGNMENT_NONE;
416 void
417 nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
418 const nsAString& aPosition,
419 PRInt32 aXPos, PRInt32 aYPos,
420 PRBool aAttributesOverride)
422 EnsureWidget();
424 mPopupState = ePopupShowing;
425 mAnchorContent = aAnchorContent;
426 mXPos = aXPos;
427 mYPos = aYPos;
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);
444 else
445 mXPos = mYPos = 0;
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.
492 mYPos += 21;
494 else {
495 InitPositionFromAnchorAlign(anchor, align);
499 mScreenXPos = -1;
500 mScreenYPos = -1;
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);
509 PRInt32 err;
510 if (!left.IsEmpty()) {
511 PRInt32 x = left.ToInteger(&err);
512 if (NS_SUCCEEDED(err))
513 mScreenXPos = x;
515 if (!top.IsEmpty()) {
516 PRInt32 y = top.ToInteger(&err);
517 if (NS_SUCCEEDED(err))
518 mScreenYPos = y;
523 void
524 nsMenuPopupFrame::InitializePopupAtScreen(PRInt32 aXPos, PRInt32 aYPos,
525 PRBool aIsContextMenu)
527 EnsureWidget();
529 mPopupState = ePopupShowing;
530 mAnchorContent = nsnull;
531 mScreenXPos = aXPos;
532 mScreenYPos = aYPos;
533 mPopupAnchor = POPUPALIGNMENT_NONE;
534 mPopupAlignment = POPUPALIGNMENT_NONE;
535 mIsContextMenu = aIsContextMenu;
536 mAdjustOffsetForContextMenu = aIsContextMenu;
539 void
540 nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
541 nsAString& aAnchor,
542 nsAString& aAlign,
543 PRInt32 aXPos, PRInt32 aYPos)
545 EnsureWidget();
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;
555 mScreenXPos = -1;
556 mScreenYPos = -1;
557 mXPos = 0;
558 mYPos = 0;
559 InitPositionFromAnchorAlign(aAnchor, aAlign);
561 else {
562 mAnchorContent = nsnull;
563 mPopupAnchor = POPUPALIGNMENT_NONE;
564 mPopupAlignment = POPUPALIGNMENT_NONE;
565 mScreenXPos = aXPos;
566 mScreenYPos = aYPos;
567 mXPos = aXPos;
568 mYPos = aYPos;
572 void
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())
586 return;
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);
604 PRBool
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())
620 return PR_FALSE;
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));
629 else {
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"));
637 if (sound)
638 sound->PlaySystemSound(NS_SYSSOUND_MENU_POPUP);
642 mShouldAutoPosition = PR_TRUE;
643 return hasChildren;
646 void
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)
654 return;
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;
661 return;
664 mPopupState = aNewState;
666 if (IsMenu())
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();
686 PRInt32 state;
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);
698 void
699 nsMenuPopupFrame::InvalidateInternal(const nsRect& aDamageRect,
700 nscoord aX, nscoord aY, nsIFrame* aForChild,
701 PRUint32 aFlags)
703 InvalidateRoot(aDamageRect + nsPoint(aX, aY), aFlags);
706 void
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.
717 nsIView*
718 nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame)
720 nsIView* view = aStartFrame->GetClosestView();
721 NS_ASSERTION(view, "frame must have a closest view!");
722 while (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();
727 if (widget) {
728 nsWindowType wtype;
729 widget->GetWindowType(wtype);
730 if (wtype == eWindowType_popup) {
731 return view;
735 nsIView* temp = view->GetParent();
736 if (!temp) {
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.
739 return view;
741 view = temp;
744 return nsnull;
747 nsPoint
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
760 nsPoint pnt;
761 switch (popupAnchor) {
762 case POPUPALIGNMENT_TOPLEFT:
763 pnt = anchorRect.TopLeft();
764 break;
765 case POPUPALIGNMENT_TOPRIGHT:
766 pnt = anchorRect.TopRight();
767 break;
768 case POPUPALIGNMENT_BOTTOMLEFT:
769 pnt = anchorRect.BottomLeft();
770 break;
771 case POPUPALIGNMENT_BOTTOMRIGHT:
772 pnt = anchorRect.BottomRight();
773 break;
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.
780 nsMargin margin;
781 GetStyleMargin()->GetMargin(margin);
782 switch (popupAlign) {
783 case POPUPALIGNMENT_TOPLEFT:
784 pnt.MoveBy(margin.left, margin.top);
785 break;
786 case POPUPALIGNMENT_TOPRIGHT:
787 pnt.MoveBy(-mRect.width - margin.right, margin.top);
788 break;
789 case POPUPALIGNMENT_BOTTOMLEFT:
790 pnt.MoveBy(margin.left, -mRect.height - margin.bottom);
791 break;
792 case POPUPALIGNMENT_BOTTOMRIGHT:
793 pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom);
794 break;
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);
808 return pnt;
811 nscoord
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.
824 if (aFlip) {
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;
831 else {
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;
842 else {
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.
849 if (aFlip) {
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;
856 else {
857 popupSize = aScreenEnd - aScreenPoint;
860 else {
861 // flip such that the popup is to the left or top of the anchor point
862 // instead.
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;
874 else {
875 aScreenPoint = aScreenEnd - aSize;
879 return popupSize;
882 nsresult
883 nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame)
885 if (!mShouldAutoPosition && !mInContentShell)
886 return NS_OK;
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.
897 if (!aAnchorFrame) {
898 if (mAnchorContent) {
899 nsCOMPtr<nsIDocument> document = mAnchorContent->GetDocument();
900 if (document) {
901 nsIPresShell *shell = document->GetPrimaryShell();
902 if (!shell)
903 return NS_ERROR_FAILURE;
905 aAnchorFrame = shell->GetPrimaryFrameFor(mAnchorContent);
909 if (!aAnchorFrame) {
910 aAnchorFrame = rootFrame;
911 if (!aAnchorFrame)
912 return NS_OK;
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
944 nsPoint screenPoint;
946 // For anchored popups, the anchor rectangle. For non-anchored popups, the
947 // size will be 0.
948 nsRect anchorRect = parentRect;
950 // indicators of whether the popup should be flipped or resized.
951 PRBool hFlip = PR_FALSE, vFlip = PR_FALSE;
953 nsMargin margin;
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
963 // coordinate.
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);
978 else {
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);
989 else {
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
1017 vFlip = PR_TRUE;
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
1023 // screen.
1024 nsRect screenRect;
1025 if (mMenuCanOverlapOSBar)
1026 devContext->GetRect(screenRect);
1027 else
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));
1083 if (sizedToPopup) {
1084 nsBoxLayoutState state(PresContext());
1085 // XXXndeakin can parentSize.width still extend outside?
1086 SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height));
1089 return NS_OK;
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)))
1113 return PR_FALSE;
1114 #endif
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))
1119 return PR_FALSE;
1123 return PR_TRUE;
1126 static nsIScrollableView* GetScrollableViewForFrame(nsIFrame* aFrame)
1128 nsIScrollableFrame* sf = do_QueryFrame(aFrame);
1129 if (!sf)
1130 return nsnull;
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)
1138 if ( ! aStart )
1139 return nsnull;
1141 nsIFrame* currFrame;
1142 nsIScrollableView* scrollableView=nsnull;
1144 // try start frame and siblings
1145 currFrame=aStart;
1146 do {
1147 scrollableView = GetScrollableViewForFrame(currFrame);
1148 if ( scrollableView )
1149 return scrollableView;
1150 currFrame = currFrame->GetNextSibling();
1151 } while ( currFrame );
1153 // try children
1154 nsIFrame* childFrame;
1155 currFrame=aStart;
1156 do {
1157 childFrame = currFrame->GetFirstChild(nsnull);
1158 scrollableView=GetScrollableView(childFrame);
1159 if ( scrollableView )
1160 return scrollableView;
1161 currFrame = currFrame->GetNextSibling();
1162 } while ( currFrame );
1164 return nsnull;
1167 void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem)
1169 if (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);
1180 // scroll down
1181 if ( itemRect.y + itemRect.height > scrollY + viewRect.height )
1182 scrollableView->ScrollTo(scrollX, itemRect.y + itemRect.height - viewRect.height, 0);
1184 // scroll up
1185 else if ( itemRect.y < scrollY )
1186 scrollableView->ScrollTo(scrollX, itemRect.y, 0);
1191 NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
1193 if (mCurrentMenu == aMenuItem)
1194 return NS_OK;
1196 if (mCurrentMenu) {
1197 mCurrentMenu->SelectMenu(PR_FALSE);
1200 if (aMenuItem) {
1201 EnsureMenuItemIsVisible(aMenuItem);
1202 aMenuItem->SelectMenu(PR_TRUE);
1205 mCurrentMenu = aMenuItem;
1207 return NS_OK;
1210 void
1211 nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
1213 mCurrentMenu = nsnull;
1216 NS_IMETHODIMP
1217 nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
1218 PRBool aSelectFirstItem)
1220 if (mCurrentMenu == aMenuItem)
1221 return NS_OK;
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))
1227 return NS_OK;
1229 // Unset the current child.
1230 if (mCurrentMenu) {
1231 mCurrentMenu->SelectMenu(PR_FALSE);
1232 nsMenuPopupFrame* popup = mCurrentMenu->GetPopup();
1233 if (popup) {
1234 if (mCurrentMenu->IsOpen()) {
1235 if (pm)
1236 pm->HidePopupAfterDelay(popup);
1241 // Set the new child.
1242 if (aMenuItem) {
1243 EnsureMenuItemIsVisible(aMenuItem);
1244 aMenuItem->SelectMenu(PR_TRUE);
1247 mCurrentMenu = aMenuItem;
1249 return NS_OK;
1252 nsMenuFrame*
1253 nsMenuPopupFrame::Enter()
1255 mIncrementalString.Truncate();
1257 // Give it to the child.
1258 if (mCurrentMenu)
1259 return mCurrentMenu->Enter();
1261 return nsnull;
1264 nsMenuFrame*
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;
1282 PRBool isShortcut;
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);
1300 return nsnull;
1302 else {
1303 #ifdef XP_WIN
1304 nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
1305 if (soundInterface)
1306 soundInterface->Beep();
1307 #endif // #ifdef XP_WIN
1310 return nsnull;
1312 else {
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;
1317 else {
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]) {
1326 charIndex++;
1328 if (charIndex == stringLength) {
1329 incrementalString.Truncate(1);
1330 stringLength = 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
1348 while (currFrame) {
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);
1364 else
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
1372 matchCount++;
1373 if (isShortcut) {
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);
1379 if (!foundActive) {
1380 // It's a first candidate item located before/on the current item
1381 if (!frameBefore)
1382 frameBefore = static_cast<nsMenuFrame *>(currFrame);
1384 else {
1385 // It's a first candidate item located after the current item
1386 if (!frameAfter)
1387 frameAfter = static_cast<nsMenuFrame *>(currFrame);
1390 else
1391 return nsnull;
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)
1402 return 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
1414 return frameAfter;
1415 else if (frameBefore) // If we haven't, use the item before the current
1416 return frameBefore;
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
1422 #ifdef XP_WIN
1423 // behavior on Windows - this item is in a menu popup off of the
1424 // menu bar, so beep and do nothing else
1425 if (isMenu) {
1426 nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
1427 if (soundInterface)
1428 soundInterface->Beep();
1430 #endif // #ifdef XP_WIN
1432 return nsnull;
1435 NS_IMETHODIMP
1436 nsMenuPopupFrame::GetWidget(nsIWidget **aWidget)
1438 nsIView * view = GetRootViewForPopup(this);
1439 if (!view)
1440 return NS_OK;
1442 *aWidget = view->GetWidget();
1443 NS_IF_ADDREF(*aWidget);
1444 return NS_OK;
1447 void
1448 nsMenuPopupFrame::AttachedDismissalListener()
1450 mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT;
1453 // helpers /////////////////////////////////////////////////////////////
1455 NS_IMETHODIMP
1456 nsMenuPopupFrame::AttributeChanged(PRInt32 aNameSpaceID,
1457 nsIAtom* aAttribute,
1458 PRInt32 aModType)
1461 nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
1462 aModType);
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) {
1471 EnsureWidget();
1472 PresContext()->PresShell()->FrameConstructor()->
1473 AddLazyChildren(mContent, LazyGeneratePopupDone, nsnull, PR_TRUE);
1476 return rv;
1479 void
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);
1489 PRInt32 err1, err2;
1490 mScreenXPos = left.ToInteger(&err1);
1491 mScreenYPos = top.ToInteger(&err2);
1493 if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2))
1494 MoveToInternal(mScreenXPos, mScreenYPos);
1497 void
1498 nsMenuPopupFrame::Destroy()
1500 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1501 if (pm)
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();
1513 void
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()) {
1524 return;
1526 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, PR_FALSE);
1527 if (!weakFrame.IsAlive()) {
1528 return;
1531 MoveToInternal(aLeft, aTop);
1534 void
1535 nsMenuPopupFrame::MoveToInternal(PRInt32 aLeft, PRInt32 aTop)
1537 // just don't support moving popups for content shells
1538 if (mInContentShell)
1539 return;
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();
1555 if (widget)
1556 widget->Move(aLeft - screenPos.x, aTop - screenPos.y);
1559 PRBool
1560 nsMenuPopupFrame::GetAutoPosition()
1562 return mShouldAutoPosition;
1565 void
1566 nsMenuPopupFrame::SetAutoPosition(PRBool aShouldAutoPosition)
1568 mShouldAutoPosition = aShouldAutoPosition;
1571 void
1572 nsMenuPopupFrame::SetConsumeRollupEvent(PRUint32 aConsumeMode)
1574 mConsumeRollupEvent = aConsumeMode;