M3: dedup palette actions by identity, disambiguate shared labels
The same QAction is often reachable through multiple GUI clients; those are true duplicates and are now collapsed by pointer identity. Distinct actions that merely share a visible label (common across plugins) are kept but disambiguated by appending the owning component's display name, and every entry is assigned a unique, stable id so activating a row always triggers the intended action rather than whichever one won a hash key.
This commit is contained in:
parent
91a0e2d260
commit
485d395d4d
|
|
@ -74,6 +74,11 @@ Therefore the palette is replaced, not extended, and the replacement is built on
|
|||
- Installs to `kf6/ktexteditor`; metadata verified valid via `KPluginMetaData`.
|
||||
- Not yet wired: frecency (the model supports a bonus, but the plugin currently
|
||||
passes 0 for every action — usage tracking is deferred).
|
||||
- Duplicate handling: actions are deduplicated by pointer identity (the same
|
||||
action reachable through several GUI clients is listed once). Distinct actions
|
||||
that merely share a visible label (e.g. two plugins each offering "Reload")
|
||||
are kept but disambiguated with their component name, and every entry gets a
|
||||
unique id so activation always triggers the right action.
|
||||
|
||||
### M4 — Radial caret menu (mouse door) — NEXT
|
||||
- Mouse-triggered radial menu positioned at the caret
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
#include <KPluginFactory>
|
||||
|
||||
#include <QAction>
|
||||
#include <QHash>
|
||||
#include <QKeySequence>
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
|
||||
namespace katecustom
|
||||
|
|
@ -64,24 +66,35 @@ OllieView::~OllieView()
|
|||
delete m_palette;
|
||||
}
|
||||
|
||||
void OllieView::collectActions(QHash<QString, QPointer<QAction>> &out) const
|
||||
QList<OllieView::ActionEntry> OllieView::collectActions() const
|
||||
{
|
||||
QList<ActionEntry> entries;
|
||||
|
||||
KXMLGUIFactory *factory = m_mainWindow->guiFactory();
|
||||
if (!factory) {
|
||||
return;
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Dedup by pointer identity: the same QAction is frequently reachable
|
||||
// through several GUI clients. Those are true duplicates and must appear
|
||||
// only once.
|
||||
QSet<QAction *> seen;
|
||||
|
||||
const QList<KXMLGUIClient *> clients = factory->clients();
|
||||
for (KXMLGUIClient *client : clients) {
|
||||
KActionCollection *coll = client->actionCollection();
|
||||
if (!coll) {
|
||||
continue;
|
||||
}
|
||||
const QString component = coll->componentDisplayName();
|
||||
const QList<QAction *> actions = coll->actions();
|
||||
for (QAction *action : actions) {
|
||||
if (!action || action->isSeparator()) {
|
||||
continue;
|
||||
}
|
||||
if (seen.contains(action)) {
|
||||
continue;
|
||||
}
|
||||
// Only offer things the user could actually invoke now.
|
||||
if (!action->isEnabled() || !action->isVisible()) {
|
||||
continue;
|
||||
|
|
@ -90,16 +103,45 @@ void OllieView::collectActions(QHash<QString, QPointer<QAction>> &out) const
|
|||
if (label.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// Prefer the stable objectName as id; fall back to the label.
|
||||
QString id = action->objectName();
|
||||
if (id.isEmpty()) {
|
||||
id = label;
|
||||
}
|
||||
if (!out.contains(id)) {
|
||||
out.insert(id, action);
|
||||
}
|
||||
seen.insert(action);
|
||||
|
||||
ActionEntry e;
|
||||
e.action = action;
|
||||
e.label = label;
|
||||
e.component = component;
|
||||
e.shortcut = action->shortcut().toString(QKeySequence::NativeText);
|
||||
entries.push_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Two distinct actions can share a visible label (e.g. different plugins
|
||||
// each offering "Reload"). Those are not duplicates, so keep them, but
|
||||
// disambiguate by provenance so the user can tell them apart.
|
||||
QHash<QString, int> labelCounts;
|
||||
for (const ActionEntry &e : entries) {
|
||||
++labelCounts[e.label];
|
||||
}
|
||||
|
||||
// Assign unique, reasonably stable ids and disambiguate colliding labels.
|
||||
QHash<QString, int> idSeq;
|
||||
for (ActionEntry &e : entries) {
|
||||
if (labelCounts.value(e.label) > 1 && !e.component.isEmpty()) {
|
||||
e.label = e.label + QStringLiteral(" — ") + e.component;
|
||||
}
|
||||
|
||||
const QString objName = e.action ? e.action->objectName() : QString();
|
||||
QString base = e.component;
|
||||
if (!base.isEmpty()) {
|
||||
base += QLatin1Char('/');
|
||||
}
|
||||
base += objName.isEmpty() ? e.label : objName;
|
||||
|
||||
// Guarantee uniqueness even if base still collides.
|
||||
const int n = idSeq[base]++;
|
||||
e.id = (n == 0) ? base : base + QStringLiteral("#") + QString::number(n);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
void OllieView::showCommandPalette()
|
||||
|
|
@ -109,22 +151,18 @@ void OllieView::showCommandPalette()
|
|||
connect(m_palette, &PaletteWidget::activatedId, this, &OllieView::runAction);
|
||||
}
|
||||
|
||||
m_actionsById.clear();
|
||||
collectActions(m_actionsById);
|
||||
const QList<ActionEntry> entries = collectActions();
|
||||
|
||||
m_actionsById.clear();
|
||||
QList<PaletteItem> items;
|
||||
items.reserve(m_actionsById.size());
|
||||
for (auto it = m_actionsById.cbegin(); it != m_actionsById.cend(); ++it) {
|
||||
QAction *action = it.value();
|
||||
if (!action) {
|
||||
continue;
|
||||
}
|
||||
items.reserve(entries.size());
|
||||
for (const ActionEntry &e : entries) {
|
||||
m_actionsById.insert(e.id, e.action);
|
||||
|
||||
PaletteItem item;
|
||||
item.id = it.key();
|
||||
item.label = action->text().remove(QLatin1Char('&')).trimmed();
|
||||
// Show the current shortcut as the group column hint, when present.
|
||||
const QString sc = action->shortcut().toString(QKeySequence::NativeText);
|
||||
item.group = sc;
|
||||
item.id = e.id;
|
||||
item.label = e.label;
|
||||
item.group = e.shortcut; // shortcut shown as the trailing hint
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,19 @@ private Q_SLOTS:
|
|||
void runAction(const QString &actionId);
|
||||
|
||||
private:
|
||||
// One harvested, runnable action with the provenance needed to build a
|
||||
// unique id and to disambiguate labels shared by several commands.
|
||||
struct ActionEntry {
|
||||
QPointer<QAction> action;
|
||||
QString label; // visible text, ampersands stripped
|
||||
QString component; // owning collection's display name (provenance)
|
||||
QString shortcut; // current shortcut, native text
|
||||
QString id; // unique, stable key used on activation
|
||||
};
|
||||
|
||||
// Collect every unique, enabled, visible QAction reachable from the
|
||||
// window's GUI factory clients. Keyed by a stable id for lookup on accept.
|
||||
void collectActions(QHash<QString, QPointer<QAction>> &out) const;
|
||||
// window's GUI factory clients, deduplicated by pointer identity.
|
||||
QList<ActionEntry> collectActions() const;
|
||||
|
||||
KTextEditor::MainWindow *m_mainWindow;
|
||||
PaletteWidget *m_palette = nullptr;
|
||||
|
|
|
|||
Loading…
Reference in New Issue