Esempio n. 1
0
class Quiz(QFrame):
     
    def __init__(self, options, parent=None):
        super(Quiz, self).__init__(parent)
        
        self.options = options
        
        """Session Info"""
        self.status = QFrame()
        #session message
        self.status.message = QLabel(u'')
        #achievements
        self.status.achievements = Achievements()
        self.status.info = QLabel(u'')
        self.status.progress = QProgressBar()
        self.status.layout = QVBoxLayout()
        #layout
        self.status.layout.addWidget(self.status.info)
        self.status.layout.addWidget(self.status.progress)
        self.status.layout.addWidget(self.status.message)
        self.status.setLayout(self.status.layout)
        #mouse event filter
        self.status.filter = StatusFilter(self.status)
        self.status.setAttribute(Qt.WA_Hover, True)
        self.status.installEventFilter(self.status.filter)

        """Items Info"""
        self.info = QFrame()
        self.info.reading = QLabel(u'')
        self.info.item = QLabel(u'')
        self.info.components = QLabel(u'')

        separator_one = QFrame()
        separator_one.setFrameShape(QFrame.HLine)
        separator_one.setFrameShadow(QFrame.Sunken)
        
        separator_two = QFrame()
        separator_two.setFrameShape(QFrame.HLine)
        separator_two.setFrameShadow(QFrame.Sunken)
        
        self.info.layout = QVBoxLayout()
        self.info.layout.addWidget(self.info.reading)
        self.info.layout.addWidget(separator_one)
        self.info.layout.addWidget(self.info.item)
        self.info.layout.addWidget(separator_two)
        self.info.layout.addWidget(self.info.components)
        self.info.setLayout(self.info.layout)
        
        """Verbose Info"""
        self.allInfo = QFrame()
        self.allInfo.layout = QGridLayout()
        self.allInfo.setLayout(self.allInfo.layout)
        #the rest is (should be) generated on the fly
        
        """Kanji info"""
        self.kanjiInfo = QFrame()
        self.kanjiInfo.layout = QVBoxLayout()
        self.kanjiInfo.info = QLabel(u'')
        self.kanjiInfo.layout.addWidget(self.kanjiInfo.info)
        self.kanjiInfo.setLayout(self.kanjiInfo.layout)
        
        """Kanji groups"""
        self.kanjiGroups = QFrame()
        self.kanjiGroups.layout = QVBoxLayout()
        self.kanjiGroups.info = QLabel(u'')
        self.kanjiGroups.layout.addWidget(self.kanjiGroups.info)
        self.kanjiGroups.setLayout(self.kanjiGroups.layout)
        
        """Global Flags"""
        #self.correct = False
        
        """Quiz Dialog"""
        self.filter = Filter()
        ####    visual components    ###
        self.countdown = QProgressBar()
        self.sentence = QLabel(u'')
        
        self.var_1st = QPushButton(u'')
        self.var_2nd = QPushButton(u'')
        self.var_3rd = QPushButton(u'')
        self.var_4th = QPushButton(u'')

        self.answered = QPushButton(u'')
        self.answered.hide()
                
        ###    layouts    ####
        self.layout_vertical = QVBoxLayout()        #main
        self.layout_horizontal = QHBoxLayout()      #buttons
        
        self.layout_horizontal.addWidget(self.var_1st)
        self.layout_horizontal.addWidget(self.var_2nd)
        self.layout_horizontal.addWidget(self.var_3rd)
        self.layout_horizontal.addWidget(self.var_4th)
        
        self.layout_vertical.addWidget(self.countdown)
        self.layout_vertical.addWidget(self.sentence)
        self.layout_vertical.addLayout(self.layout_horizontal)
        
        self.layout_horizontal.addWidget(self.answered)

        self.setLayout(self.layout_vertical)

        ###    utility components    ###
        self.trayIcon = QSystemTrayIcon(self)
        self.trayMenu = QMenu()
        
        self.gifLoading = QMovie('../res/cube.gif')
        self.gifLoading.frameChanged.connect(self.updateTrayIcon)
        
        ### initializing ###
        self.initializeResources()
        
        ### timers ###
        self.nextQuizTimer = QTimer()
        self.nextQuizTimer.setSingleShot(True)
        self.nextQuizTimer.timeout.connect(self.showQuiz)
        
        self.countdownTimer = QTimer()
        self.countdownTimer.setSingleShot(True)
        self.countdownTimer.timeout.connect(self.timeIsOut)
        
        self.trayUpdater = None
        #self.trayUpdater = RepeatTimer(1.0, self.updateTrayTooltip, self.options.getRepetitionInterval() * 60)
        self.remaining = 0
        
        """Start!"""
        if self.options.isQuizStartingAtLaunch():
            self.waitUntilNextTimeslot()
            self.trayIcon.setToolTip('Quiz has started automatically!')
            self.pauseAction.setText('&Pause')
            self.trayIcon.showMessage('Loading complete! (took ~'+ str(self.loadingTime.seconds) + ' seconds) Quiz underway.', 
                                      'Lo! Quiz already in progress!'  + self.loadingStatus, QSystemTrayIcon.MessageIcon.Warning, 10000)
        else:
            self.trayIcon.setToolTip('Quiz is not initiated!')
            self.trayIcon.showMessage('Loading complete! (took ~' + str(self.loadingTime.seconds) + ' seconds) Standing by.', 
                                      'Quiz has not started yet! If you wish, you could start it manually or enable autostart by default.'  + self.loadingStatus, 
                                      QSystemTrayIcon.MessageIcon.Information, 10000 )
            
        self.setWindowIcon(QIcon(PATH_TO_RES + ICONS + 'suzu.png'))
        """Test calls here:"""
        ###    ...    ###
        #self.connect(self.hooker, SIGNAL('noQdict'), self.noQdict)
        self.gem = self.saveGeometry()
        
    def startUpdatingTrayTooltip(self):
        self.remaining = self.nextQuizTimer.interval()
        
        if self.trayUpdater is not None and self.trayUpdater.isAlive():
            self.trayUpdater.cancel()
            
        self.trayUpdater = RepeatTimer(1.0, self.updateTrayTooltip, self.options.getRepetitionInterval() * 60)
        self.trayUpdater.start()
        
    def updateTrayTooltip(self):
        self.remaining -= UPDATE_FREQ
        self.trayIcon.setToolTip('Next quiz in ' + (str(self.remaining/UPDATE_FREQ) + ' seconds'))

#    def noQdict(self):
#        self.showSessionMessage('Nope, cannot show quick dictionary during actual quiz.')
        
####################################
#    Initialization procedures     #
####################################

    def initializeResources(self):
        
        """Initialize Options"""
