1 /*
2 Copyright (C) 2005-2006 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 <QMouseEvent>
23 #include <QResizeEvent>
24 #include <QEvent>
25 #include <QRect>
26 #include <QPainter>
27 #include <QPixmap>
28 #include <QGraphicsScene>
29 #include <QGraphicsSceneMouseEvent>
30 #include <QEvent>
31 #include <QStyleOptionGraphicsItem>
32 #include <QApplication>
33 #include <Utils.h>
34 #include "InputEngine.h"
35 #include "Themer.h"
36 
37 #include "ViewPort.h"
38 #include "ContextPointer.h"
39 
40 #include "Import.h"
41 
42 #include <cstdio>
43 
44 // Always put me below _all_ includes, this is needed
45 // in case we run with memory leak detection enabled!
46 #include "Debugger.h"
47 
48 
49 /**
50  * \class ViewPort
51  * \brief An Interface class to create Contextual, or so called 'Soft Selection' enabled Widgets.
52 
53 	The ViewPort class inherits QGraphicsView, and thus is a true Canvas type of Widget.<br />
54 	Reimplement ViewPort to create a 'Soft Selection' enabled widget. You have to create <br />
55 	a QGraphicsScene object yourself, and set it as the scene the ViewPort visualizes.
56 
57 	ViewPort should be used to visualize 'core' data objects. This is done by creating a <br />
58 	ViewItem object for each core class that has to be visualized. The naming convention <br />
59 	for classes that inherit ViewItem is: core class name + View.<br />
60 	E.g. the ViewItem class that represents an AudioClip should be named AudioClipView.
61 
62 	All keyboard and mouse events by default are propagated to the InputEngine, which in <br />
63 	turn will parse the events. In case the event sequence was recognized by the InputEngine <br />
64 	it will ask a list of (pointed) ContextItem's from ContextPointer, which in turns <br />
65 	call's the pure virtual function get_pointed_context_items(), which you have to reimplement.<br />
66 	In the reimplemented function, you have to fill the supplied list with ViewItems that are <br />
67 	under the mouse cursor, and if needed, ViewItem's that _always_ have to be taken into account. <br />
68 	One can use the convenience functions of QGraphicsView for getting ViewItem's under the mouse cursor!
69 
70 	Since there can be a certain delay before a key sequence has been verified, the ContextPointer <br />
71 	stores the position of the first event of a new key fact. This improves the pointed ViewItem <br />
72 	detection a lot in case the mouse is moved during a key sequence.<br />
73 	You should use these x/y coordinates in the get_pointed_context_items() function, see:<br />
74 	ContextPointer::on_first_input_event_x(), ContextPointer::on_first_input_event_y()
75 
76 
77  *	\sa ContextPointer, InputEngine
78  */
79 
80 
81 
ViewPort(QWidget * parent)82 ViewPort::ViewPort(QWidget* parent)
83 	: QGraphicsView(parent)
84 	, m_mode(0)
85 {
86 	PENTERCONS;
87 	setFrameStyle(QFrame::NoFrame);
88 	setAlignment(Qt::AlignLeft | Qt::AlignTop);
89 }
90 
ViewPort(QGraphicsScene * scene,QWidget * parent)91 ViewPort::ViewPort(QGraphicsScene* scene, QWidget* parent)
92 	: QGraphicsView(scene, parent)
93 	, m_mode(0)
94 {
95 	PENTERCONS;
96 	setFrameStyle(QFrame::NoFrame);
97 	setAlignment(Qt::AlignLeft | Qt::AlignTop);
98 
99 	setOptimizationFlag(DontAdjustForAntialiasing);
100 	setOptimizationFlag(DontSavePainterState);
101 	setOptimizationFlag(DontClipPainter);
102 
103 	m_holdcursor = new HoldCursor(this);
104 	scene->addItem(m_holdcursor);
105 	m_holdcursor->hide();
106 	// m_holdCursorActive is a replacement for m_holdcursor->isVisible()
107 	// in mouseMoveEvents, which crashes when a hold action in one viewport
108 	// ends with the mouse upon a different viewport.
109 	// Should get a proper fix ?
110 	m_holdCursorActive = false;
111 }
112 
~ViewPort()113 ViewPort::~ViewPort()
114 {
115 	PENTERDES;
116 
117 	cpointer().set_current_viewport((ViewPort*) 0);
118 }
119 
event(QEvent * event)120 bool ViewPort::event(QEvent * event)
121 {
122 	// We want Tab events also send to the InputEngine
123 	// so treat them as 'normal' key events.
124 	if (event->type() == QEvent::KeyPress) {
125 		QKeyEvent *ke = static_cast<QKeyEvent *>(event);
126 		if (ke->key() == Qt::Key_Tab) {
127 			keyPressEvent(ke);
128 			return true;
129 		}
130 	}
131 	if (event->type() == QEvent::KeyRelease) {
132 		QKeyEvent *ke = static_cast<QKeyEvent *>(event);
133 		if (ke->key() == Qt::Key_Tab) {
134 			keyReleaseEvent(ke);
135 			return true;
136 		}
137 	}
138 	return QGraphicsView::event(event);
139 }
140 
mouseMoveEvent(QMouseEvent * event)141 void ViewPort::mouseMoveEvent(QMouseEvent* event)
142 {
143 	PENTER4;
144 	// Qt generates mouse move events when the scrollbars move
145 	// since a mouse move event generates a jog() call for the
146 	// active holding command, this has a number of nasty side effects :-(
147 	// For now, we ignore such events....
148 	if (event->pos() == m_oldMousePos) {
149 		return;
150 	}
151 
152 	QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove);
153 	mouseEvent.setWidget(viewport());
154 // 	mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint);
155 // 	mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint);
156 	mouseEvent.setScenePos(mapToScene(event->pos()));
157 	mouseEvent.setScreenPos(event->globalPos());
158 	mouseEvent.setLastScenePos(lastMouseMoveScenePoint);
159 	mouseEvent.setLastScreenPos(mapFromScene(lastMouseMoveScenePoint));
160 	mouseEvent.setButtons(event->buttons());
161 	mouseEvent.setButton(event->button());
162 	mouseEvent.setModifiers(event->modifiers());
163 	lastMouseMoveScenePoint = mouseEvent.scenePos();
164 	mouseEvent.setAccepted(false);
165 
166 	m_oldMousePos = event->pos();
167 
168 	if (!ie().is_holding()) {
169 		QList<QGraphicsItem *> itemsUnderCursor = scene()->items(mapToScene(event->pos()));
170 		if (itemsUnderCursor.size()) {
171 			itemsUnderCursor.first()->setCursor(itemsUnderCursor.first()->cursor());
172 		} else {
173 			// If no item is below the mouse, default to default cursor
174 			viewport()->setCursor(themer()->get_cursor("Default"));
175 		}
176 		QApplication::sendEvent(scene(), &mouseEvent);
177 	} else {
178 		// It can happen that a cursor is set for a newly created viewitem
179 		// but we don't want that when the holdcursor is set!
180 		// So force it back to be a blankcursor.
181 		if (m_holdCursorActive /* was m_holdcursor->isVisible() */ && viewport()->cursor().shape() != Qt::BlankCursor) {
182 			viewport()->setCursor(Qt::BlankCursor);
183 		}
184 	}
185 
186 // 	QGraphicsView::mouseMoveEvent(event);
187 	cpointer().set_point(event->x(), event->y());
188 	event->accept();
189 }
190 
tabletEvent(QTabletEvent * event)191 void ViewPort::tabletEvent(QTabletEvent * event)
192 {
193 	PMESG("ViewPort tablet event:: x, y: %d, %d", (int)event->x(), (int)event->y());
194 	PMESG("ViewPort tablet event:: high resolution x, y: %d, %d",
195 	      (int)event->hiResGlobalX(), (int)event->hiResGlobalY());
196 	cpointer().set_point((int)event->x(), (int)event->y());
197 
198 	QGraphicsView::tabletEvent(event);
199 }
200 
enterEvent(QEvent * e)201 void ViewPort::enterEvent(QEvent* e)
202 {
203 	QGraphicsView::enterEvent(e);
204 	cpointer().set_current_viewport(this);
205 	setFocus();
206 }
207 
leaveEvent(QEvent * event)208 void ViewPort::leaveEvent ( QEvent * event )
209 {
210 	QGraphicsView::leaveEvent(event);
211 	// There can be many reasons for a leave event, sometimes
212 	// this leaves the engine in a non-cleared state, e.g. modifier
213 	// keys still can be active!! So we reset those manually here.
214 	ie().clear_modifier_keys();
215 }
216 
keyPressEvent(QKeyEvent * e)217 void ViewPort::keyPressEvent( QKeyEvent * e)
218 {
219 	ie().catch_key_press(e);
220 	e->accept();
221 }
222 
keyReleaseEvent(QKeyEvent * e)223 void ViewPort::keyReleaseEvent( QKeyEvent * e)
224 {
225 	ie().catch_key_release(e);
226 	e->accept();
227 }
228 
mousePressEvent(QMouseEvent * e)229 void ViewPort::mousePressEvent( QMouseEvent * e )
230 {
231 	ie().catch_mousebutton_press(e);
232 	e->accept();
233 }
234 
mouseReleaseEvent(QMouseEvent * e)235 void ViewPort::mouseReleaseEvent( QMouseEvent * e )
236 {
237 	ie().catch_mousebutton_release(e);
238 	e->accept();
239 }
240 
mouseDoubleClickEvent(QMouseEvent * e)241 void ViewPort::mouseDoubleClickEvent( QMouseEvent * e )
242 {
243 	ie().catch_mousebutton_doubleclick(e);
244 	e->accept();
245 }
246 
wheelEvent(QWheelEvent * e)247 void ViewPort::wheelEvent( QWheelEvent * e )
248 {
249 	ie().catch_scroll(e);
250 	e->accept();
251 }
252 
paintEvent(QPaintEvent * e)253 void ViewPort::paintEvent( QPaintEvent* e )
254 {
255 // 	PWARN("ViewPort::paintEvent()");
256 	QGraphicsView::paintEvent(e);
257 }
258 
reset_cursor()259 void ViewPort::reset_cursor( )
260 {
261 	viewport()->unsetCursor();
262 	m_holdcursor->hide();
263 	m_holdcursor->reset();
264 	m_holdCursorActive = false;
265 }
266 
set_holdcursor(const QString & cursorName)267 void ViewPort::set_holdcursor( const QString & cursorName )
268 {
269 	viewport()->setCursor(Qt::BlankCursor);
270 
271 	if (!m_holdCursorActive) {
272 		m_holdcursor->setPos(cpointer().scene_pos());
273 		m_holdcursor->show();
274 	}
275 	m_holdcursor->set_type(cursorName);
276 	m_holdCursorActive = true;
277 }
278 
set_holdcursor_text(const QString & text)279 void ViewPort::set_holdcursor_text( const QString & text )
280 {
281 	m_holdcursor->set_text(text);
282 	// TODO Find out why we have to call set_holdcursor_pos() here
283 	// AGAIN when it allready has been called in for example MoveClip::jog()
284 	// to AVOID jitter of the hold cursor text item when the cursor is
285 	// out of the viewports range
286 	set_holdcursor_pos(mapToScene(cpointer().pos()).toPoint());
287 }
288 
set_holdcursor_pos(QPoint pos)289 void ViewPort::set_holdcursor_pos(QPoint pos)
290 {
291 	m_holdcursor->set_pos(pos);
292 }
293 
set_current_mode(int mode)294 void ViewPort::set_current_mode(int mode)
295 {
296 	m_mode = mode;
297 }
298 
299 
300 
301 /**********************************************************************/
302 /*                      HoldCursor                                    */
303 /**********************************************************************/
304 
305 
HoldCursor(ViewPort * vp)306 HoldCursor::HoldCursor(ViewPort* vp)
307 	: m_vp(vp)
308 {
309 	m_textItem = new QGraphicsTextItem(this);
310 	m_textItem->setFont(themer()->get_font("ViewPort:fontscale:infocursor"));
311 
312 	setZValue(200);
313 }
314 
~HoldCursor()315 HoldCursor::~ HoldCursor( )
316 {
317 }
318 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)319 void HoldCursor::paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget )
320 {
321 	Q_UNUSED(widget);
322 	Q_UNUSED(option);
323 
324 	painter->drawPixmap(0, 0, m_pixmap);
325 }
326 
327 
set_text(const QString & text)328 void HoldCursor::set_text( const QString & text )
329 {
330 	m_text = text;
331 
332 	if (!m_text.isEmpty()) {
333 		QString html = "<html><body bgcolor=ghostwhite>" + m_text + "</body></html>";
334 		m_textItem->setHtml(html);
335 		m_textItem->show();
336 	} else {
337 		m_textItem->hide();
338 	}
339 }
340 
set_type(const QString & type)341 void HoldCursor::set_type( const QString & type )
342 {
343 	m_pixmap = find_pixmap(type);
344 	int x = (int) pos().x();
345 	int y = (int) pos().y();
346 	setPos(x - m_pixmap.width() / 2, y - m_pixmap.height() / 2);
347 }
348 
boundingRect() const349 QRectF HoldCursor::boundingRect( ) const
350 {
351 	return QRectF(0, 0, 130, 40);
352 }
353 
reset()354 void HoldCursor::reset()
355 {
356 	m_text = "";
357 	m_textItem->hide();
358 }
359 
set_pos(QPoint p)360 void HoldCursor::set_pos(QPoint p)
361 {
362 	int x = m_vp->mapFromScene(pos()).x();
363 	int y = m_vp->mapFromScene(pos()).y();
364 	int yoffset = 0;
365 
366 	if (y < 0) {
367 		yoffset = - y;
368 	} else if (y > m_vp->height() - m_pixmap.height()) {
369 		yoffset = m_vp->height() - y - m_pixmap.height();
370 	}
371 
372 	int diff = m_vp->width() - (x + m_pixmap.width() + 8);
373 
374 	if (diff < m_textItem->boundingRect().width()) {
375 		m_textItem->setPos(diff - m_pixmap.width(), yoffset);
376 	} else if (x < -m_pixmap.width()) {
377 		m_textItem->setPos(8 - x, yoffset);
378 	} else {
379 		m_textItem->setPos(m_pixmap.width() + 8, yoffset);
380 	}
381 
382 	setPos(p);
383 }
384 
385