layout/forms/nsComboboxControlFrame.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5 #include "nsCOMPtr.h"
michael@0 6 #include "nsComboboxControlFrame.h"
michael@0 7 #include "nsFocusManager.h"
michael@0 8 #include "nsFormControlFrame.h"
michael@0 9 #include "nsGkAtoms.h"
michael@0 10 #include "nsCSSAnonBoxes.h"
michael@0 11 #include "nsHTMLParts.h"
michael@0 12 #include "nsIFormControl.h"
michael@0 13 #include "nsNameSpaceManager.h"
michael@0 14 #include "nsIListControlFrame.h"
michael@0 15 #include "nsPIDOMWindow.h"
michael@0 16 #include "nsIPresShell.h"
michael@0 17 #include "nsContentList.h"
michael@0 18 #include "nsView.h"
michael@0 19 #include "nsViewManager.h"
michael@0 20 #include "nsIDOMEventListener.h"
michael@0 21 #include "nsIDOMNode.h"
michael@0 22 #include "nsISelectControlFrame.h"
michael@0 23 #include "nsContentUtils.h"
michael@0 24 #include "nsIDocument.h"
michael@0 25 #include "nsINodeInfo.h"
michael@0 26 #include "nsIScrollableFrame.h"
michael@0 27 #include "nsListControlFrame.h"
michael@0 28 #include "nsAutoPtr.h"
michael@0 29 #include "nsStyleSet.h"
michael@0 30 #include "nsNodeInfoManager.h"
michael@0 31 #include "nsContentCreatorFunctions.h"
michael@0 32 #include "nsLayoutUtils.h"
michael@0 33 #include "nsDisplayList.h"
michael@0 34 #include "nsITheme.h"
michael@0 35 #include "nsRenderingContext.h"
michael@0 36 #include "mozilla/Likely.h"
michael@0 37 #include <algorithm>
michael@0 38 #include "nsTextNode.h"
michael@0 39 #include "mozilla/AsyncEventDispatcher.h"
michael@0 40 #include "mozilla/EventStates.h"
michael@0 41 #include "mozilla/LookAndFeel.h"
michael@0 42 #include "mozilla/MouseEvents.h"
michael@0 43 #include "mozilla/unused.h"
michael@0 44
michael@0 45 using namespace mozilla;
michael@0 46
michael@0 47 NS_IMETHODIMP
michael@0 48 nsComboboxControlFrame::RedisplayTextEvent::Run()
michael@0 49 {
michael@0 50 if (mControlFrame)
michael@0 51 mControlFrame->HandleRedisplayTextEvent();
michael@0 52 return NS_OK;
michael@0 53 }
michael@0 54
michael@0 55 class nsPresState;
michael@0 56
michael@0 57 #define FIX_FOR_BUG_53259
michael@0 58
michael@0 59 // Drop down list event management.
michael@0 60 // The combo box uses the following strategy for managing the drop-down list.
michael@0 61 // If the combo box or its arrow button is clicked on the drop-down list is displayed
michael@0 62 // If mouse exits the combo box with the drop-down list displayed the drop-down list
michael@0 63 // is asked to capture events
michael@0 64 // The drop-down list will capture all events including mouse down and up and will always
michael@0 65 // return with ListWasSelected method call regardless of whether an item in the list was
michael@0 66 // actually selected.
michael@0 67 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
michael@0 68 // The drop-down list does not explicitly set capture when it is in the drop-down mode.
michael@0 69
michael@0 70
michael@0 71 /**
michael@0 72 * Helper class that listens to the combo boxes button. If the button is pressed the
michael@0 73 * combo box is toggled to open or close. this is used by Accessibility which presses
michael@0 74 * that button Programmatically.
michael@0 75 */
michael@0 76 class nsComboButtonListener : public nsIDOMEventListener
michael@0 77 {
michael@0 78 public:
michael@0 79 NS_DECL_ISUPPORTS
michael@0 80
michael@0 81 NS_IMETHOD HandleEvent(nsIDOMEvent*) MOZ_OVERRIDE
michael@0 82 {
michael@0 83 mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
michael@0 84 return NS_OK;
michael@0 85 }
michael@0 86
michael@0 87 nsComboButtonListener(nsComboboxControlFrame* aCombobox)
michael@0 88 {
michael@0 89 mComboBox = aCombobox;
michael@0 90 }
michael@0 91
michael@0 92 virtual ~nsComboButtonListener() {}
michael@0 93
michael@0 94 nsComboboxControlFrame* mComboBox;
michael@0 95 };
michael@0 96
michael@0 97 NS_IMPL_ISUPPORTS(nsComboButtonListener,
michael@0 98 nsIDOMEventListener)
michael@0 99
michael@0 100 // static class data member for Bug 32920
michael@0 101 nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
michael@0 102
michael@0 103 nsIFrame*
michael@0 104 NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
michael@0 105 {
michael@0 106 nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
michael@0 107
michael@0 108 if (it) {
michael@0 109 // set the state flags (if any are provided)
michael@0 110 it->AddStateBits(aStateFlags);
michael@0 111 }
michael@0 112
michael@0 113 return it;
michael@0 114 }
michael@0 115
michael@0 116 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
michael@0 117
michael@0 118 //-----------------------------------------------------------
michael@0 119 // Reflow Debugging Macros
michael@0 120 // These let us "see" how many reflow counts are happening
michael@0 121 //-----------------------------------------------------------
michael@0 122 #ifdef DO_REFLOW_COUNTER
michael@0 123
michael@0 124 #define MAX_REFLOW_CNT 1024
michael@0 125 static int32_t gTotalReqs = 0;;
michael@0 126 static int32_t gTotalReflows = 0;;
michael@0 127 static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
michael@0 128 static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
michael@0 129 static int32_t gReflowInx = -1;
michael@0 130
michael@0 131 #define REFLOW_COUNTER() \
michael@0 132 if (mReflowId > -1) \
michael@0 133 gReflowControlCnt[mReflowId]++;
michael@0 134
michael@0 135 #define REFLOW_COUNTER_REQUEST() \
michael@0 136 if (mReflowId > -1) \
michael@0 137 gReflowControlCntRQ[mReflowId]++;
michael@0 138
michael@0 139 #define REFLOW_COUNTER_DUMP(__desc) \
michael@0 140 if (mReflowId > -1) {\
michael@0 141 gTotalReqs += gReflowControlCntRQ[mReflowId];\
michael@0 142 gTotalReflows += gReflowControlCnt[mReflowId];\
michael@0 143 printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
michael@0 144 mReflowId, (__desc), \
michael@0 145 gReflowControlCnt[mReflowId], \
michael@0 146 gReflowControlCntRQ[mReflowId],\
michael@0 147 gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
michael@0 148 }
michael@0 149
michael@0 150 #define REFLOW_COUNTER_INIT() \
michael@0 151 if (gReflowInx < MAX_REFLOW_CNT) { \
michael@0 152 gReflowInx++; \
michael@0 153 mReflowId = gReflowInx; \
michael@0 154 gReflowControlCnt[mReflowId] = 0; \
michael@0 155 gReflowControlCntRQ[mReflowId] = 0; \
michael@0 156 } else { \
michael@0 157 mReflowId = -1; \
michael@0 158 }
michael@0 159
michael@0 160 // reflow messages
michael@0 161 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
michael@0 162 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
michael@0 163 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
michael@0 164 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
michael@0 165
michael@0 166 #else //-------------
michael@0 167
michael@0 168 #define REFLOW_COUNTER_REQUEST()
michael@0 169 #define REFLOW_COUNTER()
michael@0 170 #define REFLOW_COUNTER_DUMP(__desc)
michael@0 171 #define REFLOW_COUNTER_INIT()
michael@0 172
michael@0 173 #define REFLOW_DEBUG_MSG(_msg)
michael@0 174 #define REFLOW_DEBUG_MSG2(_msg1, _msg2)
michael@0 175 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
michael@0 176 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
michael@0 177
michael@0 178
michael@0 179 #endif
michael@0 180
michael@0 181 //------------------------------------------
michael@0 182 // This is for being VERY noisy
michael@0 183 //------------------------------------------
michael@0 184 #ifdef DO_VERY_NOISY
michael@0 185 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
michael@0 186 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
michael@0 187 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
michael@0 188 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
michael@0 189 #else
michael@0 190 #define REFLOW_NOISY_MSG(_msg)
michael@0 191 #define REFLOW_NOISY_MSG2(_msg1, _msg2)
michael@0 192 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
michael@0 193 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
michael@0 194 #endif
michael@0 195
michael@0 196 //------------------------------------------
michael@0 197 // Displays value in pixels or twips
michael@0 198 //------------------------------------------
michael@0 199 #ifdef DO_PIXELS
michael@0 200 #define PX(__v) __v / 15
michael@0 201 #else
michael@0 202 #define PX(__v) __v
michael@0 203 #endif
michael@0 204
michael@0 205 //------------------------------------------------------
michael@0 206 //-- Done with macros
michael@0 207 //------------------------------------------------------
michael@0 208
michael@0 209 nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
michael@0 210 : nsBlockFrame(aContext)
michael@0 211 , mDisplayFrame(nullptr)
michael@0 212 , mButtonFrame(nullptr)
michael@0 213 , mDropdownFrame(nullptr)
michael@0 214 , mListControlFrame(nullptr)
michael@0 215 , mDisplayWidth(0)
michael@0 216 , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
michael@0 217 , mDisplayedIndex(-1)
michael@0 218 , mLastDropDownAboveScreenY(nscoord_MIN)
michael@0 219 , mLastDropDownBelowScreenY(nscoord_MIN)
michael@0 220 , mDroppedDown(false)
michael@0 221 , mInRedisplayText(false)
michael@0 222 , mDelayedShowDropDown(false)
michael@0 223 {
michael@0 224 REFLOW_COUNTER_INIT()
michael@0 225 }
michael@0 226
michael@0 227 //--------------------------------------------------------------
michael@0 228 nsComboboxControlFrame::~nsComboboxControlFrame()
michael@0 229 {
michael@0 230 REFLOW_COUNTER_DUMP("nsCCF");
michael@0 231 }
michael@0 232
michael@0 233 //--------------------------------------------------------------
michael@0 234
michael@0 235 NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
michael@0 236 NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
michael@0 237 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
michael@0 238 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
michael@0 239 NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
michael@0 240 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
michael@0 241 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
michael@0 242
michael@0 243 #ifdef ACCESSIBILITY
michael@0 244 a11y::AccType
michael@0 245 nsComboboxControlFrame::AccessibleType()
michael@0 246 {
michael@0 247 return a11y::eHTMLComboboxType;
michael@0 248 }
michael@0 249 #endif
michael@0 250
michael@0 251 void
michael@0 252 nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
michael@0 253 {
michael@0 254 nsWeakFrame weakFrame(this);
michael@0 255 if (aOn) {
michael@0 256 nsListControlFrame::ComboboxFocusSet();
michael@0 257 sFocused = this;
michael@0 258 if (mDelayedShowDropDown) {
michael@0 259 ShowDropDown(true); // might destroy us
michael@0 260 if (!weakFrame.IsAlive()) {
michael@0 261 return;
michael@0 262 }
michael@0 263 }
michael@0 264 } else {
michael@0 265 sFocused = nullptr;
michael@0 266 mDelayedShowDropDown = false;
michael@0 267 if (mDroppedDown) {
michael@0 268 mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
michael@0 269 if (!weakFrame.IsAlive()) {
michael@0 270 return;
michael@0 271 }
michael@0 272 }
michael@0 273 // May delete |this|.
michael@0 274 mListControlFrame->FireOnChange();
michael@0 275 }
michael@0 276
michael@0 277 if (!weakFrame.IsAlive()) {
michael@0 278 return;
michael@0 279 }
michael@0 280
michael@0 281 // This is needed on a temporary basis. It causes the focus
michael@0 282 // rect to be drawn. This is much faster than ReResolvingStyle
michael@0 283 // Bug 32920
michael@0 284 InvalidateFrame();
michael@0 285 }
michael@0 286
michael@0 287 void
michael@0 288 nsComboboxControlFrame::ShowPopup(bool aShowPopup)
michael@0 289 {
michael@0 290 nsView* view = mDropdownFrame->GetView();
michael@0 291 nsViewManager* viewManager = view->GetViewManager();
michael@0 292
michael@0 293 if (aShowPopup) {
michael@0 294 nsRect rect = mDropdownFrame->GetRect();
michael@0 295 rect.x = rect.y = 0;
michael@0 296 viewManager->ResizeView(view, rect);
michael@0 297 viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
michael@0 298 } else {
michael@0 299 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
michael@0 300 nsRect emptyRect(0, 0, 0, 0);
michael@0 301 viewManager->ResizeView(view, emptyRect);
michael@0 302 }
michael@0 303
michael@0 304 // fire a popup dom event
michael@0 305 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 306 WidgetMouseEvent event(true, aShowPopup ?
michael@0 307 NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nullptr,
michael@0 308 WidgetMouseEvent::eReal);
michael@0 309
michael@0 310 nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
michael@0 311 if (shell)
michael@0 312 shell->HandleDOMEventWithTarget(mContent, &event, &status);
michael@0 313 }
michael@0 314
michael@0 315 bool
michael@0 316 nsComboboxControlFrame::ShowList(bool aShowList)
michael@0 317 {
michael@0 318 nsView* view = mDropdownFrame->GetView();
michael@0 319 if (aShowList) {
michael@0 320 NS_ASSERTION(!view->HasWidget(),
michael@0 321 "We shouldn't have a widget before we need to display the popup");
michael@0 322
michael@0 323 // Create the widget for the drop-down list
michael@0 324 view->GetViewManager()->SetViewFloating(view, true);
michael@0 325
michael@0 326 nsWidgetInitData widgetData;
michael@0 327 widgetData.mWindowType = eWindowType_popup;
michael@0 328 widgetData.mBorderStyle = eBorderStyle_default;
michael@0 329 view->CreateWidgetForPopup(&widgetData);
michael@0 330 } else {
michael@0 331 nsIWidget* widget = view->GetWidget();
michael@0 332 if (widget) {
michael@0 333 // We must do this before ShowPopup in case it destroys us (bug 813442).
michael@0 334 widget->CaptureRollupEvents(this, false);
michael@0 335 }
michael@0 336 }
michael@0 337
michael@0 338 nsWeakFrame weakFrame(this);
michael@0 339 ShowPopup(aShowList); // might destroy us
michael@0 340 if (!weakFrame.IsAlive()) {
michael@0 341 return false;
michael@0 342 }
michael@0 343
michael@0 344 mDroppedDown = aShowList;
michael@0 345 nsIWidget* widget = view->GetWidget();
michael@0 346 if (mDroppedDown) {
michael@0 347 // The listcontrol frame will call back to the nsComboboxControlFrame's
michael@0 348 // ListWasSelected which will stop the capture.
michael@0 349 mListControlFrame->AboutToDropDown();
michael@0 350 mListControlFrame->CaptureMouseEvents(true);
michael@0 351 if (widget) {
michael@0 352 widget->CaptureRollupEvents(this, true);
michael@0 353 }
michael@0 354 } else {
michael@0 355 if (widget) {
michael@0 356 view->DestroyWidget();
michael@0 357 }
michael@0 358 }
michael@0 359
michael@0 360 return weakFrame.IsAlive();
michael@0 361 }
michael@0 362
michael@0 363 class nsResizeDropdownAtFinalPosition
michael@0 364 : public nsIReflowCallback, public nsRunnable
michael@0 365 {
michael@0 366 public:
michael@0 367 nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
michael@0 368 : mFrame(aFrame)
michael@0 369 {
michael@0 370 MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition);
michael@0 371 }
michael@0 372 ~nsResizeDropdownAtFinalPosition()
michael@0 373 {
michael@0 374 MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition);
michael@0 375 }
michael@0 376
michael@0 377 virtual bool ReflowFinished() MOZ_OVERRIDE
michael@0 378 {
michael@0 379 Run();
michael@0 380 NS_RELEASE_THIS();
michael@0 381 return false;
michael@0 382 }
michael@0 383
michael@0 384 virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
michael@0 385 {
michael@0 386 NS_RELEASE_THIS();
michael@0 387 }
michael@0 388
michael@0 389 NS_IMETHODIMP Run()
michael@0 390 {
michael@0 391 if (mFrame.IsAlive()) {
michael@0 392 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
michael@0 393 AbsolutelyPositionDropDown();
michael@0 394 }
michael@0 395 return NS_OK;
michael@0 396 }
michael@0 397
michael@0 398 nsWeakFrame mFrame;
michael@0 399 };
michael@0 400
michael@0 401 nsresult
michael@0 402 nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
michael@0 403 const nsHTMLReflowState& aReflowState)
michael@0 404 {
michael@0 405 // All we want out of it later on, really, is the height of a row, so we
michael@0 406 // don't even need to cache mDropdownFrame's ascent or anything. If we don't
michael@0 407 // need to reflow it, just bail out here.
michael@0 408 if (!aReflowState.ShouldReflowAllKids() &&
michael@0 409 !NS_SUBTREE_DIRTY(mDropdownFrame)) {
michael@0 410 return NS_OK;
michael@0 411 }
michael@0 412
michael@0 413 // XXXbz this will, for small-height dropdowns, have extra space on the right
michael@0 414 // edge for the scrollbar we don't show... but that's the best we can do here
michael@0 415 // for now.
michael@0 416 nsSize availSize(aReflowState.AvailableWidth(), NS_UNCONSTRAINEDSIZE);
michael@0 417 nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame,
michael@0 418 availSize);
michael@0 419
michael@0 420 // If the dropdown's intrinsic width is narrower than our specified width,
michael@0 421 // then expand it out. We want our border-box width to end up the same as
michael@0 422 // the dropdown's so account for both sets of mComputedBorderPadding.
michael@0 423 nscoord forcedWidth = aReflowState.ComputedWidth() +
michael@0 424 aReflowState.ComputedPhysicalBorderPadding().LeftRight() -
michael@0 425 kidReflowState.ComputedPhysicalBorderPadding().LeftRight();
michael@0 426 kidReflowState.SetComputedWidth(std::max(kidReflowState.ComputedWidth(),
michael@0 427 forcedWidth));
michael@0 428
michael@0 429 // ensure we start off hidden
michael@0 430 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
michael@0 431 nsView* view = mDropdownFrame->GetView();
michael@0 432 nsViewManager* viewManager = view->GetViewManager();
michael@0 433 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
michael@0 434 nsRect emptyRect(0, 0, 0, 0);
michael@0 435 viewManager->ResizeView(view, emptyRect);
michael@0 436 }
michael@0 437
michael@0 438 // Allow the child to move/size/change-visibility its view if it's currently
michael@0 439 // dropped down
michael@0 440 int32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW;
michael@0 441 if (mDroppedDown) {
michael@0 442 flags = 0;
michael@0 443 }
michael@0 444 nsRect rect = mDropdownFrame->GetRect();
michael@0 445 nsHTMLReflowMetrics desiredSize(aReflowState);
michael@0 446 nsReflowStatus ignoredStatus;
michael@0 447 nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize,
michael@0 448 kidReflowState, rect.x, rect.y, flags,
michael@0 449 ignoredStatus);
michael@0 450
michael@0 451 // Set the child's width and height to its desired size
michael@0 452 FinishReflowChild(mDropdownFrame, aPresContext, desiredSize,
michael@0 453 &kidReflowState, rect.x, rect.y, flags);
michael@0 454 return rv;
michael@0 455 }
michael@0 456
michael@0 457 nsPoint
michael@0 458 nsComboboxControlFrame::GetCSSTransformTranslation()
michael@0 459 {
michael@0 460 nsIFrame* frame = this;
michael@0 461 bool is3DTransform = false;
michael@0 462 gfxMatrix transform;
michael@0 463 while (frame) {
michael@0 464 nsIFrame* parent;
michael@0 465 gfx3DMatrix ctm = frame->GetTransformMatrix(nullptr, &parent);
michael@0 466 gfxMatrix matrix;
michael@0 467 if (ctm.Is2D(&matrix)) {
michael@0 468 transform = transform * matrix;
michael@0 469 } else {
michael@0 470 is3DTransform = true;
michael@0 471 break;
michael@0 472 }
michael@0 473 frame = parent;
michael@0 474 }
michael@0 475 nsPoint translation;
michael@0 476 if (!is3DTransform && !transform.HasNonTranslation()) {
michael@0 477 nsPresContext* pc = PresContext();
michael@0 478 gfxPoint pixelTranslation = transform.GetTranslation();
michael@0 479 int32_t apd = pc->AppUnitsPerDevPixel();
michael@0 480 translation.x = NSFloatPixelsToAppUnits(float(pixelTranslation.x), apd);
michael@0 481 translation.y = NSFloatPixelsToAppUnits(float(pixelTranslation.y), apd);
michael@0 482 // To get the translation introduced only by transforms we subtract the
michael@0 483 // regular non-transform translation.
michael@0 484 nsRootPresContext* rootPC = pc->GetRootPresContext();
michael@0 485 if (rootPC) {
michael@0 486 translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
michael@0 487 } else {
michael@0 488 translation.x = translation.y = 0;
michael@0 489 }
michael@0 490 }
michael@0 491 return translation;
michael@0 492 }
michael@0 493
michael@0 494 class nsAsyncRollup : public nsRunnable
michael@0 495 {
michael@0 496 public:
michael@0 497 nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
michael@0 498 NS_IMETHODIMP Run()
michael@0 499 {
michael@0 500 if (mFrame.IsAlive()) {
michael@0 501 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
michael@0 502 ->RollupFromList();
michael@0 503 }
michael@0 504 return NS_OK;
michael@0 505 }
michael@0 506 nsWeakFrame mFrame;
michael@0 507 };
michael@0 508
michael@0 509 class nsAsyncResize : public nsRunnable
michael@0 510 {
michael@0 511 public:
michael@0 512 nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
michael@0 513 NS_IMETHODIMP Run()
michael@0 514 {
michael@0 515 if (mFrame.IsAlive()) {
michael@0 516 nsComboboxControlFrame* combo =
michael@0 517 static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
michael@0 518 static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
michael@0 519 SetSuppressScrollbarUpdate(true);
michael@0 520 nsCOMPtr<nsIPresShell> shell = mFrame->PresContext()->PresShell();
michael@0 521 shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
michael@0 522 NS_FRAME_IS_DIRTY);
michael@0 523 shell->FlushPendingNotifications(Flush_Layout);
michael@0 524 if (mFrame.IsAlive()) {
michael@0 525 combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
michael@0 526 static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
michael@0 527 SetSuppressScrollbarUpdate(false);
michael@0 528 if (combo->mDelayedShowDropDown) {
michael@0 529 combo->ShowDropDown(true);
michael@0 530 }
michael@0 531 }
michael@0 532 }
michael@0 533 return NS_OK;
michael@0 534 }
michael@0 535 nsWeakFrame mFrame;
michael@0 536 };
michael@0 537
michael@0 538 void
michael@0 539 nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord* aAbove,
michael@0 540 nscoord* aBelow,
michael@0 541 nsPoint* aTranslation)
michael@0 542 {
michael@0 543 // Note: At first glance, it appears that you could simply get the absolute
michael@0 544 // bounding box for the dropdown list by first getting its view, then getting
michael@0 545 // the view's nsIWidget, then asking the nsIWidget for its AbsoluteBounds.
michael@0 546 // The problem with this approach, is that the dropdown lists y location can
michael@0 547 // change based on whether the dropdown is placed below or above the display
michael@0 548 // frame. The approach, taken here is to get the absolute position of the
michael@0 549 // display frame and use its location to determine if the dropdown will go
michael@0 550 // offscreen.
michael@0 551
michael@0 552 // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
michael@0 553 // In the special case that our transform is only a 2D translation we
michael@0 554 // introduce this hack so that the dropdown will show up in the right place.
michael@0 555 *aTranslation = GetCSSTransformTranslation();
michael@0 556 *aAbove = 0;
michael@0 557 *aBelow = 0;
michael@0 558
michael@0 559 nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
michael@0 560 if (mLastDropDownBelowScreenY == nscoord_MIN) {
michael@0 561 nsRect thisScreenRect = GetScreenRectInAppUnits();
michael@0 562 mLastDropDownBelowScreenY = thisScreenRect.YMost() + aTranslation->y;
michael@0 563 mLastDropDownAboveScreenY = thisScreenRect.y + aTranslation->y;
michael@0 564 }
michael@0 565
michael@0 566 nscoord minY;
michael@0 567 nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
michael@0 568 nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
michael@0 569 if (root) {
michael@0 570 minY = root->GetScreenRectInAppUnits().y;
michael@0 571 if (mLastDropDownBelowScreenY < minY) {
michael@0 572 // Don't allow the drop-down to be placed above the content area.
michael@0 573 return;
michael@0 574 }
michael@0 575 } else {
michael@0 576 minY = screen.y;
michael@0 577 }
michael@0 578
michael@0 579 nscoord below = screen.YMost() - mLastDropDownBelowScreenY;
michael@0 580 nscoord above = mLastDropDownAboveScreenY - minY;
michael@0 581
michael@0 582 // If the difference between the space above and below is less
michael@0 583 // than a row-height, then we favor the space below.
michael@0 584 if (above >= below) {
michael@0 585 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
michael@0 586 nscoord rowHeight = lcf->GetHeightOfARow();
michael@0 587 if (above < below + rowHeight) {
michael@0 588 above -= rowHeight;
michael@0 589 }
michael@0 590 }
michael@0 591
michael@0 592 *aBelow = below;
michael@0 593 *aAbove = above;
michael@0 594 }
michael@0 595
michael@0 596 nsComboboxControlFrame::DropDownPositionState
michael@0 597 nsComboboxControlFrame::AbsolutelyPositionDropDown()
michael@0 598 {
michael@0 599 nsPoint translation;
michael@0 600 nscoord above, below;
michael@0 601 mLastDropDownBelowScreenY = nscoord_MIN;
michael@0 602 GetAvailableDropdownSpace(&above, &below, &translation);
michael@0 603 if (above <= 0 && below <= 0) {
michael@0 604 if (IsDroppedDown()) {
michael@0 605 // Hide the view immediately to minimize flicker.
michael@0 606 nsView* view = mDropdownFrame->GetView();
michael@0 607 view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
michael@0 608 NS_DispatchToCurrentThread(new nsAsyncRollup(this));
michael@0 609 }
michael@0 610 return eDropDownPositionSuppressed;
michael@0 611 }
michael@0 612
michael@0 613 nsSize dropdownSize = mDropdownFrame->GetSize();
michael@0 614 nscoord height = std::max(above, below);
michael@0 615 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
michael@0 616 if (height < dropdownSize.height) {
michael@0 617 if (lcf->GetNumDisplayRows() > 1) {
michael@0 618 // The drop-down doesn't fit and currently shows more than 1 row -
michael@0 619 // schedule a resize to show fewer rows.
michael@0 620 NS_DispatchToCurrentThread(new nsAsyncResize(this));
michael@0 621 return eDropDownPositionPendingResize;
michael@0 622 }
michael@0 623 } else if (height > (dropdownSize.height + lcf->GetHeightOfARow() * 1.5) &&
michael@0 624 lcf->GetDropdownCanGrow()) {
michael@0 625 // The drop-down fits but there is room for at least 1.5 more rows -
michael@0 626 // schedule a resize to show more rows if it has more rows to show.
michael@0 627 // (1.5 rows for good measure to avoid any rounding issues that would
michael@0 628 // lead to a loop of reflow requests)
michael@0 629 NS_DispatchToCurrentThread(new nsAsyncResize(this));
michael@0 630 return eDropDownPositionPendingResize;
michael@0 631 }
michael@0 632
michael@0 633 // Position the drop-down below if there is room, otherwise place it above
michael@0 634 // if there is room. If there is no room for it on either side then place
michael@0 635 // it below (to avoid overlapping UI like the URL bar).
michael@0 636 bool b = dropdownSize.height <= below || dropdownSize.height > above;
michael@0 637 nsPoint dropdownPosition(0, b ? GetRect().height : -dropdownSize.height);
michael@0 638 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
michael@0 639 // Align the right edge of the drop-down with the right edge of the control.
michael@0 640 dropdownPosition.x = GetRect().width - dropdownSize.width;
michael@0 641 }
michael@0 642
michael@0 643 // Don't position the view unless the position changed since it might cause
michael@0 644 // a call to NotifyGeometryChange() and an infinite loop here.
michael@0 645 const nsPoint currentPos = mDropdownFrame->GetPosition();
michael@0 646 const nsPoint newPos = dropdownPosition + translation;
michael@0 647 if (currentPos != newPos) {
michael@0 648 mDropdownFrame->SetPosition(newPos);
michael@0 649 nsContainerFrame::PositionFrameView(mDropdownFrame);
michael@0 650 }
michael@0 651 return eDropDownPositionFinal;
michael@0 652 }
michael@0 653
michael@0 654 void
michael@0 655 nsComboboxControlFrame::NotifyGeometryChange()
michael@0 656 {
michael@0 657 // We don't need to resize if we're not dropped down since ShowDropDown
michael@0 658 // does that, or if we're dirty then the reflow callback does it,
michael@0 659 // or if we have a delayed ShowDropDown pending.
michael@0 660 if (IsDroppedDown() &&
michael@0 661 !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
michael@0 662 !mDelayedShowDropDown) {
michael@0 663 // Async because we're likely in a middle of a scroll here so
michael@0 664 // frame/view positions are in flux.
michael@0 665 nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
michael@0 666 new nsResizeDropdownAtFinalPosition(this);
michael@0 667 NS_DispatchToCurrentThread(resize);
michael@0 668 }
michael@0 669 }
michael@0 670
michael@0 671 //----------------------------------------------------------
michael@0 672 //
michael@0 673 //----------------------------------------------------------
michael@0 674 #ifdef DO_REFLOW_DEBUG
michael@0 675 static int myCounter = 0;
michael@0 676
michael@0 677 static void printSize(char * aDesc, nscoord aSize)
michael@0 678 {
michael@0 679 printf(" %s: ", aDesc);
michael@0 680 if (aSize == NS_UNCONSTRAINEDSIZE) {
michael@0 681 printf("UC");
michael@0 682 } else {
michael@0 683 printf("%d", PX(aSize));
michael@0 684 }
michael@0 685 }
michael@0 686 #endif
michael@0 687
michael@0 688 //-------------------------------------------------------------------
michael@0 689 //-- Main Reflow for the Combobox
michael@0 690 //-------------------------------------------------------------------
michael@0 691
michael@0 692 nscoord
michael@0 693 nsComboboxControlFrame::GetIntrinsicWidth(nsRenderingContext* aRenderingContext,
michael@0 694 nsLayoutUtils::IntrinsicWidthType aType)
michael@0 695 {
michael@0 696 // get the scrollbar width, we'll use this later
michael@0 697 nscoord scrollbarWidth = 0;
michael@0 698 nsPresContext* presContext = PresContext();
michael@0 699 if (mListControlFrame) {
michael@0 700 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
michael@0 701 NS_ASSERTION(scrollable, "List must be a scrollable frame");
michael@0 702 scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
michael@0 703 presContext, aRenderingContext);
michael@0 704 }
michael@0 705
michael@0 706 nscoord displayWidth = 0;
michael@0 707 if (MOZ_LIKELY(mDisplayFrame)) {
michael@0 708 displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
michael@0 709 mDisplayFrame,
michael@0 710 aType);
michael@0 711 }
michael@0 712
michael@0 713 if (mDropdownFrame) {
michael@0 714 nscoord dropdownContentWidth;
michael@0 715 bool isUsingOverlayScrollbars =
michael@0 716 LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
michael@0 717 if (aType == nsLayoutUtils::MIN_WIDTH) {
michael@0 718 dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext);
michael@0 719 if (isUsingOverlayScrollbars) {
michael@0 720 dropdownContentWidth += scrollbarWidth;
michael@0 721 }
michael@0 722 } else {
michael@0 723 NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type");
michael@0 724 dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext);
michael@0 725 if (isUsingOverlayScrollbars) {
michael@0 726 dropdownContentWidth += scrollbarWidth;
michael@0 727 }
michael@0 728 }
michael@0 729 dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth,
michael@0 730 scrollbarWidth,
michael@0 731 nscoord_MAX);
michael@0 732
michael@0 733 displayWidth = std::max(dropdownContentWidth, displayWidth);
michael@0 734 }
michael@0 735
michael@0 736 // add room for the dropmarker button if there is one
michael@0 737 if (!IsThemed() || presContext->GetTheme()->ThemeNeedsComboboxDropmarker())
michael@0 738 displayWidth += scrollbarWidth;
michael@0 739
michael@0 740 return displayWidth;
michael@0 741
michael@0 742 }
michael@0 743
michael@0 744 nscoord
michael@0 745 nsComboboxControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
michael@0 746 {
michael@0 747 nscoord minWidth;
michael@0 748 DISPLAY_MIN_WIDTH(this, minWidth);
michael@0 749 minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH);
michael@0 750 return minWidth;
michael@0 751 }
michael@0 752
michael@0 753 nscoord
michael@0 754 nsComboboxControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
michael@0 755 {
michael@0 756 nscoord prefWidth;
michael@0 757 DISPLAY_PREF_WIDTH(this, prefWidth);
michael@0 758 prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH);
michael@0 759 return prefWidth;
michael@0 760 }
michael@0 761
michael@0 762 nsresult
michael@0 763 nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
michael@0 764 nsHTMLReflowMetrics& aDesiredSize,
michael@0 765 const nsHTMLReflowState& aReflowState,
michael@0 766 nsReflowStatus& aStatus)
michael@0 767 {
michael@0 768 // Constraints we try to satisfy:
michael@0 769
michael@0 770 // 1) Default width of button is the vertical scrollbar size
michael@0 771 // 2) If the width of button is bigger than our width, set width of
michael@0 772 // button to 0.
michael@0 773 // 3) Default height of button is height of display area
michael@0 774 // 4) Width of display area is whatever is left over from our width after
michael@0 775 // allocating width for the button.
michael@0 776 // 5) Height of display area is GetHeightOfARow() on the
michael@0 777 // mListControlFrame.
michael@0 778
michael@0 779 if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
michael@0 780 NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
michael@0 781 return NS_ERROR_UNEXPECTED;
michael@0 782 }
michael@0 783
michael@0 784 // Make sure the displayed text is the same as the selected option, bug 297389.
michael@0 785 int32_t selectedIndex;
michael@0 786 nsAutoString selectedOptionText;
michael@0 787 if (!mDroppedDown) {
michael@0 788 selectedIndex = mListControlFrame->GetSelectedIndex();
michael@0 789 }
michael@0 790 else {
michael@0 791 // In dropped down mode the "selected index" is the hovered menu item,
michael@0 792 // we want the last selected item which is |mDisplayedIndex| in this case.
michael@0 793 selectedIndex = mDisplayedIndex;
michael@0 794 }
michael@0 795 if (selectedIndex != -1) {
michael@0 796 mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
michael@0 797 }
michael@0 798 if (mDisplayedOptionText != selectedOptionText) {
michael@0 799 RedisplayText(selectedIndex);
michael@0 800 }
michael@0 801
michael@0 802 // First reflow our dropdown so that we know how tall we should be.
michael@0 803 ReflowDropdown(aPresContext, aReflowState);
michael@0 804 nsRefPtr<nsResizeDropdownAtFinalPosition> resize =
michael@0 805 new nsResizeDropdownAtFinalPosition(this);
michael@0 806 if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
michael@0 807 // The reflow callback queue doesn't AddRef so we keep it alive until
michael@0 808 // it's released in its ReflowFinished / ReflowCallbackCanceled.
michael@0 809 unused << resize.forget();
michael@0 810 }
michael@0 811
michael@0 812 // Get the width of the vertical scrollbar. That will be the width of the
michael@0 813 // dropdown button.
michael@0 814 nscoord buttonWidth;
michael@0 815 const nsStyleDisplay *disp = StyleDisplay();
michael@0 816 if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) {
michael@0 817 buttonWidth = 0;
michael@0 818 }
michael@0 819 else {
michael@0 820 nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
michael@0 821 NS_ASSERTION(scrollable, "List must be a scrollable frame");
michael@0 822 buttonWidth = scrollable->GetNondisappearingScrollbarWidth(
michael@0 823 PresContext(), aReflowState.rendContext);
michael@0 824 if (buttonWidth > aReflowState.ComputedWidth()) {
michael@0 825 buttonWidth = 0;
michael@0 826 }
michael@0 827 }
michael@0 828
michael@0 829 mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth;
michael@0 830
michael@0 831 nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState,
michael@0 832 aStatus);
michael@0 833 NS_ENSURE_SUCCESS(rv, rv);
michael@0 834
michael@0 835 // The button should occupy the same space as a scrollbar
michael@0 836 nsRect buttonRect = mButtonFrame->GetRect();
michael@0 837
michael@0 838 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
michael@0 839 buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().left -
michael@0 840 aReflowState.ComputedPhysicalPadding().left;
michael@0 841 }
michael@0 842 else {
michael@0 843 buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().LeftRight() +
michael@0 844 mDisplayWidth -
michael@0 845 (aReflowState.ComputedPhysicalBorderPadding().right -
michael@0 846 aReflowState.ComputedPhysicalPadding().right);
michael@0 847 }
michael@0 848 buttonRect.width = buttonWidth;
michael@0 849
michael@0 850 buttonRect.y = this->GetUsedBorder().top;
michael@0 851 buttonRect.height = mDisplayFrame->GetRect().height +
michael@0 852 this->GetUsedPadding().TopBottom();
michael@0 853
michael@0 854 mButtonFrame->SetRect(buttonRect);
michael@0 855
michael@0 856 if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
michael@0 857 !NS_FRAME_IS_FULLY_COMPLETE(aStatus)) {
michael@0 858 // This frame didn't fit inside a fragmentation container. Splitting
michael@0 859 // a nsComboboxControlFrame makes no sense, so we override the status here.
michael@0 860 aStatus = NS_FRAME_COMPLETE;
michael@0 861 }
michael@0 862 return rv;
michael@0 863 }
michael@0 864
michael@0 865 //--------------------------------------------------------------
michael@0 866
michael@0 867 nsIAtom*
michael@0 868 nsComboboxControlFrame::GetType() const
michael@0 869 {
michael@0 870 return nsGkAtoms::comboboxControlFrame;
michael@0 871 }
michael@0 872
michael@0 873 #ifdef DEBUG_FRAME_DUMP
michael@0 874 nsresult
michael@0 875 nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
michael@0 876 {
michael@0 877 return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
michael@0 878 }
michael@0 879 #endif
michael@0 880
michael@0 881
michael@0 882 //----------------------------------------------------------------------
michael@0 883 // nsIComboboxControlFrame
michael@0 884 //----------------------------------------------------------------------
michael@0 885 void
michael@0 886 nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
michael@0 887 {
michael@0 888 mDelayedShowDropDown = false;
michael@0 889 EventStates eventStates = mContent->AsElement()->State();
michael@0 890 if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
michael@0 891 return;
michael@0 892 }
michael@0 893
michael@0 894 if (!mDroppedDown && aDoDropDown) {
michael@0 895 nsFocusManager* fm = nsFocusManager::GetFocusManager();
michael@0 896 if (!fm || fm->GetFocusedContent() == GetContent()) {
michael@0 897 DropDownPositionState state = AbsolutelyPositionDropDown();
michael@0 898 if (state == eDropDownPositionFinal) {
michael@0 899 ShowList(aDoDropDown); // might destroy us
michael@0 900 } else if (state == eDropDownPositionPendingResize) {
michael@0 901 // Delay until after the resize reflow, see nsAsyncResize.
michael@0 902 mDelayedShowDropDown = true;
michael@0 903 }
michael@0 904 } else {
michael@0 905 // Delay until we get focus, see SetFocus().
michael@0 906 mDelayedShowDropDown = true;
michael@0 907 }
michael@0 908 } else if (mDroppedDown && !aDoDropDown) {
michael@0 909 ShowList(aDoDropDown); // might destroy us
michael@0 910 }
michael@0 911 }
michael@0 912
michael@0 913 void
michael@0 914 nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
michael@0 915 {
michael@0 916 mDropdownFrame = aDropDownFrame;
michael@0 917 mListControlFrame = do_QueryFrame(mDropdownFrame);
michael@0 918 }
michael@0 919
michael@0 920 nsIFrame*
michael@0 921 nsComboboxControlFrame::GetDropDown()
michael@0 922 {
michael@0 923 return mDropdownFrame;
michael@0 924 }
michael@0 925
michael@0 926 ///////////////////////////////////////////////////////////////
michael@0 927
michael@0 928 NS_IMETHODIMP
michael@0 929 nsComboboxControlFrame::RedisplaySelectedText()
michael@0 930 {
michael@0 931 nsAutoScriptBlocker scriptBlocker;
michael@0 932 return RedisplayText(mListControlFrame->GetSelectedIndex());
michael@0 933 }
michael@0 934
michael@0 935 nsresult
michael@0 936 nsComboboxControlFrame::RedisplayText(int32_t aIndex)
michael@0 937 {
michael@0 938 // Get the text to display
michael@0 939 if (aIndex != -1) {
michael@0 940 mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
michael@0 941 } else {
michael@0 942 mDisplayedOptionText.Truncate();
michael@0 943 }
michael@0 944 mDisplayedIndex = aIndex;
michael@0 945
michael@0 946 REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
michael@0 947 NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
michael@0 948
michael@0 949 // Send reflow command because the new text maybe larger
michael@0 950 nsresult rv = NS_OK;
michael@0 951 if (mDisplayContent) {
michael@0 952 // Don't call ActuallyDisplayText(true) directly here since that
michael@0 953 // could cause recursive frame construction. See bug 283117 and the comment in
michael@0 954 // HandleRedisplayTextEvent() below.
michael@0 955
michael@0 956 // Revoke outstanding events to avoid out-of-order events which could mean
michael@0 957 // displaying the wrong text.
michael@0 958 mRedisplayTextEvent.Revoke();
michael@0 959
michael@0 960 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
michael@0 961 "If we happen to run our redisplay event now, we might kill "
michael@0 962 "ourselves!");
michael@0 963
michael@0 964 nsRefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
michael@0 965 mRedisplayTextEvent = event;
michael@0 966 if (!nsContentUtils::AddScriptRunner(event))
michael@0 967 mRedisplayTextEvent.Forget();
michael@0 968 }
michael@0 969 return rv;
michael@0 970 }
michael@0 971
michael@0 972 void
michael@0 973 nsComboboxControlFrame::HandleRedisplayTextEvent()
michael@0 974 {
michael@0 975 // First, make sure that the content model is up to date and we've
michael@0 976 // constructed the frames for all our content in the right places.
michael@0 977 // Otherwise they'll end up under the wrong insertion frame when we
michael@0 978 // ActuallyDisplayText, since that flushes out the content sink by
michael@0 979 // calling SetText on a DOM node with aNotify set to true. See bug
michael@0 980 // 289730.
michael@0 981 nsWeakFrame weakThis(this);
michael@0 982 PresContext()->Document()->
michael@0 983 FlushPendingNotifications(Flush_ContentAndNotify);
michael@0 984 if (!weakThis.IsAlive())
michael@0 985 return;
michael@0 986
michael@0 987 // Redirect frame insertions during this method (see GetContentInsertionFrame())
michael@0 988 // so that any reframing that the frame constructor forces upon us is inserted
michael@0 989 // into the correct parent (mDisplayFrame). See bug 282607.
michael@0 990 NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
michael@0 991 mInRedisplayText = true;
michael@0 992 mRedisplayTextEvent.Forget();
michael@0 993
michael@0 994 ActuallyDisplayText(true);
michael@0 995 // XXXbz This should perhaps be eResize. Check.
michael@0 996 PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
michael@0 997 nsIPresShell::eStyleChange,
michael@0 998 NS_FRAME_IS_DIRTY);
michael@0 999
michael@0 1000 mInRedisplayText = false;
michael@0 1001 }
michael@0 1002
michael@0 1003 void
michael@0 1004 nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
michael@0 1005 {
michael@0 1006 if (mDisplayedOptionText.IsEmpty()) {
michael@0 1007 // Have to use a non-breaking space for line-height calculations
michael@0 1008 // to be right
michael@0 1009 static const char16_t space = 0xA0;
michael@0 1010 mDisplayContent->SetText(&space, 1, aNotify);
michael@0 1011 } else {
michael@0 1012 mDisplayContent->SetText(mDisplayedOptionText, aNotify);
michael@0 1013 }
michael@0 1014 }
michael@0 1015
michael@0 1016 int32_t
michael@0 1017 nsComboboxControlFrame::GetIndexOfDisplayArea()
michael@0 1018 {
michael@0 1019 return mDisplayedIndex;
michael@0 1020 }
michael@0 1021
michael@0 1022 //----------------------------------------------------------------------
michael@0 1023 // nsISelectControlFrame
michael@0 1024 //----------------------------------------------------------------------
michael@0 1025 NS_IMETHODIMP
michael@0 1026 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
michael@0 1027 {
michael@0 1028 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
michael@0 1029 if (!listFrame)
michael@0 1030 return NS_ERROR_FAILURE;
michael@0 1031
michael@0 1032 return listFrame->DoneAddingChildren(aIsDone);
michael@0 1033 }
michael@0 1034
michael@0 1035 NS_IMETHODIMP
michael@0 1036 nsComboboxControlFrame::AddOption(int32_t aIndex)
michael@0 1037 {
michael@0 1038 if (aIndex <= mDisplayedIndex) {
michael@0 1039 ++mDisplayedIndex;
michael@0 1040 }
michael@0 1041
michael@0 1042 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
michael@0 1043 return lcf->AddOption(aIndex);
michael@0 1044 }
michael@0 1045
michael@0 1046
michael@0 1047 NS_IMETHODIMP
michael@0 1048 nsComboboxControlFrame::RemoveOption(int32_t aIndex)
michael@0 1049 {
michael@0 1050 nsWeakFrame weakThis(this);
michael@0 1051 if (mListControlFrame->GetNumberOfOptions() > 0) {
michael@0 1052 if (aIndex < mDisplayedIndex) {
michael@0 1053 --mDisplayedIndex;
michael@0 1054 } else if (aIndex == mDisplayedIndex) {
michael@0 1055 mDisplayedIndex = 0; // IE6 compat
michael@0 1056 RedisplayText(mDisplayedIndex);
michael@0 1057 }
michael@0 1058 }
michael@0 1059 else {
michael@0 1060 // If we removed the last option, we need to blank things out
michael@0 1061 RedisplayText(-1);
michael@0 1062 }
michael@0 1063
michael@0 1064 if (!weakThis.IsAlive())
michael@0 1065 return NS_OK;
michael@0 1066
michael@0 1067 nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
michael@0 1068 return lcf->RemoveOption(aIndex);
michael@0 1069 }
michael@0 1070
michael@0 1071 NS_IMETHODIMP
michael@0 1072 nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
michael@0 1073 {
michael@0 1074 nsAutoScriptBlocker scriptBlocker;
michael@0 1075 RedisplayText(aNewIndex);
michael@0 1076 NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
michael@0 1077
michael@0 1078 nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
michael@0 1079 NS_ASSERTION(listFrame, "No list frame!");
michael@0 1080
michael@0 1081 return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
michael@0 1082 }
michael@0 1083
michael@0 1084 // End nsISelectControlFrame
michael@0 1085 //----------------------------------------------------------------------
michael@0 1086
michael@0 1087 nsresult
michael@0 1088 nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
michael@0 1089 WidgetGUIEvent* aEvent,
michael@0 1090 nsEventStatus* aEventStatus)
michael@0 1091 {
michael@0 1092 NS_ENSURE_ARG_POINTER(aEventStatus);
michael@0 1093
michael@0 1094 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
michael@0 1095 return NS_OK;
michael@0 1096 }
michael@0 1097
michael@0 1098 EventStates eventStates = mContent->AsElement()->State();
michael@0 1099 if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
michael@0 1100 return NS_OK;
michael@0 1101 }
michael@0 1102
michael@0 1103 // If we have style that affects how we are selected, feed event down to
michael@0 1104 // nsFrame::HandleEvent so that selection takes place when appropriate.
michael@0 1105 const nsStyleUserInterface* uiStyle = StyleUserInterface();
michael@0 1106 if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
michael@0 1107 return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
michael@0 1108
michael@0 1109 return NS_OK;
michael@0 1110 }
michael@0 1111
michael@0 1112
michael@0 1113 nsresult
michael@0 1114 nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
michael@0 1115 {
michael@0 1116 nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
michael@0 1117 if (!fcFrame) {
michael@0 1118 return NS_NOINTERFACE;
michael@0 1119 }
michael@0 1120
michael@0 1121 return fcFrame->SetFormProperty(aName, aValue);
michael@0 1122 }
michael@0 1123
michael@0 1124 nsIFrame*
michael@0 1125 nsComboboxControlFrame::GetContentInsertionFrame() {
michael@0 1126 return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
michael@0 1127 }
michael@0 1128
michael@0 1129 nsresult
michael@0 1130 nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
michael@0 1131 {
michael@0 1132 // The frames used to display the combo box and the button used to popup the dropdown list
michael@0 1133 // are created through anonymous content. The dropdown list is not created through anonymous
michael@0 1134 // content because its frame is initialized specifically for the drop-down case and it is placed
michael@0 1135 // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
michael@0 1136 // layout of the display and button.
michael@0 1137 //
michael@0 1138 // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
michael@0 1139 // If the content specified below does not honor the value attribute than nothing will be displayed.
michael@0 1140
michael@0 1141 // For now the content that is created corresponds to two input buttons. It would be better to create the
michael@0 1142 // tag as something other than input, but then there isn't any way to create a button frame since it
michael@0 1143 // isn't possible to set the display type in CSS2 to create a button frame.
michael@0 1144
michael@0 1145 // create content used for display
michael@0 1146 //nsIAtom* tag = NS_NewAtom("mozcombodisplay");
michael@0 1147
michael@0 1148 // Add a child text content node for the label
michael@0 1149
michael@0 1150 nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
michael@0 1151
michael@0 1152 mDisplayContent = new nsTextNode(nimgr);
michael@0 1153
michael@0 1154 // set the value of the text node
michael@0 1155 mDisplayedIndex = mListControlFrame->GetSelectedIndex();
michael@0 1156 if (mDisplayedIndex != -1) {
michael@0 1157 mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
michael@0 1158 }
michael@0 1159 ActuallyDisplayText(false);
michael@0 1160
michael@0 1161 if (!aElements.AppendElement(mDisplayContent))
michael@0 1162 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1163
michael@0 1164 mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
michael@0 1165 if (!mButtonContent)
michael@0 1166 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1167
michael@0 1168 // make someone to listen to the button. If its pressed by someone like Accessibility
michael@0 1169 // then open or close the combo box.
michael@0 1170 mButtonListener = new nsComboButtonListener(this);
michael@0 1171 mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
michael@0 1172 false, false);
michael@0 1173
michael@0 1174 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
michael@0 1175 NS_LITERAL_STRING("button"), false);
michael@0 1176 // Set tabindex="-1" so that the button is not tabbable
michael@0 1177 mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
michael@0 1178 NS_LITERAL_STRING("-1"), false);
michael@0 1179
michael@0 1180 if (!aElements.AppendElement(mButtonContent))
michael@0 1181 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1182
michael@0 1183 return NS_OK;
michael@0 1184 }
michael@0 1185
michael@0 1186 void
michael@0 1187 nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
michael@0 1188 uint32_t aFilter)
michael@0 1189 {
michael@0 1190 aElements.MaybeAppendElement(mDisplayContent);
michael@0 1191 aElements.MaybeAppendElement(mButtonContent);
michael@0 1192 }
michael@0 1193
michael@0 1194 // XXXbz this is a for-now hack. Now that display:inline-block works,
michael@0 1195 // need to revisit this.
michael@0 1196 class nsComboboxDisplayFrame : public nsBlockFrame {
michael@0 1197 public:
michael@0 1198 NS_DECL_FRAMEARENA_HELPERS
michael@0 1199
michael@0 1200 nsComboboxDisplayFrame (nsStyleContext* aContext,
michael@0 1201 nsComboboxControlFrame* aComboBox)
michael@0 1202 : nsBlockFrame(aContext),
michael@0 1203 mComboBox(aComboBox)
michael@0 1204 {}
michael@0 1205
michael@0 1206 // Need this so that line layout knows that this block's width
michael@0 1207 // depends on the available width.
michael@0 1208 virtual nsIAtom* GetType() const MOZ_OVERRIDE;
michael@0 1209
michael@0 1210 virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
michael@0 1211 {
michael@0 1212 return nsBlockFrame::IsFrameOfType(aFlags &
michael@0 1213 ~(nsIFrame::eReplacedContainsBlock));
michael@0 1214 }
michael@0 1215
michael@0 1216 virtual nsresult Reflow(nsPresContext* aPresContext,
michael@0 1217 nsHTMLReflowMetrics& aDesiredSize,
michael@0 1218 const nsHTMLReflowState& aReflowState,
michael@0 1219 nsReflowStatus& aStatus) MOZ_OVERRIDE;
michael@0 1220
michael@0 1221 virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
michael@0 1222 const nsRect& aDirtyRect,
michael@0 1223 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
michael@0 1224
michael@0 1225 protected:
michael@0 1226 nsComboboxControlFrame* mComboBox;
michael@0 1227 };
michael@0 1228
michael@0 1229 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
michael@0 1230
michael@0 1231 nsIAtom*
michael@0 1232 nsComboboxDisplayFrame::GetType() const
michael@0 1233 {
michael@0 1234 return nsGkAtoms::comboboxDisplayFrame;
michael@0 1235 }
michael@0 1236
michael@0 1237 nsresult
michael@0 1238 nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
michael@0 1239 nsHTMLReflowMetrics& aDesiredSize,
michael@0 1240 const nsHTMLReflowState& aReflowState,
michael@0 1241 nsReflowStatus& aStatus)
michael@0 1242 {
michael@0 1243 nsHTMLReflowState state(aReflowState);
michael@0 1244 if (state.ComputedHeight() == NS_INTRINSICSIZE) {
michael@0 1245 // Note that the only way we can have a computed height here is if the
michael@0 1246 // combobox had a specified height. If it didn't, size based on what our
michael@0 1247 // rows look like, for lack of anything better.
michael@0 1248 state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow());
michael@0 1249 }
michael@0 1250 nscoord computedWidth = mComboBox->mDisplayWidth -
michael@0 1251 state.ComputedPhysicalBorderPadding().LeftRight();
michael@0 1252 if (computedWidth < 0) {
michael@0 1253 computedWidth = 0;
michael@0 1254 }
michael@0 1255 state.SetComputedWidth(computedWidth);
michael@0 1256
michael@0 1257 return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
michael@0 1258 }
michael@0 1259
michael@0 1260 void
michael@0 1261 nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
michael@0 1262 const nsRect& aDirtyRect,
michael@0 1263 const nsDisplayListSet& aLists)
michael@0 1264 {
michael@0 1265 nsDisplayListCollection set;
michael@0 1266 nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
michael@0 1267
michael@0 1268 // remove background items if parent frame is themed
michael@0 1269 if (mComboBox->IsThemed()) {
michael@0 1270 set.BorderBackground()->DeleteAll();
michael@0 1271 }
michael@0 1272
michael@0 1273 set.MoveTo(aLists);
michael@0 1274 }
michael@0 1275
michael@0 1276 nsIFrame*
michael@0 1277 nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent)
michael@0 1278 {
michael@0 1279 NS_PRECONDITION(nullptr != aContent, "null ptr");
michael@0 1280
michael@0 1281 NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
michael@0 1282
michael@0 1283 if (mDisplayContent != aContent) {
michael@0 1284 // We only handle the frames for mDisplayContent here
michael@0 1285 return nullptr;
michael@0 1286 }
michael@0 1287
michael@0 1288 // Get PresShell
michael@0 1289 nsIPresShell *shell = PresContext()->PresShell();
michael@0 1290 nsStyleSet *styleSet = shell->StyleSet();
michael@0 1291
michael@0 1292 // create the style contexts for the anonymous block frame and text frame
michael@0 1293 nsRefPtr<nsStyleContext> styleContext;
michael@0 1294 styleContext = styleSet->
michael@0 1295 ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
michael@0 1296 mStyleContext);
michael@0 1297
michael@0 1298 nsRefPtr<nsStyleContext> textStyleContext;
michael@0 1299 textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext);
michael@0 1300
michael@0 1301 // Start by creating our anonymous block frame
michael@0 1302 mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
michael@0 1303 mDisplayFrame->Init(mContent, this, nullptr);
michael@0 1304
michael@0 1305 // Create a text frame and put it inside the block frame
michael@0 1306 nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
michael@0 1307
michael@0 1308 // initialize the text frame
michael@0 1309 textFrame->Init(aContent, mDisplayFrame, nullptr);
michael@0 1310 mDisplayContent->SetPrimaryFrame(textFrame);
michael@0 1311
michael@0 1312 nsFrameList textList(textFrame, textFrame);
michael@0 1313 mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
michael@0 1314 return mDisplayFrame;
michael@0 1315 }
michael@0 1316
michael@0 1317 void
michael@0 1318 nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
michael@0 1319 {
michael@0 1320 // Revoke any pending RedisplayTextEvent
michael@0 1321 mRedisplayTextEvent.Revoke();
michael@0 1322
michael@0 1323 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
michael@0 1324
michael@0 1325 if (mDroppedDown) {
michael@0 1326 MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
michael@0 1327 nsView* view = mDropdownFrame->GetView();
michael@0 1328 MOZ_ASSERT(view);
michael@0 1329 nsIWidget* widget = view->GetWidget();
michael@0 1330 if (widget) {
michael@0 1331 widget->CaptureRollupEvents(this, false);
michael@0 1332 }
michael@0 1333 }
michael@0 1334
michael@0 1335 // Cleanup frames in popup child list
michael@0 1336 mPopupFrames.DestroyFramesFrom(aDestructRoot);
michael@0 1337 nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
michael@0 1338 nsContentUtils::DestroyAnonymousContent(&mButtonContent);
michael@0 1339 nsBlockFrame::DestroyFrom(aDestructRoot);
michael@0 1340 }
michael@0 1341
michael@0 1342 const nsFrameList&
michael@0 1343 nsComboboxControlFrame::GetChildList(ChildListID aListID) const
michael@0 1344 {
michael@0 1345 if (kSelectPopupList == aListID) {
michael@0 1346 return mPopupFrames;
michael@0 1347 }
michael@0 1348 return nsBlockFrame::GetChildList(aListID);
michael@0 1349 }
michael@0 1350
michael@0 1351 void
michael@0 1352 nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
michael@0 1353 {
michael@0 1354 nsBlockFrame::GetChildLists(aLists);
michael@0 1355 mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
michael@0 1356 }
michael@0 1357
michael@0 1358 nsresult
michael@0 1359 nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
michael@0 1360 nsFrameList& aChildList)
michael@0 1361 {
michael@0 1362 nsresult rv = NS_OK;
michael@0 1363 if (kSelectPopupList == aListID) {
michael@0 1364 mPopupFrames.SetFrames(aChildList);
michael@0 1365 } else {
michael@0 1366 for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
michael@0 1367 nsCOMPtr<nsIFormControl> formControl =
michael@0 1368 do_QueryInterface(e.get()->GetContent());
michael@0 1369 if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) {
michael@0 1370 mButtonFrame = e.get();
michael@0 1371 break;
michael@0 1372 }
michael@0 1373 }
michael@0 1374 NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
michael@0 1375 rv = nsBlockFrame::SetInitialChildList(aListID, aChildList);
michael@0 1376 }
michael@0 1377 return rv;
michael@0 1378 }
michael@0 1379
michael@0 1380 //----------------------------------------------------------------------
michael@0 1381 //nsIRollupListener
michael@0 1382 //----------------------------------------------------------------------
michael@0 1383 bool
michael@0 1384 nsComboboxControlFrame::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp)
michael@0 1385 {
michael@0 1386 if (!mDroppedDown)
michael@0 1387 return false;
michael@0 1388
michael@0 1389 nsWeakFrame weakFrame(this);
michael@0 1390 mListControlFrame->AboutToRollup(); // might destroy us
michael@0 1391 if (!weakFrame.IsAlive())
michael@0 1392 return true;
michael@0 1393 ShowDropDown(false); // might destroy us
michael@0 1394 if (weakFrame.IsAlive()) {
michael@0 1395 mListControlFrame->CaptureMouseEvents(false);
michael@0 1396 }
michael@0 1397
michael@0 1398 return true;
michael@0 1399 }
michael@0 1400
michael@0 1401 nsIWidget*
michael@0 1402 nsComboboxControlFrame::GetRollupWidget()
michael@0 1403 {
michael@0 1404 nsView* view = mDropdownFrame->GetView();
michael@0 1405 MOZ_ASSERT(view);
michael@0 1406 return view->GetWidget();
michael@0 1407 }
michael@0 1408
michael@0 1409 void
michael@0 1410 nsComboboxControlFrame::RollupFromList()
michael@0 1411 {
michael@0 1412 if (ShowList(false))
michael@0 1413 mListControlFrame->CaptureMouseEvents(false);
michael@0 1414 }
michael@0 1415
michael@0 1416 int32_t
michael@0 1417 nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
michael@0 1418 {
michael@0 1419 int32_t index = mRecentSelectedIndex;
michael@0 1420 if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
michael@0 1421 mRecentSelectedIndex = aIndex;
michael@0 1422 return index;
michael@0 1423 }
michael@0 1424
michael@0 1425 class nsDisplayComboboxFocus : public nsDisplayItem {
michael@0 1426 public:
michael@0 1427 nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
michael@0 1428 nsComboboxControlFrame* aFrame)
michael@0 1429 : nsDisplayItem(aBuilder, aFrame) {
michael@0 1430 MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
michael@0 1431 }
michael@0 1432 #ifdef NS_BUILD_REFCNT_LOGGING
michael@0 1433 virtual ~nsDisplayComboboxFocus() {
michael@0 1434 MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
michael@0 1435 }
michael@0 1436 #endif
michael@0 1437
michael@0 1438 virtual void Paint(nsDisplayListBuilder* aBuilder,
michael@0 1439 nsRenderingContext* aCtx) MOZ_OVERRIDE;
michael@0 1440 NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
michael@0 1441 };
michael@0 1442
michael@0 1443 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
michael@0 1444 nsRenderingContext* aCtx)
michael@0 1445 {
michael@0 1446 static_cast<nsComboboxControlFrame*>(mFrame)
michael@0 1447 ->PaintFocus(*aCtx, ToReferenceFrame());
michael@0 1448 }
michael@0 1449
michael@0 1450 void
michael@0 1451 nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
michael@0 1452 const nsRect& aDirtyRect,
michael@0 1453 const nsDisplayListSet& aLists)
michael@0 1454 {
michael@0 1455 #ifdef NOISY
michael@0 1456 printf("%p paint at (%d, %d, %d, %d)\n", this,
michael@0 1457 aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
michael@0 1458 #endif
michael@0 1459
michael@0 1460 if (aBuilder->IsForEventDelivery()) {
michael@0 1461 // Don't allow children to receive events.
michael@0 1462 // REVIEW: following old GetFrameForPoint
michael@0 1463 DisplayBorderBackgroundOutline(aBuilder, aLists);
michael@0 1464 } else {
michael@0 1465 // REVIEW: Our in-flow child frames are inline-level so they will paint in our
michael@0 1466 // content list, so we don't need to mess with layers.
michael@0 1467 nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
michael@0 1468 }
michael@0 1469
michael@0 1470 // draw a focus indicator only when focus rings should be drawn
michael@0 1471 nsIDocument* doc = mContent->GetCurrentDoc();
michael@0 1472 if (doc) {
michael@0 1473 nsPIDOMWindow* window = doc->GetWindow();
michael@0 1474 if (window && window->ShouldShowFocusRing()) {
michael@0 1475 nsPresContext *presContext = PresContext();
michael@0 1476 const nsStyleDisplay *disp = StyleDisplay();
michael@0 1477 if ((!IsThemed(disp) ||
michael@0 1478 !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
michael@0 1479 mDisplayFrame && IsVisibleForPainting(aBuilder)) {
michael@0 1480 aLists.Content()->AppendNewToTop(
michael@0 1481 new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
michael@0 1482 }
michael@0 1483 }
michael@0 1484 }
michael@0 1485
michael@0 1486 DisplaySelectionOverlay(aBuilder, aLists.Content());
michael@0 1487 }
michael@0 1488
michael@0 1489 void nsComboboxControlFrame::PaintFocus(nsRenderingContext& aRenderingContext,
michael@0 1490 nsPoint aPt)
michael@0 1491 {
michael@0 1492 /* Do we need to do anything? */
michael@0 1493 EventStates eventStates = mContent->AsElement()->State();
michael@0 1494 if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
michael@0 1495 return;
michael@0 1496
michael@0 1497 aRenderingContext.PushState();
michael@0 1498 nsRect clipRect = mDisplayFrame->GetRect() + aPt;
michael@0 1499 aRenderingContext.IntersectClip(clipRect);
michael@0 1500
michael@0 1501 // REVIEW: Why does the old code paint mDisplayFrame again? We've
michael@0 1502 // already painted it in the children above. So clipping it here won't do
michael@0 1503 // us much good.
michael@0 1504
michael@0 1505 /////////////////////
michael@0 1506 // draw focus
michael@0 1507
michael@0 1508 aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
michael@0 1509 aRenderingContext.SetColor(StyleColor()->mColor);
michael@0 1510
michael@0 1511 //aRenderingContext.DrawRect(clipRect);
michael@0 1512
michael@0 1513 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
michael@0 1514 clipRect.width -= onePixel;
michael@0 1515 clipRect.height -= onePixel;
michael@0 1516 aRenderingContext.DrawLine(clipRect.TopLeft(), clipRect.TopRight());
michael@0 1517 aRenderingContext.DrawLine(clipRect.TopRight(), clipRect.BottomRight());
michael@0 1518 aRenderingContext.DrawLine(clipRect.BottomRight(), clipRect.BottomLeft());
michael@0 1519 aRenderingContext.DrawLine(clipRect.BottomLeft(), clipRect.TopLeft());
michael@0 1520
michael@0 1521 aRenderingContext.PopState();
michael@0 1522 }
michael@0 1523
michael@0 1524 //---------------------------------------------------------
michael@0 1525 // gets the content (an option) by index and then set it as
michael@0 1526 // being selected or not selected
michael@0 1527 //---------------------------------------------------------
michael@0 1528 NS_IMETHODIMP
michael@0 1529 nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
michael@0 1530 {
michael@0 1531 if (mDroppedDown) {
michael@0 1532 nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
michael@0 1533 if (selectFrame) {
michael@0 1534 selectFrame->OnOptionSelected(aIndex, aSelected);
michael@0 1535 }
michael@0 1536 } else {
michael@0 1537 if (aSelected) {
michael@0 1538 nsAutoScriptBlocker blocker;
michael@0 1539 RedisplayText(aIndex);
michael@0 1540 } else {
michael@0 1541 nsWeakFrame weakFrame(this);
michael@0 1542 RedisplaySelectedText();
michael@0 1543 if (weakFrame.IsAlive()) {
michael@0 1544 FireValueChangeEvent(); // Fire after old option is unselected
michael@0 1545 }
michael@0 1546 }
michael@0 1547 }
michael@0 1548
michael@0 1549 return NS_OK;
michael@0 1550 }
michael@0 1551
michael@0 1552 void nsComboboxControlFrame::FireValueChangeEvent()
michael@0 1553 {
michael@0 1554 // Fire ValueChange event to indicate data value of combo box has changed
michael@0 1555 nsContentUtils::AddScriptRunner(
michael@0 1556 new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), true,
michael@0 1557 false));
michael@0 1558 }
michael@0 1559
michael@0 1560 void
michael@0 1561 nsComboboxControlFrame::OnContentReset()
michael@0 1562 {
michael@0 1563 if (mListControlFrame) {
michael@0 1564 mListControlFrame->OnContentReset();
michael@0 1565 }
michael@0 1566 }
michael@0 1567
michael@0 1568
michael@0 1569 //--------------------------------------------------------
michael@0 1570 // nsIStatefulFrame
michael@0 1571 //--------------------------------------------------------
michael@0 1572 NS_IMETHODIMP
michael@0 1573 nsComboboxControlFrame::SaveState(nsPresState** aState)
michael@0 1574 {
michael@0 1575 if (!mListControlFrame)
michael@0 1576 return NS_ERROR_FAILURE;
michael@0 1577
michael@0 1578 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
michael@0 1579 return stateful->SaveState(aState);
michael@0 1580 }
michael@0 1581
michael@0 1582 NS_IMETHODIMP
michael@0 1583 nsComboboxControlFrame::RestoreState(nsPresState* aState)
michael@0 1584 {
michael@0 1585 if (!mListControlFrame)
michael@0 1586 return NS_ERROR_FAILURE;
michael@0 1587
michael@0 1588 nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame);
michael@0 1589 NS_ASSERTION(stateful, "Must implement nsIStatefulFrame");
michael@0 1590 return stateful->RestoreState(aState);
michael@0 1591 }
michael@0 1592
michael@0 1593
michael@0 1594 //
michael@0 1595 // Camino uses a native widget for the combobox
michael@0 1596 // popup, which affects drawing and event
michael@0 1597 // handling here and in nsListControlFrame.
michael@0 1598 //
michael@0 1599 // Also, Fennec use a custom combobox built-in widget
michael@0 1600 //
michael@0 1601
michael@0 1602 /* static */
michael@0 1603 bool
michael@0 1604 nsComboboxControlFrame::ToolkitHasNativePopup()
michael@0 1605 {
michael@0 1606 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
michael@0 1607 return true;
michael@0 1608 #else
michael@0 1609 return false;
michael@0 1610 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
michael@0 1611 }
michael@0 1612

mercurial