diff --git a/.gitignore b/.gitignore index 6175a33f..71ba940a 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,5 @@ MacroSystem/*_vcl.py # Visual Studio .vs +/PyTest/readwritefiletest/*.txt +/PyTest/readwritefiletest/*out.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index a1d63e46..ba088d61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ add_subdirectory ("NatlinkSource") # Build the NatlinkModule, that is, depend on the previous step and # make sure that the Python sources and such are fresh. Mark this # situation as a file NatlinkModule/NatlinkModule.STAMP. Similarly, mark -# mark the up-to-dateness of the tests as NatlinkModule/InstallTest.STAMP +# mark the up-to-dateness of the tests as NatlinkModule/DefaultConfig.STAMP add_subdirectory ("NatlinkModule") # build two # Now, using dependcy on the stamps, start the inno installation generator add_subdirectory ("InstallerSource") diff --git a/InstallerSource/CMakeLists.txt b/InstallerSource/CMakeLists.txt index 218decf5..164cd794 100644 --- a/InstallerSource/CMakeLists.txt +++ b/InstallerSource/CMakeLists.txt @@ -16,7 +16,7 @@ string(REPLACE "." "" PYTHON_VERSION_NO_DOT ${PYTHON_VERSION}) add_custom_command(OUTPUT "${INSTALLER_FILE_BASE}.exe" DEPENDS StampDriver # otherwise the stamps are not checked ${PROJECT_BINARY_DIR}/NatlinkModule/NatlinkModule.STAMP - ${PROJECT_BINARY_DIR}/NatlinkModule/InstallTest.STAMP + ${PROJECT_BINARY_DIR}/NatlinkModule/DefaultConfig.STAMP inno-setup-natlink.iss inno-code.iss COMMENT "Now Building Installer" COMMAND ${ISCC} diff --git a/InstallerSource/inno-setup-natlink.iss b/InstallerSource/inno-setup-natlink.iss index 3477f815..6079323b 100644 --- a/InstallerSource/inno-setup-natlink.iss +++ b/InstallerSource/inno-setup-natlink.iss @@ -51,8 +51,8 @@ Source: "{#CoreDir}\_natlink_core{code:GetDragonVersion}.pyd"; DestDir: "{#CoreD Source: "{code:GetPythonInstallPath}\python{#PythonVersionNoDot}.dll"; DestDir: "{#CoreDir}"; \ Flags: external ignoreversion; -; InstallTest -Source: "{#SourceRoot}\NatlinkModule\InstallTest\*"; DestDir: "{app}\InstallTest"; Flags: ignoreversion +; DefaultConfig +Source: "{#SourceRoot}\NatlinkModule\DefaultConfig\*"; DestDir: "{app}\DefaultConfig"; Flags: ignoreversion [Icons] diff --git a/NatlinkConfigure/README.md b/NatlinkConfigure/README.md new file mode 100644 index 00000000..ef3c69a2 --- /dev/null +++ b/NatlinkConfigure/README.md @@ -0,0 +1,29 @@ +The natlinkconfigfunctions.py is now a very much simplified version of the python2 version. + +This is because the natlink installer program handles the enabling of Natlink itself. Elevated mode is +no longer needed. + + + +With the GUI (configurenatlink.pyw) you can configure NatLink, Vocola and Unimacro, but also DragonFly, via the UserDirectory. + +This program is written in wxPython. + +The definitions are made with the (nonfree) program: wxDesigner (see http://www.roebling.de) + +The definition file for wxDesigner is called configurenatlink.wdr, these definitions that were +translated by wxDesigner into configurenatlink_wdr.py, and it is this file that is used by configurenatlink.pyw. + +It is unwise to edit these 2 files by hand, as they will be regenerated if a new wxDesigner run is done. + +The program configurenatlink.pyw uses uses functions from the module: natlinkconfigfunctions.py. + +If the GUI program doesn't work for some reason, you can fall back to +the command line interface, which is contained in +natlinkconfigfunctions.py. Just start this program from the start +menu or the folder that you are in now, preferably in elevated mode too with start_natlinkconfigfunctions.py + +Quintijn Hoogenboom, February 18, 2008, (...)April 2022 (python3) + + + diff --git a/NatlinkConfigure/configurenatlink.pyw b/NatlinkConfigure/configurenatlink.pyw new file mode 100644 index 00000000..ef237fac --- /dev/null +++ b/NatlinkConfigure/configurenatlink.pyw @@ -0,0 +1,1377 @@ +#!/bin/env python +# +# confignatlinkvocolaunimacro.py +# This module does the natlinkconfigfunctions through a +# wxPython GUI +# +# (C) Copyright Quintijn Hoogenboom, 2008-2009 +# +#---------------------------------------------------------------------------- + +try: + import wx +except ImportError: + print('Unable to run the GUI installer because module wx was not found. This probably') + print('means that wxPython is not installed.') + + print() + print('Either install wxPython (recommended) or use the CLI (Command Line Interface)') + print('Natlink configuration program.') + print() + print('A version of wxPython suitable for use with Natlink can be obtained from') + print('http://sourceforge.net/project/showfiles.php?group_id=70807&package_id=261793') + print('(each package there contains a version of wxPython; get the one appropriate') + print(' to the version of Python you have installed.)') + print() + while True: + pass + raise + +try: + from win32ui import MessageBox + def windowsMessageBox(message, title="Natlink configure program"): + """do messagebox from windows, no wx needed + """ + MessageBox(message, title) +except: + import ctypes + MessageBoxA = ctypes.windll.user32.MessageBoxA + def windowsMessageBox(message, title="Natlink configure program"): + """do messagebox from windows, no wx needed + for old versions of python + """ + MessageBoxA(None, message, title, 0) + +import os +import sys +if sys.version[0] == '2' and sys.version[2] in ['3', '5']: + pyVersion = sys.version[:3] + mess = ["Warning, this Configure Natlink GUI possibly does not work for this old python version: %s."% pyVersion, + "Also the natlink.pyd files (natlink.dll) that work with python %s are for older versions of NatSpeak (10 and before) only."% pyVersion, + 'Now the good news: you can still use Natlink for NatSpeak 10 and before even with this python version.', + 'But if the Configure Natlink GUI does not work, you should do the configuration via "natlinkconfigfunctions.py".', + 'Please start that program, preferably in elevated mode via "start_configurenatlinkfunctions.py" or via "Configure Natlink via Command Line Interface" in the start menu.', + "For Dragon 11 and later, some things may work, but it is better to upgrade to Python 2.6 or 2.7"] + + mess = '\n\n'.join(mess) + windowsMessageBox(mess) + +import sys +import traceback +from configurenatlink_wdr import * +import os +import os.path +import copy +import types +from natlinkconfigfunctions import ElevationError +# nf: natlinkinstallfunctions, imported at end of init... +nf = None +nc = None # natlinkcorefunctions +# WDR: classes + +class DialogUnimacroVocolaCompatibiliy(wx.Dialog): + def __init__(self, parent, title=None): + parent = parent + id = -1 + pos = wx.DefaultPosition + title = title or "Unimacro/Vocola compatibility" + wx.Dialog.__init__(self, parent, id, title, pos) + # WDR: dialog function YesNoAbort for MyYesNoAbortDialog + + + # WDR: dialog function DialogVocolaCombatibility for DialogUnimacroVocolaCompatibiliy + DialogVocolaCombatibility( self, True ) + + # WDR: handler declarations for DialogUnimacroVocolaCompatibiliy + # wx.EVT_CHECKBOX(self, ID_IncludeUnimacroInPythonPath, self.OnIncludeUnimacroInPythonPath) + self.Bind(wx.EVT_BUTTON, self.OnCancel, id=ID_BUTTONCancel) + self.Bind(wx.EVT_BUTTON, self.OnOK, id=ID_BUTTONOK) + + + # WDR: methods for DialogUnimacroVocolaCompatibiliy + + # def GetIncludeunimacroinpythonpath(self): + # return self.FindWindowById( ID_IncludeUnimacroInPythonPath ) + + def GetCheckboxremoveunimacroincludelines(self): + return self.FindWindowById( ID_CHECKBOXRemoveUnimacroIncludeLines ) + + def GetCheckboxmakeunimacroincludelines(self): + return self.FindWindowById( ID_CHECKBOXMakeUnimacroIncludeLines ) + + def GetCheckboxrefreshunimacrovch(self): + return self.FindWindowById( ID_CHECKBOXRefreshUnimacroVch ) + + def GetCheckboxvocolatakesunimacroactions(self): + return self.FindWindowById( ID_CHECKBOXVocolaTakesUnimacroActions ) + + # WDR: handler implementations for DialogUnimacroVocolaCompatibiliy + + # def OnIncludeUnimacroInPythonPath(self, event): + # pass + + def OnOK(self, event): + code = 0 + if self.GetCheckboxremoveunimacroincludelines().GetValue(): + code += 2 + if self.GetCheckboxmakeunimacroincludelines().GetValue(): + code += 4 + if self.GetCheckboxrefreshunimacrovch().GetValue(): + code += 1 + + + self.SetReturnCode(code) + self.Destroy() + + def OnCancel(self, event): + self.SetReturnCode(0) + self.Destroy() + + +class InfoPanel(wx.Panel): + def __init__(self, parent, id, name="infopanel", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.TAB_TRAVERSAL ): + self.frame = parent.frame + wx.Panel.__init__(self, parent, id, pos, size, style) + # WDR: dialog function InfoWindow for infopanel + InfoWindow( self, True ) + # WDR: handler declarations for infopanel + self.Bind(wx.EVT_BUTTON, self.OnButtonHelpInfo, id=ID_BUTTONHelpInfo) + self.Bind(wx.EVT_BUTTON, self.OnButtonClearDNSInifilePath, id=ID_BUTTONClearDNSInifilePath) + self.Bind(wx.EVT_BUTTON, self.OnButtonChangeDNSInifilePath, id=ID_BUTTONchangednsinifilepath) + self.Bind(wx.EVT_BUTTON, self.OnButtonClearDNSInstallPath, id=ID_BUTTONClearDNSInstallPath) + self.Bind(wx.EVT_BUTTON, self.OnButtonChangeDNSInstallPath, id=ID_BUTTONchangednsinstallpath) + self.Bind(wx.EVT_BUTTON, self.OnButtonLogInfo, id=ID_BUTTONLogInfo) + + # WDR: methods for infopanel + def GetTextctrlpythonversion(self): + return self.FindWindowById( ID_TEXTCTRLpythonversion ) + + def GetTextctrlwindowsversion(self): + return self.FindWindowById( ID_TEXTCTRLWindowsVersion ) + + def GetTextctrlnatlinkcorepath(self): + return self.FindWindowById( ID_TEXTCTRLnatlinkcorepath ) + + def GetTextctrldnsinifilepath(self): + return self.FindWindowById( ID_TEXTCTRLdnsinifilepath ) + + def GetTextctrldnsinstallpath(self): + return self.FindWindowById( ID_TEXTCTRLDNSinstallpath ) + + def GetTextctrldnsversion(self): + return self.FindWindowById( ID_TEXTCTRLDNSVersion ) + + def GetTextctrlpythonversion(self): + return self.FindWindowById( ID_TEXTCTRLpythonversion ) + + def GetTextctrlwindowsversion(self): + return self.FindWindowById( ID_TEXTCTRLWindowsVersion ) + + def GetTextctrlnatlinkcorepath(self): + return self.FindWindowById( ID_TEXTCTRLnatlinkcorepath ) + + + # WDR: handler implementations for infopanel + + def OnButtonClearDNSInifilePath(self, event): + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'C', 'c' + old_path = D['DNSIniDir'] + if old_path and os.path.isdir(old_path): + undoCmd = (undoLetter, old_path) + else: + self.setstatus("DNSIniDir was NOT set, so no action needed") + return + + statustext = 'DNSIniDir is Cleared, search (again) for default.' + result = self.do_command(doLetter, undo=undoCmd) + if result: + self.setstatus(result) + else: + self.setstatus(statustext) + self.cpanel.setInfo(leaveStatus=1) + + def OnButtonChangeDNSInifilePath(self, event): + # ask for the correct directory: + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'c', 'C' + undoCmd = (undoLetter,) + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) +## style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON) + ## search for Unimacro directory as proposal: + Path = D['DNSIniDir'] + if Path == -1: + Path = nc.getExtendedEnv("COMMON_APPDATA") + elif not Path: + Path = nc.getExtendedEnv("COMMON_APPDATA") + elif not os.path.isdir(Path): + Path = nc.getExtendedEnv("COMMON_APPDATA") + + dlg.SetPath(Path) + dlg.SetMessage('Please specify the directory where the DNS INI files are located') + statustext = 'DNS INI file location is changed' + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + else: + self.setstatus("nothing specified") + return + result = self.do_command(doLetter,new_path, undo=undoLetter) + if result: + self.setstatus(result) + else: + self.setstatus("DNSIniDir changed") + self.cpanel.setInfo(leaveStatus=1) + + + def OnButtonClearDNSInstallPath(self, event): + + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'D', 'd' + old_path = D['DNSInstallDir'] + if old_path and os.path.isdir(old_path): + undoCmd = (undoLetter, old_path) + else: + self.setstatus("DNSInstallDir was NOT set, so no action needed") + return + + statustext = 'DNSInstallDir is Cleared, search (again) for default.' + result = self.do_command(doLetter, undo=undoCmd) + if result: + self.setstatus(result) + else: + self.setstatus(statustext) + self.cpanel.setInfo(leaveStatus=1) + + def OnButtonChangeDNSInstallPath(self, event): + # ask for the correct directory: + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'd', 'D' + undoCmd = (undoLetter,) + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) +## style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON) + ## search for Unimacro directory as proposal: + Path = D['DNSInstallDir'] + if Path == -1: + Path = nc.getExtendedEnv("PROGRAMFILES") + elif not Path: + Path = nc.getExtendedEnv("PROGRAMFILES") + elif not os.path.isdir(Path): + Path = nc.getExtendedEnv("PROGRAMFILES") + + dlg.SetPath(Path) + dlg.SetMessage('Please specify the directory where DNS is installed') + statustext = 'DNS Install directory is changed' + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + else: + self.setstatus("nothing specified") + return + result = self.do_command(doLetter,new_path, undo=undoLetter) + if result: + self.setstatus(result) + else: + self.setstatus("DNSInstallDir changed") + self.cpanel.setInfo(leaveStatus=1) + + def OnButtonLogInfo(self, event): + self.cpanel.cli.do_i("dummy") + self.cpanel.cli.do_I("dummy") + self.cpanel.warning("See the info from natlinkstatus in the log panel") + + def OnButtonHelpInfo(self, event): + print('---help on DNS Install Directory:') + print('note the letters correspond to the commands in the self.cli (command line interface)') + cli = self.cpanel.cli + cli.help_d() + print('---help on DNS INI files Directory:') + cli.help_c() + text = \ +"""This info panel has no undo function, like the config panel, +but clearing the settings falls back to NatSpeak defaults + +The button Log info gives the complete natlinkstatus info in the log panel + +See more help information in the log panel""" + self.cpanel.warning(text) + + def setstatus(self, text): + """put message on log panel and on status line""" + #print text + self.frame.SetStatusText(text) + + def do_command(self, *args, **kw): + """a single letter, optionally followed by a path + for infopanel, no undo things, simply ignore + + when calling from undo button, provide 'noundo' = 1 as keyword argument. + + + """ + if len(args) < 1: + print('empty command %s'% repr(args)) + return + if len(args) > 2: + print('too many posional arguments: %s'% repr(args)) + return + letter = args[0] + if len(args) == 2: + pathName = args[1] + else: + pathName = 'dummy' + funcName = 'do_%s'% letter + cli = self.cpanel.cli + func = getattr(cli, funcName, None) + + if not func: + mess = 'invalid command: %s'% letter + print(mess) + return mess + try: + result = func(pathName) + except ElevationError: + e = sys.exc_info()[1] + + mess = 'This program should run in elevated mode (%s).'% e.message + self.error(mess) + mess2 = mess = '\n\nPlease Close and run via start_configurenatlink.py\n\nPlease close Dragon too.' + windowsMessageBox(mess) + self.cpanel.setInfo() + return result + + +class ConfigureNatlinkPanel(wx.Panel): + def __init__(self, parent, id, name="configurepanel", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.TAB_TRAVERSAL ): + global nf, nc # natlinkconfigfunctions, self.cli, natlinkcorefunctions + wx.Panel.__init__(self, parent, id, pos, size, style) + self.parent = parent + self.frame = parent.frame + # WDR: dialog function MainWindow for configurenatlink + MainWindow( self, True ) + + # WDR: handler declarations for configurenatlink + self.Bind(wx.EVT_CHECKBOX, self.OnCBVocolaTakesUnimacroActions, id=ID_CHECKBOXVocolaTakesUnimacroActions) + # wx.EVT_CHECKBOX(self, ID_IncludeUnimacroInPythonPath, self.OnButtonIncludeUnimacroInPythonPath) + self.Bind(wx.EVT_BUTTON, self.OnButtonVocolaCompatibility, id=ID_BUTTONVocolaCompatibiliy) + self.Bind(wx.EVT_BUTTON, self.OnButtonUnimacroEditor, id=ID_BUTTONUnimacroEditor) + self.Bind(wx.EVT_BUTTON, self.OnButtonUnimacroEnableDisable, id=ID_BUTTONUnimacroEnable) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp5, id=ID_BUTTONHelp5) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp1, id=ID_BUTTONHelp1) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp4, id=ID_BUTTONHelp4) + #wx.EVT_CHECKBOX(self, ID_CHECKBOXNatlinkDebug, self.OnCBNatlinkDebug) + self.Bind(wx.EVT_BUTTON, self.OnButtonClose, id=ID_BUTTONClose) + self.Bind(wx.EVT_BUTTON, self.OnButtonUndo, id=ID_BUTTONUndo) + self.Bind(wx.EVT_BUTTON, self.OnButtonUserEnableDisable, id=ID_BUTTONUserEnable) + self.Bind(wx.EVT_BUTTON, self.OnButtonVocolaEnableDisable, id=ID_BUTTONVocolaEnable) + self.Bind(wx.EVT_BUTTON, self.OnButtonNatlinkEnableDisable, id=ID_BUTTONNatlinkEnable) + self.Bind(wx.EVT_CHECKBOX, self.OnCBVocolaTakesLanguages, id=ID_CHECKBOXVocolaTakesLanguages) + self.Bind(wx.EVT_CHECKBOX, self.OnCBDebugCallback, id=ID_CHECKBOXDebugCallbackOutput) + self.Bind(wx.EVT_CHECKBOX, self.OnCBDebugLoad, id=ID_CHECKBOXDebugLoad) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp3, id=ID_BUTTONHelp3) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp2, id=ID_BUTTONHelp2) + + self.Bind(wx.EVT_BUTTON, self.OnButtonUnregister, id=ID_BUTTONunregister) + self.Bind(wx.EVT_BUTTON, self.OnButtonRegister, id=ID_BUTTONregister) + + try: + nf = __import__('natlinkconfigfunctions') + except: + + self.error('natlinkconfigfunctions import failed') + return + + class NatlinkConfigGUI(nf.NatlinkConfig): + def __init__(self, parent=None): + self.parent = parent + super(NatlinkConfigGUI, self).__init__() + def warning(self, text): + """overload, to make it also in GUI visible""" + super(NatlinkConfigGUI, self).warning(text) + #self.parent.warning(text) + + self.firstThaw = True # set to true first time and at undo action... + self.GUI = NatlinkConfigGUI(parent=self) + error = 0 + try: + self.cli = nf.CLI(self.GUI) + except ElevationError: + e = sys.exc_info()[1] + + mess = 'This program should run in elevated mode (%s).'% e.message + self.error(mess) + mess += '\n\nPlease Close and run via start_configurenatlink.pyw' + windowsMessageBox(mess) + error = 1 + except: + self.error('could not start CLI instance') + error = 1 + try: + nc = __import__('natlinkcorefunctions') + except: + self.error('could not import natlinkcorefunctions') + error = 1 + if not error: + self.config = self.cli.config + title = self.frame.GetTitle() + self.functions = self.getGetterFunctions() # including self.checkboxes + self.undoList = [] + # to see if things were changed: + self.urgentMessage = None + if not error: + self.startInfo = copy.copy(self.config.getNatlinkStatusDict()) + version = self.startInfo['InstallVersion'] + if not title.endswith(version): + title = '%s (%s)'% (title, version) + self.frame.SetTitle(title) + if self.cli.checkedConfig: + # changed installation, message from natlinkconfigfunctions + self.urgentMessage = "REREGISTER natlink.pyd and Close (restart) or Close right away to cancel (see log panel)" + self.cli.checkedConfig = None + if self.config.changesInInitPhase: + if self.cli.getFatalErrors(): + self.urgentMessage = "See the log panel for urgent startup information!!" + else: + self.urgentMessage = "See the log panel for startup information, the init phase was succesful" + + self.setInfo() + # now self.DNSName is known (NatSpeak or Dragon) + self.DNSName = self.config.getDNSName() + + def warning(self, text, title='Message from Configure Natlink GUI'): + if isinstance(text, str): + Text = text + else: + Text = '\n'.join(text) + dlg = wx.MessageDialog(self, Text, title, + wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def getGetterFunctions(self): + D = {} + +## '', +## '', '', 'CoreDirectory', +## '', '', +## 'VocolaUserDirectory' + # checkboxes should have a getter, an event (OnCB...) and + # be included in self.checkboxes list. + ##QH: should include DNSName maybe (2014) + + D['DNSVersion'] = self.frame.infopanel.GetTextctrldnsversion + D['DNSInstallDir'] = self.frame.infopanel.GetTextctrldnsinstallpath + D['PythonVersion'] = self.frame.infopanel.GetTextctrlpythonversion + D['CoreDirectory'] = self.frame.infopanel.GetTextctrlnatlinkcorepath + D['UserDirectory'] = self.GetTextctrlnatlinkuserdirectory + D['VocolaUserDirectory'] = self.GetTextctrlvocolauserdirectory + D['UnimacroUserDirectory'] = self.GetTextctrlunimacrouserdirectory + D['WindowsVersion'] = self.frame.infopanel.GetTextctrlwindowsversion + D['VocolaTakesLanguages'] = self.GetCheckboxvocolatakeslanguages + D['VocolaTakesUnimacroActions'] = self.GetCheckboxvocolatakesunimacroactions +## D['VocolaCommandFilesEditor'] = self.GetTextctrlvocolaeditor + D['DebugCallback'] = self.GetCheckboxdebugcallbackoutput + D['DebugLoad'] = self.GetCheckboxdebugload + #D['NatlinkDebug'] = self.GetCheckboxnatlinkdebug + D['DNSIniDir'] = self.frame.infopanel.GetTextctrldnsinifilepath + D['natlinkIsEnabled'] = self.GetButtonnatlinkenable + D['vocolaIsEnabled'] = self.GetButtonvocolaenable + + D['unimacroIsEnabled'] = self.GetButtonunimacroenable + D['userIsEnabled'] = self.GetButtonuserenable + # D['UnimacroEnable'] = self.GetTextctrlunimacroinifilesdirectory + D['UnimacroIniFilesEditor'] = self.GetTextctrlunimacroeditor + # D['IncludeUnimacroInPythonPath'] = self.GetIncludeunimacroinpythonpath + self.checkboxes = ['VocolaTakesLanguages', + 'VocolaTakesUnimacroActions', + 'DebugCallback', 'DebugLoad', + #'NatlinkDebug', + #'IncludeUnimacroInPythonPath' + ] + return D + + def error(self, text): + """put error message on log panel and on status line""" + print(text) + print('-'*60) + self.frame.SetStatusText(text + ' (see log)') + + def setstatus(self, text): + """put message on log panel and on status line""" + print(text) + self.frame.SetStatusText(text) + + def setInfo(self, leaveStatus=None): + """extract data for the info controls + """ + self.parent.Freeze() + D = self.config.getNatlinkStatusDict() +## print 'StatusDict:' +## for k,v in D.items(): +## if v: +## print '%s: %s'% (k,v) +## print '-------' + newStatus = {} # dict with keys Natlink, Vocola, Unimacro, values (value, changed) each of them + + + try: + changed = 0 + for key in D: + if key in ["DNSInstallDir", "DNSIniDir"]: + pass + + if key in self.functions and self.functions[key]: + func = self.functions[key] +## if func == None: +## print "no getter function for %s"% key +## continue + value = D[key] + thisOneChanged = 0 + if value != self.startInfo[key]: + thisOneChanged = 1 + changed = 1 + if key in self.checkboxes: + # value = func().Value + if self.firstThaw: + if value: + func().SetValue(True) + else: + func().SetValue(False) + + if thisOneChanged: + func().SetForegroundColour(wx.RED) + else: + func().SetForegroundColour(wx.BLACK) + else: + if key in ["DNSInstallDir", "DNSIniDir"]: + pass + # no checkbox: + label = str(value) + if key == 'DNSVersion': + # DNSFullVersion gives different information as + # natspeak help window + label = '%s'% D[key] + elif key == 'PythonVersion': + # internal version (for pyd, I believe) is eg + # take first word of Fullversion as well. + #fullPart = D['PythonFullVersion'] + #label = '%s (%s)'% (D[key], fullPart.split()[0]) + label = '%s'% D[key] + elif value == -1 and key in ["DNSInstallDir", "DNSIniDir"]: + thisOneChanged = 1 + label = "Please choose a valid path" + self.urgentMessage = "Invalid DNS path, see info panel" + if key.endswith('IsEnabled'): + if value: + label = 'Disable' + else: + label = 'Enable' + # compose newStatus for status text control: + part = key.split('Is')[0] + newStatus[part] = (value, thisOneChanged) + func().SetLabel(label) + if thisOneChanged: + func().SetForegroundColour(wx.RED) + else: + func().SetForegroundColour(wx.BLACK) + + # undo button: + undoButton = self.GetButtonundo() + if self.undoList: + undoButton.Enable(True) + else: + undoButton.Enable(False) + + if D['natlinkIsEnabled']: + value = True + else: + value = False + for key in ['VocolaTakesLanguages', + 'vocolaIsEnabled', 'unimacroIsEnabled', 'userIsEnabled', + ]: + if key in self.functions and self.functions[key]: + control = self.functions[key]() + control.Enable(value) + + self.composeStatusLine(newStatus) + self.urgentStatusLine(self.urgentMessage) + self.urgentMessage = None + + finally: + self.parent.Thaw() + self.firstThaw = False + + def composeStatusLine(self, status): + """takes a dict with Natlink, Vocola, Unimacro as keys, + and a tuple (value, changed) as values. Value=0 means disable + """ + L = [] + somethingChanged = 0 + for part in ('Natlink', 'Vocola', 'Unimacro', 'User'): + value, changed = status[part.lower()] + if value: + enableddisabled = 'enabled' + else: + enableddisabled = 'disabled' + if part == 'User': + part = 'UserDirectory' + if changed: + somethingChanged = 1 + line = '%s will be %s'% (part, enableddisabled) + line = line.upper() + L.append(line) + else: + line = '%s is %s'% (part, enableddisabled) + L.append(line) + + if part == 'Natlink' and enableddisabled == 'disabled': + break # stop further status info + + statusLine = '; '.join(L) + control = self.GetTextctrlstatus() + control.SetValue(statusLine) + if somethingChanged: + control.SetForegroundColour(wx.RED) + else: + control.SetForegroundColour(wx.BLACK) + + def urgentStatusLine(self, statusString=None): + """writes a urgent message to the status text control + """ + if not statusString: return + control = self.GetTextctrlstatus() + control.SetValue(statusString) + control.SetForegroundColour(wx.RED) + + + + def do_command(self, *args, **kw): + """a single letter, optionally followed by a path + + If you want undo information: + Provide as 'undo' keyword argument + + for single arguments (checkboxes) provide the inverted character as undo + (as is done in do_checkboxcommand) + eg self.do_command('b', undo='B') + + for other commands (with paths) 'undo' must be provided as keyword argument, + the parameters MUST be a tuple, length 1 for single letters, length 2 if a + path is provided. + eg self.do_command('V', undo=('v', 'path/to/previous')) + + when calling from undo button, provide 'noundo' = 1 as keyword argument. + + + """ + if len(args) < 1: + print('empty command %s'% repr(args)) + return + if len(args) > 2: + print('too many posional arguments: %s'% repr(args)) + return + letter = args[0] + if len(args) == 2: + pathName = args[1] + else: + pathName = 'dummy' + funcName = 'do_%s'% letter + func = getattr(self.cli, funcName, None) + if not func: + mess = 'invalid command: %s'% letter + print(mess) + return mess + + try: + result = func(pathName) + except ElevationError: + e = sys.exc_info()[1] + + mess = 'This command needs elevated mode: %s'% e.message + mess2 = mess + '\n\nClose this program and run "start_configurenatlink.py"' + self.error(mess) + self.warning(mess2) + return mess + + # append to undoList + if not 'undo' in kw: + self.setInfo() + return result + undoInfo = kw['undo'] + if type(undoInfo) == tuple and len(undoInfo) in [1,2]: + undo = undoInfo + elif isinstance(undoInfo, str): + undo = (undoInfo,) + else: + print('invalid undoInfo from button: %s'% repr(undoInfo)) + return result + self.undoList.append(undo) + self.setInfo(leaveStatus=result) + return result + + def do_checkboxcommand(self, letter, control): + """take value from control and do the command in upper or lowercase + value = 1 (checked) lowercase command + value = 0 (unchecked) uppercase command + + """ + value = control.GetValue() + if value: + doLetter = letter.lower() + undoLetter = letter.upper() + else: + doLetter = letter.upper() + undoLetter = letter.lower() + result = self.do_command(doLetter, undo=undoLetter) + if not result: + self.setstatus("checkbox option changed to %s; restart %s to take effect"% (value, self.DNSName)) + + + # WDR: methods for configurenatlink + def GetCheckboxvocolatakesunimacroactions(self): + return self.FindWindowById( ID_CHECKBOXVocolaTakesUnimacroActions ) + + # def GetIncludeunimacroinpythonpath(self): + # return self.FindWindowById( ID_IncludeUnimacroInPythonPath ) + + def GetTextctrlunimacroeditor(self): + return self.FindWindowById( ID_TEXTCTRLunimacroeditor ) + + def GetTextctrlunimacroinifilesdirectory(self): + return self.FindWindowById( ID_TEXTCTRLunimacroinifilesDirectory ) + + def GetTextctrlvocolaeditor(self): + return self.FindWindowById( ID_TEXTCTRLVocolaEditor ) + + def GetButtonvocolaeditor(self): + return self.FindWindowById( ID_BUTTONVocolaEditor ) + + def GetTextctrlstatus(self): + return self.FindWindowById( ID_TEXTCTRLstatus ) + + def GetButtonvocolaenable(self): + return self.FindWindowById( ID_BUTTONVocolaEnable ) + + + def GetTextctrlvocolauserdirectory(self): + return self.FindWindowById( ID_TEXTCTRLvocolauserdirectory ) + + def GetTextctrlunimacrouserdirectory(self): + return self.FindWindowById( ID_TEXTCTRLunimacrouserdirectory ) + + + def GetTextctrlnatlinkuserdirectory(self): + return self.FindWindowById( ID_TEXTCTRLnatlinkuserdirectory ) + + #def GetCheckboxnatlinkdebug(self): + # return self.FindWindowById( ID_CHECKBOXNatlinkDebug ) + + def GetButtonundo(self): + return self.FindWindowById( ID_BUTTONUndo ) + + def GetButtonnatlinkenable(self): + return self.FindWindowById( ID_BUTTONNatlinkEnable ) + + def GetButtonnatlinkenable(self): + return self.FindWindowById( ID_BUTTONNatlinkEnable ) + + def GetButtonunimacroenable(self): + return self.FindWindowById( ID_BUTTONUnimacroEnable ) + + def GetButtonuserenable(self): + return self.FindWindowById( ID_BUTTONUserEnable ) + + def GetCheckboxdebugcallbackoutput(self): + return self.FindWindowById( ID_CHECKBOXDebugCallbackOutput ) + + def GetCheckboxdebugload(self): + return self.FindWindowById( ID_CHECKBOXDebugLoad ) + + # def GetCheckboxdebugoutput(self): + # return self.FindWindowById( ID_CHECKBOXDebugOutput ) + + def GetCheckboxvocolatakeslanguages(self): + return self.FindWindowById( ID_CHECKBOXVocolaTakesLanguages ) + + def GetCheckboxenablenatlink(self): + return self.FindWindowById( ID_CHECKBOXEnableNatlink ) + + def GetTextctrldnsinifilepath(self): + return self.FindWindowById( ID_TEXTCTRLdnsinifilepath ) + + def GetTextctrldnsinstallpath(self): + return self.FindWindowById( ID_TEXTCTRLDNSinstallpath ) + + def GetTextctrlpythonversion(self): + return self.FindWindowById( ID_TEXTCTRLpythonversion ) + + def GetTextctrldnsversion(self): + return self.FindWindowById( ID_TEXTCTRLDNSversion ) + + + def GetTextctrlregisternatlink(self): + return self.FindWindowById( ID_TEXTCTRLregisternatlink ) + + # WDR: handler implementations for configurenatlink + + def OnCBVocolaTakesUnimacroActions(self, event): + letter = 'a' + control = self.GetCheckboxvocolatakesunimacroactions() + self.do_checkboxcommand(letter, control) + + + # def OnButtonIncludeUnimacroInPythonPath(self, event): + # letter = 'f' + # control = self.GetIncludeunimacroinpythonpath() + # self.do_checkboxcommand(letter, control) + + def OnButtonVocolaCompatibility(self, event): + title = "Unimacro features can be used by Vocola" + dlg = DialogUnimacroVocolaCompatibiliy(self, title=title) +## dlg.SetText(text) + answer = dlg.ShowModal() + if answer: + # print 'answer: %s'% answer + if answer%2: + print("(re)copy Unimacro.vch file to Vocola user commands directory") + doLetter = 'l' + statustext = 'Copied Unimacro.vch file to Vocola user commands directory' + self.do_command(doLetter) + self.setstatus(statustext) + self.setInfo() + answer -= 1 + if answer%4: + print('remove "include Unimacro.vch" lines from all Vocola command files in your Vocola user commands directory') + doLetter = 'M' + undoLetter = "m" + statustext = 'Removed "include Unimacro.vch" lines from all Vocola command files in your Vocola user commands directory' + self.do_command(doLetter, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + answer -= 2 + if answer == 4: + print('add "include Unimacro.vch" lines to all Vocola command files in your Vocola user commands directory') + doLetter = 'm' + undoLetter = "M" + statustext = 'added "include Unimacro.vch" lines to all Vocola command files in your Vocola user commands directory' + self.do_command(doLetter, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + else: + print('nothing chosen') + + def OnButtonUnimacroEditor(self, event): + D = self.config.getNatlinkStatusDict() + + doLetter = 'p' + undoLetter = 'P' + statustext = 'Unimacro Editor is specified, this will take effect after you restart %s'% self.DNSName + + # ask for the correct directory: + dlg = wx.FileDialog(self.frame, "Choose the filename of your favorite Unimacro INI files editor", + style=wx.DD_DEFAULT_STYLE) + ## search for Unimacro directory as proposal: + old_path = self.config.isValidPath(D['UnimacroIniFilesEditor'], wantFile=1) + if not old_path: + old_path = self.config.isValidPath(self.config.userregnl.get('OldUnimacroIniFilesEditor'), + wantFile=1) + if old_path: + dlg.SetPath(old_path) + else: + old_path = self.config.isValidPath("%PROGRAMFILES%", wantDirectory=1) + if old_path: + dlg.SetDirectory(old_path) + dlg.SetMessage('Choose the filename of your favorite Unimacro INI files editor; Cancel for return to default') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + if new_path and os.path.isfile(new_path) and new_path.lower().endswith('.exe'): + pass + else: + self.setstatus("no new valid (.exe) file specified") + return + else: + if old_path: + self.setstatus("Pressed Cancel, return to default") + self.do_command( undoLetter, undo=(doLetter, old_path) ) + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + +# def OnButtonVocolaEditor(self, event): +# D = self.config.getNatlinkStatusDict() +# +# doLetter = 'w' +# undoLetter = 'W' +# statustext = 'Vocola Editor is specified, this will take effect after you restart %s'% self.DNSName +# +# # ask for the correct directory: +# dlg = wx.FileDialog(self.frame, "Choose the filename of your favorite editor please", +# style=wx.DD_DEFAULT_STYLE) +# ## search for Unimacro directory as proposal: +# old_path = D['VocolaCommandFilesEditor'] +# Path = nc.getExtendedEnv("PROGRAMFILES") +# dlg.SetPath(Path) +# dlg.SetMessage('Please choose the filename of your favorite editor please\nPress cancel to return to default') +# if dlg.ShowModal() == wx.ID_OK: +# new_path = dlg.GetPath() +# if new_path and os.path.isfile(new_path) and new_path.lower().endswith('.exe'): +# pass +# else: +# self.setstatus("no new valid (.exe) file specified") +# return +# else: +# self.setstatus("Pressed Cancel, return to default") +# self.do_command( undoLetter, undo=(doLetter, old_path) ) +# return +# self.do_command(doLetter,new_path, undo=undoLetter) +# self.setstatus(statustext) +# self.setInfo() + + + def OnButtonLogInfo(self, event): + self.cli.do_i("dummy") + self.warning("See log panel") + + def OnButtonHelp5(self, event): + print('---help on re(register) natlink.pyd') + print('note the letters correspond to the commands in the self.cli (command line interface)') + self.cli.help_r() + text = \ +""" +Help about re(register) natlink.pyd you will find in the log panel + +About this configure program: + +All actions are performed immediate, mostly doing something +in the natlinkstatus.ini file of Natlink (in the MacroSystem/Core directory). + +What is changed is shown in red. The Undo button undoes these actions. + +If, for example, Vocola shows the button "Enable", it is currently disabled. + +In order to let the changes take effect, you have to restart NatSpeak. + +For the actions Enable/Disable Natlink and unregister/(re)register natlink.pyd +you need "elevated mode". +This is established by running "start_configurenatlink.py". + +""" + self.warning(text) + + + def OnButtonHelp4(self, event): + text = \ +"""User Grammar files can be activated/deactivated by specifying the UserDirectory. + +This directory should NOT be Unimacro or MacroSystem, as these are for Vocola and Unimacro. + +Dragonfly users can use this option. +""" + self.warning(text) + + def OnButtonHelp3(self, event): + print('---help on Enable/Disable Unimacro:') + print('note the letters correspond to the commands in the self.cli (command line interface)') + self.cli.help_n() + self.cli.help_o() + self.cli.help_p() + self.cli.help_l() # includes help for m and M + text = \ +""" +Unimacro is enabled by specifying the UnimacroUserDirectory. + +When you disable Unimacro, this UnimacroUserDirectory setting is cleared from the natlinkstatus.ini file. + +When Unimacro is enabled, you can also specify: + - a program for editing these user (INI) files, default is Notepad + +Vocola can use Unimacro features, by checking the checkbox in the Vocola section. + +Via the dialog Vocola Compatibility, you can handle things around the include file, Unimacro.vch. + +This file is automatically copied from (...)\\Unimacro\vocola_compatibility into the VocolaUserDirectory when Vocola is started. + +More about this in the "Vocola Compatibility" dialog. +""" + + self.warning(text) + + + def OnButtonClose(self, event): + if self.undoList: + self.warning('Please restart %s\n\n(in order to let the changes take effect)'% self.DNSName) + self.parent.frame.Destroy() + + def OnButtonUndo(self, event): + if self.undoList: + self.firstThaw = True + cmd = self.undoList.pop() + self.do_command(*cmd) + # self.getNatlinkStatusDict() + self.setstatus("Did undo") + + + def OnButtonUnimacroEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + letter = 'o' + if D['unimacroIsEnabled']: + doLetter = letter.upper() + undoLetter = letter.lower() + statustext = 'Unimacro is DISABLED, this will take effect after you restart %s'% self.DNSName + prevPath = D['UnimacroUserDirectory'] + undoCmd = (undoLetter, prevPath) + self.do_command(doLetter, undo=undoCmd) + self.setstatus(statustext) + self.setInfo() + return + # now go for enable: + doLetter = letter.lower() + undoLetter = letter.upper() + statustext = 'Unimacro/user grammars is ENABLED, this will take effect after you restart %s'% self.DNSName + + # ask for the correct directory: + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) + ## search for Unimacro User directory as proposal: + oldPath = self.config.userregnl.get('OldUnimacroUserDirectory') + if oldPath: + oldPath = self.config.isValidPath(oldPath) + if not oldPath: + tryHome = self.config.isValidPath("~") + if not tryHome: + tryHome = self.config.isValidPath("%PERSONAL%") + if tryHome: + oldPath = tryHome + if oldPath: + dlg.SetPath(oldPath) + dlg.SetMessage('Specify the UnimacroUserDirectory, where your ini files are/will be located; this also enables Unimacro.') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + if new_path and os.path.isdir(new_path): + pass + else: + self.setstatus("no new valid directory specified") + return + else: + self.setstatus("nothing specified") + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + def OnButtonUserEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + letter = 'n' + if D['userIsEnabled']: + doLetter = letter.upper() + undoLetter = letter.lower() + statustext = 'User Grammars are DISABLED, this will take effect after you restart %s'% self.DNSName + prevPath = D['UserDirectory'] + undoCmd = (undoLetter, prevPath) + self.do_command(doLetter, undo=undoCmd) + self.setstatus(statustext) + self.setInfo() + return + # now go for enable: + doLetter = letter.lower() + undoLetter = letter.upper() + statustext = 'User Grammars are ENABLED, this will take effect after you restart %s'% self.DNSName + + # ask for the correct directory: + dlg = wx.DirDialog(self.frame, "Please choose the UserDirectory, where your Natlink grammar files are located.", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) + ## search for previous directory or other default: + oldPath = self.config.userregnl.get('OldUserDirectory') + if oldPath: + oldPath = self.config.isValidPath(oldPath) + if not oldPath: + tryNatlink = os.path.join(D['CoreDirectory'], '..', '..', '..') + oldPath = self.config.isValidPath(tryNatlink) + if oldPath: + dlg.SetPath(oldPath) + dlg.SetMessage('Please specify the UserDirectory, where user grammar files are located') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + new_path = self.config.isValidPath(new_path, wantDirectory=1) + if not new_path: + self.setstatus("no new valid directory specified") + elif new_path == D['UnimacroDirectory']: + self.setstatus("Please do not specify Unimacro as UserDirectory") + return + elif new_path == D['BaseDirectory']: + self.setstatus("Please do not specify BaseDirectory, used by Vocola, as UserDirectory") + return + else: + self.setstatus("nothing specified") + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + def OnButtonVocolaEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + isValidPath = self.config.isValidPath + letter = 'v' + if D['vocolaIsEnabled']: + doLetter = letter.upper() + undoLetter = letter.lower() + statustext = 'Vocola is DISABLED, this will take effect after you restart %s'% self.DNSName + prevPath = D['VocolaUserDirectory'] + undoCmd = (undoLetter, prevPath) + self.do_command(doLetter, undo=undoCmd) + self.setstatus(statustext) + self.setInfo() + return + # now go for enable: + doLetter = letter.lower() + undoLetter = letter.upper() + statustext = 'Vocola is ENABLED, this will take effect after you restart %s'% self.DNSName + + + + # ask for the correct directory: + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE) + ## search for Vocola directory as proposal: + oldPath = self.config.userregnl.get('OldVocolaUserDirectory') + if oldPath: + oldPath = self.config.isValidPath(oldPath) + if not oldPath: + tryHome = self.config.isValidPath("~") + if not tryHome: + tryHome = self.config.isValidPath("%PERSONAL%") + if tryHome: + oldPath = tryHome + if oldPath: + dlg.SetPath(oldPath) + dlg.SetMessage('Specify the VocolaUserDirectory, where your Vocola Command Files are located; this also enables Vocola') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + if new_path and os.path.isdir(new_path): + pass + else: + self.setstatus("no new valid directory specified") + return + else: + self.setstatus("nothing specified") + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + + def OnButtonNatlinkEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + letter = 'e' + if D['natlinkIsEnabled']: + # disable: + doLetter = letter.upper() + undoLetter = letter.lower() + self.do_command(doLetter, undo=undoLetter) + if self.config.NatlinkIsEnabled(): + statustext = 'Natlink is NOT DISABLED, please run this program in "elevated mode"' + else: + statustext = 'Natlink is DISABLED, this will take effect after you restart %s'% self.DNSName + + else: + # enable: + doLetter = letter.lower() + undoLetter = letter.upper() + self.do_command(doLetter, undo=undoLetter) + if self.config.NatlinkIsEnabled(): + statustext = 'Natlink is ENABLED, this will take effect after you restart %s'% self.DNSName + else: + statustext = 'Natlink is NOT ENABLED, please run this program in "elevated mode"' + self.setstatus(statustext) + + #def OnCBNatlinkDebug(self, event): ## obsolete, QH 26-08-2013 + # letter = 'g' + # control = self.GetCheckboxnatlinkdebug() + # self.do_checkboxcommand(letter, control) + + + def OnCBVocolaTakesLanguages(self, event): + letter = 'b' + control = self.GetCheckboxvocolatakeslanguages() + self.do_checkboxcommand(letter, control) + + def OnCBDebugCallback(self, event): + letter = 'y' + control = self.GetCheckboxdebugcallbackoutput() + self.do_checkboxcommand(letter, control) + + + def OnCBDebugLoad(self, event): + letter = 'x' + control = self.GetCheckboxdebugload() + self.do_checkboxcommand(letter, control) + + + + def OnButtonHelp2(self, event): + print('---help on Enable/disable Vocola:') + print('note the letters correspond to the commands in the self.cli (command line interface)') + self.cli.help_b() + self.cli.help_a() + print('---help on additional Vocola options:') + L = [] + L.append("Vocola is enabled by specifying a directory (VocolaUserDirectory)") + L.append("where the Vocola Command files are/will be located.") + L.append("") + L.append("When you disable Vocola, this setting is cleared in the natlinkstatus.ini file.") + L.append("") + L.append('When you use more languages, eg speech profiles for English and Dutch, please read the log panel for the "Vocola multi languages" option.') + L.append("") + L.append('When you want to use Unimacro actions in your Vocola comman files, you can check the "Vocola takes Unimacro Actions" option.') + L.append("More information about this on the Natlink/Vocola/Unimacro website") + L.append("") + self.warning('\n'.join(L)) + + def OnButtonHelp1(self, event): + print('---help on Enable Natlink and corresponding functions:') + print('note the letters correspond to the commands in the self.cli (command line interface)') + self.cli.help_e() + print('---help on Natlink debug options:') + self.cli.help_x() + text = """ + +This Enables or Disables Natlink. The state of Natlink is shown in the Status bar above, and is the opposite of the button text. + +Natlink should be enabled before you can use Vocola and/or Unimacro or other python grammars, like Dragonfly. + +When Natlink is disabled, Vocola and Unimacro will -- consequently -- be disabled too. + +Note that you need elevated mode and possibly Dragon be switched off before you can Enable or Disable Natlink. + +At first run after you installed Natlink, natlink.pyd is registered silently, but Natlink is still Disabled. + +So in that case click on Enable for getting started. +""" + self.warning(text) + + + + def OnButtonUnregister(self, event): + self.do_command('R') + self.warning("Close this program, and also close %s\n\nthen you run this program again in elevated mode via start_configurenatlink.py"% self.DNSName) + # self.urgentMessage = "Close this program, restart %s, possibly computer"% self.DNSName + self.setInfo() + + def OnButtonRegister(self, event): + self.do_command('r') + self.warning("Close this program, %s, all Python applications and\n\npossibly restart your computer\n\nbefore you run this program again!"% self.DNSName) + self.urgentMessage = "Close this program, restart %s, possibly computer"% self.DNSName + self.setInfo() + + +class MyFrame(wx.Frame): + def __init__(self, parent, id, title, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE ): + wx.Frame.__init__(self, parent, id, title, pos, size, style) + self.app = parent + self.Bind(wx.EVT_MENU, self.OnMenuHelp, id=ID_MENUhelp) + self.Bind(wx.EVT_MENU, self.OnMenuClose, id=ID_MENUClose) + + self.CreateStatusBar(1) + self.SetStatusText("This is the Configure Natlink & Vocola & Unimacro GUI") + self.CreateMyMenuBar() + # insert main window here + self.nb = wx.Notebook(self, -1, name='panel', + pos=wx.Point(0, 0), size=wx.Size(592, 498), style=0) + + self.log = wx.TextCtrl(self.nb, -1, name='log', + style=wx.TE_READONLY | wx.TE_MULTILINE|wx.TE_NOHIDESEL, value='') + sys.stdout = Stdout(self.log) +## self.errors = wx.TextCtrl(self.nb, -1, name='errors', +## style=wx.TE_READONLY | wx.TE_MULTILINE|wx.TE_NOHIDESEL, value='') +## sys.stderr = Stderr(self.errors) + sys.stderr = sys.stdout + + self.nb.AddPage(imageId=-1, page=self.log, select=False, + text='log') +## self.nb.AddPage(imageId=-1, page=self.errors, select=False, +## text='errors') + self.nb.frame = self + self.infopanel =InfoPanel(self.nb, -1, name='infopanel') + self.nb.AddPage(imageId=-1, page=self.infopanel, select=False, + text='info') + self.cpanel = ConfigureNatlinkPanel(self.nb, -1, name='configurepanel') + self.nb.AddPage(imageId=-1, page=self.cpanel, select=True, + text='configure') + self.infopanel.cpanel = self.cpanel +## self.nb = wx.Notebook(name='notebook', parent=self, style=0) + + + # WDR: methods for MyFrame + def CreateMyMenuBar(self): + self.SetMenuBar(MyMenuBarFunc() ) + + def OnMenuClose(self, event): + self.Destroy() + + def OnMenuHelp(self, event): + text = ['This configure GUI makes it possible to configure Natlink, ', + 'including Vocola and Unimacro',"", + 'Detailed help is given through various help buttons and in the "log" panel',"" + 'Written by Quintijn Hoogenboom, February, 2008/May, 2009/April, 2018', + 'See also http://qh.antenna.nl/unimacro'] + self.warning('\n'.join(text)) + + + def warning(self, text, title='Message from Configure Natlink GUI'): + dlg = wx.MessageDialog(self, text, title, + wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + + # WDR: handler implementations for MyFrame + +class Stdout: + def __init__(self, object): + self.writeto = object +## self.write('stdout started') + + def flush(self): + pass + def write(self, t): + """write to output""" + self.writeto.AppendText(t) + +class Stderr: + def __init__(self, txtctrl): + self.window = txtctrl + + def flush(self): + pass + + def write(self, t): + """write to output""" + self.window.AppendText(t) + +class MyApp(wx.App): + def OnInit(self): + # wx.InitAllImageHandlers() + self.frame = MyFrame( None, -1, "Configure Natlink & Vocola & Unimacro", + [110,80], [750,735] ) + self.frame.Show(True) + return True +try: + app = MyApp(True) +except: + import sys, traceback + # traceback.print_exception(type, value, traceback[, limit[, file]]) + traceback.print_exc(file=open("configurenatlink_error.txt", "w")) + mess = traceback.format_exc() + mess += '\n\nMore info in configurenatlink_error.txt in the directory "(C:\\natlink)\\natlink\\confignatlinkvocolaunimacro"' + windowsMessageBox(mess, "Error at startup of configurenatlink") + sys.exit(1) +else: + app.MainLoop() diff --git a/NatlinkConfigure/configurenatlink.pyw.bak b/NatlinkConfigure/configurenatlink.pyw.bak new file mode 100644 index 00000000..78467825 --- /dev/null +++ b/NatlinkConfigure/configurenatlink.pyw.bak @@ -0,0 +1,1373 @@ +#!/bin/env python +# +# confignatlinkvocolaunimacro.py +# This module does the natlinkconfigfunctions through a +# wxPython GUI +# +# (C) Copyright Quintijn Hoogenboom, 2008-2009 +# +#---------------------------------------------------------------------------- + +try: + import wx +except ImportError: + print 'Unable to run the GUI installer because module wx was not found. This probably' + print 'means that wxPython is not installed.' + + print + print 'Either install wxPython (recommended) or use the CLI (Command Line Interface)' + print 'Natlink configuration program.' + print + print 'A version of wxPython suitable for use with Natlink can be obtained from' + print 'http://sourceforge.net/project/showfiles.php?group_id=70807&package_id=261793' + print '(each package there contains a version of wxPython; get the one appropriate' + print ' to the version of Python you have installed.)' + print + while True: + pass + raise + +try: + from win32ui import MessageBox + def windowsMessageBox(message, title="Natlink configure program"): + """do messagebox from windows, no wx needed + """ + MessageBox(message, title) +except: + import ctypes + MessageBoxA = ctypes.windll.user32.MessageBoxA + def windowsMessageBox(message, title="Natlink configure program"): + """do messagebox from windows, no wx needed + for old versions of python + """ + MessageBoxA(None, message, title, 0) + +import os +import sys +if sys.version[0] == '2' and sys.version[2] in ['3', '5']: + pyVersion = sys.version[:3] + mess = ["Warning, this Configure Natlink GUI possibly does not work for this old python version: %s."% pyVersion, + "Also the natlink.pyd files (natlink.dll) that work with python %s are for older versions of NatSpeak (10 and before) only."% pyVersion, + 'Now the good news: you can still use Natlink for NatSpeak 10 and before even with this python version.', + 'But if the Configure Natlink GUI does not work, you should do the configuration via "natlinkconfigfunctions.py".', + 'Please start that program, preferably in elevated mode via "start_configurenatlinkfunctions.py" or via "Configure Natlink via Command Line Interface" in the start menu.', + "For Dragon 11 and later, some things may work, but it is better to upgrade to Python 2.6 or 2.7"] + + mess = '\n\n'.join(mess) + windowsMessageBox(mess) + +import sys, traceback +from configurenatlink_wdr import * +import os, os.path, string, copy, types +from natlinkconfigfunctions import ElevationError +# nf: natlinkinstallfunctions, imported at end of init... +nf = None +nc = None # natlinkcorefunctions +# WDR: classes + +class DialogUnimacroVocolaCompatibiliy(wx.Dialog): + def __init__(self, parent, title=None): + parent = parent + id = -1 + pos = wx.DefaultPosition + title = title or "Unimacro/Vocola compatibility" + wx.Dialog.__init__(self, parent, id, title, pos) + # WDR: dialog function YesNoAbort for MyYesNoAbortDialog + + + # WDR: dialog function DialogVocolaCombatibility for DialogUnimacroVocolaCompatibiliy + DialogVocolaCombatibility( self, True ) + + # WDR: handler declarations for DialogUnimacroVocolaCompatibiliy + # wx.EVT_CHECKBOX(self, ID_IncludeUnimacroInPythonPath, self.OnIncludeUnimacroInPythonPath) + self.Bind(wx.EVT_BUTTON, self.OnCancel, id=ID_BUTTONCancel) + self.Bind(wx.EVT_BUTTON, self.OnOK, id=ID_BUTTONOK) + + + # WDR: methods for DialogUnimacroVocolaCompatibiliy + + # def GetIncludeunimacroinpythonpath(self): + # return self.FindWindowById( ID_IncludeUnimacroInPythonPath ) + + def GetCheckboxremoveunimacroincludelines(self): + return self.FindWindowById( ID_CHECKBOXRemoveUnimacroIncludeLines ) + + def GetCheckboxmakeunimacroincludelines(self): + return self.FindWindowById( ID_CHECKBOXMakeUnimacroIncludeLines ) + + def GetCheckboxrefreshunimacrovch(self): + return self.FindWindowById( ID_CHECKBOXRefreshUnimacroVch ) + + def GetCheckboxvocolatakesunimacroactions(self): + return self.FindWindowById( ID_CHECKBOXVocolaTakesUnimacroActions ) + + # WDR: handler implementations for DialogUnimacroVocolaCompatibiliy + + # def OnIncludeUnimacroInPythonPath(self, event): + # pass + + def OnOK(self, event): + code = 0 + if self.GetCheckboxremoveunimacroincludelines().GetValue(): + code += 2 + if self.GetCheckboxmakeunimacroincludelines().GetValue(): + code += 4 + if self.GetCheckboxrefreshunimacrovch().GetValue(): + code += 1 + + + self.SetReturnCode(code) + self.Destroy() + + def OnCancel(self, event): + self.SetReturnCode(0) + self.Destroy() + + +class InfoPanel(wx.Panel): + def __init__(self, parent, id, name="infopanel", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.TAB_TRAVERSAL ): + self.frame = parent.frame + wx.Panel.__init__(self, parent, id, pos, size, style) + # WDR: dialog function InfoWindow for infopanel + InfoWindow( self, True ) + # WDR: handler declarations for infopanel + self.Bind(wx.EVT_BUTTON, self.OnButtonHelpInfo, id=ID_BUTTONHelpInfo) + self.Bind(wx.EVT_BUTTON, self.OnButtonClearDNSInifilePath, id=ID_BUTTONClearDNSInifilePath) + self.Bind(wx.EVT_BUTTON, self.OnButtonChangeDNSInifilePath, id=ID_BUTTONchangednsinifilepath) + self.Bind(wx.EVT_BUTTON, self.OnButtonClearDNSInstallPath, id=ID_BUTTONClearDNSInstallPath) + self.Bind(wx.EVT_BUTTON, self.OnButtonChangeDNSInstallPath, id=ID_BUTTONchangednsinstallpath) + self.Bind(wx.EVT_BUTTON, self.OnButtonLogInfo, id=ID_BUTTONLogInfo) + + # WDR: methods for infopanel + def GetTextctrlpythonversion(self): + return self.FindWindowById( ID_TEXTCTRLpythonversion ) + + def GetTextctrlwindowsversion(self): + return self.FindWindowById( ID_TEXTCTRLWindowsVersion ) + + def GetTextctrlnatlinkcorepath(self): + return self.FindWindowById( ID_TEXTCTRLnatlinkcorepath ) + + def GetTextctrldnsinifilepath(self): + return self.FindWindowById( ID_TEXTCTRLdnsinifilepath ) + + def GetTextctrldnsinstallpath(self): + return self.FindWindowById( ID_TEXTCTRLDNSinstallpath ) + + def GetTextctrldnsversion(self): + return self.FindWindowById( ID_TEXTCTRLDNSVersion ) + + def GetTextctrlpythonversion(self): + return self.FindWindowById( ID_TEXTCTRLpythonversion ) + + def GetTextctrlwindowsversion(self): + return self.FindWindowById( ID_TEXTCTRLWindowsVersion ) + + def GetTextctrlnatlinkcorepath(self): + return self.FindWindowById( ID_TEXTCTRLnatlinkcorepath ) + + + # WDR: handler implementations for infopanel + + def OnButtonClearDNSInifilePath(self, event): + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'C', 'c' + old_path = D['DNSIniDir'] + if old_path and os.path.isdir(old_path): + undoCmd = (undoLetter, old_path) + else: + self.setstatus("DNSIniDir was NOT set, so no action needed") + return + + statustext = 'DNSIniDir is Cleared, search (again) for default.' + result = self.do_command(doLetter, undo=undoCmd) + if result: + self.setstatus(result) + else: + self.setstatus(statustext) + self.cpanel.setInfo(leaveStatus=1) + + def OnButtonChangeDNSInifilePath(self, event): + # ask for the correct directory: + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'c', 'C' + undoCmd = (undoLetter,) + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) +## style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON) + ## search for Unimacro directory as proposal: + Path = D['DNSIniDir'] + if Path == -1: + Path = nc.getExtendedEnv("COMMON_APPDATA") + elif not Path: + Path = nc.getExtendedEnv("COMMON_APPDATA") + elif not os.path.isdir(Path): + Path = nc.getExtendedEnv("COMMON_APPDATA") + + dlg.SetPath(Path) + dlg.SetMessage('Please specify the directory where the DNS INI files are located') + statustext = 'DNS INI file location is changed' + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + else: + self.setstatus("nothing specified") + return + result = self.do_command(doLetter,new_path, undo=undoLetter) + if result: + self.setstatus(result) + else: + self.setstatus("DNSIniDir changed") + self.cpanel.setInfo(leaveStatus=1) + + + def OnButtonClearDNSInstallPath(self, event): + + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'D', 'd' + old_path = D['DNSInstallDir'] + if old_path and os.path.isdir(old_path): + undoCmd = (undoLetter, old_path) + else: + self.setstatus("DNSInstallDir was NOT set, so no action needed") + return + + statustext = 'DNSInstallDir is Cleared, search (again) for default.' + result = self.do_command(doLetter, undo=undoCmd) + if result: + self.setstatus(result) + else: + self.setstatus(statustext) + self.cpanel.setInfo(leaveStatus=1) + + def OnButtonChangeDNSInstallPath(self, event): + # ask for the correct directory: + D = self.cpanel.config.getNatlinkStatusDict() + doLetter, undoLetter = 'd', 'D' + undoCmd = (undoLetter,) + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) +## style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON) + ## search for Unimacro directory as proposal: + Path = D['DNSInstallDir'] + if Path == -1: + Path = nc.getExtendedEnv("PROGRAMFILES") + elif not Path: + Path = nc.getExtendedEnv("PROGRAMFILES") + elif not os.path.isdir(Path): + Path = nc.getExtendedEnv("PROGRAMFILES") + + dlg.SetPath(Path) + dlg.SetMessage('Please specify the directory where DNS is installed') + statustext = 'DNS Install directory is changed' + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + else: + self.setstatus("nothing specified") + return + result = self.do_command(doLetter,new_path, undo=undoLetter) + if result: + self.setstatus(result) + else: + self.setstatus("DNSInstallDir changed") + self.cpanel.setInfo(leaveStatus=1) + + def OnButtonLogInfo(self, event): + self.cpanel.cli.do_i("dummy") + self.cpanel.cli.do_I("dummy") + self.cpanel.warning("See the info from natlinkstatus in the log panel") + + def OnButtonHelpInfo(self, event): + print '---help on DNS Install Directory:' + print 'note the letters correspond to the commands in the self.cli (command line interface)' + cli = self.cpanel.cli + cli.help_d() + print '---help on DNS INI files Directory:' + cli.help_c() + text = \ +"""This info panel has no undo function, like the config panel, +but clearing the settings falls back to NatSpeak defaults + +The button Log info gives the complete natlinkstatus info in the log panel + +See more help information in the log panel""" + self.cpanel.warning(text) + + def setstatus(self, text): + """put message on log panel and on status line""" + #print text + self.frame.SetStatusText(text) + + def do_command(self, *args, **kw): + """a single letter, optionally followed by a path + for infopanel, no undo things, simply ignore + + when calling from undo button, provide 'noundo' = 1 as keyword argument. + + + """ + if len(args) < 1: + print 'empty command %s'% `args` + return + if len(args) > 2: + print 'too many posional arguments: %s'% `args` + return + letter = args[0] + if len(args) == 2: + pathName = args[1] + else: + pathName = 'dummy' + funcName = 'do_%s'% letter + cli = self.cpanel.cli + func = getattr(cli, funcName, None) + + if not func: + mess = 'invalid command: %s'% letter + print mess + return mess + try: + result = func(pathName) + except ElevationError: + e = sys.exc_info()[1] + + mess = 'This program should run in elevated mode (%s).'% e.message + self.error(mess) + mess2 = mess = '\n\nPlease Close and run via start_configurenatlink.py\n\nPlease close Dragon too.' + windowsMessageBox(mess) + self.cpanel.setInfo() + return result + + +class ConfigureNatlinkPanel(wx.Panel): + def __init__(self, parent, id, name="configurepanel", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.TAB_TRAVERSAL ): + global nf, nc # natlinkconfigfunctions, self.cli, natlinkcorefunctions + wx.Panel.__init__(self, parent, id, pos, size, style) + self.parent = parent + self.frame = parent.frame + # WDR: dialog function MainWindow for configurenatlink + MainWindow( self, True ) + + # WDR: handler declarations for configurenatlink + self.Bind(wx.EVT_CHECKBOX, self.OnCBVocolaTakesUnimacroActions, id=ID_CHECKBOXVocolaTakesUnimacroActions) + # wx.EVT_CHECKBOX(self, ID_IncludeUnimacroInPythonPath, self.OnButtonIncludeUnimacroInPythonPath) + self.Bind(wx.EVT_BUTTON, self.OnButtonVocolaCompatibility, id=ID_BUTTONVocolaCompatibiliy) + self.Bind(wx.EVT_BUTTON, self.OnButtonUnimacroEditor, id=ID_BUTTONUnimacroEditor) + self.Bind(wx.EVT_BUTTON, self.OnButtonUnimacroEnableDisable, id=ID_BUTTONUnimacroEnable) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp5, id=ID_BUTTONHelp5) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp1, id=ID_BUTTONHelp1) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp4, id=ID_BUTTONHelp4) + #wx.EVT_CHECKBOX(self, ID_CHECKBOXNatlinkDebug, self.OnCBNatlinkDebug) + self.Bind(wx.EVT_BUTTON, self.OnButtonClose, id=ID_BUTTONClose) + self.Bind(wx.EVT_BUTTON, self.OnButtonUndo, id=ID_BUTTONUndo) + self.Bind(wx.EVT_BUTTON, self.OnButtonUserEnableDisable, id=ID_BUTTONUserEnable) + self.Bind(wx.EVT_BUTTON, self.OnButtonVocolaEnableDisable, id=ID_BUTTONVocolaEnable) + self.Bind(wx.EVT_BUTTON, self.OnButtonNatlinkEnableDisable, id=ID_BUTTONNatlinkEnable) + self.Bind(wx.EVT_CHECKBOX, self.OnCBVocolaTakesLanguages, id=ID_CHECKBOXVocolaTakesLanguages) + self.Bind(wx.EVT_CHECKBOX, self.OnCBDebugCallback, id=ID_CHECKBOXDebugCallbackOutput) + self.Bind(wx.EVT_CHECKBOX, self.OnCBDebugLoad, id=ID_CHECKBOXDebugLoad) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp3, id=ID_BUTTONHelp3) + self.Bind(wx.EVT_BUTTON, self.OnButtonHelp2, id=ID_BUTTONHelp2) + + self.Bind(wx.EVT_BUTTON, self.OnButtonUnregister, id=ID_BUTTONunregister) + self.Bind(wx.EVT_BUTTON, self.OnButtonRegister, id=ID_BUTTONregister) + + try: + nf = __import__('natlinkconfigfunctions') + except: + + self.error('natlinkconfigfunctions import failed') + return + + class NatlinkConfigGUI(nf.NatlinkConfig): + def __init__(self, parent=None): + self.parent = parent + super(NatlinkConfigGUI, self).__init__() + def warning(self, text): + """overload, to make it also in GUI visible""" + super(NatlinkConfigGUI, self).warning(text) + #self.parent.warning(text) + + self.firstThaw = True # set to true first time and at undo action... + self.GUI = NatlinkConfigGUI(parent=self) + error = 0 + try: + self.cli = nf.CLI(self.GUI) + except ElevationError: + e = sys.exc_info()[1] + + mess = 'This program should run in elevated mode (%s).'% e.message + self.error(mess) + mess += '\n\nPlease Close and run via start_configurenatlink.pyw' + windowsMessageBox(mess) + error = 1 + except: + self.error('could not start CLI instance') + error = 1 + try: + nc = __import__('natlinkcorefunctions') + except: + self.error('could not import natlinkcorefunctions') + error = 1 + if not error: + self.config = self.cli.config + title = self.frame.GetTitle() + self.functions = self.getGetterFunctions() # including self.checkboxes + self.undoList = [] + # to see if things were changed: + self.urgentMessage = None + if not error: + self.startInfo = copy.copy(self.config.getNatlinkStatusDict()) + version = self.startInfo['InstallVersion'] + if not title.endswith(version): + title = '%s (%s)'% (title, version) + self.frame.SetTitle(title) + if self.cli.checkedConfig: + # changed installation, message from natlinkconfigfunctions + self.urgentMessage = "REREGISTER natlink.pyd and Close (restart) or Close right away to cancel (see log panel)" + self.cli.checkedConfig = None + if self.config.changesInInitPhase: + if self.cli.getFatalErrors(): + self.urgentMessage = "See the log panel for urgent startup information!!" + else: + self.urgentMessage = "See the log panel for startup information, the init phase was succesful" + + self.setInfo() + # now self.DNSName is known (NatSpeak or Dragon) + self.DNSName = self.config.getDNSName() + + def warning(self, text, title='Message from Configure Natlink GUI'): + if isinstance(text, basestring): + Text = text + else: + Text = '\n'.join(text) + dlg = wx.MessageDialog(self, Text, title, + wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + def getGetterFunctions(self): + D = {} + +## '', +## '', '', 'CoreDirectory', +## '', '', +## 'VocolaUserDirectory' + # checkboxes should have a getter, an event (OnCB...) and + # be included in self.checkboxes list. + ##QH: should include DNSName maybe (2014) + + D['DNSVersion'] = self.frame.infopanel.GetTextctrldnsversion + D['DNSInstallDir'] = self.frame.infopanel.GetTextctrldnsinstallpath + D['PythonVersion'] = self.frame.infopanel.GetTextctrlpythonversion + D['CoreDirectory'] = self.frame.infopanel.GetTextctrlnatlinkcorepath + D['UserDirectory'] = self.GetTextctrlnatlinkuserdirectory + D['VocolaUserDirectory'] = self.GetTextctrlvocolauserdirectory + D['UnimacroUserDirectory'] = self.GetTextctrlunimacrouserdirectory + D['WindowsVersion'] = self.frame.infopanel.GetTextctrlwindowsversion + D['VocolaTakesLanguages'] = self.GetCheckboxvocolatakeslanguages + D['VocolaTakesUnimacroActions'] = self.GetCheckboxvocolatakesunimacroactions +## D['VocolaCommandFilesEditor'] = self.GetTextctrlvocolaeditor + D['DebugCallback'] = self.GetCheckboxdebugcallbackoutput + D['DebugLoad'] = self.GetCheckboxdebugload + #D['NatlinkDebug'] = self.GetCheckboxnatlinkdebug + D['DNSIniDir'] = self.frame.infopanel.GetTextctrldnsinifilepath + D['natlinkIsEnabled'] = self.GetButtonnatlinkenable + D['vocolaIsEnabled'] = self.GetButtonvocolaenable + + D['unimacroIsEnabled'] = self.GetButtonunimacroenable + D['userIsEnabled'] = self.GetButtonuserenable + # D['UnimacroEnable'] = self.GetTextctrlunimacroinifilesdirectory + D['UnimacroIniFilesEditor'] = self.GetTextctrlunimacroeditor + # D['IncludeUnimacroInPythonPath'] = self.GetIncludeunimacroinpythonpath + self.checkboxes = ['VocolaTakesLanguages', + 'VocolaTakesUnimacroActions', + 'DebugCallback', 'DebugLoad', + #'NatlinkDebug', + #'IncludeUnimacroInPythonPath' + ] + return D + + def error(self, text): + """put error message on log panel and on status line""" + print text + print '-'*60 + self.frame.SetStatusText(text + ' (see log)') + + def setstatus(self, text): + """put message on log panel and on status line""" + print text + self.frame.SetStatusText(text) + + def setInfo(self, leaveStatus=None): + """extract data for the info controls + """ + self.parent.Freeze() + D = self.config.getNatlinkStatusDict() +## print 'StatusDict:' +## for k,v in D.items(): +## if v: +## print '%s: %s'% (k,v) +## print '-------' + newStatus = {} # dict with keys Natlink, Vocola, Unimacro, values (value, changed) each of them + + + try: + changed = 0 + for key in D: + if key in ["DNSInstallDir", "DNSIniDir"]: + pass + + if key in self.functions and self.functions[key]: + func = self.functions[key] +## if func == None: +## print "no getter function for %s"% key +## continue + value = D[key] + thisOneChanged = 0 + if value != self.startInfo[key]: + thisOneChanged = 1 + changed = 1 + if key in self.checkboxes: + # value = func().Value + if self.firstThaw: + if value: + func().SetValue(True) + else: + func().SetValue(False) + + if thisOneChanged: + func().SetForegroundColour(wx.RED) + else: + func().SetForegroundColour(wx.BLACK) + else: + if key in ["DNSInstallDir", "DNSIniDir"]: + pass + # no checkbox: + label = str(value) + if key == 'DNSVersion': + # DNSFullVersion gives different information as + # natspeak help window + label = '%s'% D[key] + elif key == 'PythonVersion': + # internal version (for pyd, I believe) is eg + # take first word of Fullversion as well. + #fullPart = D['PythonFullVersion'] + #label = '%s (%s)'% (D[key], fullPart.split()[0]) + label = '%s'% D[key] + elif value == -1 and key in ["DNSInstallDir", "DNSIniDir"]: + thisOneChanged = 1 + label = "Please choose a valid path" + self.urgentMessage = "Invalid DNS path, see info panel" + if key.endswith('IsEnabled'): + if value: + label = 'Disable' + else: + label = 'Enable' + # compose newStatus for status text control: + part = key.split('Is')[0] + newStatus[part] = (value, thisOneChanged) + func().SetLabel(label) + if thisOneChanged: + func().SetForegroundColour(wx.RED) + else: + func().SetForegroundColour(wx.BLACK) + + # undo button: + undoButton = self.GetButtonundo() + if self.undoList: + undoButton.Enable(True) + else: + undoButton.Enable(False) + + if D['natlinkIsEnabled']: + value = True + else: + value = False + for key in ['VocolaTakesLanguages', + 'vocolaIsEnabled', 'unimacroIsEnabled', 'userIsEnabled', + ]: + if key in self.functions and self.functions[key]: + control = self.functions[key]() + control.Enable(value) + + self.composeStatusLine(newStatus) + self.urgentStatusLine(self.urgentMessage) + self.urgentMessage = None + + finally: + self.parent.Thaw() + self.firstThaw = False + + def composeStatusLine(self, status): + """takes a dict with Natlink, Vocola, Unimacro as keys, + and a tuple (value, changed) as values. Value=0 means disable + """ + L = [] + somethingChanged = 0 + for part in ('Natlink', 'Vocola', 'Unimacro', 'User'): + value, changed = status[part.lower()] + if value: + enableddisabled = 'enabled' + else: + enableddisabled = 'disabled' + if part == 'User': + part = 'UserDirectory' + if changed: + somethingChanged = 1 + line = '%s will be %s'% (part, enableddisabled) + line = line.upper() + L.append(line) + else: + line = '%s is %s'% (part, enableddisabled) + L.append(line) + + if part == 'Natlink' and enableddisabled == 'disabled': + break # stop further status info + + statusLine = '; '.join(L) + control = self.GetTextctrlstatus() + control.SetValue(statusLine) + if somethingChanged: + control.SetForegroundColour(wx.RED) + else: + control.SetForegroundColour(wx.BLACK) + + def urgentStatusLine(self, statusString=None): + """writes a urgent message to the status text control + """ + if not statusString: return + control = self.GetTextctrlstatus() + control.SetValue(statusString) + control.SetForegroundColour(wx.RED) + + + + def do_command(self, *args, **kw): + """a single letter, optionally followed by a path + + If you want undo information: + Provide as 'undo' keyword argument + + for single arguments (checkboxes) provide the inverted character as undo + (as is done in do_checkboxcommand) + eg self.do_command('b', undo='B') + + for other commands (with paths) 'undo' must be provided as keyword argument, + the parameters MUST be a tuple, length 1 for single letters, length 2 if a + path is provided. + eg self.do_command('V', undo=('v', 'path/to/previous')) + + when calling from undo button, provide 'noundo' = 1 as keyword argument. + + + """ + if len(args) < 1: + print 'empty command %s'% `args` + return + if len(args) > 2: + print 'too many posional arguments: %s'% `args` + return + letter = args[0] + if len(args) == 2: + pathName = args[1] + else: + pathName = 'dummy' + funcName = 'do_%s'% letter + func = getattr(self.cli, funcName, None) + if not func: + mess = 'invalid command: %s'% letter + print mess + return mess + + try: + result = func(pathName) + except ElevationError: + e = sys.exc_info()[1] + + mess = 'This command needs elevated mode: %s'% e.message + mess2 = mess + '\n\nClose this program and run "start_configurenatlink.py"' + self.error(mess) + self.warning(mess2) + return mess + + # append to undoList + if not 'undo' in kw: + self.setInfo() + return result + undoInfo = kw['undo'] + if type(undoInfo) == types.TupleType and len(undoInfo) in [1,2]: + undo = undoInfo + elif isinstance(undoInfo, basestring): + undo = (undoInfo,) + else: + print 'invalid undoInfo from button: %s'% `undoInfo` + return result + self.undoList.append(undo) + self.setInfo(leaveStatus=result) + return result + + def do_checkboxcommand(self, letter, control): + """take value from control and do the command in upper or lowercase + value = 1 (checked) lowercase command + value = 0 (unchecked) uppercase command + + """ + value = control.GetValue() + if value: + doLetter = letter.lower() + undoLetter = letter.upper() + else: + doLetter = letter.upper() + undoLetter = letter.lower() + result = self.do_command(doLetter, undo=undoLetter) + if not result: + self.setstatus("checkbox option changed to %s; restart %s to take effect"% (value, self.DNSName)) + + + # WDR: methods for configurenatlink + def GetCheckboxvocolatakesunimacroactions(self): + return self.FindWindowById( ID_CHECKBOXVocolaTakesUnimacroActions ) + + # def GetIncludeunimacroinpythonpath(self): + # return self.FindWindowById( ID_IncludeUnimacroInPythonPath ) + + def GetTextctrlunimacroeditor(self): + return self.FindWindowById( ID_TEXTCTRLunimacroeditor ) + + def GetTextctrlunimacroinifilesdirectory(self): + return self.FindWindowById( ID_TEXTCTRLunimacroinifilesDirectory ) + + def GetTextctrlvocolaeditor(self): + return self.FindWindowById( ID_TEXTCTRLVocolaEditor ) + + def GetButtonvocolaeditor(self): + return self.FindWindowById( ID_BUTTONVocolaEditor ) + + def GetTextctrlstatus(self): + return self.FindWindowById( ID_TEXTCTRLstatus ) + + def GetButtonvocolaenable(self): + return self.FindWindowById( ID_BUTTONVocolaEnable ) + + + def GetTextctrlvocolauserdirectory(self): + return self.FindWindowById( ID_TEXTCTRLvocolauserdirectory ) + + def GetTextctrlunimacrouserdirectory(self): + return self.FindWindowById( ID_TEXTCTRLunimacrouserdirectory ) + + + def GetTextctrlnatlinkuserdirectory(self): + return self.FindWindowById( ID_TEXTCTRLnatlinkuserdirectory ) + + #def GetCheckboxnatlinkdebug(self): + # return self.FindWindowById( ID_CHECKBOXNatlinkDebug ) + + def GetButtonundo(self): + return self.FindWindowById( ID_BUTTONUndo ) + + def GetButtonnatlinkenable(self): + return self.FindWindowById( ID_BUTTONNatlinkEnable ) + + def GetButtonnatlinkenable(self): + return self.FindWindowById( ID_BUTTONNatlinkEnable ) + + def GetButtonunimacroenable(self): + return self.FindWindowById( ID_BUTTONUnimacroEnable ) + + def GetButtonuserenable(self): + return self.FindWindowById( ID_BUTTONUserEnable ) + + def GetCheckboxdebugcallbackoutput(self): + return self.FindWindowById( ID_CHECKBOXDebugCallbackOutput ) + + def GetCheckboxdebugload(self): + return self.FindWindowById( ID_CHECKBOXDebugLoad ) + + # def GetCheckboxdebugoutput(self): + # return self.FindWindowById( ID_CHECKBOXDebugOutput ) + + def GetCheckboxvocolatakeslanguages(self): + return self.FindWindowById( ID_CHECKBOXVocolaTakesLanguages ) + + def GetCheckboxenablenatlink(self): + return self.FindWindowById( ID_CHECKBOXEnableNatlink ) + + def GetTextctrldnsinifilepath(self): + return self.FindWindowById( ID_TEXTCTRLdnsinifilepath ) + + def GetTextctrldnsinstallpath(self): + return self.FindWindowById( ID_TEXTCTRLDNSinstallpath ) + + def GetTextctrlpythonversion(self): + return self.FindWindowById( ID_TEXTCTRLpythonversion ) + + def GetTextctrldnsversion(self): + return self.FindWindowById( ID_TEXTCTRLDNSversion ) + + + def GetTextctrlregisternatlink(self): + return self.FindWindowById( ID_TEXTCTRLregisternatlink ) + + # WDR: handler implementations for configurenatlink + + def OnCBVocolaTakesUnimacroActions(self, event): + letter = 'a' + control = self.GetCheckboxvocolatakesunimacroactions() + self.do_checkboxcommand(letter, control) + + + # def OnButtonIncludeUnimacroInPythonPath(self, event): + # letter = 'f' + # control = self.GetIncludeunimacroinpythonpath() + # self.do_checkboxcommand(letter, control) + + def OnButtonVocolaCompatibility(self, event): + title = "Unimacro features can be used by Vocola" + dlg = DialogUnimacroVocolaCompatibiliy(self, title=title) +## dlg.SetText(text) + answer = dlg.ShowModal() + if answer: + # print 'answer: %s'% answer + if answer%2: + print "(re)copy Unimacro.vch file to Vocola user commands directory" + doLetter = 'l' + statustext = 'Copied Unimacro.vch file to Vocola user commands directory' + self.do_command(doLetter) + self.setstatus(statustext) + self.setInfo() + answer -= 1 + if answer%4: + print 'remove "include Unimacro.vch" lines from all Vocola command files in your Vocola user commands directory' + doLetter = 'M' + undoLetter = "m" + statustext = 'Removed "include Unimacro.vch" lines from all Vocola command files in your Vocola user commands directory' + self.do_command(doLetter, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + answer -= 2 + if answer == 4: + print 'add "include Unimacro.vch" lines to all Vocola command files in your Vocola user commands directory' + doLetter = 'm' + undoLetter = "M" + statustext = 'added "include Unimacro.vch" lines to all Vocola command files in your Vocola user commands directory' + self.do_command(doLetter, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + else: + print 'nothing chosen' + + def OnButtonUnimacroEditor(self, event): + D = self.config.getNatlinkStatusDict() + + doLetter = 'p' + undoLetter = 'P' + statustext = 'Unimacro Editor is specified, this will take effect after you restart %s'% self.DNSName + + # ask for the correct directory: + dlg = wx.FileDialog(self.frame, "Choose the filename of your favorite Unimacro INI files editor", + style=wx.DD_DEFAULT_STYLE) + ## search for Unimacro directory as proposal: + old_path = self.config.isValidPath(D['UnimacroIniFilesEditor'], wantFile=1) + if not old_path: + old_path = self.config.isValidPath(self.config.userregnl.get('OldUnimacroIniFilesEditor'), + wantFile=1) + if old_path: + dlg.SetPath(old_path) + else: + old_path = self.config.isValidPath("%PROGRAMFILES%", wantDirectory=1) + if old_path: + dlg.SetDirectory(old_path) + dlg.SetMessage('Choose the filename of your favorite Unimacro INI files editor; Cancel for return to default') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + if new_path and os.path.isfile(new_path) and new_path.lower().endswith('.exe'): + pass + else: + self.setstatus("no new valid (.exe) file specified") + return + else: + if old_path: + self.setstatus("Pressed Cancel, return to default") + self.do_command( undoLetter, undo=(doLetter, old_path) ) + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + +# def OnButtonVocolaEditor(self, event): +# D = self.config.getNatlinkStatusDict() +# +# doLetter = 'w' +# undoLetter = 'W' +# statustext = 'Vocola Editor is specified, this will take effect after you restart %s'% self.DNSName +# +# # ask for the correct directory: +# dlg = wx.FileDialog(self.frame, "Choose the filename of your favorite editor please", +# style=wx.DD_DEFAULT_STYLE) +# ## search for Unimacro directory as proposal: +# old_path = D['VocolaCommandFilesEditor'] +# Path = nc.getExtendedEnv("PROGRAMFILES") +# dlg.SetPath(Path) +# dlg.SetMessage('Please choose the filename of your favorite editor please\nPress cancel to return to default') +# if dlg.ShowModal() == wx.ID_OK: +# new_path = dlg.GetPath() +# if new_path and os.path.isfile(new_path) and new_path.lower().endswith('.exe'): +# pass +# else: +# self.setstatus("no new valid (.exe) file specified") +# return +# else: +# self.setstatus("Pressed Cancel, return to default") +# self.do_command( undoLetter, undo=(doLetter, old_path) ) +# return +# self.do_command(doLetter,new_path, undo=undoLetter) +# self.setstatus(statustext) +# self.setInfo() + + + def OnButtonLogInfo(self, event): + self.cli.do_i("dummy") + self.warning("See log panel") + + def OnButtonHelp5(self, event): + print '---help on re(register) natlink.pyd' + print 'note the letters correspond to the commands in the self.cli (command line interface)' + self.cli.help_r() + text = \ +""" +Help about re(register) natlink.pyd you will find in the log panel + +About this configure program: + +All actions are performed immediate, mostly doing something +in the natlinkstatus.ini file of Natlink (in the MacroSystem/Core directory). + +What is changed is shown in red. The Undo button undoes these actions. + +If, for example, Vocola shows the button "Enable", it is currently disabled. + +In order to let the changes take effect, you have to restart NatSpeak. + +For the actions Enable/Disable Natlink and unregister/(re)register natlink.pyd +you need "elevated mode". +This is established by running "start_configurenatlink.py". + +""" + self.warning(text) + + + def OnButtonHelp4(self, event): + text = \ +"""User Grammar files can be activated/deactivated by specifying the UserDirectory. + +This directory should NOT be Unimacro or MacroSystem, as these are for Vocola and Unimacro. + +Dragonfly users can use this option. +""" + self.warning(text) + + def OnButtonHelp3(self, event): + print '---help on Enable/Disable Unimacro:' + print 'note the letters correspond to the commands in the self.cli (command line interface)' + self.cli.help_n() + self.cli.help_o() + self.cli.help_p() + self.cli.help_l() # includes help for m and M + text = \ +""" +Unimacro is enabled by specifying the UnimacroUserDirectory. + +When you disable Unimacro, this UnimacroUserDirectory setting is cleared from the natlinkstatus.ini file. + +When Unimacro is enabled, you can also specify: + - a program for editing these user (INI) files, default is Notepad + +Vocola can use Unimacro features, by checking the checkbox in the Vocola section. + +Via the dialog Vocola Compatibility, you can handle things around the include file, Unimacro.vch. + +This file is automatically copied from (...)\Unimacro\vocola_compatibility into the VocolaUserDirectory when Vocola is started. + +More about this in the "Vocola Compatibility" dialog. +""" + + self.warning(text) + + + def OnButtonClose(self, event): + if self.undoList: + self.warning('Please restart %s\n\n(in order to let the changes take effect)'% self.DNSName) + self.parent.frame.Destroy() + + def OnButtonUndo(self, event): + if self.undoList: + self.firstThaw = True + cmd = self.undoList.pop() + self.do_command(*cmd) + # self.getNatlinkStatusDict() + self.setstatus("Did undo") + + + def OnButtonUnimacroEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + letter = 'o' + if D['unimacroIsEnabled']: + doLetter = letter.upper() + undoLetter = letter.lower() + statustext = 'Unimacro is DISABLED, this will take effect after you restart %s'% self.DNSName + prevPath = D['UnimacroUserDirectory'] + undoCmd = (undoLetter, prevPath) + self.do_command(doLetter, undo=undoCmd) + self.setstatus(statustext) + self.setInfo() + return + # now go for enable: + doLetter = letter.lower() + undoLetter = letter.upper() + statustext = 'Unimacro/user grammars is ENABLED, this will take effect after you restart %s'% self.DNSName + + # ask for the correct directory: + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) + ## search for Unimacro User directory as proposal: + oldPath = self.config.userregnl.get('OldUnimacroUserDirectory') + if oldPath: + oldPath = self.config.isValidPath(oldPath) + if not oldPath: + tryHome = self.config.isValidPath("~") + if not tryHome: + tryHome = self.config.isValidPath("%PERSONAL%") + if tryHome: + oldPath = tryHome + if oldPath: + dlg.SetPath(oldPath) + dlg.SetMessage('Specify the UnimacroUserDirectory, where your ini files are/will be located; this also enables Unimacro.') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + if new_path and os.path.isdir(new_path): + pass + else: + self.setstatus("no new valid directory specified") + return + else: + self.setstatus("nothing specified") + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + def OnButtonUserEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + letter = 'n' + if D['userIsEnabled']: + doLetter = letter.upper() + undoLetter = letter.lower() + statustext = 'User Grammars are DISABLED, this will take effect after you restart %s'% self.DNSName + prevPath = D['UserDirectory'] + undoCmd = (undoLetter, prevPath) + self.do_command(doLetter, undo=undoCmd) + self.setstatus(statustext) + self.setInfo() + return + # now go for enable: + doLetter = letter.lower() + undoLetter = letter.upper() + statustext = 'User Grammars are ENABLED, this will take effect after you restart %s'% self.DNSName + + # ask for the correct directory: + dlg = wx.DirDialog(self.frame, "Please choose the UserDirectory, where your Natlink grammar files are located.", + style=wx.DD_DEFAULT_STYLE-wx.DD_NEW_DIR_BUTTON) + ## search for previous directory or other default: + oldPath = self.config.userregnl.get('OldUserDirectory') + if oldPath: + oldPath = self.config.isValidPath(oldPath) + if not oldPath: + tryNatlink = os.path.join(D['CoreDirectory'], '..', '..', '..') + oldPath = self.config.isValidPath(tryNatlink) + if oldPath: + dlg.SetPath(oldPath) + dlg.SetMessage('Please specify the UserDirectory, where user grammar files are located') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + new_path = self.config.isValidPath(new_path, wantDirectory=1) + if not new_path: + self.setstatus("no new valid directory specified") + elif new_path == D['UnimacroDirectory']: + self.setstatus("Please do not specify Unimacro as UserDirectory") + return + elif new_path == D['BaseDirectory']: + self.setstatus("Please do not specify BaseDirectory, used by Vocola, as UserDirectory") + return + else: + self.setstatus("nothing specified") + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + def OnButtonVocolaEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + isValidPath = self.config.isValidPath + letter = 'v' + if D['vocolaIsEnabled']: + doLetter = letter.upper() + undoLetter = letter.lower() + statustext = 'Vocola is DISABLED, this will take effect after you restart %s'% self.DNSName + prevPath = D['VocolaUserDirectory'] + undoCmd = (undoLetter, prevPath) + self.do_command(doLetter, undo=undoCmd) + self.setstatus(statustext) + self.setInfo() + return + # now go for enable: + doLetter = letter.lower() + undoLetter = letter.upper() + statustext = 'Vocola is ENABLED, this will take effect after you restart %s'% self.DNSName + + + + # ask for the correct directory: + dlg = wx.DirDialog(self.frame, "Choose a directory please", + style=wx.DD_DEFAULT_STYLE) + ## search for Vocola directory as proposal: + oldPath = self.config.userregnl.get('OldVocolaUserDirectory') + if oldPath: + oldPath = self.config.isValidPath(oldPath) + if not oldPath: + tryHome = self.config.isValidPath("~") + if not tryHome: + tryHome = self.config.isValidPath("%PERSONAL%") + if tryHome: + oldPath = tryHome + if oldPath: + dlg.SetPath(oldPath) + dlg.SetMessage('Specify the VocolaUserDirectory, where your Vocola Command Files are located; this also enables Vocola') + if dlg.ShowModal() == wx.ID_OK: + new_path = dlg.GetPath() + if new_path and os.path.isdir(new_path): + pass + else: + self.setstatus("no new valid directory specified") + return + else: + self.setstatus("nothing specified") + return + self.do_command(doLetter,new_path, undo=undoLetter) + self.setstatus(statustext) + self.setInfo() + + + def OnButtonNatlinkEnableDisable(self, event): + D = self.config.getNatlinkStatusDict() + letter = 'e' + if D['natlinkIsEnabled']: + # disable: + doLetter = letter.upper() + undoLetter = letter.lower() + self.do_command(doLetter, undo=undoLetter) + if self.config.NatlinkIsEnabled(): + statustext = 'Natlink is NOT DISABLED, please run this program in "elevated mode"' + else: + statustext = 'Natlink is DISABLED, this will take effect after you restart %s'% self.DNSName + + else: + # enable: + doLetter = letter.lower() + undoLetter = letter.upper() + self.do_command(doLetter, undo=undoLetter) + if self.config.NatlinkIsEnabled(): + statustext = 'Natlink is ENABLED, this will take effect after you restart %s'% self.DNSName + else: + statustext = 'Natlink is NOT ENABLED, please run this program in "elevated mode"' + self.setstatus(statustext) + + #def OnCBNatlinkDebug(self, event): ## obsolete, QH 26-08-2013 + # letter = 'g' + # control = self.GetCheckboxnatlinkdebug() + # self.do_checkboxcommand(letter, control) + + + def OnCBVocolaTakesLanguages(self, event): + letter = 'b' + control = self.GetCheckboxvocolatakeslanguages() + self.do_checkboxcommand(letter, control) + + def OnCBDebugCallback(self, event): + letter = 'y' + control = self.GetCheckboxdebugcallbackoutput() + self.do_checkboxcommand(letter, control) + + + def OnCBDebugLoad(self, event): + letter = 'x' + control = self.GetCheckboxdebugload() + self.do_checkboxcommand(letter, control) + + + + def OnButtonHelp2(self, event): + print '---help on Enable/disable Vocola:' + print 'note the letters correspond to the commands in the self.cli (command line interface)' + self.cli.help_b() + self.cli.help_a() + print '---help on additional Vocola options:' + L = [] + L.append("Vocola is enabled by specifying a directory (VocolaUserDirectory)") + L.append("where the Vocola Command files are/will be located.") + L.append("") + L.append("When you disable Vocola, this setting is cleared in the natlinkstatus.ini file.") + L.append("") + L.append('When you use more languages, eg speech profiles for English and Dutch, please read the log panel for the "Vocola multi languages" option.') + L.append("") + L.append('When you want to use Unimacro actions in your Vocola comman files, you can check the "Vocola takes Unimacro Actions" option.') + L.append("More information about this on the Natlink/Vocola/Unimacro website") + L.append("") + self.warning('\n'.join(L)) + + def OnButtonHelp1(self, event): + print '---help on Enable Natlink and corresponding functions:' + print 'note the letters correspond to the commands in the self.cli (command line interface)' + self.cli.help_e() + print '---help on Natlink debug options:' + self.cli.help_x() + text = """ + +This Enables or Disables Natlink. The state of Natlink is shown in the Status bar above, and is the opposite of the button text. + +Natlink should be enabled before you can use Vocola and/or Unimacro or other python grammars, like Dragonfly. + +When Natlink is disabled, Vocola and Unimacro will -- consequently -- be disabled too. + +Note that you need elevated mode and possibly Dragon be switched off before you can Enable or Disable Natlink. + +At first run after you installed Natlink, natlink.pyd is registered silently, but Natlink is still Disabled. + +So in that case click on Enable for getting started. +""" + self.warning(text) + + + + def OnButtonUnregister(self, event): + self.do_command('R') + self.warning("Close this program, and also close %s\n\nthen you run this program again in elevated mode via start_configurenatlink.py"% self.DNSName) + # self.urgentMessage = "Close this program, restart %s, possibly computer"% self.DNSName + self.setInfo() + + def OnButtonRegister(self, event): + self.do_command('r') + self.warning("Close this program, %s, all Python applications and\n\npossibly restart your computer\n\nbefore you run this program again!"% self.DNSName) + self.urgentMessage = "Close this program, restart %s, possibly computer"% self.DNSName + self.setInfo() + + +class MyFrame(wx.Frame): + def __init__(self, parent, id, title, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE ): + wx.Frame.__init__(self, parent, id, title, pos, size, style) + self.app = parent + self.Bind(wx.EVT_MENU, self.OnMenuHelp, id=ID_MENUhelp) + self.Bind(wx.EVT_MENU, self.OnMenuClose, id=ID_MENUClose) + + self.CreateStatusBar(1) + self.SetStatusText("This is the Configure Natlink & Vocola & Unimacro GUI") + self.CreateMyMenuBar() + # insert main window here + self.nb = wx.Notebook(self, -1, name='panel', + pos=wx.Point(0, 0), size=wx.Size(592, 498), style=0) + + self.log = wx.TextCtrl(self.nb, -1, name='log', + style=wx.TE_READONLY | wx.TE_MULTILINE|wx.TE_NOHIDESEL, value='') + sys.stdout = Stdout(self.log) +## self.errors = wx.TextCtrl(self.nb, -1, name='errors', +## style=wx.TE_READONLY | wx.TE_MULTILINE|wx.TE_NOHIDESEL, value='') +## sys.stderr = Stderr(self.errors) + sys.stderr = sys.stdout + + self.nb.AddPage(imageId=-1, page=self.log, select=False, + text='log') +## self.nb.AddPage(imageId=-1, page=self.errors, select=False, +## text='errors') + self.nb.frame = self + self.infopanel =InfoPanel(self.nb, -1, name='infopanel') + self.nb.AddPage(imageId=-1, page=self.infopanel, select=False, + text='info') + self.cpanel = ConfigureNatlinkPanel(self.nb, -1, name='configurepanel') + self.nb.AddPage(imageId=-1, page=self.cpanel, select=True, + text='configure') + self.infopanel.cpanel = self.cpanel +## self.nb = wx.Notebook(name='notebook', parent=self, style=0) + + + # WDR: methods for MyFrame + def CreateMyMenuBar(self): + self.SetMenuBar(MyMenuBarFunc() ) + + def OnMenuClose(self, event): + self.Destroy() + + def OnMenuHelp(self, event): + text = ['This configure GUI makes it possible to configure Natlink, ', + 'including Vocola and Unimacro',"", + 'Detailed help is given through various help buttons and in the "log" panel',"" + 'Written by Quintijn Hoogenboom, February, 2008/May, 2009/April, 2018', + 'See also http://qh.antenna.nl/unimacro'] + self.warning('\n'.join(text)) + + + def warning(self, text, title='Message from Configure Natlink GUI'): + dlg = wx.MessageDialog(self, text, title, + wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + + # WDR: handler implementations for MyFrame + +class Stdout: + def __init__(self, object): + self.writeto = object +## self.write('stdout started') + + def flush(self): + pass + def write(self, t): + """write to output""" + self.writeto.AppendText(t) + +class Stderr: + def __init__(self, txtctrl): + self.window = txtctrl + + def flush(self): + pass + + def write(self, t): + """write to output""" + self.window.AppendText(t) + +class MyApp(wx.App): + def OnInit(self): + # wx.InitAllImageHandlers() + self.frame = MyFrame( None, -1, "Configure Natlink & Vocola & Unimacro", + [110,80], [750,735] ) + self.frame.Show(True) + return True +try: + app = MyApp(True) +except: + import sys, traceback + # traceback.print_exception(type, value, traceback[, limit[, file]]) + traceback.print_exc(file=open("configurenatlink_error.txt", "w")) + mess = traceback.format_exc() + mess += '\n\nMore info in configurenatlink_error.txt in the directory "(C:\\natlink)\\natlink\\confignatlinkvocolaunimacro"' + windowsMessageBox(mess, "Error at startup of configurenatlink") + sys.exit(1) +else: + app.MainLoop() diff --git a/NatlinkConfigure/configurenatlink.wdr b/NatlinkConfigure/configurenatlink.wdr new file mode 100644 index 00000000..fe9b4dde Binary files /dev/null and b/NatlinkConfigure/configurenatlink.wdr differ diff --git a/NatlinkConfigure/configurenatlink_wdr.py b/NatlinkConfigure/configurenatlink_wdr.py new file mode 100644 index 00000000..56d0c49e --- /dev/null +++ b/NatlinkConfigure/configurenatlink_wdr.py @@ -0,0 +1,534 @@ +# -*- coding: UTF-8 -*- + +#----------------------------------------------------------------------------- +# Python source generated by wxDesigner from file: configurenatlink.wdr +# Do not modify this file, all changes will be lost! +#----------------------------------------------------------------------------- + +# Include wxPython modules +import wx +import wx.grid +# import wx.animate + +# Window functions + +ID_TEXTCTRLstatus = 10000 +ID_BUTTONNatlinkEnable = 10001 +ID_CHECKBOXDebugCallbackOutput = 10002 +ID_CHECKBOXDebugLoad = 10003 +ID_BUTTONHelp1 = 10004 +ID_BUTTONVocolaEnable = 10005 +ID_TEXTvocolauserdir = 10006 +ID_TEXTCTRLvocolauserdirectory = 10007 +ID_TEXT = 10008 +ID_CHECKBOXVocolaTakesLanguages = 10009 +ID_CHECKBOXVocolaTakesUnimacroActions = 10010 +ID_BUTTONHelp2 = 10011 +ID_BUTTONUnimacroEnable = 10012 +ID_TEXTunimacrouserdir = 10013 +ID_TEXTCTRLunimacrouserdirectory = 10014 +ID_BUTTONUnimacroEditor = 10015 +ID_TEXTCTRLunimacroeditor = 10016 +ID_BUTTONVocolaCompatibiliy = 10017 +ID_BUTTONHelp3 = 10018 +ID_BUTTONUserEnable = 10019 +ID_TEXTnatlinkuserdirectory = 10020 +ID_TEXTCTRLnatlinkuserdirectory = 10021 +ID_BUTTONHelp4 = 10022 +ID_BUTTONregister = 10023 +ID_BUTTONunregister = 10024 +ID_BUTTONUndo = 5100 +ID_BUTTONClose = 10025 +ID_BUTTONHelp5 = 10026 + +def MainWindow( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item2 = wx.StaticBox( parent, -1, "Status" ) + item2.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item1 = wx.StaticBoxSizer( item2, wx.VERTICAL ) + + item3 = wx.FlexGridSizer( 0, 1, 0, 0 ) + + item4 = wx.TextCtrl( parent, ID_TEXTCTRLstatus, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item4.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item4, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item3.AddGrowableCol( 0 ) + + item1.Add( item3, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item1, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item6 = wx.StaticBox( parent, -1, "Natlink" ) + item6.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item5 = wx.StaticBoxSizer( item6, wx.VERTICAL ) + + item7 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item8 = wx.Button( parent, ID_BUTTONNatlinkEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item7.Add( item8, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item9 = wx.BoxSizer( wx.HORIZONTAL ) + + item10 = wx.CheckBox( parent, ID_CHECKBOXDebugCallbackOutput, "Debug Callback", wx.DefaultPosition, wx.DefaultSize, 0 ) + item9.Add( item10, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item11 = wx.CheckBox( parent, ID_CHECKBOXDebugLoad, "Debug load output", wx.DefaultPosition, wx.DefaultSize, 0 ) + item9.Add( item11, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item7.Add( item9, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item12 = wx.Button( parent, ID_BUTTONHelp1, "Help-&1", wx.DefaultPosition, wx.DefaultSize, 0 ) + item7.Add( item12, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item7.AddGrowableCol( 1 ) + + item5.Add( item7, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item5, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item14 = wx.StaticBox( parent, -1, "Vocola" ) + item14.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item13 = wx.StaticBoxSizer( item14, wx.VERTICAL ) + + item15 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item16 = wx.Button( parent, ID_BUTTONVocolaEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item16, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item17 = wx.StaticText( parent, ID_TEXTvocolauserdir, "Vocola User Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item17, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item18 = wx.TextCtrl( parent, ID_TEXTCTRLvocolauserdirectory, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item18.SetBackgroundColour( wx.LIGHT_GREY ) + item15.Add( item18, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item19 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item19, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item20 = wx.CheckBox( parent, ID_CHECKBOXVocolaTakesLanguages, "Vocola multi languages", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item20, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item21 = wx.BoxSizer( wx.HORIZONTAL ) + + item22 = wx.CheckBox( parent, ID_CHECKBOXVocolaTakesUnimacroActions, "Vocola takes Unimacro Actions", wx.DefaultPosition, [280,-1], 0 ) + item21.Add( item22, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item23 = wx.Button( parent, ID_BUTTONHelp2, "Help-&2", wx.DefaultPosition, wx.DefaultSize, 0 ) + item21.Add( item23, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item15.Add( item21, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item15.AddGrowableCol( 2 ) + + item13.Add( item15, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item13, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item25 = wx.StaticBox( parent, -1, "Unimacro" ) + item25.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item24 = wx.StaticBoxSizer( item25, wx.VERTICAL ) + + item26 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item27 = wx.Button( parent, ID_BUTTONUnimacroEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item27, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item28 = wx.StaticText( parent, ID_TEXTunimacrouserdir, "Unimacro User Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item28, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item29 = wx.TextCtrl( parent, ID_TEXTCTRLunimacrouserdirectory, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item29.SetBackgroundColour( wx.LIGHT_GREY ) + item26.Add( item29, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item30 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item30, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item31 = wx.Button( parent, ID_BUTTONUnimacroEditor, "Unimacro Editor", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item31, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item32 = wx.TextCtrl( parent, ID_TEXTCTRLunimacroeditor, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item32.SetBackgroundColour( wx.LIGHT_GREY ) + item26.Add( item32, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item33 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item33, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item34 = wx.Button( parent, ID_BUTTONVocolaCompatibiliy, "Vocola compatibility", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item34, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item35 = wx.BoxSizer( wx.HORIZONTAL ) + + item36 = wx.Button( parent, ID_BUTTONHelp3, "Help-&3", wx.DefaultPosition, wx.DefaultSize, 0 ) + item35.Add( item36, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item26.Add( item35, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item26.AddGrowableCol( 2 ) + + item24.Add( item26, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item24, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item38 = wx.StaticBox( parent, -1, "UserDirectory" ) + item37 = wx.StaticBoxSizer( item38, wx.VERTICAL ) + + item39 = wx.FlexGridSizer( 0, 4, 0, 0 ) + + item40 = wx.Button( parent, ID_BUTTONUserEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item39.Add( item40, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item41 = wx.StaticText( parent, ID_TEXTnatlinkuserdirectory, "Natlink User Directory:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) + item39.Add( item41, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item42 = wx.TextCtrl( parent, ID_TEXTCTRLnatlinkuserdirectory, "", wx.DefaultPosition, [80,-1], wx.TE_READONLY ) + item42.SetBackgroundColour( wx.LIGHT_GREY ) + item39.Add( item42, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item43 = wx.Button( parent, ID_BUTTONHelp4, "Help-&4", wx.DefaultPosition, wx.DefaultSize, 0 ) + item39.Add( item43, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item39.AddGrowableCol( 2 ) + + item37.Add( item39, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item37, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item45 = wx.StaticBox( parent, -1, "Repair" ) + item45.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item44 = wx.StaticBoxSizer( item45, wx.VERTICAL ) + + item46 = wx.FlexGridSizer( 0, 6, 0, 0 ) + + item47 = wx.Button( parent, ID_BUTTONregister, "(re)Register NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item47, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item48 = wx.Button( parent, ID_BUTTONunregister, "unRegister NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item48, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item49 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item49, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item50 = wx.Button( parent, ID_BUTTONUndo, "Undo", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item50, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item51 = wx.Button( parent, ID_BUTTONClose, "Close", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item51, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item52 = wx.Button( parent, ID_BUTTONHelp5, "Help-5", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item52, 0, wx.ALIGN_CENTER, 5 ) + + item46.AddGrowableCol( 2 ) + + item44.Add( item46, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item0.Add( item44, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +ID_TEXTDNSversion = 10027 +ID_TEXTCTRLDNSVersion = 10028 +ID_TEXTCTRLWindowsVersion = 10029 +ID_TEXTCTRLpythonversion = 10030 +ID_TEXTdnsinstallpath = 10031 +ID_TEXTCTRLDNSinstallpath = 10032 +ID_BUTTONchangednsinstallpath = 10033 +ID_BUTTONClearDNSInstallPath = 10034 +ID_TEXTdnsinifilepath = 10035 +ID_TEXTCTRLdnsinifilepath = 10036 +ID_BUTTONchangednsinifilepath = 10037 +ID_BUTTONClearDNSInifilePath = 10038 +ID_TEXTNatlinkCorePath = 10039 +ID_TEXTCTRLnatlinkcorepath = 10040 +ID_BUTTONLogInfo = 10041 +ID_BUTTONHelpInfo = 10042 + +def InfoWindow( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item2 = wx.StaticBox( parent, -1, "Info" ) + item1 = wx.StaticBoxSizer( item2, wx.VERTICAL ) + + item3 = wx.FlexGridSizer( 0, 4, 0, 0 ) + + item4 = wx.StaticText( parent, ID_TEXTDNSversion, "DNS version:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item5 = wx.BoxSizer( wx.HORIZONTAL ) + + item6 = wx.TextCtrl( parent, ID_TEXTCTRLDNSVersion, "", wx.DefaultPosition, [50,-1], wx.TE_READONLY ) + item6.SetBackgroundColour( wx.LIGHT_GREY ) + item5.Add( item6, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP|wx.BOTTOM, 5 ) + + item7 = wx.StaticText( parent, ID_TEXT, "Windows version:", wx.DefaultPosition, [180,-1], wx.ALIGN_RIGHT ) + item5.Add( item7, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item8 = wx.TextCtrl( parent, ID_TEXTCTRLWindowsVersion, "", wx.DefaultPosition, [50,-1], wx.TE_READONLY ) + item8.SetBackgroundColour( wx.LIGHT_GREY ) + item5.Add( item8, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.TOP|wx.BOTTOM, 5 ) + + item3.Add( item5, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item9 = wx.StaticText( parent, ID_TEXT, "Python version:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item9, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item10 = wx.TextCtrl( parent, ID_TEXTCTRLpythonversion, "", wx.DefaultPosition, [50,-1], wx.TE_READONLY ) + item10.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item10, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 10 ) + + item11 = wx.StaticText( parent, ID_TEXTdnsinstallpath, "DNS install path:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item11, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item12 = wx.TextCtrl( parent, ID_TEXTCTRLDNSinstallpath, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item12.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item12, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item13 = wx.Button( parent, ID_BUTTONchangednsinstallpath, "Change-&d", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item13, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item14 = wx.Button( parent, ID_BUTTONClearDNSInstallPath, "Clear-&D", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item14, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item15 = wx.StaticText( parent, ID_TEXTdnsinifilepath, "DNS ini file path:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item15, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item16 = wx.TextCtrl( parent, ID_TEXTCTRLdnsinifilepath, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item16.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item16, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item17 = wx.Button( parent, ID_BUTTONchangednsinifilepath, "Change-&c", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item17, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item18 = wx.Button( parent, ID_BUTTONClearDNSInifilePath, "Clear-&C", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item18, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item19 = wx.StaticText( parent, ID_TEXTNatlinkCorePath, "NatlinkCore path:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item19, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item20 = wx.TextCtrl( parent, ID_TEXTCTRLnatlinkcorepath, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item20.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item20, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item21 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item21, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item22 = wx.Button( parent, ID_BUTTONLogInfo, "Log info-&i", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item22, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item23 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item23, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item24 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item24, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item25 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item25, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item26 = wx.Button( parent, ID_BUTTONHelpInfo, "Help-&Info", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item26, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item3.AddGrowableCol( 1 ) + + item1.Add( item3, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item0.Add( item1, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +ID_CHECKBOXMakeUnimacroIncludeLines = 10043 +ID_CHECKBOXRemoveUnimacroIncludeLines = 10044 +ID_CHECKBOXRefreshUnimacroVch = 10045 +ID_BUTTONOK = 10046 +ID_BUTTONCancel = 10047 + +def DialogVocolaCombatibility( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item1 = wx.StaticText( parent, ID_TEXT, + "Vocola can profit from Unimacro features.\n" + "\n" + "If you wish to do so, switch on the checkbox \"Vocola takes Unimacro Actions\" in the configure panel.\n" + "\n" + "It is then useful to include the file \"Unimacro.vch\" as a wrapper around the Unimacro Shorthand Commands functions.\n" + "\n" + "With the options below, you can insert or remove an \"include\" line in all your Vocola Command Files, \n" + "and ensure the include file is copied into the correct directory. \n" + "This last action is sometimes needed after an update of the NatLink/Vocola/Unimacro system.\n" + "\n" + "Please check the options you want to be processed and press/call OK", + wx.DefaultPosition, wx.DefaultSize, 0 ) + item1.SetBackgroundColour( wx.WHITE ) + item0.Add( item1, 0, wx.ALIGN_CENTER|wx.ALL, 15 ) + + item2 = wx.CheckBox( parent, ID_CHECKBOXMakeUnimacroIncludeLines, "Insert a \"include Unimacro.vch;\" line in all your Vocola Command Files", wx.DefaultPosition, wx.DefaultSize, 0 ) + item0.Add( item2, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item3 = wx.CheckBox( parent, ID_CHECKBOXRemoveUnimacroIncludeLines, "Remove the \"include Unimacro.vch;\" lines from your Vocola Command Files", wx.DefaultPosition, wx.DefaultSize, 0 ) + item0.Add( item3, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item4 = wx.CheckBox( parent, ID_CHECKBOXRefreshUnimacroVch, "Copy the include file \"Unimacro.vch\" into your Vocola User Directory", wx.DefaultPosition, wx.DefaultSize, 0 ) + item0.Add( item4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item5 = wx.BoxSizer( wx.HORIZONTAL ) + + item6 = wx.Button( parent, ID_BUTTONOK, "OK", wx.DefaultPosition, wx.DefaultSize, 0 ) + item5.Add( item6, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item7 = wx.Button( parent, ID_BUTTONCancel, "Cancel", wx.DefaultPosition, wx.DefaultSize, 0 ) + item5.Add( item7, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item0.Add( item5, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +ID_TEXTahkexedir = 10048 +ID_TEXTCTRLAhkExeDir = 10049 +ID_BUTTONsetahkexedir = 10050 +ID_BUTTONclearahkexedir = 10051 +ID_TEXTahkuserdir = 10052 +ID_TEXTCTRLahkuserdir = 10053 +ID_BUTTONsetahkuserdir = 10054 +ID_BUTTONclearahkuserdir = 10055 +ID_BUTTONahkhelp = 10056 +ID_BUTTONcloseadvanced = 10057 +ID_BUTTONhelprepair = 10058 + +def ExtraWindow( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item2 = wx.StaticBox( parent, -1, "AutoHotkey" ) + item1 = wx.StaticBoxSizer( item2, wx.VERTICAL ) + + item3 = wx.FlexGridSizer( 0, 4, 0, 0 ) + + item4 = wx.StaticText( parent, ID_TEXTahkexedir, "Ahk Exe Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item5 = wx.TextCtrl( parent, ID_TEXTCTRLAhkExeDir, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item5.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item5, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item6 = wx.Button( parent, ID_BUTTONsetahkexedir, "Set", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item6, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item7 = wx.Button( parent, ID_BUTTONclearahkexedir, "Clear", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item7, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item8 = wx.StaticText( parent, ID_TEXTahkuserdir, "Ahk User Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item8, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item9 = wx.TextCtrl( parent, ID_TEXTCTRLahkuserdir, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item9.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item9, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item10 = wx.Button( parent, ID_BUTTONsetahkuserdir, "Set", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item10, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item11 = wx.Button( parent, ID_BUTTONclearahkuserdir, "Clear", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item11, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item12 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item12, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item13 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item13, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item14 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item14, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item15 = wx.Button( parent, ID_BUTTONahkhelp, "Help", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item15, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item3.AddGrowableCol( 1 ) + + item1.Add( item3, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item17 = wx.StaticBox( parent, -1, "Repair" ) + item17.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item16 = wx.StaticBoxSizer( item17, wx.VERTICAL ) + + item18 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item19 = wx.BoxSizer( wx.HORIZONTAL ) + + item20 = wx.Button( parent, ID_BUTTONregister, "(re)Register NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item19.Add( item20, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item21 = wx.Button( parent, ID_BUTTONunregister, "unRegister NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item19.Add( item21, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item18.Add( item19, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item22 = wx.BoxSizer( wx.HORIZONTAL ) + + item23 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, [20,20], 0 ) + item22.Add( item23, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item18.Add( item22, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item24 = wx.BoxSizer( wx.HORIZONTAL ) + + item25 = wx.Button( parent, ID_BUTTONcloseadvanced, "Close", wx.DefaultPosition, wx.DefaultSize, 0 ) + item24.Add( item25, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item26 = wx.Button( parent, ID_BUTTONhelprepair, "Help", wx.DefaultPosition, wx.DefaultSize, 0 ) + item24.Add( item26, 0, wx.ALIGN_CENTER, 5 ) + + item18.Add( item24, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item18.AddGrowableCol( 1 ) + + item16.Add( item18, 0, wx.FIXED_MINSIZE|wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.SHAPED, 0 ) + + item1.Add( item16, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item1, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +# Menubar functions + +ID_MENUClose = 10059 +ID_MENUFile = 10060 +ID_MENUhelp = 10061 + +def MyMenuBarFunc(): + item0 = wx.MenuBar() + + item1 = wx.Menu() + item1.Append( ID_MENUClose, "Close\tc", "" ) + item0.Append( item1, "File" ) + + item2 = wx.Menu() + item2.Append( ID_MENUhelp, "Help", "" ) + item0.Append( item2, "Help" ) + + return item0 + +# Toolbar functions + +# Bitmap functions + + +# End of generated file diff --git a/NatlinkConfigure/configurenatlink_wdrold.py b/NatlinkConfigure/configurenatlink_wdrold.py new file mode 100644 index 00000000..a2ef2123 --- /dev/null +++ b/NatlinkConfigure/configurenatlink_wdrold.py @@ -0,0 +1,534 @@ +# -*- coding: UTF-8 -*- + +#----------------------------------------------------------------------------- +# Python source generated by wxDesigner from file: configurenatlink.wdr +# Do not modify this file, all changes will be lost! +#----------------------------------------------------------------------------- + +# Include wxPython modules +import wx +import wx.grid +# import wx.animate + +# Window functions + +ID_TEXTCTRLstatus = 10000 +ID_BUTTONNatlinkEnable = 10001 +ID_CHECKBOXDebugCallbackOutput = 10002 +ID_CHECKBOXDebugLoad = 10003 +ID_BUTTONHelp1 = 10004 +ID_BUTTONVocolaEnable = 10005 +ID_TEXTvocolauserdir = 10006 +ID_TEXTCTRLvocolauserdirectory = 10007 +ID_TEXT = 10008 +ID_CHECKBOXVocolaTakesLanguages = 10009 +ID_CHECKBOXVocolaTakesUnimacroActions = 10010 +ID_BUTTONHelp2 = 10011 +ID_BUTTONUnimacroEnable = 10012 +ID_TEXTunimacrouserdir = 10013 +ID_TEXTCTRLunimacrouserdirectory = 10014 +ID_BUTTONUnimacroEditor = 10015 +ID_TEXTCTRLunimacroeditor = 10016 +ID_BUTTONVocolaCompatibiliy = 10017 +ID_BUTTONHelp3 = 10018 +ID_BUTTONUserEnable = 10019 +ID_TEXTnatlinkuserdirectory = 10020 +ID_TEXTCTRLnatlinkuserdirectory = 10021 +ID_BUTTONHelp4 = 10022 +ID_BUTTONregister = 10023 +ID_BUTTONunregister = 10024 +ID_BUTTONUndo = 5100 +ID_BUTTONClose = 10025 +ID_BUTTONHelp5 = 10026 + +def MainWindow( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item2 = wx.StaticBox( parent, -1, "Status" ) + item2.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item1 = wx.StaticBoxSizer( item2, wx.VERTICAL ) + + item3 = wx.FlexGridSizer( 0, 1, 0, 0 ) + + item4 = wx.TextCtrl( parent, ID_TEXTCTRLstatus, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item4.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item4, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item3.AddGrowableCol( 0 ) + + item1.Add( item3, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item1, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item6 = wx.StaticBox( parent, -1, "Natlink" ) + item6.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item5 = wx.StaticBoxSizer( item6, wx.VERTICAL ) + + item7 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item8 = wx.Button( parent, ID_BUTTONNatlinkEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item7.Add( item8, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item9 = wx.BoxSizer( wx.HORIZONTAL ) + + item10 = wx.CheckBox( parent, ID_CHECKBOXDebugCallbackOutput, "Debug Callback", wx.DefaultPosition, wx.DefaultSize, 0 ) + item9.Add( item10, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item11 = wx.CheckBox( parent, ID_CHECKBOXDebugLoad, "Debug load output", wx.DefaultPosition, wx.DefaultSize, 0 ) + item9.Add( item11, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item7.Add( item9, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item12 = wx.Button( parent, ID_BUTTONHelp1, "Help-&1", wx.DefaultPosition, wx.DefaultSize, 0 ) + item7.Add( item12, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item7.AddGrowableCol( 1 ) + + item5.Add( item7, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item5, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item14 = wx.StaticBox( parent, -1, "Vocola" ) + item14.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item13 = wx.StaticBoxSizer( item14, wx.VERTICAL ) + + item15 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item16 = wx.Button( parent, ID_BUTTONVocolaEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item16, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item17 = wx.StaticText( parent, ID_TEXTvocolauserdir, "Vocola User Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item17, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item18 = wx.TextCtrl( parent, ID_TEXTCTRLvocolauserdirectory, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item18.SetBackgroundColour( wx.LIGHT_GREY ) + item15.Add( item18, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item19 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item19, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item20 = wx.CheckBox( parent, ID_CHECKBOXVocolaTakesLanguages, "Vocola multi languages", wx.DefaultPosition, wx.DefaultSize, 0 ) + item15.Add( item20, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item21 = wx.BoxSizer( wx.HORIZONTAL ) + + item22 = wx.CheckBox( parent, ID_CHECKBOXVocolaTakesUnimacroActions, "Vocola takes Unimacro Actions", wx.DefaultPosition, [280,-1], 0 ) + item21.Add( item22, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item23 = wx.Button( parent, ID_BUTTONHelp2, "Help-&2", wx.DefaultPosition, wx.DefaultSize, 0 ) + item21.Add( item23, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item15.Add( item21, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item15.AddGrowableCol( 2 ) + + item13.Add( item15, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item13, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item25 = wx.StaticBox( parent, -1, "Unimacro" ) + item25.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item24 = wx.StaticBoxSizer( item25, wx.VERTICAL ) + + item26 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item27 = wx.Button( parent, ID_BUTTONUnimacroEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item27, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item28 = wx.StaticText( parent, ID_TEXTunimacrouserdir, "Unimacro User Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item28, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item29 = wx.TextCtrl( parent, ID_TEXTCTRLunimacrouserdirectory, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item29.SetBackgroundColour( wx.LIGHT_GREY ) + item26.Add( item29, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item30 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item30, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item31 = wx.Button( parent, ID_BUTTONUnimacroEditor, "Unimacro Editor", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item31, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item32 = wx.TextCtrl( parent, ID_TEXTCTRLunimacroeditor, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item32.SetBackgroundColour( wx.LIGHT_GREY ) + item26.Add( item32, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item33 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item33, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item34 = wx.Button( parent, ID_BUTTONVocolaCompatibiliy, "Vocola compatibility", wx.DefaultPosition, wx.DefaultSize, 0 ) + item26.Add( item34, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item35 = wx.BoxSizer( wx.HORIZONTAL ) + + item36 = wx.Button( parent, ID_BUTTONHelp3, "Help-&3", wx.DefaultPosition, wx.DefaultSize, 0 ) + item35.Add( item36, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item26.Add( item35, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item26.AddGrowableCol( 2 ) + + item24.Add( item26, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item24, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item38 = wx.StaticBox( parent, -1, "UserDirectory" ) + item37 = wx.StaticBoxSizer( item38, wx.VERTICAL ) + + item39 = wx.FlexGridSizer( 0, 4, 0, 0 ) + + item40 = wx.Button( parent, ID_BUTTONUserEnable, "Enable/Disable", wx.DefaultPosition, wx.DefaultSize, 0 ) + item39.Add( item40, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item41 = wx.StaticText( parent, ID_TEXTnatlinkuserdirectory, "Natlink User Directory:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) + item39.Add( item41, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item42 = wx.TextCtrl( parent, ID_TEXTCTRLnatlinkuserdirectory, "", wx.DefaultPosition, [80,-1], wx.TE_READONLY ) + item42.SetBackgroundColour( wx.LIGHT_GREY ) + item39.Add( item42, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item43 = wx.Button( parent, ID_BUTTONHelp4, "Help-&4", wx.DefaultPosition, wx.DefaultSize, 0 ) + item39.Add( item43, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item39.AddGrowableCol( 2 ) + + item37.Add( item39, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item37, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item45 = wx.StaticBox( parent, -1, "Repair" ) + item45.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item44 = wx.StaticBoxSizer( item45, wx.VERTICAL ) + + item46 = wx.FlexGridSizer( 0, 6, 0, 0 ) + + item47 = wx.Button( parent, ID_BUTTONregister, "(re)Register NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item47, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item48 = wx.Button( parent, ID_BUTTONunregister, "unRegister NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item48, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item49 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item49, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item50 = wx.Button( parent, ID_BUTTONUndo, "Undo", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item50, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item51 = wx.Button( parent, ID_BUTTONClose, "Close", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item51, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item52 = wx.Button( parent, ID_BUTTONHelp5, "Help-5", wx.DefaultPosition, wx.DefaultSize, 0 ) + item46.Add( item52, 0, wx.ALIGN_CENTER, 5 ) + + item46.AddGrowableCol( 2 ) + + item44.Add( item46, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item0.Add( item44, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +ID_TEXTDNSversion = 10027 +ID_TEXTCTRLDNSVersion = 10028 +ID_TEXTCTRLWindowsVersion = 10029 +ID_TEXTCTRLpythonversion = 10030 +ID_TEXTdnsinstallpath = 10031 +ID_TEXTCTRLDNSinstallpath = 10032 +ID_BUTTONchangednsinstallpath = 10033 +ID_BUTTONClearDNSInstallPath = 10034 +ID_TEXTdnsinifilepath = 10035 +ID_TEXTCTRLdnsinifilepath = 10036 +ID_BUTTONchangednsinifilepath = 10037 +ID_BUTTONClearDNSInifilePath = 10038 +ID_TEXTNatlinkCorePath = 10039 +ID_TEXTCTRLnatlinkcorepath = 10040 +ID_BUTTONLogInfo = 10041 +ID_BUTTONHelpInfo = 10042 + +def InfoWindow( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item2 = wx.StaticBox( parent, -1, "Info" ) + item1 = wx.StaticBoxSizer( item2, wx.VERTICAL ) + + item3 = wx.FlexGridSizer( 0, 4, 0, 0 ) + + item4 = wx.StaticText( parent, ID_TEXTDNSversion, "DNS version:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item5 = wx.BoxSizer( wx.HORIZONTAL ) + + item6 = wx.TextCtrl( parent, ID_TEXTCTRLDNSVersion, "", wx.DefaultPosition, [50,-1], wx.TE_READONLY ) + item6.SetBackgroundColour( wx.LIGHT_GREY ) + item5.Add( item6, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP|wx.BOTTOM, 5 ) + + item7 = wx.StaticText( parent, ID_TEXT, "Windows version:", wx.DefaultPosition, [180,-1], wx.ALIGN_RIGHT ) + item5.Add( item7, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item8 = wx.TextCtrl( parent, ID_TEXTCTRLWindowsVersion, "", wx.DefaultPosition, [50,-1], wx.TE_READONLY ) + item8.SetBackgroundColour( wx.LIGHT_GREY ) + item5.Add( item8, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.TOP|wx.BOTTOM, 5 ) + + item3.Add( item5, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item9 = wx.StaticText( parent, ID_TEXT, "Python version:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item9, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item10 = wx.TextCtrl( parent, ID_TEXTCTRLpythonversion, "", wx.DefaultPosition, [50,-1], wx.TE_READONLY ) + item10.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item10, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 10 ) + + item11 = wx.StaticText( parent, ID_TEXTdnsinstallpath, "DNS install path:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item11, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item12 = wx.TextCtrl( parent, ID_TEXTCTRLDNSinstallpath, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item12.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item12, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item13 = wx.Button( parent, ID_BUTTONchangednsinstallpath, "Change-&d", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item13, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item14 = wx.Button( parent, ID_BUTTONClearDNSInstallPath, "Clear-&D", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item14, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item15 = wx.StaticText( parent, ID_TEXTdnsinifilepath, "DNS ini file path:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item15, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item16 = wx.TextCtrl( parent, ID_TEXTCTRLdnsinifilepath, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item16.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item16, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item17 = wx.Button( parent, ID_BUTTONchangednsinifilepath, "Change-&c", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item17, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item18 = wx.Button( parent, ID_BUTTONClearDNSInifilePath, "Clear-&C", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item18, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item19 = wx.StaticText( parent, ID_TEXTNatlinkCorePath, "NatlinkCore path:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item19, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item20 = wx.TextCtrl( parent, ID_TEXTCTRLnatlinkcorepath, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item20.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item20, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item21 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item21, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item22 = wx.Button( parent, ID_BUTTONLogInfo, "Log info-&i", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item22, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item23 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item23, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item24 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item24, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item25 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item25, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item26 = wx.Button( parent, ID_BUTTONHelpInfo, "Help-&Info", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item26, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item3.AddGrowableCol( 1 ) + + item1.Add( item3, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item0.Add( item1, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +ID_CHECKBOXMakeUnimacroIncludeLines = 10043 +ID_CHECKBOXRemoveUnimacroIncludeLines = 10044 +ID_CHECKBOXRefreshUnimacroVch = 10045 +ID_BUTTONOK = 10046 +ID_BUTTONCancel = 10047 + +def DialogVocolaCombatibility( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item1 = wx.StaticText( parent, ID_TEXT, + "Vocola can profit from Unimacro features.\n" + "\n" + "If you wish to do so, switch on the checkbox \"Vocola takes Unimacro Actions\" in the configure panel.\n" + "\n" + "It is then useful to include the file \"Unimacro.vch\" as a wrapper around the Unimacro Shorthand Commands functions.\n" + "\n" + "With the options below, you can insert or remove an \"include\" line in all your Vocola Command Files, \n" + "and ensure the include file is copied into the correct directory. \n" + "This last action is sometimes needed after an update of the NatLink/Vocola/Unimacro system.\n" + "\n" + "Please check the options you want to be processed and press/call OK", + wx.DefaultPosition, wx.DefaultSize, 0 ) + item1.SetBackgroundColour( wx.WHITE ) + item0.Add( item1, 0, wx.ALIGN_CENTER|wx.ALL, 15 ) + + item2 = wx.CheckBox( parent, ID_CHECKBOXMakeUnimacroIncludeLines, "Insert a \"include Unimacro.vch;\" line in all your Vocola Command Files", wx.DefaultPosition, wx.DefaultSize, 0 ) + item0.Add( item2, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item3 = wx.CheckBox( parent, ID_CHECKBOXRemoveUnimacroIncludeLines, "Remove the \"include Unimacro.vch;\" lines from your Vocola Command Files", wx.DefaultPosition, wx.DefaultSize, 0 ) + item0.Add( item3, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item4 = wx.CheckBox( parent, ID_CHECKBOXRefreshUnimacroVch, "Copy the include file \"Unimacro.vch\" into your Vocola User Directory", wx.DefaultPosition, wx.DefaultSize, 0 ) + item0.Add( item4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item5 = wx.BoxSizer( wx.HORIZONTAL ) + + item6 = wx.Button( parent, ID_BUTTONOK, "OK", wx.DefaultPosition, wx.DefaultSize, 0 ) + item5.Add( item6, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item7 = wx.Button( parent, ID_BUTTONCancel, "Cancel", wx.DefaultPosition, wx.DefaultSize, 0 ) + item5.Add( item7, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item0.Add( item5, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +ID_TEXTahkexedir = 10048 +ID_TEXTCTRLAhkExeDir = 10049 +ID_BUTTONsetahkexedir = 10050 +ID_BUTTONclearahkexedir = 10051 +ID_TEXTahkuserdir = 10052 +ID_TEXTCTRLahkuserdir = 10053 +ID_BUTTONsetahkuserdir = 10054 +ID_BUTTONclearahkuserdir = 10055 +ID_BUTTONahkhelp = 10056 +ID_BUTTONcloseadvanced = 10057 +ID_BUTTONhelprepair = 10058 + +def ExtraWindow( parent, call_fit = True, set_sizer = True ): + item0 = wx.BoxSizer( wx.VERTICAL ) + + item2 = wx.StaticBox( parent, -1, "AutoHotkey" ) + item1 = wx.StaticBoxSizer( item2, wx.VERTICAL ) + + item3 = wx.FlexGridSizer( 0, 4, 0, 0 ) + + item4 = wx.StaticText( parent, ID_TEXTahkexedir, "Ahk Exe Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item4, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item5 = wx.TextCtrl( parent, ID_TEXTCTRLAhkExeDir, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item5.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item5, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item6 = wx.Button( parent, ID_BUTTONsetahkexedir, "Set", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item6, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item7 = wx.Button( parent, ID_BUTTONclearahkexedir, "Clear", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item7, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item8 = wx.StaticText( parent, ID_TEXTahkuserdir, "Ahk User Directory:", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item8, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item9 = wx.TextCtrl( parent, ID_TEXTCTRLahkuserdir, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY ) + item9.SetBackgroundColour( wx.LIGHT_GREY ) + item3.Add( item9, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item10 = wx.Button( parent, ID_BUTTONsetahkuserdir, "Set", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item10, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item11 = wx.Button( parent, ID_BUTTONclearahkuserdir, "Clear", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item11, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item12 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item12, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item13 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item13, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item14 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item14, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item15 = wx.Button( parent, ID_BUTTONahkhelp, "Help", wx.DefaultPosition, wx.DefaultSize, 0 ) + item3.Add( item15, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item3.AddGrowableCol( 1 ) + + item1.Add( item3, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item17 = wx.StaticBox( parent, -1, "Repair" ) + item17.SetFont( wx.Font( 10, wx.SWISS, wx.NORMAL, wx.NORMAL ) ) + item16 = wx.StaticBoxSizer( item17, wx.VERTICAL ) + + item18 = wx.FlexGridSizer( 0, 3, 0, 0 ) + + item19 = wx.BoxSizer( wx.HORIZONTAL ) + + item20 = wx.Button( parent, ID_BUTTONregister, "(re)Register NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item19.Add( item20, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item21 = wx.Button( parent, ID_BUTTONunregister, "unRegister NatLink", wx.DefaultPosition, wx.DefaultSize, 0 ) + item19.Add( item21, 0, wx.ALIGN_CENTER_VERTICAL, 5 ) + + item18.Add( item19, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item22 = wx.BoxSizer( wx.HORIZONTAL ) + + item23 = wx.StaticText( parent, ID_TEXT, "", wx.DefaultPosition, [20,20], 0 ) + item22.Add( item23, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item18.Add( item22, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + item24 = wx.BoxSizer( wx.HORIZONTAL ) + + item25 = wx.Button( parent, ID_BUTTONcloseadvanced, "Close", wx.DefaultPosition, wx.DefaultSize, 0 ) + item24.Add( item25, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 5 ) + + item26 = wx.Button( parent, ID_BUTTONhelprepair, "Help", wx.DefaultPosition, wx.DefaultSize, 0 ) + item24.Add( item26, 0, wx.ALIGN_CENTER, 5 ) + + item18.Add( item24, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + item18.AddGrowableCol( 1 ) + + item16.Add( item18, 0, wx.FIXED_MINSIZE|wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.SHAPED, 0 ) + + item1.Add( item16, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 0 ) + + item0.Add( item1, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + if set_sizer == True: + parent.SetSizer( item0 ) + if call_fit == True: + item0.SetSizeHints( parent ) + + return item0 + +# Menubar functions + +ID_MENUClose = 10059 +ID_MENUFile = 10060 +ID_MENUhelp = 10061 + +def MyMenuBarFunc(): + item0 = wx.MenuBar() + + item1 = wx.Menu() + item1.Append( ID_MENUClose, "Close\tc", "" ) + item0.Append( item1, "File" ) + + item2 = wx.Menu() + item2.Append( ID_MENUhelp, "Help", "" ) + item0.Append( item2, "Help" ) + + return item0 + +# Toolbar functions + +# Bitmap functions + + +# End of generated file diff --git a/NatlinkConfigure/natlinkconfigfunctions.py b/NatlinkConfigure/natlinkconfigfunctions.py new file mode 100644 index 00000000..f1cc2d05 --- /dev/null +++ b/NatlinkConfigure/natlinkconfigfunctions.py @@ -0,0 +1,1030 @@ +# coding=latin-1 +# +# natlinkconfigfunctions.py +# This module performs the configuration functions. +# called from natlinkconfig (a wxPython GUI), +# or directly, see below +# +# Quintijn Hoogenboom, January 2008 (...), April 2022 +# +#pylint:disable=C0302, W0702, R0904, R0201, C0116, W0613, R0914, R0912 +r""" +With the functions in this module Natlink can be configured. + +This can be done in three ways: +-Through the command line interface (CLI) which is started automatically + when this module is run (with Pythonwin, IDLE, or command line of Python) +-On the command line, using one of the different command line options +-Through the configure GUI (natlinkconfig.py), which calls into this module + This last one needs wxPython to be installed. + +*** the core directory is relative to this directory ... + ...and will be searched for first. + +Afterwards can be set: + +DNSInstallDir + - if not found in one of the predefined subfolders of %PROGRAMFILES%, + this directory can be set in HKCU\Software\Natlink. + Functions: setDNSInstallDir(path) (d path) and clearDNSInstallDir() (D) + +DNSINIDir + - if not found in one of the subfolders of %COMMON_APPDATA% + where they are expected, this one can be set in HKCU\Software\Natlink. + Functions: setDNSIniDir(path) (c path) and clearDNSIniDir() (C) + +When Natlink is enabled natlink.pyd is registered with + win32api.WinExec("regsvr32 /s pathToNatlinkPyd") (silent) + +It can be unregistered through function unregisterNatlinkPyd() see below. + +Other functions inside this module, with calls from CLI or command line: + +enableNatlink() (e)/disableNatlink() (E) + +setUserDirectory(path) (n path) or clearUserDirectory() (N) +etc. + +More at the bottom, with the CLI description... + +""" +import os +import shutil +import sys +import getopt +import cmd +from pathlib import Path +import configparser +# from win32com.shell import shell +from natlink import natlinkstatus +from natlink import config +from natlink import loader +from natlink import readwritefile +from natlink import wxdialogs + +isfile, isdir, join = os.path.isfile, os.path.isdir, os.path.join + +class NatlinkConfig: + """performs the configuration tasks of Natlink + + setting UserDirectory, UnimacroDirectory and options, VocolaDirectory and options, + Autohotkey options (ahk), and Debug option of Natlink. + + Changes are written in the config file, from which the path is taken from the loader instance. + """ + def __init__(self): + self.DNSName = 'Dragon' + self.config_path = self.get_check_config_locations() + self.config_dir = str(Path(self.config_path).parent) + self.status = natlinkstatus.NatlinkStatus() + self.getConfig() # gets self.config and self.config_encoding + self.check_config() + + def get_check_config_locations(self): + """check the location/locations as given by the loader + """ + config_path, fallback_path = loader.config_locations() + + if not isfile(config_path): + config_dir = Path(config_path).parent + if not config_dir.is_dir(): + config_dir.mkdir(parents=True) + shutil.copyfile(fallback_path, config_path) + return config_path + + def check_config(self): + """check config_file for unwanted settings + """ + self.config_remove(section='directories', option='default_config') + + def getConfig(self): + """return the config instance + """ + rwfile = readwritefile.ReadWriteFile() + config_text = rwfile.readAnything(self.config_path) + self.config = configparser.ConfigParser() + self.config.read_string(config_text) + self.config_encoding = rwfile.encoding + + def config_get(self, section, option): + """set a setting into the natlink ini file + + """ + try: + return self.config.get(section, option) + except (configparser.NoSectionError, configparser.NoOptionError): + return None + + def config_set(self, section, option, value): + """set a setting into an inifile (possibly other than natlink.ini) + + Set the setting in self.config. + + Then write with the setting included to config_path with config_encoding. + When this encoding is ascii, but there are (new) non-ascii characters, + the file is written as 'utf-8'. + + """ + if not value: + return self.config_remove(section, option) + + if not self.config.has_section(section): + self.config.add_section(section) + self.config.set(section, option, str(value)) + self.config_write() + return True + + def config_write(self): + """write the (changed) content to the ini (config) file + """ + try: + with open(self.config_path, 'w', encoding=self.config_encoding) as fp: + self.config.write(fp) + except UnicodeEncodeError as exc: + if self.config_encoding != 'ascii': + print(f'UnicodeEncodeError, cannot encode with encoding "{self.config_encoding}" the config data to file "{self.config_path}"') + raise UnicodeEncodeError from exc + with open(self.config_path, 'w', encoding='utf-8') as fp: + self.config.write(fp) + + def config_remove(self, section, option): + """removes from config file + + same effect as setting an empty value + """ + if not self.config.has_section(section): + return + self.config.remove_option(section, option) + if not self.config.options(section): + if section not in ['directories', 'settings', 'userenglish-directories', 'userspanish-directories']: + self.config.remove_section(section) + self.config_write() + + # def setUserDirectory(self, arg): + # self.setDirectory('UserDirectory', arg) + # def clearUserDirectory(self, arg): + # self.clearDirectory('UserDirectory') + + def setDirectory(self, option, dir_path, section=None): + """set the directory, specified with "key", to dir_path + + If dir_path None or invalid, go via + """ + section = section or 'directories' + if not dir_path: + prev_path = self.config_get('previous directories', option) or self.config_dir + dir_path = wxdialogs.GetDirFromDialog(f'Please choose a "{option}"', prev_path) + if not dir_path: + print('No valid directory specified') + return + dir_path = dir_path.strip() + directory = createIfNotThere(dir_path, level_up=1) + if not (directory and Path(directory).is_dir()): + if directory is False: + directory = config.expand_path(dir_path) + if dir_path == directory: + print(f'Cannot set "{option}", the given path is invalid: "{directory}"') + else: + print(f'Cannot set "{option}", the given path is invalid: "{directory}" ("{dir_path}")') + return + self.config_set(section, option, dir_path) + self.config_remove('previous directories', option) + if section == 'directories': + print(f'Set option "{option}" to "{dir_path}"') + else: + print(f'Set in section "{section}", option "{option}" to "{dir_path}"') + return + + def clearDirectory(self, option, section=None): + """clear the setting of the directory designated by option + """ + section = section or 'directories' + old_value = self.config_get(section, option) + if not old_value: + print(f'The "{option}" was not set, nothing changed...') + return + if isValidDir(old_value): + self.config_set('previous directories', option, old_value) + else: + self.config_remove('previous directories', option) + + self.config_remove(section, option) + print(f'cleared "{option}"') + + def setDragonflyUserDirectory(self, v): + key = 'DragonflyUserDirectory' + if v and isValidDir(v): + print(f'Setting the "{key}" to "{v}"') + self.config_set('directories', key, v) + self.config.remove_option('previous directories', key) + else: + print(f'Setting the DragonflyUserDirectory failed, not a valid directory: "{v}"') + return False + return True + + def clearDragonflyUserDirectory(self): + key = 'DragonflyUserDirectory' + old_value = self.config_get('directories', key) + if not old_value: + print('The "{key}" was not set, so nothing changed...') + if isValidDir(old_value): + self.config_set('previous directories', key, old_value) + self.config_remove('directories', key) + print('cleared "{DragonflyUserDirectory}"') + + def setUnimacroIniFilesEditor(self, v): + key = "UnimacroIniFilesEditor" + exefile = isValidDir(v) + if exefile and v.endswith(".exe"): + self.config_set('unimacro', key, v) + self.config.remove_option('previous directories', key) + print(f'Set {key} to "{v}"') + return True + mess = f'not a valid .exe file: "{v}" (setting: "{key}")' + return mess + + def clearUnimacroIniFilesEditor(self): + key = "UnimacroIniFilesEditor" + old_value = self.config_get('unimacro', key) + oldexefile = isValidDir(old_value, wantFile=1) + if oldexefile: + self.config_set('previous directories', key, old_value) + self.config.remove_option('unimacro', key) + print('UnimacroIniFilesEditor cleared') + return True + + def enableDebugOutput(self): + """setting registry key so debug output of loading of natlinkmain is given + + """ + key = "log_level" + settings = 'settings' + old_value = self.config_get(settings, key) + if old_value: + if old_value == 'DEBUG': + print(f'enableDebugOutput, setting is already "{old_value}"') + return True + if old_value is not None: + self.config_set('previous settings', key, old_value) + self.config_set(settings, key, 'DEBUG') + return True + + def disableDebugOutput(self): + """disables the Natlink debug output + """ + key = 'log_level' + section = 'settings' + old_value = self.config_get('previous settings', key) + if old_value: + self.config.remove_option('previous settings', key) + if old_value == 'DEBUG': + old_value = 'INFO' + else: + old_value = 'INFO' + self.config_set(section, key, old_value) + return True + + def copyUnimacroIncludeFile(self): + """copy Unimacro include file into Vocola user directory + + """ + uscFile = 'Unimacro.vch' + # also remove usc.vch from VocolaUserDirectory + unimacroDir = self.status.getUnimacroDirectory() + fromFolder = Path(unimacroDir)/'Vocola_compatibility' + toFolder = Path(self.status.getVocolaUserDirectory()) + if not unimacroDir.is_dir(): + mess = f'unimacroDir "{str(unimacroDir)}" is not a directory' + return mess + fromFile = fromFolder/uscFile + if not fromFile.is_file(): + mess = f'file "{str(fromFile)}" does not exist (is not a valid file)' + return mess + if not toFolder.is_dir(): + mess = f'vocolaUserDirectory does not exist "{str(toFolder)}" (is not a directory)' + return mess + + toFile = toFolder/uscFile + if toFolder.is_file(): + print(f'remove previous "{str(toFile)}"') + try: + os.remove(toFile) + except: + mess = f'Could not remove previous version of "{str(toFile)}"' + return mess + try: + shutil.copyfile(fromFile, toFile) + print(f'copied "{uscFile}" from "{str(fromFolder)}" to "{str(toFolder)}"') + except: + mess = f'Could not copy new version of "{uscFile}", from "{str(fromFolder)}" to "{str(toFolder)}"' + return mess + return True + + def includeUnimacroVchLineInVocolaFiles(self, subDirectory=None): + """include the Unimacro wrapper support line into all Vocola command files + + as a side effect, set the variable for Unimacro in Vocola support: + VocolaTakesUnimacroActions... + """ + uscFile = 'Unimacro.vch' + oldUscFile = 'usc.vch' +## reInclude = re.compile(r'^include\s+.*unimacro.vch;$', re.MULTILINE) +## reOldInclude = re.compile(r'^include\s+.*usc.vch;$', re.MULTILINE) + + # also remove includes of usc.vch + toFolder = self.status.getVocolaUserDirectory() + if subDirectory: + toFolder = os.path.join(toFolder, subDirectory) + includeLine = 'include ..\\%s;\n'% uscFile + else: + includeLine = 'include %s;\n'%uscFile + oldIncludeLines = ['include %s;'% oldUscFile, + 'include ..\\%s;'% oldUscFile, + 'include %s;'% uscFile.lower(), + 'include ..\\%s;'% uscFile.lower(), + ] + + if not os.path.isdir(toFolder): + mess = 'cannot find Vocola command files directory, not a valid path: %s'% toFolder + print(mess) + return mess + nFiles = 0 + for f in os.listdir(toFolder): + F = os.path.join(toFolder, f) + if f.endswith(".vcl"): + changed = 0 + correct = 0 + Output = [] + for line in open(F, 'r'): + if line.strip() == includeLine.strip(): + correct = 1 + for oldLine in oldIncludeLines: + if line.strip() == oldLine: + changed = 1 + break + else: + Output.append(line) + if changed or not correct: + # changes were made: + if not correct: + Output.insert(0, includeLine) + open(F, 'w').write(''.join(Output)) + nFiles += 1 + elif len(f) == 3 and os.path.isdir(F): + # subdirectory, recursive + self.includeUnimacroVchLineInVocolaFiles(F) + self.enableVocolaTakesUnimacroActions() + mess = 'changed %s files in %s, and set the variable "%s"'% (nFiles, toFolder, + "VocolaTakesUnimacroActions") + print(mess) + return True + + def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): + """remove the Unimacro wrapper support line into all Vocola command files + """ + uscFile = 'Unimacro.vch' + oldUscFile = 'usc.vch' +## reInclude = re.compile(r'^include\s+.*unimacro.vch;$', re.MULTILINE) +## reOldInclude = re.compile(r'^include\s+.*usc.vch;$', re.MULTILINE) + + # also remove includes of usc.vch + if subDirectory: + # for recursive call language subfolders: + toFolder = subDirectory + else: + toFolder = self.getVocolaUserDir() + + oldIncludeLines = ['include %s;'% oldUscFile, + 'include ..\\%s;'% oldUscFile, + 'include %s;'% uscFile, + 'include ..\\%s;'% uscFile, + 'include ../%s;'% oldUscFile, + 'include ../%s;'% uscFile, + 'include %s;'% uscFile.lower(), + 'include ..\\%s;'% uscFile.lower(), + 'include ../%s;'% uscFile.lower(), + ] + + + if not os.path.isdir(toFolder): + mess = 'cannot find Vocola command files directory, not a valid path: %s'% toFolder + print(mess) + return mess + nFiles = 0 + for f in os.listdir(toFolder): + F = os.path.join(toFolder, f) + if f.endswith(".vcl"): + changed = 0 + Output = [] + for line in open(F, 'r'): + for oldLine in oldIncludeLines: + if line.strip() == oldLine: + changed = 1 + break + else: + Output.append(line) + if changed: + # had break, so changes were made: + open(F, 'w').write(''.join(Output)) + nFiles += 1 + elif len(f) == 3 and os.path.isdir(F): + self.removeUnimacroVchLineInVocolaFiles(F) + mess = 'removed include lines from %s files in %s'% (nFiles, toFolder) + print(mess) + return True + + def enableVocolaTakesLanguages(self): + """setting registry so Vocola can divide different languages + + """ + key = "VocolaTakesLanguages" + self.config_set('vocola', key, 'True') + + + def disableVocolaTakesLanguages(self): + """disables so Vocola cannot take different languages + """ + key = "VocolaTakesLanguages" + self.config_set('vocola', key, 'False') + + def enableVocolaTakesUnimacroActions(self): + """setting registry so Vocola can divide different languages + + """ + key = "VocolaTakesUnimacroActions" + self.config_set('vocola', key, 'True') + + + def disableVocolaTakesUnimacroActions(self): + """disables so Vocola does not take Unimacro Actions + """ + key = "VocolaTakesUnimacroActions" + self.config_set('vocola', key, 'False') + + def openConfigFile(self): + """open the natlink.ini config file + """ + os.startfile(self.config_path) + # try: + # subprocess.call(['notepad.exe', self.config_path]) + # print(f'opened the config file: "{self.config_path}"') + # except: + # mess = 'Could not open the config file "{self.config_path}"' + # return mess + print(f'opened "{self.config_path}" in a separate window') + return True + + def printPythonPath(self): + raise NotImplementedError + + +def _main(Options=None): + """Catch the options and perform the resulting command line functions + + options: -i, --info: give status info + + -I, --reginfo: give the info in the registry about Natlink + etc., usage above... + + """ + cli = CLI() + shortOptions = "aAiIeEfFgGyYxXDCVbBNOPlmMrRzZuq" + shortArgOptions = "d:c:v:n:o:p:" + if Options: + if isinstance(Options, str): + Options = Options.split(" ", 1) + Options = [_.strip() for _ in Options] + else: + Options = sys.argv[1:] + + try: + options, args = getopt.getopt(Options, shortOptions+shortArgOptions) + except getopt.GetoptError: + print('invalid option: %s'% repr(Options)) + cli.usage() + return + + if args: + print('should not have extraneous arguments: %s'% repr(args)) + for o, v in options: + o = o.lstrip('-') + funcName = 'do_%s'% o + func = getattr(cli, funcName, None) + if not func: + print('option %s not found in cli functions: %s'% (o, funcName)) + cli.usage() + continue + if o in shortOptions: + func(None) # dummy arg + elif o in shortArgOptions: + func(v) + else: + print('options should not come here') + cli.usage() + + + +class CLI(cmd.Cmd): + """provide interactive shell control for the different options. + """ + def __init__(self, Config=None): + cmd.Cmd.__init__(self) + self.prompt = '\nNatlink config> ' + self.info = "type 'u' for usage" + self.Config = None + self.message = '' + if __name__ == "__main__": + print("Type 'u' for usage ") + + def stripCheckDirectory(self, dirName): + """allow quotes in input, and strip them. + + Return "" if directory is not valid + """ + if not dirName: + return "" + n = dirName.strip() + while n and n.startswith('"'): + n = n.strip('"') + while n and n.startswith("'"): + n = n.strip("'") + if n: + n.strip() + + if os.path.isdir(n): + return n + print('not a valid directory: %s (%s)'% (n, dirName)) + return '' + + def usage(self): + """gives the usage of the command line options or options when + the command line interface (CLI) is used + """ + print('-'*60) + print(r"""Use either from the command line like 'natlinkconfigfunctions.py -i' +or in an interactive session using the CLI (command line interface). + +[Status] + +i - info, print information about the Natlink status +I - show the natlink.ini file (in Notepad), you can manually edit. +j - print PythonPath variable + +[Natlink] + +x/X - enable/disable debug output of Natlink + +[Vocola] + +v/V - enable/disable Vocola by setting/clearing VocolaUserDirectory, + where the Vocola Command Files (.vcl) will be located. + (~ or %HOME% are allowed, for example "~/.natlink/VocolaUser") + +b/B - enable/disable distinction between languages for Vocola user files +a/A - enable/disable the possibility to use Unimacro actions in Vocola + +[Unimacro] + +o/O - enable/disable Unimacro, by setting/clearing the UnimacroUserDirectory, where + the Unimacro user INI files are located, and several other directories (~ or %HOME% allowed) +p/P - set/clear path for program that opens Unimacro INI files. +l - copy header file Unimacro.vch into Vocola User Directory +m/M - insert/remove an include line for Unimacro.vch in all Vocola + command files + +[UserDirectory] +n/N - enable/disable UserDirectory, the directory where + User Natlink grammar files are located (e.g., "~\UserDirectory") + +[AutoHotkey] +h/H - set/clear the AutoHotkey exe directory. +k/K - set/clear the User Directory for AutoHotkey scripts. +[Other] + +u/usage - give this list +q - quit + +help : give more explanation on + """) + print('='*60) + + # info---------------------------------------------------------- + def do_i(self, arg): + S = self.Config.status.getNatlinkStatusString() + S = S + '\n\nIf you changed things, you must restart Dragon' + print(S) + def do_I(self, arg): + # inifile natlinkstatus.ini settings: + self.Config.openConfigFile() + def do_j(self, arg): + # print PythonPath: + self.Config.printPythonPath() + + def help_i(self): + print('-'*60) + print("""The command info (i) gives an overview of the settings that are +currently set inside the Natlink system. + +The command settings (I) gives all the Natlink settings as got from natlinkstatus.py, +or from the config file of Natlink (overlap with (i)) + +The command (j) gives the PythonPath variable which should contain several +Natlink directories after the config GUI runs succesfully + +Settings are set by either the Natlink/Vocola/Unimacro installer +or by functions that are called by the CLI (command line interface). + +After you change settings, restart Dragon. +""") + print('='*60) + help_j = help_I = help_i + + # User Directory, Dragonfly directory ------------------------------------------------- + # for easier remembering, change n to d (DragonFly) + def do_d(self, arg): + self.Config.setDirectory('DragonflyUserDirectory', arg) + + def do_D(self, arg): + self.Config.clearDirectory('DragonflyUserDirectory') + + def do_n(self, arg): + self.Config.setDirectory('UserDirectory', arg) + + def do_N(self, arg): + self.Config.clearDirectory('UserDirectory') + + def help_n(self): + print('-'*60) + print('''Sets (n []) or clears (N) the "UserDirectory" of Natlink. +This is the folder where your own python grammar files are/will be located. +''') + def help_d(self): + print('-'*60) + print('''Sets (d []) or clears (D) the "DragonflyUserDirectory". +This is the folder where your own Dragonfly python grammar files are/will be located. +''') + + help_N = help_n + help_D = help_d + + # Unimacro User directory and Editor or Unimacro INI files----------------------------------- + def do_o(self, arg): + unimacro_dir = self.Config.status.getUnimacroDirectory() + if not unimacro_dir: + print('Unimacro is not enabled, please do a "pip install unimacro"') + return + uniUserDir = self.Config.config_get('unimacro', 'UnimacroUserDirectory') + if uniUserDir and isdir(uniUserDir): + print(f'UnimacroUserDirectory is already defined: "{uniUserDir}"\n\tto change, first clear (option "O") and then set again') + return + self.Config.setDirectory('UnimacroUserDirectory', arg, section='unimacro') + uniUserDir = self.Config.config_get('unimacro', 'UnimacroUserDirectory') + if not uniUserDir: + return + self.Config.setDirectory('UnimacroDirectory', unimacro_dir) + uniGrammarsDir = str(Path(config.expand_path(uniUserDir))/'ActiveGrammars') + if not isdir(uniGrammarsDir): + createIfNotThere(uniGrammarsDir) + ## copy start files... + self.Config.setDirectory('UnimacroGrammarsDirectory', uniGrammarsDir) + + + def do_O(self, arg): + self.Config.clearDirectory('UnimacroUserDirectory', section='unimacro') + self.Config.config_remove('directories', 'unimacrogrammarsdirectory') + self.Config.config_remove('directories', 'unimacrodirectory') + + def help_o(self): + print('-'*60) + print(r"""set/clear UnimacroUserDirectory (o /O) + + +Setting this directory also enables Unimacro. Clearing it disables Unimacro + +In this directory, your user INI files (and possibly other user +dependent files) will be put. + +You can use (if entered through the CLI) "~" (or %%HOME%%) for user home directory, or +another environment variable (%%...%%). (example: "o ~\.natlink\\UnimacroUser") +""") + print('='*60) + + help_O = help_o + + # Unimacro Command Files Editor----------------------------------------------- + def do_p(self, arg): + if os.path.isfile(arg) and arg.endswith(".exe"): + self.message = "Setting (path to) Unimacro INI Files editor to %s"% arg + print('do action: %s'% self.message) + self.Config.setUnimacroIniFilesEditor(arg) + else: + print('Please specify a valid path for the Unimacro INI files editor, not |%s|'% arg) + + def do_P(self, arg): + self.message = "Clear Unimacro INI file editor, go back to default Notepad" + print('do action: %s'% self.message) + self.Config.clearUnimacroIniFilesEditor() + + def help_p(self): + print('-'*60) + print("""set/clear path to Unimacro INI files editor (p /P) + +By default (when you clear this setting) "notepad" is used, but: + +You can specify a program you like, for example, +TextPad, NotePad++, UltraEdit, or win32pad + +You can even specify Wordpad, maybe Microsoft Word... + +""") + print('='*60) + + help_P = help_p + + # Unimacro Vocola features----------------------------------------------- + # managing the include file wrapper business. + # can be called from the Vocola compatibility button in the config GUI. + def do_l(self, arg): + self.message = "Copy include file Unimacro.vch into Vocola User Directory" + print('do action: %s'% self.message) + self.Config.copyUnimacroIncludeFile() + + def help_l(self): + print('-'*60) + print("""Copy Unimacro.vch header file into Vocola User Files directory (l) + +Insert/remove 'include Unimacro.vch' lines into/from each Vocola +command file (m/M) + +Using Unimacro.vch, you can call Unimacro shorthand commands from a +Vocola command. +""") + print('='*60) + + def do_m(self, arg): + self.message = 'Insert "include Unimacro.vch" line in each Vocola Command File' + print('do action: %s'% self.message) + self.Config.includeUnimacroVchLineInVocolaFiles() + def do_M(self, arg): + self.message = 'Remove "include Unimacro.vch" line from each Vocola Command File' + print('do action: %s'% self.message) + self.Config.removeUnimacroVchLineInVocolaFiles() + help_m = help_M = help_l + + # Vocola and Vocola User directory------------------------------------------------ + def do_v(self, arg): + """specify the VocolaUserDirectory, + + but the config needs also the VocolaDirectory and the VocolaGrammarsDirectory + """ + voc_dir = self.Config.status.getVocolaDirectory() + if not voc_dir: + print('Vocola is not enabled, please do a "pip install vocola2"') + return + + vocola_user_dir = self.Config.config_get('vocola', 'VocolaUserDirectory') + if vocola_user_dir and isdir(vocola_user_dir): + print(f'VocolaUserDirectory is already defined: "{vocola_user_dir}"\n\tto change, first clear (option "V") and then set again') + return + self.Config.setDirectory('VocolaUserDirectory', arg, section='vocola') + vocola_user_dir = self.Config.config_get('vocola', 'VocolaUserDirectory') + if not vocola_user_dir: + return + vocGrammarsDir = str(Path(config.expand_path(vocola_user_dir))/'VocolaGrammars') + createIfNotThere(vocGrammarsDir) + self.Config.setDirectory('VocolaDirectory', voc_dir) + self.Config.setDirectory('VocolaGrammarsDirectory', vocGrammarsDir) + + + def do_V(self, arg): + self.Config.clearDirectory('VocolaUserDirectory', section='vocola') + self.Config.config_remove('directories', 'vocolagrammarsdirectory') + self.Config.config_remove('directories', 'vocoladirectory') + + def help_v(self): + print('-'*60) + print(r"""Enable/disable Vocola by setting/clearing the VocolaUserDirectory +(v /V). + +In this VocolaUserDirectory your Vocola Command File are/will be located. + +if does not exist, but "one up" does, the sub directory is created. +""") + print('='*60) + + help_V = help_v + + + # enable/disable Natlink debug output... + def do_x(self, arg): + self.message = 'Print debug output to "Messages from Natlink" window' + print('do action: %s'% self.message) + self.Config.enableDebugOutput() + def do_X(self, arg): + self.message = 'Disable printing debug output to "Messages from Natlink" window' + print('do action: %s'% self.message) + self.Config.disableDebugOutput() + + def help_x(self): + print('-'*60) + print("""Enable (x)/disable (X) Natlink debug output + +This sends (sometimes lengthy) debug messages to the +"Messages from Natlink" window. +""") + print('='*60) + + help_X = help_x + + # register natlink.pyd + def do_r(self, arg): + self.message = "(Re) register and enable natlink.pyd is done from the installer program" + + def do_R(self, arg): + self.message = 'Unregister natlink.pyd and disable Natlink is done from the installer program,\nyou can uninstall Natlink via "Add or remove Programs" in Windows' + + # different Vocola options + def do_b(self, arg): + self.message = "Enable Vocola different user directories for different languages" + print('do action: %s'% self.message) + self.Config.enableVocolaTakesLanguages() + def do_B(self, arg): + self.message = "Disable Vocola different user directories for different languages" + print('do action: %s'% self.message) + self.Config.disableVocolaTakesLanguages() + + def do_a(self, arg): + self.message = "Enable Vocola taking Unimacro actions" + print('do action: %s'% self.message) + self.Config.enableVocolaTakesUnimacroActions() + def do_A(self, arg): + self.message = "Disable Vocola taking Unimacro actions" + print('do action: %s'% self.message) + self.Config.disableVocolaTakesUnimacroActions() + + def help_a(self): + print('-'*60) + print("""----Enable (a)/disable (A) Vocola taking Unimacro actions. + +These actions (Unimacro Shorthand Commands) and "meta actions" are processed by +the Unimacro actions module. + +If Unimacro is NOT enabled, it will also +be necessary that the UnimacroDirectory is put in the python path. +The special option for that is (f). + +Note this option (f) is only needed when you use Vocola with Unimacro actions, +but you do not use Unimacro. +""") + print('='*60) + + def help_b(self): + print('-'*60) + print("""----Enable (b)/disable (B) different Vocola User Directories + +If enabled, Vocola will look into a subdirectory "xxx" of +VocolaUserDirectory IF the language code of the current user speech +profile is "xxx" and is NOT "enx". + +So for English users this option will have no effect. + +The first time a command file is opened in, for example, a +Dutch speech profile (language code "nld"), a subdirectory "nld" +is created, and all existing Vocola Command files for this Dutch speech profile are copied into this folder. + +When you use your English speech profile again, ("enx") the Vocola Command files in the VocolaUserDirectory are taken again. +""") + print('='*60) + + help_B = help_b + help_A = help_a + + # autohotkey settings: + def do_h(self, arg): + self.message = 'set directory of AutoHotkey.exe to: %s'% arg + print('do action: %s'% self.message) + self.Config.setAhkExeDir(arg) + + def do_H(self, arg): + self.message = 'clear directory of AutoHotkey.exe, return to default' + print('do action: %s'% self.message) + self.Config.clearAhkExeDir() + + def do_k(self, arg): + arg = self.stripCheckDirectory(arg) # also quotes + if not arg: + return + self.message = 'set user directory for AutoHotkey scripts to: %s'% arg + self.Config.setAhkUserDir(arg) + + def do_K(self, arg): + self.message = 'clear user directory of AutoHotkey scripts, return to default' + print('do action: %s'% self.message) + self.Config.clearAhkUserDir() + + def help_h(self): + print('-'*60) + print("""----Set (h)/clear (return to default) (H) the AutoHotkey exe directory. + Assume autohotkey.exe is found there (if not AutoHotkey support will not be there) + If set to a invalid directory, AutoHotkey support will be switched off. + + Set (k)/clear (return to default) (K) the User Directory for AutoHotkey scripts. + + Note: currently these options can only be run from the natlinkconfigfunctions.py script. +""") + print('='*60) + + help_H = help_k = help_K = help_h + + # enable/disable Natlink debug output... + + def default(self, line): + print(f'no valid entry: "{line}", type "u" or "usage" for list of commands') + print() + + def do_quit(self, arg): + sys.exit() + do_q = do_quit + def do_usage(self, arg): + self.usage() + do_u = do_usage + def help_u(self): + print('-'*60) + print("""u and usage give the list of commands +lowercase commands usually set/enable something +uppercase commands usually clear/disable something +Informational commands: i and I +""") + help_usage = help_u + +def isValidDir(path): + """return the path, as str, if valid directory + + otherwise return '' + """ + result = isValidPath(path, wantDirectory=True) + return result + +def isValidPath(path, wantDirectory=None, wantFile=None): + """return the path, as str, if valid + + otherwise return '' + """ + if not path: + return '' + path_expanded = Path(config.expand_path(path)) + path = Path(path) + if wantDirectory: + if path_expanded.is_dir(): + return str(path_expanded) + if wantFile: + if path_expanded.is_file(): + return str(path_expanded) + if path.exists(): + return str(path_expanded) + return '' + + +def createIfNotThere(path_name, level_up=None): + """if path_name does not exist, but one up does, create. + + return the valid path (str) or + False, if not a valid path + if level_up, can create more step upward ( specify > 1) + """ + level_up = level_up or 1 + dir_path = isValidDir(path_name) + if dir_path: + return dir_path + start_path = config.expand_path(path_name) + up_path = Path(start_path) + + level = level_up + while level: + up_path = up_path.parent + if up_path.is_dir(): + break + level -= 1 + else: + print(f'cannot create directory, {level_up} level above should exist: "{str(up_path)}"') + return False + Path(start_path).mkdir(parents=True) + if path_name == start_path: + print(f'created directory: "{start_path}"') + else: + print(f'created directory "{path_name}": "{start_path}"') + + return start_path + +if __name__ == "__main__": + if len(sys.argv) == 1: + Cli = CLI() + natlinkConfig = NatlinkConfig() + Cli.Config = natlinkConfig + Cli.info = "type u for usage" + try: + Cli.cmdloop() + except (KeyboardInterrupt, SystemExit): + pass + else: + _main() diff --git a/NatlinkConfigure/natlinkconfigfunctions.py.bak b/NatlinkConfigure/natlinkconfigfunctions.py.bak new file mode 100644 index 00000000..a20a0490 --- /dev/null +++ b/NatlinkConfigure/natlinkconfigfunctions.py.bak @@ -0,0 +1,2178 @@ +# coding=latin-1 +# +# natlinkconfigfunctions.py +# This module performs the configuration functions. +# called from natlinkconfig (a wxPython GUI), +# or directly, see below +# +# Quintijn Hoogenboom, January 2008 (...), April 2022 +# +""" +With the functions in this module NatLink can be configured. + +This can be done in three ways: +-Through the command line interface (CLI) which is started automatically + when this module is run (with Pythonwin, IDLE, or command line of Python) +-On the command line, using one of the different command line options +-Through the configure GUI (natlinkconfig.py), which calls into this module + This last one needs wxPython to be installed. + +*** the core directory is relative to this directory ... + ...and will be searched for first. + +Afterwards can be set: + +DNSInstallDir + - if not found in one of the predefined subfolders of %PROGRAMFILES%, + this directory can be set in HKCU\Software\Natlink. + Functions: setDNSInstallDir(path) (d path) and clearDNSInstallDir() (D) + +DNSINIDir + - if not found in one of the subfolders of %COMMON_APPDATA% + where they are expected, this one can be set in HKCU\Software\Natlink. + Functions: setDNSIniDir(path) (c path) and clearDNSIniDir() (C) + +When NatLink is enabled natlink.pyd is registered with + win32api.WinExec("regsvr32 /s pathToNatlinkPyd") (silent) + +It can be unregistered through function unregisterNatlinkPyd() see below. + +Other functions inside this module, with calls from CLI or command line: + +enableNatlink() (e)/disableNatlink() (E) + +setUserDirectory(path) (n path) or clearUserDirectory() (N) +etc. + +More at the bottom, with the CLI description... + +""" +import ctypes +import traceback +import types + +try: + from win32com.shell.shell import IsUserAnAdmin +except: + IsUserAnAdmin = ctypes.windll.shell32.IsUserAnAdmin + +try: + from win32ui import MessageBox + def windowsMessageBox(message, title="NatLink configure program"): + """do messagebox from windows, no wx needed + """ + MessageBox(message, title) +except: + import ctypes + MessageBoxA = ctypes.windll.user32.MessageBoxA + def windowsMessageBox(message, title="NatLink configure program"): + """do messagebox from windows, no wx needed + for old versions of python + """ + MessageBoxA(None, message, title, 0) + +import os, shutil +import sys +import pywintypes + +#--------- two utility functions: +def getBaseFolder(globalsDict=None): + """get the folder of the calling module. + + either sys.argv[0] (when run direct) or + __file__, which can be empty. In that case take the working directory + """ + globalsDictHere = globalsDict or globals() + baseFolder = "" + if globalsDictHere['__name__'] == "__main__": + baseFolder = os.path.split(sys.argv[0])[0] + print 'baseFolder from argv: %s'% baseFolder + elif globalsDictHere['__file__']: + baseFolder = os.path.split(globalsDictHere['__file__'])[0] + print 'baseFolder from __file__: %s'% baseFolder + if not baseFolder or baseFolder == '.': + baseFolder = os.getcwd() + print 'baseFolder was empty, take wd: %s'% baseFolder + return baseFolder + +def getCoreDir(thisDir): + """get the NatLink core folder, relative from the current folder + + This folder should be relative to this with ../MacroSystem/core and should + contain natlinkmain.p, natlink.pyd, and natlinkstatus.py + + If not found like this, prints a line and returns thisDir + SHOULD ONLY BE CALLED BY natlinkconfigfunctions.py + """ + coreFolder = os.path.normpath( os.path.join(thisDir, '..', 'MacroSystem', 'core') ) + print 'coreDirectory: %s'% coreFolder + if not os.path.isdir(coreFolder): + print 'not a directory: %s'% coreFolder + return thisDir +## PydPath = os.path.join(coreFolder, 'natlink.pyd') + mainPath = os.path.join(coreFolder, 'natlinkmain.py') + statusPath = os.path.join(coreFolder, 'natlinkstatus.py') +## if not os.path.isfile(PydPath): +## print 'natlink.pyd not found in core directory: %s'% coreFolder +## return thisDir + if not os.path.isfile(mainPath): + print 'natlinkmain.py not found in core directory: %s'% coreFolder + return thisDir + if not os.path.isfile(statusPath): + print 'natlinkstatus.py not found in core directory: %s'% coreFolder + return thisDir + return coreFolder +hadFatalErrors = [] +def fatal_error(message, new_raise=None): + """prints a fatal error when running this module + + print only the first! + """ + if not hadFatalErrors: + mess = ['natlinkconfigfunctions failed because of fatal error:', + '', message, '', + 'So if Dragon is running, close it and then rerun this program (in elevated mode).'] + mess = '\n'.join(mess) + windowsMessageBox(mess) + print mess + if message not in hadFatalErrors: + hadFatalErrors.append(message) + if new_raise: + raise new_raise +#----------------------------------------------------- +from win32com.shell import shell + +import win32api + +thisDir = getBaseFolder(globals()) +coreDir = getCoreDir(thisDir) +if thisDir == coreDir: + raise IOError('natlinkconfigfunctions cannot proceed, coreDir not found...') +# appending to path if necessary: +if not os.path.normpath(thisDir) in sys.path: + thisDir = os.path.normpath(thisDir) + print 'inserting %s to pythonpath...'% thisDir + sys.path.insert(0, thisDir) + +if not os.path.normpath(coreDir) in sys.path: + coreDir = os.path.normpath(coreDir) + print 'inserting %s to pythonpath...'% coreDir + sys.path.insert(0, coreDir) + +# from core directory, use registry entries from CURRENT_USER/Software/Natlink: +import natlinkstatus, natlinkcorefunctions, RegistryDict +import os, os.path, sys, getopt, cmd, types, string, win32con + +# import natlink # to see if NatSpeak is running... + + +class NatlinkConfig(natlinkstatus.NatlinkStatus): + """performs the configuration tasks of NatLink + + userregnl got from natlinkstatus, as a Class (not instance) variable, so + should be the same among instances of this class... + + the checkCoreDirectory function is automatically performed at start, to see if the initialisation does not + take place from another place as the registered natlink.pyd... + + + """ + def __init__(self): + self.DNSName = 'Dragon' + natlinkstatus.NatlinkStatus.__init__(self, skipSpecialWarning=1) + self.changesInInitPhase = 0 + self.isElevated = IsUserAnAdmin() + + def checkCoreDirectory(self): + """check if coreDir (from this file) and coreDirectory (from natlinkstatus) match, if not, raise error + """ + coreDir2 = self.getCoreDirectory() + if coreDir2.lower() != coreDir.lower(): + fatal_error('ambiguous core directory,\nfrom this module: %s\from status in natlinkstatus: %s'% + (coreDir, coreDir2)) + def checkDNSInstallDir(self): + """check if install directory of Dragon is found + + if not rais an error + """ + try: + dnsDir = self.getDNSInstallDir() + except IOError: + dnsDir = None + if not dnsDir: + fatal_error('no valid DNSInstallDir found, please repair in Config program or Configuration GUI') + pass + + def configCheckNatlinkPydFile(self): + """see if natlink.pyd is in core directory, if not copy from correct version + if DNSInstallDir or DNSIniDir is not properly set, all goes wrong. + """ + self.checkedUrgent = 1 + if sys.version.find("64 bit") >= 0: + print '=============================================' + print 'You installed a 64 bit version of python.' + print 'NatLink cannot run with this version, please uninstall and' + print 'install a 32 bit version of python, see http://qh.antenna.nl/unimacro,,,' + print '=============================================' + return + + if self.getDNSInstallDir == -1: + return + if self.getDNSIniDir == -1: + return + + coreDir2 = self.getCoreDirectory() + if coreDir2.lower() != coreDir.lower(): + fatal_error('ambiguous core directory,\nfrom this module: %s\from status in natlinkstatus: %s'% + (coreDir, coreDir2)) + currentPydPath = os.path.join(coreDir, 'natlink.pyd') + + if not os.path.isfile(currentPydPath): + if not self.isElevated: raise ElevationError("natlink.pyd is not found") + mess = "natlink.pyd is not found, try to repair this." + # windowsMessageBox(mess) + # self.message("natlink.pyd is not found, try to repair this.") + + key = 'NatlinkPydRegistered' + # print '%s does not exist, remove "%s" from natlinkstatus.ini and setup up new pyd file...'% (currentPydPath, key) + self.userregnl.delete(key) + natlinkPydWasAlreadyThere = 0 + self.checkedUrgent = None + else: + natlinkPydWasAlreadyThere = 1 + wantedPyd = self.getWantedNatlinkPydFile() # wanted original based on python version and Dragon version + if self.checkNatlinkPydFile(fromConfig=1) == 1: # check the need for replacing natlink.pyd without messages... + self.checkedUrgent = None + return 1 # all is well + + # for message: + #fatal_error("The current file natlink.pyd is not available, the correct version or outdated, try to replace it by the proper (newer) version...") + ## now go on with trying to replace natlink.pyd with the correct version and register it... + wantedPydPath = os.path.join(coreDir, 'PYD', wantedPyd) + if not wantedPyd: + fatal_error('natlinkconfigfunctions, configCheckNatlinkPydFile: Could not find filename for wantedPydPath\ncoreDir: %s, wantedPyd: %s'% (coreDir, wantedPyd)) + return + if not os.path.isfile(wantedPydPath): + fatal_error('natlinkconfigfunctions, configCheckNatlinkPydFile: wantedPydPath does not exits: %s'% wantedPydPath) + return + if natlinkPydWasAlreadyThere: + if not self.isElevated: raise ElevationError("natlink.pyd should be changed") + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("natlink.pyd should be changed") + self.changesInInitPhase = 1 + result = self.copyNatlinkPydPythonVersion(wantedPydPath, currentPydPath) + if not result: + return + result = self.registerNatlinkPyd() + if result: + print '-'*30 + print 'Copying and registering the latest natlink.pyd was succesful.' + print 'You can now close this program and restart Dragon.' + print '-'*30 + else: + if not self.isElevated: raise ElevationError("first run of configure program must be done in elevated mode") + + result = self.copyNatlinkPydPythonVersion(wantedPydPath, currentPydPath) + self.registerNatlinkPyd(silent=1) + + return result # None if something went wrong 1 if all OK + + def removeNatlinkPyd(self): + """remove the natlink.pyd file (Dragon should be switched off) + + in order to redo the copyNatlinkPydPythonVersion again + """ + if not self.isElevated: raise ElevationError("needed for removing your previous natlink.pyd. Also close Dragon.") + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("needed for removing your previous natlink.pyd") + + coreDir = self.getCoreDirectory() + currentPydFile = os.path.join(coreDir, 'natlink.pyd') + if os.path.isfile(currentPydFile): + try: + os.remove(currentPydFile) + except (WindowsError, IOError): + fatal_error('cannot remove natlink.pyd from the core directory: %s\nProbably Dragon is running'% coreDir) + return + if os.path.isfile(currentPydFile): + fatal_error('strange, could not remove "natlink.pyd" from the core directory: "%s"Possibly Dragon is running'% coreDir) + return + # ok: + return 1 # + + def copyNatlinkPydPythonVersion(self, wantedPydFile, currentPydFile): + """copy the natlink.pyd from the correct version""" + if not self.isElevated: raise ElevationError("needed for copying the correct natlink.pyd file.") + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("needed for rcopying the correct natlink.pyd file") + + if os.path.isfile(currentPydFile): + self.unregisterNatlinkPyd() + try: + os.remove(currentPydFile) + except WindowsError: + fatal_error('cannot remove currentPydFile "%s",\nProbably you must exit Dragon first\nPossibly restart your computer.'% currentPydFile) + return + + if os.path.isfile(wantedPydFile): + try: + shutil.copyfile(wantedPydFile, currentPydFile) + print 'copied pyd (=dll) file %s to %s'% (wantedPydFile, currentPydFile) + except: + fatal_error("Could not copy %s to %s\nProbably you need to exit Dragon first."% (wantedPydFile, currentPydFile)) + return + else: + fatal_error("wantedPydFile %s is missing! Cannot copy to natlink.pyd/natlink.pyd"% wantedPydFile) + return + return 1 + + def getCoreDirectoryHKLMPythonPathDict(self, flags=win32con.KEY_ALL_ACCESS, recursive=False): + """returns the dict that contains the PythonPath section of HKLM + + Overload for config program, automatically set or repair the pythonpath variable if the format is not ok + """ + version = self.getPythonVersion() + if not version: + fatal_error("no valid Python version available") + return None, None + dottedVersion = version[0] + "." + version[1] + pythonPathSectionName = r"SOFTWARE\Python\PythonCore\%s\PythonPath"% dottedVersion + # key MUST already exist (ensure by passing flags=...: + #try: + lmPythonPathDict = RegistryDict.RegistryDict(win32con.HKEY_LOCAL_MACHINE, pythonPathSectionName, flags=flags) + #except: + # fatal_error("registry section for pythonpath does not exist yet: %s, probably invalid Python version: %s"% + # (pythonPathSectionName, version)) + # return None, None + if 'NatLink' in lmPythonPathDict.keys(): + subDict = lmPythonPathDict['NatLink'] + if isinstance(subDict, RegistryDict.RegistryDict): + if '' in subDict.keys(): + value = subDict[''] + if value and type(value) in (str, unicode): + # all well (only the value is not tested yet): + return lmPythonPathDict, pythonPathSectionName + # not ok, repair the setting, admin rights needed: + if recursive: + fatal_error("Registry entry NatLink in pythonpath cannot be set correct, This can (hopefully) be solved by closing Dragon and then running the NatLink/Unimacro/Vocola Config program with administrator rights.run this program") + return None, None + print '==== Set NatLink setting in PythonPath section of registry to "%s"'% coreDir + lmPythonPathDict['NatLink'] = {'': coreDir} + return self.getHKLMPythonPathDict(recursive=True) + + + def checkPythonPathAndRegistry(self): + """checks if core directory is + + 1. in the sys.path + ### 2. in the registry keys of HKLM\SOFTWARE\Python\PythonCore\2.7\PythonPath\NatLink + + the latter part is inserted again, as, for some reason the automatic loading of + natlinkmain needs the core directory in its path. Only take the core dir now!! + + Instead the status.checkSysPath() function checks the existence of the core, base and user + directories in the sys.path and sets then if necessary. + + If this last key is not there or empty + ---set paths of coreDirectory + ---register natlink.pyd + It is probably the first time to run this program. + + If the settings are conflicting, either + ---you want to reconfigure NatLink in a new place (these directories) + ---you ran this program from a wrong place, exit and start again from the correct directory + + """ + self.checkedUrgent = None + if __name__ == '__main__': + print "checking PythonPathAndRegistry" + try: + result = self.getHKLMPythonPathDict(flags=win32con.KEY_ALL_ACCESS) + if result is None: + pass + lmPythonPathDict, PythonPathSectionName = result + except (pywintypes.error, KeyError): + mess = 'The section "NatLink" does not exist and cannot be created in the registry. You probably should run this program with administrator rights' + self.warning(mess) + self.checkedUrgent = 1 + if not self.isElevated: raise ElevationError("needed for fixing the PythonPath in the registry settings.") + + coreDir2 = self.getCoreDirectory() + if coreDir2.lower() != coreDir.lower(): + fatal_error('ambiguous core directory,\nfrom this module: %s\from status in natlinkstatus: %s'% + (coreDir, coreDir2)) + # adding the relevant directories to the sys.path variable: + #self.checkSysPath() ## not needed in config program + + pathString = coreDir +## if lmPythonPath: +## print 'lmPythonPath: ', lmPythonPath.keys() + result = lmPythonPathDict['NatLink'] + if result and '' in result: + coreDirFromRegistry = lmPythonPathDict['NatLink'][''] + if coreDirFromRegistry.lower() != coreDir.lower(): + self.doFatalRegistryProblem(coreDirFromRegistry, coreDir) + return + else: + if not self.isElevated: raise ElevationError("needed for making changes in the PythonPath registry settings and register natlink.pyd.") + # first time install, silently register + self.registerNatlinkPyd(silent=1) + self.setNatlinkInPythonPathRegistry() + return 1 + + lmNatlinkPathDict = lmPythonPathDict['NatLink'] + Keys = lmNatlinkPathDict.keys() + if not Keys: + # first time install Section is there, but apparently empty + if not self.isElevated: raise ElevationError("needed for making changes in the PythonPath registry settings and register natlink.pyd.") + + self.registerNatlinkPyd(silent=1) + self.setNatlinkInPythonPathRegistry() + return 1 + if Keys != [""]: + if not self.isElevated: raise ElevationError("needed for making changes in the PythonPath registry settings.") + + if '' in Keys: + Keys.remove("") + fatal_error("The registry section of the pythonPathSection of HKEY_LOCAL_MACHINE:\n\tHKLM\%s\ncontains invalid keys: %s, remove them with the registry editor (regedit)\nAnd rerun this program"% + (PythonPathSectionName+r'\NatLink', Keys)) + + + # now section has default "" key, proceed: + oldPathString = lmNatlinkPathDict[""] + if oldPathString.find(';') > 0: + print 'remove double entry, go back to single entry' + self.setNatlinkInPythonPathRegistry() + + oldPathString = lmNatlinkPathDict[""] + if oldPathString.find(';') > 0: + fatal_error("did not fix double entry in registry setting of the pythonPathSection of HKEY_LOCAL_MACHINE:\n\tHKLM\%s\ncontains more entries separated by ';'. Remove with the registry editor (regedit)\nAnd rerun this program"%PythonPathSectionName+r'\NatLink') + if not oldPathString: + # empty setting, silently register + if not self.isElevated: raise ElevationError("needed for making changes in the PythonPath registry settings and register natlink.pyd.") + + self.registerNatlinkPyd(silent=1) + self.setNatlinkInPythonPathRegistry() + return 1 + + if oldPathString.lower() == pathString.lower(): + return 1 # OK + ## not ok: + self.doFatalRegistryProblem(oldPathString, pathString) + + + def doFatalRegistryProblem(self, CoreDirFromRegistry, currentCoreDir): + """registry does not match, make text and report + """ + # now for something more serious::: + text = \ +""" +The PythonPath for NatLink does not match in registry with what this program +expects + +---settings in Registry: %s +---wanted settings: %s + +You probably just installed NatLink in a new location +and you ran the config program for the first time. + +If you want the new settings, (re)register natlink.pyd (r) + +And rerun this program... + +Close %s (including Quick Start Mode), and all other Python applications +before rerunning this program. Possibly you have to restart your computer. + +If you do NOT want these new settings, simply close this program and run +from the correct place. +"""% (CoreDirFromRegistry, currentCoreDir, self.DNSName) + self.warning(text) + self.checkedUrgent = 1 + + def checkIniFiles(self): + """check if INI files are consistent + this is done through the + + """ + if self.DNSInstallDir == -1: + return + if self.DNSIniDir == -1: + return + + result = self.NatlinkIsEnabled(silent=1) + if result == None: + if not self.isElevated: raise ElevationError("needed for fixing the natlink enabled state") + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("needed for fixing the natlink enabled state") + + self.disableNatlink(silent=1) + result = self.NatlinkIsEnabled(silent=1) + if result == None: + + text = \ +"""NatLink INI file settings are inconsistent, +and cannot automatically be disabled. + +Try to disable again, acquire administrator rights or report this issue +""" + self.warning(text) + return None + else: + text = \ +"""NatLink INI file settings were inconsistent; +This has been repaired. + +NatLink is now disabled. +""" + self.warning(text) + return 1 + + + def warning(self,text): + """is currently overloaded in GUI""" + if type(text) in (types.StringType, types.UnicodeType): + T = text + else: + # list probably: + T = '\n'.join(text) + print '-'*60 + print T + print '='*60 + return T + + def error(self,text): + """is currently overloaded in GUI""" + if type(text) in (types.StringType, types.UnicodeType): + T = text + else: + # list probably: + T = '\n'.join(text) + print '-'*60 + print T + print '='*60 + return T + + + def message(self, text): + """prints message, can be overloaded in configureGUI + """ + + if type(text) in (types.StringType, types.UnicodeType): + T = text + else: + # list probably: + T = '\n'.join(text) + print '-'*60 + print T + print '='*60 + + def setstatus(self, text): + """prints status, should be overloaded in configureGUI + """ + if type(text) in (types.StringType, types.UnicodeType): + T = text + else: + # list probably: + T = '\n'.join(text) + print '-'*60 + print T + print '='*60 + + def isValidPath(self, Path, wantDirectory=None, wantFile=None): + """return the path, if valid + otherwise return "" + same as function in natlinkstatus + """ + return natlinkstatus.isValidPath(Path, wantDirectory=wantDirectory, wantFile=wantFile) + + def setNatlinkInPythonPathRegistry(self): + """sets the HKLM setting of the Python registry + + do this only when needed... + + """ + lmPythonPathDict, pythonPathSectionName = self.getHKLMPythonPathDict(flags=win32con.KEY_ALL_ACCESS) + pathString = os.path.normpath(os.path.abspath(coreDir)) + NatlinkSection = lmPythonPathDict.get('NatLink', None) + if NatlinkSection: + oldPath = NatlinkSection.get('', '') + else: + oldPath = '' + if oldPath.lower() != pathString.lower(): + try: + lmPythonPathDict['NatLink'] = {'': pathString} + self.warning('Set registry setting PythonPath/NatLink to: %s"'% pathString) + except: + self.warning("cannot set PythonPath for NatLink in registry, probably you have insufficient rights to do this, try to run the config program with administrator rights") + + + def checkNatlinkRegistryPathSettings(self, secondTry=None): + """check if the register pythonpath variable present and matches with the previous path + + """ + regDict, sectionName = self.getHKLMPythonPathDict(flags=win32con.KEY_ALL_ACCESS) + try: + value = regDict['NatLink'] + except: + # new install, new key: + if not secondTry: + try: + regDict['NatLink'] = coreDir + except: + self.error("cannot set PythonPath/NatLink setting, run Config program with administrator rights") + return + else: + # check if setting is completed: + return self.checkNatlinkRegistryPathSettings(secondTry=1) + # key was there already: + wantedValue = coreDir + if value == wantedValue: + print 'registry PythonPath/NatLink setting ok: %s'% value + return 1 + # value different from current value: + if secondTry: + self.error("cannot set correct PythonPath/NatLink setting, run config program with administrator rights") + return + + # now change to new value: + self.warning("change PythonPath/NatLink setting in registry (HKLM section) to: %s"% coreDir) + regDict['NatLink'] = coreDir + return self.checkNatlinkRegistryPathSettings(secondTry=1) + + + + #def clearNatlinkFromPythonPathRegistry(self): + # """clears the HKLM setting of the Python registry""" + # lmPythonPathDict, pythonPathSectionName = self.getHKLMPythonPathDict() + # baseDir = os.path.join(coreDir, '..') + # pathString = ';'.join(map(os.path.normpath, [coreDir, baseDir])) + # if 'NatLink' in lmPythonPathDict.keys(): + # try: + # del lmPythonPathDict['NatLink'] + # except: + # self.warning("cannot clear Python path for NatLink in registry (HKLM section), probably you have insufficient rights to do this") + + def printInifileSettings(self): + print 'Settings in file "natlinkstatus.ini" in\ncore directory: "%s"\n'% self.getCoreDirectory() + Keys = self.userregnl.keys() + Keys.sort() + for k in Keys: + print "\t% s:\t%s"% (k, self.userregnl.get(k)) + print "-"*60 + + def setDNSInstallDir(self, new_dir): + """set in registry local_machine\natlink + + try if App/Program or Program is a valid subdirectory + """ + key = 'DNSInstallDir' + checkDir = self.isValidPath(new_dir, wantDirectory=1) + while checkDir and (checkDir.lower().endswith("app") or checkDir.lower().endswith("program")): + print 'setDNSInstallDir, one directory too deep %s'% checkDir + checkDir = os.path.join(os.path.normpath(os.path.join(checkDir, '..'))) + print '... and proceed with: %s'% checkDir + if not checkDir: + mess = "setDNSInstallDir, not a valid directory: %s"% new_dir + return mess + + if self.checkDNSProgramDir(checkDir): + # print 'set DNS Install Directory to: %s'% new_dir + self.userregnl.delete("Old"+key) + self.userregnl.set(key, checkDir) + self.getDNSInstallDir(force=1) ## new settings + return + else: + mess = 'setDNSInstallDir, directory "%s" is not a correct Dragon Program Directory: %s'% checkDir + print mess + + + def clearDNSInstallDir(self): + """clear in registry local_machine\natlink\natlinkcore + + """ + key = 'DNSInstallDir' + oldvalue = self.userregnl.get(key) + if oldvalue and self.isValidPath(oldvalue): + self.userregnl.set("Old"+key, oldvalue) + self.userregnl.delete(key) + self.getDNSInstallDir(force=1) ## new settings + + + + def setDNSIniDir(self, new_dir): + """set in registry local_machine\natlink + + """ + key = 'DNSIniDir' + if os.path.isdir(new_dir): + # check INI files: + nssystem = os.path.join(new_dir, self.NSSystemIni) + nsapps = os.path.join(new_dir, self.NSAppsIni) + if not os.path.isfile(nssystem): + mess = 'folder %s does not have the INI file %s'% (new_dir, self.NSSystemIni) + print mess + return mess + if not os.path.isfile(nsapps): + mess = 'folder %s does not have the INI file %s'% (new_dir, self.NSAppsIni) + print mess + return mess + self.userregnl.set(key, new_dir) + self.userregnl.delete("Old"+key) + self.getDNSIniDir(force=1) + return # OK + else: + mess = "setDNSIniDir, not a valid directory: %s"% new_dir + print mess + return mess # signify an error... + + + def clearDNSIniDir(self): + """clear in registry local_machine\natlink\ + + """ + key = 'DNSIniDir' + oldvalue = self.userregnl.get(key) + if oldvalue and self.isValidPath(oldvalue): + self.userregnl.set("Old"+key, oldvalue) + self.userregnl.delete(key) + self.getDNSIniDir(force=1) + + def setUserDirectory(self, v): + key = 'UserDirectory' + if v and self.isValidPath(v): + print "Setting the UserDirectory of NatLink to %s"% v + self.userregnl.set(key, v) + self.userregnl.delete("Old"+key) + else: + print 'Setting the UserDirectory of NatLink failed, not a valid directory: %s'% v + + + def clearUserDirectory(self): + key = 'UserDirectory' + oldvalue = self.userregnl.get(key) + if oldvalue and self.isValidPath(oldvalue): + self.userregnl.set("Old"+key, oldvalue) + if self.userregnl.get(key): + self.userregnl.delete(key) + print 'clearing UserDirectory of NatLink' + else: + print 'The UserDirectory of NatLink was not set, nothing changed...' + + def alwaysIncludeUnimacroDirectoryInPath(self): + """set variable so natlinkstatus knows to include Unimacro in path + + This is only used when userDirectory is set to another directory as Unimacro. + Unimacro is expected at ../../Unimacro relative to the Core directory + """ + key = 'IncludeUnimacroInPythonPath' + Keys = self.userregnl.keys() + print 'set %s'% key + self.userregnl.set(key, 1) + + def ignoreUnimacroDirectoryInPathIfNotUserDirectory(self): + """clear variable so natlinkstatus knows to not include Unimacro in path + + This is only used when userDirectory is set to + another directory as Unimacro. + Unimacro is expected at ../../Unimacro relative to the Core directory + """ + key = 'IncludeUnimacroInPythonPath' + Keys = self.userregnl.keys() + + if key in Keys: + print 'clearing variable %s'% key + self.userregnl.delete(key) + else: + print 'was not set %s'% key + + + def enableNatlink(self, silent=None): + """register natlink.pyd and set settings in nssystem.INI and nsapps.ini + + """ + if not self.isElevated: raise ElevationError("needed for enabling NatLink") + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("Probably needed for enabling NatLink") + + self.registerNatlinkPyd(silent=1) + nssystemini = self.getNSSYSTEMIni() + section1 = self.section1 + key1 = self.key1 + value1 = self.value1 + # + try: + win32api.WriteProfileVal(section1, key1, value1, nssystemini) + except pywintypes.error, details: + if details[0] == 5: + print 'cannot enable NatLink (1), you probably need administrator rights' + else: + print 'unexpected error at enable NatLink (1)' + raise + + result = self.NatlinkIsEnabled(silent=1) + if result is None: + nsappsini = self.getNSAPPSIni() + section2 = self.section2 + key2 = self.key2 + value2 = self.value2 + try: + win32api.WriteProfileVal(section2, key2, value2, nsappsini) + except pywintypes.error, details: + if details[0] == 5: + print 'cannot enable NatLink (2), you probably need administrator rights' + else: + print 'unexpected error at enable NatLink (2)' + raise + result = self.NatlinkIsEnabled(silent=1) + if result == None: + text = \ +"""Cannot set the nsapps.ini setting in order to complete enableNatlink. + +Probably you did not run this program in "elevated mode". Please try to do so. +""" + self.warning(text) + return + result = self.NatlinkIsEnabled(silent=1) + if result: + if not silent: + print 'NatLink enabled, you can now restart %s'% self.DNSName + else: + if not silent: + self.warning("failed to enable NatLink") + + + def disableNatlink(self, silent=None): + """only do the nssystem.ini setting + """ + if not self.isElevated: raise ElevationError("needed for disabling NatLink") + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("Probably needed for disabling NatLink") + + nssystemini = self.getNSSYSTEMIni() + section1 = self.section1 + key1 = self.key1 + # trick with None, see testConfigureFunctions... + # this one disables NatLink: + try: + win32api.WriteProfileVal(section1, key1, None, nssystemini) + except pywintypes.error, details: + if details[0] == 5: + print 'cannot disable NatLink, you probably need administrator rights' + else: + print 'unexpected error at disable NatLink' + raise + result = self.NatlinkIsEnabled(silent=1) + if result: + t = 'NatLink is NOT disabled, you probably need administrator rights, please restart the config program in "elevated mode"' + print t + self.warning(t) + else: + if not silent: + print 'NatLink disabled, restart %s'% self.DNSName + print 'Note natlink.pyd is NOT UNREGISTERED, but this is not necessary either' + + def getVocolaUserDir(self): + key = 'VocolaUserDirectory' + value = self.userregnl.get(key, None) + return value + + def setVocolaUserDir(self, v): + key = 'VocolaUserDirectory' + if self.isValidPath(v, wantDirectory=1): + print "Setting VocolaUserDirectory %s and enable Vocola"% v + self.userregnl.set(key, v) + self.userregnl.delete("Old"+key) + else: + oldvocdir = self.userregnl.get(key) + if oldvocdir and self.isValidPath(oldvocdir, wantDirectory=1): + mess = 'not a valid directory: %s, Vocola remains enabled with VocolaUserDirectory: %s'% (v, oldvocdir) + else: + mess = 'not a valid directory: %s, Vocola remains disabled'% v + return mess + + def clearVocolaUserDir(self): + key = 'VocolaUserDirectory' + oldvalue = self.userregnl.get(key) + if oldvalue and self.isValidPath(oldvalue): + self.userregnl.set("Old"+key, oldvalue) + if self.userregnl.get(key): + self.userregnl.delete(key) + print 'clearing the VocolaUserDirectory and disable Vocola' + else: + mess = 'no valid VocolaUserDirectory, so Vocola was already disabled' + return mess + + ## autohotkey (January 2014) + def getAhkExeDir(self): + key = 'AhkExeDir' + value = self.userregnl.get(key) + return value + + def setAhkExeDir(self, v): + key = 'AhkExeDir' + ahkexedir = self.isValidPath(v, wantDirectory=1) + + if ahkexedir: + exepath = os.path.join(ahkexedir, 'autohotkey.exe') + if os.path.isfile(exepath): + print 'Set AutoHotkey Exe Directory (AhkExeDir) to %s'% v + self.userregnl.set(key, v) + self.userregnl.delete('Old'+key) + return + else: + mess = 'path does not contain "autohotkey.exe": %s'% v + else: + mess = 'not a valid directory: %s'% v + return mess + + def clearAhkUserDir(self): + key = 'AhkUserDir' + oldvalue = self.userregnl.get(key) + if oldvalue and self.isValidPath(oldvalue): + self.userregnl.set("Old"+key, oldvalue) + if self.userregnl.get(key): + self.userregnl.delete(key) + print 'Clear AutoHotkey User Directory (AhkUserDir)' + else: + mess = 'AutoHotkey User Directory (AhkUserDir) was not set, do nothing' + return mess + + def getAhkUserDir(self): + key = 'AhkUserDir' + value = self.userregnl.get(key, None) + return value + + def setAhkUserDir(self, v): + key = 'AhkUserDir' + ahkuserdir = self.isValidPath(v, wantDirectory=1) + if ahkuserdir: + print 'Set AutoHotkey User Directory (AhkUserDir) to %s'% v + self.userregnl.set(key, v) + self.userregnl.delete('Old'+key) + return + else: + mess = 'not a valid directory: %s'% v + return mess + + def clearAhkExeDir(self): + key = 'AhkExeDir' + oldvalue = self.userregnl.get(key) + if oldvalue and self.isValidPath(oldvalue): + self.userregnl.set("Old"+key, oldvalue) + if self.userregnl.get(key): + self.userregnl.delete(key) + print 'Clear AutoHotkey Exe Directory (AhkExeDir)' + else: + mess = 'AutoHotkey Exe Directory (AhkExeDir) was not set, do nothing' + return mess + + + def getUnimacroUserDir(self): + key = 'UnimacroUserDirectory' + return self.userregnl.get(key, None) + + def setUnimacroUserDir(self, v): + key = 'UnimacroUserDirectory' + oldDir = self.getUnimacroUserDir() + unimacrouserdir = self.isValidPath(v, wantDirectory=1) + if unimacrouserdir: + oldDir = self.isValidPath(oldDir, wantDirectory=1) + if oldDir == unimacrouserdir: + print 'UnimacroUserDirectory is already set to "%s", Unimacro is enabled'% v + return + print 'Set UnimacroUserDirectory to %s, enable Unimacro'% v + if oldDir: + print '\n-----------\nConsider copying inifile subdirectories (enx_inifiles or nld_inifiles)\n' \ + 'from old UnimacroUserDirectory (%s) to \n' \ + 'new UnimacroUserDirectory (%s)\n--------\n'% (oldDir, unimacrouserdir) + self.userregnl.set(key, v) + self.userregnl.delete('Old'+key) + return + else: + mess = 'not a valid directory: %s, '% v + return mess + + + def clearUnimacroUserDir(self): + """clear but keep previous value""" + key = 'UnimacroUserDirectory' + oldValue = self.userregnl.get(key) + self.userregnl.delete(key) + oldDirectory = self.isValidPath(oldValue) + if oldDirectory: + keyOld = 'Old' + key + self.userregnl.set(keyOld, oldValue) + else: + print 'UnimacroUserDirectory was already cleared, Unimacro remains disabled' + + def setUnimacroIniFilesEditor(self, v): + key = "UnimacroIniFilesEditor" + exefile = self.isValidPath(v, wantFile=1) + if exefile and v.endswith(".exe"): + self.userregnl.set(key, v) + self.userregnl.delete("Old"+key) + print 'Set UnimacroIniFilesEditor to "%s"'% v + else: + print 'not a valid .exe file: %s'% (key, v) + + def clearUnimacroIniFilesEditor(self): + key = "UnimacroIniFilesEditor" + oldvalue = self.userregnl.get(key) + oldexefile = self.isValidPath(oldvalue, wantFile=1) + if oldexefile: + self.userregnl.set("Old"+key, oldvalue) + self.userregnl.delete(key) + print 'UnimacroIniFilesEditor cleared' + + def registerNatlinkPyd(self, silent=1): + """register natlink.pyd + + if silent, do through win32api, and not report. This is done whenever NatLink is enabled. + + if NOT silent, go through os.system, and produce a message window. + + Also sets the pythonpath in the HKLM pythonpath section + """ + # give fatal error if Python is not OK... + dummy, dummy = self.getHKLMPythonPathDict(flags=win32con.KEY_ALL_ACCESS) + pythonVersion = self.getPythonVersion() + dragonVersion = self.getDNSVersion() + if not (pythonVersion and len(pythonVersion) == 2): + fatal_error('not a valid python version found: |%s|'% pythonVersion) + + # for safety unregister always: + # print 'first unregister, just to be sure...' + # self.unregisterNatlinkPyd(silent=1) + + PydPath = os.path.join(coreDir, 'natlink.pyd') + # result = self.PydIsRegistered(PydPath) + # print 'register function before: registered: %s'% result + + + if not os.path.isfile(PydPath): + fatal_error("Pyd file not found in core folder: %s"% PydPath) + + baseDir = os.path.join(coreDir, '..') + + newIniSetting = "%s;%s"% (pythonVersion, dragonVersion) + if silent: + try: + import win32api + except: + fatal_error("cannot import win32api, please see if win32all of python is properly installed") + + try: + + result = win32api.WinExec('regsvr32 /s "%s"'% PydPath) + if result: + fatal_error('failed to register %s (result: %s)\nPossibly exit Dragon and run this program in Elevated (admin) mode'% (PydPath, result)) + self.config.set('NatlinkPydRegistered', 0) + return + else: + self.userregnl.set('NatlinkPydRegistered', newIniSetting) + print 'Registring pyd file succesful: %s'% PydPath + + # print 'registered %s '% PydPath + + except: + self.userregnl.set('NatlinkPydRegistered', 0) + fatal_error("cannot register |%s|"% PydPath) + return + else: + # os.system: + result = os.system('regsvr32 "%s"'% PydPath) + if result: + print 'failed to register %s (result: %s)'% (PydPath, result) + self.userregnl.set('NatlinkPydRegistered', 0) + return + else: + self.userregnl.set('NatlinkPydRegistered', newIniSetting) + print 'Registring pyd file succesful: %s'% PydPath + + self.setNatlinkInPythonPathRegistry() + return result is None + + def PydIsRegistered(self, PydPath): + """returns True if path is registered as dll/pyd + + seems not to work or give complications. + """ + try: + # pass this step if it does not succeed: + dll = ctypes.windll[PydPath] + return True + except WindowsError: + dll = None + return False + finally: + handle = dll._handle # obtain the DLL handle + result2 = ctypes.windll.kernel32.FreeLibrary(handle) + pass + + + + def unregisterNatlinkPyd(self, silent=1): + """unregister explicit, should not be done normally + """ + dummy, dummy = self.getHKLMPythonPathDict(flags=win32con.KEY_ALL_ACCESS) + pythonVersion = self.getPythonVersion() + PydPath = os.path.join(coreDir, 'natlink.pyd') + + # if not self.PydIsRegistered(PydPath): + # print 'unregisterNatlinkPyd: is not registered, %s'% PydPath + + if not os.path.isfile(PydPath): + print 'PydPath (%s) does not exist, but pyd is registered, continue'% PydPath + + try: + # pass this step if it does not succeed: + dll = ctypes.windll[PydPath] + result = dll.DllUnregisterServer() + if result != 0: + print 'could not unregister %s'% PydPath + except KeyError: + if os.path.isfile(PydPath): + print 'Cannot unregister natlink.pyd, maybe it is not registered, maybe Dragon is running...' + else: + print 'Cannot unregister natlink.pyd.' + except: + traceback.print_exc() + finally: + handle = dll._handle # obtain the DLL handle + result2 = ctypes.windll.kernel32.FreeLibrary(handle) + + if result2 != 1: + print 'could not free the link to %s'% PydPath + + # extra check: + # registered = self.PydIsRegistered(PydPath) + # if registered and result: + # print 'unregistering %s failed'% PydPath + return result == 0 + + + def enableDebugLoadOutput(self): + """setting registry key so debug output of loading of natlinkmain is given + + """ + key = "NatlinkmainDebugLoad" + self.userregnl.set(key, 1) + + + def disableDebugLoadOutput(self): + """disables the NatLink debug output of loading of natlinkmain is given + """ + key = "NatlinkmainDebugLoad" + self.userregnl.delete(key) + + def enableDebugCallbackOutput(self): + """setting registry key so debug output of callback functions of natlinkmain is given + + """ + key = "NatlinkmainDebugCallback" + self.userregnl.set(key, 1) + + + def disableDebugCallbackOutput(self): + """disables the NatLink debug output of callback functions of natlinkmain + """ + key = "NatlinkmainDebugCallback" + self.userregnl.delete(key) + + # def enableDebugOutput(self): + # """setting registry key so debug output is in NatSpeak logfile + # + # not included in configure GUI, as NatSpeak/natlink.pyd seems not to respond + # to this option... + # """ + # key = "NatlinkDebug" + # self.userregnl.set(key, 1) + # # double in registry, natlink.pyd takes this one: + # print 'Enable %s, this setting is obsolete)'% key + # #self.userregnlOld[key] = 1 + # + # def disableDebugOutput(self): + # """disables the NatLink lengthy debug output to NatSpeak logfile + # """ + # key = "NatlinkDebug" + # self.userregnl.delete(key) + # # double in registry, natlink.pyd takes this one: + # print 'Disable NatlinkDebug, this setting is obsolete'% key + # #self.userregnlOld[key] = 0 + + def copyUnimacroIncludeFile(self): + """copy Unimacro include file into Vocola user directory + + """ + uscFile = 'Unimacro.vch' + oldUscFile = 'usc.vch' + # also remove usc.vch from VocolaUserDirectory + fromFolder = os.path.normpath(os.path.join(thisDir, '..', '..', + 'Unimacro', + 'Vocola_compatibility')) + toFolder = self.getVocolaUserDir() + if os.path.isdir(fromFolder): + fromFile = os.path.join(fromFolder,uscFile) + if os.path.isfile(fromFile): + if os.path.isdir(toFolder): + + toFile = os.path.join(toFolder, uscFile) + if os.path.isfile(toFile): + print 'remove previous %s'% toFile + try: + os.remove(toFile) + except: + pass + print 'copy %s from %s to %s'%(uscFile, fromFolder, toFolder) + try: + shutil.copyfile(fromFile, toFile) + except: + pass + else: + oldUscFile = os.path.join(toFolder, oldUscFile) + if os.path.isfile(oldUscFile): + print 'remove old usc.vcl file: %s'% oldUscFile + os.remove(oldUscFile) + return + mess = "could not copy file %s from %s to %s"%(uscFile, fromFolder, toFolder) + print mess + return mess + + + def includeUnimacroVchLineInVocolaFiles(self, subDirectory=None): + """include the Unimacro wrapper support line into all Vocola command files + + as a side effect, set the variable for Unimacro in Vocola support: + VocolaTakesUnimacroActions... + """ + uscFile = 'Unimacro.vch' + oldUscFile = 'usc.vch' +## reInclude = re.compile(r'^include\s+.*unimacro.vch;$', re.MULTILINE) +## reOldInclude = re.compile(r'^include\s+.*usc.vch;$', re.MULTILINE) + + # also remove includes of usc.vch + toFolder = self.getVocolaUserDir() + if subDirectory: + toFolder = os.path.join(toFolder, subDirectory) + includeLine = 'include ..\\%s;\n'% uscFile + else: + includeLine = 'include %s;\n'%uscFile + oldIncludeLines = ['include %s;'% oldUscFile, + 'include ..\\%s;'% oldUscFile, + 'include %s;'% uscFile.lower(), + 'include ..\\%s;'% uscFile.lower(), + ] + + if not os.path.isdir(toFolder): + mess = 'cannot find Vocola command files directory, not a valid path: %s'% toFolder + print mess + return mess + nFiles = 0 + for f in os.listdir(toFolder): + F = os.path.join(toFolder, f) + if f.endswith(".vcl"): + changed = 0 + correct = 0 + Output = [] + for line in open(F, 'r'): + if line.strip() == includeLine.strip(): + correct = 1 + for oldLine in oldIncludeLines: + if line.strip() == oldLine: + changed = 1 + break + else: + Output.append(line) + if changed or not correct: + # changes were made: + if not correct: + Output.insert(0, includeLine) + open(F, 'w').write(''.join(Output)) + nFiles += 1 + elif len(f) == 3 and os.path.isdir(F): + # subdirectory, recursive + self.includeUnimacroVchLineInVocolaFiles(F) + self.enableVocolaTakesUnimacroActions() + mess = 'changed %s files in %s, and set the variable "%s"'% (nFiles, toFolder, + "VocolaTakesUnimacroActions") + + print mess + + def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): + """remove the Unimacro wrapper support line into all Vocola command files + """ + uscFile = 'Unimacro.vch' + oldUscFile = 'usc.vch' +## reInclude = re.compile(r'^include\s+.*unimacro.vch;$', re.MULTILINE) +## reOldInclude = re.compile(r'^include\s+.*usc.vch;$', re.MULTILINE) + + # also remove includes of usc.vch + if subDirectory: + # for recursive call language subfolders: + toFolder = subDirectory + else: + toFolder = self.getVocolaUserDir() + + oldIncludeLines = ['include %s;'% oldUscFile, + 'include ..\\%s;'% oldUscFile, + 'include %s;'% uscFile, + 'include ..\\%s;'% uscFile, + 'include ../%s;'% oldUscFile, + 'include ../%s;'% uscFile, + 'include %s;'% uscFile.lower(), + 'include ..\\%s;'% uscFile.lower(), + 'include ../%s;'% uscFile.lower(), + ] + + + if not os.path.isdir(toFolder): + mess = 'cannot find Vocola command files directory, not a valid path: %s'% toFolder + print mess + return mess + nFiles = 0 + for f in os.listdir(toFolder): + F = os.path.join(toFolder, f) + if f.endswith(".vcl"): + changed = 0 + Output = [] + for line in open(F, 'r'): + for oldLine in oldIncludeLines: + if line.strip() == oldLine: + changed = 1 + break + else: + Output.append(line) + if changed: + # had break, so changes were made: + open(F, 'w').write(''.join(Output)) + nFiles += 1 + elif len(f) == 3 and os.path.isdir(F): + self.removeUnimacroVchLineInVocolaFiles(F) + mess = 'removed include lines from %s files in %s'% (nFiles, toFolder) + print mess + + + def enableVocolaTakesLanguages(self): + """setting registry so Vocola can divide different languages + + """ + key = "VocolaTakesLanguages" + self.userregnl.set(key, 1) + + + def disableVocolaTakesLanguages(self): + """disables so Vocola cannot take different languages + """ + key = "VocolaTakesLanguages" + self.userregnl.set(key, 0) + + def enableVocolaTakesUnimacroActions(self): + """setting registry so Vocola can divide different languages + + """ + key = "VocolaTakesUnimacroActions" + self.userregnl.set(key, 1) + + + def disableVocolaTakesUnimacroActions(self): + """disables so Vocola does not take Unimacro Actions + """ + key = "VocolaTakesUnimacroActions" + self.userregnl.set(key, 0) + + + + + +def _main(Options=None): + """Catch the options and perform the resulting command line functions + + options: -i, --info: give status info + + -I, --reginfo: give the info in the registry about NatLink + etc., usage above... + + """ + cli = CLI() + shortOptions = "aAiIeEfFgGyYxXDCVbBNOPlmMrRzZuq" + shortArgOptions = "d:c:v:n:o:p:" + if Options: + if type(Options) == types.StringType: + Options = Options.split(" ", 1) + Options = map(string.strip, Options) + else: + Options = sys.argv[1:] + + try: + options, args = getopt.getopt(Options, shortOptions+shortArgOptions) + except getopt.GetoptError: + print 'invalid option: %s'% `Options` + cli.usage() + return + + if args: + print 'should not have extraneous arguments: %s'% `args` + for o, v in options: + o = o.lstrip('-') + funcName = 'do_%s'% o + func = getattr(cli, funcName, None) + if not func: + print 'option %s not found in cli functions: %s'% (o, funcName) + cli.usage() + continue + if o in shortOptions: + func(None) # dummy arg + elif o in shortArgOptions: + func(v) + else: + print 'options should not come here' + cli.usage() + + + +class CLI(cmd.Cmd): + """provide interactive shell control for the different options. + """ + def __init__(self, Config=None): + cmd.Cmd.__init__(self) + self.prompt = '\nNatLink/Vocola/Unimacro config> ' + self.info = "type 'u' for usage" + if Config: + self.config = Config # initialized instance of NatlinkConfig + else: + self.config = NatlinkConfig() + try: + self.config.checkDNSInstallDir() + self.config.checkCoreDirectory() + self.config.correctIniSettings() + # check pyd file + result = self.config.configCheckNatlinkPydFile() + if result is None: + if __name__ == "__main__": + print "Error starting NatlinkConfig, Type 'u' for a usage message" + self.checkedConfig = self.config.checkedUrgent + return + + # warning if path from which this is run does not match the registry + result = self.config.checkPythonPathAndRegistry() + if result is None: + if __name__ == "__main__": + print "Error starting NatlinkConfig, Type 'u' for a usage message" + + self.checkedConfig = self.config.checkedUrgent + return + + + self.checkedConfig = self.config.checkedUrgent + return + + self.config.checkIniFiles() + self.checkedConfig = self.config.checkedUrgent + self.isValidPath = self.config.isValidPath ## convenient + for key in ObsoleteStatusKeys: + # see at top of this file! + if key in self.config.userregnl.keys(): + print 'remove obsolete key from natlinkstatus.ini: "%s"'% key + self.config.userregnl.delete(key) + self.DNSName = self.config.getDNSName() + except ElevationError: + e = sys.exc_info()[1] + print 'You need to run this program in elevated mode. (%s).'% e.message + raise + + if __name__ == "__main__": + print "Type 'u' for a usage message" + + def getFatalErrors(self): + """get the fatal errors from this module and clear them automatically + """ + global hadFatalErrors + if hadFatalErrors: + text = '\n'.join(hadFatalErrors) + hadFatalErrors = [] + return text + + def stripCheckDirectory(self, dirName): + """allow quotes in input, and strip them. + + Return "" if directory is not valid + """ + if not dirName: + return "" + n = dirName.strip() + while n and n.startswith('"'): + n = n.strip('"') + while n and n.startswith("'"): + n = n.strip("'") + if n: + n.strip() + + if os.path.isdir(n): + return n + else: + print 'not a valid directory: %s (%s)'% (n, dirName) + return '' + + + + def usage(self): + """gives the usage of the command line options or options when + the command line interface (CLI) is used + """ + print '-'*60 + print \ +"""Use either from the command line like 'natlinkconfigfunctions.py -i' +or in an interactive session using the CLI (command line interface). + +[Status] + +i - info, print information about the NatLink status +I - settings, print information about the natlinkstatus.ini settings +j - print PythonPath variable + +[NatLink] + +e/E - enable/disable NatLink + +y/Y - enable/disable debug callback output of natlinkmain +x/X - enable/disable debug load output of natlinkmain + +d/D - set/clear DNSInstallDir, the directory where NatSpeak/Dragon is installed +c/C - set/clear DNSINIDir, where NatSpeak/Dragon INI files are located + +[Vocola] + +v/V - enable/disable Vocola by setting/clearing VocolaUserDir, the user + directory for Vocola user files (~ or %HOME% allowed). + +b/B - enable/disable distinction between languages for Vocola user files +a/A - enable/disable the possibility to use Unimacro actions in Vocola + +[Unimacro] + +o/O - enable/disable Unimacro, by setting/clearing the UnimacroUserDirectory, where + the Unimacro user INI files are located, and several other directories (~ or %HOME% allowed) +p/P - set/clear path for program that opens Unimacro INI files. +l - copy header file Unimacro.vch into Vocola User Directory +m/M - insert/remove an include line for Unimacro.vch in all Vocola + command files + +[UserDirectory] +n/N - enable/disable UserDirectory, the directory where + User NatLink grammar files are located (e.g., ...\My Documents\NatLink) + +[Repair] +r/R - register/unregister NatLink, the natlink.pyd (natlink.pyd) file + (should not be needed) +z/Z - silently enables NatLink and registers natlink.pyd / disables NatLink + and unregisters natlink.pyd. +[AutoHotkey] +h/H - set/clear the AutoHotkey exe directory. +k/K - set/clear the User Directory for AutoHotkey scripts. +[Other] + +u/usage - give this list +q - quit + +help : give more explanation on + """ + print '='*60 + + # info---------------------------------------------------------- + def do_i(self, arg): + S = self.config.getNatlinkStatusString() + S = S + '\n\nIf you changed things, you must restart %s'% self.DNSName + print S + def do_I(self, arg): + # inifile natlinkstatus.ini settings: + self.config.printInifileSettings() + def do_j(self, arg): + # print PythonPath: + self.config.printPythonPath() + + def help_i(self): + print '-'*60 + print \ +"""The command info (i) gives an overview of the settings that are +currently set inside the NatLink system. + +The command settings (I) gives all the NatLink settings, kept in +natlinkstatus.ini (overlap with (i)) + +The command (j) gives the PythonPath variable which should contain several +NatLink directories after the config GUI runs succesfully + +Settings are set by either the NatLink/Vocola/Unimacro installer +or by functions that are called by the CLI (command line interface). + +After you change settings, restart %s. +"""% self.DNSName + print '='*60 + help_j = help_I = help_i + + # DNS install directory------------------------------------------ + def do_d(self, arg): + if not arg: + self.message = "please enter a directory" + return + self.message = "Change Dragon directory to: %s"% arg + return self.config.setDNSInstallDir(arg) + + def do_D(self, arg): + self.message = "Clear DNS directory in registry" + print 'do action: %s'% self.message + return self.config.clearDNSInstallDir() + + def help_d(self): + print '-'*60 + print \ +"""Set (d ) or clear (D) the directory where %s is installed. + +Setting is only needed when %s is not found at one of the "normal" places. +So setting is seldom not needed. + +When you have a pre-8 version of NatSpeak, setting this option might work. + +After you clear this setting, NatLink will, at starting time, again +search for the %s install directory in the "normal" place(s). +"""% (self.DNSName, self.DNSName, self.DNSName) + print '='*60 + help_D = help_d + + # DNS INI directory----------------------------------------- + def do_c(self, arg): + arg = self.stripCheckDirectory(arg) # also quotes + if not arg: + return + self.message = "Change %s INI files directory to: %s"% (self.DNSName, arg) + return self.config.setDNSIniDir(arg) + + + + def do_C(self, arg): + self.message = "Clear %s INI files directory in registry"% self.DNSName + print 'do action: %s'% self.message + return self.config.clearDNSIniDir() + def help_c(self): + print '-'*60 + print \ +"""Set (c ) or clear (C) the directory where %s INI file locations +(nssystem.ini and nsapps.ini) are located. + +Only needed if these cannot be found in the normal place(s): +-if you have an "alternative" place where you keep your speech profiles +-if you have a pre-8 version of NatSpeak. + +After Clearing this registry entry NatLink will, when it is started by %s, +again search for its INI files in the "default/normal" place(s). +"""% (self.DNSName, self.DNSName) + print '='*60 + help_C = help_c + + # User Directories ------------------------------------------------- + def do_n(self, arg): + if not arg: + print 'also enter a valid folder' + return + arg = arg.strip() + self.config.setUserDirectory(arg) + + def do_N(self, arg): + self.message = "Clears NatLink User Directory" + self.config.clearUserDirectory() + + # def do_f(self, arg): + # self.message = "Include UnimacroDirectory in PythonPath even if Unimacro is disabled" + # print 'do action: %s'% self.message + # self.config.alwaysIncludeUnimacroDirectoryInPath() + # def do_F(self, arg): + # self.message = "Do NOT include UnimacroDirectory in PythonPath when Unimacro is disabled" + # self.config.ignoreUnimacroDirectoryInPathIfNotUserDirectory() + + def help_n(self): + print '-'*60 + print \ +"""Sets (n ) or clears (N) the UserDirectory of NatLink. +This is the folder where your own python grammar files are/will be located. + +Note this should NOT be the BaseDirectory (Vocola is there) of the Unimacro directory. +""" + print '='*60 + + help_N = help_n + + # Unimacro User directory and Editor or Unimacro INI files----------------------------------- + def do_o(self, arg): + arg = self.stripCheckDirectory(arg) # also quotes + if not arg: + return + self.config.setUnimacroUserDir(arg) + + def do_O(self, arg): + self.message = "Clearing Unimacro user directory, and disable Unimacro" + print 'do action: %s'% self.message + self.config.clearUnimacroUserDir() + + def help_o(self): + print '-'*60 + userDir = self.config.getUserDirectory() + print \ +"""set/clear UnimacroUserDirectory (o /O) + +And enable/disable Unimacro. + +In this directory, your user INI files (and possibly other user +dependent files) will be put. + +You can use (if entered through the CLI) "~" (or %%HOME%%) for user home directory, or +another environment variable (%%...%%). (example: "o ~\NatLink\Unimacro") + +Setting this directory also enables Unimacro. Clearing it disables Unimacro +""" + print '='*60 + + help_O = help_o + + # Unimacro Command Files Editor----------------------------------------------- + def do_p(self, arg): + if os.path.isfile(arg) and arg.endswith(".exe"): + self.message = "Setting (path to) Unimacro INI Files editor to %s"% arg + print 'do action: %s'% self.message + self.config.setUnimacroIniFilesEditor(arg) + else: + print 'Please specify a valid path for the Unimacro INI files editor, not |%s|'% arg + + def do_P(self, arg): + self.message = "Clear Unimacro INI file editor, go back to default Notepad" + print 'do action: %s'% self.message + self.config.clearUnimacroIniFilesEditor() + + def help_p(self): + print '-'*60 + print \ +"""set/clear path to Unimacro INI files editor (p /P) + +By default (when you clear this setting) "notepad" is used, but: + +You can specify a program you like, for example, +TextPad, NotePad++, UltraEdit, or win32pad + +You can even specify Wordpad, maybe Microsoft Word... + +""" + print '='*60 + + help_P = help_p + + # Unimacro Vocola features----------------------------------------------- + # managing the include file wrapper business. + # can be called from the Vocola compatibility button in the config GUI. + def do_l(self, arg): + self.message = "Copy include file Unimacro.vch into Vocola User Directory" + print 'do action: %s'% self.message + self.config.copyUnimacroIncludeFile() + + def help_l(self): + print '-'*60 + print \ +"""Copy Unimacro.vch header file into Vocola User Files directory (l) + +Insert/remove 'include Unimacro.vch' lines into/from each Vocola +command file (m/M) + +Using Unimacro.vch, you can call Unimacro shorthand commands from a +Vocola command. +""" + print '='*60 + + def do_m(self, arg): + self.message = 'Insert "include Unimacro.vch" line in each Vocola Command File' + print 'do action: %s'% self.message + self.config.includeUnimacroVchLineInVocolaFiles() + def do_M(self, arg): + self.message = 'Remove "include Unimacro.vch" line from each Vocola Command File' + print 'do action: %s'% self.message + self.config.removeUnimacroVchLineInVocolaFiles() + help_m = help_M = help_l + + + # enable/disable NatLink------------------------------------------------ + def do_e(self, arg): + self.message = "Enabling NatLink:" + print 'do action: %s'% self.message + self.config.enableNatlink() + def do_E(self, arg): + self.message = "Disabling NatLink:" + self.config.disableNatlink() + + def help_e(self): + print '-'*60 + print \ +"""Enable NatLink (e) or disable NatLink (E): + +When you enable NatLink, the necessary settings in nssystem.ini and nsapps.ini +are done. + +These options require elevated mode and probably Dragon be closed. + +After you restart %s, NatLink should start, opening a window titled +'Messages from NatLink - ...'. + +When you enable NatLink, the file natlink.pyd is (re)registered silently. Use +the commands r/R to register/unregister natlink.pyd explicitly. +(see help r, but most often not needed) + +When you disable NatLink, the necessary settings in nssystem.ini and nsapps.ini +are cleared. + +After you restart %s, NatLink should NOT START ANY MORE +so the window 'Messages from NatLink' is NOT OPENED. + +Note: when you disable NatLink, the natlink.pyd file is NOT unregistered. +It is not called any more by %s, as its declaration is removed from +the Global Clients section of nssystem.ini. +"""% (self.DNSName, self.DNSName, self.DNSName) + print "="*60 + + + help_E = help_e + + + # Vocola and Vocola User directory------------------------------------------------ + def do_v(self, arg): + if not arg: + self.message = "do_v should have an argument" + return + tryPath = self.config.isValidPath(arg) + if not tryPath: + self.message = "do_v, not a valid path: %s"% arg + return + self.message = 'Set VocolaUserDirectory to "%s" and enable Vocola'% arg + print 'do action: %s'% self.message + self.config.setVocolaUserDir(arg) + + def do_V(self, arg): + self.message = "Clear VocolaUserDirectory and (therefore) disable Vocola" + print 'do action: %s'% self.message + self.config.clearVocolaUserDir() + + def help_v(self): + print '-'*60 + print \ +"""Enable/disable Vocola by setting/clearing the VocolaUserDirectory +(v /V). + +In this VocolaUserDirectory your Vocola Command File are/will be located. + + must be an existing folder; NatLink\Vocola in My Documents is a +popular choice. + +You may have to manually create this folder first. +""" + print '='*60 + + help_V = help_v + + # Vocola Command Files Editor----------------------------------------------- +## def do_w(self, arg): +## if os.path.isfile(arg) and arg.endswith(".exe"): +## print "Setting Setting Vocola Command Files editor to %s"% arg +## self.config.setVocolaCommandFilesEditor(arg) +## else: +## print 'Please specify a valid path for Vocola command files editor: |%s|'% arg +## +## def do_W(self, arg): +## print "Clear Vocola commands file editor, go back to default notepad" +## self.config.clearVocolaCommandFilesEditor() +## +## def help_w(self): +## print '-'*60 +## print \ +##"""set/clear Vocola command files editor (w path/W) +## +##By default the editor "notepad" is used. +## +##You can specify a program you like, for example, +##TextPad, NotePad++, UltraEdit, or win32pad. +## +##""" +## +## print '='*60 +## +## help_W = help_w + +## testing: + def do_s(self, arg): + pydPath = r"C:\natlink\natlink\macrosystem\core\natlink.pyd" + print 'registered?: %s'% self.config.PydIsRegistered(pydPath) + pass + def do_g(self, arg): + print 'no valid option' + pass + def do_G(self, arg): + print 'no valid option' + + def help_g(self): + print '-'*60 + print \ +"""not a valid option +""" + print '='*60 + + help_G = help_g + # enable/disable NatLink debug output... + def do_x(self, arg): + self.message = 'Enable natlinkmain giving debugLoad output to "Messages from Natlink" window' + print 'do action: %s'% self.message + self.config.enableDebugLoadOutput() + def do_X(self, arg): + self.message = 'Disable natlinkmain giving debugLoad output to "Messages from Natlink" window' + print 'do action: %s'% self.message + self.config.disableDebugLoadOutput() + # enable/disable NatLink debug output... + def do_y(self, arg): + self.message = 'Enable natlinkmain giving debugCallback output to "Messages from Natlink" window' + print 'do action: %s'% self.message + self.config.enableDebugCallbackOutput() + def do_Y(self, arg): + self.message = 'Disable natlinkmain giving debugCallback output to messages of "Messages from Natlink" window' + print 'do action: %s'% self.message + self.config.disableDebugCallbackOutput() + + + + def help_x(self): + print '-'*60 + print \ +"""Enable (x)/disable (X) natlinkmain debug load output + +Enable (y)/disable (Y) natlinkmain debug callback output + +Nearly obsolete options. + +This sends sometimes lengthy debugging messages to the +"Messages from NatLink" window. + +Mainly used when you suspect problems with the working +of NatLink, so keep off (X and Y) most of the time. +""" + print '='*60 + + help_y = help_x + help_X = help_x + help_Y = help_x + + # register natlink.pyd + def do_r(self, arg): + self.message = "(Re) register and enable natlink.pyd" + if self.config.isElevated: + print 'do action: %s'% self.message + isRegistered = self.config.userregnl.get("NatlinkPydRegistered") + #if isRegistered: + # print "If you have problems re-registering natlink.pyd, please try the following:" + # print "Un-register natlink.pyd first, then" + # print "If you want to try a new natlink.pyd, first exit this program," + # print "Remove %s\\natlink.pyd"% coreDir + # print "and restart (in elevated mode) this program." + # print "The correct python version of natlink.pyd will be copied to natlink.pyd" + # print "and it will be registered again." + # return + if not self.config.removeNatlinkPyd(): + return + self.config.configCheckNatlinkPydFile() + + self.config.enableNatlink() + else: + raise ElevationError(self.message) + + # + # + #self.config.registerNatlinkPyd(silent=None) + + def do_R(self, arg): + self.message = "Unregister natlink.pyd and disable NatLink" + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("Probably needed before you can unregister natlink.pyd") + + if self.config.isElevated: + self.config.disableNatlink(silent=1) + self.config.unregisterNatlinkPyd(silent=None) + else: + raise ElevationError(self.message) + + def do_z(self, arg): + """register silent and enable NatLink""" + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("Probably needed before you can register natlink.pyd") + + if not self.config.removeNatlinkPyd(): + return + self.config.configCheckNatlinkPydFile() + self.config.enableNatlink() + + def do_Z(self, arg): + """(SILENT) Unregister natlink.pyd and disable NatLink""" + # if self.isNatSpeakRunning(): raise NatSpeakRunningError("Probably needed before you can unregister natlink.pyd") + self.config.disableNatlink(silent=1) + self.config.unregisterNatlinkPyd(silent=1) + + def help_r(self): + print '-'*60 + print \ +"""Registers (r) / unregisters (R) natlink.pyd explicitly. + +Registering is also done (silently) when you start this program or the +configuration GUI the first time, so this option should only be needed in rare cases. + +But if you do (-r or -R) a message dialog shows up to inform you what happened. +When you unregister, NatLink is also disabled. + +When you want to try a new version of natlink.pyd, take the following steps: +-close Dragon +-remove natlink.pyd (in the MacroSystem/core directory of NatLink) +-rerun this program or the configure program in elevated mode. + +The correct version of natlink.pyd (corresponding with your python version 2.6, 2.7 (2.5 for pre Dragon 12) +will be copied to this name and registered. In the log panel of the configure GUI the steps will show the result. + +-restart Dragon. + +If you want to (silently) enable NatLink and register silently use -z, +To disable NatLink and unregister (silently) use Z +""" + print '='*60 + help_R = help_r + help_z = help_r + help_Z = help_r + + + # different Vocola options + def do_b(self, arg): + self.message = "Enable Vocola different user directories for different languages" + print 'do action: %s'% self.message + self.config.enableVocolaTakesLanguages() + def do_B(self, arg): + self.message = "Disable Vocola different user directories for different languages" + print 'do action: %s'% self.message + self.config.disableVocolaTakesLanguages() + + def do_a(self, arg): + self.message = "Enable Vocola taking Unimacro actions" + print 'do action: %s'% self.message + self.config.enableVocolaTakesUnimacroActions() + def do_A(self, arg): + self.message = "Disable Vocola taking Unimacro actions" + print 'do action: %s'% self.message + self.config.disableVocolaTakesUnimacroActions() + + def help_a(self): + print '-'*60 + print \ +"""----Enable (a)/disable (A) Vocola taking Unimacro actions. + +These actions (Unimacro Shorthand Commands) and "meta actions" are processed by +the Unimacro actions module. + +If Unimacro is NOT enabled, it will also +be necessary that the UnimacroDirectory is put in the python path. +The special option for that is (f). + +Note this option (f) is only needed when you use Vocola with Unimacro actions, +but you do not use Unimacro. +""" + print '='*60 + + def help_b(self): + print '-'*60 + print \ +"""----Enable (b)/disable (B) different Vocola User Directories + +If enabled, Vocola will look into a subdirectory "xxx" of +VocolaUserDirectory IF the language code of the current user speech +profile is "xxx" and is NOT "enx". + +So for English users this option will have no effect. + +The first time a command file is opened in, for example, a +Dutch speech profile (language code "nld"), a subdirectory "nld" +is created, and all existing Vocola Command files for this Dutch speech profile are copied into this folder. + +When you use your English speech profile again, ("enx") the Vocola Command files in the VocolaUserDirectory are taken again. +""" + print '='*60 + + help_B = help_b + help_A = help_a + + # autohotkey settings: + def do_h(self, arg): + self.message = 'set directory of AutoHotkey.exe to: %s'% arg + print 'do action: %s'% self.message + self.config.setAhkExeDir(arg) + + def do_H(self, arg): + self.message = 'clear directory of AutoHotkey.exe, return to default' + print 'do action: %s'% self.message + self.config.clearAhkExeDir() + + def do_k(self, arg): + arg = self.stripCheckDirectory(arg) # also quotes + if not arg: + return + self.message = 'set user directory for AutoHotkey scripts to: %s'% arg + self.config.setAhkUserDir(arg) + + def do_K(self, arg): + self.message = 'clear user directory of AutoHotkey scripts, return to default' + print 'do action: %s'% self.message + self.config.clearAhkUserDir() + + def help_h(self): + print '-'*60 + print \ +"""----Set (h)/clear (return to default) (H) the AutoHotkey exe directory. + Assume autohotkey.exe is found there (if not AutoHotkey support will not be there) + If set to a invalid directory, AutoHotkey support will be switched off. + + Set (k)/clear (return to default) (K) the User Directory for AutoHotkey scripts. + + Note: currently these options can only be run from the natlinkconfigfunctions.py script. +""" + print '='*60 + + help_H = help_k = help_K = help_h + + # enable/disable NatLink debug output... + + def default(self, line): + print 'no valid entry: %s, type u or usage for list of commands'% line + print + + def do_quit(self, arg): + sys.exit() + do_q = do_quit + def do_usage(self, arg): + self.usage() + do_u = do_usage + def help_u(self): + print '-'*60 + print \ +"""u and usage give the list of commands +lowercase commands usually set/enable something +uppercase commands usually clear/disable something +Informational commands: i and I +""" + help_usage = help_u + + + +if __name__ == "__main__": + if len(sys.argv) == 1: + cli = CLI() + cli.info = "type u for usage" + try: + cli.cmdloop() + except (KeyboardInterrupt, SystemExit): + pass + except ElevationError: + e = sys.exc_info()[1] + print 'please run this program in elevated mode (%s).'% e.message + cli.do_q("dummy") + except NatSpeakRunningError: + e = sys.exc_info()[1] + print 'Dragon should not be running, %s.'% e.message + else: + _main() + diff --git a/NatlinkModule/DefaultConfig/_config_instructions.py b/NatlinkModule/DefaultConfig/_config_instructions.py new file mode 100644 index 00000000..7f828fe9 --- /dev/null +++ b/NatlinkModule/DefaultConfig/_config_instructions.py @@ -0,0 +1,54 @@ +"""module that is loaded when no config file is found elsewhere + +It gives the instructions for a correct place to put natlink.ini in +""" +import os +import os.path +join, expanduser, getenv = os.path.join, os.path.expanduser, os.getenv +isfile, isdir = os.path.isfile, os.path.isdir +home = expanduser('~') + +thisFile = __file__ +this_dir, this_filename = __file__.rsplit('\\', 1) + +print() +print() +print(f'This is the file "{this_filename}" from directory "{this_dir}"') +print('This directory holds the default "natlink.ini" file, when it is not found the default or configured directory.') +print() +print(rf'The default directory is: "~\.natlink", with "~" being your HOME directory: "{home}".') +print(f'\tSo: "{home}\\.natlink"') +print() +print('There is also a custom way to configure the directory of your "natlink.ini" file:') +print() +print('Specify the environment variable "NATLINK_USERDIR", which should point to an existing directory.') +print() +print(f'When this directory does not hold the file "natlink.ini",\nyou can copy it from "{this_dir}",') +print('or (easier) run the configure program of Natlink ("configurenatlink.pyw" or "natlinkconfigfunctions.py").') +print('\tThe default version of "natlink.ini" will be copied into') +print('\tthe correct place, and you can proceed with configuring') +print('\tthe additional options in order to get started with using Natlink.') +print() + +natlink_userdir = getenv("NATLINK_USERDIR") + +if natlink_userdir: + if isdir(natlink_userdir): + print(f'You specified "NATLINK_USERDIR" to "{natlink_userdir}".') + print('So Natlink should not startup with these messages. ') + print('Probably you do not have a proper "natlink.ini" file into this path.') + print('Please run the configure program of Natlink ("configurenatlink.pyw" or "natlinkconfigfunctions.py")') + else: + print(f'You specified "NATLINK_USERDIR" to "{natlink_userdir}", but this is not a valid directory path') +else: + print() + print('You did not set "NATLINK_USERDIR" on your windows system') + print(f'The config file "natlink.ini" should now be in "{home}\\.natlink\\natlink.ini"') + print() + print(f'So... please copy the file "{this_dir}\\natlink.ini" into this location, or (better)') + print('run the configure program of Natlink ("configurenatlink.pyw" or "natlinkconfigfunctions.py")') + +print() + +print('After all these steps, (re)start Dragon and good luck...\n\n\n') + diff --git a/NatlinkModule/DefaultConfig/_information_config.py b/NatlinkModule/DefaultConfig/_information_config.py new file mode 100644 index 00000000..59c09df9 --- /dev/null +++ b/NatlinkModule/DefaultConfig/_information_config.py @@ -0,0 +1,28 @@ +"""example file for loading natlink from the default (fallback) location + +It gives configuration information + +When in trouble, rename this file to "_information_config.py" and rerun Dragon. +Otherwise, this file will not be loaded from this location. +""" +#pylint:disable=W0212 +import sys +import sysconfig +from pprint import pprint + +print() +print() +print("I'm _information_config.py and I'm being loaded, too, on my own.") +print() +print("This is your windows version from sys.getwindowsversion():") +print("---------------------------------------------------") +print(f'{sys.getwindowsversion()}') +print() +print("This is your Python configuration as per sysconfig:") +print("---------------------------------------------------") +sysconfig._main() +print("This is your Python system path sys.path:") +print("-----------------------------------------") +pprint(sys.path) +print("End of _information_config.py info.") +print("-----------------------------") diff --git a/NatlinkModule/InstallTest/natlink.ini b/NatlinkModule/DefaultConfig/natlink.ini similarity index 72% rename from NatlinkModule/InstallTest/natlink.ini rename to NatlinkModule/DefaultConfig/natlink.ini index 34f733d2..fd547c9d 100644 --- a/NatlinkModule/InstallTest/natlink.ini +++ b/NatlinkModule/DefaultConfig/natlink.ini @@ -1,11 +1,14 @@ [directories] -# typically a single line below needs uncommenting +# specify the directory / directories that are loaded when Natlink starts: +# typically a single line below needs uncommenting, but more is also possible # Caster = C:\User\X\Dropbox\Caster # Dragonfly = C:\User\X\Dropbox\Dragonfly # Vocola = C:\User\X\Dropbox\Vocola # Unimacro = C:\User\X\Dropbox\Unimacro +# UserDirectory = C:\User\X\Dropbox\UserDirectory ## can be the same as Dragonfly or Caster directory -install_test = C:\Program Files (x86)\Natlink\InstallTest +# remove this line when configuring your own directories: +default_config = C:\Program Files (x86)\Natlink\DefaultConfig [userenglish-directories] #only_loaded_if_profile_userenglish_active=C:\User\user\english-only-scripts diff --git a/NatlinkModule/InstallTest/__init__.py b/NatlinkModule/InstallTest/__init__.py deleted file mode 100644 index d7451bca..00000000 --- a/NatlinkModule/InstallTest/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Not meant to be a package! -# Just a demo of natlink's way of loading and reloading stuff. -print("I am __init__.py and I'm loaded first because I am __init__.py") -import zebra \ No newline at end of file diff --git a/NatlinkModule/InstallTest/_meet_natlink.py b/NatlinkModule/InstallTest/_meet_natlink.py deleted file mode 100644 index 013c1138..00000000 --- a/NatlinkModule/InstallTest/_meet_natlink.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys -import sysconfig -from pprint import pprint -print("I'm _meet_natlink.py and I'm being loaded, too, on my own.") -print("This is your Python configuration as per sysconfig:") -print("---------------------------------------------------") -sysconfig._main() -print("This is your Python system path sys.path:") -print("-----------------------------------------") -pprint(sys.path) -print("End of _meet_natlink.py info.") -print("-----------------------------") diff --git a/NatlinkModule/InstallTest/zebra.py b/NatlinkModule/InstallTest/zebra.py deleted file mode 100644 index 12309314..00000000 --- a/NatlinkModule/InstallTest/zebra.py +++ /dev/null @@ -1 +0,0 @@ -print("I'm zebra.py and I'm being loaded from __init__.py") diff --git a/NatlinkModule/__init__.py b/NatlinkModule/__init__.py index ae3c089f..0f6d613b 100644 --- a/NatlinkModule/__init__.py +++ b/NatlinkModule/__init__.py @@ -1,3 +1,4 @@ +#pylint:disable=C0114, W0401 from typing import Optional from ._natlink_core import * diff --git a/NatlinkModule/callbackhandler.py b/NatlinkModule/callbackhandler.py new file mode 100644 index 00000000..4523bc4c --- /dev/null +++ b/NatlinkModule/callbackhandler.py @@ -0,0 +1,75 @@ +"""CallbackHandler instances can handle callbacks (for Natlink, natlinkmain)$ +""" +#pylint:disable=C0115, C0116, W0703, R0201 +import traceback + +class CallbackHandler: + """callbacks can be set, unset (not necessary) and called + + The callbacks are inserted at 0, because they are "run" in reverse order. + When a func raises an error, it is removed from the callbacks list. + """ + def __init__(self, name): + self.callbacks = [] + self.name = name + + def info(self): + if self.callbacks: + return f'{self.name}: {self.callbacks}' + return f'{self.name}: empty' + + def set(self, func): + if not callable(func): + raise TypeError(f'callbackhandler "{self.name}", set: not a callable: {func}, (type: {type(func)})') + if func not in self.callbacks: + self.callbacks.insert(0, func) + + def delete(self, func): + if func in self.callbacks: + self.callbacks.remove(func) + # else: + # print(f'{self.name}: not a callback function for this callbackhandler ({func})') + + def run(self): + """call funcs in reverse order, and remove if it raises an exception + """ + for i in range(len(self.callbacks)-1, -1, -1): + self._call_and_catch_all_exceptions(i) + + def _call_and_catch_all_exceptions(self, i: int) -> None: + fn = self.callbacks[i] + try: + fn() + # except AttributeError: + # ## delete silently: + # del self.callbacks[i] + except Exception: + del self.callbacks[i] + print(traceback.format_exc()) + +if __name__ == "__main__": + class MakeFunctions: + def __init__(self): + self.text = '' + def a1(self): + self.text = 'a1' + print("a1") + def a2(self): + self.text = 'a2' + print("a2") + def a3(self): + self.text = 'a3' + b = 1/0 + print(f'a3: {b}') + + functions1 = MakeFunctions() + + cbh = CallbackHandler('test handler') + print(cbh.info()) + cbh.set(functions1.a1) + print(cbh.info()) + functions1 = None + cbh.run() + print(cbh.info()) + + \ No newline at end of file diff --git a/NatlinkModule/config.py b/NatlinkModule/config.py index 62e7cc83..e0f7ac43 100644 --- a/NatlinkModule/config.py +++ b/NatlinkModule/config.py @@ -1,17 +1,14 @@ -#pylint:disable=C0114, C0115, C0116, R0913 +#pylint:disable=C0114, C0115, C0116, R0913, E1101 import configparser import logging import os -from collections import OrderedDict from enum import IntEnum from typing import List, Iterable, Dict import natlink -NATLINK_INI = "natlink.ini" class NoGoodConfigFoundException(natlink.NatError): pass - class LogLevel(IntEnum): CRITICAL = logging.CRITICAL FATAL = logging.FATAL @@ -40,7 +37,7 @@ def __repr__(self) -> str: @staticmethod def get_default_config() -> 'NatlinkConfig': - return NatlinkConfig(directories_by_user=OrderedDict(), + return NatlinkConfig(directories_by_user=dict(), log_level=LogLevel.NOTSET, load_on_mic_on=True, load_on_begin_utterance=False, @@ -73,10 +70,15 @@ def from_config_parser(config: configparser.ConfigParser, config_path: str) -> ' elif section == 'directories': directories = [] for name, directory in config[section].items(): - if not os.path.isdir(directory): - print(f'from_config_parser: skip "{directory}" ("{name}"): is not a valid directory') + ## allow environment variables (or ~) in directory + directory_expanded = expand_path(directory) + if not os.path.isdir(directory_expanded): + if directory_expanded == directory: + print(f'from_config_parser: skip "{directory}" ("{name}"): is not a valid directory') + else: + print(f'from_config_parser: skip "{directory}" ("{name}"):\n\texpanded to directory "{directory_expanded}" is not a valid directory') continue - directories.append(directory) + directories.append(directory_expanded) ret.directories_by_user[''] = directories if config.has_section('settings'): @@ -98,10 +100,35 @@ def from_file(cls, fn: str) -> 'NatlinkConfig': @classmethod def from_first_found_file(cls, files: Iterable[str]) -> 'NatlinkConfig': + isfile = os.path.isfile config = configparser.ConfigParser() for fn in files: + if not isfile(fn): + continue if config.read(fn): return cls.from_config_parser(config, config_path=fn) - # should not happen, because of InstallTest - raise NoGoodConfigFoundException(f'No config file found, did you define your {NATLINK_INI}?') - \ No newline at end of file + # should not happen, because of DefaultConfig (was InstallTest) + raise NoGoodConfigFoundException('No natlink config file found, please run configure natlink program\n\t(configurenatlink.pyw or natlinkconfigfunctions.py)') + +def expand_path(input_path: str) -> str: + r"""expand path if it starts with "~" or has environment variables (%XXXX%) + + Use home ("~") or "%natlink_userdir%" + + The Documents directory can be found by "~\Documents" + + When nothing to expand, return input + """ + expanduser, expandvars, normpath = os.path.expanduser, os.path.expandvars, os.path.normpath + + if input_path.startswith('~'): + home = expanduser('~') + env_expanded = home + input_path[1:] + # print(f'expand_path: "{input_path}" include "~": expanded: "{env_expanded}"') + return normpath(env_expanded) + env_expanded = expandvars(input_path) + # print(f'env_expanded: "{env_expanded}", from envvar: "{input_path}"') + return normpath(env_expanded) + + + diff --git a/NatlinkModule/loader.py b/NatlinkModule/loader.py index acb2002a..6fbf1a3d 100644 --- a/NatlinkModule/loader.py +++ b/NatlinkModule/loader.py @@ -1,4 +1,4 @@ -#pylint:disable=C0114, C0115, C0116, R1705, R0902, W0703, E1101 +#pylint:disable=C0114, C0115, C0116, R1705, R0902, R0904, R0912, R0915, W0703, E1101 import importlib import importlib.machinery import importlib.util @@ -9,45 +9,196 @@ import time import traceback import winreg +import configparser from pathlib import Path from types import ModuleType -from typing import List, Dict, Set, Iterable, Any, Tuple, Callable, Optional +from typing import List, Dict, Set, Iterable, Any, Tuple, Callable import natlink -from natlink.config import LogLevel, NatlinkConfig, NATLINK_INI +from natlink.config import LogLevel, NatlinkConfig, expand_path +from natlink.readwritefile import ReadWriteFile +from natlink.callbackhandler import CallbackHandler +from natlink.singleton import Singleton +# the possible languages (for get_user_language) (runs at start and on_change_callback, user) +# default is "enx", being one of the English dialects... +UserLanguages = { + "Nederlands": "nld", + "Fran\xe7ais": "fra", + "Deutsch": "deu", + "Italiano": "ita", + "Espa\xf1ol": "esp", + "Dutch": "nld", + "French": "fra", + "German": "deu", + "Italian": "ita", + "Spanish": "esp",} - -class NatlinkMain: - def __init__(self, logger: logging.Logger, config: NatlinkConfig): +class NatlinkMain(metaclass=Singleton): + """main class of Natlink, make it a "singleton" + """ + + def __init__(self, logger: Any=None, config: Any = None): + if logger is None: + raise ValueError(f'loader.NatlinkMain, first instance should be called with a logging.Logger instance, not {logger}') + if config is None: + raise ValueError(f'loader.NatlinkMain, first instance should be called with a NatlinkConfig instance, not {config}') self.logger = logger self.config = config self.loaded_modules: Dict[Path, ModuleType] = {} self.prog_names_visited: Set[str] = set() # to enable loading program specific grammars self.bad_modules: Set[Path] = set() self.load_attempt_times: Dict[Path, float] = {} - self._user: str = '' - self._pre_load_callback: Optional[Callable[[], None]] = None - self._post_load_callback: Optional[Callable[[], None]] = None + self.__user: str = '' # + self.__profile: str = '' # at start and on_change_callback user + self.__language: str = '' # + self.__load_on_begin_utterance = None + self.load_on_begin_utterance = self.config.load_on_begin_utterance # set the property load_on_begin_utterance + # callback instances: + self._pre_load_callback = CallbackHandler('pre_load') + self._post_load_callback = CallbackHandler('post_load') + self._on_mic_on_callback = CallbackHandler('on_mic_on') + self._on_begin_utterance_callback = CallbackHandler('on_begin_utterance') self.seen: Set[Path] = set() # start empty in trigger_load + self.bom = self.encoding = self.config_text = '' # getconfigsetting and writeconfigsetting + + def __del__(self): + """for testing only needed, reset the class attributes + """ + self.__class__.__instance = None + self.__class__.had_init = False + + def set_on_begin_utterance_callback(self, func: Callable[[], None]) -> None: + self._on_begin_utterance_callback.set(func) + + def set_on_mic_on_callback(self, func: Callable[[], None]) -> None: + self._on_mic_on_callback.set(func) + + def set_pre_load_callback(self, func: Callable[[], None]) -> None: + self._pre_load_callback.set(func) + def set_post_load_callback(self, func: Callable[[], None]) -> None: + self._post_load_callback.set(func) - def set_pre_load_callback(self, pre_load: Optional[Callable[[], None]]) -> None: - if pre_load is None: - self._pre_load_callback = None - elif not callable(pre_load): - raise TypeError(f'pre-load callback must be callable, got type: {type(pre_load)}') - self._pre_load_callback = pre_load + def delete_on_begin_utterance_callback(self, func: Callable[[], None]) -> None: + self._on_begin_utterance_callback.delete(func) - def set_post_load_callback(self, post_load: Optional[Callable[[], None]]) -> None: - if post_load is None: - self._post_load_callback = None - elif not callable(post_load): - raise TypeError(f'post-load callback must be callable, got type: {type(post_load)}') - self._post_load_callback = post_load + def delete_on_mic_on_callback(self, func: Callable[[], None]) -> None: + self._on_mic_on_callback.delete(func) + + def delete_pre_load_callback(self, func: Callable[[], None]) -> None: + self._pre_load_callback.delete(func) + + def delete_post_load_callback(self, func: Callable[[], None]) -> None: + self._post_load_callback.delete(func) @property def module_paths_for_user(self) -> List[Path]: - return self._module_paths_in_dirs(self.config.directories_for_user(self._user)) + return self._module_paths_in_dirs(self.config.directories_for_user(self.user)) + + # @property + # def module_paths_for_directory(self) -> List[Path]: + # return self._module_paths_in_dir(self.config.directories_for_user(self.user)) + + # three properties, which are set at start or at on_change_callback: + @property + def language(self) -> str: + """holds the language of the current profile (default 'enx') + """ + if self.__language == '': + self.set_user_language() + + return self.__language or 'enx' + + @language.setter + def language(self, value: str): + if value and len(value) == 3: + self.__language = value + else: + self.__language = 'enx' + self.logger.warning(f'set language property: invalid value ("{value}"), set "enx"') + + @property + def profile(self) -> str: + """holds the directory profile of current user profile + """ + return self.__profile or '' + + @profile.setter + def profile(self, value: str): + self.__profile = value or '' + + @property + def user(self) -> str: + """holds the name of the current user profile + """ + return self.__user or '' + + @user.setter + def user(self, value: str): + self.__user = value or '' + + # load_on_begin_utterance is a property... + def get_load_on_begin_utterance(self) -> Any: + """this value is most often True or False, taken from the config file + + It can also be (set to) a positive int, with which it does + the load_on_begin_utterance so many times. After these utterances, + the value falls back to False. + + With Vocola, there is one utterance delay in the updating of the changed vocola command files. + """ + return self.__load_on_begin_utterance + + def set_load_on_begin_utterance(self, value: Any): + """set the value for loading at each utterance to True, False or positive int + + For Vocola, setting this value to 1 did not work, setting to 2 does, so + you need one extra utterance for a new vocola command to come through. + """ + if isinstance(value, int): + if value > 0: + self.logger.info(f'set_load_on_begin_utterance to {value}') + self.__load_on_begin_utterance = value + else: + self.logger.info('set_load_on_begin_utterance to False') + self.__load_on_begin_utterance = False + elif value in [True, False]: + self.__load_on_begin_utterance = value + else: + raise TypeError(f'set_load_on_begin_utterance, invalid type for value: {value} (type: {type(value)})') + + + load_on_begin_utterance = property(get_load_on_begin_utterance, set_load_on_begin_utterance) + + # def _module_paths_in_dir(self, directory: str) -> List[Path]: + # """give modules in directory + # """ + # + # def is_script(f: Path) -> bool: + # if not f.is_file(): + # return False + # if not f.suffix == '.py': + # return False + # + # if f.stem.startswith('_'): + # return True + # for prog_name in self.prog_names_visited: + # if f.stem == prog_name or f.stem.startswith( prog_name + '_'): + # return True + # return False + # + # init = '__init__.py' + # + # mod_paths: List[Path] = [] + # dir_path = Path(directory) + # scripts = sorted(filter(is_script, dir_path.iterdir())) + # init_path = dir_path.joinpath(init) + # if init_path in scripts: + # scripts.remove(init_path) + # scripts.insert(0, init_path) + # mod_paths.extend(scripts) + # + # return mod_paths def _module_paths_in_dirs(self, directories: Iterable[str]) -> List[Path]: @@ -81,8 +232,9 @@ def is_script(f: Path) -> bool: @staticmethod def _add_dirs_to_path(directories: Iterable[str]) -> None: for d in directories: - if d not in sys.path: - sys.path.insert(0, d) + d_expanded = expand_path(d) + if d_expanded not in sys.path: + sys.path.insert(0, d_expanded) def _call_and_catch_all_exceptions(self, fn: Callable[[], None]) -> None: try: @@ -117,15 +269,24 @@ def load_or_reload_module(self, mod_path: Path, force_load: bool = False) -> Non self.logger.warning(f'Attempting to load duplicate module: {mod_path})') return + # if not self.load_attempt_times: + # self.logger.warning(f'======== load_attempt_times is empty: {self.load_attempt_times}') + last_attempt_time = self.load_attempt_times.get(mod_path, 0.0) self.load_attempt_times[mod_path] = time.time() + try: if mod_path in self.bad_modules: + self.logger.debug(f'mod_path: {mod_path}, in self.bad_modules...') last_modified_time = mod_path.stat().st_mtime if force_load or last_attempt_time < last_modified_time: self.logger.info(f'loading previously bad module: {mod_name}') module = self._import_module_from_path(mod_path) - self.bad_modules.remove(mod_path) + try: + self.bad_modules.remove(mod_path) + except KeyError: + # added QH, I think it should not come here: + self.logger.warning(f'load_or_reload_module, unexpected, cannot remove key {mod_path} from self.bad_modules:\n\t{self.bad_modules}\n\t====\n') self.loaded_modules[mod_path] = module return else: @@ -133,7 +294,7 @@ def load_or_reload_module(self, mod_path: Path, force_load: bool = False) -> Non return else: maybe_module = self.loaded_modules.get(mod_path) - if maybe_module is None: + if force_load or maybe_module is None: self.logger.info(f'loading module: {mod_name}') module = self._import_module_from_path(mod_path) self.loaded_modules[mod_path] = module @@ -141,18 +302,26 @@ def load_or_reload_module(self, mod_path: Path, force_load: bool = False) -> Non else: module = maybe_module last_modified_time = mod_path.stat().st_mtime - if force_load or last_attempt_time < last_modified_time: - self.logger.info(f'reloading module: {mod_name}') + diff = last_modified_time - last_attempt_time # check for -0.1 instead of 0, a ??? + # _pre_load_callback may need this.. + if force_load or diff > 0: + if force_load: + self.logger.info(f'reloading module: {mod_name}, force_load: {force_load}') + else: + self.logger.info(f'reloading module: {mod_name}') + self.unload_module(module) del module module = self._import_module_from_path(mod_path) self.loaded_modules[mod_path] = module + self.logger.debug(f'loaded module: {module.__name__}') return else: self.logger.debug(f'skipping unchanged loaded module: {mod_name}') return except Exception: self.logger.exception(traceback.format_exc()) + self.logger.debug(f'load_or_reload_module, exception, add to self.bad_modules {mod_path}') self.bad_modules.add(mod_path) if mod_path in self.loaded_modules: old_module = self.loaded_modules.pop(mod_path) @@ -160,9 +329,9 @@ def load_or_reload_module(self, mod_path: Path, force_load: bool = False) -> Non del old_module importlib.invalidate_caches() - def load_or_reload_modules(self, mod_paths: Iterable[Path]) -> None: + def load_or_reload_modules(self, mod_paths: Iterable[Path], force_load: bool = None) -> None: for mod_path in mod_paths: - self.load_or_reload_module(mod_path) + self.load_or_reload_module(mod_path, force_load=force_load) self.seen.add(mod_path) def remove_modules_that_no_longer_exist(self) -> None: @@ -181,54 +350,124 @@ def remove_modules_that_no_longer_exist(self) -> None: importlib.invalidate_caches() - def trigger_load(self) -> None: + def trigger_load(self, force_load: bool = None) -> None: self.seen.clear() - self.logger.debug('triggering load/reload process') + if force_load: + self.logger.debug(f'triggering load/reload process (force_load: {force_load})') + else: + self.logger.debug('triggering load/reload process') + self.remove_modules_that_no_longer_exist() mod_paths = self.module_paths_for_user - if self._pre_load_callback is not None: - self.logger.debug('calling pre-load callback') - self._call_and_catch_all_exceptions(self._pre_load_callback) - self.load_or_reload_modules(mod_paths) - if self._post_load_callback is not None: - self.logger.debug('calling post-load callback') - self._call_and_catch_all_exceptions(self._post_load_callback) - loaded_diff = set(self.module_paths_for_user).difference(self.loaded_modules.keys()) - if loaded_diff: - self.logger.debug(f'second round, load new grammar files: {loaded_diff}') - - for mod_path in loaded_diff: - self.logger.debug(f'new module in second round: {mod_path}') - self.load_or_reload_module(mod_path) + self._pre_load_callback.run() + self.load_or_reload_modules(mod_paths, force_load=force_load) + self._post_load_callback.run() def on_change_callback(self, change_type: str, args: Any) -> None: - self.logger.debug(f'on_change_callback called with: change: {change_type}, args: {args}') + """on_change_callback, when another user profile is chosen, or when the mic state changes + """ if change_type == 'user': - user, _profile = args - if not isinstance(user, str): - raise TypeError(f'unexpected args given to change callback: {args}') - self._user = user + self.set_user_language(args) + self.logger.debug(f'on_change_callback, user "{self.user}", profile: "{self.profile}", language: "{self.language}"') if self.config.load_on_user_changed: - self.trigger_load() + self.trigger_load(force_load=True) elif change_type == 'mic' and args == 'on': + self.logger.debug('on_change_callback called with: "mic", "on"') + self._on_mic_on_callback.run() + if self.config.load_on_mic_on: self.trigger_load() + else: + self.logger.debug(f'on_change_callback unhandled: change_type: "{change_type}", args: "{args}"') + def on_begin_callback(self, module_info: Tuple[str, str, int]) -> None: self.logger.debug(f'on_begin_callback called with: moduleInfo: {module_info}') + self._on_begin_utterance_callback.run() + prog_name = Path(module_info[0]).stem if prog_name not in self.prog_names_visited: self.prog_names_visited.add(prog_name) self.trigger_load() - elif self.config.load_on_begin_utterance: + elif self.load_on_begin_utterance: + # manipulate this setting: + value = self.load_on_begin_utterance + if isinstance(value, int): + value -= 1 + value = value or False + self.load_on_begin_utterance = value self.trigger_load() + + def get_user_language(self, DNSuserDirectory): + """return the user language (default "enx") from Dragon inifiles + + like "nld" for Dutch, etc. + """ + isfile, isdir, join = os.path.isfile, os.path.isdir, os.path.join + + if not (DNSuserDirectory and isdir(DNSuserDirectory)): + self.logger.debug('get_user_language, no DNSuserDirectory passed, probably Dragon is not running, return "enx"') + return 'enx' + + ns_options_ini = join(DNSuserDirectory, 'options.ini') + if not (ns_options_ini and isfile(ns_options_ini)): + self.logger.debug(f'get_user_language, warning no valid ini file: "{ns_options_ini}" found, return "enx"') + return "enx" + + section = "Options" + keyname = "Last Used Acoustics" + keyToModel = self.getconfigsetting(option=keyname, section=section, filepath=ns_options_ini) + + ns_acoustic_ini = join(DNSuserDirectory, 'acoustic.ini') + section = "Base Acoustic" + if not (ns_acoustic_ini and isfile(ns_acoustic_ini)): + self.logger.debug(f'get_user_language: warning: user language cannot be found from Dragon Inifile: "{ns_acoustic_ini}", return "enx"') + return 'enx' + # user_language_long = win32api.GetProfileVal(section, keyToModel, "", ns_acoustic_ini) + user_language_long = self.getconfigsetting(option=keyToModel, section=section, filepath=ns_acoustic_ini) + user_language_long = user_language_long.split("|")[0].strip() + + if user_language_long in UserLanguages: + language = UserLanguages[user_language_long] + self.logger.debug(f'get_user_language, return "{language}", (long language: "{user_language_long}")') + else: + language = 'enx' + self.logger.debug(f'get_user_language, return userLanguage: "{language}", (long language: "{user_language_long}")') + + return language + + def set_user_language(self, args: Any = None): + """can be called from other module to explicitly set the user language to 'enx', 'nld', etc + """ + if not (args and len(args) == 2): + try: + args = natlink.getCurrentUser() + except natlink.NatError: + # when Dragon not running, for testing: + args = () + + if args: + self.user, self.profile = args + self.language = self.get_user_language(self.profile) + self.logger.debug(f'set_user_language, user: "{self.user}", profile: "{self.profile}", language: "{self.language}"') + else: + self.user, self.profile = '', '' + self.logger.warning('set_user_language, cannot get input for get_user_language, set to "enx",\n\tprobably Dragon is not running') + self.language = 'enx' + def start(self) -> None: self.logger.info(f'starting natlink loader from config file:\n\t"{self.config.config_path}"') natlink.active_loader = self + if not self.config.directories: + self.logger.warning(f'Starting Natlink, but no directories to load are specified.\n\tPlease add one or more directories\n\tin config file: "{self.config.config_path}".') + return + # self.logger.debug(f'directories: {self.config.directories}') self._add_dirs_to_path(self.config.directories) if self.config.load_on_startup: + # set language property: + self.set_user_language() self.trigger_load() natlink.setBeginCallback(self.on_begin_callback) natlink.setChangeCallback(self.on_change_callback) @@ -243,20 +482,81 @@ def setup_logger(self) -> None: self.logger.setLevel(log_level.value) self.logger.debug(f'set log level to: {log_level.name}') + def getconfigsetting(self, section: str, option: Any = None, filepath: Any = None, func: Any = None) -> str: + """get a setting from possibly an inifile other than natlink.ini + + Take a string as input, which is obtained from readwritefile.py, handling + different encodings and possible BOM marks. + + When no "option" is passed, the contents of the section are returned (a list of options) + + func can be configparser.getint or configparser.getboolean if needed, otherwise configparser.get (str) is taken. + pass: func='getboolean' or func='getint'. + """ + isfile = os.path.isfile + filepath = filepath or config_locations()[0] + if not isfile(filepath): + raise OSError(f'getconfigsetting, no valid filepath: "{filepath}"') + rwfile = ReadWriteFile() + self.config_text = rwfile.readAnything(filepath) + Config = configparser.ConfigParser() + Config.read_string(self.config_text) + + if option is None: + return Config.options(section) + + if isinstance(func, str): + func = getattr(Config, func) + else: + func = func or Config.get + + if func.__name__ == 'get': + fallback = '' + elif func.__name__ == 'getint': + fallback = 0 + elif func.__name__ == 'getboolean': + fallback = False + else: + raise TypeError(f'getconfigsetting, no fallback for "{func.__name__}"') + + func = func or Config.get + return func(section=section, option=option, fallback=fallback) + def get_natlink_system_config_filename() -> str: + return get_config_info_from_registry('installPath') + +def get_config_info_from_registry(key_name: str) -> str: hive, key, flags = (winreg.HKEY_LOCAL_MACHINE, r'Software\Natlink', winreg.KEY_WOW64_32KEY) with winreg.OpenKeyEx(hive, key, access=winreg.KEY_READ | flags) as natlink_key: - core_path, _ = winreg.QueryValueEx(natlink_key, "installPath") - return core_path + result, _ = winreg.QueryValueEx(natlink_key, key_name) + return result def config_locations() -> Iterable[str]: - join, expanduser, getenv = os.path.join, os.path.expanduser, os.getenv + """give two possible locations, the wanted and the "fallback" location + + wanted: in the '.natlink' subdirectory of `home` or in "NATLINK_USERDIR". + name is always 'natlink.ini' + + the fallback location is in the installed files, and provides the frame for the config file. + with the configurenatlink (natlinkconfigfunction.py or configfurenatlink.pyw) the fallback version + of the config file is copied into the wanted location. + """ + join, expanduser, getenv, isfile = os.path.join, os.path.expanduser, os.getenv, os.path.isfile home = expanduser('~') - possible_dirs = [join(home, '.natlink'), join(home, 'Documents', '.natlink'), - join(home, 'Documents'), home, - join(get_natlink_system_config_filename(), "InstallTest")] - return ([getenv("NATLINK_INI")] if getenv('NATLINK_INI') else - [join(loc, NATLINK_INI) for loc in possible_dirs]) + config_sub_dir = '.natlink' + natlink_inifile = 'natlink.ini' + fallback_config_file = join(get_natlink_system_config_filename(), "DefaultConfig", natlink_inifile) + if not isfile(fallback_config_file): + raise OSError(f'fallback_config_file does not exist: "{fallback_config_file}"') + # try NATLINKUSERDIR setting: + natlink_userdir_from_env = getenv("NATLINK_USERDIR") + if natlink_userdir_from_env: + nl_user_dir = expand_path(natlink_userdir_from_env) + nl_user_file = join(nl_user_dir, natlink_inifile) + return [nl_user_file, fallback_config_file] + + # choose between .natlink/natlink.ini in home or the fallback_directory: + return [join(home, config_sub_dir, natlink_inifile), fallback_config_file] def run() -> None: logger = logging.getLogger('natlink') @@ -275,4 +575,9 @@ def run() -> None: print(f'Exception: "{exc}" in loader.run', file=sys.stderr) print(traceback.format_exc()) raise Exception from exc + +if __name__ == "__main__": + natlink.natConnect() + run() + natlink.natDisconnect() \ No newline at end of file diff --git a/NatlinkModule/natlinkpydebug.py b/NatlinkModule/natlinkpydebug.py new file mode 100644 index 00000000..40ab85c7 --- /dev/null +++ b/NatlinkModule/natlinkpydebug.py @@ -0,0 +1,86 @@ +""" +code to help with debugging including: +- enable python debuggers to attach. +currently on DAP debuggers are supported. +https://microsoft.github.io/debug-adapter-protocol/ +There are several, Microsoft Visual Studio COde is known to work. +There are several, Microsoft Visual Studio COde is known to work. + +If you know how to add support for another debugger please add it. + +Written by Doug Ransom, 2021 +""" +#pylint:disable=C0116, W0703 +import os +import debugpy +import natlinkstatus + +__status = natlinkstatus.NatlinkStatus() +__natLinkPythonDebugPortEnviornmentVar= "NatlinkPyDebugPort" +__natLinkPythonDebugOnStartupVar="NatlinkPyDebugStartup" + +__pyDefaultPythonExecutor = "python.exe" +__debug_started=False +default_debugpy_port=7474 +__debugpy_debug_port=default_debugpy_port +__debugger="not configured" +dap="DAP" + +#bring a couple functions from DAP and export from our namespace +dap_is_client_connected=debugpy.is_client_connected +dap_breakpoint = debugpy.breakpoint + +def dap_info(): + return f""" +Debugger: {__debugger} DAP Port:{__debugpy_debug_port} IsClientConnected: {dap_is_client_connected()} Default DAP Port {default_debugpy_port} +Debug Started:{__debug_started} +""" + +def start_dap(): + #pylint:disable=W0603 + global __debug_started,__debugpy_debug_port,__debugger + if __debug_started: + print(f"DAP already started with debugpy for port {__debugpy_debug_port}") + return + try: + + if __natLinkPythonDebugPortEnviornmentVar in os.environ: + natLinkPythonPortStringVal = os.environ[__natLinkPythonDebugPortEnviornmentVar] + __debugpy_debug_port = int(natLinkPythonPortStringVal) + print(f"Starting debugpy on port {natLinkPythonPortStringVal}") + + python_exec = __pyDefaultPythonExecutor #for now, only the python in system path can be used for natlink and this module + print(f"Python Executable (required for debugging): '{python_exec}'") + debugpy.configure(python=f"{python_exec}") + debugpy.listen(__debugpy_debug_port) + print(f"debugpy listening on port {__debugpy_debug_port}") + __debug_started = True + __debugger = dap + + if __natLinkPythonDebugOnStartupVar in os.environ: + dos_str=os.environ[__natLinkPythonDebugOnStartupVar] + dos=len(dos_str)==1 and dos_str in "YyTt" + + if dos: + print(f"Waiting for DAP debugger to attach now as {__natLinkPythonDebugOnStartupVar} is set to {dos_str}") + debugpy.wait_for_client() + + + except Exception as ee: + print(f""" + Exception {ee} while starting debug. Possible cause is incorrect python executable specified {python_exec} +""" ) + +def debug_check_on_startup(): + #pylint:disable=W0603 + global __debug_started,__debugpy_debug_port,__debugger + debug_instructions = f"{__status.getCoreDirectory()}\\debugging python instructions.docx" + print(f"Instructions for attaching a python debugger are in {debug_instructions} ") + if __natLinkPythonDebugPortEnviornmentVar in os.environ: + start_dap() + + + + + + diff --git a/NatlinkModule/natlinkstatus.py b/NatlinkModule/natlinkstatus.py new file mode 100644 index 00000000..22ac7607 --- /dev/null +++ b/NatlinkModule/natlinkstatus.py @@ -0,0 +1,741 @@ +# +# natlinkstatus.py +# This module gives the status of Natlink to natlinkmain +# +# (C) Copyright Quintijn Hoogenboom, February 2008/January 2018/extended for python3, Natlink5.0.1 Febr 2022 +# +#pylint:disable=C0302, C0116, R0201, R0902, R0904, R0912, W0107, E1101 +"""The following functions are provided in this module: + +The functions below are put into the class NatlinkStatus. + +The functions below should not change anything in settings, only get information. + +getDNSInstallDir: + removed, not needed any more + +getDNSIniDir: + returns the directory where the NatSpeak INI files are located, + notably nssystem.ini and nsapps.ini. Got from loader. + +getDNSVersion: + returns the in the version number of NatSpeak, as an integer. So ..., 13, 15, ... + no distinction is made here between different subversions. + got indirectly from loader + +getWindowsVersion: + see source below + +get_language: + returns the 3 letter code of the language of the speech profile that + is open: 'enx', 'nld', "fra", "deu", "ita", "esp" + + get it from loader (property), is updated when user profile changes (on_change_callback) + returns 'enx' when Dragon is not running. + +get_profile, get_user: + returns the directory of the current user profile information and + returns the name of the currenct user + This information is collected from natlink.getCurrentUser(), or from + the args in on_change_callback, with type == 'user' + +get_load_on_begin_utterance and set_load_on_begin_utterance: + returns value of this property of the natlinkmain (loader) instance. + True or False, or a (small) positive int, decreasing each utterance. + + or + explicitly set this property. + +getPythonVersion: + return two character version, so without the dot! eg '38', + + Note, no indication of 32 bit version, so no '38-32' + + +getUserDirectory: get the Natlink user directory, + Especially Dragonfly users will use this directory for putting their grammar files in. + Also users that have their own custom grammar files can use this user directory + +getUnimacroDirectory: get the directory where the Unimacro system is. + When git cloned, relative to the Core directory, otherwise somewhere or in the site-packages (if pipped). This grammar will (and should) hold the _control.py grammar + and needs to be included in the load directories list of James' natlinkmain + +getUnimacroGrammarsDirectory: get the directory, where the user can put his Unimacro grammars. By default + this will be the ActiveGrammars subdirectory of the UnimacroUserDirectory. + +getUnimacroUserDirectory: get the directory of Unimacro INI files, if not return '' or + the Unimacro user directory + +getVocolaDirectory: get the directory where the Vocola system is. When cloned from git, in Vocola, relative to + the Core directory. Otherwise (when pipped) in some site-packages directory. It holds (and should hold) the + grammar _vocola_main.py. + +getVocolaUserDirectory: get the directory of Vocola User files, if not return '' + (if run from natlinkconfigfunctions use getVocolaDirectoryFromIni, which checks inifile + at each call...) + +getVocolaGrammarsDirectory: get the directory, where the compiled Vocola grammars are/will be. + This will normally be the "CompiledGrammars" subdirectory of the VocolaUserDirectory. + +NatlinkIsEnabled: + return 1 or 0 whether Natlink is enabled or not + returns None when strange values are found + (checked with the INI file settings of NSSystemIni and NSAppsIni) + +getVocolaTakesLanguages: additional settings for Vocola + +new 2014/2022 +getDNSName: return "NatSpeak" for versions <= 11 and "Dragon" for 12 (on) (obsolete in 2022) +getAhkExeDir: return the directory where AutoHotkey is found (only needed when not in default) +getAhkUserDir: return User Directory of AutoHotkey, not needed when it is in default. +get_language and other properties, see above. + +""" +import os +import sys +import stat +import platform +import logging +from typing import Any +try: + from natlink import loader +except ModuleNotFoundError: + print('Natlink is not enabled, module natlink and/or natlink.loader cannot be found\n\texit natlinkstatus.py...') + sys.exit() +from natlink import config +from natlink import singleton +import natlink + +## setup a natlinkmain instance, for getting properties from the loader: +## note, when loading the natlink module via Dragon, you can call simply: +# # # natlinkmain = loader.NatlinkMain() + +## setting up Logger and Config is needed, when running this for test: +Logger = logging.getLogger('natlink') +Config = config.NatlinkConfig.from_first_found_file(loader.config_locations()) +natlinkmain = loader.NatlinkMain(Logger, Config) + +# the possible languages (for get_language), now in loader + +shiftKeyDict = {"nld": "Shift", + "enx": 'shift', + "fra": "maj", + "deu": "umschalt", + "ita": "maiusc", + "esp": "may\xfas"} + +thisDir, thisFile = os.path.split(__file__) + +class NatlinkStatus(metaclass=singleton.Singleton): + """this class holds the Natlink status functions. + + This class is a Singleton, which means that all instances are the same object. + + Some information is retrieved from the loader, the natlinkmain (Singleton) instance. + + In natlinkconfigfunctions.py, NatlinkStatus is subclassed for configuration purposes. + in the PyTest folder there are/come test functions in TestNatlinkStatus + + """ + known_directory_options = ['userdirectory', 'dragonflyuserdirectory', + 'unimacrodirectory', 'unimacrogrammarsdirectory', + 'vocoladirectory', 'vocolagrammarsdirectory'] + + def __init__(self): + """initialise all instance variables, in this singleton class, hoeinstance + """ + self.natlinkmain = natlinkmain # global + self.DNSVersion = None + self.DNSIniDir = None + self.CoreDirectory = None + self.NatlinkDirectory = None + self.UserDirectory = None + ## Unimacro: + self.UnimacroDirectory = None + self.UnimacroUserDirectory = None + self.UnimacroGrammarsDirectory = None + ## Vocola: + self.VocolaUserDirectory = None + self.VocolaDirectory = None + self.VocolaGrammarsDirectory = None + ## AutoHotkey: + self.AhkUserDir = None + self.AhkExeDir = None + + if self.CoreDirectory is None: + self.CoreDirectory = thisDir + + @staticmethod + def getWindowsVersion(): + """extract the windows version + + return 1 of the predefined values above, or just return what the system + call returns + """ + wVersion = platform.platform() + if '-' in wVersion: + return wVersion.split('-')[1] + print('Warning, probably cannot find correct Windows Version... (%s)'% wVersion) + return wVersion + + def getPythonVersion(self): + """get the version of python + + Check if the version is supported on the "lower" side. + + length 2, without ".", so "38" etc. + """ + version = sys.version[:3] + version = version.replace(".", "") + return version + + @property + def user(self) -> str: + return self.natlinkmain.user + @property + def profile(self) -> str: + return self.natlinkmain.profile + @property + def language(self) -> str: + return self.natlinkmain.language + + @property + def load_on_begin_utterance(self) -> Any: + """inspect current value of this loader setting + """ + return self.natlinkmain.load_on_begin_utterance + + def get_user(self): + return self.user + + def get_profile(self): + return self.profile + + def get_language(self): + return self.language + + def get_load_on_begin_utterance(self): + return self.load_on_begin_utterance + + def getDNSIniDir(self): + """get the path (one above the users profile paths) where the INI files + should be located + + """ + # first try if set (by configure dialog/natlinkinstallfunctions.py) if regkey is set: + if self.DNSIniDir is not None: + return self.DNSIniDir + + self.DNSIniDir = loader.get_config_info_from_registry("dragonIniDir") + return self.DNSIniDir + + + def getDNSVersion(self): + """find the correct DNS version number (as an integer) + + 2022: extract from the dragonIniDir setting in the registry, via loader function + + """ + if self.DNSVersion is not None: + return self.DNSVersion + dragonIniDir = loader.get_config_info_from_registry("dragonIniDir") + if dragonIniDir: + try: + version = int(dragonIniDir[-2:]) + except ValueError: + print('getDNSVersion, invalid version found "{dragonIniDir[-2:]}", return 0') + version = 0 + else: + print(f'Error, cannot get dragonIniDir from registry, unknown DNSVersion "{dragonIniDir}", return 0') + version = 0 + self.DNSVersion = version + return self.DNSVersion + + def VocolaIsEnabled(self): + """Return True if Vocola is enables + + To be so, + 1. the VocolaUserDirectory (where the vocola command files (*.vcl) are located) + should be defined in the user config file + 2. the VocolaDirectory should be found, and hold '_vocola_main.py' + + """ + isdir = os.path.isdir + vocUserDir = self.getVocolaUserDirectory() + if vocUserDir and isdir(vocUserDir): + vocDir = self.getVocolaDirectory() + vocGrammarsDir = self.getVocolaGrammarsDirectory() + if vocDir and isdir(vocDir) and vocGrammarsDir and isdir(vocGrammarsDir): + return True + return False + + + def UnimacroIsEnabled(self): + """UnimacroIsEnabled: see if UnimacroDirectory and UnimacroUserDirectory are there + + _control.py should be in the UnimacroDirectory. + """ + isdir = os.path.isdir + uDir = self.getUnimacroDirectory() + if not uDir: + # print('no valid UnimacroDirectory, Unimacro is disabled') + return False + if isdir(uDir): + files = os.listdir(uDir) + if not '_control.py' in files: + print(f'UnimacroDirectory is present ({uDir}), but not "_control.py" grammar file') + return False # _control.py should be in Unimacro directory + + uuDir = self.getUnimacroUserDirectory() + if not uuDir: + return False + ugDir = self.getUnimacroGrammarsDirectory() + if not (ugDir and isdir(ugDir)): + print(f'UnimacroGrammarsDirectory ({ugDir}) not present, please create') + return False + return True + + + def UserIsEnabled(self): + userDir = self.getUserDirectory() + if userDir: + return True + return False + + def getUnimacroUserDirectory(self): + isdir, normpath = os.path.isdir, os.path.normpath + if self.UnimacroUserDirectory is not None: + return self.UnimacroUserDirectory + key = 'unimacrouserdirectory' + value = self.natlinkmain.getconfigsetting(section="unimacro", option=key) + if value and isdir(value): + self.UnimacroUserDirectory = value + return normpath(value) + if value: + expanded = loader.expand_path(value) + if expanded and isdir(expanded): + self.UnimacroUserDirectory = expanded + return normpath(expanded) + print(f'invalid directory for "{key}": "{value}"') + self.UnimacroUserDirectory = '' + return '' + + + def getUnimacroDirectory(self): + """return the path to the UnimacroDirectory + + This is the directory where the _control.py grammar is, and + is normally got via `pip install unimacro` + + """ + # When git cloned, relative to the Core directory, otherwise somewhere or in the site-packages (if pipped). + join, isdir, isfile, normpath = os.path.join, os.path.isdir, os.path.isfile, os.path.normpath + if self.UnimacroDirectory is not None: + return self.UnimacroDirectory + uDir = join(sys.prefix, "lib", "site-packages", "unimacro") + if isdir(uDir): + uFile = "_control.py" + controlGrammar = join(uDir, uFile) + if isfile(controlGrammar): + self.UnimacroDirectory = normpath(uDir) + return self.UnimacroDirectory + # print(f'UnimacroDirectory found: "{uDir}", but no valid file: "{uFile}", return ""') + return "" + # print('UnimacroDirectory not found in "lib/site-packages/unimacro", return ""') + self.UnimacroDirectory = "" + return "" + + + def getUnimacroGrammarsDirectory(self): + """return the path to the directory where the ActiveGrammars of Unimacro are located. + + Expected in "ActiveGrammars" of the UnimacroUserDirectory + (August 2020) + + """ + isdir, join, normpath, listdir = os.path.isdir, os.path.join, os.path.normpath, os.listdir + if self.UnimacroGrammarsDirectory is not None: + return self.UnimacroGrammarsDirectory + + uDir = self.getUnimacroDirectory() + if not uDir: + self.UnimacroGrammarsDirectory = '' + return '' + + uuDir = self.getUnimacroUserDirectory() + if uuDir and isdir(uuDir): + ugDir = join(uuDir, "ActiveGrammars") + if not isdir(ugDir): + os.mkdir(ugDir) + if isdir(ugDir): + ugFiles = [f for f in listdir(ugDir) if f.endswith(".py")] + if not ugFiles: + print(f"UnimacroGrammarsDirectory: {ugDir} has no pythonthon grammar files (yet), please populate this directory with the Unimacro grammars you wish to use, and then toggle your microphone") + + try: + del self.UnimacroGrammarsDirectory + except AttributeError: + pass + self.UnimacroGrammarsDirectory= normpath(ugDir) + return self.UnimacroGrammarsDirectory + + try: + del self.UnimacroGrammarsDirectory + except AttributeError: + pass + self.UnimacroGrammarsDirectory= "" # meaning is not set, for future calls. + return self.UnimacroGrammarsDirectory + + + def getCoreDirectory(self): + """return the path of the coreDirectory, MacroSystem/core + """ + return self.CoreDirectory + + + def getNatlinkDirectory(self): + """return the path of the NatlinkDirectory, two above the coreDirectory + """ + return self.NatlinkDirectory + + + def getUserDirectory(self): + """return the path to the Natlink User directory + + this one is not any more for Unimacro, but for User specified grammars, also Dragonfly + + should be set in configurenatlink, otherwise ignore... + """ + isdir, normpath = os.path.isdir, os.path.normpath + if not self.UserDirectory is None: + return self.UserDirectory + key = 'UserDirectory' + value = self.natlinkmain.getconfigsetting(section='directories', option=key) + if value and isdir(value): + self.UserDirectory = normpath(value) + return self.UserDirectory + expanded = config.expand_path(value) + if expanded and isdir(expanded): + self.UserDirectory = normpath(expanded) + return self.UserDirectory + + print('invalid path for UserDirectory: "%s"'% value) + self.UserDirectory = '' + return '' + + + def getVocolaUserDirectory(self): + + isdir, normpath = os.path.isdir, os.path.normpath + if self.VocolaUserDirectory is not None: + return self.VocolaUserDirectory + key = 'vocolauserdirectory' + section = 'vocola' + value = self.natlinkmain.getconfigsetting(section=section, option=key) + if value and isdir(value): + self.VocolaUserDirectory = normpath(value) + return value + expanded = config.expand_path(value) + if expanded and isdir(expanded): + self.VocolaUserDirectory = normpath(expanded) + return self.VocolaUserDirectory + + print(f'invalid path for VocolaUserDirectory: "{value}"') + self.VocolaUserDirectory = '' + return '' + + def getVocolaDirectory(self): + isdir, isfile, join, normpath = os.path.isdir, os.path.isfile, os.path.join, os.path.normpath + if self.VocolaDirectory is not None: + return self.VocolaDirectory + + ## try in site-packages: + vocDir = join(sys.prefix, "lib", "site-packages", "vocola2") + if not isdir(vocDir): + # print('VocolaDirectory not found in "lib/site-packages/vocola2", return ""') + self.VocolaDirectory = '' + return '' + vocFile = "_vocola_main.py" + checkGrammar = join(vocDir, vocFile) + if not isfile(checkGrammar): + print(f'VocolaDirectory found in "{vocDir}", but no file "{vocFile}" found, return ""') + self.VocolaDirectory = '' + return '' + + self.VocolaDirectory = normpath(vocDir) + return self.VocolaDirectory + + + def getVocolaGrammarsDirectory(self): + """return the VocolaGrammarsDirectory, but only if Vocola is enabled + + If so, the subdirectory CompiledGrammars is created if not there yet. + + The path of this "CompiledGrammars" directory is returned. + + If Vocola is not enabled, or anything goes wrong, return "" + + """ + join, normpath = os.path.join, os.path.normpath + if self.VocolaGrammarsDirectory is not None: + return self.VocolaGrammarsDirectory + + vUserDir = self.getVocolaUserDirectory() + if not vUserDir: + self.VocolaGrammarsDirectory = '' + return '' + + vgDir = join(vUserDir, 'VocolaGrammars') + self.VocolaGrammarsDirectory = normpath(vgDir) + return self.VocolaGrammarsDirectory + + + def getAhkUserDir(self): + if not self.AhkUserDir is None: + return self.AhkUserDir + return self.getAhkUserDirFromIni() + + + def getAhkUserDirFromIni(self): + isdir, normpath = os.path.isdir, os.path.normpath + key = 'AhkUserDir' + value = self.natlinkmain.getconfigsetting(section='autohotkey', option=key) + if value and isdir(value): + self.AhkUserDir = normpath(value) + return value + expanded = config.expand_path(value) + if expanded and isdir(expanded): + self.AhkUserDir= normpath(expanded) + return self.AhkUserDir + + print(f'invalid path for AhkUserDir: "{value}", return ""') + self.AhkUserDir = '' + return '' + + + def getAhkExeDir(self): + if not self.AhkExeDir is None: + return self.AhkExeDir + return self.getAhkExeDirFromIni() + + + def getAhkExeDirFromIni(self): + isdir, normpath = os.path.isdir, os.path.normpath + key = 'AhkExeDir' + value = self.natlinkmain.getconfigsetting(section='autohotkey', option=key) + if value and isdir(value): + self.AhkExeDir = normpath(value) + return value + expanded = config.expand_path(value) + if expanded and isdir(expanded): + self.AhkExeDir = normpath(expanded) + return self.AhkExeDir + + print(f'invalid path for AhkExeDir: "{value}", return ""') + self.AhkExeDir = '' + return '' + + def getExtraGrammarDirectories(self): + """record grammar directories that are unknown to natlinkstatus and natlinkconfigfunctions + + These directories can be entered "manually" in the `natlink.ini` file + """ + result = self.natlinkmain.getconfigsetting(section='directories') + return [s for s in result if s not in self.known_directory_options] + + def getUnimacroIniFilesEditor(self): + key = 'UnimacroIniFilesEditor' + value = self.natlinkmain.getconfigsetting(section='unimacro', option=key) + if not value: + value = 'notepad' + if self.UnimacroIsEnabled(): + return value + return '' + + def getShiftKey(self): + """return the shiftkey, for setting in natlinkmain when user language changes. + + used for self.playString in natlinkutils, for the dropping character bug. (dec 2015, QH). + """ + ## TODO: must be windows language... + windowsLanguage = 'enx' ### ??? TODO QH + try: + return "{%s}"% shiftKeyDict[windowsLanguage] + except KeyError: + print(f'no shiftKey code provided for language: "{windowsLanguage}", take empty string.') + return "" + + # get additional options Vocola + + def getVocolaTakesLanguages(self): + """gets and value for distinction of different languages in Vocola + If Vocola is not enabled, this option will also return False + """ + key = 'vocola_takes_languages' + return self.natlinkmain.getconfigsetting(section="vocola", option=key, func='getboolean') + + def getVocolaTakesUnimacroActions(self): + """gets and value for optional Vocola takes Unimacro actions + If Vocola is not enabled, this option will also return False + """ + key = 'VocolaTakesUnimacroActions' + return self.natlinkmain.getconfigsetting(section="vocola", option=key, func='getboolean') + + + def getInstallVersion(self): + version = loader.get_config_info_from_registry("version") + return version + + @staticmethod + def getDNSName(): + """return NatSpeak for versions <= 11, and Dragon for versions >= 12 + """ + return "Dragon" + + + def getNatlinkStatusDict(self): + """return actual status in a dict + + Most values come via properties... + + """ + D = {} + # properties: + D['user'] = self.get_user() + D['profile'] = self.get_profile() + D['language'] = self.get_language() + D['load_on_begin_utterance'] = self.get_load_on_begin_utterance() + + for key in ['DNSIniDir', 'WindowsVersion', 'DNSVersion', + 'PythonVersion', + 'DNSName', + 'UnimacroDirectory', 'UnimacroUserDirectory', 'UnimacroGrammarsDirectory', + 'VocolaDirectory', 'VocolaUserDirectory', 'VocolaGrammarsDirectory', + 'VocolaTakesLanguages', 'VocolaTakesUnimacroActions', + 'UnimacroIniFilesEditor', + 'ExtraGrammarDirectories', + 'InstallVersion', + # 'IncludeUnimacroInPythonPath', + 'AhkExeDir', 'AhkUserDir']: +## 'BaseTopic', 'BaseModel']: + func_name = f'get{key[0].upper() + key[1:]}' + func = getattr(self, func_name, None) + if func: + D[key] = func() + else: + print(f'no valid function for getting key: "{key}" ("{func_name}")') + + D['CoreDirectory'] = self.CoreDirectory + D['UserDirectory'] = self.getUserDirectory() + D['vocolaIsEnabled'] = self.VocolaIsEnabled() + + D['unimacroIsEnabled'] = self.UnimacroIsEnabled() + D['userIsEnabled'] = self.UserIsEnabled() + # extra for information purposes: + D['NatlinkDirectory'] = self.NatlinkDirectory + return D + + + def getNatlinkStatusString(self): + L = [] + D = self.getNatlinkStatusDict() + L.append('--- properties:') + self.appendAndRemove(L, D, 'user') + self.appendAndRemove(L, D, 'profile') + self.appendAndRemove(L, D, 'language') + self.appendAndRemove(L, D, 'load_on_begin_utterance') + + # Natlink:: + L.append('') + key = 'CoreDirectory' + self.appendAndRemove(L, D, key) + key = 'InstallVersion' + self.appendAndRemove(L, D, key) + + ## Vocola:: + if D['vocolaIsEnabled']: + self.appendAndRemove(L, D, 'vocolaIsEnabled', "---Vocola is enabled") + for key in ('VocolaUserDirectory', 'VocolaDirectory', + 'VocolaGrammarsDirectory', 'VocolaTakesLanguages', + 'VocolaTakesUnimacroActions'): + self.appendAndRemove(L, D, key) + else: + self.appendAndRemove(L, D, 'vocolaIsEnabled', "---Vocola is disabled") + for key in ('VocolaUserDirectory', 'VocolaDirectory', + 'VocolaGrammarsDirectory', 'VocolaTakesLanguages', + 'VocolaTakesUnimacroActions'): + del D[key] + + ## Unimacro: + if D['unimacroIsEnabled']: + self.appendAndRemove(L, D, 'unimacroIsEnabled', "---Unimacro is enabled") + for key in ('UnimacroUserDirectory', 'UnimacroDirectory', 'UnimacroGrammarsDirectory'): + self.appendAndRemove(L, D, key) + for key in ('UnimacroIniFilesEditor',): + self.appendAndRemove(L, D, key) + else: + self.appendAndRemove(L, D, 'unimacroIsEnabled', "---Unimacro is disabled") + for key in ('UnimacroUserDirectory', 'UnimacroIniFilesEditor', + 'UnimacroDirectory', 'UnimacroGrammarsDirectory'): + del D[key] + ## UserDirectory: + if D['userIsEnabled']: + self.appendAndRemove(L, D, 'userIsEnabled', "---User defined grammars are enabled") + for key in ('UserDirectory',): + self.appendAndRemove(L, D, key) + else: + self.appendAndRemove(L, D, 'userIsEnabled', "---User defined grammars are disabled") + del D['UserDirectory'] + + ## remaining Natlink options: + L.append('other Natlink info:') + + # system: + L.append('system information:') + for key in ['DNSIniDir', 'DNSVersion', 'DNSName', + 'WindowsVersion', 'PythonVersion']: + self.appendAndRemove(L, D, key) + + # forgotten??? + if D: + L.append('remaining information:') + for key in list(D.keys()): + self.appendAndRemove(L, D, key) + + return '\n'.join(L) + + + def appendAndRemove(self, List, Dict, Key, text=None): + if text: + List.append(text) + else: + value = Dict[Key] + if value is None or value == '': + value = '-' + if len(Key) <= 6: + List.append(f'\t{Key}\t\t\t{value}') + elif len(Key) <= 13: + List.append(f'\t{Key}\t\t{value}') + else: + List.append(f'\t{Key}\t{value}') + del Dict[Key] + +def getFileDate(modName): + #pylint:disable=C0321 + try: return os.stat(modName)[stat.ST_MTIME] + except OSError: return 0 # file not found + +def main(): + status = NatlinkStatus() + + Lang = status.get_language() + print(f'language: "{Lang}"') + print(status.getNatlinkStatusString()) + print(f'load_on_begin_utterance: {status.get_load_on_begin_utterance()}') + dns_version = status.getDNSVersion() + print(f'DNSVersion: {dns_version} (type: {type(dns_version)})') + +if __name__ == "__main__": + natlink.natConnect() + main() + natlink.natDisconnect() diff --git a/NatlinkModule/natlinkutils.py b/NatlinkModule/natlinkutils.py index 34ee0f77..90357483 100644 --- a/NatlinkModule/natlinkutils.py +++ b/NatlinkModule/natlinkutils.py @@ -39,7 +39,7 @@ use sendkeys from dragonfly via dtactions """ -#pylint:disable=C0116, C0302, R0902, W0702, E1101 +#pylint:disable=C0116, C0302, R0902, W0702, E1101, W0703 import os import os.path @@ -348,17 +348,16 @@ def load(self, gramSpec, allResults=0, hypothesis=0, grammarName=None): self.gramObj.setBeginCallback(self.beginCallback) self.gramObj.setResultsCallback(self.resultsCallback) self.gramObj.setHypothesisCallback(self.hypothesisCallback) - except: - print("GramClassBase.load(), Error at setting Callback functions ", sys.exc_info()) - traceback.print_exc() - raise + except Exception as exc: + print("GramClassBase.load, Error at setting Callback functions") + raise Exception from exc try: self.gramObj.load(gramSpec, allResults, hypothesis) - except: - print("GramClassBase.load(), Error at loading the grammar ", sys.exc_info()) - traceback.print_exc() - raise - + except natlink.NatError as exc: + print(f'GramClassBase.load, Error at loading the grammar: {exc}') + raise natlink.BadGrammar from exc + + def unload(self): self.gramObj.unload() self.gramObj.setBeginCallback(None) @@ -678,6 +677,7 @@ class GrammarBase(GramClassBase): def __init__(self): GramClassBase.__init__(self) + self.is_loaded = False self.exclusiveState = 0 self.activeRules = {} self.validRules = [] @@ -695,7 +695,7 @@ def load(self, gramSpec, allResults=0, hypothesis=0, grammarName=None): try: # print 'loading grammar %s, gramspec type: %s'% (grammarName, type(gramSpec)) # code upper ascii characters with latin1 if they were in the process entered as unicode - if not type(gramSpec) in (str, list): + if not isinstance(gramSpec, (str, list)): raise TypeError( "grammar definition of %s must be a string or a list of strings, not %s"% (grammarName, type(gramSpec))) parser = gramparser.GramParser(gramSpec, grammarName=grammarName) @@ -707,8 +707,8 @@ def load(self, gramSpec, allResults=0, hypothesis=0, grammarName=None): try: GramClassBase.load(self,gramBin,allResults,hypothesis) except natlink.BadGrammar: - print('GrammarBase, cannot load grammar, BadGrammar:\n%s\n'% gramSpec) - raise + self.is_loaded = False + return self.is_loaded # we want to keep a list of the rules which can be activated and the # known lists so we can catch errors earlier self.validRules = list(parser.exportRules.keys()) @@ -717,17 +717,21 @@ def load(self, gramSpec, allResults=0, hypothesis=0, grammarName=None): # to rule names during recognition for ruleNum, knownRule in parser.knownRules.items(): self.ruleMap[ knownRule ] = ruleNum - return 1 + self.is_loaded = True except: - print("Unexpected error load GramClassBase:", sys.exc_info()) + print("Unexpected error loading grammar:", sys.exc_info()) print(traceback.print_exc()) + self.is_loaded = False + return self.is_loaded # these are wrappers for the GramObj base methods. We also keep track of # legal rules, lists and active rules so we can do some first level error # checking def unload(self): + if not self.is_loaded: + return GramClassBase.unload(self) self.activeRules.clear() while self.validRules: @@ -735,6 +739,7 @@ def unload(self): while self.validLists: self.validLists.pop() self.exclusiveState = 0 + self.is_loaded = False def activate(self, ruleName, window=0, exclusive=None, noError=0): #pylint:disable=W0221, W0613 diff --git a/NatlinkModule/nsformat.py b/NatlinkModule/nsformat.py new file mode 100644 index 00000000..d3354796 --- /dev/null +++ b/NatlinkModule/nsformat.py @@ -0,0 +1,489 @@ +"""Python Macro Language for Dragon NaturallySpeaking + (c) Copyright 1999 by Joel Gould + Portions (c) Copyright 1999 by Dragon Systems, Inc. + + This code simulates the basic text formatting from NatSpeak. + + code written by Joel Gould, posted on the natpython discussion list on Wed, 28 Aug 2002 + + removed pre 11 things, + now for python3 version, with (normally) DNSVersion 15 (QH, June 2020)/Febr 2022 +""" +#pylint:disable=C0116, C0123, R0911, R0912, R0915, R0916 +import copy +import natlink + +flag_useradded = 0 +flag_varadded = 1 +flag_custompron = 2 +flag_nodelete = 3 +flag_passive_cap_next = 4 +flag_active_cap_next = 5 +flag_uppercase_next = 6 +flag_lowercase_next = 7 +flag_no_space_next = 8 +flag_two_spaces_next = 9 +flag_cond_no_space = 10 +flag_cap_all = 11 +flag_uppercase_all = 12 +flag_lowercase_all = 13 +flag_no_space_all = 14 +flag_reset_no_space = 15 +flag_swallow_period = 16 +flag_is_period = 17 +flag_no_formatting = 18 +flag_no_space_change = 19 +flag_no_cap_change = 20 +flag_no_space_before = 21 +flag_reset_uc_lc_caps = 22 +flag_new_line = 23 +flag_new_paragraph = 24 +flag_title_mode = 25 +flag_beginning_title_mode = 26 +flag_space_bar = 27 +flag_not_in_dictation = 28 +flag_guessedpron = 29 +flag_topicadded = 30 + +flagNames = {} +name = '' +for name in globals(): + if name.startswith('flag_') and isinstance(globals()[name], int) and 0 < globals()[name] < 32: + flagNames[globals()[name]] = name +# +flags_like_period = (9, 4, 21, 17) # flag_two_spaces_next = 9, flag_passive_cap_next = 4, flag_no_space_before = 21 +flags_like_comma = (21, ) # flag_no_space_before = 21 (flag_nodelete = 3 we just ignore here, so leave out) +flags_like_number = (10,) +flags_like_point = (8, 10, 21) # no spacing (combination with numbers seems + # obsolete (cond_no_space = 10) +flags_like_hyphen = (8, 21) # no spacing before and after +flags_like_open_quote = (8, 20) # no space next and no cap change +flags_like_close_quote = (21, 20, 19) # no space before, no cap change and no space change (??) + +# word flags from properties part of the word: +# Dragon 11... +propDict = {} +propDict['space-bar'] = (flag_space_bar, flag_no_space_next, flag_no_formatting, + flag_no_cap_change, flag_no_space_before) # (8, 18, 20, 21, 27) + +propDict['period'] = flags_like_period +propDict['point'] = flags_like_point +propDict['dot'] = flags_like_point +propDict['comma'] = flags_like_comma +propDict['cap'] = (19, 18, flag_active_cap_next) +propDict['caps-on'] = (19, 18, flag_cap_all) +propDict['caps-off'] = (19, 18, flag_reset_uc_lc_caps) +propDict['all-caps'] = (19, 18, flag_uppercase_next) +propDict['all-caps-on'] = (19, 18, flag_uppercase_all) +propDict['all-caps-off'] = (19, 18, flag_reset_uc_lc_caps) +propDict['no-caps'] = (19, 18, flag_lowercase_next) +propDict['no-caps-on'] = (19, 18, flag_lowercase_all) +propDict['no-caps-off'] = (19, 18, flag_reset_uc_lc_caps) +propDict['no-space'] = (18, 20, flag_no_space_next) +propDict['no-space-on'] = (18, 20, flag_no_space_all) +propDict['no-space-off'] = (18, 20, flag_reset_no_space) +propDict['left-double-quote'] = flags_like_open_quote +propDict['right-double-quote'] = flags_like_close_quote +# left- as left-double-quote +# right- as right-double-quote + +propDict['question-mark'] = flags_like_period +propDict['exclamation-mark'] = flags_like_period + +propDict['hyphen'] = flags_like_hyphen +propDict['at-sign'] = flags_like_hyphen +propDict['colon'] = flags_like_comma +propDict['semicolon'] = flags_like_comma +propDict['apostrophe-ess'] = flags_like_comma + +propDict['new-line'] = (flag_no_formatting, flag_no_space_next, flag_no_cap_change, flag_new_line) +propDict['new-paragraph'] = (flag_no_formatting, flag_no_space_next, flag_passive_cap_next, flag_new_paragraph) + +# spelling props: +propDict['spelling-cap'] = propDict['cap'] +propDict['letter'] = (flag_no_space_next,) # lowercase is hardcoded in below. +propDict['uppercase-letter'] = (flag_no_space_next,) + +#--------------------------------------------------------------------------- +# This is the main formatting entry point. It takes the old format state and +# a list of words and returns the new formatting state and the formatted +# string. +# +# If you already have the wordInfo for each word, you can pass in a list of +# tuples of (wordName,wordInfo) instead of just the list of words. + +def formatWords(wordList,state=None): + #pylint:disable=W0603 + global flags_like_period + language = 'enx' + if language != 'enx': + flags_like_period = (4, 21, 17) # one space after period. + + # get the getWordsInfo function, now returning a tuple of properties + # DNSVersion = status.getDNSVersion() + gwi = getWordInfo + + output = '' + emptySet = set( () ) + for entry in wordList: + if entry == 'space': + entry = r'\space-bar\space-bar' + if isinstance(entry, tuple): + assert len(entry)==2 + wordName = entry[0] + wordInfo = entry[1] + else: + if entry.find('\\letter\\') > 0: + entry = entry.lower() # letters lowercase... + wordName = entry + wordInfo = gwi(wordName) + if wordInfo is None: + wordInfo = set() + if isinstance(wordInfo, set): + wordInfo = wordInfoToFlags(wordInfo) + + # init state to a set: + if state == 0: + state = set([]) + elif state == -1: + #print "no space next at start" + state = set([flag_no_space_next]) + elif state is None: + state = set([flag_no_space_next, flag_active_cap_next]) + elif type(state) in (list, tuple): + state = set(state) + elif type(state) != type(emptySet): + state = wordInfoToFlags(state) + #print 'formatWords starting with: %s'% state + + newText, state = formatWord(wordName,wordInfo,state) + output = output + newText + + return output, state + +countDict= dict(one=1, two=2, three=3, four=4, five=5, six=6, seven=7, eight=8, nine=9, + een=1, twee=2, drie=3, vier=4, vijf=5, zes=6, zeven=7, acht=8, negen=9) + + +def formatPassword(wordList): + """format the words, no spaces capping each word, getting the numbers and repeating the @ etc +>>> formatPassword(['small', 'bird', 'three', '@']) +'SmallBird3@@@' + + """ + nextRepeat = 0 + outList = [] + for w in wordList: + if nextRepeat: + while nextRepeat: + outList.append(w) + nextRepeat -= 1 + elif w in countDict: + nextRepeat = countDict[w] + outList.append(str(nextRepeat)) + else: + outList.append(w.capitalize()) + return ''.join(outList) + +def formatLetters(wordList): + """this is more tricks, formats dngletters input + + do as input the flag_no_space_all! + return only the resulting string!! + + obsolete with Dragon 11... + """ + inputState = (flag_no_space_all,) + res, _state = formatWords(wordList, inputState) + + return res + +#--------------------------------------------------------------------------- +# This is the formatting subroutine. It handles the formatting for a single +# word using the standard Dragon NaturallySpeaking state machine. +# +# This code was adapted from shared\resobj.cpp +def formatWord(wordName,wordInfo=None,stateFlags=None, gwi=None): + ##adapted: wordInfo and stateFlags are now sets of state flags + emptySet = set() + if gwi is None: + gwi = getWordInfo + #----- + # Preparation + # assume wordInfo is a set already + if type(wordInfo) == type(emptySet): + wordFlags = wordInfo + else: + # should not come here: + wordFlags = gwi(wordName) + + if wordFlags == set(flags_like_open_quote): + pass + + # for faster lookup in Python, we convert the bit arrays am array of + # bits that are set: + # uncomment when more info is wanted: + #print 'wordFlags of |%s| are: %s (%s)'% (wordName, `wordFlags`, `showStateFlags(wordFlags)`) + if isinstance(stateFlags, set): + pass + else: + # for testing only, this function should not be called direct, but this is + # done from the testing routines + state = copy.copy(stateFlags) + if state == 0: + state = set() + elif state == -1: + state = set(flag_no_space_next) + elif state is None: + state = set([flag_no_space_next, flag_active_cap_next]) + elif type(state) in (list, tuple): + state = set(state) + else: + raise ValueError("formatWord, invalid stateFlags: %s"% repr(stateFlags)) + stateFlags = copy.copy(state) + + + # get the written form + if wordName[:2] == '\\\\': + wordName = '\\' + else: + wordName = wordName.split('\\')[0] + + #----- + # Compute the output string + output = '' + + # compute the number of CRLF's + if flag_new_line in wordFlags: + output = output + '\r\n' + elif flag_new_paragraph in wordFlags: + output = output + '\r\n\r\n' + elif flag_space_bar in wordFlags: # fix QH, oct 2011 + output = output + ' ' + + # compute the leading spacing + if ( flag_no_formatting in wordFlags or + flag_no_space_next in stateFlags or + flag_no_space_all in stateFlags or + flag_no_space_before in wordFlags or + flag_cond_no_space in stateFlags and flag_cond_no_space in wordFlags ): + # no leading space + pass + elif flag_two_spaces_next in stateFlags: + output = output + ' ' + else: + output = output + ' ' + + # the no space all flag is used so we can remove the spaces from a phase + # which may have imbeded spaces + + if not flag_no_formatting in wordFlags and flag_no_space_all in stateFlags: + wordName = ''.join(wordName.split()) + + # compute the capitalization by looking at the long term flags; this + # effects all the words in the phrase + + if flag_no_formatting in wordFlags: + # no capitalization change + pass + elif flag_lowercase_all in stateFlags: + wordName = wordName.lower() + elif flag_uppercase_all in stateFlags: + wordName = wordName.upper() + elif flag_cap_all in stateFlags and not flag_title_mode in wordFlags: + words = wordName.split() + words = [w.capitalize() for w in wordName.split()] + wordName = ' '.join(words) + elif flag_passive_cap_next in stateFlags: + wordName = wordName.capitalize() + + # compute the capitalization for the first word in the phrase which + # overrides the long term capitalization state + + if flag_no_formatting in wordFlags: + # no capitalization change + pass + elif flag_lowercase_next in stateFlags: + words = wordName.split() + words[0] = words[0].lower() + wordName= ' '.join(words) + elif flag_uppercase_next in stateFlags: + words = wordName.split() + words[0] = words[0].upper() + wordName= ' '.join(words) + elif flag_active_cap_next in stateFlags: + wordName = wordName.capitalize() + elif flag_beginning_title_mode in stateFlags: + wordName = wordName.capitalize() + + output = output + wordName + + #----- + # compute the new state flags + + # clear out the capitalization + if not flag_no_cap_change in wordFlags: + stateFlags.discard(flag_active_cap_next) + stateFlags.discard(flag_passive_cap_next) + stateFlags.discard(flag_uppercase_next) + stateFlags.discard(flag_lowercase_next) + stateFlags.discard(flag_beginning_title_mode) + + # reset the state flags + + if not flag_no_space_change in wordFlags: + stateFlags.discard(flag_no_space_next) + stateFlags.discard(flag_two_spaces_next) + # comment, experiment QH + #stateFlags.discard(flag_cond_no_space) + elif not flag_no_formatting in wordFlags: + stateFlags.discard(flag_no_space_next) + # comment, experiment QH + #stateFlags.discard(flag_cond_no_space) + # try to keep numbers and point together with this flag (QH): + stateFlags.discard(flag_cond_no_space) + + # see if we need to reset the cap flags + + if flag_reset_uc_lc_caps in wordFlags: + stateFlags.discard(flag_cap_all) + stateFlags.discard(flag_uppercase_all) + stateFlags.discard(flag_lowercase_all) + + # see if we need to reset the no space flags + + if flag_reset_no_space in wordFlags: + stateFlags.discard(flag_no_space_all) + + if flag_cap_all in wordFlags: + stateFlags.discard(flag_beginning_title_mode) + + # these flags just get copied + copyList = [ flag_active_cap_next, flag_passive_cap_next, + flag_uppercase_next, flag_lowercase_next, flag_no_space_next, + flag_two_spaces_next, flag_cond_no_space, flag_cap_all, + flag_uppercase_all, flag_lowercase_all, flag_no_space_all, + flag_swallow_period, flag_beginning_title_mode ] + + + for i in copyList: + if i in wordFlags: + stateFlags.add(i) + + if flag_new_paragraph in wordFlags and flag_is_period in wordFlags: + stateFlags.add(flag_new_paragraph) + + return output, stateFlags + +def getWordInfo(word): + r"""new getWordInfo function, extracts the word flags from + the middle word like .\period\period + + return the resulting tuple of flags + + """ + if word.find('\\') == -1: + return set() # no flags + wList = word.split('\\') + if len(wList) == 3: + prop = wList[1] + if not prop: + return set() + if prop in propDict: + return set(propDict[prop]) + if prop.startswith('left-'): + return set(propDict['left-double-quote']) + if prop.startswith('right-'): + return set(propDict['right-double-quote']) + print('getWordInfo11, unknown word property: "%s" ("%s")'% (prop, word)) + return set() # empty tuple + # should not come here + return set() + +def initializeStateFlags(*args): + """return an initial state, built up by one or more state flags + + example from natspeak_spell: + state = nsformat.initializeStateFlags(nsformat.flag_no_space_next) + + + """ + return set(args) + +def wordInfoToFlags(wordInfo): + """convert wordInfo number into a set of flags + + """ + emptySet = set(()) + if wordInfo is None: + return emptySet + if wordInfo == 0: + return emptySet + wordFlags = set() + if type(wordInfo) == int: + if wordInfo: + for i in range(32): + if wordInfo & (1<>> rwfile = ReadWriteFile() +>>> input_path = 'path/to/input_file' +>>> result = rwfile.readAnything(input_path) +>>> print(f'encoding: "{rwfile.encoding}", bom: "{rwfile.bom}") +>>> output_string = result + 'new text\n' +>>> output_path = 'path/to/output_file' +>>> rwfile.writeAnything(output_path, output_string) +``` + +The "bom mark" is sometimes/especially the case with the ini files of the Dragon program + +### using this with configparser: + ``` + rwfile = ReadWriteFile() + self.config_text = rwfile.readAnything(filepath) + Config = configparser.ConfigParser() + Config.read_string(self.config_text) + ``` + +Quintijn Hoogenboom, 2018, March 2022 +""" +#pylint:disable=R0912 +import os +import sys + +class ReadWriteFile: + """instance to read any text file and/or and write text into same or new file + + collect encoding and bom mark (byte order mark, sometimes in Windows) + + `encodings` and `encoding` can be overridden at creation of an instance. + `encodings` must then be a list of possible encodings + `encoding` is then + when `encoding` is a str, `encodings` is set to a list only containing this encoding + + the default `encodings` are: `['ascii', 'utf-8', 'cp1252', 'latin-1']` + + a file can be read via this class, and write back another string, using the same encoding and bom mark + + When the encoding is 'ascii' and at write time, non ascii characters are present, care is taken to + encode the output to another encoding, most often (default) 'utf-8'. + """ + def __init__(self, encodings=None): + self.input_path = '' + self.bom = '' + self.text = '' + self.rawText = b'' + if isinstance(encodings, str): + raise TypeError(f'readwritefile, variable "encodings" should be a list, not "{encodings}" (type: {type(encodings)}))') + self.encodings = encodings or ['ascii', 'utf-8', 'cp1252', 'latin-1'] + self.encoding = self.encodings[0] + + + def readAnything(self, input_path, encoding=None): + """take any file and decode to (unocode) string + + works best if filetype is NOT given. + + Try subsequently the encodings, unless overriden by + the encoding variable (str of list of strings) + """ + self.input_path = input_path + if encoding: + if isinstance(encoding, str): + self.encodings = [encoding] + elif isinstance(encoding, (list, tuple)): + self.encodings = list(encoding) + else: + raise ValueError(f'readwritefile, readAnything: invalid input variable "encoding": {encoding}') + self.encoding = self.encodings[0] + + if not os.path.isfile(self.input_path): + raise OSError(f'readwritefile, readAnything: not a file: "{self.input_path}"') + + with open(self.input_path, mode='rb') as file: # b is important -> binary + self.rawText = file.read() + tRaw = fixCrLf(self.rawText) + # + for codingscheme in self.encodings: + result = DecodeEncode(tRaw, codingscheme) + if not result is False: + if codingscheme in ('cp1252', 'latin-1'): + pass + if result and ord(result[0]) == 65279: # BOM, remove + result = result[1:] + self.bom = tRaw[0:3] + self.text = result + self.encoding = codingscheme + return result + print(f'readAnything: no valid encoding found for file: {input_path}') + self.text = '' + return '' + + def writeAnything(self, filepath, content, encoding=None, errors=None): + """write str or list of strings to file + + Take the given `encoding` or the `encoding` of the input (`self.encoding`) or + the first from `self.encodings` (mostly 'ascii') (new file), + and take the bom of the input or '' + + Then follow this encoding, but in case of 'ascii' and errors, use if possible, + the next encoding in `self.encodings`, probably 'utf-8'. + + If they all fail, use by default errors='xmlcharrefreplace' in order + to output all character in html format. + errors can also be 'ignore' or 'replace' + Note: with 'utf-8' as second encoding, there should be no errors! + """ + if encoding: + if isinstance(encoding, str): + self.encoding = encoding + elif isinstance(encoding, (list, tuple)): + self.encodings = encoding + self.encoding = encoding[0] + else: + raise TypeError('readwritefile, writeAnything: unexpected type of encoding: {encoding} (type: {type(encoding)})') + errors = errors or 'xmlcharrefreplace' + assert errors in ('ignore', 'replace', 'xmlcharrefreplace') + if isinstance(content, (list, tuple)): + content = '\n'.join(content) + + if not isinstance(content, str): + raise TypeError("writeAnything, content should be str, not %s (%s)"% (type(content), filepath)) + + if self.encoding != 'ascii': + i = self.encodings.index(self.encoding) + # take 'ascii' and next encoding (will be 'utf-8') + try: + firstEncoding, secondEncoding = self.encodings[i:i+2] + except ValueError: + firstEncoding, secondEncoding = self.encoding, None + else: + # if encoding specified, try only this encoding: + firstEncoding, secondEncoding = self.encoding, None + + try: + tRaw = content.encode(encoding=firstEncoding) + except UnicodeEncodeError: + if secondEncoding: + try: + tRaw = content.encode(encoding=secondEncoding) + except UnicodeEncodeError: + tRaw = content.encode(encoding=firstEncoding, errors=errors) + else: + tRaw = content.encode(encoding=firstEncoding, errors=errors) + + if sys.platform == 'win32': + tRaw = tRaw.replace(b'\n', b'\r\n') + tRaw = tRaw.replace(b'\r\r\n', b'\r\n') + + if self.bom: + # print('add bom for tRaw') + tRaw = self.bom + tRaw + outfile = open(filepath, 'wb') + # what difference does a bytearray make? (QH) + outfile.write(bytearray(tRaw)) + outfile.close() + +def fixCrLf(tRaw): + """replace crlf into lf + """ + if b'\r\r\n' in tRaw: + print('readAnything, fixCrLf: fix crcrlf') + tRaw = tRaw.replace(b'\r\r\n', b'\r\n') + if b'\r' in tRaw: + # print 'readAnything, self.fixCrLf, remove cr' + tRaw = tRaw.replace(b'\r', b'') + return tRaw + +def DecodeEncode(tRaw, filetype): + """return the decoded string or False + + used by readAnything, also see testreadanything in miscqh/test scripts + """ + try: + tDecoded = tRaw.decode(filetype) + except UnicodeDecodeError: + return False + encodedAgain = tDecoded.encode(filetype) + if encodedAgain == tRaw: + return tDecoded + return False + +# if __name__ == '__main__': + ## testing in test_readwritefile, PyTest directory diff --git a/NatlinkModule/singleton.py b/NatlinkModule/singleton.py new file mode 100644 index 00000000..86a737a4 --- /dev/null +++ b/NatlinkModule/singleton.py @@ -0,0 +1,50 @@ +"""implement a Singleton type + +""" +#pylint:disable=C0115 + +import weakref + +class Singleton(type): + _instances = weakref.WeakValueDictionary() + + def __call__(cls, *args, **kwargs): + if cls in cls._instances: + try: + the_ref = weakref.ref(cls._instances[cls]) + except KeyError: + pass + else: + if the_ref: + # still a valid reference: + return cls._instances[cls] + print(f'weakref is/was there, but ref is None: {cls}') + # This variable declaration is required to force a strong reference on the instance. + instance = super(Singleton, cls).__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] + + +class MockObject(metaclass=Singleton): + #pylint:disable=R0903 + def __init__(self, *args, **kwargs): + pass + + +if __name__ == '__main__': + #pylint:disable=W0212 + m = MockObject() + print('one instance, m:') + print(dict(Singleton._instances)) + del m + print(dict(Singleton._instances)) + print('M and N') + M = MockObject() + N = MockObject() + print(dict(Singleton._instances)) + del M + print(dict(Singleton._instances)) + del N + print(dict(Singleton._instances)) + + diff --git a/NatlinkModule/wxdialogs.py b/NatlinkModule/wxdialogs.py new file mode 100644 index 00000000..a0c2d4e5 --- /dev/null +++ b/NatlinkModule/wxdialogs.py @@ -0,0 +1,81 @@ +"""some dialogs running with wx (wxpython) +""" +#pylint:disable=E1101 +import os +import sys +import wx +from natlink import config + +def GetDirFromDialog(promptText, startdir=None): + """call the directory dialog via wxPython + + startdir is directory to start with, can be "expanded" with "~" or + environment variables, defaults to the current directory if not passed + + return a valid path (directory) or None if canceled + """ + if startdir: + startdir = config.expand_path(startdir) + if not os.path.isdir(startdir): + print(f'not a directory: "{startdir}", start with home directory') + startdir = None + if not startdir: + startdir = config.expand_path("~") + oldstdout, oldstderr = sys.stdout, sys.stderr + wxApp = wx.App() + try: + dirpath = None + dlg = wx.DirDialog(None, promptText, startdir, wx.FD_OPEN | wx.DD_NEW_DIR_BUTTON) + if dlg.ShowModal() == wx.ID_OK: + dirpath = dlg.GetPath() + return dirpath + + finally: + dlg.Destroy() + del wxApp + sys.stdout, sys.stderr = oldstdout, oldstderr + return None + + + +def GetFileFromDialog(promptText, wildcard=None, startdir=None): + """call the file dialog via wxPython + + return a valid path (file) or None if dialog was canceled + """ + # pylint: disable=E1101 + wildcard = wildcard or "*.*" + if startdir: + startdir = config.expand_path(startdir) + if not os.path.isdir(startdir): + print(f'not a directory: "{startdir}", start with home directory') + startdir = None + if not startdir: + startdir = config.expand_path("~") + + oldstdout, oldstderr = sys.stdout, sys.stderr + wxApp = wx.App() + try: + dlg = wx.FileDialog(None, promptText, startdir, wildcard, + style=wx.FD_OPEN) + if dlg.ShowModal() == wx.ID_OK: + paths = dlg.GetPaths() + if paths: + # if len(paths) > 1: + # print(r'{len(paths)} files chosen, take first "{paths[0]}"') + # with these options, only one file can be selected... + return paths[0] + print("no file chosen") + return "" + finally: + del wxApp + sys.stdout, sys.stderr = oldstdout, oldstderr + + return None + +if __name__ == "__main__": + # result = GetDirFromDialog("Please specify a directory", startdir="%onedrive%/desktop") + # print(f'GetDirFromDialog, result: {result}') + result = GetFileFromDialog("Please specify a file", wildcard="*.xlsx", startdir="~/Documents") + print(f'GetFileFromDialog, result: {result}') + \ No newline at end of file diff --git a/NatlinkSource/COM/appsupp.cpp b/NatlinkSource/COM/appsupp.cpp index b4a06b93..a50c2f1d 100644 --- a/NatlinkSource/COM/appsupp.cpp +++ b/NatlinkSource/COM/appsupp.cpp @@ -130,7 +130,7 @@ static void CallPyFunctionOrDisplayError(CDragonCode* pDragCode, PyObject* pMod, PyObject* result = PyObject_CallMethod(pMod, szName, NULL); if (result == NULL) { - std::string err = std::string("NatLink: an exception occurred in '") + std::string(szModName) + std::string(".") + std::string(szName) + std::string("'.\r\n"); + std::string err = std::string("Natlink: an exception occurred in '") + std::string(szModName) + std::string(".") + std::string(szName) + std::string("'.\r\n"); pDragCode->displayText(err.c_str(), TRUE); DisplayPythonException(pDragCode); } @@ -214,12 +214,12 @@ STDMETHODIMP CDgnAppSupport::Register( IServiceProvider * pIDgnSite ) m_pDragCode->setDuringInit( TRUE ); m_pNatlinkModule = PyImport_ImportModule( "natlink" ); if ( m_pNatlinkModule == NULL ) { - OutputDebugString( TEXT( "NatLink: an exception occurred loading 'natlink' module" ) ); + OutputDebugString( TEXT( "Natlink: an exception occurred loading 'natlink' module" ) ); DisplaySysPath(m_pDragCode); DisplayPythonException(m_pDragCode); return S_OK; } else { - m_pDragCode->displayText( "NatLink: LOADED!\n", FALSE ); + m_pDragCode->displayText( "Natlink is loaded...\n\n", FALSE ); } CallPyFunctionOrDisplayError(m_pDragCode, m_pNatlinkModule, "natlink", "redirect_all_output_to_natlink_window"); DisplayPythonException(m_pDragCode); diff --git a/NatlinkSource/natlink.rc.in b/NatlinkSource/natlink.rc.in index 44eb485a..efcafb55 100644 --- a/NatlinkSource/natlink.rc.in +++ b/NatlinkSource/natlink.rc.in @@ -98,7 +98,7 @@ IDR_APPSUPP REGISTRY "appsupp.reg" IDD_STDOUT DIALOGEX 0, 25, 285, 135 STYLE DS_SETFONT | DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME -CAPTION "Messages from Natlink 6.0" +CAPTION "Messages from Natlink" //MENU IDR_MENU FONT 9, "Courier New", 0, 0, 0x0 BEGIN diff --git a/PyTest/alltest.py b/PyTest/alltest.py new file mode 100644 index 00000000..140df001 --- /dev/null +++ b/PyTest/alltest.py @@ -0,0 +1,8 @@ +"""test all other modules in this directory""" + +import pytest + +if __name__ == "__main__": + pytest.main() + + \ No newline at end of file diff --git a/PyTest/readwritefiletest/acoustic.ini b/PyTest/readwritefiletest/acoustic.ini new file mode 100644 index 00000000..f14e489e --- /dev/null +++ b/PyTest/readwritefiletest/acoustic.ini @@ -0,0 +1,23 @@ +[Base Acoustic] +2 1=US English | BestMatch IV +2 2=US English | BestMatch IV + +[Acoustics] +2 1=2_1 +2 2=2_2 + +[Core Model] +2 1=internal-base-model-eng-USA-USA-bm4-am0 +2 2=internal-base-model-eng-USA-USA-bm4-am0 + +[User File] +2 1=enu_p1.usr +2 2=enu_p1.usr + +[User File 1] +2 1=enu_p2.usr +2 2=enu_p2.usr + +[Pass 2 Models] +2 1=internal-base-model-eng-USA-USA-bm4-am1 +2 2=internal-base-model-eng-USA-USA-bm4-am1 diff --git a/PyTest/readwritefiletest/natlink.ini b/PyTest/readwritefiletest/natlink.ini new file mode 100644 index 00000000..fd547c9d --- /dev/null +++ b/PyTest/readwritefiletest/natlink.ini @@ -0,0 +1,38 @@ +[directories] +# specify the directory / directories that are loaded when Natlink starts: +# typically a single line below needs uncommenting, but more is also possible +# Caster = C:\User\X\Dropbox\Caster +# Dragonfly = C:\User\X\Dropbox\Dragonfly +# Vocola = C:\User\X\Dropbox\Vocola +# Unimacro = C:\User\X\Dropbox\Unimacro +# UserDirectory = C:\User\X\Dropbox\UserDirectory ## can be the same as Dragonfly or Caster directory + +# remove this line when configuring your own directories: +default_config = C:\Program Files (x86)\Natlink\DefaultConfig + +[userenglish-directories] +#only_loaded_if_profile_userenglish_active=C:\User\user\english-only-scripts + +[userspanish-directories] +#only_loaded_if_profile_userspanish_active=C:\User\user\spanish-only-scripts + +[settings] + +# log_level: the log level to set the Natlink logger to. +# Possible values are: CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG, NOTSET +# (default: NOTSET) +log_level = DEBUG +# +# Determine when to check for new or changed scripts and then load or reload them. +# +# Load scripts after Dragon has loaded Natlink (default: True) +load_on_startup = True +# +# At the beginning of each utterance (default: False) +load_on_begin_utterance = False + +# When the microphone state changes to "on" (default: True) +load_on_mic_on = True + +# load or reload when the user profile changes. +load_on_user_changed = True diff --git a/PyTest/readwritefiletest/natlinkconfigured.ini b/PyTest/readwritefiletest/natlinkconfigured.ini new file mode 100644 index 00000000..570388b7 --- /dev/null +++ b/PyTest/readwritefiletest/natlinkconfigured.ini @@ -0,0 +1,40 @@ +[directories] +# specify the directory / directories that are loaded when Natlink starts: +# typically a single line below needs uncommenting, but more is also possible +UserDirectory = C:\User\X\Dropbox\UserDirectory +Vocola = C:\User\X\Dropbox\Vocola +VocolaGrammars = ~/.natlink/VocolaGrammars +Unimacro = C:\User\X\Dropbox\Unimacro +UnimacroGrammars = ~/.natlink/UnimacroGrammars +Dragonfly = C:\User\X\Dropbox\Dragonfly +Caster = C:\User\X\Dropbox\Caster + +# remove this line when configuring your own directories: +# default_config = C:\Program Files (x86)\Natlink\DefaultConfig + +[userenglish-directories] +#only_loaded_if_profile_userenglish_active=C:\User\user\english-only-scripts + +[userspanish-directories] +#only_loaded_if_profile_userspanish_active=C:\User\user\spanish-only-scripts + +[settings] + +# log_level: the log level to set the Natlink logger to. +# Possible values are: CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG, NOTSET +# (default: NOTSET) +log_level = DEBUG +# +# Determine when to check for new or changed scripts and then load or reload them. +# +# Load scripts after Dragon has loaded Natlink (default: True) +load_on_startup = True +# +# At the beginning of each utterance (default: False) +load_on_begin_utterance = False + +# When the microphone state changes to "on" (default: True) +load_on_mic_on = True + +# load or reload when the user profile changes. +load_on_user_changed = True diff --git a/PyTest/readwritefiletest/options.ini b/PyTest/readwritefiletest/options.ini new file mode 100644 index 00000000..ac1aec38 --- /dev/null +++ b/PyTest/readwritefiletest/options.ini @@ -0,0 +1,66 @@ +[Options] +Created With Wizard=1 +Dictation Source=2 +Age Selected=Zeg ik liever niet +Last Used Acoustics=2 2 +DataCollector GUID=DFE3AA18-85B4-4459-BDC5-F7C33BD8EC66 +Last Used Topic=General - Large +New Dragonbar Last Position=539,25 +ISO Code=USA +Preferred Accent Model=Standard +Skipped LME=1 +Periodic Optimizations Are Configured=3 +Self Calibration Required=0 +Show iTutorial After User Creation=0 +enx Correct Brings Up Choicebox=0 +enx Select Brings Up Choicebox=0 +Select XYZ Direction=4 +Do Delete From XYZ=1 +Results Box Pinned=1 +Results Box Feedback Strategy=10 +Results Box Enable Auto Close=1 +Correction Hot Key=0,0 +Playback Hot Key=0,0 +Fast Playback Hot Key=0,0 +enx Autopunctuation Hot Key=0,0 +Microphone Hot Key=112,0 +DragonBar Hot Key=0,0 +Sleep Hot Key=0,0 +Dictation Box Hot Key=0,0 +Light Editor Hot Key=0,0 +Transfer Text Hot Key=0,0 +Hidden Mode Hot Key=0,0 +Select Next Variable Hot Key=0,0 +Tracking Disabled App List={} +Save Speech With Document=0 +Voicebar Font=Segoe UI,17,400,0 +LastLMOrunYMDHR=_2022_2_13___15___0_ +User Used Without NMS=1 +TopicMRU=General - Large +LastLearnFromSpecificDocumentsDateYMDHR=_2022_1_29___12___0_ +LastLearnFromEmailDateYMDHR=_2020_4_25___12___0_ +bDoNotAskMeWarningOfDictBox=1 +DictationBox app-based data=2,ActiveState Komodo,1,0,737,365,675,1183,,1,0,465,379,689,911 +Sample commands position=747,234,1172,806 +Use Dictation Box=0 +DragonBar Last Floating Mode Position=642,0,1185,93 +LearnFromEmailReminderIntervalInMonths=-1 +Previous DragonBar Mode=5 +Enable Tips=0 +DragonBar Mode=0 +McEditor Size=36766185 +Results Box Position=1213,121,1264,157 +Japanese Dictation Mode=1 +Modern DragonBar Collapse Mode=1 +Profile Update Context=4 + +[Dictation Box Blacklist] +chrome=1 +MicrosoftEdge=1 +MicrosoftEdgeCP=1 +firefox=1 +iexplore=1 +komodo=1 + +[InterventionTips] +dgnbarTipID_ScratchThat=0 diff --git a/PyTest/test_callbackhandler.py b/PyTest/test_callbackhandler.py new file mode 100644 index 00000000..62287b36 --- /dev/null +++ b/PyTest/test_callbackhandler.py @@ -0,0 +1,45 @@ +#pylint:disable= C0114, C0115, C0116, W0401, W0614, W0621, W0108. W0212, R0201 + +import pytest + +from natlink.callbackhandler import CallbackHandler + +class MakeFunctions: + def a1(self): + print("a1") + def a2(self): + print("a2") + def a3(self): + b = 1/0 + print(f'a3: {b}') + +def test_callbacks(): + """try to make an invalid function, + by removing an instance that hold these functions. + the function in question still remains valid as it seems. + + """ + functions1 = MakeFunctions() + functions2 = MakeFunctions() + cbh = CallbackHandler('test handler') + print(cbh.info()) + assert len(cbh.callbacks) == 0 + cbh.set(functions1.a1) + assert len(cbh.callbacks) == 1 + print(cbh.info()) + cbh.set(functions2.a1) + assert len(cbh.callbacks) == 2 + print(cbh.info()) + cbh.run() + print(cbh.info()) + assert len(cbh.callbacks) == 2 + + # remove from functions1 + # only removint functions1 does not seem to have effect. + cbh.delete(functions1.a1) + cbh.run() + assert len(cbh.callbacks) == 1 + print(cbh.info()) + +if __name__ == "__main__": + pytest.main(['test_callbackhandler.py']) diff --git a/PyTest/test_config.py b/PyTest/test_config.py new file mode 100644 index 00000000..1ca81ff0 --- /dev/null +++ b/PyTest/test_config.py @@ -0,0 +1,30 @@ + +#pylint:disable= C0114, C0116, W0401, W0614, W0621, W0108. W0212 + +import pytest + +from natlink.config import * +from natlink import loader + +@pytest.fixture() +def empty_config(): + config = NatlinkConfig.get_default_config() + return config + +def test_empty_config(): + """does not test really + """ + print(f'empty_config: {empty_config}') + +def test_config_locations(): + """tests the lists of possible config_locations and of valid_locations + """ + locations = loader.config_locations() + assert len(locations) == 2 + valid_locations = loader.valid_config_locations() + assert len(valid_locations) > 0 + assert os.path.isfile(valid_locations[0]) + + +if __name__ == "__main__": + pytest.main(['test_config.py']) diff --git a/PyTest/test_gramparser.py b/PyTest/test_gramparser.py index 762c88fa..bbb05cb1 100644 --- a/PyTest/test_gramparser.py +++ b/PyTest/test_gramparser.py @@ -1,3 +1,5 @@ + +#pylint:disable= C0114, C0116 import pytest from natlink.gramparser import GramParser, packGrammar, GrammarSyntaxError, splitApartLines diff --git a/PyTest/test_loader.py b/PyTest/test_loader.py index 48fb8563..993fad7f 100644 --- a/PyTest/test_loader.py +++ b/PyTest/test_loader.py @@ -1,8 +1,10 @@ + +#pylint:disable= C0114, C0116, W0401, W0614, W0621, W0108. W0212 + import pytest from natlink.loader import * - class MockLoggingHandler(logging.Handler): """Mock logging handler to check for expected logs.""" @@ -40,13 +42,13 @@ def logger(): logger.reset = lambda: log_handler.reset() return logger - def del_loaded_modules(natlink_main: NatlinkMain): - for name, mod in natlink_main.loaded_modules.items(): + for _name, mod in natlink_main.loaded_modules.items(): if mod: del mod + def test_empty_config_loader(empty_config, logger): main = NatlinkMain(logger, empty_config) assert main.module_paths_for_user == [] @@ -55,11 +57,13 @@ def test_empty_config_loader(empty_config, logger): assert main.bad_modules == set() assert main.load_attempt_times == {} - def test_pre_and_post_load_setter(empty_config, logger): main = NatlinkMain(logger, empty_config) - main.set_pre_load_callback(None) - main.set_post_load_callback(None) + main.config = empty_config + with pytest.raises(TypeError): + main.set_pre_load_callback(None) + with pytest.raises(TypeError): + main.set_post_load_callback(None) with pytest.raises(TypeError): main.set_pre_load_callback("not callable") with pytest.raises(TypeError): @@ -67,10 +71,11 @@ def test_pre_and_post_load_setter(empty_config, logger): cb = lambda: None main.set_pre_load_callback(cb) main.set_post_load_callback(cb) - + def test_trigger_load_calls_pre_and_post_load(empty_config, logger, monkeypatch): main = NatlinkMain(logger, empty_config) + main.config = empty_config actual = [] def pre(): @@ -86,6 +91,9 @@ def post(): main.set_post_load_callback(post) monkeypatch.setattr(main, 'load_or_reload_modules', load) + # this one fails, because now per directory grammars are loaded, + # line 347 is now commented in favour of lin 351 loader.py (QH) + main.trigger_load() expected = [1, 2, 3] @@ -101,9 +109,9 @@ def test_load_single_good_script(tmpdir, empty_config, logger, monkeypatch): a_script.write("""x=0""") a_script.setmtime(mtime) monkeypatch.setattr(time, 'time', lambda: mtime) - + main = NatlinkMain(logger, config) - + main.config = config assert main.module_paths_for_user == [a_path] main.load_or_reload_modules(main.module_paths_for_user) @@ -111,11 +119,13 @@ def test_load_single_good_script(tmpdir, empty_config, logger, monkeypatch): assert '_a' not in sys.modules assert main.bad_modules == set() assert set(main.load_attempt_times.keys()) == {a_path} + print('mtime: {mtime}') + print('main.load_attempt_times[a_path]: {main.load_attempt_times[a_path]}') assert main.load_attempt_times[a_path] == mtime assert main.loaded_modules[a_path].x == 0 del_loaded_modules(main) - + def test_load_single_good_script_from_user_dir(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -128,12 +138,17 @@ def test_load_single_good_script_from_user_dir(tmpdir, empty_config, logger, mon monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) + main.config = config + main.__init__(logger=logger, config=config) + _modules = main.module_paths_for_user assert main.module_paths_for_user == [] - main._user = 'user' + main.user = 'user' # this way, because now user is a property + _modules = main.module_paths_for_user assert main.module_paths_for_user == [a_path] main.load_or_reload_modules(main.module_paths_for_user) + _mainkeys = set(main.loaded_modules.keys()) assert set(main.loaded_modules.keys()) == {a_path} assert '_a' not in sys.modules assert main.bad_modules == set() @@ -142,7 +157,7 @@ def test_load_single_good_script_from_user_dir(tmpdir, empty_config, logger, mon assert main.loaded_modules[a_path].x == 0 del_loaded_modules(main) - + def test_reload_single_changed_good_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -154,14 +169,15 @@ def test_reload_single_changed_good_script(tmpdir, empty_config, logger, monkeyp a_script.setmtime(mtime) monkeypatch.setattr(time, 'time', lambda: mtime) - main = NatlinkMain(logger, config) - + main = NatlinkMain(logger, empty_config) + main.__init__(logger=logger, config=empty_config) + main.load_or_reload_modules(main.module_paths_for_user) mtime += 1.0 a_script.write("""x=1""") a_script.setmtime(mtime) - + main.seen.clear() # is done at start of trigger_load main.load_or_reload_modules(main.module_paths_for_user) assert set(main.loaded_modules.keys()) == {a_path} assert main.bad_modules == set() @@ -170,7 +186,7 @@ def test_reload_single_changed_good_script(tmpdir, empty_config, logger, monkeyp assert main.loaded_modules[a_path].x == 1 del_loaded_modules(main) - + def test_remove_single_deleted_good_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -182,6 +198,7 @@ def test_remove_single_deleted_good_script(tmpdir, empty_config, logger, monkeyp monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) + main.config = config main.load_or_reload_modules(main.module_paths_for_user) @@ -193,10 +210,12 @@ def test_remove_single_deleted_good_script(tmpdir, empty_config, logger, monkeyp assert main.load_attempt_times == {} del_loaded_modules(main) - + def test_reload_should_skip_single_good_unchanged_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config + main = NatlinkMain(logger, empty_config) + main.__init__(logger=logger, config=config) config.directories_by_user[''] = [tmpdir.strpath] a_script = tmpdir.join('_a.py') a_path = Path(a_script.strpath) @@ -205,15 +224,13 @@ def test_reload_should_skip_single_good_unchanged_script(tmpdir, empty_config, l a_script.setmtime(mtime) monkeypatch.setattr(time, 'time', lambda: mtime) - main = NatlinkMain(logger, config) - main.load_or_reload_modules(main.module_paths_for_user) a_script.write("""x=1""") # set the mtime to the old mtime, so natlink should NOT reload a_script.setmtime(mtime) mtime += 1.0 - + main.seen.clear() # is done in trigger_load main.load_or_reload_modules(main.module_paths_for_user) assert set(main.loaded_modules.keys()) == {a_path} assert main.bad_modules == set() @@ -223,11 +240,12 @@ def test_reload_should_skip_single_good_unchanged_script(tmpdir, empty_config, l # make sure it still has the old value, not the new one assert main.loaded_modules[a_path].x == 0 - msg = f'skipping unchanged loaded module: _a' + ## TODO how solve this (QH) + msg = 'skipping unchanged loaded module: _a' assert msg in logger.messages['debug'] del_loaded_modules(main) - + def test_load_single_bad_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -240,7 +258,7 @@ def test_load_single_bad_script(tmpdir, empty_config, logger, monkeypatch): monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) - + main.__init__(logger=logger, config=config) main.load_or_reload_modules(main.module_paths_for_user) assert main.loaded_modules == {} assert '_a' not in sys.modules @@ -250,7 +268,7 @@ def test_load_single_bad_script(tmpdir, empty_config, logger, monkeypatch): assert len(logger.messages['error']) == 1 del_loaded_modules(main) - + def test_remove_single_deleted_bad_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -262,7 +280,8 @@ def test_remove_single_deleted_bad_script(tmpdir, empty_config, logger, monkeypa monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) - + main.__init__(logger=logger, config=config) + main.load_or_reload_modules(main.module_paths_for_user) a_script.remove() @@ -273,7 +292,7 @@ def test_remove_single_deleted_bad_script(tmpdir, empty_config, logger, monkeypa assert main.load_attempt_times == {} del_loaded_modules(main) - + def test_reload_single_changed_bad_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -286,12 +305,14 @@ def test_reload_single_changed_bad_script(tmpdir, empty_config, logger, monkeypa monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) + main.__init__(logger=logger, config=config) main.load_or_reload_modules(main.module_paths_for_user) mtime += 1.0 a_script.setmtime(mtime) logger.reset() + main.seen.clear() main.load_or_reload_modules(main.module_paths_for_user) assert main.loaded_modules == {} assert '_a' not in sys.modules @@ -301,7 +322,7 @@ def test_reload_single_changed_bad_script(tmpdir, empty_config, logger, monkeypa assert len(logger.messages['error']) == 1 del_loaded_modules(main) - + def test_reload_should_skip_single_bad_unchanged_script(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -314,6 +335,7 @@ def test_reload_should_skip_single_bad_unchanged_script(tmpdir, empty_config, lo monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) + main.__init__(logger=logger, config=config) main.load_or_reload_modules(main.module_paths_for_user) @@ -322,6 +344,7 @@ def test_reload_should_skip_single_bad_unchanged_script(tmpdir, empty_config, lo a_script.setmtime(mtime) mtime += 1.0 + main.seen.clear() main.load_or_reload_modules(main.module_paths_for_user) assert main.loaded_modules == {} assert '_a' not in sys.modules @@ -329,11 +352,11 @@ def test_reload_should_skip_single_bad_unchanged_script(tmpdir, empty_config, lo assert set(main.load_attempt_times.keys()) == {a_path} assert main.load_attempt_times[a_path] == mtime - msg = f'skipping unchanged bad module: _a' + msg = 'skipping unchanged bad module: _a' assert msg in logger.messages['info'] del_loaded_modules(main) - + def test_load_single_good_script_that_was_previously_bad(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -346,12 +369,13 @@ def test_load_single_good_script_that_was_previously_bad(tmpdir, empty_config, l monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) - + main.__init__(logger=logger, config=config) main.load_or_reload_modules(main.module_paths_for_user) mtime += 1.0 a_script.write("""x=1""") a_script.setmtime(mtime) + main.seen.clear() main.load_or_reload_modules(main.module_paths_for_user) assert set(main.loaded_modules.keys()) == {a_path} assert main.bad_modules == set() @@ -360,7 +384,7 @@ def test_load_single_good_script_that_was_previously_bad(tmpdir, empty_config, l assert main.loaded_modules[a_path].x == 1 del_loaded_modules(main) - + def test_load_single_bad_script_that_was_previously_good(tmpdir, empty_config, logger, monkeypatch): config = empty_config @@ -373,12 +397,15 @@ def test_load_single_bad_script_that_was_previously_good(tmpdir, empty_config, l monkeypatch.setattr(time, 'time', lambda: mtime) main = NatlinkMain(logger, config) + main.__init__(logger=logger, config=config) + main.__init__(logger=logger, config=config) main.load_or_reload_modules(main.module_paths_for_user) mtime += 1.0 a_script.write("""x=; #a syntax error.""") a_script.setmtime(mtime) + main.seen.clear() main.load_or_reload_modules(main.module_paths_for_user) assert main.loaded_modules == {} assert '_a' not in sys.modules @@ -388,3 +415,6 @@ def test_load_single_bad_script_that_was_previously_good(tmpdir, empty_config, l assert len(logger.messages['error']) == 1 del_loaded_modules(main) +# +if __name__ == "__main__": + pytest.main(['test_loader.py']) diff --git a/PyTest/test_readwritefile.py b/PyTest/test_readwritefile.py new file mode 100644 index 00000000..3c46447c --- /dev/null +++ b/PyTest/test_readwritefile.py @@ -0,0 +1,134 @@ + +#pylint:disable= C0114, C0116 +import os +import configparser +# import pytest +from natlink.readwritefile import ReadWriteFile + +thisFile = __file__ +thisDir, Filename = os.path.split(thisFile) +testDir = os.path.join(thisDir, 'readwritefiletest') + + +def test_read_file(): + for F in os.listdir(testDir): + if F.endswith('.ini'): + F_path = os.path.join(testDir, F) + rwfile = ReadWriteFile() + text = rwfile.readAnything(F_path) + print(f'F: "{F}", encoding: {rwfile.encoding}, bom: {rwfile.bom}') + print(f'len text: {len(text)}') + print('-'*80) + if rwfile.bom: + print(f'{rwfile.bom}') + print(f'{rwfile.rawText}') + +def test_only_write_file(): + join, isfile = os.path.join, os.path.isfile + newFile = join(testDir, 'newfile.txt') + if isfile(newFile): + os.unlink(newFile) + rwfile = ReadWriteFile() + text = '' + rwfile.writeAnything(newFile, text) + assert open(newFile, 'rb').read() == b'' + +def test_accented_characters_write_file(): + join, isfile = os.path.join, os.path.isfile + newFile = join(testDir, 'accented.txt') + if isfile(newFile): + os.unlink(newFile) + text = 'caf\xe9' + rwfile = ReadWriteFile(encodings=['ascii']) # optional encoding + # this is with default errors='xmlcharrefreplace': + rwfile.writeAnything(newFile, text) + testTextBinary = open(newFile, 'rb').read() + wanted = b'café' + assert testTextBinary == wanted + # same, default is 'xmlcharrefreplace': + rwfile.writeAnything(newFile, text, errors='xmlcharrefreplace') + testTextBinary = open(newFile, 'rb').read() + assert testTextBinary == b'café' + assert len(testTextBinary) == 9 + rwfile.writeAnything(newFile, text, errors='replace') + testTextBinary = open(newFile, 'rb').read() + assert testTextBinary == b'caf?' + assert len(testTextBinary) == 4 + rwfile.writeAnything(newFile, text, errors='ignore') + testTextBinary = open(newFile, 'rb').read() + assert testTextBinary == b'caf' + assert len(testTextBinary) == 3 + +def test_other_encodings_write_file(): + join = os.path.join + oldFile = join(testDir, 'latin1 accented.txt') + rwfile = ReadWriteFile(encodings=['latin1']) # optional encoding + text = rwfile.readAnything(oldFile) + assert text == 'latin1 café' + + + + + +def test_latin1_cp1252_write_file(): + join = os.path.join + _newFile = join(testDir, 'latin1.txt') + _newFile = join(testDir, 'cp1252.txt') + # TODO (QH) to be done, these encodings do not take all characters, + # and need special attention. + # (as long as the "fallback" is utf-8, all write files should go well!) + +def test_read_write_file(): + listdir, join, splitext = os.listdir, os.path.join, os.path.splitext + for F in listdir(testDir): + if F.endswith('.ini'): + F_path = join(testDir, F) + rwfile = ReadWriteFile() + text = rwfile.readAnything(F_path) + print(f'F: "{F}", encoding: {rwfile.encoding}, bom: {rwfile.bom}') + trunk, _ext = splitext(F) + Fout = trunk + ".txt" + Fout_path = join(testDir, Fout) + rwfile.writeAnything(Fout_path, text) + assert open(F_path, 'rb').read() == open(Fout_path, 'rb').read() + +def test_read_config_file(): + listdir, join, splitext = os.listdir, os.path.join, os.path.splitext + for F in listdir(testDir): + if F.endswith('.ini'): + if F == 'acoustics.ini': + F_path = join(testDir, F) + rwfile = ReadWriteFile() + config_text = rwfile.readAnything(F_path) + Config = configparser.ConfigParser() + Config.read_string(config_text) + assert Config.get('Acoustics', '2 2') == '2_2' + continue + + if F in ['natlink.ini', 'natlinkconfigured.ini']: + F_path = join(testDir, F) + rwfile = ReadWriteFile() + config_text = rwfile.readAnything(F_path) + Config = configparser.ConfigParser() + Config.read_string(config_text) + debug_level = Config.get('settings', 'log_level') + assert debug_level == 'DEBUG' + Config.set('settings', 'log_level', 'INFO') + new_debug_level = Config.get('settings', 'log_level') + assert new_debug_level == 'INFO' + trunk, ext = splitext(F) + Fout = trunk + 'out' + ext + Fout_path = join(testDir, Fout) + Config.write(open(Fout_path, 'w', encoding=rwfile.encoding)) + + + + + + +if __name__ == "__main__": + test_other_encodings_write_file() + # test_only_write_file() + # test_read_write_file() + # test_read_config_file() + \ No newline at end of file diff --git a/SampleMacros/OriginalSampleMacros/Index.htm b/SampleMacros/OriginalSampleMacros/Index.htm new file mode 100644 index 00000000..b38c593a --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/Index.htm @@ -0,0 +1,126 @@ + + + + NatLink Sample Macros + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sample / Tutorial Macros
NameDescription
_sample1.pyThis file represents the simplest possible example of a + NatLink macro
_sample2.pyThis file demonstrates having two commands in one grammar. + NatLink knows which rule was recognized and calls the appropiate handling + function (gotResults_xxx)
_sample3.pyThis is a sample macro file used to demonstrate how NatLink + calls result functions (gotResults_xxx) based on which words were recognized
_sample4.pysimple macro file demostrating mouse movement
_sample5.pyDemonstrates how to use the clipboard to communicate + between your application and Python code
natspeak_sample6.pyDemonstrates how to write application specific code
excel_sample7.pyusing OLE Automation to control Excel from Python macros
_sample8.pyDemonstrates how to use <dgndictation>
_globals.pySample macro file which is active all the time (not + application specific)
_mouse.pyImplements mouse and keyboard movement modes similar to DragonDictate for Windows
_sample9.pydemonstrates how to create code to run at startup"
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Utility Macros
NameDescriptionStatus
trainuser.pyThis is a sample python script which will create a new + trained user from a series of recorded sessionsunknown
transcribe.pyThis is a sample python script which will transcribe a + directory of compressed wave files from a mobile recorderunknown
windict.pyThis is a sample Python program which demonstrates how to + use the dictation object in a Python program. The user interface for this + programn is based on the PythonWin (win32) extensionsunknown
winspch.pyThis is a sample of adding grammar based speech recognition + to a simple Python application written using the Python win32 librariesunknown
wordpad.pySample macro file which is active when Microsoft WordPad is activeunknown
+ + + + + + +
+

To use these macros you must copy them to the macrosystem directory.

+

See also + NatLink MiscScripts

+ + + + + + + + + diff --git a/SampleMacros/OriginalSampleMacros/Writing Python Macros.url b/SampleMacros/OriginalSampleMacros/Writing Python Macros.url new file mode 100644 index 00000000..3df6224f --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/Writing Python Macros.url @@ -0,0 +1,7 @@ +[DEFAULT] +BASEURL=http://www.synapseadaptive.com/joel/WritingPythonMacros.html +[InternetShortcut] +URL=http://www.synapseadaptive.com/joel/WritingPythonMacros.html +Modified=D02C8C84554FC40175 +IconFile=http://www.synapseadaptive.com/favicon.ico +IconIndex=1 diff --git a/SampleMacros/OriginalSampleMacros/_globals.py b/SampleMacros/OriginalSampleMacros/_globals.py new file mode 100644 index 00000000..c675cad2 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_globals.py @@ -0,0 +1,98 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# _globals.py +# Sample macro file which is active all the time (not application specific). +# +# April 25, 1999 +# - packaged for external release +# +# March 3, 1999 +# - initial version +# +#pylint:disable=C0114, C0115, C0116, C0321, W0613, R0201, E1101, W0603 +############################################################################ +# +# This is a sample grammar file. I have implemented some basic global +# commands for example purposes. +# +# This file is loaded automatically when the Python subsystem starts because +# its filename begins with an underscore (the signal for a global module). +# +# Please see the example wordpad.py for more comments. +# + +import natlink +from natlink import natlinkutils + +class ThisGrammar(natlinkutils.GrammarBase): + + # We create a simple grammar to illustrate a couple of basic ideas. + # You can say "Python microphone off" or "Python go to sleep" which + # have exactly the same effect as "microphone off" and "go to sleep". + # + # You can also say "Python stop listening" which simulates sleeping + # by putting the system into a state where the only thing which will + # be recognized is "Python start listening" + + testGram = """ + = Python microphone off; + = Python go to sleep; + = Python stop listening; + exported = Python start listening; + exported = | | ; + """ + + # Load the grammar and activate the rule "normalState". We use + # activateSet instead of activate because activateSet is an efficient + # way of saying "deactivateAll" then "activate(xxx)" for every xxx + # in the array. + + def initialize(self): + self.load(self.testGram) + self.activateSet(['normalState']) + self.setExclusive(0) + + # When words are recognized from the rule "micOff", this function gets + # called. We turn the microphone off. + + def gotResults_micOff(self,words,fullResults): + natlink.setMicState('off') + + # When words are recognized from the rule "sleep", this function gets + # called. We put the microphone in the speeling state. This will + # cause the built-in NatSpeak global commands module to activate a + # special "wake-up" state in exclusive mode. We have no control + # over this (although we could activate our own exclusive rule at the + # same time). + + def gotResults_sleep(self,words,fullResults): + natlink.setMicState('sleeping') + + # For the rule "stop", we activate the "notListening" rule which + # contains only one subrule. We also force exclusive state for this + # grammar which turns off all other non-exclusive grammar in the system. + + def gotResults_stop(self,words,fullResults): + self.activateSet(['notListening'],exclusive=1) + + # When we get "start listening", restore the default state of this + # grammar. + + def gotResults_notListening(self,words,fullResults): + self.activateSet(['normalState'],exclusive=0) + +# +# Here is the initialization and termination code. See wordpad.py for more +# comments. +# + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_mouse.py b/SampleMacros/OriginalSampleMacros/_mouse.py new file mode 100644 index 00000000..497abcaf --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_mouse.py @@ -0,0 +1,336 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# _mouse.py +# Sample macro file which implements mouse and keyboard movement modes +# similar to DragonDictate for Windows +# +# April 1, 2000 +# Updates from Jonathan Epstein +# - cancel arrow movement when the active window changes +# - add support for tray icon during arrow movement +# + +# In the grammar we map some keywords into pixel counts according to the +# following dictionary. These numbers can be safely changed within reason. + +amountDict = { + 'little':3, # as in 'move a little left' + 'lot':10 } # as in 'move left a lot' + +# For caret movement, this represents the default speed in milliseconds +# between arrow keys + +defaultMoveSpeed = 250 + +# For caret movement, this is the rate change applied when you make it +# faster. For example, 1.5 is a 50% speed increase. + +moveRateChange = 2.0 + +# For mouse movement, this represents the default speed in milliseconds +# between pixel movements and the default number of pixels per move. We +# do not want the update rate to be less than 50 milliseconds so if it +# gets faster than that, we adjust the mouse pixels instead. + +defaultMouseSpeed = 100 +defaultMousePixels = 1 + +# For mouse movement, this is the rate change applied when you make it +# faster. For example, 1.5 is a 50% speed increase. + +mouseRateChange = 3.0 + +############################################################################ +# +# Here are some of our instance variables +# +# self.haveCallback set when the timer callback in installed +# self.curMode 1 for caret movement, 2 for mouse movement, or None +# self.curSpeed current movement speed (milliseconds for timer) +# self.curPixels for mouse movement, pixels per move +# self.lastClock time of last timer callback or 0 +# self.curDirection direction of movement as string +# + +import string # for atoi +import time # for clock +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + # when we unload the grammar, we must make sure we clear the timer + # callback so we keep a variable which is set when we currently own + # the timer callback + + def __init__(self): + self.haveCallback = 0 + self.curMode = None + self.iconState = 0 + GrammarBase.__init__(self) + + def unload(self): + if self.haveCallback: + natlink.setTimerCallback(None,0) + self.haveCallback = 0 + GrammarBase.unload(self) + + # This is our grammar. The rule 'start' is what is normally active. The + # rules 'nowMoving' and 'nowMousing' are used when we are in caret or + # mouse movement mode. + + gramDefn = """ + # this is the rule which is normally active + exported = | | + | ; + + # this rule is active when we are moving the caret + exported = + [ move ] ( {direction} | [much] faster | [much] slower ) | + stop [ moving ]; + + # this rule is active when we are moving the mouse + exported = + [ move ] ( {direction} | faster | slower ) | + stop [ moving ] | | ; + + # here are the subrules which deal with caret movement + = move {direction} | start moving {direction}; + + # here are the subrules which deal with mouse movement + = [ start moving ] mouse {direction}; + = + nudge mouse {direction} | + [ move ] mouse {direction} ( a little | a lot | {count} pixels ) | + [ move ] mouse ( a little | a lot | {count} pixels ) {direction}; + = + [ mouse ] [ left | middle | right ] [ single | double ] click; + """ + + # These are the lists which we use in our grammar. The directions and + # counts are implemented as lists to make parsing easier (words from + # lists are referenced as part of the rule which includes the list). + + listDefn = { + 'direction' : ['up','down','left','right'], + 'count' : ['1','2','3','4','5','6','7','8','9','10','11','12','13', + '14','15','16','17','18','19','20','25','30','35','40','45','50'] } + + # Load the grammar, build the direction and count lists and activate the + # main rule ('start') + + def initialize(self): + self.load(self.gramDefn) + for listName in self.listDefn.keys(): + self.setList(listName,self.listDefn[listName]) + self.activateSet(['start'],exclusive=0) + + # This subroutine moves the mouse cursor in an indicated direction + # by an indicated number of pixels + + def moveMouse(self,direction,count): + xPos,yPos = natlink.getCursorPos() + if direction == 'up': yPos = yPos - count + elif direction == 'down': yPos = yPos + count + elif direction == 'left': xPos = xPos - count + elif direction == 'right': xPos = xPos + count + xSize,ySize = natlink.getScreenSize() + if xPos < 0: xPos = 0 + if xPos >= xSize: xPos = xSize - 1 + if yPos < 0: yPos = 0 + if yPos >= ySize: yPos = ySize - 1 + natlink.playEvents([(wm_mousemove,xPos,yPos)]) + + # This subroutine cancels any active movement mode + + def cancelMode(self): + self.curMode = None + if self.haveCallback: + natlink.setTimerCallback(None,0) + self.haveCallback = 0 + self.activateSet(['start'],exclusive=0) + natlink.setTrayIcon() + + # This function is called on a timer event. If we are in a movement + # mode then we move the mouse or caret by the indicated amount. + # + # The apparent speed for mouse movement is the speed divided by the + # number of pixels per move. We calculate the number of pixels per + # move to ensure that the speed is never faster than 50 milliseconds. + + def onTimer(self): + if self.lastClock: + diff = int( (time.clock() - self.lastClock) * 1000 ) + self.lastClock = time.clock() + if self.curMode == 1: + moduleInfo = natlink.getCurrentModule() + if natlink.getMicState() == 'on' and moduleInfo == self.moduleInfo: + self.setTrayIcon(1) + # Note: it is often during a playString operation that the + # "stop moving" command occurs + natlink.playString('{'+self.curDirection+'}') + else: + self.cancelMode() + elif self.curMode == 2: + self.moveMouse(self.curDirection,self.curPixels) + + # This handles the nudgeMouse rule. We want to extract the direction + # and the count or amount. + + def gotResults_nudgeMouse(self,words,fullResults): + self.cancelMode() + direction = findKeyWord(words,self.listDefn['direction']) + count = findKeyWord(words,self.listDefn['count']) + amount = findKeyWord(words,amountDict.keys()) + if count: + count = string.atoi(count) + elif amount: + count = amountDict[amount] + self.moveMouse(direction,count) + + # This handles the mouseButton rule. We want to extract the button + # name (if specified) and whether this is a single or double click. + + def gotResults_mouseButton(self,words,fullResults): + self.cancelMode() + which = findKeyWord(words,['left','right','middle']) + if not which: which = 'left' + if 'double' in words: count = 2 + else: count = 1 + buttonClick(which,count) + + # This handles the startMoving rule. We only need to extract the + # direction. To turn on cursor movement mode we need to install a + # timer callback (warning: this is global) and set the recognition + # state to be exclusively from the rule . The cursor only + # moves in the timer callback itself. + + def gotResults_startMoving(self,words,fullResults): + self.cancelMode() + direction = findKeyWord(words,self.listDefn['direction']) + self.curMode = 1 + self.curDirection = direction + self.setTrayIcon(0) + self.moduleInfo = natlink.getCurrentModule() + self.curSpeed = defaultMoveSpeed + self.lastClock = time.clock() + natlink.setTimerCallback(self.onTimer,defaultMoveSpeed) + self.haveCallback = 1 + self.activateSet(['nowMoving'],exclusive=1) + + # This handles the nowMoving rule. We want to extract the keyword which + # tells us what to do. + + def gotResults_nowMoving(self,words,fullResults): + direction = findKeyWord(words,self.listDefn['direction']) + if direction: + self.curDirection = direction + self.setTrayIcon(0) + elif 'stop' in words: + self.cancelMode() + elif 'faster' in words: + speed = int(self.curSpeed / moveRateChange) + if 'much' in words: + speed = int(speed / (moveRateChange*moveRateChange)) + if speed < 50: speed = 50 + self.curSpeed = speed + natlink.setTimerCallback(self.onTimer,speed) + elif 'slower' in words: + speed = int(self.curSpeed * moveRateChange) + if 'much' in words: + speed = int(speed * (moveRateChange*moveRateChange)) + if speed > 4000: speed = 4000 + self.curSpeed = speed + natlink.setTimerCallback(self.onTimer,speed) + + # This handles the startMousing rule. We only need to extract the + # direction. To turn on cursor movement mode we need to install a + # timer callback (warning: this is global) and set the recognition + # state to be exclusively from the rule . The cursor only + # moves in the timer callback itself. + + def gotResults_startMousing(self,words,fullResults): + self.cancelMode() + direction = findKeyWord(words,self.listDefn['direction']) + self.curMode = 2 + self.curDirection = direction + self.curSpeed = defaultMouseSpeed + self.curPixels = defaultMousePixels + self.lastClock = time.clock() + natlink.setTimerCallback(self.onTimer,defaultMouseSpeed) + self.haveCallback = 1 + self.activateSet(['nowMousing'],exclusive=1) + + # This handles the nowMousing rule. We want to extract the keyword which + # tells us what to do. + + def gotResults_nowMousing(self,words,fullResults): + direction = findKeyWord(words,self.listDefn['direction']) + if direction: + self.curDirection = direction + elif 'stop' in words: + self.cancelMode() + elif 'faster' in words: + speed = int(self.curSpeed / moveRateChange) + pixels = self.curPixels + while speed < 50: + speed = speed * 2 + pixels = pixels * 2 + if pixels > 10: pixels = 10 + self.curSpeed = speed + self.curPixels = pixels + natlink.setTimerCallback(self.onTimer,speed) + elif 'slower' in words: + speed = int(self.curSpeed * moveRateChange) + pixels = self.curPixels + while pixels > defaultMousePixels and speed >= 2*50: + speed = speed / 2 + pixels = pixels / 2 + if speed > 2000: speed = 2000 + self.curSpeed = speed + self.curPixels = pixels + natlink.setTimerCallback(self.onTimer,speed) + + # This turns on the tray icon depending on the movement direction. + # self.iconState is used to toggle the image to animate the icon. + def setTrayIcon(self,toggleIcon): + iconName = self.curDirection + toolTip = 'moving '+self.curDirection + if not toggleIcon or self.iconState: + self.iconState = 0 + else: + self.iconState = 1 + iconName = iconName + '2' + natlink.setTrayIcon(iconName,toolTip,self.onTrayIcon) + + # This is called if the user clicks on the tray icon. We simply cancel + # movement in all cases. + def onTrayIcon(self,message): + self.cancelMode() + +# This is a simple utility subroutine. It takes two lists of words and +# returns the first word it finds which is in both lists. We use this to +# extract special words (like the direction) from recognition results. + +def findKeyWord(list1,list2): + for word in list1: + if word in list2: + return word + return None + +# +# Here is the initialization and termination code. See wordpad.py for more +# comments. +# + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None + diff --git a/SampleMacros/OriginalSampleMacros/_repeatthat.py b/SampleMacros/OriginalSampleMacros/_repeatthat.py new file mode 100644 index 00000000..b85a7886 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_repeatthat.py @@ -0,0 +1,67 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 2000 by Joel Gould +# +# This is the implementation of "repeat that". In this implementation, you +# can say "repeat that" or "repeat that N times" to repeat the last +# recognition. +# + +import natlink +from natlink.natlinkutils import * + +lastResult = None + +class CatchAllGrammar(GrammarBase): + + gramSpec = """ + exported = {emptyList}; + """ + + def initialize(self): + self.load(self.gramSpec,allResults=1) + self.activateAll() + + def gotResultsObject(self,recogType,resObj): + global lastResult + if recogType == 'reject': + lastResult = None + elif resObj.getWords(0)[:2] != ['repeat','that']: + lastResult = resObj.getWords(0) + +class RepeatGrammar(GrammarBase): + + gramSpec = """ + exported = repeat that + [ ( 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + 10 | 20 | 30 | 40 | 50 | 100 ) times ]; + """ + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + + def gotResults_start(self,words,fullResults): + global lastResult + if len(words) > 2: + count = int(words[2]) + else: + count = 1 + if lastResult: + for i in range(count): + natlink.recognitionMimic(lastResult) + +catchAllGrammar = CatchAllGrammar() +catchAllGrammar.initialize() +repeatGrammar = RepeatGrammar() +repeatGrammar.initialize() + +def unload(): + global catchAllGrammar + global repeatGrammar + if catchAllGrammar: + catchAllGrammar.unload() + catchAllGrammar = None + if repeatGrammar: + repeatGrammar.unload() + repeatGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample1.py b/SampleMacros/OriginalSampleMacros/_sample1.py new file mode 100644 index 00000000..a3f5748a --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample1.py @@ -0,0 +1,45 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created for my talk to the Boston Voice Users +# group on November 9, 1999. It is explained in my PowerPoint slides. +# +# _sample1.py +# +# This is a sample macro file with a single command. When NatSpeak has the +# focus, say "demo sample one". It should recognize the command and type: +# Heard macro "sample one". +# +# This file represents the simplest possible example of a Natlink macro. +# +# See also the variant _first_sample_docstring.py in the folder DisabledGrammars of Unimacro +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# +import sys +import os +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample one; + """ + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + + def gotResults_start(self,words,fullResults): + natlink.displayText('Heard macro "sample one"{enter}',0) + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample2.py b/SampleMacros/OriginalSampleMacros/_sample2.py new file mode 100644 index 00000000..9514eea7 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample2.py @@ -0,0 +1,55 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created for my talk to the Boston Voice Users +# group on November 9, 1999. It is explained in my PowerPoint slides. +# +# _sample2.py +# +# This is a sample macro file with a two commands. When NatSpeak has the +# focus, say "d\xe9mo sample two". It should recognize the command and type: +# Say "d\xe9mo sample two color (the word color will be in italics) +# +# Say "d\xe9mo sample two red" and it would recognize the command and type: +# The color is red (it types the name of the color you say) +# +# This file d\xe9monstrates having two commands in one grammar. Natlink knows +# which rule was recognized and calls the appropiate handling function +# (gotResults_xxx). This file also d\xe9monstrates using the actual words +# recognized to control the results (we type the name of the spoken color). +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# + + +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample two [ help ]; + exported = d\xe9mo sample two + ( red | blue | green | purple | black | white | yellow | + orange | magenta | cyan | gray ); + """ + + def gotResults_firstRule(self,words,fullResults): + natlink.playString('Say "demo sample two {ctrl+i}color{ctrl+i}"{enter}') + + def gotResults_secondRule(self,words,fullResults): + natlink.playString('The color is "%s"{enter}'%words[3]) + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample3.py b/SampleMacros/OriginalSampleMacros/_sample3.py new file mode 100644 index 00000000..431c0157 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample3.py @@ -0,0 +1,58 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created for my talk to the Boston Voice Users +# group on November 9, 1999. It is explained in my PowerPoint slides. +# +# _sample3.py +# +# This is a sample macro file used to d\xe9monstrate how Natlink calls result +# functions (gotResults_xxx) based on which words were recognized. +# +# When NatSpeak has the focus, say "d\xe9mo sample three now please". When the +# command is recognized, this code should type: +# Saw = ['d\xe9mo'] +# Saw = ['sample','three'] +# Saw = ['now','please'] +# +# Notice that gotResults_ruleOne is called twice because it is called for +# each continugous set of words in the recognition which come from that +# rule. gotResults_mainRule is never called because that rule has no words. +# See natlinkutils.py for more documentation. +# +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = ; + = d\xe9mo now please; + = sample three; + """ + + def gotResults_mainRule(self,words,fullResults): + natlink.playString('Saw = %s{enter}' % repr(words)) + + def gotResults_ruleOne(self,words,fullResults): + natlink.playString('Saw = %s{enter}' % repr(words)) + + def gotResults_ruleTwo(self,words,fullResults): + natlink.playString('Saw = %s{enter}' % repr(words)) + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample4.py b/SampleMacros/OriginalSampleMacros/_sample4.py new file mode 100644 index 00000000..eecee8a8 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample4.py @@ -0,0 +1,40 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# _sample4.py +# Sample macro file d\xe9mostrating mouse movement. +# +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample four; + """ + + def gotResults_start(self,words,fullResults): + # execute a control-left drag down 30 pixels + x,y = natlink.getCursorPos() + natlink.playEvents( [ (wm_keydown,vk_control,1), + (wm_lbuttondown,x,y), + (wm_mousemove,x,y+30), + (wm_lbuttonup,x,y+30), + (wm_keyup,vk_control,1) ] ) + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample5.py b/SampleMacros/OriginalSampleMacros/_sample5.py new file mode 100644 index 00000000..770e00a4 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample5.py @@ -0,0 +1,69 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created for my talk to the Boston Voice Users +# group on November 9, 1999. It is explained in my PowerPoint slides. +# +# _sample5.py +# +# This sample file d\xe9monstrates how to use the clipboard to communicate +# between your application and Python code. To use this macro, make sure +# that NatSpeak is active and that there is text in the window. Position +# the caret inside a word on the screen and say "d\xe9mo sample five". This +# code will then reverse the letters in the word containing the caret. +# +# You can say "d\xe9mo sample five 3 words" and this code will reverse three +# words ending with the word which contains the caret. +# +# Notice that we send keystrokes to select the words in NatSpeak. Then we +# send the keystroke ctrl+c to copy the selected word to the clipboard. The +# function natlink.getClipboard() give this code access to the clipboard. We +# reverse the text and send it back to the application (by typing it). +# +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample five + [ (1 | 2 | 3 | 4) words ]; + """ + + def gotResults_start(self,words,fullResults): + # figure out how many words + if len(words) > 3: + count = int(words[3]) + else: + count = 1 + # select that many words + natlink.playString('{ctrl+right}{left}') + natlink.playString('{ctrl+shift+left %d}'%count) + natlink.playString('{ctrl+c}') + text = natlink.getClipboard() + # reverse the text + newText = reverse(text) + natlink.playString(newText) + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + +def reverse(text): + newText = '' + for char in text: + newText = char + newText + return newText + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample6.py b/SampleMacros/OriginalSampleMacros/_sample6.py new file mode 100644 index 00000000..4aba7b3f --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample6.py @@ -0,0 +1,61 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created 2020-02-01 by Quintijn Hoogenboom +# It does the same as _sample2, but now with a list of colors +# +# _sample6.py +# +# This is a sample macro file with a two commands. When NatSpeak has the +# focus, say "d\xe9mo sample six". It should recognize the command and type: +# Say "d\xe9mo sample six color (the word color will be in italics) +# or "d\xe9mo sample six color1 color2 .... (the colors will be shown) +# Say "d\xe9mo sample six red" and it would recognize the command and type: +# The color is red (it types the name of the color you say) +# +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# + + +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample six [ help ]; + exported = d\xe9mo sample six {color}+; + """ + + def gotResults_firstRule(self,words,fullResults): + natlink.playString('Say "d\xe9mo sample six {ctrl+i}color{ctrl+i}"{enter}') + + def gotResults_secondRule(self,words,fullResults): + colors = words[3:] + if len(colors) == 1: + color = colors[0] + natlink.playString('The color is "%s"{enter}'% color) + else: + natlink.playString('The color are "%s"{enter}'% ', '.join(colors)) + + def initialize(self): + print('.... _sample6 loading') + self.load(self.gramSpec) + print('validLists: ', self.validLists) + print('validRules: ', self.validRules) + self.setList('color', ['red', 'blue', 'green', 'purple', 'white', 'yellow', 'orange', 'magenta', 'cyan', 'gray']) + print("list color set, loading ready....") + self.activateAll() + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + + + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample7.py b/SampleMacros/OriginalSampleMacros/_sample7.py new file mode 100644 index 00000000..8f6e2fa7 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample7.py @@ -0,0 +1,45 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created 2020-02-01 by Quintijn Hoogenboom +# It does the same as _sample2, but now with a list of colors +# +# _sample7.py +# +# This is more a manual test, to see if various list and optional rules work. +# + +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = mimic runone; + exported = mimic two {colors}; + exported = mimic four [{colors}]+; + exported = mimic six {colors}+; + exported = mimic seven ; + exported = mimic eight +; + = painting ; + = house | tent | church | tower; + """ + def initialize(self): + self.load(self.gramSpec) + self.setList('colors', ['red', 'blue', 'green', 'purple', 'white', 'yellow', 'orange', 'magenta', 'cyan', 'gray']) + self.activateAll() + + def gotResultsObject(self, recogType, resObj): + print('sample 7, got: %s'% resObj.getWords(0)) + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + + + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample8.py b/SampleMacros/OriginalSampleMacros/_sample8.py new file mode 100644 index 00000000..b6a55ab2 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample8.py @@ -0,0 +1,44 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 2000 by Joel Gould +# Portions (c) Copyright 2000 by Dragon Systems, Inc. +# +# does not work any more (2020) +# Put in MacroSystem folder and toggle the microphone. +# Write "d\xe9mo" to force command recognition. +# + +import string +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + imported; + imported; + exported = d\xe9mo sample eight [ stop ]; + exported = d\xe9mo sample eight spell [ stop ]; + """ + + def gotResults_dgndictation(self,words,fullResults): + words.reverse() + text = string.join(words) + natlink.playString(' ' + text) + + def gotResults_dgnletters(self,words,fullResults): + words = map(lambda x: x[:1], words) + text = string.join(words, '') + natlink.playString(' ' + text) + + def initialize(self): + self.load(self.gramSpec) + self.activateAll() + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/_sample9.py b/SampleMacros/OriginalSampleMacros/_sample9.py new file mode 100644 index 00000000..d91c0467 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sample9.py @@ -0,0 +1,24 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# _sleeping.py +# Simply by having this file in the Python command and control subsystem +# directory, we turn the microphone on and put the system in a sleeping +# state when NatSpeak first loads +# +# April 25, 1999 +# - packaged for external release +# +# March 3, 1999 +# - initial version +# + +import natlink + +natlink.setMicState('sleeping') + +def unload(): + # must be defined, we do not need to do anything here + pass diff --git a/SampleMacros/OriginalSampleMacros/_sleeping.py b/SampleMacros/OriginalSampleMacros/_sleeping.py new file mode 100644 index 00000000..d91c0467 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/_sleeping.py @@ -0,0 +1,24 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# _sleeping.py +# Simply by having this file in the Python command and control subsystem +# directory, we turn the microphone on and put the system in a sleeping +# state when NatSpeak first loads +# +# April 25, 1999 +# - packaged for external release +# +# March 3, 1999 +# - initial version +# + +import natlink + +natlink.setMicState('sleeping') + +def unload(): + # must be defined, we do not need to do anything here + pass diff --git a/SampleMacros/OriginalSampleMacros/excel_sample7.py b/SampleMacros/OriginalSampleMacros/excel_sample7.py new file mode 100644 index 00000000..4995e8ef --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/excel_sample7.py @@ -0,0 +1,86 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created for my talk to the Boston Voice Users +# group on November 9, 1999. It is explained in my PowerPoint slides. +# +# excel_sample7.py +# +# Example of using OLE Automation to control Excel from Python macros. Start +# Excel and put the names of colors (in lower case) in some of the cells. +# Then say "d\xe9mo sample seven" and all the cells in your spreadsheet which +# contain a color which change to that color. +# +# The OLE automation code uses the Win32com package. It allows you to +# access Excel just like you were using Visual Basic. See the web site at +# http://starship.python.net/crew/pirx/spam7/ for more details os using OLE +# automation from Python. +# + +import natlink +from natlink.natlinkutils import * + +import string +import win32api +import win32com.client +consts = win32com.client.constants + +colorMap = { + 'black':win32api.RGB(0,0,0), + 'dark red':win32api.RGB(128,0,0), + 'dark green':win32api.RGB(0,128,0), + 'dark yellow':win32api.RGB(128,128,0), + 'dark blue':win32api.RGB(0,0,128), + 'dark magenta':win32api.RGB(128,0,128), + 'dark cyan':win32api.RGB(0,128,128), + 'dark gray':win32api.RGB(128,128,128), + 'light gray':win32api.RGB(192,192,192), + 'light red':win32api.RGB(255,0,0), + 'light green':win32api.RGB(0,255,0), + 'light yellow':win32api.RGB(255,255,0), + 'light blue':win32api.RGB(0,0,255), + 'light magenta':win32api.RGB(255,0,255), + 'light cyan':win32api.RGB(0,255,255), + 'white':win32api.RGB(255,255,255), + 'gray':win32api.RGB(192,192,192), + 'red':win32api.RGB(255,0,0), + 'green':win32api.RGB(0,255,0), + 'yellow':win32api.RGB(255,255,0), + 'blue':win32api.RGB(0,0,255), + 'magenta':win32api.RGB(255,0,255), + 'cyan':win32api.RGB(0,255,255), +} + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample seven; + """ + + def initialize(self): + self.load(self.gramSpec) + + def gotBegin(self,moduleInfo): + winHandle=matchWindow(moduleInfo,'excel','Microsoft Excel') + if winHandle: + self.activateAll(window=winHandle) + + def gotResults_start(self,words,fullResults): + application=win32com.client.Dispatch('Excel.Application') + worksheet=application.Workbooks(1).Worksheets(1) + for row in range(1,50): + for col in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + cell=worksheet.Range(col+str(row)) + if colorMap.has_key(cell.Value): + cell.Font.Color=colorMap[cell.Value] + cell.Borders.Weight = consts.xlThick + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/natspeak_sample6.py b/SampleMacros/OriginalSampleMacros/natspeak_sample6.py new file mode 100644 index 00000000..118b301f --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/natspeak_sample6.py @@ -0,0 +1,54 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# This sample macro file was created for my talk to the Boston Voice Users +# group on November 9, 1999. It is explained in my PowerPoint slides. +# +# natspeak_sample6.py +# +# This macro file d\xe9monstrates how to write application specific code. We +# define two commands. "d\xe9mo sample six" can only be spoken to the NatSpeak +# main window. "d\xe9mo sample six font" can only be spoken to the NatSpeak +# font dialog. We make the second command exclusive so that all other +# commands are disabled when thefont dialog is active. +# + +import natlink +from natlink.natlinkutils import * + +class ThisGrammar(GrammarBase): + + gramSpec = """ + exported = d\xe9mo sample six [ main ]; + exported = d\xe9mo sample six font; + """ + + def gotBegin(self,moduleInfo): + windowId = matchWindow(moduleInfo,'natspeak','Dragon') + if windowId: + self.activate('mainRule',window=windowId,noError=1) + windowId = matchWindow(moduleInfo,'natspeak','Font') + if windowId: + self.activate('fontRule',exclusive=1,noError=1) + else: + self.deactivate('fontRule',noError=1) + self.setExclusive(0) + + def initialize(self): + self.load(self.gramSpec) + + def gotResults_mainRule(self,words,fullResults): + natlink.playString('Saw ') + + def gotResults_fontRule(self,words,fullResults): + natlink.playString('Saw ') + +thisGrammar = ThisGrammar() +thisGrammar.initialize() + +def unload(): + global thisGrammar + if thisGrammar: thisGrammar.unload() + thisGrammar = None diff --git a/SampleMacros/OriginalSampleMacros/trainuser.py b/SampleMacros/OriginalSampleMacros/trainuser.py new file mode 100644 index 00000000..8298b7e6 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/trainuser.py @@ -0,0 +1,435 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# trainuser.py +# This is a sample python script which will create a new trained user +# from a series of recorded sessions. +# + +######################################################################## +# +# This script takes the following things as input: +# +# (1) A set of NWV files which contain recordings made by speaking +# to Dragon NaturallySpeaking. +# +# An NWV file is a industry standard NIST wave file. Such files can be +# created by running the SaveWave program which comes with Dragon +# NaturallySpeaking then using NatSpeak normally. +# +# (2) A corresponding set of LST files which contain the transcripts +# for the recordings in the NWV files. There should be one LST file +# for every NWV file (with the same filename). The LST file must have +# one line for every separate utterance in the NWV file. That line +# should contain the transcript of the utterance. +# +# When you run SaveWave, it create a LST file which contains the recognized +# transacript. If there have been recognition errors this transacript +# will be wrong and need to be corrected before the LST file is used +# for training. +# +# To exclude an utterance from training (for example, because it was +# garbled), you should make the corresponding line in the LST file blank. +# +# The spelling of words in the transcripts in the LST file need to be +# modified in some cases. Specifically, a space character is used to +# delimit words so if there are any spaces inside of words (like the +# word "New York"), those spaces need to be converted into underscores. +# +# For completeness, underscores are converted into the two character +# sequence tilde-underscore (~_) and tilde characters are converted into +# the two character sequence tilde-tilde (~~). Finally, some older files +# use the two character sequence tilde-right parenthesisinstead of a single +# right parenthesis. This is supported but not required. +# +# (3) The name of a user to create. +# +# (4) The name of the base models to use to create the user. The base +# models can be found in the registry under the key: +# +# HKEY_LOCAL_MACHINE\Software\Dragon Systems\NaturallySpeaking\ +# Professional 3.0\System\Base Models +# +# For the Preferred version, the key contains the string "Preferred 3.0" +# instead of "Professional 3.0". International version will also have +# a different edition name. +# +# For Professional 3.02 and 3.52 versions, the following base models +# are available: +# BestMatch Model +# Standard Model +# +# (5) The name of the base topic to use to create the user. The base +# topics can also be found in the registry in the same place except for +# the name of the last key: +# +# ...\System\Base Topics +# +# For Professional 3.02 and 3.52 versions, the following base models +# are available: +# +# General English - BestMatch +# General English - BestMatch 64k+ +# General English - Standard +# +######################################################################## + +import string +import sys # for stdout +import glob # file name parsing +import traceback # for printing exceptions +import os.path # for filename parsing + +import wavtools # for convertBack +import natlink +from natlink.natlinkutils import * + +# +# Miscelleanous additional global stuff +# + +TrainingError = 'TrainingError' + +#--------------------------------------------------------------------------- +# trainUser +# +# This is the main entry subroutine. It creates and trains a new user +# from recorded utterances and transcripts. + +def doTraining(fileSpecs,userName='',baseModel='',baseTopic=''): + if not userName: + userName = 'Created from Python' + + # + # Expand the file specification list into a list of files without + # extensions + # + + allFiles = [] + if type(fileSpecs) == type(''): + allFiles = expandFiles(fileSpecs) + else: + for fileSpec in fileSpecs: + allFiles = allFiles + expandFiles(fileSpec) + + if not len(allFiles): + print "No files found which match fileSpec:" + print " ",fileSpecs + raise TrainingError,'no files' + + # + # Create a new user. Make sure the new user is active. + # + + createNewUser(userName,baseModel,baseTopic) + + # + # Read the LST files from disk into Python arrays. As we read the LST + # files, we need to convert the format of words from underscores (using + # tilde as an escape character) into spaces. + # + # We build up a master array which contains one entry per input file. + # Each file entry is an array with one entry per utterance. Each + # utterance entry is an array of strings representing the words + # recognized. + # + + allWords = [] + for fileName in allFiles: + allWords.append(loadWords(fileName)) + + # + # The NIST wave files have to converted into NatSpeak result objects. + # The easiest way to do this is to build a dictation grammar and recognize + # all of the utterances in the NIST wave files. + # + + allResults = [] + for fileName in allFiles: + allResults.append(loadResults(fileName)) + + # + # Produce a single array which contains tuples representing the results + # object and the transcript. Skip over any results which have blank + # transcripts. Print an error if the two arrays sizes do not match + # + + combinedResults = [] + fileCount = len(allFiles) + for fileNum in range(fileCount): + lstSize = len(allWords[fileNum]) + nwvSize = len(allResults[fileNum]) + if lstSize != nwvSize: + print 'The number of utterances in', + print allFiles[fileNum]+'.nwv','('+str(nwvSize)+')', + print 'does not match', + print 'the number of lines in', + print allFiles[fileNum]+'.lst','('+str(lstSize)+')' + for resNum in range(lstSize): + words = allWords[fileNum][resNum] + resObj = allResults[fileNum][resNum] + if len(words) and resObj: + combinedResults.append((words,resObj)) + + # + # Perform calibration on the first N utterances (20 maximum) + # + + trainingPass(combinedResults[:20],'calibrate') + + # + # Perform batch adaptation on the entire set of utterances. + # + + trainingPass(combinedResults,'longtrain') + + # + # Perform training on the entire set of utterances. + # + + trainingPass(combinedResults,'batchadapt') + + # + # Save the user. + # + + print 'Saving the user...' + natlink.saveUser() + print 'All done.' + +#--------------------------------------------------------------------------- +# expandFile +# +# We take a single file specification and use the glob function to look up +# all files in the filesystem which match that specification. Then for each +# filename we remove the extension and add that filename to a list which is +# returned. + +def expandFiles(fileSpec): + # force the extension of the fileSpec + fileSpec = os.path.splitext(fileSpec)[0] + '.nwv' + + files = [] + for fileName in glob.glob(fileSpec): + files.append( os.path.splitext(fileName)[0] ) + return files + +#--------------------------------------------------------------------------- +# createNewUser +# +# Create, then open a new user in NatSpeak. When debugging or using the +# most common error is if the user already exists and is currently active. +# In that case we just make sure that it has not been trained. (Note that +# we can not easily detect whether the user exists but is not active with +# the current natlink code but an error will be thrown). + +def createNewUser(userName,baseModel,baseTopic): + if natlink.getCurrentUser()[0] == userName: + print 'Training existing user:',userName + else: + print 'Creating user:',userName + if baseModel: + print ' using baseModel:',baseModel + else: + print ' using baseModel: (default)' + if baseTopic: + print ' using baseTopic:',baseTopic + else: + print ' using baseTopic: (default)' + natlink.createUser(userName,baseModel,baseTopic) + natlink.openUser(userName) + if natlink.getUserTraining(): + print 'Error: user is already at least partially trained.' + raise TrainingError,'user is trained' + +#--------------------------------------------------------------------------- +# loadWords +# +# We take the name of a single input file (without extension), open the +# corresponding LST file and extract all the transcripts. We return a +# list of lines. Each line being a list of words. Each word is converted +# from underscore/tilde encoded format to normal spelling with optional +# internal spaces. +# +# Note 1: we use a LS2 file if it exists instead of a LST file. +# Note 2: is the transcript is *deleted* then we make the transcript blank +# (also [reject]) + +def loadWords(fileName): + if os.access(fileName+'.ls2',4): + fileName = fileName + '.ls2' + elif os.access(fileName+'.lst',4): + fileName = fileName + '.lst' + else: + raise TrainingError,'Unable to find LST or LS2 file for %s'%fileName + + print 'Loading words from '+fileName + allWords = [] + for line in open(fileName,'r').readlines(): + if not line or string.lower(line[:7]) in ['*delete','[reject']: + allWords.append([]) + continue + words = [] + for word in string.split(line): + words.append(wavtools.convertBack(word)) + allWords.append(words) + return allWords + +#--------------------------------------------------------------------------- +# Here we create an array of results objects for every + +def loadResults(fileName): + print 'Recognizing from '+fileName+'.nwv...' + print '>',' '*70, + grammar = DictationGrammar() + grammar.initialize() + + # Here we force recognition to happen. The results objects will be + # created for each utterance in the wave file and passed to the callback + # function (callback.func) where they will be collected in an array + natlink.inputFromFile(fileName+'.nwv') + + print '' + grammar.unload() + return grammar.results + +# This is a dictation grammar. We set the special allResults flag on the +# load call which allows us to get results objects even when the recognition +# is rejected or belongs to another grammar. Then we set our grammar to be +# exclusive to make sure that no other grammar in the system gets our +# recognition results. +# +# When we get the results object after every recognition, we add it to an +# array. + +class DictationGrammar(GrammarBase): + + # this is the specification of a normal dictation grammar (the special + # global rule called dgndictation implements dictation). + + gramSpec = """ + imported; + exported = ; + """ + + # during initialization we load the grammar and activate it + + def initialize(self): + self.results = [] + if not self.load(self.gramSpec,allResults=1): + return None + self.activate('Start',exclusive=1) + + # this callback is where we get the results object + + def gotResultsObject(self,recogType,resObj): + self.results.append(resObj) + if recogType != 'reject': + words = string.ljust(string.join(resObj.getWords(0)),70) + print '\b'*72, + print words[0:70], + +#--------------------------------------------------------------------------- +# trainingPass +# +# This does a training pass on all the utterances or a subset of the +# utterances. This routine is suitable for all three types of training +# passes + +def trainingPass(combinedResults,trainingType): + count = len(combinedResults) + print 'Performing %s on %d utterances... ' % (trainingType,count), + natlink.startTraining(trainingType) + for result in combinedResults: + result[1].correction(result[0]) + count = count - 1 + sys.stdout.write('\b\b\b\b\b\b\b\b\b\b%5d left' % count) + natlink.finishTraining() + print '' + +#--------------------------------------------------------------------------- +# parseArgs +# +# Parse command line arguments. Prints help message if necessary. +# Returns fileSpecs,userName,baseModel,baseTopic where fileSpecs will +# be None to exit. + +def parseArgs(args): + if not( 1<=len(args)<=4 ) or args[0]=='?': + print '' + print 'Trainuser.py creates a new NatSpeak user by simulating enrollment' + print ' using recordings with corrected transcripts.' + print '' + print 'Usage: trainuser.py fileSpecs [userName [baseModel [baseTopic]]]' + print ' fileSpecs = name of NWV source file(s), wildcards allowed' + print ' userName = name for user to be created' + print ' baseModel = name of base acoustic models to use, one of:' + print ' "Standard Model" or standard' + print ' "BestMatch Model" or bestmatch' + print ' "BestMatch III Model" or bestmatch3' + print ' "Student Standard Model" or student' + print ' baseTopic = name of base vocabulary to use, one of:' + print ' "US General English - Standard" or standard' + print ' "US General English - BestMatch" or bestmatch' + print ' "US General English - BestMatch Plus" or plus' + print ' "Student General English - Standard"' + print ' "Student General English - BestMatch" or student' + print ' "Student General English - BestMatch Plus"' + return None,None,None,None + + fileSpecs = args[0] + if len(args)<2: + return fileSpecs,'','','' + + userName = args[1] + if len(args)<3: + return fileSpecs,userName,'','' + + baseModelDict = { + 'standard':'Standard Model', + 'bestmatch':'BestMatch Model', + 'bestmatch3':'BestMatch III Model', + 'student':'Student Standard Model', + } + baseModel = baseModelDict.get(args[2],args[2]) + if len(args)<3: + return fileSpecs,userName,baseModel,'' + + baseTopicDict = { + 'standard':'US General English - Standard', + 'bestmatch':'US General English - BestMatch', + 'plus':'US General English - BestMatch Plus', + 'student':'Student General English - BestMatch', + } + baseTopic = baseTopicDict.get(args[3],args[3]) + + return fileSpecs,userName,baseModel,baseTopic + +#--------------------------------------------------------------------------- +# run +# +# This is the main entry point. It will connect to NatSpeak and train +# a new user. In the case of an error, it will cleanly disconnect from +# NatSpeak and print the exception information, + +def run(args): + fileSpecs,userName,baseModel,baseTopic = parseArgs(args) + if not fileSpecs: + return + try: + natlink.natConnect() + doTraining(fileSpecs,userName,baseModel,baseTopic) + natlink.natDisconnect() + except TrainingError,message: + natlink.natDisconnect() + print '' + print 'TrainingError:',message + except: + natlink.natDisconnect() + print '' + traceback.print_exc() + +if __name__=='__main__': + run(sys.argv[1:]) diff --git a/SampleMacros/OriginalSampleMacros/transcribe.py b/SampleMacros/OriginalSampleMacros/transcribe.py new file mode 100644 index 00000000..eaa24cfa --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/transcribe.py @@ -0,0 +1,212 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# transcribe.py +# This is a sample python script which will transcribe a directory of +# compressed wave files from a Dragon NaturallyMobile recorder. +# +# June 14, 1999 +# - initial version +# +# TODO rename temp files with better file names and delete old ones from +# previous runs. +# + +import os +import string +import glob +import struct +import tempfile +import calendar +import natlink +import mobiletools +from natlink.natlinkutils import * + +TranscribeError = 'TranscribeError' + +# +# For simplicity in this sample script, I hardcoded the input names. +# + +# finished: 'd:\\Grand Canyon Recordings\\Card 1\\File 0*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 1\\File 1*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 1\\File [2-3]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 1\\File 4*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 1\\File [5-6]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [0]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [1]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [2]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [3]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [4]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [5]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [6-7]*.sri' +# finished: 'd:\\Grand Canyon Recordings\\Card 2\\File [8-9]*.sri' +fileSpecs = 'd:\\Grand Canyon Recordings\\Card 3\\File *.sri' + +#--------------------------------------------------------------------------- + +def doTranscription(): + + # compute a list of all files to be processed + allFiles = glob.glob(fileSpecs) + + # open each file and extract the header information; convert the list of + # files into a list of tuples of this information plus the filename + fileInfo = [] + for fileName in allFiles: + sriFile = open(fileName,'rb') + fileInfo.append( (fileName,) + decodeHeader(sriFile) ) + sriFile.close() + + # sort this array + fileInfo.sort(sortFunc) + + # process each file + for file in fileInfo: + processFile(file[0]) + +#--------------------------------------------------------------------------- +# This fill decode the header of an open SRI file. Details of the SRI file +# header are from the VoiceIt documentation. It returns a tuple with the +# following information: +# +# creation year (i.e. 1992) +# creation month (1-12) +# creation day (1-31) +# creation hour (0-23) +# creation minute (0-59) +# creation second (0-59) +# number of frames + +sriHeaderSize = 212 +sriFrameSize = 18 +wavFrameSize = 240 + +def decodeHeader(sriFile): + header = sriFile.read(sriHeaderSize) + + headerSize,headerID,unused,fileFormat,coderFormat = struct.unpack("H3sBBB",header[0:8]) + if headerSize != sriHeaderSize or headerID != 'SRI': + raise TranscribeError,'File is not a valid SRI file' + + year,month,day,hour,minute,second = struct.unpack("BBBBBB",header[12:18]) + year = year + 1992 + + packetSize,packetCount = struct.unpack("LL",header[32:40]) + if packetSize != sriFrameSize * 9 + 1: + raise TranscribeError,'Unexpected packet size in header' + + return (year,month,day,hour,minute,second,packetCount) + +#---------------------------------------------------------------------------= +# File sorting function. We sort based on date. + +def sortFunc(one,two): + for x in range(1,7): + if one[x] != two[x]: + return cmp(one[x],two[x]) + return 0 + +#--------------------------------------------------------------------------- +# Process one file + +def processFile(fileName): + + # this create a decoder object + sx96 = mobiletools.SX96Codec() + + # open the file and get the header information again (this has the side + # effect of skipping past the header) + sriFile = open(fileName,'rb') + year,month,day,hour,minute,second,packetCount = decodeHeader(sriFile) + + # compute the size of the data for the output file + frameCount = packetCount * 9 + outDataSize = frameCount * wavFrameSize + + # open an output wave file and write out a header + tempFileName = tempfile.mktemp() + '.wav' + wavFile = open(tempFileName,'wb') + writeHeader(wavFile,outDataSize) + + # iterate over each packet (9 frames) in the input file, convert the data + # and write the converted data into the output file + for i in range(packetCount): + for j in range(9): + frame = sriFile.read(sriFrameSize) + wavData = sx96.decode(frame) + wavFile.write(wavData) + # discard the extra packet descriptor byte + sriFile.read(1) + + wavFile.close() + wavFile = None + + sriFile.close() + sriFile = None + + # now we transcribe this file in NatSpeak + natlink.execScript('AppBringUp "NatSpeak"') + natlink.playString(formatDate(year,month,day,hour,minute,second)) + natlink.inputFromFile(tempFileName) + +#--------------------------------------------------------------------------- +# Create a standard Microsoft (RIFF) wave file header +# +# DWORD mainChunkType 'RIFF' +# DWORD mainChunkSize size of data plus 34 +# DWORD formType 'WAVE' +# +# DWORD fmtChunkType 'fmt ' +# DWORD fmtChunkSize size of this chunk (18) +# WORD wFormatTag 1 (WAVE_FORMAT_PCM) +# WORD nChannels 1 +# DWORD nSamplesPerSec 11025 +# DWORD nAvgBytesPerSec 22050 +# WORD nBlockAlign 2 +# WORD wBitsPerSample 16 +# WORD cbSize 0 +# +# DWORD dataChunkType 'data' +# DWORD dataChunkSize size of data + +def writeHeader(wavFile,dataSize): + chunk1 = struct.pack("4sL4s",'RIFF',dataSize+34,'WAVE') + chunk2 = struct.pack("4sLHHLLHHH",'fmt ',18,1,1,11025,22050,2,16,0) + chunk3 = struct.pack("4sL",'data',dataSize) + header = chunk1 + chunk2 + chunk3 + wavFile.write(header) + +#--------------------------------------------------------------------------- +# Formats the header on a transacribed block and returns a string. + +def formatDate(year,month,day,hour,minute,second): + monthName = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'] + dayName = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'] + weekDay = calendar.weekday(year,month,day) + hourName = 'am' + if hour > 12: + hour = hour - 12 + hourName = 'pm' + return '\n\n%s, %s %d, %d:%.2d%s) ' % (dayName[weekDay],monthName[month],day,hour,minute,hourName) + +#--------------------------------------------------------------------------- +# run +# +# This is the main entry point. It will connect to NatSpeak and train +# a new user. In the case of an error, it will cleanly disconnect from +# NatSpeak and print the exception information, + +def run(): + if not natlink.isNatSpeakRunning(): + raise TranscribeError,'NatSpeak should be running before transcribing files' + try: + natlink.natConnect() + doTranscription() + finally: + natlink.natDisconnect() + +if __name__=='__main__': + run() diff --git a/SampleMacros/OriginalSampleMacros/wiki.css b/SampleMacros/OriginalSampleMacros/wiki.css new file mode 100644 index 00000000..62de41f7 --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/wiki.css @@ -0,0 +1,1202 @@ + +a.Anchor, a.Anchor:hover, a.Anchor:active +{ + text-decoration: none; +} + +.CreateTopicWarning +{ + padding-right: 1px; + padding-left: 1px; + padding-bottom: 1px; + color: white; + padding-top: 1px; + background-color: #cc3333; +} + + +.GenericRow +{ + +} + +.GenericCell +{ + font-size: .88em; + border-collapse: collapse; + border: #909090 1px solid; +} + +.GenericKVKey +{ + font-size: .75em; + border-collapse: collapse; + border: #909090 1px solid; + background-color: #e0e0e0; + font-weight: bold; +} + +.GenericKVValue +{ + font-size: .75em; + border-collapse: collapse; + border: #909090 1px solid; +} + + +.GenericKVTable +{ + padding-top: 6px; + padding-bottom: 6px; + background-color: #f8f8c0; +} + +.GenericTable +{ + padding-top: 6px; + padding-bottom: 6px; + background-color: #f8f8f8; +} + +.GenericHeaderRow +{ + +} + +.GenericHeaderCell +{ + background-color: #e0e0e0; + font-size: .88em; + font-weight: bold; + border-collapse: collapse; + border: #909090 1px solid; +} + +.GenericEmptyHeaderCell +{ + font-size: .88em; + font-weight: bold; + border-collapse: collapse; + +} + +.GenericPageTitle +{ + background-color: royalblue; + color: white; + font-size: 1.3em; + font-weight: bold; +} + +.GenericMenuTable +{ + padding-top: 1li; + padding-bottom: 1li; + background-color: darkblue; + border: darkblue 1px solid; +} + +.GenericMenuHeaderRow +{ +} + +.GenericMenuHeaderLeft +{ + font-size: .88em; + font-weight: bold; + color: white; +} + +.GenericMenuHeaderText +{ + font-size: .88em; + font-weight: bold; + color: white; +} + +.GenericMenuRow +{ +} + +.GenericMenuLeft +{ + font-size: .88em; +} + +.GenericMenuText +{ + background-color: royalblue; + color: white; + font-size: .88em; +} + +.GenericMenuText a +{ + background-color: royalblue; + color: white; +} + +.GenericMenuText a:hover +{ + background-color: yellow; + color: blue; +} + +.GenericPageMenuColumn +{ + background-color: gray; + +} + +.UpdateDivider +{ + font-size: .8em; + background-color: Silver; + color: Black; + font-weight: bold; +} + +.UpdateKey +{ + font-size: .8em; + background-color: #e0e0e0; + color: Black; +} + +.UpdateValue +{ + font-size: .8em; + color: Black; +} + +.CompositeCacheRuleChild +{ + font-size: .8em; + color: Black; +} + + + +.BorderLeft +{ + background-color: lemonchiffon; + border-right: solid 1px silver; + margin-right: 2em; + padding: 2px; +} + +.Rule +{ + border-top: gray 1px solid; +} + +.SubscriptionNamespace +{ + font-weight: bold; + text-decoration: underline; +} + +.BorderRight +{ + background-color: lemonchiffon; + border-left: solid 1px silver; + margin-left: 2em; + padding: 2px; +} + +.BorderTop +{ + background-color: lemonchiffon; + border-bottom: solid 1px silver; + margin-bottom: 2em; + padding: 2px; +} + +.BorderBottom +{ + background-color: lemonchiffon; + border-top: solid 1px silver; + margin-top: 2em; + padding: 2px; +} + + + +.ErrorMessage +{ + margin: 1px; + border: #303030 1px solid; +} + +.BlacklistedWarning +{ + margin: 1px; + border: #303030 1px solid; + color: white; + background-color: #e0e0e0; +} + +.ErrorMessageTitle +{ + font-weight: bold; + background-color: #cc0000; + color: white; +} + +.CacheKey +{ + font-size: .8em; + color: Black; +} + +.CacheRules +{ + font-size: .8em; + background-color: #e0e0e0; + color: Black; +} + +.CacheValue +{ + font-size: .8em; +} + + +.ErrorMessageBody +{ + color: white; + background-color: #990033; +} + + +.Sidebar +{ + padding-top: 12px; + padding-bottom: 12px ; + padding-right: 12px; + background: lemonchiffon; + width: 200px; +} + +.SidebarTile +{ + border-right: #404040 1px solid; + padding-right: 2px; + border-top: #404040 1px solid; + padding-left: 2px; + font-size: .8em; + color: black; + margin-bottom: 6px; + padding-bottom: 2px; + border-left: #404040 1px solid; + width: 100%; + padding-top: 2px; + border-bottom: #404040 1px solid; +} + +.ExternalLink +{ + margin: 1px; + border-right: #d0d0d0 1px solid; + border-bottom: #d0d0d0 1px solid; + border-top: #d0d0d0 1px solid; + border-left: #d0d0d0 3px solid; + background-color: #ffffd0; +} + +.NamespaceTable +{ +} + +.FieldTable +{ +} + +.FieldName +{ + font-weight: bold; +} + +.FieldValue +{ +} + +.FieldError +{ + color: Red; + font-weight: bold; +} + +.FieldHelp +{ + font-size: .74em; + color: Gray; + padding-bottom: 3px; +} + +.NamespaceTitleLine +{ + color: white; + background-color: cornflowerblue; + font-weight: bold; +} + +.NamespaceTitleLine a +{ + color: white; + text-decoration: underline; + background-color: cornflowerblue; + font-weight: bold; +} + +.NamespaceTitleLine a:hover +{ + background-color: white; + text-decoration: underline; + color: cornflowerblue; + font-weight: bold; +} + +.AdvancedSearchLink +{ +} + +.SearchBox +{ +} + +.SidebarTileTitle +{ + background-image: url(images/tile-title.gif); + background-repeat: repeat-x; + color: #e0e0e0; +} + +.SidebarTileTitle a +{ + color: #ffcc99; + text-decoration: none; +} + +.SidebarTileTitle a:hover { + color: #FBE52F; + text-decoration:underline; +} + +.SidebarTileBody +{ + color: #404040; +} + +.SidebarTileBody a +{ + color: mediumblue; + text-decoration: none; +} + +.SidebarTileBody a:hover { + color: mediumblue; + text-decoration:underline; +} + +.SidebarTileBody td +{ + color: #404040; +} + +.CommandTable +{ + margin: 8px; +} + +.CommandTable a +{ + color: #ffcc99; + text-decoration: none; +} + +.CommandTable a:hover { + color: #FBE52F; + text-decoration:underline; +} + +.CommandTable td +{ + color: white; +} + +.NewsletterBody +{ +} + +.NewsletterInterior +{ + margin: 12px; +} + +.NewsletterName +{ + padding: 4px; + font-size: 2em; + background: #0099cc; + margin-bottom: 6px; + color: white; +} + +.NewsletterTOCHeader +{ + font-weight: bold; + text-decoration: underline; + padding-top: 14px; + margin-bottom: 6px; +} + +.NewsletterTOCFinsher +{ + font-size: 4pt; + background: #0066cc; +} + +.NewsletterTOCTable +{ + border: 1px solid silver; +} + +.NewsletterTOCHeaderCell +{ + font-size: 10pt; + border: 1px solid silver; + background: #e0e0e0; +} + +.NewsletterTOCBodyCell +{ + font-size: 10pt; + border: 1px solid silver; + color: gray; + background: #f8f8f8; +} + +.NewsletterTopicChangers +{ +} + +.NewsletterName a +{ + color: White; +} + +.NewsletterName a:hover +{ + color: White; + text-decoration:underline; +} + + +.NewsletterTopicBodyOLD +{ + margin: 0.1in; + padding: 0.05in; + border: 1px solid silver; + background: #f8f8f8; + height: 200px; + overflow: scroll; +} + +.NewsletterTopicBody +{ + border: 3px solid #0099cc; + background: #f8f8f8; + height: 200px; + padding: 6px; + overflow: scroll; +} + + +.NewsletterTopicName +{ + border-right: navy 1px solid; + padding-right: 1px; + margin-top: 16px; + border-top: navy 1px solid; + padding-left: 1px; + font-size: 1.4em; + background: #0099cc; + padding-bottom: 1px; + border-left: navy 1px solid; + color: navy; + padding-top: 1px; + border-bottom: navy 1px solid; +} + +.NewsletterTopicName a +{ + color: white; +} + +.NewsletterTopicName a:hover +{ + color: white; + text-decoration:underline; +} + + +.NewsletterDescription +{ + font-size: 1em; + margin-bottom: 6px; +} + +.NewsletterInformationHeader +{ + margin-top: 12px; + border-top: 1px solid silver; +} + +.NewsletterInfoNewsletterName +{ +} + +.NewsletterInfoTopics +{ +} + +.NewsletterDeliveredBy +{ + font-style: italic; +} + + +.Dialog +{ + padding: 10px; +} + +.DialogTitle +{ + font-size: 1.2em; + font-weight: bold; +} + +.TopicBody +{ + /* BACKGROUND: url(/watermark.jpeg) white fixed no-repeat center center; */ + color: #303030; + padding-left: .2in; + padding-top: .01in; + padding-right: .2in; + padding-bottom: .2in; +} + + +.Main +{ + /* BACKGROUND: url(/watermark.jpeg) white fixed no-repeat center center; */ + height: 100%; + height: expression(MainHeight()); /* IE only, other browsers ignore expression */ + width: 100%; + width: expression(MainWidth()); + overflow: auto; + color: #303030; + padding: .2in; + border: solid 12px #404040; +} + +.EditMain +{ + BACKGROUND: white; + height: 100%; + height: expression(MainHeight()); + width: 100%; + width: expression(MainWidth()); + overflow: auto; + color: #303030; +} + +.PreviewMain { + padding: .1in; +} + + +.SearchMain { + background: white; + color: #303030; + font-size: 80%; + padding: .2in; + border: solid 12px #404040; +} + +.Menu +{ + border-right: #3333cc thin solid; + border-top: #66ccff thin solid; + display: none; + background: #ccffcc; + border-left: #66ccff thin solid; + cursor: hand; + padding: 2px; + color: blue; + border-bottom: #3333cc thin solid; + position: absolute; +} + +.ReadOnlyStripe +{ + color: red; + font-weight: bold; +} + +.MenuItemNormal +{ + background: #ccffcc; +} + +.MenuItemHover +{ + background: #3300cc; + color: #ffff99; +} + + +.SearchColumnHeading +{ + background: #003399; + color: white; + font-size: .8em; + border-top: solid 1px black; + border-left: solid 1px black; + border-right: solid 1px black; + border-bottom: solid 1px silver; + font-weight: bold; +} + +.SearchColumnFilter +{ + background: #003399; + font-size: .8em; + color: white; + border-left: solid 1px black; + border-right: solid 1px black; + border-bottom: solid 1px black; +} + +.SearchColumnFilterBox +{ + border: solid 1px black; +} + + +.SearchEvenRow +{ + background: #ffffff; + font-size: .7em; + border-bottom: solid 1px silver; +} + +.SearchOddRow +{ + background: #f8f8f8; + font-size: .7em; + border-bottom: solid 1px silver; +} + +.ShowDiffCheckbox +{ +} + +.searchHitHead +{ + margin-top: 0.05in; + margin-left: 0.25in; + font-weight: bold; + background: #e0e0e0; +} + +.VersionList +{ + background: #f2f2f2; + color: black; +} + +.VersionBar +{ + padding: 2px; + background: #f2f2f2; + border-bottom: solid 1px #808080; + text-align: right; +} + + +.VersionButton +{ +} + +.searchHitBody +{ + margin-left: 0.25in; +} + +.searchHitBody p +{ + line-height: 1em; + margin-top: .8em; + margin-bottom: .8em; +} + +.searchHitBody td +{ + line-height: 1em; + margin-top: .8em; + margin-bottom: .8em; +} + +.searchHitBody li +{ + margin-left: .5in; + line-height: 1em; + margin-top: 0; + padding-top: 1px; + padding-bottom: 1px; + margin-bottom: 0; +} + +.searchHitBody ul +{ + margin-top: 0; + padding-top: 0; + padding-bottom: 0; + margin-bottom: 0; +} + +.searchHitBody ol +{ + margin-top: 0; + padding-top: 0; + padding-bottom: 0; + margin-bottom: 0; +} + +.FooterTable { + background: #2FA7FB; + padding: 4px; + border-top: solid 1px #404040; + height: 20px; +} + +.FooterCell { + color: white; +} + +.FooterCell a { + color: #FBE52F; + text-decoration:none; +} +.FooterCell a:hover { + color: #FBE52F; + text-decoration:underline; +} + +.HistorySpacer +{ + height: 5px; +} +.Header +{ + padding: 5px; + background: #2FA7FB; + border-bottom: solid 1px #404040; + border-right: solid 1px #404040; + color: aliceblue; + height: 20px; +} + +.Header a { + color: #FBE52F; + font-weight: bold; + text-decoration:none; +} +.Header a:hover { + font-weight: bold; + color: #FBE52F; + text-decoration:underline; +} + +.PrintMain { + padding: .2in; +} + +.SimpleBody +{ +} + +.BannedChange +{ + background: red; + color: Yellow; + font-weight: bold; +} +.ConflictingChange +{ + background: red; + color: Yellow; + font-weight: bold; +} + +.EditBody +{ + background: lemonchiffon; +} + +.SaveChanges +{ + text-align: right; + vertical-align: middle; + background: lemonchiffon; + font-size: 1.5em; + height: 22px; + padding: 2px; +} +.SaveChanges a +{ + color: yellow; + text-decoration: underline; +} + +.SimpleTopic +{ + color: black; + font-size: 16pt; +} + +.TopicBar +{ + font-weight: bold; + margin-bottom: 14px; + color: #3399cc; + padding: 1px; + border: solid 1px white; +} + +.TopicBarHover +{ + font-weight: bold; + margin-bottom: 14px; + color: #3399cc; + padding: 1px; + border: solid 1px silver; +} + +.QuickLinkInput +{ + border: none; + font-weight: bold; + font-size: 1.4em; + color: royalblue; + background: #e0e0e0; +} + +.DynamicTopicBarHelp +{ + background: #d0d0d0; + font-size: .8em; + color: black; +} + +.DefaultTopicTipText +{ + color: Gray; +} + +.TopicTipStats +{ + border-top: 1px solid grey; + font-size: .8em; + color: Gray; +} + +.StaticTopicBar +{ + font-weight: bold; + font-size: 2em; + color: #0066ff; +} + +.TopicTip +{ + display: none; + position: absolute; + border: 1px solid silver; + background: #e0e0e0; + color: Black; + padding: 1px; + font-size: .7em; +} + + +.DynamicTopicBar +{ + background: #e0e0e0; +} + + +.TopicInfo { + color: black; + text-align: center; +} + +.Property +{ + font-size: .82em; + margin-top: 5px; + margin-bottom: 5px; + margin-left: -5px; + color: #0066ff; +} + +.PropertyName +{ + font-weight: bold; + font-size: .82em; + line-height: 1.2em; +} + +.PropertyValue +{ + font-size: .82em; + color: #cc6600; + background-color: white; +} + +.Deemphasis +{ + color: #a0a0a0; +} + +.TableClass +{ + background-color: #f8f8f8; + border: #505050 1px solid; + border-collapse: collapse; +} + +.TableWithoutBorderClass +{ + border-collapse: collapse; +} + +.TableCellHighlighted +{ + background-color: #e0e0e0; + font-size: .72em; + border-collapse: collapse; + border: #909090 1px solid; +} + +.TableCell +{ + font-size: .72em; + border-collapse: collapse; + border: #909090 1px solid; +} + +.TableCellHighlightedNoBorder +{ + background-color: #e0e0e0; + font-size: .72em; +} + +.TableCellNoBorder +{ + font-size: .72em; +} + + + + + + +body { + font-family: Arial, Sans-Serif; + font-size: small; + background:white; + margin: 0; + padding: 0; +} + +td { + color:black; +} + +p +{ + font-size: 0.76em; + line-height: 1.35em; + font-family: Arial, Sans-Serif; +} +a { + color: royalblue; + text-decoration:none; +} +a:hover { + color: royalblue; + text-decoration:underline; +} + +.extLink +{ + color: royalblue; + text-decoration:none; +} + +a.create { + color: black; + text-decoration:none; + border-bottom: 1px dashed royalblue; +} +a.create:hover { + color: royalblue; + text-decoration:underline; + border-bottom: 0px; +} +pre { + font-family: lucida console; + margin-left: .35in; + line-height: 1.2em; + font-size: .7em; + background: #f7f7f7; + border: 3px double #999999 +} +code { + font-family: lucida console; +} + + +h1 +{ + border-top: mediumblue 2px solid; + padding-left: 4px; + font-size: 1.4em; + margin-left: -5px; + color: #0033cc; + padding-top: 2px; +} + + +h1 a +{ + color: darkslateblue; + text-decoration: none; +} +h1 a:hover { + color: royalblue; + text-decoration:underline; +} + +h2 { + font-size: 1.35em; + color: darkslateblue; + margin-top: 1.6em; +} +h2 a { + color: royalblue; + text-decoration: none; +} +h2 a:hover { + color: royalblue; + text-decoration:underline; +} + +h3 +{ + margin-top: 1.6em; + font-size: 1.1em; + color: darkslateblue; + font-variant: small-caps; +} +h3 a { + color: royalblue; + text-decoration: none; +} +h3 a:hover { + color: royalblue; + text-decoration:underline; +} + +h4 { + font-size: .76em; + color: darkslateblue; + margin-top: 1.6em; +} + +h5 { + font-size: .76em; + color: darkslateblue; + margin-top: 1.6em; +} + +h6 { + font-size: .76em; + color: darkslateblue; + margin-top: 1.6em; +} +h7 +{ + font-size: .76em; + color: darkslateblue; + margin-top: 1.6em; +} +a.standardsButton +{ + border:1px solid; + border-color:#ffc8a4 #7d3302 #3f1a01 #ff9a57; + padding:0px 3px 0px 3px; + font:bold 10px verdana,sans-serif; + color:#FFFFFF; background-color:#ff6600; + text-decoration:none; + margin:0px; +} +a.standardsButton:hover +{ + border:1px solid; + border-color:#ffc8a4 #7d3302 #3f1a01 #ff9a57; + padding:0px 3px 0px 3px; + font:bold 10px verdana,sans-serif; + color:#FFFFFF; background-color:#ff6600; + text-decoration:none; + margin:0px; +} +a.standardsButton:visited +{ + border:1px solid; + border-color:#ffc8a4 #7d3302 #3f1a01 #ff9a57; + padding:0px 3px 0px 3px; + font:bold 10px verdana,sans-serif; + color:#FFFFFF; background-color:#ff6600; + text-decoration:none; + margin:0px; +} +li +{ + margin-left: .05in; + line-height: 1.2em; + font-size:.76em; + margin-top: 1px; + padding-top: 2px; + padding-bottom: 2px; + margin-bottom: 1px; +} + +li li +{ + margin-left: .05in; + line-height: 1.2em; + margin-top: 1px; + font-size: 1em; + padding-top: 2px; + padding-bottom: 2px; + margin-bottom: 1px; +} +ol ol { + list-style: lower-alpha; +} + diff --git a/SampleMacros/OriginalSampleMacros/windict.py b/SampleMacros/OriginalSampleMacros/windict.py new file mode 100644 index 00000000..a1c3ba7d --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/windict.py @@ -0,0 +1,298 @@ +# +# Python Macro Language for Dragon NaturallySpeaking +# (c) Copyright 1999 by Joel Gould +# Portions (c) Copyright 1999 by Dragon Systems, Inc. +# +# windict.py +# +# This is a sample Python program which demonstrates how to use the +# Natlink dictation object in a Python program. The user interface for +# this programn is based on the PythonWin (win32) extensions. +# +# The basic idea is as follows. +# +# Dragon NaturallySpeaking handles dictation by having a dictation object +# which contains a copy oft he user document. In this way, NatSpeak knows +# what text is on the screen so it can get the spacing write. It also +# knows the text on the screen for the Select and Correct commands. +# +# When writing an application which supports dictation (and voice editing), +# the application must keep the contents of the NatSpeak dictation object in +# sync with the text on the screen. +# +# In this sample application, we illustrate doing this for a rich edit +# control. There will be a rich edit contorl on the screen and we keep its +# contents synchronized with a NatSpeak dictation object. Thus, our simple +# edit control will allow our window to behave like NatSpeak's own editor +# window (althoug hnot every feature is exposed). +# +# For correctness, you only have to intercept the "begin" callback which +# happens just before recognition starts. At that point you have to make +# sure that the internal dictation object has a complete copy of the state +# of the edit control -- text, selection and visible range. Then when +# recognition occurs, the dictation object will return information about +# what change happened --- text deleted, text added and selection moved. +# +# To optimize for better performance and to prevent the lose of recorded +# speech, it helps if the code doe snot wait until the tsart of recognition, +# but instead updates the dictation object after every change of the edit +# control. +# +# The best implementation only tells the dictation object aboiut the change +# (for example a character was typed), but in this code I update the +# dictation object with the entire contents of the edit control on every +# keystroke. +# + +import sys +import string + +# Pythonwin imports +from pywin.mfc import dialog +import win32ui +import win32api +import win32con +import win32gui + +# Speech imports +import natlink +from natlink.natlinkutils import * + +#--------------------------------------------------------------------------- +# This code describes the dialog box. I cheated when I created the dialog. +# I created the dialog box using Visual C++ so that I could use a graphical +# layout tool. Then I copied the resource file information here. The +# format of the text in the template was determined by looking at the +# pythonwin sample applications. +# + +IDC_EDIT = 1000 +IDC_MICBUTTON = 1001 + +def MakeDlgTemplate(): + style = win32con.DS_MODALFRAME|win32con.WS_POPUP|win32con.WS_VISIBLE|win32con.WS_CAPTION|win32con.WS_SYSMENU + child = win32con.WS_CHILD|win32con.WS_VISIBLE + templ = [ + ["Correction Test Window",(0, 0, 320, 197),style,None,(8,"MS Sans Serif")], + ["RICHEDIT","",IDC_EDIT,(7,7,306,167),child|win32con.ES_MULTILINE|win32con.ES_AUTOVSCROLL|win32con.ES_WANTRETURN|win32con.WS_BORDER|win32con.WS_VSCROLL|win32con.WS_TABSTOP], + [128,"Turn &Mic On",IDC_MICBUTTON,(244,176,65,14),child], + ] + return templ + +#--------------------------------------------------------------------------- +# Command grammar +# +# I have included a very simple command grammar sonsisting of one command +# "delete that" as an example. The command grammar can be easily extended. + +class CommandGrammar(GrammarBase): + + # First we list our grammar as a long string. The grammar is in SAPI + # format, where we define a series of rules as expressions built from + # other rules, words and lists. Rules which can be activated are flagged + # with the keyword "exported". + + gramSpec = """ + exported = delete that; + """ + + # Call this function to load the grammar, activate it and install + # install callback functions + + def initialize(self,dlg): + self.dlg = dlg + self.load(self.gramSpec) + self.activateAll(dlg.GetSafeHwnd()) + + # Call this function to cleanup. We have to reset the callback + # functions or the object will not be freed. + + def terminate(self): + self.dlg = None + self.unload() + + # This routine is called from GrammarBase when words are recognized from + # the rule 'DeleteThat'. We pass the recognition information directly + # to the dialog box code. + + def gotResults_DeleteThat(self,words,fullResults): + self.dlg.onCommand_DeleteThat(words) + +#--------------------------------------------------------------------------- +# VoiceDictation client +# +# This class provides a way of encapsulating the voice dictation (DictObj) +# of Natlink. We can not derive a class from DictObj because DictObj is an +# exporeted C class, not a Python class. But we can create a class which +# references a DictObj instance and makes it lkook like the class was +# inherited from DictObj. + +class VoiceDictation: + + def __init__(self): + self.dictObj = None + + # Initialization. Create a DictObj instance and activate it for the + # dialog box window. All callbacks from the DictObj instance will go + # directly to the dialog box. + + def initialize(self,dlg): + self.dlg = dlg + self.dictObj = natlink.DictObj() + self.dictObj.setBeginCallback(dlg.onTextBegin) + self.dictObj.setChangeCallback(dlg.onTextChange) + self.dictObj.activate(dlg.GetSafeHwnd()) + + # Call this function to cleanup. We have to reset the callback + # functions or the object will not be freed. + + def terminate(self): + self.dictObj.deactivate() + self.dictObj.setBeginCallback(None) + self.dictObj.setChangeCallback(None) + self.dictObj = None + + # This makes it possible to access the member functions of the DictObj + # directly as member functions of this class. + + def __getattr__(self,attr): + try: + if attr != '__dict__': + dictObj = self.__dict__['dictObj'] + if dictObj is not None: + return getattr(dictObj,attr) + except KeyError: + pass + raise AttributeError, attr + +#--------------------------------------------------------------------------- +# Dialog box + +class TestDialog(dialog.Dialog): + + # Dialog initialization. Tell the rich text control to send us change + # messages, install callbacks for buttons and initialize the command and + # dictation objects. + def OnInitDialog(self): + rc = dialog.Dialog.OnInitDialog(self) + self.HookCommand(self.onMicButton,IDC_MICBUTTON) + self.HookCommand(self.onNotify,IDC_EDIT) + self.edit = self.GetDlgItem(IDC_EDIT) + self.edit.SetEventMask(win32con.ENM_CHANGE) + self.grammar = CommandGrammar() + self.grammar.initialize(self) + self.dictObj = VoiceDictation() + self.dictObj.initialize(self) + + # When the dialog is closed, make sure we delete the grammar and + # dictation objects so the callbacks are reset + + def OnDestroy(self,msg): + self.grammar.terminate() + self.grammar = None + self.dictObj.terminate() + self.dictObj = None + + # This subroutine transfers the contents and state of the edit control + # into the dictation object. We currently don't bother to indicate + # exactly what changed. The dictation object will compare the text we + # write with the contents of its buffer and only make the necessary + # changes (as long as on one contigious region has changed). + + def updateState(self): + text = self.edit.GetWindowText() + selStart,selEnd = self.edit.GetSel() + visStart,visEnd = self.getVisibleRegion() + + self.dictObj.setLock(1) + self.dictObj.setText(text,0,0x7FFFFFFF) + self.dictObj.setTextSel(selStart,selEnd) + self.dictObj.setVisibleText(visStart,visEnd) + self.dictObj.setLock(0) + + # Utility subroutine which calculates the visible region of the edit + # control and returns the start and end of the current visible region. + + def getVisibleRegion(self): + top,bottom,left,right = self.edit.GetClientRect() + firstLine = self.edit.GetFirstVisibleLine() + visStart = self.edit.LineIndex(firstLine) + + lineCount = self.edit.GetLineCount() + lastLine = lineCount + for line in range(firstLine+1,lineCount): + charInLine = self.edit.LineIndex(line) + left,top = self.edit.GetCharPos(charInLine) + if top >= bottom: + break + lastLine = line + + visEnd = self.edit.LineIndex(lastLine+1) + if visEnd == -1: + visEnd = len(self.edit.GetWindowText()) + return visStart,visEnd + + # Special code for the microphone button. We turn the microphone on or + # off depending on its current state. + + def onMicButton(self,nID,code): + micState = natlink.getMicState() + if micState == 'on' or micState == 'sleeping': + self.SetDlgItemText(IDC_MICBUTTON,'Turn &Mic On') + natlink.setMicState('off') + else: + self.SetDlgItemText(IDC_MICBUTTON,'Turn &Mic Off') + natlink.setMicState('on') + self.edit.SetFocus() + + # When something changes in the edit control (usually because the user + # is typing), update the dictation object. + + def onNotify(self,controlId,code): + if code == win32con.EN_CHANGE: + self.updateState() + + # This routine is invoked when we hear "delete that". We simply play a + # delete key. A more sophisticated algorithm is possible, but I am + # lazy. + + def onCommand_DeleteThat(self,words): + natlink.playString('{Del}') + + # We get this callback just before recognition starts. This is our + # chance to update the dictation object just in case we missed a change + # made to the edit control. + + def onTextBegin(self,moduleInfo): + self.updateState() + + # We get this callback when something in the dictation object changes + # like text is added or something is selected by voice. We then update + # the edit control to match the dictation object. + + def onTextChange(self,delStart,delEnd,newText,selStart,selEnd): + self.dictObj.setLock(1) + self.edit.SetSel(delStart,delEnd) + self.edit.ReplaceSel(newText) + self.edit.SetSel(selStart,selEnd) + self.dictObj.setLock(0) + +#--------------------------------------------------------------------------- +# This is the main routine. Here we connect to the speech subsystem, +# create the dialog box and when the dialog is closed, disconnect from +# the speech subsystem. +# +# If an exception occurs, make sure we disconnect from NatSpeak before +# reporting the exception. + +def run(): + try: + natlink.natConnect(1) + TestDialog(MakeDlgTemplate()).DoModal() + natlink.natDisconnect() + except: + natlink.natDisconnect() + raise + +if __name__=='__main__': + run() diff --git a/SampleMacros/OriginalSampleMacros/winspch.py b/SampleMacros/OriginalSampleMacros/winspch.py new file mode 100644 index 00000000..5839053e --- /dev/null +++ b/SampleMacros/OriginalSampleMacros/winspch.py @@ -0,0 +1,262 @@ +# +# This is a sample of adding grammar based speech recognition to a simple +# Python application written using the Python win32 libraries. +# + +import sys +import string + +# Pythonwin imports +from pywin.mfc import dialog +import win32ui +import win32api +import win32con + +# Speech imports +import natlink +from natlink.natlinkutils import * + +#--------------------------------------------------------------------------- +# +# This code describes the dialog box. I cheated when I created the dialog. +# I created the dialog box using Visual C++ so that I could use a graphical +# layout tool. Then I copied the resource file information here. The format +# of the text in the template was determined by looking at the pythonwin +# sample applications. +# + +IDC_EDIT=1000 +IDC_PRESS=1001 +IDC_CLICK=1002 +IDC_MIC=1003 +IDC_NOTHING=1004 +IDC_SPEECH=1005 + +def MakeDlgTemplate(): + style = win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT + child = win32con.WS_CHILD | win32con.WS_VISIBLE + templ = [ + ["Sample Dialog",(0, 0, 264, 165),style,None,(8, "MS Sans Serif")], + [130,"You can speak the names of any of the buttons, or a number less than 1 billion.", + -1,(7,7,249,8),child|win32con.SS_LEFT], + [128,"&Press Me",IDC_PRESS,(7,21,60,14),child|win32con.BS_PUSHBUTTON|win32con.WS_TABSTOP], + [128,"&Click This Button",IDC_CLICK,(74,41,66,10),child|win32con.BS_AUTOCHECKBOX|win32con.WS_TABSTOP], + [128,"&Turn Mic On",IDC_MIC,(193,21,64,14),child|win32con.BS_PUSHBUTTON|win32con.WS_TABSTOP], + [128,"&Do Nothing",IDC_NOTHING,(74,21,60,14),child|win32con.BS_PUSHBUTTON|win32con.WS_TABSTOP], + [128,"&Speech Works",IDC_SPEECH,(7,38,60,14),child|win32con.BS_PUSHBUTTON|win32con.WS_TABSTOP], + [128,"Close &Window",win32con.IDCANCEL,(193,39,64,14),child|win32con.BS_PUSHBUTTON|win32con.WS_TABSTOP], + [130,"Messages show up here:",-1,(7,60,80,8),child|win32con.SS_LEFT], + [129,"",IDC_EDIT,(7,72,250,85),child|win32con.ES_MULTILINE|win32con.WS_BORDER| + win32con.ES_AUTOVSCROLL|win32con.WS_VSCROLL], + ] + return templ + +#--------------------------------------------------------------------------- +# +# This is the grammar. It handles all recognition and prints messages when +# anything is recognized. +# + +class TestGrammar(GrammarBase): + + # First we list our grammar as a long string. The grammar is in SAPI + # format, where we define a series of rules as expressions built from + # other rules, words and lists. Rules which can be activated are flagged + # with the keyword "exported". + # + # Our grammar lets us say the name of any button or any number less then + # 1 billion. The number grammar was included as an example of a complex + # grammar while the button grammar is very simple. + + gramSpec = """ + <1to99> = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | + 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | + 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | + 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | + 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | + 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | + 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | + 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | + 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | + 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99; + + <1to999> = <1to99> | + ( 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ) hundred [ [and] <1to99> ]; + + = 0 | <1to999> [ + thousand [ [and] <1to999> ] | + million [ [and] <1to999> [ thousand [ [and] <1to999> ] ] ] + ]; + +