1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Pierre Phaneuf <pp@ludusdesign.com>
24 : * Mats Palmgren <matspal@gmail.com>
25 : *
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.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
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 "nsIDocument.h"
49 : #include "nsIDOMHTMLCollection.h"
50 : #include "nsIDOMHTMLOptionsCollection.h"
51 : #include "nsIDOMHTMLSelectElement.h"
52 : #include "nsIDOMHTMLOptionElement.h"
53 : #include "nsComboboxControlFrame.h"
54 : #include "nsIViewManager.h"
55 : #include "nsIDOMHTMLOptGroupElement.h"
56 : #include "nsWidgetsCID.h"
57 : #include "nsIPresShell.h"
58 : #include "nsHTMLParts.h"
59 : #include "nsIDOMEventTarget.h"
60 : #include "nsEventDispatcher.h"
61 : #include "nsEventStateManager.h"
62 : #include "nsEventListenerManager.h"
63 : #include "nsIDOMKeyEvent.h"
64 : #include "nsIDOMMouseEvent.h"
65 : #include "nsIPrivateDOMEvent.h"
66 : #include "nsXPCOM.h"
67 : #include "nsISupportsPrimitives.h"
68 : #include "nsIComponentManager.h"
69 : #include "nsFontMetrics.h"
70 : #include "nsIScrollableFrame.h"
71 : #include "nsIDOMNSEvent.h"
72 : #include "nsGUIEvent.h"
73 : #include "nsIServiceManager.h"
74 : #include "nsINodeInfo.h"
75 : #ifdef ACCESSIBILITY
76 : #include "nsAccessibilityService.h"
77 : #endif
78 : #include "nsHTMLSelectElement.h"
79 : #include "nsCSSRendering.h"
80 : #include "nsITheme.h"
81 : #include "nsIDOMEventListener.h"
82 : #include "nsLayoutUtils.h"
83 : #include "nsDisplayList.h"
84 : #include "nsContentUtils.h"
85 : #include "mozilla/LookAndFeel.h"
86 :
87 : using namespace mozilla;
88 :
89 : // Constants
90 : const nscoord kMaxDropDownRows = 20; // This matches the setting for 4.x browsers
91 : const PRInt32 kNothingSelected = -1;
92 :
93 : // Static members
94 : nsListControlFrame * nsListControlFrame::mFocused = nsnull;
95 : nsString * nsListControlFrame::sIncrementalString = nsnull;
96 :
97 : // Using for incremental typing navigation
98 : #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
99 : // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
100 : // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
101 : // need to find a good place to put them together.
102 : // if someone changes one, please also change the other.
103 :
104 : DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
105 :
106 : /******************************************************************************
107 : * nsListEventListener
108 : * This class is responsible for propagating events to the nsListControlFrame.
109 : * Frames are not refcounted so they can't be used as event listeners.
110 : *****************************************************************************/
111 :
112 : class nsListEventListener : public nsIDOMEventListener
113 : {
114 : public:
115 0 : nsListEventListener(nsListControlFrame *aFrame)
116 0 : : mFrame(aFrame) { }
117 :
118 0 : void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
119 :
120 : NS_DECL_ISUPPORTS
121 : NS_DECL_NSIDOMEVENTLISTENER
122 :
123 : private:
124 : nsListControlFrame *mFrame;
125 : };
126 :
127 : //---------------------------------------------------------
128 : nsIFrame*
129 0 : NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
130 : {
131 : nsListControlFrame* it =
132 0 : new (aPresShell) nsListControlFrame(aPresShell, aPresShell->GetDocument(), aContext);
133 :
134 0 : if (it) {
135 0 : it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
136 : }
137 :
138 0 : return it;
139 : }
140 :
141 0 : NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
142 :
143 : //---------------------------------------------------------
144 0 : nsListControlFrame::nsListControlFrame(
145 : nsIPresShell* aShell, nsIDocument* aDocument, nsStyleContext* aContext)
146 : : nsHTMLScrollFrame(aShell, aContext, false),
147 : mMightNeedSecondPass(false),
148 : mHasPendingInterruptAtStartOfReflow(false),
149 0 : mLastDropdownComputedHeight(NS_UNCONSTRAINEDSIZE)
150 : {
151 0 : mComboboxFrame = nsnull;
152 0 : mChangesSinceDragStart = false;
153 0 : mButtonDown = false;
154 :
155 0 : mIsAllContentHere = false;
156 0 : mIsAllFramesHere = false;
157 0 : mHasBeenInitialized = false;
158 0 : mNeedToReset = true;
159 0 : mPostChildrenLoadedReset = false;
160 :
161 0 : mControlSelectMode = false;
162 0 : }
163 :
164 : //---------------------------------------------------------
165 0 : nsListControlFrame::~nsListControlFrame()
166 : {
167 0 : mComboboxFrame = nsnull;
168 0 : }
169 :
170 : // for Bug 47302 (remove this comment later)
171 : void
172 0 : nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
173 : {
174 : // get the receiver interface from the browser button's content node
175 0 : ENSURE_TRUE(mContent);
176 :
177 : // Clear the frame pointer on our event listener, just in case the
178 : // event listener can outlive the frame.
179 :
180 0 : mEventListener->SetFrame(nsnull);
181 :
182 0 : mContent->RemoveEventListener(NS_LITERAL_STRING("keypress"), mEventListener,
183 0 : false);
184 0 : mContent->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mEventListener,
185 0 : false);
186 0 : mContent->RemoveEventListener(NS_LITERAL_STRING("mouseup"), mEventListener,
187 0 : false);
188 0 : mContent->RemoveEventListener(NS_LITERAL_STRING("mousemove"), mEventListener,
189 0 : false);
190 :
191 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
192 0 : nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
193 : }
194 :
195 : NS_IMETHODIMP
196 0 : nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
197 : const nsRect& aDirtyRect,
198 : const nsDisplayListSet& aLists)
199 : {
200 : // We allow visibility:hidden <select>s to contain visible options.
201 :
202 : // Don't allow painting of list controls when painting is suppressed.
203 : // XXX why do we need this here? we should never reach this. Maybe
204 : // because these can have widgets? Hmm
205 0 : if (aBuilder->IsBackgroundOnly())
206 0 : return NS_OK;
207 :
208 0 : DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
209 :
210 0 : if (IsInDropDownMode()) {
211 0 : NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
212 : "need an opaque backstop color");
213 : // XXX Because we have an opaque widget and we get called to paint with
214 : // this frame as the root of a stacking context we need make sure to draw
215 : // some opaque color over the whole widget. (Bug 511323)
216 : aLists.BorderBackground()->AppendNewToBottom(
217 : new (aBuilder) nsDisplaySolidColor(aBuilder,
218 0 : this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
219 0 : mLastDropdownBackstopColor));
220 : }
221 :
222 : // REVIEW: The selection visibility code that used to be here is what
223 : // we already do by default.
224 : // REVIEW: There was code here to paint the theme background. But as far
225 : // as I can tell, we'd just paint the theme background twice because
226 : // it was redundant with nsCSSRendering::PaintBackground
227 0 : return nsHTMLScrollFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
228 : }
229 :
230 : /**
231 : * This is called by the SelectsAreaFrame, which is the same
232 : * as the frame returned by GetOptionsContainer. It's the frame which is
233 : * scrolled by us.
234 : * @param aPt the offset of this frame, relative to the rendering reference
235 : * frame
236 : */
237 0 : void nsListControlFrame::PaintFocus(nsRenderingContext& aRC, nsPoint aPt)
238 : {
239 0 : if (mFocused != this) return;
240 :
241 0 : nsPresContext* presContext = PresContext();
242 :
243 0 : nsIFrame* containerFrame = GetOptionsContainer();
244 0 : if (!containerFrame) return;
245 :
246 0 : nsIFrame* childframe = nsnull;
247 0 : nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
248 0 : if (focusedContent) {
249 0 : childframe = focusedContent->GetPrimaryFrame();
250 : }
251 :
252 0 : nsRect fRect;
253 0 : if (childframe) {
254 : // get the child rect
255 0 : fRect = childframe->GetRect();
256 : // get it into our coordinates
257 0 : fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
258 : } else {
259 : float inflation = nsLayoutUtils::FontSizeInflationFor(this,
260 0 : nsLayoutUtils::eNotInReflow);
261 0 : fRect.x = fRect.y = 0;
262 0 : fRect.width = GetScrollPortRect().width;
263 0 : fRect.height = CalcFallbackRowHeight(inflation);
264 0 : fRect.MoveBy(containerFrame->GetOffsetTo(this));
265 : }
266 0 : fRect += aPt;
267 :
268 0 : bool lastItemIsSelected = false;
269 0 : if (focusedContent) {
270 : nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
271 0 : do_QueryInterface(focusedContent);
272 0 : if (domOpt) {
273 0 : domOpt->GetSelected(&lastItemIsSelected);
274 : }
275 : }
276 :
277 : // set up back stop colors and then ask L&F service for the real colors
278 : nscolor color =
279 : LookAndFeel::GetColor(lastItemIsSelected ?
280 : LookAndFeel::eColorID_WidgetSelectForeground :
281 0 : LookAndFeel::eColorID_WidgetSelectBackground);
282 :
283 0 : nsCSSRendering::PaintFocus(presContext, aRC, fRect, color);
284 : }
285 :
286 : void
287 0 : nsListControlFrame::InvalidateFocus(const nsHTMLReflowState *aReflowState)
288 : {
289 0 : if (mFocused != this)
290 0 : return;
291 :
292 0 : nsIFrame* containerFrame = GetOptionsContainer();
293 0 : if (containerFrame) {
294 : // Invalidating from the containerFrame because that's where our focus
295 : // is drawn.
296 : // The origin of the scrollport is the origin of containerFrame.
297 : float inflation = nsLayoutUtils::FontSizeInflationFor(this,
298 : aReflowState ? nsLayoutUtils::eInReflow
299 0 : : nsLayoutUtils::eNotInReflow);
300 0 : nsRect invalidateArea = containerFrame->GetVisualOverflowRect();
301 0 : nsRect emptyFallbackArea(0, 0, GetScrollPortRect().width,
302 0 : CalcFallbackRowHeight(inflation));
303 0 : invalidateArea.UnionRect(invalidateArea, emptyFallbackArea);
304 0 : containerFrame->Invalidate(invalidateArea);
305 : }
306 : }
307 :
308 0 : NS_QUERYFRAME_HEAD(nsListControlFrame)
309 0 : NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
310 0 : NS_QUERYFRAME_ENTRY(nsIListControlFrame)
311 0 : NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
312 0 : NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame)
313 :
314 : #ifdef ACCESSIBILITY
315 : already_AddRefed<nsAccessible>
316 0 : nsListControlFrame::CreateAccessible()
317 : {
318 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
319 0 : if (accService) {
320 : return accService->CreateHTMLListboxAccessible(mContent,
321 0 : PresContext()->PresShell());
322 : }
323 :
324 0 : return nsnull;
325 : }
326 : #endif
327 :
328 : static nscoord
329 0 : GetMaxOptionHeight(nsIFrame* aContainer)
330 : {
331 0 : nscoord result = 0;
332 0 : for (nsIFrame* option = aContainer->GetFirstPrincipalChild();
333 : option; option = option->GetNextSibling()) {
334 : nscoord optionHeight;
335 0 : if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
336 0 : (do_QueryInterface(option->GetContent()))) {
337 : // an optgroup
338 0 : optionHeight = GetMaxOptionHeight(option);
339 : } else {
340 : // an option
341 0 : optionHeight = option->GetSize().height;
342 : }
343 0 : if (result < optionHeight)
344 0 : result = optionHeight;
345 : }
346 0 : return result;
347 : }
348 :
349 : static PRUint32
350 0 : GetNumberOfOptionsRecursive(nsIContent* aContent)
351 : {
352 0 : if (!aContent) {
353 0 : return 0;
354 : }
355 :
356 0 : PRUint32 optionCount = 0;
357 0 : for (nsIContent* cur = aContent->GetFirstChild();
358 : cur;
359 0 : cur = cur->GetNextSibling()) {
360 0 : if (cur->IsHTML(nsGkAtoms::option)) {
361 0 : ++optionCount;
362 0 : } else if (cur->IsHTML(nsGkAtoms::optgroup)) {
363 0 : optionCount += GetNumberOfOptionsRecursive(cur);
364 : }
365 : }
366 0 : return optionCount;
367 : }
368 :
369 : //-----------------------------------------------------------------
370 : // Main Reflow for ListBox/Dropdown
371 : //-----------------------------------------------------------------
372 :
373 : nscoord
374 0 : nsListControlFrame::CalcHeightOfARow()
375 : {
376 : // Calculate the height of a single row in the listbox or dropdown list by
377 : // using the tallest thing in the subtree, since there may be option groups
378 : // in addition to option elements, either of which may be visible or
379 : // invisible, may use different fonts, etc.
380 0 : PRInt32 heightOfARow = GetMaxOptionHeight(GetOptionsContainer());
381 :
382 : // Check to see if we have zero items (and optimize by checking
383 : // heightOfARow first)
384 0 : if (heightOfARow == 0 && GetNumberOfOptions() == 0) {
385 : float inflation =
386 0 : nsLayoutUtils::FontSizeInflationInner(this, nsLayoutUtils::eInReflow);
387 0 : heightOfARow = CalcFallbackRowHeight(inflation);
388 : }
389 :
390 0 : return heightOfARow;
391 : }
392 :
393 : nscoord
394 0 : nsListControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
395 : {
396 : nscoord result;
397 0 : DISPLAY_PREF_WIDTH(this, result);
398 :
399 : // Always add scrollbar widths to the pref-width of the scrolled
400 : // content. Combobox frames depend on this happening in the dropdown,
401 : // and standalone listboxes are overflow:scroll so they need it too.
402 0 : result = GetScrolledFrame()->GetPrefWidth(aRenderingContext);
403 : result = NSCoordSaturatingAdd(result,
404 0 : GetDesiredScrollbarSizes(PresContext(), aRenderingContext).LeftRight());
405 :
406 0 : return result;
407 : }
408 :
409 : nscoord
410 0 : nsListControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
411 : {
412 : nscoord result;
413 0 : DISPLAY_MIN_WIDTH(this, result);
414 :
415 : // Always add scrollbar widths to the min-width of the scrolled
416 : // content. Combobox frames depend on this happening in the dropdown,
417 : // and standalone listboxes are overflow:scroll so they need it too.
418 0 : result = GetScrolledFrame()->GetMinWidth(aRenderingContext);
419 0 : result += GetDesiredScrollbarSizes(PresContext(), aRenderingContext).LeftRight();
420 :
421 0 : return result;
422 : }
423 :
424 : NS_IMETHODIMP
425 0 : nsListControlFrame::Reflow(nsPresContext* aPresContext,
426 : nsHTMLReflowMetrics& aDesiredSize,
427 : const nsHTMLReflowState& aReflowState,
428 : nsReflowStatus& aStatus)
429 : {
430 0 : NS_PRECONDITION(aReflowState.ComputedWidth() != NS_UNCONSTRAINEDSIZE,
431 : "Must have a computed width");
432 :
433 0 : mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt();
434 :
435 : // If all the content and frames are here
436 : // then initialize it before reflow
437 0 : if (mIsAllContentHere && !mHasBeenInitialized) {
438 0 : if (false == mIsAllFramesHere) {
439 0 : CheckIfAllFramesHere();
440 : }
441 0 : if (mIsAllFramesHere && !mHasBeenInitialized) {
442 0 : mHasBeenInitialized = true;
443 : }
444 : }
445 :
446 0 : if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
447 0 : nsFormControlFrame::RegUnRegAccessKey(this, true);
448 : }
449 :
450 0 : if (IsInDropDownMode()) {
451 0 : return ReflowAsDropdown(aPresContext, aDesiredSize, aReflowState, aStatus);
452 : }
453 :
454 : /*
455 : * Due to the fact that our intrinsic height depends on the heights of our
456 : * kids, we end up having to do two-pass reflow, in general -- the first pass
457 : * to find the intrinsic height and a second pass to reflow the scrollframe
458 : * at that height (which will size the scrollbars correctly, etc).
459 : *
460 : * Naturaly, we want to avoid doing the second reflow as much as possible.
461 : * We can skip it in the following cases (in all of which the first reflow is
462 : * already happening at the right height):
463 : *
464 : * - We're reflowing with a constrained computed height -- just use that
465 : * height.
466 : * - We're not dirty and have no dirty kids and shouldn't be reflowing all
467 : * kids. In this case, our cached max height of a child is not going to
468 : * change.
469 : * - We do our first reflow using our cached max height of a child, then
470 : * compute the new max height and it's the same as the old one.
471 : */
472 :
473 0 : bool autoHeight = (aReflowState.ComputedHeight() == NS_UNCONSTRAINEDSIZE);
474 :
475 : mMightNeedSecondPass = autoHeight &&
476 0 : (NS_SUBTREE_DIRTY(this) || aReflowState.ShouldReflowAllKids());
477 :
478 0 : nsHTMLReflowState state(aReflowState);
479 0 : PRInt32 length = GetNumberOfOptions();
480 :
481 0 : nscoord oldHeightOfARow = HeightOfARow();
482 :
483 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoHeight) {
484 : // When not doing an initial reflow, and when the height is auto, start off
485 : // with our computed height set to what we'd expect our height to be.
486 0 : nscoord computedHeight = CalcIntrinsicHeight(oldHeightOfARow, length);
487 0 : state.ApplyMinMaxConstraints(nsnull, &computedHeight);
488 0 : state.SetComputedHeight(computedHeight);
489 : }
490 :
491 : nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize,
492 0 : state, aStatus);
493 0 : NS_ENSURE_SUCCESS(rv, rv);
494 :
495 0 : if (!mMightNeedSecondPass) {
496 0 : NS_ASSERTION(!autoHeight || HeightOfARow() == oldHeightOfARow,
497 : "How did our height of a row change if nothing was dirty?");
498 0 : NS_ASSERTION(!autoHeight ||
499 : !(GetStateBits() & NS_FRAME_FIRST_REFLOW),
500 : "How do we not need a second pass during initial reflow at "
501 : "auto height?");
502 0 : NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
503 : "Shouldn't be suppressing if we don't need a second pass!");
504 0 : if (!autoHeight) {
505 : // Update our mNumDisplayRows based on our new row height now that we
506 : // know it. Note that if autoHeight and we landed in this code then we
507 : // already set mNumDisplayRows in CalcIntrinsicHeight. Also note that we
508 : // can't use HeightOfARow() here because that just uses a cached value
509 : // that we didn't compute.
510 0 : nscoord rowHeight = CalcHeightOfARow();
511 0 : if (rowHeight == 0) {
512 : // Just pick something
513 0 : mNumDisplayRows = 1;
514 : } else {
515 0 : mNumDisplayRows = NS_MAX(1, state.ComputedHeight() / rowHeight);
516 : }
517 : }
518 :
519 0 : return rv;
520 : }
521 :
522 0 : mMightNeedSecondPass = false;
523 :
524 : // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
525 : // will have suppressed the scrollbar update.
526 0 : if (!IsScrollbarUpdateSuppressed()) {
527 : // All done. No need to do more reflow.
528 0 : NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
529 : "Shouldn't be suppressing if the height of a row has not "
530 : "changed!");
531 0 : return rv;
532 : }
533 :
534 0 : SetSuppressScrollbarUpdate(false);
535 :
536 : // Gotta reflow again.
537 : // XXXbz We're just changing the height here; do we need to dirty ourselves
538 : // or anything like that? We might need to, per the letter of the reflow
539 : // protocol, but things seem to work fine without it... Is that just an
540 : // implementation detail of nsHTMLScrollFrame that we're depending on?
541 0 : nsHTMLScrollFrame::DidReflow(aPresContext, &state, aStatus);
542 :
543 : // Now compute the height we want to have
544 0 : nscoord computedHeight = CalcIntrinsicHeight(HeightOfARow(), length);
545 0 : state.ApplyMinMaxConstraints(nsnull, &computedHeight);
546 0 : state.SetComputedHeight(computedHeight);
547 :
548 0 : nsHTMLScrollFrame::WillReflow(aPresContext);
549 :
550 : // XXXbz to make the ascent really correct, we should add our
551 : // mComputedPadding.top to it (and subtract it from descent). Need that
552 : // because nsGfxScrollFrame just adds in the border....
553 0 : return nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
554 : }
555 :
556 : nsresult
557 0 : nsListControlFrame::ReflowAsDropdown(nsPresContext* aPresContext,
558 : nsHTMLReflowMetrics& aDesiredSize,
559 : const nsHTMLReflowState& aReflowState,
560 : nsReflowStatus& aStatus)
561 : {
562 0 : NS_PRECONDITION(aReflowState.ComputedHeight() == NS_UNCONSTRAINEDSIZE,
563 : "We should not have a computed height here!");
564 :
565 0 : mMightNeedSecondPass = NS_SUBTREE_DIRTY(this) ||
566 0 : aReflowState.ShouldReflowAllKids();
567 :
568 : #ifdef DEBUG
569 0 : nscoord oldHeightOfARow = HeightOfARow();
570 : #endif
571 :
572 0 : nsHTMLReflowState state(aReflowState);
573 :
574 : nscoord oldVisibleHeight;
575 0 : if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
576 : // When not doing an initial reflow, and when the height is auto, start off
577 : // with our computed height set to what we'd expect our height to be.
578 : // Note: At this point, mLastDropdownComputedHeight can be
579 : // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to constrain
580 : // the height. That's fine; just do the same thing as last time.
581 0 : state.SetComputedHeight(mLastDropdownComputedHeight);
582 0 : oldVisibleHeight = GetScrolledFrame()->GetSize().height;
583 : } else {
584 : // Set oldVisibleHeight to something that will never test true against a
585 : // real height.
586 0 : oldVisibleHeight = NS_UNCONSTRAINEDSIZE;
587 : }
588 :
589 : nsresult rv = nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize,
590 0 : state, aStatus);
591 0 : NS_ENSURE_SUCCESS(rv, rv);
592 :
593 0 : if (!mMightNeedSecondPass) {
594 0 : NS_ASSERTION(oldVisibleHeight == GetScrolledFrame()->GetSize().height,
595 : "How did our kid's height change if nothing was dirty?");
596 0 : NS_ASSERTION(HeightOfARow() == oldHeightOfARow,
597 : "How did our height of a row change if nothing was dirty?");
598 0 : NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
599 : "Shouldn't be suppressing if we don't need a second pass!");
600 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
601 : "How can we avoid a second pass during first reflow?");
602 0 : return rv;
603 : }
604 :
605 0 : mMightNeedSecondPass = false;
606 :
607 : // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
608 : // will have suppressed the scrollbar update.
609 0 : if (!IsScrollbarUpdateSuppressed()) {
610 : // All done. No need to do more reflow.
611 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
612 : "How can we avoid a second pass during first reflow?");
613 0 : return rv;
614 : }
615 :
616 0 : SetSuppressScrollbarUpdate(false);
617 :
618 0 : nscoord visibleHeight = GetScrolledFrame()->GetSize().height;
619 0 : nscoord heightOfARow = HeightOfARow();
620 :
621 : // Gotta reflow again.
622 : // XXXbz We're just changing the height here; do we need to dirty ourselves
623 : // or anything like that? We might need to, per the letter of the reflow
624 : // protocol, but things seem to work fine without it... Is that just an
625 : // implementation detail of nsHTMLScrollFrame that we're depending on?
626 0 : nsHTMLScrollFrame::DidReflow(aPresContext, &state, aStatus);
627 :
628 : // Now compute the height we want to have
629 0 : mNumDisplayRows = kMaxDropDownRows;
630 0 : if (visibleHeight > mNumDisplayRows * heightOfARow) {
631 0 : visibleHeight = mNumDisplayRows * heightOfARow;
632 : // This is an adaptive algorithm for figuring out how many rows
633 : // should be displayed in the drop down. The standard size is 20 rows,
634 : // but on 640x480 it is typically too big.
635 : // This takes the height of the screen divides it by two and then subtracts off
636 : // an estimated height of the combobox. I estimate it by taking the max element size
637 : // of the drop down and multiplying it by 2 (this is arbitrary) then subtract off
638 : // the border and padding of the drop down (again rather arbitrary)
639 : // This all breaks down if the font of the combobox is a lot larger then the option items
640 : // or CSS style has set the height of the combobox to be rather large.
641 : // We can fix these cases later if they actually happen.
642 0 : nsRect screen = nsFormControlFrame::GetUsableScreenRect(aPresContext);
643 0 : nscoord screenHeight = screen.height;
644 :
645 0 : nscoord availDropHgt = (screenHeight / 2) - (heightOfARow*2); // approx half screen minus combo size
646 0 : availDropHgt -= aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;
647 :
648 0 : nscoord hgt = visibleHeight + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;
649 0 : if (heightOfARow > 0) {
650 0 : if (hgt > availDropHgt) {
651 0 : visibleHeight = (availDropHgt / heightOfARow) * heightOfARow;
652 : }
653 0 : mNumDisplayRows = visibleHeight / heightOfARow;
654 : } else {
655 : // Hmmm, not sure what to do here. Punt, and make both of them one
656 0 : visibleHeight = 1;
657 0 : mNumDisplayRows = 1;
658 : }
659 :
660 0 : state.SetComputedHeight(mNumDisplayRows * heightOfARow);
661 : // Note: no need to apply min/max constraints, since we have no such
662 : // rules applied to the combobox dropdown.
663 : // XXXbz this is ending up too big!! Figure out why.
664 0 : } else if (visibleHeight == 0) {
665 : // Looks like we have no options. Just size us to a single row height.
666 0 : state.SetComputedHeight(heightOfARow);
667 : } else {
668 : // Not too big, not too small. Just use it!
669 0 : state.SetComputedHeight(NS_UNCONSTRAINEDSIZE);
670 : }
671 :
672 : // Note: At this point, state.mComputedHeight can be NS_UNCONSTRAINEDSIZE in
673 : // cases when there were some options, but not too many (so no scrollbar was
674 : // needed). That's fine; just store that.
675 0 : mLastDropdownComputedHeight = state.ComputedHeight();
676 :
677 0 : nsHTMLScrollFrame::WillReflow(aPresContext);
678 0 : return nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
679 : }
680 :
681 : nsGfxScrollFrameInner::ScrollbarStyles
682 0 : nsListControlFrame::GetScrollbarStyles() const
683 : {
684 : // We can't express this in the style system yet; when we can, this can go away
685 : // and GetScrollbarStyles can be devirtualized
686 0 : PRInt32 verticalStyle = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
687 0 : : NS_STYLE_OVERFLOW_SCROLL;
688 : return nsGfxScrollFrameInner::ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN,
689 0 : verticalStyle);
690 : }
691 :
692 : bool
693 0 : nsListControlFrame::ShouldPropagateComputedHeightToScrolledContent() const
694 : {
695 0 : return !IsInDropDownMode();
696 : }
697 :
698 : //---------------------------------------------------------
699 : nsIFrame*
700 0 : nsListControlFrame::GetContentInsertionFrame() {
701 0 : return GetOptionsContainer()->GetContentInsertionFrame();
702 : }
703 :
704 : //---------------------------------------------------------
705 : // Starts at the passed in content object and walks up the
706 : // parent heierarchy looking for the nsIDOMHTMLOptionElement
707 : //---------------------------------------------------------
708 : nsIContent *
709 0 : nsListControlFrame::GetOptionFromContent(nsIContent *aContent)
710 : {
711 0 : for (nsIContent* content = aContent; content; content = content->GetParent()) {
712 0 : if (content->IsHTML(nsGkAtoms::option)) {
713 0 : return content;
714 : }
715 : }
716 :
717 0 : return nsnull;
718 : }
719 :
720 : //---------------------------------------------------------
721 : // Finds the index of the hit frame's content in the list
722 : // of option elements
723 : //---------------------------------------------------------
724 : PRInt32
725 0 : nsListControlFrame::GetIndexFromContent(nsIContent *aContent)
726 : {
727 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option;
728 0 : option = do_QueryInterface(aContent);
729 0 : if (option) {
730 : PRInt32 retval;
731 0 : option->GetIndex(&retval);
732 0 : if (retval >= 0) {
733 0 : return retval;
734 : }
735 : }
736 0 : return kNothingSelected;
737 : }
738 :
739 : //---------------------------------------------------------
740 : bool
741 0 : nsListControlFrame::ExtendedSelection(PRInt32 aStartIndex,
742 : PRInt32 aEndIndex,
743 : bool aClearAll)
744 : {
745 : return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
746 0 : true, aClearAll);
747 : }
748 :
749 : //---------------------------------------------------------
750 : bool
751 0 : nsListControlFrame::SingleSelection(PRInt32 aClickedIndex, bool aDoToggle)
752 : {
753 0 : if (mComboboxFrame) {
754 0 : mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
755 : }
756 :
757 0 : bool wasChanged = false;
758 : // Get Current selection
759 0 : if (aDoToggle) {
760 0 : wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
761 : } else {
762 : wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
763 0 : true, true);
764 : }
765 0 : ScrollToIndex(aClickedIndex);
766 :
767 : #ifdef ACCESSIBILITY
768 0 : bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
769 : #endif
770 0 : mStartSelectionIndex = aClickedIndex;
771 0 : mEndSelectionIndex = aClickedIndex;
772 0 : InvalidateFocus();
773 :
774 : #ifdef ACCESSIBILITY
775 0 : if (isCurrentOptionChanged) {
776 0 : FireMenuItemActiveEvent();
777 : }
778 : #endif
779 :
780 0 : return wasChanged;
781 : }
782 :
783 : void
784 0 : nsListControlFrame::InitSelectionRange(PRInt32 aClickedIndex)
785 : {
786 : //
787 : // If nothing is selected, set the start selection depending on where
788 : // the user clicked and what the initial selection is:
789 : // - if the user clicked *before* selectedIndex, set the start index to
790 : // the end of the first contiguous selection.
791 : // - if the user clicked *after* the end of the first contiguous
792 : // selection, set the start index to selectedIndex.
793 : // - if the user clicked *within* the first contiguous selection, set the
794 : // start index to selectedIndex.
795 : // The last two rules, of course, boil down to the same thing: if the user
796 : // clicked >= selectedIndex, return selectedIndex.
797 : //
798 : // This makes it so that shift click works properly when you first click
799 : // in a multiple select.
800 : //
801 0 : PRInt32 selectedIndex = GetSelectedIndex();
802 0 : if (selectedIndex >= 0) {
803 : // Get the end of the contiguous selection
804 0 : nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
805 0 : NS_ASSERTION(options, "Collection of options is null!");
806 : PRUint32 numOptions;
807 0 : options->GetLength(&numOptions);
808 : PRUint32 i;
809 : // Push i to one past the last selected index in the group
810 0 : for (i=selectedIndex+1; i < numOptions; i++) {
811 : bool selected;
812 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option = GetOption(options, i);
813 0 : option->GetSelected(&selected);
814 0 : if (!selected) {
815 : break;
816 : }
817 : }
818 :
819 0 : if (aClickedIndex < selectedIndex) {
820 : // User clicked before selection, so start selection at end of
821 : // contiguous selection
822 0 : mStartSelectionIndex = i-1;
823 0 : mEndSelectionIndex = selectedIndex;
824 : } else {
825 : // User clicked after selection, so start selection at start of
826 : // contiguous selection
827 0 : mStartSelectionIndex = selectedIndex;
828 0 : mEndSelectionIndex = i-1;
829 : }
830 : }
831 0 : }
832 :
833 : //---------------------------------------------------------
834 : bool
835 0 : nsListControlFrame::PerformSelection(PRInt32 aClickedIndex,
836 : bool aIsShift,
837 : bool aIsControl)
838 : {
839 0 : bool wasChanged = false;
840 :
841 0 : if (aClickedIndex == kNothingSelected) {
842 : }
843 0 : else if (GetMultiple()) {
844 0 : if (aIsShift) {
845 : // Make sure shift+click actually does something expected when
846 : // the user has never clicked on the select
847 0 : if (mStartSelectionIndex == kNothingSelected) {
848 0 : InitSelectionRange(aClickedIndex);
849 : }
850 :
851 : // Get the range from beginning (low) to end (high)
852 : // Shift *always* works, even if the current option is disabled
853 : PRInt32 startIndex;
854 : PRInt32 endIndex;
855 0 : if (mStartSelectionIndex == kNothingSelected) {
856 0 : startIndex = aClickedIndex;
857 0 : endIndex = aClickedIndex;
858 0 : } else if (mStartSelectionIndex <= aClickedIndex) {
859 0 : startIndex = mStartSelectionIndex;
860 0 : endIndex = aClickedIndex;
861 : } else {
862 0 : startIndex = aClickedIndex;
863 0 : endIndex = mStartSelectionIndex;
864 : }
865 :
866 : // Clear only if control was not pressed
867 0 : wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
868 0 : ScrollToIndex(aClickedIndex);
869 :
870 0 : if (mStartSelectionIndex == kNothingSelected) {
871 0 : mStartSelectionIndex = aClickedIndex;
872 : }
873 : #ifdef ACCESSIBILITY
874 0 : bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
875 : #endif
876 0 : mEndSelectionIndex = aClickedIndex;
877 0 : InvalidateFocus();
878 :
879 : #ifdef ACCESSIBILITY
880 0 : if (isCurrentOptionChanged) {
881 0 : FireMenuItemActiveEvent();
882 : }
883 : #endif
884 0 : } else if (aIsControl) {
885 0 : wasChanged = SingleSelection(aClickedIndex, true);
886 : } else {
887 0 : wasChanged = SingleSelection(aClickedIndex, false);
888 : }
889 : } else {
890 0 : wasChanged = SingleSelection(aClickedIndex, false);
891 : }
892 :
893 0 : return wasChanged;
894 : }
895 :
896 : //---------------------------------------------------------
897 : bool
898 0 : nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
899 : PRInt32 aClickedIndex)
900 : {
901 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
902 : bool isShift;
903 : bool isControl;
904 : #ifdef XP_MACOSX
905 : mouseEvent->GetMetaKey(&isControl);
906 : #else
907 0 : mouseEvent->GetCtrlKey(&isControl);
908 : #endif
909 0 : mouseEvent->GetShiftKey(&isShift);
910 0 : return PerformSelection(aClickedIndex, isShift, isControl);
911 : }
912 :
913 : //---------------------------------------------------------
914 : void
915 0 : nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents)
916 : {
917 : // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
918 : // so we never want to do mouse capturing. Note that we only bail if the list
919 : // is in drop-down mode, and the caller is requesting capture (we let release capture
920 : // requests go through to ensure that we can release capture requested via other
921 : // code paths, if any exist).
922 0 : if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
923 0 : return;
924 :
925 0 : if (aGrabMouseEvents) {
926 0 : nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
927 : } else {
928 0 : nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
929 :
930 0 : bool dropDownIsHidden = false;
931 0 : if (IsInDropDownMode()) {
932 0 : dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
933 : }
934 0 : if (capturingContent == mContent || dropDownIsHidden) {
935 : // only clear the capturing content if *we* are the ones doing the
936 : // capturing (or if the dropdown is hidden, in which case NO-ONE should
937 : // be capturing anything - it could be a scrollbar inside this listbox
938 : // which is actually grabbing
939 : // This shouldn't be necessary. We should simply ensure that events targeting
940 : // scrollbars are never visible to DOM consumers.
941 0 : nsIPresShell::SetCapturingContent(nsnull, 0);
942 : }
943 : }
944 : }
945 :
946 : //---------------------------------------------------------
947 : NS_IMETHODIMP
948 0 : nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
949 : nsGUIEvent* aEvent,
950 : nsEventStatus* aEventStatus)
951 : {
952 0 : NS_ENSURE_ARG_POINTER(aEventStatus);
953 :
954 : /*const char * desc[] = {"NS_MOUSE_MOVE",
955 : "NS_MOUSE_LEFT_BUTTON_UP",
956 : "NS_MOUSE_LEFT_BUTTON_DOWN",
957 : "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
958 : "NS_MOUSE_MIDDLE_BUTTON_UP",
959 : "NS_MOUSE_MIDDLE_BUTTON_DOWN",
960 : "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
961 : "NS_MOUSE_RIGHT_BUTTON_UP",
962 : "NS_MOUSE_RIGHT_BUTTON_DOWN",
963 : "NS_MOUSE_ENTER_SYNTH",
964 : "NS_MOUSE_EXIT_SYNTH",
965 : "NS_MOUSE_LEFT_DOUBLECLICK",
966 : "NS_MOUSE_MIDDLE_DOUBLECLICK",
967 : "NS_MOUSE_RIGHT_DOUBLECLICK",
968 : "NS_MOUSE_LEFT_CLICK",
969 : "NS_MOUSE_MIDDLE_CLICK",
970 : "NS_MOUSE_RIGHT_CLICK"};
971 : int inx = aEvent->message-NS_MOUSE_MESSAGE_START;
972 : if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) {
973 : printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message);
974 : } else {
975 : printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
976 : }*/
977 :
978 0 : if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
979 0 : return NS_OK;
980 :
981 : // do we have style that affects how we are selected?
982 : // do we have user-input style?
983 0 : const nsStyleUserInterface* uiStyle = GetStyleUserInterface();
984 0 : if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
985 0 : return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
986 :
987 0 : nsEventStates eventStates = mContent->AsElement()->State();
988 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED))
989 0 : return NS_OK;
990 :
991 0 : return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
992 : }
993 :
994 :
995 : //---------------------------------------------------------
996 : NS_IMETHODIMP
997 0 : nsListControlFrame::SetInitialChildList(ChildListID aListID,
998 : nsFrameList& aChildList)
999 : {
1000 : // First check to see if all the content has been added
1001 0 : mIsAllContentHere = mContent->IsDoneAddingChildren();
1002 0 : if (!mIsAllContentHere) {
1003 0 : mIsAllFramesHere = false;
1004 0 : mHasBeenInitialized = false;
1005 : }
1006 0 : nsresult rv = nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList);
1007 :
1008 : // If all the content is here now check
1009 : // to see if all the frames have been created
1010 : /*if (mIsAllContentHere) {
1011 : // If all content and frames are here
1012 : // the reset/initialize
1013 : if (CheckIfAllFramesHere()) {
1014 : ResetList(aPresContext);
1015 : mHasBeenInitialized = true;
1016 : }
1017 : }*/
1018 :
1019 0 : return rv;
1020 : }
1021 :
1022 : //---------------------------------------------------------
1023 : nsresult
1024 0 : nsListControlFrame::GetSizeAttribute(PRInt32 *aSize) {
1025 0 : nsresult rv = NS_OK;
1026 : nsIDOMHTMLSelectElement* selectElement;
1027 0 : rv = mContent->QueryInterface(NS_GET_IID(nsIDOMHTMLSelectElement),(void**) &selectElement);
1028 0 : if (mContent && NS_SUCCEEDED(rv)) {
1029 0 : rv = selectElement->GetSize(aSize);
1030 0 : NS_RELEASE(selectElement);
1031 : }
1032 0 : return rv;
1033 : }
1034 :
1035 :
1036 : //---------------------------------------------------------
1037 : NS_IMETHODIMP
1038 0 : nsListControlFrame::Init(nsIContent* aContent,
1039 : nsIFrame* aParent,
1040 : nsIFrame* aPrevInFlow)
1041 : {
1042 0 : nsresult result = nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow);
1043 :
1044 : // get the receiver interface from the browser button's content node
1045 0 : NS_ENSURE_STATE(mContent);
1046 :
1047 : // we shouldn't have to unregister this listener because when
1048 : // our frame goes away all these content node go away as well
1049 : // because our frame is the only one who references them.
1050 : // we need to hook up our listeners before the editor is initialized
1051 0 : mEventListener = new nsListEventListener(this);
1052 0 : if (!mEventListener)
1053 0 : return NS_ERROR_OUT_OF_MEMORY;
1054 :
1055 0 : mContent->AddEventListener(NS_LITERAL_STRING("keypress"), mEventListener,
1056 0 : false, false);
1057 0 : mContent->AddEventListener(NS_LITERAL_STRING("mousedown"), mEventListener,
1058 0 : false, false);
1059 0 : mContent->AddEventListener(NS_LITERAL_STRING("mouseup"), mEventListener,
1060 0 : false, false);
1061 0 : mContent->AddEventListener(NS_LITERAL_STRING("mousemove"), mEventListener,
1062 0 : false, false);
1063 :
1064 0 : mStartSelectionIndex = kNothingSelected;
1065 0 : mEndSelectionIndex = kNothingSelected;
1066 :
1067 0 : mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
1068 :
1069 0 : return result;
1070 : }
1071 :
1072 : already_AddRefed<nsIContent>
1073 0 : nsListControlFrame::GetOptionAsContent(nsIDOMHTMLOptionsCollection* aCollection, PRInt32 aIndex)
1074 : {
1075 0 : nsIContent * content = nsnull;
1076 : nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = GetOption(aCollection,
1077 0 : aIndex);
1078 :
1079 0 : NS_ASSERTION(optionElement != nsnull, "could not get option element by index!");
1080 :
1081 0 : if (optionElement) {
1082 0 : CallQueryInterface(optionElement, &content);
1083 : }
1084 :
1085 0 : return content;
1086 : }
1087 :
1088 : already_AddRefed<nsIContent>
1089 0 : nsListControlFrame::GetOptionContent(PRInt32 aIndex) const
1090 :
1091 : {
1092 0 : nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1093 0 : NS_ASSERTION(options.get() != nsnull, "Collection of options is null!");
1094 :
1095 0 : if (options) {
1096 0 : return GetOptionAsContent(options, aIndex);
1097 : }
1098 0 : return nsnull;
1099 : }
1100 :
1101 : already_AddRefed<nsIDOMHTMLOptionsCollection>
1102 0 : nsListControlFrame::GetOptions(nsIContent * aContent)
1103 : {
1104 0 : nsIDOMHTMLOptionsCollection* options = nsnull;
1105 0 : nsCOMPtr<nsIDOMHTMLSelectElement> selectElement = do_QueryInterface(aContent);
1106 0 : if (selectElement) {
1107 0 : selectElement->GetOptions(&options); // AddRefs (1)
1108 : }
1109 :
1110 0 : return options;
1111 : }
1112 :
1113 : already_AddRefed<nsIDOMHTMLOptionElement>
1114 0 : nsListControlFrame::GetOption(nsIDOMHTMLOptionsCollection* aCollection,
1115 : PRInt32 aIndex)
1116 : {
1117 0 : nsCOMPtr<nsIDOMNode> node;
1118 0 : if (NS_SUCCEEDED(aCollection->Item(aIndex, getter_AddRefs(node)))) {
1119 0 : NS_ASSERTION(node,
1120 : "Item was successful, but node from collection was null!");
1121 0 : if (node) {
1122 0 : nsIDOMHTMLOptionElement* option = nsnull;
1123 0 : CallQueryInterface(node, &option);
1124 :
1125 0 : return option;
1126 : }
1127 : } else {
1128 0 : NS_ERROR("Couldn't get option by index from collection!");
1129 : }
1130 0 : return nsnull;
1131 : }
1132 :
1133 : bool
1134 0 : nsListControlFrame::IsContentSelected(nsIContent* aContent) const
1135 : {
1136 0 : bool isSelected = false;
1137 :
1138 0 : nsCOMPtr<nsIDOMHTMLOptionElement> optEl = do_QueryInterface(aContent);
1139 0 : if (optEl)
1140 0 : optEl->GetSelected(&isSelected);
1141 :
1142 0 : return isSelected;
1143 : }
1144 :
1145 : bool
1146 0 : nsListControlFrame::IsContentSelectedByIndex(PRInt32 aIndex) const
1147 : {
1148 0 : nsCOMPtr<nsIContent> content = GetOptionContent(aIndex);
1149 0 : NS_ASSERTION(content, "Failed to retrieve option content");
1150 :
1151 0 : return IsContentSelected(content);
1152 : }
1153 :
1154 : NS_IMETHODIMP
1155 0 : nsListControlFrame::OnOptionSelected(PRInt32 aIndex, bool aSelected)
1156 : {
1157 0 : if (aSelected) {
1158 0 : ScrollToIndex(aIndex);
1159 : }
1160 0 : return NS_OK;
1161 : }
1162 :
1163 : PRIntn
1164 0 : nsListControlFrame::GetSkipSides() const
1165 : {
1166 : // Don't skip any sides during border rendering
1167 0 : return 0;
1168 : }
1169 :
1170 : void
1171 0 : nsListControlFrame::OnContentReset()
1172 : {
1173 0 : ResetList(true);
1174 0 : }
1175 :
1176 : void
1177 0 : nsListControlFrame::ResetList(bool aAllowScrolling,
1178 : const nsHTMLReflowState *aReflowState)
1179 : {
1180 : // if all the frames aren't here
1181 : // don't bother reseting
1182 0 : if (!mIsAllFramesHere) {
1183 0 : return;
1184 : }
1185 :
1186 0 : if (aAllowScrolling) {
1187 0 : mPostChildrenLoadedReset = true;
1188 :
1189 : // Scroll to the selected index
1190 0 : PRInt32 indexToSelect = kNothingSelected;
1191 :
1192 0 : nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
1193 0 : NS_ASSERTION(selectElement, "No select element!");
1194 0 : if (selectElement) {
1195 0 : selectElement->GetSelectedIndex(&indexToSelect);
1196 0 : ScrollToIndex(indexToSelect);
1197 : }
1198 : }
1199 :
1200 0 : mStartSelectionIndex = kNothingSelected;
1201 0 : mEndSelectionIndex = kNothingSelected;
1202 0 : InvalidateFocus(aReflowState);
1203 : // Combobox will redisplay itself with the OnOptionSelected event
1204 : }
1205 :
1206 : void
1207 0 : nsListControlFrame::SetFocus(bool aOn, bool aRepaint)
1208 : {
1209 0 : InvalidateFocus();
1210 :
1211 0 : if (aOn) {
1212 0 : ComboboxFocusSet();
1213 0 : mFocused = this;
1214 : } else {
1215 0 : mFocused = nsnull;
1216 : }
1217 :
1218 0 : InvalidateFocus();
1219 0 : }
1220 :
1221 0 : void nsListControlFrame::ComboboxFocusSet()
1222 : {
1223 0 : gLastKeyTime = 0;
1224 0 : }
1225 :
1226 : void
1227 0 : nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
1228 : {
1229 0 : if (nsnull != aComboboxFrame) {
1230 0 : mComboboxFrame = do_QueryFrame(aComboboxFrame);
1231 : }
1232 0 : }
1233 :
1234 : void
1235 0 : nsListControlFrame::GetOptionText(PRInt32 aIndex, nsAString & aStr)
1236 : {
1237 0 : aStr.SetLength(0);
1238 0 : nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1239 :
1240 0 : if (options) {
1241 : PRUint32 numOptions;
1242 0 : options->GetLength(&numOptions);
1243 :
1244 0 : if (numOptions != 0) {
1245 : nsCOMPtr<nsIDOMHTMLOptionElement> optionElement =
1246 0 : GetOption(options, aIndex);
1247 0 : if (optionElement) {
1248 : #if 0 // This is for turning off labels Bug 4050
1249 : nsAutoString text;
1250 : optionElement->GetLabel(text);
1251 : // the return value is always NS_OK from DOMElements
1252 : // it is meaningless to check for it
1253 : if (!text.IsEmpty()) {
1254 : nsAutoString compressText = text;
1255 : compressText.CompressWhitespace(true, true);
1256 : if (!compressText.IsEmpty()) {
1257 : text = compressText;
1258 : }
1259 : }
1260 :
1261 : if (text.IsEmpty()) {
1262 : // the return value is always NS_OK from DOMElements
1263 : // it is meaningless to check for it
1264 : optionElement->GetText(text);
1265 : }
1266 : aStr = text;
1267 : #else
1268 0 : optionElement->GetText(aStr);
1269 : #endif
1270 : }
1271 : }
1272 : }
1273 0 : }
1274 :
1275 : PRInt32
1276 0 : nsListControlFrame::GetSelectedIndex()
1277 : {
1278 : PRInt32 aIndex;
1279 :
1280 0 : nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
1281 0 : selectElement->GetSelectedIndex(&aIndex);
1282 :
1283 0 : return aIndex;
1284 : }
1285 :
1286 : already_AddRefed<nsIContent>
1287 0 : nsListControlFrame::GetCurrentOption()
1288 : {
1289 : // The mEndSelectionIndex is what is currently being selected. Use
1290 : // the selected index if this is kNothingSelected.
1291 : PRInt32 focusedIndex = (mEndSelectionIndex == kNothingSelected) ?
1292 0 : GetSelectedIndex() : mEndSelectionIndex;
1293 :
1294 0 : if (focusedIndex != kNothingSelected) {
1295 0 : return GetOptionContent(focusedIndex);
1296 : }
1297 :
1298 : nsRefPtr<nsHTMLSelectElement> selectElement =
1299 0 : nsHTMLSelectElement::FromContent(mContent);
1300 0 : NS_ASSERTION(selectElement, "Can't be null");
1301 :
1302 : // There is no a selected item return the first non-disabled item and skip all
1303 : // the option group elements.
1304 0 : nsCOMPtr<nsIDOMNode> node;
1305 :
1306 : PRUint32 length;
1307 0 : selectElement->GetLength(&length);
1308 0 : if (length) {
1309 0 : bool isDisabled = true;
1310 0 : for (PRUint32 i = 0; i < length && isDisabled; i++) {
1311 0 : if (NS_FAILED(selectElement->Item(i, getter_AddRefs(node))) || !node) {
1312 0 : break;
1313 : }
1314 0 : if (NS_FAILED(selectElement->IsOptionDisabled(i, &isDisabled))) {
1315 0 : break;
1316 : }
1317 0 : if (isDisabled) {
1318 0 : node = nsnull;
1319 : } else {
1320 0 : break;
1321 : }
1322 : }
1323 0 : if (!node) {
1324 0 : return nsnull;
1325 : }
1326 : }
1327 :
1328 0 : if (node) {
1329 0 : nsCOMPtr<nsIContent> focusedOption = do_QueryInterface(node);
1330 0 : return focusedOption.forget();
1331 : }
1332 0 : return nsnull;
1333 : }
1334 :
1335 : bool
1336 0 : nsListControlFrame::IsInDropDownMode() const
1337 : {
1338 0 : return (mComboboxFrame != nsnull);
1339 : }
1340 :
1341 : PRInt32
1342 0 : nsListControlFrame::GetNumberOfOptions()
1343 : {
1344 0 : if (mContent != nsnull) {
1345 0 : nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1346 :
1347 0 : if (!options) {
1348 0 : return 0;
1349 : } else {
1350 0 : PRUint32 length = 0;
1351 0 : options->GetLength(&length);
1352 0 : return (PRInt32)length;
1353 : }
1354 : }
1355 0 : return 0;
1356 : }
1357 :
1358 : //----------------------------------------------------------------------
1359 : // nsISelectControlFrame
1360 : //----------------------------------------------------------------------
1361 0 : bool nsListControlFrame::CheckIfAllFramesHere()
1362 : {
1363 : // Get the number of optgroups and options
1364 : //PRInt32 numContentItems = 0;
1365 0 : nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
1366 0 : if (node) {
1367 : // XXX Need to find a fail proff way to determine that
1368 : // all the frames are there
1369 0 : mIsAllFramesHere = true;//NS_OK == CountAllChild(node, numContentItems);
1370 : }
1371 : // now make sure we have a frame each piece of content
1372 :
1373 0 : return mIsAllFramesHere;
1374 : }
1375 :
1376 : NS_IMETHODIMP
1377 0 : nsListControlFrame::DoneAddingChildren(bool aIsDone)
1378 : {
1379 0 : mIsAllContentHere = aIsDone;
1380 0 : if (mIsAllContentHere) {
1381 : // Here we check to see if all the frames have been created
1382 : // for all the content.
1383 : // If so, then we can initialize;
1384 0 : if (!mIsAllFramesHere) {
1385 : // if all the frames are now present we can initialize
1386 0 : if (CheckIfAllFramesHere()) {
1387 0 : mHasBeenInitialized = true;
1388 0 : ResetList(true);
1389 : }
1390 : }
1391 : }
1392 0 : return NS_OK;
1393 : }
1394 :
1395 : NS_IMETHODIMP
1396 0 : nsListControlFrame::AddOption(PRInt32 aIndex)
1397 : {
1398 : #ifdef DO_REFLOW_DEBUG
1399 : printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
1400 : #endif
1401 :
1402 0 : if (!mIsAllContentHere) {
1403 0 : mIsAllContentHere = mContent->IsDoneAddingChildren();
1404 0 : if (!mIsAllContentHere) {
1405 0 : mIsAllFramesHere = false;
1406 0 : mHasBeenInitialized = false;
1407 : } else {
1408 0 : mIsAllFramesHere = (aIndex == GetNumberOfOptions()-1);
1409 : }
1410 : }
1411 :
1412 : // Make sure we scroll to the selected option as needed
1413 0 : mNeedToReset = true;
1414 :
1415 0 : if (!mHasBeenInitialized) {
1416 0 : return NS_OK;
1417 : }
1418 :
1419 0 : mPostChildrenLoadedReset = mIsAllContentHere;
1420 0 : return NS_OK;
1421 : }
1422 :
1423 : static PRInt32
1424 0 : DecrementAndClamp(PRInt32 aSelectionIndex, PRInt32 aLength)
1425 : {
1426 0 : return aLength == 0 ? kNothingSelected : NS_MAX(0, aSelectionIndex - 1);
1427 : }
1428 :
1429 : NS_IMETHODIMP
1430 0 : nsListControlFrame::RemoveOption(PRInt32 aIndex)
1431 : {
1432 0 : NS_PRECONDITION(aIndex >= 0, "negative <option> index");
1433 :
1434 : // Need to reset if we're a dropdown
1435 0 : if (IsInDropDownMode()) {
1436 0 : mNeedToReset = true;
1437 0 : mPostChildrenLoadedReset = mIsAllContentHere;
1438 : }
1439 :
1440 0 : if (mStartSelectionIndex != kNothingSelected) {
1441 0 : NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
1442 0 : PRInt32 numOptions = GetNumberOfOptions();
1443 : // NOTE: numOptions is the new number of options whereas aIndex is the
1444 : // unadjusted index of the removed option (hence the <= below).
1445 0 : NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
1446 :
1447 0 : PRInt32 forward = mEndSelectionIndex - mStartSelectionIndex;
1448 0 : PRInt32* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
1449 0 : PRInt32* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
1450 0 : if (aIndex < *low)
1451 0 : *low = ::DecrementAndClamp(*low, numOptions);
1452 0 : if (aIndex <= *high)
1453 0 : *high = ::DecrementAndClamp(*high, numOptions);
1454 0 : if (forward == 0)
1455 0 : *low = *high;
1456 : }
1457 : else
1458 0 : NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
1459 :
1460 0 : InvalidateFocus();
1461 0 : return NS_OK;
1462 : }
1463 :
1464 : //---------------------------------------------------------
1465 : // Set the option selected in the DOM. This method is named
1466 : // as it is because it indicates that the frame is the source
1467 : // of this event rather than the receiver.
1468 : bool
1469 0 : nsListControlFrame::SetOptionsSelectedFromFrame(PRInt32 aStartIndex,
1470 : PRInt32 aEndIndex,
1471 : bool aValue,
1472 : bool aClearAll)
1473 : {
1474 : nsRefPtr<nsHTMLSelectElement> selectElement =
1475 0 : nsHTMLSelectElement::FromContent(mContent);
1476 0 : bool wasChanged = false;
1477 : #ifdef DEBUG
1478 : nsresult rv =
1479 : #endif
1480 0 : selectElement->SetOptionsSelectedByIndex(aStartIndex,
1481 : aEndIndex,
1482 : aValue,
1483 : aClearAll,
1484 : false,
1485 : true,
1486 0 : &wasChanged);
1487 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "SetSelected failed");
1488 0 : return wasChanged;
1489 : }
1490 :
1491 : bool
1492 0 : nsListControlFrame::ToggleOptionSelectedFromFrame(PRInt32 aIndex)
1493 : {
1494 0 : nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
1495 0 : NS_ASSERTION(options, "No options");
1496 0 : if (!options) {
1497 0 : return false;
1498 : }
1499 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option = GetOption(options, aIndex);
1500 0 : NS_ASSERTION(option, "No option");
1501 0 : if (!option) {
1502 0 : return false;
1503 : }
1504 :
1505 0 : bool value = false;
1506 : #ifdef DEBUG
1507 : nsresult rv =
1508 : #endif
1509 0 : option->GetSelected(&value);
1510 :
1511 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "GetSelected failed");
1512 : nsRefPtr<nsHTMLSelectElement> selectElement =
1513 0 : nsHTMLSelectElement::FromContent(mContent);
1514 0 : bool wasChanged = false;
1515 : #ifdef DEBUG
1516 : rv =
1517 : #endif
1518 0 : selectElement->SetOptionsSelectedByIndex(aIndex,
1519 : aIndex,
1520 0 : !value,
1521 : false,
1522 : false,
1523 : true,
1524 0 : &wasChanged);
1525 :
1526 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "SetSelected failed");
1527 :
1528 0 : return wasChanged;
1529 : }
1530 :
1531 :
1532 : // Dispatch event and such
1533 : bool
1534 0 : nsListControlFrame::UpdateSelection()
1535 : {
1536 0 : if (mIsAllFramesHere) {
1537 : // if it's a combobox, display the new text
1538 0 : nsWeakFrame weakFrame(this);
1539 0 : if (mComboboxFrame) {
1540 0 : mComboboxFrame->RedisplaySelectedText();
1541 : }
1542 : // if it's a listbox, fire on change
1543 0 : else if (mIsAllContentHere) {
1544 0 : FireOnChange();
1545 : }
1546 0 : return weakFrame.IsAlive();
1547 : }
1548 0 : return true;
1549 : }
1550 :
1551 : void
1552 0 : nsListControlFrame::ComboboxFinish(PRInt32 aIndex)
1553 : {
1554 0 : gLastKeyTime = 0;
1555 :
1556 0 : if (mComboboxFrame) {
1557 0 : PerformSelection(aIndex, false, false);
1558 :
1559 0 : PRInt32 displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
1560 :
1561 0 : nsWeakFrame weakFrame(this);
1562 :
1563 0 : if (displayIndex != aIndex) {
1564 0 : mComboboxFrame->RedisplaySelectedText(); // might destroy us
1565 : }
1566 :
1567 0 : if (weakFrame.IsAlive() && mComboboxFrame) {
1568 0 : mComboboxFrame->RollupFromList(); // might destroy us
1569 : }
1570 : }
1571 0 : }
1572 :
1573 : // Send out an onchange notification.
1574 : void
1575 0 : nsListControlFrame::FireOnChange()
1576 : {
1577 0 : if (mComboboxFrame) {
1578 : // Return hit without changing anything
1579 0 : PRInt32 index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
1580 0 : if (index == NS_SKIP_NOTIFY_INDEX)
1581 0 : return;
1582 :
1583 : // See if the selection actually changed
1584 0 : if (index == GetSelectedIndex())
1585 0 : return;
1586 : }
1587 :
1588 : // Dispatch the change event.
1589 : nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent,
1590 0 : NS_LITERAL_STRING("change"), true,
1591 0 : false);
1592 : }
1593 :
1594 : NS_IMETHODIMP
1595 0 : nsListControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex, PRInt32 aNewIndex)
1596 : {
1597 0 : if (mComboboxFrame) {
1598 : // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
1599 : // event for this setting of selectedIndex.
1600 0 : mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
1601 : }
1602 :
1603 0 : ScrollToIndex(aNewIndex);
1604 0 : mStartSelectionIndex = aNewIndex;
1605 0 : mEndSelectionIndex = aNewIndex;
1606 0 : InvalidateFocus();
1607 :
1608 : #ifdef ACCESSIBILITY
1609 0 : FireMenuItemActiveEvent();
1610 : #endif
1611 :
1612 0 : return NS_OK;
1613 : }
1614 :
1615 : //----------------------------------------------------------------------
1616 : // End nsISelectControlFrame
1617 : //----------------------------------------------------------------------
1618 :
1619 : nsresult
1620 0 : nsListControlFrame::SetFormProperty(nsIAtom* aName,
1621 : const nsAString& aValue)
1622 : {
1623 0 : if (nsGkAtoms::selected == aName) {
1624 0 : return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
1625 0 : } else if (nsGkAtoms::selectedindex == aName) {
1626 : // You shouldn't be calling me for this!!!
1627 0 : return NS_ERROR_INVALID_ARG;
1628 : }
1629 :
1630 : // We should be told about selectedIndex by the DOM element through
1631 : // OnOptionSelected
1632 :
1633 0 : return NS_OK;
1634 : }
1635 :
1636 : nsresult
1637 0 : nsListControlFrame::GetFormProperty(nsIAtom* aName, nsAString& aValue) const
1638 : {
1639 : // Get the selected value of option from local cache (optimization vs. widget)
1640 0 : if (nsGkAtoms::selected == aName) {
1641 0 : nsAutoString val(aValue);
1642 0 : PRInt32 error = 0;
1643 0 : bool selected = false;
1644 0 : PRInt32 indx = val.ToInteger(&error, 10); // Get index from aValue
1645 0 : if (error == 0)
1646 0 : selected = IsContentSelectedByIndex(indx);
1647 :
1648 0 : aValue.Assign(selected ? NS_LITERAL_STRING("1") : NS_LITERAL_STRING("0"));
1649 :
1650 : // For selectedIndex, get the value from the widget
1651 0 : } else if (nsGkAtoms::selectedindex == aName) {
1652 : // You shouldn't be calling me for this!!!
1653 0 : return NS_ERROR_INVALID_ARG;
1654 : }
1655 :
1656 0 : return NS_OK;
1657 : }
1658 :
1659 : void
1660 0 : nsListControlFrame::SyncViewWithFrame()
1661 : {
1662 : // Resync the view's position with the frame.
1663 : // The problem is the dropdown's view is attached directly under
1664 : // the root view. This means its view needs to have its coordinates calculated
1665 : // as if it were in it's normal position in the view hierarchy.
1666 0 : mComboboxFrame->AbsolutelyPositionDropDown();
1667 :
1668 0 : nsContainerFrame::PositionFrameView(this);
1669 0 : }
1670 :
1671 : void
1672 0 : nsListControlFrame::AboutToDropDown()
1673 : {
1674 0 : NS_ASSERTION(IsInDropDownMode(),
1675 : "AboutToDropDown called without being in dropdown mode");
1676 :
1677 : // Our widget doesn't get invalidated on changes to the rest of the document,
1678 : // so compute and store this color at the start of a dropdown so we don't
1679 : // get weird painting behaviour.
1680 : // We start looking for backgrounds above the combobox frame to avoid
1681 : // duplicating the combobox frame's background and compose each background
1682 : // color we find underneath until we have an opaque color, or run out of
1683 : // backgrounds. We compose with the PresContext default background color,
1684 : // which is always opaque, in case we don't end up with an opaque color.
1685 : // This gives us a very poor approximation of translucency.
1686 0 : nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame);
1687 0 : nsStyleContext* context = comboboxFrame->GetStyleContext()->GetParent();
1688 0 : mLastDropdownBackstopColor = NS_RGBA(0,0,0,0);
1689 0 : while (NS_GET_A(mLastDropdownBackstopColor) < 255 && context) {
1690 : mLastDropdownBackstopColor =
1691 0 : NS_ComposeColors(context->GetStyleBackground()->mBackgroundColor,
1692 0 : mLastDropdownBackstopColor);
1693 0 : context = context->GetParent();
1694 : }
1695 : mLastDropdownBackstopColor =
1696 : NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
1697 0 : mLastDropdownBackstopColor);
1698 :
1699 0 : if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
1700 0 : ScrollToIndex(GetSelectedIndex());
1701 : #ifdef ACCESSIBILITY
1702 0 : FireMenuItemActiveEvent(); // Inform assistive tech what got focus
1703 : #endif
1704 : }
1705 0 : mItemSelectionStarted = false;
1706 0 : }
1707 :
1708 : // We are about to be rolledup from the outside (ComboboxFrame)
1709 : void
1710 0 : nsListControlFrame::AboutToRollup()
1711 : {
1712 : // We've been updating the combobox with the keyboard up until now, but not
1713 : // with the mouse. The problem is, even with mouse selection, we are
1714 : // updating the <select>. So if the mouse goes over an option just before
1715 : // he leaves the box and clicks, that's what the <select> will show.
1716 : //
1717 : // To deal with this we say "whatever is in the combobox is canonical."
1718 : // - IF the combobox is different from the current selected index, we
1719 : // reset the index.
1720 :
1721 0 : if (IsInDropDownMode()) {
1722 0 : ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
1723 : }
1724 0 : }
1725 :
1726 : NS_IMETHODIMP
1727 0 : nsListControlFrame::DidReflow(nsPresContext* aPresContext,
1728 : const nsHTMLReflowState* aReflowState,
1729 : nsDidReflowStatus aStatus)
1730 : {
1731 : nsresult rv;
1732 0 : bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
1733 0 : aPresContext->HasPendingInterrupt();
1734 :
1735 0 : if (IsInDropDownMode())
1736 : {
1737 : //SyncViewWithFrame();
1738 0 : rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus);
1739 0 : SyncViewWithFrame();
1740 : } else {
1741 0 : rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus);
1742 : }
1743 :
1744 0 : if (mNeedToReset && !wasInterrupted) {
1745 0 : mNeedToReset = false;
1746 : // Suppress scrolling to the selected element if we restored
1747 : // scroll history state AND the list contents have not changed
1748 : // since we loaded all the children AND nothing else forced us
1749 : // to scroll by calling ResetList(true). The latter two conditions
1750 : // are folded into mPostChildrenLoadedReset.
1751 : //
1752 : // The idea is that we want scroll history restoration to trump ResetList
1753 : // scrolling to the selected element, when the ResetList was probably only
1754 : // caused by content loading normally.
1755 0 : ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset, aReflowState);
1756 : }
1757 :
1758 0 : mHasPendingInterruptAtStartOfReflow = false;
1759 0 : return rv;
1760 : }
1761 :
1762 : nsIAtom*
1763 0 : nsListControlFrame::GetType() const
1764 : {
1765 0 : return nsGkAtoms::listControlFrame;
1766 : }
1767 :
1768 : void
1769 0 : nsListControlFrame::InvalidateInternal(const nsRect& aDamageRect,
1770 : nscoord aX, nscoord aY, nsIFrame* aForChild,
1771 : PRUint32 aFlags)
1772 : {
1773 0 : if (!IsInDropDownMode()) {
1774 0 : nsHTMLScrollFrame::InvalidateInternal(aDamageRect, aX, aY, this, aFlags);
1775 0 : return;
1776 : }
1777 0 : InvalidateRoot(aDamageRect + nsPoint(aX, aY), aFlags);
1778 : }
1779 :
1780 : #ifdef DEBUG
1781 : NS_IMETHODIMP
1782 0 : nsListControlFrame::GetFrameName(nsAString& aResult) const
1783 : {
1784 0 : return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
1785 : }
1786 : #endif
1787 :
1788 : nscoord
1789 0 : nsListControlFrame::GetHeightOfARow()
1790 : {
1791 0 : return HeightOfARow();
1792 : }
1793 :
1794 : nsresult
1795 0 : nsListControlFrame::IsOptionDisabled(PRInt32 anIndex, bool &aIsDisabled)
1796 : {
1797 : nsRefPtr<nsHTMLSelectElement> sel =
1798 0 : nsHTMLSelectElement::FromContent(mContent);
1799 0 : if (sel) {
1800 0 : sel->IsOptionDisabled(anIndex, &aIsDisabled);
1801 0 : return NS_OK;
1802 : }
1803 0 : return NS_ERROR_FAILURE;
1804 : }
1805 :
1806 : //----------------------------------------------------------------------
1807 : // helper
1808 : //----------------------------------------------------------------------
1809 : bool
1810 0 : nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
1811 : {
1812 : // only allow selection with the left button
1813 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1814 0 : if (mouseEvent) {
1815 : PRUint16 whichButton;
1816 0 : if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
1817 0 : return whichButton != 0?false:true;
1818 : }
1819 : }
1820 0 : return false;
1821 : }
1822 :
1823 : nscoord
1824 0 : nsListControlFrame::CalcFallbackRowHeight(float aFontSizeInflation)
1825 : {
1826 0 : nscoord rowHeight = 0;
1827 :
1828 0 : nsRefPtr<nsFontMetrics> fontMet;
1829 : nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet),
1830 0 : aFontSizeInflation);
1831 0 : if (fontMet) {
1832 0 : rowHeight = fontMet->MaxHeight();
1833 : }
1834 :
1835 0 : return rowHeight;
1836 : }
1837 :
1838 : nscoord
1839 0 : nsListControlFrame::CalcIntrinsicHeight(nscoord aHeightOfARow,
1840 : PRInt32 aNumberOfOptions)
1841 : {
1842 0 : NS_PRECONDITION(!IsInDropDownMode(),
1843 : "Shouldn't be in dropdown mode when we call this");
1844 :
1845 0 : mNumDisplayRows = 1;
1846 0 : GetSizeAttribute(&mNumDisplayRows);
1847 :
1848 0 : if (mNumDisplayRows < 1) {
1849 0 : mNumDisplayRows = 4;
1850 : }
1851 :
1852 0 : return mNumDisplayRows * aHeightOfARow;
1853 : }
1854 :
1855 : //----------------------------------------------------------------------
1856 : // nsIDOMMouseListener
1857 : //----------------------------------------------------------------------
1858 : nsresult
1859 0 : nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent)
1860 : {
1861 0 : NS_ASSERTION(aMouseEvent != nsnull, "aMouseEvent is null.");
1862 :
1863 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
1864 0 : NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
1865 :
1866 0 : UpdateInListState(aMouseEvent);
1867 :
1868 0 : mButtonDown = false;
1869 :
1870 0 : nsEventStates eventStates = mContent->AsElement()->State();
1871 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1872 0 : return NS_OK;
1873 : }
1874 :
1875 : // only allow selection with the left button
1876 : // if a right button click is on the combobox itself
1877 : // or on the select when in listbox mode, then let the click through
1878 0 : if (!IsLeftButton(aMouseEvent)) {
1879 0 : if (IsInDropDownMode()) {
1880 0 : if (!IgnoreMouseEventForSelection(aMouseEvent)) {
1881 0 : aMouseEvent->PreventDefault();
1882 0 : aMouseEvent->StopPropagation();
1883 : } else {
1884 0 : CaptureMouseEvents(false);
1885 0 : return NS_OK;
1886 : }
1887 0 : CaptureMouseEvents(false);
1888 0 : return NS_ERROR_FAILURE; // means consume event
1889 : } else {
1890 0 : CaptureMouseEvents(false);
1891 0 : return NS_OK;
1892 : }
1893 : }
1894 :
1895 0 : const nsStyleVisibility* vis = GetStyleVisibility();
1896 :
1897 0 : if (!vis->IsVisible()) {
1898 0 : return NS_OK;
1899 : }
1900 :
1901 0 : if (IsInDropDownMode()) {
1902 : // XXX This is a bit of a hack, but.....
1903 : // But the idea here is to make sure you get an "onclick" event when you mouse
1904 : // down on the select and the drag over an option and let go
1905 : // And then NOT get an "onclick" event when when you click down on the select
1906 : // and then up outside of the select
1907 : // the EventStateManager tracks the content of the mouse down and the mouse up
1908 : // to make sure they are the same, and the onclick is sent in the PostHandleEvent
1909 : // depeneding on whether the clickCount is non-zero.
1910 : // So we cheat here by either setting or unsetting the clcikCount in the native event
1911 : // so the right thing happens for the onclick event
1912 0 : nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(aMouseEvent));
1913 : nsMouseEvent * mouseEvent;
1914 0 : mouseEvent = (nsMouseEvent *) privateEvent->GetInternalNSEvent();
1915 :
1916 : PRInt32 selectedIndex;
1917 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
1918 : // If it's disabled, disallow the click and leave.
1919 0 : bool isDisabled = false;
1920 0 : IsOptionDisabled(selectedIndex, isDisabled);
1921 0 : if (isDisabled) {
1922 0 : aMouseEvent->PreventDefault();
1923 0 : aMouseEvent->StopPropagation();
1924 0 : CaptureMouseEvents(false);
1925 0 : return NS_ERROR_FAILURE;
1926 : }
1927 :
1928 0 : if (kNothingSelected != selectedIndex) {
1929 0 : nsWeakFrame weakFrame(this);
1930 0 : ComboboxFinish(selectedIndex);
1931 0 : if (!weakFrame.IsAlive())
1932 0 : return NS_OK;
1933 0 : FireOnChange();
1934 : }
1935 :
1936 0 : mouseEvent->clickCount = 1;
1937 : } else {
1938 : // the click was out side of the select or its dropdown
1939 0 : mouseEvent->clickCount = IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
1940 : }
1941 : } else {
1942 0 : CaptureMouseEvents(false);
1943 : // Notify
1944 0 : if (mChangesSinceDragStart) {
1945 : // reset this so that future MouseUps without a prior MouseDown
1946 : // won't fire onchange
1947 0 : mChangesSinceDragStart = false;
1948 0 : FireOnChange();
1949 : }
1950 : }
1951 :
1952 0 : return NS_OK;
1953 : }
1954 :
1955 : void
1956 0 : nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
1957 : {
1958 0 : if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown())
1959 0 : return;
1960 :
1961 0 : nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
1962 0 : nsRect borderInnerEdge = GetScrollPortRect();
1963 0 : if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
1964 0 : mItemSelectionStarted = true;
1965 : }
1966 : }
1967 :
1968 0 : bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent)
1969 : {
1970 0 : if (!mComboboxFrame)
1971 0 : return false;
1972 :
1973 : // Our DOM listener does get called when the dropdown is not
1974 : // showing, because it listens to events on the SELECT element
1975 0 : if (!mComboboxFrame->IsDroppedDown())
1976 0 : return true;
1977 :
1978 0 : return !mItemSelectionStarted;
1979 : }
1980 :
1981 : #ifdef ACCESSIBILITY
1982 : void
1983 0 : nsListControlFrame::FireMenuItemActiveEvent()
1984 : {
1985 0 : if (mFocused != this && !IsInDropDownMode()) {
1986 0 : return;
1987 : }
1988 :
1989 0 : nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
1990 0 : if (!optionContent) {
1991 : return;
1992 : }
1993 :
1994 0 : FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
1995 : }
1996 : #endif
1997 :
1998 : nsresult
1999 0 : nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent,
2000 : PRInt32& aCurIndex)
2001 : {
2002 0 : if (IgnoreMouseEventForSelection(aMouseEvent))
2003 0 : return NS_ERROR_FAILURE;
2004 :
2005 0 : if (nsIPresShell::GetCapturingContent() != mContent) {
2006 : // If we're not capturing, then ignore movement in the border
2007 0 : nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
2008 0 : nsRect borderInnerEdge = GetScrollPortRect();
2009 0 : if (!borderInnerEdge.Contains(pt)) {
2010 0 : return NS_ERROR_FAILURE;
2011 : }
2012 : }
2013 :
2014 : nsCOMPtr<nsIContent> content = PresContext()->EventStateManager()->
2015 0 : GetEventTargetContent(nsnull);
2016 :
2017 0 : nsCOMPtr<nsIContent> optionContent = GetOptionFromContent(content);
2018 0 : if (optionContent) {
2019 0 : aCurIndex = GetIndexFromContent(optionContent);
2020 0 : return NS_OK;
2021 : }
2022 :
2023 0 : PRInt32 numOptions = GetNumberOfOptions();
2024 0 : if (numOptions < 1)
2025 0 : return NS_ERROR_FAILURE;
2026 :
2027 0 : nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
2028 :
2029 : // If the event coordinate is above the first option frame, then target the
2030 : // first option frame
2031 0 : nsCOMPtr<nsIContent> firstOption = GetOptionContent(0);
2032 0 : NS_ASSERTION(firstOption, "Can't find first option that's supposed to be there");
2033 0 : nsIFrame* optionFrame = firstOption->GetPrimaryFrame();
2034 0 : if (optionFrame) {
2035 0 : nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
2036 0 : if (ptInOptionFrame.y < 0 && ptInOptionFrame.x >= 0 &&
2037 : ptInOptionFrame.x < optionFrame->GetSize().width) {
2038 0 : aCurIndex = 0;
2039 0 : return NS_OK;
2040 : }
2041 : }
2042 :
2043 0 : nsCOMPtr<nsIContent> lastOption = GetOptionContent(numOptions - 1);
2044 : // If the event coordinate is below the last option frame, then target the
2045 : // last option frame
2046 0 : NS_ASSERTION(lastOption, "Can't find last option that's supposed to be there");
2047 0 : optionFrame = lastOption->GetPrimaryFrame();
2048 0 : if (optionFrame) {
2049 0 : nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
2050 0 : if (ptInOptionFrame.y >= optionFrame->GetSize().height && ptInOptionFrame.x >= 0 &&
2051 : ptInOptionFrame.x < optionFrame->GetSize().width) {
2052 0 : aCurIndex = numOptions - 1;
2053 0 : return NS_OK;
2054 : }
2055 : }
2056 :
2057 0 : return NS_ERROR_FAILURE;
2058 : }
2059 :
2060 : nsresult
2061 0 : nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
2062 : {
2063 0 : NS_ASSERTION(aMouseEvent != nsnull, "aMouseEvent is null.");
2064 :
2065 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
2066 0 : NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
2067 :
2068 0 : UpdateInListState(aMouseEvent);
2069 :
2070 0 : nsEventStates eventStates = mContent->AsElement()->State();
2071 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
2072 0 : return NS_OK;
2073 : }
2074 :
2075 : // only allow selection with the left button
2076 : // if a right button click is on the combobox itself
2077 : // or on the select when in listbox mode, then let the click through
2078 0 : if (!IsLeftButton(aMouseEvent)) {
2079 0 : if (IsInDropDownMode()) {
2080 0 : if (!IgnoreMouseEventForSelection(aMouseEvent)) {
2081 0 : aMouseEvent->PreventDefault();
2082 0 : aMouseEvent->StopPropagation();
2083 : } else {
2084 0 : return NS_OK;
2085 : }
2086 0 : return NS_ERROR_FAILURE; // means consume event
2087 : } else {
2088 0 : return NS_OK;
2089 : }
2090 : }
2091 :
2092 : PRInt32 selectedIndex;
2093 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
2094 : // Handle Like List
2095 0 : mButtonDown = true;
2096 0 : CaptureMouseEvents(true);
2097 0 : mChangesSinceDragStart = HandleListSelection(aMouseEvent, selectedIndex);
2098 : } else {
2099 : // NOTE: the combo box is responsible for dropping it down
2100 0 : if (mComboboxFrame) {
2101 0 : if (!IgnoreMouseEventForSelection(aMouseEvent)) {
2102 0 : return NS_OK;
2103 : }
2104 :
2105 0 : if (!nsComboboxControlFrame::ToolkitHasNativePopup())
2106 : {
2107 0 : bool isDroppedDown = mComboboxFrame->IsDroppedDown();
2108 0 : nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame);
2109 0 : nsWeakFrame weakFrame(comboFrame);
2110 0 : mComboboxFrame->ShowDropDown(!isDroppedDown);
2111 0 : if (!weakFrame.IsAlive())
2112 0 : return NS_OK;
2113 0 : if (isDroppedDown) {
2114 0 : CaptureMouseEvents(false);
2115 : }
2116 : }
2117 : }
2118 : }
2119 :
2120 0 : return NS_OK;
2121 : }
2122 :
2123 : //----------------------------------------------------------------------
2124 : // nsIDOMMouseMotionListener
2125 : //----------------------------------------------------------------------
2126 : nsresult
2127 0 : nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent)
2128 : {
2129 0 : NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
2130 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
2131 0 : NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
2132 :
2133 0 : UpdateInListState(aMouseEvent);
2134 :
2135 0 : if (IsInDropDownMode()) {
2136 0 : if (mComboboxFrame->IsDroppedDown()) {
2137 : PRInt32 selectedIndex;
2138 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
2139 0 : PerformSelection(selectedIndex, false, false);
2140 : }
2141 : }
2142 : } else {// XXX - temporary until we get drag events
2143 0 : if (mButtonDown) {
2144 0 : return DragMove(aMouseEvent);
2145 : }
2146 : }
2147 0 : return NS_OK;
2148 : }
2149 :
2150 : nsresult
2151 0 : nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
2152 : {
2153 0 : NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
2154 :
2155 0 : UpdateInListState(aMouseEvent);
2156 :
2157 0 : if (!IsInDropDownMode()) {
2158 : PRInt32 selectedIndex;
2159 0 : if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
2160 : // Don't waste cycles if we already dragged over this item
2161 0 : if (selectedIndex == mEndSelectionIndex) {
2162 0 : return NS_OK;
2163 : }
2164 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
2165 0 : NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
2166 : bool isControl;
2167 : #ifdef XP_MACOSX
2168 : mouseEvent->GetMetaKey(&isControl);
2169 : #else
2170 0 : mouseEvent->GetCtrlKey(&isControl);
2171 : #endif
2172 : // Turn SHIFT on when you are dragging, unless control is on.
2173 : bool wasChanged = PerformSelection(selectedIndex,
2174 0 : !isControl, isControl);
2175 0 : mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
2176 : }
2177 : }
2178 0 : return NS_OK;
2179 : }
2180 :
2181 : //----------------------------------------------------------------------
2182 : // Scroll helpers.
2183 : //----------------------------------------------------------------------
2184 : nsresult
2185 0 : nsListControlFrame::ScrollToIndex(PRInt32 aIndex)
2186 : {
2187 0 : if (aIndex < 0) {
2188 : // XXX shouldn't we just do nothing if we're asked to scroll to
2189 : // kNothingSelected?
2190 0 : return ScrollToFrame(nsnull);
2191 : } else {
2192 0 : nsCOMPtr<nsIContent> content = GetOptionContent(aIndex);
2193 0 : if (content) {
2194 0 : return ScrollToFrame(content);
2195 : }
2196 : }
2197 :
2198 0 : return NS_ERROR_FAILURE;
2199 : }
2200 :
2201 : nsresult
2202 0 : nsListControlFrame::ScrollToFrame(nsIContent* aOptElement)
2203 : {
2204 : // if null is passed in we scroll to 0,0
2205 0 : if (nsnull == aOptElement) {
2206 0 : ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
2207 0 : return NS_OK;
2208 : }
2209 :
2210 : // otherwise we find the content's frame and scroll to it
2211 0 : nsIFrame *childFrame = aOptElement->GetPrimaryFrame();
2212 0 : if (childFrame) {
2213 0 : nsPoint pt = GetScrollPosition();
2214 : // get the scroll port rect relative to the scrolled frame
2215 0 : nsRect rect = GetScrollPortRect() + pt;
2216 : // get the option's rect relative to the scrolled frame
2217 0 : nsRect fRect(childFrame->GetOffsetTo(GetScrolledFrame()),
2218 0 : childFrame->GetSize());
2219 :
2220 : // See if the selected frame (fRect) is inside the scrollport
2221 : // area (rect). Check only the vertical dimension. Don't
2222 : // scroll just because there's horizontal overflow.
2223 0 : if (!(rect.y <= fRect.y && fRect.YMost() <= rect.YMost())) {
2224 : // figure out which direction we are going
2225 0 : if (fRect.YMost() > rect.YMost()) {
2226 0 : pt.y = fRect.y - (rect.height - fRect.height);
2227 : } else {
2228 0 : pt.y = fRect.y;
2229 : }
2230 0 : ScrollTo(nsPoint(fRect.x, pt.y), nsIScrollableFrame::INSTANT);
2231 : }
2232 : }
2233 0 : return NS_OK;
2234 : }
2235 :
2236 : //---------------------------------------------------------------------
2237 : // Ok, the entire idea of this routine is to move to the next item that
2238 : // is suppose to be selected. If the item is disabled then we search in
2239 : // the same direction looking for the next item to select. If we run off
2240 : // the end of the list then we start at the end of the list and search
2241 : // backwards until we get back to the original item or an enabled option
2242 : //
2243 : // aStartIndex - the index to start searching from
2244 : // aNewIndex - will get set to the new index if it finds one
2245 : // aNumOptions - the total number of options in the list
2246 : // aDoAdjustInc - the initial increment 1-n
2247 : // aDoAdjustIncNext - the increment used to search for the next enabled option
2248 : //
2249 : // the aDoAdjustInc could be a "1" for a single item or
2250 : // any number greater representing a page of items
2251 : //
2252 : void
2253 0 : nsListControlFrame::AdjustIndexForDisabledOpt(PRInt32 aStartIndex,
2254 : PRInt32 &aNewIndex,
2255 : PRInt32 aNumOptions,
2256 : PRInt32 aDoAdjustInc,
2257 : PRInt32 aDoAdjustIncNext)
2258 : {
2259 : // Cannot select anything if there is nothing to select
2260 0 : if (aNumOptions == 0) {
2261 0 : aNewIndex = kNothingSelected;
2262 0 : return;
2263 : }
2264 :
2265 : // means we reached the end of the list and now we are searching backwards
2266 0 : bool doingReverse = false;
2267 : // lowest index in the search range
2268 0 : PRInt32 bottom = 0;
2269 : // highest index in the search range
2270 0 : PRInt32 top = aNumOptions;
2271 :
2272 : // Start off keyboard options at selectedIndex if nothing else is defaulted to
2273 : //
2274 : // XXX Perhaps this should happen for mouse too, to start off shift click
2275 : // automatically in multiple ... to do this, we'd need to override
2276 : // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
2277 : // sure of the effects, though, so I'm not doing it just yet.
2278 0 : PRInt32 startIndex = aStartIndex;
2279 0 : if (startIndex < bottom) {
2280 0 : startIndex = GetSelectedIndex();
2281 : }
2282 0 : PRInt32 newIndex = startIndex + aDoAdjustInc;
2283 :
2284 : // make sure we start off in the range
2285 0 : if (newIndex < bottom) {
2286 0 : newIndex = 0;
2287 0 : } else if (newIndex >= top) {
2288 0 : newIndex = aNumOptions-1;
2289 : }
2290 :
2291 0 : while (1) {
2292 : // if the newIndex isn't disabled, we are golden, bail out
2293 0 : bool isDisabled = true;
2294 0 : if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) {
2295 : break;
2296 : }
2297 :
2298 : // it WAS disabled, so sart looking ahead for the next enabled option
2299 0 : newIndex += aDoAdjustIncNext;
2300 :
2301 : // well, if we reach end reverse the search
2302 0 : if (newIndex < bottom) {
2303 0 : if (doingReverse) {
2304 0 : return; // if we are in reverse mode and reach the end bail out
2305 : } else {
2306 : // reset the newIndex to the end of the list we hit
2307 : // reverse the incrementer
2308 : // set the other end of the list to our original starting index
2309 0 : newIndex = bottom;
2310 0 : aDoAdjustIncNext = 1;
2311 0 : doingReverse = true;
2312 0 : top = startIndex;
2313 : }
2314 0 : } else if (newIndex >= top) {
2315 0 : if (doingReverse) {
2316 0 : return; // if we are in reverse mode and reach the end bail out
2317 : } else {
2318 : // reset the newIndex to the end of the list we hit
2319 : // reverse the incrementer
2320 : // set the other end of the list to our original starting index
2321 0 : newIndex = top - 1;
2322 0 : aDoAdjustIncNext = -1;
2323 0 : doingReverse = true;
2324 0 : bottom = startIndex;
2325 : }
2326 : }
2327 : }
2328 :
2329 : // Looks like we found one
2330 0 : aNewIndex = newIndex;
2331 : }
2332 :
2333 : nsAString&
2334 0 : nsListControlFrame::GetIncrementalString()
2335 : {
2336 0 : if (sIncrementalString == nsnull)
2337 0 : sIncrementalString = new nsString();
2338 :
2339 0 : return *sIncrementalString;
2340 : }
2341 :
2342 : void
2343 1403 : nsListControlFrame::Shutdown()
2344 : {
2345 1403 : delete sIncrementalString;
2346 1403 : sIncrementalString = nsnull;
2347 1403 : }
2348 :
2349 : void
2350 0 : nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
2351 : {
2352 : // Cocoa widgets do native popups, so don't try to show
2353 : // dropdowns there.
2354 0 : if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
2355 0 : aKeyEvent->PreventDefault();
2356 0 : if (!mComboboxFrame->IsDroppedDown()) {
2357 0 : mComboboxFrame->ShowDropDown(true);
2358 : } else {
2359 0 : nsWeakFrame weakFrame(this);
2360 : // mEndSelectionIndex is the last item that got selected.
2361 0 : ComboboxFinish(mEndSelectionIndex);
2362 0 : if (weakFrame.IsAlive()) {
2363 0 : FireOnChange();
2364 : }
2365 : }
2366 : }
2367 0 : }
2368 :
2369 : nsresult
2370 0 : nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
2371 : {
2372 0 : NS_ASSERTION(aKeyEvent, "keyEvent is null.");
2373 :
2374 0 : nsEventStates eventStates = mContent->AsElement()->State();
2375 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED))
2376 0 : return NS_OK;
2377 :
2378 : // Start by making sure we can query for a key event
2379 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
2380 0 : NS_ENSURE_TRUE(keyEvent, NS_ERROR_FAILURE);
2381 :
2382 0 : PRUint32 keycode = 0;
2383 0 : PRUint32 charcode = 0;
2384 0 : keyEvent->GetKeyCode(&keycode);
2385 0 : keyEvent->GetCharCode(&charcode);
2386 :
2387 0 : bool isAlt = false;
2388 :
2389 0 : keyEvent->GetAltKey(&isAlt);
2390 0 : if (isAlt) {
2391 0 : if (keycode == nsIDOMKeyEvent::DOM_VK_UP || keycode == nsIDOMKeyEvent::DOM_VK_DOWN) {
2392 0 : DropDownToggleKey(aKeyEvent);
2393 : }
2394 0 : return NS_OK;
2395 : }
2396 :
2397 : // Get control / shift modifiers
2398 0 : bool isControl = false;
2399 0 : bool isShift = false;
2400 0 : keyEvent->GetCtrlKey(&isControl);
2401 0 : if (!isControl) {
2402 0 : keyEvent->GetMetaKey(&isControl);
2403 : }
2404 0 : keyEvent->GetShiftKey(&isShift);
2405 :
2406 : // now make sure there are options or we are wasting our time
2407 0 : nsCOMPtr<nsIDOMHTMLOptionsCollection> options = GetOptions(mContent);
2408 0 : NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
2409 :
2410 0 : PRUint32 numOptions = 0;
2411 0 : options->GetLength(&numOptions);
2412 :
2413 : // Whether we did an incremental search or another action
2414 0 : bool didIncrementalSearch = false;
2415 :
2416 : // this is the new index to set
2417 : // DOM_VK_RETURN & DOM_VK_ESCAPE will not set this
2418 0 : PRInt32 newIndex = kNothingSelected;
2419 :
2420 : // set up the old and new selected index and process it
2421 : // DOM_VK_RETURN selects the item
2422 : // DOM_VK_ESCAPE cancels the selection
2423 : // default processing checks to see if the pressed the first
2424 : // letter of an item in the list and advances to it
2425 :
2426 0 : if (isControl && (keycode == nsIDOMKeyEvent::DOM_VK_UP ||
2427 : keycode == nsIDOMKeyEvent::DOM_VK_LEFT ||
2428 : keycode == nsIDOMKeyEvent::DOM_VK_DOWN ||
2429 : keycode == nsIDOMKeyEvent::DOM_VK_RIGHT)) {
2430 : // Don't go into multiple select mode unless this list can handle it
2431 0 : isControl = mControlSelectMode = GetMultiple();
2432 0 : } else if (charcode != ' ') {
2433 0 : mControlSelectMode = false;
2434 : }
2435 0 : switch (keycode) {
2436 :
2437 : case nsIDOMKeyEvent::DOM_VK_UP:
2438 : case nsIDOMKeyEvent::DOM_VK_LEFT: {
2439 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2440 : (PRInt32)numOptions,
2441 0 : -1, -1);
2442 0 : } break;
2443 :
2444 : case nsIDOMKeyEvent::DOM_VK_DOWN:
2445 : case nsIDOMKeyEvent::DOM_VK_RIGHT: {
2446 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2447 : (PRInt32)numOptions,
2448 0 : 1, 1);
2449 0 : } break;
2450 :
2451 : case nsIDOMKeyEvent::DOM_VK_RETURN: {
2452 0 : if (mComboboxFrame != nsnull) {
2453 0 : if (mComboboxFrame->IsDroppedDown()) {
2454 0 : nsWeakFrame weakFrame(this);
2455 0 : ComboboxFinish(mEndSelectionIndex);
2456 0 : if (!weakFrame.IsAlive())
2457 0 : return NS_OK;
2458 : }
2459 0 : FireOnChange();
2460 0 : return NS_OK;
2461 : } else {
2462 0 : newIndex = mEndSelectionIndex;
2463 : }
2464 0 : } break;
2465 :
2466 : case nsIDOMKeyEvent::DOM_VK_ESCAPE: {
2467 0 : nsWeakFrame weakFrame(this);
2468 0 : AboutToRollup();
2469 0 : if (!weakFrame.IsAlive()) {
2470 0 : aKeyEvent->PreventDefault(); // since we won't reach the one below
2471 0 : return NS_OK;
2472 : }
2473 0 : } break;
2474 :
2475 : case nsIDOMKeyEvent::DOM_VK_PAGE_UP: {
2476 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2477 : (PRInt32)numOptions,
2478 0 : -NS_MAX(1, mNumDisplayRows-1), -1);
2479 0 : } break;
2480 :
2481 : case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: {
2482 : AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
2483 : (PRInt32)numOptions,
2484 0 : NS_MAX(1, mNumDisplayRows-1), 1);
2485 0 : } break;
2486 :
2487 : case nsIDOMKeyEvent::DOM_VK_HOME: {
2488 : AdjustIndexForDisabledOpt(0, newIndex,
2489 : (PRInt32)numOptions,
2490 0 : 0, 1);
2491 0 : } break;
2492 :
2493 : case nsIDOMKeyEvent::DOM_VK_END: {
2494 : AdjustIndexForDisabledOpt(numOptions-1, newIndex,
2495 : (PRInt32)numOptions,
2496 0 : 0, -1);
2497 0 : } break;
2498 :
2499 : #if defined(XP_WIN) || defined(XP_OS2)
2500 : case nsIDOMKeyEvent::DOM_VK_F4: {
2501 : DropDownToggleKey(aKeyEvent);
2502 : return NS_OK;
2503 : } break;
2504 : #endif
2505 :
2506 : case nsIDOMKeyEvent::DOM_VK_TAB: {
2507 0 : return NS_OK;
2508 : }
2509 :
2510 : default: { // Select option with this as the first character
2511 : // XXX Not I18N compliant
2512 :
2513 0 : if (isControl && charcode != ' ') {
2514 0 : return NS_OK;
2515 : }
2516 :
2517 0 : didIncrementalSearch = true;
2518 0 : if (charcode == 0) {
2519 : // Backspace key will delete the last char in the string
2520 0 : if (keycode == NS_VK_BACK && !GetIncrementalString().IsEmpty()) {
2521 0 : GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
2522 0 : aKeyEvent->PreventDefault();
2523 : }
2524 0 : return NS_OK;
2525 : }
2526 :
2527 : DOMTimeStamp keyTime;
2528 0 : aKeyEvent->GetTimeStamp(&keyTime);
2529 :
2530 : // Incremental Search: if time elapsed is below
2531 : // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
2532 : // string we will use to find options and start searching at the current
2533 : // keystroke. Otherwise, Truncate the string if it's been a long time
2534 : // since our last keypress.
2535 0 : if (keyTime - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
2536 : // If this is ' ' and we are at the beginning of the string, treat it as
2537 : // "select this option" (bug 191543)
2538 0 : if (charcode == ' ') {
2539 0 : newIndex = mEndSelectionIndex;
2540 0 : break;
2541 : }
2542 0 : GetIncrementalString().Truncate();
2543 : }
2544 0 : gLastKeyTime = keyTime;
2545 :
2546 : // Append this keystroke to the search string.
2547 0 : PRUnichar uniChar = ToLowerCase(static_cast<PRUnichar>(charcode));
2548 0 : GetIncrementalString().Append(uniChar);
2549 :
2550 : // See bug 188199, if all letters in incremental string are same, just try to match the first one
2551 0 : nsAutoString incrementalString(GetIncrementalString());
2552 0 : PRUint32 charIndex = 1, stringLength = incrementalString.Length();
2553 0 : while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
2554 0 : charIndex++;
2555 : }
2556 0 : if (charIndex == stringLength) {
2557 0 : incrementalString.Truncate(1);
2558 0 : stringLength = 1;
2559 : }
2560 :
2561 : // Determine where we're going to start reading the string
2562 : // If we have multiple characters to look for, we start looking *at* the
2563 : // current option. If we have only one character to look for, we start
2564 : // looking *after* the current option.
2565 : // Exception: if there is no option selected to start at, we always start
2566 : // *at* 0.
2567 0 : PRInt32 startIndex = GetSelectedIndex();
2568 0 : if (startIndex == kNothingSelected) {
2569 0 : startIndex = 0;
2570 0 : } else if (stringLength == 1) {
2571 0 : startIndex++;
2572 : }
2573 :
2574 : PRUint32 i;
2575 0 : for (i = 0; i < numOptions; i++) {
2576 0 : PRUint32 index = (i + startIndex) % numOptions;
2577 : nsCOMPtr<nsIDOMHTMLOptionElement> optionElement =
2578 0 : GetOption(options, index);
2579 0 : if (optionElement) {
2580 0 : nsAutoString text;
2581 0 : if (NS_OK == optionElement->GetText(text)) {
2582 0 : if (StringBeginsWith(text, incrementalString,
2583 0 : nsCaseInsensitiveStringComparator())) {
2584 0 : bool wasChanged = PerformSelection(index, isShift, isControl);
2585 0 : if (wasChanged) {
2586 : // dispatch event, update combobox, etc.
2587 0 : if (!UpdateSelection()) {
2588 0 : return NS_OK;
2589 : }
2590 : }
2591 : break;
2592 : }
2593 : }
2594 : }
2595 : } // for
2596 :
2597 0 : } break;//case
2598 : } // switch
2599 :
2600 : // We ate the key if we got this far.
2601 0 : aKeyEvent->PreventDefault();
2602 :
2603 : // If we didn't do an incremental search, clear the string
2604 0 : if (!didIncrementalSearch) {
2605 0 : GetIncrementalString().Truncate();
2606 : }
2607 :
2608 : // Actually process the new index and let the selection code
2609 : // do the scrolling for us
2610 0 : if (newIndex != kNothingSelected) {
2611 : // If you hold control, but not shift, no key will actually do anything
2612 : // except space.
2613 0 : bool wasChanged = false;
2614 0 : if (isControl && !isShift && charcode != ' ') {
2615 0 : mStartSelectionIndex = newIndex;
2616 0 : mEndSelectionIndex = newIndex;
2617 0 : InvalidateFocus();
2618 0 : ScrollToIndex(newIndex);
2619 :
2620 : #ifdef ACCESSIBILITY
2621 0 : FireMenuItemActiveEvent();
2622 : #endif
2623 0 : } else if (mControlSelectMode && charcode == ' ') {
2624 0 : wasChanged = SingleSelection(newIndex, true);
2625 : } else {
2626 0 : wasChanged = PerformSelection(newIndex, isShift, isControl);
2627 : }
2628 0 : if (wasChanged) {
2629 : // dispatch event, update combobox, etc.
2630 0 : if (!UpdateSelection()) {
2631 0 : return NS_OK;
2632 : }
2633 : }
2634 : }
2635 :
2636 0 : return NS_OK;
2637 : }
2638 :
2639 :
2640 : /******************************************************************************
2641 : * nsListEventListener
2642 : *****************************************************************************/
2643 :
2644 0 : NS_IMPL_ISUPPORTS1(nsListEventListener, nsIDOMEventListener)
2645 :
2646 : NS_IMETHODIMP
2647 0 : nsListEventListener::HandleEvent(nsIDOMEvent* aEvent)
2648 : {
2649 0 : if (!mFrame)
2650 0 : return NS_OK;
2651 :
2652 0 : nsAutoString eventType;
2653 0 : aEvent->GetType(eventType);
2654 0 : if (eventType.EqualsLiteral("keypress"))
2655 0 : return mFrame->nsListControlFrame::KeyPress(aEvent);
2656 0 : if (eventType.EqualsLiteral("mousedown"))
2657 0 : return mFrame->nsListControlFrame::MouseDown(aEvent);
2658 0 : if (eventType.EqualsLiteral("mouseup"))
2659 0 : return mFrame->nsListControlFrame::MouseUp(aEvent);
2660 0 : if (eventType.EqualsLiteral("mousemove"))
2661 0 : return mFrame->nsListControlFrame::MouseMove(aEvent);
2662 :
2663 0 : NS_ABORT();
2664 0 : return NS_OK;
2665 : }
|