Merge branch 'develop' into feature/yubikey

This commit is contained in:
Janek Bevendorff 2017-02-15 00:24:28 +01:00
commit 37c7318097
No known key found for this signature in database
GPG key ID: CFEC2F6850BFFA53
90 changed files with 6467 additions and 1685 deletions

View file

@ -17,12 +17,20 @@
*/
#include "Application.h"
#include "MainWindow.h"
#include <QAbstractNativeEventFilter>
#include <QFileOpenEvent>
#include <QSocketNotifier>
#include "autotype/AutoType.h"
#if defined(Q_OS_UNIX)
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
class XcbEventFilter : public QAbstractNativeEventFilter
{
@ -65,12 +73,18 @@ public:
Application::Application(int& argc, char** argv)
: QApplication(argc, argv)
, m_mainWindow(nullptr)
#ifdef Q_OS_UNIX
, m_unixSignalNotifier(nullptr)
#endif
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
installNativeEventFilter(new XcbEventFilter());
#elif defined(Q_OS_WIN)
installNativeEventFilter(new WinEventFilter());
#endif
#if defined(Q_OS_UNIX)
registerUnixSignals();
#endif
}
void Application::setMainWindow(QWidget* mainWindow)
@ -98,3 +112,57 @@ bool Application::event(QEvent* event)
return QApplication::event(event);
}
#if defined(Q_OS_UNIX)
int Application::unixSignalSocket[2];
void Application::registerUnixSignals()
{
int result = ::socketpair(AF_UNIX, SOCK_STREAM, 0, unixSignalSocket);
Q_ASSERT(0 == result);
if (0 != result) {
// do not register handles when socket creation failed, otherwise
// application will be unresponsive to signals such as SIGINT or SIGTERM
return;
}
QVector<int> const handledSignals = { SIGQUIT, SIGINT, SIGTERM, SIGHUP };
for (auto s: handledSignals) {
struct sigaction sigAction;
sigAction.sa_handler = handleUnixSignal;
sigemptyset(&sigAction.sa_mask);
sigAction.sa_flags = 0 | SA_RESTART;
sigaction(s, &sigAction, nullptr);
}
m_unixSignalNotifier = new QSocketNotifier(unixSignalSocket[1], QSocketNotifier::Read, this);
connect(m_unixSignalNotifier, SIGNAL(activated(int)), this, SLOT(quitBySignal()));
}
void Application::handleUnixSignal(int sig)
{
switch (sig) {
case SIGQUIT:
case SIGINT:
case SIGTERM:
{
char buf = 0;
::write(unixSignalSocket[0], &buf, sizeof(buf));
return;
}
case SIGHUP:
return;
}
}
void Application::quitBySignal()
{
m_unixSignalNotifier->setEnabled(false);
char buf;
::read(unixSignalSocket[1], &buf, sizeof(buf));
if (nullptr != m_mainWindow)
static_cast<MainWindow*>(m_mainWindow)->appExit();
}
#endif

View file

@ -21,6 +21,8 @@
#include <QApplication>
class QSocketNotifier;
class Application : public QApplication
{
Q_OBJECT
@ -34,8 +36,23 @@ public:
Q_SIGNALS:
void openFile(const QString& filename);
private Q_SLOTS:
#if defined(Q_OS_UNIX)
void quitBySignal();
#endif
private:
QWidget* m_mainWindow;
#if defined(Q_OS_UNIX)
/**
* Register Unix signals such as SIGINT and SIGTERM for clean shutdown.
*/
void registerUnixSignals();
QSocketNotifier* m_unixSignalNotifier;
static void handleUnixSignal(int sig);
static int unixSignalSocket[2];
#endif
};
#endif // KEEPASSX_APPLICATION_H

View file

@ -34,6 +34,8 @@ ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent)
{
m_ui->setupUi(this);
m_ui->messageWidget->setHidden(true);
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(generateKey()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
@ -60,7 +62,7 @@ void ChangeMasterKeyWidget::createKeyFile()
QString errorMsg;
bool created = FileKey::create(fileName, &errorMsg);
if (!created) {
MessageBox::warning(this, tr("Error"), tr("Unable to create Key File : ") + errorMsg);
m_ui->messageWidget->showMessage(tr("Unable to create Key File : ").append(errorMsg), MessageWidget::Error);
}
else {
m_ui->keyFileCombo->setEditText(fileName);
@ -125,7 +127,7 @@ void ChangeMasterKeyWidget::generateKey()
m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text()));
}
else {
MessageBox::warning(this, tr("Error"), tr("Different passwords supplied."));
m_ui->messageWidget->showMessage(tr("Different passwords supplied."), MessageWidget::Error);
m_ui->enterPasswordEdit->setText("");
m_ui->repeatPasswordEdit->setText("");
return;
@ -134,10 +136,10 @@ void ChangeMasterKeyWidget::generateKey()
if (m_ui->keyFileGroup->isChecked()) {
FileKey fileKey;
QString errorMsg;
if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) {
MessageBox::critical(this, tr("Failed to set key file"),
tr("Failed to set %1 as the Key file:\n%2")
.arg(m_ui->keyFileCombo->currentText(), errorMsg));
QString fileKeyName = m_ui->keyFileCombo->currentText();
if (!fileKey.load(fileKeyName, &errorMsg)) {
m_ui->messageWidget->showMessage(
tr("Failed to set %1 as the Key file:\n%2").arg(fileKeyName, errorMsg), MessageWidget::Error);
return;
}
m_key.addKey(fileKey);
@ -151,6 +153,7 @@ void ChangeMasterKeyWidget::generateKey()
m_key.addChallengeResponseKey(key);
}
m_ui->messageWidget->hideMessage();
Q_EMIT editFinished(true);
}

View file

@ -7,10 +7,13 @@
<x>0</x>
<y>0</y>
<width>438</width>
<height>256</height>
<height>342</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="MessageWidget" name="messageWidget" native="true"/>
</item>
<item>
<widget class="QLabel" name="headlineLabel"/>
</item>
@ -176,6 +179,12 @@
<extends>QLineEdit</extends>
<header>gui/PasswordEdit.h</header>
</customwidget>
<customwidget>
<class>MessageWidget</class>
<extends>QWidget</extends>
<header>gui/MessageWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>passwordGroup</tabstop>

View file

@ -40,6 +40,8 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
{
m_ui->setupUi(this);
m_ui->messageWidget->setHidden(true);
QFont font = m_ui->labelHeadline->font();
font.setBold(true);
font.setPointSize(font.pointSize() + 2);
@ -127,8 +129,8 @@ void DatabaseOpenWidget::openDatabase()
QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly)) {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(file.errorString()));
m_ui->messageWidget->showMessage(
tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error);
return;
}
if (m_db) {
@ -139,11 +141,14 @@ void DatabaseOpenWidget::openDatabase()
QApplication::restoreOverrideCursor();
if (m_db) {
if (m_ui->messageWidget->isVisible()) {
m_ui->messageWidget->animatedHide();
}
Q_EMIT editFinished(true);
}
else {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(reader.errorString()));
m_ui->messageWidget->showMessage(tr("Unable to open the database.")
.append("\n").append(reader.errorString()), MessageWidget::Error);
m_ui->editPassword->clear();
}
}
@ -163,7 +168,8 @@ CompositeKey DatabaseOpenWidget::databaseKey()
QString keyFilename = m_ui->comboKeyFile->currentText();
QString errorMsg;
if (!key.load(keyFilename, &errorMsg)) {
MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg));
m_ui->messageWidget->showMessage(tr("Can't open key file").append(":\n")
.append(errorMsg), MessageWidget::Error);
return CompositeKey();
}
masterKey.addKey(key);

View file

@ -10,10 +10,13 @@
<height>250</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,1,0,0,3">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0,1,0,0,3">
<property name="spacing">
<number>8</number>
</property>
<item>
<widget class="MessageWidget" name="messageWidget" native="true"/>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
@ -161,6 +164,12 @@
<extends>QLineEdit</extends>
<header>gui/PasswordEdit.h</header>
</customwidget>
<customwidget>
<class>MessageWidget</class>
<extends>QWidget</extends>
<header>gui/MessageWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>checkPassword</tabstop>

View file

