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 Communicator client 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>
24 * Mats Palmgren <mats.palmgren@bredband.net>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
42 #include "nsReadableUtils.h"
43 #include "nsUnicharUtils.h"
44 #include "nsListControlFrame.h"
45 #include "nsFormControlFrame.h" // for COMPARE macro
46 #include "nsGkAtoms.h"
47 #include "nsIFormControl.h"
48 #include "nsIDeviceContext.h"
49 #include "nsIDocument.h"
50 #include "nsIDOMHTMLCollection.h"
51 #include "nsIDOMHTMLOptionsCollection.h"
52 #include "nsIDOMNSHTMLOptionCollectn.h"
53 #include "nsIDOMHTMLSelectElement.h"
54 #include "nsIDOMNSHTMLSelectElement.h"
55 #include "nsIDOMHTMLOptionElement.h"
56 #include "nsComboboxControlFrame.h"
57 #include "nsIViewManager.h"
58 #include "nsIDOMHTMLOptGroupElement.h"
59 #include "nsWidgetsCID.h"
60 #include "nsIPresShell.h"
61 #include "nsHTMLParts.h"
62 #include "nsIDOMEventTarget.h"
63 #include "nsEventDispatcher.h"
64 #include "nsIEventStateManager.h"
65 #include "nsIEventListenerManager.h"
66 #include "nsIDOMKeyEvent.h"
67 #include "nsIDOMMouseEvent.h"
68 #include "nsIPrivateDOMEvent.h"
70 #include "nsISupportsPrimitives.h"
71 #include "nsIComponentManager.h"
72 #include "nsILookAndFeel.h"
73 #include "nsIFontMetrics.h"
74 #include "nsIScrollableFrame.h"
75 #include "nsIDOMEventTarget.h"
76 #include "nsIDOMNSEvent.h"
77 #include "nsGUIEvent.h"
78 #include "nsIServiceManager.h"
79 #include "nsINodeInfo.h"
81 #include "nsIAccessibilityService.h"
83 #include "nsISelectElement.h"
84 #include "nsIPrivateDOMEvent.h"
85 #include "nsCSSRendering.h"
87 #include "nsIDOMMouseListener.h"
88 #include "nsIDOMMouseMotionListener.h"
89 #include "nsIDOMKeyListener.h"
90 #include "nsLayoutUtils.h"
91 #include "nsDisplayList.h"
94 const nscoord kMaxDropDownRows
= 20; // This matches the setting for 4.x browsers
95 const PRInt32 kNothingSelected
= -1;
98 nsListControlFrame
* nsListControlFrame::mFocused
= nsnull
;
99 nsString
* nsListControlFrame::sIncrementalString
= nsnull
;
101 // Using for incremental typing navigation
102 #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
103 // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
104 // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
105 // need to find a good place to put them together.
106 // if someone changes one, please also change the other.
108 DOMTimeStamp
nsListControlFrame::gLastKeyTime
= 0;
110 /******************************************************************************
111 * nsListEventListener
112 * This class is responsible for propagating events to the nsListControlFrame.
113 * Frames are not refcounted so they can't be used as event listeners.
114 *****************************************************************************/
116 class nsListEventListener
: public nsIDOMKeyListener
,
117 public nsIDOMMouseListener
,
118 public nsIDOMMouseMotionListener
121 nsListEventListener(nsListControlFrame
*aFrame
)
124 void SetFrame(nsListControlFrame
*aFrame
) { mFrame
= aFrame
; }
128 // nsIDOMEventListener
129 NS_IMETHOD
HandleEvent(nsIDOMEvent
* aEvent
);
132 NS_IMETHOD
KeyDown(nsIDOMEvent
* aKeyEvent
);
133 NS_IMETHOD
KeyUp(nsIDOMEvent
* aKeyEvent
);
134 NS_IMETHOD
KeyPress(nsIDOMEvent
* aKeyEvent
);
136 // nsIDOMMouseListener
137 NS_IMETHOD
MouseDown(nsIDOMEvent
* aMouseEvent
);
138 NS_IMETHOD
MouseUp(nsIDOMEvent
* aMouseEvent
);
139 NS_IMETHOD
MouseClick(nsIDOMEvent
* aMouseEvent
);
140 NS_IMETHOD
MouseDblClick(nsIDOMEvent
* aMouseEvent
);
141 NS_IMETHOD
MouseOver(nsIDOMEvent
* aMouseEvent
);
142 NS_IMETHOD
MouseOut(nsIDOMEvent
* aMouseEvent
);
144 // nsIDOMMouseMotionListener
145 NS_IMETHOD
MouseMove(nsIDOMEvent
* aMouseEvent
);
146 NS_IMETHOD
DragMove(nsIDOMEvent
* aMouseEvent
);
149 nsListControlFrame
*mFrame
;
152 //---------------------------------------------------------
154 NS_NewListControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
156 nsListControlFrame
* it
=
157 new (aPresShell
) nsListControlFrame(aPresShell
, aPresShell
->GetDocument(), aContext
);
160 it
->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION
);
166 NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame
)
168 //---------------------------------------------------------
169 nsListControlFrame::nsListControlFrame(
170 nsIPresShell
* aShell
, nsIDocument
* aDocument
, nsStyleContext
* aContext
)
171 : nsHTMLScrollFrame(aShell
, aContext
, PR_FALSE
),
172 mMightNeedSecondPass(PR_FALSE
),
173 mHasPendingInterruptAtStartOfReflow(PR_FALSE
),
174 mLastDropdownComputedHeight(NS_UNCONSTRAINEDSIZE
)
176 mComboboxFrame
= nsnull
;
177 mChangesSinceDragStart
= PR_FALSE
;
178 mButtonDown
= PR_FALSE
;
180 mIsAllContentHere
= PR_FALSE
;
181 mIsAllFramesHere
= PR_FALSE
;
182 mHasBeenInitialized
= PR_FALSE
;
183 mNeedToReset
= PR_TRUE
;
184 mPostChildrenLoadedReset
= PR_FALSE
;
186 mControlSelectMode
= PR_FALSE
;
189 //---------------------------------------------------------
190 nsListControlFrame::~nsListControlFrame()
192 mComboboxFrame
= nsnull
;
195 // for Bug 47302 (remove this comment later)
197 nsListControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
199 // get the receiver interface from the browser button's content node
200 ENSURE_TRUE(mContent
);
202 // Clear the frame pointer on our event listener, just in case the
203 // event listener can outlive the frame.
205 mEventListener
->SetFrame(nsnull
);
207 mContent
->RemoveEventListenerByIID(static_cast<nsIDOMMouseListener
*>
209 NS_GET_IID(nsIDOMMouseListener
));
211 mContent
->RemoveEventListenerByIID(static_cast<nsIDOMMouseMotionListener
*>
213 NS_GET_IID(nsIDOMMouseMotionListener
));
215 mContent
->RemoveEventListenerByIID(static_cast<nsIDOMKeyListener
*>
217 NS_GET_IID(nsIDOMKeyListener
));
219 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), PR_FALSE
);
220 nsHTMLScrollFrame::DestroyFrom(aDestructRoot
);
224 nsListControlFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
225 const nsRect
& aDirtyRect
,
226 const nsDisplayListSet
& aLists
)
228 // We allow visibility:hidden <select>s to contain visible options.
230 // Don't allow painting of list controls when painting is suppressed.
231 // XXX why do we need this here? we should never reach this. Maybe
232 // because these can have widgets? Hmm
233 if (aBuilder
->IsBackgroundOnly())
236 DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
238 if (IsInDropDownMode()) {
239 NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor
) == 255,
240 "need an opaque backstop color");
241 // XXX Because we have an opaque widget and we get called to paint with
242 // this frame as the root of a stacking context we need make sure to draw
243 // some opaque color over the whole widget. (Bug 511323)
244 aLists
.BorderBackground()->AppendNewToBottom(
245 new (aBuilder
) nsDisplaySolidColor(
246 this, nsRect(aBuilder
->ToReferenceFrame(this), GetSize()),
247 mLastDropdownBackstopColor
));
250 // REVIEW: The selection visibility code that used to be here is what
251 // we already do by default.
252 // REVIEW: There was code here to paint the theme background. But as far
253 // as I can tell, we'd just paint the theme background twice because
254 // it was redundant with nsCSSRendering::PaintBackground
255 return nsHTMLScrollFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
259 * This is called by the SelectsAreaFrame, which is the same
260 * as the frame returned by GetOptionsContainer. It's the frame which is
262 * @param aPt the offset of this frame, relative to the rendering reference
265 void nsListControlFrame::PaintFocus(nsIRenderingContext
& aRC
, nsPoint aPt
)
267 if (mFocused
!= this) return;
269 // The mEndSelectionIndex is what is currently being selected
270 // use the selected index if this is kNothingSelected
271 PRInt32 focusedIndex
;
272 if (mEndSelectionIndex
== kNothingSelected
) {
273 focusedIndex
= GetSelectedIndex();
275 focusedIndex
= mEndSelectionIndex
;
278 nsPresContext
* presContext
= PresContext();
280 nsIFrame
* containerFrame
= GetOptionsContainer();
281 if (!containerFrame
) return;
283 nsIFrame
* childframe
= nsnull
;
284 nsresult result
= NS_ERROR_FAILURE
;
286 nsCOMPtr
<nsIContent
> focusedContent
;
288 nsCOMPtr
<nsIDOMNSHTMLSelectElement
> selectNSElement(do_QueryInterface(mContent
));
289 NS_ASSERTION(selectNSElement
, "Can't be null");
291 nsCOMPtr
<nsISelectElement
> selectElement(do_QueryInterface(mContent
));
292 NS_ASSERTION(selectElement
, "Can't be null");
294 // If we have a selected index then get that child frame
295 if (focusedIndex
!= kNothingSelected
) {
296 focusedContent
= GetOptionContent(focusedIndex
);
297 // otherwise we find the focusedContent's frame and scroll to it
298 if (focusedContent
) {
299 childframe
= focusedContent
->GetPrimaryFrame();
302 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectHTMLElement(do_QueryInterface(mContent
));
303 NS_ASSERTION(selectElement
, "Can't be null");
305 // Since there isn't a selected item we need to show a focus ring around the first
306 // non-disabled item and skip all the option group elements (nodes)
307 nsCOMPtr
<nsIDOMNode
> node
;
310 selectHTMLElement
->GetLength(&length
);
312 // find the first non-disabled item
313 PRBool isDisabled
= PR_TRUE
;
314 for (PRInt32 i
=0;i
<PRInt32(length
) && isDisabled
;i
++) {
315 if (NS_FAILED(selectNSElement
->Item(i
, getter_AddRefs(node
))) || !node
) {
318 if (NS_FAILED(selectElement
->IsOptionDisabled(i
, &isDisabled
))) {
332 // if we found a node use it, if not get the first child (this is for empty selects)
334 focusedContent
= do_QueryInterface(node
);
335 childframe
= focusedContent
->GetPrimaryFrame();
338 // Failing all else, try the first thing we have, but only if
339 // it's an element. Text frames need not apply.
340 childframe
= containerFrame
->GetFirstChild(nsnull
);
342 !childframe
->GetContent()->IsNodeOfType(nsINode::eELEMENT
)) {
351 // get the child rect
352 fRect
= childframe
->GetRect();
353 // get it into our coordinates
354 fRect
.MoveBy(childframe
->GetParent()->GetOffsetTo(this));
356 fRect
.x
= fRect
.y
= 0;
357 fRect
.width
= GetScrollPortRect().width
;
358 fRect
.height
= CalcFallbackRowHeight();
359 fRect
.MoveBy(containerFrame
->GetOffsetTo(this));
363 PRBool lastItemIsSelected
= PR_FALSE
;
364 if (focusedContent
) {
365 nsCOMPtr
<nsIDOMHTMLOptionElement
> domOpt
=
366 do_QueryInterface(focusedContent
);
368 domOpt
->GetSelected(&lastItemIsSelected
);
372 // set up back stop colors and then ask L&F service for the real colors
374 presContext
->LookAndFeel()->
375 GetColor(lastItemIsSelected
?
376 nsILookAndFeel::eColor_WidgetSelectForeground
:
377 nsILookAndFeel::eColor_WidgetSelectBackground
, color
);
379 nsCSSRendering::PaintFocus(presContext
, aRC
, fRect
, color
);
383 nsListControlFrame::InvalidateFocus()
385 if (mFocused
!= this)
388 nsIFrame
* containerFrame
= GetOptionsContainer();
389 if (containerFrame
) {
390 // Invalidating from the containerFrame because that's where our focus
392 // The origin of the scrollport is the origin of containerFrame.
393 nsRect invalidateArea
= containerFrame
->GetOverflowRect();
394 nsRect
emptyFallbackArea(0, 0, GetScrollPortRect().width
, CalcFallbackRowHeight());
395 invalidateArea
.UnionRect(invalidateArea
, emptyFallbackArea
);
396 containerFrame
->Invalidate(invalidateArea
);
400 NS_QUERYFRAME_HEAD(nsListControlFrame
)
401 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
402 NS_QUERYFRAME_ENTRY(nsIListControlFrame
)
403 NS_QUERYFRAME_ENTRY(nsISelectControlFrame
)
404 NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame
)
407 NS_IMETHODIMP
nsListControlFrame::GetAccessible(nsIAccessible
** aAccessible
)
409 nsCOMPtr
<nsIAccessibilityService
> accService
= do_GetService("@mozilla.org/accessibilityService;1");
412 nsCOMPtr
<nsIDOMNode
> node
= do_QueryInterface(mContent
);
413 nsCOMPtr
<nsIWeakReference
> weakShell(do_GetWeakReference(PresContext()->PresShell()));
414 return accService
->CreateHTMLListboxAccessible(node
, weakShell
, aAccessible
);
417 return NS_ERROR_FAILURE
;
422 GetMaxOptionHeight(nsIFrame
* aContainer
)
425 for (nsIFrame
* option
= aContainer
->GetFirstChild(nsnull
);
426 option
; option
= option
->GetNextSibling()) {
427 nscoord optionHeight
;
428 if (nsCOMPtr
<nsIDOMHTMLOptGroupElement
>
429 (do_QueryInterface(option
->GetContent()))) {
431 optionHeight
= GetMaxOptionHeight(option
);
434 optionHeight
= option
->GetSize().height
;
436 if (result
< optionHeight
)
437 result
= optionHeight
;
443 IsOptGroup(nsIContent
*aContent
)
445 return (aContent
->NodeInfo()->Equals(nsGkAtoms::optgroup
) &&
450 IsOption(nsIContent
*aContent
)
452 return (aContent
->NodeInfo()->Equals(nsGkAtoms::option
) &&
457 GetNumberOfOptionsRecursive(nsIContent
* aContent
)
459 PRUint32 optionCount
= 0;
460 const PRUint32 childCount
= aContent
? aContent
->GetChildCount() : 0;
461 for (PRUint32 index
= 0; index
< childCount
; ++index
) {
462 nsIContent
* child
= aContent
->GetChildAt(index
);
463 if (::IsOption(child
)) {
466 else if (::IsOptGroup(child
)) {
467 optionCount
+= ::GetNumberOfOptionsRecursive(child
);
474 GetOptGroupLabelsHeight(nsIContent
* aContent
,
478 const PRUint32 childCount
= aContent
? aContent
->GetChildCount() : 0;
479 for (PRUint32 index
= 0; index
< childCount
; ++index
) {
480 nsIContent
* child
= aContent
->GetChildAt(index
);
481 if (::IsOptGroup(child
)) {
482 PRUint32 numOptions
= ::GetNumberOfOptionsRecursive(child
);
483 nscoord optionsHeight
= aRowHeight
* numOptions
;
484 nsIFrame
* frame
= child
->GetPrimaryFrame();
485 nscoord totalHeight
= frame
? frame
->GetSize().height
: 0;
486 height
+= NS_MAX(0, totalHeight
- optionsHeight
);
492 //-----------------------------------------------------------------
493 // Main Reflow for ListBox/Dropdown
494 //-----------------------------------------------------------------
497 nsListControlFrame::CalcHeightOfARow()
499 // Calculate the height of a single row in the listbox or dropdown list by
500 // using the tallest thing in the subtree, since there may be option groups
501 // in addition to option elements, either of which may be visible or
502 // invisible, may use different fonts, etc.
503 PRInt32 heightOfARow
= GetMaxOptionHeight(GetOptionsContainer());
505 // Check to see if we have zero items (and optimize by checking
506 // heightOfARow first)
507 if (heightOfARow
== 0 && GetNumberOfOptions() == 0) {
508 heightOfARow
= CalcFallbackRowHeight();
515 nsListControlFrame::GetPrefWidth(nsIRenderingContext
*aRenderingContext
)
518 DISPLAY_PREF_WIDTH(this, result
);
520 // Always add scrollbar widths to the pref-width of the scrolled
521 // content. Combobox frames depend on this happening in the dropdown,
522 // and standalone listboxes are overflow:scroll so they need it too.
523 result
= GetScrolledFrame()->GetPrefWidth(aRenderingContext
);
524 result
= NSCoordSaturatingAdd(result
,
525 GetDesiredScrollbarSizes(PresContext(), aRenderingContext
).LeftRight());
531 nsListControlFrame::GetMinWidth(nsIRenderingContext
*aRenderingContext
)
534 DISPLAY_MIN_WIDTH(this, result
);
536 // Always add scrollbar widths to the min-width of the scrolled
537 // content. Combobox frames depend on this happening in the dropdown,
538 // and standalone listboxes are overflow:scroll so they need it too.
539 result
= GetScrolledFrame()->GetMinWidth(aRenderingContext
);
540 result
+= GetDesiredScrollbarSizes(PresContext(), aRenderingContext
).LeftRight();
546 nsListControlFrame::Reflow(nsPresContext
* aPresContext
,
547 nsHTMLReflowMetrics
& aDesiredSize
,
548 const nsHTMLReflowState
& aReflowState
,
549 nsReflowStatus
& aStatus
)
551 NS_PRECONDITION(aReflowState
.ComputedWidth() != NS_UNCONSTRAINEDSIZE
,
552 "Must have a computed width");
554 mHasPendingInterruptAtStartOfReflow
= aPresContext
->HasPendingInterrupt();
556 // If all the content and frames are here
557 // then initialize it before reflow
558 if (mIsAllContentHere
&& !mHasBeenInitialized
) {
559 if (PR_FALSE
== mIsAllFramesHere
) {
560 CheckIfAllFramesHere();
562 if (mIsAllFramesHere
&& !mHasBeenInitialized
) {
563 mHasBeenInitialized
= PR_TRUE
;
567 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
) {
568 nsFormControlFrame::RegUnRegAccessKey(this, PR_TRUE
);
571 if (IsInDropDownMode()) {
572 return ReflowAsDropdown(aPresContext
, aDesiredSize
, aReflowState
, aStatus
);
576 * Due to the fact that our intrinsic height depends on the heights of our
577 * kids, we end up having to do two-pass reflow, in general -- the first pass
578 * to find the intrinsic height and a second pass to reflow the scrollframe
579 * at that height (which will size the scrollbars correctly, etc).
581 * Naturaly, we want to avoid doing the second reflow as much as possible.
582 * We can skip it in the following cases (in all of which the first reflow is
583 * already happening at the right height):
585 * - We're reflowing with a constrained computed height -- just use that
587 * - We're not dirty and have no dirty kids and shouldn't be reflowing all
588 * kids. In this case, our cached max height of a child is not going to
590 * - We do our first reflow using our cached max height of a child, then
591 * compute the new max height and it's the same as the old one.
594 PRBool autoHeight
= (aReflowState
.ComputedHeight() == NS_UNCONSTRAINEDSIZE
);
596 mMightNeedSecondPass
= autoHeight
&&
597 (NS_SUBTREE_DIRTY(this) || aReflowState
.ShouldReflowAllKids());
599 nsHTMLReflowState
state(aReflowState
);
600 PRInt32 length
= GetNumberOfOptions();
602 nscoord oldHeightOfARow
= HeightOfARow();
604 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
) && autoHeight
) {
605 // When not doing an initial reflow, and when the height is auto, start off
606 // with our computed height set to what we'd expect our height to be.
607 nscoord computedHeight
= CalcIntrinsicHeight(oldHeightOfARow
, length
);
608 state
.ApplyMinMaxConstraints(nsnull
, &computedHeight
);
609 state
.SetComputedHeight(computedHeight
);
612 nsresult rv
= nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
,
614 NS_ENSURE_SUCCESS(rv
, rv
);
616 if (!mMightNeedSecondPass
) {
617 NS_ASSERTION(!autoHeight
|| HeightOfARow() == oldHeightOfARow
,
618 "How did our height of a row change if nothing was dirty?");
619 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
620 "Shouldn't be suppressing if we don't need a second pass!");
624 mMightNeedSecondPass
= PR_FALSE
;
626 // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
627 // will have suppressed the scrollbar update.
628 if (!IsScrollbarUpdateSuppressed()) {
629 // All done. No need to do more reflow.
630 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
631 "Shouldn't be suppressing if the height of a row has not "
636 SetSuppressScrollbarUpdate(PR_FALSE
);
638 // Gotta reflow again.
639 // XXXbz We're just changing the height here; do we need to dirty ourselves
640 // or anything like that? We might need to, per the letter of the reflow
641 // protocol, but things seem to work fine without it... Is that just an
642 // implementation detail of nsHTMLScrollFrame that we're depending on?
643 nsHTMLScrollFrame::DidReflow(aPresContext
, &state
, aStatus
);
645 // Now compute the height we want to have
646 nscoord computedHeight
= CalcIntrinsicHeight(HeightOfARow(), length
);
647 state
.ApplyMinMaxConstraints(nsnull
, &computedHeight
);
648 state
.SetComputedHeight(computedHeight
);
650 nsHTMLScrollFrame::WillReflow(aPresContext
);
652 // XXXbz to make the ascent really correct, we should add our
653 // mComputedPadding.top to it (and subtract it from descent). Need that
654 // because nsGfxScrollFrame just adds in the border....
655 return nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
659 nsListControlFrame::ReflowAsDropdown(nsPresContext
* aPresContext
,
660 nsHTMLReflowMetrics
& aDesiredSize
,
661 const nsHTMLReflowState
& aReflowState
,
662 nsReflowStatus
& aStatus
)
664 NS_PRECONDITION(aReflowState
.ComputedHeight() == NS_UNCONSTRAINEDSIZE
,
665 "We should not have a computed height here!");
667 mMightNeedSecondPass
= NS_SUBTREE_DIRTY(this) ||
668 aReflowState
.ShouldReflowAllKids();
671 nscoord oldHeightOfARow
= HeightOfARow();
674 nsHTMLReflowState
state(aReflowState
);
676 nscoord oldVisibleHeight
;
677 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
678 // When not doing an initial reflow, and when the height is auto, start off
679 // with our computed height set to what we'd expect our height to be.
680 // Note: At this point, mLastDropdownComputedHeight can be
681 // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to constrain
682 // the height. That's fine; just do the same thing as last time.
683 state
.SetComputedHeight(mLastDropdownComputedHeight
);
684 oldVisibleHeight
= GetScrolledFrame()->GetSize().height
;
686 // Set oldVisibleHeight to something that will never test true against a
688 oldVisibleHeight
= NS_UNCONSTRAINEDSIZE
;
691 nsresult rv
= nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
,
693 NS_ENSURE_SUCCESS(rv
, rv
);
695 if (!mMightNeedSecondPass
) {
696 NS_ASSERTION(oldVisibleHeight
== GetScrolledFrame()->GetSize().height
,
697 "How did our kid's height change if nothing was dirty?");
698 NS_ASSERTION(HeightOfARow() == oldHeightOfARow
,
699 "How did our height of a row change if nothing was dirty?");
700 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
701 "Shouldn't be suppressing if we don't need a second pass!");
702 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
703 "How can we avoid a second pass during first reflow?");
707 mMightNeedSecondPass
= PR_FALSE
;
709 // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
710 // will have suppressed the scrollbar update.
711 if (!IsScrollbarUpdateSuppressed()) {
712 // All done. No need to do more reflow.
713 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
714 "How can we avoid a second pass during first reflow?");
718 SetSuppressScrollbarUpdate(PR_FALSE
);
720 nscoord visibleHeight
= GetScrolledFrame()->GetSize().height
;
721 nscoord heightOfARow
= HeightOfARow();
723 // Gotta reflow again.
724 // XXXbz We're just changing the height here; do we need to dirty ourselves
725 // or anything like that? We might need to, per the letter of the reflow
726 // protocol, but things seem to work fine without it... Is that just an
727 // implementation detail of nsHTMLScrollFrame that we're depending on?
728 nsHTMLScrollFrame::DidReflow(aPresContext
, &state
, aStatus
);
730 // Now compute the height we want to have
731 mNumDisplayRows
= kMaxDropDownRows
;
732 if (visibleHeight
> mNumDisplayRows
* heightOfARow
) {
733 visibleHeight
= mNumDisplayRows
* heightOfARow
;
734 // This is an adaptive algorithm for figuring out how many rows
735 // should be displayed in the drop down. The standard size is 20 rows,
736 // but on 640x480 it is typically too big.
737 // This takes the height of the screen divides it by two and then subtracts off
738 // an estimated height of the combobox. I estimate it by taking the max element size
739 // of the drop down and multiplying it by 2 (this is arbitrary) then subtract off
740 // the border and padding of the drop down (again rather arbitrary)
741 // This all breaks down if the font of the combobox is a lot larger then the option items
742 // or CSS style has set the height of the combobox to be rather large.
743 // We can fix these cases later if they actually happen.
744 nsRect screen
= nsFormControlFrame::GetUsableScreenRect(aPresContext
);
745 nscoord screenHeight
= screen
.height
;
747 nscoord availDropHgt
= (screenHeight
/ 2) - (heightOfARow
*2); // approx half screen minus combo size
748 availDropHgt
-= aReflowState
.mComputedBorderPadding
.top
+ aReflowState
.mComputedBorderPadding
.bottom
;
750 nscoord hgt
= visibleHeight
+ aReflowState
.mComputedBorderPadding
.top
+ aReflowState
.mComputedBorderPadding
.bottom
;
751 if (heightOfARow
> 0) {
752 if (hgt
> availDropHgt
) {
753 visibleHeight
= (availDropHgt
/ heightOfARow
) * heightOfARow
;
755 mNumDisplayRows
= visibleHeight
/ heightOfARow
;
757 // Hmmm, not sure what to do here. Punt, and make both of them one
762 state
.SetComputedHeight(mNumDisplayRows
* heightOfARow
);
763 // Note: no need to apply min/max constraints, since we have no such
764 // rules applied to the combobox dropdown.
765 // XXXbz this is ending up too big!! Figure out why.
766 } else if (visibleHeight
== 0) {
767 // Looks like we have no options. Just size us to a single row height.
768 state
.SetComputedHeight(heightOfARow
);
770 // Not too big, not too small. Just use it!
771 state
.SetComputedHeight(NS_UNCONSTRAINEDSIZE
);
774 // Note: At this point, state.mComputedHeight can be NS_UNCONSTRAINEDSIZE in
775 // cases when there were some options, but not too many (so no scrollbar was
776 // needed). That's fine; just store that.
777 mLastDropdownComputedHeight
= state
.ComputedHeight();
779 nsHTMLScrollFrame::WillReflow(aPresContext
);
780 return nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
783 nsGfxScrollFrameInner::ScrollbarStyles
784 nsListControlFrame::GetScrollbarStyles() const
786 // We can't express this in the style system yet; when we can, this can go away
787 // and GetScrollbarStyles can be devirtualized
788 PRInt32 verticalStyle
= IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
789 : NS_STYLE_OVERFLOW_SCROLL
;
790 return nsGfxScrollFrameInner::ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN
,
795 nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const
797 return !IsInDropDownMode();
800 //---------------------------------------------------------
802 nsListControlFrame::IsOptionElement(nsIContent
* aContent
)
804 PRBool result
= PR_FALSE
;
806 nsCOMPtr
<nsIDOMHTMLOptionElement
> optElem
;
807 if (NS_SUCCEEDED(aContent
->QueryInterface(NS_GET_IID(nsIDOMHTMLOptionElement
),(void**) getter_AddRefs(optElem
)))) {
808 if (optElem
!= nsnull
) {
817 nsListControlFrame::GetContentInsertionFrame() {
818 return GetOptionsContainer()->GetContentInsertionFrame();
821 //---------------------------------------------------------
822 // Starts at the passed in content object and walks up the
823 // parent heierarchy looking for the nsIDOMHTMLOptionElement
824 //---------------------------------------------------------
826 nsListControlFrame::GetOptionFromContent(nsIContent
*aContent
)
828 for (nsIContent
* content
= aContent
; content
; content
= content
->GetParent()) {
829 if (IsOptionElement(content
)) {
837 //---------------------------------------------------------
838 // Finds the index of the hit frame's content in the list
839 // of option elements
840 //---------------------------------------------------------
842 nsListControlFrame::GetIndexFromContent(nsIContent
*aContent
)
844 nsCOMPtr
<nsIDOMHTMLOptionElement
> option
;
845 option
= do_QueryInterface(aContent
);
848 option
->GetIndex(&retval
);
853 return kNothingSelected
;
856 //---------------------------------------------------------
858 nsListControlFrame::ExtendedSelection(PRInt32 aStartIndex
,
862 return SetOptionsSelectedFromFrame(aStartIndex
, aEndIndex
,
866 //---------------------------------------------------------
868 nsListControlFrame::SingleSelection(PRInt32 aClickedIndex
, PRBool aDoToggle
)
870 if (mComboboxFrame
) {
871 mComboboxFrame
->UpdateRecentIndex(GetSelectedIndex());
874 PRBool wasChanged
= PR_FALSE
;
875 // Get Current selection
877 wasChanged
= ToggleOptionSelectedFromFrame(aClickedIndex
);
879 wasChanged
= SetOptionsSelectedFromFrame(aClickedIndex
, aClickedIndex
,
882 ScrollToIndex(aClickedIndex
);
883 mStartSelectionIndex
= aClickedIndex
;
884 mEndSelectionIndex
= aClickedIndex
;
890 nsListControlFrame::InitSelectionRange(PRInt32 aClickedIndex
)
893 // If nothing is selected, set the start selection depending on where
894 // the user clicked and what the initial selection is:
895 // - if the user clicked *before* selectedIndex, set the start index to
896 // the end of the first contiguous selection.
897 // - if the user clicked *after* the end of the first contiguous
898 // selection, set the start index to selectedIndex.
899 // - if the user clicked *within* the first contiguous selection, set the
900 // start index to selectedIndex.
901 // The last two rules, of course, boil down to the same thing: if the user
902 // clicked >= selectedIndex, return selectedIndex.
904 // This makes it so that shift click works properly when you first click
905 // in a multiple select.
907 PRInt32 selectedIndex
= GetSelectedIndex();
908 if (selectedIndex
>= 0) {
909 // Get the end of the contiguous selection
910 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
911 NS_ASSERTION(options
, "Collection of options is null!");
913 options
->GetLength(&numOptions
);
915 // Push i to one past the last selected index in the group
916 for (i
=selectedIndex
+1; i
< numOptions
; i
++) {
918 nsCOMPtr
<nsIDOMHTMLOptionElement
> option
= GetOption(options
, i
);
919 option
->GetSelected(&selected
);
925 if (aClickedIndex
< selectedIndex
) {
926 // User clicked before selection, so start selection at end of
927 // contiguous selection
928 mStartSelectionIndex
= i
-1;
929 mEndSelectionIndex
= selectedIndex
;
931 // User clicked after selection, so start selection at start of
932 // contiguous selection
933 mStartSelectionIndex
= selectedIndex
;
934 mEndSelectionIndex
= i
-1;
939 //---------------------------------------------------------
941 nsListControlFrame::PerformSelection(PRInt32 aClickedIndex
,
945 PRBool wasChanged
= PR_FALSE
;
947 if (aClickedIndex
== kNothingSelected
) {
949 else if (GetMultiple()) {
951 // Make sure shift+click actually does something expected when
952 // the user has never clicked on the select
953 if (mStartSelectionIndex
== kNothingSelected
) {
954 InitSelectionRange(aClickedIndex
);
957 // Get the range from beginning (low) to end (high)
958 // Shift *always* works, even if the current option is disabled
961 if (mStartSelectionIndex
== kNothingSelected
) {
962 startIndex
= aClickedIndex
;
963 endIndex
= aClickedIndex
;
964 } else if (mStartSelectionIndex
<= aClickedIndex
) {
965 startIndex
= mStartSelectionIndex
;
966 endIndex
= aClickedIndex
;
968 startIndex
= aClickedIndex
;
969 endIndex
= mStartSelectionIndex
;
972 // Clear only if control was not pressed
973 wasChanged
= ExtendedSelection(startIndex
, endIndex
, !aIsControl
);
974 ScrollToIndex(aClickedIndex
);
976 if (mStartSelectionIndex
== kNothingSelected
) {
977 mStartSelectionIndex
= aClickedIndex
;
978 mEndSelectionIndex
= aClickedIndex
;
980 mEndSelectionIndex
= aClickedIndex
;
983 } else if (aIsControl
) {
984 wasChanged
= SingleSelection(aClickedIndex
, PR_TRUE
);
986 wasChanged
= SingleSelection(aClickedIndex
, PR_FALSE
);
989 wasChanged
= SingleSelection(aClickedIndex
, PR_FALSE
);
995 //---------------------------------------------------------
997 nsListControlFrame::HandleListSelection(nsIDOMEvent
* aEvent
,
998 PRInt32 aClickedIndex
)
1000 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
1004 mouseEvent
->GetMetaKey(&isControl
);
1006 mouseEvent
->GetCtrlKey(&isControl
);
1008 mouseEvent
->GetShiftKey(&isShift
);
1009 return PerformSelection(aClickedIndex
, isShift
, isControl
);
1012 //---------------------------------------------------------
1014 nsListControlFrame::CaptureMouseEvents(PRBool aGrabMouseEvents
)
1016 // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
1017 // so we never want to do mouse capturing. Note that we only bail if the list
1018 // is in drop-down mode, and the caller is requesting capture (we let release capture
1019 // requests go through to ensure that we can release capture requested via other
1020 // code paths, if any exist).
1021 if (aGrabMouseEvents
&& IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
1024 if (aGrabMouseEvents
) {
1025 nsIPresShell::SetCapturingContent(mContent
, CAPTURE_IGNOREALLOWED
);
1027 nsIContent
* capturingContent
= nsIPresShell::GetCapturingContent();
1029 PRBool dropDownIsHidden
= PR_FALSE
;
1030 if (IsInDropDownMode()) {
1031 dropDownIsHidden
= !mComboboxFrame
->IsDroppedDown();
1033 if (capturingContent
== mContent
|| dropDownIsHidden
) {
1034 // only clear the capturing content if *we* are the ones doing the
1035 // capturing (or if the dropdown is hidden, in which case NO-ONE should
1036 // be capturing anything - it could be a scrollbar inside this listbox
1037 // which is actually grabbing
1038 // This shouldn't be necessary. We should simply ensure that events targeting
1039 // scrollbars are never visible to DOM consumers.
1040 nsIPresShell::SetCapturingContent(nsnull
, 0);
1045 //---------------------------------------------------------
1047 nsListControlFrame::HandleEvent(nsPresContext
* aPresContext
,
1049 nsEventStatus
* aEventStatus
)
1051 NS_ENSURE_ARG_POINTER(aEventStatus
);
1053 /*const char * desc[] = {"NS_MOUSE_MOVE",
1054 "NS_MOUSE_LEFT_BUTTON_UP",
1055 "NS_MOUSE_LEFT_BUTTON_DOWN",
1056 "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
1057 "NS_MOUSE_MIDDLE_BUTTON_UP",
1058 "NS_MOUSE_MIDDLE_BUTTON_DOWN",
1059 "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
1060 "NS_MOUSE_RIGHT_BUTTON_UP",
1061 "NS_MOUSE_RIGHT_BUTTON_DOWN",
1062 "NS_MOUSE_ENTER_SYNTH",
1063 "NS_MOUSE_EXIT_SYNTH",
1064 "NS_MOUSE_LEFT_DOUBLECLICK",
1065 "NS_MOUSE_MIDDLE_DOUBLECLICK",
1066 "NS_MOUSE_RIGHT_DOUBLECLICK",
1067 "NS_MOUSE_LEFT_CLICK",
1068 "NS_MOUSE_MIDDLE_CLICK",
1069 "NS_MOUSE_RIGHT_CLICK"};
1070 int inx = aEvent->message-NS_MOUSE_MESSAGE_START;
1071 if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) {
1072 printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message);
1074 printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
1077 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
)
1080 // do we have style that affects how we are selected?
1081 // do we have user-input style?
1082 const nsStyleUserInterface
* uiStyle
= GetStyleUserInterface();
1083 if (uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_NONE
|| uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_DISABLED
)
1084 return nsFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
1086 if (mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::disabled
))
1089 return nsHTMLScrollFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
1093 //---------------------------------------------------------
1095 nsListControlFrame::SetInitialChildList(nsIAtom
* aListName
,
1096 nsFrameList
& aChildList
)
1098 // First check to see if all the content has been added
1099 mIsAllContentHere
= mContent
->IsDoneAddingChildren();
1100 if (!mIsAllContentHere
) {
1101 mIsAllFramesHere
= PR_FALSE
;
1102 mHasBeenInitialized
= PR_FALSE
;
1104 nsresult rv
= nsHTMLScrollFrame::SetInitialChildList(aListName
, aChildList
);
1106 // If all the content is here now check
1107 // to see if all the frames have been created
1108 /*if (mIsAllContentHere) {
1109 // If all content and frames are here
1110 // the reset/initialize
1111 if (CheckIfAllFramesHere()) {
1112 ResetList(aPresContext);
1113 mHasBeenInitialized = PR_TRUE;
1120 //---------------------------------------------------------
1122 nsListControlFrame::GetSizeAttribute(PRInt32
*aSize
) {
1123 nsresult rv
= NS_OK
;
1124 nsIDOMHTMLSelectElement
* selectElement
;
1125 rv
= mContent
->QueryInterface(NS_GET_IID(nsIDOMHTMLSelectElement
),(void**) &selectElement
);
1126 if (mContent
&& NS_SUCCEEDED(rv
)) {
1127 rv
= selectElement
->GetSize(aSize
);
1128 NS_RELEASE(selectElement
);
1134 //---------------------------------------------------------
1136 nsListControlFrame::Init(nsIContent
* aContent
,
1138 nsIFrame
* aPrevInFlow
)
1140 nsresult result
= nsHTMLScrollFrame::Init(aContent
, aParent
, aPrevInFlow
);
1142 // get the receiver interface from the browser button's content node
1143 NS_ENSURE_STATE(mContent
);
1145 // we shouldn't have to unregister this listener because when
1146 // our frame goes away all these content node go away as well
1147 // because our frame is the only one who references them.
1148 // we need to hook up our listeners before the editor is initialized
1149 mEventListener
= new nsListEventListener(this);
1150 if (!mEventListener
)
1151 return NS_ERROR_OUT_OF_MEMORY
;
1153 mContent
->AddEventListenerByIID(static_cast<nsIDOMMouseListener
*>
1155 NS_GET_IID(nsIDOMMouseListener
));
1157 mContent
->AddEventListenerByIID(static_cast<nsIDOMMouseMotionListener
*>
1159 NS_GET_IID(nsIDOMMouseMotionListener
));
1161 mContent
->AddEventListenerByIID(static_cast<nsIDOMKeyListener
*>
1163 NS_GET_IID(nsIDOMKeyListener
));
1165 mStartSelectionIndex
= kNothingSelected
;
1166 mEndSelectionIndex
= kNothingSelected
;
1168 mLastDropdownBackstopColor
= PresContext()->DefaultBackgroundColor();
1174 nsListControlFrame::GetMultiple(nsIDOMHTMLSelectElement
* aSelect
) const
1176 PRBool multiple
= PR_FALSE
;
1177 nsresult rv
= NS_OK
;
1179 rv
= aSelect
->GetMultiple(&multiple
);
1181 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement
=
1182 do_QueryInterface(mContent
);
1184 if (selectElement
) {
1185 rv
= selectElement
->GetMultiple(&multiple
);
1188 if (NS_SUCCEEDED(rv
)) {
1194 already_AddRefed
<nsIContent
>
1195 nsListControlFrame::GetOptionAsContent(nsIDOMHTMLOptionsCollection
* aCollection
, PRInt32 aIndex
)
1197 nsIContent
* content
= nsnull
;
1198 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
= GetOption(aCollection
,
1201 NS_ASSERTION(optionElement
!= nsnull
, "could not get option element by index!");
1203 if (optionElement
) {
1204 CallQueryInterface(optionElement
, &content
);
1210 already_AddRefed
<nsIContent
>
1211 nsListControlFrame::GetOptionContent(PRInt32 aIndex
) const
1214 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1215 NS_ASSERTION(options
.get() != nsnull
, "Collection of options is null!");
1218 return GetOptionAsContent(options
, aIndex
);
1223 already_AddRefed
<nsIDOMHTMLOptionsCollection
>
1224 nsListControlFrame::GetOptions(nsIContent
* aContent
)
1226 nsIDOMHTMLOptionsCollection
* options
= nsnull
;
1227 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement
= do_QueryInterface(aContent
);
1228 if (selectElement
) {
1229 selectElement
->GetOptions(&options
); // AddRefs (1)
1235 already_AddRefed
<nsIDOMHTMLOptionElement
>
1236 nsListControlFrame::GetOption(nsIDOMHTMLOptionsCollection
* aCollection
,
1239 nsCOMPtr
<nsIDOMNode
> node
;
1240 if (NS_SUCCEEDED(aCollection
->Item(aIndex
, getter_AddRefs(node
)))) {
1242 "Item was successful, but node from collection was null!");
1244 nsIDOMHTMLOptionElement
* option
= nsnull
;
1245 CallQueryInterface(node
, &option
);
1250 NS_ERROR("Couldn't get option by index from collection!");
1256 nsListControlFrame::IsContentSelected(nsIContent
* aContent
) const
1258 PRBool isSelected
= PR_FALSE
;
1260 nsCOMPtr
<nsIDOMHTMLOptionElement
> optEl
= do_QueryInterface(aContent
);
1262 optEl
->GetSelected(&isSelected
);
1268 nsListControlFrame::IsContentSelectedByIndex(PRInt32 aIndex
) const
1270 nsCOMPtr
<nsIContent
> content
= GetOptionContent(aIndex
);
1271 NS_ASSERTION(content
, "Failed to retrieve option content");
1273 return IsContentSelected(content
);
1277 nsListControlFrame::OnOptionSelected(PRInt32 aIndex
, PRBool aSelected
)
1280 ScrollToIndex(aIndex
);
1286 nsListControlFrame::GetSkipSides() const
1288 // Don't skip any sides during border rendering
1293 nsListControlFrame::OnContentReset()
1299 nsListControlFrame::ResetList(PRBool aAllowScrolling
)
1301 // if all the frames aren't here
1302 // don't bother reseting
1303 if (!mIsAllFramesHere
) {
1307 if (aAllowScrolling
) {
1308 mPostChildrenLoadedReset
= PR_TRUE
;
1310 // Scroll to the selected index
1311 PRInt32 indexToSelect
= kNothingSelected
;
1313 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement(do_QueryInterface(mContent
));
1314 NS_ASSERTION(selectElement
, "No select element!");
1315 if (selectElement
) {
1316 selectElement
->GetSelectedIndex(&indexToSelect
);
1317 ScrollToIndex(indexToSelect
);
1321 mStartSelectionIndex
= kNothingSelected
;
1322 mEndSelectionIndex
= kNothingSelected
;
1324 // Combobox will redisplay itself with the OnOptionSelected event
1328 nsListControlFrame::SetFocus(PRBool aOn
, PRBool aRepaint
)
1342 void nsListControlFrame::ComboboxFocusSet()
1348 nsListControlFrame::SetComboboxFrame(nsIFrame
* aComboboxFrame
)
1350 if (nsnull
!= aComboboxFrame
) {
1351 mComboboxFrame
= do_QueryFrame(aComboboxFrame
);
1356 nsListControlFrame::GetOptionText(PRInt32 aIndex
, nsAString
& aStr
)
1359 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1362 PRUint32 numOptions
;
1363 options
->GetLength(&numOptions
);
1365 if (numOptions
!= 0) {
1366 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
=
1367 GetOption(options
, aIndex
);
1368 if (optionElement
) {
1369 #if 0 // This is for turning off labels Bug 4050
1371 optionElement
->GetLabel(text
);
1372 // the return value is always NS_OK from DOMElements
1373 // it is meaningless to check for it
1374 if (!text
.IsEmpty()) {
1375 nsAutoString compressText
= text
;
1376 compressText
.CompressWhitespace(PR_TRUE
, PR_TRUE
);
1377 if (!compressText
.IsEmpty()) {
1378 text
= compressText
;
1382 if (text
.IsEmpty()) {
1383 // the return value is always NS_OK from DOMElements
1384 // it is meaningless to check for it
1385 optionElement
->GetText(text
);
1389 optionElement
->GetText(aStr
);
1397 nsListControlFrame::GetSelectedIndex()
1401 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement(do_QueryInterface(mContent
));
1402 selectElement
->GetSelectedIndex(&aIndex
);
1408 nsListControlFrame::IsInDropDownMode() const
1410 return (mComboboxFrame
!= nsnull
);
1414 nsListControlFrame::GetNumberOfOptions()
1416 if (mContent
!= nsnull
) {
1417 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1422 PRUint32 length
= 0;
1423 options
->GetLength(&length
);
1424 return (PRInt32
)length
;
1430 //----------------------------------------------------------------------
1431 // nsISelectControlFrame
1432 //----------------------------------------------------------------------
1433 PRBool
nsListControlFrame::CheckIfAllFramesHere()
1435 // Get the number of optgroups and options
1436 //PRInt32 numContentItems = 0;
1437 nsCOMPtr
<nsIDOMNode
> node(do_QueryInterface(mContent
));
1439 // XXX Need to find a fail proff way to determine that
1440 // all the frames are there
1441 mIsAllFramesHere
= PR_TRUE
;//NS_OK == CountAllChild(node, numContentItems);
1443 // now make sure we have a frame each piece of content
1445 return mIsAllFramesHere
;
1449 nsListControlFrame::DoneAddingChildren(PRBool aIsDone
)
1451 mIsAllContentHere
= aIsDone
;
1452 if (mIsAllContentHere
) {
1453 // Here we check to see if all the frames have been created
1454 // for all the content.
1455 // If so, then we can initialize;
1456 if (!mIsAllFramesHere
) {
1457 // if all the frames are now present we can initialize
1458 if (CheckIfAllFramesHere()) {
1459 mHasBeenInitialized
= PR_TRUE
;
1468 nsListControlFrame::AddOption(PRInt32 aIndex
)
1470 #ifdef DO_REFLOW_DEBUG
1471 printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId
, this, aIndex
);
1474 if (!mIsAllContentHere
) {
1475 mIsAllContentHere
= mContent
->IsDoneAddingChildren();
1476 if (!mIsAllContentHere
) {
1477 mIsAllFramesHere
= PR_FALSE
;
1478 mHasBeenInitialized
= PR_FALSE
;
1480 mIsAllFramesHere
= (aIndex
== GetNumberOfOptions()-1);
1484 // Make sure we scroll to the selected option as needed
1485 mNeedToReset
= PR_TRUE
;
1487 if (!mHasBeenInitialized
) {
1491 mPostChildrenLoadedReset
= mIsAllContentHere
;
1496 nsListControlFrame::RemoveOption(PRInt32 aIndex
)
1498 // Need to reset if we're a dropdown
1499 if (IsInDropDownMode()) {
1500 mNeedToReset
= PR_TRUE
;
1501 mPostChildrenLoadedReset
= mIsAllContentHere
;
1504 if (mStartSelectionIndex
>= aIndex
) {
1505 --mStartSelectionIndex
;
1506 if (mStartSelectionIndex
< 0) {
1507 mStartSelectionIndex
= kNothingSelected
;
1511 if (mEndSelectionIndex
>= aIndex
) {
1512 --mEndSelectionIndex
;
1513 if (mEndSelectionIndex
< 0) {
1514 mEndSelectionIndex
= kNothingSelected
;
1522 //---------------------------------------------------------
1523 // Set the option selected in the DOM. This method is named
1524 // as it is because it indicates that the frame is the source
1525 // of this event rather than the receiver.
1527 nsListControlFrame::SetOptionsSelectedFromFrame(PRInt32 aStartIndex
,
1532 nsCOMPtr
<nsISelectElement
> selectElement(do_QueryInterface(mContent
));
1533 PRBool wasChanged
= PR_FALSE
;
1537 selectElement
->SetOptionsSelectedByIndex(aStartIndex
,
1544 NS_ASSERTION(NS_SUCCEEDED(rv
), "SetSelected failed");
1549 nsListControlFrame::ToggleOptionSelectedFromFrame(PRInt32 aIndex
)
1551 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1552 NS_ASSERTION(options
, "No options");
1556 nsCOMPtr
<nsIDOMHTMLOptionElement
> option
= GetOption(options
, aIndex
);
1557 NS_ASSERTION(option
, "No option");
1562 PRBool value
= PR_FALSE
;
1563 nsresult rv
= option
->GetSelected(&value
);
1565 NS_ASSERTION(NS_SUCCEEDED(rv
), "GetSelected failed");
1566 nsCOMPtr
<nsISelectElement
> selectElement(do_QueryInterface(mContent
));
1567 PRBool wasChanged
= PR_FALSE
;
1568 rv
= selectElement
->SetOptionsSelectedByIndex(aIndex
,
1576 NS_ASSERTION(NS_SUCCEEDED(rv
), "SetSelected failed");
1582 // Dispatch event and such
1584 nsListControlFrame::UpdateSelection()
1586 if (mIsAllFramesHere
) {
1587 // if it's a combobox, display the new text
1588 nsWeakFrame
weakFrame(this);
1589 if (mComboboxFrame
) {
1590 mComboboxFrame
->RedisplaySelectedText();
1592 // if it's a listbox, fire on change
1593 else if (mIsAllContentHere
) {
1596 return weakFrame
.IsAlive();
1602 nsListControlFrame::ComboboxFinish(PRInt32 aIndex
)
1606 if (mComboboxFrame
) {
1607 PerformSelection(aIndex
, PR_FALSE
, PR_FALSE
);
1609 PRInt32 displayIndex
= mComboboxFrame
->GetIndexOfDisplayArea();
1611 nsWeakFrame
weakFrame(this);
1613 if (displayIndex
!= aIndex
) {
1614 mComboboxFrame
->RedisplaySelectedText(); // might destroy us
1617 if (weakFrame
.IsAlive() && mComboboxFrame
) {
1618 mComboboxFrame
->RollupFromList(); // might destroy us
1623 // Send out an onchange notification.
1625 nsListControlFrame::FireOnChange()
1627 if (mComboboxFrame
) {
1628 // Return hit without changing anything
1629 PRInt32 index
= mComboboxFrame
->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX
);
1630 if (index
== NS_SKIP_NOTIFY_INDEX
)
1633 // See if the selection actually changed
1634 if (index
== GetSelectedIndex())
1638 // Dispatch the NS_FORM_CHANGE event
1639 nsEventStatus status
= nsEventStatus_eIgnore
;
1640 nsEvent
event(PR_TRUE
, NS_FORM_CHANGE
);
1642 nsCOMPtr
<nsIPresShell
> presShell
= PresContext()->GetPresShell();
1644 presShell
->HandleEventWithTarget(&event
, this, nsnull
, &status
);
1649 nsListControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex
, PRInt32 aNewIndex
)
1651 if (mComboboxFrame
) {
1652 // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
1653 // event for this setting of selectedIndex.
1654 mComboboxFrame
->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX
);
1657 ScrollToIndex(aNewIndex
);
1658 mStartSelectionIndex
= aNewIndex
;
1659 mEndSelectionIndex
= aNewIndex
;
1662 #ifdef ACCESSIBILITY
1663 FireMenuItemActiveEvent();
1669 //----------------------------------------------------------------------
1670 // End nsISelectControlFrame
1671 //----------------------------------------------------------------------
1674 nsListControlFrame::SetFormProperty(nsIAtom
* aName
,
1675 const nsAString
& aValue
)
1677 if (nsGkAtoms::selected
== aName
) {
1678 return NS_ERROR_INVALID_ARG
; // Selected is readonly according to spec.
1679 } else if (nsGkAtoms::selectedindex
== aName
) {
1680 // You shouldn't be calling me for this!!!
1681 return NS_ERROR_INVALID_ARG
;
1684 // We should be told about selectedIndex by the DOM element through
1691 nsListControlFrame::GetFormProperty(nsIAtom
* aName
, nsAString
& aValue
) const
1693 // Get the selected value of option from local cache (optimization vs. widget)
1694 if (nsGkAtoms::selected
== aName
) {
1695 nsAutoString
val(aValue
);
1697 PRBool selected
= PR_FALSE
;
1698 PRInt32 indx
= val
.ToInteger(&error
, 10); // Get index from aValue
1700 selected
= IsContentSelectedByIndex(indx
);
1702 aValue
.Assign(selected
? NS_LITERAL_STRING("1") : NS_LITERAL_STRING("0"));
1704 // For selectedIndex, get the value from the widget
1705 } else if (nsGkAtoms::selectedindex
== aName
) {
1706 // You shouldn't be calling me for this!!!
1707 return NS_ERROR_INVALID_ARG
;
1714 nsListControlFrame::SyncViewWithFrame()
1716 // Resync the view's position with the frame.
1717 // The problem is the dropdown's view is attached directly under
1718 // the root view. This means its view needs to have its coordinates calculated
1719 // as if it were in it's normal position in the view hierarchy.
1720 mComboboxFrame
->AbsolutelyPositionDropDown();
1722 nsContainerFrame::PositionFrameView(this);
1726 nsListControlFrame::AboutToDropDown()
1728 NS_ASSERTION(IsInDropDownMode(),
1729 "AboutToDropDown called without being in dropdown mode");
1731 // Our widget doesn't get invalidated on changes to the rest of the document,
1732 // so compute and store this color at the start of a dropdown so we don't
1733 // get weird painting behaviour.
1734 // We start looking for backgrounds above the combobox frame to avoid
1735 // duplicating the combobox frame's background and compose each background
1736 // color we find underneath until we have an opaque color, or run out of
1737 // backgrounds. We compose with the PresContext default background color,
1738 // which is always opaque, in case we don't end up with an opaque color.
1739 // This gives us a very poor approximation of translucency.
1740 nsIFrame
* comboboxFrame
= do_QueryFrame(mComboboxFrame
);
1741 nsStyleContext
* context
= comboboxFrame
->GetStyleContext()->GetParent();
1742 mLastDropdownBackstopColor
= NS_RGBA(0,0,0,0);
1743 while (NS_GET_A(mLastDropdownBackstopColor
) < 255 && context
) {
1744 mLastDropdownBackstopColor
=
1745 NS_ComposeColors(context
->GetStyleBackground()->mBackgroundColor
,
1746 mLastDropdownBackstopColor
);
1747 context
= context
->GetParent();
1749 mLastDropdownBackstopColor
=
1750 NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
1751 mLastDropdownBackstopColor
);
1753 if (mIsAllContentHere
&& mIsAllFramesHere
&& mHasBeenInitialized
) {
1754 ScrollToIndex(GetSelectedIndex());
1755 #ifdef ACCESSIBILITY
1756 FireMenuItemActiveEvent(); // Inform assistive tech what got focus
1759 mItemSelectionStarted
= PR_FALSE
;
1762 // We are about to be rolledup from the outside (ComboboxFrame)
1764 nsListControlFrame::AboutToRollup()
1766 // We've been updating the combobox with the keyboard up until now, but not
1767 // with the mouse. The problem is, even with mouse selection, we are
1768 // updating the <select>. So if the mouse goes over an option just before
1769 // he leaves the box and clicks, that's what the <select> will show.
1771 // To deal with this we say "whatever is in the combobox is canonical."
1772 // - IF the combobox is different from the current selected index, we
1775 if (IsInDropDownMode()) {
1776 ComboboxFinish(mComboboxFrame
->GetIndexOfDisplayArea()); // might destroy us
1781 nsListControlFrame::DidReflow(nsPresContext
* aPresContext
,
1782 const nsHTMLReflowState
* aReflowState
,
1783 nsDidReflowStatus aStatus
)
1786 PRBool wasInterrupted
= !mHasPendingInterruptAtStartOfReflow
&&
1787 aPresContext
->HasPendingInterrupt();
1789 if (IsInDropDownMode())
1791 //SyncViewWithFrame();
1792 rv
= nsHTMLScrollFrame::DidReflow(aPresContext
, aReflowState
, aStatus
);
1793 SyncViewWithFrame();
1795 rv
= nsHTMLScrollFrame::DidReflow(aPresContext
, aReflowState
, aStatus
);
1798 if (mNeedToReset
&& !wasInterrupted
) {
1799 mNeedToReset
= PR_FALSE
;
1800 // Suppress scrolling to the selected element if we restored
1801 // scroll history state AND the list contents have not changed
1802 // since we loaded all the children AND nothing else forced us
1803 // to scroll by calling ResetList(PR_TRUE). The latter two conditions
1804 // are folded into mPostChildrenLoadedReset.
1806 // The idea is that we want scroll history restoration to trump ResetList
1807 // scrolling to the selected element, when the ResetList was probably only
1808 // caused by content loading normally.
1809 ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset
);
1812 mHasPendingInterruptAtStartOfReflow
= PR_FALSE
;
1817 nsListControlFrame::GetType() const
1819 return nsGkAtoms::listControlFrame
;
1823 nsListControlFrame::IsContainingBlock() const
1825 // We are in fact the containing block for our options. They should
1826 // certainly not use our parent block (or worse yet our parent combobox) for
1832 nsListControlFrame::InvalidateInternal(const nsRect
& aDamageRect
,
1833 nscoord aX
, nscoord aY
, nsIFrame
* aForChild
,
1836 if (!IsInDropDownMode()) {
1837 nsHTMLScrollFrame::InvalidateInternal(aDamageRect
, aX
, aY
, this, aFlags
);
1840 InvalidateRoot(aDamageRect
+ nsPoint(aX
, aY
), aFlags
);
1845 nsListControlFrame::GetFrameName(nsAString
& aResult
) const
1847 return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult
);
1852 nsListControlFrame::GetHeightOfARow()
1854 return HeightOfARow();
1858 nsListControlFrame::IsOptionDisabled(PRInt32 anIndex
, PRBool
&aIsDisabled
)
1860 nsCOMPtr
<nsISelectElement
> sel(do_QueryInterface(mContent
));
1862 sel
->IsOptionDisabled(anIndex
, &aIsDisabled
);
1865 return NS_ERROR_FAILURE
;
1868 //----------------------------------------------------------------------
1870 //----------------------------------------------------------------------
1872 nsListControlFrame::IsLeftButton(nsIDOMEvent
* aMouseEvent
)
1874 // only allow selection with the left button
1875 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1877 PRUint16 whichButton
;
1878 if (NS_SUCCEEDED(mouseEvent
->GetButton(&whichButton
))) {
1879 return whichButton
!= 0?PR_FALSE
:PR_TRUE
;
1886 nsListControlFrame::CalcFallbackRowHeight()
1888 nscoord rowHeight
= 0;
1890 nsCOMPtr
<nsIFontMetrics
> fontMet
;
1891 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet
));
1893 fontMet
->GetHeight(rowHeight
);
1900 nsListControlFrame::CalcIntrinsicHeight(nscoord aHeightOfARow
,
1901 PRInt32 aNumberOfOptions
)
1903 NS_PRECONDITION(!IsInDropDownMode(),
1904 "Shouldn't be in dropdown mode when we call this");
1906 mNumDisplayRows
= 1;
1907 GetSizeAttribute(&mNumDisplayRows
);
1909 // Extra height to tack on to aHeightOfARow * mNumDisplayRows
1910 nscoord extraHeight
= 0;
1912 if (mNumDisplayRows
< 1) {
1913 // When SIZE=0 or unspecified we constrain the height to
1914 // [2..kMaxDropDownRows] rows. We add in the height of optgroup labels
1915 // (within the constraint above), bug 300474.
1916 nscoord labelHeight
= ::GetOptGroupLabelsHeight(mContent
, aHeightOfARow
);
1918 if (GetMultiple()) {
1919 if (aNumberOfOptions
< 2) {
1920 // Add in 1 aHeightOfARow also when aNumberOfOptions == 0
1921 mNumDisplayRows
= 1;
1922 extraHeight
= NS_MAX(aHeightOfARow
, labelHeight
);
1924 else if (aNumberOfOptions
* aHeightOfARow
+ labelHeight
>
1925 kMaxDropDownRows
* aHeightOfARow
) {
1926 mNumDisplayRows
= kMaxDropDownRows
;
1928 mNumDisplayRows
= aNumberOfOptions
;
1929 extraHeight
= labelHeight
;
1933 NS_NOTREACHED("Shouldn't hit this case -- we should a be a combobox if "
1934 "we have no size set and no multiple set!");
1938 return mNumDisplayRows
* aHeightOfARow
+ extraHeight
;
1941 //----------------------------------------------------------------------
1942 // nsIDOMMouseListener
1943 //----------------------------------------------------------------------
1945 nsListControlFrame::MouseUp(nsIDOMEvent
* aMouseEvent
)
1947 NS_ASSERTION(aMouseEvent
!= nsnull
, "aMouseEvent is null.");
1949 UpdateInListState(aMouseEvent
);
1951 mButtonDown
= PR_FALSE
;
1953 if (mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::disabled
)) {
1957 // only allow selection with the left button
1958 // if a right button click is on the combobox itself
1959 // or on the select when in listbox mode, then let the click through
1960 if (!IsLeftButton(aMouseEvent
)) {
1961 if (IsInDropDownMode()) {
1962 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
1963 aMouseEvent
->PreventDefault();
1964 aMouseEvent
->StopPropagation();
1966 CaptureMouseEvents(PR_FALSE
);
1969 CaptureMouseEvents(PR_FALSE
);
1970 return NS_ERROR_FAILURE
; // means consume event
1972 CaptureMouseEvents(PR_FALSE
);
1977 const nsStyleVisibility
* vis
= GetStyleVisibility();
1979 if (!vis
->IsVisible()) {
1983 if (IsInDropDownMode()) {
1984 // XXX This is a bit of a hack, but.....
1985 // But the idea here is to make sure you get an "onclick" event when you mouse
1986 // down on the select and the drag over an option and let go
1987 // And then NOT get an "onclick" event when when you click down on the select
1988 // and then up outside of the select
1989 // the EventStateManager tracks the content of the mouse down and the mouse up
1990 // to make sure they are the same, and the onclick is sent in the PostHandleEvent
1991 // depeneding on whether the clickCount is non-zero.
1992 // So we cheat here by either setting or unsetting the clcikCount in the native event
1993 // so the right thing happens for the onclick event
1994 nsCOMPtr
<nsIPrivateDOMEvent
> privateEvent(do_QueryInterface(aMouseEvent
));
1995 nsMouseEvent
* mouseEvent
;
1996 mouseEvent
= (nsMouseEvent
*) privateEvent
->GetInternalNSEvent();
1998 PRInt32 selectedIndex
;
1999 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2000 // If it's disabled, disallow the click and leave.
2001 PRBool isDisabled
= PR_FALSE
;
2002 IsOptionDisabled(selectedIndex
, isDisabled
);
2004 aMouseEvent
->PreventDefault();
2005 aMouseEvent
->StopPropagation();
2006 CaptureMouseEvents(PR_FALSE
);
2007 return NS_ERROR_FAILURE
;
2010 if (kNothingSelected
!= selectedIndex
) {
2011 nsWeakFrame
weakFrame(this);
2012 ComboboxFinish(selectedIndex
);
2013 if (!weakFrame
.IsAlive())
2018 mouseEvent
->clickCount
= 1;
2020 // the click was out side of the select or its dropdown
2021 mouseEvent
->clickCount
= IgnoreMouseEventForSelection(aMouseEvent
) ? 1 : 0;
2024 CaptureMouseEvents(PR_FALSE
);
2026 if (mChangesSinceDragStart
) {
2027 // reset this so that future MouseUps without a prior MouseDown
2028 // won't fire onchange
2029 mChangesSinceDragStart
= PR_FALSE
;
2038 nsListControlFrame::UpdateInListState(nsIDOMEvent
* aEvent
)
2040 if (!mComboboxFrame
|| !mComboboxFrame
->IsDroppedDown())
2043 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent
, this);
2044 nsRect borderInnerEdge
= GetScrollPortRect();
2045 if (pt
.y
>= borderInnerEdge
.y
&& pt
.y
< borderInnerEdge
.YMost()) {
2046 mItemSelectionStarted
= PR_TRUE
;
2050 PRBool
nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent
* aEvent
)
2052 if (!mComboboxFrame
)
2055 // Our DOM listener does get called when the dropdown is not
2056 // showing, because it listens to events on the SELECT element
2057 if (!mComboboxFrame
->IsDroppedDown())
2060 return !mItemSelectionStarted
;
2063 #ifdef ACCESSIBILITY
2065 nsListControlFrame::FireMenuItemActiveEvent()
2067 if (mFocused
!= this && !IsInDropDownMode()) {
2071 // The mEndSelectionIndex is what is currently being selected
2072 // use the selected index if this is kNothingSelected
2073 PRInt32 focusedIndex
;
2074 if (mEndSelectionIndex
== kNothingSelected
) {
2075 focusedIndex
= GetSelectedIndex();
2077 focusedIndex
= mEndSelectionIndex
;
2079 if (focusedIndex
== kNothingSelected
) {
2083 nsCOMPtr
<nsIContent
> optionContent
= GetOptionContent(focusedIndex
);
2084 if (!optionContent
) {
2088 FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent
);
2093 nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent
* aMouseEvent
,
2096 if (IgnoreMouseEventForSelection(aMouseEvent
))
2097 return NS_ERROR_FAILURE
;
2099 if (nsIPresShell::GetCapturingContent() != mContent
) {
2100 // If we're not capturing, then ignore movement in the border
2101 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent
, this);
2102 nsRect borderInnerEdge
= GetScrollPortRect();
2103 if (!borderInnerEdge
.Contains(pt
)) {
2104 return NS_ERROR_FAILURE
;
2108 nsCOMPtr
<nsIContent
> content
;
2109 PresContext()->EventStateManager()->
2110 GetEventTargetContent(nsnull
, getter_AddRefs(content
));
2112 nsCOMPtr
<nsIContent
> optionContent
= GetOptionFromContent(content
);
2113 if (optionContent
) {
2114 aCurIndex
= GetIndexFromContent(optionContent
);
2118 PRInt32 numOptions
= GetNumberOfOptions();
2120 return NS_ERROR_FAILURE
;
2122 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent
, this);
2124 // If the event coordinate is above the first option frame, then target the
2125 // first option frame
2126 nsCOMPtr
<nsIContent
> firstOption
= GetOptionContent(0);
2127 NS_ASSERTION(firstOption
, "Can't find first option that's supposed to be there");
2128 nsIFrame
* optionFrame
= firstOption
->GetPrimaryFrame();
2130 nsPoint ptInOptionFrame
= pt
- optionFrame
->GetOffsetTo(this);
2131 if (ptInOptionFrame
.y
< 0 && ptInOptionFrame
.x
>= 0 &&
2132 ptInOptionFrame
.x
< optionFrame
->GetSize().width
) {
2138 nsCOMPtr
<nsIContent
> lastOption
= GetOptionContent(numOptions
- 1);
2139 // If the event coordinate is below the last option frame, then target the
2140 // last option frame
2141 NS_ASSERTION(lastOption
, "Can't find last option that's supposed to be there");
2142 optionFrame
= lastOption
->GetPrimaryFrame();
2144 nsPoint ptInOptionFrame
= pt
- optionFrame
->GetOffsetTo(this);
2145 if (ptInOptionFrame
.y
>= optionFrame
->GetSize().height
&& ptInOptionFrame
.x
>= 0 &&
2146 ptInOptionFrame
.x
< optionFrame
->GetSize().width
) {
2147 aCurIndex
= numOptions
- 1;
2152 return NS_ERROR_FAILURE
;
2156 nsListControlFrame::MouseDown(nsIDOMEvent
* aMouseEvent
)
2158 NS_ASSERTION(aMouseEvent
!= nsnull
, "aMouseEvent is null.");
2160 UpdateInListState(aMouseEvent
);
2162 if (mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::disabled
)) {
2166 // only allow selection with the left button
2167 // if a right button click is on the combobox itself
2168 // or on the select when in listbox mode, then let the click through
2169 if (!IsLeftButton(aMouseEvent
)) {
2170 if (IsInDropDownMode()) {
2171 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
2172 aMouseEvent
->PreventDefault();
2173 aMouseEvent
->StopPropagation();
2177 return NS_ERROR_FAILURE
; // means consume event
2183 PRInt32 selectedIndex
;
2184 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2186 mButtonDown
= PR_TRUE
;
2187 CaptureMouseEvents(PR_TRUE
);
2188 mChangesSinceDragStart
= HandleListSelection(aMouseEvent
, selectedIndex
);
2189 #ifdef ACCESSIBILITY
2190 if (mChangesSinceDragStart
) {
2191 FireMenuItemActiveEvent();
2195 // NOTE: the combo box is responsible for dropping it down
2196 if (mComboboxFrame
) {
2197 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
2201 if (!nsComboboxControlFrame::ToolkitHasNativePopup())
2203 PRBool isDroppedDown
= mComboboxFrame
->IsDroppedDown();
2204 nsIFrame
* comboFrame
= do_QueryFrame(mComboboxFrame
);
2205 nsWeakFrame
weakFrame(comboFrame
);
2206 mComboboxFrame
->ShowDropDown(!isDroppedDown
);
2207 if (!weakFrame
.IsAlive())
2209 if (isDroppedDown
) {
2210 CaptureMouseEvents(PR_FALSE
);
2219 //----------------------------------------------------------------------
2220 // nsIDOMMouseMotionListener
2221 //----------------------------------------------------------------------
2223 nsListControlFrame::MouseMove(nsIDOMEvent
* aMouseEvent
)
2225 NS_ASSERTION(aMouseEvent
, "aMouseEvent is null.");
2227 UpdateInListState(aMouseEvent
);
2229 if (IsInDropDownMode()) {
2230 if (mComboboxFrame
->IsDroppedDown()) {
2231 PRInt32 selectedIndex
;
2232 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2233 PerformSelection(selectedIndex
, PR_FALSE
, PR_FALSE
);
2236 } else {// XXX - temporary until we get drag events
2238 return DragMove(aMouseEvent
);
2245 nsListControlFrame::DragMove(nsIDOMEvent
* aMouseEvent
)
2247 NS_ASSERTION(aMouseEvent
, "aMouseEvent is null.");
2249 UpdateInListState(aMouseEvent
);
2251 if (!IsInDropDownMode()) {
2252 PRInt32 selectedIndex
;
2253 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2254 // Don't waste cycles if we already dragged over this item
2255 if (selectedIndex
== mEndSelectionIndex
) {
2258 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
2259 NS_ASSERTION(mouseEvent
, "aMouseEvent is not an nsIDOMMouseEvent!");
2262 mouseEvent
->GetMetaKey(&isControl
);
2264 mouseEvent
->GetCtrlKey(&isControl
);
2266 // Turn SHIFT on when you are dragging, unless control is on.
2267 PRBool wasChanged
= PerformSelection(selectedIndex
,
2268 !isControl
, isControl
);
2269 mChangesSinceDragStart
= mChangesSinceDragStart
|| wasChanged
;
2275 //----------------------------------------------------------------------
2277 //----------------------------------------------------------------------
2279 nsListControlFrame::ScrollToIndex(PRInt32 aIndex
)
2282 // XXX shouldn't we just do nothing if we're asked to scroll to
2283 // kNothingSelected?
2284 return ScrollToFrame(nsnull
);
2286 nsCOMPtr
<nsIContent
> content
= GetOptionContent(aIndex
);
2288 return ScrollToFrame(content
);
2292 return NS_ERROR_FAILURE
;
2296 nsListControlFrame::ScrollToFrame(nsIContent
* aOptElement
)
2298 // if null is passed in we scroll to 0,0
2299 if (nsnull
== aOptElement
) {
2300 ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT
);
2304 // otherwise we find the content's frame and scroll to it
2305 nsIFrame
*childFrame
= aOptElement
->GetPrimaryFrame();
2307 nsPoint pt
= GetScrollPosition();
2308 // get the scroll port rect relative to the scrolled frame
2309 nsRect rect
= GetScrollPortRect() + pt
;
2310 // get the option's rect relative to the scrolled frame
2311 nsRect
fRect(childFrame
->GetOffsetTo(GetScrolledFrame()),
2312 childFrame
->GetSize());
2314 // See if the selected frame (fRect) is inside the scrollport
2315 // area (rect). Check only the vertical dimension. Don't
2316 // scroll just because there's horizontal overflow.
2317 if (!(rect
.y
<= fRect
.y
&& fRect
.YMost() <= rect
.YMost())) {
2318 // figure out which direction we are going
2319 if (fRect
.YMost() > rect
.YMost()) {
2320 pt
.y
= fRect
.y
- (rect
.height
- fRect
.height
);
2324 ScrollTo(nsPoint(fRect
.x
, pt
.y
), nsIScrollableFrame::INSTANT
);
2330 //---------------------------------------------------------------------
2331 // Ok, the entire idea of this routine is to move to the next item that
2332 // is suppose to be selected. If the item is disabled then we search in
2333 // the same direction looking for the next item to select. If we run off
2334 // the end of the list then we start at the end of the list and search
2335 // backwards until we get back to the original item or an enabled option
2337 // aStartIndex - the index to start searching from
2338 // aNewIndex - will get set to the new index if it finds one
2339 // aNumOptions - the total number of options in the list
2340 // aDoAdjustInc - the initial increment 1-n
2341 // aDoAdjustIncNext - the increment used to search for the next enabled option
2343 // the aDoAdjustInc could be a "1" for a single item or
2344 // any number greater representing a page of items
2347 nsListControlFrame::AdjustIndexForDisabledOpt(PRInt32 aStartIndex
,
2349 PRInt32 aNumOptions
,
2350 PRInt32 aDoAdjustInc
,
2351 PRInt32 aDoAdjustIncNext
)
2353 // Cannot select anything if there is nothing to select
2354 if (aNumOptions
== 0) {
2355 aNewIndex
= kNothingSelected
;
2359 // means we reached the end of the list and now we are searching backwards
2360 PRBool doingReverse
= PR_FALSE
;
2361 // lowest index in the search range
2363 // highest index in the search range
2364 PRInt32 top
= aNumOptions
;
2366 // Start off keyboard options at selectedIndex if nothing else is defaulted to
2368 // XXX Perhaps this should happen for mouse too, to start off shift click
2369 // automatically in multiple ... to do this, we'd need to override
2370 // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
2371 // sure of the effects, though, so I'm not doing it just yet.
2372 PRInt32 startIndex
= aStartIndex
;
2373 if (startIndex
< bottom
) {
2374 startIndex
= GetSelectedIndex();
2376 PRInt32 newIndex
= startIndex
+ aDoAdjustInc
;
2378 // make sure we start off in the range
2379 if (newIndex
< bottom
) {
2381 } else if (newIndex
>= top
) {
2382 newIndex
= aNumOptions
-1;
2386 // if the newIndex isn't disabled, we are golden, bail out
2387 PRBool isDisabled
= PR_TRUE
;
2388 if (NS_SUCCEEDED(IsOptionDisabled(newIndex
, isDisabled
)) && !isDisabled
) {
2392 // it WAS disabled, so sart looking ahead for the next enabled option
2393 newIndex
+= aDoAdjustIncNext
;
2395 // well, if we reach end reverse the search
2396 if (newIndex
< bottom
) {
2398 return; // if we are in reverse mode and reach the end bail out
2400 // reset the newIndex to the end of the list we hit
2401 // reverse the incrementer
2402 // set the other end of the list to our original starting index
2404 aDoAdjustIncNext
= 1;
2405 doingReverse
= PR_TRUE
;
2408 } else if (newIndex
>= top
) {
2410 return; // if we are in reverse mode and reach the end bail out
2412 // reset the newIndex to the end of the list we hit
2413 // reverse the incrementer
2414 // set the other end of the list to our original starting index
2416 aDoAdjustIncNext
= -1;
2417 doingReverse
= PR_TRUE
;
2418 bottom
= startIndex
;
2423 // Looks like we found one
2424 aNewIndex
= newIndex
;
2428 nsListControlFrame::GetIncrementalString()
2430 if (sIncrementalString
== nsnull
)
2431 sIncrementalString
= new nsString();
2433 return *sIncrementalString
;
2437 nsListControlFrame::Shutdown()
2439 delete sIncrementalString
;
2440 sIncrementalString
= nsnull
;
2444 nsListControlFrame::DropDownToggleKey(nsIDOMEvent
* aKeyEvent
)
2446 // Cocoa widgets do native popups, so don't try to show
2448 if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
2449 aKeyEvent
->PreventDefault();
2450 if (!mComboboxFrame
->IsDroppedDown()) {
2451 mComboboxFrame
->ShowDropDown(PR_TRUE
);
2453 nsWeakFrame
weakFrame(this);
2454 // mEndSelectionIndex is the last item that got selected.
2455 ComboboxFinish(mEndSelectionIndex
);
2456 if (weakFrame
.IsAlive()) {
2464 nsListControlFrame::KeyPress(nsIDOMEvent
* aKeyEvent
)
2466 NS_ASSERTION(aKeyEvent
, "keyEvent is null.");
2468 if (mContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::disabled
))
2471 // Start by making sure we can query for a key event
2472 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aKeyEvent
);
2473 NS_ENSURE_TRUE(keyEvent
, NS_ERROR_FAILURE
);
2475 PRUint32 keycode
= 0;
2476 PRUint32 charcode
= 0;
2477 keyEvent
->GetKeyCode(&keycode
);
2478 keyEvent
->GetCharCode(&charcode
);
2480 PRBool isAlt
= PR_FALSE
;
2482 keyEvent
->GetAltKey(&isAlt
);
2484 if (keycode
== nsIDOMKeyEvent::DOM_VK_UP
|| keycode
== nsIDOMKeyEvent::DOM_VK_DOWN
) {
2485 DropDownToggleKey(aKeyEvent
);
2490 // Get control / shift modifiers
2491 PRBool isControl
= PR_FALSE
;
2492 PRBool isShift
= PR_FALSE
;
2493 keyEvent
->GetCtrlKey(&isControl
);
2495 keyEvent
->GetMetaKey(&isControl
);
2497 keyEvent
->GetShiftKey(&isShift
);
2499 // now make sure there are options or we are wasting our time
2500 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
2501 NS_ENSURE_TRUE(options
, NS_ERROR_FAILURE
);
2503 PRUint32 numOptions
= 0;
2504 options
->GetLength(&numOptions
);
2506 // Whether we did an incremental search or another action
2507 PRBool didIncrementalSearch
= PR_FALSE
;
2509 // this is the new index to set
2510 // DOM_VK_RETURN & DOM_VK_ESCAPE will not set this
2511 PRInt32 newIndex
= kNothingSelected
;
2513 // set up the old and new selected index and process it
2514 // DOM_VK_RETURN selects the item
2515 // DOM_VK_ESCAPE cancels the selection
2516 // default processing checks to see if the pressed the first
2517 // letter of an item in the list and advances to it
2519 if (isControl
&& (keycode
== nsIDOMKeyEvent::DOM_VK_UP
||
2520 keycode
== nsIDOMKeyEvent::DOM_VK_LEFT
||
2521 keycode
== nsIDOMKeyEvent::DOM_VK_DOWN
||
2522 keycode
== nsIDOMKeyEvent::DOM_VK_RIGHT
)) {
2523 // Don't go into multiple select mode unless this list can handle it
2524 isControl
= mControlSelectMode
= GetMultiple();
2525 } else if (charcode
!= ' ') {
2526 mControlSelectMode
= PR_FALSE
;
2530 case nsIDOMKeyEvent::DOM_VK_UP
:
2531 case nsIDOMKeyEvent::DOM_VK_LEFT
: {
2532 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2533 (PRInt32
)numOptions
,
2537 case nsIDOMKeyEvent::DOM_VK_DOWN
:
2538 case nsIDOMKeyEvent::DOM_VK_RIGHT
: {
2539 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2540 (PRInt32
)numOptions
,
2544 case nsIDOMKeyEvent::DOM_VK_RETURN
: {
2545 if (mComboboxFrame
!= nsnull
) {
2546 if (mComboboxFrame
->IsDroppedDown()) {
2547 nsWeakFrame
weakFrame(this);
2548 ComboboxFinish(mEndSelectionIndex
);
2549 if (!weakFrame
.IsAlive())
2555 newIndex
= mEndSelectionIndex
;
2559 case nsIDOMKeyEvent::DOM_VK_ESCAPE
: {
2560 nsWeakFrame
weakFrame(this);
2562 if (!weakFrame
.IsAlive()) {
2563 aKeyEvent
->PreventDefault(); // since we won't reach the one below
2568 case nsIDOMKeyEvent::DOM_VK_PAGE_UP
: {
2569 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2570 (PRInt32
)numOptions
,
2571 -(mNumDisplayRows
-1), -1);
2574 case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN
: {
2575 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2576 (PRInt32
)numOptions
,
2577 (mNumDisplayRows
-1), 1);
2580 case nsIDOMKeyEvent::DOM_VK_HOME
: {
2581 AdjustIndexForDisabledOpt(0, newIndex
,
2582 (PRInt32
)numOptions
,
2586 case nsIDOMKeyEvent::DOM_VK_END
: {
2587 AdjustIndexForDisabledOpt(numOptions
-1, newIndex
,
2588 (PRInt32
)numOptions
,
2592 #if defined(XP_WIN) || defined(XP_OS2)
2593 case nsIDOMKeyEvent::DOM_VK_F4
: {
2594 DropDownToggleKey(aKeyEvent
);
2599 case nsIDOMKeyEvent::DOM_VK_TAB
: {
2603 default: { // Select option with this as the first character
2604 // XXX Not I18N compliant
2606 if (isControl
&& charcode
!= ' ') {
2610 didIncrementalSearch
= PR_TRUE
;
2611 if (charcode
== 0) {
2612 // Backspace key will delete the last char in the string
2613 if (keycode
== NS_VK_BACK
&& !GetIncrementalString().IsEmpty()) {
2614 GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
2615 aKeyEvent
->PreventDefault();
2620 DOMTimeStamp keyTime
;
2621 aKeyEvent
->GetTimeStamp(&keyTime
);
2623 // Incremental Search: if time elapsed is below
2624 // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
2625 // string we will use to find options and start searching at the current
2626 // keystroke. Otherwise, Truncate the string if it's been a long time
2627 // since our last keypress.
2628 if (keyTime
- gLastKeyTime
> INCREMENTAL_SEARCH_KEYPRESS_TIME
) {
2629 // If this is ' ' and we are at the beginning of the string, treat it as
2630 // "select this option" (bug 191543)
2631 if (charcode
== ' ') {
2632 newIndex
= mEndSelectionIndex
;
2635 GetIncrementalString().Truncate();
2637 gLastKeyTime
= keyTime
;
2639 // Append this keystroke to the search string.
2640 PRUnichar uniChar
= ToLowerCase(static_cast<PRUnichar
>(charcode
));
2641 GetIncrementalString().Append(uniChar
);
2643 // See bug 188199, if all letters in incremental string are same, just try to match the first one
2644 nsAutoString
incrementalString(GetIncrementalString());
2645 PRUint32 charIndex
= 1, stringLength
= incrementalString
.Length();
2646 while (charIndex
< stringLength
&& incrementalString
[charIndex
] == incrementalString
[charIndex
- 1]) {
2649 if (charIndex
== stringLength
) {
2650 incrementalString
.Truncate(1);
2654 // Determine where we're going to start reading the string
2655 // If we have multiple characters to look for, we start looking *at* the
2656 // current option. If we have only one character to look for, we start
2657 // looking *after* the current option.
2658 // Exception: if there is no option selected to start at, we always start
2660 PRInt32 startIndex
= GetSelectedIndex();
2661 if (startIndex
== kNothingSelected
) {
2663 } else if (stringLength
== 1) {
2668 for (i
= 0; i
< numOptions
; i
++) {
2669 PRUint32 index
= (i
+ startIndex
) % numOptions
;
2670 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
=
2671 GetOption(options
, index
);
2672 if (optionElement
) {
2674 if (NS_OK
== optionElement
->GetText(text
)) {
2675 if (StringBeginsWith(text
, incrementalString
,
2676 nsCaseInsensitiveStringComparator())) {
2677 PRBool wasChanged
= PerformSelection(index
, isShift
, isControl
);
2679 // dispatch event, update combobox, etc.
2680 if (!UpdateSelection()) {
2683 #ifdef ACCESSIBILITY
2684 FireMenuItemActiveEvent(); // Only fire if new item reached
2696 // We ate the key if we got this far.
2697 aKeyEvent
->PreventDefault();
2699 // If we didn't do an incremental search, clear the string
2700 if (!didIncrementalSearch
) {
2701 GetIncrementalString().Truncate();
2704 // Actually process the new index and let the selection code
2705 // do the scrolling for us
2706 if (newIndex
!= kNothingSelected
) {
2707 // If you hold control, no key will actually do anything except space.
2708 PRBool wasChanged
= PR_FALSE
;
2709 if (isControl
&& charcode
!= ' ') {
2710 mStartSelectionIndex
= newIndex
;
2711 mEndSelectionIndex
= newIndex
;
2713 ScrollToIndex(newIndex
);
2714 } else if (mControlSelectMode
&& charcode
== ' ') {
2715 wasChanged
= SingleSelection(newIndex
, PR_TRUE
);
2717 wasChanged
= PerformSelection(newIndex
, isShift
, isControl
);
2720 // dispatch event, update combobox, etc.
2721 if (!UpdateSelection()) {
2725 #ifdef ACCESSIBILITY
2726 if (charcode
!= ' ') {
2727 FireMenuItemActiveEvent();
2736 /******************************************************************************
2737 * nsListEventListener
2738 *****************************************************************************/
2740 NS_IMPL_ADDREF(nsListEventListener
)
2741 NS_IMPL_RELEASE(nsListEventListener
)
2742 NS_INTERFACE_MAP_BEGIN(nsListEventListener
)
2743 NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener
)
2744 NS_INTERFACE_MAP_ENTRY(nsIDOMMouseMotionListener
)
2745 NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener
)
2746 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener
, nsIDOMMouseListener
)
2747 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIDOMMouseListener
)
2748 NS_INTERFACE_MAP_END
2750 #define FORWARD_EVENT(_event) \
2752 nsListEventListener::_event(nsIDOMEvent* aEvent) \
2755 return mFrame->nsListControlFrame::_event(aEvent); \
2759 #define IGNORE_EVENT(_event) \
2761 nsListEventListener::_event(nsIDOMEvent* aEvent) \
2764 IGNORE_EVENT(HandleEvent
)
2766 /*================== nsIDOMKeyListener =========================*/
2768 IGNORE_EVENT(KeyDown
)
2770 FORWARD_EVENT(KeyPress
)
2772 /*=============== nsIDOMMouseListener ======================*/
2774 FORWARD_EVENT(MouseDown
)
2775 FORWARD_EVENT(MouseUp
)
2776 IGNORE_EVENT(MouseClick
)
2777 IGNORE_EVENT(MouseDblClick
)
2778 IGNORE_EVENT(MouseOver
)
2779 IGNORE_EVENT(MouseOut
)
2781 /*=============== nsIDOMMouseMotionListener ======================*/
2783 FORWARD_EVENT(MouseMove
)
2784 // XXXbryner does anyone call this, ever?
2785 IGNORE_EVENT(DragMove
)
2787 #undef FORWARD_EVENT