Improve Qt Quick GUI after porting to Qt 6
* Avoid use of deprecated QML features * Fix problems when moving items by making the delegates own items * Improve code in models, especially functions for moving rows * Fix sizing of dialogs * Replace code relying on `bannerClicked` event which has been removed in KF6 * Use the native file dialog if supported (and otherwise not); this should give the desired behavior under each platform out of the box * Update versioning requirements in README
This commit is contained in:
parent
4bf6a91d72
commit
6972256727
|
@ -91,7 +91,7 @@ can be passed to CMake to influence the build.
|
|||
### Optional dependencies
|
||||
* When building any Qt GUI, the library qtutilities is required.
|
||||
* When building with Qt Widgets GUI support, the following Qt modules are required (version 5.6 or higher): core gui widgets
|
||||
* When building with support for the experimental Qt Quick GUI, the following Qt/KDE modules are required (version 5.12 or higher): core gui qml quick quickcontrols2 kirigami
|
||||
* When building with support for the experimental Qt Quick GUI, the following Qt/KDE modules are required (version 6.6 or higher): core gui qml quick quickcontrols2 kirigami
|
||||
|
||||
To specify the major Qt version to use, set `QT_PACKAGE_PREFIX` (e.g. add `-DQT_PACKAGE_PREFIX:STRING=Qt6`
|
||||
to the CMake arguments). There's also `KF_PACKAGE_PREFIX` for KDE dependencies. Note that the Qt Quick GUI
|
||||
|
|
|
@ -197,7 +197,7 @@ bool FieldModelRemoveRowsCommand::internalUndo()
|
|||
/*!
|
||||
* \brief Stores the entry path for the specified \a model and \a index in \a res.
|
||||
*/
|
||||
void indexToPath(EntryModel *model, const QModelIndex &index, list<string> &res)
|
||||
static void indexToPath(EntryModel *model, const QModelIndex &index, list<string> &res)
|
||||
{
|
||||
res.clear();
|
||||
if (Entry *entry = model->entry(index)) {
|
||||
|
@ -209,7 +209,7 @@ void indexToPath(EntryModel *model, const QModelIndex &index, list<string> &res)
|
|||
* \brief Fetches the entry for the specified \a model and \a path.
|
||||
* \remarks The \a path will be modified. To prevent this use entryFromPathCpy().
|
||||
*/
|
||||
Entry *entryFromPath(EntryModel *model, list<string> &path)
|
||||
static Entry *entryFromPath(EntryModel *model, list<string> &path)
|
||||
{
|
||||
if (NodeEntry *rootEntry = model->rootEntry()) {
|
||||
return rootEntry->entryByPath(path);
|
||||
|
@ -220,7 +220,7 @@ Entry *entryFromPath(EntryModel *model, list<string> &path)
|
|||
/*!
|
||||
* \brief Fetches the entry for the specified \a model and \a path.
|
||||
*/
|
||||
Entry *entryFromPathCpy(EntryModel *model, list<string> path)
|
||||
static Entry *entryFromPathCpy(EntryModel *model, list<string> path)
|
||||
{
|
||||
return entryFromPath(model, path);
|
||||
}
|
||||
|
|
|
@ -452,31 +452,39 @@ bool EntryModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int co
|
|||
if (undoStack()) {
|
||||
return push(make_unique<EntryModelMoveRowsCommand>(this, sourceParent, sourceRow, count, destinationParent, destinationChild));
|
||||
}
|
||||
#endif
|
||||
#if CPP_UTILITIES_DEBUG_BUILD
|
||||
std::cout << "sourceRow: " << sourceRow << endl;
|
||||
std::cout << "destinationChild: " << destinationChild << endl;
|
||||
#endif
|
||||
// check validation of specified arguments: source and destination parent entries need to be node entries
|
||||
if (sourceRow < 0 || count <= 0) {
|
||||
return false;
|
||||
}
|
||||
const auto *const srcParentEntry = entry(sourceParent);
|
||||
const auto *const destParentEntry = entry(sourceParent);
|
||||
const auto *const destParentEntry = entry(destinationParent);
|
||||
if (!srcParentEntry || !destParentEntry || srcParentEntry->type() != EntryType::Node || destParentEntry->type() != EntryType::Node) {
|
||||
return false;
|
||||
}
|
||||
// determine the source parent entry and dest parent entry as node entries
|
||||
auto *const srcParentNodeEntry = static_cast<NodeEntry *>(sourceParent.internalPointer());
|
||||
auto *const destParentNodeEntry = static_cast<NodeEntry *>(destinationParent.internalPointer());
|
||||
#if CPP_UTILITIES_DEBUG_BUILD
|
||||
cout << "destinationChild: " << destinationChild << endl;
|
||||
#endif
|
||||
// source rows must be within the valid range
|
||||
if (static_cast<size_t>(sourceRow + count) > srcParentNodeEntry->children().size()
|
||||
// if source and destination parent are the same the destination child mustn't be in the source range
|
||||
|| !(srcParentNodeEntry != destParentNodeEntry || (destinationChild < sourceRow || (sourceRow + count) < destinationChild))) {
|
||||
if (static_cast<std::size_t>(sourceRow + count) > srcParentNodeEntry->children().size()) {
|
||||
return false;
|
||||
}
|
||||
// if source and destination parent are the same the destination child mustn't be in the source range
|
||||
if (srcParentNodeEntry == destParentNodeEntry) {
|
||||
if (destinationChild == sourceRow) {
|
||||
return true;
|
||||
}
|
||||
if (!(destinationChild < sourceRow || (sourceRow + count) < destinationChild)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// do not move a row to one of its own children! -> check before
|
||||
for (int index = 0; index < count; ++index) {
|
||||
Entry *const toMove = srcParentNodeEntry->children()[static_cast<size_t>(sourceRow + index)];
|
||||
Entry *const toMove = srcParentNodeEntry->children()[static_cast<std::size_t>(sourceRow + index)];
|
||||
if (toMove->type() != EntryType::Node) {
|
||||
continue;
|
||||
}
|
||||
|
@ -485,9 +493,11 @@ bool EntryModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int co
|
|||
}
|
||||
}
|
||||
// actually perform the move operation
|
||||
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
|
||||
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) {
|
||||
return false;
|
||||
}
|
||||
for (int index = 0; index < count; ++index) {
|
||||
Entry *toMove = srcParentNodeEntry->children()[static_cast<size_t>(sourceRow + index)];
|
||||
Entry *toMove = srcParentNodeEntry->children()[static_cast<std::size_t>(sourceRow + index)];
|
||||
if (srcParentNodeEntry == destParentNodeEntry && sourceRow < destinationChild) {
|
||||
toMove->setParent(destParentNodeEntry, destinationChild + index - 1);
|
||||
} else {
|
||||
|
|
|
@ -233,17 +233,15 @@ bool FieldModel::setData(const QModelIndex &index, const QVariant &value, int ro
|
|||
switch (index.column()) {
|
||||
case 0:
|
||||
beginInsertRows(index.parent(), rowCount(), rowCount());
|
||||
m_fields->emplace_back(m_accountEntry);
|
||||
m_fields->back().setName(value.toString().toStdString());
|
||||
m_fields->emplace_back(m_accountEntry).setName(value.toString().toStdString());
|
||||
endInsertRows();
|
||||
roles << Qt::DisplayRole << Qt::EditRole << IsLastRow;
|
||||
roles << Qt::DisplayRole << Qt::EditRole << Key << IsLastRow;
|
||||
break;
|
||||
case 1:
|
||||
beginInsertRows(index.parent(), rowCount(), rowCount());
|
||||
m_fields->emplace_back(m_accountEntry);
|
||||
m_fields->back().setValue(value.toString().toStdString());
|
||||
m_fields->emplace_back(m_accountEntry).setValue(value.toString().toStdString());
|
||||
endInsertRows();
|
||||
roles << Qt::DisplayRole << Qt::EditRole << IsLastRow;
|
||||
roles << Qt::DisplayRole << Qt::EditRole << Value << IsLastRow;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
@ -335,25 +333,22 @@ bool FieldModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int co
|
|||
|
||||
// validate input parameter
|
||||
if (sourceParent.isValid() || destinationParent.isValid() || sourceRow < 0 || count <= 0 || destinationChild < 0
|
||||
|| static_cast<size_t>(sourceRow + count) > m_fields->size() || static_cast<size_t>(destinationChild) >= m_fields->size()
|
||||
|| static_cast<std::size_t>(sourceRow + count) > m_fields->size() || static_cast<std::size_t>(destinationChild) >= m_fields->size()
|
||||
|| (destinationChild >= sourceRow && destinationChild < (sourceRow + count))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// begin the move
|
||||
if (destinationChild > sourceRow) {
|
||||
// move rows down: the third param is still counted in the initial array!
|
||||
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild + count);
|
||||
} else {
|
||||
// move rows up
|
||||
beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild);
|
||||
// note: When moving rows down (destinationChild > sourceRow) the third param is still counted in the initial array!
|
||||
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild > sourceRow ? destinationChild + count : destinationChild)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reserve space for temporary copies (FIXME: possible to avoid this?)
|
||||
m_fields->reserve(m_fields->size() + static_cast<size_t>(count));
|
||||
vector<Io::Field> tmp(static_cast<size_t>(count));
|
||||
m_fields->reserve(m_fields->size() + static_cast<std::size_t>(count));
|
||||
auto tmp = vector<Io::Field>(static_cast<std::size_t>(count));
|
||||
// move rows to temporary array
|
||||
move(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count, tmp.begin());
|
||||
std::move(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count, tmp.begin());
|
||||
// erase slots of rows to be moved
|
||||
m_fields->erase(m_fields->begin() + sourceRow, m_fields->begin() + sourceRow + count);
|
||||
// insert rows again at their new position
|
||||
|
|
|
@ -7,8 +7,7 @@ BasicDialog {
|
|||
id: aboutDialog
|
||||
standardButtons: Controls.Dialog.Ok
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
width: aboutDialog.availableWidth
|
||||
|
||||
Image {
|
||||
|
|
|
@ -6,9 +6,7 @@ Controls.Dialog {
|
|||
modal: true
|
||||
focus: true
|
||||
parent: applicationWindow().overlay
|
||||
//anchors.centerIn: parent // enable if requiring at least Qt 5.12 instead of setting x and y manually
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.width, Kirigami.Units.gridUnit * 30)
|
||||
|
||||
function acceptOnReturn(event) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQml.Models 2.2
|
||||
import QtQuick.Controls 2.4 as Controls
|
||||
|
@ -64,8 +64,7 @@ Kirigami.ScrollablePage {
|
|||
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
|
||||
title: qsTr("Delete %1?").arg(entryDesc)
|
||||
onAccepted: entryModel.removeRows(this.entryIndex, 1, rootIndex)
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
Controls.Label {
|
||||
text: " "
|
||||
}
|
||||
|
@ -109,13 +108,12 @@ Kirigami.ScrollablePage {
|
|||
entryModel.removeRows(this.entryIndex, 1, rootIndex)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
Controls.TextField {
|
||||
id: entryNameTextField
|
||||
Layout.preferredWidth: renameDialog.availableWidth
|
||||
placeholderText: qsTr("enter new name here")
|
||||
Keys.onPressed: renameDialog.acceptOnReturn(event)
|
||||
Keys.onPressed: (event) => renameDialog.acceptOnReturn(event)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,115 +136,6 @@ Kirigami.ScrollablePage {
|
|||
}
|
||||
}
|
||||
|
||||
// component representing an entry
|
||||
Component {
|
||||
id: listDelegateComponent
|
||||
|
||||
Kirigami.SwipeListItem {
|
||||
id: listItem
|
||||
contentItem: RowLayout {
|
||||
Kirigami.ListItemDragHandle {
|
||||
listItem: listItem
|
||||
listView: entriesListView
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
// FIXME: not sure why newIndex + 1 is required to be able to move a row at the end
|
||||
onMoveRequested: entryModel.moveRows(
|
||||
rootIndex, oldIndex, 1, rootIndex,
|
||||
oldIndex < newIndex ? newIndex + 1 : newIndex)
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Kirigami.Icon {
|
||||
width: Kirigami.Units.iconSizes.smallMedium
|
||||
height: Kirigami.Units.iconSizes.smallMedium
|
||||
Layout.fillHeight: true
|
||||
source: delegateModel.isNode(
|
||||
index) ? "folder-symbolic" : "story-editor"
|
||||
}
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: model.name
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
entryContextMenu.popup()
|
||||
return
|
||||
}
|
||||
delegateModel.handleEntryClicked(index, model.name)
|
||||
}
|
||||
onPressAndHold: entryContextMenu.popup()
|
||||
}
|
||||
Controls.Menu {
|
||||
id: entryContextMenu
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-cut"
|
||||
text: qsTr("Cut")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: {
|
||||
nativeInterface.cutEntry(
|
||||
entryModel.index(index, 0,
|
||||
rootIndex))
|
||||
showPassiveNotification(qsTr("Cut %1").arg(
|
||||
model.name))
|
||||
}
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: confirmDeletionDialog.confirmDeletion(
|
||||
model.name, index)
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-rename"
|
||||
text: qsTr("Rename")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: renameDialog.renameEntry(model.name,
|
||||
index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-cut"
|
||||
text: qsTr("Cut")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: {
|
||||
nativeInterface.cutEntry(entryModel.index(index, 0,
|
||||
rootIndex))
|
||||
showPassiveNotification(text + " " + model.name)
|
||||
}
|
||||
shortcut: StandardKey.Cut
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: confirmDeletionDialog.confirmDeletion(
|
||||
model.name, index)
|
||||
shortcut: StandardKey.Delete
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-rename"
|
||||
text: qsTr("Rename")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: renameDialog.renameEntry(model.name, index)
|
||||
shortcut: "F2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// list view to display one hierarchy level of entry model
|
||||
ListView {
|
||||
id: entriesListView
|
||||
|
@ -257,9 +146,17 @@ Kirigami.ScrollablePage {
|
|||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
reuseItems: true
|
||||
model: DelegateModel {
|
||||
id: delegateModel
|
||||
delegate: listDelegateComponent
|
||||
delegate: EntryDelegate {
|
||||
width: entriesListView.width
|
||||
view: entriesListView
|
||||
onMoveRequested:
|
||||
(oldIndex, newIndex) => {
|
||||
entryModel.moveRows(rootIndex, oldIndex, 1, rootIndex, oldIndex < newIndex ? newIndex + 1 : newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
function isNode(rowNumber) {
|
||||
return entryModel.isNode(entryModel.index(rowNumber, 0,
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQml.Models 2.2
|
||||
import QtQuick.Controls 2.4 as Controls
|
||||
import org.kde.kirigami 2.5 as Kirigami
|
||||
|
||||
Item {
|
||||
id: delegate
|
||||
|
||||
property ListView view
|
||||
implicitHeight : listItem.implicitHeight
|
||||
|
||||
signal moveRequested(int oldIndex, int newIndex)
|
||||
|
||||
Kirigami.SwipeListItem {
|
||||
id: listItem
|
||||
contentItem: RowLayout {
|
||||
Kirigami.ListItemDragHandle {
|
||||
listItem: listItem
|
||||
listView: view
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onMoveRequested:
|
||||
(oldIndex, newIndex) => delegate.moveRequested(oldIndex, newIndex)
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Kirigami.Icon {
|
||||
width: Kirigami.Units.iconSizes.smallMedium
|
||||
height: Kirigami.Units.iconSizes.smallMedium
|
||||
Layout.fillHeight: true
|
||||
source: delegateModel.isNode(
|
||||
index) ? "folder-symbolic" : "story-editor"
|
||||
}
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: model.name
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
entryContextMenu.popup()
|
||||
return
|
||||
}
|
||||
delegateModel.handleEntryClicked(index, model.name)
|
||||
}
|
||||
onPressAndHold: entryContextMenu.popup()
|
||||
}
|
||||
Controls.Menu {
|
||||
id: entryContextMenu
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-cut"
|
||||
text: qsTr("Cut")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: {
|
||||
nativeInterface.cutEntry(
|
||||
entryModel.index(index, 0,
|
||||
rootIndex))
|
||||
showPassiveNotification(qsTr("Cut %1").arg(
|
||||
model.name))
|
||||
}
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: confirmDeletionDialog.confirmDeletion(
|
||||
model.name, index)
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-rename"
|
||||
text: qsTr("Rename")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: renameDialog.renameEntry(model.name,
|
||||
index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-cut"
|
||||
text: qsTr("Cut")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: {
|
||||
nativeInterface.cutEntry(entryModel.index(index, 0,
|
||||
rootIndex))
|
||||
showPassiveNotification(text + " " + model.name)
|
||||
}
|
||||
shortcut: StandardKey.Cut
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: confirmDeletionDialog.confirmDeletion(
|
||||
model.name, index)
|
||||
shortcut: StandardKey.Delete
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-rename"
|
||||
text: qsTr("Rename")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: renameDialog.renameEntry(model.name, index)
|
||||
shortcut: "F2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQml.Models 2.2
|
||||
import QtQuick.Controls 2.4 as Controls
|
||||
import org.kde.kirigami 2.5 as Kirigami
|
||||
|
||||
Item {
|
||||
id: delegate
|
||||
|
||||
property ListView view
|
||||
implicitHeight : listItem.implicitHeight
|
||||
|
||||
signal moveRequested(int oldIndex, int newIndex)
|
||||
|
||||
Kirigami.SwipeListItem {
|
||||
id: listItem
|
||||
visible: !model.isLastRow
|
||||
contentItem: RowLayout {
|
||||
id: fieldRow
|
||||
Kirigami.ListItemDragHandle {
|
||||
listItem: listItem
|
||||
listView: view
|
||||
onMoveRequested: (oldIndex, newIndex) => delegate.moveRequested(oldIndex, newIndex)
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: {
|
||||
let pieces = []
|
||||
if (model.key) {
|
||||
pieces.push(model.key)
|
||||
}
|
||||
if (model.value) {
|
||||
pieces.push(model.value)
|
||||
}
|
||||
return pieces.join(": ")
|
||||
}
|
||||
color: palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
return fieldContextMenu.popup()
|
||||
}
|
||||
fieldDialog.init(model, index)
|
||||
fieldDialog.open()
|
||||
}
|
||||
onPressAndHold: fieldContextMenu.popup()
|
||||
}
|
||||
Controls.Menu {
|
||||
id: fieldContextMenu
|
||||
Controls.MenuItem {
|
||||
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
|
||||
text: model.isPassword ? qsTr("Mark as normal field") : qsTr(
|
||||
"Mark as password field")
|
||||
onClicked: view.model.setData(
|
||||
view.model.index(index,
|
||||
0),
|
||||
model.isPassword ? 0 : 1, 0x0100 + 1)
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-copy"
|
||||
text: model.isPassword ? qsTr("Copy password") : qsTr(
|
||||
"Copy value")
|
||||
onClicked: showPassiveNotification(
|
||||
nativeInterface.copyToClipboard(
|
||||
model.actualValue) ? qsTr("Copied") : qsTr(
|
||||
"Unable to access clipboard"))
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete field")
|
||||
onClicked: view.model.removeRows(index, 1)
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Insert empty field after this")
|
||||
onClicked: view.model.insertRows(
|
||||
index + 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
|
||||
text: model.isPassword ? qsTr(
|
||||
"Mark as normal field") : qsTr(
|
||||
"Mark as password field")
|
||||
onTriggered: view.model.setData(
|
||||
view.model.index(index, 0),
|
||||
model.isPassword ? 0 : 1, 0x0100 + 1)
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-copy"
|
||||
text: model.isPassword ? qsTr("Copy password") : qsTr(
|
||||
"Copy value")
|
||||
onTriggered: showPassiveNotification(
|
||||
nativeInterface.copyToClipboard(
|
||||
model.actualValue) ? qsTr("Copied") : qsTr(
|
||||
"Unable to access clipboard"))
|
||||
shortcut: StandardKey.Cut
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete field")
|
||||
onTriggered: view.model.removeRows(index, 1)
|
||||
shortcut: StandardKey.Delete
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Insert empty field after this")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: view.model.insertRows(index + 1, 1)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQml.Models 2.2
|
||||
import QtQuick.Controls 2.4 as Controls
|
||||
|
@ -10,6 +10,21 @@ Kirigami.ScrollablePage {
|
|||
|
||||
Layout.fillWidth: true
|
||||
title: nativeInterface.currentAccountName
|
||||
actions:[
|
||||
Kirigami.Action {
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Add field")
|
||||
visible: !nativeInterface.hasEntryFilter
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: {
|
||||
const delegateModel = fieldsListView.model
|
||||
const row = delegateModel.rowCount() - 1
|
||||
fieldDialog.init(delegateModel, row)
|
||||
fieldDialog.open()
|
||||
}
|
||||
shortcut: "Ctrl+Shift+A"
|
||||
}
|
||||
]
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
@ -32,8 +47,7 @@ Kirigami.ScrollablePage {
|
|||
fieldsListView.model.setData(column0, isPassword ? 1 : 0,
|
||||
0x0100 + 1)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
GridLayout {
|
||||
Layout.preferredWidth: fieldDialog.availableWidth
|
||||
columns: 2
|
||||
|
@ -43,7 +57,7 @@ Kirigami.ScrollablePage {
|
|||
id: fieldNameEdit
|
||||
Layout.fillWidth: true
|
||||
text: fieldDialog.fieldName
|
||||
Keys.onPressed: fieldDialog.acceptOnReturn(event)
|
||||
Keys.onPressed: (event) => fieldDialog.acceptOnReturn(event)
|
||||
}
|
||||
Controls.RoundButton {
|
||||
flat: true
|
||||
|
@ -69,7 +83,7 @@ Kirigami.ScrollablePage {
|
|||
// fix ugly bullet points under Android
|
||||
font.pointSize: hideCharacters ? fieldNameEdit.font.pointSize
|
||||
* 0.5 : fieldNameEdit.font.pointSize
|
||||
Keys.onPressed: fieldDialog.acceptOnReturn(event)
|
||||
Keys.onPressed: (event) => fieldDialog.acceptOnReturn(event)
|
||||
}
|
||||
Controls.RoundButton {
|
||||
flat: true
|
||||
|
@ -107,166 +121,27 @@ Kirigami.ScrollablePage {
|
|||
}
|
||||
}
|
||||
|
||||
// component representing a field
|
||||
Component {
|
||||
id: fieldsListDelegateComponent
|
||||
|
||||
Kirigami.SwipeListItem {
|
||||
id: fieldsListItem
|
||||
contentItem: RowLayout {
|
||||
id: fieldRow
|
||||
readonly property bool isLast: model.isLastRow
|
||||
|
||||
Kirigami.ListItemDragHandle {
|
||||
listItem: fieldsListItem
|
||||
listView: fieldsListView
|
||||
onMoveRequested: fieldsListView.model.moveRows(
|
||||
fieldsListView.model.index(-1, 0),
|
||||
oldIndex, 1,
|
||||
fieldsListView.model.index(-1, 0),
|
||||
newIndex)
|
||||
visible: !fieldRow.isLast
|
||||
}
|
||||
Kirigami.Icon {
|
||||
width: Kirigami.Units.iconSizes.smallMedium
|
||||
height: width
|
||||
source: "list-add"
|
||||
opacity: 0.6
|
||||
visible: fieldRow.isLast
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
fieldDialog.init(model, index)
|
||||
fieldDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: {
|
||||
if (fieldRow.isLast) {
|
||||
return qsTr("click to append new field")
|
||||
}
|
||||
|
||||
var pieces = []
|
||||
if (model.key) {
|
||||
pieces.push(model.key)
|
||||
}
|
||||
if (model.value) {
|
||||
pieces.push(model.value)
|
||||
}
|
||||
return pieces.join(": ")
|
||||
}
|
||||
color: fieldRow.isLast ? "gray" : palette.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (!fieldRow.isLast) {
|
||||
fieldContextMenu.popup()
|
||||
}
|
||||
return
|
||||
}
|
||||
fieldDialog.init(model, index)
|
||||
fieldDialog.open()
|
||||
}
|
||||
onPressAndHold: fieldContextMenu.popup()
|
||||
}
|
||||
Controls.Menu {
|
||||
id: fieldContextMenu
|
||||
Controls.MenuItem {
|
||||
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
|
||||
text: model.isPassword ? qsTr("Mark as normal field") : qsTr(
|
||||
"Mark as password field")
|
||||
onClicked: fieldsListView.model.setData(
|
||||
fieldsListView.model.index(index,
|
||||
0),
|
||||
model.isPassword ? 0 : 1, 0x0100 + 1)
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-copy"
|
||||
text: model.isPassword ? qsTr("Copy password") : qsTr(
|
||||
"Copy value")
|
||||
onClicked: showPassiveNotification(
|
||||
nativeInterface.copyToClipboard(
|
||||
model.actualValue) ? qsTr("Copied") : qsTr(
|
||||
"Unable to access clipboard"))
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete field")
|
||||
onClicked: fieldsListView.model.removeRows(index, 1)
|
||||
}
|
||||
Controls.MenuItem {
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Insert empty field after this")
|
||||
onClicked: fieldsListView.model.insertRows(
|
||||
index + 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: !model.isPassword ? "password-show-off" : "password-show-on"
|
||||
text: model.isPassword ? qsTr(
|
||||
"Mark as normal field") : qsTr(
|
||||
"Mark as password field")
|
||||
onTriggered: fieldsListView.model.setData(
|
||||
fieldsListView.model.index(index, 0),
|
||||
model.isPassword ? 0 : 1, 0x0100 + 1)
|
||||
visible: !fieldRow.isLast
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-copy"
|
||||
text: model.isPassword ? qsTr("Copy password") : qsTr(
|
||||
"Copy value")
|
||||
onTriggered: showPassiveNotification(
|
||||
nativeInterface.copyToClipboard(
|
||||
model.actualValue) ? qsTr("Copied") : qsTr(
|
||||
"Unable to access clipboard"))
|
||||
shortcut: StandardKey.Cut
|
||||
visible: !fieldRow.isLast
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "edit-delete"
|
||||
text: qsTr("Delete field")
|
||||
onTriggered: fieldsListView.model.removeRows(index, 1)
|
||||
shortcut: StandardKey.Delete
|
||||
visible: !fieldRow.isLast
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "list-add"
|
||||
text: qsTr("Insert empty field after this")
|
||||
enabled: !nativeInterface.hasEntryFilter
|
||||
onTriggered: fieldsListView.model.insertRows(index + 1, 1)
|
||||
visible: !fieldRow.isLast
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// list view to edit the currently selected account
|
||||
ListView {
|
||||
id: fieldsListView
|
||||
implicitWidth: Kirigami.Units.gridUnit * 30
|
||||
model: nativeInterface.fieldModel
|
||||
reuseItems: true
|
||||
moveDisplaced: Transition {
|
||||
YAnimator {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
delegate: fieldsListDelegateComponent
|
||||
delegate: FieldsDelegate {
|
||||
width: fieldsListView.width
|
||||
view: fieldsListView
|
||||
onMoveRequested:
|
||||
(oldIndex, newIndex) => {
|
||||
const model = fieldsListView.model
|
||||
const invalidIndex = model.index(-1, 0)
|
||||
model.moveRows(invalidIndex, oldIndex, 1, invalidIndex, newIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,7 @@ BasicDialog {
|
|||
qsTr("You aborted. The password has not been altered."))
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
Controls.Label {
|
||||
id: instructionLabel
|
||||
Layout.preferredWidth: passwordDialog.availableWidth
|
||||
|
@ -42,19 +41,21 @@ BasicDialog {
|
|||
id: passwordTextField
|
||||
Layout.preferredWidth: passwordDialog.availableWidth
|
||||
echoMode: showCharactersCheckBox.checked ? TextInput.Normal : TextInput.Password
|
||||
placeholderText: qsTr("enter password here, leave empty for no encryption")
|
||||
placeholderText: newPassword
|
||||
? qsTr("enter password here, leave empty for no encryption")
|
||||
: qsTr("enter password here")
|
||||
color: "#101010"
|
||||
placeholderTextColor: "#505050"
|
||||
background: Rectangle {
|
||||
border.color: "#5d5e6d"
|
||||
}
|
||||
Keys.onPressed: passwordDialog.acceptOnReturn(event)
|
||||
Keys.onPressed: (event) => passwordDialog.acceptOnReturn(event)
|
||||
}
|
||||
Controls.TextField {
|
||||
id: repeatPasswordTextField
|
||||
Layout.preferredWidth: passwordDialog.availableWidth
|
||||
visible: passwordDialog.newPassword
|
||||
&& !showCharactersCheckBox.checked
|
||||
enabled: visible && !showCharactersCheckBox.checked
|
||||
echoMode: TextInput.Password
|
||||
placeholderText: qsTr("repeat password")
|
||||
color: "#101010"
|
||||
|
|
51
qml/main.qml
51
qml/main.qml
|
@ -16,11 +16,6 @@ Kirigami.ApplicationWindow {
|
|||
|
||||
title: app.applicationName
|
||||
titleIcon: "qrc://icons/hicolor/scalable/apps/passwordmanager.svg"
|
||||
// FIXME: not sure why this doesn't work anymore
|
||||
//onBannerClicked: () => {
|
||||
// leftMenu.resetMenu()
|
||||
// aboutDialog.open()
|
||||
//}
|
||||
|
||||
visible: true
|
||||
resetMenuOnTriggered: false
|
||||
|
@ -208,15 +203,20 @@ Kirigami.ApplicationWindow {
|
|||
icon.name: "document-close"
|
||||
shortcut: StandardKey.Close
|
||||
onTriggered: nativeInterface.close()
|
||||
},
|
||||
Kirigami.Action {
|
||||
separator: true
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: qsTr("About")
|
||||
icon.name: "help-about"
|
||||
shortcut: "Ctrl+?"
|
||||
onTriggered: {
|
||||
leftMenu.resetMenu()
|
||||
aboutDialog.open()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Controls.Switch {
|
||||
text: qsTr("Use native file dialog")
|
||||
checked: nativeInterface.useNativeFileDialog
|
||||
visible: nativeInterface.supportsNativeFileDialog
|
||||
onCheckedChanged: nativeInterface.useNativeFileDialog = checked
|
||||
}
|
||||
}
|
||||
contextDrawer: Kirigami.ContextDrawer {
|
||||
id: contextDrawer
|
||||
|
@ -241,9 +241,9 @@ Kirigami.ApplicationWindow {
|
|||
id: fileSummaryDialog
|
||||
standardButtons: Controls.Dialog.Ok
|
||||
title: qsTr("File details")
|
||||
|
||||
Controls.Label {
|
||||
contentItem: Controls.TextArea {
|
||||
id: fileSummaryLabel
|
||||
readOnly: true
|
||||
text: "No file summary available"
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.Wrap
|
||||
|
@ -289,8 +289,7 @@ Kirigami.ApplicationWindow {
|
|||
Controls.DialogButtonBox.buttonRole: Controls.DialogButtonBox.RejectRole
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
Controls.TextField {
|
||||
id: filterDialogTextField
|
||||
Layout.preferredWidth: filterDialog.availableWidth
|
||||
|
@ -301,12 +300,12 @@ Kirigami.ApplicationWindow {
|
|||
|
||||
Connections {
|
||||
target: nativeInterface
|
||||
onEntryFilterChanged: {
|
||||
function onEntryFilterChanged(newFilter) {
|
||||
if (filterTextField.text !== newFilter) {
|
||||
filterTextField.text = newFilter
|
||||
}
|
||||
}
|
||||
onFileError: {
|
||||
function onFileError(errorMessage, retryAction) {
|
||||
var retryMethod = null
|
||||
if (retryAction === "load" || retryAction === "save") {
|
||||
retryMethod = retryAction
|
||||
|
@ -320,16 +319,16 @@ Kirigami.ApplicationWindow {
|
|||
})
|
||||
}
|
||||
}
|
||||
onSettingsError: {
|
||||
function onSettingsError(errorMessage) {
|
||||
showPassiveNotification(errorMessage)
|
||||
}
|
||||
onPasswordRequired: {
|
||||
function onPasswordRequired(filePath) {
|
||||
enterPasswordDialog.askForExistingPassword(
|
||||
qsTr("Password required to open %1").arg(
|
||||
nativeInterface.filePath))
|
||||
leftMenu.resetMenu()
|
||||
}
|
||||
onFileOpenChanged: {
|
||||
function onFileOpenChanged(fileOpen) {
|
||||
clearStack()
|
||||
if (!nativeInterface.fileOpen) {
|
||||
showPassiveNotification(qsTr("%1 closed").arg(
|
||||
|
@ -341,20 +340,20 @@ Kirigami.ApplicationWindow {
|
|||
nativeInterface.fileName))
|
||||
leftMenu.close()
|
||||
}
|
||||
onFileSaved: {
|
||||
function onFileSaved() {
|
||||
showPassiveNotification(qsTr("%1 saved").arg(
|
||||
nativeInterface.fileName))
|
||||
}
|
||||
onNewNotification: {
|
||||
function onNewNotification(message) {
|
||||
showPassiveNotification(message)
|
||||
}
|
||||
onCurrentAccountChanged: {
|
||||
function onCurrentAccountChanged() {
|
||||
// remove the fields page if the current account has been removed
|
||||
if (!nativeInterface.hasCurrentAccount) {
|
||||
pageStack.pop(lastEntriesPage)
|
||||
}
|
||||
}
|
||||
onEntryAboutToBeRemoved: {
|
||||
function onEntryAboutToBeRemoved(removedIndex) {
|
||||
// get the filter entry index
|
||||
if (nativeInterface.hasEntryFilter) {
|
||||
removedIndex = nativeInterface.filterEntryIndex(removedIndex)
|
||||
|
@ -372,7 +371,7 @@ Kirigami.ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
onHasEntryFilterChanged: {
|
||||
function onHasEntryFilterChanged(hasEntryFilter) {
|
||||
if (nativeInterface.fileOpen) {
|
||||
pageStack.clear()
|
||||
initStack()
|
||||
|
|
|
@ -43,7 +43,7 @@ Controller::Controller(QSettings &settings, const QString &filePath, QObject *pa
|
|||
#endif
|
||||
, m_fileOpen(false)
|
||||
, m_fileModified(false)
|
||||
, m_useNativeFileDialog(false)
|
||||
, m_useNativeFileDialog(supportsNativeFileDialog())
|
||||
, m_filterAsDialog(
|
||||
#ifdef Q_OS_ANDROID
|
||||
true
|
||||
|
|
|
@ -85,8 +85,8 @@ int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, c
|
|||
LOAD_QT_TRANSLATIONS;
|
||||
|
||||
// init Quick GUI
|
||||
auto engine = QQmlApplicationEngine();
|
||||
auto controller = Controller(*settings, file);
|
||||
auto engine = QQmlApplicationEngine();
|
||||
#ifdef Q_OS_ANDROID
|
||||
registerControllerForAndroid(&controller);
|
||||
#endif
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
<file alias="AboutDialog.qml">../qml/AboutDialog.qml</file>
|
||||
<file alias="PasswordDialog.qml">../qml/PasswordDialog.qml</file>
|
||||
<file alias="EntriesPage.qml">../qml/EntriesPage.qml</file>
|
||||
<file alias="EntryDelegate.qml">../qml/EntryDelegate.qml</file>
|
||||
<file alias="FieldsPage.qml">../qml/FieldsPage.qml</file>
|
||||
<file alias="FieldsDelegate.qml">../qml/FieldsDelegate.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Loading…
Reference in New Issue