1 /*
2 Copyright (C) 2005-2007 Remon Sijrier
3
4 This file is part of Traverso
5
6 Traverso is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 */
21
22 #include "TimeLineView.h"
23
24 #include <QPainter>
25
26 #include "Themer.h"
27 #include "SheetView.h"
28 #include "MarkerView.h"
29 #include "TimeLineViewPort.h"
30 #include "SnapList.h"
31
32 #include <ProjectManager.h>
33 #include <Project.h>
34 #include <Sheet.h>
35 #include <TimeLine.h>
36 #include <Marker.h>
37 #include <ContextPointer.h>
38 #include <Utils.h>
39 #include <defines.h>
40 #include <AddRemove.h>
41 #include <CommandGroup.h>
42 #include "Information.h"
43 #include "InputEngine.h"
44 #include <cstdlib>
45
46 #include <QFont>
47 #include <QDebug>
48
49 // Always put me below _all_ includes, this is needed
50 // in case we run with memory leak detection enabled!
51 #include "Debugger.h"
52
53
54 #define MARKER_SOFT_SELECTION_DISTANCE 50
55
56
DragMarker(MarkerView * mview,qint64 scalefactor,const QString & des)57 DragMarker::DragMarker(MarkerView* mview, qint64 scalefactor, const QString& des)
58 : Command(mview->get_marker(), des)
59 {
60 d = new Data;
61 d->view = mview;
62 m_marker= d->view->get_marker();
63 d->scalefactor = scalefactor;
64 d->bypassjog = false;
65 }
66
prepare_actions()67 int DragMarker::prepare_actions()
68 {
69 return 1;
70 }
71
begin_hold()72 int DragMarker::begin_hold()
73 {
74 m_origWhen = m_newWhen = m_marker->get_when();
75 m_marker->set_snappable(false);
76 d->view->get_sheetview()->start_shuttle(true, true);
77 d->view->set_dragging(true);
78 return 1;
79 }
80
finish_hold()81 int DragMarker::finish_hold()
82 {
83 d->view->get_sheetview()->start_shuttle(false);
84 d->view->set_dragging(false);
85 delete d;
86
87 return 1;
88 }
89
do_action()90 int DragMarker::do_action()
91 {
92 m_marker->set_when(m_newWhen);
93 m_marker->set_snappable(true);
94 return 1;
95 }
96
undo_action()97 int DragMarker::undo_action()
98 {
99 m_marker->set_when(m_origWhen);
100 m_marker->set_snappable(true);
101 return 1;
102 }
103
cancel_action()104 void DragMarker::cancel_action()
105 {
106 finish_hold();
107 undo_action();
108 }
109
move_left(bool)110 void DragMarker::move_left(bool )
111 {
112 d->bypassjog = true;
113 // Move 1 pixel to the left
114 TimeRef newpos = TimeRef(m_newWhen - d->scalefactor);
115 if (newpos < TimeRef()) {
116 newpos = TimeRef();
117 }
118 m_newWhen = newpos;
119 do_action();
120 }
121
move_right(bool)122 void DragMarker::move_right(bool )
123 {
124 d->bypassjog = true;
125 // Move 1 pixel to the right
126 m_newWhen = m_newWhen + d->scalefactor;
127 do_action();
128 }
129
jog()130 int DragMarker::jog()
131 {
132 if (d->bypassjog) {
133 int diff = d->jogBypassPos - cpointer().x();
134 if (abs(diff) > 15) {
135 d->bypassjog = false;
136 } else {
137 return 0;
138 }
139 }
140
141 d->jogBypassPos = cpointer().x();
142 TimeRef newpos = TimeRef(cpointer().scene_x() * d->scalefactor);
143
144 if (m_marker->get_timeline()->get_sheet()->is_snap_on()) {
145 SnapList* slist = m_marker->get_timeline()->get_sheet()->get_snap_list();
146 newpos = slist->get_snap_value(newpos);
147 }
148
149 if (newpos < TimeRef()) {
150 newpos = TimeRef();
151 }
152
153 m_newWhen = newpos;
154 d->view->set_position(int(m_newWhen / d->scalefactor));
155
156 d->view->get_sheetview()->update_shuttle_factor();
157
158 return 1;
159 }
160
161
162 // End DragMarker
163
164
165
TimeLineView(SheetView * view)166 TimeLineView::TimeLineView(SheetView* view)
167 : ViewItem(0, view->get_sheet()->get_timeline())
168 , m_blinkingMarker(0)
169 {
170 PENTERCONS2;
171
172 m_sv = view;
173 m_boundingRect = QRectF(0, 0, MAX_CANVAS_WIDTH, TIMELINE_HEIGHT);
174 m_timeline = m_sv->get_sheet()->get_timeline();
175
176 load_theme_data();
177
178 // Create MarkerViews for existing markers
179 foreach(Marker* marker, m_timeline->get_markers()) {
180 add_new_marker_view(marker);
181 }
182
183 // Make connections to the 'core'
184 connect(m_timeline, SIGNAL(markerAdded(Marker*)), this, SLOT(add_new_marker_view(Marker*)));
185 connect(m_timeline, SIGNAL(markerRemoved(Marker*)), this, SLOT(remove_marker_view(Marker*)));
186
187 setAcceptHoverEvents(true);
188
189 m_zooms[524288 * 640] = "20:00.000";
190 m_zooms[262144 * 640] = "10:00.000";
191 m_zooms[131072 * 640] = "5:00.000";
192 m_zooms[ 65536 * 640] = "2:30.000";
193 m_zooms[ 32768 * 640] = "1:00.000";
194 m_zooms[ 16384 * 640] = "0:30.000";
195 m_zooms[ 8192 * 640] = "0:20.000";
196 m_zooms[ 4096 * 640] = "0:10.000";
197 m_zooms[ 2048 * 640] = "0:05.000";
198 m_zooms[ 1024 * 640] = "0:02.000";
199 m_zooms[ 512 * 640] = "0:01.000";
200 m_zooms[ 256 * 640] = "0:00.800";
201 m_zooms[ 128 * 640] = "0:00.400";
202 m_zooms[ 64 * 640] = "0:00.200";
203 m_zooms[ 32 * 640] = "0:00.100";
204 m_zooms[ 16 * 640] = "0:00.050";
205 m_zooms[ 8 * 640] = "0:00.020";
206 m_zooms[ 4 * 640] = "0:00.010";
207 m_zooms[ 2 * 640] = "0:00.005";
208 m_zooms[ 1 * 640] = "0:00.002";
209 }
210
211
~TimeLineView()212 TimeLineView::~TimeLineView()
213 {
214 PENTERDES;
215 }
216
217
hzoom_changed()218 void TimeLineView::hzoom_changed( )
219 {
220 update();
221 }
222
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)223 void TimeLineView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
224 {
225 PENTER3;
226 Q_UNUSED(widget);
227
228 painter->save();
229
230 // When the scrollarea moves by a small value, the text
231 // can be screwed up, so give it some room, 100 pixels should do!
232 int xstart = (int) option->exposedRect.x() - 100;
233 int pixelcount = (int) option->exposedRect.width() + 100;
234 int expheight = (int) option->exposedRect.height();
235 int top = (int) option->exposedRect.top();
236 bool paintText = top > 28 && expheight < 2 ? false : true;
237
238 if (xstart < 0) {
239 xstart = 0;
240 }
241
242 painter->setClipRect(m_boundingRect);
243
244 int height = TIMELINE_HEIGHT;
245
246 painter->fillRect(xstart, 0, pixelcount, height, themer()->get_color("Timeline:background") );
247
248 painter->setPen(themer()->get_color("Timeline:text"));
249 painter->setFont( themer()->get_font("Timeline:fontscale:label") );
250
251 TimeRef major;
252
253 if (m_zooms.contains(m_sv->timeref_scalefactor)) {
254 major = msms_to_timeref(m_zooms[m_sv->timeref_scalefactor]);
255 } else {
256 major = 120 * m_sv->timeref_scalefactor;
257 }
258
259 // minor is double so they line up right with the majors,
260 // despite not always being an even number of frames
261 // @Ben : is still still the same when using TimeRef based calculations?
262 double minor = double(major/double(10));
263
264 TimeRef firstLocation = TimeRef(xstart * m_sv->timeref_scalefactor);
265 TimeRef lastLocation = TimeRef(xstart * m_sv->timeref_scalefactor + pixelcount * m_sv->timeref_scalefactor);
266 int xstartoffset = m_sv->hscrollbar_value();
267
268 painter->setMatrixEnabled(false);
269
270 TimeRef factor = (firstLocation/major)*major;
271 // Draw minor ticks
272 TimeRef range((lastLocation-firstLocation+major) / minor);
273 for (qint64 i = 0; i < range.universal_frame(); i++ ) {
274 int x = (int)((factor + i * minor) / m_sv->timeref_scalefactor) - xstartoffset;
275 painter->drawLine(x, height - 5, x, height - 1);
276 }
277
278 // Draw major ticks
279 for (TimeRef location = factor; location < lastLocation; location += major) {
280 int x = int(location/m_sv->timeref_scalefactor - xstartoffset);
281 painter->drawLine(x, height - 13, x, height - 1);
282 if (paintText) {
283 painter->drawText(x + 4, height - 8, timeref_to_text(location, m_sv->timeref_scalefactor));
284 }
285 }
286
287 painter->restore();
288 }
289
calculate_bounding_rect()290 void TimeLineView::calculate_bounding_rect()
291 {
292 update();
293 ViewItem::calculate_bounding_rect();
294 }
295
296
add_new_marker_view(Marker * marker)297 void TimeLineView::add_new_marker_view(Marker * marker)
298 {
299 MarkerView* view = new MarkerView(marker, m_sv, this);
300 view->set_active(false);
301 m_markerViews.append(view);
302 view->update();
303 }
304
remove_marker_view(Marker * marker)305 void TimeLineView::remove_marker_view(Marker * marker)
306 {
307 foreach(MarkerView* view, m_markerViews) {
308 if (view->get_marker() == marker) {
309 m_markerViews.removeAll(view);
310 scene()->removeItem(view);
311 m_blinkingMarker = 0;
312 delete view;
313 return;
314 }
315 }
316 }
317
add_marker()318 Command* TimeLineView::add_marker()
319 {
320 QPointF point = mapFromScene(cpointer().scene_pos());
321
322 TimeRef when(point.x() * m_sv->timeref_scalefactor);
323
324 return add_marker_at(when);
325 }
326
add_marker_at_playhead()327 Command* TimeLineView::add_marker_at_playhead()
328 {
329 return add_marker_at(m_sv->get_sheet()->get_transport_location());
330 }
331
add_marker_at(const TimeRef when)332 Command* TimeLineView::add_marker_at(const TimeRef when)
333 {
334 CommandGroup* group = new CommandGroup(m_timeline, "");
335
336 // check if it is the first marker added to the timeline
337 if (!m_timeline->get_markers().size()) {
338 if (when > TimeRef()) { // add one at the beginning of the sheet
339 Marker* m = new Marker(m_timeline, TimeRef(), Marker::CDTRACK);
340 m->set_description("");
341 group->add_command(m_timeline->add_marker(m));
342 }
343
344 TimeRef lastlocation = m_sv->get_sheet()->get_last_location();
345 if (when < lastlocation) { // add one at the end of the sheet
346 Marker* me = new Marker(m_timeline, lastlocation, Marker::ENDMARKER);
347 me->set_description(tr("End"));
348 group->add_command(m_timeline->add_marker(me));
349 }
350 }
351
352 Marker* marker = new Marker(m_timeline, when, Marker::CDTRACK);
353 marker->set_description("");
354
355 group->setText(tr("Add Marker"));
356 group->add_command(m_timeline->add_marker(marker));
357
358 return group;
359 }
360
playhead_to_marker()361 Command* TimeLineView::playhead_to_marker()
362 {
363 update_softselected_marker(QPoint(cpointer().on_first_input_event_scene_x(), cpointer().on_first_input_event_scene_y()));
364
365 if (m_blinkingMarker) {
366 m_sv->get_sheet()->set_transport_pos(m_blinkingMarker->get_marker()->get_when());
367 return 0;
368 }
369
370 return ie().did_not_implement();
371 }
372
remove_marker()373 Command* TimeLineView::remove_marker()
374 {
375 if (m_blinkingMarker) {
376 Marker* marker = m_blinkingMarker->get_marker();
377 if (marker->get_type() == Marker::ENDMARKER && m_markerViews.size() > 1) {
378 info().information(tr("You have to remove all other markers first."));
379 return ie().failure();
380 }
381 return m_timeline->remove_marker(marker);
382 }
383
384 return 0;
385 }
386
update_softselected_marker(QPoint pos)387 void TimeLineView::update_softselected_marker(QPoint pos)
388 {
389 MarkerView* prevMarker = m_blinkingMarker;
390 if (m_markerViews.size()) {
391 m_blinkingMarker = m_markerViews.first();
392 }
393
394 if (! m_blinkingMarker) {
395 return;
396 }
397
398 int x = pos.x();
399 int blinkMarkerDist = abs(x - m_blinkingMarker->position());
400
401 foreach(MarkerView* markerView, m_markerViews) {
402 int markerDist = abs(x - markerView->position());
403
404 fflush(stdout);
405 if (markerDist < blinkMarkerDist) {
406 m_blinkingMarker = markerView;
407 blinkMarkerDist = abs(x - m_blinkingMarker->position());
408 }
409 }
410
411 if (blinkMarkerDist > MARKER_SOFT_SELECTION_DISTANCE) {
412 m_blinkingMarker = 0;
413 }
414
415 if (prevMarker && (prevMarker != m_blinkingMarker) ) {
416 prevMarker->set_active(false);
417 if (m_blinkingMarker) {
418 m_blinkingMarker->set_active(true);
419 }
420 }
421
422 if (!prevMarker && m_blinkingMarker) {
423 m_blinkingMarker->set_active(true);
424 }
425 }
426
hoverEnterEvent(QGraphicsSceneHoverEvent * event)427 void TimeLineView::hoverEnterEvent ( QGraphicsSceneHoverEvent * event )
428 {
429 Q_UNUSED(event);
430
431 if (m_blinkingMarker) {
432 m_blinkingMarker->set_active(true);
433 }
434 }
435
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)436 void TimeLineView::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event )
437 {
438 Q_UNUSED(event);
439
440 if (ie().is_holding()) {
441 event->ignore();
442 return;
443 }
444
445 if (m_blinkingMarker) {
446 // TODO add these functions, or something else to
447 // let the user know which marker is to be moved!
448 m_blinkingMarker->set_active(false);
449 m_blinkingMarker = 0;
450 }
451 }
452
453
hoverMoveEvent(QGraphicsSceneHoverEvent * event)454 void TimeLineView::hoverMoveEvent ( QGraphicsSceneHoverEvent * event )
455 {
456 QPoint pos((int)event->pos().x(), (int)event->pos().y());
457
458 update_softselected_marker(pos);
459 }
460
461
drag_marker()462 Command * TimeLineView::drag_marker()
463 {
464 update_softselected_marker(QPoint(cpointer().on_first_input_event_scene_x(), cpointer().on_first_input_event_scene_y()));
465
466 if (m_blinkingMarker) {
467 return new DragMarker(m_blinkingMarker, m_sv->timeref_scalefactor, tr("Drag Marker"));
468 }
469
470 return ie().did_not_implement();
471 }
472
clear_markers()473 Command * TimeLineView::clear_markers()
474 {
475 CommandGroup* group = new CommandGroup(m_timeline, tr("Clear Markers"));
476
477 foreach(Marker *m, m_timeline->get_markers()) {
478 group->add_command(m_timeline->remove_marker(m));
479 }
480
481 return group;
482 }
483
load_theme_data()484 void TimeLineView::load_theme_data()
485 {
486 // TODO Load pixmap, fonts, colors from themer() !!
487 calculate_bounding_rect();
488 }
489
490