Merge remote-tracking branch 'remotes/origin/issue21'

This commit is contained in:
Jakub Melka 2022-07-30 18:48:55 +02:00
commit b30150a503
12 changed files with 706 additions and 1 deletions

View File

@ -351,6 +351,7 @@ public:
PDFWidget* getWidget() const { return m_widget; }
bool isUsingOpenGL() const { return m_useOpenGL; }
const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; }
PageRotation getPageRotation() const { return m_controller->getPageRotation(); }
void setFeatures(PDFRenderer::Features features);
void setPreferredMeshResolutionRatio(PDFReal ratio);

View File

@ -469,6 +469,70 @@ PDFTextSelection PDFTextLayout::createTextSelection(PDFInteger pageIndex, const
return selection;
}
PDFTextSelection PDFTextLayout::selectBlock(const size_t blockIndex, PDFInteger pageIndex, QColor color) const
{
PDFTextSelection selection;
if (blockIndex >= m_blocks.size())
{
return selection;
}
const PDFTextBlock& textBlock = m_blocks[blockIndex];
if (textBlock.getLines().empty())
{
return selection;
}
PDFCharacterPointer ptrA;
PDFCharacterPointer ptrB;
ptrA.pageIndex = pageIndex;
ptrA.blockIndex = blockIndex;
ptrA.lineIndex = 0;
ptrA.characterIndex = 0;
ptrB.pageIndex = pageIndex;
ptrB.blockIndex = blockIndex;
ptrB.lineIndex = textBlock.getLines().size() - 1;
ptrB.characterIndex = textBlock.getLines().back().getCharacters().size() - 1;
selection.addItems({ PDFTextSelectionItem(ptrA, ptrB) }, color);
selection.build();
return selection;
}
PDFTextSelection PDFTextLayout::selectLineInBlock(const size_t blockIndex, const size_t lineIndex, PDFInteger pageIndex, QColor color) const
{
PDFTextSelection selection;
if (blockIndex >= m_blocks.size())
{
return selection;
}
const PDFTextBlock& textBlock = m_blocks[blockIndex];
if (lineIndex >= textBlock.getLines().size())
{
return selection;
}
PDFCharacterPointer ptrA;
PDFCharacterPointer ptrB;
ptrA.pageIndex = pageIndex;
ptrA.blockIndex = blockIndex;
ptrA.lineIndex = lineIndex;
ptrA.characterIndex = 0;
ptrB.pageIndex = pageIndex;
ptrB.blockIndex = blockIndex;
ptrB.lineIndex = lineIndex;
ptrB.characterIndex = textBlock.getLines()[lineIndex].getCharacters().size() - 1;
selection.addItems({ PDFTextSelectionItem(ptrA, ptrB) }, color);
selection.build();
return selection;
}
QString PDFTextLayout::getTextFromSelection(PDFTextSelection::iterator itBegin, PDFTextSelection::iterator itEnd, PDFInteger pageIndex) const
{
QStringList text;

View File

@ -381,6 +381,19 @@ public:
/// \param pageIndex Index of the page
QString getTextFromSelection(const PDFTextSelection& selection, PDFInteger pageIndex) const;
/// Creates text selection for whole block
/// \param blockIndex Text block index
/// \param pageIndex pageIndex
/// \param color Selection color
PDFTextSelection selectBlock(const size_t blockIndex, PDFInteger pageIndex, QColor color) const;
/// Creates text selection for signle line of text block
/// \param blockIndex Text block index
/// \param lineIndex Line index
/// \param pageIndex pageIndex
/// \param color Selection color
PDFTextSelection selectLineInBlock(const size_t blockIndex, const size_t lineIndex, PDFInteger pageIndex, QColor color) const;
friend QDataStream& operator<<(QDataStream& stream, const PDFTextLayout& layout);
friend QDataStream& operator>>(QDataStream& stream, PDFTextLayout& layout);

View File

@ -19,6 +19,7 @@
#include "pdfdrawwidget.h"
#include "pdfcompiler.h"
#include "pdfwidgetutils.h"
#include "pdfpainterutils.h"
#include "pdfdbgheap.h"
#include <QLabel>
@ -745,6 +746,7 @@ PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObje
m_predefinedTools[PickRectangleTool] = pickTool;
m_predefinedTools[FindTextTool] = new PDFFindTextTool(proxy, actions.findPrevAction, actions.findNextAction, this, parentDialog);
m_predefinedTools[SelectTextTool] = new PDFSelectTextTool(proxy, actions.selectTextToolAction, actions.copyTextAction, actions.selectAllAction, actions.deselectAction, this);
m_predefinedTools[SelectTableTool] = new PDFSelectTableTool(proxy, actions.selectTableToolAction, this);
m_predefinedTools[MagnifierTool] = new PDFMagnifierTool(proxy, actions.magnifierAction, this);
m_predefinedTools[ScreenshotTool] = new PDFScreenshotTool(proxy, actions.screenshotToolAction, this);
m_predefinedTools[ExtractImageTool] = new PDFExtractImageTool(proxy, actions.extractImageAction, this);
@ -1367,4 +1369,519 @@ void PDFExtractImageTool::onImagePicked(const QImage& image)
}
}
PDFSelectTableTool::PDFSelectTableTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) :
BaseClass(proxy, action, parent),
m_pickTool(nullptr),
m_pageIndex(-1),
m_isTransposed(false)
{
m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this);
connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFSelectTableTool::onRectanglePicked);
setCursor(Qt::CrossCursor);
updateActions();
}
void PDFSelectTableTool::drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const
{
BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
if (isTablePicked() && pageIndex == m_pageIndex)
{
PDFPainterStateGuard guard(painter);
QColor color = QColor::fromRgbF(0.0, 0.0, 0.5, 0.2);
QRectF rectangle = pagePointToDevicePointMatrix.mapRect(m_pickedRectangle);
const PDFReal lineWidth = PDFWidgetUtils::scaleDPI_x(getProxy()->getWidget(), 2.0);
QPen pen(Qt::SolidLine);
pen.setWidthF(lineWidth);
painter->setPen(std::move(pen));
painter->setBrush(QBrush(color));
painter->drawRect(rectangle);
for (const PDFReal columnPosition : m_horizontalBreaks)
{
QPointF startPoint(columnPosition, m_pickedRectangle.top());
QPointF endPoint(columnPosition, m_pickedRectangle.bottom());
painter->drawLine(pagePointToDevicePointMatrix.map(startPoint), pagePointToDevicePointMatrix.map(endPoint));
}
for (const PDFReal rowPosition : m_verticalBreaks)
{
QPointF startPoint(m_pickedRectangle.left(), rowPosition);
QPointF endPoint(m_pickedRectangle.right(), rowPosition);
painter->drawLine(pagePointToDevicePointMatrix.map(startPoint), pagePointToDevicePointMatrix.map(endPoint));
}
}
}
void PDFSelectTableTool::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
BaseClass::mousePressEvent(widget, event);
if (event->isAccepted() || !isTablePicked())
{
return;
}
if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton)
{
QPointF pagePoint;
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
if (pageIndex != -1 && pageIndex == m_pageIndex && m_pickedRectangle.contains(pagePoint))
{
const PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex);
bool isSelectingColumns = false;
const PageRotation rotation = getPageRotationCombined(page->getPageRotation(), getProxy()->getPageRotation());
switch (rotation)
{
case pdf::PageRotation::None:
case pdf::PageRotation::Rotate180:
isSelectingColumns = event->button() == Qt::LeftButton;
break;
case pdf::PageRotation::Rotate90:
case pdf::PageRotation::Rotate270:
isSelectingColumns = event->button() == Qt::RightButton;
break;
default:
Q_ASSERT(false);
break;
}
const PDFReal distanceThresholdPixels = PDFWidgetUtils::scaleDPI_x(widget, 7.0);
const PDFReal distanceThreshold = getProxy()->transformPixelToDeviceSpace(distanceThresholdPixels);
if (isSelectingColumns)
{
auto it = std::find_if(m_horizontalBreaks.begin(), m_horizontalBreaks.end(), [distanceThreshold, pagePoint](const PDFReal value) { return qAbs(value - pagePoint.x()) < distanceThreshold; });
if (it != m_horizontalBreaks.end())
{
m_horizontalBreaks.erase(it);
}
else if (pagePoint.x() > m_pickedRectangle.left() + distanceThreshold && pagePoint.x() < m_pickedRectangle.right() - distanceThreshold)
{
m_horizontalBreaks.insert(std::lower_bound(m_horizontalBreaks.begin(), m_horizontalBreaks.end(), pagePoint.x()), pagePoint.x());
}
}
else
{
auto it = std::find_if(m_verticalBreaks.begin(), m_verticalBreaks.end(), [distanceThreshold, pagePoint](const PDFReal value) { return qAbs(value - pagePoint.y()) < distanceThreshold; });
if (it != m_verticalBreaks.end())
{
m_verticalBreaks.erase(it);
}
else if (pagePoint.y() > m_pickedRectangle.top() + distanceThreshold && pagePoint.y() < m_pickedRectangle.bottom() - distanceThreshold)
{
m_verticalBreaks.insert(std::lower_bound(m_verticalBreaks.begin(), m_verticalBreaks.end(), pagePoint.y()), pagePoint.y());
}
}
emit getProxy()->repaintNeeded();
event->accept();
}
}
}
void PDFSelectTableTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
{
BaseClass::mouseMoveEvent(widget, event);
if (!event->isAccepted() && isTablePicked())
{
QPointF pagePoint;
const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint);
if (pageIndex != -1 && pageIndex == m_pageIndex && m_pickedRectangle.contains(pagePoint))
{
setCursor(Qt::CrossCursor);
}
else
{
setCursor(Qt::ArrowCursor);
}
}
}
void PDFSelectTableTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
if (event == QKeySequence::Copy)
{
event->accept();
return;
}
}
void PDFSelectTableTool::keyPressEvent(QWidget* widget, QKeyEvent* event)
{
Q_UNUSED(widget);
if (event == QKeySequence::Copy ||
event->key() == Qt::Key_Return ||
event->key() == Qt::Key_Enter)
{
// Create table cells
struct TableCell
{
size_t row = 0;
size_t column = 0;
QRectF rectangle;
QString text;
};
std::vector<TableCell> tableCells;
std::vector<PDFReal> horizontalBreaks = m_horizontalBreaks;
std::vector<PDFReal> verticalBreaks = m_verticalBreaks;
horizontalBreaks.insert(horizontalBreaks.begin(), m_pickedRectangle.left());
horizontalBreaks.push_back(m_pickedRectangle.right());
verticalBreaks.insert(verticalBreaks.begin(), m_pickedRectangle.top());
verticalBreaks.push_back(m_pickedRectangle.bottom());
tableCells.reserve((horizontalBreaks.size() - 1) * (verticalBreaks.size() - 1));
for (size_t rowIndex = 1; rowIndex < verticalBreaks.size(); ++rowIndex)
{
const PDFReal top = verticalBreaks[rowIndex - 1];
const PDFReal bottom = verticalBreaks[rowIndex];
const PDFReal height = bottom - top;
for (size_t columnIndex = 1; columnIndex < horizontalBreaks.size(); ++columnIndex)
{
const PDFReal left = horizontalBreaks[columnIndex - 1];
const PDFReal right = horizontalBreaks[columnIndex];
const PDFReal width = right - left;
TableCell cell;
cell.row = rowIndex;
cell.column = columnIndex;
cell.rectangle = QRectF(left, top, width, height);
tableCells.push_back(cell);
}
}
const PDFTextBlocks& textBlocks = m_textLayout.getTextBlocks();
for (size_t i = 0; i < textBlocks.size(); ++i)
{
// Detect, if whole block can be in some text cell
const PDFTextBlock& textBlock = textBlocks[i];
QRectF textRect = textBlock.getBoundingBox().boundingRect();
auto it = std::find_if(tableCells.begin(), tableCells.end(), [textRect](const auto& cell) { return cell.rectangle.contains(textRect); });
if (it != tableCells.end())
{
// Jakub Melka: whole block is contained in the cell
PDFTextSelection blockSelection = m_textLayout.selectBlock(i, m_pageIndex, QColor());
QString text = m_textLayout.getTextFromSelection(blockSelection, m_pageIndex);
TableCell& cell = *it;
cell.text = QString("%1 %2").arg(cell.text, text).trimmed();
continue;
}
const PDFTextLines& textLines = textBlock.getLines();
for (size_t j = 0; j < textLines.size(); ++j)
{
const PDFTextLine& textLine = textLines[j];
QRectF boundingRect = textLine.getBoundingBox().boundingRect();
auto it = std::find_if(tableCells.begin(), tableCells.end(), [boundingRect](const auto& cell) { return cell.rectangle.contains(boundingRect); });
if (it != tableCells.end())
{
// Jakub Melka: whole block is contained in the cell
PDFTextSelection blockSelection = m_textLayout.selectLineInBlock(i, j, m_pageIndex, QColor());
QString text = m_textLayout.getTextFromSelection(blockSelection, m_pageIndex);
TableCell& cell = *it;
cell.text = QString("%1 %2").arg(cell.text, text).trimmed();
continue;
}
}
}
if (m_isTransposed)
{
auto comparator = [](const TableCell& left, const TableCell right)
{
return std::make_pair(left.column, left.row) < std::make_pair(right.column, right.row);
};
std::sort(tableCells.begin(), tableCells.end(), comparator);
}
// Make CSV string
QString string;
{
QTextStream stream(&string, QIODevice::WriteOnly | QIODevice::Text);
bool isFirst = true;
for (const TableCell& tableCell : tableCells)
{
if ((!m_isTransposed && tableCell.column == 1) ||
(m_isTransposed && tableCell.row == 1))
{
if (isFirst)
{
isFirst = false;
}
else
{
stream << Qt::endl;
}
}
stream << tableCell.text << ";";
}
}
QApplication::clipboard()->setText(string);
setActive(false);
event->accept();
}
}
void PDFSelectTableTool::setActiveImpl(bool active)
{
BaseClass::setActiveImpl(active);
if (active)
{
addTool(m_pickTool);
}
else
{
// Clear all data
setPageIndex(-1);
setPickedRectangle(QRectF());
setTextLayout(PDFTextLayout());
m_isTransposed = false;
m_horizontalBreaks.clear();
m_verticalBreaks.clear();
if (getTopToolstackTool())
{
removeTool();
}
}
}
void PDFSelectTableTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle)
{
removeTool();
setPageIndex(pageIndex);
setPickedRectangle(pageRectangle);
setTextLayout(getProxy()->getTextLayoutCompiler()->createTextLayout(pageIndex));
const PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex);
const PageRotation rotation = getPageRotationCombined(page->getPageRotation(), getProxy()->getPageRotation());
switch (rotation)
{
case pdf::PageRotation::None:
case pdf::PageRotation::Rotate180:
m_isTransposed = false;
break;
case pdf::PageRotation::Rotate90:
case pdf::PageRotation::Rotate270:
m_isTransposed = true;
break;
default:
Q_ASSERT(false);
break;
}
autodetectTableGeometry();
emit messageDisplayRequest(tr("Table region was selected. Use left/right mouse buttons to add/remove rows/columns, then use Enter key to copy the table."), 5000);
}
void PDFSelectTableTool::autodetectTableGeometry()
{
// Strategy: for horizontal/vertical direction,
// detect columns/rows as follow: create overlap
// graph for each direction, detect overlap components.
// Then, remove "bridges" - rectangles overlapping
// two other rectangles, and these two rectangles
// does not overlap. These bridges are often header
// rows / columns.
// Detect text rectangles - divide them by lines
std::vector<QRectF> rectangles;
for (const PDFTextBlock& textBlock : m_textLayout.getTextBlocks())
{
for (const PDFTextLine& textLine : textBlock.getLines())
{
QRectF boundingRect = textLine.getBoundingBox().boundingRect();
if (!m_pickedRectangle.contains(boundingRect))
{
continue;
}
rectangles.push_back(boundingRect);
}
}
auto createComponents = [&](bool isHorizontal) -> std::vector<QRectF>
{
std::vector<QRectF> resultComponents;
std::map<size_t, std::set<size_t>> isOverlappedGraph;
// Create graph of overlapped rectangles
for (size_t i = 0; i < rectangles.size(); ++i)
{
isOverlappedGraph[i].insert(i);
for (size_t j = i + 1; j < rectangles.size(); ++j)
{
const QRectF& leftRect = rectangles[i];
const QRectF& rightRect = rectangles[j];
if ((isHorizontal && isRectangleHorizontallyOverlapped(leftRect, rightRect)) ||
(!isHorizontal && isRectangleVerticallyOverlapped(leftRect, rightRect)))
{
isOverlappedGraph[i].insert(j);
isOverlappedGraph[j].insert(i);
}
}
}
std::set<size_t> bridges;
// Detect bridges, i bridge <=> exist k,j, where isOverlappedGraph[i] has { k, j },
// and isOverlappedGraph[k] has not j. Second check is not neccessary, because
// graph is undirectional - if j is in isOverlappedGraph[k], then k is in isOverlappedGraph[j].
for (size_t i = 0; i < rectangles.size(); ++i)
{
bool isBridge = false;
for (size_t k : isOverlappedGraph[i])
{
if (k == i)
{
continue;
}
for (size_t j : isOverlappedGraph[i])
{
if (k == j)
{
continue;
}
if (!isOverlappedGraph[k].count(j))
{
isBridge = true;
break;
}
}
if (isBridge)
{
break;
}
}
if (isBridge)
{
bridges.insert(i);
}
}
// Remove bridges from overlapped graph
for (const size_t i : bridges)
{
isOverlappedGraph.erase(i);
}
for (auto& item : isOverlappedGraph)
{
std::set<size_t> result;
std::set_difference(item.second.begin(), item.second.end(), bridges.begin(), bridges.end(), std::inserter(result, result.end()));
item.second = std::move(result);
}
// Now, each component is a clique
std::set<size_t> visited;
for (auto& item : isOverlappedGraph)
{
if (visited.count(item.first))
{
continue;
}
visited.insert(item.second.begin(), item.second.end());
QRectF boundingRectangle;
for (size_t i : item.second)
{
boundingRectangle = boundingRectangle.united(rectangles[i]);
}
if (!boundingRectangle.isEmpty())
{
resultComponents.push_back(boundingRectangle);
}
}
return resultComponents;
};
// Columns
m_horizontalBreaks.clear();
std::vector<QRectF> columnComponents = createComponents(true);
std::sort(columnComponents.begin(), columnComponents.end(), [](const auto& left, const auto& right) { return left.center().x() < right.center().x(); });
for (size_t i = 1; i < columnComponents.size(); ++i)
{
const qreal start = columnComponents[i - 1].right();
const qreal end = columnComponents[i].left();
const qreal middle = (start + end) * 0.5;
m_horizontalBreaks.push_back(middle);
}
// Rows
m_verticalBreaks.clear();
std::vector<QRectF> rowComponents = createComponents(false);
std::sort(rowComponents.begin(), rowComponents.end(), [](const auto& left, const auto& right) { return left.center().y() < right.center().y(); });
for (size_t i = 1; i < rowComponents.size(); ++i)
{
const qreal start = rowComponents[i - 1].bottom();
const qreal end = rowComponents[i].top();
const qreal middle = (start + end) * 0.5;
m_verticalBreaks.push_back(middle);
}
}
bool PDFSelectTableTool::isTablePicked() const
{
return m_pageIndex != -1 && !m_pickedRectangle.isEmpty();
}
void PDFSelectTableTool::setTextLayout(PDFTextLayout&& newTextLayout)
{
m_textLayout = std::move(newTextLayout);
}
void PDFSelectTableTool::setPageIndex(PDFInteger newPageIndex)
{
m_pageIndex = newPageIndex;
}
void PDFSelectTableTool::setPickedRectangle(const QRectF& newPickedRectangle)
{
m_pickedRectangle = newPickedRectangle;
}
} // namespace pdf

