]> git.sesse.net Git - kdenlive/blob - src/abstractgroupitem.cpp
Make sure we cannot move a clip to a locked track
[kdenlive] / src / abstractgroupitem.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Marco Gittler (g.marco@freenet.de)              *
3  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
19  ***************************************************************************/
20
21 #include "abstractgroupitem.h"
22 #include "abstractclipitem.h"
23 #include "kdenlivesettings.h"
24 #include "customtrackscene.h"
25 #include "customtrackview.h"
26
27 #include <KDebug>
28
29 #include <QPainter>
30 #include <QStyleOptionGraphicsItem>
31 #include <QDomDocument>
32 #include <QMimeData>
33 #include <QGraphicsSceneMouseEvent>
34
35
36 AbstractGroupItem::AbstractGroupItem(double /* fps */) :
37         QObject(),
38         QGraphicsItemGroup()
39 {
40     setZValue(1);
41     setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
42 #if QT_VERSION >= 0x040600
43     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
44 #endif
45     setAcceptDrops(true);
46     m_resizeInfos = QList <ItemInfo>();
47 }
48
49 int AbstractGroupItem::type() const
50 {
51     return GROUPWIDGET;
52 }
53
54 int AbstractGroupItem::track() const
55 {
56     return (int)(scenePos().y() / KdenliveSettings::trackheight());
57 }
58
59 void AbstractGroupItem::setItemLocked(bool locked)
60 {
61     if (locked)
62         setSelected(false);
63
64     setFlag(QGraphicsItem::ItemIsMovable, !locked);
65     setFlag(QGraphicsItem::ItemIsSelectable, !locked);
66
67     foreach (QGraphicsItem *child, childItems())
68         ((AbstractClipItem *)child)->setItemLocked(locked);
69 }
70
71 bool AbstractGroupItem::isItemLocked() const
72 {
73     return !(flags() & (QGraphicsItem::ItemIsSelectable));
74 }
75
76 CustomTrackScene* AbstractGroupItem::projectScene()
77 {
78     if (scene()) return static_cast <CustomTrackScene*>(scene());
79     return NULL;
80 }
81
82 QPainterPath AbstractGroupItem::clipGroupShape(QPointF offset) const
83 {
84     return groupShape(AVWIDGET, offset);
85 }
86
87 QPainterPath AbstractGroupItem::transitionGroupShape(QPointF offset) const
88 {
89     return groupShape(TRANSITIONWIDGET, offset);
90 }
91
92 QPainterPath AbstractGroupItem::groupShape(GRAPHICSRECTITEM type, QPointF offset) const
93 {
94     QPainterPath path;
95     QList<QGraphicsItem *> children = childItems();
96     for (int i = 0; i < children.count(); i++) {
97         if (children.at(i)->type() == (int)type) {
98             QRectF r(children.at(i)->sceneBoundingRect());
99             r.translate(offset);
100             path.addRect(r);
101         } else if (children.at(i)->type() == GROUPWIDGET) {
102             QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
103             for (int j = 0; j < subchildren.count(); j++) {
104                 if (subchildren.at(j)->type() == (int)type) {
105                     QRectF r(subchildren.at(j)->sceneBoundingRect());
106                     r.translate(offset);
107                     path.addRect(r);
108                 }
109             }
110         }
111     }
112     return path;
113 }
114
115 void AbstractGroupItem::addItem(QGraphicsItem * item)
116 {
117     addToGroup(item);
118     item->setFlag(QGraphicsItem::ItemIsMovable, false);
119 }
120
121 void AbstractGroupItem::removeItem(QGraphicsItem * item)
122 {
123     removeFromGroup(item);
124 }
125
126 void AbstractGroupItem::fixItemRect()
127 {
128     QPointF start = boundingRect().topLeft();
129     if (start != QPointF(0, 0)) {
130         translate(0 - start.x(), 0 - start.y());
131         setPos(start);
132     }
133 }
134
135 /*ItemInfo AbstractGroupItem::info() const {
136     ItemInfo itemInfo;
137     itemInfo.startPos = m_startPos;
138     itemInfo.track = m_track;
139     return itemInfo;
140 }*/
141
142 // virtual
143 void AbstractGroupItem::paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *)
144 {
145     QColor bgcolor(100, 100, 200, 100);
146     QRectF bound = option->exposedRect.adjusted(0, 0, 1, 1);
147     p->setClipRect(bound);
148     const QRectF mapped = p->worldTransform().mapRect(option->exposedRect);
149     p->setWorldMatrixEnabled(false);
150     p->setBrush(bgcolor);
151     QPen pen = p->pen();
152     pen.setColor(QColor(200, 90, 90));
153     pen.setStyle(Qt::DashLine);
154     pen.setWidthF(0.0);
155     p->setPen(pen);
156     p->drawRoundedRect(mapped, 3, 3);
157 }
158
159 //virtual
160 QVariant AbstractGroupItem::itemChange(GraphicsItemChange change, const QVariant &value)
161 {
162     if (change == QGraphicsItem::ItemSelectedChange) {
163         if (value.toBool()) setZValue(10);
164         else setZValue(1);
165     }
166     if (change == ItemPositionChange && scene() && parentItem() == 0) {
167         // calculate new position.
168         const int trackHeight = KdenliveSettings::trackheight();
169         QPointF start = sceneBoundingRect().topLeft();
170         QPointF newPos = value.toPointF();
171         int xpos = projectScene()->getSnapPointForPos((int)(start.x() + newPos.x() - pos().x()), KdenliveSettings::snaptopoints());
172
173         xpos = qMax(xpos, 0);
174         //kDebug()<<"GRP XPOS:"<<xpos<<", START:"<<start.x()<<",NEW:"<<newPos.x()<<"; SCENE:"<<scenePos().x()<<",POS:"<<pos().x();
175         newPos.setX((int)(pos().x() + xpos - (int) start.x()));
176         QStringList lockedTracks = property("locked_tracks").toStringList();
177         int proposedTrack = (property("y_absolute").toInt() + newPos.y()) / trackHeight;
178         // Check if top item is a clip or a transition
179         int offset = 0;
180         int topTrack = -1;
181         QList<int> groupTracks;
182         QList<QGraphicsItem *> children = childItems();
183         for (int i = 0; i < children.count(); i++) {
184             int currentTrack = 0;
185             if (children.at(i)->type() == AVWIDGET || children.at(i)->type() == TRANSITIONWIDGET) {
186                 currentTrack = static_cast <AbstractClipItem*> (children.at(i))->track();
187                 if (!groupTracks.contains(currentTrack)) groupTracks.append(currentTrack);
188             }
189             else if (children.at(i)->type() == GROUPWIDGET) currentTrack = static_cast <AbstractGroupItem*> (children.at(i))->track();
190             else continue;
191             if (children.at(i)->type() == AVWIDGET) {
192                 if (topTrack == -1 || currentTrack <= topTrack) {
193                     offset = 0;
194                     topTrack = currentTrack;
195                 }
196             } else if (children.at(i)->type() == TRANSITIONWIDGET) {
197                 if (topTrack == -1 || currentTrack < topTrack) {
198                     offset = (int)(trackHeight / 3 * 2 - 1);
199                     topTrack = currentTrack;
200                 }
201             } else if (children.at(i)->type() == GROUPWIDGET) {
202                 QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
203                 bool clipGroup = false;
204                 for (int j = 0; j < subchildren.count(); j++) {
205                     if (subchildren.at(j)->type() == AVWIDGET) {
206                         int subTrack = static_cast <AbstractClipItem*> (subchildren.at(j))->track();
207                         if (!groupTracks.contains(subTrack)) groupTracks.append(subTrack);
208                         clipGroup = true;
209                     }
210                 }
211                 if (clipGroup) {
212                     if (topTrack == -1 || currentTrack <= topTrack) {
213                         offset = 0;
214                         topTrack = currentTrack;
215                     }
216                 } else {
217                     if (topTrack == -1 || currentTrack < topTrack) {
218                         offset = (int)(trackHeight / 3 * 2 - 1);
219                         topTrack = currentTrack;
220                     }
221                 }
222             }
223         }
224         // Check no clip in the group goes outside of existing tracks
225         int maximumTrack = projectScene()->tracksCount() - 1;
226         int groupHeight = 0;
227         for (int i = 0; i < groupTracks.count(); i++) {
228             int offset = groupTracks.at(i) - topTrack;
229             if (offset > groupHeight) groupHeight = offset; 
230         }
231         maximumTrack -= groupHeight;
232         proposedTrack = qMin(proposedTrack, maximumTrack);
233         proposedTrack = qMax(proposedTrack, 0);
234         int groupOffset = proposedTrack - topTrack;
235         if (!lockedTracks.isEmpty()) {
236             for (int i = 0; i < groupTracks.count(); i++) {
237                 if (lockedTracks.contains(QString::number(groupTracks.at(i) + groupOffset))) {
238                     return pos();
239                 }
240             }
241         }
242         newPos.setY((int)((proposedTrack) * trackHeight) + offset);
243         //if (newPos == start) return start;
244
245         /*if (newPos.x() < 0) {
246             // If group goes below 0, adjust position to 0
247             return QPointF(pos().x() - start.x(), pos().y());
248         }*/
249
250         QList<QGraphicsItem*> collidingItems;
251         QPainterPath shape;
252         if (projectScene()->editMode() == NORMALEDIT) {
253             shape = clipGroupShape(newPos - pos());
254             collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
255             collidingItems.removeAll(this);
256             for (int i = 0; i < children.count(); i++) {
257                 if (children.at(i)->type() == GROUPWIDGET) {
258                     QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
259                     for (int j = 0; j < subchildren.count(); j++) {
260                         collidingItems.removeAll(subchildren.at(j));
261                     }
262                 }
263                 collidingItems.removeAll(children.at(i));
264             }
265         }
266         if (!collidingItems.isEmpty()) {
267             bool forwardMove = xpos > start.x();
268             int offset = 0;
269             for (int i = 0; i < collidingItems.count(); i++) {
270                 QGraphicsItem *collision = collidingItems.at(i);
271                 if (collision->type() == AVWIDGET) {
272                     // Collision
273                     if (newPos.y() != pos().y()) {
274                         // Track change results in collision, restore original position
275                         return pos();
276                     }
277                     AbstractClipItem *item = static_cast <AbstractClipItem *>(collision);
278                     if (forwardMove) {
279                         // Moving forward, determine best pos
280                         QPainterPath clipPath;
281                         clipPath.addRect(item->sceneBoundingRect());
282                         QPainterPath res = shape.intersected(clipPath);
283                         offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
284                     } else {
285                         // Moving backward, determine best pos
286                         QPainterPath clipPath;
287                         clipPath.addRect(item->sceneBoundingRect());
288                         QPainterPath res = shape.intersected(clipPath);
289                         offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
290                     }
291                 }
292             }
293             if (offset > 0) {
294                 if (forwardMove) {
295                     newPos.setX(newPos.x() - offset);
296                 } else {
297                     newPos.setX(newPos.x() + offset);
298                 }
299                 // If there is still a collision after our position adjust, restore original pos
300                 collidingItems = scene()->items(clipGroupShape(newPos - pos()), Qt::IntersectsItemShape);
301                 collidingItems.removeAll(this);
302                 for (int i = 0; i < children.count(); i++) {
303                     if (children.at(i)->type() == GROUPWIDGET) {
304                         QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
305                         for (int j = 0; j < subchildren.count(); j++) {
306                             collidingItems.removeAll(subchildren.at(j));
307                         }
308                     }
309                     collidingItems.removeAll(children.at(i));
310                 }
311                 for (int i = 0; i < collidingItems.count(); i++)
312                     if (collidingItems.at(i)->type() == AVWIDGET) return pos();
313             }
314         }
315
316         if (projectScene()->editMode() == NORMALEDIT) {
317             shape = transitionGroupShape(newPos - pos());
318             collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
319             collidingItems.removeAll(this);
320             for (int i = 0; i < children.count(); i++) {
321                 if (children.at(i)->type() == GROUPWIDGET) {
322                     QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
323                     for (int j = 0; j < subchildren.count(); j++) {
324                         collidingItems.removeAll(subchildren.at(j));
325                     }
326                 }
327                 collidingItems.removeAll(children.at(i));
328             }
329         }
330         if (collidingItems.isEmpty()) return newPos;
331         else {
332             bool forwardMove = xpos > start.x();
333             int offset = 0;
334             for (int i = 0; i < collidingItems.count(); i++) {
335                 QGraphicsItem *collision = collidingItems.at(i);
336                 if (collision->type() == TRANSITIONWIDGET) {
337                     // Collision
338                     if (newPos.y() != pos().y()) {
339                         // Track change results in collision, restore original position
340                         return pos();
341                     }
342                     AbstractClipItem *item = static_cast <AbstractClipItem *>(collision);
343                     if (forwardMove) {
344                         // Moving forward, determine best pos
345                         QPainterPath clipPath;
346                         clipPath.addRect(item->sceneBoundingRect());
347                         QPainterPath res = shape.intersected(clipPath);
348                         offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
349                     } else {
350                         // Moving backward, determine best pos
351                         QPainterPath clipPath;
352                         clipPath.addRect(item->sceneBoundingRect());
353                         QPainterPath res = shape.intersected(clipPath);
354                         offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
355                     }
356                 }
357             }
358             if (offset > 0) {
359                 if (forwardMove) {
360                     newPos.setX(newPos.x() - offset);
361                 } else {
362                     newPos.setX(newPos.x() + offset);
363                 }
364                 // If there is still a collision after our position adjust, restore original pos
365                 collidingItems = scene()->items(transitionGroupShape(newPos - pos()), Qt::IntersectsItemShape);
366                 for (int i = 0; i < children.count(); i++) {
367                     collidingItems.removeAll(children.at(i));
368                 }
369                 for (int i = 0; i < collidingItems.count(); i++)
370                     if (collidingItems.at(i)->type() == TRANSITIONWIDGET) return pos();
371             }
372         }
373         return newPos;
374     }
375     return QGraphicsItemGroup::itemChange(change, value);
376 }
377
378 //virtual
379 void AbstractGroupItem::dropEvent(QGraphicsSceneDragDropEvent * event)
380 {
381     QString effects = QString::fromUtf8(event->mimeData()->data("kdenlive/effectslist"));
382     QDomDocument doc;
383     doc.setContent(effects, true);
384     QDomElement e = doc.documentElement();
385     e.setAttribute("kdenlive_ix", 0);
386     CustomTrackView *view = (CustomTrackView *) scene()->views()[0];
387     QPointF dropPos = event->scenePos();
388     QList<QGraphicsItem *> selection = scene()->items(dropPos);
389     AbstractClipItem *dropChild = NULL;
390     for (int i = 0; i < selection.count(); i++) {
391         if (selection.at(i)->type() == AVWIDGET) {
392             dropChild = (AbstractClipItem *) selection.at(i);
393             break;
394         }
395     }           
396     if (view) view->slotAddGroupEffect(e, this, dropChild);
397 }
398
399 //virtual
400 void AbstractGroupItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
401 {
402     event->setAccepted(event->mimeData()->hasFormat("kdenlive/effectslist"));
403 }
404
405 void AbstractGroupItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
406 {
407     Q_UNUSED(event)
408 }
409
410 // virtual
411 void AbstractGroupItem::mousePressEvent(QGraphicsSceneMouseEvent * event)
412 {
413     if (event->modifiers() & Qt::ShiftModifier) {
414         // User want to do a rectangle selection, so ignore the event to pass it to the view
415         event->ignore();
416     } else {
417         QList <QGraphicsItem *>list = scene()->items(event->scenePos());
418         // only allow group move if we click over an item in the group
419         foreach(const QGraphicsItem *item, list) {
420             if (item->type() == TRANSITIONWIDGET || item->type() == AVWIDGET) {
421                 QGraphicsItem::mousePressEvent(event);
422                 return;
423             }
424         }
425         event->ignore();
426     }
427 }
428
429 void AbstractGroupItem::resizeStart(int diff)
430 {
431     bool info = false;
432     if (m_resizeInfos.isEmpty())
433         info = true;
434     int maximum = diff;
435     QList <QGraphicsItem *> children = childItems();
436     QList <AbstractClipItem *> items;
437     int itemcount = 0;
438     for (int i = 0; i < children.count(); ++i) {
439         AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(i));
440         if (item && item->type() == AVWIDGET) {
441             items << item;
442             if (info)
443                 m_resizeInfos << item->info();
444             item->resizeStart((int)(m_resizeInfos.at(itemcount).startPos.frames(item->fps())) + diff);
445             int itemdiff = (int)(item->startPos() - m_resizeInfos.at(itemcount).startPos).frames(item->fps());
446             if (qAbs(itemdiff) < qAbs(maximum))
447                 maximum = itemdiff;
448             ++itemcount;
449         }
450     }
451     
452     for (int i = 0; i < items.count(); ++i)
453         items.at(i)->resizeStart((int)(m_resizeInfos.at(i).startPos.frames(items.at(i)->fps())) + maximum);
454 }
455
456 void AbstractGroupItem::resizeEnd(int diff)
457 {
458     bool info = false;
459     if (m_resizeInfos.isEmpty())
460         info = true;
461     int maximum = diff;
462     QList <QGraphicsItem *> children = childItems();
463     QList <AbstractClipItem *> items;
464     int itemcount = 0;
465     for (int i = 0; i < children.count(); ++i) {
466         AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(i));
467         if (item && item->type() == AVWIDGET) {
468             items << item;
469             if (info)
470                 m_resizeInfos << item->info();
471             item->resizeEnd((int)(m_resizeInfos.at(itemcount).endPos.frames(item->fps())) + diff);
472             int itemdiff = (int)(item->endPos() - m_resizeInfos.at(itemcount).endPos).frames(item->fps());
473             if (qAbs(itemdiff) < qAbs(maximum))
474                 maximum = itemdiff;
475             ++itemcount;
476         }
477     }
478
479     for (int i = 0; i < items.count(); ++i)
480         items.at(i)->resizeEnd((int)(m_resizeInfos.at(i).endPos.frames(items.at(i)->fps())) + maximum);
481 }
482
483 QList< ItemInfo > AbstractGroupItem::resizeInfos()
484 {
485     return m_resizeInfos;
486 }
487
488 void AbstractGroupItem::clearResizeInfos()
489 {
490     // m_resizeInfos.clear() will crash in some cases for unknown reasons - ttill
491     m_resizeInfos = QList <ItemInfo>();
492 }
493
494 GenTime AbstractGroupItem::duration()
495 {
496     QList <QGraphicsItem *> children = childItems();
497     GenTime start = GenTime(-1.0);
498     GenTime end = GenTime();
499     for (int i = 0; i < children.count(); ++i) {
500         if (children.at(i)->type() != GROUPWIDGET) {
501             AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(i));
502             if (item) {
503                 if (start < GenTime() || item->startPos() < start)
504                     start = item->startPos();
505                 if (item->endPos() > end)
506                     end = item->endPos();
507             }
508         } else {
509             children << children.at(i)->childItems();
510         }
511     }
512     return end - start;
513 }