free hand draw in qgraphicsview - qt

i have gone through the scribble example for how to draw free form . i want the same free form draw using QGraphicsitem on qgraphicsview. i should draw it as a graphicsitem as i can move the selected free form every where in the scene.
i tried this
DrawnPathItem = this->scene()->addPath(QPainterPath());
QGraphicsLineItem liner;
liner.setLine( QLineF(startPoint, endPoint) );
liner.setPen(QPen(Qt::red));
QPainterPath path = DrawnPathItem->path();
path.setFillRule(Qt::WindingFill);
path.addPath( liner.shape() );
path = path.simplified();
DrawnPathItem->setPath(path);

i did it
using
void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
myPath = new QGraphicsPathItem();
previous = event->scenePos();
QPainterPath p;
p.moveTo(previous);
myPath->setPath(p);
this->addItem(myPath);
}
void ::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(myPath)
{
QPainterPath path = myPath->path();
previous = event->scenePos();
path.lineTo(previous);
myPath->setPath(path);
}

Related

Implement selection on QChartView

I want to make an implementation of chart selection based on QChart and QChartView.
The family of the classes have a big advantage - easy use of openGL and animations, for example:
QLineSeries *series = new QLineSeries();
series->setUseOpenGL(true); // <==
QChart *chart = new QChart();
chart->addSeries(series);
chart->setAnimationOptions(QChart::AllAnimations); // <==
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
The QChartView class provides the useful zoom feature - QChartView::setRubberBand():
chartView->setRubberBand(QChartView::RectangleRubberBand);
The main problem is that the rubber band can be used only for zoom, but I need to implement it for horizontal selection without zoom, as the feature usually implemented in audio editors:
Now, when I have inherit QChartView, I can disable zoom after selection:
class ChartView : public QChartView
...
bool m_drawRubberBand;
QRubberBand m_rubberBand;
...
ChartView::ChartView(QChart *chart, QWidget *parent)
: QChartView(chart, parent)
{
setRubberBand(QChartView::HorizontalRubberBand);
}
...
// Just copy-paste from the Qt 5 sources - file \Src\qtcharts\src\charts\qchartview.cpp:
/*!
If the rubber band rectangle is displayed in the press event specified by
\a event, the event data is used to update the rubber band geometry.
Otherwise, the default QGraphicsView::mouseMoveEvent() implementation is called.
*/
void ChartView::mouseMoveEvent(QMouseEvent *event)
{
if (m_drawRubberBand && m_rubberBand.isVisible())
{
QRect rect = chart()->plotArea().toRect();
int width = event->pos().x() - m_rubberBandOrigin.x();
int height = event->pos().y() - m_rubberBandOrigin.y();
if (!rubberBand().testFlag(VerticalRubberBand))
{
m_rubberBandOrigin.setY(rect.top());
height = rect.height();
}
if (!rubberBand().testFlag(HorizontalRubberBand))
{
m_rubberBandOrigin.setX(rect.left());
width = rect.width();
}
m_rubberBand.setGeometry(QRect(m_rubberBandOrigin.x(), m_rubberBandOrigin.y(), width, height).normalized());
}
else
{
QGraphicsView::mouseMoveEvent(event);
}
}
Then I can just don't implement the zoom action on the mouse key release event:
void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
if (m_rubberBand.isVisible())
{
if (event->button() == Qt::LeftButton)
{
m_drawRubberBand = false;
do_nothing(); // <==
}
}
}
So, my questions now:
How borders of the the visual rubber band can be mapped to real chart's coordinates. I.e., how can the selection be mapped into a line series on the chart? Now I receive same wrong coordinates:
void MyView::resizeEvent(QResizeEvent *event)
{
QChartView::resizeEvent(event);
QRect rct(QPoint(10, 10), QPoint(20, 20));
qDebug() << mapToScene(rct); <==
}
Output:
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
QPolygonF(QPointF(10,10)QPointF(21,10)QPointF(21,21)QPointF(10,21))
...
How can an existing rubber selection be proportionally resized together with the view?
Edit: May be it is a useful keyword - QGraphicsScene::setSelectionArea().
The Qt 5 chip example which provides nice rubber band selection, but the example based on QGraphicsView, not on QChartView.
The question is resolved thanks to the reply to this answer: Get mouse coordinates in QChartView's axis system
The key moment: it was necessary to invoke QChart::mapToValue() for a correct coordinates transform:
QPointF ChartView::point_to_chart(const QPoint &pnt)
{
QPointF scene_point = mapToScene(pnt);
QPointF chart_point = chart()->mapToValue(scene_point);
return chart_point;
}
And the inverse transformation:
QPoint ChartView::chart_to_view_point(QPointF char_coord)
{
QPointF scene_point = chart()->mapToPosition(char_coord);
QPoint view_point = mapFromScene(scene_point);
return view_point;
}
That's how I have implemented resize of the rubber band on the resizeEvent.
Firstly, I save the current rubber band on mouse release event:
void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
if (m_rubberBand.isVisible())
{
update_rubber_band(event);
m_drawRubberBand = false;
save_current_rubber_band(); <==
}
}
Where:
void ChartView::update_rubber_band(QMouseEvent * event)
{
QRect rect = chart()->plotArea().toRect();
int width = event->pos().x() - m_rubberBandOrigin.x();
int height = event->pos().y() - m_rubberBandOrigin.y();
if (!rubberBand().testFlag(VerticalRubberBand))
{
m_rubberBandOrigin.setY(rect.top());
height = rect.height();
}
if (!rubberBand().testFlag(HorizontalRubberBand))
{
m_rubberBandOrigin.setX(rect.left());
width = rect.width();
}
m_rubberBand.setGeometry(QRect(m_rubberBandOrigin.x(), m_rubberBandOrigin.y(), width, height).normalized());
}
And:
void ChartView::save_current_rubber_band()
{
QRect rect = m_rubberBand.geometry();
QPointF chart_top_left = point_to_chart(rect.topLeft());
m_chartRectF.setTopLeft(chart_top_left);
QPointF chart_bottom_right = point_to_chart(rect.bottomRight());
m_chartRectF.setBottomRight(chart_bottom_right);
}
And how I repaint the rubber on the resize event:
void ChartView::resizeEvent(QResizeEvent *event)
{
QChartView::resizeEvent(event);
if (m_rubberBand.isVisible())
{
restore_rubber_band();
}
apply_nice_numbers();
}
Where:
void ChartView::restore_rubber_band()
{
QPoint view_top_left = chart_to_view_point(m_chartRectF.topLeft());
QPoint view_bottom_right = chart_to_view_point(m_chartRectF.bottomRight());
m_rubberBandOrigin = view_top_left;
m_rubberBand.setGeometry(QRect(view_top_left, view_bottom_right));
}
And don't forget about the "nice numbers":
void ChartView::apply_nice_numbers()
{
QList<QAbstractAxis*> axes_list = chart()->axes();
for each(QAbstractAxis* abstract_axis in axes_list)
{
QValueAxis* value_axis = qobject_cast<QValueAxis*>(abstract_axis);
if (value_axis)
{
value_axis->applyNiceNumbers();
}
}
}
This logic in action.
Before resize:
After resize:

