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)
Exemple #3
0
    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)
Exemple #5
0
    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")
Exemple #6
0
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();
Exemple #19
0
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))