# HG changeset patch # User Laurent Bessard # Date 1363907931 -3600 # Node ID 7fbde4a19ec31ed35955b3b011447f82de452c14 # Parent 72ee7f3e3cf3083f35c141df759b65cb0ca3a9fe Improved Log Viewer functionalities diff -r 72ee7f3e3cf3 -r 7fbde4a19ec3 controls/LogViewer.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): diff -r 72ee7f3e3cf3 -r 7fbde4a19ec3 graphics/GraphicCommons.py --- 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)