sysListView32.py 13.5 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
# -*- coding: UTF-8 -*-
#NVDAObjects/IAccessible/sysListView32.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2012 NV Access Limited, Peter Vágner
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import time
from ctypes import *
from ctypes.wintypes import *
from comtypes import BSTR
import oleacc
import NVDAHelper
import watchdog
import controlTypes
import speech
import api
import eventHandler
import winKernel
import winUser
from . import IAccessible, List
from ..window import Window
import watchdog
from NVDAObjects.behaviors import RowWithoutCellObjects, RowWithFakeNavigation
import config

#Window messages
LVM_FIRST=0x1000
LVM_GETITEMW=LVM_FIRST+75
LVM_GETITEMSTATE=LVM_FIRST+44
LVM_GETFOCUSEDGROUP=LVM_FIRST+93
LVM_GETITEMCOUNT=LVM_FIRST+4
LVM_GETITEM=LVM_FIRST+75
LVN_GETDISPINFO=0xFFFFFF4F
LVM_GETITEMTEXTW=LVM_FIRST+115
LVM_GETHEADER=LVM_FIRST+31
LVM_GETCOLUMNORDERARRAY=LVM_FIRST+59
LVM_GETCOLUMNW=LVM_FIRST+95
LVM_GETSELECTEDCOUNT =(LVM_FIRST+50)
LVNI_SELECTED =2
LVM_GETNEXTITEM =(LVM_FIRST+12)
LVM_GETVIEW=LVM_FIRST+143
LV_VIEW_DETAILS=0x0001
LVM_GETSUBITEMRECT=LVM_FIRST+56
LV_VIEW_TILE=0x0004

#item mask flags
LVIF_TEXT=0x01 
LVIF_IMAGE=0x02
LVIF_PARAM=0x04
LVIF_STATE=0x08
LVIF_INDENT=0x10
LVIF_GROUPID=0x100
LVIF_COLUMNS=0x200

#Item states
LVIS_FOCUSED=0x01
LVIS_SELECTED=0x02
LVIS_STATEIMAGEMASK=0xF000

LVS_REPORT=0x0001
LVS_TYPEMASK=0x0003
LVS_OWNERDRAWFIXED=0x0400

#column mask flags
LVCF_FMT=1
LVCF_WIDTH=2
LVCF_TEXT=4
LVCF_SUBITEM=8
LVCF_IMAGE=16
LVCF_ORDER=32

CBEMAXSTRLEN=260

# listview header window messages
HDM_FIRST=0x1200
HDM_GETITEMCOUNT=HDM_FIRST

class LVITEM(Structure):
	_fields_=[
		('mask',c_uint),
		('iItem',c_int),
		('iSubItem',c_int),
		('state',c_uint),
		('stateMask',c_uint),
		('pszText',c_void_p),
		('cchTextMax',c_int),
		('iImage',c_int),
		('lParam',LPARAM),
		('iIndent',c_int),
		('iGroupID',c_int),
		('cColumns',c_uint),
		('puColumns',c_uint),
		('piColFmt',POINTER(c_int)),
		('iGroup',c_int),
	]

class LVITEM64(Structure):
	_fields_=[
		('mask',c_uint),
		('iItem',c_int),
		('iSubItem',c_int),
		('state',c_uint),
		('stateMask',c_uint),
		('pszText',c_ulonglong),
		('cchTextMax',c_int),
		('iImage',c_int),
		('lParam',c_ulonglong),
		('iIndent',c_int),
		('iGroupID',c_int),
		('cColumns',c_uint),
		('puColumns',c_uint),
		('piColFmt',c_ulonglong),
		('iGroup',c_int),
	]

class LVCOLUMN(Structure):
	_fields_=[
		('mask',c_uint),
		('fmt',c_int),
		('cx',c_int),
		('pszText',c_void_p),
		('cchTextMax',c_int),
		('iSubItem',c_int),
		('iImage',c_int),
		('iOrder',c_int),
		('cxMin',c_int),
		('cxDefault',c_int),
		('cxIdeal',c_int),
	]