View File

@ -373,6 +373,56 @@ private:
QColor m_selectionRectangleColor;
};
/// Tool for selection of table in document. Rows and columns
/// are automatically detected and are modifiable by the user.
class PDFSelectTableTool : public PDFWidgetTool
{
Q_OBJECT
private:
using BaseClass = PDFWidgetTool;
public:
/// Construct new table selection tool
/// \param proxy Draw widget proxy
/// \param action Tool activation action
/// \param parent Parent object
explicit PDFSelectTableTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent);
virtual void drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const override;
virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override;
virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override;
virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override;
virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override;
protected:
virtual void setActiveImpl(bool active) override;
private:
void setPickedRectangle(const QRectF& newPickedRectangle);
void setPageIndex(PDFInteger newPageIndex);
void setTextLayout(PDFTextLayout&& newTextLayout);
void autodetectTableGeometry();
bool isTablePicked() const;
void onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle);
PDFPickTool* m_pickTool;
PDFInteger m_pageIndex;
QRectF m_pickedRectangle;
PDFTextLayout m_textLayout;
bool m_isTransposed;
std::vector<PDFReal> m_horizontalBreaks;
std::vector<PDFReal> m_verticalBreaks;
};
/// Tool that makes screenshot of page area and copies it to the clipboard,
/// using current client area to determine image size.
class PDF4QTLIBSHARED_EXPORT PDFScreenshotTool : public PDFWidgetTool
@ -436,6 +486,7 @@ public:
QAction* findPrevAction = nullptr; ///< Action for navigating to previous result
QAction* findNextAction = nullptr; ///< Action for navigating to next result
QAction* selectTextToolAction = nullptr;
QAction* selectTableToolAction = nullptr;
QAction* selectAllAction = nullptr;
QAction* deselectAction = nullptr;
QAction* copyTextAction = nullptr;
@ -460,6 +511,7 @@ public:
PickRectangleTool,
FindTextTool,
SelectTextTool,
SelectTableTool,
MagnifierTool,
ScreenshotTool,
ExtractImageTool,

