mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-10 06:49:50 -05:00
4e8b00da34
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
353 lines
11 KiB
C++
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());
|
|
}
|