キュートなタイマー


PyQtの練習でタイマーを作りました。

日本語資料が少ないので大変。



それに、コンソール上Ctrl+Cで死んでくれない、
インタラクティブシェルでQApplicationを作ると入力が重くなって、
実質的にインタラクティブシェルでの実験は無理・・・




wxPythonより高度っぽいですが、ちょっとワガママ?

#encoding:shift-jis
from __future__ import division, with_statement, print_function
import sys
from itertools import *
import time
import math

from PyQt4.QtGui import *
from PyQt4.QtCore import *

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

class TimerThread(QThread):
    def __init__(self, parent, length, interval=0.1):
        QThread.__init__(self, parent)
        self.length = length
        self._stop = False
        self.interval = interval
        
    def stop(self):
        self._stop = True
    
    def run(self):
        starttime = time.time()
        while not self._stop:
            remain = int(math.ceil(self.length - (time.time() - starttime)))
            self.emit(SIGNAL('tick(int)'), remain)
            if remain == 0:
                self.emit(SIGNAL('finish()'))
                break
            time.sleep(self.interval)

def setTabOrders(*widgets):
    for w1, w2 in pairwise(widgets):
        QWidget.setTabOrder(w1, w2)
    
class TimerWindow(QWidget):
    def __init__(self, parent=None):
        super(TimerWindow, self).__init__(parent)
        
        font = self.font()
        font.setPointSize(18)
        self.setFont(font)
        
        self.min = QLineEdit(self)
        self.min.setValidator(QIntValidator(self))
        self.sec = QLineEdit(self)
        self.sec.setValidator(QIntValidator(self))
        
        self.btn = QPushButton("Start", self)
        self.btn.setDefault(True)
        
        toplayout = QHBoxLayout()
        toplayout.addWidget(self.min)
        toplayout.addWidget(QLabel(u"分"))
        toplayout.addWidget(self.sec)
        toplayout.addWidget(QLabel(u"秒"))
        
        bottomlayout = QHBoxLayout()
        bottomlayout.addWidget(self.btn)
        
        layout = QVBoxLayout()
        layout.addLayout(toplayout)
        layout.addLayout(bottomlayout)
        
        self.setLayout(layout)
        self.reset_display()
        
        setTabOrders(self.min, self.sec, self.btn, self.min)
        self.min.setFocus()
        self.min.selectAll()
        
        self.resize(300, 100)
        
        QObject.connect(self.btn, SIGNAL("clicked()"), self.start)
        
        def select_sec():
            self.sec.setFocus()
            self.sec.selectAll()
            
        QObject.connect(self.min, SIGNAL("returnPressed()"), select_sec)
        QObject.connect(self.sec, SIGNAL("returnPressed()"), self.start)
        
    def start(self):
        def err(msg): 
            QMessageBox.warning(self, "Timer", msg)
        secstr = unicode(self.sec.text()).strip()
        minstr = unicode(self.min.text()).strip()
        
        if not minstr:
            err(u"分を入力してください")
            return
        if not secstr:
            err(u"秒を入力してください")
            return
        try:
            m = int(minstr)
        except ValueError as e:
            err(u"分を半角数字で入力してください")
            return
        try:
            s = int(secstr)
        except ValueError as e:
            err(u"秒を半角数字で入力してください")
            return
        
        self.min.setEnabled(False)
        self.sec.setEnabled(False)
        self.btn.setText("Stop")
        
        QObject.disconnect(self.btn, SIGNAL("clicked()"), self.start)
        QObject.connect(self.btn, SIGNAL("clicked()"), self.stop)
        
        self.timer = TimerThread(self, m * 60 + s)
        QObject.connect(self.timer, SIGNAL('tick(int)'), self.tick)
        QObject.connect(self.timer, SIGNAL('finish()'), self.finish)
        
        self.timer.start()
    
    def stop(self):
        self.timer.stop()
        self.sec.setEnabled(True)
        self.min.setEnabled(True)
        self.setWindowTitle("Timer")
        
        self.min.setFocus()
        self.min.selectAll()
        
        self.btn.setText("Start")
        QObject.disconnect(self.btn, SIGNAL("clicked()"), self.stop)
        QObject.connect(self.btn, SIGNAL("clicked()"), self.start)
        
    def tick(self, remain):
        m, s = remain // 60, remain % 60
        self.min.setText(unicode(m))
        self.sec.setText(unicode(s))
        self.setWindowTitle(u"残{0}分{1}秒".format(m, s))
    
    def finish(self):
        self.stop()
        self.reset_display()
        QMessageBox.information(self, "Timer", u"時間です")
        
    def reset_display(self):
        self.min.setText("0")
        self.sec.setText("0")
        self.setWindowTitle("Timer")
    
def main():
    global app
    app = QApplication(sys.argv)
    w = TimerWindow()
    w.show()
    app.exec_()
    
if __name__ == "__main__":
    import doctest
    doctest.testmod()
    main()