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 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         # These are lists of Tuples = (,)
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  # Qt5
00099         except AttributeError:
00100             setSectionResizeMode = self.table_view.horizontalHeader().setResizeMode  # Qt4
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         # Filter factory dictionary:
00145         # index 0 is a label describing the widget, index 1 is the class that provides filtering logic
00146         # index 2 is the widget that sets the data in the filter class, index 3 are the arguments for the widget class constructor
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         # list of TextBrowserDialogs to close when cleaning up
00162         self._browsers = []
00163 
00164         # This defaults the filters panel to start by taking 50% of the available space
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                 # flattens the _highlight filters list and only adds the item if it doesn't already exist
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         # pack the new filter tuple onto the filter list
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         # place the widget in the proper location
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                 # flattens the _exclude filters list and only adds the item if it doesn't already exist
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         # pack the new filter tuple onto the filter list
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         # place the widget in the proper location
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                 # Test if the filter we are adding already exists if it does use the existing filter
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                 # Test if the filter we are adding already exists if it does use the existing filter
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         # menutext entries turned into
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             # This processes the dynamic list entries (severity, node and topic)
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             # extract column header
00523             columns = lines[0].split(';')
00524             if len(lines) < 2:
00525                 return True
00526 
00527             # join wrapped lines
00528             rows = []
00529             last_wrapped = False
00530             for line in lines[1:]:
00531                 # ignore empty lines
00532                 if not line:
00533                     continue
00534                 # check for quotes and remove them
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                 # ignore line without prefix if previous line was not wrapped
00548                 if not has_prefix and not last_wrapped:
00549                     continue
00550                 # remove wrapped line which is not continued on the next line
00551                 if last_wrapped and has_prefix:
00552                     rows.pop()
00553 
00554                 # add/append lines
00555                 if last_wrapped:
00556                     rows[-1] += line
00557                 else:
00558                     # add line without quote prefix
00559                     rows.append(line)
00560 
00561                 last_wrapped = not has_suffix
00562 
00563             # generate message for each row
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')))


rqt_console
Author(s): Aaron Blasdel
autogenerated on Wed May 3 2017 02:48:27