mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-01-16 02:20:50 +01:00
503 lines
18 KiB
C++
503 lines
18 KiB
C++
// Copyright (C) 2021 Jakub Melka
|
|
//
|
|
// This file is part of Pdf4Qt.
|
|
//
|
|
// Pdf4Qt is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// with the written consent of the copyright owner, any later version.
|
|
//
|
|
// Pdf4Qt is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with Pdf4Qt. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include "mainwindow.h"
|
|
#include "ui_mainwindow.h"
|
|
|
|
#include "pdfwidgetutils.h"
|
|
#include "pdfdocumentreader.h"
|
|
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
#include <QInputDialog>
|
|
#include <QDesktopWidget>
|
|
#include <QClipboard>
|
|
#include <QToolBar>
|
|
#include <QDesktopServices>
|
|
|
|
namespace pdfdocpage
|
|
{
|
|
|
|
MainWindow::MainWindow(QWidget* parent) :
|
|
QMainWindow(parent),
|
|
ui(new Ui::MainWindow),
|
|
m_model(new PageItemModel(this)),
|
|
m_delegate(new PageItemDelegate(m_model, this)),
|
|
m_dropAction(Qt::IgnoreAction)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
m_delegate->setPageImageSize(getDefaultPageImageSize());
|
|
|
|
ui->documentItemsView->setModel(m_model);
|
|
ui->documentItemsView->setItemDelegate(m_delegate);
|
|
setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(800, 600)));
|
|
|
|
ui->actionClear->setData(int(Operation::Clear));
|
|
ui->actionCloneSelection->setData(int(Operation::CloneSelection));
|
|
ui->actionRemoveSelection->setData(int(Operation::RemoveSelection));
|
|
ui->actionReplaceSelection->setData(int(Operation::ReplaceSelection));
|
|
ui->actionRestoreRemovedItems->setData(int(Operation::RestoreRemovedItems));
|
|
ui->actionCut->setData(int(Operation::Cut));
|
|
ui->actionCopy->setData(int(Operation::Copy));
|
|
ui->actionPaste->setData(int(Operation::Paste));
|
|
ui->actionRotate_Left->setData(int(Operation::RotateLeft));
|
|
ui->actionRotate_Right->setData(int(Operation::RotateRight));
|
|
ui->actionGroup->setData(int(Operation::Group));
|
|
ui->actionUngroup->setData(int(Operation::Ungroup));
|
|
ui->actionSelect_None->setData(int(Operation::SelectNone));
|
|
ui->actionSelect_All->setData(int(Operation::SelectAll));
|
|
ui->actionSelect_Even->setData(int(Operation::SelectEven));
|
|
ui->actionSelect_Odd->setData(int(Operation::SelectOdd));
|
|
ui->actionSelect_Portrait->setData(int(Operation::SelectPortrait));
|
|
ui->actionSelect_Landscape->setData(int(Operation::SelectLandscape));
|
|
ui->actionZoom_In->setData(int(Operation::ZoomIn));
|
|
ui->actionZoom_Out->setData(int(Operation::ZoomOut));
|
|
ui->actionUnited_Document->setData(int(Operation::Unite));
|
|
ui->actionSeparate_to_Multiple_Documents->setData(int(Operation::Separate));
|
|
ui->actionSeparate_to_Multiple_Documents_Grouped->setData(int(Operation::SeparateGrouped));
|
|
ui->actionInsert_Image->setData(int(Operation::InsertImage));
|
|
ui->actionInsert_Empty_Page->setData(int(Operation::InsertEmptyPage));
|
|
ui->actionInsert_Page_from_PDF->setData(int(Operation::InsertPDF));
|
|
ui->actionGet_Source->setData(int(Operation::GetSource));
|
|
ui->actionAbout->setData(int(Operation::About));
|
|
|
|
QToolBar* mainToolbar = addToolBar(tr("Main"));
|
|
mainToolbar->setObjectName("main_toolbar");
|
|
mainToolbar->addAction(ui->actionAddDocument);
|
|
mainToolbar->addSeparator();
|
|
mainToolbar->addActions({ ui->actionCloneSelection, ui->actionRemoveSelection });
|
|
mainToolbar->addSeparator();
|
|
mainToolbar->addActions({ ui->actionCut, ui->actionCopy, ui->actionPaste });
|
|
mainToolbar->addSeparator();
|
|
mainToolbar->addActions({ ui->actionGroup, ui->actionUngroup });
|
|
QToolBar* insertToolbar = addToolBar(tr("Insert"));
|
|
insertToolbar->setObjectName("insert_toolbar");
|
|
insertToolbar->addActions({ ui->actionInsert_Page_from_PDF, ui->actionInsert_Image, ui->actionInsert_Empty_Page });
|
|
QToolBar* selectToolbar = addToolBar(tr("Select"));
|
|
selectToolbar->setObjectName("select_toolbar");
|
|
selectToolbar->addActions({ ui->actionSelect_None, ui->actionSelect_All, ui->actionSelect_Even, ui->actionSelect_Odd, ui->actionSelect_Portrait, ui->actionSelect_Landscape });
|
|
QToolBar* zoomToolbar = addToolBar(tr("Zoom"));
|
|
zoomToolbar->setObjectName("zoom_toolbar");
|
|
zoomToolbar->addActions({ ui->actionZoom_In, ui->actionZoom_Out });
|
|
QToolBar* makeToolbar = addToolBar(tr("Make"));
|
|
makeToolbar->setObjectName("make_toolbar");
|
|
makeToolbar->addActions({ ui->actionUnited_Document, ui->actionSeparate_to_Multiple_Documents, ui->actionSeparate_to_Multiple_Documents_Grouped });
|
|
|
|
QSize iconSize = pdf::PDFWidgetUtils::scaleDPI(this, QSize(24, 24));
|
|
auto toolbars = findChildren<QToolBar*>();
|
|
for (QToolBar* toolbar : toolbars)
|
|
{
|
|
toolbar->setIconSize(iconSize);
|
|
ui->menuWindow->addAction(toolbar->toggleViewAction());
|
|
}
|
|
|
|
connect(&m_mapper, QOverload<int>::of(&QSignalMapper::mapped), this, &MainWindow::onMappedActionTriggered);
|
|
connect(ui->documentItemsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions);
|
|
|
|
QList<QAction*> actions = findChildren<QAction*>();
|
|
for (QAction* action : actions)
|
|
{
|
|
QVariant actionData = action->data();
|
|
if (actionData.isValid())
|
|
{
|
|
connect(action, &QAction::triggered, &m_mapper, QOverload<>::of(&QSignalMapper::map));
|
|
m_mapper.setMapping(action, actionData.toInt());
|
|
}
|
|
}
|
|
|
|
loadSettings();
|
|
updateActions();
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
saveSettings();
|
|
delete ui;
|
|
}
|
|
|
|
QSize MainWindow::getMinPageImageSize() const
|
|
{
|
|
return pdf::PDFWidgetUtils::scaleDPI(this, QSize(40, 40));
|
|
}
|
|
|
|
QSize MainWindow::getDefaultPageImageSize() const
|
|
{
|
|
return pdf::PDFWidgetUtils::scaleDPI(this, QSize(100, 100));
|
|
}
|
|
|
|
QSize MainWindow::getMaxPageImageSize() const
|
|
{
|
|
return pdf::PDFWidgetUtils::scaleDPI(this, QSize(250, 250));
|
|
}
|
|
|
|
void MainWindow::on_actionClose_triggered()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void MainWindow::on_actionAddDocument_triggered()
|
|
{
|
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)"));
|
|
if (!fileName.isEmpty())
|
|
{
|
|
addDocument(fileName);
|
|
}
|
|
}
|
|
|
|
void MainWindow::onMappedActionTriggered(int actionId)
|
|
{
|
|
performOperation(static_cast<Operation>(actionId));
|
|
}
|
|
|
|
void MainWindow::updateActions()
|
|
{
|
|
QList<QAction*> actions = findChildren<QAction*>();
|
|
for (QAction* action : actions)
|
|
{
|
|
QVariant actionData = action->data();
|
|
if (actionData.isValid())
|
|
{
|
|
action->setEnabled(canPerformOperation(static_cast<Operation>(actionData.toInt())));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::loadSettings()
|
|
{
|
|
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName());
|
|
settings.beginGroup("MainWindow");
|
|
QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
|
|
if (geometry.isEmpty())
|
|
{
|
|
QRect availableGeometry = QApplication::desktop()->availableGeometry(this);
|
|
QRect windowRect(0, 0, availableGeometry.width() / 2, availableGeometry.height() / 2);
|
|
windowRect = windowRect.translated(availableGeometry.center() - windowRect.center());
|
|
setGeometry(windowRect);
|
|
}
|
|
else
|
|
{
|
|
restoreGeometry(geometry);
|
|
}
|
|
|
|
QByteArray state = settings.value("windowState", QByteArray()).toByteArray();
|
|
if (!state.isEmpty())
|
|
{
|
|
restoreState(state);
|
|
}
|
|
settings.endGroup();
|
|
|
|
settings.beginGroup("Settings");
|
|
m_settings.directory = settings.value("directory").toString();
|
|
settings.endGroup();
|
|
}
|
|
|
|
void MainWindow::saveSettings()
|
|
{
|
|
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName());
|
|
settings.beginGroup("MainWindow");
|
|
settings.setValue("geometry", saveGeometry());
|
|
settings.setValue("windowState", saveState());
|
|
settings.endGroup();
|
|
|
|
settings.beginGroup("Settings");
|
|
settings.setValue("directory", m_settings.directory);
|
|
settings.endGroup();
|
|
}
|
|
|
|
void MainWindow::addDocument(const QString& fileName)
|
|
{
|
|
auto queryPassword = [this](bool* ok)
|
|
{
|
|
*ok = false;
|
|
return QInputDialog::getText(this, tr("Encrypted document"), tr("Enter password to access document content"), QLineEdit::Password, QString(), ok);
|
|
};
|
|
|
|
// Mark current directory as this
|
|
QFileInfo fileInfo(fileName);
|
|
m_settings.directory = fileInfo.dir().absolutePath();
|
|
|
|
// Try to open a new document
|
|
pdf::PDFDocumentReader reader(nullptr, qMove(queryPassword), true, false);
|
|
pdf::PDFDocument document = reader.readFromFile(fileName);
|
|
|
|
QString errorMessage = reader.getErrorMessage();
|
|
pdf::PDFDocumentReader::Result result = reader.getReadingResult();
|
|
if (result == pdf::PDFDocumentReader::Result::OK)
|
|
{
|
|
const pdf::PDFSecurityHandler* securityHandler = document.getStorage().getSecurityHandler();
|
|
if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Assemble) ||
|
|
securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Modify))
|
|
{
|
|
m_model->addDocument(fileName, qMove(document));
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::critical(this, tr("Error"), tr("Document security doesn't permit to organize pages."));
|
|
}
|
|
}
|
|
else if (result == pdf::PDFDocumentReader::Result::Failed)
|
|
{
|
|
QMessageBox::critical(this, tr("Error"), errorMessage);
|
|
}
|
|
|
|
updateActions();
|
|
}
|
|
|
|
bool MainWindow::canPerformOperation(Operation operation) const
|
|
{
|
|
QModelIndexList selection = ui->documentItemsView->selectionModel()->selection().indexes();
|
|
const bool isSelected = !selection.isEmpty();
|
|
const bool isMultiSelected = selection.size() > 1;
|
|
const bool isModelEmpty = m_model->rowCount(QModelIndex()) == 0;
|
|
|
|
switch (operation)
|
|
{
|
|
case Operation::Clear:
|
|
return true;
|
|
|
|
case Operation::CloneSelection:
|
|
case Operation::RemoveSelection:
|
|
case Operation::ReplaceSelection:
|
|
return isSelected;
|
|
|
|
case Operation::RestoreRemovedItems:
|
|
return !m_model->isTrashBinEmpty();
|
|
|
|
case Operation::Cut:
|
|
case Operation::Copy:
|
|
return isSelected;
|
|
|
|
case Operation::Paste:
|
|
{
|
|
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
|
return mimeData && mimeData->hasFormat(PageItemModel::getMimeDataType());
|
|
}
|
|
|
|
case Operation::RotateLeft:
|
|
case Operation::RotateRight:
|
|
return isSelected;
|
|
|
|
case Operation::Group:
|
|
return isMultiSelected;
|
|
case Operation::Ungroup:
|
|
return m_model->isGrouped(selection);
|
|
|
|
case Operation::SelectNone:
|
|
return isSelected;
|
|
|
|
case Operation::SelectAll:
|
|
case Operation::SelectEven:
|
|
case Operation::SelectOdd:
|
|
case Operation::SelectPortrait:
|
|
case Operation::SelectLandscape:
|
|
return !isModelEmpty;
|
|
|
|
case Operation::ZoomIn:
|
|
return m_delegate->getPageImageSize() != getMaxPageImageSize();
|
|
|
|
case Operation::ZoomOut:
|
|
return m_delegate->getPageImageSize() != getMinPageImageSize();
|
|
|
|
case Operation::Unite:
|
|
case Operation::Separate:
|
|
case Operation::SeparateGrouped:
|
|
return !isModelEmpty;
|
|
|
|
case Operation::InsertImage:
|
|
case Operation::InsertEmptyPage:
|
|
case Operation::InsertPDF:
|
|
case Operation::GetSource:
|
|
case Operation::About:
|
|
return true;
|
|
|
|
default:
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MainWindow::performOperation(Operation operation)
|
|
{
|
|
switch (operation)
|
|
{
|
|
case Operation::Clear:
|
|
{
|
|
m_model->clear();
|
|
break;
|
|
}
|
|
case Operation::CloneSelection:
|
|
{
|
|
m_model->cloneSelection(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
}
|
|
|
|
case Operation::RemoveSelection:
|
|
{
|
|
m_model->removeSelection(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
}
|
|
|
|
case Operation::RestoreRemovedItems:
|
|
{
|
|
QModelIndexList restoredItemIndices = m_model->restoreRemovedItems();
|
|
QItemSelection itemSelection;
|
|
for (const QModelIndex& index : restoredItemIndices)
|
|
{
|
|
itemSelection.select(index, index);
|
|
}
|
|
ui->documentItemsView->selectionModel()->select(itemSelection, QItemSelectionModel::ClearAndSelect);
|
|
break;
|
|
}
|
|
|
|
case Operation::Cut:
|
|
case Operation::Copy:
|
|
{
|
|
QModelIndexList indices = ui->documentItemsView->selectionModel()->selection().indexes();
|
|
|
|
if (indices.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (QMimeData* mimeData = m_model->mimeData(indices))
|
|
{
|
|
QApplication::clipboard()->setMimeData(mimeData);
|
|
}
|
|
|
|
ui->documentItemsView->clearSelection();
|
|
m_dropAction = (operation == Operation::Cut) ? Qt::MoveAction : Qt::CopyAction;
|
|
break;
|
|
}
|
|
|
|
case Operation::Paste:
|
|
{
|
|
QModelIndexList indices = ui->documentItemsView->selectionModel()->selection().indexes();
|
|
|
|
int insertRow = m_model->rowCount(QModelIndex()) - 1;
|
|
if (!indices.isEmpty())
|
|
{
|
|
insertRow = indices.back().row();
|
|
}
|
|
|
|
QModelIndex insertIndex = m_model->index(insertRow, 0, QModelIndex());
|
|
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
|
if (m_model->canDropMimeData(mimeData, m_dropAction, -1, -1, insertIndex))
|
|
{
|
|
m_model->dropMimeData(mimeData, m_dropAction, -1, -1, insertIndex);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Operation::Group:
|
|
m_model->group(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
|
|
case Operation::Ungroup:
|
|
m_model->ungroup(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
|
|
case Operation::SelectNone:
|
|
ui->documentItemsView->clearSelection();
|
|
break;
|
|
|
|
case Operation::SelectAll:
|
|
ui->documentItemsView->selectAll();
|
|
break;
|
|
|
|
case Operation::SelectEven:
|
|
ui->documentItemsView->selectionModel()->select(m_model->getSelectionEven(), QItemSelectionModel::ClearAndSelect);
|
|
break;
|
|
|
|
case Operation::SelectOdd:
|
|
ui->documentItemsView->selectionModel()->select(m_model->getSelectionOdd(), QItemSelectionModel::ClearAndSelect);
|
|
break;
|
|
|
|
case Operation::SelectPortrait:
|
|
ui->documentItemsView->selectionModel()->select(m_model->getSelectionPortrait(), QItemSelectionModel::ClearAndSelect);
|
|
break;
|
|
|
|
case Operation::SelectLandscape:
|
|
ui->documentItemsView->selectionModel()->select(m_model->getSelectionLandscape(), QItemSelectionModel::ClearAndSelect);
|
|
break;
|
|
|
|
case Operation::ZoomIn:
|
|
{
|
|
QSize pageImageSize = m_delegate->getPageImageSize();
|
|
pageImageSize *= 1.2;
|
|
pageImageSize = pageImageSize.boundedTo(getMaxPageImageSize());
|
|
|
|
if (pageImageSize != m_delegate->getPageImageSize())
|
|
{
|
|
m_delegate->setPageImageSize(pageImageSize);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Operation::ZoomOut:
|
|
{
|
|
QSize pageImageSize = m_delegate->getPageImageSize();
|
|
pageImageSize /= 1.2;
|
|
pageImageSize = pageImageSize.expandedTo(getMinPageImageSize());
|
|
|
|
if (pageImageSize != m_delegate->getPageImageSize())
|
|
{
|
|
m_delegate->setPageImageSize(pageImageSize);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Operation::RotateLeft:
|
|
m_model->rotateLeft(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
|
|
case Operation::RotateRight:
|
|
m_model->rotateRight(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
|
|
case Operation::ReplaceSelection:
|
|
Q_ASSERT(false);
|
|
break;
|
|
|
|
case Operation::GetSource:
|
|
QDesktopServices::openUrl(QUrl("https://github.com/JakubMelka/PdfForQt"));
|
|
break;
|
|
|
|
case Operation::InsertEmptyPage:
|
|
m_model->insertEmptyPage(ui->documentItemsView->selectionModel()->selection().indexes());
|
|
break;
|
|
|
|
case Operation::InsertImage:
|
|
case Operation::InsertPDF:
|
|
case Operation::About:
|
|
|
|
case Operation::Unite:
|
|
case Operation::Separate:
|
|
case Operation::SeparateGrouped:
|
|
|
|
default:
|
|
Q_ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
updateActions();
|
|
}
|
|
|
|
} // namespace pdfdocpage
|