VirtualBox

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

Last change on this file was 104393, checked in by vboxsync, 4 weeks ago

FE/Qt. bugref:10622. Using new UITranslationEventListener in the UIActionPool class.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use