Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@1571: #
andrej@1571: # Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
andrej@1571: #
andrej@1571: # See COPYING file for copyrights details.
andrej@1571: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
andrej@1571: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
andrej@1571: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Laurent@814: 
andrej@1853: 
andrej@1853: from __future__ import absolute_import
andrej@2437: from __future__ import division
Laurent@814: 
Laurent@814: import wx
Laurent@814: 
andrej@1853: from controls.DebugVariablePanel.DebugVariableViewer import DebugVariableViewer
andrej@1853: from controls.DebugVariablePanel.GraphButton import GraphButton
Laurent@1199: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@1200: #                     Debug Variable Text Viewer Drop Target
andrej@1782: # -------------------------------------------------------------------------------
Laurent@1200: 
Laurent@1200: 
Laurent@1200: class DebugVariableTextDropTarget(wx.TextDropTarget):
andrej@1736:     """
andrej@1736:     Class that implements a custom drop target class for Debug Variable Text Viewer
andrej@1736:     """
andrej@1730: 
Laurent@1200:     def __init__(self, parent, window):
Laurent@1200:         """
Laurent@1200:         Constructor
Laurent@1200:         @param parent: Reference to Debug Variable Text Viewer
Laurent@1200:         @param window: Reference to the Debug Variable Panel
Laurent@1200:         """
Laurent@814:         wx.TextDropTarget.__init__(self)
Laurent@1200:         self.ParentControl = parent
Laurent@1200:         self.ParentWindow = window
andrej@1730: 
Laurent@916:     def __del__(self):
Laurent@1200:         """
Laurent@1200:         Destructor
Laurent@1200:         """
Laurent@1200:         # Remove reference to Debug Variable Text Viewer and Debug Variable
Laurent@1200:         # Panel
Laurent@1200:         self.ParentControl = None
Laurent@916:         self.ParentWindow = None
andrej@1730: 
Laurent@928:     def OnDragOver(self, x, y, d):
Laurent@1200:         """
Laurent@1200:         Function called when mouse is dragged over Drop Target
Laurent@1200:         @param x: X coordinate of mouse pointer
Laurent@1200:         @param y: Y coordinate of mouse pointer
Laurent@1200:         @param d: Suggested default for return value
Laurent@1200:         """
Laurent@1200:         # Signal parent that mouse is dragged over
Laurent@1200:         self.ParentControl.OnMouseDragging(x, y)
andrej@1730: 
Laurent@928:         return wx.TextDropTarget.OnDragOver(self, x, y, d)
andrej@1730: 
Laurent@814:     def OnDropText(self, x, y, data):
Laurent@1200:         """
Laurent@1215:         Function called when mouse is released in Drop Target
Laurent@1200:         @param x: X coordinate of mouse pointer
Laurent@1200:         @param y: Y coordinate of mouse pointer
Laurent@1200:         @param data: Text associated to drag'n drop
Laurent@1200:         """
Laurent@1218:         # Signal Debug Variable Panel to reset highlight
Laurent@1218:         self.ParentWindow.ResetHighlight()
andrej@1730: 
Laurent@814:         message = None
andrej@1730: 
Laurent@1200:         # Check that data is valid regarding DebugVariablePanel
Laurent@814:         try:
Laurent@814:             values = eval(data)
andrej@2450:             if not isinstance(values, tuple):
Laurent@1207:                 raise ValueError
andrej@1780:         except Exception:
Laurent@1200:             message = _("Invalid value \"%s\" for debug variable") % data
Laurent@814:             values = None
andrej@1730: 
Laurent@1200:         # Display message if data is invalid
Laurent@814:         if message is not None:
Laurent@814:             wx.CallAfter(self.ShowMessage, message)
andrej@1730: 
Laurent@1200:         # Data contain a reference to a variable to debug
Laurent@1200:         elif values[1] == "debug":
andrej@1730: 
Laurent@1200:             # Get Before which Viewer the variable has to be moved or added
Laurent@1200:             # according to the position of mouse in Viewer.
andrej@1847:             _width, height = self.ParentControl.GetSize()
Laurent@1200:             target_idx = self.ParentControl.GetIndex()
andrej@2437:             if y > height // 2:
Laurent@1200:                 target_idx += 1
andrej@1730: 
Laurent@1200:             # Drag'n Drop is an internal is an internal move inside Debug
andrej@1730:             # Variable Panel
Laurent@1200:             if len(values) > 2 and values[2] == "move":
andrej@1730:                 self.ParentWindow.MoveValue(values[0],
Laurent@1200:                                             target_idx)
andrej@1730: 
Laurent@1200:             # Drag'n Drop was initiated by another control of Beremiz
Laurent@945:             else:
andrej@1730:                 self.ParentWindow.InsertValue(values[0],
andrej@1730:                                               target_idx,
Laurent@1200:                                               force=True)
andrej@1730: 
Laurent@928:     def OnLeave(self):
Laurent@1200:         """
Laurent@1200:         Function called when mouse is leave Drop Target
Laurent@1200:         """
Laurent@1200:         # Signal Debug Variable Panel to reset highlight
Laurent@1198:         self.ParentWindow.ResetHighlight()
andrej@1730: 
Laurent@928:         return wx.TextDropTarget.OnLeave(self)
andrej@1730: 
Laurent@814:     def ShowMessage(self, message):
Laurent@1200:         """
Laurent@1200:         Show error message in Error Dialog
Laurent@1200:         @param message: Error message to display
Laurent@1200:         """
andrej@1730:         dialog = wx.MessageDialog(self.ParentWindow,
andrej@1730:                                   message,
andrej@1730:                                   _("Error"),
andrej@1745:                                   wx.OK | wx.ICON_ERROR)
Laurent@814:         dialog.ShowModal()
Laurent@814:         dialog.Destroy()
Laurent@814: 
Laurent@1200: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@1200: #                      Debug Variable Text Viewer Class
andrej@1782: # -------------------------------------------------------------------------------
Laurent@1200: 
Laurent@1200: class DebugVariableTextViewer(DebugVariableViewer, wx.Panel):
andrej@1736:     """
andrej@1736:     Class that implements a Viewer that display variable values as a text
andrej@1736:     """
andrej@1730: 
andrej@1852:     def __init__(self, parent, window, items=None):
Laurent@1200:         """
Laurent@1200:         Constructor
Laurent@1200:         @param parent: Parent wx.Window of DebugVariableText
Laurent@1200:         @param window: Reference to the Debug Variable Panel
Laurent@1200:         @param items: List of DebugVariableItem displayed by Viewer
Laurent@1200:         """
Laurent@1198:         DebugVariableViewer.__init__(self, window, items)
andrej@1730: 
Laurent@1198:         wx.Panel.__init__(self, parent)
Laurent@1200:         # Set panel background colour
Laurent@1198:         self.SetBackgroundColour(wx.WHITE)
Laurent@1200:         # Define panel drop target
Laurent@1200:         self.SetDropTarget(DebugVariableTextDropTarget(self, window))
andrej@1730: 
Laurent@1200:         # Bind events
Laurent@1198:         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
Laurent@1198:         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
Laurent@1214:         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
Laurent@1198:         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
Laurent@1198:         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
Laurent@1198:         self.Bind(wx.EVT_SIZE, self.OnResize)
Laurent@1198:         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
Laurent@1198:         self.Bind(wx.EVT_PAINT, self.OnPaint)
andrej@1730: 
Laurent@1200:         # Define panel min size for parent sizer layout
Laurent@1198:         self.SetMinSize(wx.Size(0, 25))
andrej@1730: 
Laurent@1200:         # Add buttons to Viewer
Laurent@1199:         for bitmap, callback in [("force", self.OnForceButton),
Laurent@1199:                                  ("release", self.OnReleaseButton),
Laurent@1199:                                  ("delete_graph", self.OnCloseButton)]:
Laurent@1199:             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
andrej@1730: 
Laurent@1198:     def RefreshViewer(self):
andrej@2509:         """Triggers EVT_PAINT event to refresh UI"""
andrej@2509:         self.Refresh()
andrej@2509: 
andrej@2509:     def DrawViewer(self):
andrej@2509:         """
andrej@2509:         Redraw content displayed by Viewer
Laurent@1200:         """
Edouard@2592: 
Laurent@1200:         # Create buffered DC for drawing in panel
Laurent@1198:         width, height = self.GetSize()
edouard@3303:         bitmap = wx.Bitmap(width, height)
Edouard@2592:         dc = wx.BufferedDC(wx.PaintDC(self), bitmap)
Laurent@1198:         dc.Clear()
andrej@1730: 
Laurent@1200:         # Get Graphics Context for DC, for anti-aliased and transparent
Laurent@1200:         # rendering
Laurent@1198:         gc = wx.GCDC(dc)
andrej@1730: 
Laurent@1200:         # Get first item
Laurent@1200:         item = self.ItemsDict.values()[0]
andrej@1730: 
Laurent@1200:         # Get item variable path masked according Debug Variable Panel mask
Laurent@1200:         item_path = item.GetVariable(
andrej@1878:             self.ParentWindow.GetVariableNameMask())
andrej@1730: 
Laurent@1200:         # Draw item variable path at Viewer left side
Laurent@1200:         w, h = gc.GetTextExtent(item_path)
andrej@2437:         gc.DrawText(item_path, 20, (height - h) // 2)
andrej@1730: 
Laurent@1200:         # Update 'Release' button state and text color according to item forced
Laurent@1200:         # flag value
Laurent@1200:         item_forced = item.IsForced()
Laurent@1200:         self.Buttons[1].Enable(item_forced)
Laurent@1200:         self.RefreshButtonsPosition()
Laurent@1200:         if item_forced:
Laurent@1198:             gc.SetTextForeground(wx.BLUE)
andrej@1730: 
Laurent@1200:         # Draw item current value at right side of Viewer
Laurent@1200:         item_value = item.GetValue()
Laurent@1198:         w, h = gc.GetTextExtent(item_value)
andrej@2437:         gc.DrawText(item_value, width - 40 - w, (height - h) // 2)
andrej@1730: 
Laurent@1200:         # Draw other Viewer common elements
Laurent@1198:         self.DrawCommonElements(gc)
andrej@1730: 
Laurent@1198:     def OnLeftDown(self, event):
Laurent@1200:         """
Laurent@1200:         Function called when mouse left button is pressed
Laurent@1200:         @param event: wx.MouseEvent
Laurent@1200:         """
Laurent@1200:         # Get first item
Laurent@1200:         item = self.ItemsDict.values()[0]
andrej@1730: 
Laurent@1200:         # Calculate item path bounding box
andrej@1847:         _width, height = self.GetSize()
Laurent@1200:         item_path = item.GetVariable(
andrej@1878:             self.ParentWindow.GetVariableNameMask())
Laurent@1200:         w, h = self.GetTextExtent(item_path)
andrej@1730: 
Laurent@1200:         # Test if mouse has been pressed in this bounding box. In that case
andrej@1730:         # start a move drag'n drop of item variable
Laurent@1198:         x, y = event.GetPosition()
Laurent@1200:         item_path_bbox = wx.Rect(20, (height - h) / 2, w, h)
edouard@3303:         if item_path_bbox.Contains(x, y):
Laurent@1214:             self.ShowButtons(False)
Laurent@1200:             data = wx.TextDataObject(str((item.GetVariable(), "debug", "move")))
Laurent@1198:             dragSource = wx.DropSource(self)
Laurent@1198:             dragSource.SetData(data)
Laurent@1198:             dragSource.DoDragDrop()
andrej@1730: 
Laurent@1200:         # In other case handle event normally
Laurent@1198:         else:
Laurent@1198:             event.Skip()
andrej@1730: 
Laurent@1198:     def OnLeftUp(self, event):
Laurent@1200:         """
Laurent@1200:         Function called when mouse left button is released
Laurent@1200:         @param event: wx.MouseEvent
Laurent@1200:         """
Laurent@1200:         # Execute callback on button under mouse pointer if it exists
Laurent@1198:         x, y = event.GetPosition()
Edouard@2742:         self.HandleButton(x, y)
Laurent@1198:         event.Skip()
andrej@1730: 
Laurent@1214:     def OnLeftDClick(self, event):
Laurent@1214:         """
Laurent@1214:         Function called when mouse left button is double clicked
Laurent@1214:         @param event: wx.MouseEvent
Laurent@1214:         """
Laurent@1214:         # Only numeric variables can be toggled to graph canvas
Laurent@1214:         if self.ItemsDict.values()[0].IsNumVariable():
Laurent@1214:             self.ParentWindow.ToggleViewerType(self)
andrej@1730: 
Laurent@1198:     def OnPaint(self, event):
Laurent@1200:         """
Laurent@1200:         Function called when redrawing Viewer content is needed
Laurent@1200:         @param event: wx.PaintEvent
Laurent@1200:         """
andrej@2509:         self.DrawViewer()
Laurent@1198:         event.Skip()