Merge branch 'release/2.3.0' into develop

This commit is contained in:
Janek Bevendorff 2018-02-21 09:41:20 +01:00
commit d0c583b5e2
20 changed files with 567 additions and 168 deletions

View File

@ -73,27 +73,49 @@ set(KEEPASSXC_VERSION_MINOR "3")
set(KEEPASSXC_VERSION_PATCH "0")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
set(KEEPASSXC_RELEASE_BUILD OFF CACHE BOOLEAN "Remove stability warnings")
set(KEEPASSXC_BUILD_TYPE "Snapshot" CACHE STRING "Set KeePassXC build type to distinguish between stable releases and snapshots")
set_property(CACHE KEEPASSXC_BUILD_TYPE PROPERTY STRINGS Snapshot Release PreRelease)
# Check if on a tag, if so build as a release
execute_process(COMMAND git tag --points-at HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_TAG)
if(GIT_TAG)
set(KEEPASSXC_RELEASE_BUILD ON)
elseif(NOT KEEPASSXC_RELEASE_BUILD)
string(REGEX REPLACE "\r?\n$" "" GIT_TAG "${GIT_TAG}")
if(GIT_TAG MATCHES "^[\\.0-9]+-(alpha|beta)[0-9]+$")
set(KEEPASSXC_BUILD_TYPE PreRelease)
set(KEEPASSXC_VERSION ${GIT_TAG})
elseif(GIT_TAG MATCHES "^[\\.0-9]+$")
set(KEEPASSXC_BUILD_TYPE Release)
set(KEEPASSXC_VERSION ${GIT_TAG})
endif()
endif()
if(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease" AND NOT GIT_TAG)
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview")
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "Snapshot")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot")
endif()
if(KEEPASSXC_BUILD_TYPE STREQUAL "Release")
set(KEEPASSXC_BUILD_TYPE_RELEASE ON)
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease")
set(KEEPASSXC_BUILD_TYPE_PRE_RELEASE ON)
else()
set(KEEPASSXC_BUILD_TYPE_SNAPSHOT ON)
endif()
message(STATUS "Setting up build for KeePassXC v${KEEPASSXC_VERSION}\n")
# Distribution info
set(KEEPASSXC_DIST True)
set(KEEPASSXC_DIST_TYPE "Other" CACHE STRING "KeePassXC Distribution type")
set(KEEPASSXC_DIST ON)
set(KEEPASSXC_DIST_TYPE "Other" CACHE STRING "KeePassXC Distribution Type")
set_property(CACHE KEEPASSXC_DIST_TYPE PROPERTY STRINGS Snap AppImage Other)
if(KEEPASSXC_DIST_TYPE STREQUAL "Snap")
set(KEEPASSXC_DIST_SNAP True)
set(KEEPASSXC_DIST_SNAP ON)
elseif(KEEPASSXC_DIST_TYPE STREQUAL "AppImage")
set(KEEPASSXC_DIST_APPIMAGE True)
set(KEEPASSXC_DIST_APPIMAGE ON)
elseif(KEEPASSXC_DIST_TYPE STREQUAL "Other")
unset(KEEPASSXC_DIST)
endif()

View File

@ -246,7 +246,7 @@ checkVersionInCMake() {
local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)"
local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)"
local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d.)"
local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d. | cut -f1 -d-)"
grep -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt
if [ $? -ne 0 ]; then
@ -582,19 +582,26 @@ build() {
done
init
checkWorkingTreeClean
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
if $BUILD_SNAPSHOT; then
if ${BUILD_SNAPSHOT}; then
TAG_NAME="HEAD"
local branch=`git rev-parse --abbrev-ref HEAD`
logInfo "Using current branch ${branch} to build..."
RELEASE_NAME="${RELEASE_NAME}-snapshot"
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot"
else
logInfo "Checking out release tag '${TAG_NAME}'..."
checkWorkingTreeClean
if $(echo "$TAG_NAME" | grep -qP "\-(alpha|beta)\\d+\$"); then
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=PreRelease"
logInfo "Checking out pre-release tag '${TAG_NAME}'..."
else
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
logInfo "Checking out release tag '${TAG_NAME}'..."
fi
git checkout "$TAG_NAME"
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_RELEASE_BUILD=ON"
fi
logInfo "Creating output directory..."
@ -604,20 +611,28 @@ build() {
exitError "Failed to create output directory!"
fi
if $BUILD_SOURCE_TARBALL; then
if ${BUILD_SOURCE_TARBALL}; then
logInfo "Creating source tarball..."
local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
TARBALL_NAME="${app_name_lower}-${RELEASE_NAME}-src.tar.xz"
git archive --format=tar "$TAG_NAME" --prefix="${app_name_lower}-${RELEASE_NAME}/" \
| xz -6 > "${OUTPUT_DIR}/${TARBALL_NAME}"
| xz -6 > "${OUTPUT_DIR}/${TARBALL_NAME}"
fi
if [ -e "${OUTPUT_DIR}/build-release" ]; then
logInfo "Cleaning existing build directory..."
rm -r "${OUTPUT_DIR}/build-release" 2> /dev/null
if [ $? -ne 0 ]; then
exitError "Failed to clean existing build directory, please do it manually."
fi
fi
logInfo "Creating build directory..."
mkdir -p "${OUTPUT_DIR}/build-release"
cd "${OUTPUT_DIR}/build-release"
logInfo "Configuring sources..."
for p in $BUILD_PLUGINS; do
for p in ${BUILD_PLUGINS}; do
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
done
@ -654,13 +669,13 @@ build() {
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" $CMAKE_OPTIONS "$SRC_DIR"
logInfo "Compiling and packaging sources..."
make $MAKE_OPTIONS preinstall
mingw32-make $MAKE_OPTIONS preinstall
# Call cpack directly instead of calling make package.
# This is important because we want to build the MSI when making a
# release.
cpack -G "NSIS;WIX;ZIP"
cpack -G "NSIS;ZIP;${CPACK_GENERATORS}"
mv "./${APP_NAME}-${RELEASE_NAME}-"*.{exe,msi,zip} ../
mv "./${APP_NAME}-${RELEASE_NAME}-"*.* ../
else
mkdir -p "${OUTPUT_DIR}/bin-release"

View File

@ -269,8 +269,8 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
}
/**
* Global Autotype entry-point funcion
* Perform global autotype on the active window
* Global Autotype entry-point function
* Perform global Auto-Type on the active window
*/
void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
{
@ -304,10 +304,19 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
if (matchList.isEmpty()) {
m_inAutoType.unlock();
QString message = tr("Couldn't find an entry that matches the window title:");
message.append("\n\n");
message.append(windowTitle);
MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message);
if (qobject_cast<QApplication*>(QCoreApplication::instance())) {
auto* msgBox = new QMessageBox();
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setWindowTitle(tr("Auto-Type - KeePassXC"));
msgBox->setText(tr("Couldn't find an entry that matches the window title:").append("\n\n")
.append(windowTitle));
msgBox->setIcon(QMessageBox::Information);
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->show();
msgBox->raise();
msgBox->activateWindow();
}
emit autotypeRejected();
} else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
@ -315,7 +324,7 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
m_inAutoType.unlock();
} else {
m_windowFromGlobal = m_plugin->activeWindow();
AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog();
auto* selectDialog = new AutoTypeSelectDialog();
connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)),
SLOT(performAutoTypeFromGlobal(AutoTypeMatch)));
connect(selectDialog, SIGNAL(rejected()), SLOT(resetInAutoType()));

