QPropertyAnimation: Immedately jump to end of animation? - c++

I use a QPropertyAnimation to animate user input to navigate in a widget. Namely, I use it to smooth zooming using the mouse wheel.
Currently, when the user gives a new input (rotates the mouse wheel), the current animation gets canceled and a new animation starts, beginning with the current value of my zoom property.
For example, if a zoom-in operation scales the view by factor 2, we can imagine the following scenarios:
User input | Zoom before the animation | Animation's end value
----------------------+-----------------------------+--------------------------
Mouse wheel up | 100 % | 200 %
(wait) | |
Mouse wheel up | 200 % | 400 %
(wait) | |
Mouse wheel up | 400 % | 800 %
But if the user doesn't wait for the animation to be finished:
User input | Zoom before the animation | Animation's end value
----------------------+-----------------------------+--------------------------
Mouse wheel up | 100 % | 200 %
Mouse wheel up | 110 % | 220 %
Mouse wheel up | 120 % | 240 %
What I want (again, the user doesn't wait):
User input | Zoom before the animation | Animation's end value
----------------------+-----------------------------+--------------------------
Mouse wheel up | 100 % | 200 %
Mouse wheel up | immediately jump to 200 % | 400 %
Mouse wheel up | immediately jump to 400 % | 800 %
I hate applications where there can't be made a further user input until the end of an animation, and therefore I mostly hate smooth animations. So what I want is: When the user gives another user input and there is currently an animation running, "skip" this animation by jumping to the end of this animation.
The simpliest solution would be to just use the end value of the previous animation for the start value of the new one, but I want to abstract the "type" of animation which is currently performed; it doesn't have to be a zooming animation, but may also be a scrolling, panning, whatever animation.
So is there a possibility, without further knowledge of the animation (I only have a pointer to a QPropertyAnimation), to make it immediately jump to the end?
Currently, my code looks like this:
class View : ...
{
// Property I want to animate using the mouse wheel
Q_PROPERTY(qreal scale READ currentScale WRITE setScale)
...
private:
// Pointer to the animation, which can also be another animation!
QPropertyAnimation *viewAnimation;
}
void View::wheelEvent(QWheelEvent *e)
{
qreal scaleDelta = pow(1.002, e->delta());
qreal newScale = currentScale() * scaleDelta;
if(viewAnimation)
{
// --- CODE SHOULD BE INSERTED HERE ---
// --- Jump to end of viewAnimation ---
// --- rather than canceling it ---
cancelViewAnimation();
}
viewAnimation = new QPropertyAnimation(this, "scale", this);
viewAnimation->setStartValue(currentScale());
viewAnimation->setEndValue(newScale);
viewAnimation->setDuration(100);
connect(viewAnimation, SIGNAL(finished()), SLOT(cancelViewAnimation()));
viewAnimation->start();
}
void View::cancelViewAnimation()
{
if(viewAnimation)
{
viewAnimation->stop();
viewAnimation->deleteLater();
viewAnimation = NULL;
}
}

I think this will do it:
if(viewAnimation)
{
// jump to end of animation, without any further knowledge of it:
viewAnimation->targetObject()->setProperty(viewAnimation->propertyName(),
viewAnimation->endValue());
cancelViewAnimation();
}

Related

Arduino line following robot for beginners in C++(Arduino)

I'm working on a school project with very little resources. I need to make a line following robot which finds the way through a maze by turning left when possible. I already have a small body, but the robot doesn't seem to turn left/follow the line when needed. I also have no clue how I can get rid of small deviations and how I can make the robot turn when there are no more lines?
Materials:
Arduino Leonardo
qtr-8a ground sensor
two basic DC motors
Code:
#include <DRV8835MotorShield.h> //setup for motors
DRV8835MotorShield motors;
void setup() {
// put your setup code here, to run once:
motors.flipM1(true);
motors.flipM2(true);
}
void loop() {
//left groundsensor= analogRead(A0);
//rightgroundsensor = analogRead(A2);
//middlegroundsensor = analogRead(A1);
//motorstM1//right weel
//motors.setM2Speed(100);//left weel
if(analogRead(A0) > 750){ //turn left
motors.setM1Speed(100);
motors.setM2Speed(-100);
}
else if(analogRead(A1) > 750){//nothing left, so go straight
motors.setM1Speed(100);
motors.setM2Speed(100);
}
else if(analogRead(A2) > 750){//nothing in the middle, so robot has deviated, turn right a bit, if the deviation is to the rigth, I don't need the next loop
while(analogRead(A0) < 750){//turn right till black line is in the middle again
motors.setM1Speed(-100);
motors.setM1Speed(100);
}
}
else{//if they are all < 750, it's a deviation or a dead end, I will have to watch how the robot deviates so that I can turn let it always turn in the opposite direction
while(analogRead(A1) < 750){//turn right/left till black line is in the middle again
motors.setM1Speed(-100);
motors.setM1Speed(100);
}
}
}
//left groundsensor= analogRead(A0);
//rightgroundsensor = analogRead(A2);
//middlegroundsensor = analogRead(A1);
I'm assuming the line is wide enough that it can not be between 2 sensors and have both sensors report white. Correct?
Lets consider all the cases the car can run into:
Straight line
Ideally left should be white and middle black. In that case drive straight. But the car will drift left or right.
If A0 gets black turn more to the left. If A1 gets white turn more to the right.
Left turn, Left Branch
A0 and A1 both turn black, turn to the left.
4 way cross
A0, A1 and A2 turn black, turn to the left.
Right branch
A2 turns black, keep going straight
Right turn
A2 turns black and a bit later A1 turns white, turn right
As you see A2 can be ignored. And if you merge all the cases you get:
| A0 | A1 | movement
| white | white | turn right
| white | black | straight
| black | white | turn left or right, shouldn't really be happening
| black | black | turn left
So check for those 4 cases in loop() with a small delay and it should solve the maze.
Note: If the car gets stuck turning one direction for a long time you might want to go a bit straight. If the line is too thick you might just turn on the line and never see the edge. If you have white, white driving in a spiral to find the line again could help. Both cases indicate your loop is too slow or your speed to high. You should never loose the edge of the line.

How to get the QLayouts to expand properly?

My structure is as follows:
QWidget
-QHBoxLayout
-QLabel
-QVBoxLayout
-QLabel
-QWebView
I want the HBoxLayout to fill the width however large the container may be but go no more or less. However, I want the QVBoxLayout to expand to accommodate the size of its contents in the vertical direction.
+-------------+------------------------------+
| FixedTitle: | Expanding to Width Title +
| |------------------------------+
| | +
| | this is a test which wraps to+
| | the next line +
| | +
| | +
| | +
| | bla bla bla +
| | +
| | +
| | +
| | there are no vertical scroll +
| | bars here +
+-------------+------------------------------+
In this example, FixedTitle's width is however big it needs to be, but does not resize ever. Expanding to Width Title fills up the remaining horizontal space.
So far, I have:
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QHBoxLayout *layout = new QHBoxLayout;
this->setLayout(layout);
layout->addWidget(new QLabel(QString("FixedTitle")), 0, Qt::AlignTop);
QVBoxLayout *v_layout = new QVBoxLayout;
v_layout->setSizeConstraint(QLayout::SetNoConstraint);
layout->addLayout(v_layout);
v_layout ->addWidget(new QLabel(QString("Expanding to Width Title")), 1, Qt::AlignTop | Qt::AlignLeft);
QWebView *view = new QWebView();
QTextEdit text;
text.setPlainText(QSString("\nthis is a test which wraps to the next line\n\n\nbla bla bla\n\n\nthere are no vertical scroll bars here"));
view->setHtml(text.toHtml());
int width = view->page()->mainFrame()->contentsSize().width();
int height = view->page()->mainFrame()->contentsSize().height();
view->page()->setViewportSize(QSize(width, height));
view->resize(width, height);
view->setFixedSize(width, height);
v_layout->addWidget(view);
There are two problems with this: 1. It ignores the width of the container and 2. It still doesnt get the height of the QWebView correct.
How do I fix this?
This is my take on an answer... and forgive me but its written in PyQt.
I feel that your shouldn't be thinking so much about resizing the containing widget to the contents of the QWebView, but rather just have the size policy set to expanding, turn off the scrollbars, and defer sizing to whatever layout this container is added to. It makes no sense to try and manually resize it.
from PyQt4 import QtCore, QtGui, QtWebKit
class WebWidget(QtGui.QWidget):
def __init__(self):
super(WebWidget, self).__init__()
layout = QtGui.QHBoxLayout(self)
title = QtGui.QLabel("FixedTitle:")
# title.setText("A much larger fixed title")
title.setSizePolicy(
QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.Fixed)
layout.addWidget(title, 0, QtCore.Qt.AlignTop)
v_layout = QtGui.QVBoxLayout()
layout.addLayout(v_layout)
expandingTitle = QtGui.QLabel("Expanding to Width Title")
expandingTitle.setSizePolicy(
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Fixed)
v_layout.addWidget(expandingTitle)
text = QtGui.QTextEdit()
view = QtWebKit.QWebView()
view.setSizePolicy(
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
view.page().mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical,
QtCore.Qt.ScrollBarAlwaysOff )
view.page().mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal,
QtCore.Qt.ScrollBarAlwaysOff )
v_layout.addWidget(view, 1)
text.setPlainText("""
this is a test which wraps to the next line\n\n\n
bla bla bla\n\n\nthere are no vertical scroll bars here
""")
view.setHtml(text.toHtml())
v_layout.addStretch()
self.view = view
view.page().mainFrame().contentsSizeChanged.connect(self.updateWebSize)
def updateWebSize(self, size=None):
if size is None:
size = self.view.page().mainFrame().contentsSize()
self.view.setFixedSize(size)
def resizeEvent(self, event):
super(WebWidget, self).resizeEvent(event)
self.updateWebSize()
if __name__ == "__main__":
app = QtGui.QApplication([])
w = QtGui.QScrollArea()
w.resize(800,600)
web = WebWidget()
w.setWidget(web)
w.setWidgetResizable(True)
w.show()
app.exec_()
The left title is set to preferred width and fixed height, so that it gets the width it needs but doesn't grow vertically.
The expanding title gets an expanding width policy and a fixed height.
The QWebView gets expanding policies in both directions, and its scrollbars turned off.
And for an example. I just created a QScrollArea and set the WebWidget into it, so that you can see the parent layout will allow the Web view to grow as big as it wants to, but will handle overflow with scrollbars.

