VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/graphics/UIGraphicsTextPane.cpp@ 103988

Last change on this file since 103988 was 103795, checked in by vboxsync, 7 months ago

FE/Qt: bugref:10450: Get rid of old Qt hack for versions less than 5.11.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
  • Property svn:mergeinfo set to (toggle deleted branches)
    /branches/VBox-3.0/src/VBox/Frontends/VirtualBox/src/selector/graphics/chooser/UIGChooserItemMachine.cpp58652,​70973
    /branches/VBox-3.2/src/VBox/Frontends/VirtualBox/src/selector/graphics/chooser/UIGChooserItemMachine.cpp66309,​66318
    /branches/VBox-4.0/src/VBox/Frontends/VirtualBox/src/selector/graphics/chooser/UIGChooserItemMachine.cpp70873
    /branches/VBox-4.1/src/VBox/Frontends/VirtualBox/src/selector/graphics/chooser/UIGChooserItemMachine.cpp74233
    /branches/VBox-4.2/src/VBox/Frontends/VirtualBox/src/selector/graphics/details/UIGDetailsElement.cpp91503-91504,​91506-91508,​91510,​91514-91515,​91521
    /branches/VBox-4.3/src/VBox/Frontends/VirtualBox/src/selector/graphics/details/UIGDetailsElement.cpp91223
    /branches/VBox-4.3/trunk/src/VBox/Frontends/VirtualBox/src/selector/graphics/details/UIGDetailsElement.cpp91223
    /branches/dsen/gui/src/VBox/Frontends/VirtualBox/src/selector/graphics/chooser/UIGChooserItemMachine.cpp79076-79078,​79089,​79109-79110,​79112-79113,​79127-79130,​79134,​79141,​79151,​79155,​79157-79159,​79193,​79197
    /branches/dsen/gui2/src/VBox/Frontends/VirtualBox/src/selector/graphics/details/UIGDetailsElement.cpp79562-79569,​79572-79573,​79578,​79581-79582,​79590-79591,​79598-79599,​79602-79603,​79605-79606,​79632,​79635,​79637,​79644
    /branches/dsen/gui3/src/VBox/Frontends/VirtualBox/src/selector/graphics/details/UIGDetailsElement.cpp79645-79692
    /trunk/src/VBox/Frontends/VirtualBox/src/selector/graphics/chooser/UIGChooserItemMachine.cpp79225,​79271
