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