#Issue 21: Table selection tool + graphic

This commit is contained in:
Jakub Melka 2022-07-23 17:03:20 +02:00
parent eb17cdbed1
commit 159d6ad5d9
10 changed files with 315 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

@ -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,208 @@ 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_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::setActiveImpl(bool active)
{
BaseClass::setActiveImpl(active);
if (active)
{
addTool(m_pickTool);
}
else
{
// Clear all data
setPageIndex(-1);
setPickedRectangle(QRectF());
setTextLayout(PDFTextLayout());
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));
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()
{
}
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,53 @@ 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 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;
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 +483,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 +508,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