← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 2388: 1. Make types and attributes in all editors clickable

 

------------------------------------------------------------
revno: 2388
committer: Václav Šmilauer <eudoxos@xxxxxxxx>
branch nick: trunk
timestamp: Wed 2010-07-28 00:04:20 +0200
message:
  1. Make types and attributes in all editors clickable
  2. Add editor for Se3, Vector3i, Vector2, Vector2i
  3. Fix bugs in eigen's wrapper for Vector2i and Vector3i
modified:
  core/main/main.py.in
  doc/sphinx/conf.py
  gui/qt4/Inspector.py
  gui/qt4/SerializableEditor.py
  gui/qt4/__init__.py
  py/mathWrap/miniEigen.cpp


--
lp:yade
https://code.launchpad.net/~yade-dev/yade/trunk

Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-dev/yade/trunk/+edit-subscription
=== modified file 'core/main/main.py.in'
--- core/main/main.py.in	2010-07-27 12:37:10 +0000
+++ core/main/main.py.in	2010-07-27 22:04:20 +0000
@@ -79,8 +79,7 @@
 	import sys
 	if len(sys.argv)>0:
 		arg0=sys.argv[0]
-		if qt3: yade.qt.Controller();
-		if qt4: _c=yade.qt.Controller(); _c.show()
+		if qt3 or qt4: yade.qt.Controller();
 		if sum(bool(arg0.endswith(ext)) for ext in ('.xml','.xml.bz2','.xml.gz','.yade','.yade.gz','.yade.bz2','.bin','.bin.gz','.bin.bz2'))>0:
 			if len(sys.argv)>1: raise RuntimeError('Extra arguments to saved simulation to run: '+' '.join(sys.argv[1:]))
 			sys.stderr.write("Running simulation "+arg0+'\n')
@@ -125,8 +124,7 @@
 					# only with the gui
 					# the escape codes might not work on non-linux terminals.
 					]
-					+(['"\e[24~": "\C-Uyade.qt.Controller();\C-M"','"\e[23~": "\C-Uyade.qt.View();\C-M"','"\e[21~": "\C-Uyade.qt.Controller(), yade.qt.View();\C-M"','"\e[20~": "\C-Uyade.qt.Generator();\C-M"'] if qt3 else []) #F12,F11,F10,F9
-					+(['"\e[24~": "\C-U_c=yade.qt.Controller();_c.show()\C-M"','"\e[23~": "\C-Uyade.qt.View();\C-M"','"\e[21~": "\C-U_c=yade.qt.Controller(); _c.show(); yade.qt.View();\C-M"','"\e[20~": "\C-Uyade.qt.Generator();\C-M"'] if qt4 else []) #F12,F11,F10,F9
+					+(['"\e[24~": "\C-Uyade.qt.Controller();\C-M"','"\e[23~": "\C-Uyade.qt.View();\C-M"','"\e[21~": "\C-Uyade.qt.Controller(), yade.qt.View();\C-M"','"\e[20~": "\C-Uyade.qt.Generator();\C-M"'] if (qt3 or qt4) else []) #F12,F11,F10,F9
 					+['"\e[19~": "\C-Uimport yade.plot; yade.plot.plot();\C-M"', #F8
 					'"\e[A": history-search-backward', '"\e[B": history-search-forward', # incremental history forward/backward
 					]

=== modified file 'doc/sphinx/conf.py'
--- doc/sphinx/conf.py	2010-07-16 08:14:46 +0000
+++ doc/sphinx/conf.py	2010-07-27 22:04:20 +0000
@@ -543,7 +543,7 @@
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-html_theme_options = {'stickysidebar':'true'}
+html_theme_options = {'stickysidebar':'true','collapsiblesidebar':'true','rightsidebar':'false'}
 
 # Add any paths that contain custom themes here, relative to this directory.
 #html_theme_path = []

=== modified file 'gui/qt4/Inspector.py'
--- gui/qt4/Inspector.py	2010-07-27 12:47:04 +0000
+++ gui/qt4/Inspector.py	2010-07-27 22:04:20 +0000
@@ -111,7 +111,9 @@
 				return
 			else: v[0].selection=self.idGlSync=self.bodyId # changed here, set in the viewer
 		meId=self.bodyIdBox.value(); pos=self.intrWithCombo.currentIndex()
