00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 import 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
00069 self.background_task_cancel = False
00070
00071
00072 self._playhead_lock = threading.RLock()
00073 self._max_play_speed = 1024.0
00074 self._min_play_speed = 1.0 / 1024.0
00075 self._play_speed = 0.0
00076 self._play_all = False
00077 self._playhead_positions_cvs = {}
00078 self._playhead_positions = {}
00079 self._message_loaders = {}
00080 self._messages_cvs = {}
00081 self._messages = {}
00082 self._message_listener_threads = {}
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
00090 self.stick_to_end = False
00091 self._play_timer = QTimer()
00092 self._play_timer.timeout.connect(self.on_idle)
00093 self._play_timer.setInterval(3)
00094
00095
00096 self._context = context
00097 self.popups = {}
00098 self._views = []
00099 self._listeners = {}
00100
00101
00102
00103
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
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
00166 if self._timeline_frame._stamp_left is None:
00167 self._timeline_frame.reset_timeline()
00168
00169
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
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
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
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
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
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
00379 total_messages = len(bag_entries)
00380
00381
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
00723
00724
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]