26 from time import time as gettime |
26 from time import time as gettime |
27 import numpy |
27 import numpy |
28 |
28 |
29 import wx |
29 import wx |
30 |
30 |
31 from graphics import DebugViewer, REFRESH_PERIOD |
31 from graphics import DebugViewer, REFRESH_PERIOD, ToolTip, TOOLTIP_WAIT_PERIOD |
32 from targets.typemapping import LogLevelsCount, LogLevels |
32 from targets.typemapping import LogLevelsCount, LogLevels |
33 from util.BitmapLibrary import GetBitmap |
33 from util.BitmapLibrary import GetBitmap |
34 |
34 |
35 THUMB_SIZE_RATIO = 1. / 8. |
35 THUMB_SIZE_RATIO = 1. / 8. |
36 |
36 |
37 def ArrowPoints(direction, width, height, offset): |
37 def ArrowPoints(direction, width, height, xoffset, yoffset): |
38 if direction == wx.TOP: |
38 if direction == wx.TOP: |
39 return [wx.Point(1, offset + height - 2), |
39 return [wx.Point(xoffset + 1, yoffset + height - 2), |
40 wx.Point(width / 2, offset + 1), |
40 wx.Point(xoffset + width / 2, yoffset + 1), |
41 wx.Point(width - 1, offset + height - 2)] |
41 wx.Point(xoffset + width - 1, yoffset + height - 2)] |
42 else: |
42 else: |
43 return [wx.Point(1, offset - height + 1), |
43 return [wx.Point(xoffset + 1, yoffset - height + 1), |
44 wx.Point(width / 2, offset - 2), |
44 wx.Point(xoffset + width / 2, yoffset - 2), |
45 wx.Point(width - 1, offset - height + 1)] |
45 wx.Point(xoffset + width - 1, yoffset - height + 1)] |
46 |
46 |
47 class LogScrollBar(wx.Panel): |
47 class LogScrollBar(wx.Panel): |
48 |
48 |
49 def __init__(self, parent, size): |
49 def __init__(self, parent, size): |
50 wx.Panel.__init__(self, parent, size=size) |
50 wx.Panel.__init__(self, parent, size=size) |
135 |
135 |
136 gc = wx.GCDC(dc) |
136 gc = wx.GCDC(dc) |
137 |
137 |
138 width, height = self.GetClientSize() |
138 width, height = self.GetClientSize() |
139 |
139 |
140 gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 2)) |
140 gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3)) |
141 gc.SetBrush(wx.GREY_BRUSH) |
141 gc.SetBrush(wx.GREY_BRUSH) |
142 |
142 |
143 gc.DrawLines(ArrowPoints(wx.TOP, width, width * 0.75, 2 * width)) |
143 gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 - 3)) |
144 gc.DrawLines(ArrowPoints(wx.TOP, width, width * 0.75, 2 * width + 6)) |
144 gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) / 4 + 3)) |
145 |
145 |
146 gc.DrawLines(ArrowPoints(wx.BOTTOM, width, width * 0.75, height - 2 * width)) |
146 gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 + 3)) |
147 gc.DrawLines(ArrowPoints(wx.BOTTOM, width, width * 0.75, height - 2 * width - 6)) |
147 gc.DrawLines(ArrowPoints(wx.BOTTOM, width * 0.75, width * 0.5, 2, (height * 3 - width) / 4 - 3)) |
148 |
148 |
149 thumb_rect = self.GetThumbRect() |
149 thumb_rect = self.GetThumbRect() |
150 exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y, |
150 exclusion_rect = wx.Rect(thumb_rect.x, thumb_rect.y, |
151 thumb_rect.width, thumb_rect.height) |
151 thumb_rect.width, thumb_rect.height) |
152 if self.Parent.IsMessagePanelTop(): |
152 if self.Parent.IsMessagePanelTop(): |
162 exclusion_rect.width, exclusion_rect.height) |
162 exclusion_rect.width, exclusion_rect.height) |
163 |
163 |
164 gc.SetPen(wx.GREY_PEN) |
164 gc.SetPen(wx.GREY_PEN) |
165 gc.SetBrush(wx.GREY_BRUSH) |
165 gc.SetBrush(wx.GREY_BRUSH) |
166 |
166 |
167 gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0)) |
167 gc.DrawPolygon(ArrowPoints(wx.TOP, width, width, 0, 0)) |
168 |
168 |
169 gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, height)) |
169 gc.DrawPolygon(ArrowPoints(wx.BOTTOM, width, width, 0, height)) |
170 |
170 |
171 gc.DrawRectangle(thumb_rect.x, thumb_rect.y, |
171 gc.DrawRectangle(thumb_rect.x, thumb_rect.y, |
172 thumb_rect.width, thumb_rect.height) |
172 thumb_rect.width, thumb_rect.height) |
173 |
173 |
174 dc.EndDrawing() |
174 dc.EndDrawing() |
214 dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY"))) |
210 dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY"))) |
215 |
211 |
216 dc.DrawRectangle(self.Position.x, self.Position.y, |
212 dc.DrawRectangle(self.Position.x, self.Position.y, |
217 self.Size.width, self.Size.height) |
213 self.Size.width, self.Size.height) |
218 |
214 |
219 dc.SetFont(self.Font) |
|
220 |
|
221 w, h = dc.GetTextExtent(self.Label) |
215 w, h = dc.GetTextExtent(self.Label) |
222 dc.DrawText(self.Label, |
216 dc.DrawText(self.Label, |
223 self.Position.x + (self.Size.width - w) / 2, |
217 self.Position.x + (self.Size.width - w) / 2, |
224 self.Position.y + (self.Size.height - h) / 2) |
218 self.Position.y + (self.Size.height - h) / 2) |
225 |
219 |
226 DATE_INFO_SIZE = 10 |
220 DATE_INFO_SIZE = 10 |
227 MESSAGE_INFO_SIZE = 30 |
221 MESSAGE_INFO_SIZE = 25 |
228 |
222 |
229 class LogMessage: |
223 class LogMessage: |
230 |
224 |
231 def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg): |
225 def __init__(self, tv_sec, tv_nsec, level, level_bitmap, msg): |
232 self.Date = datetime.fromtimestamp(tv_sec) |
226 self.Date = datetime.fromtimestamp(tv_sec) |
241 def __cmp__(self, other): |
235 def __cmp__(self, other): |
242 if self.Date == other.Date: |
236 if self.Date == other.Date: |
243 return cmp(self.Seconds, other.Seconds) |
237 return cmp(self.Seconds, other.Seconds) |
244 return cmp(self.Date, other.Date) |
238 return cmp(self.Date, other.Date) |
245 |
239 |
|
240 def GetFullText(self): |
|
241 date = self.Date.replace(second=int(self.Seconds)) |
|
242 nsec = (self.Seconds % 1.) * 1e9 |
|
243 return "%s at %s.%9.9d:\n%s" % ( |
|
244 LogLevels[self.Level], |
|
245 str(date), nsec, |
|
246 self.Message) |
|
247 |
246 def Draw(self, dc, offset, width, draw_date): |
248 def Draw(self, dc, offset, width, draw_date): |
247 if draw_date: |
249 if draw_date: |
248 datetime_text = self.Date.strftime("%d/%m/%y %H:%M") |
250 datetime_text = self.Date.strftime("%d/%m/%y %H:%M") |
249 dw, dh = dc.GetTextExtent(datetime_text) |
251 dw, dh = dc.GetTextExtent(datetime_text) |
250 dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2) |
252 dc.DrawText(datetime_text, (width - dw) / 2, offset + (DATE_INFO_SIZE - dh) / 2) |
255 dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2) |
257 dc.DrawText(seconds_text, 5, offset + (MESSAGE_INFO_SIZE - sh) / 2) |
256 |
258 |
257 bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight() |
259 bw, bh = self.LevelBitmap.GetWidth(), self.LevelBitmap.GetHeight() |
258 dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2) |
260 dc.DrawBitmap(self.LevelBitmap, 10 + sw, offset + (MESSAGE_INFO_SIZE - bh) / 2) |
259 |
261 |
260 mw, mh = dc.GetTextExtent(self.Message) |
262 text = self.Message.replace("\n", " ") |
261 dc.DrawText(self.Message, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2) |
263 mw, mh = dc.GetTextExtent(text) |
|
264 dc.DrawText(text, 15 + sw + bw, offset + (MESSAGE_INFO_SIZE - mh) / 2) |
262 |
265 |
263 def GetHeight(self, draw_date): |
266 def GetHeight(self, draw_date): |
264 if draw_date: |
267 if draw_date: |
265 return DATE_INFO_SIZE + MESSAGE_INFO_SIZE |
268 return DATE_INFO_SIZE + MESSAGE_INFO_SIZE |
266 return MESSAGE_INFO_SIZE |
269 return MESSAGE_INFO_SIZE |
312 message_panel_sizer.AddGrowableRow(0) |
315 message_panel_sizer.AddGrowableRow(0) |
313 main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW) |
316 main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.GROW) |
314 |
317 |
315 self.MessagePanel = wx.Panel(self) |
318 self.MessagePanel = wx.Panel(self) |
316 if wx.Platform == '__WXMSW__': |
319 if wx.Platform == '__WXMSW__': |
317 self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New') |
320 self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New') |
318 else: |
321 else: |
319 self.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier') |
322 self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier') |
320 self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp) |
323 self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp) |
|
324 self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp) |
321 self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick) |
325 self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick) |
|
326 self.MessagePanel.Bind(wx.EVT_MOTION, self.OnMessagePanelMotion) |
|
327 self.MessagePanel.Bind(wx.EVT_LEAVE_WINDOW, self.OnMessagePanelLeaveWindow) |
322 self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel) |
328 self.MessagePanel.Bind(wx.EVT_MOUSEWHEEL, self.OnMessagePanelMouseWheel) |
323 self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground) |
329 self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground) |
324 self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint) |
330 self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint) |
325 self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize) |
331 self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize) |
326 message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW) |
332 message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW) |
409 for new_message in new_messages: |
420 for new_message in new_messages: |
410 self.LogMessages.append(new_message) |
421 self.LogMessages.append(new_message) |
411 self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp]) |
422 self.LogMessagesTimestamp = numpy.append(self.LogMessagesTimestamp, [new_message.Timestamp]) |
412 if self.CurrentMessage is None or self.CurrentMessage == old_length - 1: |
423 if self.CurrentMessage is None or self.CurrentMessage == old_length - 1: |
413 self.CurrentMessage = len(self.LogMessages) - 1 |
424 self.CurrentMessage = len(self.LogMessages) - 1 |
|
425 self.ResetMessageToolTip() |
|
426 self.MessageToolTipTimer.Stop() |
414 self.NewDataAvailable(None) |
427 self.NewDataAvailable(None) |
415 |
428 |
416 def FilterLogMessage(self, message, timestamp=None): |
429 def FilterLogMessage(self, message, timestamp=None): |
417 return (message.Level in self.CurrentFilter and |
430 return (message.Level in self.CurrentFilter and |
418 message.Message.find(self.CurrentSearchValue) != -1 and |
431 message.Message.find(self.CurrentSearchValue) != -1 and |
594 def GenerateOnDurationButton(self, duration): |
607 def GenerateOnDurationButton(self, duration): |
595 def OnDurationButton(): |
608 def OnDurationButton(): |
596 self.ScrollMessagePanelByTimestamp(duration) |
609 self.ScrollMessagePanelByTimestamp(duration) |
597 return OnDurationButton |
610 return OnDurationButton |
598 |
611 |
599 def OnMessagePanelLeftUp(self, event): |
612 def GetCopyMessageToClipboardFunction(self, message): |
|
613 def CopyMessageToClipboardFunction(event): |
|
614 self.ParentWindow.SetCopyBuffer(message.GetFullText()) |
|
615 return CopyMessageToClipboardFunction |
|
616 |
|
617 def GetMessageByScreenPos(self, posx, posy): |
600 if self.CurrentMessage is not None: |
618 if self.CurrentMessage is not None: |
601 posx, posy = event.GetPosition() |
|
602 for button in self.LeftButtons + self.RightButtons: |
|
603 if button.HitTest(posx, posy): |
|
604 button.ProcessCallback() |
|
605 break |
|
606 event.Skip() |
|
607 |
|
608 def OnMessagePanelLeftDCLick(self, event): |
|
609 if self.CurrentMessage is not None: |
|
610 posx, posy = event.GetPosition() |
|
611 width, height = self.MessagePanel.GetClientSize() |
619 width, height = self.MessagePanel.GetClientSize() |
612 message_idx = self.CurrentMessage |
620 message_idx = self.CurrentMessage |
613 message = self.LogMessages[message_idx] |
621 message = self.LogMessages[message_idx] |
614 draw_date = True |
622 draw_date = True |
615 offset = 5 |
623 offset = 5 |
616 |
624 |
617 while offset < height and message is not None: |
625 while offset < height and message is not None: |
618 if draw_date: |
626 if draw_date: |
619 offset += DATE_INFO_SIZE |
627 offset += DATE_INFO_SIZE |
620 |
628 |
621 if offset <= posy < offset + MESSAGE_INFO_SIZE: |
629 if offset <= posy < offset + MESSAGE_INFO_SIZE: |
622 self.CurrentSearchValue = message.Message |
630 return message |
623 self.SearchMessage.SetValue(message.Message) |
631 |
624 self.ResetMessagePanel() |
|
625 break |
|
626 |
|
627 offset += MESSAGE_INFO_SIZE |
632 offset += MESSAGE_INFO_SIZE |
628 |
633 |
629 previous_message, message_idx = self.GetPreviousMessage(message_idx) |
634 previous_message, message_idx = self.GetPreviousMessage(message_idx) |
630 if previous_message is not None: |
635 if previous_message is not None: |
631 draw_date = message.Date != previous_message.Date |
636 draw_date = message.Date != previous_message.Date |
632 message = previous_message |
637 message = previous_message |
|
638 return None |
|
639 |
|
640 def OnMessagePanelLeftUp(self, event): |
|
641 if self.CurrentMessage is not None: |
|
642 posx, posy = event.GetPosition() |
|
643 for button in self.LeftButtons + self.RightButtons: |
|
644 if button.HitTest(posx, posy): |
|
645 button.ProcessCallback() |
|
646 break |
|
647 event.Skip() |
|
648 |
|
649 def OnMessagePanelRightUp(self, event): |
|
650 message = self.GetMessageByScreenPos(*event.GetPosition()) |
|
651 if message is not None: |
|
652 menu = wx.Menu(title='') |
|
653 |
|
654 new_id = wx.NewId() |
|
655 menu.Append(help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Copy")) |
|
656 self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), id=new_id) |
|
657 |
|
658 self.MessagePanel.PopupMenu(menu) |
|
659 menu.Destroy() |
|
660 event.Skip() |
|
661 |
|
662 def OnMessagePanelLeftDCLick(self, event): |
|
663 message = self.GetMessageByScreenPos(*event.GetPosition()) |
|
664 if message is not None: |
|
665 self.SearchMessage.SetFocus() |
|
666 self.SearchMessage.SetValue(message.Message) |
|
667 event.Skip() |
|
668 |
|
669 def ResetMessageToolTip(self): |
|
670 if self.MessageToolTip is not None: |
|
671 self.MessageToolTip.Destroy() |
|
672 self.MessageToolTip = None |
|
673 |
|
674 def OnMessageToolTipTimer(self, event): |
|
675 if self.LastMousePos is not None: |
|
676 message = self.GetMessageByScreenPos(*self.LastMousePos) |
|
677 if message is not None: |
|
678 tooltip_pos = self.MessagePanel.ClientToScreen(self.LastMousePos) |
|
679 tooltip_pos.x += 10 |
|
680 tooltip_pos.y += 10 |
|
681 self.MessageToolTip = ToolTip(self.MessagePanel, message.GetFullText(), False) |
|
682 self.MessageToolTip.SetFont(self.Font) |
|
683 self.MessageToolTip.MoveToolTip(tooltip_pos) |
|
684 self.MessageToolTip.Show() |
|
685 event.Skip() |
|
686 |
|
687 def OnMessagePanelMotion(self, event): |
|
688 if not event.Dragging(): |
|
689 self.ResetMessageToolTip() |
|
690 self.LastMousePos = event.GetPosition() |
|
691 self.MessageToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True) |
|
692 event.Skip() |
|
693 |
|
694 def OnMessagePanelLeaveWindow(self, event): |
|
695 self.ResetMessageToolTip() |
|
696 self.LastMousePos = None |
|
697 self.MessageToolTipTimer.Stop() |
633 event.Skip() |
698 event.Skip() |
634 |
699 |
635 def OnMessagePanelMouseWheel(self, event): |
700 def OnMessagePanelMouseWheel(self, event): |
636 self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta()) |
701 self.ScrollMessagePanel(event.GetWheelRotation() / event.GetWheelDelta()) |
637 event.Skip() |
702 event.Skip() |