class LVCOLUMN64(Structure):
	_fields_=[
		('mask',c_uint),
		('fmt',c_int),
		('cx',c_int),
		('pszText',c_ulonglong),
		('cchTextMax',c_int),
		('iSubItem',c_int),
		('iImage',c_int),
		('iOrder',c_int),
		('cxMin',c_int),
		('cxDefault',c_int),
		('cxIdeal',c_int),
	]

class AutoFreeBSTR(BSTR):
	"""A BSTR that *always* frees itself on deletion.""" 
	_needsfree=True

class List(List):

	def getListGroupInfo(self,groupIndex):
		header=AutoFreeBSTR()
		footer=AutoFreeBSTR()
		state=c_int()
		if watchdog.cancellableExecute(NVDAHelper.localLib.nvdaInProcUtils_sysListView32_getGroupInfo,self.appModule.helperLocalBindingHandle,self.windowHandle,groupIndex,byref(header),byref(footer),byref(state))!=0:
			return None
		return dict(header=header.value,footer=footer.value,state=state.value,groupIndex=groupIndex)

	def _get_name(self):
		name=super(List,self)._get_name()
		if not name:
			name=super(IAccessible,self)._get_name()
		return name

	def event_gainFocus(self):
		#See if this object is the focus and the focus is on a group item.
		#if so, then morph this object to a groupingItem object
		if self is api.getFocusObject():
			groupIndex=watchdog.cancellableSendMessage(self.windowHandle,LVM_GETFOCUSEDGROUP,0,0)
			if groupIndex>=0:
				info=self.getListGroupInfo(groupIndex)
				if info is not None:
					ancestors=api.getFocusAncestors()
					if api.getFocusDifferenceLevel()==len(ancestors)-1:
						self.event_focusEntered()
					groupingObj=GroupingItem(windowHandle=self.windowHandle,parentNVDAObject=self,groupInfo=info)
					return eventHandler.queueEvent("gainFocus",groupingObj)
		return super(List,self).event_gainFocus()

	def _get_isMultiColumn(self):
		view =  watchdog.cancellableSendMessage(self.windowHandle, LVM_GETVIEW, 0, 0)
		if view in (LV_VIEW_DETAILS, LV_VIEW_TILE):
			return True
		elif view == 0:
			# #2673: This could indicate that LVM_GETVIEW is not supported (comctl32 < 6.0).
			# Unfortunately, it could also indicate LV_VIEW_ICON.
			# Hopefully, no one sets LVS_REPORT and then LV_VIEW_ICON.
			return self.windowStyle & LVS_TYPEMASK == LVS_REPORT
		return False

	def _get_rowCount(self):
		return watchdog.cancellableSendMessage(self.windowHandle, LVM_GETITEMCOUNT, 0, 0)

	def _get_columnCount(self):
		if not self.isMultiColumn:
			return 0
		headerHwnd= watchdog.cancellableSendMessage(self.windowHandle,LVM_GETHEADER,0,0)
		count = watchdog.cancellableSendMessage(headerHwnd, HDM_GETITEMCOUNT, 0, 0)
		if not count:
			return 1
		return count

	def _get__columnOrderArray(self):
		coa=(c_int *self.columnCount)()
		processHandle=self.processHandle
		internalCoa=winKernel.virtualAllocEx(processHandle,None,sizeof(coa),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			winKernel.writeProcessMemory(processHandle,internalCoa,byref(coa),sizeof(coa),None)
			res = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETCOLUMNORDERARRAY, self.columnCount, internalCoa)
			if res:
				winKernel.readProcessMemory(processHandle,internalCoa,byref(coa),sizeof(coa),None)
		finally:
			winKernel.virtualFreeEx(processHandle,internalCoa,0,winKernel.MEM_RELEASE)
		return coa