File size: 17.9 KB
Line 
1/* $Id: UIGraphicsTextPane.cpp 103795 2024-03-11 19:36:59Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIGraphicsTextPane class implementation.
4 */
5
6/*
7 * Copyright (C) 2012-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 <QAccessibleObject>
30#include <QPainter>
31#include <QTextLayout>
32#include <QApplication>
33#include <QFontMetrics>
34#include <QGraphicsSceneHoverEvent>
35#include <QRegularExpression>
36
37/* GUI includes: */
38#include "UIGraphicsTextPane.h"
39#include "UIRichTextString.h"
40
41/* Other VBox includes: */
42#include <iprt/assert.h>
43
44/** QAccessibleObject extension used as an accessibility interface for UITextTableLine. */
45class UIAccessibilityInterfaceForUITextTableLine : public QAccessibleObject
46{
47public:
48
49 /** Returns an accessibility interface for passed @a strClassname and @a pObject. */
50 static QAccessibleInterface *pFactory(const QString &strClassname, QObject *pObject)
51 {
52 /* Creating Details-view accessibility interface: */
53 if (pObject && strClassname == QLatin1String("UITextTableLine"))
54 return new UIAccessibilityInterfaceForUITextTableLine(pObject);
55
56 /* Null by default: */
57 return 0;
58 }
59
60 /** Constructs an accessibility interface passing @a pObject to the base-class. */
61 UIAccessibilityInterfaceForUITextTableLine(QObject *pObject)
62 : QAccessibleObject(pObject)
63 {}
64
65 /** Returns the parent. */
66 virtual QAccessibleInterface *parent() const RT_OVERRIDE
67 {
68 /* Make sure line still alive: */
69 AssertPtrReturn(line(), 0);
70
71 /* Return the parent: */
72 return QAccessible::queryAccessibleInterface(line()->parent());
73 }
74
75 /** Returns the number of children. */
76 virtual int childCount() const RT_OVERRIDE { return 0; }
77 /** Returns the child with the passed @a iIndex. */
78 virtual QAccessibleInterface *child(int /* iIndex */) const RT_OVERRIDE { return 0; }
79 /** Returns the index of the passed @a pChild. */
80 virtual int indexOfChild(const QAccessibleInterface * /* pChild */) const RT_OVERRIDE { return -1; }
81
82 /** Returns the rect. */
83 virtual QRect rect() const RT_OVERRIDE
84 {
85 /* Make sure parent still alive: */
86 AssertPtrReturn(parent(), QRect());
87
88 /* Return the parent's rect for now: */
89 /// @todo Return sub-rect.
90 return parent()->rect();
91 }
92
93 /** Returns a text for the passed @a enmTextRole. */
94 virtual QString text(QAccessible::Text enmTextRole) const RT_OVERRIDE
95 {
96 /* Make sure line still alive: */
97 AssertPtrReturn(line(), QString());
98
99 /* Return the description: */
100 if (enmTextRole == QAccessible::Description)
101 return UIGraphicsTextPane::tr("%1: %2", "'key: value', like 'Name: MyVM'").arg(line()->string1(), line()->string2());
102
103 /* Null-string by default: */
104 return QString();
105 }
106
107 /** Returns the role. */
108 virtual QAccessible::Role role() const RT_OVERRIDE { return QAccessible::StaticText; }
109 /** Returns the state. */
110 virtual QAccessible::State state() const RT_OVERRIDE { return QAccessible::State(); }
111
112private:
113
114 /** Returns corresponding text-table line. */
115 UITextTableLine *line() const { return qobject_cast<UITextTableLine*>(object()); }
116};
117
118UIGraphicsTextPane::UIGraphicsTextPane(QIGraphicsWidget *pParent, QPaintDevice *pPaintDevice)
119 : QIGraphicsWidget(pParent)
120 , m_pPaintDevice(pPaintDevice)
121 , m_iMargin(0)
122 , m_iSpacing(10)
123 , m_iMinimumTextColumnWidth(100)
124 , m_fMinimumSizeHintInvalidated(true)
125 , m_iMinimumTextWidth(0)
126 , m_iMinimumTextHeight(0)
127 , m_fAnchorCanBeHovered(true)
128{
129 /* Install text-table line accessibility interface factory: */
130 QAccessible::installFactory(UIAccessibilityInterfaceForUITextTableLine::pFactory);
131
132 /* We do support hover-events: */
133 setAcceptHoverEvents(true);
134}
135
136UIGraphicsTextPane::~UIGraphicsTextPane()
137{
138 /* Clear text-layouts: */
139 while (!m_leftList.isEmpty()) delete m_leftList.takeLast();
140 while (!m_rightList.isEmpty()) delete m_rightList.takeLast();
141}
142
143void UIGraphicsTextPane::setText(const UITextTable &text)
144{
145 /* Clear text: */
146 m_text.clear();
147
148 /* For each the line of the passed table: */
149 foreach (const UITextTableLine &line, text)
150 {
151 /* Lines: */
152 QString strLeftLine = line.string1();
153 QString strRightLine = line.string2();
154
155 /* If 2nd line is NOT empty: */
156 if (!strRightLine.isEmpty())
157 {
158 /* Take both lines 'as is': */
159 m_text << UITextTableLine(strLeftLine, strRightLine, parentWidget());
160 }
161 /* If 2nd line is empty: */
162 else
163 {
164 /* Parse the 1st one to sub-lines: */
165 QStringList subLines = strLeftLine.split(QRegularExpression("\\n"));
166 foreach (const QString &strSubLine, subLines)
167 m_text << UITextTableLine(strSubLine, QString(), parentWidget());
168 }
169 }
170
171 /* Update text-layout: */
172 updateTextLayout(true);
173
174 /* Update minimum size-hint: */
175 updateGeometry();
176}
177
178void UIGraphicsTextPane::setAnchorRoleRestricted(const QString &strAnchorRole, bool fRestricted)
179{
180 /* Make sure something changed: */
181 if ( (fRestricted && m_restrictedAnchorRoles.contains(strAnchorRole))
182 || (!fRestricted && !m_restrictedAnchorRoles.contains(strAnchorRole)))
183 return;
184
185 /* Apply new value: */
186 if (fRestricted)
187 m_restrictedAnchorRoles << strAnchorRole;
188 else
189 m_restrictedAnchorRoles.remove(strAnchorRole);
190
191 /* Reset hovered anchor: */
192 m_strHoveredAnchor.clear();
193 updateHoverStuff();
194}
195
196void UIGraphicsTextPane::updateTextLayout(bool fFull /* = false */)
197{
198 /* Prepare variables: */
199 QFontMetrics fm(font(), m_pPaintDevice);
200 int iMaximumTextWidth = (int)size().width() - 2 * m_iMargin - m_iSpacing;
201
202 /* Search for the maximum column widths: */
203 int iMaximumLeftColumnWidth = 0;
204 int iMaximumRightColumnWidth = 0;
205 bool fSingleColumnText = true;
206 foreach (const UITextTableLine &line, m_text)
207 {
208 bool fRightColumnPresent = !line.string2().isEmpty();
209 if (fRightColumnPresent)
210 fSingleColumnText = false;
211 QString strLeftLine = fRightColumnPresent ? line.string1() + ":" : line.string1();
212 QString strRightLine = line.string2();
213#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
214 iMaximumLeftColumnWidth = qMax(iMaximumLeftColumnWidth, fm.horizontalAdvance(strLeftLine));
215 iMaximumRightColumnWidth = qMax(iMaximumRightColumnWidth, fm.horizontalAdvance(strRightLine));
216#else
217 iMaximumLeftColumnWidth = qMax(iMaximumLeftColumnWidth, fm.width(strLeftLine));
218 iMaximumRightColumnWidth = qMax(iMaximumRightColumnWidth, fm.width(strRightLine));
219#endif
220 }
221 iMaximumLeftColumnWidth += 1;
222 iMaximumRightColumnWidth += 1;
223
224 /* Calculate text attributes: */
225 int iLeftColumnWidth = 0;
226 int iRightColumnWidth = 0;
227 /* Left column only: */
228 if (fSingleColumnText)
229 {
230 /* Full update? */
231 if (fFull)
232 {
233 /* Minimum width for left column: */
234 int iMinimumLeftColumnWidth = qMin(m_iMinimumTextColumnWidth, iMaximumLeftColumnWidth);
235 /* Minimum width for whole text: */
236 m_iMinimumTextWidth = iMinimumLeftColumnWidth;
237 }
238
239 /* Current width for left column: */
240 iLeftColumnWidth = qMax(m_iMinimumTextColumnWidth, iMaximumTextWidth);
241 }
242 /* Two columns: */
243 else
244 {
245 /* Full update? */
246 if (fFull)
247 {
248 /* Minimum width for left column: */
249 int iMinimumLeftColumnWidth = iMaximumLeftColumnWidth;
250 /* Minimum width for right column: */
251 int iMinimumRightColumnWidth = qMin(m_iMinimumTextColumnWidth, iMaximumRightColumnWidth);
252 /* Minimum width for whole text: */
253 m_iMinimumTextWidth = iMinimumLeftColumnWidth + m_iSpacing + iMinimumRightColumnWidth;
254 }
255
256 /* Current width for left column: */
257 iLeftColumnWidth = iMaximumLeftColumnWidth;
258 /* Current width for right column: */
259 iRightColumnWidth = iMaximumTextWidth - iLeftColumnWidth;
260 }
261
262 /* Clear old text-layouts: */
263 while (!m_leftList.isEmpty()) delete m_leftList.takeLast();
264 while (!m_rightList.isEmpty()) delete m_rightList.takeLast();
265
266 /* Prepare new text-layouts: */
267 int iTextX = m_iMargin;
268 int iTextY = m_iMargin;
269 /* Populate text-layouts: */
270 m_iMinimumTextHeight = 0;
271 foreach (const UITextTableLine &line, m_text)
272 {
273 /* Left layout: */
274 int iLeftColumnHeight = 0;
275 if (!line.string1().isEmpty())
276 {
277 bool fRightColumnPresent = !line.string2().isEmpty();
278 m_leftList << buildTextLayout(font(), m_pPaintDevice,
279 fRightColumnPresent ? line.string1() + ":" : line.string1(),
280 iLeftColumnWidth, iLeftColumnHeight,
281 m_strHoveredAnchor);
282 m_leftList.last()->setPosition(QPointF(iTextX, iTextY));
283 }
284
285 /* Right layout: */
286 int iRightColumnHeight = 0;
287 if (!line.string2().isEmpty())
288 {
289 m_rightList << buildTextLayout(font(), m_pPaintDevice,
290 line.string2(),
291 iRightColumnWidth, iRightColumnHeight,
292 m_strHoveredAnchor);
293 m_rightList.last()->setPosition(QPointF(iTextX + iLeftColumnWidth + m_iSpacing, iTextY));
294 }
295
296 /* Maximum colum height? */
297 int iMaximumColumnHeight = qMax(iLeftColumnHeight, iRightColumnHeight);
298
299 /* Indent Y: */
300 iTextY += iMaximumColumnHeight;
301 /* Append summary text height: */
302 m_iMinimumTextHeight += iMaximumColumnHeight;
303 }
304}
305
306void UIGraphicsTextPane::updateGeometry()
307{
308 /* Discard cached minimum size-hint: */
309 m_fMinimumSizeHintInvalidated = true;
310
311 /* Call to base-class to notify layout if any: */
312 QIGraphicsWidget::updateGeometry();
313
314 /* And notify listeners which are not layouts: */
315 emit sigGeometryChanged();
316}
317
318QSizeF UIGraphicsTextPane::sizeHint(Qt::SizeHint which, const QSizeF &constraint /* = QSizeF() */) const
319{
320 /* For minimum size-hint: */
321 if (which == Qt::MinimumSize)
322 {
323 /* If minimum size-hint invalidated: */
324 if (m_fMinimumSizeHintInvalidated)
325 {
326 /* Recache minimum size-hint: */
327 m_minimumSizeHint = QSizeF(2 * m_iMargin + m_iMinimumTextWidth,
328 2 * m_iMargin + m_iMinimumTextHeight);
329 m_fMinimumSizeHintInvalidated = false;
330 }
331 /* Return cached minimum size-hint: */
332 return m_minimumSizeHint;
333 }
334 /* Call to base-class for other size-hints: */
335 return QIGraphicsWidget::sizeHint(which, constraint);
336}
337
338void UIGraphicsTextPane::resizeEvent(QGraphicsSceneResizeEvent*)
339{
340 /* Update text-layout: */
341 updateTextLayout();
342
343 /* Update minimum size-hint: */
344 updateGeometry();
345}
346
347void UIGraphicsTextPane::hoverLeaveEvent(QGraphicsSceneHoverEvent *pEvent)
348{
349 /* Redirect to common handler: */
350 handleHoverEvent(pEvent);
351}
352
353void UIGraphicsTextPane::hoverMoveEvent(QGraphicsSceneHoverEvent *pEvent)
354{
355 /* Redirect to common handler: */
356 handleHoverEvent(pEvent);
357}
358
359void UIGraphicsTextPane::handleHoverEvent(QGraphicsSceneHoverEvent *pEvent)
360{
361 /* Ignore if anchor can't be hovered: */
362 if (!m_fAnchorCanBeHovered)
363 return;
364
365 /* Prepare variables: */
366 QPoint mousePosition = pEvent->pos().toPoint();
367 QString strHoveredAnchor;
368 QString strHoveredAnchorRole;
369
370 /* Search for hovered-anchor in the left list: */
371 strHoveredAnchor = searchForHoveredAnchor(m_pPaintDevice, m_leftList, mousePosition);
372 strHoveredAnchorRole = strHoveredAnchor.section(',', 0, 0);
373 if (!strHoveredAnchor.isNull() && !m_restrictedAnchorRoles.contains(strHoveredAnchorRole))
374 {
375 m_strHoveredAnchor = strHoveredAnchor;
376 return updateHoverStuff();
377 }
378
379 /* Then search for hovered-anchor in the right one: */
380 strHoveredAnchor = searchForHoveredAnchor(m_pPaintDevice, m_rightList, mousePosition);
381 strHoveredAnchorRole = strHoveredAnchor.section(',', 0, 0);
382 if (!strHoveredAnchor.isNull() && !m_restrictedAnchorRoles.contains(strHoveredAnchorRole))
383 {
384 m_strHoveredAnchor = strHoveredAnchor;
385 return updateHoverStuff();
386 }
387
388 /* Finally clear it for good: */
389 if (!m_strHoveredAnchor.isNull())
390 {
391 m_strHoveredAnchor.clear();
392 return updateHoverStuff();
393 }
394}
395
396void UIGraphicsTextPane::updateHoverStuff()
397{
398 /* Update mouse-cursor: */
399 if (m_strHoveredAnchor.isNull())
400 unsetCursor();
401 else
402 setCursor(Qt::PointingHandCursor);
403
404 /* Update text-layout: */
405 updateTextLayout();
406
407 /* Update tool-tip: */
408 const QString strType = m_strHoveredAnchor.section(',', 0, 0);
409 if (strType == "#attach" || strType == "#mount")
410 setToolTip(m_strHoveredAnchor.section(',', -1));
411 else
412 setToolTip(QString());
413
414 /* Update text-pane: */
415 update();
416}
417
418void UIGraphicsTextPane::mousePressEvent(QGraphicsSceneMouseEvent*)
419{
420 /* Make sure some anchor hovered: */
421 if (m_strHoveredAnchor.isNull())
422 return;
423
424 /* Restrict anchor hovering: */
425 m_fAnchorCanBeHovered = false;
426
427 /* Cache clicked anchor: */
428 QString strClickedAnchor = m_strHoveredAnchor;
429
430 /* Clear hovered anchor: */
431 m_strHoveredAnchor.clear();
432 updateHoverStuff();
433
434 /* Notify listeners about anchor clicked: */
435 emit sigAnchorClicked(strClickedAnchor);
436
437 /* Allow anchor hovering again: */
438 m_fAnchorCanBeHovered = true;
439}
440
441void UIGraphicsTextPane::paint(QPainter *pPainter, const QStyleOptionGraphicsItem*, QWidget*)
442{
443 /* Draw all the text-layouts: */
444 foreach (QTextLayout *pTextLayout, m_leftList)
445 pTextLayout->draw(pPainter, QPoint(0, 0));
446 foreach (QTextLayout *pTextLayout, m_rightList)
447 pTextLayout->draw(pPainter, QPoint(0, 0));
448}
449
450/* static */
451QTextLayout* UIGraphicsTextPane::buildTextLayout(const QFont &font, QPaintDevice *pPaintDevice,
452 const QString &strText, int iWidth, int &iHeight,
453 const QString &strHoveredAnchor)
454{
455 /* Prepare variables: */
456 QFontMetrics fm(font, pPaintDevice);
457 int iLeading = fm.leading();
458
459 /* Parse incoming string with UIRichTextString capabilities: */
460 //printf("Text: {%s}\n", strText.toUtf8().constData());
461 UIRichTextString ms(strText);
462 ms.setHoveredAnchor(strHoveredAnchor);
463
464 /* Create layout; */
465 QTextLayout *pTextLayout = new QTextLayout(ms.toString(), font, pPaintDevice);
466 pTextLayout->setFormats(ms.formatRanges());
467
468 /* Configure layout: */
469 QTextOption textOption;
470 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
471 pTextLayout->setTextOption(textOption);
472
473 /* Build layout: */
474 pTextLayout->beginLayout();
475 while (1)
476 {
477 QTextLine line = pTextLayout->createLine();
478 if (!line.isValid())
479 break;
480
481 line.setLineWidth(iWidth);
482 iHeight += iLeading;
483 line.setPosition(QPointF(0, iHeight));
484 iHeight += (int)line.height();
485 }
486 pTextLayout->endLayout();
487
488 /* Return layout: */
489 return pTextLayout;
490}
491
492/* static */
493QString UIGraphicsTextPane::searchForHoveredAnchor(QPaintDevice *pPaintDevice, const QList<QTextLayout*> &list, const QPoint &mousePosition)
494{
495 /* Analyze passed text-layouts: */
496 foreach (QTextLayout *pTextLayout, list)
497 {
498 /* Prepare variables: */
499 QFontMetrics fm(pTextLayout->font(), pPaintDevice);
500
501 /* Text-layout attributes: */
502 const QPoint layoutPosition = pTextLayout->position().toPoint();
503 const QString strLayoutText = pTextLayout->text();
504
505 /* Enumerate format ranges: */
506 foreach (const QTextLayout::FormatRange &range, pTextLayout->formats())
507 {
508 /* Skip unrelated formats: */
509 if (!range.format.isAnchor())
510 continue;
511
512 /* Parse 'anchor' format: */
513 const int iStart = range.start;
514 const int iLength = range.length;
515 QRegion formatRegion;
516 for (int iTextPosition = iStart; iTextPosition < iStart + iLength; ++iTextPosition)
517 {
518 QTextLine layoutLine = pTextLayout->lineForTextPosition(iTextPosition);
519 QPoint linePosition = layoutLine.position().toPoint();
520 int iSymbolX = (int)layoutLine.cursorToX(iTextPosition);
521 QRect symbolRect = QRect(layoutPosition.x() + linePosition.x() + iSymbolX,
522 layoutPosition.y() + linePosition.y(),
523#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
524 fm.horizontalAdvance(strLayoutText[iTextPosition]) + 1,
525#else
526 fm.width(strLayoutText[iTextPosition]) + 1,
527#endif
528 fm.height());
529 formatRegion += symbolRect;
530 }
531
532 /* Is that something we looking for? */
533 if (formatRegion.contains(mousePosition))
534 return range.format.anchorHref();
535 }
536 }
537
538 /* Null string by default: */
539 return QString();
540}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette