Palette: guarantee distinguishable labels, never drop distinct actions
Dedup is now strictly by QAction pointer identity (functionally lossless: the same action object listed once). Distinct actions are never collapsed. When two distinct actions would show the same label, enrich it through a fallback chain until every visible row is unique: component name -> objectName -> shortcut -> numeric suffix. Ids remain unique so activation always hits the intended action.
This commit is contained in:
parent
485d395d4d
commit
d227d6161b
12
docs/PLAN.md
12
docs/PLAN.md
|
|
@ -74,11 +74,13 @@ 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.
|
||||
- Duplicate handling: actions are deduplicated only by pointer identity (the
|
||||
exact same action object reachable through several GUI clients is listed
|
||||
once — functionally lossless). Distinct actions are never dropped. When two
|
||||
distinct actions would render with the same label, the label is enriched
|
||||
through a fallback chain until every visible row is unique: component name →
|
||||
objectName → shortcut → numeric suffix. Each entry also gets a unique id so
|
||||
activation always triggers the intended action.
|
||||
|
||||
### M4 — Radial caret menu (mouse door) — NEXT
|
||||
- Mouse-triggered radial menu positioned at the caret
|
||||
|
|
|
|||
|
|
@ -114,21 +114,63 @@ QList<OllieView::ActionEntry> OllieView::collectActions() const
|
|||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Distinct actions can share a visible label (e.g. different plugins each
|
||||
// offering "Reload"). These are NOT duplicates — never drop them. Instead
|
||||
// guarantee every visible row is distinguishable by enriching colliding
|
||||
// labels through a fallback chain of discriminators, and give each entry a
|
||||
// unique id so activation always triggers the intended action.
|
||||
QHash<QString, int> labelCounts;
|
||||
for (const ActionEntry &e : entries) {
|
||||
++labelCounts[e.label];
|
||||
}
|
||||
|
||||
// Assign unique, reasonably stable ids and disambiguate colliding labels.
|
||||
// First pass: for any shared label, append the strongest discriminator
|
||||
// available (component, else objectName, else shortcut).
|
||||
for (ActionEntry &e : entries) {
|
||||
if (labelCounts.value(e.label) <= 1) {
|
||||
continue;
|
||||
}
|
||||
const QString objName = e.action ? e.action->objectName() : QString();
|
||||
QString disc;
|
||||
if (!e.component.isEmpty()) {
|
||||
disc = e.component;
|
||||
} else if (!objName.isEmpty()) {
|
||||
disc = objName;
|
||||
} else if (!e.shortcut.isEmpty()) {
|
||||
disc = e.shortcut;
|
||||
}
|
||||
if (!disc.isEmpty()) {
|
||||
e.label = e.label + QStringLiteral(" — ") + disc;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: if any labels are STILL identical (same label and same
|
||||
// discriminator), append objectName/shortcut, then a numeric suffix, so no
|
||||
// two rows ever look the same.
|
||||
QHash<QString, int> stillColliding;
|
||||
for (const ActionEntry &e : entries) {
|
||||
++stillColliding[e.label];
|
||||
}
|
||||
QHash<QString, int> dupSeq;
|
||||
for (ActionEntry &e : entries) {
|
||||
if (stillColliding.value(e.label) <= 1) {
|
||||
continue;
|
||||
}
|
||||
const QString objName = e.action ? e.action->objectName() : QString();
|
||||
if (!objName.isEmpty() && !e.label.contains(objName)) {
|
||||
e.label += QStringLiteral(" [") + objName + QLatin1Char(']');
|
||||
} else if (!e.shortcut.isEmpty() && !e.label.contains(e.shortcut)) {
|
||||
e.label += QStringLiteral(" (") + e.shortcut + QLatin1Char(')');
|
||||
} else {
|
||||
// Last resort: number the indistinguishable rows 1..n.
|
||||
const int n = ++dupSeq[e.label];
|
||||
e.label += QStringLiteral(" (") + QString::number(n) + QLatin1Char(')');
|
||||
}
|
||||
}
|
||||
|
||||
// Assign unique, reasonably stable ids (independent of the display label).
|
||||
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()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue