File indexing completed on 2024-12-22 04:40:11
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 "lineswidget.h" 0009 0010 #include "appglobal.h" 0011 #include "application.h" 0012 #include "core/richtext/richdocument.h" 0013 #include "actions/useractionnames.h" 0014 #include "dialogs/actionwithtargetdialog.h" 0015 #include "gui/treeview/linesitemdelegate.h" 0016 #include "gui/treeview/linesselectionmodel.h" 0017 0018 #include <QHeaderView> 0019 #include <QMenu> 0020 #include <QMimeData> 0021 #include <QMouseEvent> 0022 #include <QPainter> 0023 0024 #include <KConfigGroup> 0025 #include <KSharedConfig> 0026 0027 0028 #define RESTORE_STATE false 0029 0030 using namespace SubtitleComposer; 0031 0032 LinesWidget::LinesWidget(QWidget *parent) 0033 : TreeView(parent), 0034 m_scrollFollowsModel(true), 0035 m_translationMode(false), 0036 m_showingContextMenu(false), 0037 m_itemsDelegate(new LinesItemDelegate(this)), 0038 m_inlineEditor(nullptr) 0039 { 0040 setModel(new LinesModel(this)); 0041 selectionModel()->deleteLater(); 0042 setSelectionModel(new LinesSelectionModel(model())); 0043 0044 for(int column = 0, columnCount = model()->columnCount(); column < columnCount; ++column) 0045 setItemDelegateForColumn(column, m_itemsDelegate); 0046 0047 QHeaderView *hdr = header(); 0048 hdr->setSectionsClickable(false); 0049 hdr->setSectionsMovable(false); 0050 updateHeader(); 0051 0052 setUniformRowHeights(true); 0053 setItemsExpandable(false); 0054 setAutoExpandDelay(-1); 0055 setExpandsOnDoubleClick(false); 0056 setRootIsDecorated(false); 0057 setAllColumnsShowFocus(true); 0058 setSortingEnabled(false); 0059 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); 0060 0061 setSelectionMode(QAbstractItemView::ExtendedSelection); 0062 setSelectionBehavior(QAbstractItemView::SelectRows); 0063 0064 m_gridPen.setColor(palette().mid().color().lighter(120)); 0065 0066 setAcceptDrops(true); 0067 viewport()->installEventFilter(this); 0068 0069 connect(selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LinesWidget::onCurrentRowChanged); 0070 } 0071 0072 LinesWidget::~LinesWidget() 0073 { 0074 } 0075 0076 void 0077 LinesWidget::updateHeader() 0078 { 0079 QHeaderView *hdr = header(); 0080 for(int i = 0; i < LinesModel::ColumnCount; i++) { 0081 switch(i) { 0082 case LinesModel::Text: 0083 hdr->setSectionResizeMode(i, QHeaderView::Stretch); 0084 hdr->setSectionHidden(i, false); 0085 break; 0086 case LinesModel::Translation: 0087 hdr->setSectionResizeMode(i, QHeaderView::Stretch); 0088 hdr->setSectionHidden(i, !m_translationMode); 0089 break; 0090 default: 0091 hdr->setSectionResizeMode(i, QHeaderView::ResizeToContents); 0092 hdr->setSectionHidden(i, false); 0093 break; 0094 } 0095 } 0096 // do this last so columns get autosized 0097 hdr->setSectionResizeMode(LinesModel::Text, QHeaderView::Interactive); 0098 hdr->setSectionResizeMode(LinesModel::Translation, QHeaderView::Interactive); 0099 // give some small size to last column, it will stretch anyways 0100 hdr->resizeSection(LinesModel::Translation, 100); 0101 } 0102 0103 void 0104 LinesWidget::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) 0105 { 0106 auto ehint = LinesItemDelegate::ExtendedEditHint(hint); 0107 if(ehint == LinesItemDelegate::NoHint || ehint == LinesItemDelegate::SubmitModelCache || ehint == LinesItemDelegate::RevertModelCache) { 0108 TreeView::closeEditor(editor, hint); 0109 } else { 0110 QModelIndex index = currentIndex(); 0111 0112 switch(ehint) { 0113 case LinesItemDelegate::EditPreviousItem: 0114 if(m_translationMode) { 0115 if(index.column() == LinesModel::Translation) 0116 index = index.sibling(index.row(), LinesModel::Text); 0117 else if(index.row()) 0118 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) 0119 index = index.sibling(moveCursor(MoveUp, Qt::NoModifier).row(), LinesModel::Translation); 0120 #else 0121 index = moveCursor(MoveUp, Qt::NoModifier).siblingAtColumn(LinesModel::Translation); 0122 #endif 0123 break; 0124 } 0125 // fallthrough 0126 case LinesItemDelegate::EditUpperItem: 0127 index = moveCursor(MoveUp, Qt::NoModifier); 0128 break; 0129 case LinesItemDelegate::EditNextItem: 0130 if(m_translationMode) { 0131 if(index.column() == LinesModel::Text) 0132 index = index.sibling(index.row(), LinesModel::Translation); 0133 else if(index.row() < model()->rowCount() - 1) 0134 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) 0135 index = index.sibling(moveCursor(MoveDown, Qt::NoModifier).row(), LinesModel::Text); 0136 #else 0137 index = moveCursor(MoveDown, Qt::NoModifier).siblingAtColumn(LinesModel::Text); 0138 #endif 0139 break; 0140 } 0141 // fallthrough 0142 case LinesItemDelegate::EditLowerItem: 0143 index = moveCursor(MoveDown, Qt::NoModifier); 0144 break; 0145 default: break; 0146 } 0147 0148 QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::NoUpdate; 0149 if(selectionMode() != NoSelection) { 0150 flags = QItemSelectionModel::ClearAndSelect; 0151 switch(selectionBehavior()) { 0152 case SelectItems: break; 0153 case SelectRows: flags |= QItemSelectionModel::Rows; break; 0154 case SelectColumns: flags |= QItemSelectionModel::Columns; break; 0155 } 0156 } 0157 0158 TreeView::closeEditor(editor, QAbstractItemDelegate::NoHint); 0159 0160 QPersistentModelIndex persistent(index); 0161 selectionModel()->setCurrentIndex(persistent, flags); 0162 0163 // currentChanged signal would have already started editing 0164 if((index.flags() & Qt::ItemIsEditable) && !(editTriggers() & QAbstractItemView::CurrentChanged)) 0165 edit(persistent); 0166 } 0167 } 0168 0169 void 0170 LinesWidget::editCurrentLineInPlace(bool primaryText) 0171 { 0172 QModelIndex curIdx = currentIndex(); 0173 if(curIdx.isValid()) { 0174 const int col = primaryText || !m_translationMode ? LinesModel::Text : LinesModel::Translation; 0175 if(col != curIdx.column()) { 0176 curIdx = model()->index(curIdx.row(), col); 0177 setCurrentIndex(curIdx); 0178 } 0179 edit(curIdx); 0180 } 0181 } 0182 0183 bool 0184 LinesWidget::showingContextMenu() 0185 { 0186 return m_showingContextMenu; 0187 } 0188 0189 SubtitleLine * 0190 LinesWidget::currentLine() const 0191 { 0192 return qobject_cast<LinesSelectionModel *>(selectionModel())->currentLine(); 0193 } 0194 0195 int 0196 LinesWidget::currentLineIndex() const 0197 { 0198 const QModelIndex idx = currentIndex(); 0199 return idx.isValid() ? idx.row() : -1; 0200 } 0201 0202 int 0203 LinesWidget::firstSelectedIndex() const 0204 { 0205 int row = -1; 0206 const QItemSelection &selection = selectionModel()->selection(); 0207 for(const QItemSelectionRange &r : selection) { 0208 if(row == -1 || r.top() < row) 0209 row = r.top(); 0210 } 0211 return row; 0212 } 0213 0214 int 0215 LinesWidget::lastSelectedIndex() const 0216 { 0217 int row = -1; 0218 const QItemSelection &selection = selectionModel()->selection(); 0219 for(const QItemSelectionRange &r : selection) { 0220 if(r.bottom() > row) 0221 row = r.bottom(); 0222 } 0223 return row; 0224 } 0225 0226 bool 0227 LinesWidget::selectionHasMultipleRanges() const 0228 { 0229 const QItemSelection &selection = selectionModel()->selection(); 0230 return selection.size() > 1; 0231 } 0232 0233 RangeList 0234 LinesWidget::selectionRanges() const 0235 { 0236 RangeList ranges; 0237 0238 const QItemSelection &selection = selectionModel()->selection(); 0239 for(const QItemSelectionRange &r : selection) { 0240 ranges << Range(r.top(), r.bottom()); 0241 } 0242 0243 return ranges; 0244 } 0245 0246 RangeList 0247 LinesWidget::targetRanges(int target) const 0248 { 0249 switch(target) { 0250 case ActionWithTargetDialog::AllLines: 0251 return Range::full(); 0252 case ActionWithTargetDialog::Selection: return selectionRanges(); 0253 case ActionWithTargetDialog::FromSelected: { 0254 int index = firstSelectedIndex(); 0255 return index < 0 ? RangeList() : Range::upper(index); 0256 } 0257 case ActionWithTargetDialog::UpToSelected: { 0258 int index = lastSelectedIndex(); 0259 return index < 0 ? RangeList() : Range::lower(index); 0260 } 0261 default: 0262 return RangeList(); 0263 } 0264 } 0265 0266 void 0267 LinesWidget::loadConfig() 0268 { 0269 #if RESTORE_STATE 0270 KConfigGroup group(KSharedConfig::openConfig()->group("LinesWidget Settings")); 0271 0272 QByteArray state; 0273 QStringList strState = group.readXdgListEntry("Columns State", QStringList() << QString()); 0274 for(QStringList::ConstIterator it = strState.constBegin(), end = strState.constEnd(); it != end; ++it) 0275 state.append(char(it->toInt())); 0276 header()->restoreState(state); 0277 #endif 0278 } 0279 0280 void 0281 LinesWidget::saveConfig() 0282 { 0283 KConfigGroup group(KSharedConfig::openConfig()->group("LinesWidget Settings")); 0284 0285 QStringList strState; 0286 QByteArray state = header()->saveState(); 0287 for(int index = 0, size = state.size(); index < size; ++index) 0288 strState.append(QString::number(state[index])); 0289 group.writeXdgListEntry("Columns State", strState); 0290 } 0291 0292 void 0293 LinesWidget::setSubtitle(Subtitle *subtitle) 0294 { 0295 model()->setSubtitle(subtitle); 0296 } 0297 0298 void 0299 LinesWidget::setTranslationMode(bool enabled) 0300 { 0301 if(m_translationMode == enabled) 0302 return; 0303 0304 m_translationMode = enabled; 0305 updateHeader(); 0306 } 0307 0308 void 0309 LinesWidget::setCurrentLine(SubtitleLine *line, bool clearSelection) 0310 { 0311 if(!line) 0312 return; 0313 0314 selectionModel()->setCurrentIndex(model()->index(line->index(), 0), 0315 QItemSelectionModel::Select | QItemSelectionModel::Rows 0316 | (clearSelection ? QItemSelectionModel::Clear : QItemSelectionModel::NoUpdate)); 0317 } 0318 0319 void 0320 LinesWidget::setPlayingLine(SubtitleLine *line) 0321 { 0322 model()->setPlayingLine(line); 0323 } 0324 0325 void 0326 LinesWidget::mouseDoubleClickEvent(QMouseEvent *e) 0327 { 0328 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0329 QModelIndex index = indexAt(viewport()->mapFromGlobal(e->globalPos())); 0330 #else 0331 QModelIndex index = indexAt(viewport()->mapFromGlobal(e->globalPosition()).toPoint()); 0332 #endif 0333 if(index.isValid()) 0334 emit lineDoubleClicked(model()->subtitle()->line(index.row())); 0335 } 0336 0337 bool 0338 LinesWidget::eventFilter(QObject *object, QEvent *event) 0339 { 0340 if(object == viewport()) { 0341 switch(event->type()) { 0342 case QEvent::DragEnter: 0343 case QEvent::Drop: 0344 foreach(const QUrl &url, static_cast<QDropEvent *>(event)->mimeData()->urls()) { 0345 if(url.scheme() == QLatin1String("file")) { 0346 event->accept(); 0347 if(event->type() == QEvent::Drop) { 0348 app()->openSubtitle(url); 0349 } 0350 return true; // eat event 0351 } 0352 } 0353 event->ignore(); 0354 return true; 0355 0356 case QEvent::DragMove: 0357 return true; // eat event 0358 0359 default: 0360 ; 0361 } 0362 } 0363 // standard event processing 0364 return TreeView::eventFilter(object, event); 0365 } 0366 0367 inline static void 0368 addAppAction(QMenu *menu, const char *actionName, bool checkable=false, bool checked=false) 0369 { 0370 QAction *action = app()->action(actionName); 0371 if(checkable) { 0372 action->setCheckable(true); 0373 action->setChecked(checked); 0374 } 0375 menu->addAction(action); 0376 if(checkable) 0377 action->setCheckable(false); 0378 } 0379 0380 void 0381 LinesWidget::contextMenuEvent(QContextMenuEvent *e) 0382 { 0383 SubtitleLine *subLine = static_cast<LinesSelectionModel *>(selectionModel())->currentLine(); 0384 0385 QMenu menu; 0386 addAppAction(&menu, ACT_SELECT_ALL_LINES); 0387 addAppAction(&menu, ACT_EDIT_CURRENT_LINE_IN_PLACE); 0388 menu.addSeparator(); 0389 addAppAction(&menu, ACT_INSERT_BEFORE_CURRENT_LINE); 0390 addAppAction(&menu, ACT_INSERT_AFTER_CURRENT_LINE); 0391 addAppAction(&menu, ACT_REMOVE_SELECTED_LINES); 0392 menu.addSeparator(); 0393 addAppAction(&menu, ACT_JOIN_SELECTED_LINES); 0394 addAppAction(&menu, ACT_SPLIT_SELECTED_LINES); 0395 menu.addSeparator(); 0396 addAppAction(&menu, ACT_ANCHOR_TOGGLE); 0397 addAppAction(&menu, ACT_ANCHOR_REMOVE_ALL); 0398 menu.addSeparator(); 0399 0400 QMenu textsMenu(i18n("Texts")); 0401 addAppAction(&textsMenu, ACT_ADJUST_TEXTS); 0402 addAppAction(&textsMenu, ACT_UNBREAK_TEXTS); 0403 addAppAction(&textsMenu, ACT_SIMPLIFY_SPACES); 0404 addAppAction(&textsMenu, ACT_CHANGE_CASE); 0405 addAppAction(&textsMenu, ACT_FIX_PUNCTUATION); 0406 addAppAction(&textsMenu, ACT_TRANSLATE); 0407 textsMenu.addSeparator(); 0408 addAppAction(&textsMenu, ACT_SPELL_CHECK); 0409 menu.addMenu(&textsMenu); 0410 0411 QMenu stylesMenu(i18n("Styles")); 0412 const int styleFlags = subLine ? subLine->primaryDoc()->cummulativeStyleFlags() | subLine->secondaryDoc()->cummulativeStyleFlags() : 0; 0413 addAppAction(&stylesMenu, ACT_TOGGLE_SELECTED_LINES_BOLD, true, styleFlags & RichString::Bold); 0414 addAppAction(&stylesMenu, ACT_TOGGLE_SELECTED_LINES_ITALIC, true, styleFlags & RichString::Italic); 0415 addAppAction(&stylesMenu, ACT_TOGGLE_SELECTED_LINES_UNDERLINE, true, styleFlags & RichString::Underline); 0416 addAppAction(&stylesMenu, ACT_TOGGLE_SELECTED_LINES_STRIKETHROUGH, true, styleFlags & RichString::StrikeThrough); 0417 addAppAction(&stylesMenu, ACT_CHANGE_SELECTED_LINES_TEXT_COLOR); 0418 menu.addMenu(&stylesMenu); 0419 0420 QMenu timesMenu(i18n("Times")); 0421 addAppAction(×Menu, ACT_SHIFT_SELECTED_LINES_FORWARDS); 0422 addAppAction(×Menu, ACT_SHIFT_SELECTED_LINES_BACKWARDS); 0423 timesMenu.addSeparator(); 0424 addAppAction(×Menu, ACT_SHIFT); 0425 addAppAction(×Menu, ACT_DURATION_LIMITS); 0426 addAppAction(×Menu, ACT_AUTOMATIC_DURATIONS); 0427 addAppAction(×Menu, ACT_MAXIMIZE_DURATIONS); 0428 addAppAction(×Menu, ACT_FIX_OVERLAPPING_LINES); 0429 menu.addMenu(×Menu); 0430 0431 QMenu errorsMenu(i18n("Errors")); 0432 addAppAction(&errorsMenu, ACT_TOGGLE_SELECTED_LINES_MARK, true, subLine ? subLine->errorFlags() & SubtitleLine::UserMark : 0); 0433 errorsMenu.addSeparator(); 0434 addAppAction(&errorsMenu, ACT_DETECT_ERRORS); 0435 addAppAction(&errorsMenu, ACT_CLEAR_ERRORS); 0436 menu.addMenu(&errorsMenu); 0437 0438 m_showingContextMenu = true; 0439 menu.exec(e->globalPos()); 0440 m_showingContextMenu = false; 0441 0442 e->ignore(); 0443 0444 TreeView::contextMenuEvent(e); 0445 } 0446 0447 void 0448 LinesWidget::onCurrentRowChanged() 0449 { 0450 auto sm = qobject_cast<LinesSelectionModel *>(selectionModel()); 0451 emit currentLineChanged(sm->currentLine()); 0452 } 0453 0454 void 0455 LinesWidget::drawHorizontalDotLine(QPainter *painter, int x1, int x2, int y) 0456 { 0457 static int aux; 0458 0459 if(x1 > x2) { 0460 aux = x1; 0461 x1 = x2; 0462 x2 = aux; 0463 } 0464 0465 for(int x = x1 + 1; x <= x2; x += 2) 0466 painter->drawPoint(x, y); 0467 } 0468 0469 void 0470 LinesWidget::drawVerticalDotLine(QPainter *painter, int x, int y1, int y2) 0471 { 0472 static int aux; 0473 0474 if(y1 > y2) { 0475 aux = y1; 0476 y1 = y2; 0477 y2 = aux; 0478 } 0479 0480 for(int y = y1 + 1; y <= y2; y += 2) 0481 painter->drawPoint(x, y); 0482 } 0483 0484 void 0485 LinesWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0486 { 0487 TreeView::drawRow(painter, option, index); 0488 0489 const int visibleColumns = m_translationMode ? LinesModel::ColumnCount : LinesModel::ColumnCount - 1; 0490 const int row = index.row(); 0491 const bool rowSelected = selectionModel()->isSelected(index); 0492 const QPalette palette = this->palette(); 0493 const QRect rowRect = QRect(visualRect(model()->index(row, 0)).topLeft(), 0494 visualRect(model()->index(row, visibleColumns - 1)).bottomRight()); 0495 0496 // draw row grid 0497 painter->setPen(m_gridPen); 0498 if(!rowSelected) 0499 drawHorizontalDotLine(painter, rowRect.left(), rowRect.right(), rowRect.bottom()); 0500 for(int column = 0; column < visibleColumns - 1; ++column) { 0501 const QRect cellRect = visualRect(model()->index(row, column)); 0502 drawVerticalDotLine(painter, cellRect.right(), rowRect.top(), rowRect.bottom()); 0503 } 0504 if(index.row() == currentIndex().row()) { 0505 painter->setPen(palette.windowText().color()); 0506 0507 drawHorizontalDotLine(painter, rowRect.left(), rowRect.right(), rowRect.top()); 0508 drawHorizontalDotLine(painter, rowRect.left(), rowRect.right(), rowRect.bottom()); 0509 0510 drawVerticalDotLine(painter, rowRect.left(), rowRect.top(), rowRect.bottom()); 0511 drawVerticalDotLine(painter, rowRect.right(), rowRect.top(), rowRect.bottom()); 0512 } 0513 }