def __init__(self, file, isRetinaEnabled, parent, webcammode=False): super(QWidget, self).__init__() self.parent = parent self.isVideo = False self.webcam = webcammode if file: self.file = file self.isVideo = isinstance(file, Video) self.timer = QTimer() self.timer.timeout.connect(self.nextFrame) self.frames = parent.currentFrames # set up video capture dependent on source if self.isVideo: self.cap = cv2.VideoCapture(self.file.filepath) codec = cv2.VideoWriter_fourcc(*self.filetypes[self.file.type]) self.cap.set(cv2.CAP_PROP_FOURCC, codec) self.maxFrames = self.cap.get(cv2.CAP_PROP_FRAME_COUNT) # if webcam mode, start immediately elif self.webcam: self.cap = cv2.VideoCapture(0) self.isBGR = True self.timer.start(1000.0 / 30) # if still images, no need for video capture else: self.maxFrames = len(self.frames) - 1 self.parent.scrubSlider.setRange(0, self.maxFrames) self.parent.scrubSlider_2.setRange(0, self.maxFrames) self.framePos = 0 self.videoFrame = parent.label self.focalFrame = parent.focallabel self.corticalFrame = parent.corticallabel self.focusFrame = parent.biglabel if isRetinaEnabled: self.retina, self.fixation = ip.prepareLiveRetina(self.cap) self.cortex = ip.createCortex()
def __init__(self): super(Foo, self).__init__() self._action_set = None self._update_current_value_signal.connect(self._update_current_value) self._scene_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_scene_dialog.ui') loadUi(ui_file, self._scene_notification) self._scene_notification_timer = QTimer(main_window) self._scene_notification_timer.setInterval(5000) self._scene_notification_timer.setSingleShot(True) self._scene_notification_timer.timeout.connect(self._hide_scene_notification) self._scene_notification.finished.connect(self._hide_scene_notification) self._input_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_input_method_dialog.ui') loadUi(ui_file, self._input_notification) self._input_notification_timer = QTimer(main_window) self._input_notification_timer.setInterval(5000) self._input_notification_timer.setSingleShot(True) self._input_notification_timer.timeout.connect(self._hide_input_method_notification) self._input_notification.finished.connect(self._hide_input_method_notification) self._show_input_notification_signal.connect(self._show_input_notification) self._hide_input_notification_signal.connect(self._hide_input_method_notification) self._show_input_notification_signal.connect(self._show_scene_notification) self._hide_input_notification_signal.connect(self._hide_scene_notification) self._update_color_of_line_edit_signal.connect(self._update_color_of_line_edit)
def __init__(self, context, icon_paths=[]): self._graveyard = [] ok_icon = ['bg-green.svg', 'ic-diagnostics.svg'] warn_icon = ['bg-yellow.svg', 'ic-diagnostics.svg', 'ol-warn-badge.svg'] err_icon = ['bg-red.svg', 'ic-diagnostics.svg', 'ol-err-badge.svg'] stale_icon = ['bg-grey.svg', 'ic-diagnostics.svg', 'ol-stale-badge.svg'] icons = [ok_icon, warn_icon, err_icon, stale_icon] super(MonitorDashWidget, self).__init__('MonitorWidget', icons, icon_paths=icon_paths) self.setFixedSize(self._icons[0].actualSize(QSize(50, 30))) self._monitor = None self._close_mutex = QMutex() self._show_mutex = QMutex() self._last_update = rospy.Time.now() self.context = context self.clicked.connect(self._show_monitor) self._monitor_shown = False self.setToolTip('Diagnostics') self._diagnostics_toplevel_state_sub = rospy.Subscriber('diagnostics_toplevel_state', DiagnosticStatus, self.toplevel_state_callback) self._top_level_state = -1 self._stall_timer = QTimer() self._stall_timer.timeout.connect(self._stalled) self._stalled() self._plugin_settings = None self._instance_settings = None
def __init__(self): super(Foo, self).__init__() self._action_set = None self._update_current_value_signal.connect(self._update_current_value) self._scene_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_scene_dialog.ui') loadUi(ui_file, self._scene_notification) self._scene_notification_timer = QTimer(main_window) self._scene_notification_timer.setInterval(5000) self._scene_notification_timer.setSingleShot(True) self._scene_notification_timer.timeout.connect( self._hide_scene_notification) self._scene_notification.finished.connect( self._hide_scene_notification) self._input_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_input_method_dialog.ui') loadUi(ui_file, self._input_notification) self._input_notification_timer = QTimer(main_window) self._input_notification_timer.setInterval(5000) self._input_notification_timer.setSingleShot(True) self._input_notification_timer.timeout.connect( self._hide_input_method_notification) self._input_notification.finished.connect( self._hide_input_method_notification) self._show_input_notification_signal.connect( self._show_input_notification) self._hide_input_notification_signal.connect( self._hide_input_method_notification) self._show_input_notification_signal.connect( self._show_scene_notification) self._hide_input_notification_signal.connect( self._hide_scene_notification) self._update_color_of_line_edit_signal.connect( self._update_color_of_line_edit)
def __init__(self): super(MatPlotWidget, self).__init__() self.setObjectName("MatPlotWidget") ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "MatPlot.ui") loadUi(ui_file, self, {"MatDataPlot": MatDataPlot}) self.subscribe_topic_button.setEnabled(False) self._topic_completer = TopicCompleter(self.topic_edit) self.topic_edit.setCompleter(self._topic_completer) self._start_time = rospy.get_time() self._rosdata = {} # setup drag 'n drop self.data_plot.dropEvent = self.dropEvent self.data_plot.dragEnterEvent = self.dragEnterEvent # init and start update timer for plot self._update_plot_timer = QTimer(self) self._update_plot_timer.timeout.connect(self.update_plot) self._update_plot_timer.start(40) # connect combobox self.comboBox.currentIndexChanged.connect(self.on_combo_box_changed) # params (colors) self.texts = {} self.data_plot._colors = {} self.sub_topics = {} n = 0 for tag in rospy.get_param("/tags"): self.texts[tag] = rospy.get_param("/" + tag + "_text") color = [float(i) for i in list(rospy.get_param("/" + tag + "_color").split(","))] self.data_plot._colors[self.texts[tag]] = color if n != 0: # dont have a topic for the reference self.sub_topics[tag] = "/error_mags/" + rospy.get_param("/tags")[0] + "_ref/" + tag + "_tgt/mag_horiz" else: n += 1 # start with subscription to filtered self.add_topic("/error_mags/rtk_ref/flt_tgt/mag_horiz")
class MonitorDashWidget(IconToolButton): """ A widget which brings up the rqt_robot_monitor. :param context: The plugin context to create the monitor in. :type context: qt_gui.plugin_context.PluginContext """ def __init__(self, context, icon_paths=[]): self._graveyard = [] ok_icon = ['bg-green.svg', 'ic-diagnostics.svg'] warn_icon = ['bg-yellow.svg', 'ic-diagnostics.svg', 'ol-warn-badge.svg'] err_icon = ['bg-red.svg', 'ic-diagnostics.svg', 'ol-err-badge.svg'] stale_icon = ['bg-grey.svg', 'ic-diagnostics.svg', 'ol-stale-badge.svg'] icons = [ok_icon, warn_icon, err_icon, stale_icon] super(MonitorDashWidget, self).__init__('MonitorWidget', icons, icon_paths=icon_paths) self.setFixedSize(self._icons[0].actualSize(QSize(50, 30))) self._monitor = None self._close_mutex = QMutex() self._show_mutex = QMutex() self._last_update = rospy.Time.now() self.context = context self.clicked.connect(self._show_monitor) self._monitor_shown = False self.setToolTip('Diagnostics') self._diagnostics_toplevel_state_sub = rospy.Subscriber('diagnostics_toplevel_state', DiagnosticStatus, self.toplevel_state_callback) self._top_level_state = -1 self._stall_timer = QTimer() self._stall_timer.timeout.connect(self._stalled) self._stalled() self._plugin_settings = None self._instance_settings = None def toplevel_state_callback(self, msg): self._is_stale = False self._stall_timer.start(5000) if self._top_level_state != msg.level: if (msg.level >= 2): self.update_state(2) self.setToolTip("Diagnostics: Error") elif (msg.level == 1): self.update_state(1) self.setToolTip("Diagnostics: Warning") else: self.update_state(0) self.setToolTip("Diagnostics: OK") self._top_level_state = msg.level def _stalled(self): self._stall_timer.stop() self._is_stale = True self.update_state(3) self.setToolTip("Diagnostics: Stale\nNo message received on dashboard_agg in the last 5 seconds") def _show_monitor(self): with QMutexLocker(self._show_mutex): try: if self._monitor_shown: self.context.remove_widget(self._monitor) self._monitor_close() self._monitor_shown = False else: self._monitor = RobotMonitorWidget(self.context, 'diagnostics_agg') if self._plugin_settings: self._monitor.restore_settings(self._plugin_settings, self._instance_settings) self.context.add_widget(self._monitor) self._monitor_shown = True except Exception: if self._monitor_shown == False: raise # TODO when closeEvents is available fix this hack (It ensures the button will toggle correctly) self._monitor_shown = False self._show_monitor() def _monitor_close(self): if self._monitor_shown: with QMutexLocker(self._close_mutex): if self._plugin_settings: self._monitor.save_settings(self._plugin_settings, self._instance_settings) self._monitor.shutdown() self._monitor.close() self._graveyard.append(self._monitor) self._monitor = None def shutdown_widget(self): if self._monitor: self._monitor.shutdown() self._diagnostics_toplevel_state_sub.unregister() def save_settings(self, plugin_settings, instance_settings): if self._monitor_shown: self._monitor.save_settings(self._plugin_settings, self._instance_settings) def restore_settings(self, plugin_settings, instance_settings): self._plugin_settings = plugin_settings self._instance_settings = instance_settings
class Foo(QObject): current_values_changed = Signal(str) current_duration_changed = Signal(float) _update_current_value_signal = Signal() _show_input_notification_signal = Signal() _hide_input_notification_signal = Signal() _show_scene_notification_signal = Signal() _hide_scene_notification_signal = Signal() _update_color_of_line_edit_signal = Signal() def __init__(self): super(Foo, self).__init__() self._action_set = None self._update_current_value_signal.connect(self._update_current_value) self._scene_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_scene_dialog.ui') loadUi(ui_file, self._scene_notification) self._scene_notification_timer = QTimer(main_window) self._scene_notification_timer.setInterval(5000) self._scene_notification_timer.setSingleShot(True) self._scene_notification_timer.timeout.connect( self._hide_scene_notification) self._scene_notification.finished.connect( self._hide_scene_notification) self._input_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_input_method_dialog.ui') loadUi(ui_file, self._input_notification) self._input_notification_timer = QTimer(main_window) self._input_notification_timer.setInterval(5000) self._input_notification_timer.setSingleShot(True) self._input_notification_timer.timeout.connect( self._hide_input_method_notification) self._input_notification.finished.connect( self._hide_input_method_notification) self._show_input_notification_signal.connect( self._show_input_notification) self._hide_input_notification_signal.connect( self._hide_input_method_notification) self._show_input_notification_signal.connect( self._show_scene_notification) self._hide_input_notification_signal.connect( self._hide_scene_notification) self._update_color_of_line_edit_signal.connect( self._update_color_of_line_edit) def _hide_scene_notification(self, result=None): self._scene_notification_timer.stop() self._scene_notification.hide() def _hide_input_method_notification(self, result=None): self._input_notification_timer.stop() self._input_notification.hide() # this method is call by ROS, all UI interaction is delayed via signals def update_current_value(self): global check_collisions global currently_in_collision if self._action_set is not None: self._action_set.stop() self._action_set = kontrol_subscriber.get_action_set() if self._action_set is not None: self.current_duration_changed.emit(self._action_set.get_duration()) if not is_in_slider_mode(): currently_in_collision = False self._show_input_notification_signal.emit() return if self._input_notification_timer.isActive(): self._hide_input_notification_signal.emit() #print('update_current_value()') if not kontrol_subscriber.is_valid_action_set(): self._show_scene_notification_signal.emit() return if self._scene_notification_timer.isActive(): self._hide_scene_notification_signal.emit() joint_values = kontrol_subscriber.get_joint_values() if check_collisions: in_collision = collision_checker.is_in_collision(joint_values) else: in_collision = False collision_toggled = (currently_in_collision != in_collision) currently_in_collision = in_collision if collision_toggled: self._update_color_of_line_edit_signal.emit() self._update_current_value_signal.emit() def _show_input_notification(self): # open dialog which closes after some s_hide_scene_notification_hide_scene_notificationeconds or when confirmed manually self._input_notification.show() self._input_notification_timer.start() def _show_scene_notification(self): # open dialog which closes after some seconds or when confirmed manually self._scene_notification.show() self._scene_notification_timer.start() def _update_color_of_line_edit(self): global currently_in_collision palette = main_window.lineEdit.palette() if currently_in_collision: palette.setColor(QPalette.Text, QColor(255, 0, 0)) else: palette.setColor(QPalette.Text, default_color) main_window.lineEdit.setPalette(palette) def _update_current_value(self): global currently_in_collision main_window.append_pushButton.setEnabled(not currently_in_collision) main_window.insert_before_pushButton.setEnabled( not currently_in_collision) value = self._action_set.to_string() self.current_values_changed.emit(value) for action in self._action_set._actions: action._duration = 0.1 if use_sim_time else 0.5 self._action_set.execute()
# hide design-only widgets #main_window.square_tableWidget.setVisible(False) sigint_called = False def sigint_handler(*args): global sigint_called print('\nsigint_handler()') sigint_called = True main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) # replace placeholder with rviz visualization panel index = main_window.robot_view_verticalLayout.indexOf( main_window.robot_view_widget) stretch = main_window.robot_view_verticalLayout.stretch(index) main_window.robot_view_verticalLayout.removeWidget( main_window.robot_view_widget) robot_view = rviz.VisualizationPanel() # hide rviz display list robot_view.children()[1].hide() main_window.robot_view_verticalLayout.insertWidget(index, robot_view, stretch) robot_view.setSizes([0])
class VideoPlayer(QWidget): """Enables playback functions and broadcasting of video stream to UI elements Subscribers are named individually because they require different images or data """ video = None videoFrame = None focalFrame = None corticalFrame = None focusFrame = None framePos = 0 maxFrames = 0 retina = None cortex = None fixation = None filetypes = {'mp4': 'mp4v', 'jpg': 'jpeg', 'avi': 'xvid'} def __init__(self, file, isRetinaEnabled, parent, webcammode=False): super(QWidget, self).__init__() self.parent = parent self.isVideo = False self.webcam = webcammode if file: self.file = file self.isVideo = isinstance(file, Video) self.timer = QTimer() self.timer.timeout.connect(self.nextFrame) self.frames = parent.currentFrames # set up video capture dependent on source if self.isVideo: self.cap = cv2.VideoCapture(self.file.filepath) codec = cv2.VideoWriter_fourcc(*self.filetypes[self.file.type]) self.cap.set(cv2.CAP_PROP_FOURCC, codec) self.maxFrames = self.cap.get(cv2.CAP_PROP_FRAME_COUNT) # if webcam mode, start immediately elif self.webcam: self.cap = cv2.VideoCapture(0) self.isBGR = True self.timer.start(1000.0 / 30) # if still images, no need for video capture else: self.maxFrames = len(self.frames) - 1 self.parent.scrubSlider.setRange(0, self.maxFrames) self.parent.scrubSlider_2.setRange(0, self.maxFrames) self.framePos = 0 self.videoFrame = parent.label self.focalFrame = parent.focallabel self.corticalFrame = parent.corticallabel self.focusFrame = parent.biglabel if isRetinaEnabled: self.retina, self.fixation = ip.prepareLiveRetina(self.cap) self.cortex = ip.createCortex() def nextFrame(self): """Retrieves next frame for display whether video or image""" if self.isVideo or self.webcam: ret, frame = self.cap.read() if ret: self.framePos = self.cap.get(cv2.CAP_PROP_POS_FRAMES) self.updateDisplay(frame) else: self.framePos += 1 if self.framePos < self.maxFrames else 0 self.setCurrent() def start(self): self.timer.start(1000.0 / 30) self.parent.startButton.setDisabled(True) self.parent.startButton_2.setDisabled(True) self.parent.pauseButton.setDisabled(False) self.parent.pauseButton_2.setDisabled(False) def pause(self): self.timer.stop() self.parent.pauseButton.setDisabled(True) self.parent.pauseButton_2.setDisabled(True) self.parent.startButton.setDisabled(False) self.parent.startButton_2.setDisabled(False) def setCurrent(self): """Sets the current frame based on user input from playback buttons""" if self.framePos <= self.maxFrames: if self.isVideo: self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.framePos) else: self.currentframe = self.frames[self.framePos] self.updateDisplay(self.currentframe.image) def skip(self, framePos): self.framePos = framePos self.setCurrent() def skipBck(self): self.framePos = self.framePos - 1 if self.framePos > 0 else 0 self.setCurrent() def skipFwd(self): self.framePos = self.framePos + 1 if self.framePos < self.maxFrames else 0 self.setCurrent() def updateDisplay(self, frame): """Update all subscribed UI elements with images or data""" self.parent.scrubSlider.setValue(self.framePos) self.parent.scrubSlider_2.setValue(self.framePos) self.parent.frameNum.display(self.framePos) self.parent.frameNum_2.display(self.framePos) # update the metadata table if we're on the main tab if self.parent.maintabWidget.currentIndex() == 1: self.parent.displayMetaData(self.framePos) if not (self.isVideo or self.webcam): self.isBGR = self.currentframe.vectortype == 'BGR' self.videoFrame.setPixmap( ip.convertToPixmap(frame, 480, 360, self.isBGR)) # if retina is activated display live backprojection and cortical image if self.retina: v = self.retina.sample(frame, self.fixation) tight = self.retina.backproject_last() cortical = self.cortex.cort_img(v) self.focalFrame.setPixmap( ip.convertToPixmap(tight, 480, 360, self.isBGR)) self.corticalFrame.setPixmap( ip.convertToPixmap(cortical, 480, 360, self.isBGR)) self.focusFrame.setPixmap( ip.convertToPixmap(cortical, 1280, 720, self.isBGR)) else: self.focusFrame.setPixmap( ip.convertToPixmap(frame, 1280, 720, self.isBGR))
class Foo(QObject): current_values_changed = Signal(str) current_duration_changed = Signal(float) _update_current_value_signal = Signal() _show_input_notification_signal = Signal() _hide_input_notification_signal = Signal() _show_scene_notification_signal = Signal() _hide_scene_notification_signal = Signal() _update_color_of_line_edit_signal = Signal() def __init__(self): super(Foo, self).__init__() self._action_set = None self._update_current_value_signal.connect(self._update_current_value) self._scene_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_scene_dialog.ui') loadUi(ui_file, self._scene_notification) self._scene_notification_timer = QTimer(main_window) self._scene_notification_timer.setInterval(5000) self._scene_notification_timer.setSingleShot(True) self._scene_notification_timer.timeout.connect(self._hide_scene_notification) self._scene_notification.finished.connect(self._hide_scene_notification) self._input_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'wrong_input_method_dialog.ui') loadUi(ui_file, self._input_notification) self._input_notification_timer = QTimer(main_window) self._input_notification_timer.setInterval(5000) self._input_notification_timer.setSingleShot(True) self._input_notification_timer.timeout.connect(self._hide_input_method_notification) self._input_notification.finished.connect(self._hide_input_method_notification) self._show_input_notification_signal.connect(self._show_input_notification) self._hide_input_notification_signal.connect(self._hide_input_method_notification) self._show_input_notification_signal.connect(self._show_scene_notification) self._hide_input_notification_signal.connect(self._hide_scene_notification) self._update_color_of_line_edit_signal.connect(self._update_color_of_line_edit) def _hide_scene_notification(self, result=None): self._scene_notification_timer.stop() self._scene_notification.hide() def _hide_input_method_notification(self, result=None): self._input_notification_timer.stop() self._input_notification.hide() # this method is call by ROS, all UI interaction is delayed via signals def update_current_value(self): global check_collisions global currently_in_collision if self._action_set is not None: self._action_set.stop() self._action_set = kontrol_subscriber.get_action_set() if self._action_set is not None: self.current_duration_changed.emit(self._action_set.get_duration()) if not is_in_slider_mode(): currently_in_collision = False self._show_input_notification_signal.emit() return if self._input_notification_timer.isActive(): self._hide_input_notification_signal.emit() #print('update_current_value()') if not kontrol_subscriber.is_valid_action_set(): self._show_scene_notification_signal.emit() return if self._scene_notification_timer.isActive(): self._hide_scene_notification_signal.emit() joint_values = kontrol_subscriber.get_joint_values() if check_collisions: in_collision = collision_checker.is_in_collision(joint_values) else: in_collision = False collision_toggled = (currently_in_collision != in_collision) currently_in_collision = in_collision if collision_toggled: self._update_color_of_line_edit_signal.emit() self._update_current_value_signal.emit() def _show_input_notification(self): # open dialog which closes after some s_hide_scene_notification_hide_scene_notificationeconds or when confirmed manually self._input_notification.show() self._input_notification_timer.start() def _show_scene_notification(self): # open dialog which closes after some seconds or when confirmed manually self._scene_notification.show() self._scene_notification_timer.start() def _update_color_of_line_edit(self): global currently_in_collision palette = main_window.lineEdit.palette() if currently_in_collision: palette.setColor(QPalette.Text, QColor(255, 0, 0)) else: palette.setColor(QPalette.Text, default_color) main_window.lineEdit.setPalette(palette) def _update_current_value(self): global currently_in_collision main_window.append_pushButton.setEnabled(not currently_in_collision) main_window.insert_before_pushButton.setEnabled(not currently_in_collision) value = self._action_set.to_string() self.current_values_changed.emit(value) for action in self._action_set._actions: action._duration = 0.1 if use_sim_time else 0.5 self._action_set.execute()
if not icon.isNull(): main_window.PoseList_tabWidget.setTabText(index, '') main_window.PoseList_tabWidget.setTabIcon(index, icon) # hide design-only widgets #main_window.square_tableWidget.setVisible(False) sigint_called = False def sigint_handler(*args): global sigint_called print('\nsigint_handler()') sigint_called = True main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) # replace placeholder with rviz visualization panel index = main_window.robot_view_verticalLayout.indexOf(main_window.robot_view_widget) stretch = main_window.robot_view_verticalLayout.stretch(index) main_window.robot_view_verticalLayout.removeWidget(main_window.robot_view_widget) robot_view = rviz.VisualizationPanel() # hide rviz display list robot_view.children()[1].hide() main_window.robot_view_verticalLayout.insertWidget(index, robot_view, stretch) robot_view.setSizes([0]) config = tempfile.NamedTemporaryFile('w') if not show_point_clouds:
while 1: sliderDialObjs.append(self.sliderDialSet.pop()); except KeyError: return sliderDialObjs; # Ensure that cnt-C works: sigint_called = False def sigint_handler(*args): global sigint_called print('\nsigint_handler()') sigint_called = True slider.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler, # because it coaxes processing out of the underlying # C++ world: timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) if __name__ == '__main__': app = QApplication(sys.argv); slider = AccessibleSlider(); try: sys.exit(app.exec_()) except KeyboardInterrupt, ROSInterruptException: slider.shutdown(); sys.exit();
class MorseChallenger(QMainWindow): def __init__(self): super(MorseChallenger,self).__init__(); self.PIXELS_TO_MOVE_PER_TIMEOUT = 3 self.ABSOLUTE_MAX_NUM_FLOATERS = 10; self.FLOATER_POINT_SIZE = 20; self.LETTER_CHECKBOX_NUM_COLS = 6; # Number of timeout cycles that detonated # letters stay visible: self.DETONATION_VISIBLE_CYCLES = 4; self.maxNumFloaters = 1; self.lettersToUse = set(); self.floatersAvailable = set(); self.floatersInUse = set(); self.bookeepingAccessLock = threading.Lock(); # Floaters that detonated. Values are number of timeouts # the detonation was visible: self.floatersDetonated = {}; # Used to launch new floaters at random times: self.cyclesSinceLastLaunch = 0; self.dialogService = DialogService(); self.optionsFilePath = os.path.join(os.getenv('HOME'), '.morser/morseChallenger.cfg'); # Load UI for Morse Challenger: relPathQtCreatorFileMainWin = "qt_files/morseChallenger/morseChallenger.ui"; qtCreatorXMLFilePath = self.findFile(relPathQtCreatorFileMainWin); if qtCreatorXMLFilePath is None: raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFileMainWin); # Make QtCreator generated UI a child of this instance: loadUi(qtCreatorXMLFilePath, self); self.windowTitle = "Morse Challenger"; self.setWindowTitle(self.windowTitle); self.iconDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons') self.explosionPixMap = QPixmap(os.path.join(self.iconDir, 'explosion.png')); CommChannel.registerSignals(MorseChallengerSignals); self.connectWidgets(); self.generateLetterCheckBoxes(); self.simultaneousLettersComboBox.addItems(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); self.generateFloaters(); self.setFocusPolicy(Qt.ClickFocus); self.letterMoveTimer = QTimer(); self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider()); # milliseconds self.letterMoveTimer.setSingleShot(False); self.letterMoveTimer.timeout.connect(self.moveLetters); # Bring options from config file: self.setOptions(); # Styling: self.createColors(); self.setStyleSheet("QWidget{background-color: %s}" % self.lightBlueColor.name()); self.show(); def connectWidgets(self): self.speedHSlider.valueChanged.connect(self.speedChangedAction); self.startPushButton.clicked.connect(self.startAction); self.stopPushButton.clicked.connect(self.stopAction); self.simultaneousLettersComboBox.currentIndexChanged.connect(self.maxNumSimultaneousFloatersAction); CommChannel.getSignal('MorseChallengerSignals.focusLostInadvertently').connect(self.reassertWindowFocus); def createColors(self): self.grayBlueColor = QColor(89,120,137); # Letter buttons self.lightBlueColor = QColor(206,230,243); # Background self.darkGray = QColor(65,88,101); # Central buttons self.wordListFontColor = QColor(62,143,185); # Darkish blue. self.purple = QColor(147,124,195); # Gesture button pressed def generateLetterCheckBoxes(self): self.lettersToCheckBoxes = {}; self.checkBoxesToLetters = {}; letters = sorted(codeKey.values()); numRows = (len(letters) % self.LETTER_CHECKBOX_NUM_COLS) + 1; col = 0; row = 0; letterIndex = 0; while(True): letter = letters[letterIndex]; checkbox = QCheckBox(); self.lettersToCheckBoxes[letter] = checkbox; self.checkBoxesToLetters[checkbox] = letter; checkbox.setText(str(letter)); checkbox.toggled.connect(partial(self.letterCheckAction, checkbox)); self.letterCheckboxGridLayout.addWidget(checkbox, row, col); col += 1; if (col >= self.LETTER_CHECKBOX_NUM_COLS): col = 0; row += 1; letterIndex += 1; if letterIndex >= len(letters): break; def generateFloaters(self): for i in range(self.ABSOLUTE_MAX_NUM_FLOATERS): floaterLabel = QLabel(); font = QtGui.QFont() font.setFamily("Courier") font.setFixedPitch(True) font.setPointSize(self.FLOATER_POINT_SIZE); floaterLabel.setFont(font); self.floatersAvailable.add(floaterLabel); def letterCheckAction(self, checkbox, checkedOrNot): changedLetter = self.checkBoxesToLetters[checkbox]; configedLetters = self.cfgParser.get('Main', 'letters'); if checkedOrNot: self.lettersToUse.add(self.checkBoxesToLetters[checkbox]); configedLetters += changedLetter; else: self.lettersToUse.discard(self.checkBoxesToLetters[checkbox]); configedLetters.replace(changedLetter, ""); self.cfgParser.set('Main', 'letters', configedLetters); self.saveOptions(); def startAction(self): self.launchFloaters(1); self.letterMoveTimer.start(); def stopAction(self): self.letterMoveTimer.stop(); floatersInUseCopy = self.floatersInUse.copy(); for floater in floatersInUseCopy: self.decommissionFloater(floater); def maxNumSimultaneousFloatersAction(self, newNum): # New Combo box picked: 0-based: self.maxNumFloaters = newNum + 1; try: self.cfgParser.set('Main','numLettersTogether', str(newNum)); self.saveOptions(); except AttributeError: # At startup cfgParser won't exist yet. Ignore: pass def speedChangedAction(self, newSpeed): self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider(newSpeed)); # msec. self.cfgParser.set('Main','letterSpeed', str(newSpeed)); self.saveOptions(); def keyPressEvent(self, keyEvent): letter = keyEvent.text(); self.letterLineEdit.setText(letter); matchingFloaters = {}; # Find all active floaters that have the pressed key's letter: for floater in self.floatersInUse: if floater.text() == letter: floaterY = floater.y(); try: matchingFloaters[floaterY].append(floater); except KeyError: matchingFloaters[floaterY] = [floater]; if len(matchingFloaters) == 0: self.activateWindow(); self.setFocus(); return; # Find the lowest ones: lowestY = self.projectionScreenWidget.y(); yPositions = matchingFloaters.keys(); for floater in matchingFloaters[max(yPositions)]: self.decommissionFloater(floater); self.activateWindow(); self.setFocus(); def focusInEvent(self, event): ''' Ensure that floaters are always on top of the app window, even if user changes speed slider, checkmarks, etc.: @param event: @type event: ''' self.raiseAllFloaters(); def resizeEvent(self, event): newWinRect = self.geometry(); self.cfgParser.set('Appearance', 'winGeometry', str(newWinRect.x()) + ',' + str(newWinRect.y()) + ',' + str(newWinRect.width()) + ',' + str(newWinRect.height())); self.saveOptions(); def raiseAllFloaters(self): for floater in self.floatersInUse: floater.raise_(); for floater in self.floatersDetonated.keys(): floater.raise_(); def decommissionFloater(self, floaterLabel): floaterLabel.setHidden(True); # Just in case: protect removal, in case caller # passes in an already decommissioned floater: try: self.floatersInUse.remove(floaterLabel); except KeyError: pass # Adding a floater twice is not an issue, # b/c floatersAvailable is a set: self.floatersAvailable.add(floaterLabel); def timerIntervalFromSpeedSlider(self, newSpeed=None): if newSpeed is None: newSpeed = self.speedHSlider.value(); return max(500 - newSpeed, 20); #msec def randomLetters(self, numLetters): if len(self.lettersToUse) == 0: self.dialogService.showErrorMsg("You must turn on a checkmark for at least one letter."); return None; lettersToDeploy = []; for i in range(numLetters): letter = random.sample(self.lettersToUse, 1); lettersToDeploy.extend(letter); return lettersToDeploy; def moveLetters(self): self.cyclesSinceLastLaunch += 1; # Did floater detonate during previous timeout?: detonatedFloaters = self.floatersDetonated.keys(); for detonatedFloater in detonatedFloaters: self.floatersDetonated[detonatedFloater] += 1; if self.floatersDetonated[detonatedFloater] > self.DETONATION_VISIBLE_CYCLES: self.decommissionFloater(detonatedFloater); del self.floatersDetonated[detonatedFloater]; remainingFloaters = self.floatersDetonated.keys(); for floaterLabel in self.floatersInUse: if floaterLabel in remainingFloaters: continue; geo = floaterLabel.geometry(); newY = geo.y() + self.PIXELS_TO_MOVE_PER_TIMEOUT; savedHeight = geo.height(); if newY > self.height(): newY = self.height(); #******** self.detonate(floaterLabel); geo.setY(newY) geo.setHeight(savedHeight); floaterLabel.setGeometry(geo); # Done advancing each floater. Is it time to start a new floater? numFloatersToLaunch = self.maxNumFloaters - len(self.floatersInUse) + len(self.floatersDetonated); if numFloatersToLaunch > 0: # Use the following commented lines if you want the launching of # new floaters to be random, e.g. between 2 and 10 timeout intervals: # if self.cyclesSinceLastLaunch > random.randint(2,10): # self.launchFloaters(self.maxNumFloaters - len(self.floatersInUse)); self.launchFloaters(numFloatersToLaunch); # Launching floaters deactivates the main window, thereby losing the keyboard focus. # We can't seem to reassert that focus within this timer interrupt service routine. # So, issue a signal that will do it after return: CommChannel.getSignal('MorseChallengerSignals.focusLostInadvertently').emit(); def launchFloaters(self, numFloaters): newLetters = self.randomLetters(numFloaters); if newLetters is None: return; for letter in newLetters: # Pick a random horizontal location, at least 3 pixels in # from the left edge, and at most 3 pixels back from the right edge: screenGeo = self.projectionScreenWidget.geometry(); appWinGeo = self.geometry(); appWinX = appWinGeo.x(); # Random number among all the application window's X coordinates: xLoc = random.randint(appWinX + 3, appWinX + screenGeo.width() - 3); yLoc = appWinGeo.y(); self.getFloaterLabel(letter, xLoc, yLoc); self.cyclesSinceLastLaunch = 0; def getFloaterLabel(self, letter, x, y): try: label = self.floatersAvailable.pop(); label.clear(); except KeyError: return None; self.floatersInUse.add(label); label.setText(letter); label.move(x,y); label.setHidden(False); def detonate(self, floaterLabel): # Floater was detonated zero cycles ago: self.floatersDetonated[floaterLabel] = 0; floaterLabel.clear(); floaterLabel.setPixmap(self.explosionPixMap); def findFile(self, path, matchFunc=os.path.isfile): if path is None: return None for dirname in sys.path: candidate = os.path.join(dirname, path) if matchFunc(candidate): return candidate return None; def reassertWindowFocus(self): self.window().activateWindow(); self.raise_(); self.raiseAllFloaters(); def setOptions(self): self.optionsDefaultDict = { 'letters' : "", 'letterSpeed' : str(10), 'numLettersTogether' : str(0), 'winGeometry' : '100,100,700,560', } self.cfgParser = ConfigParser.SafeConfigParser(self.optionsDefaultDict); self.cfgParser.add_section('Main'); self.cfgParser.add_section('Appearance'); self.cfgParser.read(self.optionsFilePath); mainWinGeometry = self.cfgParser.get('Appearance', 'winGeometry'); # Get four ints from the comma-separated string of upperLeftX, upperLeftY, # Width,Height numbers: try: nums = mainWinGeometry.split(','); self.setGeometry(QRect(int(nums[0].strip()),int(nums[1].strip()),int(nums[2].strip()),int(nums[3].strip()))); except Exception as e: self.dialogService.showErrorMsg("Could not set window size; config file spec not grammatical: %s. (%s" % (mainWinGeometry, `e`)); letterSpeed = self.cfgParser.getint('Main', 'letterSpeed'); self.speedHSlider.setValue(letterSpeed); #self.speedChangedAction(letterSpeed); self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider()); # milliseconds numLettersTogether = self.cfgParser.getint('Main', 'numLettersTogether'); self.simultaneousLettersComboBox.setCurrentIndex(numLettersTogether); lettersToUse = self.cfgParser.get('Main', 'letters'); for letter in lettersToUse: self.lettersToCheckBoxes[letter].setChecked(True); def saveOptions(self): try: # Does the config dir already exist? If not # create it: optionsDir = os.path.dirname(self.optionsFilePath); if not os.path.isdir(optionsDir): os.makedirs(optionsDir, 0777); with open(self.optionsFilePath, 'wb') as outFd: self.cfgParser.write(outFd); except IOError as e: self.dialogService.showErrorMsg("Could not save options: %s" % `e`); def exit(self): QApplication.quit(); def closeEvent(self, event): QApplication.quit(); # Bubble event up: event.ignore();
def __init__(self, label, parent=None): super(GestureButton, self).__init__(label, parent=parent); #tmpStyle = "QPushButton {background-color: red}"; #self.setStyleSheet(tmpStyle); self.setFlicksEnabled(True); # Timer for deciding when mouse has left this # button long enough that even returning to this # button will not be counted as a flick, but as # a new entrance. # NOTE: QTimer emits the signal "timeout()" when it # expires. In contrast, QBasicTimer emits a timer *event* # For use with state transitions, use QEventTransition # for QBasicTimer, but QSignalTransition for QTimer. self.maxFlickDurationTimer = QTimer(); self.maxFlickDurationTimer.setSingleShot(True); # Bit of a hack to get around a Pyside bug/missing-feature: # Timer used with zero delay to trigger a QSignalTransition # with a standard signal (no custom signals seem to work): self.flickDetectDoneTrigger = QTimer(); self.flickDetectDoneTrigger.setSingleShot(True); self.setMouseTracking(True); self.latestMousePos = QPoint(0,0); self.connectWidgets(); self.initSignals(); # ------------------ State Definitions ------------------ # Parent state for north/south/east/west states: self.stateGestureActivatePending = QState(); #self.stateGestureActivatePending.assignProperty(self, "text", "Toggle pending"); # States for: mouse left button; will mouse flick back? self.stateNorthExit = QState(self.stateGestureActivatePending); self.stateSouthExit = QState(self.stateGestureActivatePending); self.stateEastExit = QState(self.stateGestureActivatePending); self.stateWestExit = QState(self.stateGestureActivatePending); self.stateGestureActivatePending.setInitialState(self.stateNorthExit); # State: mouse entered button area not as part of a flick: self.stateEntered = QState(); #self.stateEntered.assignProperty(self, "text", "Entered"); # State: mouse re-entered button after leaving briefly: self.stateReEntered = QState(); #self.stateReEntered.assignProperty(self, "text", "Re-Entered"); # State: mouse outside for longer than GestureButton.FLICK_TIME_THRESHOLD seconds: self.stateIdle = QState(); #self.stateIdle.assignProperty(self, "text", "Idle"); # ------------------ Transition Definitions ------------------ # From Idle to Entered: triggered automatically by mouse entering button area: self.toEnteredTrans = HotEntryTransition(self, QEvent.Enter, sourceState=self.stateIdle); self.toEnteredTrans.setTargetState(self.stateEntered); # From Entered to GestureActivatePending: mouse cursor left button area, # and the user may or may not return the cursor to the button area # in time to trigger a flick. Transition triggered automatically by # mouse leaving button area. A timer is set. If it runs to 0, # a transition to Idle will be triggered. But if mouse re-enters button # area before the timer expires, the ReEntered state will become active: self.toGAPendingTrans = TimeSettingStateTransition(self, QEvent.Leave, sourceState=self.stateEntered); self.toGAPendingTrans.setTargetState(self.stateGestureActivatePending); # From GestureActivatePending to ReEntered. Triggered if mouse cursor # re-enters button area after having left before the maxFlickDurationTimer # has run down. Triggered automatically by mouse entering the button area: self.toReEnteredTrans = TimeReadingStateTransition(self, QEvent.Enter, self.toGAPendingTrans, sourceState=self.stateGestureActivatePending); self.toReEnteredTrans.setTargetState(self.stateReEntered); # From GestureActivePending to Idle. Triggered by maxFlickDurationTimer running to 0 # before mouse cursor re-entered button area after leaving. Triggered by timer that # is set when state GestureActivatePending is entered. self.toIdleTrans = HotExitTransition(self, self.maxFlickDurationTimer, sourceState=self.stateGestureActivatePending); self.toIdleTrans.setTargetState(self.stateIdle); # From ReEntered to Entered. Triggered a zero-delay timer timeout set # in TimeReadingStateTransition after that transition has determined # whether a mouse cursor entry into the button space was a flick, or not. # (Note: In the PySide version # current at this writing, neither custom events nor custom signals # seem to work for triggering QEventTransaction or QSignalTransaction, # respectively) self.toEnteredFromReEnteredTrans = QSignalTransition(self.flickDetectDoneTrigger, SIGNAL("timeout()"), sourceState=self.stateReEntered); self.toEnteredFromReEnteredTrans.setTargetState(self.stateEntered); # ---------------------- State Machine -------QtCore.signal-------------- self.stateMachine = QStateMachine(); self.stateMachine.addState(self.stateGestureActivatePending); self.stateMachine.addState(self.stateEntered); self.stateMachine.addState(self.stateReEntered); self.stateMachine.addState(self.stateIdle); self.installEventFilter(self); self.stateMachine.setInitialState(self.stateIdle); self.stateMachine.start();
class GestureButton(QPushButton): FLICK_TIME_THRESHOLD_MASTER = 0.5; # seconds def __init__(self, label, parent=None): super(GestureButton, self).__init__(label, parent=parent); #tmpStyle = "QPushButton {background-color: red}"; #self.setStyleSheet(tmpStyle); self.setFlicksEnabled(True); # Timer for deciding when mouse has left this # button long enough that even returning to this # button will not be counted as a flick, but as # a new entrance. # NOTE: QTimer emits the signal "timeout()" when it # expires. In contrast, QBasicTimer emits a timer *event* # For use with state transitions, use QEventTransition # for QBasicTimer, but QSignalTransition for QTimer. self.maxFlickDurationTimer = QTimer(); self.maxFlickDurationTimer.setSingleShot(True); # Bit of a hack to get around a Pyside bug/missing-feature: # Timer used with zero delay to trigger a QSignalTransition # with a standard signal (no custom signals seem to work): self.flickDetectDoneTrigger = QTimer(); self.flickDetectDoneTrigger.setSingleShot(True); self.setMouseTracking(True); self.latestMousePos = QPoint(0,0); self.connectWidgets(); self.initSignals(); # ------------------ State Definitions ------------------ # Parent state for north/south/east/west states: self.stateGestureActivatePending = QState(); #self.stateGestureActivatePending.assignProperty(self, "text", "Toggle pending"); # States for: mouse left button; will mouse flick back? self.stateNorthExit = QState(self.stateGestureActivatePending); self.stateSouthExit = QState(self.stateGestureActivatePending); self.stateEastExit = QState(self.stateGestureActivatePending); self.stateWestExit = QState(self.stateGestureActivatePending); self.stateGestureActivatePending.setInitialState(self.stateNorthExit); # State: mouse entered button area not as part of a flick: self.stateEntered = QState(); #self.stateEntered.assignProperty(self, "text", "Entered"); # State: mouse re-entered button after leaving briefly: self.stateReEntered = QState(); #self.stateReEntered.assignProperty(self, "text", "Re-Entered"); # State: mouse outside for longer than GestureButton.FLICK_TIME_THRESHOLD seconds: self.stateIdle = QState(); #self.stateIdle.assignProperty(self, "text", "Idle"); # ------------------ Transition Definitions ------------------ # From Idle to Entered: triggered automatically by mouse entering button area: self.toEnteredTrans = HotEntryTransition(self, QEvent.Enter, sourceState=self.stateIdle); self.toEnteredTrans.setTargetState(self.stateEntered); # From Entered to GestureActivatePending: mouse cursor left button area, # and the user may or may not return the cursor to the button area # in time to trigger a flick. Transition triggered automatically by # mouse leaving button area. A timer is set. If it runs to 0, # a transition to Idle will be triggered. But if mouse re-enters button # area before the timer expires, the ReEntered state will become active: self.toGAPendingTrans = TimeSettingStateTransition(self, QEvent.Leave, sourceState=self.stateEntered); self.toGAPendingTrans.setTargetState(self.stateGestureActivatePending); # From GestureActivatePending to ReEntered. Triggered if mouse cursor # re-enters button area after having left before the maxFlickDurationTimer # has run down. Triggered automatically by mouse entering the button area: self.toReEnteredTrans = TimeReadingStateTransition(self, QEvent.Enter, self.toGAPendingTrans, sourceState=self.stateGestureActivatePending); self.toReEnteredTrans.setTargetState(self.stateReEntered); # From GestureActivePending to Idle. Triggered by maxFlickDurationTimer running to 0 # before mouse cursor re-entered button area after leaving. Triggered by timer that # is set when state GestureActivatePending is entered. self.toIdleTrans = HotExitTransition(self, self.maxFlickDurationTimer, sourceState=self.stateGestureActivatePending); self.toIdleTrans.setTargetState(self.stateIdle); # From ReEntered to Entered. Triggered a zero-delay timer timeout set # in TimeReadingStateTransition after that transition has determined # whether a mouse cursor entry into the button space was a flick, or not. # (Note: In the PySide version # current at this writing, neither custom events nor custom signals # seem to work for triggering QEventTransaction or QSignalTransaction, # respectively) self.toEnteredFromReEnteredTrans = QSignalTransition(self.flickDetectDoneTrigger, SIGNAL("timeout()"), sourceState=self.stateReEntered); self.toEnteredFromReEnteredTrans.setTargetState(self.stateEntered); # ---------------------- State Machine -------QtCore.signal-------------- self.stateMachine = QStateMachine(); self.stateMachine.addState(self.stateGestureActivatePending); self.stateMachine.addState(self.stateEntered); self.stateMachine.addState(self.stateReEntered); self.stateMachine.addState(self.stateIdle); self.installEventFilter(self); self.stateMachine.setInitialState(self.stateIdle); self.stateMachine.start(); #self.setGeometry(500, 500, 200,100); #self.show() # ------------------------ Public Methods ------------------------------- @staticmethod def setFlicksEnabled(doEnable): ''' Controls whether flicking in and out of buttons is enabled. If flicks are enabled, then mouse-left-button signals are delayed for FLICK_TIME_THRESHOLD seconds. If flicks are disabled, then those signals are delivered immediately after the cursor leaves a button. @param doEnable: set to True if the flick feature is to be enabled. Else set to False @type doEnable: boolean ''' if doEnable: GestureButton.flickEnabled = True; GestureButton.FLICK_TIME_THRESHOLD = GestureButton.FLICK_TIME_THRESHOLD_MASTER; else: GestureButton.flickEnabled = True; GestureButton.FLICK_TIME_THRESHOLD = 0.0; @staticmethod def flicksEnabled(): ''' Returns whether the flick feature is enabled. @return: True if flicking is enabled, else False. @rtype: boolean ''' return GestureButton.flickEnabled; # ------------------------ Private Methods ------------------------------- def initSignals(self): #******************* # self.signals = GestureSignals(); # CommChannel.getInstance().registerSignals(self.signals); #CommChannel.getInstance().registerSignals(GestureSignals); CommChannel.registerSignals(GestureSignals); #******************* def connectWidgets(self): #self.maxFlickDurationTimer.timeout.connect(self.flickThresholdExceeded); pass; def eventFilter(self, target, event): if target == self and (event.__class__ == QMouseEvent): self.latestMousePos = event.pos(); return False; # def mouseMoveEvent(self, mouseEvent): # self.latestMousePos = mouseEvent.pos(); # super(GestureButton, self).mouseMoveEvent(mouseEvent); @Slot(QPushButton) def flickThresholdExceeded(self): print "Flick opportunity over." return False; def findClosestButtonBorder(self): ''' Retrieves current mouse position, which is assumed to have been saved in the gesture button's lastMousePos instance variable by a mouseMove signal handler or event filter. Compares this mouse position with the four button borders. Returns a FlickDirection to indicate which border is closest to the mouse. All this uses global coordinates. @return: FlickDirection member. @raise ValueError: if minimum distance cannot be determined. ''' # Global mouse position: mousePos = self.mapToGlobal(self.latestMousePos) # Get: (upperLeftGlobal_x, upperLeftGlobal_y, width, height): gestureButtonRect = self.geometry() # Recompute upper left and lower right in global coords # each time, b/c window may have moved: topLeftPtGlobal = self.mapToGlobal(gestureButtonRect.topLeft()); bottomRightPtGlobal = self.mapToGlobal(gestureButtonRect.bottomRight()); # Do mouse coord absolute value compare with button edges, # because the last recorded mouse event is often just still # inside the button when this 'mouse left' signal arrives: distMouseFromTop = math.fabs(mousePos.y() - topLeftPtGlobal.y()) distMouseFromBottom = math.fabs(mousePos.y() - bottomRightPtGlobal.y()) distMouseFromLeft = math.fabs(mousePos.x() - topLeftPtGlobal.x()) distMouseFromRight = math.fabs(mousePos.x() - bottomRightPtGlobal.x()) minDist = min(distMouseFromTop, distMouseFromBottom, distMouseFromLeft, distMouseFromRight) if minDist == distMouseFromTop: return FlickDirection.NORTH elif minDist == distMouseFromBottom: return FlickDirection.SOUTH elif minDist == distMouseFromLeft: return FlickDirection.WEST elif minDist == distMouseFromRight: return FlickDirection.EAST else: raise ValueError("Failed to compute closest button border.") def __str__(self): if self.text() is not None: return self.text(); else: return "<noLabel>";
def __init__(self): super(TBoard, self).__init__(); self.setWindowTitle("TBoard"); # Find QtCreator's XML file in the PYTHONPATH: currDir = os.path.realpath(__file__); relPathQtCreatorFile = "tboard_ui/tboard_ui.ui"; qtCreatorXMLFilePath = Utilities.findFile(relPathQtCreatorFile); if qtCreatorXMLFilePath is None: raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFile); # Make QtCreator generated UI a child if this instance: python_qt_binding.loadUi(qtCreatorXMLFilePath, self); self.letterWidgets = [self.ABCWidget, self.DEFWidget, self.GHIWidget, self.JKLWidget, self.MNOWidget, self.PQRWidget, self.STUVWidget, self.WXYZWidget]; self.createColors(); # Populate all empty letter board button widgets with # GestureButton instances: self.populateGestureButtons(); self.preparePixmaps(); # Increase the width of the word area scrollbar: self.wordList.verticalScrollBar().setFixedWidth(WORD_LIST_SCROLLBAR_WIDTH) # pixels # Where we accumulate evolving words in encoded form # (see symbolToEnc dict in word_collection.py): self.encEvolvingWord = ""; self.currButtonUsedForFlick = False; self.wordCollection = TelPadEncodedWordCollection(); # Timer to ensure that a crossed-out button doesn't # stay crossed out forever: self.crossOutTimer = QTimer(); self.crossOutTimer.setSingleShot(True); self.crossedOutButtons = []; # Popup dialog for adding new words to dictionary: self.initNewDictWordDialog(); # Gesture buttons all in Dialpad (speed-write mode): self.buttonEditMode = ButtonEditMode.DIALPAD; # Disable selecting for the remaining-words panel: self.wordList.setFocusPolicy(Qt.NoFocus); # Mutex for keeping very fast flicking gestures # out of each others' hair: self.mutex = QMutex(); # The system clipboard for copy: self.clipboard = QApplication.clipboard(); # Speak-button not working yet: self.speakButton.setDisabled(True); self.connectWidgets(); #self.setGeometry(500, 500, 300, 100); self.show();
class TBoard(QWidget): # Ids for checkboxes in the 'add-new-word' dialog: RARELY_BUTTON_ID = 0; OCCCASIONALLY_BUTTON_ID = 1; CONSTANTLY_BUTTON_ID = 2; def __init__(self): super(TBoard, self).__init__(); self.setWindowTitle("TBoard"); # Find QtCreator's XML file in the PYTHONPATH: currDir = os.path.realpath(__file__); relPathQtCreatorFile = "tboard_ui/tboard_ui.ui"; qtCreatorXMLFilePath = Utilities.findFile(relPathQtCreatorFile); if qtCreatorXMLFilePath is None: raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFile); # Make QtCreator generated UI a child if this instance: python_qt_binding.loadUi(qtCreatorXMLFilePath, self); self.letterWidgets = [self.ABCWidget, self.DEFWidget, self.GHIWidget, self.JKLWidget, self.MNOWidget, self.PQRWidget, self.STUVWidget, self.WXYZWidget]; self.createColors(); # Populate all empty letter board button widgets with # GestureButton instances: self.populateGestureButtons(); self.preparePixmaps(); # Increase the width of the word area scrollbar: self.wordList.verticalScrollBar().setFixedWidth(WORD_LIST_SCROLLBAR_WIDTH) # pixels # Where we accumulate evolving words in encoded form # (see symbolToEnc dict in word_collection.py): self.encEvolvingWord = ""; self.currButtonUsedForFlick = False; self.wordCollection = TelPadEncodedWordCollection(); # Timer to ensure that a crossed-out button doesn't # stay crossed out forever: self.crossOutTimer = QTimer(); self.crossOutTimer.setSingleShot(True); self.crossedOutButtons = []; # Popup dialog for adding new words to dictionary: self.initNewDictWordDialog(); # Gesture buttons all in Dialpad (speed-write mode): self.buttonEditMode = ButtonEditMode.DIALPAD; # Disable selecting for the remaining-words panel: self.wordList.setFocusPolicy(Qt.NoFocus); # Mutex for keeping very fast flicking gestures # out of each others' hair: self.mutex = QMutex(); # The system clipboard for copy: self.clipboard = QApplication.clipboard(); # Speak-button not working yet: self.speakButton.setDisabled(True); self.connectWidgets(); #self.setGeometry(500, 500, 300, 100); self.show(); # -------------------------------------- UI Setup Methods ------------------------- def initNewDictWordDialog(self): ''' Initializes dialog window for user to add a new word to the dictionary. ''' # Find QtCreator's XML file in the PYTHONPATH: currDir = os.path.realpath(__file__); relPathQtCreatorFile = "tboard_ui/addWord_dialog/addWordDialog.ui"; qtCreatorXMLFilePath = Utilities.findFile(relPathQtCreatorFile); if qtCreatorXMLFilePath is None: raise ValueError("Can't find QtCreator user interface file for 'new dictionary word' dialog file %s" % relPathQtCreatorFile); #****self.addWordDialog = QWidget(); self.addWordDialog = QDialog(); python_qt_binding.loadUi(qtCreatorXMLFilePath, self.addWordDialog); # Assign int IDs to the frequency checkboxes: rareButton = self.addWordDialog.useRarelyButton; occasionButton = self.addWordDialog.useOccasionallyButton; constantButton = self.addWordDialog.useConstantlyButton; self.addWordButtonGroup = QButtonGroup(); self.addWordButtonGroup.addButton(rareButton, TBoard.RARELY_BUTTON_ID); self.addWordButtonGroup.addButton(occasionButton, TBoard.OCCCASIONALLY_BUTTON_ID); self.addWordButtonGroup.addButton(constantButton, TBoard.CONSTANTLY_BUTTON_ID); self.addWordDialog.hide(); def populateGestureButtons(self): ''' Creates GestureButton instances for each telephone pad button. Creates convenience data structures: - C{letterButtons} is an array of all GestureButton instances - C{letterButtonToID} maps GestureButton instances to the buttons' IDs, which happen to be their label strings ('ABC', 'DEF', etc.). - C{idToLetterButton} is the reverse: a dictionary mapping button labels, like "ABC" to the corresponding button instance. This function also sets style sheets for the all GestureButton instances. ''' # Sorted array of all letter button objs: self.letterButtons = []; # Letter button object to button label: "ABC", "DEF", etc: self.letterButtonToID = {}; self.idToLetterButton = {}; for buttonID in ButtonID.legalValues: self.letterButtons.append(GestureButton(ButtonID.toString(buttonID), parent=self.letterWidgets[buttonID])); self.letterButtonToID[self.letterButtons[-1]] = self.letterButtons[-1].text(); self.letterButtons[-1].setGeometry(0,0,200,100); self.idToLetterButton[self.letterButtons[-1].text()] = self.letterButtons[-1]; for buttonObj in self.letterButtons: self.setGestureButtonStyle(buttonObj, StyleID.RELEASED); #****buttonObj.setFocusPolicy(Qt.FocusPolicy.NoFocus); buttonObj.setFocusPolicy(Qt.NoFocus); def connectWidgets(self): ''' Connect signals and button slots to their handlers. ''' CommChannel.getSignal('GestureSignals.flickSig').connect(self.handleButtonFlicks); CommChannel.getSignal('GestureSignals.buttonEnteredSig').connect(self.handleButtonEntered); CommChannel.getSignal('GestureSignals.buttonExitedSig').connect(self.handleButtonExited); self.eraseWordButton.clicked.connect(self.handleEraseWordButton); self.addWordButton.clicked.connect(self.handleSaveWordButton); self.crossOutTimer.timeout.connect(self.handleCrossoutTimeout); # Number pad: for button in set([self.numPad1, self.numPad2, self.numPad3, self.numPad4, self.numPad5, self.numPad6, self.numPad7, self.numPad8, self.numPad9, self.numPad0]): button.clicked.connect(partial(self.handleNumPad, button)); # Special characters: for button in set([self.commaButton, self.colonButton, self.questionButton, self.atButton, self.leftParenButton, self.periodButton, self.backspaceButton, self.slashButton, self.spaceButton, self.rightParenButton]): button.clicked.connect(partial(self.handleSpecialChars, button)); # Gesture button clicks (switching between dialpad and letter mode: for buttonObj in self.letterButtons: buttonObj.clicked.connect(partial(self.handleGestureButtonClick, buttonObj)); # Add word dialog box capitalization checkbox state changed: self.addWordDialog.capitalizeCheckbox.stateChanged.connect(self.handleAddDictWordCapitalizeStateChanged); # Add word dialog box OK or Cancel botton clicked: self.addWordDialog.cancelButton.clicked.connect(partial(self.handleAddDictWordOK_Cancel, self.addWordDialog.cancelButton)); self.addWordDialog.addWordButton.clicked.connect(partial(self.handleAddDictWordOK_Cancel, self.addWordDialog.addWordButton)); # CopyAll button: self.copyButton.clicked.connect(self.handleCopyAll); def preparePixmaps(self): ''' Pull icons from the file system, and turn them into pixmaps. ''' imgDirPath = os.path.join(os.path.dirname(__file__), "img/"); self.buttonBackGroundPixmaps = []; buttonWidth = self.letterButtons[0].width(); buttonHeight = self.letterButtons[0].height(); for backgroundImgNum in range(NUM_LETTER_BUTTONS): buttonPixmap = QPixmap(); #****imgPath = os.path.join(imgDirPath, "tboardButtonBackgroundTrail" + str(backgroundImgNum + 1) + ".png"); imgPath = os.path.join(imgDirPath, "tboardButtonBackgroundsSmall" + str(backgroundImgNum + 1) + ".png"); if not buttonPixmap.load(imgPath): raise IOError("Could not find button background icon at " + imgPath); #scaledPixmap = buttonPixmap.scaled(buttonWidth, buttonHeight); #*****scaledPixmap = buttonPixmap.scaled(buttonWidth + 50, buttonHeight); scaledPixmap = buttonPixmap; #***** self.buttonBackGroundPixmaps.append(scaledPixmap); self.crossedOutButtonBackground = QPixmap(); imgPath = os.path.join(imgDirPath, "tboardButtonBackgroundCrossedOut.png"); if not self.crossedOutButtonBackground.load(imgPath): raise IOError("Could not find crossed-out button background icon at " + imgPath); self.crossedOutButtonBackground = self.crossedOutButtonBackground.scaled(buttonWidth + 50, buttonHeight); # Initialize all buttons to a background with # trail spot 8 (darkest): for button in self.letterButtons: self.setButtonImage(button, self.buttonBackGroundPixmaps[NUM_LETTER_BUTTONS - 1]); # Initialize dictionary that tracks button background icons. # Keys are button objs, values are ints that index into the # self.buttonBackgroundPixmaps array. None means the button # is so old that it has a plain background, or that it was never # used so far: self.currentButtonBackgrounds = {}; for buttonObj in self.letterButtons: self.currentButtonBackgrounds[buttonObj] = None; self.setStyleSheet("QWidget{background-color: %s}" % self.offWhiteColor.name()); def createColors(self): ''' Create QColor objects from RGB values. ''' self.grayBlueColor = QColor(89,120,137); # Letter buttons self.offWhiteColor = QColor(206,230,243); # Background self.darkGray = QColor(65,88,101); # Central buttons self.wordListFontColor = QColor(62,143,185); # Darkish blue. self.purple = QColor(147,124,195); # Gesture button pressed def setGestureButtonStyle(self, buttonObj, styleID): ''' Style a gesture button. @param buttonObj: the button to style @type buttonObj: GestureButton @param styleID: whether button is pressed or released @type styleID: StyleID ''' if styleID == StyleID.RELEASED: buttonObj.setStyleSheet("background-color: %s; color: %s; border: 2px outset %s; border-radius: 15; font-size: 18px" % (self.grayBlueColor.name(), self.offWhiteColor.name(), self.offWhiteColor.name())); elif styleID == StyleID.PRESSED: buttonObj.setStyleSheet("background-color: %s; color: %s; border-radius: 15; font-size: 22px" % (self.purple.name(), self.offWhiteColor.name())); self.setFocus(); # -------------------------------------- Signal Handlers ------------------------- @Slot(GestureButton, int) def handleButtonFlicks(self, gestureButton, flickDirection): ''' Action on flicking in and out of a gesture button. @param gestureButton: button that was flicked @type gestureButton: GestureButton @param flickDirection: cursor flicked North, South, East, or West @type flickDirection: GestureButton.FlickDirection ''' #print "Flick direction: " + FlickDirection.toString(flickDirection); # Protect against re-entry. Not that # QtCore.QMutexLocker locks when created, and # unlocks when destroyed (i.e. when function # is left; no explicit unlocking needed); myMutexLocker = QMutexLocker(self.mutex); # West flick: Undo last letter (in Dialpad mode) or # highlight next letter (in Letter_Edit mod): if flickDirection == FlickDirection.WEST: if self.buttonEditMode == ButtonEditMode.DIALPAD: self.erasePreviousLetter(); self.showRemainingWords(); self.updateTickerTape(); else: # individual-letter-input mode: self.highlightNextLetter(gestureButton, FlickDirection.WEST); # North flick: Scroll word list up: elif flickDirection == FlickDirection.NORTH: currRemainingWordsRow = self.wordList.currentRow(); if currRemainingWordsRow == 0 or self.buttonEditMode == ButtonEditMode.LETTER_INPUT: pass; else: self.wordList.setCurrentRow(currRemainingWordsRow - 1); # South flick: Scroll word list down: elif flickDirection == FlickDirection.SOUTH: currRemainingWordsRow = self.wordList.currentRow(); if currRemainingWordsRow >= self.wordList.count() or self.buttonEditMode == ButtonEditMode.LETTER_INPUT: pass; else: self.wordList.setCurrentRow(currRemainingWordsRow + 1); # East flick: Accept word that is selected in remaining words list: else: if self.buttonEditMode == ButtonEditMode.LETTER_INPUT: self.highlightNextLetter(gestureButton, FlickDirection.EAST); else: # No word in word list? count = self.wordList.count() currItem = self.wordList.currentItem(); if count <= 0 or currItem is None: pass; else: self.outputPanel.insertPlainText(" " + currItem.text()); # Word entry done for this word: self.eraseCurrentWord(); # Remember that we just used this button # for a flick action. self.currButtonUsedForFlick = True; @Slot(GestureButton) def handleButtonEntered(self, gestureButtonObj): #print "Button %s entered" % str(gestureButtonObj); pass; @Slot(GestureButton) def handleButtonExited(self, gestureButtonObj): ''' Handler for cursor having entered a gesture button. @param gestureButtonObj: button object that was entered @type gestureButtonObj: GestureButton ''' # Protect against re-entry. Not that # QtCore.QMutexLocker locks when created, and # unlocks when destroyed (i.e. when function # is left; no explicit unlocking needed); myMutexLocker = QMutexLocker(self.mutex); # If we are in letter entry mode, return this button # to Dialpad mode: if self.buttonEditMode == ButtonEditMode.LETTER_INPUT: self.switchButtonMode(gestureButtonObj, ButtonEditMode.DIALPAD); return; # If button being left was just used for a flick, # don't count the exit: if self.currButtonUsedForFlick: self.currButtonUsedForFlick = False; return; # Get 'ABC', or 'DEF', etc representation from the button: buttonLabelAsStr = str(gestureButtonObj); newEncLetter = self.wordCollection.encodeTelPadLabel(buttonLabelAsStr); self.encEvolvingWord += newEncLetter; self.shiftButtonTrails(HistoryShiftDir.OLDER, newHead=gestureButtonObj); #print self.encEvolvingWord; self.showRemainingWords(); self.updateTickerTape(); def handleEraseWordButton(self): ''' Handler for erase-word button clicked. ''' self.eraseCurrentWord(); self.showRemainingWords(); self.updateTickerTape(); def handleCrossoutTimeout(self): ''' Timeout handler that detects crossing out a letter by running through a gesture button and back. ''' for buttonObj in self.crossedOutButtons: self.setButtonImage(buttonObj, self.buttonBackGroundPixmaps[self.currentButtonBackgrounds[buttonObj]]); self.crossedOutButtons = []; def handleNumPad(self, numButton): ''' Handler for number pad button pressed. @param numButton: button object @type numButton: QPushButton ''' buttonLabel = numButton.text(); self.outputPanel.insertPlainText(buttonLabel); def handleSpecialChars(self, specCharButton): ''' Handler: special character button pushed. @param specCharButton: button object @type specCharButton: QPushButton ''' if specCharButton == self.backspaceButton: self.outputPanel.textCursor().deletePreviousChar(); return; elif specCharButton == self.spaceButton: char = " "; else: char = specCharButton.text(); self.outputPanel.insertPlainText(char); def handleGestureButtonClick(self, buttonObj): ''' Handler for gesture button click. @param buttonObj: Button object @type buttonObj: GestureButton ''' # If button is in default dialpad mode, switch to letter-input mode: if self.buttonEditMode == ButtonEditMode.DIALPAD: self.switchButtonMode(buttonObj, ButtonEditMode.LETTER_INPUT); # If button is in letter edit mode, add the currently # capitalized letter to the output panel, and switch the # button back into default dialpad mode: elif self.buttonEditMode == ButtonEditMode.LETTER_INPUT: labelBeingEdited = buttonObj.text(); capLetter = labelBeingEdited[self.findCapitalLetter(labelBeingEdited)]; self.outputPanel.insertPlainText(capLetter.lower()); self.switchButtonMode(buttonObj, ButtonEditMode.DIALPAD); def handleSaveWordButton(self): # Get content of output panel: currOutput = self.outputPanel.toPlainText(); # If noth'n there, done: if len(currOutput) == 0: QMessageBox.information(self, "Dictionary addition", "Output panel has no content; so there is no word to save.", QMessageBox.Ok, QMessageBox.NoButton); return; # Get the last word in the output panel: newWord = re.split("[.;:?! @()]", currOutput)[-1]; # Ask user about capitalization, and expected word frequency. # This call will raise a modal dialog box. Signal handlers # handleAddDictWordCapitalizeStateChanged(), and handleAddDictWordOK_Cancel() # take it from there: self.getAddWordUserInfo(newWord); def handleAddDictWordCapitalizeStateChanged(self, newCapsState): dialog = self.addWordDialog; newWord = dialog.newWord.text(); if newCapsState == 0: dialog.newWord.setText(newWord.lower()); else: dialog.newWord.setText(newWord.capitalize()); def handleAddDictWordOK_Cancel(self, button): if button == self.addWordDialog.cancelButton: self.addWordDialog.hide(); return; frequencyCheckboxID = self.addWordButtonGroup.checkedId(); if frequencyCheckboxID == TBoard.RARELY_BUTTON_ID: freqRank = UseFrequency.RARELY; elif frequencyCheckboxID == TBoard.OCCCASIONALLY_BUTTON_ID: freqRank = UseFrequency.OCCASIONALLY; elif frequencyCheckboxID == TBoard.CONSTANTLY_BUTTON_ID: freqRank = UseFrequency.CONSTANTLY; else: raise ValueError("Unknown use frequency checkbox ID in add word to dictionary dialog handling: " + str(frequencyCheckboxID)); self.doAddWordButton(self.addWordDialog.newWord.text(), freqRank); self.addWordDialog.hide(); def handleCopyAll(self): self.clipboard.setText(self.outputPanel.toPlainText()); # -------------------------------------- UI Manipulation ------------------------- def doAddWordButton(self, newWord, rank): additionResult = self.wordCollection.addToUserDict(newWord, rankInt=rank); if additionResult: QMessageBox.information(self, # dialog parent "Dictionary addition", "Word '%s' has been saved in user dictionary." % newWord, QMessageBox.Ok, QMessageBox.NoButton); else: QMessageBox.information(self, # dialog parent "Dictionary addition", "Word '%s' was already in the dictionary. No action taken" % newWord, QMessageBox.Ok, QMessageBox.NoButton); def switchButtonMode(self, buttonObj, newEditMode): if newEditMode == ButtonEditMode.DIALPAD: self.setGestureButtonStyle(buttonObj, StyleID.RELEASED); self.buttonEditMode = ButtonEditMode.DIALPAD; buttonObj.setText(buttonObj.text().upper()); elif newEditMode == ButtonEditMode.LETTER_INPUT: self.setGestureButtonStyle(buttonObj, StyleID.PRESSED); self.buttonEditMode = ButtonEditMode.LETTER_INPUT; buttonObj.setText(buttonObj.text().capitalize()); def highlightNextLetter(self, buttonObj, flickDirection): label = buttonObj.text(); capitalLetterPos = self.findCapitalLetter(label); label = label.lower(); if flickDirection == FlickDirection.EAST: newCapPos = (capitalLetterPos + 1) % len(label); else: newCapPos = (capitalLetterPos - 1) % len(label); #label = label[:newCapPos] + label[newCapPos].upper() + label[min(newCapPos + 1, len(label) - 1):]; label = label[:newCapPos] + label[newCapPos].upper() + label[newCapPos + 1:] if newCapPos < len(label) else ""; buttonObj.setText(label); def findCapitalLetter(self, word): for (pos, char) in enumerate(word): if char.isupper(): return pos; raise ValueError("No capital letter found."); def updateTickerTape(self): if len(self.encEvolvingWord) == 0: self.tickerTape.setText(""); return; visibleEncoding = ""; for encChar in self.encEvolvingWord: dialpadButtonLabel = self.wordCollection.decodeTelPadLabel(encChar); buttonID = ButtonID.toButtonID(dialpadButtonLabel); visibleEncoding += ButtonID.idToStringable(buttonID); self.tickerTape.setText(visibleEncoding); def showRemainingWords(self): remainingWords = self.wordCollection.prefix_search(self.encEvolvingWord); rankSortedWords = sorted(remainingWords, key=self.wordCollection.rank); self.wordList.clear(); self.wordList.addItems(rankSortedWords); self.wordList.setCurrentRow(0); #print self.wordCollection.prefix_search(self.encEvolvingWord); def eraseCurrentWord(self): self.encEvolvingWord = ""; self.updateTickerTape(); self.eraseTrail(); self.wordList.clear(); def erasePreviousLetter(self): if len(self.encEvolvingWord) == 0: # Just to make sure, erase all history trail: self.eraseTrail(); return; oldNewestButton = self.getButtonFromEncodedLetter(self.encEvolvingWord[-1]); self.encEvolvingWord = self.encEvolvingWord[0:-1]; if len(self.encEvolvingWord) > 0: newNewestButton = self.getButtonFromEncodedLetter(self.encEvolvingWord[-1]); else: newNewestButton = None; self.shiftButtonTrails(HistoryShiftDir.YOUNGER, newHead=newNewestButton); self.crossOutButton(oldNewestButton); if len(self.encEvolvingWord) == 0: self.eraseTrail(); def crossOutButton(self, buttonObj): ''' Show the given button crossed out. Update the currentButtonBackgrounds dict to show that this button now has a different background (not one of the trails. @param buttonObj: GestureButton object to cross out. @type buttonObj: QPushButton ''' self.setButtonImage(buttonObj, self.crossedOutButtonBackground); self.crossedOutButtons.append(buttonObj); # Take the crossout away in 2 seconds: self.crossOutTimer.start(2000); def eraseTrail(self): ''' Erase the history trail. ''' for buttonObj in self.letterButtons: self.setButtonImage(buttonObj, self.buttonBackGroundPixmaps[-1]); self.currentButtonBackgrounds[buttonObj] = NUM_LETTER_BUTTONS - 1; def shiftButtonTrails(self, direction, newHead=None): if direction == HistoryShiftDir.OLDER: # Every button gets one older: for buttonObj in self.letterButtons: currPixmapIndex = self.currentButtonBackgrounds[buttonObj]; if currPixmapIndex is None: continue; if currPixmapIndex >= NUM_LETTER_BUTTONS - 1: # Button already as old as it gets: continue; buttonObj.setIcon(QIcon(self.buttonBackGroundPixmaps[currPixmapIndex + 1])); self.currentButtonBackgrounds[buttonObj] = currPixmapIndex + 1; if newHead is not None: self.setButtonImage(newHead, self.buttonBackGroundPixmaps[0]); self.currentButtonBackgrounds[newHead] = 0; else: # Make everyone younger: for buttonObj in self.letterButtons: currPixmapIndex = self.currentButtonBackgrounds[buttonObj]; if currPixmapIndex is None: # Button has a special, temporary background, like being crossed out: continue; if currPixmapIndex <= 0: # Button already as young as it gets. Make it the oldest: self.setButtonImage(buttonObj,self.buttonBackGroundPixmaps[NUM_LETTER_BUTTONS - 1]); self.setButtonImage(buttonObj, self.buttonBackGroundPixmaps[currPixmapIndex - 1]); self.currentButtonBackgrounds[buttonObj] = currPixmapIndex - 1; def setButtonImage(self, gestureButtonObj, pixmap): # PySide: gestureButtonObj.setIcon(pixmap); gestureButtonObj.setIcon(QIcon(pixmap)); gestureButtonObj.setIconSize(pixmap.rect().size()); def getAddWordUserInfo(self, newWord): ''' Prepare the Add New Word dialog, and show it: @param newWord: @type newWord: ''' # New word capitalized? Pre-set the Capitalize checkbox accordingly: if newWord.istitle(): self.addWordDialog.capitalizeCheckbox.setChecked(True); else: self.addWordDialog.capitalizeCheckbox.setChecked(False); # Init the label in the dialog box that shows the word to be added: self.addWordDialog.newWord.setText(newWord); # Place the dialog somewhere over the application window: tboardGeoRect = self.geometry(); dialogGeoRect = self.addWordDialog.geometry(); newDialogGeo = QRect(tboardGeoRect.x() + 50, tboardGeoRect.y() + 50, dialogGeoRect.width(), dialogGeoRect.height()); self.addWordDialog.setGeometry(newDialogGeo); self.addWordDialog.show(); # -------------------------------------- Handy Methods ------------------------- def getButtonFromEncodedLetter(self, encLetter): # From the encoded letter, get the corresponding # "ABC", "PQR", etc label: buttonLabel = self.wordCollection.decodeTelPadLabel(encLetter); buttonID = ButtonID.toButtonID(buttonLabel); return self.letterButtons[buttonID];
def __init__(self): super(MorseChallenger,self).__init__(); self.PIXELS_TO_MOVE_PER_TIMEOUT = 3 self.ABSOLUTE_MAX_NUM_FLOATERS = 10; self.FLOATER_POINT_SIZE = 20; self.LETTER_CHECKBOX_NUM_COLS = 6; # Number of timeout cycles that detonated # letters stay visible: self.DETONATION_VISIBLE_CYCLES = 4; self.maxNumFloaters = 1; self.lettersToUse = set(); self.floatersAvailable = set(); self.floatersInUse = set(); self.bookeepingAccessLock = threading.Lock(); # Floaters that detonated. Values are number of timeouts # the detonation was visible: self.floatersDetonated = {}; # Used to launch new floaters at random times: self.cyclesSinceLastLaunch = 0; self.dialogService = DialogService(); self.optionsFilePath = os.path.join(os.getenv('HOME'), '.morser/morseChallenger.cfg'); # Load UI for Morse Challenger: relPathQtCreatorFileMainWin = "qt_files/morseChallenger/morseChallenger.ui"; qtCreatorXMLFilePath = self.findFile(relPathQtCreatorFileMainWin); if qtCreatorXMLFilePath is None: raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFileMainWin); # Make QtCreator generated UI a child of this instance: loadUi(qtCreatorXMLFilePath, self); self.windowTitle = "Morse Challenger"; self.setWindowTitle(self.windowTitle); self.iconDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons') self.explosionPixMap = QPixmap(os.path.join(self.iconDir, 'explosion.png')); CommChannel.registerSignals(MorseChallengerSignals); self.connectWidgets(); self.generateLetterCheckBoxes(); self.simultaneousLettersComboBox.addItems(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); self.generateFloaters(); self.setFocusPolicy(Qt.ClickFocus); self.letterMoveTimer = QTimer(); self.letterMoveTimer.setInterval(self.timerIntervalFromSpeedSlider()); # milliseconds self.letterMoveTimer.setSingleShot(False); self.letterMoveTimer.timeout.connect(self.moveLetters); # Bring options from config file: self.setOptions(); # Styling: self.createColors(); self.setStyleSheet("QWidget{background-color: %s}" % self.lightBlueColor.name()); self.show();
class MatPlotWidget(QWidget): """The actual Widget """ def __init__(self): super(MatPlotWidget, self).__init__() self.setObjectName("MatPlotWidget") ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "MatPlot.ui") loadUi(ui_file, self, {"MatDataPlot": MatDataPlot}) self.subscribe_topic_button.setEnabled(False) self._topic_completer = TopicCompleter(self.topic_edit) self.topic_edit.setCompleter(self._topic_completer) self._start_time = rospy.get_time() self._rosdata = {} # setup drag 'n drop self.data_plot.dropEvent = self.dropEvent self.data_plot.dragEnterEvent = self.dragEnterEvent # init and start update timer for plot self._update_plot_timer = QTimer(self) self._update_plot_timer.timeout.connect(self.update_plot) self._update_plot_timer.start(40) # connect combobox self.comboBox.currentIndexChanged.connect(self.on_combo_box_changed) # params (colors) self.texts = {} self.data_plot._colors = {} self.sub_topics = {} n = 0 for tag in rospy.get_param("/tags"): self.texts[tag] = rospy.get_param("/" + tag + "_text") color = [float(i) for i in list(rospy.get_param("/" + tag + "_color").split(","))] self.data_plot._colors[self.texts[tag]] = color if n != 0: # dont have a topic for the reference self.sub_topics[tag] = "/error_mags/" + rospy.get_param("/tags")[0] + "_ref/" + tag + "_tgt/mag_horiz" else: n += 1 # start with subscription to filtered self.add_topic("/error_mags/rtk_ref/flt_tgt/mag_horiz") def update_plot(self): for topic_name, rosdata in self._rosdata.items(): data_x, data_y = rosdata.next() self.data_plot.update_value(topic_name, data_x, data_y) self.data_plot.draw_plot() @Slot("QDragEnterEvent*") def dragEnterEvent(self, event): if not event.mimeData().hasText(): if not hasattr(event.source(), "selectedItems") or len(event.source().selectedItems()) == 0: qWarning( "MatPlot.dragEnterEvent(): not hasattr(event.source(), selectedItems) or len(event.source().selectedItems()) == 0" ) return item = event.source().selectedItems()[0] ros_topic_name = item.data(0, Qt.UserRole) if ros_topic_name == None: qWarning("MatPlot.dragEnterEvent(): not hasattr(item, ros_topic_name_)") return # get topic name if event.mimeData().hasText(): topic_name = str(event.mimeData().text()) else: droped_item = event.source().selectedItems()[0] topic_name = str(droped_item.data(0, Qt.UserRole)) # check for numeric field type is_numeric, message = is_slot_numeric(topic_name) if is_numeric: event.acceptProposedAction() else: qWarning('MatPlot.dragEnterEvent(): rejecting: "%s"' % (message)) @Slot("QDropEvent*") def dropEvent(self, event): if event.mimeData().hasText(): topic_name = str(event.mimeData().text()) else: droped_item = event.source().selectedItems()[0] topic_name = str(droped_item.data(0, Qt.UserRole)) self.add_topic(topic_name) @Slot(str) def on_topic_edit_textChanged(self, topic_name): # on empty topic name, update topics if topic_name in ("", "/"): self._topic_completer.update_topics() is_numeric, message = is_slot_numeric(topic_name) self.subscribe_topic_button.setEnabled(is_numeric) self.subscribe_topic_button.setToolTip(message) @Slot() def on_subscribe_topic_button_clicked(self): self.add_topic(str(self.topic_edit.text())) def add_topic(self, topic_name): if topic_name in self._rosdata: qWarning("MatPlot.add_topic(): topic already subscribed: %s" % topic_name) return self._rosdata[topic_name] = ROSData(topic_name, self._start_time) data_x, data_y = self._rosdata[topic_name].next() color = self.data_plot._colors[self.comboBox.currentText()] self.data_plot.add_curve(topic_name, data_x, data_y, color) @Slot() def on_clear_button_clicked(self): self.clean_up_subscribers() @Slot(bool) def on_pause_button_clicked(self, checked): if checked: self._update_plot_timer.stop() else: self._update_plot_timer.start(40) def clean_up_subscribers(self): for topic_name, rosdata in self._rosdata.items(): rosdata.close() self.data_plot.remove_curve(topic_name) self._rosdata = {} @Slot(str) def on_combo_box_changed(self, text): # print('In on_combo_box_changed') self.on_clear_button_clicked() self.data_plot.tgt_name = self.comboBox.currentText() tag = [tg for tg, txt in self.texts.iteritems() if txt == self.comboBox.currentText()][0] self.add_topic(self.sub_topics[tag]) window_title = " ".join( [ "Ground Plane Error Magnitude of Sensor:", self.comboBox.currentText(), "for Reference:", self.data_plot.ref_name, ] ) self.setWindowTitle(QApplication.translate("MatPlotWidget", window_title, None, QApplication.UnicodeUTF8))