The subjects of our project is making a program who simulate a Fusion.
We have some problem with the colliding with our classe Fusion. We want to make a shape complex for our colliding.
printScreenFusionProgramm
Our shape is two circle near each other and we dont want to have a bounding rect but shape "complex"...
this is our Fusion class
Fusion::Fusion(int x, int y)
{
this->setPos(x, y);
}
void Fusion::shape(){
//...
}
void Fusion::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
//set the color
QBrush brushColor(QColor(Qt::blue));
painter->setBrush(brushColor);
painter->setPen(QColor(Qt::blue));
painter->drawEllipse(0,0,40,40);
painter->drawEllipse(-20,-20,40,40);
}
void Fusion::doCollision()
{
// get a new position
// change the angle with randomness
if(qrand() %1)
{
setRotation(rotation() + (180 + (qrand() % 10)));
}
else
{
setRotation(rotation() + (180 + (qrand() % -10)));
}
// check if the new position is in bounds
QPointF newPoint = mapToParent(-(boundingRect().width()), -(boundingRect().width() + 2));
if(!scene()->sceneRect().contains((newPoint)))
{
// move back in bounds
newPoint = mapToParent(0,0);
}
else
{
// set the new position
setPos(newPoint);
}
}
void Fusion::advance(int step)
{
if(!step) return;
if(!scene()->collidingItems(this).isEmpty())
{
doCollision();
}
setPos(mapToParent(0, -1));
}
You need to reimplement the "shape" method for your graphics items to return the actual shape of your object. You can return any shape you want in a QPainterPath, and Qt will use that for collision detection.
Related
There is a large set of rectangles. Given a point, I need to quickly find if this point belongs to at least one of the rectangles, and find one.
A space partitioning index would do the trick. You could, in a pinch, add all the rectangles to the graphics scene — it will index them for you and hit tests will have O(log(N)) cost. You’d want a “low cost” item like:
class RectItem : public QGraphicsItem {
QSizeF size;
public:
RectItem(const QRectF &r = {}) : size(r.size()) {
setFlag(ItemHasNoContents);
setPos(r.topLeft());
}
RectItem(const RectItem &o) :
RectItem(o.boundingRect()) {
if (o.scene()) o.scene()->addItem(this);
}
void setRect(const QRect &r) {
prepareGeometryChange();
setPos(r.topLeft());
size = r.size();
}
QRectF boundingRect() const override {
return {{}, size};
}
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget *) override {}
};
Then:
class HitCache {
QGraphicsScene scene;
std::vector<RectItem> items;
public:
int addRect(const QRectF &rect) {
items.emplace_back(rect);
return items.size()-1;
}
int itemAt(const QPointF &p) const {
auto *item = scene.itemAt(p, {});
if (item)
return item - &items[0];
return -1;
}
QRectF operator[](int i) const {
return items[i].boundingRect();
}
RectItem &get(int i) {
return items[i];
}
};
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:
So I have looked at other similar questions on stackoverflow but none seem to help. The problem is that the animation doesn't occur, but once I click somewhere in the QGraphicsView, it updates to the end position.
I'm animating QGraphicsRectItem with QPropertyAnimation, so I made a new class and extended QObject and QGraphicsRectItem:
class MyGraphicsRectItem : public QObject, public QGraphicsRectItem {
Q_OBJECT
Q_PROPERTY(QPointF position READ position WRITE setPosition)
Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)
public:
...
QPointF position();
void setPosition(QPointF animBox);
QRectF geometry();
void setGeometry(QRectF geo);
...
}
One common problem with regarding this problem is that QPropertyAnimation goes out of scope when the function finishes, but I think I circumvented this problem using QAbstractAnimation::DeleteWhenStopped. In my MainWindow.cpp, I have:
group = new QParallelAnimationGroup;
for (int i = 0; i < 10 ; i += 1) {
MyGraphicsRectItem *temp = dynamic_cast<MyGraphicsRectItem*>(histZToItem[i]);
QPropertyAnimation *anim = new QPropertyAnimation(temp, "position");
anim->setDuration(500);
anim->setStartValue(temp->pos());
QPropertyAnimation *geo = new QPropertyAnimation(temp, "geometry");
geo->setDuration(500);
geo->setStartValue(temp->rect());
geo->setEndValue(QRectF(0, 0, colWidth, -80));
if (i > anchorID) {
anim->setEndValue(QPointF(40 + spaceWidth * (i) + colWidth * (i - 1), graphScene->height() - 40));
} else {
anim->setEndValue(QPointF(40 + spaceWidth * (i + 1) + colWidth * (i), graphScene->height() - 40));
}
group->addAnimation(geo);
group->addAnimation(anim);
}
group->start(QAbstractAnimation::DeleteWhenStopped);
Any ideas?
Edit
Here are my implementations for position, setPosition, geometry and setGeometry:
QPointF MyGraphicsRectItem::position()
{
return QGraphicsRectItem::pos();
}
void MyGraphicsRectItem::setPosition(QPointF animPos)
{
QGraphicsRectItem::setPos(animPos);
}
QRectF MyGraphicsRectItem::geometry()
{
return rect();
}
void MyGraphicsRectItem::setGeometry(QRectF geo)
{
setRect(geo);
}
EDIT 2
Here's the constructor:
MyGraphicsRectItem::MyGraphicsRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent) :QGraphicsRectItem(x, y, width, height, parent) {
setAcceptHoverEvents(true);
}
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);
I make my own class from QWidget with redefine of paintEvent(), mousePressEvent(), mouseReleaseEvent() and mouseMoveEvent(). All that methods for move widgets over other widget (yellow).
When i create my widgets in a layout, it looks like this:
But when I move black widget to the bottom and red to the top like this:
and resize window, all widgets refresh to their align positions:
But i want, when i move one widget higher then another, the widgets should align in layout in new places, like this:
Which function i should redefine to do it?
P.S.
There is a piece of code, that can move widgets positions inside layout (change their indexes), but i don't know how find out their (x,y) position to calculate new indexes in layout. I think, that i can do it in resizeEvent().
But it when it event was emitted, positions already changed to old. (like before moveing on 1 picture), and i need positions after moveing (like on secon picture). How can i get position of widget before it will be aligned?
or How can i change order of widget in layout by drag and drop with the mouse?
I write my own widget, then redefine following methods: mouseReleaseEvent(), paintEvent(), mousePressEvent(), mouseMoveEvent(). In mousePressEvent() I hold old X and Y positions and mouse position on figure. Then in mouseMoveEvent() i calculate if minimum distance of mouse move is riched and move widget to new position (it not moves widget index in layout). After it, if emitted mouseReleaseEvent() i just calculate new index of moving widget and change and update parent layout. If widget moves less then it height, then layout just updates without changing widget index.
void SimpleWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if (!IsMinimumDistanceRiched(event))
{
return;
}
int y = event->globalY() - mouseClickY + oldY;
int BottomBorder = parentWidget->geometry().height() - this->geometry().height();
if(y < 0) y = 0;
else if(y > BottomBorder) y = BottomBorder;
move(oldX, y);
}
void SimpleWidget::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
dragStartPosition = event->pos();
oldX = this->geometry().x();
oldY = this->geometry().y();
mouseClickX = event->globalX();
mouseClickY = event->globalY();
}
bool SimpleWidget::IsMinimumDistanceRiched(QMouseEvent *event)
{
return (event->pos() - dragStartPosition).manhattanLength() >= QApplication::startDragDistance();
}
bool SimpleWidget::moveInLayout(QWidget *widget, MoveDirection direction)
{
QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(widget->parentWidget()->layout());
const int index = myLayout->indexOf(widget);
if (direction == MoveUp && index == 0)
{
return false;
}
if (direction == MoveDown && index == myLayout->count()-1 )
{
return false;
}
const int newIndex = direction == MoveUp ? index - 1 : index + 1;
myLayout->removeWidget(widget);
myLayout->insertWidget(newIndex , widget);
return true;
}
void SimpleWidget::paintEvent(QPaintEvent *)
{
QStyleOption o;
o.initFrom(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);
}
void SimpleWidget::mouseReleaseEvent(QMouseEvent *)
{
int y = geometry().y();
MoveDirection direct;
int offset;
if(oldY > y)
{
offset = oldY - y;
direct = MoveUp;
}
else if(oldY < y)
{
offset = y - oldY;
direct = MoveDown;
}
int count = offset/height();
for(int i = 0; i < count; i++)
{
moveInLayout(this, direct);
}
update();
QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(this->parentWidget->layout());
myLayout->update();
this->saveGeometry();
}