-		meLabel=makeBodyLabel(O.bodies[meId])
+		try:
+			meLabel=makeBodyLabel(O.bodies[meId])
+		except IndexError: meLabel=u'…'
 		self.plusLabel.setText(' '.join(meLabel.split()[1:])+'  <b>+</b>') # do not repeat the id
 		self.bodyIdBox.setMaximum(len(O.bodies)-1)
 		others=[(i.id1 if i.id1!=meId else i.id2) for i in O.interactions.withBody(self.bodyIdBox.value()) if i.isReal]

=== modified file 'gui/qt4/SerializableEditor.py'
--- gui/qt4/SerializableEditor.py	2010-07-27 12:37:10 +0000
+++ gui/qt4/SerializableEditor.py	2010-07-27 22:04:20 +0000
@@ -9,6 +9,9 @@
 logging.basicConfig(level=logging.INFO)
 #from logging import debug,info,warning,error
 from yade import *
+import yade.qt
+
+seqSerializableShowType=True # show type headings in serializable sequences (takes vertical space, but makes the type hyperlinked)
 
 
 class AttrEditor():
@@ -80,37 +83,89 @@
 		self.isHot(False)
 
 class AttrEditor_MatrixX(AttrEditor,QFrame):
-	def __init__(self,parent,ser,attr,rows,cols,idxConverter):
+	def __init__(self,parent,ser,attr,rows,cols,idxConverter,variableCols=False,emptyCell=None):
 		'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []'
 		AttrEditor.__init__(self,ser,attr)
 		QFrame.__init__(self,parent)
-		self.rows,self.cols=rows,cols
+		self.rows,self.cols,self.variableCols=rows,cols,variableCols
 		self.idxConverter=idxConverter
 		self.setContentsMargins(0,0,0,0)
+		val=getattr(self.ser,self.attr)
 		self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
 		for row,col in itertools.product(range(self.rows),range(self.cols)):
-			w=QLineEdit('');
+			if self.variableCols and col>=len(val[row]):
+				if emptyCell: self.grid.addWidget(QLabel(emptyCell,self),row,col)
+				continue
+			w=QLineEdit('')
 			self.grid.addWidget(w,row,col);
 			w.textEdited.connect(self.isHot)
 			w.editingFinished.connect(self.update)
 	def refresh(self):
 		val=getattr(self.ser,self.attr)
 		for row,col in itertools.product(range(self.rows),range(self.cols)):
-			self.grid.itemAtPosition(row,col).widget().setText(str(val[self.idxConverter(row,col)]))
+			if self.variableCols and col>=len(val[row]): continue
+			self.grid.itemAtPosition(row,col).widget().setText(str(self.getItem(val,row,col)))
+	def getItem(self,obj,row,col):
+		if self.variableCols: return obj[row][col]
+		else: return obj[self.idxConverter(row,col)]
+	def setItem(self,val,row,col,newVal):
+		if self.variableCols: val[row][col]=newVal
+		else: val[self.idxConverter(row,col)]=newVal
 	def update(self):
 		try:
 			val=getattr(self.ser,self.attr)
 			for row,col in itertools.product(range(self.rows),range(self.cols)):
+				if self.variableCols and col>=len(val[row]): continue
 				w=self.grid.itemAtPosition(row,col).widget()
-				if w.isModified(): val[self.idxConverter(row,col)]=float(w.text())
+				if w.isModified(): self.setItem(val,row,col,float(w.text()))
 			logging.debug('setting'+str(val))
 			self.setAttribute(self.ser,self.attr,val)
 		except ValueError: self.refresh()
 		self.isHot(False)
 
