File indexing completed on 2024-05-26 04:32:46

0001 /*
0002   SPDX-FileCopyrightText: 2020 Saurabh Kumar <saurabhk660@gmail.com>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <QDebug>
0008 #include <QPainter>
0009 #include <QPaintEvent>
0010 #include <QMenu>
0011 #include <QProxyStyle>
0012 #include <QStyleFactory>
0013 
0014 #include "StoryboardView.h"
0015 #include "StoryboardModel.h"
0016 #include "StoryboardDelegate.h"
0017 #include "KisAddRemoveStoryboardCommand.h"
0018 
0019 class StoryboardStyle : public QProxyStyle
0020 {
0021 public:
0022     StoryboardStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {}
0023 
0024     void drawPrimitive(PrimitiveElement element,
0025                        const QStyleOption *option,
0026                        QPainter *painter,
0027                        const QWidget *widget) const override
0028     {
0029         if (element == QStyle::PE_IndicatorItemViewItemDrop)
0030         {
0031             QColor color(widget->palette().color(QPalette::Highlight).lighter());
0032             if (option->rect.width() == 0 && option->rect.height() == 0){
0033                 return;
0034             }
0035             else if (option->rect.width() == 0) {
0036                 QBrush brush(color);
0037 
0038                 QRect r(option->rect);
0039                 r.setLeft(r.left() - 4);
0040                 r.setRight(r.right() + 4);
0041 
0042                 painter->fillRect(r, brush);
0043             }
0044             else if (option->rect.height() == 0) {
0045                 QBrush brush(color);
0046 
0047                 QRect r(option->rect);
0048                 r.setTop(r.top() - 4);
0049                 r.setBottom(r.bottom() + 4);
0050 
0051                 painter->fillRect(r, brush);
0052             }
0053         }
0054         else
0055         {
0056             QProxyStyle::drawPrimitive(element, option, painter, widget);
0057         }
0058     }
0059 };
0060 
0061 /**
0062  * This view draws the children of every index in the first column of 
0063  * the model inside the parent
0064  *
0065  * */
0066 
0067 StoryboardView::StoryboardView(QWidget *parent)
0068     : QListView(parent)
0069     , m_itemOrientation(Qt::Vertical)
0070     , m_commentIsVisible(true)
0071     , m_thumbnailIsVisible(true)
0072 {
0073     setSelectionBehavior(SelectRows);
0074     setDefaultDropAction(Qt::MoveAction);
0075     setResizeMode(QListView::Adjust);
0076     setUniformItemSizes(true);
0077     setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0078     setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
0079     QWidget::setMouseTracking(true);
0080     setContextMenuPolicy(Qt::CustomContextMenu);
0081     setDragEnabled(true);
0082     viewport()->setAcceptDrops(true);
0083     setDropIndicatorShown(true);
0084     setDragDropMode(QAbstractItemView::InternalMove);
0085 
0086     QStyle *newStyle = QStyleFactory::create(this->style()->objectName());
0087     // proxy style steals the ownership of the style and deletes it later
0088     StoryboardStyle *proxyStyle = new StoryboardStyle(newStyle);
0089     proxyStyle->setParent(this);
0090     setStyle(proxyStyle);
0091 
0092     connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
0093                 this, SLOT(slotContextMenuRequested(const QPoint &)));
0094 
0095     connect(this, &StoryboardView::clicked,
0096             this, &StoryboardView::slotItemClicked);
0097 }
0098 
0099 StoryboardView::~StoryboardView()
0100 {}
0101 
0102 void StoryboardView::paintEvent(QPaintEvent *event)
0103 {
0104     event->accept();
0105     QListView::paintEvent(event);
0106 
0107     //ask delegate to draw the child nodes too
0108     QPainter painter(viewport());
0109     int itemNum = model()->rowCount();
0110     for (int row = 0; row < itemNum; row++) {
0111         QModelIndex index = model()->index(row, 0);
0112         int childNum = model()->rowCount(index);
0113         for (int childRow = 0; childRow < childNum; childRow++) {
0114 
0115             QModelIndex childIndex = model()->index(childRow, 0, index);
0116 
0117             QStyleOptionViewItem option;
0118             if (selectionModel()->isSelected(childIndex)) {
0119                 option.state |= QStyle::State_Selected;
0120             }
0121             if (childIndex == selectionModel()->currentIndex()) {
0122                 option.state |= QStyle::State_HasFocus;
0123             }
0124             option.font = font();
0125             option.fontMetrics = fontMetrics();
0126             option.rect = visualRect(childIndex);
0127             itemDelegate()->paint(&painter, option, childIndex);
0128         }
0129     }
0130 }
0131 
0132 QRect StoryboardView::visualRect(const QModelIndex &index) const
0133 {
0134     /*
0135      *    fw = fontWidth
0136      * 
0137      *  (3*fw+2),        (5*fw+10)  _____ (4*fw+10)
0138      *    |                |       /
0139      *    |                |      /
0140      *   ,_________________________,
0141      *   |__|_____________|____|___|  ---------->(fontHeight)
0142      *   |                         |
0143      *   |                         |
0144      *   |                         |
0145      *   |                         |
0146      *   |_________________________|
0147      */
0148 
0149     if (!index.isValid() || !index.parent().isValid()) {
0150         return QListView::visualRect(index);
0151     }
0152     else {
0153         QRect parentRect = visualRect(index.parent());
0154         parentRect.setTopLeft(parentRect.topLeft() + QPoint(5, 5));
0155         parentRect.setBottomRight(parentRect.bottomRight() - QPoint(5, 5));
0156         int fontHeight = fontMetrics().height() + 3;
0157 #if QT_VERSION >= QT_VERSION_CHECK(5,11,0)
0158         int numericFontWidth = fontMetrics().horizontalAdvance("0");
0159 #else
0160     int numericFontWidth = fontMetrics().width("0");
0161 #endif
0162 
0163 
0164         int parentWidth = parentRect.width();
0165         int childRow = index.row();
0166 
0167         int thumbnailWidth = parentWidth;
0168         if (m_itemOrientation == Qt::Horizontal) {
0169             thumbnailWidth = 250;
0170         }
0171         switch (childRow)
0172         {
0173             case StoryboardItem::FrameNumber:
0174             {   
0175                 //the frame thumbnail rect
0176                 if (!thumbnailIsVisible()) {
0177                     parentRect.setSize(QSize(3*numericFontWidth + 2, fontHeight));
0178                     return parentRect;
0179                 }
0180 
0181                 parentRect.setSize(QSize(thumbnailWidth, 120));
0182                 parentRect.translate(0, fontHeight);
0183                 return parentRect;
0184             }
0185             case StoryboardItem::ItemName:
0186             {
0187                 QRect itemNameRect = parentRect;
0188                 itemNameRect.setSize(QSize(thumbnailWidth - (12 * numericFontWidth + 22), fontHeight));
0189                 itemNameRect.moveLeft(parentRect.left() + 3*numericFontWidth + 2);
0190                 return itemNameRect;
0191             }
0192             case StoryboardItem::DurationSecond:
0193             {
0194                 QRect secondRect = parentRect;
0195                 secondRect.setSize(QSize(5 * numericFontWidth + 10, fontHeight));
0196                 secondRect.moveLeft(parentRect.left() + thumbnailWidth - 9*numericFontWidth  -20);
0197                 return secondRect;
0198             }
0199             case StoryboardItem::DurationFrame:
0200             {
0201                 QRect frameRect = parentRect;
0202                 frameRect.setSize(QSize(4 * numericFontWidth + 10, fontHeight));
0203                 frameRect.moveLeft(parentRect.left() + thumbnailWidth - 4*numericFontWidth  - 10);
0204                 return frameRect;
0205             }
0206             default:
0207             {
0208                 //comment rect
0209                 if (!commentIsVisible()) {
0210                     return QRect();
0211                 }
0212 
0213                 int thumbnailheight = thumbnailIsVisible() ? 120 : 0;
0214                 if (m_itemOrientation == Qt::Vertical) {
0215                     const StoryboardModel* Model = dynamic_cast<const StoryboardModel*>(model());
0216                     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(Model, QRect());
0217                     parentRect.setTop(parentRect.top() + thumbnailheight + fontHeight + Model->visibleCommentsUpto(index) * 100);
0218                     parentRect.setHeight(100);
0219                     return parentRect;
0220                 }
0221                 else {
0222                     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(model(), QRect());
0223                     const StoryboardModel* storyboardModel = dynamic_cast<const StoryboardModel*>(model());
0224                     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(storyboardModel, QRect());
0225                     int numVisibleComments = storyboardModel->visibleCommentCount();
0226                     int commentWidth = 200;
0227                     if (numVisibleComments) {
0228                         commentWidth = qMax(200, (viewport()->width() - 250) / numVisibleComments);
0229                     }
0230                     parentRect.setSize(QSize(commentWidth, thumbnailheight + fontHeight));
0231                     parentRect.moveLeft(parentRect.left() + thumbnailWidth + storyboardModel->visibleCommentsUpto(index) * commentWidth);
0232                     return parentRect;
0233                 }
0234             }
0235         }
0236     }
0237 }
0238 
0239 QModelIndex StoryboardView::indexAt(const QPoint &point) const
0240 {
0241     QModelIndex index = QListView::indexAt(point);
0242     if (index.isValid()) {
0243         //look for the index in children of the current index
0244         int numChild = model()->rowCount(index);
0245         for (int row = 0; row < numChild; row++) {
0246             QRect childRect = visualRect(model()->index(row, 0, index));
0247             if (childRect.contains(point)) {
0248                 return model()->index(row, 0, index);
0249             }
0250         }
0251     }
0252     return index;
0253 }
0254 
0255 void StoryboardView::setItemOrientation(Qt::Orientation orientation)
0256 {
0257     m_itemOrientation = orientation;
0258 }
0259 
0260 Qt::Orientation StoryboardView::itemOrientation()
0261 {
0262     return m_itemOrientation;
0263 }
0264 
0265 bool StoryboardView::commentIsVisible() const
0266 {
0267     return m_commentIsVisible;
0268 }
0269 
0270 bool StoryboardView::thumbnailIsVisible() const
0271 {
0272     return m_thumbnailIsVisible;
0273 }
0274 
0275 void StoryboardView::setCommentVisibility(bool value)
0276 {
0277     m_commentIsVisible = value;
0278 }
0279 
0280 void StoryboardView::setThumbnailVisibility(bool value)
0281 {
0282     m_thumbnailIsVisible = value;
0283 }
0284 
0285 void StoryboardView::slotContextMenuRequested(const QPoint &point)
0286 {
0287     StoryboardModel* pModel = dynamic_cast<StoryboardModel*>(model());
0288     QMenu contextMenu;
0289     QModelIndex index = indexAt(point);
0290     if (!index.isValid()) {
0291         contextMenu.addAction(i18nc("Add new scene as the last storyboard", "Add Scene"), [index, pModel] {pModel->insertItem(index, false); });
0292     }
0293     else if (index.parent().isValid()) {
0294         index = index.parent();
0295     }
0296 
0297     if (index.isValid()) {
0298         contextMenu.addAction(i18nc("Add scene after active scene", "Add Scene After"), [index, pModel] {pModel->insertItem(index, true); });
0299         if (index.row() > 0) {
0300             contextMenu.addAction(i18nc("Add scene before active scene", "Add Scene Before"), [index, pModel] {pModel->insertItem(index, false); });
0301         }
0302 
0303         contextMenu.addAction(i18nc("Duplicate current scene from storyboard docker", "Duplicate Scene"), [index, pModel] {
0304            int row = index.row();
0305            KisDuplicateStoryboardCommand *command = new KisDuplicateStoryboardCommand(row, pModel);
0306            command->redo();
0307            pModel->pushUndoCommand(command);
0308         });
0309 
0310         contextMenu.addAction(i18nc("Remove current scene from storyboards", "Remove Scene"), [index, pModel] {
0311             int row = index.row();
0312             KisRemoveStoryboardCommand *command = new KisRemoveStoryboardCommand(row, pModel->getData().at(row), pModel);
0313             pModel->removeItem(index, command);
0314             pModel->pushUndoCommand(command);
0315         });
0316     }
0317     contextMenu.exec(viewport()->mapToGlobal(point));
0318 }
0319 
0320 void StoryboardView::slotItemClicked(const QModelIndex &clicked)
0321 {
0322     StoryboardModel* sbModel = dynamic_cast<StoryboardModel*>(model());
0323 
0324     if(sbModel) {
0325         sbModel->visualizeScene(clicked.parent().isValid() ? clicked.parent() : clicked);
0326     }
0327 }
0328 
0329 void StoryboardView::setCurrentItem(int frame)
0330 {
0331     KIS_SAFE_ASSERT_RECOVER_RETURN(model());
0332     const StoryboardModel* sbModel = dynamic_cast<const StoryboardModel*>(model());
0333     KIS_SAFE_ASSERT_RECOVER_RETURN(sbModel);
0334     QModelIndex index = sbModel->indexFromFrame(frame);
0335     if (index.isValid()) {
0336         selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
0337         selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
0338         scrollTo(index);
0339     }
0340 }
0341 
0342 void StoryboardView::mouseReleaseEvent(QMouseEvent *event) {
0343     QModelIndex index = indexAt(event->pos());
0344 
0345     // To prevent selection changes from occurring when hitting the "plus" button,
0346     // we want to filter out these inputs before passing it up to QListView / QAbstractItemView
0347     if (index.isValid() && index.parent().isValid() && index.row() == StoryboardItem::FrameNumber) {
0348         StoryboardDelegate* sbDelegate = dynamic_cast<StoryboardDelegate*>(itemDelegate(index));
0349         QRect itemRect = visualRect(index);
0350         if (sbDelegate && sbDelegate->isOverlappingActionIcons(itemRect, event)) {
0351             return;
0352         }
0353     }
0354 
0355     QListView::mouseReleaseEvent(event);
0356 }
0357 
0358 QSize StoryboardView::sizeHint() const {
0359     if (model()) {
0360         StoryboardModel* m_storyboardModel = static_cast<StoryboardModel*>(model());
0361         const bool hasContent = m_storyboardModel->hasIndex(0,0);
0362         if (hasContent) {
0363             const bool hasComments = m_storyboardModel->visibleCommentCount() > 0;
0364             const bool hasMoreThanOneComment = m_storyboardModel->visibleCommentCount() > 1;
0365             const float commentPadding = hasComments ? 1.0f + (0.1f * hasMoreThanOneComment) : 0.0f;
0366             const int thumbnailWidth = 286;
0367             const int commentWidth = 200 * commentPadding;
0368             return QSize(thumbnailWidth + commentWidth, 128);
0369         }
0370     }
0371 
0372     return QSize(250, 128);
0373 }