#        self.options = Options()
        self.loadingStatus = u''
        
        self.qload = QuickLoad(self.options)
        if self.options.isLoadingOnStart():
            self.qload.exec_()
        
        """Pre-initialization"""
        self.animationTimer = ()
        self.progressTimer = ()
        self.grid_layout =()
        
        """Initialize Statistics"""
        self.stats = Stats()
        
        """Config Here"""
        self.initializeComposition()
        self.initializeComponents()
        self.setMenus()
        self.trayIcon.show()
        #self.startTrayLoading()
        
        """"Initialize Dictionaries    (will take a some time!)"""
        time_start = datetime.now()
        
        self.trayIcon.showMessage('Loading...', 'Initializing dictionaries', QSystemTrayIcon.MessageIcon.Information, 20000 )
        # kanji composition #
        if self.options.isLoadingRadk(): self.rdk = RadkDict()
        else: self.loadingStatus += '--> Radikt disabled!\n'
        # edict dictionary
        if self.options.isLoadingEdict():
            edict_file = resource_filename('cjktools_data', 'dict/je_edict')
            self.edict = auto_format.load_dictionary(edict_file)
        else: 
            self.edict = None
            self.loadingStatus += '--> Edict disabled!\n'
        # kanjidict dictionary #
        if self.options.isLoadingKdict(): self.kjd = kanjidic.Kanjidic()
        else: 
            self.kjd = None
            self.loadingStatus += '--> Kanjidict disabled!\n'
        # Kanji.Odyssey groups #
        self.groups = KanjiGrouper()
        if self.options.isLoadingGroups(): self.groups.loadgroupsFromDump()
        else: self.loadingStatus += '--> Kanji.Odyssey disabled!\n'
        
        """Initializing srs system"""
        self.trayIcon.showMessage('Loading...', 'Initializing databases', QSystemTrayIcon.MessageIcon.Information, 20000 )
        self.srs = srsScheduler()
        if self.options.isLoadingDb(): self.srs.initializeCurrentSession(self.options.getQuizMode(), self.options.getSessionSize())
        else: self.loadingStatus += '--> Database disabled!\n'
        
        """Jmdict lookup"""
        self.jmdict = DictionaryLookup()
        if self.options.isLoadingJmdict(): 
            self.jmdict.loadJmdictFromDumpRegex()
            self.jmdict.joinTables()
        else: self.loadingStatus += '--> Jmdict disabled!\n'
                
        """Manual add dialog"""
        self.manualAddDialog = ManualAdd(self.srs.db)
        
        if self.loadingStatus != '': self.loadingStatus = '\n\n' + self.loadingStatus
        
        time_end = datetime.now()
        self.loadingTime =  time_end - time_start

####################################
#    Composition and appearance    #
####################################

    def initializeComposition(self):
        
        """Main Dialog"""
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        #NB: This font will be used in buttons
        self.setFont(QFont(Fonts.TukusiMyoutyouProLB, self.options.getQuizFontSize()))

        desktop = QApplication.desktop().screenGeometry()
        self.setGeometry(QRect(desktop.width() - H_INDENT, desktop.height() - V_INDENT, D_WIDTH, D_HEIGHT))
        
        self.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Info dialog"""
        self.info.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.info.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.info.setGeometry(QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT, desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        
        self.info.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Verbose info dialog"""
        self.allInfo.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.allInfo.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.allInfo.setGeometry(QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT, desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        
        self.allInfo.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Session message"""
        self.status.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.status.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.status.setGeometry(QRect(desktop.width() - H_INDENT, desktop.height() - V_INDENT - S_HEIGHT - S_INDENT - S_CORRECTION, S_WIDTH, S_HEIGHT))
        self.status.setMinimumSize(S_WIDTH, S_HEIGHT)
#        self.status.setMinimumWidth(S_WIDTH)
        
        self.status.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        self.setMask(roundCorners(self.rect(),5))
#        self.status.setMask(roundCorners(self.status.rect(),5))
        #self.info.setMask(roundCorners(self.info.rect(),5))
        #self.allInfo.setMask(roundCorners(self.allInfo.rect(),5))
        
        """Kanji info"""
        self.kanjiInfo.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.kanjiInfo.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.kanjiInfo.setGeometry(QRect(desktop.width() - H_INDENT - K_WIDTH - K_INDENT, desktop.height() - V_INDENT, K_WIDTH, K_HEIGHT))
        
        self.kanjiInfo.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Kanji groups"""
        self.kanjiGroups.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.kanjiGroups.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.kanjiGroups.setGeometry(QRect(desktop.width() - H_INDENT - G_WIDTH - G_INDENT, desktop.height() - V_INDENT, G_WIDTH, G_HEIGHT))
        
        self.kanjiGroups.setStyleSheet("QWidget { background-color: rgb(250, 250, 250); }")
        
#        self.setMask(roundCorners(self.rect(),5))
#        self.status.setMask(roundCorners(self.status.rect(),5))

    def initializeComponents(self):
        self.countdown.setMaximumHeight(6)
        self.countdown.setRange(0, self.options.getCountdownInterval() * 100)
        self.countdown.setTextVisible(False)
        self.countdown.setStyleSheet("QProgressbar { background-color: rgb(255, 255, 255); }")
        
        #self.setFont(QFont(Fonts.SyoutyouProEl, 40))#self.options.getQuizFontSize()))
        
        self.sentence.setAlignment(Qt.AlignmentFlag.AlignCenter)
        #self.sentence.setFont(QFont(Fonts.HiragiNoMarugotoProW4, self.options.getSentenceFontSize()))
        self.sentence.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
        
        self.sentence.setWordWrap(True)
        self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
        
        self.status.message.setFont(QFont('Cambria', self.options.getMessageFontSize()))
        self.status.layout.setAlignment(Qt.AlignCenter)
        self.status.message.setWordWrap(False)
        self.status.message.setAlignment(Qt.AlignCenter)
        self.status.layout.setMargin(0)
        
        self.status.info.setHidden(True)
        self.status.progress.setHidden(True)
        self.status.progress.setMaximumHeight(10)
        self.status.progress.setRange(0, self.status.achievements.threshold)
        self.status.layout.setAlignment(Qt.AlignCenter)
        self.status.info.setAlignment(Qt.AlignCenter)
        self.status.info.setFont(QFont(Fonts.RyuminPr5, 13))
        self.status.info.setWordWrap(False)
        
        self.status.gem = self.status.saveGeometry()
        
        self.info.item.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 36))
        self.info.reading.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 16))
        self.info.components.setFont((QFont(Fonts.HiragiNoMyoutyouProW3, 14)))
        #self.info.item.setWordWrap(True)
        self.info.components.setWordWrap(True)
        #self.info.layout.setAlignment(Qt.AlignCenter)
        self.info.layout.setMargin(0)
        #self.info.layout.setSizeConstraint(self.info.layout.SetFixedSize)       #NB: would work nice, if the anchor point was in right corner
        
        self.info.reading.setAlignment(Qt.AlignCenter)
        self.info.item.setAlignment(Qt.AlignCenter)
        self.info.components.setAlignment(Qt.AlignCenter)
        
        #self.info.setLayoutDirection(Qt.RightToLeft)
        
        self.info.reading.setStyleSheet("QLabel { color: rgb(155, 155, 155); }")
        self.info.components.setStyleSheet("QLabel { color: rgb(100, 100, 100); }")
        
        self.kanjiInfo.info.setFont(QFont(Fonts.MSMyoutyou, 14.5))
        self.kanjiInfo.info.setAlignment(Qt.AlignCenter)
        self.kanjiInfo.info.setWordWrap(True)
        self.kanjiInfo.layout.setMargin(0)
        
        self.kanjiGroups.info.setFont(QFont(Fonts.MSMyoutyou, 18.5))
        self.kanjiGroups.info.setAlignment(Qt.AlignCenter)
        self.kanjiGroups.info.setWordWrap(True)
        self.kanjiGroups.layout.setMargin(0)
        
        #NB: ...
        self.answered.setMaximumWidth(D_WIDTH)
        self.answered.setFont(QFont('Calibri', 11))