+class AttrEditor_MatrixXi(AttrEditor,QFrame):
+	def __init__(self,parent,ser,attr,rows,cols,idxConverter):
+		'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []'
+		AttrEditor.__init__(self,ser,attr)
+		QFrame.__init__(self,parent)
+		self.rows,self.cols=rows,cols
+		self.idxConverter=idxConverter
+		self.setContentsMargins(0,0,0,0)
+		self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			w=QSpinBox()
+			w.setRange(int(-1e10),int(1e10)); w.setSingleStep(1);
+			self.grid.addWidget(w,row,col);
+			w.valueChanged.connect(self.update)
+		self.refresh()
+	def refresh(self):
+		val=getattr(self.ser,self.attr)
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			self.grid.itemAtPosition(row,col).widget().setValue(val[self.idxConverter(row,col)])
+	def update(self):
+		return
+		val=getattr(self.ser,self.attr); modified=False
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			w=self.grid.itemAtPosition(row,col).widget()
+			if w.value()!=val[self.idxConverter(row,col)]:
+				modified=True; val[self.idxConverter(row,col)]=w.value()
+		if not modified: return
+		logging.debug('setting'+str(val))
+		setattribute(self.ser,self.attr,val)
+
+class AttrEditor_Vector3i(AttrEditor_MatrixXi):
+	def __init__(self,parent,ser,attr):
+		AttrEditor_MatrixXi.__init__(self,parent,ser,attr,1,3,lambda r,c:c)
+class AttrEditor_Vector2i(AttrEditor_MatrixXi):
+	def __init__(self,parent,ser,attr):
+		AttrEditor_MatrixXi.__init__(self,parent,ser,attr,1,2,lambda r,c:c)
+
 class AttrEditor_Vector3(AttrEditor_MatrixX):
 	def __init__(self,parent,ser,attr):
 		AttrEditor_MatrixX.__init__(self,parent,ser,attr,1,3,lambda r,c:c)
+class AttrEditor_Vector2(AttrEditor_MatrixX):
+	def __init__(self,parent,ser,attr):
+		AttrEditor_MatrixX.__init__(self,parent,ser,attr,1,2,lambda r,c:c)
 class AttrEditor_Matrix3(AttrEditor_MatrixX):
 	def __init__(self,parent,ser,attr):
 		AttrEditor_MatrixX.__init__(self,parent,ser,attr,3,3,lambda r,c:(r,c))
@@ -119,7 +174,7 @@
 		AttrEditor_MatrixX.__init__(self,parent,ser,attr,1,4,lambda r,c:c)
 class AttrEditor_Se3(AttrEditor_MatrixX):
 	def __init__(self,parent,ser,attr):
-		AttrEditor_MatrixX.__init__(self,parent,ser,attr,1,4,lambda r,c:c)
+		AttrEditor_MatrixX.__init__(self,parent,ser,attr,2,4,idxConverter=None,variableCols=True,emptyCell=u'←<i>pos</i> ↙<i>ori</i>')
 
 class AttrEditor_ListStr(AttrEditor,QPlainTextEdit):
 	def __init__(self,parent,ser,attr):
@@ -137,6 +192,11 @@
 		self.setAttribute(self.ser,self.attr,str(t).strip().split('\n'))
 		if not self.hasFocus(): self.isHot(False)
 
+class Se3FakeType: pass
+
+_fundamentalEditorMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,Quaternion:AttrEditor_Quaternion,Vector2:AttrEditor_Vector2,Vector3:AttrEditor_Vector3,Matrix3:AttrEditor_Matrix3,Vector3i:AttrEditor_Vector3i,Vector2i:AttrEditor_Vector2i,Se3FakeType:AttrEditor_Se3,(str,):AttrEditor_ListStr}
+_fundamentalInitValues={bool:True,str:'',int:0,float:0.0,Quaternion:Quaternion.Identity,Vector3:Vector3.Zero,Matrix3:Matrix3.Zero,Se3FakeType:(Vector3.Zero,Quaternion.Identity),Vector3i:Vector3i.Zero,Vector2i:Vector2i.Zero,Vector2:Vector2.Zero}
+
 class SerializableEditor(QFrame):
 	"Class displaying and modifying serializable attributes of a yade object."
 	import collections
