I have QGraphicsView, which has multiple QGraphicsItem's. On this view, I am applying some transformation using features like Zoom-in, zoom-out, fit -In etc.
So before applying fit-in feature on my current view, I want to store view's current transformation ( view's current situation ) in a variable. And then I want to apply Fit-in feature. ( so that, in Undo-Redo I can use that stored position)
But I do not know how to store current position of view in a variable. I tried this way :
void myClass::FitIn()
{
QTransform t = _view->transform();
QScrollBar* hrSBar = _view->horizontalScrollBar();
QScrollBar* verSBar = _view->verticalScrollBar();
myCommand* c = new myCommand(t,hrSBar,verSBar);
undoStack->push(c);
_view->resetTransform();
}
How to store, current view in another variable ?
***** EDIT ******
myCommand.cpp
myCommand::myCommand(QTransform t, QScrollBar *hr, QScrollBar *vr)
{
this->trans = t;
this->horizontalSBar = hr;
this->verticalSBar = vr;
}
void myCommand::undo()
{
_mView->setTransform(trans);
_mView->horizontalScrollBar()->setValue(horizontalSBar->maximum());
_mView->verticalScrollBar()->setValue(verticalSBar->maximum());
}
void myCommand::redo()
{
_mView->setTransform(trans); // Segmentation Fault occurs
_mView->horizontalScrollBar()->setValue(horizontalSBar->maximum());
_mView->verticalScrollBar()->setValue(verticalSBar->maximum());
}
myCommand.h
class myCommand: public QUndoCommand
{
public:
myCommand(QTransform t, QScrollBar* hr, QScrollBar* vr);
private:
QGraphicsScene* _mScene;
QGraphicsView* _mView;
}
As revealed by source code of mapToScene, three parameters define the part of the scene that is visible on the viewport:
QGraphicsView::transform()
QAbstractScrollArea::horizontalScrollBar()->value()
QAbstractScrollArea::verticalScrollBar()->value()
To implement an undo-redo framework, those three parameters should be stored before every operation an restored upon undo.
Related
I want to select rectangle/polyline through scene with mouse click and should be able to print it's name and other property. It's name and other properties are in the graph node. But I dont want to interact graph again.
So when I was drawing rectangle/polyline through graph co-ordinates, I should be able to store some pointer of graph node on rectangle/polyline so when I will click on rectangle/polyline, then through that pointer I can access it's name and other properties.
Question is `Is this possible ?
Among all above parameters, I want to store only _Ptr ( it is basically a pointer, but store as long, while using it will be type cast )
rect = new myRect();
while(true)
{
for(auto iter = verts.begin();iter != verts.end();++iter)
{
// getting co-ordinates of rectangle
QGraphicsRectItem* rectItem = rect->createRect(co-ordinates of rectangle);
rect->_Ptr = iter->_Ptr; // trying to store _crossRefPtr
}
}
myRect.h
class myRect : public QGraphicsRectItem
{
........
QGraphicsRectItem* createRect(QRectF& rect);
}
And when I will click on rectangle on scene through mouse I am doing like this:
if(_scene->selectedItems().count() != 0)
{
foreach(QGraphicsItem* currentItem, _scene->selectedItems())
{
QGraphicsRectItem* rItem = qgraphicsitem_cast<QGraphicsRectItem*>(currentItem);
if(rItem)
{
myRect* r = reinterpret_cast<myRect*>(rItem);
if(r)
{
Instance (rectangle)
Instance* i = reinterpret_cast<Instance*>(r->_boostRefPtr);
qDebug()<< i->Name();
}
}
But aobve way is wrong. Getting run time errors ( Showing Verbose stack trace)
So the question is :
How to store pointer on QGraphicsItem so that, once they get selected,
that pointer will be accessed ?
Use Qt's dynamic properties, check QObject::setProperty. It should do the trick.
But AFAIC, I would have used a double QMap to associate directly <graph_node, QGraphicsItem> AND <QGraphicsItem, graph_node> - so you can search quickly for both associations, and both in O(log2(n)) complexity. You can store this either as a static part of your graph (better) or standalone (not the best idea). Obviously, if your graph is already in O(log2(n)), you don't need this map and you need only the <QGraphicsItem, graph_node> one.
I want to draw lines inside column that show possible connections between different signals(Further, I also want to make radiobuttons on them to choose what connections are active).
But now I have trouble that delegates allow me to SetItemDelegate only for all column or all row. So I can't just make different blocks of this lines like vertical line, corner lines, horizontal line and then paint them depending on data in cells. I attached an example image. What should I use to draw something like this?
Something like:
Define a new style, override drawPrimitive method and do custom painting?
Could you show me an example, please?
Lines example
What I have for now
My main code for creating rows with signals(I take them from .txt file for simulation for now):
int IPFilesize = IPfilespl.size();
ui->CompTab->setRowCount(IPFilesize);
for (int i = 0; i<IPFilesize; i++)
{
QWidget *ChBx = new QWidget();
QCheckBox *pCheckBox = new QCheckBox();
QHBoxLayout *pLayout = new QHBoxLayout(ChBx);
pLayout->addWidget(pCheckBox);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0,0,0,0);
ChBx->setLayout(pLayout);
ui->CompTab->setCellWidget(i, 0, ChBx);
//connect(ChBx,SIGNAL(clicked()),this,SLOT(checkboxClicked()));
}
for (int ii = 0; ii<IPFilesize; ii++)
{
ui->CompTab->setItem(ii, 2, new QTableWidgetItem(IPfilespl.at(ii)) );
//connect(ChBx,SIGNAL(clicked()),this,SLOT(checkboxClicked()));
}
ui->CompTab->setItemDelegateForColumn(1, new WireDelegateDown());
Header code
class WireDelegate: public QStyledItemDelegate { protected: void paint(QPainter* painter, const QStyleOptionViewItem& opt, const QModelIndex& index) const {
int x = opt.rect.x();
double y = opt.rect.y();
QPoint c = opt.rect.center();
double centerx = c.x();
double centery = c.y();
double r = opt.rect.right();
double width = opt.rect.width();
double height = opt.rect.height();
QPainterPath path;
path.addRect(centerx, centery-height/2, 5.0, height/2);
path.moveTo(0, 0);
path.addRect(centerx, centery, width/2, 5.0);
path = path.simplified();
painter->drawPath(path);
Your item delegate could be a subclass of QAbstractItemDelegate. Then you can set its type with a property like shapeType (or whatever you name it). Based on the shapeType, you can do internal painting stuff from within the reimplemented paint method.
void MyConnectionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (m_shapeType == ShapeType::horizontalLine) {
//..... Your fancy drawings happens here based on shapetype
} else if (m_shapeType == ShapeType::verticalLine) {
.
.
.
As I see in the picture (your desired result) it's not going to be simple and it can get quite complicated to implement such behavior. You will have to calculate the width, height, position of lines, colors, dots, arrows, nodes, etc for each delegate. When you exactly know which entities should be drawn in each cell, painting them using QPainter is a simple task.
You might consider whether QTableView is getting in the way more than it helps you. The built-in widgets are fantastic, but often, I've found that when I need to venture outside the realm of what they were specifically designed to do, I end up spending more time working around them than I get benefit. I don't know the right solution for what you're doing, but if it were me, I'd explore writing my own view based on QAbstractItemView and then just doing my own custom painting for the whole thing.
The downside of doing that is that QTableView provides a lot of interaction support for you, so if the interaction is a big benefit to you, then you have to write your own as well. So it's a trade-off. It's also a possibility that the built-in interaction for QTableView also gets in the way of what you're trying to do. It can go either way.
I'm trying to create customObject (rectangle and it inherit from QGraphicsItem) that will be painted on scene with ceratin text(stored in attribute), but when I scale it - i wish to keep same size of text. Here is my over. paint function:
void CustomRectangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *widget)
{
QColor currentColor = get_ColorByCurrentState();
QRectF rect = boundingRect();
QPen pen(currentColor, Own_LineWidith);
painter->setPen(pen);
painter->drawRect(rect);
QRectF rect_text(rect.x(), rect.y(),100,100);
painter->drawText(rect_text,this->getText() );
}
and my two scaling functions:
void CustomObject::scaleUp()
{
scale(ScaleFactor_X,ScaleFactor_Y);
}
void CustomObject::scaleDown()
{
scale(1/ScaleFactor_X,1/ScaleFactor_Y);
}
But text still keep scaling along with rectangle.
EDIT 1
I tried adding it another way, i nfucntion that creates and adds my rectangle to scene (here - named "newObject"), but result is still the same.
QGraphicsTextItem* GTI = new QGraphicsTextItem(newObject->toStringForScene(), newObject);
I'm beginign to think that I shoud create each text object as separeted object and save it different list. Ofcours, i would have to update it then, whenever it's object moved.
Try this:
QGraphicsTextItem* gti = new QgraphicsTextItem("text");
gti->setFont(QFont("Arial", 18));
// this is important
gti->setFlag(QGraphicsTextItem::ItemIgnoresTransformations, true);
scene->addItem(gti);
The QGraphicsItem::ItemIgnoresTransformations flag prevents your graphics item to be scaled when you scale your view (QGraphicsView).
That means that you need a separated item for rendering text. But it can be a child item of your rectangle item.
I resolved this with QGraphicsTextItem's poitner as class's attribute.
QGraphicsTextItem* GTI;
I initialzie it in constructor:
GTI_Description = new QGraphicsTextItem(this->toStringForScene());
and then I call function to updated it's X and Y:
void updateTextPosition()
{
GTI->setX( this->x() );
GTI->setY( this->y() );
}
and to add it to the scene:
addTextToScene(DragScene* _scene)
{
updateDescriptionPosition();
_scene->addItem(GTI_GTI);
_scene->update();
}
Then i just call updateTextPosition() whenerver I change positions (in my mouseRelease event's handler).
Disclaimer: I am pretty much a beginner with QT.
I've been struggling for some time to rotate a QGraphicsView (no 3D rotation) but, despite what i do, it doesn't work. I have tried:
QTransform transform;
transform.rotate(45);
ui->graphicsView->setTransform(transform);
or more simply:
ui->graphicsView->rotate(45);
These seem like very straightforward ways to do it that should work, but for some reason, whenever i run it, the QGraphicsView doesn't rotate at all. If possible, i'd like some direct and easy to understand code snippets, and/or what i'm doing wrong.
EDIT: This is the code in the widget cpp file i have problems with. It should be a simple timer with an animated hourglass icon. It gets repeated every .5 seconds.
void Widget::timerEvent(QTimerEvent *event)
{
++timeFlag;
++timerFlag;
if (timerFlag < 115){
animateTimer = QString("\":/new/100/timerFrames/timerIconFrame%1.png\"").arg(timerFlag);
QPixmap pix(animateTimer);
pixmapitem.setPixmap(pix);
scene.addItem(&pixmapitem);
ui->graphicsView_2->setScene(&scene);
}
if (timerFlag >= 115 && timerFlag < 119){
//
}
if(timerFlag == 119){
ui->graphicsView_2->setStyleSheet("border-image:url(:/new/100/timerIconPix.PNG);border:0px;}");
}
if(timerFlag == 120){
timerFlag = 0;
}
if (timeFlag==2){
timeFlag = 0;
if(sec>=10){
ui->label_2->setText(QString("%1:%2").arg(min).arg(sec));
} else {
ui->label_2->setText(QString("%1:0%2").arg(min).arg(sec));
}
++sec;
if (sec == 60) {
sec = 0;
++min;
}
}
}
You're merely decorating the QGraphicsView using the style mechanism. You could have used a plain QWidget instead, since you don't use any graphics view functionality. None of the images in the stylesheet are what the view actually displays. The image must be on the scene displayed by the view.
Set the image on a QGraphicsPixmapItem, add that item to a scene, set the scene on the view, and then the transformations will work. You can then keep replacing the pixmap in the timer handler.
Finally, you must also check the timer id in the timerEvent. I assume that you're using a QBasicTimer, say called m_timer, you'd then check as follows:
void Widget::timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
... // rest of the code
}
As you can see, the code that you've not included in the original question was absolutely essential! Without it, the question was wholly off-topic.
You need to implement a QGraphicsView, a QGraphicsScene and then add something that inherits from QGraphicsItem to that scene to rotate.
Here is an example that rotates a QWidget in a QGraphicsView:
QGraphicsView* view = new QGraphicsView(parent);
QGraphicsScene* scene = new QGraphicsScene(view);
view->setScene(scene);
// Widget to rotate - important to not parent it
QWidget* widget = new QWidget();
QProxyWidget proxy_widget = scene_->addWidget(widget);
QPropertyAnimation* animation = new QPropertyAnimation(proxy_widget, "rotation");
animation->setDuration(5000);
animation->setStartValue(0);
animation->setEndValue(360);
animation->setEasingCurve(QEasingCurve::Linear);
animation->start(QAbstractAnimation::DeleteWhenStopped);
What I am trying to do is render a qwidget onto a different window (manually using a QPainter)
I have a QWidget (w) with a layout and a bunch of child controls. w is hidden. Until w is shown, there is no layout calculations happening, which is expected.
When I call w->render(painter, w->mapToGlobal(QPoint(0,0)), I get a bunch of controls all overlapping each other.
w->layout()->activate();w->layout()->update() doesn't seem to do anything.
Is there a way to force the layout to happen without showing w?
Forcing a layout calculation on a widget without showing it on the screen:
widget->setAttribute(Qt::WA_DontShowOnScreen);
widget->show();
The show() call will force the layout calculation, and Qt::WA_DontShowOnScreen ensures that the widget is not explicitly shown.
The layout calculation of a widget can be forced by calling invalidate() followed by activate() on its layout, even if the widget is hidden. This also causes the widget's size() and sizeHint() functions to return correct and updated values, even if show() has not yet been called on that widget.
It is however necessary to care about all child widgets and layouts recursively, as a layout recalculation request doesn't automatically propagate to the childs.
The following code shows how to do this.
/**
* Forces the given widget to update, even if it's hidden.
*/
void forceUpdate(QWidget *widget) {
// Update all child widgets.
for (int i = 0; i < widget->children().size(); i++) {
QObject *child = widget->children()[i];
if (child->isWidgetType()) {
forceUpdate((QWidget *)child);
}
}
// Invalidate the layout of the widget.
if (widget->layout()) {
invalidateLayout(widget->layout());
}
}
/**
* Helper function for forceUpdate(). Not self-sufficient!
*/
void invalidateLayout(QLayout *layout) {
// Recompute the given layout and all its child layouts.
for (int i = 0; i < layout->count(); i++) {
QLayoutItem *item = layout->itemAt(i);
if (item->layout()) {
invalidateLayout(item->layout());
} else {
item->invalidate();
}
}
layout->invalidate();
layout->activate();
}
Try with the QWidget::sizeHint() method, which is supposed to return the size of the widget once laid out.
I had some succes in a similar problem by first calling w->layout()->update() before w->layout()->activate(). That seems to force the activate() to actually do something rather than think it is fine because the window isn't being shown anyway.
When going through QWidget::grab() I noticed this part:
if (r.width() < 0 || r.height() < 0) {
// For grabbing widgets that haven't been shown yet,
// we trigger the layouting mechanism to determine the widget's size.
r = d->prepareToRender(QRegion(), renderFlags).boundingRect();
r.setTopLeft(rectangle.topLeft());
}
QWidget::grab() has been introduced in Qt 5.0, and this test function with a QDialog containing a layout seems to work on Qt 5.5.1
int layoutTest_2(QApplication& a)
{
CLayoutTestDlg dlg; // initial dlg size can also be set in the constructor
// https://stackoverflow.com/questions/21635427
QPixmap pixmap = dlg.grab(); // must be called with default/negative-size QRect
bool savedOK = pixmap.save("E:/temp/dlg_img.png");
// saving is not necessary, but by now the layout should be done
dlg.show();
return a.exec();
}
This worked for me when using the sizeHint() plus translating the painter, however I do this inside the paint() method.
void ParentWidget::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->save();
painter->translate(option.rect.topLeft());
w->render(painter);
painter->restore();
}
In this case, option.rect.topLeft() gives me the correct placement. You should try a more sensible coordinate instead of w->mapToGlobal(QPoint(0,0).