← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-pkg/yade/git-trunk] Rev 3691: Prepare Qt5-build.

 

------------------------------------------------------------
revno: 3691
committer: Anton Gladky <gladky.anton@xxxxxxxxx>
timestamp: Fri 2015-06-26 22:19:22 +0200
message:
  Prepare Qt5-build.
  
  Not functional yet.
removed:
  gui/qt4/build
added:
  gui/qt5/
  gui/qt5/GLViewer.cpp
  gui/qt5/GLViewer.hpp
  gui/qt5/GLViewerDisplay.cpp
  gui/qt5/GLViewerMouse.cpp
  gui/qt5/Inspector.py
  gui/qt5/OpenGLManager.cpp
  gui/qt5/OpenGLManager.hpp
  gui/qt5/SerializableEditor.py
  gui/qt5/XYZ.png
  gui/qt5/XYZ.xpm
  gui/qt5/YZX.png
  gui/qt5/YZX.xpm
  gui/qt5/ZXY.png
  gui/qt5/ZXY.xpm
  gui/qt5/_GLViewer.cpp
  gui/qt5/__init__.py
  gui/qt5/controller.ui
  gui/qt5/img.qrc
  gui/qt5/yade-favicon.png
  gui/qt5/yade-favicon.xpm
modified:
  CMakeLists.txt
  gui/CMakeLists.txt


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

Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-pkg/yade/git-trunk/+edit-subscription
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt	2015-06-12 13:05:43 +0000
+++ CMakeLists.txt	2015-06-26 20:19:22 +0000
@@ -22,9 +22,10 @@
 #  runtimePREFIX: used for packaging, when install directory is not the same is runtime directory (/usr/local by default)
 #  CHUNKSIZE: set >1, if you want several sources to be compiled at once. Increases compilation speed and RAM-consumption during it (1 by default)
 #  VECTORIZE: enables vectorization and alignment in Eigen3 library, experimental (OFF by default)
+#  USE_QT5: use QT5 for GUI, experimental (OFF by default)
 
 project(Yade C CXX)
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8.11)
 
 set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cMake")
 
@@ -126,6 +127,7 @@
 OPTION(ENABLE_LIQMIGRATION "Enable liquid control (very experimental), see [Mani2013] for details" OFF)
 OPTION(ENABLE_MASK_ARBITRARY "Enable arbitrary precision of bitmask variables (only Body::groupMask yet implemented) (experimental). Use -DMASK_ARBITRARY_SIZE=int to set number of used bits (256 by default)" OFF)
 OPTION(ENABLE_PROFILING "Enable profiling, e.g. shows some more metrics, which can define bottlenecks of the code (OFF by default)" OFF)
+OPTION(USE_QT5 "USE Qt5 for GUI" OFF)
 
 #===========================================================
 # Use Eigen3 by default
@@ -214,25 +216,42 @@
 ENDIF(ENABLE_GTS)
 #===========================================================
 IF(ENABLE_GUI)
-  FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtOpenGL)
   FIND_PACKAGE(OpenGL)
   FIND_PACKAGE(GLUT)
   FIND_PACKAGE(glib2)
   FIND_PACKAGE(QGLVIEWER)
-  IF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
-    SET(GUI_LIBS ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES})
-    SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL")
-    INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
-    INCLUDE_DIRECTORIES(${QT_INCLUDES})
     
-    MESSAGE(STATUS "Found GUI-LIBS")
-    SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI")
-  ELSE(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
-    MESSAGE(STATUS "GUI-LIBS NOT found")
-    SET(DISABLED_FEATS "${DISABLED_FEATS} GUI")
-    SET(ENABLE_GUI OFF)
-  ENDIF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+  IF(USE_QT5)
+    FIND_PACKAGE(Qt5 CONFIG REQUIRED Widgets Xml OpenGL)
+    IF(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+      SET(GUI_LIBS ${GLUT_LIBRARY} ${QGLVIEWER_LIBRARIES})
+      SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp")
+      SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL")
+      
+      MESSAGE(STATUS "Found GUI-Qt5-LIBS")
+      SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI-Qt5")
+    ELSE(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+      MESSAGE(STATUS "GUI-Qt5-LIBS NOT found")
+      SET(DISABLED_FEATS "${DISABLED_FEATS} GUI-Qt5")
+      SET(ENABLE_GUI OFF)
+    ENDIF(Qt5Widgets_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+  ELSE(USE_QT5)   # Use Qt4
+    FIND_PACKAGE(Qt4 COMPONENTS QtCore QtGui QtOpenGL)
+    IF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+      SET(GUI_LIBS ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QGLVIEWER_LIBRARIES})
+      SET(GUI_SRC_LIB "lib/opengl/GLUtils.cpp")
+      SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DYADE_OPENGL")
+      INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
+      INCLUDE_DIRECTORIES(${QT_INCLUDES})
+      
+      MESSAGE(STATUS "Found GUI-LIBS")
+      SET(CONFIGURED_FEATS "${CONFIGURED_FEATS} GUI")
+    ELSE(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+      MESSAGE(STATUS "GUI-LIBS NOT found")
+      SET(DISABLED_FEATS "${DISABLED_FEATS} GUI")
+      SET(ENABLE_GUI OFF)
+    ENDIF(QT4_FOUND AND OPENGL_FOUND AND GLUT_FOUND AND GLIB2_FOUND AND QGLVIEWER_FOUND)
+  ENDIF(USE_QT5)
 ELSE(ENABLE_GUI)
   SET(DISABLED_FEATS "${DISABLED_FEATS} GUI")
 ENDIF(ENABLE_GUI)

=== modified file 'gui/CMakeLists.txt'
--- gui/CMakeLists.txt	2013-10-14 20:51:11 +0000
+++ gui/CMakeLists.txt	2015-06-26 20:19:22 +0000
@@ -1,34 +1,64 @@
 IF(${ENABLE_GUI})
-  INCLUDE(${QT_USE_FILE})
   INCLUDE_DIRECTORIES(${QGLVIEWER_INCLUDE_DIR})
-  
-  SET(_GLViewer_MOC_HEADERS qt4/GLViewer.hpp;qt4/OpenGLManager.hpp)
-  SET(_GLViewer_SOURCE_FILES qt4/GLViewer.cpp;qt4/_GLViewer.cpp;qt4/OpenGLManager.cpp;qt4/GLViewerDisplay.cpp;qt4/GLViewerMouse.cpp)
-  
-  QT4_WRAP_CPP(_GLViewer_MOC_OUTFILES ${_GLViewer_MOC_HEADERS})
-  
-  ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES} ${_GLViewer_MOC_OUTFILES})
-  SET_TARGET_PROPERTIES(_GLViewer PROPERTIES PREFIX "")
-  TARGET_LINK_LIBRARIES(_GLViewer ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QT_LIBRARIES} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
-  IF(GL2PS_FOUND AND ENABLE_GL2PS)
-    TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES})
-  ENDIF(GL2PS_FOUND AND ENABLE_GL2PS)
-  INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt)
-  
-  FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt4/*.py")
-  INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt)
-  
-  EXECUTE_PROCESS(
-        COMMAND "pyrcc4" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc"
-        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
-        RESULT_VARIABLE rv
-        )
-  
-  EXECUTE_PROCESS(
-        COMMAND "pyuic4" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui"
-        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
-        RESULT_VARIABLE rv
-        )
-  INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py  DESTINATION ${YADE_PY_PATH}/yade/qt)
-  
+  IF(USE_QT5)
+    SET(CMAKE_AUTOMOC ON)
+    SET(_GLViewer_SOURCE_FILES qt5/GLViewer.cpp;qt5/_GLViewer.cpp;qt5/OpenGLManager.cpp;qt5/GLViewerDisplay.cpp;qt5/GLViewerMouse.cpp)
+    ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES})
+    
+    TARGET_LINK_LIBRARIES(_GLViewer Qt5::Widgets Qt5::Xml Qt5::OpenGL
+                                    ${GLUT_LIBRARY} ${OPENGL_LIBRARY}
+                                    ${QGLVIEWER_LIBRARIES}
+                                    ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
+    IF(GL2PS_FOUND AND ENABLE_GL2PS)
+      TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES})
+    ENDIF(GL2PS_FOUND AND ENABLE_GL2PS)
+    INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt)
+    
+    FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt5/*.py")
+    INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt)
+    
+    EXECUTE_PROCESS(
+          COMMAND "pyrcc5" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc"
+          WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
+          RESULT_VARIABLE rv
+          )
+    
+    EXECUTE_PROCESS(
+          COMMAND "pyuic5" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui"
+          WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt5
+          RESULT_VARIABLE rv
+          )
+    INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py  DESTINATION ${YADE_PY_PATH}/yade/qt)
+  ELSE(USE_QT5)
+    INCLUDE(${QT_USE_FILE})
+    
+    SET(_GLViewer_MOC_HEADERS qt4/GLViewer.hpp;qt4/OpenGLManager.hpp)
+    SET(_GLViewer_SOURCE_FILES qt4/GLViewer.cpp;qt4/_GLViewer.cpp;qt4/OpenGLManager.cpp;qt4/GLViewerDisplay.cpp;qt4/GLViewerMouse.cpp)
+    
+    QT4_WRAP_CPP(_GLViewer_MOC_OUTFILES ${_GLViewer_MOC_HEADERS})
+    
+    ADD_LIBRARY(_GLViewer SHARED ${_GLViewer_SOURCE_FILES} ${_GLViewer_MOC_OUTFILES})
+    SET_TARGET_PROPERTIES(_GLViewer PROPERTIES PREFIX "")
+    TARGET_LINK_LIBRARIES(_GLViewer ${GLUT_LIBRARY} ${OPENGL_LIBRARY} ${QT_LIBRARIES} ${QGLVIEWER_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
+    IF(GL2PS_FOUND AND ENABLE_GL2PS)
+      TARGET_LINK_LIBRARIES(_GLViewer ${GL2PS_LIBRARIES})
+    ENDIF(GL2PS_FOUND AND ENABLE_GL2PS)
+    INSTALL(TARGETS _GLViewer DESTINATION ${YADE_PY_PATH}/yade/qt)
+    
+    FILE(GLOB filesPYQT "${CMAKE_CURRENT_SOURCE_DIR}/qt4/*.py")
+    INSTALL(FILES ${filesPYQT} DESTINATION ${YADE_PY_PATH}/yade/qt)
+    
+    EXECUTE_PROCESS(
+          COMMAND "pyrcc4" "-o" "${CMAKE_BINARY_DIR}/img_rc.py" "img.qrc"
+          WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
+          RESULT_VARIABLE rv
+          )
+    
+    EXECUTE_PROCESS(
+          COMMAND "pyuic4" "-o" "${CMAKE_BINARY_DIR}/ui_controller.py" "controller.ui"
+          WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/qt4
+          RESULT_VARIABLE rv
+          )
+    INSTALL(FILES ${CMAKE_BINARY_DIR}/img_rc.py ${CMAKE_BINARY_DIR}/ui_controller.py  DESTINATION ${YADE_PY_PATH}/yade/qt)
+  ENDIF(USE_QT5)
 ENDIF(${ENABLE_GUI})

=== removed file 'gui/qt4/build'
--- gui/qt4/build	2010-07-24 18:10:24 +0000
+++ gui/qt4/build	1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
-#!/bin/sh
-pyrcc4 -o img_rc.py img.qrc
-pyuic4 controller.ui > ui_controller.py
-pyuic4 SeqSerializable.ui > ui_SeqSerializable.py

=== added directory 'gui/qt5'
=== added file 'gui/qt5/GLViewer.cpp'
--- gui/qt5/GLViewer.cpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewer.cpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,488 @@
+/*************************************************************************
+*  Copyright (C) 2004 by Olivier Galizzi                                 *
+*  olivier.galizzi@xxxxxxx                                               *
+*  Copyright (C) 2005 by Janek Kozicki                                   *
+*  cosurgi@xxxxxxxxxx                                                    *
+*                                                                        *
+*  This program is free software; it is licensed under the terms of the  *
+*  GNU General Public License v2 or later. See file LICENSE for details. *
+*************************************************************************/
+
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+
+#include<lib/opengl/OpenGLWrapper.hpp>
+#include<core/Body.hpp>
+#include<core/Scene.hpp>
+#include<core/Interaction.hpp>
+#include<core/DisplayParameters.hpp>
+#include<boost/algorithm/string.hpp>
+#include<sstream>
+#include<iomanip>
+#include<boost/algorithm/string/case_conv.hpp>
+#include<lib/serialization/ObjectIO.hpp>
+#include<lib/pyutil/gil.hpp>
+#include<QtGui/qevent.h>
+
+#ifdef YADE_GL2PS
+	#include<gl2ps.h>
+#endif
+
+static unsigned initBlocked(State::DOF_NONE);
+
+CREATE_LOGGER(GLViewer);
+
+GLLock::GLLock(GLViewer* _glv): boost::try_mutex::scoped_lock(Omega::instance().renderMutex), glv(_glv){
+	glv->makeCurrent();
+}
+GLLock::~GLLock(){ glv->doneCurrent(); }
+
+
+#define _W3 setw(3)<<setfill('0')
+#define _W2 setw(2)<<setfill('0')
+GLViewer::~GLViewer(){ /* get the GL mutex when closing */ GLLock lock(this); /* cerr<<"Destructing view #"<<viewId<<endl;*/ }
+
+void GLViewer::closeEvent(QCloseEvent *e){
+	LOG_DEBUG("Will emit closeView for view #"<<viewId);
+	OpenGLManager::self->emitCloseView(viewId);
+	e->accept();
+}
+
+GLViewer::GLViewer(int _viewId, const shared_ptr<OpenGLRenderer>& _renderer, QGLWidget* shareWidget): QGLViewer(/*parent*/(QWidget*)NULL,shareWidget), renderer(_renderer), viewId(_viewId) {
+	isMoving=false;
+	drawGrid=0;
+	drawScale=true;
+	timeDispMask=TIME_REAL|TIME_VIRT|TIME_ITER;
+	cut_plane = 0;
+	cut_plane_delta = -2;
+	gridSubdivide = false;
+	resize(550,550);
+	last=-1;
+	if(viewId==0) setWindowTitle("Primary view");
+	else setWindowTitle(("Secondary view #"+boost::lexical_cast<string>(viewId)).c_str());
+
+	show();
+	
+	mouseMovesCamera();
+	manipulatedClipPlane=-1;
+
+	if(manipulatedFrame()==0) setManipulatedFrame(new qglviewer::ManipulatedFrame());
+
+	xyPlaneConstraint=shared_ptr<qglviewer::LocalConstraint>(new qglviewer::LocalConstraint());
+	manipulatedFrame()->setConstraint(NULL);
+
+	setKeyDescription(Qt::Key_Return,"Run simulation.");
+	setKeyDescription(Qt::Key_A,"Toggle visibility of global axes.");
+	setKeyDescription(Qt::Key_C,"Set scene center so that all bodies are visible; if a body is selected, center around this body.");
+	setKeyDescription(Qt::Key_C & Qt::AltModifier,"Set scene center to median body position (same as space)");
+	setKeyDescription(Qt::Key_D,"Toggle time display mask");
+	setKeyDescription(Qt::Key_G,"Toggle grid visibility; g turns on and cycles");
+	setKeyDescription(Qt::Key_G & Qt::ShiftModifier ,"Hide grid.");
+	setKeyDescription(Qt::Key_M, "Move selected object.");
+	setKeyDescription(Qt::Key_X,"Show the xz [shift: xy] (up-right) plane (clip plane: align normal with +x)");
+	setKeyDescription(Qt::Key_Y,"Show the yx [shift: yz] (up-right) plane (clip plane: align normal with +y)");
+	setKeyDescription(Qt::Key_Z,"Show the zy [shift: zx] (up-right) plane (clip plane: align normal with +z)");
+	setKeyDescription(Qt::Key_Period,"Toggle grid subdivision by 10");
+	setKeyDescription(Qt::Key_S,"Save QGLViewer state to /tmp/qglviewerState.xml");
+	setKeyDescription(Qt::Key_T,"Switch orthographic / perspective camera");
+	setKeyDescription(Qt::Key_O,"Set narrower field of view");
+	setKeyDescription(Qt::Key_P,"Set wider field of view");
+	setKeyDescription(Qt::Key_R,"Revolve around scene center");
+	setKeyDescription(Qt::Key_V,"Save PDF of the current view to /tmp/yade-snapshot-0001.pdf (whichever number is available first). (Must be compiled with the gl2ps feature.)");
+ 	setPathKey(-Qt::Key_F1);
+ 	setPathKey(-Qt::Key_F2);
+	setKeyDescription(Qt::Key_Escape,"Manipulate scene (default)");
+	setKeyDescription(Qt::Key_F1,"Manipulate clipping plane #1");
+	setKeyDescription(Qt::Key_F2,"Manipulate clipping plane #2");
+	setKeyDescription(Qt::Key_F3,"Manipulate clipping plane #3");
+	setKeyDescription(Qt::Key_1,"Make the manipulated clipping plane parallel with plane #1");
+	setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #2");
+	setKeyDescription(Qt::Key_2,"Make the manipulated clipping plane parallel with plane #3");
+	setKeyDescription(Qt::Key_1 & Qt::AltModifier,"Add/remove plane #1 to/from the bound group");
+	setKeyDescription(Qt::Key_2 & Qt::AltModifier,"Add/remove plane #2 to/from the bound group");
+	setKeyDescription(Qt::Key_3 & Qt::AltModifier,"Add/remove plane #3 to/from the bound group");
+	setKeyDescription(Qt::Key_0,"Clear the bound group");
+	setKeyDescription(Qt::Key_7,"Load [Alt: save] view configuration #0");
+	setKeyDescription(Qt::Key_8,"Load [Alt: save] view configuration #1");
+	setKeyDescription(Qt::Key_9,"Load [Alt: save] view configuration #2");
+	setKeyDescription(Qt::Key_Space,"Center scene (same as Alt-C); clip plane: activate/deactivate");
+
+	centerScene();
+}
+
+bool GLViewer::isManipulating(){
+	return isMoving || manipulatedClipPlane>=0;
+}
+
+void GLViewer::resetManipulation(){
+	mouseMovesCamera();
+	setSelectedName(-1);
+	isMoving=false;
+	manipulatedClipPlane=-1;
+}
+
+void GLViewer::startClipPlaneManipulation(int planeNo){
+	assert(planeNo<renderer->numClipPlanes);
+	resetManipulation();
+	mouseMovesManipulatedFrame(xyPlaneConstraint.get());
+	manipulatedClipPlane=planeNo;
+	const Se3r se3(renderer->clipPlaneSe3[planeNo]);
+	manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(se3.position[0],se3.position[1],se3.position[2]),qglviewer::Quaternion(se3.orientation.x(),se3.orientation.y(),se3.orientation.z(),se3.orientation.w()));
+	string grp=strBoundGroup();
+	displayMessage("Manipulating clip plane #"+boost::lexical_cast<string>(planeNo+1)+(grp.empty()?grp:" (bound planes:"+grp+")"));
+}
+
+string GLViewer::getState(){
+	QString origStateFileName=stateFileName();
+	string tmpFile=Omega::instance().tmpFilename();
+	setStateFileName(QString(tmpFile.c_str())); saveStateToFile(); setStateFileName(origStateFileName);
+	LOG_WARN("State saved to temp file "<<tmpFile);
+	// read tmp file contents and return it as string
+	// this will replace all whitespace by space (nowlines will disappear, which is what we want)
+	ifstream in(tmpFile.c_str()); string ret; while(!in.eof()){string ss; in>>ss; ret+=" "+ss;}; in.close();
+	boost::filesystem::remove(boost::filesystem::path(tmpFile));
+	return ret;
+}
+
+void GLViewer::setState(string state){
+	string tmpFile=Omega::instance().tmpFilename();
+	std::ofstream out(tmpFile.c_str());
+	if(!out.good()){ LOG_ERROR("Error opening temp file `"<<tmpFile<<"', loading aborted."); return; }
+	out<<state; out.close();
+	LOG_WARN("Will load state from temp file "<<tmpFile);
+	QString origStateFileName=stateFileName(); setStateFileName(QString(tmpFile.c_str())); restoreStateFromFile(); setStateFileName(origStateFileName);
+	boost::filesystem::remove(boost::filesystem::path(tmpFile));
+}
+
+void GLViewer::keyPressEvent(QKeyEvent *e)
+{
+	last_user_event = boost::posix_time::second_clock::local_time();
+
+	if(false){}
+	/* special keys: Escape and Space */
+	else if(e->key()==Qt::Key_A){ toggleAxisIsDrawn(); return; }
+	else if(e->key()==Qt::Key_Escape){
+		if(!isManipulating()){ setSelectedName(-1); return; }
+		else { resetManipulation(); displayMessage("Manipulating scene."); }
+	}
+	else if(e->key()==Qt::Key_Space){
+		if(manipulatedClipPlane>=0) {displayMessage("Clip plane #"+boost::lexical_cast<string>(manipulatedClipPlane+1)+(renderer->clipPlaneActive[manipulatedClipPlane]?" de":" ")+"activated"); renderer->clipPlaneActive[manipulatedClipPlane]=!renderer->clipPlaneActive[manipulatedClipPlane]; }
+		else{ centerMedianQuartile(); }
+	}
+	/* function keys */
+	else if(e->key()==Qt::Key_F1 || e->key()==Qt::Key_F2 || e->key()==Qt::Key_F3 /* || ... */ ){
+		int n=0; if(e->key()==Qt::Key_F1) n=1; else if(e->key()==Qt::Key_F2) n=2; else if(e->key()==Qt::Key_F3) n=3; assert(n>0); int planeId=n-1;
+		if(planeId>=renderer->numClipPlanes) return;
+		if(planeId!=manipulatedClipPlane) startClipPlaneManipulation(planeId);
+	}
+	/* numbers */
+	else if(e->key()==Qt::Key_0 && (e->modifiers() & Qt::AltModifier)) { boundClipPlanes.clear(); displayMessage("Cleared bound planes group.");}
+	else if(e->key()==Qt::Key_1 || e->key()==Qt::Key_2 || e->key()==Qt::Key_3 /* || ... */ ){
+		int n=0; if(e->key()==Qt::Key_1) n=1; else if(e->key()==Qt::Key_2) n=2; else if(e->key()==Qt::Key_3) n=3; assert(n>0); int planeId=n-1;
+		if(planeId>=renderer->numClipPlanes) return; // no such clipping plane
+		if(e->modifiers() & Qt::AltModifier){
+			if(boundClipPlanes.count(planeId)==0) {boundClipPlanes.insert(planeId); displayMessage("Added plane #"+boost::lexical_cast<string>(planeId+1)+" to the bound group: "+strBoundGroup());}
+			else {boundClipPlanes.erase(planeId); displayMessage("Removed plane #"+boost::lexical_cast<string>(planeId+1)+" from the bound group: "+strBoundGroup());}
+		}
+		else if(manipulatedClipPlane>=0 && manipulatedClipPlane!=planeId) {
+			const Quaternionr& o=renderer->clipPlaneSe3[planeId].orientation;
+			manipulatedFrame()->setOrientation(qglviewer::Quaternion(o.x(),o.y(),o.z(),o.w()));
+			displayMessage("Copied orientation from plane #1");
+		}
+	}
+	else if(e->key()==Qt::Key_7 || e->key()==Qt::Key_8 || e->key()==Qt::Key_9){
+		int nn=-1; if(e->key()==Qt::Key_7)nn=0; else if(e->key()==Qt::Key_8)nn=1; else if(e->key()==Qt::Key_9)nn=2; assert(nn>=0); size_t n=(size_t)nn;
+		if(e->modifiers() & Qt::AltModifier) saveDisplayParameters(n);
+		else useDisplayParameters(n);
+	}
+	/* letters alphabetically */
+	else if(e->key()==Qt::Key_C && (e->modifiers() & Qt::AltModifier)){ displayMessage("Median centering"); centerMedianQuartile(); }
+	else if(e->key()==Qt::Key_C){
+		// center around selected body
+		if(selectedName() >= 0 && (*(Omega::instance().getScene()->bodies)).exists(selectedName())) setSceneCenter(manipulatedFrame()->position());
+		// make all bodies visible
+		else centerScene();
+	}
+	else if(e->key()==Qt::Key_D &&(e->modifiers() & Qt::AltModifier)){ Body::id_t id; if((id=Omega::instance().getScene()->selectedBody)>=0){ const shared_ptr<Body>& b=Body::byId(id); b->setDynamic(!b->isDynamic()); LOG_INFO("Body #"<<id<<" now "<<(b->isDynamic()?"":"NOT")<<" dynamic"); } }
+	else if(e->key()==Qt::Key_D) {timeDispMask+=1; if(timeDispMask>(TIME_REAL|TIME_VIRT|TIME_ITER))timeDispMask=0; }
+	else if(e->key()==Qt::Key_G) { if(e->modifiers() & Qt::ShiftModifier){ drawGrid=0; return; } else drawGrid++; if(drawGrid>=8) drawGrid=0; }
+	else if (e->key()==Qt::Key_M && selectedName() >= 0){ 
+		if(!(isMoving=!isMoving)){
+			displayMessage("Moving done.");
+			if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;} mouseMovesCamera();}
+		else{ 	displayMessage("Moving selected object");
+			
+			long selection = Omega::instance().getScene()->selectedBody;
+			initBlocked=Body::byId(Body::id_t(selection))->state->blockedDOFs; last=selection;
+			Body::byId(Body::id_t(selection))->state->blockedDOFs=State::DOF_ALL;
+			Quaternionr& q = Body::byId(selection)->state->ori;
+			Vector3r&    v = Body::byId(selection)->state->pos;
+			manipulatedFrame()->setPositionAndOrientation(qglviewer::Vec(v[0],v[1],v[2]),qglviewer::Quaternion(q.x(),q.y(),q.z(),q.w()));
+			mouseMovesManipulatedFrame();}
+	}
+	else if (e->key() == Qt::Key_T) camera()->setType(camera()->type()==qglviewer::Camera::ORTHOGRAPHIC ? qglviewer::Camera::PERSPECTIVE : qglviewer::Camera::ORTHOGRAPHIC);
+	else if(e->key()==Qt::Key_O) camera()->setFieldOfView(camera()->fieldOfView()*0.9);
+	else if(e->key()==Qt::Key_P) camera()->setFieldOfView(camera()->fieldOfView()*1.1);
+	else if(e->key()==Qt::Key_R){ // reverse the clipping plane; revolve around scene center if no clipping plane selected
+		if(manipulatedClipPlane>=0 && manipulatedClipPlane<renderer->numClipPlanes){
+			/* here, we must update both manipulatedFrame orientation and renderer->clipPlaneSe3 orientation in the same way */
+			Quaternionr& ori=renderer->clipPlaneSe3[manipulatedClipPlane].orientation;
+			ori=Quaternionr(AngleAxisr(Mathr::PI,Vector3r(0,1,0)))*ori; 
+			manipulatedFrame()->setOrientation(qglviewer::Quaternion(qglviewer::Vec(0,1,0),Mathr::PI)*manipulatedFrame()->orientation());
+			displayMessage("Plane #"+boost::lexical_cast<string>(manipulatedClipPlane+1)+" reversed.");
+		}
+		else {
+			camera()->setRevolveAroundPoint(sceneCenter());
+		}
+	}
+	else if(e->key()==Qt::Key_S){
+		LOG_INFO("Saving QGLViewer state to /tmp/qglviewerState.xml");
+		setStateFileName("/tmp/qglviewerState.xml"); saveStateToFile(); setStateFileName(QString::null);
+	}
+	else if(e->key()==Qt::Key_L){
+		LOG_INFO("Loading QGLViewer state from /tmp/qglviewerState.xml");
+		setStateFileName("/tmp/qglviewerState.xml"); restoreStateFromFile(); setStateFileName(QString::null);
+	}
+	else if(e->key()==Qt::Key_X || e->key()==Qt::Key_Y || e->key()==Qt::Key_Z){
+		int axisIdx=(e->key()==Qt::Key_X?0:(e->key()==Qt::Key_Y?1:2));
+		if(manipulatedClipPlane<0){
+			qglviewer::Vec up(0,0,0), vDir(0,0,0);
+			bool alt=(e->modifiers() && Qt::ShiftModifier);
+			up[axisIdx]=1; vDir[(axisIdx+(alt?2:1))%3]=alt?1:-1;
+			camera()->setViewDirection(vDir);
+			camera()->setUpVector(up);
+			centerMedianQuartile();
+		}
+		else{ // align clipping normal plane with world axis
+			// x: (0,1,0),pi/2; y: (0,0,1),pi/2; z: (1,0,0),0
+			qglviewer::Vec axis(0,0,0); axis[(axisIdx+1)%3]=1; Real angle=axisIdx==2?0:Mathr::PI/2;
+			manipulatedFrame()->setOrientation(qglviewer::Quaternion(axis,angle));
+		}
+	}
+	else if(e->key()==Qt::Key_Period) gridSubdivide = !gridSubdivide;
+	else if(e->key()==Qt::Key_Return){
+		if (Omega::instance().isRunning()) Omega::instance().pause();
+		else Omega::instance().run();
+		LOG_INFO("Running...");
+	}
+#ifdef YADE_GL2PS
+	else if(e->key()==Qt::Key_V){
+		for(int i=0; ;i++){
+			std::ostringstream fss; fss<<"/tmp/yade-snapshot-"<<setw(4)<<setfill('0')<<i<<".pdf";
+			if(!boost::filesystem::exists(fss.str())){ nextFrameSnapshotFilename=fss.str(); break; }
+		}
+		LOG_INFO("Will save snapshot to "<<nextFrameSnapshotFilename);
+	}
+#endif
+#if 0
+	else if( e->key()==Qt::Key_Plus ){
+			cut_plane = std::min(1.0, cut_plane + std::pow(10.0,(double)cut_plane_delta));
+			static_cast<YadeCamera*>(camera())->setCuttingDistance(cut_plane);
+			displayMessage("Cut plane: "+boost::lexical_cast<std::string>(cut_plane));
+	}else if( e->key()==Qt::Key_Minus ){
+			cut_plane = std::max(0.0, cut_plane - std::pow(10.0,(double)cut_plane_delta));
+			static_cast<YadeCamera*>(camera())->setCuttingDistance(cut_plane);
+			displayMessage("Cut plane: "+boost::lexical_cast<std::string>(cut_plane));
+	}else if( e->key()==Qt::Key_Slash ){
+			cut_plane_delta -= 1;
+			displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast<std::string>(cut_plane_delta));
+	}else if( e->key()==Qt::Key_Asterisk ){
+			cut_plane_delta = std::min(1+cut_plane_delta,-1);
+			displayMessage("Cut plane increment: 1e"+(cut_plane_delta>0?std::string("+"):std::string(""))+boost::lexical_cast<std::string>(cut_plane_delta));
+	}
+#endif
+
+	else if(e->key()!=Qt::Key_Escape && e->key()!=Qt::Key_Space) QGLViewer::keyPressEvent(e);
+	updateGL();
+}
+/* Center the scene such that periodic cell is contained in the view */
+void GLViewer::centerPeriodic(){
+	Scene* scene=Omega::instance().getScene().get();
+	assert(scene->isPeriodic);
+	Vector3r center=.5*scene->cell->getSize();
+	Vector3r halfSize=.5*scene->cell->getSize();
+	float radius=std::max(halfSize[0],std::max(halfSize[1],halfSize[2]));
+	LOG_DEBUG("Periodic scene center="<<center<<", halfSize="<<halfSize<<", radius="<<radius);
+	setSceneCenter(qglviewer::Vec(center[0],center[1],center[2]));
+	setSceneRadius(radius*1.5);
+	showEntireScene();
+}
+
+/* Calculate medians for x, y and z coordinates of all bodies;
+ *then set scene center to median position and scene radius to 2*inter-quartile distance.
+ *
+ * This function eliminates the effect of lonely bodies that went nuts and enlarge
+ * the scene's Aabb in such a way that fitting the scene to see the Aabb makes the
+ * "central" (where most bodies is) part very small or even invisible.
+ */
+void GLViewer::centerMedianQuartile(){
+	Scene* scene=Omega::instance().getScene().get();
+	if(scene->isPeriodic){ centerPeriodic(); return; }
+	long nBodies=scene->bodies->size();
+	if(nBodies<4) {
+		LOG_DEBUG("Less than 4 bodies, median makes no sense; calling centerScene() instead.");
+		return centerScene();
+	}
+	std::vector<Real> coords[3];
+	for(int i=0;i<3;i++)coords[i].reserve(nBodies);
+	FOREACH(shared_ptr<Body> b, *scene->bodies){
+		if(!b) continue;
+		for(int i=0; i<3; i++) coords[i].push_back(b->state->pos[i]);
+	}
+	Vector3r median,interQuart;
+	for(int i=0;i<3;i++){
+		sort(coords[i].begin(),coords[i].end());
+		median[i]=*(coords[i].begin()+nBodies/2);
+		interQuart[i]=*(coords[i].begin()+3*nBodies/4)-*(coords[i].begin()+nBodies/4);
+	}
+	LOG_DEBUG("Median position is"<<median<<", inter-quartile distance is "<<interQuart);
+	setSceneCenter(qglviewer::Vec(median[0],median[1],median[2]));
+	setSceneRadius(2*(interQuart[0]+interQuart[1]+interQuart[2])/3.);
+	showEntireScene();
+}
+
+void GLViewer::centerScene(){
+	Scene* rb=Omega::instance().getScene().get();
+	if (!rb) return;
+	if(rb->isPeriodic){ centerPeriodic(); return; }
+	LOG_INFO("Select with shift, press 'm' to move.");
+	Vector3r min,max;	
+	if(not(rb->bound)){ rb->updateBound();}
+	
+	min=rb->bound->min; max=rb->bound->max;
+	bool hasNan=(isnan(min[0])||isnan(min[1])||isnan(min[2])||isnan(max[0])||isnan(max[1])||isnan(max[2]));
+	Real minDim=std::min(max[0]-min[0],std::min(max[1]-min[1],max[2]-min[2]));
+	if(minDim<=0 || hasNan){
+		// Aabb is not yet calculated...
+		LOG_DEBUG("scene's bound not yet calculated or has zero or nan dimension(s), attempt get that from bodies' positions.");
+		Real inf=std::numeric_limits<Real>::infinity();
+		min=Vector3r(inf,inf,inf); max=Vector3r(-inf,-inf,-inf);
+		FOREACH(const shared_ptr<Body>& b, *rb->bodies){
+			if(!b) continue;
+			max=max.cwiseMax(b->state->pos);
+			min=min.cwiseMin(b->state->pos);
+		}
+		if(isinf(min[0])||isinf(min[1])||isinf(min[2])||isinf(max[0])||isinf(max[1])||isinf(max[2])){ LOG_DEBUG("No min/max computed from bodies either, setting cube (-1,-1,-1)×(1,1,1)"); min=-Vector3r::Ones(); max=Vector3r::Ones(); }
+	} else {LOG_DEBUG("Using scene's Aabb");}
+
+	LOG_DEBUG("Got scene box min="<<min<<" and max="<<max);
+	Vector3r center = (max+min)*0.5;
+	Vector3r halfSize = (max-min)*0.5;
+	float radius=std::max(halfSize[0],std::max(halfSize[1],halfSize[2])); if(radius<=0) radius=1;
+	LOG_DEBUG("Scene center="<<center<<", halfSize="<<halfSize<<", radius="<<radius);
+	setSceneCenter(qglviewer::Vec(center[0],center[1],center[2]));
+	setSceneRadius(radius*1.5);
+	showEntireScene();
+}
+
+// new object selected.
+// set frame coordinates, and isDynamic=false;
+void GLViewer::postSelection(const QPoint& point) 
+{
+	LOG_DEBUG("Selection is "<<selectedName());
+	int selection = selectedName();
+	if(selection<0){
+		if (last>=0) {
+			Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1; Omega::instance().getScene()->selectedBody = -1;}
+		if(isMoving){
+			displayMessage("Moving finished"); mouseMovesCamera(); isMoving=false;
+			Omega::instance().getScene()->selectedBody = -1;
+		}
+		return;
+	}
+	if(selection>=0 && (*(Omega::instance().getScene()->bodies)).exists(selection)){
+		resetManipulation();
+		if (last>=0) {Body::byId(Body::id_t(last))->state->blockedDOFs=initBlocked; last=-1;}
+		if(Body::byId(Body::id_t(selection))->isClumpMember()){ // select clump (invisible) instead of its member
+			LOG_DEBUG("Clump member #"<<selection<<" selected, selecting clump instead.");
+			selection=Body::byId(Body::id_t(selection))->clumpId;			
+		}
+		
+		setSelectedName(selection);
+		LOG_DEBUG("New selection "<<selection);
+		displayMessage("Selected body #"+boost::lexical_cast<string>(selection)+(Body::byId(selection)->isClump()?" (clump)":""));
+		Omega::instance().getScene()->selectedBody = selection;
+			PyGILState_STATE gstate;
+			gstate = PyGILState_Ensure();
+      boost::python::object main=boost::python::import("__main__");
+      boost::python::object global=main.attr("__dict__");
+				// the try/catch block must be properly nested inside PyGILState_Ensure and PyGILState_Release
+				try{
+          boost::python::eval(string("onBodySelect("+boost::lexical_cast<string>(selection)+")").c_str(),global,global);
+				} catch (boost::python::error_already_set const &) {
+					LOG_DEBUG("unable to call onBodySelect. Not defined?");
+				}
+			PyGILState_Release(gstate);
+			// see https://svn.boost.org/trac/boost/ticket/2781 for exception handling
+	}
+}
+
+// maybe new object will be selected.
+// if so, then set isDynamic of previous selection, to old value
+void GLViewer::endSelection(const QPoint &point){
+	manipulatedClipPlane=-1;
+	QGLViewer::endSelection(point);
+}
+
+string GLViewer::getRealTimeString(){
+	ostringstream oss;
+  boost::posix_time::time_duration t=Omega::instance().getRealTime_duration();
+	unsigned d=t.hours()/24,h=t.hours()%24,m=t.minutes(),s=t.seconds();
+	oss<<"clock ";
+	if(d>0) oss<<d<<"days "<<_W2<<h<<":"<<_W2<<m<<":"<<_W2<<s;
+	else if(h>0) oss<<_W2<<h<<":"<<_W2<<m<<":"<<_W2<<s;
+	else oss<<_W2<<m<<":"<<_W2<<s;
+	return oss.str();
+}
+#undef _W2
+#undef _W3
+
+// cut&paste from QGLViewer::domElement documentation
+QDomElement GLViewer::domElement(const QString& name, QDomDocument& document) const{
+	QDomElement de=document.createElement("grid");
+	string val; if(drawGrid & 1) val+="x"; if(drawGrid & 2)val+="y"; if(drawGrid & 4)val+="z";
+	de.setAttribute("normals",val.c_str());
+	QDomElement de2=document.createElement("timeDisplay"); de2.setAttribute("mask",timeDispMask);
+	QDomElement res=QGLViewer::domElement(name,document);
+	res.appendChild(de);
+	res.appendChild(de2);
+	return res;
+}
+
+// cut&paste from QGLViewer::initFromDomElement documentation
+void GLViewer::initFromDOMElement(const QDomElement& element){
+	QGLViewer::initFromDOMElement(element);
+	QDomElement child=element.firstChild().toElement();
+	while (!child.isNull()){
+		if (child.tagName()=="gridXYZ" && child.hasAttribute("normals")){
+			string val=child.attribute("normals").toLower().toStdString();
+			drawGrid=0;
+			if(val.find("x")!=string::npos) drawGrid+=1; if(val.find("y")!=string::npos)drawGrid+=2; if(val.find("z")!=string::npos)drawGrid+=4;
+		}
+		if(child.tagName()=="timeDisplay" && child.hasAttribute("mask")) timeDispMask=atoi(child.attribute("mask").toLatin1());
+		child = child.nextSibling().toElement();
+	}
+}
+
+boost::posix_time::ptime GLViewer::getLastUserEvent(){return last_user_event;};
+
+#if QGLVIEWER_VERSION>=0x020603
+qreal YadeCamera::zNear() const
+#else
+float YadeCamera::zNear() const
+#endif
+{
+  float z = distanceToSceneCenter() - zClippingCoefficient()*sceneRadius()*(1.f-2*cuttingDistance);
+
+  // Prevents negative or null zNear values.
+  const float zMin = zNearCoefficient() * zClippingCoefficient() * sceneRadius();
+  if (z < zMin)
+/*    switch (type())
+      {
+      case Camera::PERSPECTIVE  :*/ z = zMin; /*break;
+      case Camera::ORTHOGRAPHIC : z = 0.0;  break;
+      }*/
+  return z;
+}
+
+

