Progress bar adding a threshold line - qt

I've progress bar to display the instantaneous value of real-time sensor.
the range for the QProgressBar is set as
ui->progressBar->setRange(0, 0.5*100); // to make range integer
ui->progressBar->setValue(sensor_read*100);
It is working ok. But I need to set a "threshold" value using a doubleSpinBox , as shown below:
The position of the dotted line (which is a QLabel) is the threshold that can be set using a doubleSpinBox.
My requirement is to change the height of the dotted line with respect to the threshold value.
the top and bottom y coordinates of the QProgressBar is 250 and 450
How do I get proportional y coordinate for the QLabel (dotted line) when I set a threshold value using doubleSpinBox?

Using a QLabel is possible, but it might be hard to get the exact position of the line inside the QLabel itself to adjust correctly. If your QLabel is only 1px high, this should work fine, otherwise you would need to adjust a bit more in detail.
So if the QLabel has the QProgressBar set as parent, then you can set the y coordinate of the QLabel inside the QProgressBar accordingly with something like below.
Call this function every time the threshold is modified, or when the widget (i.e. the progress bar) is resized. This is done by connecting on the signal:
connect(spinBox, &QDoubleSpinBox::valueChanged, this, &MyClass::updateThreshold);
void MyClass::updateThreshold()
{
const double threshold = spinBox->value();
int labelY = qRound(threshold / progressBar->maximum() * progressBar->height());
label->move(0, progressBar->height() - labelY); // y is inverted
}
void MyClass::resizeEvent(QResizeEvent *event)
{
updateThreshold();
}
Another way is to create a new class that inherits QProgressBar and to override the paintEvent() function. Inside the paintEvent, you could draw a horizontal line and the text using the QPainter at the correct position, using the same calculation than above. There you would not need a QLabel.

Related

How can I wrap text in QGraphicsItem?

1) How can I wrap text in a QGraphicsTextItem to fit a fixed rectangle, with width and height ?
Right now I am experimenting with creating a text, getting its bounding rectangle, and resizing it to fit the box - but I can't get wrapping.
class TTT: public QGraphicsTextItem {
TTT() {
{
setPlainText("abcd");
qreal x = m_itemSize.width()/boundingRect().width();
qreal y = m_itemSize.height()/boundingRect().height();
scale(x, y);
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
// experiment with clip regions
// text gets covered by hole in clip
QRegion r0(boundingRect().toRect());
QRegion r1(QRect(5, 5, 10, 10), QRegion::Ellipse);
QRegion r2 = r0.subtracted(r1);
painter->setClipRegion(r2);
painter->setBrush(Qt::yellow);
painter->drawRect(boundingRect());
QGraphicsTextItem::paint(painter, option, widget);
}
}
What makes wrapping happen, how can I trigger it ?
Right now, as I keep typing, the box is automatically expanding.
2) Is it possible to wrap the text in a QGraphicsItem / QGraphicTextItem subclass in a shape that is not a rectangle ?
(Something like in the image above)
I tried to use clipRegion, see code above, but I guess it is not the right way to go, clipping cuts the text but did not wrap.
Maybe it would... If I could figure out how to wrap text in the first place ?
Qt 4.8
You did not specify Qt version but try:
void QGraphicsTextItem::setTextWidth(qreal width)
Sets the preferred width for the item's text. If the actual text is wider than >the specified width then it will be broken into multiple lines.
If width is set to -1 then the text will not be broken into multiple lines >unless it is enforced through an explicit line break or a new paragraph.
The default value is -1.
In answer to 1) I'd opt not to use the QGraphicsTextItem, but draw the text directly in your QGraphicsItem's paint function using the drawText overloaded function, which takes a QTextOption parameter.
Using this, you can set the WrapMode, for example, with a call to
QTextOption::setWrapMode(QTextOption:: WordWrap)
As for 2) with a non-rectangular shape, I don't think Qt will do this for you.
Doing it yourself you can use QFontMetrics, to work out just how much text would fit in each line, depending upon where it lies within its bounding item.
Alternatively, you could adapt the concept of a text-to-path method.

Shift `QGraphicsTextItem` position relative to the center of the text?