How can we make a QRubberBand semi-transparent

I have already used
setOpacity();
setAttribute(Qt:WA_TranseculentBackground:)
even i have tied all the available solution nothing has effect.
this is my code
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight);
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
rubberBand->hide();
if(e->x()<ui->videoShowLabel->x()||e->y()<ui->videoShowLabel->y())
{
selectWithInLabel.critical(0,"Error", "Select within the LABEL !");
selectWithInLabel.setFixedSize(500, 200);
}
else{
topLeft = e->pos();
myPoint = ui->videoShowLabel->mapFromGlobal(this->mapToGlobal(e->pos()));
}
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->setWindowOpacity(0.5);
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new RubberBand(RubberBand::Rectangle, this);
}
What should i do to make rubberBand semiTransparent
I assume you sub classed QRubberBand (RubberBand).
After calling the setWindowopacity the paint event is generated (http://doc.qt.io/qt-5/qwidget.html#windowOpacity-prop)
So redefine the paint event in RubberBand class.
Inside the paint event call "initStyleOption" (given below)
http://doc.qt.io/qt-5/qrubberband.html#initStyleOption
By calling "initStyleOption" you can set the rubber band parameters for drawing.
The real issue with making the QRubberband semi-transparent is that mplayer is painting on a window without Qt having any knowledge of it. Hence Qt itself cannot act as a compositor to generate the required effect.
One possibility would be to make the QRubberBand a top level window. That way the compositing is the responsibility of the underlying graphics system rather than Qt.
With that in mind try the following. Firstly a utility base class to manage the geometry...
class abstract_rubber_band {
public:
virtual QRect get_geometry () const
{
return(QRect(m_parent->mapFromGlobal(widget().geometry().topLeft()), widget().size()));
}
virtual void set_geometry (const QRect &rect)
{
widget().setGeometry(map_rect(rect));
}
protected:
explicit abstract_rubber_band (QWidget *parent)
: m_parent(parent)
{}
/*
* #param point Coords relative to effective parent.
*/
QPoint map_point (QPoint point) const
{
if (point.x() < 0)
point.rx() = 0;
else if (point.x() >= m_parent->width())
point.rx() = m_parent->width() - 1;
if (point.y() < 0)
point.ry() = 0;
else if (point.y() >= m_parent->height())
point.ry() = m_parent->height() - 1;
point = m_parent->mapToGlobal(point);
return(point);
}
QRect map_rect (QRect rect) const
{
return(QRect(map_point(rect.topLeft()), map_point(rect.bottomRight())));
}
private:
QWidget &widget ()
{
return(dynamic_cast<QWidget &>(*this));
}
const QWidget &widget () const
{
return(dynamic_cast<const QWidget &>(*this));
}
QWidget *m_parent;
};
Now use the above as a base of the required rubber band class...
class rubber_band: public abstract_rubber_band,
public QRubberBand {
using super = QRubberBand;
public:
/*
* #param parent Note that this isn't actually used as the
* parent widget but rather the widget to which
* this rubber_band should be confined.
*/
explicit rubber_band (QWidget *parent)
: abstract_rubber_band(parent)
, super(QRubberBand::Rectangle)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(rect(), QColor::fromRgbF(0.5, 0.5, 1.0, 0.25));
QPen pen(Qt::green);
pen.setWidth(5);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
/*
* Display the current geometry in the top left corner.
*/
QRect geom(get_geometry());
painter.drawText(rect().adjusted(5, 5, 0, 0),
Qt::AlignLeft | Qt::AlignTop,
QString("%1x%2+%3+%4").arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
}
};
The above rubber_band class should almost be a drop in replacement for QRubberBand. The main difference is that rather than reading/writing its geometry with geometry/setGeometry you must use get_geometry/set_geometry -- those will perform the mapping to/from global coordinates.
In your particular case create the rubber_band with...
rubberBand = new rubber_band(ui->videoShowLabel);

How to save an image in a QGraphicsView into a bmp/jpg

I am a newbie for Qt.
The question is that: After turning an image into the QGraphicsView, I use a qrubberband to select the cropping area of the image. It is successful to select the croppping region at the moment.But I don't know how to save that cropping region into a jpg/bmp afterwards. Note that I made an ui component for the GraphicsView called CGraphicsView.
void CGraphicsView::mousePressEvent
( QMouseEvent* event)
{
mypoint = event->pos();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);//new rectangle band
rubberBand->setGeometry(QRect(mypoint, QSize()));
rubberBand->show();
}
void CGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
if (rubberBand)
{
rubberBand->setGeometry(QRect(mypoint, event->pos()).normalized());//Area Bounding
}
}
void CGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
if (rubberBand)
{
QRect myRect(mypoint, event->pos());
rubberBand->hide();// hide on mouse Release
QImage copyImage; //<= this Qimage hold nothing
copyImage = copyImage.copy(myRect);
}
}
There is special method in Qt. It allows get screenshot of view.
QString fileName = "path";
QPixmap pixMap = QPixmap::grabWidget(graphicsView, rectRegion);
pixMap.save(fileName);
Save() method can save picture in different formats and with compression.
Also with grabWidget() method you can grab another widgets too. Moreover, this method take QRect as argument so you can create screenshot of region what you need.
You can save a part of your scene to an image like :
QPixmap pixmap=QPixmap(myRect.size());
QString filename = QFileDialog::getSaveFileName( this->parentWidget(), tr("Save As"), tr("image.png"));
if( !filename.isEmpty() )
{
QPainter painter( &pixmap );
painter.setRenderHint(QPainter::Antialiasing);
scene->render( &painter, pixmap.rect(),myRect, Qt::KeepAspectRatio );
painter.end();
pixmap.save(filename,"PNG");
}

