SVGHMI: Fixed typo on session manager unregister, leading to wrong count of sessions and then exceptions when creating more session than allowed in protocol options. Also added more safety check in protocol in case session would be missing.
#!/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) 2013: 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
from __future__ import division
from datetime import datetime
from time import time as gettime
from weakref import proxy
import numpy
import wx
from six.moves import xrange
from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
from runtime.loglevels import LogLevelsCount, LogLevels
from util.BitmapLibrary import GetBitmap
THUMB_SIZE_RATIO = 1. / 8.
def ArrowPoints(direction, width, height, xoffset, yoffset):
if direction == wx.TOP:
return [wx.Point(xoffset + 1, yoffset + height - 2),
wx.Point(xoffset + width // 2, yoffset + 1),
wx.Point(xoffset + width - 1, yoffset + height - 2)]
else:
return [wx.Point(xoffset + 1, yoffset - height + 1),
wx.Point(xoffset + width // 2, yoffset - 2),
wx.Point(xoffset + width - 1, yoffset - height + 1)]
class LogScrollBar(wx.Panel):
def __init__(self, parent, size):
wx.Panel.__init__(self, parent, size=size)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SIZE, self.OnResize)
self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
self.ThumbScrollingStartPos = None
def GetRangeRect(self):
width, height = self.GetClientSize()
return wx.Rect(0, width, width, height - 2 * width)
def GetThumbRect(self):
width, _height = self.GetClientSize()
range_rect = self.GetRangeRect()
thumb_size = range_rect.height * THUMB_SIZE_RATIO
thumb_range = range_rect.height - thumb_size
thumb_center_position = (thumb_size + (self.ThumbPosition + 1) * thumb_range) / 2.
thumb_start = int(thumb_center_position - thumb_size / 2.)
thumb_end = int(thumb_center_position + thumb_size / 2.)
return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
def RefreshThumbPosition(self, thumb_position=None):
if thumb_position is None:
thumb_position = self.ThumbPosition
if self.Parent.IsMessagePanelTop():
thumb_position = max(0., thumb_position)
if self.Parent.IsMessagePanelBottom():
thumb_position = min(0., thumb_position)
if thumb_position != self.ThumbPosition:
self.ThumbPosition = thumb_position
self.Parent.SetScrollSpeed(self.ThumbPosition)
self.Refresh()
def OnLeftDown(self, event):
self.CaptureMouse()
posx, posy = event.GetPosition()
width, height = self.GetClientSize()
range_rect = self.GetRangeRect()
thumb_rect = self.GetThumbRect()
if range_rect.InsideXY(posx, posy):
if thumb_rect.InsideXY(posx, posy):
self.ThumbScrollingStartPos = wx.Point(posx, posy)
elif posy < thumb_rect.y:
self.Parent.ScrollToLast()
elif posy > thumb_rect.y + thumb_rect.height:
self.Parent.ScrollToFirst()
elif posy < width:
self.Parent.ScrollMessagePanelByPage(1)
elif posy > height - width:
self.Parent.ScrollMessagePanelByPage(-1)
event.Skip()
def OnLeftUp(self, event):
self.ThumbScrollingStartPos = None
self.RefreshThumbPosition(0.)
if self.HasCapture():
self.ReleaseMouse()
event.Skip()
def OnMotion(self, event):
if event.Dragging() and self.ThumbScrollingStartPos is not None:
_posx, posy = event.GetPosition()
range_rect = self.GetRangeRect()
thumb_size = range_rect.height * THUMB_SIZE_RATIO
thumb_range = range_rect.height - thumb_size
self.RefreshThumbPosition(
max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. // thumb_range, 1.)))
event.Skip()
def OnResize(self, event):
self.Refresh()
event.Skip()
def OnEraseBackground(self, event):
pass
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self)
dc.Clear()
dc.BeginDrawing()
gc = wx.GCDC(dc)
width, height = self.GetClientSize()
gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
gc.SetBrush(wx.GREY_BRUSH)
gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) // 4 - 3))
gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) // 4 + 3))
gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) // 4 + 3))
gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) // 4 - 3))
thumb_rect = self.GetThumbRect()
exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
thumb_rect.width, thumb_rect.height)
if self.Parent.IsMessagePanelTop():
exclusion_rect.y, exclusion_rect.height = width, exclusion_rect.y + exclusion_rect.height - width
if self.Parent.IsMessagePanelBottom():
exclusion_rect.height = height - width - exclusion_rect.y
if exclusion_rect != thumb_rect:
colour = wx.NamedColour("LIGHT GREY")
gc.SetPen(wx.Pen(colour))
gc.SetBrush(wx.Brush(colour))
gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y,
exclusion_rect.width, exclusion_rect.height)
gc.SetPen(wx.GREY_PEN)
gc.SetBrush(wx.GREY_BRUSH)
gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
thumb_rect.width, thumb_rect.height)
dc.EndDrawing()
event.Skip()
BUTTON_SIZE = (70, 15)
class LogButton(object):
def __init__(self, label, callback):
self.Position = wx.Point(0, 0)
self.Size = wx.Size(*BUTTON_SIZE)
self.Label = label
self.Shown = True
self.Callback = callback
def __del__(self):
self.callback = None
def GetSize(self):
return self.Size
def SetPosition(self, x, y):
self.Position = wx.Point(x, y)
def HitTest(self, x, y):
rect = wx.Rect(self.Position.x, self.Position.y,
self.Size.width, self.Size.height)
if rect.InsideXY(x, y):
return True
return False
def ProcessCallback(self):
if self.Callback is not None:
wx.CallAfter(self.Callback)
def Draw(self, dc):
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
dc.DrawRectangle(self.Position.x, self.Position.y,
self.Size.width, self.Size.height)
w, h = dc.GetTextExtent(self.Label)
dc.DrawText(self.Label,
self.Position.x + (self.Size.width - w) // 2,
self.Position.y + (self.Size.height - h) // 2)
DATE_INFO_SIZE = 10
MESSAGE_INFO_SIZE = 18
class LogMessage(object):
def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
self.Date = datetime.utcfromtimestamp(tv_sec)
self.Seconds = self.Date.second + tv_nsec * 1e-9
self.Date = self.Date.replace(second=0)
self.Timestamp = tv_sec + tv_nsec * 1e-9
self.Level = level
self.LevelBitmap = level_bitmap
self.Message = msg
self.DrawDate = True
def __cmp__(self, other):
if self.Date == other.Date:
return cmp(self.Seconds, other.Seconds)
return cmp(self.Date, other.Date)
def GetFullText(self):
date = self.Date.replace(second=int(self.Seconds))
nsec = (self.Seconds % 1.) * 1e9
return "%s at %s.%9.9d:\n%s" % (
LogLevels[self.Level],
str(date), nsec,
self.Message)
def Draw(self, dc, offset, width, draw_date):
if draw_date:
datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
dw, dh = dc.GetTextExtent(datetime_text)
dc.DrawText(datetime_text, (width - dw) // 2, offset + (DATE_INFO_SIZE - dh) // 2)
offset += DATE_INFO_SIZE
seconds_text = "%12.9f" % self.Seconds
sw, sh = dc.GetTextExtent(seconds_text)
dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) // 2)
bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) // 2)
text = self.Message.replace("\n", " ")
_mw, mh = dc.GetTextExtent(text)
dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) // 2)
def GetHeight(self, draw_date):
if draw_date:
return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
return MESSAGE_INFO_SIZE
SECOND = 1
MINUTE = 60 * SECOND
HOUR = 60 * MINUTE
DAY = 24 * HOUR
CHANGE_TIMESTAMP_BUTTONS = [(_("1d"), DAY),
(_("1h"), HOUR),
(_("1m"), MINUTE),
(_("1s"), SECOND)]
class LogViewer(DebugViewer, wx.Panel):
def __init__(self, parent, window):
wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
DebugViewer.__init__(self, None, False, False)
main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
main_sizer.AddGrowableCol(0)
main_sizer.AddGrowableRow(1)
filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
self.MessageFilter.Append(_("All"))
levels = LogLevels[:3]
levels.reverse()
for level in levels:
self.MessageFilter.Append(_(level))
self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
self.SearchMessage.ShowSearchButton(True)
self.SearchMessage.ShowCancelButton(True)
self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
self.OnSearchMessageSearchButtonClick, self.SearchMessage)
self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN,
self.OnSearchMessageCancelButtonClick, self.SearchMessage)
filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"),
size=wx.Size(28, 28), style=wx.NO_BORDER)
self.CleanButton.SetToolTipString(_("Clean log messages"))
self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
filter_sizer.AddWindow(self.CleanButton)
message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
message_panel_sizer.AddGrowableCol(0)
message_panel_sizer.AddGrowableRow(0)
main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
self.MessagePanel = wx.Panel(self)
if wx.Platform == '__WXMSW__':
self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
else:
self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='FreeMono')
self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp)
self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
self.MessagePanel.Bind(wx.EVT_MOTION, self.OnMessagePanelMotion)
self.MessagePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnMessagePanelLeaveWindow)
self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
self.SetSizer(main_sizer)
self.LeftButtons = []
for label, callback in [("+" + text, self.GenerateOnDurationButton(duration))
for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
self.LeftButtons.append(LogButton(label, callback))
self.RightButtons = []
for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration))
for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
self.RightButtons.append(LogButton(label, callback))
self.MessageFilter.SetSelection(0)
self.LogSource = None
self.ResetLogMessages()
self.ParentWindow = window
self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
self.CurrentFilter = self.LevelFilters[0]
self.CurrentSearchValue = ""
self.ScrollSpeed = 0.
self.LastStartTime = None
self.ScrollTimer = wx.Timer(self, -1)
self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
self.LastMousePos = None
self.MessageToolTip = None
self.MessageToolTipTimer = wx.Timer(self, -1)
self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
def __del__(self):
self.ScrollTimer.Stop()
def ResetLogMessages(self):
self.ResetLogCounters()
self.OldestMessages = []
self.LogMessages = []
self.LogMessagesTimestamp = numpy.array([])
self.CurrentMessage = None
self.HasNewData = False
def SetLogSource(self, log_source):
self.LogSource = proxy(log_source) if log_source is not None else None
self.CleanButton.Enable(self.LogSource is not None)
if log_source is not None:
self.ResetLogMessages()
wx.CallAfter(self.RefreshView)
def GetLogMessageFromSource(self, msgidx, level):
if self.LogSource is not None:
answer = self.LogSource.GetLogMessage(level, msgidx)
if answer is not None:
msg, _tick, tv_sec, tv_nsec = answer
return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
return None
def ResetLogCounters(self):
self.previous_log_count = [None]*LogLevelsCount
def SetLogCounters(self, log_count):
new_messages = []
for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
if count is not None and prev != count:
if prev is None:
dump_end = max(-1, count - 10)
oldest_message = (-1, None)
else:
dump_end = prev - 1
for msgidx in xrange(count-1, dump_end, -1):
new_message = self.GetLogMessageFromSource(msgidx, level)
if new_message is None:
if prev is None:
oldest_message = (-1, None)
break
if prev is None:
oldest_message = (msgidx, new_message)
if len(new_messages) == 0:
new_messages = [new_message]
else:
new_messages.insert(0, new_message)
else:
new_messages.insert(0, new_message)
if prev is None and len(self.OldestMessages) <= level:
self.OldestMessages.append(oldest_message)
self.previous_log_count[level] = count
new_messages.sort()
if len(new_messages) > 0:
self.HasNewData = True
if self.CurrentMessage is not None:
current_is_last = self.GetNextMessage(self.CurrentMessage)[0] is None
else:
current_is_last = True
for new_message in new_messages:
self.LogMessages.append(new_message)
self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp])
if current_is_last:
self.ScrollToLast(False)
self.ResetMessageToolTip()
self.MessageToolTipTimer.Stop()
self.ParentWindow.SelectTab(self)
self.NewDataAvailable(None)
def FilterLogMessage(self, message, timestamp=None):
return (message.Level in self.CurrentFilter and
message.Message.find(self.CurrentSearchValue) != -1 and
(timestamp is None or message.Timestamp < timestamp))
def GetMessageByTimestamp(self, timestamp):
if self.CurrentMessage is not None:
msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
message = self.LogMessages[msgidx]
if self.FilterLogMessage(message) and message.Timestamp > timestamp:
return self.GetPreviousMessage(msgidx, timestamp)
return message, msgidx
return None, None
def GetNextMessage(self, msgidx):
while msgidx < len(self.LogMessages) - 1:
message = self.LogMessages[msgidx + 1]
if self.FilterLogMessage(message):
return message, msgidx + 1
msgidx += 1
return None, None
def GetPreviousMessage(self, msgidx, timestamp=None):
message = None
while 0 < msgidx < len(self.LogMessages):
message = self.LogMessages[msgidx - 1]
if self.FilterLogMessage(message, timestamp):
return message, msgidx - 1
msgidx -= 1
if len(self.LogMessages) > 0:
message = self.LogMessages[0]
for _idx, msg in self.OldestMessages:
if msg is not None and msg > message:
message = msg
while message is not None:
level = message.Level
oldest_msgidx, _oldest_message = self.OldestMessages[level]
if oldest_msgidx > 0:
message = self.GetLogMessageFromSource(oldest_msgidx - 1, level)
if message is not None:
self.OldestMessages[level] = (oldest_msgidx - 1, message)
else:
self.OldestMessages[level] = (-1, None)
else:
message = None
self.OldestMessages[level] = (-1, None)
if message is not None:
message_idx = 0
while (message_idx < len(self.LogMessages) and
self.LogMessages[message_idx] < message):
message_idx += 1
if len(self.LogMessages) > 0:
current_message = self.LogMessages[self.CurrentMessage]
else:
current_message = message
self.LogMessages.insert(message_idx, message)
self.LogMessagesTimestamp = numpy.insert(
self.LogMessagesTimestamp,
[message_idx],
[message.Timestamp])
self.CurrentMessage = self.LogMessages.index(current_message)
if message_idx == 0 and self.FilterLogMessage(message, timestamp):
return message, 0
for _idx, msg in self.OldestMessages:
if msg is not None and (message is None or msg > message):
message = msg
return None, None
def RefreshNewData(self, *args, **kwargs):
if self.HasNewData:
self.HasNewData = False
self.RefreshView()
DebugViewer.RefreshNewData(self, *args, **kwargs)
def RefreshView(self):
width, height = self.MessagePanel.GetClientSize()
bitmap = wx.EmptyBitmap(width, height)
dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
dc.Clear()
dc.BeginDrawing()
if self.CurrentMessage is not None:
dc.SetFont(self.Font)
for button in self.LeftButtons + self.RightButtons:
button.Draw(dc)
message_idx = self.CurrentMessage
message = self.LogMessages[message_idx]
draw_date = True
offset = 5
while offset < height and message is not None:
message.Draw(dc, offset, width, draw_date)
offset += message.GetHeight(draw_date)
previous_message, message_idx = self.GetPreviousMessage(message_idx)
if previous_message is not None:
draw_date = message.Date != previous_message.Date
message = previous_message
dc.EndDrawing()
self.MessageScrollBar.RefreshThumbPosition()
def IsPLCLogEmpty(self):
empty = True
for _level, prev in zip(xrange(LogLevelsCount), self.previous_log_count):
if prev is not None:
empty = False
break
return empty
def IsMessagePanelTop(self, message_idx=None):
if message_idx is None:
message_idx = self.CurrentMessage
if message_idx is not None:
return self.GetNextMessage(message_idx)[0] is None
return True
def IsMessagePanelBottom(self, message_idx=None):
if message_idx is None:
message_idx = self.CurrentMessage
if message_idx is not None:
_width, height = self.MessagePanel.GetClientSize()
offset = 5
message = self.LogMessages[message_idx]
draw_date = True
while message is not None and offset < height:
offset += message.GetHeight(draw_date)
previous_message, message_idx = self.GetPreviousMessage(message_idx)
if previous_message is not None:
draw_date = message.Date != previous_message.Date
message = previous_message
return offset < height
return True
def ScrollMessagePanel(self, scroll):
if self.CurrentMessage is not None:
message = self.LogMessages[self.CurrentMessage]
while scroll > 0 and message is not None:
message, msgidx = self.GetNextMessage(self.CurrentMessage)
if message is not None:
self.CurrentMessage = msgidx
scroll -= 1
while scroll < 0 and message is not None and not self.IsMessagePanelBottom():
message, msgidx = self.GetPreviousMessage(self.CurrentMessage)
if message is not None:
self.CurrentMessage = msgidx
scroll += 1
self.RefreshView()
def ScrollMessagePanelByPage(self, page):
if self.CurrentMessage is not None:
_width, height = self.MessagePanel.GetClientSize()
message_per_page = max(1, (height - DATE_INFO_SIZE) // MESSAGE_INFO_SIZE - 1)
self.ScrollMessagePanel(page * message_per_page)
def ScrollMessagePanelByTimestamp(self, seconds):
if self.CurrentMessage is not None:
current_message = self.LogMessages[self.CurrentMessage]
message, msgidx = self.GetMessageByTimestamp(current_message.Timestamp + seconds)
if message is None or self.IsMessagePanelBottom(msgidx):
self.ScrollToFirst()
else:
if seconds > 0 and self.CurrentMessage == msgidx and msgidx < len(self.LogMessages) - 1:
msgidx += 1
self.CurrentMessage = msgidx
self.RefreshView()
def ResetMessagePanel(self):
if len(self.LogMessages) > 0:
self.CurrentMessage = len(self.LogMessages) - 1
message = self.LogMessages[self.CurrentMessage]
while message is not None and not self.FilterLogMessage(message):
message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
self.RefreshView()
def OnMessageFilterChanged(self, event):
self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
self.ResetMessagePanel()
event.Skip()
def OnSearchMessageChanged(self, event):
self.CurrentSearchValue = self.SearchMessage.GetValue()
self.ResetMessagePanel()
event.Skip()
def OnSearchMessageSearchButtonClick(self, event):
self.CurrentSearchValue = self.SearchMessage.GetValue()
self.ResetMessagePanel()
event.Skip()
def OnSearchMessageCancelButtonClick(self, event):
self.CurrentSearchValue = ""
self.SearchMessage.SetValue("")
self.ResetMessagePanel()
event.Skip()
def OnCleanButton(self, event):
if self.LogSource is not None and not self.IsPLCLogEmpty():
self.LogSource.ResetLogCount()
self.ResetLogMessages()
self.RefreshView()
event.Skip()
def GenerateOnDurationButton(self, duration):
def OnDurationButton():
self.ScrollMessagePanelByTimestamp(duration)
return OnDurationButton
def GetCopyMessageToClipboardFunction(self, message):
def CopyMessageToClipboardFunction(event):
self.ParentWindow.SetCopyBuffer(message.GetFullText())
return CopyMessageToClipboardFunction
def GetMessageByScreenPos(self, posx, posy):
if self.CurrentMessage is not None:
_width, height = self.MessagePanel.GetClientSize()
message_idx = self.CurrentMessage
message = self.LogMessages[message_idx]
draw_date = True
offset = 5
while offset < height and message is not None:
if draw_date:
offset += DATE_INFO_SIZE
if offset <= posy < offset + MESSAGE_INFO_SIZE:
return message
offset += MESSAGE_INFO_SIZE
previous_message, message_idx = self.GetPreviousMessage(message_idx)
if previous_message is not None:
draw_date = message.Date != previous_message.Date
message = previous_message
return None
def OnMessagePanelLeftUp(self, event):
if self.CurrentMessage is not None:
posx, posy = event.GetPosition()
for button in self.LeftButtons + self.RightButtons:
if button.HitTest(posx, posy):
button.ProcessCallback()
break
event.Skip()
def OnMessagePanelRightUp(self, event):
message = self.GetMessageByScreenPos(*event.GetPosition())
if message is not None:
menu = wx.Menu(title='')
menu_entry = menu.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=_("Copy"))
self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), menu_entry)
self.MessagePanel.PopupMenu(menu)
menu.Destroy()
event.Skip()
def OnMessagePanelLeftDCLick(self, event):
message = self.GetMessageByScreenPos(*event.GetPosition())
if message is not None:
self.SearchMessage.SetFocus()
self.SearchMessage.SetValue(message.Message)
event.Skip()
def ResetMessageToolTip(self):
if self.MessageToolTip is not None:
self.MessageToolTip.Destroy()
self.MessageToolTip = None
def OnMessageToolTipTimer(self, event):
if self.LastMousePos is not None:
message = self.GetMessageByScreenPos(*self.LastMousePos)
if message is not None:
tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
tooltip_pos.x += 10
tooltip_pos.y += 10
self.MessageToolTip = CustomToolTip(self.MessagePanel, message.GetFullText(), False)
self.MessageToolTip.SetFont(self.Font)
self.MessageToolTip.SetToolTipPosition(tooltip_pos)
self.MessageToolTip.Show()
event.Skip()
def OnMessagePanelMotion(self, event):
if not event.Dragging():
self.ResetMessageToolTip()
self.LastMousePos = event.GetPosition()
self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
event.Skip()
def OnMessagePanelLeaveWindow(self, event):
self.ResetMessageToolTip()
self.LastMousePos = None
self.MessageToolTipTimer.Stop()
event.Skip()
def OnMessagePanelMouseWheel(self, event):
self.ScrollMessagePanel(event.GetWheelRotation() // event.GetWheelDelta())
event.Skip()
def OnMessagePanelEraseBackground(self, event):
pass
def OnMessagePanelPaint(self, event):
self.RefreshView()
event.Skip()
def OnMessagePanelResize(self, event):
width, _height = self.MessagePanel.GetClientSize()
offset = 2
for button in self.LeftButtons:
button.SetPosition(offset, 2)
w, _h = button.GetSize()
offset += w + 2
offset = width - 2
for button in self.RightButtons:
w, _h = button.GetSize()
button.SetPosition(offset - w, 2)
offset -= w + 2
if self.IsMessagePanelBottom():
self.ScrollToFirst()
else:
self.RefreshView()
event.Skip()
def OnScrollTimer(self, event):
if self.ScrollSpeed != 0.:
speed_norm = abs(self.ScrollSpeed)
period = REFRESH_PERIOD / speed_norm
self.ScrollMessagePanel(-speed_norm / self.ScrollSpeed)
self.LastStartTime = gettime()
self.ScrollTimer.Start(int(period * 1000), True)
event.Skip()
def SetScrollSpeed(self, speed):
if speed == 0.:
self.ScrollTimer.Stop()
else:
speed_norm = abs(speed)
period = REFRESH_PERIOD / speed_norm
current_time = gettime()
if self.LastStartTime is not None:
elapsed_time = current_time - self.LastStartTime
if elapsed_time > period:
self.ScrollMessagePanel(-speed_norm / speed)
self.LastStartTime = current_time
else:
period -= elapsed_time
else:
self.LastStartTime = current_time
self.ScrollTimer.Start(int(period * 1000), True)
self.ScrollSpeed = speed
def ScrollToLast(self, refresh=True):
if len(self.LogMessages) > 0:
self.CurrentMessage = len(self.LogMessages) - 1
message = self.LogMessages[self.CurrentMessage]
if not self.FilterLogMessage(message):
message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
if refresh:
self.RefreshView()
def ScrollToFirst(self):
if len(self.LogMessages) > 0:
message_idx = 0
message = self.LogMessages[message_idx]
if not self.FilterLogMessage(message):
next_message, msgidx = self.GetNextMessage(message_idx)
if next_message is not None:
message_idx = msgidx
message = next_message
while message is not None:
message, msgidx = self.GetPreviousMessage(message_idx)
if message is not None:
message_idx = msgidx
message = self.LogMessages[message_idx]
if self.FilterLogMessage(message):
while message is not None:
message, msgidx = self.GetNextMessage(message_idx)
if message is not None:
if not self.IsMessagePanelBottom(msgidx):
break
message_idx = msgidx
self.CurrentMessage = message_idx
else:
self.CurrentMessage = None
self.RefreshView()