@@ -180,13 +240,14 @@
 		vecMap={
 			'int':int,'long':int,'body_id_t':long,'size_t':long,
 			'Real':float,'float':float,'double':float,
-			'Vector3r':Vector3,'Matrix3r':Matrix3,
+			'Vector3r':Vector3,'Matrix3r':Matrix3,'Se3r':Se3FakeType,
 			'string':str,
-			'BodyCallback':BodyCallback,'IntrCallback':IntrCallback,'BoundFunctor':BoundFunctor,'InteractionGeometryFunctor':InteractionGeometryFunctor,'InteractionPhysicsFunctor':InteractionPhysicsFunctor,'LawFunctor':LawFunctor
+			'BodyCallback':BodyCallback,'IntrCallback':IntrCallback,'BoundFunctor':BoundFunctor,'InteractionGeometryFunctor':InteractionGeometryFunctor,'InteractionPhysicsFunctor':InteractionPhysicsFunctor,'LawFunctor':LawFunctor,
+			'GlShapeFunctor':GlShapeFunctor,'GlStateFunctor':GlStateFunctor,'GlInteractionGeometryFunctor':GlInteractionGeometryFunctor,'GlInteractionPhysicsFunctor':GlInteractionPhysicsFunctor,'GlBoundFunctor':GlBoundFunctor
 		}
 		for T,ret in vecMap.items():
 			if vecTest(T,cxxT):
-				logging.debug("Got type %s from cxx type %s"%(ret.__name__,cxxT))
+				logging.debug("Got type %s from cxx type %s"%(repr(ret),cxxT))
 				return (ret,)
 		logging.error("Unable to guess python type from cxx type '%s'"%cxxT)
 		return None
@@ -197,7 +258,8 @@
 			logging.error('TypeError when getting attributes of '+str(self.ser)+',skipping. ')
 			import traceback
 			traceback.print_exc()
-		for attr in self.ser.dict():
+		attrs=self.ser.dict().keys(); attrs.sort()
+		for attr in attrs:
 			val=getattr(self.ser,attr) # get the value using serattr, as it might be different from what the dictionary provides (e.g. Body.blockedDOFs)
 			t=None
 			if attr in self.ignoredAttrs or attr=='blockedDOFs': # HACK here
@@ -206,13 +268,35 @@
 				t=self.getListTypeFromDocstring(attr)
 				if not t and len(val)==0: t=(val[0].__class__,) # 1-tuple is list of the contained type
 				#if not t: raise RuntimeError('Unable to guess type of '+str(self.ser)+'.'+attr)
+			# hack for Se3, which is returned as (Vector3,Quaternion) in python
+			elif isinstance(val,tuple) and len(val)==2 and val[0].__class__==Vector3 and val[1].__class__==Quaternion: t=Se3FakeType
 			else: t=val.__class__
 			#logging.debug('Attr %s is of type %s'%(attr,((t[0].__name__,) if isinstance(t,tuple) else t.__name__)))
 			self.entries.append(self.EntryData(name=attr,T=t))
+	def getDocstring(self,attr=None):
+		"If attr is *None*, return docstring of the Serializable itself"
+		doc=(getattr(self.ser.__class__,attr).__doc__ if attr else self.ser.__class__.__doc__)
+		if not doc: return None
+		doc=re.sub(':y(attrtype|default):`[^`]*`','',doc)
+		doc=re.sub(':yref:`([^`]*)`','\\1',doc)
+		import textwrap
+		wrapper=textwrap.TextWrapper(replace_whitespace=False)
+		return wrapper.fill(textwrap.dedent(doc))
+	def getLabelWithUrl(self,attr=None):
+		# the class for which is the attribute defined is the top-most base where it still exists... (is there a more straight-forward way?!)
+		# we only walk the direct inheritance
+		if attr:
+			klass=self.ser.__class__
+			while attr in dir(klass.__bases__[0]): klass=klass.__bases__[0]
+			#import textwrap; linkName='<br>'.join(textwrap.wrap(attr,width=10))
+			linkName=attr
+		else:
+			klass=self.ser.__class__
+			linkName=klass.__name__
+		return '<a href="%s#yade.wrapper.%s%s">%s</a>'%(yade.qt.sphinxDocWrapperPage,klass.__name__,(('.'+attr) if attr else ''),linkName)
 	def mkWidget(self,entry):
 		if not entry.T: return None
-		typeMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,Quaternion:AttrEditor_Quaternion,Vector3:AttrEditor_Vector3,Matrix3:AttrEditor_Matrix3,(str,):AttrEditor_ListStr}
-		Klass=typeMap.get(entry.T,None)
+		Klass=_fundamentalEditorMap.get(entry.T,None)
 		if Klass:
 			widget=Klass(self,self.ser,entry.name)
 			widget.setFocusPolicy(Qt.StrongFocus)
