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