mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge pull request #1305 from fonic/improved_entry_view
Improve and extend entry view table
This commit is contained in:
commit
0a876c8af9
@ -138,6 +138,8 @@ void Config::init(const QString& fileName)
|
||||
m_defaults.insert("GUI/DarkTrayIcon", false);
|
||||
m_defaults.insert("GUI/MinimizeToTray", false);
|
||||
m_defaults.insert("GUI/MinimizeOnClose", false);
|
||||
m_defaults.insert("GUI/HideUsernames", false);
|
||||
m_defaults.insert("GUI/HidePasswords", true);
|
||||
}
|
||||
|
||||
Config* Config::instance()
|
||||
|
@ -181,7 +181,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
|
||||
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
|
||||
connect(m_detailSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(detailSplitterSizesChanged()));
|
||||
connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged()));
|
||||
connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
|
||||
connect(m_entryView, SIGNAL(entryActivated(Entry*, EntryModel::ModelColumn)),
|
||||
@ -290,28 +290,52 @@ void DatabaseWidget::setDetailSplitterSizes(const QList<int> &sizes)
|
||||
m_detailSplitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
QList<int> DatabaseWidget::entryHeaderViewSizes() const
|
||||
/**
|
||||
* Get current state of entry view 'Hide Usernames' setting
|
||||
*/
|
||||
bool DatabaseWidget::isUsernamesHidden() const
|
||||
{
|
||||
QList<int> sizes;
|
||||
|
||||
for (int i = 0; i < m_entryView->header()->count(); i++) {
|
||||
sizes.append(m_entryView->header()->sectionSize(i));
|
||||
}
|
||||
|
||||
return sizes;
|
||||
return m_entryView->isUsernamesHidden();
|
||||
}
|
||||
|
||||
void DatabaseWidget::setEntryViewHeaderSizes(const QList<int>& sizes)
|
||||
/**
|
||||
* Set state of entry view 'Hide Usernames' setting
|
||||
*/
|
||||
void DatabaseWidget::setUsernamesHidden(const bool hide)
|
||||
{
|
||||
const bool enoughSizes = sizes.size() == m_entryView->header()->count();
|
||||
Q_ASSERT(enoughSizes);
|
||||
if (!enoughSizes) {
|
||||
return;
|
||||
}
|
||||
m_entryView->setUsernamesHidden(hide);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizes.size(); i++) {
|
||||
m_entryView->header()->resizeSection(i, sizes[i]);
|
||||
}
|
||||
/**
|
||||
* Get current state of entry view 'Hide Passwords' setting
|
||||
*/
|
||||
bool DatabaseWidget::isPasswordsHidden() const
|
||||
{
|
||||
return m_entryView->isPasswordsHidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state of entry view 'Hide Passwords' setting
|
||||
*/
|
||||
void DatabaseWidget::setPasswordsHidden(const bool hide)
|
||||
{
|
||||
m_entryView->setPasswordsHidden(hide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current view state of entry view
|
||||
*/
|
||||
QByteArray DatabaseWidget::entryViewState() const
|
||||
{
|
||||
return m_entryView->viewState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set view state of entry view
|
||||
*/
|
||||
bool DatabaseWidget::setEntryViewState(const QByteArray& state) const
|
||||
{
|
||||
return m_entryView->setViewState(state);
|
||||
}
|
||||
|
||||
void DatabaseWidget::clearAllWidgets()
|
||||
@ -660,8 +684,8 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
|
||||
|
||||
void DatabaseWidget::createGroup()
|
||||
{
|
||||
Q_ASSERT(m_groupView->currentGroup());
|
||||
if (!m_groupView->currentGroup()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -674,8 +698,8 @@ void DatabaseWidget::createGroup()
|
||||
void DatabaseWidget::deleteGroup()
|
||||
{
|
||||
Group* currentGroup = m_groupView->currentGroup();
|
||||
Q_ASSERT(currentGroup && canDeleteCurrentGroup());
|
||||
if (!currentGroup || !canDeleteCurrentGroup()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -910,10 +934,31 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
|
||||
void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column)
|
||||
{
|
||||
if (column == EntryModel::Url && !entry->url().isEmpty()) {
|
||||
openUrlForEntry(entry);
|
||||
Q_ASSERT(entry);
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
||||
// Implement 'copy-on-doubleclick' functionality for certain columns
|
||||
switch (column) {
|
||||
case EntryModel::Username:
|
||||
setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->username()));
|
||||
break;
|
||||
case EntryModel::Password:
|
||||
setClipboardTextAndMinimize(entry->resolveMultiplePlaceholders(entry->password()));
|
||||
break;
|
||||
case EntryModel::Url:
|
||||
if (!entry->url().isEmpty()) {
|
||||
openUrlForEntry(entry);
|
||||
}
|
||||
break;
|
||||
// TODO: switch to 'Notes' tab in details view/pane
|
||||
//case EntryModel::Notes:
|
||||
// break;
|
||||
// TODO: switch to 'Attachments' tab in details view/pane
|
||||
//case EntryModel::Attachments:
|
||||
// break;
|
||||
default:
|
||||
switchToEntryEdit(entry);
|
||||
}
|
||||
}
|
||||
@ -1151,7 +1196,7 @@ bool DatabaseWidget::canDeleteCurrentGroup() const
|
||||
|
||||
bool DatabaseWidget::isInSearchMode() const
|
||||
{
|
||||
return m_entryView->inEntryListMode();
|
||||
return m_entryView->inSearchMode();
|
||||
}
|
||||
|
||||
Group* DatabaseWidget::currentGroup() const
|
||||
|
@ -92,8 +92,12 @@ public:
|
||||
void setMainSplitterSizes(const QList<int>& sizes);
|
||||
QList<int> detailSplitterSizes() const;
|
||||
void setDetailSplitterSizes(const QList<int>& sizes);
|
||||
QList<int> entryHeaderViewSizes() const;
|
||||
void setEntryViewHeaderSizes(const QList<int>& sizes);
|
||||
bool isUsernamesHidden() const;
|
||||
void setUsernamesHidden(const bool hide);
|
||||
bool isPasswordsHidden() const;
|
||||
void setPasswordsHidden(const bool hide);
|
||||
QByteArray entryViewState() const;
|
||||
bool setEntryViewState(const QByteArray& state) const;
|
||||
void clearAllWidgets();
|
||||
bool currentEntryHasTitle();
|
||||
bool currentEntryHasUsername();
|
||||
@ -127,7 +131,7 @@ signals:
|
||||
void searchModeActivated();
|
||||
void mainSplitterSizesChanged();
|
||||
void detailSplitterSizesChanged();
|
||||
void entryColumnSizesChanged();
|
||||
void entryViewStateChanged();
|
||||
void updateSearch(QString text);
|
||||
|
||||
public slots:
|
||||
|
@ -27,16 +27,20 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent)
|
||||
{
|
||||
m_mainSplitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
|
||||
m_detailSplitterSizes = variantToIntList(config()->get("GUI/DetailSplitterState"));
|
||||
m_columnSizesList = variantToIntList(config()->get("GUI/EntryListColumnSizes"));
|
||||
m_columnSizesSearch = variantToIntList(config()->get("GUI/EntrySearchColumnSizes"));
|
||||
m_hideUsernames = config()->get("GUI/HideUsernames").toBool();
|
||||
m_hidePasswords = config()->get("GUI/HidePasswords").toBool();
|
||||
m_listViewState = config()->get("GUI/ListViewState").toByteArray();
|
||||
m_searchViewState = config()->get("GUI/SearchViewState").toByteArray();
|
||||
}
|
||||
|
||||
DatabaseWidgetStateSync::~DatabaseWidgetStateSync()
|
||||
{
|
||||
config()->set("GUI/SplitterState", intListToVariant(m_mainSplitterSizes));
|
||||
config()->set("GUI/DetailSplitterState", intListToVariant(m_detailSplitterSizes));
|
||||
config()->set("GUI/EntryListColumnSizes", intListToVariant(m_columnSizesList));
|
||||
config()->set("GUI/EntrySearchColumnSizes", intListToVariant(m_columnSizesSearch));
|
||||
config()->set("GUI/HideUsernames", m_hideUsernames);
|
||||
config()->set("GUI/HidePasswords", m_hidePasswords);
|
||||
config()->set("GUI/ListViewState", m_listViewState);
|
||||
config()->set("GUI/SearchViewState", m_searchViewState);
|
||||
}
|
||||
|
||||
void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
|
||||
@ -70,8 +74,8 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
|
||||
SLOT(updateSplitterSizes()));
|
||||
connect(m_activeDbWidget, SIGNAL(detailSplitterSizesChanged()),
|
||||
SLOT(updateSplitterSizes()));
|
||||
connect(m_activeDbWidget, SIGNAL(entryColumnSizesChanged()),
|
||||
SLOT(updateColumnSizes()));
|
||||
connect(m_activeDbWidget, SIGNAL(entryViewStateChanged()),
|
||||
SLOT(updateViewState()));
|
||||
connect(m_activeDbWidget, SIGNAL(listModeActivated()),
|
||||
SLOT(restoreListView()));
|
||||
connect(m_activeDbWidget, SIGNAL(searchModeActivated()),
|
||||
@ -83,19 +87,55 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore entry view list view state
|
||||
*
|
||||
* NOTE:
|
||||
* States of entry view 'Hide Usernames'/'Hide Passwords' settings are global,
|
||||
* i.e. they are the same for both list and search mode
|
||||
*
|
||||
* NOTE:
|
||||
* If m_listViewState is empty, the list view has been activated for the first
|
||||
* time after starting with a clean (or invalid) config. Thus, save the current
|
||||
* state. Without this, m_listViewState would remain empty until there is an
|
||||
* actual view state change (e.g. column is resized)
|
||||
*/
|
||||
void DatabaseWidgetStateSync::restoreListView()
|
||||
{
|
||||
if (!m_columnSizesList.isEmpty()) {
|
||||
m_activeDbWidget->setEntryViewHeaderSizes(m_columnSizesList);
|
||||
m_activeDbWidget->setUsernamesHidden(m_hideUsernames);
|
||||
m_activeDbWidget->setPasswordsHidden(m_hidePasswords);
|
||||
|
||||
if (!m_listViewState.isEmpty()) {
|
||||
m_activeDbWidget->setEntryViewState(m_listViewState);
|
||||
} else {
|
||||
m_listViewState = m_activeDbWidget->entryViewState();
|
||||
}
|
||||
|
||||
m_blockUpdates = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore entry view search view state
|
||||
*
|
||||
* NOTE:
|
||||
* States of entry view 'Hide Usernames'/'Hide Passwords' settings are global,
|
||||
* i.e. they are the same for both list and search mode
|
||||
*
|
||||
* NOTE:
|
||||
* If m_searchViewState is empty, the search view has been activated for the
|
||||
* first time after starting with a clean (or invalid) config. Thus, save the
|
||||
* current state. Without this, m_searchViewState would remain empty until
|
||||
* there is an actual view state change (e.g. column is resized)
|
||||
*/
|
||||
void DatabaseWidgetStateSync::restoreSearchView()
|
||||
{
|
||||
if (!m_columnSizesSearch.isEmpty()) {
|
||||
m_activeDbWidget->setEntryViewHeaderSizes(m_columnSizesSearch);
|
||||
m_activeDbWidget->setUsernamesHidden(m_hideUsernames);
|
||||
m_activeDbWidget->setPasswordsHidden(m_hidePasswords);
|
||||
|
||||
if (!m_searchViewState.isEmpty()) {
|
||||
m_activeDbWidget->setEntryViewState(m_searchViewState);
|
||||
} else {
|
||||
m_searchViewState = m_activeDbWidget->entryViewState();
|
||||
}
|
||||
|
||||
m_blockUpdates = false;
|
||||
@ -116,17 +156,26 @@ void DatabaseWidgetStateSync::updateSplitterSizes()
|
||||
m_detailSplitterSizes = m_activeDbWidget->detailSplitterSizes();
|
||||
}
|
||||
|
||||
void DatabaseWidgetStateSync::updateColumnSizes()
|
||||
/**
|
||||
* Update entry view list/search view state
|
||||
*
|
||||
* NOTE:
|
||||
* States of entry view 'Hide Usernames'/'Hide Passwords' settings are global,
|
||||
* i.e. they are the same for both list and search mode
|
||||
*/
|
||||
void DatabaseWidgetStateSync::updateViewState()
|
||||
{
|
||||
if (m_blockUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_activeDbWidget->isGroupSelected()) {
|
||||
m_columnSizesList = m_activeDbWidget->entryHeaderViewSizes();
|
||||
}
|
||||
else {
|
||||
m_columnSizesSearch = m_activeDbWidget->entryHeaderViewSizes();
|
||||
m_hideUsernames = m_activeDbWidget->isUsernamesHidden();
|
||||
m_hidePasswords = m_activeDbWidget->isPasswordsHidden();
|
||||
|
||||
if (m_activeDbWidget->isInSearchMode()) {
|
||||
m_searchViewState = m_activeDbWidget->entryViewState();
|
||||
} else {
|
||||
m_listViewState = m_activeDbWidget->entryViewState();
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,8 +189,7 @@ QList<int> DatabaseWidgetStateSync::variantToIntList(const QVariant& variant)
|
||||
int size = var.toInt(&ok);
|
||||
if (ok) {
|
||||
result.append(size);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result.clear();
|
||||
break;
|
||||
}
|
||||
@ -160,4 +208,3 @@ QVariant DatabaseWidgetStateSync::intListToVariant(const QList<int>& list)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ public slots:
|
||||
private slots:
|
||||
void blockUpdates();
|
||||
void updateSplitterSizes();
|
||||
void updateColumnSizes();
|
||||
void updateViewState();
|
||||
|
||||
private:
|
||||
static QList<int> variantToIntList(const QVariant& variant);
|
||||
@ -48,8 +48,12 @@ private:
|
||||
bool m_blockUpdates;
|
||||
QList<int> m_mainSplitterSizes;
|
||||
QList<int> m_detailSplitterSizes;
|
||||
QList<int> m_columnSizesList;
|
||||
QList<int> m_columnSizesSearch;
|
||||
|
||||
bool m_hideUsernames;
|
||||
bool m_hidePasswords;
|
||||
|
||||
QByteArray m_listViewState;
|
||||
QByteArray m_searchViewState;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASEWIDGETSTATESYNC_H
|
||||
|
@ -127,7 +127,7 @@ void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
|
||||
shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
|
||||
m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()));
|
||||
} else {
|
||||
m_ui->passwordLabel->setText("****");
|
||||
m_ui->passwordLabel->setText(QString("\u25cf").repeated(6));
|
||||
}
|
||||
|
||||
QString url = m_currentEntry->webUrl();
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <QFont>
|
||||
#include <QMimeData>
|
||||
#include <QPalette>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/DatabaseIcons.h"
|
||||
#include "core/Entry.h"
|
||||
@ -27,9 +28,20 @@
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
|
||||
// String being displayed when hiding content
|
||||
const QString EntryModel::HiddenContentDisplay(QString("\u25cf").repeated(6));
|
||||
|
||||
// Format used to display dates
|
||||
const Qt::DateFormat EntryModel::DateFormat = Qt::DefaultLocaleShortDate;
|
||||
|
||||
// Paperclip symbol
|
||||
const QString EntryModel::PaperClipDisplay("\U0001f4ce");
|
||||
|
||||
EntryModel::EntryModel(QObject* parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_group(nullptr)
|
||||
, m_hideUsernames(false)
|
||||
, m_hidePasswords(true)
|
||||
{
|
||||
}
|
||||
|
||||
@ -64,7 +76,7 @@ void EntryModel::setGroup(Group* group)
|
||||
makeConnections(group);
|
||||
|
||||
endResetModel();
|
||||
emit switchedToGroupMode();
|
||||
emit switchedToListMode();
|
||||
}
|
||||
|
||||
void EntryModel::setEntryList(const QList<Entry*>& entries)
|
||||
@ -101,24 +113,26 @@ void EntryModel::setEntryList(const QList<Entry*>& entries)
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
emit switchedToEntryListMode();
|
||||
emit switchedToSearchMode();
|
||||
}
|
||||
|
||||
int EntryModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return m_entries.size();
|
||||
}
|
||||
}
|
||||
|
||||
int EntryModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
// Advised by Qt documentation
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 4;
|
||||
return 12;
|
||||
}
|
||||
|
||||
QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
@ -141,24 +155,95 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
case Title:
|
||||
result = entry->resolveMultiplePlaceholders(entry->title());
|
||||
if (attr->isReference(EntryAttributes::TitleKey)) {
|
||||
result.prepend(tr("Ref: ","Reference abbreviation"));
|
||||
result.prepend(tr("Ref: ", "Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Username:
|
||||
result = entry->resolveMultiplePlaceholders(entry->username());
|
||||
if (m_hideUsernames) {
|
||||
result = EntryModel::HiddenContentDisplay;
|
||||
} else {
|
||||
result = entry->resolveMultiplePlaceholders(entry->username());
|
||||
}
|
||||
if (attr->isReference(EntryAttributes::UserNameKey)) {
|
||||
result.prepend(tr("Ref: ","Reference abbreviation"));
|
||||
result.prepend(tr("Ref: ", "Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Password:
|
||||
if (m_hidePasswords) {
|
||||
result = EntryModel::HiddenContentDisplay;
|
||||
} else {
|
||||
result = entry->resolveMultiplePlaceholders(entry->password());
|
||||
}
|
||||
if (attr->isReference(EntryAttributes::PasswordKey)) {
|
||||
result.prepend(tr("Ref: ", "Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Url:
|
||||
result = entry->displayUrl();
|
||||
result = entry->resolveMultiplePlaceholders(entry->displayUrl());
|
||||
if (attr->isReference(EntryAttributes::URLKey)) {
|
||||
result.prepend(tr("Ref: ","Reference abbreviation"));
|
||||
result.prepend(tr("Ref: ", "Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Notes:
|
||||
// Display only first line of notes in simplified format
|
||||
result = entry->resolveMultiplePlaceholders(entry->notes().section("\n", 0, 0).simplified());
|
||||
if (attr->isReference(EntryAttributes::NotesKey)) {
|
||||
result.prepend(tr("Ref: ", "Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Expires:
|
||||
// Display either date of expiry or 'Never'
|
||||
result = entry->timeInfo().expires() ? entry->timeInfo().expiryTime().toLocalTime().toString(EntryModel::DateFormat) : tr("Never");
|
||||
return result;
|
||||
case Created:
|
||||
result = entry->timeInfo().creationTime().toLocalTime().toString(EntryModel::DateFormat);
|
||||
return result;
|
||||
case Modified:
|
||||
result = entry->timeInfo().lastModificationTime().toLocalTime().toString(EntryModel::DateFormat);
|
||||
return result;
|
||||
case Accessed:
|
||||
result = entry->timeInfo().lastAccessTime().toLocalTime().toString(EntryModel::DateFormat);
|
||||
return result;
|
||||
case Paperclip:
|
||||
result = entry->attachments()->keys().isEmpty() ? QString() : EntryModel::PaperClipDisplay;
|
||||
return result;
|
||||
case Attachments:
|
||||
// Display comma-separated list of attachments
|
||||
QList<QString> attachments = entry->attachments()->keys();
|
||||
for (int i = 0; i < attachments.size(); ++i) {
|
||||
if (result.isEmpty()) {
|
||||
result.append(attachments.at(i));
|
||||
continue;
|
||||
}
|
||||
result.append(QString(", ") + attachments.at(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (role == Qt::DecorationRole) {
|
||||
} else if (role == Qt::UserRole) { // Qt::UserRole is used as sort role, see EntryView::EntryView()
|
||||
switch (index.column()) {
|
||||
case Username:
|
||||
return entry->resolveMultiplePlaceholders(entry->username());
|
||||
case Password:
|
||||
return entry->resolveMultiplePlaceholders(entry->password());
|
||||
case Expires:
|
||||
// There seems to be no better way of expressing 'infinity'
|
||||
return entry->timeInfo().expires() ? entry->timeInfo().expiryTime() : QDateTime(QDate(9999, 1, 1));
|
||||
case Created:
|
||||
return entry->timeInfo().creationTime();
|
||||
case Modified:
|
||||
return entry->timeInfo().lastModificationTime();
|
||||
case Accessed:
|
||||
return entry->timeInfo().lastAccessTime();
|
||||
case Paperclip:
|
||||
// Display entries with attachments above those without when
|
||||
// sorting ascendingly (and vice versa when sorting descendingly)
|
||||
return entry->attachments()->keys().isEmpty() ? 1 : 0;
|
||||
default:
|
||||
// For all other columns, simply use data provided by Qt::Display-
|
||||
// Role for sorting
|
||||
return data(index, Qt::DisplayRole);
|
||||
}
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
switch (index.column()) {
|
||||
case ParentGroup:
|
||||
if (entry->group()) {
|
||||
@ -168,20 +253,17 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
case Title:
|
||||
if (entry->isExpired()) {
|
||||
return databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return entry->iconScaledPixmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (role == Qt::FontRole) {
|
||||
} else if (role == Qt::FontRole) {
|
||||
QFont font;
|
||||
if (entry->isExpired()) {
|
||||
font.setStrikeOut(true);
|
||||
}
|
||||
return font;
|
||||
}
|
||||
else if (role == Qt::TextColorRole) {
|
||||
} else if (role == Qt::TextColorRole) {
|
||||
if (entry->hasReferences()) {
|
||||
QPalette p;
|
||||
return QVariant(p.color(QPalette::Active, QPalette::Mid));
|
||||
@ -190,6 +272,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
@ -200,8 +283,24 @@ QVariant EntryModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||
return tr("Title");
|
||||
case Username:
|
||||
return tr("Username");
|
||||
case Password:
|
||||
return tr("Password");
|
||||
case Url:
|
||||
return tr("URL");
|
||||
case Notes:
|
||||
return tr("Notes");
|
||||
case Expires:
|
||||
return tr("Expires");
|
||||
case Created:
|
||||
return tr("Created");
|
||||
case Modified:
|
||||
return tr("Modified");
|
||||
case Accessed:
|
||||
return tr("Accessed");
|
||||
case Paperclip:
|
||||
return EntryModel::PaperClipDisplay;
|
||||
case Attachments:
|
||||
return tr("Attachments");
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,8 +321,7 @@ Qt::ItemFlags EntryModel::flags(const QModelIndex& modelIndex) const
|
||||
{
|
||||
if (!modelIndex.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return QAbstractItemModel::flags(modelIndex) | Qt::ItemIsDragEnabled;
|
||||
}
|
||||
}
|
||||
@ -264,8 +362,7 @@ QMimeData* EntryModel::mimeData(const QModelIndexList& indexes) const
|
||||
if (seenEntries.isEmpty()) {
|
||||
delete data;
|
||||
return nullptr;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
data->setData(mimeTypes().at(0), encoded);
|
||||
return data;
|
||||
}
|
||||
@ -337,3 +434,53 @@ void EntryModel::makeConnections(const Group* group)
|
||||
connect(group, SIGNAL(entryRemoved(Entry*)), SLOT(entryRemoved()));
|
||||
connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state of 'Hide Usernames' setting
|
||||
*/
|
||||
bool EntryModel::isUsernamesHidden() const
|
||||
{
|
||||
return m_hideUsernames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state of 'Hide Usernames' setting and signal change
|
||||
*/
|
||||
void EntryModel::setUsernamesHidden(const bool hide)
|
||||
{
|
||||
m_hideUsernames = hide;
|
||||
emit usernamesHiddenChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state of 'Hide Passwords' setting
|
||||
*/
|
||||
bool EntryModel::isPasswordsHidden() const
|
||||
{
|
||||
return m_hidePasswords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state of 'Hide Passwords' setting and signal change
|
||||
*/
|
||||
void EntryModel::setPasswordsHidden(const bool hide)
|
||||
{
|
||||
m_hidePasswords = hide;
|
||||
emit passwordsHiddenChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle state of 'Hide Usernames' setting
|
||||
*/
|
||||
void EntryModel::toggleUsernamesHidden(const bool hide)
|
||||
{
|
||||
setUsernamesHidden(hide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle state of 'Hide Passwords' setting
|
||||
*/
|
||||
void EntryModel::togglePasswordsHidden(const bool hide)
|
||||
{
|
||||
setPasswordsHidden(hide);
|
||||
}
|
||||
|
@ -33,7 +33,15 @@ public:
|
||||
ParentGroup = 0,
|
||||
Title = 1,
|
||||
Username = 2,
|
||||
Url = 3
|
||||
Password = 3,
|
||||
Url = 4,
|
||||
Notes = 5,
|
||||
Expires = 6,
|
||||
Created = 7,
|
||||
Modified = 8,
|
||||
Accessed = 9,
|
||||
Paperclip = 10,
|
||||
Attachments = 11
|
||||
};
|
||||
|
||||
explicit EntryModel(QObject* parent = nullptr);
|
||||
@ -52,12 +60,21 @@ public:
|
||||
|
||||
void setEntryList(const QList<Entry*>& entries);
|
||||
|
||||
bool isUsernamesHidden() const;
|
||||
void setUsernamesHidden(const bool hide);
|
||||
bool isPasswordsHidden() const;
|
||||
void setPasswordsHidden(const bool hide);
|
||||
|
||||
signals:
|
||||
void switchedToEntryListMode();
|
||||
void switchedToGroupMode();
|
||||
void switchedToListMode();
|
||||
void switchedToSearchMode();
|
||||
void usernamesHiddenChanged();
|
||||
void passwordsHiddenChanged();
|
||||
|
||||
public slots:
|
||||
void setGroup(Group* group);
|
||||
void toggleUsernamesHidden(const bool hide);
|
||||
void togglePasswordsHidden(const bool hide);
|
||||
|
||||
private slots:
|
||||
void entryAboutToAdd(Entry* entry);
|
||||
@ -74,6 +91,13 @@ private:
|
||||
QList<Entry*> m_entries;
|
||||
QList<Entry*> m_orgEntries;
|
||||
QList<const Group*> m_allGroups;
|
||||
|
||||
bool m_hideUsernames;
|
||||
bool m_hidePasswords;
|
||||
|
||||
static const QString HiddenContentDisplay;
|
||||
static const Qt::DateFormat DateFormat;
|
||||
static const QString PaperClipDisplay;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYMODEL_H
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
|
||||
#include "gui/SortFilterHideProxyModel.h"
|
||||
|
||||
@ -26,12 +27,14 @@ EntryView::EntryView(QWidget* parent)
|
||||
: QTreeView(parent)
|
||||
, m_model(new EntryModel(this))
|
||||
, m_sortModel(new SortFilterHideProxyModel(this))
|
||||
, m_inEntryListMode(false)
|
||||
, m_inSearchMode(false)
|
||||
{
|
||||
m_sortModel->setSourceModel(m_model);
|
||||
m_sortModel->setDynamicSortFilter(true);
|
||||
m_sortModel->setSortLocaleAware(true);
|
||||
m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
// Use Qt::UserRole as sort role, see EntryModel::data()
|
||||
m_sortModel->setSortRole(Qt::UserRole);
|
||||
QTreeView::setModel(m_sortModel);
|
||||
|
||||
setUniformRowHeights(true);
|
||||
@ -40,17 +43,72 @@ EntryView::EntryView(QWidget* parent)
|
||||
setDragEnabled(true);
|
||||
setSortingEnabled(true);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
header()->setDefaultSectionSize(150);
|
||||
|
||||
// QAbstractItemView::startDrag() uses this property as the default drag action
|
||||
setDefaultDropAction(Qt::MoveAction);
|
||||
|
||||
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
||||
connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(entrySelectionChanged()));
|
||||
connect(m_model, SIGNAL(switchedToEntryListMode()), SLOT(switchToEntryListMode()));
|
||||
connect(m_model, SIGNAL(switchedToGroupMode()), SLOT(switchToGroupMode()));
|
||||
|
||||
connect(m_model, SIGNAL(switchedToListMode()), SLOT(switchToListMode()));
|
||||
connect(m_model, SIGNAL(switchedToSearchMode()), SLOT(switchToSearchMode()));
|
||||
connect(m_model, SIGNAL(usernamesHiddenChanged()), SIGNAL(viewStateChanged()));
|
||||
connect(m_model, SIGNAL(passwordsHiddenChanged()), SIGNAL(viewStateChanged()));
|
||||
connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex)));
|
||||
|
||||
m_headerMenu = new QMenu(this);
|
||||
m_headerMenu->setTitle(tr("Customize View"));
|
||||
m_headerMenu->addSection(tr("Customize View"));
|
||||
|
||||
m_hideUsernamesAction = m_headerMenu->addAction(tr("Hide Usernames"), m_model, SLOT(toggleUsernamesHidden(bool)));
|
||||
m_hideUsernamesAction->setCheckable(true);
|
||||
m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(togglePasswordsHidden(bool)));
|
||||
m_hidePasswordsAction->setCheckable(true);
|
||||
m_headerMenu->addSeparator();
|
||||
|
||||
// Actions to toggle column visibility, each carrying the corresponding
|
||||
// colummn index as data
|
||||
m_columnActions = new QActionGroup(this);
|
||||
m_columnActions->setExclusive(false);
|
||||
for (int columnIndex = 1; columnIndex < header()->count(); ++columnIndex) {
|
||||
QString caption = m_model->headerData(columnIndex, Qt::Horizontal, Qt::DisplayRole).toString();
|
||||
QAction* action = m_headerMenu->addAction(caption);
|
||||
action->setCheckable(true);
|
||||
action->setData(columnIndex);
|
||||
m_columnActions->addAction(action);
|
||||
}
|
||||
connect(m_columnActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleColumnVisibility(QAction*)));
|
||||
|
||||
m_headerMenu->addSeparator();
|
||||
m_headerMenu->addAction(tr("Fit to window"), this, SLOT(fitColumnsToWindow()));
|
||||
m_headerMenu->addAction(tr("Fit to contents"), this, SLOT(fitColumnsToContents()));
|
||||
m_headerMenu->addSeparator();
|
||||
m_headerMenu->addAction(tr("Reset to defaults"), this, SLOT(resetViewToDefaults()));
|
||||
|
||||
header()->setDefaultSectionSize(100);
|
||||
// Stretching of last section interferes with fitting columns to window
|
||||
header()->setStretchLastSection(false);
|
||||
header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)));
|
||||
connect(header(), SIGNAL(sectionCountChanged(int, int)), this, SIGNAL(viewStateChanged()));
|
||||
connect(header(), SIGNAL(sectionMoved(int, int, int)), this, SIGNAL(viewStateChanged()));
|
||||
connect(header(), SIGNAL(sectionResized(int, int, int)), this, SIGNAL(viewStateChanged()));
|
||||
connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SIGNAL(viewStateChanged()));
|
||||
|
||||
// TODO: not working as expected, columns will end up being very small,
|
||||
// most likely due to the widget not being sized properly at this time
|
||||
//fitColumnsToWindow();
|
||||
|
||||
// Configure default search view state and save for later use
|
||||
header()->showSection(EntryModel::ParentGroup);
|
||||
m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder);
|
||||
sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder);
|
||||
m_defaultSearchViewState = header()->saveState();
|
||||
|
||||
// Configure default list view state and save for later use
|
||||
header()->hideSection(EntryModel::ParentGroup);
|
||||
m_sortModel->sort(EntryModel::Title, Qt::AscendingOrder);
|
||||
sortByColumn(EntryModel::Title, Qt::AscendingOrder);
|
||||
m_defaultListViewState = header()->saveState();
|
||||
}
|
||||
|
||||
void EntryView::keyPressEvent(QKeyEvent* event)
|
||||
@ -83,15 +141,14 @@ void EntryView::setFirstEntryActive()
|
||||
if (m_model->rowCount() > 0) {
|
||||
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
|
||||
setCurrentEntry(m_model->entryFromIndex(index));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
emit entrySelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool EntryView::inEntryListMode()
|
||||
bool EntryView::inSearchMode()
|
||||
{
|
||||
return m_inEntryListMode;
|
||||
return m_inSearchMode;
|
||||
}
|
||||
|
||||
void EntryView::emitEntryActivated(const QModelIndex& index)
|
||||
@ -117,8 +174,7 @@ Entry* EntryView::currentEntry()
|
||||
QModelIndexList list = selectionModel()->selectedRows();
|
||||
if (list.size() == 1) {
|
||||
return m_model->entryFromIndex(m_sortModel->mapToSource(list.first()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@ -138,30 +194,222 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index)
|
||||
{
|
||||
if (index.isValid()) {
|
||||
return m_model->entryFromIndex(m_sortModel->mapToSource(index));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void EntryView::switchToEntryListMode()
|
||||
/**
|
||||
* Switch to list mode, i.e. list entries of group
|
||||
*/
|
||||
void EntryView::switchToListMode()
|
||||
{
|
||||
m_sortModel->hideColumn(0, false);
|
||||
if (!m_inSearchMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sortModel->sort(1, Qt::AscendingOrder);
|
||||
m_sortModel->sort(0, Qt::AscendingOrder);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
m_inEntryListMode = true;
|
||||
header()->hideSection(EntryModel::ParentGroup);
|
||||
m_inSearchMode = false;
|
||||
}
|
||||
|
||||
void EntryView::switchToGroupMode()
|
||||
/**
|
||||
* Switch to search mode, i.e. list search results
|
||||
*/
|
||||
void EntryView::switchToSearchMode()
|
||||
{
|
||||
m_sortModel->hideColumn(0, true);
|
||||
if (m_inSearchMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sortModel->sort(-1, Qt::AscendingOrder);
|
||||
m_sortModel->sort(0, Qt::AscendingOrder);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
header()->showSection(EntryModel::ParentGroup);
|
||||
|
||||
m_inEntryListMode = false;
|
||||
// Always set sorting to column 'Group', as it does not feel right to
|
||||
// have the last known sort configuration of search view restored by
|
||||
// 'DatabaseWidgetStateSync', which is what happens without this
|
||||
m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder);
|
||||
sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder);
|
||||
|
||||
m_inSearchMode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state of 'Hide Usernames' setting (NOTE: just pass-through for
|
||||
* m_model)
|
||||
*/
|
||||
bool EntryView::isUsernamesHidden() const
|
||||
{
|
||||
return m_model->isUsernamesHidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state of 'Hide Usernames' setting (NOTE: just pass-through for m_model)
|
||||
*/
|
||||
void EntryView::setUsernamesHidden(const bool hide)
|
||||
{
|
||||
m_model->setUsernamesHidden(hide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state of 'Hide Passwords' setting (NOTE: just pass-through for
|
||||
* m_model)
|
||||
*/
|
||||
bool EntryView::isPasswordsHidden() const
|
||||
{
|
||||
return m_model->isPasswordsHidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state of 'Hide Passwords' setting (NOTE: just pass-through for m_model)
|
||||
*/
|
||||
void EntryView::setPasswordsHidden(const bool hide)
|
||||
{
|
||||
m_model->setPasswordsHidden(hide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current view state
|
||||
*/
|
||||
QByteArray EntryView::viewState() const
|
||||
{
|
||||
return header()->saveState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set view state
|
||||
*/
|
||||
bool EntryView::setViewState(const QByteArray& state) const
|
||||
{
|
||||
return header()->restoreState(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync checkable menu actions to current state and display header context
|
||||
* menu at specified position
|
||||
*/
|
||||
void EntryView::showHeaderMenu(const QPoint& position)
|
||||
{
|
||||
m_hideUsernamesAction->setChecked(m_model->isUsernamesHidden());
|
||||
m_hidePasswordsAction->setChecked(m_model->isPasswordsHidden());
|
||||
const QList<QAction*> actions = m_columnActions->actions();
|
||||
for (auto& action : actions) {
|
||||
Q_ASSERT(static_cast<QMetaType::Type>(action->data().type()) == QMetaType::Int);
|
||||
if (static_cast<QMetaType::Type>(action->data().type()) != QMetaType::Int) {
|
||||
continue;
|
||||
}
|
||||
int columnIndex = action->data().toInt();
|
||||
bool hidden = header()->isSectionHidden(columnIndex) || (header()->sectionSize(columnIndex) == 0);
|
||||
action->setChecked(!hidden);
|
||||
}
|
||||
|
||||
m_headerMenu->popup(mapToGlobal(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visibility of column referenced by triggering action
|
||||
*/
|
||||
void EntryView::toggleColumnVisibility(QAction *action)
|
||||
{
|
||||
// Verify action carries a column index as data. Since QVariant.toInt()
|
||||
// below will accept anything that's interpretable as int, perform a type
|
||||
// check here to make sure data actually IS int
|
||||
Q_ASSERT(static_cast<QMetaType::Type>(action->data().type()) == QMetaType::Int);
|
||||
if (static_cast<QMetaType::Type>(action->data().type()) != QMetaType::Int) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle column visibility. Visible columns will only be hidden if at
|
||||
// least one visible column remains, as the table header will disappear
|
||||
// entirely when all columns are hidden
|
||||
int columnIndex = action->data().toInt();
|
||||
if (action->isChecked()) {
|
||||
header()->showSection(columnIndex);
|
||||
if (header()->sectionSize(columnIndex) == 0) {
|
||||
header()->resizeSection(columnIndex, header()->defaultSectionSize());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((header()->count() - header()->hiddenSectionCount()) > 1) {
|
||||
header()->hideSection(columnIndex);
|
||||
return;
|
||||
}
|
||||
action->setChecked(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize columns to fit all visible columns within the available space
|
||||
*
|
||||
* NOTE:
|
||||
* If EntryView::resizeEvent() is overridden at some point in the future,
|
||||
* its implementation MUST call the corresponding parent method using
|
||||
* 'QTreeView::resizeEvent(event)'. Without this, fitting to window will
|
||||
* be broken and/or work unreliably (stumbled upon during testing)
|
||||
*
|
||||
* NOTE:
|
||||
* Testing showed that it is absolutely necessary to emit signal 'viewState
|
||||
* Changed' here. Without this, an incomplete view state might get saved by
|
||||
* 'DatabaseWidgetStateSync' (e.g. only some columns resized)
|
||||
*/
|
||||
void EntryView::fitColumnsToWindow()
|
||||
{
|
||||
header()->resizeSections(QHeaderView::Stretch);
|
||||
emit viewStateChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize columns to fit current table contents, i.e. make all contents
|
||||
* entirely visible
|
||||
*/
|
||||
void EntryView::fitColumnsToContents()
|
||||
{
|
||||
// Resize columns to fit contents
|
||||
header()->resizeSections(QHeaderView::ResizeToContents);
|
||||
|
||||
// Determine total width of currently visible columns. If there is
|
||||
// still some space available on the header, equally distribute it to
|
||||
// visible columns and add remaining fraction to last visible column
|
||||
int width = 0;
|
||||
for (int columnIndex = 0; columnIndex < header()->count(); ++columnIndex) {
|
||||
if (!header()->isSectionHidden(columnIndex)) {
|
||||
width += header()->sectionSize(columnIndex);
|
||||
}
|
||||
}
|
||||
int visible = header()->count() - header()->hiddenSectionCount();
|
||||
int avail = header()->width() - width;
|
||||
if ((visible <= 0) || (avail <= 0)) {
|
||||
return;
|
||||
}
|
||||
int add = avail / visible;
|
||||
width = 0;
|
||||
int last = 0;
|
||||
for (int columnIndex = 0; columnIndex < header()->count(); ++columnIndex) {
|
||||
if (!header()->isSectionHidden(columnIndex)) {
|
||||
header()->resizeSection(columnIndex, header()->sectionSize(columnIndex) + add);
|
||||
width += header()->sectionSize(columnIndex);
|
||||
if (header()->visualIndex(columnIndex) > last) {
|
||||
last = header()->visualIndex(columnIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
header()->resizeSection(header()->logicalIndex(last), header()->sectionSize(last) + (header()->width() - width));
|
||||
|
||||
// Shouldn't be necessary due to use of header()->resizeSection, but
|
||||
// lets just do it anyway for the sake of completeness
|
||||
emit viewStateChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset view to defaults
|
||||
*/
|
||||
void EntryView::resetViewToDefaults()
|
||||
{
|
||||
m_model->setUsernamesHidden(false);
|
||||
m_model->setPasswordsHidden(true);
|
||||
|
||||
if (m_inSearchMode) {
|
||||
header()->restoreState(m_defaultSearchViewState);
|
||||
} else {
|
||||
header()->restoreState(m_defaultListViewState);
|
||||
}
|
||||
|
||||
fitColumnsToWindow();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ class Entry;
|
||||
class EntryModel;
|
||||
class Group;
|
||||
class SortFilterHideProxyModel;
|
||||
class QActionGroup;
|
||||
|
||||
class EntryView : public QTreeView
|
||||
{
|
||||
@ -38,9 +39,15 @@ public:
|
||||
void setCurrentEntry(Entry* entry);
|
||||
Entry* entryFromIndex(const QModelIndex& index);
|
||||
void setEntryList(const QList<Entry*>& entries);
|
||||
bool inEntryListMode();
|
||||
bool inSearchMode();
|
||||
int numberOfSelectedEntries();
|
||||
void setFirstEntryActive();
|
||||
bool isUsernamesHidden() const;
|
||||
void setUsernamesHidden(const bool hide);
|
||||
bool isPasswordsHidden() const;
|
||||
void setPasswordsHidden(const bool hide);
|
||||
QByteArray viewState() const;
|
||||
bool setViewState(const QByteArray& state) const;
|
||||
|
||||
public slots:
|
||||
void setGroup(Group* group);
|
||||
@ -49,6 +56,7 @@ signals:
|
||||
void entryActivated(Entry* entry, EntryModel::ModelColumn column);
|
||||
void entryPressed(Entry* entry);
|
||||
void entrySelectionChanged();
|
||||
void viewStateChanged();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
@ -56,13 +64,26 @@ protected:
|
||||
private slots:
|
||||
void emitEntryActivated(const QModelIndex& index);
|
||||
void emitEntryPressed(const QModelIndex& index);
|
||||
void switchToEntryListMode();
|
||||
void switchToGroupMode();
|
||||
void switchToListMode();
|
||||
void switchToSearchMode();
|
||||
void showHeaderMenu(const QPoint& position);
|
||||
void toggleColumnVisibility(QAction *action);
|
||||
void fitColumnsToWindow();
|
||||
void fitColumnsToContents();
|
||||
void resetViewToDefaults();
|
||||
|
||||
private:
|
||||
EntryModel* const m_model;
|
||||
SortFilterHideProxyModel* const m_sortModel;
|
||||
bool m_inEntryListMode;
|
||||
bool m_inSearchMode;
|
||||
|
||||
QByteArray m_defaultListViewState;
|
||||
QByteArray m_defaultSearchViewState;
|
||||
|
||||
QMenu* m_headerMenu;
|
||||
QAction* m_hideUsernamesAction;
|
||||
QAction* m_hidePasswordsAction;
|
||||
QActionGroup* m_columnActions;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYVIEW_H
|
||||
|
@ -288,9 +288,15 @@ void TestEntryModel::testProxyModel()
|
||||
|
||||
modelSource->setGroup(db->rootGroup());
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Update comparison value of modelProxy->columnCount() to account for
|
||||
* additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified',
|
||||
* 'Accessed', 'Paperclip' and 'Attachments'
|
||||
*/
|
||||
QSignalSpy spyColumnRemove(modelProxy, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)));
|
||||
modelProxy->hideColumn(0, true);
|
||||
QCOMPARE(modelProxy->columnCount(), 3);
|
||||
QCOMPARE(modelProxy->columnCount(), 11);
|
||||
QVERIFY(spyColumnRemove.size() >= 1);
|
||||
|
||||
int oldSpyColumnRemoveSize = spyColumnRemove.size();
|
||||
@ -304,9 +310,15 @@ void TestEntryModel::testProxyModel()
|
||||
entryList << entry;
|
||||
modelSource->setEntryList(entryList);
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Update comparison value of modelProxy->columnCount() to account for
|
||||
* additional columns 'Password', 'Notes', 'Expires', 'Created', 'Modified',
|
||||
* 'Accessed', 'Paperclip' and 'Attachments'
|
||||
*/
|
||||
QSignalSpy spyColumnInsert(modelProxy, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)));
|
||||
modelProxy->hideColumn(0, false);
|
||||
QCOMPARE(modelProxy->columnCount(), 4);
|
||||
QCOMPARE(modelProxy->columnCount(), 12);
|
||||
QVERIFY(spyColumnInsert.size() >= 1);
|
||||
|
||||
int oldSpyColumnInsertSize = spyColumnInsert.size();
|
||||
|
@ -698,7 +698,7 @@ void TestGui::testDeleteEntry()
|
||||
QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton);
|
||||
clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton);
|
||||
QVERIFY(entryDeleteWidget->isVisible());
|
||||
QVERIFY(entryDeleteWidget->isEnabled());
|
||||
QVERIFY(!m_db->metadata()->recycleBin());
|
||||
@ -709,8 +709,8 @@ void TestGui::testDeleteEntry()
|
||||
QCOMPARE(entryView->model()->rowCount(), 3);
|
||||
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 1);
|
||||
|
||||
clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton);
|
||||
clickIndex(entryView->model()->index(2, 0), entryView, Qt::LeftButton, Qt::ControlModifier);
|
||||
clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton);
|
||||
clickIndex(entryView->model()->index(2, 1), entryView, Qt::LeftButton, Qt::ControlModifier);
|
||||
QCOMPARE(entryView->selectionModel()->selectedRows().size(), 2);
|
||||
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
@ -729,7 +729,7 @@ void TestGui::testDeleteEntry()
|
||||
groupView, Qt::LeftButton);
|
||||
QCOMPARE(groupView->currentGroup()->name(), m_db->metadata()->recycleBin()->name());
|
||||
|
||||
clickIndex(entryView->model()->index(0, 0), entryView, Qt::LeftButton);
|
||||
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
|
||||
MessageBox::setNextAnswer(QMessageBox::No);
|
||||
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
||||
QCOMPARE(entryView->model()->rowCount(), 3);
|
||||
@ -740,8 +740,8 @@ void TestGui::testDeleteEntry()
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 2);
|
||||
|
||||
clickIndex(entryView->model()->index(0, 0), entryView, Qt::LeftButton);
|
||||
clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton, Qt::ControlModifier);
|
||||
clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton);
|
||||
clickIndex(entryView->model()->index(1, 1), entryView, Qt::LeftButton, Qt::ControlModifier);
|
||||
MessageBox::setNextAnswer(QMessageBox::Yes);
|
||||
QTest::mouseClick(entryDeleteWidget, Qt::LeftButton);
|
||||
QCOMPARE(entryView->model()->rowCount(), 0);
|
||||
|
Loading…
Reference in New Issue
Block a user