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