Exemple #1
0
 def __init__(self, gui):
     super(JoystickButton, self).__init__()
     self.gui = gui  # parent widget
     self.size = 360  # size in pixels of the push button
     self.state = None  # a QPoint representing the position of the crosshair in pixels
     self.pan_limits = (0, 360)  # width of the push button in pixels
     self.tilt_limits = (0, 360)  # height of the push button in pixels
     self.setCheckable(True)  # allow the push button to be 'checked'
     self.setFixedWidth(self.size)
     self.setFixedHeight(self.size)
     self.center = QPoint(
         self.size / 2.0, self.size / 2.0
     )  # (0,0) in pixels start from the upper left corner of the push button, so set the center to the middle (QPoint type)
     self.current_pos = QPoint(
         self.size / 2.0, self.size / 2.0
     )  # a QPoint representing the current position of the turret in pixels
     self.pan_deg_limits = [
         self.gui.name_map["pan"]["min_lower_limit"],
         self.gui.name_map["pan"]["max_upper_limit"]
     ]  # Pan joint limits in degrees
     self.tilt_deg_limits = [
         self.gui.name_map["tilt"]["min_lower_limit"],
         self.gui.name_map["tilt"]["max_upper_limit"]
     ]  # Tilt joint limits in degrees
     self.setJointCommands([
         float(self.gui.name_map["pan"]["display"].text()),
         float(self.gui.name_map["tilt"]["display"].text())
     ])  # set the default crosshair position to the current turret position
Exemple #2
0
def transformPointToPixelCoordinates(pt, map, image_size):
    map_point_f = ((pt - QPointF(map.map.info.origin.position.x,
                                 map.map.info.origin.position.y)) *
                   (1.0 / map.map.info.resolution))
    map_point = QPoint(int(map_point_f.x()), int(map_point_f.y()))
    map_size = QSize(map.map.info.width, map.map.info.height)
    scaled_point = scalePoint(map_point, map_size, image_size)
    return QPoint(scaled_point.x(), image_size.height() - scaled_point.y() - 1)
Exemple #3
0
 def mousePressEvent(self, event):
     '''
     Set the starting position for a drag event when the mouse button is pressed
     '''
     self.drag_droped_pos = QPoint(0, 0)
     self.drag_initiated = False
     if event.button() == Qt.LeftButton:
         self.drag_start_pos = event.pos()
     QTabBar.mousePressEvent(self, event)
Exemple #4
0
 def dragEnterEvent(self, event):
     '''
     Determine if the drag has entered a tab position from another tab position
     '''
     self.drag_droped_pos = QPoint(0, 0)
     mime_data = event.mimeData()
     formats = mime_data.formats()
     if 'action' in formats and mime_data.data('action') == 'application/tab-detach':
         event.acceptProposedAction()
     QTabBar.dragMoveEvent(self, event)
Exemple #5
0
 def setJointStates(self, positions):
     new_pan = round(
         self.deg2Pxl(positions[0], self.pan_deg_limits[0],
                      self.pan_deg_limits[1]))
     new_tilt = round(self.size - self.deg2Pxl(
         positions[1], self.tilt_deg_limits[0], self.tilt_deg_limits[1]))
     # Since this function is called at around 100 Hz, only update the QPushButton if the current position has significantly changed
     if (abs(new_pan - self.current_pos.x()) >= 1
             or abs(new_tilt - self.current_pos.y()) >= 1):
         self.current_pos = QPoint(new_pan, new_tilt)
         self.update()
Exemple #6
0
    def __init__(self, parent=None):
        QTabBar.__init__(self, parent)

        self.setAcceptDrops(True)
        self.setElideMode(Qt.ElideRight)
        self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)

        self.drag_start_pos = QPoint()
        self.drag_droped_pos = QPoint()
        self.mouse_cursor = QCursor()
        self.drag_initiated = False
Exemple #7
0
    def __init__(self, parent=None):
        glformat = QGLFormat()
        glformat.setSampleBuffers(True)
        super(GLWidget, self).__init__(glformat, parent)

        self.setCursor(Qt.OpenHandCursor)
        self.setMouseTracking(True)

        self._modelview_matrix = numpy.identity(4)
        self._near = 0.001
        self._far = 100000.0
        self._fovy = 45.0
        self._radius = 5.0
        self._last_point_2d = QPoint()
        self._last_point_3d = [0.0, 0.0, 0.0]
        self._last_point_3d_ok = False
    def paintEvent(self, event):
        geom = super(AxisWidget, self).geometry()
        h = geom.height()
        w = geom.width()

        box = QRect(2, 2, w - 4, h - 4)
        horiz = QLine(2, h / 2, w - 2, h / 2)
        vert = QLine(w / 2, 2, w / 2, h - 2)
        targ = QPoint(self._x * (w - 4) / 2 + w / 2,
                      self._y * (h - 4) / 2 + h / 2)

        plt = super(AxisWidget, self).palette()
        linebrsh = plt.dark()
        targetbrsh = plt.highlight()

        linepen = QPen(linebrsh, 1, Qt.SolidLine, Qt.SquareCap)
        targetpen = QPen(targetbrsh, 2, Qt.SolidLine, Qt.SquareCap)

        qp = QPainter()
        qp.begin(self)
        qp.setPen(linepen)
        qp.drawRect(box)
        qp.drawLine(horiz)
        qp.drawLine(vert)
        qp.setPen(targetpen)
        qp.drawEllipse(targ, 10, 10)
        qp.end()
Exemple #9
0
    def paint(self, painter, option, index):
        '''
        Use the QTextDokument to represent the HTML text.
        @see: U{http://www.pyside.org/docs/pyside/PySide/QtGui/QAbstractItemDelegate.html#PySide.QtGui.QAbstractItemDelegate}
        '''
        options = QStyleOptionViewItem(option)
        self.initStyleOption(options, index)

        style = QApplication.style(
        ) if options.widget is None else options.widget.style()

        doc = QTextDocument()
        doc.setHtml(self.toHTML(options.text))
        # doc.setTextWidth(option.rect.width())

        options.text = ''
        style.drawControl(QStyle.CE_ItemViewItem, options, painter)

        ctx = QAbstractTextDocumentLayout.PaintContext()

        # Highlighting text if item is selected
        # if (optionV4.state and QStyle::State_Selected):
        #  ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));

        textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options,
                                        options.widget)
        painter.save()
        painter.translate(
            QPoint(textRect.topLeft().x(),
                   textRect.topLeft().y() - 3))
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        doc.documentLayout().draw(painter, ctx)

        painter.restore()
