File indexing completed on 2024-12-22 04:40:10
0001 /* 0002 SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar> 0003 SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "linesitemdelegate.h" 0009 #include "gui/treeview/lineswidget.h" 0010 #include "gui/treeview/richdocumentptr.h" 0011 #include "gui/treeview/richlineedit.h" 0012 0013 #include <QApplication> 0014 #include <QKeyEvent> 0015 #include <QPainter> 0016 #include <QTextOption> 0017 #include <QTextBlock> 0018 0019 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) 0020 #define horizontalAdvance width 0021 #endif 0022 0023 using namespace SubtitleComposer; 0024 0025 LinesItemDelegate::LinesItemDelegate(LinesWidget *parent) 0026 : QStyledItemDelegate(parent) 0027 { 0028 } 0029 0030 LinesItemDelegate::~LinesItemDelegate() 0031 { 0032 } 0033 0034 bool 0035 LinesItemDelegate::eventFilter(QObject *object, QEvent *event) 0036 { 0037 QWidget *editor = qobject_cast<QWidget *>(object); 0038 if(!editor) 0039 return false; 0040 0041 if(event->type() == QEvent::KeyPress) { 0042 switch(static_cast<QKeyEvent *>(event)->key()) { 0043 case Qt::Key_Tab: 0044 emit commitData(editor); 0045 emit closeEditor(editor, QAbstractItemDelegate::EditNextItem); 0046 return true; 0047 0048 case Qt::Key_Backtab: 0049 emit commitData(editor); 0050 emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem); 0051 return true; 0052 0053 case Qt::Key_Up: 0054 emit commitData(editor); 0055 emit closeEditor(editor, QAbstractItemDelegate::EndEditHint(EditUpperItem)); 0056 return true; 0057 0058 case Qt::Key_Down: 0059 emit commitData(editor); 0060 emit closeEditor(editor, QAbstractItemDelegate::EndEditHint(EditLowerItem)); 0061 return true; 0062 0063 case Qt::Key_Enter: 0064 case Qt::Key_Return: 0065 if(static_cast<QKeyEvent *>(event)->modifiers()) { 0066 emit commitData(editor); 0067 emit closeEditor(editor, QAbstractItemDelegate::EndEditHint(EditLowerItem)); 0068 return true; 0069 } 0070 if(qobject_cast<RichLineEdit *>(object)) 0071 return false; 0072 break; 0073 0074 default: 0075 break; 0076 } 0077 } 0078 0079 return QStyledItemDelegate::eventFilter(object, event); 0080 } 0081 0082 static const QIcon & markIcon() { static QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-warning")); return icon; } 0083 static const QIcon & errorIcon() { static QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-error")); return icon; } 0084 static const QIcon & anchorIcon() { static QIcon icon = QIcon::fromTheme(QStringLiteral("anchor")); return icon; } 0085 0086 inline static bool isRichDoc(const QModelIndex &index) { return index.column() >= LinesModel::Text; } 0087 0088 static void 0089 drawTextPrimitive(QPainter *painter, const QStyle *style, const QStyleOptionViewItem &option, const QRect &rect, QPalette::ColorGroup cg, const QModelIndex &index) 0090 { 0091 const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, option.widget) + 1; 0092 Qt::Alignment alignment = QStyle::visualAlignment(option.direction, option.displayAlignment); 0093 0094 QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding 0095 0096 if(index.column() == LinesModel::ShowTime && index.data(LinesModel::AnchoredRole).toInt() == -1) 0097 cg = QPalette::Disabled; 0098 0099 QColor textColor; 0100 if(option.state & QStyle::State_Selected) 0101 textColor = option.palette.color(cg, QPalette::HighlightedText); 0102 else 0103 textColor = option.palette.color(cg, QPalette::Text); 0104 0105 painter->setPen(textColor); 0106 0107 QString text = option.fontMetrics.elidedText(option.text, option.textElideMode, textRect.width()); 0108 0109 if(index.column() == LinesModel::Number && index.data(LinesModel::AnchoredRole).toInt() == 1) { 0110 int iconSize = qMin(textRect.width(), textRect.height()) - 3; 0111 QRect iconRect = QRect(textRect.right() - iconSize - 2, textRect.y() + 1, iconSize, iconSize); 0112 0113 QIcon::Mode mode = QIcon::Normal; 0114 if(!(option.state & QStyle::State_Enabled)) 0115 mode = QIcon::Disabled; 0116 else if(option.state & QStyle::State_Selected) 0117 mode = QIcon::Selected; 0118 QIcon::State state = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off; 0119 0120 textRect.setRight(textRect.right() - iconSize); 0121 0122 anchorIcon().paint(painter, iconRect, option.decorationAlignment, mode, state); 0123 } 0124 0125 if(index.column() && index.column() < LinesModel::Text) { 0126 int o = 0; 0127 // find start of non zero time 0128 for(int i = 0; ; i++) { 0129 if(i == text.length()) { 0130 o = i; 0131 break; 0132 } 0133 const QChar &ch = text.at(i); 0134 if(ch != QChar('0')) { 0135 if(ch != QChar(':') && ch != QChar('.')) 0136 break; 0137 o = i + 1; 0138 } 0139 } 0140 if(o) { 0141 // fix rect based on alignment 0142 if(alignment & Qt::AlignRight) { 0143 alignment = (alignment & ~Qt::AlignRight) | Qt::AlignLeft; 0144 textRect.setLeft(textRect.right() - painter->fontMetrics().horizontalAdvance(text)); 0145 } else if(alignment & Qt::AlignHCenter) { 0146 alignment = (alignment & ~Qt::AlignHCenter) | Qt::AlignLeft; 0147 const int w = painter->fontMetrics().horizontalAdvance(text); 0148 textRect.setLeft(textRect.left() + (textRect.width() - w) / 2); 0149 textRect.setRight(textRect.left() + w); 0150 } 0151 // draw zero time semi-transparent 0152 const QString sub = text.left(o); 0153 text.remove(0, o); 0154 QColor altColor = textColor; 0155 altColor.setAlpha(textColor.alpha() * 2 / 5); 0156 painter->setPen(altColor); 0157 painter->drawText(textRect, alignment, sub); 0158 painter->setPen(textColor); 0159 textRect.setLeft(textRect.left() + painter->fontMetrics().horizontalAdvance(sub)); 0160 } 0161 } 0162 painter->drawText(textRect, alignment, text); 0163 } 0164 0165 static void 0166 drawRichText(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) 0167 { 0168 painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); 0169 0170 const RichDocument *doc = option.index.data(Qt::DisplayRole).value<RichDocumentPtr>(); 0171 RichDocumentLayout *docLayout = doc->documentLayout(); 0172 0173 QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; 0174 if(cg == QPalette::Normal && !(option.state & QStyle::State_Active)) 0175 cg = QPalette::Inactive; 0176 painter->setPen(option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text)); 0177 0178 const QStyle *style = option.widget ? option.widget->style() : QApplication::style(); 0179 int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, option.widget) + 1; 0180 0181 QTextOption textOption; 0182 textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment)); 0183 textOption.setFlags(QTextOption::IncludeTrailingSpaces); 0184 textOption.setWrapMode(QTextOption::NoWrap); 0185 textOption.setTextDirection(option.direction); 0186 0187 const QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); 0188 qreal xOff = textRect.left(); 0189 0190 // prepare line seprator 0191 const qreal sepWidth = qreal(textRect.height()) / 2.; 0192 docLayout->separatorResize(QSizeF(sepWidth, textRect.height())); 0193 0194 // layout and draw text 0195 for(QTextBlock bi = doc->begin(); bi != doc->end(); bi = bi.next()) { 0196 QTextLayout bl; 0197 bl.setCacheEnabled(true); 0198 bl.setFont(option.font); 0199 bl.setTextOption(textOption); 0200 QString text = bi.text() + QChar(QChar::LineSeparator); 0201 // replace certain non-printable characters with spaces (to avoid drawing boxes 0202 // when using fonts that don't have glyphs for such characters) 0203 QChar *uc = text.data(); 0204 for(int i = 0; i < (int)text.length(); ++i) { 0205 if((uc[i].unicode() < 0x20 && uc[i].unicode() != 0x09) 0206 || uc[i] == QChar::LineSeparator 0207 || uc[i] == QChar::ParagraphSeparator 0208 || uc[i] == QChar::ObjectReplacementCharacter) 0209 uc[i] = QChar(QChar::Space); 0210 } 0211 bl.setText(text); 0212 bl.setFormats(docLayout->applyCSS(bi.textFormats())); 0213 bl.beginLayout(); 0214 for(;;) { 0215 QTextLine line = bl.createLine(); 0216 if(!line.isValid()) 0217 break; 0218 line.setLeadingIncluded(true); 0219 line.setLineWidth(10000); 0220 line.setPosition(QPointF(xOff, textRect.top() + (qreal(textRect.height()) - line.height()) / 2.)); 0221 const int w = line.naturalTextWidth(); 0222 xOff += w + sepWidth; 0223 line.setLineWidth(w); 0224 } 0225 bl.endLayout(); 0226 0227 const int n = bl.lineCount(); 0228 for(int i = 0; i < n; i++) { 0229 const QTextLine &tl = bl.lineAt(i); 0230 tl.draw(painter, QPointF()); 0231 docLayout->separatorDraw(painter, QPointF(tl.position().x() - sepWidth, tl.position().y() - tl.descent())); 0232 } 0233 } 0234 } 0235 0236 void 0237 LinesItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const 0238 { 0239 QStyleOptionViewItem option = opt; 0240 initStyleOption(&option, index); 0241 0242 const QWidget *widget = option.widget; 0243 const QStyle *style = widget ? widget->style() : QApplication::style(); 0244 0245 painter->save(); 0246 painter->setClipRect(option.rect); 0247 0248 QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, widget); 0249 0250 QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; 0251 if(cg == QPalette::Normal && !(option.state & QStyle::State_Active)) 0252 cg = QPalette::Inactive; 0253 0254 // draw the background 0255 if(index.data(LinesModel::PlayingLineRole).toBool()) { 0256 const bool sel = option.state & QStyle::State_Selected; 0257 QColor bg = option.palette.color(cg, sel ? QPalette::Highlight : QPalette::Base); 0258 QColor fg = option.palette.color(cg, sel ? QPalette::HighlightedText : QPalette::Text); 0259 const int ld = fg.value() - bg.value(); 0260 if(ld > 0) 0261 bg = bg.lighter(100 + ld / 2); 0262 else 0263 bg = bg.darker(100 + ld / -9); 0264 option.palette.setColor(cg, QPalette::Highlight, bg); 0265 option.state |= QStyle::State_Selected; 0266 } 0267 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget); 0268 0269 // draw the icon(s) 0270 const bool showMarkedIcon = index.data(LinesModel::MarkedRole).toBool(); 0271 const bool showErrorIcon = index.data(LinesModel::ErrorRole).toBool(); 0272 0273 if(showMarkedIcon || showErrorIcon) { 0274 int iconSize = qMin(textRect.width(), textRect.height()) - 3; 0275 QRect iconRect = QRect(textRect.x() + 2, textRect.y() + 1, iconSize, iconSize); 0276 0277 QIcon::Mode mode = QIcon::Normal; 0278 if(!(option.state & QStyle::State_Enabled)) 0279 mode = QIcon::Disabled; 0280 else if(option.state & QStyle::State_Selected) 0281 mode = QIcon::Selected; 0282 QIcon::State state = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off; 0283 0284 if(showMarkedIcon) { 0285 markIcon().paint(painter, iconRect, option.decorationAlignment, mode, state); 0286 textRect.setX(textRect.x() + iconSize + 2); 0287 if(showErrorIcon) 0288 iconRect.translate(iconSize + 2, 0); 0289 } 0290 0291 if(showErrorIcon) { 0292 errorIcon().paint(painter, iconRect, option.decorationAlignment, mode, state); 0293 textRect.setX(textRect.x() + iconSize + 2); 0294 } 0295 } 0296 // draw the text 0297 if(isRichDoc(index) || !option.text.isEmpty()) { 0298 if(option.state & QStyle::State_Editing) { 0299 painter->setPen(option.palette.color(cg, QPalette::Text)); 0300 painter->drawRect(textRect.adjusted(0, 0, -1, -1)); 0301 } else if(isRichDoc(index)) { 0302 drawRichText(painter, option, textRect); 0303 } else { 0304 drawTextPrimitive(painter, style, option, textRect, cg, index); 0305 } 0306 } 0307 // draw the focus rect 0308 if(option.state & QStyle::State_HasFocus) { 0309 QStyleOptionFocusRect frOption; 0310 frOption.QStyleOption::operator=(option); 0311 frOption.rect = style->subElementRect(QStyle::SE_ItemViewItemFocusRect, &option, widget); 0312 frOption.state |= QStyle::State_KeyboardFocusChange; 0313 frOption.state |= QStyle::State_Item; 0314 frOption.backgroundColor = option.palette.color((option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); 0315 0316 style->drawPrimitive(QStyle::PE_FrameFocusRect, &frOption, painter, widget); 0317 } 0318 0319 painter->restore(); 0320 } 0321 0322 QString 0323 LinesItemDelegate::displayText(const QVariant &value, const QLocale &locale) const 0324 { 0325 if(value.userType() == qMetaTypeId<RichDocumentPtr>()) 0326 return QString(); 0327 return QStyledItemDelegate::displayText(value, locale); 0328 } 0329 0330 QWidget * 0331 LinesItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const 0332 { 0333 if(isRichDoc(index)) { 0334 RichLineEdit *ed = new RichLineEdit(option, parent); 0335 linesWidget()->m_inlineEditor = ed; 0336 connect(ed, &QObject::destroyed, this, [this, ed](){ 0337 if(linesWidget()->m_inlineEditor == ed) 0338 linesWidget()->m_inlineEditor = nullptr; 0339 }); 0340 return ed; 0341 } 0342 return QStyledItemDelegate::createEditor(parent, option, index); 0343 } 0344 0345 void 0346 LinesItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 0347 { 0348 if(isRichDoc(index)) { 0349 RichDocument *doc = index.data(Qt::EditRole).value<RichDocumentPtr>(); 0350 if(!doc) 0351 return; 0352 RichLineEdit *edit = static_cast<RichLineEdit *>(editor); 0353 if(edit->document() != doc) 0354 edit->setDocument(doc); 0355 } else { 0356 QStyledItemDelegate::setEditorData(editor, index); 0357 } 0358 }