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 Close(self):
00180 if (self._subscriber is not None):
00181 self._subscriber.unregister()
00182 self._subscriber = None
00183 wx.Panel.Close(self)
00184
00185 def __del__(self):
00186 if (self._subscriber is not None):
00187 self._subscriber.unregister()
00188
00189 def enable(self):
00190 wx.Panel.Enable(self)
00191 self._timeline.Enable()
00192 self._pause_button.Enable()
00193
00194 def disable(self):
00195 wx.Panel.Disable(self)
00196 self._timeline.Disable()
00197 self._pause_button.Disable()
00198 self.unpause()
00199
00200 def clear(self):
00201 self._queue.clear()
00202 self._timeline.SetRange(1, 1)
00203 self._timeline.SetValue(1)
00204
00205 def is_paused(self):
00206 return self._paused
00207
00208 def pause(self):
00209 self._paused = True
00210 self._pause_button.SetBackgroundColour(wx.Colour(123, 193, 255))
00211 self._pause_button.SetToolTip(wx.ToolTip("Resume message updates"))
00212
00213 if (self._pause_callback is not None):
00214 self._pause_callback(True)
00215
00216 self._pause_button.SetValue(True)
00217
00218 def unpause(self):
00219 if (self._pause_callback is not None):
00220 self._pause_callback(False)
00221
00222 self._pause_button.SetBackgroundColour(wx.NullColour)
00223 self._pause_button.SetToolTip(wx.ToolTip("Pause message updates"))
00224 self._paused = False
00225 if (self._last_msg is not None):
00226 self._tracking_latest = True
00227 self._new_msg(self._last_msg)
00228
00229 self._pause_button.SetValue(False)
00230
00231 def on_pause(self, event):
00232 if (event.IsChecked()):
00233 self.pause()
00234 else:
00235 self.unpause()
00236
00237 def on_slider_scroll(self, event):
00238 val = self._timeline.GetValue() - 1
00239 if (val == self._last_val):
00240 return
00241
00242 if (val >= len(self._queue)):
00243 return
00244
00245 self._last_val = val
00246
00247 if (not self._paused and self._pause_callback is not None):
00248 self._pause_callback(True)
00249
00250 self._pause_button.SetValue(True)
00251 self._pause_button.SetBackgroundColour(wx.Colour(123, 193, 255))
00252 self._paused = True
00253 self._tracking_latest = False
00254
00255 msg = self._queue[val]
00256
00257 self._message_callback(msg)
00258
00259 def callback(self, msg):
00260 wx.CallAfter(self._new_msg, msg)
00261
00262 def add_msg(self, msg):
00263 self._new_msg(msg)
00264
00265 def _new_msg(self, msg):
00266 self._last_msg = msg
00267 if (self._message_receipt_callback is not None):
00268 self._message_receipt_callback(msg)
00269 if (self._paused):
00270 return
00271
00272 self._queue.append(msg)
00273 if (len(self._queue) > self._count):
00274 self._queue.popleft()
00275
00276 new_len = len(self._queue)
00277
00278 self._timeline.SetRange(1, new_len)
00279
00280 if (self._tracking_latest):
00281 self._timeline.SetValue(new_len)
00282 self._message_callback(msg)
00283
00284 def _get_color_for_value(self, val):
00285 if (val == 1 and len(self._queue) == 0):
00286 return wx.LIGHT_GREY
00287
00288 return self._color_callback(self._queue[val - 1])
00289
00290 def get_messages(self):
00291 return self._queue
00292