From 9dade247a71664d900771ac1e9b04a3a8f680a82 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 31 Aug 2022 20:18:23 +0200 Subject: [PATCH 01/11] bump to 4.1.1 august 2022, not stable yet... --- src/unimacro/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index f549158..17a96bf 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -23,7 +23,7 @@ import os import sys -__version__ = '4.1.0' +__version__ = '4.1.1' def get_site_packages_dir(fileOfModule): """get directory of calling module, if possible in site-packages From 0464b6d8d4222f175e9b677b477d836d72dc6ec6 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Thu, 22 Sep 2022 17:21:10 +0200 Subject: [PATCH 02/11] bump to 4.1.2, needs dtactions 1.5.5 --- pyproject.toml | 2 +- src/unimacro/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aca0cf1..58d9c2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires-python = ">=3.9" readme = "README.md" -dependencies = ["dtactions >= 1.5.0", +dependencies = ["dtactions >= 1.5.5", "debugpy >= 1.2.0", "pywin32 >= 300", "natlinkcore >= 5.2.1"] diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index 17a96bf..ce718a1 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -23,7 +23,7 @@ import os import sys -__version__ = '4.1.1' +__version__ = '4.1.2' # requires the dtactions 1.5.5, with new sendkeys (from send_input, vocola) def get_site_packages_dir(fileOfModule): """get directory of calling module, if possible in site-packages From 2ea33002d0747f38169f4dbde0ffcb808917874d Mon Sep 17 00:00:00 2001 From: Doug Ransom Date: Sun, 10 Mar 2024 17:23:41 -0700 Subject: [PATCH 03/11] updated doc string for logging, rewrote Union & Intersect to use the set features now available. --- src/unimacro/natlinkutilsbj.py | 38 ++++++++++------------------------ 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/unimacro/natlinkutilsbj.py b/src/unimacro/natlinkutilsbj.py index e7c91bc..f95b12c 100644 --- a/src/unimacro/natlinkutilsbj.py +++ b/src/unimacro/natlinkutilsbj.py @@ -196,40 +196,21 @@ def letterUppercase(l): PythonServerExe=os.path.join(PythonwinPath, 'pserver1') # returns the union of two lists -def Union(L1,L2): +def Union(L1,L2) -> list: """old fashioned union function of two lists """ - if not isinstance(L1, list): - raise TypeError(f'function Union, first input variable not a list: {type(L1)}') - if not isinstance(L2, list): - raise TypeError(f'function Union, second input variable not a list: {type(L2)}') - L=L1[:] - for i in L2: - if not i in L1: - L.append(i) - return L + return list(set(L1).union(L2)) # returns the intersection of two lists def Intersect(L1,L2): """old fashioned intersection function of two lists """ - if not isinstance(L1, list): - raise TypeError(f'function Intersect, first input variable not a list: {type(L1)}') - if not isinstance(L2, list): - raise TypeError(f'function Intersect, second input variable not a list: {type(L2)}') - L=[] - for i in L2: - if i in L1: - L.append(i) - return L - -def reverseDict(Dict): + return set(L1).intersection(L2) + +def reverseDict(Dict : dict): """reverse a dict, ignoring multiple values of the original """ - revDict={} - for key in list(Dict.keys()): - revDict[Dict[key]]=key - return revDict + return {val: key for (key, val) in Dict.items()} def joinNestedStringLists(l): """nested lists of strings are "flattened" into one string @@ -321,9 +302,12 @@ def getLogger(self) -> logging.Logger: #avoid copy and pasting methods that delegate to getLoger() def wrapped_log(method): - """Delegates to {method} of a Logger object from self.getLogger()""" + """Creates a function that delegates to the 'method' of the Logger object from self.getLogger()""" def fn(self,*args,**kwargs): - logger=self.getLogger() + f""" + Forwards {method} to the Logger Object from self.getLogger(). + """ + logger : Logger=self.getLogger() try: return method(logger,*args,**kwargs) except Exception as e: From c38d3c972365beba1aa0491e80274d7e0585804a Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 13 Mar 2024 16:59:50 +0100 Subject: [PATCH 04/11] Check unimacro feb24 (#49) * checking getTopOrChild function... * fixing, at lasts, switch on a unimacro grammar, doing a reload (by the loader), with the force option. * _general.py fixed several dgndictation things (also in nsformat.py of natlinkcore) * brought win32clipboard under try finally * tidy up _general * work in progress _folders, getting natlinktimer again at work... * working on the _folders grammar option and checking automatic tracking of recent folders and files and subfolders * effort to diminish the errors when an ini file is invalid... * working on getActiveFolder Co-authored-by: Doug Ransom --- .gitignore | 1 + pyproject.toml | 9 +- src/unimacro/OtherUnimacroGrammars/_latex.py | 4 +- src/unimacro/UnimacroGrammars/_brackets.py | 19 +- .../UnimacroGrammars/_clickbyvoice.py | 39 +- .../_folders fillinstancevariables.txt | 151 +++ src/unimacro/UnimacroGrammars/_folders.py | 1126 +++++++---------- src/unimacro/UnimacroGrammars/_general.py | 156 +-- src/unimacro/UnimacroGrammars/_lines.py | 4 +- .../settings folders grammar.txt | 27 + src/unimacro/__init__.py | 31 +- src/unimacro/_control.py | 268 ++-- src/unimacro/natlinkutilsbj.py | 108 +- .../sample_ini/enx_inifiles/_brackets.ini | 31 - .../sample_ini/enx_inifiles/_clickbyvoice.ini | 37 - .../sample_ini/enx_inifiles/_folders.ini | 120 -- .../sample_ini/enx_inifiles/_general.ini | 88 -- .../sample_ini/enx_inifiles/_lines.ini | 77 -- .../sample_ini/enx_inifiles/_tasks.ini | 133 -- tests/test_grammar_brackets.py | 63 + tests/test_grammar_folders.py | 4 +- tests/test_grammar_general.py | 3 + tests/test_new_inifile_directory.py | 61 + 23 files changed, 1064 insertions(+), 1496 deletions(-) create mode 100644 src/unimacro/UnimacroGrammars/_folders fillinstancevariables.txt create mode 100644 src/unimacro/UnimacroGrammars/settings folders grammar.txt delete mode 100644 src/unimacro/sample_ini/enx_inifiles/_brackets.ini delete mode 100644 src/unimacro/sample_ini/enx_inifiles/_clickbyvoice.ini delete mode 100644 src/unimacro/sample_ini/enx_inifiles/_folders.ini delete mode 100644 src/unimacro/sample_ini/enx_inifiles/_general.ini delete mode 100644 src/unimacro/sample_ini/enx_inifiles/_lines.ini delete mode 100644 src/unimacro/sample_ini/enx_inifiles/_tasks.ini create mode 100644 tests/test_grammar_brackets.py create mode 100644 tests/test_new_inifile_directory.py diff --git a/.gitignore b/.gitignore index 89cdb74..5302b09 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ __pycache__/ /src/unimacro/grammar.bin README.html /src/unimacro/UnimacroGrammars/rememberdialog.py +/tests/rememberdialog.py diff --git a/pyproject.toml b/pyproject.toml index f6cdeba..f282a2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi" name = "unimacro" authors = [{name = "Quintijn Hoogenboom (maintainer)", email = "q.hoogenboom@antenna.nl"}] dynamic = ["version", "description"] -requires-python = ">=3.9" +requires-python = ">=3.10" readme = "README.md" @@ -35,11 +35,16 @@ test = [ "pytest >=7.1.2","flake8" ] dev = [ - "pyenvutils","entry-point-inspector" + "pyenvutils","entry-point-inspector","build" ] [project.entry-points."natlink.grammars"] unimacro_builtins = "unimacro.UnimacroGrammars:locateme" +[project.entry-points."dt.loggers"] + unimacro ="unimacro:logname" + control="unimacro:control_logger_name" + folders="unimacro:folders_logger_name" + [tool.pytest.ini_options] minversion = "7.1.2" addopts = "--capture=tee-sys " diff --git a/src/unimacro/OtherUnimacroGrammars/_latex.py b/src/unimacro/OtherUnimacroGrammars/_latex.py index 08fbf82..c231d19 100644 --- a/src/unimacro/OtherUnimacroGrammars/_latex.py +++ b/src/unimacro/OtherUnimacroGrammars/_latex.py @@ -263,8 +263,8 @@ def unload(): # here code to interactive run this module natlink.natConnect() try: - thisGrammar = ThisGrammar() - thisGrammar.startInifile(modName = '_latex') + thisGrammar = ThisGrammar(inifile_stem='_latex') + thisGrammar.startInifile() thisGrammar.initialize() Words = ['add', 'option', 'draft'] FR = {} diff --git a/src/unimacro/UnimacroGrammars/_brackets.py b/src/unimacro/UnimacroGrammars/_brackets.py index 65d86c1..83adde0 100644 --- a/src/unimacro/UnimacroGrammars/_brackets.py +++ b/src/unimacro/UnimacroGrammars/_brackets.py @@ -81,12 +81,12 @@ def rule_brackets(self, words): newpleft = pList[0] newpright = pList[1] else: - lenph = int(len(p)/2) + lenph = len(p)//2 newpleft, newpright = p[:lenph], p[lenph:] # make more brackets together, from outer to inner: self.pleft = self.pleft + newpleft self.pright = newpright + self.pright - #print 'pleft: "%s", pright: "%s"'% (repr(self.pleft), repr(self.pright)) + # print(f'result rule_brackets: |{self.pleft}|, pright: |{self.pright}|') def subrule_before(self, words): "(here|between|empty)+" @@ -113,8 +113,11 @@ def gotResults(self, words, fullResults): text = "" if self.here: + print('do a left buttonClick') unimacroutils.buttonClick('left', 1) + print('do a "visibleWait') unimacroutils.visibleWait() + print('after a visibleWait') leftText = rightText = leftTextDict = rightTextDict = "" if text: @@ -134,13 +137,13 @@ def gotResults(self, words, fullResults): if lSpacing: keystroke(lSpacing) - action(self.pleft) + keystroke(self.pleft) unimacroutils.visibleWait() if text: #print 'text: |%s|'% repr(text) keystroke(text) unimacroutils.visibleWait() - action(self.pright) + keystroke(self.pright) unimacroutils.visibleWait() if rSpacing: @@ -199,9 +202,9 @@ def stripFromBothSides(text): text = text.rstrip() return text, leftText, rightText -# standard stuff Joel (adapted for possible empty gramSpec, QH, unimacro) +# standard stuff Joel (adapted in course of time, QH) def unload(): - #pylint:disable=W0603 + #pylint:disable=W0603, E0601 global bracketsGrammar if bracketsGrammar: bracketsGrammar.unload() @@ -211,8 +214,8 @@ def unload(): if __name__ == "__main__": natlink.natConnect() try: - bracketsGrammar = BracketsGrammar() - bracketsGrammar.startInifile(modName = '_brackets') + bracketsGrammar = BracketsGrammar(inifile_stem='_brackets') + bracketsGrammar.startInifile() bracketsGrammar.initialize() finally: natlink.natDisconnect() diff --git a/src/unimacro/UnimacroGrammars/_clickbyvoice.py b/src/unimacro/UnimacroGrammars/_clickbyvoice.py index 3f1271c..26bd705 100644 --- a/src/unimacro/UnimacroGrammars/_clickbyvoice.py +++ b/src/unimacro/UnimacroGrammars/_clickbyvoice.py @@ -18,6 +18,8 @@ # # the lists {pagecommands} and {tabcommands} in the inifile (edit chrome hah) # +#pylint:disable=C0209 + """ This commands grammar allows clicking by voice @@ -25,8 +27,7 @@ in the foreground """ -#pylint:disable= - +import natlink from unimacro import natlinkutilsbj as natbj from dtactions.unimacro import unimacroutils from dtactions.unimacro.unimacroactions import doAction as action @@ -53,7 +54,7 @@ class ThisGrammar(ancestor): gramSpec = """ exported = ((show) (numbers) [{additionalonoroff}]+) | ((numbers) {additionalonoroff}+) ; - exported = (hide) (numbers) [after {n1-20}]; + exported = (hide) (numbers); exported = (pick) [{navigateoptions}]; # is already in _tasks grammar: @@ -67,6 +68,7 @@ class ThisGrammar(ancestor): (page (back|forward) [{n1-20}]) | page {pagecommands} | (next|previous) page {pagecommands}; + #and the numbers grammar (0,...,999 or chain of digits): """+numberGram @@ -155,17 +157,15 @@ def gotResults_shownumbers(self, words, fullResults): self.showNumbers = showNumbers self.getInputcontrol() self.doOption(showNumbers) + print('clickbyvoice, before finishInputControl') self.finishInputControl() - + print('clickbyvoice, after finishInputControl') + def gotResults_hidenumbers(self, words, fullResults): """hide the numbers """ - wordFound, _position = self.hasCommon(words, "after", withIndex=True) - if wordFound: - print("setting timer later") - self.getInputcontrol() self.doOption(self.hideNumbers) self.finishInputControl() @@ -330,11 +330,26 @@ def fillInstanceVariables(self): # standard stuff Joel (adapted for possible empty gramSpec, QH, unimacro) -thisGrammar = ThisGrammar() -if thisGrammar.gramSpec: +if __name__ == "__main__": + ## interactive use, for debugging: + with natlink.natConnect(): + try: + thisGrammar = ThisGrammar(inifile_stem="_clickbyvoice") + # thisGrammar.startInifile() + thisGrammar.initialize() + print('clickbyvoice, before finishInputControl') + thisGrammar.finishInputControl() + print('clickbyvoice, after finishInputControl') + + + + finally: + thisGrammar.unload() +elif __name__.find('.') == -1: + # standard startup when Dragon starts: + thisGrammar = ThisGrammar() thisGrammar.initialize() -else: - thisGrammar = None + def unload(): #pylint:disable=W0603 diff --git a/src/unimacro/UnimacroGrammars/_folders fillinstancevariables.txt b/src/unimacro/UnimacroGrammars/_folders fillinstancevariables.txt new file mode 100644 index 0000000..b089af3 --- /dev/null +++ b/src/unimacro/UnimacroGrammars/_folders fillinstancevariables.txt @@ -0,0 +1,151 @@ +fillinstancevariables old: + def fillInstanceVariables(self): + """fills the necessary instance variables + take the lists of folders, virtualdrives (optional) and remotedrives (optional). + + """ + #pylint:disable=R0914 + self.useOtherExplorer = self.ini.get('general', 'use other explorer') + if self.useOtherExplorer: + if os.path.isfile(self.useOtherExplorer): + print('_folders, use as default explorer: "%s"'% self.useOtherExplorer) + else: + print('_folders, variable "use other explorer" set to: "%s" (use data from "actions.ini")'% self.useOtherExplorer) + + ## callback time in seconds: + interval = self.ini.getFloat('general', 'track folders interval') + if interval: + self.trackFoldersInterval = int(interval*1000) # give in seconds + else: + self.trackFoldersInterval = 4000 # default 4 seconds + + self.recentfoldersDict = {} + inipath = self.ini.getFilename() + if inipath.endswith('.ini'): + _changingDataIniPath = inipath.replace(".ini", "changingdata.pickle") + self.pickleChangingData = inipath.replace(".ini", "changingdata.pickle") + else: + self.pickleChangingData = "" + + ## automatic tracking of recent folders : + self.trackFoldersHistory = self.ini.getInt('general', 'track folders history') + if self.trackFoldersHistory: + if self.pickleChangingData: + self.recentfoldersDict = self.loadRecentFoldersDict() + if self.recentfoldersDict: + print("recentfolders, set %s keys from _folderschangingdata.pickle"% len(self.recentfoldersDict)) + else: + print("recentfolder, no previous recentfolders cached in _folderschangingdata.pickle") + + self.doTrackFoldersHistory = True # can be started or stopped with command + # recent [folders] START or recent [folders] STOP + intervalSeconds = self.trackFoldersInterval/1000 + print('maintain a list of %s recent folders (Explorer or File Dialog) at every utterance and every %s seconds'% (self.trackFoldersHistory, intervalSeconds)) + natlinktimer.setTimerCallback(self.catchTimerRecentFolders, self.trackFoldersInterval) # every 5 seconds + else: + self.doTrackFoldersHistory = False + if self.doTrackFoldersHistory: + rfList = self.ini.get('recentfolders') + for key in rfList: + value = self.ini.get('recentfolders', key) + self.recentfoldersDict[key] = value + # extract special variables from ini file: + self.virtualDriveDict = {} + wantedVirtualDriveList = self.ini.get('virtualdrives') + if wantedVirtualDriveList: + self.resolveVirtualDrives(wantedVirtualDriveList) + self.virtualDriveList = list(self.virtualDriveDict.keys()) + # print '_folders, virtual drives from dict: %s'% repr(self.virtualDriveDict) + # print '_folders, virtual drives from list: %s'% ', '.join(self.virtualDriveList) + + # checking the passes of all folders: + foldersList = self.ini.get('folders') + self.foldersDict = {} + for f in foldersList: + raw_folder = self.ini.get('folders', f) + folder = self.substituteFolder(raw_folder) + if not os.path.isdir(folder): + print(f'warning _folders, folder "{f}" does not exist: "{folder}"') + # self.ini.delete('folders', f) + # self.ini.set('obsolete folders', f, folder) + continue + self.foldersDict[f] = folder + + # track virtual drives if in ini file: + self.trackFolders = self.ini.getList('general', 'track folders virtualdrives') + self.trackFiles = self.ini.getList('general', 'track files virtualdrives') + # below this threshold, the getting of subfolders and files in a directory is not printed in the messages window + self.notifyThresholdMilliseconds = self.ini.getInt("general", "notify threshold milliseconds", 50) + # print("_folders, notify threshold milliseconds: %s"% self.notifyThresholdMilliseconds) + # in order to accept .py but it should be (for fnmatch) *.py etc.: + self.acceptFileExtensions = self.ini.getList('general', 'track file extensions') + self.ignoreFilePatterns = self.ini.getList('general', 'ignore file patterns') + + # these are for automatic tracking the current folder: + self.trackAutoFolders = self.ini.getBool('general', 'automatic track folders') + self.trackAutoFiles = self.ini.getBool('general', 'automatic track files') + + self.foldersSections = ['folders'] + # track folders: + for trf in self.trackFolders: + if not trf: + continue + trf2 = self.substituteFolder(trf) + #print 'input: %s, output: %s'% (trf, trf2) + if not os.path.isdir(trf2): + print('warning, no valid folder associated with: %s (%s) (skip for track virtualdrives)'% (trf, trf2)) + continue + #else: + # print 'valid folder for tracking: %s (%s)'% (trf, trf2) + subf = [f for f in os.listdir(trf2) if os.path.isdir(os.path.join(trf2, f))] + self.trackFoldersSection = 'folders %s'% trf + self.ini.delete(self.trackFoldersSection) # not in inifile + self.foldersSections.append(self.trackFoldersSection) + self.acceptVirtualDrivesFolder(trf, trf2) # without f, take virtualdrive itself... + for f in subf: + ## if no strange signs in folder name: + self.acceptVirtualDrivesFolder(trf, trf2, f) + #self.cleanupIniFoldersSection(self.trackFoldersSection, trf) + #self.removeObsoleteIniSections(prefix="folders ", validPostfixes=self.trackFolders) + self.removeObsoleteIniSections(prefix="folders ", validPostfixes=[]) # do not keep in ini file + + # do the files: + self.filesDict = {} + self.trackFiles = self.ini.getList('general', 'track files virtualdrives') + # in order to accept .py but it should be (for fnmatch) *.py etc.: + self.acceptFileExtensions = self.ini.getList('general', 'track file extensions') + self.ignoreFilePatterns = self.ini.getList('general', 'ignore file patterns') + self.filesSections = ['files'] + # from section files (manual): + filesList = self.ini.get('files') + if filesList: + for f in filesList[:]: + filename = self.substituteFilename(self.ini.get('files', f)) + if not os.path.isfile(filename): + print(f'warning _folders, file "{f}" does not exist: "{filename}"') + # self.ini.delete('files', f) + # self.ini.set('obsolete files', f, filename) + continue + self.filesDict[f] = filename + + for trf in self.trackFiles: + if not trf: + continue + trf2 = self.substituteFolder(trf) + if not os.path.isdir(trf2): + print('warning, no valid folder associated with: %s (%s) (skip for track files)'% (trf, trf2)) + continue + filesList = [f for f in os.listdir(trf2) if os.path.isfile(os.path.join(trf2, f))] + self.trackFilesSection = 'files %s'% trf + self.ini.delete(self.trackFoldersSection) # not in inifile + self.filesSections.append(self.trackFilesSection) + for f in filesList: + self.acceptFileInFilesDict(trf, trf2, f) + #self.cleanupIniFilesSection(self.trackFilesSection, trf) + self.removeObsoleteIniSections(prefix="files ", validPostfixes=[]) + #self.removeObsoleteIniSections(prefix="files ", validPostfixes=self.trackFiles) # not in inifile any more + + # self.childBehavesLikeTop = self.ini.getDict('general', 'child behaves like top') + # self.topBehavesLikeChild = self.ini.getDict('general', 'top behaves like child') + # save changes if there were any: + self.ini.writeIfChanged() diff --git a/src/unimacro/UnimacroGrammars/_folders.py b/src/unimacro/UnimacroGrammars/_folders.py index 1c90ef4..b541e13 100644 --- a/src/unimacro/UnimacroGrammars/_folders.py +++ b/src/unimacro/UnimacroGrammars/_folders.py @@ -6,8 +6,8 @@ # grammar: _folders.py # Written by: Quintijn Hoogenboom (QH softwaretraining & advies) # starting 2003, revised QH march 2011 -# moved to the GitHub/Dictation-toolbox April 2020 -#pylint:disable=C0302, W0613, W0702, R0911, R0912, R0913, R0914, R0915 +# moved to the GitHub/Dictation-toolbox April 2020, improved vastly Febr 2024 (with partly new options) +#pylint:disable=C0302, W0613, W0702, R0911, R0912, R0913, R0914, R0915, W0212, W0703 #pylint:disable=E1101, C0209 r"""with this grammar, you can reach folders, files and websites from any window. From some windows (my computer and most dialog windows) the folders and files @@ -31,23 +31,15 @@ In the inifile also the commands for start this computer or start windows explorer must be given. Correct these commands ("Edit Folders"/"Bewerk folders") if they do not work correct. -New feature: if you want to use xxexplorer (can be used hands-free very -easy, look in https://www.netez.com/xxExplorer), in section [general] -you can put a variable -xxexplorer = path to exe or false ('') This explorer is then taken if you are in or if Explorer is explicitly asked for. The strategy for "New" and "Explorer" (when you say "new", "nieuw", "explorer" in the folder command, are complicated, look below -The git additional commands are only valid if you specify a valid git client in the ini file general section -(git executable) (To be implemented) (##TODO) - """ import re -import copy import pickle #recentfoldersDict import os import sys @@ -57,9 +49,10 @@ import urllib.parse import urllib.error import ctypes # get window text -import traceback from pathlib import Path # from pprint import pprint +from io import StringIO +from logging import getLogger import win32gui from win32com.client import Dispatch import win32clipboard @@ -67,13 +60,15 @@ import natlink from natlinkcore import readwritefile from natlinkcore import natlinktimer +from natlinkcore import natlinkstatus from dtactions.unimacro import extenvvars from dtactions import messagefunctions as mess from dtactions import natlinkclipboard from dtactions.unimacro.unimacroactions import doAction as action from dtactions.unimacro.unimacroactions import doKeystroke as keystroke -from dtactions.unimacro.unimacroactions import do_YESNO as YesNo +# from dtactions.unimacro.unimacroactions import do_YESNO as YesNo from dtactions.unimacro.unimacroactions import UnimacroBringUp +from dtactions.unimacro.unimacroactions import Message from dtactions.unimacro import unimacroutils # from dtactions.unimacro.unimacroactions import Message # from dtactions.unimacro import unimacroactions as actions @@ -82,7 +77,7 @@ # import natlinkcore.natlinkutils as natut thisDir = str(Path(__file__).parent) - +status = natlinkstatus.NatlinkStatus() # for getting unicode explorer window titles: GetWindowText = ctypes.windll.user32.GetWindowTextW GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW @@ -101,15 +96,29 @@ Classes = ('ExploreWClass', 'CabinetWClass') # together with track folders history: -doRecentFolderCommand = True # some child windows have to behave as top window (specified in ini file): # note: title is converted to lowercase, only full title is recognised -try: - thisGrammar -except NameError: - thisGrammar = None ancestor = natbj.IniGrammar + +#note this is basically copy & pasted into ThisGrammar +#some global scope functions need the same logger. +def logger_name(): + "natlink.unimacro.folders" + +logger = getLogger(logger_name()) + +#logger should be used instead of print +#replace print to avoid unintended use. +builtin_print=print +def our_print(*args,**kwargs): + f=StringIO() + builtin_print(args,kwargs,file=f) + value=f.getvalue() + logger.debug("print called instead of logging functions: %s", value) + logger.error(value) + + class ThisGrammar(ancestor): """grammar for quickly going to folders, files and websites """ @@ -123,11 +132,9 @@ class ThisGrammar(ancestor): # commands with special status, must correspond to a right hand side # of a ini file entry (section foldercommands or filecommands) # remote, openwith have hardcoded details. - optionalfoldercommands = ['new', 'explorer', 'paste', 'copy', 'remote', 'git'] - optionalfilecommands = ['copy', 'paste', 'edit', 'paste', 'remote', 'openwith', 'git'] + optionalfoldercommands = ['new', 'explorer', 'paste', 'copy', 'remote'] + optionalfilecommands = ['copy', 'paste', 'edit', 'paste', 'remote', 'openwith'] - # only used if siteRoot is a valid folder - optionalsitecommands = ['input', 'output', 'local', 'online'] gramSpec = """ exported = folder ({folders}[]); @@ -135,49 +142,52 @@ class ThisGrammar(ancestor): exported = drive {letters} []; exported = ((this|here) folder) (|); = new | here | paste | on ({letters}|{virtualdrivesspoken}) | - (git) {gitfoldercommands}| | {foldercommands}; exported = folder up|folder up {n1-10}; - exported = file ({files}|{subfiles})[|]; # add dot {extensions} later again + exported = recent [folder] ({recentfolders}|SHOW|HIDE|RESET|START|STOP) []; + + exported = file ({files}|{subfiles})[|]; exported = ((here|this) file) (|); = {filecommands}| on ({letters}|{virtualdrivesspoken}) | - ('open with') {fileopenprograms}| - (git) {gitfilecommands}| - ; + ('open with') {fileopenprograms}|; exported = website {websites} []; exported = (this website) (|); = ('open with') {websiteopenprograms}| ; - = remember; + = remember; = (copy (name|path)) | ((name|path) copy); -## set all environment variables into the folders list... - exported = set environment folders; """ - if doRecentFolderCommand: - gramSpec += """ exported = recent [folder] ({recentfolders}|SHOW|HIDE|RESET|START|STOP) [];""" def initialize(self): # self.envDict = natlinkcorefunctions.getAllFolderEnvironmentVariables() # for (generalised) environment variables - self.subfiles = self.subfiles = self.activeFolder = None # for catching on the fly in explorer windows (CabinetClassW) + self.subfiles = self.subfiles = self.activeFolder = self.activeTimerFolder = None # for catching on the fly in explorer windows (CabinetClassW) self.className = None self.dialogWindowTitle = "" # for recent folders dialog, grammar in natspeak.py self.dialogNumberRange = [] # ditto self.catchRemember = "" - self.activeFolder = None + self.inTimerRecentFolders = False self.prevDisplayRecentFolders = None # displaying recent folders list + self.prevActiveFolder = None self.subfoldersDict = {} self.subfilesDict = {} self.foldersSet = set() if not self.language: - print("no valid language in grammar "+__name__+" grammar not initialized") + self.error("no valid language in grammar "+__name__+" grammar not initialized") return self.load(self.gramSpec) - self.lastSite = None self.switchOnOrOff() # initialises lists from inifile, and switches on + def loggerName(self) ->str: + """Returns the name of a logger. Replace this and loggerShortName to create a logger for an inherited grammar. """ + return "natlink.unimacro.folders" + + def loggerShortName(self) ->str: + """A key for use as a spoken form or user interface item. """ + return "folders" + def gotBegin(self,moduleInfo): if self.checkForChanges: self.checkInifile() # refills grammar lists and instance variables @@ -186,25 +196,24 @@ def gotBegin(self,moduleInfo): self.checkForChanges -= 1 if self.mayBeSwitchedOn == 'exclusive': - print("exclusive (_folders), do switchOnOrOff") + self.info("exclusive (_folders), do switchOnOrOff") self.switchOnOrOff() self.progInfo = unimacroutils.getProgInfo() hndle = self.progInfo.hndle classname = self.progInfo.classname - activeFolder = self.getActiveFolder(hndle, classname) - if self.trackAutoFiles or self.trackAutoFolders: + # activeFolder = self.getActiveFolder(hndle, classname) + if self.trackFilesAtUtterance or self.trackSubfoldersAtUtterance: activeFolder = self.getActiveFolder(hndle, classname) - if activeFolder: - self.handleTrackFilesAndFolders(activeFolder) - - if hndle and self.trackFoldersHistory: - self.catchTimerRecentFolders(hndle) + self.handleTrackFilesAndFolders(activeFolder) + + if hndle and self.trackRecentFoldersAtUtterance: + self.catchTimerRecentFolders(hndle, classname) def gotResultsInit(self,words,fullResults): if self.mayBeSwitchedOn == 'exclusive': - print('recog folders, switch off mic') + self.info('recog folders, switch off mic') natbj.SetMic('off') self.wantedFolder = self.wantedFile = self.wantedWebsite = None self.catchRemember = None @@ -213,7 +222,7 @@ def gotResultsInit(self,words,fullResults): # folder options: # CopyName and PasteName refers to the folder, file or website name # Cut, Copy Paste of file or folder is not implemented - self.New = self.Here = self.Remote = self.Git = self.Cut = self.Copy = self.Paste = self.CopyNamePath = self.PastePath = False + self.New = self.Here = self.Remote = self.Cut = self.Copy = self.Paste = self.CopyNamePath = self.PastePath = False # file options: # OpenWith goes via Open. self.Open = self.Edit = None @@ -232,15 +241,15 @@ def handleTrackFilesAndFolders(self, activeFolder): if self.activeFolder: self.emptyListsForActiveFolder() - # print 'empty lists for active folder %s, now: %s'% (self.activeFolder, activeFolder) + self.debug('empty lists for active folder %s, now: %s',self.activeFolder, activeFolder) self.activeFolder = None if activeFolder and os.path.isdir(activeFolder): self.fillListsForActiveFolder(activeFolder) - print('set %s (sub)files and %s subfolders'% (len(self.subfilesDict), len(self.subfoldersDict))) + # nFiles, nFolders = len(self.subfilesDict), len(self.subfoldersDict) + # print(f'set {nFiles} (sub)files and {nFolders} subfolders') self.activeFolder = activeFolder return - print(f'_folders, handleTrackFilesAndFolders, invalid activeFolder: {activeFolder}') def fillList(self, listName): """fill a list in the grammar from the data of the inifile @@ -256,14 +265,14 @@ def fillList(self, listName): # print("foldersSet: %s"% self.foldersSet) self.setList('folders', items) return items - print('no folders to set list to') + self.info('no folders to set list to') self.emptyList('folders') elif listName == 'subfolders': if self.subfoldersDict: items = list(self.subfoldersDict.keys()) self.setList('subfolders', items) return items - print('no subfolders to set list to') + self.info('no subfolders to set list to') self.emptyList('subfolders') elif listName == 'files': @@ -272,17 +281,12 @@ def fillList(self, listName): self.setList('files', items) return items elif listName == 'recentfolders': + self.loadRecentFoldersDict() if self.recentfoldersDict: items = list(self.recentfoldersDict.keys()) self.setList('recentfolders', items) return items - print('no recentfolders in iniChangingData.ini') self.emptyList('recentfolders') - - elif listName in ['gitfilecommands', 'gitfoldercommands']: - if self.doGit: - return ancestor.fillList(self, listName) - self.emptyList(listName) else: return ancestor.fillList(self, listName) return None @@ -290,70 +294,86 @@ def fillList(self, listName): def dumpRecentFoldersDict(self): """for making the dict of recent folders persistent """ - dumpToPickle(self.recentfoldersDict, self.pickleChangingData) + if self.pickleChangingData: + dumpToPickle(self.recentfoldersDict, self.pickleChangingData) + else: + self.info('dumpRecentFoldersDict, no self.pickleChangingData') def loadRecentFoldersDict(self): """for getting the dict of recent folders from previous session """ - result = loadFromPickle(self.pickleChangingData) - if result and isinstance(result, dict): - return result - return {} + if self.pickleChangingData and Path(self.pickleChangingData).is_file(): + result = loadFromPickle(str(self.pickleChangingData)) + if result and isinstance(result, dict): + self.recentfoldersDict = result + return + self.info('no recent folders dict') + self.recentfoldersDict = {} def fillInstanceVariables(self): """fills the necessary instance variables take the lists of folders, virtualdrives (optional) and remotedrives (optional). """ - #pylint:disable=R0914 - self.citrixApps = self.ini.getList('general', 'citrix apps') - if self.citrixApps: - print('_folders does special action for citrixApps: %s'% self.citrixApps) - self.xxExplorer = self.ini.get('general', '2xexplorer') + # valid options, value possibly corresponding to an previous option text + optionsdict = {} + optionsdict['initial on'] = '' + optionsdict['child behaves like top'] = '' + + actualoptions = set(self.ini.get('general')) + + self.useOtherExplorer = self.ini.get('general', 'use other explorer') + optionsdict['use other explorer'] = '' if self.useOtherExplorer: if os.path.isfile(self.useOtherExplorer): - print('_folders, use as default explorer: "%s"'% self.useOtherExplorer) + self.info('_folders, use as default explorer: "%s"', self.useOtherExplorer) else: - print('_folders, variable "use other explorer" set to: "%s" (use data from "actions.ini")'% self.useOtherExplorer) + self.info('_folders, variable "use other explorer" set to: "%s" (use data from "actions.ini")' , self.useOtherExplorer) + + # these are for automatic tracking the current folder at an utterance: + optionsdict['track files at utterance'] = 'automatic track files' + optionsdict['track subfolders at utterance'] = 'automatic track folders' + self.trackSubfoldersAtUtterance = self.ini.getInt('general', 'track subfolders at utterance', 0) + self.trackFilesAtUtterance = self.ini.getInt('general', 'track files at utterance', 0) + optionsdict['track recent folders at utterance'] = '' + optionsdict['max recent folders'] = '' + # track recent folder at gotbegin or with timer: ## callback time in seconds: - interval = self.ini.getFloat('general', 'track folders interval') - if interval: - self.trackFoldersInterval = int(interval*1000) # give in seconds - else: - self.trackFoldersInterval = 4000 # default 5 seconds - + optionsdict['timer track folders interval'] = '' + interval = self.ini.getInt('general', 'timer track folders interval', 0) # default 0 (off). + if interval and interval > 100: + self.warning(f'_folders, warning, "timer track folders interval" should be set in seconds, not {interval}') + interval = 0 + self.trackFoldersTimerInterval = int(interval*1000) # give in seconds self.recentfoldersDict = {} - inipath = self.ini.getFilename() - if inipath.endswith('.ini'): - _changingDataIniPath = inipath.replace(".ini", "changingdata.pickle") - self.pickleChangingData = inipath.replace(".ini", "changingdata.pickle") - else: - self.pickleChangingData = "" - - ## automatic tracking of recent folders : - self.trackFoldersHistory = self.ini.getInt('general', 'track folders history') - if self.trackFoldersHistory: - if self.pickleChangingData: - self.recentfoldersDict = self.loadRecentFoldersDict() - if self.recentfoldersDict: - print("recentfolders, set %s keys from _folderschangingdata.pickle"% len(self.recentfoldersDict)) - else: - print("recentfolder, no previous recentfolders cached in _folderschangingdata.pickle") - self.doTrackFoldersHistory = True # can be started or stopped with command + self.trackRecentFoldersAtUtterance = self.ini.getBool('general', 'track recent folders at utterance') + self.maxRecentFolders = 0 + + self.pickleChangingData = str(Path(status.getUnimacroDataDirectory())/"recentfoldersdata.pickle") + if self.trackFoldersTimerInterval or self.trackRecentFoldersAtUtterance: + + optionsdict['max recent folders'] = '' + self.maxRecentFolders = self.ini.getInt('general', 'max recent folders', 50) + self.doTrackRecentFolders = True # can be started or stopped with command # recent [folders] START or recent [folders] STOP - intervalSeconds = self.trackFoldersInterval/1000 - print('maintain a list of %s recent folders (Explorer or File Dialog) at every utterance and every %s seconds'% (self.trackFoldersHistory, intervalSeconds)) - natlinktimer.setTimerCallback(self.catchTimerRecentFolders, self.trackFoldersInterval) # every 5 seconds + intervalSeconds = int(self.trackFoldersTimerInterval/1000) + if self.trackFoldersTimerInterval or self.trackRecentFoldersAtUtterance: + if not self.trackFoldersTimerInterval: + track_message=f'maintain a list of (max) {self.maxRecentFolders} recent folders (Explorer or File Dialog) at every utterance' + elif not self.trackRecentFoldersAtUtterance: + track_message=f'maintain a list of (max) {self.maxRecentFolders} recent folders (Explorer or File Dialog) every {intervalSeconds} seconds' + else: + track_message=f'maintain a list of (max) {self.maxRecentFolders} recent folders (Explorer or File Dialog) at every utterance and every {intervalSeconds} seconds' + self.info(track_message) + if self.trackFoldersTimerInterval: + natlinktimer.setTimerCallback(self.catchTimerRecentFolders, self.trackFoldersTimerInterval) # every 5 seconds default... else: - self.doTrackFoldersHistory = False - if self.doTrackFoldersHistory: - rfList = self.ini.get('recentfolders') - for key in rfList: - value = self.ini.get('recentfolders', key) - self.recentfoldersDict[key] = value + self.doTrackRecentFolders = False + + # virtual drives: # extract special variables from ini file: self.virtualDriveDict = {} wantedVirtualDriveList = self.ini.get('virtualdrives') @@ -367,36 +387,29 @@ def fillInstanceVariables(self): foldersList = self.ini.get('folders') self.foldersDict = {} for f in foldersList: - folder = self.substituteFolder(self.ini.get('folders', f)) + raw_folder = self.ini.get('folders', f) + folder = self.substituteFolder(raw_folder) if not os.path.isdir(folder): - print(f'warning _folders, folder "{f}" does not exist: "{folder}"') + self.warning(f'warning _folders, folder "{f}" does not exist: "{folder}"') # self.ini.delete('folders', f) # self.ini.set('obsolete folders', f, folder) continue self.foldersDict[f] = folder # track virtual drives if in ini file: + optionsdict['track folders virtualdrives'] = '' + optionsdict['track files virtualdrives'] = '' self.trackFolders = self.ini.getList('general', 'track folders virtualdrives') self.trackFiles = self.ini.getList('general', 'track files virtualdrives') - # below this threshold, the getting of subfolders and files in a directory is not printed in the messages window - self.notifyThresholdMilliseconds = self.ini.getInt("general", "notify threshold milliseconds", 10) - print("_folders, notify threshold milliseconds: %s"% self.notifyThresholdMilliseconds) - # in order to accept .py but it should be (for fnmatch) *.py etc.: + + # in order to accept files in the list but it should be (for fnmatch) *.py etc.: + optionsdict['track file extensions'] = '' + optionsdict['ignore file patterns'] = '' + self.acceptFileExtensions = self.ini.getList('general', 'track file extensions') self.ignoreFilePatterns = self.ini.getList('general', 'ignore file patterns') - # these are for automatic tracking the current folder: - self.trackAutoFolders = self.ini.getBool('general', 'automatic track folders') - self.trackAutoFiles = self.ini.getBool('general', 'automatic track files') - self.doGit = self.ini.get('general', 'git executable') - if self.doGit: - if not os.path.isfile(self.doGit): - print('not a valid path to git executable: %s, ignore'% self.doGit) - self.doGit = None - if not self.doGit: - self.iniIgnoreGrammarLists.extend(['gitfoldercommands', 'gitfilecommands']) - self.foldersSections = ['folders'] # track folders: for trf in self.trackFolders: @@ -405,7 +418,7 @@ def fillInstanceVariables(self): trf2 = self.substituteFolder(trf) #print 'input: %s, output: %s'% (trf, trf2) if not os.path.isdir(trf2): - print('warning, no valid folder associated with: %s (%s) (skip for track virtualdrives)'% (trf, trf2)) + self.warning('warning, no valid folder associated with: %s (%s) (skip for track virtualdrives)', trf, trf2) continue #else: # print 'valid folder for tracking: %s (%s)'% (trf, trf2) @@ -425,8 +438,6 @@ def fillInstanceVariables(self): self.filesDict = {} self.trackFiles = self.ini.getList('general', 'track files virtualdrives') # in order to accept .py but it should be (for fnmatch) *.py etc.: - self.acceptFileExtensions = self.ini.getList('general', 'track file extensions') - self.ignoreFilePatterns = self.ini.getList('general', 'ignore file patterns') self.filesSections = ['files'] # from section files (manual): filesList = self.ini.get('files') @@ -434,7 +445,7 @@ def fillInstanceVariables(self): for f in filesList[:]: filename = self.substituteFilename(self.ini.get('files', f)) if not os.path.isfile(filename): - print(f'warning _folders, file "{f}" does not exist: "{filename}"') + self.warning(f'warning _folders, file "{f}" does not exist: "{filename}"') # self.ini.delete('files', f) # self.ini.set('obsolete files', f, filename) continue @@ -445,7 +456,7 @@ def fillInstanceVariables(self): continue trf2 = self.substituteFolder(trf) if not os.path.isdir(trf2): - print('warning, no valid folder associated with: %s (%s) (skip for track files)'% (trf, trf2)) + self.warning('warning, no valid folder associated with: %s (%s) (skip for track files)', trf, trf2) continue filesList = [f for f in os.listdir(trf2) if os.path.isfile(os.path.join(trf2, f))] self.trackFilesSection = 'files %s'% trf @@ -460,8 +471,26 @@ def fillInstanceVariables(self): # self.childBehavesLikeTop = self.ini.getDict('general', 'child behaves like top') # self.topBehavesLikeChild = self.ini.getDict('general', 'top behaves like child') # save changes if there were any: - self.ini.writeIfChanged() + self.ini.writeIfChanged() + + self.checkValidOptions(optionsdict, actualoptions) + def checkValidOptions(self, optionsdict, actualoptions): + """check if all options given are valid and give feedback + """ + validoptions = set(optionsdict) + oldoptions = actualoptions - validoptions + if oldoptions: + self.info(f'obsolete options: {oldoptions}') + # give new option, if available: + for old in oldoptions: + for k,v in optionsdict.items(): + if v == old: + self.info(f'replace option "{old}" into "{k}" please') + break + unusedoptions = validoptions - actualoptions + for unused in unusedoptions: + self.warning(f'-- option "{unused}" is not set, grammar "_folders",\n\tplease set (possibly without value) in section [general]') def fillGrammarLists(self, listOfLists=None): """fills the lists of the grammar with data from inifile @@ -506,7 +535,7 @@ def resolveVirtualDrives(self, wantedVirtualDrives): if wantedVirtualDrives: textline = ", ".join(wantedVirtualDrives) - print(f'Warning: could not resolve "virtualdrive" entries: {textline}, ignore') + self.warning(f'Warning: could not resolve "virtualdrive" entries: {textline}, ignore') # for dr in wantedVirtualDrives: # virtualDrive = self.ini.get('virtualdrives', dr) # self.ini.delete('virtualdrives', dr) @@ -570,43 +599,36 @@ def getActiveFolder(self, hndle=None, className=None): if not hndle: # print("getActiveFolder, not a foreground hndle found: %s"% hndle) return None - if className is None: + try: className = win32gui.GetClassName(hndle) - # print 'getActiveFolder, className: %s'% className + except Exception as e: + if e.args[0] == 1400: + print(f'exception: {e}') + else: + print(f'unexpected exception: {e}') + return None + if not className: return None f = None if className == "CabinetWClass": f = mess.getFolderFromCabinetWClass(hndle) - # if f and f.startswith("search-ms"): - # keystroke("{esc}") - # unimacroutils.Wait() - # f = mess.getFolderFromDialog(hndle, className) - if not f: - print("getActiveFolder, CabinetWClass failed: %s"% hndle) elif className == '#32770': f = mess.getFolderFromDialog(hndle, className) - if not f: - return None - # if not f: - # print "getActiveFolder, #32770 failed: %s"% hndle - else: - # print 'class for activeFolder: %s'% className - return None if not f: - if className == 'CabinetWClass': - print('_folders, getActiveFolder, no folder found in className %s'% className) + self.prevActiveFolder = f return None if os.path.isdir(f): nf = os.path.normpath(f) - # print("getActiveFolder: %s"% nf) + if nf != self.prevActiveFolder: + self.debug("getActiveFolder, got: %s",nf) + self.prevActiveFolder = nf return nf - # print("folder in getActiveFolder: %s"% f) - realFolder = extenvvars.getFolderFromLibraryName(f) - if realFolder: - # print("getActiveFolder realFolder for %s: %s"% (f, realFolder)) - return realFolder - print('_folders, getActiveFolder, could not find folder for %s'% f) + result = extenvvars.getFolderFromLibraryName(f) + if result and os.path.isdir(f): + self.debug("getActiveFolder, via getFolderFromLibraryName %s: %s", f, result) + return os.path.normpath(result) + self.warning('getActiveFolder, strange invalid path for folder: %s', f) return None def fillListsForActiveFolder(self, activeFolder): @@ -615,47 +637,53 @@ def fillListsForActiveFolder(self, activeFolder): this is for the automatic filling of the active window (either explorer, CabinetWClass, or child #32770. - - Seems to fail in windows XP and before. - + """ subs = os.listdir(activeFolder) # print 'subs: %s'% subs subfolders = [s for s in subs if os.path.isdir(os.path.join(activeFolder, s))] subfiles = [s for s in subs if os.path.isfile(os.path.join(activeFolder, s))] - self.subfoldersDict = self.getSpokenFormsDict(subfolders) - self.subfilesDict = self.getSpokenFormsDict(subfiles, extensions=1) + if self.trackFilesAtUtterance: + if len(subfiles) > self.trackFilesAtUtterance: + self.info(f'_folders, only set first {self.trackFilesAtUtterance} files, total: {len(subfiles)}') + subfiles = subfiles[:self.trackFilesAtUtterance] + self.subfilesDict = self.getSpokenFormsDict(subfiles, extensions=1) + else: + self.subfilesDict = {} + + if self.trackSubfoldersAtUtterance: + if len(subfolders) > self.trackSubfoldersAtUtterance: + self.info(f'_folders, only set first {self.trackSubfoldersAtUtterance} subfolders of total: {len(subfolders)}') + subfolders = subfolders[:self.trackSubfoldersAtUtterance] + self.subfoldersDict = self.getSpokenFormsDict(subfolders) + else: + self.subfoldersDict = {} + # print 'activeFolder, %s, subfolders: %s'% (activeFolder, self.subfoldersDict.keys()) # print 'activeFolder, %s, subfiles: %s'% (activeFolder, self.subfilesDict.keys()) # print 'activeFolder, %s, subfiles: %s'% (activeFolder, self.subfilesDict) - if self.trackAutoFiles and self.subfilesDict: + if self.subfilesDict: self.setList('subfiles', list(self.subfilesDict.keys())) - if self.trackAutoFolders and self.subfoldersDict: - n0 = time.time() + if self.subfoldersDict: self.setList('subfolders', list(self.subfoldersDict.keys())) - n1 = time.time() - elapsed = int((n1 - n0)*1000) - if elapsed > self.notifyThresholdMilliseconds: - print('set %s subfolders in %s milliseconds'% (len(list(self.subfoldersDict.keys())), elapsed)) self.activeFolder = activeFolder + if self.subfilesDict and self.subfoldersDict: + self.info(f'activeFolder, set {len(subfiles)} files and {len(subfolders)} subfolders') + elif self.subfilesDict: + self.info(f'activeFolder, set {len(subfiles)} files') + elif self.subfoldersDict: + self.info(f'activeFolder, set {len(subfolders)} subfolders') + def emptyListsForActiveFolder(self): """no sublists, empty """ - n0 = time.time() - lenSubFolders = len(self.subfoldersDict) - lenSubFiles = len(self.subfilesDict) - if self.trackAutoFiles: + if self.trackFilesAtUtterance: self.emptyList('subfiles') self.subfilesDict.clear() - if self.trackAutoFolders: + if self.trackSubfoldersAtUtterance: self.emptyList('subfolders') self.subfoldersDict.clear() - n1 = time.time() - elapsed = int((n1 - n0)*1000) - if elapsed: - # if elapsed > self.notifyThresholdMilliseconds: - print('emptyListsForActiveFolder: emptied %s subfolders and %s (sub)files in %s milliseconds'% (lenSubFolders, lenSubFiles, elapsed)) self.activeFolder = None @@ -668,10 +696,10 @@ def cleanupIniFoldersSection(self, section, vd): continue folder = self.substituteFolder(vd + ':/' + f) if not os.path.isdir(folder): - print('remove entry from ini folders section %s: %s (%s)'% (section, f, folder)) + self.info('remove entry from ini folders section %s: %s (%s)', section, f, folder) self.ini.delete(section, f) elif not self.acceptFileName(f): - print('remove entry from ini folders section %s: %s (%s)(invalid folder name)'% (section, f, folder)) + self.info('remove entry from ini folders section %s: %s (%s)(invalid folder name)' ,section, f, folder) self.ini.delete(section, f) self.ini.writeIfChanged() @@ -682,13 +710,13 @@ def cleanupIniFilesSection(self, section, vd): filename = self.substituteFolder(vd + ':/' + f) trunk, ext = os.path.splitext(f) if not self.acceptExtension(ext): - print('remove entry from ini files section %s: %s (%s)(invalid extension)'% (section, f, filename)) + self.info('remove entry from ini files section %s: %s (%s)(invalid extension)', section, f, filename) self.ini.delete(section, f) elif not self.acceptFileName(trunk): - print('remove entry from ini files section %s: %s (%s)(invalid filename)'% (section, f, filename)) + self.info('remove entry from ini files section %s: %s (%s)(invalid filename)', section, f, filename) self.ini.delete(section, f) elif not os.path.isfile(filename): - print('remove entry from ini files section %s: %s (%s)'% (section, f, filename)) + self.info('remove entry from ini files section %s: %s (%s)', section, f, filename) self.ini.delete(section, f) self.ini.writeIfChanged() @@ -705,7 +733,7 @@ def removeObsoleteIniSections(self, prefix, validPostfixes): if section == prefix + postfix: break else: - print('_folders grammar, deleting ini file section: %s'% section) + self.info('_folders grammar, deleting ini file section: %s', section) self.ini.delete(section) self.ini.writeIfChanged() @@ -745,24 +773,30 @@ def catchTimerRecentFolders(self, hndle=None, className=None): Or with the subfolder or folder ... on virtual drive command. - Whenever there is a folder in the foreground, is is cached as recentfolder. + Whenever there is a folder in the foreground, it is cached as recentfolder. When the buffer grows too large, the first inserted items are removed from the list - (QH, March 2020) + (QH, March 2020, Febr 2024) """ # print('_folders, catchTimerRecentFolders') - activeFolder = self.getActiveFolder(hndle, className) - if not activeFolder: - return - if activeFolder == self.activeFolder: - return - self.activeFolder = activeFolder - # print(f'get new folders for {activeFolder}') - # activeFolder = os.path.normcase(activeFolder) - if self.recentfoldersDict and activeFolder == list(self.recentfoldersDict.values())[-1]: + if self.inTimerRecentFolders: return - spokenName = self.getFolderBasenameRemember(activeFolder) - self.manageRecentFolders(spokenName, activeFolder) + self.inTimerRecentFolders = True + try: + activeFolder = self.getActiveFolder(hndle, className) + if not activeFolder: + return + if activeFolder == self.activeTimerFolder: + return + self.activeTimerFolder = activeFolder + # activeFolder = os.path.normcase(activeFolder) + if self.recentfoldersDict and activeFolder == list(self.recentfoldersDict.values())[-1]: + return + spokenName = self.getFolderBasenameRemember(activeFolder) + # print(f'add activeFolder "{activeFolder}" to recent, spoken name: "{spokenName}"') + self.manageRecentFolders(spokenName, activeFolder) + finally: + self.inTimerRecentFolders = False def manageRecentFolders(self, Spoken, Folder): """manage the internals of the recent folders dict @@ -771,38 +805,33 @@ def manageRecentFolders(self, Spoken, Folder): Or from the subfolder or folder ... on virtual drive commands """ # first see if the buffer needs to be shrinked: - buffer = max(10, self.trackFoldersHistory//10) - if len(self.recentfoldersDict) > self.trackFoldersHistory + buffer: - print("shrink recentfoldersDict with %s items to %s"% (buffer, self.trackFoldersHistory)) - while len(self.recentfoldersDict) >= self.trackFoldersHistory: - keysList = list(self.recentfoldersDict.keys()) - _removeItem = self.recentfoldersDict.pop(keysList[0]) - # print('_folders, remove from recent folders: %s (%s)'% (keysList[0], removeItem)) - # print("refilling recentfolders list with %s items'"% len(self.recentfoldersDict)) - self.setList('recentfolders', list(self.recentfoldersDict.keys())) - self.dumpRecentFoldersDict() - # self.pickleChangingData.delete('recentfolders') - # for key, value in self.recentfoldersDict.items(): - # # self.pickleChangingData.set("recentfolders", key, value) - # self.pickleChangingData.writeIfChanged() + buffer = max(10, self.maxRecentFolders//10) + self.info(f'manageRecentFolders buffer: {buffer}, self.maxRecentFolders: {self.maxRecentFolders}, len(recentfoldersDict): {len(self.recentfoldersDict)}') + if self.recentfoldersDict: + if len(self.recentfoldersDict) > self.maxRecentFolders + buffer: + self.info("shrink recentfoldersDict with %s items to %s", buffer, self.maxRecentFolders) + while len(self.recentfoldersDict) >= self.maxRecentFolders: + keysList = list(self.recentfoldersDict.keys()) + _removeItem = self.recentfoldersDict.pop(keysList[0]) + # print('_folders, remove from recent folders: %s (%s)'% (keysList[0], removeItem)) + # print("refilling recentfolders list with %s items'"% len(self.recentfoldersDict)) + self.setList('recentfolders', list(self.recentfoldersDict.keys())) + self.dumpRecentFoldersDict() if not Spoken: return if Spoken in self.recentfoldersDict: spokenFolder = self.recentfoldersDict[Spoken] if spokenFolder == Folder: + self.info(f'readd {Spoken} to recentfoldersDict') del self.recentfoldersDict[Spoken] self.recentfoldersDict[Spoken] = Folder self.dumpRecentFoldersDict() - # self.pickleChangingData.set("recentfolders", Spoken, Folder) # print('re-enter Folder in recent folders: %s (%s)'% (Spoken, Folder)) elif Folder not in self.foldersSet: - print('-- "recent [folder] %s": %s\nNote: "folder %s", points to: %s'% (Spoken, Folder, Spoken, spokenFolder)) + # print('-- "recent [folder] %s": %s\nNote: "folder %s", points to: %s'% (Spoken, Folder, Spoken, spokenFolder)) del self.recentfoldersDict[Spoken] self.recentfoldersDict[Spoken] = Folder self.dumpRecentFoldersDict() - ## try to maintain order: - # self.pickleChangingDatahangingData.delete('recentfolders', Spoken) - # self.pickleChangingData.set("recentfolders", Spoken, Folder) else: # print('adding Folder in recent folders: %s (%s)'% (Spoken, Folder)) self.recentfoldersDict[Spoken] = Folder @@ -812,18 +841,25 @@ def manageRecentFolders(self, Spoken, Folder): # self.pickleChangingData.writeIfChanged() def startRecentFolders(self): - self.doTrackFoldersHistory = True + self.doTrackRecentFolders = True self.fillList('recentfolders') - natlinktimer.setTimerCallback(self.catchTimerRecentFolders, self.trackFoldersInterval) # should have milliseconds - print("track folders history is started, the timer callback is on") + timerInterval = self.trackFoldersTimerInterval + if timerInterval: + self.info(f'start timer interval {timerInterval} milliseconds') + else: + timerInterval = 1000 + self.info(f'start timer with interval {timerInterval} milliseconds, for this session only') + natlinktimer.setTimerCallback(self.catchTimerRecentFolders, self.trackFoldersTimerInterval) # should have milliseconds def stopRecentFolders(self): - self.doTrackFoldersHistory = True + self.doTrackRecentFolders = False natlinktimer.setTimerCallback(self.catchTimerRecentFolders, 0) self.dumpRecentFoldersDict() self.recentfoldersDict = {} self.emptyList('recentfolders') - print("track folders history is stopped, the timer callback is off") + self.info("the track recent folders timer is stopped, for this session" if self.trackFoldersTimerInterval \ + else "the track recent folders timer is stopped.") + def resetRecentFolders(self): self.recentfoldersDict = {} @@ -831,20 +867,25 @@ def resetRecentFolders(self): # self.pickleChangingData.delete('recentfolders') # self.pickleChangingData.writeIfChanged() self.emptyList('recentfolders') - + def displayRecentFolders(self): """display the list of recent folders """ - message = ["_folders, recent folders:"] - for name, value in self.recentfoldersDict.items(): - message.append('- %s: %s'% (name, value)) - message.append('-'*20) - message = '\n'.join(mess) - if message == self.prevDisplayRecentFolders: - print("recent folders, no change") + mess_list = ["--- recent folders:"] + if not self.recentfoldersDict: + message = 'recent folders list is empty at the moment' + self.prevDisplayRecentFolders = message + self.info(message) return - self.prevDisplayRecentFolders = message - print(message) + for name, value in reversed(self.recentfoldersDict.items()): + mess_list.append('- %s: %s'% (name, value)) + mess_list.append('-'*20) + message = '\n'.join(mess_list) + if message == self.prevDisplayRecentFolders: + self.info("recent folders, no change") + else: + self.prevDisplayRecentFolders = message + Message(message) # def gotoRecentFolder(self, chooseNum): @@ -854,94 +895,6 @@ def displayRecentFolders(self): # wantedFolder = self.recentfoldersList[chooseNum] # self.gotoFolder(wantedFolder) - def gotResults_siteshort(self,words,fullResults): - """switch to last mentioned site in the list - mainly for private use, a lot of folders reside in the root folder, - siteRoot. They all have an input folder and a output folder. - - - """ - if self.lastSite: - words.insert(1, self.lastSite) - print('lastSite: %s'% words) - self.gotResults_site(words, fullResults) - else: - self.DisplayMessage('no "lastSite" available yet') - - - def gotResults_setenvironmentfolders(self,words,fullResults): - """switch to last mentioned site in the list - mainly for private use, a lot of folders reside in the root folder, - siteRoot. They all have an input folder and a output folder. - - """ - reverseOldValues = {'ignore': []} - for k in self.ini.get('folders'): - val = self.ini.get('folders', k) - if val: - reverseOldValues.setdefault(val, []).append(k) - else: - reverseOldValues['ignore'].append(k) - reverseVirtualDrives = {} - for k in self.ini.get('virtualdrives'): - val = self.ini.get('virtualdrives', k) - reverseVirtualDrives.setdefault(val, []).append(k) - -## print reverseOldValues - allFolders = copy.copy(self.envDict) # natlinkcorefunctions.getAllFolderEnvironmentVariables() - kandidates = {} - ignore = reverseOldValues['ignore'] - for (k,v) in list(allFolders.items()): - kSpeakable = k.replace("_", " ") - if k in ignore or kSpeakable in ignore: - continue - oldV = self.ini.get('folders', k, "") or self.ini.get('folders', kSpeakable) - if oldV: - vPercented = "%" + k + "%" - if oldV == v: - continue - if oldV == vPercented: - kPrevious = reverseOldValues[vPercented] -## print 'vPercented: %s, kPrevious: %s'% (vPercented, kPrevious) - if vPercented in reverseOldValues: - if k in kPrevious or kSpeakable in kPrevious: - continue - print('already in there: %s (%s), but spoken form changed to %s'% \ - (k, v, kPrevious)) - continue - else: - print('different for %s: old: %s, new: %s'% (k, oldV, v)) - kandidates[k] = v - count = len(kandidates) - - if not kandidates: - self.DisplayMessage("no new environment variables to put into the folders section") - return - mes = ["%s new environment variables for your folders section of the grammar _folders"% count] - - Keys = list(kandidates.keys()) - Keys.sort() - for k in Keys: - mes.append("%s\t\t%s"% (k, kandidates[k])) - - mes.append('\n\nDo you want these new environment variables in your folders section?') - - - - if YesNo('\n'.join(mes)): - for (k,v) in list(kandidates.items()): - if k.find('_') > 0: - kSpeakable = k.replace("_", " ") - if self.ini.get('folders', k): - self.ini.delete('folders', k) - else: - kSpeakable = k - self.ini.set('folders', kSpeakable, "%" + k + "%") - self.ini.write() - self.DisplayMessage('added %s entries, say "Show|Edit folders" to browse'% count) - else: - self.DisplayMessage('nothing added, command canceled') - def gotResults_website(self,words,fullResults): """start webbrowser, websites in inifile unders [websites] @@ -978,7 +931,7 @@ def gotResults_thiswebsite(self,words,fullResults): self.wantedWebsite = unimacroutils.getClipboard() self.wantedWebsite = self.wantedWebsite.rstrip("/") self.catchRemember = "website" - print('this website: %s'% self.wantedWebsite) + self.info('this website: %s', self.wantedWebsite) unimacroutils.restoreClipboard() if self.hasCommon(words, "remember"): ## dgndictation is not used at the moment!! @@ -988,11 +941,11 @@ def gotResults_thiswebsite(self,words,fullResults): self.checkForChanges = True spokenWebsite = self.getWebsiteBasenameRemember(self.wantedWebsite) if not spokenWebsite: - print("_folders, could not extract a nice spoken website from %s\nTry "% self.wantedWebsite) - print('Try "this website remember as "') + self.info("_folders, could not extract a nice spoken website from %s\nTry ", self.wantedWebsite) + self.info('Try "this website remember as "') return self.ini.set("websites", spokenWebsite, self.wantedWebsite) - print('with "website %s" you can now open %s'% (spokenWebsite, self.wantedWebsite)) + self.info('with "website %s" you can now open %s', spokenWebsite, self.wantedWebsite) self.ini.write() def getWebsiteBasenameRemember(self, url): @@ -1014,7 +967,7 @@ def getFileBasenameRemember(self, filePath): if not spokenList: return namePart if len(spokenList) > 1: - print('getFileBasenameRemember, more spoken alternatives found: %s, return first item'% spokenList) + self.info('getFileBasenameRemember, more spoken alternatives found: %s, return first item', spokenList) return spokenList[0] # def checkSubfolderRecent(self, name, folder): @@ -1067,7 +1020,7 @@ def getFolderBasenameRemember(self, folderPath): if not spokenList: return namePart if len(spokenList) > 1: - print('getFolderBasenameRemember, more spoken alternatives found: %s'% spokenList) + self.info('getFolderBasenameRemember, more spoken alternatives found: %s', spokenList) return spokenList[0] @@ -1079,7 +1032,7 @@ def gotResults_websitecommands(self,words,fullResults): open with list in inifile, expected right hand sides to be browsers """ if not self.wantedWebsite: - print('websitecommands, no valid self.wantedWebsite: %s'% self.wantedWebsite) + self.info('websitecommands, no valid self.wantedWebsite: %s', self.wantedWebsite) nextOpenWith = False @@ -1090,7 +1043,7 @@ def gotResults_websitecommands(self,words,fullResults): self.Open = self.getFromInifile(w, 'websiteopenprograms') nextOpenWith = False else: - print("unexpected website option: %s"% w) + self.warning("unexpected website option: %s", w) def gotResults_subfolder(self, words, fullResults): """collects the given command words and try to find the given subfolder @@ -1104,10 +1057,9 @@ def gotResults_subfolder(self, words, fullResults): if self.activeFolder and folderWord in self.subfoldersDict: subfolder = self.subfoldersDict[folderWord] folder = os.path.join(self.activeFolder, subfolder) - print('subfolder: %s'% folder) else: - print('cannot find subfolder: %s'% folderWord) - print('subfoldersDict: %s'% self.subfoldersDict) + self.info('cannot find subfolder: %s', folderWord) + self.info('subfoldersDict: %s', self.subfoldersDict) return # subfolder = None # folder1 = self.foldersDict[words[1]] @@ -1116,8 +1068,7 @@ def gotResults_subfolder(self, words, fullResults): # if no next rule, simply go: self.wantedFolder = folder self.Here = True - if doRecentFolderCommand: - self.manageRecentFolders(folderWord, folder) + self.manageRecentFolders(folderWord, folder) def gotResults_recentfolder(self,words,fullResults): """give list of recent folders and choose option @@ -1127,7 +1078,7 @@ def gotResults_recentfolder(self,words,fullResults): return if self.hasCommon("RESET", words[-1]): self.resetRecentFolders() - print("Reset recent folders list") + self.info("Reset recent folders list") return if self.hasCommon("START", words[-1]): self.startRecentFolders() @@ -1137,133 +1088,12 @@ def gotResults_recentfolder(self,words,fullResults): return if not self.recentfoldersDict: - print("no recentfolders yet") + self.info("no recentfolders yet") return name = words[-1] folder = self.recentfoldersDict[name] - print("recentfolder, name: %s, folder: %s"% (name, folder)) + self.info("recentfolder, name: %s, folder: %s", name, folder) self.wantedFolder = folder - - def gotResults_site(self,words,fullResults): - """switch to one of the sites in the list - mainly for private use, a lot of folders reside in the root folder, - siteRoot. They all have an input folder and a output folder. - - """ - print('site: %s'% words) - siteSpoken = words[1] - self.lastSite = None # name of site - if siteSpoken in self.sitesDict: - siteName = self.sitesDict[siteSpoken] - self.lastSite = siteName - else: - raise ValueError("no siteName for %s"% siteSpoken) - - self.site = self.getSiteInstance(siteName) - - if siteName in self.sitesInstances: - self.site = self.sitesInstances[siteName] - else: - site = self.getSiteInstance(siteName) - if site: - self.sitesInstances[siteName] = site - self.lastSite = siteName - self.site = site - else: - self.site = None - print('could not get site: %s'% siteName) - # - #if site is None: - # print 'invalid site: %s, marking in ini file'% site - # self.ini.set('sites', siteName, '') - # self.ini.write() - # return - if self.nextRule == 'sitecommands': - print('site %s, waiting for sitecommands'% self.site) - else: - if self.site: - self.wantedFolder = self.site.rootDir - else: - print("_folders, site command (private QH), no site specified") - - def gotResults_sitecommands(self, words, fullResults): - """do the various options for sites (QH special). - Assume lastSite is set - """ - if not self.site: - print("sitecommands, no last or current site set") - return - print('sitecommands for "%s": %s (site: %s)'% (self.lastSite, words, self.site)) - site = self.site - website, folder = None, None - for command in words: - command = self.getFromInifile(words[0], 'sitecommands') - - if command == 'input': - print('input: %s'% words) - folder = str(site.sAbs) - elif command == 'output': - folder = str(site.hAbs) - elif command == 'local': - website = os.path.join(str(site.hAbs), 'index.html') - elif command == 'online': - sitePrefix = site.sitePrefix - if isinstance(sitePrefix, dict): - for v in sitePrefix.values(): - sitePrefix = v - break - - website = os.path.join(str(sitePrefix), 'index.html') - elif command == 'testsite': - if 'sg' in self.sitesInstances: - testsite = self.sitesInstances['sg'] - else: - testsite = self.getSiteInstance('sg') - if testsite: - self.sitesInstances['sg'] = testsite - - if testsite: - # site at sitegen site: - website = os.path.join(str(testsite.sitePrefix['nl']), self.lastSite, 'index.html') - - if self.nextRule: - if folder: - self.wantedFolder = folder - return - if website: - self.wantedWebsite = website - return - print('no valid folder or website for nextRule') - return - if folder: - self.gotoFolder(folder) - self.wantedFolder = None - elif website: - self.gotoWebsite(website) - self.wantedWebsite = None - - - def getSiteInstance(self, siteName): - """return pageopen function of site instance, or None - """ - try: - site = __import__(siteName) - except ImportError: - print('cannot import module %s'% siteName) - print(traceback.print_exc()) - currentDir = '.' in sys.path - print('currentDir in sys.path: %s'% currentDir) - print('sys.path: %s'% sys.path) - return None - if 'pagesopen' in dir(site): - try: - po = site.pagesopen() - return po - except: - print('"pagesopen" failed for site %s'% siteName) - return None - print('no function "pagesopen" in module: %s'% siteName) - return None def findFolderWithIndex(self, root, allowed, ignore=None): """get the first folder with a file index.html""" @@ -1310,61 +1140,53 @@ def gotResults_foldercommands(self, words, fullResults): different) """ if not self.wantedFolder: - print('rule foldercommands, no wantedFolder, return') + self.info('rule foldercommands, no wantedFolder, return') return - nextGit = nextRemote = False + nextRemote = False for w in words: if self.hasCommon(w, 'here'): - print("got Here: ", w) + self.info(f"got Here: {w}") self.Here = True elif self.hasCommon(w, 'new'): - print("got New: ", w) + self.info(f"got New: {w}") self.New = True elif self.hasCommon(w, 'paste'): - print("got Paste, set PastePath: ", w) + self.info(f"got Paste, set PastePath: {w}") self.PastePath = True elif self.hasCommon(w, 'on'): - print("got Remote: ", w) + self.info("got Remote: {w}") nextRemote = True elif nextRemote: remoteLetter = self.getFromInifile(w, 'letters', noWarning=1) remoteVirtualDrive = self.getFromInifile(w, 'virtualdrivesspoken', noWarning=1) if remoteLetter: - print('remoteLetter: %s'% remoteLetter) + self.info('remoteLetter: %s', remoteLetter) self.Remote = remoteLetter.upper() + ":" elif remoteVirtualDrive: self.Remote = self.virtualDriveDict[remoteVirtualDrive] - print('remoteVirtualDrive: %s, resolves to: %s'% (remoteVirtualDrive, self.Remote)) + self.info('remoteVirtualDrive: %s, resolves to: %s', remoteVirtualDrive, self.Remote) nextRemote = False - elif self.hasCommon(w, 'git'): - print("got Git: ", w) - nextGit = True - elif nextGit: - print("got gitCommand: ", w) - gitCommand = self.getFromInifile(w, 'gitfoldercommands') - self.Git = gitCommand - nextGit = False # again else: opt = self.getFromInifile(w, 'foldercommands') - print("got FolderOptions: ", opt) + self.info("got FolderOptions: %s", opt) self.FolderOptions.append(opt) def gotResults_namepathcopy(self, words, fullResults): """copy the name or the path of a folder, file or website """ if not self.catchRemember: - print("_folders, namepathcopy, do not know what to copy, folder, file or website") + self.info("_folders, namepathcopy, do not know what to copy, folder, file or website") return if self.hasCommon(words, "name"): what = "name" elif self.hasCommon(words, "path"): what = "path" else: - print("_folders, namepathcopy, choose copy name or path, not: %s"% repr(words)) + self.info("_folders, namepathcopy, choose copy name or path, not: %s", repr(words)) return if self.catchRemember == "folder": if not self.wantedFolder: - print("_folders, namepathcopy, no valid folder") + self.info("_folders, namepathcopy, no valid folder") return self.wantedFolder = self.wantedFolder.rstrip("/\\") if what == "name": @@ -1373,7 +1195,7 @@ def gotResults_namepathcopy(self, words, fullResults): result = self.wantedFolder elif self.catchRemember == "file": if not self.wantedFile: - print("_folders, namepathcopy, no valid file") + self.info("_folders, namepathcopy, no valid file") return if what == "name": result = os.path.split(self.wantedFile)[-1] @@ -1382,20 +1204,20 @@ def gotResults_namepathcopy(self, words, fullResults): elif self.catchRemember == "website": if not self.wantedWebsite: - print("_folders, namepathcopy, no valid website") + self.info("_folders, namepathcopy, no valid website") return if what == 'name': result = self.wantedWebsite.split("/")[-1] else: result = self.wantedWebsite.split()[-1] - print('namepathcopy, result: %s (type: %s)'% (result, type(result))) + self.info('namepathcopy, result: %s (type: %s)', result, type(result)) unimacroutils.setClipboard(result, 13) # 13 unicode!! def gotResults_remember(self, words, fullResults): """treat the remember function, filling items in ini files """ if not self.catchRemember: - print('_folders, in remember rule, but nothing to remember') + self.info('_folders, in remember rule, but nothing to remember') return if self.catchRemember == "folder": self.rememberBase = self.getFolderBasenameRemember(self.wantedFolder) @@ -1426,7 +1248,7 @@ def gotResults_remember(self, words, fullResults): default = self.rememberBase section = 'files' else: - print('_folders, invalid value for catchRemember: %s'% self.catchRemember) + self.info('_folders, invalid value for catchRemember: %s', self.catchRemember) return prompt = "Remember in Unimacro _folders grammar" inifile = self.ini._file @@ -1437,11 +1259,11 @@ def gotResults_remember(self, words, fullResults): pausetime = 3 # reset variables, no action in gotResults: self.wantedFile = self.wantedFolder = self.wantedWebsite = "" - print(f'thisDir: {thisDir}') + self.info(f'thisDir: {thisDir}') UnimacroDirectory = extenvvars.expandEnvVariableAtStart('%Unimacro%') - print(f'UnimacroDirectory: {UnimacroDirectory}') + self.info(f'UnimacroDirectory: {UnimacroDirectory}') UnimacroGrammarsDirectory = extenvvars.expandEnvVariableAtStart('%UnimacroGrammars%') - print(f'UnimacroGrammarsDirectory: {UnimacroGrammarsDirectory}') + self.info(f'UnimacroGrammarsDirectory: {UnimacroGrammarsDirectory}') makeFromTemplateAndExecute(UnimacroDirectory, "unimacrofoldersremembertemplate.py", UnimacroGrammarsDirectory, "rememberdialog.py", prompt, text, default, inifile, section, value, pausetime=pausetime) @@ -1456,7 +1278,7 @@ def get_active_explorer(self, hndle=None): for window in shell.Windows(): if int(window.HWND) == int(hndle): return window - print("_folders: no active explorer.") + self.info("_folders: no active explorer.") return None def get_current_directory(self, hndle=None): @@ -1474,7 +1296,7 @@ def get_current_directory(self, hndle=None): def get_selected_paths(self): window = self.get_active_explorer() if window is None: - print('get_selected_paths, cannot find application') + self.info('get_selected_paths, cannot find application') return None items = window.Document.SelectedItems() paths = [] @@ -1499,12 +1321,12 @@ def gotResults_thisfile(self, words, fullResults): ## wait for the mouse to have stoppede moving button, nClick = 'left', 1 if not self.doWaitForMouseToStop(): - print('_folders, thisfile, mouse did not stop, cannot click') + self.info('_folders, thisfile, mouse did not stop, cannot click') return unimacroutils.buttonClick(button, nClick) unimacroutils.visibleWait() - # print 'filenames: %s'% self.get_selected_filenames() + # self.info 'filenames: %s'% self.get_selected_filenames() self.wantedFile = None # paths = self.get_selected_paths() # if paths: @@ -1513,7 +1335,7 @@ def gotResults_thisfile(self, words, fullResults): # self.wantedFile = p # break # else: - # print "warning, thisfile: no valid file found" + # self.info "warning, thisfile: no valid file found" # # else: unimacroutils.saveClipboard() @@ -1527,32 +1349,32 @@ def gotResults_thisfile(self, words, fullResults): paths1 = [p for p in paths1 if os.path.isfile(p)] paths2 = get_selected_files(folders=False) - print('get_system_folderinfo: %s'% paths1) - print('get_selected_files: %s'% paths2) + self.info('get_system_folderinfo: %s', paths1) + self.info('get_selected_files: %s', paths2) if paths1 and paths2: if paths1 == paths2: paths = paths1 else: - print('_thisfile, different info for both methods:\nVia Clipboard %s\nVia this module functions: %s'% \ - (repr(paths1), repr(paths2))) + self.info('_thisfile, different info for both methods:\nVia Clipboard %s\nVia this module functions: %s', \ + repr(paths1), repr(paths2)) paths = paths2 elif paths1: - print('_thisfile, only paths1 (via clipboard) has data: %s'% repr(paths1)) + self.info('_thisfile, only paths1 (via clipboard) has data: %s', repr(paths1)) paths = paths1 elif paths2: paths = paths2 - print('_thisfile, only paths2 (this module functions) has data: %s'% repr(paths2)) + self.info('_thisfile, only paths2 (this module functions) has data: %s', repr(paths2)) else: - print('no paths info found with either methods') + self.info('no paths info found with either methods') paths = None if not paths: - print("no selected file found") + self.info("no selected file found") return self.wantedFile = paths[0] if len(paths) > 1: - print("warning, more files selected, take the first one: %s"% self.wantedFile) - print('wantedFile: %s'% self.wantedFile) + self.warning("warning, more files selected, take the first one: %s", self.wantedFile) + self.info('wantedFile: %s', self.wantedFile) self.catchRemember = "file" def gotResults_disc(self,words,fullResults): @@ -1561,7 +1383,7 @@ def gotResults_disc(self,words,fullResults): if letter: f = letter + ":\\" else: - print('_folders, ruls disc, no letter provided: %s'% words) + self.info('_folders, ruls disc, no letter provided: %s', words) return if self.nextRule in ['foldercommands']: @@ -1582,28 +1404,28 @@ def gotResults_file(self,words,fullResults): if extension: File, _old_extension = os.path.splitext (File) File = File +'.' + extension - print('got file: %s'% File) + self.info('got file: %s', File) File = os.path.join(self.activeFolder, File) if not os.path.isfile(File): - print('folders, file, from subfilesList, not a valid path: %s (return None)'% File) + self.info('folders, file, from subfilesList, not a valid path: %s (return None)', File) File = None else: - print('file from subfileslist: %s'% File) + self.info('file from subfileslist: %s', File) self.catchRemember = "file" if not File: try: File = self.filesDict[wantedFile] except KeyError: - print('file cannot be found in filesDict: %s (and not in subfilesDict either)'% wantedFile) + self.info('file cannot be found in filesDict: %s (and not in subfilesDict either)', wantedFile) return File = self.substituteFolder(File) - print("_folders, get file: actual filename (fixed fileslist): %s"% File) + self.info("_folders, get file: actual filename (fixed fileslist): %s", File) extension =self.getFromInifile(words, 'extensions', noWarning=1) if extension: File, _old_extension =os.path.splitext (File) File = File +'.' + extension if not os.path.isfile(File): - print('invalid file: %s'% File) + self.info('invalid file: %s', File) return if self.nextRule in ["filecommands", "remember"]: self.wantedFile = File @@ -1614,7 +1436,7 @@ def gotResults_file(self,words,fullResults): def gotResults_filecommands(self, words, fullResults): if not self.wantedFile: - print('rule filecommands, no wantedFile, return') + self.info('rule filecommands, no wantedFile, return') return # print 'filecommands: %s'% words OpenWith = Remote = False @@ -1630,23 +1452,15 @@ def gotResults_filecommands(self, words, fullResults): remoteLetter = self.getFromInifile(w, 'letters', noWarning=1) remoteVirtualDrive = self.getFromInifile(w, 'virtualdrivesspoken', noWarning=1) if remoteLetter: - print('remoteLetter: %s'% remoteLetter) + self.info('remoteLetter: %s', remoteLetter) self.Remote = remoteLetter.upper() + ":" elif remoteVirtualDrive: self.Remote = self.virtualDriveDict[remoteVirtualDrive] - print('remoteVirtualDrive: %s, resolves to: %s'% (remoteVirtualDrive, self.Remote)) + self.info('remoteVirtualDrive: %s, resolves to: %s', remoteVirtualDrive, self.Remote) Remote = False - elif self.hasCommon(w, 'git'): - print("got Git: ", w) - Git = True - elif Git: - print("got gitCommand: ", w) - gitCommand = self.getFromInifile(w, 'gitfilecommands') - self.Git = gitCommand - Git = False # again else: act = self.getFromInifile(w, 'foldercommands') - print("got FileCommand: ", act) + self.info("got FileCommand: ", act) self.FileOptions.append(act) def gotResults_thisfolder(self,words,fullResults): @@ -1662,7 +1476,7 @@ def gotResults_thisfolder(self,words,fullResults): ## wait for the mouse to have stoppede moving button, nClick = 'left', 1 if not self.doWaitForMouseToStop(): - print("_folders, command thisfolder: doWaitForMouseToStop fails") + self.info("_folders, command thisfolder: doWaitForMouseToStop fails") return unimacroutils.buttonClick(button, nClick) unimacroutils.visibleWait() @@ -1674,6 +1488,7 @@ def gotResults_thisfolder(self,words,fullResults): keystroke("{ctrl+c}") unimacroutils.Wait() paths1 = natlinkclipboard.Clipboard.Get_folderinfo() + unimacroutils.restoreClipboard() if paths1: paths1 = [p for p in paths1 if os.path.isdir(p)] paths2 = get_selected_files(folders=True) @@ -1682,35 +1497,35 @@ def gotResults_thisfolder(self,words,fullResults): if paths1 == paths2: paths = paths1 else: - print('_thisfolder, different info for both methods:\nVia Clipboard %s\nVia this module functions: %s'% \ - (repr(paths1), repr(paths2))) + self.info('_thisfolder, different info for both methods:\nVia Clipboard %s\nVia this module functions: %s', \ + repr(paths1), repr(paths2)) paths = paths2 elif paths1: - print('_thisfolder, only paths1 (via clipboard) has data: %s'% repr(paths1)) + self.info('_thisfolder, only paths1 (via clipboard) has data: %s', repr(paths1)) paths = paths1 elif paths2: # paths = paths2 - print('_thisfolder, only paths2 (this module functions) has data: %s'% repr(paths2)) + self.info('_thisfolder, only paths2 (this module functions) has data: %s', repr(paths2)) else: - print('no paths info found with either methods') + self.info('no paths info found with either methods') paths = None - print('paths:::: %s'% paths) # + self.info('paths:::: %s', paths) # if paths: self.wantedFolder = paths[0] if len(paths) > 1: - print("warning, more items selected, take the first one: %s"% self.wantedFolder) + self.info("warning, more items selected, take the first one: %s", self.wantedFolder) elif self.activeFolder: - print('take activeFolder: %s'% self.activeFolder) + self.info('take activeFolder: %s', self.activeFolder) self.wantedFolder = self.activeFolder else: - print('"this folder" no selected folder found') + self.info('"this folder" no selected folder found') return if os.path.isdir(self.wantedFolder): - # print '_folders, this folder, wantedFolder: %s'% self.wantedFolder + # self.info '_folders, this folder, wantedFolder: %s'% self.wantedFolder self.catchRemember = "folder" # in case remember follows else: - print('_folders, wantedFolder not a valid folder: %s'% self.wantedFolder) + self.info('_folders, wantedFolder not a valid folder: %s', self.wantedFolder) # def gotResults_folderup(self,words,fullResults): @@ -1724,16 +1539,16 @@ def gotResults_folderup(self,words,fullResults): # print 'iambrowser: %s Iamexplorer: %s'% (browser, IamExplorer) istop = self.getTopOrChild(self.progInfo, childClass="#32770") # True if top window if IamChild32770: - print("IamChild32770: ", self.activeFolder) + self.info(f"IamChild32770: {self.activeFolder}") if not self.activeFolder: self.activeFolder = mess.getFolderFromDialog(hndle, classname) - print("IamChild32770 getFolderFromDialog: ", self.activeFolder) + self.info(f"IamChild32770 getFolderFromDialog: {self.activeFolder}") if self.activeFolder: newfolder = self.goUpInPath(self.activeFolder, upn) - #print 'newfolder (up %s): %s'% (upn, newfolder) + #self.info 'newfolder (up %s): %s'% (upn, newfolder) self.gotoInThisDialog(newfolder, hndle, classname) else: - print('method not working (any more) for #32770: %s'% title) + self.info('method not working (any more) for #32770: %s', title) elif not istop: # child window actions @@ -1759,18 +1574,18 @@ def gotResults_folderup(self,words,fullResults): self.activeFolder = mess.getFolderFromCabinetWClass(hndle) if self.activeFolder: newfolder = self.goUpInPath(self.activeFolder, upn) - print('newfolder (up %s): %s'% (upn, newfolder)) + self.info('newfolder (up %s): %s', upn, newfolder) self.gotoInThisComputer(newfolder) else: - print('method not working any more, going folder up') + self.info('method not working any more, going folder up') action("MP 1, 50, 10, 0") for _i in range(upn): action("{backspace} VW") else: - print('yet to implement, folder up for %s'% prog) + self.info('yet to implement, folder up for %s', prog) - #print 'had folder up: %s'% words + #self.info 'had folder up: %s'% words def substituteFolder(self, folder): @@ -1784,9 +1599,9 @@ def substituteFolder(self, folder): """ folder = folder.replace('/', '\\') folder = self.substituteEnvVariable(folder) - if not self.virtualDriveDict: - #print 'no virtualDriveDict, return %s'% folder - return folder + # if not self.virtualDriveDict: + # #print 'no virtualDriveDict, return %s'% folder + # return folder if folder in self.virtualDriveDict: drive, rest = folder, "" elif folder.find(':\\') > 0: @@ -1906,47 +1721,28 @@ def gotoWebsite(self, f): """goto the file f, options in instance variables FileOptions: list - Git (with gitfileoptions), False or the git action to be taken Remote, False or the virtual drive to be inserted Open, False or app to Open with (default) Edit, False or app to Edit with, if fails, take Notepad - ##special case for citrix """ if self.Open: - print("gotoWebsite %s with: %s", (f, self.Open)) + self.info("gotoWebsite %s with: %s", f, self.Open) else: - print("gotoWebsite: ", f) + self.info("gotoWebsite: %s", f) self.openWebsiteDefault(f, openWith=self.Open) def gotoFile(self, f): """goto the file f, options in instance variables FileOptions: list - Git (with gitfileoptions), False or the git action to be taken Remote, False or the virtual drive to be inserted Open, False or app to Open with (default) Edit, False or app to Edit with, if fails, take Notepad - - ##special case for citrix """ - if self.citrixApps: - prog = self.progInfo.prog - - print('citrixApps: %s app: %s'% (self.citrixApps, prog)) - if prog in self.citrixApps: - print('doing action gotoFolder for citrixApp: %s'% prog) - action("<>") - keystroke(f) - - # keystroke("{enter}") - return - if not os.path.isfile(f): self.DisplayMessage('file does not exist: %s'% f) return - prog = self.progInfo.prog - # istop logic, with functions from action.py module, settings from: # child behaves like top = natspeak: dragon-balk # top behaves like child = komodo: find, komodo; thunderbird: bericht opslaan @@ -1954,16 +1750,12 @@ def gotoFile(self, f): istop = self.getTopOrChild( self.progInfo, childClass="#32770") # True if top if self.Remote: - print('Remote: %s'% self.Remote) + self.info('Remote: %s', self.Remote) f = self.getValidFile(f, self.Remote) if not f: return - if self.Git: - print('git command "%s" for file "%s"'% (self.Git, f)) - self.doGitCommand(self.Git, f) - return mode = 'edit' if self.Open: mode = 'open' @@ -1978,7 +1770,7 @@ def gotoFile(self, f): if not istop: # child window actions # put the mouse in the left top corner of the window: - print("Open file from child window: %s"% f) + self.info("Open file from child window: %s", f) action("RMP 1, 0.02, 0.05, 0") action('<>') unimacroutils.saveClipboard() @@ -2001,10 +1793,10 @@ def openFileDefault(self, filename, mode=None, windowStyle=None, name=None, open """ ## action('CW') if not os.path.isfile(filename): - print('file does not exist, cannot open: %s'% filename) + self.info('file does not exist, cannot open: %s', filename) return if not ancestor.openFileDefault(self, filename, mode=mode, openWith=openWith): - print('could not open %s (mode: %s, openWith: %s)'% (filename, mode, openWith)) + self.info('could not open %s (mode: %s, openWith: %s)', filename, mode, openWith) return try: # try is needed in case function is called from another class (dirty trick with _control, sorry) @@ -2024,12 +1816,12 @@ def openFolderDefault(self, foldername, mode=None, windowStyle=None, openWith=No #print 'going to open folder: %s'% foldername if not ancestor.openFolderDefault(self, foldername, mode=mode, openWith=openWith): - print('failed to open folder: %s'% foldername) + self.info('failed to open folder: %s', foldername) return for act in self.FolderOptions: if act: - print("openFolderDefault, action: %s"% act) + self.info("openFolderDefault, action: %s", act) action(act) # This is the function which does the real work, depending on the @@ -2037,7 +1829,7 @@ def openFolderDefault(self, foldername, mode=None, windowStyle=None, openWith=No def gotoFolder(self, f): """go to the specified folder - all the options are via instance variables, New, Here, Copy, Paste, Remote, Git (all False by default) + all the options are via instance variables, New, Here, Copy, Paste, Remote (all False by default) and FolderOptions (a list, initially empty). f = the (local) folder to go to @@ -2050,7 +1842,6 @@ def gotoFolder(self, f): --Remote: pass the (virtual) drive where the folder is wanted --Copy: investigate --Paste: only paste the path at the place where you are. - --Git: do a git command on the folder. To be done. this is the central routine, with complicated strategy for getting it, in pseudocode: @@ -2094,11 +1885,6 @@ def gotoFolder(self, f): else: if part of path is common, switch to that and goto folder - ## remove subversion support, - ## git support if git executable isdefined in section [general] - - ##special if citrixApps is set, just open the folder. - """ ## undoncitionally if folderoption New is used: if self.New: @@ -2106,42 +1892,29 @@ def gotoFolder(self, f): return prog = self.progInfo.prog - if self.citrixApps: - - print('citrixApps: %s app: %s'% (self.citrixApps, prog)) - if prog in self.citrixApps: - print('doing action gotoFolder for citrixApp: %s'% prog) - action("<>") - keystroke(f) - keystroke("{enter}") - return f = os.path.normpath(f) if not os.path.isdir(f): self.DisplayMessage('folder does not exist: %s'% f) return if prog == 'cmd': - print("_folder, for cmd: %s"% f) + self.info("_folder, for cmd: %s", f) # t = " " + f action('SCLIP(%s)'% f) return - if self.Git: - self.doGitCommand(self.Git, f) - - # xx = self.xxExplorer if self.Remote: - print('Remote: %s'% self.Remote) + self.info('Remote: %s', self.Remote) f = self.getValidDirectory(f, self.Remote) - print('Remote: %s'% f) + self.info('Remote: %s', f) if not f: return if self.PastePath: action("SCLIP(%s)"%f) - print("PastePath: %s"% f) + self.info("PastePath: %s", f) return # if self.CopyNamePath: - print('put path on clipboard: "%s"'% f) + self.info('put path on clipboard: "%s"', f) unimacroutils.setClipboard(f) return @@ -2149,7 +1922,7 @@ def gotoFolder(self, f): prog = self.progInfo.prog hndle = self.progInfo.hndle if not hndle: - print('_folders, gotoFolder: no window handle found, return') + self.info('_folders, gotoFolder: no window handle found, return') # Iam2x = prog == '2xexplorer' IamExplorer = prog == 'explorer' _browser = prog in ['iexplore', 'firefox','opera', 'netscp', 'brave'] @@ -2166,13 +1939,13 @@ def gotoFolder(self, f): # print("go from here activeFolder: %s"% self.activeFolder) self.gotoInThisDialog(f, hndle, self.className) return - print("no files/folder dialog, treat as top window") + self.info("no files/folder dialog, treat as top window") self.openFolderDefault(f) return if not istop: # child window actions # put the mouse in the left top corner of the window: - print("_folders, child window, comes ever here???") + self.info("_folders, child window, comes ever here???") action("RMP 1, 0.02, 0.05, 0") action('<>') unimacroutils.saveClipboard() @@ -2226,11 +1999,11 @@ def gotoFolder(self, f): if exactList: ## print 'exactList %s' % (exactList) if len(exactList) > 1: - print('warning, 2 matching windows: %s'% exactList) + self.info('warning, 2 matching windows: %s', exactList) t, h = exactList[0] unimacroutils.SetForegroundWindow(h) elif overList: -## print 'over List %s' % (overList) +## self.info 'over List %s' % (overList) # eg f = d:\\a\\b # and elements of overList are d:\\a\\b\\c and d:\\a\\b\\c\\d # goto shortest element @@ -2256,7 +2029,7 @@ def gotoFolder(self, f): # eg f = d:\\a\\b\\c # elementes of underList are d:\\a d:\\a\\b etc. # go to longest element and switch in that window to folder - print('under list, go to first folder') + self.info('under list, go to first folder') lenMax = 0 for t, h in underList: @@ -2278,7 +2051,7 @@ def gotoFolder(self, f): if unimacroutils.SetForegroundWindow(h): self.gotoInThisComputer(f) else: - print('could not set foregroundwindow: %s'% h) + self.info('could not set foregroundwindow: %s', h) self.openFolderDefault(f) else: @@ -2286,7 +2059,7 @@ def gotoFolder(self, f): self.openFolderDefault(f) else: # no this computer windows (yet) - print("grammar folders shouldn't be here!") + self.info("grammar folders shouldn't be here!") def getValidDirectory(self, f, remote): @@ -2316,7 +2089,8 @@ def getValidDirectory(self, f, remote): if os.path.isdir(tryF): return tryF fparts.pop(0) - print('_folders, no valid remote folder found for %s and remote: %s'% (f, remote)) + self.info('_folders, no valid remote folder found for %s and remote: %s', f, remote) + return '' def getValidFile(self, f, remote): _fdrive, fdir = os.path.splitdrive(f) @@ -2327,40 +2101,9 @@ def getValidFile(self, f, remote): if os.path.isfile(tryF): return tryF fparts.pop(0) - print('_folders, no valid remote file found for %s and remote: %s'% (f, remote)) + self.info('_folders, no valid remote file found for %s and remote: %s', f, remote) + return '' - - def getListOfSites(self, root): - """return list of sitenames, to be found as python files in root - - """ - pyfiles = [f for f in os.listdir(root) if f.endswith('.py')] - #print 'pyfiles for sites: %s'% pyfiles - D = {} - entries = self.ini.get('sites') - for p in pyfiles: - trunk = p[:-3] - if not reOnlyLowerCase.match(trunk): - continue # only lowercase items can be a sites item, so __init__ and HTMLgen etc are skipped - if trunk in entries: - spokenList = self.ini.getList('sites', trunk) - if not spokenList: - #print 'empty item in siteslist: %s'% trunk - continue - for spoken in spokenList: - spoken = self.spokenforms.correctLettersForDragonVersion(spoken) - D[spoken] = trunk - else: - # make new entry in sites section - if len(trunk) <= 3: - spoken = '. '.join(list(trunk.upper()))+'.' - else: - spoken = trunk - spoken = self.spokenforms.correctLettersForDragonVersion(spoken) - D[spoken] = trunk - #print 'set in sites: %s -> %s'% (trunk, spoken) - self.ini.set('sites', trunk, spoken) - return D def gotResults(self, words,fullResults): """at last do most of the actions, depending on the variables collected in the rules. @@ -2371,30 +2114,6 @@ def gotResults(self, words,fullResults): self.gotoFile(self.wantedFile) if self.wantedWebsite: self.gotoWebsite(self.wantedWebsite) - - - def doGitCommand(self, command, path): - """launch git with command and path - """ - args = '/command:%s /path:""%s""'% (command, path) - - # Construct arguments and spawn TortoiseSVN. - name = "git %s %s"% (command, path) - print('future git %s, %s'% (name, args)) - ## does not work (yet)... - # unimacroutils.AppBringUp(name, self.doGit, args) - -# return 1 - - def doStart2xExplorer(self): - """starting the 2xExplorer, obsolete - - """ - command = 'AppBringUp "%s"'% self.xxExplorer -## print 'starting 2xExplorer: %s'% command - natlink.execScript(command) - unimacroutils.Wait(1.0) - keystroke("{alt+space}{extdown 4}{enter}") def gotoInThisComputer(self, f): """perform the keystrokes to go to a folder in this computer @@ -2417,7 +2136,7 @@ def gotoInThisDialog(self, f, hndle, className): elif os.path.isfile(f): folder, filename = os.path.split(f) else: - print('invalid target for gotoInThisDialog: %s'% f) + self.info('invalid target for gotoInThisDialog: %s', f) return if folder != activeFolder: @@ -2428,7 +2147,7 @@ def gotoInThisDialog(self, f, hndle, className): action('W') keystroke('{shift+tab}') if filename: - action("SCLIP %s"% filename) + action("SCLIP %s", filename) # keystroke(filename) def gotoInOtherExplorer(self, f): @@ -2440,7 +2159,7 @@ def gotoInOtherExplorer(self, f): if self.useOtherExplorer == "xplorer2": keystroke("{shift+tab}%s{enter}{down}{up}"% f) else: - print('_folders, please specify in function "gotoInOtherExplorer" for "use other explorer": "%s"'% self.useOtherExplorer) + self.info('_folders, please specify in function "gotoInOtherExplorer" for "use other explorer": "%s"', self.useOtherExplorer) def goUpInPath(self, PATH, nsteps=None): """return a new path, n steps up in hierarchy, default 1 @@ -2467,9 +2186,9 @@ def doStartWindowsExplorer(self): try: unimacroutils.waitForNewWindow(50, 0.05) # 2,5 seconds max except unimacroutils.NatlinkCommandTimeOut: - print('Error with action "start windows explorer" (%s) from command in grammar + "_folders".' % \ + self.info('Error with action "start windows explorer" (%s) from command in grammar + "_folders".' , \ startExplorer) - print('Correct in ini file by using the command: ' + {'enx': "Edit Folders", + self.info('Correct in ini file by using the command: ' + {'enx': "Edit Folders", 'nld': "Bewerk folders"}[self.language]) return None return 1 @@ -2515,7 +2234,7 @@ def getExplorerTitles(): """ TitlesHandles = [] ## Classes come from global variable at top of this module - ##print 'Classes:', Classes + ##self.info 'Classes:', Classes ## Classes = None win32gui.EnumWindows(getExplWindowsWithText, (TitlesHandles, Classes)) return TitlesHandles @@ -2583,7 +2302,7 @@ def get_clipboard_formats(): while f: formats.append(f) f = win32clipboard.EnumClipboardFormats(f) - # print '_folders, clipboard formats: %s'% formats + # self.info '_folders, clipboard formats: %s'% formats return formats def get_selected_files(folders=False): @@ -2597,7 +2316,7 @@ def get_selected_files(folders=False): time.sleep(0.1) files = get_clipboard_files(folders) # cb.copy_to_system() - # print 'files: %s'% files + # self.info 'files: %s'% files return files def get_clipboard_files(folders=False): @@ -2605,27 +2324,28 @@ def get_clipboard_files(folders=False): Enumerate clipboard content and return files either directly copied or highlighted path copied ''' - files = None - win32clipboard.OpenClipboard() - f = get_clipboard_formats() - if win32clipboard.CF_HDROP in f: - files = win32clipboard.GetClipboardData(win32clipboard.CF_HDROP) - else: - # print 'get_clipboard_files, not expected clipboard format CF_HDROP, but %s'% f - if win32clipboard.CF_UNICODETEXT in f: - files = [win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)] - elif win32clipboard.CF_TEXT in f: - files = [win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)] - elif win32clipboard.CF_OEMTEXT in f: - files = [win32clipboard.GetClipboardData(win32clipboard.CF_OEMTEXT)] - if not files: - # print "get_clipboard_files, no files found from clipboard" - return None - if folders: - files = [f for f in files if os.path.isdir(f)] if files else None - else: - files = [f for f in files if os.path.isfile(f)] if files else None - win32clipboard.CloseClipboard() + try: + win32clipboard.OpenClipboard() + f = get_clipboard_formats() + if win32clipboard.CF_HDROP in f: + files = win32clipboard.GetClipboardData(win32clipboard.CF_HDROP) + else: + # self.info 'get_clipboard_files, not expected clipboard format CF_HDROP, but %s'% f + if win32clipboard.CF_UNICODETEXT in f: + files = [win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)] + elif win32clipboard.CF_TEXT in f: + files = [win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)] + elif win32clipboard.CF_OEMTEXT in f: + files = [win32clipboard.GetClipboardData(win32clipboard.CF_OEMTEXT)] + if not files: + # self.info "get_clipboard_files, no files found from clipboard" + return None + if folders: + files = [f for f in files if os.path.isdir(f)] if files else None + else: + files = [f for f in files if os.path.isfile(f)] if files else None + finally: + win32clipboard.CloseClipboard() return files def makeFromTemplateAndExecute(unimacrofolder, templatefile, unimacrogrammarsfolder, exefile, prompt, text, default, inifile, section, value, pausetime=0): @@ -2634,7 +2354,7 @@ def makeFromTemplateAndExecute(unimacrofolder, templatefile, unimacrogrammarsfol meant for setting up a inputbox dialog """ rwfile = readwritefile.ReadWriteFile() - print(f'unimacrofolder: {unimacrofolder}') + logger.info('unimacrofolder: %s, unimacrofolder') Text = rwfile.readAnything(os.path.join(unimacrofolder, templatefile)) # print(f'OldText: {Text}') for orig, toreplace in [('$prompt$', prompt), ('$default$', default), ('$text$', text), @@ -2670,9 +2390,9 @@ def connectOutlook(): pass #outlookApp = win32com.client.Dispatch('Outlook.Application') if outlookApp: - print('outlook application collected') + logger.info('outlook application collected') return outlookApp - print('outlook not connected') + logger.info('outlook not connected') outlookApp = None outlookAppProgram = None return outlookApp @@ -2695,9 +2415,6 @@ def dumpToPickle(data, picklePath): """dump the data to picklePath """ # print("dumpToPickle %s, %s"% (picklePath, len(data))) - if not data: - os.remove(picklePath) - return try: with open(picklePath, 'wb') as pp: pickle.dump(data, pp) @@ -2720,18 +2437,29 @@ def unload(): # print("unloading folders grammar") thisGrammar.stopRecentFolders() # stopping the timer callback thisGrammar.unload() - print("unloaded folders grammar") + logger.info("unloaded folders grammar") thisGrammar = None if __name__ == "__main__": ## interactive use, for debugging: - natlink.natConnect() - try: - thisGrammar = ThisGrammar(inifile_stem="_folders") - thisGrammar.startInifile() - thisGrammar.initialize() - finally: - natlink.natDisconnect() + with natlink.natConnect(): + try: + thisGrammar = ThisGrammar(inifile_stem="_folders") + # thisGrammar.startInifile() + thisGrammar.initialize() + + # get hndle of a explore window (via _general "give window info") and try interactive + # thisGrammar.catchTimerRecentFolders(132524, "CabinetWClass") + thisGrammar.getActiveFolder(67062) + thisGrammar.displayRecentFolders() + + # # Words = ['folder', 'dtactions'] + # Fr = {} + # Words = ['subfolder', 'hello'] + # thisGrammar.gotResultsInit(Words, Fr) + # thisGrammar.gotResults_subfolder(Words, Fr) + finally: + thisGrammar.unload() elif __name__.find('.') == -1: # standard startup when Dragon starts: thisGrammar = ThisGrammar() diff --git a/src/unimacro/UnimacroGrammars/_general.py b/src/unimacro/UnimacroGrammars/_general.py index 222bc4d..15c6a34 100644 --- a/src/unimacro/UnimacroGrammars/_general.py +++ b/src/unimacro/UnimacroGrammars/_general.py @@ -1,22 +1,18 @@ +# This was the old "general" intro for all unimacro grammar files: # This file is part of a SourceForge project called "unimacro" see # http://unimacro.SourceForge.net and http://qh.antenna.nl/unimacro # (c) copyright 2003 see http://qh.antenna.nl/unimacro/aboutunimacro.html # or the file COPYRIGHT.txt in the natlink\natlink directory # -# _general.PY +# _general.py # # written by Quintijn Hoogenboom (QH softwaretraining & advies), -# developed during the past few years. # # #pylint:disable=C0302, R0904, C0209, C0321, R0912, R0914, R0915, R0911 #pylint:disable=E1101 -"""do a set of general commands, with language versions possible, version 7 - -a lot of commands from the previous version removed, inserted a search and dictate -mode, that only works when spell mode or command mode is on. - +"""do a set of general commands """ import re @@ -97,15 +93,15 @@ "{Right}": "{Left}"} modes = ['spell', 'command', 'numbers', 'normal', 'dictation', 'dictate'] -normalSet = ['test', 'reload', 'info', 'undo', 'redo', 'namephrase', 'batch', +normalSet = ['test', 'reload', 'info', 'undo', 'redo', 'namephrase', 'comment', 'documentation', 'modes', 'variable', 'search', 'highlight', # for Shane, enable, because Vocola did not fix _anything yet 'browsewith', 'hyphenatephrase', 'pastepart', - 'password', 'presscode', 'choose'] + 'password', 'choose'] #normalSet = ['hyphenatephrase'] # skip 'openuser' commandSet = normalSet[:] + ['dictate'] - +thisGrammar = None ancestor=natbj.IniGrammar class ThisGrammar(ancestor): @@ -127,9 +123,7 @@ class ThisGrammar(ancestor): imported; # imported; exported = Make documentation; - exported = do batch words; exported = test micstate; - exported = (press|address) (|); exported = choose {n1-10}; exported = reload Natlink; exported = give (user | prog |window |unimacro| path) (info|information) ; @@ -150,8 +144,12 @@ class ThisGrammar(ancestor): exported = ('browse with') {browsers}; exported = 'open user' {users}; exported = 'password' ; + exported = (press letter) ; """] +# A\determinerNormalTestThisNowI\pronounTest.\period\full stop +#NormalTestNormalI\pronounTestNormallyTestNormalAttestNormal.\period\periodSigns +#HelloTesting def initialize(self): if self.language: @@ -165,20 +163,9 @@ def initialize(self): # print('specialSearchWords: %s'% self.specialSearchWords) self.setNumbersList('count', Counts) self.setList('modes', modes) -## self.testlist = ['11\\Canon fiftyfive two fifty', -## '12\\Canon', -## '15\\C. Z. J.', -## '19\\Helios fourtyfour M.', -## '38\\Vivitar seventy one fifty', -## '32\\Contax twentyeight millimeter', -## '33\\C. Z. J. one thirtyfive number one', -## 'Canon 2870', -## '09\\Canon 1022', -## '31\\Tamron 2875'] -## #self.setList('testlist', self.testlist) -## self.emptyList('testlist') self.gotPassword = 0 - # self.gotPresscode = 0 + self.passwordEnding = None + self.gotPresscode = 0 # print "%s, activateSet: %s"% (self.name, normalSet) # self.deactivateAll() # why is this necessary? The activateAll in switchOn is definitly now Ok... self.title = 'Unimacro grammar "'+__name__+'" (language: '+self.language+')' @@ -213,8 +200,6 @@ def gotResultsInit(self,words,fullResults): natut.buttonClick() unimacroutils.Wait() self.progInfo = unimacroutils.getProgInfo() - print(f'progInfo _general: {self.progInfo.prog}, {self.progInfo.title}') - def gotResults_password(self,words,fullResults): """interpret password as dictate Cap dictation words @@ -222,7 +207,6 @@ def gotResults_password(self,words,fullResults): """ self.gotPassword = 1 - print('gotPassword: ', self.gotPassword) def gotResults_pastepart(self,words,fullResults): """paste part of clipboard, parts are separated by ";" @@ -274,83 +258,6 @@ def gotResults_before(self,words,fullResults): if self.hasCommon(words, 'here'): natut.buttonClick('left', 1) - def gotResults_batch(self,words,fullResults): - - _files = [f[:-4] for f in os.listdir(wordsFolder)] - if _files: - print('in folder: %s, files: %s'% (wordsFolder, _files)) - else: - print('in folder: %s, no files found'% wordsFolder) - return - - for f in _files: - F = f + '.txt' - if f == 'deleted words': - print('delete words!') - for l in open(os.path.join(wordsFolder, F)): - w = l.strip() - if w.find('\\\\') > 0: - w, p = w.split('\\\\') - print(f, ', word to delete :', w) - unimacroutils.deleteWordIfNecessary(w) - continue - - if f in FORMATS: - formatting = FORMATS[f] - print('to formatting for file: %s: %x'% (f, formatting)) - else: - print('no formatting information for file: %s'% f) - formatting = 0 - - for l in open(os.path.join(wordsFolder, F)): - p = 0 # possibly user defined properties - w = l.strip() - print(f, ', word:', w) - if w.find('\\\\') > 0: - w, p = w.split('\\\\') - exec("p = %s"%p) -## pList = unimacroutils.ListOfProperties(p) -## for pp in pList: -## print pp - newFormat = p or formatting - unimacroutils.addWordIfNecessary(w) - formatOld = natlink.getWordInfo(w) - if formatOld == newFormat: - print('format already okay: %s (%x)'% (w, newFormat)) - else: - natlink.setWordInfo(w, newFormat) - print('format set for %s: %x'% (w, newFormat)) - -## def gotResults_datetime(self,words,fullResults): -## """print copy or playback date, time or date and time -## """ -## Print = self.hasCommon(words, 'print') -## Speak = self.hasCommon(words, 'give') -## Date = self.hasCommon(words, 'date') -## Time = self.hasCommon(words, 'time') -## if Date and Time: -## DateTime = 1 -## result = [] -## if Date: -## dateformat = "%m/%d/%Y" -## cdate = datetime.date.today() -## fdate = cdate.strftime(dateformat) -## result.append(fdate) -## if Time: -## timeformat = "%H:%M" -## ctime = datetime.datetime.now().time() -## ftime = ctime.strftime(timeformat) -## result.append(ftime) -## if result: -## result = ' '.join(result) -## else: -## print 'no date or time in result' -## return -## if Print: -## keystroke(result) -## elif Speak: -## natlink.execScript('TTSPlayString "%s"'% result) - def gotResults_highlight(self,words,fullResults): # for Shane self.highlight = 1 @@ -454,7 +361,6 @@ def gotResults_dgndictation(self,words,fullResults): text = nsformat.formatPassword(words) keystroke(text) self.gotPassword = 0 - print('reset gotPassword, ', self.gotPassword) return if self.gotVariable: print('do variable trick %s on %s'% (self.gotVariable, words)) @@ -674,21 +580,6 @@ def gotResults_stopwatch(self,words,fullResults): action(f'MSG {elapsed:.2f} seconds') self.startTime = t - -# sstarting message - def gotResults_presscode(self,words,fullResults): - """pressing letters or dictation in for example explorer - - """ - self.gotPresscode = 1 - print(f'got presscode: {words}, presscode: {self.gotPresscode}') - if self.hasCommon(words, 'address'): - action('{alt+d}; VW') - # search maybe useful for safari, but does not make sense otherwise - # if self.hasCommon(words, 'search'): - # action('{ctrl+k}; VW') - - def gotResults_choose(self,words,fullResults): """choose alternative, via actions @@ -777,15 +668,16 @@ def gotResults_info(self,words,fullResults): T.append(f' .toporchild\t{p.toporchild}') T.append(f' .classname\t{p.classname}') T.append(f' .hndle:\t{hndle}') - childClass = "#32770" - overruleIsTop = self.getTopOrChild(self.progInfo, childClass=childClass) - - if p.toporchild != overruleIsTop: - T.append('') - if overruleIsTop: - T.append("**** treat as TOP window although it is a child window") - else: - T.append("**** treat as CHILD window although it is a top window") + # # for special behaviour: + # childClass = "#32770" + # overruleIsTop = self.getTopOrChild(self.progInfo, childClass=childClass) + # + # if p.toporchild != overruleIsTop: + # T.append('') + # if overruleIsTop: + # T.append("**** treat as TOP window although it is a child window") + # else: + # T.append(f'**** treat as CHILD window although it is a top window (classname: {classname})') elif self.hasCommon(words,'user'): @@ -1209,7 +1101,9 @@ def unload(): # if __name__ == "__main__": # here code to interactive run this module - pass + thisGrammar = ThisGrammar(inifile_stem='_general') + thisGrammar.startInifile() + thisGrammar.initialize() elif __name__.find('.') == -1: # this is caught when this module is imported by the loader (when Dragon/Natlink starts) thisGrammar = ThisGrammar() diff --git a/src/unimacro/UnimacroGrammars/_lines.py b/src/unimacro/UnimacroGrammars/_lines.py index 839727d..8cc066b 100644 --- a/src/unimacro/UnimacroGrammars/_lines.py +++ b/src/unimacro/UnimacroGrammars/_lines.py @@ -780,8 +780,8 @@ def unload(): # here code to interactive run this module natlink.natConnect() try: - thisGrammar = ThisGrammar() - thisGrammar.startInifile(modName = '_lines') + thisGrammar = ThisGrammar(inifile_stem='_lines') + thisGrammar.startInifile() thisGrammar.initialize() thisGrammar.progInfo = unimacroutils.getProgInfo() seqsAndRules = [(['line'], 'linenum'), (['seven', 'two', 'three'], '__0to9')] diff --git a/src/unimacro/UnimacroGrammars/settings folders grammar.txt b/src/unimacro/UnimacroGrammars/settings folders grammar.txt new file mode 100644 index 0000000..3dcfdca --- /dev/null +++ b/src/unimacro/UnimacroGrammars/settings folders grammar.txt @@ -0,0 +1,27 @@ + +old settings (until feb 2024) +[general] +automatic track files = T +automatic track folders = T +child behaves like top = natspeak: dragonbar, dictation box, messages +ignore file patterns = ~*; .* +initial on = 1 +track file extensions = .py; .txt; .ini; .docx; .xlsx; .pdf +track files virtualdrives = +track folders virtualdrives = + +newer settings febr 2024 +[general] +ignore file patterns = ~*; .* +initial on = 1 +notify threshold milliseconds = 50 +start this computer = HW start, My Computer +start windows explorer = HW start, Windows explorer +track file extensions = .py; .txt; .ini; .jpg; .jpeg; .png; .docx; .xlsx +*****track files at utterance = 45 +track files virtualdrives = +track recent folder at utterance = 50 +*****track subfolders at utterance = 100 +max recent folders = 75 +track folders virtualdrives = md; dr; pr; fb +track recent folders timer interval = 4 diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index 213d197..be84d55 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -1,28 +1,25 @@ """Unimacro __init__ -utility functions, to get calling directory of module (in site-packages), -...and to check the existence of a directory, for example .natlink in the home directory. +Note there will be a global variable created in the unimacro module 'ulogger' which is Logging.Logger object named 'natlink.unimacro' +You can always access it by name. It is created in _control.py. -Note: -as user, having pipped the package, the scripts run from the site-packages directory - -as developer, you have to clone the package, then `build_package` and, - after a `pip uninstall unimacro`, `flit install --symlink`. - See instructions in the file README.md in the source directory of the package. - -get_site_packages_dir: can be called in the calling module like: - -``` -try: - from unimacro.__init__ import get_site_packages_dir -except ModuleNotFoundError: - print('Run this module after "build_package" and "flit install --symlink"\n') - -sitePackagesDir = get_site_packages_dir(__file__) -``` """ import os import sys +#these functions are in this module so that they can be loaded without loading a lot of unimacro code. +#they could be in a seperate .py file in unimacro to achieve the same (ie not in the control grammar). +#these will possilby be removed since we may not need them to enumerate the grammars and ask for log names. + +def folders_logger_name() -> str: + return "natlink.unimacro.folders" + +def control_logger_name() -> str : + return "natlink.unimacro.control" +def logname() -> str: + """ Returns the name of the unimacro logger.""" + return "natlink.unimacro" __version__ = '4.1.4.2' diff --git a/src/unimacro/_control.py b/src/unimacro/_control.py index d2a339b..d9556e8 100644 --- a/src/unimacro/_control.py +++ b/src/unimacro/_control.py @@ -9,7 +9,8 @@ # _control.py, adapted version of_gramutils.py # Author: Bart Jan van Os, Version: 1.0, nov 1999 # starting new version Quintijn Hoogenboom, August 2003, for python3 2023 -#pylint:disable=C0115, C0116, W0702, R0904, R0911, R0912, R0914, R0915, W0201, W0613, W0107, C0209, E0601, W0602 +#pylint:disable=C0115, C0116, W0702, R0904, R0911, R0912, R0914, R0915, W0201, W0613, W0107, C0209, E0601, W0602, C0112 +#pylint:disable=R1735, W0703 #pylint:disable=E1101 import os @@ -17,6 +18,11 @@ import shutil import string from pathlib import Path +#a global logger for unimacro. perfectly reasonable to access by name instead. +import logging as l +import importlib.metadata as meta +import sys + import natlink from natlinkcore import loader from natlinkcore import natlinkstatus @@ -25,11 +31,31 @@ from dtactions.unimacro import unimacroactions as actions from unimacro import natlinkutilsbj as natbj -from unimacro import spokenforms +from unimacro import spokenforms + +#from unimacro.logger import ulogger + +#for some reason, importing amodule which does this doesn't work. Likely because natlinkmain must be started first for +#this sublogger natlink.unimacro to work correctly. +import unimacro as unimacro_l #bring in so we can add a variable ulogger to the namespace. +ulogger : l.Logger = l.getLogger(unimacro_l.logname()) +#Loggers can be created for any module, and they can propogate to the parent Logger, or not. +#As an example, this module for the control grammar has its own child logger of unimacro. +#Note an entry point has to be defined as well, in pyproject.toml, so Loggers for various natlink components can be discovered. +control_logger=l.getLogger(unimacro_l.control_logger_name()) + + +unimacro_l.__dict__['ulogger']=ulogger +ulogger.debug("natlink.unimacro logger available") status = natlinkstatus.NatlinkStatus() natlinkmain = loader.NatlinkMain() + + + + + tracecount = list(map(str, list(range(1, 10)))) # #Constants for the UtilGrammar @@ -42,24 +68,47 @@ # # showAll = 1 # reset if no echo of exclusive commands is wished -#voicecodeHome = None -#if 'VCODE_HOME' in os.environ: -# voicecodeHome = os.environ['VCODE_HOME'] -# if os.path.isdir(voicecodeHome): -# for subFolder in ('Admin', 'Config', 'Mediator'): -# newFolder = os.path.join(voicecodeHome, subFolder) -# if os.path.isdir(newFolder) and newFolder not in sys.path: -# sys.path.append(newFolder) -# print 'appending to sys.path: %s'% newFolder -# else: -# print '_control: VCODE_HOME points NOT to a directory: %s'% voicecodeHome -# voicecodeHome = None - + +def natlink_loggers() ->dict: + """ + returns dictionary, keys are the names of the module to show to users (or for them to use in dication), + values are the string names of the loggers. + For example, {'unimacro':'natlink.unimacro'}. + Any python module/package/etc. can enable their own logger by defining an entry point in group 'natlink.loggers'. + The entry point must be a function that returns a logger name. Is the Python 'logging' module. + + """ + + + discovered_eps=meta.entry_points(group='dt.loggers') + ulogger.debug('Entry Points for natlink.loggers: %s', discovered_eps) + loggers = dict() + for ep in discovered_eps: + try: + name=ep.name + module=ep.module + module_loaded=module in sys.modules + + ulogger.debug(f"Entry Point {ep} module: {module} is loaded: {module_loaded}. {'' if module_loaded else 'Not adding to list of available loggers.'} ") + + #only add the logger to the list of available loggers if the module is already loaded. + if module_loaded: + f=ep.load() + logname=f() + loggers[name]=logname + except Exception as e: + ulogger.error(f"Attempting to load EntryPoint {ep},error\n {e}") + return loggers ancestor = natbj.IniGrammar class UtilGrammar(ancestor): language = status.get_language() - iniIgnoreGrammarLists = ['gramnames', 'tracecount', 'message'] # are set in this module + + loggers=natlink_loggers() + loggers_names=sorted(loggers.keys()) + + ulogger.debug("Control: Available Loggers %s", loggers_names) + iniIgnoreGrammarLists = ['gramnames', 'tracecount', 'message', 'logger_names'] # are set in this module name = 'control' ## normalSet = ['show', 'edit', 'trace', 'switch', 'message'] @@ -67,14 +116,17 @@ class UtilGrammar(ancestor): # commands for controlling module actions specialList = [] specialList.append("actions") - if spokenforms: - specialList.append("'spoken forms'") + + # if spokenforms: ## assume spokenforms is imported!!! + specialList.append("'spoken forms'") + specialList.append("loggers") if specialList: specials = "|" + '|'.join(specialList) + ulogger.debug('specialList for "show": %s', specials) else: specials = "" - gramRules = ['show', 'edit', 'switch', 'showexclusive', 'resetexclusive', 'checkalphabet', 'message'] + gramRules = ['show', 'edit', 'switch', 'showexclusive', 'resetexclusive', 'checkalphabet', 'message','setlogging','loglevel'] gramDict = {} gramDict['show'] = """ exported = show ((all|active) grammars | {gramnames} | (grammar|inifile) {gramnames} @@ -86,6 +138,9 @@ class UtilGrammar(ancestor): gramDict['resetexclusive'] = """ exported = reset (exclusive | exclusive grammars);""" gramDict['checkalphabet'] = """ exported = check alphabet;""" gramDict['message'] = """ exported = {message};""" + gramDict['setlogging'] = """ exported = {logmodulename} loglevel ;""" + gramDict['loglevel'] = " = (debug|info|warning|error|critical);" + gramSpec = [] assert set(gramRules) == set(gramDict.keys()) @@ -95,18 +150,6 @@ class UtilGrammar(ancestor): for rulename in gramRules: gramSpec.append(gramDict[rulename]) - - - ## extra: the trace rule: - if specials: - specials2 = specials[1:] # remove initial "|" (at show it is "| actions | 'spoken forms'", here it is - # "actions | 'spoken forms'" only, because gramnames etc are not implemented - # for tracing) - traceSpecial = """ exported = trace (("""+ specials2 +""") | - ((on|off| {tracecount})("""+ specials2 +""")) | - (("""+ specials2 +""") (on|off|{tracecount}))) ;""" - gramSpec.append(traceSpecial) # add trace for actions of spoken forms (latter not implemented) - Mode = Normal LastMode = Normal @@ -117,21 +160,30 @@ def initialize(self): # temp set allResults to 0, disabling the messages trick: if not self.load(self.gramSpec, allResults=showAll): return + + print(f'loggers_names: {self.loggers_names}') + self.setList('logmodulename',self.loggers_names) self.RegisterControlObject(self) self.emptyList('message') # at post load # allGramNames = self.getUnimacroGrammarNames() # self.setList('gramnames', allGramNames) - self.setNumbersList('tracecount', tracecount) - self.activateAll() self.setMode(Normal) self.startExclusive = self.exclusive # exclusive state at start of recognition! ## if unimacroutils.getUser() == 'martijn': ## print 'martijn, set exclusive %s'% self.name ## self.setExclusive(1) - print('---now starting other Unimacro grammars:') + self.info('---now starting other Unimacro grammars:') + + def loggerName(self) ->str: + """Returns the name of a logger. Replace this and loggerShortName to create a logger for an inherited grammar. """ + return unimacro_l.control_logger_name() + + def loggerShortName(self) ->str: + """A key for use as a spoken form or user interface item. """ + return "control" def unload(self): self.UnregisterControlObject() @@ -182,14 +234,14 @@ def restoreMode(self): def gotResults_checkalphabet(self,words,fullResults): """check the exact spoken versions of the alphabet in spokenforms """ - version = unimacroutils.getDNSVersion() + version = status.getDNSVersion() _spok = spokenforms.SpokenForms(self.language, version) alph = 'alphabet' ini = spokenforms.ini for letter in string.ascii_lowercase: spoken = ini.get(alph, letter, '') if not spoken: - print('fill in in "%s_spokenform.ini", [alphabet] spoken for: "%s"'% (self.language, letter)) + self.info('fill in in "%s_spokenform.ini", [alphabet] spoken for: "%s"'% (self.language, letter)) continue if version < 11: normalform = '%s\\%s'% (letter.upper(), spoken) @@ -198,7 +250,7 @@ def gotResults_checkalphabet(self,words,fullResults): try: natlink.recognitionMimic([normalform]) except natlink.MimicFailed: - print('invalid spoken form "%s" for "%s"'% (spoken, letter)) + self.info('invalid spoken form "%s" for "%s"'% (spoken, letter)) if spoken == spoken.lower(): spoken = spoken.capitalize() trying = 'try capitalized variant' @@ -214,48 +266,12 @@ def gotResults_checkalphabet(self,words,fullResults): try: natlink.recognitionMimic([normalform]) except natlink.MimicFailed: - print('%s fails also: "%s" for "%s"'% (trying, spoken, letter)) + self.info('%s fails also: "%s" for "%s"'% (trying, spoken, letter)) else: - print('alphabet section is corrected with: "%s = %s"'% (letter, spoken)) + self.info('alphabet section is corrected with: "%s = %s"'% (letter, spoken)) ini.set(alph, letter, spoken) ini.writeIfChanged() - - def gotResults_trace(self,words,fullResults): - print('control, trace: %s'% words) - traceNumList = self.getNumbersFromSpoken(words) # returns a string or None - if traceNumList: - traceNum = int(traceNumList[0]) - else: - traceNum = None - - if self.hasCommon(words, 'actions'): - if self.hasCommon(words, 'show'): - actions.debugActionsShow() - elif self.hasCommon(words, 'off'): - actions.debugActions(0) - elif self.hasCommon(words, 'on'): - actions.debugActions(1) - elif traceNum: - actions.debugActions(traceNum) - else: - actions.debugActions(1) - elif self.hasCommon(words, 'spoken forms'): - print("no tracing possible for spoken forms") - - #def gotResults_voicecode(self,words,fullResults): - # """switch on if requirements are fulfilled - # - # voicecodeHome must exist - # emacs must be in foreground - # """ - # wxmed = os.path.join(voicecodeHome, 'mediator', 'wxmediator.py') - # if os.path.isfile(wxmed): - # commandLine = r"%spython.exe %s > D:\foo1.txt >> D:\foo2.txt"% (sys.prefix, wxmed) - # os.system(commandLine) - # else: - # print 'not a file: %s'% wxmed - def gotResults_switch(self,words,fullResults): #print 'control, switch: %s'% words if self.hasCommon(words, 'on'): @@ -275,6 +291,7 @@ def gotResults_switch(self,words,fullResults): for gname, gram in G.items(): if gram == self: continue + gram.checkForChanges = 1 self.switch(gram, gname, switchOn) else: gname = self.hasCommon(words, Gkeys) @@ -284,25 +301,37 @@ def gotResults_switch(self,words,fullResults): self.switch(gram, gname, switchOn) # self never needs switching on else: - print('_control switch, no valid grammar found, command: %s'% words) + self.info('_control switch, no valid grammar found, command: %s'% words) def switch(self, gram, gname, switchOn): """switch on or off grammar, and set in inifile, gram is the grammar object gname is the grammar name - switchOn is True or Fals + switchOn is True or False """ if gram == self: - print(f'should not be here, do not switch on of off _control {gram}') + self.error(f'should not be here, do not switch on of off _control {gram}') return None if switchOn: - self.checkInifile() - gram.ini.set('general', 'initial on', 1) - gram.ini.write() - unimacroutils.Wait(0.1) + if gram.ini: + gram.checkInifile() + gram.ini.set('general', 'initial on', 1) + gram.ini.write() + unimacroutils.Wait(0.1) + else: + self.error(f'--- ini file of grammar {gname} is invalid, please try "edit {gname}"...') + gramName = gram.getName() + unimacro_grammars_paths = self.getUnimacroGrammarNamesPaths() + try: + filepath = Path(unimacro_grammars_paths[gramName]) + except KeyError: + self.error(f'_control, grammar not in unimacro_grammars_paths dict: {gramName}, cannot switchOn') + return None + # now reload with force option. + self.info(f'_control, now reload grammar "{gramName}":') + natlinkmain.seen.clear() + natlinkmain.load_or_reload_module(filepath, force_load=True) - # gram.unload() - gram.initialize() return 1 # switch off: @@ -311,9 +340,31 @@ def switch(self, gram, gname, switchOn): gram.cancelMode() gram.deactivateAll() # gram.unload() - print('grammar "%s" switched off'% gram.getName()) + self.info('grammar "%s" switched off'% gram.getName()) return 1 - + + def gotResults_setlogging(self,words, fullresults): + """Sets a logger (name in first word) to a new loglevel + """ + self.debug(f"unimacro logger gotResults_logging_level words: {words} fullResults: {fullresults}") + + loglevel_for = words[0] # something like natlink, unimacro,... + new_level_str_mc,_ = fullresults[-1] + new_log_level_str = new_level_str_mc.upper() + #the string should be in the + logger_name = self.loggers[loglevel_for] + new_log_level = l.__dict__[new_log_level_str] + + self.info(f"New Log Level {new_log_level_str} for logger {logger_name}") + logger=l.getLogger(logger_name) + logger.setLevel(new_log_level) + + # def gotResults_loglevel(self,words,fullresults): + # """ + # """ + # self.debug(f"gotResults_loglevel words: {words} fullResults: {fullresults}") + + def gotResults_showexclusive(self,words,fullResults): All = 0 @@ -323,7 +374,7 @@ def gotResults_showexclusive(self,words,fullResults): else: Start=() # fix state at this moment (in case of Active grammars popup) - print(f'_control, showexclusive, exclusiveGrammars: {natbj.exclusiveGrammars}') + self.info(f'_control, showexclusive, exclusiveGrammars: {natbj.exclusiveGrammars}') if natbj.exclusiveGrammars: Exclusive = 1 self.BrowsePrepare(Start, All, Exclusive) @@ -347,7 +398,7 @@ def gotResults_showexclusive(self,words,fullResults): def gotResults_resetexclusive(self,words,fullResults): - print('reset exclusive') + self.info('reset exclusive') exclGrammars = natbj.getExclusiveGrammars() if exclGrammars: T = ['exclusive grammars:'] @@ -386,14 +437,28 @@ def gotResults_show(self,words,fullResults): if self.hasCommon(words, 'exclusive'): G = self.getExclusiveGrammars() exclNames = [gname for gname, gram in G.items() if gram.isExclusive()] - print(f'exclusive grammars (+ control) are: {exclNames}') + self.info(f'exclusive grammars (+ control) are: {exclNames}') self.gotResults_showexclusive(words, fullResults) return + if self.hasCommon(words,"loggers"): + L = ['\nAvailable Loggers apart from the root (natlink) logger:'] + for key, loggerid in self.loggers.items(): + logger=l.getLogger(loggerid) + level = logger.getEffectiveLevel() + levelname = l.getLevelName(level) + L.append(f'-- {key}: {loggerid}, loglevel: {levelname}') + L.append('The individual loglevels can be changed with "name loglevel (debug|info|warning|error|critical)" \n') + self.message('\n'.join(L)) + return grammars = self.getUnimacroGrammars() gramNames = list(grammars.keys()) - # print(f'_control, gramNames: {gramNames}') + print("gramNames") + print(f'{gramNames}') + print(f"self.debug {self.debug} self.info {self.info}") + self.info("info") + self.debug(f'_control, gramNames: {gramNames}') gramName = self.hasCommon(words, gramNames) if gramName: grammar = grammars[gramName] @@ -438,9 +503,9 @@ def gotResults_show(self,words,fullResults): activeGrammars = [g for g in G if G[g].isActive()] inactiveGrammars = [g for g in G if G[g].isLoaded() and not G[g].isActive()] switchedOffGrammars = [g for g in G if not G[g].isLoaded()] - print(f'activeGrammars: {activeGrammars}') - print(f'inactiveGrammars: {inactiveGrammars}') - print(f'switchedOffGrammars: {switchedOffGrammars}') + self.info(f'activeGrammars: {activeGrammars}') + self.info(f'inactiveGrammars: {inactiveGrammars}') + self.info(f'switchedOffGrammars: {switchedOffGrammars}') # for grammar_name, gram in G.items(): # print(f'grammar_name: {grammar_name}, gram: {gram}') @@ -511,18 +576,18 @@ def gotResults_edit(self,words,fullResults): try: grammar = grammars[gramName] except KeyError: - print(f'grammar {words[-1:]} not found in list of gramNames:\n{gramNames}') + self.error(f'grammar {words[-1:]} not found in list of gramNames:\n{gramNames}') return # print(f'grammar: {gramName}: {grammar}') if self.hasCommon(words, 'grammar'): unimacro_grammars_paths = self.getUnimacroGrammarNamesPaths() - print(f'unimacro_grammars_paths:\n{unimacro_grammars_paths}\n') + # print(f'unimacro_grammars_paths:\n{unimacro_grammars_paths}\n') try: filepath = unimacro_grammars_paths[gramName] except KeyError: - print(f'grammar not in unimacro_grammars_paths dict: {gramName}') + self.error(f'grammar not in unimacro_grammars_paths dict: {gramName}') return - print(f'open for edit file: "{filepath}"') + self.info(f'open for edit file: "{filepath}"') self.openFileDefault(filepath, mode="edit", name=f'edit grammar {gramName}') else: # edit the inifile @@ -536,7 +601,7 @@ def switchOff(self, **kw): """overload, this grammar never switches off """ - print('remains switched on: %s' % self) + self.info('remains switched on: %s' % self) def switchOn(self, **kw): """overload, just switch on @@ -596,7 +661,7 @@ def getUnimacroGrammarNamesPaths(self): unimacro_modules[name] = try_file break else: - print(f'not found in natlink_modules_files: {name}') + self.info(f'not found in natlink_modules_files: {name}') unimacro_modules[name] = name # not found return unimacro_modules @@ -677,16 +742,13 @@ def checkOriginalFileWithActualTxtPy(name, org_path, txt_path, py_path): # standard stuff Joel (adapted for python3, QH, unimacro): if __name__ == "__main__": ## interactive use, for debugging: - natlink.natConnect() - try: + with natlink.natConnect(): utilGrammar = UtilGrammar(inifile_stem='_control') utilGrammar.startInifile() utilGrammar.initialize() Words = ['edit', 'grammar', 'control'] FR = {} utilGrammar.gotResults_edit(Words, FR) - finally: - natlink.natDisconnect() elif __name__.find('.') == -1: # standard startup when Dragon starts: utilGrammar = UtilGrammar() diff --git a/src/unimacro/natlinkutilsbj.py b/src/unimacro/natlinkutilsbj.py index 43b630e..2c08df3 100644 --- a/src/unimacro/natlinkutilsbj.py +++ b/src/unimacro/natlinkutilsbj.py @@ -27,7 +27,7 @@ # See the class BrowsableGrammar for documentation on the use of # the Grammar browser. # revised many times by Quintijn Hoogenboom -#pylint:disable=C0302, C0116, W0702, W0201, W0703, R0915, R0913, W0613, R0912, R0914, R0902, C0209, W0602 +#pylint:disable=C0302, C0116, W0702, W0201, W0703, R0915, R0913, W0613, R0912, R0914, R0902, C0209, W0602, W0212 #pylint:disable=E1101 """subclasses classes for natlink grammar files and utility functions @@ -44,8 +44,9 @@ import copy import string from pathlib import Path +import logging +from logging import Logger import win32com - import natlink from natlinkcore import loader from natlinkcore import gramparser # for translation with GramScannerReverse @@ -69,7 +70,7 @@ from unimacro import D_ from unimacro import spokenforms # for numbers spoken forms, IniGrammar (and also then DocstringGrammar) - +from unimacro import logname status = natlinkstatus.NatlinkStatus() natlinkmain = loader.NatlinkMain() @@ -302,8 +303,41 @@ def __init__(self): self.want_on_or_off = None # True: on False: off None: no decision self.hypothesis = 0 self.allResults = 0 - - + + + def loggerName(self) ->str: + """Returns the name of a logger. Replace this and loggerShortName to create a logger for an inherited grammar. """ + return logname() + + def loggerShortName(self) ->str: + """A key for use as a spoken form or user interface item.""" + return "unimacro" + + def getLogger(self) -> logging.Logger: + return logging.getLogger(self.loggerName()) + + + + # TODO Doug, I can understand this a bit, but is it ok? It seems to stop Dragon... QH + #avoid copy and pasting methods that delegate to getLoger() + def wrapped_log(method): + """Delegates to {method} of a Logger object from self.getLogger()""" + def fn(self,*args,**kwargs): + logger=self.getLogger() + try: + return method(logger,*args,**kwargs) + except Exception as e: + print("Failure attempting to call {method} on {logger}, \nargs {args} \nkwargs {kwargs}\nException:\n{e}") + return False + + return fn + + #add methods to delegate calls to logger, so we wave info, warn, etc. + wrapped_logger=[Logger.info,Logger.setLevel,Logger.debug,Logger.warning,Logger.error,Logger.exception,Logger.critical,Logger.log] + for n in wrapped_logger: + locals()[n.__name__]=wrapped_log(n) + + def getExclusiveGrammars(self): """return the dict of (name, grammarobject) of GrammarX objects that are exclusive """ @@ -483,7 +517,7 @@ def activate(self, ruleName, window=0, exclusive=None, noError=0): if exclusive is not None: self.setExclusive(exclusive) - def deactivate(self, ruleName, noError=0): + def deactivate(self, ruleName, noError=0, dpi16trick=True): self.__inherited.deactivate(self, ruleName, noError) # if not self.activeRules: # self.isActive = 0 @@ -1024,11 +1058,13 @@ def __init__(self, inifile_stem=None): self.module_name = self.__module__.rsplit('.', maxsplit=1)[-1] self.inifile_stem = inifile_stem or self.module_name self.language = status.get_language() - try: - self.ini - except AttributeError: + + + if not hasattr(self, 'ini'): self.startInifile() self.name = self.checkName() + if self.ini is None: + print(f'Serious warning, grammar "{self.name}" has no valid ini file, please correct errors') try: self.iniIgnoreGrammarLists except AttributeError: @@ -1055,7 +1091,6 @@ def __init__(self, inifile_stem=None): # here the grammar is not loaded yet, but the ini file is present # this could have been done in the user grammar already... - self.debug = None # can be set in some grammar at initialize time #mod = sys.modules[self.__module__] ## version = getattr(mod, '__version__', '---') self.DNSVersion = status.getDNSVersion() @@ -1097,10 +1132,12 @@ def checkName(self): else: if not self.name is self.__class__.name: return self.name - - n = self.ini.get('grammar name', 'name') - if n: - return n + try: + n = self.ini.get('grammar name', 'name') + if n: + return n + except AttributeError: + pass try: n = self.name @@ -1108,9 +1145,10 @@ def checkName(self): n = self.__module__ n = n.replace('_', ' ') n = n.strip() - print(f'setting grammar name to: {n}') - self.ini.set('grammar name', 'name', n) - self.ini.write() + if self.ini: + print(f'setting grammar name to: {n}') + self.ini.set('grammar name', 'name', n) + self.ini.write() return n def hasCommon(self, one, two, allResults=None, withIndex=None): @@ -1173,6 +1211,9 @@ def translateGrammar(self, gramSpec): """ #pylint:disable=R0914, R0912, R0915, R1702 + if not self.ini: + return None + self.gramWords = {} # keys: new grammar words, values mappings to the old grammar words maybe empty if no translateWords translateWords = self.getDictOfGrammarWordsTranslations() # from current inifile if not translateWords: @@ -1527,7 +1568,7 @@ def switchOn(self, **kw): # try: self.fillGrammarLists() - print(f'IniGrammar switched on: {self.getName()}') + # print(f'IniGrammar switched on: {self.getName()}') def showInifile(self, body=None, grammarLists=None, ini=None, showGramspec=1, prefix=None, commandExplanation=None, @@ -1570,9 +1611,9 @@ def showInifile(self, body=None, grammarLists=None, ini=None, L.append(prefix) else: try: - formatLine = {'nld': 'Uitleg voor Unimacro (NatLink) grammatica "%s" (bestand: %s.py)'}[language] + formatLine = {'nld': 'Uitleg voor Unimacro (Natlink) grammatica "%s" (bestand: %s.py)'}[language] except: - formatLine = 'Help for Unimacro (NatLink) grammar "%s" (file: %s.py)' + formatLine = 'Help for Unimacro (Natlink) grammar "%s" (file: %s.py)' L.append(formatLine % (self.name, moduleName)) L.append('') @@ -1813,7 +1854,8 @@ def fillGrammarLists(self, listOfLists=None): """ #pylint:disable=R0912 if not self.ini: - raise UnimacroError('no ini file active for grammar: %s'% self.GetName()) + print(f'--- no valid ini file for grammar: "{self.GetName()}", will not fill grammar lists\n\tPlease try to correct via "edit {self.getName()}"') + return ini = self.ini fromGrammar = copy.copy(self.validLists) allListsFromIni = ini.get() @@ -1970,11 +2012,9 @@ def fillList(self, listName): # else: l = self.ini.get(n) if l: - #if self.debug: #print '%s: filling list %s with %s items'% (self.name, listName, len(l)) self.setList(n, l) return 1 - #if self.debug: #print '%s: not filling list %s, no items found'% (self.name, listName) self.emptyList(n) return None @@ -2385,7 +2425,10 @@ def makeDefaultInifile(self, inifile=None, enxVersion=None): self.ini.set('an instruction', 'note 3', 'This section can be deleted after reading') self.fillDefaultInifile(self.ini) + self.ini.writeIfChanged() self.ini.close() + name = self.getName() + print(f'Please edit this new inifile: {self.ini._file}, possibly by calling "edit {name}') def fillDefaultInifile(self, ini): """set initial settings for ini file, overload! @@ -2396,9 +2439,12 @@ def fillDefaultInifile(self, ini): name = self.__module__.strip('_') name = name.replace("_", " ") ini.set('grammar name', 'name', name) - for l in self.validLists: - if not('iniIgnoreGrammarLists' in dir(self) and l in self.iniIgnoreGrammarLists): - ini.set(l) + + if hasattr(self, "validLists") and hasattr(self, 'iniIgnoreGrammarLists'): + for l in self.validLists: + if not l in self.iniIgnoreGrammarLists: + ini.set(l) + ini.write() def error(self, message): """gives an error message, and leaves variable Error @@ -2821,7 +2867,6 @@ def getTopOrChild(self, progInfo=None, childClass=None): and then always return False, child, except even when rule in actions.ini says different... """ - #TODO QH this routine sucks if progInfo is None: progInfo = unimacroutils.getProgInfo() @@ -2833,17 +2878,14 @@ def getTopOrChild(self, progInfo=None, childClass=None): istop = False elif childClass and progInfo.classname == childClass: if actions.childWindowBehavesLikeTop( progInfo ): - if self.debug: - print('getTopOrChild: top mode, altough of class "%s", but because of "child behaves like top" in "actions.ini"'% childClass) + self.debug('getTopOrChild: top mode, altough of class "%s", but because of "child behaves like top" in "actions.ini"'% childClass) istop = True else: - if self.debug: - print('getTopOrChild: child mode, because of className "%s"'% childClass) + self.debug('getTopOrChild: child mode, because of className "%s"'% childClass) istop = False else: if actions.childWindowBehavesLikeTop( progInfo ): - if self.debug: - print('getTopOrChild: top mode, because but because of "child behaves like top" in "actions.ini"') + self.debug('getTopOrChild: top mode, because but because of "child behaves like top" in "actions.ini"') istop = True return istop diff --git a/src/unimacro/sample_ini/enx_inifiles/_brackets.ini b/src/unimacro/sample_ini/enx_inifiles/_brackets.ini deleted file mode 100644 index 53589f1..0000000 --- a/src/unimacro/sample_ini/enx_inifiles/_brackets.ini +++ /dev/null @@ -1,31 +0,0 @@ -[brackets] -angle brackets = <> -asteriscs = ** -braces = {} -brackets = () -colons = :: -double angle brackets = <<>> -double quotes = '""' -double underscores = ____ -index = [] -parens = () -parenthesis = () -quotes = '""' -single quotes = "''" -square brackets = [] -triple quotes = '""""""' -underscores = __ -vertical bars = || -HTML square brackets = [|] -HTML angle brackets = <|> - -[general] -initial on = 1 - - -[grammar name] -name = brackets - - -[grammar words] -between = between diff --git a/src/unimacro/sample_ini/enx_inifiles/_clickbyvoice.ini b/src/unimacro/sample_ini/enx_inifiles/_clickbyvoice.ini deleted file mode 100644 index 1f24787..0000000 --- a/src/unimacro/sample_ini/enx_inifiles/_clickbyvoice.ini +++ /dev/null @@ -1,37 +0,0 @@ -[additionalonoroff] -on = + -contrast = c -hibrid = h -overlay = o -off = - - - -[general] -initial on = 1 -show numbers = :+h - -[grammar name] -name = clickbyvoice - - -[grammar words] -pick = click | pick - -[navigateoptions] -drop down = f;{alt+down} -focus = f -hover = h -copy content = s -copy link = k -link copy = k -new doc = t -new document = t -new tab = t -new tab stay = b -new window = w -quick menu = f;;;;{shift+f10} -shortcut menu = f;;;;{shift+f10} - - -[pagecommands] -refresh = {f5} diff --git a/src/unimacro/sample_ini/enx_inifiles/_folders.ini b/src/unimacro/sample_ini/enx_inifiles/_folders.ini deleted file mode 100644 index e4d45ab..0000000 --- a/src/unimacro/sample_ini/enx_inifiles/_folders.ini +++ /dev/null @@ -1,120 +0,0 @@ -[extensions] -pdf = pdf -doc X. = docx -C. S. S. = css -H. T. M. L. = html -T. X. T. = txt -doc = doc -ini = ini - -[filecommands] -close = <> -copy = copy -edit = edit -maximise = <> -minimise = <> -paste = paste -print = <> -restore = <> - - -[fileopenprograms] -edit = edit -emacs = emacs -notepad = notepad -word = winword - -[files] - - -[foldercommands] -close = <> -copy = copy -explorer = explorer -maximize = <> -minimize = <> -new = new -paste = paste -restore = <> - -[folders] -documents = ~ - - -[general] -automatic track files = F -automatic track folders = F -child behaves like top = natspeak: dragonbar, dictation box -ignore file patterns = ~*; .* -track file extensions = .py; .doc; .xls; .txt; .ini; .docx; .xlsx -track files virtualdrives = -track folders virtualdrives = - -[grammar name] -name = folders - - -[grammar words] -drive = drive -edit = edit -environment = environment -explorer = explorer -file = file -folder = folder -folder up = folder up -folders = folders -input = input -local = local -maximise = maximise -new = new -on = on -open with = open with -set = set -site = site -this = this -website = website - - -[letters] -Quebec = q -alpha = a -charlie = c -delta = d -echo = e -foxtrot = f -golf = g -hotel = h -india = i -juliet = j -kilo = k - -[subversionfilecommands] -add = add -commit = commit -update = update -log = log - - -[subversionfoldercommands] -add = add -commit = commit -export = export -update = update -log = log - -[virtualdrives] -md = %HOME% -pf = %PROGRAMFILES% - - -[websiteopenprograms] -chrome = chrome -firefox = firefox -internet explorer = iexplore -explorer = iexplore - - -[websites] -sitegen = sitegen.nl -unimacro = http://qh.antenna.nl/unimacro -speech computing forum = http://www.speechcomputing.com/tracker \ No newline at end of file diff --git a/src/unimacro/sample_ini/enx_inifiles/_general.ini b/src/unimacro/sample_ini/enx_inifiles/_general.ini deleted file mode 100644 index 8df4936..0000000 --- a/src/unimacro/sample_ini/enx_inifiles/_general.ini +++ /dev/null @@ -1,88 +0,0 @@ -[browsers] -Chrome = chrome -Firefox = firefox -Google Chrome = chrome -Internet Explorer = iexplore -Netscape = netscape -Opera = opera - -unimacro grammars = - _general; _oops; sol; _folders - _brackets; _commands; _editcomments - _number;_repeat;_tasks; firefox_browsing - _keystrokes; _lines; - -unimacro modules = natlinkutilsqh; natlinkutilsbj; actions; BrowseGrammar - -[general] -initial on = 1 - - -[grammar name] -name = general - - -[grammar words] -after = after -back = back -batch = batch -before = before -browse with = browse with -command = Command -comment = Comment -do = do -documentation = documentation -down = down -extend = extend -for = for -forward = forward -give = Give -go back = go back -here = Here -hyphenate = Hyphenate -info = info -information = information -insert = insert -last = last -make = Make -method = Method -mode = mode -mouse = Mouse -name = Name -natlink = Natlink -new = new -path = path -phrase = Phrase -redo = Redo -release = Release -reload = reload -search = search -test = test -that = That -times = times -trick = trick -undo = Undo -unimacro = unimacro -up = up -user = user -variable = Variable -window = window -with = with -without = without -word = word -words = words - -[name phrase] -inbetweenwords = van; de; the -secondchristiannames = louise - - -[searchwords] -class = "class " -function = "def " -section = "[" - - -[spoken forms] -kcs = kay see es -ksc = kay essee \ No newline at end of file diff --git a/src/unimacro/sample_ini/enx_inifiles/_lines.ini b/src/unimacro/sample_ini/enx_inifiles/_lines.ini deleted file mode 100644 index 48fcace..0000000 --- a/src/unimacro/sample_ini/enx_inifiles/_lines.ini +++ /dev/null @@ -1,77 +0,0 @@ -[general] -deactivate = - sol - natspeak: messages, correct - -ignore = - empty - natspeak: mouse grid - - -initial on = 1 - -[grammar name] -name = lines - -[grammar words] -and = and -copy = copy -down = down -here = Here -hundred = hundred -left = left -line = line -line base = line base -lines = lines -million = million -move = move -next = next -number = number -off = off -plus = plus -previous = previous -right = right -that = THAT -these = these -this = this -thousand = thousand -through = through -to = to -up = up -word = word -words = words - - -[simpleaction] -collapse = {numkey-} -comment = <> -copy = <> -copy to DragonPad = HW copy that to DragonPad -cut = <> -cut to DragonPad = HW cut that to DragonPad -delete = <> -duplicate = <> -edit = HW Edit that -edit comment = HW Edit, Comment -edit docstring = HW Edit, Doc String -emacs = <> -end = <> -expand = {numkey+} -indent = <> -indent too = <><> -insert = <> -paste = <><> -paste over = <> -select = -uncomment = <> -unindent = <> -unindent too = <><> - - -[wordaction] -copy = <> -cut = <> -delete = <> -paste = <> -select = -self dot paste = self.<> diff --git a/src/unimacro/sample_ini/enx_inifiles/_tasks.ini b/src/unimacro/sample_ini/enx_inifiles/_tasks.ini deleted file mode 100644 index b33257e..0000000 --- a/src/unimacro/sample_ini/enx_inifiles/_tasks.ini +++ /dev/null @@ -1,133 +0,0 @@ -[application] -calc = calc -calculator = calc -command = cmd -dragonpad = dragonpad -edit = edit -emacs = emacs -email = outlook -excel = excel -firefox = firefox -idle = idle -internet = iexplore -messages = messages -notepad = notepad -pythonwin = pythonwin -voice code = voicecode -voice coder = voicecode -word = winword - - -[directionplusreverse] -center = center; middle - -centerdown = - centerdown; downcenter; centerbottom; bottomcenter; - middledown; downmiddle; middlebottom; bottommiddle; - - -centerup = centerup; upcenter; centertop; topcenter; middleup; middletop; upmiddle; topmiddle - -down = down; bottom -left = left -leftcenter = leftcenter; centerleft; leftmiddle; middleleft -leftdown = leftdown; leftbottom; bottomleft; downleft -leftup = leftup; upleft; lefttop; topleft -right = -rightcenter = rightcenter; centerright; rightmiddle; middleright -rightdown = rightdown; rightbottom; downright; bottomright -rightup = righttop; rightup; upright; topright -up = up; top - - -[directionreverse] -down = down -left = left -right = -up = up; top - - -[general] -center mouse = T -enable search commands = F -do taskswitch with windows key = F -initial on = 1 -max icon number = 15 -max task number = 30 -max window number = 9 -split left right = 0.5 -split top down = 0.5 -switchapps = pythonwin; emacs; dragonpad; edit; idle - - -[grammar name] -name = tasks - - -[grammar words] -back = Back -centimeters = centimeters -clock = clock -convert = convert -degrees = degrees -dos = dos -file = file -get task position = get task position -give = give -here = Here -icon = icon -inches = inches -info = info -menu = menu -millimeters = millimeters -move = move -next = Next -percent = percent -pixels = pixels -position = position -previous = Previous -remove cursor = remove cursor -resize = resize -search = search -shrink = shrink -start = start -stretch = stretch -switch file to = switch file to -task = task -to = to -unix = unix -window = Window -windows = windows - - -[iconaction] -OK = {enter} -shortcut menu = {shift+f10} -space = {space} - - -[startmenucommands] -all programs = {extup}{extright} -close = SSK {esc} - -[firstlast] -before last = -2 -fifth = 5 -first = 1 -fourth = 4 -last = -1 -second = 2 -third = 3 - -[taskaction] -close = <> -kill = KW -maximise = <> -minimise = <> -mouse = -other display = <> -refresh = <> -resize = <> -restore = <> -test = <>;<>; <> -tile = RW;TOCLOCK right; h; RTW diff --git a/tests/test_grammar_brackets.py b/tests/test_grammar_brackets.py new file mode 100644 index 0000000..26a27b1 --- /dev/null +++ b/tests/test_grammar_brackets.py @@ -0,0 +1,63 @@ +#pylint:disable=E1101 +from pathlib import Path +import pytest +import natlink +from unimacro.UnimacroGrammars import _brackets +from natlinkcore import natlinkstatus +status = natlinkstatus.NatlinkStatus + +thisDir = Path(__file__).parent + + +def test_testpleftpright(unimacro_setup): + """check the split of a brackets string in left and right + + "" should return " and " + asymmetric strings like f'|' (with a | as split) should return f' and ' + + """ + gram = _brackets.BracketsGrammar() + gram.startInifile() #modName = '_tasks') + gram.initialize() + + # test one + words = ['brackets', 'quotes'] + fr = {} + gram.gotResultsInit(words, fr) + # print(f'natbj.loadedGrammars: {natbj.loadedGrammars}') + gram.rule_brackets(words) + assert gram.pleft == '("' + assert gram.pright == '")' + + # test two: + words = ['quotes'] + gram.gotResultsInit(words, fr) + # print(f'natbj.loadedGrammars: {natbj.loadedGrammars}') + gram.rule_brackets(words) + assert gram.pleft == '"' + assert gram.pright == '"' + +def test_testactivebracketrules(unimacro_setup): + """do all the active words in the bracket rules accordin to the inifile + + """ + gram = _brackets.BracketsGrammar() + gram.startInifile() #modName = '_tasks') + gram.initialize() + + # test one + all_words = gram.ini.get('brackets') + for i, word in enumerate(all_words): + n = i + 1 + words = [word] + result = gram.ini.get('brackets', word) + print(f'test {word}, expect {result}') + fr = {} + gram.gotResultsInit(words, fr) + # print(f'natbj.loadedGrammars: {natbj.loadedGrammars}') + gram.rule_brackets(words) + assert n == len(all_words) + + +if __name__ == "__main__": + pytest.main(['test_grammar_brackets.py']) diff --git a/tests/test_grammar_folders.py b/tests/test_grammar_folders.py index edc3324..8e46b02 100644 --- a/tests/test_grammar_folders.py +++ b/tests/test_grammar_folders.py @@ -12,6 +12,8 @@ def test_fill_folders_list(unimacro_setup): thisGrammar = ThisGrammar() thisGrammar.startInifile() #modName = '_folders') thisGrammar.initialize() + # minor assertion, mainly for interactive use + assert thisGrammar.foldersDict def test_folder_remember(unimacro_setup): """go through folder remember functions @@ -26,7 +28,7 @@ def test_folder_remember(unimacro_setup): thisGrammar.wantedFolder = r'C:\Windows' thisGrammar.gotResults_remember(words, FR) - + # no assertion, for interactive use if __name__ == "__main__": diff --git a/tests/test_grammar_general.py b/tests/test_grammar_general.py index 3297008..70cac8b 100644 --- a/tests/test_grammar_general.py +++ b/tests/test_grammar_general.py @@ -20,9 +20,12 @@ def test_getTopOrChild(unimacro_setup,monkeypatch): thisGrammar.startInifile() #modName = '_general') thisGrammar.initialize() hndle = 10 + ##(progpath, prog, title, toporchild, classname, hndle) ## ProgInfo(progpath, prog, title, toporchild, classname, HNDLE) + progInfo = unimacroutils.ProgInfo('path/to/program.exe', 'program', 'window title', 'top', 'classname', hndle) + # thisGrammar.gotBegin(modInfo) thisGrammar.progInfo = progInfo assert thisGrammar.getTopOrChild(progInfo=progInfo, childClass=None) is True diff --git a/tests/test_new_inifile_directory.py b/tests/test_new_inifile_directory.py new file mode 100644 index 0000000..cb5f3f2 --- /dev/null +++ b/tests/test_new_inifile_directory.py @@ -0,0 +1,61 @@ +"""this one tests if an new .ini file is automatically made, when no examples are available... + +Not very nice programmed, but for the moment it works (January 2024, Quintijn) + +""" +import os +from pathlib import Path +import pytest +from unimacro import natlinkutilsbj as natbj +from natlinkcore import natlinkstatus +status = natlinkstatus.NatlinkStatus() +#boilerplate to copy the grammar ini file into the correct location +#just chagne grammar_ini to the name of the grammar ini file you want copied into +#unimacro user directory +# from conftest import make_copy_grammar_ini_fixture +grammar_ini= "_newbie.ini" +#grammar_ini_fixture will depend on unimacro_setup, so you dont' need to specfiy unimacro_setup +#as a fixture in your tests. Though you can +# grammar_ini_fixture=make_copy_grammar_ini_fixture(grammar_ini) +#end of boilderplate to copy the gramar ini file into correct location + +# #leave these first two samples in as documentation in test_brackets. confests docs say to look in this file for examples. +# def test_sample_grammar_ini_fixture1(grammar_ini_fixture): +# assert True +# +# def test_sample_grammar_ini_fixture2(grammar_ini_fixture,unimacro_setup): +# print(f"Unimacro setup sample: {unimacro_setup} grammar_ini_fixutre {grammar_ini_fixture} (should be the same)") +# assert True + + +class Newbie(natbj.IniGrammar): + """simple grammar which is initially on + """ + gramSpec = """ exported = 'grammar on'; + """ + def initialize(self): + self.ini.set('general', 'initial on', "True") + self.switchOnOrOff() + def gotResults_gramon(self, words, fullResults): + print(f'got gramon: {words}') + + +def test_get_newbie_without_demo_inifile(unimacro_setup): + """see if we can get all the grammars + """ + # this works apparently in the normal unimacro_user_directory, first delete _newbie.ini + inifile_stem = "_newbie" + userDir = status.getUnimacroUserDirectory() + commandDir = os.path.join(userDir, status.language +"_inifiles") + inifile = Path(commandDir)/ f'{inifile_stem}.ini' + if inifile.is_file(): + inifile.unlink() + + newbie = Newbie(inifile_stem="_newbie") + newbie.initialize() + assert newbie.ini + + +if __name__ == "__main__": + pytest.main(['test_new_inifile_directory.py']) + \ No newline at end of file From f79ecbc69bcc2363be33d233a80e011024cf2c28 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 13 Mar 2024 18:23:17 +0100 Subject: [PATCH 05/11] Update __init__.py to 4.1.4.4 --- src/unimacro/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index be84d55..43769f2 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -21,5 +21,5 @@ def control_logger_name() -> str : def logname() -> str: """ Returns the name of the unimacro logger.""" return "natlink.unimacro" -__version__ = '4.1.4.2' +__version__ = '4.1.4.4' From bc542eae930f4127e5b0e0fed81ce1f8aea35d6b Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Fri, 15 Mar 2024 15:41:02 +0100 Subject: [PATCH 06/11] Update __init__.py to 4.1.4.5 trying an intermediate release --- src/unimacro/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index 43769f2..9da60fb 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -21,5 +21,5 @@ def control_logger_name() -> str : def logname() -> str: """ Returns the name of the unimacro logger.""" return "natlink.unimacro" -__version__ = '4.1.4.4' +__version__ = '4.1.4.5' From 5c040a5e668c9cbf62c6b6fe0307fbe2197e8da6 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 1 May 2024 17:58:43 +0200 Subject: [PATCH 07/11] Check unimacro feb24, go to version 4.1.5 (#50) * _general.py fixed several dgndictation things (also in nsformat.py of natlinkcore) * fixed a few flaws in _brackets, started test_grammar_brackets.py * brought win32clipboard under try finally * tidy up _general * few small things _clickbyvoice.py, mostly tyding up things * work in progress _folders, getting natlinktimer again at work... * logging _folders settings * working on the _folders grammar option and checking automatic tracking of recent folders and files en subfolders * refinement with switching on a unimacro grammar * effort to diminish the errors when an ini file is invalid... * _folders automatic catching files and folders made working again, including the natlinktimer. * add number simple.ini to sample ini files... * changed extenvvars in dtactions, with changes here too (when error persists, try "pip upate dtactions" * go to version 4.1.5 --------- Co-authored-by: Doug Ransom --- src/unimacro/UnimacroGrammars/_folders.py | 33 ++++++++++++----------- src/unimacro/__init__.py | 2 +- tests/test_grammar_folders.py | 1 + 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/unimacro/UnimacroGrammars/_folders.py b/src/unimacro/UnimacroGrammars/_folders.py index b541e13..7b16604 100644 --- a/src/unimacro/UnimacroGrammars/_folders.py +++ b/src/unimacro/UnimacroGrammars/_folders.py @@ -76,6 +76,9 @@ # from unimacro.unimacro_wxpythondialogs import InputBox # import natlinkcore.natlinkutils as natut +# manipulating file names with env variables etc... +envvars = extenvvars.ExtEnvVars() + thisDir = str(Path(__file__).parent) status = natlinkstatus.NatlinkStatus() # for getting unicode explorer window titles: @@ -160,7 +163,6 @@ class ThisGrammar(ancestor): = (copy (name|path)) | ((name|path) copy); """ - def initialize(self): # self.envDict = natlinkcorefunctions.getAllFolderEnvironmentVariables() # for (generalised) environment variables self.subfiles = self.subfiles = self.activeFolder = self.activeTimerFolder = None # for catching on the fly in explorer windows (CabinetClassW) @@ -169,11 +171,12 @@ def initialize(self): self.dialogNumberRange = [] # ditto self.catchRemember = "" self.inTimerRecentFolders = False - self.prevDisplayRecentFolders = None # displaying recent folders list self.prevActiveFolder = None self.subfoldersDict = {} self.subfilesDict = {} self.foldersSet = set() + + if not self.language: self.error("no valid language in grammar "+__name__+" grammar not initialized") return @@ -547,7 +550,7 @@ def getFolderFromVirtualDrive(self, vd): also make alternative paths possible like (C|D):/Documents """ # natlinkcorefunctions.printAllEnvVariables() - vd = extenvvars.expandEnvVariables(vd) + vd = envvars.expandEnvVariables(vd) for possiblePath in loop_through_alternative_paths(vd): folder = self.substituteFolder(possiblePath) if os.path.isdir(folder): @@ -624,8 +627,8 @@ def getActiveFolder(self, hndle=None, className=None): self.debug("getActiveFolder, got: %s",nf) self.prevActiveFolder = nf return nf - result = extenvvars.getFolderFromLibraryName(f) - if result and os.path.isdir(f): + result = envvars.getFolderFromLibraryName(f) + if result and os.path.isdir(result): self.debug("getActiveFolder, via getFolderFromLibraryName %s: %s", f, result) return os.path.normpath(result) self.warning('getActiveFolder, strange invalid path for folder: %s', f) @@ -874,18 +877,13 @@ def displayRecentFolders(self): mess_list = ["--- recent folders:"] if not self.recentfoldersDict: message = 'recent folders list is empty at the moment' - self.prevDisplayRecentFolders = message self.info(message) return for name, value in reversed(self.recentfoldersDict.items()): mess_list.append('- %s: %s'% (name, value)) mess_list.append('-'*20) message = '\n'.join(mess_list) - if message == self.prevDisplayRecentFolders: - self.info("recent folders, no change") - else: - self.prevDisplayRecentFolders = message - Message(message) + Message(message) # def gotoRecentFolder(self, chooseNum): @@ -1260,9 +1258,9 @@ def gotResults_remember(self, words, fullResults): # reset variables, no action in gotResults: self.wantedFile = self.wantedFolder = self.wantedWebsite = "" self.info(f'thisDir: {thisDir}') - UnimacroDirectory = extenvvars.expandEnvVariableAtStart('%Unimacro%') + UnimacroDirectory = envvars.expandEnvVariableAtStart('%Unimacro%') self.info(f'UnimacroDirectory: {UnimacroDirectory}') - UnimacroGrammarsDirectory = extenvvars.expandEnvVariableAtStart('%UnimacroGrammars%') + UnimacroGrammarsDirectory = envvars.expandEnvVariableAtStart('%UnimacroGrammars%') self.info(f'UnimacroGrammarsDirectory: {UnimacroGrammarsDirectory}') makeFromTemplateAndExecute(UnimacroDirectory, "unimacrofoldersremembertemplate.py", UnimacroGrammarsDirectory, "rememberdialog.py", prompt, text, default, inifile, section, value, pausetime=pausetime) @@ -1629,7 +1627,7 @@ def substituteEnvVariable(self,folder): With expandEnvVars, also NATLINK and related variables can be handled. NATLINKDIRECTORY, COREDIRECTORY etc. """ - substitute = extenvvars.expandEnvVariables(folder) + substitute = envvars.expandEnvVariables(folder) return substitute def substituteFilename(self, filename): @@ -2447,10 +2445,15 @@ def unload(): thisGrammar = ThisGrammar(inifile_stem="_folders") # thisGrammar.startInifile() thisGrammar.initialize() + # print(thisGrammar.envvars) + # get hndle of a explore window (via _general "give window info") and try interactive + # thisGrammar.catchTimerRecentFolders(132524, "CabinetWClass") + active_folder = thisGrammar.getActiveFolder(198434) + print(f'active_folder: {active_folder}') # get hndle of a explore window (via _general "give window info") and try interactive # thisGrammar.catchTimerRecentFolders(132524, "CabinetWClass") - thisGrammar.getActiveFolder(67062) + thisGrammar.getActiveFol>>> master thisGrammar.displayRecentFolders() # # Words = ['folder', 'dtactions'] diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index 9da60fb..f3f0847 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -21,5 +21,5 @@ def control_logger_name() -> str : def logname() -> str: """ Returns the name of the unimacro logger.""" return "natlink.unimacro" -__version__ = '4.1.4.5' +__version__ = '4.1.5' diff --git a/tests/test_grammar_folders.py b/tests/test_grammar_folders.py index 8e46b02..de4144d 100644 --- a/tests/test_grammar_folders.py +++ b/tests/test_grammar_folders.py @@ -5,6 +5,7 @@ thisDir = Path(__file__).parent + def test_fill_folders_list(unimacro_setup): """see if (with debugging) the list folders (foldersDict) is filled """ From 9eb122d678b95cbb65ac7b163196eb41723a3fef Mon Sep 17 00:00:00 2001 From: Doug Ransom Date: Wed, 7 Aug 2024 10:14:05 -0700 Subject: [PATCH 08/11] Union, intersect take advantage of built ins now availalbe. Tweaks around logging. --- src/unimacro/UnimacroGrammars/_folders.py | 1 + src/unimacro/natlinkutilsbj.py | 19 +++++++++++--- .../sample_global_dictation/_control.py | 25 ++++++++++--------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/unimacro/UnimacroGrammars/_folders.py b/src/unimacro/UnimacroGrammars/_folders.py index 4793810..22c815f 100644 --- a/src/unimacro/UnimacroGrammars/_folders.py +++ b/src/unimacro/UnimacroGrammars/_folders.py @@ -231,6 +231,7 @@ def gotResultsInit(self,words,fullResults): self.progInfo = unimacroutils.getProgInfo() + def handleTrackFilesAndFolders(self, activeFolder): """set or empty lists for activeFolder and set/reset self.activeFolder """ diff --git a/src/unimacro/natlinkutilsbj.py b/src/unimacro/natlinkutilsbj.py index f95b12c..b1bee29 100644 --- a/src/unimacro/natlinkutilsbj.py +++ b/src/unimacro/natlinkutilsbj.py @@ -317,11 +317,22 @@ def fn(self,*args,**kwargs): return fn #add methods to delegate calls to logger, so we wave info, warn, etc. - wrapped_logger=[Logger.info,Logger.setLevel,Logger.debug,Logger.warning,Logger.error,Logger.exception,Logger.critical,Logger.log] - for n in wrapped_logger: - locals()[n.__name__]=wrapped_log(n) - + #this would be the better way to do it, but we haven't found a way to get code completion + #wrapped_logger=[Logger.info,Logger.setLevel,Logger.debug,Logger.warning,Logger.error,Logger.exception,Logger.critical,Logger.log] + #for n in wrapped_logger: + # locals()[n.__name__]=wrapped_log(n) + #instead, copy and paste. + + info=wrapped_log(Logger.info) + setLevel=wrapped_log(Logger.setLevel) + debug=wrapped_log(Logger.debug) + warning=wrapped_log(Logger.warning) + error=wrapped_log(Logger.error) + exception=wrapped_log(Logger.exception) + critical=wrapped_log(Logger.critical) + log=wrapped_log(Logger.log) + def getExclusiveGrammars(self): """return the dict of (name, grammarobject) of GrammarX objects that are exclusive """ diff --git a/src/unimacro/sample_global_dictation/_control.py b/src/unimacro/sample_global_dictation/_control.py index 0dfd2e0..a105fac 100644 --- a/src/unimacro/sample_global_dictation/_control.py +++ b/src/unimacro/sample_global_dictation/_control.py @@ -173,7 +173,7 @@ def gotBegin(self, moduleInfo): def gotResultsInit(self,words,fullResults): if self.mayBeSwitchedOn == 'exclusive': - print('recog controle, switch off mic: %s'% words) + self.warning('recog controle, switch off mic: %s', words) natbj.SetMic('off') if self.exclusive and self.doMessages: self.DisplayMessage('<%s>'% ' '.join(words)) @@ -227,7 +227,7 @@ def restoreMode(self): self.Mode = self.LastMode def gotResults_trace(self,words,fullResults): - print('control, trace: %s'% words) + self.info('control, trace: %s', words) if self.hasCommon(words, 'actions'): if self.hasCommon(words, 'show'): actions.debugActionsShow() @@ -250,15 +250,16 @@ def gotResults_voicecode(self,words,fullResults): wxmed = os.path.join(voicecodeHome, 'mediator', 'wxmediator.py') if os.path.isfile(wxmed): commandLine = r"%spython.exe %s > D:\foo1.txt >> D:\foo2.txt"% (sys.prefix, wxmed) + self.debug("commandLine : %s",commandLine) os.system(commandLine) else: - print('not a file: %s'% wxmed) + self.info('not a file: %s',wxmed) def gotResults_switch(self,words,fullResults): - print('control, switch: %s'% words) + self.info('control, switch: %s', words) if self.hasCommon(words, 'on'): func = 'switchOn' elif self.hasCommon(words, 'off'): @@ -272,16 +273,16 @@ def gotResults_switch(self,words,fullResults): self.DisplayMessage(t) return if self.hasCommon(words, 'all grammars'): - print('%s all grammars:'% func) + self.info('%s all grammars:', func) natbj.CallAllGrammarObjects(func, ()) - print("-"*10) + self.info("-"*10) else: gramname = self.hasCommon(words, list(natbj.allUnimacroGrammars.keys())) if gramname: gram = natbj.allUnimacroGrammars[gramname] gram.callIfExists(func, ()) else: - print('no grammar name found: %s'% gramname) + self.warning('no grammar name found: %s'% gramname) def gotResults_showexclusive(self,words,fullResults): if natbj.exclusiveGrammars: @@ -335,7 +336,7 @@ def gotResults_show(self,words,fullResults): return if natbj.exclusiveGrammars: - print('exclusive (+ control) are: %s'% ' '.join(list(natbj.exclusiveGrammars.keys()))) + self.info('exclusive (+ control) are: %s', ' '.join(list(natbj.exclusiveGrammars.keys()))) grammars = natbj.allUnimacroGrammars gramNames = list(grammars.keys()) @@ -379,7 +380,7 @@ def gotResults_show(self,words,fullResults): Start=(' '.join(name),[]) else: Start=() - print('start browsing with: %s'% All) + self.info('start browsing with: %s', All) self.Browse(Start,All) @@ -410,7 +411,7 @@ def gotResults_edit(self,words,fullResults): self.DisplayMessage('grammar "%s" has no method "editInifile"'% gramName) return else: - print('no grammar name found') + self.info('no grammar name found') def switchOff(self, **kw): @@ -454,7 +455,7 @@ def __init__(self): natlinkutils.DictGramBase.__init__(self) def initialize(self): - print('initializing/loading DictGrammar!!') + self.info('initializing/loading DictGrammar!!') self.load() natbj.RegisterMessageObject(self) @@ -464,7 +465,7 @@ def unload(self): def gotResults(self, words): ## pass - print('messageDictGrammar: heard dictation: %s '% words) + self.info('messageDictGrammar: heard dictation: %s ', words) # standard stuff Joel (adapted for possible empty gramSpec, QH, unimacro) From ad2208e9e226ad335a98b0732b942c350faf38ef Mon Sep 17 00:00:00 2001 From: Doug Ransom Date: Wed, 7 Aug 2024 11:12:44 -0700 Subject: [PATCH 09/11] Merge error corrected --- src/unimacro/UnimacroGrammars/_folders.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/unimacro/UnimacroGrammars/_folders.py b/src/unimacro/UnimacroGrammars/_folders.py index 7191273..efb2ee3 100644 --- a/src/unimacro/UnimacroGrammars/_folders.py +++ b/src/unimacro/UnimacroGrammars/_folders.py @@ -2456,8 +2456,6 @@ def unload(): # get hndle of a explore window (via _general "give window info") and try interactive # thisGrammar.catchTimerRecentFolders(132524, "CabinetWClass") - thisGrammar.getActiveFol>>> master - thisGrammar.displayRecentFolders() # # Words = ['folder', 'dtactions'] # Fr = {} From 756ea88db2e6ab21a88f38478e3b344d0819d00e Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Thu, 8 Aug 2024 11:20:20 +0200 Subject: [PATCH 10/11] adding rememberdialog.py (in a mixture of things) --- .../UnimacroGrammars/rememberdialog.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/unimacro/UnimacroGrammars/rememberdialog.py diff --git a/src/unimacro/UnimacroGrammars/rememberdialog.py b/src/unimacro/UnimacroGrammars/rememberdialog.py new file mode 100644 index 0000000..2f7c7b6 --- /dev/null +++ b/src/unimacro/UnimacroGrammars/rememberdialog.py @@ -0,0 +1,51 @@ +"""template for _folders grammar, remember function. +This template is filled with the actual values and fired. It asks for a spoken form to "remember a given file", this value +is put in the _folders.ini config file. +""" +#pylint:disable=W0621 +import time +import PySimpleGUI as sg +from dtactions.unimacro import inivars + +prompt = """Remember in Unimacro _folders grammar""" # readable text +text = """Remember folder "C:/Users/Gebruiker/Music" for future calling? + +Please give a spoken form for this folder and choose OK; or Cancel...""" # input text, the key of the +inifile = "C:/Users/Gebruiker/Documents/unimacro_clean_start/enx_inifiles/_folders.ini" +section = "folders" +value = "C:/Users/Gebruiker/Music" +title = "test" +default = "Music" +pausetime = 3 # should be replaced by 0 or a positive int value + + + +def InputBox(text, prompt, title, default): + """the dialog, which returns the wanted spoken form""" + layout = [[sg.Text(prompt)], + [sg.InputText(default)], + [sg.OK(), sg.Cancel()]] + + window = sg.Window(title, layout) + + _event, values = window.read() + window.close() + + return values[0] + +if __name__ == "__main__": + result = InputBox(text, prompt, title, default) + if result: + key = result + ini = inivars.IniVars(inifile) + ini.set(section, key, value) + ini.write() + print(f'Wrote "{key} = {value}" to inifile') + print('Call "edit folders" to edit or delete') + else: + print("Action canceled, no change of ini file") + # if pausetime is given, launch as .py, otherwise launch as .pyw: + if pausetime: + # print "sleep: %s"% pausetime + time.sleep(pausetime) + \ No newline at end of file From b46490eb91cf596d62ea86a6aae4c4f983682625 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Sat, 10 Aug 2024 10:49:32 +0200 Subject: [PATCH 11/11] retry version to 4.1.6 --- src/unimacro/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/unimacro/__init__.py b/src/unimacro/__init__.py index ea62e15..15b1b53 100644 --- a/src/unimacro/__init__.py +++ b/src/unimacro/__init__.py @@ -8,7 +8,7 @@ import os import sys -__version__ = '4.1.2' # requires the dtactions 1.5.5, with new sendkeys (from send_input, vocola) +__version__ = '4.1.6' # requires the dtactions 1.5.5, with new sendkeys (from send_input, vocola) #they could be in a seperate .py file in unimacro to achieve the same (ie not in the control grammar). #these will possilby be removed since we may not need them to enumerate the grammars and ask for log names. @@ -21,5 +21,4 @@ def control_logger_name() -> str : def logname() -> str: """ Returns the name of the unimacro logger.""" return "natlink.unimacro" -__version__ = '4.1.5'