View File

@ -91,5 +91,6 @@
<file>resources/pce-same-size.svg</file>
<file>resources/pce-same-width.svg</file>
<file>resources/certificate-manager.svg</file>
<file>resources/select-table.svg</file>
</qresource>
</RCC>

View File

@ -44,7 +44,7 @@ PDFAboutDialog::PDFAboutDialog(QWidget* parent) :
ui->tableWidget->setSelectionMode(QTableView::SingleSelection);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
for (size_t i = 0; i < infos.size(); ++i)
for (int i = 0; i < int(infos.size()); ++i)
{
const pdf::PDFDependentLibraryInfo& info = infos[i];
ui->tableWidget->setItem(i, 0, new QTableWidgetItem(info.library));

View File

@ -939,6 +939,7 @@ void PDFProgramController::initializeToolManager()
actions.findPrevAction = m_actionManager->getAction(PDFActionManager::FindPrevious);
actions.findNextAction = m_actionManager->getAction(PDFActionManager::FindNext);
actions.selectTextToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectText);
actions.selectTableToolAction = m_actionManager->getAction(PDFActionManager::ToolSelectTable);
actions.selectAllAction = m_actionManager->getAction(PDFActionManager::SelectTextAll);
actions.deselectAction = m_actionManager->getAction(PDFActionManager::DeselectText);
actions.copyTextAction = m_actionManager->getAction(PDFActionManager::CopyText);