@ -21,6 +21,8 @@
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/SymmetricCipher.h"
#include "format/KeePass2.h"
#include "keys/CompositeKey.h"
DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent)
@ -53,6 +55,7 @@ void DatabaseSettingsWidget::load(Database* db)
m_ui->dbDescriptionEdit->setText(meta->description());
m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
m_ui->defaultUsernameEdit->setText(meta->defaultUserName());
m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher()));
m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds());
if (meta->historyMaxItems() > -1) {
m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
@ -82,6 +85,8 @@ void DatabaseSettingsWidget::save()
meta->setName(m_ui->dbNameEdit->text());
meta->setDescription(m_ui->dbDescriptionEdit->text());
meta->setDefaultUserName(m_ui->defaultUsernameEdit->text());
m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast<SymmetricCipher::Algorithm>
(m_ui->AlgorithmComboBox->currentIndex())));
meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked());
if (static_cast<quint64>(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) {
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

View file

@ -49,35 +49,7 @@
<item row="2" column="1">
<widget class="QLineEdit" name="dbDescriptionEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="transformRoundsLabel">
<property name="text">
<string>Transform rounds:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="defaultUsernameLabel">
<property name="text">
<string>Default username:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="defaultUsernameEdit">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Use recycle bin:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="historyMaxSizeSpinBox">
@ -100,7 +72,7 @@
</item>
</layout>
</item>
<item row="6" column="1">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSpinBox" name="historyMaxItemsSpinBox">
@ -117,7 +89,7 @@
</item>
</layout>
</item>
<item row="3" column="1">
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSpinBox" name="transformRoundsSpinBox">
@ -144,23 +116,72 @@
</item>
</layout>
</item>
<item row="6" column="0">
<item row="8" column="0">
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
<property name="text">
<string>Max. history items:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="9" column="0">
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
<property name="text">
<string>Max. history size:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="5" column="0">
<widget class="QLabel" name="transformRoundsLabel">
<property name="text">
<string>Transform rounds:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="recycleBinEnabledCheckBox"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="defaultUsernameLabel">
<property name="text">
<string>Default username:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Use recycle bin:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="defaultUsernameEdit">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="AlgorithmLabel">
<property name="text">
<string>Algorithm:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="AlgorithmComboBox">
<item>
<property name="text">
<string>AES: 256 Bit (default)</string>
</property>
</item>
<item>
<property name="text">
<string>Twofish: 256 Bit</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>

View file

@ -116,7 +116,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
QFileInfo fileInfo(fileName);
QString canonicalFilePath = fileInfo.canonicalFilePath();
if (canonicalFilePath.isEmpty()) {
MessageBox::warning(this, tr("Warning"), tr("File not found!"));
Q_EMIT messageGlobal(tr("File not found!"), MessageWidget::Error);
return;
}
@ -136,8 +136,9 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
QFile file(fileName);
if (!file.open(QIODevice::ReadWrite)) {
if (!file.open(QIODevice::ReadOnly)) {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(file.errorString()));
// can't open
Q_EMIT messageGlobal(
tr("Unable to open the database.").append("\n").append(file.errorString()), MessageWidget::Error);
return;
}
else {
@ -184,6 +185,10 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
insertDatabase(db, dbStruct);
if (dbStruct.readOnly) {
Q_EMIT messageTab(tr("File opened in read only mode."), MessageWidget::Warning);
}
updateLastDatabases(dbStruct.filePath);
if (!pw.isNull() || !keyFile.isEmpty()) {
@ -192,6 +197,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
else {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath);
}
Q_EMIT messageDismissGlobal();
}
void DatabaseTabWidget::mergeDatabase()
@ -246,7 +252,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Close?"),
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName),
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName.toHtmlEscaped()),
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
if (result == QMessageBox::Cancel) {
return false;
@ -262,7 +268,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Save changes?"),
tr("\"%1\" was modified.\nSave changes?").arg(dbName),
tr("\"%1\" was modified.\nSave changes?").arg(dbName.toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
if (!saveDatabase(db)) {
@ -322,8 +328,8 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
// write the database to the file
m_writer.writeDatabase(&saveFile, db);
if (m_writer.hasError()) {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ m_writer.errorString());
Q_EMIT messageTab(tr("Writing the database failed.").append("\n")
.append(m_writer.errorString()), MessageWidget::Error);
return false;
}
@ -332,17 +338,18 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
dbStruct.modified = false;
dbStruct.dbWidget->databaseSaved();
updateTabName(db);
Q_EMIT messageDismissTab();
return true;
}
else {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ saveFile.errorString());
Q_EMIT messageTab(tr("Writing the database failed.").append("\n")
.append(saveFile.errorString()), MessageWidget::Error);
return false;
}
}
else {
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
+ saveFile.errorString());
Q_EMIT messageTab(tr("Writing the database failed.").append("\n")
.append(saveFile.errorString()), MessageWidget::Error);
return false;
}
}
@ -486,8 +493,9 @@ void DatabaseTabWidget::exportToCsv()
CsvExporter csvExporter;
if (!csvExporter.exportDatabase(fileName, db)) {
MessageBox::critical(this, tr("Error"), tr("Writing the CSV file failed.") + "\n\n"
+ csvExporter.errorString());
Q_EMIT messageGlobal(
tr("Writing the CSV file failed.").append("\n")
.append(csvExporter.errorString()), MessageWidget::Error);
}
}
@ -716,6 +724,7 @@ void DatabaseTabWidget::lockDatabases()
}
else if (result == QMessageBox::Discard) {
m_dbList[db].modified = false;
m_dbList[db].dbWidget->databaseSaved();
}
else if (result == QMessageBox::Cancel) {
continue;

View file

@ -23,12 +23,14 @@
#include "format/KeePass2Writer.h"
#include "gui/DatabaseWidget.h"
#include "gui/MessageWidget.h"
class DatabaseWidget;
class DatabaseWidgetStateSync;
class DatabaseOpenWidget;
class QFile;
class QLockFile;
class MessageWidget;
struct DatabaseManagerStruct
{
@ -84,6 +86,10 @@ Q_SIGNALS:
void activateDatabaseChanged(DatabaseWidget* dbWidget);
void databaseLocked(DatabaseWidget* dbWidget);
void databaseUnlocked(DatabaseWidget* dbWidget);
void messageGlobal(const QString&, MessageWidget::MessageType type);
void messageTab(const QString&, MessageWidget::MessageType type);
void messageDismissGlobal();
void messageDismissTab();
private Q_SLOTS:
void updateTabName(Database* db);

View file

@ -19,6 +19,7 @@
#include <QAction>
#include <QDesktopServices>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QFile>
@ -59,7 +60,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
, m_newParent(nullptr)
{
m_mainWidget = new QWidget(this);
QLayout* layout = new QHBoxLayout(m_mainWidget);
m_messageWidget = new MessageWidget(this);
m_messageWidget->setHidden(true);
QVBoxLayout* mainLayout = new QVBoxLayout();
QLayout* layout = new QHBoxLayout();
mainLayout->addWidget(m_messageWidget);
mainLayout->addLayout(layout);
m_splitter = new QSplitter(m_mainWidget);
m_splitter->setChildrenCollapsible(false);
@ -104,7 +112,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_splitter->setStretchFactor(1, 70);
layout->addWidget(m_splitter);
m_mainWidget->setLayout(layout);
m_mainWidget->setLayout(mainLayout);
m_editEntryWidget = new EditEntryWidget();
m_editEntryWidget->setObjectName("editEntryWidget");
@ -312,8 +320,10 @@ void DatabaseWidget::cloneEntry()
return;
}
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle);
entry->setGroup(currentEntry->group());
if (isInSearchMode())
search(m_lastSearchText);
m_entryView->setFocus();
m_entryView->setCurrentEntry(entry);
}
@ -341,7 +351,7 @@ void DatabaseWidget::deleteEntries()
result = MessageBox::question(
this, tr("Delete entry?"),
tr("Do you really want to delete the entry \"%1\" for good?")
.arg(selectedEntries.first()->title()),
.arg(selectedEntries.first()->title().toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No);
}
else {
@ -365,7 +375,7 @@ void DatabaseWidget::deleteEntries()
result = MessageBox::question(
this, tr("Move entry to recycle bin?"),
tr("Do you really want to move entry \"%1\" to the recycle bin?")
.arg(selectedEntries.first()->title()),
.arg(selectedEntries.first()->title().toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No);
}
else {
@ -494,8 +504,46 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
}
if (urlString.startsWith("cmd://")) {
// check if decision to execute command was stored
if (entry->attributes()->hasKey(EntryAttributes::RememberCmdExecAttr)) {
if (entry->attributes()->value(EntryAttributes::RememberCmdExecAttr) == "1") {
QProcess::startDetached(urlString.mid(6));
}
return;
}
// otherwise ask user
if (urlString.length() > 6) {
QProcess::startDetached(urlString.mid(6));
QString cmdTruncated = urlString.mid(6);
if (cmdTruncated.length() > 400)
cmdTruncated = cmdTruncated.left(400) + " […]";
QMessageBox msgbox(QMessageBox::Icon::Question,
tr("Execute command?"),
tr("Do you really want to execute the following command?<br><br>%1<br>")
.arg(cmdTruncated.toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No,
this
);
msgbox.setDefaultButton(QMessageBox::No);
QCheckBox* checkbox = new QCheckBox(tr("Remember my choice"), &msgbox);
msgbox.setCheckBox(checkbox);
bool remember = false;
QObject::connect(checkbox, &QCheckBox::stateChanged, [&](int state) {
if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked) {
remember = true;
}
});
int result = msgbox.exec();
if (result == QMessageBox::Yes) {
QProcess::startDetached(urlString.mid(6));
}
if (remember) {
entry->attributes()->set(EntryAttributes::RememberCmdExecAttr,
result == QMessageBox::Yes ? "1" : "0");
}
}
}
else {
@ -532,7 +580,7 @@ void DatabaseWidget::deleteGroup()
QMessageBox::StandardButton result = MessageBox::question(
this, tr("Delete group?"),
tr("Do you really want to delete the group \"%1\" for good?")
.arg(currentGroup->name()),
.arg(currentGroup->name().toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
delete currentGroup;
@ -648,7 +696,7 @@ void DatabaseWidget::updateMasterKey(bool accepted)
QApplication::restoreOverrideCursor();
if (!result) {
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
m_messageWidget->showMessage(tr("Unable to calculate master key"), MessageWidget::Error);
return;
}
}
@ -688,14 +736,14 @@ void DatabaseWidget::mergeDatabase(bool accepted)
{
if (accepted) {
if (!m_db) {
MessageBox::critical(this, tr("Error"), tr("No current database."));
m_messageWidget->showMessage(tr("No current database."), MessageWidget::Error);
return;
}
Database* srcDb = static_cast<DatabaseOpenWidget*>(sender())->database();
if (!srcDb) {
MessageBox::critical(this, tr("Error"), tr("No source database, nothing to do."));
m_messageWidget->showMessage(tr("No source database, nothing to do."), MessageWidget::Error);
return;
}
@ -722,15 +770,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
replaceDatabase(db);
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
for (Group* group : groups) {
if (group->uuid() == m_groupBeforeLock) {
m_groupView->setCurrentGroup(group);
break;
}
}
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
m_groupBeforeLock = Uuid();
m_entryBeforeLock = Uuid();
setCurrentWidget(m_mainWidget);
m_unlockDatabaseWidget->clearForms();
Q_EMIT unlockedDatabase();
@ -755,7 +798,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
void DatabaseWidget::switchToEntryEdit()
{
Entry* entry = m_entryView->currentEntry();
Q_ASSERT(entry);
if (!entry) {
return;
}
@ -766,7 +809,7 @@ void DatabaseWidget::switchToEntryEdit()
void DatabaseWidget::switchToGroupEdit()
{
Group* group = m_groupView->currentGroup();
Q_ASSERT(group);
if (!group) {
return;
}
@ -943,6 +986,10 @@ void DatabaseWidget::lock()
m_groupBeforeLock = m_db->rootGroup()->uuid();
}
if (m_entryView->currentEntry()) {
m_entryBeforeLock = m_entryView->currentEntry()->uuid();
}
clearAllWidgets();
m_unlockDatabaseWidget->load(m_filename);
setCurrentWidget(m_unlockDatabaseWidget);
@ -1028,18 +1075,33 @@ void DatabaseWidget::reloadDatabaseFile()
}
}
Uuid groupBeforeReload;
if (m_groupView && m_groupView->currentGroup()) {
groupBeforeReload = m_groupView->currentGroup()->uuid();
}
else {
groupBeforeReload = m_db->rootGroup()->uuid();
}
Uuid entryBeforeReload;
if (m_entryView && m_entryView->currentEntry()) {
entryBeforeReload = m_entryView->currentEntry()->uuid();
}
replaceDatabase(db);
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
}
else {
MessageBox::critical(this, tr("Autoreload Failed"),
tr("Could not parse or unlock the new database file while attempting"
" to autoreload this database."));
m_messageWidget->showMessage(
tr("Could not parse or unlock the new database file while attempting"
" to autoreload this database."), MessageWidget::Error);
}
}
else {
MessageBox::critical(this, tr("Autoreload Failed"),
tr("Could not open the new database file while attempting to autoreload"
" this database."));
m_messageWidget->showMessage(
tr("Could not open the new database file while attempting to autoreload this database."),
MessageWidget::Error);
}
// Rewatch the database file
@ -1061,6 +1123,35 @@ QStringList DatabaseWidget::customEntryAttributes() const
return entry->attributes()->customKeys();
}
/*
* Restores the focus on the group and entry that was focused
* before the database was locked or reloaded.
*/
void DatabaseWidget::restoreGroupEntryFocus(Uuid groupUuid, Uuid entryUuid)
{
Group* restoredGroup = nullptr;
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
for (Group* group : groups) {
if (group->uuid() == groupUuid) {
restoredGroup = group;
break;
}
}
if (restoredGroup != nullptr) {
m_groupView->setCurrentGroup(restoredGroup);
const QList<Entry*> entries = restoredGroup->entries();
for (Entry* entry : entries) {
if (entry->uuid() == entryUuid) {
m_entryView->setCurrentEntry(entry);
break;
}
}
}
}
bool DatabaseWidget::isGroupSelected() const
{
return m_groupView->currentGroup() != nullptr;
@ -1136,3 +1227,15 @@ void DatabaseWidget::closeUnlockDialog()
{
m_unlockDatabaseDialog->close();
}
void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
m_messageWidget->showMessage(text, type);
}
void DatabaseWidget::hideMessage()
{
if (m_messageWidget->isVisible()) {
m_messageWidget->animatedHide();
}
}

View file

@ -26,6 +26,7 @@
#include "core/Uuid.h"
#include "gui/entry/EntryModel.h"
#include "gui/MessageWidget.h"
class ChangeMasterKeyWidget;
class DatabaseOpenWidget;
@ -43,9 +44,14 @@ class QMenu;
class QSplitter;
class QLabel;
class UnlockDatabaseWidget;
class MessageWidget;
class UnlockDatabaseDialog;
class QFileSystemWatcher;
namespace Ui {
class SearchWidget;
}
class DatabaseWidget : public QStackedWidget
{
Q_OBJECT
@ -145,6 +151,8 @@ public Q_SLOTS:
void search(const QString& searchtext);
void setSearchCaseSensitive(bool state);
void endSearch();
void showMessage(const QString& text, MessageWidget::MessageType type);
void hideMessage();
private Q_SLOTS:
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
@ -163,6 +171,7 @@ private Q_SLOTS:
// Database autoreload slots
void onWatchedFileChanged();
void reloadDatabaseFile();
void restoreGroupEntryFocus(Uuid groupUuid, Uuid EntryUuid);
private:
void setClipboardTextAndMinimize(const QString& text);
@ -190,6 +199,8 @@ private:
Group* m_newParent;
QString m_filename;
Uuid m_groupBeforeLock;
Uuid m_entryBeforeLock;
MessageWidget* m_messageWidget;
// Search state
QString m_lastSearchText;

View file

@ -149,3 +149,4 @@ QVariant DatabaseWidgetStateSync::intListToVariant(const QList<int>& list)
return result;
}

View file

@ -25,6 +25,8 @@ EditWidget::EditWidget(QWidget* parent)
m_ui->setupUi(this);
setReadOnly(false);
m_ui->messageWidget->setHidden(true);
QFont headerLabelFont = m_ui->headerLabel->font();
headerLabelFont.setBold(true);
headerLabelFont.setPointSize(headerLabelFont.pointSize() + 2);
@ -86,3 +88,15 @@ bool EditWidget::readOnly() const
{
return m_readOnly;
}
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
m_ui->messageWidget->showMessage(text, type);
}
void EditWidget::hideMessage()
{
if (m_ui->messageWidget->isVisible()) {
m_ui->messageWidget->animatedHide();
}
}

