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