Put these files in the right place
[mozilla-central.git] / layout / forms / nsComboboxControlFrame.cpp
blob140956e1f575c9347aeeb94c80d046dc2ecbe674
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
24 * Mats Palmgren <mats.palmgren@bredband.net>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
39 #include "nsCOMPtr.h"
40 #include "nsReadableUtils.h"
41 #include "nsComboboxControlFrame.h"
42 #include "nsIDOMEventTarget.h"
43 #include "nsFrameManager.h"
44 #include "nsFormControlFrame.h"
45 #include "nsGfxButtonControlFrame.h"
46 #include "nsGkAtoms.h"
47 #include "nsCSSAnonBoxes.h"
48 #include "nsHTMLParts.h"
49 #include "nsIFormControl.h"
50 #include "nsINameSpaceManager.h"
51 #include "nsIDOMElement.h"
52 #include "nsIListControlFrame.h"
53 #include "nsIDOMHTMLCollection.h"
54 #include "nsIDOMHTMLSelectElement.h"
55 #include "nsIDOMHTMLOptionElement.h"
56 #include "nsIDOMNSHTMLOptionCollectn.h"
57 #include "nsIPresShell.h"
58 #include "nsIDeviceContext.h"
59 #include "nsIView.h"
60 #include "nsIViewManager.h"
61 #include "nsIScrollableView.h"
62 #include "nsEventDispatcher.h"
63 #include "nsIEventStateManager.h"
64 #include "nsIEventListenerManager.h"
65 #include "nsIDOMNode.h"
66 #include "nsIPrivateDOMEvent.h"
67 #include "nsISelectControlFrame.h"
68 #include "nsXPCOM.h"
69 #include "nsISupportsPrimitives.h"
70 #include "nsIComponentManager.h"
71 #include "nsContentUtils.h"
72 #include "nsTextFragment.h"
73 #include "nsCSSFrameConstructor.h"
74 #include "nsIDocument.h"
75 #include "nsINodeInfo.h"
76 #include "nsIScrollableFrame.h"
77 #include "nsListControlFrame.h"
78 #include "nsContentCID.h"
79 #ifdef ACCESSIBILITY
80 #include "nsIAccessibilityService.h"
81 #endif
82 #include "nsIServiceManager.h"
83 #include "nsIDOMNode.h"
84 #include "nsGUIEvent.h"
85 #include "nsAutoPtr.h"
86 #include "nsStyleSet.h"
87 #include "nsNodeInfoManager.h"
88 #include "nsContentCreatorFunctions.h"
89 #include "nsLayoutUtils.h"
90 #include "nsDisplayList.h"
91 #include "nsITheme.h"
92 #include "nsThemeConstants.h"
94 NS_IMETHODIMP
95 nsComboboxControlFrame::RedisplayTextEvent::Run()
97 if (mControlFrame)
98 mControlFrame->HandleRedisplayTextEvent();
99 return NS_OK;
102 class nsPresState;
104 #define FIX_FOR_BUG_53259
106 // Drop down list event management.
107 // The combo box uses the following strategy for managing the drop-down list.
108 // If the combo box or it's arrow button is clicked on the drop-down list is displayed
109 // If mouse exit's the combo box with the drop-down list displayed the drop-down list
110 // is asked to capture events
111 // The drop-down list will capture all events including mouse down and up and will always
112 // return with ListWasSelected method call regardless of whether an item in the list was
113 // actually selected.
114 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
115 // The drop-down list does not explicitly set capture when it is in the drop-down mode.
118 //XXX: This is temporary. It simulates pseudo states by using a attribute selector on
120 const PRInt32 kSizeNotSet = -1;
123 * Helper class that listens to the combo boxes button. If the button is pressed the
124 * combo box is toggled to open or close. this is used by Accessibility which presses
125 * that button Programmatically.
127 class nsComboButtonListener: public nsIDOMMouseListener
129 public:
131 NS_DECL_ISUPPORTS
132 NS_IMETHOD HandleEvent(nsIDOMEvent* anEvent) { return PR_FALSE; }
133 NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent) { return PR_FALSE; }
134 NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent) { return PR_FALSE; }
135 NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent) { return PR_FALSE; }
136 NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent) { return PR_FALSE; }
137 NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent) { return PR_FALSE; }
139 NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent)
141 mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
142 return NS_OK;
145 nsComboButtonListener(nsComboboxControlFrame* aCombobox)
147 mComboBox = aCombobox;
150 virtual ~nsComboButtonListener() {}
152 nsComboboxControlFrame* mComboBox;
155 NS_IMPL_ISUPPORTS2(nsComboButtonListener,
156 nsIDOMMouseListener,
157 nsIDOMEventListener)
159 // static class data member for Bug 32920
160 nsComboboxControlFrame * nsComboboxControlFrame::mFocused = nsnull;
162 nsIFrame*
163 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aStateFlags)
165 nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
167 if (it) {
168 // set the state flags (if any are provided)
169 it->AddStateBits(aStateFlags);
172 return it;
175 //-----------------------------------------------------------
176 // Reflow Debugging Macros
177 // These let us "see" how many reflow counts are happening
178 //-----------------------------------------------------------
179 #ifdef DO_REFLOW_COUNTER
181 #define MAX_REFLOW_CNT 1024
182 static PRInt32 gTotalReqs = 0;;
183 static PRInt32 gTotalReflows = 0;;
184 static PRInt32 gReflowControlCntRQ[MAX_REFLOW_CNT];
185 static PRInt32 gReflowControlCnt[MAX_REFLOW_CNT];
186 static PRInt32 gReflowInx = -1;
188 #define REFLOW_COUNTER() \
189 if (mReflowId > -1) \
190 gReflowControlCnt[mReflowId]++;
192 #define REFLOW_COUNTER_REQUEST() \
193 if (mReflowId > -1) \
194 gReflowControlCntRQ[mReflowId]++;
196 #define REFLOW_COUNTER_DUMP(__desc) \
197 if (mReflowId > -1) {\
198 gTotalReqs += gReflowControlCntRQ[mReflowId];\
199 gTotalReflows += gReflowControlCnt[mReflowId];\
200 printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
201 mReflowId, (__desc), \
202 gReflowControlCnt[mReflowId], \
203 gReflowControlCntRQ[mReflowId],\
204 gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
207 #define REFLOW_COUNTER_INIT() \
208 if (gReflowInx < MAX_REFLOW_CNT) { \
209 gReflowInx++; \
210 mReflowId = gReflowInx; \
211 gReflowControlCnt[mReflowId] = 0; \
212 gReflowControlCntRQ[mReflowId] = 0; \
213 } else { \
214 mReflowId = -1; \
217 // reflow messages
218 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
219 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
220 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
221 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
223 #else //-------------
225 #define REFLOW_COUNTER_REQUEST()
226 #define REFLOW_COUNTER()
227 #define REFLOW_COUNTER_DUMP(__desc)
228 #define REFLOW_COUNTER_INIT()
230 #define REFLOW_DEBUG_MSG(_msg)
231 #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
232 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
233 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
236 #endif
238 //------------------------------------------
239 // This is for being VERY noisy
240 //------------------------------------------
241 #ifdef DO_VERY_NOISY
242 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
243 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
244 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
245 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
246 #else
247 #define REFLOW_NOISY_MSG(_msg)
248 #define REFLOW_NOISY_MSG2(_msg1, _msg2)
249 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
250 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
251 #endif
253 //------------------------------------------
254 // Displays value in pixels or twips
255 //------------------------------------------
256 #ifdef DO_PIXELS
257 #define PX(__v) __v / 15
258 #else
259 #define PX(__v) __v
260 #endif
262 //------------------------------------------------------
263 //-- Done with macros
264 //------------------------------------------------------
266 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
267 : nsBlockFrame(aContext),
268 mDisplayWidth(0)
270 mListControlFrame = nsnull;
271 mDroppedDown = PR_FALSE;
272 mDisplayFrame = nsnull;
273 mButtonFrame = nsnull;
274 mDropdownFrame = nsnull;
276 mInRedisplayText = PR_FALSE;
278 mRecentSelectedIndex = NS_SKIP_NOTIFY_INDEX;
280 REFLOW_COUNTER_INIT()
283 //--------------------------------------------------------------
284 nsComboboxControlFrame::~nsComboboxControlFrame()
286 REFLOW_COUNTER_DUMP("nsCCF");
289 //--------------------------------------------------------------
291 NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
292 NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
293 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
294 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
295 NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
296 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
297 NS_QUERYFRAME_ENTRY(nsIScrollableViewProvider)
298 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
300 NS_IMPL_QUERY_INTERFACE1(nsComboboxControlFrame, nsIRollupListener)
302 NS_IMETHODIMP_(nsrefcnt)
303 nsComboboxControlFrame::AddRef()
305 return 2;
308 NS_IMETHODIMP_(nsrefcnt)
309 nsComboboxControlFrame::Release()
311 return 1;
314 #ifdef ACCESSIBILITY
315 NS_IMETHODIMP nsComboboxControlFrame::GetAccessible(nsIAccessible** aAccessible)
317 nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");
319 if (accService) {
320 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(mContent);
321 nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(PresContext()->PresShell()));
322 return accService->CreateHTMLComboboxAccessible(node, weakShell, aAccessible);
325 return NS_ERROR_FAILURE;
327 #endif
329 void
330 nsComboboxControlFrame::SetFocus(PRBool aOn, PRBool aRepaint)
332 nsWeakFrame weakFrame(this);
333 if (aOn) {
334 nsListControlFrame::ComboboxFocusSet();
335 mFocused = this;
336 } else {
337 mFocused = nsnull;
338 if (mDroppedDown) {
339 mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
340 if (!weakFrame.IsAlive()) {
341 return;
344 // May delete |this|.
345 mListControlFrame->FireOnChange();
348 if (!weakFrame.IsAlive()) {
349 return;
352 // This is needed on a temporary basis. It causes the focus
353 // rect to be drawn. This is much faster than ReResolvingStyle
354 // Bug 32920
355 Invalidate(nsRect(0,0,mRect.width,mRect.height));
357 // Make sure the content area gets updated for where the dropdown was
358 // This is only needed for embedding, the focus may go to
359 // the chrome that is not part of the Gecko system (Bug 83493)
360 // XXX this is rather inefficient
361 nsIViewManager* vm = PresContext()->GetViewManager();
362 if (vm) {
363 vm->UpdateAllViews(NS_VMREFRESH_NO_SYNC);
367 void
368 nsComboboxControlFrame::ShowPopup(PRBool aShowPopup)
370 nsIView* view = mDropdownFrame->GetView();
371 nsIViewManager* viewManager = view->GetViewManager();
373 if (aShowPopup) {
374 nsRect rect = mDropdownFrame->GetRect();
375 rect.x = rect.y = 0;
376 viewManager->ResizeView(view, rect);
377 viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
378 } else {
379 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
380 nsRect emptyRect(0, 0, 0, 0);
381 viewManager->ResizeView(view, emptyRect);
384 // fire a popup dom event
385 nsEventStatus status = nsEventStatus_eIgnore;
386 nsMouseEvent event(PR_TRUE, aShowPopup ?
387 NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nsnull,
388 nsMouseEvent::eReal);
390 nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
391 if (shell)
392 shell->HandleDOMEventWithTarget(mContent, &event, &status);
395 PRBool
396 nsComboboxControlFrame::ShowList(nsPresContext* aPresContext, PRBool aShowList)
398 nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
400 nsWeakFrame weakFrame(this);
401 ShowPopup(aShowList); // might destroy us
402 if (!weakFrame.IsAlive()) {
403 return PR_FALSE;
406 mDroppedDown = aShowList;
407 if (mDroppedDown) {
408 // The listcontrol frame will call back to the nsComboboxControlFrame's
409 // ListWasSelected which will stop the capture.
410 mListControlFrame->AboutToDropDown();
411 mListControlFrame->CaptureMouseEvents(PR_TRUE);
414 // XXXbz so why do we need to flush here, exactly?
415 shell->GetDocument()->FlushPendingNotifications(Flush_Layout);
416 if (!weakFrame.IsAlive()) {
417 return PR_FALSE;
420 nsIFrame* listFrame = do_QueryFrame(mListControlFrame);
421 if (listFrame) {
422 nsIView* view = listFrame->GetView();
423 NS_ASSERTION(view, "nsComboboxControlFrame view is null");
424 if (view) {
425 nsIWidget* widget = view->GetWidget();
426 if (widget)
427 widget->CaptureRollupEvents(this, mDroppedDown, mDroppedDown);
431 return weakFrame.IsAlive();
434 nsresult
435 nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
436 const nsHTMLReflowState& aReflowState)
438 // All we want out of it later on, really, is the height of a row, so we
439 // don't even need to cache mDropdownFrame's ascent or anything. If we don't
440 // need to reflow it, just bail out here.
441 if (!aReflowState.ShouldReflowAllKids() &&
442 !NS_SUBTREE_DIRTY(mDropdownFrame)) {
443 return NS_OK;
446 // XXXbz this will, for small-height dropdowns, have extra space on the right
447 // edge for the scrollbar we don't show... but that's the best we can do here
448 // for now.
449 nsSize availSize(aReflowState.availableWidth, NS_UNCONSTRAINEDSIZE);
450 nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
451 availSize);
453 // If the dropdown's intrinsic width is narrower than our specified width,
454 // then expand it out. We want our border-box width to end up the same as
455 // the dropdown's so account for both sets of mComputedBorderPadding.
456 nscoord forcedWidth = aReflowState.ComputedWidth() +
457 aReflowState.mComputedBorderPadding.LeftRight() -
458 kidReflowState.mComputedBorderPadding.LeftRight();
459 kidReflowState.SetComputedWidth(PR_MAX(kidReflowState.ComputedWidth(),
460 forcedWidth));
462 // ensure we start off hidden
463 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
464 nsIView* view = mDropdownFrame->GetView();
465 nsIViewManager* viewManager = view->GetViewManager();
466 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
467 nsRect emptyRect(0, 0, 0, 0);
468 viewManager->ResizeView(view, emptyRect);
471 // Allow the child to move/size/change-visibility its view if it's currently
472 // dropped down
473 PRInt32 flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
474 if (mDroppedDown) {
475 flags = 0;
477 nsRect rect = mDropdownFrame->GetRect();
478 nsHTMLReflowMetrics desiredSize;
479 nsReflowStatus ignoredStatus;
480 nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize,
481 kidReflowState, rect.x, rect.y, flags,
482 ignoredStatus);
484 // Set the child's width and height to it's desired size
485 FinishReflowChild(mDropdownFrame, aPresContext, &kidReflowState,
486 desiredSize, rect.x, rect.y, flags);
487 return rv;
490 void
491 nsComboboxControlFrame::AbsolutelyPositionDropDown()
493 // Position the dropdown list. It is positioned below the display frame if there is enough
494 // room on the screen to display the entire list. Otherwise it is placed above the display
495 // frame.
497 // Note: As first glance, it appears that you could simply get the absolute bounding box for the
498 // dropdown list by first getting its view, then getting the view's nsIWidget, then asking the nsIWidget
499 // for it's AbsoluteBounds. The problem with this approach, is that the dropdown lists y location can
500 // change based on whether the dropdown is placed below or above the display frame.
501 // The approach, taken here is to get use the absolute position of the display frame and use it's location
502 // to determine if the dropdown will go offscreen.
504 // Use the height calculated for the area frame so it includes both
505 // the display and button heights.
506 nscoord dropdownYOffset = GetRect().height;
507 nsPresContext* presContext = PresContext();
508 // XXX: Enable this code to debug popping up above the display frame, rather than below it
509 nsSize dropdownSize = mDropdownFrame->GetSize();
511 nscoord screenHeightInPixels = 0;
512 if (NS_SUCCEEDED(nsFormControlFrame::GetScreenHeight(presContext, screenHeightInPixels))) {
513 // Get the height of the dropdown list in pixels.
514 nscoord absoluteDropDownHeight = presContext->AppUnitsToDevPixels(dropdownSize.height);
515 // Check to see if the drop-down list will go offscreen
516 if (GetScreenRect().YMost() + absoluteDropDownHeight > screenHeightInPixels) {
517 // move the dropdown list up
518 dropdownYOffset = - (dropdownSize.height);
522 nsPoint dropdownPosition;
523 const nsStyleVisibility* vis = GetStyleVisibility();
524 if (vis->mDirection == NS_STYLE_DIRECTION_RTL) {
525 // Align the right edge of the drop-down with the right edge of the control.
526 dropdownPosition.x = GetRect().width - dropdownSize.width;
527 } else {
528 dropdownPosition.x = 0;
530 dropdownPosition.y = dropdownYOffset;
532 mDropdownFrame->SetPosition(dropdownPosition);
535 //----------------------------------------------------------
537 //----------------------------------------------------------
538 #ifdef DO_REFLOW_DEBUG
539 static int myCounter = 0;
541 static void printSize(char * aDesc, nscoord aSize)
543 printf(" %s: ", aDesc);
544 if (aSize == NS_UNCONSTRAINEDSIZE) {
545 printf("UC");
546 } else {
547 printf("%d", PX(aSize));
550 #endif
552 //-------------------------------------------------------------------
553 //-- Main Reflow for the Combobox
554 //-------------------------------------------------------------------
556 nscoord
557 nsComboboxControlFrame::GetIntrinsicWidth(nsIRenderingContext* aRenderingContext,
558 nsLayoutUtils::IntrinsicWidthType aType)
560 // get the scrollbar width, we'll use this later
561 nscoord scrollbarWidth = 0;
562 nsPresContext* presContext = PresContext();
563 if (mListControlFrame) {
564 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
565 NS_ASSERTION(scrollable, "List must be a scrollable frame");
566 scrollbarWidth =
567 scrollable->GetDesiredScrollbarSizes(presContext, aRenderingContext).LeftRight();
570 nscoord displayWidth = 0;
571 if (NS_LIKELY(mDisplayFrame)) {
572 displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
573 mDisplayFrame,
574 aType);
577 if (mDropdownFrame) {
578 nscoord dropdownContentWidth;
579 if (aType == nsLayoutUtils::MIN_WIDTH) {
580 dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext);
581 } else {
582 NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type");
583 dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext);
585 dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
586 scrollbarWidth,
587 nscoord_MAX);
589 displayWidth = PR_MAX(dropdownContentWidth, displayWidth);
592 // add room for the dropmarker button if there is one
593 if (!IsThemed() || presContext->GetTheme()->ThemeNeedsComboboxDropmarker())
594 displayWidth += scrollbarWidth;
596 return displayWidth;
600 nscoord
601 nsComboboxControlFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
603 nscoord minWidth;
604 DISPLAY_MIN_WIDTH(this, minWidth);
605 minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH);
606 return minWidth;
609 nscoord
610 nsComboboxControlFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
612 nscoord prefWidth;
613 DISPLAY_PREF_WIDTH(this, prefWidth);
614 prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH);
615 return prefWidth;
618 NS_IMETHODIMP
619 nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
620 nsHTMLReflowMetrics& aDesiredSize,
621 const nsHTMLReflowState& aReflowState,
622 nsReflowStatus& aStatus)
624 // Constraints we try to satisfy:
626 // 1) Default width of button is the vertical scrollbar size
627 // 2) If the width of button is bigger than our width, set width of
628 // button to 0.
629 // 3) Default height of button is height of display area
630 // 4) Width of display area is whatever is left over from our width after
631 // allocating width for the button.
632 // 5) Height of display area is GetHeightOfARow() on the
633 // mListControlFrame.
635 if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
636 NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
637 return NS_ERROR_UNEXPECTED;
640 // Make sure the displayed text is the same as the selected option, bug 297389.
641 PRInt32 selectedIndex;
642 nsAutoString selectedOptionText;
643 if (!mDroppedDown) {
644 selectedIndex = mListControlFrame->GetSelectedIndex();
646 else {
647 // In dropped down mode the "selected index" is the hovered menu item,
648 // we want the last selected item which is |mDisplayedIndex| in this case.
649 selectedIndex = mDisplayedIndex;
651 if (selectedIndex != -1) {
652 mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
654 if (mDisplayedOptionText != selectedOptionText) {
655 RedisplayText(selectedIndex);
658 // First reflow our dropdown so that we know how tall we should be.
659 ReflowDropdown(aPresContext, aReflowState);
661 // Get the width of the vertical scrollbar. That will be the width of the
662 // dropdown button.
663 nscoord buttonWidth;
664 const nsStyleDisplay *disp = GetStyleDisplay();
665 if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) {
666 buttonWidth = 0;
668 else {
669 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
670 NS_ASSERTION(scrollable, "List must be a scrollable frame");
671 buttonWidth =
672 scrollable->GetDesiredScrollbarSizes(PresContext(),
673 aReflowState.rendContext).LeftRight();
674 if (buttonWidth > aReflowState.ComputedWidth()) {
675 buttonWidth = 0;
679 mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
681 nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState,
682 aStatus);
683 NS_ENSURE_SUCCESS(rv, rv);
685 // Now set the correct width and height on our button. The width we need to
686 // set always, the height only if we had an auto height.
687 nsRect buttonRect = mButtonFrame->GetRect();
688 // If we have a non-intrinsic computed height, our kids should have sized
689 // themselves properly on their own.
690 if (aReflowState.ComputedHeight() == NS_INTRINSICSIZE) {
691 // The display frame is going to be the right height and width at this
692 // point. Use its height as the button height.
693 nsRect displayRect = mDisplayFrame->GetRect();
694 buttonRect.height = displayRect.height;
695 buttonRect.y = displayRect.y;
697 #ifdef DEBUG
698 else {
699 nscoord buttonHeight = mButtonFrame->GetSize().height;
700 nscoord displayHeight = mDisplayFrame->GetSize().height;
702 // The button and display area should be equal heights, unless the computed
703 // height on the combobox is too small to fit their borders and padding.
704 NS_ASSERTION(buttonHeight == displayHeight ||
705 (aReflowState.ComputedHeight() < buttonHeight &&
706 buttonHeight ==
707 mButtonFrame->GetUsedBorderAndPadding().TopBottom()) ||
708 (aReflowState.ComputedHeight() < displayHeight &&
709 displayHeight ==
710 mDisplayFrame->GetUsedBorderAndPadding().TopBottom()),
711 "Different heights?");
713 #endif
715 if (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
716 // Make sure the right edge of the button frame stays where it is now
717 buttonRect.x -= buttonWidth - buttonRect.width;
719 buttonRect.width = buttonWidth;
720 mButtonFrame->SetRect(buttonRect);
722 return rv;
725 //--------------------------------------------------------------
727 nsIAtom*
728 nsComboboxControlFrame::GetType() const
730 return nsGkAtoms::comboboxControlFrame;
733 #ifdef NS_DEBUG
734 NS_IMETHODIMP
735 nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
737 return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
739 #endif
742 //----------------------------------------------------------------------
743 // nsIComboboxControlFrame
744 //----------------------------------------------------------------------
745 void
746 nsComboboxControlFrame::ShowDropDown(PRBool aDoDropDown)
748 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
749 return;
752 if (!mDroppedDown && aDoDropDown) {
753 if (mListControlFrame) {
754 mListControlFrame->SyncViewWithFrame();
756 ShowList(PresContext(), aDoDropDown); // might destroy us
757 } else if (mDroppedDown && !aDoDropDown) {
758 ShowList(PresContext(), aDoDropDown); // might destroy us
762 void
763 nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
765 mDropdownFrame = aDropDownFrame;
766 mListControlFrame = do_QueryFrame(mDropdownFrame);
769 nsIFrame*
770 nsComboboxControlFrame::GetDropDown()
772 return mDropdownFrame;
775 ///////////////////////////////////////////////////////////////
777 NS_IMETHODIMP
778 nsComboboxControlFrame::RedisplaySelectedText()
780 return RedisplayText(mListControlFrame->GetSelectedIndex());
783 nsresult
784 nsComboboxControlFrame::RedisplayText(PRInt32 aIndex)
786 // Get the text to display
787 if (aIndex != -1) {
788 mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
789 } else {
790 mDisplayedOptionText.Truncate();
792 mDisplayedIndex = aIndex;
794 REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
795 NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
797 // Send reflow command because the new text maybe larger
798 nsresult rv = NS_OK;
799 if (mDisplayContent) {
800 // Don't call ActuallyDisplayText(PR_TRUE) directly here since that
801 // could cause recursive frame construction. See bug 283117 and the comment in
802 // HandleRedisplayTextEvent() below.
804 // Revoke outstanding events to avoid out-of-order events which could mean
805 // displaying the wrong text.
806 mRedisplayTextEvent.Revoke();
808 nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
809 rv = NS_DispatchToCurrentThread(event);
810 if (NS_SUCCEEDED(rv))
811 mRedisplayTextEvent = event;
813 return rv;
816 void
817 nsComboboxControlFrame::HandleRedisplayTextEvent()
819 // First, make sure that the content model is up to date and we've
820 // constructed the frames for all our content in the right places.
821 // Otherwise they'll end up under the wrong insertion frame when we
822 // ActuallyDisplayText, since that flushes out the content sink by
823 // calling SetText on a DOM node with aNotify set to true. See bug
824 // 289730.
825 PresContext()->Document()->
826 FlushPendingNotifications(Flush_ContentAndNotify);
828 // Redirect frame insertions during this method (see GetContentInsertionFrame())
829 // so that any reframing that the frame constructor forces upon us is inserted
830 // into the correct parent (mDisplayFrame). See bug 282607.
831 NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
832 mInRedisplayText = PR_TRUE;
833 mRedisplayTextEvent.Forget();
835 ActuallyDisplayText(PR_TRUE);
836 // XXXbz This should perhaps be eResize. Check.
837 PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
838 nsIPresShell::eStyleChange,
839 NS_FRAME_IS_DIRTY);
841 mInRedisplayText = PR_FALSE;
844 void
845 nsComboboxControlFrame::ActuallyDisplayText(PRBool aNotify)
847 if (mDisplayedOptionText.IsEmpty()) {
848 // Have to use a non-breaking space for line-height calculations
849 // to be right
850 static const PRUnichar space = 0xA0;
851 mDisplayContent->SetText(&space, 1, aNotify);
852 } else {
853 mDisplayContent->SetText(mDisplayedOptionText, aNotify);
857 PRInt32
858 nsComboboxControlFrame::GetIndexOfDisplayArea()
860 return mDisplayedIndex;
863 //----------------------------------------------------------------------
864 // nsISelectControlFrame
865 //----------------------------------------------------------------------
866 NS_IMETHODIMP
867 nsComboboxControlFrame::DoneAddingChildren(PRBool aIsDone)
869 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
870 if (!listFrame)
871 return NS_ERROR_FAILURE;
873 return listFrame->DoneAddingChildren(aIsDone);
876 NS_IMETHODIMP
877 nsComboboxControlFrame::AddOption(nsPresContext* aPresContext, PRInt32 aIndex)
879 if (aIndex <= mDisplayedIndex) {
880 ++mDisplayedIndex;
883 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
884 return lcf->AddOption(aPresContext, aIndex);
888 NS_IMETHODIMP
889 nsComboboxControlFrame::RemoveOption(nsPresContext* aPresContext, PRInt32 aIndex)
891 if (mListControlFrame->GetNumberOfOptions() > 0) {
892 if (aIndex < mDisplayedIndex) {
893 --mDisplayedIndex;
894 } else if (aIndex == mDisplayedIndex) {
895 mDisplayedIndex = 0; // IE6 compat
896 RedisplayText(mDisplayedIndex);
899 else {
900 // If we removed the last option, we need to blank things out
901 RedisplayText(-1);
904 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
905 return lcf->RemoveOption(aPresContext, aIndex);
908 NS_IMETHODIMP
909 nsComboboxControlFrame::GetOptionSelected(PRInt32 aIndex, PRBool* aValue)
911 NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
913 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
914 NS_ASSERTION(listFrame, "No list frame!");
916 return listFrame->GetOptionSelected(aIndex, aValue);
919 NS_IMETHODIMP
920 nsComboboxControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex, PRInt32 aNewIndex)
922 RedisplayText(aNewIndex);
923 NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
925 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
926 NS_ASSERTION(listFrame, "No list frame!");
928 return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
931 // End nsISelectControlFrame
932 //----------------------------------------------------------------------
934 NS_IMETHODIMP
935 nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
936 nsGUIEvent* aEvent,
937 nsEventStatus* aEventStatus)
939 NS_ENSURE_ARG_POINTER(aEventStatus);
941 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
942 return NS_OK;
944 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
945 return NS_OK;
948 // If we have style that affects how we are selected, feed event down to
949 // nsFrame::HandleEvent so that selection takes place when appropriate.
950 const nsStyleUserInterface* uiStyle = GetStyleUserInterface();
951 if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
952 return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
954 return NS_OK;
958 nsresult
959 nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
961 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
962 if (!fcFrame) {
963 return NS_NOINTERFACE;
966 return fcFrame->SetFormProperty(aName, aValue);
969 nsresult
970 nsComboboxControlFrame::GetFormProperty(nsIAtom* aName, nsAString& aValue) const
972 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
973 if (!fcFrame) {
974 return NS_ERROR_FAILURE;
977 return fcFrame->GetFormProperty(aName, aValue);
980 nsIFrame*
981 nsComboboxControlFrame::GetContentInsertionFrame() {
982 return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
985 nsresult
986 nsComboboxControlFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
988 // The frames used to display the combo box and the button used to popup the dropdown list
989 // are created through anonymous content. The dropdown list is not created through anonymous
990 // content because it's frame is initialized specifically for the drop-down case and it is placed
991 // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
992 // layout of the display and button.
994 // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
995 // If the content specified below does not honor the value attribute than nothing will be displayed.
997 // For now the content that is created corresponds to two input buttons. It would be better to create the
998 // tag as something other than input, but then there isn't any way to create a button frame since it
999 // isn't possible to set the display type in CSS2 to create a button frame.
1001 // create content used for display
1002 //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
1004 // Add a child text content node for the label
1006 nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
1008 NS_NewTextNode(getter_AddRefs(mDisplayContent), nimgr);
1009 if (!mDisplayContent)
1010 return NS_ERROR_OUT_OF_MEMORY;
1012 // set the value of the text node
1013 mDisplayedIndex = mListControlFrame->GetSelectedIndex();
1014 if (mDisplayedIndex != -1) {
1015 mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
1017 ActuallyDisplayText(PR_FALSE);
1019 if (!aElements.AppendElement(mDisplayContent))
1020 return NS_ERROR_OUT_OF_MEMORY;
1022 nsCOMPtr<nsINodeInfo> nodeInfo;
1023 nodeInfo = nimgr->GetNodeInfo(nsGkAtoms::input, nsnull, kNameSpaceID_None);
1025 // create button which drops the list down
1026 NS_NewHTMLElement(getter_AddRefs(mButtonContent), nodeInfo, PR_FALSE);
1027 if (!mButtonContent)
1028 return NS_ERROR_OUT_OF_MEMORY;
1030 // make someone to listen to the button. If its pressed by someone like Accessibility
1031 // then open or close the combo box.
1032 mButtonListener = new nsComboButtonListener(this);
1033 if (!mButtonListener)
1034 return NS_ERROR_OUT_OF_MEMORY;
1035 mButtonContent->AddEventListenerByIID(mButtonListener,
1036 NS_GET_IID(nsIDOMMouseListener));
1038 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1039 NS_LITERAL_STRING("button"), PR_FALSE);
1040 // Set tabindex="-1" so that the button is not tabbable
1041 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
1042 NS_LITERAL_STRING("-1"), PR_FALSE);
1044 if (!aElements.AppendElement(mButtonContent))
1045 return NS_ERROR_OUT_OF_MEMORY;
1047 return NS_OK;
1050 // XXXbz this is a for-now hack. Now that display:inline-block works,
1051 // need to revisit this.
1052 class nsComboboxDisplayFrame : public nsBlockFrame {
1053 public:
1054 nsComboboxDisplayFrame (nsStyleContext* aContext,
1055 nsComboboxControlFrame* aComboBox)
1056 : nsBlockFrame(aContext),
1057 mComboBox(aComboBox)
1060 // Need this so that line layout knows that this block's width
1061 // depends on the available width.
1062 virtual nsIAtom* GetType() const;
1064 virtual PRBool IsFrameOfType(PRUint32 aFlags) const
1066 return nsBlockFrame::IsFrameOfType(aFlags &
1067 ~(nsIFrame::eReplacedContainsBlock));
1070 NS_IMETHOD Reflow(nsPresContext* aPresContext,
1071 nsHTMLReflowMetrics& aDesiredSize,
1072 const nsHTMLReflowState& aReflowState,
1073 nsReflowStatus& aStatus);
1075 NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
1076 const nsRect& aDirtyRect,
1077 const nsDisplayListSet& aLists);
1079 protected:
1080 nsComboboxControlFrame* mComboBox;
1083 nsIAtom*
1084 nsComboboxDisplayFrame::GetType() const
1086 return nsGkAtoms::comboboxDisplayFrame;
1089 NS_IMETHODIMP
1090 nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
1091 nsHTMLReflowMetrics& aDesiredSize,
1092 const nsHTMLReflowState& aReflowState,
1093 nsReflowStatus& aStatus)
1095 nsHTMLReflowState state(aReflowState);
1096 if (state.ComputedHeight() == NS_INTRINSICSIZE) {
1097 // Note that the only way we can have a computed height here is if the
1098 // combobox had a specified height. If it didn't, size based on what our
1099 // rows look like, for lack of anything better.
1100 state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow());
1102 nscoord computedWidth = mComboBox->mDisplayWidth -
1103 state.mComputedBorderPadding.LeftRight();
1104 if (computedWidth < 0) {
1105 computedWidth = 0;
1107 state.SetComputedWidth(computedWidth);
1109 return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1112 NS_IMETHODIMP
1113 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1114 const nsRect& aDirtyRect,
1115 const nsDisplayListSet& aLists)
1117 nsDisplayListCollection set;
1118 nsresult rv = nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
1119 if (NS_FAILED(rv))
1120 return rv;
1122 // remove background items if parent frame is themed
1123 if (mComboBox->IsThemed()) {
1124 set.BorderBackground()->DeleteAll();
1127 set.MoveTo(aLists);
1129 return NS_OK;
1132 nsIFrame*
1133 nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent)
1135 NS_PRECONDITION(nsnull != aContent, "null ptr");
1137 NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
1139 if (mDisplayContent != aContent) {
1140 // We only handle the frames for mDisplayContent here
1141 return nsnull;
1144 // Get PresShell
1145 nsIPresShell *shell = PresContext()->PresShell();
1146 nsStyleSet *styleSet = shell->StyleSet();
1148 // create the style contexts for the anonymous block frame and text frame
1149 nsRefPtr<nsStyleContext> styleContext;
1150 styleContext = styleSet->
1151 ResolvePseudoStyleFor(mContent,
1152 nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
1153 mStyleContext);
1154 if (NS_UNLIKELY(!styleContext)) {
1155 return nsnull;
1158 nsRefPtr<nsStyleContext> textStyleContext;
1159 textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
1160 if (NS_UNLIKELY(!textStyleContext)) {
1161 return nsnull;
1164 // Start by by creating our anonymous block frame
1165 mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
1166 if (NS_UNLIKELY(!mDisplayFrame)) {
1167 return nsnull;
1170 nsresult rv = mDisplayFrame->Init(mContent, this, nsnull);
1171 if (NS_FAILED(rv)) {
1172 mDisplayFrame->Destroy();
1173 mDisplayFrame = nsnull;
1174 return nsnull;
1177 // Create a text frame and put it inside the block frame
1178 mTextFrame = NS_NewTextFrame(shell, textStyleContext);
1179 if (NS_UNLIKELY(!mTextFrame)) {
1180 return nsnull;
1183 // initialize the text frame
1184 rv = mTextFrame->Init(aContent, mDisplayFrame, nsnull);
1185 if (NS_FAILED(rv)) {
1186 mDisplayFrame->Destroy();
1187 mDisplayFrame = nsnull;
1188 mTextFrame->Destroy();
1189 mTextFrame = nsnull;
1190 return nsnull;
1193 mDisplayFrame->SetInitialChildList(nsnull, mTextFrame);
1194 return mDisplayFrame;
1197 void
1198 nsComboboxControlFrame::Destroy()
1200 // Revoke any pending RedisplayTextEvent
1201 mRedisplayTextEvent.Revoke();
1203 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), PR_FALSE);
1205 if (mDroppedDown) {
1206 // Get parent view
1207 nsIFrame * listFrame = do_QueryFrame(mListControlFrame);
1208 if (listFrame) {
1209 nsIView* view = listFrame->GetView();
1210 NS_ASSERTION(view, "nsComboboxControlFrame view is null");
1211 if (view) {
1212 nsIWidget* widget = view->GetWidget();
1213 if (widget)
1214 widget->CaptureRollupEvents(this, PR_FALSE, PR_TRUE);
1219 // Cleanup frames in popup child list
1220 mPopupFrames.DestroyFrames();
1221 nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
1222 nsContentUtils::DestroyAnonymousContent(&mButtonContent);
1223 nsBlockFrame::Destroy();
1227 nsIFrame*
1228 nsComboboxControlFrame::GetFirstChild(nsIAtom* aListName) const
1230 if (nsGkAtoms::selectPopupList == aListName) {
1231 return mPopupFrames.FirstChild();
1233 return nsBlockFrame::GetFirstChild(aListName);
1236 NS_IMETHODIMP
1237 nsComboboxControlFrame::SetInitialChildList(nsIAtom* aListName,
1238 nsIFrame* aChildList)
1240 nsresult rv = NS_OK;
1241 if (nsGkAtoms::selectPopupList == aListName) {
1242 mPopupFrames.SetFrames(aChildList);
1243 } else {
1244 rv = nsBlockFrame::SetInitialChildList(aListName, aChildList);
1246 for (nsIFrame * child = aChildList; child;
1247 child = child->GetNextSibling()) {
1248 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(child->GetContent());
1249 if (formControl && formControl->GetType() == NS_FORM_INPUT_BUTTON) {
1250 mButtonFrame = child;
1251 break;
1254 NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1256 return rv;
1259 #define NS_COMBO_FRAME_POPUP_LIST_INDEX (NS_BLOCK_LIST_COUNT)
1261 nsIAtom*
1262 nsComboboxControlFrame::GetAdditionalChildListName(PRInt32 aIndex) const
1264 // Maintain a separate child list for the dropdown list (i.e. popup listbox)
1265 // This is necessary because we don't want the listbox to be included in the layout
1266 // of the combox's children because it would take up space, when it is suppose to
1267 // be floating above the display.
1268 if (aIndex < NS_BLOCK_LIST_COUNT) {
1269 return nsBlockFrame::GetAdditionalChildListName(aIndex);
1272 if (NS_COMBO_FRAME_POPUP_LIST_INDEX == aIndex) {
1273 return nsGkAtoms::selectPopupList;
1275 return nsnull;
1278 //----------------------------------------------------------------------
1279 //nsIRollupListener
1280 //----------------------------------------------------------------------
1281 NS_IMETHODIMP
1282 nsComboboxControlFrame::Rollup(nsIContent** aLastRolledUp)
1284 if (aLastRolledUp)
1285 *aLastRolledUp = nsnull;
1287 if (mDroppedDown) {
1288 nsWeakFrame weakFrame(this);
1289 mListControlFrame->AboutToRollup(); // might destroy us
1290 if (!weakFrame.IsAlive())
1291 return NS_OK;
1292 ShowDropDown(PR_FALSE); // might destroy us
1293 if (!weakFrame.IsAlive())
1294 return NS_OK;
1295 mListControlFrame->CaptureMouseEvents(PR_FALSE);
1297 return NS_OK;
1300 void
1301 nsComboboxControlFrame::RollupFromList()
1303 if (ShowList(PresContext(), PR_FALSE))
1304 mListControlFrame->CaptureMouseEvents(PR_FALSE);
1307 PRInt32
1308 nsComboboxControlFrame::UpdateRecentIndex(PRInt32 aIndex)
1310 PRInt32 index = mRecentSelectedIndex;
1311 if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1312 mRecentSelectedIndex = aIndex;
1313 return index;
1316 class nsDisplayComboboxFocus : public nsDisplayItem {
1317 public:
1318 nsDisplayComboboxFocus(nsComboboxControlFrame* aFrame)
1319 : nsDisplayItem(aFrame) {
1320 MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
1322 #ifdef NS_BUILD_REFCNT_LOGGING
1323 virtual ~nsDisplayComboboxFocus() {
1324 MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
1326 #endif
1328 virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
1329 const nsRect& aDirtyRect);
1330 NS_DISPLAY_DECL_NAME("ComboboxFocus")
1333 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
1334 nsIRenderingContext* aCtx, const nsRect& aDirtyRect)
1336 static_cast<nsComboboxControlFrame*>(mFrame)
1337 ->PaintFocus(*aCtx, aBuilder->ToReferenceFrame(mFrame));
1340 NS_IMETHODIMP
1341 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1342 const nsRect& aDirtyRect,
1343 const nsDisplayListSet& aLists)
1345 #ifdef NOISY
1346 printf("%p paint at (%d, %d, %d, %d)\n", this,
1347 aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
1348 #endif
1350 if (aBuilder->IsForEventDelivery()) {
1351 // Don't allow children to receive events.
1352 // REVIEW: following old GetFrameForPoint
1353 nsresult rv = DisplayBorderBackgroundOutline(aBuilder, aLists);
1354 NS_ENSURE_SUCCESS(rv, rv);
1355 } else {
1356 // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1357 // content list, so we don't need to mess with layers.
1358 nsresult rv = nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
1359 NS_ENSURE_SUCCESS(rv, rv);
1362 nsPresContext *presContext = PresContext();
1363 const nsStyleDisplay *disp = GetStyleDisplay();
1364 if ((!IsThemed(disp) ||
1365 !presContext->GetTheme()->ThemeDrawsFocusForWidget(presContext, this, disp->mAppearance)) &&
1366 mDisplayFrame && IsVisibleForPainting(aBuilder)) {
1367 nsresult rv = aLists.Content()->AppendNewToTop(new (aBuilder)
1368 nsDisplayComboboxFocus(this));
1369 NS_ENSURE_SUCCESS(rv, rv);
1372 return DisplaySelectionOverlay(aBuilder, aLists);
1375 void nsComboboxControlFrame::PaintFocus(nsIRenderingContext& aRenderingContext,
1376 nsPoint aPt)
1378 /* Do we need to do anything? */
1379 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled) ||
1380 mFocused != this)
1381 return;
1383 aRenderingContext.PushState();
1384 nsRect clipRect = mDisplayFrame->GetRect() + aPt;
1385 aRenderingContext.SetClipRect(clipRect, nsClipCombine_kIntersect);
1387 // REVIEW: Why does the old code paint mDisplayFrame again? We've
1388 // already painted it in the children above. So clipping it here won't do
1389 // us much good.
1391 /////////////////////
1392 // draw focus
1394 aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
1395 aRenderingContext.SetColor(GetStyleColor()->mColor);
1397 //aRenderingContext.DrawRect(clipRect);
1399 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
1400 clipRect.width -= onePixel;
1401 clipRect.height -= onePixel;
1402 aRenderingContext.DrawLine(clipRect.x, clipRect.y,
1403 clipRect.x+clipRect.width, clipRect.y);
1404 aRenderingContext.DrawLine(clipRect.x+clipRect.width, clipRect.y,
1405 clipRect.x+clipRect.width, clipRect.y+clipRect.height);
1406 aRenderingContext.DrawLine(clipRect.x+clipRect.width, clipRect.y+clipRect.height,
1407 clipRect.x, clipRect.y+clipRect.height);
1408 aRenderingContext.DrawLine(clipRect.x, clipRect.y+clipRect.height,
1409 clipRect.x, clipRect.y);
1410 aRenderingContext.DrawLine(clipRect.x, clipRect.y+clipRect.height,
1411 clipRect.x, clipRect.y);
1413 aRenderingContext.PopState();
1416 //----------------------------------------------------------------------
1417 //nsIScrollableViewProvider
1418 //----------------------------------------------------------------------
1419 nsIScrollableView* nsComboboxControlFrame::GetScrollableView()
1421 if (!mDropdownFrame)
1422 return nsnull;
1424 nsIScrollableFrame* scrollable = do_QueryFrame(mDropdownFrame);
1425 if (!scrollable)
1426 return nsnull;
1428 return scrollable->GetScrollableView();
1431 //---------------------------------------------------------
1432 // gets the content (an option) by index and then set it as
1433 // being selected or not selected
1434 //---------------------------------------------------------
1435 NS_IMETHODIMP
1436 nsComboboxControlFrame::OnOptionSelected(nsPresContext* aPresContext,
1437 PRInt32 aIndex,
1438 PRBool aSelected)
1440 if (mDroppedDown) {
1441 nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1442 if (selectFrame) {
1443 selectFrame->OnOptionSelected(aPresContext, aIndex, aSelected);
1445 } else {
1446 if (aSelected) {
1447 RedisplayText(aIndex);
1448 } else {
1449 RedisplaySelectedText();
1450 FireValueChangeEvent(); // Fire after old option is unselected
1454 return NS_OK;
1457 void nsComboboxControlFrame::FireValueChangeEvent()
1459 // Fire ValueChange event to indicate data value of combo box has changed
1460 nsCOMPtr<nsIDOMEvent> event;
1461 nsPresContext* presContext = PresContext();
1462 if (NS_SUCCEEDED(nsEventDispatcher::CreateEvent(presContext, nsnull,
1463 NS_LITERAL_STRING("Events"),
1464 getter_AddRefs(event)))) {
1465 event->InitEvent(NS_LITERAL_STRING("ValueChange"), PR_TRUE, PR_TRUE);
1467 nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(event));
1468 privateEvent->SetTrusted(PR_TRUE);
1469 nsEventDispatcher::DispatchDOMEvent(mContent, nsnull, event, nsnull,
1470 nsnull);
1474 void
1475 nsComboboxControlFrame::OnContentReset()
1477 if (mListControlFrame) {
1478 mListControlFrame->OnContentReset();
1483 //--------------------------------------------------------
1484 // nsIStatefulFrame
1485 //--------------------------------------------------------
1486 NS_IMETHODIMP
1487 nsComboboxControlFrame::SaveState(SpecialStateID aStateID,
1488 nsPresState** aState)
1490 if (!mListControlFrame)
1491 return NS_ERROR_FAILURE;
1493 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1494 return stateful->SaveState(aStateID, aState);
1497 NS_IMETHODIMP
1498 nsComboboxControlFrame::RestoreState(nsPresState* aState)
1500 if (!mListControlFrame)
1501 return NS_ERROR_FAILURE;
1503 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
1504 NS_ASSERTION(stateful, "Must implement nsIStatefulFrame");
1505 return stateful->RestoreState(aState);
1510 // Camino uses a native widget for the combobox
1511 // popup, which affects drawing and event
1512 // handling here and in nsListControlFrame.
1515 /* static */
1516 PRBool
1517 nsComboboxControlFrame::ToolkitHasNativePopup()
1519 #ifdef XP_MACOSX
1520 return nsContentUtils::GetBoolPref("ui.use_native_popup_windows");
1521 #else
1522 return PR_FALSE;
1523 #endif