keepassxc/src/gui/EditWidgetIcons.cpp
Bernhard Kirchen 4e8b00da34 Add custom icon purging and bulk deletion
This change adds a new database settings widget 
named "maintenance", using a wrench icon. This widget is designated to be the home for database related maintenance tasks. 

Initially, managing custom icons is now possible from that new tab. The feature includes bulk removing of
any number of selected custom icons and automatic purging of unused custom icons by the click of a button.

Fixes #2110
2021-02-27 08:13:05 -05:00

353 lines
11 KiB
C++

/*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "EditWidgetIcons.h"
#include "ui_EditWidgetIcons.h"
#include <QFileDialog>
#include <QMessageBox>
#include "core/Config.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "gui/IconModels.h"
#include "gui/MessageBox.h"
#ifdef WITH_XC_NETWORKING
#include "gui/IconDownloader.h"
#endif
IconStruct::IconStruct()
: uuid(QUuid())
, number(0)
, applyTo(ApplyIconToOptions::THIS_ONLY)
{
}
EditWidgetIcons::EditWidgetIcons(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::EditWidgetIcons())
, m_db(nullptr)
, m_applyIconTo(ApplyIconToOptions::THIS_ONLY)
, m_defaultIconModel(new DefaultIconModel(this))
, m_customIconModel(new CustomIconModel(this))
#ifdef WITH_XC_NETWORKING
, m_downloader(new IconDownloader())
#endif
{
m_ui->setupUi(this);
m_ui->defaultIconsView->setModel(m_defaultIconModel);
m_ui->customIconsView->setModel(m_customIconModel);
m_ui->applyIconToPushButton->setMenu(createApplyIconToMenu());
// clang-format off
connect(m_ui->defaultIconsView, SIGNAL(clicked(QModelIndex)), this, SLOT(updateRadioButtonDefaultIcons()));
connect(m_ui->customIconsView, SIGNAL(clicked(QModelIndex)), this, SLOT(updateRadioButtonCustomIcons()));
connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SLOT(updateWidgetsDefaultIcons(bool)));
connect(m_ui->customIconsRadio, SIGNAL(toggled(bool)), this, SLOT(updateWidgetsCustomIcons(bool)));
connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIconFromFile()));
connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon()));
connect(m_ui->applyIconToPushButton->menu(), SIGNAL(triggered(QAction*)), SLOT(confirmApplyIconTo(QAction*)));
connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated()));
connect(m_ui->defaultIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SIGNAL(widgetUpdated()));
connect(m_ui->customIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SIGNAL(widgetUpdated()));
#ifdef WITH_XC_NETWORKING
connect(m_downloader.data(),
SIGNAL(finished(const QString&, const QImage&)),
SLOT(iconReceived(const QString&, const QImage&)));
#endif
// clang-format on
m_ui->faviconButton->setVisible(false);
m_ui->addButton->setEnabled(true);
}
EditWidgetIcons::~EditWidgetIcons()
{
}
IconStruct EditWidgetIcons::state()
{
Q_ASSERT(m_db);
Q_ASSERT(!m_currentUuid.isNull());
IconStruct iconStruct;
if (m_ui->defaultIconsRadio->isChecked()) {
QModelIndex index = m_ui->defaultIconsView->currentIndex();
if (index.isValid()) {
iconStruct.number = index.row();
} else {
Q_ASSERT(false);
}
} else {
QModelIndex index = m_ui->customIconsView->currentIndex();
if (index.isValid()) {
iconStruct.uuid = m_customIconModel->uuidFromIndex(m_ui->customIconsView->currentIndex());
} else {
iconStruct.number = -1;
}
}
iconStruct.applyTo = m_applyIconTo;
return iconStruct;
}
void EditWidgetIcons::reset()
{
m_db.reset();
m_currentUuid = QUuid();
}
void EditWidgetIcons::load(const QUuid& currentUuid,
const QSharedPointer<Database>& database,
const IconStruct& iconStruct,
const QString& url)
{
Q_ASSERT(database);
Q_ASSERT(!currentUuid.isNull());
m_db = database;
m_currentUuid = currentUuid;
setUrl(url);
m_customIconModel->setIcons(database->metadata()->customIconsPixmaps(IconSize::Default),
database->metadata()->customIconsOrder());
QUuid iconUuid = iconStruct.uuid;
if (iconUuid.isNull()) {
int iconNumber = iconStruct.number;
m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(iconNumber, 0));
m_ui->defaultIconsRadio->setChecked(true);
} else {
QModelIndex index = m_customIconModel->indexFromUuid(iconUuid);
if (index.isValid()) {
m_ui->customIconsView->setCurrentIndex(index);
m_ui->customIconsRadio->setChecked(true);
} else {
m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(0, 0));
m_ui->defaultIconsRadio->setChecked(true);
}
}
m_applyIconTo = ApplyIconToOptions::THIS_ONLY;
m_ui->applyIconToPushButton->menu()->defaultAction()->activate(QAction::Trigger);
}
void EditWidgetIcons::setShowApplyIconToButton(bool state)
{
m_ui->applyIconToPushButton->setVisible(state);
}
QMenu* EditWidgetIcons::createApplyIconToMenu()
{
auto* applyIconToMenu = new QMenu(this);
QAction* defaultAction = applyIconToMenu->addAction(tr("Apply to this group only"));
defaultAction->setData(QVariant::fromValue(ApplyIconToOptions::THIS_ONLY));
applyIconToMenu->setDefaultAction(defaultAction);
applyIconToMenu->addSeparator();
applyIconToMenu->addAction(tr("Also apply to child groups"))
->setData(QVariant::fromValue(ApplyIconToOptions::CHILD_GROUPS));
applyIconToMenu->addAction(tr("Also apply to child entries"))
->setData(QVariant::fromValue(ApplyIconToOptions::CHILD_ENTRIES));
applyIconToMenu->addAction(tr("Also apply to all children"))
->setData(QVariant::fromValue(ApplyIconToOptions::ALL_CHILDREN));
return applyIconToMenu;
}
void EditWidgetIcons::setUrl(const QString& url)
{
#ifdef WITH_XC_NETWORKING
m_url = url;
m_ui->faviconButton->setVisible(!url.isEmpty());
#else
Q_UNUSED(url);
m_ui->faviconButton->setVisible(false);
#endif
}
void EditWidgetIcons::downloadFavicon()
{
#ifdef WITH_XC_NETWORKING
if (!m_url.isEmpty()) {
m_downloader->setUrl(m_url);
m_downloader->download();
}
#endif
}
void EditWidgetIcons::iconReceived(const QString& url, const QImage& icon)
{
#ifdef WITH_XC_NETWORKING
Q_UNUSED(url);
if (icon.isNull()) {
QString message(tr("Unable to fetch favicon."));
if (!config()->get(Config::Security_IconDownloadFallback).toBool()) {
message.append("\n").append(
tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security"));
}
emit messageEditEntry(message, MessageWidget::Error);
return;
}
if (!addCustomIcon(icon)) {
emit messageEditEntry(tr("Existing icon selected."), MessageWidget::Information);
}
#else
Q_UNUSED(url);
Q_UNUSED(icon);
#endif
}
void EditWidgetIcons::abortRequests()
{
#ifdef WITH_XC_NETWORKING
if (m_downloader) {
m_downloader->abortDownload();
}
#endif
}
void EditWidgetIcons::addCustomIconFromFile()
{
if (!m_db) {
return;
}
QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files"));
auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter);
if (!filenames.empty()) {
QStringList errornames;
int numexisting = 0;
for (const auto& filename : filenames) {
if (!filename.isEmpty()) {
auto icon = QImage(filename);
if (icon.isNull()) {
errornames << filename;
} else if (!addCustomIcon(icon)) {
// Icon already exists in database
++numexisting;
}
}
}
int numloaded = filenames.size() - errornames.size() - numexisting;
QString msg;
if (numloaded > 0) {
msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded);
} else {
msg = tr("No icons were loaded");
}
if (numexisting > 0) {
msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting);
}
if (!errornames.empty()) {
// Show the first 8 icons that failed to load
errornames = errornames.mid(0, 8);
emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + "\n"
+ errornames.join("\n"),
MessageWidget::Error);
} else if (numloaded > 0) {
emit messageEditEntry(msg, MessageWidget::Positive);
} else {
emit messageEditEntry(msg, MessageWidget::Information);
}
}
}
bool EditWidgetIcons::addCustomIcon(const QImage& icon)
{
bool added = false;
if (m_db) {
// Don't add an icon larger than 128x128, but retain original size if smaller
auto scaledicon = icon;
if (icon.width() > 128 || icon.height() > 128) {
scaledicon = icon.scaled(128, 128);
}
QUuid uuid = m_db->metadata()->findCustomIcon(scaledicon);
if (uuid.isNull()) {
uuid = QUuid::createUuid();
m_db->metadata()->addCustomIcon(uuid, scaledicon);
m_customIconModel->setIcons(m_db->metadata()->customIconsPixmaps(IconSize::Default),
m_db->metadata()->customIconsOrder());
added = true;
}
// Select the new or existing icon
updateRadioButtonCustomIcons();
QModelIndex index = m_customIconModel->indexFromUuid(uuid);
m_ui->customIconsView->setCurrentIndex(index);
emit widgetUpdated();
}
return added;
}
void EditWidgetIcons::updateWidgetsDefaultIcons(bool check)
{
if (check) {
QModelIndex index = m_ui->defaultIconsView->currentIndex();
if (!index.isValid()) {
m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(0, 0));
} else {
m_ui->defaultIconsView->setCurrentIndex(index);
}
m_ui->customIconsView->selectionModel()->clearSelection();
}
}
void EditWidgetIcons::updateWidgetsCustomIcons(bool check)
{
if (check) {
QModelIndex index = m_ui->customIconsView->currentIndex();
if (!index.isValid()) {
m_ui->customIconsView->setCurrentIndex(m_customIconModel->index(0, 0));
} else {
m_ui->customIconsView->setCurrentIndex(index);
}
m_ui->defaultIconsView->selectionModel()->clearSelection();
}
}
void EditWidgetIcons::updateRadioButtonDefaultIcons()
{
m_ui->defaultIconsRadio->setChecked(true);
}
void EditWidgetIcons::updateRadioButtonCustomIcons()
{
m_ui->customIconsRadio->setChecked(true);
}
void EditWidgetIcons::confirmApplyIconTo(QAction* action)
{
m_applyIconTo = action->data().value<ApplyIconToOptions>();
m_ui->applyIconToPushButton->setText(action->text());
}