VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/UIMediumSizeEditor.cpp

Last change on this file was 104929, checked in by vboxsync, 3 months ago

FE/Qt: bugref:10666, bugref:10669: UIMediumSizeSlider: Implementing own accessibility interface; Adjusting tool-tip to have no HTML tags.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.3 KB
Line 
1/* $Id: UIMediumSizeEditor.cpp 104929 2024-06-14 16:13:09Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMediumSizeEditor class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2024 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 <QAccessibleWidget>
30#include <QGridLayout>
31#include <QLabel>
32#include <QRegularExpressionValidator>
33
34/* GUI includes: */
35#include "QILineEdit.h"
36#include "UIConverter.h"
37#include "UIGlobalSession.h"
38#include "UIMediumSizeEditor.h"
39#include "UITranslator.h"
40#include "UITranslationEventListener.h"
41
42/* COM includes: */
43#include "CSystemProperties.h"
44
45
46/** QAccessibleWidget extension used as an accessibility interface for UIMediumSizeSlider. */
47class UIAccessibilityInterfaceForUIMediumSizeSlider : public QAccessibleWidget
48{
49public:
50
51 /** Returns an accessibility interface for passed @a strClassname and @a pObject. */
52 static QAccessibleInterface *pFactory(const QString &strClassname, QObject *pObject)
53 {
54 /* Creating QIRichTextLabel accessibility interface: */
55 if (pObject && strClassname == QLatin1String("UIMediumSizeSlider"))
56 return new UIAccessibilityInterfaceForUIMediumSizeSlider(qobject_cast<QWidget*>(pObject));
57
58 /* Null by default: */
59 return 0;
60 }
61
62 /** Constructs an accessibility interface passing @a pWidget to the base-class. */
63 UIAccessibilityInterfaceForUIMediumSizeSlider(QWidget *pWidget)
64 : QAccessibleWidget(pWidget, QAccessible::Slider)
65 {}
66
67 /** Returns a text for the passed @a enmTextRole. */
68 virtual QString text(QAccessible::Text enmTextRole) const RT_OVERRIDE
69 {
70 /* Make sure label still alive: */
71 AssertPtrReturn(slider(), QString());
72
73 /* Non-macOS screen-readers using QAccessible::Value for slider: */
74 if (enmTextRole == QAccessible::Value)
75 return slider()->scaledValueToString();
76
77 /* Call to base-class: */
78 return QAccessibleWidget::text(enmTextRole);
79 }
80
81private:
82
83 /** Returns corresponding UIMediumSizeSlider. */
84 UIMediumSizeSlider *slider() const
85 {
86 return qobject_cast<UIMediumSizeSlider*>(widget());
87 }
88};
89
90
91/*********************************************************************************************************************************
92* Class UIMediumSizeSlider implementation. *
93*********************************************************************************************************************************/
94
95UIMediumSizeSlider::UIMediumSizeSlider(qulonglong uSizeMax, QWidget *pParent /* = 0 */)
96 : QSlider(pParent)
97 , m_iSliderScale(calculateSliderScale(uSizeMax))
98 , m_uScaledMinimum(0)
99 , m_uScaledMaximum(100)
100 , m_uScaledValue(0)
101{
102 /* Install UIMediumSizeSlider accessibility interface factory: */
103 QAccessible::installFactory(UIAccessibilityInterfaceForUIMediumSizeSlider::pFactory);
104
105 /* Configure basic properties: */
106 setFocusPolicy(Qt::StrongFocus);
107 setOrientation(Qt::Horizontal);
108 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
109
110 /* Configure tick look&feel: */
111 setTickPosition(QSlider::TicksBelow);
112 setTickInterval(0);
113
114 /* Configure scaling: */
115 setPageStep(m_iSliderScale);
116 setSingleStep(m_iSliderScale / 8);
117
118 /* Connection converting usual value into scaled one: */
119 connect(this, &UIMediumSizeSlider::valueChanged,
120 this, &UIMediumSizeSlider::sltValueChanged);
121}
122
123void UIMediumSizeSlider::setScaledMinimum(qulonglong uMinimum)
124{
125 /* Make sure something got changed and save the change: */
126 if (m_uScaledMinimum == uMinimum)
127 return;
128 m_uScaledMinimum = uMinimum;
129 /* Call to base-class: */
130 QSlider::setMinimum(sizeMBToSlider(m_uScaledMinimum, m_iSliderScale));
131}
132
133void UIMediumSizeSlider::setScaledMaximum(qulonglong uMaximum)
134{
135 /* Make sure something got changed and save the change: */
136 if (m_uScaledMaximum == uMaximum)
137 return;
138 m_uScaledMaximum = uMaximum;
139 /* Call to base-class: */
140 QSlider::setMaximum(sizeMBToSlider(m_uScaledMaximum, m_iSliderScale));
141}
142
143void UIMediumSizeSlider::setScaledValue(qulonglong uValue)
144{
145 /* Make sure something got changed and save the change: */
146 if (m_uScaledValue == uValue)
147 return;
148 m_uScaledValue = uValue;
149 /* Call to base-class: */
150 QSlider::setValue(sizeMBToSlider(m_uScaledValue, m_iSliderScale));
151}
152
153QString UIMediumSizeSlider::scaledValueToString()
154{
155 return UITranslator::formatSize(m_uScaledValue);
156}
157
158void UIMediumSizeSlider::sltValueChanged(int iValue)
159{
160 /* Convert from usual to scaled value: */
161 emit sigScaledValueChanged(sliderToSizeMB(iValue, m_iSliderScale));
162}
163
164/* static */
165int UIMediumSizeSlider::calculateSliderScale(qulonglong uMaximumMediumSize)
166{
167 /* Detect how many steps to recognize between adjacent powers of 2
168 * to ensure that the last slider step is exactly that we need: */
169 int iSliderScale = 0;
170 const int iPower = log2i(uMaximumMediumSize);
171 qulonglong uTickMB = (qulonglong)1 << iPower;
172 if (uTickMB < uMaximumMediumSize)
173 {
174 qulonglong uTickMBNext = (qulonglong)1 << (iPower + 1);
175 qulonglong uGap = uTickMBNext - uMaximumMediumSize;
176 iSliderScale = (int)((uTickMBNext - uTickMB) / uGap);
177#ifdef VBOX_WS_MAC
178 // WORKAROUND:
179 // There is an issue with Qt5 QSlider under OSX:
180 // Slider tick count (maximum - minimum) is limited with some
181 // "magical number" - 588351, having it more than that brings
182 // unpredictable results like slider token jumping and disappearing,
183 // so we are limiting tick count by lowering slider-scale 128 times.
184 iSliderScale /= 128;
185#endif /* VBOX_WS_MAC */
186 }
187 return qMax(iSliderScale, 8);
188}
189
190/* static */
191int UIMediumSizeSlider::log2i(qulonglong uValue)
192{
193 if (!uValue)
194 return 0;
195 int iPower = -1;
196 while (uValue)
197 {
198 ++iPower;
199 uValue >>= 1;
200 }
201 return iPower;
202}
203
204/* static */
205int UIMediumSizeSlider::sizeMBToSlider(qulonglong uValue, int iSliderScale)
206{
207 /* Make sure *any* slider value is multiple of s_uSectorSize: */
208 uValue /= UIMediumSizeEditor::s_uSectorSize;
209
210 /* Calculate result: */
211 const int iPower = log2i(uValue);
212 const qulonglong uTickMB = qulonglong (1) << iPower;
213 const qulonglong uTickMBNext = qulonglong (1) << (iPower + 1);
214 const int iStep = (uValue - uTickMB) * iSliderScale / (uTickMBNext - uTickMB);
215 const int iResult = iPower * iSliderScale + iStep;
216
217 /* Return result: */
218 return iResult;
219}
220
221/* static */
222qulonglong UIMediumSizeSlider::sliderToSizeMB(int uValue, int iSliderScale)
223{
224 /* Calculate result: */
225 const int iPower = uValue / iSliderScale;
226 const int iStep = uValue % iSliderScale;
227 const qulonglong uTickMB = qulonglong (1) << iPower;
228 const qulonglong uTickMBNext = qulonglong (1) << (iPower + 1);
229 qulonglong uResult = uTickMB + (uTickMBNext - uTickMB) * iStep / iSliderScale;
230
231 /* Make sure *any* slider value is multiple of s_uSectorSize: */
232 uResult *= UIMediumSizeEditor::s_uSectorSize;
233
234 /* Return result: */
235 return uResult;
236}
237
238
239/*********************************************************************************************************************************
240* Class UIMediumSizeEditor implementation. *
241*********************************************************************************************************************************/
242
243/* static */
244const qulonglong UIMediumSizeEditor::s_uSectorSize = 512;
245
246UIMediumSizeEditor::UIMediumSizeEditor(QWidget *pParent, qulonglong uMinimumSize /* = _4M */)
247 : QWidget(pParent)
248 , m_uSizeMin(uMinimumSize)
249 , m_uSizeMax(gpGlobalSession->virtualBox().GetSystemProperties().GetInfoVDSize())
250 , m_uSize(0)
251 , m_pSlider(0)
252 , m_pLabelMinSize(0)
253 , m_pLabelMaxSize(0)
254 , m_pEditor(0)
255{
256 prepare();
257}
258
259void UIMediumSizeEditor::setMediumSize(qulonglong uSize)
260{
261 /* Remember the new size: */
262 m_uSize = uSize;
263
264 /* And assign it to the slider & editor: */
265 m_pSlider->blockSignals(true);
266 m_pSlider->setScaledValue(m_uSize);
267 m_pSlider->blockSignals(false);
268 m_pEditor->blockSignals(true);
269 m_pEditor->setText(UITranslator::formatSize(m_uSize));
270 m_strSizeSuffix = gpConverter->toString(UITranslator::parseSizeSuffix(m_pEditor->text()));
271 m_pEditor->blockSignals(false);
272 updateSizeToolTips(m_uSize);
273}
274
275void UIMediumSizeEditor::sltRetranslateUI()
276{
277 /* Translate labels: */
278 m_pLabelMinSize->setText(UITranslator::formatSize(m_uSizeMin));
279 m_pLabelMaxSize->setText(UITranslator::formatSize(m_uSizeMax));
280
281 /* Translate fields: */
282 m_pSlider->setToolTip(tr("Holds the size of this medium."));
283 m_pEditor->setToolTip(tr("Holds the size of this medium."));
284 m_pLabelMinSize->setToolTip(tr("Minimum size for this medium."));
285 m_pLabelMaxSize->setToolTip(tr("Maximum size for this medium."));
286}
287
288void UIMediumSizeEditor::sltSizeSliderChanged(qulonglong uValue)
289{
290 /* Update the current size: */
291 m_uSize = uValue;
292 /* Update the other widget: */
293 m_pEditor->blockSignals(true);
294 m_pEditor->setText(UITranslator::formatSize(m_uSize));
295 m_strSizeSuffix = gpConverter->toString(UITranslator::parseSizeSuffix(m_pEditor->text()));
296 m_pEditor->blockSignals(false);
297 updateSizeToolTips(m_uSize);
298 /* Notify the listeners: */
299 emit sigSizeChanged(m_uSize);
300}
301
302void UIMediumSizeEditor::sltSizeEditorTextChanged()
303{
304 QString strSizeString = ensureSizeSuffix(m_pEditor->text());
305
306 m_pEditor->blockSignals(true);
307 int iCursorPosition = m_pEditor->cursorPosition();
308 m_pEditor->setText(strSizeString);
309 m_pEditor->setCursorPosition(iCursorPosition);
310 m_pEditor->blockSignals(false);
311
312 /* Update the current size: */
313 m_uSize = checkSectorSizeAlignment(UITranslator::parseSize(strSizeString));
314
315 /* Update the other widget: */
316 m_pSlider->blockSignals(true);
317 m_pSlider->setScaledValue(m_uSize);
318 m_pSlider->blockSignals(false);
319 updateSizeToolTips(m_uSize);
320 /* Notify the listeners: */
321 emit sigSizeChanged(m_uSize);
322}
323
324void UIMediumSizeEditor::prepare()
325{
326 /* Configure reg-exp: */
327 m_regExNonDigitOrSeparator = QRegularExpression(QString("[^\\d%1]").arg(UITranslator::decimalSep()));
328
329 /* Create layout: */
330 QGridLayout *pLayout = new QGridLayout(this);
331 if (pLayout)
332 {
333 /* Configure layout: */
334 pLayout->setContentsMargins(0, 0, 0, 0);
335 pLayout->setColumnStretch(0, 1);
336 pLayout->setColumnStretch(1, 1);
337 pLayout->setColumnStretch(2, 0);
338
339 /* Create size slider: */
340 m_pSlider = new UIMediumSizeSlider(m_uSizeMax, this);
341 if (m_pSlider)
342 {
343 /* Configure slider: */
344 m_pSlider->setScaledMinimum(m_uSizeMin);
345 m_pSlider->setScaledMaximum(m_uSizeMax);
346 connect(m_pSlider, &UIMediumSizeSlider::sigScaledValueChanged,
347 this, &UIMediumSizeEditor::sltSizeSliderChanged);
348
349 /* Add into layout: */
350 pLayout->addWidget(m_pSlider, 0, 0, 1, 2, Qt::AlignTop);
351 }
352
353 /* Create minimum size label: */
354 m_pLabelMinSize = new QLabel;
355 if (m_pLabelMinSize)
356 {
357 /* Configure label: */
358 m_pLabelMinSize->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
359
360 /* Add into layout: */
361 pLayout->addWidget(m_pLabelMinSize, 1, 0);
362 }
363
364 /* Create maximum size label: */
365 m_pLabelMaxSize = new QLabel;
366 if (m_pLabelMaxSize)
367 {
368 /* Configure label: */
369 m_pLabelMaxSize->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
370
371 /* Add into layout: */
372 pLayout->addWidget(m_pLabelMaxSize, 1, 1);
373 }
374
375 /* Create size editor: */
376 m_pEditor = new QILineEdit;
377 if (m_pEditor)
378 {
379 /* Configure editor: */
380 m_pEditor->installEventFilter(this);
381 m_pEditor->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
382 m_pEditor->setFixedWidthByText("88888.88 MB");
383 m_pEditor->setAlignment(Qt::AlignRight);
384 m_pEditor->setValidator(new QRegularExpressionValidator(QRegularExpression(UITranslator::sizeRegexp()), this));
385 connect(m_pEditor, &QILineEdit::textChanged,
386 this, &UIMediumSizeEditor::sltSizeEditorTextChanged);
387
388 /* Add into layout: */
389 pLayout->addWidget(m_pEditor, 0, 2, Qt::AlignTop);
390 }
391 }
392
393 /* Apply language settings: */
394 sltRetranslateUI();
395 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
396 this, &UIMediumSizeEditor::sltRetranslateUI);
397}
398
399void UIMediumSizeEditor::updateSizeToolTips(qulonglong uSize)
400{
401 const QString strToolTip = tr("%1 (%2 B)").arg(UITranslator::formatSize(uSize)).arg(uSize);
402 m_pSlider->setToolTip(strToolTip);
403 m_pEditor->setToolTip(strToolTip);
404}
405
406qulonglong UIMediumSizeEditor::checkSectorSizeAlignment(qulonglong uSize)
407{
408 if (s_uSectorSize == 0 || uSize % s_uSectorSize == 0)
409 return uSize;
410 return (uSize / s_uSectorSize) * s_uSectorSize;
411}
412
413QString UIMediumSizeEditor::ensureSizeSuffix(const QString &strSizeString)
414{
415 /* Try to update the m_strSizeSuffix: */
416 if (UITranslator::hasSizeSuffix(strSizeString))
417 m_strSizeSuffix = gpConverter->toString(UITranslator::parseSizeSuffix(strSizeString));
418
419 /* Remove any chars from the string except digits and decimal separator and then add a space and size suffix: */
420 QString strOnlyDigits(strSizeString);
421 return QString("%1 %2").arg(strOnlyDigits.remove(m_regExNonDigitOrSeparator)).arg(m_strSizeSuffix);
422}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle
ContactPrivacy/Do Not Sell My InfoTerms of Use