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
5 : *
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/
10 : *
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.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
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.
22 : *
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>
28 : *
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.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : #include "nsMenuPopupFrame.h"
44 : #include "nsGkAtoms.h"
45 : #include "nsIContent.h"
46 : #include "prtypes.h"
47 : #include "nsIAtom.h"
48 : #include "nsPresContext.h"
49 : #include "nsStyleContext.h"
50 : #include "nsCSSRendering.h"
51 : #include "nsINameSpaceManager.h"
52 : #include "nsIViewManager.h"
53 : #include "nsWidgetsCID.h"
54 : #include "nsMenuFrame.h"
55 : #include "nsMenuBarFrame.h"
56 : #include "nsPopupSetFrame.h"
57 : #include "nsEventDispatcher.h"
58 : #include "nsPIDOMWindow.h"
59 : #include "nsIDOMScreen.h"
60 : #include "nsIPresShell.h"
61 : #include "nsFrameManager.h"
62 : #include "nsIDocument.h"
63 : #include "nsRect.h"
64 : #include "nsIComponentManager.h"
65 : #include "nsBoxLayoutState.h"
66 : #include "nsIScrollableFrame.h"
67 : #include "nsGUIEvent.h"
68 : #include "nsIRootBox.h"
69 : #include "nsIDocShellTreeItem.h"
70 : #include "nsReadableUtils.h"
71 : #include "nsUnicharUtils.h"
72 : #include "nsLayoutUtils.h"
73 : #include "nsContentUtils.h"
74 : #include "nsCSSFrameConstructor.h"
75 : #include "nsEventStateManager.h"
76 : #include "nsIPopupBoxObject.h"
77 : #include "nsPIWindowRoot.h"
78 : #include "nsIReflowCallback.h"
79 : #include "nsBindingManager.h"
80 : #include "nsIDocShellTreeOwner.h"
81 : #include "nsIBaseWindow.h"
82 : #include "nsISound.h"
83 : #include "nsIScreenManager.h"
84 : #include "nsIServiceManager.h"
85 : #include "nsThemeConstants.h"
86 : #include "nsDisplayList.h"
87 : #include "mozilla/Preferences.h"
88 : #include "mozilla/LookAndFeel.h"
89 :
90 : using namespace mozilla;
91 :
92 : PRInt8 nsMenuPopupFrame::sDefaultLevelIsTop = -1;
93 :
94 : // NS_NewMenuPopupFrame
95 : //
96 : // Wrapper for creating a new menu popup container
97 : //
98 : nsIFrame*
99 0 : NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
100 : {
101 0 : return new (aPresShell) nsMenuPopupFrame (aPresShell, aContext);
102 : }
103 :
104 0 : NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
105 :
106 0 : NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
107 0 : NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
108 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
109 :
110 : //
111 : // nsMenuPopupFrame ctor
112 : //
113 0 : nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext)
114 : :nsBoxFrame(aShell, aContext),
115 : mCurrentMenu(nsnull),
116 : mPrefSize(-1, -1),
117 : mLastClientOffset(0, 0),
118 : mPopupType(ePopupTypePanel),
119 : mPopupState(ePopupClosed),
120 : mPopupAlignment(POPUPALIGNMENT_NONE),
121 : mPopupAnchor(POPUPALIGNMENT_NONE),
122 : mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT),
123 : mFlipBoth(false),
124 : mIsOpenChanged(false),
125 : mIsContextMenu(false),
126 : mAdjustOffsetForContextMenu(false),
127 : mGeneratedChildren(false),
128 : mMenuCanOverlapOSBar(false),
129 : mShouldAutoPosition(true),
130 : mInContentShell(true),
131 : mIsMenuLocked(false),
132 : mIsDragPopup(false),
133 : mHFlip(false),
134 0 : mVFlip(false)
135 : {
136 : // the preference name is backwards here. True means that the 'top' level is
137 : // the default, and false means that the 'parent' level is the default.
138 0 : if (sDefaultLevelIsTop >= 0)
139 0 : return;
140 : sDefaultLevelIsTop =
141 0 : Preferences::GetBool("ui.panel.default_level_parent", false);
142 : } // ctor
143 :
144 :
145 : NS_IMETHODIMP
146 0 : nsMenuPopupFrame::Init(nsIContent* aContent,
147 : nsIFrame* aParent,
148 : nsIFrame* aPrevInFlow)
149 : {
150 0 : nsresult rv = nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
151 0 : NS_ENSURE_SUCCESS(rv, rv);
152 :
153 : // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
154 : // look&feel object
155 : mMenuCanOverlapOSBar =
156 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0;
157 :
158 0 : rv = CreatePopupView();
159 0 : NS_ENSURE_SUCCESS(rv, rv);
160 :
161 : // XXX Hack. The popup's view should float above all other views,
162 : // so we use the nsIView::SetFloating() to tell the view manager
163 : // about that constraint.
164 0 : nsIView* ourView = GetView();
165 0 : nsIViewManager* viewManager = ourView->GetViewManager();
166 0 : viewManager->SetViewFloating(ourView, true);
167 :
168 0 : mPopupType = ePopupTypePanel;
169 0 : nsIDocument* doc = aContent->OwnerDoc();
170 : PRInt32 namespaceID;
171 0 : nsCOMPtr<nsIAtom> tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID);
172 0 : if (namespaceID == kNameSpaceID_XUL) {
173 0 : if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup)
174 0 : mPopupType = ePopupTypeMenu;
175 0 : else if (tag == nsGkAtoms::tooltip)
176 0 : mPopupType = ePopupTypeTooltip;
177 : }
178 :
179 0 : if (mPopupType == ePopupTypePanel &&
180 : aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
181 0 : nsGkAtoms::drag, eIgnoreCase)) {
182 0 : mIsDragPopup = true;
183 : }
184 :
185 0 : nsCOMPtr<nsISupports> cont = PresContext()->GetContainer();
186 0 : nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont);
187 0 : PRInt32 type = -1;
188 0 : if (dsti && NS_SUCCEEDED(dsti->GetItemType(&type)) &&
189 : type == nsIDocShellTreeItem::typeChrome)
190 0 : mInContentShell = false;
191 :
192 : // To improve performance, create the widget for the popup only if it is not
193 : // a leaf. Leaf popups such as menus will create their widgets later when
194 : // the popup opens.
195 0 : if (!IsLeaf() && !ourView->HasWidget()) {
196 0 : CreateWidgetForView(ourView);
197 : }
198 :
199 0 : if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) &&
200 : aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default,
201 0 : nsGkAtoms::_true, eIgnoreCase)) {
202 : nsIRootBox* rootBox =
203 0 : nsIRootBox::GetRootBox(PresContext()->GetPresShell());
204 0 : if (rootBox) {
205 0 : rootBox->SetDefaultTooltip(aContent);
206 : }
207 : }
208 :
209 0 : return rv;
210 : }
211 :
212 : bool
213 0 : nsMenuPopupFrame::IsNoAutoHide() const
214 : {
215 : // Panels with noautohide="true" don't hide when the mouse is clicked
216 : // outside of them, or when another application is made active. Non-autohide
217 : // panels cannot be used in content windows.
218 0 : return (!mInContentShell && mPopupType == ePopupTypePanel &&
219 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide,
220 0 : nsGkAtoms::_true, eIgnoreCase));
221 : }
222 :
223 : nsPopupLevel
224 0 : nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const
225 : {
226 : // The popup level is determined as follows, in this order:
227 : // 1. non-panels (menus and tooltips) are always topmost
228 : // 2. any specified level attribute
229 : // 3. if a titlebar attribute is set, use the 'floating' level
230 : // 4. if this is a noautohide panel, use the 'parent' level
231 : // 5. use the platform-specific default level
232 :
233 : // If this is not a panel, this is always a top-most popup.
234 0 : if (mPopupType != ePopupTypePanel)
235 0 : return ePopupLevelTop;
236 :
237 : // If the level attribute has been set, use that.
238 : static nsIContent::AttrValuesArray strings[] =
239 : {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nsnull};
240 0 : switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::level,
241 0 : strings, eCaseMatters)) {
242 : case 0:
243 0 : return ePopupLevelTop;
244 : case 1:
245 0 : return ePopupLevelParent;
246 : case 2:
247 0 : return ePopupLevelFloating;
248 : }
249 :
250 : // Panels with titlebars most likely want to be floating popups.
251 0 : if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar))
252 0 : return ePopupLevelFloating;
253 :
254 : // If this panel is a noautohide panel, the default is the parent level.
255 0 : if (aIsNoAutoHide)
256 0 : return ePopupLevelParent;
257 :
258 : // Otherwise, the result depends on the platform.
259 0 : return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent;
260 : }
261 :
262 : void
263 0 : nsMenuPopupFrame::EnsureWidget()
264 : {
265 0 : nsIView* ourView = GetView();
266 0 : if (!ourView->HasWidget()) {
267 0 : NS_ASSERTION(!mGeneratedChildren && !GetFirstPrincipalChild(),
268 : "Creating widget for MenuPopupFrame with children");
269 0 : CreateWidgetForView(ourView);
270 : }
271 0 : }
272 :
273 : nsresult
274 0 : nsMenuPopupFrame::CreateWidgetForView(nsIView* aView)
275 : {
276 : // Create a widget for ourselves.
277 0 : nsWidgetInitData widgetData;
278 0 : widgetData.mWindowType = eWindowType_popup;
279 0 : widgetData.mBorderStyle = eBorderStyle_default;
280 0 : widgetData.clipSiblings = true;
281 0 : widgetData.mPopupHint = mPopupType;
282 0 : widgetData.mNoAutoHide = IsNoAutoHide();
283 0 : widgetData.mIsDragPopup = mIsDragPopup;
284 :
285 0 : nsAutoString title;
286 0 : if (mContent && widgetData.mNoAutoHide) {
287 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar,
288 0 : nsGkAtoms::normal, eCaseMatters)) {
289 0 : widgetData.mBorderStyle = eBorderStyle_title;
290 :
291 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
292 :
293 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close,
294 0 : nsGkAtoms::_true, eCaseMatters)) {
295 : widgetData.mBorderStyle =
296 0 : static_cast<enum nsBorderStyle>(widgetData.mBorderStyle | eBorderStyle_close);
297 : }
298 : }
299 : }
300 :
301 0 : nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this);
302 0 : bool viewHasTransparentContent = !mInContentShell &&
303 : (eTransparencyTransparent ==
304 0 : mode);
305 0 : nsIContent* parentContent = GetContent()->GetParent();
306 0 : nsIAtom *tag = nsnull;
307 0 : if (parentContent)
308 0 : tag = parentContent->Tag();
309 0 : widgetData.mDropShadow = !(viewHasTransparentContent || tag == nsGkAtoms::menulist);
310 0 : widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide);
311 :
312 : // panels which have a parent level need a parent widget. This allows them to
313 : // always appear in front of the parent window but behind other windows that
314 : // should be in front of it.
315 0 : nsCOMPtr<nsIWidget> parentWidget;
316 0 : if (widgetData.mPopupLevel != ePopupLevelTop) {
317 0 : nsCOMPtr<nsISupports> cont = PresContext()->GetContainer();
318 0 : nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont);
319 0 : if (!dsti)
320 0 : return NS_ERROR_FAILURE;
321 :
322 0 : nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
323 0 : dsti->GetTreeOwner(getter_AddRefs(treeOwner));
324 0 : if (!treeOwner) return NS_ERROR_FAILURE;
325 :
326 0 : nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(treeOwner));
327 0 : if (baseWindow)
328 0 : baseWindow->GetMainWidget(getter_AddRefs(parentWidget));
329 : }
330 :
331 : nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget,
332 0 : true, true);
333 0 : if (NS_FAILED(rv)) {
334 0 : return rv;
335 : }
336 :
337 0 : nsIWidget* widget = aView->GetWidget();
338 0 : widget->SetTransparencyMode(mode);
339 0 : widget->SetWindowShadowStyle(GetShadowStyle());
340 :
341 : // most popups don't have a title so avoid setting the title if there isn't one
342 0 : if (!title.IsEmpty()) {
343 0 : widget->SetTitle(title);
344 : }
345 :
346 0 : return NS_OK;
347 : }
348 :
349 : PRUint8
350 0 : nsMenuPopupFrame::GetShadowStyle()
351 : {
352 0 : PRUint8 shadow = GetStyleUIReset()->mWindowShadow;
353 0 : if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT)
354 0 : return shadow;
355 :
356 0 : switch (GetStyleDisplay()->mAppearance) {
357 : case NS_THEME_TOOLTIP:
358 0 : return NS_STYLE_WINDOW_SHADOW_TOOLTIP;
359 : case NS_THEME_MENUPOPUP:
360 0 : return NS_STYLE_WINDOW_SHADOW_MENU;
361 : }
362 0 : return NS_STYLE_WINDOW_SHADOW_DEFAULT;
363 : }
364 :
365 : // this class is used for dispatching popupshown events asynchronously.
366 : class nsXULPopupShownEvent : public nsRunnable
367 0 : {
368 : public:
369 0 : nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext)
370 0 : : mPopup(aPopup), mPresContext(aPresContext)
371 : {
372 0 : }
373 :
374 0 : NS_IMETHOD Run()
375 : {
376 0 : nsMouseEvent event(true, NS_XUL_POPUP_SHOWN, nsnull, nsMouseEvent::eReal);
377 0 : return nsEventDispatcher::Dispatch(mPopup, mPresContext, &event);
378 : }
379 :
380 : private:
381 : nsCOMPtr<nsIContent> mPopup;
382 : nsRefPtr<nsPresContext> mPresContext;
383 : };
384 :
385 : NS_IMETHODIMP
386 0 : nsMenuPopupFrame::SetInitialChildList(ChildListID aListID,
387 : nsFrameList& aChildList)
388 : {
389 : // unless the list is empty, indicate that children have been generated.
390 0 : if (aChildList.NotEmpty())
391 0 : mGeneratedChildren = true;
392 0 : return nsBoxFrame::SetInitialChildList(aListID, aChildList);
393 : }
394 :
395 : bool
396 0 : nsMenuPopupFrame::IsLeaf() const
397 : {
398 0 : if (mGeneratedChildren)
399 0 : return false;
400 :
401 0 : if (mPopupType != ePopupTypeMenu) {
402 : // any panel with a type attribute, such as the autocomplete popup,
403 : // is always generated right away.
404 0 : return !mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::type);
405 : }
406 :
407 : // menu popups generate their child frames lazily only when opened, so
408 : // behave like a leaf frame. However, generate child frames normally if
409 : // the parent menu has a sizetopopup attribute. In this case the size of
410 : // the parent menu is dependent on the size of the popup, so the frames
411 : // need to exist in order to calculate this size.
412 0 : nsIContent* parentContent = mContent->GetParent();
413 : return (parentContent &&
414 0 : !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup));
415 : }
416 :
417 : void
418 0 : nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup)
419 : {
420 0 : if (!mGeneratedChildren)
421 0 : return;
422 :
423 0 : bool shouldPosition = true;
424 0 : bool isOpen = IsOpen();
425 0 : if (!isOpen) {
426 : // if the popup is not open, only do layout while showing or if the menu
427 : // is sized to the popup
428 0 : shouldPosition = (mPopupState == ePopupShowing);
429 0 : if (!shouldPosition && !aSizedToPopup)
430 0 : return;
431 : }
432 :
433 : // if the popup has just been opened, make sure the scrolled window is at 0,0
434 0 : if (mIsOpenChanged) {
435 0 : nsIScrollableFrame *scrollframe = do_QueryFrame(GetChildBox());
436 0 : if (scrollframe) {
437 0 : scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT);
438 : }
439 : }
440 :
441 : // get the preferred, minimum and maximum size. If the menu is sized to the
442 : // popup, then the popup's width is the menu's width.
443 0 : nsSize prefSize = GetPrefSize(aState);
444 0 : nsSize minSize = GetMinSize(aState);
445 0 : nsSize maxSize = GetMaxSize(aState);
446 :
447 0 : if (aSizedToPopup) {
448 0 : prefSize.width = aParentMenu->GetRect().width;
449 : }
450 0 : prefSize = BoundsCheck(minSize, prefSize, maxSize);
451 :
452 : // if the size changed then set the bounds to be the preferred size
453 0 : bool sizeChanged = (mPrefSize != prefSize);
454 0 : if (sizeChanged) {
455 0 : SetBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
456 0 : mPrefSize = prefSize;
457 : }
458 :
459 0 : if (shouldPosition) {
460 0 : SetPopupPosition(aParentMenu, false);
461 : }
462 :
463 0 : nsRect bounds(GetRect());
464 0 : Layout(aState);
465 :
466 : // if the width or height changed, readjust the popup position. This is a
467 : // special case for tooltips where the preferred height doesn't include the
468 : // real height for its inline element, but does once it is laid out.
469 : // This is bug 228673 which doesn't have a simple fix.
470 0 : if (!aParentMenu) {
471 0 : nsSize newsize = GetSize();
472 0 : if (newsize.width > bounds.width || newsize.height > bounds.height) {
473 : // the size after layout was larger than the preferred size,
474 : // so set the preferred size accordingly
475 0 : mPrefSize = newsize;
476 0 : if (isOpen) {
477 0 : SetPopupPosition(nsnull, false);
478 : }
479 : }
480 : }
481 :
482 0 : nsPresContext* pc = PresContext();
483 0 : if (isOpen) {
484 0 : nsIView* view = GetView();
485 0 : nsIViewManager* viewManager = view->GetViewManager();
486 0 : nsRect rect = GetRect();
487 0 : rect.x = rect.y = 0;
488 :
489 0 : viewManager->ResizeView(view, rect);
490 :
491 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
492 0 : mPopupState = ePopupOpenAndVisible;
493 0 : nsContainerFrame::SyncFrameViewProperties(pc, this, nsnull, view, 0);
494 : }
495 :
496 : // finally, if the popup just opened, send a popupshown event
497 0 : if (mIsOpenChanged) {
498 0 : mIsOpenChanged = false;
499 0 : nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc);
500 0 : NS_DispatchToCurrentThread(event);
501 : }
502 : }
503 :
504 : nsIContent*
505 0 : nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame)
506 : {
507 0 : while (aMenuPopupFrame) {
508 0 : if (aMenuPopupFrame->mTriggerContent)
509 0 : return aMenuPopupFrame->mTriggerContent;
510 :
511 : // check up the menu hierarchy until a popup with a trigger node is found
512 0 : nsMenuFrame* menuFrame = aMenuPopupFrame->GetParentMenu();
513 0 : if (!menuFrame)
514 0 : break;
515 :
516 0 : nsMenuParent* parentPopup = menuFrame->GetMenuParent();
517 0 : if (!parentPopup || !parentPopup->IsMenu())
518 0 : break;
519 :
520 0 : aMenuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup);
521 : }
522 :
523 0 : return nsnull;
524 : }
525 :
526 : void
527 0 : nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
528 : const nsAString& aAlign)
529 : {
530 0 : mTriggerContent = nsnull;
531 :
532 0 : if (aAnchor.EqualsLiteral("topleft"))
533 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
534 0 : else if (aAnchor.EqualsLiteral("topright"))
535 0 : mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
536 0 : else if (aAnchor.EqualsLiteral("bottomleft"))
537 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
538 0 : else if (aAnchor.EqualsLiteral("bottomright"))
539 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
540 0 : else if (aAnchor.EqualsLiteral("leftcenter"))
541 0 : mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
542 0 : else if (aAnchor.EqualsLiteral("rightcenter"))
543 0 : mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
544 0 : else if (aAnchor.EqualsLiteral("topcenter"))
545 0 : mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
546 0 : else if (aAnchor.EqualsLiteral("bottomcenter"))
547 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
548 : else
549 0 : mPopupAnchor = POPUPALIGNMENT_NONE;
550 :
551 0 : if (aAlign.EqualsLiteral("topleft"))
552 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
553 0 : else if (aAlign.EqualsLiteral("topright"))
554 0 : mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
555 0 : else if (aAlign.EqualsLiteral("bottomleft"))
556 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
557 0 : else if (aAlign.EqualsLiteral("bottomright"))
558 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
559 : else
560 0 : mPopupAlignment = POPUPALIGNMENT_NONE;
561 0 : }
562 :
563 : void
564 0 : nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
565 : nsIContent* aTriggerContent,
566 : const nsAString& aPosition,
567 : PRInt32 aXPos, PRInt32 aYPos,
568 : bool aAttributesOverride)
569 : {
570 0 : EnsureWidget();
571 :
572 0 : mPopupState = ePopupShowing;
573 0 : mAnchorContent = aAnchorContent;
574 0 : mTriggerContent = aTriggerContent;
575 0 : mXPos = aXPos;
576 0 : mYPos = aYPos;
577 0 : mAdjustOffsetForContextMenu = false;
578 :
579 : // if aAttributesOverride is true, then the popupanchor, popupalign and
580 : // position attributes on the <popup> override those values passed in.
581 : // If false, those attributes are only used if the values passed in are empty
582 0 : if (aAnchorContent) {
583 0 : nsAutoString anchor, align, position, flip;
584 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor);
585 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align);
586 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position);
587 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip);
588 :
589 0 : if (aAttributesOverride) {
590 : // if the attributes are set, clear the offset position. Otherwise,
591 : // the offset is used to adjust the position from the anchor point
592 0 : if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty())
593 0 : position.Assign(aPosition);
594 : else
595 0 : mXPos = mYPos = 0;
596 : }
597 0 : else if (!aPosition.IsEmpty()) {
598 0 : position.Assign(aPosition);
599 : }
600 :
601 0 : mFlipBoth = flip.EqualsLiteral("both");
602 :
603 0 : position.CompressWhitespace();
604 0 : PRInt32 spaceIdx = position.FindChar(' ');
605 : // if there is a space in the position, assume it is the anchor and
606 : // alignment as two separate tokens.
607 0 : if (spaceIdx >= 0) {
608 0 : InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
609 : }
610 0 : else if (position.EqualsLiteral("before_start")) {
611 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
612 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
613 : }
614 0 : else if (position.EqualsLiteral("before_end")) {
615 0 : mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
616 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
617 : }
618 0 : else if (position.EqualsLiteral("after_start")) {
619 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
620 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
621 : }
622 0 : else if (position.EqualsLiteral("after_end")) {
623 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
624 0 : mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
625 : }
626 0 : else if (position.EqualsLiteral("start_before")) {
627 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
628 0 : mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
629 : }
630 0 : else if (position.EqualsLiteral("start_after")) {
631 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
632 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
633 : }
634 0 : else if (position.EqualsLiteral("end_before")) {
635 0 : mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
636 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
637 : }
638 0 : else if (position.EqualsLiteral("end_after")) {
639 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
640 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
641 : }
642 0 : else if (position.EqualsLiteral("overlap")) {
643 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
644 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
645 : }
646 0 : else if (position.EqualsLiteral("after_pointer")) {
647 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
648 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
649 : // XXXndeakin this is supposed to anchor vertically after, but with the
650 : // horizontal position as the mouse pointer.
651 0 : mYPos += 21;
652 : }
653 : else {
654 0 : InitPositionFromAnchorAlign(anchor, align);
655 : }
656 : }
657 :
658 0 : mScreenXPos = -1;
659 0 : mScreenYPos = -1;
660 :
661 0 : if (aAttributesOverride) {
662 : // Use |left| and |top| dimension attributes to position the popup if
663 : // present, as they may have been persisted.
664 0 : nsAutoString left, top;
665 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
666 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
667 :
668 : PRInt32 err;
669 0 : if (!left.IsEmpty()) {
670 0 : PRInt32 x = left.ToInteger(&err);
671 0 : if (NS_SUCCEEDED(err))
672 0 : mScreenXPos = x;
673 : }
674 0 : if (!top.IsEmpty()) {
675 0 : PRInt32 y = top.ToInteger(&err);
676 0 : if (NS_SUCCEEDED(err))
677 0 : mScreenYPos = y;
678 : }
679 : }
680 0 : }
681 :
682 : void
683 0 : nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
684 : PRInt32 aXPos, PRInt32 aYPos,
685 : bool aIsContextMenu)
686 : {
687 0 : EnsureWidget();
688 :
689 0 : mPopupState = ePopupShowing;
690 0 : mAnchorContent = nsnull;
691 0 : mTriggerContent = aTriggerContent;
692 0 : mScreenXPos = aXPos;
693 0 : mScreenYPos = aYPos;
694 0 : mFlipBoth = false;
695 0 : mPopupAnchor = POPUPALIGNMENT_NONE;
696 0 : mPopupAlignment = POPUPALIGNMENT_NONE;
697 0 : mIsContextMenu = aIsContextMenu;
698 0 : mAdjustOffsetForContextMenu = aIsContextMenu;
699 0 : }
700 :
701 : void
702 0 : nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
703 : nsAString& aAnchor,
704 : nsAString& aAlign,
705 : PRInt32 aXPos, PRInt32 aYPos)
706 : {
707 0 : EnsureWidget();
708 :
709 0 : mPopupState = ePopupShowing;
710 0 : mAdjustOffsetForContextMenu = false;
711 0 : mFlipBoth = false;
712 :
713 : // this popup opening function is provided for backwards compatibility
714 : // only. It accepts either coordinates or an anchor and alignment value
715 : // but doesn't use both together.
716 0 : if (aXPos == -1 && aYPos == -1) {
717 0 : mAnchorContent = aAnchorContent;
718 0 : mScreenXPos = -1;
719 0 : mScreenYPos = -1;
720 0 : mXPos = 0;
721 0 : mYPos = 0;
722 0 : InitPositionFromAnchorAlign(aAnchor, aAlign);
723 : }
724 : else {
725 0 : mAnchorContent = nsnull;
726 0 : mPopupAnchor = POPUPALIGNMENT_NONE;
727 0 : mPopupAlignment = POPUPALIGNMENT_NONE;
728 0 : mScreenXPos = aXPos;
729 0 : mScreenYPos = aYPos;
730 0 : mXPos = aXPos;
731 0 : mYPos = aYPos;
732 : }
733 0 : }
734 :
735 : void
736 0 : nsMenuPopupFrame::ShowPopup(bool aIsContextMenu, bool aSelectFirstItem)
737 : {
738 0 : mIsContextMenu = aIsContextMenu;
739 :
740 0 : if (mPopupState == ePopupShowing) {
741 0 : mPopupState = ePopupOpen;
742 0 : mIsOpenChanged = true;
743 :
744 0 : nsMenuFrame* menuFrame = GetParentMenu();
745 0 : if (menuFrame) {
746 0 : nsWeakFrame weakFrame(this);
747 0 : menuFrame->PopupOpened();
748 0 : if (!weakFrame.IsAlive())
749 : return;
750 : }
751 :
752 : // do we need an actual reflow here?
753 : // is SetPopupPosition all that is needed?
754 0 : PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
755 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
756 :
757 0 : if (mPopupType == ePopupTypeMenu) {
758 0 : nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
759 0 : if (sound)
760 0 : sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
761 : }
762 : }
763 :
764 0 : mShouldAutoPosition = true;
765 : }
766 :
767 : void
768 0 : nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState)
769 : {
770 0 : NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
771 : "popup being set to unexpected state");
772 :
773 : // don't hide the popup when it isn't open
774 0 : if (mPopupState == ePopupClosed || mPopupState == ePopupShowing)
775 0 : return;
776 :
777 : // clear the trigger content if the popup is being closed. But don't clear
778 : // it if the popup is just being made invisible as a popuphiding or command
779 : // event may want to retrieve it.
780 0 : if (aNewState == ePopupClosed) {
781 : // if the popup had a trigger node set, clear the global window popup node
782 : // as well
783 0 : if (mTriggerContent) {
784 0 : nsIDocument* doc = mContent->GetCurrentDoc();
785 0 : if (doc) {
786 0 : nsPIDOMWindow* win = doc->GetWindow();
787 0 : if (win) {
788 0 : nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
789 0 : if (root) {
790 0 : root->SetPopupNode(nsnull);
791 : }
792 : }
793 : }
794 : }
795 0 : mTriggerContent = nsnull;
796 0 : mAnchorContent = nsnull;
797 : }
798 :
799 : // when invisible and about to be closed, HidePopup has already been called,
800 : // so just set the new state to closed and return
801 0 : if (mPopupState == ePopupInvisible) {
802 0 : if (aNewState == ePopupClosed)
803 0 : mPopupState = ePopupClosed;
804 0 : return;
805 : }
806 :
807 0 : mPopupState = aNewState;
808 :
809 0 : if (IsMenu())
810 0 : SetCurrentMenuItem(nsnull);
811 :
812 0 : mIncrementalString.Truncate();
813 :
814 0 : LockMenuUntilClosed(false);
815 :
816 0 : mIsOpenChanged = false;
817 0 : mCurrentMenu = nsnull; // make sure no current menu is set
818 0 : mHFlip = mVFlip = false;
819 :
820 0 : nsIView* view = GetView();
821 0 : nsIViewManager* viewManager = view->GetViewManager();
822 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
823 0 : viewManager->ResizeView(view, nsRect(0, 0, 0, 0));
824 :
825 0 : FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent);
826 :
827 : // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
828 : // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
829 : // This code may not the best solution, but we can leave it here until we find the better approach.
830 0 : NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
831 0 : nsEventStates state = mContent->AsElement()->State();
832 :
833 0 : if (state.HasState(NS_EVENT_STATE_HOVER)) {
834 0 : nsEventStateManager *esm = PresContext()->EventStateManager();
835 0 : esm->SetContentState(nsnull, NS_EVENT_STATE_HOVER);
836 : }
837 :
838 0 : nsMenuFrame* menuFrame = GetParentMenu();
839 0 : if (menuFrame) {
840 0 : menuFrame->PopupClosed(aDeselectMenu);
841 : }
842 : }
843 :
844 : void
845 0 : nsMenuPopupFrame::InvalidateInternal(const nsRect& aDamageRect,
846 : nscoord aX, nscoord aY, nsIFrame* aForChild,
847 : PRUint32 aFlags)
848 : {
849 0 : InvalidateRoot(aDamageRect + nsPoint(aX, aY), aFlags);
850 0 : }
851 :
852 : void
853 0 : nsMenuPopupFrame::GetLayoutFlags(PRUint32& aFlags)
854 : {
855 0 : aFlags = NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY;
856 0 : }
857 :
858 : ///////////////////////////////////////////////////////////////////////////////
859 : // GetRootViewForPopup
860 : // Retrieves the view for the popup widget that contains the given frame.
861 : // If the given frame is not contained by a popup widget, return the
862 : // root view of the root viewmanager.
863 : nsIView*
864 0 : nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame)
865 : {
866 0 : nsIView* view = aStartFrame->GetClosestView();
867 0 : NS_ASSERTION(view, "frame must have a closest view!");
868 0 : while (view) {
869 : // Walk up the view hierarchy looking for a view whose widget has a
870 : // window type of eWindowType_popup - in other words a popup window
871 : // widget. If we find one, this is the view we want.
872 0 : nsIWidget* widget = view->GetWidget();
873 0 : if (widget) {
874 : nsWindowType wtype;
875 0 : widget->GetWindowType(wtype);
876 0 : if (wtype == eWindowType_popup) {
877 0 : return view;
878 : }
879 : }
880 :
881 0 : nsIView* temp = view->GetParent();
882 0 : if (!temp) {
883 : // Otherwise, we've walked all the way up to the root view and not
884 : // found a view for a popup window widget. Just return the root view.
885 0 : return view;
886 : }
887 0 : view = temp;
888 : }
889 :
890 0 : return nsnull;
891 : }
892 :
893 : nsPoint
894 0 : nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect,
895 : FlipStyle& aHFlip, FlipStyle& aVFlip)
896 : {
897 : // flip the anchor and alignment for right-to-left
898 0 : PRInt8 popupAnchor(mPopupAnchor);
899 0 : PRInt8 popupAlign(mPopupAlignment);
900 0 : if (IsDirectionRTL()) {
901 : // no need to flip the centered anchor types
902 0 : if (popupAnchor < POPUPALIGNMENT_LEFTCENTER) {
903 0 : popupAnchor = -popupAnchor;
904 : }
905 0 : popupAlign = -popupAlign;
906 : }
907 :
908 : // first, determine at which corner of the anchor the popup should appear
909 0 : nsPoint pnt;
910 0 : switch (popupAnchor) {
911 : case POPUPALIGNMENT_LEFTCENTER:
912 0 : pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
913 0 : anchorRect.y = pnt.y;
914 0 : anchorRect.height = 0;
915 0 : break;
916 : case POPUPALIGNMENT_RIGHTCENTER:
917 0 : pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
918 0 : anchorRect.y = pnt.y;
919 0 : anchorRect.height = 0;
920 0 : break;
921 : case POPUPALIGNMENT_TOPCENTER:
922 0 : pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
923 0 : anchorRect.x = pnt.x;
924 0 : anchorRect.width = 0;
925 0 : break;
926 : case POPUPALIGNMENT_BOTTOMCENTER:
927 0 : pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
928 0 : anchorRect.x = pnt.x;
929 0 : anchorRect.width = 0;
930 0 : break;
931 : case POPUPALIGNMENT_TOPRIGHT:
932 0 : pnt = anchorRect.TopRight();
933 0 : break;
934 : case POPUPALIGNMENT_BOTTOMLEFT:
935 0 : pnt = anchorRect.BottomLeft();
936 0 : break;
937 : case POPUPALIGNMENT_BOTTOMRIGHT:
938 0 : pnt = anchorRect.BottomRight();
939 0 : break;
940 : case POPUPALIGNMENT_TOPLEFT:
941 : default:
942 0 : pnt = anchorRect.TopLeft();
943 0 : break;
944 : }
945 :
946 : // If the alignment is on the right edge of the popup, move the popup left
947 : // by the width. Similarly, if the alignment is on the bottom edge of the
948 : // popup, move the popup up by the height. In addition, account for the
949 : // margins of the popup on the edge on which it is aligned.
950 0 : nsMargin margin(0, 0, 0, 0);
951 0 : GetStyleMargin()->GetMargin(margin);
952 0 : switch (popupAlign) {
953 : case POPUPALIGNMENT_TOPRIGHT:
954 0 : pnt.MoveBy(-mRect.width - margin.right, margin.top);
955 0 : break;
956 : case POPUPALIGNMENT_BOTTOMLEFT:
957 0 : pnt.MoveBy(margin.left, -mRect.height - margin.bottom);
958 0 : break;
959 : case POPUPALIGNMENT_BOTTOMRIGHT:
960 0 : pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom);
961 0 : break;
962 : case POPUPALIGNMENT_TOPLEFT:
963 : default:
964 0 : pnt.MoveBy(margin.left, margin.top);
965 0 : break;
966 : }
967 :
968 : // Flipping horizontally is allowed as long as the popup is above or below
969 : // the anchor. This will happen if both the anchor and alignment are top or
970 : // both are bottom, but different values. Similarly, flipping vertically is
971 : // allowed if the popup is to the left or right of the anchor. In this case,
972 : // the values of the constants are such that both must be positive or both
973 : // must be negative. A special case, used for overlap, allows flipping
974 : // vertically as well.
975 : // If we are flipping in both directions, we want to set a flip style both
976 : // horizontally and vertically. However, we want to flip on the inside edge
977 : // of the anchor. Consider the example of a typical dropdown menu.
978 : // Vertically, we flip the popup on the outside edges of the anchor menu,
979 : // however horizontally, we want to to use the inside edges so the popup
980 : // still appears underneath the anchor menu instead of floating off the
981 : // side of the menu.
982 0 : if (popupAnchor >= POPUPALIGNMENT_LEFTCENTER) {
983 0 : if (popupAnchor == POPUPALIGNMENT_LEFTCENTER ||
984 : popupAnchor == POPUPALIGNMENT_RIGHTCENTER) {
985 0 : aHFlip = FlipStyle_Outside;
986 0 : aVFlip = FlipStyle_Inside;
987 : }
988 : else {
989 0 : aHFlip = FlipStyle_Inside;
990 0 : aVFlip = FlipStyle_Outside;
991 : }
992 : }
993 : else {
994 0 : FlipStyle anchorEdge = mFlipBoth ? FlipStyle_Inside : FlipStyle_None;
995 0 : aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
996 0 : if (((popupAnchor > 0) == (popupAlign > 0)) ||
997 : (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
998 0 : aVFlip = FlipStyle_Outside;
999 : else
1000 0 : aVFlip = anchorEdge;
1001 : }
1002 :
1003 : return pnt;
1004 : }
1005 :
1006 : nscoord
1007 0 : nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
1008 : nscoord aScreenBegin, nscoord aScreenEnd,
1009 : nscoord aAnchorBegin, nscoord aAnchorEnd,
1010 : nscoord aMarginBegin, nscoord aMarginEnd,
1011 : nscoord aOffsetForContextMenu, FlipStyle aFlip,
1012 : bool* aFlipSide)
1013 : {
1014 : // all of the coordinates used here are in app units relative to the screen
1015 0 : nscoord popupSize = aSize;
1016 0 : if (aScreenPoint < aScreenBegin) {
1017 : // at its current position, the popup would extend past the left or top
1018 : // edge of the screen, so it will have to be moved or resized.
1019 0 : if (aFlip) {
1020 : // for inside flips, we flip on the opposite side of the anchor
1021 0 : nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1022 0 : nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1023 :
1024 : // check whether there is more room to the left and right (or top and
1025 : // bottom) of the anchor and put the popup on the side with more room.
1026 0 : if (startpos - aScreenBegin >= aScreenEnd - endpos) {
1027 0 : aScreenPoint = aScreenBegin;
1028 0 : popupSize = startpos - aScreenPoint - aMarginEnd;
1029 : }
1030 : else {
1031 : // flip such that the popup is to the right or bottom of the anchor
1032 : // point instead. However, when flipping use the same margin size.
1033 0 : *aFlipSide = true;
1034 0 : aScreenPoint = endpos + aMarginEnd;
1035 : // check if the new position is still off the right or bottom edge of
1036 : // the screen. If so, resize the popup.
1037 0 : if (aScreenPoint + aSize > aScreenEnd) {
1038 0 : popupSize = aScreenEnd - aScreenPoint;
1039 : }
1040 : }
1041 : }
1042 : else {
1043 0 : aScreenPoint = aScreenBegin;
1044 : }
1045 : }
1046 0 : else if (aScreenPoint + aSize > aScreenEnd) {
1047 : // at its current position, the popup would extend past the right or
1048 : // bottom edge of the screen, so it will have to be moved or resized.
1049 0 : if (aFlip) {
1050 : // for inside flips, we flip on the opposite side of the anchor
1051 0 : nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1052 0 : nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1053 :
1054 : // check whether there is more room to the left and right (or top and
1055 : // bottom) of the anchor and put the popup on the side with more room.
1056 0 : if (aScreenEnd - endpos >= startpos - aScreenBegin) {
1057 0 : if (mIsContextMenu) {
1058 0 : aScreenPoint = aScreenEnd - aSize;
1059 : }
1060 : else {
1061 0 : aScreenPoint = endpos + aMarginBegin;
1062 0 : popupSize = aScreenEnd - aScreenPoint;
1063 : }
1064 : }
1065 : else {
1066 : // flip such that the popup is to the left or top of the anchor point
1067 : // instead.
1068 0 : *aFlipSide = true;
1069 0 : aScreenPoint = startpos - aSize - aMarginBegin - aOffsetForContextMenu;
1070 :
1071 : // check if the new position is still off the left or top edge of the
1072 : // screen. If so, resize the popup.
1073 0 : if (aScreenPoint < aScreenBegin) {
1074 0 : aScreenPoint = aScreenBegin;
1075 0 : if (!mIsContextMenu) {
1076 0 : popupSize = startpos - aScreenPoint - aMarginBegin;
1077 : }
1078 : }
1079 : }
1080 : }
1081 : else {
1082 0 : aScreenPoint = aScreenEnd - aSize;
1083 : }
1084 : }
1085 :
1086 : // Make sure that the point is within the screen boundaries and that the
1087 : // size isn't off the edge of the screen. This can happen when a large
1088 : // positive or negative margin is used.
1089 0 : if (aScreenPoint < aScreenBegin) {
1090 0 : aScreenPoint = aScreenBegin;
1091 : }
1092 0 : if (aScreenPoint > aScreenEnd) {
1093 0 : aScreenPoint = aScreenEnd - aSize;
1094 : }
1095 :
1096 : // If popupSize ended up being negative, or the original size was actually
1097 : // smaller than the calculated popup size, just use the original size instead.
1098 0 : if (popupSize <= 0 || aSize < popupSize) {
1099 0 : popupSize = aSize;
1100 : }
1101 0 : return NS_MIN(popupSize, aScreenEnd - aScreenPoint);
1102 : }
1103 :
1104 : nsresult
1105 0 : nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove)
1106 : {
1107 0 : if (!mShouldAutoPosition)
1108 0 : return NS_OK;
1109 :
1110 : // If this is due to a move, return early if the popup hasn't been laid out
1111 : // yet. On Windows, this can happen when using a drag popup before it opens.
1112 0 : if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
1113 0 : return NS_OK;
1114 : }
1115 :
1116 0 : nsPresContext* presContext = PresContext();
1117 0 : nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame();
1118 0 : NS_ASSERTION(rootFrame->GetView() && GetView() &&
1119 : rootFrame->GetView() == GetView()->GetParent(),
1120 : "rootFrame's view is not our view's parent???");
1121 :
1122 : // if the frame is not specified, use the anchor node passed to OpenPopup. If
1123 : // that wasn't specified either, use the root frame. Note that mAnchorContent
1124 : // might be a different document so its presshell must be used.
1125 0 : if (!aAnchorFrame) {
1126 0 : if (mAnchorContent) {
1127 0 : aAnchorFrame = mAnchorContent->GetPrimaryFrame();
1128 : }
1129 :
1130 0 : if (!aAnchorFrame) {
1131 0 : aAnchorFrame = rootFrame;
1132 0 : if (!aAnchorFrame)
1133 0 : return NS_OK;
1134 : }
1135 : }
1136 :
1137 0 : bool sizedToPopup = false;
1138 0 : if (aAnchorFrame->GetContent()) {
1139 : // the popup should be the same size as the anchor menu, for example, a menulist.
1140 0 : sizedToPopup = nsMenuFrame::IsSizedToPopup(aAnchorFrame->GetContent(), false);
1141 : }
1142 :
1143 : // the dimensions of the anchor in its app units
1144 0 : nsRect parentRect = aAnchorFrame->GetScreenRectInAppUnits();
1145 :
1146 : // the anchor may be in a different document with a different scale,
1147 : // so adjust the size so that it is in the app units of the popup instead
1148 : // of the anchor.
1149 : parentRect = parentRect.ConvertAppUnitsRoundOut(
1150 0 : aAnchorFrame->PresContext()->AppUnitsPerDevPixel(),
1151 0 : presContext->AppUnitsPerDevPixel());
1152 :
1153 : // Set the popup's size to the preferred size. Below, this size will be
1154 : // adjusted to fit on the screen or within the content area. If the anchor
1155 : // is sized to the popup, use the anchor's width instead of the preferred
1156 : // width. The preferred size should already be set by the parent frame.
1157 0 : NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
1158 : "preferred size of popup not set");
1159 0 : mRect.width = sizedToPopup ? parentRect.width : mPrefSize.width;
1160 0 : mRect.height = mPrefSize.height;
1161 :
1162 : // the screen position in app units where the popup should appear
1163 0 : nsPoint screenPoint;
1164 :
1165 : // For anchored popups, the anchor rectangle. For non-anchored popups, the
1166 : // size will be 0.
1167 0 : nsRect anchorRect = parentRect;
1168 :
1169 : // indicators of whether the popup should be flipped or resized.
1170 0 : FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None;
1171 :
1172 0 : nsMargin margin(0, 0, 0, 0);
1173 0 : GetStyleMargin()->GetMargin(margin);
1174 :
1175 : // the screen rectangle of the root frame, in dev pixels.
1176 0 : nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
1177 :
1178 0 : nsDeviceContext* devContext = presContext->DeviceContext();
1179 0 : nscoord offsetForContextMenu = 0;
1180 :
1181 0 : if (IsAnchored()) {
1182 : // if we are anchored, there are certain things we don't want to do when
1183 : // repositioning the popup to fit on the screen, such as end up positioned
1184 : // over the anchor, for instance a popup appearing over the menu label.
1185 : // When doing this reposition, we want to move the popup to the side with
1186 : // the most room. The combination of anchor and alignment dictate if we
1187 : // readjust above/below or to the left/right.
1188 0 : if (mAnchorContent) {
1189 : // move the popup according to the anchor and alignment. This will also
1190 : // tell us which axis the popup is flush against in case we have to move
1191 : // it around later. The AdjustPositionForAnchorAlign method accounts for
1192 : // the popup's margin.
1193 0 : screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip);
1194 : }
1195 : else {
1196 : // with no anchor, the popup is positioned relative to the root frame
1197 0 : anchorRect = rootScreenRect;
1198 0 : screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top);
1199 : }
1200 :
1201 : // mXPos and mYPos specify an additonal offset passed to OpenPopup that
1202 : // should be added to the position
1203 0 : if (IsDirectionRTL())
1204 0 : screenPoint.x -= presContext->CSSPixelsToAppUnits(mXPos);
1205 : else
1206 0 : screenPoint.x += presContext->CSSPixelsToAppUnits(mXPos);
1207 0 : screenPoint.y += presContext->CSSPixelsToAppUnits(mYPos);
1208 :
1209 : // If this is a noautohide popup, set the screen coordinates of the popup.
1210 : // This way, the popup stays at the location where it was opened even when
1211 : // the window is moved. Popups at the parent level follow the parent
1212 : // window as it is moved and remained anchored, so we want to maintain the
1213 : // anchoring instead.
1214 0 : if (IsNoAutoHide() && PopupLevel(true) != ePopupLevelParent) {
1215 : // Account for the margin that will end up being added to the screen coordinate
1216 : // the next time SetPopupPosition is called.
1217 0 : mScreenXPos = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left);
1218 0 : mScreenYPos = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top);
1219 : }
1220 : }
1221 : else {
1222 : // the popup is positioned at a screen coordinate.
1223 : // first convert the screen position in mScreenXPos and mScreenYPos from
1224 : // CSS pixels into device pixels, ignoring any scaling as mScreenXPos and
1225 : // mScreenYPos are unscaled screen coordinates.
1226 0 : PRInt32 factor = devContext->UnscaledAppUnitsPerDevPixel();
1227 :
1228 : // context menus should be offset by two pixels so that they don't appear
1229 : // directly where the cursor is. Otherwise, it is too easy to have the
1230 : // context menu close up again.
1231 0 : if (mAdjustOffsetForContextMenu) {
1232 : PRInt32 offsetForContextMenuDev =
1233 0 : nsPresContext::CSSPixelsToAppUnits(2) / factor;
1234 0 : offsetForContextMenu = presContext->DevPixelsToAppUnits(offsetForContextMenuDev);
1235 : }
1236 :
1237 : // next, convert into app units accounting for the scaling
1238 : screenPoint.x = presContext->DevPixelsToAppUnits(
1239 0 : nsPresContext::CSSPixelsToAppUnits(mScreenXPos) / factor);
1240 : screenPoint.y = presContext->DevPixelsToAppUnits(
1241 0 : nsPresContext::CSSPixelsToAppUnits(mScreenYPos) / factor);
1242 0 : anchorRect = nsRect(screenPoint, nsSize(0, 0));
1243 :
1244 : // add the margins on the popup
1245 : screenPoint.MoveBy(margin.left + offsetForContextMenu,
1246 0 : margin.top + offsetForContextMenu);
1247 :
1248 : // screen positioned popups can be flipped vertically but never horizontally
1249 0 : vFlip = FlipStyle_Outside;
1250 : }
1251 :
1252 : // If a panel is being moved, don't constrain or flip it. But always do this for
1253 : // content shells, so that the popup doesn't extend outside the containing frame.
1254 0 : if (mInContentShell || !aIsMove || mPopupType != ePopupTypePanel) {
1255 0 : nsRect screenRect = GetConstraintRect(anchorRect, rootScreenRect);
1256 :
1257 : // ensure that anchorRect is on screen
1258 0 : if (!anchorRect.IntersectRect(anchorRect, screenRect)) {
1259 0 : anchorRect.width = anchorRect.height = 0;
1260 : // if the anchor isn't within the screen, move it to the edge of the screen.
1261 0 : if (anchorRect.x < screenRect.x)
1262 0 : anchorRect.x = screenRect.x;
1263 0 : if (anchorRect.XMost() > screenRect.XMost())
1264 0 : anchorRect.x = screenRect.XMost();
1265 0 : if (anchorRect.y < screenRect.y)
1266 0 : anchorRect.y = screenRect.y;
1267 0 : if (anchorRect.YMost() > screenRect.YMost())
1268 0 : anchorRect.y = screenRect.YMost();
1269 : }
1270 :
1271 : // shrink the the popup down if it is larger than the screen size
1272 0 : if (mRect.width > screenRect.width)
1273 0 : mRect.width = screenRect.width;
1274 0 : if (mRect.height > screenRect.height)
1275 0 : mRect.height = screenRect.height;
1276 :
1277 : // at this point the anchor (anchorRect) is within the available screen
1278 : // area (screenRect) and the popup is known to be no larger than the screen.
1279 : // Next, check if there is enough space to show the popup at full size when
1280 : // positioned at screenPoint. If not, flip the popups to the opposite side
1281 : // of their anchor point, or resize them as necessary.
1282 : mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
1283 : screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
1284 0 : margin.left, margin.right, offsetForContextMenu, hFlip, &mHFlip);
1285 :
1286 : mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
1287 : screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
1288 0 : margin.top, margin.bottom, offsetForContextMenu, vFlip, &mVFlip);
1289 :
1290 0 : NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y &&
1291 : screenPoint.x + mRect.width <= screenRect.XMost() &&
1292 : screenPoint.y + mRect.height <= screenRect.YMost(),
1293 : "Popup is offscreen");
1294 : }
1295 :
1296 : // determine the x and y position of the view by subtracting the desired
1297 : // screen position from the screen position of the root frame.
1298 0 : nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft();
1299 :
1300 : // snap the view's position to device pixels, see bug 622507
1301 0 : viewPoint.x = presContext->RoundAppUnitsToNearestDevPixels(viewPoint.x);
1302 0 : viewPoint.y = presContext->RoundAppUnitsToNearestDevPixels(viewPoint.y);
1303 :
1304 0 : nsIView* view = GetView();
1305 0 : NS_ASSERTION(view, "popup with no view");
1306 :
1307 : // Offset the position by the width and height of the borders and titlebar.
1308 : // Even though GetClientOffset should return (0, 0) when there is no
1309 : // titlebar or borders, we skip these calculations anyway for non-panels
1310 : // to save time since they will never have a titlebar.
1311 0 : nsIWidget* widget = view->GetWidget();
1312 0 : if (mPopupType == ePopupTypePanel && widget) {
1313 0 : mLastClientOffset = widget->GetClientOffset();
1314 0 : viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x);
1315 0 : viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y);
1316 : }
1317 :
1318 0 : presContext->GetPresShell()->GetViewManager()->
1319 0 : MoveViewTo(view, viewPoint.x, viewPoint.y);
1320 :
1321 : // Now that we've positioned the view, sync up the frame's origin.
1322 0 : nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
1323 :
1324 0 : if (sizedToPopup) {
1325 0 : nsBoxLayoutState state(PresContext());
1326 : // XXXndeakin can parentSize.width still extend outside?
1327 0 : SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height));
1328 : }
1329 :
1330 0 : return NS_OK;
1331 : }
1332 :
1333 : /* virtual */ nsMenuFrame*
1334 0 : nsMenuPopupFrame::GetCurrentMenuItem()
1335 : {
1336 0 : return mCurrentMenu;
1337 : }
1338 :
1339 : nsRect
1340 0 : nsMenuPopupFrame::GetConstraintRect(const nsRect& aAnchorRect,
1341 : const nsRect& aRootScreenRect)
1342 : {
1343 0 : nsIntRect screenRectPixels;
1344 0 : nsPresContext* presContext = PresContext();
1345 :
1346 : // determine the available screen space. It will be reduced by the OS chrome
1347 : // such as menubars. It addition, for content shells, it will be the area of
1348 : // the content rather than the screen.
1349 0 : nsCOMPtr<nsIScreen> screen;
1350 0 : nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
1351 0 : if (sm) {
1352 : // for content shells, get the screen where the root frame is located.
1353 : // This is because we need to constrain the content to this content area,
1354 : // so we should use the same screen. Otherwise, use the screen where the
1355 : // anchor is located.
1356 0 : nsRect rect = mInContentShell ? aRootScreenRect : aAnchorRect;
1357 0 : PRInt32 width = rect.width > 0 ? presContext->AppUnitsToDevPixels(rect.width) : 1;
1358 0 : PRInt32 height = rect.height > 0 ? presContext->AppUnitsToDevPixels(rect.height) : 1;
1359 0 : sm->ScreenForRect(presContext->AppUnitsToDevPixels(rect.x),
1360 : presContext->AppUnitsToDevPixels(rect.y),
1361 0 : width, height, getter_AddRefs(screen));
1362 0 : if (screen) {
1363 : // get the total screen area if the popup is allowed to overlap it.
1364 0 : if (mMenuCanOverlapOSBar && !mInContentShell)
1365 0 : screen->GetRect(&screenRectPixels.x, &screenRectPixels.y,
1366 0 : &screenRectPixels.width, &screenRectPixels.height);
1367 : else
1368 0 : screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y,
1369 0 : &screenRectPixels.width, &screenRectPixels.height);
1370 : }
1371 : }
1372 :
1373 : // keep a 3 pixel margin to the right and bottom of the screen for the WinXP dropshadow
1374 0 : screenRectPixels.SizeTo(screenRectPixels.width - 3, screenRectPixels.height - 3);
1375 :
1376 0 : nsRect screenRect = screenRectPixels.ToAppUnits(presContext->AppUnitsPerDevPixel());
1377 0 : if (mInContentShell) {
1378 : // for content shells, clip to the client area rather than the screen area
1379 0 : screenRect.IntersectRect(screenRect, aRootScreenRect);
1380 : }
1381 :
1382 : return screenRect;
1383 : }
1384 :
1385 0 : void nsMenuPopupFrame::CanAdjustEdges(PRInt8 aHorizontalSide, PRInt8 aVerticalSide, nsIntPoint& aChange)
1386 : {
1387 0 : PRInt8 popupAlign(mPopupAlignment);
1388 0 : if (IsDirectionRTL()) {
1389 0 : popupAlign = -popupAlign;
1390 : }
1391 :
1392 0 : if (aHorizontalSide == (mHFlip ? NS_SIDE_RIGHT : NS_SIDE_LEFT)) {
1393 0 : if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) {
1394 0 : aChange.x = 0;
1395 : }
1396 : }
1397 0 : else if (aHorizontalSide == (mHFlip ? NS_SIDE_LEFT : NS_SIDE_RIGHT)) {
1398 0 : if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1399 0 : aChange.x = 0;
1400 : }
1401 : }
1402 :
1403 0 : if (aVerticalSide == (mVFlip ? NS_SIDE_BOTTOM : NS_SIDE_TOP)) {
1404 0 : if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) {
1405 0 : aChange.y = 0;
1406 : }
1407 : }
1408 0 : else if (aVerticalSide == (mVFlip ? NS_SIDE_TOP : NS_SIDE_BOTTOM)) {
1409 0 : if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1410 0 : aChange.y = 0;
1411 : }
1412 : }
1413 0 : }
1414 :
1415 0 : bool nsMenuPopupFrame::ConsumeOutsideClicks()
1416 : {
1417 : // If the popup has explicitly set a consume mode, honor that.
1418 0 : if (mConsumeRollupEvent != nsIPopupBoxObject::ROLLUP_DEFAULT)
1419 0 : return (mConsumeRollupEvent == nsIPopupBoxObject::ROLLUP_CONSUME);
1420 :
1421 0 : nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1422 0 : if (parentContent) {
1423 0 : nsINodeInfo *ni = parentContent->NodeInfo();
1424 0 : if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL))
1425 0 : return true; // Consume outside clicks for combo boxes on all platforms
1426 : #if defined(XP_WIN) || defined(XP_OS2)
1427 : // Don't consume outside clicks for menus in Windows
1428 : if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) ||
1429 : ni->Equals(nsGkAtoms::splitmenu, kNameSpaceID_XUL) ||
1430 : ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) ||
1431 : ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) ||
1432 : ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) &&
1433 : (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1434 : nsGkAtoms::menu, eCaseMatters) ||
1435 : parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1436 : nsGkAtoms::menuButton, eCaseMatters)))) {
1437 : return false;
1438 : }
1439 : #endif
1440 0 : if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) {
1441 : // Don't consume outside clicks for autocomplete widget
1442 0 : if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1443 0 : nsGkAtoms::autocomplete, eCaseMatters))
1444 0 : return false;
1445 : }
1446 : }
1447 :
1448 0 : return true;
1449 : }
1450 :
1451 : // XXXroc this is megalame. Fossicking around for a frame of the right
1452 : // type is a recipe for disaster in the long term.
1453 0 : nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart)
1454 : {
1455 0 : if (!aStart)
1456 0 : return nsnull;
1457 :
1458 : // try start frame and siblings
1459 0 : nsIFrame* currFrame = aStart;
1460 0 : do {
1461 0 : nsIScrollableFrame* sf = do_QueryFrame(currFrame);
1462 0 : if (sf)
1463 0 : return sf;
1464 0 : currFrame = currFrame->GetNextSibling();
1465 : } while (currFrame);
1466 :
1467 : // try children
1468 0 : currFrame = aStart;
1469 0 : do {
1470 0 : nsIFrame* childFrame = currFrame->GetFirstPrincipalChild();
1471 0 : nsIScrollableFrame* sf = GetScrollFrame(childFrame);
1472 0 : if (sf)
1473 0 : return sf;
1474 0 : currFrame = currFrame->GetNextSibling();
1475 : } while (currFrame);
1476 :
1477 0 : return nsnull;
1478 : }
1479 :
1480 0 : void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem)
1481 : {
1482 0 : if (aMenuItem) {
1483 0 : aMenuItem->PresContext()->PresShell()->
1484 : ScrollFrameRectIntoView(aMenuItem,
1485 0 : nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()),
1486 : NS_PRESSHELL_SCROLL_ANYWHERE,
1487 : NS_PRESSHELL_SCROLL_ANYWHERE,
1488 : nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
1489 0 : nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
1490 : }
1491 0 : }
1492 :
1493 0 : NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
1494 : {
1495 0 : if (mCurrentMenu == aMenuItem)
1496 0 : return NS_OK;
1497 :
1498 0 : if (mCurrentMenu) {
1499 0 : mCurrentMenu->SelectMenu(false);
1500 : }
1501 :
1502 0 : if (aMenuItem) {
1503 0 : EnsureMenuItemIsVisible(aMenuItem);
1504 0 : aMenuItem->SelectMenu(true);
1505 : }
1506 :
1507 0 : mCurrentMenu = aMenuItem;
1508 :
1509 0 : return NS_OK;
1510 : }
1511 :
1512 : void
1513 0 : nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
1514 : {
1515 0 : mCurrentMenu = nsnull;
1516 0 : }
1517 :
1518 : NS_IMETHODIMP
1519 0 : nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
1520 : bool aSelectFirstItem)
1521 : {
1522 0 : if (mCurrentMenu == aMenuItem)
1523 0 : return NS_OK;
1524 :
1525 : // When a context menu is open, the current menu is locked, and no change
1526 : // to the menu is allowed.
1527 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1528 0 : if (!mIsContextMenu && pm && pm->HasContextMenu(this))
1529 0 : return NS_OK;
1530 :
1531 : // Unset the current child.
1532 0 : if (mCurrentMenu) {
1533 0 : mCurrentMenu->SelectMenu(false);
1534 0 : nsMenuPopupFrame* popup = mCurrentMenu->GetPopup();
1535 0 : if (popup) {
1536 0 : if (mCurrentMenu->IsOpen()) {
1537 0 : if (pm)
1538 0 : pm->HidePopupAfterDelay(popup);
1539 : }
1540 : }
1541 : }
1542 :
1543 : // Set the new child.
1544 0 : if (aMenuItem) {
1545 0 : EnsureMenuItemIsVisible(aMenuItem);
1546 0 : aMenuItem->SelectMenu(true);
1547 : }
1548 :
1549 0 : mCurrentMenu = aMenuItem;
1550 :
1551 0 : return NS_OK;
1552 : }
1553 :
1554 : nsMenuFrame*
1555 0 : nsMenuPopupFrame::Enter(nsGUIEvent* aEvent)
1556 : {
1557 0 : mIncrementalString.Truncate();
1558 :
1559 : // Give it to the child.
1560 0 : if (mCurrentMenu)
1561 0 : return mCurrentMenu->Enter(aEvent);
1562 :
1563 0 : return nsnull;
1564 : }
1565 :
1566 : nsMenuFrame*
1567 0 : nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction)
1568 : {
1569 : PRUint32 charCode, keyCode;
1570 0 : aKeyEvent->GetCharCode(&charCode);
1571 0 : aKeyEvent->GetKeyCode(&keyCode);
1572 :
1573 0 : doAction = false;
1574 :
1575 : // Enumerate over our list of frames.
1576 0 : nsIFrame* immediateParent = nsnull;
1577 : PresContext()->PresShell()->
1578 0 : FrameConstructor()->GetInsertionPoint(this, nsnull, &immediateParent);
1579 0 : if (!immediateParent)
1580 0 : immediateParent = this;
1581 :
1582 0 : PRUint32 matchCount = 0, matchShortcutCount = 0;
1583 0 : bool foundActive = false;
1584 : bool isShortcut;
1585 0 : nsMenuFrame* frameBefore = nsnull;
1586 0 : nsMenuFrame* frameAfter = nsnull;
1587 0 : nsMenuFrame* frameShortcut = nsnull;
1588 :
1589 0 : nsIContent* parentContent = mContent->GetParent();
1590 :
1591 : bool isMenu = parentContent &&
1592 0 : !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL);
1593 :
1594 : static DOMTimeStamp lastKeyTime = 0;
1595 : DOMTimeStamp keyTime;
1596 0 : aKeyEvent->GetTimeStamp(&keyTime);
1597 :
1598 0 : if (charCode == 0) {
1599 0 : if (keyCode == NS_VK_BACK) {
1600 0 : if (!isMenu && !mIncrementalString.IsEmpty()) {
1601 0 : mIncrementalString.SetLength(mIncrementalString.Length() - 1);
1602 0 : return nsnull;
1603 : }
1604 : else {
1605 : #ifdef XP_WIN
1606 : nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
1607 : if (soundInterface)
1608 : soundInterface->Beep();
1609 : #endif // #ifdef XP_WIN
1610 : }
1611 : }
1612 0 : return nsnull;
1613 : }
1614 : else {
1615 0 : PRUnichar uniChar = ToLowerCase(static_cast<PRUnichar>(charCode));
1616 0 : if (isMenu || // Menu supports only first-letter navigation
1617 : keyTime - lastKeyTime > INC_TYP_INTERVAL) // Interval too long, treat as new typing
1618 0 : mIncrementalString = uniChar;
1619 : else {
1620 0 : mIncrementalString.Append(uniChar);
1621 : }
1622 : }
1623 :
1624 : // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one
1625 0 : nsAutoString incrementalString(mIncrementalString);
1626 0 : PRUint32 charIndex = 1, stringLength = incrementalString.Length();
1627 0 : while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
1628 0 : charIndex++;
1629 : }
1630 0 : if (charIndex == stringLength) {
1631 0 : incrementalString.Truncate(1);
1632 0 : stringLength = 1;
1633 : }
1634 :
1635 0 : lastKeyTime = keyTime;
1636 :
1637 : nsIFrame* currFrame;
1638 : // NOTE: If you crashed here due to a bogus |immediateParent| it is
1639 : // possible that the menu whose shortcut is being looked up has
1640 : // been destroyed already. One strategy would be to
1641 : // setTimeout(<func>,0) as detailed in:
1642 : // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
1643 0 : currFrame = immediateParent->GetFirstPrincipalChild();
1644 :
1645 0 : PRInt32 menuAccessKey = -1;
1646 0 : nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
1647 :
1648 : // We start searching from first child. This process is divided into two parts
1649 : // -- before current and after current -- by the current item
1650 0 : while (currFrame) {
1651 0 : nsIContent* current = currFrame->GetContent();
1652 :
1653 : // See if it's a menu item.
1654 0 : if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, true)) {
1655 0 : nsAutoString textKey;
1656 0 : if (menuAccessKey >= 0) {
1657 : // Get the shortcut attribute.
1658 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey);
1659 : }
1660 0 : if (textKey.IsEmpty()) { // No shortcut, try first letter
1661 0 : isShortcut = false;
1662 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey);
1663 0 : if (textKey.IsEmpty()) // No label, try another attribute (value)
1664 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::value, textKey);
1665 : }
1666 : else
1667 0 : isShortcut = true;
1668 :
1669 0 : if (StringBeginsWith(textKey, incrementalString,
1670 0 : nsCaseInsensitiveStringComparator())) {
1671 : // mIncrementalString is a prefix of textKey
1672 0 : if (currFrame->GetType() == nsGkAtoms::menuFrame) {
1673 : // There is one match
1674 0 : matchCount++;
1675 0 : if (isShortcut) {
1676 : // There is one shortcut-key match
1677 0 : matchShortcutCount++;
1678 : // Record the matched item. If there is only one matched shortcut item, do it
1679 0 : frameShortcut = static_cast<nsMenuFrame *>(currFrame);
1680 : }
1681 0 : if (!foundActive) {
1682 : // It's a first candidate item located before/on the current item
1683 0 : if (!frameBefore)
1684 0 : frameBefore = static_cast<nsMenuFrame *>(currFrame);
1685 : }
1686 : else {
1687 : // It's a first candidate item located after the current item
1688 0 : if (!frameAfter)
1689 0 : frameAfter = static_cast<nsMenuFrame *>(currFrame);
1690 : }
1691 : }
1692 : else
1693 0 : return nsnull;
1694 : }
1695 :
1696 : // Get the active status
1697 0 : if (current->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
1698 0 : nsGkAtoms::_true, eCaseMatters)) {
1699 0 : foundActive = true;
1700 0 : if (stringLength > 1) {
1701 : // If there is more than one char typed, the current item has highest priority,
1702 : // otherwise the item next to current has highest priority
1703 0 : if (currFrame == frameBefore)
1704 0 : return frameBefore;
1705 : }
1706 : }
1707 : }
1708 0 : currFrame = currFrame->GetNextSibling();
1709 : }
1710 :
1711 0 : doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1));
1712 :
1713 0 : if (matchShortcutCount == 1) // We have one matched shortcut item
1714 0 : return frameShortcut;
1715 0 : if (frameAfter) // If we have matched item after the current, use it
1716 0 : return frameAfter;
1717 0 : else if (frameBefore) // If we haven't, use the item before the current
1718 0 : return frameBefore;
1719 :
1720 : // If we don't match anything, rollback the last typing
1721 0 : mIncrementalString.SetLength(mIncrementalString.Length() - 1);
1722 :
1723 : // didn't find a matching menu item
1724 : #ifdef XP_WIN
1725 : // behavior on Windows - this item is in a menu popup off of the
1726 : // menu bar, so beep and do nothing else
1727 : if (isMenu) {
1728 : nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
1729 : if (soundInterface)
1730 : soundInterface->Beep();
1731 : }
1732 : #endif // #ifdef XP_WIN
1733 :
1734 0 : return nsnull;
1735 : }
1736 :
1737 : void
1738 0 : nsMenuPopupFrame::LockMenuUntilClosed(bool aLock)
1739 : {
1740 0 : mIsMenuLocked = aLock;
1741 :
1742 : // Lock / unlock the parent, too.
1743 0 : nsIFrame* parent = GetParent();
1744 0 : if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
1745 0 : nsMenuParent* parentParent = static_cast<nsMenuFrame*>(parent)->GetMenuParent();
1746 0 : if (parentParent) {
1747 0 : parentParent->LockMenuUntilClosed(aLock);
1748 : }
1749 : }
1750 0 : }
1751 :
1752 : nsIWidget*
1753 0 : nsMenuPopupFrame::GetWidget()
1754 : {
1755 0 : nsIView * view = GetRootViewForPopup(this);
1756 0 : if (!view)
1757 0 : return nsnull;
1758 :
1759 0 : return view->GetWidget();
1760 : }
1761 :
1762 : void
1763 0 : nsMenuPopupFrame::AttachedDismissalListener()
1764 : {
1765 0 : mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT;
1766 0 : }
1767 :
1768 : nsresult
1769 0 : nsMenuPopupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1770 : const nsRect& aDirtyRect,
1771 : const nsDisplayListSet& aLists)
1772 : {
1773 : // don't pass events to drag popups
1774 0 : if (aBuilder->IsForEventDelivery() && mIsDragPopup) {
1775 0 : return NS_OK;
1776 : }
1777 :
1778 0 : return nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
1779 : }
1780 :
1781 : // helpers /////////////////////////////////////////////////////////////
1782 :
1783 : NS_IMETHODIMP
1784 0 : nsMenuPopupFrame::AttributeChanged(PRInt32 aNameSpaceID,
1785 : nsIAtom* aAttribute,
1786 : PRInt32 aModType)
1787 :
1788 : {
1789 : nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
1790 0 : aModType);
1791 :
1792 0 : if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top)
1793 0 : MoveToAttributePosition();
1794 :
1795 0 : if (aAttribute == nsGkAtoms::label) {
1796 : // set the label for the titlebar
1797 0 : nsIView* view = GetView();
1798 0 : if (view) {
1799 0 : nsIWidget* widget = view->GetWidget();
1800 0 : if (widget) {
1801 0 : nsAutoString title;
1802 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
1803 0 : if (!title.IsEmpty()) {
1804 0 : widget->SetTitle(title);
1805 : }
1806 : }
1807 : }
1808 : }
1809 :
1810 0 : return rv;
1811 : }
1812 :
1813 : void
1814 0 : nsMenuPopupFrame::MoveToAttributePosition()
1815 : {
1816 : // Move the widget around when the user sets the |left| and |top| attributes.
1817 : // Note that this is not the best way to move the widget, as it results in lots
1818 : // of FE notifications and is likely to be slow as molasses. Use |moveTo| on
1819 : // nsIPopupBoxObject if possible.
1820 0 : nsAutoString left, top;
1821 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
1822 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
1823 : PRInt32 err1, err2;
1824 0 : PRInt32 xpos = left.ToInteger(&err1);
1825 0 : PRInt32 ypos = top.ToInteger(&err2);
1826 :
1827 0 : if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2))
1828 0 : MoveTo(xpos, ypos, false);
1829 0 : }
1830 :
1831 : void
1832 0 : nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot)
1833 : {
1834 0 : nsIFrame* parent = GetParent();
1835 0 : if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
1836 : // clear the open attribute on the parent menu
1837 : nsContentUtils::AddScriptRunner(
1838 0 : new nsUnsetAttrRunnable(parent->GetContent(), nsGkAtoms::open));
1839 : }
1840 :
1841 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1842 0 : if (pm)
1843 0 : pm->PopupDestroyed(this);
1844 :
1845 : nsIRootBox* rootBox =
1846 0 : nsIRootBox::GetRootBox(PresContext()->GetPresShell());
1847 0 : if (rootBox && rootBox->GetDefaultTooltip() == mContent) {
1848 0 : rootBox->SetDefaultTooltip(nsnull);
1849 : }
1850 :
1851 0 : nsBoxFrame::DestroyFrom(aDestructRoot);
1852 0 : }
1853 :
1854 :
1855 : void
1856 0 : nsMenuPopupFrame::MoveTo(PRInt32 aLeft, PRInt32 aTop, bool aUpdateAttrs)
1857 : {
1858 0 : nsIWidget* widget = GetWidget();
1859 0 : if ((mScreenXPos == aLeft && mScreenYPos == aTop) &&
1860 0 : (!widget || widget->GetClientOffset() == mLastClientOffset)) {
1861 0 : return;
1862 : }
1863 :
1864 : // reposition the popup at the specified coordinates. Don't clear the anchor
1865 : // and position, because the popup can be reset to its anchor position by
1866 : // using (-1, -1) as coordinates. Subtract off the margin as it will be
1867 : // added to the position when SetPopupPosition is called.
1868 0 : nsMargin margin(0, 0, 0, 0);
1869 0 : GetStyleMargin()->GetMargin(margin);
1870 0 : nsPresContext* presContext = PresContext();
1871 0 : mScreenXPos = aLeft - presContext->AppUnitsToIntCSSPixels(margin.left);
1872 0 : mScreenYPos = aTop - presContext->AppUnitsToIntCSSPixels(margin.top);
1873 :
1874 0 : SetPopupPosition(nsnull, true);
1875 :
1876 0 : nsCOMPtr<nsIContent> popup = mContent;
1877 0 : if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
1878 0 : popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
1879 : {
1880 0 : nsAutoString left, top;
1881 0 : left.AppendInt(aLeft);
1882 0 : top.AppendInt(aTop);
1883 0 : popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
1884 0 : popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
1885 : }
1886 : }
1887 :
1888 : bool
1889 0 : nsMenuPopupFrame::GetAutoPosition()
1890 : {
1891 0 : return mShouldAutoPosition;
1892 : }
1893 :
1894 : void
1895 0 : nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition)
1896 : {
1897 0 : mShouldAutoPosition = aShouldAutoPosition;
1898 0 : }
1899 :
1900 : void
1901 0 : nsMenuPopupFrame::SetConsumeRollupEvent(PRUint32 aConsumeMode)
1902 : {
1903 0 : mConsumeRollupEvent = aConsumeMode;
1904 0 : }
1905 :
1906 : /**
1907 : * KEEP THIS IN SYNC WITH nsContainerFrame::CreateViewForFrame
1908 : * as much as possible. Until we get rid of views finally...
1909 : */
1910 : nsresult
1911 0 : nsMenuPopupFrame::CreatePopupView()
1912 : {
1913 0 : if (HasView()) {
1914 0 : return NS_OK;
1915 : }
1916 :
1917 0 : nsIViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager();
1918 0 : NS_ASSERTION(nsnull != viewManager, "null view manager");
1919 :
1920 : // Create a view
1921 0 : nsIView* parentView = viewManager->GetRootView();
1922 0 : nsViewVisibility visibility = nsViewVisibility_kHide;
1923 0 : PRInt32 zIndex = PR_INT32_MAX;
1924 0 : bool autoZIndex = false;
1925 :
1926 0 : NS_ASSERTION(parentView, "no parent view");
1927 :
1928 : // Create a view
1929 0 : nsIView *view = viewManager->CreateView(GetRect(), parentView, visibility);
1930 0 : if (view) {
1931 0 : viewManager->SetViewZIndex(view, autoZIndex, zIndex);
1932 : // XXX put view last in document order until we can do better
1933 0 : viewManager->InsertChild(parentView, view, nsnull, true);
1934 : }
1935 :
1936 : // Remember our view
1937 0 : SetView(view);
1938 :
1939 0 : NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
1940 : ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view));
1941 :
1942 0 : if (!view)
1943 0 : return NS_ERROR_OUT_OF_MEMORY;
1944 :
1945 0 : return NS_OK;
1946 : }
|