Exemple #10
0
 def setJointCommands(self, commands):
     new_pan = round(
         self.deg2Pxl(commands[0], self.pan_deg_limits[0],
                      self.pan_deg_limits[1]))
     new_tilt = round(self.size - self.deg2Pxl(
         commands[1], self.tilt_deg_limits[0], self.tilt_deg_limits[1]))
     self.setState(QPoint(new_pan, new_tilt))
     self.update()
    def __init__(self, parent=None):
        glformat = QGLFormat()
        glformat.setSampleBuffers(True)
        super(GLWidget, self).__init__(glformat, parent)

        self.setCursor(Qt.OpenHandCursor)
        self.setMouseTracking(True)

        self._modelview_matrix = numpy.identity(4)
        self._near = 0.1
        self._far = 4000.0
        self._fovy = 45.0
        self._radius = 50.0
        self._last_point_2d = QPoint()
        self._last_point_3d = [0.0, 0.0, 0.0]
        self._last_point_3d_ok = False
Exemple #12
0
 def readSettings(self):
     if nm.settings().store_geometry:
         settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE)
         settings.beginGroup("editor")
         maximized = settings.value("maximized", 'false') == 'true'
         if maximized:
             self.showMaximized()
         else:
             self.resize(settings.value("size", QSize(800, 640)))
             self.move(settings.value("pos", QPoint(0, 0)))
         try:
             self.restoreState(settings.value("window_state"))
         except:
             import traceback
             print traceback.format_exc()
         settings.endGroup()
Exemple #13
0
        def drawControl (self, element, opt, painter, widget=None):

#            print element
            if element == QStyle.CE_HeaderLabel:
                hv = widget
#                if not hv or hv.orientation() != Qt.Horizontal:
#                    return super(StateHistoryDialog.MyStyle, self).drawControl(element, opt, p, widget)
                header = opt

                if header.section < 3:
                    return super(StateHistoryDialog.MyStyle, self).drawControl(element, opt, painter, widget)

                painter.save()
                #// painter->translate(header->rect.topLeft())
                rect = header.rect.bottomLeft()
                painter.translate(QPoint(rect.x() + 10, rect.y() + 5))
#                print "drawControl"
                painter.rotate(-90)
                painter.drawText(0,0,header.text)
                painter.restore()
                return
#            return QProxyStyle::drawControl(element, option, painter, widget);
#            print "drawControl"
            return super(StateHistoryDialog.MyStyle, self).drawControl(element, opt, painter, widget)
Exemple #14
0
 def setState(self, ev):
     pnt = QPoint(ev.x(), ev.y())
     # Since (0,0) is in the upper left corner, that means that increasingly negative Tilt values will be closer to the top of the push button; However,
     # However, it makes visual sense to flip this so that increasingly negative Tilt values are closer to the bottom of the push button; thus, the funky math... :)
     if (pnt.x() < self.pan_limits[0] or pnt.y() <
         (self.size - self.tilt_limits[1]) or pnt.x() > self.pan_limits[1]
             or pnt.y() > (self.size - self.tilt_limits[0])):
         return
     if self.state == pnt:
         return
     self.state = pnt
     self.update()
     if self.isChecked():
         cmd_x = self.pxl2Deg(self.state.x(), self.pan_deg_limits[0],
                              self.pan_deg_limits[1])
         cmd_y = -self.pxl2Deg(self.state.y(), self.tilt_deg_limits[0],
                               self.tilt_deg_limits[1])
         self.gui.name_map["pan"]["display"].setText("%.1f" % cmd_x)
         self.gui.name_map["tilt"]["display"].setText("%.1f" % cmd_y)
         self.gui.update_slider_bar("pan")
         self.gui.update_slider_bar("tilt")
