Merge branch 'develop' into feature/yubikey

This commit is contained in:
Janek Bevendorff
2017-02-15 00:24:28 +01:00
90 changed files with 6467 additions and 1685 deletions

View File

@@ -86,9 +86,11 @@ set(keepassx_SOURCES
gui/FileDialog.cpp
gui/IconModels.cpp
gui/KeePass1OpenWidget.cpp
gui/KMessageWidget.cpp
gui/LineEdit.cpp
gui/MainWindow.cpp
gui/MessageBox.cpp
gui/MessageWidget.cpp
gui/PasswordEdit.cpp
gui/PasswordGeneratorWidget.cpp
gui/PasswordComboBox.cpp
@@ -226,6 +228,7 @@ target_link_libraries(${PROGNAME}
Qt5::Widgets
Qt5::Network
${GCRYPT_LIBRARIES}
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES})
if(YUBIKEY_FOUND)
@@ -269,9 +272,25 @@ if(APPLE)
endif()
if(MINGW)
set(CPACK_GENERATOR "ZIP")
string(REPLACE "AMD" "Win" OUTPUT_FILE_POSTFIX "${CMAKE_HOST_SYSTEM_PROCESSOR}")
set(CPACK_GENERATOR "ZIP;NSIS")
set(CPACK_STRIP_FILES ON)
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION_NUM}")
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROGNAME})
set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION})
set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team")
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2")
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico")
set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}")
set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${PROGNAME}.exe")
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${CMAKE_SOURCE_DIR}/share/windows/installer-wizard.bmp")
set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP "${CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP}")
set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'")
set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'")
set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org")
set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}")
set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe")
include(CPack)
install(CODE "
@@ -279,5 +298,9 @@ if(MINGW)
" COMPONENT Runtime)
include(DeployQt4)
install_qt4_executable(${PROGNAME}.exe "qjpeg;qgif;qico;qtaccessiblewidgets")
install_qt4_executable(${PROGNAME}.exe)
add_custom_command(TARGET ${PROGNAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${Qt5Core_DIR}/../../../share/qt5/plugins/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
$<TARGET_FILE_DIR:${PROGNAME}>)
install(FILES $<TARGET_FILE_DIR:${PROGNAME}>/qwindows$<$<CONFIG:Debug>:d>.dll DESTINATION "platforms")
endif()

View File

@@ -396,6 +396,9 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
else if (tmplName.compare("enter",Qt::CaseInsensitive)==0) {
list.append(new AutoTypeKey(Qt::Key_Enter));
}
else if (tmplName.compare("space",Qt::CaseInsensitive)==0) {
list.append(new AutoTypeKey(Qt::Key_Space));
}
else if (tmplName.compare("up",Qt::CaseInsensitive)==0) {
list.append(new AutoTypeKey(Qt::Key_Up));
}

View File

@@ -98,7 +98,9 @@ QString AutoTypePlatformMac::activeWindowTitle()
if (windowLayer(window) == 0) {
// First toplevel window in list (front to back order)
title = windowTitle(window);
break;
if (!title.isEmpty()) {
break;
}
}
}

View File

@@ -435,6 +435,8 @@ KeySym AutoTypePlatformX11::keyToKeySym(Qt::Key key)
return XK_Tab;
case Qt::Key_Enter:
return XK_Return;
case Qt::Key_Space:
return XK_space;
case Qt::Key_Up:
return XK_Up;
case Qt::Key_Down:

View File

@@ -353,6 +353,12 @@ void Entry::setTitle(const QString& title)
void Entry::setUrl(const QString& url)
{
bool remove = url != m_attributes->value(EntryAttributes::URLKey) &&
(m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "1" ||
m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "0");
if (remove) {
m_attributes->remove(EntryAttributes::RememberCmdExecAttr);
}
m_attributes->set(EntryAttributes::URLKey, url, m_attributes->isProtected(EntryAttributes::URLKey));
}
@@ -508,7 +514,8 @@ Entry* Entry::clone(CloneFlags flags) const
entry->m_data.timeInfo.setLocationChanged(now);
}
if (flags & CloneRenameTitle)
entry->setTitle(entry->title() + tr(" - Clone"));
return entry;
}
@@ -639,12 +646,17 @@ QString Entry::resolvePlaceholder(const QString& str) const
const QList<QString> keyList = attributes()->keys();
for (const QString& key : keyList) {
Qt::CaseSensitivity cs = Qt::CaseInsensitive;
QString k = key;
if (!EntryAttributes::isDefaultAttribute(key)) {
cs = Qt::CaseSensitive;
k.prepend("{S:");
} else {
k.prepend("{");
}
QString k = key;
k.prepend("{").append("}");
k.append("}");
if (result.compare(k,cs)==0) {
result.replace(result,attributes()->value(key));
break;

View File

@@ -115,7 +115,8 @@ public:
CloneNoFlags = 0,
CloneNewUuid = 1, // generate a random uuid for the clone
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
CloneIncludeHistory = 4 // clone the history items
CloneIncludeHistory = 4, // clone the history items
CloneRenameTitle = 8 // add "-Clone" after the original title
};
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)

