* Make the qDebug more usable. Do not have an additional newline and print
[WebKit-https.git] / WebCore / platform / qt / PlatformScrollBarQt.cpp
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2007 Staikos Computing Services Inc. <info@staikos.net>
4  * Copyright (C) 2007 Trolltech ASA
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29
30 #include "PlatformScrollBar.h"
31
32 #include "EventHandler.h"
33 #include "FrameView.h"
34 #include "Frame.h"
35 #include "GraphicsContext.h"
36 #include "IntRect.h"
37 #include "PlatformMouseEvent.h"
38
39 #include <QApplication>
40 #include <QDebug>
41 #include <QPainter>
42 #include <QStyle>
43
44 using namespace std;
45
46 namespace WebCore {
47
48 const double cInitialTimerDelay = 0.25;
49 const double cNormalTimerDelay = 0.05;
50
51 PlatformScrollbar::PlatformScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size)
52     : Scrollbar(client, orientation, size)
53     , m_pressedPos(0)
54     , m_pressedPart(QStyle::SC_None)
55     , m_hoveredPart(QStyle::SC_None)
56     , m_scrollTimer(this, &PlatformScrollbar::autoscrollTimerFired)
57 {
58     QStyle *s = QApplication::style();
59
60     m_opt.state = QStyle::State_Active | QStyle::State_Enabled;
61     m_opt.sliderValue = m_opt.sliderPosition = 0;
62     m_opt.upsideDown = false;
63     setEnabled(true);
64     if (size != RegularScrollbar)
65         m_opt.state |= QStyle::State_Mini;
66     if (orientation == HorizontalScrollbar) {
67         m_opt.rect.setHeight(horizontalScrollbarHeight(size));
68         m_opt.orientation = Qt::Horizontal;
69         m_opt.state |= QStyle::State_Horizontal;
70     } else {
71         m_opt.rect.setWidth(verticalScrollbarWidth(size));
72         m_opt.orientation = Qt::Vertical;
73         m_opt.state &= ~QStyle::State_Horizontal;
74     }
75 }
76
77 PlatformScrollbar::~PlatformScrollbar()
78 {
79     stopTimerIfNeeded();
80 }
81
82 void PlatformScrollbar::updateThumbPosition()
83 {
84     invalidate();
85 }
86
87 void PlatformScrollbar::updateThumbProportion()
88 {
89     invalidate();
90 }
91
92 int PlatformScrollbar::width() const
93 {
94     return m_opt.rect.width();
95 }
96
97 int PlatformScrollbar::height() const
98 {
99     return m_opt.rect.height();
100 }
101
102 void PlatformScrollbar::setRect(const IntRect& rect)
103 {
104     setFrameGeometry(rect);
105 }
106
107 IntRect PlatformScrollbar::frameGeometry() const
108 {
109     return m_opt.rect;
110 }
111
112 void PlatformScrollbar::setFrameGeometry(const IntRect& rect)
113 {
114     m_opt.rect = rect;
115 }
116
117 bool PlatformScrollbar::isEnabled() const
118 {
119     return m_opt.state & QStyle::State_Enabled;
120 }
121
122 void PlatformScrollbar::setEnabled(bool enabled)
123 {
124     if (enabled != isEnabled()) {
125         if (enabled) {
126             m_opt.state |= QStyle::State_Enabled;
127         } else {
128             m_opt.state &= ~QStyle::State_Enabled;
129         }
130         invalidate();
131     }
132 }
133
134 void PlatformScrollbar::paint(GraphicsContext* graphicsContext, const IntRect& damageRect)
135 {
136     if (controlSize() != RegularScrollbar) {
137         m_opt.state |= QStyle::State_Mini;
138     } else {
139         m_opt.state &= ~QStyle::State_Mini;
140     }
141     m_opt.orientation = (orientation() == VerticalScrollbar) ? Qt::Vertical : Qt::Horizontal;
142     QStyle *s = QApplication::style();
143     if (orientation() == HorizontalScrollbar) {
144         m_opt.rect.setHeight(horizontalScrollbarHeight(controlSize()));
145         m_opt.state |= QStyle::State_Horizontal;
146     } else {
147         m_opt.rect.setWidth(verticalScrollbarWidth(controlSize()));
148         m_opt.state &= ~QStyle::State_Horizontal;
149     }
150
151     if (graphicsContext->paintingDisabled() || !m_opt.rect.isValid())
152         return;
153
154     // Don't paint anything if the scrollbar doesn't intersect the damage rect.
155     if (!m_opt.rect.intersects(damageRect))
156         return;
157
158     QPainter *p = graphicsContext->platformContext();
159     m_opt.sliderValue = value();
160     m_opt.sliderPosition = value();
161     m_opt.pageStep = m_visibleSize;
162     m_opt.singleStep = m_lineStep;
163     m_opt.minimum = 0;
164     m_opt.maximum = qMax(0, m_totalSize - m_visibleSize);
165     if (m_pressedPart != QStyle::SC_None) {
166         m_opt.activeSubControls = m_pressedPart;
167     } else {
168         m_opt.activeSubControls = m_hoveredPart;
169     }
170
171     const QPoint topLeft = m_opt.rect.topLeft();
172     p->translate(topLeft);
173     m_opt.rect.moveTo(QPoint(0, 0));
174     QApplication::style()->drawComplexControl(QStyle::CC_ScrollBar, &m_opt, p, 0);
175     m_opt.rect.moveTo(topLeft);
176     p->translate(-topLeft);
177 }
178
179 int PlatformScrollbar::thumbPosition() const
180 {
181     if (isEnabled())
182         return (int)((float)m_currentPos * (trackLength() - thumbLength()) / (m_totalSize - m_visibleSize));
183     return 0;
184 }
185
186 int PlatformScrollbar::thumbLength() const
187 {
188     IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0);
189     return m_orientation == HorizontalScrollbar ? thumb.width() : thumb.height();
190 }
191
192 int PlatformScrollbar::trackLength() const
193 {
194     IntRect track = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarGroove, 0);
195     return m_orientation == HorizontalScrollbar ? track.width() : track.height();
196 }
197
198 bool PlatformScrollbar::handleMouseMoveEvent(const PlatformMouseEvent& evt)
199 {
200     const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
201     //qDebug() << "PlatformScrollbar::handleMouseMoveEvent" << m_opt.rect << pos << evt.pos();
202
203     m_opt.state |= QStyle::State_MouseOver;
204     const QPoint ctlPt = m_opt.rect.topLeft();
205     m_opt.rect.moveTo(0, 0);
206     QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
207     m_opt.rect.moveTo(ctlPt);
208
209     if (sc == m_pressedPart) {
210         m_opt.state |= QStyle::State_Sunken;
211     } else {
212         m_opt.state &= ~QStyle::State_Sunken;
213     }
214
215     if (m_pressedPart == QStyle::SC_ScrollBarSlider) {
216         // Drag the thumb.
217         int thumbPos = thumbPosition();
218         int thumbLen = thumbLength();
219         int trackLen = trackLength();
220         int maxPos = trackLen - thumbLen;
221         int delta = 0;
222         if (m_orientation == HorizontalScrollbar)
223             delta = pos.x() - m_pressedPos;
224         else
225             delta = pos.y() - m_pressedPos;
226
227         if (delta > 0)
228             // The mouse moved down/right.
229             delta = min(maxPos - thumbPos, delta);
230         else if (delta < 0)
231             // The mouse moved up/left.
232             delta = max(-thumbPos, delta);
233
234         if (delta != 0) {
235             setValue((int)((float)(thumbPos + delta) * (m_totalSize - m_visibleSize) / (trackLen - thumbLen)));
236             m_pressedPos += thumbPosition() - thumbPos;
237         }
238         
239         return true;
240     }
241
242     if (m_pressedPart != QStyle::SC_None)
243         m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
244
245     if (sc != m_hoveredPart) {
246         if (m_pressedPart != QStyle::SC_None) {
247             if (sc == m_pressedPart) {
248                 // The mouse is moving back over the pressed part.  We
249                 // need to start up the timer action again.
250                 startTimerIfNeeded(cNormalTimerDelay);
251                 invalidate();
252             } else if (m_hoveredPart == m_pressedPart) {
253                 // The mouse is leaving the pressed part.  Kill our timer
254                 // if needed.
255                 stopTimerIfNeeded();
256                 invalidate();
257             }
258         } else {
259             invalidate();
260         }
261         m_hoveredPart = sc;
262     } 
263
264     return true;
265 }
266
267 bool PlatformScrollbar::handleMouseOutEvent(const PlatformMouseEvent& evt)
268 {
269     m_opt.state &= ~QStyle::State_MouseOver;
270     m_opt.state &= ~QStyle::State_Sunken;
271     invalidate();
272     return true;
273 }
274
275 bool PlatformScrollbar::handleMousePressEvent(const PlatformMouseEvent& evt)
276 {
277     const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
278     //qDebug() << "PlatformScrollbar::handleMousePressEvent" << m_opt.rect << pos << evt.pos();
279
280     const QPoint ctlPt = m_opt.rect.topLeft();
281     m_opt.rect.moveTo(0, 0);
282     QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
283     m_opt.rect.moveTo(ctlPt);
284     switch (sc) {
285         case QStyle::SC_ScrollBarAddLine:
286         case QStyle::SC_ScrollBarSubLine:
287         case QStyle::SC_ScrollBarSlider:
288             m_opt.state |= QStyle::State_Sunken;
289         case QStyle::SC_ScrollBarAddPage:
290         case QStyle::SC_ScrollBarSubPage:
291         case QStyle::SC_ScrollBarGroove:
292             m_pressedPart = sc;
293             break;
294         default:
295             m_pressedPart = QStyle::SC_None;
296             return false;
297     }
298     m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
299     autoscrollPressedPart(cInitialTimerDelay);
300     invalidate();
301     return true;
302 }
303
304 bool PlatformScrollbar::handleMouseReleaseEvent(const PlatformMouseEvent&)
305 {
306     m_opt.state &= ~QStyle::State_Sunken;
307     m_pressedPart = QStyle::SC_None;
308     m_pressedPos = 0;
309     stopTimerIfNeeded();
310     invalidate();
311     return true;
312 }
313
314 void PlatformScrollbar::startTimerIfNeeded(double delay)
315 {
316     // Don't do anything for the thumb.
317     if (m_pressedPart == QStyle::SC_ScrollBarSlider)
318         return;
319
320     // Handle the track.  We halt track scrolling once the thumb is level
321     // with us.
322     if (m_pressedPart == QStyle::SC_ScrollBarGroove && thumbUnderMouse()) {
323         invalidate();
324         m_hoveredPart = QStyle::SC_ScrollBarSlider;
325         return;
326     }
327
328     // We can't scroll if we've hit the beginning or end.
329     ScrollDirection dir = pressedPartScrollDirection();
330     if (dir == ScrollUp || dir == ScrollLeft) {
331         if (m_currentPos == 0)
332             return;
333     } else {
334         if (m_currentPos == m_totalSize - m_visibleSize)
335             return;
336     }
337
338     m_scrollTimer.startOneShot(delay);
339 }
340
341 void PlatformScrollbar::stopTimerIfNeeded()
342 {
343     if (m_scrollTimer.isActive())
344         m_scrollTimer.stop();
345 }
346
347 void PlatformScrollbar::autoscrollPressedPart(double delay)
348 {
349     // Don't do anything for the thumb or if nothing was pressed.
350     if (m_pressedPart == QStyle::SC_ScrollBarSlider || m_pressedPart == QStyle::SC_None)
351         return;
352
353     // Handle the track.
354     if (m_pressedPart == QStyle::SC_ScrollBarGroove && thumbUnderMouse()) {
355         invalidate();
356         m_hoveredPart = QStyle::SC_ScrollBarSlider;
357         return;
358     }
359
360     // Handle the arrows and track.
361     if (scroll(pressedPartScrollDirection(), pressedPartScrollGranularity()))
362         startTimerIfNeeded(delay);
363 }
364
365 void PlatformScrollbar::autoscrollTimerFired(Timer<PlatformScrollbar>*)
366 {
367     autoscrollPressedPart(cNormalTimerDelay);
368 }
369
370 ScrollDirection PlatformScrollbar::pressedPartScrollDirection()
371 {
372     if (m_orientation == HorizontalScrollbar) {
373         if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarSubPage)
374             return ScrollLeft;
375         return ScrollRight;
376     } else {
377         if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarSubPage)
378             return ScrollUp;
379         return ScrollDown;
380     }
381 }
382
383 ScrollGranularity PlatformScrollbar::pressedPartScrollGranularity()
384 {
385     if (m_pressedPart == QStyle::SC_ScrollBarSubLine || m_pressedPart == QStyle::SC_ScrollBarAddLine)
386         return ScrollByLine;
387     return ScrollByPage;
388 }
389
390 bool PlatformScrollbar::thumbUnderMouse()
391 {
392     // Construct a rect.
393     IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0);
394     thumb.move(-m_opt.rect.x(), -m_opt.rect.y());
395     int begin = (m_orientation == HorizontalScrollbar) ? thumb.x() : thumb.y();
396     int end = (m_orientation == HorizontalScrollbar) ? thumb.right() : thumb.bottom();
397     return (begin <= m_pressedPos && m_pressedPos < end);
398 }
399
400 int PlatformScrollbar::horizontalScrollbarHeight(ScrollbarControlSize controlSize)
401 {
402     QStyle *s = QApplication::style();
403     QStyleOptionSlider o;
404     o.orientation = Qt::Horizontal;
405     o.state |= QStyle::State_Horizontal;
406     if (controlSize != RegularScrollbar)
407         o.state |= QStyle::State_Mini;
408     return s->pixelMetric(QStyle::PM_ScrollBarExtent, &o, 0);
409 }
410
411 int PlatformScrollbar::verticalScrollbarWidth(ScrollbarControlSize controlSize)
412 {
413     QStyle *s = QApplication::style();
414     QStyleOptionSlider o;
415     o.orientation = Qt::Vertical;
416     o.state &= ~QStyle::State_Horizontal;
417     if (controlSize != RegularScrollbar)
418         o.state |= QStyle::State_Mini;
419     return s->pixelMetric(QStyle::PM_ScrollBarExtent, &o, 0);
420 }
421
422 IntRect PlatformScrollbar::windowClipRect() const
423 {
424     IntRect clipRect = m_opt.rect;
425     if (m_client)
426         clipRect.intersect(m_client->windowClipRect());
427     return clipRect;
428 }
429
430 }
431
432 // vim: ts=4 sw=4 et