class GLWidget(QGLWidget):

    def __init__(self, parent=None):
        glformat = QGLFormat()
        glformat.setSampleBuffers(True)
        super(GLWidget, self).__init__(glformat, parent)

        self.setCursor(Qt.OpenHandCursor)
        self.setMouseTracking(True)

        self._modelview_matrix = numpy.identity(4)
        self._near = 0.1
        self._far = 4000.0
        self._fovy = 45.0
        self._radius = 50.0
        self._last_point_2d = QPoint()
        self._last_point_3d = [0.0, 0.0, 0.0]
        self._last_point_3d_ok = False


    def initializeGL(self):
        glClearColor(0.65, 0.65, 0.65, 0.0)
        glEnable(GL_DEPTH_TEST)

    def resizeGL(self, width, height):
        glViewport(0, 0, width, height)
        self.set_projection(self._near, self._far, self._fovy)
        self.updateGL()

    def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glMatrixMode(GL_MODELVIEW)
        glLoadMatrixd(self._modelview_matrix)

    def get_view_matrix(self):
        return self._modelview_matrix.tolist()

    def set_view_matrix(self, matrix):
        self._modelview_matrix = numpy.array(matrix)
    def load_view_matrix(self,view_matrix):
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glLoadMatrixd(view_matrix)
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def set_projection(self, near, far, fovy):
        self._near = near
        self._far = far
        self._fovy = fovy
        self.makeCurrent()
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        height = max(self.height(), 1)
        gluPerspective(self._fovy, float(self.width()) / float(height), self._near, self._far)
        self.updateGL()

    def reset_view(self):
        # scene pos and size
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
        self.view_all()

    def reset_rotation(self):
        self._modelview_matrix[0] = [1.0, 0.0, 0.0, 0.0]
        self._modelview_matrix[1] = [0.0, 1.0, 0.0, 0.0]
        self._modelview_matrix[2] = [0.0, 0.0, 1.0, 0.0]
        glMatrixMode(GL_MODELVIEW)
        glLoadMatrixd(self._modelview_matrix)

    def translate(self, trans):
        # translate the object
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslated(trans[0], trans[1], trans[2])
        glMultMatrixd(self._modelview_matrix)
        # update _modelview_matrix
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def translate_absolute(self, trans):
        # translate the object
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslated(trans[0], trans[1], trans[2])
        self._modelview_matrix[3] = [0, 0, 0, self._modelview_matrix[3][3]]
        glMultMatrixd(self._modelview_matrix)
        # update _modelview_matrix
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def rotate(self, axis, angle):
        # rotate the object
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        t = [self._modelview_matrix[3][0], self._modelview_matrix[3][1], self._modelview_matrix[3][2]]
        glTranslatef(t[0], t[1], t[2])
        glRotated(angle, axis[0], axis[1], axis[2])
        glTranslatef(-t[0], -t[1], -t[2])
        glMultMatrixd(self._modelview_matrix)
        # update _modelview_matrix
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def rotate_absolute(self,axis,angle):
        # rotate the object
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        t = [self._modelview_matrix[3][0], self._modelview_matrix[3][1], self._modelview_matrix[3][2]]
        glTranslatef(t[0], t[1], t[2])
        glRotated(angle, axis[0], axis[1], axis[2])
        glTranslatef(-t[0], -t[1], -t[2])
        matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
        matrix[3][0], matrix[3][1],matrix[3][2] = t
        glLoadMatrixd(matrix)
        # update _modelview_matrix
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def view_all(self):
        self.translate([-self._modelview_matrix[0][3], -self._modelview_matrix[1][3], -self._modelview_matrix[2][3] - self._radius / 2.0])

    def wheelEvent(self, event):
        # only zoom when no mouse buttons are pressed, to prevent interference with other user interactions
        if event.buttons() == Qt.NoButton:
            try:
                delta = event.angleDelta().y()
            except AttributeError:
                delta = event.delta()
            d = float(delta) / 200.0 * self._radius
            self.translate([0.0, 0.0, d])
            self.updateGL()
            event.accept()

    def mousePressEvent(self, event):
        self._last_point_2d = event.pos()
        self._last_point_3d_ok, self._last_point_3d = self._map_to_sphere(self._last_point_2d)

    def mouseMoveEvent(self, event):
        new_point_2d = event.pos()

        if not self.rect().contains(new_point_2d):
            return

        new_point_3d_ok, new_point_3d = self._map_to_sphere(new_point_2d)


        dy = float(new_point_2d.y() - self._last_point_2d.y())
        h = float(self.height())

        # left button: move in x-y-direction
        if event.buttons() == Qt.LeftButton and event.modifiers() == Qt.NoModifier:
            dx = float(new_point_2d.x() - self._last_point_2d.x())
            w = float(self.width())
            z = -self._modelview_matrix[3][2] / self._modelview_matrix[3][3]
            n = 0.01 * self._radius
            up = math.tan(self._fovy / 2.0 * math.pi / 180.0) * n
            right = up * w / h
            self.translate([2.0 * dx / w * right / n * z, -2.0 * dy / h * up / n * z, 0.0])
            # left and middle button (or left + ctrl): rotate around center
        elif event.buttons() == (Qt.LeftButton | Qt.MidButton) or (event.buttons() == Qt.LeftButton and event.modifiers() == Qt.ControlModifier):
            if self._last_point_3d_ok and new_point_3d_ok:
                cos_angle = numpy.dot(self._last_point_3d, new_point_3d)
                if abs(cos_angle) < 1.0:
                    axis = numpy.cross(self._last_point_3d, new_point_3d)
                    angle = 2.0 * math.acos(cos_angle) * 180.0 / math.pi
                    self.rotate(axis, angle)
        # middle button (or left + shift): move in z-direction
        elif event.buttons() == Qt.MidButton or (event.buttons() == Qt.LeftButton and event.modifiers() == Qt.ShiftModifier):
            delta_z = self._radius * dy * 20.0 / h
            self.translate([0.0, 0.0, delta_z])

        # remember the new points and flag
        self._last_point_2d = new_point_2d
        self._last_point_3d = new_point_3d
        self._last_point_3d_ok = new_point_3d_ok

        # trigger redraw
        self.updateGL()

    def mouseReleaseEvent(self, _event):
        self._last_point_3d_ok = False


    def _map_to_sphere(self, pos):
        v = [0.0, 0.0, 0.0]
        # check if inside widget
        if self.rect().contains(pos):
            # map widget coordinates to the centered unit square [-0.5..0.5] x [-0.5..0.5]
            v[0] = float(pos.x() - 0.5 * self.width()) / self.width()
            v[1] = float(0.5 * self.height() - pos.y()) / self.height()
            # use Pythagoras to compute z (the sphere has radius sqrt(2.0*0.5*0.5))
            v[2] = math.sqrt(max(0.5 - v[0] * v[0] - v[1] * v[1], 0.0))
            # normalize direction to unit sphere
            v = numpy.array(v) / numpy.linalg.norm(v)
            return True, v
        else:
            return False, v

    def unproject_mouse_on_scene(self,pos):
        start_x, start_y, start_z = gluUnProject(pos.x(), pos.y(), 1, model=self._modelview_matrix,
                     proj=glGetDoublev(GL_PROJECTION_MATRIX))
        end_x, end_y, end_z = gluUnProject(pos.x(), pos.y(), 0, model=self._modelview_matrix,
                     proj=glGetDoublev(GL_PROJECTION_MATRIX))

        diff_x = end_x - start_x
        diff_y = end_y - start_y
        diff_z = end_z - start_z

        t = (0 - start_z) / diff_z

        x = start_x + (diff_x * t)
        y = start_y + (diff_y * t)

        return x, y,0
Exemple #16
0
 def get_rectangular_polygon(self, pt1, pt2):
     return QPolygon(
         [pt1, QPoint(pt1.x(), pt2.y()), pt2,
          QPoint(pt2.x(), pt1.y())])