@@ -234,12 +318,14 @@
 		grid.setVerticalSpacing(0)
 		grid.setLabelAlignment(Qt.AlignRight)
 		if self.showType:
-			lab=QLabel(u'<b>→  '+self.ser.__class__.__name__+u'  ←</b>')
-			lab.setFrameShape(QFrame.Box); lab.setFrameShadow(QFrame.Sunken); lab.setLineWidth(2); lab.setAlignment(Qt.AlignHCenter)
+			lab=QLabel(u'<b>→  '+self.getLabelWithUrl()+u'  ←</b>')
+			lab.setFrameShape(QFrame.Box); lab.setFrameShadow(QFrame.Sunken); lab.setLineWidth(2); lab.setAlignment(Qt.AlignHCenter); lab.linkActivated.connect(yade.qt.openUrl)
+			lab.setToolTip(self.getDocstring())
 			grid.setWidget(0,QFormLayout.SpanningRole,lab)
 		for entry in self.entries:
 			entry.widget=self.mkWidget(entry)
-			grid.addRow(entry.name,entry.widget if entry.widget else QLabel('<i>unhandled type</i>'))
+			label=QLabel(self); label.setText(self.getLabelWithUrl(entry.name)); label.setToolTip(self.getDocstring(entry.name)); label.linkActivated.connect(yade.qt.openUrl)
+			grid.addRow(label,entry.widget if entry.widget else QLabel('<i>unhandled type</i>'))
 		self.setLayout(grid)
 		self.refreshEvent()
 	def refreshEvent(self):
@@ -247,7 +333,6 @@
 			if e.widget and not e.widget.hot: e.widget.refresh()
 	def refresh(self): pass
 
-#import sys; sys.path.append('.')
 from yade.qt.ui_SeqSerializable import Ui_SeqSerializable
 
 class SeqSerializable(QFrame,Ui_SeqSerializable):
@@ -272,10 +357,13 @@
 		ret=ser.label if (hasattr(self,'label') and len(ser.label)>0) else str(ser).replace('instance at','')
 		return ('' if pos<0 else str(pos)+'. ')+ret
 	def relabelItems(self):
-		for i in range(self.tb.count()): self.tb.setItemText(i,self.mkItemLabel(self.tb.widget(i).ser,i))
+		for i in range(self.tb.count()):
+			self.tb.setItemText(i,self.mkItemLabel(self.tb.widget(i).ser,i))
+			self.tb.setItemToolTip(i,self.tb.widget(i).getDocstring(None))
 	def fillToolBox(self):
 		for i,ser in enumerate(self.getter()):
-			self.tb.addItem(SerializableEditor(ser,parent=None),self.mkItemLabel(ser,i))
+			self.tb.addItem(SerializableEditor(ser,parent=None,showType=seqSerializableShowType),self.mkItemLabel(ser,i))
+			self.relabelItems() # updates tooltips
 	def moveUpSlot(self):
 		ix=self.tb.currentIndex();
 		if ix==0: return # already all way up
@@ -307,7 +395,7 @@
 		if not dialog.exec_(): return # cancelled
 		ser=dialog.result()
 		ix=self.tb.currentIndex(); 
-		self.tb.insertItem(ix,SerializableEditor(ser,parent=None),self.mkItemLabel(ser,ix))
+		self.tb.insertItem(ix,SerializableEditor(ser,parent=None,showType=seqSerializableShowType),self.mkItemLabel(ser,ix))
 		self.tb.setCurrentIndex(ix)
 		self.update(); self.relabelItems()
 	def update(self):
@@ -323,6 +411,35 @@
 		self.tb.setCurrentIndex(ix)
 	def refresh(self): pass # refreshEvent(self)
 
