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
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/
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
15 * The Original Code is mozilla.org code.
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.
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
38 #include "nsSelectsAreaFrame.h"
40 #include "nsIDOMHTMLOptionElement.h"
41 #include "nsIContent.h"
42 #include "nsListControlFrame.h"
43 #include "nsDisplayList.h"
46 NS_NewSelectsAreaFrame(nsIPresShell
* aShell
, nsStyleContext
* aContext
, PRUint32 aFlags
)
48 nsSelectsAreaFrame
* it
= new (aShell
) nsSelectsAreaFrame(aContext
);
51 // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
52 // aren't expanded by right floats outside the select.
53 it
->SetFlags(aFlags
| NS_BLOCK_FLOAT_MGR
);
59 //---------------------------------------------------------
61 nsSelectsAreaFrame::IsOptionElement(nsIContent
* aContent
)
63 PRBool result
= PR_FALSE
;
65 nsCOMPtr
<nsIDOMHTMLOptionElement
> optElem
;
66 if (NS_SUCCEEDED(aContent
->QueryInterface(NS_GET_IID(nsIDOMHTMLOptionElement
),(void**) getter_AddRefs(optElem
)))) {
67 if (optElem
!= nsnull
) {
75 //---------------------------------------------------------
77 nsSelectsAreaFrame::IsOptionElementFrame(nsIFrame
*aFrame
)
79 nsIContent
*content
= aFrame
->GetContent();
81 return IsOptionElement(content
);
87 * This wrapper class lets us redirect mouse hits from the child frame of
88 * an option element to the element's own frame.
89 * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
91 class nsDisplayOptionEventGrabber
: public nsDisplayWrapList
{
93 nsDisplayOptionEventGrabber(nsIFrame
* aFrame
, nsDisplayItem
* aItem
)
94 : nsDisplayWrapList(aFrame
, aItem
) {}
95 nsDisplayOptionEventGrabber(nsIFrame
* aFrame
, nsDisplayList
* aList
)
96 : nsDisplayWrapList(aFrame
, aList
) {}
97 virtual nsIFrame
* HitTest(nsDisplayListBuilder
* aBuilder
, nsPoint aPt
,
98 HitTestState
* aState
);
99 NS_DISPLAY_DECL_NAME("OptionEventGrabber")
101 virtual nsDisplayWrapList
* WrapWithClone(nsDisplayListBuilder
* aBuilder
,
102 nsDisplayItem
* aItem
);
105 nsIFrame
* nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder
* aBuilder
,
106 nsPoint aPt
, HitTestState
* aState
)
108 nsIFrame
* frame
= mList
.HitTest(aBuilder
, aPt
, aState
);
111 nsIFrame
* selectedFrame
= frame
;
112 while (selectedFrame
&&
113 !nsSelectsAreaFrame::IsOptionElementFrame(selectedFrame
)) {
114 selectedFrame
= selectedFrame
->GetParent();
117 return selectedFrame
;
119 // else, keep the original result, which could be this frame
125 nsDisplayWrapList
* nsDisplayOptionEventGrabber::WrapWithClone(
126 nsDisplayListBuilder
* aBuilder
, nsDisplayItem
* aItem
) {
127 return new (aBuilder
) nsDisplayOptionEventGrabber(aItem
->GetUnderlyingFrame(), aItem
);
130 class nsOptionEventGrabberWrapper
: public nsDisplayWrapper
133 nsOptionEventGrabberWrapper() {}
134 virtual nsDisplayItem
* WrapList(nsDisplayListBuilder
* aBuilder
,
135 nsIFrame
* aFrame
, nsDisplayList
* aList
) {
136 // We can't specify the underlying frame here. We need this list to be
137 // exploded if sorted.
138 return new (aBuilder
) nsDisplayOptionEventGrabber(nsnull
, aList
);
140 virtual nsDisplayItem
* WrapItem(nsDisplayListBuilder
* aBuilder
,
141 nsDisplayItem
* aItem
) {
142 return new (aBuilder
) nsDisplayOptionEventGrabber(aItem
->GetUnderlyingFrame(), aItem
);
146 static nsListControlFrame
* GetEnclosingListFrame(nsIFrame
* aSelectsAreaFrame
)
148 nsIFrame
* frame
= aSelectsAreaFrame
->GetParent();
150 if (frame
->GetType() == nsGkAtoms::listControlFrame
)
151 return static_cast<nsListControlFrame
*>(frame
);
152 frame
= frame
->GetParent();
157 class nsDisplayListFocus
: public nsDisplayItem
{
159 nsDisplayListFocus(nsSelectsAreaFrame
* aFrame
) : nsDisplayItem(aFrame
) {
160 MOZ_COUNT_CTOR(nsDisplayListFocus
);
162 #ifdef NS_BUILD_REFCNT_LOGGING
163 virtual ~nsDisplayListFocus() {
164 MOZ_COUNT_DTOR(nsDisplayListFocus
);
168 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
) {
169 // override bounds because the list item focus ring may extend outside
170 // the nsSelectsAreaFrame
171 nsListControlFrame
* listFrame
= GetEnclosingListFrame(GetUnderlyingFrame());
172 return listFrame
->GetOverflowRect() + aBuilder
->ToReferenceFrame(listFrame
);
174 virtual void Paint(nsDisplayListBuilder
* aBuilder
, nsIRenderingContext
* aCtx
,
175 const nsRect
& aDirtyRect
) {
176 nsListControlFrame
* listFrame
= GetEnclosingListFrame(GetUnderlyingFrame());
177 // listFrame must be non-null or we wouldn't get called.
178 listFrame
->PaintFocus(*aCtx
, aBuilder
->ToReferenceFrame(listFrame
));
180 NS_DISPLAY_DECL_NAME("ListFocus")
184 nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
185 const nsRect
& aDirtyRect
,
186 const nsDisplayListSet
& aLists
)
188 if (!aBuilder
->IsForEventDelivery())
189 return BuildDisplayListInternal(aBuilder
, aDirtyRect
, aLists
);
191 nsDisplayListCollection set
;
192 nsresult rv
= BuildDisplayListInternal(aBuilder
, aDirtyRect
, set
);
193 NS_ENSURE_SUCCESS(rv
, rv
);
195 nsOptionEventGrabberWrapper wrapper
;
196 return wrapper
.WrapLists(aBuilder
, this, set
, aLists
);
200 nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder
* aBuilder
,
201 const nsRect
& aDirtyRect
,
202 const nsDisplayListSet
& aLists
)
204 nsresult rv
= nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
205 NS_ENSURE_SUCCESS(rv
, rv
);
207 nsListControlFrame
* listFrame
= GetEnclosingListFrame(this);
208 if (listFrame
&& listFrame
->IsFocused()) {
209 // we can't just associate the display item with the list frame,
210 // because then the list's scrollframe won't clip it (the scrollframe
211 // only clips contained descendants).
212 return aLists
.Outlines()->AppendNewToTop(new (aBuilder
)
213 nsDisplayListFocus(this));
220 nsSelectsAreaFrame::Reflow(nsPresContext
* aPresContext
,
221 nsHTMLReflowMetrics
& aDesiredSize
,
222 const nsHTMLReflowState
& aReflowState
,
223 nsReflowStatus
& aStatus
)
225 nsListControlFrame
* list
= GetEnclosingListFrame(this);
227 "Must have an nsListControlFrame! Frame constructor is "
230 PRBool isInDropdownMode
= list
->IsInDropDownMode();
232 // See similar logic in nsListControlFrame::Reflow and
233 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
235 if (isInDropdownMode
) {
236 // Store the height now in case it changes during
237 // nsBlockFrame::Reflow for some odd reason.
238 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
239 oldHeight
= GetSize().height
;
241 oldHeight
= NS_UNCONSTRAINEDSIZE
;
245 nsresult rv
= nsBlockFrame::Reflow(aPresContext
, aDesiredSize
,
246 aReflowState
, aStatus
);
247 NS_ENSURE_SUCCESS(rv
, rv
);
249 // Check whether we need to suppress scrolbar updates. We want to do that if
250 // we're in a possible first pass and our height of a row has changed.
251 if (list
->MightNeedSecondPass()) {
252 nscoord newHeightOfARow
= list
->CalcHeightOfARow();
253 // We'll need a second pass if our height of a row changed. For
254 // comboboxes, we'll also need it if our height changed. If we're going
255 // to do a second pass, suppress scrollbar updates for this pass.
256 if (newHeightOfARow
!= mHeightOfARow
||
257 (isInDropdownMode
&& (oldHeight
!= aDesiredSize
.height
||
258 oldHeight
!= GetSize().height
))) {
259 mHeightOfARow
= newHeightOfARow
;
260 list
->SetSuppressScrollbarUpdate(PR_TRUE
);