In Qt GUI application, I made a dialog containing a table. When I double click a row in the table, I want:
a popup window to show up;
get points data according to that row from a database;
plot those points on the popup window.
I've done the fetch function of points data in a database.cpp. But according to the rule, the plotting function has to be in dialog.cpp, in a void Dialog::paintEvent(QPaintEvent *event) function. Can I do the plotting function lineTo() in that database.cpp data fetch function?
You can paint in a QPixmap from anywhere, and pass that pixmap to the popup dialog to be displayed inside a QLabel or painted by the paintEvent function.
You can also use QPolygonF which has the advantage of being more cleanly scalable.
Look at the function generatePixmap in that article (Qt Quaterly), then use QLabel::setPixmap to assign the pixmap to the label.
Related
I am working on a visualization SW which uses QGraphicsView and QGraphicsScene (Qt4.8, PyQt). I need LOD (level of detail) handling depending on zoom level. For this I create more QGraphicsScenes and I render the whole scene to each with different LOD level. (There is a QGraphicsScene for each LOD level).
I switch between these QGraphicsScenes depending on the zoom level. (QGraphicsView.setScene()).
The big problem is that the QGraphicsView's ScrollBars resets their positions when setScene() is called.
Here is a code snippet which tries to restore the ScrollBars values, but it doesn't work:
hsb = self.horizontalScrollBar()
vsb = self.verticalScrollBar()
hv, vv = hsb.value(), vsb.value()
self.lod = i
self.setScene(self.scenes[self.lod])
hsb.setValue(hv) # this simply doesn't work !!!!
vsb.setValue(vv)
Any idea to retain the positions of the ScrollBars? Or any idea for efficient LOD handling?
I could try to connect the ScrollBar's value changed signals into some custom slots which would disconnect from its own signal than re-set the ScrollBar's value from a saved one. That would be a very lame and ugly solution.
I suggest to use a single scene to manage different level of details.
You can get the current level of detail directly inside the QGraphicsItem::paint method and draw the item accordingly.
Example C++:
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
qreal levelOfDetail = QStyleOptionGraphicsItem::levelOfDetailFromTransform(painter->worldTransform());
//draw the item depending on the level of detail
}
Python
def paint(self, painter, option, widget):
levelOfDetail = QStyleOptionGraphicsItem.optionlevelOfDetailFromTransform(painter.worldTransform())
#draw the item depending on the level of detail
See QStyleOptionGraphicsItem::levelOfDetailFromTransform (for pyqt4 QStyleOptionGraphicsItem.levelOfDetailFromTransform )
For large scene, with a lot of items, consider to group items. You can check the level of details when the scene transform (or the view transform) changes and show/hide the group of items. See QGraphicsItemGroup.
I have been playing with QMovie a bit trying to mirror a movie as well as play it in reverse.
For the mirroring bit I tried assigning a negative width to no avail. Since QImage does offer facilities for this I had hoped QMovie would do the same.
There don't seem to be any facilities in the QMovie object for those things, so I'm wondering if I could manipulate the QIODevice to the QMovie object instead to achieve this, but this is completely new territory for me and I don't see anything obvious in the documentation that would achieve mirroring or playing in reverse.
The code example to start with would be the same as on the PySide page:
label = QLabel()
movie = QMovie("animations/fire.gif")
label.setMovie(movie)
movie.start()
Any ideas would be greatly appreciated.
Thanks,
Frank
QMovie doesn't provide the methods to set the current image, so you have to use directly QImageReader to play it in reverse (using QImageReader::jumpToImage()). This is not very easy bacause the delay between a frame and the next can change, but however you can get it calling QImageReader::nextImageDelay().
To display the movie, you can implement your own widget to draw the movie as you want.
In the paintEvent() you can set a transformation to the painter to get the mirror effect and than draw the current image of the movie.
Example:
void MyWidget::paintEvent(QPaintEvent * event)
{
QPainter painter(this);
painter.scale(-1, 1); // x-axis mirror
//here maybe you must adjust the transformation to center and scale the movie.
painter.drawImage(0, 0, currentImage);
}
To play the movie you have to set a timer that change the current image.
Example:
//you must create a timer in the constructor and connect it to this slot
void MyWidget::timeoutSlot()
{
int currentImageIndex;
//here you have to compute the image index
imageReader.jumpToImage(currentImageIndex);
currentImage = imageReader.read(); //maybe you want to read it only if the index is changed
update();
}
Here you can find an example of widget subclass, with timer and painter transformation
See also QImageReader, QTimer, QWidget::paintEvent
I am using Qt5.
I have a loop which generates multiple (number specified by the user) plots by using QCustomPlot (http://www.qcustomplot.com/) each shown in their own dialog. I want the user to be able to save one of the plots, so in each dialog there is a menu bar with an Action "Save as PDF".
I have a List of the plots (QList< QCustomPlot*> >) which each plot is added to when it is created in the loop. My issue is how to select from the list which plot should be saved when the user triggers the action. Here's the main code:
while(currentPlotNum<NumPlots){
//code for generating plots
QAction *saveAsPdfAction = new QAction("Save As PDF",plotDialog);
QFileDialog *saveAsPdfDialog = new QFileDialog(plotDialog);
saveAsPdfDialog->setFileMode(QFileDialog::AnyFile);
saveAsPdfDialog->setNameFilter("PDF Files (*.pdf)");
QObject::connect(saveAsPdfAction,SIGNAL(triggered()),saveAsPdfDialog,SLOT(exec()));
QSignalMapper *signalMapper = new QSignalMapper(saveAsPdfDialog);
QObject::connect(saveAsPdfAction,SIGNAL(triggered()),signalMapper,SLOT(map()));
signalMapper->setMapping(saveAsPdfAction,currentPlotNum);
QObject::connect(signalMapper,SIGNAL(mapped(int)),this,SLOT(setWorkingPlot(int)));
QObject::connect(saveAsPdfDialog,SIGNAL(fileSelected(QString)),this,SLOT(saveToPDF(QString)));
currentPlotNum++;
}
then here are the two SLOTS:
void samplePlots::setWorkingPlot(int value){
workingPlot = value;
}
void samplePlots::saveToPDF(QString PdfFileName){
plotList[workingPlot]->savePdf(PdfFileName,false,600,600);
}
I run the application and generate say 3 plots, when I click the button to save one of the plots, the plot that actually gets saved is seemingly a random choice of one of the 3, rather than the plot in the dialog which i click the button in.
Ideally I would have been able to pass the QCustomPlot* itself through the SignalMaper, but it doesn't seem as though I can do that. I also tried to have the Slot as a lambda (following the syntax here http://www.artandlogic.com/blog/2013/09/qt-5-and-c11-lambdas-are-your-friend/ but I couldn't get it to work.
If anyone has Ideas of how to fix my problem that would be great.
Connect each 'saveToPdf buttons' triggered(bool) signal to a custom signal of your derived displaying QDialog (lets call it saveRequested()).
store in the dialog the index of the displaying plot as well and save your QSignalMapper (not needed).
then connect your main class where your list is stored to that saveRequested() signal, cast the QObject::sender() to your Dialog and access the plot in the list.
cheers
I have a QwtPlot that contains some curves and I would like to be able to get the selected point(s) (and curve pointer) from these curves : select a point by clicking and select points by dragging a rect.
I use the following code:
// Picker with click point machine to provide point selection
QwtPlotPicker* clickPicker = new QwtPlotPicker(this->canvas());
clickPicker->setStateMachine(new QwtPickerClickPointMachine);
clickPicker->setMousePattern(0,Qt::LeftButton,Qt::SHIFT);
connect(clickPicker, SIGNAL(appended(QPointF)),
this, SLOT(pointSelected(QPointF)));
// Picker with drag rect machine to provide multiple points selection
QwtPlotPicker* rectPicker = new QwtPlotPicker(
this->xBottom, this->yLeft, QwtPicker::RectRubberBand,
QwtPicker::AlwaysOff, this->canvas());
QwtPickerDragRectMachine* test = new QwtPickerDragRectMachine();
test->setState(QwtPickerMachine::RectSelection);
rectPicker->setStateMachine(test);
connect(rectPicker, SIGNAL(selected(QRectF)),
this, SLOT(pointsSelected(QRectF)));
but the pointSelected slot is called every time I click on the QwtPlot and not only on a curve
BTW, I also try to connect a slot to the signal QwtPlotPicker::selected(const QVector &pa) but it is never emitting ...
I think it is more convenient to use the CanvasPicker as it comes with the examples and can be extended easily.
Please have a look at the event_filter which comes with Qwt. You should use the class CanvasPicker (it is not part of the Qwt API, but you'll find the code in the examples).
You can instantiate it in your class using
picker = new CanvasPicker(plot); // plot is a pointer to your instance of QwtPlot
You'll see that the event filter is installed in the constructor of CanvasPicker.
Now have a look at CanvasPicker::eventFilter(QObject *object, QEvent *e) which is called when an event occurs in the event loop of QwtPlot. Implement your application logic in the switch construct, f.i. change case QEvent::MouseMove:.
The easy way to create the long cross line cursor (as long as viewport) is create a cross line graphicsItem, when mouse moved, set the item's pos property.
But this way will be very slow when the scene is complex, because it should update the whole viewport to update the cursor's pos.
The another easy way is setCursor(QCursor(..)),use a QPixmap to define the long cross line, this way will very fast , but the cursor will exceed the viewport rect.
Is there another way to show a long cross line cursor fastly?
Thanks very much!
If I understand correctly, you want to draw an horizontal line and a vertical line, crossing at the cursor position, and being as large as the viewport is.
A poosible solution would be to reimplement QGraphicsScene::drawForeground() to draw the two lines with the painter.
The problem is that the scene doesn't know about the mouse position. This means the view will have to track it and inform the scene when the mouse position has changed.
To do that, you'll have to create your own GraphicsScene (inheriting QGraphicsScene) and your own GraphicsView (inheriting QGraphicsView).
On your GraphicsView constructor, you'll have to start mouse tracking. This will make your you receive a mouseMoveEvent each time the mouse moves inside the view :
GraphicsViewTrack::GraphicsViewTrack(QWidget* parent) : QGraphicsView(parent) {
setMouseTracking(true);
}
void GraphicsViewTrack::mouseMoveEvent(QMouseEvent* pEvent) {
QPointF MousePos = this->mapToScene(pEvent->pos());
emit mousePosChanged(MousePos.toPoint());
}
As you can see in the code snippet above, the view is emitting a signal (mousePosChanged) to which the scene will be connected. This signal contains the mouse position, converted to the scene's coordinates.
Now, on the scene side, you have to add a slot which will be called when the mouse position changed, store the new mouse position in a member variable and reimplement QGraphicsScene::drawForeground() :
void GraphicsSceneCross::drawForeground(QPainter* painter, const QRectF& rect) {
QRectF SceneRect = this->sceneRect();
painter->setPen(QPen(Qt::black, 1));
painter->drawLine(SceneRect.left(), m_MousePos.y(), SceneRect.right(), m_MousePos.y());
painter->drawLine(m_MousePos.x(), SceneRect.top(), m_MousePos.x(), SceneRect.bottom());
}
void GraphicsSceneCross::onMouseChanged(QPoint NewMousePos) {
m_MousePos = NewMousePos; // Store the mouse position in a member variable
invalidate(); // Tells the scene it should be redrawn
}
The last thing to do is connect the GraphicsView's signal to the GraphicsScene slot.
I'll let you check if this solution is acceptable performance wise.
Based on Jerome answer and using python I created this code in my QGraphicsScene subclass:
def drawForeground(self, painter, rect):
if self.guidesEnabled:
painter.setClipRect(rect)
painter.setPen(self.guidePen)
painter.drawLine(self.coords.x(), rect.top(), self.coords.x(), rect.bottom())
painter.drawLine(rect.left(), self.coords.y(), rect.right(), self.coords.y())
def mouseMoveEvent(self, event):
self.coords = event.scenePos()
self.invalidate()
It should be straighforward for you to write its appropiate C++ code. Note that I take advantage of the rect argument passed by Qt Api framework and I clip the painter to that
area, since it's the visible area to be drawn.
I also cache the pen object, since I realized in other experiments that creating objects while painting will penalty performace and doing it so you also give the user the chance to set a custom pen in your program options.
I have found a way to do this!
I develops under Windows system, so can use lower GDI api jumping out of Qt's painting system.
The detail is get the HDC of the QGraphicsView's viewPort. Then in the QMouseEvent of QGraphicsView use "MoveToEx" and "LineTo" drawing two lines on the viewport,then I should do is erase the "old" cursor, It's easy to do this using "setROP2(HDC dc,R2_NOT)",then draw the old Cursor stored again.
This method doesn't enter the QPainter system, so the GraphicsItems under the cursor will not be repaint.
To solve the filker problem when the mouse moves fast, I don't use "double buffer". I used QTimer to update the cursor at CPU's idle. The detail is in QMouseEvent ,don't update the cursor at time ,but store the position to a list, When CPU idle, draw the cursor at the list of positions
I wish this will help others who meets the same problem with me.
Thanks Jérôme, who gave me useful tip of QGraphicsScene.