VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/globals/UIShortcutPool.cpp@ 104358

Last change on this file since 104358 was 104358, checked in by vboxsync, 6 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.3 KB
Line 
1/* $Id: UIShortcutPool.cpp 104358 2024-04-18 05:33:40Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIShortcutPool class implementation.
4 */
5
6/*
7 * Copyright (C) 2011-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* GUI includes: */
29#include "UIActionPool.h"
30#include "UIExtraDataManager.h"
31#include "UIGlobalSession.h"
32#include "UIShortcutPool.h"
33#include "UITranslationEventListener.h"
34
35/* Namespaces: */
36using namespace UIExtraDataDefs;
37
38
39/*********************************************************************************************************************************
40* Class UIShortcut implementation. *
41*********************************************************************************************************************************/
42
43void UIShortcut::setScope(const QString &strScope)
44{
45 m_strScope = strScope;
46}
47
48const QString &UIShortcut::scope() const
49{
50 return m_strScope;
51}
52
53void UIShortcut::setDescription(const QString &strDescription)
54{
55 m_strDescription = strDescription;
56}
57
58const QString &UIShortcut::description() const
59{
60 return m_strDescription;
61}
62
63void UIShortcut::setSequences(const QList<QKeySequence> &sequences)
64{
65 m_sequences = sequences;
66}
67
68const QList<QKeySequence> &UIShortcut::sequences() const
69{
70 return m_sequences;
71}
72
73void UIShortcut::setDefaultSequence(const QKeySequence &defaultSequence)
74{
75 m_defaultSequence = defaultSequence;
76}
77
78const QKeySequence &UIShortcut::defaultSequence() const
79{
80 return m_defaultSequence;
81}
82
83void UIShortcut::setStandardSequence(const QKeySequence &standardSequence)
84{
85 m_standardSequence = standardSequence;
86}
87
88const QKeySequence &UIShortcut::standardSequence() const
89{
90 return m_standardSequence;
91}
92
93QString UIShortcut::primaryToNativeText() const
94{
95 return m_sequences.isEmpty() ? QString() : m_sequences.first().toString(QKeySequence::NativeText);
96}
97
98QString UIShortcut::primaryToPortableText() const
99{
100 return m_sequences.isEmpty() ? QString() : m_sequences.first().toString(QKeySequence::PortableText);
101}
102
103
104/*********************************************************************************************************************************
105* Class UIShortcutPool implementation. *
106*********************************************************************************************************************************/
107
108/* static */
109UIShortcutPool *UIShortcutPool::s_pInstance = 0;
110const QString UIShortcutPool::s_strShortcutKeyTemplate = QString("%1/%2");
111const QString UIShortcutPool::s_strShortcutKeyTemplateRuntime = s_strShortcutKeyTemplate.arg(GUI_Input_MachineShortcuts);
112
113void UIShortcutPool::create(UIType enmType)
114{
115 /* Check that instance do NOT exists: */
116 if (s_pInstance)
117 return;
118
119 /* Create instance: */
120 new UIShortcutPool(enmType);
121
122 /* Prepare instance: */
123 s_pInstance->prepare();
124}
125
126void UIShortcutPool::destroy()
127{
128 /* Check that instance exists: */
129 if (!s_pInstance)
130 return;
131
132 /* Cleanup instance: */
133 s_pInstance->cleanup();
134
135 /* Delete instance: */
136 delete s_pInstance;
137}
138
139UIShortcut &UIShortcutPool::shortcut(UIActionPool *pActionPool, UIAction *pAction)
140{
141 /* Compose shortcut key: */
142 const QString strShortcutKey(s_strShortcutKeyTemplate.arg(pActionPool->shortcutsExtraDataID(),
143 pAction->shortcutExtraDataID()));
144 /* Return existing if any: */
145 if (m_shortcuts.contains(strShortcutKey))
146 return shortcut(strShortcutKey);
147 /* Create and return new one: */
148 UIShortcut &newShortcut = m_shortcuts[strShortcutKey];
149 newShortcut.setScope(pAction->shortcutScope());
150 newShortcut.setDescription(pAction->name());
151 const QKeySequence &defaultSequence = pAction->defaultShortcut(pActionPool->type());
152 const QKeySequence &standardSequence = pAction->standardShortcut(pActionPool->type());
153 newShortcut.setSequences(QList<QKeySequence>() << defaultSequence << standardSequence);
154 newShortcut.setDefaultSequence(defaultSequence);
155 newShortcut.setStandardSequence(standardSequence);
156 return newShortcut;
157}
158
159UIShortcut &UIShortcutPool::shortcut(const QString &strPoolID, const QString &strActionID)
160{
161 /* Return if present, autocreate if necessary: */
162 return shortcut(s_strShortcutKeyTemplate.arg(strPoolID, strActionID));
163}
164
165void UIShortcutPool::setOverrides(const QMap<QString, QString> &overrides)
166{
167 /* Iterate over all the overrides: */
168 const QList<QString> shortcutKeys = overrides.keys();
169 foreach (const QString &strShortcutKey, shortcutKeys)
170 {
171 /* Make no changes if there is no such shortcut: */
172 if (!m_shortcuts.contains(strShortcutKey))
173 continue;
174 /* Assign overridden sequences to the shortcut: */
175 m_shortcuts[strShortcutKey].setSequences(QList<QKeySequence>() << overrides[strShortcutKey]);
176 }
177 /* Save overrides: */
178 saveOverrides();
179}
180
181void UIShortcutPool::applyShortcuts(UIActionPool *pActionPool)
182{
183 /* For each the action of the passed action-pool: */
184 foreach (UIAction *pAction, pActionPool->actions())
185 {
186 /* Skip menu actions: */
187 if (pAction->type() == UIActionType_Menu)
188 continue;
189
190 /* Compose shortcut key: */
191 const QString strShortcutKey = s_strShortcutKeyTemplate.arg(pActionPool->shortcutsExtraDataID(),
192 pAction->shortcutExtraDataID());
193 /* If shortcut key is already known: */
194 if (m_shortcuts.contains(strShortcutKey))
195 {
196 /* Get corresponding shortcut: */
197 UIShortcut &existingShortcut = m_shortcuts[strShortcutKey];
198 /* Copy the scope from the action to the shortcut: */
199 existingShortcut.setScope(pAction->shortcutScope());
200 /* Copy the description from the action to the shortcut: */
201 existingShortcut.setDescription(pAction->name());
202 /* Copy the sequences from the shortcut to the action: */
203 pAction->setShortcuts(existingShortcut.sequences());
204 pAction->retranslateUi();
205 /* Copy default and standard sequences from the action to the shortcut: */
206 if (pActionPool->type() == m_enmType)
207 {
208 existingShortcut.setDefaultSequence(pAction->defaultShortcut(pActionPool->type()));
209 existingShortcut.setStandardSequence(pAction->standardShortcut(pActionPool->type()));
210 }
211 }
212 /* If shortcut key is NOT known yet: */
213 else
214 {
215 /* Create corresponding shortcut: */
216 UIShortcut &newShortcut = m_shortcuts[strShortcutKey];
217 /* Copy the action's default sequence to both the shortcut & the action: */
218 const QKeySequence &defaultSequence = pAction->defaultShortcut(pActionPool->type());
219 const QKeySequence &standardSequence = pAction->standardShortcut(pActionPool->type());
220 newShortcut.setSequences(QList<QKeySequence>() << defaultSequence << standardSequence);
221 newShortcut.setDefaultSequence(defaultSequence);
222 newShortcut.setStandardSequence(standardSequence);
223 pAction->setShortcuts(newShortcut.sequences());
224 pAction->retranslateUi();
225 /* Copy the description from the action to the shortcut: */
226 newShortcut.setScope(pAction->shortcutScope());
227 newShortcut.setDescription(pAction->name());
228 }
229 }
230}
231
232/* static */
233QKeySequence UIShortcutPool::standardSequence(QKeySequence::StandardKey enmKey)
234{
235 /* We have some overrides: */
236 switch (enmKey)
237 {
238#ifdef VBOX_WS_MAC
239 /* So called Apple default sequence for HelpContents action (CMD+?)
240 * is no more the default one on macOS for many years.
241 * Instead they have redesigned this shortcut to open
242 * system-wide Help menu with native search field.
243 * But the Contents action has no shortcut anymore.
244 * We could leave it empty or make it CMD+/ instead. */
245 case QKeySequence::HelpContents:
246 return QKeySequence("Ctrl+/");
247#endif
248 default:
249 break;
250 }
251
252 /* Use QKeySequence constructor by default: */
253 return QKeySequence(enmKey);
254}
255
256void UIShortcutPool::sltRetranslateUI()
257{
258 /* Translate own defaults: */
259 m_shortcuts[s_strShortcutKeyTemplateRuntime.arg("PopupMenu")]
260 .setDescription(QApplication::translate("UIActionPool", "Popup Menu"));
261}
262
263void UIShortcutPool::sltReloadSelectorShortcuts()
264{
265 /* Clear selector shortcuts first: */
266 const QList<QString> shortcutKeyList = m_shortcuts.keys();
267 foreach (const QString &strShortcutKey, shortcutKeyList)
268 if (strShortcutKey.startsWith(GUI_Input_SelectorShortcuts))
269 m_shortcuts.remove(strShortcutKey);
270
271 /* Load selector defaults: */
272 loadDefaultsFor(GUI_Input_SelectorShortcuts);
273 /* Load selector overrides: */
274 loadOverridesFor(GUI_Input_SelectorShortcuts);
275
276 /* Notify manager shortcuts reloaded: */
277 emit sigManagerShortcutsReloaded();
278}
279
280void UIShortcutPool::sltReloadMachineShortcuts()
281{
282 /* Clear machine shortcuts first: */
283 const QList<QString> shortcutKeyList = m_shortcuts.keys();
284 foreach (const QString &strShortcutKey, shortcutKeyList)
285 if (strShortcutKey.startsWith(GUI_Input_MachineShortcuts))
286 m_shortcuts.remove(strShortcutKey);
287
288 /* Load machine defaults: */
289 loadDefaultsFor(GUI_Input_MachineShortcuts);
290 /* Load machine overrides: */
291 loadOverridesFor(GUI_Input_MachineShortcuts);
292
293 /* Notify runtime shortcuts reloaded: */
294 emit sigRuntimeShortcutsReloaded();
295}
296
297UIShortcutPool::UIShortcutPool(UIType enmType)
298 : m_enmType(enmType)
299{
300 /* Prepare instance: */
301 if (!s_pInstance)
302 s_pInstance = this;
303}
304
305UIShortcutPool::~UIShortcutPool()
306{
307 /* Cleanup instance: */
308 if (s_pInstance == this)
309 s_pInstance = 0;
310}
311
312void UIShortcutPool::prepare()
313{
314 /* Load defaults: */
315 loadDefaults();
316 /* Load overrides: */
317 loadOverrides();
318 /* Prepare connections: */
319 prepareConnections();
320}
321
322void UIShortcutPool::prepareConnections()
323{
324 /* Connect to extra-data signals: */
325 connect(gEDataManager, &UIExtraDataManager::sigSelectorUIShortcutChange,
326 this, &UIShortcutPool::sltReloadSelectorShortcuts);
327 connect(gEDataManager, &UIExtraDataManager::sigRuntimeUIShortcutChange,
328 this, &UIShortcutPool::sltReloadMachineShortcuts);
329 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
330 this, &UIShortcutPool::sltRetranslateUI);
331}
332
333void UIShortcutPool::loadDefaults()
334{
335 /* Load selector defaults: */
336 loadDefaultsFor(GUI_Input_SelectorShortcuts);
337 /* Load machine defaults: */
338 loadDefaultsFor(GUI_Input_MachineShortcuts);
339}
340
341void UIShortcutPool::loadDefaultsFor(const QString &strPoolExtraDataID)
342{
343 /* Default shortcuts for Selector UI: */
344 if (strPoolExtraDataID == GUI_Input_SelectorShortcuts)
345 {
346 /* Nothing for now.. */
347 }
348 /* Default shortcuts for Runtime UI: */
349 else if (strPoolExtraDataID == GUI_Input_MachineShortcuts)
350 {
351 /* Default shortcut for the Runtime Popup Menu: */
352 m_shortcuts.insert(s_strShortcutKeyTemplateRuntime.arg("PopupMenu"),
353 UIShortcut(QString(),
354 QApplication::translate("UIActionPool", "Popup Menu"),
355 QList<QKeySequence>() << QString("Home"),
356 QString("Home"),
357 QString()));
358 }
359}
360
361void UIShortcutPool::loadOverrides()
362{
363 /* Load selector overrides: */
364 loadOverridesFor(GUI_Input_SelectorShortcuts);
365 /* Load machine overrides: */
366 loadOverridesFor(GUI_Input_MachineShortcuts);
367}
368
369void UIShortcutPool::loadOverridesFor(const QString &strPoolExtraDataID)
370{
371 /* Compose shortcut key template: */
372 const QString strShortcutKeyTemplate(s_strShortcutKeyTemplate.arg(strPoolExtraDataID));
373 /* Iterate over all the overrides: */
374 const QStringList overrides = gEDataManager->shortcutOverrides(strPoolExtraDataID);
375 foreach (const QString &strKeyValuePair, overrides)
376 {
377 /* Make sure override structure is valid: */
378 int iDelimiterPosition = strKeyValuePair.indexOf('=');
379 if (iDelimiterPosition < 0)
380 continue;
381
382 /* Get shortcut ID/sequence: */
383 QString strShortcutExtraDataID = strKeyValuePair.left(iDelimiterPosition);
384 const QString strShortcutSequence = strKeyValuePair.right(strKeyValuePair.length() - iDelimiterPosition - 1);
385
386 // Hack for handling "Save" as "SaveState":
387 if (strShortcutExtraDataID == "Save")
388 strShortcutExtraDataID = "SaveState";
389
390 /* Compose corresponding shortcut key: */
391 const QString strShortcutKey(strShortcutKeyTemplate.arg(strShortcutExtraDataID));
392 /* Modify map with composed key/value: */
393 if (!m_shortcuts.contains(strShortcutKey))
394 m_shortcuts.insert(strShortcutKey,
395 UIShortcut(QString(),
396 QString(),
397 QList<QKeySequence>() << strShortcutSequence,
398 QString(),
399 QString()));
400 else
401 {
402 /* Get corresponding value: */
403 UIShortcut &shortcut = m_shortcuts[strShortcutKey];
404 /* Check if corresponding shortcut overridden by value: */
405 if (shortcut.primaryToPortableText().compare(strShortcutSequence, Qt::CaseInsensitive) != 0)
406 {
407 /* Shortcut unassigned? */
408 if (strShortcutSequence.compare("None", Qt::CaseInsensitive) == 0)
409 shortcut.setSequences(QList<QKeySequence>());
410 /* Or reassigned? */
411 else
412 shortcut.setSequences(QList<QKeySequence>() << strShortcutSequence);
413 }
414 }
415 }
416}
417
418void UIShortcutPool::saveOverrides()
419{
420 /* Load selector overrides: */
421 saveOverridesFor(GUI_Input_SelectorShortcuts);
422 /* Load machine overrides: */
423 saveOverridesFor(GUI_Input_MachineShortcuts);
424}
425
426void UIShortcutPool::saveOverridesFor(const QString &strPoolExtraDataID)
427{
428 /* Compose shortcut prefix: */
429 const QString strShortcutPrefix(s_strShortcutKeyTemplate.arg(strPoolExtraDataID, QString()));
430 /* Populate the list of all the known overrides: */
431 QStringList overrides;
432 const QList<QString> shortcutKeys = m_shortcuts.keys();
433 foreach (const QString &strShortcutKey, shortcutKeys)
434 {
435 /* Check if the key starts from the proper prefix: */
436 if (!strShortcutKey.startsWith(strShortcutPrefix))
437 continue;
438 /* Get corresponding shortcut: */
439 const UIShortcut &shortcut = m_shortcuts[strShortcutKey];
440 /* Check if the sequence for that shortcut differs from default or standard: */
441 if ( shortcut.sequences().contains(shortcut.defaultSequence())
442 || ( !shortcut.standardSequence().isEmpty()
443 && shortcut.sequences().contains(shortcut.standardSequence())))
444 continue;
445 /* Add the shortcut sequence into overrides list: */
446 overrides << QString("%1=%2").arg(QString(strShortcutKey).remove(strShortcutPrefix),
447 shortcut.primaryToPortableText());
448 }
449 /* Save overrides into the extra-data: */
450 gpGlobalSession->virtualBox().SetExtraDataStringList(strPoolExtraDataID, overrides);
451}
452
453UIShortcut &UIShortcutPool::shortcut(const QString &strShortcutKey)
454{
455 return m_shortcuts[strShortcutKey];
456}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use