+class NewFundamentalDialog(QDialog):
+	def __init__(self,parent,attrName,typeObj,typeStr):
+		QDialog.__init__(self,parent)
+		self.setWindowTitle('%s (type %s)'%(attrName,typeStr))
+		self.layout=QVBoxLayout(self)
+		self.scroll=QScrollArea(self)
+		self.scroll.setWidgetResizable(True)
+		self.buttons=QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel);
+		self.buttons.accepted.connect(self.accept)
+		self.buttons.rejected.connect(self.reject)
+		self.layout.addWidget(self.scroll)
+		self.layout.addWidget(self.buttons)
+		self.setWindowModality(Qt.WindowModal)
+		class FakeObjClass: pass
+		self.fakeObj=FakeObjClass()
+		self.attrName=attrName
+		Klass=_fundamentalEditorMap.get(typeObj,None)
+		initValue=_fundamentalInitValues.get(typeObj,typeObj())
+		setattr(self.fakeObj,attrName,initValue)
+		if Klass:
+			self.widget=Klass(None,self.fakeObj,attrName)
+			self.scroll.setWidget(self.widget)
+			self.scroll.show()
+			self.widget.refresh()
+		else: raise RuntimeError("Unable to construct new dialog for type %s"%(typeStr))
+	def result(self):
+		self.widget.update()
+		return getattr(self.fakeObj,self.attrName)
+
 class NewSerializableDialog(QDialog):
 	def __init__(self,parent,baseClassName,includeBase=True):
 		import yade.system
@@ -334,6 +451,7 @@
 		self.combo.addItems(childs)
 		self.combo.currentIndexChanged.connect(self.comboSlot)
 		self.scroll=QScrollArea(self)
+		self.scroll.setWidgetResizable(True)
 		self.buttons=QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel);
 		self.buttons.accepted.connect(self.accept)
 		self.buttons.rejected.connect(self.reject)
@@ -346,7 +464,7 @@
 	def comboSlot(self,index):
 		item=str(self.combo.itemText(index))
 		self.ser=eval(item+'()')
-		self.scroll.setWidget(SerializableEditor(self.ser,self.scroll))
+		self.scroll.setWidget(SerializableEditor(self.ser,self.scroll,showType=True))
 		self.scroll.show()
 	def result(self): return self.ser
 	def sizeHint(self): return QSize(180,400)

=== modified file 'gui/qt4/__init__.py'
--- gui/qt4/__init__.py	2010-07-27 12:37:10 +0000
+++ gui/qt4/__init__.py	2010-07-27 22:04:20 +0000
@@ -10,12 +10,54 @@
 
 from yade.qt._GLViewer import *
 
+maxWebWindows=2
+"Number of webkit windows that will be cycled to show help on clickable objects"
+webWindows=[] 
+"holds instances of QtWebKit windows; clicking an url will open it in the window that was the least recently updated"
+sphinxOnlineDocPath='https://www.yade-dem.org/sphinx/'
+"Base URL for the documentation. Packaged versions should change to the local installation directory."
+
+# find if we have docs installed locally from package
+import yade.config
+import os.path
+sphinxLocalDocPath=yade.config.prefix+'/share/doc/yade'+yade.config.suffix+'/html/'
+# sorry
+_eudoxosLocalPathHack=sphinxLocalDocPath='/home/vaclav/yt/doc/sphinx/_build/html/'
+if os.path.exists(_eudoxosLocalPathHack): sphinxLocalDocPath=_eudoxosLocalPathHack
+# end sorry
+sphinxWrapperPart='yade.wrapper.html'
+sphinxDocWrapperPage=(('file://'+sphinxLocalDocPath) if os.path.exists(sphinxLocalDocPath+'/'+sphinxWrapperPart) else sphinxOnlineDocPath)+'/'+sphinxWrapperPart
+
+
+def openUrl(url):
+	from PyQt4 import QtWebKit
+	global maxWebWindows,webWindows
+	reuseLast=False
+	# use the last window if the class is the same and only the attribute differs
+	try:
+		reuseLast=(len(webWindows)>0 and str(webWindows[-1].url()).split('#')[-1].split('.')[2]==url.split('#')[-1].split('.')[2])
+	except: pass
+	if not reuseLast:
+		if len(webWindows)<maxWebWindows: webWindows.append(QtWebKit.QWebView())
+		else: webWindows=webWindows[1:]+[webWindows[0]]
+	web=webWindows[-1]
+	web.load(QUrl(url)); web.setWindowTitle(url);
+	if 1:
+		def killSidebar(result):
+			frame=web.page().mainFrame()
+			frame.evaluateJavaScript("var bv=$('.bodywrapper'); bv.css('margin','0 0 0 0');")
+			frame.evaluateJavaScript("var sbw=$('.sphinxsidebarwrapper'); sbw.css('display','none');")
+			frame.evaluateJavaScript("var sb=$('.sphinxsidebar'); sb.css('display','none'); ")
+			frame.evaluateJavaScript("var sb=$('.sidebar'); sb.css('width','0px'); ")
+			web.loadFinished.disconnect(killSidebar)
+		web.loadFinished.connect(killSidebar)
+	web.show();	web.raise_()
 
 
 global _controller
 controller=None
 