####################################
#        Updating content          #
####################################        
    def updateContent(self):
        
        """Resetting multi-label sentence"""
        if self.grid_layout != ():
            for i in range(0, self.grid_layout.count()):
                    self.grid_layout.itemAt(i).widget().hide()
            self.layout_vertical.removeItem(self.grid_layout)
            self.grid_layout.setParent(None)
            self.update()
        if self.sentence.isHidden():    
            self.sentence.show()
        self.showButtonsQuiz()
        
        """Getting actual content"""
        self.srs.getNextItem()
              
        example = self.srs.getCurrentExample()
        
        # checking for no example case
        if example is None:
            self.manualAddDialog.setProblemKanji(self.srs.getCurrentItemKanji())
            done = self.manualAddDialog.exec_()
            
            if done == 0:
                self.updateContent()
            elif done == 1:
                self.updateContent()
            else:
                pass
        else:
            example = example.replace(self.srs.getWordFromExample(), u"<font color='blue'>" + self.srs.getWordFromExample() + u"</font>")
            
            # checking sentence length
            if len(self.srs.currentExample.sentence) > SENTENCE_MAX: self.sentence.setFont(QFont(self.options.getSentenceFont(), MIN_FONT_SIZE))
            else: self.sentence.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
            
            #temporary debug info:
#            print len(example), self.sentence.font()
            
            self.sentence.setText(example)
            
            readings = self.srs.getQuizVariants()

            changeFont = False
            for item in readings:
                if len(item) > BUTTON_KANA_MAX : changeFont = True
                
            try:
                for i in range(0, self.layout_horizontal.count()):
                        if i > 3: break
                        self.layout_horizontal.itemAt(i).widget().setText(u'')
                        
                        if changeFont:
                            self.layout_horizontal.itemAt(i).widget().setStyleSheet('QPushButton { font-family: ' + self.options.getQuizFont() + '; font-size: 11pt; }')
                        else:
                            self.layout_horizontal.itemAt(i).widget().setStyleSheet('QPushButton { font-family: ' + self.options.getQuizFont() + '; font-size: %spt; }' % self.options.getQuizFontSize())
                            
                        self.layout_horizontal.itemAt(i).widget().setText(readings[i])
            except:
                log.debug(u'Not enough quiz variants for ' + self.srs.getCurrentItem())
        
    def getReadyPostLayout(self):
        self.sentence.hide()
        self.update()
        
        self.grid_layout = QGridLayout()
        self.grid_layout.setSpacing(0)
        self.labels = []
        
        columns_mod = 0
        #font size depending on sentence length
        if len(self.srs.currentExample.sentence) > SENTENCE_MAX: font =  QFont(self.options.getSentenceFont(), MIN_FONT_SIZE); columns_mod = 6
        else: font = QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize())
        
        #row, column, rows span, columns span, max columns
        i = 0; j = 0; r = 1; c = 1; n = COLUMNS_MAX + columns_mod
        for word in self.srs.parseCurrentExample():
            label = QLabel(word)
            label.setFont(font)
            
            label.setAttribute(Qt.WA_Hover, True)
            label.installEventFilter(self.filter)
            self.labels.append(label)
            
            if len(label.text()) > 1: c = len(label.text())
            else: c = 1
            #Don't ask, really
            if j + c > n: i = i + 1; j = 0
            
            self.grid_layout.addWidget(self.labels.pop(), i, j, r, c)       #NB: Ehh, pop should remove label from list, shouldn't it?
            
            if j <= n: j = j + c
            else: j = 0; i = i + 1
        
        self.grid_layout.setAlignment(Qt.AlignCenter)
        self.layout_vertical.insertLayout(1, self.grid_layout)

        self.update()
        
    def hideButtonsQuiz(self):
        self.var_1st.hide()
        self.var_2nd.hide()
        self.var_3rd.hide()
        self.var_4th.hide()
        
        self.answered.clicked.connect(self.hideQuizAndWaitForNext)
        self.answered.show()
        
    def showButtonsQuiz(self):
        self.var_1st.show()
        self.var_2nd.show()
        self.var_3rd.show()
        self.var_4th.show()
        
        self.answered.hide()
        self.answered.disconnect()
        
####################################
#        Timers and animations     #
####################################

    def waitUntilNextTimeslot(self):
        #if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
        self.nextQuizTimer.start(self.options.getRepetitionInterval() * 60 * 1000)  #options are in minutes    NB: how do neatly I convert minutes to ms?
        self.startUpdatingTrayTooltip()
        
    def beginCountdown(self):
        self.trayIcon.setToolTip('Quiz in progress!')
        self.pauseAction.setText('&Pause')
#        self.pauseAction.setShortcut('P')
        
        self.countdownTimer.start(self.options.getCountdownInterval() * 1000)

        self.progressTimer = RepeatTimer(0.01, self.updateCountdownBar, self.options.getCountdownInterval() * 100)
        self.progressTimer.start()
        
    def updateCountdownBar(self):
        self.countdown.setValue(self.countdown.value() - 1)
        #print self.countdown.value()
        self.countdown.update()         #NB: without .update() recursive repaint crushes qt

    def fade(self):
        if self.windowOpacity() == 1:
            self.animationTimer = RepeatTimer(0.025, self.fadeOut, 40)
            self.animationTimer.start()
        else:
            self.animationTimer = RepeatTimer(0.025, self.fadeIn, 40)
            self.animationTimer.start()
    
    def fadeIn(self):
        self.setWindowOpacity(self.windowOpacity() + 0.1)
        
    def fadeOut(self):
        self.setWindowOpacity(self.windowOpacity() - 0.1)
        
    def stopCountdown(self):
        self.progressTimer.cancel()
        self.countdownTimer.stop()
        self.countdown.setValue(0)
        
