mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-03 04:45:19 -05:00
Merge branch 'develop' into feature/yubikey
This commit is contained in:
commit
37c7318097
90 changed files with 6467 additions and 1685 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -149,3 +149,4 @@ QVariant DatabaseWidgetStateSync::intListToVariant(const QList<int>& list)
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
480
src/gui/KMessageWidget.cpp
Normal 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
342
src/gui/KMessageWidget.h
Normal 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 */
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
36
src/gui/MessageWidget.cpp
Normal 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
36
src/gui/MessageWidget.h
Normal 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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ public:
|
|||
|
||||
Q_SIGNALS:
|
||||
void editFinished(bool accepted);
|
||||
void messageEditEntry(QString, MessageWidget::MessageType);
|
||||
void messageEditEntryDismiss();
|
||||
|
||||
private Q_SLOTS:
|
||||
void save();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue