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 : * Pierre Phaneuf <pp@ludusdesign.com>
24 : *
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.
36 : *
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"
44 :
45 : nsIFrame*
46 0 : NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, PRUint32 aFlags)
47 : {
48 0 : nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
49 :
50 0 : 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 0 : it->SetFlags(aFlags | NS_BLOCK_FLOAT_MGR);
54 : }
55 :
56 0 : return it;
57 : }
58 :
59 0 : NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
60 :
61 : //---------------------------------------------------------
62 : /**
63 : * This wrapper class lets us redirect mouse hits from the child frame of
64 : * an option element to the element's own frame.
65 : * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
66 : */
67 0 : class nsDisplayOptionEventGrabber : public nsDisplayWrapList {
68 : public:
69 0 : nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
70 : nsIFrame* aFrame, nsDisplayItem* aItem)
71 0 : : nsDisplayWrapList(aBuilder, aFrame, aItem) {}
72 0 : nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
73 : nsIFrame* aFrame, nsDisplayList* aList)
74 0 : : nsDisplayWrapList(aBuilder, aFrame, aList) {}
75 : virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
76 : HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames);
77 0 : NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER)
78 :
79 : virtual nsDisplayWrapList* WrapWithClone(nsDisplayListBuilder* aBuilder,
80 : nsDisplayItem* aItem);
81 : };
82 :
83 0 : void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder* aBuilder,
84 : const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
85 : {
86 0 : nsTArray<nsIFrame*> outFrames;
87 0 : mList.HitTest(aBuilder, aRect, aState, &outFrames);
88 :
89 0 : for (PRUint32 i = 0; i < outFrames.Length(); i++) {
90 0 : nsIFrame* selectedFrame = outFrames.ElementAt(i);
91 0 : while (selectedFrame &&
92 0 : !(selectedFrame->GetContent() &&
93 0 : selectedFrame->GetContent()->IsHTML(nsGkAtoms::option))) {
94 0 : selectedFrame = selectedFrame->GetParent();
95 : }
96 0 : if (selectedFrame) {
97 0 : aOutFrames->AppendElement(selectedFrame);
98 : } else {
99 : // keep the original result, which could be this frame
100 0 : aOutFrames->AppendElement(outFrames.ElementAt(i));
101 : }
102 : }
103 :
104 0 : }
105 :
106 0 : nsDisplayWrapList* nsDisplayOptionEventGrabber::WrapWithClone(
107 : nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
108 : return new (aBuilder)
109 0 : nsDisplayOptionEventGrabber(aBuilder, aItem->GetUnderlyingFrame(), aItem);
110 : }
111 :
112 : class nsOptionEventGrabberWrapper : public nsDisplayWrapper
113 : {
114 : public:
115 0 : nsOptionEventGrabberWrapper() {}
116 0 : virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
117 : nsIFrame* aFrame, nsDisplayList* aList) {
118 : // We can't specify the underlying frame here. We need this list to be
119 : // exploded if sorted.
120 0 : return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, nsnull, aList);
121 : }
122 0 : virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
123 : nsDisplayItem* aItem) {
124 0 : return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->GetUnderlyingFrame(), aItem);
125 : }
126 : };
127 :
128 0 : static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
129 : {
130 0 : nsIFrame* frame = aSelectsAreaFrame->GetParent();
131 0 : while (frame) {
132 0 : if (frame->GetType() == nsGkAtoms::listControlFrame)
133 0 : return static_cast<nsListControlFrame*>(frame);
134 0 : frame = frame->GetParent();
135 : }
136 0 : return nsnull;
137 : }
138 :
139 : class nsDisplayListFocus : public nsDisplayItem {
140 : public:
141 0 : nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
142 : nsSelectsAreaFrame* aFrame) :
143 0 : nsDisplayItem(aBuilder, aFrame) {
144 0 : MOZ_COUNT_CTOR(nsDisplayListFocus);
145 0 : }
146 : #ifdef NS_BUILD_REFCNT_LOGGING
147 0 : virtual ~nsDisplayListFocus() {
148 0 : MOZ_COUNT_DTOR(nsDisplayListFocus);
149 0 : }
150 : #endif
151 :
152 0 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) {
153 : // override bounds because the list item focus ring may extend outside
154 : // the nsSelectsAreaFrame
155 0 : nsListControlFrame* listFrame = GetEnclosingListFrame(GetUnderlyingFrame());
156 0 : return listFrame->GetVisualOverflowRectRelativeToSelf() +
157 0 : aBuilder->ToReferenceFrame(listFrame);
158 : }
159 0 : virtual void Paint(nsDisplayListBuilder* aBuilder,
160 : nsRenderingContext* aCtx) {
161 0 : nsListControlFrame* listFrame = GetEnclosingListFrame(GetUnderlyingFrame());
162 : // listFrame must be non-null or we wouldn't get called.
163 0 : listFrame->PaintFocus(*aCtx, aBuilder->ToReferenceFrame(listFrame));
164 0 : }
165 0 : NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
166 : };
167 :
168 : NS_IMETHODIMP
169 0 : nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
170 : const nsRect& aDirtyRect,
171 : const nsDisplayListSet& aLists)
172 : {
173 0 : if (!aBuilder->IsForEventDelivery())
174 0 : return BuildDisplayListInternal(aBuilder, aDirtyRect, aLists);
175 :
176 0 : nsDisplayListCollection set;
177 0 : nsresult rv = BuildDisplayListInternal(aBuilder, aDirtyRect, set);
178 0 : NS_ENSURE_SUCCESS(rv, rv);
179 :
180 0 : nsOptionEventGrabberWrapper wrapper;
181 0 : return wrapper.WrapLists(aBuilder, this, set, aLists);
182 : }
183 :
184 : nsresult
185 0 : nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
186 : const nsRect& aDirtyRect,
187 : const nsDisplayListSet& aLists)
188 : {
189 0 : nsresult rv = nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
190 0 : NS_ENSURE_SUCCESS(rv, rv);
191 :
192 0 : nsListControlFrame* listFrame = GetEnclosingListFrame(this);
193 0 : if (listFrame && listFrame->IsFocused()) {
194 : // we can't just associate the display item with the list frame,
195 : // because then the list's scrollframe won't clip it (the scrollframe
196 : // only clips contained descendants).
197 : return aLists.Outlines()->AppendNewToTop(new (aBuilder)
198 0 : nsDisplayListFocus(aBuilder, this));
199 : }
200 :
201 0 : return NS_OK;
202 : }
203 :
204 : NS_IMETHODIMP
205 0 : nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
206 : nsHTMLReflowMetrics& aDesiredSize,
207 : const nsHTMLReflowState& aReflowState,
208 : nsReflowStatus& aStatus)
209 : {
210 0 : nsListControlFrame* list = GetEnclosingListFrame(this);
211 0 : NS_ASSERTION(list,
212 : "Must have an nsListControlFrame! Frame constructor is "
213 : "broken");
214 :
215 0 : bool isInDropdownMode = list->IsInDropDownMode();
216 :
217 : // See similar logic in nsListControlFrame::Reflow and
218 : // nsListControlFrame::ReflowAsDropdown. We need to match it here.
219 : nscoord oldHeight;
220 0 : if (isInDropdownMode) {
221 : // Store the height now in case it changes during
222 : // nsBlockFrame::Reflow for some odd reason.
223 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
224 0 : oldHeight = GetSize().height;
225 : } else {
226 0 : oldHeight = NS_UNCONSTRAINEDSIZE;
227 : }
228 : }
229 :
230 : nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize,
231 0 : aReflowState, aStatus);
232 0 : NS_ENSURE_SUCCESS(rv, rv);
233 :
234 : // Check whether we need to suppress scrolbar updates. We want to do that if
235 : // we're in a possible first pass and our height of a row has changed.
236 0 : if (list->MightNeedSecondPass()) {
237 0 : nscoord newHeightOfARow = list->CalcHeightOfARow();
238 : // We'll need a second pass if our height of a row changed. For
239 : // comboboxes, we'll also need it if our height changed. If we're going
240 : // to do a second pass, suppress scrollbar updates for this pass.
241 0 : if (newHeightOfARow != mHeightOfARow ||
242 : (isInDropdownMode && (oldHeight != aDesiredSize.height ||
243 0 : oldHeight != GetSize().height))) {
244 0 : mHeightOfARow = newHeightOfARow;
245 0 : list->SetSuppressScrollbarUpdate(true);
246 : }
247 : }
248 :
249 0 : return rv;
250 : }
|