Improved Log Viewer functionalities
authorLaurent Bessard
Fri, 22 Mar 2013 00:18:51 +0100
changeset 993 7fbde4a19ec3
parent 992 72ee7f3e3cf3
child 994 0401295d9804
Improved Log Viewer functionalities
controls/LogViewer.py
graphics/GraphicCommons.py
--- a/controls/LogViewer.py	Thu Mar 21 17:33:50 2013 +0100
+++ b/controls/LogViewer.py	Fri Mar 22 00:18:51 2013 +0100
@@ -28,21 +28,21 @@
 
 import wx
 
-from graphics import DebugViewer, REFRESH_PERIOD
+from graphics import DebugViewer, REFRESH_PERIOD, ToolTip, TOOLTIP_WAIT_PERIOD
 from targets.typemapping import LogLevelsCount, LogLevels
 from util.BitmapLibrary import GetBitmap
 
 THUMB_SIZE_RATIO = 1. / 8.
 
-def ArrowPoints(direction, width, height, offset):
+def ArrowPoints(direction, width, height, xoffset, yoffset):
     if direction == wx.TOP:
-        return [wx.Point(1, offset + height - 2),
-                wx.Point(width / 2, offset + 1),
-                wx.Point(width - 1, offset + height - 2)]
+        return [wx.Point(xoffset + 1, yoffset + height - 2),
+                wx.Point(xoffset + width / 2, yoffset + 1),
+                wx.Point(xoffset + width - 1, yoffset + height - 2)]
     else:
-        return [wx.Point(1, offset - height + 1),
-                wx.Point(width / 2, offset - 2),
-                wx.Point(width - 1, offset - height + 1)]
+        return [wx.Point(xoffset + 1, yoffset - height + 1),
+                wx.Point(xoffset + width / 2, yoffset - 2),
+                wx.Point(xoffset + width - 1, yoffset - height + 1)]
 
 class LogScrollBar(wx.Panel):
     
@@ -137,14 +137,14 @@
         
         width, height = self.GetClientSize()
         
-        gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 2))
+        gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
         gc.SetBrush(wx.GREY_BRUSH)
         
-        gc.DrawLines(ArrowPoints(wx.TOP, width, width * 0.75, 2 * width))
-        gc.DrawLines(ArrowPoints(wx.TOP, width, width * 0.75, 2 * width + 6))
-        
-        gc.DrawLines(ArrowPoints(wx.BOTTOM, width, width * 0.75, height - 2 * width))
-        gc.DrawLines(ArrowPoints(wx.BOTTOM, width, width * 0.75, height - 2 * width - 6))
+        gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3))
+        gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3))
+        
+        gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3))
+        gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3))
         
         thumb_rect = self.GetThumbRect()
         exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y,
@@ -164,9 +164,9 @@
         gc.SetPen(wx.GREY_PEN)
         gc.SetBrush(wx.GREY_BRUSH)
         
-        gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0))
-        
-        gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, height))
+        gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0))
+        
+        gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height))
             
         gc.DrawRectangle(thumb_rect.x, thumb_rect.y, 
                          thumb_rect.width, thumb_rect.height)
@@ -181,10 +181,6 @@
     def __init__(self, label, callback):
         self.Position = wx.Point(0, 0)
         self.Size = wx.Size(*BUTTON_SIZE)
-        if wx.Platform == '__WXMSW__':
-            self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
-        else:
-            self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
         self.Label = label
         self.Shown = True
         self.Callback = callback
@@ -216,15 +212,13 @@
         dc.DrawRectangle(self.Position.x, self.Position.y, 
                          self.Size.width, self.Size.height)
         
-        dc.SetFont(self.Font)
-        
         w, h = dc.GetTextExtent(self.Label)
         dc.DrawText(self.Label, 
             self.Position.x + (self.Size.width - w) / 2, 
             self.Position.y + (self.Size.height - h) / 2)
 
 DATE_INFO_SIZE = 10
-MESSAGE_INFO_SIZE = 30
+MESSAGE_INFO_SIZE = 25
 
 class LogMessage:
     
@@ -243,6 +237,14 @@
             return cmp(self.Seconds, other.Seconds)
         return cmp(self.Date, other.Date)
     