Exemple #17
0
class JoystickButton(QPushButton):

    ### @brief Initialization of the JoystickButton class; it builds up from a QPushButton
    ### @param gui - the parent widget
    ### @details - code inspired from the JoystickButton class created in 'pyqtgraph'
    def __init__(self, gui):
        super(JoystickButton, self).__init__()
        self.gui = gui  # parent widget
        self.size = 360  # size in pixels of the push button
        self.state = None  # a QPoint representing the position of the crosshair in pixels
        self.pan_limits = (0, 360)  # width of the push button in pixels
        self.tilt_limits = (0, 360)  # height of the push button in pixels
        self.setCheckable(True)  # allow the push button to be 'checked'
        self.setFixedWidth(self.size)
        self.setFixedHeight(self.size)
        self.center = QPoint(
            self.size / 2.0, self.size / 2.0
        )  # (0,0) in pixels start from the upper left corner of the push button, so set the center to the middle (QPoint type)
        self.current_pos = QPoint(
            self.size / 2.0, self.size / 2.0
        )  # a QPoint representing the current position of the turret in pixels
        self.pan_deg_limits = [
            self.gui.name_map["pan"]["min_lower_limit"],
            self.gui.name_map["pan"]["max_upper_limit"]
        ]  # Pan joint limits in degrees
        self.tilt_deg_limits = [
            self.gui.name_map["tilt"]["min_lower_limit"],
            self.gui.name_map["tilt"]["max_upper_limit"]
        ]  # Tilt joint limits in degrees
        self.setJointCommands([
            float(self.gui.name_map["pan"]["display"].text()),
            float(self.gui.name_map["tilt"]["display"].text())
        ])  # set the default crosshair position to the current turret position

    ### @brief Returns whether the joystick button is currently pressed
    ### @param <boolean> [out] - current button state
    def isActive(self):
        if self.isChecked():
            return True
        else:
            return False

    ### @brief Redefines what happens when the Joystick button is pressed
    ### @param ev - QMouseEvent
    def mousePressEvent(self, ev):
        ev.accept()
        self.setChecked(True)
        self.setState(ev)

    ### @brief Redefines what happens when the mouse is moved
    ### @param ev - QMouseEvent
    def mouseMoveEvent(self, ev):
        ev.accept()
        self.setState(ev)

    ### @brief Redefines what happens when the mouse is released
    ### @param ev - QMouseEvent
    def mouseReleaseEvent(self, ev):
        ev.accept()
        self.setChecked(False)

    ### @brief Draws a circle in the QPushButton
    ### @param pnt - QPoint specifying where the circle should be drawn
    ### @param color - QColor specifying the circle color
    ### @param fill - whether the inside of the circle should be colored
    def drawCircle(self, pnt, color, fill=True):
        p = QPainter(self)
        p.setPen(color)
        if (fill):
            p.setBrush(QBrush(color))
        p.drawEllipse(pnt, 3, 3)

    ### @brief Draws a crosshair centered at the specified point
    ### @param pnt - QPoint specifying the point at which to draw the crosshair
    def drawCrosshair(self, pnt):
        p = QPainter(self)
        horz_line = QLine(0, pnt.y(), self.size, pnt.y())
        vert_line = QLine(pnt.x(), 0, pnt.x(), self.size)
        p.drawLines(horz_line, vert_line)

    ### @brief Draws a green rectangle showing the valid positions that the pan and tilt motors can be commanded
    def drawRect(self):
        p = QPainter(self)
        in_bounds = QRect(self.pan_limits[0], self.size - self.tilt_limits[1],
                          self.pan_limits[1] - self.pan_limits[0],
                          self.tilt_limits[1] - self.tilt_limits[0])
        p.fillRect(in_bounds, QColor(0, 200, 0, 128))
        p.drawRect(in_bounds)

    ### @brief Redefines a paint event (essentially what is drawn on the QPushButton)
    ### @param ev - QPaintEvent
    def paintEvent(self, ev):
        QPushButton.paintEvent(self, ev)
        self.drawRect()
        self.drawCircle(self.state, QColor(0, 0, 0), False)
        self.drawCircle(self.center, QColor(0, 0, 0))
        self.drawCircle(self.current_pos, QColor(255, 0, 0))
        self.drawCrosshair(self.state)

    ### @brief Converts from degrees to pixels
    ### @param value - values in degrees to convert to pixels
    ### @param min_lower_limit - absolute minimum angle that the motor can reach in degrees
    ### @param max_upper_limit - absolute maximum angle that the motor can reach in degrees
    ### @param new_val - value converted to pixels
    def deg2Pxl(self, value, min_lower_limit, max_upper_limit):
        degree_span = max_upper_limit - min_lower_limit
        value_scaled = float(value - min_lower_limit) / degree_span
        new_val = value_scaled * self.size
        return new_val

    ### @brief Converts from pixels to degrees
    ### @param value - values in pixels to convert to degrees
    ### @param min_lower_limit - absolute minimum angle that the motor can reach in degrees
    ### @param max_upper_limit - absolute maximum angle that the motor can reach in degrees
    ### @param new_val - value converted to degrees
    def pxl2Deg(self, value, min_lower_limit, max_upper_limit):
        value_scaled = float(value) / self.size
        new_val = min_lower_limit + value_scaled * (max_upper_limit -
                                                    min_lower_limit)
        return new_val

    ### @brief Sets the state of the crosshair in pixels
    ### @param ev - QMouseEvent containing the position of the cursor in pixels
    ### @details - checks to make sure that the desired crosshair position is within the pan and tilt limits before setting it; it
    ###            also converts the cursor position from pixels to degrees and updates the parent widget's slider bars accordingly
    def setState(self, ev):
        pnt = QPoint(ev.x(), ev.y())
        # Since (0,0) is in the upper left corner, that means that increasingly negative Tilt values will be closer to the top of the push button; However,
        # However, it makes visual sense to flip this so that increasingly negative Tilt values are closer to the bottom of the push button; thus, the funky math... :)
        if (pnt.x() < self.pan_limits[0] or pnt.y() <
            (self.size - self.tilt_limits[1]) or pnt.x() > self.pan_limits[1]
                or pnt.y() > (self.size - self.tilt_limits[0])):
            return
        if self.state == pnt:
            return
        self.state = pnt
        self.update()
        if self.isChecked():
            cmd_x = self.pxl2Deg(self.state.x(), self.pan_deg_limits[0],
                                 self.pan_deg_limits[1])
            cmd_y = -self.pxl2Deg(self.state.y(), self.tilt_deg_limits[0],
                                  self.tilt_deg_limits[1])
            self.gui.name_map["pan"]["display"].setText("%.1f" % cmd_x)
            self.gui.name_map["tilt"]["display"].setText("%.1f" % cmd_y)
            self.gui.update_slider_bar("pan")
            self.gui.update_slider_bar("tilt")

    ### @brief If the user changes the default Pan min/max values, this function converts the values from degrees to pixels
    ### @param min_value - new lower limit in degrees
    ### @param max_value - new upper limit in degrees
    def setPanMinMax(self, min_value, max_value):
        new_min = round(
            self.deg2Pxl(min_value, self.pan_deg_limits[0],
                         self.pan_deg_limits[1]))
        new_max = round(
            self.deg2Pxl(max_value, self.pan_deg_limits[0],
                         self.pan_deg_limits[1]))
        self.pan_limits = (new_min, new_max)
        self.update()

    ### @brief If the user changes the default Tilt min/max values, this function converts the values from degrees to pixels
    ### @param min_value - new lower limit in degrees
    ### @param max_value - new upper limit in degrees
    def setTiltMinMax(self, min_value, max_value):
        new_min = round(
            self.deg2Pxl(min_value, self.tilt_deg_limits[0],
                         self.tilt_deg_limits[1]))
        new_max = round(
            self.deg2Pxl(max_value, self.tilt_deg_limits[0],
                         self.tilt_deg_limits[1]))
        self.tilt_limits = (new_min, new_max)
        self.update()

    ### @brief Converts the current joint positions from degrees to pixels
    ### @param positions - list containing the pan and tilt values in degrees
    def setJointStates(self, positions):
        new_pan = round(
            self.deg2Pxl(positions[0], self.pan_deg_limits[0],
                         self.pan_deg_limits[1]))
        new_tilt = round(self.size - self.deg2Pxl(
            positions[1], self.tilt_deg_limits[0], self.tilt_deg_limits[1]))
        # Since this function is called at around 100 Hz, only update the QPushButton if the current position has significantly changed
        if (abs(new_pan - self.current_pos.x()) >= 1
                or abs(new_tilt - self.current_pos.y()) >= 1):
            self.current_pos = QPoint(new_pan, new_tilt)
            self.update()

    ### @brief Converts the current joint commands from degrees to pixels
    ### @param commands - list containing the pan and tilt values in degrees
    def setJointCommands(self, commands):
        new_pan = round(
            self.deg2Pxl(commands[0], self.pan_deg_limits[0],
                         self.pan_deg_limits[1]))
        new_tilt = round(self.size - self.deg2Pxl(
            commands[1], self.tilt_deg_limits[0], self.tilt_deg_limits[1]))
        self.setState(QPoint(new_pan, new_tilt))
        self.update()
    def _event(self, e):
        if e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
            qDebug('%spress, rel=%s, global=%s, diff=%s' % (
                (' - pseudo ' if self._releasing_and_repressing_while_dragging
                 else ''), e.pos(), e.globalPos(), e.globalPos() - self.pos()))
        if e.type() == QEvent.MouseButtonRelease and e.button(
        ) == Qt.LeftButton:
            qDebug('%srelease, rel=%s, global=%s, diff=%s' % (
                (' - pseudo ' if self._releasing_and_repressing_while_dragging
                 else ''), e.pos(), e.globalPos(), e.globalPos() - self.pos()))

        # store local position when pressing button before starting the custom drag'n'drop
        # only allow when layout is not frozen
        if self._dragging_parent is None and e.type(
        ) == QEvent.MouseButtonPress and e.button() == Qt.LeftButton and bool(
                self.features() & QDockWidget.DockWidgetMovable):
            self._dragging_local_pos = e.pos()

        if self._dragging_parent is None and self._dragging_local_pos is not None and e.type(
        ) == QEvent.Move and QApplication.mouseButtons() & Qt.LeftButton:
            if self._widget_at(e.pos()) is not None:
                qDebug(
                    'DockWidget._event() start drag, dockwidget=%s, parent=%s, floating=%s, pos=%s'
                    % (str(self), str(self.parent()), str(
                        self.isFloating()), str(self._dragging_local_pos)))
                self._dragging_parent = self.parent()
                # ignore further mouse events so that the widget behind this dock widget can be determined
                self.setAttribute(Qt.WA_TransparentForMouseEvents)

                # collect all main windows (except self.main_window) to re-implement QApplication.widgetAt() in self._widget_at()
                self._main_windows = [
                    self._container_manager.get_root_main_window()
                ]
                for container in self._container_manager.get_containers():
                    if container == self:
                        continue
                    self._main_windows.append(container.main_window)

        # unset local position when releasing button even when custom drag'n'drop has not been started
        if self._dragging_local_pos is not None and e.type(
        ) == QEvent.MouseButtonRelease and e.button(
        ) == Qt.LeftButton and not self._releasing_and_repressing_while_dragging:
            self._dragging_local_pos = None

        if self._dragging_parent is not None and e.type(
        ) == QEvent.MouseButtonRelease and e.button(
        ) == Qt.LeftButton and not self._releasing_and_repressing_while_dragging:
            qDebug(
                'DockWidget._event() stop drag, dockwidget=%s, parent=%s\n' %
                (self, self.parent()))
            self._dragging_parent = None
            self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
            self._main_windows = []

        if self._dragging_parent is not None and e.type(
        ) == QEvent.MouseMove and e.buttons(
        ) & Qt.LeftButton and not self._releasing_and_repressing_while_dragging:
            widget = self._widget_at(e.globalPos())
            new_parent = self._get_new_parent(widget)
            #print 'new_parent', new_parent, (new_parent.objectName() if new_parent else '')
            if new_parent is not None and new_parent != self.parent():
                self._releasing_and_repressing_while_dragging = True

                # schedule stop of pseudo drag'n'drop and let it complete
                mouse_release_event = QMouseEvent(QEvent.MouseButtonRelease,
                                                  self._dragging_local_pos,
                                                  e.globalPos(), Qt.LeftButton,
                                                  Qt.NoButton, e.modifiers())
                QApplication.instance().postEvent(self, mouse_release_event)
                QApplication.sendPostedEvents()

                # schedule reparent to hovered main window and let it complete
                reparent_event = ReparentEvent(self, new_parent)
                QApplication.instance().postEvent(self._container_manager,
                                                  reparent_event)
                QApplication.sendPostedEvents()

                # reenable mouse events to be able to receive upcoming pseudo mouse events
                self.setAttribute(Qt.WA_TransparentForMouseEvents, False)

                # schedule restart of pseudo drag'n'drop and let it complete
                mouse_repress_event = QMouseEvent(QEvent.MouseButtonPress,
                                                  self._dragging_local_pos,
                                                  e.globalPos(), Qt.LeftButton,
                                                  Qt.LeftButton, e.modifiers())
                QApplication.instance().postEvent(self, mouse_repress_event)
                QApplication.sendPostedEvents()

                # schedule move to trigger dock widget drag'n'drop required for snapping and showing rubber band and let it complete
                # move forth...
                mouse_move_event = QMouseEvent(
                    QEvent.MouseMove, self._dragging_local_pos,
                    e.globalPos() +
                    QPoint(QApplication.startDragDistance(), 1), Qt.NoButton,
                    Qt.LeftButton, e.modifiers())
                QApplication.instance().postEvent(self, mouse_move_event)
                QApplication.sendPostedEvents()
                # ...and back
                mouse_move_event = QMouseEvent(QEvent.MouseMove,
                                               self._dragging_local_pos,
                                               e.globalPos(), Qt.NoButton,
                                               Qt.LeftButton, e.modifiers())
                QApplication.instance().postEvent(self, mouse_move_event)
                QApplication.sendPostedEvents()

                # restore attributes after repressing the button
                self.setAttribute(Qt.WA_TransparentForMouseEvents)

                self._releasing_and_repressing_while_dragging = False

        return super(DockWidget, self).event(e)
