Qt - drawing after receiving signal - qt

I have a simple application, that consists of one line edit, where user writes an answer and a confirmation button. If the answer was correct, I'd like to draw a simple figure (green / red circle for example). I have three source files - main, form (here I connect the signal from button with answer from lineedit and determine if answer is correct) and figureWidget, where i'd like to draw my figure. My problem is, that I can't find out how to connect the signal from "form" with my figureWidget (especially what with what and where :-) ) Any hints?
file form.cpp:
#include <QtCore>
#include "ui_untitled.h"
#include "Form.h"
#include "figureWidget.h"
Form::Form(QWidget * parent, Qt::WindowFlags f) : QWidget ( parent,f ) {
ui.setupUi(this);
connect(ui.pushButton, SIGNAL(buttonPressed()), this, SLOT(checkAnswer()))
//this checks if answer is correct after pushing button
connect(this,SIGNAL(correctness(QString)),ui.figureWidget, SLOT(drawFigure(QString)));
//I expect signal from this class (here I check the answer), and draw the circle in figureWidget
}
void Form::checkAnswer() {
if (ui.lineEdit == "1") emit correctness(QString("right");
else emit correctness(QString("false")
}
then I have the figureWidget.cpp:
#include <QtGui>
#include <iostream>
#include "figureWidget.h"
figureWidget::figureWidget(QWidget* parent) : QWidget(parent) {
}
figureWidget::~figureWidget() {
}
void figureWidget::drawFigure(QString newVal) {
if (newVal == correctVal)
update();
}
void figureWidget::paintEvent(QPaintEvent* ) {
QPainter painter(this);
painter.setPen(QRgb(0x0000ff00));
painter.drawEllipse(0, 0, 10, 10);
}
in debug i get:
No such slot QWidget::drawFigure(QString) in ...

I think you can try this:
connect(this,SIGNAL(correctness(QString)),this, SLOT(drawFigure(QString)));
void Form::drawFigure(QString val)
{
ui->figureWidget->drawFigure(val);
}
But how could figureWidget appears after 'ui->'??? You cannot draw it on the UI statically.

Related

How to simulate a drag and drop action using QTest

In order to create a test case for a drag and drop bug in the QTreeView widget I tried to simulate a drag and drop mouse movement behavior.
I basically select the first element in the QTreeView and want it to drag and drop on the third element. I did this by using a combination of QTest::mousePress and QTest::mouseMove. At the end there should be of course a QTest::mouseRelease, but I already failed in reproducing the mousePress and mouseMove.
These are steps necessary to reproduce:
User moves mouse on center of first item
User presses the left mouse button
User holds the left mouse button pressed and moves the mouse to the center of the third item
User releases the left mouse button
If one does these action as described I can see, that the QTreeView Widgets reacts appropriately, and indicating special highlights and vertical lines, in case the item will be move between to items.
Unfortunately, my automatized test fails to reproduce this behavior. It seems that calling QTest::mousePress in sequence does something different. Also using a pair of QTest::mousePress and QTest::mouseMove is something differently.
This is my code:
main.cpp
#include "TestObject.h"
#include <QTest>
QTEST_MAIN(TestObject)
TestObject.h
#include <QtTest/QtTest>
class TestObject : public QObject
{
Q_OBJECT
private slots:
void dragAndDrop();
};
TestObject.cpp
#include "TestObject.h"
#include "TestObject.moc"
#include <QTest>
#include <QTreeView>
#include <QStandardItemModel>
#include <QPropertyAnimation>
#include "MouseMover.h"
void TestObject::dragAndDrop() {
qDebug() << "Hello";
QStandardItemModel model;
QTreeView view;
view.setModel(&model);
view.show();
view.setHeaderHidden(true);
view.setDragDropMode(QAbstractItemView::DragDropMode::DragDrop);
view.setDefaultDropAction(Qt::DropAction::MoveAction);
view.setColumnHidden(1, true);
for (auto rowIter = 0; rowIter < 3; rowIter++) {
QList<QStandardItem*> items;
for (auto colIter = 0; colIter < 2; colIter++) {
items << new QStandardItem(QString("%1-%2").arg(rowIter).arg(colIter));
}
model.appendRow(items);
}
MouseMover mover;
mover.setWidget(view.viewport());
QPropertyAnimation anim(&mover, "mousePosition");
QTimer::singleShot(0, [&]() {
auto startValue = view.visualRect(model.index(0, 0)).center();
auto endValue = view.visualRect(model.index(2, 0)).center();
QTest::mousePress(view.viewport(), Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier, startValue);
anim.setStartValue(startValue);
anim.setEndValue(endValue);
anim.setDuration(500);
anim.start();
});
qApp->exec();
}
MouseMover.h
#pragma once
#include <QObject>
#include <QTest>
class MouseMover : public QObject {
Q_OBJECT
public:
Q_PROPERTY(QPoint mousePosition READ mousePosition WRITE setMousePosition MEMBER mMousePosition)
void setWidget(QWidget* widget) {
mWidget = widget;
}
QPoint mousePosition() const {
return mMousePosition;
}
void setMousePosition(const QPoint& pos) {
mMousePosition = pos;
if (mWidget) {
QTest::mousePress(mWidget, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier, mMousePosition);
QTest::mouseMove(mWidget, mMousePosition);
}
}
private:
QPoint mMousePosition;
QWidget* mWidget{ nullptr };
};
MouseMover.cpp
#include "MouseMover.h"
mouseMoveEvent() handler starts and blocks at QDrag.exec(). In order to simulate the drop, you have to schedule it e.g. with QTimer:
def complete_qdrag_exec():
QTest.mouseMove(drop_target)
QTest.qWait(50)
QTest.mouseClick(drop_target, Qt.MouseButton.LeftButton)
QTimer.singleShot(1000, complete_qdrag_exec)
QTest.mousePress(drag_source, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, QPoint(10, 10))
QTest.mouseMove(drag_source, QPoint(50, 50)) # Ensure distance sufficient for DND start threshold
Above is plain Python (PyQt6/PySide6). I assume it's similar for C++ ...

QT push button / stop button

I have a push button that I'd like to change to a stop button when clicked. Currently the button's text says "auto fire", it runs an endless loop and it's text changes to "stop auto fire" when clicked. My problem is breaking the endless loop by clicking/pressing this button again after the text changes.
Code so far:
void Cpp_Fire::on_auto_fire_clicked()
{
while(true)
{
ui->auto_fire->setText("Stop Auto Fire");
on_manual_fire_clicked();
}
}
I tried inserting a different slot into the loop above that runs when after the button is pressed (it runs when the button is released to be precise) but I couldn't get it to work.
I know this could be done with signals/slots and a separate stop button but I'm unfamiliar with that method and I prefer the method I described.
The problem with your endless loop is that nothing else gets a chance to work.
One approach you could use is to use a QTimer with a short interval to call the on_manual_fire_clicked() method, then have the on_auto_fire_clicked() method be responsible for changing the text on the button and enabling / disabling the timer.
The ui should get enough time to respond to clicks etc if you do it that way.
edit:
For more info on using QTimer have a look at this page:
How to use QTimer
or this tutorial:
http://www.bogotobogo.com/Qt/Qt5_QTimer.php
Here's some code:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
void timerslot();
private:
Ui::MainWindow *ui;
QTimer* myTimer;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QTimer>
#include<QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
myTimer = new QTimer(this);
myTimer->setInterval(500);
myTimer->setSingleShot(false);
connect(myTimer, SIGNAL(timeout()), this, SLOT(timerslot()));
myTimer->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::timerslot()
{
qDebug() << "timeslot";
}
void MainWindow::on_pushButton_clicked()
{
if ( this->myTimer->isActive() == true ) {
this->myTimer->stop();
ui->pushButton->setText("Start");
} else {
this->myTimer->start(500);
ui->pushButton->setText("Stop");
}
}
I hope you get the idea and can convert it to your needs.
I fully agree Michael's answer.
This will also affect the repaint! (Try to put some windows over your application, while in endless-loop: you should see repaint problems).
Don't use endless-loops, specially not within slots!
Try QTimer, or move object to a QThread.
While in such loop: Give GUI-Thread some time. You can call QCoreApplication::processEvents().. But be careful with this.
A simple (still poor) solution with QTimer could be:
(I found, Michael entered an example in his answer. - Use it.).
//have a QTimer 'timer' in the class, and a connect signal
//timer.timeout() to 'onSingleShotFired()'
void Cpp_Fire::on_auto_fire_clicked()
{
if ( ui->auto_fire->text() == "Stop Auto Fire" )
{
timer.stop();
ui->auto_fire->setText("Start Auto Fire");
}
else
{
//MSEC_AUTOFIRE_DELAY is the delay between the autofire-shots
timer.start( MSEC_AUTOFIRE_DELAY );
ui->auto_fire->setText("Stop Auto Fire");
}
}

Drawing Line at current position using QGraphicsLineItem

I want to draw a line using QGraphicsLineItem. What exactly I want is that on clicking at GraphicsView, after second click Line must be drawn. I am confused with the syntax of QGraphicsLineItem and also how to use it. I am new to Qt. Please help me out to solve this problem.
You can use this code snippet.
*h
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QStack>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent);
public slots:
private:
QStack<QPoint> stack;
};
#endif // GRAPHICSSCENE_H
*.cpp
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QPoint pos = mouseEvent->scenePos().toPoint();
if(stack.isEmpty())
stack.append(pos);
else if(stack.count() == 1)
{
stack.append(pos);
addLine(QLine(stack.pop(),stack.pop()),QPen(Qt::green));
}
}
}
Usage:
GraphicsScene *scene = new GraphicsScene(this);
ui->graphicsView->setScene(scene);
ui->graphicsView->show();
Edit: more beautiful solution which works as you need.
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
QPoint pos = mouseEvent->scenePos().toPoint();
if(stack.isEmpty())
stack.append(pos);
else
addLine(QLine(pos,stack.pop()),QPen(Qt::green));
}
}
You can derive the graphics view/scene and override the mousePressEvent
Below is example using derived QGraphicsScene and overridden mousePressEvent
Class Definition :
class MyScene : public QGraphicsScene
Data Members :
QList<QPointF> m_clickPositions;
int m_mode;
Code :
void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(false == sceneRect().contains(event->scenePos()))
{
QGraphicsScene::mousePressEvent(event);
}
else if(Qt::LeftButton == event->button() && m_mode == ConstructMode)
{
m_clickPositions.append(event->scenePos());
if(m_clickPositions.size() == 2)
{
QLineF lineF(m_clickPositions[0], m_clickPositions[1]);
QGraphicsLineItem* item = this->addLine(lineF);
m_clickPositions.clear();
m_mode = ScrollMode;
}
}
}
I had used something similar in my project and extracted the code. Hope this helps.
Please comment is this is not working.
Edit ::
ConstructMode and Scroll mode are used in the above program so that I can distinguish whether I want to Draw/Construct or just scroll the scene. You can remove them and the declaration of m_mode if not required by you.
If you want to use the modes you can define some public constants and add a method setMode(). Please see the code below.
MyScene.h or some Constant file if you have one
#define ConstructMode 100
#define ScrollMode 101
And add the following function
void MyScene::setMode(int mode)
{
m_mode = mode;
}
After this if you want to enter the construction mode you will need to call myScene->setMode(ConstructMode) everytime, as after the item is constructed the mode is reset to ScrollMode.

