I try to lock mouse cursor movements to the left half of the screen. I have following screen setup:
On the left side is a Qt window of size 1120x1080, on the right side a GL window of size 800x1080.
I use Openbox window manager under Ubuntu 12.10. The window layout stays fixed.
I need to restrict mouse movement to the Qt window.
To get the mouse to stay in the window, enable mouse movement with:
setMouseTracking(true);
and override void QWidget::mouseMovement( QMouseEvent * event )
void TheWindow::mouseMoveEvent ( QMouseEvent * event )
{
// get window size without frame
QRect s = geometry();
// get current cursor position
int x = event->globalX();
int y = event->globalY();
bool reset = false;
// Check cursor position relative to window
if (event->x() < 0)
{
x -= event->x();
reset = true;
}
else if (event->x() >= s.width())
{
x += s.width() - event->x() - 1;
reset = true;
}
if (event->y() < 0)
{
y -= event->y();
reset = true;
}
else if (event->y() >= s.height())
{
y += s.height() - event->y() - 1;
reset = true;
}
// if reset needed move cursor
if (reset) QCursor::setPos(x,y);
}
this involving QGraphicsItem::itemChange(). If you have an item which you want to restrict to a certain area then reimplement itemChange() for that item and monitor QGraphicsItem::ItemPositionHasChanged changes to see whether the items wants to be placed outside your area of interest and prevent that by returning a position from inside that area. for example:
QVariant QGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
switch (change) {
case ItemPositionHasChanged:
if(x() < -200 || y() < -200 || x() > 200 || y() > 200)
setPos(0, 0);
graph->itemMoved();
break;
default:
break;
};
return QGraphicsItem::itemChange(change, value);
}
Related
I am building a major user interface but I am stuck on a problem and in order to shrink the problem I build a small working example that carries exactly the problem.
After the user creates a new .db file using the icon and saving for example to Desktop it is possible to load images(only in .png format for now) in the QGraphicsView and cliking on the checkbox to enable from Right: Drag to Right:Select it is possible to draw boxes on the image. With a right click inside the drawn box we can open a QDialog that asks to name the extracted image from the box. The image is stored in the QTableView as shown in the small working example below:
The problem:
I tried several ways to erase the box (and the related index on the QTableView of the related box) but none of them was successful.
With the use of the "Erase Square" QPushButton I am trying to erase the box. Every box can be re-activated by just left double-clicking within the interested box. See below what I am trying to achieve:
I draw the box on the QGraphicsView and right mouse click to capture the image from the box:
void MainWindow::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::RightButton)
{
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ShowContextMenu(const QPoint &)));
}
}
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
Q_UNUSED(event);
leftScene->clearSelection(); // Selections would also render to the file
leftScene->setSceneRect(leftScene->itemsBoundingRect()); // Re-shrink the scene to it's bounding contents
QImage image(leftScene->sceneRect().size().toSize(), QImage::Format_ARGB32); // Create the image with the exact size of the shrunk scene
image.fill(Qt::transparent); // Start all pixels transparent
QPainter painter(&image);
leftScene->render(&painter);
QImage subimage = image.copy(QRect(start.toPoint(), end.toPoint()));
clipSceneDialog d(this);
d.show();
d.setImage(subimage);
d.setHeaderImage(currentLeftImagePath);
d.setBoundingBox(QRect(start.toPoint(), end.toPoint()));
if(d.exec() == QDialog::Rejected) {
return;
} else {
//
}
Param result = d.getData();
Parameters* param = new Parameters(result);
mDatabaseLeftCamera->addItem(param);
mModelLeftCamera->select();
ui->tableView->show();
}
Draw as many boxes as the user wants:
void MainWindow::onRubberBandUpdate(const QRect &viewportRect,
const QPointF &fromScenePoint,
const QPointF &toScenePoint)
{
if(viewportRect.isNull() && fromScenePoint.isNull() && toScenePoint.isNull() && imageLoaded)
{
if(currentSelection >= 0)
selections[currentSelection]->setActiveState(false);
QRectF select;
select.setCoords(start.x(), start.y(), end.x(), end.y());
square = new Square(select);
square->setActiveState(true);
currentSelection = selections.size();
selections.append(square);
leftScene->addItem(square->getGraphics());
ui->graphicsView->show();
}
else
{
start = fromScenePoint;
end = toScenePoint;
}
}
After drawing several boxes the user decides to reactivate one of the boxes by double-cliking within the box:
void MainWindow::onSceneDoubleClick(QPointF point)
{
qDebug() << "Click!";
QList<QGraphicsItem*> foundItems = leftScene->items(point);
if(foundItems.size() > 0 && foundItems[0]->group() != nullptr)
{
qDebug() << "Found";
int i = 0;
for(i=0;i<selections.size();i++)
{
qDebug() << "Iterate";
if(selections[i]->getGraphics() == foundItems[0]->group())
{
qDebug() << "Target";
break;
}
}
if(currentSelection >= 0)
selections[currentSelection]->setActiveState(false);
currentSelection = i;
selections[currentSelection]->setActiveState(true);
}
}
The user decides to erase the re-activated square:
void MainWindow::on_eraseSquare_clicked()
{
clearSceneLeft();
}
void MainWindow::clearSceneLeft()
{
if (selections.size() > 0) {
qDeleteAll(selections);
selections.clear();
currentSelection = -1;
}
for(int p=0;p<shape.size();p++)
{
leftScene->removeItem(shape[p]);
delete shape[p];
}
shape.clear();
}
But this is not working and nothing happens. Additionally because the box corresponds to an SQL reference on the QTableView I dont know how that could be connected in the SQL class.
I have been trying for many days to solve this problem and am running out of ideas. Thanks for shedding light on this problem or point in the right direction.
If you would like to try the small example interface you can do it from here
I make my own class from QWidget with redefine of paintEvent(), mousePressEvent(), mouseReleaseEvent() and mouseMoveEvent(). All that methods for move widgets over other widget (yellow).
When i create my widgets in a layout, it looks like this:
But when I move black widget to the bottom and red to the top like this:
and resize window, all widgets refresh to their align positions:
But i want, when i move one widget higher then another, the widgets should align in layout in new places, like this:
Which function i should redefine to do it?
P.S.
There is a piece of code, that can move widgets positions inside layout (change their indexes), but i don't know how find out their (x,y) position to calculate new indexes in layout. I think, that i can do it in resizeEvent().
But it when it event was emitted, positions already changed to old. (like before moveing on 1 picture), and i need positions after moveing (like on secon picture). How can i get position of widget before it will be aligned?
or How can i change order of widget in layout by drag and drop with the mouse?
I write my own widget, then redefine following methods: mouseReleaseEvent(), paintEvent(), mousePressEvent(), mouseMoveEvent(). In mousePressEvent() I hold old X and Y positions and mouse position on figure. Then in mouseMoveEvent() i calculate if minimum distance of mouse move is riched and move widget to new position (it not moves widget index in layout). After it, if emitted mouseReleaseEvent() i just calculate new index of moving widget and change and update parent layout. If widget moves less then it height, then layout just updates without changing widget index.
void SimpleWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if (!IsMinimumDistanceRiched(event))
{
return;
}
int y = event->globalY() - mouseClickY + oldY;
int BottomBorder = parentWidget->geometry().height() - this->geometry().height();
if(y < 0) y = 0;
else if(y > BottomBorder) y = BottomBorder;
move(oldX, y);
}
void SimpleWidget::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
dragStartPosition = event->pos();
oldX = this->geometry().x();
oldY = this->geometry().y();
mouseClickX = event->globalX();
mouseClickY = event->globalY();
}
bool SimpleWidget::IsMinimumDistanceRiched(QMouseEvent *event)
{
return (event->pos() - dragStartPosition).manhattanLength() >= QApplication::startDragDistance();
}
bool SimpleWidget::moveInLayout(QWidget *widget, MoveDirection direction)
{
QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(widget->parentWidget()->layout());
const int index = myLayout->indexOf(widget);
if (direction == MoveUp && index == 0)
{
return false;
}
if (direction == MoveDown && index == myLayout->count()-1 )
{
return false;
}
const int newIndex = direction == MoveUp ? index - 1 : index + 1;
myLayout->removeWidget(widget);
myLayout->insertWidget(newIndex , widget);
return true;
}
void SimpleWidget::paintEvent(QPaintEvent *)
{
QStyleOption o;
o.initFrom(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this);
}
void SimpleWidget::mouseReleaseEvent(QMouseEvent *)
{
int y = geometry().y();
MoveDirection direct;
int offset;
if(oldY > y)
{
offset = oldY - y;
direct = MoveUp;
}
else if(oldY < y)
{
offset = y - oldY;
direct = MoveDown;
}
int count = offset/height();
for(int i = 0; i < count; i++)
{
moveInLayout(this, direct);
}
update();
QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(this->parentWidget->layout());
myLayout->update();
this->saveGeometry();
}
Can someone please share the algorithm to rotate and resize a QGraphicsLineItem present on a QGraphicsScene? I wish to be able to click on either end of the line and rotate it while the opposite end automatically becomes the anchor point for rotation.
I have tried the solution given below and it works brilliantly!!. The solution is for line rotation only. This solution is provided to me when i posted the question at other Qt platform. All the credit goes to original author who provided the solution to me.
void Line::mousePressEvent( QGraphicsSceneMouseEvent * event ){
const QPointF pos = event->pos();
const qreal l1 = QLineF( pos, this->line().p1() ).length();
const qreal l2 = QLineF( pos, this->line().p2() ).length();
const qreal threshold = 3.5;
if( l1 < l2 and l1 < threshold ){
_dragIndex = 1;
} else if ( l2 < l1 and l2 < threshold ){
_dragIndex = 0;
} else{
_dragIndex = -1;
}
event->setAccepted( _dragIndex != -1 );
}
void Line::mouseMoveEvent( QGraphicsSceneMouseEvent * event ){
if( _dragIndex != -1 ){
const QPointF anchor = _dragIndex == 0 ? this->line().p1() : this->line().p2();
QLineF ma = QLineF(anchor,event->pos());
ma.setLength( line().length() );
const QPointF rotated = anchor + QPointF( ma.dx(), ma.dy() );
this->setLine( _dragIndex == 0 ? QLineF(anchor,rotated) : QLineF(rotated,anchor) );
}
}
void Line::mouseReleaseEvent( QGraphicsSceneMouseEvent * event ){
_dragIndex = -1;
QGraphicsLineItem::mouseReleaseEvent(event);
}
1- Overload mouseMoveEvent of your scene. Make sure you are in the initial state, no clicks, nothing. If position is close enough to the ends, set your flag as such.
// scene.cpp
void mouseMoveEvent(QMouseEvent *e){
if(moveToRotate){
lineItem->rotate(flag, e->pos());
...
return;
}
if(isClose(e->pos(), lineItem->firstEnd)
flag = 1;
else if(isClose(e->pos(), lineItem->secondEnd)
flag = 2;
else
flag = 0;
return;
}
2- Overload mouseClickEvent of the scene. If flag is set (1 or 2), get your anchor point. You can overload paint method of lineItem to highlight the end point. Also set firstClick flag.
void mouseClickEvent(QMouseEvent *e){ // firstClick happened
if(flag !=0){ //process click
firstClick = true;
paintAnchor = true;
moveToRotate = true;
}
else // neglect click
return;
return;
}
// myCustomLineItemclassThatInheritsQGraphicsLineItem.cpp
void paint(... ){
if(paintAnchor){
...
}
}
There are obvious errors, but the general flow should be as such. You may need a signal to connect the boolean to the paint event of another class. You may need to overload mouseReleaseEvent to properly set moveToRotate. And, of course rotate and isClose methods are yours to be written -maybe even exist in the API.
Just construct the main flowchart, it will help you a lot.
Im implementing the zooming of my QGraphicsView using wheelEvent
void View::wheelEvent(QWheelEvent *e)
{
if (e->modifiers().testFlag(Qt::ControlModifier)){ // zoom only when CTRL key pressed
int numSteps = e->delta() / 15 / 8;
if (numSteps == 0) {
e->ignore();
return;
}
qreal sc = pow(1.25, numSteps); // I use scale factor 1.25
this->zoom(sc);
e->accept();
}
}
and the zoom item
void View::zoom(qreal scaleFactor)
{
scale(scaleFactor, scaleFactor);
}
here i don’t want to zoom out too deeper, all i need it to restrict the scaling to certain point , i have to limit the zoom out so i tried to find the transform point
qreal
View::zoomScale() const
{
return transform().m11();
}
but with this i cant able to restrict the zooming .
please help me find the solution.
You can calculate the zoom factor relative to the "normal zoom" and decide if you can zoom or not.
For example, you can take a QRect for reference and check its size after the zoom :
void ClassA::scale(qreal scaleFactor) {
QRectF(0, 0, 1, 1); // A reference
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(r).width(); // absolute zoom factor
if ( factor > 20 ) { // Don't zoom more than 20x
return;
}
this->scale(scaleFactor, scaleFactor);
}
I have a scene which has fixed dimensions from (0;0) to (481;270):
scene->setSceneRect(0, 0, 481, 270);
Inside of it, I have a custom GraphicsItem and I can move it thanks to the flag ItemisMovable, but I would like it to stay within the Scene; I actually mean I don't want it to have coordinates neither under (0;0) nor over (481;270).
I tried several solutions like overriding QGraphicsItem::itemChange() or even QGraphicsItem::mouseMoveEvent() but I still cannot manage to reach what I want to do.
What is the suitable solution for my needs? Do I use QGraphicsItem::itemChange() badly?
Thanks in advance.
You can override QGraphicsItem::mouseMoveEvent() like this:
YourItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem::mouseMoveEvent(event); // move the item...
// ...then check the bounds
if (x() < 0)
setPos(0, y());
else if (x() > 481)
setPos(481, y());
if (y() < 0)
setPos(x(), 0);
else if (y() > 270)
setPos(x(), 270);
}
This code keeps your complete item in scene. Not only the upper left pixel of your item.
void YourItem::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
{
QGraphicsItem::mouseMoveEvent(event);
if (x() < 0)
{
setPos(0, y());
}
else if (x() + boundingRect().right() > scene()->width())
{
setPos(scene()->width() - boundingRect().width(), y());
}
if (y() < 0)
{
setPos(x(), 0);
}
else if ( y()+ boundingRect().bottom() > scene()->height())
{
setPos(x(), scene()->height() - boundingRect().height());
}
}
Warning: The suggested solutions won't work for multiply selected items. The problem is, only one of the items receives a mouse move event in that case.
Actually, the Qt Documentation on QGraphicsItem provides an example which exactly solves the problem of limiting the movement of items to the scene rect:
QVariant Component::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
// value is the new position.
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
// Keep the item inside the scene rect.
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
return newPos;
}
}
return QGraphicsItem::itemChange(change, value);
}
Note I: You'd have to enable the QGraphicsItem::ItemSendsScenePositionChanges flag:
item->setFlags(QGraphicsItem::ItemIsMovable
| QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemSendsScenePositionChanges);
Note II: If you only want to react on finished movement, consider using the GraphicsItemChange flag ItemPositionHasChanged