controls/LogViewer.py
changeset 1441 826730e60407
parent 1195 8f8d9859e9fc
child 1571 486f94a8032c
equal deleted inserted replaced
1440:e8daabf2c438 1441:826730e60407
     1 #!/usr/bin/env python
     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     2 # -*- coding: utf-8 -*-
     3 
     3 
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
     5 #based on the plcopen standard. 
     5 #based on the plcopen standard.
     6 #
     6 #
     7 #Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
     7 #Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
     8 #
     8 #
     9 #See COPYING file for copyrights details.
     9 #See COPYING file for copyrights details.
    10 #
    10 #
    30 
    30 
    31 from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
    31 from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
    32 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
    32 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
    33 from targets.typemapping import LogLevelsCount, LogLevels
    33 from targets.typemapping import LogLevelsCount, LogLevels
    34 from util.BitmapLibrary import GetBitmap
    34 from util.BitmapLibrary import GetBitmap
       
    35 from weakref import proxy
    35 
    36 
    36 THUMB_SIZE_RATIO = 1. / 8.
    37 THUMB_SIZE_RATIO = 1. / 8.
    37 
    38 
    38 def ArrowPoints(direction, width, height, xoffset, yoffset):
    39 def ArrowPoints(direction, width, height, xoffset, yoffset):
    39     if direction == wx.TOP:
    40     if direction == wx.TOP:
    44         return [wx.Point(xoffset + 1, yoffset - height + 1),
    45         return [wx.Point(xoffset + 1, yoffset - height + 1),
    45                 wx.Point(xoffset + width / 2, yoffset - 2),
    46                 wx.Point(xoffset + width / 2, yoffset - 2),
    46                 wx.Point(xoffset + width - 1, yoffset - height + 1)]
    47                 wx.Point(xoffset + width - 1, yoffset - height + 1)]
    47 
    48 
    48 class LogScrollBar(wx.Panel):
    49 class LogScrollBar(wx.Panel):
    49     
    50 
    50     def __init__(self, parent, size):
    51     def __init__(self, parent, size):
    51         wx.Panel.__init__(self, parent, size=size)
    52         wx.Panel.__init__(self, parent, size=size)
    52         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
    53         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
    53         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
    54         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
    54         self.Bind(wx.EVT_MOTION, self.OnMotion)
    55         self.Bind(wx.EVT_MOTION, self.OnMotion)
    55         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
    56         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
    56         self.Bind(wx.EVT_PAINT, self.OnPaint)
    57         self.Bind(wx.EVT_PAINT, self.OnPaint)
    57         self.Bind(wx.EVT_SIZE, self.OnResize)
    58         self.Bind(wx.EVT_SIZE, self.OnResize)
    58         
    59 
    59         self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
    60         self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
    60         self.ThumbScrollingStartPos = None
    61         self.ThumbScrollingStartPos = None
    61     
    62 
    62     def GetRangeRect(self):
    63     def GetRangeRect(self):
    63         width, height = self.GetClientSize()
    64         width, height = self.GetClientSize()
    64         return wx.Rect(0, width, width, height - 2 * width)
    65         return wx.Rect(0, width, width, height - 2 * width)
    65     
    66 
    66     def GetThumbRect(self):
    67     def GetThumbRect(self):
    67         width, height = self.GetClientSize()
    68         width, height = self.GetClientSize()
    68         range_rect = self.GetRangeRect()
    69         range_rect = self.GetRangeRect()
    69         thumb_size = range_rect.height * THUMB_SIZE_RATIO
    70         thumb_size = range_rect.height * THUMB_SIZE_RATIO
    70         thumb_range = range_rect.height - thumb_size
    71         thumb_range = range_rect.height - thumb_size
    71         thumb_center_position = (thumb_size + (self.ThumbPosition + 1) * thumb_range) / 2.
    72         thumb_center_position = (thumb_size + (self.ThumbPosition + 1) * thumb_range) / 2.
    72         thumb_start = int(thumb_center_position - thumb_size / 2.)
    73         thumb_start = int(thumb_center_position - thumb_size / 2.)
    73         thumb_end = int(thumb_center_position + thumb_size / 2.)
    74         thumb_end = int(thumb_center_position + thumb_size / 2.)
    74         return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
    75         return wx.Rect(0, range_rect.y + thumb_start, width, thumb_end - thumb_start)
    75     
    76 
    76     def RefreshThumbPosition(self, thumb_position=None):
    77     def RefreshThumbPosition(self, thumb_position=None):
    77         if thumb_position is None:
    78         if thumb_position is None:
    78             thumb_position = self.ThumbPosition
    79             thumb_position = self.ThumbPosition
    79         if self.Parent.IsMessagePanelTop():
    80         if self.Parent.IsMessagePanelTop():
    80             thumb_position = max(0., thumb_position)
    81             thumb_position = max(0., thumb_position)
    82             thumb_position = min(0., thumb_position)
    83             thumb_position = min(0., thumb_position)
    83         if thumb_position != self.ThumbPosition:
    84         if thumb_position != self.ThumbPosition:
    84             self.ThumbPosition = thumb_position
    85             self.ThumbPosition = thumb_position
    85             self.Parent.SetScrollSpeed(self.ThumbPosition)
    86             self.Parent.SetScrollSpeed(self.ThumbPosition)
    86         self.Refresh()
    87         self.Refresh()
    87     
    88 
    88     def OnLeftDown(self, event):
    89     def OnLeftDown(self, event):
    89         self.CaptureMouse()
    90         self.CaptureMouse()
    90         posx, posy = event.GetPosition()
    91         posx, posy = event.GetPosition()
    91         width, height = self.GetClientSize()
    92         width, height = self.GetClientSize()
    92         range_rect = self.GetRangeRect()
    93         range_rect = self.GetRangeRect()
   101         elif posy < width:
   102         elif posy < width:
   102             self.Parent.ScrollMessagePanelByPage(1)
   103             self.Parent.ScrollMessagePanelByPage(1)
   103         elif posy > height - width:
   104         elif posy > height - width:
   104             self.Parent.ScrollMessagePanelByPage(-1)
   105             self.Parent.ScrollMessagePanelByPage(-1)
   105         event.Skip()
   106         event.Skip()
   106         
   107 
   107     def OnLeftUp(self, event):
   108     def OnLeftUp(self, event):
   108         self.ThumbScrollingStartPos = None
   109         self.ThumbScrollingStartPos = None
   109         self.RefreshThumbPosition(0.)
   110         self.RefreshThumbPosition(0.)
   110         if self.HasCapture():
   111         if self.HasCapture():
   111             self.ReleaseMouse()
   112             self.ReleaseMouse()
   112         event.Skip()
   113         event.Skip()
   113         
   114 
   114     def OnMotion(self, event):
   115     def OnMotion(self, event):
   115         if event.Dragging() and self.ThumbScrollingStartPos is not None:
   116         if event.Dragging() and self.ThumbScrollingStartPos is not None:
   116             posx, posy = event.GetPosition()
   117             posx, posy = event.GetPosition()
   117             width, height = self.GetClientSize()
   118             width, height = self.GetClientSize()
   118             range_rect = self.GetRangeRect()
   119             range_rect = self.GetRangeRect()
   119             thumb_size = range_rect.height * THUMB_SIZE_RATIO
   120             thumb_size = range_rect.height * THUMB_SIZE_RATIO
   120             thumb_range = range_rect.height - thumb_size
   121             thumb_range = range_rect.height - thumb_size
   121             self.RefreshThumbPosition(
   122             self.RefreshThumbPosition(
   122                 max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
   123                 max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
   123         event.Skip()
   124         event.Skip()
   124     
   125 
   125     def OnResize(self, event):
   126     def OnResize(self, event):
   126         self.Refresh()
   127         self.Refresh()
   127         event.Skip()
   128         event.Skip()
   128     
   129 
   129     def OnEraseBackground(self, event):
   130     def OnEraseBackground(self, event):
   130         pass
   131         pass
   131     
   132 
   132     def OnPaint(self, event):
   133     def OnPaint(self, event):
   133         dc = wx.BufferedPaintDC(self)
   134         dc = wx.BufferedPaintDC(self)
   134         dc.Clear()
   135         dc.Clear()
   135         dc.BeginDrawing()
   136         dc.BeginDrawing()
   136         
   137 
   137         gc = wx.GCDC(dc)
   138         gc = wx.GCDC(dc)
   138         
   139 
   139         width, height = self.GetClientSize()
   140         width, height = self.GetClientSize()
   140         
   141 
   141         gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
   142         gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
   142         gc.SetBrush(wx.GREY_BRUSH)
   143         gc.SetBrush(wx.GREY_BRUSH)
   143         
   144 
   144         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
   145         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
   145         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
   146         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
   146         
   147 
   147         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
   148         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
   148         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
   149         gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
   149         
   150 
   150         thumb_rect = self.GetThumbRect()
   151         thumb_rect = self.GetThumbRect()
   151         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
   152         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
   152                                  thumb_rect.width, thumb_rect.height)
   153                                  thumb_rect.width, thumb_rect.height)
   153         if self.Parent.IsMessagePanelTop():
   154         if self.Parent.IsMessagePanelTop():
   154             exclusion_rect.y, exclusion_rect.height = width, exclusion_rect.y + exclusion_rect.height - width
   155             exclusion_rect.y, exclusion_rect.height = width, exclusion_rect.y + exclusion_rect.height - width
   156             exclusion_rect.height = height - width - exclusion_rect.y
   157             exclusion_rect.height = height - width - exclusion_rect.y
   157         if exclusion_rect != thumb_rect:
   158         if exclusion_rect != thumb_rect:
   158             colour = wx.NamedColour("LIGHT GREY")
   159             colour = wx.NamedColour("LIGHT GREY")
   159             gc.SetPen(wx.Pen(colour))
   160             gc.SetPen(wx.Pen(colour))
   160             gc.SetBrush(wx.Brush(colour))
   161             gc.SetBrush(wx.Brush(colour))
   161         
   162 
   162             gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y, 
   163             gc.DrawRectangle(exclusion_rect.x, exclusion_rect.y,
   163                              exclusion_rect.width, exclusion_rect.height)
   164                              exclusion_rect.width, exclusion_rect.height)
   164         
   165 
   165         gc.SetPen(wx.GREY_PEN)
   166         gc.SetPen(wx.GREY_PEN)
   166         gc.SetBrush(wx.GREY_BRUSH)
   167         gc.SetBrush(wx.GREY_BRUSH)
   167         
   168 
   168         gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
   169         gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
   169         
   170 
   170         gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
   171         gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
   171             
   172 
   172         gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
   173         gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
   173                          thumb_rect.width, thumb_rect.height)
   174                          thumb_rect.width, thumb_rect.height)
   174         
   175 
   175         dc.EndDrawing()
   176         dc.EndDrawing()
   176         event.Skip()
   177         event.Skip()
   177 
   178 
   178 BUTTON_SIZE = (30, 15)
   179 BUTTON_SIZE = (30, 15)
   179 
   180 
   180 class LogButton():
   181 class LogButton():
   181     
   182 
   182     def __init__(self, label, callback):
   183     def __init__(self, label, callback):
   183         self.Position = wx.Point(0, 0)
   184         self.Position = wx.Point(0, 0)
   184         self.Size = wx.Size(*BUTTON_SIZE)
   185         self.Size = wx.Size(*BUTTON_SIZE)
   185         self.Label = label
   186         self.Label = label
   186         self.Shown = True
   187         self.Shown = True
   187         self.Callback = callback
   188         self.Callback = callback
   188     
   189 
   189     def __del__(self):
   190     def __del__(self):
   190         self.callback = None
   191         self.callback = None
   191     
   192 
   192     def GetSize(self):
   193     def GetSize(self):
   193         return self.Size
   194         return self.Size
   194     
   195 
   195     def SetPosition(self, x, y):
   196     def SetPosition(self, x, y):
   196         self.Position = wx.Point(x, y)
   197         self.Position = wx.Point(x, y)
   197     
   198 
   198     def HitTest(self, x, y):
   199     def HitTest(self, x, y):
   199         rect = wx.Rect(self.Position.x, self.Position.y, 
   200         rect = wx.Rect(self.Position.x, self.Position.y,
   200                        self.Size.width, self.Size.height)
   201                        self.Size.width, self.Size.height)
   201         if rect.InsideXY(x, y):
   202         if rect.InsideXY(x, y):
   202             return True
   203             return True
   203         return False
   204         return False
   204     
   205 
   205     def ProcessCallback(self):
   206     def ProcessCallback(self):
   206         if self.Callback is not None:
   207         if self.Callback is not None:
   207             wx.CallAfter(self.Callback)
   208             wx.CallAfter(self.Callback)
   208             
   209 
   209     def Draw(self, dc):
   210     def Draw(self, dc):
   210         dc.SetPen(wx.TRANSPARENT_PEN)
   211         dc.SetPen(wx.TRANSPARENT_PEN)
   211         dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
   212         dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
   212         
   213 
   213         dc.DrawRectangle(self.Position.x, self.Position.y, 
   214         dc.DrawRectangle(self.Position.x, self.Position.y,
   214                          self.Size.width, self.Size.height)
   215                          self.Size.width, self.Size.height)
   215         
   216 
   216         w, h = dc.GetTextExtent(self.Label)
   217         w, h = dc.GetTextExtent(self.Label)
   217         dc.DrawText(self.Label, 
   218         dc.DrawText(self.Label,
   218             self.Position.x + (self.Size.width - w) / 2, 
   219             self.Position.x + (self.Size.width - w) / 2,
   219             self.Position.y + (self.Size.height - h) / 2)
   220             self.Position.y + (self.Size.height - h) / 2)
   220 
   221 
   221 DATE_INFO_SIZE = 10
   222 DATE_INFO_SIZE = 10
   222 MESSAGE_INFO_SIZE = 18
   223 MESSAGE_INFO_SIZE = 18
   223 
   224 
   224 class LogMessage:
   225 class LogMessage:
   225     
   226 
   226     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
   227     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
   227         self.Date = datetime.utcfromtimestamp(tv_sec)
   228         self.Date = datetime.utcfromtimestamp(tv_sec)
   228         self.Seconds = self.Date.second + tv_nsec * 1e-9
   229         self.Seconds = self.Date.second + tv_nsec * 1e-9
   229         self.Date = self.Date.replace(second=0)
   230         self.Date = self.Date.replace(second=0)
   230         self.Timestamp = tv_sec + tv_nsec * 1e-9
   231         self.Timestamp = tv_sec + tv_nsec * 1e-9
   231         self.Level = level
   232         self.Level = level
   232         self.LevelBitmap = level_bitmap
   233         self.LevelBitmap = level_bitmap
   233         self.Message = msg
   234         self.Message = msg
   234         self.DrawDate = True
   235         self.DrawDate = True
   235     
   236 
   236     def __cmp__(self, other):
   237     def __cmp__(self, other):
   237         if self.Date == other.Date:
   238         if self.Date == other.Date:
   238             return cmp(self.Seconds, other.Seconds)
   239             return cmp(self.Seconds, other.Seconds)
   239         return cmp(self.Date, other.Date)
   240         return cmp(self.Date, other.Date)
   240     
   241 
   241     def GetFullText(self):
   242     def GetFullText(self):
   242         date = self.Date.replace(second=int(self.Seconds))
   243         date = self.Date.replace(second=int(self.Seconds))
   243         nsec = (self.Seconds % 1.) * 1e9
   244         nsec = (self.Seconds % 1.) * 1e9
   244         return "%s at %s.%9.9d:\n%s" % (
   245         return "%s at %s.%9.9d:\n%s" % (
   245             LogLevels[self.Level],
   246             LogLevels[self.Level],
   246             str(date), nsec,
   247             str(date), nsec,
   247             self.Message)
   248             self.Message)
   248     
   249 
   249     def Draw(self, dc, offset, width, draw_date):
   250     def Draw(self, dc, offset, width, draw_date):
   250         if draw_date:
   251         if draw_date:
   251             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
   252             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
   252             dw, dh = dc.GetTextExtent(datetime_text)
   253             dw, dh = dc.GetTextExtent(datetime_text)
   253             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
   254             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
   254             offset += DATE_INFO_SIZE
   255             offset += DATE_INFO_SIZE
   255         
   256 
   256         seconds_text = "%12.9f" % self.Seconds
   257         seconds_text = "%12.9f" % self.Seconds
   257         sw, sh = dc.GetTextExtent(seconds_text)
   258         sw, sh = dc.GetTextExtent(seconds_text)
   258         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
   259         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
   259         
   260 
   260         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
   261         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
   261         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
   262         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
   262         
   263 
   263         text = self.Message.replace("\n", " ")
   264         text = self.Message.replace("\n", " ")
   264         mw, mh = dc.GetTextExtent(text)
   265         mw, mh = dc.GetTextExtent(text)
   265         dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
   266         dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
   266         
   267 
   267     def GetHeight(self, draw_date):
   268     def GetHeight(self, draw_date):
   268         if draw_date:
   269         if draw_date:
   269             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
   270             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
   270         return MESSAGE_INFO_SIZE
   271         return MESSAGE_INFO_SIZE
   271 
   272 
   278                             (_("1h"), HOUR),
   279                             (_("1h"), HOUR),
   279                             (_("1m"), MINUTE),
   280                             (_("1m"), MINUTE),
   280                             (_("1s"), SECOND)]
   281                             (_("1s"), SECOND)]
   281 
   282 
   282 class LogViewer(DebugViewer, wx.Panel):
   283 class LogViewer(DebugViewer, wx.Panel):
   283     
   284 
   284     def __init__(self, parent, window):
   285     def __init__(self, parent, window):
   285         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
   286         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
   286         DebugViewer.__init__(self, None, False, False)
   287         DebugViewer.__init__(self, None, False, False)
   287         
   288 
   288         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
   289         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
   289         main_sizer.AddGrowableCol(0)
   290         main_sizer.AddGrowableCol(0)
   290         main_sizer.AddGrowableRow(1)
   291         main_sizer.AddGrowableRow(1)
   291         
   292 
   292         filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
   293         filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
   293         main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
   294         main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
   294         
   295 
   295         self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
   296         self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
   296         self.MessageFilter.Append(_("All"))
   297         self.MessageFilter.Append(_("All"))
   297         levels = LogLevels[:3]
   298         levels = LogLevels[:3]
   298         levels.reverse()
   299         levels.reverse()
   299         for level in levels:
   300         for level in levels:
   300             self.MessageFilter.Append(_(level))
   301             self.MessageFilter.Append(_(level))
   301         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
   302         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
   302         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
   303         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
   303         
   304 
   304         self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
   305         self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
   305         self.SearchMessage.ShowSearchButton(True)
   306         self.SearchMessage.ShowSearchButton(True)
   306         self.SearchMessage.ShowCancelButton(True)
   307         self.SearchMessage.ShowCancelButton(True)
   307         self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
   308         self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
   308         self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
   309         self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN,
   309               self.OnSearchMessageSearchButtonClick, self.SearchMessage)
   310               self.OnSearchMessageSearchButtonClick, self.SearchMessage)
   310         self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, 
   311         self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN,
   311               self.OnSearchMessageCancelButtonClick, self.SearchMessage)
   312               self.OnSearchMessageCancelButtonClick, self.SearchMessage)
   312         filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
   313         filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL)
   313         
   314 
   314         self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"), 
   315         self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"),
   315               size=wx.Size(28, 28), style=wx.NO_BORDER)
   316               size=wx.Size(28, 28), style=wx.NO_BORDER)
   316         self.CleanButton.SetToolTipString(_("Clean log messages"))
   317         self.CleanButton.SetToolTipString(_("Clean log messages"))
   317         self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
   318         self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
   318         filter_sizer.AddWindow(self.CleanButton)
   319         filter_sizer.AddWindow(self.CleanButton)
   319         
   320 
   320         message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
   321         message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
   321         message_panel_sizer.AddGrowableCol(0)
   322         message_panel_sizer.AddGrowableCol(0)
   322         message_panel_sizer.AddGrowableRow(0)
   323         message_panel_sizer.AddGrowableRow(0)
   323         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
   324         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
   324         
   325 
   325         self.MessagePanel = wx.Panel(self)
   326         self.MessagePanel = wx.Panel(self)
   326         if wx.Platform == '__WXMSW__':
   327         if wx.Platform == '__WXMSW__':
   327             self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
   328             self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
   328         else:
   329         else:
   329             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
   330             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
   335         self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
   336         self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
   336         self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
   337         self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
   337         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
   338         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
   338         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
   339         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
   339         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
   340         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
   340         
   341 
   341         self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
   342         self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
   342         message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
   343         message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
   343         
   344 
   344         self.SetSizer(main_sizer)
   345         self.SetSizer(main_sizer)
   345         
   346 
   346         self.LeftButtons = []
   347         self.LeftButtons = []
   347         for label, callback in [("+" + text, self.GenerateOnDurationButton(duration)) 
   348         for label, callback in [("+" + text, self.GenerateOnDurationButton(duration))
   348                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
   349                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
   349             self.LeftButtons.append(LogButton(label, callback))
   350             self.LeftButtons.append(LogButton(label, callback))
   350         
   351 
   351         self.RightButtons = []
   352         self.RightButtons = []
   352         for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration)) 
   353         for label, callback in [("-" + text, self.GenerateOnDurationButton(-duration))
   353                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
   354                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS]:
   354             self.RightButtons.append(LogButton(label, callback))
   355             self.RightButtons.append(LogButton(label, callback))
   355         
   356 
   356         self.MessageFilter.SetSelection(0)
   357         self.MessageFilter.SetSelection(0)
   357         self.LogSource = None
   358         self.LogSource = None
   358         self.ResetLogMessages()
   359         self.ResetLogMessages()
   359         self.ParentWindow = window
   360         self.ParentWindow = window
   360     
   361 
   361         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
   362         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
   362         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
   363         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
   363         self.CurrentFilter = self.LevelFilters[0]
   364         self.CurrentFilter = self.LevelFilters[0]
   364         self.CurrentSearchValue = ""
   365         self.CurrentSearchValue = ""
   365         
   366 
   366         self.ScrollSpeed = 0.
   367         self.ScrollSpeed = 0.
   367         self.LastStartTime = None
   368         self.LastStartTime = None
   368         self.ScrollTimer = wx.Timer(self, -1)
   369         self.ScrollTimer = wx.Timer(self, -1)
   369         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
   370         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
   370         
   371 
   371         self.LastMousePos = None
   372         self.LastMousePos = None
   372         self.MessageToolTip = None
   373         self.MessageToolTip = None
   373         self.MessageToolTipTimer = wx.Timer(self, -1)
   374         self.MessageToolTipTimer = wx.Timer(self, -1)
   374         self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
   375         self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
   375     
   376 
   376     def __del__(self):
   377     def __del__(self):
   377         self.ScrollTimer.Stop()
   378         self.ScrollTimer.Stop()
   378     
   379 
   379     def ResetLogMessages(self):
   380     def ResetLogMessages(self):
   380         self.previous_log_count = [None]*LogLevelsCount
   381         self.previous_log_count = [None]*LogLevelsCount
   381         self.OldestMessages = []
   382         self.OldestMessages = []
   382         self.LogMessages = []
   383         self.LogMessages = []
   383         self.LogMessagesTimestamp = numpy.array([])
   384         self.LogMessagesTimestamp = numpy.array([])
   384         self.CurrentMessage = None
   385         self.CurrentMessage = None
   385         self.HasNewData = False
   386         self.HasNewData = False
   386     
   387 
   387     def SetLogSource(self, log_source):
   388     def SetLogSource(self, log_source):
   388         self.LogSource = log_source
   389         self.LogSource = proxy(log_source) if log_source else None
   389         self.CleanButton.Enable(self.LogSource is not None)
   390         self.CleanButton.Enable(self.LogSource is not None)
   390         if log_source is not None:
   391         if log_source is not None:
   391             self.ResetLogMessages()
   392             self.ResetLogMessages()
   392             self.RefreshView()
   393             self.RefreshView()
   393     
   394 
   394     def GetLogMessageFromSource(self, msgidx, level):
   395     def GetLogMessageFromSource(self, msgidx, level):
   395         if self.LogSource is not None:
   396         if self.LogSource is not None:
   396             answer = self.LogSource.GetLogMessage(level, msgidx)
   397             answer = self.LogSource.GetLogMessage(level, msgidx)
   397             if answer is not None:
   398             if answer is not None:
   398                 msg, tick, tv_sec, tv_nsec = answer
   399                 msg, tick, tv_sec, tv_nsec = answer
   399                 return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
   400                 return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
   400         return None
   401         return None
   401     
   402 
   402     def SetLogCounters(self, log_count):
   403     def SetLogCounters(self, log_count):
   403         new_messages = []
   404         new_messages = []
   404         for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
   405         for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
   405             if count is not None and prev != count:
   406             if count is not None and prev != count:
   406                 if prev is None:
   407                 if prev is None:
   439                 self.ScrollToLast(False)
   440                 self.ScrollToLast(False)
   440                 self.ResetMessageToolTip()
   441                 self.ResetMessageToolTip()
   441                 self.MessageToolTipTimer.Stop()
   442                 self.MessageToolTipTimer.Stop()
   442                 self.ParentWindow.SelectTab(self)
   443                 self.ParentWindow.SelectTab(self)
   443             self.NewDataAvailable(None)
   444             self.NewDataAvailable(None)
   444     
   445 
   445     def FilterLogMessage(self, message, timestamp=None):
   446     def FilterLogMessage(self, message, timestamp=None):
   446         return (message.Level in self.CurrentFilter and 
   447         return (message.Level in self.CurrentFilter and
   447                 message.Message.find(self.CurrentSearchValue) != -1 and
   448                 message.Message.find(self.CurrentSearchValue) != -1 and
   448                 (timestamp is None or message.Timestamp < timestamp))
   449                 (timestamp is None or message.Timestamp < timestamp))
   449     
   450 
   450     def GetMessageByTimestamp(self, timestamp):
   451     def GetMessageByTimestamp(self, timestamp):
   451         if self.CurrentMessage is not None:
   452         if self.CurrentMessage is not None:
   452             msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
   453             msgidx = numpy.argmin(abs(self.LogMessagesTimestamp - timestamp))
   453             message = self.LogMessages[msgidx]
   454             message = self.LogMessages[msgidx]
   454             if self.FilterLogMessage(message) and message.Timestamp > timestamp:
   455             if self.FilterLogMessage(message) and message.Timestamp > timestamp:
   455                 return self.GetPreviousMessage(msgidx, timestamp)
   456                 return self.GetPreviousMessage(msgidx, timestamp)
   456             return message, msgidx
   457             return message, msgidx
   457         return None, None
   458         return None, None
   458     
   459 
   459     def GetNextMessage(self, msgidx):
   460     def GetNextMessage(self, msgidx):
   460         while msgidx < len(self.LogMessages) - 1:
   461         while msgidx < len(self.LogMessages) - 1:
   461             message = self.LogMessages[msgidx + 1]
   462             message = self.LogMessages[msgidx + 1]
   462             if self.FilterLogMessage(message):
   463             if self.FilterLogMessage(message):
   463                 return message, msgidx + 1
   464                 return message, msgidx + 1
   464             msgidx += 1
   465             msgidx += 1
   465         return None, None
   466         return None, None
   466     
   467 
   467     def GetPreviousMessage(self, msgidx, timestamp=None):
   468     def GetPreviousMessage(self, msgidx, timestamp=None):
   468         message = None
   469         message = None
   469         while 0 < msgidx < len(self.LogMessages):
   470         while 0 < msgidx < len(self.LogMessages):
   470             message = self.LogMessages[msgidx - 1]
   471             message = self.LogMessages[msgidx - 1]
   471             if self.FilterLogMessage(message, timestamp):
   472             if self.FilterLogMessage(message, timestamp):
   488                 else:
   489                 else:
   489                     message = None
   490                     message = None
   490                     self.OldestMessages[level] = (-1, None)
   491                     self.OldestMessages[level] = (-1, None)
   491                 if message is not None:
   492                 if message is not None:
   492                     message_idx = 0
   493                     message_idx = 0
   493                     while (message_idx < len(self.LogMessages) and 
   494                     while (message_idx < len(self.LogMessages) and
   494                            self.LogMessages[message_idx] < message):
   495                            self.LogMessages[message_idx] < message):
   495                         message_idx += 1
   496                         message_idx += 1
   496                     if len(self.LogMessages) > 0:
   497                     if len(self.LogMessages) > 0:
   497                         current_message = self.LogMessages[self.CurrentMessage]
   498                         current_message = self.LogMessages[self.CurrentMessage]
   498                     else:
   499                     else:
   499                         current_message = message
   500                         current_message = message
   500                     self.LogMessages.insert(message_idx, message)
   501                     self.LogMessages.insert(message_idx, message)
   501                     self.LogMessagesTimestamp = numpy.insert(
   502                     self.LogMessagesTimestamp = numpy.insert(
   502                             self.LogMessagesTimestamp, 
   503                             self.LogMessagesTimestamp,
   503                             [message_idx], 
   504                             [message_idx],
   504                             [message.Timestamp])
   505                             [message.Timestamp])
   505                     self.CurrentMessage = self.LogMessages.index(current_message)
   506                     self.CurrentMessage = self.LogMessages.index(current_message)
   506                     if message_idx == 0 and self.FilterLogMessage(message, timestamp):
   507                     if message_idx == 0 and self.FilterLogMessage(message, timestamp):
   507                         return message, 0
   508                         return message, 0
   508                 for idx, msg in self.OldestMessages:
   509                 for idx, msg in self.OldestMessages:
   509                     if msg is not None and (message is None or msg > message):
   510                     if msg is not None and (message is None or msg > message):
   510                         message = msg
   511                         message = msg
   511         return None, None
   512         return None, None
   512     
   513 
   513     def RefreshNewData(self, *args, **kwargs):
   514     def RefreshNewData(self, *args, **kwargs):
   514         if self.HasNewData:
   515         if self.HasNewData:
   515             self.HasNewData = False
   516             self.HasNewData = False
   516             self.RefreshView()
   517             self.RefreshView()
   517         DebugViewer.RefreshNewData(self, *args, **kwargs)
   518         DebugViewer.RefreshNewData(self, *args, **kwargs)
   518     
   519 
   519     def RefreshView(self):
   520     def RefreshView(self):
   520         width, height = self.MessagePanel.GetClientSize()
   521         width, height = self.MessagePanel.GetClientSize()
   521         bitmap = wx.EmptyBitmap(width, height)
   522         bitmap = wx.EmptyBitmap(width, height)
   522         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
   523         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
   523         dc.Clear()
   524         dc.Clear()
   524         dc.BeginDrawing()
   525         dc.BeginDrawing()
   525         
   526 
   526         if self.CurrentMessage is not None:
   527         if self.CurrentMessage is not None:
   527             
   528 
   528             dc.SetFont(self.Font)
   529             dc.SetFont(self.Font)
   529             
   530 
   530             for button in self.LeftButtons + self.RightButtons:
   531             for button in self.LeftButtons + self.RightButtons:
   531                 button.Draw(dc)
   532                 button.Draw(dc)
   532             
   533 
   533             message_idx = self.CurrentMessage
   534             message_idx = self.CurrentMessage
   534             message = self.LogMessages[message_idx]
   535             message = self.LogMessages[message_idx]
   535             draw_date = True
   536             draw_date = True
   536             offset = 5
   537             offset = 5
   537             while offset < height and message is not None:
   538             while offset < height and message is not None:
   538                 message.Draw(dc, offset, width, draw_date)
   539                 message.Draw(dc, offset, width, draw_date)
   539                 offset += message.GetHeight(draw_date)
   540                 offset += message.GetHeight(draw_date)
   540                 
   541 
   541                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   542                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   542                 if previous_message is not None:
   543                 if previous_message is not None:
   543                     draw_date = message.Date != previous_message.Date
   544                     draw_date = message.Date != previous_message.Date
   544                 message = previous_message
   545                 message = previous_message
   545         
   546 
   546         dc.EndDrawing()
   547         dc.EndDrawing()
   547         
   548 
   548         self.MessageScrollBar.RefreshThumbPosition()
   549         self.MessageScrollBar.RefreshThumbPosition()
   549     
   550 
   550     def IsMessagePanelTop(self, message_idx=None):
   551     def IsMessagePanelTop(self, message_idx=None):
   551         if message_idx is None:
   552         if message_idx is None:
   552             message_idx = self.CurrentMessage
   553             message_idx = self.CurrentMessage
   553         if message_idx is not None:
   554         if message_idx is not None:
   554             return self.GetNextMessage(message_idx)[0] is None
   555             return self.GetNextMessage(message_idx)[0] is None
   555         return True
   556         return True
   556     
   557 
   557     def IsMessagePanelBottom(self, message_idx=None):
   558     def IsMessagePanelBottom(self, message_idx=None):
   558         if message_idx is None:
   559         if message_idx is None:
   559             message_idx = self.CurrentMessage
   560             message_idx = self.CurrentMessage
   560         if message_idx is not None:
   561         if message_idx is not None:
   561             width, height = self.MessagePanel.GetClientSize()
   562             width, height = self.MessagePanel.GetClientSize()
   568                 if previous_message is not None:
   569                 if previous_message is not None:
   569                     draw_date = message.Date != previous_message.Date
   570                     draw_date = message.Date != previous_message.Date
   570                 message = previous_message
   571                 message = previous_message
   571             return offset < height
   572             return offset < height
   572         return True
   573         return True
   573     
   574 
   574     def ScrollMessagePanel(self, scroll):
   575     def ScrollMessagePanel(self, scroll):
   575         if self.CurrentMessage is not None:
   576         if self.CurrentMessage is not None:
   576             message = self.LogMessages[self.CurrentMessage]
   577             message = self.LogMessages[self.CurrentMessage]
   577             while scroll > 0 and message is not None:
   578             while scroll > 0 and message is not None:
   578                 message, msgidx = self.GetNextMessage(self.CurrentMessage)
   579                 message, msgidx = self.GetNextMessage(self.CurrentMessage)
   583                 message, msgidx = self.GetPreviousMessage(self.CurrentMessage)
   584                 message, msgidx = self.GetPreviousMessage(self.CurrentMessage)
   584                 if message is not None:
   585                 if message is not None:
   585                     self.CurrentMessage = msgidx
   586                     self.CurrentMessage = msgidx
   586                     scroll += 1
   587                     scroll += 1
   587             self.RefreshView()
   588             self.RefreshView()
   588     
   589 
   589     def ScrollMessagePanelByPage(self, page):
   590     def ScrollMessagePanelByPage(self, page):
   590         if self.CurrentMessage is not None:
   591         if self.CurrentMessage is not None:
   591             width, height = self.MessagePanel.GetClientSize()
   592             width, height = self.MessagePanel.GetClientSize()
   592             message_per_page = max(1, (height - DATE_INFO_SIZE) / MESSAGE_INFO_SIZE - 1)
   593             message_per_page = max(1, (height - DATE_INFO_SIZE) / MESSAGE_INFO_SIZE - 1)
   593             self.ScrollMessagePanel(page * message_per_page)
   594             self.ScrollMessagePanel(page * message_per_page)
   594     
   595 
   595     def ScrollMessagePanelByTimestamp(self, seconds):
   596     def ScrollMessagePanelByTimestamp(self, seconds):
   596         if self.CurrentMessage is not None:
   597         if self.CurrentMessage is not None:
   597             current_message = self.LogMessages[self.CurrentMessage]
   598             current_message = self.LogMessages[self.CurrentMessage]
   598             message, msgidx = self.GetMessageByTimestamp(current_message.Timestamp + seconds)
   599             message, msgidx = self.GetMessageByTimestamp(current_message.Timestamp + seconds)
   599             if message is None or self.IsMessagePanelBottom(msgidx):
   600             if message is None or self.IsMessagePanelBottom(msgidx):
   601             else:
   602             else:
   602                 if seconds > 0 and self.CurrentMessage == msgidx and msgidx < len(self.LogMessages) - 1:
   603                 if seconds > 0 and self.CurrentMessage == msgidx and msgidx < len(self.LogMessages) - 1:
   603                     msgidx += 1
   604                     msgidx += 1
   604                 self.CurrentMessage = msgidx
   605                 self.CurrentMessage = msgidx
   605                 self.RefreshView()
   606                 self.RefreshView()
   606             
   607 
   607     def ResetMessagePanel(self):
   608     def ResetMessagePanel(self):
   608         if len(self.LogMessages) > 0:
   609         if len(self.LogMessages) > 0:
   609             self.CurrentMessage = len(self.LogMessages) - 1
   610             self.CurrentMessage = len(self.LogMessages) - 1
   610             message = self.LogMessages[self.CurrentMessage]
   611             message = self.LogMessages[self.CurrentMessage]
   611             while message is not None and not self.FilterLogMessage(message):
   612             while message is not None and not self.FilterLogMessage(message):
   612                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
   613                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
   613             self.RefreshView()
   614             self.RefreshView()
   614     
   615 
   615     def OnMessageFilterChanged(self, event):
   616     def OnMessageFilterChanged(self, event):
   616         self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
   617         self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
   617         self.ResetMessagePanel()
   618         self.ResetMessagePanel()
   618         event.Skip()
   619         event.Skip()
   619     
   620 
   620     def OnSearchMessageChanged(self, event):
   621     def OnSearchMessageChanged(self, event):
   621         self.CurrentSearchValue = self.SearchMessage.GetValue()
   622         self.CurrentSearchValue = self.SearchMessage.GetValue()
   622         self.ResetMessagePanel()
   623         self.ResetMessagePanel()
   623         event.Skip()
   624         event.Skip()
   624         
   625 
   625     def OnSearchMessageSearchButtonClick(self, event):
   626     def OnSearchMessageSearchButtonClick(self, event):
   626         self.CurrentSearchValue = self.SearchMessage.GetValue()
   627         self.CurrentSearchValue = self.SearchMessage.GetValue()
   627         self.ResetMessagePanel()
   628         self.ResetMessagePanel()
   628         event.Skip()
   629         event.Skip()
   629     
   630 
   630     def OnSearchMessageCancelButtonClick(self, event):
   631     def OnSearchMessageCancelButtonClick(self, event):
   631         self.CurrentSearchValue = ""
   632         self.CurrentSearchValue = ""
   632         self.SearchMessage.SetValue("")
   633         self.SearchMessage.SetValue("")
   633         self.ResetMessagePanel()
   634         self.ResetMessagePanel()
   634         event.Skip()
   635         event.Skip()
   635     
   636 
   636     def OnCleanButton(self, event):
   637     def OnCleanButton(self, event):
   637         if self.LogSource is not None:
   638         if self.LogSource is not None:
   638             self.LogSource.ResetLogCount()
   639             self.LogSource.ResetLogCount()
   639         self.ResetLogMessages()
   640         self.ResetLogMessages()
   640         self.RefreshView()
   641         self.RefreshView()
   641         event.Skip()
   642         event.Skip()
   642     
   643 
   643     def GenerateOnDurationButton(self, duration):
   644     def GenerateOnDurationButton(self, duration):
   644         def OnDurationButton():
   645         def OnDurationButton():
   645             self.ScrollMessagePanelByTimestamp(duration)
   646             self.ScrollMessagePanelByTimestamp(duration)
   646         return OnDurationButton
   647         return OnDurationButton
   647     
   648 
   648     def GetCopyMessageToClipboardFunction(self, message):
   649     def GetCopyMessageToClipboardFunction(self, message):
   649         def CopyMessageToClipboardFunction(event):
   650         def CopyMessageToClipboardFunction(event):
   650             self.ParentWindow.SetCopyBuffer(message.GetFullText())
   651             self.ParentWindow.SetCopyBuffer(message.GetFullText())
   651         return CopyMessageToClipboardFunction
   652         return CopyMessageToClipboardFunction
   652     
   653 
   653     def GetMessageByScreenPos(self, posx, posy):
   654     def GetMessageByScreenPos(self, posx, posy):
   654         if self.CurrentMessage is not None:
   655         if self.CurrentMessage is not None:
   655             width, height = self.MessagePanel.GetClientSize()
   656             width, height = self.MessagePanel.GetClientSize()
   656             message_idx = self.CurrentMessage
   657             message_idx = self.CurrentMessage
   657             message = self.LogMessages[message_idx]
   658             message = self.LogMessages[message_idx]
   658             draw_date = True
   659             draw_date = True
   659             offset = 5
   660             offset = 5
   660             
   661 
   661             while offset < height and message is not None:
   662             while offset < height and message is not None:
   662                 if draw_date:
   663                 if draw_date:
   663                     offset += DATE_INFO_SIZE
   664                     offset += DATE_INFO_SIZE
   664     
   665 
   665                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
   666                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
   666                     return message
   667                     return message
   667         
   668 
   668                 offset += MESSAGE_INFO_SIZE
   669                 offset += MESSAGE_INFO_SIZE
   669                 
   670 
   670                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   671                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   671                 if previous_message is not None:
   672                 if previous_message is not None:
   672                     draw_date = message.Date != previous_message.Date
   673                     draw_date = message.Date != previous_message.Date
   673                 message = previous_message
   674                 message = previous_message
   674         return None
   675         return None
   675     
   676 
   676     def OnMessagePanelLeftUp(self, event):
   677     def OnMessagePanelLeftUp(self, event):
   677         if self.CurrentMessage is not None:
   678         if self.CurrentMessage is not None:
   678             posx, posy = event.GetPosition()
   679             posx, posy = event.GetPosition()
   679             for button in self.LeftButtons + self.RightButtons:
   680             for button in self.LeftButtons + self.RightButtons:
   680                 if button.HitTest(posx, posy):
   681                 if button.HitTest(posx, posy):
   681                     button.ProcessCallback()
   682                     button.ProcessCallback()
   682                     break
   683                     break
   683         event.Skip()
   684         event.Skip()
   684     
   685 
   685     def OnMessagePanelRightUp(self, event):
   686     def OnMessagePanelRightUp(self, event):
   686         message = self.GetMessageByScreenPos(*event.GetPosition())
   687         message = self.GetMessageByScreenPos(*event.GetPosition())
   687         if message is not None:
   688         if message is not None:
   688             menu = wx.Menu(title='')
   689             menu = wx.Menu(title='')
   689             
   690 
   690             new_id = wx.NewId()
   691             new_id = wx.NewId()
   691             menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
   692             menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
   692             self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
   693             self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
   693     
   694 
   694             self.MessagePanel.PopupMenu(menu)
   695             self.MessagePanel.PopupMenu(menu)
   695             menu.Destroy()
   696             menu.Destroy()
   696         event.Skip()
   697         event.Skip()
   697     
   698 
   698     def OnMessagePanelLeftDCLick(self, event):
   699     def OnMessagePanelLeftDCLick(self, event):
   699         message = self.GetMessageByScreenPos(*event.GetPosition())
   700         message = self.GetMessageByScreenPos(*event.GetPosition())
   700         if message is not None:
   701         if message is not None:
   701             self.SearchMessage.SetFocus()
   702             self.SearchMessage.SetFocus()
   702             self.SearchMessage.SetValue(message.Message)
   703             self.SearchMessage.SetValue(message.Message)
   703         event.Skip()
   704         event.Skip()
   704     
   705 
   705     def ResetMessageToolTip(self):
   706     def ResetMessageToolTip(self):
   706         if self.MessageToolTip is not None:
   707         if self.MessageToolTip is not None:
   707             self.MessageToolTip.Destroy()
   708             self.MessageToolTip.Destroy()
   708             self.MessageToolTip = None
   709             self.MessageToolTip = None
   709     
   710 
   710     def OnMessageToolTipTimer(self, event):
   711     def OnMessageToolTipTimer(self, event):
   711         if self.LastMousePos is not None:
   712         if self.LastMousePos is not None:
   712             message = self.GetMessageByScreenPos(*self.LastMousePos)
   713             message = self.GetMessageByScreenPos(*self.LastMousePos)
   713             if message is not None:
   714             if message is not None:
   714                 tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
   715                 tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
   717                 self.MessageToolTip = CustomToolTip(self.MessagePanel, message.GetFullText(), False)
   718                 self.MessageToolTip = CustomToolTip(self.MessagePanel, message.GetFullText(), False)
   718                 self.MessageToolTip.SetFont(self.Font)
   719                 self.MessageToolTip.SetFont(self.Font)
   719                 self.MessageToolTip.SetToolTipPosition(tooltip_pos)
   720                 self.MessageToolTip.SetToolTipPosition(tooltip_pos)
   720                 self.MessageToolTip.Show()
   721                 self.MessageToolTip.Show()
   721         event.Skip()
   722         event.Skip()
   722     
   723 
   723     def OnMessagePanelMotion(self, event):
   724     def OnMessagePanelMotion(self, event):
   724         if not event.Dragging():
   725         if not event.Dragging():
   725             self.ResetMessageToolTip()
   726             self.ResetMessageToolTip()
   726             self.LastMousePos = event.GetPosition()
   727             self.LastMousePos = event.GetPosition()
   727             self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
   728             self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
   728         event.Skip()
   729         event.Skip()
   729         
   730 
   730     def OnMessagePanelLeaveWindow(self, event):
   731     def OnMessagePanelLeaveWindow(self, event):
   731         self.ResetMessageToolTip()
   732         self.ResetMessageToolTip()
   732         self.LastMousePos = None
   733         self.LastMousePos = None
   733         self.MessageToolTipTimer.Stop()
   734         self.MessageToolTipTimer.Stop()
   734         event.Skip()
   735         event.Skip()
   735     
   736 
   736     def OnMessagePanelMouseWheel(self, event):
   737     def OnMessagePanelMouseWheel(self, event):
   737         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
   738         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
   738         event.Skip()
   739         event.Skip()
   739     
   740 
   740     def OnMessagePanelEraseBackground(self, event):
   741     def OnMessagePanelEraseBackground(self, event):
   741         pass
   742         pass
   742     
   743 
   743     def OnMessagePanelPaint(self, event):
   744     def OnMessagePanelPaint(self, event):
   744         self.RefreshView()
   745         self.RefreshView()
   745         event.Skip()
   746         event.Skip()
   746     
   747 
   747     def OnMessagePanelResize(self, event):
   748     def OnMessagePanelResize(self, event):
   748         width, height = self.MessagePanel.GetClientSize()
   749         width, height = self.MessagePanel.GetClientSize()
   749         offset = 2
   750         offset = 2
   750         for button in self.LeftButtons:
   751         for button in self.LeftButtons:
   751             button.SetPosition(offset, 2)
   752             button.SetPosition(offset, 2)
   759         if self.IsMessagePanelBottom():
   760         if self.IsMessagePanelBottom():
   760             self.ScrollToFirst()
   761             self.ScrollToFirst()
   761         else:
   762         else:
   762             self.RefreshView()
   763             self.RefreshView()
   763         event.Skip()
   764         event.Skip()
   764     
   765 
   765     def OnScrollTimer(self, event):
   766     def OnScrollTimer(self, event):
   766         if self.ScrollSpeed != 0.:
   767         if self.ScrollSpeed != 0.:
   767             speed_norm = abs(self.ScrollSpeed)
   768             speed_norm = abs(self.ScrollSpeed)
   768             period = REFRESH_PERIOD / speed_norm
   769             period = REFRESH_PERIOD / speed_norm
   769             self.ScrollMessagePanel(-speed_norm / self.ScrollSpeed)
   770             self.ScrollMessagePanel(-speed_norm / self.ScrollSpeed)
   770             self.LastStartTime = gettime()
   771             self.LastStartTime = gettime()
   771             self.ScrollTimer.Start(int(period * 1000), True)
   772             self.ScrollTimer.Start(int(period * 1000), True)
   772         event.Skip()
   773         event.Skip()
   773     
   774 
   774     def SetScrollSpeed(self, speed):
   775     def SetScrollSpeed(self, speed):
   775         if speed == 0.:
   776         if speed == 0.:
   776             self.ScrollTimer.Stop()
   777             self.ScrollTimer.Stop()
   777         else:
   778         else:
   778             speed_norm = abs(speed)
   779             speed_norm = abs(speed)
   786                 else:
   787                 else:
   787                     period -= elapsed_time
   788                     period -= elapsed_time
   788             else:
   789             else:
   789                 self.LastStartTime = current_time
   790                 self.LastStartTime = current_time
   790             self.ScrollTimer.Start(int(period * 1000), True)
   791             self.ScrollTimer.Start(int(period * 1000), True)
   791         self.ScrollSpeed = speed    
   792         self.ScrollSpeed = speed
   792     
   793 
   793     def ScrollToLast(self, refresh=True):
   794     def ScrollToLast(self, refresh=True):
   794         if len(self.LogMessages) > 0:
   795         if len(self.LogMessages) > 0:
   795             self.CurrentMessage = len(self.LogMessages) - 1
   796             self.CurrentMessage = len(self.LogMessages) - 1
   796             message = self.LogMessages[self.CurrentMessage]
   797             message = self.LogMessages[self.CurrentMessage]
   797             if not self.FilterLogMessage(message):
   798             if not self.FilterLogMessage(message):