ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvKeySequences.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
11 
12 // app
13 #include <CVLog.h>
14 
15 #include <QAction>
16 #include <QKeySequence>
17 #include <QMap>
18 #include <QPointer>
19 #include <QSet>
20 
21 namespace {
22 
27 struct Shortcuts {
29  QSet<ecvModalShortcut*> Siblings;
30 
32  ecvModalShortcut* mruShortcut = nullptr;
33 
34  // TODO: Allow a key-sequence to be "default to last" when a shortcut is
35  // removed/disabled as an option?
36  // TODO: Hold tooltip or preference name?
37 };
38 
42 struct Dictionary {
43  QMap<QKeySequence, Shortcuts> Data;
44 };
45 
47 Dictionary g_keys;
48 
49 } // anonymous namespace
50 
51 //-----------------------------------------------------------------------------
53  static ecvKeySequences inst(nullptr);
54  return inst;
55 }
56 
57 //-----------------------------------------------------------------------------
59  : QObject(parent), m_silence(false) {
60  CVLog::PrintVerbose("[ecvKeySequences] Modal shortcut manager initialized");
61 }
62 
63 //-----------------------------------------------------------------------------
65  const QKeySequence& keySequence) const {
66  ecvModalShortcut* active = nullptr;
67  QMap<QKeySequence, Shortcuts>::iterator iter =
68  g_keys.Data.find(keySequence);
69  if (iter == g_keys.Data.end()) {
70  return active;
71  }
72 
73  for (auto& sibling : iter->Siblings) {
74  if (sibling && sibling->isEnabled()) {
75  active = sibling;
76  break;
77  }
78  }
79  return active;
80 }
81 
82 //-----------------------------------------------------------------------------
84  const QKeySequence& keySequence, QAction* action, QWidget* parent) {
85  if (keySequence.isEmpty()) {
87  "[ecvKeySequences] Attempted to register empty key sequence");
88  return nullptr;
89  }
90 
91  // Create the modal shortcut
92  auto shortcut = new ecvModalShortcut(keySequence, action, parent);
93 
94  // Add to siblings list
95  auto& keyData = g_keys.Data[keySequence];
96  keyData.Siblings.insert(shortcut);
97 
98  // Initially disable (will be enabled below)
99  shortcut->setEnabled(false);
100 
101  // Connect signals
102  QObject::connect(shortcut, &ecvModalShortcut::enabled, this,
104  // QObject::connect(shortcut, &ecvModalShortcut::disabled,
105  // this, &ecvKeySequences::enableNextSibling);
106  QObject::connect(shortcut, &ecvModalShortcut::unregister, this,
108 
109  // Enable (this will trigger disableSiblings() to ensure only one is active)
110  shortcut->setEnabled(true);
111 
113  QString("[ecvKeySequences] Registered modal shortcut: %1 (ID: %2)")
114  .arg(keySequence.toString())
115  .arg(shortcut->objectName().isEmpty()
116  ? "unnamed"
117  : shortcut->objectName()));
118 
119  return shortcut;
120 }
121 
122 //-----------------------------------------------------------------------------
124  if (!target) {
125  return;
126  }
127 
128  // Find the key sequence for this shortcut
129  QMap<QKeySequence, Shortcuts>::iterator iter =
130  g_keys.Data.find(target->keySequence());
131  if (iter == g_keys.Data.end()) {
132  return;
133  }
134 
135  // Check if any sibling of target is currently active
136  bool hasActiveSibling = false;
137  for (auto& sibling : iter->Siblings) {
138  if (sibling != target && sibling && sibling->isEnabled()) {
139  hasActiveSibling = true;
140  break;
141  }
142  }
143 
144  // If no sibling is active, reorder has no effect (as per ParaView
145  // documentation)
146  if (!hasActiveSibling) {
147  return;
148  }
149 
150  // Mark target as the most recently used shortcut
151  // This allows enableNextSibling() to activate it when the current
152  // shortcut is disabled
153  iter->mruShortcut = target;
154 
156  QString("[ecvKeySequences] Reordered shortcut: %1 (MRU set)")
157  .arg(target->keySequence().toString()));
158 }
159 
160 //-----------------------------------------------------------------------------
161 void ecvKeySequences::dumpShortcuts(const QKeySequence& keySequence) const {
162  QMap<QKeySequence, Shortcuts>::iterator iter =
163  g_keys.Data.find(keySequence);
164  if (iter == g_keys.Data.end()) {
165  CVLog::Print(
166  QString("[ecvKeySequences] No shortcuts registered for: %1")
167  .arg(keySequence.toString()));
168  return;
169  }
170 
171  CVLog::PrintVerbose(QString("[ecvKeySequences] Shortcuts for %1:")
172  .arg(keySequence.toString()));
173  for (auto& sibling : iter->Siblings) {
174  if (sibling) {
175  CVLog::PrintVerbose(QString(" - %1: %2")
176  .arg(sibling->objectName().isEmpty()
177  ? "unnamed"
178  : sibling->objectName())
179  .arg(sibling->isEnabled()
180  ? "enabled"
181  : "disabled"));
182  }
183  }
184 }
185 
186 //-----------------------------------------------------------------------------
188  if (m_silence) {
189  return;
190  }
191 
192  auto* shortcut = qobject_cast<ecvModalShortcut*>(this->sender());
193  if (!shortcut) {
194  return;
195  }
196 
197  QMap<QKeySequence, Shortcuts>::iterator iter =
198  g_keys.Data.find(shortcut->keySequence());
199  if (iter == g_keys.Data.end()) {
200  return;
201  }
202 
203  // Update MRU shortcut to the one being enabled
204  iter->mruShortcut = shortcut;
205 
206  // Update MRU shortcut to the one being enabled
207  iter->mruShortcut = shortcut;
208 
209  // Disable all siblings except the one being enabled
210  m_silence = true;
211  int disabledCount = 0;
212  for (auto& sibling : iter->Siblings) {
213  if (sibling != shortcut && sibling->isEnabled()) {
214  sibling->setEnabled(false);
215  disabledCount++;
216  }
217  }
218  m_silence = false;
219 
220  if (disabledCount > 0) {
222  QString("[ecvKeySequences] Disabled %1 sibling(s) for %2")
223  .arg(disabledCount)
224  .arg(shortcut->keySequence().toString()));
225  }
226 }
227 
228 //-----------------------------------------------------------------------------
230  if (m_silence) {
231  return;
232  }
233 
234  auto* shortcut = qobject_cast<ecvModalShortcut*>(this->sender());
235  if (!shortcut) {
236  return;
237  }
238 
239  // Find the key sequence for this shortcut
240  QMap<QKeySequence, Shortcuts>::iterator iter =
241  g_keys.Data.find(shortcut->keySequence());
242  if (iter == g_keys.Data.end()) {
243  return;
244  }
245 
246  // Activate the most-recently-used sibling if available
247  // This implements the reordering behavior: when a shortcut is disabled,
248  // the MRU shortcut (set via reorder()) becomes the next one to activate
249  if (iter->mruShortcut && iter->mruShortcut != shortcut) {
250  // Check if the MRU shortcut's parent widget is still valid and enabled
251  QWidget* parentWidget =
252  qobject_cast<QWidget*>(iter->mruShortcut->parent());
253  if (parentWidget && parentWidget->isEnabled() &&
254  parentWidget->isVisible()) {
255  iter->mruShortcut->setEnabled(true);
257  QString("[ecvKeySequences] Enabled next sibling (MRU): %1")
258  .arg(shortcut->keySequence().toString()));
259  } else {
260  // MRU shortcut's widget is disabled/hidden, try to find another
261  // enabled sibling
262  for (auto& sibling : iter->Siblings) {
263  if (sibling != shortcut && sibling) {
264  QWidget* siblingParent =
265  qobject_cast<QWidget*>(sibling->parent());
266  if (siblingParent && siblingParent->isEnabled() &&
267  siblingParent->isVisible()) {
268  sibling->setEnabled(true);
269  CVLog::PrintVerbose(QString("[ecvKeySequences] Enabled "
270  "next sibling: "
271  "%1")
272  .arg(shortcut->keySequence()
273  .toString()));
274  break;
275  }
276  }
277  }
278  }
279  } else {
280  // No MRU shortcut set, try to find any enabled sibling
281  for (auto& sibling : iter->Siblings) {
282  if (sibling != shortcut && sibling) {
283  QWidget* siblingParent =
284  qobject_cast<QWidget*>(sibling->parent());
285  if (siblingParent && siblingParent->isEnabled() &&
286  siblingParent->isVisible()) {
287  sibling->setEnabled(true);
289  QString("[ecvKeySequences] Enabled next sibling: "
290  "%1")
291  .arg(shortcut->keySequence().toString()));
292  break;
293  }
294  }
295  }
296  }
297 }
298 
299 //-----------------------------------------------------------------------------
301  auto* shortcut = qobject_cast<ecvModalShortcut*>(this->sender());
302  if (!shortcut) {
303  return;
304  }
305 
306  QMap<QKeySequence, Shortcuts>::iterator iter =
307  g_keys.Data.find(shortcut->keySequence());
308  if (iter == g_keys.Data.end()) {
309  return;
310  }
311 
312  // Remove the shortcut from the key sequence list
313  iter->Siblings.remove(shortcut);
314  QObject::disconnect(shortcut);
315 
317  QString("[ecvKeySequences] Unregistered modal shortcut: %1")
318  .arg(shortcut->keySequence().toString()));
319 
320  // If no more siblings, remove the key sequence entry
321  if (iter->Siblings.empty()) {
322  g_keys.Data.erase(iter);
323  }
324 }
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
Definition: CVLog.cpp:103
Manage key sequences used for shortcuts.
virtual void enableNextSibling()
virtual void disableSiblings()
void reorder(ecvModalShortcut *target)
virtual void removeModalShortcut()
bool m_silence
Set true in slot implementations to avoid signal/slot recursion.
ecvModalShortcut * active(const QKeySequence &keySequence) const
ecvModalShortcut * addModalShortcut(const QKeySequence &keySequence, QAction *action, QWidget *parent)
static ecvKeySequences & instance()
void dumpShortcuts(const QKeySequence &keySequence) const
ecvKeySequences(QObject *parent)
Manage an action and/or widget's responsivity to a shortcut.
bool isEnabled() const
QKeySequence keySequence() const