message_data_model.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2012, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from python_qt_binding.QtCore import QAbstractTableModel, QModelIndex, Qt, qWarning
34 from python_qt_binding.QtGui import QBrush, QIcon
35 
36 from .message import Message
37 from .message_list import MessageList
38 
39 
40 class MessageDataModel(QAbstractTableModel):
41 
42  # the column names must match the message attributes
43  columns = ['message', 'severity', 'node', 'stamp', 'topics', 'location']
44 
45  severity_colors = {
46  Message.DEBUG: QBrush(Qt.darkCyan),
47  Message.INFO: QBrush(Qt.darkBlue),
48  Message.WARN: QBrush(Qt.darkYellow),
49  Message.ERROR: QBrush(Qt.darkRed),
50  Message.FATAL: QBrush(Qt.red),
51  }
52 
53  def __init__(self):
54  super(MessageDataModel, self).__init__()
55  self._messages = MessageList()
56  self._message_limit = 20000
57  self._info_icon = QIcon.fromTheme('dialog-information')
58  self._warning_icon = QIcon.fromTheme('dialog-warning')
59  self._error_icon = QIcon.fromTheme('dialog-error')
60 
61  # BEGIN Required implementations of QAbstractTableModel functions
62 
63  def rowCount(self, parent=None):
64  return len(self._messages)
65 
66  def columnCount(self, parent=None):
67  return len(MessageDataModel.columns) + 1
68 
69  def data(self, index, role=None):
70  if role is None:
71  role = Qt.DisplayRole
72  if index.row() >= 0 and index.row() < len(self._messages):
73  msg = self._messages[index.row()]
74  if index.column() == 0:
75  if role == Qt.DisplayRole:
76  return '#%d' % msg.id
77  elif index.column() > 0 and index.column() < len(MessageDataModel.columns) + 1:
78  column = MessageDataModel.columns[index.column() - 1]
79  if role == Qt.DisplayRole or role == Qt.UserRole:
80  if column == 'stamp':
81  if role != Qt.UserRole:
82  data = msg.get_stamp_string()
83  else:
84  data = msg.get_stamp_for_compare()
85  else:
86  data = getattr(msg, column)
87  # map severity enum to label
88  if role == Qt.DisplayRole and column == 'severity':
89  data = Message.SEVERITY_LABELS[data]
90  # implode topic names
91  if column == 'topics':
92  data = ', '.join(data)
93  # append row number to define strict order
94  if role == Qt.UserRole:
95  # append row number to define strict order
96  # shortest string representation to compare stamps
97  # print(column, data, str(index.row()).zfill(len(str(len(self._messages)))))
98  data = str(data) + ' %08x' % index.row()
99  return data
100 
101  # decorate message column with severity icon
102  if role == Qt.DecorationRole and column == 'message':
103  if msg.severity in [Message.DEBUG, Message.INFO]:
104  return self._info_icon
105  elif msg.severity in [Message.WARN]:
106  return self._warning_icon
107  elif msg.severity in [Message.ERROR, Message.FATAL]:
108  return self._error_icon
109 
110  # colorize severity label
111  if role == Qt.ForegroundRole and column == 'severity':
112  assert msg.severity in MessageDataModel.severity_colors, \
113  'Unknown severity type: %s' % msg.severity
114  return MessageDataModel.severity_colors[msg.severity]
115 
116  if role == Qt.ToolTipRole and column != 'severity':
117  if column == 'stamp':
118  data = msg.get_stamp_string()
119  elif column == 'topics':
120  data = ', '.join(msg.topics)
121  else:
122  data = getattr(msg, column)
123  # <font> tag enables word wrap by forcing rich text
124  return '<font>' + data + '<br/><br/>' + \
125  self.tr('Right click for menu.') + '</font>'
126 
127  def headerData(self, section, orientation, role=None):
128  if role is None:
129  role = Qt.DisplayRole
130  if orientation == Qt.Horizontal:
131  if role == Qt.DisplayRole:
132  if section == 0:
133  return self.tr('#')
134  else:
135  return MessageDataModel.columns[section - 1].capitalize()
136  if role == Qt.ToolTipRole:
137  if section == 0:
138  return self.tr('Sort the rows by serial number in descendig order')
139  else:
140  return self.tr(
141  'Sorting the table by a column other then the serial number slows down the '
142  'interaction especially when recording high frequency data')
143 
144  # END Required implementations of QAbstractTableModel functions
145 
146  def get_message_limit(self):
147  return self._message_limit
148 
149  def set_message_limit(self, new_limit):
150  self._message_limit = new_limit
152 
153  def _enforce_message_limit(self, limit):
154  if len(self._messages) > limit:
155  self.beginRemoveRows(QModelIndex(), limit, len(self._messages) - 1)
156  del self._messages[limit:len(self._messages)]
157  self.endRemoveRows()
158 
159  def insert_rows(self, msgs):
160  # never try to insert more message than the limit
161  if len(msgs) > self._message_limit:
162  msgs = msgs[-self._message_limit:]
163  # reduce model before insert
164  limit = self._message_limit - len(msgs)
165  self._enforce_message_limit(limit)
166  # insert newest messages
167  self.beginInsertRows(QModelIndex(), 0, len(msgs) - 1)
168  self._messages.extend(msgs)
169  self.endInsertRows()
170 
171  def remove_rows(self, rowlist):
172  """
173  :param rowlist: list of row indexes, ''list(int)''
174  :returns: True if the indexes were removed successfully, ''bool''
175  """
176  if len(rowlist) == 0:
177  if len(self._messages) > 0:
178  try:
179  self.beginRemoveRows(QModelIndex(), 0, len(self._messages))
180  del self._messages[0:len(self._messages)]
181  self.endRemoveRows()
182  except:
183  return False
184  else:
185  rowlist = list(set(rowlist))
186  rowlist.sort(reverse=True)
187  dellist = [rowlist[0]]
188  for row in rowlist[1:]:
189  if dellist[-1] - 1 > row:
190  try:
191  self.beginRemoveRows(QModelIndex(), dellist[-1], dellist[0])
192  del self._messages[dellist[-1]:dellist[0] + 1]
193  self.endRemoveRows()
194  except:
195  return False
196  dellist = []
197  dellist.append(row)
198  if len(dellist) > 0:
199  try:
200  self.beginRemoveRows(QModelIndex(), dellist[-1], dellist[0])
201  del self._messages[dellist[-1]:dellist[0] + 1]
202  self.endRemoveRows()
203  except:
204  return False
205  return True
206 
207  def get_selected_text(self, rowlist):
208  """
209  Returns an easily readable block of text for the currently selected rows
210  :param rowlist: list of row indexes, ''list(int)''
211  :returns: the text from those indexes, ''str''
212  """
213  text = None
214  if len(rowlist) != 0:
215  text = ''
216  rowlist = list(set(rowlist))
217  for row in rowlist:
218  text += self._messages[row].pretty_print()
219  return text
220 
221  def get_time_range(self, rowlist):
222  """
223  :param rowlist: a list of row indexes, ''list''
224  :returns: a tuple of min and max times in a rowlist in
225  '(unix timestamp).(fraction of second)' format, ''tuple(str,str)''
226  """
227  min_ = float("inf")
228  max_ = float("-inf")
229  for row in rowlist:
230  item = self._messages[row].time_as_datestamp()
231  if float(item) > float(max_):
232  max_ = item
233  if float(item) < float(min_):
234  min_ = item
235  return min_, max_
236 
237  def get_unique_nodes(self):
238  nodes = set()
239  for message in self._messages:
240  nodes.add(message.node)
241  return nodes
242 
244  severities = set()
245  for message in self._messages:
246  severities.add(message.severity)
247  return severities
248 
249  def get_unique_topics(self):
250  topics = set()
251  for message in self._messages:
252  for topic in message.topics:
253  topics.add(topic)
254  return topics
255 
256  def get_severity_dict(self):
257  return Message.SEVERITY_LABELS
258 
259  def get_message_between(self, start_time, end_time=None):
260  """
261  :param start_time: time to start in timestamp form (including decimal
262  fractions of a second is acceptable, ''unixtimestamp''
263  :param end_time: time to end in timestamp form (including decimal
264  fractions of a second is acceptable, ''unixtimestamp'' (Optional)
265  :returns: list of messages in the time range ''list[message]''
266  """
267  msgs = []
268  for message in self._messages:
269  msg_time = message.stamp[0] + float(message.stamp[1]) / 10**9
270  if msg_time >= float(start_time) and (end_time is None or msg_time <= float(end_time)):
271  msgs.append(message)
272  return msgs
def get_message_between(self, start_time, end_time=None)
def headerData(self, section, orientation, role=None)


rqt_console
Author(s): Aaron Blasdel
autogenerated on Sun May 24 2020 03:23:49