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