raw_view.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2009, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 """
00034 Defines a raw view: a TopicMessageView that displays the message contents in a tree.
00035 """
00036 
00037 import codecs
00038 import math
00039 
00040 import wx
00041 
00042 import rospy
00043 
00044 import bag_helper
00045 from plugin.topic_message_view import TopicMessageView
00046 
00047 class RawView(TopicMessageView):
00048     name = 'Raw'
00049     
00050     def __init__(self, timeline, parent):
00051         TopicMessageView.__init__(self, timeline, parent)
00052 
00053         self.message_tree = MessageTree(self.parent)
00054         
00055         self.parent.Bind(wx.EVT_SIZE, self.on_size)
00056 
00057     ## MessageView implementation
00058 
00059     def message_viewed(self, bag, msg_details):
00060         TopicMessageView.message_viewed(self, bag, msg_details)
00061 
00062         topic, msg, t = msg_details
00063         if t is None:
00064             self.message_cleared()
00065         else:
00066             wx.CallAfter(self.message_tree.set_message, msg)
00067 
00068     def message_cleared(self):
00069         TopicMessageView.message_cleared(self)
00070         wx.CallAfter(self.message_tree.set_message, None)
00071 
00072     ## Events
00073 
00074     def on_size(self, event):
00075         self.message_tree.Size = self.parent.ClientSize
00076 
00077 class MessageTree(wx.TreeCtrl):
00078     def __init__(self, parent):
00079         wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.TR_MULTIPLE)
00080 
00081         self._font = wx.Font(9, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
00082 
00083         self._msg = None
00084 
00085         self._expanded_paths = None
00086 
00087         self.Bind(wx.EVT_KEY_UP, self.on_key_up)
00088 
00089     @property
00090     def msg(self):
00091         return self._msg
00092     
00093     def set_message(self, msg):
00094         # Remember whether items were expanded or not before deleting
00095         if self._msg:
00096             for item in self.get_all_items():
00097                 path = self.get_item_path(item)
00098                 if self.IsExpanded(item):
00099                     self._expanded_paths.add(path)
00100                 elif path in self._expanded_paths:
00101                     self._expanded_paths.remove(path)
00102 
00103             self.DeleteAllItems()
00104 
00105         if msg:
00106             # Populate the tree
00107             self._add_msg_object(None, '', 'msg', msg, msg._type)
00108             
00109             if self._expanded_paths is None:
00110                 self._expanded_paths = set()
00111             else:
00112                 # Expand those that were previously expanded, and collapse any paths that we've seen for the first time
00113                 for item in self.get_all_items():
00114                     path = self.get_item_path(item)
00115                     if path in self._expanded_paths:
00116                         self.Expand(item)
00117                     else:
00118                         self.Collapse(item)
00119 
00120         self._msg = msg
00121 
00122         self.Refresh()
00123 
00124     def on_key_up(self, event):
00125         key, ctrl = event.KeyCode, event.ControlDown()
00126 
00127         if ctrl:
00128             if key == ord('C') or key == ord('c'):
00129                 # Ctrl-C: copy text from selected items to clipboard
00130                 self._copy_text_to_clipboard()
00131             elif key == ord('A') or key == ord('a'):
00132                 # Ctrl-A: select all
00133                 self._select_all()
00134 
00135     def _select_all(self):
00136         first_selected = self.GetFirstVisibleItem()
00137         for i in self.get_all_items():
00138             if not self.IsSelected(i):
00139                 self.SelectItem(i, True)
00140                 
00141         if first_selected is not None:
00142             self.ScrollTo(first_selected)
00143 
00144     def _copy_text_to_clipboard(self):
00145         # Get the indented text for all selected items
00146         
00147         def get_distance(item, ancestor, distance=0):
00148             parent = self.GetItemParent(item)
00149             if parent == ancestor:
00150                 return distance
00151             else:
00152                 return get_distance(parent, ancestor, distance + 1)
00153 
00154         root = self.GetRootItem()
00155         text = '\n'.join([('\t' * get_distance(i, root)) + self.GetItemText(i) for i in self.GetSelections()])
00156 
00157         # Copy the text to the clipboard
00158         if wx.TheClipboard.Open():
00159             try:
00160                 wx.TheClipboard.SetData(wx.TextDataObject(text))
00161             finally:
00162                 wx.TheClipboard.Close()
00163                 
00164     def get_item_path(self, item):
00165         return self.GetItemPyData(item)[0].replace(' ', '')   # remove spaces that may get introduced in indexing, e.g. [  3] is [3]
00166 
00167     def get_all_items(self):
00168         items = []
00169         try:
00170             self.traverse(self.RootItem, items.append)
00171         except Exception:
00172             # @todo: large messages can cause a stack overflow due to recursion
00173             pass
00174         return items
00175 
00176     def traverse(self, root, function):
00177         if self.ItemHasChildren(root):
00178             first_child = self.GetFirstChild(root)[0]
00179             function(first_child)
00180             self.traverse(first_child, function)
00181 
00182         child = self.GetNextSibling(root)
00183         if child:
00184             function(child)
00185             self.traverse(child, function)
00186 
00187     def _add_msg_object(self, parent, path, name, obj, obj_type):
00188         label = name
00189         
00190         if hasattr(obj, '__slots__'):
00191             subobjs = [(slot, getattr(obj, slot)) for slot in obj.__slots__]
00192         elif type(obj) in [list, tuple]:
00193             len_obj = len(obj)
00194             if len_obj == 0:
00195                 subobjs = []
00196             else:
00197                 w = int(math.ceil(math.log10(len_obj)))
00198                 subobjs = [('[%*d]' % (w, i), subobj) for (i, subobj) in enumerate(obj)]
00199         else:
00200             subobjs = []
00201         
00202         if type(obj) in [int, long, float]:
00203             if type(obj) == float:
00204                 obj_repr = '%.6f' % obj
00205             else:
00206                 obj_repr = str(obj)
00207 
00208             if obj_repr[0] == '-':
00209                 label += ': %s' % obj_repr
00210             else:
00211                 label += ':  %s' % obj_repr
00212 
00213         elif type(obj) in [str, bool, int, long, float, complex, rospy.Time]:
00214             # Ignore any binary data
00215             obj_repr = codecs.utf_8_decode(str(obj), 'ignore')[0]
00216             
00217             # Truncate long representations
00218             if len(obj_repr) >= 50:
00219                 obj_repr = obj_repr[:50] + '...'
00220             
00221             label += ': ' + obj_repr
00222 
00223         if parent is None:
00224             item = self.AddRoot(label)
00225         else:
00226             item = self.AppendItem(parent, label)
00227 
00228         self.SetItemFont(item, self._font)
00229         self.SetItemPyData(item, (path, obj_type))
00230 
00231         for subobj_name, subobj in subobjs:
00232             if subobj is None:
00233                 continue
00234             
00235             if path == '':
00236                 subpath = subobj_name                       # root field
00237             elif subobj_name.startswith('['):
00238                 subpath = '%s%s' % (path, subobj_name)      # list, dict, or tuple
00239             else:
00240                 subpath = '%s.%s' % (path, subobj_name)     # attribute (prefix with '.')
00241 
00242             if hasattr(subobj, '_type'):
00243                 subobj_type = subobj._type
00244             else:
00245                 subobj_type = type(subobj).__name__
00246 
00247             self._add_msg_object(item, subpath, subobj_name, subobj, subobj_type)


rxbag
Author(s): Tim Field
autogenerated on Mon Oct 6 2014 07:26:07