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 <matspal@gmail.com>
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 "nsIDOMHTMLOptionElement.h"
55 #include "nsComboboxControlFrame.h"
56 #include "nsIViewManager.h"
57 #include "nsIDOMHTMLOptGroupElement.h"
58 #include "nsWidgetsCID.h"
59 #include "nsIPresShell.h"
60 #include "nsHTMLParts.h"
61 #include "nsIDOMEventTarget.h"
62 #include "nsEventDispatcher.h"
63 #include "nsIEventStateManager.h"
64 #include "nsIEventListenerManager.h"
65 #include "nsIDOMKeyEvent.h"
66 #include "nsIDOMMouseEvent.h"
67 #include "nsIPrivateDOMEvent.h"
69 #include "nsISupportsPrimitives.h"
70 #include "nsIComponentManager.h"
71 #include "nsILookAndFeel.h"
72 #include "nsIFontMetrics.h"
73 #include "nsIScrollableFrame.h"
74 #include "nsIDOMEventTarget.h"
75 #include "nsIDOMNSEvent.h"
76 #include "nsGUIEvent.h"
77 #include "nsIServiceManager.h"
78 #include "nsINodeInfo.h"
80 #include "nsAccessibilityService.h"
82 #include "nsISelectElement.h"
83 #include "nsIPrivateDOMEvent.h"
84 #include "nsCSSRendering.h"
86 #include "nsIDOMMouseListener.h"
87 #include "nsIDOMMouseMotionListener.h"
88 #include "nsIDOMKeyListener.h"
89 #include "nsLayoutUtils.h"
90 #include "nsDisplayList.h"
91 #include "nsIEventStateManager.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(aBuilder
,
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
<nsIDOMHTMLSelectElement
> selectDOMElement(do_QueryInterface(mContent
));
289 NS_ASSERTION(selectDOMElement
, "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(selectDOMElement
->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
);
341 if (childframe
&& !childframe
->GetContent()->IsElement()) {
350 // get the child rect
351 fRect
= childframe
->GetRect();
352 // get it into our coordinates
353 fRect
.MoveBy(childframe
->GetParent()->GetOffsetTo(this));
355 fRect
.x
= fRect
.y
= 0;
356 fRect
.width
= GetScrollPortRect().width
;
357 fRect
.height
= CalcFallbackRowHeight();
358 fRect
.MoveBy(containerFrame
->GetOffsetTo(this));
362 PRBool lastItemIsSelected
= PR_FALSE
;
363 if (focusedContent
) {
364 nsCOMPtr
<nsIDOMHTMLOptionElement
> domOpt
=
365 do_QueryInterface(focusedContent
);
367 domOpt
->GetSelected(&lastItemIsSelected
);
371 // set up back stop colors and then ask L&F service for the real colors
373 presContext
->LookAndFeel()->
374 GetColor(lastItemIsSelected
?
375 nsILookAndFeel::eColor_WidgetSelectForeground
:
376 nsILookAndFeel::eColor_WidgetSelectBackground
, color
);
378 nsCSSRendering::PaintFocus(presContext
, aRC
, fRect
, color
);
382 nsListControlFrame::InvalidateFocus()
384 if (mFocused
!= this)
387 nsIFrame
* containerFrame
= GetOptionsContainer();
388 if (containerFrame
) {
389 // Invalidating from the containerFrame because that's where our focus
391 // The origin of the scrollport is the origin of containerFrame.
392 nsRect invalidateArea
= containerFrame
->GetVisualOverflowRect();
393 nsRect
emptyFallbackArea(0, 0, GetScrollPortRect().width
, CalcFallbackRowHeight());
394 invalidateArea
.UnionRect(invalidateArea
, emptyFallbackArea
);
395 containerFrame
->Invalidate(invalidateArea
);
399 NS_QUERYFRAME_HEAD(nsListControlFrame
)
400 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
401 NS_QUERYFRAME_ENTRY(nsIListControlFrame
)
402 NS_QUERYFRAME_ENTRY(nsISelectControlFrame
)
403 NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame
)
406 already_AddRefed
<nsAccessible
>
407 nsListControlFrame::CreateAccessible()
409 nsAccessibilityService
* accService
= nsIPresShell::AccService();
411 return accService
->CreateHTMLListboxAccessible(mContent
,
412 PresContext()->PresShell());
420 GetMaxOptionHeight(nsIFrame
* aContainer
)
423 for (nsIFrame
* option
= aContainer
->GetFirstChild(nsnull
);
424 option
; option
= option
->GetNextSibling()) {
425 nscoord optionHeight
;
426 if (nsCOMPtr
<nsIDOMHTMLOptGroupElement
>
427 (do_QueryInterface(option
->GetContent()))) {
429 optionHeight
= GetMaxOptionHeight(option
);
432 optionHeight
= option
->GetSize().height
;
434 if (result
< optionHeight
)
435 result
= optionHeight
;
441 IsOptGroup(nsIContent
*aContent
)
443 return (aContent
->NodeInfo()->Equals(nsGkAtoms::optgroup
) &&
448 IsOption(nsIContent
*aContent
)
450 return (aContent
->NodeInfo()->Equals(nsGkAtoms::option
) &&
455 GetNumberOfOptionsRecursive(nsIContent
* aContent
)
457 PRUint32 optionCount
= 0;
458 const PRUint32 childCount
= aContent
? aContent
->GetChildCount() : 0;
459 for (PRUint32 index
= 0; index
< childCount
; ++index
) {
460 nsIContent
* child
= aContent
->GetChildAt(index
);
461 if (::IsOption(child
)) {
464 else if (::IsOptGroup(child
)) {
465 optionCount
+= ::GetNumberOfOptionsRecursive(child
);
471 //-----------------------------------------------------------------
472 // Main Reflow for ListBox/Dropdown
473 //-----------------------------------------------------------------
476 nsListControlFrame::CalcHeightOfARow()
478 // Calculate the height of a single row in the listbox or dropdown list by
479 // using the tallest thing in the subtree, since there may be option groups
480 // in addition to option elements, either of which may be visible or
481 // invisible, may use different fonts, etc.
482 PRInt32 heightOfARow
= GetMaxOptionHeight(GetOptionsContainer());
484 // Check to see if we have zero items (and optimize by checking
485 // heightOfARow first)
486 if (heightOfARow
== 0 && GetNumberOfOptions() == 0) {
487 heightOfARow
= CalcFallbackRowHeight();
494 nsListControlFrame::GetPrefWidth(nsIRenderingContext
*aRenderingContext
)
497 DISPLAY_PREF_WIDTH(this, result
);
499 // Always add scrollbar widths to the pref-width of the scrolled
500 // content. Combobox frames depend on this happening in the dropdown,
501 // and standalone listboxes are overflow:scroll so they need it too.
502 result
= GetScrolledFrame()->GetPrefWidth(aRenderingContext
);
503 result
= NSCoordSaturatingAdd(result
,
504 GetDesiredScrollbarSizes(PresContext(), aRenderingContext
).LeftRight());
510 nsListControlFrame::GetMinWidth(nsIRenderingContext
*aRenderingContext
)
513 DISPLAY_MIN_WIDTH(this, result
);
515 // Always add scrollbar widths to the min-width of the scrolled
516 // content. Combobox frames depend on this happening in the dropdown,
517 // and standalone listboxes are overflow:scroll so they need it too.
518 result
= GetScrolledFrame()->GetMinWidth(aRenderingContext
);
519 result
+= GetDesiredScrollbarSizes(PresContext(), aRenderingContext
).LeftRight();
525 nsListControlFrame::Reflow(nsPresContext
* aPresContext
,
526 nsHTMLReflowMetrics
& aDesiredSize
,
527 const nsHTMLReflowState
& aReflowState
,
528 nsReflowStatus
& aStatus
)
530 NS_PRECONDITION(aReflowState
.ComputedWidth() != NS_UNCONSTRAINEDSIZE
,
531 "Must have a computed width");
533 mHasPendingInterruptAtStartOfReflow
= aPresContext
->HasPendingInterrupt();
535 // If all the content and frames are here
536 // then initialize it before reflow
537 if (mIsAllContentHere
&& !mHasBeenInitialized
) {
538 if (PR_FALSE
== mIsAllFramesHere
) {
539 CheckIfAllFramesHere();
541 if (mIsAllFramesHere
&& !mHasBeenInitialized
) {
542 mHasBeenInitialized
= PR_TRUE
;
546 if (GetStateBits() & NS_FRAME_FIRST_REFLOW
) {
547 nsFormControlFrame::RegUnRegAccessKey(this, PR_TRUE
);
550 if (IsInDropDownMode()) {
551 return ReflowAsDropdown(aPresContext
, aDesiredSize
, aReflowState
, aStatus
);
555 * Due to the fact that our intrinsic height depends on the heights of our
556 * kids, we end up having to do two-pass reflow, in general -- the first pass
557 * to find the intrinsic height and a second pass to reflow the scrollframe
558 * at that height (which will size the scrollbars correctly, etc).
560 * Naturaly, we want to avoid doing the second reflow as much as possible.
561 * We can skip it in the following cases (in all of which the first reflow is
562 * already happening at the right height):
564 * - We're reflowing with a constrained computed height -- just use that
566 * - We're not dirty and have no dirty kids and shouldn't be reflowing all
567 * kids. In this case, our cached max height of a child is not going to
569 * - We do our first reflow using our cached max height of a child, then
570 * compute the new max height and it's the same as the old one.
573 PRBool autoHeight
= (aReflowState
.ComputedHeight() == NS_UNCONSTRAINEDSIZE
);
575 mMightNeedSecondPass
= autoHeight
&&
576 (NS_SUBTREE_DIRTY(this) || aReflowState
.ShouldReflowAllKids());
578 nsHTMLReflowState
state(aReflowState
);
579 PRInt32 length
= GetNumberOfOptions();
581 nscoord oldHeightOfARow
= HeightOfARow();
583 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
) && autoHeight
) {
584 // When not doing an initial reflow, and when the height is auto, start off
585 // with our computed height set to what we'd expect our height to be.
586 nscoord computedHeight
= CalcIntrinsicHeight(oldHeightOfARow
, length
);
587 state
.ApplyMinMaxConstraints(nsnull
, &computedHeight
);
588 state
.SetComputedHeight(computedHeight
);
591 nsresult rv
= nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
,
593 NS_ENSURE_SUCCESS(rv
, rv
);
595 if (!mMightNeedSecondPass
) {
596 NS_ASSERTION(!autoHeight
|| HeightOfARow() == oldHeightOfARow
,
597 "How did our height of a row change if nothing was dirty?");
598 NS_ASSERTION(!autoHeight
||
599 !(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
600 "How do we not need a second pass during initial reflow at "
602 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
603 "Shouldn't be suppressing if we don't need a second pass!");
605 // Update our mNumDisplayRows based on our new row height now that we
606 // know it. Note that if autoHeight and we landed in this code then we
607 // already set mNumDisplayRows in CalcIntrinsicHeight. Also note that we
608 // can't use HeightOfARow() here because that just uses a cached value
609 // that we didn't compute.
610 nscoord rowHeight
= CalcHeightOfARow();
611 if (rowHeight
== 0) {
612 // Just pick something
615 mNumDisplayRows
= NS_MAX(1, state
.ComputedHeight() / rowHeight
);
622 mMightNeedSecondPass
= PR_FALSE
;
624 // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
625 // will have suppressed the scrollbar update.
626 if (!IsScrollbarUpdateSuppressed()) {
627 // All done. No need to do more reflow.
628 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
629 "Shouldn't be suppressing if the height of a row has not "
634 SetSuppressScrollbarUpdate(PR_FALSE
);
636 // Gotta reflow again.
637 // XXXbz We're just changing the height here; do we need to dirty ourselves
638 // or anything like that? We might need to, per the letter of the reflow
639 // protocol, but things seem to work fine without it... Is that just an
640 // implementation detail of nsHTMLScrollFrame that we're depending on?
641 nsHTMLScrollFrame::DidReflow(aPresContext
, &state
, aStatus
);
643 // Now compute the height we want to have
644 nscoord computedHeight
= CalcIntrinsicHeight(HeightOfARow(), length
);
645 state
.ApplyMinMaxConstraints(nsnull
, &computedHeight
);
646 state
.SetComputedHeight(computedHeight
);
648 nsHTMLScrollFrame::WillReflow(aPresContext
);
650 // XXXbz to make the ascent really correct, we should add our
651 // mComputedPadding.top to it (and subtract it from descent). Need that
652 // because nsGfxScrollFrame just adds in the border....
653 return nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
657 nsListControlFrame::ReflowAsDropdown(nsPresContext
* aPresContext
,
658 nsHTMLReflowMetrics
& aDesiredSize
,
659 const nsHTMLReflowState
& aReflowState
,
660 nsReflowStatus
& aStatus
)
662 NS_PRECONDITION(aReflowState
.ComputedHeight() == NS_UNCONSTRAINEDSIZE
,
663 "We should not have a computed height here!");
665 mMightNeedSecondPass
= NS_SUBTREE_DIRTY(this) ||
666 aReflowState
.ShouldReflowAllKids();
669 nscoord oldHeightOfARow
= HeightOfARow();
672 nsHTMLReflowState
state(aReflowState
);
674 nscoord oldVisibleHeight
;
675 if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW
)) {
676 // When not doing an initial reflow, and when the height is auto, start off
677 // with our computed height set to what we'd expect our height to be.
678 // Note: At this point, mLastDropdownComputedHeight can be
679 // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to constrain
680 // the height. That's fine; just do the same thing as last time.
681 state
.SetComputedHeight(mLastDropdownComputedHeight
);
682 oldVisibleHeight
= GetScrolledFrame()->GetSize().height
;
684 // Set oldVisibleHeight to something that will never test true against a
686 oldVisibleHeight
= NS_UNCONSTRAINEDSIZE
;
689 nsresult rv
= nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
,
691 NS_ENSURE_SUCCESS(rv
, rv
);
693 if (!mMightNeedSecondPass
) {
694 NS_ASSERTION(oldVisibleHeight
== GetScrolledFrame()->GetSize().height
,
695 "How did our kid's height change if nothing was dirty?");
696 NS_ASSERTION(HeightOfARow() == oldHeightOfARow
,
697 "How did our height of a row change if nothing was dirty?");
698 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
699 "Shouldn't be suppressing if we don't need a second pass!");
700 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
701 "How can we avoid a second pass during first reflow?");
705 mMightNeedSecondPass
= PR_FALSE
;
707 // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
708 // will have suppressed the scrollbar update.
709 if (!IsScrollbarUpdateSuppressed()) {
710 // All done. No need to do more reflow.
711 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
712 "How can we avoid a second pass during first reflow?");
716 SetSuppressScrollbarUpdate(PR_FALSE
);
718 nscoord visibleHeight
= GetScrolledFrame()->GetSize().height
;
719 nscoord heightOfARow
= HeightOfARow();
721 // Gotta reflow again.
722 // XXXbz We're just changing the height here; do we need to dirty ourselves
723 // or anything like that? We might need to, per the letter of the reflow
724 // protocol, but things seem to work fine without it... Is that just an
725 // implementation detail of nsHTMLScrollFrame that we're depending on?
726 nsHTMLScrollFrame::DidReflow(aPresContext
, &state
, aStatus
);
728 // Now compute the height we want to have
729 mNumDisplayRows
= kMaxDropDownRows
;
730 if (visibleHeight
> mNumDisplayRows
* heightOfARow
) {
731 visibleHeight
= mNumDisplayRows
* heightOfARow
;
732 // This is an adaptive algorithm for figuring out how many rows
733 // should be displayed in the drop down. The standard size is 20 rows,
734 // but on 640x480 it is typically too big.
735 // This takes the height of the screen divides it by two and then subtracts off
736 // an estimated height of the combobox. I estimate it by taking the max element size
737 // of the drop down and multiplying it by 2 (this is arbitrary) then subtract off
738 // the border and padding of the drop down (again rather arbitrary)
739 // This all breaks down if the font of the combobox is a lot larger then the option items
740 // or CSS style has set the height of the combobox to be rather large.
741 // We can fix these cases later if they actually happen.
742 nsRect screen
= nsFormControlFrame::GetUsableScreenRect(aPresContext
);
743 nscoord screenHeight
= screen
.height
;
745 nscoord availDropHgt
= (screenHeight
/ 2) - (heightOfARow
*2); // approx half screen minus combo size
746 availDropHgt
-= aReflowState
.mComputedBorderPadding
.top
+ aReflowState
.mComputedBorderPadding
.bottom
;
748 nscoord hgt
= visibleHeight
+ aReflowState
.mComputedBorderPadding
.top
+ aReflowState
.mComputedBorderPadding
.bottom
;
749 if (heightOfARow
> 0) {
750 if (hgt
> availDropHgt
) {
751 visibleHeight
= (availDropHgt
/ heightOfARow
) * heightOfARow
;
753 mNumDisplayRows
= visibleHeight
/ heightOfARow
;
755 // Hmmm, not sure what to do here. Punt, and make both of them one
760 state
.SetComputedHeight(mNumDisplayRows
* heightOfARow
);
761 // Note: no need to apply min/max constraints, since we have no such
762 // rules applied to the combobox dropdown.
763 // XXXbz this is ending up too big!! Figure out why.
764 } else if (visibleHeight
== 0) {
765 // Looks like we have no options. Just size us to a single row height.
766 state
.SetComputedHeight(heightOfARow
);
768 // Not too big, not too small. Just use it!
769 state
.SetComputedHeight(NS_UNCONSTRAINEDSIZE
);
772 // Note: At this point, state.mComputedHeight can be NS_UNCONSTRAINEDSIZE in
773 // cases when there were some options, but not too many (so no scrollbar was
774 // needed). That's fine; just store that.
775 mLastDropdownComputedHeight
= state
.ComputedHeight();
777 nsHTMLScrollFrame::WillReflow(aPresContext
);
778 return nsHTMLScrollFrame::Reflow(aPresContext
, aDesiredSize
, state
, aStatus
);
781 nsGfxScrollFrameInner::ScrollbarStyles
782 nsListControlFrame::GetScrollbarStyles() const
784 // We can't express this in the style system yet; when we can, this can go away
785 // and GetScrollbarStyles can be devirtualized
786 PRInt32 verticalStyle
= IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
787 : NS_STYLE_OVERFLOW_SCROLL
;
788 return nsGfxScrollFrameInner::ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN
,
793 nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const
795 return !IsInDropDownMode();
798 //---------------------------------------------------------
800 nsListControlFrame::IsOptionElement(nsIContent
* aContent
)
802 PRBool result
= PR_FALSE
;
804 nsCOMPtr
<nsIDOMHTMLOptionElement
> optElem
;
805 if (NS_SUCCEEDED(aContent
->QueryInterface(NS_GET_IID(nsIDOMHTMLOptionElement
),(void**) getter_AddRefs(optElem
)))) {
806 if (optElem
!= nsnull
) {
815 nsListControlFrame::GetContentInsertionFrame() {
816 return GetOptionsContainer()->GetContentInsertionFrame();
819 //---------------------------------------------------------
820 // Starts at the passed in content object and walks up the
821 // parent heierarchy looking for the nsIDOMHTMLOptionElement
822 //---------------------------------------------------------
824 nsListControlFrame::GetOptionFromContent(nsIContent
*aContent
)
826 for (nsIContent
* content
= aContent
; content
; content
= content
->GetParent()) {
827 if (IsOptionElement(content
)) {
835 //---------------------------------------------------------
836 // Finds the index of the hit frame's content in the list
837 // of option elements
838 //---------------------------------------------------------
840 nsListControlFrame::GetIndexFromContent(nsIContent
*aContent
)
842 nsCOMPtr
<nsIDOMHTMLOptionElement
> option
;
843 option
= do_QueryInterface(aContent
);
846 option
->GetIndex(&retval
);
851 return kNothingSelected
;
854 //---------------------------------------------------------
856 nsListControlFrame::ExtendedSelection(PRInt32 aStartIndex
,
860 return SetOptionsSelectedFromFrame(aStartIndex
, aEndIndex
,
864 //---------------------------------------------------------
866 nsListControlFrame::SingleSelection(PRInt32 aClickedIndex
, PRBool aDoToggle
)
868 if (mComboboxFrame
) {
869 mComboboxFrame
->UpdateRecentIndex(GetSelectedIndex());
872 PRBool wasChanged
= PR_FALSE
;
873 // Get Current selection
875 wasChanged
= ToggleOptionSelectedFromFrame(aClickedIndex
);
877 wasChanged
= SetOptionsSelectedFromFrame(aClickedIndex
, aClickedIndex
,
880 ScrollToIndex(aClickedIndex
);
881 mStartSelectionIndex
= aClickedIndex
;
882 mEndSelectionIndex
= aClickedIndex
;
888 nsListControlFrame::InitSelectionRange(PRInt32 aClickedIndex
)
891 // If nothing is selected, set the start selection depending on where
892 // the user clicked and what the initial selection is:
893 // - if the user clicked *before* selectedIndex, set the start index to
894 // the end of the first contiguous selection.
895 // - if the user clicked *after* the end of the first contiguous
896 // selection, set the start index to selectedIndex.
897 // - if the user clicked *within* the first contiguous selection, set the
898 // start index to selectedIndex.
899 // The last two rules, of course, boil down to the same thing: if the user
900 // clicked >= selectedIndex, return selectedIndex.
902 // This makes it so that shift click works properly when you first click
903 // in a multiple select.
905 PRInt32 selectedIndex
= GetSelectedIndex();
906 if (selectedIndex
>= 0) {
907 // Get the end of the contiguous selection
908 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
909 NS_ASSERTION(options
, "Collection of options is null!");
911 options
->GetLength(&numOptions
);
913 // Push i to one past the last selected index in the group
914 for (i
=selectedIndex
+1; i
< numOptions
; i
++) {
916 nsCOMPtr
<nsIDOMHTMLOptionElement
> option
= GetOption(options
, i
);
917 option
->GetSelected(&selected
);
923 if (aClickedIndex
< selectedIndex
) {
924 // User clicked before selection, so start selection at end of
925 // contiguous selection
926 mStartSelectionIndex
= i
-1;
927 mEndSelectionIndex
= selectedIndex
;
929 // User clicked after selection, so start selection at start of
930 // contiguous selection
931 mStartSelectionIndex
= selectedIndex
;
932 mEndSelectionIndex
= i
-1;
937 //---------------------------------------------------------
939 nsListControlFrame::PerformSelection(PRInt32 aClickedIndex
,
943 PRBool wasChanged
= PR_FALSE
;
945 if (aClickedIndex
== kNothingSelected
) {
947 else if (GetMultiple()) {
949 // Make sure shift+click actually does something expected when
950 // the user has never clicked on the select
951 if (mStartSelectionIndex
== kNothingSelected
) {
952 InitSelectionRange(aClickedIndex
);
955 // Get the range from beginning (low) to end (high)
956 // Shift *always* works, even if the current option is disabled
959 if (mStartSelectionIndex
== kNothingSelected
) {
960 startIndex
= aClickedIndex
;
961 endIndex
= aClickedIndex
;
962 } else if (mStartSelectionIndex
<= aClickedIndex
) {
963 startIndex
= mStartSelectionIndex
;
964 endIndex
= aClickedIndex
;
966 startIndex
= aClickedIndex
;
967 endIndex
= mStartSelectionIndex
;
970 // Clear only if control was not pressed
971 wasChanged
= ExtendedSelection(startIndex
, endIndex
, !aIsControl
);
972 ScrollToIndex(aClickedIndex
);
974 if (mStartSelectionIndex
== kNothingSelected
) {
975 mStartSelectionIndex
= aClickedIndex
;
976 mEndSelectionIndex
= aClickedIndex
;
978 mEndSelectionIndex
= aClickedIndex
;
981 } else if (aIsControl
) {
982 wasChanged
= SingleSelection(aClickedIndex
, PR_TRUE
);
984 wasChanged
= SingleSelection(aClickedIndex
, PR_FALSE
);
987 wasChanged
= SingleSelection(aClickedIndex
, PR_FALSE
);
993 //---------------------------------------------------------
995 nsListControlFrame::HandleListSelection(nsIDOMEvent
* aEvent
,
996 PRInt32 aClickedIndex
)
998 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aEvent
);
1002 mouseEvent
->GetMetaKey(&isControl
);
1004 mouseEvent
->GetCtrlKey(&isControl
);
1006 mouseEvent
->GetShiftKey(&isShift
);
1007 return PerformSelection(aClickedIndex
, isShift
, isControl
);
1010 //---------------------------------------------------------
1012 nsListControlFrame::CaptureMouseEvents(PRBool aGrabMouseEvents
)
1014 // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
1015 // so we never want to do mouse capturing. Note that we only bail if the list
1016 // is in drop-down mode, and the caller is requesting capture (we let release capture
1017 // requests go through to ensure that we can release capture requested via other
1018 // code paths, if any exist).
1019 if (aGrabMouseEvents
&& IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
1022 if (aGrabMouseEvents
) {
1023 nsIPresShell::SetCapturingContent(mContent
, CAPTURE_IGNOREALLOWED
);
1025 nsIContent
* capturingContent
= nsIPresShell::GetCapturingContent();
1027 PRBool dropDownIsHidden
= PR_FALSE
;
1028 if (IsInDropDownMode()) {
1029 dropDownIsHidden
= !mComboboxFrame
->IsDroppedDown();
1031 if (capturingContent
== mContent
|| dropDownIsHidden
) {
1032 // only clear the capturing content if *we* are the ones doing the
1033 // capturing (or if the dropdown is hidden, in which case NO-ONE should
1034 // be capturing anything - it could be a scrollbar inside this listbox
1035 // which is actually grabbing
1036 // This shouldn't be necessary. We should simply ensure that events targeting
1037 // scrollbars are never visible to DOM consumers.
1038 nsIPresShell::SetCapturingContent(nsnull
, 0);
1043 //---------------------------------------------------------
1045 nsListControlFrame::HandleEvent(nsPresContext
* aPresContext
,
1047 nsEventStatus
* aEventStatus
)
1049 NS_ENSURE_ARG_POINTER(aEventStatus
);
1051 /*const char * desc[] = {"NS_MOUSE_MOVE",
1052 "NS_MOUSE_LEFT_BUTTON_UP",
1053 "NS_MOUSE_LEFT_BUTTON_DOWN",
1054 "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
1055 "NS_MOUSE_MIDDLE_BUTTON_UP",
1056 "NS_MOUSE_MIDDLE_BUTTON_DOWN",
1057 "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
1058 "NS_MOUSE_RIGHT_BUTTON_UP",
1059 "NS_MOUSE_RIGHT_BUTTON_DOWN",
1060 "NS_MOUSE_ENTER_SYNTH",
1061 "NS_MOUSE_EXIT_SYNTH",
1062 "NS_MOUSE_LEFT_DOUBLECLICK",
1063 "NS_MOUSE_MIDDLE_DOUBLECLICK",
1064 "NS_MOUSE_RIGHT_DOUBLECLICK",
1065 "NS_MOUSE_LEFT_CLICK",
1066 "NS_MOUSE_MIDDLE_CLICK",
1067 "NS_MOUSE_RIGHT_CLICK"};
1068 int inx = aEvent->message-NS_MOUSE_MESSAGE_START;
1069 if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) {
1070 printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message);
1072 printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
1075 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
)
1078 // do we have style that affects how we are selected?
1079 // do we have user-input style?
1080 const nsStyleUserInterface
* uiStyle
= GetStyleUserInterface();
1081 if (uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_NONE
|| uiStyle
->mUserInput
== NS_STYLE_USER_INPUT_DISABLED
)
1082 return nsFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
1084 nsEventStates eventStates
= mContent
->IntrinsicState();
1085 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
))
1088 return nsHTMLScrollFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
1092 //---------------------------------------------------------
1094 nsListControlFrame::SetInitialChildList(nsIAtom
* aListName
,
1095 nsFrameList
& aChildList
)
1097 // First check to see if all the content has been added
1098 mIsAllContentHere
= mContent
->IsDoneAddingChildren();
1099 if (!mIsAllContentHere
) {
1100 mIsAllFramesHere
= PR_FALSE
;
1101 mHasBeenInitialized
= PR_FALSE
;
1103 nsresult rv
= nsHTMLScrollFrame::SetInitialChildList(aListName
, aChildList
);
1105 // If all the content is here now check
1106 // to see if all the frames have been created
1107 /*if (mIsAllContentHere) {
1108 // If all content and frames are here
1109 // the reset/initialize
1110 if (CheckIfAllFramesHere()) {
1111 ResetList(aPresContext);
1112 mHasBeenInitialized = PR_TRUE;
1119 //---------------------------------------------------------
1121 nsListControlFrame::GetSizeAttribute(PRInt32
*aSize
) {
1122 nsresult rv
= NS_OK
;
1123 nsIDOMHTMLSelectElement
* selectElement
;
1124 rv
= mContent
->QueryInterface(NS_GET_IID(nsIDOMHTMLSelectElement
),(void**) &selectElement
);
1125 if (mContent
&& NS_SUCCEEDED(rv
)) {
1126 rv
= selectElement
->GetSize(aSize
);
1127 NS_RELEASE(selectElement
);
1133 //---------------------------------------------------------
1135 nsListControlFrame::Init(nsIContent
* aContent
,
1137 nsIFrame
* aPrevInFlow
)
1139 nsresult result
= nsHTMLScrollFrame::Init(aContent
, aParent
, aPrevInFlow
);
1141 // get the receiver interface from the browser button's content node
1142 NS_ENSURE_STATE(mContent
);
1144 // we shouldn't have to unregister this listener because when
1145 // our frame goes away all these content node go away as well
1146 // because our frame is the only one who references them.
1147 // we need to hook up our listeners before the editor is initialized
1148 mEventListener
= new nsListEventListener(this);
1149 if (!mEventListener
)
1150 return NS_ERROR_OUT_OF_MEMORY
;
1152 mContent
->AddEventListenerByIID(static_cast<nsIDOMMouseListener
*>
1154 NS_GET_IID(nsIDOMMouseListener
));
1156 mContent
->AddEventListenerByIID(static_cast<nsIDOMMouseMotionListener
*>
1158 NS_GET_IID(nsIDOMMouseMotionListener
));
1160 mContent
->AddEventListenerByIID(static_cast<nsIDOMKeyListener
*>
1162 NS_GET_IID(nsIDOMKeyListener
));
1164 mStartSelectionIndex
= kNothingSelected
;
1165 mEndSelectionIndex
= kNothingSelected
;
1167 mLastDropdownBackstopColor
= PresContext()->DefaultBackgroundColor();
1172 already_AddRefed
<nsIContent
>
1173 nsListControlFrame::GetOptionAsContent(nsIDOMHTMLOptionsCollection
* aCollection
, PRInt32 aIndex
)
1175 nsIContent
* content
= nsnull
;
1176 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
= GetOption(aCollection
,
1179 NS_ASSERTION(optionElement
!= nsnull
, "could not get option element by index!");
1181 if (optionElement
) {
1182 CallQueryInterface(optionElement
, &content
);
1188 already_AddRefed
<nsIContent
>
1189 nsListControlFrame::GetOptionContent(PRInt32 aIndex
) const
1192 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1193 NS_ASSERTION(options
.get() != nsnull
, "Collection of options is null!");
1196 return GetOptionAsContent(options
, aIndex
);
1201 already_AddRefed
<nsIDOMHTMLOptionsCollection
>
1202 nsListControlFrame::GetOptions(nsIContent
* aContent
)
1204 nsIDOMHTMLOptionsCollection
* options
= nsnull
;
1205 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement
= do_QueryInterface(aContent
);
1206 if (selectElement
) {
1207 selectElement
->GetOptions(&options
); // AddRefs (1)
1213 already_AddRefed
<nsIDOMHTMLOptionElement
>
1214 nsListControlFrame::GetOption(nsIDOMHTMLOptionsCollection
* aCollection
,
1217 nsCOMPtr
<nsIDOMNode
> node
;
1218 if (NS_SUCCEEDED(aCollection
->Item(aIndex
, getter_AddRefs(node
)))) {
1220 "Item was successful, but node from collection was null!");
1222 nsIDOMHTMLOptionElement
* option
= nsnull
;
1223 CallQueryInterface(node
, &option
);
1228 NS_ERROR("Couldn't get option by index from collection!");
1234 nsListControlFrame::IsContentSelected(nsIContent
* aContent
) const
1236 PRBool isSelected
= PR_FALSE
;
1238 nsCOMPtr
<nsIDOMHTMLOptionElement
> optEl
= do_QueryInterface(aContent
);
1240 optEl
->GetSelected(&isSelected
);
1246 nsListControlFrame::IsContentSelectedByIndex(PRInt32 aIndex
) const
1248 nsCOMPtr
<nsIContent
> content
= GetOptionContent(aIndex
);
1249 NS_ASSERTION(content
, "Failed to retrieve option content");
1251 return IsContentSelected(content
);
1255 nsListControlFrame::OnOptionSelected(PRInt32 aIndex
, PRBool aSelected
)
1258 ScrollToIndex(aIndex
);
1264 nsListControlFrame::GetSkipSides() const
1266 // Don't skip any sides during border rendering
1271 nsListControlFrame::OnContentReset()
1277 nsListControlFrame::ResetList(PRBool aAllowScrolling
)
1279 // if all the frames aren't here
1280 // don't bother reseting
1281 if (!mIsAllFramesHere
) {
1285 if (aAllowScrolling
) {
1286 mPostChildrenLoadedReset
= PR_TRUE
;
1288 // Scroll to the selected index
1289 PRInt32 indexToSelect
= kNothingSelected
;
1291 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement(do_QueryInterface(mContent
));
1292 NS_ASSERTION(selectElement
, "No select element!");
1293 if (selectElement
) {
1294 selectElement
->GetSelectedIndex(&indexToSelect
);
1295 ScrollToIndex(indexToSelect
);
1299 mStartSelectionIndex
= kNothingSelected
;
1300 mEndSelectionIndex
= kNothingSelected
;
1302 // Combobox will redisplay itself with the OnOptionSelected event
1306 nsListControlFrame::SetFocus(PRBool aOn
, PRBool aRepaint
)
1320 void nsListControlFrame::ComboboxFocusSet()
1326 nsListControlFrame::SetComboboxFrame(nsIFrame
* aComboboxFrame
)
1328 if (nsnull
!= aComboboxFrame
) {
1329 mComboboxFrame
= do_QueryFrame(aComboboxFrame
);
1334 nsListControlFrame::GetOptionText(PRInt32 aIndex
, nsAString
& aStr
)
1337 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1340 PRUint32 numOptions
;
1341 options
->GetLength(&numOptions
);
1343 if (numOptions
!= 0) {
1344 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
=
1345 GetOption(options
, aIndex
);
1346 if (optionElement
) {
1347 #if 0 // This is for turning off labels Bug 4050
1349 optionElement
->GetLabel(text
);
1350 // the return value is always NS_OK from DOMElements
1351 // it is meaningless to check for it
1352 if (!text
.IsEmpty()) {
1353 nsAutoString compressText
= text
;
1354 compressText
.CompressWhitespace(PR_TRUE
, PR_TRUE
);
1355 if (!compressText
.IsEmpty()) {
1356 text
= compressText
;
1360 if (text
.IsEmpty()) {
1361 // the return value is always NS_OK from DOMElements
1362 // it is meaningless to check for it
1363 optionElement
->GetText(text
);
1367 optionElement
->GetText(aStr
);
1375 nsListControlFrame::GetSelectedIndex()
1379 nsCOMPtr
<nsIDOMHTMLSelectElement
> selectElement(do_QueryInterface(mContent
));
1380 selectElement
->GetSelectedIndex(&aIndex
);
1386 nsListControlFrame::IsInDropDownMode() const
1388 return (mComboboxFrame
!= nsnull
);
1392 nsListControlFrame::GetNumberOfOptions()
1394 if (mContent
!= nsnull
) {
1395 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1400 PRUint32 length
= 0;
1401 options
->GetLength(&length
);
1402 return (PRInt32
)length
;
1408 //----------------------------------------------------------------------
1409 // nsISelectControlFrame
1410 //----------------------------------------------------------------------
1411 PRBool
nsListControlFrame::CheckIfAllFramesHere()
1413 // Get the number of optgroups and options
1414 //PRInt32 numContentItems = 0;
1415 nsCOMPtr
<nsIDOMNode
> node(do_QueryInterface(mContent
));
1417 // XXX Need to find a fail proff way to determine that
1418 // all the frames are there
1419 mIsAllFramesHere
= PR_TRUE
;//NS_OK == CountAllChild(node, numContentItems);
1421 // now make sure we have a frame each piece of content
1423 return mIsAllFramesHere
;
1427 nsListControlFrame::DoneAddingChildren(PRBool aIsDone
)
1429 mIsAllContentHere
= aIsDone
;
1430 if (mIsAllContentHere
) {
1431 // Here we check to see if all the frames have been created
1432 // for all the content.
1433 // If so, then we can initialize;
1434 if (!mIsAllFramesHere
) {
1435 // if all the frames are now present we can initialize
1436 if (CheckIfAllFramesHere()) {
1437 mHasBeenInitialized
= PR_TRUE
;
1446 nsListControlFrame::AddOption(PRInt32 aIndex
)
1448 #ifdef DO_REFLOW_DEBUG
1449 printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId
, this, aIndex
);
1452 if (!mIsAllContentHere
) {
1453 mIsAllContentHere
= mContent
->IsDoneAddingChildren();
1454 if (!mIsAllContentHere
) {
1455 mIsAllFramesHere
= PR_FALSE
;
1456 mHasBeenInitialized
= PR_FALSE
;
1458 mIsAllFramesHere
= (aIndex
== GetNumberOfOptions()-1);
1462 // Make sure we scroll to the selected option as needed
1463 mNeedToReset
= PR_TRUE
;
1465 if (!mHasBeenInitialized
) {
1469 mPostChildrenLoadedReset
= mIsAllContentHere
;
1474 DecrementAndClamp(PRInt32 aSelectionIndex
, PRInt32 aLength
)
1476 return aLength
== 0 ? kNothingSelected
: NS_MAX(0, aSelectionIndex
- 1);
1480 nsListControlFrame::RemoveOption(PRInt32 aIndex
)
1482 NS_PRECONDITION(aIndex
>= 0, "negative <option> index");
1484 // Need to reset if we're a dropdown
1485 if (IsInDropDownMode()) {
1486 mNeedToReset
= PR_TRUE
;
1487 mPostChildrenLoadedReset
= mIsAllContentHere
;
1490 if (mStartSelectionIndex
!= kNothingSelected
) {
1491 NS_ASSERTION(mEndSelectionIndex
!= kNothingSelected
, "");
1492 PRInt32 numOptions
= GetNumberOfOptions();
1493 // NOTE: numOptions is the new number of options whereas aIndex is the
1494 // unadjusted index of the removed option (hence the <= below).
1495 NS_ASSERTION(aIndex
<= numOptions
, "out-of-bounds <option> index");
1497 PRInt32 forward
= mEndSelectionIndex
- mStartSelectionIndex
;
1498 PRInt32
* low
= forward
>= 0 ? &mStartSelectionIndex
: &mEndSelectionIndex
;
1499 PRInt32
* high
= forward
>= 0 ? &mEndSelectionIndex
: &mStartSelectionIndex
;
1501 *low
= ::DecrementAndClamp(*low
, numOptions
);
1502 if (aIndex
<= *high
)
1503 *high
= ::DecrementAndClamp(*high
, numOptions
);
1508 NS_ASSERTION(mEndSelectionIndex
== kNothingSelected
, "");
1514 //---------------------------------------------------------
1515 // Set the option selected in the DOM. This method is named
1516 // as it is because it indicates that the frame is the source
1517 // of this event rather than the receiver.
1519 nsListControlFrame::SetOptionsSelectedFromFrame(PRInt32 aStartIndex
,
1524 nsCOMPtr
<nsISelectElement
> selectElement(do_QueryInterface(mContent
));
1525 PRBool wasChanged
= PR_FALSE
;
1529 selectElement
->SetOptionsSelectedByIndex(aStartIndex
,
1536 NS_ASSERTION(NS_SUCCEEDED(rv
), "SetSelected failed");
1541 nsListControlFrame::ToggleOptionSelectedFromFrame(PRInt32 aIndex
)
1543 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
1544 NS_ASSERTION(options
, "No options");
1548 nsCOMPtr
<nsIDOMHTMLOptionElement
> option
= GetOption(options
, aIndex
);
1549 NS_ASSERTION(option
, "No option");
1554 PRBool value
= PR_FALSE
;
1555 nsresult rv
= option
->GetSelected(&value
);
1557 NS_ASSERTION(NS_SUCCEEDED(rv
), "GetSelected failed");
1558 nsCOMPtr
<nsISelectElement
> selectElement(do_QueryInterface(mContent
));
1559 PRBool wasChanged
= PR_FALSE
;
1560 rv
= selectElement
->SetOptionsSelectedByIndex(aIndex
,
1568 NS_ASSERTION(NS_SUCCEEDED(rv
), "SetSelected failed");
1574 // Dispatch event and such
1576 nsListControlFrame::UpdateSelection()
1578 if (mIsAllFramesHere
) {
1579 // if it's a combobox, display the new text
1580 nsWeakFrame
weakFrame(this);
1581 if (mComboboxFrame
) {
1582 mComboboxFrame
->RedisplaySelectedText();
1584 // if it's a listbox, fire on change
1585 else if (mIsAllContentHere
) {
1588 return weakFrame
.IsAlive();
1594 nsListControlFrame::ComboboxFinish(PRInt32 aIndex
)
1598 if (mComboboxFrame
) {
1599 PerformSelection(aIndex
, PR_FALSE
, PR_FALSE
);
1601 PRInt32 displayIndex
= mComboboxFrame
->GetIndexOfDisplayArea();
1603 nsWeakFrame
weakFrame(this);
1605 if (displayIndex
!= aIndex
) {
1606 mComboboxFrame
->RedisplaySelectedText(); // might destroy us
1609 if (weakFrame
.IsAlive() && mComboboxFrame
) {
1610 mComboboxFrame
->RollupFromList(); // might destroy us
1615 // Send out an onchange notification.
1617 nsListControlFrame::FireOnChange()
1619 if (mComboboxFrame
) {
1620 // Return hit without changing anything
1621 PRInt32 index
= mComboboxFrame
->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX
);
1622 if (index
== NS_SKIP_NOTIFY_INDEX
)
1625 // See if the selection actually changed
1626 if (index
== GetSelectedIndex())
1630 // Dispatch the change event.
1631 nsContentUtils::DispatchTrustedEvent(mContent
->GetOwnerDoc(), mContent
,
1632 NS_LITERAL_STRING("change"), PR_TRUE
,
1637 nsListControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex
, PRInt32 aNewIndex
)
1639 if (mComboboxFrame
) {
1640 // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
1641 // event for this setting of selectedIndex.
1642 mComboboxFrame
->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX
);
1645 ScrollToIndex(aNewIndex
);
1646 mStartSelectionIndex
= aNewIndex
;
1647 mEndSelectionIndex
= aNewIndex
;
1650 #ifdef ACCESSIBILITY
1651 FireMenuItemActiveEvent();
1657 //----------------------------------------------------------------------
1658 // End nsISelectControlFrame
1659 //----------------------------------------------------------------------
1662 nsListControlFrame::SetFormProperty(nsIAtom
* aName
,
1663 const nsAString
& aValue
)
1665 if (nsGkAtoms::selected
== aName
) {
1666 return NS_ERROR_INVALID_ARG
; // Selected is readonly according to spec.
1667 } else if (nsGkAtoms::selectedindex
== aName
) {
1668 // You shouldn't be calling me for this!!!
1669 return NS_ERROR_INVALID_ARG
;
1672 // We should be told about selectedIndex by the DOM element through
1679 nsListControlFrame::GetFormProperty(nsIAtom
* aName
, nsAString
& aValue
) const
1681 // Get the selected value of option from local cache (optimization vs. widget)
1682 if (nsGkAtoms::selected
== aName
) {
1683 nsAutoString
val(aValue
);
1685 PRBool selected
= PR_FALSE
;
1686 PRInt32 indx
= val
.ToInteger(&error
, 10); // Get index from aValue
1688 selected
= IsContentSelectedByIndex(indx
);
1690 aValue
.Assign(selected
? NS_LITERAL_STRING("1") : NS_LITERAL_STRING("0"));
1692 // For selectedIndex, get the value from the widget
1693 } else if (nsGkAtoms::selectedindex
== aName
) {
1694 // You shouldn't be calling me for this!!!
1695 return NS_ERROR_INVALID_ARG
;
1702 nsListControlFrame::SyncViewWithFrame()
1704 // Resync the view's position with the frame.
1705 // The problem is the dropdown's view is attached directly under
1706 // the root view. This means its view needs to have its coordinates calculated
1707 // as if it were in it's normal position in the view hierarchy.
1708 mComboboxFrame
->AbsolutelyPositionDropDown();
1710 nsContainerFrame::PositionFrameView(this);
1714 nsListControlFrame::AboutToDropDown()
1716 NS_ASSERTION(IsInDropDownMode(),
1717 "AboutToDropDown called without being in dropdown mode");
1719 // Our widget doesn't get invalidated on changes to the rest of the document,
1720 // so compute and store this color at the start of a dropdown so we don't
1721 // get weird painting behaviour.
1722 // We start looking for backgrounds above the combobox frame to avoid
1723 // duplicating the combobox frame's background and compose each background
1724 // color we find underneath until we have an opaque color, or run out of
1725 // backgrounds. We compose with the PresContext default background color,
1726 // which is always opaque, in case we don't end up with an opaque color.
1727 // This gives us a very poor approximation of translucency.
1728 nsIFrame
* comboboxFrame
= do_QueryFrame(mComboboxFrame
);
1729 nsStyleContext
* context
= comboboxFrame
->GetStyleContext()->GetParent();
1730 mLastDropdownBackstopColor
= NS_RGBA(0,0,0,0);
1731 while (NS_GET_A(mLastDropdownBackstopColor
) < 255 && context
) {
1732 mLastDropdownBackstopColor
=
1733 NS_ComposeColors(context
->GetStyleBackground()->mBackgroundColor
,
1734 mLastDropdownBackstopColor
);
1735 context
= context
->GetParent();
1737 mLastDropdownBackstopColor
=
1738 NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
1739 mLastDropdownBackstopColor
);
1741 if (mIsAllContentHere
&& mIsAllFramesHere
&& mHasBeenInitialized
) {
1742 ScrollToIndex(GetSelectedIndex());
1743 #ifdef ACCESSIBILITY
1744 FireMenuItemActiveEvent(); // Inform assistive tech what got focus
1747 mItemSelectionStarted
= PR_FALSE
;
1750 // We are about to be rolledup from the outside (ComboboxFrame)
1752 nsListControlFrame::AboutToRollup()
1754 // We've been updating the combobox with the keyboard up until now, but not
1755 // with the mouse. The problem is, even with mouse selection, we are
1756 // updating the <select>. So if the mouse goes over an option just before
1757 // he leaves the box and clicks, that's what the <select> will show.
1759 // To deal with this we say "whatever is in the combobox is canonical."
1760 // - IF the combobox is different from the current selected index, we
1763 if (IsInDropDownMode()) {
1764 ComboboxFinish(mComboboxFrame
->GetIndexOfDisplayArea()); // might destroy us
1769 nsListControlFrame::DidReflow(nsPresContext
* aPresContext
,
1770 const nsHTMLReflowState
* aReflowState
,
1771 nsDidReflowStatus aStatus
)
1774 PRBool wasInterrupted
= !mHasPendingInterruptAtStartOfReflow
&&
1775 aPresContext
->HasPendingInterrupt();
1777 if (IsInDropDownMode())
1779 //SyncViewWithFrame();
1780 rv
= nsHTMLScrollFrame::DidReflow(aPresContext
, aReflowState
, aStatus
);
1781 SyncViewWithFrame();
1783 rv
= nsHTMLScrollFrame::DidReflow(aPresContext
, aReflowState
, aStatus
);
1786 if (mNeedToReset
&& !wasInterrupted
) {
1787 mNeedToReset
= PR_FALSE
;
1788 // Suppress scrolling to the selected element if we restored
1789 // scroll history state AND the list contents have not changed
1790 // since we loaded all the children AND nothing else forced us
1791 // to scroll by calling ResetList(PR_TRUE). The latter two conditions
1792 // are folded into mPostChildrenLoadedReset.
1794 // The idea is that we want scroll history restoration to trump ResetList
1795 // scrolling to the selected element, when the ResetList was probably only
1796 // caused by content loading normally.
1797 ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset
);
1800 mHasPendingInterruptAtStartOfReflow
= PR_FALSE
;
1805 nsListControlFrame::GetType() const
1807 return nsGkAtoms::listControlFrame
;
1811 nsListControlFrame::IsContainingBlock() const
1813 // We are in fact the containing block for our options. They should
1814 // certainly not use our parent block (or worse yet our parent combobox) for
1820 nsListControlFrame::InvalidateInternal(const nsRect
& aDamageRect
,
1821 nscoord aX
, nscoord aY
, nsIFrame
* aForChild
,
1824 if (!IsInDropDownMode()) {
1825 nsHTMLScrollFrame::InvalidateInternal(aDamageRect
, aX
, aY
, this, aFlags
);
1828 InvalidateRoot(aDamageRect
+ nsPoint(aX
, aY
), aFlags
);
1833 nsListControlFrame::GetFrameName(nsAString
& aResult
) const
1835 return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult
);
1840 nsListControlFrame::GetHeightOfARow()
1842 return HeightOfARow();
1846 nsListControlFrame::IsOptionDisabled(PRInt32 anIndex
, PRBool
&aIsDisabled
)
1848 nsCOMPtr
<nsISelectElement
> sel(do_QueryInterface(mContent
));
1850 sel
->IsOptionDisabled(anIndex
, &aIsDisabled
);
1853 return NS_ERROR_FAILURE
;
1856 //----------------------------------------------------------------------
1858 //----------------------------------------------------------------------
1860 nsListControlFrame::IsLeftButton(nsIDOMEvent
* aMouseEvent
)
1862 // only allow selection with the left button
1863 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
1865 PRUint16 whichButton
;
1866 if (NS_SUCCEEDED(mouseEvent
->GetButton(&whichButton
))) {
1867 return whichButton
!= 0?PR_FALSE
:PR_TRUE
;
1874 nsListControlFrame::CalcFallbackRowHeight()
1876 nscoord rowHeight
= 0;
1878 nsCOMPtr
<nsIFontMetrics
> fontMet
;
1879 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet
));
1881 fontMet
->GetHeight(rowHeight
);
1888 nsListControlFrame::CalcIntrinsicHeight(nscoord aHeightOfARow
,
1889 PRInt32 aNumberOfOptions
)
1891 NS_PRECONDITION(!IsInDropDownMode(),
1892 "Shouldn't be in dropdown mode when we call this");
1894 mNumDisplayRows
= 1;
1895 GetSizeAttribute(&mNumDisplayRows
);
1897 if (mNumDisplayRows
< 1) {
1898 mNumDisplayRows
= 4;
1901 return mNumDisplayRows
* aHeightOfARow
;
1904 //----------------------------------------------------------------------
1905 // nsIDOMMouseListener
1906 //----------------------------------------------------------------------
1908 nsListControlFrame::MouseUp(nsIDOMEvent
* aMouseEvent
)
1910 NS_ASSERTION(aMouseEvent
!= nsnull
, "aMouseEvent is null.");
1912 UpdateInListState(aMouseEvent
);
1914 mButtonDown
= PR_FALSE
;
1916 nsEventStates eventStates
= mContent
->IntrinsicState();
1917 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
1921 // only allow selection with the left button
1922 // if a right button click is on the combobox itself
1923 // or on the select when in listbox mode, then let the click through
1924 if (!IsLeftButton(aMouseEvent
)) {
1925 if (IsInDropDownMode()) {
1926 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
1927 aMouseEvent
->PreventDefault();
1928 aMouseEvent
->StopPropagation();
1930 CaptureMouseEvents(PR_FALSE
);
1933 CaptureMouseEvents(PR_FALSE
);
1934 return NS_ERROR_FAILURE
; // means consume event
1936 CaptureMouseEvents(PR_FALSE
);
1941 const nsStyleVisibility
* vis
= GetStyleVisibility();
1943 if (!vis
->IsVisible()) {
1947 if (IsInDropDownMode()) {
1948 // XXX This is a bit of a hack, but.....
1949 // But the idea here is to make sure you get an "onclick" event when you mouse
1950 // down on the select and the drag over an option and let go
1951 // And then NOT get an "onclick" event when when you click down on the select
1952 // and then up outside of the select
1953 // the EventStateManager tracks the content of the mouse down and the mouse up
1954 // to make sure they are the same, and the onclick is sent in the PostHandleEvent
1955 // depeneding on whether the clickCount is non-zero.
1956 // So we cheat here by either setting or unsetting the clcikCount in the native event
1957 // so the right thing happens for the onclick event
1958 nsCOMPtr
<nsIPrivateDOMEvent
> privateEvent(do_QueryInterface(aMouseEvent
));
1959 nsMouseEvent
* mouseEvent
;
1960 mouseEvent
= (nsMouseEvent
*) privateEvent
->GetInternalNSEvent();
1962 PRInt32 selectedIndex
;
1963 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
1964 // If it's disabled, disallow the click and leave.
1965 PRBool isDisabled
= PR_FALSE
;
1966 IsOptionDisabled(selectedIndex
, isDisabled
);
1968 aMouseEvent
->PreventDefault();
1969 aMouseEvent
->StopPropagation();
1970 CaptureMouseEvents(PR_FALSE
);
1971 return NS_ERROR_FAILURE
;
1974 if (kNothingSelected
!= selectedIndex
) {
1975 nsWeakFrame
weakFrame(this);
1976 ComboboxFinish(selectedIndex
);
1977 if (!weakFrame
.IsAlive())
1982 mouseEvent
->clickCount
= 1;
1984 // the click was out side of the select or its dropdown
1985 mouseEvent
->clickCount
= IgnoreMouseEventForSelection(aMouseEvent
) ? 1 : 0;
1988 CaptureMouseEvents(PR_FALSE
);
1990 if (mChangesSinceDragStart
) {
1991 // reset this so that future MouseUps without a prior MouseDown
1992 // won't fire onchange
1993 mChangesSinceDragStart
= PR_FALSE
;
2002 nsListControlFrame::UpdateInListState(nsIDOMEvent
* aEvent
)
2004 if (!mComboboxFrame
|| !mComboboxFrame
->IsDroppedDown())
2007 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent
, this);
2008 nsRect borderInnerEdge
= GetScrollPortRect();
2009 if (pt
.y
>= borderInnerEdge
.y
&& pt
.y
< borderInnerEdge
.YMost()) {
2010 mItemSelectionStarted
= PR_TRUE
;
2014 PRBool
nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent
* aEvent
)
2016 if (!mComboboxFrame
)
2019 // Our DOM listener does get called when the dropdown is not
2020 // showing, because it listens to events on the SELECT element
2021 if (!mComboboxFrame
->IsDroppedDown())
2024 return !mItemSelectionStarted
;
2027 #ifdef ACCESSIBILITY
2029 nsListControlFrame::FireMenuItemActiveEvent()
2031 if (mFocused
!= this && !IsInDropDownMode()) {
2035 // The mEndSelectionIndex is what is currently being selected
2036 // use the selected index if this is kNothingSelected
2037 PRInt32 focusedIndex
;
2038 if (mEndSelectionIndex
== kNothingSelected
) {
2039 focusedIndex
= GetSelectedIndex();
2041 focusedIndex
= mEndSelectionIndex
;
2043 if (focusedIndex
== kNothingSelected
) {
2047 nsCOMPtr
<nsIContent
> optionContent
= GetOptionContent(focusedIndex
);
2048 if (!optionContent
) {
2052 FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent
);
2057 nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent
* aMouseEvent
,
2060 if (IgnoreMouseEventForSelection(aMouseEvent
))
2061 return NS_ERROR_FAILURE
;
2063 if (nsIPresShell::GetCapturingContent() != mContent
) {
2064 // If we're not capturing, then ignore movement in the border
2065 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent
, this);
2066 nsRect borderInnerEdge
= GetScrollPortRect();
2067 if (!borderInnerEdge
.Contains(pt
)) {
2068 return NS_ERROR_FAILURE
;
2072 nsCOMPtr
<nsIContent
> content
;
2073 PresContext()->EventStateManager()->
2074 GetEventTargetContent(nsnull
, getter_AddRefs(content
));
2076 nsCOMPtr
<nsIContent
> optionContent
= GetOptionFromContent(content
);
2077 if (optionContent
) {
2078 aCurIndex
= GetIndexFromContent(optionContent
);
2082 PRInt32 numOptions
= GetNumberOfOptions();
2084 return NS_ERROR_FAILURE
;
2086 nsPoint pt
= nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent
, this);
2088 // If the event coordinate is above the first option frame, then target the
2089 // first option frame
2090 nsCOMPtr
<nsIContent
> firstOption
= GetOptionContent(0);
2091 NS_ASSERTION(firstOption
, "Can't find first option that's supposed to be there");
2092 nsIFrame
* optionFrame
= firstOption
->GetPrimaryFrame();
2094 nsPoint ptInOptionFrame
= pt
- optionFrame
->GetOffsetTo(this);
2095 if (ptInOptionFrame
.y
< 0 && ptInOptionFrame
.x
>= 0 &&
2096 ptInOptionFrame
.x
< optionFrame
->GetSize().width
) {
2102 nsCOMPtr
<nsIContent
> lastOption
= GetOptionContent(numOptions
- 1);
2103 // If the event coordinate is below the last option frame, then target the
2104 // last option frame
2105 NS_ASSERTION(lastOption
, "Can't find last option that's supposed to be there");
2106 optionFrame
= lastOption
->GetPrimaryFrame();
2108 nsPoint ptInOptionFrame
= pt
- optionFrame
->GetOffsetTo(this);
2109 if (ptInOptionFrame
.y
>= optionFrame
->GetSize().height
&& ptInOptionFrame
.x
>= 0 &&
2110 ptInOptionFrame
.x
< optionFrame
->GetSize().width
) {
2111 aCurIndex
= numOptions
- 1;
2116 return NS_ERROR_FAILURE
;
2120 nsListControlFrame::MouseDown(nsIDOMEvent
* aMouseEvent
)
2122 NS_ASSERTION(aMouseEvent
!= nsnull
, "aMouseEvent is null.");
2124 UpdateInListState(aMouseEvent
);
2126 nsEventStates eventStates
= mContent
->IntrinsicState();
2127 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
2131 // only allow selection with the left button
2132 // if a right button click is on the combobox itself
2133 // or on the select when in listbox mode, then let the click through
2134 if (!IsLeftButton(aMouseEvent
)) {
2135 if (IsInDropDownMode()) {
2136 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
2137 aMouseEvent
->PreventDefault();
2138 aMouseEvent
->StopPropagation();
2142 return NS_ERROR_FAILURE
; // means consume event
2148 PRInt32 selectedIndex
;
2149 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2151 mButtonDown
= PR_TRUE
;
2152 CaptureMouseEvents(PR_TRUE
);
2153 mChangesSinceDragStart
= HandleListSelection(aMouseEvent
, selectedIndex
);
2154 #ifdef ACCESSIBILITY
2155 if (mChangesSinceDragStart
) {
2156 FireMenuItemActiveEvent();
2160 // NOTE: the combo box is responsible for dropping it down
2161 if (mComboboxFrame
) {
2162 if (!IgnoreMouseEventForSelection(aMouseEvent
)) {
2166 if (!nsComboboxControlFrame::ToolkitHasNativePopup())
2168 PRBool isDroppedDown
= mComboboxFrame
->IsDroppedDown();
2169 nsIFrame
* comboFrame
= do_QueryFrame(mComboboxFrame
);
2170 nsWeakFrame
weakFrame(comboFrame
);
2171 mComboboxFrame
->ShowDropDown(!isDroppedDown
);
2172 if (!weakFrame
.IsAlive())
2174 if (isDroppedDown
) {
2175 CaptureMouseEvents(PR_FALSE
);
2184 //----------------------------------------------------------------------
2185 // nsIDOMMouseMotionListener
2186 //----------------------------------------------------------------------
2188 nsListControlFrame::MouseMove(nsIDOMEvent
* aMouseEvent
)
2190 NS_ASSERTION(aMouseEvent
, "aMouseEvent is null.");
2192 UpdateInListState(aMouseEvent
);
2194 if (IsInDropDownMode()) {
2195 if (mComboboxFrame
->IsDroppedDown()) {
2196 PRInt32 selectedIndex
;
2197 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2198 PerformSelection(selectedIndex
, PR_FALSE
, PR_FALSE
);
2201 } else {// XXX - temporary until we get drag events
2203 return DragMove(aMouseEvent
);
2210 nsListControlFrame::DragMove(nsIDOMEvent
* aMouseEvent
)
2212 NS_ASSERTION(aMouseEvent
, "aMouseEvent is null.");
2214 UpdateInListState(aMouseEvent
);
2216 if (!IsInDropDownMode()) {
2217 PRInt32 selectedIndex
;
2218 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent
, selectedIndex
))) {
2219 // Don't waste cycles if we already dragged over this item
2220 if (selectedIndex
== mEndSelectionIndex
) {
2223 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
2224 NS_ASSERTION(mouseEvent
, "aMouseEvent is not an nsIDOMMouseEvent!");
2227 mouseEvent
->GetMetaKey(&isControl
);
2229 mouseEvent
->GetCtrlKey(&isControl
);
2231 // Turn SHIFT on when you are dragging, unless control is on.
2232 PRBool wasChanged
= PerformSelection(selectedIndex
,
2233 !isControl
, isControl
);
2234 mChangesSinceDragStart
= mChangesSinceDragStart
|| wasChanged
;
2240 //----------------------------------------------------------------------
2242 //----------------------------------------------------------------------
2244 nsListControlFrame::ScrollToIndex(PRInt32 aIndex
)
2247 // XXX shouldn't we just do nothing if we're asked to scroll to
2248 // kNothingSelected?
2249 return ScrollToFrame(nsnull
);
2251 nsCOMPtr
<nsIContent
> content
= GetOptionContent(aIndex
);
2253 return ScrollToFrame(content
);
2257 return NS_ERROR_FAILURE
;
2261 nsListControlFrame::ScrollToFrame(nsIContent
* aOptElement
)
2263 // if null is passed in we scroll to 0,0
2264 if (nsnull
== aOptElement
) {
2265 ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT
);
2269 // otherwise we find the content's frame and scroll to it
2270 nsIFrame
*childFrame
= aOptElement
->GetPrimaryFrame();
2272 nsPoint pt
= GetScrollPosition();
2273 // get the scroll port rect relative to the scrolled frame
2274 nsRect rect
= GetScrollPortRect() + pt
;
2275 // get the option's rect relative to the scrolled frame
2276 nsRect
fRect(childFrame
->GetOffsetTo(GetScrolledFrame()),
2277 childFrame
->GetSize());
2279 // See if the selected frame (fRect) is inside the scrollport
2280 // area (rect). Check only the vertical dimension. Don't
2281 // scroll just because there's horizontal overflow.
2282 if (!(rect
.y
<= fRect
.y
&& fRect
.YMost() <= rect
.YMost())) {
2283 // figure out which direction we are going
2284 if (fRect
.YMost() > rect
.YMost()) {
2285 pt
.y
= fRect
.y
- (rect
.height
- fRect
.height
);
2289 ScrollTo(nsPoint(fRect
.x
, pt
.y
), nsIScrollableFrame::INSTANT
);
2295 //---------------------------------------------------------------------
2296 // Ok, the entire idea of this routine is to move to the next item that
2297 // is suppose to be selected. If the item is disabled then we search in
2298 // the same direction looking for the next item to select. If we run off
2299 // the end of the list then we start at the end of the list and search
2300 // backwards until we get back to the original item or an enabled option
2302 // aStartIndex - the index to start searching from
2303 // aNewIndex - will get set to the new index if it finds one
2304 // aNumOptions - the total number of options in the list
2305 // aDoAdjustInc - the initial increment 1-n
2306 // aDoAdjustIncNext - the increment used to search for the next enabled option
2308 // the aDoAdjustInc could be a "1" for a single item or
2309 // any number greater representing a page of items
2312 nsListControlFrame::AdjustIndexForDisabledOpt(PRInt32 aStartIndex
,
2314 PRInt32 aNumOptions
,
2315 PRInt32 aDoAdjustInc
,
2316 PRInt32 aDoAdjustIncNext
)
2318 // Cannot select anything if there is nothing to select
2319 if (aNumOptions
== 0) {
2320 aNewIndex
= kNothingSelected
;
2324 // means we reached the end of the list and now we are searching backwards
2325 PRBool doingReverse
= PR_FALSE
;
2326 // lowest index in the search range
2328 // highest index in the search range
2329 PRInt32 top
= aNumOptions
;
2331 // Start off keyboard options at selectedIndex if nothing else is defaulted to
2333 // XXX Perhaps this should happen for mouse too, to start off shift click
2334 // automatically in multiple ... to do this, we'd need to override
2335 // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
2336 // sure of the effects, though, so I'm not doing it just yet.
2337 PRInt32 startIndex
= aStartIndex
;
2338 if (startIndex
< bottom
) {
2339 startIndex
= GetSelectedIndex();
2341 PRInt32 newIndex
= startIndex
+ aDoAdjustInc
;
2343 // make sure we start off in the range
2344 if (newIndex
< bottom
) {
2346 } else if (newIndex
>= top
) {
2347 newIndex
= aNumOptions
-1;
2351 // if the newIndex isn't disabled, we are golden, bail out
2352 PRBool isDisabled
= PR_TRUE
;
2353 if (NS_SUCCEEDED(IsOptionDisabled(newIndex
, isDisabled
)) && !isDisabled
) {
2357 // it WAS disabled, so sart looking ahead for the next enabled option
2358 newIndex
+= aDoAdjustIncNext
;
2360 // well, if we reach end reverse the search
2361 if (newIndex
< bottom
) {
2363 return; // if we are in reverse mode and reach the end bail out
2365 // reset the newIndex to the end of the list we hit
2366 // reverse the incrementer
2367 // set the other end of the list to our original starting index
2369 aDoAdjustIncNext
= 1;
2370 doingReverse
= PR_TRUE
;
2373 } else if (newIndex
>= top
) {
2375 return; // if we are in reverse mode and reach the end bail out
2377 // reset the newIndex to the end of the list we hit
2378 // reverse the incrementer
2379 // set the other end of the list to our original starting index
2381 aDoAdjustIncNext
= -1;
2382 doingReverse
= PR_TRUE
;
2383 bottom
= startIndex
;
2388 // Looks like we found one
2389 aNewIndex
= newIndex
;
2393 nsListControlFrame::GetIncrementalString()
2395 if (sIncrementalString
== nsnull
)
2396 sIncrementalString
= new nsString();
2398 return *sIncrementalString
;
2402 nsListControlFrame::Shutdown()
2404 delete sIncrementalString
;
2405 sIncrementalString
= nsnull
;
2409 nsListControlFrame::DropDownToggleKey(nsIDOMEvent
* aKeyEvent
)
2411 // Cocoa widgets do native popups, so don't try to show
2413 if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
2414 aKeyEvent
->PreventDefault();
2415 if (!mComboboxFrame
->IsDroppedDown()) {
2416 mComboboxFrame
->ShowDropDown(PR_TRUE
);
2418 nsWeakFrame
weakFrame(this);
2419 // mEndSelectionIndex is the last item that got selected.
2420 ComboboxFinish(mEndSelectionIndex
);
2421 if (weakFrame
.IsAlive()) {
2429 nsListControlFrame::KeyPress(nsIDOMEvent
* aKeyEvent
)
2431 NS_ASSERTION(aKeyEvent
, "keyEvent is null.");
2433 nsEventStates eventStates
= mContent
->IntrinsicState();
2434 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
))
2437 // Start by making sure we can query for a key event
2438 nsCOMPtr
<nsIDOMKeyEvent
> keyEvent
= do_QueryInterface(aKeyEvent
);
2439 NS_ENSURE_TRUE(keyEvent
, NS_ERROR_FAILURE
);
2441 PRUint32 keycode
= 0;
2442 PRUint32 charcode
= 0;
2443 keyEvent
->GetKeyCode(&keycode
);
2444 keyEvent
->GetCharCode(&charcode
);
2446 PRBool isAlt
= PR_FALSE
;
2448 keyEvent
->GetAltKey(&isAlt
);
2450 if (keycode
== nsIDOMKeyEvent::DOM_VK_UP
|| keycode
== nsIDOMKeyEvent::DOM_VK_DOWN
) {
2451 DropDownToggleKey(aKeyEvent
);
2456 // Get control / shift modifiers
2457 PRBool isControl
= PR_FALSE
;
2458 PRBool isShift
= PR_FALSE
;
2459 keyEvent
->GetCtrlKey(&isControl
);
2461 keyEvent
->GetMetaKey(&isControl
);
2463 keyEvent
->GetShiftKey(&isShift
);
2465 // now make sure there are options or we are wasting our time
2466 nsCOMPtr
<nsIDOMHTMLOptionsCollection
> options
= GetOptions(mContent
);
2467 NS_ENSURE_TRUE(options
, NS_ERROR_FAILURE
);
2469 PRUint32 numOptions
= 0;
2470 options
->GetLength(&numOptions
);
2472 // Whether we did an incremental search or another action
2473 PRBool didIncrementalSearch
= PR_FALSE
;
2475 // this is the new index to set
2476 // DOM_VK_RETURN & DOM_VK_ESCAPE will not set this
2477 PRInt32 newIndex
= kNothingSelected
;
2479 // set up the old and new selected index and process it
2480 // DOM_VK_RETURN selects the item
2481 // DOM_VK_ESCAPE cancels the selection
2482 // default processing checks to see if the pressed the first
2483 // letter of an item in the list and advances to it
2485 if (isControl
&& (keycode
== nsIDOMKeyEvent::DOM_VK_UP
||
2486 keycode
== nsIDOMKeyEvent::DOM_VK_LEFT
||
2487 keycode
== nsIDOMKeyEvent::DOM_VK_DOWN
||
2488 keycode
== nsIDOMKeyEvent::DOM_VK_RIGHT
)) {
2489 // Don't go into multiple select mode unless this list can handle it
2490 isControl
= mControlSelectMode
= GetMultiple();
2491 } else if (charcode
!= ' ') {
2492 mControlSelectMode
= PR_FALSE
;
2496 case nsIDOMKeyEvent::DOM_VK_UP
:
2497 case nsIDOMKeyEvent::DOM_VK_LEFT
: {
2498 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2499 (PRInt32
)numOptions
,
2503 case nsIDOMKeyEvent::DOM_VK_DOWN
:
2504 case nsIDOMKeyEvent::DOM_VK_RIGHT
: {
2505 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2506 (PRInt32
)numOptions
,
2510 case nsIDOMKeyEvent::DOM_VK_RETURN
: {
2511 if (mComboboxFrame
!= nsnull
) {
2512 if (mComboboxFrame
->IsDroppedDown()) {
2513 nsWeakFrame
weakFrame(this);
2514 ComboboxFinish(mEndSelectionIndex
);
2515 if (!weakFrame
.IsAlive())
2521 newIndex
= mEndSelectionIndex
;
2525 case nsIDOMKeyEvent::DOM_VK_ESCAPE
: {
2526 nsWeakFrame
weakFrame(this);
2528 if (!weakFrame
.IsAlive()) {
2529 aKeyEvent
->PreventDefault(); // since we won't reach the one below
2534 case nsIDOMKeyEvent::DOM_VK_PAGE_UP
: {
2535 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2536 (PRInt32
)numOptions
,
2537 -NS_MAX(1, mNumDisplayRows
-1), -1);
2540 case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN
: {
2541 AdjustIndexForDisabledOpt(mEndSelectionIndex
, newIndex
,
2542 (PRInt32
)numOptions
,
2543 NS_MAX(1, mNumDisplayRows
-1), 1);
2546 case nsIDOMKeyEvent::DOM_VK_HOME
: {
2547 AdjustIndexForDisabledOpt(0, newIndex
,
2548 (PRInt32
)numOptions
,
2552 case nsIDOMKeyEvent::DOM_VK_END
: {
2553 AdjustIndexForDisabledOpt(numOptions
-1, newIndex
,
2554 (PRInt32
)numOptions
,
2558 #if defined(XP_WIN) || defined(XP_OS2)
2559 case nsIDOMKeyEvent::DOM_VK_F4
: {
2560 DropDownToggleKey(aKeyEvent
);
2565 case nsIDOMKeyEvent::DOM_VK_TAB
: {
2569 default: { // Select option with this as the first character
2570 // XXX Not I18N compliant
2572 if (isControl
&& charcode
!= ' ') {
2576 didIncrementalSearch
= PR_TRUE
;
2577 if (charcode
== 0) {
2578 // Backspace key will delete the last char in the string
2579 if (keycode
== NS_VK_BACK
&& !GetIncrementalString().IsEmpty()) {
2580 GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
2581 aKeyEvent
->PreventDefault();
2586 DOMTimeStamp keyTime
;
2587 aKeyEvent
->GetTimeStamp(&keyTime
);
2589 // Incremental Search: if time elapsed is below
2590 // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
2591 // string we will use to find options and start searching at the current
2592 // keystroke. Otherwise, Truncate the string if it's been a long time
2593 // since our last keypress.
2594 if (keyTime
- gLastKeyTime
> INCREMENTAL_SEARCH_KEYPRESS_TIME
) {
2595 // If this is ' ' and we are at the beginning of the string, treat it as
2596 // "select this option" (bug 191543)
2597 if (charcode
== ' ') {
2598 newIndex
= mEndSelectionIndex
;
2601 GetIncrementalString().Truncate();
2603 gLastKeyTime
= keyTime
;
2605 // Append this keystroke to the search string.
2606 PRUnichar uniChar
= ToLowerCase(static_cast<PRUnichar
>(charcode
));
2607 GetIncrementalString().Append(uniChar
);
2609 // See bug 188199, if all letters in incremental string are same, just try to match the first one
2610 nsAutoString
incrementalString(GetIncrementalString());
2611 PRUint32 charIndex
= 1, stringLength
= incrementalString
.Length();
2612 while (charIndex
< stringLength
&& incrementalString
[charIndex
] == incrementalString
[charIndex
- 1]) {
2615 if (charIndex
== stringLength
) {
2616 incrementalString
.Truncate(1);
2620 // Determine where we're going to start reading the string
2621 // If we have multiple characters to look for, we start looking *at* the
2622 // current option. If we have only one character to look for, we start
2623 // looking *after* the current option.
2624 // Exception: if there is no option selected to start at, we always start
2626 PRInt32 startIndex
= GetSelectedIndex();
2627 if (startIndex
== kNothingSelected
) {
2629 } else if (stringLength
== 1) {
2634 for (i
= 0; i
< numOptions
; i
++) {
2635 PRUint32 index
= (i
+ startIndex
) % numOptions
;
2636 nsCOMPtr
<nsIDOMHTMLOptionElement
> optionElement
=
2637 GetOption(options
, index
);
2638 if (optionElement
) {
2640 if (NS_OK
== optionElement
->GetText(text
)) {
2641 if (StringBeginsWith(text
, incrementalString
,
2642 nsCaseInsensitiveStringComparator())) {
2643 PRBool wasChanged
= PerformSelection(index
, isShift
, isControl
);
2645 // dispatch event, update combobox, etc.
2646 if (!UpdateSelection()) {
2649 #ifdef ACCESSIBILITY
2650 FireMenuItemActiveEvent(); // Only fire if new item reached
2662 // We ate the key if we got this far.
2663 aKeyEvent
->PreventDefault();
2665 // If we didn't do an incremental search, clear the string
2666 if (!didIncrementalSearch
) {
2667 GetIncrementalString().Truncate();
2670 // Actually process the new index and let the selection code
2671 // do the scrolling for us
2672 if (newIndex
!= kNothingSelected
) {
2673 // If you hold control, no key will actually do anything except space.
2674 PRBool wasChanged
= PR_FALSE
;
2675 if (isControl
&& charcode
!= ' ') {
2676 mStartSelectionIndex
= newIndex
;
2677 mEndSelectionIndex
= newIndex
;
2679 ScrollToIndex(newIndex
);
2680 } else if (mControlSelectMode
&& charcode
== ' ') {
2681 wasChanged
= SingleSelection(newIndex
, PR_TRUE
);
2683 wasChanged
= PerformSelection(newIndex
, isShift
, isControl
);
2686 // dispatch event, update combobox, etc.
2687 if (!UpdateSelection()) {
2691 #ifdef ACCESSIBILITY
2692 if (charcode
!= ' ') {
2693 FireMenuItemActiveEvent();
2702 /******************************************************************************
2703 * nsListEventListener
2704 *****************************************************************************/
2706 NS_IMPL_ADDREF(nsListEventListener
)
2707 NS_IMPL_RELEASE(nsListEventListener
)
2708 NS_INTERFACE_MAP_BEGIN(nsListEventListener
)
2709 NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener
)
2710 NS_INTERFACE_MAP_ENTRY(nsIDOMMouseMotionListener
)
2711 NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener
)
2712 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener
, nsIDOMMouseListener
)
2713 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIDOMMouseListener
)
2714 NS_INTERFACE_MAP_END
2716 #define FORWARD_EVENT(_event) \
2718 nsListEventListener::_event(nsIDOMEvent* aEvent) \
2721 return mFrame->nsListControlFrame::_event(aEvent); \
2725 #define IGNORE_EVENT(_event) \
2727 nsListEventListener::_event(nsIDOMEvent* aEvent) \
2730 IGNORE_EVENT(HandleEvent
)
2732 /*================== nsIDOMKeyListener =========================*/
2734 IGNORE_EVENT(KeyDown
)
2736 FORWARD_EVENT(KeyPress
)
2738 /*=============== nsIDOMMouseListener ======================*/
2740 FORWARD_EVENT(MouseDown
)
2741 FORWARD_EVENT(MouseUp
)
2742 IGNORE_EVENT(MouseClick
)
2743 IGNORE_EVENT(MouseDblClick
)
2744 IGNORE_EVENT(MouseOver
)
2745 IGNORE_EVENT(MouseOut
)
2747 /*=============== nsIDOMMouseMotionListener ======================*/
2749 FORWARD_EVENT(MouseMove
)
2750 // XXXbryner does anyone call this, ever?
2751 IGNORE_EVENT(DragMove
)
2753 #undef FORWARD_EVENT