Newer
Older
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# Start of frontend.py - Tk and other front-ends
def interrupt_instructions():
if soundCollector or app or appuifw or android: return ""
elif msvcrt: return "\nPress Space if you have to interrupt the lesson."
elif riscos_sound: return "\nLesson interruption not yet implemented on RISC OS. If you stop the program before the end of the lesson, your progress will be lost. Sorry about that."
elif winCEsound: return "\nLesson interruption not implemented on\nWinCE without GUI. Can't stop, sorry!"
elif macsound: return "\nPress Ctrl-C if you have to interrupt the lesson."
else: return "\nPress Control-C if you have to interrupt the lesson."
appTitle += time.strftime(" %A") # in case leave 2+ instances on the desktop
def waitOnMessage(msg):
global warnings_printed
if type(msg)==type(u""): msg2=msg.encode("utf-8")
else: msg2=msg
if appuifw:
t=appuifw.Text() ; t.add(u"".join(warnings_printed)+msg) ; appuifw.app.body = t # in case won't fit in the query() (and don't use note() because it doesn't wait)
elif android:
# android.notify("Gradint","".join(warnings_printed)+msg) # doesn't work?
android.dialogCreateAlert("Gradint","".join(warnings_printed)+msg)
android.dialogSetPositiveButtonText("OK")
android.dialogShow() ; android.dialogGetResponse()
elif app:
if not (winsound or winCEsound or mingw32 or cygwin): show_info(msg2+"\n\nWaiting for you to press OK on the message box... ",True) # in case terminal is in front
app.todo.alert = "".join(warnings_printed)+msg
while True:
try:
if not hasattr(app.todo,"alert"): break
except: break # app destroyed
time.sleep(0.5)
if not (winsound or winCEsound or mingw32 or cygwin): show_info("OK\n",True)
else:
if clearScreen(): msg2 = "This is "+program_name.replace("(c)","\n(c)")+"\n\n"+msg2 # clear screen is less confusing for beginners, but NB it may not happen if warnings etc
show_info(msg2+"\n\n"+cond(winCEsound,"Press OK to continue\n","Press Enter to continue\n"))
sys.stderr.flush() # hack because some systems don't do it (e.g. some mingw32 builds), and we don't want the user to fail to see why the program is waiting (especially when there's an error)
try:
raw_input(cond(winCEsound,"See message under this window.","")) # (WinCE uses boxes for raw_input so may need to repeat the message - but can't because the prompt is size-limited, so need to say look under the window)
clearScreen() # less confusing for beginners
except EOFError: show_info("EOF on input - continuing\n")
def getYN(msg,defaultIfEof="n"):
if appuifw:
appuifw.app.body = None
return appuifw.query(u""+msg,'query')
elif android:
android.dialogCreateAlert("Gradint",msg)
android.dialogSetPositiveButtonText("Yes") # TODO do we have to localise this ourselves or can we have a platform default?
android.dialogSetNegativeButtonText("No")
android.dialogShow()
return android.dialogGetResponse().result['which'] == 'positive'
while app and not hasattr(app,"answer_given"): time.sleep(0.5)
if not app: raise SystemExit
ans = app.answer_given
del app.answer_given
return ans
else:
ans=None
clearScreen() # less confusing for beginners
while not ans=='y' and not ans=='n':
try: ans = raw_input("%s\nPress y for yes, or n for no. Then press Enter. --> " % (msg,))
except EOFError:
ans=defaultIfEof ; print ans
clearScreen() # less confusing for beginners
if ans=='y': return 1
return 0
def primitive_synthloop():
interactive = appuifw or winCEsound or android or not hasattr(sys.stdin,"isatty") or sys.stdin.isatty()
if interactive: interactive=cond(winCEsound and warnings_printed,"(see warnings under this window) Say:","Say: ") # (WinCE uses an input box so need to repeat the warnings if any - but can't because prompt is size-limited, so need to say move the window.)
else: interactive="" # no prompt on the raw_input (we might be doing outputFile="-" as well)
if justSynthesize: justSynthesize=justSynthesize.encode("utf-8")
else: break
if android:
justSynthesize = android.dialogGetInput("Gradint",interactive).result
if type(justSynthesize)==type(u""): justSynthesize=justSynthesize.encode("utf-8")
else:
try: justSynthesize=raw_input(interactive)
except EOFError: break
if (winCEsound or riscos_sound or android) and not justSynthesize: break # because no way to send EOF (and we won't be taking i/p from a file)
if interactive and not readline:
interactive="('a' for again) Say: "
if justSynthesize=="a": justSynthesize=old_js
if justSynthesize: lang = just_synthesize(interactive,lang)
# and see if it transliterates:
if justSynthesize and lang and not "#" in justSynthesize:
if justSynthesize.startswith(lang+" "):
t = transliterates_differently(justSynthesize[len(lang+" "):],lang)
if t: t=lang+" "+t
else: t = transliterates_differently(justSynthesize,lang)
if t:
if appuifw: justSynthesize = t
else: show_info("Spoken as "+t+"\n")
if warnings_printed: # at end not beginning, because don't want to overwrite the info message if appuifw
if appuifw:
t=appuifw.Text()
t.add(u"".join(warnings_printed))
appuifw.app.body = t
elif android: waitOnMessage("") # (makeToast doesn't stay around for very long)
# else they'll have already been printed
warnings_printed = []
if android:
if not isDirectory("/mnt/sdcard/svox") and not isDirectory("/system/tts/lang_pico"): waitOnMessage("English voice might not be installed. Check under Home > Menu > Settings > Voice output > text to speech > Pico > English")
if winCEsound: return None # user might be paying per byte! + difficult to switch back if no Alt-Tab program
except: g=0
if g and (winCEsound or macsound or (hasattr(g,"background") and g.background) or (hasattr(webbrowser,"BackgroundBrowser") and g.__class__==webbrowser.BackgroundBrowser) or (hasattr(webbrowser,"Konqueror") and g.__class__==webbrowser.Konqueror)):
return g.open_new(url)
# else don't risk it - it might be text-mode and unsuitable for multitask-with-gradint
if winsound: return not os.system('start "%ProgramFiles%\\Internet Explorer\\iexplore.exe" '+url) # use os.system not system here (don't know why but system() doesn't always work for IE)
# (NB DON'T replace % with %%, it doesn't work. just hope nobody set an environment variable to any hex code we're using in mp3web)
def clearScreen():
global warnings_printed
if not (winsound or mingw32 or unix): return # can't do it anyway
if warnings_printed:
# don't do it this time (had warnings etc)
warnings_printed = []
return
if winsound or mingw32: os.system("cls")
else: os.system("clear 1>&2") # (1>&2 in case using stdout for something else)
return True
cancelledFiles = []
def handleInterrupt(): # called only if there was an interrupt while the runner was running (interrupts in setup etc are propagated back to mainmenu/exit instead, because lesson state could be anything)
needCountItems = 0
if saveProgress:
if dbase and not dbase.saved_completely:
show_info("Calculating partial progress... ") # (normally quite quick but might not be on PDAs etc, + we want this written if not app)
needCountItems = 1 # used if not app
elif dbase and not app: show_info("Interrupted on not-first-time; no need to save partial progress\n")
# DON'T init cancelledFiles to empty - there may have been other missed events.
while copy_of_runner_events:
cancelledEvent = copy_of_runner_events[0][0]
try: runner.cancel(copy_of_runner_events[0][1])
except: pass # wasn't in the queue - must have slipped out
del copy_of_runner_events[0]
# cancelledEvent = runner.queue[0][-1][0] worked in python 2.3, but sched implementation seems to have changed in python 2.5 so we're using copy_of_runner_events instead
if hasattr(cancelledEvent,"wordToCancel") and cancelledEvent.wordToCancel: cancelledFiles.append(cancelledEvent.wordToCancel)
if not app and needCountItems and cancelledFiles: show_info("(%d cancelled items)...\n" % len(cancelledFiles))
global repeatMode ; repeatMode = 0 # so Ctrl-C on justSynth-with-R works
tkNumWordsToShow = 10 # the default number of list-box items
def addStatus(widget,status,mouseOnly=0):
# Be VERY CAREFUL with status line changes. Don't do it on things that are focused by default (except with mouseOnly=1). Don't do it when the default status line might be the widest thing (i.e. when list box is not displayed) or window size could jump about too much. And in any event don't use lines longer than about 53 characters (the approx default width of the listbox when using monospace fonts).
# (NB addStatus now takes effect only when the list box is displayed anyway, so OK for buttons that might also be displayed without it)
widget.bind('<Enter>',lambda *args:app.set_statusline(status))
widget.bind('<Leave>',app.restore_statusline)
if not mouseOnly:
widget.bind('<FocusIn>',lambda *args:app.set_statusline(status))
widget.bind('<FocusOut>',app.restore_statusline)
button = Tkinter.Button(parent)
button["text"] = text
button["command"] = command
button.bind('<Return>',command) # so can Tab through them
button.bind('<ButtonRelease-3>', app.wrongMouseButton)
bindUpDown(button,True)
return button
def addButton(parent,text,command,packing=None,status=None):
button = makeButton(parent,text,command)
if status: addStatus(button,status)
if packing=="nopack": pass
elif packing: button.pack(packing)
else: button.pack()
return button
def addLabel(row,label):
label = Tkinter.Label(row,text=label)
label.pack({"side":"left"})
return label
def CXVMenu(e): # callback for right-click
e.widget.focus()
m=Tkinter.Menu(None, tearoff=0, takefocus=0)
if macsound:
cut,copy,paste = "<<Cut>>","<<Copy>>","<<Paste>>"
else:
ctrl="<Control-"
cut,copy,paste = ctrl+'x>',ctrl+'c>',ctrl+'v>'
def evgen(e,cmd): e.widget.event_generate(cmd)
funclist = [("Paste",paste),("Delete",'<Delete>')]
if not macsound:
funclist = [("Cut",cut),("Copy",copy)]+funclist # doesn't work reliably on Mac Tk
for l,cmd in funclist: m.add_command(label=l,command=(lambda e=e,c=cmd: e.widget.after(10,evgen,e,c)))
m.add_command(label="Select All",command=(lambda e=e: e.widget.after(10,selectAll,e)))
m.tk_popup(e.x_root-3, e.y_root+3,entry="0")
def selectAll(e):
e.widget.event_generate('<Home>')
e.widget.event_generate('<Shift-End>')
def selectAllButNumber(e): # hack for recording.py - select all but any number at the start
e.widget.event_generate('<Home>')
for i in list(e.widget.theText.get()):
if "0"<=i<="9" or i=="_": e.widget.event_generate('<Right>')
else: return e.widget.event_generate('<Shift-End>')
def addTextBox(row,wide=0):
text = Tkinter.StringVar(row)
entry = Tkinter.Entry(row,textvariable=text)
if macsound:
entry.bind('<Control-ButtonRelease-1>',CXVMenu)
entry.bind('<ButtonRelease-2>',CXVMenu)
if winCEsound:
if WMstandard: # non-numeric inputs no good on WMstandard Tkinter
def doRawInput(text,entry):
app.input_to_set = text
app.menu_response="input"
entry.bind('<Return>',lambda e:doRawInput(text,entry))
if wide: # put help in 1st wide textbox
global had_doRawInput
try: had_doRawInput
except:
had_doRawInput=1
text.set("(Push OK to type A-Z)") # (if changing this message, change it below too)
class E: pass
e=E() ; e.widget = entry
entry.after(10,lambda *args:selectAll(e))
else: # PocketPC: try to detect long clicks. This is awkward. time.time is probably 1sec resolution so will get false +ves if go by that only.
def timeStamp(entry): entry.buttonPressTime=time.time()
entry.bind('<ButtonPress-1>',lambda e:timeStamp(entry))
global lastDblclkAdvisory,lastDblclk
lastDblclkAdvisory=lastDblclk=0
def pasteInstructions(t):
if t>=0.5: # they probably want tap-and-hold, which we don't do properly
global lastDblclkAdvisory
if t<2 and (lastDblclkAdvisory>time.time()-30 or lastDblclk>time.time()-90): return # reduce repeated false +ves
lastDblclkAdvisory=time.time()
app.todo.alert="Double-click in the box if you want to replace it with the clipboard contents"
def doPaste(text,entry):
text.set(entry.selection_get(selection="CLIPBOARD"))
global lastDblclk ; lastDblclk=time.time()
entry.bind('<ButtonRelease-1>',lambda e:pasteInstructions(time.time()-getattr(entry,"buttonPressTime",time.time())))
entry.bind('<Double-Button-1>',lambda e:doPaste(text,entry))
# Tkinter bug workaround (some versions): event_generate from within a key event handler can be unreliable, so the Ctrl-A handler delays selectAll by 10ms:
entry.bind(cond(macsound,'<Command-a>','<Control-a>'),(lambda e:e.widget.after(10,lambda e=e:selectAll(e))))
if wide=="nopack": pass
elif wide:
if winCEsound or olpc: entry["width"]=1 # so it will squash down rather than push off-screen any controls to the right (but DON'T do this on other platforms, where we want the window to expand in that case, e.g. when there are cache controls)
entry.pack(side="left",fill=Tkinter.X,expand=1)
else: entry.pack({"side":"left"})
return text,entry
def bindUpDown(o,alsoLeftRight=False): # bind the up and down arrows to do shift-tab and tab (may be easier for some users, especially on devices where tab is awkward)
tab=(lambda e:e.widget.after(10,lambda e=e:e.widget.event_generate('<Tab>')))
shTab=(lambda e:e.widget.after(10,lambda e=e:e.widget.event_generate('<Shift-Tab>')))
o.bind('<Up>',shTab)
o.bind('<Down>',tab)
if alsoLeftRight:
o.bind('<Left>',shTab)
o.bind('<Right>',tab)
label = addLabel(row,"") # will set contents later
text,entry = addTextBox(row,wide)
return label,text,entry
def addRow(parent,wide=0):
row = Tkinter.Frame(parent)
if wide: row.pack(fill=Tkinter.X,expand=1)
else: row.pack()
return row
def addRightRow(widerow): # call only after adding any left-hand buttons. better tab order than filling buttons from the right.
rrow = Tkinter.Frame(widerow)
rrow.pack(side="right") ; return rrow
def make_output_row(parent):
# make a row of buttons for choosing where the output goes to
# if there aren't any options then return None
# we also put script-variant selection here, if any
row = None
row = Tkinter.Frame(parent)
row.pack(fill=Tkinter.X,expand=1)
return row
GUIlang = GUI_languages.get(firstLanguage,firstLanguage)
if "@variants-"+GUIlang in GUI_translations: # the firstLanguage has script variants
row=getRow(row)
if not hasattr(app,"scriptVariant"): app.scriptVariant = Tkinter.StringVar(app)
count = 0
Tkinter.Radiobutton(row, text=u" "+variant+u" ", variable=app.scriptVariant, value=str(count), indicatoron=forceRadio).pack({"side":"left"})
app.scriptVariant.set(str(scriptVariants.get(GUIlang,0)))
row=getRow(row)
if not hasattr(app,"voiceOption"): app.voiceOption = Tkinter.StringVar(app)
Tkinter.Radiobutton(row, text=u" Normal ", variable=app.voiceOption, value="", indicatoron=forceRadio).pack({"side":"left"})
for o in guiVoiceOptions: Tkinter.Radiobutton(row, text=u" "+o[1].upper()+o[2:]+u" ", variable=app.voiceOption, value=o, indicatoron=forceRadio).pack({"side":"left"})
app.voiceOption.set(voiceOption)
if not gotSox: return row # can't do any file output without sox
if not hasattr(app,"outputTo"):
app.outputTo = Tkinter.StringVar(app) # NB app not parent (as parent is no longer app)
app.outputTo.set("0") # not "" or get tri-state boxes on OS X 10.6
rightrow = addRightRow(row) # to show beginners this row probably isn't the most important thing despite being in a convenient place, we'll right-align
t = Tkinter.Radiobutton(rightrow, text=cond(forceRadio,""," ")+ftu+" ", variable=app.outputTo, value=fileType, indicatoron=forceRadio)
addStatus(t,"Select this to save a lesson or\na phrase to a%s %s file" % (cond(ftu[0] in "AEFHILMNORSX","n",""),ftu))
if winsound or mingw32: got_windows_encoder = fileExists(programFiles+"\\Windows Media Components\\Encoder\\WMCmd.vbs")
elif cygwin: got_windows_encoder = fileExists(programFiles+"/Windows Media Components/Encoder/WMCmd.vbs")
else: got_windows_encoder = 0
Tkinter.Label(rightrow,text=localise("To")+":").pack({"side":"left"})
t=Tkinter.Radiobutton(rightrow, text=cond(forceRadio,""," ")+localise("Speaker")+" ", variable=app.outputTo, value="0", indicatoron=forceRadio) # (must be value="0" not value="" for OS X 10.6 otherwise the other buttons become tri-state)
addStatus(t,"Select this to send all sounds to\nthe speaker, not to files on disk")
if got_program("lame"): addFiletypeButton("mp3")
if got_windows_encoder: addFiletypeButton("wma")
if got_program("neroAacEnc") or got_program("faac") or got_program("afconvert"): addFiletypeButton("aac")
if got_program("oggenc"): addFiletypeButton("ogg")
if got_program("toolame"): addFiletypeButton("mp2")
if got_program("speexenc"): addFiletypeButton("spx")
addFiletypeButton("wav")
# "Get MP3 encoder" and "Get WMA encoder" changed to "MP3..." and "WMA..." to save width (+ no localisation necessary)
if unix and not got_program("lame") and got_program("make") and got_program("gcc") and (got_program("curl") or got_program("wget")): addButton(rightrow,"MP3...",app.getEncoder,status="Press this to compile an MP3 encoder\nso Gradint can output to MP3 files") # (checking gcc as well as make because some distros strangely have make but no compiler; TODO what if has a non-gcc compiler)
# (no longer available) elif (winsound or mingw32) and not got_windows_encoder and not got_program("lame"): addButton(rightrow,"WMA...",app.getEncoder,status="Press this to download a WMA encoder\nso Gradint can output to WMA files")
return row
def updateSettingsFile(fname,newVals):
# leaves comments etc intact, but TODO does not cope with changing variables that have been split over multiple lines
replacement_lines = []
try: oldLines=u8strip(read(fname)).replace("\r\n","\n").split("\n")
except IOError: oldLines=[]
for l in oldLines:
found=0
for k in newVals.keys():
if l.startswith(k):
replacement_lines.append(k+"="+repr(newVals[k]))
del newVals[k]
found=1
if not found: replacement_lines.append(l)
for k,v in newVals.items(): replacement_lines.append(k+"="+repr(v))
if replacement_lines and replacement_lines[-1]: replacement_lines.append("") # ensure blank line at end so there's a \n but we don't add 1 more with each save
open(fname,"w").write("\n".join(replacement_lines))
def asUnicode(x): # for handling the return value of Tkinter entry.get()
try: return u""+x # original behaviour
except: # some localised versions of Windows e.g. German will return Latin1 instead of Unicode, so try interpreting it as utf-8 and Latin-1
try: return x.decode("utf-8")
except: return x.decode("iso-8859-1") # TODO can we get what it actually IS? (on German WinXP, sys.getdefaultencoding==ascii and locale==C but Tkinter still returns Latin1)
onLeft = winCEsound or olpc or (winsound and hasattr(app,"isBigPrint")) # window placement on Vista can sometimes end up too far to the right in big print recordings manager
s.grid(row=rowNo,column=cond(onLeft,0,1),sticky="ns"+cond(onLeft,"w","e"))
c=Tkinter.Canvas(parent,bd=0,width=200,height=100,yscrollcommand=s.set)
s.config(command=c.yview)
scrolledFrame=Tkinter.Frame(c) ; c.create_window(0,0,window=scrolledFrame,anchor="nw")
# Mousewheel binding. TODO the following bind_all assumes only one scrolledFrame on screen at once (redirect all mousewheel events to the frame; necessary as otherwise they'll go to buttons etc)
app.ScrollUpHandler = lambda *args:c.yview("scroll","-1","units")
app.ScrollDownHandler = lambda *args:c.yview("scroll","1","units")
scrolledFrame.bind_all('<Button-4>',app.ScrollUpHandler)
scrolledFrame.bind_all('<Button-5>',app.ScrollDownHandler)
# DON'T bind <MouseWheel> on Windows - our version of Tk will segfault when it occurs. See http://mail.python.org/pipermail/python-bugs-list/2005-May/028768.html but we can't patch our library.zip's Tkinter anymore (TODO can we use newer Tk DLLs and ensure setup.bat updates them?)
return scrolledFrame, c
# GUI presets buttons:
shortDescriptionName = "short-description"+dottxt
longDescriptionName = "long-description"+dottxt
class ExtraButton(object):
def __init__(self,directory):
self.shortDescription = u8strip(read(directory+os.sep+shortDescriptionName)).strip(wsp)
if fileExists(directory+os.sep+longDescriptionName): self.longDescription = u8strip(read(directory+os.sep+longDescriptionName)).strip(wsp)
else: self.longDescription = self.shortDescription
self.directory = directory
def add(self):
app.extra_button_callables.append(self) # so we're not lost when deleted from the waiting list
self.button = addButton(app.rightPanel,localise("Add ")+unicode(self.shortDescription,"utf-8"),self,{"fill":"x"})
if not tkMessageBox.askyesno(app.master.title(),unicode(self.longDescription,"utf-8")+"\n"+localise("Add this to your collection?")): return
newName = self.directory
if os.sep in newName: newName=newName[newName.rfind(os.sep)+1:]
if newName.endswith(exclude_from_scan): newName=newName[:-len(exclude_from_scan)]
if not newName: newName="1"
ls = []
try: ls = os.listdir(samplesDirectory)
name1=newName
while newName in ls: newName+="1"
name2=newName
newName = samplesDirectory+os.sep+newName
os.rename(self.directory,newName)
which_collection = localise(" has been added to your recorded words collection.")
if fileExists(newName+os.sep+"add-to-vocab"+dottxt):
which_collection = localise(" has been added to your collection.")
o=open(vocabFile,"a")
o.write("# --- BEGIN "+self.shortDescription+" ---\n")
o.write(u8strip(read(newName+os.sep+"add-to-vocab"+dottxt)).strip(wsp)+"\n")
o.write("# ----- END "+self.shortDescription+" ---\n")
if hasattr(app,"vocabList"): del app.vocabList # so re-reads
os.remove(newName+os.sep+"add-to-vocab"+dottxt)
if fileExists(newName+os.sep+"add-to-languages"+dottxt):
changed = 0
for lang in u8strip(read(newName+os.sep+"add-to-languages"+dottxt)).strip(wsp).split():
if not lang in [firstLanguage,secondLanguage]+otherLanguages:
otherLanguages.append(lang) ; changed = 1
if changed: sanitise_otherLanguages(), updateSettingsFile("advanced"+dottxt,{"otherLanguages":otherLanguages,"possible_otherLanguages":possible_otherLanguages})
os.remove(newName+os.sep+"add-to-languages"+dottxt)
promptsAdd = newName+os.sep+"add-to-prompts"
if isDirectory(promptsAdd):
for f in os.listdir(promptsAdd):
if fileExists_stat(promptsDirectory+os.sep+f): os.remove(promptsAdd+os.sep+f)
else: os.rename(promptsAdd+os.sep+f, promptsDirectory+os.sep+f)
os.rmdir(promptsAdd)
if not name1==name2: which_collection += "\n(NB you already had a "+name1+" so the new one was called "+name2+" - you might want to sort this out.)"
self.button.pack_forget()
app.extra_button_callables.remove(self)
if extra_buttons_waiting_list: app.add_extra_button()
if tkMessageBox.askyesno(app.master.title(),unicode(self.shortDescription,"utf-8")+which_collection+"\n"+localise("Do you want to start learning immediately?")): app.makelesson()
extra_buttons_waiting_list = []
def make_extra_buttons_waiting_list():
if os.sep in samplesDirectory:
oneUp=samplesDirectory[:samplesDirectory.rfind(os.sep)]
if not oneUp: oneUp=os.sep
else: oneUp=os.getcwd()
for d in [samplesDirectory,oneUp]:
try: ls = os.listdir(d)
ls.sort()
for l in ls:
if l.endswith(exclude_from_scan) and fileExists(d+os.sep+l+os.sep+shortDescriptionName): extra_buttons_waiting_list.append(ExtraButton(d+os.sep+l))
def focusButton(button):
button.focus()
if macsound: # focus() should display the fact on Windows and Linux, but doesn't on OS X so:
def flashButton(button,state):
try: button.config(state=state)
except: pass # maybe not a button
for t in range(250,1000,250): # (NB avoid epilepsy's 5-30Hz!)
app.after(t,lambda *args:flashButton(button,"active"))
app.after(t+150,lambda *args:flashButton(button,"normal"))
# (Don't like flashing, but can't make it permanently active as it won't change when the focus does)
if WMstandard: GUI_omit_statusline = 1 # unlikely to be room (and can disrupt nav)
def startTk():
class Application(Tkinter.Frame):
def __init__(self, master=None):
Tkinter.Frame.__init__(self, master)
class EmptyClass: pass
self.todo = EmptyClass() ; self.toRestore = []
self.ScrollUpHandler = self.ScrollDownHandler = lambda *args:True
global app ; app = self
make_extra_buttons_waiting_list()
if olpc: self.master.option_add('*font',cond(extra_buttons_waiting_list,'Helvetica 9','Helvetica 14'))
elif macsound and Tkinter.TkVersion>=8.6: self.master.option_add('*font','System 13') # ok with magnification. Note >13 causes square buttons. (Including this line causes "Big print" to work)
elif WMstandard: self.master.option_add('*font','Helvetica 7') # TODO on ALL WMstandard devices?
if winsound or cygwin or macsound: self.master.resizable(1,0) # resizable in X direction but not Y (latter doesn't make sense, see below). (Don't do this on X11 because on some distros it results in loss of automatic expansion as we pack more widgets.)
if commands.getoutput("xlsatoms|grep COMPIZ_WINDOW").find("COMPIZ")>-1: # (not _COMPIZ_WM_WINDOW_BLUR, that's sometimes present outside Compiz)
# Compiz sometimes has trouble auto-resizing our window (e.g. on Ubuntu 11.10)
self.master.geometry("%dx%d" % (self.winfo_screenwidth(),self.winfo_screenheight()))
if not GUI_always_big_print: self.todo.alert = "Gradint had to maximize itself because your window manager is Compiz which sometimes has trouble handling Tkinter window sizes"
self.extra_button_callables = []
self.pack(fill=Tkinter.BOTH,expand=1)
self.leftPanel = Tkinter.Frame(self)
self.leftPanel.pack(side="left",fill=Tkinter.X,expand=1) # "fill" needed so listbox can fill later
self.rightPanel = None # for now
self.cancelling = 0 # guard against multiple presses of Cancel
self.Label = Tkinter.Label(self.leftPanel,text="Please wait a moment")
self.Label.pack()
self.Label["wraplength"]=self.Label.winfo_screenwidth() # don't go off-screen in teacherMode
# See if we can figure out what Tk is doing with the fonts (on systems without magnification):
try:
f=str(self.Label.cget("font")).split()
nominalSize = intor0(f[-1])
if nominalSize: f=" ".join(f[:-1])+" %d"
else: # Tk 8.5+ ?
f=str(self.tk.eval('set font [font actual '+' '.join(f)+']')).split()
upNext = 0
for i in range(len(f)):
if f[i]=="-size": upNext=1
elif upNext:
nominalSize=intor0(f[i])
if nominalSize<0: nominalSize,f[i] = -nominalSize,"-%d"
else: f[i]="%d"
break
f=" ".join(f)
if not "%d" in f: raise Exception("wrong format") # caught below
pixelSize = self.Label.winfo_reqheight()-2*int(str(self.Label["borderwidth"]))-2*int(str(self.Label["pady"]))
# NB DO NOT try to tell Tk a desired pixel size - you may get a *larger* pixel size. Need to work out the desired nominal size.
approx_lines_per_screen_when_large = 25 # TODO really? (24 at 800x600 192dpi 15in but misses the status line, but OK for advanced users. setting 25 gives nominal 7 which is rather smaller.)
largeNominalSize = int(nominalSize*self.Label.winfo_screenheight()/approx_lines_per_screen_when_large/pixelSize)
else: self.after(100,self.check_window_position) # (needs to happen when window is already drawn if you want it to preserve the X co-ordinate)
except: pass # wrong font format or something - can't do it
if winCEsound and ask_teacherMode: self.Label["font"]="Helvetica 16" # might make it slightly easier
self.remake_cancel_button(localise("Cancel lesson"))
self.Cancel.focus() # (default focus if we don't add anything else, e.g. reader)
self.copyright_string = u"This is "+(u""+program_name).replace("(c)",u"\n\u00a9").replace("-",u"\u2013")
self.Version = Tkinter.Label(self.leftPanel,text=self.copyright_string)
if olpc: self.Version["font"]='Helvetica 9'
self.pollInterval = cond(winCEsound,300,100) # ms
self.startTime=time.time()
self.after(self.pollInterval,self.poll)
# and hide the console on Mac OS:
try: self.tk.call('console','hide')
except: pass
self.change_button_shown = 0
self.bind("<Leave>",self.restore_copyright)
self.bind("<FocusOut>",self.restore_copyright)
global recorderMode
if recorderMode:
if tkSnack: doRecWords()
else:
show_warning("Cannot do recorderMode because tkSnack library (python-tksnack) not installed")
recorderMode = 0
def remake_cancel_button(self,text=""): # sometimes need to re-make it to preserve tab order
self.CancelRow = addRow(self.leftPanel)
self.Cancel = addButton(self.CancelRow,text,self.cancel,{"side":"left"})
self.CancelRow.pack()
def set_statusline(self,text): # ONLY from callbacks
if GUI_omit_statusline or not hasattr(self,"ListBox"): return # status changes on main screen can cause too much jumping
if not "\n" in text: text += "\n(TODO: Make that a 2-line message)" # being 2 lines helps to reduce flashing problems. but don't want to leave 2nd line blank.
self.Version["text"] = text
if not winCEsound: self.balance_statusline,self.pollInterval = self.pollInterval,10
# self.Version["text"] = self.copyright_string
self.Version["text"] = "\n"
def restore_copyright(self,*args): self.Version["text"] = self.copyright_string
def addOrTestScreen_poll(self):
if hasattr(self,"balance_statusline"): # try to prevent flashing on some systems/languages due to long statusline causing window resize which then takes the mouse out of the button that set the long statusline etc
if self.Version.winfo_reqwidth() > self.ListBox.winfo_reqwidth(): self.ListBox["width"] = int(self.ListBox["width"])+1
else:
self.pollInterval = self.balance_statusline
del self.balance_statusline
self.sync_listbox_etc()
if self.ListBox.curselection():
if not self.change_button_shown:
self.ChangeButton.pack()
self.change_button_shown = 1
else:
if self.change_button_shown:
self.ChangeButton.pack_forget()
self.change_button_shown = 0
if self.toRestore:
if not hasattr(self,"restoreButton"): self.restoreButton = addButton(self.TestEtcCol,localise("Restore"),self.restoreText,status="This button will undo\nGradint's transliteration of the input")
elif hasattr(self,"restoreButton"):
self.restoreButton.pack_forget() ; del self.restoreButton
try:
if hasattr(self,"set_watch_cursor"):
self.config(cursor="watch") ; self.TestTextButton.config(cursor="watch")
del self.set_watch_cursor
if hasattr(self,"unset_watch_cursor"):
self.config(cursor="") ; self.TestTextButton.config(cursor="")
del self.unset_watch_cursor
except: pass # (if the Tk for some reason doesn't support them then that's OK)
def poll(self):
try:
if hasattr(self,"ListBox"): self.addOrTestScreen_poll()
if hasattr(self,"scriptVariant"):
v = self.scriptVariant.get()
if v: v=int(v)
else: v=0
if not v==scriptVariants.get(firstLanguage,0): self.setVariant(v)
if hasattr(self,"voiceOption") and not self.voiceOption.get()==voiceOption:
voiceOption=self.voiceOption.get() ; updateSettingsFile(settingsFile,{"voiceOption":voiceOption})
outTo = self.outputTo.get()
if hasattr(self,"lastOutTo") and self.lastOutTo==outTo: pass
else:
self.lastOutTo = outTo
if outTo: self.TestTextButton["text"]=localise("To")+" "+outTo.upper()
else: self.TestTextButton["text"]=localise("Speak")
# used to be called "Test" instead of "Speak", but some people didn't understand that THEY'RE doing the testing (not the computer)
if hasattr(self,"MakeLessonButton"):
if outTo: self.MakeLessonButton["text"]=localise("Make")+" "+outTo.upper()
else: self.MakeLessonButton["text"]=localise("Start lesson") # less confusing for beginners than "Make lesson", if someone else has set up the words
if hasattr(self,"BriefIntButton"):
if emergency_lessonHold_to < time.time(): t=localise("Brief interrupt")
else: t=localise("Resume")+" ("+str(int(emergency_lessonHold_to-time.time()))+")"
if not self.BriefIntButton["text"]==t:
self.BriefIntButton["text"]=t
if t==localise("Brief interrupt"): self.Label["text"]=localise("Resuming...")
if not self.todo.__dict__: return # can skip the rest
if hasattr(self.todo,"not_first_time"):
self.Cancel["text"] = "Stop lesson"
del self.todo.not_first_time
if hasattr(self.todo,"set_main_menu") and not recorderMode:
# set up the main menu (better do it on this thread just in case)
self.cancelling = 0 # in case just pressed "stop lesson" on a repeat - make sure Quit button will now work
self.CancelRow.pack_forget()
if self.todo.set_main_menu=="keep-outrow":
if hasattr(self,"OutputRow"): self.OutputRow.pack(fill=Tkinter.X,expand=1) # just done pack_forget in thindown
else:
if hasattr(self,"OutputRow"): self.OutputRow.pack_forget()
outRow = make_output_row(self.leftPanel)
if outRow: self.OutputRow=outRow
self.TestButton = addButton(self.leftPanel,localise(cond(self.wordsExist,"Manage word list","Create word list")),self.showtest) # used to be called "Add or test words", but "Manage word list" may be better for beginners. And it seems that "Create word list" is even better for absolute beginners, although it shouldn't matter if self.wordsExist is not always set back to 0 when it should be.
self.make_lesson_row()
if userNameFile:
global GUI_usersRow
# if GUI_usersRow: GUI_usersRow.pack() else: -- don't do this (need to re-create every time for correct tab order)
GUI_usersRow=addRow(self.leftPanel)
updateUserRow(1)
if hasattr(self,"bigPrintFont"):
self.BigPrintButton = addButton(self.leftPanel,localise("Big print"),self.bigPrint)
self.BigPrintButton["font"]=self.bigPrintFont
self.remake_cancel_button(localise("Quit"))
if not GUI_omit_statusline: self.Version.pack(fill=Tkinter.X,expand=1)
if olpc or self.todo.set_main_menu=="test" or GUI_for_editing_only: self.showtest() # olpc: otherwise will just get a couple of options at the top and a lot of blank space (no way to centre it)
if hasattr(self.todo,"alert"):
# we have to do it on THIS thread (especially on Windows / Cygwin; Mac OS and Linux might get away with doing it from another thread)
tkMessageBox.showinfo(self.master.title(),self.todo.alert)
del self.todo.alert
if hasattr(self.todo,"question"):
self.answer_given = tkMessageBox.askyesno(self.master.title(),self.todo.question)
del self.todo.question
if hasattr(self.todo,"set_label"):
self.Label["text"] = self.todo.set_label
del self.todo.set_label
if hasattr(self.todo,"thindown"):
self.thin_down_for_lesson()
self.setLabel(self.todo.thindown)
del self.todo.thindown
if hasattr(self.todo,"add_briefinterrupt_button") and runner:
self.BriefIntButton = addButton(self.CancelRow,localise("Brief interrupt"),self.briefInterrupt,{"side":"left"}) # on RHS of Cancel = reminescient of the stop and pause controls on a tape recorder
del self.todo.add_briefinterrupt_button
if hasattr(self.todo,"remove_briefinterrupt_button"):
if hasattr(self,"BriefIntButton"):
self.BriefIntButton.pack_forget() ; del self.BriefIntButton
elif hasattr(self.todo,"add_briefinterrupt_button"): del self.todo.add_briefinterrupt_button # cancel pressed while still making lesson
del self.todo.remove_briefinterrupt_button
if hasattr(self.todo,"clear_text_boxes"):
self.Text1.set("") ; self.Text2.set("") ; self.Entry1.focus()
del self.todo.clear_text_boxes
if hasattr(self.todo,"undoRecordFrom"):
theRecorderControls.undoRecordFrom()
del self.todo.undoRecordFrom
if hasattr(self.todo,"input_response"): # WMstandard
self.input_to_set.set(self.todo.input_response)
del self.todo.input_response,self.input_to_set
if hasattr(self.todo,"exit_ASAP"):
self.master.destroy()
self.pollInterval = 0
finally: # (try to make sure GUI exceptions at least don't stop the poll loop)
if self.pollInterval: self.after(self.pollInterval,self.poll)
def briefInterrupt(self,*args):
global emergency_lessonHold_to
if emergency_lessonHold_to:
emergency_lessonHold_to = 0
self.setLabel("")
elif finishTime-lessonLen + 20 >= time.time(): # (TODO customise the 20?)
global askAgain_explain
askAgain_explain = "A brief interrupt when you've only just started is never a good idea. "
self.cancel()
else:
emergency_lessonHold_to = time.time() + briefInterruptLength
self.setLabel(localise("Emergency brief interrupt"))
def make_lesson_row(self): # creates but doesn't pack. May need to re-make to preserve tab order. (Assumes any existing one is pack_forget)
words,mins = str(maxNewWords),cond(int(maxLenOfLesson/60)==maxLenOfLesson/60.0,str(int(maxLenOfLesson/60)),str(maxLenOfLesson/60.0))
if hasattr(self,"NumWords"): words=self.NumWords.get()
if hasattr(self,"Minutes"): mins=self.Minutes.get()
self.LessonRow = addRow(self.leftPanel)
if GUI_for_editing_only: return
self.NumWords,entry = addTextBox(self.LessonRow)
entry["width"]=2
addStatus(entry,"Limits the maximum number of NEW words\nthat are put in each lesson")
self.NumWords.set(words)
addLabel(self.LessonRow,localise(cond(fileExists(progressFile),"new ","")+"words in"))
self.Minutes,self.MinsEntry = addTextBox(self.LessonRow)
addStatus(self.MinsEntry,"Limits the maximum time\nthat a lesson is allowed to take")
self.MinsEntry["width"]=3
self.Minutes.set(mins)
addLabel(self.LessonRow,localise("mins"))
self.MakeLessonButton=addButton(self.LessonRow,localise("Start lesson"),self.makelesson,{"side":"left"},status="Press to create customized lessons\nusing the words in your collection")
self.lastOutTo=-1 # so it updates the Start Lesson button if needed
self.MakeLessonButton.bind('<FocusIn>',(lambda *args:app.after(10,lambda *args:app.MinsEntry.selection_clear())))
def sync_listbox_etc(self):
if not hasattr(self,"vocabList"):
if hasattr(self,"needVocablist"): return # already waiting for main thread to make one
while self.ListBox.get(0): self.ListBox.delete(0) # clear completely (otherwise the following would just do a least-disruptive update)
self.ListBox.insert(0,"Updating list from "+vocabFile+"...")
self.needVocablist=1
return
elif hasattr(self,"needVocablist"):
del self.needVocablist
self.ListBox.delete(0) # the Loading...
self.lastText1=1 # so continues below
text1,text2 = asUnicode(self.Text1.get()),asUnicode(self.Text2.get())
self.lastText1,self.lastText2 = text1,text2
if WMstandard and text1=="(Push OK to type A-Z)": text1=""
if text1 or text2: self.Cancel["text"] = localise(cond(self.ListBox.curselection(),"Cancel selection","Clear input boxes"))
else: self.Cancel["text"] = localise(cond(olpc or GUI_for_editing_only,"Quit","Back to main menu"))
h = hanzi_only(text1)
if Tk_might_display_wrong_hanzi and not self.Label1["text"].endswith(wrong_hanzi_message) and (h or hanzi_only(text2)): self.Label1["text"]+=("\n"+wrong_hanzi_message)
if h and not u"".join(fix_compatibility(text1).split())==hanzi_and_punc(text1):
# There is hanzi in the L2 text, but it's not all hanzi. This might mean they've pasted in a mixture of hanzi+pinyin from ruby markup (or maybe even hanzi+pinyin+English), so offer to trim it down to hanzi only. (Allow spacing differences.)
if not hasattr(self,"stripButton"): self.stripButton=addButton(self.TestEtcCol,localise("Delete non-hanzi"),self.stripText,status="If you pasted a mix of hanzi and\nother annotations, this can remove the annotations.")
elif hasattr(self,"stripButton"):
self.stripButton.pack_forget() ; del self.stripButton
if synthCache:
cacheManagementOptions = [] # (text, oldKey, newKey, oldFile, newFile)
for t,l in [(text1.encode('utf-8'),secondLanguage),(text2.encode('utf-8'),firstLanguage)]:
k,f = synthcache_lookup("!synth:"+t+"_"+l,justQueryCache=1)
if f:
if (partials_langname(l) in synth_partials_voices or get_synth_if_possible(l,0)): # (no point having these buttons if there's no chance we can synth it by any method OTHER than the cache)
if k in synthCache_transtbl and k[0]=="_": cacheManagementOptions.append(("Keep in "+l+" cache",k,k[1:],0,0))
elif k[0]=="_": cacheManagementOptions.append(("Keep in "+l+" cache",0,0,f,f[1:]))
if k in synthCache_transtbl: cacheManagementOptions.append(("Reject from "+l+" cache",k,"__rejected_"+k,0,0))
else: cacheManagementOptions.append(("Reject from "+l+" cache",0,0,f,"__rejected_"+f))
else:
k,f = synthcache_lookup("!synth:__rejected_"+t+"_"+l,justQueryCache=1)
if not f: k,f = synthcache_lookup("!synth:__rejected__"+t+"_"+l,justQueryCache=1)
if f:
if k in synthCache_transtbl: cacheManagementOptions.append(("Undo "+l+" cache reject",k,k[11:],0,0))
else: cacheManagementOptions.append(("Undo "+l+" cache reject",0,0,f,f[11:]))
elif l==secondLanguage and mp3web and not ';' in t: cacheManagementOptions.append(("Get from "+mp3webName,0,0,0,0))
if not hasattr(self,"cacheManagementOptions"):
self.cacheManagementOptions = []
self.cacheManagementButtons = []
if not cacheManagementOptions==self.cacheManagementOptions:
for b in self.cacheManagementButtons: b.pack_forget()
self.cacheManagementButtons = []
for txt,a,b,c,d in cacheManagementOptions: self.cacheManagementButtons.append(addButton(self.TestEtcCol,txt,lambda e=self,a=a,b=b,c=c,d=d:e.doSynthcacheManagement(a,b,c,d),status="This button is for synthCache management.\nsynthCache is explained in advanced"+extsep+"txt"))
if self.ListBox.curselection():
if not (text1 or text2): self.ListBox.selection_clear(0,'end') # probably just added a new word while another was selected (added a variation) - clear selection to reduce confusion
else: return # don't try to be clever with searches when editing an existing item (the re-ordering can be confusing)
text1,text2 = text1.lower().replace(" ",""),text2.lower().replace(" ","") # ignore case and whitespace when searching
l=map(lambda (x,y):x+"="+y, filter(lambda (x,y):text1 in x.lower().replace(" ","") and text2 in y.lower().replace(" ",""),self.vocabList)[-tkNumWordsToShow:])
l.reverse() ; synchronizeListbox(self.ListBox,l) # show in reverse order, in case the bottom of the list box is off-screen
def doSynthcacheManagement(self,oldKey,newKey,oldFname,newFname):
# should be a quick operation - might as well do it in the GUI thread
if (oldKey,oldFname) == (0,0): # special for mp3web
self.menu_response="mp3web" ; return
if oldKey in synthCache_transtbl:
if newKey: synthCache_transtbl[newKey]=synthCache_transtbl[oldKey]
else: del synthCache_transtbl[oldKey]
open(synthCache+os.sep+transTbl,'w').write("".join([v+" "+k+"\n" for k,v in synthCache_transtbl.items()]))
if oldFname:
del synthCache_contents[oldFname]
if newFname:
os.rename(synthCache+os.sep+oldFname,synthCache+os.sep+newFname)
synthCache_contents[newFname]=1
else: os.remove(synthCache+os.sep+oldFname)
self.lastText1 = 1 # ensure different so cache-management options get updated
def restoreText(self,*args):
for control,current,restoreTo in self.toRestore:
if asUnicode(control.get())==current: control.set(restoreTo)
def stripText(self,*args): self.Text1.set(fix_commas(hanzi_and_punc(asUnicode(self.Text1.get()))))
def thin_down_for_lesson(self):
if hasattr(self,"OutputRow"): self.OutputRow.pack_forget()
if hasattr(self,"CopyFromButton"):
self.CopyFromButton.pack_forget() ; del self.CopyFromButton
self.LessonRow.pack_forget()
if GUI_usersRow: GUI_usersRow.pack_forget()
if hasattr(self,"BigPrintButton"):
self.BigPrintButton.pack_forget() ; del self.BigPrintButton
if hasattr(self,"TestButton"): self.TestButton.pack_forget()
else:
for i in [self.row1,self.row2,self.row3,self.row4,self.ListBox,self.rightPanel]: i.pack_forget()
if hasattr(self,"alternateRightPanel"):
self.alternateRightPanel.pack_forget()
del self.alternateRightPanel
if self.change_button_shown:
self.ChangeButton.pack_forget()
self.change_button_shown = 0
del self.ListBox # so doesn't sync lists, or assume Cancel button is a Clear button
if hasattr(self,"cacheManagementButtons"):
for b in self.cacheManagementButtons: b.pack_forget()
del self.cacheManagementButtons,self.cacheManagementOptions
app.master.title(appTitle)
self.CancelRow.pack_forget() ; self.Version.pack_forget()
self.Label.pack() ; self.CancelRow.pack()
self.Label["text"] = "Working..." # (to be replaced by time indication on real-time, not on output-to-file)
self.Cancel["text"] = localise("Quit")
self.master.option_add('*Scrollbar*width',int(16*self.bigPrintMult)) # (works on some systems; usually ineffective on Mac)
self.Label["font"]=self.bigPrintFont
del self.bigPrintFont # (TODO do we want an option to undo it? or would that take too much of the big print real-estate.)
def bigPrint(self,*args):
self.thin_down_for_lesson()
self.Version["font"]=self.bigPrintFont
self.bigPrint0()
if self.rightPanel: # oops, need to re-construct it
global extra_buttons_waiting_list
extra_buttons_waiting_list = []
make_extra_buttons_waiting_list()
self.rightPanel = None
self.check_window_position()
self.todo.set_main_menu = 1
def check_window_position(self,*args): # called when likely to be large print and filling the screen
try: self.master.geometry("+"+str(int(self.winfo_rootx()))+"+0")
except: pass
def makelesson(self,*args):
if hasattr(self,"userNo"): select_userNumber(intor0(self.userNo.get())) # in case some race condition stopped that from taking effect before (e.g. WinCE)
try: numWords=int(self.NumWords.get())
except:
self.todo.alert = localise("Error: maximum number of new words must be an integer") ; return
self.todo.alert = localise("Error: minutes must be a number") ; return
problem=0 # following message boxes have to be resistant to "I'm just going to click 'yes' without reading it" users who subsequently complain that Gradint is ineffective. Make the 'yes' option put them back into the parameters, and provide an extra 'proceed anyway' on 'no'.
if numWords>=10:
if tkMessageBox.askyesno(self.master.title(),localise("%s new words is a lot to remember at once. Reduce to 5?") % (str(numWords),)):
numWords=5 ; self.NumWords.set("5")
else: problem=1
if mins>30:
if tkMessageBox.askyesno(self.master.title(),localise("More than 30 minutes is rarely more helpful. Reduce to 30?")):
mins=30;self.Minutes.set("30")
else: problem=1
if mins<20:
if tkMessageBox.askyesno(self.master.title(),localise("Less than 20 minutes can be a rush. Increase to 20?")):
mins=20;self.Minutes.set("20")
else: problem=1
if problem and not tkMessageBox.askyesno(self.master.title(),localise("Proceed anyway?")): return
global maxNewWords,maxLenOfLesson
d={}
if not maxNewWords==numWords: d["maxNewWords"]=maxNewWords=numWords
if not maxLenOfLesson==int(mins*60): d["maxLenOfLesson"]=maxLenOfLesson=int(mins*60)
if d: updateSettingsFile("advanced"+dottxt,d)
self.thin_down_for_lesson()
self.Cancel["text"] = localise("Cancel lesson")
self.menu_response = "go"
def showtest(self,*args): # Can assume main menu is shown at the moment.
title = localise(cond(self.wordsExist,"Manage word list","Create word list"))
if hasattr(self,"userNo"):
try: uname = lastUserNames[intor0(self.userNo.get())]
except IndexError: uname="" # can happen if it's 0 but list is empty
if uname:
title += (": "+uname)
select_userNumber(intor0(self.userNo.get())) # in case some race condition stopped that from taking effect before (e.g. WinCE)
app.master.title(title)
if hasattr(self,"BigPrintButton"):
self.BigPrintButton.pack_forget() ; del self.BigPrintButton
self.TestButton.pack_forget() ; del self.TestButton
for i in [self.LessonRow,self.CancelRow,self.Version]: i.pack_forget()
if GUI_usersRow: GUI_usersRow.pack_forget()
self.row1 = addRow(self.leftPanel,1)
self.row2 = addRow(self.leftPanel,1)
self.row3 = addRow(self.leftPanel)
self.row4 = addRow(self.leftPanel,1)
self.Label1,self.Text1,self.Entry1 = addLabelledBox(self.row1,True)
self.TestEtcCol = addRow(self.row1) # effectively adding a column to the end of the row, for "Speak" and any other buttons to do with 2nd-language text (although be careful not to add too many due to tabbing)
self.TestTextButton = addButton(self.TestEtcCol,"",self.testText,status="Use this button to check how the computer\nwill pronounce words before you add them") # will set text in updateLanguageLabels
self.Label2,self.Text2,self.Entry2 = addLabelledBox(self.row2,True)
if not WMstandard:
self.Entry1.bind('<Return>',self.testText)
self.Entry1.bind('<F5>',self.debugText)
self.Entry2.bind('<Return>',self.addText)
for e in [self.Entry1,self.Entry2]: addStatus(e,"Enter a word or phrase to add or to test\nor to search your existing collection",mouseOnly=1)
self.AddButton = addButton(self.row2,"",self.addText,status="Adds the pair to your vocabulary collection\nor adds extra revision if it's already there") # will set text in updateLanguageLabels
self.L1Label,self.L1Text,self.L1Entry = addLabelledBox(self.row3,status="The abbreviation of your\nfirst (i.e. native) language")
self.L2Label,self.L2Text,self.L2Entry = addLabelledBox(self.row3,status="The abbreviation of the other\nlanguage that you learn most")
self.L1Entry.bind('<Return>',lambda e:e.widget.after(10,lambda e=e:e.widget.event_generate('<Tab>')))
self.L2Entry.bind('<Return>',self.changeLanguages)
for e in [self.L1Entry,self.L2Entry]: e.bind('<Button-1>',(lambda e:e.widget.after(10,lambda e=e:selectAll(e))))
self.ChangeLanguageButton = addButton(self.row3,"",self.changeLanguages,status="Use this button to set your\nfirst and second languages") # will set text in updateLanguageLabels
self.ChangeLanguageButton.bind('<FocusIn>',(lambda *args:app.after(10,lambda *args:app.L2Entry.selection_clear())))
self.AddButton.bind('<FocusIn>',(lambda *args:app.after(10,lambda *args:app.L1Entry.selection_clear()))) # for backwards tabbing
if GUI_omit_settings and (vocabFile==user0[1] or fileExists(vocabFile)): self.row3.pack_forget()
self.RecordedWordsButton = addButton(self.row4,"",self.showRecordedWords,{"side":"left"},status="This button lets you manage recorded\n(as opposed to computer-voiced) words")
row4right = addRightRow(self.row4)
self.EditVocabButton = addButton(row4right,"",self.openVocabFile,{"side":"left"},status="This button lets you edit your\nvocab collection in "+textEditorName)
if not GUI_omit_settings: addButton(row4right,"advanced"+dottxt,self.openAdvancedTxt,{"side":"left"},status="Press this button to change voices,\nlearn multiple languages, etc")
else: # no text editor, but can at least have Recorded Words button now we have a built-in manager
self.make_lesson_row()
self.RecordedWordsButton = addButton(self.LessonRow,"",self.showRecordedWords,{"side":"right"},status="This button lets you manage recorded\n(as opposed to computer-voiced) words")
if textEditorCommand and lastUserNames and lastUserNames[0]: self.CopyFromButton = addButton(cond(GUI_omit_settings,row4right,self.LessonRow),localise("Copy from..."),self.showCopyFrom,{"side":"left"},status="This button lets you copy recorded\nand computer-voiced words from other users") # TODO if not textEditorCommand then only reason why can't have this is row4right won't be defined, need to fix that (however probably don't want to bother on XO etc)
self.remake_cancel_button(localise(cond(olpc or GUI_for_editing_only,"Quit","Back to main menu")))
self.ChangeButton = addButton(self.CancelRow,"",self.changeItem,{"side":"left"},status="Press to alter or to delete\nthe currently-selected word in the list") ; self.ChangeButton.pack_forget() # don't display it until select a list item
self.updateLanguageLabels()
self.LessonRow.pack() ; self.CancelRow.pack()
self.ListBox = Tkinter.Listbox(self.leftPanel, takefocus=0) # TODO takefocus=0 for now. bindUpDown? but up/down/left/right also need to work IN the listbox, this could be tricky. Also need to populate the input boxes when on the list box.
self.ListBox.bind('<ButtonRelease-1>', self.getListItem)
self.ListBox.bind('<ButtonRelease-3>', self.wrongMouseButton)
addStatus(self.ListBox,"This is your collection of computer-voiced words.\nClick to hear, change or remove an item.")
self.ListBox["width"]=1 # so it will also squash down if window is narrow
if winCEsound: self.ListBox["font"]="Helvetica 12" # larger is awkward, but it doesn't have to be SO small!
elif macsound and Tkinter.TkVersion>=8.6: self.ListBox["font"]=cond(hasattr(self,"isBigPrint"),"System 20","System 16") # 16 ok with magnification, clearer than 13
self.ListBox.pack(fill=Tkinter.X,expand=1) # DON'T fill Y as well, because if you do we'll have to implement more items, and that could lose the clarity of incremental search
if not GUI_omit_statusline: self.Version.pack(fill=Tkinter.X,expand=1)
self.lastText1,self.lastText2=1,1 # (different from empty string, so it sync's)
if not self.rightPanel:
self.rightPanel = Tkinter.Frame(self)
for i in range(min(max_extra_buttons,len(extra_buttons_waiting_list))): self.add_extra_button()
if not hasattr(self,"userNo") or not intor0(self.userNo.get()): self.rightPanel.pack({"side":"left"})
elif self.extra_button_callables:
self.alternateRightPanel = Tkinter.Frame(self)
self.alternateRightPanel.pack({"side":"left"})
curWidth = self.winfo_width()
addLabel(self.alternateRightPanel,"Only the first user can access the preset collections, but you can copy the vocab lists and recordings from each other once you've added them.")["wraplength"]=int(curWidth/2) # (presets are not really compatible with multiple users, unless re-write for copy-and-track-what's-done, which would take double the disk space on a one-person setup AND would have trouble upgrading existing users who have started into their presets)
self.Entry1.focus()
def add_extra_button(self):
global extra_buttons_waiting_list
extra_buttons_waiting_list[0].add()
extra_buttons_waiting_list = extra_buttons_waiting_list[1:]
def openVocabFile(self,*args): self.fileToEdit, self.menu_response = vocabFile,"edit"
def openAdvancedTxt(self,*args): self.fileToEdit, self.menu_response = "advanced"+dottxt,"edit"
def showCopyFrom(self,*args):
m=Tkinter.Menu(None, tearoff=0, takefocus=0)
for i in range(len(lastUserNames)):
if lastUserNames[i] and not i==intor0(self.userNo.get()):
if fileExists(addUserToFname(user0[1],i)): m.add_command(label=u"Copy vocab list from "+lastUserNames[i],command=(lambda e=None,i=i:self.copyVocabFrom(i)))
m.add_command(label=u"Copy recordings to/from "+lastUserNames[i],command=(lambda e=None,i=i:self.setToOpen((addUserToFname(user0[0],i),addUserToFname(user0[0],intor0(self.userNo.get()))))))
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
m.tk_popup(self.CopyFromButton.winfo_rootx(),self.CopyFromButton.winfo_rooty(),entry="0")
def setToOpen(self,toOpen): self.menu_response,self.toOpen = "samplesCopy",toOpen
def copyVocabFrom(self,userNo):
# Copy any NEW vocab lines (including comments). TODO could also insert them in the right place (like 'diff' without the deletions)
select_userNumber(userNo,updateGUI=0)
vCopyFrom = vocabLinesWithLangs()
select_userNumber(intor0(self.userNo.get()),updateGUI=0)
vCurrent = list2set(vocabLinesWithLangs())
o=appendVocabFileInRightLanguages()
langs = (secondLanguage,firstLanguage)
for newLangs,line in vCopyFrom:
if (newLangs,line) in vCurrent: continue # already got it
if not newLangs==langs: o.write("SET LANGUAGES "+" ".join(list(newLangs))+"\n")
o.write(line+"\n")
langs = newLangs
o.close()
if hasattr(self,"vocabList"): del self.vocabList # re-read
def setVariant(self,v):
scriptVariants[firstLanguage] = v
updateSettingsFile(settingsFile,{"scriptVariants":scriptVariants})
if hasattr(self,"TestButton"):
self.thin_down_for_lesson()
self.todo.set_main_menu="keep-outrow"
else: self.updateLanguageLabels()
def changeLanguages(self,*args):
global firstLanguage,secondLanguage
firstLanguage1=asUnicode(self.L1Text.get()).encode('utf-8')
secondLanguage1=asUnicode(self.L2Text.get()).encode('utf-8')
if (firstLanguage,secondLanguage) == (firstLanguage1,secondLanguage1): # they didn't change anything
langs = ESpeakSynth().describe_supported_languages()
msg = (localise("To change languages, edit the boxes that say '%s' and '%s', then press the '%s' button.") % (firstLanguage,secondLanguage,localise("Change languages")))+"\n\n"+localise("Recorded words may be in ANY languages, and you may choose your own abbreviations for them. However if you want to use the computer voice for anything then please use standard abbreviations.")
if langs:
if tkMessageBox.askyesno(self.master.title(),msg+" "+localise("Would you like to see a list of the standard abbreviations for languages that can be computer voiced?")): self.todo.alert = localise("Languages with computer voices (some better than others):")+"\n"+langs
else: self.todo.alert = msg+" "+localise("(Sorry, a list of these is not available on this system - check eSpeak installation.)")
return
need_redisplay = "@variants-"+GUI_languages.get(firstLanguage,firstLanguage) in GUI_translations or "@variants-"+GUI_languages.get(firstLanguage1,firstLanguage1) in GUI_translations # if EITHER old or new lang has variants, MUST reconstruct that row. (TODO also do it anyway to get the "Speaker" etc updated? but may cause unnecessary flicker if that's no big problem)