-class Controller(QWidget,Ui_Controller):
+class ControllerClass(QWidget,Ui_Controller):
 	def __init__(self,parent=None):
 		QWidget.__init__(self)
 		self.setupUi(self)
@@ -55,7 +97,7 @@
 		"update generator parameters when a new one is selected"
 		gen=eval(str(genStr)+'()')
 		self.generator=gen
-		se=SerializableEditor(gen,parent=self.generatorArea,ignoredAttrs=set(['outputFileName']))
+		se=SerializableEditor(gen,parent=self.generatorArea,ignoredAttrs=set(['outputFileName']),showType=True)
 		self.generatorArea.setWidget(se)
 	def pythonComboSlot(self,cmd):
 		try:
@@ -76,7 +118,7 @@
 				v=View(); v.center()
 	def displayComboSlot(self,dispStr):
 		ser=(self.renderer if dispStr=='OpenGLRenderer' else eval(str(dispStr)+'()'))
-		se=SerializableEditor(ser,parent=self.displayArea,ignoredAttrs=set(['label']))
+		se=SerializableEditor(ser,parent=self.displayArea,ignoredAttrs=set(['label']),showType=True)
 		self.displayArea.setWidget(se)
 	def loadSlot(self):
 		f=QFileDialog.getOpenFileName(self,'Load simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*')
@@ -185,9 +227,14 @@
 		
 def Generator():
 	global controller
-	if not controller: controller=Controller();
-	controller.show()
+	if not controller: controller=ControllerClass();
+	controller.show(); controller.raise_()
 	controller.setTabActive('generator')
+def Controller():
+	global controller
+	if not controller: controller=ControllerClass();
+	controller.show(); controller.raise_()
+	controller.setTabActive('simulation')
 
 #if __name__=='__main__':
 #	from PyQt4 import QtGui

=== modified file 'py/mathWrap/miniEigen.cpp'
--- py/mathWrap/miniEigen.cpp	2010-07-12 13:20:02 +0000
+++ py/mathWrap/miniEigen.cpp	2010-07-27 22:04:20 +0000
@@ -26,9 +26,9 @@
 void Matrix3r_set_item_linear(Matrix3r & self, int idx, Real value){ IDX_CHECK(idx,9); self(idx/3,idx%3)=value; }
 
 Real Vector3r_get_item(const Vector3r & self, int idx){ IDX_CHECK(idx,3); return self[idx]; }
-int  Vector3i_get_item(const Vector3r & self, int idx){ IDX_CHECK(idx,3); return self[idx]; }
+int  Vector3i_get_item(const Vector3i & self, int idx){ IDX_CHECK(idx,3); return self[idx]; }
 Real Vector2r_get_item(const Vector2r & self, int idx){ IDX_CHECK(idx,2); return self[idx]; }
-int  Vector2i_get_item(const Vector2r & self, int idx){ IDX_CHECK(idx,2); return self[idx]; }
+int  Vector2i_get_item(const Vector2i & self, int idx){ IDX_CHECK(idx,2); return self[idx]; }
 
 Real Quaternionr_get_item(const Quaternionr & self, int idx){ IDX_CHECK(idx,4); if(idx==0) return self.x(); if(idx==1) return self.y(); if(idx==2) return self.z(); return self.w(); }
 Real Matrix3r_get_item(Matrix3r & self, bp::tuple _idx){ int idx[2]; int mx[2]={3,3}; IDX2_CHECKED_TUPLE_INTS(_idx,mx,idx); return self(idx[0],idx[1]); }