=== added file 'gui/qt5/GLViewer.hpp'
--- gui/qt5/GLViewer.hpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewer.hpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,163 @@
+// Copyright (C) 2004 by Olivier Galizzi, Janek Kozicki                  *
+// © 2008 Václav Šmilauer
+#pragma once
+
+#ifndef Q_MOC_RUN 
+#include<core/Omega.hpp>
+#include<pkg/common/OpenGLRenderer.hpp>
+#include<pkg/common/PeriodicEngines.hpp>
+#include<boost/date_time/posix_time/posix_time.hpp>
+#endif 
+
+#include <QGLViewer/qglviewer.h>
+#include <QGLViewer/manipulatedFrame.h>
+#include <QGLViewer/constraint.h>
+
+using std::setw;
+using std::setfill;
+using std::setprecision;
+
+/*! Class handling user interaction with the openGL rendering of simulation.
+ *
+ * Clipping planes:
+ * ================
+ *
+ * Clipping plane is manipulated after hitting F1, F2, .... To end the manipulation, press Escape.
+ *
+ * Keystrokes during clipping plane manipulation:
+ * * space activates/deactives the clipping plane
+ * * x,y,z aligns the plane with yz, xz, xy planes
+ * * left-double-click aligns the plane with world coordinates system (canonical planes + 45˚ interpositions)
+ * * 1,2,... will align the current plane with #1, #2, ... (same orientation)
+ * * r reverses the plane (normal*=-1)a
+ *
+ * Keystrokes that work regardless of whether a clipping plane is being manipulated:
+ * * Alt-1,Alt-2,... adds/removes the respective plane to bound group:
+ * 	mutual positions+orientations of planes in the group are maintained when one of those planes is manipulated
+ *
+ * Clip plane number is 3; change YADE_RENDERER_NUM_CLIP_PLANE, complete switches "|| ..." in keyPressEvent
+ * and recompile to have more.
+ */
+class GLViewer : public QGLViewer
+{	
+	Q_OBJECT 
+	
+	friend class QGLThread;
+	protected:
+		shared_ptr<OpenGLRenderer> renderer;
+
+	private :
+
+		bool			isMoving;
+		bool			wasDynamic;
+		float			cut_plane;
+		int			cut_plane_delta;
+		bool			gridSubdivide;
+		long			last;
+		int manipulatedClipPlane;
+		set<int> boundClipPlanes;
+		shared_ptr<qglviewer::LocalConstraint> xyPlaneConstraint;
+		string strBoundGroup(){string ret;FOREACH(int i, boundClipPlanes) ret+=" "+boost::lexical_cast<string>(i+1);return ret;}
+		boost::posix_time::ptime last_user_event;
+
+     public:
+		//virtual void updateGL(void);
+
+		const int viewId;
+
+		void centerMedianQuartile();
+		int 	drawGrid;
+		bool 	drawScale;
+		int timeDispMask;
+		enum{TIME_REAL=1,TIME_VIRT=2,TIME_ITER=4};
+
+		GLViewer(int viewId, const shared_ptr<OpenGLRenderer>& renderer, QGLWidget* shareWidget=0);
+		virtual ~GLViewer();
+		#if 0
+			virtual void paintGL();
+		#endif
+		virtual void draw();
+		virtual void drawWithNames();
+		void displayMessage(const std::string& s){ QGLViewer::displayMessage(QString(s.c_str()));}
+		void centerScene();
+		void centerPeriodic();
+		void mouseMovesCamera();
+		void mouseMovesManipulatedFrame(qglviewer::Constraint* c=NULL);
+		void resetManipulation();
+		bool isManipulating();
+		void startClipPlaneManipulation(int planeNo);
+		//! get QGLViewer state as string (XML); QGLViewer normally only supports saving state to file.
+		string getState();
+		//! set QGLViewer state from string (XML); QGLVIewer normally only supports loading state from file.
+		void setState(string);
+		//! Load display parameters (QGLViewer and OpenGLRenderer) from Scene::dispParams[n] and use them
+		void useDisplayParameters(size_t n);
+		//! Save display parameters (QGOViewer and OpenGLRenderer) to Scene::dispParams[n]
+		void saveDisplayParameters(size_t n);
+		//! Get radius of the part of scene that fits the current view
+		float displayedSceneRadius();
+		//! Get center of the part of scene that fits the current view
+		qglviewer::Vec displayedSceneCenter();
+
+		//! Adds our attributes to the QGLViewer state that can be saved
+		QDomElement domElement(const QString& name, QDomDocument& document) const;
+		//! Adds our attributes to the QGLViewer state that can be restored
+		void initFromDOMElement(const QDomElement& element);
+
+		// if defined, snapshot will be saved to this file right after being drawn and the string will be reset.
+		// this way the caller will be notified of the frame being saved successfully.
+		string nextFrameSnapshotFilename;
+		#ifdef YADE_GL2PS
+			// output stream for gl2ps; initialized as needed
+			FILE* gl2psStream;
+		#endif
+
+		boost::posix_time::ptime getLastUserEvent();
+
+
+		DECLARE_LOGGER;
+	protected :
+		virtual void keyPressEvent(QKeyEvent *e);
+		virtual void postDraw();
+		// overridden in the player that doesn't get time from system clock but from the db
+		virtual string getRealTimeString();
+		virtual void closeEvent(QCloseEvent *e);
+		virtual void postSelection(const QPoint& point);
+		virtual void endSelection(const QPoint &point);
+		virtual void mouseDoubleClickEvent(QMouseEvent *e);
+		virtual void wheelEvent(QWheelEvent* e);
+		virtual void mouseMoveEvent(QMouseEvent *e);
+		virtual void mousePressEvent(QMouseEvent *e);
+};
+
+/*! Get unconditional lock on a GL view.
+
+Use if you need to manipulate GL context in some way.
+The ctor doesn't return until the lock has been acquired
+and the lock is released when the GLLock object is desctructed;
+*/
+class GLLock: public boost::try_mutex::scoped_lock{
+	GLViewer* glv;
+	public:
+		GLLock(GLViewer* _glv);
+		~GLLock(); 
+};
+
+
+class YadeCamera : public qglviewer::Camera
+{	
+	Q_OBJECT 
+	private:
+		float cuttingDistance;
+        public :
+		YadeCamera():cuttingDistance(0){};
+#if QGLVIEWER_VERSION>=0x020603
+		virtual qreal zNear() const;
+#else
+		virtual float zNear() const;
+#endif
+		virtual void setCuttingDistance(float s){cuttingDistance=s;};
+};
+
+
+

