-2

I have a PyQt application in which I have to implement a drap n drop functionality of points made via QPainter. My problem is that I'm even able to drag those points outside the window scope e.g. I can drag the point to the title bar or taskbar and leave it there and once left there, I can no longer drag them back to my Mainwindow.

Please provide a solution so that I can never ever drag them there.

Code:

import sys
import numpy as np

from PyQt4 import QtCore, QtGui


class Canvas(QtGui.QWidget):

    DELTA = 100 #for the minimum distance        

    def __init__(self, parent=None):
        super(Canvas, self).__init__(parent)
        self.draggin_idx = -1        
        self.points = np.array([[x[0],x[1]] for x in [[100,200], [200,200], [100,400], [200,400]]], dtype=np.float)  
        self.id = None 

        self.points_dict = {}

        for i, x in enumerate(self.points):
            point=(int(x[0]),int(x[1]))
            self.points_dict[i] = point

    def paintEvent(self, e):
        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawPoints(qp)
        self.drawLines(qp)
        qp.end()

    def drawPoints(self, qp):
        # qp.setPen(QtCore.Qt.red)
        pen = QtGui.QPen()
        pen.setWidth(10)
        pen.setColor(QtGui.QColor('red'))
        qp.setPen(pen)
        for x,y in self.points:
            qp.drawPoint(x,y)        

    def drawLines(self, qp):
        # pen.setWidth(5)
        # pen.setColor(QtGui.QColor('red'))
        qp.setPen(QtCore.Qt.red)
        qp.drawLine(self.points_dict[0][0], self.points_dict[0][1], self.points_dict[1][0], self.points_dict[1][1])
        qp.drawLine(self.points_dict[1][0], self.points_dict[1][1], self.points_dict[3][0], self.points_dict[3][1])
        qp.drawLine(self.points_dict[3][0], self.points_dict[3][1], self.points_dict[2][0], self.points_dict[2][1])
        qp.drawLine(self.points_dict[2][0], self.points_dict[2][1], self.points_dict[0][0], self.points_dict[0][1])

    def _get_point(self, evt):
        return np.array([evt.pos().x(),evt.pos().y()])

    #get the click coordinates
    def mousePressEvent(self, evt):
        if evt.button() == QtCore.Qt.LeftButton and self.draggin_idx == -1:
            point = self._get_point(evt)
            int_point = (int(point[0]), int(point[1]))
            min_dist = ((int_point[0]-self.points_dict[0][0])**2 + (int_point[1]-self.points_dict[0][1])**2)**0.5
            
            for i, x in enumerate(list(self.points_dict.values())):
                distance = ((int_point[0]-x[0])**2 + (int_point[1]-x[1])**2)**0.5
                if min_dist >= distance:
                    min_dist = distance
                    self.id = i
                    
            #dist will hold the square distance from the click to the points
            dist = self.points - point
            dist = dist[:,0]**2 + dist[:,1]**2
            dist[dist>self.DELTA] = np.inf #obviate the distances above DELTA
            if dist.min() < np.inf:
                self.draggin_idx = dist.argmin()        

    def mouseMoveEvent(self, evt):
        if self.draggin_idx != -1:
            point = self._get_point(evt)
            self.points[self.draggin_idx] = point
            self.update()

    def mouseReleaseEvent(self, evt):
        if evt.button() == QtCore.Qt.LeftButton and self.draggin_idx != -1:
            point = self._get_point(evt)
            int_point = (int(point[0]), int(point[1]))
            self.points_dict[self.id] = int_point
            self.points[self.draggin_idx] = point
            self.draggin_idx = -1
            self.update()


if __name__ == "__main__":
    app = QtGui.QApplication([])
    win = Canvas()
    win.showMaximized()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Voldemort
  • 175
  • 5
  • 20
  • Please, make your examples **coherent** (other than **minimal**). A QGraphicsScene is **NOT** a QWidget. – musicamante Feb 10 '21 at 17:19
  • @musicamante, ohh...I didn't know that "A QGraphicsScene is NOT a QWidget". I'll take care of this next time onwards. – Voldemort Feb 10 '21 at 17:22
  • 1
    @deepanshu Do not change or add more problems to your post as that will make your question unsolvable. The answers are based on the MRE provided by the OP, if you change the MRE then you will probably have another answer and that is a big drawback. If you have a new problem then create a new post – eyllanesc Feb 10 '21 at 17:23
  • @eyllanesc, Okay I'll make sure it doesn't happen again. But could you please help me out? – Voldemort Feb 10 '21 at 17:26
  • 1
    @deepanshu No, if I help you in your new post then another user will take it as an example and do the same wrong action – eyllanesc Feb 10 '21 at 17:27
  • 1
    Even if you didn't know that, you cannot ask a question with code that is not related to what you need. While examples should be kept minimal, they also must reflect their context; the problem is not that you didn't know about the difference, but that you didn't mention any of that *at all*. If you ask about QPainter and show a widget with its paintEvent, then how can we even imagine that you're trying to do a completely different thing? And, yes, we could help you out: create a *new and appropriate* question, as we won't answer here, because is completely off topic to what you asked. – musicamante Feb 10 '21 at 17:29
  • 1
    @deepanshu Please do not modify your post by adding information from the QGraphicsScene since in your MRE there is no trace of it, also you have already indicated that the published answer solves your problem. With these types of actions you are only discouraging the community from helping you – eyllanesc Feb 10 '21 at 17:29
  • @eyllanesc sorry...should I post a separate question about implementing this over a ```QGraphicsScene```? – Voldemort Feb 10 '21 at 17:31
  • 1
    @deepanshu Exactly, if you have another problem (just entering another element makes it another problem) then what should be done ?: create a new post. That is normal in SO and I am surprised that you do not know that rule since you have more than 24 posts made and more than 3 years on the site – eyllanesc Feb 10 '21 at 17:33

1 Answers1

1

This has nothing to do with the painting (which obviously cannot go "outside"), but to the way you're getting the coordinates.

Just ensure that the point is within the margins of the widget:

    def _get_point(self, evt):
        pos = evt.pos()
        if pos.x() < 0:
            pos.setX(0)
        elif pos.x() > self.width():
            pos.setX(self.width())
        if pos.y() < 0:
            pos.setY(0)
        elif pos.y() > self.height():
            pos.setY(self.height())
        return np.array([pos.x(), pos.y()])
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • @musicamanate The problem got solved thanks to you. But could you please tell me one thing, when I apply this whole '''drag n drop''' over a ```QGraphicsView```, then I'm unable to drag the points upto corners and edges....some amount of padding is always left. Why is this happening? – Voldemort Feb 10 '21 at 17:13
  • 1
    @deepanshu a QGraphicsScene is a **completely** different thing: the boundaries of a scene are virtually infinite (that's why it's scrollable). If your question was about using a QGraphicsScene, instead of a standard QWidget, you should have asked about *THAT*. – musicamante Feb 10 '21 at 17:18