1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Original Author: David W. Hyatt (hyatt@netscape.com)
24 : * Mike Pinkerton (pinkerton@netscape.com)
25 : * Dean Tessman <dean_tessman@hotmail.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : //
42 : // nsMenuPopupFrame
43 : //
44 :
45 : #ifndef nsMenuPopupFrame_h__
46 : #define nsMenuPopupFrame_h__
47 :
48 : #include "prtypes.h"
49 : #include "nsIAtom.h"
50 : #include "nsGkAtoms.h"
51 : #include "nsCOMPtr.h"
52 : #include "nsMenuFrame.h"
53 : #include "nsIDOMEventTarget.h"
54 :
55 : #include "nsBoxFrame.h"
56 : #include "nsMenuParent.h"
57 :
58 : #include "nsITimer.h"
59 :
60 : class nsIWidget;
61 :
62 : // XUL popups can be in several different states. When opening a popup, the
63 : // state changes as follows:
64 : // ePopupClosed - initial state
65 : // ePopupShowing - during the period when the popupshowing event fires
66 : // ePopupOpen - between the popupshowing event and being visible. Creation
67 : // of the child frames, layout and reflow occurs in this state.
68 : // ePopupOpenAndVisible - layout is done and the popup's view and widget are
69 : // made visible. The popupshown event fires.
70 : // When closing a popup:
71 : // ePopupHidden - during the period when the popuphiding event fires and
72 : // the popup is removed.
73 : // ePopupClosed - the popup's widget is made invisible.
74 : enum nsPopupState {
75 : // state when a popup is not open
76 : ePopupClosed,
77 : // state from when a popup is requested to be shown to after the
78 : // popupshowing event has been fired.
79 : ePopupShowing,
80 : // state while a popup is open but the widget is not yet visible
81 : ePopupOpen,
82 : // state while a popup is open and visible on screen
83 : ePopupOpenAndVisible,
84 : // state from when a popup is requested to be hidden to when it is closed.
85 : ePopupHiding,
86 : // state which indicates that the popup was hidden without firing the
87 : // popuphiding or popuphidden events. It is used when executing a menu
88 : // command because the menu needs to be hidden before the command event
89 : // fires, yet the popuphiding and popuphidden events are fired after. This
90 : // state can also occur when the popup is removed because the document is
91 : // unloaded.
92 : ePopupInvisible
93 : };
94 :
95 : // How a popup may be flipped. Flipping to the outside edge is like how
96 : // a submenu would work. The entire popup is flipped to the opposite side
97 : // of the anchor.
98 : enum FlipStyle {
99 : FlipStyle_None = 0,
100 : FlipStyle_Outside = 1,
101 : FlipStyle_Inside = 2
102 : };
103 :
104 : // values are selected so that the direction can be flipped just by
105 : // changing the sign
106 : #define POPUPALIGNMENT_NONE 0
107 : #define POPUPALIGNMENT_TOPLEFT 1
108 : #define POPUPALIGNMENT_TOPRIGHT -1
109 : #define POPUPALIGNMENT_BOTTOMLEFT 2
110 : #define POPUPALIGNMENT_BOTTOMRIGHT -2
111 :
112 : #define POPUPALIGNMENT_LEFTCENTER 16
113 : #define POPUPALIGNMENT_RIGHTCENTER 17
114 : #define POPUPALIGNMENT_TOPCENTER 18
115 : #define POPUPALIGNMENT_BOTTOMCENTER 19
116 :
117 : #define INC_TYP_INTERVAL 1000 // 1s. If the interval between two keypresses is shorter than this,
118 : // treat as a continue typing
119 : // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
120 : // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
121 : // need to find a good place to put them together.
122 : // if someone changes one, please also change the other.
123 :
124 : nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
125 :
126 : class nsIViewManager;
127 : class nsIView;
128 : class nsMenuPopupFrame;
129 :
130 : class nsMenuPopupFrame : public nsBoxFrame, public nsMenuParent
131 0 : {
132 : public:
133 : NS_DECL_QUERYFRAME_TARGET(nsMenuPopupFrame)
134 : NS_DECL_QUERYFRAME
135 : NS_DECL_FRAMEARENA_HELPERS
136 :
137 : nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext);
138 :
139 : // nsMenuParent interface
140 : virtual nsMenuFrame* GetCurrentMenuItem();
141 : NS_IMETHOD SetCurrentMenuItem(nsMenuFrame* aMenuItem);
142 : virtual void CurrentMenuIsBeingDestroyed();
143 : NS_IMETHOD ChangeMenuItem(nsMenuFrame* aMenuItem, bool aSelectFirstItem);
144 :
145 : // as popups are opened asynchronously, the popup pending state is used to
146 : // prevent multiple requests from attempting to open the same popup twice
147 0 : nsPopupState PopupState() { return mPopupState; }
148 0 : void SetPopupState(nsPopupState aPopupState) { mPopupState = aPopupState; }
149 :
150 0 : NS_IMETHOD SetActive(bool aActiveFlag) { return NS_OK; } // We don't care.
151 0 : virtual bool IsActive() { return false; }
152 0 : virtual bool IsMenuBar() { return false; }
153 :
154 : /*
155 : * When this popup is open, should clicks outside of it be consumed?
156 : * Return true if the popup should rollup on an outside click,
157 : * but consume that click so it can't be used for anything else.
158 : * Return false to allow clicks outside the popup to activate content
159 : * even when the popup is open.
160 : * ---------------------------------------------------------------------
161 : *
162 : * Should clicks outside of a popup be eaten?
163 : *
164 : * Menus Autocomplete Comboboxes
165 : * Mac Eat No Eat
166 : * Win No No Eat
167 : * Unix Eat No Eat
168 : *
169 : */
170 : bool ConsumeOutsideClicks();
171 :
172 0 : virtual bool IsContextMenu() { return mIsContextMenu; }
173 :
174 0 : virtual bool MenuClosed() { return true; }
175 :
176 : virtual void LockMenuUntilClosed(bool aLock);
177 0 : virtual bool IsMenuLocked() { return mIsMenuLocked; }
178 :
179 : nsIWidget* GetWidget();
180 :
181 : // The dismissal listener gets created and attached to the window.
182 : void AttachedDismissalListener();
183 :
184 : // Overridden methods
185 : NS_IMETHOD Init(nsIContent* aContent,
186 : nsIFrame* aParent,
187 : nsIFrame* aPrevInFlow);
188 :
189 : NS_IMETHOD AttributeChanged(PRInt32 aNameSpaceID,
190 : nsIAtom* aAttribute,
191 : PRInt32 aModType);
192 :
193 : virtual void DestroyFrom(nsIFrame* aDestructRoot);
194 :
195 : virtual void InvalidateInternal(const nsRect& aDamageRect,
196 : nscoord aX, nscoord aY, nsIFrame* aForChild,
197 : PRUint32 aFlags);
198 :
199 : // returns true if the popup is a panel with the noautohide attribute set to
200 : // true. These panels do not roll up automatically.
201 : bool IsNoAutoHide() const;
202 :
203 0 : nsPopupLevel PopupLevel() const
204 : {
205 0 : return PopupLevel(IsNoAutoHide());
206 : }
207 :
208 : void EnsureWidget();
209 :
210 : nsresult CreateWidgetForView(nsIView* aView);
211 : PRUint8 GetShadowStyle();
212 :
213 : NS_IMETHOD SetInitialChildList(ChildListID aListID,
214 : nsFrameList& aChildList);
215 :
216 : virtual bool IsLeaf() const;
217 :
218 : // layout, position and display the popup as needed
219 : void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup);
220 :
221 : nsIView* GetRootViewForPopup(nsIFrame* aStartFrame);
222 :
223 : // set the position of the popup either relative to the anchor aAnchorFrame
224 : // (or the frame for mAnchorContent if aAnchorFrame is null) or at a specific
225 : // point if a screen position (mScreenXPos and mScreenYPos) are set. The popup
226 : // will be adjusted so that it is on screen. If aIsMove is true, then the popup
227 : // is being moved, and should not be flipped.
228 : nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove);
229 :
230 0 : bool HasGeneratedChildren() { return mGeneratedChildren; }
231 0 : void SetGeneratedChildren() { mGeneratedChildren = true; }
232 :
233 : // called when the Enter key is pressed while the popup is open. This will
234 : // just pass the call down to the current menu, if any. If a current menu
235 : // should be opened as a result, this method should return the frame for
236 : // that menu, or null if no menu should be opened. Also, calling Enter will
237 : // reset the current incremental search string, calculated in
238 : // FindMenuWithShortcut.
239 : nsMenuFrame* Enter(nsGUIEvent* aEvent);
240 :
241 0 : nsPopupType PopupType() const { return mPopupType; }
242 0 : bool IsMenu() { return mPopupType == ePopupTypeMenu; }
243 0 : bool IsOpen() { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; }
244 :
245 0 : bool IsDragPopup() { return mIsDragPopup; }
246 :
247 : // returns the parent menupopup, if any
248 0 : nsMenuFrame* GetParentMenu() {
249 0 : nsIFrame* parent = GetParent();
250 0 : if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
251 0 : return static_cast<nsMenuFrame *>(parent);
252 : }
253 0 : return nsnull;
254 : }
255 :
256 : static nsIContent* GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame);
257 0 : void ClearTriggerContent() { mTriggerContent = nsnull; }
258 :
259 : // returns true if the popup is in a content shell, or false for a popup in
260 : // a chrome shell
261 0 : bool IsInContentShell() { return mInContentShell; }
262 :
263 : // the Initialize methods are used to set the anchor position for
264 : // each way of opening a popup.
265 : void InitializePopup(nsIContent* aAnchorContent,
266 : nsIContent* aTriggerContent,
267 : const nsAString& aPosition,
268 : PRInt32 aXPos, PRInt32 aYPos,
269 : bool aAttributesOverride);
270 :
271 : /**
272 : * @param aIsContextMenu if true, then the popup is
273 : * positioned at a slight offset from aXPos/aYPos to ensure the
274 : * (presumed) mouse position is not over the menu.
275 : */
276 : void InitializePopupAtScreen(nsIContent* aTriggerContent,
277 : PRInt32 aXPos, PRInt32 aYPos,
278 : bool aIsContextMenu);
279 :
280 : void InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
281 : nsAString& aAnchor,
282 : nsAString& aAlign,
283 : PRInt32 aXPos, PRInt32 aYPos);
284 :
285 : // indicate that the popup should be opened
286 : void ShowPopup(bool aIsContextMenu, bool aSelectFirstItem);
287 : // indicate that the popup should be hidden. The new state should either be
288 : // ePopupClosed or ePopupInvisible.
289 : void HidePopup(bool aDeselectMenu, nsPopupState aNewState);
290 :
291 : // locate and return the menu frame that should be activated for the
292 : // supplied key event. If doAction is set to true by this method,
293 : // then the menu's action should be carried out, as if the user had pressed
294 : // the Enter key. If doAction is false, the menu should just be highlighted.
295 : // This method also handles incremental searching in menus so the user can
296 : // type the first few letters of an item/s name to select it.
297 : nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction);
298 :
299 0 : void ClearIncrementalString() { mIncrementalString.Truncate(); }
300 :
301 0 : virtual nsIAtom* GetType() const { return nsGkAtoms::menuPopupFrame; }
302 :
303 : #ifdef DEBUG
304 0 : NS_IMETHOD GetFrameName(nsAString& aResult) const
305 : {
306 0 : return MakeFrameName(NS_LITERAL_STRING("MenuPopup"), aResult);
307 : }
308 : #endif
309 :
310 : void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame);
311 :
312 : // Move the popup to the screen coordinate (aLeft, aTop). If aUpdateAttrs
313 : // is true, and the popup already has left or top attributes, then those
314 : // attributes are updated to the new location.
315 : // The frame may be destroyed by this method.
316 : void MoveTo(PRInt32 aLeft, PRInt32 aTop, bool aUpdateAttrs);
317 :
318 : bool GetAutoPosition();
319 : void SetAutoPosition(bool aShouldAutoPosition);
320 : void SetConsumeRollupEvent(PRUint32 aConsumeMode);
321 :
322 : nsIScrollableFrame* GetScrollFrame(nsIFrame* aStart);
323 :
324 : // For a popup that should appear anchored at the given rect, determine
325 : // the screen area that it is constrained by. This will be the available
326 : // area of the screen the popup should be displayed on. Content popups,
327 : // however, will also be constrained by the content area, given by
328 : // aRootScreenRect. All coordinates are in app units.
329 : nsRect GetConstraintRect(const nsRect& aAnchorRect, const nsRect& aRootScreenRect);
330 :
331 : // Determines whether the given edges of the popup may be moved, where
332 : // aHorizontalSide and aVerticalSide are one of the NS_SIDE_* constants, or
333 : // 0 for no movement in that direction. aChange is the distance to move on
334 : // those sides. If will be reset to 0 if the side cannot be adjusted at all
335 : // in that direction. For example, a popup cannot be moved if it is anchored
336 : // on a particular side.
337 : //
338 : // Later, when bug 357725 is implemented, we can make this adjust aChange by
339 : // the amount that the side can be resized, so that minimums and maximums
340 : // can be taken into account.
341 : void CanAdjustEdges(PRInt8 aHorizontalSide, PRInt8 aVerticalSide, nsIntPoint& aChange);
342 :
343 : // Return true if the popup is positioned relative to an anchor.
344 0 : bool IsAnchored() const { return mScreenXPos == -1 && mScreenYPos == -1; }
345 :
346 : // Return the anchor if there is one.
347 0 : nsIContent* GetAnchor() const { return mAnchorContent; }
348 :
349 : // Return the screen coordinates of the popup, or (-1, -1) if anchored.
350 0 : nsIntPoint ScreenPosition() const { return nsIntPoint(mScreenXPos, mScreenYPos); }
351 :
352 : NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
353 : const nsRect& aDirtyRect,
354 : const nsDisplayListSet& aLists);
355 :
356 0 : nsIntPoint GetLastClientOffset() const { return mLastClientOffset; }
357 :
358 : protected:
359 :
360 : // returns the popup's level.
361 : nsPopupLevel PopupLevel(bool aIsNoAutoHide) const;
362 :
363 : // redefine to tell the box system not to move the views.
364 : virtual void GetLayoutFlags(PRUint32& aFlags);
365 :
366 : void InitPositionFromAnchorAlign(const nsAString& aAnchor,
367 : const nsAString& aAlign);
368 :
369 : // return the position where the popup should be, when it should be
370 : // anchored at anchorRect. aHFlip and aVFlip will be set if the popup may be
371 : // flipped in that direction if there is not enough space available.
372 : nsPoint AdjustPositionForAnchorAlign(nsRect& anchorRect,
373 : FlipStyle& aHFlip, FlipStyle& aVFlip);
374 :
375 : // check if the popup will fit into the available space and resize it. This
376 : // method handles only one axis at a time so is called twice, once for
377 : // horizontal and once for vertical. All arguments are specified for this
378 : // one axis. All coordinates are in app units relative to the screen.
379 : // aScreenPoint - the point where the popup should appear
380 : // aSize - the size of the popup
381 : // aScreenBegin - the left or top edge of the screen
382 : // aScreenEnd - the right or bottom edge of the screen
383 : // aAnchorBegin - the left or top edge of the anchor rectangle
384 : // aAnchorEnd - the right or bottom edge of the anchor rectangle
385 : // aMarginBegin - the left or top margin of the popup
386 : // aMarginEnd - the right or bottom margin of the popup
387 : // aOffsetForContextMenu - the additional offset to add for context menus
388 : // aFlip - how to flip or resize the popup when there isn't space
389 : // aFlipSide - pointer to where current flip mode is stored
390 : nscoord FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
391 : nscoord aScreenBegin, nscoord aScreenEnd,
392 : nscoord aAnchorBegin, nscoord aAnchorEnd,
393 : nscoord aMarginBegin, nscoord aMarginEnd,
394 : nscoord aOffsetForContextMenu, FlipStyle aFlip,
395 : bool* aFlipSide);
396 :
397 : // Move the popup to the position specified in its |left| and |top| attributes.
398 : void MoveToAttributePosition();
399 :
400 : /**
401 : * Return whether the popup direction should be RTL.
402 : * If the popup has an anchor, its direction is the anchor direction.
403 : * Otherwise, its the general direction of the UI.
404 : *
405 : * Return whether the popup direction should be RTL.
406 : */
407 0 : bool IsDirectionRTL() const {
408 0 : return mAnchorContent && mAnchorContent->GetPrimaryFrame()
409 0 : ? mAnchorContent->GetPrimaryFrame()->GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL
410 0 : : GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
411 : }
412 :
413 : // Create a popup view for this frame. The view is added a child of the root
414 : // view, and is initially hidden.
415 : nsresult CreatePopupView();
416 :
417 : nsString mIncrementalString; // for incremental typing navigation
418 :
419 : // the content that the popup is anchored to, if any, which may be in a
420 : // different document than the popup.
421 : nsCOMPtr<nsIContent> mAnchorContent;
422 :
423 : // the content that triggered the popup, typically the node where the mouse
424 : // was clicked. It will be cleared when the popup is hidden.
425 : nsCOMPtr<nsIContent> mTriggerContent;
426 :
427 : nsMenuFrame* mCurrentMenu; // The current menu that is active.
428 :
429 : // A popup's preferred size may be different than its actual size stored in
430 : // mRect in the case where the popup was resized because it was too large
431 : // for the screen. The preferred size mPrefSize holds the full size the popup
432 : // would be before resizing. Computations are performed using this size.
433 : nsSize mPrefSize;
434 :
435 : // the position of the popup. The screen coordinates, if set to values other
436 : // than -1, override mXPos and mYPos.
437 : PRInt32 mXPos;
438 : PRInt32 mYPos;
439 : PRInt32 mScreenXPos;
440 : PRInt32 mScreenYPos;
441 : // The value of the client offset of our widget the last time we positioned
442 : // ourselves. We store this so that we can detect when it changes but the
443 : // position of our widget didn't change.
444 : nsIntPoint mLastClientOffset;
445 :
446 : nsPopupType mPopupType; // type of popup
447 : nsPopupState mPopupState; // open state of the popup
448 :
449 : // popup alignment relative to the anchor node
450 : PRInt8 mPopupAlignment;
451 : PRInt8 mPopupAnchor;
452 : // One of nsIPopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME
453 : PRInt8 mConsumeRollupEvent;
454 : bool mFlipBoth; // flip in both directions
455 :
456 : bool mIsOpenChanged; // true if the open state changed since the last layout
457 : bool mIsContextMenu; // true for context menus
458 : // true if we need to offset the popup to ensure it's not under the mouse
459 : bool mAdjustOffsetForContextMenu;
460 : bool mGeneratedChildren; // true if the contents have been created
461 :
462 : bool mMenuCanOverlapOSBar; // can we appear over the taskbar/menubar?
463 : bool mShouldAutoPosition; // Should SetPopupPosition be allowed to auto position popup?
464 : bool mInContentShell; // True if the popup is in a content shell
465 : bool mIsMenuLocked; // Should events inside this menu be ignored?
466 : bool mIsDragPopup; // True if this is a popup used for drag feedback
467 :
468 : // the flip modes that were used when the popup was opened
469 : bool mHFlip;
470 : bool mVFlip;
471 :
472 : static PRInt8 sDefaultLevelIsTop;
473 : }; // class nsMenuPopupFrame
474 :
475 : #endif
|