projectlistview.cpp 19.2 KB
Newer Older
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/***************************************************************************
 *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

20
// Self
Vincent Pinon's avatar
Vincent Pinon committed
21 22 23 24 25 26 27 28
#include "projectlistview.h"

// KDEnlive
#include "projectitem.h"
#include "subprojectitem.h"
#include "folderprojectitem.h"

#include "kdenlivesettings.h"
29

30 31 32 33 34 35
// Qt
#include <QApplication>
#include <QHeaderView>
#include <QAction>

// KDE
36 37
#include <KDebug>
#include <KMenu>
38
#include <KLocalizedString>
39

40 41 42
ProjectListView::ProjectListView(QWidget *parent)
    : QTreeWidget(parent)
    , m_dragStarted(false)
43
{
44 45 46 47 48 49
    setSelectionMode(QAbstractItemView::ExtendedSelection);
    setDragDropMode(QAbstractItemView::DragDrop);
    setDropIndicatorShown(true);
    setAlternatingRowColors(true);
    setDragEnabled(true);
    setAcceptDrops(true);
50
    setFrameShape(QFrame::NoFrame);
51
    setRootIsDecorated(true);
52

53
    updateStyleSheet();
54

55
    setColumnCount(4);
56
    QStringList headers;
57
    headers << i18n("Clip") << i18n("Description") << i18n("Rating") << i18n("Date");
58
    setHeaderLabels(headers);
59 60
    setIndentation(12);
    
61 62
    QHeaderView* headerView = header();
    headerView->setContextMenuPolicy(Qt::CustomContextMenu);
Laurent Montel's avatar
Laurent Montel committed
63 64 65 66
    connect(headerView, SIGNAL(customContextMenuRequested(QPoint)),
            this, SLOT(configureColumns(QPoint)));
    connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(slotCollapsed(QTreeWidgetItem*)));
    connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(slotExpanded(QTreeWidgetItem*)));
67 68 69
    headerView->setClickable(true);
    headerView->setSortIndicatorShown(true);
    headerView->setMovable(false);
70
    sortByColumn(0, Qt::AscendingOrder);
71
    setSortingEnabled(true);
72
    installEventFilter(this);
73 74 75 76 77 78 79 80 81 82

    if (!KdenliveSettings::showdescriptioncolumn()) {
        hideColumn(1);
    }
    if (!KdenliveSettings::showratingcolumn()) {
        hideColumn(2);
    }
    if (!KdenliveSettings::showdatecolumn()) {
        hideColumn(3);
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
83 84
}

85 86
ProjectListView::~ProjectListView()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
87 88
}

89 90 91 92 93 94 95 96 97 98 99 100
void ProjectListView::updateStyleSheet()
{
    QString style = "QTreeView::branch:has-siblings:!adjoins-item{border-image: none;border:0px} \
    QTreeView::branch:has-siblings:adjoins-item {border-image: none;border:0px}      \
    QTreeView::branch:!has-children:!has-siblings:adjoins-item {border-image: none;border:0px} \
    QTreeView::branch:has-children:!has-siblings:closed,QTreeView::branch:closed:has-children:has-siblings {   \
         border-image: none;image: url(:/images/stylesheet-branch-closed.png);}      \
    QTreeView::branch:open:has-children:!has-siblings,QTreeView::branch:open:has-children:has-siblings  {    \
         border-image: none;image: url(:/images/stylesheet-branch-open.png);}";
    setStyleSheet(style);
}

101 102 103 104 105
void ProjectListView::processLayout()
{
    executeDelayedItemsLayout();
}

106 107
void ProjectListView::configureColumns(const QPoint& pos)
{
108 109 110 111
    KMenu popup(this);
    popup.addTitle(i18nc("@title:menu", "Columns"));

    QHeaderView* headerView = header();
112
    for (int i = 1; i < headerView->count(); ++i) {
113 114 115
        const QString text = model()->headerData(i, Qt::Horizontal).toString();
        QAction* action = popup.addAction(text);
        action->setCheckable(true);
116
        action->setChecked(!headerView->isSectionHidden(i));
117 118 119 120 121 122 123 124 125 126
        action->setData(i);
    }

    QAction* activatedAction = popup.exec(header()->mapToGlobal(pos));
    if (activatedAction != 0) {
        const bool show = activatedAction->isChecked();

        // remember the changed column visibility in the settings
        const int columnIndex = activatedAction->data().toInt();
        switch (columnIndex) {
127
        case 1:
128 129
            KdenliveSettings::setShowdescriptioncolumn(show);
            break;
130
        case 2:
131 132
            KdenliveSettings::setShowratingcolumn(show);
            break;
133 134 135
        case 3:
            KdenliveSettings::setShowdatecolumn(show);
            break;
136 137 138 139 140 141 142 143 144 145 146
        default:
            break;
        }

        // apply the changed column visibility
        if (show) {
            showColumn(columnIndex);
        } else {
            hideColumn(columnIndex);
        }
    }
147 148
}

149
// virtual
150 151
void ProjectListView::contextMenuEvent(QContextMenuEvent * event)
{
152
    emit requestMenu(event->globalPos(), itemAt(event->pos()));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
153 154
}

155 156
void ProjectListView::slotCollapsed(QTreeWidgetItem *item)
{
157
    if (item->type() == ProjectFoldeType) {
158 159 160 161 162 163 164
        blockSignals(true);
        static_cast <FolderProjectItem *>(item)->switchIcon();
        blockSignals(false);
    }
}

void ProjectListView::slotExpanded(QTreeWidgetItem *item)
165
{
166
    if (item->type() == ProjectFoldeType) {
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
        blockSignals(true);
        static_cast <FolderProjectItem *>(item)->switchIcon();
        blockSignals(false);
    }
}

bool ProjectListView::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) {
        QKeyEvent* ke = (QKeyEvent*) event;
        if (ke->key() == Qt::Key_Plus) {
            if (currentItem()) currentItem()->setExpanded(true);
            event->accept();
            return true;
        } else if (ke->key() == Qt::Key_Minus) {
            if (currentItem()) currentItem()->setExpanded(false);
            event->accept();
            return true;
        } else {
            return false;
187
        }
188 189 190 191
    } else {
        // pass the event on to the parent class
        return QTreeWidget::eventFilter(obj, event);
    }
192 193
}

194
// virtual
195 196
void ProjectListView::mouseDoubleClickEvent(QMouseEvent * event)
{
197
    QTreeWidgetItem *it = itemAt(event->pos());
198
    if (!it) {
199
	emit pauseMonitor();
200 201
        emit addClip();
        return;
202
    }
203
    ProjectItem *item;
204
    if (it->type() == ProjectFoldeType) {
205 206 207 208 209 210 211 212
        if ((columnAt(event->pos().x()) == 0)) {
            QPixmap pix = qVariantValue<QPixmap>(it->data(0, Qt::DecorationRole));
            int offset = pix.width() + indentation();
            if (event->pos().x() < offset) {
                it->setExpanded(!it->isExpanded());
                event->accept();
            } else QTreeWidget::mouseDoubleClickEvent(event);
        }
213 214
        return;
    }
215
    if (it->type() == ProjectSubclipType) {
216
        // subitem
217 218 219 220
        if ((columnAt(event->pos().x()) == 1)) {
            QTreeWidget::mouseDoubleClickEvent(event);
            return;
        }
221 222
        item = static_cast <ProjectItem *>(it->parent());
    } else item = static_cast <ProjectItem *>(it);
223

224
    if (!(item->flags() & Qt::ItemIsDragEnabled)) return;
225 226

    int column = columnAt(event->pos().x());
227
    if (column == 0 && (item->clipType() == SlideShow || item->clipType() == Text || item->clipType() == Color || it->childCount() > 0)) {
228 229 230
        QPixmap pix = qVariantValue<QPixmap>(it->data(0, Qt::DecorationRole));
        int offset = pix.width() + indentation();
        if (item->parent()) offset += indentation();
231 232 233 234 235 236 237
        if (it->childCount() > 0) {
            if (offset > event->pos().x()) {
                it->setExpanded(!it->isExpanded());
                event->accept();
                return;
            }
        } else if (pix.isNull() || offset < event->pos().x()) {
238 239 240 241
            QTreeWidget::mouseDoubleClickEvent(event);
            return;
        }
    }
242
    if ((column == 1) && it->type() != ProjectSubclipType) {
243 244 245 246
        QTreeWidget::mouseDoubleClickEvent(event);
        return;
    }
    emit showProperties(item->referencedClip());
247 248 249 250
}


// virtual
251 252
void ProjectListView::dropEvent(QDropEvent *event)
{
253
    FolderProjectItem *item = NULL;
254
    QTreeWidgetItem *it = itemAt(event->pos());
255
    while (it && it->type() != ProjectFoldeType) {
256
        it = it->parent();
257
    }
258
    if (it) item = static_cast <FolderProjectItem *>(it);
259 260
    if (event->mimeData()->hasUrls()) {
        QString groupName;
261
        QString groupId;
262
        if (item) {
263 264
            groupName = item->groupName();
            groupId = item->clipId();
265
        }
266
        emit addClip(event->mimeData()->urls(), groupName, groupId);
267 268
        event->setDropAction(Qt::CopyAction);
        event->accept();
269
        QTreeWidget::dropEvent(event);
270
        return;
271
    } else if (event->mimeData()->hasFormat("kdenlive/producerslist")) {
272
        if (item) {
273 274 275 276 277 278
            //emit addClip(event->mimeData->text());
            const QList <QTreeWidgetItem *> list = selectedItems();
            ProjectItem *clone;
            QString parentId = item->clipId();
            foreach(QTreeWidgetItem *it, list) {
                // TODO allow dragging of folders ?
279
                if (it->type() == ProjectClipType) {
Vincent Pinon's avatar
Vincent Pinon committed
280 281
                    if (it->parent()) clone = static_cast<ProjectItem*>(it->parent()->takeChild(it->parent()->indexOfChild(it)));
                    else clone = static_cast<ProjectItem*>(takeTopLevelItem(indexOfTopLevelItem(it)));
282
                    if (clone && item) {
283 284 285 286 287
                        item->addChild(clone);
                        QMap <QString, QString> props;
                        props.insert("groupname", item->groupName());
                        props.insert("groupid", parentId);
                        clone->setProperties(props);
288
                    }
289 290 291
                } else item = NULL;
            }
        } else {
292
            // item dropped in empty zone, move it to top level
293
            const QList <QTreeWidgetItem *> list = selectedItems();
294 295
            ProjectItem *clone;
            foreach(QTreeWidgetItem *it, list) {
296
                if (it->type() != ProjectClipType) continue;
297
                QTreeWidgetItem *parent = it->parent();
298
                if (parent/* && ((ProjectItem *) it)->clipId() < 10000*/)  {
299
                    kDebug() << "++ item parent: " << parent->text(1);
300 301 302 303 304 305
                    clone = static_cast <ProjectItem*>(parent->takeChild(parent->indexOfChild(it)));
                    if (clone) {
                        addTopLevelItem(clone);
                        clone->clearProperty("groupname");
                        clone->clearProperty("groupid");
                    }
306 307 308
                }
            }
        }
