Welcome, guest | Sign In | My Account | Store | Cart
# -*- coding: iso-8859-1 -*-

import os
import time
import math
from tkinter import *
from tkinter.font import Font
import tkinter.filedialog
import tkinter.messagebox

# parameters
encodings
= ['latin-1','utf-8'] # available text file encodings (default first)
width
,height=300,300 # meters canvas dimensions
len1
,len2 = 0.85,0.3 # needle dimensions, relative to meter ray
ray
= int(0.7*width/2) # meter circle
x0
,y0 = width/2,width/2 # position of circle center inside canvas
min_speed
,max_speed = 0,400 # minimum and maximum speed, in characters/minute
step_speed
= 50 # step between speed marks on meter
min_err
,max_err,step_err = 0,10,1 # same for error rate in %

# localisation
langs
= {'en':'English','fr':'Français'}
try: # to guess language from locale
   
import locale
    default_lang
= locale.getdefaultlocale()[0][:2]
    langs
[default_lang]
except:
    default_lang
= 'en'

_
= {'title':{'en':'Typing skills meter','fr':'Dactylomètre'},
   
'exo':{'en':'Exercices','fr':'Exercices'},
   
'opts':{'en':'Options','fr':'Options'},
   
'paste':{'en':'Paste','fr':'Coller'},
   
'open':{'en':'Open...','fr':'Ouvrir...'},
   
'clear':{'en':'Clear','fr':'Effacer'},
   
'restart':{'en':'Start again','fr':'Recommencer'},
   
'encoding':{'en':'Encoding','fr':'Encodage'},
   
'speed':{'en':'Speed','fr':'Vitesse'},
   
'err':{'en':'Errors','fr':'Erreurs'},
   
'cpm':{'en':'Chars/min','fr':'Cars/min'}
   
}

def set_libs(lang):
    root
.title(_['title'][lang])
    menu
.entryconfig(1,label=_['exo'][lang])
    menu
.entryconfig(2,label=_['opts'][lang])
    speed
.itemconfig(speed.title,text=_['speed'][lang])
    speed
.itemconfig(speed.unit,text=_['cpm'][lang])
    errors
.itemconfig(errors.title,text=_['err'][lang])
    errors
.itemconfig(errors.unit,text='%')
   
for i,entry in enumerate(['open','paste','clear','restart']):
        file_menu
.entryconfig(i,label=_[entry][lang])
   
root
= Tk()
font
= Font(family="Courier New",size=12,weight='bold') # text font
meter_font
= Font(family="Arial",size=12,weight='normal')

result_box
= None
exo
= None
t0
= None

class Exercise:

   
def __init__(self,text=""):
       
self.text = text
        model
.delete(1.0,END)
        model
.insert(END,self.text)

   
def reset(self):
       
self.start = None
       
self.line,self.col = 1,0
       
self.nb_errors = 0
        speed
.draw_needle(0)
        errors
.draw_needle(0)
       
for tag in 'done','error','old_error':
            model
.tag_remove(tag,1.0,END)
        model
.mark_set(INSERT,1.0)
        model
.focus()

class Meter(Canvas):

   
def draw(self,vmin,vmax,step,title,unit):
       
self.vmin = vmin
       
self.vmax = vmax
        x0
= width/2
        y0
= width/2
        ray
= int(0.7*width/2)
       
self.title = self.create_text(width/2,20,fill="#000",
            font
=meter_font)
       
self.create_oval(x0-ray*1.1,y0-ray*1.1,x0+ray*1.1,y0+ray*1.1,
            fill
="#DDD")
       
self.create_oval(x0-ray,y0-ray,x0+ray,y0+ray,fill="#000")
        coef
= 0.1
       
self.create_oval(x0-ray*coef,y0-ray*coef,x0+ray*coef,y0+ray*coef,
            fill
="white")
       
for i in range(1+int((vmax-vmin)/step)):
            v
= vmin + step*i
            angle
= (5+6*((v-vmin)/(vmax-vmin)))*math.pi/4
           
self.create_line(x0+ray*math.sin(angle)*0.9,
                y0
- ray*math.cos(angle)*0.9,
                x0
+ray*math.sin(angle)*0.98,
                y0
- ray*math.cos(angle)*0.98,fill="#FFF",width=2)
           
self.create_text(x0+ray*math.sin(angle)*0.75,
                y0
- ray*math.cos(angle)*0.75,
                text
=v,fill="#FFF",font=meter_font)
           
if i==int(vmax-vmin)/step:
               
continue
           
for dv in range(1,5):
                angle
= (5+6*((v+dv*(step/5)-vmin)/(vmax-vmin)))*math.pi/4
               
self.create_line(x0+ray*math.sin(angle)*0.94,
                    y0
- ray*math.cos(angle)*0.94,
                    x0
+ray*math.sin(angle)*0.98,
                    y0
- ray*math.cos(angle)*0.98,fill="#FFF")
       
self.unit = self.create_text(width/2,y0+0.8*ray,fill="#FFF",
            font
=meter_font)
       
self.needle = self.create_line(x0-ray*math.sin(5*math.pi/4)*len2,
            y0
+ray*math.cos(5*math.pi/4)*len2,
            x0
+ray*math.sin(5*math.pi/4)*len1,
            y0
-ray*math.cos(5*math.pi/4)*len1,
            width
=2,fill="#FFF")

   
def draw_needle(self,v):
        v
= max(v,self.vmin)
        v
= min(v,self.vmax)
        angle
= (5+6*((v-self.vmin)/(self.vmax-self.vmin)))*math.pi/4
       