View file

@ -21,6 +21,7 @@
#include <QScopedPointer>
#include "gui/DialogyWidget.h"
#include "gui/MessageWidget.h"
class QLabel;
@ -48,6 +49,10 @@ Q_SIGNALS:
void accepted();
void rejected();
protected Q_SLOTS:
void showMessage(const QString& text, MessageWidget::MessageType type);
void hideMessage();
private:
const QScopedPointer<Ui::EditWidget> m_ui;
bool m_readOnly;

View file

@ -11,6 +11,9 @@
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="MessageWidget" name="messageWidget" native="true"/>
</item>
<item>
<widget class="QLabel" name="headerLabel">
<property name="text">
@ -58,6 +61,12 @@
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MessageWidget</class>
<extends>QWidget</extends>
<header>gui/MessageWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>CategoryListWidget</class>
<extends>QListWidget</extends>

View file

@ -158,12 +158,11 @@ void EditWidgetIcons::fetchFavicon(QUrl url)
void EditWidgetIcons::fetchFaviconFromGoogle(QString domain)
{
if (m_fallbackToGoogle) {
if (m_fallbackToGoogle) {
abortFaviconDownload();
m_fallbackToGoogle = false;
fetchFavicon(QUrl("http://www.google.com/s2/favicons?domain=" + domain));
}
else {
} else {
abortFaviconDownload();
MessageBox::warning(this, tr("Error"), tr("Unable to fetch favicon."));
}
@ -190,6 +189,10 @@ void EditWidgetIcons::abortFaviconDownload(bool clearRedirect)
void EditWidgetIcons::onRequestFinished(QNetworkReply *reply)
{
if (m_database == nullptr) {
return;
}
if (!reply->error()) {
QImage image;
image.loadFromData(reply->readAll());
@ -244,7 +247,7 @@ void EditWidgetIcons::addCustomIcon()
m_ui->customIconsView->setCurrentIndex(index);
}
else {
MessageBox::critical(this, tr("Error"), tr("Can't read icon"));
Q_EMIT messageEditEntry(tr("Can't read icon"), MessageWidget::Error);
}
}
}
@ -298,9 +301,8 @@ void EditWidgetIcons::removeCustomIcon()
}
}
else {
MessageBox::information(this, tr("Can't delete icon!"),
tr("Can't delete icon. Still used by %1 items.")
.arg(iconUsedCount));
Q_EMIT messageEditEntry(
tr("Can't delete icon. Still used by %1 items.").arg(iconUsedCount), MessageWidget::Error);
}
}
}