+    def GetFullText(self):
+        date = self.Date.replace(second=int(self.Seconds))
+        nsec = (self.Seconds % 1.) * 1e9
+        return "%s at %s.%9.9d:\n%s" % (
+            LogLevels[self.Level],
+            str(date), nsec,
+            self.Message)
+    
     def Draw(self, dc, offset, width, draw_date):
         if draw_date:
             datetime_text = self.Date.strftime("%d/%m/%y %H:%M")
@@ -257,8 +259,9 @@
         bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight()
         dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2)
         
-        mw, mh = dc.GetTextExtent(self.Message)
-        dc.DrawText(self.Message, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
+        text = self.Message.replace("\n", " ")
+        mw, mh = dc.GetTextExtent(text)
+        dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2)
         
     def GetHeight(self, draw_date):
         if draw_date:
@@ -314,11 +317,14 @@
         
         self.MessagePanel = wx.Panel(self)
         if wx.Platform == '__WXMSW__':
-            self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
+            self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
         else:
-            self.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
+            self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
         self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
+        self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp)
         self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
+        self.MessagePanel.Bind(wx.EVT_MOTION, self.OnMessagePanelMotion)
+        self.MessagePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnMessagePanelLeaveWindow)
         self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel)
         self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
@@ -354,6 +360,11 @@
         self.LastStartTime = None
         self.ScrollTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self.ScrollTimer)
+        
+        self.LastMousePos = None
+        self.MessageToolTip = None
+        self.MessageToolTipTimer = wx.Timer(self, -1)
+        self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
     
     def __del__(self):
         self.ScrollTimer.Stop()
@@ -411,6 +422,8 @@
                 self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp])
             if self.CurrentMessage is None or self.CurrentMessage == old_length - 1:
                 self.CurrentMessage = len(self.LogMessages) - 1
+                self.ResetMessageToolTip()
+                self.MessageToolTipTimer.Stop()
             self.NewDataAvailable(None)
     
     def FilterLogMessage(self, message, timestamp=None):
@@ -485,11 +498,11 @@
         
         if self.CurrentMessage is not None:
             
+            dc.SetFont(self.Font)
+            
             for button in self.LeftButtons + self.RightButtons:
                 button.Draw(dc)
             
-            dc.SetFont(self.Font)
-            
             message_idx = self.CurrentMessage
             message = self.LogMessages[message_idx]
             draw_date = True
@@ -596,18 +609,13 @@
             self.ScrollMessagePanelByTimestamp(duration)
         return OnDurationButton
     
-    def OnMessagePanelLeftUp(self, event):
+    def GetCopyMessageToClipboardFunction(self, message):
+        def CopyMessageToClipboardFunction(event):
+            self.ParentWindow.SetCopyBuffer(message.GetFullText())
+        return CopyMessageToClipboardFunction
+    
+    def GetMessageByScreenPos(self, posx, posy):
         if self.CurrentMessage is not None:
-            posx, posy = event.GetPosition()
-            for button in self.LeftButtons + self.RightButtons:
-                if button.HitTest(posx, posy):
-                    button.ProcessCallback()
-                    break
-        event.Skip()
-    
-    def OnMessagePanelLeftDCLick(self, event):
-        if self.CurrentMessage is not None:
-            posx, posy = event.GetPosition()
             width, height = self.MessagePanel.GetClientSize()
             message_idx = self.CurrentMessage
             message = self.LogMessages[message_idx]
@@ -617,19 +625,76 @@
             while offset < height and message is not None:
                 if draw_date:
                     offset += DATE_INFO_SIZE
-
+    
                 if offset <= posy < offset + MESSAGE_INFO_SIZE:
