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


rqt_bag
Author(s): Aaron Blasdel
autogenerated on Fri Jan 3 2014 11:55:06