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 }