controls/LogViewer.py
changeset 993 7fbde4a19ec3
parent 988 30e7571c10d0
child 997 d6da2ccafca4
equal deleted inserted replaced
992:72ee7f3e3cf3 993:7fbde4a19ec3
    26 from time import time as gettime
    26 from time import time as gettime
    27 import numpy
    27 import numpy
    28 
    28 
    29 import wx
    29 import wx
    30 
    30 
    31 from graphics import DebugViewer, REFRESH_PERIOD
    31 from graphics import DebugViewer, REFRESH_PERIOD, ToolTip, TOOLTIP_WAIT_PERIOD
    32 from targets.typemapping import LogLevelsCount, LogLevels
    32 from targets.typemapping import LogLevelsCount, LogLevels
    33 from util.BitmapLibrary import GetBitmap
    33 from util.BitmapLibrary import GetBitmap
    34 
    34 
    35 THUMB_SIZE_RATIO = 1. / 8.
    35 THUMB_SIZE_RATIO = 1. / 8.
    36 
    36 
    37 def ArrowPoints(direction, width, height, offset):
    37 def ArrowPoints(direction, width, height, xoffset, yoffset):
    38     if direction == wx.TOP:
    38     if direction == wx.TOP:
    39         return [wx.Point(1, offset + height - 2),
    39         return [wx.Point(xoffset + 1, yoffset + height - 2),
    40                 wx.Point(width / 2, offset + 1),
    40                 wx.Point(xoffset + width / 2, yoffset + 1),
    41                 wx.Point(width - 1, offset + height - 2)]
    41                 wx.Point(xoffset + width - 1, yoffset + height - 2)]
    42     else:
    42     else:
    43         return [wx.Point(1, offset - height + 1),
    43         return [wx.Point(xoffset + 1, yoffset - height + 1),
    44                 wx.Point(width / 2, offset - 2),
    44                 wx.Point(xoffset + width / 2, yoffset - 2),
    45                 wx.Point(width - 1, offset - height + 1)]
    45                 wx.Point(xoffset + width - 1, yoffset - height + 1)]
    46 
    46 
    47 class LogScrollBar(wx.Panel):
    47 class LogScrollBar(wx.Panel):
    48     
    48     
    49     def __init__(self, parent, size):
    49     def __init__(self, parent, size):
    50         wx.Panel.__init__(self, parent, size=size)
    50         wx.Panel.__init__(self, parent, size=size)
   135         
   135         
   136         gc = wx.GCDC(dc)
   136         gc = wx.GCDC(dc)
   137         
   137         
   138         width, height = self.GetClientSize()
   138         width, height = self.GetClientSize()
   139         
   139         
   140         gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 2))
   140         gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
   141         gc.SetBrush(wx.GREY_BRUSH)
   141         gc.SetBrush(wx.GREY_BRUSH)
   142         
   142         
   143         gc.DrawLines(ArrowPoints(wx.TOP, width, width * 0.75, 2 * width))
   143         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
   144         gc.DrawLines(ArrowPoints(wx.TOP, width, width * 0.75, 2 * width + 6))
   144         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
   145         
   145         
   146         gc.DrawLines(ArrowPoints(wx.BOTTOM, width, width * 0.75, height - 2 * width))
   146         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
   147         gc.DrawLines(ArrowPoints(wx.BOTTOM, width, width * 0.75, height - 2 * width - 6))
   147         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
   148         
   148         
   149         thumb_rect = self.GetThumbRect()
   149         thumb_rect = self.GetThumbRect()
   150         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
   150         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
   151                                  thumb_rect.width, thumb_rect.height)
   151                                  thumb_rect.width, thumb_rect.height)
   152         if self.Parent.IsMessagePanelTop():
   152         if self.Parent.IsMessagePanelTop():
   162                              exclusion_rect.width, exclusion_rect.height)
   162                              exclusion_rect.width, exclusion_rect.height)
   163         
   163         
   164         gc.SetPen(wx.GREY_PEN)
   164         gc.SetPen(wx.GREY_PEN)
   165         gc.SetBrush(wx.GREY_BRUSH)
   165         gc.SetBrush(wx.GREY_BRUSH)
   166         
   166         
   167         gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0))
   167         gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
   168         
   168         
   169         gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, height))
   169         gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
   170             
   170             
   171         gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
   171         gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
   172                          thumb_rect.width, thumb_rect.height)
   172                          thumb_rect.width, thumb_rect.height)
   173         
   173         
   174         dc.EndDrawing()
   174         dc.EndDrawing()
   179 class LogButton():
   179 class LogButton():
   180     
   180     
   181     def __init__(self, label, callback):
   181     def __init__(self, label, callback):
   182         self.Position = wx.Point(0, 0)
   182         self.Position = wx.Point(0, 0)
   183         self.Size = wx.Size(*BUTTON_SIZE)
   183         self.Size = wx.Size(*BUTTON_SIZE)
   184         if wx.Platform == '__WXMSW__':
       
   185             self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
       
   186         else:
       
   187             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
       
   188         self.Label = label
   184         self.Label = label
   189         self.Shown = True
   185         self.Shown = True
   190         self.Callback = callback
   186         self.Callback = callback
   191     
   187     
   192     def __del__(self):
   188     def __del__(self):
   214         dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
   210         dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
   215         
   211         
   216         dc.DrawRectangle(self.Position.x, self.Position.y, 
   212         dc.DrawRectangle(self.Position.x, self.Position.y, 
   217                          self.Size.width, self.Size.height)
   213                          self.Size.width, self.Size.height)
   218         
   214         
   219         dc.SetFont(self.Font)
       
   220         
       
   221         w, h = dc.GetTextExtent(self.Label)
   215         w, h = dc.GetTextExtent(self.Label)
   222         dc.DrawText(self.Label, 
   216         dc.DrawText(self.Label, 
   223             self.Position.x + (self.Size.width - w) / 2, 
   217             self.Position.x + (self.Size.width - w) / 2, 
   224             self.Position.y + (self.Size.height - h) / 2)
   218             self.Position.y + (self.Size.height - h) / 2)
   225 
   219 
   226 DATE_INFO_SIZE = 10
   220 DATE_INFO_SIZE = 10
   227 MESSAGE_INFO_SIZE = 30
   221 MESSAGE_INFO_SIZE = 25
   228 
   222 
   229 class LogMessage:
   223 class LogMessage:
   230     
   224     
   231     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
   225     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
   232         self.Date = datetime.fromtimestamp(tv_sec)
   226         self.Date = datetime.fromtimestamp(tv_sec)
   241     def __cmp__(self, other):
   235     def __cmp__(self, other):
   242         if self.Date == other.Date:
   236         if self.Date == other.Date:
   243             return cmp(self.Seconds, other.Seconds)
   237             return cmp(self.Seconds, other.Seconds)
   244         return cmp(self.Date, other.Date)
   238         return cmp(self.Date, other.Date)
   245     
   239     
       
   240     def GetFullText(self):
       
   241         date = self.Date.replace(second=int(self.Seconds))
       
   242         nsec = (self.Seconds % 1.) * 1e9
       
   243         return "%s at %s.%9.9d:\n%s" % (
       
   244             LogLevels[self.Level],
       
   245             str(date), nsec,
       
   246             self.Message)
       
   247     
   246     def Draw(self, dc, offset, width, draw_date):
   248     def Draw(self, dc, offset, width, draw_date):
   247         if draw_date:
   249         if draw_date:
   248             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
   250             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
   249             dw, dh = dc.GetTextExtent(datetime_text)
   251             dw, dh = dc.GetTextExtent(datetime_text)
   250             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
   252             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
   255         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
   257         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
   256         
   258         
   257         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
   259         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
   258         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
   260         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
   259         
   261         
   260         mw, mh = dc.GetTextExtent(self.Message)
   262         text = self.Message.replace("\n", " ")
   261         dc.DrawText(self.Message, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
   263         mw, mh = dc.GetTextExtent(text)
       
   264         dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
   262         
   265         
   263     def GetHeight(self, draw_date):
   266     def GetHeight(self, draw_date):
   264         if draw_date:
   267         if draw_date:
   265             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
   268             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
   266         return MESSAGE_INFO_SIZE
   269         return MESSAGE_INFO_SIZE
   312         message_panel_sizer.AddGrowableRow(0)
   315         message_panel_sizer.AddGrowableRow(0)
   313         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
   316         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
   314         
   317         
   315         self.MessagePanel = wx.Panel(self)
   318         self.MessagePanel = wx.Panel(self)
   316         if wx.Platform == '__WXMSW__':
   319         if wx.Platform == '__WXMSW__':
   317             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
   320             self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
   318         else:
   321         else:
   319             self.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
   322             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
   320         self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
   323         self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
       
   324         self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp)
   321         self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
   325         self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
       
   326         self.MessagePanel.Bind(wx.EVT_MOTION, self.OnMessagePanelMotion)
       
   327         self.MessagePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnMessagePanelLeaveWindow)
   322         self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
   328         self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
   323         self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
   329         self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
   324         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
   330         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
   325         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
   331         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
   326         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
   332         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
   352         
   358         
   353         self.ScrollSpeed = 0.
   359         self.ScrollSpeed = 0.
   354         self.LastStartTime = None
   360         self.LastStartTime = None
   355         self.ScrollTimer = wx.Timer(self, -1)
   361         self.ScrollTimer = wx.Timer(self, -1)
   356         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
   362         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
       
   363         
       
   364         self.LastMousePos = None
       
   365         self.MessageToolTip = None
       
   366         self.MessageToolTipTimer = wx.Timer(self, -1)
       
   367         self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
   357     
   368     
   358     def __del__(self):
   369     def __del__(self):
   359         self.ScrollTimer.Stop()
   370         self.ScrollTimer.Stop()
   360     
   371     
   361     def ResetLogMessages(self):
   372     def ResetLogMessages(self):
   409             for new_message in new_messages:
   420             for new_message in new_messages:
   410                 self.LogMessages.append(new_message)
   421                 self.LogMessages.append(new_message)
   411                 self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp])
   422                 self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp])
   412             if self.CurrentMessage is None or self.CurrentMessage == old_length - 1:
   423             if self.CurrentMessage is None or self.CurrentMessage == old_length - 1:
   413                 self.CurrentMessage = len(self.LogMessages) - 1
   424                 self.CurrentMessage = len(self.LogMessages) - 1
       
   425                 self.ResetMessageToolTip()
       
   426                 self.MessageToolTipTimer.Stop()
   414             self.NewDataAvailable(None)
   427             self.NewDataAvailable(None)
   415     
   428     
   416     def FilterLogMessage(self, message, timestamp=None):
   429     def FilterLogMessage(self, message, timestamp=None):
   417         return (message.Level in self.CurrentFilter and 
   430         return (message.Level in self.CurrentFilter and 
   418                 message.Message.find(self.CurrentSearchValue) != -1 and
   431                 message.Message.find(self.CurrentSearchValue) != -1 and
   483         dc.Clear()
   496         dc.Clear()
   484         dc.BeginDrawing()
   497         dc.BeginDrawing()
   485         
   498         
   486         if self.CurrentMessage is not None:
   499         if self.CurrentMessage is not None:
   487             
   500             
       
   501             dc.SetFont(self.Font)
       
   502             
   488             for button in self.LeftButtons + self.RightButtons:
   503             for button in self.LeftButtons + self.RightButtons:
   489                 button.Draw(dc)
   504                 button.Draw(dc)
   490             
       
   491             dc.SetFont(self.Font)
       
   492             
   505             
   493             message_idx = self.CurrentMessage
   506             message_idx = self.CurrentMessage
   494             message = self.LogMessages[message_idx]
   507             message = self.LogMessages[message_idx]
   495             draw_date = True
   508             draw_date = True
   496             offset = 5
   509             offset = 5
   594     def GenerateOnDurationButton(self, duration):
   607     def GenerateOnDurationButton(self, duration):
   595         def OnDurationButton():
   608         def OnDurationButton():
   596             self.ScrollMessagePanelByTimestamp(duration)
   609             self.ScrollMessagePanelByTimestamp(duration)
   597         return OnDurationButton
   610         return OnDurationButton
   598     
   611     
   599     def OnMessagePanelLeftUp(self, event):
   612     def GetCopyMessageToClipboardFunction(self, message):
       
   613         def CopyMessageToClipboardFunction(event):
       
   614             self.ParentWindow.SetCopyBuffer(message.GetFullText())
       
   615         return CopyMessageToClipboardFunction
       
   616     
       
   617     def GetMessageByScreenPos(self, posx, posy):
   600         if self.CurrentMessage is not None:
   618         if self.CurrentMessage is not None:
   601             posx, posy = event.GetPosition()
       
   602             for button in self.LeftButtons + self.RightButtons:
       
   603                 if button.HitTest(posx, posy):
       
   604                     button.ProcessCallback()
       
   605                     break
       
   606         event.Skip()
       
   607     
       
   608     def OnMessagePanelLeftDCLick(self, event):
       
   609         if self.CurrentMessage is not None:
       
   610             posx, posy = event.GetPosition()
       
   611             width, height = self.MessagePanel.GetClientSize()
   619             width, height = self.MessagePanel.GetClientSize()
   612             message_idx = self.CurrentMessage
   620             message_idx = self.CurrentMessage
   613             message = self.LogMessages[message_idx]
   621             message = self.LogMessages[message_idx]
   614             draw_date = True
   622             draw_date = True
   615             offset = 5
   623             offset = 5
   616             
   624             
   617             while offset < height and message is not None:
   625             while offset < height and message is not None:
   618                 if draw_date:
   626                 if draw_date:
   619                     offset += DATE_INFO_SIZE
   627                     offset += DATE_INFO_SIZE
   620 
   628     
   621                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
   629                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
   622                     self.CurrentSearchValue = message.Message
   630                     return message
   623                     self.SearchMessage.SetValue(message.Message)
   631         
   624                     self.ResetMessagePanel()
       
   625                     break
       
   626                 
       
   627                 offset += MESSAGE_INFO_SIZE
   632                 offset += MESSAGE_INFO_SIZE
   628                 
   633                 
   629                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   634                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   630                 if previous_message is not None:
   635                 if previous_message is not None:
   631                     draw_date = message.Date != previous_message.Date
   636                     draw_date = message.Date != previous_message.Date
   632                 message = previous_message
   637                 message = previous_message
       
   638         return None
       
   639     
       
   640     def OnMessagePanelLeftUp(self, event):
       
   641         if self.CurrentMessage is not None:
       
   642             posx, posy = event.GetPosition()
       
   643             for button in self.LeftButtons + self.RightButtons:
       
   644                 if button.HitTest(posx, posy):
       
   645                     button.ProcessCallback()
       
   646                     break
       
   647         event.Skip()
       
   648     
       
   649     def OnMessagePanelRightUp(self, event):
       
   650         message = self.GetMessageByScreenPos(*event.GetPosition())
       
   651         if message is not None:
       
   652             menu = wx.Menu(title='')
       
   653             
       
   654             new_id = wx.NewId()
       
   655             menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
       
   656             self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
       
   657     
       
   658             self.MessagePanel.PopupMenu(menu)
       
   659             menu.Destroy()
       
   660         event.Skip()
       
   661     
       
   662     def OnMessagePanelLeftDCLick(self, event):
       
   663         message = self.GetMessageByScreenPos(*event.GetPosition())
       
   664         if message is not None:
       
   665             self.SearchMessage.SetFocus()
       
   666             self.SearchMessage.SetValue(message.Message)
       
   667         event.Skip()
       
   668     
       
   669     def ResetMessageToolTip(self):
       
   670         if self.MessageToolTip is not None:
       
   671             self.MessageToolTip.Destroy()
       
   672             self.MessageToolTip = None
       
   673     
       
   674     def OnMessageToolTipTimer(self, event):
       
   675         if self.LastMousePos is not None:
       
   676             message = self.GetMessageByScreenPos(*self.LastMousePos)
       
   677             if message is not None:
       
   678                 tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
       
   679                 tooltip_pos.x += 10
       
   680                 tooltip_pos.y += 10
       
   681                 self.MessageToolTip = ToolTip(self.MessagePanel, message.GetFullText(), False)
       
   682                 self.MessageToolTip.SetFont(self.Font)
       
   683                 self.MessageToolTip.MoveToolTip(tooltip_pos)
       
   684                 self.MessageToolTip.Show()
       
   685         event.Skip()
       
   686     
       
   687     def OnMessagePanelMotion(self, event):
       
   688         if not event.Dragging():
       
   689             self.ResetMessageToolTip()
       
   690             self.LastMousePos = event.GetPosition()
       
   691             self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
       
   692         event.Skip()
       
   693         
       
   694     def OnMessagePanelLeaveWindow(self, event):
       
   695         self.ResetMessageToolTip()
       
   696         self.LastMousePos = None
       
   697         self.MessageToolTipTimer.Stop()
   633         event.Skip()
   698         event.Skip()
   634     
   699     
   635     def OnMessagePanelMouseWheel(self, event):
   700     def OnMessagePanelMouseWheel(self, event):
   636         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
   701         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
   637         event.Skip()
   702         event.Skip()