Remove most dependencies of Widget/ScrollView onto native QWidgets.
[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  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28
29 #include "PlatformScrollBar.h"
30
31 #include "EventHandler.h"
32 #include "FrameView.h"
33 #include "Frame.h"
34 #include "GraphicsContext.h"
35 #include "IntRect.h"
36 #include "PlatformMouseEvent.h"
37
38 #include <QApplication>
39 #include <QDebug>
40 #include <QPainter>
41 #include <QStyle>
42
43 using namespace std;
44
45 namespace WebCore {
46
47 const double cInitialTimerDelay = 0.25;
48 const double cNormalTimerDelay = 0.05;
49
50 PlatformScrollbar::PlatformScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size)
51     : Scrollbar(client, orientation, size), m_pressedPos(0),
52       m_pressedPart(QStyle::SC_None),
53       m_hoveredPart(QStyle::SC_None),
54       m_scrollTimer(this, &PlatformScrollbar::autoscrollTimerFired)
55 {
56     QStyle *s = QApplication::style();
57
58     m_opt.state = QStyle::State_Active | QStyle::State_Enabled;
59     m_opt.sliderValue = m_opt.sliderPosition = 0;
60     m_opt.upsideDown = false;
61     setEnabled(true);
62     if (size != RegularScrollbar)
63         m_opt.state |= QStyle::State_Mini;
64     if (orientation == HorizontalScrollbar) {
65         m_opt.rect.setHeight(horizontalScrollbarHeight(size));
66         m_opt.orientation = Qt::Horizontal;
67         m_opt.state |= QStyle::State_Horizontal;
68     } else {
69         m_opt.rect.setWidth(verticalScrollbarWidth(size));
70         m_opt.orientation = Qt::Vertical;
71         m_opt.state &= ~QStyle::State_Horizontal;
72     }
73 }
74
75 PlatformScrollbar::~PlatformScrollbar()
76 {
77     stopTimerIfNeeded();
78 }
79
80 void PlatformScrollbar::updateThumbPosition()
81 {
82     invalidate();
83 }
84
85 void PlatformScrollbar::updateThumbProportion()
86 {
87     invalidate();
88 }
89
90 int PlatformScrollbar::width() const
91 {
92     return m_opt.rect.width();
93 }
94
95 int PlatformScrollbar::height() const
96 {
97     return m_opt.rect.height();
98 }
99
100 void PlatformScrollbar::setRect(const IntRect& rect)
101 {
102     setFrameGeometry(rect);
103 }
104
105 IntRect PlatformScrollbar::frameGeometry() const
106 {
107     return m_opt.rect;
108 }
109
110 void PlatformScrollbar::setFrameGeometry(const IntRect& rect)
111 {
112     m_opt.rect = rect;
113 }
114
115 bool PlatformScrollbar::isEnabled() const
116 {
117     return m_opt.state & QStyle::State_Enabled;
118 }
119
120 void PlatformScrollbar::setEnabled(bool enabled)
121 {
122     if (enabled != isEnabled()) {
123         if (enabled) {
124             m_opt.state |= QStyle::State_Enabled;
125         } else {
126             m_opt.state &= ~QStyle::State_Enabled;
127         }
128         invalidate();
129     }
130 }
131
132 void PlatformScrollbar::paint(GraphicsContext* graphicsContext, const IntRect& damageRect)
133 {
134     if (controlSize() != RegularScrollbar) {
135         m_opt.state |= QStyle::State_Mini;
136     } else {
137         m_opt.state &= ~QStyle::State_Mini;
138     }
139     m_opt.orientation = (orientation() == VerticalScrollbar) ? Qt::Vertical : Qt::Horizontal;
140     QStyle *s = QApplication::style();
141     if (orientation() == HorizontalScrollbar) {
142         m_opt.rect.setHeight(horizontalScrollbarHeight(controlSize()));
143         m_opt.state |= QStyle::State_Horizontal;
144     } else {
145         m_opt.rect.setWidth(verticalScrollbarWidth(controlSize()));
146         m_opt.state &= ~QStyle::State_Horizontal;
147     }
148
149     if (graphicsContext->paintingDisabled() || !m_opt.rect.isValid())
150         return;
151
152     // Don't paint anything if the scrollbar doesn't intersect the damage rect.
153     if (!m_opt.rect.intersects(damageRect))
154         return;
155
156     QPainter *p = graphicsContext->platformContext();
157     m_opt.sliderValue = value();
158     m_opt.sliderPosition = value();
159     m_opt.pageStep = m_visibleSize;
160     m_opt.singleStep = m_lineStep;
161     m_opt.minimum = 0;
162     m_opt.maximum = qMax(0, m_totalSize - m_visibleSize);
163     if (m_pressedPart != QStyle::SC_None) {
164         m_opt.activeSubControls = m_pressedPart;
165     } else {
166         m_opt.activeSubControls = m_hoveredPart;
167     }
168
169     const QPoint topLeft = m_opt.rect.topLeft();
170     p->translate(topLeft);
171     m_opt.rect.moveTo(QPoint(0, 0));
172     QApplication::style()->drawComplexControl(QStyle::CC_ScrollBar, &m_opt, p, 0);
173     m_opt.rect.moveTo(topLeft);
174     p->translate(-topLeft);
175 }
176
177 int PlatformScrollbar::thumbPosition() const
178 {
179     if (isEnabled())
180         return (int)((float)m_currentPos * (trackLength() - thumbLength()) / (m_totalSize - m_visibleSize));
181     return 0;
182 }
183
184 int PlatformScrollbar::thumbLength() const
185 {
186     IntRect thumb = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarSlider, 0);
187     return m_orientation == HorizontalScrollbar ? thumb.width() : thumb.height();
188 }
189
190 int PlatformScrollbar::trackLength() const
191 {
192     IntRect track = QApplication::style()->subControlRect(QStyle::CC_ScrollBar, &m_opt, QStyle::SC_ScrollBarGroove, 0);
193     return m_orientation == HorizontalScrollbar ? track.width() : track.height();
194 }
195
196 bool PlatformScrollbar::handleMouseMoveEvent(const PlatformMouseEvent& evt)
197 {
198     const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
199     //qDebug() << "PlatformScrollbar::handleMouseMoveEvent" << m_opt.rect << pos << endl;
200
201     m_opt.state |= QStyle::State_MouseOver;
202     const QPoint ctlPt = m_opt.rect.topLeft();
203     m_opt.rect.moveTo(0, 0);
204     QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
205     m_opt.rect.moveTo(ctlPt);
206
207     if (sc == m_pressedPart) {
208         m_opt.state |= QStyle::State_Sunken;
209     } else {
210         m_opt.state &= ~QStyle::State_Sunken;
211     }
212
213     if (m_pressedPart == QStyle::SC_ScrollBarSlider) {
214         // Drag the thumb.
215         int thumbPos = thumbPosition();
216         int thumbLen = thumbLength();
217         int trackLen = trackLength();
218         int maxPos = trackLen - thumbLen;
219         int delta = 0;
220         if (m_orientation == HorizontalScrollbar)
221             delta = pos.x() - m_pressedPos;
222         else
223             delta = pos.y() - m_pressedPos;
224
225         if (delta > 0)
226             // The mouse moved down/right.
227             delta = min(maxPos - thumbPos, delta);
228         else if (delta < 0)
229             // The mouse moved up/left.
230             delta = max(-thumbPos, delta);
231
232         if (delta != 0) {
233             setValue((int)((float)(thumbPos + delta) * (m_totalSize - m_visibleSize) / (trackLen - thumbLen)));
234             m_pressedPos += thumbPosition() - thumbPos;
235         }
236         
237         return true;
238     }
239
240     if (m_pressedPart != QStyle::SC_None)
241         m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
242
243     if (sc != m_hoveredPart) {
244         if (m_pressedPart != QStyle::SC_None) {
245             if (sc == m_pressedPart) {
246                 // The mouse is moving back over the pressed part.  We
247                 // need to start up the timer action again.
248                 startTimerIfNeeded(cNormalTimerDelay);
249                 invalidate();
250             } else if (m_hoveredPart == m_pressedPart) {
251                 // The mouse is leaving the pressed part.  Kill our timer
252                 // if needed.
253                 stopTimerIfNeeded();
254                 invalidate();
255             }
256         } else {
257             invalidate();
258         }
259         m_hoveredPart = sc;
260     } 
261
262     return true;
263 }
264
265 bool PlatformScrollbar::handleMouseOutEvent(const PlatformMouseEvent& evt)
266 {
267     m_opt.state &= ~QStyle::State_MouseOver;
268     m_opt.state &= ~QStyle::State_Sunken;
269     invalidate();
270     return true;
271 }
272
273 bool PlatformScrollbar::handleMousePressEvent(const PlatformMouseEvent& evt)
274 {
275     const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
276     //qDebug() << "PlatformScrollbar::handleMousePressEvent" << m_opt.rect << pos << endl;
277
278     const QPoint ctlPt = m_opt.rect.topLeft();
279     m_opt.rect.moveTo(0, 0);
280     QStyle::SubControl sc = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &m_opt, QPoint(pos) - ctlPt, 0);
281     m_opt.rect.moveTo(ctlPt);
282     switch (sc) {
283         case QStyle::SC_ScrollBarAddLine:
284         case QStyle::SC_ScrollBarSubLine:
285         case QStyle::SC_ScrollBarSlider:
286             m_opt.state |= QStyle::State_Sunken;
287         case QStyle::SC_ScrollBarAddPage:
288         case QStyle::SC_ScrollBarSubPage:
289         case QStyle::SC_ScrollBarGroove:
290             m_pressedPart = sc;
291             break;
292         default:
293             m_pressedPart = QStyle::SC_None;
294             return false;
295     }
296     m_pressedPos = m_orientation == HorizontalScrollbar ? pos.x() : pos.y();
297     autoscrollPressedPart(cInitialTimerDelay);
298     invalidate();
299     return true;
300 }
301
302 bool PlatformScrollbar::handleMouseReleaseEvent(const PlatformMouseEvent& evt)
303 {
304     const QPoint pos = parent()->convertFromContainingWindow(evt.pos());
305     //qDebug() << "PlatformScrollbar::handleMouseReleaseEvent" << m_opt.rect << pos << endl;
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