I have been creating a basic graphics program(like MS Paint) with simple GUI. I have two classes where one is a MainWindow which holds all buttons, sliders etc and a second class is a custom widget called drawingArea on which a user is able to draw.
Basicly I've implemented most of functions, but unfortunetly I stucked at filling function, which should work just like one in MS Paint. I decided to use so called floodFill algorithm and after few hours of fighting(I am a beginner in Qt) I have managed to make it work. But not at all. The problem is I am only able to fill black-colored regions(shapes, lines, points etc) with the color I choose. But when it comes to filling different colors, it just puts one pixel in chosen color. In funcion fill(...) below I am passing two arguments - point(mouseClicked) and a color of that point. Here:
void drawingArea::fill(const QPoint &point, QColor act)
{
QPainter painter(&image);
QPen myPen(actualColor);
myPen.setWidth(1);
painter.setPen(myPen);
QQueue<QPoint> pixels;
pixels.enqueue(point);
while(pixels.isEmpty() == 0)
{
QPoint newPoint = pixels.dequeue();
QColor actual;
actual.fromRgb(image.pixel(newPoint));
if(actual == act)
{
painter.drawPoint(newPoint);
update();
QPoint left((newPoint.x()-1), newPoint.y());
if(left.x() >0 && left.x() < image.width() && image.pixel(left) == act.rgb())
{
pixels.enqueue(left);
painter.drawPoint(left);
update();
}
QPoint right((newPoint.x()+1), newPoint.y());
if(right.x() > 0 && right.x() < image.width() && image.pixel(right) == act.rgb())
{
pixels.enqueue(right);
painter.drawPoint(right);
update();
}
QPoint up((newPoint.x()), (newPoint.y()-1));
if(up.y() > 0 && up.y() < image.height() && image.pixel(up) == act.rgb())
{
pixels.enqueue(up);
painter.drawPoint(up);
update();
}
QPoint down((newPoint.x()), (newPoint.y()+1));
if(down.y() > 0 && down.y() < image.height() && image.pixel(down) == act.rgb())
{
pixels.enqueue(down);
painter.drawPoint(down);
update();
}
}
}
}
I call this fill(...) function inside mousePressEvent here:
void drawingArea::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
lastPoint = event->pos();
QColor clr;
clr.fromRgb(image.pixel(lastPoint));
firstPoint = event->pos();
isDrawing = true;
if(isColorcheck) colorcheck(lastPoint);
if(isFill) fill(lastPoint,clr);
}
}
The really don't understand how it is working only on black color, when passing argument QColor of different colors.
I would really appreciate any help or ideas.
There is a fundamental problem with your code. Don't do the drawing from just any method called in your class. The drawing needs to be done in paint event callback that you define. QWidget::paintEvent worth exploring.
void MyClass::paintEvent(QPaintEvent *event)
{
// do the drawing depending on the context and you may
// define that context in other event handlers, etc.
}
Other than that it is hard to say what else is wrong. BTW, you can trigger drawing event by repaint() or update(). You need to consider how the exact area of redraw is defined in your code.
Related
So I'm trying to scale an Image to fully fit a label from top to bottom. The problem is the outputs from the labels width, and height is not pixel values so I'm not sure how to correctly go about this. Here's my code.
void MainWindow::drawPixmap()
{
int labelWidth = ui->picLabel->width();
int labelHeight = ui->picLabel->height();
if(labelWidth <= labelHeight){
pixMap = this->pixMap.scaledToWidth(labelWidth);
ui->picLabel->setPixmap(pixMap);
}
else{
pixMap = this->pixMap.scaledToHeight(labelHeight);
ui->picLabel->setPixmap(pixMap);
}
}
Here's a visual of my problem, the black box is a border around the label box. I'm trying to get the image in the center to have its top and bottom touch the black box on respective sides.
Thanks for any help!
The problem could be when you calling this function. If you have called this function prior to making the label visible, then the size of the label could be incorrect after the label becomes visible.
Also, think about what happens when the label resize (if ever). You will still have to update the size of pixmap.
If your label is never going to change size, try calling it after the label has become visible.
Alternatively, try setting QLabel::setScaledContents(true)
If both the above doesn't work, try installing an eventFilter on the label to get the label's size change event, in there, you could do your above code
MainWindow::MainWindow()
{
....
ui->picLabel->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
bool res = Base::eventFilter(object, event);
if (object == ui->picLabel && event->type() == QEvent::Resize)
{
int labelWidth = ui->picLabel->width();
int labelHeight = ui->picLabel->height();
if(labelWidth <= labelHeight){
pixMap = this->pixMap.scaledToWidth(labelWidth);
ui->picLabel->setPixmap(pixMap);
}
else{
pixMap = this->pixMap.scaledToHeight(labelHeight);
ui->picLabel->setPixmap(pixMap);
}
}
return res;
}
I have a scatter plot represented by a QXYSeries and viewed with a ChartView from Qt Charts 5.7.
I want to hover my mouse over the plot, have "hovered" trigger within a certain distance, rather than only when my cursor is directly on top of a point. Imagine a circle around the mouse, that will trigger hovered whenever any part of the series is within it.
Is there a way to get this behavior?
Eventually, I got this behavior by creating a class that inherits from QChartView and overriding mouseMoveEvent(QMouseEvent* event) thusly:
void ScatterView::mouseMoveEvent(QMouseEvent* event)
{
if(!this->chart()->axisX() || !this->chart()->axisY())
{
return;
}
QPointF inPoint;
QPointF chartPoint;
inPoint.setX(event->x());
inPoint.setY(event->y());
chartPoint = chart()->mapToValue(inPoint);
handleMouseMoved(chartPoint);
}
void ScatterView::handleMouseMoved(const QPointF &point)
{
QPointF mousePoint = point;
qreal distance(0.2); //distance from mouse to point in chart axes
foreach (QPointF currentPoint, scatterSeries->points()) {
qreal currentDistance = qSqrt((currentPoint.x() - mousePoint.x())
* (currentPoint.x() - mousePoint.x())
+ (currentPoint.y() - mousePoint.y())
* (currentPoint.y() - mousePoint.y()));
if (currentDistance < distance) {
triggerPoint(currentPoint);
}
}
}
I created my own classes (view and scene) to display image and objects I added to it, even got zoom in/out function implemented to my view, but now I have to add new functionality and I don't even know how to start looking for it.
Whenever I press the scroll button of my mouse and hold it - I wish to move around the scene, to see different parts of it - just like I would with sliders. It is supposed to be similar to any other program allowing to zoom in/out to image and move around zoomed picture to see different parts of it.
Unfortunately - I don't even know how to look for some basic, because "moving" and similar refer to dragging objects around.
EDIT 1
void CustomGraphicView::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() == Qt::MidButton)
{
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
translate(event->x(),event->y());
}
}
Tried this - but it is working in reverse.
I suppose you know how to handle events using Qt.
So, to translate (move) your view use the QGraphicsView::translate() method.
EDIT
How to use it:
void CustomGraphicsView::mousePressEvent(QMouseEvent* event)
{
if (e->button() == Qt::MiddleButton)
{
// Store original position.
m_originX = event->x();
m_originY = event->y();
}
}
void CustomGraphicsView::mouseMoveEvent(QMouseEvent* event)
{
if (e->buttons() & Qt::MidButton)
{
QPointF oldp = mapToScene(m_originX, m_originY);
QPointF newP = mapToScene(event->pos());
QPointF translation = newp - oldp;
translate(translation.x(), translation.y());
m_originX = event->x();
m_originY = event->y();
}
}
I am using the graphics view to paint the graphicsitem in it. Earlier when I clicked the button the respective item was painted only once, to again paint the same entity I had topush the button again. To overcome this I constructed the signal to allow to add the entities multiple times without having the need to push the button again. But when I using vector to store the points.It does not append, limiting its capacity to 2 only. Following is my output and the code
circle.cpp
void circle::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton) {
if(mFirstClick){
x1 = e->pos().x();
y1 = e->pos().y();
mFirstClick = false;
mSecondClick = true;
}
else if(!mFirstClick && mSecondClick){
x2 = e->pos().x();
y2 = e->pos().y();
mPaintFlag = true;
mSecondClick = false;
update();
emit DrawFinished();
_store.set_point(e->pos());
store_point.push_back(_store);
qDebug() << _store.getValue();
qDebug() << "Size of vector =" << store_point.size() << "and" << store_point.capacity();
update();
}
}
mainwindow.cpp
void MainWindow::drawCircle(){
item2 = new circle;
scene->addItem(item2);
qDebug() << "Circle Created";
connect(item2, SIGNAL(DrawFinished()), this, SLOT(drawCircle()));
}
output
Circle Created
QPointF(60, 87)
Size of vector = 1 and 1
Circle Created
QPointF(77, 221)
Size of vector = 2 and 2
QPointF(333, 57)
Size of vector = 1 and 1
When I remove the signal DrawFinished(), the points store perfectly but the item gets painted only once. I need to pushthe button again:(. Following is the output after removing the signal.
QPointF(74, 80)
Size of vector = 1 and 1
QPointF(118, 165)
Size of vector = 2 and 2
QPointF(335, 97)
Size of vector = 3 and 4
What needs to be done to perfectly store the points as well as allow repainting. Please do help me to sort out all this.
Well, not sure if this would answer your request but a comment is too small to write what i want to tell you.
I don't really get what is the purpose of your signal DrawFinished(). Even if it's obvious thanks to the name, I don't think you need it.
If I sum up what you really want, you have a QGraphicView where you want to draw some shapes. Next to it, you have at least one (let's say 3) buttons to select which shapes you want to draw (Circle, Triangle, Rectangle).
Lets say you want to draw some circles, you click on the CircleButton, and then, click on the QGraphicView.
To my mind, I would create something like this:
Two classes, MainWindow and View, view which inherits from QGraphicView. Your three buttons are defined with Qt designer in your MainWindow class. So When you click on a button, you can emit a signal to notify the View.
In the View class you could have one vector for each shapes.
MainWindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void drawCircle();
void drawRectangle();
void drawTriangle();
private:
Ui::MainWindow *ui;
View view;
private slots:
void slotOnCircleButton();
void slotOnRectangleButton();
void slotOnTriangleButton();
};
MainWindow.cpp
[...]
void MainWindow::slotOnCircleButton()
{
emit(drawCircle());
}
[...]
View.h
class View : public QGraphicsView
{
Q_OBJECT
public:
explicit View(QWidget *parent = 0);
enum DrawingMode
{
UNDEFINED,
CIRCLE,
TRIANGLE,
RECTANGLE
}
signals:
public slots:
void slotOnDrawCircle();
void slotOnDrawRectangle();
void slotOnDrawTriangle();
private:
DrawingMode mode;
QVector<QPointF> vectorCircle;
QVector<QPointF> vectorTriangle;
QVector<QPointF> vectorRectangle;
};
View.cpp
[...]
void View::slotOnDrawCircle()
{
this->mode = CIRCLE;
}
[...]
void View::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
if(e->button()==Qt::LeftButton && this->mode != UNDEFINED)
{
qreal x1 = e->pos().x();
qreal y1 = e->pos().y();
if(this->mode == CIRCLE)
{
this->vectorCircle.append(e->pos());
}
else if(...)
[...]
// updatePainting();
}
}
When updating the view, you just have to travel throw your 3 vectors and drawing circle, rectangle or triangle.
This way you don't have such a spaghetti code, it's quite clear.
I didn't run the code so there is probable some minor mistakes, don't forget to make your connections and your initializations.
All, I implemented a QGraphicsItem which is a polygon. I sped up development by using QGraphicsEllipseItem as the points (for the dragging capabilities.) However, I am now having difficulty in update() functionality. My code is posted at the end, my questions are:
Am I taking the right approach here? I am starting to doubt myself
Am I supposed to be calling QGraphicsItem::update() in my implementation? I call it alot
Some other info:
I did a dirty little hack. In my actual code I also inherit from QObject. This allows me to install an eventFilter on the scene() (which I know has been set using itemChange). From the scene I filter QGraphicsSceneMouseEvent during mouse moves and call QGraphicsItem::update() otherwise my lines wouldn't be redrawn.
When I remove my InteractivePolygon from the scene now, my lines are not removed! I have to call scene()->update. I feel like something's not right.
declaration:
class InteractivePolygon : public QGraphicsItem
{
public:
//Only important methods
QRectF boundingRect() const;
void paint(bla bla bla);
bool eventFilter(QObject *, QEvent *);
private:
QList<QGraphicsEllipseItem *> m_points;
void AddPolygonPoint(QPointF);
QGraphicsEllipseItem * MakeNewPoint(QPointF);
}
implementation:
QRectF InteractivePolygon::boundingRect() const
{
return childrenBoundingRect();
}
void InteractivePolygon::paint(QPainter painter.. otherstuf)
{
QPen line_pen(QColor(255,0,0));
painter->setPen(line_pen);
if(m_points.count() > 1)
{
for(int i = 1; i < m_points.count(); ++i)
painter->drawLine(m_points[i-1]->pos(), m_points[i]->pos());
}
}
void AddPolygonPoint(QRectF point)
{
QGraphicsEllipseItem * new_item = MakeNewPoint(point);
new_item->setParent(this);
m_points->push_front(new_item);
update();
}
QGraphicsEllipseItem * InteractivePolygon::MakeNewPoint(QPointF & new_point)
{
QGraphicsEllipseItem * result = 0;
result = new QGraphicsEllipseItem();
result->setPos(new_point);
result->setRect(-4, -4, 8, 8);
result->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable)
return result;
}
//Lets pretend this method is correctly setup/exists
bool InteractivePolygon::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::QEvent::GraphicsSceneMouseMove)
{
QGraphicsSceneMouseEvent * mouse_move = (QGraphicsSceneMouseEvent *)event;
//Selected index is set else, let's assume it works
if(selected_index)
{
update(); //If I don't do this, my lines in my paint() are not redrawn.
}
}
}
The solution was prepareGeometryChange() everytime I changed anything in my item that would change its bounding box. Everything is redrawn correctly and updated as necessary.
This allowed me to remove all update() calls.