Search code examples
pythonpython-3.xpyqtpyqt4qpainter

Prevent QPainter point from going outside the window scope


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_())

Solution

  • 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()])