bag_timeline.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 rospy
00034 import rosbag
00035 import time
00036 import threading
00037 
00038 
00039 from python_qt_binding.QtCore import Qt, QTimer, qWarning, Signal
00040 from python_qt_binding.QtGui import QGraphicsScene, QMessageBox
00041 
00042 import bag_helper
00043 
00044 from .timeline_frame import TimelineFrame
00045 from .message_listener_thread import MessageListenerThread
00046 from .message_loader_thread import MessageLoaderThread
00047 from .player import Player
00048 from .recorder import Recorder
00049 from .timeline_menu import TimelinePopupMenu
00050 
00051 
00052 class BagTimeline(QGraphicsScene):
00053     """
00054     BagTimeline contains bag files, all information required to display the bag data visualization on the screen
00055     Also handles events
00056     """
00057     status_bar_changed_signal = Signal()
00058     selected_region_changed = Signal(rospy.Time, rospy.Time)
00059 
00060     def __init__(self, context, publish_clock):
00061         """
00062         :param context: plugin context hook to enable adding rqt_bag plugin widgets as ROS_GUI snapin panes, ''PluginContext''
00063         """
00064         super(BagTimeline, self).__init__()
00065         self._bags = []
00066         self._bag_lock = threading.RLock()
00067 
00068         self.background_task = None  # Display string
00069         self.background_task_cancel = False
00070 
00071         # Playing / Recording
00072         self._playhead_lock = threading.RLock()
00073         self._max_play_speed = 1024.0  # fastest X play speed
00074         self._min_play_speed = 1.0 / 1024.0  # slowest X play speed
00075         self._play_speed = 0.0
00076         self._play_all = False
00077         self._playhead_positions_cvs = {}
00078         self._playhead_positions = {}  # topic -> (bag, position)
00079         self._message_loaders = {}
00080         self._messages_cvs = {}
00081         self._messages = {}  # topic -> (bag, msg_data)
00082         self._message_listener_threads = {}  # listener -> MessageListenerThread
00083         self._player = False
00084         self._publish_clock = publish_clock
00085         self._recorder = None
00086         self.last_frame = None
00087         self.last_playhead = None
00088         self.desired_playhead = None
00089         self.wrap = True  # should the playhead wrap when it reaches the end?
00090         self.stick_to_end = False  # should the playhead stick to the end?
00091         self._play_timer = QTimer()
00092         self._play_timer.timeout.connect(self.on_idle)
00093         self._play_timer.setInterval(3)
00094 
00095         # Plugin popup management
00096         self._context = context
00097         self.popups = {}
00098         self._views = []
00099         self._listeners = {}
00100 
00101         # Initialize scene
00102         # the timeline renderer fixes use of black pens and fills, so ensure we fix white here for contrast.
00103         # otherwise a dark qt theme will default it to black and the frame render pen will be unreadable
00104         self.setBackgroundBrush(Qt.white)
00105         self._timeline_frame = TimelineFrame()
00106         self._timeline_frame.setPos(0, 0)
00107         self.addItem(self._timeline_frame)
00108 
00109         self.background_progress = 0
00110         self.__closed = False
00111 
00112     def get_context(self):
00113         """
00114         :returns: the ROS_GUI context, 'PluginContext'
00115         """
00116         return self._context
00117 
00118     def handle_close(self):
00119         """
00120         Cleans up the timeline, bag and any threads
00121         """
00122         if self.__closed:
00123             return
00124         else:
00125             self.__closed = True
00126         self._play_timer.stop()
00127         for topic in self._get_topics():
00128             self.stop_publishing(topic)
00129             self._message_loaders[topic].stop()
00130         if self._player:
00131             self._player.stop()
00132         if self._recorder:
00133             self._recorder.stop()
00134         if self.background_task is not None:
00135             self.background_task_cancel = True
00136         self._timeline_frame.handle_close()
00137         for bag in self._bags:
00138             bag.close()
00139         for frame in self._views:
00140             if frame.parent():
00141                 self._context.remove_widget(frame)
00142 
00143     # Bag Management and access
00144     def add_bag(self, bag):
00145         """
00146         creates an indexing thread for each new topic in the bag
00147         fixes the boarders and notifies the indexing thread to index the new items bags
00148         :param bag: ros bag file, ''rosbag.bag''
00149         """
00150         self._bags.append(bag)
00151 
00152         bag_topics = bag_helper.get_topics(bag)
00153 
00154         new_topics = set(bag_topics) - set(self._timeline_frame.topics)
00155 
00156         for topic in new_topics:
00157             self._playhead_positions_cvs[topic] = threading.Condition()
00158             self._messages_cvs[topic] = threading.Condition()
00159             self._message_loaders[topic] = MessageLoaderThread(self, topic)
00160 
00161         self._timeline_frame._start_stamp = self._get_start_stamp()
00162         self._timeline_frame._end_stamp = self._get_end_stamp()
00163         self._timeline_frame.topics = self._get_topics()
00164         self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype()
00165         # If this is the first bag, reset the timeline
00166         if self._timeline_frame._stamp_left is None:
00167             self._timeline_frame.reset_timeline()
00168 
00169         # Invalidate entire index cache for all topics in this bag
00170         with self._timeline_frame.index_cache_cv:
00171             for topic in bag_topics:
00172                 self._timeline_frame.invalidated_caches.add(topic)
00173                 if topic in self._timeline_frame.index_cache:
00174                     del self._timeline_frame.index_cache[topic]
00175 
00176             self._timeline_frame.index_cache_cv.notify()
00177 
00178     #TODO Rethink API and if these need to be visible
00179     def _get_start_stamp(self):
00180         """
00181         :return: first stamp in the bags, ''rospy.Time''
00182         """
00183         with self._bag_lock:
00184             start_stamp = None
00185             for bag in self._bags:
00186                 bag_start_stamp = bag_helper.get_start_stamp(bag)
00187                 if bag_start_stamp is not None and (start_stamp is None or bag_start_stamp < start_stamp):
00188                     start_stamp = bag_start_stamp
00189             return start_stamp
00190 
00191     def _get_end_stamp(self):
00192         """
00193         :return: last stamp in the bags, ''rospy.Time''
00194         """
00195         with self._bag_lock:
00196             end_stamp = None
00197             for bag in self._bags:
00198                 bag_end_stamp = bag_helper.get_end_stamp(bag)
00199                 if bag_end_stamp is not None and (end_stamp is None or bag_end_stamp > end_stamp):
00200                     end_stamp = bag_end_stamp
00201             return end_stamp
00202 
00203     def _get_topics(self):
00204         """
00205         :return: sorted list of topic names, ''list(str)''
00206         """
00207         with self._bag_lock:
00208             topics = set()
00209             for bag in self._bags:
00210                 for topic in bag_helper.get_topics(bag):
00211                     topics.add(topic)
00212             return sorted(topics)
00213 
00214     def _get_topics_by_datatype(self):
00215         """
00216         :return: dict of list of topics for each datatype, ''dict(datatype:list(topic))''
00217         """
00218         with self._bag_lock:
00219             topics_by_datatype = {}
00220             for bag in self._bags:
00221                 for datatype, topics in bag_helper.get_topics_by_datatype(bag).items():
00222                     topics_by_datatype.setdefault(datatype, []).extend(topics)
00223             return topics_by_datatype
00224 
00225     def get_datatype(self, topic):
00226         """
00227         :return: datatype associated with a topic, ''str''
00228         :raises: if there are multiple datatypes assigned to a single topic, ''Exception''
00229         """
00230         with self._bag_lock:
00231             datatype = None
00232             for bag in self._bags:
00233                 bag_datatype = bag_helper.get_datatype(bag, topic)
00234                 if datatype and bag_datatype and (bag_datatype != datatype):
00235                     raise Exception('topic %s has multiple datatypes: %s and %s' % (topic, datatype, bag_datatype))
00236                 if bag_datatype:
00237                     datatype = bag_datatype
00238             return datatype
00239 
00240     def get_entries(self, topics, start_stamp, end_stamp):
00241         """
00242         generator function for bag entries
00243         :param topics: list of topics to query, ''list(str)''
00244         :param start_stamp: stamp to start at, ''rospy.Time''
00245         :param end_stamp: stamp to end at, ''rospy,Time''
00246         :returns: entries the bag file, ''msg''
00247         """
00248         with self._bag_lock:
00249             from rosbag import bag  # for _mergesort
00250             bag_entries = []
00251             for b in self._bags:
00252                 bag_start_time = bag_helper.get_start_stamp(b)
00253                 if bag_start_time is not None and bag_start_time > end_stamp:
00254                     continue
00255 
00256                 bag_end_time = bag_helper.get_end_stamp(b)
00257                 if bag_end_time is not None and bag_end_time < start_stamp:
00258                     continue
00259 
00260                 connections = list(b._get_connections(topics))
00261                 bag_entries.append(b._get_entries(connections, start_stamp, end_stamp))
00262 
00263             for entry, _ in bag._mergesort(bag_entries, key=lambda entry: entry.time):
00264                 yield entry
00265 
00266     def get_entries_with_bags(self, topic, start_stamp, end_stamp):
00267         """
00268         generator function for bag entries
00269         :param topics: list of topics to query, ''list(str)''
00270         :param start_stamp: stamp to start at, ''rospy.Time''
00271         :param end_stamp: stamp to end at, ''rospy,Time''
00272         :returns: tuple of (bag, entry) for the entries in the bag file, ''(rosbag.bag, msg)''
00273         """
00274         with self._bag_lock:
00275             from rosbag import bag  # for _mergesort
00276 
00277             bag_entries = []
00278             bag_by_iter = {}
00279             for b in self._bags:
00280                 bag_start_time = bag_helper.get_start_stamp(b)
00281                 if bag_start_time is not None and bag_start_time > end_stamp:
00282                     continue
00283 
00284                 bag_end_time = bag_helper.get_end_stamp(b)
00285                 if bag_end_time is not None and bag_end_time < start_stamp:
00286                     continue
00287 
00288                 connections = list(b._get_connections(topic))
00289                 it = iter(b._get_entries(connections, start_stamp, end_stamp))
00290                 bag_by_iter[it] = b
00291                 bag_entries.append(it)
00292 
00293             for entry, it in bag._mergesort(bag_entries, key=lambda entry: entry.time):
00294                 yield bag_by_iter[it], entry
00295 
00296     def get_entry(self, t, topic):
00297         """
00298         Access a bag entry
00299         :param t: time, ''rospy.Time''
00300         :param topic: the topic to be accessed, ''str''
00301         :return: tuple of (bag, entry) corisponding to time t and topic, ''(rosbag.bag, msg)''
00302         """
00303         with self._bag_lock:
00304             entry_bag, entry = None, None
00305             for bag in self._bags:
00306                 bag_entry = bag._get_entry(t, bag._get_connections(topic))
00307                 if bag_entry and (not entry or bag_entry.time > entry.time):
00308                     entry_bag, entry = bag, bag_entry
00309 
00310             return entry_bag, entry
00311 
00312     def get_entry_after(self, t):
00313         """
00314         Access a bag entry
00315         :param t: time, ''rospy.Time''
00316         :return: tuple of (bag, entry) corisponding to time t, ''(rosbag.bag, msg)''
00317         """
00318         with self._bag_lock:
00319             entry_bag, entry = None, None
00320             for bag in self._bags:
00321                 bag_entry = bag._get_entry_after(t, bag._get_connections())
00322                 if bag_entry and (not entry or bag_entry.time < entry.time):
00323                     entry_bag, entry = bag, bag_entry
00324 
00325             return entry_bag, entry
00326 
00327     def get_next_message_time(self):
00328         """
00329         :return: time of the next message after the current playhead position,''rospy.Time''
00330         """
00331         if self._timeline_frame.playhead is None:
00332             return None
00333 
00334         _, entry = self.get_entry_after(self._timeline_frame.playhead)
00335         if entry is None:
00336             return self._timeline_frame._start_stamp
00337 
00338         return entry.time
00339 
00340     ### Copy messages to...
00341 
00342     def start_background_task(self, background_task):
00343         """
00344         Verify that a background task is not currently running before starting a new one
00345         :param background_task: name of the background task, ''str''
00346         """
00347         if self.background_task is not None:
00348             QMessageBox(QMessageBox.Warning, 'Exclamation', 'Background operation already running:\n\n%s' % self.background_task, QMessageBox.Ok).exec_()
00349             return False
00350 
00351         self.background_task = background_task
00352         self.background_task_cancel = False
00353         return True
00354 
00355     def stop_background_task(self):
00356         self.background_task = None
00357 
00358     def copy_region_to_bag(self, filename):
00359         if len(self._bags) > 0:
00360             self._export_region(filename, self._timeline_frame.topics, self._timeline_frame.play_region[0], self._timeline_frame.play_region[1])
00361 
00362     def _export_region(self, path, topics, start_stamp, end_stamp):
00363         """
00364         Starts a thread to save the current selection to a new bag file
00365         :param path: filesystem path to write to, ''str''
00366         :param topics: topics to write to the file, ''list(str)''
00367         :param start_stamp: start of area to save, ''rospy.Time''
00368         :param end_stamp: end of area to save, ''rospy.Time''
00369         """
00370         if not self.start_background_task('Copying messages to "%s"' % path):
00371             return
00372         # TODO implement a status bar area with information on the current save status
00373         bag_entries = list(self.get_entries_with_bags(topics, start_stamp, end_stamp))
00374 
00375         if self.background_task_cancel:
00376             return
00377 
00378         # Get the total number of messages to copy
00379         total_messages = len(bag_entries)
00380 
00381         # If no messages, prompt the user and return
00382         if total_messages == 0:
00383             QMessageBox(QMessageBox.Warning, 'rqt_bag', 'No messages found', QMessageBox.Ok).exec_()
00384             self.stop_background_task()
00385             return
00386 
00387         # Open the path for writing
00388         try:
00389             export_bag = rosbag.Bag(path, 'w')
00390         except Exception:
00391             QMessageBox(QMessageBox.Warning, 'rqt_bag', 'Error opening bag file [%s] for writing' % path, QMessageBox.Ok).exec_()
00392             self.stop_background_task()
00393             return
00394 
00395         # Run copying in a background thread
00396         self._export_thread = threading.Thread(target=self._run_export_region, args=(export_bag, topics, start_stamp, end_stamp, bag_entries))
00397         self._export_thread.start()
00398 
00399     def _run_export_region(self, export_bag, topics, start_stamp, end_stamp, bag_entries):
00400         """
00401         Threaded function that saves the current selection to a new bag file
00402         :param export_bag: bagfile to write to, ''rosbag.bag''
00403         :param topics: topics to write to the file, ''list(str)''
00404         :param start_stamp: start of area to save, ''rospy.Time''
00405         :param end_stamp: end of area to save, ''rospy.Time''
00406         """
00407         total_messages = len(bag_entries)
00408         update_step = max(1, total_messages / 100)
00409         message_num = 1
00410         progress = 0
00411         # Write out the messages
00412         for bag, entry in bag_entries:
00413             if self.background_task_cancel:
00414                 break
00415             try:
00416                 topic, msg, t = self.read_message(bag, entry.position)
00417                 export_bag.write(topic, msg, t)
00418             except Exception as ex:
00419                 qWarning('Error exporting message at position %s: %s' % (str(entry.position), str(ex)))
00420                 export_bag.close()
00421                 self.stop_background_task()
00422                 return
00423 
00424             if message_num % update_step == 0 or message_num == total_messages:
00425                 new_progress = int(100.0 * (float(message_num) / total_messages))
00426                 if new_progress != progress:
00427                     progress = new_progress
00428                     if not self.background_task_cancel:
00429                         self.background_progress = progress
00430                         self.status_bar_changed_signal.emit()
00431 
00432             message_num += 1
00433 
00434         # Close the bag
00435         try:
00436             self.background_progress = 0
00437             self.status_bar_changed_signal.emit()
00438             export_bag.close()
00439         except Exception as ex:
00440             QMessageBox(QMessageBox.Warning, 'rqt_bag', 'Error closing bag file [%s]: %s' % (export_bag.filename, str(ex)), QMessageBox.Ok).exec_()
00441         self.stop_background_task()
00442 
00443     def read_message(self, bag, position):
00444         with self._bag_lock:
00445             return bag._read_message(position)
00446 
00447     ### Mouse events
00448     def on_mouse_down(self, event):
00449         if event.buttons() == Qt.LeftButton:
00450             self._timeline_frame.on_left_down(event)
00451         elif event.buttons() == Qt.MidButton:
00452             self._timeline_frame.on_middle_down(event)
00453         elif event.buttons() == Qt.RightButton:
00454             topic = self._timeline_frame.map_y_to_topic(event.y())
00455             TimelinePopupMenu(self, event, topic)
00456 
00457     def on_mouse_up(self, event):
00458         self._timeline_frame.on_mouse_up(event)
00459 
00460     def on_mouse_move(self, event):
00461         self._timeline_frame.on_mouse_move(event)
00462 
00463     def on_mousewheel(self, event):
00464         self._timeline_frame.on_mousewheel(event)
00465 
00466     # Zooming
00467 
00468     def zoom_in(self):
00469         self._timeline_frame.zoom_in()
00470 
00471     def zoom_out(self):
00472         self._timeline_frame.zoom_out()
00473 
00474     def reset_zoom(self):
00475         self._timeline_frame.reset_zoom()
00476 
00477     def translate_timeline_left(self):
00478         self._timeline_frame.translate_timeline_left()
00479 
00480     def translate_timeline_right(self):
00481         self._timeline_frame.translate_timeline_right()
00482 
00483     ### Publishing
00484     def is_publishing(self, topic):
00485         return self._player and self._player.is_publishing(topic)
00486 
00487     def start_publishing(self, topic):
00488         if not self._player and not self._create_player():
00489             return False
00490 
00491         self._player.start_publishing(topic)
00492         return True
00493 
00494     def stop_publishing(self, topic):
00495         if not self._player:
00496             return False
00497 
00498         self._player.stop_publishing(topic)
00499         return True
00500 
00501     def _create_player(self):
00502         if not self._player:
00503             try:
00504                 self._player = Player(self)
00505                 if self._publish_clock:
00506                     self._player.start_clock_publishing()
00507             except Exception as ex:
00508                 qWarning('Error starting player; aborting publish: %s' % str(ex))
00509                 return False
00510 
00511         return True
00512 
00513     def set_publishing_state(self, start_publishing):
00514         if start_publishing:
00515             for topic in self._timeline_frame.topics:
00516                 if not self.start_publishing(topic):
00517                     break
00518         else:
00519             for topic in self._timeline_frame.topics:
00520                 self.stop_publishing(topic)
00521 
00522     # property: play_all
00523     def _get_play_all(self):
00524         return self._play_all
00525 
00526     def _set_play_all(self, play_all):
00527         if play_all == self._play_all:
00528             return
00529 
00530         self._play_all = not self._play_all
00531 
00532         self.last_frame = None
00533         self.last_playhead = None
00534         self.desired_playhead = None
00535 
00536     play_all = property(_get_play_all, _set_play_all)
00537 
00538     def toggle_play_all(self):
00539         self.play_all = not self.play_all
00540 
00541     ### Playing
00542     def on_idle(self):
00543         self._step_playhead()
00544 
00545     def _step_playhead(self):
00546         """
00547         moves the playhead to the next position based on the desired position
00548         """
00549         # Reset when the playing mode switchs
00550         if self._timeline_frame.playhead != self.last_playhead:
00551             self.last_frame = None
00552             self.last_playhead = None
00553             self.desired_playhead = None
00554 
00555         if self._play_all:
00556             self.step_next_message()
00557         else:
00558             self.step_fixed()
00559 
00560     def step_fixed(self):
00561         """
00562         Moves the playhead a fixed distance into the future based on the current play speed
00563         """
00564         if self.play_speed == 0.0 or not self._timeline_frame.playhead:
00565             self.last_frame = None
00566             self.last_playhead = None
00567             return
00568 
00569         now = rospy.Time.from_sec(time.time())
00570         if self.last_frame:
00571             # Get new playhead
00572             if self.stick_to_end:
00573                 new_playhead = self.end_stamp
00574             else:
00575                 new_playhead = self._timeline_frame.playhead + rospy.Duration.from_sec((now - self.last_frame).to_sec() * self.play_speed)
00576 
00577                 start_stamp, end_stamp = self._timeline_frame.play_region
00578 
00579                 if new_playhead > end_stamp:
00580                     if self.wrap:
00581                         if self.play_speed > 0.0:
00582                             new_playhead = start_stamp
00583                         else:
00584                             new_playhead = end_stamp
00585                     else:
00586                         new_playhead = end_stamp
00587 
00588                         if self.play_speed > 0.0:
00589                             self.stick_to_end = True
00590 
00591                 elif new_playhead < start_stamp:
00592                     if self.wrap:
00593                         if self.play_speed < 0.0:
00594                             new_playhead = end_stamp
00595                         else:
00596                             new_playhead = start_stamp
00597                     else:
00598                         new_playhead = start_stamp
00599 
00600             # Update the playhead
00601             self._timeline_frame.playhead = new_playhead
00602 
00603         self.last_frame = now
00604         self.last_playhead = self._timeline_frame.playhead
00605 
00606     def step_next_message(self):
00607         """
00608         Move the playhead to the next message
00609         """
00610         if self.play_speed <= 0.0 or not self._timeline_frame.playhead:
00611             self.last_frame = None
00612             self.last_playhead = None
00613             return
00614 
00615         if self.last_frame:
00616             if not self.desired_playhead:
00617                 self.desired_playhead = self._timeline_frame.playhead
00618             else:
00619                 delta = rospy.Time.from_sec(time.time()) - self.last_frame
00620                 if delta > rospy.Duration.from_sec(0.1):
00621                     delta = rospy.Duration.from_sec(0.1)
00622                 self.desired_playhead += delta
00623 
00624             # Get the occurrence of the next message
00625             next_message_time = self.get_next_message_time()
00626 
00627             if next_message_time < self.desired_playhead:
00628                 self._timeline_frame.playhead = next_message_time
00629             else:
00630                 self._timeline_frame.playhead = self.desired_playhead
00631 
00632         self.last_frame = rospy.Time.from_sec(time.time())
00633         self.last_playhead = self._timeline_frame.playhead
00634     ### Recording
00635 
00636     def record_bag(self, filename, all=True, topics=[], regex=False, limit=0):
00637         try:
00638             self._recorder = Recorder(filename, bag_lock=self._bag_lock, all=all, topics=topics, regex=regex, limit=limit)
00639         except Exception, ex:
00640             qWarning('Error opening bag for recording [%s]: %s' % (filename, str(ex)))
00641             return
00642 
00643         self._recorder.add_listener(self._message_recorded)
00644 
00645         self.add_bag(self._recorder.bag)
00646 
00647         self._recorder.start()
00648 
00649         self.wrap = False
00650         self._timeline_frame._index_cache_thread.period = 0.1
00651 
00652         self.update()
00653 
00654     def toggle_recording(self):
00655         if self._recorder:
00656             self._recorder.toggle_paused()
00657             self.update()
00658 
00659     def _message_recorded(self, topic, msg, t):
00660         if self._timeline_frame._start_stamp is None:
00661             self._timeline_frame._start_stamp = t
00662             self._timeline_frame._end_stamp = t
00663             self._timeline_frame._playhead = t
00664         elif self._timeline_frame._end_stamp is None or t > self._timeline_frame._end_stamp:
00665             self._timeline_frame._end_stamp = t
00666 
00667         if not self._timeline_frame.topics or topic not in self._timeline_frame.topics:
00668             self._timeline_frame.topics = self._get_topics()
00669             self._timeline_frame._topics_by_datatype = self._get_topics_by_datatype()
00670 
00671             self._playhead_positions_cvs[topic] = threading.Condition()
00672             self._messages_cvs[topic] = threading.Condition()
00673             self._message_loaders[topic] = MessageLoaderThread(self, topic)
00674 
00675         if self._timeline_frame._stamp_left is None:
00676             self.reset_zoom()
00677 
00678         # Notify the index caching thread that it has work to do
00679         with self._timeline_frame.index_cache_cv:
00680             self._timeline_frame.invalidated_caches.add(topic)
00681             self._timeline_frame.index_cache_cv.notify()
00682 
00683         if topic in self._listeners:
00684             for listener in self._listeners[topic]:
00685                 try:
00686                     listener.timeline_changed()
00687                 except Exception, ex:
00688                     qWarning('Error calling timeline_changed on %s: %s' % (type(listener), str(ex)))
00689 
00690     ### Views / listeners
00691     def add_view(self, topic, frame):
00692         self._views.append(frame)
00693 
00694     def has_listeners(self, topic):
00695         return topic in self._listeners
00696 
00697     def add_listener(self, topic, listener):
00698         self._listeners.setdefault(topic, []).append(listener)
00699 
00700         self._message_listener_threads[(topic, listener)] = MessageListenerThread(self, topic, listener)
00701         # Notify the message listeners
00702         self._message_loaders[topic].reset()
00703         with self._playhead_positions_cvs[topic]:
00704             self._playhead_positions_cvs[topic].notify_all()
00705 
00706         self.update()
00707 
00708     def remove_listener(self, topic, listener):
00709         topic_listeners = self._listeners.get(topic)
00710         if topic_listeners is not None and listener in topic_listeners:
00711             topic_listeners.remove(listener)
00712 
00713             if len(topic_listeners) == 0:
00714                 del self._listeners[topic]
00715 
00716             # Stop the message listener thread
00717             if (topic, listener) in self._message_listener_threads:
00718                 self._message_listener_threads[(topic, listener)].stop()
00719                 del self._message_listener_threads[(topic, listener)]
00720             self.update()
00721 
00722     ### Playhead
00723 
00724     # property: play_speed
00725     def _get_play_speed(self):
00726         if self._timeline_frame._paused:
00727             return 0.0
00728         return self._play_speed
00729 
00730     def _set_play_speed(self, play_speed):
00731         if play_speed == self._play_speed:
00732             return
00733 
00734         if play_speed > 0.0:
00735             self._play_speed = min(self._max_play_speed, max(self._min_play_speed, play_speed))
00736         elif play_speed < 0.0:
00737             self._play_speed = max(-self._max_play_speed, min(-self._min_play_speed, play_speed))
00738         else:
00739             self._play_speed = play_speed
00740 
00741         if self._play_speed < 1.0:
00742             self.stick_to_end = False
00743 
00744         self.update()
00745     play_speed = property(_get_play_speed, _set_play_speed)
00746 
00747     def toggle_play(self):
00748         if self._play_speed != 0.0:
00749             self.play_speed = 0.0
00750         else:
00751             self.play_speed = 1.0
00752 
00753     def navigate_play(self):
00754         self.play_speed = 1.0
00755         self._play_timer.start()
00756 
00757     def navigate_stop(self):
00758         self.play_speed = 0.0
00759         self._play_timer.stop()
00760 
00761     def navigate_rewind(self):
00762         if self._play_speed < 0.0:
00763             new_play_speed = self._play_speed * 2.0
00764         elif self._play_speed == 0.0:
00765             new_play_speed = -1.0
00766         else:
00767             new_play_speed = self._play_speed * 0.5
00768 
00769         self.play_speed = new_play_speed
00770 
00771     def navigate_fastforward(self):
00772         if self._play_speed > 0.0:
00773             new_play_speed = self._play_speed * 2.0
00774         elif self._play_speed == 0.0:
00775             new_play_speed = 2.0
00776         else:
00777             new_play_speed = self._play_speed * 0.5
00778 
00779         self.play_speed = new_play_speed
00780 
00781     def navigate_start(self):
00782         self._timeline_frame.playhead = self._timeline_frame.play_region[0]
00783 
00784     def navigate_end(self):
00785         self._timeline_frame.playhead = self._timeline_frame.play_region[1]


rqt_bag
Author(s): Aaron Blasdel, Tim Field
autogenerated on Mon Oct 6 2014 07:15:30