I have a number of classes that inherit from QGraphicsItem, that get to be arranged in a certain way. For simplicity of calculations, I made the scenes, and items, centered in (0, 0) (with the boundingRect() having +/- coordinates).
QGraphicsTextItem subclass defies me, its pos() is relative to top left point.
I have tried a number of things to shift it so it centers in the text center (for example, the suggested solution here - the code referenced actually cuts my text and only shows the bottom left quarter).
I imagined that the solution should be something simple, like
void TextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->translate( -boundingRect().width()/2.0, -boundingRect().height()/2.0 );
QGraphicsTextItem::paint(painter, option, widget );
}
the above "sort of" works - but as I increase the item scale -> increase the font, the displayed item is cut off...
I tried to set the pos() - but the problem is, I still need to track the actual position on the scene, so I cannot just replace it.
A slightly unpleasant side effect - centering the QGraphicsView on the element does not work either.
How can I make my QGraphicsTextItem show its position relative to the center of the text ?
Edit: one of the experiments of changing the boundingRect():
QRectF TextItem::boundingRect() const
{
QRectF rect = QGraphicsTextItem::boundingRect();
rect.translate(QPointF(-rect.width()/2.0, -rect.height()/2.0));
return rect;
}
I had to shift the initial position, as well as the resize, to trigger a new position - I was unable to do it in paint() because, as I thought from the start, any repaint would continuously recalculate the position.
Only the initial position needs to be adjusted - but as the font size (or style...) changes, its bounding rectangle also changes, so the position must be recalculated - based on previous position.
In the constructor,
setPos(- boundingRect().width()/2, - boundingRect().height()/2);
in the function that modifies item (font) size,
void TextItem::setSize(int s)
{
QRectF oldRect = boundingRect();
QFont f;
f.setPointSize(s);
setFont(f);
if(m_scale != s)
{
m_scale = s;
qreal x = pos().x() - boundingRect().width()/2.0 + oldRect.width()/2.0;
qreal y = pos().y() - boundingRect().height()/2.0 + oldRect.height()/2.0;
setPos(QPointF(x, y));
}
}

Is there a way to specify selection area for QGraphicsItem?

I have some kind of histogram drawn with QGraphicsRectItem; some of these rectangles are long, some short. While it is no problem to select a long rectangle, one might have difficulties with the short ones.
So I was wondering if there is a way to specify custom area that would trigger mousePressEvent for the item, so rectangles would have the same size selection area.
Apart from rectangles I also draw some text on the same line. Would it be helpful to group them somehow and write mousePressEvent for the group instead?
Thank you!
It would be useful to see an example image of what you're asking, but I think I know what mean.
Personally, I'd just create my own class, inherited from QGraphicsItem (or QGraphicsObject, if you want signals and slots). This class can then provide a boundingRect() of the full area that you want to represent the area to be selected, but the paint() function only draw the visible part of the bar. Something like this: -
class Bar: public QGraphicsItem
{
Q_OBJECT
public:
Bar(int x, int y, int width, int height, int visibleBarHeight);
// returns the area of the object
QRectF boundingRect() const;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
};
In the paint function, you would draw a rect up to the visible bar height, but in boundingRect, return the full rect. That way, the bar could visibly be very small, but the object is of full height and would respond to mouse selection above the visible area of the bar.
As for the text, you could either add it as a child to this object and signal the parent when it gets selected, or extend the boundingRect of this Bar class and render it in the paint function.
Note that boundingRect is the area represented by the object, in local coordinates. If you have an object that isn't defined by a rectangle, you'd also want to implement the shape() function. By default, shape() calls boundingRect().

How to remove/clear previously drawn lines before redrawing new lines in Qt?