=== added file 'gui/qt5/GLViewerDisplay.cpp'
--- gui/qt5/GLViewerDisplay.cpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewerDisplay.cpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,287 @@
+/*************************************************************************
+*  Copyright (C) 2004 by Olivier Galizzi                                 *
+*  olivier.galizzi@xxxxxxx                                               *
+*  Copyright (C) 2005 by Janek Kozicki                                   *
+*  cosurgi@xxxxxxxxxx                                                    *
+*                                                                        *
+*  This program is free software; it is licensed under the terms of the  *
+*  GNU General Public License v2 or later. See file LICENSE for details. *
+*************************************************************************/
+
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+
+#include<lib/opengl/OpenGLWrapper.hpp>
+#include<core/Body.hpp>
+#include<core/Scene.hpp>
+#include<core/Interaction.hpp>
+#include<core/DisplayParameters.hpp>
+#include<boost/algorithm/string.hpp>
+#include<sstream>
+#include<iomanip>
+#include<boost/algorithm/string/case_conv.hpp>
+#include<lib/serialization/ObjectIO.hpp>
+#include<lib/pyutil/gil.hpp>
+
+
+#include<QtGui/qevent.h>
+
+#ifdef YADE_GL2PS
+	#include<gl2ps.h>
+#endif
+
+void GLViewer::useDisplayParameters(size_t n){
+	LOG_DEBUG("Loading display parameters from #"<<n);
+	vector<shared_ptr<DisplayParameters> >& dispParams=Omega::instance().getScene()->dispParams;
+	if(dispParams.size()<=(size_t)n){ throw std::invalid_argument(("Display parameters #"+boost::lexical_cast<string>(n)+" don't exist (number of entries "+boost::lexical_cast<string>(dispParams.size())+")").c_str());; return;}
+	const shared_ptr<DisplayParameters>& dp=dispParams[n];
+	string val;
+	if(dp->getValue("OpenGLRenderer",val)){ istringstream oglre(val);
+		yade::ObjectIO::load<decltype(renderer),boost::archive::xml_iarchive>(oglre,"renderer",renderer);
+	}
+	else { LOG_WARN("OpenGLRenderer configuration not found in display parameters, skipped.");}
+	if(dp->getValue("GLViewer",val)){ GLViewer::setState(val); displayMessage("Loaded view configuration #"+boost::lexical_cast<string>(n)); }
+	else { LOG_WARN("GLViewer configuration not found in display parameters, skipped."); }
+}
+
+void GLViewer::saveDisplayParameters(size_t n){
+	LOG_DEBUG("Saving display parameters to #"<<n);
+	vector<shared_ptr<DisplayParameters> >& dispParams=Omega::instance().getScene()->dispParams;
+	if(dispParams.size()<=n){while(dispParams.size()<=n) dispParams.push_back(shared_ptr<DisplayParameters>(new DisplayParameters));} assert(n<dispParams.size());
+	shared_ptr<DisplayParameters>& dp=dispParams[n];
+	ostringstream oglre;
+	yade::ObjectIO::save<decltype(renderer),boost::archive::xml_oarchive>(oglre,"renderer",renderer);
+	dp->setValue("OpenGLRenderer",oglre.str());
+	dp->setValue("GLViewer",GLViewer::getState());
+	displayMessage("Saved view configuration ot #"+boost::lexical_cast<string>(n));
+}
+
+void GLViewer::draw()
+{
+#ifdef YADE_GL2PS
+	if(!nextFrameSnapshotFilename.empty() && boost::algorithm::ends_with(nextFrameSnapshotFilename,".pdf")){
+		gl2psStream=fopen(nextFrameSnapshotFilename.c_str(),"wb");
+		if(!gl2psStream){ int err=errno; throw runtime_error(string("Error opening file ")+nextFrameSnapshotFilename+": "+strerror(err)); }
+		LOG_DEBUG("Start saving snapshot to "<<nextFrameSnapshotFilename);
+		size_t nBodies=Omega::instance().getScene()->bodies->size();
+		int sortAlgo=(nBodies<100 ? GL2PS_BSP_SORT : GL2PS_SIMPLE_SORT);
+		gl2psBeginPage(/*const char *title*/"Some title", /*const char *producer*/ "Yade",
+			/*GLint viewport[4]*/ NULL,
+			/*GLint format*/ GL2PS_PDF, /*GLint sort*/ sortAlgo, /*GLint options*/GL2PS_SIMPLE_LINE_OFFSET|GL2PS_USE_CURRENT_VIEWPORT|GL2PS_TIGHT_BOUNDING_BOX|GL2PS_COMPRESS|GL2PS_OCCLUSION_CULL|GL2PS_NO_BLENDING, 
+			/*GLint colormode*/ GL_RGBA, /*GLint colorsize*/0, 
+			/*GL2PSrgba *colortable*/NULL, 
+			/*GLint nr*/0, /*GLint ng*/0, /*GLint nb*/0, 
+			/*GLint buffersize*/4096*4096 /* 16MB */, /*FILE *stream*/ gl2psStream,
+			/*const char *filename*/NULL);
+	}
+#endif
+
+	qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]);
+	if(Omega::instance().getScene()){
+		const shared_ptr<Scene>& scene=Omega::instance().getScene();
+		int selection = selectedName();
+		if(selection!=-1 && (*(Omega::instance().getScene()->bodies)).exists(selection) && isMoving){
+			static Real lastTimeMoved(0);
+#if QGLVIEWER_VERSION>=0x020603
+			qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#else
+			float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#endif
+			if(last == selection) // delay by one redraw, so the body will not jump into 0,0,0 coords
+			{
+				Quaternionr& q = (*(Omega::instance().getScene()->bodies))[selection]->state->ori;
+				Vector3r&    v = (*(Omega::instance().getScene()->bodies))[selection]->state->pos;
+				Vector3r&    vel = (*(Omega::instance().getScene()->bodies))[selection]->state->vel;
+				Vector3r&    angVel = (*(Omega::instance().getScene()->bodies))[selection]->state->angVel;
+				angVel=Vector3r::Zero(); 
+				Real dt=(scene->time-lastTimeMoved); lastTimeMoved=scene->time;
+				if (dt!=0) { vel[0]=-(v[0]-v0)/dt; vel[1]=-(v[1]-v1)/dt; vel[2]=-(v[2]-v2)/dt;}
+				else vel[0]=vel[1]=vel[2]=0;
+				//FIXME: should update spin like velocity above, when the body is rotated:
+				double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3);	q.x()=q0;q.y()=q1;q.z()=q2;q.w()=q3;
+			}
+			(*(Omega::instance().getScene()->bodies))[selection]->userForcedDisplacementRedrawHook();	
+		}
+		if(manipulatedClipPlane>=0){
+			assert(manipulatedClipPlane<renderer->numClipPlanes);
+#if QGLVIEWER_VERSION>=0x020603
+			qreal v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#else
+			float v0,v1,v2; manipulatedFrame()->getPosition(v0,v1,v2);
+#endif
+			double q0,q1,q2,q3; manipulatedFrame()->getOrientation(q0,q1,q2,q3);
+			Se3r newSe3(Vector3r(v0,v1,v2),Quaternionr(q0,q1,q2,q3)); newSe3.orientation.normalize();
+			const Se3r& oldSe3=renderer->clipPlaneSe3[manipulatedClipPlane];
+			FOREACH(int planeId, boundClipPlanes){
+				if(planeId>=renderer->numClipPlanes || !renderer->clipPlaneActive[planeId] || planeId==manipulatedClipPlane) continue;
+				Se3r& boundSe3=renderer->clipPlaneSe3[planeId];
+				Quaternionr relOrient=oldSe3.orientation.conjugate()*boundSe3.orientation; relOrient.normalize();
+				Vector3r relPos=oldSe3.orientation.conjugate()*(boundSe3.position-oldSe3.position);
+				boundSe3.position=newSe3.position+newSe3.orientation*relPos;
+				boundSe3.orientation=newSe3.orientation*relOrient;
+				boundSe3.orientation.normalize();
+			}
+			renderer->clipPlaneSe3[manipulatedClipPlane]=newSe3;
+		}
+		scene->renderer=renderer;
+		renderer->render(scene, selectedName());
+	}
+}
+
+void GLViewer::drawWithNames(){
+	qglviewer::Vec vd=camera()->viewDirection(); renderer->viewDirection=Vector3r(vd[0],vd[1],vd[2]);
+	const shared_ptr<Scene> scene(Omega::instance().getScene());
+	scene->renderer=renderer;
+	renderer->scene=scene;
+	renderer->renderShape();
+}
+
+
+qglviewer::Vec GLViewer::displayedSceneCenter(){
+	return camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2 /* pixels */ ,height()/2 /* pixels */, /*middle between near plane and far plane*/ .5));
+}
+
+float GLViewer::displayedSceneRadius(){
+	return (camera()->unprojectedCoordinatesOf(qglviewer::Vec(width()/2,height()/2,.5))-camera()->unprojectedCoordinatesOf(qglviewer::Vec(0,0,.5))).norm();
+}
+
+void GLViewer::postDraw(){
+	Real wholeDiameter=QGLViewer::camera()->sceneRadius()*2;
+
+	renderer->viewInfo.sceneRadius=QGLViewer::camera()->sceneRadius();
+	qglviewer::Vec c=QGLViewer::camera()->sceneCenter();
+	renderer->viewInfo.sceneCenter=Vector3r(c[0],c[1],c[2]);
+
+	Real dispDiameter=min(wholeDiameter,max((Real)displayedSceneRadius()*2,wholeDiameter/1e3)); // limit to avoid drawing 1e5 lines with big zoom level
+	//qglviewer::Vec center=QGLViewer::camera()->sceneCenter();
+	Real gridStep=pow(10,(floor(log10(dispDiameter)-.7)));
+	Real scaleStep=pow(10,(floor(log10(displayedSceneRadius()*2)-.7))); // unconstrained
+	int nSegments=((int)(wholeDiameter/gridStep))+1;
+	Real realSize=nSegments*gridStep;
+	//LOG_TRACE("nSegments="<<nSegments<<",gridStep="<<gridStep<<",realSize="<<realSize);
+	glPushMatrix();
+
+	nSegments *= 2; // there's an error in QGLViewer::drawGrid(), so we need to mitigate it by '* 2'
+	// XYZ grids
+	glLineWidth(.5);
+	if(drawGrid & 1) {glColor3f(0.6,0.3,0.3); glPushMatrix(); glRotated(90.,0.,1.,0.); QGLViewer::drawGrid(realSize,nSegments); glPopMatrix();}
+	if(drawGrid & 2) {glColor3f(0.3,0.6,0.3); glPushMatrix(); glRotated(90.,1.,0.,0.); QGLViewer::drawGrid(realSize,nSegments); glPopMatrix();}
+	if(drawGrid & 4) {glColor3f(0.3,0.3,0.6); glPushMatrix(); /*glRotated(90.,0.,1.,0.);*/ QGLViewer::drawGrid(realSize,nSegments); glPopMatrix();}
+	if(gridSubdivide){
+		if(drawGrid & 1) {glColor3f(0.4,0.1,0.1); glPushMatrix(); glRotated(90.,0.,1.,0.); QGLViewer::drawGrid(realSize,nSegments*10); glPopMatrix();}
+		if(drawGrid & 2) {glColor3f(0.1,0.4,0.1); glPushMatrix(); glRotated(90.,1.,0.,0.); QGLViewer::drawGrid(realSize,nSegments*10); glPopMatrix();}
+		if(drawGrid & 4) {glColor3f(0.1,0.1,0.4); glPushMatrix(); /*glRotated(90.,0.,1.,0.);*/ QGLViewer::drawGrid(realSize,nSegments*10); glPopMatrix();}
+	}
+	
+	// scale
+	if(drawScale){
+		Real segmentSize=scaleStep;
+		qglviewer::Vec screenDxDy[3]; // dx,dy for x,y,z scale segments
+		int extremalDxDy[2]={0,0};
+		for(int axis=0; axis<3; axis++){
+			qglviewer::Vec delta(0,0,0); delta[axis]=segmentSize;
+			qglviewer::Vec center=displayedSceneCenter();
+			screenDxDy[axis]=camera()->projectedCoordinatesOf(center+delta)-camera()->projectedCoordinatesOf(center);
+			for(int xy=0;xy<2;xy++)extremalDxDy[xy]=(axis>0 ? min(extremalDxDy[xy],(int)screenDxDy[axis][xy]) : screenDxDy[axis][xy]);
+		}
+		//LOG_DEBUG("Screen offsets for axes: "<<" x("<<screenDxDy[0][0]<<","<<screenDxDy[0][1]<<") y("<<screenDxDy[1][0]<<","<<screenDxDy[1][1]<<") z("<<screenDxDy[2][0]<<","<<screenDxDy[2][1]<<")");
+		int margin=10; // screen pixels
+		int scaleCenter[2]; scaleCenter[0]=std::abs(extremalDxDy[0])+margin; scaleCenter[1]=std::abs(extremalDxDy[1])+margin;
+		//LOG_DEBUG("Center of scale "<<scaleCenter[0]<<","<<scaleCenter[1]);
+		//displayMessage(QString().sprintf("displayed scene radius %g",displayedSceneRadius()));
+		startScreenCoordinatesSystem();
+			glDisable(GL_LIGHTING);
+			glDisable(GL_DEPTH_TEST);
+			glLineWidth(3.0);
+			for(int axis=0; axis<3; axis++){
+				Vector3r color(.4,.4,.4); color[axis]=.9;
+				glColor3v(color);
+				glBegin(GL_LINES);
+				glVertex2f(scaleCenter[0],scaleCenter[1]);
+				glVertex2f(scaleCenter[0]+screenDxDy[axis][0],scaleCenter[1]+screenDxDy[axis][1]);
+				glEnd();
+			}
+			glLineWidth(1.);
+			glEnable(GL_DEPTH_TEST);
+			QGLViewer::drawText(scaleCenter[0],scaleCenter[1],QString().sprintf("%.3g",(double)scaleStep));
+		stopScreenCoordinatesSystem();
+	}
+
+	// cutting planes (should be moved to OpenGLRenderer perhaps?)
+	// only painted if one of those is being manipulated
+	if(manipulatedClipPlane>=0){
+		for(int planeId=0; planeId<renderer->numClipPlanes; planeId++){
+			if(!renderer->clipPlaneActive[planeId] && planeId!=manipulatedClipPlane) continue;
+			glPushMatrix();
+				const Se3r& se3=renderer->clipPlaneSe3[planeId];
+				AngleAxisr aa(se3.orientation);	
+				glTranslatef(se3.position[0],se3.position[1],se3.position[2]);
+				glRotated(aa.angle()*Mathr::RAD_TO_DEG,aa.axis()[0],aa.axis()[1],aa.axis()[2]);
+				Real cff=1;
+				if(!renderer->clipPlaneActive[planeId]) cff=.4;
+				glColor3f(max((Real)0.,cff*cos(planeId)),max((Real)0.,cff*sin(planeId)),planeId==manipulatedClipPlane); // variable colors
+				QGLViewer::drawGrid(realSize,2*nSegments);
+				drawArrow(wholeDiameter/6);
+			glPopMatrix();
+		}
+	}
+	
+	Scene* rb=Omega::instance().getScene().get();
+	#define _W3 setw(3)<<setfill('0')
+	#define _W2 setw(2)<<setfill('0')
+	if(timeDispMask!=0){
+		const int lineHt=13;
+		unsigned x=10,y=height()-3-lineHt*2;
+		glColor3v(Vector3r(1,1,1));
+		if(timeDispMask & GLViewer::TIME_VIRT){
+			ostringstream oss;
+			const Real& t=Omega::instance().getScene()->time;
+			unsigned min=((unsigned)t/60),sec=(((unsigned)t)%60),msec=((unsigned)(1e3*t))%1000,usec=((unsigned long)(1e6*t))%1000,nsec=((unsigned long)(1e9*t))%1000;
+			if(min>0) oss<<_W2<<min<<":"<<_W2<<sec<<"."<<_W3<<msec<<"m"<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+			else if (sec>0) oss<<_W2<<sec<<"."<<_W3<<msec<<"m"<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+			else if (msec>0) oss<<_W3<<msec<<"m"<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+			else if (usec>0) oss<<_W3<<usec<<"u"<<_W3<<nsec<<"n";
+			else oss<<_W3<<nsec<<"ns";
+			QGLViewer::drawText(x,y,oss.str().c_str());
+			y-=lineHt;
+		}
+		glColor3v(Vector3r(0,.5,.5));
+		if(timeDispMask & GLViewer::TIME_REAL){
+			QGLViewer::drawText(x,y,getRealTimeString().c_str() /* virtual, since player gets that from db */);
+			y-=lineHt;
+		}
+		if(timeDispMask & GLViewer::TIME_ITER){
+			ostringstream oss;
+			oss<<"#"<<rb->iter;
+			if(rb->stopAtIter>rb->iter) oss<<" ("<<setiosflags(ios::fixed)<<setw(3)<<setprecision(1)<<setfill('0')<<(100.*rb->iter)/rb->stopAtIter<<"%)";
+			QGLViewer::drawText(x,y,oss.str().c_str());
+			y-=lineHt;
+		}
+		if(drawGrid){
+			glColor3v(Vector3r(1,1,0));
+			ostringstream oss;
+			oss<<"grid: "<<setprecision(4)<<gridStep;
+			if(gridSubdivide) oss<<" (minor "<<setprecision(3)<<gridStep*.1<<")";
+			QGLViewer::drawText(x,y,oss.str().c_str());
+			y-=lineHt;
+		}
+	}
+	QGLViewer::postDraw();
+	if(!nextFrameSnapshotFilename.empty()){
+		#ifdef YADE_GL2PS
+			if(boost::algorithm::ends_with(nextFrameSnapshotFilename,".pdf")){
+				gl2psEndPage();
+				LOG_DEBUG("Finished saving snapshot to "<<nextFrameSnapshotFilename);
+				fclose(gl2psStream);
+			} else
+	#endif
+		{
+			// save the snapshot
+			saveSnapshot(QString(nextFrameSnapshotFilename.c_str()),/*overwrite*/ true);
+		}
+		// notify the caller that it is done already (probably not an atomic op :-|, though)
+		nextFrameSnapshotFilename.clear();
+	}
+}
+
+

=== added file 'gui/qt5/GLViewerMouse.cpp'
--- gui/qt5/GLViewerMouse.cpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/GLViewerMouse.cpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,113 @@
+/*************************************************************************
+*  Copyright (C) 2004 by Olivier Galizzi                                 *
+*  olivier.galizzi@xxxxxxx                                               *
+*  Copyright (C) 2005 by Janek Kozicki                                   *
+*  cosurgi@xxxxxxxxxx                                                    *
+*                                                                        *
+*  This program is free software; it is licensed under the terms of the  *
+*  GNU General Public License v2 or later. See file LICENSE for details. *
+*************************************************************************/
+
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+
+#include<lib/opengl/OpenGLWrapper.hpp>
+#include<core/Body.hpp>
+#include<core/Scene.hpp>
+#include<core/Interaction.hpp>
+#include<core/DisplayParameters.hpp>
+#include<boost/algorithm/string.hpp>
+#include<sstream>
+#include<iomanip>
+#include<boost/algorithm/string/case_conv.hpp>
+#include<lib/serialization/ObjectIO.hpp>
+#include<lib/pyutil/gil.hpp>
+#include<QGLViewer/manipulatedCameraFrame.h>
+
+#include<QtGui/qevent.h>
+
+#ifdef YADE_GL2PS
+	#include<gl2ps.h>
+#endif
+
+void GLViewer::mouseMovesCamera(){
+  setWheelBinding(Qt::ShiftModifier , FRAME, ZOOM);
+  setWheelBinding(Qt::NoModifier, CAMERA, ZOOM);
+
+#if QGLVIEWER_VERSION>=0x020500
+  setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ZOOM);
+  setMouseBinding(Qt::ShiftModifier, Qt::MidButton, FRAME, TRANSLATE);
+  setMouseBinding(Qt::ShiftModifier, Qt::RightButton, FRAME, ROTATE);
+  
+  setMouseBinding(Qt::NoModifier, Qt::MidButton, CAMERA, ZOOM);
+  setMouseBinding(Qt::NoModifier, Qt::LeftButton, CAMERA, ROTATE);
+  setMouseBinding(Qt::NoModifier, Qt::RightButton, CAMERA, TRANSLATE);
+#else
+  setMouseBinding(Qt::SHIFT + Qt::LeftButton, SELECT);
+  setMouseBinding(Qt::SHIFT + Qt::LeftButton + Qt::RightButton, FRAME, ZOOM);
+  setMouseBinding(Qt::SHIFT + Qt::MidButton, FRAME, TRANSLATE);
+  setMouseBinding(Qt::SHIFT + Qt::RightButton, FRAME, ROTATE);
+  
+  setMouseBinding(Qt::LeftButton + Qt::RightButton, CAMERA, ZOOM);
+  setMouseBinding(Qt::MidButton, CAMERA, ZOOM);
+  setMouseBinding(Qt::LeftButton, CAMERA, ROTATE);
+  setMouseBinding(Qt::RightButton, CAMERA, TRANSLATE);
+  camera()->frame()->setWheelSensitivity(-1.0f);
+#endif
+};
+
+void GLViewer::mouseMovesManipulatedFrame(qglviewer::Constraint* c){
+	setMouseBinding(Qt::LeftButton + Qt::RightButton, FRAME, ZOOM);
+	setMouseBinding(Qt::MidButton, FRAME, ZOOM);
+	setMouseBinding(Qt::LeftButton, FRAME, ROTATE);
+	setMouseBinding(Qt::RightButton, FRAME, TRANSLATE);
+	setWheelBinding(Qt::NoModifier , FRAME, ZOOM);
+	manipulatedFrame()->setConstraint(c);
+}
+
+
+void GLViewer::mouseMoveEvent(QMouseEvent *e){
+	last_user_event = boost::posix_time::second_clock::local_time();
+	QGLViewer::mouseMoveEvent(e);
+}
+
+void GLViewer::mousePressEvent(QMouseEvent *e){
+	last_user_event = boost::posix_time::second_clock::local_time();
+	QGLViewer::mousePressEvent(e);
+}
+
+/* Handle double-click event; if clipping plane is manipulated, align it with the global coordinate system.
+ * Otherwise pass the event to QGLViewer to handle it normally.
+ *
+ * mostly copied over from ManipulatedFrame::mouseDoubleClickEvent
+ */
+void GLViewer::mouseDoubleClickEvent(QMouseEvent *event){
+	last_user_event = boost::posix_time::second_clock::local_time();
+
+	if(manipulatedClipPlane<0) { /* LOG_DEBUG("Double click not on clipping plane"); */ QGLViewer::mouseDoubleClickEvent(event); return; }
+#if QT_VERSION >= 0x040000
+	if (event->modifiers() == Qt::NoModifier)
+#else
+	if (event->state() == Qt::NoButton)
+#endif
+	switch (event->button()){
+		case Qt::LeftButton:  manipulatedFrame()->alignWithFrame(NULL,true); LOG_DEBUG("Aligning cutting plane"); break;
+		default: break; // avoid warning
+	}
+}
+
+void GLViewer::wheelEvent(QWheelEvent* event){
+	last_user_event = boost::posix_time::second_clock::local_time();
+
+	if(manipulatedClipPlane<0){ QGLViewer::wheelEvent(event); return; }
+	assert(manipulatedClipPlane<renderer->numClipPlanes);
+	float distStep=1e-3*sceneRadius();
+	float dist=event->delta()*manipulatedFrame()->wheelSensitivity()*distStep;
+	Vector3r normal=renderer->clipPlaneSe3[manipulatedClipPlane].orientation*Vector3r(0,0,1);
+	qglviewer::Vec newPos=manipulatedFrame()->position()+qglviewer::Vec(normal[0],normal[1],normal[2])*dist;
+	manipulatedFrame()->setPosition(newPos);
+	renderer->clipPlaneSe3[manipulatedClipPlane].position=Vector3r(newPos[0],newPos[1],newPos[2]);
+	updateGL();
+	/* in draw, bound cutting planes will be moved as well */
+}
+