####################################
#        Actions and events        #
####################################    
    
    def setMenus(self):
        
        self.showQuizAction = QAction('&Quiz me now!', self, triggered=self.showQuiz)
        self.showQuizAction.setIcon(QIcon(PATH_TO_RES + TRAY + NOW_ICON))
        self.trayMenu.addAction(self.showQuizAction)
        
        self.pauseAction = QAction('&Start quiz!', self, triggered=self.pauseQuiz)
        self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + START_ICON))
        self.trayMenu.addAction(self.pauseAction)
        
        self.trayMenu.addSeparator()
        
        self.quickDictAction = QAction('Quick &dictionary', self, triggered=self.showQuickDict)
        self.quickDictAction.setIcon(QIcon(PATH_TO_RES + TRAY + DICT_ICON))
        self.trayMenu.addAction(self.quickDictAction)
        
        self.optionsAction = QAction('&Options', self, triggered=self.showOptions)
        self.optionsAction.setIcon(QIcon(PATH_TO_RES + TRAY + OPTIONS_ICON))
        self.trayMenu.addAction(self.optionsAction)
        
        self.quickLoadAction = QAction('Quick &load', self, triggered=self.showQuickLoad)
        self.quickLoadAction.setIcon(QIcon(PATH_TO_RES + TRAY + LOAD_ICON))
        self.trayMenu.addAction(self.quickLoadAction)
        
        self.trayMenu.addSeparator()
        
        self.aboutAction = QAction('&About', self, triggered=self.showAbout)
        self.aboutAction.setIcon(QIcon(PATH_TO_RES + TRAY + ABOUT_ICON))
        self.trayMenu.addAction(self.aboutAction)
        
        self.globalStatsAction = QAction('&Global statistics', self, triggered=self.showGlobalStatistics)
        self.globalStatsAction.setIcon(QIcon(PATH_TO_RES + TRAY + STAT_ICON))
        self.trayMenu.addAction(self.globalStatsAction)
        
        self.utilAction = QAction('U&tilities', self, triggered=self.showToolsDialog)
        self.utilAction.setIcon(QIcon(PATH_TO_RES + TRAY + UTILS_ICON))
        self.trayMenu.addAction(self.utilAction)
        
        self.trayMenu.addSeparator()
        
        self.quitAction = QAction('&Exit', self, triggered=self.saveAndExit)
        self.quitAction.setIcon(QIcon(PATH_TO_RES + TRAY + CLOSE_ICON))
        self.trayMenu.addAction(self.quitAction)
        

        self.trayIcon.setContextMenu(self.trayMenu)
        self.trayIcon.activated.connect(self.onTrayIconActivated)

    def onTrayIconActivated(self, reason):
        '''
        if reason == QSystemTrayIcon.DoubleClick:
            print 'tray icon double clicked'
        '''
        if reason == QSystemTrayIcon.Trigger:
            if self.isHidden():
                self.trayIcon.showMessage('Current session statistics:', 'Running time:\t\t' + self.stats.getRunningTime() + 
                                          '\nItems seen:\t\t' + str(self.stats.totalItemSeen) + 
                                          '\nCorrect answers:\t\t' + str(self.stats.answeredCorrect) +
                                          '\nWrong answers:\t\t' + self.stats.getIncorrectAnswersCount() +
                                          '\nCorrect ratio:\t\t' + self.stats.getCorrectRatioPercent() +
                                          #'\nQuiz total time:\t\t' + self.stats.getQuizActive() +
                                          '\nQuiz paused time:\t\t' + self.stats.getPausedTime() +
                                          '\nTotal pondering time:\t' + self.stats.getMusingsTime() +
                                          '\nTotal post-quiz time:\t' + self.stats.getQuizTime() +
                                          '\nAverage pondering:\t' + self.stats.getAverageMusingTime() +
                                          '\nAverage post-quiz:\t' + self.stats.getAveragePostQuizTime(), 
                                          QSystemTrayIcon.MessageIcon.Information, 20000)
    
    def setButtonsActions(self):

        if self.var_1st.text() == self.srs.getCorrectAnswer():
                self.var_1st.clicked.connect(self.correctAnswer)
        else:
                self.var_1st.clicked.connect(self.wrongAnswer)
               
        if self.var_2nd.text() == self.srs.getCorrectAnswer():
                self.var_2nd.clicked.connect(self.correctAnswer)
        else:
                self.var_2nd.clicked.connect(self.wrongAnswer)
                
        if self.var_3rd.text() == self.srs.getCorrectAnswer():
                self.var_3rd.clicked.connect(self.correctAnswer)
        else:
                self.var_3rd.clicked.connect(self.wrongAnswer)
                
        if self.var_4th.text() == self.srs.getCorrectAnswer():
                self.var_4th.clicked.connect(self.correctAnswer)
        else:
                self.var_4th.clicked.connect(self.wrongAnswer)
                
        self.var_1st.setShortcut('1')
        self.var_2nd.setShortcut('2')
        self.var_3rd.setShortcut('3')
        self.var_4th.setShortcut('4')
                
    def resetButtonsActions(self):
        self.var_1st.disconnect()
        self.var_2nd.disconnect()
        self.var_3rd.disconnect()
        self.var_4th.disconnect()
        
    def postAnswerActions(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        
    def refocusQuiz(self):
        self.answered.setShortcut('Space')
        self.activateWindow()
        self.answered.setFocus()
        
    def correctAnswer(self):
        self.postAnswerActions()
        
        self.srs.answeredCorrect()
        self.stats.quizAnsweredCorrect()
        
        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        self.status.achievements.correctAnswer()
        self.showSessionMessage(u'<font color=green>Correct: ' + self.srs.getCorrectAnswer() + '</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime() 
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')
        self.refocusQuiz()
        
    def wrongAnswer(self):
        self.postAnswerActions()
        
        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()
        
        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())
        
        self.status.achievements.wrongAnswer()
        self.showSessionMessage(u'<font color=tomato>Wrong! Should be: '+ self.srs.getCorrectAnswer() + '</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime()
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')
        self.refocusQuiz()
            
    def timeIsOut(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        QTimer.singleShot(50, self.hideButtonsQuiz)     #NB: slight artificial lag to prevent recursive repaint crush (when mouse is suddenly over repainted button)
        self.getReadyPostLayout()
        
        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())
        
        self.status.achievements.wrongAnswer()
        self.showSessionMessage(u'<font color=tomato>Timeout! Should be: ' + self.srs.getCorrectAnswer() + '</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime()
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')    
        self.refocusQuiz()
        
    def checkTranslationSize(self, translation):
        if len(translation) > TRANSLATION_CHARS_LIMIT:
            self.answered.setStyleSheet('QPushButton { font-size: 9pt; }')
            
            space_indices = [i for i, value in enumerate(translation) if value == ' ']
            find_nearest_index = lambda value,list : min(list, key = lambda x:abs(x - value))
            nearest_index = find_nearest_index(TRANSLATION_CHARS_LIMIT, space_indices)
            translation = translation[:nearest_index] + '\n' + translation[nearest_index + 1:]
        else:
            self.answered.setStyleSheet('QPushButton { font-size: 11pt; }')
        
        self.answered.setText(translation)
    
    def hideQuizAndWaitForNext(self):
        self.stats.postQuizEnded()
        
        self.status.hide()
        self.info.hide()
        self.allInfo.hide()
        self.resetButtonsActions()
        
        self.setWindowOpacity(1)
        self.fade()
        QTimer.singleShot(1000, self.hide)
        self.waitUntilNextTimeslot()
        self.updater.mayUpdate = True
 
    def pauseQuiz(self):
        if self.isHidden():
            if self.pauseAction.text() == '&Pause':
                self.nextQuizTimer.stop()
                self.pauseAction.setText('&Unpause')
#                self.pauseAction.setShortcut('U')
                self.trayIcon.setToolTip('Quiz paused!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'inactive.png'))
                self.trayUpdater.cancel()
                self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + START_ICON))
                self.stats.pauseStarted()
                
                self.updater.mayUpdate = True
                
            elif self.pauseAction.text() == '&Start quiz!':
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
#                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
                self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + PAUSE_ICON))
            else:
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
#                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
                self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + PAUSE_ICON))
                self.stats.pauseEnded()
                
                self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Sorry, cannot pause while quiz in progress!')
 
    def showQuiz(self):
        if self.isHidden():
            self.updateContent()
            self.setButtonsActions()
            
            #self.restoreGeometry(self.gem)
            self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
            
            self.show()
            self.setWindowOpacity(0)
            self.fade()

            self.countdown.setValue(self.options.getCountdownInterval() * 100)
            self.beginCountdown()
            self.stats.musingsStarted()
            
            if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
            self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Quiz is already underway!')
         
    def showOptions(self):
        self.optionsDialog.show()
        
    def showAbout(self):
        self.about.show()
        
    def showQuickDict(self):
        self.qdict.showQDict = True
        
    def showGlobalStatistics(self):
        self.statistics.show()
        
    def showToolsDialog(self):
        self.tools.show()
        
    def showQuickLoad(self):
        self.qload.show()
        
    def startTrayLoading(self):
        self.gifLoading.start()
        #self.iconTimer = QTimer()
        #self.iconTimer.timeout.connect(self.updateTrayIcon)
        #self.iconTimer.start(100)
        
    def stopTrayLoading(self):
        self.gifLoading.stop()
        
    def updateTrayIcon(self):
        self.trayIcon.setIcon(self.gifLoading.currentPixmap())
        
    def showSessionMessage(self, message):
        """Shows info message"""
        self.status.message.setText(message)
        if self.status.achievements.achieved is not None:
            self.status.info.setText(self.status.achievements.achieved[1] + '\t( ' + self.status.achievements.achieved[0] + ' )')
            self.status.progress.hide()
            self.status.move(self.status.x(), self.status.y() - 15)
            self.status.info.show()
