Backed out changeset 8366e5cc9f57 (bug 125282) because of four windows unit test...
[mozilla-central.git] / layout / forms / nsSelectsAreaFrame.cpp
blobbe7a1475c5756b0c5ab0275eb395ea27a36cbc13
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
13 * License.
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.
22 * Contributor(s):
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"
39 #include "nsCOMPtr.h"
40 #include "nsIDOMHTMLOptionElement.h"
41 #include "nsIContent.h"
42 #include "nsListControlFrame.h"
43 #include "nsDisplayList.h"
45 nsIFrame*
46 NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRUint32 aFlags)
48 nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
50 if (it) {
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);
56 return it;
59 //---------------------------------------------------------
60 PRBool
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) {
68 result = PR_TRUE;
72 return result;
75 //---------------------------------------------------------
76 PRBool
77 nsSelectsAreaFrame::IsOptionElementFrame(nsIFrame *aFrame)
79 nsIContent *content = aFrame->GetContent();
80 if (content) {
81 return IsOptionElement(content);
83 return PR_FALSE;
86 /**
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 {
92 public:
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);
110 if (frame) {
111 nsIFrame* selectedFrame = frame;
112 while (selectedFrame &&
113 !nsSelectsAreaFrame::IsOptionElementFrame(selectedFrame)) {
114 selectedFrame = selectedFrame->GetParent();
116 if (selectedFrame) {
117 return selectedFrame;
119 // else, keep the original result, which could be this frame
122 return frame;
125 nsDisplayWrapList* nsDisplayOptionEventGrabber::WrapWithClone(
126 nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
127 return new (aBuilder) nsDisplayOptionEventGrabber(aItem->GetUnderlyingFrame(), aItem);
130 class nsOptionEventGrabberWrapper : public nsDisplayWrapper
132 public:
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();
149 while (frame) {
150 if (frame->GetType() == nsGkAtoms::listControlFrame)
151 return static_cast<nsListControlFrame*>(frame);
152 frame = frame->GetParent();
154 return nsnull;
157 class nsDisplayListFocus : public nsDisplayItem {
158 public:
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);
166 #endif
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")
183 NS_IMETHODIMP
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);
199 nsresult
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));
216 return NS_OK;
219 NS_IMETHODIMP
220 nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
221 nsHTMLReflowMetrics& aDesiredSize,
222 const nsHTMLReflowState& aReflowState,
223 nsReflowStatus& aStatus)
225 nsListControlFrame* list = GetEnclosingListFrame(this);
226 NS_ASSERTION(list,
227 "Must have an nsListControlFrame! Frame constructor is "
228 "broken");
230 PRBool isInDropdownMode = list->IsInDropDownMode();
232 // See similar logic in nsListControlFrame::Reflow and
233 // nsListControlFrame::ReflowAsDropdown. We need to match it here.
234 nscoord oldHeight;
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;
240 } else {
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);
264 return rv;