Can't get positions of QGraphicsItems in scene

I am trying to get the positions of the graphicsitems in the scene.
But their QPointF value always remains (0,0).
I am painting when mouse-click event occurs. On debugging scene->items(), I get
(QGraphicsItem(this =0x22edff0, parent =0x0, pos =QPointF(0, 0) , z = 0 , flags = ( ) ) )
for each graphics item in scene but with different memory address.
This is my mainwindow.cpp code:
#include "mainwindow.h"
#include <QDebug>
MainWindow::MainWindow()
{
scene = new QGraphicsScene;
view = new QGraphicsView;
view->setScene(scene);
button = new QPushButton("Item");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button);
layout->addWidget(view);
setLayout(layout);
connect(button, SIGNAL(clicked()), this, SLOT(createItem()));
}
void MainWindow::createItem()
{
myEntity = new Item;
scene->addItem(myEntity);
count_items();
}
void MainWindow::count_items()
{
qDebug() << scene->items().count();
qDebug() << scene->items();
}
MainWindow::~MainWindow()
{}
This is my item.cpp code:
#include "item.h"
Item::Item()
{
ClickFlag = true;
PaintFlag = false;
}
Item::~Item(){}
QRectF Item::boundingRect() const
{
// outer most edges
return QRectF(0,0,1450,1400);
}
void Item::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button()==Qt::LeftButton){
if(ClickFlag){
x = event->pos().x();
y = event->pos().y();
PaintFlag = true;
ClickFlag = false;
}
}
}
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
if(PaintFlag){
QPen paintPen;
paintPen.setWidth(4);
pt.setX(x);
pt.setY(y);
painter->setPen(paintPen);
painter->drawPoint(x,y);
update();
}
}
I can't seem to find the position of these items correctly.
This task is supposed to be implemented in another way. For example:
Use QGraphicsScene::addEllipse to add small ellipse (which will look like a point) to the scene. Save the pointer to it in a class variable. The ellipse itself should be at the center, e.g. (-1, -1, 2, 2).
Reimplement QGraphicsScene::mousePressEvent, detect mouse clicks and call setPos for the ellipse item (or add new ellipse each time and immediately call setPos if you need multiple points).
Use QGraphicsItem::pos to get previously set positions.
Reimplementing QGraphicsItem::paint is usually an over-complication. Qt have plenty of item classes for all common needs. Just build your scene from geometric primitives, pixmaps, etc.