View File

@ -161,6 +161,7 @@ public:
PageLayoutTwoColumns,
PageLayoutFirstPageOnRightSide,
ToolSelectText,
ToolSelectTable,
ToolMagnifier,
ToolScreenshot,
ToolExtractImage,

View File

@ -181,6 +181,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
m_actionManager->setAction(PDFActionManager::PageLayoutTwoColumns, ui->actionPageLayoutTwoColumns);
m_actionManager->setAction(PDFActionManager::PageLayoutFirstPageOnRightSide, ui->actionFirstPageOnRightSide);
m_actionManager->setAction(PDFActionManager::ToolSelectText, ui->actionSelectText);
m_actionManager->setAction(PDFActionManager::ToolSelectTable, ui->actionSelectTable);
m_actionManager->setAction(PDFActionManager::ToolMagnifier, ui->actionMagnifier);
m_actionManager->setAction(PDFActionManager::ToolScreenshot, ui->actionScreenshot);
m_actionManager->setAction(PDFActionManager::ToolExtractImage, ui->actionExtractImage);
@ -238,6 +239,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
// Tools
ui->mainToolBar->addAction(ui->actionSelectText);
ui->mainToolBar->addAction(ui->actionSelectTable);
ui->mainToolBar->addAction(ui->actionCreateTextHighlight);
ui->mainToolBar->addAction(ui->actionCreateTextUnderline);
ui->mainToolBar->addAction(ui->actionCreateTextStrikeout);

