graphics/RubberBand.py
author Edouard Tisserant <edouard.tisserant@gmail.com>
Mon, 27 Jun 2022 10:26:04 +0200
branchwxPython4
changeset 3526 074046800624
parent 3306 841fb2ee1213
child 3746 41be039fbb8c
permissions -rw-r--r--
Merge default in wxPython4 branch
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
# See COPYING file for copyrights details.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


from __future__ import absolute_import
import wx

from graphics.GraphicCommons import GetScaledEventPosition


# -------------------------------------------------------------------------------
#                               Viewer RubberBand
# -------------------------------------------------------------------------------


class RubberBand(object):
    """
    Class that implements a rubberband for graphic Viewers
    """

    def __init__(self, viewer):
        """
        Constructor
        @param viewer: Viewer on which rubberband must be drawn
        """
        self.Viewer = viewer

        # wx.Panel on which rubberband will be drawn
        self.DrawingSurface = viewer.Editor

        self.Reset()

    def Reset(self):
        """
        Initialize internal attributes of rubberband
        """
        self.StartPoint = None
        self.CurrentBBox = None
        self.LastBBox = None

    def IsShown(self):
        """
        Indicate if rubberband is drawn on viewer
        @return: True if rubberband is drawn
        """
        return self.CurrentBBox is not None

    def GetCurrentExtent(self):
        """
        Return the rubberband bounding box
        @return: Rubberband bounding box (wx.Rect object)
        """
        # In case of rubberband not shown, return the last rubberband
        # bounding box
        if self.IsShown():
            return self.CurrentBBox
        return self.LastBBox

    def OnLeftDown(self, event, dc, scaling):
        """
        Called when left mouse is pressed on Viewer. Starts to edit a new
        rubberband bounding box
        @param event: Mouse event
        @param dc: Device Context of Viewer
        @param scaling: PLCOpen scaling applied on Viewer
        """
        # Save the point where mouse was pressed in Viewer unit, position may
        # be modified by scroll and zoom applied on viewer
        self.StartPoint = GetScaledEventPosition(event, dc, scaling)

        # Initialize rubberband bounding box
        self.CurrentBBox = wx.Rect(self.StartPoint.x, self.StartPoint.y, 0, 0)

        # Change viewer mouse cursor to reflect a rubberband bounding box is
        # edited
        self.DrawingSurface.SetCursor(wx.Cursor(wx.CURSOR_CROSS))

        self.Redraw()

    def OnMotion(self, event, dc, scaling):
        """
        Called when mouse is dragging over Viewer. Update the current edited
        rubberband bounding box
        @param event: Mouse event
        @param dc: Device Context of Viewer
        @param scaling: PLCOpen scaling applied on Viewer
        """
        # Get mouse position in Viewer unit, position may be modified by scroll
        # and zoom applied on viewer
        pos = GetScaledEventPosition(event, dc, scaling)

        # Save the last bounding box drawn for erasing it later
        self.LastBBox = wx.Rect(0, 0, 0, 0)
        self.LastBBox.Union(self.CurrentBBox)

        # Calculate new position and size of the box
        self.CurrentBBox.x = min(pos.x, self.StartPoint.x)
        self.CurrentBBox.y = min(pos.y, self.StartPoint.y)
        self.CurrentBBox.width = abs(pos.x - self.StartPoint.x) + 1
        self.CurrentBBox.height = abs(pos.y - self.StartPoint.y) + 1

        self.Redraw()

    def OnLeftUp(self, event, dc, scaling):
        """
        Called when mouse is release from Viewer. Erase the current edited
        rubberband bounding box
        @param event: Mouse event
        @param dc: Device Context of Viewer
        @param scaling: PLCOpen scaling applied on Viewer
        """
        # Change viewer mouse cursor to default
        self.DrawingSurface.SetCursor(wx.NullCursor)

        # Save the last edited bounding box
        self.LastBBox = self.CurrentBBox
        self.CurrentBBox = None

        self.Redraw()

    def DrawBoundingBoxes(self, bboxes, dc=None):
        """
        Draw a list of bounding box on Viewer in the order given using XOR
        logical function
        @param bboxes: List of bounding boxes to draw on viewer
        @param dc: Device Context of Viewer (default None)
        """
        # Get viewer Device Context if not given
        if dc is None:
            dc = self.Viewer.GetLogicalDC()

        # Save current viewer scale factors before resetting them in order to
        # avoid rubberband pen to be scaled
        scalex, scaley = dc.GetUserScale()
        dc.SetUserScale(1, 1)

        # Set DC drawing style
        dc.SetPen(wx.Pen(wx.WHITE, style=wx.DOT))
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.SetLogicalFunction(wx.XOR)

        # Draw the bounding boxes using viewer scale factor
        for bbox in bboxes:
            if bbox is not None:
                dc.DrawRectangle(
                    bbox.x * scalex, bbox.y * scaley,
                    bbox.width * scalex, bbox.height * scaley)

        dc.SetLogicalFunction(wx.COPY)

        # Restore Viewer scale factor
        dc.SetUserScale(scalex, scaley)

    def Redraw(self, dc=None):
        """
        Redraw rubberband on Viewer
        @param dc: Device Context of Viewer (default None)
        """
        # Erase last bbox and draw current bbox
        self.DrawBoundingBoxes([self.LastBBox, self.CurrentBBox], dc)

    def Erase(self, dc=None):
        """
        Erase rubberband from Viewer
        @param dc: Device Context of Viewer (default None)
        """
        # Erase last bbox
        self.DrawBoundingBoxes([self.LastBBox], dc)

    def Draw(self, dc=None):
        """
        Draw rubberband on Viewer
        @param dc: Device Context of Viewer (default None)
        """
        # Erase last bbox and draw current bbox
        self.DrawBoundingBoxes([self.CurrentBBox], dc)


def PatchRubberBandForGTK3():
    """
    GTK3 implementation of DC doesn't support SetLogicalFuntion(XOR)
    Then Rubberband can't be erased by just redrawing it on the same place
    So this is a complete refresh instead, eating a lot of CPU.
    """
    def Redraw(self, dc=None):
        self.Viewer.Refresh()
        self.Draw()

    RubberBand.Redraw = Redraw

    def Erase(self, dc=None):
        self.Viewer.Refresh()

    RubberBand.Erase = Erase


if "gtk3" in wx.PlatformInfo:
    PatchRubberBandForGTK3()