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 }