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