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