=== added file 'gui/qt5/Inspector.py'
--- gui/qt5/Inspector.py	1970-01-01 00:00:00 +0000
+++ gui/qt5/Inspector.py	2015-06-26 20:19:22 +0000
@@ -0,0 +1,285 @@
+# encoding: utf-8
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from yade import *
+from yade.qt.SerializableEditor import *
+import yade.qt
+
+
+class EngineInspector(QWidget):
+	def __init__(self,parent=None):
+		QWidget.__init__(self,parent)
+		grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0)
+		self.serEd=SeqSerializable(parent=None,getter=lambda:O.engines,setter=lambda x:setattr(O,'engines',x),serType=Engine,path='O.engines')
+		grid.addWidget(self.serEd)
+		self.setLayout(grid)
+#class MaterialsInspector(QWidget):
+#	def __init__(self,parent=None):
+#		QWidget.__init__(self,parent)
+#		grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0)
+#		self.serEd=SeqSerializable(parent=None,getter=lambda:O.materials,setter=lambda x:setattr(O,'materials',x),serType=Engine)
+#		grid.addWidget(self.serEd)
+#		self.setLayout(grid)
+
+class CellInspector(QWidget):
+	def __init__(self,parent=None):
+		QWidget.__init__(self,parent)
+		self.layout=QVBoxLayout(self) #; self.layout.setSpacing(0); self.layout.setMargin(0)
+		self.periCheckBox=QCheckBox('periodic boundary',self)
+		self.periCheckBox.clicked.connect(self.update)
+		self.layout.addWidget(self.periCheckBox)
+		self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True)
+		self.layout.addWidget(self.scroll)
+		self.setLayout(self.layout)
+		self.refresh()
+		self.refreshTimer=QTimer(self)
+		self.refreshTimer.timeout.connect(self.refresh)
+		self.refreshTimer.start(1000)
+	def refresh(self):
+		self.periCheckBox.setChecked(O.periodic)
+		editor=self.scroll.widget()
+		if not O.periodic and editor: self.scroll.takeWidget()
+		if (O.periodic and not editor) or (editor and editor.ser!=O.cell):
+			self.scroll.setWidget(SerializableEditor(O.cell,parent=self,showType=True,path='O.cell'))
+	def update(self):
+		self.scroll.takeWidget() # do this before changing periodicity, otherwise the SerializableEditor will raise exception about None object
+		O.periodic=self.periCheckBox.isChecked()
+		self.refresh()
+		
+	
+
+def makeBodyLabel(b):
+	ret=unicode(b.id)+u' '
+	if not b.shape: ret+=u'⬚'
+	else:
+		typeMap={'Sphere':u'⚫','Facet':u'△','Wall':u'┃','Box':u'⎕','Cylinder':u'⌭','ChainedCylinder':u'☡','Clump':u'☍'}
+		ret+=typeMap.get(b.shape.__class__.__name__,u'﹖')
+	if not b.dynamic: ret+=u'⚓'
+	elif b.state.blockedDOFs: ret+=u'⎈'
+	return ret
+
+def getBodyIdFromLabel(label):
+	try:
+		return int(unicode(label).split()[0])
+	except ValueError:
+		print 'Error with label:',unicode(label)
+		return -1
+
+class BodyInspector(QWidget):
+	def __init__(self,bodyId=-1,parent=None,bodyLinkCallback=None,intrLinkCallback=None):
+		QWidget.__init__(self,parent)
+		v=yade.qt.views()
+		self.bodyId=bodyId
+		if bodyId<0 and len(v)>0 and v[0].selection>0: self.bodyId=v[0].selection
+		self.idGlSync=self.bodyId
+		self.bodyLinkCallback,self.intrLinkCallback=bodyLinkCallback,intrLinkCallback
+		self.bodyIdBox=QSpinBox(self)
+		self.bodyIdBox.setMinimum(0)
+		self.bodyIdBox.setMaximum(100000000)
+		self.bodyIdBox.setValue(self.bodyId)
+		self.intrWithCombo=QComboBox(self);
+		self.gotoBodyButton=QPushButton(u'→ #',self)
+		self.gotoIntrButton=QPushButton(u'→ #+#',self)
+		# id selector
+		topBoxWidget=QWidget(self); topBox=QHBoxLayout(topBoxWidget); topBox.setMargin(0); #topBox.setSpacing(0); 
+		hashLabel=QLabel('#',self); hashLabel.setFixedWidth(8)
+		topBox.addWidget(hashLabel)
+		topBox.addWidget(self.bodyIdBox)
+		self.plusLabel=QLabel('+',self); topBox.addWidget(self.plusLabel)
+		hashLabel2=QLabel('#',self); hashLabel2.setFixedWidth(8); topBox.addWidget(hashLabel2)
+		topBox.addWidget(self.intrWithCombo)
+		topBox.addStretch()
+		topBox.addWidget(self.gotoBodyButton)
+		topBox.addWidget(self.gotoIntrButton)
+		topBoxWidget.setLayout(topBox)
+		# forces display
+		forcesWidget=QFrame(self); forcesWidget.setFrameShape(QFrame.Box); self.forceGrid=QGridLayout(forcesWidget); 
+		self.forceGrid.setVerticalSpacing(0); self.forceGrid.setHorizontalSpacing(9); self.forceGrid.setMargin(4);
+		for i,j in itertools.product((0,1,2,3),(-1,0,1,2)):
+			lab=QLabel('<small>'+('force','torque','move','rot')[i]+'</small>' if j==-1 else ''); self.forceGrid.addWidget(lab,i,j+1);
+			if j>=0: lab.setAlignment(Qt.AlignRight)
+			if i>1: lab.hide() # do not show forced moves and rotations by default (they will appear if non-zero)
+		self.showMovRot=False
+		#
+		self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+		self.grid.addWidget(topBoxWidget)
+		self.grid.addWidget(forcesWidget)
+		self.scroll=QScrollArea(self)
+		self.scroll.setWidgetResizable(True)
+		self.grid.addWidget(self.scroll)
+		self.tryShowBody()
+		self.bodyIdBox.valueChanged.connect(self.bodyIdSlot)
+		self.gotoBodyButton.clicked.connect(self.gotoBodySlot)
+		self.gotoIntrButton.clicked.connect(self.gotoIntrSlot)
+		self.refreshTimer=QTimer(self)
+		self.refreshTimer.timeout.connect(self.refreshEvent)
+		self.refreshTimer.start(1000)
+		self.intrWithCombo.addItems(['0']); self.intrWithCombo.setCurrentIndex(0);
+		self.intrWithCombo.setMinimumWidth(80)
+		self.setWindowTitle('Body #%d'%self.bodyId)
+		self.gotoBodySlot()
+	def displayForces(self):
+		if self.bodyId<0: return
+		try:
+			val=[O.forces.f(self.bodyId),O.forces.t(self.bodyId),O.forces.move(self.bodyId),O.forces.rot(self.bodyId)]
+			hasMovRot=(val[2]!=Vector3.Zero or val[3]!=Vector3.Zero)
+			if hasMovRot!=self.showMovRot:
+				for i,j in itertools.product((2,3),(-1,0,1,2)):
+					if hasMovRot: self.forceGrid.itemAtPosition(i,j+1).widget().show()
+					else: self.forceGrid.itemAtPosition(i,j+1).widget().hide()
+				self.showMovRot=hasMovRot
+			rows=((0,1,2,3) if hasMovRot else (0,1))
+			for i,j in itertools.product(rows,(0,1,2)):
+				self.forceGrid.itemAtPosition(i,j+1).widget().setText('<small>'+str(val[i][j])+'</small>')
+		except IndexError:pass
+	def tryShowBody(self):
+		try:
+			b=O.bodies[self.bodyId]
+			self.serEd=SerializableEditor(b,showType=True,parent=self,path='O.bodies[%d]'%self.bodyId)
+		except IndexError:
+			self.serEd=QFrame(self)
+			self.bodyId=-1
+		self.scroll.setWidget(self.serEd)
+	def changeIdSlot(self,newId):
+		self.bodyIdBox.setValue(newId);
+		self.bodyIdSlot()
+	def bodyIdSlot(self):
+		self.bodyId=self.bodyIdBox.value()
+		self.tryShowBody()
+		self.setWindowTitle('Body #%d'%self.bodyId)
+		self.refreshEvent()
+	def gotoBodySlot(self):
+		try:
+			id=int(getBodyIdFromLabel(self.intrWithCombo.currentText()))
+		except ValueError: return # empty id
+		if not self.bodyLinkCallback:
+			self.bodyIdBox.setValue(id); self.bodyId=id
+		else: self.bodyLinkCallback(id)
+	def gotoIntrSlot(self):
+		ids=self.bodyIdBox.value(),getBodyIdFromLabel(self.intrWithCombo.currentText())
+		if not self.intrLinkCallback:
+			self.ii=InteractionInspector(ids)
+			self.ii.show()
+		else: self.intrLinkCallback(ids)
+	def refreshEvent(self):
+		try: O.bodies[self.bodyId]
+		except: self.bodyId=-1 # invalidate deleted body
+		# no body shown yet, try to get the first one...
+		if self.bodyId<0 and len(O.bodies)>0:
+			try:
+				print 'SET ZERO'
+				b=O.bodies[0]; self.bodyIdBox.setValue(0)
+			except IndexError: pass
+		v=yade.qt.views()
+		if len(v)>0 and v[0].selection!=self.bodyId:
+			if self.idGlSync==self.bodyId: # changed in the viewer, reset ourselves
+				self.bodyId=self.idGlSync=v[0].selection; self.changeIdSlot(self.bodyId)
+				return
+			else: v[0].selection=self.idGlSync=self.bodyId # changed here, set in the viewer
+		meId=self.bodyIdBox.value(); pos=self.intrWithCombo.currentIndex()
+		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]
+		others.sort()
+		self.intrWithCombo.clear()
+		self.intrWithCombo.addItems([makeBodyLabel(O.bodies[i]) for i in others])
+		if pos>self.intrWithCombo.count() or pos<0: pos=0
+		self.intrWithCombo.setCurrentIndex(pos);
+		other=self.intrWithCombo.itemText(pos)
+		if other=='':
+			self.gotoBodyButton.setEnabled(False); self.gotoIntrButton.setEnabled(False)
+			other=u'∅'
+		else:
+			self.gotoBodyButton.setEnabled(True); self.gotoIntrButton.setEnabled(True)
+		self.gotoBodyButton.setText(u'→ %s'%other)
+		self.gotoIntrButton.setText(u'→ %s + %s'%(meLabel,other))
+		self.displayForces()
+		
+class InteractionInspector(QWidget):
+	def __init__(self,ids=None,parent=None,bodyLinkCallback=None):
+		QWidget.__init__(self,parent)
+		self.bodyLinkCallback=bodyLinkCallback
+		self.ids=ids
+		self.gotoId1Button=QPushButton(u'#…',self)
+		self.gotoId2Button=QPushButton(u'#…',self)
+		self.gotoId1Button.clicked.connect(self.gotoId1Slot)
+		self.gotoId2Button.clicked.connect(self.gotoId2Slot)
+		topBoxWidget=QWidget(self)
+		topBox=QHBoxLayout(topBoxWidget)
+		topBox.addWidget(self.gotoId1Button)
+		labelPlus=QLabel('+',self); labelPlus.setAlignment(Qt.AlignHCenter)
+		topBox.addWidget(labelPlus)
+		topBox.addWidget(self.gotoId2Button)
+		topBoxWidget.setLayout(topBox)
+		self.setWindowTitle(u'No interaction')
+		self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+		self.grid.addWidget(topBoxWidget,0,0)
+		self.scroll=QScrollArea(self)
+		self.scroll.setWidgetResizable(True)
+		self.grid.addWidget(self.scroll)
+		self.refreshTimer=QTimer(self)
+		self.refreshTimer.timeout.connect(self.refreshEvent)
+		self.refreshTimer.start(1000)
+		if self.ids: self.setupInteraction()
+	def setupInteraction(self):
+		try:
+			intr=O.interactions[self.ids[0],self.ids[1]]
+			self.serEd=SerializableEditor(intr,showType=True,parent=self.scroll,path='O.interactions[%d,%d]'%(self.ids[0],self.ids[1]))
+			self.scroll.setWidget(self.serEd)
+			self.gotoId1Button.setText('#'+makeBodyLabel(O.bodies[self.ids[0]]))
+			self.gotoId2Button.setText('#'+makeBodyLabel(O.bodies[self.ids[1]]))
+			self.setWindowTitle('Interaction #%d + #%d'%(self.ids[0],self.ids[1]))
+		except IndexError:
+			if self.ids:  # reset view (there was an interaction)
+				self.ids=None
+				self.serEd=QFrame(self.scroll); self.scroll.setWidget(self.serEd) 
+				self.setWindowTitle('No interaction')
+				self.gotoId1Button.setText(u'#…'); self.gotoId2Button.setText(u'#…');
+	def gotoId(self,bodyId):
+		if self.bodyLinkCallback: self.bodyLinkCallback(bodyId)
+		else: self.bi=BodyInspector(bodyId); self.bi.show()
+	def gotoId1Slot(self): self.gotoId(self.ids[0])
+	def gotoId2Slot(self): self.gotoId(self.ids[1])
+	def refreshEvent(self):
+		# no ids yet -- try getting the first interaction, if it exists
+		if not self.ids:
+			try:
+				i=O.interactions.nth(0)
+				self.ids=i.id1,i.id2
+				self.setupInteraction()
+				return
+			except IndexError: return # no interaction exists at all
+		try: # try to fetch an existing interaction
+			O.interactions[self.ids[0],self.ids[1]]
+		except IndexError:
+			self.ids=None
+			
+class SimulationInspector(QWidget):
+	def __init__(self,parent=None):
+		QWidget.__init__(self,parent)
+		self.setWindowTitle("Simulation Inspection")
+		self.tabWidget=QTabWidget(self)
+
+		self.engineInspector=EngineInspector(parent=None)
+		self.bodyInspector=BodyInspector(parent=None,intrLinkCallback=self.changeIntrIds)
+		self.intrInspector=InteractionInspector(parent=None,bodyLinkCallback=self.changeBodyId)
+		self.cellInspector=CellInspector(parent=None)
+
+		for i,name,widget in [(0,'Engines',self.engineInspector),(1,'Bodies',self.bodyInspector),(2,'Interactions',self.intrInspector),(3,'Cell',self.cellInspector)]:
+			self.tabWidget.addTab(widget,name)
+		grid=QGridLayout(self); grid.setSpacing(0); grid.setMargin(0)
+		grid.addWidget(self.tabWidget)
+		self.setLayout(grid)
+	def changeIntrIds(self,ids):
+		self.tabWidget.removeTab(2); self.intrInspector.close()
+		self.intrInspector=InteractionInspector(ids=ids,parent=None,bodyLinkCallback=self.changeBodyId)
+		self.tabWidget.insertTab(2,self.intrInspector,'Interactions')
+		self.tabWidget.setCurrentIndex(2)
+	def changeBodyId(self,id):
+		self.bodyInspector.changeIdSlot(id)
+		self.tabWidget.setCurrentIndex(1)
+		

=== added file 'gui/qt5/OpenGLManager.cpp'
--- gui/qt5/OpenGLManager.cpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/OpenGLManager.cpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,76 @@
+#include"OpenGLManager.hpp"
+
+CREATE_LOGGER(OpenGLManager);
+
+OpenGLManager* OpenGLManager::self=NULL;
+
+OpenGLManager::OpenGLManager(QObject* parent): QObject(parent){
+	if(self) throw runtime_error("OpenGLManager instance already exists, uses OpenGLManager::self to retrieve it.");
+	self=this;
+	renderer=shared_ptr<OpenGLRenderer>(new OpenGLRenderer);
+	renderer->init();
+	connect(this,SIGNAL(createView()),this,SLOT(createViewSlot()));
+	connect(this,SIGNAL(resizeView(int,int,int)),this,SLOT(resizeViewSlot(int,int,int)));
+	connect(this,SIGNAL(closeView(int)),this,SLOT(closeViewSlot(int)));
+	connect(this,SIGNAL(startTimerSignal()),this,SLOT(startTimerSlot()),Qt::QueuedConnection);
+}
+
+void OpenGLManager::timerEvent(QTimerEvent* event){
+	//cerr<<".";
+	boost::mutex::scoped_lock lock(viewsMutex);
+	// when sharing the 0th view widget, it should be enough to update the primary view only
+	//if(views.size()>0) views[0]->updateGL();
+	#if 1
+		FOREACH(const shared_ptr<GLViewer>& view, views){ if(view) view->updateGL(); }
+	#endif
+}
+
+void OpenGLManager::createViewSlot(){
+	boost::mutex::scoped_lock lock(viewsMutex);
+	if(views.size()==0){
+		views.push_back(shared_ptr<GLViewer>(new GLViewer(0,renderer,/*shareWidget*/(QGLWidget*)0)));
+	} else {
+		throw runtime_error("Secondary views not supported");
+		//views.push_back(shared_ptr<GLViewer>(new GLViewer(views.size(),renderer,views[0].get())));
+	}	
+}
+
+void OpenGLManager::resizeViewSlot(int id, int wd, int ht){
+	views[id]->resize(wd,ht);
+}
+
+void OpenGLManager::closeViewSlot(int id){
+	boost::mutex::scoped_lock lock(viewsMutex);
+	for(size_t i=views.size()-1; (!views[i]); i--){ views.resize(i); } // delete empty views from the end
+	if(id<0){ // close the last one existing
+		assert(*views.rbegin()); // this should have been sanitized by the loop above
+		views.resize(views.size()-1); // releases the pointer as well
+	}
+	if(id==0){
+		LOG_DEBUG("Closing primary view.");
+		if(views.size()==1) views.clear();
+		else{ LOG_INFO("Cannot close primary view, secondary views still exist."); }
+	}
+}
+void OpenGLManager::centerAllViews(){
+	boost::mutex::scoped_lock lock(viewsMutex);
+	FOREACH(const shared_ptr<GLViewer>& g, views){ if(!g) continue; g->centerScene(); }
+}
+void OpenGLManager::startTimerSlot(){
+	startTimer(50);
+}
+
+int OpenGLManager::waitForNewView(float timeout,bool center){
+	size_t origViewCount=views.size();
+	emitCreateView();
+	float t=0;
+	while(views.size()!=origViewCount+1){
+		usleep(50000); t+=.05;
+		// wait at most 5 secs
+		if(t>=timeout) {
+			LOG_ERROR("Timeout waiting for the new view to open, giving up."); return -1;
+		}
+	}
+	if(center)(*views.rbegin())->centerScene();
+	return (*views.rbegin())->viewId; 
+}

=== added file 'gui/qt5/OpenGLManager.hpp'
--- gui/qt5/OpenGLManager.hpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/OpenGLManager.hpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,47 @@
+#pragma once
+//#include
+
+#ifndef Q_MOC_RUN 
+#include"GLViewer.hpp"
+#endif 
+
+#include<QObject>
+#include<boost/thread/mutex.hpp>
+
+/*
+Singleton class managing OpenGL views,
+a renderer instance and timer to refresh the display.
+*/
+class OpenGLManager: public QObject{
+	Q_OBJECT
+	DECLARE_LOGGER;
+	public:
+		static OpenGLManager* self;
+		OpenGLManager(QObject *parent=0);
+		// manipulation must lock viewsMutex!
+		std::vector<shared_ptr<GLViewer> > views;
+		shared_ptr<OpenGLRenderer> renderer;
+		// signals are protected, emitting them is therefore wrapped with such funcs
+		void emitResizeView(int id, int wd, int ht){ emit resizeView(id,wd,ht); }
+		void emitCreateView(){ emit createView(); }
+		void emitStartTimer(){ emit startTimerSignal(); }
+		void emitCloseView(int id){ emit closeView(id); }
+		// create a new view and wait for it to become available; return the view number
+		// if timout (in seconds) elapses without the view to come up, reports error and returns -1
+		int waitForNewView(float timeout=5., bool center=true);
+	signals:
+		void createView();
+		void resizeView(int id, int wd, int ht);
+		void closeView(int id);
+		// this is used to start timer from the main thread via postEvent (ugly)
+		void startTimerSignal();
+	public slots:
+		virtual void createViewSlot();
+		virtual void resizeViewSlot(int id, int wd, int ht);
+		virtual void closeViewSlot(int id=-1);
+		virtual void timerEvent(QTimerEvent* event);
+		virtual void startTimerSlot();
+		void centerAllViews();
+	private:
+		boost::mutex viewsMutex;
+};