View file

@ -26,6 +26,7 @@
#include "core/Global.h"
#include "core/Uuid.h"
#include "gui/MessageWidget.h"
class Database;
class DefaultIconModel;
@ -58,6 +59,10 @@ public:
public Q_SLOTS:
void setUrl(const QString &url);
Q_SIGNALS:
void messageEditEntry(QString, MessageWidget::MessageType);
void messageEditEntryDismiss();
private Q_SLOTS:
void downloadFavicon();
void fetchFavicon(QUrl url);

480
src/gui/KMessageWidget.cpp Normal file
View file

@ -0,0 +1,480 @@
/* This file is part of the KDE libraries
*
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "KMessageWidget.h"
#include <QAction>
#include <QEvent>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QShowEvent>
#include <QTimeLine>
#include <QToolButton>
#include <QStyle>
//---------------------------------------------------------------------
// KMessageWidgetPrivate
//---------------------------------------------------------------------
class KMessageWidgetPrivate
{
public:
void init(KMessageWidget *);
KMessageWidget *q;
QFrame *content;
QLabel *iconLabel;
QLabel *textLabel;
QToolButton *closeButton;
QTimeLine *timeLine;
QIcon icon;
KMessageWidget::MessageType messageType;
bool wordWrap;
QList<QToolButton *> buttons;
QPixmap contentSnapShot;
void createLayout();
void updateSnapShot();
void updateLayout();
void slotTimeLineChanged(qreal);
void slotTimeLineFinished();
int bestContentHeight() const;
};
void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
{
q = q_ptr;
q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
timeLine = new QTimeLine(500, q);
QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal)));
QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished()));
content = new QFrame(q);
content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
wordWrap = false;
iconLabel = new QLabel(content);
iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
iconLabel->hide();
textLabel = new QLabel(content);
textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
QObject::connect(textLabel, SIGNAL(linkActivated(QString)), q, SIGNAL(linkActivated(QString)));
QObject::connect(textLabel, SIGNAL(linkHovered(QString)), q, SIGNAL(linkHovered(QString)));
QAction *closeAction = new QAction(q);
closeAction->setText(KMessageWidget::tr("&Close"));
closeAction->setToolTip(KMessageWidget::tr("Close message"));
closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton));
QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide()));
closeButton = new QToolButton(content);
closeButton->setAutoRaise(true);
closeButton->setDefaultAction(closeAction);
q->setMessageType(KMessageWidget::Information);
}
void KMessageWidgetPrivate::createLayout()
{
delete content->layout();
content->resize(q->size());
qDeleteAll(buttons);
buttons.clear();
Q_FOREACH (QAction *action, q->actions()) {
QToolButton *button = new QToolButton(content);
button->setDefaultAction(action);
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
buttons.append(button);
}
// AutoRaise reduces visual clutter, but we don't want to turn it on if
// there are other buttons, otherwise the close button will look different
// from the others.
closeButton->setAutoRaise(buttons.isEmpty());
if (wordWrap) {
QGridLayout *layout = new QGridLayout(content);
// Set alignment to make sure icon does not move down if text wraps
layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
layout->addWidget(textLabel, 0, 1);
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addStretch();
Q_FOREACH (QToolButton *button, buttons) {
// For some reason, calling show() is necessary if wordwrap is true,
// otherwise the buttons do not show up. It is not needed if
// wordwrap is false.
button->show();
buttonLayout->addWidget(button);
}
buttonLayout->addWidget(closeButton);
layout->addItem(buttonLayout, 1, 0, 1, 2);
} else {
QHBoxLayout *layout = new QHBoxLayout(content);
layout->addWidget(iconLabel);
layout->addWidget(textLabel);
Q_FOREACH (QToolButton *button, buttons) {
layout->addWidget(button);
}
layout->addWidget(closeButton);
};
if (q->isVisible()) {
q->setFixedHeight(content->sizeHint().height());
}
q->updateGeometry();
}
void KMessageWidgetPrivate::updateLayout()
{
if (content->layout()) {
createLayout();
}
}
void KMessageWidgetPrivate::updateSnapShot()
{
// Attention: updateSnapShot calls QWidget::render(), which causes the whole
// window layouts to be activated. Calling this method from resizeEvent()
// can lead to infinite recursion, see:
// https://bugs.kde.org/show_bug.cgi?id=311336
contentSnapShot = QPixmap(content->size() * q->devicePixelRatio());
contentSnapShot.setDevicePixelRatio(q->devicePixelRatio());
contentSnapShot.fill(Qt::transparent);
content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren);
}
void KMessageWidgetPrivate::slotTimeLineChanged(qreal value)
{
q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height());
q->update();
}
void KMessageWidgetPrivate::slotTimeLineFinished()
{
if (timeLine->direction() == QTimeLine::Forward) {
// Show
// We set the whole geometry here, because it may be wrong if a
// KMessageWidget is shown right when the toplevel window is created.
content->setGeometry(0, 0, q->width(), bestContentHeight());
// notify about finished animation
emit q->showAnimationFinished();
} else {
// hide and notify about finished animation
q->hide();
emit q->hideAnimationFinished();
}
}
int KMessageWidgetPrivate::bestContentHeight() const
{
int height = content->heightForWidth(q->width());
if (height == -1) {
height = content->sizeHint().height();
}
return height;
}
//---------------------------------------------------------------------
// KMessageWidget
//---------------------------------------------------------------------
KMessageWidget::KMessageWidget(QWidget *parent)
: QFrame(parent)
, d(new KMessageWidgetPrivate)
{
d->init(this);
}
KMessageWidget::KMessageWidget(const QString &text, QWidget *parent)
: QFrame(parent)
, d(new KMessageWidgetPrivate)
{
d->init(this);
setText(text);
}
KMessageWidget::~KMessageWidget()
{
delete d;
}
QString KMessageWidget::text() const
{
return d->textLabel->text();
}
void KMessageWidget::setText(const QString &text)
{
d->textLabel->setText(text);
updateGeometry();
}
KMessageWidget::MessageType KMessageWidget::messageType() const
{
return d->messageType;
}
static QColor darkShade(QColor c)
{
qreal contrast = 0.7; // taken from kcolorscheme for the dark shade
qreal darkAmount;
if (c.lightnessF() < 0.006) { /* too dark */
darkAmount = 0.02 + 0.40 * contrast;
} else if (c.lightnessF() > 0.93) { /* too bright */
darkAmount = -0.06 - 0.60 * contrast;
} else {
darkAmount = (-c.lightnessF()) * (0.55 + contrast * 0.35);
}
qreal v = c.lightnessF() + darkAmount;
v = v > 0.0 ? (v < 1.0 ? v : 1.0) : 0.0;
c.setHsvF(c.hslHueF(), c.hslSaturationF(), v);
return c;
}
void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
{
d->messageType = type;
QColor bg0, bg1, bg2, border, fg;
switch (type) {
case Positive:
bg1.setRgb(0, 110, 40); // values taken from kcolorscheme.cpp (Positive)
break;
case Information:
bg1 = palette().highlight().color();
break;
case Warning:
bg1.setRgb(176, 128, 0); // values taken from kcolorscheme.cpp (Neutral)
break;
case Error:
bg1.setRgb(191, 3, 3); // values taken from kcolorscheme.cpp (Negative)
break;
}
// Colors
fg = palette().highlightedText().color();
bg0 = bg1.lighter(110);
bg2 = bg1.darker(110);
border = darkShade(bg1);
d->content->setStyleSheet(
QString(QLatin1String(".QFrame {"
"background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,"
" stop: 0 %1,"
" stop: 0.1 %2,"
" stop: 1.0 %3);"
"border-radius: 5px;"
"border: 1px solid %4;"
"margin: %5px;"
"}"
".QLabel { color: %6; }"
))
.arg(bg0.name())
.arg(bg1.name())
.arg(bg2.name())
.arg(border.name())
// DefaultFrameWidth returns the size of the external margin + border width. We know our border is 1px, so we subtract this from the frame normal QStyle FrameWidth to get our margin
.arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) - 1)
.arg(fg.name())
);
}
QSize KMessageWidget::sizeHint() const
{
ensurePolished();
return d->content->sizeHint();
}
QSize KMessageWidget::minimumSizeHint() const
{
ensurePolished();
return d->content->minimumSizeHint();
}
bool KMessageWidget::event(QEvent *event)
{
if (event->type() == QEvent::Polish && !d->content->layout()) {
d->createLayout();
}
return QFrame::event(event);
}
void KMessageWidget::resizeEvent(QResizeEvent *event)
{
QFrame::resizeEvent(event);
if (d->timeLine->state() == QTimeLine::NotRunning) {
d->content->resize(width(), d->bestContentHeight());
}
}
int KMessageWidget::heightForWidth(int width) const
{
ensurePolished();
return d->content->heightForWidth(width);
}
void KMessageWidget::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
if (d->timeLine->state() == QTimeLine::Running) {
QPainter painter(this);
painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue());
painter.drawPixmap(0, 0, d->contentSnapShot);
}
}
bool KMessageWidget::wordWrap() const
{
return d->wordWrap;
}
void KMessageWidget::setWordWrap(bool wordWrap)
{
d->wordWrap = wordWrap;
d->textLabel->setWordWrap(wordWrap);
QSizePolicy policy = sizePolicy();
policy.setHeightForWidth(wordWrap);
setSizePolicy(policy);
d->updateLayout();
// Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum
// height is set, causing the widget to be too high.
// Mostly visible in test programs.
if (wordWrap) {
setMinimumHeight(0);
}
}
bool KMessageWidget::isCloseButtonVisible() const
{
return d->closeButton->isVisible();
}
void KMessageWidget::setCloseButtonVisible(bool show)
{
d->closeButton->setVisible(show);
updateGeometry();
}
void KMessageWidget::addAction(QAction *action)
{
QFrame::addAction(action);
d->updateLayout();
}
void KMessageWidget::removeAction(QAction *action)
{
QFrame::removeAction(action);
d->updateLayout();
}
void KMessageWidget::animatedShow()
{
if (!style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) {
show();
emit showAnimationFinished();
return;
}
if (isVisible()) {
return;
}
QFrame::show();
setFixedHeight(0);
int wantedHeight = d->bestContentHeight();
d->content->setGeometry(0, -wantedHeight, width(), wantedHeight);
d->updateSnapShot();
d->timeLine->setDirection(QTimeLine::Forward);
if (d->timeLine->state() == QTimeLine::NotRunning) {
d->timeLine->start();
}
}
void KMessageWidget::animatedHide()
{
if (!style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) {
hide();
emit hideAnimationFinished();
return;
}
if (!isVisible()) {
hide();
return;
}
d->content->move(0, -d->content->height());
d->updateSnapShot();
d->timeLine->setDirection(QTimeLine::Backward);
if (d->timeLine->state() == QTimeLine::NotRunning) {
d->timeLine->start();
}
}
bool KMessageWidget::isHideAnimationRunning() const
{
return (d->timeLine->direction() == QTimeLine::Backward)
&& (d->timeLine->state() == QTimeLine::Running);
}
bool KMessageWidget::isShowAnimationRunning() const
{
return (d->timeLine->direction() == QTimeLine::Forward)
&& (d->timeLine->state() == QTimeLine::Running);
}
QIcon KMessageWidget::icon() const
{
return d->icon;
}
void KMessageWidget::setIcon(const QIcon &icon)
{
d->icon = icon;
if (d->icon.isNull()) {
d->iconLabel->hide();
} else {
const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
d->iconLabel->setPixmap(d->icon.pixmap(size));
d->iconLabel->show();
}
}
#include "moc_KMessageWidget.cpp"