#            self.status.setMask(roundCorners(self.status.rect(),5))
        else:
            self.status.info.setText(u'')
            self.status.info.hide()
            self.status.restoreGeometry(self.status.gem)
#            self.status.setMask(roundCorners(self.status.rect(),5))
#        print self.status.y()
        self.status.adjustSize()
        self.status.show()
 
    def saveAndExit(self):
        self.hide()
        self.status.hide()
        self.allInfo.hide()
        self.kanjiInfo.hide()
        
        self.trayIcon.showMessage('Shutting down...', 'Saving session', QSystemTrayIcon.MessageIcon.Information, 20000 )
        
        if self.countdownTimer.isActive():
                self.countdownTimer.stop()
        if self.nextQuizTimer.isActive():
                self.nextQuizTimer.stop()
        if self.progressTimer != () and self.progressTimer.isAlive():
                self.progressTimer.cancel()
        if self.trayUpdater is not None and self.trayUpdater.isAlive() :
                self.trayUpdater.cancel()    
            
        self.rehash.checkSessionResults()
        
        self.srs.endCurrentSession(self.stats)
        self.trayIcon.hide()

        self.hooker.stop()

        self.updater.stop()
        self.optionsDialog.close()
        self.about.close()
        self.qdict.close()
        self.close()        
        
    def addReferences(self, about, options, qdict, updater, tools, statistics, web, rehash):
        self.about = about
        self.optionsDialog = options
        self.qdict = qdict
        self.updater = updater
        self.tools = tools
        self.statistics = statistics
        self.status.web = web
        self.rehash = rehash
        
    def initGlobalHotkeys(self):
        def toggleWidgetFlag(): self.qdict.showQDict = True
        self.hooker = GlobalHotkeyManager(toggleWidgetFlag , 'Q')
        self.hooker.setDaemon(True)
        self.hooker.start()
        
    def showEvent(self, event):
        self.restoreGeometry(self.gem)
Esempio n. 2
0
class NanoKontrol2InputHandler(InputHandler):
  def __init__(self, *args, **kwargs):
    InputHandler.__init__(self, *args, **kwargs)
    self._leds = dict()
    self._hold_button = None
    self._hold_timer = None
    # a port to send messages back to transport controllers
    self._send_port = None
    # the names of controller clients that have been connected 
    #  to the transport
    self._connected_controllers = set()
    self.transport.add_observer(self.on_transport_change)
    self.on_transport_change()
  def destroy(self):
    if (self._send_port is not None):
      client = self._send_port.client
      sinks = self._send_port.get_connections()
      for sink in sinks:
        client.disconnect(self._send_port, sink)
    self.transport.remove_observer(self.on_transport_change)
    InputHandler.destroy(self)
  # connect back to all controlling devices
  def on_transport_change(self):
    # update connections to controlling devices
    ports = self.port.get_connections()
    for port in ports:
      name = port.name.split(':')[0]
      if (name in self._connected_controllers): continue
      self.connect_controller(name)
    # update LED state on controlling devices
    if (self.transport):
      self.update_led(self.PLAY_BUTTON, self.transport.playing)
      self.update_led(self.RECORD_BUTTON, self.transport.recording)
      self.update_led(self.CYCLE_BUTTON, self.transport.cycling)
  def connect_controller(self, name):
    # get an input port for the device
    client = self.port.client
    input_ports = client.get_ports(name_pattern=name+':.*',
                                   type_pattern='.*midi.*',
                                   flags=jackpatch.JackPortIsInput)
    if (len(input_ports) == 0): return
    input_port = input_ports[0]
    if (self._send_port is None):
      if (self._response_port is not None):
        self._send_port = self._response_port
      else:
        self._send_port = jackpatch.Port(client=client,
                                         name="midi.TX", 
                                         flags=jackpatch.JackPortIsOutput)
    self.port.client.connect(self._send_port, input_port)
    self._connected_controllers.add(name)
    self.init_controllers()
  def init_controllers(self):
   # send a sysex message to let the controller know we'll be
   #  managing the state of its LEDs
   self.send_message([ 0xF0, 0x42, 0x40, 0x00, 0x01, 0x13,
                       0x00, 0x00, 0x00, 0x01, 0xF7 ])
   # turn off all LEDs initially
   for i in range(0, 128):
     self.update_led(i, False)
  # send a message back to the controlling device
  def send_message(self, data):
    if (self._send_port is None): return
    self._send_port.send(data, 0.0)
  # controller button values
  PLAY_BUTTON = 0x29
  STOP_BUTTON = 0x2A
  BACK_BUTTON = 0x2B
  FORWARD_BUTTON = 0x2C
  RECORD_BUTTON = 0x2D
  CYCLE_BUTTON = 0x2E
  PREVIOUS_TRACK_BUTTON = 0x3A
  NEXT_TRACK_BUTTON = 0x3B
  SET_MARK_BUTTON = 0x3C
  PREVIOUS_MARK_BUTTON = 0x3D
  NEXT_MARK_BUTTON = 0x3E
  # controller type masks
  TRANSPORT = 0x28
  MARK = 0x38
  LEVEL = 0x00
  PAN = 0x10
  SOLO = 0x20
  MUTE = 0x30
  ARM = 0x40
  PER_TRACK = (LEVEL, PAN, SOLO, MUTE, ARM)
  # interpret messages
  def handle_message(self, data, time):
    # ignore all long messages
    if (len(data) != 3): return
    (status, controller, value) = data
    # filter for control-change messages
    if ((status & 0xF0) != 0xB0): return
    # get the kind of control this is
    kind = (controller & 0xF8)
    # handle mode buttons
    if (controller == self.PLAY_BUTTON):
      if ((value > 64) and (self.transport)):
        self.transport.play()
    elif (controller == self.RECORD_BUTTON):
      if ((value > 64) and (self.transport)):
        self.transport.record()
    elif (controller == self.CYCLE_BUTTON):
      if ((value > 64) and (self.transport)):
        self.transport.cycling = not self.transport.cycling
    # handle action buttons
    elif ((kind == self.TRANSPORT) or (kind == self.MARK)):
      if (value > 64):
        if (self.transport):
          if (controller == self.STOP_BUTTON):
            self.transport.stop()
          elif (controller == self.BACK_BUTTON):
            self.transport.skip_back()
            self.set_holding(controller)
          elif (controller == self.FORWARD_BUTTON):
            self.transport.skip_forward()
            self.set_holding(controller)
          elif (controller == self.PREVIOUS_MARK_BUTTON):
            self.transport.previous_mark()
            self.set_holding(controller)
          elif (controller == self.NEXT_MARK_BUTTON):
            self.transport.next_mark()
            self.set_holding(controller)
          elif (controller == self.SET_MARK_BUTTON):
            self.transport.toggle_mark()
            self.set_holding(controller)
      else:
        self.clear_holding()
      self.update_led(controller, value > 64)
  # handle a button being held down
  def set_holding(self, button):
    self._hold_button = button
    if (self._hold_timer is None):
      self._hold_timer = QTimer(self)
      self._hold_timer.timeout.connect(self.on_hold)
    self._hold_timer.setInterval(500)
    self._hold_timer.start()
  def clear_holding(self):
    self._hold_button = None
    if (self._hold_timer is not None):
      self._hold_timer.stop()
  def on_hold(self):
    if (self._hold_button is None):
      try:
        self._hold_timer.stop()
      except AttributeError: pass
      return
    self.handle_message([ 0xBF, self._hold_button, 127 ], 0.0)
    if ((self._hold_timer is not None) and 
        (self._hold_timer.interval() != 100)):
      self._hold_timer.setInterval(100)
  # send a message to update the state of a button LED
  def update_led(self, button, on):
    try:
      old_value = self._leds[button]
    except KeyError:
      old_value = None
    value = 0
    if (on):
      value = 127
    if (value != old_value):
      self._leds[button] = value
      self.send_message([ 0xBF, button, value ])