How to keep the OpenGL viewport in an SDL window stationary when a window is resized?

I have an SDL window containing an OpenGL viewport. When the user resizes the window by dragging a window edge, I'd like for (1) the viewport to expand to fill the window, and (2) for the contents of the viewport to remain stationary.
For example:
------------------------
| | |
| <------ | |
| | X |
| <------ | |
| | |
------------------------
If the left edge of the window is dragged further left, then the object X in the OpenGL viewport should remain stationary.
I'm finding that the code I'm calling on SDL_VIDEORESIZE events is not only not doing this, but also causing some awful flickering:
SDL_FreeSurface(window);
window = SDL_SetVideoMode(_w, _h, 32, SDL_OPENGL | SDL_RESIZABLE);
glViewport(0, 0, _w, _h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
const double w_2 = _w/2.0;
const double h_2 = _h/2.0;
gluOrtho2D(-w_2, w_2, -h_2, h_2);
// adjust model matrix
const double s_cosT = _scale*cos(_theta);
const double s_sinT = _scale*sin(_theta);
model[ 0] = s_cosT;
model[ 1] = s_sinT;
model[ 4] = -s_sinT;
model[ 5] = s_cosT;
model[12] = s_cosT*_x - s_sinT*_y;
model[13] = s_sinT*_x + s_cosT*_y;
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd(model);
What this code seems to do when a window is resized is to translate what was at the center of the old viewport to be at the center of the new viewport. The same code behaved as I expected when used with GLUT instead of SDL---with GLUT, resizing the window by dragging the edges did not cause the contents of the viewport to recenter.
What accounts for this difference? How can I fix it?
The only way to do this is to also have view matrix move with the window. This means that in addition to changing your transformation matrix when you resize, you must also adjust it when you click and drag the window. To demonstrate the equivalence of moving a window and repeated resizing, consider what would happen if you do a bunch of incremental resizings like so:
|---------------| 1. Starting window
|-------| 2. Resize left edge
|---------------| 3. Resize right edge
Thus, to get the desired behavior you should set your gluOrtho2D matrix to the following value every time your window moves/gets resized (or alternatively at the start of the frame where you draw everything):
int wind_x, wind_y;
SDL_GetWindowPosition(window, &wind_x, &wind_y);
gluOrtho2D(wind_x, wind_x + w, wind_y, wind_y + h);

selection in a lot of polylines with QGraphicsView doesn't work fine

In Qt, there is no "QGraphicsPolylineItem", so I have to implement it form QGraphicsItem.
I reimplement 'paint' and 'boundingRect' functions.In 'paint()' function, I just simply draw all lines in the polyline.
It's OK without any user's interaction.
For selection and movable function, I reimplement the 'QPainterPath* shape()' function like this:
QPainterPath ContourLineShape::shape() const
{
QPainterPath path;
QPointF p0=this->poly.at(0)->at(0);
path.moveTo(p0.x(),p0.y());
foreach(QPointF p,poly)
path.lineTo(p.x(),p.y());
return path;
}
but the result is wrong. when i click on a polyline ,it always selects another.
Then I try to implement the selection myself, like this:
void GraphicsView::mousePressEvent ( QMouseEvent * event )
{
int x=event->pos().x();
int y=event->pos().y();
QList<QGraphicsItem *>items=this->items(x-5,y-5,10,10,Qt::IntersectsItemShape);
for(int i=items.size()-1;i>=0;i--)
{
QGraphicsItem *item=items.at(i);
if(item->type()==QGraphicsItem::UserType+4)//UserType+4 is my polyline type.
{
item->setSelected(true);
return; //one shape has been selected.
}
}
}
This solution looks like right, but it's not exact. If a polyline like this:
----------------------
|
| o<-click here |
| |
| /\ /\ |
| / \ / \ /-----------
/ V V
The click point is far from the bound lines ,but the shape still can be selected(it's not what I wanted). Even if I changed the selection mode to Qt::ContainsItemBoundingRect or Qt::ContainsItemShape.., the result is still wrong.
Is there any easy way to solve this problem?Or,must I calculate the distances from 'click point' to all the segments to decide if it's selected?
Thanks!
Does calling setFlags(QGraphicsItem::ItemClipsToShape) help?

How to programmatically move a window slowly, as if the user were doing it?

I am aware of the MoveWindow() and SetWindowPos() functions. I know how to use them correctly. However, what I am trying to accomplish is move a window slowly and smoothly as if a user is dragging it.
I have yet to get this to work correctly. What I tried was getting the current coordinates with GetWindowRect() and then using the setwindow and movewindow functions, incrementing Right by 10 pixels each call.
Any ideas?
Here is what I had beside all my definitions.
while(1)
{
GetWindowRect(notepad,&window);
Sleep(1000);
SetWindowPos(
notepad,
HWND_TOPMOST,
window.top - 10,
window.right,
400,
400,
TRUE
);
}
If you want smooth animation, you'll need to make it time-based, and allow Windows to process messages in between movements. Set a timer, and respond to WM_TIMER notifications by moving the window a distance based on the elapsed time since your animation started. For natural-looking movement, don't use a linear function for determining the distance - instead, try something like Robert Harvey's suggested function.
Pseudocode:
//
// animate as a function of time - could use something else, but time is nice.
lengthInMS = 10*1000; // ten second animation length
StartAnimation(desiredPos)
{
originalPos = GetWindowPos();
startTime = GetTickCount();
// omitted: hwnd, ID - you'll call SetTimer differently
// based on whether or not you have a window of your own
timerID = SetTimer(30, callback);
}
callback()
{
elapsed = GetTickCount()-startTime;
if ( elapsed >= lengthInMS )
{
// done - move to destination and stop animation timer.
MoveWindow(desiredPos);
KillTimer(timerID);
}
// convert elapsed time into a value between 0 and 1
pos = elapsed / lengthInMS;
// use Harvey's function to provide smooth movement between original
// and desired position
newPos.x = originalPos.x*(1-SmoothMoveELX(pos))
+ desiredPos.x*SmoothMoveELX(pos);
newPos.y = originalPos.y*(1-SmoothMoveELX(pos))
+ desiredPos.y*SmoothMoveELX(pos);
MoveWindow(newPos);
}
I found this code which should do what you want. It's in c#, but you should be able to adapt it:
increment a variable between 0 and 1 (lets call it "inc" and make it global) using small increments (.03?) and use the function below to give a smooth motion.
Math goes like this:
currentx=x1*(1-smoothmmoveELX(inc)) + x2*smoothmmoveELX(inc)
currenty=y1*(1-smoothmmoveELX(inc)) + y2*smoothmmoveELX(inc)
Code:
public double SmoothMoveELX(double x)
{
double PI = Atn(1) * 4;
return (Cos((1 - x) * PI) + 1) / 2;
}
http://www.vbforums.com/showthread.php?t=568889
A naturally-moving window would accelerate as it started moving, and decelerate as it stopped. The speed vs. time graph would look like a bell curve, or maybe the top of a triangle wave. The triangle wave would be easier to implement.
As you move the box, you need to steadily increase the number of pixels you are moving the box each time through the loop, until you reach the halfway point between point a and point b, at which you will steadily decrease the number of pixels you are moving the box by. There is no special math involved; it is just addition and subtraction.
If you are bored enough, you can do loopback VNC to drag the mouse yourself.
Now, as for why you would want to I don't know.