View File

@@ -24,6 +24,7 @@ const QString EntryAttributes::URLKey = "URL";
const QString EntryAttributes::NotesKey = "Notes";
const QStringList EntryAttributes::DefaultAttributes(QStringList() << TitleKey << UserNameKey
<< PasswordKey << URLKey << NotesKey);
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
EntryAttributes::EntryAttributes(QObject* parent)
: QObject(parent)

View File

@@ -52,6 +52,7 @@ public:
static const QString URLKey;
static const QString NotesKey;
static const QStringList DefaultAttributes;
static const QString RememberCmdExecAttr;
static bool isDefaultAttribute(const QString& key);
Q_SIGNALS:

View File

@@ -42,7 +42,11 @@ QList<Entry*> EntrySearcher::searchEntries(const QString& searchTerm, const Grou
const QList<Group*> children = group->children();
for (Group* childGroup : children) {
if (childGroup->searchingEnabled() != Group::Disable) {
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
if (matchGroup(searchTerm, childGroup, caseSensitivity)) {
searchResult.append(childGroup->entriesRecursive());
} else {
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
}
}
}
@@ -69,3 +73,21 @@ bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensiti
entry->url().contains(word, caseSensitivity) ||
entry->notes().contains(word, caseSensitivity);
}
bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
{
const QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
for (const QString& word : wordList) {
if (!wordMatch(word, group, caseSensitivity)) {
return false;
}
}
return true;
}
bool EntrySearcher::wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity)
{
return group->name().contains(word, caseSensitivity) ||
group->notes().contains(word, caseSensitivity);
}

View File

@@ -33,6 +33,8 @@ private:
QList<Entry*> searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
QList<Entry*> matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity);
bool wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity);
bool matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
bool wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity);
};
#endif // KEEPASSX_ENTRYSEARCHER_H

View File

@@ -211,6 +211,9 @@ FilePath::FilePath()
else if (testSetDir(appDirPath + "/share")) {
}
#endif
// Last ditch test when running in the build directory (mainly for travis tests)
else if (testSetDir(QString(KEEPASSX_SOURCE_DIR) + "/share")) {
}
if (m_dataPath.isEmpty()) {
qWarning("FilePath::DataPath: can't find data dir");

View File

@@ -83,3 +83,23 @@ QString SymmetricCipher::errorString() const
{
return m_backend->errorString();
}
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher)
{
if (cipher == KeePass2::CIPHER_AES) {
return SymmetricCipher::Aes256;
}
else {
return SymmetricCipher::Twofish;
}
}
Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
{
switch (algo) {
case SymmetricCipher::Aes256:
return KeePass2::CIPHER_AES;
default:
return KeePass2::CIPHER_TWOFISH;
}
}

View File

@@ -23,6 +23,7 @@
#include <QString>
#include "crypto/SymmetricCipherBackend.h"
#include "format/KeePass2.h"
class SymmetricCipher
{
@@ -71,6 +72,9 @@ public:
int blockSize() const;
QString errorString() const;
static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher);
static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo);
private:
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
SymmetricCipher::Direction direction);

View File

@@ -33,6 +33,7 @@ namespace KeePass2
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
const Uuid CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
const QByteArray INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A");

View File

