console_widget.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2012, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 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         # These are lists of Tuples = (,)
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         # Filter factory dictionary:
00139         # index 0 is a label describing the widget, index 1 is the class that provides filtering logic
00140         # index 2 is the widget that sets the data in the filter class, index 3 are the arguments for the widget class constructor
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         # list of TextBrowserDialogs to close when cleaning up
00156         self._browsers = []
00157 
00158         # This defaults the filters panel to start by taking 50% of the available space
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                 # flattens the _highlight filters list and only adds the item if it doesn't already exist
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         # pack the new filter tuple onto the filter list
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         # place the widget in the proper location
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                 # flattens the _exclude filters list and only adds the item if it doesn't already exist
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         # pack the new filter tuple onto the filter list
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         # place the widget in the proper location
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                 # Test if the filter we are adding already exists if it does use the existing filter
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                 # Test if the filter we are adding already exists if it does use the existing filter
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         # menutext entries turned into
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             # This processes the dynamic list entries (severity, node and topic)
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             # extract column header
00517             columns = lines[0].split(';')
00518             if len(lines) < 2:
00519                 return True
00520 
00521             # join wrapped lines
00522             rows = []
00523             last_wrapped = False
00524             for line in lines[1:]:
00525                 # ignore empty lines
00526                 if not line:
00527                     continue
00528                 # check for quotes and remove them
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                 # ignore line without prefix if previous line was not wrapped
00542                 if not has_prefix and not last_wrapped:
00543                     continue
00544                 # remove wrapped line which is not continued on the next line
00545                 if last_wrapped and has_prefix:
00546                     rows.pop()
00547 
00548                 # add/append lines
00549                 if last_wrapped:
00550                     rows[-1] += line
00551                 else:
00552                     # add line without quote prefix
00553                     rows.append(line)
00554 
00555                 last_wrapped = not has_suffix
00556 
00557             # generate message for each row
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')))


rqt_console
Author(s): Aaron Blasdel
autogenerated on Mon Oct 6 2014 07:15:09