Esempio n. 3
0
class Navigator(Singleton,object):
    
    instance = None
    logger = None
    PATH_SEPARATOR = '-'
    
    JUMP_SIGNAL_PAGE_LOAD_FINISH = 'PAGE LOAD FINISH'
    JUMP_SIGNAL_REQUEST_LOAD_FINISH = 'REQUEST LOAD FINISH'
    
    CROSS_AXIS_METHOD_AJAX = 'AJAX'
    CROSS_AXIS_METHOD_FULL_LOAD = 'FULL LOAD'
    
    def __init__(self):
        self.network_manager = CustomNetworkAccessManager.getInstance()
        self.axis = []
        self.human_events = {}
        self.node_collected_data = {}
        self.last_url = None   
        self.current_url = QUrl()
        self.current_axis_id = None
        self.crossed_axis_method = None
        self.crossed_axes = []
        self.crossed_axes_string = ''
        
        self.cross_axis_delay = QTimer()
        self.cross_axis_delay.setSingleShot(True)
        self.cross_axis_delay.timeout.connect(self.crossedAxis)
        self.cross_axis_params = None
        
        self.loading_timeout = QTimer()
        self.loading_timeout.setSingleShot(True)
        self.loading_timeout.setInterval(30000)
        self.loading_timeout.timeout.connect(self.loadingTimeoutAction)
        
        self.inactivity_timeout = QTimer()
        self.inactivity_timeout.setSingleShot(True)
        self.inactivity_timeout.setInterval(10000)
        self.inactivity_timeout.timeout.connect(self.inactivityTimeoutAction)
        
        self.page_reloads = 0
        
        Logger.getLoggerFor(self.__class__)

    def takeEntryPoint(self):        
        g = GraphParser.getInstance()
        for ax in self.axis:
            self.logger.info('Axis : %s' % ax)
            
        if g.entry_point:
            self.logger.info('Taking the entry point %s' % g.entry_point)
            self.last_url = QUrl(g.entry_point)
            inst =  Jaime.getInstance()
            inst.view.load(QUrl(g.entry_point))    
            return True
        
        self.logger.error("Can't take entry point, graph has't entry point")
        return False
        
    def matchesSelector(self,axis):
        inst =  Jaime.getInstance()
        frame = inst.view.page().mainFrame()
        document = frame.documentElement()
        selector = axis.css_selector_condition
#         print 'document %s ' % document.evaluateJavaScript("""this.querySelector('%s')""" % selector)        
        ret = frame.findAllElements(selector)
        
        print '%s axis %s matches css selector ? %s' % (selector,axis,bool(ret.count()))
        self.logger.debug('axis %s matches css selector ? %s' % (axis,bool(ret.count())))
#         for i in range(ret.count()):
#             self.logger.error(ret.at(i).toInnerXml())
        return ret.count()
    
    def matches(self,axis):
        m_from = False
        m_to = False
        m_method = True
        m_selector = True
        m_route = True
        
        if axis.from_url is not None :            
            if isinstance(axis.from_url,str):
                m_from = axis.from_url == self.last_url.toString()
            elif axis.from_url.match(self.last_url.toString()):                
                m_from = True
        else:
            m_from = True
        
        if axis.to_url is not None:
            if isinstance(axis.to_url,str):
                m_to =  axis.to_url == self.current_url.toString()
            elif axis.to_url.match(self.current_url.toString()):
                m_to = True         
        else:
            m_to = True
        
#         print 'el axis %s tiene method %s, %s '  % (axis,
#                                                     axis.axis_method,
#                                                     self.crossed_axis_method
#                                                     )
        
        if self.crossed_axis_method == RouteAxis.CROSS_AXIS_METHOD_AJAX or \
                axis.axis_method :
            
            if self.crossed_axis_method != axis.axis_method:
                m_method = False           
        
        if axis.css_selector_condition:
            m_selector = axis.not_css_selector ^ bool(self.matchesSelector(axis))            
        
        if len(axis.previous_axis)>0:
            m_route = False
            for path_index in range(len(axis.previous_axis)):
                path =  string.join( axis.previous_axis[path_index], self.PATH_SEPARATOR)
                self.logger.debug( "matching axis (%s) with path %s" % (axis.id,path))
                if self.crossed_axes_string.find(path) == 0:
                    m_route = True 
                    break
        
        self.logger.debug("Matches (%s) %s\nfrom %s  to %s method %s selector %s route %s" % (axis.id,
                                                                                             axis.comment,
                                                                                             m_from,m_to,m_method,m_selector,m_route))
        return m_from and m_to and m_method and m_selector and m_route
    
    def crossedAxis(self,ajax=False):
        if self.cross_axis_params and self.cross_axis_params[0]: 
            ajax = True
            
        self.cross_axis_params = None
        self.crossed_axes_string = string.join(self.crossed_axes,self.PATH_SEPARATOR)
        self.logger.info("crossedAxis, path: \n%s" % string.join(self.crossed_axes,"\n"))
        if ajax:
            self.crossed_axis_method =  RouteAxis.CROSS_AXIS_METHOD_AJAX
        else:
            self.crossed_axis_method =  RouteAxis.CROSS_AXIS_METHOD_FULL_LOAD
            
        print 'Cruse un eje por %s ' % self.crossed_axis_method
        
        for ax in self.axis:
            if self.matches(ax):
                self.current_axis_id = ax.id
                # print 'current axis %s'  % ax.id
                self.logger.info('Axis matched %s' % ax)                
                self.startSecuence(ax)
                return
        self.logger.warning("Can't match any axis")                
        self.inactivity_timeout.start()            
    
    def collectData(self,id,collector,params):
        if not self.getAxis(id):
            self.logger.error("Can't collect data from inexistent axis")
            return 

        if id not  in self.node_collected_data:
            self.node_collected_data[id] = []
            
        self.node_collected_data[id].append((collector,params))
    
    def setAxis(self,route_axis,human_events):
        self.logger.info('setting axis %s ' % route_axis)
        if route_axis.id in self.human_events:
            raise Exception('Axis repetido')
        
        self.human_events[route_axis.id] = human_events
        self.axis.append(route_axis)