=== added file 'gui/qt5/SerializableEditor.py'
--- gui/qt5/SerializableEditor.py	1970-01-01 00:00:00 +0000
+++ gui/qt5/SerializableEditor.py	2015-06-26 20:19:22 +0000
@@ -0,0 +1,828 @@
+# encoding: utf-8
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5 import QtGui
+
+import re,itertools
+import logging
+logging.trace=logging.debug
+logging.basicConfig(level=logging.INFO)
+
+from yade import *
+import yade.qt
+
+from minieigen import *
+
+seqSerializableShowType=True # show type headings in serializable sequences (takes vertical space, but makes the type hyperlinked)
+
+# BUG: cursor is moved to the beginning of the input field even if it has focus
+# checking for focus seems to return True always and cursor is never moved
+# the 'True or' part effectively disables the condition (so that the cursor is moved always), but it might be fixed in the future somehow
+# if True or w.hasFocus(): w.home(False)
+# Janek: It looks like I've fixed this BUG, please do more testing.
+#
+
+def makeWrapperHref(text,className,attr=None,static=False):
+	"""Create clickable HTML hyperlink to a Yade class or its attribute.
+	
+	:param className: name of the class to link to.
+	:param attr: attribute to link to. If given, must exist directly in given *className*; if not given or empty, link to the class itself is created and *attr* is ignored.
+	:return: HTML with the hyperref.
+	"""
+	if not static: return '<a href="%s#yade.wrapper.%s%s">%s</a>'%(yade.qt.sphinxDocWrapperPage,className,(('.'+attr) if attr else ''),text)
+	else:          return '<a href="%s#ystaticattr-%s.%s">%s</a>'%(yade.qt.sphinxDocWrapperPage,className,attr,text)
+
+def serializableHref(ser,attr=None,text=None):
+	"""Return HTML href to a *ser* optionally to the attribute *attr*.
+	The class hierarchy is crawled upwards to find out in which parent class is *attr* defined,
+	so that the href target is a valid link. In that case, only single inheritace is assumed and
+	the first class from the top defining *attr* is used.
+
+	:param ser: object of class deriving from :yref:`Serializable`, or string; if string, *attr* must be empty.
+	:param attr: name of the attribute to link to; if empty, linke to the class itself is created.
+	:param text: visible text of the hyperlink; if not given, either class name or attribute name without class name (when *attr* is not given) is used.
+
+	:returns: HTML with the hyperref.
+	"""
+	# klass is a class name given as string
+	if isinstance(ser,str):
+		if attr: raise InvalidArgument("When *ser* is a string, *attr* must be empty (only class link can be created)")
+		return makeWrapperHref(text if text else ser,ser)
+	# klass is a type object
+	if attr:
+		klass=ser.__class__
+		while attr in dir(klass.__bases__[0]): klass=klass.__bases__[0]
+		if not text: text=attr
+	else:
+		klass=ser.__class__
+		if not text: text=klass.__name__
+	return makeWrapperHref(text,klass.__name__,attr,static=(attr and getattr(klass,attr)==getattr(ser,attr)))
+
+class AttrEditor():
+	"""Abstract base class handing some aspects common to all attribute editors.
+	Holds exacly one attribute which is updated whenever it changes."""
+	def __init__(self,getter=None,setter=None):
+		self.getter,self.setter=getter,setter
+		self.hot,self.focused=False,False
+		self.widget=None
+	def refresh(self): pass
+	def update(self): pass
+	def isHot(self,hot=True):
+		"Called when the widget gets focus; mark it hot, change colors etc."
+		if hot==self.hot: return
+		self.hot=hot
+		if hot: self.setStyleSheet('QWidget { background: red }')
+		else: self.setStyleSheet('QWidget { background: none }')
+	def sizeHint(self): return QSize(150,12)
+	def trySetter(self,val):
+		try: self.setter(val)
+		except AttributeError: self.setEnabled(False)
+		self.isHot(False)
+
+class AttrEditor_Bool(AttrEditor,QFrame):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QFrame.__init__(self,parent)
+		self.checkBox=QCheckBox(self)
+		lay=QVBoxLayout(self); lay.setSpacing(0); lay.setMargin(0); lay.addStretch(1); lay.addWidget(self.checkBox); lay.addStretch(1)
+		self.checkBox.clicked.connect(self.update)
+	def refresh(self): self.checkBox.setChecked(self.getter())
+	def update(self): self.trySetter(self.checkBox.isChecked())
+
+class AttrEditor_Int(AttrEditor,QSpinBox):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QSpinBox.__init__(self,parent)
+		self.setRange(int(-1e9),int(1e9)); self.setSingleStep(1);
+		self.valueChanged.connect(self.update)
+	def refresh(self):
+		if (not self.hasFocus()): self.setValue(self.getter())
+	def update(self):  self.trySetter(self.value())
+
+class AttrEditor_Str(AttrEditor,QLineEdit):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QLineEdit.__init__(self,parent)
+		self.textEdited.connect(self.isHot)
+		self.selectionChanged.connect(self.isHot)
+		self.editingFinished.connect(self.update)
+	def refresh(self):
+		if (not self.hasFocus()): self.setText(self.getter())
+	def update(self):  self.trySetter(str(self.text()))
+
+class AttrEditor_Float(AttrEditor,QLineEdit):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QLineEdit.__init__(self,parent)
+		self.textEdited.connect(self.isHot)
+		self.selectionChanged.connect(self.isHot)
+		self.editingFinished.connect(self.update)
+	def refresh(self):
+		#if True or not self.hasFocus(): self.home(False)
+		if (not self.hasFocus()):
+			self.setText(str(self.getter()));
+			self.home(False)
+	def update(self):
+		try: self.trySetter(float(self.text()))
+		except ValueError: self.refresh()
+
+class AttrEditor_Complex(AttrEditor,QLineEdit):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QFrame.__init__(self,parent)
+		self.rows,self.cols=1,2
+		self.setContentsMargins(0,0,0,0)
+		self.first=True
+		val=self.getter()
+		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('')
+			self.grid.addWidget(w,row,col);
+			w.textEdited.connect(self.isHot)
+			w.selectionChanged.connect(self.isHot)
+			w.editingFinished.connect(self.update)
+	def refresh(self,force=False):
+		val=self.getter()
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			w=self.grid.itemAtPosition(row,col).widget()
+			if(self.first or force):
+				w.setText(str(val.real if col==0 else val.imag))
+			#if True or not w.hasFocus: w.home(False) # make the left-most part visible, if the text is wider than the widget
+			if (not w.hasFocus):
+				w.setText(str(val.real if col==0 else val.imag))
+				w.home(False) # make the left-most part visible, if the text is wider than the widget
+		self.first=False
+	def update(self):
+		try:
+			val=self.getter()
+			w1=self.grid.itemAtPosition(0,0).widget()
+			w2=self.grid.itemAtPosition(0,1).widget()
+			if w1.isModified() or w2.isModified():
+				val=complex(float(w1.text()),float(w2.text()))
+			logging.debug('setting'+str(val))
+			self.trySetter(val)
+		except ValueError:
+			self.refresh(force=True)
+	def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_Quaternion(AttrEditor,QFrame):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QFrame.__init__(self,parent)
+		self.grid=QHBoxLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+		for i in range(4):
+			if i==3:
+				# add vertical divider (axis | angle)
+				f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4)
+				self.grid.addWidget(f)
+			w=QLineEdit('')
+			self.grid.addWidget(w);
+			w.textEdited.connect(self.isHot)
+			w.selectionChanged.connect(self.isHot)
+			w.editingFinished.connect(self.update)
+	def refresh(self):
+		val=self.getter(); axis,angle=val.toAxisAngle()
+		for i in (0,1,2,4):
+			#if True or not w.hasFocus(): w.home(False)
+			w=self.grid.itemAt(i).widget();
+			if (not w.hasFocus()):
+				w.setText(str(axis[i] if i<3 else angle));
+				w.home(False)
+	def update(self):
+		try:
+			x=[float((self.grid.itemAt(i).widget().text())) for i in (0,1,2,4)]
+		except ValueError:
+			self.refresh()
+			return
+		q=Quaternion(Vector3(x[0],x[1],x[2]),x[3]); q.normalize() # from axis-angle
+		self.trySetter(q) 
+	def setFocus(self): self.grid.itemAt(0).widget().setFocus()
+
+class AttrEditor_Se3(AttrEditor,QFrame):
+	def __init__(self,parent,getter,setter):
+		AttrEditor.__init__(self,getter,setter)
+		QFrame.__init__(self,parent)
+		self.grid=QGridLayout(self); self.grid.setSpacing(0); self.grid.setMargin(0)
+		for row,col in itertools.product(range(2),range(5)): # one additional column for vertical line in quaternion
+			if (row,col)==(0,3): continue
+			if (row,col)==(0,4): self.grid.addWidget(QLabel(u'←<i>pos</i> ↙<i>ori</i>',self),row,col); continue
+			if (row,col)==(1,3):
+				f=QFrame(self); f.setFrameShape(QFrame.VLine); f.setFrameShadow(QFrame.Sunken); f.setFixedWidth(4); self.grid.addWidget(f,row,col); continue
+			w=QLineEdit('')
+			self.grid.addWidget(w,row,col);
+			w.textEdited.connect(self.isHot)
+			w.selectionChanged.connect(self.isHot)
+			w.editingFinished.connect(self.update)
+	def refresh(self):
+		pos,ori=self.getter(); axis,angle=ori.toAxisAngle()
+		for i in (0,1,2,4):
+			#if True or not w.hasFocus(): w.home(False)
+			w=self.grid.itemAtPosition(1,i).widget();
+			if (not w.hasFocus()):
+				w.setText(str(axis[i] if i<3 else angle));
+				w.home(False)
+		for i in (0,1,2):
+			#if True or not w.hasFocus(): w.home(False)
+			w=self.grid.itemAtPosition(0,i).widget();
+			if (not w.hasFocus()):
+				w.setText(str(pos[i]));
+				w.home(False)
+	def update(self):
+		try:
+			q=[float((self.grid.itemAtPosition(1,i).widget().text())) for i in (0,1,2,4)]
+			v=[float((self.grid.itemAtPosition(0,i).widget().text())) for i in (0,1,2)]
+		except ValueError:
+			self.refresh()
+			return
+		qq=Quaternion(Vector3(q[0],q[1],q[2]),q[3]); qq.normalize() # from axis-angle
+		self.trySetter((v,qq)) 
+	def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_MatrixX(AttrEditor,QFrame):
+	def __init__(self,parent,getter,setter,rows,cols,idxConverter):
+		'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []'
+		AttrEditor.__init__(self,getter,setter)
+		QFrame.__init__(self,parent)
+		self.rows,self.cols=rows,cols
+		self.idxConverter=idxConverter
+		self.setContentsMargins(0,0,0,0)
+		self.first=True
+		val=self.getter()
+		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('')
+			self.grid.addWidget(w,row,col);
+			w.textEdited.connect(self.isHot)
+			w.selectionChanged.connect(self.isHot)
+			w.editingFinished.connect(self.update)
+	def refresh(self,force=False):
+		val=self.getter()
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			w=self.grid.itemAtPosition(row,col).widget()
+			if(self.first or force):
+				w.setText(str(val[self.idxConverter(row,col)]))
+			if (not w.hasFocus):
+				w.setText(str(val[self.idxConverter(row,col)]))
+				w.home(False) # make the left-most part visible, if the text is wider than the widget
+		self.first=False
+	def update(self):
+		try:
+			val=self.getter()
+			for row,col in itertools.product(range(self.rows),range(self.cols)):
+				w=self.grid.itemAtPosition(row,col).widget()
+				if w.isModified(): val[self.idxConverter(row,col)]=float(w.text())
+			logging.debug('setting'+str(val))
+			self.trySetter(val)
+		except ValueError:
+			self.refresh(force=True)
+	def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_MatrixXi(AttrEditor,QFrame):
+	def __init__(self,parent,getter,setter,rows,cols,idxConverter):
+		'idxConverter converts row,col tuple to either (row,col), (col) etc depending on what access is used for []'
+		AttrEditor.__init__(self,getter,setter)
+		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(-1e9),int(1e9)); w.setSingleStep(1);
+			self.grid.addWidget(w,row,col);
+		self.refresh() # refresh before connecting signals!
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			self.grid.itemAtPosition(row,col).widget().valueChanged.connect(self.update)
+	def refresh(self):
+		val=self.getter()
+		for row,col in itertools.product(range(self.rows),range(self.cols)):
+			w=self.grid.itemAtPosition(row,col).widget().setValue(val[self.idxConverter(row,col)])
+	def update(self):
+		val=self.getter(); 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))
+		self.trySetter(val)
+	def setFocus(self): self.grid.itemAtPosition(0,0).widget().setFocus()
+
+class AttrEditor_Vector6i(AttrEditor_MatrixXi):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,6,lambda r,c:c)
+class AttrEditor_Vector3i(AttrEditor_MatrixXi):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,3,lambda r,c:c)
+class AttrEditor_Vector2i(AttrEditor_MatrixXi):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixXi.__init__(self,parent,getter,setter,1,2,lambda r,c:c)
+
+class AttrEditor_Vector6(AttrEditor_MatrixX):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,6,lambda r,c:c)
+class AttrEditor_Vector3(AttrEditor_MatrixX):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,3,lambda r,c:c)
+class AttrEditor_Vector2(AttrEditor_MatrixX):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixX.__init__(self,parent,getter,setter,1,2,lambda r,c:c)
+class AttrEditor_Matrix3(AttrEditor_MatrixX):
+	def __init__(self,parent,getter,setter):
+		AttrEditor_MatrixX.__init__(self,parent,getter,setter,3,3,lambda r,c:(r,c))
+
+class Se3FakeType: pass
+
+_fundamentalEditorMap={bool:AttrEditor_Bool,str:AttrEditor_Str,int:AttrEditor_Int,float:AttrEditor_Float,complex:AttrEditor_Complex,Quaternion:AttrEditor_Quaternion,Vector2:AttrEditor_Vector2,Vector3:AttrEditor_Vector3,Vector6:AttrEditor_Vector6,Matrix3:AttrEditor_Matrix3,Vector6i:AttrEditor_Vector6i,Vector3i:AttrEditor_Vector3i,Vector2i:AttrEditor_Vector2i,Se3FakeType:AttrEditor_Se3}
+_fundamentalInitValues={bool:True,str:'',int:0,float:0.0,complex:0.0j,Quaternion:Quaternion((0,1,0),0.0),Vector3:Vector3.Zero,Matrix3:Matrix3.Zero,Vector6:Vector6.Zero,Vector6i:Vector6i.Zero,Vector3i:Vector3i.Zero,Vector2i:Vector2i.Zero,Vector2:Vector2.Zero,Se3FakeType:(Vector3.Zero,Quaternion((0,1,0),0.0))}
+
+class SerQLabel(QLabel):
+	def __init__(self,parent,label,tooltip,path):
+		QLabel.__init__(self,parent)
+		self.path=path
+		self.setText(label)
+		if tooltip or path: self.setToolTip(('<b>'+path+'</b><br>' if self.path else '')+(tooltip if tooltip else ''))
+		self.linkActivated.connect(yade.qt.openUrl)
+	def mousePressEvent(self,event):
+		if event.button()!=Qt.MidButton:
+			event.ignore(); return
+		# middle button clicked, paste pasteText to clipboard
+		cb=QApplication.clipboard()
+		cb.setText(self.path,mode=QClipboard.Clipboard)
+		cb.setText(self.path,mode=QClipboard.Selection) # X11 global selection buffer
+		event.accept()
+
+class SerializableEditor(QFrame):
+	"Class displaying and modifying serializable attributes of a yade object."
+	import collections
+	import logging
+	# each attribute has one entry associated with itself
+	class EntryData:
+		def __init__(self,name,T,flags=0):
+			self.name,self.T,self.flags=name,T,flags
+			self.lineNo,self.widget=None,None
+	def __init__(self,ser,parent=None,ignoredAttrs=set(),showType=False,path=None):
+		"Construct window, *ser* is the object we want to show."
+		QtGui.QFrame.__init__(self,parent)
+		self.ser=ser
+		self.path=(ser.label if (hasattr(ser,'label') and ser.label) else path)
+		self.showType=showType
+		self.hot=False
+		self.entries=[]
+		self.ignoredAttrs=ignoredAttrs
+		logging.debug('New Serializable of type %s'%ser.__class__.__name__)
+		self.setWindowTitle(str(ser))
+		self.mkWidgets()
+		self.refreshTimer=QTimer(self)
+		self.refreshTimer.timeout.connect(self.refreshEvent)
+		self.refreshTimer.start(500)
+	def getListTypeFromDocstring(self,attr):
+		"Guess type of array by scanning docstring for :yattrtype: and parsing its argument; ugly, but works."
+		doc=getattr(self.ser.__class__,attr).__doc__
+		if doc==None:
+			logging.error("Attribute %s has no docstring."%attr)
+			return None
+		m=re.search(r':yattrtype:`([^`]*)`',doc)
+		if not m:
+			logging.error("Attribute %s does not contain :yattrtype:`....` (docstring is '%s'"%(attr,doc))
+			return None
+		cxxT=m.group(1)
+		logging.debug('Got type "%s" from :yattrtype:'%cxxT)
+		def vecTest(T,cxxT):
+			#regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(std\s*::)?\s*('+T+r')\s*>\s*$'
+			regexp=r'^\s*(std\s*::)?\s*vector\s*<\s*(shared_ptr\s*<\s*)?\s*(std\s*::)?\s*('+T+r')(\s*>)?\s*>\s*$'
+			m=re.match(regexp,cxxT)
+			return m
+		vecMap={
+			'bool':bool,'int':int,'long':int,'Body::id_t':long,'size_t':long,
+			'Real':float,'float':float,'double':float,'complex':complex,'std::complex<Real>':complex,
+			'Vector6r':Vector6,'Vector6i':Vector6i,'Vector3i':Vector3i,'Vector2r':Vector2,'Vector2i':Vector2i,
+			'Vector3r':Vector3,'Matrix3r':Matrix3,'Se3r':Se3FakeType,
+			'string':str,
+			#'BodyCallback':BodyCallback,
+			'IntrCallback':IntrCallback,'BoundFunctor':BoundFunctor,'IGeomFunctor':IGeomFunctor,'IPhysFunctor':IPhysFunctor,'LawFunctor':LawFunctor,'KinematicEngine':KinematicEngine,
+			'GlShapeFunctor':GlShapeFunctor,'GlStateFunctor':GlStateFunctor,'GlIGeomFunctor':GlIGeomFunctor,'GlIPhysFunctor':GlIPhysFunctor,'GlBoundFunctor':GlBoundFunctor,'GlExtraDrawer':GlExtraDrawer
+		}
+		for T,ret in vecMap.items():
+			if vecTest(T,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
+	def mkAttrEntries(self):
+		if self.ser==None: return
+		try:
+			d=self.ser.dict()
+		except TypeError:
+			logging.error('TypeError when getting attributes of '+str(self.ser)+',skipping. ')
+			import traceback
+			traceback.print_exc()
+		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
+			doc=getattr(self.ser.__class__,attr).__doc__;
+			# some attributes are not shown
+			if '|yhidden|' in doc: continue
+			if attr in self.ignoredAttrs: continue
+# FIXME: (Janek) Implementing Quantum Mechanics makes some DEM assumptions
+# invalid.  I think that we should rethink what base class Body contains, so
+# that in QM we would not need to use this hack to hide some variables.
+# However it is great to note that only this little 'cosmetic' hack is needed
+# to make Quantum Mechanics possible in yade
+# See also: class QuantumMechanicalState, class QuantumMechanicalBody, gui/qt4/SerializableEditor.py
+			if hasattr(self.ser,"qtHide") and (attr in getattr(self.ser,"qtHide").split()): continue
+			if isinstance(val,list):
+				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__
+			match=re.search(':yattrflags:`\s*([0-9]+)\s*`',doc) # non-empty attribute
+			flags=int(match.group(1)) if match else 0
+			#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))
+			#print("name: "+str(attr)+"\tflag: "+str(flags))
+	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 ''
+		doc=re.sub(':y(attrtype|default|attrflags):`[^`]*`','',doc)
+		statAttr=re.compile('^.. ystaticattr::.*$',re.MULTILINE|re.DOTALL)
+		doc=re.sub(statAttr,'',doc) # static classes have their proper docs at the beginning, discard static memeber docs
+		# static: attribute of the type is the same object as attribute of the instance
+		# in that case, get docstring from the class documentation by parsing it
+		if attr and getattr(self.ser.__class__,attr)==getattr(self.ser,attr): doc=self.getStaticAttrDocstring(attr)
+		doc=re.sub(':yref:`([^`]*)`','\\1',doc)
+		import textwrap
+		wrapper=textwrap.TextWrapper(replace_whitespace=False)
+		return wrapper.fill(textwrap.dedent(doc))
+	def getStaticAttrDocstring(self,attr):
+		ret=''; c=self.ser.__class__
+		while hasattr(c,attr) and hasattr(c.__base__,attr): c=c.__base__
+		start='.. ystaticattr:: %s.%s('%(c.__name__,attr)
+		if start in c.__doc__:
+			ll=c.__doc__.split('\n')
+			for i in range(len(ll)):
+				if ll[i].startswith(start): break
+			for i in range(i+1,len(ll)):
+				if len(ll[i])>0 and ll[i][0] not in ' \t': break
+				ret+=ll[i]
+			return ret
+		else: return '[no documentation found]'
+	def mkWidget(self,entry):
+		if not entry.T: return None
+		# single fundamental object
+		Klass=_fundamentalEditorMap.get(entry.T,None)
+		getter,setter=lambda: getattr(self.ser,entry.name), lambda x: setattr(self.ser,entry.name,x)
+		if Klass:
+			widget=Klass(self,getter=getter,setter=setter)
+			widget.setFocusPolicy(Qt.StrongFocus)
+			if (entry.flags & AttrFlags.readonly): widget.setEnabled(False)
+			return widget
+		# sequences
+		if entry.T.__class__==tuple:
+			assert(len(entry.T)==1) # we don't handle tuples of other lenghts
+			# sequence of serializables
+			T=entry.T[0]
+			if (issubclass(T,Serializable) or T==Serializable):
+				widget=SeqSerializable(self,getter,setter,T,path=(self.path+'.'+entry.name if self.path else None),shrink=True)
+				return widget
+			if (T in _fundamentalEditorMap):
+				widget=SeqFundamentalEditor(self,getter,setter,T)
+				return widget
+			return None
+		# a serializable
+		if issubclass(entry.T,Serializable) or entry.T==Serializable:
+			obj=getattr(self.ser,entry.name)
+			if hasattr(obj,'label') and obj.label: path=obj.label
+			elif self.path: path=self.path+'.'+entry.name
+			else: path=None
+			widget=SerializableEditor(getattr(self.ser,entry.name),parent=self,showType=self.showType,path=(self.path+'.'+entry.name if self.path else None))
+			widget.setFrameShape(QFrame.Box); widget.setFrameShadow(QFrame.Raised); widget.setLineWidth(1)
+			return widget
+		return None
+	def mkWidgets(self):
+		self.mkAttrEntries()
+		grid=QFormLayout()
+		grid.setContentsMargins(2,2,2,2)
+		grid.setVerticalSpacing(0)
+		grid.setLabelAlignment(Qt.AlignRight)
+		if self.showType:
+			lab=SerQLabel(self,makeSerializableLabel(self.ser,addr=True,href=True),tooltip=self.getDocstring(),path=self.path)
+			lab.setFrameShape(QFrame.Box); lab.setFrameShadow(QFrame.Sunken); lab.setLineWidth(2); lab.setAlignment(Qt.AlignHCenter); lab.linkActivated.connect(yade.qt.openUrl)
+			grid.setWidget(0,QFormLayout.SpanningRole,lab)
+		for entry in self.entries:
+			entry.widget=self.mkWidget(entry)
+			objPath=(self.path+'.'+entry.name) if self.path else None
+			label=SerQLabel(self,serializableHref(self.ser,entry.name),tooltip=self.getDocstring(entry.name),path=objPath)
+			grid.addRow(label,entry.widget if entry.widget else QLabel('<i>unhandled type</i>'))
+		self.setLayout(grid)
+		self.refreshEvent()
+	def refreshEvent(self):
+		for e in self.entries:
+			if e.widget and not e.widget.hot: e.widget.refresh()
+	def refresh(self): pass
+
+def makeSerializableLabel(ser,href=False,addr=True,boldHref=True,num=-1,count=-1):
+	ret=u''
+	if num>=0:
+		if count>=0: ret+=u'%d/%d. '%(num,count)
+		else: ret+=u'%d. '%num
+	if href: ret+=(u' <b>' if boldHref else u' ')+serializableHref(ser)+(u'</b> ' if boldHref else u' ')
+	else: ret+=ser.__class__.__name__+' '
+	if hasattr(ser,'label') and ser.label: ret+=u' “'+unicode(ser.label)+u'”'
+	# do not show address if there is a label already
+	elif addr:
+		import re
+		ss=unicode(ser); m=re.match(u'<(.*) instance at (0x.*)>',ss)
+		if m: ret+=m.group(2)
+		else: logging.warning(u"Serializable converted to str ('%s') does not contain 'instance at 0x…'"%ss)
+	return ret
+
+class SeqSerializableComboBox(QFrame):
+	def __init__(self,parent,getter,setter,serType,path=None,shrink=False):
+		QFrame.__init__(self,parent)
+		self.getter,self.setter,self.serType,self.path,self.shrink=getter,setter,serType,path,shrink
+		self.layout=QVBoxLayout(self)
+		topLineFrame=QFrame(self)
+		topLineLayout=QHBoxLayout(topLineFrame);
+		for l in self.layout, topLineLayout: l.setSpacing(0); l.setContentsMargins(0,0,0,0)
+		topLineFrame.setLayout(topLineLayout)
+		buttons=(self.newButton,self.killButton,self.upButton,self.downButton)=[QPushButton(label,self) for label in (u'☘',u'☠',u'↑',u'↓')]
+		buttonSlots=(self.newSlot,self.killSlot,self.upSlot,self.downSlot) # same order as buttons
+		for b in buttons: b.setStyleSheet('QPushButton { font-size: 15pt; }'); b.setFixedWidth(30); b.setFixedHeight(30)
+		self.combo=QComboBox(self)
+		self.combo.setSizeAdjustPolicy(QComboBox.AdjustToContents)
+		for w in buttons[0:2]+[self.combo,]+buttons[2:4]: topLineLayout.addWidget(w)
+		self.layout.addWidget(topLineFrame) # nested layout
+		self.scroll=QScrollArea(self); self.scroll.setWidgetResizable(True)
+		self.layout.addWidget(self.scroll)
+		self.seqEdit=None # currently edited serializable
+		self.setLayout(self.layout)
+		self.hot=None # API compat with SerializableEditor
+		self.setFrameShape(QFrame.Box); self.setFrameShadow(QFrame.Raised); self.setLineWidth(1)
+		# signals
+		for b,slot in zip(buttons,buttonSlots): b.clicked.connect(slot)
+		self.combo.currentIndexChanged.connect(self.comboIndexSlot)
+		self.refreshEvent()
+		# periodic refresh
+		self.refreshTimer=QTimer(self)
+		self.refreshTimer.timeout.connect(self.refreshEvent)
+		self.refreshTimer.start(1000) # 1s should be enough
+		#print 'SeqSerializable path is',self.path
+	def comboIndexSlot(self,ix): # different seq item selected
+		currSeq=self.getter();
+		if len(currSeq)==0: ix=-1
+		logging.debug('%s comboIndexSlot len=%d, ix=%d'%(self.serType.__name__,len(currSeq),ix))
+		self.downButton.setEnabled(ix<len(currSeq)-1)
+		self.upButton.setEnabled(ix>0)
+		self.combo.setEnabled(ix>=0)
+		if ix>=0:
+			ser=currSeq[ix]
+			self.seqEdit=SerializableEditor(ser,parent=self,showType=seqSerializableShowType,path=(self.path+'['+str(ix)+']') if self.path else None)
+			self.scroll.setWidget(self.seqEdit)
+			if self.shrink:
+				self.sizeHint=lambda: QSize(100,1000)
+				self.scroll.sizeHint=lambda: QSize(100,1000)
+				self.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding)
+				self.scroll.sizePolicy().setVerticalPolicy(QSizePolicy.Expanding)
+				self.setMinimumHeight(min(300,self.seqEdit.height()+self.combo.height()+10))
+				self.setMaximumHeight(100000)
+				self.scroll.setMaximumHeight(100000)
+		else:
+			self.scroll.setWidget(QFrame())
+			if self.shrink:
+				self.setMaximumHeight(self.combo.height()+10);
+				self.scroll.setMaximumHeight(0)
+	def serLabel(self,ser,i=-1):
+		return ('' if i<0 else str(i)+'. ')+str(ser)[1:-1].replace('instance at ','')
+	def refreshEvent(self,forceIx=-1):
+		currSeq=self.getter()
+		comboEnabled=self.combo.isEnabled()
+		if comboEnabled and len(currSeq)==0: self.comboIndexSlot(-1) # force refresh, otherwise would not happen from the initially empty state
+		ix,cnt=self.combo.currentIndex(),self.combo.count()
+		# serializable currently being edited (which can be absent) or the one of which index is forced
+		ser=(self.seqEdit.ser if self.seqEdit else None) if forceIx<0 else currSeq[forceIx] 
+		if comboEnabled and len(currSeq)==cnt and (ix<0 or ser==currSeq[ix]): return
+		if not comboEnabled and len(currSeq)==0: return
+		logging.debug(self.serType.__name__+' rebuilding list from scratch')
+		self.combo.clear()
+		if len(currSeq)>0:
+			prevIx=-1
+			for i,s in enumerate(currSeq):
+				self.combo.addItem(makeSerializableLabel(s,num=i,count=len(currSeq),addr=False))
+				if s==ser: prevIx=i
+			if forceIx>=0: newIx=forceIx # force the index (used from newSlot to make the new element active)
+			elif prevIx>=0: newIx=prevIx # if found what was active before, use it
+			elif ix>=0: newIx=ix         # otherwise use the previous index (e.g. after deletion)
+			else: newIx=0                  # fallback to 0
+			logging.debug('%s setting index %d'%(self.serType.__name__,newIx))
+			self.combo.setCurrentIndex(newIx)
+		else:
+			logging.debug('%s EMPTY, setting index 0'%(self.serType.__name__))
+			self.combo.setCurrentIndex(-1)
+		self.killButton.setEnabled(len(currSeq)>0)
+	def newSlot(self):
+		dialog=NewSerializableDialog(self,self.serType.__name__)
+		if not dialog.exec_(): return # cancelled
+		ser=dialog.result()
+		ix=self.combo.currentIndex()
+		currSeq=self.getter(); currSeq.insert(ix,ser); self.setter(currSeq)
+		logging.debug('%s new item created at index %d'%(self.serType.__name__,ix))
+		self.refreshEvent(forceIx=ix)
+	def killSlot(self):
+		ix=self.combo.currentIndex()
+		currSeq=self.getter(); del currSeq[ix]; self.setter(currSeq)
+		self.refreshEvent()
+	def upSlot(self):
+		i=self.combo.currentIndex()
+		assert(i>0)
+		currSeq=self.getter();
+		prev,curr=currSeq[i-1:i+1]; currSeq[i-1],currSeq[i]=curr,prev; self.setter(currSeq)
+		self.refreshEvent(forceIx=i-1)
+	def downSlot(self):
+		i=self.combo.currentIndex()
+		currSeq=self.getter(); assert(i<len(currSeq)-1);
+		curr,nxt=currSeq[i:i+2]; currSeq[i],currSeq[i+1]=nxt,curr; self.setter(currSeq)
+		self.refreshEvent(forceIx=i+1)
+	def refresh(self): pass # API compat with SerializableEditor
+
+SeqSerializable=SeqSerializableComboBox
+
+
+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
+		QDialog.__init__(self,parent)
+		self.setWindowTitle('Create new object of type %s'%baseClassName)
+		self.layout=QVBoxLayout(self)
+		self.combo=QComboBox(self)
+		childs=list(yade.system.childClasses(baseClassName,includeBase=False)); childs.sort()
+		if includeBase:
+			self.combo.addItem(baseClassName)
+			self.combo.insertSeparator(1000)
+		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)
+		self.layout.addWidget(self.combo)
+		self.layout.addWidget(self.scroll)
+		self.layout.addWidget(self.buttons)
+		self.ser=None
+		self.combo.setCurrentIndex(0); self.comboSlot(0)
+		self.setWindowModality(Qt.WindowModal)
+	def comboSlot(self,index):
+		item=str(self.combo.itemText(index))
+		self.ser=eval(item+'()')
+		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)
+
+class SeqFundamentalEditor(QFrame):
+	def __init__(self,parent,getter,setter,itemType):
+		QFrame.__init__(self,parent)
+		self.getter,self.setter,self.itemType=getter,setter,itemType
+		self.layout=QVBoxLayout()
+		topLineFrame=QFrame(self); topLineLayout=QHBoxLayout(topLineFrame)
+		self.form=QFormLayout()
+		self.form.setContentsMargins(0,0,0,0)
+		self.form.setVerticalSpacing(0)
+		self.form.setLabelAlignment(Qt.AlignLeft)
+		self.formFrame=QFrame(self); self.formFrame.setLayout(self.form)
+		self.layout.addWidget(self.formFrame)
+		self.setLayout(self.layout)
+		# SerializableEditor API compat
+		self.hot=False
+		self.rebuild()
+		# periodic refresh
+		self.refreshTimer=QTimer(self)
+		self.refreshTimer.timeout.connect(self.refreshEvent)
+		self.refreshTimer.start(1000) # 1s should be enough
+	def contextMenuEvent(self, event):
+		index=self.localPositionToIndex(event.pos())
+		seq=self.getter()
+		if len(seq)==0: index=-1
+		field=self.form.itemAt(index,QFormLayout.LabelRole).widget() if index>=0 else None
+		menu=QMenu(self)
+		actNew,actKill,actUp,actDown=[menu.addAction(name) for name in (u'☘ New',u'☠ Remove',u'↑ Up',u'↓ Down')]
+		if index<0: [a.setEnabled(False) for a in actKill,actUp,actDown]
+		if index==len(seq)-1: actDown.setEnabled(False)
+		if index==0: actUp.setEnabled(False)
+		if field: field.setStyleSheet('QWidget { background: green }')
+		act=menu.exec_(self.mapToGlobal(event.pos()))
+		if field: field.setStyleSheet('QWidget { background: none }')
+		if not act: return
+		if act==actNew: self.newSlot(index)
+		elif act==actKill: self.killSlot(index)
+		elif act==actUp: self.upSlot(index)
+		elif act==actDown: self.downSlot(index)
+	def localPositionToIndex(self,pos):
+		gp=self.mapToGlobal(pos)
+		for row in range(self.form.count()/2):
+			w,i=self.form.itemAt(row,QFormLayout.FieldRole),self.form.itemAt(row,QFormLayout.LabelRole)
+			for wi in w.widget(),i.widget():
+				x0,y0,x1,y1=wi.geometry().getCoords(); globG=QRect(self.mapToGlobal(QPoint(x0,y0)),self.mapToGlobal(QPoint(x1,y1)))
+				if globG.contains(gp):
+					return row
+		return -1
+	def newSlot(self,i):
+		seq=self.getter();
+		seq.insert(i,_fundamentalInitValues.get(self.itemType,self.itemType()))
+		self.setter(seq)
+		self.rebuild()
+	def killSlot(self,i):
+		seq=self.getter(); assert(i<len(seq)); del seq[i]; self.setter(seq)
+		self.refreshEvent()
+	def upSlot(self,i):
+		seq=self.getter(); assert(i<len(seq));
+		prev,curr=seq[i-1:i+1]; seq[i-1],seq[i]=curr,prev; self.setter(seq)
+		self.refreshEvent(forceIx=i-1)
+	def downSlot(self,i):
+		seq=self.getter(); assert(i<len(seq)-1);
+		curr,nxt=seq[i:i+2]; seq[i],seq[i+1]=nxt,curr; self.setter(seq)
+		self.refreshEvent(forceIx=i+1)
+	def rebuild(self):
+		currSeq=self.getter()
+		# clear everything
+		rows=self.form.count()/2
+		for row in range(rows):
+			logging.trace('counts',self.form.rowCount(),self.form.count())
+			for wi in self.form.itemAt(row,QFormLayout.FieldRole),self.form.itemAt(row,QFormLayout.LabelRole):
+				self.form.removeItem(wi)
+				logging.trace('deleting widget',wi.widget())
+				widget=wi.widget(); widget.hide(); del widget # for some reason, deleting does not make the thing disappear visually; hiding does, however
+			logging.trace('counts after ',self.form.rowCount(),self.form.count())
+		logging.debug('cleared')
+		# add everything
+		Klass=_fundamentalEditorMap.get(self.itemType,None)
+		if not Klass:
+			errMsg=QTextEdit(self)
+			errMsg.setReadOnly(True); errMsg.setText("Sorry, editing sequences of %s's is not (yet?) implemented."%(self.itemType.__name__))
+			self.form.insertRow(0,'<b>Error</b>',errMsg)
+			return
+		class ItemGetter():
+			def __init__(self,getter,index): self.getter,self.index=getter,index
+			def __call__(self): return self.getter()[self.index]
+		class ItemSetter():
+			def __init__(self,getter,setter,index): self.getter,self.setter,self.index=getter,setter,index
+			def __call__(self,val): seq=self.getter(); seq[self.index]=val; self.setter(seq)
+		for i,item in enumerate(currSeq):
+			widget=Klass(self,ItemGetter(self.getter,i),ItemSetter(self.getter,self.setter,i)) #proxy,'value')
+			self.form.insertRow(i,'%d. '%i,widget)
+			logging.debug('added item %d %s'%(i,str(widget)))
+		if len(currSeq)==0: self.form.insertRow(0,'<i>empty</i>',QLabel('<i>(right-click for menu)</i>'))
+		logging.debug('rebuilt, will refresh now')
+		self.refreshEvent(dontRebuild=True) # avoid infinite recursion it the length would change meanwhile
+	def refreshEvent(self,dontRebuild=False,forceIx=-1):
+		currSeq=self.getter()
+		if len(currSeq)!=self.form.count()/2: #rowCount():
+			if dontRebuild: return # length changed behind our back, just pretend nothing happened and update next time instead
+			self.rebuild()
+			currSeq=self.getter()
+		for i in range(len(currSeq)):
+			item=self.form.itemAt(i,QFormLayout.FieldRole)
+			logging.trace('got item #%d %s'%(i,str(item.widget())))
+			widget=item.widget()
+			if not widget.hot:
+				widget.refresh()
+			if forceIx>=0 and forceIx==i: widget.setFocus()
+	def refresh(self): pass # SerializableEditor API
+
+
+
+

