layout/xul/nsMenuPopupFrame.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

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 }

mercurial