#         QtCore.QObject.disconnect(self.human_events[route_axis.id], 
#                                   QtCore.SIGNAL("finished()"), 
#                                   self.secuenceFinished)
        QtCore.QObject.connect(self.human_events[route_axis.id], 
                               QtCore.SIGNAL("finished()"), 
                               self.secuenceFinished)
    
    def startSecuence(self,axis):
        inst =  Jaime.getInstance()
        try :
            if axis.max_crosses > 0 and axis.max_crosses <= axis.cross_counter:
                self.logger.info('el axis supero la cantidad maxima de loops ')
                if axis.exit_point:
                    exit_axis = self.getAxis(axis.exit_point)
                    self.logger.info('Salto hacia el exit_axis %s' %  exit_axis)
                    self.startSecuence(exit_axis)
                else:
                    self.logger.error('No hay exit axis muero %s' % axis.exit_point)
                    inst =  Jaime.getInstance()
                    inst.finishWork()                    
            else:

                axis.cross_counter += 1
                h_ev = self.human_events[axis.id]            
                self.crossed_axes.insert(0,axis.id)
                if len(self.crossed_axes) >= 10: self.crossed_axes.pop()            
                self.logger.info('Stopeo el inactivity timeout')
                self.inactivity_timeout.stop()            
                if axis.id in self.node_collected_data:
                    inst.route_node.doWork(self.node_collected_data[axis.id])                    
                h_ev.fire()
                
        except Exception as e:
            # print 'Excepcion en startSecuence %s' % e
            self.logger.error(e)
            
    def getAxis(self,axis_id):
        for ax in self.axis:
            if ax.id == axis_id:
                return ax
        return None
    
    def secuenceFinished(self):
        self.logger.info('estarteo  el inactivity timeout' )
        self.inactivity_timeout.start()
    
    def inactivityTimeoutAction(self):
        
        inst =  Jaime.getInstance()
        self.logger.info('inactivity timeout action fired after %s seconds' % (self.inactivity_timeout.interval()/1000))
        
        if not len(self.crossed_axes):
            return 
        last_axis = self.crossed_axes[0]
        ax = self.getAxis(last_axis)
        
        retry_axis_id = ax.retry_axis
        retry_axis = self.getAxis(retry_axis_id)
        
        if retry_axis:
            self.logger.info('El axis %s tiene como retry axis a %s lo estarteo' % (ax.id,ax.retry_axis))
            self.startSecuence(retry_axis)
    
    def processLoadStarted(self):
        inst =  Jaime.getInstance()
        self.logger.info('Page started load to %s' % inst.view.url().toString())
        
#         print inst.page.mainFrame().requestedUrl()
        self.inactivity_timeout.stop()            
        self.loading_timeout.stop()
        self.loading_timeout.start()
        
        self.logger.info('Starting loading timeout and stopping inactivity timeout')        
        self.last_url = inst.view.url()
        
    def loadingTimeoutAction(self):
        self.logger.warning('Loading timeout fired')
        inst =  Jaime.getInstance()
        if (not inst.view.url().isEmpty() and re.match('http.?://',inst.view.url().toString()) ) \
                and not self.page_reloads:
            self.logger.info('Timeout fired, reloading last url %s' % inst.view.url().toString())
            self.page_reloads += 1            
            self.loading_timeout.stop()                    
            inst.view.reload()            
            
        else:
            self.logger.error("""Timeout fired, clossing jaime, there isn't last_url or max_reloads reatched""" )
            inst.finishWork()
                        
    def processPageLoadFinished(self,status):
        inst =  Jaime.getInstance()
        self.logger.info('Page finished load to %s with status %s ' % (inst.view.url().toString(),
                                                                       status))
        if status:
            self.current_url = inst.view.url()
            self.loading_timeout.stop()
            self.page_reloads = 0 
            self.logger.info('Stopping loading timeout')    
        else:
            self.current_url = QUrl()           
            
        self.testJumpRules(self.JUMP_SIGNAL_PAGE_LOAD_FINISH,
                           status)       
        
    def testJumpRules(self,signal,*args):
#         self.logger.info('Call to restJumpRules with signal %s' % signal)
#         print 'Call to restJumpRules %s' % ( signal)
        if signal == self.JUMP_SIGNAL_PAGE_LOAD_FINISH:
            print 'llamo a crosed axis'
            self.pushCrossedAxis()            
        
        elif signal == self.JUMP_SIGNAL_REQUEST_LOAD_FINISH:
            if not self.current_axis_id:
                return
            
            req_headers = args[0]
            rep_headers = args[1]
            
            ax = self.getAxis(self.current_axis_id)
#             print '%s tiene exit_method %s' % (ax,ax.axis_exit_method)
            if ax.axis_exit_method and  \
                    ax.axis_exit_method == RouteAxis.CROSS_AXIS_METHOD_AJAX:
                
                if 'X-Requested-With' in req_headers:
                    
                    if ax.axis_exit_method_toggled :
                        ax.axis_exit_method = ax.axis_exit_method_toggled
                        ax.axis_exit_method_toggled = None
                    
                    self.pushCrossedAxis(True)
    
    def pushCrossedAxis(self,*params):
        if self.cross_axis_delay.isActive() or \
                self.cross_axis_params is not None:
            self.logger.warning("""Can't push crossAxis call, there is another call in process""")
            return 
        
        self.cross_axis_params = params                
        
        ax = self.getAxis(self.current_axis_id)        
        
        if ax and  ax.delay_node_test: 
            delay = ax.delay_node_test
        else:
            delay = None
            
        if delay:            
            self.cross_axis_delay.setInterval(delay)
            self.cross_axis_delay.start()
            self.logger.info('Delaying %s seconds crossedAxis call ' % int( int(delay) /1000)  )            
        else:
            self.crossedAxis()
