7#include "mnemonicattached.h"
9#include <QGuiApplication>
11#include <QQuickRenderControl>
12#include <QQuickWindow>
13#include <QRegularExpression>
20static QString removeReducedCJKAccMark(
const QString &label,
int pos)
26 && label[pos].isLetterOrNumber()) {
30 while (p1 >= 0 && !label[p1].isLetterOrNumber()) {
35 while (p2 < len && !label[p2].isLetterOrNumber()) {
42 return strView.left(pos - 1) + strView.mid(p2 + 1);
43 }
else if (p2 + 1 == len) {
44 return strView.left(p1) + strView.mid(pos + 2);
55 bool accmarkRemoved =
false;
69 label = removeReducedCJKAccMark(label, p);
71 accmarkRemoved =
true;
84 if (!accmarkRemoved) {
86 for (
const QChar c : std::as_const(label)) {
87 if (c.unicode() >= 0x2e00) {
99 label = removeReducedCJKAccMark(label, p + 1);
108class MnemonicEventFilter :
public QObject
113 static MnemonicEventFilter &instance()
115 static MnemonicEventFilter s_instance;
119 bool eventFilter(
QObject *watched, QEvent *
event)
override
124 QKeyEvent *ke =
static_cast<QKeyEvent *
>(
event);
130 QKeyEvent *ke =
static_cast<QKeyEvent *
>(
event);
132 m_altPressed =
false;
137 m_altPressed =
false;
150 MnemonicEventFilter()
153 qGuiApp->installEventFilter(
this);
156 bool m_altPressed =
false;
159MnemonicAttached::MnemonicAttached(
QObject *parent)
162 connect(&MnemonicEventFilter::instance(), &MnemonicEventFilter::altPressed,
this, &MnemonicAttached::onAltPressed);
163 connect(&MnemonicEventFilter::instance(), &MnemonicEventFilter::altReleased,
this, &MnemonicAttached::onAltReleased);
166MnemonicAttached::~MnemonicAttached()
168 s_sequenceToObject.remove(m_sequence);
171QWindow *MnemonicAttached::window()
const
174 if (
auto *window = parentItem->window()) {
186void MnemonicAttached::onAltPressed()
188 if (m_active || !m_enabled || m_richTextLabel.isEmpty()) {
192 auto *win = window();
193 if (!win || !win->isActive()) {
197 m_actualRichTextLabel = m_richTextLabel;
198 Q_EMIT richTextLabelChanged();
203void MnemonicAttached::onAltReleased()
205 if (!m_active || m_richTextLabel.isEmpty()) {
212 m_actualRichTextLabel = removeAcceleratorMarker(m_label);
213 Q_EMIT richTextLabelChanged();
219void MnemonicAttached::calculateWeights()
224 bool start_character =
true;
225 bool wanted_character =
false;
227 while (pos < m_label.length()) {
228 QChar c = m_label[pos];
232 start_character =
true;
241 weight += FIRST_CHARACTER_EXTRA_WEIGHT;
243 }
else if (start_character) {
244 weight += WORD_BEGINNING_EXTRA_WEIGHT;
245 start_character =
false;
249 if (wanted_character) {
250 weight += WANTED_ACCEL_EXTRA_WEIGHT;
251 wanted_character =
false;
256 weight += (50 - pos);
261 if (c == QLatin1Char(
'&')
262 && (pos != m_label.length() - 1
263 && m_label[pos + 1] != QLatin1Char(
'&')
264 && m_label[pos + 1].isLetterOrNumber())) {
265 wanted_character =
true;
270 while (m_weights.contains(weight)) {
274 if (c != QLatin1Char(
'&')) {
275 m_weights[weight] = c;
282 if (m_weights.isEmpty()) {
283 m_weight = m_baseWeight;
285 m_weight = m_baseWeight + (std::prev(m_weights.cend())).key();
289void MnemonicAttached::updateSequence()
291 const QKeySequence oldSequence = m_sequence;
293 if (!m_sequence.isEmpty()) {
294 s_sequenceToObject.remove(m_sequence);
301 const QString text =
label().
replace(QStringLiteral(
"& "), QStringLiteral(
"&& "));
302 m_actualRichTextLabel = removeAcceleratorMarker(text);
306 if (m_mnemonicLabel != m_actualRichTextLabel) {
307 m_mnemonicLabel = m_actualRichTextLabel;
308 Q_EMIT mnemonicLabelChanged();
309 Q_EMIT richTextLabelChanged();
312 if (m_sequence != oldSequence) {
318 m_mnemonicLabel = text;
319 m_mnemonicLabel.
replace(QRegularExpression(QLatin1String(
"\\&([^\\&])")), QStringLiteral(
"\\1"));
321 if (!m_weights.isEmpty()) {
322 QMap<int, QChar>::const_iterator i = m_weights.constEnd();
327 QKeySequence ks(QStringLiteral(
"Alt+") % c);
328 MnemonicAttached *otherMa = s_sequenceToObject.value(ks);
329 Q_ASSERT(otherMa !=
this);
330 if (!otherMa || otherMa->m_weight < m_weight) {
333 s_sequenceToObject.remove(otherMa->
sequence());
334 otherMa->m_sequence = {};
337 s_sequenceToObject[ks] =
this;
339 m_richTextLabel = text;
340 m_richTextLabel.
replace(QRegularExpression(QLatin1String(
"\\&([^\\&])")), QStringLiteral(
"\\1"));
341 m_mnemonicLabel = text;
342 const int mnemonicPos = m_mnemonicLabel.
indexOf(c);
344 if (mnemonicPos > -1 && (mnemonicPos == 0 || m_mnemonicLabel[mnemonicPos - 1] != QLatin1Char(
'&'))) {
345 m_mnemonicLabel.replace(mnemonicPos, 1, QStringLiteral(
"&") % c);
348 const int richTextPos = m_richTextLabel.indexOf(c);
349 if (richTextPos > -1) {
350 m_richTextLabel.replace(richTextPos, 1, QLatin1String(
"<u>") % c % QLatin1String(
"</u>"));
355 otherMa->updateSequence();
360 }
while (i != m_weights.constBegin());
363 if (m_sequence != oldSequence) {
367 Q_EMIT richTextLabelChanged();
368 Q_EMIT mnemonicLabelChanged();
371void MnemonicAttached::setLabel(
const QString &text)
373 if (m_label == text) {
384 if (!m_actualRichTextLabel.isEmpty()) {
385 return m_actualRichTextLabel;
387 return removeAcceleratorMarker(m_label);
393 return m_mnemonicLabel;
401void MnemonicAttached::setEnabled(
bool enabled)
427 m_baseWeight = ACTION_ELEMENT_WEIGHT;
430 m_baseWeight = DIALOG_BUTTON_EXTRA_WEIGHT;
433 m_baseWeight = MENU_ITEM_WEIGHT;
436 m_baseWeight = FORM_LABEL_WEIGHT;
439 m_baseWeight = SECONDARY_CONTROL_WEIGHT;
443 if (m_weights.isEmpty()) {
444 m_weight = m_baseWeight;
446 m_weight = m_baseWeight + (std::prev(m_weights.constEnd())).key();
448 Q_EMIT controlTypeChanged();
453 return m_controlType;
468 return new MnemonicAttached(
object);
471void MnemonicAttached::setActive(
bool active)
476 if (!m_active && m_active ==
active) {
483 if (m_actualRichTextLabel != m_richTextLabel) {
484 m_actualRichTextLabel = m_richTextLabel;
485 Q_EMIT richTextLabelChanged();
489 m_actualRichTextLabel = removeAcceleratorMarker(m_label);
490 Q_EMIT richTextLabelChanged();
496#include "mnemonicattached.moc"
This Attached property is used to calculate automated keyboard sequences to trigger actions based upo...
QString richTextLabel
The user-visible final label, which will have the shortcut letter underlined, such as "<u>O</u>k".
QString label
The label of the control we want to compute a mnemonic for, instance "Label:" or "&Ok".
@ DialogButton
buttons for dialogs
@ ActionElement
pushbuttons, checkboxes etc
@ FormLabel
Buddy label in a FormLayout.
bool enabled
Only if true this mnemonic will be considered for the global assignment default: true.
bool active
True when the user is pressing alt and the accelerators should be shown.
QString mnemonicLabel
The label with an "&" mnemonic in the place which will have the shortcut assigned,...
MnemonicAttached::ControlType controlType
The type of control this mnemonic is attached: different types of controls have different importance ...
QKeySequence sequence
The final key sequence assigned, if any: it will be Alt+alphanumeric char.
QString label(StandardShortcut id)
bool isLetterOrNumber(char32_t ucs4)
virtual bool event(QEvent *e)
QObject * parent() const const
T qobject_cast(QObject *object)
QWindow * renderWindowFor(QQuickWindow *win, QPoint *offset)
const QChar at(qsizetype position) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)