annotate view/Overview.cpp @ 1553:76e4302a3fc2

Fix note numbering - ensure stable across whole track (as it used to be, but without scanning all notes in paint in order to do that)
author Chris Cannam
date Fri, 22 Nov 2019 14:12:50 +0000
parents e540aa5d89cd
children a0b2f3b4dd2f
rev   line source
Chris@173 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@173 2
Chris@173 3 /*
Chris@173 4 Sonic Visualiser
Chris@173 5 An audio file viewer and annotation editor.
Chris@173 6 Centre for Digital Music, Queen Mary, University of London.
Chris@182 7 This file copyright 2006 Chris Cannam and QMUL.
Chris@173 8
Chris@173 9 This program is free software; you can redistribute it and/or
Chris@173 10 modify it under the terms of the GNU General Public License as
Chris@173 11 published by the Free Software Foundation; either version 2 of the
Chris@173 12 License, or (at your option) any later version. See the file
Chris@173 13 COPYING included with this distribution for more information.
Chris@173 14 */
Chris@173 15
Chris@173 16 #include "Overview.h"
Chris@173 17 #include "layer/Layer.h"
Chris@173 18 #include "data/model/Model.h"
Chris@173 19 #include "base/ZoomConstraint.h"
Chris@173 20
Chris@173 21 #include <QPaintEvent>
Chris@173 22 #include <QPainter>
Chris@173 23 #include <iostream>
Chris@173 24
Chris@643 25 //#define DEBUG_OVERVIEW 1
Chris@642 26
Chris@682 27
Chris@173 28 Overview::Overview(QWidget *w) :
Chris@173 29 View(w, false),
Chris@854 30 m_clickedInRange(false),
Chris@854 31 m_dragCentreFrame(0)
Chris@173 32 {
Chris@173 33 setObjectName(tr("Overview"));
Chris@173 34 m_followPan = false;
Chris@173 35 m_followZoom = false;
Chris@211 36 setPlaybackFollow(PlaybackIgnore);
Chris@274 37 m_modelTestTime.start();
Chris@965 38
Chris@965 39 bool light = hasLightBackground();
Chris@965 40 if (light) m_boxColour = Qt::darkGray;
Chris@965 41 else m_boxColour = Qt::lightGray;
Chris@173 42 }
Chris@173 43
Chris@173 44 void
Chris@1481 45 Overview::modelChangedWithin(ModelId modelId, sv_frame_t startFrame, sv_frame_t endFrame)
Chris@173 46 {
Chris@1325 47 using namespace std::rel_ops;
Chris@1325 48
Chris@253 49 bool zoomChanged = false;
Chris@253 50
Chris@908 51 sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame();
Chris@1183 52 ZoomLevel zoomLevel { ZoomLevel::FramesPerPixel, int(frameCount / width()) };
Chris@1183 53 if (zoomLevel.level < 1) zoomLevel.level = 1;
Chris@1325 54 zoomLevel = getZoomConstraintLevel(zoomLevel, ZoomConstraint::RoundUp);
Chris@253 55 if (zoomLevel != m_zoomLevel) {
Chris@253 56 zoomChanged = true;
Chris@253 57 }
Chris@253 58
Chris@253 59 if (!zoomChanged) {
Chris@274 60 if (m_modelTestTime.elapsed() < 1000) {
Chris@835 61 for (LayerList::const_iterator i = m_layerStack.begin();
Chris@835 62 i != m_layerStack.end(); ++i) {
Chris@1475 63 auto model = ModelById::get((*i)->getModel());
Chris@1475 64 if (model && (!model->isOK() || !model->isReady())) {
Chris@274 65 return;
Chris@274 66 }
Chris@253 67 }
Chris@274 68 } else {
Chris@274 69 m_modelTestTime.restart();
Chris@253 70 }
Chris@253 71 }
Chris@253 72
Chris@1481 73 View::modelChangedWithin(modelId, startFrame, endFrame);
Chris@173 74 }
Chris@173 75
Chris@173 76 void
Chris@173 77 Overview::modelReplaced()
Chris@173 78 {
Chris@339 79 m_playPointerFrame = getAlignedPlaybackFrame();
Chris@173 80 View::modelReplaced();
Chris@173 81 }
Chris@173 82
Chris@173 83 void
Chris@211 84 Overview::registerView(View *view)
Chris@173 85 {
Chris@211 86 m_views.insert(view);
Chris@173 87 update();
Chris@173 88 }
Chris@173 89
Chris@173 90 void
Chris@211 91 Overview::unregisterView(View *view)
Chris@173 92 {
Chris@211 93 m_views.erase(view);
Chris@173 94 update();
Chris@173 95 }
Chris@173 96
Chris@173 97 void
Chris@908 98 Overview::globalCentreFrameChanged(sv_frame_t
Chris@806 99 #ifdef DEBUG_OVERVIEW
Chris@806 100 f
Chris@806 101 #endif
Chris@806 102 )
Chris@173 103 {
Chris@642 104 #ifdef DEBUG_OVERVIEW
Chris@682 105 cerr << "Overview::globalCentreFrameChanged: " << f << endl;
Chris@642 106 #endif
Chris@211 107 update();
Chris@211 108 }
Chris@173 109
Chris@211 110 void
Chris@908 111 Overview::viewCentreFrameChanged(View *v, sv_frame_t
Chris@806 112 #ifdef DEBUG_OVERVIEW
Chris@806 113 f
Chris@806 114 #endif
Chris@806 115 )
Chris@211 116 {
Chris@642 117 #ifdef DEBUG_OVERVIEW
Chris@682 118 cerr << "Overview[" << this << "]::viewCentreFrameChanged(" << v << "): " << f << endl;
Chris@642 119 #endif
Chris@211 120 if (m_views.find(v) != m_views.end()) {
Chris@1266 121 update();
Chris@173 122 }
Chris@211 123 }
Chris@173 124
Chris@173 125 void
Chris@1326 126 Overview::viewZoomLevelChanged(View *v, ZoomLevel, bool)
Chris@173 127 {
Chris@222 128 if (v == this) return;
Chris@211 129 if (m_views.find(v) != m_views.end()) {
Chris@1266 130 update();
Chris@173 131 }
Chris@173 132 }
Chris@173 133
Chris@173 134 void
Chris@908 135 Overview::viewManagerPlaybackFrameChanged(sv_frame_t f)
Chris@173 136 {
Chris@642 137 #ifdef DEBUG_OVERVIEW
Chris@682 138 cerr << "Overview[" << this << "]::viewManagerPlaybackFrameChanged(" << f << "): " << f << endl;
Chris@642 139 #endif
Chris@642 140
Chris@173 141 bool changed = false;
Chris@173 142
Chris@339 143 f = getAlignedPlaybackFrame();
Chris@339 144
Chris@173 145 if (getXForFrame(m_playPointerFrame) != getXForFrame(f)) changed = true;
Chris@173 146 m_playPointerFrame = f;
Chris@173 147
Chris@173 148 if (changed) update();
Chris@173 149 }
Chris@173 150
Chris@871 151 QColor
Chris@871 152 Overview::getFillWithin() const
Chris@871 153 {
Chris@871 154 return Qt::transparent;
Chris@871 155 }
Chris@871 156
Chris@871 157 QColor
Chris@871 158 Overview::getFillWithout() const
Chris@871 159 {
Chris@871 160 QColor c = palette().window().color();
Chris@871 161 c.setAlpha(100);
Chris@871 162 return c;
Chris@871 163 }
Chris@871 164
Chris@173 165 void
Chris@965 166 Overview::setBoxColour(QColor c)
Chris@965 167 {
Chris@965 168 m_boxColour = c;
Chris@965 169 }
Chris@965 170
Chris@965 171 void
Chris@173 172 Overview::paintEvent(QPaintEvent *e)
Chris@173 173 {
Chris@1325 174 using namespace std::rel_ops;
Chris@1325 175
Chris@173 176 // Recalculate zoom in case the size of the widget has changed.
Chris@173 177
Chris@642 178 #ifdef DEBUG_OVERVIEW
Chris@682 179 cerr << "Overview::paintEvent: width is " << width() << ", centre frame " << m_centreFrame << endl;
Chris@642 180 #endif
Chris@214 181
Chris@908 182 sv_frame_t startFrame = getModelsStartFrame();
Chris@908 183 sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame();
Chris@1325 184 ZoomLevel zoomLevel { ZoomLevel::FramesPerPixel, int(frameCount / width()) };
Chris@1325 185 if (zoomLevel.level < 1) zoomLevel.level = 1;
Chris@1325 186 zoomLevel = getZoomConstraintLevel(zoomLevel, ZoomConstraint::RoundUp);
Chris@173 187 if (zoomLevel != m_zoomLevel) {
Chris@1266 188 m_zoomLevel = zoomLevel;
Chris@1266 189 emit zoomLevelChanged(m_zoomLevel, m_followZoom);
Chris@173 190 }
Chris@253 191
Chris@1326 192 sv_frame_t centreFrame = startFrame +
Chris@1326 193 sv_frame_t(round(m_zoomLevel.pixelsToFrames(width()/2)));
Chris@1326 194
Chris@173 195 if (centreFrame > (startFrame + getModelsEndFrame())/2) {
Chris@1266 196 centreFrame = (startFrame + getModelsEndFrame())/2;
Chris@173 197 }
Chris@173 198 if (centreFrame != m_centreFrame) {
Chris@642 199 #ifdef DEBUG_OVERVIEW
Chris@682 200 cerr << "Overview::paintEvent: Centre frame changed from "
Chris@642 201 << m_centreFrame << " to " << centreFrame << " and thus start frame from " << getStartFrame();
Chris@642 202 #endif
Chris@1266 203 m_centreFrame = centreFrame;
Chris@642 204 #ifdef DEBUG_OVERVIEW
Chris@682 205 cerr << " to " << getStartFrame() << endl;
Chris@642 206 #endif
Chris@1266 207 emit centreFrameChanged(m_centreFrame, false, PlaybackIgnore);
Chris@173 208 }
Chris@173 209
Chris@173 210 View::paintEvent(e);
Chris@173 211
Chris@173 212 QPainter paint;
Chris@173 213 paint.begin(this);
Chris@871 214 paint.setClipRegion(e->region());
Chris@871 215 paint.setRenderHints(QPainter::Antialiasing);
Chris@871 216
Chris@871 217 // We paint a rounded rect for each distinct set of view extents,
Chris@871 218 // and we colour in the inside and outside of the rect that
Chris@871 219 // corresponds to the current view. (One small caveat -- we don't
Chris@871 220 // know which rect that is yet. We'll have to figure it out
Chris@871 221 // somehow...)
Chris@173 222
Chris@871 223 std::set<std::pair<int, int> > extents;
Chris@871 224 std::vector<QRect> rects;
Chris@871 225 QRect primary;
Chris@173 226
Chris@173 227 int y = 0;
Chris@173 228
Chris@211 229 for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
Chris@1266 230 if (!*i) continue;
Chris@173 231
Chris@1266 232 View *w = (View *)*i;
Chris@173 233
Chris@1266 234 sv_frame_t f0 = w->getFrameForX(0);
Chris@1266 235 sv_frame_t f1 = w->getFrameForX(w->width());
Chris@173 236
Chris@339 237 if (f0 >= 0) {
Chris@908 238 sv_frame_t rf0 = w->alignToReference(f0);
Chris@339 239 f0 = alignFromReference(rf0);
Chris@339 240 }
Chris@339 241 if (f1 >= 0) {
Chris@908 242 sv_frame_t rf1 = w->alignToReference(f1);
Chris@339 243 f1 = alignFromReference(rf1);
Chris@339 244 }
Chris@339 245
Chris@1266 246 int x0 = getXForFrame(f0);
Chris@1266 247 int x1 = getXForFrame(f1);
Chris@173 248
Chris@1266 249 if (x1 <= x0) x1 = x0 + 1;
Chris@871 250
Chris@871 251 std::pair<int, int> extent(x0, x1);
Chris@871 252
Chris@871 253 if (extents.find(extent) == extents.end()) {
Chris@871 254
Chris@1266 255 y += height() / 10 + 1;
Chris@871 256 extents.insert(extent);
Chris@173 257
Chris@871 258 QRect vr(x0, y, x1 - x0, height() - 2 * y);
Chris@871 259 rects.push_back(vr);
Chris@871 260 primary = vr; //!!! for now
Chris@871 261 }
Chris@871 262 }
Chris@871 263
Chris@871 264 QPainterPath without;
matthiasm@935 265 without.addRoundedRect(primary, 4, 4);
Chris@871 266 without.addRect(rect());
Chris@871 267 paint.setPen(Qt::NoPen);
Chris@871 268 paint.setBrush(getFillWithout());
Chris@871 269 paint.drawPath(without);
Chris@871 270
Chris@871 271 paint.setBrush(getFillWithin());
matthiasm@935 272 paint.drawRoundedRect(primary, 4, 4);
Chris@871 273
Chris@871 274 foreach (QRect vr, rects) {
Chris@871 275 paint.setBrush(Qt::NoBrush);
Chris@965 276 paint.setPen(QPen(m_boxColour, 2));
matthiasm@935 277 paint.drawRoundedRect(vr, 4, 4);
Chris@173 278 }
Chris@173 279
Chris@173 280 paint.end();
Chris@173 281 }
Chris@173 282
Chris@173 283 void
Chris@173 284 Overview::mousePressEvent(QMouseEvent *e)
Chris@173 285 {
Chris@173 286 m_clickPos = e->pos();
Chris@908 287 sv_frame_t clickFrame = getFrameForX(m_clickPos.x());
Chris@339 288 if (clickFrame > 0) m_dragCentreFrame = clickFrame;
Chris@339 289 else m_dragCentreFrame = 0;
Chris@339 290 m_clickedInRange = true;
Chris@339 291
Chris@211 292 for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
Chris@1266 293 if (*i && (*i)->getAligningModel() == getAligningModel()) {
Chris@339 294 m_dragCentreFrame = (*i)->getCentreFrame();
Chris@339 295 break;
Chris@339 296 }
Chris@173 297 }
Chris@173 298 }
Chris@173 299
Chris@173 300 void
Chris@173 301 Overview::mouseReleaseEvent(QMouseEvent *e)
Chris@173 302 {
Chris@173 303 if (m_clickedInRange) {
Chris@1266 304 mouseMoveEvent(e);
Chris@173 305 }
Chris@173 306 m_clickedInRange = false;
Chris@173 307 }
Chris@173 308
Chris@173 309 void
Chris@173 310 Overview::mouseMoveEvent(QMouseEvent *e)
Chris@173 311 {
Chris@173 312 if (!m_clickedInRange) return;
Chris@173 313
Chris@806 314 int xoff = int(e->x()) - int(m_clickPos.x());
Chris@1326 315 sv_frame_t frameOff = sv_frame_t(round(m_zoomLevel.pixelsToFrames(xoff)));
Chris@173 316
Chris@908 317 sv_frame_t newCentreFrame = m_dragCentreFrame;
Chris@173 318 if (frameOff > 0) {
Chris@1266 319 newCentreFrame += frameOff;
Chris@908 320 } else if (newCentreFrame >= -frameOff) {
Chris@1266 321 newCentreFrame += frameOff;
Chris@173 322 } else {
Chris@1266 323 newCentreFrame = 0;
Chris@173 324 }
Chris@173 325
Chris@173 326 if (newCentreFrame >= getModelsEndFrame()) {
Chris@1266 327 newCentreFrame = getModelsEndFrame();
Chris@1266 328 if (newCentreFrame > 0) --newCentreFrame;
Chris@173 329 }
Chris@173 330
Chris@1328 331 sv_frame_t pixel = sv_frame_t(round(m_zoomLevel.pixelsToFrames(1)));
Chris@1328 332
Chris@173 333 if (std::max(m_centreFrame, newCentreFrame) -
Chris@1326 334 std::min(m_centreFrame, newCentreFrame) >
Chris@1328 335 pixel) {
Chris@908 336 sv_frame_t rf = alignToReference(newCentreFrame);
Chris@643 337 #ifdef DEBUG_OVERVIEW
Chris@682 338 cerr << "Overview::mouseMoveEvent: x " << e->x() << " and click x " << m_clickPos.x() << " -> frame " << newCentreFrame << " -> rf " << rf << endl;
Chris@643 339 #endif
Chris@817 340 if (m_followPlay == PlaybackScrollContinuous ||
Chris@817 341 m_followPlay == PlaybackScrollPageWithCentre) {
Chris@817 342 emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
Chris@817 343 } else {
Chris@817 344 emit centreFrameChanged(rf, true, PlaybackIgnore);
Chris@817 345 }
Chris@173 346 }
Chris@173 347 }
Chris@173 348
Chris@189 349 void
Chris@189 350 Overview::mouseDoubleClickEvent(QMouseEvent *e)
Chris@189 351 {
Chris@908 352 sv_frame_t frame = getFrameForX(e->x());
Chris@908 353 sv_frame_t rf = 0;
Chris@339 354 if (frame > 0) rf = alignToReference(frame);
Chris@643 355 #ifdef DEBUG_OVERVIEW
Chris@682 356 cerr << "Overview::mouseDoubleClickEvent: frame " << frame << " -> rf " << rf << endl;
Chris@643 357 #endif
Chris@643 358 m_clickedInRange = false; // we're not starting a drag with the second click
Chris@339 359 emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
Chris@189 360 }
Chris@173 361
Chris@189 362 void
Chris@189 363 Overview::enterEvent(QEvent *)
Chris@189 364 {
Chris@189 365 emit contextHelpChanged(tr("Click and drag to navigate; double-click to jump"));
Chris@189 366 }
Chris@189 367
Chris@189 368 void
Chris@189 369 Overview::leaveEvent(QEvent *)
Chris@189 370 {
Chris@189 371 emit contextHelpChanged("");
Chris@189 372 }
Chris@189 373
Chris@189 374