class GroupingItem(Window):

	def __init__(self,windowHandle=None,parentNVDAObject=None,groupInfo=None):
		super(GroupingItem,self).__init__(windowHandle=windowHandle)
		self.parent=parentNVDAObject
		self.groupInfo=groupInfo

	def _isEqual(self,other):
		return isinstance(other,self.__class__) and self.groupInfo==other.groupInfo

	def _set_groupInfo(self,info):
		self._groupInfoTime=time.time()
		self._groupInfo=info
		for gesture in ("kb:leftArrow", "kb:rightArrow"):
			self.bindGesture(gesture, "collapseOrExpand")

	def _get_groupInfo(self):
		now=time.time()
		if (now-self._groupInfoTime)>0.25:
			self._groupInfoTime=now
			self._groupInfo=self.parent.getListGroupInfo(self._groupInfo['groupIndex'])
		return self._groupInfo

	def _get_name(self):
		return self.groupInfo['header']

	role = controlTypes.ROLE_GROUPING

	def _get_value(self):
		return self.groupInfo['footer']

	def _get_states(self):
		states=set()
		if self.groupInfo['state']&1:
			states.add(controlTypes.STATE_COLLAPSED)
		else:
			states.add(controlTypes.STATE_EXPANDED)
		return states

	def script_collapseOrExpand(self,gesture):
		gesture.send()
		eventHandler.queueEvent("stateChange",self)

class ListItemWithoutColumnSupport(IAccessible):

	def initOverlayClass(self):
		if self.appModule.is64BitProcess:
			self.LVITEM = LVITEM64
			self.LVCOLUMN = LVCOLUMN64
		else:
			self.LVITEM = LVITEM
			self.LVCOLUMN = LVCOLUMN

	description = None

	def _get_value(self):
		value=super(ListItemWithoutColumnSupport,self)._get_description()
		if (not value or value.isspace()) and self.windowStyle & LVS_OWNERDRAWFIXED:
			value=self.displayText
		if not value:
			return None
		#Some list view items in Vista can contain annoying left-to-right and right-to-left indicator characters which really should not be there.
		value=value.replace(u'\u200E','')
		value=value.replace(u'\u200F','')
		return value

	def _get_positionInfo(self):
		index=self.IAccessibleChildID
		totalCount=watchdog.cancellableSendMessage(self.windowHandle,LVM_GETITEMCOUNT,0,0)
		return dict(indexInGroup=index,similarItemsInGroup=totalCount) 

	def event_stateChange(self):
		if self.hasFocus:
			super(ListItemWithoutColumnSupport,self).event_stateChange()