@@ -124,7 +124,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256,
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()),
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
@@ -336,7 +336,7 @@ void KeePass2Reader::setCipher(const QByteArray& data)
else {
Uuid uuid(data);
if (uuid != KeePass2::CIPHER_AES) {
if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) {
raiseError("Unsupported cipher");
}
else {

View File

@@ -93,8 +93,8 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
CHECK_RETURN(writeData(header.data()));
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
SymmetricCipher::Encrypt);
SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()),
SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
cipherStream.init(finalKey, encryptionIV);
if (!cipherStream.open(QIODevice::WriteOnly)) {
raiseError(cipherStream.errorString());

View File

@@ -388,7 +388,7 @@ void KeePass2XmlReader::parseBinaries()
QString id = attr.value("ID").toString();
QByteArray data;
if (attr.value("Compressed").compare("True", Qt::CaseInsensitive) == 0) {
if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) {
data = readCompressedBinary();
}
else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 Pedro Alves <devel@pgalves.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MessageWidget.h"
MessageWidget::MessageWidget(QWidget* parent)
:KMessageWidget(parent)
{
}
void MessageWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
setMessageType(type);
setText(text);
animatedShow();
}
void MessageWidget::hideMessage()
{
animatedHide();
}

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

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 Pedro Alves <devel@pgalves.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MESSAGEWIDGET_H
#define MESSAGEWIDGET_H
#include "gui/KMessageWidget.h"
class MessageWidget : public KMessageWidget
{
Q_OBJECT
public:
explicit MessageWidget(QWidget* parent = 0);
public Q_SLOTS:
void showMessage(const QString& text, MessageWidget::MessageType type);
void hideMessage();
};
#endif // MESSAGEWIDGET_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ PasswordGenerator HttpSettings::m_generator;
bool HttpSettings::isEnabled()
{
return config()->get("Http/Enabled", true).toBool();
return config()->get("Http/Enabled", false).toBool();
}
void HttpSettings::setEnabled(bool enabled)
@@ -126,18 +126,6 @@ void HttpSettings::setSupportKphFields(bool supportKphFields)
config()->set("Http/SupportKphFields", supportKphFields);
}
QString HttpSettings::httpHost()
{
static const QString host = "localhost";
return config()->get("Http/Host", host).toString().toUtf8();
}
void HttpSettings::setHttpHost(QString host)
{
config()->set("Http/Host", host);
}
int HttpSettings::httpPort()
{
static const int PORT = 19455;

View File

@@ -42,8 +42,6 @@ public:
static void setSearchInAllDatabases(bool searchInAllDatabases);
static bool supportKphFields();
static void setSupportKphFields(bool supportKphFields);
static QString httpHost();
static void setHttpHost(QString host);
static int httpPort();
static void setHttpPort(int port);

View File

@@ -15,6 +15,8 @@
#include "ui_OptionDialog.h"
#include "HttpSettings.h"
#include <QMessageBox>
OptionDialog::OptionDialog(QWidget *parent) :
QWidget(parent),
ui(new Ui::OptionDialog())
@@ -41,7 +43,6 @@ void OptionDialog::loadSettings()
ui->sortByUsername->setChecked(true);
else
ui->sortByTitle->setChecked(true);
ui->httpHost->setText(settings.httpHost());
ui->httpPort->setText(QString::number(settings.httpPort()));
/*
@@ -70,8 +71,14 @@ void OptionDialog::saveSettings()
settings.setUnlockDatabase(ui->unlockDatabase->isChecked());
settings.setMatchUrlScheme(ui->matchUrlScheme->isChecked());
settings.setSortByUsername(ui->sortByUsername->isChecked());
settings.setHttpHost(ui->httpHost->text());
settings.setHttpPort(ui->httpPort->text().toInt());
int port = ui->httpPort->text().toInt();
if (port < 1024) {
QMessageBox::warning(this, tr("Cannot bind to privileged ports"),
tr("Cannot bind to privileged ports below 1024!\nUsing default port 19455."));
port = 19455;
}
settings.setHttpPort(port);
/*
settings.setPasswordUseLowercase(ui->checkBoxLower->isChecked());

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>605</width>
<height>389</height>
<height>429</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,7 +17,7 @@
<item>
<widget class="QCheckBox" name="enableHttpServer">
<property name="text">
<string>Enable KeepassXC Http protocol
<string>Enable KeepassXC HTTP protocol
This is required for accessing your databases from ChromeIPass or PassIFox</string>
</property>
</widget>
@@ -28,7 +28,7 @@ This is required for accessing your databases from ChromeIPass or PassIFox</stri
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
@@ -201,32 +201,41 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_1">
<item>
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>HTTP Host:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="httpHost">
<property name="placeholderText">
<string>Default host: localhost</string>
</property>
</widget>
</item>
</layout>
<spacer name="verticalSpacer_4">
<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>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QLineEdit" name="httpPort">
<property name="inputMask">
<string notr="true">d0000</string>
</property>
<property name="placeholderText">
<string>Default port: 19455</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>KeePassXC will listen to this port on 127.0.0.1</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -237,15 +246,8 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
<property name="text">
<string>HTTP Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="httpPort">
<property name="inputMask">
<string notr="true">d0000</string>
</property>
<property name="placeholderText">
<string>Default port: 19455</string>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>

View File

@@ -331,68 +331,34 @@ void Server::start(void)
if (m_started)
return;
bool nohost = true;
// local loopback hardcoded, since KeePassHTTP handshake
// is not safe against interception
QHostAddress address("127.0.0.1");
int port = HttpSettings::httpPort();
void* addrx = NULL;
unsigned int flags = MHD_USE_SELECT_INTERNALLY;
QHostInfo info = QHostInfo::fromName(HttpSettings::httpHost());
if (!info.addresses().isEmpty()) {
void* addrx = NULL;
unsigned int flags = MHD_USE_SELECT_INTERNALLY;
QHostAddress address = info.addresses().first();
struct sockaddr_in *addr = static_cast<struct sockaddr_in*>(calloc(1, sizeof(struct sockaddr_in)));
addrx = static_cast<void*>(addr);
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
addr->sin_addr.s_addr = htonl(address.toIPv4Address());
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
struct sockaddr_in *addr = static_cast<struct sockaddr_in*>(calloc(1, sizeof(struct sockaddr_in)));
addrx = static_cast<void*>(addr);
addr->sin_family = AF_INET;
addr->sin_port = htons(HttpSettings::httpPort());
addr->sin_addr.s_addr = htonl(address.toIPv4Address());
nohost = false;
} else {
struct sockaddr_in6 *addr = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
addrx = static_cast<void*>(addr);
addr->sin6_family = AF_INET6;
addr->sin6_port = htons(HttpSettings::httpPort());
memcpy(&addr->sin6_addr, address.toIPv6Address().c, 16);
nohost = false;
flags |= MHD_USE_IPv6;
}
if (nohost) {
qWarning("HTTPPlugin: Faled to get configured host!");
} else {
if (NULL == (daemon = MHD_start_daemon(flags, port, NULL, NULL,
&this->request_handler_wrapper, this,
MHD_OPTION_NOTIFY_COMPLETED,
this->request_completed, NULL,
MHD_OPTION_SOCK_ADDR,
addrx,
MHD_OPTION_END))) {
nohost = true;
qWarning("HTTPPlugin: Failed to bind to configured host!");
} else {
nohost = false;
//qWarning("HTTPPlugin: Binded to configured host.");
}
}
if (addrx != NULL)
free(addrx);
if (NULL == (daemon = MHD_start_daemon(flags, port, NULL, NULL,
&this->request_handler_wrapper, this,
MHD_OPTION_NOTIFY_COMPLETED,
this->request_completed, NULL,
MHD_OPTION_SOCK_ADDR,
addrx,
MHD_OPTION_END))) {
qWarning("HTTPPlugin: Failed to bind to localhost!");
} else {
m_started = true;
}
if (nohost) {
if (NULL == (daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port, NULL, NULL,
&this->request_handler_wrapper, this,
MHD_OPTION_NOTIFY_COMPLETED,
this->request_completed, NULL,
MHD_OPTION_END))) {
qWarning("HTTPPlugin: Fatal! Failed to bind to both configured and default hosts!");
} else {
qWarning("HTTPPlugin: Bound to fallback address 0.0.0.0/:::!");
}
}
m_started = true;
if (addrx != NULL)
free(addrx);
}

View File

@@ -480,7 +480,8 @@ void Service::updateEntry(const QString &, const QString &uuid, const QString &l
//ShowNotification(QString("%0: You have an entry change prompt waiting, click to activate").arg(requestId));
if ( HttpSettings::alwaysAllowUpdate()
|| QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host()).arg(u),
tr("Do you want to update the information in %1 - %2?")
.arg(QUrl(url).host().toHtmlEscaped()).arg(u.toHtmlEscaped()),
QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes ) {
entry->beginUpdate();
entry->setUsername(login);

View File

@@ -28,6 +28,16 @@
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
#ifdef QT_STATIC
#include <QtPlugin>
#ifdef Q_OS_WIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#elif Q_OS_LINUX
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#endif
#endif
int main(int argc, char** argv)
{
#ifdef QT_NO_DEBUG