Exemple #19
0
def transformPointToRealWorldCoordinates(pt, map, image_size):
    map_size = QSize(map.map.info.width, map.map.info.height)
    vertically_flipped_point = QPoint(pt.x(), image_size.height() - pt.y() - 1)
    map_point = scalePoint(vertically_flipped_point, image_size, map_size)
    return QPointF(map.map.info.origin.position.x, map.map.info.origin.position.y) + \
            QPointF(map_point) * map.map.info.resolution
Exemple #20
0
class GLWidget(QGLWidget):
    def __init__(self, parent=None):
        glformat = QGLFormat()
        glformat.setSampleBuffers(True)
        super(GLWidget, self).__init__(glformat, parent)

        self.setCursor(Qt.OpenHandCursor)
        self.setMouseTracking(True)

        self._modelview_matrix = numpy.identity(4)
        self._near = 0.001
        self._far = 100000.0
        self._fovy = 45.0
        self._radius = 5.0
        self._last_point_2d = QPoint()
        self._last_point_3d = [0.0, 0.0, 0.0]
        self._last_point_3d_ok = False

    ## ============================================
    ## callbacks for QGLWidget
    def initializeGL(self):
        glClearColor(1.0, 0.0, 0.0, 0.0)
        glEnable(GL_DEPTH_TEST)

    def resizeGL(self, width, height):
        glViewport(0, 0, width, height)
        self.set_projection(self._near, self._far, self._fovy)
        self.updateGL()

    def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glMatrixMode(GL_MODELVIEW)
        glLoadMatrixd(self._modelview_matrix)

    def get_view_matrix(self):
        return self._modelview_matrix.tolist()

    def set_view_matrix(self, matrix):
        self._modelview_matrix = numpy.array(matrix)

    def set_projection(self, near, far, fovy):
        self._near = near
        self._far = far
        self._fovy = fovy
        self.makeCurrent()
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        height = max(self.height(), 1)
        gluPerspective(self._fovy,
                       float(self.width()) / float(height), self._near,
                       self._far)
        self.updateGL()

    def reset_view(self):
        # scene pos and size
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
        self.view_all()

    def reset_rotation(self):
        self._modelview_matrix[0] = [1.0, 0.0, 0.0, 0.0]
        self._modelview_matrix[1] = [0.0, 1.0, 0.0, 0.0]
        self._modelview_matrix[2] = [0.0, 0.0, 1.0, 0.0]
        glMatrixMode(GL_MODELVIEW)
        glLoadMatrixd(self._modelview_matrix)

    def translate(self, trans):
        # translate the object
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslated(trans[0], trans[1], trans[2])
        glMultMatrixd(self._modelview_matrix)
        # update _modelview_matrix
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def rotate(self, axis, angle):
        # rotate the object
        self.makeCurrent()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        t = [
            self._modelview_matrix[3][0], self._modelview_matrix[3][1],
            self._modelview_matrix[3][2]
        ]
        glTranslatef(t[0], t[1], t[2])
        glRotated(angle, axis[0], axis[1], axis[2])
        glTranslatef(-t[0], -t[1], -t[2])
        glMultMatrixd(self._modelview_matrix)
        # update _modelview_matrix
        self._modelview_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)

    def view_all(self):
        self.translate([
            -self._modelview_matrix[0][3], -self._modelview_matrix[1][3],
            -self._modelview_matrix[2][3] - self._radius / 2.0
        ])

    ## ============================================
    ## Qt's event(?)
    def wheelEvent(self, event):
        # only zoom when no mouse buttons are pressed, to prevent interference with other user interactions
        if event.buttons() == Qt.NoButton:
            d = float(event.delta()) / 200.0 * self._radius
            self.translate([0.0, 0.0, d])
            self.updateGL()
            event.accept()

    def mousePressEvent(self, event):
        self._last_point_2d = event.pos()
        self._last_point_3d_ok, self._last_point_3d = self._map_to_sphere(
            self._last_point_2d)

    def mouseMoveEvent(self, event):
        new_point_2d = event.pos()

        if not self.rect().contains(new_point_2d):
            return

        new_point_3d_ok, new_point_3d = self._map_to_sphere(new_point_2d)

        dy = float(new_point_2d.y() - self._last_point_2d.y())
        h = float(self.height())

        # left button: rotate around center
        if event.buttons() == Qt.LeftButton and event.modifiers(
        ) == Qt.NoModifier:
            if self._last_point_3d_ok and new_point_3d_ok:
                cos_angle = numpy.dot(self._last_point_3d, new_point_3d)
                if abs(cos_angle) < 1.0:
                    axis = numpy.cross(self._last_point_3d, new_point_3d)
                    angle = 2.0 * math.acos(cos_angle) * 180.0 / math.pi
                    self.rotate(axis, angle)

        # middle button (or left + shift): move in x-y-direction
        elif event.buttons() == Qt.MidButton or (
                event.buttons() == Qt.LeftButton
                and event.modifiers() == Qt.ShiftModifier):
            dx = float(new_point_2d.x() - self._last_point_2d.x())
            w = float(self.width())
            z = -self._modelview_matrix[3][2] / self._modelview_matrix[3][3]
            n = 0.01 * self._radius
            up = math.tan(self._fovy / 2.0 * math.pi / 180.0) * n
            right = up * w / h
            self.translate([
                2.0 * dx / w * right / n * z, -2.0 * dy / h * up / n * z, 0.0
            ])

        # left and middle button (or left + ctrl): move in z-direction
        elif event.buttons() == (Qt.LeftButton | Qt.MidButton) or (
                event.buttons() == Qt.LeftButton
                and event.modifiers() == Qt.ControlModifier):
            delta_z = self._radius * dy * 2.0 / h
            self.translate([0.0, 0.0, delta_z])

        # remember the new points and flag
        self._last_point_2d = new_point_2d
        self._last_point_3d = new_point_3d
        self._last_point_3d_ok = new_point_3d_ok

        # trigger redraw
        self.updateGL()

    def mouseReleaseEvent(self, _event):
        self._last_point_3d_ok = False

    def _map_to_sphere(self, pos):
        v = [0.0, 0.0, 0.0]
        # check if inside widget
        if self.rect().contains(pos):
            # map widget coordinates to the centered unit square [-0.5..0.5] x [-0.5..0.5]
            v[0] = float(pos.x() - 0.5 * self.width()) / self.width()
            v[1] = float(0.5 * self.height() - pos.y()) / self.height()
            # use Pythagoras to compute z (the sphere has radius sqrt(2.0*0.5*0.5))
            v[2] = math.sqrt(max(0.5 - v[0] * v[0] - v[1] * v[1], 0.0))
            # normalize direction to unit sphere
            v = numpy.array(v) / numpy.linalg.norm(v)
            return True, v
        else:
            return False, v

    def get_texture(self, qimage):
        return self.bindTexture(qimage)
    def __init__(self,
                 items=list(),
                 buttons=QDialogButtonBox.Cancel | QDialogButtonBox.Ok,
                 exclusive=False,
                 preselect_all=False,
                 title='',
                 description='',
                 icon='',
                 parent=None,
                 select_if_single=True,
                 checkitem1='',
                 checkitem2='',
                 closein=0,
                 store_geometry=''):
        '''
        Creates an input dialog.
        @param items: a list with strings
        @type items: C{list()}
        '''
        QDialog.__init__(self, parent=parent)
        self.setObjectName(' - '.join(['SelectDialog', utf8(items)]))

        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(3, 3, 3, 3)

        # add filter row
        self.filter_field = EnhancedLineEdit(self)
        self.filter_field.setPlaceholderText("filter")
        self.filter_field.textChanged.connect(self._on_filter_changed)
        self.verticalLayout.addWidget(self.filter_field)

        if description:
            self.description_frame = QFrame(self)
            descriptionLayout = QHBoxLayout(self.description_frame)
            #      descriptionLayout.setContentsMargins(1, 1, 1, 1)
            if icon:
                self.icon_label = QLabel(self.description_frame)
                self.icon_label.setSizePolicy(QSizePolicy.Fixed,
                                              QSizePolicy.Fixed)
                self.icon_label.setPixmap(
                    QPixmap(icon).scaled(30, 30, Qt.KeepAspectRatio))
                descriptionLayout.addWidget(self.icon_label)
            self.description_label = QLabel(self.description_frame)
            self.description_label.setWordWrap(True)
            self.description_label.setText(description)
            descriptionLayout.addWidget(self.description_label)
            self.verticalLayout.addWidget(self.description_frame)

        # create area for the parameter
        self.content = MainBox(self)
        if items:
            self.scroll_area = QScrollArea(self)
            self.scroll_area.setFocusPolicy(Qt.NoFocus)
            self.scroll_area.setObjectName("scroll_area")
            self.scroll_area.setWidgetResizable(True)
            self.scroll_area.setWidget(self.content)
            self.verticalLayout.addWidget(self.scroll_area)

        self.checkitem1 = checkitem1
        self.checkitem1_result = False
        self.checkitem2 = checkitem2
        self.checkitem2_result = False

        # add select all option
        if not exclusive and items:
            self._ignore_next_toggle = False
            self.select_all_checkbox = QCheckBox('all entries')
            self.select_all_checkbox.setTristate(True)
            self.select_all_checkbox.stateChanged.connect(
                self._on_select_all_checkbox_stateChanged)
            self.verticalLayout.addWidget(self.select_all_checkbox)
            self.content.toggled.connect(self._on_main_toggle)
        if self.checkitem1:
            self.checkitem1_checkbox = QCheckBox(self.checkitem1)
            self.checkitem1_checkbox.stateChanged.connect(
                self._on_select_checkitem1_checkbox_stateChanged)
            self.verticalLayout.addWidget(self.checkitem1_checkbox)
        if self.checkitem2:
            self.checkitem2_checkbox = QCheckBox(self.checkitem2)
            self.checkitem2_checkbox.stateChanged.connect(
                self._on_select_checkitem2_checkbox_stateChanged)
            self.verticalLayout.addWidget(self.checkitem2_checkbox)
        if not items:
            spacerItem = QSpacerItem(1, 1, QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)
            self.verticalLayout.addItem(spacerItem)

        self._close_timer = None
        self._closein = closein - 1
        if closein > 0:
            self.closein_label = QLabel("OK in %d sec..." % closein)
            self.closein_label.setAlignment(Qt.AlignRight)
            self.verticalLayout.addWidget(self.closein_label)
            self._close_timer = threading.Timer(1.0, self._on_close_timer)
            self._close_timer.start()

        # create buttons
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setObjectName("buttonBox")
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(buttons)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self.verticalLayout.addWidget(self.buttonBox)

        # set the input fields
        if items:
            self.content.createFieldsFromValues(items, exclusive)
            if (select_if_single and len(items) == 1) or preselect_all:
                self.select_all_checkbox.setCheckState(Qt.Checked)

        if not items or len(items) < 7:
            self.filter_field.setVisible(False)

        # restore from configuration file
        self._geometry_name = store_geometry
        if store_geometry and nm.settings().store_geometry:
            settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE)
            settings.beginGroup(store_geometry)
            self.resize(settings.value("size", QSize(480, 320)))
            pos = settings.value("pos", QPoint(0, 0))
            if pos.x() != 0 and pos.y() != 0:
                self.move(pos)
            settings.endGroup()
