comparison view/View.cpp @ 127:89c625dda204

* Reorganising code base. This revision will not compile.
author Chris Cannam
date Mon, 31 Jul 2006 11:44:37 +0000
parents
children 33929e0c3c6b
comparison
equal deleted inserted replaced
126:0e95c127bb53 127:89c625dda204
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 Chris Cannam.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "base/View.h"
17 #include "base/Layer.h"
18 #include "base/Model.h"
19 #include "base/ZoomConstraint.h"
20 #include "base/Profiler.h"
21
22 #include "layer/TimeRulerLayer.h" //!!! damn, shouldn't be including that here
23 #include "model/PowerOfSqrtTwoZoomConstraint.h" //!!! likewise
24
25 #include <QPainter>
26 #include <QPaintEvent>
27 #include <QRect>
28 #include <QApplication>
29
30 #include <iostream>
31 #include <cassert>
32 #include <math.h>
33
34 //#define DEBUG_VIEW_WIDGET_PAINT 1
35
36 using std::cerr;
37 using std::endl;
38
39 View::View(QWidget *w, bool showProgress) :
40 QFrame(w),
41 m_centreFrame(0),
42 m_zoomLevel(1024),
43 m_followPan(true),
44 m_followZoom(true),
45 m_followPlay(PlaybackScrollPage),
46 m_lightBackground(true),
47 m_showProgress(showProgress),
48 m_cache(0),
49 m_cacheCentreFrame(0),
50 m_cacheZoomLevel(1024),
51 m_selectionCached(false),
52 m_deleting(false),
53 m_haveSelectedLayer(false),
54 m_manager(0),
55 m_propertyContainer(new ViewPropertyContainer(this))
56 {
57 // QWidget::setAttribute(Qt::WA_PaintOnScreen);
58 }
59
60 View::~View()
61 {
62 // std::cerr << "View::~View(" << this << ")" << std::endl;
63
64 m_deleting = true;
65 delete m_propertyContainer;
66 }
67
68 PropertyContainer::PropertyList
69 View::getProperties() const
70 {
71 PropertyContainer::PropertyList list;
72 list.push_back("Global Scroll");
73 list.push_back("Global Zoom");
74 list.push_back("Follow Playback");
75 return list;
76 }
77
78 QString
79 View::getPropertyLabel(const PropertyName &pn) const
80 {
81 if (pn == "Global Scroll") return tr("Global Scroll");
82 if (pn == "Global Zoom") return tr("Global Zoom");
83 if (pn == "Follow Playback") return tr("Follow Playback");
84 return "";
85 }
86
87 PropertyContainer::PropertyType
88 View::getPropertyType(const PropertyContainer::PropertyName &name) const
89 {
90 if (name == "Global Scroll") return PropertyContainer::ToggleProperty;
91 if (name == "Global Zoom") return PropertyContainer::ToggleProperty;
92 if (name == "Follow Playback") return PropertyContainer::ValueProperty;
93 return PropertyContainer::InvalidProperty;
94 }
95
96 int
97 View::getPropertyRangeAndValue(const PropertyContainer::PropertyName &name,
98 int *min, int *max) const
99 {
100 if (name == "Global Scroll") return m_followPan;
101 if (name == "Global Zoom") return m_followZoom;
102 if (name == "Follow Playback") {
103 if (min) *min = 0;
104 if (max) *max = 2;
105 return int(m_followPlay);
106 }
107 if (min) *min = 0;
108 if (max) *max = 0;
109 return 0;
110 }
111
112 QString
113 View::getPropertyValueLabel(const PropertyContainer::PropertyName &name,
114 int value) const
115 {
116 if (name == "Follow Playback") {
117 switch (value) {
118 default:
119 case 0: return tr("Scroll");
120 case 1: return tr("Page");
121 case 2: return tr("Off");
122 }
123 }
124 return tr("<unknown>");
125 }
126
127 void
128 View::setProperty(const PropertyContainer::PropertyName &name, int value)
129 {
130 if (name == "Global Scroll") {
131 setFollowGlobalPan(value != 0);
132 } else if (name == "Global Zoom") {
133 setFollowGlobalZoom(value != 0);
134 } else if (name == "Follow Playback") {
135 switch (value) {
136 default:
137 case 0: setPlaybackFollow(PlaybackScrollContinuous); break;
138 case 1: setPlaybackFollow(PlaybackScrollPage); break;
139 case 2: setPlaybackFollow(PlaybackIgnore); break;
140 }
141 }
142 }
143
144 size_t
145 View::getPropertyContainerCount() const
146 {
147 return m_layers.size() + 1; // the 1 is for me
148 }
149
150 const PropertyContainer *
151 View::getPropertyContainer(size_t i) const
152 {
153 return (const PropertyContainer *)(((View *)this)->
154 getPropertyContainer(i));
155 }
156
157 PropertyContainer *
158 View::getPropertyContainer(size_t i)
159 {
160 if (i == 0) return m_propertyContainer;
161 return m_layers[i-1];
162 }
163
164 bool
165 View::getValueExtents(QString unit, float &min, float &max, bool &log) const
166 {
167 bool have = false;
168
169 for (LayerList::const_iterator i = m_layers.begin();
170 i != m_layers.end(); ++i) {
171
172 QString layerUnit;
173 float layerMin = 0.0, layerMax = 0.0;
174 float displayMin = 0.0, displayMax = 0.0;
175 bool layerLog = false;
176
177 if ((*i)->getValueExtents(layerMin, layerMax, layerLog, layerUnit) &&
178 layerUnit.toLower() == unit.toLower()) {
179
180 if ((*i)->getDisplayExtents(displayMin, displayMax)) {
181
182 min = displayMin;
183 max = displayMax;
184 log = layerLog;
185 have = true;
186 break;
187
188 } else {
189
190 if (!have || layerMin < min) min = layerMin;
191 if (!have || layerMax > max) max = layerMax;
192 if (layerLog) log = true;
193 have = true;
194 }
195 }
196 }
197
198 return have;
199 }
200
201 int
202 View::getTextLabelHeight(const Layer *layer, QPainter &paint) const
203 {
204 std::map<int, Layer *> sortedLayers;
205
206 for (LayerList::const_iterator i = m_layers.begin();
207 i != m_layers.end(); ++i) {
208 if ((*i)->needsTextLabelHeight()) {
209 sortedLayers[getObjectExportId(*i)] = *i;
210 }
211 }
212
213 int y = 15 + paint.fontMetrics().ascent();
214
215 for (std::map<int, Layer *>::const_iterator i = sortedLayers.begin();
216 i != sortedLayers.end(); ++i) {
217 if (i->second == layer) return y;
218 y += paint.fontMetrics().height();
219 }
220
221 return y;
222 }
223
224 void
225 View::propertyContainerSelected(View *client, PropertyContainer *pc)
226 {
227 if (client != this) return;
228
229 if (pc == m_propertyContainer) {
230 if (m_haveSelectedLayer) {
231 m_haveSelectedLayer = false;
232 update();
233 }
234 return;
235 }
236
237 delete m_cache;
238 m_cache = 0;
239
240 Layer *selectedLayer = 0;
241
242 for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
243 if (*i == pc) {
244 selectedLayer = *i;
245 m_layers.erase(i);
246 break;
247 }
248 }
249
250 if (selectedLayer) {
251 m_haveSelectedLayer = true;
252 m_layers.push_back(selectedLayer);
253 update();
254 } else {
255 m_haveSelectedLayer = false;
256 }
257 }
258
259 void
260 View::toolModeChanged()
261 {
262 // std::cerr << "View::toolModeChanged(" << m_manager->getToolMode() << ")" << std::endl;
263 }
264
265 long
266 View::getStartFrame() const
267 {
268 size_t w2 = (width() / 2) * m_zoomLevel;
269 size_t frame = m_centreFrame;
270 if (frame >= w2) {
271 frame -= w2;
272 return (frame / m_zoomLevel * m_zoomLevel);
273 } else {
274 frame = w2 - frame;
275 frame = frame / m_zoomLevel * m_zoomLevel;
276 return -(long)frame - m_zoomLevel;
277 }
278 }
279
280 size_t
281 View::getEndFrame() const
282 {
283 return getFrameForX(width()) - 1;
284 }
285
286 void
287 View::setStartFrame(long f)
288 {
289 setCentreFrame(f + m_zoomLevel * (width() / 2));
290 }
291
292 bool
293 View::setCentreFrame(size_t f, bool e)
294 {
295 bool changeVisible = false;
296
297 if (m_centreFrame != f) {
298
299 int formerPixel = m_centreFrame / m_zoomLevel;
300
301 m_centreFrame = f;
302
303 int newPixel = m_centreFrame / m_zoomLevel;
304
305 if (newPixel != formerPixel) {
306
307 #ifdef DEBUG_VIEW_WIDGET_PAINT
308 std::cout << "View(" << this << ")::setCentreFrame: newPixel " << newPixel << ", formerPixel " << formerPixel << std::endl;
309 #endif
310 update();
311
312 changeVisible = true;
313 }
314
315 if (e) emit centreFrameChanged(this, f, m_followPan);
316 }
317
318 return changeVisible;
319 }
320
321 int
322 View::getXForFrame(long frame) const
323 {
324 return (frame - getStartFrame()) / m_zoomLevel;
325 }
326
327 long
328 View::getFrameForX(int x) const
329 {
330 return (long(x) * long(m_zoomLevel)) + getStartFrame();
331 }
332
333 float
334 View::getYForFrequency(float frequency,
335 float minf,
336 float maxf,
337 bool logarithmic) const
338 {
339 int h = height();
340
341 if (logarithmic) {
342
343 static float lastminf = 0.0, lastmaxf = 0.0;
344 static float logminf = 0.0, logmaxf = 0.0;
345
346 if (lastminf != minf) {
347 lastminf = (minf == 0.0 ? 1.0 : minf);
348 logminf = log10f(minf);
349 }
350 if (lastmaxf != maxf) {
351 lastmaxf = (maxf < lastminf ? lastminf : maxf);
352 logmaxf = log10f(maxf);
353 }
354
355 if (logminf == logmaxf) return 0;
356 return h - (h * (log10f(frequency) - logminf)) / (logmaxf - logminf);
357
358 } else {
359
360 if (minf == maxf) return 0;
361 return h - (h * (frequency - minf)) / (maxf - minf);
362 }
363 }
364
365 float
366 View::getFrequencyForY(int y,
367 float minf,
368 float maxf,
369 bool logarithmic) const
370 {
371 int h = height();
372
373 if (logarithmic) {
374
375 static float lastminf = 0.0, lastmaxf = 0.0;
376 static float logminf = 0.0, logmaxf = 0.0;
377
378 if (lastminf != minf) {
379 lastminf = (minf == 0.0 ? 1.0 : minf);
380 logminf = log10f(minf);
381 }
382 if (lastmaxf != maxf) {
383 lastmaxf = (maxf < lastminf ? lastminf : maxf);
384 logmaxf = log10f(maxf);
385 }
386
387 if (logminf == logmaxf) return 0;
388 return pow(10.f, logminf + ((logmaxf - logminf) * (h - y)) / h);
389
390 } else {
391
392 if (minf == maxf) return 0;
393 return minf + ((h - y) * (maxf - minf)) / h;
394 }
395 }
396
397 int
398 View::getZoomLevel() const
399 {
400 #ifdef DEBUG_VIEW_WIDGET_PAINT
401 std::cout << "zoom level: " << m_zoomLevel << std::endl;
402 #endif
403 return m_zoomLevel;
404 }
405
406 void
407 View::setZoomLevel(size_t z)
408 {
409 if (m_zoomLevel != int(z)) {
410 m_zoomLevel = z;
411 emit zoomLevelChanged(this, z, m_followZoom);
412 update();
413 }
414 }
415
416 View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
417 QProgressBar(parent)
418 {
419 QFont f(font());
420 f.setPointSize(f.pointSize() * 8 / 10);
421 setFont(f);
422 }
423
424 void
425 View::addLayer(Layer *layer)
426 {
427 delete m_cache;
428 m_cache = 0;
429
430 m_layers.push_back(layer);
431
432 m_progressBars[layer] = new LayerProgressBar(this);
433 m_progressBars[layer]->setMinimum(0);
434 m_progressBars[layer]->setMaximum(100);
435 m_progressBars[layer]->setMinimumWidth(80);
436 m_progressBars[layer]->hide();
437
438 connect(layer, SIGNAL(layerParametersChanged()),
439 this, SLOT(layerParametersChanged()));
440 connect(layer, SIGNAL(layerNameChanged()),
441 this, SLOT(layerNameChanged()));
442 connect(layer, SIGNAL(modelChanged()),
443 this, SLOT(modelChanged()));
444 connect(layer, SIGNAL(modelCompletionChanged()),
445 this, SLOT(modelCompletionChanged()));
446 connect(layer, SIGNAL(modelChanged(size_t, size_t)),
447 this, SLOT(modelChanged(size_t, size_t)));
448 connect(layer, SIGNAL(modelReplaced()),
449 this, SLOT(modelReplaced()));
450
451 update();
452
453 emit propertyContainerAdded(layer);
454 }
455
456 void
457 View::removeLayer(Layer *layer)
458 {
459 if (m_deleting) {
460 return;
461 }
462
463 delete m_cache;
464 m_cache = 0;
465
466 for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
467 if (*i == layer) {
468 m_layers.erase(i);
469 if (m_progressBars.find(layer) != m_progressBars.end()) {
470 delete m_progressBars[layer];
471 m_progressBars.erase(layer);
472 }
473 break;
474 }
475 }
476
477 update();
478
479 emit propertyContainerRemoved(layer);
480 }
481
482 Layer *
483 View::getSelectedLayer()
484 {
485 if (m_haveSelectedLayer && !m_layers.empty()) {
486 return getLayer(getLayerCount() - 1);
487 } else {
488 return 0;
489 }
490 }
491
492 const Layer *
493 View::getSelectedLayer() const
494 {
495 return const_cast<const Layer *>(const_cast<View *>(this)->getSelectedLayer());
496 }
497
498 void
499 View::setViewManager(ViewManager *manager)
500 {
501 if (m_manager) {
502 m_manager->disconnect(this, SLOT(viewManagerCentreFrameChanged(void *, unsigned long, bool)));
503 m_manager->disconnect(this, SLOT(viewManagerZoomLevelChanged(void *, unsigned long, bool)));
504 disconnect(m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)));
505 disconnect(m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)));
506 disconnect(m_manager, SIGNAL(toolModeChanged()));
507 disconnect(m_manager, SIGNAL(selectionChanged()));
508 disconnect(m_manager, SIGNAL(inProgressSelectionChanged()));
509 }
510
511 m_manager = manager;
512 if (m_followPan) setCentreFrame(m_manager->getGlobalCentreFrame(), false);
513 if (m_followZoom) setZoomLevel(m_manager->getGlobalZoom());
514
515 connect(m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
516 this, SLOT(viewManagerCentreFrameChanged(void *, unsigned long, bool)));
517 connect(m_manager, SIGNAL(playbackFrameChanged(unsigned long)),
518 this, SLOT(viewManagerPlaybackFrameChanged(unsigned long)));
519 connect(m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
520 this, SLOT(viewManagerZoomLevelChanged(void *, unsigned long, bool)));
521 connect(m_manager, SIGNAL(toolModeChanged()),
522 this, SLOT(toolModeChanged()));
523 connect(m_manager, SIGNAL(selectionChanged()),
524 this, SLOT(selectionChanged()));
525 connect(m_manager, SIGNAL(inProgressSelectionChanged()),
526 this, SLOT(selectionChanged()));
527 connect(m_manager, SIGNAL(overlayModeChanged()),
528 this, SLOT(update()));
529
530 connect(this, SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
531 m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)));
532 connect(this, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
533 m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)));
534
535 toolModeChanged();
536 }
537
538 void
539 View::setFollowGlobalPan(bool f)
540 {
541 m_followPan = f;
542 emit propertyContainerPropertyChanged(m_propertyContainer);
543 }
544
545 void
546 View::setFollowGlobalZoom(bool f)
547 {
548 m_followZoom = f;
549 emit propertyContainerPropertyChanged(m_propertyContainer);
550 }
551
552 void
553 View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style)
554 {
555 if (style == OutlinedText) {
556
557 QColor origPenColour = paint.pen().color();
558 QColor penColour = origPenColour;
559 QColor surroundColour = Qt::white; //palette().background().color();
560
561 if (!hasLightBackground()) {
562 int h, s, v;
563 penColour.getHsv(&h, &s, &v);
564 penColour = QColor::fromHsv(h, s, 255 - v);
565 surroundColour = Qt::black;
566 }
567
568 paint.setPen(surroundColour);
569
570 for (int dx = -1; dx <= 1; ++dx) {
571 for (int dy = -1; dy <= 1; ++dy) {
572 if (!(dx || dy)) continue;
573 paint.drawText(x + dx, y + dy, text);
574 }
575 }
576
577 paint.setPen(penColour);
578
579 paint.drawText(x, y, text);
580
581 paint.setPen(origPenColour);
582
583 } else {
584
585 std::cerr << "ERROR: View::drawVisibleText: Boxed style not yet implemented!" << std::endl;
586 }
587 }
588
589 void
590 View::setPlaybackFollow(PlaybackFollowMode m)
591 {
592 m_followPlay = m;
593 emit propertyContainerPropertyChanged(m_propertyContainer);
594 }
595
596 void
597 View::modelChanged()
598 {
599 QObject *obj = sender();
600
601 #ifdef DEBUG_VIEW_WIDGET_PAINT
602 std::cerr << "View(" << this << ")::modelChanged()" << std::endl;
603 #endif
604
605 // If the model that has changed is not used by any of the cached
606 // layers, we won't need to recreate the cache
607
608 bool recreate = false;
609
610 bool discard;
611 LayerList scrollables = getScrollableBackLayers(false, discard);
612 for (LayerList::const_iterator i = scrollables.begin();
613 i != scrollables.end(); ++i) {
614 if (*i == obj || (*i)->getModel() == obj) {
615 recreate = true;
616 break;
617 }
618 }
619
620 if (recreate) {
621 delete m_cache;
622 m_cache = 0;
623 }
624
625 checkProgress(obj);
626
627 update();
628 }
629
630 void
631 View::modelChanged(size_t startFrame, size_t endFrame)
632 {
633 QObject *obj = sender();
634
635 long myStartFrame = getStartFrame();
636 size_t myEndFrame = getEndFrame();
637
638 #ifdef DEBUG_VIEW_WIDGET_PAINT
639 std::cerr << "View(" << this << ")::modelChanged(" << startFrame << "," << endFrame << ") [me " << myStartFrame << "," << myEndFrame << "]" << std::endl;
640 #endif
641
642 if (myStartFrame > 0 && endFrame < size_t(myStartFrame)) {
643 checkProgress(obj);
644 return;
645 }
646 if (startFrame > myEndFrame) {
647 checkProgress(obj);
648 return;
649 }
650
651 // If the model that has changed is not used by any of the cached
652 // layers, we won't need to recreate the cache
653
654 bool recreate = false;
655
656 bool discard;
657 LayerList scrollables = getScrollableBackLayers(false, discard);
658 for (LayerList::const_iterator i = scrollables.begin();
659 i != scrollables.end(); ++i) {
660 if (*i == obj || (*i)->getModel() == obj) {
661 recreate = true;
662 break;
663 }
664 }
665
666 if (recreate) {
667 delete m_cache;
668 m_cache = 0;
669 }
670
671 if (long(startFrame) < myStartFrame) startFrame = myStartFrame;
672 if (endFrame > myEndFrame) endFrame = myEndFrame;
673
674 int x0 = getXForFrame(startFrame);
675 int x1 = getXForFrame(endFrame + 1);
676 if (x1 < x0) x1 = x0;
677
678 checkProgress(obj);
679
680 update();
681 //!!! update(x0, 0, x1 - x0 + 1, height());
682 }
683
684 void
685 View::modelCompletionChanged()
686 {
687 QObject *obj = sender();
688 checkProgress(obj);
689 }
690
691 void
692 View::modelReplaced()
693 {
694 #ifdef DEBUG_VIEW_WIDGET_PAINT
695 std::cerr << "View(" << this << ")::modelReplaced()" << std::endl;
696 #endif
697 delete m_cache;
698 m_cache = 0;
699
700 update();
701 }
702
703 void
704 View::layerParametersChanged()
705 {
706 Layer *layer = dynamic_cast<Layer *>(sender());
707
708 #ifdef DEBUG_VIEW_WIDGET_PAINT
709 std::cerr << "View::layerParametersChanged()" << std::endl;
710 #endif
711
712 delete m_cache;
713 m_cache = 0;
714 update();
715
716 if (layer) {
717 emit propertyContainerPropertyChanged(layer);
718 }
719 }
720
721 void
722 View::layerNameChanged()
723 {
724 Layer *layer = dynamic_cast<Layer *>(sender());
725 if (layer) emit propertyContainerNameChanged(layer);
726 }
727
728 void
729 View::viewManagerCentreFrameChanged(void *p, unsigned long f, bool locked)
730 {
731 if (m_followPan && p != this && locked) {
732 if (m_manager && (sender() == m_manager)) {
733 #ifdef DEBUG_VIEW_WIDGET_PAINT
734 std::cerr << this << ": manager frame changed " << f << " from " << p << std::endl;
735 #endif
736 setCentreFrame(f);
737 if (p == this) repaint();
738 }
739 }
740 }
741
742 void
743 View::viewManagerPlaybackFrameChanged(unsigned long f)
744 {
745 if (m_manager) {
746 if (sender() != m_manager) return;
747 }
748
749 if (m_playPointerFrame == f) return;
750 bool visible = (getXForFrame(m_playPointerFrame) != getXForFrame(f));
751 size_t oldPlayPointerFrame = m_playPointerFrame;
752 m_playPointerFrame = f;
753 if (!visible) return;
754
755 switch (m_followPlay) {
756
757 case PlaybackScrollContinuous:
758 if (QApplication::mouseButtons() == Qt::NoButton) {
759 setCentreFrame(f, false);
760 }
761 break;
762
763 case PlaybackScrollPage:
764 {
765 int xold = getXForFrame(oldPlayPointerFrame);
766 update(xold - 1, 0, 3, height());
767
768 long w = getEndFrame() - getStartFrame();
769 w -= w/5;
770 long sf = (f / w) * w - w/8;
771
772 if (m_manager &&
773 m_manager->isPlaying() &&
774 m_manager->getPlaySelectionMode()) {
775 MultiSelection::SelectionList selections = m_manager->getSelections();
776 if (!selections.empty()) {
777 size_t selectionStart = selections.begin()->getStartFrame();
778 if (sf < long(selectionStart) - w / 10) {
779 sf = long(selectionStart) - w / 10;
780 }
781 }
782 }
783
784 #ifdef DEBUG_VIEW_WIDGET_PAINT
785 std::cerr << "PlaybackScrollPage: f = " << f << ", sf = " << sf << ", start frame "
786 << getStartFrame() << std::endl;
787 #endif
788
789 // We don't consider scrolling unless the pointer is outside
790 // the clearly visible range already
791
792 int xnew = getXForFrame(m_playPointerFrame);
793
794 #ifdef DEBUG_VIEW_WIDGET_PAINT
795 std::cerr << "xnew = " << xnew << ", width = " << width() << std::endl;
796 #endif
797
798 if (xnew < width()/8 || xnew > (width()*7)/8) {
799 if (QApplication::mouseButtons() == Qt::NoButton) {
800 long offset = getFrameForX(width()/2) - getStartFrame();
801 long newCentre = sf + offset;
802 bool changed = setCentreFrame(newCentre, false);
803 if (changed) {
804 xold = getXForFrame(oldPlayPointerFrame);
805 update(xold - 1, 0, 3, height());
806 }
807 }
808 }
809
810 update(xnew - 1, 0, 3, height());
811
812 break;
813 }
814
815 case PlaybackIgnore:
816 if (long(f) >= getStartFrame() && f < getEndFrame()) {
817 update();
818 }
819 break;
820 }
821 }
822
823 void
824 View::viewManagerZoomLevelChanged(void *p, unsigned long z, bool locked)
825 {
826 if (m_followZoom && p != this && locked) {
827 if (m_manager && (sender() == m_manager)) {
828 setZoomLevel(z);
829 if (p == this) repaint();
830 }
831 }
832 }
833
834 void
835 View::selectionChanged()
836 {
837 if (m_selectionCached) {
838 delete m_cache;
839 m_cache = 0;
840 m_selectionCached = false;
841 }
842 update();
843 }
844
845 size_t
846 View::getModelsStartFrame() const
847 {
848 bool first = true;
849 size_t startFrame = 0;
850
851 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
852
853 if ((*i)->getModel() && (*i)->getModel()->isOK()) {
854
855 size_t thisStartFrame = (*i)->getModel()->getStartFrame();
856
857 if (first || thisStartFrame < startFrame) {
858 startFrame = thisStartFrame;
859 }
860 first = false;
861 }
862 }
863 return startFrame;
864 }
865
866 size_t
867 View::getModelsEndFrame() const
868 {
869 bool first = true;
870 size_t endFrame = 0;
871
872 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
873
874 if ((*i)->getModel() && (*i)->getModel()->isOK()) {
875
876 size_t thisEndFrame = (*i)->getModel()->getEndFrame();
877
878 if (first || thisEndFrame > endFrame) {
879 endFrame = thisEndFrame;
880 }
881 first = false;
882 }
883 }
884
885 if (first) return getModelsStartFrame();
886 return endFrame;
887 }
888
889 int
890 View::getModelsSampleRate() const
891 {
892 //!!! Just go for the first, for now. If we were supporting
893 // multiple samplerates, we'd probably want to do frame/time
894 // conversion in the model
895
896 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
897 if ((*i)->getModel() && (*i)->getModel()->isOK()) {
898 return (*i)->getModel()->getSampleRate();
899 }
900 }
901 return 0;
902 }
903
904 bool
905 View::areLayersScrollable() const
906 {
907 // True iff all views are scrollable
908 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
909 if (!(*i)->isLayerScrollable(this)) return false;
910 }
911 return true;
912 }
913
914 View::LayerList
915 View::getScrollableBackLayers(bool testChanged, bool &changed) const
916 {
917 changed = false;
918
919 // We want a list of all the scrollable layers that are behind the
920 // backmost non-scrollable layer.
921
922 LayerList scrollables;
923 bool metUnscrollable = false;
924
925 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
926 if ((*i)->isLayerDormant(this)) continue;
927 if ((*i)->isLayerOpaque()) {
928 // You can't see anything behind an opaque layer!
929 scrollables.clear();
930 if (metUnscrollable) break;
931 }
932 if (!metUnscrollable && (*i)->isLayerScrollable(this)) {
933 scrollables.push_back(*i);
934 } else {
935 metUnscrollable = true;
936 }
937 }
938
939 if (testChanged && scrollables != m_lastScrollableBackLayers) {
940 m_lastScrollableBackLayers = scrollables;
941 changed = true;
942 }
943 return scrollables;
944 }
945
946 View::LayerList
947 View::getNonScrollableFrontLayers(bool testChanged, bool &changed) const
948 {
949 changed = false;
950 LayerList nonScrollables;
951
952 // Everything in front of the first non-scrollable from the back
953 // should also be considered non-scrollable
954
955 bool started = false;
956
957 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
958 if ((*i)->isLayerDormant(this)) continue;
959 if (!started && (*i)->isLayerScrollable(this)) {
960 continue;
961 }
962 started = true;
963 if ((*i)->isLayerOpaque()) {
964 // You can't see anything behind an opaque layer!
965 nonScrollables.clear();
966 }
967 nonScrollables.push_back(*i);
968 }
969
970 if (testChanged && nonScrollables != m_lastNonScrollableBackLayers) {
971 m_lastNonScrollableBackLayers = nonScrollables;
972 changed = true;
973 }
974
975 return nonScrollables;
976 }
977
978 size_t
979 View::getZoomConstraintBlockSize(size_t blockSize,
980 ZoomConstraint::RoundingDirection dir)
981 const
982 {
983 size_t candidate = blockSize;
984 bool haveCandidate = false;
985
986 PowerOfSqrtTwoZoomConstraint defaultZoomConstraint;
987
988 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
989
990 const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint();
991 if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint;
992
993 size_t thisBlockSize =
994 zoomConstraint->getNearestBlockSize(blockSize, dir);
995
996 // Go for the block size that's furthest from the one
997 // passed in. Most of the time, that's what we want.
998 if (!haveCandidate ||
999 (thisBlockSize > blockSize && thisBlockSize > candidate) ||
1000 (thisBlockSize < blockSize && thisBlockSize < candidate)) {
1001 candidate = thisBlockSize;
1002 haveCandidate = true;
1003 }
1004 }
1005
1006 return candidate;
1007 }
1008
1009 void
1010 View::zoom(bool in)
1011 {
1012 int newZoomLevel = m_zoomLevel;
1013
1014 if (in) {
1015 newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1,
1016 ZoomConstraint::RoundDown);
1017 } else {
1018 newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
1019 ZoomConstraint::RoundUp);
1020 }
1021
1022 if (newZoomLevel != m_zoomLevel) {
1023 setZoomLevel(newZoomLevel);
1024 }
1025 }
1026
1027 void
1028 View::scroll(bool right, bool lots)
1029 {
1030 long delta;
1031 if (lots) {
1032 delta = (getEndFrame() - getStartFrame()) / 2;
1033 } else {
1034 delta = (getEndFrame() - getStartFrame()) / 20;
1035 }
1036 if (right) delta = -delta;
1037
1038 if (int(m_centreFrame) < delta) {
1039 setCentreFrame(0);
1040 } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
1041 setCentreFrame(getModelsEndFrame());
1042 } else {
1043 setCentreFrame(m_centreFrame - delta);
1044 }
1045 }
1046
1047 void
1048 View::checkProgress(void *object)
1049 {
1050 if (!m_showProgress) return;
1051
1052 int ph = height();
1053
1054 for (ProgressMap::const_iterator i = m_progressBars.begin();
1055 i != m_progressBars.end(); ++i) {
1056
1057 if (i->first == object) {
1058
1059 int completion = i->first->getCompletion(this);
1060
1061 if (completion >= 100) {
1062
1063 i->second->hide();
1064
1065 } else {
1066
1067 i->second->setText(i->first->getPropertyContainerName());
1068 i->second->setValue(completion);
1069 i->second->move(0, ph - i->second->height());
1070
1071 i->second->show();
1072 i->second->update();
1073
1074 ph -= i->second->height();
1075 }
1076 } else {
1077 if (i->second->isVisible()) {
1078 ph -= i->second->height();
1079 }
1080 }
1081 }
1082 }
1083
1084 void
1085 View::paintEvent(QPaintEvent *e)
1086 {
1087 // Profiler prof("View::paintEvent", false);
1088 // std::cerr << "View::paintEvent" << std::endl;
1089
1090 if (m_layers.empty()) {
1091 QFrame::paintEvent(e);
1092 return;
1093 }
1094
1095 // ensure our constraints are met
1096 m_zoomLevel = getZoomConstraintBlockSize(m_zoomLevel,
1097 ZoomConstraint::RoundUp);
1098
1099 QPainter paint;
1100 bool repaintCache = false;
1101 bool paintedCacheRect = false;
1102
1103 QRect cacheRect(rect());
1104
1105 if (e) {
1106 cacheRect &= e->rect();
1107 #ifdef DEBUG_VIEW_WIDGET_PAINT
1108 std::cerr << "paint rect " << cacheRect.width() << "x" << cacheRect.height()
1109 << ", my rect " << width() << "x" << height() << std::endl;
1110 #endif
1111 }
1112
1113 QRect nonCacheRect(cacheRect);
1114
1115 // If not all layers are scrollable, but some of the back layers
1116 // are, we should store only those in the cache.
1117
1118 bool layersChanged = false;
1119 LayerList scrollables = getScrollableBackLayers(true, layersChanged);
1120 LayerList nonScrollables = getNonScrollableFrontLayers(true, layersChanged);
1121 bool selectionCacheable = nonScrollables.empty();
1122 bool haveSelections = m_manager && !m_manager->getSelections().empty();
1123 bool selectionDrawn = false;
1124
1125 // If all the non-scrollable layers are non-opaque, then we draw
1126 // the selection rectangle behind them and cache it. If any are
1127 // opaque, however, we can't cache.
1128 //
1129 if (!selectionCacheable) {
1130 selectionCacheable = true;
1131 for (LayerList::const_iterator i = nonScrollables.begin();
1132 i != nonScrollables.end(); ++i) {
1133 if ((*i)->isLayerOpaque()) {
1134 selectionCacheable = false;
1135 break;
1136 }
1137 }
1138 }
1139
1140 if (selectionCacheable) {
1141 QPoint localPos;
1142 bool closeToLeft, closeToRight;
1143 if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
1144 selectionCacheable = false;
1145 }
1146 }
1147
1148 #ifdef DEBUG_VIEW_WIDGET_PAINT
1149 std::cerr << "View(" << this << ")::paintEvent: have " << scrollables.size()
1150 << " scrollable back layers and " << nonScrollables.size()
1151 << " non-scrollable front layers" << std::endl;
1152 std::cerr << "haveSelections " << haveSelections << ", selectionCacheable "
1153 << selectionCacheable << ", m_selectionCached " << m_selectionCached << std::endl;
1154 #endif
1155
1156 if (layersChanged || scrollables.empty() ||
1157 (haveSelections && (selectionCacheable != m_selectionCached))) {
1158 delete m_cache;
1159 m_cache = 0;
1160 m_selectionCached = false;
1161 }
1162
1163 if (!scrollables.empty()) {
1164 if (!m_cache ||
1165 m_cacheZoomLevel != m_zoomLevel ||
1166 width() != m_cache->width() ||
1167 height() != m_cache->height()) {
1168
1169 // cache is not valid
1170
1171 if (cacheRect.width() < width()/10) {
1172 #ifdef DEBUG_VIEW_WIDGET_PAINT
1173 std::cerr << "View(" << this << ")::paintEvent: small repaint, not bothering to recreate cache" << std::endl;
1174 #endif
1175 } else {
1176 delete m_cache;
1177 m_cache = new QPixmap(width(), height());
1178 #ifdef DEBUG_VIEW_WIDGET_PAINT
1179 std::cerr << "View(" << this << ")::paintEvent: recreated cache" << std::endl;
1180 #endif
1181 cacheRect = rect();
1182 repaintCache = true;
1183 }
1184
1185 } else if (m_cacheCentreFrame != m_centreFrame) {
1186
1187 long dx =
1188 getXForFrame(m_cacheCentreFrame) -
1189 getXForFrame(m_centreFrame);
1190
1191 if (dx > -width() && dx < width()) {
1192 #if defined(Q_WS_WIN32) || defined(Q_WS_MAC)
1193 // Copying a pixmap to itself doesn't work properly on Windows
1194 // or Mac (it only works when moving in one direction)
1195 static QPixmap *tmpPixmap = 0;
1196 if (!tmpPixmap ||
1197 tmpPixmap->width() != width() ||
1198 tmpPixmap->height() != height()) {
1199 delete tmpPixmap;
1200 tmpPixmap = new QPixmap(width(), height());
1201 }
1202 paint.begin(tmpPixmap);
1203 paint.drawPixmap(0, 0, *m_cache);
1204 paint.end();
1205 paint.begin(m_cache);
1206 paint.drawPixmap(dx, 0, *tmpPixmap);
1207 paint.end();
1208 #else
1209 // But it seems to be fine on X11
1210 paint.begin(m_cache);
1211 paint.drawPixmap(dx, 0, *m_cache);
1212 paint.end();
1213 #endif
1214
1215 if (dx < 0) {
1216 cacheRect = QRect(width() + dx, 0, -dx, height());
1217 } else {
1218 cacheRect = QRect(0, 0, dx, height());
1219 }
1220 #ifdef DEBUG_VIEW_WIDGET_PAINT
1221 std::cerr << "View(" << this << ")::paintEvent: scrolled cache by " << dx << std::endl;
1222 #endif
1223 } else {
1224 cacheRect = rect();
1225 #ifdef DEBUG_VIEW_WIDGET_PAINT
1226 std::cerr << "View(" << this << ")::paintEvent: scrolling too far" << std::endl;
1227 #endif
1228 }
1229 repaintCache = true;
1230
1231 } else {
1232 #ifdef DEBUG_VIEW_WIDGET_PAINT
1233 std::cerr << "View(" << this << ")::paintEvent: cache is good" << std::endl;
1234 #endif
1235 paint.begin(this);
1236 paint.drawPixmap(cacheRect, *m_cache, cacheRect);
1237 paint.end();
1238 QFrame::paintEvent(e);
1239 paintedCacheRect = true;
1240 }
1241
1242 m_cacheCentreFrame = m_centreFrame;
1243 m_cacheZoomLevel = m_zoomLevel;
1244 }
1245
1246 #ifdef DEBUG_VIEW_WIDGET_PAINT
1247 // std::cerr << "View(" << this << ")::paintEvent: cacheRect " << cacheRect << ", nonCacheRect " << (nonCacheRect | cacheRect) << ", repaintCache " << repaintCache << ", paintedCacheRect " << paintedCacheRect << std::endl;
1248 #endif
1249
1250 // Scrollable (cacheable) items first
1251
1252 if (!paintedCacheRect) {
1253
1254 if (repaintCache) paint.begin(m_cache);
1255 else paint.begin(this);
1256
1257 paint.setClipRect(cacheRect);
1258
1259 if (hasLightBackground()) {
1260 paint.setPen(Qt::white);
1261 paint.setBrush(Qt::white);
1262 } else {
1263 paint.setPen(Qt::black);
1264 paint.setBrush(Qt::black);
1265 }
1266 paint.drawRect(cacheRect);
1267
1268 paint.setPen(Qt::black);
1269 paint.setBrush(Qt::NoBrush);
1270
1271 for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
1272 paint.setRenderHint(QPainter::Antialiasing, false);
1273 paint.save();
1274 (*i)->paint(this, paint, cacheRect);
1275 paint.restore();
1276 }
1277
1278 if (haveSelections && selectionCacheable) {
1279 drawSelections(paint);
1280 m_selectionCached = repaintCache;
1281 selectionDrawn = true;
1282 }
1283
1284 paint.end();
1285
1286 if (repaintCache) {
1287 cacheRect |= (e ? e->rect() : rect());
1288 paint.begin(this);
1289 paint.drawPixmap(cacheRect, *m_cache, cacheRect);
1290 paint.end();
1291 }
1292 }
1293
1294 // Now non-cacheable items. We always need to redraw the
1295 // non-cacheable items across at least the area we drew of the
1296 // cacheable items.
1297
1298 nonCacheRect |= cacheRect;
1299
1300 paint.begin(this);
1301 paint.setClipRect(nonCacheRect);
1302
1303 if (scrollables.empty()) {
1304 if (hasLightBackground()) {
1305 paint.setPen(Qt::white);
1306 paint.setBrush(Qt::white);
1307 } else {
1308 paint.setPen(Qt::black);
1309 paint.setBrush(Qt::black);
1310 }
1311 paint.drawRect(nonCacheRect);
1312 }
1313
1314 paint.setPen(Qt::black);
1315 paint.setBrush(Qt::NoBrush);
1316
1317 for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
1318 // Profiler profiler2("View::paintEvent non-cacheable");
1319 (*i)->paint(this, paint, nonCacheRect);
1320 }
1321
1322 paint.end();
1323
1324 paint.begin(this);
1325 if (e) paint.setClipRect(e->rect());
1326 if (!m_selectionCached) {
1327 drawSelections(paint);
1328 }
1329 paint.end();
1330
1331 if (m_followPlay != PlaybackScrollContinuous) {
1332
1333 paint.begin(this);
1334
1335 if (long(m_playPointerFrame) > getStartFrame() &&
1336 m_playPointerFrame < getEndFrame()) {
1337
1338 int playx = getXForFrame(m_playPointerFrame);
1339
1340 paint.setPen(Qt::black);
1341 paint.drawLine(playx - 1, 0, playx - 1, height() - 1);
1342 paint.drawLine(playx + 1, 0, playx + 1, height() - 1);
1343 paint.drawPoint(playx, 0);
1344 paint.drawPoint(playx, height() - 1);
1345 paint.setPen(Qt::white);
1346 paint.drawLine(playx, 1, playx, height() - 2);
1347 }
1348
1349 paint.end();
1350 }
1351
1352 QFrame::paintEvent(e);
1353 }
1354
1355 void
1356 View::drawSelections(QPainter &paint)
1357 {
1358 MultiSelection::SelectionList selections;
1359
1360 if (m_manager) {
1361 selections = m_manager->getSelections();
1362 if (m_manager->haveInProgressSelection()) {
1363 bool exclusive;
1364 Selection inProgressSelection =
1365 m_manager->getInProgressSelection(exclusive);
1366 if (exclusive) selections.clear();
1367 selections.insert(inProgressSelection);
1368 }
1369 }
1370
1371 paint.save();
1372 paint.setBrush(QColor(150, 150, 255, 80));
1373
1374 int sampleRate = getModelsSampleRate();
1375
1376 QPoint localPos;
1377 long illuminateFrame = -1;
1378 bool closeToLeft, closeToRight;
1379
1380 if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
1381 illuminateFrame = getFrameForX(localPos.x());
1382 }
1383
1384 const QFontMetrics &metrics = paint.fontMetrics();
1385
1386 for (MultiSelection::SelectionList::iterator i = selections.begin();
1387 i != selections.end(); ++i) {
1388
1389 int p0 = getXForFrame(i->getStartFrame());
1390 int p1 = getXForFrame(i->getEndFrame());
1391
1392 if (p1 < 0 || p0 > width()) continue;
1393
1394 #ifdef DEBUG_VIEW_WIDGET_PAINT
1395 std::cerr << "View::drawSelections: " << p0 << ",-1 [" << (p1-p0) << "x" << (height()+1) << "]" << std::endl;
1396 #endif
1397
1398 bool illuminateThis =
1399 (illuminateFrame >= 0 && i->contains(illuminateFrame));
1400
1401 paint.setPen(QColor(150, 150, 255));
1402 paint.drawRect(p0, -1, p1 - p0, height() + 1);
1403
1404 if (illuminateThis) {
1405 paint.save();
1406 if (hasLightBackground()) {
1407 paint.setPen(QPen(Qt::black, 2));
1408 } else {
1409 paint.setPen(QPen(Qt::white, 2));
1410 }
1411 if (closeToLeft) {
1412 paint.drawLine(p0, 1, p1, 1);
1413 paint.drawLine(p0, 0, p0, height());
1414 paint.drawLine(p0, height() - 1, p1, height() - 1);
1415 } else if (closeToRight) {
1416 paint.drawLine(p0, 1, p1, 1);
1417 paint.drawLine(p1, 0, p1, height());
1418 paint.drawLine(p0, height() - 1, p1, height() - 1);
1419 } else {
1420 paint.setBrush(Qt::NoBrush);
1421 paint.drawRect(p0, 1, p1 - p0, height() - 2);
1422 }
1423 paint.restore();
1424 }
1425
1426 if (sampleRate && shouldLabelSelections() && m_manager &&
1427 m_manager->getOverlayMode() != ViewManager::NoOverlays) {
1428
1429 QString startText = QString("%1 / %2")
1430 .arg(QString::fromStdString
1431 (RealTime::frame2RealTime
1432 (i->getStartFrame(), sampleRate).toText(true)))
1433 .arg(i->getStartFrame());
1434
1435 QString endText = QString(" %1 / %2")
1436 .arg(QString::fromStdString
1437 (RealTime::frame2RealTime
1438 (i->getEndFrame(), sampleRate).toText(true)))
1439 .arg(i->getEndFrame());
1440
1441 QString durationText = QString("(%1 / %2) ")
1442 .arg(QString::fromStdString
1443 (RealTime::frame2RealTime
1444 (i->getEndFrame() - i->getStartFrame(), sampleRate)
1445 .toText(true)))
1446 .arg(i->getEndFrame() - i->getStartFrame());
1447
1448 int sw = metrics.width(startText),
1449 ew = metrics.width(endText),
1450 dw = metrics.width(durationText);
1451
1452 int sy = metrics.ascent() + metrics.height() + 4;
1453 int ey = sy;
1454 int dy = sy + metrics.height();
1455
1456 int sx = p0 + 2;
1457 int ex = sx;
1458 int dx = sx;
1459
1460 if (sw + ew > (p1 - p0)) {
1461 ey += metrics.height();
1462 dy += metrics.height();
1463 }
1464
1465 if (ew < (p1 - p0)) {
1466 ex = p1 - 2 - ew;
1467 }
1468
1469 if (dw < (p1 - p0)) {
1470 dx = p1 - 2 - dw;
1471 }
1472
1473 paint.drawText(sx, sy, startText);
1474 paint.drawText(ex, ey, endText);
1475 paint.drawText(dx, dy, durationText);
1476 }
1477 }
1478
1479 paint.restore();
1480 }
1481
1482 QString
1483 View::toXmlString(QString indent, QString extraAttributes) const
1484 {
1485 QString s;
1486
1487 s += indent;
1488
1489 s += QString("<view "
1490 "centre=\"%1\" "
1491 "zoom=\"%2\" "
1492 "followPan=\"%3\" "
1493 "followZoom=\"%4\" "
1494 "tracking=\"%5\" "
1495 "light=\"%6\" %7>\n")
1496 .arg(m_centreFrame)
1497 .arg(m_zoomLevel)
1498 .arg(m_followPan)
1499 .arg(m_followZoom)
1500 .arg(m_followPlay == PlaybackScrollContinuous ? "scroll" :
1501 m_followPlay == PlaybackScrollPage ? "page" : "ignore")
1502 .arg(m_lightBackground)
1503 .arg(extraAttributes);
1504
1505 for (size_t i = 0; i < m_layers.size(); ++i) {
1506 s += m_layers[i]->toXmlString(indent + " ");
1507 }
1508
1509 s += indent + "</view>\n";
1510
1511 return s;
1512 }
1513
1514 ViewPropertyContainer::ViewPropertyContainer(View *v) :
1515 m_v(v)
1516 {
1517 connect(m_v, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
1518 this, SIGNAL(propertyChanged(PropertyContainer::PropertyName)));
1519 }
1520
1521 #ifdef INCLUDE_MOCFILES
1522 #include "View.moc.cpp"
1523 #endif
1524