Mercurial > hg > easaier-soundaccess
comparison view/Pane.cpp @ 0:fc9323a41f5a
start base : Sonic Visualiser sv1-1.0rc1
author | lbajardsilogic |
---|---|
date | Fri, 11 May 2007 09:08:14 +0000 |
parents | |
children | 7b19f2719f91 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:fc9323a41f5a |
---|---|
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 and QMUL. | |
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 "Pane.h" | |
17 #include "layer/Layer.h" | |
18 #include "data/model/Model.h" | |
19 #include "base/ZoomConstraint.h" | |
20 #include "base/RealTime.h" | |
21 #include "base/Profiler.h" | |
22 #include "ViewManager.h" | |
23 #include "base/CommandHistory.h" | |
24 #include "layer/WaveformLayer.h" | |
25 | |
26 #include <QPaintEvent> | |
27 #include <QPainter> | |
28 #include <iostream> | |
29 #include <cmath> | |
30 | |
31 //!!! for HUD -- pull out into a separate class | |
32 #include <QFrame> | |
33 #include <QGridLayout> | |
34 #include <QPushButton> | |
35 #include "widgets/Thumbwheel.h" | |
36 #include "widgets/Panner.h" | |
37 #include "widgets/RangeInputDialog.h" | |
38 #include "widgets/NotifyingPushButton.h" | |
39 | |
40 using std::cerr; | |
41 using std::endl; | |
42 | |
43 Pane::Pane(QWidget *w) : | |
44 View(w, true), | |
45 m_identifyFeatures(false), | |
46 m_clickedInRange(false), | |
47 m_shiftPressed(false), | |
48 m_ctrlPressed(false), | |
49 m_navigating(false), | |
50 m_resizing(false), | |
51 m_centreLineVisible(true), | |
52 m_scaleWidth(0), | |
53 m_headsUpDisplay(0), | |
54 m_vpan(0), | |
55 m_hthumb(0), | |
56 m_vthumb(0), | |
57 m_reset(0) | |
58 { | |
59 setObjectName("Pane"); | |
60 setMouseTracking(true); | |
61 | |
62 updateHeadsUpDisplay(); | |
63 } | |
64 | |
65 void | |
66 Pane::updateHeadsUpDisplay() | |
67 { | |
68 Profiler profiler("Pane::updateHeadsUpDisplay", true); | |
69 | |
70 if (!isVisible()) return; | |
71 | |
72 /* | |
73 int count = 0; | |
74 int currentLevel = 1; | |
75 int level = 1; | |
76 while (true) { | |
77 if (getZoomLevel() == level) currentLevel = count; | |
78 int newLevel = getZoomConstraintBlockSize(level + 1, | |
79 ZoomConstraint::RoundUp); | |
80 if (newLevel == level) break; | |
81 if (newLevel == 131072) break; //!!! just because | |
82 level = newLevel; | |
83 ++count; | |
84 } | |
85 | |
86 std::cerr << "Have " << count+1 << " zoom levels" << std::endl; | |
87 */ | |
88 | |
89 Layer *layer = 0; | |
90 if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); | |
91 | |
92 if (!m_headsUpDisplay) { | |
93 | |
94 m_headsUpDisplay = new QFrame(this); | |
95 | |
96 QGridLayout *layout = new QGridLayout; | |
97 layout->setMargin(0); | |
98 layout->setSpacing(0); | |
99 m_headsUpDisplay->setLayout(layout); | |
100 | |
101 m_hthumb = new Thumbwheel(Qt::Horizontal); | |
102 m_hthumb->setObjectName(tr("Horizontal Zoom")); | |
103 layout->addWidget(m_hthumb, 1, 0, 1, 2); | |
104 m_hthumb->setFixedWidth(70); | |
105 m_hthumb->setFixedHeight(16); | |
106 m_hthumb->setDefaultValue(0); | |
107 m_hthumb->setSpeed(0.6); | |
108 connect(m_hthumb, SIGNAL(valueChanged(int)), this, | |
109 SLOT(horizontalThumbwheelMoved(int))); | |
110 connect(m_hthumb, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
111 connect(m_hthumb, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
112 | |
113 m_vpan = new Panner; | |
114 layout->addWidget(m_vpan, 0, 1); | |
115 m_vpan->setFixedWidth(12); | |
116 m_vpan->setFixedHeight(70); | |
117 m_vpan->setAlpha(80, 130); | |
118 connect(m_vpan, SIGNAL(rectExtentsChanged(float, float, float, float)), | |
119 this, SLOT(verticalPannerMoved(float, float, float, float))); | |
120 connect(m_vpan, SIGNAL(doubleClicked()), | |
121 this, SLOT(editVerticalPannerExtents())); | |
122 connect(m_vpan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
123 connect(m_vpan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
124 | |
125 m_vthumb = new Thumbwheel(Qt::Vertical); | |
126 m_vthumb->setObjectName(tr("Vertical Zoom")); | |
127 layout->addWidget(m_vthumb, 0, 2); | |
128 m_vthumb->setFixedWidth(16); | |
129 m_vthumb->setFixedHeight(70); | |
130 connect(m_vthumb, SIGNAL(valueChanged(int)), this, | |
131 SLOT(verticalThumbwheelMoved(int))); | |
132 connect(m_vthumb, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
133 connect(m_vthumb, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
134 | |
135 if (layer) { | |
136 RangeMapper *rm = layer->getNewVerticalZoomRangeMapper(); | |
137 if (rm) m_vthumb->setRangeMapper(rm); | |
138 } | |
139 | |
140 m_reset = new NotifyingPushButton; | |
141 m_reset->setFixedHeight(16); | |
142 m_reset->setFixedWidth(16); | |
143 layout->addWidget(m_reset, 1, 2); | |
144 connect(m_reset, SIGNAL(clicked()), m_hthumb, SLOT(resetToDefault())); | |
145 connect(m_reset, SIGNAL(clicked()), m_vthumb, SLOT(resetToDefault())); | |
146 connect(m_reset, SIGNAL(clicked()), m_vpan, SLOT(resetToDefault())); | |
147 connect(m_reset, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
148 connect(m_reset, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
149 } | |
150 | |
151 int count = 0; | |
152 int current = 0; | |
153 int level = 1; | |
154 | |
155 //!!! pull out into function (presumably in View) | |
156 bool haveConstraint = false; | |
157 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); | |
158 ++i) { | |
159 if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) { | |
160 haveConstraint = true; | |
161 break; | |
162 } | |
163 } | |
164 | |
165 if (haveConstraint) { | |
166 while (true) { | |
167 if (getZoomLevel() == level) current = count; | |
168 int newLevel = getZoomConstraintBlockSize(level + 1, | |
169 ZoomConstraint::RoundUp); | |
170 if (newLevel == level) break; | |
171 level = newLevel; | |
172 if (++count == 50) break; | |
173 } | |
174 } else { | |
175 // if we have no particular constraints, we can really spread out | |
176 while (true) { | |
177 if (getZoomLevel() >= level) current = count; | |
178 int step = level / 10; | |
179 int pwr = 0; | |
180 while (step > 0) { | |
181 ++pwr; | |
182 step /= 2; | |
183 } | |
184 step = 1; | |
185 while (pwr > 0) { | |
186 step *= 2; | |
187 --pwr; | |
188 } | |
189 // std::cerr << level << std::endl; | |
190 level += step; | |
191 if (++count == 100 || level > 262144) break; | |
192 } | |
193 } | |
194 | |
195 // std::cerr << "Have " << count << " zoom levels" << std::endl; | |
196 | |
197 m_hthumb->setMinimumValue(0); | |
198 m_hthumb->setMaximumValue(count); | |
199 m_hthumb->setValue(count - current); | |
200 | |
201 // std::cerr << "set value to " << count-current << std::endl; | |
202 | |
203 // std::cerr << "default value is " << m_hthumb->getDefaultValue() << std::endl; | |
204 | |
205 if (count != 50 && m_hthumb->getDefaultValue() == 0) { | |
206 m_hthumb->setDefaultValue(count - current); | |
207 // std::cerr << "set default value to " << m_hthumb->getDefaultValue() << std::endl; | |
208 } | |
209 | |
210 bool haveVThumb = false; | |
211 | |
212 if (layer) { | |
213 int defaultStep = 0; | |
214 int max = layer->getVerticalZoomSteps(defaultStep); | |
215 if (max == 0) { | |
216 m_vthumb->hide(); | |
217 } else { | |
218 haveVThumb = true; | |
219 m_vthumb->show(); | |
220 m_vthumb->blockSignals(true); | |
221 m_vthumb->setMinimumValue(0); | |
222 m_vthumb->setMaximumValue(max); | |
223 m_vthumb->setDefaultValue(defaultStep); | |
224 m_vthumb->setValue(layer->getCurrentVerticalZoomStep()); | |
225 m_vthumb->blockSignals(false); | |
226 | |
227 // std::cerr << "Vertical thumbwheel: min 0, max " << max | |
228 // << ", default " << defaultStep << ", value " | |
229 // << m_vthumb->getValue() << std::endl; | |
230 | |
231 } | |
232 } | |
233 | |
234 updateVerticalPanner(); | |
235 | |
236 if (m_manager && m_manager->getZoomWheelsEnabled() && | |
237 width() > 120 && height() > 100) { | |
238 if (!m_headsUpDisplay->isVisible()) { | |
239 m_headsUpDisplay->show(); | |
240 } | |
241 if (haveVThumb) { | |
242 m_headsUpDisplay->setFixedHeight(m_vthumb->height() + m_hthumb->height()); | |
243 m_headsUpDisplay->move(width() - 86, height() - 86); | |
244 } else { | |
245 m_headsUpDisplay->setFixedHeight(m_hthumb->height()); | |
246 m_headsUpDisplay->move(width() - 86, height() - 16); | |
247 } | |
248 } else { | |
249 m_headsUpDisplay->hide(); | |
250 } | |
251 } | |
252 | |
253 void | |
254 Pane::updateVerticalPanner() | |
255 { | |
256 if (!m_vpan || !m_manager || !m_manager->getZoomWheelsEnabled()) return; | |
257 | |
258 // In principle we should show or hide the panner on the basis of | |
259 // whether the top layer has adjustable display extents, and we do | |
260 // that below. However, we have no basis for layout of the panner | |
261 // if the vertical scroll wheel is not also present. So if we | |
262 // have no vertical scroll wheel, we should remove the panner as | |
263 // well. Ideally any layer that implements display extents should | |
264 // implement vertical zoom steps as well, but they don't all at | |
265 // the moment. | |
266 | |
267 Layer *layer = 0; | |
268 if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); | |
269 int discard; | |
270 if (layer && layer->getVerticalZoomSteps(discard) == 0) { | |
271 m_vpan->hide(); | |
272 return; | |
273 } | |
274 | |
275 float vmin, vmax, dmin, dmax; | |
276 if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax) && vmax != vmin) { | |
277 float y0 = (dmin - vmin) / (vmax - vmin); | |
278 float y1 = (dmax - vmin) / (vmax - vmin); | |
279 m_vpan->blockSignals(true); | |
280 m_vpan->setRectExtents(0, 1.0 - y1, 1, y1 - y0); | |
281 m_vpan->blockSignals(false); | |
282 m_vpan->show(); | |
283 } else { | |
284 m_vpan->hide(); | |
285 } | |
286 } | |
287 | |
288 bool | |
289 Pane::shouldIlluminateLocalFeatures(const Layer *layer, QPoint &pos) const | |
290 { | |
291 QPoint discard; | |
292 bool b0, b1; | |
293 | |
294 if (layer == getSelectedLayer() && | |
295 !shouldIlluminateLocalSelection(discard, b0, b1)) { | |
296 | |
297 pos = m_identifyPoint; | |
298 return m_identifyFeatures; | |
299 } | |
300 | |
301 return false; | |
302 } | |
303 | |
304 bool | |
305 Pane::shouldIlluminateLocalSelection(QPoint &pos, | |
306 bool &closeToLeft, | |
307 bool &closeToRight) const | |
308 { | |
309 if (m_identifyFeatures && | |
310 m_manager && | |
311 m_manager->getToolMode() == ViewManager::EditMode && | |
312 !m_manager->getSelections().empty() && | |
313 !selectionIsBeingEdited()) { | |
314 | |
315 Selection s(getSelectionAt(m_identifyPoint.x(), | |
316 closeToLeft, closeToRight)); | |
317 | |
318 if (!s.isEmpty()) { | |
319 if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) { | |
320 | |
321 pos = m_identifyPoint; | |
322 return true; | |
323 } | |
324 } | |
325 } | |
326 | |
327 return false; | |
328 } | |
329 | |
330 bool | |
331 Pane::selectionIsBeingEdited() const | |
332 { | |
333 if (!m_editingSelection.isEmpty()) { | |
334 if (m_mousePos != m_clickPos && | |
335 getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) { | |
336 return true; | |
337 } | |
338 } | |
339 return false; | |
340 } | |
341 | |
342 void | |
343 Pane::setCentreLineVisible(bool visible) | |
344 { | |
345 m_centreLineVisible = visible; | |
346 update(); | |
347 } | |
348 | |
349 void | |
350 Pane::paintEvent(QPaintEvent *e) | |
351 { | |
352 // Profiler profiler("Pane::paintEvent", true); | |
353 | |
354 QPainter paint; | |
355 | |
356 QRect r(rect()); | |
357 | |
358 if (e) { | |
359 r = e->rect(); | |
360 } | |
361 /* | |
362 paint.begin(this); | |
363 paint.setClipRect(r); | |
364 | |
365 if (hasLightBackground()) { | |
366 paint.setPen(Qt::white); | |
367 paint.setBrush(Qt::white); | |
368 } else { | |
369 paint.setPen(Qt::black); | |
370 paint.setBrush(Qt::black); | |
371 } | |
372 paint.drawRect(r); | |
373 | |
374 paint.end(); | |
375 */ | |
376 View::paintEvent(e); | |
377 | |
378 paint.begin(this); | |
379 | |
380 if (e) { | |
381 paint.setClipRect(r); | |
382 } | |
383 | |
384 const Model *waveformModel = 0; // just for reporting purposes | |
385 | |
386 int fontHeight = paint.fontMetrics().height(); | |
387 int fontAscent = paint.fontMetrics().ascent(); | |
388 | |
389 if (m_manager && | |
390 !m_manager->isPlaying() && | |
391 m_manager->getToolMode() == ViewManager::SelectMode) { | |
392 | |
393 for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { | |
394 --vi; | |
395 | |
396 std::vector<QRect> crosshairExtents; | |
397 | |
398 if ((*vi)->getCrosshairExtents(this, paint, m_identifyPoint, | |
399 crosshairExtents)) { | |
400 (*vi)->paintCrosshairs(this, paint, m_identifyPoint); | |
401 break; | |
402 } else if ((*vi)->isLayerOpaque()) { | |
403 break; | |
404 } | |
405 } | |
406 } | |
407 | |
408 for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { | |
409 --vi; | |
410 | |
411 if (dynamic_cast<WaveformLayer *>(*vi)) { | |
412 waveformModel = (*vi)->getModel(); | |
413 } | |
414 | |
415 if (!m_manager || !m_manager->shouldShowVerticalScale()) { | |
416 m_scaleWidth = 0; | |
417 } else { | |
418 m_scaleWidth = (*vi)->getVerticalScaleWidth(this, paint); | |
419 } | |
420 | |
421 if (m_scaleWidth > 0 && r.left() < m_scaleWidth) { | |
422 | |
423 // Profiler profiler("Pane::paintEvent - painting vertical scale", true); | |
424 | |
425 // std::cerr << "Pane::paintEvent: calling paint.save() in vertical scale block" << std::endl; | |
426 paint.save(); | |
427 | |
428 paint.setPen(Qt::black); | |
429 paint.setBrush(Qt::white); | |
430 paint.drawRect(0, -1, m_scaleWidth, height()+1); | |
431 | |
432 paint.setBrush(Qt::NoBrush); | |
433 (*vi)->paintVerticalScale | |
434 (this, paint, QRect(0, 0, m_scaleWidth, height())); | |
435 | |
436 paint.restore(); | |
437 } | |
438 | |
439 if (m_identifyFeatures) { | |
440 | |
441 QPoint pos = m_identifyPoint; | |
442 QString desc = (*vi)->getFeatureDescription(this, pos); | |
443 | |
444 if (desc != "") { | |
445 | |
446 paint.save(); | |
447 | |
448 int tabStop = | |
449 paint.fontMetrics().width(tr("Some lengthy prefix:")); | |
450 | |
451 QRect boundingRect = | |
452 paint.fontMetrics().boundingRect | |
453 (rect(), | |
454 Qt::AlignRight | Qt::AlignTop | Qt::TextExpandTabs, | |
455 desc, tabStop); | |
456 | |
457 if (hasLightBackground()) { | |
458 paint.setPen(Qt::NoPen); | |
459 paint.setBrush(QColor(250, 250, 250, 200)); | |
460 } else { | |
461 paint.setPen(Qt::NoPen); | |
462 paint.setBrush(QColor(50, 50, 50, 200)); | |
463 } | |
464 | |
465 int extra = paint.fontMetrics().descent(); | |
466 paint.drawRect(width() - boundingRect.width() - 10 - extra, | |
467 10 - extra, | |
468 boundingRect.width() + 2 * extra, | |
469 boundingRect.height() + extra); | |
470 | |
471 if (hasLightBackground()) { | |
472 paint.setPen(QColor(150, 20, 0)); | |
473 } else { | |
474 paint.setPen(QColor(255, 150, 100)); | |
475 } | |
476 | |
477 QTextOption option; | |
478 option.setWrapMode(QTextOption::NoWrap); | |
479 option.setAlignment(Qt::AlignRight | Qt::AlignTop); | |
480 option.setTabStop(tabStop); | |
481 paint.drawText(QRectF(width() - boundingRect.width() - 10, 10, | |
482 boundingRect.width(), | |
483 boundingRect.height()), | |
484 desc, | |
485 option); | |
486 | |
487 paint.restore(); | |
488 } | |
489 } | |
490 | |
491 break; | |
492 } | |
493 | |
494 int sampleRate = getModelsSampleRate(); | |
495 paint.setBrush(Qt::NoBrush); | |
496 | |
497 if (m_centreLineVisible && | |
498 m_manager && | |
499 m_manager->shouldShowCentreLine()) { | |
500 | |
501 QColor c = QColor(0, 0, 0); | |
502 if (!hasLightBackground()) { | |
503 c = QColor(240, 240, 240); | |
504 } | |
505 paint.setPen(c); | |
506 int x = width() / 2 + 1; | |
507 paint.drawLine(x, 0, x, height() - 1); | |
508 paint.drawLine(x-1, 1, x+1, 1); | |
509 paint.drawLine(x-2, 0, x+2, 0); | |
510 paint.drawLine(x-1, height() - 2, x+1, height() - 2); | |
511 paint.drawLine(x-2, height() - 1, x+2, height() - 1); | |
512 | |
513 paint.setPen(QColor(50, 50, 50)); | |
514 | |
515 int y = height() - fontHeight | |
516 + fontAscent - 6; | |
517 | |
518 LayerList::iterator vi = m_layers.end(); | |
519 | |
520 if (vi != m_layers.begin()) { | |
521 | |
522 switch ((*--vi)->getPreferredFrameCountPosition()) { | |
523 | |
524 case Layer::PositionTop: | |
525 y = fontAscent + 6; | |
526 break; | |
527 | |
528 case Layer::PositionMiddle: | |
529 y = (height() - fontHeight) / 2 | |
530 + fontAscent; | |
531 break; | |
532 | |
533 case Layer::PositionBottom: | |
534 // y already set correctly | |
535 break; | |
536 } | |
537 } | |
538 | |
539 if (m_manager && m_manager->shouldShowFrameCount()) { | |
540 | |
541 if (sampleRate) { | |
542 | |
543 QString text(QString::fromStdString | |
544 (RealTime::frame2RealTime | |
545 (m_centreFrame, sampleRate).toText(true))); | |
546 | |
547 int tw = paint.fontMetrics().width(text); | |
548 int x = width()/2 - 4 - tw; | |
549 | |
550 drawVisibleText(paint, x, y, text, OutlinedText); | |
551 } | |
552 | |
553 QString text = QString("%1").arg(m_centreFrame); | |
554 | |
555 int x = width()/2 + 4; | |
556 | |
557 drawVisibleText(paint, x, y, text, OutlinedText); | |
558 } | |
559 | |
560 } else { | |
561 | |
562 paint.setPen(QColor(50, 50, 50)); | |
563 } | |
564 | |
565 if (waveformModel && | |
566 m_manager && | |
567 m_manager->shouldShowDuration() && | |
568 r.y() + r.height() >= height() - fontHeight - 6) { | |
569 | |
570 size_t modelRate = waveformModel->getSampleRate(); | |
571 size_t playbackRate = m_manager->getPlaybackSampleRate(); | |
572 size_t outputRate = m_manager->getOutputSampleRate(); | |
573 | |
574 QString srNote = ""; | |
575 | |
576 // Show (R) for waveform models that will be resampled on | |
577 // playback, and (X) for waveform models that will be played | |
578 // at the wrong rate because their rate differs from the | |
579 // current playback rate (which is not necessarily that of the | |
580 // main model). | |
581 | |
582 if (playbackRate != 0) { | |
583 if (modelRate == playbackRate) { | |
584 if (modelRate != outputRate) srNote = " " + tr("(R)"); | |
585 } else { | |
586 srNote = " " + tr("(X)"); | |
587 } | |
588 } | |
589 | |
590 QString desc = tr("%1 / %2Hz%3") | |
591 .arg(RealTime::frame2RealTime(waveformModel->getEndFrame(), | |
592 sampleRate) | |
593 .toText(false).c_str()) | |
594 .arg(modelRate) | |
595 .arg(srNote); | |
596 | |
597 if (r.x() < m_scaleWidth + 5 + paint.fontMetrics().width(desc)) { | |
598 drawVisibleText(paint, m_scaleWidth + 5, | |
599 height() - fontHeight + fontAscent - 6, | |
600 desc, OutlinedText); | |
601 } | |
602 } | |
603 | |
604 if (m_manager && | |
605 m_manager->shouldShowLayerNames() && | |
606 r.y() + r.height() >= height() - int(m_layers.size()) * fontHeight - 6) { | |
607 | |
608 std::vector<QString> texts; | |
609 int maxTextWidth = 0; | |
610 | |
611 for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { | |
612 | |
613 QString text = (*i)->getLayerPresentationName(); | |
614 int tw = paint.fontMetrics().width(text); | |
615 bool reduced = false; | |
616 while (tw > width() / 3 && text.length() > 4) { | |
617 if (!reduced && text.length() > 8) { | |
618 text = text.left(text.length() - 4); | |
619 } else { | |
620 text = text.left(text.length() - 2); | |
621 } | |
622 reduced = true; | |
623 tw = paint.fontMetrics().width(text + "..."); | |
624 } | |
625 if (reduced) { | |
626 texts.push_back(text + "..."); | |
627 } else { | |
628 texts.push_back(text); | |
629 } | |
630 if (tw > maxTextWidth) maxTextWidth = tw; | |
631 } | |
632 | |
633 int lly = height() - 6; | |
634 int llx = width() - maxTextWidth - 5; | |
635 | |
636 if (m_manager->getZoomWheelsEnabled()) { | |
637 lly -= 20; | |
638 llx -= 36; | |
639 } | |
640 | |
641 if (r.x() + r.width() >= llx) { | |
642 | |
643 for (size_t i = 0; i < texts.size(); ++i) { | |
644 | |
645 if (i + 1 == texts.size()) { | |
646 paint.setPen(Qt::black); | |
647 } | |
648 | |
649 drawVisibleText(paint, llx, | |
650 lly - fontHeight + fontAscent, | |
651 texts[i], OutlinedText); | |
652 | |
653 lly -= fontHeight; | |
654 } | |
655 } | |
656 } | |
657 | |
658 if (m_clickedInRange && m_shiftPressed) { | |
659 if (m_manager && (m_manager->getToolMode() == ViewManager::NavigateMode)) { | |
660 //!!! be nice if this looked a bit more in keeping with the | |
661 //selection block | |
662 paint.setPen(Qt::blue); | |
663 paint.drawRect(m_clickPos.x(), m_clickPos.y(), | |
664 m_mousePos.x() - m_clickPos.x(), | |
665 m_mousePos.y() - m_clickPos.y()); | |
666 } | |
667 } | |
668 | |
669 if (selectionIsBeingEdited()) { | |
670 | |
671 int offset = m_mousePos.x() - m_clickPos.x(); | |
672 int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset; | |
673 int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset; | |
674 | |
675 if (m_editingSelectionEdge < 0) { | |
676 p1 = getXForFrame(m_editingSelection.getEndFrame()); | |
677 } else if (m_editingSelectionEdge > 0) { | |
678 p0 = getXForFrame(m_editingSelection.getStartFrame()); | |
679 } | |
680 | |
681 paint.save(); | |
682 if (hasLightBackground()) { | |
683 paint.setPen(QPen(Qt::black, 2)); | |
684 } else { | |
685 paint.setPen(QPen(Qt::white, 2)); | |
686 } | |
687 | |
688 //!!! duplicating display policy with View::drawSelections | |
689 | |
690 if (m_editingSelectionEdge < 0) { | |
691 paint.drawLine(p0, 1, p1, 1); | |
692 paint.drawLine(p0, 0, p0, height()); | |
693 paint.drawLine(p0, height() - 1, p1, height() - 1); | |
694 } else if (m_editingSelectionEdge > 0) { | |
695 paint.drawLine(p0, 1, p1, 1); | |
696 paint.drawLine(p1, 0, p1, height()); | |
697 paint.drawLine(p0, height() - 1, p1, height() - 1); | |
698 } else { | |
699 paint.setBrush(Qt::NoBrush); | |
700 paint.drawRect(p0, 1, p1 - p0, height() - 2); | |
701 } | |
702 paint.restore(); | |
703 } | |
704 | |
705 paint.end(); | |
706 } | |
707 | |
708 bool | |
709 Pane::render(QPainter &paint, int xorigin, size_t f0, size_t f1) | |
710 { | |
711 if (!View::render(paint, xorigin + m_scaleWidth, f0, f1)) { | |
712 return false; | |
713 } | |
714 | |
715 if (m_scaleWidth > 0) { | |
716 | |
717 for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { | |
718 --vi; | |
719 | |
720 paint.save(); | |
721 | |
722 paint.setPen(Qt::black); | |
723 paint.setBrush(Qt::white); | |
724 paint.drawRect(xorigin, -1, m_scaleWidth, height()+1); | |
725 | |
726 paint.setBrush(Qt::NoBrush); | |
727 (*vi)->paintVerticalScale | |
728 (this, paint, QRect(xorigin, 0, m_scaleWidth, height())); | |
729 | |
730 paint.restore(); | |
731 break; | |
732 } | |
733 } | |
734 | |
735 return true; | |
736 } | |
737 | |
738 QImage * | |
739 Pane::toNewImage(size_t f0, size_t f1) | |
740 { | |
741 size_t x0 = f0 / getZoomLevel(); | |
742 size_t x1 = f1 / getZoomLevel(); | |
743 | |
744 QImage *image = new QImage(x1 - x0 + m_scaleWidth, | |
745 height(), QImage::Format_RGB32); | |
746 | |
747 int formerScaleWidth = m_scaleWidth; | |
748 | |
749 if (m_manager && m_manager->shouldShowVerticalScale()) { | |
750 for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { | |
751 --vi; | |
752 QPainter paint(image); | |
753 m_scaleWidth = (*vi)->getVerticalScaleWidth(this, paint); | |
754 break; | |
755 } | |
756 } else { | |
757 m_scaleWidth = 0; | |
758 } | |
759 | |
760 if (m_scaleWidth != formerScaleWidth) { | |
761 delete image; | |
762 image = new QImage(x1 - x0 + m_scaleWidth, | |
763 height(), QImage::Format_RGB32); | |
764 } | |
765 | |
766 QPainter *paint = new QPainter(image); | |
767 if (!render(*paint, 0, f0, f1)) { | |
768 delete paint; | |
769 delete image; | |
770 return 0; | |
771 } else { | |
772 delete paint; | |
773 return image; | |
774 } | |
775 } | |
776 | |
777 QSize | |
778 Pane::getImageSize(size_t f0, size_t f1) | |
779 { | |
780 QSize s = View::getImageSize(f0, f1); | |
781 QImage *image = new QImage(100, 100, QImage::Format_RGB32); | |
782 QPainter paint(image); | |
783 | |
784 int sw = 0; | |
785 if (m_manager && m_manager->shouldShowVerticalScale()) { | |
786 for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) { | |
787 --vi; | |
788 QPainter paint(image); | |
789 sw = (*vi)->getVerticalScaleWidth(this, paint); | |
790 break; | |
791 } | |
792 } | |
793 | |
794 return QSize(sw + s.width(), s.height()); | |
795 } | |
796 | |
797 size_t | |
798 Pane::getFirstVisibleFrame() const | |
799 { | |
800 long f0 = getFrameForX(m_scaleWidth); | |
801 size_t f = View::getFirstVisibleFrame(); | |
802 if (f0 < 0 || f0 < long(f)) return f; | |
803 return f0; | |
804 } | |
805 | |
806 Selection | |
807 Pane::getSelectionAt(int x, bool &closeToLeftEdge, bool &closeToRightEdge) const | |
808 { | |
809 closeToLeftEdge = closeToRightEdge = false; | |
810 | |
811 if (!m_manager) return Selection(); | |
812 | |
813 long testFrame = getFrameForX(x - 5); | |
814 if (testFrame < 0) { | |
815 testFrame = getFrameForX(x); | |
816 if (testFrame < 0) return Selection(); | |
817 } | |
818 | |
819 Selection selection = m_manager->getContainingSelection(testFrame, true); | |
820 if (selection.isEmpty()) return selection; | |
821 | |
822 int lx = getXForFrame(selection.getStartFrame()); | |
823 int rx = getXForFrame(selection.getEndFrame()); | |
824 | |
825 int fuzz = 2; | |
826 if (x < lx - fuzz || x > rx + fuzz) return Selection(); | |
827 | |
828 int width = rx - lx; | |
829 fuzz = 3; | |
830 if (width < 12) fuzz = width / 4; | |
831 if (fuzz < 1) fuzz = 1; | |
832 | |
833 if (x < lx + fuzz) closeToLeftEdge = true; | |
834 if (x > rx - fuzz) closeToRightEdge = true; | |
835 | |
836 return selection; | |
837 } | |
838 | |
839 bool | |
840 Pane::canTopLayerMoveVertical() | |
841 { | |
842 float vmin, vmax, dmin, dmax; | |
843 if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) return false; | |
844 if (dmin <= vmin && dmax >= vmax) return false; | |
845 return true; | |
846 } | |
847 | |
848 bool | |
849 Pane::getTopLayerDisplayExtents(float &vmin, float &vmax, | |
850 float &dmin, float &dmax, | |
851 QString *unit) | |
852 { | |
853 Layer *layer = 0; | |
854 if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); | |
855 if (!layer) return false; | |
856 bool vlog; | |
857 QString vunit; | |
858 bool rv = (layer->getValueExtents(vmin, vmax, vlog, vunit) && | |
859 layer->getDisplayExtents(dmin, dmax)); | |
860 if (unit) *unit = vunit; | |
861 return rv; | |
862 } | |
863 | |
864 bool | |
865 Pane::setTopLayerDisplayExtents(float dmin, float dmax) | |
866 { | |
867 Layer *layer = 0; | |
868 if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); | |
869 if (!layer) return false; | |
870 return layer->setDisplayExtents(dmin, dmax); | |
871 } | |
872 | |
873 void | |
874 Pane::mousePressEvent(QMouseEvent *e) | |
875 { | |
876 if (e->buttons() & Qt::RightButton) { | |
877 emit contextHelpChanged(""); | |
878 emit rightButtonMenuRequested(mapToGlobal(e->pos())); | |
879 return; | |
880 } | |
881 | |
882 m_clickPos = e->pos(); | |
883 m_clickedInRange = true; | |
884 m_editingSelection = Selection(); | |
885 m_editingSelectionEdge = 0; | |
886 m_shiftPressed = (e->modifiers() & Qt::ShiftModifier); | |
887 m_ctrlPressed = (e->modifiers() & Qt::ControlModifier); | |
888 m_dragMode = UnresolvedDrag; | |
889 | |
890 ViewManager::ToolMode mode = ViewManager::NavigateMode; | |
891 if (m_manager) mode = m_manager->getToolMode(); | |
892 | |
893 m_navigating = false; | |
894 | |
895 if (mode == ViewManager::NavigateMode || (e->buttons() & Qt::MidButton)) { | |
896 | |
897 if (mode != ViewManager::NavigateMode) { | |
898 setCursor(Qt::PointingHandCursor); | |
899 } | |
900 | |
901 m_navigating = true; | |
902 m_dragCentreFrame = m_centreFrame; | |
903 m_dragStartMinValue = 0; | |
904 | |
905 float vmin, vmax, dmin, dmax; | |
906 if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) { | |
907 m_dragStartMinValue = dmin; | |
908 } | |
909 | |
910 } else if (mode == ViewManager::SelectMode) { | |
911 | |
912 if (!hasTopLayerTimeXAxis()) return; | |
913 | |
914 bool closeToLeft = false, closeToRight = false; | |
915 Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight); | |
916 | |
917 if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { | |
918 | |
919 m_manager->removeSelection(selection); | |
920 | |
921 if (closeToLeft) { | |
922 m_selectionStartFrame = selection.getEndFrame(); | |
923 } else { | |
924 m_selectionStartFrame = selection.getStartFrame(); | |
925 } | |
926 | |
927 m_manager->setInProgressSelection(selection, false); | |
928 m_resizing = true; | |
929 | |
930 } else { | |
931 | |
932 int mouseFrame = getFrameForX(e->x()); | |
933 size_t resolution = 1; | |
934 int snapFrame = mouseFrame; | |
935 | |
936 Layer *layer = getSelectedLayer(); | |
937 if (layer && !m_shiftPressed) { | |
938 layer->snapToFeatureFrame(this, snapFrame, | |
939 resolution, Layer::SnapLeft); | |
940 } | |
941 | |
942 if (snapFrame < 0) snapFrame = 0; | |
943 m_selectionStartFrame = snapFrame; | |
944 if (m_manager) { | |
945 m_manager->setInProgressSelection(Selection(snapFrame, | |
946 snapFrame + resolution), | |
947 !m_ctrlPressed); | |
948 } | |
949 | |
950 m_resizing = false; | |
951 } | |
952 | |
953 update(); | |
954 | |
955 } else if (mode == ViewManager::DrawMode) { | |
956 | |
957 Layer *layer = getSelectedLayer(); | |
958 if (layer && layer->isLayerEditable()) { | |
959 layer->drawStart(this, e); | |
960 } | |
961 | |
962 } else if (mode == ViewManager::EditMode) { | |
963 | |
964 if (!editSelectionStart(e)) { | |
965 Layer *layer = getSelectedLayer(); | |
966 if (layer && layer->isLayerEditable()) { | |
967 layer->editStart(this, e); | |
968 } | |
969 } | |
970 } | |
971 | |
972 emit paneInteractedWith(); | |
973 } | |
974 | |
975 void | |
976 Pane::mouseReleaseEvent(QMouseEvent *e) | |
977 { | |
978 if (e->buttons() & Qt::RightButton) { | |
979 return; | |
980 } | |
981 | |
982 ViewManager::ToolMode mode = ViewManager::NavigateMode; | |
983 if (m_manager) mode = m_manager->getToolMode(); | |
984 | |
985 if (m_clickedInRange) { | |
986 mouseMoveEvent(e); | |
987 } | |
988 | |
989 if (m_navigating || mode == ViewManager::NavigateMode) { | |
990 | |
991 m_navigating = false; | |
992 | |
993 if (mode != ViewManager::NavigateMode) { | |
994 // restore cursor | |
995 toolModeChanged(); | |
996 } | |
997 | |
998 if (m_shiftPressed) { | |
999 | |
1000 int x0 = min(m_clickPos.x(), m_mousePos.x()); | |
1001 int x1 = max(m_clickPos.x(), m_mousePos.x()); | |
1002 | |
1003 int y0 = min(m_clickPos.y(), m_mousePos.y()); | |
1004 int y1 = max(m_clickPos.y(), m_mousePos.y()); | |
1005 | |
1006 zoomToRegion(x0, y0, x1, y1); | |
1007 } | |
1008 | |
1009 } else if (mode == ViewManager::SelectMode) { | |
1010 | |
1011 if (!hasTopLayerTimeXAxis()) return; | |
1012 | |
1013 if (m_manager && m_manager->haveInProgressSelection()) { | |
1014 | |
1015 bool exclusive; | |
1016 Selection selection = m_manager->getInProgressSelection(exclusive); | |
1017 | |
1018 if (selection.getEndFrame() < selection.getStartFrame() + 2) { | |
1019 selection = Selection(); | |
1020 } | |
1021 | |
1022 m_manager->clearInProgressSelection(); | |
1023 | |
1024 if (exclusive) { | |
1025 m_manager->setSelection(selection); | |
1026 } else { | |
1027 m_manager->addSelection(selection); | |
1028 } | |
1029 } | |
1030 | |
1031 update(); | |
1032 | |
1033 } else if (mode == ViewManager::DrawMode) { | |
1034 | |
1035 Layer *layer = getSelectedLayer(); | |
1036 if (layer && layer->isLayerEditable()) { | |
1037 layer->drawEnd(this, e); | |
1038 update(); | |
1039 } | |
1040 | |
1041 } else if (mode == ViewManager::EditMode) { | |
1042 | |
1043 if (!editSelectionEnd(e)) { | |
1044 Layer *layer = getSelectedLayer(); | |
1045 if (layer && layer->isLayerEditable()) { | |
1046 layer->editEnd(this, e); | |
1047 update(); | |
1048 } | |
1049 } | |
1050 } | |
1051 | |
1052 m_clickedInRange = false; | |
1053 | |
1054 emit paneInteractedWith(); | |
1055 } | |
1056 | |
1057 void | |
1058 Pane::mouseMoveEvent(QMouseEvent *e) | |
1059 { | |
1060 if (e->buttons() & Qt::RightButton) { | |
1061 return; | |
1062 } | |
1063 | |
1064 updateContextHelp(&e->pos()); | |
1065 | |
1066 ViewManager::ToolMode mode = ViewManager::NavigateMode; | |
1067 if (m_manager) mode = m_manager->getToolMode(); | |
1068 | |
1069 QPoint prevPoint = m_identifyPoint; | |
1070 m_identifyPoint = e->pos(); | |
1071 | |
1072 if (!m_clickedInRange) { | |
1073 | |
1074 if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) { | |
1075 bool closeToLeft = false, closeToRight = false; | |
1076 getSelectionAt(e->x(), closeToLeft, closeToRight); | |
1077 if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { | |
1078 setCursor(Qt::SizeHorCursor); | |
1079 } else { | |
1080 setCursor(Qt::ArrowCursor); | |
1081 } | |
1082 } | |
1083 | |
1084 if (!m_manager->isPlaying()) { | |
1085 | |
1086 if (getSelectedLayer()) { | |
1087 | |
1088 bool previouslyIdentifying = m_identifyFeatures; | |
1089 m_identifyFeatures = true; | |
1090 | |
1091 if (m_identifyFeatures != previouslyIdentifying || | |
1092 m_identifyPoint != prevPoint) { | |
1093 update(); | |
1094 } | |
1095 } | |
1096 } | |
1097 | |
1098 return; | |
1099 } | |
1100 | |
1101 if (m_navigating || mode == ViewManager::NavigateMode) { | |
1102 | |
1103 if (m_shiftPressed) { | |
1104 | |
1105 m_mousePos = e->pos(); | |
1106 update(); | |
1107 | |
1108 } else { | |
1109 | |
1110 dragTopLayer(e); | |
1111 } | |
1112 | |
1113 } else if (mode == ViewManager::SelectMode) { | |
1114 | |
1115 if (!hasTopLayerTimeXAxis()) return; | |
1116 | |
1117 dragExtendSelection(e); | |
1118 | |
1119 } else if (mode == ViewManager::DrawMode) { | |
1120 | |
1121 Layer *layer = getSelectedLayer(); | |
1122 if (layer && layer->isLayerEditable()) { | |
1123 layer->drawDrag(this, e); | |
1124 } | |
1125 | |
1126 } else if (mode == ViewManager::EditMode) { | |
1127 | |
1128 if (!editSelectionDrag(e)) { | |
1129 Layer *layer = getSelectedLayer(); | |
1130 if (layer && layer->isLayerEditable()) { | |
1131 layer->editDrag(this, e); | |
1132 } | |
1133 } | |
1134 } | |
1135 } | |
1136 | |
1137 void | |
1138 Pane::zoomToRegion(int x0, int y0, int x1, int y1) | |
1139 { | |
1140 int w = x1 - x0; | |
1141 | |
1142 long newStartFrame = getFrameForX(x0); | |
1143 | |
1144 long visibleFrames = getEndFrame() - getStartFrame(); | |
1145 if (newStartFrame <= -visibleFrames) { | |
1146 newStartFrame = -visibleFrames + 1; | |
1147 } | |
1148 | |
1149 if (newStartFrame >= long(getModelsEndFrame())) { | |
1150 newStartFrame = getModelsEndFrame() - 1; | |
1151 } | |
1152 | |
1153 float ratio = float(w) / float(width()); | |
1154 // std::cerr << "ratio: " << ratio << std::endl; | |
1155 size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio); | |
1156 if (newZoomLevel < 1) newZoomLevel = 1; | |
1157 | |
1158 // std::cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << std::endl; | |
1159 setZoomLevel(getZoomConstraintBlockSize(newZoomLevel)); | |
1160 setStartFrame(newStartFrame); | |
1161 | |
1162 QString unit; | |
1163 float min, max; | |
1164 bool log; | |
1165 Layer *layer = 0; | |
1166 for (LayerList::const_iterator i = m_layers.begin(); | |
1167 i != m_layers.end(); ++i) { | |
1168 if ((*i)->getValueExtents(min, max, log, unit) && | |
1169 (*i)->getDisplayExtents(min, max)) { | |
1170 layer = *i; | |
1171 break; | |
1172 } | |
1173 } | |
1174 | |
1175 if (layer) { | |
1176 if (log) { | |
1177 min = (min < 0.0) ? -log10f(-min) : (min == 0.0) ? 0.0 : log10f(min); | |
1178 max = (max < 0.0) ? -log10f(-max) : (max == 0.0) ? 0.0 : log10f(max); | |
1179 } | |
1180 float rmin = min + ((max - min) * (height() - y1)) / height(); | |
1181 float rmax = min + ((max - min) * (height() - y0)) / height(); | |
1182 std::cerr << "min: " << min << ", max: " << max << ", y0: " << y0 << ", y1: " << y1 << ", h: " << height() << ", rmin: " << rmin << ", rmax: " << rmax << std::endl; | |
1183 if (log) { | |
1184 rmin = powf(10, rmin); | |
1185 rmax = powf(10, rmax); | |
1186 } | |
1187 std::cerr << "finally: rmin: " << rmin << ", rmax: " << rmax << " " << unit.toStdString() << std::endl; | |
1188 | |
1189 layer->setDisplayExtents(rmin, rmax); | |
1190 updateVerticalPanner(); | |
1191 } | |
1192 } | |
1193 | |
1194 void | |
1195 Pane::dragTopLayer(QMouseEvent *e) | |
1196 { | |
1197 // We need to avoid making it too easy to drag both | |
1198 // horizontally and vertically, in the case where the | |
1199 // mouse is moved "mostly" in horizontal or vertical axis | |
1200 // with only a small variation in the other axis. This is | |
1201 // particularly important during playback (when we want to | |
1202 // avoid small horizontal motions) or in slow refresh | |
1203 // layers like spectrogram (when we want to avoid small | |
1204 // vertical motions). | |
1205 // | |
1206 // To this end we have horizontal and vertical thresholds | |
1207 // and a series of states: unresolved, horizontally or | |
1208 // vertically constrained, free. | |
1209 // | |
1210 // When the mouse first moves, we're unresolved: we | |
1211 // restrict ourselves to whichever direction seems safest, | |
1212 // until the mouse has passed a small threshold distance | |
1213 // from the click point. Then we lock in to one of the | |
1214 // constrained modes, based on which axis that distance | |
1215 // was measured in first. Finally, if it turns out we've | |
1216 // also moved more than a certain larger distance in the | |
1217 // other direction as well, we may switch into free mode. | |
1218 // | |
1219 // If the top layer is incapable of being dragged | |
1220 // vertically, the logic is short circuited. | |
1221 | |
1222 int xdiff = e->x() - m_clickPos.x(); | |
1223 int ydiff = e->y() - m_clickPos.y(); | |
1224 int smallThreshold = 10, bigThreshold = 50; | |
1225 | |
1226 bool canMoveVertical = canTopLayerMoveVertical(); | |
1227 bool canMoveHorizontal = true; | |
1228 | |
1229 if (!canMoveHorizontal) { | |
1230 m_dragMode = HorizontalDrag; | |
1231 } | |
1232 | |
1233 if (m_dragMode == UnresolvedDrag) { | |
1234 | |
1235 if (abs(ydiff) > smallThreshold && | |
1236 abs(ydiff) > abs(xdiff) * 2) { | |
1237 m_dragMode = VerticalDrag; | |
1238 } else if (abs(xdiff) > smallThreshold && | |
1239 abs(xdiff) > abs(ydiff) * 2) { | |
1240 m_dragMode = HorizontalDrag; | |
1241 } else if (abs(xdiff) > smallThreshold && | |
1242 abs(ydiff) > smallThreshold) { | |
1243 m_dragMode = FreeDrag; | |
1244 } else { | |
1245 // When playing, we don't want to disturb the play | |
1246 // position too easily; when not playing, we don't | |
1247 // want to move up/down too easily | |
1248 if (m_manager && m_manager->isPlaying()) { | |
1249 canMoveHorizontal = false; | |
1250 } else { | |
1251 canMoveVertical = false; | |
1252 } | |
1253 } | |
1254 } | |
1255 | |
1256 if (m_dragMode == VerticalDrag) { | |
1257 if (abs(xdiff) > bigThreshold) m_dragMode = FreeDrag; | |
1258 else canMoveHorizontal = false; | |
1259 } | |
1260 | |
1261 if (m_dragMode == HorizontalDrag && canMoveVertical) { | |
1262 if (abs(ydiff) > bigThreshold) m_dragMode = FreeDrag; | |
1263 else canMoveVertical = false; | |
1264 } | |
1265 | |
1266 if (canMoveHorizontal) { | |
1267 | |
1268 long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x()); | |
1269 | |
1270 size_t newCentreFrame = m_dragCentreFrame; | |
1271 | |
1272 if (frameOff < 0) { | |
1273 newCentreFrame -= frameOff; | |
1274 } else if (newCentreFrame >= size_t(frameOff)) { | |
1275 newCentreFrame -= frameOff; | |
1276 } else { | |
1277 newCentreFrame = 0; | |
1278 } | |
1279 | |
1280 if (newCentreFrame >= getModelsEndFrame()) { | |
1281 newCentreFrame = getModelsEndFrame(); | |
1282 if (newCentreFrame > 0) --newCentreFrame; | |
1283 } | |
1284 | |
1285 if (getXForFrame(m_centreFrame) != getXForFrame(newCentreFrame)) { | |
1286 setCentreFrame(newCentreFrame); | |
1287 } | |
1288 } | |
1289 | |
1290 if (canMoveVertical) { | |
1291 | |
1292 float vmin = 0.f, vmax = 0.f; | |
1293 float dmin = 0.f, dmax = 0.f; | |
1294 | |
1295 if (getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) { | |
1296 | |
1297 // std::cerr << "ydiff = " << ydiff << std::endl; | |
1298 | |
1299 float perpix = (dmax - dmin) / height(); | |
1300 float valdiff = ydiff * perpix; | |
1301 // std::cerr << "valdiff = " << valdiff << std::endl; | |
1302 | |
1303 float newmin = m_dragStartMinValue + valdiff; | |
1304 float newmax = m_dragStartMinValue + (dmax - dmin) + valdiff; | |
1305 if (newmin < vmin) { | |
1306 newmax += vmin - newmin; | |
1307 newmin += vmin - newmin; | |
1308 } | |
1309 if (newmax > vmax) { | |
1310 newmin -= newmax - vmax; | |
1311 newmax -= newmax - vmax; | |
1312 } | |
1313 // std::cerr << "(" << dmin << ", " << dmax << ") -> (" | |
1314 // << newmin << ", " << newmax << ") (drag start " << m_dragStartMinValue << ")" << std::endl; | |
1315 | |
1316 setTopLayerDisplayExtents(newmin, newmax); | |
1317 updateVerticalPanner(); | |
1318 } | |
1319 } | |
1320 } | |
1321 | |
1322 void | |
1323 Pane::dragExtendSelection(QMouseEvent *e) | |
1324 { | |
1325 int mouseFrame = getFrameForX(e->x()); | |
1326 size_t resolution = 1; | |
1327 int snapFrameLeft = mouseFrame; | |
1328 int snapFrameRight = mouseFrame; | |
1329 | |
1330 Layer *layer = getSelectedLayer(); | |
1331 if (layer && !m_shiftPressed) { | |
1332 layer->snapToFeatureFrame(this, snapFrameLeft, | |
1333 resolution, Layer::SnapLeft); | |
1334 layer->snapToFeatureFrame(this, snapFrameRight, | |
1335 resolution, Layer::SnapRight); | |
1336 } | |
1337 | |
1338 // std::cerr << "snap: frame = " << mouseFrame << ", start frame = " << m_selectionStartFrame << ", left = " << snapFrameLeft << ", right = " << snapFrameRight << std::endl; | |
1339 | |
1340 if (snapFrameLeft < 0) snapFrameLeft = 0; | |
1341 if (snapFrameRight < 0) snapFrameRight = 0; | |
1342 | |
1343 size_t min, max; | |
1344 | |
1345 if (m_selectionStartFrame > size_t(snapFrameLeft)) { | |
1346 min = snapFrameLeft; | |
1347 max = m_selectionStartFrame; | |
1348 } else if (size_t(snapFrameRight) > m_selectionStartFrame) { | |
1349 min = m_selectionStartFrame; | |
1350 max = snapFrameRight; | |
1351 } else { | |
1352 min = snapFrameLeft; | |
1353 max = snapFrameRight; | |
1354 } | |
1355 | |
1356 if (m_manager) { | |
1357 m_manager->setInProgressSelection(Selection(min, max), | |
1358 !m_resizing && !m_ctrlPressed); | |
1359 } | |
1360 | |
1361 bool doScroll = false; | |
1362 if (!m_manager) doScroll = true; | |
1363 if (!m_manager->isPlaying()) doScroll = true; | |
1364 if (m_followPlay != PlaybackScrollContinuous) doScroll = true; | |
1365 | |
1366 if (doScroll) { | |
1367 int offset = mouseFrame - getStartFrame(); | |
1368 int available = getEndFrame() - getStartFrame(); | |
1369 if (offset >= available * 0.95) { | |
1370 int move = int(offset - available * 0.95) + 1; | |
1371 setCentreFrame(m_centreFrame + move); | |
1372 } else if (offset <= available * 0.10) { | |
1373 int move = int(available * 0.10 - offset) + 1; | |
1374 if (move < 0) { | |
1375 setCentreFrame(m_centreFrame + (-move)); | |
1376 } else if (m_centreFrame > move) { | |
1377 setCentreFrame(m_centreFrame - move); | |
1378 } else { | |
1379 setCentreFrame(0); | |
1380 } | |
1381 } | |
1382 } | |
1383 | |
1384 update(); | |
1385 } | |
1386 | |
1387 void | |
1388 Pane::mouseDoubleClickEvent(QMouseEvent *e) | |
1389 { | |
1390 if (e->buttons() & Qt::RightButton) { | |
1391 return; | |
1392 } | |
1393 | |
1394 // std::cerr << "mouseDoubleClickEvent" << std::endl; | |
1395 | |
1396 m_clickPos = e->pos(); | |
1397 m_clickedInRange = true; | |
1398 m_shiftPressed = (e->modifiers() & Qt::ShiftModifier); | |
1399 m_ctrlPressed = (e->modifiers() & Qt::ControlModifier); | |
1400 | |
1401 ViewManager::ToolMode mode = ViewManager::NavigateMode; | |
1402 if (m_manager) mode = m_manager->getToolMode(); | |
1403 | |
1404 if (mode == ViewManager::NavigateMode || | |
1405 mode == ViewManager::EditMode) { | |
1406 | |
1407 Layer *layer = getSelectedLayer(); | |
1408 if (layer && layer->isLayerEditable()) { | |
1409 layer->editOpen(this, e); | |
1410 } | |
1411 } | |
1412 } | |
1413 | |
1414 void | |
1415 Pane::leaveEvent(QEvent *) | |
1416 { | |
1417 bool previouslyIdentifying = m_identifyFeatures; | |
1418 m_identifyFeatures = false; | |
1419 if (previouslyIdentifying) update(); | |
1420 emit contextHelpChanged(""); | |
1421 } | |
1422 | |
1423 void | |
1424 Pane::resizeEvent(QResizeEvent *) | |
1425 { | |
1426 updateHeadsUpDisplay(); | |
1427 } | |
1428 | |
1429 void | |
1430 Pane::wheelEvent(QWheelEvent *e) | |
1431 { | |
1432 //std::cerr << "wheelEvent, delta " << e->delta() << std::endl; | |
1433 | |
1434 int count = e->delta(); | |
1435 | |
1436 if (count > 0) { | |
1437 if (count >= 120) count /= 120; | |
1438 else count = 1; | |
1439 } | |
1440 | |
1441 if (count < 0) { | |
1442 if (count <= -120) count /= 120; | |
1443 else count = -1; | |
1444 } | |
1445 | |
1446 if (e->modifiers() & Qt::ControlModifier) { | |
1447 | |
1448 // Scroll left or right, rapidly | |
1449 | |
1450 if (getStartFrame() < 0 && | |
1451 getEndFrame() >= getModelsEndFrame()) return; | |
1452 | |
1453 long delta = ((width() / 2) * count * m_zoomLevel); | |
1454 | |
1455 if (int(m_centreFrame) < delta) { | |
1456 setCentreFrame(0); | |
1457 } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) { | |
1458 setCentreFrame(getModelsEndFrame()); | |
1459 } else { | |
1460 setCentreFrame(m_centreFrame - delta); | |
1461 } | |
1462 | |
1463 } else { | |
1464 | |
1465 // Zoom in or out | |
1466 | |
1467 int newZoomLevel = m_zoomLevel; | |
1468 | |
1469 while (count > 0) { | |
1470 if (newZoomLevel <= 2) { | |
1471 newZoomLevel = 1; | |
1472 break; | |
1473 } | |
1474 newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, | |
1475 ZoomConstraint::RoundDown); | |
1476 --count; | |
1477 } | |
1478 | |
1479 while (count < 0) { | |
1480 newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, | |
1481 ZoomConstraint::RoundUp); | |
1482 ++count; | |
1483 } | |
1484 | |
1485 if (newZoomLevel != m_zoomLevel) { | |
1486 setZoomLevel(newZoomLevel); | |
1487 } | |
1488 } | |
1489 | |
1490 emit paneInteractedWith(); | |
1491 } | |
1492 | |
1493 void | |
1494 Pane::horizontalThumbwheelMoved(int value) | |
1495 { | |
1496 //!!! dupe with updateHeadsUpDisplay | |
1497 | |
1498 int count = 0; | |
1499 int level = 1; | |
1500 | |
1501 | |
1502 //!!! pull out into function (presumably in View) | |
1503 bool haveConstraint = false; | |
1504 for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); | |
1505 ++i) { | |
1506 if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) { | |
1507 haveConstraint = true; | |
1508 break; | |
1509 } | |
1510 } | |
1511 | |
1512 if (haveConstraint) { | |
1513 while (true) { | |
1514 if (m_hthumb->getMaximumValue() - value == count) break; | |
1515 int newLevel = getZoomConstraintBlockSize(level + 1, | |
1516 ZoomConstraint::RoundUp); | |
1517 if (newLevel == level) break; | |
1518 level = newLevel; | |
1519 if (++count == 50) break; | |
1520 } | |
1521 } else { | |
1522 while (true) { | |
1523 if (m_hthumb->getMaximumValue() - value == count) break; | |
1524 int step = level / 10; | |
1525 int pwr = 0; | |
1526 while (step > 0) { | |
1527 ++pwr; | |
1528 step /= 2; | |
1529 } | |
1530 step = 1; | |
1531 while (pwr > 0) { | |
1532 step *= 2; | |
1533 --pwr; | |
1534 } | |
1535 // std::cerr << level << std::endl; | |
1536 level += step; | |
1537 if (++count == 100 || level > 262144) break; | |
1538 } | |
1539 } | |
1540 | |
1541 // std::cerr << "new level is " << level << std::endl; | |
1542 setZoomLevel(level); | |
1543 } | |
1544 | |
1545 void | |
1546 Pane::verticalThumbwheelMoved(int value) | |
1547 { | |
1548 Layer *layer = 0; | |
1549 if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1); | |
1550 if (layer) { | |
1551 int defaultStep = 0; | |
1552 int max = layer->getVerticalZoomSteps(defaultStep); | |
1553 if (max == 0) { | |
1554 updateHeadsUpDisplay(); | |
1555 return; | |
1556 } | |
1557 if (value > max) { | |
1558 value = max; | |
1559 } | |
1560 layer->setVerticalZoomStep(value); | |
1561 updateVerticalPanner(); | |
1562 } | |
1563 } | |
1564 | |
1565 void | |
1566 Pane::verticalPannerMoved(float x0, float y0, float w, float h) | |
1567 { | |
1568 float vmin, vmax, dmin, dmax; | |
1569 if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax)) return; | |
1570 float y1 = y0 + h; | |
1571 float newmax = vmin + ((1.0 - y0) * (vmax - vmin)); | |
1572 float newmin = vmin + ((1.0 - y1) * (vmax - vmin)); | |
1573 std::cerr << "verticalPannerMoved: (" << x0 << "," << y0 << "," << w | |
1574 << "," << h << ") -> (" << newmin << "," << newmax << ")" << std::endl; | |
1575 setTopLayerDisplayExtents(newmin, newmax); | |
1576 } | |
1577 | |
1578 void | |
1579 Pane::editVerticalPannerExtents() | |
1580 { | |
1581 if (!m_vpan || !m_manager || !m_manager->getZoomWheelsEnabled()) return; | |
1582 | |
1583 float vmin, vmax, dmin, dmax; | |
1584 QString unit; | |
1585 if (!getTopLayerDisplayExtents(vmin, vmax, dmin, dmax, &unit) | |
1586 || vmax == vmin) { | |
1587 return; | |
1588 } | |
1589 | |
1590 RangeInputDialog dialog(tr("Enter new range"), | |
1591 tr("New vertical display range, from %1 to %2 %4:") | |
1592 .arg(vmin).arg(vmax).arg(unit), | |
1593 unit, vmin, vmax, this); | |
1594 dialog.setRange(dmin, dmax); | |
1595 | |
1596 if (dialog.exec() == QDialog::Accepted) { | |
1597 dialog.getRange(dmin, dmax); | |
1598 setTopLayerDisplayExtents(dmin, dmax); | |
1599 updateVerticalPanner(); | |
1600 } | |
1601 } | |
1602 | |
1603 bool | |
1604 Pane::editSelectionStart(QMouseEvent *e) | |
1605 { | |
1606 if (!m_identifyFeatures || | |
1607 !m_manager || | |
1608 m_manager->getToolMode() != ViewManager::EditMode) { | |
1609 return false; | |
1610 } | |
1611 | |
1612 bool closeToLeft, closeToRight; | |
1613 Selection s(getSelectionAt(e->x(), closeToLeft, closeToRight)); | |
1614 if (s.isEmpty()) return false; | |
1615 m_editingSelection = s; | |
1616 m_editingSelectionEdge = (closeToLeft ? -1 : closeToRight ? 1 : 0); | |
1617 m_mousePos = e->pos(); | |
1618 return true; | |
1619 } | |
1620 | |
1621 bool | |
1622 Pane::editSelectionDrag(QMouseEvent *e) | |
1623 { | |
1624 if (m_editingSelection.isEmpty()) return false; | |
1625 m_mousePos = e->pos(); | |
1626 update(); | |
1627 return true; | |
1628 } | |
1629 | |
1630 bool | |
1631 Pane::editSelectionEnd(QMouseEvent *) | |
1632 { | |
1633 if (m_editingSelection.isEmpty()) return false; | |
1634 | |
1635 int offset = m_mousePos.x() - m_clickPos.x(); | |
1636 Layer *layer = getSelectedLayer(); | |
1637 | |
1638 if (offset == 0 || !layer) { | |
1639 m_editingSelection = Selection(); | |
1640 return true; | |
1641 } | |
1642 | |
1643 int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset; | |
1644 int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset; | |
1645 | |
1646 long f0 = getFrameForX(p0); | |
1647 long f1 = getFrameForX(p1); | |
1648 | |
1649 Selection newSelection(f0, f1); | |
1650 | |
1651 if (m_editingSelectionEdge == 0) { | |
1652 | |
1653 CommandHistory::getInstance()->startCompoundOperation | |
1654 (tr("Drag Selection"), true); | |
1655 | |
1656 layer->moveSelection(m_editingSelection, f0); | |
1657 | |
1658 } else { | |
1659 | |
1660 CommandHistory::getInstance()->startCompoundOperation | |
1661 (tr("Resize Selection"), true); | |
1662 | |
1663 if (m_editingSelectionEdge < 0) { | |
1664 f1 = m_editingSelection.getEndFrame(); | |
1665 } else { | |
1666 f0 = m_editingSelection.getStartFrame(); | |
1667 } | |
1668 | |
1669 newSelection = Selection(f0, f1); | |
1670 layer->resizeSelection(m_editingSelection, newSelection); | |
1671 } | |
1672 | |
1673 m_manager->removeSelection(m_editingSelection); | |
1674 m_manager->addSelection(newSelection); | |
1675 | |
1676 CommandHistory::getInstance()->endCompoundOperation(); | |
1677 | |
1678 m_editingSelection = Selection(); | |
1679 return true; | |
1680 } | |
1681 | |
1682 void | |
1683 Pane::toolModeChanged() | |
1684 { | |
1685 ViewManager::ToolMode mode = m_manager->getToolMode(); | |
1686 // std::cerr << "Pane::toolModeChanged(" << mode << ")" << std::endl; | |
1687 | |
1688 switch (mode) { | |
1689 | |
1690 case ViewManager::NavigateMode: | |
1691 setCursor(Qt::PointingHandCursor); | |
1692 break; | |
1693 | |
1694 case ViewManager::SelectMode: | |
1695 setCursor(Qt::ArrowCursor); | |
1696 break; | |
1697 | |
1698 case ViewManager::EditMode: | |
1699 setCursor(Qt::UpArrowCursor); | |
1700 break; | |
1701 | |
1702 case ViewManager::DrawMode: | |
1703 setCursor(Qt::CrossCursor); | |
1704 break; | |
1705 /* | |
1706 case ViewManager::TextMode: | |
1707 setCursor(Qt::IBeamCursor); | |
1708 break; | |
1709 */ | |
1710 } | |
1711 } | |
1712 | |
1713 void | |
1714 Pane::zoomWheelsEnabledChanged() | |
1715 { | |
1716 updateHeadsUpDisplay(); | |
1717 update(); | |
1718 } | |
1719 | |
1720 void | |
1721 Pane::viewZoomLevelChanged(View *v, unsigned long z, bool locked) | |
1722 { | |
1723 // std::cerr << "Pane[" << this << "]::zoomLevelChanged (global now " | |
1724 // << (m_manager ? m_manager->getGlobalZoom() : 0) << ")" << std::endl; | |
1725 | |
1726 View::viewZoomLevelChanged(v, z, locked); | |
1727 | |
1728 if (m_hthumb && !m_hthumb->isVisible()) return; | |
1729 | |
1730 if (v != this) { | |
1731 if (!locked || !m_followZoom) return; | |
1732 } | |
1733 | |
1734 if (m_manager && m_manager->getZoomWheelsEnabled()) { | |
1735 updateHeadsUpDisplay(); | |
1736 } | |
1737 } | |
1738 | |
1739 void | |
1740 Pane::propertyContainerSelected(View *v, PropertyContainer *pc) | |
1741 { | |
1742 Layer *layer = 0; | |
1743 | |
1744 if (getLayerCount() > 0) { | |
1745 layer = getLayer(getLayerCount() - 1); | |
1746 disconnect(layer, SIGNAL(verticalZoomChanged()), | |
1747 this, SLOT(verticalZoomChanged())); | |
1748 } | |
1749 | |
1750 View::propertyContainerSelected(v, pc); | |
1751 updateHeadsUpDisplay(); | |
1752 | |
1753 if (m_vthumb) { | |
1754 RangeMapper *rm = 0; | |
1755 if (layer) rm = layer->getNewVerticalZoomRangeMapper(); | |
1756 if (rm) m_vthumb->setRangeMapper(rm); | |
1757 } | |
1758 | |
1759 if (getLayerCount() > 0) { | |
1760 layer = getLayer(getLayerCount() - 1); | |
1761 connect(layer, SIGNAL(verticalZoomChanged()), | |
1762 this, SLOT(verticalZoomChanged())); | |
1763 } | |
1764 } | |
1765 | |
1766 void | |
1767 Pane::verticalZoomChanged() | |
1768 { | |
1769 Layer *layer = 0; | |
1770 | |
1771 if (getLayerCount() > 0) { | |
1772 | |
1773 layer = getLayer(getLayerCount() - 1); | |
1774 | |
1775 if (m_vthumb && m_vthumb->isVisible()) { | |
1776 m_vthumb->setValue(layer->getCurrentVerticalZoomStep()); | |
1777 } | |
1778 } | |
1779 } | |
1780 | |
1781 void | |
1782 Pane::updateContextHelp(const QPoint *pos) | |
1783 { | |
1784 QString help = ""; | |
1785 | |
1786 if (m_clickedInRange) { | |
1787 emit contextHelpChanged(""); | |
1788 return; | |
1789 } | |
1790 | |
1791 ViewManager::ToolMode mode = ViewManager::NavigateMode; | |
1792 if (m_manager) mode = m_manager->getToolMode(); | |
1793 | |
1794 bool editable = false; | |
1795 Layer *layer = getSelectedLayer(); | |
1796 if (layer && layer->isLayerEditable()) { | |
1797 editable = true; | |
1798 } | |
1799 | |
1800 if (mode == ViewManager::NavigateMode) { | |
1801 | |
1802 help = tr("Click and drag to navigate"); | |
1803 | |
1804 } else if (mode == ViewManager::SelectMode) { | |
1805 | |
1806 if (!hasTopLayerTimeXAxis()) return; | |
1807 | |
1808 bool haveSelection = (m_manager && !m_manager->getSelections().empty()); | |
1809 | |
1810 if (haveSelection) { | |
1811 if (editable) { | |
1812 help = tr("Click and drag to select a range; hold Shift to avoid snapping to items; hold Ctrl for multi-select; middle-click and drag to navigate"); | |
1813 } else { | |
1814 help = tr("Click and drag to select a range; hold Ctrl for multi-select; middle-click and drag to navigate"); | |
1815 } | |
1816 | |
1817 if (pos) { | |
1818 bool closeToLeft = false, closeToRight = false; | |
1819 Selection selection = getSelectionAt(pos->x(), closeToLeft, closeToRight); | |
1820 if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) { | |
1821 | |
1822 help = tr("Click and drag to move the selection boundary"); | |
1823 } | |
1824 } | |
1825 } else { | |
1826 if (editable) { | |
1827 help = tr("Click and drag to select a range; hold Shift to avoid snapping to items; middle-click to navigate"); | |
1828 } else { | |
1829 help = tr("Click and drag to select a range; middle-click and drag to navigate"); | |
1830 } | |
1831 } | |
1832 | |
1833 } else if (mode == ViewManager::DrawMode) { | |
1834 | |
1835 //!!! could call through to a layer function to find out exact meaning | |
1836 if (editable) { | |
1837 help = tr("Click to add a new item in the active layer"); | |
1838 } | |
1839 | |
1840 } else if (mode == ViewManager::EditMode) { | |
1841 | |
1842 //!!! could call through to layer | |
1843 if (editable) { | |
1844 help = tr("Click and drag an item in the active layer to move it"); | |
1845 if (pos) { | |
1846 bool closeToLeft = false, closeToRight = false; | |
1847 Selection selection = getSelectionAt(pos->x(), closeToLeft, closeToRight); | |
1848 if (!selection.isEmpty()) { | |
1849 help = tr("Click and drag to move all items in the selected range"); | |
1850 } | |
1851 } | |
1852 } | |
1853 } | |
1854 | |
1855 emit contextHelpChanged(help); | |
1856 } | |
1857 | |
1858 void | |
1859 Pane::mouseEnteredWidget() | |
1860 { | |
1861 QWidget *w = dynamic_cast<QWidget *>(sender()); | |
1862 if (!w) return; | |
1863 | |
1864 if (w == m_vpan) { | |
1865 emit contextHelpChanged(tr("Click and drag to adjust the visible range of the vertical scale")); | |
1866 } else if (w == m_vthumb) { | |
1867 emit contextHelpChanged(tr("Click and drag to adjust the vertical zoom level")); | |
1868 } else if (w == m_hthumb) { | |
1869 emit contextHelpChanged(tr("Click and drag to adjust the horizontal zoom level")); | |
1870 } else if (w == m_reset) { | |
1871 emit contextHelpChanged(tr("Reset horizontal and vertical zoom levels to their defaults")); | |
1872 } | |
1873 } | |
1874 | |
1875 void | |
1876 Pane::mouseLeftWidget() | |
1877 { | |
1878 emit contextHelpChanged(""); | |
1879 } | |
1880 | |
1881 QString | |
1882 Pane::toXmlString(QString indent, QString extraAttributes) const | |
1883 { | |
1884 return View::toXmlString | |
1885 (indent, | |
1886 QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3") | |
1887 .arg(m_centreLineVisible).arg(height()).arg(extraAttributes)); | |
1888 } | |
1889 | |
1890 |