309
        emit projectModified();
310 311 312
    } else if (event->mimeData()->hasFormat("kdenlive/clip")) {
        QStringList list = QString(event->mimeData()->data("kdenlive/clip")).split(';');
        emit addClipCut(list.at(0), list.at(1).toInt(), list.at(2).toInt());
313
    }
314 315 316 317 318 319
    if (event->source() == this) {
	event->setDropAction(Qt::MoveAction);
        event->accept();
    } else {
	event->acceptProposedAction();
    }
320
    QTreeWidget::dropEvent(event);
321 322 323
}

// virtual
324 325
void ProjectListView::mousePressEvent(QMouseEvent *event)
{
326
    if (event->button() == Qt::LeftButton) {
327
        m_DragStartPosition = event->pos();
328
        m_dragStarted = true;
329
        /*QTreeWidgetItem *underMouse = itemAt(event->pos());
330 331 332 333 334 335 336 337 338
        ProjectItem *item = static_cast<ProjectItem *>(underMouse);
        if (item) {
            QRect itemRect = visualItemRect(item);
            if (item->underJobMenu(itemRect, event->pos())) {
                emit display
            }
            
            && underMouse->isSelected()) emit focusMonitor()
        }*/
339 340
    }
    QTreeWidget::mousePressEvent(event);
341 342
}