View File

@ -3,14 +3,14 @@
#ifndef KEEPASSX_CONFIG_KEEPASSX_H
#define KEEPASSX_CONFIG_KEEPASSX_H
#define KEEPASSX_VERSION "${KEEPASSXC_VERSION}"
#define KEEPASSX_VERSION "@KEEPASSXC_VERSION@"
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
#define KEEPASSX_SOURCE_DIR "@CMAKE_SOURCE_DIR@"
#define KEEPASSX_BINARY_DIR "@CMAKE_BINARY_DIR@"
#define KEEPASSX_PREFIX_DIR "${CMAKE_INSTALL_PREFIX}"
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"
#define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}"
#define KEEPASSX_PREFIX_DIR "@CMAKE_INSTALL_PREFIX@"
#define KEEPASSX_PLUGIN_DIR "@PLUGIN_INSTALL_DIR@"
#define KEEPASSX_DATA_DIR "@DATA_INSTALL_DIR@"
#cmakedefine WITH_XC_AUTOTYPE
#cmakedefine WITH_XC_NETWORKING
@ -19,13 +19,16 @@
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
#cmakedefine KEEPASSXC_BUILD_TYPE_RELEASE
#cmakedefine KEEPASSXC_BUILD_TYPE_PRE_RELEASE
#cmakedefine KEEPASSXC_BUILD_TYPE_SNAPSHOT
#cmakedefine KEEPASSXC_DIST
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"
#cmakedefine KEEPASSXC_DIST_SNAP
#cmakedefine KEEPASSXC_DIST_APPIMAGE
#cmakedefine KEEPASSXC_RELEASE_BUILD
#cmakedefine HAVE_PR_SET_DUMPABLE 1
#cmakedefine HAVE_RLIMIT_CORE 1
#cmakedefine HAVE_PT_DENY_ATTACH 1

View File

@ -772,23 +772,41 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", qPrintable(uuid().toHex()));
return placeholder;
}
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
switch (typeOfPlaceholder) {
case PlaceholderType::NotPlaceholder:
case PlaceholderType::Unknown:
return placeholder;
case PlaceholderType::Title:
return title();
if (placeholderType(title()) == PlaceholderType::Title) {
return title();
}
return resolvePlaceholderRecursive(title(), maxDepth - 1);
case PlaceholderType::UserName:
return username();
if (placeholderType(username()) == PlaceholderType::UserName) {
return username();
}
return resolvePlaceholderRecursive(username(), maxDepth - 1);
case PlaceholderType::Password:
return password();
if (placeholderType(password()) == PlaceholderType::Password) {
return password();
}
return resolvePlaceholderRecursive(password(), maxDepth - 1);
case PlaceholderType::Notes:
return notes();
case PlaceholderType::Totp:
return totp();
if (placeholderType(notes()) == PlaceholderType::Notes) {
return notes();
}
return resolvePlaceholderRecursive(notes(), maxDepth - 1);
case PlaceholderType::Url:
return url();
if (placeholderType(url()) == PlaceholderType::Url) {
return url();
}
return resolvePlaceholderRecursive(url(), maxDepth - 1);
case PlaceholderType::UrlWithoutScheme:
case PlaceholderType::UrlScheme:
case PlaceholderType::UrlHost:
@ -802,6 +820,9 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
return resolveUrlPlaceholder(strUrl, typeOfPlaceholder);
}
case PlaceholderType::Totp:
// totp can't have placeholder inside
return totp();
case PlaceholderType::CustomAttribute: {
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
return attributes()->hasKey(key) ? attributes()->value(key) : QString();
@ -815,6 +836,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", qPrintable(uuid().toHex()));
return placeholder;
}
// resolving references in format: {REF:<WantedField>@<SearchIn>:<SearchText>}
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing
@ -828,6 +854,9 @@ QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder,
const QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
const EntryReferenceType searchInType = Entry::referenceType(searchIn);
Q_ASSERT(m_group);
Q_ASSERT(m_group->database());
const Entry* refEntry = m_group->database()->resolveEntry(searchText, searchInType);
if (refEntry) {

View File

@ -700,7 +700,8 @@ Entry* KdbxXmlReader::parseEntry(bool history)
entry->setIcon(uuid);
}
continue;
}if (m_xml.name() == "ForegroundColor") {
}
if (m_xml.name() == "ForegroundColor") {
entry->setForegroundColor(readColor());
continue;
}