=== added file 'gui/qt5/XYZ.png'
Binary files gui/qt5/XYZ.png	1970-01-01 00:00:00 +0000 and gui/qt5/XYZ.png	2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/XYZ.xpm'
--- gui/qt5/XYZ.xpm	1970-01-01 00:00:00 +0000
+++ gui/qt5/XYZ.xpm	2015-06-26 20:19:22 +0000
@@ -0,0 +1,45 @@
+/* XPM */
+static char * XYZ_xpm[] = {
+"40 40 2 1",
+" 	c None",
+".	c #000000",
+"   ..  ..                               ",
+"    .  .                                ",
+"    ....                                ",
+"    ....                                ",
+"     ..                                 ",
+"     ..                                 ",
+"     ..                                 ",
+"     ..                                 ",
+"                                        ",
+"      .                                 ",
+"      .                                 ",
+"     ...                                ",
+"     ...                                ",
+"     ...                                ",
+"    .....                               ",
+"    .....                               ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+" ...........                            ",
+" .         .                            ",
+" . ......  .                    ..  ..  ",
+" .     ..  .            ..       .  .   ",
+" .    ..   .            .....    ....   ",
+" .   ..    ....................   ..    ",
+" .   .     .            .....     ..    ",
+" .  ..     .            ...      ....   ",
+" . ..      .                     .  .   ",
+" . ......  .                    ..  ..  ",
+" .         .                            ",
+" ...........                            ",
+"                                        ",
+"                                        "};

=== added file 'gui/qt5/YZX.png'
Binary files gui/qt5/YZX.png	1970-01-01 00:00:00 +0000 and gui/qt5/YZX.png	2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/YZX.xpm'
--- gui/qt5/YZX.xpm	1970-01-01 00:00:00 +0000
+++ gui/qt5/YZX.xpm	2015-06-26 20:19:22 +0000
@@ -0,0 +1,45 @@
+/* XPM */
+static char * YZX_xpm[] = {
+"40 40 2 1",
+" 	c None",
+".	c #000000",
+"    ......                              ",
+"        ..                              ",
+"       ..                               ",
+"      ..                                ",
+"      .                                 ",
+"     ..                                 ",
+"    ..                                  ",
+"    ......                              ",
+"                                        ",
+"      .                                 ",
+"      .                                 ",
+"     ...                                ",
+"     ...                                ",
+"     ...                                ",
+"    .....                               ",
+"    .....                               ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+" ...........                            ",
+" .         .                            ",
+" .  ..  .. .                    ..  ..  ",
+" .   .  .  .            ..       .  .   ",
+" .   ....  .            .....    ....   ",
+" .    ..   ....................  ....   ",
+" .    ..   .            .....     ..    ",
+" .   ....  .            ...       ..    ",
+" .   .  .  .                      ..    ",
+" .  ..  .. .                      ..    ",
+" .         .                            ",
+" ...........                            ",
+"                                        ",
+"                                        "};

=== added file 'gui/qt5/ZXY.png'
Binary files gui/qt5/ZXY.png	1970-01-01 00:00:00 +0000 and gui/qt5/ZXY.png	2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/ZXY.xpm'
--- gui/qt5/ZXY.xpm	1970-01-01 00:00:00 +0000
+++ gui/qt5/ZXY.xpm	2015-06-26 20:19:22 +0000
@@ -0,0 +1,45 @@
+/* XPM */
+static char * ZXY_xpm[] = {
+"40 40 2 1",
+" 	c None",
+".	c #000000",
+"    ..  ..                              ",
+"     .  .                               ",
+"     ....                               ",
+"      ..                                ",
+"      ..                                ",
+"     ....                               ",
+"     .  .                               ",
+"    ..  ..                              ",
+"                                        ",
+"      .                                 ",
+"      .                                 ",
+"     ...                                ",
+"     ...                                ",
+"     ...                                ",
+"    .....                               ",
+"    .....                               ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+"      .                                 ",
+" ...........                            ",
+" .         .                            ",
+" .  ..  .. .                    ......  ",
+" .   .  .  .            ..          ..  ",
+" .   ....  .            .....      ..   ",
+" .   ....  ....................   ..    ",
+" .    ..   .            .....     .     ",
+" .    ..   .            ...      ..     ",
+" .    ..   .                    ..      ",
+" .    ..   .                    ......  ",
+" .         .                            ",
+" ...........                            ",
+"                                        ",
+"                                        "};