Exemple #22
0
class my_widget(QWidget):

    mouse_is_pressed = False
    cursor_pos = QPoint(0,0)
    cur_nradius = 0 #normalized radius
    cur_sector = 0

    def __init__  (self,*args):
        QWidget. __init__ (self,*args)

    def paintEvent(self,event=None):
	#global mouse_is_pressed

	w_height = self.frameGeometry().height()
	w_width = self.frameGeometry().width()
	if w_height > w_width:
	    diam = w_width
	else:
	    diam = w_height
	radius = int(diam / 2) - 10

	start_x = w_width/2 - radius
	start_y = w_height/2 - radius

	line1_x1 = w_width/2 + radius*0.707
	line1_x2 = w_width/2 - radius*0.707
	line1_y1 = w_height/2 - radius*0.707
	line1_y2 = w_height/2 + radius*0.707
	line2_x1 = w_width/2 - radius*0.707
	line2_x2 = w_width/2 + radius*0.707
	line2_y1 = line1_y1
	line2_y2 = line1_y2

        painter=QPainter()
        painter.begin(self)
        painter.setPen(QPen(Qt.black))
        painter.drawLine(line1_x1, line1_y1, line1_x2, line1_y2) #cross
	painter.drawLine(line2_x1, line2_y1, line2_x2, line2_y2)
	painter.drawEllipse(start_x,start_y , (radius*2), (radius*2))

	if self.mouse_is_pressed == True:
	    painter.setBrush(Qt.blue)
	    rad = self.get_radius(w_width, w_height, self.cursor_pos)
            pos = self.get_pos(w_width, w_height, self.cursor_pos)
	    if (radius < rad):
	        rad = radius
	    self.draw_arc(painter, w_width, w_height, pos, rad)
	    self.cur_nradius = rad*1.0/radius
	    self.cur_sector = pos
	else:
	    self.cur_nradius = 0.0
	    self.cur_sector = 0.0
        painter.end()
	#print self.mouse_is_pressed
        pass

    #get direction of mouse point
    def get_pos(self, w_width, w_height, cursor_pos):
	center_x = w_width/2
	center_y = w_height/2
	angle = math.atan2((center_x - cursor_pos.x()),(center_y - cursor_pos.y()))*180.0/math.pi
	angle = angle - 45
	if (angle<0):
		angle = 360+angle
	pos = int(angle/90) + 1
	if (pos>3):
		pos = 0
	return pos

    #get radius of mouse point
    def get_radius(self, w_width, w_height, cursor_pos):
	center_x = w_width/2
	center_y = w_height/2
	tmp_val = math.pow((center_x - cursor_pos.x()),2) + math.pow((center_y - cursor_pos.y()),2)
	rad = int(math.sqrt(tmp_val))
	return rad

    def draw_arc(self,qp, w_width, w_height, pos, rad):
	start_x = w_width/2 - rad
	start_y = w_height/2 - rad
	rectangle = QRect(start_x,start_y,(rad*2),(rad*2))
	qp.drawPie(rectangle, (45+90*pos)*16, 90*16)
	

    def mousePressEvent(self, QMouseEvent):
	self.cursor_pos = QMouseEvent.pos()
	self.mouse_is_pressed = True
	my_widget.update(self)

    def mouseMoveEvent(self, QMouseEvent):
	self.cursor_pos = QMouseEvent.pos()
	self.mouse_is_pressed = True

    def mouseReleaseEvent(self, QMouseEvent):
	self.cursor_pos = QMouseEvent.pos()
	self.mouse_is_pressed = False
	my_widget.update(self)
