controls/LogViewer.py
changeset 981 fc671a3e95a9
parent 979 1a68113a323d
child 982 e3c264099bd0
equal deleted inserted replaced
980:c7ba67d01d65 981:fc671a3e95a9
    21 #You should have received a copy of the GNU General Public
    21 #You should have received a copy of the GNU General Public
    22 #License along with this library; if not, write to the Free Software
    22 #License along with this library; if not, write to the Free Software
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    24 
    24 
    25 from datetime import datetime
    25 from datetime import datetime
       
    26 from time import time as gettime
    26 
    27 
    27 import wx
    28 import wx
    28 
    29 
    29 from graphics import DebugViewer, REFRESH_PERIOD
    30 from graphics import DebugViewer, REFRESH_PERIOD
    30 from targets.typemapping import LogLevelsCount, LogLevels
    31 from targets.typemapping import LogLevelsCount, LogLevels
    31 from util.BitmapLibrary import GetBitmap
    32 from util.BitmapLibrary import GetBitmap
    32 
    33 
    33 SPEED_VALUES = [10, 5, 2, 1, 0, -1, -2, -5, -10]
    34 THUMB_SIZE_RATIO = 1. / 8.
    34 
    35 
    35 class MyScrollBar(wx.Panel):
    36 class MyScrollBar(wx.Panel):
    36     
    37     
    37     def __init__(self, parent, size):
    38     def __init__(self, parent, size):
    38         wx.Panel.__init__(self, parent, size=size)
    39         wx.Panel.__init__(self, parent, size=size)
    40         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
    41         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
    41         self.Bind(wx.EVT_MOTION, self.OnMotion)
    42         self.Bind(wx.EVT_MOTION, self.OnMotion)
    42         self.Bind(wx.EVT_PAINT, self.OnPaint)
    43         self.Bind(wx.EVT_PAINT, self.OnPaint)
    43         self.Bind(wx.EVT_SIZE, self.OnResize)
    44         self.Bind(wx.EVT_SIZE, self.OnResize)
    44         
    45         
    45         self.ThumbPosition = SPEED_VALUES.index(0)
    46         self.ThumbPosition = 0. # -1 <= ThumbPosition <= 1
    46         self.ThumbScrolling = False
    47         self.ThumbScrollingStartPos = None
    47     
    48     
    48     def GetRangeRect(self):
    49     def GetRangeRect(self):
    49         width, height = self.GetClientSize()
    50         width, height = self.GetClientSize()
    50         return wx.Rect(0, width, width, height - 2 * width)
    51         return wx.Rect(0, width, width, height - 2 * width)
    51     
    52     
    52     def GetThumbRect(self):
    53     def GetThumbRect(self):
    53         width, height = self.GetClientSize()
    54         width, height = self.GetClientSize()
    54         range_rect = self.GetRangeRect()
    55         range_rect = self.GetRangeRect()
       
    56         thumb_size = range_rect.height * THUMB_SIZE_RATIO
       
    57         thumb_range = range_rect.height - thumb_size
       
    58         thumb_center_position = (thumb_size + (self.ThumbPosition + 1) * thumb_range) / 2.
       
    59         thumb_start = int(thumb_center_position - thumb_size / 2.)
       
    60         thumb_end = int(thumb_center_position + thumb_size / 2.)
       
    61         return wx.Rect(1, range_rect.y + thumb_start, width - 1, thumb_end - thumb_start)
       
    62     
       
    63     def RefreshThumbPosition(self, thumb_position=None):
       
    64         if thumb_position is None:
       
    65             thumb_position = self.ThumbPosition
    55         if self.Parent.IsMessagePanelTop():
    66         if self.Parent.IsMessagePanelTop():
    56             thumb_start = 0
    67             thumb_position = max(0., thumb_position)
    57         else:
       
    58             thumb_start = int(float(self.ThumbPosition * range_rect.height) / len(SPEED_VALUES))
       
    59         if self.Parent.IsMessagePanelBottom():
    68         if self.Parent.IsMessagePanelBottom():
    60             thumb_end = range_rect.height
    69             thumb_position = min(0., thumb_position)
    61         else:
    70         if thumb_position != self.ThumbPosition:
    62             thumb_end = int(float((self.ThumbPosition + 1) * range_rect.height) / len(SPEED_VALUES))
    71             self.ThumbPosition = thumb_position
    63         return wx.Rect(1, range_rect.y + thumb_start, width - 1, thumb_end - thumb_start)
    72             self.Parent.SetScrollSpeed(self.ThumbPosition)
       
    73         self.Refresh()
    64     
    74     
    65     def OnLeftDown(self, event):
    75     def OnLeftDown(self, event):
    66         self.CaptureMouse()
    76         self.CaptureMouse()
    67         posx, posy = event.GetPosition()
    77         posx, posy = event.GetPosition()
    68         width, height = self.GetClientSize()
    78         width, height = self.GetClientSize()
    69         range_rect = self.GetRangeRect()
    79         range_rect = self.GetRangeRect()
    70         thumb_rect = self.GetThumbRect()
    80         thumb_rect = self.GetThumbRect()
    71         if range_rect.InsideXY(posx, posy):
    81         if range_rect.InsideXY(posx, posy):
    72             if thumb_rect.InsideXY(posx, posy):
    82             if thumb_rect.InsideXY(posx, posy):
    73                 self.ThumbScrolling = True
    83                 self.ThumbScrollingStartPos = wx.Point(posx, posy)
    74             elif posy < thumb_rect.y:
    84             elif posy < thumb_rect.y:
    75                 self.Parent.ScrollPageUp()
    85                 self.Parent.ScrollToLast()
    76             elif posy > thumb_rect.y + thumb_rect.height:
    86             elif posy > thumb_rect.y + thumb_rect.height:
    77                 self.Parent.ScrollPageDown()
    87                 self.Parent.ScrollToFirst()
    78         elif posy < width:
    88         elif posy < width:
    79             self.Parent.SetScrollSpeed(1)
    89             pass
    80         elif posy > height - width:
    90         elif posy > height - width:
    81             self.Parent.SetScrollSpeed(-1)
    91             pass
    82         event.Skip()
    92         event.Skip()
    83         
    93         
    84     def OnLeftUp(self, event):
    94     def OnLeftUp(self, event):
    85         self.ThumbScrolling = False
    95         self.ThumbScrollingStartPos = None
    86         self.ThumbPosition = SPEED_VALUES.index(0)
    96         self.RefreshThumbPosition(0.)
    87         self.Parent.SetScrollSpeed(SPEED_VALUES[self.ThumbPosition])
       
    88         self.Refresh()
       
    89         if self.HasCapture():
    97         if self.HasCapture():
    90             self.ReleaseMouse()
    98             self.ReleaseMouse()
    91         event.Skip()
    99         event.Skip()
    92         
   100         
    93     def OnMotion(self, event):
   101     def OnMotion(self, event):
    94         if event.Dragging() and self.ThumbScrolling:
   102         if event.Dragging() and self.ThumbScrollingStartPos is not None:
    95             posx, posy = event.GetPosition()
   103             posx, posy = event.GetPosition()
    96             width, height = self.GetClientSize()
   104             width, height = self.GetClientSize()
    97             range_rect = self.GetRangeRect()
   105             range_rect = self.GetRangeRect()
    98             if range_rect.InsideXY(posx, posy):
   106             thumb_size = range_rect.height * THUMB_SIZE_RATIO
    99                 new_thumb_position = int(float(posy - range_rect.y) * len(SPEED_VALUES) / range_rect.height)
   107             thumb_range = range_rect.height - thumb_size
   100                 thumb_rect = self.GetThumbRect()
   108             self.RefreshThumbPosition(
   101                 if self.ThumbPosition == SPEED_VALUES.index(0):
   109                 max(-1., min((posy - self.ThumbScrollingStartPos.y) * 2. / thumb_range, 1.)))
   102                     if thumb_rect.y == width:
       
   103                         new_thumb_position = max(new_thumb_position, SPEED_VALUES.index(0))
       
   104                     if thumb_rect.y + thumb_rect.height == height - width:
       
   105                         new_thumb_position = min(new_thumb_position, SPEED_VALUES.index(0))
       
   106                 if new_thumb_position != self.ThumbPosition:
       
   107                     self.ThumbPosition = new_thumb_position
       
   108                     self.Parent.SetScrollSpeed(SPEED_VALUES[new_thumb_position])
       
   109                     self.Refresh()
       
   110         event.Skip()
   110         event.Skip()
   111     
   111     
   112     def OnResize(self, event):
   112     def OnResize(self, event):
   113         self.Refresh()
   113         self.Refresh()
   114         event.Skip()
   114         event.Skip()
   116     def OnPaint(self, event):
   116     def OnPaint(self, event):
   117         dc = wx.BufferedPaintDC(self)
   117         dc = wx.BufferedPaintDC(self)
   118         dc.Clear()
   118         dc.Clear()
   119         dc.BeginDrawing()
   119         dc.BeginDrawing()
   120         
   120         
       
   121         width, height = self.GetClientSize()
       
   122         
       
   123         thumb_rect = self.GetThumbRect()
       
   124         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
       
   125                                  thumb_rect.width, thumb_rect.height)
       
   126         if self.Parent.IsMessagePanelTop():
       
   127             exclusion_rect.y, exclusion_rect.height = width, exclusion_rect.y + exclusion_rect.height - width
       
   128         if self.Parent.IsMessagePanelBottom():
       
   129             exclusion_rect.height = height - width - exclusion_rect.y
       
   130         if exclusion_rect != thumb_rect:
       
   131             colour = wx.NamedColour("LIGHT GREY")
       
   132             dc.SetPen(wx.Pen(colour))
       
   133             dc.SetBrush(wx.Brush(colour))
       
   134         
       
   135             dc.DrawRectangle(exclusion_rect.x, exclusion_rect.y, 
       
   136                              exclusion_rect.width, exclusion_rect.height)
       
   137         
   121         dc.SetPen(wx.GREY_PEN)
   138         dc.SetPen(wx.GREY_PEN)
   122         dc.SetBrush(wx.GREY_BRUSH)
   139         dc.SetBrush(wx.GREY_BRUSH)
   123         
       
   124         width, height = self.GetClientSize()
       
   125         
   140         
   126         dc.DrawPolygon([wx.Point(width / 2, 1),
   141         dc.DrawPolygon([wx.Point(width / 2, 1),
   127                         wx.Point(1, width - 2),
   142                         wx.Point(1, width - 2),
   128                         wx.Point(width - 1, width - 2)])
   143                         wx.Point(width - 1, width - 2)])
   129         
   144         
   130         dc.DrawPolygon([wx.Point(width / 2, height - 1),
   145         dc.DrawPolygon([wx.Point(width / 2, height - 1),
   131                         wx.Point(2, height - width + 1),
   146                         wx.Point(2, height - width + 1),
   132                         wx.Point(width - 1, height - width + 1)])
   147                         wx.Point(width - 1, height - width + 1)])
   133         
   148             
   134         thumb_rect = self.GetThumbRect()
       
   135         dc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
   149         dc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
   136                          thumb_rect.width, thumb_rect.height)
   150                          thumb_rect.width, thumb_rect.height)
   137         
   151         
   138         dc.EndDrawing()
   152         dc.EndDrawing()
   139         event.Skip()
   153         event.Skip()
   211         for level in levels:
   225         for level in levels:
   212             self.MessageFilter.Append(_(level))
   226             self.MessageFilter.Append(_(level))
   213         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
   227         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
   214         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.GROW)
   228         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.GROW)
   215         
   229         
   216         self.SearchMessage = wx.SearchCtrl(self)
   230         self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
   217         self.SearchMessage.ShowSearchButton(True)
   231         self.SearchMessage.ShowSearchButton(True)
   218         self.Bind(wx.EVT_TEXT, self.OnSearchMessageChanged, self.SearchMessage)
   232         self.SearchMessage.ShowCancelButton(True)
       
   233         self.Bind(wx.EVT_TEXT_ENTER, self.OnSearchMessageChanged, self.SearchMessage)
   219         self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
   234         self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
   220               self.OnSearchMessageButtonClick, self.SearchMessage)
   235               self.OnSearchMessageSearchButtonClick, self.SearchMessage)
       
   236         self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, 
       
   237               self.OnSearchMessageCancelButtonClick, self.SearchMessage)
   221         filter_sizer.AddWindow(self.SearchMessage, 3, flag=wx.GROW)
   238         filter_sizer.AddWindow(self.SearchMessage, 3, flag=wx.GROW)
   222         
   239         
   223         message_panel_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
   240         message_panel_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
   224         message_panel_sizer.AddGrowableCol(1)
   241         message_panel_sizer.AddGrowableCol(1)
   225         message_panel_sizer.AddGrowableRow(0)
   242         message_panel_sizer.AddGrowableRow(0)
   226         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
   243         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
   227         
   244         
   228         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
   245         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
   229         for label, callback in [(_("First"), self.OnFirstButton)] + \
   246         for label, callback in [("+" + text, self.GenerateOnDurationButton(duration)) 
   230                                [("+" + text, self.GenerateOnDurationButton(duration)) 
       
   231                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS] +\
   247                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS] +\
   232                                [("-" + text, self.GenerateOnDurationButton(-duration)) 
   248                                [("-" + text, self.GenerateOnDurationButton(-duration)) 
   233                                 for text, duration in REVERSE_CHANGE_TIMESTAMP_BUTTONS] + \
   249                                 for text, duration in REVERSE_CHANGE_TIMESTAMP_BUTTONS]:
   234                                [(_("Last"), self.OnLastButton)]:
       
   235             button = wx.Button(self, label=label)
   250             button = wx.Button(self, label=label)
   236             self.Bind(wx.EVT_BUTTON, callback, button)
   251             self.Bind(wx.EVT_BUTTON, callback, button)
   237             buttons_sizer.AddWindow(button, 1, wx.ALIGN_CENTER_VERTICAL)
   252             buttons_sizer.AddWindow(button, 1, wx.ALIGN_CENTER_VERTICAL)
   238         message_panel_sizer.AddSizer(buttons_sizer, flag=wx.GROW)
   253         message_panel_sizer.AddSizer(buttons_sizer, flag=wx.GROW)
   239         
   254         
   240         self.MessagePanel = wx.Panel(self)
   255         self.MessagePanel = wx.Panel(self)
   241         if wx.Platform == '__WXMSW__':
   256         if wx.Platform == '__WXMSW__':
   242             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
   257             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
   243         else:
   258         else:
   244             self.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')    
   259             self.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')    
       
   260         self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
   245         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
   261         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
   246         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
   262         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
   247         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
   263         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
   248         
   264         
   249         self.MessageScrollBar = MyScrollBar(self, wx.Size(16, -1))
   265         self.MessageScrollBar = MyScrollBar(self, wx.Size(16, -1))
   257         self.ParentWindow = window
   273         self.ParentWindow = window
   258     
   274     
   259         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
   275         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
   260         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
   276         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
   261         self.CurrentFilter = self.LevelFilters[0]
   277         self.CurrentFilter = self.LevelFilters[0]
   262         
   278         self.CurrentSearchValue = ""
   263         self.ScrollSpeed = 0
   279         
       
   280         self.ScrollSpeed = 0.
       
   281         self.LastStartTime = None
   264         self.ScrollTimer = wx.Timer(self, -1)
   282         self.ScrollTimer = wx.Timer(self, -1)
   265         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
   283         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
   266     
   284     
   267     def __del__(self):
   285     def __del__(self):
   268         self.ScrollTimer.Stop()
   286         self.ScrollTimer.Stop()
   318                 self.LogMessages.append(new_message)
   336                 self.LogMessages.append(new_message)
   319             if self.CurrentMessage is None or self.CurrentMessage == old_length - 1:
   337             if self.CurrentMessage is None or self.CurrentMessage == old_length - 1:
   320                 self.CurrentMessage = len(self.LogMessages) - 1
   338                 self.CurrentMessage = len(self.LogMessages) - 1
   321             self.NewDataAvailable(None)
   339             self.NewDataAvailable(None)
   322     
   340     
   323     def GetNextMessage(self, msgidx, levels=range(4)):
   341     def FilterLogMessage(self, message):
       
   342         return message.Level in self.CurrentFilter and message.Message.find(self.CurrentSearchValue) != -1
       
   343     
       
   344     def GetNextMessage(self, msgidx):
   324         while msgidx < len(self.LogMessages) - 1:
   345         while msgidx < len(self.LogMessages) - 1:
   325             message = self.LogMessages[msgidx + 1]
   346             message = self.LogMessages[msgidx + 1]
   326             if message.Level in levels:
   347             if self.FilterLogMessage(message):
   327                 return message, msgidx + 1
   348                 return message, msgidx + 1
   328             msgidx += 1
   349             msgidx += 1
   329         return None, None
   350         return None, None
   330             
   351     
   331     def GetPreviousMessage(self, msgidx, levels=range(4)):
   352     def GetPreviousMessage(self, msgidx):
   332         message = None
   353         message = None
   333         while 0 < msgidx < len(self.LogMessages):
   354         while 0 < msgidx < len(self.LogMessages):
   334             message = self.LogMessages[msgidx - 1]
   355             message = self.LogMessages[msgidx - 1]
   335             if message.Level in levels:
   356             if self.FilterLogMessage(message):
   336                 return message, msgidx - 1
   357                 return message, msgidx - 1
   337             msgidx -= 1
   358             msgidx -= 1
   338         if len(self.LogMessages) > 0:
   359         if len(self.LogMessages) > 0:
   339             message = self.LogMessages[0]
   360             message = self.LogMessages[0]
   340             while message is not None:
   361             while message is not None:
   356                     self.LogMessages.insert(0, message)
   377                     self.LogMessages.insert(0, message)
   357                     if self.CurrentMessage is not None:
   378                     if self.CurrentMessage is not None:
   358                         self.CurrentMessage += 1
   379                         self.CurrentMessage += 1
   359                     else:
   380                     else:
   360                         self.CurrentMessage = 0
   381                         self.CurrentMessage = 0
   361                     if message.Level in levels:
   382                     if self.FilterLogMessage(message):
   362                         return message, 0
   383                         return message, 0
   363         return None, None
   384         return None, None
   364     
   385     
   365     def RefreshNewData(self, *args, **kwargs):
   386     def RefreshNewData(self, *args, **kwargs):
   366         if self.HasNewData:
   387         if self.HasNewData:
   383             offset = 5
   404             offset = 5
   384             while offset < height and message is not None:
   405             while offset < height and message is not None:
   385                 message.Draw(dc, offset, width, draw_date)
   406                 message.Draw(dc, offset, width, draw_date)
   386                 offset += message.GetHeight(draw_date)
   407                 offset += message.GetHeight(draw_date)
   387                 
   408                 
   388                 previous_message, message_idx = self.GetPreviousMessage(message_idx, self.CurrentFilter)
   409                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   389                 if previous_message is not None:
   410                 if previous_message is not None:
   390                     draw_date = message.Date != previous_message.Date
   411                     draw_date = message.Date != previous_message.Date
   391                 message = previous_message
   412                 message = previous_message
   392         
   413         
   393         dc.EndDrawing()
   414         dc.EndDrawing()
   394         
   415         
   395         self.MessageScrollBar.Refresh()
   416         self.MessageScrollBar.RefreshThumbPosition()
   396     
       
   397     def OnMessageFilterChanged(self, event):
       
   398         self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
       
   399         if len(self.LogMessages) > 0:
       
   400             self.CurrentMessage = len(self.LogMessages) - 1
       
   401             message = self.LogMessages[self.CurrentMessage]
       
   402             while message is not None and message.Level not in self.CurrentFilter:
       
   403                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage, self.CurrentFilter)
       
   404             self.RefreshView()
       
   405         event.Skip()
       
   406     
   417     
   407     def IsMessagePanelTop(self, message_idx=None):
   418     def IsMessagePanelTop(self, message_idx=None):
   408         if message_idx is None:
   419         if message_idx is None:
   409             message_idx = self.CurrentMessage
   420             message_idx = self.CurrentMessage
   410         if message_idx is not None:
   421         if message_idx is not None:
   411             return self.GetNextMessage(message_idx, self.CurrentFilter)[0] is None
   422             return self.GetNextMessage(message_idx)[0] is None
   412         return True
   423         return True
   413     
   424     
   414     def IsMessagePanelBottom(self, message_idx=None):
   425     def IsMessagePanelBottom(self, message_idx=None):
   415         if message_idx is None:
   426         if message_idx is None:
   416             message_idx = self.CurrentMessage
   427             message_idx = self.CurrentMessage
   419             offset = 5
   430             offset = 5
   420             message = self.LogMessages[message_idx]
   431             message = self.LogMessages[message_idx]
   421             draw_date = True
   432             draw_date = True
   422             while message is not None and offset < height:
   433             while message is not None and offset < height:
   423                 offset += message.GetHeight(draw_date)
   434                 offset += message.GetHeight(draw_date)
   424                 previous_message, message_idx = self.GetPreviousMessage(message_idx, self.CurrentFilter)
   435                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
   425                 if previous_message is not None:
   436                 if previous_message is not None:
   426                     draw_date = message.Date != previous_message.Date
   437                     draw_date = message.Date != previous_message.Date
   427                 message = previous_message
   438                 message = previous_message
   428             return offset < height
   439             return offset < height
   429         return True
   440         return True
   430     
   441     
   431     def ScrollMessagePanel(self, scroll):
   442     def ScrollMessagePanel(self, scroll):
   432         if self.CurrentMessage is not None:
   443         if self.CurrentMessage is not None:
   433             message = self.LogMessages[self.CurrentMessage]
   444             message = self.LogMessages[self.CurrentMessage]
   434             while scroll > 0 and message is not None:
   445             while scroll > 0 and message is not None:
   435                 message, msgidx = self.GetNextMessage(self.CurrentMessage, self.CurrentFilter)
   446                 message, msgidx = self.GetNextMessage(self.CurrentMessage)
   436                 if message is not None:
   447                 if message is not None:
   437                     self.CurrentMessage = msgidx
   448                     self.CurrentMessage = msgidx
   438                     scroll -= 1
   449                     scroll -= 1
   439             while scroll < 0 and message is not None and not self.IsMessagePanelBottom():
   450             while scroll < 0 and message is not None and not self.IsMessagePanelBottom():
   440                 message, msgidx = self.GetPreviousMessage(self.CurrentMessage, self.CurrentFilter)
   451                 message, msgidx = self.GetPreviousMessage(self.CurrentMessage)
   441                 if message is not None:
   452                 if message is not None:
   442                     self.CurrentMessage = msgidx
   453                     self.CurrentMessage = msgidx
   443                     scroll += 1
   454                     scroll += 1
   444             self.RefreshView()
   455             self.RefreshView()
   445         
   456     
   446     def OnSearchMessageChanged(self, event):
   457     def ResetMessagePanel(self):
   447         event.Skip()
       
   448         
       
   449     def OnSearchMessageButtonClick(self, event):
       
   450         event.Skip()
       
   451     
       
   452     def OnFirstButton(self, event):
       
   453         if len(self.LogMessages) > 0:
   458         if len(self.LogMessages) > 0:
   454             self.CurrentMessage = len(self.LogMessages) - 1
   459             self.CurrentMessage = len(self.LogMessages) - 1
   455             message = self.LogMessages[self.CurrentMessage]
   460             message = self.LogMessages[self.CurrentMessage]
   456             if message.Level not in self.CurrentFilter:
   461             while message is not None and not self.FilterLogMessage(message):
   457                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage, self.CurrentFilter)
   462                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
   458             self.RefreshView()
   463             self.RefreshView()
   459         event.Skip()
   464     
   460         
   465     def OnMessageFilterChanged(self, event):
   461     def OnLastButton(self, event):
   466         self.CurrentFilter = self.LevelFilters[self.MessageFilter.GetSelection()]
       
   467         self.ResetMessagePanel()
       
   468         event.Skip()
       
   469     
       
   470     def OnSearchMessageChanged(self, event):
       
   471         self.CurrentSearchValue = self.SearchMessage.GetValue()
       
   472         self.ResetMessagePanel()
       
   473         event.Skip()
       
   474         
       
   475     def OnSearchMessageSearchButtonClick(self, event):
       
   476         self.CurrentSearchValue = self.SearchMessage.GetValue()
       
   477         self.ResetMessagePanel()
       
   478         event.Skip()
       
   479     
       
   480     def OnSearchMessageCancelButtonClick(self, event):
       
   481         self.CurrentSearchValue = ""
       
   482         self.SearchMessage.SetValue("")
       
   483         self.ResetMessagePanel()
       
   484         event.Skip()
       
   485     
       
   486     def GenerateOnDurationButton(self, duration):
       
   487         def OnDurationButton(event):
       
   488             event.Skip()
       
   489         return OnDurationButton
       
   490     
       
   491     def OnMessagePanelMouseWheel(self, event):
       
   492         self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta())
       
   493         event.Skip()
       
   494     
       
   495     def OnMessagePanelPaint(self, event):
       
   496         self.RefreshView()
       
   497         event.Skip()
       
   498     
       
   499     def OnMessagePanelResize(self, event):
       
   500         if self.IsMessagePanelBottom():
       
   501             self.ScrollToFirst()
       
   502         else:
       
   503             self.RefreshView()
       
   504         event.Skip()
       
   505     
       
   506     def OnScrollTimer(self, event):
       
   507         if self.ScrollSpeed != 0.:
       
   508             speed_norm = abs(self.ScrollSpeed)
       
   509             period = REFRESH_PERIOD / speed_norm
       
   510             self.ScrollMessagePanel(-speed_norm / self.ScrollSpeed)
       
   511             self.LastStartTime = gettime()
       
   512             self.ScrollTimer.Start(int(period * 1000), True)
       
   513         event.Skip()
       
   514     
       
   515     def SetScrollSpeed(self, speed):
       
   516         if speed == 0.:
       
   517             self.ScrollTimer.Stop()
       
   518         else:
       
   519             speed_norm = abs(speed)
       
   520             period = REFRESH_PERIOD / speed_norm
       
   521             current_time = gettime()
       
   522             if self.LastStartTime is not None:
       
   523                 elapsed_time = current_time - self.LastStartTime
       
   524                 if elapsed_time > period:
       
   525                     self.ScrollMessagePanel(-speed_norm / speed)
       
   526                     self.LastStartTime = current_time
       
   527                 else:
       
   528                     period -= elapsed_time
       
   529             else:
       
   530                 self.LastStartTime = current_time
       
   531             self.ScrollTimer.Start(int(period * 1000), True)
       
   532         self.ScrollSpeed = speed    
       
   533     
       
   534     def ScrollToLast(self):
       
   535         if len(self.LogMessages) > 0:
       
   536             self.CurrentMessage = len(self.LogMessages) - 1
       
   537             message = self.LogMessages[self.CurrentMessage]
       
   538             if not self.FilterLogMessage(message):
       
   539                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage)
       
   540             self.RefreshView()
       
   541 
       
   542     def ScrollToFirst(self):
   462         if len(self.LogMessages) > 0:
   543         if len(self.LogMessages) > 0:
   463             message_idx = 0
   544             message_idx = 0
   464             message = self.LogMessages[message_idx]
   545             message = self.LogMessages[message_idx]
   465             if message.Level not in self.CurrentFilter:
   546             if not self.FilterLogMessage(message):
   466                 next_message, msgidx = self.GetNextMessage(message_idx, self.CurrentFilter)
   547                 next_message, msgidx = self.GetNextMessage(message_idx)
   467                 if next_message is not None:
   548                 if next_message is not None:
   468                     message_idx = msgidx
   549                     message_idx = msgidx
   469                     message = next_message
   550                     message = next_message
   470             while message is not None:
   551             while message is not None:
   471                 message, msgidx = self.GetPreviousMessage(message_idx, self.CurrentFilter)
   552                 message, msgidx = self.GetPreviousMessage(message_idx)
   472                 if message is not None:
   553                 if message is not None:
   473                     message_idx = msgidx
   554                     message_idx = msgidx
   474             message = self.LogMessages[message_idx]
   555             message = self.LogMessages[message_idx]
   475             if message.Level in self.CurrentFilter:
   556             if self.FilterLogMessage(message):
   476                 while message is not None:
   557                 while message is not None:
   477                     message, msgidx = self.GetNextMessage(message_idx, self.CurrentFilter)
   558                     message, msgidx = self.GetNextMessage(message_idx)
   478                     if message is not None:
   559                     if message is not None:
   479                         if not self.IsMessagePanelBottom(msgidx):
   560                         if not self.IsMessagePanelBottom(msgidx):
   480                             break
   561                             break
   481                         message_idx = msgidx
   562                         message_idx = msgidx
   482                 self.CurrentMessage = message_idx
   563                 self.CurrentMessage = message_idx
   483             else:
   564             else:
   484                 self.CurrentMessage = None
   565                 self.CurrentMessage = None
   485             self.RefreshView()
   566             self.RefreshView()
   486         event.Skip()
       
   487     
       
   488     def GenerateOnDurationButton(self, duration):
       
   489         def OnDurationButton(event):
       
   490             event.Skip()
       
   491         return OnDurationButton
       
   492     
       
   493     def OnMessagePanelPaint(self, event):
       
   494         self.RefreshView()
       
   495         event.Skip()
       
   496     
       
   497     def OnMessagePanelResize(self, event):
       
   498         self.RefreshView()
       
   499         event.Skip()
       
   500     
       
   501     def OnScrollTimer(self, event):
       
   502         if self.ScrollSpeed != 0:
       
   503             speed_norm = abs(self.ScrollSpeed)
       
   504             if speed_norm <= 5:
       
   505                 self.ScrollMessagePanel(speed_norm / self.ScrollSpeed)
       
   506                 period = REFRESH_PERIOD * 5000 / speed_norm
       
   507             else:
       
   508                 self.ScrollMessagePanel(self.ScrollSpeed / 5)
       
   509                 period = REFRESH_PERIOD * 1000
       
   510             self.ScrollTimer.Start(period, True)
       
   511         event.Skip()
       
   512     
       
   513     def SetScrollSpeed(self, speed):
       
   514         if speed == 0:
       
   515             self.ScrollTimer.Stop()
       
   516         else:
       
   517             if not self.ScrollTimer.IsRunning():
       
   518                 speed_norm = abs(speed)
       
   519                 if speed_norm <= 5:
       
   520                     self.ScrollMessagePanel(speed_norm / speed)
       
   521                     period = REFRESH_PERIOD * 5000 / speed_norm
       
   522                 else:
       
   523                     period = REFRESH_PERIOD * 1000
       
   524                     self.ScrollMessagePanel(speed / 5)
       
   525                 self.ScrollTimer.Start(period, True)
       
   526         self.ScrollSpeed = speed    
       
   527     
       
   528     def ScrollPageUp(self):
       
   529         pass
       
   530 
       
   531     def ScrollPageDown(self):
       
   532         pass