342
src/gui/KMessageWidget.h Normal file
View file

@ -0,0 +1,342 @@
/* This file is part of the KDE libraries
*
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef KMESSAGEWIDGET_H
#define KMESSAGEWIDGET_H
#include <QFrame>
class KMessageWidgetPrivate;
/**
* @short A widget to provide feedback or propose opportunistic interactions.
*
* KMessageWidget can be used to provide inline positive or negative
* feedback, or to implement opportunistic interactions.
*
* As a feedback widget, KMessageWidget provides a less intrusive alternative
* to "OK Only" message boxes. If you want to avoid a modal KMessageBox,
* consider using KMessageWidget instead.
*
* Examples of KMessageWidget look as follows, all of them having an icon set
* with setIcon(), and the first three show a close button:
*
* \image html kmessagewidget.png "KMessageWidget with different message types"
*
* <b>Negative feedback</b>
*
* The KMessageWidget can be used as a secondary indicator of failure: the
* first indicator is usually the fact the action the user expected to happen
* did not happen.
*
* Example: User fills a form, clicks "Submit".
*
* @li Expected feedback: form closes
* @li First indicator of failure: form stays there
* @li Second indicator of failure: a KMessageWidget appears on top of the
* form, explaining the error condition
*
* When used to provide negative feedback, KMessageWidget should be placed
* close to its context. In the case of a form, it should appear on top of the
* form entries.
*
* KMessageWidget should get inserted in the existing layout. Space should not
* be reserved for it, otherwise it becomes "dead space", ignored by the user.
* KMessageWidget should also not appear as an overlay to prevent blocking
* access to elements the user needs to interact with to fix the failure.
*
* <b>Positive feedback</b>
*
* KMessageWidget can be used for positive feedback but it shouldn't be
* overused. It is often enough to provide feedback by simply showing the
* results of an action.
*
* Examples of acceptable uses:
*
* @li Confirm success of "critical" transactions
* @li Indicate completion of background tasks
*
* Example of unadapted uses:
*
* @li Indicate successful saving of a file
* @li Indicate a file has been successfully removed
*
* <b>Opportunistic interaction</b>
*
* Opportunistic interaction is the situation where the application suggests to
* the user an action he could be interested in perform, either based on an
* action the user just triggered or an event which the application noticed.
*
* Example of acceptable uses:
*
* @li A browser can propose remembering a recently entered password
* @li A music collection can propose ripping a CD which just got inserted
* @li A chat application may notify the user a "special friend" just connected
*
* @author Aurélien Gâteau <agateau@kde.org>
* @since 4.7
*/
class KMessageWidget : public QFrame
{
Q_OBJECT
Q_ENUMS(MessageType)
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap)
Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible)
Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType)
Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
public:
/**
* Available message types.
* The background colors are chosen depending on the message type.
*/
enum MessageType {
Positive,
Information,
Warning,
Error
};
/**
* Constructs a KMessageWidget with the specified @p parent.
*/
explicit KMessageWidget(QWidget *parent = 0);
/**
* Constructs a KMessageWidget with the specified @p parent and
* contents @p text.
*/
explicit KMessageWidget(const QString &text, QWidget *parent = 0);
/**
* Destructor.
*/
~KMessageWidget();
/**
* Get the text of this message widget.
* @see setText()
*/
QString text() const;
/**
* Check whether word wrap is enabled.
*
* If word wrap is enabled, the message widget wraps the displayed text
* as required to the available width of the widget. This is useful to
* avoid breaking widget layouts.
*
* @see setWordWrap()
*/
bool wordWrap() const;
/**
* Check whether the close button is visible.
*
* @see setCloseButtonVisible()
*/
bool isCloseButtonVisible() const;
/**
* Get the type of this message.
* By default, the type is set to KMessageWidget::Information.
*
* @see KMessageWidget::MessageType, setMessageType()
*/
MessageType messageType() const;
/**
* Add @p action to the message widget.
* For each action a button is added to the message widget in the
* order the actions were added.
*
* @param action the action to add
* @see removeAction(), QWidget::actions()
*/
void addAction(QAction *action);
/**
* Remove @p action from the message widget.
*
* @param action the action to remove
* @see KMessageWidget::MessageType, addAction(), setMessageType()
*/
void removeAction(QAction *action);
/**
* Returns the preferred size of the message widget.
*/
QSize sizeHint() const Q_DECL_OVERRIDE;
/**
* Returns the minimum size of the message widget.
*/
QSize minimumSizeHint() const Q_DECL_OVERRIDE;
/**
* Returns the required height for @p width.
* @param width the width in pixels
*/
int heightForWidth(int width) const Q_DECL_OVERRIDE;
/**
* The icon shown on the left of the text. By default, no icon is shown.
* @since 4.11
*/
QIcon icon() const;
/**
* Check whether the hide animation started by calling animatedHide()
* is still running. If animations are disabled, this function always
* returns @e false.
*
* @see animatedHide(), hideAnimationFinished()
* @since 5.0
*/
bool isHideAnimationRunning() const;
/**
* Check whether the show animation started by calling animatedShow()
* is still running. If animations are disabled, this function always
* returns @e false.
*
* @see animatedShow(), showAnimationFinished()
* @since 5.0
*/
bool isShowAnimationRunning() const;
public Q_SLOTS:
/**
* Set the text of the message widget to @p text.
* If the message widget is already visible, the text changes on the fly.
*
* @param text the text to display, rich text is allowed
* @see text()
*/
void setText(const QString &text);
/**
* Set word wrap to @p wordWrap. If word wrap is enabled, the text()
* of the message widget is wrapped to fit the available width.
* If word wrap is disabled, the message widget's minimum size is
* such that the entire text fits.
*
* @param wordWrap disable/enable word wrap
* @see wordWrap()
*/
void setWordWrap(bool wordWrap);
/**
* Set the visibility of the close button. If @p visible is @e true,
* a close button is shown that calls animatedHide() if clicked.
*
* @see closeButtonVisible(), animatedHide()
*/
void setCloseButtonVisible(bool visible);
/**
* Set the message type to @p type.
* By default, the message type is set to KMessageWidget::Information.
*
* @see messageType(), KMessageWidget::MessageType
*/
void setMessageType(KMessageWidget::MessageType type);
/**
* Show the widget using an animation.
*/
void animatedShow();
/**
* Hide the widget using an animation.
*/
void animatedHide();
/**
* Define an icon to be shown on the left of the text
* @since 4.11
*/
void setIcon(const QIcon &icon);
Q_SIGNALS:
/**
* This signal is emitted when the user clicks a link in the text label.
* The URL referred to by the href anchor is passed in contents.
* @param contents text of the href anchor
* @see QLabel::linkActivated()
* @since 4.10
*/
void linkActivated(const QString &contents);
/**
* This signal is emitted when the user hovers over a link in the text label.
* The URL referred to by the href anchor is passed in contents.
* @param contents text of the href anchor
* @see QLabel::linkHovered()
* @since 4.11
*/
void linkHovered(const QString &contents);
/**
* This signal is emitted when the hide animation is finished, started by
* calling animatedHide(). If animations are disabled, this signal is
* emitted immediately after the message widget got hidden.
*
* @note This signal is @e not emitted if the widget was hidden by
* calling hide(), so this signal is only useful in conjunction
* with animatedHide().
*
* @see animatedHide()
* @since 5.0
*/
void hideAnimationFinished();
/**
* This signal is emitted when the show animation is finished, started by
* calling animatedShow(). If animations are disabled, this signal is
* emitted immediately after the message widget got shown.
*
* @note This signal is @e not emitted if the widget was shown by
* calling show(), so this signal is only useful in conjunction
* with animatedShow().
*
* @see animatedShow()
* @since 5.0
*/
void showAnimationFinished();
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
bool event(QEvent *event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
private:
KMessageWidgetPrivate *const d;
friend class KMessageWidgetPrivate;
Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal))
Q_PRIVATE_SLOT(d, void slotTimeLineFinished())
};
#endif /* KMESSAGEWIDGET_H */

