00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 
00030 
00031 
00032 
00033 
00034 
00035 
00036 
00037 PKG = "robot_monitor"
00038 
00039 from collections import deque
00040 import rospy
00041 import roslib
00042 
00043 import wx
00044 import os
00045 import os.path
00046 
00047 def clamp(val, min, max):
00048     if (val < min):
00049         return min
00050     if (val > max):
00051         return max
00052     
00053     return val
00054 
00055 class ColoredTimeline(wx.Control):
00056     def __init__(self, parent, min_val, max_val, val, color_callback):
00057         wx.Control.__init__(self, parent, wx.ID_ANY)
00058         
00059         self._min = min_val
00060         self._max = max_val
00061         self._val = val
00062         self._color_callback = color_callback
00063         
00064         self.SetMinSize(wx.Size(-1, 16))
00065         self.SetMaxSize(wx.Size(-1, 16))
00066 
00067         self._timeline_marker_bitmap = wx.Bitmap(os.path.join(roslib.packages.get_pkg_dir(PKG), 'icons/timeline_marker.png'), wx.BITMAP_TYPE_PNG)
00068         
00069         self.Bind(wx.EVT_PAINT, self.on_paint)
00070         self.Bind(wx.EVT_SIZE, self.on_size)
00071         self.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse)
00072         
00073         self._left_down = False
00074 
00075         self.SetToolTip(wx.ToolTip("Drag to rewind messages"))
00076         
00077     def SetValue(self, val):
00078         self._val = clamp(int(val), self._min, self._max)
00079         self.Refresh()
00080         
00081     def GetValue(self):
00082         return self._val
00083     
00084     def SetRange(self, min_val, max_val):
00085         self._min = min_val
00086         self._max = max_val
00087         self._val = clamp(self._val, min, max)
00088         self.Refresh()
00089         
00090     def on_size(self, event):
00091         self.Refresh()
00092         
00093     def on_paint(self, event):
00094         dc = wx.PaintDC(self)
00095         dc.Clear()
00096         
00097         is_enabled = self.IsEnabled()
00098 
00099         (width, height) = self.GetSizeTuple()
00100         length = self._max + 1 - self._min
00101         value_size = width / float(length)
00102         for i in xrange(0, length):
00103             if (is_enabled):
00104                 color = self._color_callback(i + self._min)
00105             else:
00106                 color = wx.LIGHT_GREY
00107             end_color = wx.Colour(0.6 * color.Red(), 0.6 * color.Green(), 0.6 * color.Blue())
00108             dc.SetPen(wx.Pen(color))
00109             dc.SetBrush(wx.Brush(color))
00110             start = i * value_size
00111             end = (i + 1) * value_size
00112             dc.GradientFillLinear(wx.Rect(start, 0, end, height), color, end_color, wx.SOUTH)
00113             
00114             if (i > 0):
00115                 dc.SetPen(wx.BLACK_PEN)
00116                 dc.DrawLine(start, 0, start, height)
00117             
00118         marker_x = (self._val - 1) * value_size + (value_size / 2.0) - (self._timeline_marker_bitmap.GetWidth() / 2.0)
00119         dc.DrawBitmap(self._timeline_marker_bitmap, marker_x, 0, True)
00120         
00121     def _set_val_from_x(self, x):
00122         (width, height) = self.GetSizeTuple()
00123         
00124         length = self._max + 1 - self._min
00125         value_size = width / float(length)
00126         self.SetValue(x / value_size + 1)
00127         
00128     def on_mouse(self, event):
00129         if (event.LeftDown()):
00130             self._left_down = True
00131         elif (event.LeftUp()):
00132             self._left_down = False
00133             self._set_val_from_x(event.GetX())
00134             wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_COMMAND_SCROLL_CHANGED.typeId, self.GetId()))
00135         
00136         if (self._left_down):
00137             self._set_val_from_x(event.GetX())
00138             wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_COMMAND_SCROLL_THUMBTRACK.typeId, self.GetId()))
00139 
00140 class MessageTimeline(wx.Panel):
00141     def __init__(self, parent, count, topic, type, message_callback, color_callback, pause_callback):
00142         wx.Panel.__init__(self, parent, wx.ID_ANY)
00143         
00144         self._count = count
00145         self._message_callback = message_callback
00146         self._color_callback = color_callback
00147         self._pause_callback = pause_callback
00148         self._queue = deque()
00149         
00150         sizer = wx.BoxSizer(wx.HORIZONTAL)
00151         self._timeline = ColoredTimeline(self, 1, 1, 1, self._get_color_for_value)
00152         sizer.Add(self._timeline, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
00153         self._pause_button = wx.ToggleButton(self, wx.ID_ANY, "Pause")
00154         self._pause_button.SetToolTip(wx.ToolTip("Pause message updates"))
00155         sizer.Add(self._pause_button, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
00156         self.SetSizer(sizer)
00157         
00158         self._pause_button.Bind(wx.EVT_TOGGLEBUTTON, self.on_pause)
00159         self._paused = False
00160         self._last_msg = None
00161         
00162         self._tracking_latest = True
00163         self.Layout()
00164         
00165         self._last_val = 2
00166         
00167         self._timeline.Bind(wx.EVT_COMMAND_SCROLL_THUMBTRACK, self.on_slider_scroll)
00168         self._timeline.Bind(wx.EVT_COMMAND_SCROLL_CHANGED, self.on_slider_scroll)
00169         
00170         self._subscriber = None
00171         if (topic is not None):
00172             self._subscriber = rospy.Subscriber(topic, type, self.callback)
00173             
00174         self._message_receipt_callback = None
00175         
00176     def set_message_receipt_callback(self, cb):
00177         self._message_receipt_callback = cb
00178         
00179     def __del__(self):
00180         if (self._subscriber is not None):
00181             self._subscriber.unregister()
00182             
00183     def enable(self):
00184         wx.Panel.Enable(self)
00185         self._timeline.Enable()
00186         self._pause_button.Enable()
00187         
00188     def disable(self):
00189         wx.Panel.Disable(self)
00190         self._timeline.Disable()
00191         self._pause_button.Disable()
00192         self.unpause()
00193         
00194     def clear(self):
00195         self._queue.clear()
00196         self._timeline.SetRange(1, 1)
00197         self._timeline.SetValue(1)
00198         
00199     def is_paused(self):
00200         return self._paused
00201     
00202     def pause(self):
00203         self._paused = True
00204         self._pause_button.SetBackgroundColour(wx.Colour(123, 193, 255))
00205         self._pause_button.SetToolTip(wx.ToolTip("Resume message updates"))
00206         
00207         if (self._pause_callback is not None):
00208             self._pause_callback(True)
00209             
00210         self._pause_button.SetValue(True)
00211     
00212     def unpause(self):
00213         if (self._pause_callback is not None):
00214             self._pause_callback(False)
00215             
00216         self._pause_button.SetBackgroundColour(wx.NullColour)
00217         self._pause_button.SetToolTip(wx.ToolTip("Pause message updates"))
00218         self._paused = False
00219         if (self._last_msg is not None):
00220             self._tracking_latest = True
00221             self._new_msg(self._last_msg)
00222             
00223         self._pause_button.SetValue(False)
00224         
00225     def on_pause(self, event):
00226         if (event.IsChecked()):
00227             self.pause()
00228         else:
00229             self.unpause()
00230                 
00231     def on_slider_scroll(self, event):
00232         val = self._timeline.GetValue() - 1
00233         if (val == self._last_val):
00234             return
00235         
00236         if (val >= len(self._queue)):
00237             return
00238         
00239         self._last_val = val
00240         
00241         if (not self._paused and self._pause_callback is not None):
00242             self._pause_callback(True)
00243             
00244         self._pause_button.SetValue(True)
00245         self._pause_button.SetBackgroundColour(wx.Colour(123, 193, 255))
00246         self._paused = True
00247         self._tracking_latest = False
00248         
00249         msg = self._queue[val]
00250         
00251         self._message_callback(msg)
00252         
00253     def callback(self, msg):
00254         wx.CallAfter(self._new_msg, msg)
00255         
00256     def add_msg(self, msg):
00257         self._new_msg(msg)
00258         
00259     def _new_msg(self, msg):
00260         self._last_msg = msg
00261         if (self._message_receipt_callback is not None):
00262             self._message_receipt_callback(msg)
00263         if (self._paused):
00264             return
00265         
00266         self._queue.append(msg)
00267         if (len(self._queue) > self._count):
00268             self._queue.popleft()
00269             
00270         new_len = len(self._queue)
00271             
00272         self._timeline.SetRange(1, new_len)
00273         
00274         if (self._tracking_latest):
00275             self._timeline.SetValue(new_len)
00276             self._message_callback(msg)
00277             
00278     def _get_color_for_value(self, val):
00279         if (val == 1 and len(self._queue) == 0):
00280             return wx.LIGHT_GREY
00281         
00282         return self._color_callback(self._queue[val - 1])
00283             
00284     def get_messages(self):
00285         return self._queue
00286