Trees | Indices | Help |
---|
|
1 #!/usr/bin/env python 2 3 # To do: 4 # - Volume control 5 # - Somewhere (moveEvent()?_: ensure that no overlap of morse win with active window. 6 # If so, show error msg. (Just put into Doc 7 # - Occasional X error: output in startup window, and cursor runs into dot/dash buttons. 8 # - Publish package 9 10 # Doc: 11 # - Abort if mouse click in rest zone. 12 # - Left click to suspend beeping and cursor contraint and timing measure 13 # - Right click: recenter cursor 14 # - Prefs in $HOME/.morser/morser.cfg 15 # - Cheat sheet (Menu) 16 # - Options window 17 # - Crosshair blinks yellow when word separation detected. 18 # - If output gibberish, and you know your Morse was good, *lower* inter letter dwell time 19 # 20 # Needed PYTHONPATH: 21 # /opt/ros/fuerte/lib/python2.7/dist-packages:/home/paepcke/fuerte/stacks/robhum_ui_utils:/home/paepcke/fuerte/stacks/robhum_ui_utils/gesture_buttons/src:/opt/ros/fuerte/stacks/python_qt_binding/src:/home/paepcke/fuerte/stacks/robhum_ui_utils/qt_comm_channel/src:/home/paepcke/fuerte/stacks/robhum_ui_utils/qt_dialog_service/src:/home/paepcke/fuerte/stacks/robhum_ui_utils/virtual_keyboard/src: 22 23 # Dash/Dot blue value: 35,60,149 24 25 import roslib; roslib.load_manifest('morseInput') 26 27 import sys 28 import os 29 import re 30 import fcntl 31 import ConfigParser 32 from functools import partial 33 34 from gesture_buttons.gesture_button import GestureButton 35 from gesture_buttons.gesture_button import FlickDirection 36 37 from qt_comm_channel.commChannel import CommChannel 38 from qt_dialog_service.qt_dialog_service import DialogService 39 40 from morseToneGeneration import MorseGenerator 41 from morseToneGeneration import Morse 42 from morseToneGeneration import TimeoutReason 43 44 from morseCheatSheet import MorseCheatSheet; 45 46 from morseSpeedTimer import MorseSpeedTimer; 47 48 from virtual_keyboard.virtual_keyboard import VirtualKeyboard 49 50 from python_qt_binding import loadUi; 51 from python_qt_binding import QtGui; 52 from python_qt_binding import QtCore; 53 #from word_completion.word_collection import WordCollection; 54 from QtGui import QApplication, QMainWindow, QMessageBox, QWidget, QCursor, QHoverEvent, QColor, QIcon; 55 from QtGui import QMenuBar, QToolTip, QLabel, QPixmap, QRegExpValidator; 56 from QtCore import QPoint, Qt, QTimer, QEvent, Signal, QCoreApplication, QRect, QRegExp; 63 67 73 77 8183 ''' 84 Manages all UI interactions with the Morse code generation. 85 ''' 86 87 MORSE_BUTTON_WIDTH = 100; #px 88 MORSE_BUTTON_HEIGHT = 100; #px 89 90 SUPPORT_BUTTON_WIDTHS = 80; #px: The maximum Space and Backspace button widths. 91 SUPPORT_BUTTON_HEIGHTS = 80; #px: The maximum Space and Backspace button heights. 92 93 MOUSE_UNCONSTRAIN_TIMEOUT = 300; # msec 94 95 HEAD_TRACKER = True; 961160 1161 if __name__ == '__main__': 1162 1163 app = QApplication(sys.argv); 1164 #QApplication.setStyle(QCleanlooksStyle()) 1165 try: 1166 morser = MorseInput(); 1167 app.exec_(); 1168 morser.exit(); 1169 finally: 1170 try: 1171 fcntl.lockf(MorseInput.morserLockFD, fcntl.LOCK_UN) 1172 except IOError: 1173 print ("Could not release Morser lock.") 1174 sys.exit(); 117598 super(MorseInput,self).__init__(); 99 100 # Only allow a single instance of the Morser program to run: 101 self.morserLockFile = '/tmp/morserLock.lk'; 102 MorseInput.morserLockFD = open(self.morserLockFile, 'w') 103 try: 104 # Attempt to lock the lock file exclusively, but 105 # throw IOError if already locked, rather than waiting 106 # for unlock (the LOCK_NB ORing): 107 fcntl.lockf(MorseInput.morserLockFD, fcntl.LOCK_EX | fcntl.LOCK_NB) 108 except IOError: 109 errMsg = "The Morser program is already running. Please quit it.\n" +\ 110 "If Morser really is not running, execute 'rm %s' in a terminal window." % self.morserLockFile; 111 sys.stderr.write(errMsg) 112 sys.exit() 113 114 # Disallow focus acquisition for the morse window. 115 # Needed to prevent preserve focus on window that 116 # is supposed to receive the clear text of the 117 # morse: 118 self.setFocusPolicy(Qt.NoFocus); 119 120 self.iconDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons') 121 122 CommChannel.registerSignals(MorseInputSignals); 123 124 # Find QtCreator's XML file in the PYTHONPATH, and load it: 125 currDir = os.path.realpath(__file__); 126 127 # Load UI for Morse input: 128 relPathQtCreatorFileMainWin = "qt_files/morseInput/morseInput.ui"; 129 qtCreatorXMLFilePath = self.findFile(relPathQtCreatorFileMainWin); 130 if qtCreatorXMLFilePath is None: 131 raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFileMainWin); 132 # Make QtCreator generated UI a child if this instance: 133 loadUi(qtCreatorXMLFilePath, self); 134 self.windowTitle = "Morser: Semi-automatic Morse code input"; 135 self.setWindowTitle(self.windowTitle); 136 137 # Load UI for Morse options dialog: 138 relPathQtCreatorFileOptionsDialog = "qt_files/morserOptions/morseroptions.ui"; 139 qtCreatorXMLFilePath = self.findFile(relPathQtCreatorFileOptionsDialog); 140 if qtCreatorXMLFilePath is None: 141 raise ValueError("Can't find QtCreator user interface file %s" % relPathQtCreatorFileOptionsDialog); 142 # Make QtCreator generated UI a child if this instance: 143 self.morserOptionsDialog = loadUi(qtCreatorXMLFilePath); 144 145 # Load UI for Morse Cheat Sheet: 146 self.morseCheatSheet = MorseCheatSheet(self); 147 148 self.dialogService = DialogService(); 149 150 # Get a morse generator that manages all Morse 151 # generation and timing: 152 self.morseGenerator = MorseGenerator(callback=MorseInput.letterCompleteNotification); 153 154 # Get virtual keyboard that can 'fake' X11 keyboard inputs: 155 self.virtKeyboard = VirtualKeyboard(); 156 157 # setOptions() needs to be called after instantiation 158 # of morseGenerator, so that we can obtain the generator's 159 # defaults for timings: 160 self.optionsFilePath = os.path.join(os.getenv('HOME'), '.morser/morser.cfg'); 161 self.setOptions(); 162 163 # Create the gesture buttons for dot/dash/space/backspace: 164 self.insertGestureButtons(); 165 GestureButton.setFlicksEnabled(False); 166 167 self.installMenuBar(); 168 self.installStatusBar(); 169 170 # Get a speed measurer (must be defined before 171 # call to connectWidgets(): 172 self.speedMeasurer = MorseSpeedTimer(self); 173 174 self.connectWidgets(); 175 self.cursorEnteredOnce = False; 176 177 # Set cursor to hand icon while inside Morser: 178 self.morseCursor = QCursor(Qt.OpenHandCursor); 179 QApplication.setOverrideCursor(self.morseCursor); 180 #QApplication.restoreOverrideCursor() 181 182 self.blinkTimer = None; 183 self.flashTimer = None; 184 185 # Init capability of constraining cursor to 186 # move only virtically and horizontally: 187 self.initCursorConstrainer(); 188 189 # Don't allow editing of the ticker tape: 190 self.tickerTapeLineEdit.setFocusPolicy(Qt.NoFocus); 191 # But allow anything to be placed inside programmatically: 192 tickerTapeRegExp = QRegExp('.*'); 193 tickerTapeValidator = QRegExpValidator(tickerTapeRegExp); 194 self.tickerTapeLineEdit.setValidator(tickerTapeValidator); 195 196 # Deceleration readout is floating point with up to 2 digits after decimal: 197 cursorDecelerationRegExp = QRegExp(r'[\d]{1}[.]{1}[\d]{1,2}$'); 198 cursorDecelerationValidator = QRegExpValidator(cursorDecelerationRegExp); 199 self.morserOptionsDialog.cursorDecelerationReadoutLineEdit.setValidator(cursorDecelerationValidator); 200 201 self.expandPushButton.setFocusPolicy(Qt.NoFocus); 202 203 # Styling: 204 self.createColors(); 205 self.setStyleSheet("QWidget{background-color: %s}" % self.lightBlueColor.name()); 206 207 # Power state: initially on: 208 self.poweredUp = True; 209 210 self.show(); 211 212 # Compute global x positions of dash/dot buttons facing 213 # towards the rest area: 214 self.computeInnerButtonEdges(); 215 216 # Monitor mouse, so that we can constrain mouse movement to 217 # vertical and horizontal (must be set after the affected 218 # widget(s) are visible): 219 #self.setMouseTracking(True) 220 self.centralWidget.installEventFilter(self); 221 self.centralWidget.setMouseTracking(True)222224 self.recentMousePos = None; 225 self.currentMouseDirection = None; 226 self.enableConstrainVertical = False; 227 # Holding left mouse button inside the Morse 228 # window will suspend cursor constraining, 229 # if it is enabled. Letting go of the button 230 # will re-enable constraints. This var 231 # keeps track of suspension so mouse-button-up 232 # knows whether to re-instate constraining: 233 self.cursorContraintSuspended = False; 234 235 # Timer that frees the cursor from 236 # vertical/horizontal constraint every few 237 # milliseconds, unless mouse keeps moving: 238 self.mouseUnconstrainTimer = QTimer(); 239 self.mouseUnconstrainTimer.setInterval(MorseInput.MOUSE_UNCONSTRAIN_TIMEOUT); 240 self.mouseUnconstrainTimer.setSingleShot(True); 241 self.mouseUnconstrainTimer.timeout.connect(self.unconstrainTheCursor);242244 # Remember the X position of the global-screen right 245 # edge of the dot button for reference in the event filter: 246 localGeo = self.dotButton.geometry(); 247 dotButtonGlobalPos = self.mapToGlobal(QPoint(localGeo.x() + localGeo.width(), 248 localGeo.y())); 249 self.dotButtonGlobalRight = dotButtonGlobalPos.x(); 250 # Remember the X position of the global-screen left 251 # edge of the dash button for reference in the event filter: 252 localGeo = self.dashButton.geometry(); 253 dashButtonGlobalPos = self.mapToGlobal(QPoint(localGeo.x(), localGeo.y())); 254 self.dashButtonGlobalLeft = dashButtonGlobalPos.x(); 255 256 # Remember global location of the central point in the rest zone: 257 self.crosshairLabel.setMaximumHeight(11); # Height of crosshair icon 258 self.crosshairLabel.setMaximumWidth(11); # Width of crosshair icon 259 # Compute the location of the crosshair in the center of 260 # the rest area. The addition of 20 pixels to the Y-coordinate 261 # accounts for Ubuntu's title bar at the top of the display, 262 # which mapToGlobal() does not account for: 263 self.centralRestGlobalPos = self.mapToGlobal(self.crosshairLabel.pos() + QPoint(0,20));264 267 271273 self.grayBlueColor = QColor(89,120,137); # Letter buttons 274 self.lightBlueColor = QColor(206,230,243); # Background 275 self.darkGray = QColor(65,88,101); # Central buttons 276 self.wordListFontColor = QColor(62,143,185); # Darkish blue. 277 self.purple = QColor(147,124,195); # Gesture button pressed278 279281 if path is None: 282 return None 283 for dirname in sys.path: 284 candidate = os.path.join(dirname, path) 285 if matchFunc(candidate): 286 return candidate 287 return None;288290 291 self.dotButton = GestureButton('dot'); 292 self.dotButton.setIcon(QIcon(os.path.join(self.iconDir, 'dot.png'))); 293 self.dotButton.setText(""); 294 # Don't have button assume the pressed-down color when 295 # clicked: 296 self.dotButton.setFocusPolicy(Qt.NoFocus); 297 self.dotButton.setMinimumHeight(MorseInput.MORSE_BUTTON_HEIGHT); 298 self.dotButton.setMinimumWidth(MorseInput.MORSE_BUTTON_WIDTH); 299 self.dotAndDashHLayout.addWidget(self.dotButton); 300 301 self.dotAndDashHLayout.addStretch(); 302 303 # Crosshair: 304 self.crosshairPixmapClear = QPixmap(os.path.join(self.iconDir, 'crosshairEmpty.png')); 305 self.crosshairPixmapGreen = QPixmap(os.path.join(self.iconDir, 'crosshairGreen.png')); 306 self.crosshairPixmapYellow = QPixmap(os.path.join(self.iconDir, 'crosshairYellow.png')); 307 self.crosshairPixmapRED = QPixmap(os.path.join(self.iconDir, 'crosshairRed.png')); 308 self.crosshairLabel = QLabel(); 309 self.crosshairLabel.setPixmap(self.crosshairPixmapClear); 310 self.crosshairLabel.setText(""); 311 self.dotAndDashHLayout.addWidget(self.crosshairLabel); 312 313 self.dotAndDashHLayout.addStretch(); 314 315 self.dashButton = GestureButton('dash'); 316 self.dashButton.setIcon(QIcon(os.path.join(self.iconDir, 'dash.png'))); 317 self.dashButton.setText(""); 318 # Don't have button assume the pressed-down color when 319 # clicked: 320 self.dashButton.setFocusPolicy(Qt.NoFocus); 321 self.dashButton.setMinimumHeight(MorseInput.MORSE_BUTTON_HEIGHT); 322 self.dashButton.setMinimumWidth(MorseInput.MORSE_BUTTON_WIDTH); 323 self.dotAndDashHLayout.addWidget(self.dashButton); 324 325 self.eowButton = GestureButton('Space'); 326 self.eowButton.setAutoRepeat(True); 327 # Don't have button assume the pressed-down color when 328 # clicked: 329 self.eowButton.setFocusPolicy(Qt.NoFocus); 330 self.eowButton.setMaximumWidth(MorseInput.SUPPORT_BUTTON_WIDTHS) 331 self.eowButton.setMinimumHeight(MorseInput.SUPPORT_BUTTON_HEIGHTS) 332 self.endOfWordButtonHLayout.addWidget(self.eowButton); 333 334 self.backspaceButton = GestureButton('Backspace'); 335 self.backspaceButton.setAutoRepeat(True); 336 # Don't have button assume the pressed-down color when 337 # clicked: 338 self.backspaceButton.setFocusPolicy(Qt.NoFocus); 339 self.backspaceButton.setMaximumWidth(MorseInput.SUPPORT_BUTTON_WIDTHS) 340 self.backspaceButton.setMinimumHeight(MorseInput.SUPPORT_BUTTON_HEIGHTS) 341 self.backspaceHLayout.addWidget(self.backspaceButton); 342 343 self.powerPushButton.setIcon(QIcon(os.path.join(self.iconDir, 'powerIconSmall.png'))); 344 # Don't have button assume the pressed-down color when 345 # clicked: 346 self.powerPushButton.setFocusPolicy(Qt.NoFocus); 347 self.powerPushButton.setChecked(True); 348 349 # Prevent focus on the two clear buttons: 350 self.speedMeasureClearButton.setFocusPolicy(Qt.NoFocus); 351 self.tickerTapeClearButton.setFocusPolicy(Qt.NoFocus); 352 self.timeMeButton.setFocusPolicy(Qt.NoFocus);353355 exitAction = QtGui.QAction(QtGui.QIcon('exit.png'), '&Exit', self) 356 exitAction.setShortcut('Ctrl+Q') 357 exitAction.setStatusTip('Exit application') 358 exitAction.triggered.connect(self.close) 359 360 raiseOptionsDialogAction = QtGui.QAction(QtGui.QIcon('preferences-desktop-accessibility.png'), '&Options', self) 361 362 raiseOptionsDialogAction.setShortcut('Ctrl+O'); 363 raiseOptionsDialogAction.setStatusTip('Show options and settings') 364 raiseOptionsDialogAction.triggered.connect(self.showOptions) 365 366 raiseCheatSheetAction = QtGui.QAction(QtGui.QIcon('preferences-desktop-accessibility.png'), '&Cheat sheet', self) 367 raiseCheatSheetAction.setShortcut('Ctrl+M') 368 raiseCheatSheetAction.setStatusTip('Show Morse code cheat sheet') 369 raiseCheatSheetAction.triggered.connect(self.showCheatSheet) 370 371 fileMenu = self.menuBar.addMenu('&File') 372 fileMenu.addAction(exitAction) 373 374 editMenu = self.menuBar.addMenu('&Edit') 375 editMenu.addAction(raiseOptionsDialogAction) 376 377 viewMenu = self.menuBar.addMenu('&View') 378 viewMenu.addAction(raiseCheatSheetAction) 379 # When showing table the first time, we move it left so that it 380 # does not totally obscure the Morse window: 381 self.shownCheatSheetBefore = False;382 385 386388 389 # Signal connections: 390 CommChannel.getSignal('GestureSignals.buttonEnteredSig').connect(self.buttonEntered); 391 CommChannel.getSignal('GestureSignals.buttonExitedSig').connect(self.buttonExited); 392 CommChannel.getSignal('MorseInputSignals.letterDone').connect(self.deliverInput); 393 CommChannel.getSignal('MorseInputSignals.panelCollapsed').connect(self.adjustMainWindowHeight); 394 395 # Main window: 396 self.timeMeButton.pressed.connect(self.speedMeasurer.timeMeToggled); 397 self.tickerTapeClearButton.clicked.connect(self.tickerTapeClear); 398 self.powerPushButton.pressed.connect(self.powerToggled); 399 self.expandPushButton.clicked.connect(self.togglePanelExpansion); 400 401 # Options dialog: 402 self.morserOptionsDialog.cursorConstraintCheckBox.stateChanged.connect(partial(self.checkboxStateChanged, 403 self.morserOptionsDialog.cursorConstraintCheckBox)); 404 self.morserOptionsDialog.wordStopSegmentationCheckBox.stateChanged.connect(partial(self.checkboxStateChanged, 405 self.morserOptionsDialog.wordStopSegmentationCheckBox)); 406 self.morserOptionsDialog.typeOutputRadioButton.toggled.connect(partial(self.checkboxStateChanged, 407 self.morserOptionsDialog.typeOutputRadioButton)); 408 self.morserOptionsDialog.speechOutputRadioButton.toggled.connect(partial(self.checkboxStateChanged, 409 self.morserOptionsDialog.speechOutputRadioButton)); 410 self.morserOptionsDialog.useTickerCheckBox.toggled.connect(partial(self.checkboxStateChanged, 411 self.morserOptionsDialog.useTickerCheckBox)); 412 413 self.morserOptionsDialog.cursorDecelerationSlider.valueChanged.connect(partial(self.sliderStateChanged, 414 self.morserOptionsDialog.cursorDecelerationSlider)); 415 self.morserOptionsDialog.keySpeedSlider.valueChanged.connect(partial(self.sliderStateChanged, 416 self.morserOptionsDialog.keySpeedSlider)); 417 self.morserOptionsDialog.interLetterDelaySlider.valueChanged.connect(partial(self.sliderStateChanged, 418 self.morserOptionsDialog.interLetterDelaySlider)); 419 self.morserOptionsDialog.interWordDelaySlider.valueChanged.connect(partial(self.sliderStateChanged, 420 self.morserOptionsDialog.interWordDelaySlider)); 421 422 self.morserOptionsDialog.savePushButton.clicked.connect(self.optionsSaveButton); 423 self.morserOptionsDialog.cancelPushButton.clicked.connect(self.optionsCancelButton); 424 425 self.morserOptionsDialog.cursorDecelerationReadoutLineEdit.editingFinished.connect(partial(self.sliderReadoutModified, 426 self.morserOptionsDialog.cursorDecelerationSlider)); 427 self.morserOptionsDialog.cursorDecelerationReadoutLineEdit.returnPressed.connect(partial(self.sliderReadoutModified, 428 self.morserOptionsDialog.cursorDecelerationSlider)); 429 self.morserOptionsDialog.keySpeedReadoutLineEdit.editingFinished.connect(partial(self.sliderReadoutModified, 430 self.morserOptionsDialog.keySpeedSlider)); 431 self.morserOptionsDialog.keySpeedReadoutLineEdit.returnPressed.connect(partial(self.sliderReadoutModified, 432 self.morserOptionsDialog.keySpeedSlider)); 433 self.morserOptionsDialog.letterDwellReadoutLineEdit.editingFinished.connect(partial(self.sliderReadoutModified, 434 self.morserOptionsDialog.interLetterDelaySlider)); 435 self.morserOptionsDialog.letterDwellReadoutLineEdit.returnPressed.connect(partial(self.sliderReadoutModified, 436 self.morserOptionsDialog.interLetterDelaySlider)); 437 self.morserOptionsDialog.wordDwellReadoutLineEdit.editingFinished.connect(partial(self.sliderReadoutModified, 438 self.morserOptionsDialog.interWordDelaySlider)); 439 self.morserOptionsDialog.wordDwellReadoutLineEdit.returnPressed.connect(partial(self.sliderReadoutModified, 440 self.morserOptionsDialog.interWordDelaySlider));441443 ''' 444 Expand or hide the main window's bottom panel, which holds the speed meter. 445 @param expansion: expand vs. hide 446 @type expansion: PanelExpansion 447 ''' 448 if expansion == PanelExpansion.LESS: 449 self.expandPushButton.setIcon(QIcon(os.path.join(self.iconDir, 'plusSign.png'))); 450 self.speedMeasureWidget.setHidden(True); 451 # Remember this state in configuration: 452 self.cfgParser.set('Appearance','morePanelExpanded',str(False)); 453 newMainWinGeo = self.getAdjustedWinGeo(-1 * self.speedMeasureWidget.geometry().height()); 454 MorseInputSignals.getSignal('MorseInputSignals.panelCollapsed').emit(newMainWinGeo.x(), 455 newMainWinGeo.y(), 456 newMainWinGeo.width(), 457 newMainWinGeo.height()); 458 else: 459 self.expandPushButton.setIcon(QIcon(os.path.join(self.iconDir, 'minusSign.png'))); 460 self.speedMeasureWidget.setHidden(False); 461 self.cfgParser.set('Appearance','morePanelExpanded',str(True));462464 ''' 465 Given a positive or negative number of pixels, 466 return a rectangle that is respectively higher or 467 shorter than the current main window. 468 @param pixels: number of pixels to add or subtract from the height 469 @type pixels: int 470 @return: new rectangle 471 @rtype: QRect 472 ''' 473 geo = self.geometry(); 474 newHeight = geo.height() + pixels; 475 newGeo = QRect(geo.x(), geo.y(), geo.width(), newHeight); 476 return newGeo;477 478 @QtCore.Slot(int,int,int,int)480 ''' 481 Given a rectangle, adjust the main window dimensions 482 to take that shape. 483 @param x: 484 @type x: int 485 @param y: 486 @type y: int 487 @param width: 488 @type width: int 489 @param height: 490 @type height: int 491 ''' 492 self.setMaximumHeight(self.geometry().height() - self.speedMeasureWidget.geometry().height());493495 if self.speedMeasureWidget.isVisible(): 496 self.expandMorePanel(PanelExpansion.LESS); 497 else: 498 self.expandMorePanel(PanelExpansion.MORE);499 502504 self.morseCheatSheet.show(); 505 if not self.shownCheatSheetBefore: 506 cheatSheetGeo = self.morseCheatSheet.geometry(); 507 cheatSheetGeo.moveLeft(200); 508 self.morseCheatSheet.setGeometry(cheatSheetGeo); 509 self.shownCheatSheetBefore = True;510512 513 self.optionsDefaultDict = { 514 'outputDevice' : str(OutputType.TYPE), 515 'letterDwellSegmentation' : str(True), 516 'wordDwellSegmentation' : str(True), 517 'constrainCursorInHotZone' : str(False), 518 'keySpeed' : str(1.7), 519 'cursorDeceleration' : str(0.5), 520 'interLetterDwellDelay' : str(self.morseGenerator.getInterLetterTime()), 521 'interWordDwellDelay' : str(self.morseGenerator.getInterWordTime()), 522 'winGeometry' : '100,100,350,350', 523 'useTickerTape' : str(True), 524 'morePanelExpanded' : str(True), 525 } 526 527 self.cfgParser = ConfigParser.SafeConfigParser(self.optionsDefaultDict); 528 self.cfgParser.add_section('Morse generation'); 529 self.cfgParser.add_section('Output'); 530 self.cfgParser.add_section('Appearance'); 531 self.cfgParser.read(self.optionsFilePath); 532 533 mainWinGeometry = self.cfgParser.get('Appearance', 'winGeometry'); 534 # Get four ints from the comma-separated string of upperLeftX, upperLeftY, 535 # Width,Height numbers: 536 try: 537 nums = mainWinGeometry.split(','); 538 self.setGeometry(QRect(int(nums[0].strip()),int(nums[1].strip()),int(nums[2].strip()),int(nums[3].strip()))); 539 except Exception as e: 540 self.dialogService.showErrorMsg("Could not set window size; config file spec not grammatical: %s. (%s" % (mainWinGeometry, `e`)); 541 542 self.setCursorDeceleration(self.cfgParser.getfloat('Morse generation', 'cursorDeceleration')); 543 544 self.morseGenerator.setInterLetterDelay(self.cfgParser.getfloat('Morse generation', 'interLetterDwellDelay')); 545 self.morseGenerator.setInterWordDelay(self.cfgParser.getfloat('Morse generation', 'interWordDwellDelay')); 546 self.morseGenerator.setSpeed(self.cfgParser.getfloat('Morse generation', 'keySpeed')); 547 548 self.constrainCursorInHotZone = self.cfgParser.getboolean('Morse generation', 'constrainCursorInHotZone'); 549 self.outputDevice = self.cfgParser.getint('Output', 'outputDevice'); 550 self.letterDwellSegmentation = self.cfgParser.getboolean('Morse generation', 'letterDwellSegmentation'); 551 self.letterDwellSegmentation = self.cfgParser.getboolean('Morse generation', 'wordDwellSegmentation'); 552 553 self.useTickerTape = self.cfgParser.getboolean('Output', 'useTickerTape'); 554 555 self.panelExpanded = self.cfgParser.getboolean('Appearance', 'morePanelExpanded'); 556 if self.panelExpanded: 557 self.expandMorePanel(PanelExpansion.MORE); 558 else: 559 self.expandMorePanel(PanelExpansion.LESS); 560 561 # Make the options dialog reflect the options we just established: 562 # Path to Morser options file: 563 self.initOptionsDialogFromOptions();564566 567 # Cursor constraint: 568 self.morserOptionsDialog.cursorConstraintCheckBox.setChecked(self.cfgParser.getboolean('Morse generation', 'constrainCursorInHotZone')); 569 570 # Automatic word segmentation: 571 enableWordSegmentation = self.cfgParser.getboolean('Morse generation', 'wordDwellSegmentation'); 572 self.morserOptionsDialog.wordStopSegmentationCheckBox.setChecked(enableWordSegmentation); 573 if not enableWordSegmentation: 574 self.morserOptionsDialog.interWordDelaySlider.setEnabled(False); 575 self.morserOptionsDialog.wordDwellReadoutLineEdit.setEnabled(False); 576 577 # Output to X11 vs. Speech: 578 self.morserOptionsDialog.typeOutputRadioButton.setChecked(self.cfgParser.getint('Output', 'outputDevice')==OutputType.TYPE); 579 self.morserOptionsDialog.speechOutputRadioButton.setChecked(self.cfgParser.getint('Output', 'outputDevice')==OutputType.SPEAK); 580 581 582 # Cursor deceleration: 583 cursorDeceleration = self.cfgParser.getfloat('Morse generation', 'cursorDeceleration'); 584 self.morserOptionsDialog.cursorDecelerationSlider.setValue(int(cursorDeceleration * 100)); 585 # Readout for the cursor deceleration: 586 self.morserOptionsDialog.cursorDecelerationReadoutLineEdit.setText(str(cursorDeceleration)); 587 588 # Key speed slider: 589 self.morserOptionsDialog.keySpeedSlider.setValue(int(10*self.cfgParser.getfloat('Morse generation', 'keySpeed'))); 590 # Init the readout of the speed slider: 591 self.morserOptionsDialog.keySpeedReadoutLineEdit.setText(str(self.morserOptionsDialog.keySpeedSlider.value())); 592 593 # Dwell time that indicates end of Morse letter: 594 interLetterSecs = self.cfgParser.getfloat('Morse generation', 'interLetterDwellDelay'); 595 self.morserOptionsDialog.interLetterDelaySlider.setValue(int(interLetterSecs*1000.)); # inter-letter dwell slider is in msecs 596 # Init the readout of the letter dwell slider: 597 self.morserOptionsDialog.letterDwellReadoutLineEdit.setText(str(self.morserOptionsDialog.interLetterDelaySlider.value())); 598 599 # Dwell time that indicates end of word: 600 interWordSecs = self.cfgParser.getfloat('Morse generation', 'interWordDwellDelay'); 601 self.morserOptionsDialog.interWordDelaySlider.setValue(int(interWordSecs*1000.)); # inter-word dwell slider is in msecs 602 # Init the readout of the word dwell slider: 603 self.morserOptionsDialog.wordDwellReadoutLineEdit.setText(str(self.morserOptionsDialog.interWordDelaySlider.value())); 604 self.morserOptionsDialog.useTickerCheckBox.setChecked(self.cfgParser.getboolean('Output', 'useTickerTape'));605607 if slider == self.morserOptionsDialog.cursorDecelerationSlider: 608 slider.setValue(int(float(self.morserOptionsDialog.cursorDecelerationReadoutLineEdit.text()) * 100.0)); 609 elif slider == self.morserOptionsDialog.keySpeedSlider: 610 slider.setValue(int(self.morserOptionsDialog.keySpeedReadoutLineEdit.text())); 611 elif slider == self.morserOptionsDialog.interLetterDelaySlider: 612 slider.setValue(int(self.morserOptionsDialog.letterDwellReadoutLineEdit.text())); 613 elif slider == self.morserOptionsDialog.interWordDelaySlider: 614 slider.setValue(int(self.morserOptionsDialog.wordDwellReadoutLineEdit.text())); 615 else: 616 raise ValueError("Expecting one of the options slider objects.") 617 slider.setFocus();618 619621 ''' 622 Called when any of the option dialog's checkboxes change: 623 @param checkbox: the affected checkbox 624 @type checkbox: QCheckBox 625 @param newState: the new state, though Qt docs are cagey about what this means: an int of some kind. 626 @type newState: QCheckState 627 ''' 628 checkboxNowChecked = checkbox.isChecked(); 629 if checkbox == self.morserOptionsDialog.cursorConstraintCheckBox: 630 self.cfgParser.set('Morse generation','constrainCursorInHotZone',str(checkboxNowChecked)); 631 self.constrainCursorInHotZone = checkboxNowChecked; 632 elif checkbox == self.morserOptionsDialog.useTickerCheckBox: 633 self.cfgParser.set('Output','useTickerTape', str(checkboxNowChecked)); 634 self.useTickerTape = checkboxNowChecked; 635 elif checkbox == self.morserOptionsDialog.wordStopSegmentationCheckBox: 636 self.cfgParser.set('Morse generation', 'wordDwellSegmentation', str(checkboxNowChecked)); 637 self.cfgParser.set('Morse generation', 'interWordDwellDelay', str(self.morserOptionsDialog.interWordDelaySlider.value()/1000.)); 638 # Enable or disable the inter word delay slider and text box if 639 # word dwell is enabled, and vice versa: 640 if checkboxNowChecked: 641 self.morserOptionsDialog.interWordDelaySlider.setEnabled(True); 642 self.morserOptionsDialog.wordDwellReadoutLineEdit.setEnabled(True); 643 self.morseGenerator.setInterWordDelay(int(self.morserOptionsDialog.wordDwellReadoutLineEdit.text())/1000.0); 644 else: 645 self.morserOptionsDialog.interWordDelaySlider.setEnabled(False); 646 self.morserOptionsDialog.wordDwellReadoutLineEdit.setEnabled(False); 647 # Disable word segmentation: 648 self.morseGenerator.setInterWordDelay(-1); 649 elif checkbox == self.morserOptionsDialog.typeOutputRadioButton: 650 self.cfgParser.set('Output', 'outputDevice', str(OutputType.TYPE)); 651 #************** 652 pass 653 #************** 654 elif checkbox == self.morserOptionsDialog.speechOutputRadioButton: 655 self.cfgParser.set('Output', 'outputDevice', str(OutputType.SPEAK)); 656 #************** 657 pass 658 #************** 659 else: 660 raise ValueError('Unknown checkbox: %s' % str(checkbox));661 662664 #slider.setToolTip(str(newValue)); 665 #QToolTip.showText(slider.pos(), str(newValue), slider, slider.geometry()) 666 if slider == self.morserOptionsDialog.cursorDecelerationSlider: 667 newValue = newValue/100.0 668 # Update readout: 669 self.morserOptionsDialog.cursorDecelerationReadoutLineEdit.setText(str(newValue)); 670 self.cfgParser.set('Morse generation', 'cursorDeceleration', str(newValue)); 671 self.setCursorDeceleration(newValue); 672 elif slider == self.morserOptionsDialog.keySpeedSlider: 673 # Update readout: 674 self.morserOptionsDialog.keySpeedReadoutLineEdit.setText(str(newValue)); 675 # Speed slider goes from 1 to 6, but QT is set to 676 # have it go from 1 to 60, because fractional intervals 677 # are not allowed. So, scale the read value: 678 newValue = newValue/10.0 679 self.cfgParser.set('Morse generation', 'keySpeed', str(newValue)); 680 self.morseGenerator.setSpeed(newValue); 681 elif slider == self.morserOptionsDialog.interLetterDelaySlider: 682 self.morserOptionsDialog.letterDwellReadoutLineEdit.setText(str(newValue)); 683 valInSecs = newValue/1000.; 684 self.cfgParser.set('Morse generation', 'interLetterDwellDelay', str(valInSecs)); 685 self.morseGenerator.setInterLetterDelay(valInSecs); 686 elif slider == self.morserOptionsDialog.interWordDelaySlider: 687 self.morserOptionsDialog.wordDwellReadoutLineEdit.setText(str(newValue)); 688 valInSecs = newValue/1000.; 689 self.cfgParser.set('Morse generation', 'interWordDwellDelay', str(valInSecs)); 690 self.morseGenerator.setInterWordDelay(valInSecs);691693 ''' 694 Change deceleration multiplier for cursor movement inside the 695 rest zone. Value should vary between 0.001 and 1.0 696 @param newValue: multiplier that decelerates cursor. 697 @type newValue: float. 698 ''' 699 self.cursorAcceleration = newValue;700702 try: 703 # Does the config dir already exist? If not 704 # create it: 705 optionsDir = os.path.dirname(self.optionsFilePath); 706 if not os.path.isdir(optionsDir): 707 os.makedirs(optionsDir, 0777); 708 with open(self.optionsFilePath, 'wb') as outFd: 709 self.cfgParser.write(outFd); 710 except IOError as e: 711 self.dialogService.showErrorMsg("Could not save options: %s" % `e`); 712 713 self.morserOptionsDialog.hide();714716 ''' 717 Undo option changes user played with while 718 option box was open. 719 ''' 720 self.morserOptionsDialog.hide(); 721 self.cfgParser.read(self.optionsFilePath); 722 self.initOptionsDialogFromOptions();723 737 743745 746 eventType = event.type(); 747 748 if eventType == QEvent.Enter: 749 # The first time cursor ever enters the Morse 750 # window do the following: 751 if not self.cursorEnteredOnce: 752 # Get the Morse windows X11 window ID saved, 753 # so that we can later activate it whenever 754 # the cursor leaves the Morse window. 755 # First, make the Morse window active: 756 self.virtKeyboard.activateWindow(windowTitle=self.windowTitle); 757 self.virtKeyboard.saveActiveWindowID('morseWinID'); 758 # Also, initialize the keyboard destination: 759 self.virtKeyboard.saveActiveWindowID('keyboardTarget'); 760 self.cursorEnteredOnce = True; 761 762 # Remember X11 window that is active as we 763 # enter the application window, but don't remember 764 # this morse code window, if that was the active one: 765 self.virtKeyboard.saveActiveWindowID('currentActiveWindow'); 766 #*********** 767 # For testing cursor enter/leave focus changes: 768 #currentlyActiveWinID = self.virtKeyboard._getWinIDSafely_('currentActiveWindow'); 769 #morseWinID = self.virtKeyboard._getWinIDSafely_('morseWinID'); 770 #print("Morse win: %s. Curr-active win: %s" % (morseWinID, currentlyActiveWinID)) 771 #*********** 772 if not self.virtKeyboard.windowsEqual('morseWinID', 'currentActiveWindow'): 773 self.virtKeyboard.saveActiveWindowID('keyboardTarget'); 774 #*************** 775 #print("Keyboard target: %s" % self.virtKeyboard._getWinIDSafely_('keyboardTarget')); 776 #*************** 777 self.virtKeyboard.activateWindow(retrievalKey='keyboardTarget'); 778 779 elif eventType == QEvent.Leave: 780 self.virtKeyboard.activateWindow(retrievalKey='morseWinID'); 781 #if (eventType == QEvent.MouseMove) or (event == QHoverEvent): 782 elif eventType == QEvent.MouseMove: 783 if self.constrainCursorInHotZone: 784 self.mouseUnconstrainTimer.stop(); 785 self.handleCursorConstraint(event); 786 # Pass this event on to its destination (rather than filtering it): 787 return False;788790 ''' 791 Called when window is repositioned. Need to 792 recompute the cashed global-x positions of 793 the right-side dot button, and the left-side 794 dash button. 795 @param event: move event 796 @type event: QMoveEvent 797 ''' 798 self.computeInnerButtonEdges();799801 802 if (mouseEvent.button() != Qt.LeftButton): 803 return; 804 805 # Re-activate the most recently active X11 window 806 # to ensure the letters are directed to the 807 # proper window, and not this morse window: 808 self.virtKeyboard.activateWindow('keyboardTarget'); 809 if self.cursorInRestZone(mouseEvent.pos()): 810 self.morseGenerator.abortCurrentMorseElement(); 811 812 # Release cursor constraint while mouse button is pressed down. 813 if self.constrainCursorInHotZone: 814 self.stopCursorConstraint(); 815 self.cursorContraintSuspended = True; 816 817 # Pause speed timing, if it's running: 818 self.speedMeasurer.pauseTiming(); 819 820 mouseEvent.accept();821823 824 # If right button is the one that was released, 825 # re-center the mouse to the crosshair. This is 826 # needed with the head mouse tracker to re-calibrate 827 # where it thinks the cursor is located: 828 if mouseEvent.button() == Qt.RightButton: 829 self.morseCursor.setPos(self.centralRestGlobalPos); 830 831 if self.cursorContraintSuspended: 832 self.cursorContraintSuspended = False; 833 self.startCursorConstraint(); 834 # Resume speed timing, (if it was paused: 835 self.speedMeasurer.resumeTiming();836838 newMorseWinRect = self.geometry(); 839 self.cfgParser.set('Appearance', 840 'winGeometry', 841 str(newMorseWinRect.x()) + ',' + 842 str(newMorseWinRect.y()) + ',' + 843 str(newMorseWinRect.width()) + ',' + 844 str(newMorseWinRect.height())); 845 self.optionsSaveButton(); 846 # Update cache of button edge and rest area positions: 847 self.computeInnerButtonEdges();848850 # Button geometries are local, so convert the 851 # given global position: 852 localPos = self.mapFromGlobal(pos); 853 854 dotButtonGeo = self.dotButton.geometry(); 855 dashButtonGeo = self.dashButton.geometry(); 856 return localPos.x() > dotButtonGeo.right() and\ 857 localPos.x() < dashButtonGeo.left() and\ 858 localPos.y() > dotButtonGeo.top() and\ 859 localPos.y() < dotButtonGeo.bottom();860862 ''' 863 Return True if given position is within the given button object. 864 An optional positive or negative tolerance is added to the 865 button dimensions. This addition allows for caller to compensate 866 for cursor drift by 'blurring' the true button edges. 867 @param buttonObj: QPushButton or derivative to check. 868 @type buttonObj: QPushButton 869 @param pos: x/y coordinate to test 870 @type pos: QPoint 871 @param tolerance: number of pixels the cursor may be outside the button, yet still be reported as inside. 872 @type tolerance: int 873 ''' 874 # Button geometries are local, so convert the 875 # given global position: 876 buttonGeo = buttonObj.geometry(); 877 globalButtonPos = self.mapToGlobal(QPoint(buttonGeo.x() + tolerance, 878 buttonGeo.y() + tolerance)); 879 globalGeo = QRect(globalButtonPos.x(), globalButtonPos.y(), buttonGeo.width(), buttonGeo.height()); 880 return globalGeo.contains(pos);881883 ''' 884 Manages constraining the cursor to vertical/horizontal. Caller is 885 responsible for checking that cursor constraining is wanted. This 886 method assumes so. 887 @param mouseEvent: mouse move event that needs to be addressed 888 @type mouseEvent: QMouseEvent 889 ''' 890 891 try: 892 if self.recentMousePos is None: 893 # Very first time: establish a 'previous' mouse cursor position: 894 self.recentMousePos = mouseEvent.globalPos(); 895 self.headTrackerCursorDrift = 0; 896 return; 897 898 globalPosX = mouseEvent.globalX() 899 globalPosY = mouseEvent.globalY() 900 globalPos = QPoint(globalPosX, globalPosY); 901 localPos = mouseEvent.pos(); 902 903 # If we were already within the dot or dash button 904 # before this new mouse move, and the new mouse 905 # position is still inside that button, then keep 906 # the mouse at the inner edge of the respective button. 907 # ('Inner edge' means facing the resting zone): 908 oldInDot = self.cursorInButton(self.dotButton, self.recentMousePos); 909 oldInDash = self.cursorInButton(self.dashButton, self.recentMousePos); 910 newInDot = self.cursorInButton(self.dotButton, globalPos, tolerance=1); 911 newInDash = self.cursorInButton(self.dashButton, globalPos, tolerance=0); 912 913 oldInButton = oldInDot or oldInDash; 914 newInButton = newInDot or newInDash; 915 916 # Mouse moving within one of the buttons? If 917 # so, keep mouse at the button's inner edge 918 # (facing the rest zone): 919 if newInButton: 920 if newInDot: 921 # The '-1' moves the cursor slightly left into 922 # the Dot button, rather than keeping it right on 923 # the edge. This is to avoid the cursor seemingej 924 # To 'bounce' off the right dot button border back 925 # into the dead zone. The pixel is the drop shadow 926 # on the right side of the dot button. The '+12' places 927 # the hand cursor a bit below the crosshair, so that 928 # color flashes of the crosshair can be seen: 929 self.morseCursor.setPos(self.dotButtonGlobalRight-1, self.centralRestGlobalPos.y() + 12); 930 elif newInDash: 931 self.morseCursor.setPos(self.dashButtonGlobalLeft+1, self.centralRestGlobalPos.y() + 12); 932 return; 933 934 # Only constrain while in rest zone (central empty space), or 935 # inside the dot or dash buttons: 936 if not (self.cursorInRestZone(globalPos) or newInButton): 937 return; 938 939 # If cursor moved while we are constraining motion 940 # vertically or horizontally, enforce that constraint now: 941 if self.currentMouseDirection is not None: 942 if self.currentMouseDirection == Direction.HORIZONTAL: 943 cursorMove = globalPosX - self.recentMousePos.x(); 944 correctedGlobalX = self.recentMousePos.x() + int(cursorMove * self.cursorAcceleration); 945 correctedCurPos = QPoint(correctedGlobalX, self.centralRestGlobalPos.y() + 12); 946 self.recentMousePos.setX(correctedGlobalX); 947 else: 948 cursorMove = globalPosY - self.recentMousePos.y(); 949 correctedGlobalY = self.recentMousePos.y() + int(cursorMove * self.cursorAcceleration); 950 correctedCurPos = QPoint(self.recentMousePos.x(), correctedGlobalPosY); 951 self.recentMousePos.setY(correctedGlobalPosY); 952 self.morseCursor.setPos(correctedCurPos); 953 return; 954 955 # Not currently constraining mouse move. To init, check which 956 # movement larger compared to the most recent position: x or y: 957 # Only constraining horizontally? 958 # Constraint is horizontal, even if there is any initial vertical movement. 959 if (not self.enableConstrainVertical) or abs(globalPosX - self.recentMousePos.x()) > abs(globalPosY - self.recentMousePos.y()): 960 self.currentMouseDirection = Direction.HORIZONTAL; 961 else: 962 self.currentMouseDirection = Direction.VERTICAL; 963 self.recentMousePos = mouseEvent.globalPos(); 964 finally: 965 # Set timer to unconstrain the mouse if it is 966 # not moved for a while (interval is set in __init__()). 967 # If we are not constraining horizontally and vertically 968 # then don't set the timeout: if self.enableConstrainVertical: 969 if self.enableConstrainVertical: 970 self.mouseUnconstrainTimer.setInterval(MorseInput.MOUSE_UNCONSTRAIN_TIMEOUT); 971 self.mouseUnconstrainTimer.start(); 972 return973975 # If user is hovering inside the dot or dash button, 976 # keep the hor/vert mouse move constraint going, even 977 # though the timeout of no mouse movement is done: 978 if self.dotButton.underMouse() or self.dashButton.underMouse(): 979 self.mouseUnconstrainTimer.start(); 980 return 981 self.currentMouseDirection = None; 982 self.recentMousePos = None;983 984 @staticmethod986 ''' 987 Called from MorseGenerator when one letter has 988 become available, or when a dwell end-of-letter, 989 or dwell end-of-word was detected. Sends a signal 990 and returns right away. 991 @param reason: indicator whether regular letter, or end of word. 992 @type reason: TimeoutReason 993 ''' 994 MorseInputSignals.getSignal('MorseInputSignals.letterDone').emit(reason, details);995 996 @QtCore.Slot(int,str)998 alpha = self.morseGenerator.getAndRemoveAlphaStr() 999 if reason == TimeoutReason.END_OF_WORD: 1000 alpha += ' '; 1001 self.outputLetters(alpha); 1002 # Give very brief indication that word boundary detected: 1003 self.flashCrosshair(crossHairColor=Crosshairs.YELLOW); 1004 elif reason == TimeoutReason.END_OF_LETTER: 1005 self.outputLetters(alpha); 1006 elif reason == TimeoutReason.BAD_MORSE_INPUT: 1007 self.statusBar.showMessage("Bad Morse input: '%s'" % detail, 4000); # milliseconds10081010 if self.outputDevice == OutputType.TYPE: 1011 for letter in lettersToSend: 1012 # Write to the local ticker tape: 1013 self.tickerTapeAppend(letter); 1014 # Then write to the X11 window in focus: 1015 if letter == '\b': 1016 self.outputBackspace(); 1017 elif letter == '\r': 1018 self.outputNewline(); 1019 else: 1020 #print(letter); 1021 self.virtKeyboard.typeTextToActiveWindow(letter); 1022 elif self.outputDevice == OutputType.SPEAK: 1023 print("Speech not yet implemented.");10241026 self.virtKeyboard.typeControlCharToActiveWindow('BackSpace'); 1027 # Also output to the ticker tape if appropriate: 1028 self.tickerTapeAppend('\b');10291031 self.virtKeyboard.typeControlCharToActiveWindow('Linefeed'); 1032 # Also output to the ticker tape if appropriate: 1033 self.tickerTapeAppend('\r');1034 10371039 self.tickerTapeSet('');10401042 if not self.useTickerTape: 1043 return; 1044 if text == '\b': 1045 # The backspace() method on QLineEdit doesn't work. 1046 # Maybe because we disallow focus on the widget to 1047 # avoid people thinking they can edit. So work 1048 # around this limitation: 1049 #self.tickerTapeLineEdit.backspace(); 1050 tickerContent = self.tickerTapeLineEdit.text(); 1051 if len(tickerContent) == 0: 1052 return; 1053 self.tickerTapeSet(tickerContent[:-1]); 1054 elif text == '\r': 1055 self.tickerTapeSet(self.tickerTapeLineEdit.text() + '\\n'); 1056 else: 1057 self.tickerTapeSet(self.tickerTapeLineEdit.text() + text);10581060 if crossHairColor == Crosshairs.CLEAR: 1061 self.crosshairLabel.setPixmap(self.crosshairPixmapClear); 1062 elif crossHairColor == Crosshairs.GREEN: 1063 self.crosshairLabel.setPixmap(self.crosshairPixmapGreen); 1064 elif crossHairColor == Crosshairs.YELLOW: 1065 self.crosshairLabel.setPixmap(self.crosshairPixmapYellow); 1066 elif crossHairColor == Crosshairs.RED: 1067 self.crosshairLabel.setPixmap(self.crosshairPixmapRed); 1068 else: 1069 raise ValueError("Crosshairs are available in clear, green, yellow, and red.") 1070 self.crosshairLabel.setVisible(True);1071 10741076 if self.flashTimer is not None: 1077 return; 1078 self.flashTimer = QTimer(self); 1079 self.flashTimer.setSingleShot(True); 1080 self.flashTimer.setInterval(250); # msecs 1081 self.showCrossHair(crossHairColor); 1082 self.flashTimer.timeout.connect(partial(self.restoreCrosshair, Crosshairs.CLEAR)); 1083 self.flashTimer.start();10841086 ''' 1087 Show the crosshair with the given color. Stop the flash timer. 1088 This is a timeout method. Used by flashCrosshair(). 1089 @param crossHairColor: 1090 @type crossHairColor: 1091 ''' 1092 self.flashTimer.stop(); 1093 self.flashTimer = None; 1094 self.showCrossHair(crossHairColor);10951097 if doBlink: 1098 # If timer already going, don't start a second one: 1099 if self.blinkTimer is not None: 1100 return; 1101 self.blinkTimer = QTimer(self); 1102 self.blinkTimer.setSingleShot(False); 1103 self.blinkTimer.setInterval(500); # msecs 1104 self.crosshairBlinkerOn = True; 1105 self.blinkTimer.timeout.connect(partial(self.toggleBlink, crossHairColor)); 1106 self.blinkTimer.start(); 1107 else: 1108 try: 1109 self.blinkTimer.stop(); 1110 except: 1111 pass 1112 self.blinkTimer = None; 1113 self.showCrossHair(crossHairColor);11141116 ''' 1117 If alternates between crosshair on and off. 1118 This is a timeout method. Used by blinkCrosshair() 1119 @param crossHairColor: 1120 @type crossHairColor: 1121 ''' 1122 if self.crosshairBlinkerOn: 1123 self.hideCrossHair(); 1124 self.crosshairBlinkerOn = False; 1125 else: 1126 self.showCrossHair(crossHairColor); 1127 self.crosshairBlinkerOn = True;11281130 if self.poweredUp: 1131 self.dotButton.setEnabled(False); 1132 self.dashButton.setEnabled(False); 1133 self.eowButton.setEnabled(False); 1134 self.backspaceButton.setEnabled(False); 1135 self.poweredUp = False; 1136 else: 1137 self.dotButton.setEnabled(True); 1138 self.dashButton.setEnabled(True); 1139 self.eowButton.setEnabled(True); 1140 self.backspaceButton.setEnabled(True); 1141 self.poweredUp = True;1142 1146 11521154 try: 1155 self.morserOptionsDialog.close(); 1156 self.morseGenerator.stopMorseGenerator(); 1157 except: 1158 # Best effort: 1159 pass
Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Thu Feb 21 11:48:07 2013 | http://epydoc.sourceforge.net |