Compare commits

...

3 Commits

Author SHA1 Message Date
jordi fita mas 392d993c8a Add MnemonicLabel and MnemonicAction components
Looks like i am going to do many Labels and Actions that require
mnemonics, and i do not want to setup the properties everytime.
2024-12-21 05:13:46 +01:00
jordi fita mas 268f4329c0 Add Mnemonic attached property
This is to accept Alt+(whatever has & in front in the label) for labels,
buttons, actions, and whatever requires a nmenonic.

I created an attached property because it is kind of similar to QML’s
Keys property.
2024-12-21 04:56:09 +01:00
jordi fita mas ca882f992b Do not keep a copy of database’s connection name
Since i only plan to use a single connection, i can use QtSql’s default
connection name.

According to the documentation, i have to make sure that no query or
database object is open when i remove the database.  I now use C++
scopes for that, but i need to have a QString declared outside of it to
get the default connection’s name.
2024-12-20 21:29:46 +01:00
9 changed files with 278 additions and 42 deletions

View File

@ -5,13 +5,18 @@ qt_add_executable(${PROJECT_NAME}
qt_add_qml_module(${PROJECT_NAME} qt_add_qml_module(${PROJECT_NAME}
URI Camper URI Camper
VERSION 1.0 VERSION 1.0
DEPENDENCIES QtCore DEPENDENCIES
QtCore
QtQuick
SOURCES SOURCES
database.cpp database.h database.cpp database.h
mnemonicattached.cpp mnemonicattached.h
QML_FILES QML_FILES
ErrorNotification.qml ErrorNotification.qml
LoginPage.qml LoginPage.qml
Main.qml Main.qml
MnemonicAction.qml
MnemonicLabel.qml
ReservationsPage.qml ReservationsPage.qml
SelectableLabel.qml SelectableLabel.qml
) )

View File

@ -9,36 +9,26 @@ Page {
title: qsTr("Login") title: qsTr("Login")
ColumnLayout { ColumnLayout {
Label { MnemonicLabel {
text: qsTr("&User:") buddy: user
mnemonic: qsTr("&User:")
} }
TextField { TextField {
id: user id: user
focus: true focus: true
validator: RegularExpressionValidator {
regularExpression: /[^s].*/
} }
onAccepted: function () { MnemonicLabel {
loginAction.trigger(); buddy: password
} mnemonic: qsTr("&Password:")
}
Label {
text: qsTr("&Password:")
} }
TextField { TextField {
id: password id: password
echoMode: TextInput.Password echoMode: TextInput.Password
onAccepted: function () {
loginAction.trigger();
}
} }
Button { Button {
@ -46,11 +36,10 @@ Page {
} }
} }
Action { MnemonicAction {
id: loginAction id: loginAction
enabled: user.acceptableInput mnemonic: qsTr("Log &in")
text: "&Login"
onTriggered: function () { onTriggered: function () {
Database.open(user.text, password.text); Database.open(user.text, password.text);

12
src/MnemonicAction.qml Normal file
View File

@ -0,0 +1,12 @@
import QtQuick
import QtQuick.Controls
Action {
id: control
required property string mnemonic
Mnemonic.label: mnemonic
shortcut: Mnemonic.sequence
text: Mnemonic.richTextLabel
}

21
src/MnemonicLabel.qml Normal file
View File

@ -0,0 +1,21 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
Label {
id: control
required property Item buddy
required property string mnemonic
Mnemonic.label: control.mnemonic
text: Mnemonic.richTextLabel
Shortcut {
sequence: control.Mnemonic.sequence
onActivated: function () {
control.buddy.forceActiveFocus();
}
}
}

View File

@ -10,13 +10,19 @@ Page {
anchors.fill: parent anchors.fill: parent
ToolButton { ToolButton {
icon.name: "system-log-out" action: logoutAction
text: qsTr("&Log out") }
}
}
onClicked: function () { MnemonicAction {
id: logoutAction
icon.name: "system-log-out"
mnemonic: qsTr("Log &out")
onTriggered: function () {
Database.close(); Database.close();
} }
} }
} }
}
}

View File

@ -6,7 +6,6 @@
Database::Database(QObject *parent) Database::Database(QObject *parent)
: QObject{parent} : QObject{parent}
, m_pool{} , m_pool{}
, m_connectionName{"main"}
{ {
m_pool.setMaxThreadCount(1); m_pool.setMaxThreadCount(1);
m_pool.setExpiryTimeout(-1); m_pool.setExpiryTimeout(-1);
@ -15,28 +14,36 @@ Database::Database(QObject *parent)
QFuture<void> Database::open(const QString &user, const QString &password) QFuture<void> Database::open(const QString &user, const QString &password)
{ {
return QtConcurrent::run(&m_pool, [this, user, password]() { return QtConcurrent::run(&m_pool, [this, user, password]() {
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL", m_connectionName); QString errorMessage;
QString connectionName;
{
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
db.setConnectOptions("service=camper; options=-csearch_path=camper,public"); db.setConnectOptions("service=camper; options=-csearch_path=camper,public");
if (db.open(user, password)) { if (db.open(user, password)) {
emit opened(); emit opened();
} else { return;
const QString errorMessage(db.lastError().text());
db = QSqlDatabase(); // Otherwise removeDatabase complains is still being used.
QSqlDatabase::removeDatabase(m_connectionName);
emit errorOcurred(errorMessage);
} }
errorMessage = db.lastError().text();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
emit errorOcurred(errorMessage);
}); });
} }
QFuture<void> Database::close() QFuture<void> Database::close()
{ {
return QtConcurrent::run(&m_pool, [this]() { return QtConcurrent::run(&m_pool, [this]() {
QSqlDatabase db = QSqlDatabase::database(m_connectionName); QString connectionName;
{
QSqlDatabase db = QSqlDatabase::database();
if (!db.isValid()) { if (!db.isValid()) {
return; return;
} }
connectionName = db.connectionName();
db.close(); db.close();
QSqlDatabase::removeDatabase(m_connectionName); }
QSqlDatabase::removeDatabase(connectionName);
emit closed(); emit closed();
}); });
} }

View File

@ -25,7 +25,6 @@ signals:
private: private:
QThreadPool m_pool; QThreadPool m_pool;
QString m_connectionName;
}; };
#endif // DATABASE_H #endif // DATABASE_H

147
src/mnemonicattached.cpp Normal file
View File

@ -0,0 +1,147 @@
#include "mnemonicattached.h"
#include <QEvent>
#include <QGuiApplication>
#include <QKeyEvent>
using namespace Qt::Literals::StringLiterals;
class MnemonicEventFilter : public QObject
{
Q_OBJECT
public:
static MnemonicEventFilter &instance()
{
static MnemonicEventFilter s_instance;
return s_instance;
}
bool isAltPressed() const { return m_altPressed; }
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched);
if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Alt) {
m_altPressed = true;
emit altPressed();
}
} else if (event->type() == QEvent::KeyRelease) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Alt) {
m_altPressed = false;
emit altReleased();
}
} else if (event->type() == QEvent::ApplicationStateChange) {
if (m_altPressed) {
m_altPressed = false;
emit altReleased();
}
}
return false;
}
Q_SIGNALS:
void altPressed();
void altReleased();
private:
MnemonicEventFilter()
: QObject(nullptr)
{
qGuiApp->installEventFilter(this);
}
bool m_altPressed = false;
};
MnemonicAttached::MnemonicAttached(QObject *parent)
: QObject{parent}
, m_label{}
, m_richTextLabel{}
, m_active{MnemonicEventFilter::instance().isAltPressed()}
{
connect(&MnemonicEventFilter::instance(),
&MnemonicEventFilter::altPressed,
this,
&MnemonicAttached::onAltPressed);
connect(&MnemonicEventFilter::instance(),
&MnemonicEventFilter::altReleased,
this,
&MnemonicAttached::onAltReleased);
}
MnemonicAttached *MnemonicAttached::qmlAttachedProperties(QObject *object)
{
return new MnemonicAttached(object);
}
QString MnemonicAttached::label() const
{
return m_label;
}
void MnemonicAttached::setLabel(const QString &label)
{
if (m_label == label) {
return;
}
m_label = label;
emit labelChanged();
emit sequenceChanged();
updateRichText();
}
QString MnemonicAttached::richTextLabel() const
{
return m_richTextLabel;
}
QKeySequence MnemonicAttached::sequence() const
{
return QKeySequence::mnemonic(m_label);
}
void MnemonicAttached::onAltPressed()
{
if (m_active) {
return;
}
m_active = true;
updateRichText();
}
void MnemonicAttached::onAltReleased()
{
if (!m_active) {
return;
}
m_active = false;
updateRichText();
}
void MnemonicAttached::updateRichText()
{
QString richTextLabel;
qsizetype pos = m_label.indexOf('&'_L1);
if (pos < 0 || pos + 1 == m_label.length()) {
richTextLabel = m_label;
} else if (m_active) {
richTextLabel = m_label.left(pos) + "<u>"_L1 + m_label.at(pos + 1) + "</u>"
+ m_label.mid(pos + 2);
} else {
richTextLabel = m_label.left(pos) + m_label.mid(pos + 1);
}
if (m_richTextLabel == richTextLabel) {
return;
}
m_richTextLabel = richTextLabel;
emit richTextLabelChanged();
}
#include "mnemonicattached.moc"
#include "moc_mnemonicattached.cpp"

50
src/mnemonicattached.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef MNEMONICATTACHED_H
#define MNEMONICATTACHED_H
#include <QKeySequence>
#include <QObject>
#include <QString>
#include <QtQmlIntegration>
class MnemonicAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged FINAL)
Q_PROPERTY(QString richTextLabel READ richTextLabel NOTIFY richTextLabelChanged FINAL)
Q_PROPERTY(QKeySequence sequence READ sequence NOTIFY sequenceChanged FINAL)
QML_NAMED_ELEMENT(Mnemonic)
QML_UNCREATABLE("Mnemonic is only available via attached properties")
QML_ATTACHED(MnemonicAttached)
public:
explicit MnemonicAttached(QObject *parent = nullptr);
static MnemonicAttached *qmlAttachedProperties(QObject *object);
QString label() const;
void setLabel(const QString &label);
QString richTextLabel() const;
QKeySequence sequence() const;
signals:
void enabledChanged();
void labelChanged();
void richTextLabelChanged();
void sequenceChanged();
private slots:
void onAltPressed();
void onAltReleased();
private:
void updateRichText();
QString m_label;
QString m_richTextLabel;
bool m_active;
};
#endif // MNEMONICATTACHED_H