self.coords(self.needle,x0-ray*math.sin(angle)*len2,
            y0
+ray*math.cos(angle)*len2,
            x0
+ray*math.sin(angle)*len1,
            y0
-ray*math.cos(angle)*len1)

def paste():
   
global exo
   
try:
        txt
= root.clipboard_get()
        model
.insert(END,txt)
        exo
= Exercise(txt)
        exo
.reset()
        root
.clipboard_clear()
   
except:
       
return

def open_text():
   
global exo
    exo_dir
= os.path.join(os.getcwd(),'texts')
   
if not os.path.exists(exo_dir):
        os
.mkdir(exo_dir)
    filename
= tkinter.filedialog.askopenfilename(initialdir=exo_dir)
   
if filename:
        src
= open(filename,encoding=encoding.get())
       
try:
            model_txt
= '\n'.join([l.rstrip() for l in src.readlines()])
       
except UnicodeDecodeError:
            tkinter
.messagebox.showerror('Encoding error',
                message
=("Can't open file %s with encoding %s")
               
%(os.path.basename(filename),encoding.get()))
           
return
        exo
= Exercise(model_txt)
        exo
.reset()

def clear():
   
global exo
    model
.delete(1.0,END)
    exo
= None

def start_again():
   
global result_box
   
if exo is not None:
        exo
.reset()
   
if result_box is not None:
        result_box
.destroy()
        result_box
= None

def type_key(event):
   
global exo,result_box, t0
   
if exo is None:
       
return 'break'
    pos
= model.index("%s.%s" %(exo.line,exo.col))
    nbcars
= len(model.get(0.0,pos))
   
if exo is None:
       
if nbcars>0:
            model_txt
= model.get(0.0,END+"-1 chars")
            model_txt
= model_txt.encode('utf-8')
            exo
= Exercise(model_txt)
       
else:
           
return        
   
if exo.start is None:
        exo
.start = time.time()
   
elif nbcars>3:
        carspm
= 60*nbcars/(time.time()-exo.start)
        speed
.draw_needle(carspm)
       
    pos
= model.index("%s.%s" %(exo.line,exo.col))
    pos_see
= model.index("%s+30c" %pos)
    model
.see(pos_see)
    good
= model.get(model.index("%s.%s" %(exo.line,exo.col)))
    typed
= event.char
   
if event.keysym in ["Shift_L","Shift_R","Multi_key"]:
       
return 'break'

    flag
= (typed=='\r' and good=='\n') or (good == typed)
   
   
if not flag: # error
        exo
.nb_errors += 1
        err_txt
= "%s error" %exo.nb_errors
       
if exo.nb_errors > 1:
            err_txt
+= "s"
        model
.tag_add('error',pos)
   
else:
        exo
.col += 1
       
if good == "\n":
            exo
.line += 1
            exo
.col = 0
       
if 'error' in model.tag_names(pos):
            model
.tag_remove('error',pos)
            model
.tag_add('old_error',pos)
       
else:
            model
.tag_add('done',pos)
        model
.mark_set(INSERT,model.index('%s+1c' %pos))
   
if nbcars:
        err_rate
= 100*round(float(exo.nb_errors)/float(nbcars),4)
        errors
.draw_needle(err_rate)

   
if model.index("%s.%s" %(exo.line,exo.col)) == model.index(END+"-1 chars"):
        result_box
= Toplevel(root)
       
Label(result_box,text="The exercice is finished").pack()
       
Button(result_box,text="Start again",command=start_again).pack()
       
Button(result_box,text="Other text",command=open_text).pack()

   
return 'break'


encoding
= StringVar(root)
encoding
.set(encodings[0])
lang
= StringVar(root)
lang
.set(default_lang)

menu
= Menu(root)
file_menu
= Menu(menu,tearoff=False)
for command in open_text,paste,clear,start_again:
    file_menu
.add_command(command=command)
menu
.add_cascade(label="Exercices",menu=file_menu)

options_menu
= Menu(menu,tearoff=False)
menuLang
= Menu(options_menu,tearoff=0)
for lang in langs:
    menuLang
.add_command(label=langs[lang],command=lambda x=lang:set_libs(x))
options_menu
.add_cascade(menu=menuLang,label='Language')
menuEncoding
= Menu(options_menu,tearoff=0)
for enc in encodings:
    menuEncoding
.add_command(label=enc,command=lambda x=enc:encoding.set(x))
options_menu
.add_cascade(menu=menuEncoding,label='Encoding')
menu
.add_cascade(label="Options",menu=options_menu)

root
.config(menu=menu)

# meters zone
meters
= Frame(root,width=2*width,height=width,bg="white")
speed
= Meter(meters,width=width,height=height)
speed
.draw(min_speed,max_speed,step_speed,"Speed","Chars/min")
speed
.pack(side=LEFT)
errors
= Meter(meters,width=width,height=width)
errors
.draw(min_err,max_err,step_err,"Errors","%")
errors
.pack()
meters
.pack(anchor=S,fill=Y,expand=True)

# text zone
model
= Text(root,width=80,height=20,wrap=WORD,font=font,padx=10,pady=10,relief=RIDGE)
model
.tag_config('done',foreground="#303030",background="#D0D0D0")
model
.tag_config('error',foreground="#FF0000",background="#FFFFFF")
model
.tag_config('old_error',foreground="#FF0000",background="#D0D0D0")
model
.bind('<Key>',type_key)
model
.pack()

set_libs
(default_lang)
root
.mainloop()

History