xtal256
1st October 2010, 04:31
I wanted to make a tooltip "bubble" or "balloon" for my app which can pop up in my app's status bar.
Since it appears that the only way to get such a widget is in the Window's taskbar (using QSystemTrayIcon), i decided to make my own.
I have it mostly working, but one thing i do not know how to do is make it resize so that it fits all the text but keeps a decent aspect ratio (i.e. does not get extremely long or tall).
This is what i have so far:
http://img214.imageshack.us/img214/5279/balloonx.png
I thought i could get the bounding box of the text within an initial rect, with Qt::TextWordWrap set, then check it's aspect ratio. If it is not right try again with a bigger box until i get one the right size.
I came up with this code:
QRect findBestSize(const QFontMetrics& fontMetrics, const String& message) {
QRect rect = fontMetrics.boundingRect(0, 0, 100, 50,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
float ratio = float(rect.width())/float(rect.height());
while(ratio < 1.5 || ratio > 2.0) {
rect.adjust(0, 0, 10, 5);
rect = fontMetrics.boundingRect(0, 0, rect.width(), rect.height(),
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
ratio = float(rect.width())/float(rect.height());
}
return rect;
}
But it doesn't work. And i am not sure what boundingRect() actually does. What is the rect that you give it and what rect does it return?
I am not even sure how my code should work, but i am thinking that there are many different sized rects which can contain the text (i.e. such that the text wraps to two lines, three lines, etc) and i want the rect which is about 3:1 ratio.
Here is the full code below:
// File: BalloonTip.h
#ifndef BALLOON_TIP_H
#define BALLOON_TIP_H
class BalloonTip : public QWidget {
private:
static const QBrush backgroundBrush;
static const QPen outlinePen;
static const int textPadding = 6;
static const int arrowHeight = 20; // That triangle bit at the bottom
QTimer expireTimer;
QWidget* owner; // TODO: think of a better name.
public:
BalloonTip();
~BalloonTip() {}
void setOwner(QWidget* widget) { owner = widget; }
void showMessage(const String& message, int timeout/*ms*/);
void showMessage(QWidget* widget, const String& message, int timeout/*ms*/) {
setOwner(widget); showMessage(message, timeout);
}
protected:
void paintEvent(QPaintEvent*);
};
#endif // BALLOON_TIP_H
/************************************************** ************/
// File: BalloonTip.cpp
#include "BalloonTip.h"
const QBrush BalloonTip::backgroundBrush(QColor(255, 255, 225));
const QPen BalloonTip::outlinePen(Qt::black, 1);
BalloonTip::BalloonTip() :
QWidget(NULL, Qt::ToolTip | Qt::FramelessWindowHint),
expireTimer(), owner(NULL) {
setAttribute(Qt::WA_TranslucentBackground);
expireTimer.setSingleShot(true);
connect(&expireTimer, SIGNAL(timeout()), this, SLOT(hide()));
}
/* TODO: Get this working. What it should do is start at a minimum size,
100,50 and increase the bounding box until all the text fits in AND
the aspect ration is between 1.5 and 2.0
QRect findBestSize(const QFontMetrics& fontMetrics, const String& message) {
QRect rect = fontMetrics.boundingRect(0, 0, 100, 50,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
float ratio = float(rect.width())/float(rect.height());
while(ratio < 1.5 || ratio > 2.0) {
rect.adjust(0, 0, 10, 5);
rect = fontMetrics.boundingRect(0, 0, rect.width(), rect.height(),
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
ratio = float(rect.width())/float(rect.height());
}
return rect;
}*/
void BalloonTip::showMessage(const String& message, int timeout/*ms*/) {
if (!owner) return;
QPoint ownerPos = owner->mapToGlobal(QPoint(0,0));
int width = (textPadding*2)+200; // Hard-code size for now
int height = (textPadding*2)+60+arrowHeight;
int x = ownerPos.x() - (width-15-(owner->width()/2));
int y = ownerPos.y() - height;
setWindowTitle(message);
move(x, y); resize(width, height);
expireTimer.start(timeout);
show();
update(); // Force redraw if it is already showing
}
/*------------------------------------------------------------------+
| Do custom painting. The entire rect is filled with the background |
| colour and the clip region is used to make an outline path. |
+------------------------------------------------------------------*/
void BalloonTip::paintEvent(QPaintEvent*) {
QPainter painter(this);
const int roundness = 12;
const int rectBase = height()-arrowHeight;
painter.setRenderHint(QPainter::Antialiasing);
QPoint p1(width()-35, rectBase-2);
QPoint p2(width()-15, height());
QPoint p3(width()-15, rectBase-2);
QPolygon triangle;
triangle << p1 << p2 << p3;
// TODO: This doesn't look quite right when drawing the bottom
// triangle bit. I need to erase the border of the rectangle
// in the triangle area, but anti-aliasing means that the triangle's
// border then slighly overlaps.
painter.setBrush(backgroundBrush);
painter.setPen(outlinePen);
painter.drawRoundedRect(1, 1, width()-2, rectBase-2, roundness, roundness);
painter.setPen(Qt::transparent);
painter.drawPolygon(triangle);
painter.setPen(outlinePen);
painter.drawLine(p1, p2);
painter.drawLine(p2, p3);
painter.drawText(textPadding, textPadding,
width()-textPadding, rectBase-textPadding,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap,
windowTitle());
}
Can anyone help me?
thanks
Since it appears that the only way to get such a widget is in the Window's taskbar (using QSystemTrayIcon), i decided to make my own.
I have it mostly working, but one thing i do not know how to do is make it resize so that it fits all the text but keeps a decent aspect ratio (i.e. does not get extremely long or tall).
This is what i have so far:
http://img214.imageshack.us/img214/5279/balloonx.png
I thought i could get the bounding box of the text within an initial rect, with Qt::TextWordWrap set, then check it's aspect ratio. If it is not right try again with a bigger box until i get one the right size.
I came up with this code:
QRect findBestSize(const QFontMetrics& fontMetrics, const String& message) {
QRect rect = fontMetrics.boundingRect(0, 0, 100, 50,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
float ratio = float(rect.width())/float(rect.height());
while(ratio < 1.5 || ratio > 2.0) {
rect.adjust(0, 0, 10, 5);
rect = fontMetrics.boundingRect(0, 0, rect.width(), rect.height(),
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
ratio = float(rect.width())/float(rect.height());
}
return rect;
}
But it doesn't work. And i am not sure what boundingRect() actually does. What is the rect that you give it and what rect does it return?
I am not even sure how my code should work, but i am thinking that there are many different sized rects which can contain the text (i.e. such that the text wraps to two lines, three lines, etc) and i want the rect which is about 3:1 ratio.
Here is the full code below:
// File: BalloonTip.h
#ifndef BALLOON_TIP_H
#define BALLOON_TIP_H
class BalloonTip : public QWidget {
private:
static const QBrush backgroundBrush;
static const QPen outlinePen;
static const int textPadding = 6;
static const int arrowHeight = 20; // That triangle bit at the bottom
QTimer expireTimer;
QWidget* owner; // TODO: think of a better name.
public:
BalloonTip();
~BalloonTip() {}
void setOwner(QWidget* widget) { owner = widget; }
void showMessage(const String& message, int timeout/*ms*/);
void showMessage(QWidget* widget, const String& message, int timeout/*ms*/) {
setOwner(widget); showMessage(message, timeout);
}
protected:
void paintEvent(QPaintEvent*);
};
#endif // BALLOON_TIP_H
/************************************************** ************/
// File: BalloonTip.cpp
#include "BalloonTip.h"
const QBrush BalloonTip::backgroundBrush(QColor(255, 255, 225));
const QPen BalloonTip::outlinePen(Qt::black, 1);
BalloonTip::BalloonTip() :
QWidget(NULL, Qt::ToolTip | Qt::FramelessWindowHint),
expireTimer(), owner(NULL) {
setAttribute(Qt::WA_TranslucentBackground);
expireTimer.setSingleShot(true);
connect(&expireTimer, SIGNAL(timeout()), this, SLOT(hide()));
}
/* TODO: Get this working. What it should do is start at a minimum size,
100,50 and increase the bounding box until all the text fits in AND
the aspect ration is between 1.5 and 2.0
QRect findBestSize(const QFontMetrics& fontMetrics, const String& message) {
QRect rect = fontMetrics.boundingRect(0, 0, 100, 50,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
float ratio = float(rect.width())/float(rect.height());
while(ratio < 1.5 || ratio > 2.0) {
rect.adjust(0, 0, 10, 5);
rect = fontMetrics.boundingRect(0, 0, rect.width(), rect.height(),
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, message);
ratio = float(rect.width())/float(rect.height());
}
return rect;
}*/
void BalloonTip::showMessage(const String& message, int timeout/*ms*/) {
if (!owner) return;
QPoint ownerPos = owner->mapToGlobal(QPoint(0,0));
int width = (textPadding*2)+200; // Hard-code size for now
int height = (textPadding*2)+60+arrowHeight;
int x = ownerPos.x() - (width-15-(owner->width()/2));
int y = ownerPos.y() - height;
setWindowTitle(message);
move(x, y); resize(width, height);
expireTimer.start(timeout);
show();
update(); // Force redraw if it is already showing
}
/*------------------------------------------------------------------+
| Do custom painting. The entire rect is filled with the background |
| colour and the clip region is used to make an outline path. |
+------------------------------------------------------------------*/
void BalloonTip::paintEvent(QPaintEvent*) {
QPainter painter(this);
const int roundness = 12;
const int rectBase = height()-arrowHeight;
painter.setRenderHint(QPainter::Antialiasing);
QPoint p1(width()-35, rectBase-2);
QPoint p2(width()-15, height());
QPoint p3(width()-15, rectBase-2);
QPolygon triangle;
triangle << p1 << p2 << p3;
// TODO: This doesn't look quite right when drawing the bottom
// triangle bit. I need to erase the border of the rectangle
// in the triangle area, but anti-aliasing means that the triangle's
// border then slighly overlaps.
painter.setBrush(backgroundBrush);
painter.setPen(outlinePen);
painter.drawRoundedRect(1, 1, width()-2, rectBase-2, roundness, roundness);
painter.setPen(Qt::transparent);
painter.drawPolygon(triangle);
painter.setPen(outlinePen);
painter.drawLine(p1, p2);
painter.drawLine(p2, p3);
painter.drawText(textPadding, textPadding,
width()-textPadding, rectBase-textPadding,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap,
windowTitle());
}
Can anyone help me?
thanks