CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / layout / forms / nsListControlFrame.cpp
blob4962582e0d781a96211977fd27fb7299e8e56d85
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla 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.
22 * Contributor(s):
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 ***** */
40 #include "nscore.h"
41 #include "nsCOMPtr.h"
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"
68 #include "nsXPCOM.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"
79 #ifdef ACCESSIBILITY
80 #include "nsAccessibilityService.h"
81 #endif
82 #include "nsISelectElement.h"
83 #include "nsIPrivateDOMEvent.h"
84 #include "nsCSSRendering.h"
85 #include "nsITheme.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"
93 // Constants
94 const nscoord kMaxDropDownRows = 20; // This matches the setting for 4.x browsers
95 const PRInt32 kNothingSelected = -1;
97 // Static members
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
120 public:
121 nsListEventListener(nsListControlFrame *aFrame)
122 : mFrame(aFrame) { }
124 void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
126 NS_DECL_ISUPPORTS
128 // nsIDOMEventListener
129 NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
131 // nsIDOMKeyListener
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);
148 private:
149 nsListControlFrame *mFrame;
152 //---------------------------------------------------------
153 nsIFrame*
154 NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
156 nsListControlFrame* it =
157 new (aPresShell) nsListControlFrame(aPresShell, aPresShell->GetDocument(), aContext);
159 if (it) {
160 it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
163 return it;
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)
196 void
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*>
208 (mEventListener),
209 NS_GET_IID(nsIDOMMouseListener));
211 mContent->RemoveEventListenerByIID(static_cast<nsIDOMMouseMotionListener*>
212 (mEventListener),
213 NS_GET_IID(nsIDOMMouseMotionListener));
215 mContent->RemoveEventListenerByIID(static_cast<nsIDOMKeyListener*>
216 (mEventListener),
217 NS_GET_IID(nsIDOMKeyListener));
219 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), PR_FALSE);
220 nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
223 NS_IMETHODIMP
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())
234 return NS_OK;
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
261 * scrolled by us.
262 * @param aPt the offset of this frame, relative to the rendering reference
263 * frame
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();
274 } else {
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();
301 } else {
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;
309 PRUint32 length;
310 selectHTMLElement->GetLength(&length);
311 if (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) {
316 break;
318 if (NS_FAILED(selectElement->IsOptionDisabled(i, &isDisabled))) {
319 break;
321 if (isDisabled) {
322 node = nsnull;
323 } else {
324 break;
327 if (!node) {
328 return;
332 // if we found a node use it, if not get the first child (this is for empty selects)
333 if (node) {
334 focusedContent = do_QueryInterface(node);
335 childframe = focusedContent->GetPrimaryFrame();
337 if (!childframe) {
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()) {
342 childframe = nsnull;
344 result = NS_OK;
348 nsRect fRect;
349 if (childframe) {
350 // get the child rect
351 fRect = childframe->GetRect();
352 // get it into our coordinates
353 fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
354 } else {
355 fRect.x = fRect.y = 0;
356 fRect.width = GetScrollPortRect().width;
357 fRect.height = CalcFallbackRowHeight();
358 fRect.MoveBy(containerFrame->GetOffsetTo(this));
360 fRect += aPt;
362 PRBool lastItemIsSelected = PR_FALSE;
363 if (focusedContent) {
364 nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
365 do_QueryInterface(focusedContent);
366 if (domOpt) {
367 domOpt->GetSelected(&lastItemIsSelected);
371 // set up back stop colors and then ask L&F service for the real colors
372 nscolor color;
373 presContext->LookAndFeel()->
374 GetColor(lastItemIsSelected ?
375 nsILookAndFeel::eColor_WidgetSelectForeground :
376 nsILookAndFeel::eColor_WidgetSelectBackground, color);
378 nsCSSRendering::PaintFocus(presContext, aRC, fRect, color);
381 void
382 nsListControlFrame::InvalidateFocus()
384 if (mFocused != this)
385 return;
387 nsIFrame* containerFrame = GetOptionsContainer();
388 if (containerFrame) {
389 // Invalidating from the containerFrame because that's where our focus
390 // is drawn.
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)
405 #ifdef ACCESSIBILITY
406 already_AddRefed<nsAccessible>
407 nsListControlFrame::CreateAccessible()
409 nsAccessibilityService* accService = nsIPresShell::AccService();
410 if (accService) {
411 return accService->CreateHTMLListboxAccessible(mContent,
412 PresContext()->PresShell());
415 return nsnull;
417 #endif
419 static nscoord
420 GetMaxOptionHeight(nsIFrame* aContainer)
422 nscoord result = 0;
423 for (nsIFrame* option = aContainer->GetFirstChild(nsnull);
424 option; option = option->GetNextSibling()) {
425 nscoord optionHeight;
426 if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
427 (do_QueryInterface(option->GetContent()))) {
428 // an optgroup
429 optionHeight = GetMaxOptionHeight(option);
430 } else {
431 // an option
432 optionHeight = option->GetSize().height;
434 if (result < optionHeight)
435 result = optionHeight;
437 return result;
440 static inline PRBool
441 IsOptGroup(nsIContent *aContent)
443 return (aContent->NodeInfo()->Equals(nsGkAtoms::optgroup) &&
444 aContent->IsHTML());
447 static inline PRBool
448 IsOption(nsIContent *aContent)
450 return (aContent->NodeInfo()->Equals(nsGkAtoms::option) &&
451 aContent->IsHTML());
454 static PRUint32
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)) {
462 ++optionCount;
464 else if (::IsOptGroup(child)) {
465 optionCount += ::GetNumberOfOptionsRecursive(child);
468 return optionCount;
471 //-----------------------------------------------------------------
472 // Main Reflow for ListBox/Dropdown
473 //-----------------------------------------------------------------
475 nscoord
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();
490 return heightOfARow;
493 nscoord
494 nsListControlFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
496 nscoord result;
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());
506 return result;
509 nscoord
510 nsListControlFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
512 nscoord result;
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();
521 return result;
524 NS_IMETHODIMP
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
565 * height.
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
568 * change.
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,
592 state, aStatus);
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 "
601 "auto height?");
602 NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
603 "Shouldn't be suppressing if we don't need a second pass!");
604 if (!autoHeight) {
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
613 mNumDisplayRows = 1;
614 } else {
615 mNumDisplayRows = NS_MAX(1, state.ComputedHeight() / rowHeight);
619 return rv;
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 "
630 "changed!");
631 return rv;
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);
656 nsresult
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();
668 #ifdef DEBUG
669 nscoord oldHeightOfARow = HeightOfARow();
670 #endif
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;
683 } else {
684 // Set oldVisibleHeight to something that will never test true against a
685 // real height.
686 oldVisibleHeight = NS_UNCONSTRAINEDSIZE;
689 nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize,
690 state, aStatus);
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?");
702 return rv;
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?");
713 return rv;
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;
754 } else {
755 // Hmmm, not sure what to do here. Punt, and make both of them one
756 visibleHeight = 1;
757 mNumDisplayRows = 1;
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);
767 } else {
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,
789 verticalStyle);
792 PRBool
793 nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const
795 return !IsInDropDownMode();
798 //---------------------------------------------------------
799 PRBool
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) {
807 result = PR_TRUE;
811 return result;
814 nsIFrame*
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 //---------------------------------------------------------
823 nsIContent *
824 nsListControlFrame::GetOptionFromContent(nsIContent *aContent)
826 for (nsIContent* content = aContent; content; content = content->GetParent()) {
827 if (IsOptionElement(content)) {
828 return content;
832 return nsnull;
835 //---------------------------------------------------------
836 // Finds the index of the hit frame's content in the list
837 // of option elements
838 //---------------------------------------------------------
839 PRInt32
840 nsListControlFrame::GetIndexFromContent(nsIContent *aContent)
842 nsCOMPtr<nsIDOMHTMLOptionElement> option;
843 option = do_QueryInterface(aContent);
844 if (option) {
845 PRInt32 retval;
846 option->GetIndex(&retval);
847 if (retval >= 0) {
848 return retval;
851 return kNothingSelected;
854 //---------------------------------------------------------
855 PRBool
856 nsListControlFrame::ExtendedSelection(PRInt32 aStartIndex,
857 PRInt32 aEndIndex,
858 PRBool aClearAll)
860 return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
861 PR_TRUE, aClearAll);
864 //---------------------------------------------------------
865 PRBool
866 nsListControlFrame::SingleSelection(PRInt32 aClickedIndex, PRBool aDoToggle)
868 if (mComboboxFrame) {
869 mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
872 PRBool wasChanged = PR_FALSE;
873 // Get Current selection
874 if (aDoToggle) {
875 wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
876 } else {
877 wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
878 PR_TRUE, PR_TRUE);
880 ScrollToIndex(aClickedIndex);
881 mStartSelectionIndex = aClickedIndex;
882 mEndSelectionIndex = aClickedIndex;
883 InvalidateFocus();
884 return wasChanged;
887 void
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!");
910 PRUint32 numOptions;
911 options->GetLength(&numOptions);
912 PRUint32 i;
913 // Push i to one past the last selected index in the group
914 for (i=selectedIndex+1; i < numOptions; i++) {
915 PRBool selected;
916 nsCOMPtr<nsIDOMHTMLOptionElement> option = GetOption(options, i);
917 option->GetSelected(&selected);
918 if (!selected) {
919 break;
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;
928 } else {
929 // User clicked after selection, so start selection at start of
930 // contiguous selection
931 mStartSelectionIndex = selectedIndex;
932 mEndSelectionIndex = i-1;
937 //---------------------------------------------------------
938 PRBool
939 nsListControlFrame::PerformSelection(PRInt32 aClickedIndex,
940 PRBool aIsShift,
941 PRBool aIsControl)
943 PRBool wasChanged = PR_FALSE;
945 if (aClickedIndex == kNothingSelected) {
947 else if (GetMultiple()) {
948 if (aIsShift) {
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
957 PRInt32 startIndex;
958 PRInt32 endIndex;
959 if (mStartSelectionIndex == kNothingSelected) {
960 startIndex = aClickedIndex;
961 endIndex = aClickedIndex;
962 } else if (mStartSelectionIndex <= aClickedIndex) {
963 startIndex = mStartSelectionIndex;
964 endIndex = aClickedIndex;
965 } else {
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;
977 } else {
978 mEndSelectionIndex = aClickedIndex;
980 InvalidateFocus();
981 } else if (aIsControl) {
982 wasChanged = SingleSelection(aClickedIndex, PR_TRUE);
983 } else {
984 wasChanged = SingleSelection(aClickedIndex, PR_FALSE);
986 } else {
987 wasChanged = SingleSelection(aClickedIndex, PR_FALSE);
990 return wasChanged;
993 //---------------------------------------------------------
994 PRBool
995 nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
996 PRInt32 aClickedIndex)
998 nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
999 PRBool isShift;
1000 PRBool isControl;
1001 #ifdef XP_MACOSX
1002 mouseEvent->GetMetaKey(&isControl);
1003 #else
1004 mouseEvent->GetCtrlKey(&isControl);
1005 #endif
1006 mouseEvent->GetShiftKey(&isShift);
1007 return PerformSelection(aClickedIndex, isShift, isControl);
1010 //---------------------------------------------------------
1011 void
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())
1020 return;
1022 if (aGrabMouseEvents) {
1023 nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
1024 } else {
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 //---------------------------------------------------------
1044 NS_IMETHODIMP
1045 nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
1046 nsGUIEvent* aEvent,
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);
1071 } else {
1072 printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
1075 if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
1076 return NS_OK;
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))
1086 return NS_OK;
1088 return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1092 //---------------------------------------------------------
1093 NS_IMETHODIMP
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;
1116 return rv;
1119 //---------------------------------------------------------
1120 nsresult
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);
1129 return rv;
1133 //---------------------------------------------------------
1134 NS_IMETHODIMP
1135 nsListControlFrame::Init(nsIContent* aContent,
1136 nsIFrame* aParent,
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*>
1153 (mEventListener),
1154 NS_GET_IID(nsIDOMMouseListener));
1156 mContent->AddEventListenerByIID(static_cast<nsIDOMMouseMotionListener*>
1157 (mEventListener),
1158 NS_GET_IID(nsIDOMMouseMotionListener));
1160 mContent->AddEventListenerByIID(static_cast<nsIDOMKeyListener*>
1161 (mEventListener),
1162 NS_GET_IID(nsIDOMKeyListener));
1164 mStartSelectionIndex = kNothingSelected;
1165 mEndSelectionIndex = kNothingSelected;
1167 mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
1169 return result;
1172 already_AddRefed<nsIContent>
1173 nsListControlFrame::GetOptionAsContent(nsIDOMHTMLOptionsCollection* aCollection, PRInt32 aIndex)
1175 nsIContent * content = nsnull;
1176 nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = GetOption(aCollection,
1177 aIndex);
1179 NS_ASSERTION(optionElement != nsnull, "could not get option element by index!");
1181 if (optionElement) {
1182 CallQueryInterface(optionElement, &content);
1185 return 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!");
1195 if (options) {
1196 return GetOptionAsContent(options, aIndex);
1198 return nsnull;
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)
1210 return options;
1213 already_AddRefed<nsIDOMHTMLOptionElement>
1214 nsListControlFrame::GetOption(nsIDOMHTMLOptionsCollection* aCollection,
1215 PRInt32 aIndex)
1217 nsCOMPtr<nsIDOMNode> node;
1218 if (NS_SUCCEEDED(aCollection->Item(aIndex, getter_AddRefs(node)))) {
1219 NS_ASSERTION(node,
1220 "Item was successful, but node from collection was null!");
1221 if (node) {
1222 nsIDOMHTMLOptionElement* option = nsnull;
1223 CallQueryInterface(node, &option);
1225 return option;
1227 } else {
1228 NS_ERROR("Couldn't get option by index from collection!");
1230 return nsnull;
1233 PRBool
1234 nsListControlFrame::IsContentSelected(nsIContent* aContent) const
1236 PRBool isSelected = PR_FALSE;
1238 nsCOMPtr<nsIDOMHTMLOptionElement> optEl = do_QueryInterface(aContent);
1239 if (optEl)
1240 optEl->GetSelected(&isSelected);
1242 return isSelected;
1245 PRBool
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);
1254 NS_IMETHODIMP
1255 nsListControlFrame::OnOptionSelected(PRInt32 aIndex, PRBool aSelected)
1257 if (aSelected) {
1258 ScrollToIndex(aIndex);
1260 return NS_OK;
1263 PRIntn
1264 nsListControlFrame::GetSkipSides() const
1266 // Don't skip any sides during border rendering
1267 return 0;
1270 void
1271 nsListControlFrame::OnContentReset()
1273 ResetList(PR_TRUE);
1276 void
1277 nsListControlFrame::ResetList(PRBool aAllowScrolling)
1279 // if all the frames aren't here
1280 // don't bother reseting
1281 if (!mIsAllFramesHere) {
1282 return;
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;
1301 InvalidateFocus();
1302 // Combobox will redisplay itself with the OnOptionSelected event
1305 void
1306 nsListControlFrame::SetFocus(PRBool aOn, PRBool aRepaint)
1308 InvalidateFocus();
1310 if (aOn) {
1311 ComboboxFocusSet();
1312 mFocused = this;
1313 } else {
1314 mFocused = nsnull;
1317 InvalidateFocus();
1320 void nsListControlFrame::ComboboxFocusSet()
1322 gLastKeyTime = 0;
1325 void
1326 nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
1328 if (nsnull != aComboboxFrame) {
1329 mComboboxFrame = do_QueryFrame(aComboboxFrame);
1333 void
1334 nsListControlFrame::GetOptionText(PRInt32 aIndex, nsAString & aStr)
1336 aStr.SetLength(0);
1337 nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1339 if (options) {
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
1348 nsAutoString text;
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);
1365 aStr = text;
1366 #else
1367 optionElement->GetText(aStr);
1368 #endif
1374 PRInt32
1375 nsListControlFrame::GetSelectedIndex()
1377 PRInt32 aIndex;
1379 nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
1380 selectElement->GetSelectedIndex(&aIndex);
1382 return aIndex;
1385 PRBool
1386 nsListControlFrame::IsInDropDownMode() const
1388 return (mComboboxFrame != nsnull);
1391 PRInt32
1392 nsListControlFrame::GetNumberOfOptions()
1394 if (mContent != nsnull) {
1395 nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1397 if (!options) {
1398 return 0;
1399 } else {
1400 PRUint32 length = 0;
1401 options->GetLength(&length);
1402 return (PRInt32)length;
1405 return 0;
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));
1416 if (node) {
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;
1426 NS_IMETHODIMP
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;
1438 ResetList(PR_TRUE);
1442 return NS_OK;
1445 NS_IMETHODIMP
1446 nsListControlFrame::AddOption(PRInt32 aIndex)
1448 #ifdef DO_REFLOW_DEBUG
1449 printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
1450 #endif
1452 if (!mIsAllContentHere) {
1453 mIsAllContentHere = mContent->IsDoneAddingChildren();
1454 if (!mIsAllContentHere) {
1455 mIsAllFramesHere = PR_FALSE;
1456 mHasBeenInitialized = PR_FALSE;
1457 } else {
1458 mIsAllFramesHere = (aIndex == GetNumberOfOptions()-1);
1462 // Make sure we scroll to the selected option as needed
1463 mNeedToReset = PR_TRUE;
1465 if (!mHasBeenInitialized) {
1466 return NS_OK;
1469 mPostChildrenLoadedReset = mIsAllContentHere;
1470 return NS_OK;
1473 static PRInt32
1474 DecrementAndClamp(PRInt32 aSelectionIndex, PRInt32 aLength)
1476 return aLength == 0 ? kNothingSelected : NS_MAX(0, aSelectionIndex - 1);
1479 NS_IMETHODIMP
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;
1500 if (aIndex < *low)
1501 *low = ::DecrementAndClamp(*low, numOptions);
1502 if (aIndex <= *high)
1503 *high = ::DecrementAndClamp(*high, numOptions);
1504 if (forward == 0)
1505 *low = *high;
1507 else
1508 NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
1510 InvalidateFocus();
1511 return NS_OK;
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.
1518 PRBool
1519 nsListControlFrame::SetOptionsSelectedFromFrame(PRInt32 aStartIndex,
1520 PRInt32 aEndIndex,
1521 PRBool aValue,
1522 PRBool aClearAll)
1524 nsCOMPtr<nsISelectElement> selectElement(do_QueryInterface(mContent));
1525 PRBool wasChanged = PR_FALSE;
1526 #ifdef DEBUG
1527 nsresult rv =
1528 #endif
1529 selectElement->SetOptionsSelectedByIndex(aStartIndex,
1530 aEndIndex,
1531 aValue,
1532 aClearAll,
1533 PR_FALSE,
1534 PR_TRUE,
1535 &wasChanged);
1536 NS_ASSERTION(NS_SUCCEEDED(rv), "SetSelected failed");
1537 return wasChanged;
1540 PRBool
1541 nsListControlFrame::ToggleOptionSelectedFromFrame(PRInt32 aIndex)
1543 nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1544 NS_ASSERTION(options, "No options");
1545 if (!options) {
1546 return PR_FALSE;
1548 nsCOMPtr<nsIDOMHTMLOptionElement> option = GetOption(options, aIndex);
1549 NS_ASSERTION(option, "No option");
1550 if (!option) {
1551 return PR_FALSE;
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,
1561 aIndex,
1562 !value,
1563 PR_FALSE,
1564 PR_FALSE,
1565 PR_TRUE,
1566 &wasChanged);
1568 NS_ASSERTION(NS_SUCCEEDED(rv), "SetSelected failed");
1570 return wasChanged;
1574 // Dispatch event and such
1575 PRBool
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) {
1586 FireOnChange();
1588 return weakFrame.IsAlive();
1590 return PR_TRUE;
1593 void
1594 nsListControlFrame::ComboboxFinish(PRInt32 aIndex)
1596 gLastKeyTime = 0;
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.
1616 void
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)
1623 return;
1625 // See if the selection actually changed
1626 if (index == GetSelectedIndex())
1627 return;
1630 // Dispatch the change event.
1631 nsContentUtils::DispatchTrustedEvent(mContent->GetOwnerDoc(), mContent,
1632 NS_LITERAL_STRING("change"), PR_TRUE,
1633 PR_FALSE);
1636 NS_IMETHODIMP
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;
1648 InvalidateFocus();
1650 #ifdef ACCESSIBILITY
1651 FireMenuItemActiveEvent();
1652 #endif
1654 return NS_OK;
1657 //----------------------------------------------------------------------
1658 // End nsISelectControlFrame
1659 //----------------------------------------------------------------------
1661 nsresult
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
1673 // OnOptionSelected
1675 return NS_OK;
1678 nsresult
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);
1684 PRInt32 error = 0;
1685 PRBool selected = PR_FALSE;
1686 PRInt32 indx = val.ToInteger(&error, 10); // Get index from aValue
1687 if (error == 0)
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;
1698 return NS_OK;
1701 void
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);
1713 void
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
1745 #endif
1747 mItemSelectionStarted = PR_FALSE;
1750 // We are about to be rolledup from the outside (ComboboxFrame)
1751 void
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
1761 // reset the index.
1763 if (IsInDropDownMode()) {
1764 ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
1768 NS_IMETHODIMP
1769 nsListControlFrame::DidReflow(nsPresContext* aPresContext,
1770 const nsHTMLReflowState* aReflowState,
1771 nsDidReflowStatus aStatus)
1773 nsresult rv;
1774 PRBool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
1775 aPresContext->HasPendingInterrupt();
1777 if (IsInDropDownMode())
1779 //SyncViewWithFrame();
1780 rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus);
1781 SyncViewWithFrame();
1782 } else {
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;
1801 return rv;
1804 nsIAtom*
1805 nsListControlFrame::GetType() const
1807 return nsGkAtoms::listControlFrame;
1810 PRBool
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
1815 // their sizing.
1816 return PR_TRUE;
1819 void
1820 nsListControlFrame::InvalidateInternal(const nsRect& aDamageRect,
1821 nscoord aX, nscoord aY, nsIFrame* aForChild,
1822 PRUint32 aFlags)
1824 if (!IsInDropDownMode()) {
1825 nsHTMLScrollFrame::InvalidateInternal(aDamageRect, aX, aY, this, aFlags);
1826 return;
1828 InvalidateRoot(aDamageRect + nsPoint(aX, aY), aFlags);
1831 #ifdef DEBUG
1832 NS_IMETHODIMP
1833 nsListControlFrame::GetFrameName(nsAString& aResult) const
1835 return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
1837 #endif
1839 nscoord
1840 nsListControlFrame::GetHeightOfARow()
1842 return HeightOfARow();
1845 nsresult
1846 nsListControlFrame::IsOptionDisabled(PRInt32 anIndex, PRBool &aIsDisabled)
1848 nsCOMPtr<nsISelectElement> sel(do_QueryInterface(mContent));
1849 if (sel) {
1850 sel->IsOptionDisabled(anIndex, &aIsDisabled);
1851 return NS_OK;
1853 return NS_ERROR_FAILURE;
1856 //----------------------------------------------------------------------
1857 // helper
1858 //----------------------------------------------------------------------
1859 PRBool
1860 nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
1862 // only allow selection with the left button
1863 nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1864 if (mouseEvent) {
1865 PRUint16 whichButton;
1866 if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
1867 return whichButton != 0?PR_FALSE:PR_TRUE;
1870 return PR_FALSE;
1873 nscoord
1874 nsListControlFrame::CalcFallbackRowHeight()
1876 nscoord rowHeight = 0;
1878 nsCOMPtr<nsIFontMetrics> fontMet;
1879 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet));
1880 if (fontMet) {
1881 fontMet->GetHeight(rowHeight);
1884 return rowHeight;
1887 nscoord
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 //----------------------------------------------------------------------
1907 nsresult
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)) {
1918 return NS_OK;
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();
1929 } else {
1930 CaptureMouseEvents(PR_FALSE);
1931 return NS_OK;
1933 CaptureMouseEvents(PR_FALSE);
1934 return NS_ERROR_FAILURE; // means consume event
1935 } else {
1936 CaptureMouseEvents(PR_FALSE);
1937 return NS_OK;
1941 const nsStyleVisibility* vis = GetStyleVisibility();
1943 if (!vis->IsVisible()) {
1944 return NS_OK;
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);
1967 if (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())
1978 return NS_OK;
1979 FireOnChange();
1982 mouseEvent->clickCount = 1;
1983 } else {
1984 // the click was out side of the select or its dropdown
1985 mouseEvent->clickCount = IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
1987 } else {
1988 CaptureMouseEvents(PR_FALSE);
1989 // Notify
1990 if (mChangesSinceDragStart) {
1991 // reset this so that future MouseUps without a prior MouseDown
1992 // won't fire onchange
1993 mChangesSinceDragStart = PR_FALSE;
1994 FireOnChange();
1998 return NS_OK;
2001 void
2002 nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
2004 if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown())
2005 return;
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)
2017 return PR_FALSE;
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())
2022 return PR_TRUE;
2024 return !mItemSelectionStarted;
2027 #ifdef ACCESSIBILITY
2028 void
2029 nsListControlFrame::FireMenuItemActiveEvent()
2031 if (mFocused != this && !IsInDropDownMode()) {
2032 return;
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();
2040 } else {
2041 focusedIndex = mEndSelectionIndex;
2043 if (focusedIndex == kNothingSelected) {
2044 return;
2047 nsCOMPtr<nsIContent> optionContent = GetOptionContent(focusedIndex);
2048 if (!optionContent) {
2049 return;
2052 FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
2054 #endif
2056 nsresult
2057 nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent,
2058 PRInt32& aCurIndex)
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);
2079 return NS_OK;
2082 PRInt32 numOptions = GetNumberOfOptions();
2083 if (numOptions < 1)
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();
2093 if (optionFrame) {
2094 nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
2095 if (ptInOptionFrame.y < 0 && ptInOptionFrame.x >= 0 &&
2096 ptInOptionFrame.x < optionFrame->GetSize().width) {
2097 aCurIndex = 0;
2098 return NS_OK;
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();
2107 if (optionFrame) {
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;
2112 return NS_OK;
2116 return NS_ERROR_FAILURE;
2119 nsresult
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)) {
2128 return NS_OK;
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();
2139 } else {
2140 return NS_OK;
2142 return NS_ERROR_FAILURE; // means consume event
2143 } else {
2144 return NS_OK;
2148 PRInt32 selectedIndex;
2149 if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
2150 // Handle Like List
2151 mButtonDown = PR_TRUE;
2152 CaptureMouseEvents(PR_TRUE);
2153 mChangesSinceDragStart = HandleListSelection(aMouseEvent, selectedIndex);
2154 #ifdef ACCESSIBILITY
2155 if (mChangesSinceDragStart) {
2156 FireMenuItemActiveEvent();
2158 #endif
2159 } else {
2160 // NOTE: the combo box is responsible for dropping it down
2161 if (mComboboxFrame) {
2162 if (!IgnoreMouseEventForSelection(aMouseEvent)) {
2163 return NS_OK;
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())
2173 return NS_OK;
2174 if (isDroppedDown) {
2175 CaptureMouseEvents(PR_FALSE);
2181 return NS_OK;
2184 //----------------------------------------------------------------------
2185 // nsIDOMMouseMotionListener
2186 //----------------------------------------------------------------------
2187 nsresult
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
2202 if (mButtonDown) {
2203 return DragMove(aMouseEvent);
2206 return NS_OK;
2209 nsresult
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) {
2221 return NS_OK;
2223 nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
2224 NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
2225 PRBool isControl;
2226 #ifdef XP_MACOSX
2227 mouseEvent->GetMetaKey(&isControl);
2228 #else
2229 mouseEvent->GetCtrlKey(&isControl);
2230 #endif
2231 // Turn SHIFT on when you are dragging, unless control is on.
2232 PRBool wasChanged = PerformSelection(selectedIndex,
2233 !isControl, isControl);
2234 mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
2237 return NS_OK;
2240 //----------------------------------------------------------------------
2241 // Scroll helpers.
2242 //----------------------------------------------------------------------
2243 nsresult
2244 nsListControlFrame::ScrollToIndex(PRInt32 aIndex)
2246 if (aIndex < 0) {
2247 // XXX shouldn't we just do nothing if we're asked to scroll to
2248 // kNothingSelected?
2249 return ScrollToFrame(nsnull);
2250 } else {
2251 nsCOMPtr<nsIContent> content = GetOptionContent(aIndex);
2252 if (content) {
2253 return ScrollToFrame(content);
2257 return NS_ERROR_FAILURE;
2260 nsresult
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);
2266 return NS_OK;
2269 // otherwise we find the content's frame and scroll to it
2270 nsIFrame *childFrame = aOptElement->GetPrimaryFrame();
2271 if (childFrame) {
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);
2286 } else {
2287 pt.y = fRect.y;
2289 ScrollTo(nsPoint(fRect.x, pt.y), nsIScrollableFrame::INSTANT);
2292 return NS_OK;
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
2311 void
2312 nsListControlFrame::AdjustIndexForDisabledOpt(PRInt32 aStartIndex,
2313 PRInt32 &aNewIndex,
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;
2321 return;
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
2327 PRInt32 bottom = 0;
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) {
2345 newIndex = 0;
2346 } else if (newIndex >= top) {
2347 newIndex = aNumOptions-1;
2350 while (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) {
2354 break;
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) {
2362 if (doingReverse) {
2363 return; // if we are in reverse mode and reach the end bail out
2364 } else {
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
2368 newIndex = bottom;
2369 aDoAdjustIncNext = 1;
2370 doingReverse = PR_TRUE;
2371 top = startIndex;
2373 } else if (newIndex >= top) {
2374 if (doingReverse) {
2375 return; // if we are in reverse mode and reach the end bail out
2376 } else {
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
2380 newIndex = top - 1;
2381 aDoAdjustIncNext = -1;
2382 doingReverse = PR_TRUE;
2383 bottom = startIndex;
2388 // Looks like we found one
2389 aNewIndex = newIndex;
2392 nsAString&
2393 nsListControlFrame::GetIncrementalString()
2395 if (sIncrementalString == nsnull)
2396 sIncrementalString = new nsString();
2398 return *sIncrementalString;
2401 void
2402 nsListControlFrame::Shutdown()
2404 delete sIncrementalString;
2405 sIncrementalString = nsnull;
2408 void
2409 nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
2411 // Cocoa widgets do native popups, so don't try to show
2412 // dropdowns there.
2413 if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
2414 aKeyEvent->PreventDefault();
2415 if (!mComboboxFrame->IsDroppedDown()) {
2416 mComboboxFrame->ShowDropDown(PR_TRUE);
2417 } else {
2418 nsWeakFrame weakFrame(this);
2419 // mEndSelectionIndex is the last item that got selected.
2420 ComboboxFinish(mEndSelectionIndex);
2421 if (weakFrame.IsAlive()) {
2422 FireOnChange();
2428 nsresult
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))
2435 return NS_OK;
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);
2449 if (isAlt) {
2450 if (keycode == nsIDOMKeyEvent::DOM_VK_UP || keycode == nsIDOMKeyEvent::DOM_VK_DOWN) {
2451 DropDownToggleKey(aKeyEvent);
2453 return NS_OK;
2456 // Get control / shift modifiers
2457 PRBool isControl = PR_FALSE;
2458 PRBool isShift = PR_FALSE;
2459 keyEvent->GetCtrlKey(&isControl);
2460 if (!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;
2494 switch (keycode) {
2496 case nsIDOMKeyEvent::DOM_VK_UP:
2497 case nsIDOMKeyEvent::DOM_VK_LEFT: {
2498 AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2499 (PRInt32)numOptions,
2500 -1, -1);
2501 } break;
2503 case nsIDOMKeyEvent::DOM_VK_DOWN:
2504 case nsIDOMKeyEvent::DOM_VK_RIGHT: {
2505 AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2506 (PRInt32)numOptions,
2507 1, 1);
2508 } break;
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())
2516 return NS_OK;
2518 FireOnChange();
2519 return NS_OK;
2520 } else {
2521 newIndex = mEndSelectionIndex;
2523 } break;
2525 case nsIDOMKeyEvent::DOM_VK_ESCAPE: {
2526 nsWeakFrame weakFrame(this);
2527 AboutToRollup();
2528 if (!weakFrame.IsAlive()) {
2529 aKeyEvent->PreventDefault(); // since we won't reach the one below
2530 return NS_OK;
2532 } break;
2534 case nsIDOMKeyEvent::DOM_VK_PAGE_UP: {
2535 AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2536 (PRInt32)numOptions,
2537 -NS_MAX(1, mNumDisplayRows-1), -1);
2538 } break;
2540 case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: {
2541 AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2542 (PRInt32)numOptions,
2543 NS_MAX(1, mNumDisplayRows-1), 1);
2544 } break;
2546 case nsIDOMKeyEvent::DOM_VK_HOME: {
2547 AdjustIndexForDisabledOpt(0, newIndex,
2548 (PRInt32)numOptions,
2549 0, 1);
2550 } break;
2552 case nsIDOMKeyEvent::DOM_VK_END: {
2553 AdjustIndexForDisabledOpt(numOptions-1, newIndex,
2554 (PRInt32)numOptions,
2555 0, -1);
2556 } break;
2558 #if defined(XP_WIN) || defined(XP_OS2)
2559 case nsIDOMKeyEvent::DOM_VK_F4: {
2560 DropDownToggleKey(aKeyEvent);
2561 return NS_OK;
2562 } break;
2563 #endif
2565 case nsIDOMKeyEvent::DOM_VK_TAB: {
2566 return NS_OK;
2569 default: { // Select option with this as the first character
2570 // XXX Not I18N compliant
2572 if (isControl && charcode != ' ') {
2573 return NS_OK;
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();
2583 return NS_OK;
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;
2599 break;
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]) {
2613 charIndex++;
2615 if (charIndex == stringLength) {
2616 incrementalString.Truncate(1);
2617 stringLength = 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
2625 // *at* 0.
2626 PRInt32 startIndex = GetSelectedIndex();
2627 if (startIndex == kNothingSelected) {
2628 startIndex = 0;
2629 } else if (stringLength == 1) {
2630 startIndex++;
2633 PRUint32 i;
2634 for (i = 0; i < numOptions; i++) {
2635 PRUint32 index = (i + startIndex) % numOptions;
2636 nsCOMPtr<nsIDOMHTMLOptionElement> optionElement =
2637 GetOption(options, index);
2638 if (optionElement) {
2639 nsAutoString text;
2640 if (NS_OK == optionElement->GetText(text)) {
2641 if (StringBeginsWith(text, incrementalString,
2642 nsCaseInsensitiveStringComparator())) {
2643 PRBool wasChanged = PerformSelection(index, isShift, isControl);
2644 if (wasChanged) {
2645 // dispatch event, update combobox, etc.
2646 if (!UpdateSelection()) {
2647 return NS_OK;
2649 #ifdef ACCESSIBILITY
2650 FireMenuItemActiveEvent(); // Only fire if new item reached
2651 #endif
2653 break;
2657 } // for
2659 } break;//case
2660 } // switch
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;
2678 InvalidateFocus();
2679 ScrollToIndex(newIndex);
2680 } else if (mControlSelectMode && charcode == ' ') {
2681 wasChanged = SingleSelection(newIndex, PR_TRUE);
2682 } else {
2683 wasChanged = PerformSelection(newIndex, isShift, isControl);
2685 if (wasChanged) {
2686 // dispatch event, update combobox, etc.
2687 if (!UpdateSelection()) {
2688 return NS_OK;
2691 #ifdef ACCESSIBILITY
2692 if (charcode != ' ') {
2693 FireMenuItemActiveEvent();
2695 #endif
2698 return NS_OK;
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) \
2717 NS_IMETHODIMP \
2718 nsListEventListener::_event(nsIDOMEvent* aEvent) \
2720 if (mFrame) \
2721 return mFrame->nsListControlFrame::_event(aEvent); \
2722 return NS_OK; \
2725 #define IGNORE_EVENT(_event) \
2726 NS_IMETHODIMP \
2727 nsListEventListener::_event(nsIDOMEvent* aEvent) \
2728 { return NS_OK; }
2730 IGNORE_EVENT(HandleEvent)
2732 /*================== nsIDOMKeyListener =========================*/
2734 IGNORE_EVENT(KeyDown)
2735 IGNORE_EVENT(KeyUp)
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
2754 #undef IGNORE_EVENT