diff options
Diffstat (limited to 'src/gui/entry/EntryAttachmentsWidget.cpp')
-rw-r--r-- | src/gui/entry/EntryAttachmentsWidget.cpp | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp new file mode 100644 index 000000000..16908361a --- /dev/null +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -0,0 +1,370 @@ +#include "EntryAttachmentsWidget.h" +#include "ui_EntryAttachmentsWidget.h" + +#include <QDesktopServices> +#include <QDir> +#include <QDropEvent> +#include <QFile> +#include <QFileInfo> +#include <QMimeData> +#include <QTemporaryFile> + +#include "EntryAttachmentsModel.h" +#include "core/Config.h" +#include "core/EntryAttachments.h" +#include "core/Tools.h" +#include "gui/FileDialog.h" +#include "gui/MessageBox.h" + + +EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) : + QWidget(parent) + , m_ui(new Ui::EntryAttachmentsWidget) + , m_entryAttachments(new EntryAttachments(this)) + , m_attachmentsModel(new EntryAttachmentsModel(this)) + , m_readOnly(false) + , m_buttonsVisible(true) +{ + m_ui->setupUi(this); + + m_ui->attachmentsView->setAcceptDrops(false); + m_ui->attachmentsView->viewport()->setAcceptDrops(true); + m_ui->attachmentsView->viewport()->installEventFilter(this); + + m_attachmentsModel->setEntryAttachments(m_entryAttachments); + m_ui->attachmentsView->setModel(m_attachmentsModel); + m_ui->attachmentsView->verticalHeader()->hide(); + m_ui->attachmentsView->horizontalHeader()->setStretchLastSection(true); + m_ui->attachmentsView->horizontalHeader()->resizeSection(EntryAttachmentsModel::NameColumn, 400); + m_ui->attachmentsView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_ui->attachmentsView->setSelectionMode(QAbstractItemView::ExtendedSelection); + + m_ui->actionsWidget->setVisible(m_buttonsVisible); + connect(this, SIGNAL(buttonsVisibleChanged(bool)), m_ui->actionsWidget, SLOT(setVisible(bool))); + + connect(this, SIGNAL(readOnlyChanged(bool)), SLOT(updateButtonsEnabled())); + connect(m_attachmentsModel, SIGNAL(modelReset()), SLOT(updateButtonsEnabled())); + connect(m_ui->attachmentsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(updateButtonsEnabled())); + + connect(m_ui->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openAttachment(QModelIndex))); + connect(m_ui->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveSelectedAttachments())); + connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); + connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); + connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); + + updateButtonsEnabled(); +} + +EntryAttachmentsWidget::~EntryAttachmentsWidget() +{ +} + +const EntryAttachments* EntryAttachmentsWidget::entryAttachments() const +{ + return m_entryAttachments; +} + +bool EntryAttachmentsWidget::isReadOnly() const +{ + return m_readOnly; +} + +bool EntryAttachmentsWidget::isButtonsVisible() const +{ + return m_buttonsVisible; +} + +void EntryAttachmentsWidget::setEntryAttachments(const EntryAttachments* attachments) +{ + Q_ASSERT(attachments != nullptr); + m_entryAttachments->copyDataFrom(attachments); +} + +void EntryAttachmentsWidget::clearAttachments() +{ + m_entryAttachments->clear(); +} + +void EntryAttachmentsWidget::setReadOnly(bool readOnly) +{ + if (m_readOnly == readOnly) { + return; + } + + m_readOnly = readOnly; + emit readOnlyChanged(m_readOnly); +} + +void EntryAttachmentsWidget::setButtonsVisible(bool buttonsVisible) +{ + if (m_buttonsVisible == buttonsVisible) { + return; + } + + m_buttonsVisible = buttonsVisible; + emit buttonsVisibleChanged(m_buttonsVisible); +} + +QByteArray EntryAttachmentsWidget::getAttachment(const QString& name) +{ + return m_entryAttachments->value(name); +} + +void EntryAttachmentsWidget::setAttachment(const QString& name, const QByteArray& value) +{ + m_entryAttachments->set(name, value); +} + +void EntryAttachmentsWidget::removeAttachment(const QString& name) +{ + if (!isReadOnly() && m_entryAttachments->hasKey(name)) { + m_entryAttachments->remove(name); + } +} + +void EntryAttachmentsWidget::insertAttachments() +{ + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return; + } + + QString defaultDirPath = config()->get("LastAttachmentDir").toString(); + const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists(); + if (!dirExists) { + defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first(); + } + + const QStringList filenames = fileDialog()->getOpenFileNames(this, tr("Select files"), defaultDirPath); + if (filenames.isEmpty()) { + return; + } + + config()->set("LastAttachmentDir", QFileInfo(filenames.first()).absolutePath()); + + QString errorMessage; + if (!insertAttachments(filenames, errorMessage)) { + errorOccurred(errorMessage); + } +} + +void EntryAttachmentsWidget::removeSelectedAttachments() +{ + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return; + } + + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); + if (indexes.isEmpty()) { + return; + } + + const QString question = tr("Are you sure you want to remove %n attachment(s)?", "", indexes.count()); + QMessageBox::StandardButton answer = MessageBox::question(this, tr("Confirm Remove"), + question, QMessageBox::Yes | QMessageBox::No); + if (answer == QMessageBox::Yes) { + QStringList keys; + for (const QModelIndex& index: indexes) { + keys.append(m_attachmentsModel->keyByIndex(index)); + } + m_entryAttachments->remove(keys); + } +} + +void EntryAttachmentsWidget::saveSelectedAttachments() +{ + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); + if (indexes.isEmpty()) { + return; + } + + QString defaultDirPath = config()->get("LastAttachmentDir").toString(); + const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists(); + if (!dirExists) { + defaultDirPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + } + + const QString saveDirPath = fileDialog()->getExistingDirectory(this, tr("Save attachments"), defaultDirPath); + if (saveDirPath.isEmpty()) { + return; + } + + QDir saveDir(saveDirPath); + if (!saveDir.exists()) { + if (saveDir.mkpath(saveDir.absolutePath())) { + errorOccurred(tr("Unable to create directory:\n%1").arg(saveDir.absolutePath())); + return; + } + } + config()->set("LastAttachmentDir", QFileInfo(saveDir.absolutePath()).absolutePath()); + + QStringList errors; + for (const QModelIndex& index: indexes) { + const QString filename = m_attachmentsModel->keyByIndex(index); + const QString attachmentPath = saveDir.absoluteFilePath(filename); + + if (QFileInfo::exists(attachmentPath)) { + const QString question(tr("Are you sure you want to overwrite the existing file \"%1\" with the attachment?")); + auto answer = MessageBox::question(this, tr("Confirm overwrite"), question.arg(filename), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (answer == QMessageBox::No) { + continue; + } else if (answer == QMessageBox::Cancel) { + return; + } + } + + QFile file(attachmentPath); + const QByteArray attachmentData = m_entryAttachments->value(filename); + const bool saveOk = file.open(QIODevice::WriteOnly) && file.write(attachmentData) == attachmentData.size(); + if (!saveOk) { + errors.append(QString("%1 - %2").arg(filename, file.errorString())); + } + } + + if (!errors.isEmpty()) { + errorOccurred(tr("Unable to save attachments:\n%1").arg(errors.join('\n'))); + } +} + +void EntryAttachmentsWidget::openAttachment(const QModelIndex& index) +{ + Q_ASSERT(index.isValid()); + if (!index.isValid()) { + return; + } + + QString errorMessage; + if (!openAttachment(index, errorMessage)) { + errorOccurred(tr("Unable to open attachment:\n%1").arg(errorMessage)); + } +} + +void EntryAttachmentsWidget::openSelectedAttachments() +{ + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); + if (indexes.isEmpty()) { + return; + } + + QStringList errors; + for (const QModelIndex& index: indexes) { + QString errorMessage; + if (!openAttachment(index, errorMessage)) { + const QString filename = m_attachmentsModel->keyByIndex(index); + errors.append(QString("%1 - %2").arg(filename, errorMessage)); + }; + } + + if (!errors.isEmpty()) { + errorOccurred(tr("Unable to open attachments:\n%1").arg(errors.join('\n'))); + } +} + +void EntryAttachmentsWidget::updateButtonsEnabled() +{ + const bool hasSelection = m_ui->attachmentsView->selectionModel()->hasSelection(); + + m_ui->addAttachmentButton->setEnabled(!m_readOnly); + m_ui->removeAttachmentButton->setEnabled(hasSelection && !m_readOnly); + + m_ui->saveAttachmentButton->setEnabled(hasSelection); + m_ui->openAttachmentButton->setEnabled(hasSelection); +} + +bool EntryAttachmentsWidget::insertAttachments(const QStringList& filenames, QString& errorMessage) +{ + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return false; + } + + QStringList errors; + for (const QString &filename: filenames) { + QByteArray data; + QFile file(filename); + const QFileInfo fInfo(filename); + const bool readOk = file.open(QIODevice::ReadOnly) && Tools::readAllFromDevice(&file, data); + if (readOk) { + m_entryAttachments->set(fInfo.fileName(), data); + } else { + errors.append(QString("%1 - %2").arg(fInfo.fileName(), file.errorString())); + } + } + + if (!errors.isEmpty()) { + errorMessage = tr("Unable to open files:\n%1").arg(errors.join('\n')); + } + + return errors.isEmpty(); +} + +bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& errorMessage) +{ + const QString filename = m_attachmentsModel->keyByIndex(index); + const QByteArray attachmentData = m_entryAttachments->value(filename); + + // tmp file will be removed once the database (or the application) has been closed + const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); + + QScopedPointer<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this)); + + const bool saveOk = tmpFile->open() + && tmpFile->write(attachmentData) == attachmentData.size() + && tmpFile->flush(); + if (!saveOk) { + errorMessage = QString("%1 - %2").arg(filename, tmpFile->errorString()); + return false; + } + + tmpFile->close(); + const bool openOk = QDesktopServices::openUrl(QUrl::fromLocalFile(tmpFile->fileName())); + if (!openOk) { + errorMessage = QString("Can't open file \"%1\"").arg(filename); + return false; + } + + // take ownership of the tmpFile pointer + tmpFile.take(); + return true; +} + +bool EntryAttachmentsWidget::eventFilter(QObject* watched, QEvent* e) +{ + if (watched == m_ui->attachmentsView->viewport() && !isReadOnly()) { + const QEvent::Type eventType = e->type(); + if (eventType == QEvent::DragEnter || eventType == QEvent::DragMove) { + QDropEvent* dropEv = static_cast<QDropEvent*>(e); + const QMimeData* mimeData = dropEv->mimeData(); + if (mimeData->hasUrls()) { + dropEv->acceptProposedAction(); + return true; + } + } else if (eventType == QEvent::Drop) { + QDropEvent* dropEv = static_cast<QDropEvent*>(e); + const QMimeData* mimeData = dropEv->mimeData(); + if (mimeData->hasUrls()) { + dropEv->acceptProposedAction(); + QStringList filenames; + const QList<QUrl> urls = mimeData->urls(); + for (const QUrl& url: urls) { + const QFileInfo fInfo(url.toLocalFile()); + if (fInfo.isFile()) { + filenames.append(fInfo.absoluteFilePath()); + } + } + + QString errorMessage; + if (!insertAttachments(filenames, errorMessage)) { + errorOccurred(errorMessage); + } + + return true; + } + } + } + + return QWidget::eventFilter(watched, e); +} |