=== added file 'gui/qt5/_GLViewer.cpp'
--- gui/qt5/_GLViewer.cpp	1970-01-01 00:00:00 +0000
+++ gui/qt5/_GLViewer.cpp	2015-06-26 20:19:22 +0000
@@ -0,0 +1,107 @@
+#include"GLViewer.hpp"
+#include"OpenGLManager.hpp"
+#include<pkg/common/OpenGLRenderer.hpp>
+#include<lib/pyutil/doc_opts.hpp>
+
+#include<QApplication>
+#include<QCloseEvent>
+
+namespace py=boost::python;
+
+qglviewer::Vec tuple2vec(py::tuple t){ qglviewer::Vec ret; for(int i=0;i<3;i++){py::extract<Real> e(t[i]); if(!e.check()) throw invalid_argument("Element #"+boost::lexical_cast<string>(i)+" is not a number"); ret[i]=e();} return ret;};
+py::tuple vec2tuple(qglviewer::Vec v){return py::make_tuple(v[0],v[1],v[2]);};
+
+class pyGLViewer{
+	const size_t viewNo;
+	public:
+		#define GLV if((OpenGLManager::self->views.size()<=viewNo) || !(OpenGLManager::self->views[viewNo])) throw runtime_error("No view #"+boost::lexical_cast<string>(viewNo)); GLViewer* glv=OpenGLManager::self->views[viewNo].get();
+		pyGLViewer(size_t _viewNo=0): viewNo(_viewNo){}
+		void close(){ GLV; QCloseEvent* e(new QCloseEvent); QApplication::postEvent(glv,e); }
+		py::tuple get_grid(){GLV; return py::make_tuple(bool(glv->drawGrid & 1),bool(glv->drawGrid & 2),bool(glv->drawGrid & 4));}
+		void set_grid(py::tuple t){GLV; glv->drawGrid=0; for(int i=0;i<3;i++) if(py::extract<bool>(t[i])()) glv->drawGrid+=1<<i;}
+		#define VEC_GET_SET(property,getter,setter) Vector3r get_##property(){GLV; qglviewer::Vec v=getter(); return Vector3r(v[0],v[1],v[2]); } void set_##property(const Vector3r& t){GLV;  setter(qglviewer::Vec(t[0],t[1],t[2]));}
+		VEC_GET_SET(upVector,glv->camera()->upVector,glv->camera()->setUpVector);
+		VEC_GET_SET(lookAt,glv->camera()->position()+glv->camera()->viewDirection,glv->camera()->lookAt);
+		VEC_GET_SET(viewDir,glv->camera()->viewDirection,glv->camera()->setViewDirection);
+		VEC_GET_SET(eyePosition,glv->camera()->position,glv->camera()->setPosition);
+		#define BOOL_GET_SET(property,getter,setter)void set_##property(bool b){GLV; setter(b);} bool get_##property(){GLV; return getter();}
+		BOOL_GET_SET(axes,glv->axisIsDrawn,glv->setAxisIsDrawn);
+		BOOL_GET_SET(fps,glv->FPSIsDisplayed,glv->setFPSIsDisplayed);
+		bool get_scale(){GLV; return glv->drawScale;} void set_scale(bool b){GLV; glv->drawScale=b;}
+		bool get_orthographic(){GLV; return glv->camera()->type()==qglviewer::Camera::ORTHOGRAPHIC;}
+		void set_orthographic(bool b){GLV; return glv->camera()->setType(b ? qglviewer::Camera::ORTHOGRAPHIC : qglviewer::Camera::PERSPECTIVE);}
+		int get_selection(void){ GLV; return glv->selectedName(); } void set_selection(int s){ GLV; glv->setSelectedName(s); }
+		#define FLOAT_GET_SET(property,getter,setter)void set_##property(Real r){GLV; setter(r);} Real get_##property(){GLV; return getter();}
+		FLOAT_GET_SET(sceneRadius,glv->sceneRadius,glv->setSceneRadius);
+		void fitAABB(const Vector3r& min, const Vector3r& max){GLV;  glv->camera()->fitBoundingBox(qglviewer::Vec(min[0],min[1],min[2]),qglviewer::Vec(max[0],max[1],max[2]));}
+		void fitSphere(const Vector3r& center,Real radius){GLV;  glv->camera()->fitSphere(qglviewer::Vec(center[0],center[1],center[2]),radius);}
+		void showEntireScene(){GLV;  glv->camera()->showEntireScene();}
+		void center(bool median){GLV;  if(median)glv->centerMedianQuartile(); else glv->centerScene();}
+		Vector2i get_screenSize(){GLV;  return Vector2i(glv->width(),glv->height());}
+		void set_screenSize(Vector2i t){ /*GLV;*/ OpenGLManager::self->emitResizeView(viewNo,t[0],t[1]);}
+		string pyStr(){return string("<GLViewer for view #")+boost::lexical_cast<string>(viewNo)+">";}
+		void saveDisplayParameters(size_t n){GLV;  glv->saveDisplayParameters(n);}
+		void useDisplayParameters(size_t n){GLV;  glv->useDisplayParameters(n);}
+		void loadState(string filename){GLV; QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->restoreStateFromFile(); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);}
+		void saveState(string filename){GLV;  QString origStateFileName=glv->stateFileName(); glv->setStateFileName(QString(filename.c_str())); glv->saveStateToFile(); glv->setStateFileName(origStateFileName);}
+		string get_timeDisp(){GLV;  const int& m(glv->timeDispMask); string ret; if(m&GLViewer::TIME_REAL) ret+='r'; if(m&GLViewer::TIME_VIRT) ret+="v"; if(m&GLViewer::TIME_ITER) ret+="i"; return ret;}
+		void set_timeDisp(string s){GLV;  int& m(glv->timeDispMask); m=0; FOREACH(char c, s){switch(c){case 'r': m|=GLViewer::TIME_REAL; break; case 'v': m|=GLViewer::TIME_VIRT; break; case 'i': m|=GLViewer::TIME_ITER; break; default: throw invalid_argument(string("Invalid flag for timeDisp: `")+c+"'");}}}
+		void set_bgColor(const Vector3r& c){ QColor cc(255*c[0],255*c[1],255*c[2]); GLV;  glv->setBackgroundColor(cc);} Vector3r get_bgColor(){ GLV;  QColor c(glv->backgroundColor()); return Vector3r(c.red()/255.,c.green()/255.,c.blue()/255.);}
+		void saveSnapshot(string filename) {GLV; glv->nextFrameSnapshotFilename = filename;}
+		#undef GLV
+		#undef VEC_GET_SET
+		#undef BOOL_GET_SET
+		#undef FLOAT_GET_SET
+};
+
+// ask to create a new view and wait till it exists
+pyGLViewer createView(){ 
+	int id=OpenGLManager::self->waitForNewView();
+	if(id<0) throw std::runtime_error("Unable to open new 3d view.");
+	return pyGLViewer((*OpenGLManager::self->views.rbegin())->viewId);
+}
+
+py::list getAllViews(){ py::list ret; FOREACH(const shared_ptr<GLViewer>& v, OpenGLManager::self->views){ if(v) ret.append(pyGLViewer(v->viewId)); } return ret; };
+void centerViews(void){ OpenGLManager::self->centerAllViews(); }
+
+shared_ptr<OpenGLRenderer> getRenderer(){ return OpenGLManager::self->renderer; }
+
+BOOST_PYTHON_MODULE(_GLViewer){
+	YADE_SET_DOCSTRING_OPTS;
+	
+	OpenGLManager* glm=new OpenGLManager(); // keep this singleton object forever
+	glm->emitStartTimer();
+
+	py::def("View",createView,"Create a new 3d view.");
+	py::def("center",centerViews,"Center all views.");
+	py::def("views",getAllViews,"Return list of all open :yref:`yade.qt.GLViewer` objects");
+	
+	py::def("Renderer",&getRenderer,"Return the active :yref:`OpenGLRenderer` object.");
+
+	py::class_<pyGLViewer>("GLViewer",py::no_init)
+		.add_property("upVector",&pyGLViewer::get_upVector,&pyGLViewer::set_upVector,"Vector that will be shown oriented up on the screen.")
+		.add_property("lookAt",&pyGLViewer::get_lookAt,&pyGLViewer::set_lookAt,"Point at which camera is directed.")
+		.add_property("viewDir",&pyGLViewer::get_viewDir,&pyGLViewer::set_viewDir,"Camera orientation (as vector).")
+		.add_property("eyePosition",&pyGLViewer::get_eyePosition,&pyGLViewer::set_eyePosition,"Camera position.")
+		.add_property("grid",&pyGLViewer::get_grid,&pyGLViewer::set_grid,"Display square grid in zero planes, as 3-tuple of bools for yz, xz, xy planes.")
+		.add_property("fps",&pyGLViewer::get_fps,&pyGLViewer::set_fps,"Show frames per second indicator.")
+		.add_property("axes",&pyGLViewer::get_axes,&pyGLViewer::set_axes,"Show arrows for axes.")
+		.add_property("scale",&pyGLViewer::get_scale,&pyGLViewer::set_scale,"Scale of the view (?)")
+		.add_property("sceneRadius",&pyGLViewer::get_sceneRadius,&pyGLViewer::set_sceneRadius,"Visible scene radius.")
+		.add_property("ortho",&pyGLViewer::get_orthographic,&pyGLViewer::set_orthographic,"Whether orthographic projection is used; if false, use perspective projection.")
+		.add_property("screenSize",&pyGLViewer::get_screenSize,&pyGLViewer::set_screenSize,"Size of the viewer's window, in scree pixels")
+		.add_property("timeDisp",&pyGLViewer::get_timeDisp,&pyGLViewer::set_timeDisp,"Time displayed on in the vindow; is a string composed of characters *r*, *v*, *i* standing respectively for real time, virtual time, iteration number.")
+		// .add_property("bgColor",&pyGLViewer::get_bgColor,&pyGLViewer::set_bgColor) // useless: OpenGLRenderer::Background_color is used via openGL directly, bypassing QGLViewer background property
+		.def("fitAABB",&pyGLViewer::fitAABB,(py::arg("mn"),py::arg("mx")),"Adjust scene bounds so that Axis-aligned bounding box given by its lower and upper corners *mn*, *mx* fits in.")
+		.def("fitSphere",&pyGLViewer::fitSphere,(py::arg("center"),py::arg("radius")),"Adjust scene bounds so that sphere given by *center* and *radius* fits in.")
+		.def("showEntireScene",&pyGLViewer::showEntireScene)
+		.def("center",&pyGLViewer::center,(py::arg("median")=true),"Center view. View is centered either so that all bodies fit inside (*median* = False), or so that 75\% of bodies fit inside (*median* = True).")
+		.def("saveState",&pyGLViewer::saveState,(py::arg("stateFilename")=".qglviewer.xml"),"Save display parameters into a file. Saves state for both :yref:`GLViewer<yade._qt.GLViewer>` and associated :yref:`OpenGLRenderer`.")
+		.def("loadState",&pyGLViewer::loadState,(py::arg("stateFilename")=".qglviewer.xml"),"Load display parameters from file saved previously into.")
+		.def("__repr__",&pyGLViewer::pyStr).def("__str__",&pyGLViewer::pyStr)
+		.def("close",&pyGLViewer::close)
+		.def("saveSnapshot",&pyGLViewer::saveSnapshot,(py::arg("filename")),"Save the current view to image file")
+		.add_property("selection",&pyGLViewer::get_selection,&pyGLViewer::set_selection)
+		;
+}
+