View File

@ -136,6 +136,8 @@
<addaction name="actionSelectTextAll"/>
<addaction name="actionDeselectText"/>
<addaction name="separator"/>
<addaction name="actionSelectTable"/>
<addaction name="separator"/>
<addaction name="actionEncryption"/>
<addaction name="actionOptimize"/>
<addaction name="separator"/>
@ -470,6 +472,18 @@
<string>Select text</string>
</property>
</action>
<action name="actionSelectTable">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="pdf4qtviewer.qrc">
<normaloff>:/resources/select-table.svg</normaloff>:/resources/select-table.svg</iconset>
</property>
<property name="text">
<string>Select table</string>
</property>
</action>
<action name="actionSelectTextAll">
<property name="icon">
<iconset resource="pdf4qtviewer.qrc">

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Vrstva_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<polygon fill="#292D32" points="0.754,19.056 0.754,19.055 0.754,19.055 "/>
<path fill="#292D32" d="M20.458,5.659c-0.061-0.151-0.122-0.3-0.213-0.434c-0.406-0.61-1.025-0.918-1.845-0.918h-1.636h-1.634
L3.303,4.308c-0.939,0-1.704,0.484-2.058,1.273C1.217,5.7,1.069,6.356,1.052,6.851L1.047,19.676c0,0,0.025,0.778,0.159,1.18
c0.066,0.185,0.139,0.369,0.25,0.538c0.408,0.606,1.03,0.914,1.848,0.914h15.095c0.874-0.004,1.596-0.421,1.979-1.113
c0.192-0.407,0.255-1.249,0.271-1.523l0.006-12.733C20.653,6.424,20.485,5.768,20.458,5.659z M2.126,8.354c0-0.594,0-1.188,0-1.78
c0.001-0.748,0.431-1.179,1.178-1.179h7.548h7.551c0.743,0,1.171,0.432,1.171,1.183c0.002,0.554,0,1.107,0,1.661v0.887H2.126V8.354z
M19.562,10.24v2.922h-3.729V10.24H19.562z M5.867,10.235v2.919H2.142v-2.919H5.867z M5.868,21.218
C5.855,21.22,5.844,21.22,5.832,21.22c-0.848,0-1.697,0-2.545-0.002c-0.712,0-1.157-0.443-1.16-1.154c0-0.406,0-0.812,0-1.216
v-0.561h3.742L5.868,21.218L5.868,21.218z M5.875,17.182H2.144v-2.919h3.731V17.182z M6.967,10.243h3.327v2.914H6.967V10.243z
M10.293,21.206H6.968v-2.912h3.325V21.206z M10.299,17.182H6.97v-2.919h3.329V17.182z M14.725,21.202h-3.319v-2.918h3.319V21.202z
M14.73,17.184h-3.325V14.26h3.325V17.184z M14.73,13.157h-3.321v-2.921h3.321V13.157z M15.828,14.265h3.729v2.921h-3.729V14.265z
M19.573,19.306v0.259c0,0.259,0,0.526-0.03,0.781c-0.055,0.469-0.485,0.858-0.96,0.865c-0.471,0.008-0.941,0.01-1.413,0.01
l-1.338-0.002v-2.928h3.742l0.002,0.252C19.575,18.8,19.577,19.053,19.573,19.306z"/>
<g>
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M21.506,5.837c-0.144,0.036-0.272,0.067-0.401,0.097
c-0.156,0.037-0.275-0.065-0.264-0.225c0.007-0.087,0.064-0.15,0.16-0.174c0.207-0.05,0.412-0.098,0.618-0.147
c0.074-0.018,0.147-0.035,0.221-0.052c0.125-0.03,0.232,0.034,0.264,0.16c0.068,0.279,0.136,0.561,0.202,0.84
c0.03,0.126-0.03,0.227-0.147,0.255c-0.123,0.029-0.222-0.032-0.253-0.16c-0.025-0.104-0.051-0.209-0.075-0.313
c-0.001-0.002-0.005-0.005-0.009-0.01c-0.039,0.042-0.075,0.086-0.114,0.126c-0.482,0.497-1.07,0.728-1.759,0.672
c-0.953-0.077-1.74-0.78-1.938-1.715c-0.015-0.071-0.025-0.142-0.032-0.214c-0.011-0.123,0.067-0.216,0.183-0.228
c0.114-0.012,0.209,0.064,0.224,0.185c0.044,0.37,0.181,0.701,0.434,0.978c0.385,0.424,0.868,0.624,1.441,0.584
c0.486-0.033,0.888-0.243,1.207-0.611C21.478,5.874,21.487,5.862,21.506,5.837z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M18.766,3.757c0.104-0.024,0.192-0.046,0.283-0.067
c0.044-0.011,0.088-0.022,0.131-0.031c0.114-0.024,0.211,0.04,0.239,0.154c0.028,0.11-0.029,0.217-0.14,0.244
c-0.291,0.072-0.583,0.141-0.875,0.208c-0.1,0.023-0.204-0.042-0.229-0.146c-0.073-0.29-0.144-0.582-0.211-0.874
c-0.024-0.103,0.049-0.207,0.153-0.233c0.105-0.026,0.216,0.035,0.246,0.143c0.028,0.109,0.053,0.22,0.081,0.338
c0.036-0.041,0.067-0.076,0.1-0.111c0.322-0.349,0.724-0.553,1.185-0.653c0.93-0.204,2.262,0.36,2.535,1.691
c0.012,0.059,0.02,0.12,0.029,0.181c0.02,0.13-0.048,0.229-0.169,0.247c-0.131,0.02-0.225-0.056-0.24-0.193
c-0.021-0.188-0.066-0.37-0.15-0.54c-0.283-0.582-0.744-0.931-1.388-1.009c-0.616-0.074-1.132,0.139-1.543,0.604
C18.792,3.721,18.783,3.734,18.766,3.757z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB