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