mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-03 01:04:13 +02:00
e03e523069
Every .js file in the "scripts" folder of the user's data folder that starts with a // ==QETScript== header becomes a command: in Projet > Scripts, as a button on a new Scripts toolbar, and, because it is registered with ShortcutManager as diagrameditor.script.<file name>, in the shortcut settings, the shortcut bar (S) and command search. The header gives its name, icon (a file next to the script or a theme icon; a tile with its initials otherwise), tooltip, default shortcut and when it is enabled (always, with a selection, with a conductor selected). The folder is watched, so a script added, edited or deleted while QET is open appears, changes or goes without a restart. A file with a header that cannot be used gets no button; the Scripts menu lists it with the reason. The menu also opens the folder, and holds "Exécuter un script…". A click runs the script on the current project as one undo step named after it, and asks to switch scripting on first, like "Exécuter un script…" does: scripting stays off by default. ShortcutManager::unregisterAction() takes a command out of the lists when its script is deleted, and lets it come back under a new name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
260 lines
7.4 KiB
C++
260 lines
7.4 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "shortcutmanager.h"
|
|
|
|
#include <QAbstractButton>
|
|
#include <QAction>
|
|
#include <QObject>
|
|
#include <QSettings>
|
|
#include <QVariant>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
const QString SETTINGS_GROUP = QStringLiteral("shortcuts/");
|
|
}
|
|
|
|
ShortcutManager &ShortcutManager::instance()
|
|
{
|
|
static ShortcutManager manager;
|
|
return manager;
|
|
}
|
|
|
|
/**
|
|
@brief ShortcutManager::savedSequence
|
|
@param id
|
|
@param default_sequence
|
|
@return the user-overridden sequence stored for \a id, or \a default_sequence
|
|
if the user never overrode it. A stored-but-empty sequence (the user
|
|
cleared the shortcut) is returned as an empty QKeySequence, not the default.
|
|
*/
|
|
QKeySequence ShortcutManager::savedSequence(const QString &id, const QKeySequence &default_sequence) const
|
|
{
|
|
QSettings settings;
|
|
const QString key = SETTINGS_GROUP + id;
|
|
if (!settings.contains(key)) {
|
|
return default_sequence;
|
|
}
|
|
return QKeySequence::fromString(settings.value(key).toString());
|
|
}
|
|
|
|
/**
|
|
@brief ShortcutManager::registerAction
|
|
Register \a target under \a id, applying the user's saved override (if
|
|
any) or \a default_sequence otherwise. \a target must be a QAction or a
|
|
QAbstractButton -- both declare a "shortcut" QKeySequence property, which
|
|
is what's actually read/written here, so this works for either without a
|
|
separate code path. Safe to call once per instance that shares this id
|
|
(e.g. once per open window of the same kind) -- the first call to reach a
|
|
given id fixes its category, description (taken from the target's current
|
|
"text" property) and default sequence for the lifetime of the
|
|
application; later calls just register another live target to update.
|
|
*/
|
|
void ShortcutManager::registerAction(QObject *target, const QString &id,
|
|
const QString &category,
|
|
const QKeySequence &default_sequence)
|
|
{
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
Entry entry;
|
|
entry.category = category;
|
|
entry.description = target->property("text").toString();
|
|
entry.description.remove(QLatin1Char('&'));
|
|
entry.default_sequence = default_sequence;
|
|
it = m_entries.insert(id, entry);
|
|
m_order << id;
|
|
}
|
|
|
|
QList<QPointer<QObject>> &targets = it->targets;
|
|
targets.erase(std::remove_if(targets.begin(), targets.end(),
|
|
[](const QPointer<QObject> &t) { return t.isNull(); }),
|
|
targets.end());
|
|
if (!targets.contains(target)) {
|
|
targets << target;
|
|
}
|
|
|
|
target->setProperty("shortcut", QVariant::fromValue(savedSequence(id, it->default_sequence)));
|
|
}
|
|
|
|
/**
|
|
@brief ShortcutManager::unregisterAction
|
|
Forget \a target under \a id, and the id itself once no live target is
|
|
left. For commands that come and go while the application runs, such
|
|
as a stored script deleted from its folder: without this its id stays
|
|
listed in the shortcut settings, the shortcut bar and command search,
|
|
and its first description stays fixed even after it is renamed. The
|
|
user's saved shortcut for the id is kept, so the command picks it up
|
|
again if it comes back.
|
|
*/
|
|
void ShortcutManager::unregisterAction(QObject *target, const QString &id)
|
|
{
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
return;
|
|
}
|
|
QList<QPointer<QObject>> &targets = it->targets;
|
|
targets.erase(std::remove_if(targets.begin(), targets.end(),
|
|
[target](const QPointer<QObject> &t) {
|
|
return t.isNull() || t.data() == target; }),
|
|
targets.end());
|
|
if (targets.isEmpty()) {
|
|
m_entries.erase(it);
|
|
m_order.removeAll(id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return every registered shortcut, in registration order, along with its
|
|
currently effective sequence -- for display in the Shortcuts config page.
|
|
*/
|
|
QList<ShortcutManager::ShortcutInfo> ShortcutManager::allShortcuts() const
|
|
{
|
|
QList<ShortcutInfo> list;
|
|
for (const QString &id : m_order) {
|
|
const Entry &entry = m_entries.value(id);
|
|
ShortcutInfo info;
|
|
info.id = id;
|
|
info.category = entry.category;
|
|
info.description = entry.description;
|
|
info.default_sequence = entry.default_sequence;
|
|
info.current_sequence = savedSequence(id, entry.default_sequence);
|
|
list << info;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
QKeySequence ShortcutManager::currentSequence(const QString &id) const
|
|
{
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
return QKeySequence();
|
|
}
|
|
return savedSequence(id, it->default_sequence);
|
|
}
|
|
|
|
/**
|
|
@brief ShortcutManager::setSequence
|
|
Persist \a sequence as the binding for \a id and apply it immediately to
|
|
every currently live QAction registered under that id. Persisted as "no
|
|
override" when \a sequence matches the id's default, so a future QET
|
|
version raising that default takes effect for users who never customized it.
|
|
*/
|
|
void ShortcutManager::setSequence(const QString &id, const QKeySequence &sequence)
|
|
{
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
return;
|
|
}
|
|
|
|
QSettings settings;
|
|
const QString key = SETTINGS_GROUP + id;
|
|
if (sequence == it->default_sequence) {
|
|
settings.remove(key);
|
|
} else {
|
|
settings.setValue(key, sequence.toString());
|
|
}
|
|
|
|
for (const QPointer<QObject> &target : std::as_const(it->targets)) {
|
|
if (target) {
|
|
target->setProperty("shortcut", QVariant::fromValue(sequence));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShortcutManager::resetToDefault(const QString &id)
|
|
{
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
return;
|
|
}
|
|
setSequence(id, it->default_sequence);
|
|
}
|
|
|
|
void ShortcutManager::resetAllToDefaults()
|
|
{
|
|
for (const QString &id : m_order) {
|
|
resetToDefault(id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ShortcutManager::trigger
|
|
@param id
|
|
@return see the declaration's doc comment
|
|
*/
|
|
bool ShortcutManager::trigger(const QString &id) const
|
|
{
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
return false;
|
|
}
|
|
|
|
for (const QPointer<QObject> &target : std::as_const(it->targets))
|
|
{
|
|
if (!target) {
|
|
continue;
|
|
}
|
|
if (auto *action = qobject_cast<QAction *>(target.data())) {
|
|
action->trigger();
|
|
return true;
|
|
}
|
|
if (auto *button = qobject_cast<QAbstractButton *>(target.data())) {
|
|
button->click();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@return the QAction registered under @a id that belongs to @a owner --
|
|
that is, has @a owner among its ancestors -- or nullptr. Several windows
|
|
of the same kind each register their own action under one id, so a
|
|
window asking for "its" action has to say which window it is.
|
|
@param id
|
|
@param owner : the window, or nullptr for the first live action
|
|
*/
|
|
QAction *ShortcutManager::action(const QString &id, const QObject *owner) const
|
|
{
|
|
auto it = m_entries.find(id);
|
|
if (it == m_entries.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (const QPointer<QObject> &target : std::as_const(it->targets))
|
|
{
|
|
auto *action = qobject_cast<QAction *>(target.data());
|
|
if (!action) {
|
|
continue;
|
|
}
|
|
if (!owner) {
|
|
return action;
|
|
}
|
|
for (const QObject *o = action->parent(); o; o = o->parent()) {
|
|
if (o == owner) {
|
|
return action;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|