|
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 |