diff --git a/Pdf4QtLib/sources/pdfdocumenttextflow.cpp b/Pdf4QtLib/sources/pdfdocumenttextflow.cpp index b3c4bfe..5f32f8d 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextflow.cpp +++ b/Pdf4QtLib/sources/pdfdocumenttextflow.cpp @@ -829,6 +829,11 @@ void PDFDocumentTextFlowEditor::setText(const QString& text, size_t index) updateModifiedFlag(index); } +bool PDFDocumentTextFlowEditor::isSelectionEmpty() const +{ + return std::all_of(m_editedTextFlow.cbegin(), m_editedTextFlow.cend(), [](const auto& item) { return !item.editedItemFlags.testFlag(Selected); }); +} + void PDFDocumentTextFlowEditor::selectByRectangle(QRectF rectangle) { for (auto& item : m_editedTextFlow) @@ -846,6 +851,37 @@ void PDFDocumentTextFlowEditor::selectByRectangle(QRectF rectangle) } } +void PDFDocumentTextFlowEditor::selectByContainedText(QString text) +{ + for (auto& item : m_editedTextFlow) + { + const bool isContained = item.text.contains(text, Qt::CaseSensitive); + item.editedItemFlags.setFlag(Selected, isContained); + } +} + +void PDFDocumentTextFlowEditor::selectByRegularExpression(const QRegularExpression& expression) +{ + for (auto& item : m_editedTextFlow) + { + QRegularExpressionMatch match = expression.match(item.text, 0, QRegularExpression::NormalMatch, QRegularExpression::NoMatchOption); + const bool hasMatch = match.hasMatch(); + item.editedItemFlags.setFlag(Selected, hasMatch); + } +} + +void PDFDocumentTextFlowEditor::selectByPageIndices(const pdf::PDFClosedIntervalSet& indices) +{ + std::vector pageIndices = indices.unfold(); + std::sort(pageIndices.begin(), pageIndices.end()); + + for (auto& item : m_editedTextFlow) + { + const bool isPageValid = std::binary_search(pageIndices.begin(), pageIndices.end(), item.pageIndex + 1); + item.editedItemFlags.setFlag(Selected, isPageValid); + } +} + void PDFDocumentTextFlowEditor::createEditedFromOriginalTextFlow() { const size_t count = m_originalTextFlow.getSize(); diff --git a/Pdf4QtLib/sources/pdfdocumenttextflow.h b/Pdf4QtLib/sources/pdfdocumenttextflow.h index 2dab81b..15719ad 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextflow.h +++ b/Pdf4QtLib/sources/pdfdocumenttextflow.h @@ -20,6 +20,7 @@ #include "pdfglobal.h" #include "pdfexception.h" +#include "pdfutils.h" namespace pdf { @@ -198,6 +199,9 @@ public: /// Returns true, if text flow is empty bool isEmpty() const { return m_originalTextFlow.isEmpty(); } + /// Returns true, if text selection is empty + bool isSelectionEmpty() const; + /// Returns item count in edited text flow size_t getItemCount() const { return m_editedTextFlow.size(); } @@ -214,6 +218,18 @@ public: /// \param rectangle Selection rectangle void selectByRectangle(QRectF rectangle); + /// Select items which contains text + /// \param text Text + void selectByContainedText(QString text); + + /// Select items which matches regular expression + /// \param expression Regular expression + void selectByRegularExpression(const QRegularExpression& expression); + + /// Select all items on a given page indices + /// \param indices Indices + void selectByPageIndices(const PDFClosedIntervalSet& indices); + private: void createEditedFromOriginalTextFlow(); void updateModifiedFlag(size_t index); diff --git a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp index cb41ba7..0bf16fe 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp +++ b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp @@ -252,4 +252,37 @@ void PDFDocumentTextFlowEditorModel::selectByRectangle(QRectF rectangle) emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); } +void PDFDocumentTextFlowEditorModel::selectByContainedText(QString text) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByContainedText(text); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::selectByRegularExpression(const QRegularExpression& expression) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByRegularExpression(expression); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::selectByPageIndices(const PDFClosedIntervalSet& indices) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByPageIndices(indices); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h index ce59ec8..6498cb9 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h +++ b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h @@ -19,6 +19,7 @@ #define PDFDOCUMENTTEXTFLOWEDITORMODEL_H #include "pdfglobal.h" +#include "pdfutils.h" #include @@ -63,6 +64,9 @@ public: void setSelectionActivated(bool activate); void selectByRectangle(QRectF rectangle); + void selectByContainedText(QString text); + void selectByRegularExpression(const QRegularExpression& expression); + void selectByPageIndices(const pdf::PDFClosedIntervalSet& indices); private: PDFDocumentTextFlowEditor* m_editor; diff --git a/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.cpp b/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.cpp index 75dadff..065302f 100644 --- a/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.cpp +++ b/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.cpp @@ -18,9 +18,12 @@ #include "audiobookplugin.h" #include "pdfdrawwidget.h" #include "pdfwidgettool.h" +#include "pdfutils.h" #include #include +#include +#include namespace pdfplugin { @@ -54,6 +57,7 @@ void AudioBookPlugin::setWidget(pdf::PDFWidget* widget) m_actionCreateTextStream = new QAction(QIcon(":/pdfplugins/audiobook/create-text-stream.svg"), tr("Create Text Stream for Audio Book"), this); m_actionCreateTextStream->setObjectName("actionAudioBook_CreateTextStream"); + connect(m_actionCreateTextStream, &QAction::triggered, this, &AudioBookPlugin::onCreateTextStreamTriggered); m_actionSynchronizeFromTableToGraphics = new QAction(QIcon(":/pdfplugins/audiobook/synchronize-from-table-to-graphics.svg"), tr("Synchronize Selection from Table to Graphics"), this); m_actionSynchronizeFromTableToGraphics->setObjectName("actionAudioBook_SynchronizeFromTableToGraphics"); @@ -77,12 +81,15 @@ void AudioBookPlugin::setWidget(pdf::PDFWidget* widget) m_actionSelectByContainedText = new QAction(QIcon(":/pdfplugins/audiobook/select-by-contained-text.svg"), tr("Select by Contained Text"), this); m_actionSelectByContainedText->setObjectName("actionAudioBook_SelectByContainedText"); + connect(m_actionSelectByContainedText, &QAction::triggered, this, &AudioBookPlugin::onSelectByContainedText); m_actionSelectByRegularExpression = new QAction(QIcon(":/pdfplugins/audiobook/select-by-regular-expression.svg"), tr("Select by Regular Expression"), this); m_actionSelectByRegularExpression->setObjectName("actionAudioBook_SelectByRegularExpression"); + connect(m_actionSelectByRegularExpression, &QAction::triggered, this, &AudioBookPlugin::onSelectByRegularExpression); m_actionSelectByPageList = new QAction(QIcon(":/pdfplugins/audiobook/select-by-page-list.svg"), tr("Select by Page List"), this); m_actionSelectByPageList->setObjectName("actionAudioBook_SelectByPageList"); + connect(m_actionSelectByPageList, &QAction::triggered, this, &AudioBookPlugin::onSelectByPageList); m_actionRestoreOriginalText = new QAction(QIcon(":/pdfplugins/audiobook/restore-original-text.svg"), tr("Restore Original Text"), this); m_actionRestoreOriginalText->setObjectName("actionAudioBook_RestoreOriginalText"); @@ -96,8 +103,6 @@ void AudioBookPlugin::setWidget(pdf::PDFWidget* widget) m_actionCreateAudioBook = new QAction(QIcon(":/pdfplugins/audiobook/create-audio-book.svg"), tr("Create Audio Book"), this); m_actionCreateAudioBook->setObjectName("actionAudioBook_CreateAudioBook"); - connect(m_actionCreateTextStream, &QAction::triggered, this, &AudioBookPlugin::onCreateTextStreamTriggered); - updateActions(); } @@ -107,7 +112,16 @@ void AudioBookPlugin::setDocument(const pdf::PDFModifiedDocument& document) if (document.hasReset()) { + if (m_audioTextStreamEditorModel) + { + m_audioTextStreamEditorModel->beginFlowChange(); + } m_textFlowEditor.clear(); + if (m_audioTextStreamEditorModel) + { + m_audioTextStreamEditorModel->endFlowChange(); + } + updateActions(); } } @@ -150,6 +164,8 @@ void AudioBookPlugin::onCreateTextStreamTriggered() m_audioTextStreamEditorModel = new pdf::PDFDocumentTextFlowEditorModel(m_audioTextStreamDockWidget); m_audioTextStreamEditorModel->setEditor(&m_textFlowEditor); m_audioTextStreamDockWidget->setModel(m_audioTextStreamEditorModel); + connect(m_audioTextStreamEditorModel, &pdf::PDFDocumentTextFlowEditorModel::modelReset, this, &AudioBookPlugin::updateActions); + connect(m_audioTextStreamEditorModel, &pdf::PDFDocumentTextFlowEditorModel::dataChanged, this, &AudioBookPlugin::updateActions); } m_audioTextStreamDockWidget->show(); @@ -183,6 +199,70 @@ void AudioBookPlugin::onSelectByRectangle() m_widget->getToolManager()->pickRectangle(std::bind(&AudioBookPlugin::onRectanglePicked, this, std::placeholders::_1, std::placeholders::_2)); } +void AudioBookPlugin::onSelectByContainedText() +{ + QString text = m_audioTextStreamDockWidget->getSelectionText(); + m_audioTextStreamDockWidget->clearSelectionText(); + + if (!text.isEmpty()) + { + m_audioTextStreamEditorModel->selectByContainedText(text); + } + else + { + QMessageBox::critical(m_audioTextStreamDockWidget, tr("Error"), tr("Cannot select items by text, because text is empty.")); + } +} + +void AudioBookPlugin::onSelectByRegularExpression() +{ + QString pattern = m_audioTextStreamDockWidget->getSelectionText(); + m_audioTextStreamDockWidget->clearSelectionText(); + + if (!pattern.isEmpty()) + { + QRegularExpression expression(pattern); + + if (expression.isValid()) + { + m_audioTextStreamEditorModel->selectByRegularExpression(expression); + } + else + { + QMessageBox::critical(m_audioTextStreamDockWidget, tr("Error"), tr("Regular expression is not valid. %1").arg(expression.errorString())); + } + } + else + { + QMessageBox::critical(m_audioTextStreamDockWidget, tr("Error"), tr("Cannot select items by regular expression, because regular expression definition is empty.")); + } +} + +void AudioBookPlugin::onSelectByPageList() +{ + QString pageIndicesText = m_audioTextStreamDockWidget->getSelectionText(); + m_audioTextStreamDockWidget->clearSelectionText(); + + if (!pageIndicesText.isEmpty()) + { + QString errorMessage; + auto pageIndices = pdf::PDFClosedIntervalSet::parse(1, m_document->getCatalog()->getPageCount(), pageIndicesText, &errorMessage); + + if (errorMessage.isEmpty()) + { + m_audioTextStreamEditorModel->selectByPageIndices(pageIndices); + } + else + { + QMessageBox::critical(m_audioTextStreamDockWidget, tr("Error"), tr("Cannot select items by page indices, because page indices are invalid. %1").arg(errorMessage)); + } + } + else + { + QMessageBox::critical(m_audioTextStreamDockWidget, tr("Error"), tr("Cannot select items by page indices, because page indices are empty.")); + } +} + void AudioBookPlugin::onRectanglePicked(pdf::PDFInteger pageIndex, QRectF rectangle) { Q_UNUSED(pageIndex); @@ -192,6 +272,18 @@ void AudioBookPlugin::onRectanglePicked(pdf::PDFInteger pageIndex, QRectF rectan void AudioBookPlugin::updateActions() { m_actionCreateTextStream->setEnabled(m_document); + m_actionSynchronizeFromTableToGraphics->setEnabled(true); + m_actionSynchronizeFromGraphicsToTable->setEnabled(true); + m_actionActivateSelection->setEnabled(!m_textFlowEditor.isSelectionEmpty()); + m_actionDeactivateSelection->setEnabled(!m_textFlowEditor.isSelectionEmpty()); + m_actionSelectByRectangle->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionSelectByContainedText->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionSelectByRegularExpression->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionSelectByPageList->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionRestoreOriginalText->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionMoveSelectionUp->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionMoveSelectionDown->setEnabled(!m_textFlowEditor.isEmpty()); + m_actionCreateAudioBook->setEnabled(!m_textFlowEditor.isEmpty()); } } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.h b/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.h index b8c12c6..c4b7df1 100644 --- a/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.h +++ b/Pdf4QtViewerPlugins/AudioBookPlugin/audiobookplugin.h @@ -48,6 +48,9 @@ private: void onActivateSelection(); void onDeactivateSelection(); void onSelectByRectangle(); + void onSelectByContainedText(); + void onSelectByRegularExpression(); + void onSelectByPageList(); void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF rectangle); diff --git a/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.cpp b/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.cpp index 1832e69..c8deeba 100644 --- a/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.cpp +++ b/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.cpp @@ -85,4 +85,14 @@ void AudioTextStreamEditorDockWidget::setModel(pdf::PDFDocumentTextFlowEditorMod ui->textStreamTableView->setModel(m_model); } +QString AudioTextStreamEditorDockWidget::getSelectionText() const +{ + return m_selectionTextEdit->text(); +} + +void AudioTextStreamEditorDockWidget::clearSelectionText() +{ + m_selectionTextEdit->clear(); +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.h b/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.h index fd3dee2..57e14f0 100644 --- a/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.h +++ b/Pdf4QtViewerPlugins/AudioBookPlugin/audiotextstreameditordockwidget.h @@ -63,6 +63,9 @@ public: QToolBar* getToolBar() const { return m_toolBar; } + QString getSelectionText() const; + void clearSelectionText(); + private: Ui::AudioTextStreamEditorDockWidget* ui; pdf::PDFDocumentTextFlowEditorModel* m_model;