343 344 345 346
// virtual
void ProjectListView::mouseReleaseEvent(QMouseEvent *event)
{
    QTreeWidget::mouseReleaseEvent(event);
347
    QTreeWidgetItem *underMouse = itemAt(event->pos());
348
    if (underMouse) emit focusMonitor(true);
349
}
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
350

351
// virtual
352 353
void ProjectListView::mouseMoveEvent(QMouseEvent *event)
{
354
    //kDebug() << "// DRAG STARTED, MOUSE MOVED: ";
355
    if (!m_dragStarted) return;
356

357
    if ((event->pos() - m_DragStartPosition).manhattanLength()
358 359
            < QApplication::startDragDistance())
        return;
360

361 362
    QTreeWidgetItem *it = itemAt(m_DragStartPosition);
    if (!it) return;
363 364 365 366 367 368
    if (it && (it->flags() & Qt::ItemIsDragEnabled)) {
	QDrag *drag = new QDrag(this);
        QMimeData *mimeData = new QMimeData;
        const QList <QTreeWidgetItem *> list = selectedItems();
        QStringList ids;
        foreach(const QTreeWidgetItem *item, list) {
369
	    if (item->type() == ProjectFoldeType) {
370
		const int children = item->childCount();
371
                for (int i = 0; i < children; ++i) {
372
		    ids.append(static_cast <ProjectItem *>(item->child(i))->clipId());
373
                }
374
	    } else if (item->type() == ProjectSubclipType) {
375 376 377 378
		const ProjectItem *parentclip = static_cast <const ProjectItem *>(item->parent());
		const SubProjectItem *clickItem = static_cast <const SubProjectItem *>(item);
		QPoint p = clickItem->zone();
		QString data = parentclip->clipId();
Vincent Pinon's avatar
Vincent Pinon committed
379 380
		data.append('/' + QString::number(p.x()));
		data.append('/' + QString::number(p.y()));
381 382 383 384
		ids.append(data);
            } else {
		const ProjectItem *clip = static_cast <const ProjectItem *>(item);
                ids.append(clip->clipId());
385 386
            }
        }
387 388 389 390 391 392 393 394 395 396
        if (ids.isEmpty()) return;
        QByteArray data;
        data.append(ids.join(";").toUtf8()); //doc.toString().toUtf8());
        mimeData->setData("kdenlive/producerslist", data);
        //mimeData->setText(ids.join(";")); //doc.toString());
        //mimeData->setImageData(image);
        drag->setMimeData(mimeData);
        drag->setPixmap(it->data(0, Qt::DecorationRole).value<QPixmap>());
        drag->setHotSpot(QPoint(0, 40));
        drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
397 398 399
    }
}