I am drawing few lines using paintEvent(QPaintEvent * event). Sometime later if I want to draw new lines then the previously drawn lines are not cleared/removed. How could I draw the new lines only, by removing/clearing old lines. Is there any property to clear the previously drawn lines.Please let me know.
void QGraphWidget::paintEvent(QPaintEvent * event)
{
const QRect & rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
drawLines(painter, rect);//calling painter.drawLine() inside
}
Thanks...
In my opinion, the correct approach is to clear the area before drawing new lines. This can be achieved either by using the autoFillBackground property as proposed by Stephen Chu, or by calling manually the QPainter::eraseRect() before drawing your lines in the QGraphWidget::paintEvent method.
void QGraphWidget::paintEvent(QPaintEvent * event)
{
const QRect & rect = event->rect();
QPainter painter(this);
painter.eraseRect(rect);
painter.setRenderHint(QPainter::Antialiasing);
drawLines(painter, rect);
}
Another option is to draw "negatively" the lines from the previous call to QGraphWidget::paintEvent. If you store the coordinate of your old lines, you might first draw lines using the background brush and then draw your new lines using the foreground brush. See code sample that might fit into you drawLines method. Note that if you draw something else in your widget, drawing the lines negatively might erase some of the other graphics which is why the first approach, erasing all the GraphWidget area, is better.
// save the current brush
QBrush currentBrush = painter.brush();
// draw the old line with the background brush
painter.setBrush(background());
painter.drawLine(oldLine);
// draw the new line with the current brush
painter.setBrush(currentBrush);
painter.drawLine(newLine);
You want to fill your widget with window background color before redraw. Set autoFillBackground to true after you create the widget and Qt will handle this for you
I don't think there is a specific call to remove the line. If you're repainting the entire area each time paintEvent() function is called you shouldn't see previous lines. If you're not repainting the area, you'll have to draw over the line yourself. This code for instance is drawing a line in a different position each time the method is invoked:
QPainter painter(this);
painter.setBrush(QBrush(Qt::red));
painter.drawRect(rect());
painter.setPen(QPen(Qt::yellow));
static int i = 0;
painter.drawLine(QPointF(i, i), QPointF(i, height() - i));
i++;
but "previous lines" are cleared completely. If you want to keep those you'll have to repaint only a specific area or you'll have to repaint those.
This is one way to delete whole line from QT.
me->setFocus();
int pos;
QTextCursor tc= me->textCursor();
pos=tc.columnNumber();
tc.select(QTextCursor::LineUnderCursor);
QString str=tc.selectedText();
tc.removeSelectedText();
tc.movePosition(QTextCursor::NextBlock,QTextCursor::MoveAnchor);
tc.insertText(str);
tc.insertBlock();
tc.movePosition(QTextCursor::PreviousBlock,QTextCursor::MoveAnchor);
tc.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
me->setTextCursor(tc);
return true;

Zooming in/out on a mouser point ?

As seen in the pictures.
I have QWidget inside a QScrollArea.
QWidget act as a render widget for cell image and some vector based contour data.
User can performe zoom in/out and what simply happens is, it changes the QPainters scale and change the size of QWidget size accordinly.
Now I want to perform the zooming in/out on the point under the mouse. (like zooming action in GIMP).
How to calculate the new positions of the scrollbars according to the zoom level ?
Is it better to implement this using transformations without using a scrollarea?
One solution could be to derive a new class from QScrollArea and reimplementing wheelEvent for example so that zooming is performed with the mouse wheel and at the current mouse cursor position.
This method works by adjusting scroll bar positions accordingly to reflect the new zoom level. This means as long as there is no visible scroll bar, zooming does not take place under mouse cursor position. This is the behavior of most image viewer applications.
void wheelEvent(QWheelEvent* e) {
double OldScale = ... // Get old scale factor
double NewScale = ... // Set new scale, use QWheelEvent...
QPointF ScrollbarPos = QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value());
QPointF DeltaToPos = e->posF() / OldScale - widget()->pos() / OldScale;
QPointF Delta = DeltaToPos * NewScale - DeltaToPos * OldScale;
widget()->resize(/* Resize according to new scale factor */);
horizontalScrollBar()->setValue(ScrollbarPos.x() + Delta.x());
verticalScrollBar()->setValue(ScrollbarPos.y() + Delta.y());
}
Will void QScrollArea::ensureVisible(int x, int y, int xmargin = 50, int ymargin = 50) do what you need?
You need to pick up the wheelEvent() on the QWidget, get the event.pos() and pass it into the QscrollArea.ensureVisible(), right after scaling your QWidget.
def wheelEvent(self, event):
self.setFixedSize(newWidth, newHeight)
self.parent().ensureVisible(event.pos())
That should more or less produce what you want.

Resources