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 import os
00034
00035 from python_qt_binding import loadUi
00036 from python_qt_binding.QtGui import QApplication, QCursor, QFileDialog, QHeaderView,QIcon, QMenu, QMessageBox, QTableView, QWidget
00037 from python_qt_binding.QtCore import QRegExp, Qt, qWarning
00038
00039 import time
00040 import datetime
00041
00042 from rqt_py_common.ini_helper import pack, unpack
00043
00044 from .filters.custom_filter import CustomFilter
00045 from .filters.location_filter import LocationFilter
00046 from .filters.message_filter import MessageFilter
00047 from .filters.node_filter import NodeFilter
00048 from .filters.severity_filter import SeverityFilter
00049 from .filters.time_filter import TimeFilter
00050 from .filters.topic_filter import TopicFilter
00051
00052 from .filters.custom_filter_widget import CustomFilterWidget
00053 from .filters.filter_wrapper_widget import FilterWrapperWidget
00054 from .filters.list_filter_widget import ListFilterWidget
00055 from .filters.text_filter_widget import TextFilterWidget
00056 from .filters.time_filter_widget import TimeFilterWidget
00057
00058 from .message import Message
00059 from .message_data_model import MessageDataModel
00060
00061 from .text_browse_dialog import TextBrowseDialog
00062
00063
00064 class ConsoleWidget(QWidget):
00065 """
00066 Primary widget for the rqt_console plugin.
00067 """
00068 def __init__(self, proxy_model, rospack, minimal=False):
00069 """
00070 :param proxymodel: the proxy model to display in the widget,''QSortFilterProxyModel''
00071 :param minimal: if true the load, save and column buttons will be hidden as well as the filter splitter, ''bool''
00072 """
00073 super(ConsoleWidget, self).__init__()
00074 self._proxy_model = proxy_model
00075 self._model = self._proxy_model.sourceModel()
00076 self._paused = False
00077 self._rospack = rospack
00078
00079
00080 self._exclude_filters = []
00081 self._highlight_filters = []
00082
00083 ui_file = os.path.join(self._rospack.get_path('rqt_console'), 'resource', 'console_widget.ui')
00084 loadUi(ui_file, self)
00085
00086 if minimal:
00087 self.load_button.hide()
00088 self.save_button.hide()
00089 self.column_resize_button.hide()
00090 self.setObjectName('ConsoleWidget')
00091 self.table_view.setModel(proxy_model)
00092
00093 self._columnwidth = (60, 100, 70, 100, 100, 100, 100)
00094 for idx, width in enumerate(self._columnwidth):
00095 self.table_view.horizontalHeader().resizeSection(idx, width)
00096
00097 def update_sort_indicator(logical_index, order):
00098 if logical_index == 0:
00099 self._proxy_model.sort(-1)
00100 self.table_view.horizontalHeader().setSortIndicatorShown(logical_index != 0)
00101 self.table_view.horizontalHeader().sortIndicatorChanged.connect(update_sort_indicator)
00102
00103 self.add_exclude_button.setIcon(QIcon.fromTheme('list-add'))
00104 self.add_highlight_button.setIcon(QIcon.fromTheme('list-add'))
00105 self.pause_button.setIcon(QIcon.fromTheme('media-playback-pause'))
00106 if not self.pause_button.icon().isNull():
00107 self.pause_button.setText('')
00108 self.record_button.setIcon(QIcon.fromTheme('media-record'))
00109 if not self.record_button.icon().isNull():
00110 self.record_button.setText('')
00111 self.load_button.setIcon(QIcon.fromTheme('document-open'))
00112 if not self.load_button.icon().isNull():
00113 self.load_button.setText('')
00114 self.save_button.setIcon(QIcon.fromTheme('document-save'))
00115 if not self.save_button.icon().isNull():
00116 self.save_button.setText('')
00117 self.clear_button.setIcon(QIcon.fromTheme('edit-clear'))
00118 if not self.clear_button.icon().isNull():
00119 self.clear_button.setText('')
00120 self.highlight_exclude_button.setIcon(QIcon.fromTheme('format-text-strikethrough'))
00121
00122 self.pause_button.clicked[bool].connect(self._handle_pause_clicked)
00123 self.record_button.clicked[bool].connect(self._handle_record_clicked)
00124 self.load_button.clicked[bool].connect(self._handle_load_clicked)
00125 self.save_button.clicked[bool].connect(self._handle_save_clicked)
00126 self.column_resize_button.clicked[bool].connect(self._handle_column_resize_clicked)
00127 self.clear_button.clicked[bool].connect(self._handle_clear_button_clicked)
00128
00129 self.table_view.mouseDoubleClickEvent = self._handle_mouse_double_click
00130 self.table_view.mousePressEvent = self._handle_mouse_press
00131 self.table_view.keyPressEvent = self._handle_custom_keypress
00132
00133 self.highlight_exclude_button.clicked[bool].connect(self._proxy_model.set_show_highlighted_only)
00134
00135 self.add_highlight_button.clicked.connect(self._add_highlight_filter)
00136 self.add_exclude_button.clicked.connect(self._add_exclude_filter)
00137
00138
00139
00140
00141 self._filter_factory_order = ['message', 'severity', 'node', 'time', 'topic', 'location', 'custom']
00142 self.filter_factory = {'message': (self.tr('...containing'), MessageFilter, TextFilterWidget),
00143 'severity': (self.tr('...with severities'), SeverityFilter, ListFilterWidget, self._model.get_severity_dict),
00144 'node': (self.tr('...from node'), NodeFilter, ListFilterWidget, self._model.get_unique_nodes),
00145 'time': (self.tr('...from time range'), TimeFilter, TimeFilterWidget, self.get_time_range_from_selection),
00146 'topic': (self.tr('...from topic'), TopicFilter, ListFilterWidget, self._model.get_unique_topics),
00147 'location': (self.tr('...from location'), LocationFilter, TextFilterWidget),
00148 'custom': (self.tr('Custom'), CustomFilter, CustomFilterWidget, [self._model.get_severity_dict, self._model.get_unique_nodes, self._model.get_unique_topics])}
00149
00150 self._model.rowsInserted.connect(self.update_status)
00151 self._model.rowsRemoved.connect(self.update_status)
00152 self._proxy_model.rowsInserted.connect(self.update_status)
00153 self._proxy_model.rowsRemoved.connect(self.update_status)
00154
00155
00156 self._browsers = []
00157
00158
00159 if minimal:
00160 self.table_splitter.setSizes([1, 0])
00161 else:
00162 self.table_splitter.setSizes([1, 1])
00163 self.exclude_table.resizeColumnsToContents()
00164 self.highlight_table.resizeColumnsToContents()
00165
00166 def get_message_summary(self, start_time_offset=None, end_time_offset=None):
00167 """
00168 :param start_time: number of seconds before now to start, ''int'' (optional)
00169 :param end_time: number of seconds before now to end, ''int'' (optional)
00170 :returns: summary of message numbers within time
00171 """
00172 current_time = time.mktime(datetime.datetime.now().timetuple())
00173 if start_time_offset is None:
00174 start_time = current_time - 240
00175 else:
00176 start_time = current_time - start_time_offset
00177 if end_time_offset is not None:
00178 end_time = current_time - end_time_offset
00179 else:
00180 end_time = None
00181
00182 message_subset = self._model.get_message_between(start_time, end_time)
00183
00184 class Message_Summary(object):
00185 __slots__ = 'fatal', 'error', 'warn', 'info', 'debug'
00186
00187 def __init__(self, messages):
00188 self.fatal = 0
00189 self.error = 0
00190 self.warn = 0
00191 self.info = 0
00192 self.debug = 0
00193 for message in messages:
00194 if message.severity == Message.DEBUG:
00195 self.debug += 1
00196 elif message.severity == Message.INFO:
00197 self.info += 1
00198 elif message.severity == Message.WARN:
00199 self.warn += 1
00200 elif message.severity == Message.ERROR:
00201 self.error += 1
00202 elif message.severity == Message.FATAL:
00203 self.fatal += 1
00204 else:
00205 assert False, "Unknown severity type '%s'" % str(message.severity)
00206
00207 return Message_Summary(message_subset)
00208
00209 def get_time_range_from_selection(self):
00210 """
00211 :returns: the range of time of messages in the current table selection (min, max), ''tuple(str,str)''
00212 """
00213 rowlist = []
00214 indexes = self.table_view.selectionModel().selectedIndexes()
00215
00216 if indexes:
00217 rowlist = [self._proxy_model.mapToSource(current).row() for current in indexes]
00218 rowlist = sorted(list(set(rowlist)))
00219
00220 mintime, maxtime = self._model.get_time_range(rowlist)
00221 return (mintime, maxtime)
00222 return (-1, -1)
00223
00224 def _delete_highlight_filter(self):
00225 """
00226 Deletes any highlight filters which have a checked delete button
00227 """
00228 for index, item in enumerate(self._highlight_filters):
00229 if item[1].delete_button.isChecked():
00230 self._proxy_model.delete_highlight_filter(index)
00231 self.highlight_table.removeCellWidget(index, 0)
00232 self.highlight_table.removeRow(index)
00233 item[0].filter_changed_signal.disconnect(self._proxy_model.handle_highlight_filters_changed)
00234 item[1].delete_button.clicked.disconnect(self._delete_highlight_filter)
00235 del self._highlight_filters[index]
00236
00237 def _delete_exclude_filter(self):
00238 """
00239 Deletes any exclude filters which have a checked delete button
00240 """
00241 for index, item in enumerate(self._exclude_filters):
00242 if item[1].delete_button.isChecked():
00243 self._proxy_model.delete_exclude_filter(index)
00244 self.exclude_table.removeCellWidget(index, 0)
00245 self.exclude_table.removeRow(index)
00246 item[0].filter_changed_signal.disconnect(self._proxy_model.handle_exclude_filters_changed)
00247 item[1].delete_button.clicked.disconnect(self._delete_exclude_filter)
00248 del self._exclude_filters[index]
00249
00250 def _add_highlight_filter(self, filter_index=False):
00251 """
00252 :param filter_index: if false then this function shows a QMenu to allow the user to choose a type of message filter. ''bool''
00253 OR
00254 :param filter_index: the index of the filter to be added, ''int''
00255 :return: if a filter was added then the index is returned, ''int''
00256 OR
00257 :return: if no filter was added then None is returned, ''NoneType''
00258 """
00259 if filter_index is False:
00260 filter_index = -1
00261 filter_select_menu = QMenu()
00262 for index in self._filter_factory_order:
00263
00264 if index in ['message', 'location'] or not self.filter_factory[index][1] in [type(item) for sublist in self._highlight_filters for item in sublist]:
00265 filter_select_menu.addAction(self.filter_factory[index][0])
00266 action = filter_select_menu.exec_(QCursor.pos())
00267 if action is None:
00268 return
00269 for index in self._filter_factory_order:
00270 if self.filter_factory[index][0] == action.text():
00271 filter_index = index
00272 if filter_index == -1:
00273 return
00274
00275 index = len(self._highlight_filters)
00276 newfilter = self.filter_factory[filter_index][1]()
00277 if len(self.filter_factory[filter_index]) >= 4:
00278 newwidget = self.filter_factory[filter_index][2](newfilter, self._rospack, self.filter_factory[filter_index][3])
00279 else:
00280 newwidget = self.filter_factory[filter_index][2](newfilter, self._rospack)
00281
00282
00283 self._highlight_filters.append((newfilter, FilterWrapperWidget(newwidget, self.filter_factory[filter_index][0]), filter_index))
00284 self._proxy_model.add_highlight_filter(newfilter)
00285 newfilter.filter_changed_signal.connect(self._proxy_model.handle_highlight_filters_changed)
00286 self._highlight_filters[index][1].delete_button.clicked.connect(self._delete_highlight_filter)
00287 self._model.rowsInserted.connect(self._highlight_filters[index][1].repopulate)
00288
00289
00290 self.highlight_table.insertRow(index)
00291 self.highlight_table.setCellWidget(index, 0, self._highlight_filters[index][1])
00292 self.highlight_table.resizeColumnsToContents()
00293 self.highlight_table.resizeRowsToContents()
00294 newfilter.filter_changed_signal.emit()
00295 return index
00296
00297 def _add_exclude_filter(self, filter_index=False):
00298 """
00299 :param filter_index: if false then this function shows a QMenu to allow the user to choose a type of message filter. ''bool''
00300 OR
00301 :param filter_index: the index of the filter to be added, ''int''
00302 :return: if a filter was added then the index is returned, ''int''
00303 OR
00304 :return: if no filter was added then None is returned, ''NoneType''
00305 """
00306 if filter_index is False:
00307 filter_index = -1
00308 filter_select_menu = QMenu()
00309 for index in self._filter_factory_order:
00310
00311 if index in ['message', 'location'] or not self.filter_factory[index][1] in [type(item) for sublist in self._exclude_filters for item in sublist]:
00312 filter_select_menu.addAction(self.filter_factory[index][0])
00313 action = filter_select_menu.exec_(QCursor.pos())
00314 if action is None:
00315 return None
00316 for index in self._filter_factory_order:
00317 if self.filter_factory[index][0] == action.text():
00318 filter_index = index
00319 if filter_index == -1:
00320 return None
00321
00322 index = len(self._exclude_filters)
00323 newfilter = self.filter_factory[filter_index][1]()
00324 if len(self.filter_factory[filter_index]) >= 4:
00325 newwidget = self.filter_factory[filter_index][2](newfilter, self._rospack, self.filter_factory[filter_index][3])
00326 else:
00327 newwidget = self.filter_factory[filter_index][2](newfilter, self._rospack)
00328
00329
00330 self._exclude_filters.append((newfilter, FilterWrapperWidget(newwidget, self.filter_factory[filter_index][0]), filter_index))
00331 self._proxy_model.add_exclude_filter(newfilter)
00332 newfilter.filter_changed_signal.connect(self._proxy_model.handle_exclude_filters_changed)
00333 self._exclude_filters[index][1].delete_button.clicked.connect(self._delete_exclude_filter)
00334 self._model.rowsInserted.connect(self._exclude_filters[index][1].repopulate)
00335
00336
00337 self.exclude_table.insertRow(index)
00338 self.exclude_table.setCellWidget(index, 0, self._exclude_filters[index][1])
00339 self.exclude_table.resizeColumnsToContents()
00340 self.exclude_table.resizeRowsToContents()
00341 newfilter.filter_changed_signal.emit()
00342 return index
00343
00344 def _process_highlight_exclude_filter(self, selection, selectiontype, exclude=False):
00345 """
00346 Modifies the relevant filters (based on selectiontype) to remove (exclude=True)
00347 or highlight (exclude=False) the selection from the dataset in the tableview.
00348 :param selection: the actual selection, ''str''
00349 :param selectiontype: the type of selection, ''str''
00350 :param exclude: If True process as an exclude filter, False process as an highlight filter, ''bool''
00351 """
00352 types = {self.tr('Node'): 2, self.tr('Topic'): 4, self.tr('Severity'): 1, self.tr('Message'): 0}
00353 try:
00354 col = types[selectiontype]
00355 except:
00356 raise RuntimeError("Bad Column name in ConsoleWidget._process_highlight_exclude_filter()")
00357
00358 if col == 0:
00359 unique_messages = set()
00360 selected_indexes = self.table_view.selectionModel().selectedIndexes()
00361 num_selected = len(selected_indexes) / 6
00362 for index in range(num_selected):
00363 unique_messages.add(selected_indexes[num_selected * col + index].data())
00364 unique_messages = list(unique_messages)
00365 for message in unique_messages:
00366 message = message.replace('\\', '\\\\')
00367 message = message.replace('.', '\\.')
00368 if exclude:
00369 filter_index = self._add_exclude_filter(selectiontype.lower())
00370 filter_widget = self._exclude_filters[filter_index][1].findChildren(QWidget, QRegExp('.*FilterWidget.*'))[0]
00371 else:
00372 filter_index = self._add_highlight_filter(col)
00373 filter_widget = self._highlight_filters[filter_index][1].findChildren(QWidget, QRegExp('.*FilterWidget.*'))[0]
00374 filter_widget.set_regex(True)
00375 filter_widget.set_text('^' + message + '$')
00376
00377 else:
00378 if exclude:
00379
00380 if self.filter_factory[selectiontype.lower()][1] not in [type(item) for sublist in self._exclude_filters for item in sublist]:
00381 filter_index = self._add_exclude_filter(selectiontype.lower())
00382 else:
00383 for index, item in enumerate(self._exclude_filters):
00384 if type(item[0]) == self.filter_factory[selectiontype.lower()][1]:
00385 filter_index = index
00386 else:
00387
00388 if self.filter_factory[selectiontype.lower()][1] not in [type(item) for sublist in self._highlight_filters for item in sublist]:
00389 filter_index = self._add_highlight_filter(col)
00390 else:
00391 for index, item in enumerate(self._highlight_filters):
00392 if type(item[0]) == self.filter_factory[selectiontype.lower()][1]:
00393 filter_index = index
00394
00395 if exclude:
00396 filter_widget = self._exclude_filters[filter_index][1].findChildren(QWidget, QRegExp('.*FilterWidget.*'))[0]
00397 filter_widget.select_item(selection)
00398 else:
00399 filter_widget = self._highlight_filters[filter_index][1].findChildren(QWidget, QRegExp('.*FilterWidget.*'))[0]
00400 filter_widget.select_item(selection)
00401
00402 def _rightclick_menu(self, event):
00403 """
00404 Dynamically builds the rightclick menu based on the unique column data
00405 from the passed in datamodel and then launches it modally
00406 :param event: the mouse event object, ''QMouseEvent''
00407 """
00408 severities = {}
00409 for severity, label in Message.SEVERITY_LABELS.items():
00410 if severity in self._model.get_unique_severities():
00411 severities[severity] = label
00412 nodes = sorted(self._model.get_unique_nodes())
00413 topics = sorted(self._model.get_unique_topics())
00414
00415
00416 menutext = []
00417 menutext.append([self.tr('Exclude'), [[self.tr('Severity'), severities], [self.tr('Node'), nodes], [self.tr('Topic'), topics], [self.tr('Selected Message(s)')]]])
00418 menutext.append([self.tr('Highlight'), [[self.tr('Severity'), severities], [self.tr('Node'), nodes], [self.tr('Topic'), topics], [self.tr('Selected Message(s)')]]])
00419 menutext.append([self.tr('Copy Selected')])
00420 menutext.append([self.tr('Browse Selected')])
00421
00422 menu = QMenu()
00423 submenus = []
00424 subsubmenus = []
00425 for item in menutext:
00426 if len(item) > 1:
00427 submenus.append(QMenu(item[0], menu))
00428 for subitem in item[1]:
00429 if len(subitem) > 1:
00430 subsubmenus.append(QMenu(subitem[0], submenus[-1]))
00431 if isinstance(subitem[1], dict):
00432 for key in sorted(subitem[1].keys()):
00433 action = subsubmenus[-1].addAction(subitem[1][key])
00434 action.setData(key)
00435 else:
00436 for subsubitem in subitem[1]:
00437 subsubmenus[-1].addAction(subsubitem)
00438 submenus[-1].addMenu(subsubmenus[-1])
00439 else:
00440 submenus[-1].addAction(subitem[0])
00441 menu.addMenu(submenus[-1])
00442 else:
00443 menu.addAction(item[0])
00444 action = menu.exec_(event.globalPos())
00445
00446 if action is None or action == 0:
00447 return
00448 elif action.text() == self.tr('Browse Selected'):
00449 self._show_browsers()
00450 elif action.text() == self.tr('Copy Selected'):
00451 rowlist = []
00452 for current in self.table_view.selectionModel().selectedIndexes():
00453 rowlist.append(self._proxy_model.mapToSource(current).row())
00454 copytext = self._model.get_selected_text(rowlist)
00455 if copytext is not None:
00456 clipboard = QApplication.clipboard()
00457 clipboard.setText(copytext)
00458 elif action.text() == self.tr('Selected Message(s)'):
00459 if action.parentWidget().title() == self.tr('Highlight'):
00460 self._process_highlight_exclude_filter(action.text(), 'Message', False)
00461 elif action.parentWidget().title() == self.tr('Exclude'):
00462 self._process_highlight_exclude_filter(action.text(), 'Message', True)
00463 else:
00464 raise RuntimeError("Menu format corruption in ConsoleWidget._rightclick_menu()")
00465 else:
00466
00467 try:
00468 roottitle = action.parentWidget().parentWidget().title()
00469 except:
00470 raise RuntimeError("Menu format corruption in ConsoleWidget._rightclick_menu()")
00471
00472 if roottitle == self.tr('Highlight'):
00473 self._process_highlight_exclude_filter(action.text(), action.parentWidget().title(), False)
00474 elif roottitle == self.tr('Exclude'):
00475 self._process_highlight_exclude_filter(action.text(), action.parentWidget().title(), True)
00476 else:
00477 raise RuntimeError("Unknown Root Action %s selected in ConsoleWidget._rightclick_menu()" % roottitle)
00478
00479 def update_status(self):
00480 """
00481 Sets the message display label to the current value
00482 """
00483 if self._model.rowCount() == self._proxy_model.rowCount():
00484 tip = self.tr('Displaying %d messages') % (self._model.rowCount())
00485 else:
00486 tip = self.tr('Displaying %d of %d messages') % (self._proxy_model.rowCount(), self._model.rowCount())
00487 self.messages_label.setText(tip)
00488
00489 def cleanup_browsers_on_close(self):
00490 for browser in self._browsers:
00491 browser.close()
00492
00493 def _show_browsers(self):
00494 rowlist = []
00495 for current in self.table_view.selectionModel().selectedIndexes():
00496 rowlist.append(self._proxy_model.mapToSource(current).row())
00497 browsetext = self._model.get_selected_text(rowlist)
00498 if browsetext is not None:
00499 self._browsers.append(TextBrowseDialog(browsetext, self._rospack))
00500 self._browsers[-1].show()
00501
00502 def _handle_clear_button_clicked(self, checked):
00503 self._model.remove_rows([])
00504 Message._next_id = 1
00505
00506 def _handle_load_clicked(self, checked):
00507 filename = QFileDialog.getOpenFileName(self, self.tr('Load from File'), '.', self.tr('rqt_console message file {.csv} (*.csv)'))
00508 if filename[0] != '':
00509 try:
00510 with open(filename[0], 'r') as h:
00511 lines = h.read().splitlines()
00512 except IOError as e:
00513 qWarning(str(e))
00514 return False
00515
00516
00517 columns = lines[0].split(';')
00518 if len(lines) < 2:
00519 return True
00520
00521
00522 rows = []
00523 last_wrapped = False
00524 for line in lines[1:]:
00525
00526 if not line:
00527 continue
00528
00529 if line == '"':
00530 has_prefix = not last_wrapped
00531 has_suffix = last_wrapped
00532 line = ''
00533 else:
00534 has_prefix = line[0] == '"'
00535 if has_prefix:
00536 line = line[1:]
00537 has_suffix = line[-1] == '"'
00538 if has_suffix:
00539 line = line[:-1]
00540
00541
00542 if not has_prefix and not last_wrapped:
00543 continue
00544
00545 if last_wrapped and has_prefix:
00546 rows.pop()
00547
00548
00549 if last_wrapped:
00550 rows[-1] += line
00551 else:
00552
00553 rows.append(line)
00554
00555 last_wrapped = not has_suffix
00556
00557
00558 messages = []
00559 skipped = []
00560 for row in rows:
00561 data = row.split('";"')
00562 msg = Message()
00563 msg.set_stamp_format('hh:mm:ss.ZZZ (yyyy-MM-dd)')
00564 for i, column in enumerate(columns):
00565 value = data[i]
00566 if column == 'message':
00567 msg.message = value.replace('\\"', '"')
00568 elif column == 'severity':
00569 msg.severity = int(value)
00570 if msg.severity not in Message.SEVERITY_LABELS:
00571 skipped.append('Unknown severity value: %s' % value)
00572 msg = None
00573 break
00574 elif column == 'stamp':
00575 parts = value.split('.')
00576 if len(parts) != 2:
00577 skipped.append('Unknown timestamp format: %s' % value)
00578 msg = None
00579 break
00580 msg.stamp = (int(parts[0]), int(parts[1]))
00581 elif column == 'topics':
00582 msg.topics = value.split(',')
00583 elif column == 'node':
00584 msg.node = value
00585 elif column == 'location':
00586 msg.location = value
00587 else:
00588 skipped.append('Unknown column: %s' % column)
00589 msg = None
00590 break
00591 if msg:
00592 messages.append(msg)
00593 if skipped:
00594 qWarning('Skipped %d rows since they do not appear to be in rqt_console message file format:\n- %s' % (len(skipped), '\n- '.join(skipped)))
00595
00596 if messages:
00597 self._model.insert_rows(messages)
00598
00599 self._handle_pause_clicked(True)
00600
00601 return True
00602
00603 else:
00604 qWarning('File does not appear to be a rqt_console message file: missing file header.')
00605 return False
00606
00607 def _handle_save_clicked(self, checked):
00608 filename = QFileDialog.getSaveFileName(self, 'Save to File', '.', self.tr('rqt_console msg file {.csv} (*.csv)'))
00609 if filename[0] != '':
00610 filename = filename[0]
00611 if filename[-4:] != '.csv':
00612 filename += '.csv'
00613 try:
00614 handle = open(filename, 'w')
00615 except IOError as e:
00616 qWarning(str(e))
00617 return
00618 try:
00619 handle.write(';'.join(MessageDataModel.columns) + '\n')
00620 for index in range(self._proxy_model.rowCount()):
00621 row = self._proxy_model.mapToSource(self._proxy_model.index(index, 0)).row()
00622 msg = self._model._messages[row]
00623 data = {}
00624 data['message'] = msg.message.replace('"', '\\"')
00625 data['severity'] = str(msg.severity)
00626 data['node'] = msg.node
00627 data['stamp'] = str(msg.stamp[0]) + '.' + str(msg.stamp[1]).zfill(9)
00628 data['topics'] = ','.join(msg.topics)
00629 data['location'] = msg.location
00630 line = []
00631 for column in MessageDataModel.columns:
00632 line.append('"%s"' % data[column])
00633 handle.write(';'.join(line) + '\n')
00634 except Exception as e:
00635 qWarning('File save failed: %s' % str(e))
00636 return False
00637 finally:
00638 handle.close()
00639 return True
00640
00641 def _handle_pause_clicked(self):
00642 self._paused = True
00643 self.pause_button.setVisible(False)
00644 self.record_button.setVisible(True)
00645
00646 def _handle_record_clicked(self):
00647 self._paused = False
00648 self.pause_button.setVisible(True)
00649 self.record_button.setVisible(False)
00650
00651 def _handle_column_resize_clicked(self):
00652 self.table_view.resizeColumnsToContents()
00653
00654 def _delete_selected_rows(self):
00655 rowlist = []
00656 for current in self.table_view.selectionModel().selectedIndexes():
00657 rowlist.append(self._proxy_model.mapToSource(current).row())
00658 rowlist = list(set(rowlist))
00659 return self._model.remove_rows(rowlist)
00660
00661 def _handle_custom_keypress(self, event, old_keyPressEvent=QTableView.keyPressEvent):
00662 """
00663 Handles the delete key.
00664 The delete key removes the tableview's selected rows from the datamodel
00665 """
00666 if event.key() == Qt.Key_Delete and len(self._model._messages) > 0:
00667 delete = QMessageBox.Yes
00668 if len(self.table_view.selectionModel().selectedIndexes()) == 0:
00669 delete = QMessageBox.question(self, self.tr('Message'), self.tr("Are you sure you want to delete all messages?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
00670 if delete == QMessageBox.Yes and event.key() == Qt.Key_Delete and event.modifiers() == Qt.NoModifier:
00671 if self._delete_selected_rows():
00672 event.accept()
00673 return old_keyPressEvent(self.table_view, event)
00674
00675 def _handle_mouse_double_click(self, event, old_doubleclickevent=QTableView.mouseDoubleClickEvent):
00676 if event.buttons() & Qt.LeftButton and event.modifiers() == Qt.NoModifier:
00677 self._show_browsers()
00678 event.accept()
00679 return old_doubleclickevent(self.table_view, event)
00680
00681 def _handle_mouse_press(self, event, old_pressEvent=QTableView.mousePressEvent):
00682 if event.buttons() & Qt.RightButton and event.modifiers() == Qt.NoModifier:
00683 self._rightclick_menu(event)
00684 event.accept()
00685 return old_pressEvent(self.table_view, event)
00686
00687 def save_settings(self, plugin_settings, instance_settings):
00688 instance_settings.set_value('settings_exist', True)
00689
00690 instance_settings.set_value('table_splitter', self.table_splitter.saveState())
00691 instance_settings.set_value('filter_splitter', self.filter_splitter.saveState())
00692
00693 instance_settings.set_value('paused', self._paused)
00694 instance_settings.set_value('show_highlighted_only', self.highlight_exclude_button.isChecked())
00695
00696 exclude_filters = []
00697 for index, item in enumerate(self._exclude_filters):
00698 exclude_filters.append(item[2])
00699 filter_settings = instance_settings.get_settings('exclude_filter_' + str(index))
00700 item[1].save_settings(filter_settings)
00701 instance_settings.set_value('exclude_filters', pack(exclude_filters))
00702
00703 highlight_filters = []
00704 for index, item in enumerate(self._highlight_filters):
00705 highlight_filters.append(item[2])
00706 filter_settings = instance_settings.get_settings('highlight_filter_' + str(index))
00707 item[1].save_settings(filter_settings)
00708 instance_settings.set_value('highlight_filters', pack(highlight_filters))
00709 instance_settings.set_value('message_limit', self._model.get_message_limit())
00710
00711 def restore_settings(self, pluggin_settings, instance_settings):
00712 if instance_settings.contains('table_splitter'):
00713 self.table_splitter.restoreState(instance_settings.value('table_splitter'))
00714 if instance_settings.contains('filter_splitter'):
00715 self.filter_splitter.restoreState(instance_settings.value('filter_splitter'))
00716 else:
00717 self.filter_splitter.setSizes([1, 1])
00718
00719 paused = instance_settings.value('paused') in [True, 'true']
00720 if paused:
00721 self._handle_pause_clicked()
00722 else:
00723 self._handle_record_clicked()
00724 self.highlight_exclude_button.setChecked(instance_settings.value('show_highlighted_only') in [True, 'true'])
00725 self._proxy_model.set_show_highlighted_only(self.highlight_exclude_button.isChecked())
00726
00727 for item in self._exclude_filters:
00728 item[1].delete_button.setChecked(True)
00729 self._delete_exclude_filter()
00730 if instance_settings.contains('exclude_filters'):
00731 exclude_filters = unpack(instance_settings.value('exclude_filters'))
00732 if exclude_filters is not None:
00733 for index, item in enumerate(exclude_filters):
00734 self._add_exclude_filter(item)
00735 filter_settings = instance_settings.get_settings('exclude_filter_' + str(index))
00736 self._exclude_filters[-1][1].restore_settings(filter_settings)
00737 else:
00738 self._add_exclude_filter('severity')
00739
00740 for item in self._highlight_filters:
00741 item[1].delete_button.setChecked(True)
00742 self._delete_highlight_filter()
00743 if instance_settings.contains('highlight_filters'):
00744 highlight_filters = unpack(instance_settings.value('highlight_filters'))
00745 if highlight_filters is not None:
00746 for index, item in enumerate(highlight_filters):
00747 self._add_highlight_filter(item)
00748 filter_settings = instance_settings.get_settings('highlight_filter_' + str(index))
00749 self._highlight_filters[-1][1].restore_settings(filter_settings)
00750 else:
00751 self._add_highlight_filter('message')
00752
00753 if instance_settings.contains('message_limit'):
00754 self._model.set_message_limit(int(instance_settings.value('message_limit')))