View file

@ -49,8 +49,8 @@ void KeePass1OpenWidget::openDatabase()
QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly)) {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(file.errorString()));
m_ui->messageWidget->showMessage( tr("Unable to open the database.").append("\n")
.append(file.errorString()), MessageWidget::Error);
return;
}
if (m_db) {
@ -65,8 +65,9 @@ void KeePass1OpenWidget::openDatabase()
Q_EMIT editFinished(true);
}
else {
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
.append(reader.errorString()));
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n")
.append(reader.errorString()), MessageWidget::Error);
m_ui->editPassword->clear();
}
}

View file

@ -104,6 +104,7 @@ MainWindow::MainWindow()
#endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
QAction* toggleViewAction = m_ui->toolBar->toggleViewAction();
toggleViewAction->setText(tr("Show toolbar"));
m_ui->menuView->addAction(toggleViewAction);
@ -137,13 +138,14 @@ MainWindow::MainWindow()
this, SLOT(lockDatabasesAfterInactivity()));
applySettingsChanges();
setShortcut(m_ui->actionDatabaseNew, QKeySequence::New, Qt::CTRL + Qt::Key_N);
setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O);
setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S);
setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs);
setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L);
setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N);
m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N);
m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E);
m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D);
m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K);
@ -275,8 +277,18 @@ MainWindow::MainWindow()
connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool)));
connect(m_ui->passwordGeneratorWidget, SIGNAL(dialogTerminated()), SLOT(closePasswordGen()));
connect(m_ui->welcomeWidget, SIGNAL(newDatabase()), SLOT(switchToNewDatabase()));
connect(m_ui->welcomeWidget, SIGNAL(openDatabase()), SLOT(switchToOpenDatabase()));
connect(m_ui->welcomeWidget, SIGNAL(openDatabaseFile(QString)), SLOT(switchToDatabaseFile(QString)));
connect(m_ui->welcomeWidget, SIGNAL(importKeePass1Database()), SLOT(switchToKeePass1Database()));
connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)), this, SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
connect(m_ui->tabWidget, SIGNAL(messageTab(QString,MessageWidget::MessageType)), this, SLOT(displayTabMessage(QString, MessageWidget::MessageType)));
connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage()));
updateTrayIcon();
}
@ -364,7 +376,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
bool groupSelected = dbWidget->isGroupSelected();
m_ui->actionEntryNew->setEnabled(!inSearch);
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
m_ui->actionEntryClone->setEnabled(singleEntrySelected);
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
m_ui->actionEntryDelete->setEnabled(entriesSelected);
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
@ -530,6 +542,30 @@ void MainWindow::closePasswordGen()
switchToPasswordGen(false);
}
void MainWindow::switchToNewDatabase()
{
m_ui->tabWidget->newDatabase();
switchToDatabases();
}
void MainWindow::switchToOpenDatabase()
{
m_ui->tabWidget->openDatabase();
switchToDatabases();
}
void MainWindow::switchToDatabaseFile(QString file)
{
m_ui->tabWidget->openDatabase(file);
switchToDatabases();
}
void MainWindow::switchToKeePass1Database()
{
m_ui->tabWidget->importKeePass1Database();
switchToDatabases();
}
void MainWindow::databaseStatusChanged(DatabaseWidget *)
{
updateTrayIcon();
@ -755,7 +791,7 @@ void MainWindow::repairDatabase()
if (fileName.isEmpty()) {
return;
}
QScopedPointer<QDialog> dialog(new QDialog(this));
DatabaseRepairWidget* dbRepairWidget = new DatabaseRepairWidget(dialog.data());
connect(dbRepairWidget, SIGNAL(success()), dialog.data(), SLOT(accept()));
@ -770,8 +806,9 @@ void MainWindow::repairDatabase()
KeePass2Writer writer;
writer.writeDatabase(saveFileName, dbRepairWidget->database());
if (writer.hasError()) {
QMessageBox::critical(this, tr("Error"),
tr("Writing the database failed.").append("\n\n").append(writer.errorString()));
displayGlobalMessage(
tr("Writing the database failed.").append("\n").append(writer.errorString()),
MessageWidget::Error);
}
}
}
@ -787,3 +824,26 @@ bool MainWindow::isTrayIconEnabled() const
&& QSystemTrayIcon::isSystemTrayAvailable();
#endif
}
void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type)
{
m_ui->globalMessageWidget->showMessage(text, type);
}
void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type)
{
m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type);
}
void MainWindow::hideGlobalMessage()
{
m_ui->globalMessageWidget->hideMessage();
}
void MainWindow::hideTabMessage()
{
if (m_ui->stackedWidget->currentIndex() == 0) {
m_ui->tabWidget->currentDatabaseWidget()->hideMessage();
}
}

View file

@ -42,6 +42,7 @@ public:
public Q_SLOTS:
void openDatabase(const QString& fileName, const QString& pw = QString(),
const QString& keyFile = QString());
void appExit();
protected:
void closeEvent(QCloseEvent* event) override;
@ -54,6 +55,10 @@ private Q_SLOTS:
void switchToDatabases();
void switchToSettings();
void switchToPasswordGen(bool enabled);
void switchToNewDatabase();
void switchToOpenDatabase();
void switchToDatabaseFile(QString file);
void switchToKeePass1Database();
void closePasswordGen();
void databaseStatusChanged(DatabaseWidget *dbWidget);
void databaseTabChanged(int tabIndex);
@ -68,9 +73,12 @@ private Q_SLOTS:
void applySettingsChanges();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
void toggleWindow();
void appExit();
void lockDatabasesAfterInactivity();
void repairDatabase();
void displayGlobalMessage(const QString& text, MessageWidget::MessageType type);
void displayTabMessage(const QString& text, MessageWidget::MessageType type);
void hideGlobalMessage();
void hideTabMessage();
private:
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);

View file

