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