Adding a QSizeGrip to the corner of a QLabel

I'm attempting to produce a widget that consists of a text display that can be resized by the user grabbing the lower right corner. So far I've been able to generate this:
I've applied a red background to the layout to make it more obvious what's going on. I've used the following code to generate this:
m_sizeGrip = new QSizeGrip( this );
m_layout = new QHBoxLayout( this );
m_label = new QLabel( this );
m_layout->setContentsMargins( QMargins() );
m_layout->setSpacing( 0 );
m_layout->addWidget( m_label );
m_layout->addWidget( m_sizeGrip, 0, Qt::AlignBottom | Qt::AlignRight );
setWindowFlags( Qt::SubWindow );
Basically, it's a horizontal layout with the label and grip added to it, which is then installed on a QWidget. My problem is that I'd like the grip to be on the lower right corner of the label, rather than the parent widget. I'd also like to make it invisible while keeping it enabled.
Or perhaps I'm going about this the wrong way. My ultimate goal is to have a textual display widget that can be resized by the user either horizontally or vertically, but doesn't have a visible grip that would obscure the text. Am I already on the right track with the code above, or is there a better way to achieve this?
You could create a custom QLabel for that. The idea would be to track mouse move events (which by default only fire when a mouse button is pressed), and resize based on how much the mouse traveled since the last event.
This allows you to control exactly how to display the "gripper" (if at all) and what shape it should have. You can constrain the resizing to vertical or horizontal (or not).
Here's a demo of how you could do that (resizes both ways). Warning: this might wreak havoc in your layout.
#include <QtGui>
class GripLabel: public QLabel
{
Q_OBJECT
public:
GripLabel(QString const& title, QWidget* parent = 0)
: QLabel(title, parent),
resizing(false),
gripSize(10, 10)
{
// Prevent the widget from disappearing altogether
// Bare minimum would be gripSize
setMinimumSize(100, 30);
}
QSize sizeHint() const
{
return minimumSize();
}
protected:
bool mouseInGrip(QPoint mousePos)
{
// "handle" is in the lower right hand corner
return ((mousePos.x() > (width() - gripSize.width()))
&& (mousePos.y() > (height() - gripSize.height())));
}
void mousePressEvent(QMouseEvent *e)
{
// Check if we hit the grip handle
if (mouseInGrip(e->pos())) {
oldPos = e->pos();
resizing = true;
} else {
resizing = false;
}
}
void mouseMoveEvent(QMouseEvent *e)
{
if (resizing) {
// adapt the widget size based on mouse movement
QPoint delta = e->pos() - oldPos;
oldPos = e->pos();
setMinimumSize(width()+delta.x(), height()+delta.y());
updateGeometry();
}
}
void paintEvent(QPaintEvent *e)
{
QLabel::paintEvent(e);
QPainter p(this);
p.setPen(Qt::red);
p.drawRect(width()-gripSize.width(), height()-gripSize.height(),
gripSize.width(), gripSize.height());
}
private:
bool resizing;
QSize gripSize;
QPoint oldPos;
};
Sample main:
#include "griplabel.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QWidget *w = new QWidget;
QPushButton *b = new QPushButton("button");
GripLabel *l = new GripLabel("Hello");
QHBoxLayout *y = new QHBoxLayout;
y->addWidget(b);
y->addWidget(l);
y->setSizeConstraint(QLayout::SetFixedSize);
w->setLayout(y);
w->show();
return app.exec();
}

Resources