#include "EntryAttachmentsWidget.h" #include "ui_EntryAttachmentsWidget.h" #include #include #include #include #include #include #include #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); } 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 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(e); const QMimeData* mimeData = dropEv->mimeData(); if (mimeData->hasUrls()) { dropEv->acceptProposedAction(); return true; } } else if (eventType == QEvent::Drop) { QDropEvent* dropEv = static_cast(e); const QMimeData* mimeData = dropEv->mimeData(); if (mimeData->hasUrls()) { dropEv->acceptProposedAction(); QStringList filenames; const QList 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); }