controls/LogViewer.py
changeset 978 3290eff761f1
child 979 1a68113a323d
equal deleted inserted replaced
976:0ba3d9cd61e8 978:3290eff761f1
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    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
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from datetime import datetime
       
    26 
       
    27 import wx
       
    28 
       
    29 from graphics import DebugViewer, REFRESH_PERIOD
       
    30 from targets.typemapping import LogLevelsCount, LogLevels
       
    31 from util.BitmapLibrary import GetBitmap
       
    32 
       
    33 SPEED_VALUES = [10, 5, 2, 1, 0, -1, -2, -5, -10]
       
    34 
       
    35 class MyScrollBar(wx.Panel):
       
    36     
       
    37     def __init__(self, parent, size):
       
    38         wx.Panel.__init__(self, parent, size=size)
       
    39         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
       
    40         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
       
    41         self.Bind(wx.EVT_MOTION, self.OnMotion)
       
    42         self.Bind(wx.EVT_PAINT, self.OnPaint)
       
    43         self.Bind(wx.EVT_SIZE, self.OnResize)
       
    44         
       
    45         self.ThumbPosition = SPEED_VALUES.index(0)
       
    46         self.ThumbScrolling = False
       
    47     
       
    48     def GetRangeRect(self):
       
    49         width, height = self.GetClientSize()
       
    50         return wx.Rect(0, width, width, height - 2 * width)
       
    51     
       
    52     def GetThumbRect(self):
       
    53         width, height = self.GetClientSize()
       
    54         range_rect = self.GetRangeRect()
       
    55         if self.Parent.IsMessagePanelTop():
       
    56             thumb_start = 0
       
    57         else:
       
    58             thumb_start = int(float(self.ThumbPosition * range_rect.height) / len(SPEED_VALUES))
       
    59         if self.Parent.IsMessagePanelBottom():
       
    60             thumb_end = range_rect.height
       
    61         else:
       
    62             thumb_end = int(float((self.ThumbPosition + 1) * range_rect.height) / len(SPEED_VALUES))
       
    63         return wx.Rect(1, range_rect.y + thumb_start, width - 1, thumb_end - thumb_start)
       
    64     
       
    65     def OnLeftDown(self, event):
       
    66         self.CaptureMouse()
       
    67         posx, posy = event.GetPosition()
       
    68         width, height = self.GetClientSize()
       
    69         range_rect = self.GetRangeRect()
       
    70         thumb_rect = self.GetThumbRect()
       
    71         if range_rect.InsideXY(posx, posy):
       
    72             if thumb_rect.InsideXY(posx, posy):
       
    73                 self.ThumbScrolling = True
       
    74             elif posy < thumb_rect.y:
       
    75                 self.Parent.ScrollPageUp()
       
    76             elif posy > thumb_rect.y + thumb_rect.height:
       
    77                 self.Parent.ScrollPageDown()
       
    78         elif posy < width:
       
    79             self.Parent.SetScrollSpeed(1)
       
    80         elif posy > height - width:
       
    81             self.Parent.SetScrollSpeed(-1)
       
    82         event.Skip()
       
    83         
       
    84     def OnLeftUp(self, event):
       
    85         self.ThumbScrolling = False
       
    86         self.ThumbPosition = SPEED_VALUES.index(0)
       
    87         self.Parent.SetScrollSpeed(SPEED_VALUES[self.ThumbPosition])
       
    88         self.Refresh()
       
    89         if self.HasCapture():
       
    90             self.ReleaseMouse()
       
    91         event.Skip()
       
    92         
       
    93     def OnMotion(self, event):
       
    94         if event.Dragging() and self.ThumbScrolling:
       
    95             posx, posy = event.GetPosition()
       
    96             width, height = self.GetClientSize()
       
    97             range_rect = self.GetRangeRect()
       
    98             if range_rect.InsideXY(posx, posy):
       
    99                 new_thumb_position = int(float(posy - range_rect.y) * len(SPEED_VALUES) / range_rect.height)
       
   100                 thumb_rect = self.GetThumbRect()
       
   101                 if self.ThumbPosition == SPEED_VALUES.index(0):
       
   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()
       
   111     
       
   112     def OnResize(self, event):
       
   113         self.Refresh()
       
   114         event.Skip()
       
   115     
       
   116     def OnPaint(self, event):
       
   117         dc = wx.BufferedPaintDC(self)
       
   118         dc.Clear()
       
   119         dc.BeginDrawing()
       
   120         
       
   121         dc.SetPen(wx.GREY_PEN)
       
   122         dc.SetBrush(wx.GREY_BRUSH)
       
   123         
       
   124         width, height = self.GetClientSize()
       
   125         
       
   126         dc.DrawPolygon([wx.Point(width / 2, 1),
       
   127                         wx.Point(1, width - 2),
       
   128                         wx.Point(width - 1, width - 2)])
       
   129         
       
   130         dc.DrawPolygon([wx.Point(width / 2, height - 1),
       
   131                         wx.Point(2, height - width + 1),
       
   132                         wx.Point(width - 1, height - width + 1)])
       
   133         
       
   134         thumb_rect = self.GetThumbRect()
       
   135         dc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
       
   136                          thumb_rect.width, thumb_rect.height)
       
   137         
       
   138         dc.EndDrawing()
       
   139         event.Skip()
       
   140 
       
   141 DATE_INFO_SIZE = 10
       
   142 MESSAGE_INFO_SIZE = 30
       
   143 
       
   144 class LogMessage:
       
   145     
       
   146     def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg):
       
   147         self.Date = datetime.fromtimestamp(tv_sec)
       
   148         self.Seconds = self.Date.second + tv_nsec * 1e-9
       
   149         self.Date = self.Date.replace(second=0)
       
   150         self.Level = level
       
   151         self.LevelBitmap = level_bitmap
       
   152         self.Message = msg
       
   153         self.DrawDate = True
       
   154     
       
   155     def __cmp__(self, other):
       
   156         if self.Date == other.Date:
       
   157             return cmp(self.Seconds, other.Seconds)
       
   158         return cmp(self.Date, other.Date)
       
   159     
       
   160     def Draw(self, dc, offset, width, draw_date):
       
   161         if draw_date:
       
   162             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
       
   163             dw, dh = dc.GetTextExtent(datetime_text)
       
   164             dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2)
       
   165             offset += DATE_INFO_SIZE
       
   166         
       
   167         seconds_text = "%12.9f" % self.Seconds
       
   168         sw, sh = dc.GetTextExtent(seconds_text)
       
   169         dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2)
       
   170         
       
   171         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
       
   172         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
       
   173         
       
   174         mw, mh = dc.GetTextExtent(self.Message)
       
   175         dc.DrawText(self.Message, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
       
   176         
       
   177     def GetHeight(self, draw_date):
       
   178         if draw_date:
       
   179             return DATE_INFO_SIZE + MESSAGE_INFO_SIZE
       
   180         return MESSAGE_INFO_SIZE
       
   181 
       
   182 SECOND = 1
       
   183 MINUTE = 60 * SECOND
       
   184 HOUR = 60 * MINUTE
       
   185 DAY = 24 * HOUR
       
   186 
       
   187 CHANGE_TIMESTAMP_BUTTONS = [(_("1d"), DAY),
       
   188                             (_("1h"), HOUR),
       
   189                             (_("1m"), MINUTE),
       
   190                             (_("1s"), SECOND)]
       
   191 REVERSE_CHANGE_TIMESTAMP_BUTTONS = CHANGE_TIMESTAMP_BUTTONS[:]
       
   192 REVERSE_CHANGE_TIMESTAMP_BUTTONS.reverse()
       
   193 
       
   194 class LogViewer(DebugViewer, wx.Panel):
       
   195     
       
   196     def __init__(self, parent, window):
       
   197         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
       
   198         DebugViewer.__init__(self, None, False, False)
       
   199         
       
   200         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
       
   201         main_sizer.AddGrowableCol(0)
       
   202         main_sizer.AddGrowableRow(1)
       
   203         
       
   204         filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
       
   205         main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP|wx.LEFT|wx.RIGHT|wx.GROW)
       
   206         
       
   207         self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
       
   208         self.MessageFilter.Append(_("All"))
       
   209         levels = LogLevels[:3]
       
   210         levels.reverse()
       
   211         for level in levels:
       
   212             self.MessageFilter.Append(_(level))
       
   213         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
       
   214         filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT|wx.GROW)
       
   215         
       
   216         self.SearchMessage = wx.SearchCtrl(self)
       
   217         self.SearchMessage.ShowSearchButton(True)
       
   218         self.Bind(wx.EVT_TEXT, self.OnSearchMessageChanged, self.SearchMessage)
       
   219         self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, 
       
   220               self.OnSearchMessageButtonClick, self.SearchMessage)
       
   221         filter_sizer.AddWindow(self.SearchMessage, 3, flag=wx.GROW)
       
   222         
       
   223         message_panel_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
       
   224         message_panel_sizer.AddGrowableCol(1)
       
   225         message_panel_sizer.AddGrowableRow(0)
       
   226         main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW)
       
   227         
       
   228         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
       
   229         for label, callback in [(_("First"), self.OnFirstButton)] + \
       
   230                                [("+" + text, self.GenerateOnDurationButton(duration)) 
       
   231                                 for text, duration in CHANGE_TIMESTAMP_BUTTONS] +\
       
   232                                [("-" + text, self.GenerateOnDurationButton(-duration)) 
       
   233                                 for text, duration in REVERSE_CHANGE_TIMESTAMP_BUTTONS] + \
       
   234                                [(_("Last"), self.OnLastButton)]:
       
   235             button = wx.Button(self, label=label)
       
   236             self.Bind(wx.EVT_BUTTON, callback, button)
       
   237             buttons_sizer.AddWindow(button, 1, wx.ALIGN_CENTER_VERTICAL)
       
   238         message_panel_sizer.AddSizer(buttons_sizer, flag=wx.GROW)
       
   239         
       
   240         self.MessagePanel = wx.Panel(self)
       
   241         if wx.Platform == '__WXMSW__':
       
   242             self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
       
   243         else:
       
   244             self.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')    
       
   245         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
       
   246         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
       
   247         message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
       
   248         
       
   249         self.MessageScrollBar = MyScrollBar(self, wx.Size(16, -1))
       
   250         message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
       
   251         
       
   252         self.SetSizer(main_sizer)
       
   253     
       
   254         self.MessageFilter.SetSelection(0)
       
   255         self.LogSource = None
       
   256         self.ResetLogMessages()
       
   257         self.ParentWindow = window
       
   258     
       
   259         self.LevelIcons = [GetBitmap(level) for level in LogLevels]
       
   260         self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
       
   261         self.CurrentFilter = self.LevelFilters[0]
       
   262         
       
   263         self.ScrollSpeed = 0
       
   264         self.ScrollTimer = wx.Timer(self, -1)
       
   265         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
       
   266     
       
   267     def __del__(self):
       
   268         self.ScrollTimer.Stop()
       
   269     
       
   270     def ResetLogMessages(self):
       
   271         self.previous_log_count = [None]*LogLevelsCount
       
   272         self.OldestMessages = []
       
   273         self.LogMessages = []
       
   274         self.CurrentMessage = None
       
   275         self.HasNewData = False
       
   276     
       
   277     def SetLogSource(self, log_source):
       
   278         self.LogSource = log_source
       
   279         if log_source is not None:
       
   280             self.ResetLogMessages()
       
   281             self.RefreshView()
       
   282     
       
   283     def GetLogMessageFromSource(self, msgidx, level):
       
   284         if self.LogSource is not None:
       
   285             answer = self.LogSource.GetLogMessage(level, msgidx)
       
   286             if answer is not None:
       
   287                 msg, tick, tv_sec, tv_nsec = answer
       
   288                 return LogMessage(tv_sec, tv_nsec, level, self.LevelIcons[level], msg)
       
   289         return None
       
   290     
       
   291     def SetLogCounters(self, log_count):
       
   292         new_messages = []
       
   293         for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
       
   294             if count is not None and prev != count:
       
   295                 if prev is None:
       
   296                     dump_end = count - 2
       
   297                 else:
       
   298                     dump_end = prev - 1
       
   299                 for msgidx in xrange(count-1, dump_end,-1):
       
   300                     new_message = self.GetLogMessageFromSource(msgidx, level)
       
   301                     if new_message is not None:
       
   302                         if prev is None:
       
   303                             self.OldestMessages.append((msgidx, new_message))
       
   304                             if len(new_messages) == 0 or new_message > new_messages[0]:
       
   305                                 new_messages = [new_message]
       
   306                         else:
       
   307                             new_messages.insert(0, new_message)
       
   308                     else:
       
   309                         if prev is None:
       
   310                             self.OldestMessages.append((-1, None))
       
   311                         break
       
   312                 self.previous_log_count[level] = count
       
   313         new_messages.sort()
       
   314         if len(new_messages) > 0:
       
   315             self.HasNewData = True
       
   316             old_length = len(self.LogMessages)
       
   317             for new_message in new_messages:
       
   318                 self.LogMessages.append(new_message)
       
   319             if self.CurrentMessage is None or self.CurrentMessage == old_length - 1:
       
   320                 self.CurrentMessage = len(self.LogMessages) - 1
       
   321             self.NewDataAvailable(None)
       
   322     
       
   323     def GetNextMessage(self, msgidx, levels=range(4)):
       
   324         while msgidx < len(self.LogMessages) - 1:
       
   325             message = self.LogMessages[msgidx + 1]
       
   326             if message.Level in levels:
       
   327                 return message, msgidx + 1
       
   328             msgidx += 1
       
   329         return None, None
       
   330             
       
   331     def GetPreviousMessage(self, msgidx, levels=range(4)):
       
   332         message = None
       
   333         while 0 < msgidx < len(self.LogMessages):
       
   334             message = self.LogMessages[msgidx - 1]
       
   335             if message.Level in levels:
       
   336                 return message, msgidx - 1
       
   337             msgidx -= 1
       
   338         if len(self.LogMessages) > 0:
       
   339             message = self.LogMessages[0]
       
   340             while message is not None:
       
   341                 level = message.Level
       
   342                 oldest_msgidx, oldest_message = self.OldestMessages[level]
       
   343                 if oldest_msgidx > 0:
       
   344                     old_message = self.GetLogMessageFromSource(oldest_msgidx - 1, level)
       
   345                     if old_message is not None:
       
   346                         self.OldestMessages[level] = (oldest_msgidx - 1, old_message)
       
   347                     else:
       
   348                         self.OldestMessages[level] = (-1, None)
       
   349                 else:
       
   350                     self.OldestMessages[level] = (-1, None)
       
   351                 message = None
       
   352                 for idx, msg in self.OldestMessages:
       
   353                     if msg is not None and (message is None or msg > message):
       
   354                         message = msg
       
   355                 if message is not None:
       
   356                     self.LogMessages.insert(0, message)
       
   357                     if self.CurrentMessage is not None:
       
   358                         self.CurrentMessage += 1
       
   359                     else:
       
   360                         self.CurrentMessage = 0
       
   361                     if message.Level in levels:
       
   362                         return message, 0
       
   363         return None, None
       
   364     
       
   365     def RefreshNewData(self, *args, **kwargs):
       
   366         if self.HasNewData:
       
   367             self.HasNewData = False
       
   368             self.RefreshView()
       
   369         DebugViewer.RefreshNewData(self, *args, **kwargs)
       
   370     
       
   371     def RefreshView(self):
       
   372         width, height = self.MessagePanel.GetClientSize()
       
   373         bitmap = wx.EmptyBitmap(width, height)
       
   374         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
       
   375         dc.Clear()
       
   376         dc.SetFont(self.Font)
       
   377         dc.BeginDrawing()
       
   378         
       
   379         if self.CurrentMessage is not None:
       
   380             message_idx = self.CurrentMessage
       
   381             message = self.LogMessages[message_idx]
       
   382             draw_date = True
       
   383             offset = 5
       
   384             while offset < height and message is not None:
       
   385                 message.Draw(dc, offset, width, draw_date)
       
   386                 offset += message.GetHeight(draw_date)
       
   387                 
       
   388                 previous_message, message_idx = self.GetPreviousMessage(message_idx, self.CurrentFilter)
       
   389                 if previous_message is not None:
       
   390                     draw_date = message.Date != previous_message.Date
       
   391                 message = previous_message
       
   392         
       
   393         dc.EndDrawing()
       
   394         
       
   395         self.MessageScrollBar.Refresh()
       
   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     
       
   407     def IsMessagePanelTop(self, message_idx=None):
       
   408         if message_idx is None:
       
   409             message_idx = self.CurrentMessage
       
   410         if message_idx is not None:
       
   411             return self.GetNextMessage(message_idx, self.CurrentFilter)[0] is None
       
   412         return True
       
   413     
       
   414     def IsMessagePanelBottom(self, message_idx=None):
       
   415         if message_idx is None:
       
   416             message_idx = self.CurrentMessage
       
   417         if message_idx is not None:
       
   418             width, height = self.MessagePanel.GetClientSize()
       
   419             offset = 5
       
   420             message = self.LogMessages[message_idx]
       
   421             draw_date = True
       
   422             while message is not None and offset < height:
       
   423                 offset += message.GetHeight(draw_date)
       
   424                 previous_message, message_idx = self.GetPreviousMessage(message_idx, self.CurrentFilter)
       
   425                 if previous_message is not None:
       
   426                     draw_date = message.Date != previous_message.Date
       
   427                 message = previous_message
       
   428             return offset < height
       
   429         return True
       
   430     
       
   431     def ScrollMessagePanel(self, scroll):
       
   432         if self.CurrentMessage is not None:
       
   433             message = self.LogMessages[self.CurrentMessage]
       
   434             while scroll > 0 and message is not None:
       
   435                 message, msgidx = self.GetNextMessage(self.CurrentMessage, self.CurrentFilter)
       
   436                 if message is not None:
       
   437                     self.CurrentMessage = msgidx
       
   438                     scroll -= 1
       
   439             while scroll < 0 and message is not None and not self.IsMessagePanelBottom():
       
   440                 message, msgidx = self.GetPreviousMessage(self.CurrentMessage, self.CurrentFilter)
       
   441                 if message is not None:
       
   442                     self.CurrentMessage = msgidx
       
   443                     scroll += 1
       
   444             self.RefreshView()
       
   445         
       
   446     def OnSearchMessageChanged(self, event):
       
   447         event.Skip()
       
   448         
       
   449     def OnSearchMessageButtonClick(self, event):
       
   450         event.Skip()
       
   451     
       
   452     def OnFirstButton(self, event):
       
   453         if len(self.LogMessages) > 0:
       
   454             self.CurrentMessage = len(self.LogMessages) - 1
       
   455             message = self.LogMessages[self.CurrentMessage]
       
   456             if message.Level not in self.CurrentFilter:
       
   457                 message, self.CurrentMessage = self.GetPreviousMessage(self.CurrentMessage, self.CurrentFilter)
       
   458             self.RefreshView()
       
   459         event.Skip()
       
   460         
       
   461     def OnLastButton(self, event):
       
   462         if len(self.LogMessages) > 0:
       
   463             message_idx = 0
       
   464             message = self.LogMessages[message_idx]
       
   465             if message.Level not in self.CurrentFilter:
       
   466                 next_message, msgidx = self.GetNextMessage(message_idx, self.CurrentFilter)
       
   467                 if next_message is not None:
       
   468                     message_idx = msgidx
       
   469                     message = next_message
       
   470             while message is not None:
       
   471                 message, msgidx = self.GetPreviousMessage(message_idx, self.CurrentFilter)
       
   472                 if message is not None:
       
   473                     message_idx = msgidx
       
   474             message = self.LogMessages[message_idx]
       
   475             if message.Level in self.CurrentFilter:
       
   476                 while message is not None:
       
   477                     message, msgidx = self.GetNextMessage(message_idx, self.CurrentFilter)
       
   478                     if message is not None:
       
   479                         if not self.IsMessagePanelBottom(msgidx):
       
   480                             break
       
   481                         message_idx = msgidx
       
   482                 self.CurrentMessage = message_idx
       
   483             else:
       
   484                 self.CurrentMessage = None
       
   485             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