View File

@ -54,8 +54,8 @@ AboutDialog::AboutDialog(QWidget* parent)
QString debugInfo = "KeePassXC - ";
debugInfo.append(tr("Version %1\n").arg(KEEPASSX_VERSION));
#ifndef KEEPASSXC_RELEASE_BUILD
debugInfo.append(tr("Build Type: Snapshot\n"));
#ifndef KEEPASSXC_BUILD_TYPE_RELEASE
debugInfo.append(tr("Build Type: %1\n").arg(KEEPASSXC_BUILD_TYPE));
#endif
if (!commitHash.isEmpty()) {
debugInfo.append(tr("Revision: %1").arg(commitHash.left(7)).append("\n"));

View File

@ -147,6 +147,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Project Maintainers:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
@ -156,15 +163,29 @@
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;p&gt;Project Maintainers:&lt;/p&gt;
&lt;ul&gt;
<string notr="true">&lt;ul&gt;
&lt;li&gt;droidmonkey&lt;/li&gt;
&lt;li&gt;phoerious&lt;/li&gt;
&lt;li&gt;TheZ3ro&lt;/li&gt;
&lt;li&gt;louib&lt;/li&gt;
&lt;li&gt;weslly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Special thanks from the KeePassXC team go to debfx for creating the original KeePassX.&lt;/&gt;</string>
&lt;/ul&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Special thanks from the KeePassXC team go to debfx for creating the original KeePassX.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
@ -198,8 +219,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>423</width>
<height>816</height>
<width>449</width>
<height>803</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">

View File

@ -776,21 +776,18 @@ void DatabaseWidget::switchToView(bool accepted)
m_newGroup->setParent(m_newParent);
m_groupView->setCurrentGroup(m_newGroup);
m_groupView->expandGroup(m_newParent);
}
else {
} else {
delete m_newGroup;
}
m_newGroup = nullptr;
m_newParent = nullptr;
}
else if (m_newEntry) {
} else if (m_newEntry) {
if (accepted) {
m_newEntry->setGroup(m_newParent);
m_entryView->setFocus();
m_entryView->setCurrentEntry(m_newEntry);
}
else {
} else {
delete m_newEntry;
}
@ -798,6 +795,10 @@ void DatabaseWidget::switchToView(bool accepted)
m_newParent = nullptr;
}
if (accepted) {
showMessage(tr("Entry updated successfully."), MessageWidget::Positive, false, 2000);
}
setCurrentWidget(m_mainWidget);
}
@ -819,7 +820,16 @@ void DatabaseWidget::switchToEntryEdit(Entry* entry)
void DatabaseWidget::switchToEntryEdit(Entry* entry, bool create)
{
Group* group = currentGroup();
// If creating an entry, it will be in `currentGroup()` so it's
// okay to use but when editing, the entry may not be in
// `currentGroup()` so we get the entry's group.
Group* group;
if (create) {
group = currentGroup();
} else {
group = entry->group();
}
Q_ASSERT(group);
m_editEntryWidget->loadEntry(entry, create, false, group->name(), m_db);

View File

@ -119,7 +119,8 @@ bool EditWidget::readOnly() const
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
m_ui->messageWidget->showMessage(text, type);
m_ui->messageWidget->setCloseButtonVisible(false);
m_ui->messageWidget->showMessage(text, type, 2000);
}
void EditWidget::hideMessage()

View File

