├── manifest-translated.ini.tpl ├── manifest.ini.tpl ├── addon └── appModules │ ├── wdexpress.py │ └── devenv.py ├── style.css ├── site_scons └── site_tools │ └── gettexttool │ └── __init__.py ├── readme.md ├── sconstruct └── COPYING.txt /manifest-translated.ini.tpl: -------------------------------------------------------------------------------- 1 | summary = "{addon_summary}" 2 | description = """{addon_description}""" 3 | -------------------------------------------------------------------------------- /manifest.ini.tpl: -------------------------------------------------------------------------------- 1 | name = {addon_name} 2 | summary = "{addon_summary}" 3 | description = """{addon_description}""" 4 | author = "{addon_author}" 5 | url = {addon_url} 6 | version = {addon_version} 7 | docFileName = {addon_docFileName} 8 | -------------------------------------------------------------------------------- /addon/appModules/wdexpress.py: -------------------------------------------------------------------------------- 1 | # appModule for visual studio 2 | #author: mohammad suliman (mohmad.s93@gmail.com) 3 | #This file is covered by the GNU General Public License. 4 | #See the file COPYING for more details. 5 | #Copyright (C) 2016 Mohammad Suliman 6 | 7 | #this app module is for visual studio express, we use the same exact module of the community edition (devenv.py) 8 | from devenv import * -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | body { 3 | font-family : Verdana, Arial, Helvetica, Sans-serif; 4 | color : #FFFFFF; 5 | background-color : #000000; 6 | line-height: 1.2em; 7 | } 8 | h1, h2 {text-align: center} 9 | dt { 10 | font-weight : bold; 11 | float : left; 12 | width: 10%; 13 | clear: left 14 | } 15 | dd { 16 | margin : 0 0 0.4em 0; 17 | float : left; 18 | width: 90%; 19 | display: block; 20 | } 21 | p { clear : both; 22 | } 23 | a { text-decoration : underline; 24 | } 25 | :active { 26 | text-decoration : none; 27 | } 28 | a:focus, a:hover {outline: solid} 29 | :link {color: #0000FF; 30 | background-color: #FFFFFF} 31 | -------------------------------------------------------------------------------- /site_scons/site_tools/gettexttool/__init__.py: -------------------------------------------------------------------------------- 1 | """ This tool allows generation of gettext .mo compiled files, pot files from source code files 2 | and pot files for merging. 3 | 4 | Three new builders are added into the constructed environment: 5 | 6 | - gettextMoFile: generates .mo file from .pot file using msgfmt. 7 | - gettextPotFile: Generates .pot file from source code files. 8 | - gettextMergePotFile: Creates a .pot file appropriate for merging into existing .po files. 9 | 10 | To properly configure get text, define the following variables: 11 | 12 | - gettext_package_bugs_address 13 | - gettext_package_name 14 | - gettext_package_version 15 | 16 | 17 | """ 18 | from SCons.Action import Action 19 | 20 | def exists(env): 21 | return True 22 | 23 | XGETTEXT_COMMON_ARGS = ( 24 | "--msgid-bugs-address='$gettext_package_bugs_address' " 25 | "--package-name='$gettext_package_name' " 26 | "--package-version='$gettext_package_version' " 27 | "-c -o $TARGET $SOURCES" 28 | ) 29 | 30 | def generate(env): 31 | env.SetDefault(gettext_package_bugs_address="example@example.com") 32 | env.SetDefault(gettext_package_name="") 33 | env.SetDefault(gettext_package_version="") 34 | 35 | env['BUILDERS']['gettextMoFile']=env.Builder( 36 | action=Action("msgfmt -o $TARGET $SOURCE", "Compiling translation $SOURCE"), 37 | suffix=".mo", 38 | src_suffix=".po" 39 | ) 40 | 41 | env['BUILDERS']['gettextPotFile']=env.Builder( 42 | action=Action("xgettext " + XGETTEXT_COMMON_ARGS, "Generating pot file $TARGET"), 43 | suffix=".pot") 44 | 45 | env['BUILDERS']['gettextMergePotFile']=env.Builder( 46 | action=Action("xgettext " + "--omit-header --no-location " + XGETTEXT_COMMON_ARGS, 47 | "Generating pot file $TARGET"), 48 | suffix=".pot") 49 | 50 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #add-on for visual studio 2 | 3 | this add-on aims to resolve some issues with visual studio, and to enhance the user experience while using NVDA. 4 | 5 | ##downloading the add-on 6 | you can download the add-on using 7 | [this link](https://www.dropbox.com/s/eizkvddpnitmoyx/visualStudio-1.0dev.nvda-addon?dl=1) 8 | and then install it as any other add-on 9 | 10 | ##supported versions 11 | the add-on has been tested with visual studio 2013 express and 2015 community and enterprise editions. however, it is expected to work with all editions from 2010 to 2015. 12 | if you have encountered any problem with getting the add-on to work with any edition of VS 2010 to 2015, please let me know. 13 | also, if you have verified that the fixes by the add-on work with a version of VS which hasn't been tested so far, I'll be happy to hear from you, and update this doc accordingly. 14 | 15 | ##issues which have been fixed so far 16 | * Improvements to intelliSense. 17 | * fixes for accessibility of the debug windows. 18 | * status bar is now reported when using standard NVDA key stroke (NVDA + end) 19 | * break points are reported via speech and beeps 20 | * enhancements to the files / tools windows switcher in visual studio 2015 21 | * fixes to the file / tools windows switcher in older versions of visual studio 22 | * quick info tool tips are now supported. the shortcut for invoking this tool tip is ctrl + k and then ctrl + i 23 | * basic support for parameter info, use ctrl + shift + space to invoke this tool tip 24 | * error list navigation improvements: 25 | * errors list now works with NVDA's commands for navigating tables: control + alt + left / right arrow. 26 | * it is possible to directly access each column with control + alt + number, where number is the number of the column you wish to access. for example: to access the first column use control + alt + 1. 27 | * NVDA now reports the current line when navigating the code with the debugger. (stepping in / over / out) with the corresponding keyboard commands. 28 | * fixes for the menus: 29 | * keyboard shortcuts are now reported for each menu item if available 30 | * availability of submenus is reported 31 | * tool box tool window improvements and fixes 32 | * in windows forms designer, moving UI elements / resizing them with the keyboard is now reported 33 | * using ctrl + f6 / ctrl + shift + f6 to switch between opened code editors is now reported 34 | 35 | The add-on is still under development, so expect more fixes and enhancements. 36 | 37 | ##Fixes in more detail 38 | 39 | * fixes to intelliSense: unnecessary announcements when using intelliSense were removed to improve productivity. 40 | additionally, positional info of the suggested intelliSense items is not reported by default. EG "int 1 of 9" is now reported as "int". you can control whether to have this info from visual studio settings dialog under NVDA preferences menu. 41 | NVDA's behavior with intelliSense is now as the following: 42 | the focus of NVDA is usually placed in the editor, and the navigator object is the intelliSense menu item. so, you can review the last reported intelliSense item with review commands, (numpad keys on the desktop), and in the same time to type as usual in the editor and to get feedback. 43 | * fixes to debugging windows: NVDA now reads the content of the watch, locals,, autos and call stack windows. 44 | 45 | ##controlling the behavior of the add-on 46 | the add-on includes a GUI dialog under NVDA preferences menu to control some settings within the add-on 47 | settings you can control: 48 | * reporting breakpoints with speech: you can choose whether breakpoints should be reported with speech. 49 | * beeping on breakpoints: you can choose whether NVDA should make a beep when the caret reaches a line with breakpoint. 50 | * reporting intelliSense position info: you can choose whether position info of the code completion items should be reported by NVDA. 51 | 52 | ##important notes 53 | 54 | * I forgot to add support for express versions of visual studio, so, the add-on should work with those versions of VS now... let me know if this doesn't happen for some reason. 55 | * I need your feedback to know which parts of the UI need further improvements, as well as feedback for the features which were already implemented. 56 | You can reach me at: 57 | mohmad.s93@gmail.com 58 | Please feel free to let me know your thoughts. 59 | -------------------------------------------------------------------------------- /sconstruct: -------------------------------------------------------------------------------- 1 | # NVDA add-on template SCONSTRUCT file 2 | #Copyright (C) 2012, 2014 Rui Batista 3 | #This file is covered by the GNU General Public License. 4 | #See the file COPYING.txt for more details. 5 | 6 | import codecs 7 | import gettext 8 | import os 9 | import os.path 10 | import zipfile 11 | 12 | import buildVars 13 | 14 | 15 | def md2html(source, dest): 16 | import markdown 17 | lang = os.path.basename(os.path.dirname(source)).replace('_', '-') 18 | title="{addonSummary} {addonVersion}".format(addonSummary=buildVars.addon_info["addon_summary"], addonVersion=buildVars.addon_info["addon_version"]) 19 | headerDic = { 20 | "[[!meta title=\"": "# ", 21 | "\"]]": " #", 22 | } 23 | with codecs.open(source, "r", "utf-8") as f: 24 | mdText = f.read() 25 | for k, v in headerDic.iteritems(): 26 | mdText = mdText.replace(k, v, 1) 27 | htmlText = markdown.markdown(mdText) 28 | with codecs.open(dest, "w", "utf-8") as f: 29 | f.write("\n" + 30 | "\n" + 32 | "\n" % (lang, lang) + 33 | "\n" + 34 | "\n" + 35 | "\n" + 36 | "%s\n" % title + 37 | "\n\n" 38 | ) 39 | f.write(htmlText) 40 | f.write("\n\n") 41 | 42 | def mdTool(env): 43 | mdAction=env.Action( 44 | lambda target,source,env: md2html(source[0].path, target[0].path), 45 | lambda target,source,env: 'Generating %s'%target[0], 46 | ) 47 | mdBuilder=env.Builder( 48 | action=mdAction, 49 | suffix='.html', 50 | src_suffix='.md', 51 | ) 52 | env['BUILDERS']['markdown']=mdBuilder 53 | 54 | 55 | env = Environment(ENV=os.environ, tools=['gettexttool', mdTool]) 56 | env.Append(**buildVars.addon_info) 57 | 58 | addonFile = env.File("${addon_name}-${addon_version}.nvda-addon") 59 | 60 | def addonGenerator(target, source, env, for_signature): 61 | action = env.Action(lambda target, source, env : createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None, 62 | lambda target, source, env : "Generating Addon %s" % target[0]) 63 | return action 64 | 65 | def manifestGenerator(target, source, env, for_signature): 66 | action = env.Action(lambda target, source, env : generateManifest(source[0].abspath, target[0].abspath) and None, 67 | lambda target, source, env : "Generating manifest %s" % target[0]) 68 | return action 69 | 70 | 71 | def translatedManifestGenerator(target, source, env, for_signature): 72 | dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), "..")) 73 | lang = os.path.basename(dir) 74 | action = env.Action(lambda target, source, env : generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None, 75 | lambda target, source, env : "Generating translated manifest %s" % target[0]) 76 | return action 77 | 78 | env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator) 79 | env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator) 80 | env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator) 81 | 82 | def createAddonHelp(dir): 83 | docsDir = os.path.join(dir, "doc") 84 | if os.path.isfile("style.css"): 85 | cssPath = os.path.join(docsDir, "style.css") 86 | cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE")) 87 | env.Depends(addon, cssTarget) 88 | if os.path.isfile("readme.md"): 89 | readmePath = os.path.join(docsDir, "en", "readme.md") 90 | readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE")) 91 | env.Depends(addon, readmeTarget) 92 | 93 | 94 | 95 | def createAddonBundleFromPath(path, dest): 96 | """ Creates a bundle from a directory that contains an addon manifest file.""" 97 | basedir = os.path.abspath(path) 98 | with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z: 99 | # FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled. 100 | for dir, dirnames, filenames in os.walk(basedir): 101 | relativePath = os.path.relpath(dir, basedir) 102 | for filename in filenames: 103 | pathInBundle = os.path.join(relativePath, filename) 104 | absPath = os.path.join(dir, filename) 105 | if pathInBundle not in buildVars.excludedFiles: z.write(absPath, pathInBundle) 106 | return dest 107 | 108 | def generateManifest(source, dest): 109 | with codecs.open(source, "r", "utf-8") as f: 110 | manifest_template = f.read() 111 | manifest = manifest_template.format(**buildVars.addon_info) 112 | with codecs.open(dest, "w", "utf-8") as f: 113 | f.write(manifest) 114 | 115 | def generateTranslatedManifest(source, language, out): 116 | _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).ugettext 117 | vars = {} 118 | for var in ("addon_summary", "addon_description"): 119 | vars[var] = _(buildVars.addon_info[var]) 120 | with codecs.open(source, "r", "utf-8") as f: 121 | manifest_template = f.read() 122 | result = manifest_template.format(**vars) 123 | with codecs.open(out, "w", "utf-8") as f: 124 | f.write(result) 125 | 126 | def expandGlobs(files): 127 | return [f for pattern in files for f in env.Glob(pattern)] 128 | 129 | addon = env.NVDAAddon(addonFile, env.Dir('addon')) 130 | 131 | langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))] 132 | 133 | #Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated 134 | for dir in langDirs: 135 | poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po")) 136 | moFile=env.gettextMoFile(poFile) 137 | env.Depends(moFile, poFile) 138 | translatedManifest = env.NVDATranslatedManifest(dir.File("manifest.ini"), [moFile, os.path.join("manifest-translated.ini.tpl")]) 139 | env.Depends(translatedManifest, ["buildVars.py"]) 140 | env.Depends(addon, [translatedManifest, moFile]) 141 | 142 | pythonFiles = expandGlobs(buildVars.pythonSources) 143 | for file in pythonFiles: 144 | env.Depends(addon, file) 145 | 146 | #Convert markdown files to html 147 | createAddonHelp("addon") # We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager 148 | for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')): 149 | htmlFile = env.markdown(mdFile) 150 | env.Depends(htmlFile, mdFile) 151 | env.Depends(addon, htmlFile) 152 | 153 | # Pot target 154 | i18nFiles = expandGlobs(buildVars.i18nSources) 155 | gettextvars={ 156 | 'gettext_package_bugs_address' : 'nvda-translations@freelists.org', 157 | 'gettext_package_name' : buildVars.addon_info['addon_name'], 158 | 'gettext_package_version' : buildVars.addon_info['addon_version'] 159 | } 160 | 161 | pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars) 162 | env.Alias('pot', pot) 163 | env.Depends(pot, i18nFiles) 164 | mergePot = env.gettextMergePotFile("${addon_name}-merge.pot", i18nFiles, **gettextvars) 165 | env.Alias('mergePot', mergePot) 166 | env.Depends(mergePot, i18nFiles) 167 | 168 | # Generate Manifest path 169 | manifest = env.NVDAManifest(os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl")) 170 | # Ensure manifest is rebuilt if buildVars is updated. 171 | env.Depends(manifest, "buildVars.py") 172 | 173 | env.Depends(addon, manifest) 174 | env.Default(addon) 175 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /addon/appModules/devenv.py: -------------------------------------------------------------------------------- 1 | # appModule for visual studio 2 | #author: mohammad suliman (mohmad.s93@gmail.com) 3 | #This file is covered by the GNU General Public License. 4 | #See the file COPYING for more details. 5 | #Copyright (C) 2016 Mohammad Suliman 6 | 7 | import appModuleHandler 8 | import addonHandler 9 | from NVDAObjects.UIA import UIA, WpfTextView 10 | try: 11 | from NVDAObjects.UIA import Toast_win8 as Toast 12 | except ImportError: 13 | from NVDAObjects.UIA import Toast 14 | from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation 15 | from NVDAObjects.IAccessible import IAccessible, ContentGenericClient 16 | from NVDAObjects.window import Desktop 17 | from NVDAObjects import NVDAObjectTextInfo 18 | import textInfos 19 | import controlTypes 20 | import UIAHandler 21 | import api 22 | import ui 23 | import tones 24 | from logHandler import log 25 | import eventHandler 26 | import scriptHandler 27 | from globalCommands import SCRCAT_FOCUS 28 | import re 29 | import speech 30 | import config 31 | import gui 32 | import wx 33 | 34 | #initialize the translation system 35 | addonHandler.initTranslation() 36 | 37 | #a config spec for visual studio settings within NVDA's configuration 38 | confspec = { 39 | "announceBreakpoints": "boolean(default=True)", 40 | "beepOnBreakpoints": "boolean(default=True)", 41 | "reportIntelliSensePosInfo": "boolean(default=False)" 42 | } 43 | 44 | # global vars 45 | #whether last focused object was an intelliSense item 46 | intelliSenseLastFocused = False 47 | #last focused intelliSense object 48 | lastFocusedIntelliSenseItem = None 49 | #whether the caret has moved to a different line in the code editor 50 | caretMovedToDifferentLine = False 51 | 52 | class AppModule(appModuleHandler.AppModule): 53 | 54 | def __init__(self, processID, appName=None): 55 | super(AppModule, self).__init__(processID, appName) 56 | #add visual studio entry to preferences menu of NVDA 57 | self.preferencesMenu = gui.mainFrame.sysTrayIcon.preferencesMenu 58 | self.settingsItem = self.preferencesMenu.Append(wx.ID_ANY, 59 | # Translators: name of visual studio settings option in the menu. 60 | _("&Visual Studio settings..."), 61 | "") 62 | gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onSettings, self.settingsItem) 63 | #add a seqtion to nvda's configuration for VS 64 | config.conf.spec["visualStudio"] = confspec 65 | 66 | def onSettings(self, evt): 67 | gui.mainFrame._popupSettingsDialog(VSSettingsDialog) 68 | 69 | def terminate(self): 70 | super(AppModule, self).terminate() 71 | try: 72 | self.preferencesMenu.RemoveItem(self.settingsItem) 73 | except wx.PyDeadObjectError: 74 | pass 75 | 76 | def chooseNVDAObjectOverlayClasses(self, obj, clsList): 77 | if obj.role == controlTypes.ROLE_TAB and isinstance(obj, UIA) and obj.UIAElement.currentClassName == "TabItem": 78 | clsList.insert(0, editorTabItem) 79 | elif obj.role == controlTypes.ROLE_TABCONTROL and isinstance(obj, UIA) and obj.UIAElement.currentClassName == "DocumentGroup": 80 | clsList.insert(0, editorTabControl) 81 | elif isinstance(obj, UIA) and obj.UIAElement.currentClassName == "IntellisenseMenuItem" and obj.role == controlTypes.ROLE_MENUITEM: 82 | clsList.insert(0, IntelliSenseMenuItem) 83 | elif isinstance(obj, UIA) and obj.UIAElement.currentClassName == "MenuItem" and obj.role == controlTypes.ROLE_MENUITEM: 84 | clsList.insert(0, VSMenuItem) 85 | elif obj.windowClassName == 'TREEGRID' and obj.role == controlTypes.ROLE_WINDOW: 86 | clsList.insert(0, VarsTreeView) 87 | elif obj.name is None and obj.windowClassName == 'TREEGRID' and obj.role == controlTypes.ROLE_PANE: 88 | clsList.insert(0, BadVarView) 89 | elif isinstance(obj, UIA) and obj.UIAElement.currentClassName == "TextMarker" and obj.role == controlTypes.ROLE_UNKNOWN and obj.name.startswith("Breakpoint"): 90 | clsList.insert(0, Breakpoint) 91 | elif isinstance(obj, UIA) and obj.UIAElement.currentClassName == "WpfTextView" and obj.role == controlTypes.ROLE_EDITABLETEXT: 92 | clsList.insert(0, TextEditor) 93 | elif obj.role == controlTypes.ROLE_DATAITEM and isinstance(obj, UIA) and obj.UIAElement.currentClassName == "ListViewItem": 94 | clsList.insert(0, ErrorsListItem) 95 | elif obj.name == "Quick Info Tool Tip" and obj.role == controlTypes.ROLE_TOOLTIP: 96 | clsList.insert(0, QuickInfoToolTip) 97 | elif obj.name == "Signature Help" and obj.role == controlTypes.ROLE_UNKNOWN and isinstance(obj, UIA) and obj.UIAElement.currentClassName == "WpfSignatureHelp": 98 | clsList.insert(0, ParameterInfo) 99 | elif obj.role == controlTypes.ROLE_LISTITEM and obj.windowClassName == "TBToolboxPane": 100 | clsList.insert(0, ToolboxItem) 101 | elif obj.name == "Active Files" and obj.role in (controlTypes.ROLE_DIALOG, controlTypes.ROLE_LIST): 102 | clsList.insert(0, SwitcherDialog) 103 | elif isinstance(obj, IAccessible) and obj.windowClassName.startswith("WindowsForms10.") and obj.windowText != "PropertyGridView": 104 | clsList.insert(0, FormsComponent) 105 | elif isinstance(obj, UIA) and obj.UIAElement.currentClassName == "ViewPresenter" and obj.role == controlTypes.ROLE_PANE: 106 | clsList.insert(0, EditorAncestor) 107 | 108 | def event_NVDAObject_init(self, obj): 109 | if obj.name == "Active Files" and obj.role in (controlTypes.ROLE_DIALOG, controlTypes.ROLE_LIST): 110 | #this object reports the descktop object as its parent, this causes 2 issues 111 | #redundent announcement of the foreground object 112 | #and losing the real foreground object which makes reporting the status bar script not reliable, which is crootial for breakpoint reporting to work. 113 | obj.role = controlTypes.ROLE_LIST 114 | parent = obj.parent 115 | if isinstance(parent, Desktop): 116 | obj.parent = api.getForegroundObject() 117 | #description here also is redundant, so, remove it 118 | obj.description = "" 119 | elif obj.windowClassName == "ToolWindowSelectAccList": 120 | #all objects with this window class name have a description which is identical to the name 121 | #don't think that someone is interested to hear it 122 | obj.description = "" 123 | 124 | def event_appModule_loseFocus(self): 125 | global intelliSenseLastFocused 126 | global lastFocusedIntelliSenseItem 127 | lastFocusedIntelliSenseItem = None 128 | intelliSenseLastFocused = False 129 | 130 | def event_gainFocus(self, obj, nextHandler): 131 | global intelliSenseLastFocused, lastFocusedIntelliSenseItem 132 | if isinstance(obj, UIA) and obj.UIAElement.currentClassName == "WpfTextView" and obj.role == controlTypes.ROLE_EDITABLETEXT: 133 | # in many cases, the editor fire focus events when intelliSense menu is opened, which leads to a lengthy announcements after reporting the current intelliSense item 134 | #so, allow the focus to return to the editor if that happens, but don't report the focus event, and set the navigator object to be last reported intelliSense item to allow the user to review 135 | if self._isCompletionPopupShowing(): 136 | api.setNavigatorObject(lastFocusedIntelliSenseItem) 137 | return 138 | if self._shouldIgnoreFocusEvent(obj): 139 | return 140 | intelliSenseLastFocused = False 141 | lastFocusedIntelliSenseItem = None 142 | nextHandler() 143 | 144 | def _isCompletionPopupShowing(self): 145 | obj = api.getForegroundObject() 146 | try: 147 | if obj.firstChild.firstChild.firstChild.next.next.role == controlTypes.ROLE_POPUPMENU: 148 | return True 149 | except Exception as e: 150 | pass 151 | try: 152 | obj1 = obj .firstChild 153 | obj2 = obj1.firstChild 154 | if obj1.role == controlTypes.ROLE_WINDOW and obj1.name == ''\ 155 | and obj2.role == controlTypes.ROLE_WINDOW and obj2.name == '': 156 | return True 157 | except Exception as e: 158 | pass 159 | return False 160 | 161 | def _shouldIgnoreFocusEvent(self, obj): 162 | if obj.name is None and obj.role == controlTypes.ROLE_UNKNOWN and obj.windowClassName == "TBToolboxPane": 163 | #a pane that gets in the way within tool box tool window. 164 | #don't report the focus event for this element, a correct focus will follow up 165 | return True 166 | 167 | #almost copied from NVDA core with minor modifications 168 | #will be removed when NVDA resolve status bar issues 169 | def script_reportStatusLine(self, gesture): 170 | #it seems that the status bar is the last child of the forground object 171 | #so, get it from there 172 | obj = api.getForegroundObject().lastChild 173 | found=False 174 | if obj and obj.role == controlTypes.ROLE_STATUSBAR: 175 | text = api.getStatusBarText(obj) 176 | api.setNavigatorObject(obj) 177 | found=True 178 | else: 179 | info=api.getForegroundObject().flatReviewPosition 180 | if info: 181 | info.expand(textInfos.UNIT_STORY) 182 | info.collapse(True) 183 | info.expand(textInfos.UNIT_LINE) 184 | text=info.text 185 | info.collapse() 186 | api.setReviewPosition(info) 187 | found=True 188 | if not found: 189 | # Translators: Reported when there is no status line for the current program or window. 190 | ui.message(_("No status line found")) 191 | return 192 | if scriptHandler.getLastScriptRepeatCount()==0: 193 | ui.message(text) 194 | else: 195 | speech.speakSpelling(text) 196 | # Translators: Input help mode message for report status line text command. 197 | script_reportStatusLine.__doc__ = _("Reads the current application status bar and moves the navigator to it. If pressed twice, spells the information") 198 | script_reportStatusLine.category=SCRCAT_FOCUS 199 | 200 | def script_reportParameterInfo(self, gesture): 201 | # get the parameter info object 202 | try: 203 | obj = api.getForegroundObject().firstChild.firstChild 204 | except: 205 | return 206 | if obj.role == controlTypes.ROLE_TOOLTIP: 207 | # emulate an alert event for this object 208 | eventHandler.queueEvent("alert", obj) 209 | 210 | __gestures = { 211 | "kb(desktop):NVDA+End": "reportStatusLine", 212 | "kb(laptop):NVDA+Shift+End": "reportStatusLine", 213 | "kb:control+shift+space": "reportParameterInfo" 214 | } 215 | 216 | 217 | def _shouldIgnoreEditorAncestorFocusEvents(): 218 | #we don't report focusEntered events for some of the text editor ancestors when last focused object was IntelliSense menu item. 219 | #this is useful in following cases: 220 | #when the user chooses a completion from the intelliSense or when he/she closes this menu 221 | #sometimes when navigating the completion list of intelliSense, the editor fires focus events 222 | global intelliSenseLastFocused 223 | return intelliSenseLastFocused == True 224 | 225 | class editorTabItem(UIA): 226 | """one of the editor focus ancestors, we ignore focus entered events in some cases 227 | see _shouldIgnoreEditorAncestorFocusEvents for more info 228 | """ 229 | 230 | def event_focusEntered(self): 231 | if _shouldIgnoreEditorAncestorFocusEvents(): 232 | return 233 | return super(editorTabItem, self).event_focusEntered() 234 | 235 | class editorTabControl(UIA): 236 | """one of the editor focus ancestors, we ignore focus entered events in some cases 237 | see _shouldIgnoreEditorAncestorFocusEvents for more info 238 | """ 239 | 240 | def event_focusEntered(self): 241 | if _shouldIgnoreEditorAncestorFocusEvents(): 242 | return 243 | return super(editorTabControl, self).event_focusEntered() 244 | 245 | 246 | REG_CUT_POS_INFO = re.compile(" \d+ of \d+$") 247 | REG_GET_ITEM_INDEX = re.compile("^ \d+") 248 | REG_GET_GROUP_COUNT = re.compile("\d+$") 249 | class IntelliSenseMenuItem(UIA): 250 | 251 | def _get_states(self): 252 | states = set() 253 | #only fetch the states witch are likely to change 254 | #fetching some states for this view can throw an exception, which causes a latency 255 | e=self.UIACachedStatesElement 256 | try: 257 | hasKeyboardFocus=e.cachedHasKeyboardFocus 258 | except COMError: 259 | hasKeyboardFocus=False 260 | if hasKeyboardFocus: 261 | states.add(controlTypes.STATE_FOCUSED) 262 | # Don't fetch the role unless we must, but never fetch it more than once. 263 | role=None 264 | if e.getCachedPropertyValue(UIAHandler.UIA_IsSelectionItemPatternAvailablePropertyId): 265 | role=self.role 266 | states.add(controlTypes.STATE_CHECKABLE if role==controlTypes.ROLE_RADIOBUTTON else controlTypes.STATE_SELECTABLE) 267 | if e.getCachedPropertyValue(UIAHandler.UIA_SelectionItemIsSelectedPropertyId): 268 | states.add(controlTypes.STATE_CHECKED if role==controlTypes.ROLE_RADIOBUTTON else controlTypes.STATE_SELECTED) 269 | # those states won't change for this UI element, so add them to the states set 270 | states.add(controlTypes.STATE_FOCUSABLE) 271 | states.add(controlTypes.STATE_READONLY) 272 | return states 273 | 274 | def event_gainFocus(self): 275 | global intelliSenseLastFocused 276 | global lastFocusedIntelliSenseItem 277 | intelliSenseLastFocused = True 278 | lastFocusedIntelliSenseItem = self 279 | super(IntelliSenseMenuItem, self).event_gainFocus() 280 | 281 | def _get_name(self): 282 | # by default, the name of the intelliSense menu item includes the position info 283 | #so, remove it 284 | oldName = super(IntelliSenseMenuItem, self).name 285 | newName = re.sub(REG_CUT_POS_INFO, u"", oldName) 286 | return newName 287 | 288 | def _get_positionInfo(self): 289 | """gets the position info of the intelliSense menu item based on the original name 290 | the user can control whether to have this position info from VS settings dialog 291 | """ 292 | if not config.conf["visualStudio"]["reportIntelliSensePosInfo"]: 293 | return {} 294 | oldName = super(IntelliSenseMenuItem, self).name 295 | try: 296 | positionalInfoStr = re.search(REG_CUT_POS_INFO, oldName).group() 297 | except: 298 | return {} 299 | info={} 300 | itemIndex = int(re.search(REG_GET_ITEM_INDEX, positionalInfoStr).group()) 301 | if itemIndex>0: 302 | info['indexInGroup']=itemIndex 303 | groupCount = int(re.search(REG_GET_GROUP_COUNT, positionalInfoStr).group()) 304 | if groupCount>0: 305 | info['similarItemsInGroup'] = groupCount 306 | return info 307 | 308 | 309 | class VarsTreeView(IAccessible): 310 | """the parent view of the variables view in the locals / autos/ watch/ call stack windows""" 311 | 312 | role = controlTypes.ROLE_TREEVIEW 313 | name = '' 314 | 315 | def event_focusEntered(self): 316 | #for some reason, NVDA doesn't execute a focusEntered event for this object, so force it to do so 317 | speech.speakObject(self,reason=controlTypes.REASON_FOCUSENTERED) 318 | 319 | # a regular expression for removing level info from first matching child's value, see _get_positionInfo for more info 320 | REG_CUT_LEVEL_INFO = re.compile(" @ tree depth \d+$") 321 | #a regular expression for getting the level from the first matching child value, see _get_positionInfo for more info 322 | REG_GET_LEVEL = re.compile("\d+$") 323 | class BadVarView(ContentGenericClient): 324 | """the view that showes the variable info (name, value, type) in the locals / autos / watch windows 325 | also, the call stack window uses this view to expose its info 326 | accessibility info for this view is retreaved from the children of the parent view. 327 | the matching children for the view has the focused / selected state. The number of matching children is 3, except for the call stack tool window, there, the number of matching children is 2. 328 | refer to _getMatchingParentChildren method for more info 329 | """ 330 | 331 | role = controlTypes.ROLE_TREEVIEWITEM 332 | TextInfo=NVDAObjectTextInfo 333 | 334 | def _getMatchingParentChildren(self): 335 | parentChildren = self.parent.children 336 | matchingChildren = [] 337 | for index, child in enumerate(parentChildren): 338 | if controlTypes.STATE_SELECTED in child.states or controlTypes.STATE_FOCUSED in child.states and not child.name.startswith("[Column"): 339 | matchingChildren.append(parentChildren[index + 1]) 340 | matchingChildren.append(parentChildren[index + 2]) 341 | if self._isCallStackWindow(): 342 | break 343 | matchingChildren.append(parentChildren[index + 3]) 344 | break 345 | return matchingChildren 346 | 347 | def _isCallStackWindow(self): 348 | try: 349 | return self.parent.parent.parent.parent.name == "Call Stack" 350 | except: 351 | return False 352 | 353 | def isDuplicateIAccessibleEvent(self,obj): 354 | if isinstance(obj, BadVarView): 355 | return self == obj 356 | return super(BadVarView, self).isDuplicateIAccessibleEvent(obj) 357 | 358 | def _get_name(self): 359 | matchingChildren = self._getMatchingParentChildren() 360 | if not matchingChildren: 361 | return None 362 | if len(matchingChildren) < 2: 363 | return None 364 | res = [] 365 | for child in matchingChildren: 366 | name = child.name 367 | value = child.value 368 | #remove the level info 369 | value = re.sub(REG_CUT_LEVEL_INFO, u"", value) 370 | res.append(name + u": ") 371 | res.append(value) 372 | res.append(u", ") 373 | #remove last coma 374 | res.pop(-1) 375 | return u"".join(res) 376 | 377 | def _get_states(self): 378 | superStates = super(BadVarView, self).states 379 | matchingChildren = self._getMatchingParentChildren() 380 | if matchingChildren is None: 381 | return superStates 382 | if len(matchingChildren) == 0: 383 | return superStates 384 | states = matchingChildren[0]._get_states() | superStates 385 | if self.name.startswith("Name: None"): 386 | #if this happens, then the view has no meaningful info 387 | states.add(controlTypes.STATE_UNAVAILABLE) 388 | return states 389 | 390 | def _isEqual(self, other): 391 | if not isinstance(other, BadVarView): 392 | return False 393 | return self is other 394 | 395 | def _get_positionInfo(self): 396 | # only calculate the level 397 | #the level is found in the first matching child's value. which is usually the name of the variable 398 | #suppose the view shows info about a var called i, which is not a part of an array, then value string will be as following: 399 | # i @ tree depth 1 400 | #index in group, similar items in group are not easy to calculate, and it won't be efficient 401 | matchingChildren = self._getMatchingParentChildren() 402 | if not matchingChildren: 403 | return {} 404 | matchingChildStr = matchingChildren.pop(0).value 405 | levelStr = re.search(REG_GET_LEVEL, matchingChildStr) 406 | if levelStr is None: 407 | return {} 408 | levelStr = levelStr.group() 409 | if not levelStr.isdigit(): 410 | return {} 411 | level = int(levelStr) 412 | if level <= 0: 413 | return {} 414 | info = {} 415 | info["level"] = level 416 | return info 417 | 418 | def event_stateChange(self): 419 | #we don't need to report this event for 2 reasons: 420 | #expand / collapse events is faked with the scripts below, they won't work otherwise 421 | #the view is more responsive without reporting this event 422 | return 423 | 424 | def event_gainFocus(self): 425 | if self.hasFocus == False: 426 | #don't report focus event for this view if the hasFocus property is False 427 | #this event is redundant and confusing, and a correct focus event will be fired after this one 428 | return 429 | self.parent.firstChild = self 430 | super(BadVarView, self).event_gainFocus() 431 | 432 | def event_typedCharacter(self, ch): 433 | #default implementation of typedCharacter causes VS and NVDA to crash badly, if the user hits esc while in the quick watch window 434 | #the direct reason for the problem is that NVDA tries to get the states for the object to decide whether typing is protected, and it seams the object will be already destroied in that stage 435 | #only speek typed characters if needed 436 | if config.conf["keyboard"]["speakTypedCharacters"] and ord(ch)>=32: 437 | speech.speakSpelling(ch) 438 | return 439 | 440 | def script_expand(self, gesture): 441 | if controlTypes.STATE_COLLAPSED in self.states: 442 | #translators: a message indecating that a tree view item in watch / locals /... tool window has been expanded 443 | ui.message(_("expanded")) 444 | gesture.send() 445 | 446 | def script_collapse(self, gesture): 447 | if controlTypes.STATE_EXPANDED in self.states: 448 | #translators: a message indecating that a tree view item in watch / locals /... tool window has been collapsed 449 | ui.message(_("collapsed")) 450 | gesture.send() 451 | 452 | __gestures = { 453 | "kb:leftArrow": "collapse", 454 | "kb:rightArrow": "expand" 455 | } 456 | 457 | 458 | class VSMenuItem(UIA): 459 | """ordinary menu items in visual studio""" 460 | 461 | def _get_states(self): 462 | states = super(VSMenuItem, self)._get_states() 463 | # visual studio exposes the menu item which has a sub menu as collapsed/ expanded 464 | #add HASPOPup state to fix NVDA behavior when this state is present 465 | if controlTypes.STATE_COLLAPSED in states or controlTypes.STATE_EXPANDED in states: 466 | states.add(controlTypes.STATE_HASPOPUP) 467 | #this state is redundant in this context, it causes NVDA to say "not checked" for each menu item 468 | states.discard(controlTypes.STATE_CHECKABLE) 469 | return states 470 | 471 | def _get_keyboardShortcut(self): 472 | #this method is redundant for NVDA 16.3 and newer. However, we need it for older versions of NVDA 473 | ret = "" 474 | try: 475 | ret += self.UIAElement.currentAccessKey 476 | except COMError: 477 | pass 478 | if ret != "": 479 | #add a double space to the end of the string 480 | ret +=" " 481 | try: 482 | ret += self.UIAElement.currentAcceleratorKey 483 | except COMError: 484 | pass 485 | return ret 486 | 487 | #regular expression to get line info text from the entire status bar text 488 | REG_GET_LINE_TEXT = re.compile("Ln \d+") 489 | #a regular expression to get line number from line info text in the status bar 490 | REG_GET_LINE_NUM = re.compile("\d+$") 491 | 492 | def _getCurLineNumber(): 493 | """gets current line number which has the caret in the editor based on status bar text""" 494 | obj = api.getForegroundObject().lastChild 495 | text = None 496 | if obj and obj.role == controlTypes.ROLE_STATUSBAR: 497 | text = api.getStatusBarText(obj) 498 | if not text: 499 | return 0 500 | try: 501 | lineInfo = re.search(REG_GET_LINE_TEXT, text).group() 502 | except: 503 | return 0 504 | try: 505 | lineNum = int(re.search(REG_GET_LINE_NUM, lineInfo).group()) 506 | except: 507 | return 0 508 | if lineNum <= 0: 509 | return 0 510 | return lineNum 511 | 512 | REG_GET_BREAKPOINT_STATE = re.compile("Enabled|Disabled") 513 | class Breakpoint(UIA): 514 | """a class for break point control to allow us to detect and report break points once the caret reaches a line with break point""" 515 | 516 | def event_nameChange(self): 517 | #a nameChange event is fired by breakpoint UI control when the caret reaches a line with breakpoint, so, we rely on this to announce breakpoints 518 | global caretMovedToDifferentLine 519 | if not caretMovedToDifferentLine: 520 | #a nameChange event can be fired multiple times when moving by character within the same line, so, return if we already announced the break point for the current line 521 | return 522 | caretMovedToDifferentLine = False 523 | currentLineNum = _getCurLineNumber() 524 | BPLineNum = self._getLineNumber() 525 | if currentLineNum == 0 or BPLineNum == 0 \ 526 | or currentLineNum != BPLineNum: 527 | return 528 | if config.conf["visualStudio"]["beepOnBreakpoints"]: 529 | tones.beep(1000, 50) 530 | if not config.conf["visualStudio"]["announceBreakpoints"]: 531 | return 532 | message = _("breakpoint") 533 | state = re.search(REG_GET_BREAKPOINT_STATE, self.name) 534 | if state: 535 | message += " " 536 | message += state.group() 537 | ui.message(message) 538 | 539 | def _getLineNumber(self): 540 | """gets the line number of the breakpoint based on the automation ID""" 541 | try: 542 | ret=self.UIAElement.currentAutomationID 543 | except Exception as e: 544 | return 0 545 | try: 546 | lineNum = int(re.search(REG_GET_LINE_NUM, ret).group()) 547 | except: 548 | return 0 549 | if lineNum <= 0: 550 | return 0 551 | return lineNum 552 | 553 | class TextEditor(WpfTextView): 554 | """VS text editor view 555 | we need this class to try to tell whether the caret has moved to a different line, this helps us to not make several announcements of the same breakpoint when moving the caret by character left and rite on the same line 556 | also, commands for navigating the code with the debugger now causes NVDA to report the line which was executed. 557 | """ 558 | 559 | description = "" 560 | 561 | def script_caret_moveByLine(self, gesture): 562 | global caretMovedToDifferentLine 563 | caretMovedToDifferentLine = True 564 | super(TextEditor, self).script_caret_moveByLine(gesture) 565 | 566 | #this method is only a work around til the bug with compareing UIA bookmarks is resolved 567 | #we need to bind debugger stepping commands to moveByLine script only 568 | def script_debugger_step(self, gesture): 569 | global caretMovedToDifferentLine 570 | caretMovedToDifferentLine = True 571 | try: 572 | info=self.makeTextInfo(textInfos.POSITION_CARET) 573 | except: 574 | log.debug("exception") 575 | gesture.send() 576 | return 577 | bookmark=info.bookmark 578 | gesture.send() 579 | for i in xrange(4): 580 | caretMoved,newInfo=self._hasCaretMoved(bookmark) 581 | if not caretMoved: 582 | log.debug("caret move failed") 583 | self._caretScriptPostMovedHelper(textInfos.UNIT_LINE,gesture,newInfo) 584 | 585 | __gestures = { 586 | "kb:f10": "debugger_step", 587 | "kb:f11": "debugger_step", 588 | "kb:f5": "debugger_step", 589 | "kb:shift+f11": "debugger_step" 590 | } 591 | 592 | #a regular expression to split the error list menu item name to columns 593 | REG_SPLIT_ERROR = re.compile("(Severity:.*)(Code:.*)(Description:.*\r?\n?.*)(Project:.*)(File:.*)(Line:.*)") 594 | #a regular expression to split the error list menu item name to columns when no code column is available 595 | REG_SPLIT_ERROR_NO_CODE_COL = re.compile("(Severity:.*)(Description:.*\r?\n?.*)(Project:.*)(File:.*)(Line:.*)") 596 | #a regular expression to split the error list menu item name to columns when no file column is available 597 | REG_SPLIT_ERROR_NO_FILE_COL = re.compile("(Severity:.*)(Code:.*)(Description:.*\r?\n?.*)(Project:.*)(Line:.*)") 598 | #a regular expression to split the error list menu item name to columns when no line column is available 599 | REG_SPLIT_ERROR_NO_LINE_COL = re.compile("(Severity:.*)(Code:.*)(Description:.*\r?\n?.*)(Project:.*)(File:.*)") 600 | class ErrorsListItem(RowWithoutCellObjects, RowWithFakeNavigation, UIA): 601 | """ a class for list item of the errors list 602 | the goal is to enable the user to navigate each row with NVDA's commands for navigating tables (ctrl+alt+right/left arrow). in addition, it is possible to move directly to a column with ctrl + alt + number, where the number is the column number we wish to move to 603 | """ 604 | 605 | def _getColumnContent(self, column): 606 | children = UIA._get_children(self) 607 | try: 608 | return children[column - 1].firstChild.name 609 | except Exception as e: 610 | log.debug(e) 611 | return "" 612 | 613 | def _getColumnHeader(self, column): 614 | text = self._getColumnContentAndHeader(column) 615 | # extract the header 616 | text = text.split(":", 1)[0] 617 | #remove spaces if there are any 618 | text = text.strip() 619 | return text 620 | 621 | def _getColumnContentAndHeader(self, column): 622 | if column < 1 or column > self.childCount: 623 | return "" 624 | try: 625 | return re.search(REG_SPLIT_ERROR, self.name).group(column) 626 | except: 627 | pass 628 | try: 629 | return re.search(REG_SPLIT_ERROR_NO_CODE_COL, self.name).group(column) 630 | except: 631 | pass 632 | try: 633 | return re.search(REG_SPLIT_ERROR_NO_FILE_COL, self.name).group(column) 634 | except: 635 | pass 636 | try: 637 | return re.search(REG_SPLIT_ERROR_NO_LINE_COL, self.name).group(column) 638 | except: 639 | pass 640 | return "" 641 | 642 | def _getColumnLocation(self,column): 643 | if column < 1 or column > self.childCount: 644 | return None 645 | child = None 646 | try: 647 | child = UIA._get_children(self)[column - 1].firstChild 648 | except Exception as e: 649 | log.debug(e) 650 | if not child: 651 | return None 652 | return child.location 653 | 654 | def _get_childCount(self): 655 | return len(UIA._get_children(self)) 656 | 657 | def initOverlayClass(self): 658 | for i in xrange(1, self.childCount + 1): 659 | self.bindGesture("kb:control+alt+%d" %i, "moveToColumn") 660 | 661 | def script_moveToColumn(self, gesture): 662 | keyName = gesture.displayName 663 | # extract the number from the key name 664 | columnNum = re.search("\d+$", keyName).group() 665 | columnNum = int(columnNum) 666 | self._moveToColumnNumber(columnNum) 667 | 668 | 669 | class QuickInfoToolTip(Toast): 670 | """quick info toast, the goal is to get this view to be considered as toast by NVDA, so it will be reported when it fires an alert event""" 671 | 672 | def _get_name(self): 673 | return u"Quick Info" 674 | 675 | def _get_description(self): 676 | # this view has a long description, don't think the user wants to hear it every tiem he invokes the quick info 677 | return "" 678 | 679 | #think the parameter info is useless, the info which is exposed to screen readers seems very poor compared to the description of this view in VS documentation 680 | #so, I am seriously considering removing it 681 | class ParameterInfo (Toast): 682 | role = controlTypes.ROLE_TOOLTIP 683 | 684 | def _get_description(self): 685 | return "" 686 | 687 | class ToolboxItem(IAccessible): 688 | """the tool box item view in the tool box tool windo""" 689 | 690 | role = controlTypes.ROLE_TREEVIEWITEM 691 | 692 | def event_gainFocus(self): 693 | badStates = set((controlTypes.STATE_INVISIBLE, controlTypes.STATE_UNAVAILABLE, controlTypes.STATE_OFFSCREEN)) 694 | if badStates.issubset(self.states) or controlTypes.STATE_SELECTED not in self.states: 695 | #if the object has those states, or the object don't has a selected state, don't report this invalid focus event. 696 | #a valid focus event will be fired after then. 697 | return 698 | super(ToolboxItem, self).event_gainFocus() 699 | 700 | def event_stateChange(self): 701 | #no need to report state change for this object for the following reasons: 702 | #on expand / collaps: a focus event is fired 703 | #a state change event is fired when moving between tool box items, and causes NVDA to announce "not available" each time 704 | return 705 | 706 | def _get_value(self): 707 | #the value is exposed as level info, don't report it 708 | return 709 | 710 | def _get_positionInfo(self): 711 | info = {} 712 | level = super(ToolboxItem, self).value 713 | #the level is zero based, unlike NVDA's convention of 1 based level, so, fix it. 714 | level = int(level) 715 | level += 1 716 | info["level"] = level 717 | return info 718 | 719 | class SwitcherDialog(IAccessible): 720 | """the view of the file / tool windows switcher which is used to move between opened files and active tool windows 721 | in latest version of VS (2015 currently), only gainFocus event method is needed to report the first selected entry when a file is opened 722 | in older versions, this overlay class manages all the user interaction with this view. AKA moving between entries using the corresponding keyboard commands 723 | """ 724 | 725 | def initOverlayClass(self): 726 | #all entries of the dialog (active files and active tool windows entries) 727 | self.entries = [] 728 | #whether a focus entered event should be fired to the active files list 729 | self.shouldFireFocusEnteredEventFiles = True 730 | #whether a focus entered event should be fired to the active tool windows list 731 | self.shouldFireFocusEnteredEventTools = True 732 | 733 | def event_gainFocus(self): 734 | #add active files entries 735 | try: 736 | self.entries.extend(self.children[1].children) 737 | except: 738 | #no active files 739 | pass 740 | #add active tool windows entries 741 | try: 742 | self.entries.extend(self.children[0].children) 743 | except: 744 | #no active tool windows, this should not happen never 745 | pass 746 | self._reportSelectedEntry() 747 | 748 | def _getSelectedEntry(self): 749 | for entry in self.entries: 750 | if controlTypes.STATE_SELECTED in entry.states: 751 | return entry 752 | return None 753 | 754 | def _reportSelectedEntry(self): 755 | obj = self._getSelectedEntry() 756 | if obj is None: 757 | return 758 | self._reportFocusEnteredEventForParent(obj) 759 | api.setNavigatorObject(obj) 760 | obj.reportFocus() 761 | 762 | def _reportFocusEnteredEventForParent(self, obj): 763 | """checks if we need to fire a focusEntered event for the selected entry's parent, and fires an event if we need to""" 764 | if obj.parent.name == "Active Files" and self.shouldFireFocusEnteredEventFiles: 765 | eventHandler.executeEvent("focusEntered", obj.parent) 766 | self.shouldFireFocusEnteredEventFiles = False 767 | self.shouldFireFocusEnteredEventTools = True 768 | if obj.parent.name == "Active Tool Windows" and self.shouldFireFocusEnteredEventTools: 769 | eventHandler.executeEvent("focusEntered", obj.parent) 770 | self.shouldFireFocusEnteredEventFiles = True 771 | self.shouldFireFocusEnteredEventTools = False 772 | 773 | def script_onEntryChange(self, gesture): 774 | gesture.send() 775 | studioVersion = self.appModule.productVersion[:2] 776 | studioVersion = int(studioVersion) 777 | if studioVersion >= 14: 778 | #if VS 2015 or higher is the version used, then don't do any thing, a correct focus event will be fired, and the controle will move to the focused view. 779 | return 780 | self._reportSelectedEntry() 781 | 782 | __gestures = { 783 | "kb:control+downArrow": "onEntryChange", 784 | "kb:control+upArrow": "onEntryChange", 785 | "kb:control+leftArrow": "onEntryChange", 786 | "kb:control+rightArrow": "onEntryChange", 787 | "kb:control+tab": "onEntryChange", 788 | "kb:control+shift+tab": "onEntryChange" 789 | } 790 | 791 | 792 | REG_SPLIT_LOCATION_TEXT = re.compile("(\d+), (\d+) (\d+), (\d+)") 793 | class FormsComponent(IAccessible): 794 | """the UI component in windows forms designer """ 795 | 796 | def script_onSizeChange(self, gesture): 797 | gesture.send() 798 | #get the position from the status bar 799 | obj = api.getForegroundObject().lastChild 800 | text = obj.children[2].name 801 | width = re.match(REG_SPLIT_LOCATION_TEXT, text).group(3) 802 | hight = re.match(REG_SPLIT_LOCATION_TEXT, text).group(4) 803 | #translators: the width and the hight of a UI element in windows forms designer 804 | msg = _("width: %s hight: %s" %(width, hight)) 805 | ui.message(msg) 806 | 807 | def script_onLocationChange(self, gesture): 808 | gesture.send() 809 | #get the location from the status bar 810 | obj = api.getForegroundObject().lastChild 811 | text = obj.children[2].name 812 | x = re.match(REG_SPLIT_LOCATION_TEXT, text).group(1) 813 | y = re.match(REG_SPLIT_LOCATION_TEXT, text).group(2) 814 | #translators: the x coord and the y coord of a UI element in windows forms designer 815 | msg = _("X: %s y: %s" %(x, y)) 816 | ui.message(msg) 817 | 818 | __gestures = { 819 | "kb:shift+upArrow": "onSizeChange", 820 | "kb:shift+downArrow": "onSizeChange", 821 | "kb:shift+rightArrow": "onSizeChange", 822 | "kb:shift+leftArrow": "onSizeChange", 823 | "kb:control+upArrow": "onLocationChange", 824 | "kb:control+downArrow": "onLocationChange", 825 | "kb:control+rightArrow": "onLocationChange", 826 | "kb:control+leftArrow": "onLocationChange", 827 | "kb:upArrow": "onLocationChange", 828 | "kb:downArrow": "onLocationChange", 829 | "kb:leftArrow": "onLocationChange", 830 | "kb:rightArrow": "onLocationChange" 831 | } 832 | 833 | class EditorAncestor(UIA): 834 | """an ancestor of the code editor, we need this because this control returns true incorrectly when comparing it with other instance of the same type 835 | this causes NVDA to not execute focus entered events when it should do 836 | the issue is present when using ctrl + f6 / ctrl + shift + f6 to move between openned code editors 837 | """ 838 | 839 | def _isEqual(self, other): 840 | return False 841 | 842 | 843 | class VSSettingsDialog(gui.SettingsDialog): 844 | """a gui dialog for visual studio settings dialog""" 845 | 846 | # Translators: title of a dialog. 847 | title = _("Visual Studio settings") 848 | 849 | def makeSettings(self, settingsSizer): 850 | # Translators: label of a checkbox which toggles the announcement of breakpoints via speech 851 | self.announceBreakpointCheckBox=wx.CheckBox(self, wx.NewId(), label=_("&Announce breakpoints via speech")) 852 | self.announceBreakpointCheckBox.SetValue(config.conf["visualStudio"]["announceBreakpoints"]) 853 | settingsSizer.Add(self.announceBreakpointCheckBox,border=10, flag=wx.BOTTOM) 854 | # Translators: label of a checkbox which toggles the beep on breakpoints option 855 | self.beepOnBreakpointCheckBox=wx.CheckBox(self, wx.NewId(), label=_("&Beep on breakpoints")) 856 | self.beepOnBreakpointCheckBox.SetValue(config.conf["visualStudio"]["beepOnBreakpoints"]) 857 | settingsSizer.Add(self.beepOnBreakpointCheckBox,border=10, flag=wx.BOTTOM) 858 | # Translators: label of a checkbox which toggles reporting of intelliSense menu item position info 859 | self.reportIntelliSensePosInfoCheckBox=wx.CheckBox(self, wx.NewId(), label=_("&Report intelliSense menu item position information")) 860 | self.reportIntelliSensePosInfoCheckBox.SetValue(config.conf["visualStudio"]["reportIntelliSensePosInfo"]) 861 | settingsSizer.Add(self.reportIntelliSensePosInfoCheckBox,border=10, flag=wx.BOTTOM) 862 | 863 | def postInit(self): 864 | self.announceBreakpointCheckBox.SetFocus() 865 | 866 | def onOk(self, evt): 867 | super(VSSettingsDialog, self).onOk(evt) 868 | config.conf["visualStudio"]["announceBreakpoints"] = self.announceBreakpointCheckBox.IsChecked() 869 | config.conf["visualStudio"]["beepOnBreakpoints"] = self.beepOnBreakpointCheckBox.IsChecked() 870 | config.conf["visualStudio"]["reportIntelliSensePosInfo"] = self.reportIntelliSensePosInfoCheckBox.IsChecked() 871 | --------------------------------------------------------------------------------