400
// virtual
401
void ProjectListView::dragLeaveEvent(QDragLeaveEvent *event)
402
{
403 404
    // stop playing because we get a crash otherwise when fetching the thumbnails
    emit pauseMonitor();
405
    QTreeWidget::dragLeaveEvent(event);
406 407
}

408 409
QStringList ProjectListView::mimeTypes() const
{
410
    QStringList qstrList;
411
    qstrList << QTreeWidget::mimeTypes();
412 413 414
    // list of accepted mime types for drop
    qstrList.append("text/uri-list");
    qstrList.append("text/plain");
415
    qstrList.append("kdenlive/producerslist");
416
    qstrList.append("kdenlive/clip");
417 418
    return qstrList;
}
419 420


421 422
Qt::DropActions ProjectListView::supportedDropActions() const
{
423
    // returns what actions are supported when dropping
424
    return Qt::MoveAction | Qt::CopyAction;
425 426
}

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.column() == 0 && !index.data(ItemDelegate::DurationRole).isNull()) {
        QRect r1 = option.rect;
        painter->save();
        QStyleOptionViewItemV4 opt(option);
        QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
        style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);

        if (option.state & QStyle::State_Selected) {
            painter->setPen(option.palette.highlightedText().color());
        }
        const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
        QPixmap pixmap = qVariantValue<QPixmap>(index.data(Qt::DecorationRole));
        QPoint pixmapPoint(r1.left() + textMargin, r1.top() + (r1.height() - pixmap.height()) / 2);
        painter->drawPixmap(pixmapPoint, pixmap);
        int decoWidth = pixmap.width() + 2 * textMargin;

        QFont font = painter->font();
        font.setBold(true);
        painter->setFont(font);
        int mid = (int)((r1.height() / 2));
        r1.adjust(decoWidth, 0, 0, -mid);
        QRect r2 = option.rect;
        r2.adjust(decoWidth, mid, 0, 0);
        painter->drawText(r1, Qt::AlignLeft | Qt::AlignBottom, index.data().toString());
        font.setBold(false);
        painter->setFont(font);
        QString subText = index.data(ItemDelegate::DurationRole).toString();
        int usage = index.data(ItemDelegate::UsageRole).toInt();
        if (usage != 0) {
            subText.append(QString::fromLatin1(" (%1)").arg(usage));
        }

        QRectF bounding;
        painter->drawText(r2, Qt::AlignLeft | Qt::AlignVCenter , subText, &bounding);
        int jobProgress = index.data(Qt::UserRole + 5).toInt();
