mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-15 09:17:28 -05:00
Add header context menu to entry view table
Add header context menu to entry view table (accessible using right click on header), providing: - Actions to toggle 'Hide Usernames' / 'Hide Passwords' - Actions to toggle column visibility - Actions to resize columns - Action to reset view to defaults
This commit is contained in:
parent
268035ff9e
commit
31d73626e5
@ -578,3 +578,21 @@ void EntryModel::setHidePasswords(const bool hide)
|
||||
m_hidePasswords = hide;
|
||||
emit hidePasswordsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Toggle state of 'Hide Usernames' setting
|
||||
*/
|
||||
void EntryModel::toggleHideUsernames(const bool hide)
|
||||
{
|
||||
setHideUsernames(hide);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Toggle state of 'Hide Passwords' setting
|
||||
*/
|
||||
void EntryModel::toggleHidePasswords(const bool hide)
|
||||
{
|
||||
setHidePasswords(hide);
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ public:
|
||||
signals:
|
||||
void switchedToEntryListMode();
|
||||
void switchedToGroupMode();
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Signals to notify about state changes of 'Hide Usernames' and 'Hide
|
||||
@ -88,6 +89,13 @@ signals:
|
||||
public slots:
|
||||
void setGroup(Group* group);
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Slots to toggle state of 'Hide Usernames' and 'Hide Passwords' settings
|
||||
*/
|
||||
void toggleHideUsernames(const bool hide);
|
||||
void toggleHidePasswords(const bool hide);
|
||||
|
||||
private slots:
|
||||
void entryAboutToAdd(Entry* entry);
|
||||
void entryAdded(Entry* entry);
|
||||
|
@ -19,9 +19,26 @@
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Add include required for header context menu
|
||||
*/
|
||||
#include <QMenu>
|
||||
|
||||
#include "gui/SortFilterHideProxyModel.h"
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
*
|
||||
* TODO NOTE:
|
||||
* Currently, 'zombie' columns which are not hidden but have width == 0
|
||||
* (rendering them invisible) may appear. This is caused by DatabaseWidget
|
||||
* StateSync. Corresponding checks/workarounds may be removed once sync
|
||||
* code is updated accordingly
|
||||
* -> relevant code pieces: if (header()->sectionSize(...) == 0) { ... }
|
||||
*
|
||||
*/
|
||||
|
||||
EntryView::EntryView(QWidget* parent)
|
||||
: QTreeView(parent)
|
||||
, m_model(new EntryModel(this))
|
||||
@ -47,7 +64,6 @@ 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);
|
||||
@ -58,6 +74,69 @@ EntryView::EntryView(QWidget* parent)
|
||||
connect(m_model, SIGNAL(switchedToGroupMode()), SLOT(switchToGroupMode()));
|
||||
|
||||
connect(this, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryPressed(QModelIndex)));
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Create header context menu:
|
||||
* - Actions to toggle state of 'Hide Usernames'/'Hide Passwords' settings
|
||||
* - Actions to toggle column visibility, with each action carrying 'its'
|
||||
* column index as data
|
||||
* - Actions to resize columns
|
||||
* - Action to reset view to defaults
|
||||
*/
|
||||
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(toggleHideUsernames(bool)));
|
||||
m_hideUsernamesAction->setCheckable(true);
|
||||
m_hidePasswordsAction = m_headerMenu->addAction(tr("Hide Passwords"), m_model, SLOT(toggleHidePasswords(bool)));
|
||||
m_hidePasswordsAction->setCheckable(true);
|
||||
m_headerMenu->addSeparator();
|
||||
|
||||
m_columnActions = new QActionGroup(this);
|
||||
m_columnActions->setExclusive(false);
|
||||
for (int colidx = 1; colidx < header()->count(); colidx++) {
|
||||
QString caption = m_model->headerData(colidx, Qt::Horizontal, Qt::DisplayRole).toString();
|
||||
QAction* action = m_headerMenu->addAction(caption);
|
||||
action->setCheckable(true);
|
||||
action->setData(colidx);
|
||||
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()));
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Configure header:
|
||||
* - Set default section size
|
||||
* - Disable stretching of last section (interferes with fitting columns
|
||||
* to window)
|
||||
* - Associate with context menu
|
||||
*/
|
||||
header()->setDefaultSectionSize(100);
|
||||
header()->setStretchLastSection(false);
|
||||
header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showHeaderMenu(QPoint)));
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Finalize setup by resetting view to defaults. Although not really
|
||||
* necessary at this point, it makes sense in order to avoid duplicating
|
||||
* code (sorting order, visibility of first column etc.)
|
||||
*
|
||||
* TODO:
|
||||
* Not working as expected, columns will end up being very small, most
|
||||
* likely due to EntryView not being sized properly at this time. Either
|
||||
* find a way to make this work by analizing when/where EntryView is
|
||||
* created or remove
|
||||
*/
|
||||
//resetViewToDefaults();
|
||||
}
|
||||
|
||||
void EntryView::keyPressEvent(QKeyEvent* event)
|
||||
@ -153,22 +232,201 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index)
|
||||
|
||||
void EntryView::switchToEntryListMode()
|
||||
{
|
||||
m_sortModel->hideColumn(0, false);
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Use header()->showSection() instead of m_sortModel->hideColumn() as
|
||||
* the latter messes up column indices, interfering with code relying on
|
||||
* proper indices
|
||||
*/
|
||||
header()->showSection(EntryModel::ParentGroup);
|
||||
if (header()->sectionSize(EntryModel::ParentGroup) == 0) {
|
||||
header()->resizeSection(EntryModel::ParentGroup, header()->defaultSectionSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Set sorting column and order (TODO: check what first two lines do, if
|
||||
* they are actually necessary, if indices are still correct and if indices
|
||||
* may be replaced by EntryModel::<column>)
|
||||
*/
|
||||
m_sortModel->sort(1, Qt::AscendingOrder);
|
||||
m_sortModel->sort(0, Qt::AscendingOrder);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder);
|
||||
|
||||
m_inEntryListMode = true;
|
||||
}
|
||||
|
||||
void EntryView::switchToGroupMode()
|
||||
{
|
||||
m_sortModel->hideColumn(0, true);
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Use header()->hideSection() instead of m_sortModel->hideColumn() as
|
||||
* the latter messes up column indices, interfering with code relying on
|
||||
* proper indices
|
||||
*/
|
||||
header()->hideSection(EntryModel::ParentGroup);
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Set sorting column and order (TODO: check what first two lines do, if
|
||||
* they are actually necessary, if indices are still correct and if indices
|
||||
* may be replaced by EntryModel::<column>)
|
||||
*/
|
||||
m_sortModel->sort(-1, Qt::AscendingOrder);
|
||||
m_sortModel->sort(0, Qt::AscendingOrder);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
sortByColumn(EntryModel::Title, Qt::AscendingOrder);
|
||||
|
||||
m_inEntryListMode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Sync checkable menu actions to current state and display header context
|
||||
* menu at specified position
|
||||
*/
|
||||
void EntryView::showHeaderMenu(const QPoint& position)
|
||||
{
|
||||
/* Sync checked state of menu actions to current state of view */
|
||||
m_hideUsernamesAction->setChecked(m_model->hideUsernames());
|
||||
m_hidePasswordsAction->setChecked(m_model->hidePasswords());
|
||||
foreach (QAction *action, m_columnActions->actions()) {
|
||||
if (static_cast<QMetaType::Type>(action->data().type()) != QMetaType::Int) {
|
||||
Q_ASSERT(false);
|
||||
continue;
|
||||
}
|
||||
int colidx = action->data().toInt();
|
||||
bool hidden = header()->isSectionHidden(colidx) || (header()->sectionSize(colidx) == 0);
|
||||
action->setChecked(!hidden);
|
||||
}
|
||||
|
||||
/* Display menu */
|
||||
m_headerMenu->popup(mapToGlobal(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* 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
|
||||
*/
|
||||
if (static_cast<QMetaType::Type>(action->data().type()) != QMetaType::Int) {
|
||||
Q_ASSERT(false);
|
||||
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, rendering the context menu in-
|
||||
* accessible
|
||||
*/
|
||||
int colidx = action->data().toInt();
|
||||
if (action->isChecked()) {
|
||||
header()->showSection(colidx);
|
||||
if (header()->sectionSize(colidx) == 0) {
|
||||
header()->resizeSection(colidx, header()->defaultSectionSize());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((header()->count() - header()->hiddenSectionCount()) > 1) {
|
||||
header()->hideSection(colidx);
|
||||
}
|
||||
else {
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* 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)
|
||||
*/
|
||||
void EntryView::fitColumnsToWindow()
|
||||
{
|
||||
header()->resizeSections(QHeaderView::Stretch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* 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 colidx = 0; colidx < header()->count(); colidx++) {
|
||||
if (!header()->isSectionHidden(colidx)) {
|
||||
width += header()->sectionSize(colidx);
|
||||
}
|
||||
}
|
||||
int visible = header()->count() - header()->hiddenSectionCount();
|
||||
int avail = header()->width() - width;
|
||||
if ((visible > 0) && (avail > 0)) {
|
||||
int add = avail / visible;
|
||||
width = 0;
|
||||
int last = 0;
|
||||
for (int colidx = 0; colidx < header()->count(); colidx++) {
|
||||
if (!header()->isSectionHidden(colidx)) {
|
||||
header()->resizeSection(colidx, header()->sectionSize(colidx) + add);
|
||||
width += header()->sectionSize(colidx);
|
||||
if (header()->visualIndex(colidx) > last) {
|
||||
last = header()->visualIndex(colidx);
|
||||
}
|
||||
}
|
||||
}
|
||||
header()->resizeSection(header()->logicalIndex(last), header()->sectionSize(last) + (header()->width() - width));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Reset view to defaults
|
||||
*
|
||||
* NOTE:
|
||||
* header()->saveState()/restoreState() could also be used for this, but
|
||||
* testing showed that it complicates things more than it helps when trying
|
||||
* to account for current list mode
|
||||
*/
|
||||
void EntryView::resetViewToDefaults()
|
||||
{
|
||||
/* Reset state of 'Hide Usernames'/'Hide Passwords' settings */
|
||||
m_model->setHideUsernames(false);
|
||||
m_model->setHidePasswords(true);
|
||||
|
||||
/* Reset visibility, size and position of all columns */
|
||||
for (int colidx = 0; colidx < header()->count(); colidx++) {
|
||||
header()->showSection(colidx);
|
||||
header()->resizeSection(colidx, header()->defaultSectionSize());
|
||||
header()->moveSection(header()->visualIndex(colidx), colidx);
|
||||
}
|
||||
|
||||
/* Reenter current list mode (affects first column and sorting) */
|
||||
if (m_inEntryListMode) {
|
||||
switchToEntryListMode();
|
||||
}
|
||||
else {
|
||||
switchToGroupMode();
|
||||
}
|
||||
|
||||
/* Nicely fitting columns to window feels like a sane default */
|
||||
fitColumnsToWindow();
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ class Entry;
|
||||
class EntryModel;
|
||||
class Group;
|
||||
class SortFilterHideProxyModel;
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Add forward declaration for QActionGroup
|
||||
*/
|
||||
class QActionGroup;
|
||||
|
||||
class EntryView : public QTreeView
|
||||
{
|
||||
@ -59,10 +64,29 @@ private slots:
|
||||
void switchToEntryListMode();
|
||||
void switchToGroupMode();
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Slots for header context menu and actions
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Fonic <https://github.com/fonic>
|
||||
* Properties to store header context menu and actions
|
||||
*/
|
||||
QMenu* m_headerMenu;
|
||||
QAction* m_hideUsernamesAction;
|
||||
QAction* m_hidePasswordsAction;
|
||||
QActionGroup* m_columnActions;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYVIEW_H
|
||||
|
Loading…
Reference in New Issue
Block a user