@ -2,6 +2,9 @@
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
@ -14,6 +17,9 @@
<string notr="true">KeePassXC</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
@ -27,8 +33,24 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="MessageWidget" name="globalMessageWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>2</number>
</property>
@ -83,7 +105,45 @@
<widget class="QWidget" name="pageWelcome">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="WelcomeWidget" name="welcomeWidget" native="true"/>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>50</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="WelcomeWidget" name="welcomeWidget" native="true">
<zorder>horizontalSpacer_2</zorder>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>50</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
@ -104,7 +164,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>26</height>
<height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -155,15 +215,15 @@
<addaction name="actionEntryCopyNotes"/>
<addaction name="separator"/>
</widget>
<addaction name="actionEntryNew"/>
<addaction name="actionEntryClone"/>
<addaction name="actionEntryEdit"/>
<addaction name="actionEntryDelete"/>
<addaction name="actionEntryCopyUsername"/>
<addaction name="actionEntryCopyPassword"/>
<addaction name="menuEntryCopyAttribute"/>
<addaction name="actionEntryAutoType"/>
<addaction name="actionEntryOpenUrl"/>
<addaction name="actionEntryEdit"/>
<addaction name="actionEntryClone"/>
<addaction name="actionEntryDelete"/>
<addaction name="actionEntryNew"/>
</widget>
<widget class="QMenu" name="menuGroups">
<property name="title">
@ -203,6 +263,7 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionDatabaseNew"/>
<addaction name="actionDatabaseOpen"/>
<addaction name="actionDatabaseSave"/>
<addaction name="separator"/>
@ -254,9 +315,9 @@
</property>
</action>
<action name="actionDatabaseMerge">
<property name="text">
<string>Merge from KeePassX database</string>
</property>
<property name="text">
<string>Merge from KeePassX database</string>
</property>
</action>
<action name="actionEntryNew">
<property name="enabled">
@ -452,6 +513,12 @@
</action>
</widget>
<customwidgets>
<customwidget>
<class>MessageWidget</class>
<extends>QWidget</extends>
<header>gui/MessageWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DatabaseTabWidget</class>
<extends>QTabWidget</extends>

36
src/gui/MessageWidget.cpp Normal file
View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 Pedro Alves <devel@pgalves.com>
*
* 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 "MessageWidget.h"
MessageWidget::MessageWidget(QWidget* parent)
:KMessageWidget(parent)
{
}
void MessageWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
setMessageType(type);
setText(text);
animatedShow();
}
void MessageWidget::hideMessage()
{
animatedHide();
}

36
src/gui/MessageWidget.h Normal file
View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 Pedro Alves <devel@pgalves.com>
*
* 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/>.
*/
#ifndef MESSAGEWIDGET_H
#define MESSAGEWIDGET_H
#include "gui/KMessageWidget.h"
class MessageWidget : public KMessageWidget
{
Q_OBJECT
public:
explicit MessageWidget(QWidget* parent = 0);
public Q_SLOTS:
void showMessage(const QString& text, MessageWidget::MessageType type);
void hideMessage();
};
#endif // MESSAGEWIDGET_H

View file

@ -45,11 +45,15 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator()));
// set font size of password quality and entropy labels dynamically to 80% of the default font size
// set font size of password quality and entropy labels dynamically to 80% of
// the default font size, but make it no smaller than 8pt
QFont defaultFont;
defaultFont.setPointSize(static_cast<int>(defaultFont.pointSize() * 0.8f));
m_ui->entropyLabel->setFont(defaultFont);
m_ui->strengthLabel->setFont(defaultFont);
int smallerSize = static_cast<int>(defaultFont.pointSize() * 0.8f);
if (smallerSize >= 8) {
defaultFont.setPointSize(smallerSize);
m_ui->entropyLabel->setFont(defaultFont);
m_ui->strengthLabel->setFont(defaultFont);
}
loadSettings();
reset();
@ -132,8 +136,10 @@ void PasswordGeneratorWidget::updatePasswordStrength(const QString& password)
void PasswordGeneratorWidget::generatePassword()
{
QString password = m_generator->generatePassword();
m_ui->editNewPassword->setText(password);
if (m_generator->isValid()) {
QString password = m_generator->generatePassword();
m_ui->editNewPassword->setText(password);
}
}
void PasswordGeneratorWidget::applyPassword()
@ -279,5 +285,11 @@ void PasswordGeneratorWidget::updateGenerator()
m_generator->setCharClasses(classes);
m_generator->setFlags(flags);
if (m_generator->isValid()) {
m_ui->buttonGenerate->setEnabled(true);
} else {
m_ui->buttonGenerate->setEnabled(false);
}
regeneratePassword();
}

View file

@ -104,11 +104,6 @@ QProgressBar::chunk {
<height>30</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>strength</string>
</property>
@ -144,11 +139,6 @@ QProgressBar::chunk {
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>entropy</string>
</property>
@ -295,6 +285,12 @@ QProgressBar::chunk {
</item>
<item>
<widget class="QToolButton" name="checkBoxNumbers">
<property name="minimumSize">
<size>
<width>0</width>
<height>26</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
@ -314,6 +310,12 @@ QProgressBar::chunk {
</item>
<item>
<widget class="QToolButton" name="checkBoxSpecialChars">
<property name="minimumSize">
<size>
<width>0</width>
<height>26</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>

View file

@ -34,12 +34,14 @@ SearchWidget::SearchWidget(QWidget *parent)
m_searchTimer->setSingleShot(true);
connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer()));
connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(startSearch()));
connect(m_ui->searchIcon, SIGNAL(triggered(QAction*)), m_ui->searchEdit, SLOT(setFocus()));
connect(m_ui->searchIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(setFocus()));
connect(m_ui->clearIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(clear()));
connect(m_ui->clearIcon, SIGNAL(pressed()), m_ui->searchEdit, SLOT(setFocus()));
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch()));
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
new QShortcut(Qt::CTRL + Qt::Key_F, m_ui->searchEdit, SLOT(setFocus()), nullptr, Qt::ApplicationShortcut);
new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut);
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut);
m_ui->searchEdit->installEventFilter(this);
@ -51,6 +53,9 @@ SearchWidget::SearchWidget(QWidget *parent)
m_ui->searchIcon->setIcon(filePath()->icon("actions", "system-search"));
m_ui->searchIcon->setMenu(searchMenu);
m_ui->searchIcon->setPopupMode(QToolButton::MenuButtonPopup);
m_ui->clearIcon->setIcon(filePath()->icon("actions", "edit-clear-locationbar-rtl"));
m_ui->clearIcon->setEnabled(false);
}
SearchWidget::~SearchWidget()
@ -67,23 +72,24 @@ bool SearchWidget::eventFilter(QObject *obj, QEvent *event)
return true;
}
else if (keyEvent->matches(QKeySequence::Copy)) {
// If Control+C is pressed in the search edit when no
// text is selected, copy the password of the current
// entry.
// If Control+C is pressed in the search edit when no text
// is selected, copy the password of the current entry
if (!m_ui->searchEdit->hasSelectedText()) {
emit copyPressed();
return true;
}
}
else if (keyEvent->matches(QKeySequence::MoveToNextLine)) {
// If Down is pressed at EOL in the search edit, move
// the focus to the entry view.
QLineEdit* searchEdit = m_ui->searchEdit;
if (!searchEdit->hasSelectedText() &&
searchEdit->cursorPosition() == searchEdit->text().length()) {
if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) {
// If down is pressed at EOL, move the focus to the entry view
emit downPressed();
return true;
}
else {
// Otherwise move the cursor to EOL
m_ui->searchEdit->setCursorPosition(m_ui->searchEdit->text().length());
return true;
}
}
}
@ -96,6 +102,7 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx)
mx.connect(this, SIGNAL(caseSensitiveChanged(bool)), SLOT(setSearchCaseSensitive(bool)));
mx.connect(this, SIGNAL(copyPressed()), SLOT(copyPassword()));
mx.connect(this, SIGNAL(downPressed()), SLOT(setFocus()));
mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit()));
}
void SearchWidget::databaseChanged(DatabaseWidget *dbWidget)
@ -125,6 +132,9 @@ void SearchWidget::startSearch()
m_searchTimer->stop();
}
bool hasText = m_ui->searchEdit->text().length() > 0;
m_ui->clearIcon->setEnabled(hasText);
search(m_ui->searchEdit->text());
}
@ -138,3 +148,9 @@ void SearchWidget::setCaseSensitive(bool state)
m_actionCaseSensitive->setChecked(state);
updateCaseSensitive();
}
void SearchWidget::searchFocus()
{
m_ui->searchEdit->setFocus();
m_ui->searchEdit->selectAll();
}

View file

@ -48,6 +48,7 @@ signals:
void escapePressed();
void copyPressed();
void downPressed();
void enterPressed();
public slots:
void databaseChanged(DatabaseWidget* dbWidget);
@ -56,6 +57,7 @@ private slots:
void startSearchTimer();
void startSearch();
void updateCaseSensitive();
void searchFocus();
private:
const QScopedPointer<Ui::SearchWidget> m_ui;