Sliding OSX Dock type "sidebar" rather embedded QDockWidget type?

I want to make a sliding "sidebar" similar to the functionality of the OSX "Dock" (e.g. mouse passes edge of screen and Dock slides out). I've been playing around with QDockWidget but since that is embedded in the window layout, it causes everything to shift when it becomes visible.
Can someone suggest a way to implement this?
Doesn't need to float (as a separate window/tool bar)
Should scale to window height (e.g. window can be fullscreen or default size)
Doesn't need to slide (animate) if that is complicated.
I'm new to Qt and so don't want to over-think this. Is this just a matter of a custom widget or should I be looking at a borderless window? The custom widget approach seems right but I don't know how to specify that it overlay other window content and also scale if the window scales.
QDockWidget has nothing to do with what you want - behaviorally. Just because it's called a Dock widget doesn't mean it's the same "Dock" concept as in OS X. It merely means that it docks somewhere. QDockWidget's documentation quite explicitly explains what is meant by the docking behavior.
The code below implements the behavior you seem to want. Whether it's good design or not is arguable. The reason the code is "convoluted" seems to hint that nobody is expected to come up with such a UI design. What's wrong with actually clicking a button somewhere to display the slider window?
The code works under both Qt 4.8 and 5.1.
Note: This begs to be implemented in Qt Quick 2. That's what it was designed for :) Of course Qt 4.6+ improved the behavior of the QWidget-moving animations, and Qt 5 does further tweaks, but really this code smells bad and there's a good reason it does: QWidget API, while powerful, ultimately encapsulates a set of APIs that date to 1984 when the original Macintosh was released. There's only so much you can do when you have to composite results from a bunch of stacked painters. In Qt Quick, the rendering is done by the GPU. The animation amounts to passing a couple of new floats to the GPU to update a single transformation matrix. That's it.
#include <QApplication>
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPainter>
#include <QGradient>
#include <QMouseEvent>
#include <QPropertyAnimation>
class Slider : public QWidget {
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
QPainter p(this);
QLinearGradient g(QPointF(0,0), QPointF(rect().bottomRight()));
g.setColorAt(0, Qt::blue);
g.setColorAt(1, Qt::gray);
p.setBackground(g);
p.eraseRect(rect());
p.setPen(Qt::yellow);
p.setFont(QFont("Helvetica", 48));
p.drawText(rect(), "Click Me To Hide");
}
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE {
hide();
}
public:
explicit Slider(QWidget *parent = 0) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
};
class Window : public QWidget {
QGridLayout m_layout;
Slider m_slider;
QLabel m_label;
QPropertyAnimation m_animation;
public:
explicit Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f),
m_layout(this),
m_slider(this),
m_animation(&m_slider, "pos")
{
setMouseTracking(true);
m_layout.addWidget(&m_label);
m_slider.hide();
m_slider.setMouseTracking(false);
m_animation.setStartValue(QPoint(-width(), 0));
m_animation.setEndValue(QPoint(0, 0));
m_animation.setDuration(500);
m_animation.setEasingCurve(QEasingCurve::InCubic);
}
void leaveEvent(QEvent *) {
if (window() && QCursor::pos().x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
void childEvent(QChildEvent * ev) {
if (ev->added() && ev->child()->isWidgetType()) {
ev->child()->installEventFilter(this);
static_cast<QWidget*>(ev->child())->setMouseTracking(true);
}
}
bool event(QEvent * ev) {
eventFilter(this, ev);
return QWidget::event(ev);
}
bool eventFilter(QObject *, QEvent * ev) {
if (ev->type() == QEvent::MouseMove) {
auto pos = QCursor::pos();
if (window() && window()->isFullScreen()) {
if (pos.x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
m_label.setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
}
return false;
}
void resizeEvent(QResizeEvent *) {
m_slider.resize(size());
m_animation.setStartValue(QPoint(-width(), 0));
}
Q_SLOT void showSlider() {
if (m_slider.isVisible() || (window() && qApp->activeWindow() != window())) return;
m_slider.raise();
m_slider.show();
m_animation.start();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}

QDateEdit calendar popup

I'm trying to get a QDateEdit to allow the QCalendarWidget to show when requested (rather than just on clicking the down arrow). For example, somewhere in my class I should be able to say:
ui.datepicker.showCalendar()
and it should load up the calendar that appears right below the date picker.
It looks like I need to sub-class QDateEdit, as this doesn't work:
QDateEdit *de = new QDateEdit();
de->calendarWidget()->show();
I've also tried sending keyboard commands as dictated when you go through the QDateTimeEdit.cpp source for Qt, but seems my keyboard shortcuts are disabled or something.
Any ideas on what I have to do to sub-class to get this to work? I was thinking of something like:
class MyDateEdit : QDateEdit
{
Q_OBJECT
protected:
void mouseEvent(QEvent *event) {
this.calendarWidget().show();
}
};
But alas that also doesn't seem to compile in or work correctly.
Enable "setCalendarPopup ( bool enable )" in QDateTimeEdit allows to popup the calendar
I was able to figure it out on my own - still no sure how to get QDateEdit to work properly, but I used a QLineEdit and it suited my needs. Just connect QCalendarWidget's "onClick(QDate)" to a slot you create that does a:
setText(date.toString("M/d/yyyy"));
ui->calendar->hide();
Then add an event filter to the QLineEdit using the "OnFocusIn" event that does a "ui->calendar->show();" See: Get a notification/event/signal when a Qt widget gets focus
#Rob S answer
You were right with event filter approach we would do same with QDateEdit.
I am writing the code which extends your approach with QDateEdit :
In mainwindow.h I created a QCalendar pointer (Using QtCreator)
Following is the code of mainwindow.cpp (I am giving out fullcode so that rookies like me can benifit from it)
Make sure you set buttonSymbol and calendarpopup property to false to make it work correctly
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCalendarWidget>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->dateEdit->setDate(QDate::currentDate());
widget=new QCalendarWidget(); //widget is QCalendar pointer
ui->verticalLayout->addWidget(widget);
widget->setWindowFlags(Qt::Popup); // we need widget to popup
ui->dateEdit->installEventFilter(this);
connect(widget,SIGNAL(clicked(QDate)),ui->dateEdit,SLOT(setDate(QDate)));
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::InputMethodQuery)
{
if (object == ui->dateEdit)
{
if(widget->isVisible()==false && ui->dateEdit->calendarWidget()->isVisible()==false) // this done to avoid conflict
{
qWarning(QString().number(event->type()).toStdString().c_str());
qWarning(object->objectName().toLatin1().data());
widget->move(ui->dateEdit->mapToGlobal(QPoint(0,ui->dateEdit->height())));
widget->show();
}
}
}
return false;
}
OR :: Alternatively we can use QCalendarWidget provided by dateEdit, though its not much efficient as turing it to Popup will mess with its internal. Give it a shot if you want
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCompleter>
#include <QCalendarWidget>
#include <QMouseEvent>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->dateEdit->setDate(QDate::currentDate());
widget = ui->dateEdit->calendarWidget();
widget->setWindowFlags(Qt::Popup);
ui->dateEdit->installEventFilter(this);
//connecting widget with dateedit
ui->dateEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
ui->dateEdit->setCalendarPopup(true);
connect(widget,SIGNAL(clicked(QDate)),ui->dateEdit,SLOT(setDate(QDate)));
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->dateEdit)
{
if (event->type() == QEvent::FocusIn || event->type() == QEvent::MouseButtonPress)
{
// WE NEED MOUSE EVENT TO AVOID INTERFERNCE WITH CALENDAR POPUP BUTTON SITUATED AT CORNER OF dateEdit WIDGET
if(widget->isVisible()==false && ( ((QMouseEvent* )event)->x()< (ui->dateEdit->width()-10)))
{
widget->move(ui->dateEdit->mapToGlobal(QPoint(0,ui->dateEdit->height())));
widget->show();
}
}
}
return false;
}
I'd like to offer option similar to #Dr. Xperience's answer that encapsulates calendar widget in QDateEdit subclass:
#include <QDateEdit>
#include <QCalendarWidget>
class DateEdit : public QDateEdit {
Q_OBJECT
public:
explicit DateEdit(QWidget *parent = nullptr);
protected:
virtual void focusInEvent(QFocusEvent *event) override;
private:
QCalendarWidget *calendar = new QCalendarWidget(this);
};
DateEdit::DateEdit(QWidget *parent) : QDateEdit (parent) {
setButtonSymbols(QAbstractSpinBox::NoButtons);
setCalendarPopup(false);
setDate(QDate::currentDate());
calendar->setWindowFlags(Qt::Popup);
connect(calendar, &QCalendarWidget::clicked, this, [&](const QDate &date) {
setDate(date);
calendar->hide();
});
}
void DateEdit::focusInEvent(QFocusEvent *event) {
if (!calendar->isVisible()) {
calendar->setSelectedDate(date());
calendar->move(mapToGlobal(QPoint(0, height())));
calendar->show();
}
return QDateEdit::focusInEvent(event);
}
Warning: If you place this widget using QtDesigner, it will override buttonSymbols and calendarPopup properties, so you have to set it manually to hide QDateEdit's buttons.
Here is my hacky approach to the issue. After fighting for quite a while to have something clean, I read the source code of QDateEditor (which in fact is just a simplified QDateTimeEditor) and it seems to be no clean solution. The following is code for toggle() rather than show(), but still:
// Enable the calendar popup
date_editor->setCalendarPopup(true);
// Show the calendar popup by default
// There seems to be no proper interface to achieve that
// Fake a mouse click on the right-hand-side button
QPointF point = date_editor->rect().bottomRight() - QPointF{5, 5};
QCoreApplication::postEvent(
date_editor,
new QMouseEvent(QEvent::MouseButtonPress, point, Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier));
Using something like this you can keep relying on the editor's validation features.
BTW, another annoying thing about the built-in editor that makes a QLineEdit tempting is that (at least in my case) the keyboard cursor is not shown by default. This is very confusing. To solve this I did:
// Select a section so that the cursor is be visible
date_editor->setSelectedSection(QDateTimeEdit::DaySection);
This or course selects the day section of the date, but if you use keyboard arrows the selection vanished, but you can see the keyboard cursor.

Resources