Home | Trees | Indices | Help |
---|
|
1 import sys 2 import os 3 4 import rospy 5 6 from functools import partial; 7 from threading import Timer; 8 9 from python_qt_binding.QtGui import QTextEdit, QErrorMessage, QMainWindow, QColor, QPushButton, QVBoxLayout, QHBoxLayout, QGridLayout, QDialog, QLabel 10 from python_qt_binding.QtGui import QButtonGroup, QRadioButton, QFrame, QInputDialog, QDoubleSpinBox, QMessageBox 11 from python_qt_binding.QtCore import pyqtSignal 12 13 14 #TODO: Play repeatedly. 15 #TODO: From speakeasy_node's capabilities message, find all voices and their tts engines. Represent them in the radio buttons. 16 17 NUM_SPEECH_PROGRAM_BUTTONS = 24; 22 26 33 39 43 47 48 # Whether the initial UI is started with the 49 # Robot as play destination, or with Local as 50 # destination. This module variable may be 51 # used by clients to prepare the environment 52 # ahead of building the GUI (e.g. initialize 53 # a ROS node, or local sound engine.): 54 55 DEFAULT_PLAY_LOCATION = PlayLocation.ROBOT;56 57 58 #-------------------------------- TextPanel Class --------------------------- 59 -class TextPanel(QTextEdit):60 ''' 61 Text input field whose number of lines may be specified approximately. 62 ''' 63 64 # Class whose instances implements a sizeable, editable text input field. 65 66 #---------------------------------- 67 # Initializer 68 #-------------- 699871 super(TextPanel, self).__init__(None); 72 73 if numLines is not None: 74 # Want the text area to be numLines high. 75 # Get the app's font, get its height, and use it 76 # to specify the text field's maximum height: 77 currentFontHeight = self.fontMetrics().height(); 78 # add 5 pixels for each line: 79 self.setMinimumHeight((currentFontHeight * numLines) + (5 * numLines)); 80 self.setMinimumWidth(600); #px81 82 #---------------------------------- 83 # getText 84 #-------------- 8587 # Get text field text as plain text, and convert 88 # unicode to ascii, ignoring errors: 89 #return self.toPlainText().encode('ascii', 'ignore'); 90 return self.toPlainText().encode('utf-8');91 92 #---------------------------------- 93 # isEmpty 94 #-------------- 9599 # ----------------------------------------------- Class CommChannel ------------------------------------ 100 101 102 #class CommChannel(QObject): 103 # ''' 104 # Combines signals into one place. 105 # ''' 106 # hideButtonSignal = Signal(QPushButton); # hide the given button 107 # showButtonSignal = Signal(QPushButton); # show the hidden button 108 # 109 # def __init__(self): 110 # super(CommChannel, self).__init__(); 111 112 # ----------------------------------------------- Class DialogService ------------------------------------ 113 114 -class DialogService(object):115 120 121 122 #---------------------------------- 123 # Initializer 124 #-------------- 125189127 128 # All-purpose error popup message: 129 # Used by self.showErrorMsgByErrorCode(<errorCode>), 130 # or self.showErrorMsg(<string>). Returns a 131 # QErrorMessage without parent, but with QWindowFlags set 132 # properly to be a dialog popup box: 133 self.errorMsgPopup = QErrorMessage.qtHandler(); 134 # Re-parent the popup, retaining the window flags set 135 # by the qtHandler: 136 self.errorMsgPopup.setParent(parent, self.errorMsgPopup.windowFlags()); 137 self.errorMsgPopup.setStyleSheet(SpeakEasyGUI.stylesheetAppBG); 138 139 self.infoMsg = QMessageBox(parent=parent); 140 self.infoMsg.setStyleSheet(SpeakEasyGUI.stylesheetAppBG);141 142 #---------------------------------- 143 # showErrorMsg 144 #-------------- 145 QErrorMessage147 ''' 148 Given a string, pop up an error dialog. 149 @param errMsg: The message 150 @type errMsg: string 151 ''' 152 self.errorMsgPopup.showMessage(errMsg);153 154 #---------------------------------- 155 # showInfoMsg 156 #-------------- 157 161 162 #---------------------------------- 163 # newButtonSetOrUpdateCurrent 164 #-------------- 165167 ''' 168 Asks user whether saving of button set is to be 169 to a new file, or an update to the current file. 170 Cancel is offered as well. 171 @return: ButtonSaveResult.NEW_SET, ButtonSaveResult.UPDATE_CURRENT, or ButtonSaveResult.CANCEL 172 @rtype: DialogService.ButtonSaveResult 173 ''' 174 msgBox = QMessageBox() 175 msgBox.setStyleSheet(SpeakEasyGUI.stylesheetAppBG); 176 msgBox.setText('Create new button set, or update current set?') 177 178 updateCurrButton = QPushButton('Update current') 179 msgBox.addButton(updateCurrButton, QMessageBox.NoRole) 180 181 newSetButton = QPushButton('Create new set') 182 msgBox.addButton(newSetButton, QMessageBox.YesRole) 183 184 cancelButton = QPushButton('Cancel'); 185 msgBox.addButton(cancelButton, QMessageBox.RejectRole) 186 187 value = msgBox.exec_(); 188 return value;190 191 192 #---------------------------------------------------- SpeakEasyGUI Class --------------------------- 193 194 -class SpeakEasyGUI(QMainWindow):195 ''' 196 One instance of this class builds the entire sound play UI. 197 Instance variable that hold widgets of interest to controllers: 198 199 - C{speechInputFld} 200 - C{onceOrRepeatDict} 201 - C{voicesRadioButtonsDict} 202 - C{recorderDict} 203 - C{programButtonDict} 204 - C{soundButtonDict} 205 206 ''' 207 208 # ---------------------- Durations -------------------- 209 210 # Number of seconds a program button must be pressed to 211 # go into program mode: 212 PROGRAM_BUTTON_HOLD_TIME = 3.0; 213 214 # Amount of time the program button stays in alternate look 215 # to indicate that it is changing to programming mode: 216 217 PROGRAM_BUTTON_LOOK_CHANGE_DURATION = 0.2; # seconds 218 219 # ---------------------- Button and Font Sizes -------------------- 220 221 # Minimum height of buttons: 222 BUTTON_MIN_HEIGHT = 30; # pixels 223 # Pushbutton minimum font size: 224 BUTTON_LABEL_FONT_SIZE = 16; # pixels 225 226 # Radio button minimum font size: 227 RADIO_BUTTON_LABEL_FONT_SIZE = 16; # pixels 228 229 EDIT_FIELD_TEXT_SIZE = 18; # pixels 230 231 NUM_OF_PROGRAM_BUTTON_COLUMNS = 4; 232 NUM_OF_SOUND_BUTTON_COLUMNS = 4; 233 234 # ---------------------- Names for Voices ---------------------- 235 236 # Official names of voices as recognized by the 237 # underlying text-to-speech engines: 238 voices = {'VOICE_1': 'voice_kal_diphone', # Festival voice 239 'VOICE_2': 'David', # Cepstral voices 240 'VOICE_3': 'Amy', 241 'VOICE_4': 'Shouty', 242 'VOICE_5': 'Whispery', 243 'VOICE_6': 'Lawrence', 244 'VOICE_7': 'William' 245 }; 246 247 # ---------------------- Names for Widgets ---------------------- 248 249 interactionWidgets = { 250 'PLAY_ONCE': 'Play once', 251 'PLAY_REPEATEDLY': 'Play repeatedly', 252 'PLAY_REPEATEDLY_PERIOD': 'Pause between plays', 253 'VOICE_1': 'Machine', 254 'VOICE_2': 'David', 255 'VOICE_3': 'Amy', 256 'VOICE_4': 'Shout', 257 'VOICE_5': 'Whisper', 258 'VOICE_6': 'Lawrence', 259 'VOICE_7': 'William', 260 'PLAY_TEXT': 'Play Text', 261 'STOP': 'Stop', 262 'STOP_ALL' : 'Stop All', 263 264 'SPEECH_1' : 'Speech 1', 265 'SPEECH_2' : 'Speech 2', 266 'SPEECH_3' : 'Speech 3', 267 'SPEECH_4' : 'Speech 4', 268 'SPEECH_5' : 'Speech 5', 269 'SPEECH_6' : 'Speech 6', 270 'SPEECH_7' : 'Speech 7', 271 'SPEECH_8' : 'Speech 8', 272 'SPEECH_9' : 'Speech 9', 273 'SPEECH_10' : 'Speech 10', 274 'SPEECH_11' : 'Speech 11', 275 'SPEECH_12' : 'Speech 12', 276 'SPEECH_13' : 'Speech 13', 277 'SPEECH_14' : 'Speech 14', 278 'SPEECH_15' : 'Speech 15', 279 'SPEECH_16' : 'Speech 16', 280 'SPEECH_17' : 'Speech 17', 281 'SPEECH_18' : 'Speech 18', 282 'SPEECH_19' : 'Speech 19', 283 'SPEECH_20' : 'Speech 20', 284 'SPEECH_21' : 'Speech 21', 285 'SPEECH_22' : 'Speech 22', 286 'SPEECH_23' : 'Speech 23', 287 'SPEECH_24' : 'Speech 24', 288 289 'SOUND_1' : 'Rooster', 290 'SOUND_2' : 'Drill', 291 'SOUND_3' : 'Bull call', 292 'SOUND_4' : 'Clown horn', 293 'SOUND_5' : 'Cash register', 294 'SOUND_6' : 'Glass breaking', 295 'SOUND_7' : 'Cell door', 296 'SOUND_8' : 'Cow', 297 'SOUND_9' : 'Birds', 298 'SOUND_10' : 'Big truck', 299 'SOUND_11' : 'Seagulls', 300 'SOUND_12' : 'Lift', 301 302 'NEW_SPEECH_SET' : 'Save speech set', 303 'PICK_SPEECH_SET' : 'Pick different speech set', 304 305 'PLAY_LOCALLY' : 'Play locally', 306 'PLAY_AT_ROBOT' : 'Play at robot', 307 'PASTE' : 'Paste', 308 'CLEAR' : 'Clear', 309 } 310 311 # ---------------------- Application Background styling -------------------- 312 313 # Application background: 314 veryLightBlue = QColor(230, 255,255); 315 stylesheetAppBG = 'QDialog {background-color: %s}' % veryLightBlue.name(); 316 317 defaultStylesheet = stylesheetAppBG; 318 defaultStylesheetName = "Default"; 319 320 # ---------------------- Edit field styling ----------------- 321 322 editFieldBGColor = QColor(12,21,109); 323 editFieldTextColor = QColor(244,244,246); 324 325 inputFldStylesheet =\ 326 'TextPanel {background-color: ' + editFieldBGColor.name() +\ 327 '; color: ' + editFieldTextColor.name() +\ 328 '; font-size: ' + str(EDIT_FIELD_TEXT_SIZE) + 'pt' +\ 329 '}'; 330 331 # ---------------------- Pushbutton styling -------------------- 332 333 # Button color definitions: 334 recorderButtonBGColor = QColor(176,220,245); # Light blue 335 recorderButtonDisabledBGColor = QColor(187,200,208); # Gray-blue 336 recorderButtonTextColor = QColor(0,0,0); # Black 337 programButtonBGColor = QColor(117,150,169); # Middle blue 338 programButtonTextColor = QColor(251,247,247);# Off-white 339 soundButtonBGColor = QColor(110,134,211); # Dark blue 340 soundButtonTextColor = QColor(251,247,247); # Off-white 341 342 # Button stylesheets: 343 recorderButtonStylesheet =\ 344 'QPushButton {background-color: ' + recorderButtonBGColor.name() +\ 345 '; color: ' + recorderButtonTextColor.name() +\ 346 '; font-size: ' + str(BUTTON_LABEL_FONT_SIZE) + 'px' +\ 347 '}'; 348 349 recorderButtonDisabledStylesheet =\ 350 'QPushButton {background-color: ' + recorderButtonDisabledBGColor.name() +\ 351 '; color: ' + recorderButtonTextColor.name() +\ 352 '; font-size: ' + str(BUTTON_LABEL_FONT_SIZE) + 'px' +\ 353 '}'; 354 355 programButtonStylesheet =\ 356 'QPushButton {background-color: ' + programButtonBGColor.name() +\ 357 '; color: ' + programButtonTextColor.name() +\ 358 '; font-size: ' + str(BUTTON_LABEL_FONT_SIZE) + 'px' +\ 359 '}'; 360 361 # Stylesheet for when program button look is temporarily 362 # changed to indicate transition to programming mode: 363 programButtonModeTransitionStylesheet =\ 364 'QPushButton {background-color: ' + editFieldBGColor.name() +\ 365 '; color: ' + programButtonTextColor.name() +\ 366 '; font-size: ' + str(BUTTON_LABEL_FONT_SIZE) + 'px' +\ 367 '}'; 368 369 370 soundButtonStylesheet =\ 371 'QPushButton {background-color: ' + soundButtonBGColor.name() +\ 372 '; color: ' + soundButtonTextColor.name() +\ 373 '; font-size: ' + str(BUTTON_LABEL_FONT_SIZE) + 'px' +\ 374 '}'; 375 376 # ---------------------- Radiobutton and Play Repeat Delay Spinbox styling ----------------- 377 378 # Radiobutton color definitions: 379 playOnceRepeatButtonBGColor = QColor(121,229,230); # Very light cyan 380 voicesButtonBGColor = QColor(97,164,165); # Darker cyan 381 382 383 playOnceRepeatButtonStylesheet =\ 384 'font-size: ' + str(RADIO_BUTTON_LABEL_FONT_SIZE) + 'px' +\ 385 '; color: ' + soundButtonTextColor.name() +\ 386 '; background-color: ' + voicesButtonBGColor.name(); 387 388 playRepeatSpinboxStylesheet =\ 389 'font-size: ' + str(RADIO_BUTTON_LABEL_FONT_SIZE) + 'px' +\ 390 '; color: ' + soundButtonTextColor.name() +\ 391 '; background-color: ' + voicesButtonBGColor.name(); 392 393 # voiceButtonStylesheet =\ 394 # 'font-size: ' + str(RADIO_BUTTON_LABEL_FONT_SIZE) + 'px' +\ 395 # '; background-color: ' + voicesButtonBGColor.name(); 396 397 voiceButtonStylesheet =\ 398 'font-size: ' + str(RADIO_BUTTON_LABEL_FONT_SIZE) + 'px' +\ 399 '; color: ' + soundButtonTextColor.name() +\ 400 '; background-color: ' + voicesButtonBGColor.name(); 401 402 # ---------------------- Signals ----------------- 403 404 hideButtonSignal = pyqtSignal(QPushButton); # hide the given button 405 showButtonSignal = pyqtSignal(QPushButton); # show the hidden button 406 407 #---------------------------------- 408 # Initializer 409 #-------------- 4101067412 413 super(SpeakEasyGUI, self).__init__(parent); 414 415 self.stand_alone = stand_alone; 416 self.sound_effect_labels = sound_effect_labels; 417 if (sound_effect_labels is None): 418 raise ValueError("Must pass in non-null array of sound effect button labels."); 419 420 #self.setMaximumWidth(1360); 421 #self.setMaximumHeight(760); 422 423 # Vertical box to hold all top level widget groups 424 appLayout = QVBoxLayout(); 425 appWidget = QDialog(); 426 appWidget.setStyleSheet(SpeakEasyGUI.defaultStylesheet); 427 # Activate the window resize handle in the lower right corner 428 # of the app window: 429 appWidget.setSizeGripEnabled(True); 430 431 self.setCentralWidget(appWidget); 432 433 self.addTitle(appLayout); 434 self.addTxtInputFld(appLayout); 435 self.buildTapeRecorderButtons(appLayout); 436 self.addOnceOrRepeat_And_VoiceRadioButtons(appLayout); 437 438 self.buildHorizontalDivider(appLayout); 439 self.buildProgramButtons(appLayout); 440 self.buildSoundButtons(appLayout); 441 self.buildButtonSetControls(appLayout); 442 self.buildOptionsRadioButtons(appLayout); 443 444 appWidget.setLayout(appLayout); 445 446 self.show();447 448 #---------------------------------- 449 # programButtonIterator 450 #-------------- 451453 454 if gridLayout is None: 455 gridLayout = self.programButtonGridLayout; 456 457 programButtonArray = []; 458 # Collect the buttons into a flat array: 459 for row in range(gridLayout.rowCount()): 460 for col in range(SpeakEasyGUI.NUM_OF_PROGRAM_BUTTON_COLUMNS): 461 layoutItem = gridLayout.itemAtPosition(row, col); 462 if layoutItem is not None: 463 programButtonArray.append(layoutItem.widget()); 464 return iter(programButtonArray);465 466 #---------------------------------- 467 # replaceProgramButtons 468 #-------------- 469471 472 programButtonObjIt = self.programButtonIterator(); 473 # Remove the existing program buttons from the application's 474 # layout, and mark them for deletion: 475 try: 476 while True: 477 programButtonObj = programButtonObjIt.next(); 478 self.programButtonGridLayout.removeWidget(programButtonObj); 479 programButtonObj.deleteLater(); 480 except StopIteration: 481 pass 482 483 # Make an array of button labels from the ButtonProgram 484 # instances in buttonProgramsArray: 485 buttonLabelArr = []; 486 for buttonProgram in buttonProgramsArray: 487 buttonLabelArr.append(buttonProgram.getLabel()); 488 489 # No more program buttons present. Make 490 # new ones, and add them to the grid layout: 491 (newProgramButtonGridLayout, self.programButtonDict) =\ 492 SpeakEasyGUI.buildButtonGrid(buttonLabelArr, 493 SpeakEasyGUI.NUM_OF_PROGRAM_BUTTON_COLUMNS); 494 for buttonObj in self.programButtonDict.values(): 495 buttonObj.setStyleSheet(SpeakEasyGUI.programButtonStylesheet); 496 buttonObj.setMinimumHeight(SpeakEasyGUI.BUTTON_MIN_HEIGHT); 497 498 # Transfer the buttons from this new gridlayout object to the 499 # original layout that is embedded in the application Dialog: 500 newButtonsIt = self.programButtonIterator(gridLayout=newProgramButtonGridLayout); 501 try: 502 while True: 503 self.programButtonGridLayout.addWidget(newButtonsIt.next()); 504 except StopIteration: 505 pass;506 507 #---------------------------------- 508 # connectSignalsToWidgets 509 #-------------- 510 511 # def connectSignalsToWidgets(self): 512 # self.commChannel = CommChannel(); 513 # self.commChannel.hideButtonSignal.connect(SpeakEasyGUI.hideButtonHandler); 514 # self.commChannel.showButtonSignal.connect(SpeakEasyGUI.showButtonHandler); 515 516 517 #---------------------------------- 518 # addTitle 519 #-------------- 520522 title = QLabel("<H1>SpeakEasy</H1>"); 523 hbox = QHBoxLayout(); 524 hbox.addStretch(1); 525 hbox.addWidget(title); 526 hbox.addStretch(1); 527 528 layout.addLayout(hbox);529 530 531 #---------------------------------- 532 # addTxtInputFld 533 #-------------- 534536 ''' 537 Creates text input field label and text field 538 in a horizontal box layout. Adds that hbox layout 539 to the passed-in layout. 540 541 Sets instance variables: 542 1. C{self.speechInputFld} 543 544 @param layout: Layout object to which the label/txt-field C{hbox} is to be added. 545 @type layout: QLayout 546 ''' 547 speechControlsLayout = QHBoxLayout(); 548 speechControlsLayout.addStretch(1); 549 speechInputFldLabel = QLabel("<b>What to say:</b>") 550 speechControlsLayout.addWidget(speechInputFldLabel); 551 552 self.speechInputFld = TextPanel(numLines=5); 553 self.speechInputFld.setStyleSheet(SpeakEasyGUI.inputFldStylesheet); 554 self.speechInputFld.setFontPointSize(SpeakEasyGUI.EDIT_FIELD_TEXT_SIZE); 555 556 speechControlsLayout.addWidget(self.speechInputFld); 557 558 layout.addLayout(speechControlsLayout);559 560 #---------------------------------- 561 # addVoiceRadioButtons 562 #-------------- 563565 ''' 566 Creates radio buttons for selecting whether a 567 sound is to play once, or repeatedly until stopped. 568 Also adds radio buttons for selecting voices. 569 Places all in a horizontal box layout. Adds 570 that hbox layout to the passed-in layout. 571 572 Sets instance variables: 573 1. C{self.onceOrRepeatDict} 574 2. C{self.voicesRadioButtonsDict} 575 576 @param layout: Layout object to which the label/txt-field C{hbox} is to be added. 577 @type layout: QLayout 578 ''' 579 580 hbox = QHBoxLayout(); 581 582 (self.onceOrRepeatGroup, onceOrRepeatButtonLayout, self.onceOrRepeatDict) =\ 583 self.buildRadioButtons([SpeakEasyGUI.interactionWidgets['PLAY_ONCE'], 584 SpeakEasyGUI.interactionWidgets['PLAY_REPEATEDLY'] 585 ], 586 Orientation.HORIZONTAL, 587 Alignment.LEFT, 588 activeButtons=[SpeakEasyGUI.interactionWidgets['PLAY_ONCE']], 589 behavior=CheckboxGroupBehavior.RADIO_BUTTONS); 590 591 self.replayPeriodSpinBox = QDoubleSpinBox(self); 592 self.replayPeriodSpinBox.setRange(0.0, 99.9); # seconds 593 self.replayPeriodSpinBox.setSingleStep(0.5); 594 self.replayPeriodSpinBox.setDecimals(1); 595 onceOrRepeatButtonLayout.addWidget(self.replayPeriodSpinBox); 596 secondsLabel = QLabel("secs delay"); 597 onceOrRepeatButtonLayout.addWidget(secondsLabel); 598 599 # Create an array of voice radio button labels: 600 voiceRadioButtonLabels = []; 601 for voiceKey in SpeakEasyGUI.voices.keys(): 602 voiceRadioButtonLabels.append(SpeakEasyGUI.interactionWidgets[voiceKey]); 603 604 (self.voicesGroup, voicesButtonLayout, self.voicesRadioButtonsDict) =\ 605 self.buildRadioButtons(voiceRadioButtonLabels, 606 Orientation.HORIZONTAL, 607 Alignment.RIGHT, 608 activeButtons=[SpeakEasyGUI.interactionWidgets['VOICE_1']], 609 behavior=CheckboxGroupBehavior.RADIO_BUTTONS); 610 611 # Style all the radio buttons: 612 for playFreqButton in self.onceOrRepeatDict.values(): 613 playFreqButton.setStyleSheet(SpeakEasyGUI.playOnceRepeatButtonStylesheet); 614 for playFreqButton in self.voicesRadioButtonsDict.values(): 615 playFreqButton.setStyleSheet(SpeakEasyGUI.voiceButtonStylesheet); 616 #...and the replay delay spinbox: 617 self.replayPeriodSpinBox.setStyleSheet(SpeakEasyGUI.playRepeatSpinboxStylesheet); 618 #****** replayPeriodSpinBox styling 619 620 hbox.addLayout(onceOrRepeatButtonLayout); 621 hbox.addStretch(1); 622 hbox.addLayout(voicesButtonLayout); 623 layout.addLayout(hbox);624 625 #---------------------------------- 626 # buildTapeRecorderButtons 627 #-------------- 628630 ''' 631 Creates tape recorder buttons (Play Text, Stop,etc.). 632 Places all in a row, though the layout is a 633 QGridLayout. Adds QGridLayout to the passed-in 634 layout. 635 636 Sets instance variables: 637 1. C{self.recorderDict} 638 639 @param layout: Layout object to which the label/txt-field C{QGridlayout} is to be added. 640 @type layout: QLayout 641 ''' 642 643 (buttonGridLayout, self.recorderButtonDict) =\ 644 SpeakEasyGUI.buildButtonGrid([SpeakEasyGUI.interactionWidgets['PLAY_TEXT'], 645 SpeakEasyGUI.interactionWidgets['STOP'], 646 #SpeakEasyGUI.interactionWidgets['STOP_ALL'] # Stop button already stops all. 647 ], 648 2); # Two columns 649 for buttonObj in self.recorderButtonDict.values(): 650 buttonObj.setStyleSheet(SpeakEasyGUI.recorderButtonStylesheet); 651 buttonObj.setMinimumHeight(SpeakEasyGUI.BUTTON_MIN_HEIGHT); 652 layout.addLayout(buttonGridLayout);653 654 #---------------------------------- 655 # buildProgramButtons 656 #-------------- 657659 ''' 660 Creates grid of buttons for saving sounds. 661 Adds the resulting QGridLayout to the passed-in 662 layout. 663 664 Sets instance variables: 665 1. C{self.programButtonDict} 666 667 @param layout: Layout object to which the label/txt-field C{QGridlayout} is to be added. 668 @type layout: QLayout 669 ''' 670 671 buttonLabelArr = []; 672 for i in range(NUM_SPEECH_PROGRAM_BUTTONS): 673 key = "SPEECH_" + str(i+1); 674 buttonLabelArr.append(SpeakEasyGUI.interactionWidgets[key]); 675 676 (self.programButtonGridLayout, self.programButtonDict) =\ 677 SpeakEasyGUI.buildButtonGrid(buttonLabelArr, 678 SpeakEasyGUI.NUM_OF_PROGRAM_BUTTON_COLUMNS); 679 for buttonObj in self.programButtonDict.values(): 680 buttonObj.setStyleSheet(SpeakEasyGUI.programButtonStylesheet); 681 buttonObj.setMinimumHeight(SpeakEasyGUI.BUTTON_MIN_HEIGHT); 682 683 layout.addLayout(self.programButtonGridLayout);684 685 #---------------------------------- 686 # buildSoundButtons 687 #-------------- 688690 ''' 691 Creates grid of buttons for playing canned sounds. 692 Adds the resulting QGridLayout to the passed-in 693 layout. 694 695 Sets instance variables: 696 1. C{self.soundButtonDict} 697 698 @param layout: Layout object to which the label/txt-field C{QGridlayout} is to be added. 699 @type layout: QLayout 700 ''' 701 702 for i in range(len(self.sound_effect_labels)): 703 key = "SOUND_" + str(i); 704 SpeakEasyGUI.interactionWidgets[key] = self.sound_effect_labels[i]; 705 706 (buttonGridLayout, self.soundButtonDict) =\ 707 SpeakEasyGUI.buildButtonGrid(self.sound_effect_labels, SpeakEasyGUI.NUM_OF_SOUND_BUTTON_COLUMNS); 708 for buttonObj in self.soundButtonDict.values(): 709 buttonObj.setStyleSheet(SpeakEasyGUI.soundButtonStylesheet); 710 buttonObj.setMinimumHeight(SpeakEasyGUI.BUTTON_MIN_HEIGHT); 711 712 layout.addLayout(buttonGridLayout);713 714 #---------------------------------- 715 # buildButtonSetControls 716 #-------------- 717719 720 buttonLabelArray = [self.interactionWidgets['NEW_SPEECH_SET'], self.interactionWidgets['PICK_SPEECH_SET']]; 721 # Two columns of buttons: 722 (buttonGridLayout, self.speechSetButtonDict) = SpeakEasyGUI.buildButtonGrid(buttonLabelArray, 2); 723 for buttonObj in self.speechSetButtonDict.values(): 724 buttonObj.setStyleSheet(SpeakEasyGUI.programButtonStylesheet); 725 buttonObj.setMinimumHeight(SpeakEasyGUI.BUTTON_MIN_HEIGHT); 726 layout.addLayout(buttonGridLayout);727 728 #---------------------------------- 729 # buildOptionsRadioButtons 730 #-------------- 731733 hbox = QHBoxLayout(); 734 (self.playLocalityGroup, playLocalityButtonLayout, self.playLocalityRadioButtonsDict) =\ 735 self.buildRadioButtons([SpeakEasyGUI.interactionWidgets['PLAY_LOCALLY'], 736 SpeakEasyGUI.interactionWidgets['PLAY_AT_ROBOT'] 737 ], 738 Orientation.HORIZONTAL, 739 Alignment.LEFT, 740 activeButtons=[SpeakEasyGUI.interactionWidgets[DEFAULT_PLAY_LOCATION]], 741 behavior=CheckboxGroupBehavior.RADIO_BUTTONS); 742 #behavior=CheckboxGroupBehavior.CHECKBOXES); 743 744 # Style all the radio buttons: 745 for playLocalityButton in self.playLocalityRadioButtonsDict.values(): 746 playLocalityButton.setStyleSheet(SpeakEasyGUI.voiceButtonStylesheet); 747 hbox.addLayout(playLocalityButtonLayout); 748 hbox.addStretch(1); 749 self.buildConvenienceButtons(hbox); 750 layout.addLayout(hbox);751 752 #---------------------------------- 753 # buildConvenienceButtons 754 #-------------- 755757 ''' 758 Creates buttons meant for accessibility convenience. 759 Example: Paste. 760 Places all in a row, though the layout is a 761 QGridLayout. Adds QGridLayout to the passed-in 762 layout. 763 764 Sets instance variables: 765 1. C{self.convenienceButtonDict} 766 767 @param layout: Layout object to which the label/txt-field C{QGridlayout} is to be added. 768 @type layout: QLayout 769 ''' 770 771 (buttonGridLayout, self.convenienceButtonDict) =\ 772 SpeakEasyGUI.buildButtonGrid([SpeakEasyGUI.interactionWidgets['PASTE'], 773 SpeakEasyGUI.interactionWidgets['CLEAR'], 774 ], 775 2); # Two columns 776 for buttonObj in self.convenienceButtonDict.values(): 777 buttonObj.setStyleSheet(SpeakEasyGUI.recorderButtonStylesheet); 778 buttonObj.setMinimumHeight(SpeakEasyGUI.BUTTON_MIN_HEIGHT); 779 layout.addLayout(buttonGridLayout);780 781 #---------------------------------- 782 # buildHorizontalDivider 783 #-------------- 784786 frame = QFrame(); 787 frame.setFrameShape(QFrame.HLine); 788 #*******frame.setFrameStyle(QFrame.Shadow.Sunken.value()); 789 frame.setLineWidth(3); 790 frame.setMidLineWidth(3); 791 layout.addWidget(frame);792 793 794 #---------------------------------- 795 # buildRadioButtons 796 #-------------- 797 798 # Note: Whenever a button is switched on or off it emits the 799 # PySide.QtGui.QAbstractButton.toggled() signal. Connect to this 800 # signal if you want to trigger an action each time the button changes state. 801802 - def buildRadioButtons(self, 803 labelTextArray, 804 orientation, 805 alignment, 806 activeButtons=None, 807 behavior=CheckboxGroupBehavior.RADIO_BUTTONS):808 ''' 809 @param labelTextArray: Names of buttons 810 @type labelTextArray: [string] 811 @param orientation: Whether to arrange the buttons vertically or horizontally. 812 @type orientation: Orientation 813 @param alignment: whether buttons should be aligned Left/Center/Right for horizontal, 814 Top/Center/Bottom for vertical:/c 815 @type alignment: Alignment 816 @param activeButtons: Name of the buttons that is to be checked initially. Or None. 817 @type activeButtons: [string] 818 @param behavior: Indicates whether the button group is to behave like Radio Buttons, or like Checkboxes. 819 @type behavior: CheckboxGroupBehavior 820 @return 821 1. The button group that contains the related buttons. Caller: ensure that 822 this object does not go out of scope. 823 2. The button layout, which callers will need to add to 824 their own layouts. 825 3. and a dictionary mapping button names to button objects that 826 were created within this method. This dict is needed by the 827 controller. 828 @rtype (QButtonGroup, QLayout, dict<string,QRadioButton>). 829 ''' 830 831 # Button control management group. It has no visible 832 # representation. It allows the radio button bahavior, for instance: 833 buttonGroup = QButtonGroup(); 834 835 if behavior == CheckboxGroupBehavior.CHECKBOXES: 836 buttonGroup.setExclusive(False); 837 else: 838 buttonGroup.setExclusive(True); 839 840 if orientation == Orientation.VERTICAL: 841 buttonCompLayout = QVBoxLayout(); 842 # Arrange for the alignment (Part 1): 843 if (alignment == Alignment.CENTER) or\ 844 (alignment == Alignment.BOTTOM): 845 buttonCompLayout.addStretch(1); 846 else: 847 buttonCompLayout = QHBoxLayout(); 848 # Arrange for the alignment (Part 1): 849 if (alignment == Alignment.CENTER) or\ 850 (alignment == Alignment.RIGHT): 851 buttonCompLayout.addStretch(1); 852 853 # Build the buttons: 854 buttonDict = {}; 855 856 for label in labelTextArray: 857 button = QRadioButton(label); 858 buttonDict[label] = button; 859 # Add button to the button management group: 860 buttonGroup.addButton(button); 861 # Add the button to the visual group: 862 buttonCompLayout.addWidget(button); 863 if label in activeButtons: 864 button.setChecked(True); 865 866 if orientation == Orientation.VERTICAL: 867 buttonCompLayout = QVBoxLayout(); 868 # Arrange for the alignment (Part 2): 869 if (alignment == Alignment.CENTER) or\ 870 (alignment == Alignment.TOP): 871 buttonCompLayout.addStretch(1); 872 else: 873 # Arrange for the alignment (Part 2): 874 if (alignment == Alignment.CENTER) or\ 875 (alignment == Alignment.LEFT): 876 buttonCompLayout.addStretch(1); 877 878 return (buttonGroup, buttonCompLayout, buttonDict);879 880 #---------------------------------- 881 # buildButtonGrid 882 #-------------- 883 884 @staticmethod887 ''' 888 Creates a grid of QPushButton widgets. They will be 889 890 @param labelTextArray: Button labels. 891 @type labelTextArray: [string] 892 @param numColumns: The desired width of the button grid. 893 @type numColumns: int 894 @return: 1. a grid layout with the button objects inside. 895 2. a dictionary mapping button labels to button objects. 896 @rtype: QGridLayout 897 ''' 898 899 buttonLayout = QGridLayout(); 900 901 # Compute number of button rows: 902 numRows = len(labelTextArray) / numColumns; 903 # If this number of rows leaves a few buttons left over (< columnNum), 904 # then add a row for those: 905 if (len(labelTextArray) % numColumns) > 0: 906 numRows += 1; 907 908 buttonDict = {} 909 910 row = 0; 911 col = 0; 912 for buttonLabel in labelTextArray: 913 button = QPushButton(buttonLabel); 914 buttonDict[buttonLabel] = button; 915 buttonLayout.addWidget(button, row, col); 916 col += 1; 917 if (col > (numColumns - 1)): 918 col = 0; 919 row += 1; 920 921 return (buttonLayout, buttonDict);922 923 #---------------------------------- 924 # getNewButtonLabel 925 #-------------- 926928 ''' 929 Requests a new button label from the user. 930 Returns None if user canceled out, or a string 931 with the new button label. 932 @return: None if user canceled, else string from input field. 933 @rtype: {None | string} 934 935 ''' 936 prompt = "New label for this button:"; 937 dialogBox = QInputDialog(parent=self); 938 dialogBox.setStyleSheet(SpeakEasyGUI.stylesheetAppBG); 939 dialogBox.setLabelText(prompt); 940 dialogBox.setCancelButtonText("Keep existing label"); 941 userChoice = dialogBox.exec_(); 942 943 if userChoice == QDialog.Accepted: 944 return dialogBox.textValue(); 945 else: 946 return None;947 948 #---------------------------------- 949 # playOnceChecked 950 #-------------- 951 954 955 #---------------------------------- 956 # setPlayOnceChecked 957 #-------------- 958 961 962 #---------------------------------- 963 # playRepeatedlyChecked 964 #-------------- 965 968 969 #---------------------------------- 970 # setPlayRepeatedlyChecked 971 #-------------- 972 975 976 #---------------------------------- 977 # playRepeatPeriod 978 #-------------- 979 982 983 #---------------------------------- 984 # activeVoice 985 #-------------- 986988 ''' 989 Return the official name of the voice that is currently 990 checked in the UI. This is the name that will be recognized 991 by the underlying text-to-speech engine(s). 992 @return: Name of voice as per the SpeakEasyGUI.voices dict. 993 @rtype: string 994 ''' 995 for voiceKey in SpeakEasyGUI.voices.keys(): 996 if self.voicesRadioButtonsDict[SpeakEasyGUI.interactionWidgets[voiceKey]].isChecked(): 997 return SpeakEasyGUI.voices[voiceKey];998 999 #---------------------------------- 1000 # whereToPlay 1001 #-------------- 10021004 ''' 1005 Returns which of the play location options is selected: Locally or Robot. 1006 @return: Selection of where sound and text-to-speech output is to occur. 1007 @rtype: PlayLocation 1008 ''' 1009 if self.playLocalityRadioButtonsDict[SpeakEasyGUI.interactionWidgets['PLAY_LOCALLY']].isChecked(): 1010 return PlayLocation.LOCALLY; 1011 else: 1012 return PlayLocation.ROBOT;1013 1014 #---------------------------------- 1015 # setWhereToPlay 1016 #-------------- 10171019 ''' 1020 Set the option radio button that determines where sound is produced, 1021 locally, or at the robot. No action is taken. This method merely sets 1022 the appropriate radio button. 1023 @param playLocation: PlayLocation.LOCALLY, or PlayLocation.ROBOT 1024 @type playLocation: PlayLocation 1025 ''' 1026 1027 if playLocation == PlayLocation.LOCALLY: 1028 radioButton = self.playLocalityRadioButtonsDict[SpeakEasyGUI.interactionWidgets['PLAY_LOCALLY']]; 1029 else: 1030 radioButton = self.playLocalityRadioButtonsDict[SpeakEasyGUI.interactionWidgets['PLAY_AT_ROBOT']]; 1031 radioButton.setChecked(True);1032 1033 #---------------------------------- 1034 # setButtonLabel 1035 #-------------- 10361038 buttonObj.setText(label);1039 1040 #---------------------------------- 1041 # blinkButton 1042 #-------------- 10431045 ''' 1046 Used to make a program button blink in some way to indicate 1047 that it is changing into programming mode. Since this method 1048 is triggered by a timer thread, it cannot make any GUI changes. 1049 Instead, it sends a signal to have the GUI thread place the 1050 button into an alternative look. It then schedules a call 1051 to itself for a short time later. At that point it sends a 1052 signal to the GUI thread to return the button to its usual 1053 look: 1054 @param buttonObj: The button to be blinked 1055 @type buttonObj: QPushButton 1056 @param turnOn: Indicates whether to turn the button back into 1057 its normal state, or whether to make it take on 1058 its alternate look. 1059 @type turnOn: bool 1060 ''' 1061 if turnOn: 1062 self.showButtonSignal.emit(buttonObj); 1063 else: 1064 self.hideButtonSignal.emit(buttonObj); 1065 self.buttonBlinkTimer = Timer(SpeakEasyGUI.PROGRAM_BUTTON_LOOK_CHANGE_DURATION, partial(self.blinkButton, buttonObj, True)); 1066 self.buttonBlinkTimer.start();1068 # --------------------- Manage Sound Effect Buttons ------------------- 1069 1070 1071 -def alternateLookHandler(buttonObj):1072 #buttonObj.hide(); 1073 buttonObj.setStyleSheet(SpeakEasyGUI.programButtonModeTransitionStylesheet);1074 1078 1079 if __name__ == "__main__": 1080 1081 from PyQt4.QtGui import QStyleFactory, QApplication; 1082 1083 # Create a Qt application 1084 1085 # style = QStyleFactory.create("Plastique"); 1086 # style = QStyleFactory.create("Motif"); 1087 # style = QStyleFactory.create("GTK+"); 1088 # style = QStyleFactory.create("Windows"); 1089 1090 style = QStyleFactory.create("Cleanlooks"); 1091 QApplication.setStyle(style); 1092 app = QApplication(sys.argv); 1093 1094 soundPlayGui = SpeakEasyGUI(); 1095 soundPlayGui.hideButtonSignal.connect(alternateLookHandler); 1096 soundPlayGui.showButtonSignal.connect(standardLookHandler); 1097 1098 # Enter Qt application main loop 1099 sys.exit(app.exec_()); 1100
Home | Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Mon Nov 26 18:08:32 2012 | http://epydoc.sourceforge.net |