class ListItem(RowWithFakeNavigation, RowWithoutCellObjects, ListItemWithoutColumnSupport):

	def _getColumnLocationRaw(self,index):
		processHandle=self.processHandle
		localRect=RECT(left=2,top=index)
		internalRect=winKernel.virtualAllocEx(processHandle,None,sizeof(localRect),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			winKernel.writeProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None)
			watchdog.cancellableSendMessage(self.windowHandle,LVM_GETSUBITEMRECT, (self.IAccessibleChildID-1), internalRect)
			winKernel.readProcessMemory(processHandle,internalRect,byref(localRect),sizeof(localRect),None)
		finally:
			winKernel.virtualFreeEx(processHandle,internalRect,0,winKernel.MEM_RELEASE)
		windll.user32.ClientToScreen(self.windowHandle,byref(localRect))
		windll.user32.ClientToScreen(self.windowHandle,byref(localRect,8))
		return (localRect.left,localRect.top,localRect.right-localRect.left,localRect.bottom-localRect.top)

	def _getColumnLocation(self,column):
		return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1])

	def _getColumnContentRaw(self, index):
		buffer=None
		processHandle=self.processHandle
		internalItem=winKernel.virtualAllocEx(processHandle,None,sizeof(self.LVITEM),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			internalText=winKernel.virtualAllocEx(processHandle,None,CBEMAXSTRLEN*2,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
			try:
				item=self.LVITEM(iItem=self.IAccessibleChildID-1,mask=LVIF_TEXT|LVIF_COLUMNS,iSubItem=index,pszText=internalText,cchTextMax=CBEMAXSTRLEN)
				winKernel.writeProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
				len = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETITEMTEXTW, (self.IAccessibleChildID-1), internalItem)
				if len:
					winKernel.readProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
					buffer=create_unicode_buffer(len)
					winKernel.readProcessMemory(processHandle,item.pszText,buffer,sizeof(buffer),None)
			finally:
				winKernel.virtualFreeEx(processHandle,internalText,0,winKernel.MEM_RELEASE)
		finally:
			winKernel.virtualFreeEx(processHandle,internalItem,0,winKernel.MEM_RELEASE)
		return buffer.value if buffer else None

	def _getColumnContent(self, column):
		return self._getColumnContentRaw(self.parent._columnOrderArray[column - 1])

	def _getColumnImageIDRaw(self, index):
		processHandle=self.processHandle
		internalItem=winKernel.virtualAllocEx(processHandle,None,sizeof(self.LVITEM),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			item=self.LVITEM(iItem=self.IAccessibleChildID-1,mask=LVIF_IMAGE|LVIF_COLUMNS,iSubItem=index)
			winKernel.writeProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
			item.mask=LVIF_IMAGE|LVIF_COLUMNS
			winKernel.writeProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
			watchdog.cancellableSendMessage(self.windowHandle,LVM_GETITEMW, 0, internalItem)
			winKernel.readProcessMemory(processHandle,internalItem,byref(item),sizeof(item),None)
		finally:
			winKernel.virtualFreeEx(processHandle,internalItem,0,winKernel.MEM_RELEASE)
		return item.iImage

	def _getColumnImageID(self, column):
		return self._getColumnImageIDRaw(self.parent._columnOrderArray[column - 1])

	def _getColumnHeaderRaw(self,index):
		buffer=None
		processHandle=self.processHandle
		internalColumn=winKernel.virtualAllocEx(processHandle,None,sizeof(self.LVCOLUMN),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
		try:
			internalText=winKernel.virtualAllocEx(processHandle,None,CBEMAXSTRLEN*2,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
			try:
				column=self.LVCOLUMN(mask=LVCF_TEXT,iSubItem=index,pszText=internalText,cchTextMax=CBEMAXSTRLEN)
				winKernel.writeProcessMemory(processHandle,internalColumn,byref(column),sizeof(self.LVCOLUMN),None)
				res = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETCOLUMNW, index, internalColumn)
				if res:
					winKernel.readProcessMemory(processHandle,internalColumn,byref(column),sizeof(self.LVCOLUMN),None)
					buffer=create_unicode_buffer(column.cchTextMax)
					winKernel.readProcessMemory(processHandle,column.pszText,buffer,sizeof(buffer),None)
			finally:
				winKernel.virtualFreeEx(processHandle,internalText,0,winKernel.MEM_RELEASE)
		finally:
			winKernel.virtualFreeEx(processHandle,internalColumn,0,winKernel.MEM_RELEASE)
		return buffer.value if buffer else None

	def _getColumnHeader(self, column):
		return self._getColumnHeaderRaw(self.parent._columnOrderArray[column - 1])

	def _get_name(self):
		parent = self.parent
		if not isinstance(parent, List) or not parent.isMultiColumn or self._shouldDisableMultiColumn:
			name = super(ListItem, self).name
			if name:
				return name
			elif self.windowStyle & LVS_OWNERDRAWFIXED:
				return self.displayText
			return name
		textList = []
		for col in xrange(1, self.childCount + 1):
			content = self._getColumnContent(col)
			if not content:
				continue
			if config.conf["documentFormatting"]["reportTableHeaders"] and col != 1:
				header = self._getColumnHeader(col)
			else:
				header = None
			if header:
				textList.append("%s: %s" % (header, content))
			else:
				textList.append(content)
		return "; ".join(textList)

	value = None

	def _get__shouldDisableMultiColumn(self):
		if self.windowStyle & LVS_OWNERDRAWFIXED:
			# This is owner drawn, but there may still be column content.
			# accDescription will be empty if there is no column content,
			# in which case multi-column support must be disabled.
			ret = not super(ListItemWithoutColumnSupport, self).description
			if ret:
				self.childCount = 0
		else:
			ret = False
		self._shouldDisableMultiColumn = ret
		return ret