View file

@ -10,45 +10,69 @@
<height>34</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
<number>3</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
<number>7</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="searchIcon">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Find:</string>
</property>
</widget>
</item>
</layout>
<item>
<widget class="QToolButton" name="searchIcon">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string>Search</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="searchEdit"/>
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="styleSheet">
<string notr="true">padding:3px</string>
</property>
<property name="placeholderText">
<string>Find</string>
</property>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="clearIcon">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string>Clear</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>

View file

@ -18,13 +18,51 @@
#include "WelcomeWidget.h"
#include "ui_WelcomeWidget.h"
#include "config-keepassx.h"
#include "core/FilePath.h"
#include "core/Config.h"
WelcomeWidget::WelcomeWidget(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::WelcomeWidget())
{
m_ui->setupUi(this);
m_ui->welcomeLabel->setText(m_ui->welcomeLabel->text() + " " + KEEPASSX_VERSION);
QFont welcomeLabelFont = m_ui->welcomeLabel->font();
welcomeLabelFont.setBold(true);
welcomeLabelFont.setPointSize(welcomeLabelFont.pointSize() + 4);
m_ui->welcomeLabel->setFont(welcomeLabelFont);
m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(64));
m_ui->recentListWidget->clear();
const QStringList lastDatabases = config()->get("LastDatabases", QVariant()).toStringList();
for (const QString& database : lastDatabases) {
QListWidgetItem *itm = new QListWidgetItem;
itm->setText(database);
m_ui->recentListWidget->addItem(itm);
}
bool recent_visibility = (m_ui->recentListWidget->count() > 0);
m_ui->startLabel->setVisible(!recent_visibility);
m_ui->recentListWidget->setVisible(recent_visibility);
m_ui->recentLabel->setVisible(recent_visibility);
connect(m_ui->buttonNewDatabase, SIGNAL(clicked()), SIGNAL(newDatabase()));
connect(m_ui->buttonOpenDatabase, SIGNAL(clicked()), SIGNAL(openDatabase()));
connect(m_ui->buttonImportKeePass1, SIGNAL(clicked()), SIGNAL(importKeePass1Database()));
connect(m_ui->recentListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this,
SLOT(openDatabaseFromFile(QListWidgetItem*)));
}
WelcomeWidget::~WelcomeWidget()
{
}
void WelcomeWidget::openDatabaseFromFile(QListWidgetItem* item)
{
if (item->text().isEmpty()) {
return;
}
Q_EMIT openDatabaseFile(item->text());
}

View file

@ -19,6 +19,7 @@
#define KEEPASSX_WELCOMEWIDGET_H
#include <QWidget>
#include <QListWidgetItem>
namespace Ui {
class WelcomeWidget;
@ -32,6 +33,15 @@ public:
explicit WelcomeWidget(QWidget* parent = nullptr);
~WelcomeWidget();
Q_SIGNALS:
void newDatabase();
void openDatabase();
void openDatabaseFile(QString);
void importKeePass1Database();
private Q_SLOTS:
void openDatabaseFromFile(QListWidgetItem* item);
private:
const QScopedPointer<Ui::WelcomeWidget> m_ui;
};

View file

@ -2,17 +2,176 @@
<ui version="4.0">
<class>WelcomeWidget</class>
<widget class="QWidget" name="WelcomeWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>419</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>450</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelWelcome">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="iconLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="welcomeLabel">
<property name="text">
<string>Welcome!</string>
<string>Welcome to KeePassXC</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="startLabel">
<property name="text">
<string>Start storing your passwords securely in a KeePassXC database</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonNewDatabase">
<property name="text">
<string>Create new database</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonOpenDatabase">
<property name="text">
<string>Open existing database</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonImportKeePass1">
<property name="text">
<string>Import from KeePass 1</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="recentLabel">
<property name="text">
<string>Recent databases</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="recentListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>110</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View file

@ -77,6 +77,9 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
connect(this, SIGNAL(accepted()), SLOT(saveEntry()));
connect(this, SIGNAL(rejected()), SLOT(cancel()));
connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType)));
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
}
EditEntryWidget::~EditEntryWidget()
@ -89,6 +92,7 @@ void EditEntryWidget::setupMain()
add(tr("Entry"), m_mainWidget);
m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false));
connect(m_mainUi->togglePasswordButton, SIGNAL(toggled(bool)), m_mainUi->passwordEdit, SLOT(setShowPassword(bool)));
connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool)));
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
@ -271,14 +275,15 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
m_history = history;
if (history) {
setHeadline(QString("%1 > %2").arg(parentName, tr("Entry history")));
setHeadline(QString("%1 > %2").arg(parentName.toHtmlEscaped(), tr("Entry history")));
}
else {
if (create) {
setHeadline(QString("%1 > %2").arg(parentName, tr("Add entry")));
setHeadline(QString("%1 > %2").arg(parentName.toHtmlEscaped(), tr("Add entry")));
}
else {
setHeadline(QString("%1 > %2 > %3").arg(parentName, entry->title(), tr("Edit entry")));
setHeadline(QString("%1 > %2 > %3").arg(parentName.toHtmlEscaped(),
entry->title().toHtmlEscaped(), tr("Edit entry")));
}
}
@ -393,12 +398,13 @@ void EditEntryWidget::saveEntry()
{
if (m_history) {
clear();
hideMessage();
Q_EMIT editFinished(false);
return;
}
if (!passwordsEqual()) {
MessageBox::warning(this, tr("Error"), tr("Different passwords supplied."));
showMessage(tr("Different passwords supplied."), MessageWidget::Error);
return;
}
@ -433,6 +439,9 @@ void EditEntryWidget::saveEntry()
void EditEntryWidget::updateEntryData(Entry* entry) const
{
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
entry->attachments()->copyDataFrom(m_entryAttachments);
entry->setTitle(m_mainUi->titleEdit->text());
entry->setUsername(m_mainUi->usernameEdit->text());
entry->setUrl(m_mainUi->urlEdit->text());
@ -442,9 +451,6 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
entry->setNotes(m_mainUi->notesEdit->toPlainText());
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
entry->attachments()->copyDataFrom(m_entryAttachments);
IconStruct iconStruct = m_iconsWidget->state();
if (iconStruct.number < 0) {
@ -472,6 +478,7 @@ void EditEntryWidget::cancel()
{
if (m_history) {
clear();
hideMessage();
Q_EMIT editFinished(false);
return;
}
@ -495,6 +502,7 @@ void EditEntryWidget::clear()
m_autoTypeAssoc->clear();
m_historyModel->clear();
m_iconsWidget->reset();
hideMessage();
}
bool EditEntryWidget::hasBeenModified() const
@ -628,15 +636,13 @@ void EditEntryWidget::insertAttachment()
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
MessageBox::warning(this, tr("Error"),
tr("Unable to open file").append(":\n").append(file.errorString()));
showMessage(tr("Unable to open file").append(":\n").append(file.errorString()), MessageWidget::Error);
return;
}
QByteArray data;
if (!Tools::readAllFromDevice(&file, data)) {
MessageBox::warning(this, tr("Error"),
tr("Unable to open file").append(":\n").append(file.errorString()));
showMessage(tr("Unable to open file").append(":\n").append(file.errorString()), MessageWidget::Error);
return;
}
@ -663,13 +669,11 @@ void EditEntryWidget::saveCurrentAttachment()
QFile file(savePath);
if (!file.open(QIODevice::WriteOnly)) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file.errorString()));
showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error);
return;
}
if (file.write(attachmentData) != attachmentData.size()) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file.errorString()));
showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error);
return;
}
}
@ -690,20 +694,17 @@ void EditEntryWidget::openAttachment(const QModelIndex& index)
QTemporaryFile* file = new QTemporaryFile(tmpFileTemplate, this);
if (!file->open()) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file->errorString()));
showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error);
return;
}
if (file->write(attachmentData) != attachmentData.size()) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file->errorString()));
showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error);
return;
}
if (!file->flush()) {
MessageBox::warning(this, tr("Error"),
tr("Unable to save the attachment:\n").append(file->errorString()));
showMessage(tr("Unable to save the attachment:\n").append(file->errorString()), MessageWidget::Error);
return;
}

View file

@ -77,9 +77,6 @@
</item>
<item>
<widget class="QToolButton" name="togglePasswordGeneratorButton">
<property name="text">
<string>Generate</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>

View file

@ -43,6 +43,9 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
connect(this, SIGNAL(accepted()), SLOT(save()));
connect(this, SIGNAL(rejected()), SLOT(cancel()));
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType)));
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
}
EditGroupWidget::~EditGroupWidget()

View file

@ -45,6 +45,8 @@ public:
Q_SIGNALS:
void editFinished(bool accepted);
void messageEditEntry(QString, MessageWidget::MessageType);
void messageEditEntryDismiss();
private Q_SLOTS:
void save();