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();
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()
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()
row = None execute_sequence(get_current_tab_index(), row) main_window.run_sequence_from_selection_pushButton.clicked.connect( execute_current_sequence_from_selection) select_row_signal = RelaySignalInt() select_row_signal.relay_signal.connect(set_selected_row) collision_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'collision_dialog.ui') loadUi(ui_file, collision_notification) collision_notification_timer = QTimer(main_window) collision_notification_timer.setInterval(3000) collision_notification_timer.setSingleShot(True) def hide_collision_notification(result=None): collision_notification_timer.stop() collision_notification.hide() collision_notification_timer.timeout.connect(hide_collision_notification) collision_notification.finished.connect(hide_collision_notification) def check_buttons(): triggered_buttons = kontrol_subscriber.get_triggered_buttons()
row = get_selected_row() if row == get_row_count() - 1: row = None execute_sequence(get_current_tab_index(), row) main_window.run_sequence_from_selection_pushButton.clicked.connect(execute_current_sequence_from_selection) select_row_signal = RelaySignalInt() select_row_signal.relay_signal.connect(set_selected_row) collision_notification = QDialog(main_window) ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'src', 'collision_dialog.ui') loadUi(ui_file, collision_notification) collision_notification_timer = QTimer(main_window) collision_notification_timer.setInterval(3000) collision_notification_timer.setSingleShot(True) def hide_collision_notification(result=None): collision_notification_timer.stop() collision_notification.hide() collision_notification_timer.timeout.connect(hide_collision_notification) collision_notification.finished.connect(hide_collision_notification) def check_buttons(): triggered_buttons = kontrol_subscriber.get_triggered_buttons() if KontrolSubscriber.previous_button in triggered_buttons: if get_selected_row() is not None: select_row_signal.emit(get_selected_row() - 1) else: select_row_signal.emit(0)