Esempio n. 4
0
class Transport(observable.Object, unit.Sink):
  # extra signals
  recording_will_start = Signal()
  recording_started = Signal()
  recording_will_stop = Signal()
  recording_stopped = Signal()
  # regular init stuff
  def __init__(self, time=0.0, duration=0.0, cycling=False, marks=(), 
               protocol='nanokontrol2'):
    observable.Object.__init__(self)
    unit.Sink.__init__(self)
    # set the interval to update at
    self.update_interval = 0.5
    # get a bridge to the JACK transport
    self._client = jackpatch.Client('jackdaw-transport')
    self._client.activate()
    self._transport = jackpatch.Transport(client=self._client)
    # make a timer to update the transport model when the time changes
    self._update_timer = QTimer(self)
    self._update_timer.setInterval(self.update_interval * 1000)
    self._update_timer.timeout.connect(self.update_timeout)
    # set up internal state
    self._recording = False
    self._cycling = cycling
    # store all time marks
    self.marks = observable.List(marks)
    self._sorting_marks = False
    self.marks.add_observer(self.on_marks_change)
    # the start and end times of the cycle region, which will default
    #  to the next and previous marks if not set externally
    self._cycle_start_time = None
    self.cycle_start_time = None
    self._cycle_end_time = None
    self.cycle_end_time = None
    # store the duration, which is notional and non-constraining, 
    #  and which is meant to be maintained by users of the transport
    self._duration = duration
    # store the time
    self._local_time = None
    self.time = time
    self._last_played_to = time
    self._last_display_update = 0
    self._start_time = None
    self._local_is_rolling = None
    # the amount to change time by when the skip buttons are pressed
    self.skip_delta = 1.0 # seconds
    # make a port and client for transport control
    self._sink_type = 'midi'
    self._sink_port = jackpatch.Port(name='midi.RX', client=self._client,
                                     flags=jackpatch.JackPortIsInput)
    self._source_port = jackpatch.Port(client=self._client,
                                       name="midi.TX", 
                                       flags=jackpatch.JackPortIsOutput)
    self._input_handler = None
    self._protocol = None
    self.protocol = protocol
    # the amount of time to allow between updates of the display
    self.display_interval = 0.05 # seconds
    # start updating
    self._update_timer.start()
    self.update()
  # add methods for easy button binding
  def play(self, *args):
    self.playing = True
  def record(self, *args):
    self.recording = True
  def stop(self, *args):
    self.playing = False
    self.recording = False
  # the protocol to use to interpret MIDI input
  @property
  def protocol(self):
    return(self._protocol)
  @protocol.setter
  def protocol(self, value):
    if ((value != self._protocol) and (value in self.protocols)):
      self._protocol = value
      (name, cls) = self.protocols[value]
      if (self._input_handler is not None):
        self._input_handler.destroy()
      self._input_handler = cls(port=self.sink_port, 
                                response_port=self._source_port, 
                                transport=self)
  # return a map from protocols to handler classes
  @property
  def protocols(self):
    return(collections.OrderedDict((
      ('realtime', ('System Realtime', SystemRealtimeInputHandler)),
      ('nanokontrol2', ('Korg NanoKONTROL2', NanoKontrol2InputHandler))
    )))
  # whether play mode is on
  @property
  def playing(self):
    return((self.is_rolling) and (not self.recording))
  @playing.setter
  def playing(self, value):
    value = (value == True)
    if (self.playing != value):
      self.recording = False
      if (value):
        self.start()
      else:
        self.pause()
      self.on_change()
  # whether record mode is on
  @property
  def recording(self):
    return((self.is_rolling) and (self._recording))
  @recording.setter
  def recording(self, value):
    value = (value == True)
    if (self._recording != value):
      self.playing = False
      if (value):
        self.recording_will_start.emit()
      else:
        self.recording_will_stop.emit()
      self._recording = value
      if (self._recording):
        self.start()
      else:
        self.pause()
      self.on_change()
      if (value):
        self.recording_started.emit()
      else:
        self.recording_stopped.emit()
  # whether the transport is playing or recording
  @property
  def is_rolling(self):
    if (self._local_is_rolling is not None):
      return(self._local_is_rolling)
    return(self._transport.is_rolling)
  # whether cycle mode is on
  @property
  def cycling(self):
    return(self._cycling)
  @cycling.setter
  def cycling(self, value):
    value = (value == True)
    if (self._cycling != value):
      self.update_cycle_bounds()
      self._cycling = value
      self.on_change()
  # get the current timepoint of the transport
  @property
  def time(self):
    if (self._local_time is not None):
      return(self._local_time)
    return(self._transport.time)
  @time.setter
  def time(self, t):
    # don't allow the time to be set while recording
    if (self._recording): return
    # don't let the time be negative
    t = max(0.0, t)
    # record the local time setting so we can use it while the transport 
    #  updates to the new location
    self._local_time = t
    self._transport.time = t
    self.update_cycle_bounds()
    self.on_change()
  @property
  def duration(self):
    return(self._duration)
  @duration.setter
  def duration(self, value):
    if (value != self._duration):
      self._duration = value
      self.on_change()
  # start the time moving forward
  def start(self):
    self._last_played_to = self.time
    # establish the cycle region
    self.update_cycle_bounds()
    self._local_is_rolling = True
    self._transport.start()
    self.update()
  # stop time moving forward
  def pause(self):
    self._local_is_rolling = False
    self._transport.stop()
    self.update()
  def update_timeout(self):
    is_rolling = self.is_rolling
    current_time = self.time
    # clear the locally stored settings because now we're checking 
    #  the real transport
    self._local_is_rolling = None
    self._local_time = None
    self.update(is_rolling=is_rolling, current_time=current_time)
  def update(self, is_rolling=None, current_time=None):
    if (is_rolling is None):
      is_rolling = self.is_rolling
    if (current_time is None):
      current_time = self.time
    # update infrequently when not running and frequently when running
    if (not is_rolling):
      if (self._update_timer.interval() != 500):
        self._update_timer.setInterval(500)
        self.on_change()
    else:
      if (self._update_timer.interval() != 50):
        self._update_timer.setInterval(50)
      # do cycling
      if ((self.cycling) and (self._cycle_end_time is not None) and 
          (current_time > self._cycle_end_time)):
        # only play up to the cycle end time
        self.on_play_to(self._cycle_end_time)
        # bounce back to the start, maintaining any interval we went past the end
        self._last_played_to = self._cycle_start_time
        current_time = (self._cycle_start_time + 
          (current_time - self._cycle_end_time))
        self.time = current_time
      # play up to the current time
      self.on_play_to(current_time)
    # notify for a display update if the minimum interval has passed
    elapsed = current_time - self._last_display_update
    if (abs(elapsed) >= self.display_interval):
      self.on_change()
      self._last_display_update = current_time
  # handle the playback of the span after and including self._last_played_to
  #  and up to but not including the given time
  def on_play_to(self, end_time):
    # prepare for the next range
    self._last_played_to = end_time
  # set the cycle region based on the current time
  def update_cycle_bounds(self):
    current_time = self.time
    if (self.cycle_start_time is not None):
      self._cycle_start_time = self.cycle_start_time
    else:
      mark = self.get_previous_mark(current_time + 0.001)
      self._cycle_start_time = mark.time if mark is not None else None
    if (self.cycle_end_time is not None):
      self._cycle_end_time = self.cycle_end_time
    else:
      mark = self.get_next_mark(current_time)
      self._cycle_end_time = mark.time if mark is not None else None
  # jump to the beginning or end
  def go_to_beginning(self):
    self.time = 0.0
  def go_to_end(self):
    self.time = self.duration
  # skip forward or back in time
  def skip_back(self, *args):
    self.time = self.time - self.skip_delta
  def skip_forward(self, *args):
    self.time = self.time + self.skip_delta
  # toggle a mark at the current time
  def toggle_mark(self, *args):
    UndoManager.begin_action(self.marks)
    t = self.time
    found = False
    for mark in set(self.marks):
      if (mark.time == t):
        self.marks.remove(mark)
        found = True
    if (not found):
      self.marks.append(Mark(time=t))
    UndoManager.end_action()
  def on_marks_change(self):
    if (self._sorting_marks): return
    self._sorting_marks = True
    self.marks.sort()
    self.on_change()
    self._sorting_marks = False
  # return the time of the next or previous mark relative to a given time
  def get_previous_mark(self, from_time):
    for mark in reversed(self.marks):
      if (mark.time < from_time):
        return(mark)
    # if we're back past the first mark, treat the beginning 
    #  like a virtual mark
    return(Mark(time=0.0))
  def get_next_mark(self, from_time):
    for mark in self.marks:
      if (mark.time > from_time):
        return(mark)
    return(None)
  # move to the next or previous mark
  def previous_mark(self, *args):
    mark = self.get_previous_mark(self.time)
    if (mark is not None):
      self.time = mark.time
  def next_mark(self, *args):
    mark = self.get_next_mark(self.time)
    if (mark is not None):
      self.time = mark.time
  # transport serialization
  def serialize(self):
    return({
      'time': self.time,
      'duration': self.duration,
      'cycling': self.cycling,
      'marks': list(self.marks),
      'protocol': self.protocol
    })