464 465
        if (jobProgress != 0 && jobProgress != JobDone && jobProgress != JobAborted) {
            if (jobProgress != JobCrashed) {
466 467 468 469 470 471 472 473 474
                // Draw job progress bar
                QColor color = option.palette.alternateBase().color();
                color.setAlpha(150);
                painter->setPen(option.palette.link().color());
                QRect progress(pixmapPoint.x() + 2, pixmapPoint.y() + pixmap.height() - 9, pixmap.width() - 4, 7);
                painter->setBrush(QBrush(color));
                painter->drawRect(progress);
                painter->setBrush(option.palette.link());
                progress.adjust(2, 2, -2, -2);
475
                if (jobProgress == JobWaiting) {
476 477 478 479 480 481 482 483 484
                    progress.setLeft(progress.right() - 2);
                    painter->drawRect(progress);
                    progress.moveLeft(progress.left() - 5);
                    painter->drawRect(progress);
                }
                else if (jobProgress > 0) {
                    progress.setWidth(progress.width() * jobProgress / 100);
                    painter->drawRect(progress);
                }
485
            } else if (jobProgress == JobCrashed) {
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
                QString jobText = index.data(Qt::UserRole + 7).toString();
                if (!jobText.isEmpty()) {
                    QRectF txtBounding = painter->boundingRect(r2, Qt::AlignRight | Qt::AlignVCenter, QLatin1Char(' ') + jobText + QLatin1Char(' ') );
                    painter->setPen(Qt::NoPen);
                    painter->setBrush(option.palette.highlight());
                    painter->drawRoundedRect(txtBounding, 2, 2);
                    painter->setPen(option.palette.highlightedText().color());
                    painter->drawText(txtBounding, Qt::AlignCenter, jobText);
                }
            }
        }

        painter->restore();
    } else if (index.column() == 2 && KdenliveSettings::activate_nepomuk()) {
        if (index.data().toString().isEmpty()) {
            QStyledItemDelegate::paint(painter, option, index);
            return;
        }
        QRect r1 = option.rect;
        if (option.state & (QStyle::State_Selected)) {
            painter->fillRect(r1, option.palette.highlight());
        }
#ifdef NEPOMUK
        KRatingPainter::paintRating(painter, r1, Qt::AlignCenter, index.data().toInt());
#endif
#ifdef NEPOMUKCORE
        KRatingPainter::paintRating(painter, r1, Qt::AlignCenter, index.data().toInt());
#endif

    } else {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
520
#include "projectlistview.moc"