File indexing completed on 2024-04-28 15:51:41
0001 /* 0002 SPDX-FileCopyrightText: 2006 Chu Xiaodong <xiaodongchu@gmail.com> 0003 SPDX-FileCopyrightText: 2006 Pino Toscano <pino@kde.org> 0004 0005 Work sponsored by the LiMux project of the city of Munich: 0006 SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "annotwindow.h" 0012 0013 // qt/kde includes 0014 #include <KLocalizedString> 0015 #include <KStandardAction> 0016 #include <KTextEdit> 0017 #include <QAction> 0018 #include <QApplication> 0019 #include <QDebug> 0020 #include <QEvent> 0021 #include <QFont> 0022 #include <QFontInfo> 0023 #include <QFontMetrics> 0024 #include <QLabel> 0025 #include <QLayout> 0026 #include <QMenu> 0027 #include <QPushButton> 0028 #include <QSizeGrip> 0029 #include <QStyle> 0030 #include <QToolButton> 0031 0032 // local includes 0033 #include "core/annotations.h" 0034 #include "core/document.h" 0035 #include "latexrenderer.h" 0036 #include <KMessageBox> 0037 #include <core/utils.h> 0038 0039 class CloseButton : public QPushButton 0040 { 0041 Q_OBJECT 0042 0043 public: 0044 explicit CloseButton(QWidget *parent = Q_NULLPTR) 0045 : QPushButton(parent) 0046 { 0047 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0048 QSize size = QSize(14, 14); 0049 setFixedSize(size); 0050 setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton)); 0051 setIconSize(size); 0052 setToolTip(i18n("Close this note")); 0053 setCursor(Qt::ArrowCursor); 0054 } 0055 }; 0056 0057 class MovableTitle : public QWidget 0058 { 0059 Q_OBJECT 0060 0061 public: 0062 explicit MovableTitle(AnnotWindow *parent) 0063 : QWidget(parent) 0064 { 0065 QVBoxLayout *mainlay = new QVBoxLayout(this); 0066 mainlay->setContentsMargins(0, 0, 0, 0); 0067 mainlay->setSpacing(0); 0068 // close button row 0069 QHBoxLayout *buttonlay = new QHBoxLayout(); 0070 mainlay->addLayout(buttonlay); 0071 titleLabel = new QLabel(this); 0072 QFont f = titleLabel->font(); 0073 f.setBold(true); 0074 titleLabel->setFont(f); 0075 titleLabel->setCursor(Qt::SizeAllCursor); 0076 buttonlay->addWidget(titleLabel); 0077 dateLabel = new QLabel(this); 0078 dateLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); 0079 f = dateLabel->font(); 0080 f.setPointSize(QFontInfo(f).pointSize() - 2); 0081 dateLabel->setFont(f); 0082 dateLabel->setCursor(Qt::SizeAllCursor); 0083 buttonlay->addWidget(dateLabel); 0084 CloseButton *close = new CloseButton(this); 0085 connect(close, &QAbstractButton::clicked, parent, &QWidget::close); 0086 buttonlay->addWidget(close); 0087 // option button row 0088 QHBoxLayout *optionlay = new QHBoxLayout(); 0089 mainlay->addLayout(optionlay); 0090 authorLabel = new QLabel(this); 0091 authorLabel->setCursor(Qt::SizeAllCursor); 0092 authorLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); 0093 optionlay->addWidget(authorLabel); 0094 optionButton = new QToolButton(this); 0095 QString opttext = i18n("Options"); 0096 optionButton->setText(opttext); 0097 optionButton->setAutoRaise(true); 0098 QSize s = QFontMetrics(optionButton->font()).boundingRect(opttext).size() + QSize(8, 8); 0099 optionButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0100 optionButton->setFixedSize(s); 0101 optionlay->addWidget(optionButton); 0102 // ### disabled for now 0103 optionButton->hide(); 0104 latexButton = new QToolButton(this); 0105 QHBoxLayout *latexlay = new QHBoxLayout(); 0106 QString latextext = i18n("This annotation may contain LaTeX code.\nClick here to render."); 0107 latexButton->setText(latextext); 0108 latexButton->setAutoRaise(true); 0109 s = QFontMetrics(latexButton->font()).boundingRect(0, 0, this->width(), this->height(), 0, latextext).size() + QSize(8, 8); 0110 latexButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0111 latexButton->setFixedSize(s); 0112 latexButton->setCheckable(true); 0113 latexButton->setVisible(false); 0114 latexlay->addSpacing(1); 0115 latexlay->addWidget(latexButton); 0116 latexlay->addSpacing(1); 0117 mainlay->addLayout(latexlay); 0118 connect(latexButton, &QToolButton::clicked, parent, &AnnotWindow::renderLatex); 0119 connect(parent, &AnnotWindow::containsLatex, latexButton, &QWidget::setVisible); 0120 0121 titleLabel->installEventFilter(this); 0122 dateLabel->installEventFilter(this); 0123 authorLabel->installEventFilter(this); 0124 } 0125 0126 bool eventFilter(QObject *obj, QEvent *e) override 0127 { 0128 if (obj != titleLabel && obj != authorLabel && obj != dateLabel) { 0129 return false; 0130 } 0131 0132 QMouseEvent *me = nullptr; 0133 switch (e->type()) { 0134 case QEvent::MouseButtonPress: 0135 me = (QMouseEvent *)e; 0136 mousePressPos = me->pos(); 0137 parentWidget()->raise(); 0138 break; 0139 case QEvent::MouseButtonRelease: 0140 mousePressPos = QPoint(); 0141 break; 0142 case QEvent::MouseMove: { 0143 me = (QMouseEvent *)e; 0144 0145 // viewport info 0146 const QPoint topLeftPoint = parentWidget()->parentWidget()->pos(); 0147 const int viewportHeight = parentWidget()->parentWidget()->height(); 0148 const int viewportWidth = parentWidget()->parentWidget()->width(); 0149 0150 // annotation's popup window info 0151 QPoint newPositionPoint = me->pos() - mousePressPos + parentWidget()->pos(); 0152 const int annotHeight = parentWidget()->height(); 0153 const int annotWidth = parentWidget()->width(); 0154 0155 // make sure x is in range 0156 if (newPositionPoint.x() < topLeftPoint.x()) { 0157 newPositionPoint.setX(topLeftPoint.x()); 0158 } else if (newPositionPoint.x() + annotWidth > topLeftPoint.x() + viewportWidth) { 0159 newPositionPoint.setX(topLeftPoint.x() + viewportWidth - annotWidth); 0160 } 0161 0162 // make sure y is in range 0163 if (newPositionPoint.y() < topLeftPoint.y()) { 0164 newPositionPoint.setY(topLeftPoint.y()); 0165 } else if (newPositionPoint.y() + annotHeight > topLeftPoint.y() + viewportHeight) { 0166 newPositionPoint.setY(topLeftPoint.y() + viewportHeight - annotHeight); 0167 } 0168 0169 parentWidget()->move(newPositionPoint); 0170 break; 0171 } 0172 default: 0173 return false; 0174 } 0175 return true; 0176 } 0177 0178 void setTitle(const QString &title) 0179 { 0180 titleLabel->setText(QStringLiteral(" ") + title); 0181 } 0182 0183 void setDate(const QDateTime &dt) 0184 { 0185 dateLabel->setText(QLocale().toString(dt.toTimeSpec(Qt::LocalTime), QLocale::ShortFormat) + QLatin1Char(' ')); 0186 } 0187 0188 void setAuthor(const QString &author) 0189 { 0190 authorLabel->setText(QStringLiteral(" ") + author); 0191 } 0192 0193 void connectOptionButton(QObject *recv, const char *method) 0194 { 0195 connect(optionButton, SIGNAL(clicked()), recv, method); 0196 } 0197 0198 void uncheckLatexButton() 0199 { 0200 latexButton->setChecked(false); 0201 } 0202 0203 private: 0204 QLabel *titleLabel; 0205 QLabel *dateLabel; 0206 QLabel *authorLabel; 0207 QPoint mousePressPos; 0208 QToolButton *optionButton; 0209 QToolButton *latexButton; 0210 }; 0211 0212 // Qt::SubWindow is needed to make QSizeGrip work 0213 AnnotWindow::AnnotWindow(QWidget *parent, Okular::Annotation *annot, Okular::Document *document, int page) 0214 : QFrame(parent, Qt::SubWindow) 0215 , m_annot(annot) 0216 , m_document(document) 0217 , m_page(page) 0218 { 0219 setAutoFillBackground(true); 0220 setFrameStyle(Panel | Raised); 0221 setAttribute(Qt::WA_DeleteOnClose); 0222 setObjectName(QStringLiteral("AnnotWindow")); 0223 0224 const bool canEditAnnotation = m_document->canModifyPageAnnotation(annot); 0225 0226 textEdit = new KTextEdit(this); 0227 textEdit->setAcceptRichText(false); 0228 textEdit->setPlainText(m_annot->contents()); 0229 textEdit->installEventFilter(this); 0230 textEdit->setUndoRedoEnabled(false); 0231 0232 m_prevCursorPos = textEdit->textCursor().position(); 0233 m_prevAnchorPos = textEdit->textCursor().anchor(); 0234 0235 connect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText); 0236 connect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText); 0237 connect(textEdit, &KTextEdit::aboutToShowContextMenu, this, &AnnotWindow::slotUpdateUndoAndRedoInContextMenu); 0238 connect(m_document, &Okular::Document::annotationContentsChangedByUndoRedo, this, &AnnotWindow::slotHandleContentsChangedByUndoRedo); 0239 0240 if (!canEditAnnotation) { 0241 textEdit->setReadOnly(true); 0242 } 0243 0244 QVBoxLayout *mainlay = new QVBoxLayout(this); 0245 mainlay->setContentsMargins(2, 2, 2, 2); 0246 mainlay->setSpacing(0); 0247 m_title = new MovableTitle(this); 0248 mainlay->addWidget(m_title); 0249 mainlay->addWidget(textEdit); 0250 QHBoxLayout *lowerlay = new QHBoxLayout(); 0251 mainlay->addLayout(lowerlay); 0252 lowerlay->addItem(new QSpacerItem(5, 5, QSizePolicy::Expanding, QSizePolicy::Fixed)); 0253 QSizeGrip *sb = new QSizeGrip(this); 0254 lowerlay->addWidget(sb); 0255 0256 m_latexRenderer = new GuiUtils::LatexRenderer(); 0257 // The Q_EMIT below is not wrong even if emitting signals from the constructor it's usually wrong 0258 // in this case the signal it's connected to inside MovableTitle constructor a few lines above 0259 Q_EMIT containsLatex(GuiUtils::LatexRenderer::mightContainLatex(m_annot->contents())); // clazy:exclude=incorrect-emit 0260 0261 m_title->setTitle(m_annot->window().summary()); 0262 m_title->connectOptionButton(this, SLOT(slotOptionBtn())); 0263 0264 setGeometry(10, 10, 300, 300); 0265 0266 reloadInfo(); 0267 } 0268 0269 AnnotWindow::~AnnotWindow() 0270 { 0271 delete m_latexRenderer; 0272 } 0273 0274 Okular::Annotation *AnnotWindow::annotation() const 0275 { 0276 return m_annot; 0277 } 0278 0279 void AnnotWindow::updateAnnotation(Okular::Annotation *a) 0280 { 0281 m_annot = a; 0282 } 0283 0284 void AnnotWindow::reloadInfo() 0285 { 0286 QColor newcolor; 0287 if (m_annot->subType() == Okular::Annotation::AText) { 0288 Okular::TextAnnotation *textAnn = static_cast<Okular::TextAnnotation *>(m_annot); 0289 if (textAnn->textType() == Okular::TextAnnotation::InPlace && textAnn->inplaceIntent() == Okular::TextAnnotation::TypeWriter) { 0290 newcolor = QColor(0xfd, 0xfd, 0x96); 0291 } 0292 } 0293 if (!newcolor.isValid()) { 0294 newcolor = m_annot->style().color().isValid() ? QColor(m_annot->style().color().red(), m_annot->style().color().green(), m_annot->style().color().blue(), 255) : Qt::yellow; 0295 } 0296 if (newcolor != m_color) { 0297 m_color = newcolor; 0298 setPalette(QPalette(m_color)); 0299 QPalette pl = textEdit->palette(); 0300 pl.setColor(QPalette::Base, m_color); 0301 textEdit->setPalette(pl); 0302 } 0303 m_title->setAuthor(m_annot->author()); 0304 m_title->setDate(m_annot->modificationDate()); 0305 } 0306 0307 int AnnotWindow::pageNumber() const 0308 { 0309 return m_page; 0310 } 0311 0312 void AnnotWindow::showEvent(QShowEvent *event) 0313 { 0314 QFrame::showEvent(event); 0315 0316 // focus the content area by default 0317 textEdit->setFocus(); 0318 } 0319 0320 bool AnnotWindow::eventFilter(QObject *o, QEvent *e) 0321 { 0322 if (e->type() == QEvent::ShortcutOverride) { 0323 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e); 0324 if (keyEvent->key() == Qt::Key_Escape) { 0325 e->accept(); 0326 return true; 0327 } 0328 } else if (e->type() == QEvent::KeyPress) { 0329 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e); 0330 if (keyEvent == QKeySequence::Undo) { 0331 m_document->undo(); 0332 return true; 0333 } else if (keyEvent == QKeySequence::Redo) { 0334 m_document->redo(); 0335 return true; 0336 } else if (keyEvent->key() == Qt::Key_Escape) { 0337 close(); 0338 return true; 0339 } 0340 } else if (e->type() == QEvent::FocusIn) { 0341 raise(); 0342 } 0343 return QFrame::eventFilter(o, e); 0344 } 0345 0346 void AnnotWindow::slotUpdateUndoAndRedoInContextMenu(QMenu *menu) 0347 { 0348 if (!menu) { 0349 return; 0350 } 0351 0352 QList<QAction *> actionList = menu->actions(); 0353 enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs }; 0354 0355 QAction *kundo = KStandardAction::create( 0356 KStandardAction::Undo, 0357 m_document, 0358 [doc = m_document] { 0359 // We need a QueuedConnection because undoing may end up destroying the menu this action is on 0360 // because it will undo the addition of the annotation. If it's not queued things gets unhappy 0361 // because the menu is destroyed in the middle of processing the click on the menu itself 0362 QMetaObject::invokeMethod(doc, &Okular::Document::undo, Qt::QueuedConnection); 0363 }, 0364 menu); 0365 QAction *kredo = KStandardAction::create(KStandardAction::Redo, m_document, SLOT(redo()), menu); 0366 connect(m_document, &Okular::Document::canUndoChanged, kundo, &QAction::setEnabled); 0367 connect(m_document, &Okular::Document::canRedoChanged, kredo, &QAction::setEnabled); 0368 kundo->setEnabled(m_document->canUndo()); 0369 kredo->setEnabled(m_document->canRedo()); 0370 0371 QAction *oldUndo, *oldRedo; 0372 oldUndo = actionList[UndoAct]; 0373 oldRedo = actionList[RedoAct]; 0374 0375 menu->insertAction(oldUndo, kundo); 0376 menu->insertAction(oldRedo, kredo); 0377 0378 menu->removeAction(oldUndo); 0379 menu->removeAction(oldRedo); 0380 } 0381 0382 void AnnotWindow::slotOptionBtn() 0383 { 0384 // TODO: call context menu in pageview 0385 // Q_EMIT sig... 0386 } 0387 0388 void AnnotWindow::slotsaveWindowText() 0389 { 0390 const QString contents = textEdit->toPlainText(); 0391 const int cursorPos = textEdit->textCursor().position(); 0392 if (contents != m_annot->contents()) { 0393 m_document->editPageAnnotationContents(m_page, m_annot, contents, cursorPos, m_prevCursorPos, m_prevAnchorPos); 0394 Q_EMIT containsLatex(GuiUtils::LatexRenderer::mightContainLatex(textEdit->toPlainText())); 0395 } 0396 m_prevCursorPos = cursorPos; 0397 m_prevAnchorPos = textEdit->textCursor().anchor(); 0398 } 0399 0400 void AnnotWindow::renderLatex(bool render) 0401 { 0402 if (render) { 0403 textEdit->setReadOnly(true); 0404 disconnect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText); 0405 disconnect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText); 0406 textEdit->setAcceptRichText(true); 0407 QString contents = m_annot->contents(); 0408 contents = Qt::convertFromPlainText(contents); 0409 QColor fontColor = textEdit->textColor(); 0410 int fontSize = textEdit->fontPointSize(); 0411 QString latexOutput; 0412 GuiUtils::LatexRenderer::Error errorCode = m_latexRenderer->renderLatexInHtml(contents, fontColor, fontSize, Okular::Utils::realDpi(nullptr).width(), latexOutput); 0413 switch (errorCode) { 0414 case GuiUtils::LatexRenderer::LatexNotFound: 0415 KMessageBox::error(this, i18n("Cannot find latex executable."), i18n("LaTeX rendering failed")); 0416 m_title->uncheckLatexButton(); 0417 renderLatex(false); 0418 break; 0419 case GuiUtils::LatexRenderer::DvipngNotFound: 0420 KMessageBox::error(this, i18n("Cannot find dvipng executable."), i18n("LaTeX rendering failed")); 0421 m_title->uncheckLatexButton(); 0422 renderLatex(false); 0423 break; 0424 case GuiUtils::LatexRenderer::LatexFailed: 0425 KMessageBox::detailedError(this, i18n("A problem occurred during the execution of the 'latex' command."), latexOutput, i18n("LaTeX rendering failed")); 0426 m_title->uncheckLatexButton(); 0427 renderLatex(false); 0428 break; 0429 case GuiUtils::LatexRenderer::DvipngFailed: 0430 KMessageBox::error(this, i18n("A problem occurred during the execution of the 'dvipng' command."), i18n("LaTeX rendering failed")); 0431 m_title->uncheckLatexButton(); 0432 renderLatex(false); 0433 break; 0434 case GuiUtils::LatexRenderer::NoError: 0435 default: 0436 textEdit->setHtml(contents); 0437 break; 0438 } 0439 } else { 0440 textEdit->setAcceptRichText(false); 0441 textEdit->setPlainText(m_annot->contents()); 0442 connect(textEdit, &KTextEdit::textChanged, this, &AnnotWindow::slotsaveWindowText); 0443 connect(textEdit, &KTextEdit::cursorPositionChanged, this, &AnnotWindow::slotsaveWindowText); 0444 textEdit->setReadOnly(false); 0445 } 0446 } 0447 0448 void AnnotWindow::slotHandleContentsChangedByUndoRedo(Okular::Annotation *annot, const QString &contents, int cursorPos, int anchorPos) 0449 { 0450 if (annot != m_annot) { 0451 return; 0452 } 0453 0454 textEdit->setPlainText(contents); 0455 QTextCursor c = textEdit->textCursor(); 0456 c.setPosition(anchorPos); 0457 c.setPosition(cursorPos, QTextCursor::KeepAnchor); 0458 m_prevCursorPos = cursorPos; 0459 m_prevAnchorPos = anchorPos; 0460 textEdit->setTextCursor(c); 0461 textEdit->setFocus(); 0462 Q_EMIT containsLatex(GuiUtils::LatexRenderer::mightContainLatex(m_annot->contents())); 0463 } 0464 0465 #include "annotwindow.moc"