Exemple #23
0
class DetachableTabBar(QTabBar):
    '''
    The TabBar class re-implements some of the functionality of the QTabBar widget
    '''
    detach_tab_signal = Signal(int, QPoint, bool)  # tab at pos, mouse cursor position, by double click
    move_tab_signal = Signal(int, int)
    empty_signal = Signal()

    def __init__(self, parent=None):
        QTabBar.__init__(self, parent)

        self.setAcceptDrops(True)
        self.setElideMode(Qt.ElideRight)
        self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)

        self.drag_start_pos = QPoint()
        self.drag_droped_pos = QPoint()
        self.mouse_cursor = QCursor()
        self.drag_initiated = False

    def mouseDoubleClickEvent(self, event):
        '''
        Send the detach_tab_signal when a tab is double clicked
        '''
        event.accept()
        self.detach_tab_signal.emit(self.tabAt(event.pos()), self.mouse_cursor.pos(), True)

    def mousePressEvent(self, event):
        '''
        Set the starting position for a drag event when the mouse button is pressed
        '''
        self.drag_droped_pos = QPoint(0, 0)
        self.drag_initiated = False
        if event.button() == Qt.LeftButton:
            self.drag_start_pos = event.pos()
        QTabBar.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        '''
        Determine if the current movement is a drag.  If it is, convert it into a QDrag.  If the
        drag ends inside the tab bar, emit an move_tab_signal.  If the drag ends outside the tab
        bar, emit an detach_tab_signal.
        '''
        # Determine if the current movement is detected as a drag
        if not self.drag_start_pos.isNull() and ((event.pos() - self.drag_start_pos).manhattanLength() > QApplication.startDragDistance()):
            self.drag_initiated = True

        # If the current movement is a drag initiated by the left button
        if ((event.buttons() & Qt.LeftButton)) and self.drag_initiated:

            # Stop the move event
            finishMoveEvent = QMouseEvent(QEvent.MouseMove, event.pos(), Qt.NoButton, Qt.NoButton, Qt.NoModifier)
            QTabBar.mouseMoveEvent(self, finishMoveEvent)

            # Convert the move event into a drag
            drag = QDrag(self)
            mime_data = QMimeData()
            mime_data.setData('action', b'application/tab-detach')
            drag.setMimeData(mime_data)

            # Create the appearance of dragging the tab content
            # tab_index = self.tabAt(self.drag_start_pos)
            pixmap = self.parentWidget().grab()
            targetPixmap = QPixmap(pixmap.size())
            targetPixmap.fill(Qt.transparent)
            painter = QPainter(targetPixmap)
            painter.setOpacity(0.85)
            painter.drawPixmap(0, 0, pixmap)
            painter.end()
            drag.setPixmap(targetPixmap)

            # Initiate the drag
            dropAction = drag.exec_(Qt.MoveAction | Qt.CopyAction)

            # If the drag completed outside of the tab bar, detach the tab and move
            # the content to the current cursor position
            if dropAction == Qt.IgnoreAction:
                event.accept()
                self.detach_tab_signal.emit(self.tabAt(self.drag_start_pos), self.mouse_cursor.pos(), False)
            elif dropAction == Qt.MoveAction:
                # else if the drag completed inside the tab bar, move the selected tab to the new position
                if not self.drag_droped_pos.isNull():
                    self.move_tab_signal.emit(self.tabAt(self.drag_start_pos), self.tabAt(self.drag_droped_pos))
                else:
                    # else if the drag completed inside the tab bar new TabBar, move the selected tab to the new TabBar
                    self.detach_tab_signal.emit(self.tabAt(self.drag_start_pos), self.mouse_cursor.pos(), False)
                event.accept()
        else:
            QTabBar.mouseMoveEvent(self, event)

    def dragEnterEvent(self, event):
        '''
        Determine if the drag has entered a tab position from another tab position
        '''
        self.drag_droped_pos = QPoint(0, 0)
        mime_data = event.mimeData()
        formats = mime_data.formats()
        if 'action' in formats and mime_data.data('action') == 'application/tab-detach':
            event.acceptProposedAction()
        QTabBar.dragMoveEvent(self, event)

    def dropEvent(self, event):
        '''
        Get the position of the end of the drag
        '''
        self.drag_droped_pos = event.pos()
        mime_data = event.mimeData()
        formats = mime_data.formats()
        if 'action' in formats and mime_data.data('action') == 'application/tab-detach':
            event.acceptProposedAction()
        QTabBar.dropEvent(self, event)
    
    def prepared_for_drop(self):
        return not self.drag_droped_pos.isNull()

    def tabRemoved(self, index):
        QTabBar.tabRemoved(self, index)
        if self.count() == 0:
            self.empty_signal.emit()