-                    self.CurrentSearchValue = message.Message
-                    self.SearchMessage.SetValue(message.Message)
-                    self.ResetMessagePanel()
-                    break
-                
+                    return message
+        
                 offset += MESSAGE_INFO_SIZE
                 
                 previous_message, message_idx = self.GetPreviousMessage(message_idx)
                 if previous_message is not None:
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
+        return None
+    
+    def OnMessagePanelLeftUp(self, event):
+        if self.CurrentMessage is not None:
+            posx, posy = event.GetPosition()
+            for button in self.LeftButtons + self.RightButtons:
+                if button.HitTest(posx, posy):
+                    button.ProcessCallback()
+                    break
+        event.Skip()
+    
+    def OnMessagePanelRightUp(self, event):
+        message = self.GetMessageByScreenPos(*event.GetPosition())
+        if message is not None:
+            menu = wx.Menu(title='')
+            
+            new_id = wx.NewId()
+            menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy"))
+            self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id)
+    
+            self.MessagePanel.PopupMenu(menu)
+            menu.Destroy()
+        event.Skip()
+    
+    def OnMessagePanelLeftDCLick(self, event):
+        message = self.GetMessageByScreenPos(*event.GetPosition())
+        if message is not None:
+            self.SearchMessage.SetFocus()
+            self.SearchMessage.SetValue(message.Message)
+        event.Skip()
+    
+    def ResetMessageToolTip(self):
+        if self.MessageToolTip is not None:
+            self.MessageToolTip.Destroy()
+            self.MessageToolTip = None
+    
+    def OnMessageToolTipTimer(self, event):
+        if self.LastMousePos is not None:
+            message = self.GetMessageByScreenPos(*self.LastMousePos)
+            if message is not None:
+                tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos)
+                tooltip_pos.x += 10
+                tooltip_pos.y += 10
+                self.MessageToolTip = ToolTip(self.MessagePanel, message.GetFullText(), False)
+                self.MessageToolTip.SetFont(self.Font)
+                self.MessageToolTip.MoveToolTip(tooltip_pos)
+                self.MessageToolTip.Show()
+        event.Skip()
+    
+    def OnMessagePanelMotion(self, event):
+        if not event.Dragging():
+            self.ResetMessageToolTip()
+            self.LastMousePos = event.GetPosition()
+            self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
+        event.Skip()
+        
+    def OnMessagePanelLeaveWindow(self, event):
+        self.ResetMessageToolTip()
+        self.LastMousePos = None
+        self.MessageToolTipTimer.Stop()
         event.Skip()
     
     def OnMessagePanelMouseWheel(self, event):
--- a/graphics/GraphicCommons.py	Thu Mar 21 17:33:50 2013 +0100
+++ b/graphics/GraphicCommons.py	Fri Mar 22 00:18:51 2013 +0100
@@ -578,20 +578,27 @@
 
 class ToolTip(wx.PopupWindow):
     
-    def __init__(self, parent, tip):
+    def __init__(self, parent, tip, restricted=True):
         wx.PopupWindow.__init__(self, parent)
         
         self.CurrentPosition = wx.Point(0, 0)
+        self.Restricted = restricted
         
         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
         self.SetTip(tip)
         
+        self.Font = wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"])
+        
         self.Bind(wx.EVT_PAINT, self.OnPaint)
-        
+    
+    def SetFont(self, font):
+        self.Font = font
+        self.RefreshTip()
+    
     def SetTip(self, tip):
         lines = []
         for line in tip.splitlines():
-            if line != "":
+            if self.Restricted and line != "":
                 words = line.split()
                 new_line = words[0]
                 for word in words[1:]:
@@ -603,7 +610,7 @@
                 lines.append(new_line)
             else:
                 lines.append(line)
-        if len(lines) > TOOLTIP_MAX_LINE:
+        if self.Restricted and len(lines) > TOOLTIP_MAX_LINE:
             self.Tip = lines[:TOOLTIP_MAX_LINE]
             if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
                 self.Tip[-1] += "..."
@@ -614,14 +621,20 @@
         wx.CallAfter(self.RefreshTip)
     
     def MoveToolTip(self, pos):
-        self.CurrentPosition = pos
+        screen_size = wx.GetDisplaySize()
+        w, h = self.GetTipExtent()
+        self.CurrentPosition = wx.Point(
+            max(0, min(pos.x, screen_size[0] - w - 4)),
+            max(0, min(pos.y, screen_size[1] - h - 4))) 
         self.SetPosition(pos)
     
     def GetTipExtent(self):
         max_width = 0
         max_height = 0
         for line in self.Tip:
-            w, h = self.GetTextExtent(line)
+            dc = wx.MemoryDC()
+            dc.SetFont(self.Font)
+            w, h = dc.GetTextExtent(line)
             max_width = max(max_width, w)
             max_height += h
         return max_width, max_height
@@ -638,7 +651,7 @@
         dc.Clear()
         dc.SetPen(MiterPen(wx.BLACK))
         dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
-        dc.SetFont(wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"]))
+        dc.SetFont(self.Font)
         dc.BeginDrawing()
         w, h = self.GetTipExtent()
         dc.DrawRectangle(0, 0, w + 4, h + 4)