=== added file 'gui/qt5/__init__.py'
--- gui/qt5/__init__.py	1970-01-01 00:00:00 +0000
+++ gui/qt5/__init__.py	2015-06-26 20:19:22 +0000
@@ -0,0 +1,289 @@
+# encoding: utf-8
+import yade.runtime
+if not yade.runtime.hasDisplay:
+	msg = "Connecting to DISPLAY at Yade startup failed, unable to activate the qt4 interface."
+	import os
+	if 'YADE_BATCH' in os.environ:
+		msg += "\nDo not import qt when running in batch mode."
+	raise ImportError(msg)
+
+from PyQt5.QtGui import *
+from PyQt5 import QtCore
+
+from yade.qt.ui_controller import Ui_Controller
+
+from yade.qt.Inspector import *
+from yade import *
+import yade.system, yade.config
+
+from yade.qt._GLViewer import *
+
+maxWebWindows=1
+"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/doc/'
+"Base URL for the documentation. Packaged versions should change to the local installation directory."
+
+
+import os.path
+# find if we have docs installed locally from package
+sphinxLocalDocPath=yade.config.prefix+'/share/doc/yade'+yade.config.suffix+'-doc/html/'
+sphinxBuildDocPath=yade.config.sourceRoot+'/doc/sphinx/_build/html/'
+# we prefer the packaged documentation for this version, if installed
+if   os.path.exists(sphinxLocalDocPath+'/index.html'): sphinxPrefix='file://'+sphinxLocalDocPath
+# otherwise look for documentation generated in the source tree
+elif  os.path.exists(sphinxBuildDocPath+'/index.html'): sphinxPrefix='file://'+sphinxBuildDocPath
+# fallback to online docs
+else: sphinxPrefix=sphinxOnlineDocPath
+
+sphinxDocWrapperPage=sphinxPrefix+'/yade.wrapper.html'
+
+
+
+
+def openUrl(url):
+	from PyQt5 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])
+		#print 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 0:
+		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_()
+
+
+controller=None
+
+class ControllerClass(QWidget,Ui_Controller):
+	def __init__(self,parent=None):
+		QWidget.__init__(self)
+		self.setupUi(self)
+		self.generator=None # updated automatically
+		self.renderer=Renderer() # only hold one instance, managed by OpenGLManager
+		self.addPreprocessors()
+		self.addRenderers()
+		global controller
+		controller=self
+		self.refreshTimer=QtCore.QTimer()
+		self.refreshTimer.timeout.connect(self.refreshEvent)
+		self.refreshTimer.start(200)
+		self.iterPerSecTimeout=1 # how often to recompute the number of iterations per second
+		self.iterTimes,self.iterValues,self.iterPerSec=[],[],0 # arrays to keep track of the simulation speed
+		self.dtEditUpdate=True # to avoid updating while being edited
+		# show off with this one as well now
+	def addPreprocessors(self):
+		for c in yade.system.childClasses('FileGenerator'):
+			self.generatorCombo.addItem(c)
+	def addRenderers(self):
+		self.displayCombo.addItem('OpenGLRenderer'); afterSep=1
+		for bc in ('GlShapeFunctor','GlStateFunctor','GlBoundFunctor','GlIGeomFunctor','GlIPhysFunctor'):
+			if afterSep>0: self.displayCombo.insertSeparator(10000); afterSep=0
+			for c in yade.system.childClasses(bc) | set([bc]):
+				inst=eval(c+'()');
+				if len(set(inst.dict().keys())-set(['label']))>0:
+					self.displayCombo.addItem(c); afterSep+=1
+	def inspectSlot(self):
+		self.inspector=SimulationInspector(parent=None)
+		self.inspector.show()
+	def setTabActive(self,what):
+		if what=='simulation': ix=0
+		elif what=='display': ix=1
+		elif what=='generator': ix=2
+		elif what=='python': ix=3
+		else: raise ValueErorr("No such tab: "+what)
+		self.controllerTabs.setCurrentIndex(ix)
+	def generatorComboSlot(self,genStr):
+		"update generator parameters when a new one is selected"
+		gen=eval(str(genStr)+'()')
+		self.generator=gen
+		se=SerializableEditor(gen,parent=self.generatorArea,showType=True)
+		self.generatorArea.setWidget(se)
+	def pythonComboSlot(self,cmd):
+		try:
+			code=compile(str(cmd),'<UI entry>','exec')
+			exec code in globals()
+		except:
+			import traceback
+			traceback.print_exc()
+	def generateSlot(self):
+		filename=str(self.generatorFilenameEdit.text())
+		if self.generatorMemoryCheck.isChecked():
+			filename=':memory:'+filename
+			print 'BUG: Saving to memory slots freezes Yade (cause unknown). Cross fingers.'
+		#print 'Will save to ',filename
+		self.generator.generate(filename)
+		if self.generatorAutoCheck:
+			O.load(filename)
+			self.setTabActive('simulation')
+			if len(views())==0:
+				v=View(); v.center()
+	def displayComboSlot(self,dispStr):
+		ser=(self.renderer if dispStr=='OpenGLRenderer' else eval(str(dispStr)+'()'))
+		path='yade.qt.Renderer()' if dispStr=='OpenGLRenderer' else dispStr
+		se=SerializableEditor(ser,parent=self.displayArea,ignoredAttrs=set(['label']),showType=True,path=path)
+		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);; *.*')
+		f=str(f)
+		if not f: return # cancelled
+		self.deactivateControls()
+		O.load(f)
+	def saveSlot(self):
+		f=QFileDialog.getSaveFileName(self,'Save simulation','','Yade simulations (*.xml *.xml.bz2 *.xml.gz *.yade *.yade.gz *.yade.bz2);; *.*')
+		f=str(f)
+		if not f: return # cancelled
+		O.save(f)
+	def reloadSlot(self):
+		self.deactivateControls()
+		from yade import plot
+		plot.splitData()
+		O.reload()
+	def dtFixedSlot(self):
+		O.dt=O.dt
+		O.dynDt=False
+	def dtDynSlot(self):
+		O.dt=-O.dt
+	def dtEditNoupdateSlot(self):
+		self.dtEditUpdate=False
+	def dtEditedSlot(self):
+		try:
+			t=float(self.dtEdit.text())
+			O.dt=t
+		except ValueError: pass
+		self.dtEdit.setText(str(O.dt))
+		self.dtEditUpdate=True
+	def playSlot(self):	O.run()
+	def pauseSlot(self): O.pause()
+	def stepSlot(self):  O.step()
+	def subStepSlot(self,value): O.subStepping=bool(value)
+	def show3dSlot(self, show):
+		vv=views()
+		assert(len(vv) in (0,1))
+		if show:
+			if len(vv)==0: View()
+		else:
+			if len(vv)>0: vv[0].close()
+	def setReferenceSlot(self):
+		# sets reference periodic cell as well
+		utils.setRefSe3()
+	def centerSlot(self):
+		for v in views(): v.center()
+	def setViewAxes(self,dir,up):
+		try:
+			v=views()[0]
+			v.viewDir=dir
+			v.upVector=up
+			v.center()
+		except IndexError: pass
+	def xyzSlot(self): self.setViewAxes((0,0,-1),(0,1,0))
+	def yzxSlot(self): self.setViewAxes((-1,0,0),(0,0,1))
+	def zxySlot(self): self.setViewAxes((0,-1,0),(1,0,0))
+	def refreshEvent(self):
+		self.refreshValues()
+		self.activateControls()
+	def deactivateControls(self):
+		self.realTimeLabel.setText('')
+		self.virtTimeLabel.setText('')
+		self.iterLabel.setText('')
+		self.fileLabel.setText('<i>[loading]</i>')
+		self.playButton.setEnabled(False)
+		self.pauseButton.setEnabled(False)
+		self.stepButton.setEnabled(False)
+		self.subStepCheckbox.setEnabled(False)
+		self.reloadButton.setEnabled(False)
+		self.dtFixedRadio.setEnabled(False)
+		self.dtDynRadio.setEnabled(False)
+		self.dtEdit.setEnabled(False)
+		self.dtEdit.setText('')
+		self.dtEditUpdate=True
+	def activateControls(self):
+		hasSim=len(O.engines)>0
+		running=O.running
+		if hasSim:
+			self.playButton.setEnabled(not running)
+			self.pauseButton.setEnabled(running)
+			self.reloadButton.setEnabled(O.filename is not None)
+			self.stepButton.setEnabled(not running)
+			self.subStepCheckbox.setEnabled(not running)
+		else:
+			self.playButton.setEnabled(False)
+			self.pauseButton.setEnabled(False)
+			self.reloadButton.setEnabled(False)
+			self.stepButton.setEnabled(False)
+			self.subStepCheckbox.setEnabled(False)
+		self.dtFixedRadio.setEnabled(True)
+		self.dtDynRadio.setEnabled(O.dynDtAvailable)
+		dynDt=O.dynDt
+		self.dtFixedRadio.setChecked(not dynDt)
+		self.dtDynRadio.setChecked(dynDt)
+		if dynDt or self.dtEditUpdate:
+			self.dtEdit.setText(str(O.dt))
+		if dynDt: self.dtEditUpdate=True
+		self.dtEdit.setEnabled(not dynDt)
+		fn=O.filename
+		self.fileLabel.setText(fn if fn else '<i>[no file]</i>')
+
+	def refreshValues(self):
+		rt=int(O.realtime); t=O.time; iter=O.iter;
+		assert(len(self.iterTimes)==len(self.iterValues))
+		if len(self.iterTimes)==0: self.iterTimes.append(rt); self.iterValues.append(iter); self.iterPerSec=0 # update always for the first time
+		elif rt-self.iterTimes[-1]>self.iterPerSecTimeout: # update after a timeout
+			if len(self.iterTimes)==1: self.iterTimes.append(self.iterTimes[0]); self.iterValues.append(self.iterValues[0]) # 2 values, first one is bogus
+			self.iterTimes[0]=self.iterTimes[1]; self.iterValues[0]=self.iterValues[1]
+			self.iterTimes[1]=rt; self.iterValues[1]=iter;
+			self.iterPerSec=(self.iterValues[-1]-self.iterValues[-2])/(self.iterTimes[-1]-self.iterTimes[-2])
+		if not O.running: self.iterPerSec=0
+		stopAtIter=O.stopAtIter
+		subStepInfo=''
+		if O.subStepping:
+			subStep=O.subStep
+			if subStep==-1: subStepInfo=u'→ <i>prologue</i>'
+			elif subStep>=0 and subStep<len(O.engines):
+				e=O.engines[subStep]; subStepInfo=u'→ %s'%(e.label if e.label else e.__class__.__name__)
+			elif subStep==len(O.engines): subStepInfo=u'→ <i>epilogue</i>'
+			else: raise RuntimeError("Invalid O.subStep value %d, should be ∈{-1,…,len(o.engines)}"%subStep)
+			subStepInfo="<br><small>sub %d/%d [%s]</small>"%(subStep,len(O.engines),subStepInfo)
+		self.subStepCheckbox.setChecked(O.subStepping) # might have been changed async
+		if stopAtIter<=iter:
+			self.realTimeLabel.setText('%02d:%02d:%02d'%(rt//3600,(rt%3600)//60,rt%60))
+			self.iterLabel.setText('#%ld, %.1f/s %s'%(iter,self.iterPerSec,subStepInfo))
+		else:
+			e=int((stopAtIter-iter)*self.iterPerSec)
+			self.realTimeLabel.setText('%02d:%02d:%02d (ETA %02d:%02d:%02d)'%(rt//3600,rt//60,rt%60,e//3600,e//60,e%60))
+			self.iterLabel.setText('#%ld / %ld, %.1f/s %s'%(O.iter,stopAtIter,self.iterPerSec,subStepInfo))
+		if t!=float('inf'):
+			s=int(t); ms=int(t*1000)%1000; us=int(t*1000000)%1000; ns=int(t*1000000000)%1000
+			self.virtTimeLabel.setText(u'%03ds%03dm%03dμ%03dn'%(s,ms,us,ns))
+		else: self.virtTimeLabel.setText(u'[ ∞ ] ?!')
+		self.show3dButton.setChecked(len(views())>0)
+		
+def Generator():
+	global controller
+	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')
+def Inspector():
+	global controller
+	if not controller: controller=ControllerClass();
+	controller.inspectSlot()

=== added file 'gui/qt5/controller.ui'
--- gui/qt5/controller.ui	1970-01-01 00:00:00 +0000
+++ gui/qt5/controller.ui	2015-06-26 20:19:22 +0000
@@ -0,0 +1,1135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Controller</class>
+ <widget class="QDialog" name="Controller">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>290</width>
+    <height>495</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Yade</string>
+  </property>
+  <property name="windowIcon">
+   <iconset resource="img.qrc">
+    <normaloff>:/img/yade-favicon.xpm</normaloff>:/img/yade-favicon.xpm</iconset>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_3">
+   <property name="margin">
+    <number>0</number>
+   </property>
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <item row="0" column="0">
+    <widget class="QTabWidget" name="controllerTabs">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>Simulation</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout">
+       <property name="margin">
+        <number>0</number>
+       </property>
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <item row="0" column="0">
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <property name="margin">
+          <number>6</number>
+         </property>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <property name="sizeConstraint">
+            <enum>QLayout::SetMinAndMaxSize</enum>
+           </property>
+           <item>
+            <widget class="QPushButton" name="loadButton">
+             <property name="enabled">
+              <bool>true</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>0</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>Load</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QPushButton" name="saveButton">
+             <property name="enabled">
+              <bool>true</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="minimumSize">
+              <size>
+               <width>0</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>Save</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QPushButton" name="inspectButton">
+             <property name="text">
+              <string>Inspect</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QFormLayout" name="formLayout">
+           <property name="sizeConstraint">
+            <enum>QLayout::SetMinimumSize</enum>
+           </property>
+           <property name="fieldGrowthPolicy">
+            <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+           </property>
+           <property name="horizontalSpacing">
+            <number>6</number>
+           </property>
+           <property name="verticalSpacing">
+            <number>6</number>
+           </property>
+           <property name="margin">
+            <number>6</number>
+           </property>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_6">
+             <property name="text">
+              <string>real</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLabel" name="realTimeLabel">
+             <property name="text">
+              <string>00:00:00</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="label_7">
+             <property name="text">
+              <string>virt</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLabel" name="virtTimeLabel">
+             <property name="text">
+              <string>00:000.000m000μ000n</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="label_8">
+             <property name="text">
+              <string>iter</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QLabel" name="iterLabel">
+             <property name="text">
+              <string>#0, 0.0/s</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0">
+            <widget class="QLabel" name="label_9">
+             <property name="text">
+              <string>Δt</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="1">
+            <layout class="QVBoxLayout" name="verticalLayout">
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout">
+               <item>
+                <widget class="QRadioButton" name="dtFixedRadio">
+                 <property name="text">
+                  <string>fixed</string>
+                 </property>
+                 <property name="checked">
+                  <bool>true</bool>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QRadioButton" name="dtDynRadio">
+                 <property name="enabled">
+                  <bool>false</bool>
+                 </property>
+                 <property name="text">
+                  <string>time stepper</string>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <widget class="QLineEdit" name="dtEdit">
+               <property name="enabled">
+                <bool>false</bool>
+               </property>
+               <property name="focusPolicy">
+                <enum>Qt::ClickFocus</enum>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_5">
+           <item>
+            <spacer name="horizontalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QLabel" name="fileLabel">
+             <property name="text">
+              <string>[no file]</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QVBoxLayout" name="verticalLayout_2">
+           <property name="spacing">
+            <number>6</number>
+           </property>
+           <property name="margin">
+            <number>6</number>
+           </property>
+           <item>
+            <layout class="QHBoxLayout" name="horizontalLayout_3">
+             <property name="spacing">
+              <number>0</number>
+             </property>
+             <item>
+              <widget class="QPushButton" name="playButton">
+               <property name="enabled">
+                <bool>false</bool>
+               </property>
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                 <horstretch>5</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="font">
+                <font>
+                 <pointsize>18</pointsize>
+                </font>
+               </property>
+               <property name="text">
+                <string>▶</string>
+               </property>
+               <property name="iconSize">
+                <size>
+                 <width>32</width>
+                 <height>32</height>
+                </size>
+               </property>
+               <property name="default">
+                <bool>true</bool>
+               </property>
+               <property name="flat">
+                <bool>false</bool>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QPushButton" name="pauseButton">
+               <property name="enabled">
+                <bool>false</bool>
+               </property>
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                 <horstretch>4</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="font">
+                <font>
+                 <pointsize>18</pointsize>
+                </font>
+               </property>
+               <property name="text">
+                <string>▮▮</string>
+               </property>
+               <property name="iconSize">
+                <size>
+                 <width>32</width>
+                 <height>32</height>
+                </size>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item>
+            <layout class="QHBoxLayout" name="horizontalLayout_4">
+             <property name="spacing">
+              <number>0</number>
+             </property>
+             <item>
+              <layout class="QGridLayout" name="gridLayout_9" rowstretch="0,0" columnstretch="0" rowminimumheight="0,0" columnminimumwidth="0">
+               <property name="margin">
+                <number>0</number>
+               </property>
+               <property name="spacing">
+                <number>0</number>
+               </property>
+               <item row="0" column="0">
+                <widget class="QPushButton" name="stepButton">
+                 <property name="enabled">
+                  <bool>false</bool>
+                 </property>
+                 <property name="sizePolicy">
+                  <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                   <horstretch>2</horstretch>
+                   <verstretch>0</verstretch>
+                  </sizepolicy>
+                 </property>
+                 <property name="font">
+                  <font>
+                   <pointsize>12</pointsize>
+                  </font>
+                 </property>
+                 <property name="text">
+                  <string>▶▮</string>
+                 </property>
+                 <property name="iconSize">
+                  <size>
+                   <width>32</width>
+                   <height>32</height>
+                  </size>
+                 </property>
+                </widget>
+               </item>
+               <item row="1" column="0">
+                <widget class="QCheckBox" name="subStepCheckbox">
+                 <property name="font">
+                  <font>
+                   <pointsize>7</pointsize>
+                  </font>
+                 </property>
+                 <property name="text">
+                  <string>sub-step</string>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <widget class="QPushButton" name="reloadButton">
+               <property name="enabled">
+                <bool>false</bool>
+               </property>
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+                 <horstretch>5</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="font">
+                <font>
+                 <pointsize>22</pointsize>
+                </font>
+               </property>
+               <property name="text">
+                <string>↻</string>
+               </property>
+               <property name="iconSize">
+                <size>
+                 <width>32</width>
+                 <height>32</height>
+                </size>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QGridLayout" name="gridLayout_2">
+           <item row="0" column="0">
+            <widget class="QPushButton" name="show3dButton">
+             <property name="text">
+              <string>Show 3D</string>
+             </property>
+             <property name="checkable">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QPushButton" name="referenceButton">
+             <property name="text">
+              <string>Reference</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="2">
+            <widget class="QPushButton" name="centerButton">
+             <property name="text">
+              <string>Center</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QPushButton" name="xyzButton">
+             <property name="minimumSize">
+              <size>
+               <width>48</width>
+               <height>48</height>
+              </size>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+             <property name="icon">
+              <iconset resource="img.qrc">
+               <normaloff>:/img/XYZ.xpm</normaloff>:/img/XYZ.xpm</iconset>
+             </property>
+             <property name="iconSize">
+              <size>
+               <width>40</width>
+               <height>40</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QPushButton" name="yzxButton">
+             <property name="minimumSize">
+              <size>
+               <width>48</width>
+               <height>48</height>
+              </size>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+             <property name="icon">
+              <iconset resource="img.qrc">
+               <normaloff>:/img/YZX.xpm</normaloff>:/img/YZX.xpm</iconset>
+             </property>
+             <property name="iconSize">
+              <size>
+               <width>40</width>
+               <height>40</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="2">
+            <widget class="QPushButton" name="zxyButton">
+             <property name="minimumSize">
+              <size>
+               <width>48</width>
+               <height>48</height>
+              </size>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+             <property name="icon">
+              <iconset resource="img.qrc">
+               <normaloff>:/img/ZXY.xpm</normaloff>:/img/ZXY.xpm</iconset>
+             </property>
+             <property name="iconSize">
+              <size>
+               <width>40</width>
+               <height>40</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="title">
+       <string>Display</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_7">
+       <property name="margin">
+        <number>0</number>
+       </property>
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <item row="0" column="0">
+        <widget class="QComboBox" name="displayCombo"/>
+       </item>
+       <item row="1" column="0">
+        <widget class="QScrollArea" name="displayArea">
+         <property name="widgetResizable">
+          <bool>true</bool>
+         </property>
+         <widget class="QWidget" name="displayAreaWidget">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>284</width>
+            <height>433</height>
+           </rect>
+          </property>
+         </widget>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_4">
+      <attribute name="title">
+       <string>Generate</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_6">
+       <property name="margin">
+        <number>0</number>
+       </property>
+       <item row="0" column="0">
+        <layout class="QGridLayout" name="gridLayout_5">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item row="0" column="0">
+          <widget class="QComboBox" name="generatorCombo"/>
+         </item>
+         <item row="1" column="0">
+          <widget class="QScrollArea" name="generatorArea">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+             <horstretch>200</horstretch>
+             <verstretch>200</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>0</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="widgetResizable">
+            <bool>true</bool>
+           </property>
+           <widget class="QWidget" name="generatorAreaWidget">
+            <property name="geometry">
+             <rect>
+              <x>0</x>
+              <y>0</y>
+              <width>500</width>
+              <height>500</height>
+             </rect>
+            </property>
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>500</width>
+              <height>500</height>
+             </size>
+            </property>
+            <property name="maximumSize">
+             <size>
+              <width>398</width>
+              <height>336</height>
+             </size>
+            </property>
+           </widget>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <layout class="QGridLayout" name="gridLayout_4">
+           <item row="0" column="0">
+            <widget class="QCheckBox" name="generatorMemoryCheck">
+             <property name="enabled">
+              <bool>false</bool>
+             </property>
+             <property name="text">
+              <string>memory slot</string>
+             </property>
+             <property name="checkable">
+              <bool>false</bool>
+             </property>
+             <property name="checked">
+              <bool>false</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1" colspan="2">
+            <widget class="QLineEdit" name="generatorFilenameEdit">
+             <property name="text">
+              <string>/tmp/scene.yade.gz</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0" colspan="2">
+            <widget class="QCheckBox" name="generatorAutoCheck">
+             <property name="text">
+              <string>open automatically</string>
+             </property>
+             <property name="checked">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="2">
+            <widget class="QPushButton" name="generateButton">
+             <property name="text">
+              <string>Generate</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_3">
+      <attribute name="title">
+       <string>Python</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_8">
+       <item row="0" column="0">
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <item>
+          <widget class="QComboBox" name="pythonCombo">
+           <property name="font">
+            <font>
+             <family>Monospace</family>
+            </font>
+           </property>
+           <property name="cursor">
+            <cursorShape>IBeamCursor</cursorShape>
+           </property>
+           <property name="focusPolicy">
+            <enum>Qt::StrongFocus</enum>
+           </property>
+           <property name="autoFillBackground">
+            <bool>false</bool>
+           </property>
+           <property name="editable">
+            <bool>true</bool>
+           </property>
+           <property name="insertPolicy">
+            <enum>QComboBox::InsertAtTop</enum>
+           </property>
+           <property name="minimumContentsLength">
+            <number>1</number>
+           </property>
+           <property name="duplicatesEnabled">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>&lt;i&gt;(Output appears in the terminal)&lt;/i&gt;</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="verticalSpacer">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>40</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="img.qrc"/>
+ </resources>
+ <connections>
+  <connection>
+   <sender>loadButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>loadSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>93</x>
+     <y>64</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>0</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>saveButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>saveSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>184</x>
+     <y>64</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>0</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>dtFixedRadio</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>dtFixedSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>143</x>
+     <y>170</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>0</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>dtDynRadio</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>dtDynSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>268</x>
+     <y>176</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>4</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>dtEdit</sender>
+   <signal>editingFinished()</signal>
+   <receiver>Controller</receiver>
+   <slot>dtEditedSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>147</x>
+     <y>204</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>43</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>playButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>playSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>116</x>
+     <y>304</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>3</x>
+     <y>309</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>pauseButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>pauseSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>268</x>
+     <y>304</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>311</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>referenceButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>setReferenceSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>184</x>
+     <y>429</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>5</x>
+     <y>469</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>centerButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>centerSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>275</x>
+     <y>429</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>469</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>xyzButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>xyzSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>93</x>
+     <y>460</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>0</x>
+     <y>469</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>yzxButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>yzxSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>184</x>
+     <y>485</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>243</x>
+     <y>469</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>zxyButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>zxySlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>275</x>
+     <y>485</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>469</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>generateButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>generateSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>101</x>
+     <y>60</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>469</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>dtEdit</sender>
+   <signal>textEdited(QString)</signal>
+   <receiver>Controller</receiver>
+   <slot>dtEditNoupdateSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>147</x>
+     <y>204</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>205</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>dtEdit</sender>
+   <signal>cursorPositionChanged(int,int)</signal>
+   <receiver>Controller</receiver>
+   <slot>dtEditNoupdateSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>147</x>
+     <y>204</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>181</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>generatorCombo</sender>
+   <signal>currentIndexChanged(QString)</signal>
+   <receiver>Controller</receiver>
+   <slot>generatorComboSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>101</x>
+     <y>60</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>130</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>displayCombo</sender>
+   <signal>currentIndexChanged(QString)</signal>
+   <receiver>Controller</receiver>
+   <slot>displayComboSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>101</x>
+     <y>45</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>108</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>pythonCombo</sender>
+   <signal>activated(QString)</signal>
+   <receiver>Controller</receiver>
+   <slot>pythonComboSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>107</x>
+     <y>66</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>68</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>inspectButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>inspectSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>247</x>
+     <y>54</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>0</x>
+     <y>58</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>reloadButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>reloadSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>194</x>
+     <y>363</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>284</x>
+     <y>437</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>stepButton</sender>
+   <signal>clicked()</signal>
+   <receiver>Controller</receiver>
+   <slot>stepSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>79</x>
+     <y>363</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>6</x>
+     <y>450</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>subStepCheckbox</sender>
+   <signal>stateChanged(int)</signal>
+   <receiver>Controller</receiver>
+   <slot>subStepSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>42</x>
+     <y>376</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>-2</x>
+     <y>381</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>show3dButton</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>Controller</receiver>
+   <slot>show3dSlot(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>42</x>
+     <y>418</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>-2</x>
+     <y>422</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>loadSlot()</slot>
+  <slot>saveSlot()</slot>
+  <slot>reloadSlot()</slot>
+  <slot>playSlot()</slot>
+  <slot>pauseSlot()</slot>
+  <slot>stepSlot()</slot>
+  <slot>setReferenceSlot()</slot>
+  <slot>centerSlot()</slot>
+  <slot>dtFixedSlot()</slot>
+  <slot>dtDynSlot()</slot>
+  <slot>dtEditedSlot()</slot>
+  <slot>xyzSlot()</slot>
+  <slot>yzxSlot()</slot>
+  <slot>zxySlot()</slot>
+  <slot>generateSlot()</slot>
+  <slot>dtEditNoupdateSlot()</slot>
+  <slot>generatorComboSlot()</slot>
+  <slot>displayComboSlot()</slot>
+  <slot>pythonComboSlot()</slot>
+  <slot>pythonEditSlot()</slot>
+  <slot>inspectSlot()</slot>
+  <slot>subStepSlot()</slot>
+  <slot>show3dSlot(bool)</slot>
+ </slots>
+</ui>

=== added file 'gui/qt5/img.qrc'
--- gui/qt5/img.qrc	1970-01-01 00:00:00 +0000
+++ gui/qt5/img.qrc	2015-06-26 20:19:22 +0000
@@ -0,0 +1,8 @@
+<RCC>
+  <qresource prefix="img">
+    <file>yade-favicon.xpm</file>
+    <file>XYZ.xpm</file>
+    <file>YZX.xpm</file>
+    <file>ZXY.xpm</file>
+  </qresource>
+</RCC>

=== added file 'gui/qt5/yade-favicon.png'
Binary files gui/qt5/yade-favicon.png	1970-01-01 00:00:00 +0000 and gui/qt5/yade-favicon.png	2015-06-26 20:19:22 +0000 differ
=== added file 'gui/qt5/yade-favicon.xpm'
--- gui/qt5/yade-favicon.xpm	1970-01-01 00:00:00 +0000
+++ gui/qt5/yade-favicon.xpm	2015-06-26 20:19:22 +0000
@@ -0,0 +1,229 @@
+/* XPM */
+static char * yade_favicon_xpm[] = {
+"16 16 210 2",
+"  	c None",
+". 	c #F47372",
+"+ 	c #F1B4B5",
+"@ 	c #909092",
+"# 	c #EA5A5A",
+"$ 	c #8E4D3C",
+"% 	c #746C35",
+"& 	c #616233",
+"* 	c #4B4B0E",
+"= 	c #939191",
+"- 	c #8A8A8A",
+"; 	c #808380",
+"> 	c #6F6E6D",
+", 	c #4F4D2B",
+"' 	c #646427",
+") 	c #383827",
+"! 	c #EB9D9E",
+"~ 	c #7F6B5D",
+"{ 	c #828500",
+"] 	c #A2A200",
+"^ 	c #838500",
+"/ 	c #404031",
+"( 	c #AEAEAE",
+"_ 	c #8C8C8C",
+": 	c #ABAAAF",
+"< 	c #444319",
+"[ 	c #9E9C01",
+"} 	c #B4B106",
+"| 	c #908E0C",
+"1 	c #4C4C4F",
+"2 	c #D0BBBB",
+"3 	c #B07E83",
+"4 	c #706137",
+"5 	c #B9B704",
+"6 	c #C8C901",
+"7 	c #686800",
+"8 	c #4C4C51",
+"9 	c #A3A1A3",
+"0 	c #ABA9AC",
+"a 	c #45444A",
+"b 	c #8F8E00",
+"c 	c #A3A005",
+"d 	c #AAA809",
+"e 	c #25241B",
+"f 	c #D79090",
+"g 	c #E25E5E",
+"h 	c #FCBEC9",
+"i 	c #E6AF6F",
+"j 	c #D6B700",
+"k 	c #ACAA00",
+"l 	c #3F4000",
+"m 	c #53525D",
+"n 	c #666472",
+"o 	c #6A6A02",
+"p 	c #B7B509",
+"q 	c #A3A407",
+"r 	c #3F4008",
+"s 	c #E07B7A",
+"t 	c #E35151",
+"u 	c #DC6565",
+"v 	c #C76B67",
+"w 	c #D79E42",
+"x 	c #ABA10D",
+"y 	c #99A400",
+"z 	c #393C21",
+"A 	c #40401D",
+"B 	c #9A9A01",
+"C 	c #B6B406",
+"D 	c #737100",
+"E 	c #545157",
+"F 	c #DCA8A8",
+"G 	c #DAA5A7",
+"H 	c #D86261",
+"I 	c #D85D5D",
+"J 	c #DEC7D1",
+"K 	c #E1BE91",
+"L 	c #B5C100",
+"M 	c #848D04",
+"N 	c #818103",
+"O 	c #8C8C08",
+"P 	c #7A7A02",
+"Q 	c #504F33",
+"R 	c #827F8D",
+"S 	c #C85757",
+"T 	c #CC6666",
+"U 	c #D65757",
+"V 	c #DB5353",
+"W 	c #E59596",
+"X 	c #E58A82",
+"Y 	c #BFB500",
+"Z 	c #B4BD05",
+"` 	c #BBBA0B",
+" .	c #A4A306",
+"..	c #5E5D13",
+"+.	c #565463",
+"@.	c #B47070",
+"#.	c #C69392",
+"$.	c #D57E7E",
+"%.	c #DA2929",
+"&.	c #ED393A",
+"*.	c #8B3537",
+"=.	c #949700",
+"-.	c #9AA706",
+";.	c #ABB10A",
+">.	c #747502",
+",.	c #2C2B35",
+"'.	c #B2AFBB",
+").	c #B86363",
+"!.	c #C8BEBE",
+"~.	c #DBCFCF",
+"{.	c #DF7373",
+"].	c #F16466",
+"^.	c #E79DA6",
+"/.	c #5A5408",
+"(.	c #EEAC06",
+"_.	c #EBBA08",
+":.	c #312E0B",
+"<.	c #7D7B8A",
+"[.	c #8C899A",
+"}.	c #858394",
+"|.	c #777588",
+"1.	c #B75E5E",
+"2.	c #C38181",
+"3.	c #D58383",
+"4.	c #AB5B5A",
+"5.	c #DF484A",
+"6.	c #F28088",
+"7.	c #5F4507",
+"8.	c #FD8D08",
+"9.	c #FFBD0C",
+"0.	c #3D330C",
+"a.	c #B8B5C6",
+"b.	c #858297",
+"c.	c #BD6464",
+"d.	c #C24748",
+"e.	c #CD5E5F",
+"f.	c #D2ADAE",
+"g.	c #DA8E91",
+"h.	c #E73643",
+"i.	c #5B3206",
+"j.	c #EF8308",
+"k.	c #EF7510",
+"l.	c #3C2A0F",
+"m.	c #ADAEC2",
+"n.	c #8F8DA3",
+"o.	c #8A88A1",
+"p.	c #9490AB",
+"q.	c #69687B",
+"r.	c #C30C0C",
+"s.	c #BD3C3F",
+"t.	c #C6777A",
+"u.	c #D6B7BA",
+"v.	c #D9A2A7",
+"w.	c #D9848C",
+"x.	c #DBC5C3",
+"y.	c #E49A37",
+"z.	c #F59212",
+"A.	c #482E17",
+"B.	c #D5ABBA",
+"C.	c #817A95",
+"D.	c #A491A9",
+"E.	c #8986A2",
+"F.	c #8E89A7",
+"G.	c #7B7795",
+"H.	c #C55252",
+"I.	c #C37072",
+"J.	c #BE4951",
+"K.	c #CA6A72",
+"L.	c #CF4E5A",
+"M.	c #D49098",
+"N.	c #DDD2D6",
+"O.	c #E6A489",
+"P.	c #EB7313",
+"Q.	c #452716",
+"R.	c #FEC1CD",
+"S.	c #E76B80",
+"T.	c #F7BBC5",
+"U.	c #CBACBA",
+"V.	c #9292AD",
+"W.	c #837FA0",
+"X.	c #C62F2F",
+"Y.	c #C73E3F",
+"Z.	c #C53F4A",
+"`.	c #B93344",
+" +	c #CD3C4D",
+".+	c #D75767",
+"++	c #E17484",
+"@+	c #C46559",
+"#+	c #DA743E",
+"$+	c #53241E",
+"%+	c #ED546E",
+"&+	c #E66E82",
+"*+	c #F3BCC6",
+"=+	c #E19FAF",
+"-+	c #B81415",
+";+	c #BA4D4F",
+">+	c #AD7782",
+",+	c #AC4B5D",
+"'+	c #C09FA9",
+")+	c #BB3A52",
+"!+	c #C14F67",
+"~+	c #CD5858",
+"{+	c #E1AEAC",
+"]+	c #D9D0D0",
+"^+	c #D25169",
+"/+	c #DC7C8F",
+"(+	c #F0C9D1",
+"_+	c #E6A3B2",
+":+	c #A27791",
+"<+	c #AA98B1",
+". +   @                         ",
+"# $ % & * = - ;     > , '   )   ",
+"! ~ { ] ^ / ( _   : < [ } | 1   ",
+"2 3 4 5 6 7 8 9 0 a b c d e     ",
+"f g h i j k l m n o p q r       ",
+"s t u v w x y z A B C D E       ",
+"F G H I J K L M N O P Q R       ",
+"S T U V W X Y Z `  ...+.        ",
+"@.#.$.%.&.*.=.-.;.>.,.    '.    ",
+").!.~.{.].^./.(._.:.<.[.}.|.    ",
+"1.2.3.4.5.6.7.8.9.0.a.    b.    ",
+"c.d.e.f.g.h.i.j.k.l.m.n.o.  p.q.",
+"r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.",
+"H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.",
+"X.Y.Z.`. +.+++@+#+$+%+&+*+=+    ",
+"-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+"};