1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4   Rosegarden
5   A MIDI and audio sequencer and musical notation editor.
6   Copyright 2000-2021 the Rosegarden development team.
7 
8   Other copyrights also apply to some parts of this work.  Please
9   see the AUTHORS file and individual file headers for details.
10 
11   This program is free software; you can redistribute it and/or
12   modify it under the terms of the GNU General Public License as
13   published by the Free Software Foundation; either version 2 of the
14   License, or (at your option) any later version.  See the file
15   COPYING included with this distribution for more information.
16 */
17 
18 #define RG_MODULE_STRING "[RosegardenScrollView]"
19 
20 #include "RosegardenScrollView.h"
21 
22 #include "misc/Debug.h"
23 #include "gui/rulers/StandardRuler.h"
24 
25 #include <QApplication>
26 #include <QCursor>
27 #include <QMouseEvent>
28 #include <QRect>
29 #include <QScrollBar>
30 #include <QSizePolicy>
31 #include <QTimer>
32 #include <QWheelEvent>
33 #include <QWidget>
34 
35 #include <algorithm>  // std::swap(), std::min(), std::max()
36 
37 #include <math.h>
38 
39 
40 namespace Rosegarden
41 {
42 
43 
RosegardenScrollView(QWidget * parent)44 RosegardenScrollView::RosegardenScrollView(QWidget *parent)
45     : QAbstractScrollArea(parent),
46 
47       m_bottomRuler(nullptr),
48       m_contentsWidth(0),
49       m_contentsHeight(0)
50 {
51     // Turn off the frame which causes positioning issues.
52     // The rest of the code assumes there is no frame.
53     setFrameStyle(QFrame::NoFrame);
54 
55     m_autoScroller.connectScrollArea(this);
56 }
57 
contentsX()58 int RosegardenScrollView::contentsX()
59 {
60     return horizontalScrollBar()->value();
61 }
62 
contentsY()63 int RosegardenScrollView::contentsY()
64 {
65     return verticalScrollBar()->value();
66 }
67 
resizeContents(int w,int h)68 void RosegardenScrollView::resizeContents(int w, int h)
69 {
70     // Code lifted from Q3ScrollView::resizeContents().
71 
72     // Hold on to the old values.
73     int ow = m_contentsWidth;
74     int oh = m_contentsHeight;
75 
76     // We need to set these before we do the swaps, otherwise we may be
77     // storing the wrong (post-swap) values.
78     m_contentsWidth = w;
79     m_contentsHeight = h;
80 
81     // This was necessary until I fixed the resizeEvent connection
82     //d->scrollbar_timer.start(0, true);
83 
84     //### CJ - Don't think this is necessary - slightly confused as we're
85     //         resizing the content, not the widget
86     //if (d->children.isEmpty() && d->policy == Default)
87     //    setResizePolicy(Manual);
88 
89     // Make sure "w" is the larger one.
90     // ??? This would be easier to read using minWidth and maxWidth for the
91     //     smaller and the larger.
92     if (ow > w) {
93         std::swap(w, ow);
94     }
95 
96     // Refresh area ow..w (minWidth..maxWidth)
97     if (ow < viewport()->width()  &&  w >= 0) {
98         if (ow < 0)
99             ow = 0;
100         if (w > viewport()->width())
101             w = viewport()->width();
102         viewport()->update(contentsX()+ow, 0, w-ow, viewport()->height());
103     }
104 
105     // Make sure "h" is the larger one.
106     // ??? This would be easier to read using minHeight and maxHeight for
107     //     the smaller and the larger.
108     if (oh > h) {
109         std::swap(h, oh);
110     }
111 
112     // Refresh area oh..h (minHeight..maxHeight)
113     if (oh < viewport()->height()  &&  h >= 0) {
114         if (oh < 0)
115             oh = 0;
116         if (h > viewport()->height())
117             h = viewport()->height();
118         viewport()->update(0, contentsY()+oh, viewport()->width(), h-oh);
119     }
120 
121     // Since the contents size has changed, make sure the
122     // scrollbars are updated.
123     updateScrollBars();
124 }
125 
updateContents(int x,int y,int w,int h)126 void RosegardenScrollView::updateContents(int x, int y, int w, int h)
127 {
128     // Code lifted from Q3ScrollView::updateContents().
129 
130     if (!isVisible() || !updatesEnabled())
131         return;
132 
133     // Translate contents coords to viewport coords.
134     x -= contentsX();
135     y -= contentsY();
136 
137     // Cut off any portion left of the left edge.
138     if (x < 0) {
139         w += x;
140         x = 0;
141     }
142     // Cut off any portion above the top edge.
143     if (y < 0) {
144         h += y;
145         y = 0;
146     }
147 
148     if (w < 0 || h < 0)
149         return;
150 
151     // If x or y are beyond the viewport, bail.
152     if (x > viewport()->width()  ||  y > viewport()->height())
153         return;
154 
155     // No need to update more than can be seen.
156     if (w > viewport()->width())
157         w = viewport()->width();
158     if (h > viewport()->height())
159         h = viewport()->height();
160 
161     //### CJ - I don't think we used a clipped_viewport on Q3ScrollView
162     //if (d->clipped_viewport) {
163     //    // Translate clipper() to viewport()
164     //    x -= d->clipped_viewport->x();
165     //    y -= d->clipped_viewport->y();
166     //}
167 
168     viewport()->update(x, y, w, h);
169 }
170 
updateContents(const QRect & r)171 void RosegardenScrollView::updateContents(const QRect &r)
172 {
173     updateContents(r.x(), r.y(), r.width(), r.height());
174 }
175 
updateContents()176 void RosegardenScrollView::updateContents()
177 {
178     viewport()->update();
179 }
180 
updateScrollBars()181 void RosegardenScrollView::updateScrollBars()
182 {
183     horizontalScrollBar()->setMaximum(
184         std::max(m_contentsWidth - viewport()->width(), 0));
185     horizontalScrollBar()->setPageStep(viewport()->width());
186     horizontalScrollBar()->setSingleStep(viewport()->width() / 10);
187 
188     verticalScrollBar()->setMaximum(
189         std::max(m_contentsHeight - viewport()->height(), 0));
190     verticalScrollBar()->setPageStep(viewport()->height());
191     // Note: The vertical scrollbar's single step is set to the track
192     //       height in TrackEditor::init().
193 }
194 
viewportToContents(const QPoint & vp)195 QPoint RosegardenScrollView::viewportToContents(const QPoint &vp)
196 {
197     return QPoint(vp.x() + contentsX(),
198                   vp.y() + contentsY());
199 }
200 
setBottomRuler(StandardRuler * ruler)201 void RosegardenScrollView::setBottomRuler(StandardRuler *ruler)
202 {
203     m_bottomRuler = ruler;
204     if (m_bottomRuler) {
205         m_bottomRuler->setParent(this);
206         m_bottomRuler->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
207         // ??? Why do we have to add 1 to get enough room?
208         //     - Are the viewport's limits being ignored?  Is someone
209         //       overdrawing the bottom by 1?
210         //     - Is the bottom ruler expanding by 1?  No.  The hint is 25,
211         //       we size it to 25, and it stays at 25.
212         //     - Inclusive vs. exclusive math?  Don't think so.
213         setViewportMargins( 0, 0, 0, m_bottomRuler->sizeHint().height() + 1 );
214     }
215 }
216 
startAutoScroll()217 void RosegardenScrollView::startAutoScroll()
218 {
219     m_autoScroller.start();
220 }
221 
stopAutoScroll()222 void RosegardenScrollView::stopAutoScroll()
223 {
224     m_autoScroller.stop();
225 }
226 
scrollHoriz(int x)227 void RosegardenScrollView::scrollHoriz(int x)
228 {
229     QScrollBar *hbar = horizontalScrollBar();
230 
231     const int contentsX = hbar->value();
232 
233     if (x == 0) {
234 
235         hbar->setValue(0);
236 
237     } else if (x > (contentsX + viewport()->width() * 1.6)  ||
238                x < (contentsX - viewport()->width() * 0.7)) {
239         // The requested x is relatively far away.
240 
241         // Put it slightly to the left of center.
242         hbar->setValue(x - int(viewport()->width() * 0.4));
243 
244     } else if (x > (contentsX + viewport()->width() * 0.9)) {
245         // The requested x is slightly off to the right.
246 
247         // Go right a little more than half of a page.
248         hbar->setValue(contentsX + int(viewport()->width() * 0.6));
249 
250     } else if (x < (contentsX + viewport()->width() * 0.1)) {
251         // The requested x is slightly off to the left.
252 
253         // Go left a little more than half of a page.
254         hbar->setValue(contentsX - int(viewport()->width() * 0.6));
255     }
256 }
257 
scrollVert(int y)258 void RosegardenScrollView::scrollVert(int y)
259 {
260     // Ignore any request made before we've actually been rendered and sized.
261     if (viewport()->height() <= 1)
262         return;
263 
264     QScrollBar *vbar = verticalScrollBar();
265 
266     // One "line" (track) from the bottom.
267     int bottomMargin = contentsY() + viewport()->height() - vbar->singleStep();
268 
269     // If the requested y is below the bottom margin.
270     if (y > bottomMargin) {
271         int scrollY = y - bottomMargin;
272 
273         // Scroll to make sure the requested y is visible.
274         vbar->setValue(vbar->value() + scrollY);
275 
276         return;
277     }
278 
279     // If the requested y is above the top.
280     if (y < contentsY()) {
281         int scrollY = y - contentsY();
282 
283         // Scroll to make sure the requested y is at the top.
284         vbar->setValue(vbar->value() + scrollY);
285 
286         return;
287     }
288 }
289 
resizeEvent(QResizeEvent * e)290 void RosegardenScrollView::resizeEvent(QResizeEvent *e)
291 {
292     QAbstractScrollArea::resizeEvent(e);
293 
294     // Since the viewport size has changed, we need to update
295     // the scrollbars.
296     updateScrollBars();
297 
298     // Make sure the bottom ruler is where it needs to be.
299     updateBottomRulerGeometry();
300 
301     // Let TrackEditor know so it can resize the TrackButtons to match.
302     emit viewportResize();
303 }
304 
updateBottomRulerGeometry()305 void RosegardenScrollView::updateBottomRulerGeometry()
306 {
307     if (!m_bottomRuler)
308         return;
309 
310     int bottomRulerHeight = m_bottomRuler->sizeHint().height();
311     // Since there's no margin (see the call to setFrameStyle() in
312     // the ctor), we can assume the viewport coords match up with
313     // the parent coords.  No need to transform.
314     QRect viewportRect = viewport()->rect();
315 
316     // Move the bottom ruler to below the viewport.
317     m_bottomRuler->setGeometry(
318             viewportRect.left(),
319             viewportRect.bottom() + 1,  // +1 to be just under
320             viewportRect.width(),
321             bottomRulerHeight);  // See the call to setViewportMargins().
322 }
323 
wheelEvent(QWheelEvent * e)324 void RosegardenScrollView::wheelEvent(QWheelEvent *e)
325 {
326     //RG_DEBUG << "wheelEvent()";
327     //RG_DEBUG << "  delta(): " << e->delta();
328     //RG_DEBUG << "  angleDelta(): " << e->angleDelta();
329     //RG_DEBUG << "  pixelDelta(): " << e->pixelDelta();
330 
331     // See also Panned::processWheelEvent().
332 
333     // We'll handle this.  Don't pass to parent.
334     e->accept();
335 
336     QPoint angleDelta = e->angleDelta();
337 
338     // Ctrl+wheel to zoom
339     if (e->modifiers() & Qt::CTRL) {
340         // Wheel down
341         if (angleDelta.y() > 0)
342             emit zoomIn();
343         else if (angleDelta.y() < 0)  // Wheel up
344             emit zoomOut();
345 
346         return;
347     }
348 
349     // Shift+wheel to scroll left/right.
350     // If shift is pressed and we are scrolling vertically...
351     if ((e->modifiers() & Qt::SHIFT)  &&  angleDelta.y() != 0) {
352         // Transform the incoming vertical scroll event into a
353         // horizontal scroll event.
354 
355         // Swap x/y
356         QPoint pixelDelta2(e->pixelDelta().y(), e->pixelDelta().x());
357         QPoint angleDelta2(angleDelta.y(), angleDelta.x());
358 
359         // Create a new event.
360         // We remove the Qt::SHIFT modifier otherwise we end up
361         // moving left/right a page at a time.
362 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
363         QWheelEvent e2(
364                 e->position(),  // pos
365                 e->globalPosition(),  // globalPos
366                 pixelDelta2,  // pixelDelta
367                 angleDelta2,  // angleDelta
368                 e->buttons(),  // buttons
369                 e->modifiers() & ~Qt::SHIFT,  // modifiers
370                 e->phase(),  // phase
371                 e->inverted(),  // inverted
372                 e->source());  // source
373 #else
374         QWheelEvent e2(
375                 e->pos(),  // pos
376                 e->globalPosF(),  // globalPos
377                 pixelDelta2,  // pixelDelta
378                 angleDelta2,  // angleDelta
379                 e->delta(),  // qt4Delta
380                 Qt::Horizontal,  // qt4Orientation
381                 e->buttons(),  // buttons
382                 e->modifiers() & ~Qt::SHIFT,  // modifiers
383                 e->phase(),  // phase
384                 e->source(),  // source
385                 e->inverted());  // inverted
386 #endif
387 
388         // Let baseclass handle as usual.
389         QAbstractScrollArea::wheelEvent(&e2);
390 
391         return;
392     }
393 
394     // Let baseclass handle normal scrolling.
395     QAbstractScrollArea::wheelEvent(e);
396 }
397 
398 
399 }
400