3
* VBox frontends: Qt GUI ("VirtualBox"):
4
* innotek Qt extensions: QIRichLabel class implementation
8
* Copyright (C) 2006-2007 innotek GmbH
10
* This file is part of VirtualBox Open Source Edition (OSE), as
11
* available from http://www.virtualbox.org. This file is free software;
12
* you can redistribute it and/or modify it under the terms of the GNU
13
* General Public License as published by the Free Software Foundation,
14
* in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
15
* distribution. VirtualBox OSE is distributed in the hope that it will
16
* be useful, but WITHOUT ANY WARRANTY of any kind.
20
* This class is based on the original QLabel implementation.
23
#include "QIRichLabel.h"
30
#include <qapplication.h>
31
#include <qsimplerichtext.h>
32
#include <qstylesheet.h>
35
#include <qfocusdata.h>
37
#include <qpopupmenu.h>
39
#include <qclipboard.h>
46
:img (0), pix (0), valid_hints (-1)
48
QImage* img; // for scaled contents
49
QPixmap* pix; // for scaled contents
52
int valid_hints; // stores the frameWidth() for the stored size hint, -1 otherwise
56
QIRichLabel::QIRichLabel (QWidget *parent, const char *name, WFlags f)
57
: QFrame (parent, name, f | WMouseNoMask)
63
QIRichLabel::QIRichLabel (const QString &text, QWidget *parent, const char *name,
65
: QFrame (parent, name, f | WMouseNoMask)
72
QIRichLabel::QIRichLabel (QWidget *buddy, const QString &text,
73
QWidget *parent, const char *name, WFlags f)
74
: QFrame (parent, name, f | WMouseNoMask)
82
QIRichLabel::~QIRichLabel()
89
void QIRichLabel::init()
91
mMaxHeightMode = false;
99
align = AlignAuto | AlignVCenter | ExpandTabs;
102
scaledcontents = FALSE;
103
textformat = Qt::AutoText;
106
d = new QLabelPrivate;
108
QAction *copyAction = new QAction (this, "copyAction");
109
connect (copyAction, SIGNAL (activated()),
110
this, SLOT (putToClipBoard()));
111
copyAction->setMenuText (tr ("Copy to clipboard"));
113
popupMenu = new QPopupMenu (this, "contextMenu");
114
copyAction->addTo (popupMenu);
116
setMouseTracking (true);
120
void QIRichLabel::setFixedHeight (int aHeight)
122
baseheight = aHeight;
123
QFrame::setFixedHeight (baseheight);
127
void QIRichLabel::setText (const QString &text)
131
QSize osh = sizeHint();
132
bool hadRichtext = doc != 0;
137
bool useRichText = (textformat == RichText ||
138
((textformat == AutoText) && QStyleSheet::mightBeRichText (ltext)));
140
// ### Setting accelerators for rich text labels will not work.
141
// Eg. <b>>Hello</b> will return ALT+G which is clearly
144
int p = QAccel::shortcutKey (ltext);
147
accel = new QAccel (this, "accel label accel");
148
accel->connectItem (accel->insertItem (p),
149
this, SLOT (acceleratorSlot()));
157
if (align & AlignRight)
158
t.prepend ("<div align=\"right\">");
159
else if (align & AlignHCenter)
160
t.prepend ("<div align=\"center\">");
161
if ((align & WordBreak) == 0)
162
t.prepend ("<nobr>");
163
doc = new QSimpleRichText (compressText(0), font());
168
if (mMaxHeightMode && (int)baseheight < heightForWidth (width()))
170
baseheight = heightForWidth (width());
171
QFrame::setFixedHeight (baseheight);
176
void QIRichLabel::clear()
178
setText (QString::fromLatin1 (""));
182
void QIRichLabel::setPixmap (const QPixmap &pixmap)
184
QSize osh = sizeHint();
186
if (!lpixmap || lpixmap->serialNumber() != pixmap.serialNumber()) {
188
lpixmap = new QPixmap (pixmap);
191
if (lpixmap->depth() == 1 && !lpixmap->mask())
192
lpixmap->setMask (*((QBitmap *)lpixmap));
198
void QIRichLabel::setPicture (const QPicture &picture)
200
QSize osh = sizeHint();
202
lpicture = new QPicture (picture);
208
void QIRichLabel::setNum (int num)
216
void QIRichLabel::setNum (double num)
224
void QIRichLabel::setAlignment (int alignment)
226
if (alignment == align)
228
QSize osh = sizeHint();
231
align = alignment | ShowPrefix;
237
ltext = QString::null;
245
void QIRichLabel::setIndent (int indent)
247
extraMargin = indent;
248
updateLabel (QSize (-1, -1));
252
void QIRichLabel::setAutoResize (bool enable)
254
if ((bool)autoresize != enable) {
257
adjustSize(); // calls resize which repaints
262
void QIRichLabel::setMaxHeightMode (bool aEnabled)
264
mMaxHeightMode = aEnabled;
268
QSize QIRichLabel::sizeForWidth (int w) const
271
QPixmap *pix = pixmap();
272
QPicture *pic = picture();
273
QMovie *mov = movie();
275
int hextra = 2 * frameWidth();
277
QFontMetrics fm (fontMetrics());
278
int xw = fm.width ('x');
279
if (!mov && !pix && !pic) {
281
if (m < 0 && hextra) // no indent, but we do have a frame
282
m = xw / 2 - margin();
284
int horizAlign = QApplication::horizontalAlignment( align );
285
if ((horizAlign & AlignLeft) || (horizAlign & AlignRight))
287
if ((align & AlignTop) || (align & AlignBottom))
295
br = pic->boundingRect();
297
br = mov->framePixmap().rect();
299
int focusIndent = hasFocus() ? 3 : 0;
300
int oldW = doc->width();
301
if ( align & WordBreak ) {
305
doc->setWidth (w-hextra - 2*focusIndent);
307
br = QRect (0, 0, doc->widthUsed(), doc->height());
308
doc->setWidth (oldW);
311
bool tryWidth = (w < 0) && (align & WordBreak);
317
br = fm.boundingRect (0, 0, w ,2000, alignment(), text());
318
if (tryWidth && br.height() < 4*fm.lineSpacing() && br.width() > w/2)
319
br = fm.boundingRect (0, 0, w/2, 2000, alignment(), text());
320
if (tryWidth && br.height() < 2*fm.lineSpacing() && br.width() > w/4)
321
br = fm.boundingRect (0, 0, w/4, 2000, alignment(), text());
323
int wid = br.width() + hextra;
324
int hei = br.height() + vextra;
326
return QSize (wid, hei);
330
int QIRichLabel::heightForWidth (int w) const
335
return sizeForWidth (w).height();
336
return QWidget::heightForWidth(w);
340
QSize QIRichLabel::sizeHint() const
342
if ( d->valid_hints != frameWidth() )
343
(void) QIRichLabel::minimumSizeHint();
348
QSize QIRichLabel::minimumSizeHint() const
350
if ( d->valid_hints == frameWidth() )
354
d->valid_hints = frameWidth();
355
d->sh = sizeForWidth (-1);
360
(align & WordBreak) == 0) {
363
// think about caching these for performance
364
sz.rwidth() = sizeForWidth (0).width();
365
sz.rheight() = sizeForWidth (QWIDGETSIZE_MAX).height();
366
if (d->sh.height() < sz.height())
367
sz.rheight() = d->sh.height();
369
if (sizePolicy().horData() == QSizePolicy::Ignored)
371
if (sizePolicy().verData() == QSizePolicy::Ignored)
378
void QIRichLabel::mouseMoveEvent (QMouseEvent *aEvent)
382
QString link = doc->anchorAt (aEvent->pos());
383
if (!link.isEmpty()) /* Mouse cursor above link */
384
setCursor (QCursor (Qt::PointingHandCursor));
385
else /* Mouse cursor above non-link */
386
setCursor (QCursor (Qt::ArrowCursor));
390
void QIRichLabel::mousePressEvent (QMouseEvent *aEvent)
394
QString link = doc->anchorAt (aEvent->pos());
395
/* Check for mouse left button clicked on the link */
396
if (!link.isEmpty() && aEvent->button() == LeftButton)
397
emit clickedOnLink (link);
401
void QIRichLabel::resizeEvent (QResizeEvent *e)
403
QFrame::resizeEvent (e);
405
static const bool doc = FALSE;
407
// optimize for standard labels
408
if (frameShape() == NoFrame && (align & WordBreak) == 0 && !doc &&
409
(e->oldSize().width() >= e->size().width() && (align & AlignLeft) == AlignLeft)
410
&& (e->oldSize().height() >= e->size().height() && (align & AlignTop) == AlignTop)) {
411
setWFlags (WResizeNoErase);
415
clearWFlags (WResizeNoErase);
416
QRect cr = contentsRect();
417
if ( !lpixmap || !cr.isValid() ||
418
// masked pixmaps can only reduce flicker when being top/left
419
// aligned and when we do not perform scaled contents
420
(lpixmap->hasAlpha() && (scaledcontents || ((align & (AlignLeft|AlignTop)) != (AlignLeft|AlignTop)))))
423
setWFlags (WResizeNoErase);
425
if (!scaledcontents) {
426
// don't we all love QFrame? Reduce pixmap flicker
427
QRegion reg = QRect (QPoint(0, 0), e->size());
428
reg = reg.subtract (cr);
431
int w = lpixmap->width();
432
int h = lpixmap->height();
433
if ((align & Qt::AlignVCenter) == Qt::AlignVCenter)
434
y += cr.height()/2 - h/2;
435
else if ((align & Qt::AlignBottom) == Qt::AlignBottom)
436
y += cr.height() - h;
437
if ((align & Qt::AlignRight) == Qt::AlignRight )
439
else if ((align & Qt::AlignHCenter) == Qt::AlignHCenter )
440
x += cr.width()/2 - w/2;
442
reg = reg.unite (QRect (cr.x(), cr.y(), x - cr.x(), cr.height()));
444
reg = reg.unite (QRect (cr.x(), cr.y(), cr.width(), y - cr.y()));
446
if (x + w < cr.right())
447
reg = reg.unite (QRect (x + w, cr.y(), cr.right() - x - w, cr.height()));
448
if (y + h < cr.bottom())
449
reg = reg.unite (QRect (cr.x(), y + h, cr.width(), cr.bottom() - y - h));
456
void QIRichLabel::focusInEvent (QFocusEvent *aEvent)
458
QFrame::focusInEvent (aEvent);
463
void QIRichLabel::keyPressEvent (QKeyEvent *aEvent)
465
switch (aEvent->key())
469
focusData()->prev()->setFocus();
473
focusData()->next()->setFocus();
481
void QIRichLabel::contextMenuEvent (QContextMenuEvent *aEvent)
483
popupBuffer = doc->anchorAt (aEvent->pos());
484
if (hasFocus() || !popupBuffer.isEmpty())
485
popupMenu->popup (aEvent->globalPos());
489
void QIRichLabel::putToClipBoard()
491
QString toClipBoard = ltext;
493
if (popupBuffer.isEmpty())
494
toClipBoard.remove (QRegExp ("<[^>]*>"));
496
toClipBoard = popupBuffer;
498
QApplication::clipboard()->setText (toClipBoard);
502
QString QIRichLabel::compressText (int aPaneWidth) const
504
QString allText = ltext;
506
if (aPaneWidth == -1) aPaneWidth = width();
507
int indentSize = fontMetrics().width ("x...x") + frameWidth() * 2;
509
QStringList strList = QStringList::split ("<br>", allText);
510
for (QStringList::Iterator it = strList.begin(); it != strList.end(); ++it)
512
QString oneString = *it;
513
int oldSize = fontMetrics().width (oneString);
520
QString filteredString = oneString;
521
filteredString.remove (QRegExp (QString ("<[^>]+>")));
522
textWidth = fontMetrics().width (filteredString);
523
if (textWidth + indentSize > aPaneWidth)
525
QRegExp regStart ("(<compact(\\s+elipsis=\"(start|middle|end)\")?>)");
526
QRegExp regFinish ("</compact>");
527
start = regStart.search (oneString);
528
finish = regFinish.search (oneString);
529
if (start == -1 || finish == -1 || finish < start)
532
if (regStart.cap(3) == "start")
533
position = start + regStart.cap(1).length();
534
else if (regStart.cap(3) == "middle" || regStart.cap(3).isEmpty())
535
position = start + regStart.cap(1).length() +
536
(finish-start-regStart.cap(1).length())/2;
537
else if (regStart.cap(3) == "end")
538
position = finish - 1;
542
if (position == finish ||
543
position == start + (int) regStart.cap(1).length() - 1)
545
oneString.remove (position, 1);
547
} while (textWidth + indentSize > aPaneWidth);
548
if (position) oneString.insert (position, "...");
550
int newSize = fontMetrics().width (oneString);
551
if (newSize < oldSize) *it = oneString;
553
QString result = strList.join ("<br>");
558
void QIRichLabel::drawContents (QPainter *p)
560
QRect cr = contentsRect();
562
QPixmap *pix = pixmap();
563
QPicture *pic = picture();
564
QMovie *mov = movie();
566
if (!mov && !pix && !pic) {
568
if (m < 0 && frameWidth()) // no indent, but we do have a frame
569
m = fontMetrics().width ('x') / 2 - margin();
571
int hAlign = QApplication::horizontalAlignment (align);
572
if (hAlign & AlignLeft)
573
cr.setLeft (cr.left() + m);
574
if (hAlign & AlignRight)
575
cr.setRight (cr.right() - m);
576
if (align & AlignTop)
577
cr.setTop (cr.top() + m);
578
if (align & AlignBottom)
579
cr.setBottom (cr.bottom() - m);
584
// ### should add movie to qDrawItem
585
QRect r = style().itemRect (p, cr, align, isEnabled(), &(mov->framePixmap()),
587
// ### could resize movie frame at this point
588
p->drawPixmap (r.x(), r.y(), mov->framePixmap());
593
QToolTip::remove (this);
594
QString filteredText = compressText();
595
doc = new QSimpleRichText (filteredText, font());
597
int focusIndent = hasFocus() ? 3 : 0;
598
doc->setWidth (p, cr.width() - 2*focusIndent);
599
int rh = doc->height();
601
if (align & AlignVCenter)
602
yo = (cr.height()-rh)/2;
603
else if (align & AlignBottom)
606
style().styleHint (QStyle::SH_EtchDisabledText, this)) {
607
QColorGroup cg = colorGroup();
608
cg.setColor (QColorGroup::Text, cg.light());
609
doc->draw (p, cr.x()+1, cr.y()+yo+1, cr, cg, 0);
612
// QSimpleRichText always draws with QColorGroup::Text as with
613
// background mode PaletteBase. QIRichLabel typically has
614
// background mode PaletteBackground, so we create a temporary
615
// color group with the text color adjusted.
616
QColorGroup cg = colorGroup();
622
const QColorGroup &standartGroup = QApplication::palette().active();
623
cg.setColor (QColorGroup::Text,
624
standartGroup.color (QColorGroup::HighlightedText));
625
paper.setColor (standartGroup.color (QColorGroup::Highlight));
626
paper.setStyle (QBrush::SolidPattern);
629
cg.setColor (QColorGroup::Text, paletteForegroundColor());
632
doc->draw (p, cr.x()+focusIndent, cr.y()+yo, cr, cg, &paper);
634
style().drawPrimitive (QStyle::PE_FocusRect, p, cr, cg,
635
QStyle::Style_FocusAtBorder,
638
if (filteredText != ltext)
639
QToolTip::add (this, QString (" %1").arg (ltext));
642
QRect br = pic->boundingRect();
644
int rh = br.height();
645
if ( scaledcontents ) {
647
p->translate (cr.x(), cr.y());
648
p->scale ((double)cr.width()/rw, (double)cr.height()/rh);
649
p->drawPicture (-br.x(), -br.y(), *pic);
654
if (align & AlignVCenter)
655
yo = (cr.height()-rh)/2;
656
else if (align & AlignBottom)
658
if (align & AlignRight)
660
else if (align & AlignHCenter)
661
xo = (cr.width()-rw)/2;
662
p->drawPicture (cr.x()+xo-br.x(), cr.y()+yo-br.y(), *pic);
666
if (scaledcontents && pix) {
668
d->img = new QImage (lpixmap->convertToImage());
671
d->pix = new QPixmap;
672
if (d->pix->size() != cr.size())
673
d->pix->convertFromImage (d->img->smoothScale (cr.width(), cr.height()));
676
int alignment = align;
677
if ((align & ShowPrefix) && !style().styleHint(QStyle::SH_UnderlineAccelerator, this))
678
alignment |= NoAccel;
679
// ordinary text or pixmap label
680
style().drawItem ( p, cr, alignment, colorGroup(), isEnabled(),
686
void QIRichLabel::updateLabel (QSize oldSizeHint)
689
QSizePolicy policy = sizePolicy();
690
bool wordBreak = align & WordBreak;
691
policy.setHeightForWidth (wordBreak);
692
if (policy != sizePolicy())
693
setSizePolicy (policy);
694
if (sizeHint() != oldSizeHint)
698
update (contentsRect());
700
update (contentsRect());
705
void QIRichLabel::acceleratorSlot()
709
QWidget * w = lbuddy;
710
while (w->focusProxy())
712
if (!w->hasFocus() &&
715
w->focusPolicy() != NoFocus) {
716
QFocusEvent::setReason (QFocusEvent::Shortcut);
718
QFocusEvent::resetReason();
723
void QIRichLabel::buddyDied()
729
void QIRichLabel::setBuddy (QWidget *buddy)
732
setAlignment (alignment() | ShowPrefix);
734
setAlignment (alignment() & ~ShowPrefix);
737
disconnect (lbuddy, SIGNAL (destroyed()), this, SLOT (buddyDied()));
744
if (!( textformat == RichText || (textformat == AutoText &&
745
QStyleSheet::mightBeRichText(ltext))))
747
int p = QAccel::shortcutKey (ltext);
750
accel = new QAccel (this, "accel label accel");
751
accel->connectItem (accel->insertItem (p),
752
this, SLOT (acceleratorSlot()));
756
connect (lbuddy, SIGNAL (destroyed()), this, SLOT (buddyDied()));
760
QWidget * QIRichLabel::buddy() const
766
void QIRichLabel::movieUpdated (const QRect &rect)
768
QMovie *mov = movie();
769
if (mov && !mov->isNull()) {
770
QRect r = contentsRect();
771
r = style().itemRect (0, r, align, isEnabled(), &(mov->framePixmap()),
773
r.moveBy (rect.x(), rect.y());
774
r.setWidth (QMIN (r.width(), rect.width()));
775
r.setHeight (QMIN (r.height(), rect.height()));
776
repaint (r, mov->framePixmap().mask() != 0);
781
void QIRichLabel::movieResized (const QSize &size)
786
movieUpdated (QRect (QPoint(0,0), size));
791
void QIRichLabel::setMovie (const QMovie &movie)
793
QSize osh = sizeHint();
796
lmovie = new QMovie (movie);
797
lmovie->connectResize (this, SLOT (movieResized (const QSize&)));
798
lmovie->connectUpdate (this, SLOT (movieUpdated (const QRect&)));
800
if (!lmovie->running()) // Assume that if the movie is running,
801
updateLabel (osh); // resize/update signals will come soon enough
805
void QIRichLabel::clearContents()
822
ltext = QString::null;
828
lmovie->disconnectResize (this, SLOT (movieResized (const QSize&)));
829
lmovie->disconnectUpdate (this, SLOT (movieUpdated (const QRect&)));
836
QMovie* QIRichLabel::movie() const
842
Qt::TextFormat QIRichLabel::textFormat() const
847
void QIRichLabel::setTextFormat( Qt::TextFormat format )
849
if (format != textformat) {
853
ltext = QString::null;
860
void QIRichLabel::fontChange (const QFont&)
862
if (!ltext.isEmpty()) {
864
doc->setDefaultFont (font());
866
updateLabel (QSize (-1, -1));
871
bool QIRichLabel::hasScaledContents() const
873
return scaledcontents;
877
void QIRichLabel::setScaledContents (bool enable)
879
if ((bool)scaledcontents == enable)
881
scaledcontents = enable;
888
update (contentsRect());
892
void QIRichLabel::setFont (const QFont &f)