@ -425,7 +425,7 @@ MainWindow::MainWindow()
}
#endif
#ifndef KEEPASSXC_RELEASE_BUILD
#ifndef KEEPASSXC_BUILD_TYPE_RELEASE
m_ui->globalMessageWidget->showMessage(tr("WARNING: You are using an unstable build of KeePassXC!\n"
"There is a high risk of corruption, maintain a backup of your databases.\n"
"This version is not meant for production use."),

View File

@ -151,6 +151,11 @@ void PasswordGeneratorWidget::setStandaloneMode(bool standalone)
}
}
QString PasswordGeneratorWidget::getGeneratedPassword()
{
return m_ui->editNewPassword->text();
}
void PasswordGeneratorWidget::keyPressEvent(QKeyEvent* e)
{
if (e->key() == Qt::Key_Escape && m_standalone == true) {

View File

@ -49,16 +49,18 @@ public:
void saveSettings();
void reset();
void setStandaloneMode(bool standalone);
public Q_SLOTS:
QString getGeneratedPassword();
public slots:
void regeneratePassword();
void applyPassword();
void copyPassword();
signals:
void appliedPassword(const QString& password);
void dialogTerminated();
private slots:
void applyPassword();
void copyPassword();
void updateButtonsEnabled(const QString& password);
void updatePasswordStrength(const QString& password);
void togglePasswordShown(bool hidden);

View File

@ -31,6 +31,7 @@
#include <QTemporaryFile>
#include <QMimeData>
#include <QEvent>
#include <QColorDialog>
#include "autotype/AutoType.h"
#include "core/Config.h"
@ -97,7 +98,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
connect(this, SIGNAL(accepted()), SLOT(acceptEntry()));
connect(this, SIGNAL(rejected()), SLOT(cancel()));
connect(this, SIGNAL(apply()), SLOT(saveEntry()));
connect(this, SIGNAL(apply()), SLOT(commitEntry()));
connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType)));
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
@ -127,7 +128,7 @@ void EditEntryWidget::setupMain()
QAction *action = new QAction(this);
action->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(action, SIGNAL(triggered()), this, SLOT(saveEntry()));
connect(action, SIGNAL(triggered()), this, SLOT(commitEntry()));
this->addAction(action);
m_mainUi->passwordGenerator->hide();
@ -156,6 +157,8 @@ void EditEntryWidget::setupAdvanced()
connect(m_advancedUi->attributesView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(updateCurrentAttribute()));
connect(m_advancedUi->fgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
connect(m_advancedUi->bgColorButton, SIGNAL(clicked()), SLOT(pickColor()));
}
void EditEntryWidget::setupIcon()
@ -188,6 +191,8 @@ void EditEntryWidget::setupAutoType()
connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(clearCurrentAssoc()));
connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)),
SLOT(applyCurrentAssoc()));
connect(m_autoTypeUi->customWindowSequenceButton, SIGNAL(toggled(bool)),
SLOT(applyCurrentAssoc()));
connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)),
SLOT(applyCurrentAssoc()));
}
@ -591,6 +596,8 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
editTriggers = QAbstractItemView::DoubleClicked;
}
m_advancedUi->attributesView->setEditTriggers(editTriggers);
setupColorButton(true, entry->foregroundColor());
setupColorButton(false, entry->backgroundColor());
m_iconsWidget->setEnabled(!m_history);
m_autoTypeUi->sequenceEdit->setReadOnly(m_history);
m_autoTypeUi->windowTitleCombo->lineEdit()->setReadOnly(m_history);
@ -676,20 +683,39 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
m_mainUi->titleEdit->setFocus();
}
void EditEntryWidget::saveEntry()
/**
* Commit the form values to in-memory database representation
*
* @return true is commit successful, otherwise false
*/
bool EditEntryWidget::commitEntry()
{
if (m_history) {
clear();
hideMessage();
emit editFinished(false);
return;
return true;
}
if (!passwordsEqual()) {
showMessage(tr("Different passwords supplied."), MessageWidget::Error);
return;
return false;
}
// Ask the user to apply the generator password, if open
if (m_mainUi->togglePasswordGeneratorButton->isChecked() &&
m_mainUi->passwordGenerator->getGeneratedPassword() != m_mainUi->passwordEdit->text()) {
auto answer = MessageBox::question(this, tr("Apply generated password?"),
tr("Do you want to apply the generated password to this entry?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (answer == QMessageBox::Yes) {
m_mainUi->passwordGenerator->applyPassword();
}
}
// Hide the password generator
m_mainUi->togglePasswordGeneratorButton->setChecked(false);
if (m_advancedUi->attributesView->currentIndex().isValid() && m_advancedUi->attributesEdit->isEnabled()) {
QString key = m_attributesModel->keyByIndex(m_advancedUi->attributesView->currentIndex());
m_entryAttributes->set(key, m_advancedUi->attributesEdit->toPlainText(),
@ -727,19 +753,18 @@ void EditEntryWidget::saveEntry()
updateSSHAgent();
}
#endif
showMessage(tr("Entry updated successfully."), MessageWidget::Positive);
return true;
}
void EditEntryWidget::acceptEntry()
{
// Check if passwords are mismatched first to prevent saving
if (!passwordsEqual()) {
showMessage(tr("Different passwords supplied."), MessageWidget::Error);
return;
if (commitEntry()) {
clear();
hideMessage();
emit editFinished(true);
}
saveEntry();
clear();
emit editFinished(true);
}
void EditEntryWidget::updateEntryData(Entry* entry) const
@ -756,26 +781,35 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
entry->setNotes(m_mainUi->notesEdit->toPlainText());
if (m_advancedUi->fgColorCheckBox->isChecked() &&
m_advancedUi->fgColorButton->property("color").isValid()) {
entry->setForegroundColor(QColor(m_advancedUi->fgColorButton->property("color").toString()));
} else {
entry->setForegroundColor(QColor());
}
if (m_advancedUi->bgColorCheckBox->isChecked() &&
m_advancedUi->bgColorButton->property("color").isValid()) {
entry->setBackgroundColor(QColor(m_advancedUi->bgColorButton->property("color").toString()));
} else {
entry->setBackgroundColor(QColor());
}
IconStruct iconStruct = m_iconsWidget->state();
if (iconStruct.number < 0) {
entry->setIcon(Entry::DefaultIconNumber);
}
else if (iconStruct.uuid.isNull()) {
} else if (iconStruct.uuid.isNull()) {
entry->setIcon(iconStruct.number);
}
else {
} else {
entry->setIcon(iconStruct.uuid);
}
entry->setAutoTypeEnabled(m_autoTypeUi->enableButton->isChecked());
if (m_autoTypeUi->inheritSequenceButton->isChecked()) {
entry->setDefaultAutoTypeSequence(QString());
}
else {
if (AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) {
entry->setDefaultAutoTypeSequence(m_autoTypeUi->sequenceEdit->text());
}
} else if (AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) {
entry->setDefaultAutoTypeSequence(m_autoTypeUi->sequenceEdit->text());
}
entry->autoTypeAssociations()->copyDataFrom(m_autoTypeAssoc);
@ -1120,3 +1154,38 @@ QMenu* EditEntryWidget::createPresetsMenu()
expirePresetsMenu->addAction(tr("1 year"))->setData(QVariant::fromValue(TimeDelta::fromYears(1)));
return expirePresetsMenu;
}
void EditEntryWidget::setupColorButton(bool foreground, const QColor& color)
{
QWidget* button = m_advancedUi->fgColorButton;
QCheckBox* checkBox = m_advancedUi->fgColorCheckBox;
if (!foreground) {
button = m_advancedUi->bgColorButton;
checkBox = m_advancedUi->bgColorCheckBox;
}
if (color.isValid()) {
button->setStyleSheet(QString("background-color:%1").arg(color.name()));
button->setProperty("color", color.name());
checkBox->setChecked(true);
} else {
button->setStyleSheet("");
button->setProperty("color", QVariant());
checkBox->setChecked(false);
}
}
void EditEntryWidget::pickColor()
{
bool isForeground = (sender() == m_advancedUi->fgColorButton);
QColor oldColor = QColor(m_advancedUi->fgColorButton->property("color").toString());
if (!isForeground) {
oldColor = QColor(m_advancedUi->bgColorButton->property("color").toString());
}
QColorDialog colorDialog(this);
QColor newColor = colorDialog.getColor(oldColor);
if (newColor.isValid()) {
setupColorButton(isForeground, newColor);
}
}

View File

@ -74,7 +74,7 @@ signals:
private slots:
void acceptEntry();
void saveEntry();
bool commitEntry();
void cancel();
void togglePasswordGeneratorButton(bool checked);
void setGeneratedPassword(const QString& password);
@ -99,6 +99,7 @@ private slots:
void updateHistoryButtons(const QModelIndex& current, const QModelIndex& previous);
void useExpiryPreset(QAction* action);
void toggleHideNotes(bool visible);
void pickColor();
#ifdef WITH_XC_SSHAGENT
void updateSSHAgent();
void updateSSHAgentAttachment();
@ -120,6 +121,7 @@ private:
#endif
void setupProperties();
void setupHistory();
void setupColorButton(bool foreground, const QColor& color);
bool passwordsEqual();
void setForms(const Entry* entry, bool restore = false);

View File

@ -2,19 +2,15 @@
<ui version="4.0">
<class>EditEntryWidgetAdvanced</class>
<widget class="QWidget" name="EditEntryWidgetAdvanced">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>532</width>
<height>364</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="attributesBox">
<property name="title">
@ -157,6 +153,96 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="colorsBox" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="fgColorCheckBox">
<property name="text">
<string>Foreground Color:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fgColorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>30</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="bgColorCheckBox">
<property name="text">
<string>Background Color:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bgColorButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<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>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -18,6 +18,7 @@
#include "EntryModel.h"
#include <QFont>
#include <QFontMetrics>
#include <QMimeData>
#include <QPalette>
#include <QDateTime>
@ -263,10 +264,26 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
font.setStrikeOut(true);
}
return font;
} else if (role == Qt::TextColorRole) {
} else if (role == Qt::ForegroundRole) {
if (entry->hasReferences()) {
QPalette p;
return QVariant(p.color(QPalette::Active, QPalette::Mid));
} else if (entry->foregroundColor().isValid()) {
return QVariant(entry->foregroundColor());
}
} else if (role == Qt::BackgroundRole) {
if (entry->backgroundColor().isValid()) {
return QVariant(entry->backgroundColor());
}
} else if (role == Qt::TextAlignmentRole) {
if (index.column() == Paperclip) {
return Qt::AlignCenter;
}
} else if (role == Qt::SizeHintRole) {
if (index.column() == Paperclip) {
QFont font;
QFontMetrics fm(font);
return fm.width(PaperClipDisplay) / 2;
}
}
@ -275,7 +292,9 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
Q_UNUSED(orientation);
if (role == Qt::DisplayRole) {
switch (section) {
case ParentGroup:
return tr("Group");
@ -302,6 +321,11 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
case Attachments:
return tr("Attachments");
}
} else if (role == Qt::TextAlignmentRole) {
switch (section) {
case Paperclip:
return Qt::AlignCenter;
}
}
return QVariant();

View File

@ -1,4 +1,5 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@ -15,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QScopedPointer>
#include "TestEntry.h"
#include "TestGlobal.h"
#include "crypto/Crypto.h"
@ -28,7 +31,7 @@ void TestEntry::initTestCase()
void TestEntry::testHistoryItemDeletion()
{
Entry* entry = new Entry();
QScopedPointer<Entry> entry(new Entry());
QPointer<Entry> historyEntry = new Entry();
entry->addHistoryItem(historyEntry);
@ -39,13 +42,11 @@ void TestEntry::testHistoryItemDeletion()
entry->removeHistoryItems(historyEntriesToRemove);
QCOMPARE(entry->historyItems().size(), 0);
QVERIFY(historyEntry.isNull());
delete entry;
}
void TestEntry::testCopyDataFrom()
{
Entry* entry = new Entry();
QScopedPointer<Entry> entry(new Entry());
entry->setTitle("testtitle");
entry->attributes()->set("attr1", "abc");
@ -62,9 +63,8 @@ void TestEntry::testCopyDataFrom()
assoc.sequence = "4";
entry->autoTypeAssociations()->add(assoc);
Entry* entry2 = new Entry();
entry2->copyDataFrom(entry);
delete entry;
QScopedPointer<Entry> entry2(new Entry());
entry2->copyDataFrom(entry.data());
QCOMPARE(entry2->title(), QString("testtitle"));
QCOMPARE(entry2->attributes()->value("attr1"), QString("abc"));
@ -77,13 +77,11 @@ void TestEntry::testCopyDataFrom()
QCOMPARE(entry2->autoTypeAssociations()->size(), 2);
QCOMPARE(entry2->autoTypeAssociations()->get(0).window, QString("1"));
QCOMPARE(entry2->autoTypeAssociations()->get(1).window, QString("3"));
delete entry2;
}
void TestEntry::testClone()
{
Entry* entryOrg = new Entry();
QScopedPointer<Entry> entryOrg(new Entry());
entryOrg->setUuid(Uuid::random());
entryOrg->setTitle("Original Title");
entryOrg->beginUpdate();
@ -96,42 +94,58 @@ void TestEntry::testClone()
entryOrgTime.setCreationTime(dateTime);
entryOrg->setTimeInfo(entryOrgTime);
Entry* entryCloneNone = entryOrg->clone(Entry::CloneNoFlags);
QScopedPointer<Entry> entryCloneNone(entryOrg->clone(Entry::CloneNoFlags));
QCOMPARE(entryCloneNone->uuid(), entryOrg->uuid());
QCOMPARE(entryCloneNone->title(), QString("New Title"));
QCOMPARE(entryCloneNone->historyItems().size(), 0);
QCOMPARE(entryCloneNone->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
delete entryCloneNone;
Entry* entryCloneNewUuid = entryOrg->clone(Entry::CloneNewUuid);
QScopedPointer<Entry> entryCloneNewUuid(entryOrg->clone(Entry::CloneNewUuid));
QVERIFY(entryCloneNewUuid->uuid() != entryOrg->uuid());
QVERIFY(!entryCloneNewUuid->uuid().isNull());
QCOMPARE(entryCloneNewUuid->title(), QString("New Title"));
QCOMPARE(entryCloneNewUuid->historyItems().size(), 0);
QCOMPARE(entryCloneNewUuid->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
delete entryCloneNewUuid;
Entry* entryCloneResetTime = entryOrg->clone(Entry::CloneResetTimeInfo);
QScopedPointer<Entry> entryCloneResetTime(entryOrg->clone(Entry::CloneResetTimeInfo));
QCOMPARE(entryCloneResetTime->uuid(), entryOrg->uuid());
QCOMPARE(entryCloneResetTime->title(), QString("New Title"));
QCOMPARE(entryCloneResetTime->historyItems().size(), 0);
QVERIFY(entryCloneResetTime->timeInfo().creationTime() != entryOrg->timeInfo().creationTime());
delete entryCloneResetTime;
Entry* entryCloneHistory = entryOrg->clone(Entry::CloneIncludeHistory);
QScopedPointer<Entry> entryCloneHistory(entryOrg->clone(Entry::CloneIncludeHistory));
QCOMPARE(entryCloneHistory->uuid(), entryOrg->uuid());
QCOMPARE(entryCloneHistory->title(), QString("New Title"));
QCOMPARE(entryCloneHistory->historyItems().size(), 1);
QCOMPARE(entryCloneHistory->historyItems().at(0)->title(), QString("Original Title"));
QCOMPARE(entryCloneHistory->timeInfo().creationTime(), entryOrg->timeInfo().creationTime());
delete entryCloneHistory;
delete entryOrg;
Database db;
auto* entryOrgClone = entryOrg->clone(Entry::CloneNoFlags);
entryOrgClone->setGroup(db.rootGroup());
Entry* entryCloneUserRef = entryOrgClone->clone(Entry::CloneUserAsRef);
entryCloneUserRef->setGroup(db.rootGroup());
QCOMPARE(entryCloneUserRef->uuid(), entryOrgClone->uuid());
QCOMPARE(entryCloneUserRef->title(), QString("New Title"));
QCOMPARE(entryCloneUserRef->historyItems().size(), 0);
QCOMPARE(entryCloneUserRef->timeInfo().creationTime(), entryOrgClone->timeInfo().creationTime());
QVERIFY(entryCloneUserRef->attributes()->isReference(EntryAttributes::UserNameKey));
QCOMPARE(entryCloneUserRef->resolvePlaceholder(entryCloneUserRef->username()), entryOrgClone->username());
Entry* entryClonePassRef = entryOrgClone->clone(Entry::ClonePassAsRef);
entryClonePassRef->setGroup(db.rootGroup());
QCOMPARE(entryClonePassRef->uuid(), entryOrgClone->uuid());
QCOMPARE(entryClonePassRef->title(), QString("New Title"));
QCOMPARE(entryClonePassRef->historyItems().size(), 0);
QCOMPARE(entryClonePassRef->timeInfo().creationTime(), entryOrgClone->timeInfo().creationTime());
QVERIFY(entryClonePassRef->attributes()->isReference(EntryAttributes::PasswordKey));
QCOMPARE(entryClonePassRef->resolvePlaceholder(entryCloneUserRef->password()), entryOrg->password());
}
void TestEntry::testResolveUrl()
{
Entry* entry = new Entry();
QScopedPointer<Entry> entry(new Entry());
QString testUrl("www.google.com");
QString testCmd("cmd://firefox " + testUrl);
QString testComplexCmd("cmd://firefox --start-now --url 'http://" + testUrl + "' --quit");
@ -152,8 +166,6 @@ void TestEntry::testResolveUrl()
QCOMPARE(entry->resolveUrl(nonHttpUrl), QString(""));
// Test no URL
QCOMPARE(entry->resolveUrl(noUrl), QString(""));
delete entry;
}
void TestEntry::testResolveUrlPlaceholders()
@ -189,9 +201,9 @@ void TestEntry::testResolveUrlPlaceholders()
void TestEntry::testResolveRecursivePlaceholders()
{
Database db;
Group* root = db.rootGroup();
auto* root = db.rootGroup();
Entry* entry1 = new Entry();
auto* entry1 = new Entry();
entry1->setGroup(root);
entry1->setUuid(Uuid::random());
entry1->setTitle("{USERNAME}");
@ -201,7 +213,7 @@ void TestEntry::testResolveRecursivePlaceholders()
entry1->attributes()->set("CustomTitle", "RecursiveValue");
QCOMPARE(entry1->resolveMultiplePlaceholders(entry1->title()), QString("RecursiveValue"));
Entry* entry2 = new Entry();
auto* entry2 = new Entry();
entry2->setGroup(root);
entry2->setUuid(Uuid::random());
entry2->setTitle("Entry2Title");
@ -213,7 +225,7 @@ void TestEntry::testResolveRecursivePlaceholders()
entry2->attributes()->set("Port", "1234");
entry2->attributes()->set("Uri", "uri/path");
Entry* entry3 = new Entry();
auto* entry3 = new Entry();
entry3->setGroup(root);
entry3->setUuid(Uuid::random());
entry3->setTitle(QString("{REF:T@I:%1}").arg(entry2->uuid().toHex()));
@ -226,7 +238,7 @@ void TestEntry::testResolveRecursivePlaceholders()
QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->password()), QString("RecursiveValue"));
QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->url()), QString("http://127.0.0.1:1234/uri/path"));
Entry* entry4 = new Entry();
auto* entry4 = new Entry();
entry4->setGroup(root);
entry4->setUuid(Uuid::random());
entry4->setTitle(QString("{REF:T@I:%1}").arg(entry3->uuid().toHex()));
@ -239,7 +251,7 @@ void TestEntry::testResolveRecursivePlaceholders()
QCOMPARE(entry4->resolveMultiplePlaceholders(entry4->password()), QString("RecursiveValue"));
QCOMPARE(entry4->resolveMultiplePlaceholders(entry4->url()), QString("http://127.0.0.1:1234/uri/path"));
Entry* entry5 = new Entry();
auto* entry5 = new Entry();
entry5->setGroup(root);
entry5->setUuid(Uuid::random());
entry5->attributes()->set("Scheme", "http");
@ -256,14 +268,25 @@ void TestEntry::testResolveRecursivePlaceholders()
const QString url("http://username:password@host.org:2017/some/path?q=e&t=s#fragment");
QCOMPARE(entry5->resolveMultiplePlaceholders(entry5->url()), url);
QCOMPARE(entry5->resolveMultiplePlaceholders(entry5->title()), QString("title+/some/path+fragment+title"));
auto* entry6 = new Entry();
entry6->setGroup(root);
entry6->setUuid(Uuid::random());
entry6->setTitle(QString("{REF:T@I:%1}").arg(entry3->uuid().toHex()));
entry6->setUsername(QString("{TITLE}"));
entry6->setPassword(QString("{PASSWORD}"));
QCOMPARE(entry6->resolvePlaceholder(entry6->title()), QString("Entry2Title"));
QCOMPARE(entry6->resolvePlaceholder(entry6->username()), QString("Entry2Title"));
QCOMPARE(entry6->resolvePlaceholder(entry6->password()), QString("{PASSWORD}"));
}
void TestEntry::testResolveReferencePlaceholders()
{
Database db;
Group* root = db.rootGroup();
auto* root = db.rootGroup();
Entry* entry1 = new Entry();
auto* entry1 = new Entry();
entry1->setGroup(root);
entry1->setUuid(Uuid::random());
entry1->setTitle("Title1");
@ -273,9 +296,9 @@ void TestEntry::testResolveReferencePlaceholders()
entry1->setNotes("Notes1");
entry1->attributes()->set("CustomAttribute1", "CustomAttributeValue1");
Group* group = new Group();
auto* group = new Group();
group->setParent(root);
Entry* entry2 = new Entry();
auto* entry2 = new Entry();
entry2->setGroup(group);
entry2->setUuid(Uuid::random());
entry2->setTitle("Title2");
@ -285,7 +308,7 @@ void TestEntry::testResolveReferencePlaceholders()
entry2->setNotes("Notes2");
entry2->attributes()->set("CustomAttribute2", "CustomAttributeValue2");
Entry* entry3 = new Entry();
auto* entry3 = new Entry();
entry3->setGroup(group);
entry3->setUuid(Uuid::random());
entry3->setTitle("{S:AttributeTitle}");
@ -299,7 +322,7 @@ void TestEntry::testResolveReferencePlaceholders()
entry3->attributes()->set("AttributeUrl", "UrlValue");
entry3->attributes()->set("AttributeNotes", "NotesValue");
Entry* tstEntry = new Entry();
auto* tstEntry = new Entry();
tstEntry->setGroup(root);
tstEntry->setUuid(Uuid::random());
@ -356,67 +379,67 @@ void TestEntry::testResolveNonIdPlaceholdersToUuid()
Database db;
auto* root = db.rootGroup();
Entry referencedEntryTitle;
referencedEntryTitle.setGroup(root);
referencedEntryTitle.setTitle("myTitle");
referencedEntryTitle.setUuid(Uuid::random());
auto* referencedEntryTitle = new Entry();
referencedEntryTitle->setGroup(root);
referencedEntryTitle->setTitle("myTitle");
referencedEntryTitle->setUuid(Uuid::random());
Entry referencedEntryUsername;
referencedEntryUsername.setGroup(root);
referencedEntryUsername.setUsername("myUser");
referencedEntryUsername.setUuid(Uuid::random());
auto* referencedEntryUsername = new Entry();
referencedEntryUsername->setGroup(root);
referencedEntryUsername->setUsername("myUser");
referencedEntryUsername->setUuid(Uuid::random());
Entry referencedEntryPassword;
referencedEntryPassword.setGroup(root);
referencedEntryPassword.setPassword("myPassword");
referencedEntryPassword.setUuid(Uuid::random());
auto* referencedEntryPassword = new Entry();
referencedEntryPassword->setGroup(root);
referencedEntryPassword->setPassword("myPassword");
referencedEntryPassword->setUuid(Uuid::random());
Entry referencedEntryUrl;
referencedEntryUrl.setGroup(root);
referencedEntryUrl.setUrl("myUrl");
referencedEntryUrl.setUuid(Uuid::random());
auto* referencedEntryUrl = new Entry();
referencedEntryUrl->setGroup(root);
referencedEntryUrl->setUrl("myUrl");
referencedEntryUrl->setUuid(Uuid::random());
Entry referencedEntryNotes;
referencedEntryNotes.setGroup(root);
referencedEntryNotes.setNotes("myNotes");
referencedEntryNotes.setUuid(Uuid::random());
auto* referencedEntryNotes = new Entry();
referencedEntryNotes->setGroup(root);
referencedEntryNotes->setNotes("myNotes");
referencedEntryNotes->setUuid(Uuid::random());
const QList<QChar> placeholders{'T', 'U', 'P', 'A', 'N'};
for (const QChar searchIn : placeholders) {
for (const QChar& searchIn : placeholders) {
const Entry* referencedEntry = nullptr;
QString newEntryNotesRaw("{REF:I@%1:%2}");
switch(searchIn.toLatin1()) {
case 'T':
referencedEntry = &referencedEntryTitle;
referencedEntry = referencedEntryTitle;
newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->title());
break;
case 'U':
referencedEntry = &referencedEntryUsername;
referencedEntry = referencedEntryUsername;
newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->username());
break;
case 'P':
referencedEntry = &referencedEntryPassword;
referencedEntry = referencedEntryPassword;
newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->password());
break;
case 'A':
referencedEntry = &referencedEntryUrl;
referencedEntry = referencedEntryUrl;
newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->url());
break;
case 'N':
referencedEntry = &referencedEntryNotes;
referencedEntry = referencedEntryNotes;
newEntryNotesRaw = newEntryNotesRaw.arg(searchIn, referencedEntry->notes());
break;
default:
break;
}
Entry newEntry;
newEntry.setGroup(root);
newEntry.setNotes(newEntryNotesRaw);
auto* newEntry = new Entry();
newEntry->setGroup(root);
newEntry->setNotes(newEntryNotesRaw);
const auto newEntryNotesResolved =
newEntry.resolveMultiplePlaceholders(newEntry.notes());
const QString newEntryNotesResolved =
newEntry->resolveMultiplePlaceholders(newEntry->notes());
QCOMPARE(newEntryNotesResolved, QString(referencedEntry->uuid().toHex()));
}
}
@ -424,9 +447,9 @@ void TestEntry::testResolveNonIdPlaceholdersToUuid()
void TestEntry::testResolveClonedEntry()
{
Database db;
Group* root = db.rootGroup();
auto* root = db.rootGroup();
Entry* original = new Entry();
auto* original = new Entry();
original->setGroup(root);
original->setUuid(Uuid::random());
original->setTitle("Title");

View File

@ -26,6 +26,7 @@
#include <QLabel>
#include <QMimeData>
#include <QPushButton>
#include <QCheckBox>
#include <QSpinBox>
#include <QPlainTextEdit>
#include <QComboBox>
@ -61,6 +62,7 @@
#include "gui/entry/EntryView.h"
#include "gui/group/GroupModel.h"
#include "gui/group/GroupView.h"
#include "gui/group/EditGroupWidget.h"
#include "keys/PasswordKey.h"
void TestGui::initTestCase()
@ -278,6 +280,7 @@ void TestGui::testTabs()
void TestGui::testEditEntry()
{
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
int editCount = 0;
// Select the first entry in the database
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
@ -304,7 +307,24 @@ void TestGui::testEditEntry()
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
QCOMPARE(entry->title(), QString("Sample Entry_test"));
QCOMPARE(entry->historyItems().size(), 1);
QCOMPARE(entry->historyItems().size(), ++editCount);
// Test entry colors (simulate choosing a color)
editEntryWidget->setCurrentPage(1);
auto fgColor = QColor(Qt::red);
auto bgColor = QColor(Qt::blue);
// Set foreground color
auto colorButton = editEntryWidget->findChild<QPushButton*>("fgColorButton");
auto colorCheckBox = editEntryWidget->findChild<QCheckBox*>("fgColorCheckBox");
colorButton->setProperty("color", fgColor);
colorCheckBox->setChecked(true);
// Set background color
colorButton = editEntryWidget->findChild<QPushButton*>("bgColorButton");
colorCheckBox = editEntryWidget->findChild<QCheckBox*>("bgColorCheckBox");
colorButton->setProperty("color", bgColor);
colorCheckBox->setChecked(true);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton);
QCOMPARE(entry->historyItems().size(), ++editCount);
// Test protected attributes
editEntryWidget->setCurrentPage(1);
@ -336,12 +356,68 @@ void TestGui::testEditEntry()
// Confirm edit was made
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
QCOMPARE(entry->title(), QString("Sample Entry_test"));
QCOMPARE(entry->historyItems().size(), 2);
QCOMPARE(entry->foregroundColor(), fgColor);
QCOMPARE(entryItem.data(Qt::ForegroundRole), QVariant(fgColor));
QCOMPARE(entry->backgroundColor(), bgColor);
QCOMPARE(entryItem.data(Qt::BackgroundRole), QVariant(bgColor));
QCOMPARE(entry->historyItems().size(), ++editCount);
// Confirm modified indicator is showing
QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_dbFileName));
}
void TestGui::testSearchEditEntry()
{
// Regression test for Issue #1447 -- Uses example from issue description
// Find buttons for group creation
EditGroupWidget* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
QLineEdit* nameEdit = editGroupWidget->findChild<QLineEdit*>("nameEdit");
QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");
// Add groups "Good" and "Bad"
m_dbWidget->createGroup();
QTest::keyClicks(nameEdit, "Good");
QTest::mouseClick(editGroupWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup()); // Makes "Good" and "Bad" on the same level
m_dbWidget->createGroup();
QTest::keyClicks(nameEdit, "Bad");
QTest::mouseClick(editGroupWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup());
// Find buttons for entry creation
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
// Create "Doggy" in "Good"
Group* goodGroup = m_dbWidget->currentGroup()->findChildByName(QString("Good"));
m_dbWidget->groupView()->setCurrentGroup(goodGroup);
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QTest::keyClicks(titleEdit, "Doggy");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
// Select "Bad" group in groupView
Group* badGroup = m_db->rootGroup()->findChildByName(QString("Bad"));
m_dbWidget->groupView()->setCurrentGroup(badGroup);
// Search for "Doggy" entry
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
QTest::keyClicks(searchTextEdit, "Doggy");
QTRY_VERIFY(m_dbWidget->isInSearchMode());
// Goto "Doggy"'s edit view
QTest::keyClick(searchTextEdit, Qt::Key_Return);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
// Check the path in header is "parent-group > entry"
QCOMPARE(m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget")->findChild<QLabel*>("headerLabel")->text(),
QString("Good > Doggy > Edit entry"));
}
void TestGui::testAddEntry()
{
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");

View File

@ -46,6 +46,7 @@ private slots:
void testAutoreloadDatabase();
void testTabs();
void testEditEntry();
void testSearchEditEntry();
void testAddEntry();
void testPasswordEntryEntropy();
void testDicewareEntryEntropy();