1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Dean Tessman <dean_tessman@hotmail.com>
24 : * Stan Shebs <stanshebs@earthlink.net>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : //
41 : // Eric Vaughan
42 : // Netscape Communications
43 : //
44 : // See documentation in associated header file
45 : //
46 :
47 : #include "nsSliderFrame.h"
48 : #include "nsStyleContext.h"
49 : #include "nsPresContext.h"
50 : #include "nsIContent.h"
51 : #include "nsCOMPtr.h"
52 : #include "nsINameSpaceManager.h"
53 : #include "nsGkAtoms.h"
54 : #include "nsHTMLParts.h"
55 : #include "nsIPresShell.h"
56 : #include "nsCSSRendering.h"
57 : #include "nsEventListenerManager.h"
58 : #include "nsIDOMEventTarget.h"
59 : #include "nsIDOMMouseEvent.h"
60 : #include "nsIDocument.h"
61 : #include "nsScrollbarButtonFrame.h"
62 : #include "nsISliderListener.h"
63 : #include "nsIScrollbarMediator.h"
64 : #include "nsScrollbarFrame.h"
65 : #include "nsRepeatService.h"
66 : #include "nsBoxLayoutState.h"
67 : #include "nsSprocketLayout.h"
68 : #include "nsIServiceManager.h"
69 : #include "nsGUIEvent.h"
70 : #include "nsContentUtils.h"
71 : #include "nsLayoutUtils.h"
72 : #include "nsDisplayList.h"
73 : #include "mozilla/Preferences.h"
74 : #include "mozilla/LookAndFeel.h"
75 :
76 : using namespace mozilla;
77 :
78 : bool nsSliderFrame::gMiddlePref = false;
79 : PRInt32 nsSliderFrame::gSnapMultiplier;
80 :
81 : // Turn this on if you want to debug slider frames.
82 : #undef DEBUG_SLIDER
83 :
84 : static already_AddRefed<nsIContent>
85 0 : GetContentOfBox(nsIBox *aBox)
86 : {
87 0 : nsIContent* content = aBox->GetContent();
88 0 : NS_IF_ADDREF(content);
89 0 : return content;
90 : }
91 :
92 : nsIFrame*
93 0 : NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
94 : {
95 0 : return new (aPresShell) nsSliderFrame(aPresShell, aContext);
96 : }
97 :
98 0 : NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
99 :
100 0 : nsSliderFrame::nsSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext):
101 : nsBoxFrame(aPresShell, aContext),
102 : mCurPos(0),
103 : mChange(0),
104 0 : mUserChanged(false)
105 : {
106 0 : }
107 :
108 : // stop timer
109 0 : nsSliderFrame::~nsSliderFrame()
110 : {
111 0 : }
112 :
113 : NS_IMETHODIMP
114 0 : nsSliderFrame::Init(nsIContent* aContent,
115 : nsIFrame* aParent,
116 : nsIFrame* aPrevInFlow)
117 : {
118 0 : nsresult rv = nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
119 :
120 : static bool gotPrefs = false;
121 0 : if (!gotPrefs) {
122 0 : gotPrefs = true;
123 :
124 0 : gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition");
125 0 : gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier");
126 : }
127 :
128 0 : mCurPos = GetCurrentPosition(aContent);
129 :
130 0 : return rv;
131 : }
132 :
133 : NS_IMETHODIMP
134 0 : nsSliderFrame::RemoveFrame(ChildListID aListID,
135 : nsIFrame* aOldFrame)
136 : {
137 0 : nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame);
138 0 : if (mFrames.IsEmpty())
139 0 : RemoveListener();
140 :
141 0 : return rv;
142 : }
143 :
144 : NS_IMETHODIMP
145 0 : nsSliderFrame::InsertFrames(ChildListID aListID,
146 : nsIFrame* aPrevFrame,
147 : nsFrameList& aFrameList)
148 : {
149 0 : bool wasEmpty = mFrames.IsEmpty();
150 0 : nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
151 0 : if (wasEmpty)
152 0 : AddListener();
153 :
154 0 : return rv;
155 : }
156 :
157 : NS_IMETHODIMP
158 0 : nsSliderFrame::AppendFrames(ChildListID aListID,
159 : nsFrameList& aFrameList)
160 : {
161 : // if we have no children and on was added then make sure we add the
162 : // listener
163 0 : bool wasEmpty = mFrames.IsEmpty();
164 0 : nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList);
165 0 : if (wasEmpty)
166 0 : AddListener();
167 :
168 0 : return rv;
169 : }
170 :
171 : PRInt32
172 0 : nsSliderFrame::GetCurrentPosition(nsIContent* content)
173 : {
174 0 : return GetIntegerAttribute(content, nsGkAtoms::curpos, 0);
175 : }
176 :
177 : PRInt32
178 0 : nsSliderFrame::GetMinPosition(nsIContent* content)
179 : {
180 0 : return GetIntegerAttribute(content, nsGkAtoms::minpos, 0);
181 : }
182 :
183 : PRInt32
184 0 : nsSliderFrame::GetMaxPosition(nsIContent* content)
185 : {
186 0 : return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100);
187 : }
188 :
189 : PRInt32
190 0 : nsSliderFrame::GetIncrement(nsIContent* content)
191 : {
192 0 : return GetIntegerAttribute(content, nsGkAtoms::increment, 1);
193 : }
194 :
195 :
196 : PRInt32
197 0 : nsSliderFrame::GetPageIncrement(nsIContent* content)
198 : {
199 0 : return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10);
200 : }
201 :
202 : PRInt32
203 0 : nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, PRInt32 defaultValue)
204 : {
205 0 : nsAutoString value;
206 0 : content->GetAttr(kNameSpaceID_None, atom, value);
207 0 : if (!value.IsEmpty()) {
208 : PRInt32 error;
209 :
210 : // convert it to an integer
211 0 : defaultValue = value.ToInteger(&error);
212 : }
213 :
214 0 : return defaultValue;
215 : }
216 :
217 : class nsValueChangedRunnable : public nsRunnable
218 0 : {
219 : public:
220 0 : nsValueChangedRunnable(nsISliderListener* aListener,
221 : nsIAtom* aWhich,
222 : PRInt32 aValue,
223 : bool aUserChanged)
224 : : mListener(aListener), mWhich(aWhich),
225 0 : mValue(aValue), mUserChanged(aUserChanged)
226 0 : {}
227 :
228 0 : NS_IMETHODIMP Run()
229 : {
230 0 : return mListener->ValueChanged(nsDependentAtomString(mWhich),
231 0 : mValue, mUserChanged);
232 : }
233 :
234 : nsCOMPtr<nsISliderListener> mListener;
235 : nsCOMPtr<nsIAtom> mWhich;
236 : PRInt32 mValue;
237 : bool mUserChanged;
238 : };
239 :
240 : class nsDragStateChangedRunnable : public nsRunnable
241 0 : {
242 : public:
243 0 : nsDragStateChangedRunnable(nsISliderListener* aListener,
244 : bool aDragBeginning)
245 : : mListener(aListener),
246 0 : mDragBeginning(aDragBeginning)
247 0 : {}
248 :
249 0 : NS_IMETHODIMP Run()
250 : {
251 0 : return mListener->DragStateChanged(mDragBeginning);
252 : }
253 :
254 : nsCOMPtr<nsISliderListener> mListener;
255 : bool mDragBeginning;
256 : };
257 :
258 : NS_IMETHODIMP
259 0 : nsSliderFrame::AttributeChanged(PRInt32 aNameSpaceID,
260 : nsIAtom* aAttribute,
261 : PRInt32 aModType)
262 : {
263 : nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
264 0 : aModType);
265 : // if the current position changes
266 0 : if (aAttribute == nsGkAtoms::curpos) {
267 0 : rv = CurrentPositionChanged(PresContext(), false);
268 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "failed to change position");
269 0 : if (NS_FAILED(rv))
270 0 : return rv;
271 0 : } else if (aAttribute == nsGkAtoms::minpos ||
272 : aAttribute == nsGkAtoms::maxpos) {
273 : // bounds check it.
274 :
275 0 : nsIBox* scrollbarBox = GetScrollbar();
276 0 : nsCOMPtr<nsIContent> scrollbar;
277 0 : scrollbar = GetContentOfBox(scrollbarBox);
278 0 : PRInt32 current = GetCurrentPosition(scrollbar);
279 0 : PRInt32 min = GetMinPosition(scrollbar);
280 0 : PRInt32 max = GetMaxPosition(scrollbar);
281 :
282 : // inform the parent <scale> that the minimum or maximum changed
283 0 : nsIFrame* parent = GetParent();
284 0 : if (parent) {
285 0 : nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
286 0 : if (sliderListener) {
287 : nsContentUtils::AddScriptRunner(
288 : new nsValueChangedRunnable(sliderListener, aAttribute,
289 0 : aAttribute == nsGkAtoms::minpos ? min : max, false));
290 : }
291 : }
292 :
293 0 : if (current < min || current > max)
294 : {
295 0 : if (current < min || max < min)
296 0 : current = min;
297 0 : else if (current > max)
298 0 : current = max;
299 :
300 : // set the new position and notify observers
301 0 : nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
302 0 : if (scrollbarFrame) {
303 0 : nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
304 0 : if (mediator) {
305 0 : mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), current);
306 : }
307 : }
308 :
309 : nsContentUtils::AddScriptRunner(
310 0 : new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current));
311 : }
312 : }
313 :
314 0 : if (aAttribute == nsGkAtoms::minpos ||
315 : aAttribute == nsGkAtoms::maxpos ||
316 : aAttribute == nsGkAtoms::pageincrement ||
317 : aAttribute == nsGkAtoms::increment) {
318 :
319 0 : PresContext()->PresShell()->
320 0 : FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
321 : }
322 :
323 0 : return rv;
324 : }
325 :
326 : NS_IMETHODIMP
327 0 : nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
328 : const nsRect& aDirtyRect,
329 : const nsDisplayListSet& aLists)
330 : {
331 0 : if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
332 : // This is EVIL, we shouldn't be messing with event delivery just to get
333 : // thumb mouse drag events to arrive at the slider!
334 : return aLists.Outlines()->AppendNewToTop(new (aBuilder)
335 0 : nsDisplayEventReceiver(aBuilder, this));
336 : }
337 :
338 0 : return nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
339 : }
340 :
341 : NS_IMETHODIMP
342 0 : nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
343 : const nsRect& aDirtyRect,
344 : const nsDisplayListSet& aLists)
345 : {
346 : // if we are too small to have a thumb don't paint it.
347 0 : nsIBox* thumb = GetChildBox();
348 :
349 0 : if (thumb) {
350 0 : nsRect thumbRect(thumb->GetRect());
351 0 : nsMargin m;
352 0 : thumb->GetMargin(m);
353 0 : thumbRect.Inflate(m);
354 :
355 0 : nsRect crect;
356 0 : GetClientRect(crect);
357 :
358 0 : if (crect.width < thumbRect.width || crect.height < thumbRect.height)
359 0 : return NS_OK;
360 : }
361 :
362 0 : return nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
363 : }
364 :
365 : NS_IMETHODIMP
366 0 : nsSliderFrame::DoLayout(nsBoxLayoutState& aState)
367 : {
368 : // get the thumb should be our only child
369 0 : nsIBox* thumbBox = GetChildBox();
370 :
371 0 : if (!thumbBox) {
372 0 : SyncLayout(aState);
373 0 : return NS_OK;
374 : }
375 :
376 0 : EnsureOrient();
377 :
378 : #ifdef DEBUG_LAYOUT
379 : if (mState & NS_STATE_DEBUG_WAS_SET) {
380 : if (mState & NS_STATE_SET_TO_DEBUG)
381 : SetDebug(aState, true);
382 : else
383 : SetDebug(aState, false);
384 : }
385 : #endif
386 :
387 : // get the content area inside our borders
388 0 : nsRect clientRect;
389 0 : GetClientRect(clientRect);
390 :
391 : // get the scrollbar
392 0 : nsIBox* scrollbarBox = GetScrollbar();
393 0 : nsCOMPtr<nsIContent> scrollbar;
394 0 : scrollbar = GetContentOfBox(scrollbarBox);
395 :
396 : // get the thumb's pref size
397 0 : nsSize thumbSize = thumbBox->GetPrefSize(aState);
398 :
399 0 : if (IsHorizontal())
400 0 : thumbSize.height = clientRect.height;
401 : else
402 0 : thumbSize.width = clientRect.width;
403 :
404 0 : PRInt32 curPos = GetCurrentPosition(scrollbar);
405 0 : PRInt32 minPos = GetMinPosition(scrollbar);
406 0 : PRInt32 maxPos = GetMaxPosition(scrollbar);
407 0 : PRInt32 pageIncrement = GetPageIncrement(scrollbar);
408 :
409 0 : maxPos = NS_MAX(minPos, maxPos);
410 0 : curPos = clamped(curPos, minPos, maxPos);
411 :
412 0 : nscoord& availableLength = IsHorizontal() ? clientRect.width : clientRect.height;
413 0 : nscoord& thumbLength = IsHorizontal() ? thumbSize.width : thumbSize.height;
414 :
415 0 : if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetFlex(aState) > 0) {
416 0 : float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement);
417 0 : thumbLength = NS_MAX(thumbLength, NSToCoordRound(availableLength * ratio));
418 : }
419 :
420 : // Round the thumb's length to device pixels.
421 0 : nsPresContext* presContext = PresContext();
422 : thumbLength = presContext->DevPixelsToAppUnits(
423 0 : presContext->AppUnitsToDevPixels(thumbLength));
424 :
425 : // mRatio translates the thumb position in app units to the value.
426 0 : mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1;
427 :
428 : // in reverse mode, curpos is reversed such that lower values are to the
429 : // right or bottom and increase leftwards or upwards. In this case, use the
430 : // offset from the end instead of the beginning.
431 : bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
432 0 : nsGkAtoms::reverse, eCaseMatters);
433 0 : nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
434 :
435 : // set the thumb's coord to be the current pos * the ratio.
436 0 : nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height);
437 0 : PRInt32& thumbPos = (IsHorizontal() ? thumbRect.x : thumbRect.y);
438 0 : thumbPos += NSToCoordRound(pos * mRatio);
439 :
440 0 : nsRect oldThumbRect(thumbBox->GetRect());
441 0 : LayoutChildAt(aState, thumbBox, thumbRect);
442 :
443 0 : SyncLayout(aState);
444 :
445 : // Redraw only if thumb changed size.
446 0 : if (!oldThumbRect.IsEqualInterior(thumbRect))
447 0 : Redraw(aState);
448 :
449 0 : return NS_OK;
450 : }
451 :
452 :
453 : NS_IMETHODIMP
454 0 : nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
455 : nsGUIEvent* aEvent,
456 : nsEventStatus* aEventStatus)
457 : {
458 0 : NS_ENSURE_ARG_POINTER(aEventStatus);
459 :
460 : // If a web page calls event.preventDefault() we still want to
461 : // scroll when scroll arrow is clicked. See bug 511075.
462 0 : if (!mContent->IsInNativeAnonymousSubtree() &&
463 : nsEventStatus_eConsumeNoDefault == *aEventStatus) {
464 0 : return NS_OK;
465 : }
466 :
467 0 : nsIBox* scrollbarBox = GetScrollbar();
468 0 : nsCOMPtr<nsIContent> scrollbar;
469 0 : scrollbar = GetContentOfBox(scrollbarBox);
470 0 : bool isHorizontal = IsHorizontal();
471 :
472 0 : if (isDraggingThumb())
473 : {
474 0 : switch (aEvent->message) {
475 : case NS_MOUSE_MOVE: {
476 : nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
477 0 : this);
478 0 : if (mChange) {
479 : // We're in the process of moving the thumb to the mouse,
480 : // but the mouse just moved. Make sure to update our
481 : // destination point.
482 0 : mDestinationPoint = eventPoint;
483 0 : StopRepeat();
484 0 : StartRepeat();
485 0 : break;
486 : }
487 :
488 0 : nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
489 :
490 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
491 0 : if (!thumbFrame) {
492 0 : return NS_OK;
493 : }
494 :
495 : // take our current position and subtract the start location
496 0 : pos -= mDragStart;
497 0 : bool isMouseOutsideThumb = false;
498 0 : if (gSnapMultiplier) {
499 0 : nsSize thumbSize = thumbFrame->GetSize();
500 0 : if (isHorizontal) {
501 : // horizontal scrollbar - check if mouse is above or below thumb
502 : // XXXbz what about looking at the .y of the thumb's rect? Is that
503 : // always zero here?
504 0 : if (eventPoint.y < -gSnapMultiplier * thumbSize.height ||
505 : eventPoint.y > thumbSize.height +
506 : gSnapMultiplier * thumbSize.height)
507 0 : isMouseOutsideThumb = true;
508 : }
509 : else {
510 : // vertical scrollbar - check if mouse is left or right of thumb
511 0 : if (eventPoint.x < -gSnapMultiplier * thumbSize.width ||
512 : eventPoint.x > thumbSize.width +
513 : gSnapMultiplier * thumbSize.width)
514 0 : isMouseOutsideThumb = true;
515 : }
516 : }
517 0 : if (isMouseOutsideThumb)
518 : {
519 0 : SetCurrentThumbPosition(scrollbar, mThumbStart, false, true, false);
520 0 : return NS_OK;
521 : }
522 :
523 : // set it
524 0 : SetCurrentThumbPosition(scrollbar, pos, false, true, true); // with snapping
525 : }
526 0 : break;
527 :
528 : case NS_MOUSE_BUTTON_UP:
529 0 : if (static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eLeftButton ||
530 : (static_cast<nsMouseEvent*>(aEvent)->button == nsMouseEvent::eMiddleButton &&
531 : gMiddlePref)) {
532 : // stop capturing
533 0 : AddListener();
534 0 : DragThumb(false);
535 0 : if (mChange) {
536 0 : StopRepeat();
537 0 : mChange = 0;
538 : }
539 : //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state.
540 0 : return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
541 : }
542 : }
543 :
544 : //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
545 0 : return NS_OK;
546 0 : } else if ((aEvent->message == NS_MOUSE_BUTTON_DOWN &&
547 : static_cast<nsMouseEvent*>(aEvent)->button ==
548 : nsMouseEvent::eLeftButton &&
549 : #ifdef XP_MACOSX
550 : // On Mac the option key inverts the scroll-to-here preference.
551 : (static_cast<nsMouseEvent*>(aEvent)->isAlt != GetScrollToClick())) ||
552 : #else
553 0 : (static_cast<nsMouseEvent*>(aEvent)->isShift != GetScrollToClick())) ||
554 : #endif
555 : (gMiddlePref && aEvent->message == NS_MOUSE_BUTTON_DOWN &&
556 : static_cast<nsMouseEvent*>(aEvent)->button ==
557 : nsMouseEvent::eMiddleButton)) {
558 :
559 : nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
560 0 : this);
561 0 : nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
562 :
563 : // adjust so that the middle of the thumb is placed under the click
564 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
565 0 : if (!thumbFrame) {
566 0 : return NS_OK;
567 : }
568 0 : nsSize thumbSize = thumbFrame->GetSize();
569 0 : nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
570 :
571 : // set it
572 0 : nsWeakFrame weakFrame(this);
573 : // should aMaySnap be true here?
574 0 : SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false, false);
575 0 : NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
576 :
577 0 : DragThumb(true);
578 :
579 0 : if (isHorizontal)
580 0 : mThumbStart = thumbFrame->GetPosition().x;
581 : else
582 0 : mThumbStart = thumbFrame->GetPosition().y;
583 :
584 0 : mDragStart = pos - mThumbStart;
585 : }
586 :
587 : // XXX hack until handle release is actually called in nsframe.
588 : // if (aEvent->message == NS_MOUSE_EXIT_SYNTH || aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP || aEvent->message == NS_MOUSE_LEFT_BUTTON_UP)
589 : // HandleRelease(aPresContext, aEvent, aEventStatus);
590 :
591 0 : if (aEvent->message == NS_MOUSE_EXIT_SYNTH && mChange)
592 0 : HandleRelease(aPresContext, aEvent, aEventStatus);
593 :
594 0 : return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
595 : }
596 :
597 : // Helper function to collect the "scroll to click" metric. Beware of
598 : // caching this, users expect to be able to change the system preference
599 : // and see the browser change its behavior immediately.
600 : bool
601 0 : nsSliderFrame::GetScrollToClick()
602 : {
603 : // if there is no parent scrollbar, check the movetoclick attribute. If set
604 : // to true, always scroll to the click point. If false, never do this.
605 : // Otherwise, the default is true on Mac and false on other platforms.
606 0 : if (GetScrollbar() == this)
607 : #ifdef XP_MACOSX
608 : return !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
609 : nsGkAtoms::_false, eCaseMatters);
610 :
611 : // if there was no scrollbar, always scroll on click
612 : bool scrollToClick = false;
613 : PRInt32 scrollToClickMetric;
614 : nsresult rv = LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick,
615 : &scrollToClickMetric);
616 : if (NS_SUCCEEDED(rv) && scrollToClickMetric == 1)
617 : scrollToClick = true;
618 : return scrollToClick;
619 :
620 : #else
621 : return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
622 0 : nsGkAtoms::_true, eCaseMatters);
623 0 : return false;
624 : #endif
625 : }
626 :
627 : nsIBox*
628 0 : nsSliderFrame::GetScrollbar()
629 : {
630 : // if we are in a scrollbar then return the scrollbar's content node
631 : // if we are not then return ours.
632 : nsIFrame* scrollbar;
633 0 : nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
634 :
635 0 : if (scrollbar == nsnull)
636 0 : return this;
637 :
638 0 : return scrollbar->IsBoxFrame() ? scrollbar : this;
639 : }
640 :
641 : void
642 0 : nsSliderFrame::PageUpDown(nscoord change)
643 : {
644 : // on a page up or down get our page increment. We get this by getting the scrollbar we are in and
645 : // asking it for the current position and the page increment. If we are not in a scrollbar we will
646 : // get the values from our own node.
647 0 : nsIBox* scrollbarBox = GetScrollbar();
648 0 : nsCOMPtr<nsIContent> scrollbar;
649 0 : scrollbar = GetContentOfBox(scrollbarBox);
650 :
651 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
652 0 : nsGkAtoms::reverse, eCaseMatters))
653 0 : change = -change;
654 :
655 0 : nscoord pageIncrement = GetPageIncrement(scrollbar);
656 0 : PRInt32 curpos = GetCurrentPosition(scrollbar);
657 0 : PRInt32 minpos = GetMinPosition(scrollbar);
658 0 : PRInt32 maxpos = GetMaxPosition(scrollbar);
659 :
660 : // get the new position and make sure it is in bounds
661 0 : PRInt32 newpos = curpos + change * pageIncrement;
662 0 : if (newpos < minpos || maxpos < minpos)
663 0 : newpos = minpos;
664 0 : else if (newpos > maxpos)
665 0 : newpos = maxpos;
666 :
667 0 : SetCurrentPositionInternal(scrollbar, newpos, true, false);
668 0 : }
669 :
670 : // called when the current position changed and we need to update the thumb's location
671 : nsresult
672 0 : nsSliderFrame::CurrentPositionChanged(nsPresContext* aPresContext,
673 : bool aImmediateRedraw)
674 : {
675 0 : nsIBox* scrollbarBox = GetScrollbar();
676 0 : nsCOMPtr<nsIContent> scrollbar;
677 0 : scrollbar = GetContentOfBox(scrollbarBox);
678 :
679 : // get the current position
680 0 : PRInt32 curPos = GetCurrentPosition(scrollbar);
681 :
682 : // do nothing if the position did not change
683 0 : if (mCurPos == curPos)
684 0 : return NS_OK;
685 :
686 : // get our current min and max position from our content node
687 0 : PRInt32 minPos = GetMinPosition(scrollbar);
688 0 : PRInt32 maxPos = GetMaxPosition(scrollbar);
689 :
690 0 : maxPos = NS_MAX(minPos, maxPos);
691 0 : curPos = clamped(curPos, minPos, maxPos);
692 :
693 : // get the thumb's rect
694 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
695 0 : if (!thumbFrame)
696 0 : return NS_OK; // The thumb may stream in asynchronously via XBL.
697 :
698 0 : nsRect thumbRect = thumbFrame->GetRect();
699 :
700 0 : nsRect clientRect;
701 0 : GetClientRect(clientRect);
702 :
703 : // figure out the new rect
704 0 : nsRect newThumbRect(thumbRect);
705 :
706 : bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
707 0 : nsGkAtoms::reverse, eCaseMatters);
708 0 : nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
709 :
710 0 : if (IsHorizontal())
711 0 : newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio);
712 : else
713 0 : newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
714 :
715 : // set the rect
716 0 : thumbFrame->SetRect(newThumbRect);
717 :
718 : // Redraw the scrollbar
719 0 : InvalidateWithFlags(clientRect, aImmediateRedraw ? INVALIDATE_IMMEDIATE : 0);
720 :
721 0 : mCurPos = curPos;
722 :
723 : // inform the parent <scale> if it exists that the value changed
724 0 : nsIFrame* parent = GetParent();
725 0 : if (parent) {
726 0 : nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
727 0 : if (sliderListener) {
728 : nsContentUtils::AddScriptRunner(
729 0 : new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged));
730 : }
731 : }
732 :
733 0 : return NS_OK;
734 : }
735 :
736 0 : static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) {
737 0 : nsAutoString str;
738 0 : str.AppendInt(aNewPos);
739 :
740 0 : if (aIsSmooth) {
741 0 : aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
742 : }
743 0 : aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify);
744 0 : if (aIsSmooth) {
745 0 : aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
746 : }
747 0 : }
748 :
749 : // Use this function when you want to set the scroll position via the position
750 : // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
751 : // the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
752 : void
753 0 : nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos,
754 : bool aIsSmooth, bool aImmediateRedraw, bool aMaySnap)
755 : {
756 0 : nsRect crect;
757 0 : GetClientRect(crect);
758 0 : nscoord offset = IsHorizontal() ? crect.x : crect.y;
759 0 : PRInt32 newPos = NSToIntRound((aNewThumbPos - offset) / mRatio);
760 :
761 0 : if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap,
762 0 : nsGkAtoms::_true, eCaseMatters)) {
763 : // If snap="true", then the slider may only be set to min + (increment * x).
764 : // Otherwise, the slider may be set to any positive integer.
765 0 : PRInt32 increment = GetIncrement(aScrollbar);
766 0 : newPos = NSToIntRound(newPos / float(increment)) * increment;
767 : }
768 :
769 0 : SetCurrentPosition(aScrollbar, newPos, aIsSmooth, aImmediateRedraw);
770 0 : }
771 :
772 : // Use this function when you know the target scroll position of the scrolled content.
773 : // aNewPos should be passed to this function as a position as if the minpos is 0.
774 : // That is, the minpos will be added to the position by this function. In a reverse
775 : // direction slider, the newpos should be the distance from the end.
776 : void
777 0 : nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, PRInt32 aNewPos,
778 : bool aIsSmooth, bool aImmediateRedraw)
779 : {
780 : // get min and max position from our content node
781 0 : PRInt32 minpos = GetMinPosition(aScrollbar);
782 0 : PRInt32 maxpos = GetMaxPosition(aScrollbar);
783 :
784 : // in reverse direction sliders, flip the value so that it goes from
785 : // right to left, or bottom to top.
786 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
787 0 : nsGkAtoms::reverse, eCaseMatters))
788 0 : aNewPos = maxpos - aNewPos;
789 : else
790 0 : aNewPos += minpos;
791 :
792 : // get the new position and make sure it is in bounds
793 0 : if (aNewPos < minpos || maxpos < minpos)
794 0 : aNewPos = minpos;
795 0 : else if (aNewPos > maxpos)
796 0 : aNewPos = maxpos;
797 :
798 0 : SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth, aImmediateRedraw);
799 0 : }
800 :
801 : void
802 0 : nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, PRInt32 aNewPos,
803 : bool aIsSmooth,
804 : bool aImmediateRedraw)
805 : {
806 0 : nsCOMPtr<nsIContent> scrollbar = aScrollbar;
807 0 : nsIBox* scrollbarBox = GetScrollbar();
808 :
809 0 : mUserChanged = true;
810 :
811 0 : nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
812 0 : if (scrollbarFrame) {
813 : // See if we have a mediator.
814 0 : nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
815 0 : if (mediator) {
816 0 : nsRefPtr<nsPresContext> context = PresContext();
817 0 : nsCOMPtr<nsIContent> content = GetContent();
818 0 : mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), aNewPos);
819 : // 'mediator' might be dangling now...
820 0 : UpdateAttribute(scrollbar, aNewPos, false, aIsSmooth);
821 0 : nsIFrame* frame = content->GetPrimaryFrame();
822 0 : if (frame && frame->GetType() == nsGkAtoms::sliderFrame) {
823 : static_cast<nsSliderFrame*>(frame)->
824 0 : CurrentPositionChanged(frame->PresContext(), aImmediateRedraw);
825 : }
826 0 : mUserChanged = false;
827 : return;
828 : }
829 : }
830 :
831 0 : UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
832 0 : mUserChanged = false;
833 :
834 : #ifdef DEBUG_SLIDER
835 : printf("Current Pos=%d\n",aNewPos);
836 : #endif
837 :
838 : }
839 :
840 : nsIAtom*
841 0 : nsSliderFrame::GetType() const
842 : {
843 0 : return nsGkAtoms::sliderFrame;
844 : }
845 :
846 : NS_IMETHODIMP
847 0 : nsSliderFrame::SetInitialChildList(ChildListID aListID,
848 : nsFrameList& aChildList)
849 : {
850 0 : nsresult r = nsBoxFrame::SetInitialChildList(aListID, aChildList);
851 :
852 0 : AddListener();
853 :
854 0 : return r;
855 : }
856 :
857 : nsresult
858 0 : nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent)
859 : {
860 : // Only process the event if the thumb is not being dragged.
861 0 : if (mSlider && !mSlider->isDraggingThumb())
862 0 : return mSlider->MouseDown(aEvent);
863 :
864 0 : return NS_OK;
865 : }
866 :
867 : nsresult
868 0 : nsSliderFrame::MouseDown(nsIDOMEvent* aMouseEvent)
869 : {
870 : #ifdef DEBUG_SLIDER
871 : printf("Begin dragging\n");
872 : #endif
873 :
874 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aMouseEvent));
875 0 : if (!mouseEvent)
876 0 : return NS_OK;
877 :
878 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
879 0 : nsGkAtoms::_true, eCaseMatters))
880 0 : return NS_OK;
881 :
882 0 : PRUint16 button = 0;
883 0 : mouseEvent->GetButton(&button);
884 0 : if (!(button == 0 || (button == 1 && gMiddlePref)))
885 0 : return NS_OK;
886 :
887 0 : bool isHorizontal = IsHorizontal();
888 :
889 0 : bool scrollToClick = false;
890 : #ifndef XP_MACOSX
891 : // On Mac there's no scroll-to-here when clicking the thumb
892 0 : mouseEvent->GetShiftKey(&scrollToClick);
893 0 : if (button != 0) {
894 0 : scrollToClick = true;
895 : }
896 : #endif
897 :
898 : nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent,
899 0 : this);
900 0 : nscoord pos = isHorizontal ? pt.x : pt.y;
901 :
902 : // If shift click or middle button, first
903 : // place the middle of the slider thumb under the click
904 0 : nsCOMPtr<nsIContent> scrollbar;
905 0 : nscoord newpos = pos;
906 0 : if (scrollToClick) {
907 : // adjust so that the middle of the thumb is placed under the click
908 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
909 0 : if (!thumbFrame) {
910 0 : return NS_OK;
911 : }
912 0 : nsSize thumbSize = thumbFrame->GetSize();
913 0 : nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
914 :
915 0 : newpos -= (thumbLength/2);
916 :
917 0 : nsIBox* scrollbarBox = GetScrollbar();
918 0 : scrollbar = GetContentOfBox(scrollbarBox);
919 : }
920 :
921 0 : DragThumb(true);
922 :
923 0 : if (scrollToClick) {
924 : // should aMaySnap be true here?
925 0 : SetCurrentThumbPosition(scrollbar, newpos, false, false, false);
926 : }
927 :
928 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
929 0 : if (!thumbFrame) {
930 0 : return NS_OK;
931 : }
932 :
933 0 : if (isHorizontal)
934 0 : mThumbStart = thumbFrame->GetPosition().x;
935 : else
936 0 : mThumbStart = thumbFrame->GetPosition().y;
937 :
938 0 : mDragStart = pos - mThumbStart;
939 :
940 : #ifdef DEBUG_SLIDER
941 : printf("Pressed mDragStart=%d\n",mDragStart);
942 : #endif
943 :
944 0 : return NS_OK;
945 : }
946 :
947 : void
948 0 : nsSliderFrame::DragThumb(bool aGrabMouseEvents)
949 : {
950 : // inform the parent <scale> that a drag is beginning or ending
951 0 : nsIFrame* parent = GetParent();
952 0 : if (parent) {
953 0 : nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
954 0 : if (sliderListener) {
955 : nsContentUtils::AddScriptRunner(
956 0 : new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents));
957 : }
958 : }
959 :
960 0 : nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nsnull,
961 0 : aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0);
962 0 : }
963 :
964 : bool
965 0 : nsSliderFrame::isDraggingThumb()
966 : {
967 0 : return (nsIPresShell::GetCapturingContent() == GetContent());
968 : }
969 :
970 : void
971 0 : nsSliderFrame::AddListener()
972 : {
973 0 : if (!mMediator) {
974 0 : mMediator = new nsSliderMediator(this);
975 : }
976 :
977 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
978 0 : if (!thumbFrame) {
979 0 : return;
980 : }
981 0 : thumbFrame->GetContent()->
982 0 : AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator,
983 0 : false, false);
984 : }
985 :
986 : void
987 0 : nsSliderFrame::RemoveListener()
988 : {
989 0 : NS_ASSERTION(mMediator, "No listener was ever added!!");
990 :
991 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
992 0 : if (!thumbFrame)
993 0 : return;
994 :
995 0 : thumbFrame->GetContent()->
996 0 : RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false);
997 : }
998 :
999 : NS_IMETHODIMP
1000 0 : nsSliderFrame::HandlePress(nsPresContext* aPresContext,
1001 : nsGUIEvent* aEvent,
1002 : nsEventStatus* aEventStatus)
1003 : {
1004 : #ifdef XP_MACOSX
1005 : // On Mac the option key inverts the scroll-to-here preference.
1006 : if (((nsMouseEvent *)aEvent)->isAlt != GetScrollToClick())
1007 : #else
1008 0 : if (((nsMouseEvent *)aEvent)->isShift != GetScrollToClick())
1009 : #endif
1010 0 : return NS_OK;
1011 :
1012 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
1013 0 : if (!thumbFrame) // display:none?
1014 0 : return NS_OK;
1015 :
1016 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
1017 0 : nsGkAtoms::_true, eCaseMatters))
1018 0 : return NS_OK;
1019 :
1020 0 : nsRect thumbRect = thumbFrame->GetRect();
1021 :
1022 0 : nscoord change = 1;
1023 : nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
1024 0 : this);
1025 0 : if (IsHorizontal() ? eventPoint.x < thumbRect.x
1026 : : eventPoint.y < thumbRect.y)
1027 0 : change = -1;
1028 :
1029 0 : mChange = change;
1030 0 : DragThumb(true);
1031 0 : mDestinationPoint = eventPoint;
1032 0 : StartRepeat();
1033 0 : PageUpDown(change);
1034 0 : return NS_OK;
1035 : }
1036 :
1037 : NS_IMETHODIMP
1038 0 : nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
1039 : nsGUIEvent* aEvent,
1040 : nsEventStatus* aEventStatus)
1041 : {
1042 0 : StopRepeat();
1043 :
1044 0 : return NS_OK;
1045 : }
1046 :
1047 : void
1048 0 : nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot)
1049 : {
1050 : // tell our mediator if we have one we are gone.
1051 0 : if (mMediator) {
1052 0 : mMediator->SetSlider(nsnull);
1053 0 : mMediator = nsnull;
1054 : }
1055 0 : StopRepeat();
1056 :
1057 : // call base class Destroy()
1058 0 : nsBoxFrame::DestroyFrom(aDestructRoot);
1059 0 : }
1060 :
1061 : nsSize
1062 0 : nsSliderFrame::GetPrefSize(nsBoxLayoutState& aState)
1063 : {
1064 0 : EnsureOrient();
1065 0 : return nsBoxFrame::GetPrefSize(aState);
1066 : }
1067 :
1068 : nsSize
1069 0 : nsSliderFrame::GetMinSize(nsBoxLayoutState& aState)
1070 : {
1071 0 : EnsureOrient();
1072 :
1073 : // our min size is just our borders and padding
1074 0 : return nsBox::GetMinSize(aState);
1075 : }
1076 :
1077 : nsSize
1078 0 : nsSliderFrame::GetMaxSize(nsBoxLayoutState& aState)
1079 : {
1080 0 : EnsureOrient();
1081 0 : return nsBoxFrame::GetMaxSize(aState);
1082 : }
1083 :
1084 : void
1085 0 : nsSliderFrame::EnsureOrient()
1086 : {
1087 0 : nsIBox* scrollbarBox = GetScrollbar();
1088 :
1089 0 : bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
1090 0 : if (isHorizontal)
1091 0 : mState |= NS_STATE_IS_HORIZONTAL;
1092 : else
1093 0 : mState &= ~NS_STATE_IS_HORIZONTAL;
1094 0 : }
1095 :
1096 :
1097 0 : void nsSliderFrame::Notify(void)
1098 : {
1099 0 : bool stop = false;
1100 :
1101 0 : nsIFrame* thumbFrame = mFrames.FirstChild();
1102 0 : if (!thumbFrame) {
1103 0 : StopRepeat();
1104 0 : return;
1105 : }
1106 0 : nsRect thumbRect = thumbFrame->GetRect();
1107 :
1108 0 : bool isHorizontal = IsHorizontal();
1109 :
1110 : // See if the thumb has moved past our destination point.
1111 : // if it has we want to stop.
1112 0 : if (isHorizontal) {
1113 0 : if (mChange < 0) {
1114 0 : if (thumbRect.x < mDestinationPoint.x)
1115 0 : stop = true;
1116 : } else {
1117 0 : if (thumbRect.x + thumbRect.width > mDestinationPoint.x)
1118 0 : stop = true;
1119 : }
1120 : } else {
1121 0 : if (mChange < 0) {
1122 0 : if (thumbRect.y < mDestinationPoint.y)
1123 0 : stop = true;
1124 : } else {
1125 0 : if (thumbRect.y + thumbRect.height > mDestinationPoint.y)
1126 0 : stop = true;
1127 : }
1128 : }
1129 :
1130 :
1131 0 : if (stop) {
1132 0 : StopRepeat();
1133 : } else {
1134 0 : PageUpDown(mChange);
1135 : }
1136 : }
1137 :
1138 0 : NS_IMPL_ISUPPORTS1(nsSliderMediator,
1139 : nsIDOMEventListener)
|