]> SALOME platform Git repositories - modules/gui.git/commitdiff
Salome HOME
Integration of CurvePlot tool in GUI.
authorabn <adrien.bruneton@cea.fr>
Mon, 8 Aug 2016 08:52:30 +0000 (10:52 +0200)
committerrnv <rnv@opencascade.com>
Wed, 31 Aug 2016 09:27:09 +0000 (12:27 +0300)
97 files changed:
tools/CurvePlot/CMakeLists.txt [new file with mode: 0755]
tools/CurvePlot/SalomeCURVEPLOTConfig.cmake.in [new file with mode: 0644]
tools/CurvePlot/cmake_files/CMakeLists.txt [new file with mode: 0755]
tools/CurvePlot/cmake_files/FindSalomeCURVEPLOT.cmake [new file with mode: 0644]
tools/CurvePlot/resources/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/resources/CURVEPLOT_msg_en.ts [new file with mode: 0644]
tools/CurvePlot/resources/CURVEPLOT_msg_fr.ts [new file with mode: 0644]
tools/CurvePlot/resources/draw_lines.png [new file with mode: 0644]
tools/CurvePlot/resources/draw_points.png [new file with mode: 0644]
tools/CurvePlot/resources/dump_view.png [new file with mode: 0644]
tools/CurvePlot/resources/fit_all.png [new file with mode: 0644]
tools/CurvePlot/resources/fit_area.png [new file with mode: 0644]
tools/CurvePlot/resources/hor_linear.png [new file with mode: 0644]
tools/CurvePlot/resources/hor_logarithmic.png [new file with mode: 0644]
tools/CurvePlot/resources/legend.png [new file with mode: 0644]
tools/CurvePlot/resources/settings.png [new file with mode: 0755]
tools/CurvePlot/resources/ver_linear.png [new file with mode: 0644]
tools/CurvePlot/resources/ver_logarithmic.png [new file with mode: 0644]
tools/CurvePlot/resources/zoom_pan.png [new file with mode: 0644]
tools/CurvePlot/src/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/cpp/CMakeLists.txt [new file with mode: 0755]
tools/CurvePlot/src/cpp/CurvePlot.cxx [new file with mode: 0644]
tools/CurvePlot/src/cpp/CurvePlot.hxx [new file with mode: 0644]
tools/CurvePlot/src/cpp/CurvePlot_Exception.hxx [new file with mode: 0644]
tools/CurvePlot/src/cpp/test/CMakeLists.txt [new file with mode: 0755]
tools/CurvePlot/src/cpp/test/test_curveplot.cxx [new file with mode: 0644]
tools/CurvePlot/src/cpp/test/test_curveplot.hxx [new file with mode: 0644]
tools/CurvePlot/src/python/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/controller/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/controller/PlotController.py [new file with mode: 0644]
tools/CurvePlot/src/python/controller/__init__.py [new file with mode: 0644]
tools/CurvePlot/src/python/controller/utils.py.in [new file with mode: 0644]
tools/CurvePlot/src/python/model/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/model/CurveModel.py [new file with mode: 0644]
tools/CurvePlot/src/python/model/Model.py [new file with mode: 0644]
tools/CurvePlot/src/python/model/PlotManager.py [new file with mode: 0644]
tools/CurvePlot/src/python/model/TableModel.py [new file with mode: 0644]
tools/CurvePlot/src/python/model/XYPlotSetModel.py [new file with mode: 0644]
tools/CurvePlot/src/python/pyqtside/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/pyqtside/QtCore.py [new file with mode: 0644]
tools/CurvePlot/src/python/pyqtside/QtGui.py [new file with mode: 0644]
tools/CurvePlot/src/python/pyqtside/__init__.py [new file with mode: 0644]
tools/CurvePlot/src/python/pyqtside/pyside_dynamic.py [new file with mode: 0644]
tools/CurvePlot/src/python/pyqtside/uic.py [new file with mode: 0644]
tools/CurvePlot/src/python/test/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/test/PlotCurve_Standalone.py [new file with mode: 0755]
tools/CurvePlot/src/python/test/PlotTestBase.py [new file with mode: 0644]
tools/CurvePlot/src/python/test/README.txt [new file with mode: 0644]
tools/CurvePlot/src/python/test/SalomePyQt_MockUp.py.in [new file with mode: 0644]
tools/CurvePlot/src/python/test/TestDesktop.py [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testAddCurveAppend_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testAddCurve_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testAddPlotSet_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testClearPlotSet2_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testClearPlotSet_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testCopyCurve_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_curve_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_plotSet_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeleteCurve1_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeleteCurve2_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeleteCurve3_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeletePlotSet1_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testDeletePlotSet2_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testExtendCurve_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testLockRepaint_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testPlotCurveFromTable_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testResetCurve_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve2_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve3_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetCurrentPlotSet_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetCurveLabel_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetCurveMarker_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetLabelX_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetLabelY_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetLegendVisible_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetPlotSetTitle_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetXSciNotation_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSetYSciNotation_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSettingsCurveColor_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testSettingsCurveMarker_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testToggleXLog_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/baselines/testToggleYLog_a.png [new file with mode: 0644]
tools/CurvePlot/src/python/test/plot_test.py [new file with mode: 0644]
tools/CurvePlot/src/python/ui/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/ui/CurveTreeDockWidget.py [new file with mode: 0644]
tools/CurvePlot/src/python/ui/CurveTreeDockWidget.ui [new file with mode: 0644]
tools/CurvePlot/src/python/ui/PlotSettings.py [new file with mode: 0644]
tools/CurvePlot/src/python/ui/PlotSettings.ui [new file with mode: 0644]
tools/CurvePlot/src/python/ui/PlotWidget.py [new file with mode: 0644]
tools/CurvePlot/src/python/ui/PlotWidget.ui [new file with mode: 0644]
tools/CurvePlot/src/python/views/CMakeLists.txt [new file with mode: 0644]
tools/CurvePlot/src/python/views/CurveBrowserView.py [new file with mode: 0644]
tools/CurvePlot/src/python/views/CurveTabsView.py [new file with mode: 0644]
tools/CurvePlot/src/python/views/CurveView.py [new file with mode: 0644]
tools/CurvePlot/src/python/views/View.py [new file with mode: 0644]
tools/CurvePlot/src/python/views/XYView.py [new file with mode: 0644]

diff --git a/tools/CurvePlot/CMakeLists.txt b/tools/CurvePlot/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..41cd1dc
--- /dev/null
@@ -0,0 +1,225 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8 FATAL_ERROR)
+INCLUDE(CMakeDependentOption)
+
+PROJECT(SalomeCURVEPLOT C CXX)
+
+# Ensure a proper linker behavior:
+CMAKE_POLICY(SET CMP0003 NEW)
+
+# Versioning
+# ===========
+# Project name, upper case
+STRING(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UC)
+
+SET(${PROJECT_NAME_UC}_MAJOR_VERSION 7)
+SET(${PROJECT_NAME_UC}_MINOR_VERSION 6)
+SET(${PROJECT_NAME_UC}_PATCH_VERSION 0)
+SET(${PROJECT_NAME_UC}_VERSION
+  ${${PROJECT_NAME_UC}_MAJOR_VERSION}.${${PROJECT_NAME_UC}_MINOR_VERSION}.${${PROJECT_NAME_UC}_PATCH_VERSION})
+SET(${PROJECT_NAME_UC}_VERSION_DEV 1)
+
+# User options
+# ============
+OPTION(SALOME_CURVEPLOT_STANDALONE "Standalone installation of CURVEPLOT" OFF)
+OPTION(SALOME_BUILD_DOC "Generate SALOME CURVEPLOT documentation" ON)
+OPTION(SALOME_BUILD_TESTS "Generate SALOME CURVEPLOT tests" ON)
+
+# Common CMake macros
+# ===================
+SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files")
+IF(EXISTS ${CONFIGURATION_ROOT_DIR})
+  LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake")
+  INCLUDE(SalomeMacros)
+ELSE()
+  MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !")
+ENDIF()
+
+IF(NOT SALOME_CURVEPLOT_STANDALONE)
+    # Find KERNEL 
+    # ==============
+    SET(KERNEL_ROOT_DIR $ENV{KERNEL_ROOT_DIR} CACHE PATH "Path to the Salome KERNEL")
+    IF( EXISTS ${KERNEL_ROOT_DIR} )
+      LIST(APPEND CMAKE_MODULE_PATH "${KERNEL_ROOT_DIR}/salome_adm/cmake_files")
+      FIND_PACKAGE(SalomeKERNEL REQUIRED)
+      KERNEL_WITH_CORBA() # check whether KERNEL builded with CORBA
+      ADD_DEFINITIONS(${KERNEL_DEFINITIONS} -DSALOME_KERNEL)
+      INCLUDE_DIRECTORIES(${KERNEL_INCLUDE_DIRS})
+    ELSE( EXISTS ${KERNEL_ROOT_DIR} )
+      MESSAGE(FATAL_ERROR "We absolutely need a Salome KERNEL, please define KERNEL_ROOT_DIR or turn option SALOME_CURVEPLOT_STANDALONE to ON!")
+    ENDIF( EXISTS ${KERNEL_ROOT_DIR} )
+    
+    # Find SALOME GUI (needed for the C++ part - PyUtils)
+    # ==============
+    SET(GUI_ROOT_DIR $ENV{GUI_ROOT_DIR} CACHE PATH "Path to the Salome GUI")
+    IF(EXISTS ${GUI_ROOT_DIR})
+      LIST(APPEND CMAKE_MODULE_PATH "${GUI_ROOT_DIR}/adm_local/cmake_files")
+      FIND_PACKAGE(SalomeGUI)
+    ELSE(EXISTS ${GUI_ROOT_DIR})
+      MESSAGE(FATAL_ERROR "We absolutely need a Salome GUI, please define GUI_ROOT_DIR or turn option SALOME_CURVEPLOT_STANDALONE to ON!")
+    ENDIF(EXISTS ${GUI_ROOT_DIR})
+    
+    # Platform setup
+    # ==============
+    INCLUDE(SalomeSetupPlatform)   # From KERNEL
+ENDIF(NOT SALOME_CURVEPLOT_STANDALONE)
+
+# Always build libraries as shared objects:
+SET(BUILD_SHARED_LIBS TRUE)
+# Local macros:
+LIST(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/adm_local/cmake_files")
+
+IF(SALOME_CURVEPLOT_STANDALONE)
+    INCLUDE(SalomeMacros)
+ENDIF()
+
+
+# Prerequisites
+# =============
+# Find "big" prerequisites first - they reference themselves many others
+#   -> this can helps to find the smaller prerequisites and detect conflicts.
+# In our case KERNEL may have already loaded many prerequisites which are also used in OSCAR_POST:
+
+##
+## From KERNEL:
+##
+
+# Mandatory products
+FIND_PACKAGE(SalomePythonInterp    REQUIRED)
+FIND_PACKAGE(SalomePythonLibs    REQUIRED)
+FIND_PACKAGE(SalomeNumPySciPy    REQUIRED)
+
+# Qt4
+FIND_PACKAGE(SalomeQt4 REQUIRED COMPONENTS QtCore QtGui)
+INCLUDE(${QT_USE_FILE})
+
+# Optional products:
+
+IF(SALOME_BUILD_DOC)
+#  FIND_PACKAGE(SalomeDoxygen)
+#  FIND_PACKAGE(SalomeSphinx)
+#  SALOME_LOG_OPTIONAL_PACKAGE(Doxygen SALOME_BUILD_DOC)
+#  SALOME_LOG_OPTIONAL_PACKAGE(Sphinx SALOME_BUILD_DOC)
+#  ADD_DEFINITIONS(-DDOXYGEN_IS_OK)
+ENDIF()
+
+IF(SALOME_BUILD_TESTS)
+  ENABLE_TESTING()
+ENDIF()
+
+# Detection summary:
+SALOME_PACKAGE_REPORT_AND_CHECK()
+
+# Directories
+# ===========
+IF(SALOME_CURVEPLOT_STANDALONE)
+    SET(SALOME_INSTALL_LIBS lib/salome CACHE PATH "Install path: SALOME libs")
+    SET(_pydir lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages)
+    SET(SALOME_INSTALL_PYTHON ${_pydir}/salome CACHE PATH "Install path: SALOME Python stuff")
+    SET(SALOME_INSTALL_SCRIPT_PYTHON bin/salome CACHE PATH 
+        "Install path: SALOME Python scripts")
+    SET(SALOME_INSTALL_CMAKE_LOCAL adm_local/cmake_files CACHE PATH 
+        "Install path: local SALOME CMake files") 
+    SET(SALOME_INSTALL_RES share/salome/resources CACHE PATH "Install path: SALOME resources")
+    SET(SALOME_INSTALL_DOC share/doc/resources CACHE PATH "Install path: SALOME documentation")
+ELSE()
+    SET(SALOME_INSTALL_LIBS "${SALOME_INSTALL_LIBS}" CACHE PATH "Install path: SALOME libs")
+    SET(SALOME_INSTALL_PYTHON "${SALOME_INSTALL_PYTHON}" CACHE PATH 
+        "Install path: SALOME Python scripts")
+    SET(SALOME_INSTALL_SCRIPT_PYTHON "${SALOME_INSTALL_SCRIPT_PYTHON}" CACHE PATH 
+        "Install path: SALOME Python scripts")
+    SET(SALOME_INSTALL_CMAKE_LOCAL ${SALOME_INSTALL_CMAKE_LOCAL} CACHE PATH 
+        "Install path: local SALOME CMake files") 
+    SET(SALOME_INSTALL_RES "${SALOME_INSTALL_RES}" CACHE PATH "Install path: SALOME resources")
+    SET(SALOME_INSTALL_DOC "${SALOME_INSTALL_DOC}" CACHE PATH "Install path: SALOME documentation")
+    SET(SALOME_INSTALL_HEADERS ${SALOME_INSTALL_HEADERS} CACHE PATH "Install path: SALOME headers")
+ENDIF()
+
+# Specific to CURVEPLOT:
+SET(SALOME_CURVEPLOT_INSTALL_RES_DATA "${SALOME_INSTALL_RES}/curveplot" CACHE PATH 
+    "Install path: SALOME CURVEPLOT specific data")
+# Package installation path (lib/python2.7/...) 
+SET(SALOME_CURVEPLOT_INSTALL_PYTHON ${SALOME_INSTALL_PYTHON}/curveplot CACHE INTERNAL  
+    "Install path: SALOME CURVEPLOT Python packages" FORCE)
+SET(SALOME_CURVEPLOT_INSTALL_SCRIPT_PYTHON ${SALOME_INSTALL_SCRIPT_PYTHON} CACHE INTERNAL  
+    "Install path: SALOME CURVEPLOT Python main entry points" FORCE)
+
+# Sources 
+# ========
+ADD_SUBDIRECTORY(src)
+ADD_SUBDIRECTORY(resources)
+ADD_SUBDIRECTORY(cmake_files)
+IF(SALOME_BUILD_DOC)
+#   ADD_SUBDIRECTORY(doc)
+ENDIF()
+
+# Configuration export
+# (here only the level 1 prerequisites are exposed)
+# ====================
+INCLUDE(CMakePackageConfigHelpers)
+
+# List of targets in this project we want to make visible to the rest of the world.
+# They all have to be INSTALL'd with the option "EXPORT ${PROJECT_NAME}TargetGroup"
+SET(_${PROJECT_NAME}_exposed_targets)
+IF(NOT SALOME_CURVEPLOT_STANDALONE)
+   SET(_${PROJECT_NAME}_exposed_targets CurvePlot)
+ENDIF()
+
+# Add all targets to the build-tree export set
+EXPORT(TARGETS ${_${PROJECT_NAME}_exposed_targets}
+  FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake)
+
+# Create the configuration files:
+#   - in the build tree:
+
+#      Ensure the variables are always defined for the configure:
+SET(KERNEL_ROOT_DIR "${KERNEL_ROOT_DIR}")
+SET(GUI_ROOT_DIR "${GUI_ROOT_DIR}")
+SET(QT4_ROOT_DIR "${QT4_ROOT_DIR}")
+SET(PYQT4_ROOT_DIR "${PYQT4_ROOT_DIR}")
+SET(PYTHON_ROOT_DIR "${PYTHON_ROOT_DIR}")
+SET(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/include")
+
+# Build variables that will be expanded when configuring Salome<MODULE>Config.cmake:
+SALOME_CONFIGURE_PREPARE(PyQt4 Qt4 Python)
+
+CONFIGURE_PACKAGE_CONFIG_FILE(${PROJECT_NAME}Config.cmake.in 
+    ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
+    INSTALL_DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}"
+    PATH_VARS CONF_INCLUDE_DIRS SALOME_INSTALL_CMAKE_LOCAL CMAKE_INSTALL_PREFIX
+       KERNEL_ROOT_DIR GUI_ROOT_DIR QT4_ROOT_DIR PYQT4_ROOT_DIR PYTHON_ROOT_DIR)
+
+WRITE_BASIC_PACKAGE_VERSION_FILE(${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
+    VERSION ${${PROJECT_NAME_UC}_VERSION}
+    COMPATIBILITY AnyNewerVersion)
+  
+# Install the CMake configuration files:
+INSTALL(FILES
+  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
+  DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}")
+
+# Install the export set for use with the install-tree
+IF(NOT SALOME_CURVEPLOT_STANDALONE)
+    INSTALL(EXPORT ${PROJECT_NAME}TargetGroup DESTINATION "${SALOME_INSTALL_CMAKE_LOCAL}" 
+        FILE ${PROJECT_NAME}Targets.cmake)
+ENDIF()
+  
diff --git a/tools/CurvePlot/SalomeCURVEPLOTConfig.cmake.in b/tools/CurvePlot/SalomeCURVEPLOTConfig.cmake.in
new file mode 100644 (file)
index 0000000..76b16dd
--- /dev/null
@@ -0,0 +1,100 @@
+# Copyright (C) 2013-2015  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+# - Config file for the @PROJECT_NAME@ package
+# It defines the following variables. 
+# Specific to the pacakge @PROJECT_NAME@ itself:
+#  @PROJECT_NAME_UC@_ROOT_DIR_EXP - the root path of the installation providing this CMake file
+#
+
+### Initialisation performed by CONFIGURE_PACKAGE_CONFIG_FILE:
+@PACKAGE_INIT@
+
+# Options exported by the package:
+SET(SALOME_CURVEPLOT_BUILD_DOC    @SALOME_BUILD_DOC@)
+SET(SALOME_CURVEPLOT_BUILD_TESTS  @SALOME_BUILD_TESTS@)
+SET(SALOME_CURVEPLOT_STANDALONE   @SALOME_CURVEPLOT_STANDALONE@)
+
+# Load the dependencies for the libraries of @PROJECT_NAME@ 
+# (contains definitions for IMPORTED targets). This is only 
+# imported if we are not built as a subproject (in this case targets are already there)
+IF(NOT TARGET CurvePlot AND NOT @PROJECT_NAME@_BINARY_DIR AND NOT SALOME_CURVEPLOT_STANDALONE)
+  INCLUDE("@PACKAGE_SALOME_INSTALL_CMAKE_LOCAL@/@PROJECT_NAME@Targets.cmake")
+ENDIF()   
+
+# Package root dir:
+SET_AND_CHECK(CURVEPLOT_ROOT_DIR_EXP "@PACKAGE_CMAKE_INSTALL_PREFIX@")
+
+# Include directories
+SET_AND_CHECK(CURVEPLOT_INCLUDE_DIRS "${CURVEPLOT_ROOT_DIR_EXP}/@SALOME_INSTALL_HEADERS@")
+
+#### Now the specificities
+
+
+# Level 1 prerequisites:
+IF(SALOME_CURVEPLOT_STANDALONE)
+  SET_AND_CHECK(KERNEL_ROOT_DIR_EXP "@PACKAGE_KERNEL_ROOT_DIR@")
+  SET_AND_CHECK(GUI_ROOT_DIR_EXP "@PACKAGE_GUI_ROOT_DIR@")
+ENDIF()
+
+SET_AND_CHECK(QT4_ROOT_DIR_EXP "@PACKAGE_QT4_ROOT_DIR@")
+SET_AND_CHECK(PYQT4_ROOT_DIR_EXP "@PACKAGE_PYQT4_ROOT_DIR@")
+SET_AND_CHECK(PYTHON_ROOT_DIR_EXP "@PACKAGE_PYTHON_ROOT_DIR@")
+
+
+# For all prerequisites, load the corresponding targets if the package was used 
+# in CONFIG mode. This ensures dependent projects link correctly
+# without having to set LD_LIBRARY_PATH:
+SET(_PREREQ @_PREREQ_LIST@)
+SET(_PREREQ_CONFIG_DIR @_PREREQ_DIR_LIST@)
+SET(_PREREQ_COMPONENTS "@_PREREQ_COMPO_LIST@")
+LIST(LENGTH _PREREQ_CONFIG_DIR _list_len)
+IF(NOT _list_len EQUAL 0)
+  # Another CMake stupidity - FOREACH(... RANGE r) generates r+1 numbers ...
+  MATH(EXPR _range "${_list_len}-1")
+  FOREACH(_p RANGE ${_range})  
+    LIST(GET _PREREQ            ${_p} _pkg    )
+    LIST(GET _PREREQ_CONFIG_DIR ${_p} _pkg_dir)
+    LIST(GET _PREREQ_COMPONENTS ${_p} _pkg_compo)
+    MESSAGE(STATUS "===> Reloading targets from ${_pkg} ...")
+    IF(NOT _pkg_compo)
+      FIND_PACKAGE(${_pkg} REQUIRED NO_MODULE 
+          PATHS "${_pkg_dir}" 
+          NO_DEFAULT_PATH)
+    ELSE()
+      STRING(REPLACE "," ";" _compo_lst "${_pkg_compo}")
+      MESSAGE(STATUS "===> (components: ${_pkg_compo})")
+      FIND_PACKAGE(${_pkg} REQUIRED NO_MODULE
+          COMPONENTS ${_compo_lst} 
+          PATHS "${_pkg_dir}"
+          NO_DEFAULT_PATH)
+    ENDIF()
+  ENDFOREACH()
+ENDIF()
+
+# Installation directories
+SET(SALOME_INSTALL_LIBS "@SALOME_INSTALL_LIBS@")
+SET(SALOME_INSTALL_SCRIPT_PYTHON "@SALOME_INSTALL_SCRIPT_PYTHON@")
+SET(SALOME_INSTALL_CMAKE_LOCAL "@SALOME_INSTALL_CMAKE_LOCAL@")
+SET(SALOME_INSTALL_PYTHON "@SALOME_INSTALL_PYTHON@")
+SET(SALOME_INSTALL_RES "@SALOME_INSTALL_RES@")
+SET(SALOME_INSTALL_DOC "@SALOME_INSTALL_DOC@")
+
+# Exposed CURVEPLOT targets:
+SET(CURVEPLOT_CurvePlot CurvePlot)
diff --git a/tools/CurvePlot/cmake_files/CMakeLists.txt b/tools/CurvePlot/cmake_files/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..c2580cf
--- /dev/null
@@ -0,0 +1,24 @@
+# Copyright (C) 2012-2015  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(_adm_data
+  FindSalomeCURVEPLOT.cmake
+)
+
+INSTALL(FILES ${_adm_data} DESTINATION ${SALOME_INSTALL_CMAKE_LOCAL})
diff --git a/tools/CurvePlot/cmake_files/FindSalomeCURVEPLOT.cmake b/tools/CurvePlot/cmake_files/FindSalomeCURVEPLOT.cmake
new file mode 100644 (file)
index 0000000..ef6c144
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright (C) 2007-2015  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author: Adrien Bruneton
+#
+
+# CURVEPLOT detection for Salome - this is typically called by dependent modules
+#
+# The detection is simpler than for other prerequisites.
+# See explanation in FindSalomeKERNEL.cmake.
+#
+
+IF(NOT SalomeCURVEPLOT_FIND_QUIETLY)
+  MESSAGE(STATUS "Looking for Salome CURVEPLOT ...")
+ENDIF()
+
+SET(CMAKE_PREFIX_PATH "${CURVEPLOT_ROOT_DIR}")
+SALOME_FIND_PACKAGE(SalomeCURVEPLOT SalomeCURVEPLOT CONFIG)
+
+IF(NOT SalomeCURVEPLOT_FIND_QUIETLY)
+  MESSAGE(STATUS "Found Salome CURVEPLOT: ${CURVEPLOT_ROOT_DIR}")
+ENDIF()
+
diff --git a/tools/CurvePlot/resources/CMakeLists.txt b/tools/CurvePlot/resources/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5abf728
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright (C) 2010-2015  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+INCLUDE(UseQtExt)
+
+SET(_res
+    CURVEPLOT_msg_en.ts
+    CURVEPLOT_msg_fr.ts
+)
+
+QT_INSTALL_TS_RESOURCES("${_res}" "${SALOME_CURVEPLOT_INSTALL_RES_DATA}")
+
+SET(dist_salomeres_DATA
+  dump_view.png
+  fit_all.png
+  fit_area.png
+  zoom_pan.png
+  draw_points.png
+  draw_lines.png
+  legend.png
+  hor_linear.png
+  ver_linear.png
+  hor_logarithmic.png
+  ver_logarithmic.png
+  settings.png
+  )
+
+FOREACH(f ${dist_salomeres_DATA})
+  INSTALL(FILES ${f} DESTINATION ${SALOME_CURVEPLOT_INSTALL_RES_DATA})
+ENDFOREACH()
diff --git a/tools/CurvePlot/resources/CURVEPLOT_msg_en.ts b/tools/CurvePlot/resources/CURVEPLOT_msg_en.ts
new file mode 100644 (file)
index 0000000..dff128c
--- /dev/null
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="en_US">
+<context>
+    <name>CURVEPLOT</name>
+    <message>
+        <source>DUMP_VIEW_TXT</source>
+        <translation>Dump view</translation>
+    </message>
+    <message>
+        <source>FIT_ALL_TXT</source>
+        <translation>Fit all</translation>
+    </message>
+    <message>
+        <source>FIT_AREA_TXT</source>
+        <translation>Fit area</translation>
+    </message>
+    <message>
+        <source>ZOOM_TXT</source>
+        <translation>Zoom</translation>
+    </message>
+    <message>
+        <source>ZOOM_PAN_TXT</source>
+        <translation>Panning</translation>
+    </message>
+    <message>
+        <source>DRAW_POINTS_TXT</source>
+        <translation>Draw points</translation>
+    </message>
+    <message>
+        <source>DRAW_LINES_TXT</source>
+        <translation>Draw lines</translation>
+    </message>
+    <message>
+        <source>HOR_LINEAR_TXT</source>
+        <translation>Horizontal axis: linear</translation>
+    </message>
+    <message>
+        <source>HOR_LOGARITHMIC_TXT</source>
+        <translation>Horizontal axis: logarithmic</translation>
+    </message>
+    <message>
+        <source>VER_LINEAR_TXT</source>
+        <translation>Vertical axis: linear</translation>
+    </message>
+    <message>
+        <source>VER_LOGARITHMIC_TXT</source>
+        <translation>Vertical axis: logarithmic</translation>
+    </message>
+    <message>
+        <source>SHOW_LEGEND_TXT</source>
+        <translation>Show legend</translation>
+    </message>
+    <message>
+        <source>HIDE_LEGEND_TXT</source>
+        <translation>Hide legend</translation>
+    </message>
+    <message>
+        <source>SETTINGS_TXT</source>
+        <translation>Settings</translation>
+    </message>
+    <message>
+        <source>DUMP_VIEW_FILE</source>
+        <translation>Dump view to file</translation>
+    </message>
+    <message>
+        <source>IMAGES_FILES</source>
+        <translation>Images Files (*.png)</translation>
+    </message>
+    <message>
+        <source>PDF_FILES</source>
+        <translation>PDF files (*.pdf)</translation>
+    </message>
+    <message>
+        <source>POSTSCRIPT_FILES</source>
+        <translation>PostScript files (*.ps)</translation>
+    </message>
+    <message>
+        <source>ENCAPSULATED_POSTSCRIPT_FILES</source>
+        <translation>Encapsulated PostScript files (*.eps)</translation>
+    </message>
+</context>
+</TS>
\ No newline at end of file
diff --git a/tools/CurvePlot/resources/CURVEPLOT_msg_fr.ts b/tools/CurvePlot/resources/CURVEPLOT_msg_fr.ts
new file mode 100644 (file)
index 0000000..2e72cc2
--- /dev/null
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS>
+    <context>
+    <name>CURVEPLOT</name>
+    <message>
+        <source>DUMP_VIEW_TXT</source>
+        <translation>Sauvegarder la scène</translation>
+    </message>
+    <message>
+        <source>FIT_ALL_TXT</source>
+        <translation>Tout afficher</translation>
+    </message>
+    <message>
+        <source>FIT_AREA_TXT</source>
+        <translation>Afficher la zone</translation>
+    </message>
+    <message>
+        <source>ZOOM_TXT</source>
+        <translation>Zoomer</translation>
+    </message>
+    <message>
+        <source>PAN_TXT</source>
+        <translation>Déplacer</translation>
+    </message>
+    <message>
+        <source>DRAW_POINTS_TXT</source>
+        <translation>Dessiner des points</translation>
+    </message>
+    <message>
+        <source>DRAW_LINES_TXT</source>
+        <translation>Dessiner des lignes</translation>
+    </message>
+    <message>
+        <source>HOR_LINEAR_TXT</source>
+        <translation>Axe horizontal : linéaire</translation>
+    </message>
+    <message>
+        <source>HOR_LOGARITHMIC_TXT</source>
+        <translation>Axe horizontal : logarithmique</translation>
+    </message>
+    <message>
+        <source>VER_LINEAR_TXT</source>
+        <translation>Axe vertical : linéaire</translation>
+    </message>
+    <message>
+        <source>VER_LOGARITHMIC_TXT</source>
+        <translation>Axe vertical : logarithmique</translation>
+    </message>
+    <message>
+        <source>SHOW_LEGEND_TXT</source>
+        <translation>Afficher la légende</translation>
+    </message>
+    <message>
+        <source>HIDE_LEGEND_TXT</source>
+        <translation>Cacher la légende</translation>
+    </message>
+    <message>
+        <source>SETTINGS_TXT</source>
+        <translation>Paramètres</translation>
+    </message>
+    <message>
+        <source>DUMP_VIEW_FILE</source>
+        <translation>Enregistrer la vue dans le fichier</translation>
+    </message>
+    <message>
+        <source>IMAGES_FILES</source>
+        <translation>Fichiers images (*.png)</translation>
+    </message>
+    <message>
+        <source>PDF_FILES</source>
+        <translation>Fichiers PDF (*.pdf)</translation>
+    </message>
+    <message>
+        <source>POSTSCRIPT_FILES</source>
+        <translation>Fichiers PostScript (*.ps)</translation>
+    </message>
+    <message>
+        <source>ENCAPSULATED_POSTSCRIPT_FILES</source>
+        <translation>Fichiers PostScript encapsulés (*.eps)</translation>
+    </message>
+</context>
+</TS>
\ No newline at end of file
diff --git a/tools/CurvePlot/resources/draw_lines.png b/tools/CurvePlot/resources/draw_lines.png
new file mode 100644 (file)
index 0000000..9789452
Binary files /dev/null and b/tools/CurvePlot/resources/draw_lines.png differ
diff --git a/tools/CurvePlot/resources/draw_points.png b/tools/CurvePlot/resources/draw_points.png
new file mode 100644 (file)
index 0000000..3cdea33
Binary files /dev/null and b/tools/CurvePlot/resources/draw_points.png differ
diff --git a/tools/CurvePlot/resources/dump_view.png b/tools/CurvePlot/resources/dump_view.png
new file mode 100644 (file)
index 0000000..b02616f
Binary files /dev/null and b/tools/CurvePlot/resources/dump_view.png differ
diff --git a/tools/CurvePlot/resources/fit_all.png b/tools/CurvePlot/resources/fit_all.png
new file mode 100644 (file)
index 0000000..87e001d
Binary files /dev/null and b/tools/CurvePlot/resources/fit_all.png differ
diff --git a/tools/CurvePlot/resources/fit_area.png b/tools/CurvePlot/resources/fit_area.png
new file mode 100644 (file)
index 0000000..450dc56
Binary files /dev/null and b/tools/CurvePlot/resources/fit_area.png differ
diff --git a/tools/CurvePlot/resources/hor_linear.png b/tools/CurvePlot/resources/hor_linear.png
new file mode 100644 (file)
index 0000000..0140fcc
Binary files /dev/null and b/tools/CurvePlot/resources/hor_linear.png differ
diff --git a/tools/CurvePlot/resources/hor_logarithmic.png b/tools/CurvePlot/resources/hor_logarithmic.png
new file mode 100644 (file)
index 0000000..fb62cd7
Binary files /dev/null and b/tools/CurvePlot/resources/hor_logarithmic.png differ
diff --git a/tools/CurvePlot/resources/legend.png b/tools/CurvePlot/resources/legend.png
new file mode 100644 (file)
index 0000000..81c0627
Binary files /dev/null and b/tools/CurvePlot/resources/legend.png differ
diff --git a/tools/CurvePlot/resources/settings.png b/tools/CurvePlot/resources/settings.png
new file mode 100755 (executable)
index 0000000..64df246
Binary files /dev/null and b/tools/CurvePlot/resources/settings.png differ
diff --git a/tools/CurvePlot/resources/ver_linear.png b/tools/CurvePlot/resources/ver_linear.png
new file mode 100644 (file)
index 0000000..7acc8fe
Binary files /dev/null and b/tools/CurvePlot/resources/ver_linear.png differ
diff --git a/tools/CurvePlot/resources/ver_logarithmic.png b/tools/CurvePlot/resources/ver_logarithmic.png
new file mode 100644 (file)
index 0000000..825d59e
Binary files /dev/null and b/tools/CurvePlot/resources/ver_logarithmic.png differ
diff --git a/tools/CurvePlot/resources/zoom_pan.png b/tools/CurvePlot/resources/zoom_pan.png
new file mode 100644 (file)
index 0000000..ec56cac
Binary files /dev/null and b/tools/CurvePlot/resources/zoom_pan.png differ
diff --git a/tools/CurvePlot/src/CMakeLists.txt b/tools/CurvePlot/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..247339e
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+ADD_SUBDIRECTORY(python)
+
+IF(NOT SALOME_CURVEPLOT_STANDALONE)
+    ADD_SUBDIRECTORY(cpp)
+ENDIF()
+
diff --git a/tools/CurvePlot/src/cpp/CMakeLists.txt b/tools/CurvePlot/src/cpp/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..edf5c66
--- /dev/null
@@ -0,0 +1,56 @@
+# Copyright (C) 2012-2015  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+ADD_SUBDIRECTORY(test)
+
+# --- options ---
+
+# additional include directories
+INCLUDE_DIRECTORIES(
+  ${PYTHON_INCLUDE_DIRS}
+  ${NUMPY_INCLUDE_DIR}
+  ${GUI_INCLUDE_DIRS}
+)
+
+# additional preprocessor / compiler flags
+ADD_DEFINITIONS(${PYTHON_DEFINITIONS})
+
+# libraries to link to
+SET(_link_LIBRARIES ${PYTHON_LIBRARIES} ${GUI_PyInterp})
+
+# --- headers ---
+
+# header files / no moc processing
+SET(_other_HEADERS
+  CurvePlot.hxx
+#  ColumnVector.hxx
+)
+
+# sources / static
+SET(_other_SOURCES
+  CurvePlot.cxx
+#  ColumnVector.cxx
+)
+# --- rules ---
+
+ADD_LIBRARY(CurvePlot ${_other_SOURCES})
+TARGET_LINK_LIBRARIES(CurvePlot ${_link_LIBRARIES})
+INSTALL(TARGETS CurvePlot EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${SALOME_INSTALL_LIBS})
+
+INSTALL(FILES ${_other_HEADERS} DESTINATION ${SALOME_INSTALL_HEADERS})
diff --git a/tools/CurvePlot/src/cpp/CurvePlot.cxx b/tools/CurvePlot/src/cpp/CurvePlot.cxx
new file mode 100644 (file)
index 0000000..fe32da6
--- /dev/null
@@ -0,0 +1,442 @@
+// Copyright (C) 2010-2015  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien BRUNETON
+//
+
+#include <Python.h>
+
+#define PY_ARRAY_UNIQUE_SYMBOL CURVEPLOT_ARRAY_API    // see initializeCurvePlot()
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+#include <numpy/ndarraytypes.h>
+#include <numpy/ndarrayobject.h>
+#include <PyInterp_Utils.h>  // GUI
+
+#include "CurvePlot.hxx"
+#include "CurvePlot_Exception.hxx"
+
+namespace
+{
+  PyObject * strToPyUnicode(std::string s)
+  {
+    return PyUnicode_DecodeUTF8(s.c_str(), s.size(), (char*)"strict");
+  }
+
+  void HandleAndPrintPyError(std::string msg)
+  {
+    if(PyErr_Occurred())
+      {
+        PyErr_Print();
+        throw CURVEPLOT::Exception(msg);
+      }
+  }
+};
+
+namespace CURVEPLOT
+{
+  /**
+   * To be called before doing anything
+   */
+  void InitializeCurvePlot()
+  {
+    PyLockWrapper lock;
+    // TODO: discuss where the below should really happen:
+    // doc: http://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api
+    import_array(); // a macro really!
+  }
+
+  class ColumnVector::Internal
+  {
+  public:
+    Internal() : _npArray(0) {}
+    ~Internal() {}
+
+    PyArrayObject * _npArray;
+  };
+
+  ColumnVector::ColumnVector()
+  {
+    _impl = new Internal();
+  }
+
+  ColumnVector::~ColumnVector()
+  {
+    delete _impl;
+  }
+
+  int ColumnVector::size() const
+  {
+    if (!_impl->_npArray)
+      return 0;
+    {
+        PyLockWrapper lock;
+        int ndim = PyArray_NDIM(_impl->_npArray);
+        if (ndim != 1)
+          throw Exception("ColumnVector::size() : wrong number of dimensions for internal array!!");
+        npy_intp * dims = PyArray_DIMS(_impl->_npArray);
+        return dims[0];
+    }
+  }
+
+  ColumnVector ColumnVector::BuildFromCMemory(double * data, int size)
+  {
+    ColumnVector ret;
+    if (size <= 0)
+      return ret;
+    npy_intp dims[1] = {size};
+
+    {
+      PyLockWrapper lock;
+      PyObject * obj = PyArray_SimpleNewFromData(1,dims,NPY_DOUBLE, data);
+      PyArrayObject * aobj = (PyArrayObject * )obj;
+
+      // Make Numpy responsible of the memory of the array (the memory will be freed
+      // as soon as the array is released in NumPy)
+      PyArray_ENABLEFLAGS(aobj, NPY_ARRAY_OWNDATA);
+
+      ret._impl->_npArray = aobj;
+      return ret;
+    }
+  }
+
+  ColumnVector ColumnVector::BuildFromStdVector(const std::vector<double> & vec)
+  {
+    ColumnVector ret;
+    if (vec.size() == 0)
+      return ret;
+
+    double * c_mem = (double *)malloc(sizeof(double) * vec.size());
+    if (!c_mem)
+      throw Exception("ColumnVector::BuildFromStdVector() : memory allocation error!");
+    const double * data = &vec.front();
+    std::copy(data, data+vec.size(), c_mem);
+    npy_intp dims[1] = {(intptr_t) vec.size()};
+
+    {
+      PyLockWrapper lock;
+      PyObject * obj = PyArray_SimpleNewFromData(1,dims,NPY_DOUBLE, c_mem);
+      PyArrayObject * aobj = (PyArrayObject * )obj;
+
+      // Make Numpy responsible of the memory of the array (the memory will be freed
+      // as soon as the array is released in NumPy)
+      PyArray_ENABLEFLAGS(aobj, NPY_ARRAY_OWNDATA);
+
+      ret._impl->_npArray = aobj;
+      return ret;
+    }
+  }
+
+  std::string ColumnVector::toStdString() const
+  {
+    std::string ret_str = "(None)";
+    if (!_impl->_npArray)
+      return ret_str;
+
+    {
+       PyLockWrapper lock;
+       PyObjWrapper ret_py(
+                 PyObject_CallMethod((PyObject *)_impl->_npArray, (char *)"__str__", NULL)
+                 );
+       // Now extract the returned string
+       if(!PyString_Check(ret_py))
+         throw Exception("CurvePlot::toStdString(): Unexpected returned type!");
+       ret_str = std::string(PyString_AsString(ret_py));
+    }
+    return ret_str;
+  }
+
+  void ColumnVector::createPythonVar(std::string varName) const
+  {
+    PyObject* main_module = PyImport_AddModule((char*)"__main__");
+    PyObject* global_dict = PyModule_GetDict(main_module);
+    PyDict_SetItemString(global_dict, varName.c_str(), (PyObject *)_impl->_npArray);
+  }
+
+  void ColumnVector::cleanPythonVar(std::string varName) const
+  {
+    // Could be a static method really ...
+
+    std::string s = std::string("del ") + varName;
+    const char * cmd = s.c_str();
+    PyRun_SimpleString(cmd);
+  }
+
+  CurvePlot * CurvePlot::_instance = NULL;
+
+  class CurvePlot::Internal
+  {
+  public:
+    Internal() : _controller(0) {}
+    ///! Plot2d controller from Python:
+    PyObject * _controller;
+  };
+
+  CurvePlot::CurvePlot(bool test_mode)
+  {
+    // TODO: do use an intermediate variable '__cont', but use directly Py***CallMethod()
+    _impl = new Internal();
+    {
+    PyLockWrapper lock;
+    std::string code;
+    if (test_mode)
+       code = std::string("import curveplot; from SalomePyQt_MockUp import SalomePyQt;") +
+           std::string("__cont=curveplot.PlotController.GetInstance(sgPyQt=SalomePyQt())");
+    else
+       code = std::string("import curveplot;")+
+           std::string("__cont=curveplot.PlotController.GetInstance()");
+
+    int ret = PyRun_SimpleString(const_cast<char*>(code.c_str()));
+    if (ret == -1)
+      throw Exception("CurvePlot::CurvePlot(): Unable to load curveplot Python module!");
+
+    // Now get the reference to __engine and save the pointer.
+    // All the calls below returns *borrowed* references
+    PyObject* main_module = PyImport_AddModule((char*)"__main__");
+    PyObject* global_dict = PyModule_GetDict(main_module);
+    PyObject* tmp = PyDict_GetItemString(global_dict, "__cont");
+
+    _impl->_controller = tmp;
+    Py_INCREF(_impl->_controller);
+    PyRun_SimpleString(const_cast<char*>("del __cont"));
+    }
+  }
+
+  CurvePlot::~CurvePlot()
+  {
+    if(_impl->_controller != NULL)
+      {
+        PyLockWrapper lock;
+        Py_XDECREF(_impl->_controller);
+      }
+    delete _impl;
+  }
+
+  CurvePlot * CurvePlot::GetInstance(bool test_mode)
+  {
+    if(!_instance)
+      _instance = new CurvePlot(test_mode);
+    return _instance;
+  }
+
+  void CurvePlot::ToggleCurveBrowser(bool with_curve_browser)
+  {
+    if(_instance)
+      throw Exception("CurvePlot::ToggleCurveBrowser() must be invoked before anything else!");
+
+    PyLockWrapper lock;
+    std::string bool_s = with_curve_browser ? "True" : "False";
+    std::string cod = std::string("import curveplot; curveplot.PlotController.WITH_CURVE_BROWSER=") + bool_s;
+    PyRun_SimpleString(const_cast<char *>(cod.c_str()));
+    HandleAndPrintPyError("CurvePlot::ToggleCurveBrowser(): Unable to toggle Curve Browser!");
+  }
+
+  PlotID CurvePlot::AddCurve(const ColumnVector & x, const ColumnVector & y,
+                             PlotID & plot_set_id,
+                             std::string curve_label/*=""*/, std::string x_label/*=""*/, std::string y_label/*=""*/,
+                             bool append/*=true*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObject * xx = (PyObject *)x._impl->_npArray;
+    PyObject * yy = (PyObject *)y._impl->_npArray;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"AddCurve", (char *)"OOOOOi", xx, yy,
+              strToPyUnicode(curve_label), strToPyUnicode(x_label), strToPyUnicode(y_label),
+              append ? 1 : 0)
+          );
+    HandleAndPrintPyError("CurvePlot::AddCurve(): unexpected error!");
+    // Now extract curve_id and plot_set_id from the returned tuple:
+    if(!PyTuple_Check(ret))
+        throw Exception("CurvePlot::AddCurve(): Unexpected returned type!");
+    PyObject * o1 = PyTuple_GetItem(ret, 0);
+    if (!PyInt_Check(o1))
+      throw Exception("CurvePlot::AddCurve(): Unexpected returned type!");
+    PlotID curveId = PyInt_AsLong(o1);
+    PyObject * o2 = PyTuple_GetItem(ret, 1);
+    if (!PyInt_Check(o2))
+      throw Exception("CurvePlot::AddCurve(): Unexpected returned type!");
+    plot_set_id = PyInt_AsLong(o2);
+    return curveId;
+  }
+
+  PlotID CurvePlot::AddPlotSet(std::string title/*=""*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"AddPlotSet", (char *)"O", strToPyUnicode(title))
+          );
+    HandleAndPrintPyError("CurvePlot::AddPlotSet(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  PlotID CurvePlot::DeleteCurve(PlotID curve_id/*=-1*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"DeleteCurve", (char *)"i", curve_id)
+          );
+    HandleAndPrintPyError("CurvePlot::DeleteCurve(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  PlotID CurvePlot::DeletePlotSet(PlotID plot_set_id/*=-1*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"DeletePlotSet", (char *)"i", plot_set_id)
+          );
+    HandleAndPrintPyError("CurvePlot::DeletePlotSet(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  PlotID CurvePlot::ClearPlotSet(PlotID plot_set_id/*=-1*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"ClearPlotSet", (char *)"i", plot_set_id)
+          );
+    HandleAndPrintPyError("CurvePlot::ClearPlotSet(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  bool CurvePlot::SetXLabel(std::string x_label, PlotID plot_set_id/*=-1*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"SetXLabel", (char *)"Oi", strToPyUnicode(x_label), plot_set_id)
+          );
+    HandleAndPrintPyError("CurvePlot::SetXLabel(): unexpected error!");
+    return ((PyObject *)ret == Py_True);
+  }
+
+  bool CurvePlot::SetYLabel(std::string y_label, PlotID plot_set_id/*=-1*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"SetYLabel", (char *)"Oi", strToPyUnicode(y_label), plot_set_id)
+          );
+    HandleAndPrintPyError("CurvePlot::SetYLabel(): unexpected error!");
+    return ((PyObject *)ret == Py_True);
+  }
+  
+  bool CurvePlot::SetPlotSetTitle(std::string title, PlotID plot_set_id/*=-1*/)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"SetPlotSetTitle", (char *)"Oi", strToPyUnicode(title), plot_set_id));
+    HandleAndPrintPyError("CurvePlot::SetPlotSetTitle(): unexpected error!");
+    return ((PyObject *)ret == Py_True);
+  }
+
+  PlotID CurvePlot::GetPlotSetID(PlotID curve_id)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"GetPlotSetID", (char *)"i", curve_id)
+          );
+    HandleAndPrintPyError("CurvePlot::GetPlotSetID(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  PlotID CurvePlot::GetPlotSetIDByName(std::string name)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"GetPlotSetIDByName", (char *)"O", strToPyUnicode(name))
+          );
+    HandleAndPrintPyError("CurvePlot::GetPlotSetIDByName(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  PlotID CurvePlot::GetCurrentCurveID()
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"GetCurrentCurveID", (char *)"")
+          );
+    HandleAndPrintPyError("CurvePlot::GetCurrentCurveID(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  PlotID CurvePlot::GetCurrentPlotSetID()
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"GetCurrentPlotSetID", (char *)"")
+          );
+    HandleAndPrintPyError("CurvePlot::GetCurrentPlotSetID(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  bool CurvePlot::IsValidPlotSetID(PlotID plot_set_id)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"IsValidPlotSetID", (char *)"i", plot_set_id));
+    HandleAndPrintPyError("CurvePlot::IsValidPlotSetID(): unexpected error!");
+    return ((PyObject *)ret == Py_True);
+  }
+
+  int CurvePlot::GetSalomeViewID(PlotID plot_set_id)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"GetSalomeViewID", (char *)"i", plot_set_id));
+    HandleAndPrintPyError("CurvePlot::GetSalomeViewID(): unexpected error!");
+    return PyLong_AsLong(ret);
+  }
+
+  void CurvePlot::OnSalomeViewTryClose(int salome_view_id)
+  {
+    PyLockWrapper lock;
+    PyObject * cont = GetInstance()->_impl->_controller;
+
+    PyObjWrapper ret(
+          PyObject_CallMethod(cont, (char *)"OnSalomeViewTryClose", (char *)"i", salome_view_id));
+    HandleAndPrintPyError("CurvePlot::OnSalomeViewTryClose(): unexpected error!");
+  }
+
+}
diff --git a/tools/CurvePlot/src/cpp/CurvePlot.hxx b/tools/CurvePlot/src/cpp/CurvePlot.hxx
new file mode 100644 (file)
index 0000000..2b3fbb2
--- /dev/null
@@ -0,0 +1,133 @@
+// Copyright (C) 2010-2015  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien BRUNETON
+//
+
+#ifndef SRC_CPP_CURVEPLOT_HXX_
+#define SRC_CPP_CURVEPLOT_HXX_
+
+#include <string>
+#include <vector>
+
+namespace CURVEPLOT
+{
+  typedef int PlotID;
+
+  /**
+   * This function should be called before doing anything in the CURVEPLOT namespace.
+   */
+  void InitializeCurvePlot();
+
+  class ColumnVector
+  {
+  public:
+    friend class CurvePlot;
+
+    virtual ~ColumnVector();
+
+    /**
+     * Build a ColumnVector from a std::vector() of double. The memory is copied for now (TODO: optimize this).
+     */
+    static ColumnVector BuildFromStdVector(const std::vector<double> & vec);
+
+    /**
+     * Build a ColumnVector from a block of memory which was malloc'ed.
+     * The memory is not copied, and the array thus created becomes responsible of the block.
+     * So do NOT free the memory that you pass.
+     */
+    static ColumnVector BuildFromCMemory(double * data, int size);
+
+    /**
+     * Get the current size of the vector.
+     */
+    int size() const;
+
+    /** Get a string representation */
+    std::string toStdString() const;
+
+  private:
+    class Internal;
+    ColumnVector();
+
+    void createPythonVar(std::string varName) const;
+    void cleanPythonVar(std::string varName) const;
+
+    Internal * _impl;
+  };
+
+
+  /**
+   * C++ wrapping of the public API exposed in the Python package curveplot. See doc there.
+   */
+  class CurvePlot
+  {
+  public:
+    static PlotID AddCurve(const ColumnVector & x, const ColumnVector & y,
+                           PlotID & plot_set_id,
+                           std::string curve_label="", std::string x_label="", std::string y_label="",
+                           bool append=true);
+
+    static PlotID AddPlotSet(std::string title="");
+
+    static PlotID DeleteCurve(PlotID curve_id=-1);
+
+    static PlotID DeletePlotSet(PlotID plot_set_id=-1);
+
+    static PlotID ClearPlotSet(PlotID plot_set_id=-1);
+
+    static bool SetXLabel(std::string x_label, PlotID plot_set_id=-1);
+
+    static bool SetYLabel(std::string y_label, PlotID plot_set_id=-1);
+
+    static bool SetPlotSetTitle(std::string title, PlotID plot_set_id=-1);
+
+    static PlotID GetPlotSetID(PlotID curve_id);
+
+    static PlotID GetPlotSetIDByName(std::string name);
+
+    static PlotID GetCurrentCurveID();
+
+    static PlotID GetCurrentPlotSetID();
+
+    static void ToggleCurveBrowser(bool with_curve_browser);
+
+    static bool IsValidPlotSetID(PlotID plot_set_id=-1);
+
+    static int GetSalomeViewID(PlotID plot_set_id);
+
+    static CurvePlot * GetInstance(bool test_mode=false);
+
+    /**! Temporary ... */
+    static void OnSalomeViewTryClose(int salome_view_id);
+
+  protected:
+
+  private:
+    class Internal;
+
+    static CurvePlot * _instance;
+
+    CurvePlot(bool testMode);
+    virtual ~CurvePlot();
+
+    Internal * _impl;
+  };
+}
+
+#endif /* SRC_CPP_CURVEPLOT_HXX_ */
diff --git a/tools/CurvePlot/src/cpp/CurvePlot_Exception.hxx b/tools/CurvePlot/src/cpp/CurvePlot_Exception.hxx
new file mode 100644 (file)
index 0000000..f8f5057
--- /dev/null
@@ -0,0 +1,40 @@
+// Copyright (C) 2010-2015  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien BRUNETON
+//
+
+#include <exception>
+#include <string>
+
+namespace CURVEPLOT
+{
+  class Exception : public std::exception
+  {
+  public:
+    Exception(const std::string & what) : _what(what) {}
+    const char* what() const throw() { return _what.c_str(); }
+
+    ~Exception() throw () {}
+  private:
+    const std::string _what;
+  };
+}
+
+
+
diff --git a/tools/CurvePlot/src/cpp/test/CMakeLists.txt b/tools/CurvePlot/src/cpp/test/CMakeLists.txt
new file mode 100755 (executable)
index 0000000..17e1c15
--- /dev/null
@@ -0,0 +1,51 @@
+# Copyright (C) 2012-2015  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+# additional include directories
+INCLUDE_DIRECTORIES(
+  ${PYTHON_INCLUDE_DIRS}
+  ${GUI_INCLUDE_DIRS}
+  ${QT_INCLUDES}
+  ${CMAKE_CURRENT_SOURCE_DIR}/..
+)
+
+# additional preprocessor / compiler flags
+ADD_DEFINITIONS(${PYTHON_DEFINITIONS} ${QT_DEFINITIONS})
+
+# libraries to link to
+SET(_link_LIBRARIES ${PYTHON_LIBRARIES} ${GUI_PyInterp} ${QT_LIBRARIES} CurvePlot)
+
+# --- headers ---
+
+SET(_moc_HEADERS
+  test_curveplot.hxx
+)
+
+# sources / static
+SET(_other_SOURCES
+  test_curveplot.cxx
+)
+# --- rules ---
+
+# sources / moc wrappings
+QT4_WRAP_CPP(_moc_SOURCES ${_moc_HEADERS})
+
+ADD_EXECUTABLE(test_curveplot ${_other_SOURCES} ${_moc_SOURCES})
+TARGET_LINK_LIBRARIES(test_curveplot ${_link_LIBRARIES})
+
diff --git a/tools/CurvePlot/src/cpp/test/test_curveplot.cxx b/tools/CurvePlot/src/cpp/test/test_curveplot.cxx
new file mode 100644 (file)
index 0000000..3954b7d
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright (C) 2010-2015  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien BRUNETON
+//
+
+#include "test_curveplot.hxx"
+
+#include <PyInterp_Utils.h>  // GUI
+#include <iostream>
+#include <vector>
+#include "CurvePlot.hxx"
+
+#include <PyInterp_Interp.h>  // GUI
+
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QMainWindow>
+#include <QList>
+#include <QWidget>
+#include <QVBoxLayout>
+#include <QDockWidget>
+#include <QPushButton>
+
+using namespace CURVEPLOT;
+
+/* The real test is in this function ! */
+void TestCurvePlot::onClicked()
+{
+  int ps_id = -1;
+  std::cout << "click\n" << std::endl;
+
+  /* Now the real test: */
+//  std::vector<double> x = {1.0,2.0,3.0,4.0,5.0,6.0,7.0};
+  std::vector<double> x;
+  for (int i=1; i <= 7; i++)
+    x.push_back(double(i));
+  //std::vector<double> y = {1.0,4.0,9.0,16.0,25.0,36.0,49.0};
+  std::vector<double> y;
+  for (int i=0; i < 7; i++)
+      y.push_back(double(i*i));
+//  std::vector<double> x(2000);
+//  std::vector<double> y(2000);
+//  for(int i = 0 ; i < x.size(); i++)
+//    {
+//      x[i] = i*1.0;
+//      y[i] = i*2.0;
+//    }
+  ColumnVector xx(ColumnVector::BuildFromStdVector(x));
+  ColumnVector yy(ColumnVector::BuildFromStdVector(y));
+//  std::string s = xx.toStdString();
+//  std::cout << "test xx: " << s << std::endl;
+  std::cout << "setting X label " << CurvePlot::SetXLabel("tôtô") << std::endl;
+  PlotID crv_id = CurvePlot::AddCurve(xx, yy, ps_id, "the cérve", "th x", "the y-s", false);
+  std::cout << "setting X label " << CurvePlot::SetXLabel("tôtô") << std::endl;
+}
+
+void initPython()
+{
+  if (!Py_IsInitialized()){
+      // Python is not initialized
+      Py_Initialize(); // Initialize the interpreter
+
+      PyEval_InitThreads(); // Create (and acquire) the Python global interpreter lock (GIL)
+      PyEval_ReleaseLock();
+  }
+}
+
+/* Little hack to gather widgets created on the Python side  */
+void getWigdets(QApplication * app, QWidget *& crvBrowser, QWidget *& tabWidget)
+{
+  QList<QWidget *> lst(app->topLevelWidgets());
+  crvBrowser = NULL;
+  tabWidget = NULL;
+  foreach(QWidget * w, lst)
+  {
+    if (w->objectName() == QString("TabWidget"))
+      tabWidget = w;
+    if (w->objectName() == QString("CurveTreeDockWidget"))
+      crvBrowser = w;
+  }
+}
+
+int main(int argc, char ** argv)
+{
+  int ret = -1;
+  /* The below part is done automatically in SALOME context */
+  QApplication app (argc, argv);
+  QDesktopWidget * dw = app.desktop();
+
+  QMainWindow mw;
+  mw.resize((int)(dw->width()*0.25), (int)(dw->height()*0.7));
+  mw.show();
+
+  initPython();
+  InitializeCurvePlot();
+
+  {
+    // Make sure the first instanciation of CurvePlot is made in test mode!
+    CurvePlot::ToggleCurveBrowser(false);
+    CurvePlot::GetInstance(true);
+
+    QWidget * crvBrowser = 0, * tabWidget = 0;
+    getWigdets(&app, crvBrowser, tabWidget);
+    QDockWidget * dock = new QDockWidget(&mw);
+    QPushButton * but = new QPushButton("Hello");
+    TestCurvePlot * t2d = new TestCurvePlot();
+    but->connect(but, SIGNAL(clicked()), t2d, SLOT(onClicked()));
+    QWidget * w = new QWidget(dock);
+    dock->setWidget(w);
+    QVBoxLayout * vbl = new QVBoxLayout(w);
+    vbl->addWidget(but);
+    if (crvBrowser)
+      vbl->addWidget(crvBrowser);
+    mw.addDockWidget(Qt::LeftDockWidgetArea, dock);
+    mw.setCentralWidget(tabWidget);
+
+    /* Finalization */
+    ret = app.exec();
+  }
+
+  Py_Finalize(); // must be after GIL release
+  return ret;
+}
diff --git a/tools/CurvePlot/src/cpp/test/test_curveplot.hxx b/tools/CurvePlot/src/cpp/test/test_curveplot.hxx
new file mode 100644 (file)
index 0000000..7d646bc
--- /dev/null
@@ -0,0 +1,10 @@
+#include <QObject>
+
+class TestCurvePlot : public QObject
+{
+  Q_OBJECT
+
+public slots:
+  void onClicked();
+
+};
diff --git a/tools/CurvePlot/src/python/CMakeLists.txt b/tools/CurvePlot/src/python/CMakeLists.txt
new file mode 100644 (file)
index 0000000..779111c
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+ADD_SUBDIRECTORY(controller)
+ADD_SUBDIRECTORY(ui)
+ADD_SUBDIRECTORY(model)
+ADD_SUBDIRECTORY(views)
+ADD_SUBDIRECTORY(pyqtside)
+IF(SALOME_BUILD_TESTS)
+  ADD_SUBDIRECTORY(test)
+ENDIF()
diff --git a/tools/CurvePlot/src/python/controller/CMakeLists.txt b/tools/CurvePlot/src/python/controller/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1c51e37
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SALOME_CONFIGURE_FILE(utils.py.in ${CMAKE_CURRENT_BINARY_DIR}/utils.py)
+
+SET(_all_lib_SCRIPTS
+    PlotController.py
+    __init__.py
+    ${CMAKE_CURRENT_BINARY_DIR}/utils.py
+)
+
+SALOME_INSTALL_SCRIPTS("${_all_lib_SCRIPTS}" ${SALOME_CURVEPLOT_INSTALL_PYTHON})
diff --git a/tools/CurvePlot/src/python/controller/PlotController.py b/tools/CurvePlot/src/python/controller/PlotController.py
new file mode 100644 (file)
index 0000000..76be5e6
--- /dev/null
@@ -0,0 +1,710 @@
+from CurveBrowserView import CurveBrowserView
+from PlotManager import PlotManager
+from CurveTabsView import CurveTabsView
+from CurveModel import CurveModel
+from TableModel import TableModel
+from utils import Logger
+import numpy as np
+
+class PlotController(object):
+  """ Controller for 2D curve plotting functionalities.
+  """
+  __UNIQUE_INSTANCE = None  # my poor impl. of a singleton
+  
+  ## For testing purposes:
+  WITH_CURVE_BROWSER = True
+  WITH_CURVE_TABS = True
+  
+  def __init__(self, sgPyQt=None):
+    if self.__UNIQUE_INSTANCE is None:
+      self.__trueInit(sgPyQt)
+    else:
+      raise Exception("The PlotController must be a singleton - use GetInstance()")
+
+  def __trueInit(self, sgPyQt=None):
+    if sgPyQt is None:
+      import SalomePyQt
+      sgPyQt = SalomePyQt.SalomePyQt()
+    self._sgPyQt = sgPyQt
+    self._modelViews = {}
+    self._browserContextualMenu = None
+    self._blockNotifications = False
+    self._blockViewClosing = False
+    self._callbacks = []
+    
+    self._plotManager = PlotManager(self)
+    
+    if self.WITH_CURVE_BROWSER:
+      self._curveBrowserView = CurveBrowserView(self)
+      self.associate(self._plotManager, self._curveBrowserView)
+    else:
+      self._curveBrowserView = None  
+    if self.WITH_CURVE_TABS:
+      self._curveTabsView = CurveTabsView(self)
+      self.associate(self._plotManager, self._curveTabsView)
+    else:
+      self._curveTabsView = None
+    PlotController.__UNIQUE_INSTANCE = self
+  
+  @classmethod
+  def GetInstance(cls, sgPyQt=None):
+    if cls.__UNIQUE_INSTANCE is None:
+      # First instanciation:
+      PlotController(sgPyQt)
+    return cls.__UNIQUE_INSTANCE
+  
+  @classmethod
+  def Destroy(cls):
+    cls.__UNIQUE_INSTANCE = None
+  
+  def setFixedSizeWidget(self):
+    """ For testing purposes - ensure visible Qt widgets have a fixed size.
+    """
+    if self.WITH_CURVE_BROWSER:
+      self._curveBrowserView.treeWidget.resize(100,200)
+    if self.WITH_CURVE_TABS:
+      self._sgPyQt._tabWidget.resize(600,600)
+  
+  def associate(self, model, view):
+    """
+    Associates a model to a view, and sets the view to listen to this model 
+    changes.
+    
+    :param model: Model -- The model to be associated to the view.
+    :param view: View -- The view.
+    
+    """    
+    if model is None or view is None:
+        return
+  
+    view.setModel(model)
+    self.setModelListener(model, view)
+  
+  def setModelListener(self, model, view):
+    """
+    Sets a view to listen to all changes of the given model
+    """
+    l = self._modelViews.setdefault(model, [])
+    if not view in l and view is not None:
+      l.append(view) 
+  
+  def removeModelListeners(self, model):
+    """ 
+    Removes the given model from the list of listeners. All views previously connected to this model
+    won't receive its update notification anymore.
+    """
+    self._modelViews.pop(model)
+  
+  def notify(self, model, what=""):
+    """
+    Notifies the view when model changes.
+    
+    :param model: Model -- The updated model.
+    """
+    if model is None or self._blockNotifications:
+      return
+    
+    if not self._modelViews.has_key(model):
+      return
+    
+    for view in self._modelViews[model]:
+      method = "on%s" % what
+      if what != "" and what is not None and hasattr(view, method):
+        exec "view.%s()" % method
+      elif hasattr(view, "update"):
+        # Generic update:
+        view.update()
+    
+  def setBrowserContextualMenu(self, menu):
+    """ Provide a menu to be contextually shown in the curve browser """
+    self._browserContextualMenu = menu
+    
+  def setCurvePlotRequestingClose(self, bool):
+    self._blockViewClosing = bool
+    
+  def onCurrentCurveChange(self):
+    ps = self._plotManager.getCurrentPlotSet()
+    if not ps is None:
+      crv = ps.getCurrentCurve()
+      if crv is not None:
+        crv_id = crv.getID() 
+        for c in self._callbacks:
+          c(crv_id)
+    
+  #####
+  ##### Public static API
+  #####
+  
+  @classmethod
+  def AddCurve(cls, x, y, curve_label="", x_label="", y_label="", append=True):
+    """ Add a new curve and make the plot set where it is drawn the active one.
+        If no plot set exists, or none is active, a new plot set will be created, even if append is True.
+        @param x x data
+        @param y y data
+        @param curve_label label of the curve being ploted (optional, default to empty string). This is what is
+        shown in the legend.
+        @param x_label label for the X axis
+        @param y_label label for the Y axis
+        @param append whether to add the curve to the active plot set (default) or into a new one.
+        @return the id of the created curve, and the id of the corresponding plot set.
+    """
+    from XYView import XYView
+    control = cls.GetInstance()
+    pm = control._plotManager
+    t = TableModel(control)
+    data = np.transpose(np.vstack([x, y]))
+    t.setData(data)
+    # ensure a single Matplotlib repaint for all operations to come in AddCurve
+    prevLock = pm.isRepaintLocked()
+    if not prevLock:
+      pm.lockRepaint()
+    curveID, plotSetID = control.plotCurveFromTable(t, x_col_index=0, y_col_index=1, 
+                                                    curve_label=curve_label, append=append)
+    ps = pm._plotSets[plotSetID]
+    if x_label != "":
+      ps.setXLabel(x_label)
+    if y_label != "": 
+      ps.setYLabel(y_label)
+    if not prevLock:
+      pm.unlockRepaint()
+    return curveID, plotSetID
+
+  @classmethod  
+  def ExtendCurve(cls, crv_id, x, y):
+    """ Add new points to an already created curve
+    @raise if invalid plot set ID is given
+    """
+    control = cls.GetInstance()
+    ps = control._plotManager.getPlotSetContainingCurve(crv_id)
+    if ps is None:
+      raise ValueError("Curve ID (%d) not found for extension!" % crv_id)
+    crv_mod = ps._curves[crv_id]
+    data = np.transpose(np.vstack([x, y]))
+    crv_mod.extendData(data)
+    
+  @classmethod
+  def ResetCurve(cls, crv_id):
+    """ Reset a given curve: all data are cleared, but the curve is still 
+    alive with all its attributes (color, etc ...). Mostly used in conjunction
+    with ExtendCurve above
+    @raise if invalid plot set ID is given
+    """
+    control = cls.GetInstance()
+    ps = control._plotManager.getPlotSetContainingCurve(crv_id)
+    if ps is None:
+      raise ValueError("Curve ID (%d) not found for reset!" % crv_id)
+    crv_mod = ps._curves[crv_id]
+    crv_mod.resetData()
+    
+  @classmethod
+  def AddPlotSet(cls, title=""):
+    """ Creates a new plot set (a tab with several curves) and returns its ID. A title can be passed,
+    otherwise a default one will be created.
+    By default this new plot set becomes the active one.
+    """
+    control = cls.GetInstance()
+    ps = control._plotManager.createXYPlotSet()
+    control.setModelListener(ps, control._curveBrowserView)
+    # Controller itself must be notified for curve picking:
+    control.setModelListener(ps, control)
+    if title != "":
+      ps.setTitle(title)
+    return ps.getID()
+            
+  @classmethod
+  def CopyCurve(cls, curve_id, plot_set_id):
+    """ Copy a given curve to a given plot set ID
+    @return ID of the newly created curve
+    """
+    control = cls.GetInstance()
+    psID = cls.GetPlotSetID(curve_id)
+    if psID == -1:
+      raise ValueError("Curve ID (%d) not found for duplication!" % curve_id)
+    plot_set_src = control._plotManager._plotSets[psID]
+    plot_set_tgt = control._plotManager._plotSets.get(plot_set_id, None)
+    if plot_set_tgt is None:
+      raise ValueError("Plot set ID (%d) invalid for duplication!" % plot_set_id)
+    crv = plot_set_src._curves[curve_id]
+    new_crv = crv.clone()
+    control.setModelListener(new_crv, control._curveBrowserView)
+    plot_set_tgt.addCurve(new_crv)
+    return new_crv.getID()
+      
+  @classmethod
+  def DeleteCurve(cls, curve_id=-1):
+    """ By default, delete the current curve, if any. Otherwise do nothing.
+        @return the id of the deleted curve or -1
+    """
+    Logger.Debug("Delete curve")
+    control = cls.GetInstance()
+    # Find the right plot set:
+    if curve_id == -1:
+      curve_id = cls.GetCurrentCurveID()
+      if curve_id == -1:
+        # No current curve, do nothing
+        return -1 
+      
+    psID = cls.GetPlotSetID(curve_id)
+    if psID == -1:
+      raise ValueError("Curve ID (%d) not found for deletion!" % curve_id)
+    crv = control._plotManager._plotSets[psID]._curves[curve_id]
+    control._plotManager._plotSets[psID].removeCurve(curve_id)
+    control.removeModelListeners(crv)
+    return curve_id
+  
+  @classmethod
+  def DeletePlotSet(cls, plot_set_id=-1):
+    """ By default, delete the current plot set, if any. Otherwise do nothing.
+        This will automatically make the last added plot set the current one.
+        @return the id of the deleted plot set or -1
+    """
+    Logger.Debug("PlotController::DeletePlotSet %d" % plot_set_id)
+    control = cls.GetInstance()
+    # Find the right plot set:
+    if plot_set_id == -1:
+      plot_set_id = cls.GetCurrentPlotSetID()
+      if plot_set_id == -1:
+        # No current, do nothing
+        return -1
+
+    ps = control._plotManager.removeXYPlotSet(plot_set_id)
+    for _, crv in ps._curves.items():
+      control.removeModelListeners(crv)
+    control.removeModelListeners(ps)
+    psets = control._plotManager._plotSets 
+    if len(psets):
+      control._plotManager.setCurrentPlotSet(psets.keys()[-1])
+    return plot_set_id
+  
+  @classmethod
+  def usedMem(cls):
+      import gc
+      gc.collect()
+      import resource
+      m = resource.getrusage(resource.RUSAGE_SELF)[2]*resource.getpagesize()/1e6
+      print "** Used memory: %.2f Mb" % m
+  
+  @classmethod
+  def DeleteCurrentItem(cls):
+    """ Delete currently active item, be it a plot set or a curve.
+    @return (True, plot_sed_id) if a plot set was deleted or (False, curve_id) if a curve was deleted, or (True, -1)
+    if nothing was deleted.
+    """
+    c_id = cls.GetCurrentCurveID()
+    ps_id = cls.GetCurrentPlotSetID()
+    ret = True, -1
+    if ps_id == -1:
+      Logger.Info("PlotController.DeleteCurrentItem(): nothing selected, nothing to delete!")
+      return True,-1
+    # Do we delete a curve or a full plot set    
+    if c_id == -1:
+      cls.DeletePlotSet(ps_id)
+      ret = True, ps_id
+    else:
+      cls.DeleteCurve(c_id)
+      ret = False, c_id
+    return ret
+  
+  @classmethod
+  def ClearPlotSet(cls, ps_id=-1):
+    """ Clear all curves in a given plot set. By default clear the current plot set without deleting it,
+    if no default plot set is currently active, do nothing.
+    @return id of the cleared plot set
+    @raise if invalid plot set ID is given
+    """
+    pm = cls.GetInstance()._plotManager
+    if ps_id == -1:
+      ps_id = cls.GetCurrentPlotSetID()
+      if ps_id == -1:
+        return ps_id
+    ps = pm._plotSets.get(ps_id, None)
+    if ps is None:
+      raise ValueError("Invalid plot set ID (%d)!" % ps_id)
+    ps.eraseAll()
+    return ps_id
+  
+#   @classmethod
+#   def ClearAll(cls):
+#     # TODO: optimize
+#     pm = cls.GetInstance()._plotManager
+#     ids = pm._plotSets.keys()
+#     for i in ids:
+#       cls.DeletePlotSet(i)
+  
+  @classmethod
+  def SetXLabel(cls, x_label, plot_set_id=-1):
+    """  By default set the X axis label for the current plot set, if any. Otherwise do nothing.
+         @return True if the label was set
+    """
+    pm = cls.GetInstance()._plotManager
+    if plot_set_id == -1:
+      plot_set_id = cls.GetCurrentPlotSetID()
+      if plot_set_id == -1:
+        # Do nothing
+        return False 
+    ps = pm._plotSets.get(plot_set_id, None)
+    if ps is None:
+      raise Exception("Invalid plot set ID (%d)!" % plot_set_id)
+    ps.setXLabel(x_label)
+    return True
+     
+  @classmethod 
+  def SetYLabel(cls, y_label, plot_set_id=-1):
+    """ By default set the Y axis label for the current plot set, if any. Otherwise do nothing.
+         @return True if the label was set
+    """
+    pm = cls.GetInstance()._plotManager
+    if plot_set_id == -1:
+      plot_set_id = cls.GetCurrentPlotSetID()
+      if plot_set_id == -1:
+        # Do nothing
+        return False 
+    ps = pm._plotSets.get(plot_set_id, None)
+    if ps is None:
+      raise Exception("Invalid plot set ID (%d)!" % plot_set_id)
+    ps.setYLabel(y_label)
+    return True
+     
+  @classmethod 
+  def SetPlotSetTitle(cls, title, plot_set_id=-1):
+    """ By default set the title for the current plot set, if any. Otherwise do nothing.
+         @return True if the title was set
+    """
+    pm = cls.GetInstance()._plotManager
+    if plot_set_id == -1:
+      plot_set_id = cls.GetCurrentPlotSetID()
+      if plot_set_id == -1:
+        # Do nothing
+        return False 
+    ps = pm._plotSets.get(plot_set_id, None)
+    if ps is None:
+      raise Exception("Invalid plot set ID (%d)!" % plot_set_id)
+    ps.setTitle(title)
+    return True
+  
+  @classmethod
+  def GetPlotSetID(cls, curve_id):
+    """ @return plot set id for a given curve or -1 if invalid curve ID
+    """
+    control = cls.GetInstance()
+    cps = control._plotManager.getPlotSetContainingCurve(curve_id)
+    if cps is None:
+      return -1
+    return cps.getID()
+  
+  @classmethod
+  def GetPlotSetIDByName(cls, name):
+    """ @return the first plot set whose name matches the provided name. Otherwise returns -1
+    """ 
+    pm = cls.GetInstance()._plotManager
+    for _, ps in pm._plotSets.items():
+      if ps._title == name:
+        return ps.getID()
+    return -1
+  
+  @classmethod
+  def GetAllPlotSets(cls):
+    """ @return two lists: plot set names, and corresponding plot set IDs
+    """
+    pm = cls.GetInstance()._plotManager
+    it = pm._plotSets.items()
+    ids, inst, titles = [], [], []
+    if len(it):  
+      ids, inst = zip(*it)        
+    if len(inst):
+      titles = [i.getTitle() for i in inst]
+    return list(ids), titles
+  
+  @classmethod
+  def GetCurrentCurveID(cls):
+    """ @return current curve ID or -1 if no curve is currently active
+    """
+    control = cls.GetInstance()
+    crv = control._plotManager.getCurrentCurve()
+    if crv is None:
+      return -1
+    return crv.getID()
+     
+  @classmethod   
+  def GetCurrentPlotSetID(cls):
+    """ @return current plot set ID or -1 if no plot set is currently active
+    """
+    control = cls.GetInstance()
+    cps = control._plotManager.getCurrentPlotSet()
+    if cps is None:
+      return -1
+    return cps.getID()  
+
+  @classmethod
+  def SetCurrentPlotSet(cls, ps_id):
+    """ Set the current active plot set. Use -1 to unset any current plot set.
+    @throw if invalid ps_id
+    """
+    control = cls.GetInstance()
+    control._plotManager.setCurrentPlotSet(ps_id)
+
+  @classmethod
+  def SetCurrentCurve(cls, crv_id):
+    """ Set the current active curve.
+    @return corresponding plot set ID
+    @throw if invalid crv_id
+    """
+    control = cls.GetInstance()
+    ps_id = control._plotManager.setCurrentCurve(crv_id)
+    return ps_id
+
+  @classmethod
+  def ActiveViewChanged(cls, viewID):
+    """ This method is to be plugged direclty in the activeViewChanged() slot of a standard
+    Python SALOME module so that the curve browser stays in sync with the selected SALOME view
+    """
+    control = cls.GetInstance()
+    # Get XYView from SALOME view ID
+    xyview = control._curveTabsView._XYViews.get(viewID, None)
+    if not xyview is None:
+      plotSetID = xyview.getModel().getID()
+      control._plotManager.setCurrentPlotSet(plotSetID)
+
+  @classmethod
+  def ToggleCurveBrowser(cls, active):
+    if cls.__UNIQUE_INSTANCE is not None:
+      raise Exception("ToggleCurveBrowser() must be invoked before doing anything in plot2D!")
+    cls.WITH_CURVE_BROWSER = active
+    
+  @classmethod
+  def IsValidPlotSetID(cls, plot_set_id):
+    """ 
+    @return True if plot_set_id is the identifier of a valid and existing plot set.
+    """
+    control = cls.GetInstance()
+    return control._plotManager._plotSets.has_key(plot_set_id)
+
+  @classmethod
+  def GetSalomeViewID(cls, plot_set_id):
+    """
+    @return the salome view ID associated to a given plot set. -1 if invalid plot_set_id
+    """
+    control = cls.GetInstance()
+    d = control._curveTabsView.mapModId2ViewId()
+    return d.get(plot_set_id, -1)
+
+  @classmethod
+  def OnSalomeViewTryClose(cls, salome_view_id):
+    control = cls.GetInstance()
+    if not control._blockViewClosing:
+      Logger.Debug("PlotController::OnSalomeViewTryClose %d" % salome_view_id)
+#       control._sgPyQt.setViewClosable(salome_view_id, False)
+      # Get XYView from SALOME view ID
+      xyview = control._curveTabsView._XYViews.get(salome_view_id, None)
+      if not xyview is None:
+        plotSetID = xyview.getModel().getID()
+        Logger.Debug("PlotController::OnSalomeViewTryClose internal CurvePlot view ID is %d" % plotSetID)
+        control._plotManager.removeXYPlotSet(plotSetID)
+      else:
+        Logger.Warning("Internal error - could not match SALOME view ID %d with CurvePlot view!" % salome_view_id)
+
+  @classmethod
+  def SetCurveMarker(cls, crv_id, marker):
+    """ Change curve marker. Available markers are:
+    CURVE_MARKERS = [ "o" ,#  circle
+                    "*",  # star
+                    "+",  # plus
+                    "x",  # x
+                    "s",  # square
+                    "p",  # pentagon
+                    "h",  # hexagon1
+                    "8",  # octagon
+                    "D",  # diamond
+                    "^",  # triangle_up
+                    "<",  # triangle_left
+                    ">",  # triangle_right
+                    "1",  # tri_down
+                    "2",  # tri_up
+                    "3",  # tri_left
+                    "4",  # tri_right
+                    "v",  # triangle_down
+                    "H",  # hexagon2
+                    "d",  # thin diamond
+                    "",   # NO MARKER
+                   ]
+    @raise if invalid curve ID or marker
+    """
+    from XYView import XYView
+    from CurveView import CurveView
+    if not marker in XYView.CURVE_MARKERS:
+      raise ValueError("Invalid marker: '%s'" % marker)
+    
+    cont = cls.GetInstance()
+    for mod, views in cont._modelViews.items():
+      if isinstance(mod, CurveModel) and mod.getID() == crv_id:
+        for v in views:
+          if isinstance(v, CurveView):
+            v.setMarker(marker)
+            # Update curve display and legend:
+            v._parentXYView.repaint()
+            v._parentXYView.showHideLegend()
+            found = True
+        
+    if not found:
+      raise Exception("Invalid curve ID or curve currently not displayed (curve_id=%d)!" % crv_id)
+
+  @classmethod
+  def SetCurveLabel(cls, crv_id, label):
+    """ Change curve label
+    @raise if invalid curve id
+    """
+    cont = cls.GetInstance()
+    cps = cont._plotManager.getPlotSetContainingCurve(crv_id)
+    if cps is None:
+      raise ValueError("Invalid curve ID: %d" % crv_id)
+    cps._curves[crv_id].setTitle(label)
+
+  @classmethod
+  def __XYViewOperation(cls, func, ps_id, args, kwargs):
+    """ Private. To factorize methods accessing the XYView to change a display element. """
+    from XYPlotSetModel import XYPlotSetModel
+    from XYView import XYView
+    
+    cont = cls.GetInstance()
+    for mod, views in cont._modelViews.items():
+      if isinstance(mod, XYPlotSetModel) and mod.getID() == ps_id:
+        for v in views:
+          if isinstance(v, XYView):
+            exec "v.%s(*args, **kwargs)" % func
+            found = True
+    if not found:
+      raise Exception("Invalid plot set ID or plot set currently not displayed (ps_id=%d)!" % ps_id)
+
+
+  @classmethod
+  def SetXLog(cls, ps_id, log=True):
+    """ Toggle the X axis into logarithmic scale.
+    @param ps_id plot set ID
+    @param log if set to True, log scale is used, otherwise linear scale is used
+    @raise if invalid plot set ID
+    """
+    args, kwargs = [log], {}
+    cls.__XYViewOperation("setXLog", ps_id, args, kwargs)
+
+  @classmethod
+  def SetYLog(cls, ps_id, log=True):
+    """ Toggle the Y axis into logarithmic scale.
+    @param ps_id plot set ID
+    @param log if set to True, log scale is used, otherwise linear scale is used
+    @raise if invalid plot set ID
+    """
+    args, kwargs = [log], {}
+    cls.__XYViewOperation("setYLog", ps_id, args, kwargs)
+     
+  @classmethod
+  def SetXSciNotation(cls, ps_id, sciNotation=False):
+    """ Change the format (scientific notation or not) of the X axis.
+    @param ps_id plot set ID
+    @param sciNotation if set to True, scientific notation is used, otherwise plain notation is used
+    @raise if invalid plot set ID
+    """
+    args, kwargs = [sciNotation], {}
+    cls.__XYViewOperation("setXSciNotation", ps_id, args, kwargs)
+   
+  @classmethod
+  def SetYSciNotation(cls, ps_id, sciNotation=False):
+    """ Change the format (scientific notation or not) of the Y axis.
+    @param ps_id plot set ID
+    @param sciNotation if set to True, scientific notation is used, otherwise plain notation is used
+    @raise if invalid plot set ID
+    """
+    args, kwargs = [sciNotation], {}
+    cls.__XYViewOperation("setYSciNotation", ps_id, args, kwargs)
+
+  @classmethod
+  def SetLegendVisible(cls, ps_id, visible=True):
+    """ Change the visibility of the legend.
+    @param ps_id plot set ID
+    @param visible if set to True, show legend, otherwise hide it.
+    @raise if invalid plot set ID
+    """
+    args, kwargs = [visible], {}
+    cls.__XYViewOperation("setLegendVisible", ps_id, args, kwargs)
+    
+
+  ###
+  ### More advanced functions
+  ###
+  @classmethod
+  def RegisterCallback(cls, callback):
+    cont = cls.GetInstance()
+    cont._callbacks.append(callback)
+  
+  @classmethod
+  def ClearCallbacks(cls):
+    cont = cls.GetInstance()
+    cont._callbacks = []
+  
+  @classmethod
+  def LockRepaint(cls):
+    control = cls.GetInstance()
+    control._plotManager.lockRepaint()
+  
+  @classmethod
+  def UnlockRepaint(cls):
+    control = cls.GetInstance()
+    control._plotManager.unlockRepaint()  
+  
+  def createTable(self, data, table_name="table"):
+    t = TableModel(self)
+    t.setData(data)
+    t.setTitle(table_name)
+    return t
+     
+  def plotCurveFromTable(self, table, x_col_index=0, y_col_index=1, curve_label="", append=True):
+    """
+    :returns: a tuple containing the unique curve ID and the plot set ID 
+    """
+    # Regardless of 'append', we must create a view if none there:
+    if self._plotManager.getCurrentPlotSet() is None or not append:
+      ps = self._plotManager.createXYPlotSet()
+      self.setModelListener(ps, self._curveBrowserView)
+      # For curve picking, controller must listen:
+      self.setModelListener(ps, self)
+      cps_title = table.getTitle()
+    else:
+      cps_title = None
+
+    cps = self._plotManager.getCurrentPlotSet()
+    
+    cm = CurveModel(self, table, y_col_index)
+    cm.setXAxisIndex(x_col_index)
+    
+    # X axis label
+    tix = table.getColumnTitle(x_col_index)
+    if tix != "":
+      cps.setXLabel(tix)
+    
+    # Curve label
+    if curve_label != "":
+      cm.setTitle(curve_label)
+    else:
+      ti = table.getColumnTitle(y_col_index)
+      if ti != "":
+        cm.setTitle(ti)
+
+    # Plot set title
+    if cps_title != "" and cps_title is not None:
+      Logger.Debug("about to set title to: " + cps_title)  
+      cps.setTitle(cps_title)
+    
+    cps.addCurve(cm)
+    mp = self._curveTabsView.mapModId2ViewId()
+    xyview_id = mp[cps.getID()]
+    xyview = self._curveTabsView._XYViews[xyview_id]
+    
+    if cps_title is None:  # no plot set was created above
+      self._plotManager.setCurrentPlotSet(cps.getID())
+      
+    # Make CurveBrowser and CurveView depend on changes in the curve itself:
+    self.setModelListener(cm, self._curveBrowserView)
+    self.setModelListener(cm, xyview._curveViews[cm.getID()])
+    # Upon change on the curve also update the full plot, notably for the auto-fit and the legend:
+    self.setModelListener(cm, xyview)
+        
+    return cm.getID(),cps.getID()
diff --git a/tools/CurvePlot/src/python/controller/__init__.py b/tools/CurvePlot/src/python/controller/__init__.py
new file mode 100644 (file)
index 0000000..fd0b521
--- /dev/null
@@ -0,0 +1,58 @@
+"""
+CurvePlot Python package.
+"""
+try:
+  # Some unicode chars are improperly rendered with the default 'tkagg' backend (delta for ex)
+  # and the Bitstream font which is the first one by default. Try to use DejaVu which is more 
+  # comprehensive.
+  ## !!Order of the sequence below is highly sensitive!!
+  import pyqtside   # will trigger the PySide/PyQt4 switch
+  import matplotlib
+  matplotlib.use('Qt4Agg')
+  import matplotlib.pyplot as plt  # must come after the PySide/PyQt4 switch!
+  plt.rcParams['font.sans-serif'].insert(0, u"DejaVu Sans")
+except:
+  print "Warning: could not switch matplotlib to 'Qt4agg' backend. Some characters might be displayed improperly!"
+
+from PlotController import PlotController
+from TableModel import TableModel
+from CurveModel import CurveModel
+from PlotManager import PlotManager
+from XYPlotSetModel import XYPlotSetModel
+
+## The static API of PlotController is the main interface of the package and is hence exposed at package level:
+AddCurve = PlotController.AddCurve
+AddPlotSet = PlotController.AddPlotSet
+ExtendCurve = PlotController.ExtendCurve
+ResetCurve = PlotController.ResetCurve
+CopyCurve = PlotController.CopyCurve
+DeleteCurve = PlotController.DeleteCurve
+DeletePlotSet = PlotController.DeletePlotSet
+ClearPlotSet = PlotController.ClearPlotSet
+SetXLabel = PlotController.SetXLabel
+SetYLabel = PlotController.SetYLabel
+GetPlotSetID = PlotController.GetPlotSetID
+GetPlotSetIDByName = PlotController.GetPlotSetIDByName
+GetAllPlotSets = PlotController.GetAllPlotSets
+GetCurrentCurveID = PlotController.GetCurrentCurveID
+GetCurrentPlotSetID = PlotController.GetCurrentPlotSetID
+SetCurrentCurve = PlotController.SetCurrentCurve
+SetCurrentPlotSet = PlotController.SetCurrentPlotSet
+DeleteCurrentItem = PlotController.DeleteCurrentItem
+SetCurveMarker = PlotController.SetCurveMarker
+SetCurveLabel = PlotController.SetCurveLabel
+SetXLog = PlotController.SetXLog
+SetYLog = PlotController.SetYLog
+SetXSciNotation = PlotController.SetXSciNotation
+SetYSciNotation = PlotController.SetYSciNotation
+RegisterCallback = PlotController.RegisterCallback
+ClearCallbacks = PlotController.ClearCallbacks
+GetSalomeViewID = PlotController.GetSalomeViewID
+SetLegendVisible = PlotController.SetLegendVisible
+SetPlotSetTitle = PlotController.SetPlotSetTitle
+
+# For advanced usage only:
+GetInstance = PlotController.GetInstance
+LockRepaint = PlotController.LockRepaint
+UnlockRepaint = PlotController.UnlockRepaint
+
diff --git a/tools/CurvePlot/src/python/controller/utils.py.in b/tools/CurvePlot/src/python/controller/utils.py.in
new file mode 100644 (file)
index 0000000..87f8e76
--- /dev/null
@@ -0,0 +1,85 @@
+class Logger(object):
+    """
+    Debug Info.
+    """
+    LOG_LEVEL = 1 # 0 means all, 1 means all but DEBUG, 2 means all but INFO and DEBUG, 3 only FATAL
+
+    @classmethod
+    def Debug(cls, msg):
+        """
+        Prints an information message to the standard output.
+        
+        :param msg: str -- The message to be printed.
+        
+        """
+        if cls.LOG_LEVEL <= 0:
+            cls.__log("[DEBUG]", msg)
+
+
+    @classmethod
+    def Info(cls, msg):
+        """
+        Prints an information message to the standard output.
+        
+        :param msg: str -- The message to be printed.
+        
+        """
+        if cls.LOG_LEVEL <= 1:
+            cls.__log("[INFO]", msg)
+
+    
+    @classmethod
+    def Warning(cls, msg):
+        """
+        Prints a warning message to the standard output.
+        
+        :param msg: str -- The message to be printed.
+        
+        """
+        if cls.LOG_LEVEL <= 2:
+            cls.__log("[WARNING]", msg)
+        
+    
+    @classmethod
+    def FatalError(cls, msg):
+        """
+        Prints an error message to the standard output.
+        
+        :param msg: str -- The message to be printed.
+        :raises: Exception.
+        
+        """
+        if cls.LOG_LEVEL <= 3:
+            cls.__log("[FATAL]", msg)
+            raise Exception(msg)
+
+    @classmethod
+    def __log(cls, typ, msg):
+        print "%s: %s" % (typ, msg)
+      
+def trQ(tag, context="CURVEPLOT"):
+  """ @return a QString read from the translation file """
+  from pyqtside.QtGui import QApplication 
+  return QApplication.translate(context, tag) 
+
+def trU(tag, context="CURVEPLOT"):
+  """ @return same as above, but returns a Python unicode string.  """
+  qs = trQ(tag, context)
+  return unicode(qs, 'utf-8')
+
+def toUnicodeWithWarning(s, method_name):
+  try: 
+    s = unicode(s)
+  except:
+    Logger.Warning("%s - warning, passing non-unicode, non-ASCII string '%s'! Trying to convert myself to UTF-8 ..." % (method_name, s))
+    s = unicode(s, 'utf-8')
+  return s
+
+def completeResPath(fileName):
+  import os
+  subPath = "@SALOME_CURVEPLOT_INSTALL_PYTHON@"
+  rd = os.environ.get("CURVEPLOT_ROOT_DIR", None)
+  if rd is None:
+    raise Exception("CURVEPLOT_ROOT_DIR is not defined!")
+  filePath = os.path.join(rd, subPath, fileName)
+  return filePath
diff --git a/tools/CurvePlot/src/python/model/CMakeLists.txt b/tools/CurvePlot/src/python/model/CMakeLists.txt
new file mode 100644 (file)
index 0000000..964d05e
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(_all_lib_SCRIPTS
+    Model.py
+    CurveModel.py
+    TableModel.py
+    PlotManager.py
+    XYPlotSetModel.py
+)
+    
+SALOME_INSTALL_SCRIPTS("${_all_lib_SCRIPTS}" ${SALOME_CURVEPLOT_INSTALL_PYTHON})
diff --git a/tools/CurvePlot/src/python/model/CurveModel.py b/tools/CurvePlot/src/python/model/CurveModel.py
new file mode 100644 (file)
index 0000000..c111796
--- /dev/null
@@ -0,0 +1,68 @@
+from Model import Model
+from utils import toUnicodeWithWarning
+
+class CurveModel(Model):  
+  def __init__(self, controller, table=None, index=-1):
+    Model.__init__(self, controller)
+    self._title = "Curve %d" % self.getID()
+    self._table = table
+    self._yaxisIndex = index   # column index in the table
+    self._xaxisIndex = 0  # By default the first column of the table is used for the X-s
+    
+  def clone(self):
+    ret = CurveModel(self._controller)
+    ret._title = self._title
+    ret._table = self._table  # TO CHECK: deep copy here? (I think not needed in Python ...)
+    ret._yaxisIndex = self._yaxisIndex
+    ret._xaxisIndex = self._xaxisIndex
+    return ret
+    
+  def setTable(self, t, silent=False):
+    self._table = t
+    if not silent:
+      self.notifyChange("DataChange") 
+    
+  def getTable(self):
+    return self._table
+    
+  def extendData(self, t, silent=False):
+    self._table.extend(t)
+    if not silent:
+      self.notifyChange("DataChange")
+    
+  def resetData(self):
+    self._table.clear()
+    self.notifyChange("DataChange")
+    
+  def setTitle(self, ti, silent=False):
+    ti = toUnicodeWithWarning(ti, "CurveModel::setTitle()")
+    self._title = ti
+    if not silent:
+      self.notifyChange("CurveTitleChange") 
+    
+  def getTitle(self):
+    return self._title
+    
+  def getYAxisIndex(self):
+    return self._yaxisIndex
+  
+  def setYAxisIndex(self, idx, silent=False):
+    self._yaxisIndex = idx
+    if not silent:
+      self.notifyChange("YAxisIndexChange")
+  
+  def getXAxisIndex(self):
+    return self._xaxisIndex
+  
+  def setXAxisIndex(self, idx, silent=False):
+    sh = self._table.getShape()
+    if idx >= sh[1]:
+      raise ValueError("Index out of bound (is %d, but max is %d)" % (idx, sh[1]))
+    self._xaxisIndex = idx
+    if not silent:
+      self.notifyChange("XAxisIndexChange")
+    
+  def getID(self):
+    return self._id
+  
+    
diff --git a/tools/CurvePlot/src/python/model/Model.py b/tools/CurvePlot/src/python/model/Model.py
new file mode 100644 (file)
index 0000000..0db911b
--- /dev/null
@@ -0,0 +1,44 @@
+class Model(object):
+    START_ID = -1
+  
+    @classmethod
+    def __GenerateID(cls):
+        cls.START_ID += 1
+        return cls.START_ID
+
+    def __init__( self, controller ):
+        """Constructor"""
+    
+        self._name = None
+        self._controller = controller
+        self._id = self.__GenerateID()  # A unique ID for this class of object
+    
+    def getID(self):
+        return self._id
+      
+    def getController(self):
+        """
+        :returns: Controller -- This model's controller.
+        """
+        return self._controller
+    
+    def setController(self, controller):
+        """
+        Sets the controller of this model.
+        
+        :param controller: Controller -- The controller of the model.
+        
+        """
+        self._controller = controller
+    
+    def notifyChange( self, what="" ) :
+        """
+        Notifies the controller that this model's data has changed.
+        """
+        if self._controller != None:
+            self._controller.notify(self, what)
+    
+    def updateTimeStamps (self, modifiesList):
+        raise NotImplementedError
+
+pass
diff --git a/tools/CurvePlot/src/python/model/PlotManager.py b/tools/CurvePlot/src/python/model/PlotManager.py
new file mode 100644 (file)
index 0000000..665c2a7
--- /dev/null
@@ -0,0 +1,99 @@
+from Model import Model
+from XYPlotSetModel import XYPlotSetModel
+from utils import Logger
+
+class PlotManager(Model):
+  def __init__(self, controller):
+    from collections import OrderedDict
+    Model.__init__(self, controller)
+    self._currentPlotSet = None
+    self._plotSets = OrderedDict()    # key: plotSet ID, value: instance of XYPlotSetModel. We use an OrderedDict so that
+                                      # when removing elemetns, we can easily re-select the last-but-one.
+    self._lockRepaint = False  # if True, repaint routines are blocked.
+    self._plotSetsRepaint = set() # plot waiting for repaint/update while repaint is locked
+  
+  def isEmpty(self):
+    return len(self._plotSets) == 0
+  
+  def setCurrentPlotSet(self, plotSetID, silent=False):
+    if not self._plotSets.has_key(plotSetID) and plotSetID != -1:
+      raise ValueError("Invalid plot set ID (%d)!" % plotSetID)
+    self._currentPlotSet = self._plotSets.get(plotSetID, None)
+    if not silent:
+      self.notifyChange("CurrentPlotSetChange") 
+  
+  def getCurrentPlotSet(self):
+    return self._currentPlotSet
+  
+  def getPlotSetContainingCurve(self, curveID):
+    for ps in self._plotSets.values():
+      if ps._curves.has_key(curveID):
+        return ps
+    return None
+  
+  def setCurrentCurve(self, curveId):
+    ps = self.getPlotSetContainingCurve(curveId)
+    if ps is None and curveId != -1:
+      raise ValueError("Invalid curve ID (%d)!" % curveId)
+    self.clearAllCurrentCurve()
+    if curveId == -1:      
+      return -1
+    ps_id = ps.getID()
+    currPs = self.getCurrentPlotSet()
+    if currPs is None or currPs.getID() != ps_id: 
+      self.setCurrentPlotSet(ps_id)
+    ps.setCurrentCurve(curveId)
+    return ps_id
+  
+  def getCurrentCurve(self):
+    ps = self.getCurrentPlotSet()
+    if ps is None:
+      return None
+    return ps.getCurrentCurve()
+  
+  def clearAllCurrentCurve(self, silent=False):
+    for psID in self._plotSets:
+      self._plotSets[psID].setCurrentCurve(-1)
+    if not silent:
+      self.notifyChange("CurrentCurveChange")
+  
+  def createXYPlotSet(self, silent=False):
+    cv = XYPlotSetModel(self._controller)
+    self._plotSets[cv.getID()] = cv
+    self._currentPlotSet = cv
+    if not silent:
+      self.notifyChange("NewPlotSet")
+    return cv
+  
+  def removeXYPlotSet(self, plotSetID):
+    Logger.Debug("====> PlotManager::removeXYPlotSet() %d" % plotSetID)
+    if not self._plotSets.has_key(plotSetID):
+      print self._plotSets
+      raise ValueError("Plot set ID (%d) not found for deletion!" % plotSetID)
+    ps = self._plotSets.pop(plotSetID)
+    if self._currentPlotSet is ps:
+      self._currentPlotSet = None
+    self.notifyChange("RemovePlotSet")
+    return ps
+  
+  def clearAll(self):
+    self._plotSets = {}
+    self._currentPlotSet = None
+    self.notifyChange("ClearAll")
+    
+  def lockRepaint(self):
+    self._lockRepaint = True
+    self._plotSetsRepaint = set()
+    
+  def isRepaintLocked(self):
+    return self._lockRepaint
+  
+  def registerRepaint(self, ps_id):
+    self._plotSetsRepaint.add(ps_id)
+    
+  def unlockRepaint(self):
+    self._lockRepaint = False
+    for obj in self._plotSetsRepaint:
+      obj.notifyChange()
+    self._plotSetsRepaint = set()
+    
\ No newline at end of file
diff --git a/tools/CurvePlot/src/python/model/TableModel.py b/tools/CurvePlot/src/python/model/TableModel.py
new file mode 100644 (file)
index 0000000..2f30aa7
--- /dev/null
@@ -0,0 +1,93 @@
+import numpy as np
+from Model import Model
+from utils import toUnicodeWithWarning, Logger
+
+class TableModel(Model):
+  def __init__(self, controller):
+    Model.__init__(self, controller)
+    self._columnTitles = {}
+    self._data = None
+    self._title = ""
+  
+  def getData(self):
+    return self._data
+  
+  def __checkAndFormatData(self, data):
+    if isinstance(data, np.ndarray):
+      if len(data.shape) == 1:
+        data = np.resize(data, (data.shape[0], 1))
+      elif len(data.shape) == 2:
+        pass
+      else:
+        raise ValueError("Invalid shape! Must be a vector or a rank-2 tensor (i.e. a matrix)!")
+    elif isinstance(data, list):
+      data = np.array((len(data), 1), dtype=np.float64)
+      data[:] = data
+    return data 
+  
+  def setData(self, data):
+    data = self.__checkAndFormatData(data)
+    self._data = data
+    self.notifyChange("DataChange")
+  
+  def extend(self, data):
+    data = self.__checkAndFormatData(data)
+    if data.shape[1] != self._data.shape[1]:
+      raise ValueError("Invalid shape! Must have the same number of columns than already existing data!")
+    self._data = np.vstack([self._data, data])
+    self.notifyChange("DataChange")
+
+  def clear(self):
+    sh = self.getShape()
+    # Void data but keeping same number of cols:
+    self._data = np.zeros((0, sh[1]))
+    self.notifyChange("DataChange")
+  
+  def getShape(self):
+    if self._data is not None:
+      return self._data.shape
+    else:
+      return (0,0)
+  
+  def setTitle(self, ti):
+    ti = toUnicodeWithWarning(ti, "TableModel::setTitle()")
+    self._title = ti
+    self.notifyChange("TitleChange")
+  
+  def getTitle(self):
+    return self._title
+  
+  def addColumn(self, lst):
+    sh = self.getShape()
+    if sh != (0,0):
+      if len(lst) != sh[0]:
+        raise ValueError("Invalid number of rows in added column! (is %d, should be %d)" % (len(lst), sh[0]))
+      # Add a column
+      tmp = self._data
+      self._data = np.zeros((sh[0],sh[1]+1))
+      self._data[:,:-1] = tmp
+      idx = -1
+    else:
+      # First assignation
+      self._data = np.zeros((len(lst), 1), dtype=np.float64)
+      idx = 0
+    self._data[:, idx] = lst
+    self.notifyChange("DataChange") 
+  
+  def setColumnTitle(self, index, txt):
+    self._columnTitles[index] = txt
+    self.notifyChange("ColumnTitleChange") 
+  
+  def getColumnTitle(self, index):
+    return self._columnTitles.get(index, "")
+  
+  def removeValue(self, nrow, ncol):
+    sh = self.getShape()
+    if nrow >= sh[0] or ncol >= sh[1]:
+      raise ValueError("Specified row and column (%d, %d) invalid with current data size (%d, %d)" % (nrow, ncol, sh[0], sh[1]))
+    self._data[nrow, ncol] = np.NaN
+    self.notifyChange("DataChange") 
+    
+  def __str__(self):
+    return self._data.__str__()
+    
diff --git a/tools/CurvePlot/src/python/model/XYPlotSetModel.py b/tools/CurvePlot/src/python/model/XYPlotSetModel.py
new file mode 100644 (file)
index 0000000..18bb60e
--- /dev/null
@@ -0,0 +1,63 @@
+from Model import Model
+from utils import toUnicodeWithWarning
+
+class XYPlotSetModel(Model):
+  
+  def __init__(self, controller):
+    Model.__init__(self, controller)
+    self._title = "Plot set %d" % self.getID()
+    self._curves = {}  # Key: model ID, value: CurveModel
+    self._currentCurve = None
+    self._xlabel = ""
+    self._ylabel = ""
+    
+  def eraseAll(self):
+    self._curves = {}
+    self._currentCurve = None
+    self.notifyChange("ClearAll")
+    
+  def setTitle(self, title, silent=False):
+    title = toUnicodeWithWarning(title, "XYPlotSetModel::setTitle()")
+    self._title = title
+    if not silent:
+      self.notifyChange("TitleChange")
+      
+  def getTitle(self):
+    return self._title
+      
+  def setCurrentCurve(self, curveID, silent=False):
+    if not self._curves.has_key(curveID) and curveID != -1:
+      raise ValueError("Invalid curve ID (%d)!" % curveID)
+    self._currentCurve = self._curves.get(curveID, None)
+    if not silent:
+      self.notifyChange("CurrentCurveChange") 
+
+  def getCurrentCurve(self):
+    return self._currentCurve
+      
+  def addCurve(self, curve, silent=False):
+    self._curves[curve.getID()] = curve
+    if not silent:
+      self.notifyChange("AddCurve")
+  
+  def removeCurve(self, curveID, silent=False):
+    if not self._curves.has_key(curveID):
+      raise ValueError("Curve ID (%d) not found for deletion!" % curveID)
+    c = self._curves.pop(curveID)
+    if self._currentCurve is c:
+      self._currentCurve = None
+    if not silent:
+      self.notifyChange("RemoveCurve")
+    
+  def setXLabel(self, x_label, silent=False):
+    x_label = toUnicodeWithWarning(x_label, "XYPlotSetModel::setXLabel()")
+    self._xlabel = x_label
+    if not silent:
+      self.notifyChange("XLabelChange")
+  
+  def setYLabel(self, y_label, silent=False):
+    y_label = toUnicodeWithWarning(y_label, "XYPlotSetModel::setYLabel()")
+    self._ylabel = y_label
+    if not silent:
+      self.notifyChange("YLabelChange")
+  
\ No newline at end of file
diff --git a/tools/CurvePlot/src/python/pyqtside/CMakeLists.txt b/tools/CurvePlot/src/python/pyqtside/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9ec5d87
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(_pyqtside_SCRIPTS
+    __init__.py
+    QtCore.py
+    QtGui.py
+    pyside_dynamic.py
+    uic.py
+)
+
+SALOME_INSTALL_SCRIPTS("${_pyqtside_SCRIPTS}" ${SALOME_INSTALL_PYTHON}/pyqtside)
diff --git a/tools/CurvePlot/src/python/pyqtside/QtCore.py b/tools/CurvePlot/src/python/pyqtside/QtCore.py
new file mode 100644 (file)
index 0000000..6e90c29
--- /dev/null
@@ -0,0 +1,7 @@
+from . import _use_pyqt
+if _use_pyqt:
+  from PyQt4.QtCore import *
+  Slot = pyqtSlot  
+  Signal = pyqtSignal 
+else:
+  from PySide.QtCore import *
diff --git a/tools/CurvePlot/src/python/pyqtside/QtGui.py b/tools/CurvePlot/src/python/pyqtside/QtGui.py
new file mode 100644 (file)
index 0000000..15c7ee1
--- /dev/null
@@ -0,0 +1,37 @@
+from . import _use_pyqt
+if _use_pyqt:
+  from PyQt4.QtGui import *
+  
+  # Make QVariant invisible in PyQt4 since they don't exist in
+  # PySide ...
+  __original_itemData = QComboBox.itemData
+  def new_itemData(*args, **kargs):
+    from PyQt4.QtCore import QVariant
+    variant = __original_itemData(*args, **kargs)
+    funcS = lambda : (str(variant.toString()), True)
+    dico = {QVariant.Int: variant.toInt, QVariant.String: funcS,
+     QVariant.Bool: variant.toBool, QVariant.Double: variant.toDouble}
+    conv = dico.get(variant.type(), None)
+    if conv is None:
+      raise Exception("Unsupported variant type in pyqtside: '%s'!" % variant.typeName())
+    return conv()[0]
+  
+  QComboBox.itemData = new_itemData 
+else:
+  from PySide.QtGui import *
+  
+  __original_ofn = QFileDialog.getOpenFileName
+  __original_sfn = QFileDialog.getSaveFileName
+  
+  # In PySide, getOpenFileName and co returns 2 values, and only one in PyQt4 ...
+  def newOfn(cls,*args, **kargs):
+    tup = __original_ofn(*args, **kargs)
+    return tup[0]
+    
+  def newSfn(cls,*args, **kargs):
+    tup = __original_sfn(*args, **kargs)
+    return tup[0]
+    
+  QFileDialog.getOpenFileName = classmethod(newOfn)
+  QFileDialog.getSaveFileName = classmethod(newSfn)
+
diff --git a/tools/CurvePlot/src/python/pyqtside/__init__.py b/tools/CurvePlot/src/python/pyqtside/__init__.py
new file mode 100644 (file)
index 0000000..ef46e4b
--- /dev/null
@@ -0,0 +1,30 @@
+"""
+Group under one hat PySide and PyQt4. PyQt4 is tried first.
+"""
+
+try:
+  import os
+  if os.getenv("CURVEPLOT_FORCE_PYSIDE") is not None:
+    raise Exception
+  import PyQt4
+  _use_pyqt = True
+  print "Using PyQt4 run-time ..."
+except:
+  try:
+    import PySide
+    _use_pyqt = False
+    print "Using PySide run-time ..."
+  except:
+    raise Exception("Neither PyQt4 nor PySide could be imported!")
+
+# Matplotlib has to be handled very early, otherwise it will switch to whatever it
+# finds first on the machine
+try: 
+  import matplotlib
+  if _use_pyqt:  back = 'PyQt4'
+  else:          back = 'PySide'
+  matplotlib.rcParams['backend.qt4'] = back
+  print "Matplotlib found - Set matplotlib backend to '%s'!" % back 
+except:
+  # No matplotlib, silently discard err message.
+  pass
diff --git a/tools/CurvePlot/src/python/pyqtside/pyside_dynamic.py b/tools/CurvePlot/src/python/pyqtside/pyside_dynamic.py
new file mode 100644 (file)
index 0000000..a3ab604
--- /dev/null
@@ -0,0 +1,86 @@
+"""
+    How to load a user interface dynamically with PySide.
+    .. moduleauthor::  Sebastian Wiesner  <lunaryorn@gmail.com>
+"""
+from PySide.QtCore import Slot, QMetaObject
+from PySide.QtUiTools import QUiLoader
+
+class UiLoader(QUiLoader):
+    """
+    Subclass :class:`~PySide.QtUiTools.QUiLoader` to create the user interface
+    in a base instance.
+
+    Unlike :class:`~PySide.QtUiTools.QUiLoader` itself this class does not
+    create a new instance of the top-level widget, but creates the user
+    interface in an existing instance of the top-level class.
+
+    This mimics the behaviour of :func:`PyQt4.uic.loadUi`.
+    """
+    def __init__(self, baseinstance, customWidgets=None):
+        """
+        Create a loader for the given ``baseinstance``.
+
+        The user interface is created in ``baseinstance``, which must be an
+        instance of the top-level class in the user interface to load, or a
+        subclass thereof.
+
+        ``customWidgets`` is a dictionary mapping from class name to class object
+        for widgets that you've promoted in the Qt Designer interface. Usually,
+        this should be done by calling registerCustomWidget on the QUiLoader, but
+        with PySide 1.1.2 on Ubuntu 12.04 x86_64 this causes a segfault.
+
+        ``parent`` is the parent object of this loader.
+        """
+        QUiLoader.__init__(self, baseinstance)
+        self.baseinstance = baseinstance
+        self.customWidgets = customWidgets
+
+    def createWidget(self, class_name, parent=None, name=''):
+        """
+        Function that is called for each widget defined in ui file,
+        overridden here to populate baseinstance instead.
+        """
+        if parent is None and self.baseinstance:
+            return self.baseinstance
+        else:
+            if class_name in self.availableWidgets():
+                # create a new widget for child widgets
+                widget = QUiLoader.createWidget(self, class_name, parent, name)
+            else:
+                try:
+                    widget = self.customWidgets[class_name](parent)
+                except (TypeError, KeyError) as e:
+                    raise Exception('No custom widget ' + class_name + ' found in customWidgets param of UiLoader __init__.')
+
+            if self.baseinstance:
+                setattr(self.baseinstance, name, widget)
+
+            return widget
+
+def loadUi(uifile, baseinstance=None, customWidgets=None):
+    """
+    Dynamically load a user interface from the given ``uifile``.
+    ``uifile`` is a string containing a file name of the UI file to load.
+
+    If ``baseinstance`` is ``None``, the a new instance of the top-level widget
+    will be created.  Otherwise, the user interface is created within the given
+    ``baseinstance``.  In this case ``baseinstance`` must be an instance of the
+    top-level widget class in the UI file to load, or a subclass thereof. .
+
+    ``customWidgets`` is a dictionary mapping from class name to class object
+    for widgets that you've promoted in the Qt Designer interface. Usually,
+    this should be done by calling registerCustomWidget on the QUiLoader, but
+    with PySide 1.1.2 on Ubuntu 12.04 x86_64 this causes a segfault.
+
+    :method:`~PySide.QtCore.QMetaObject.connectSlotsByName()` is called on the
+    created user interface, so you can implemented your slots according to its
+    conventions in your widget class.
+
+    Return ``baseinstance``, if ``baseinstance`` is not ``None``.  Otherwise
+    return the newly created instance of the user interface.
+    """
+
+    loader = UiLoader(baseinstance, customWidgets)
+    widget = loader.load(uifile)
+    QMetaObject.connectSlotsByName(widget)
+    return widget
diff --git a/tools/CurvePlot/src/python/pyqtside/uic.py b/tools/CurvePlot/src/python/pyqtside/uic.py
new file mode 100644 (file)
index 0000000..3f9565b
--- /dev/null
@@ -0,0 +1,7 @@
+from . import _use_pyqt
+if _use_pyqt:
+  from PyQt4.uic import loadUi as loadUiGen
+else: 
+  from pyside_dynamic import loadUi as loadUiGen
+
+
diff --git a/tools/CurvePlot/src/python/test/CMakeLists.txt b/tools/CurvePlot/src/python/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..24f9eca
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(SGPYQT_RES_DIR "${SALOME_INSTALL_RES}")
+SALOME_CONFIGURE_FILE(SalomePyQt_MockUp.py.in mockup/SalomePyQt_MockUp.py)
+
+SET(_all_SCRIPTS
+    PlotCurve_Standalone.py
+    plot_test.py
+    TestDesktop.py
+)
+
+SALOME_INSTALL_SCRIPTS("${_all_SCRIPTS}" ${SALOME_INSTALL_SCRIPT_PYTHON}/tests)
+SALOME_INSTALL_SCRIPTS(${CMAKE_CURRENT_BINARY_DIR}/mockup/SalomePyQt_MockUp.py ${SALOME_INSTALL_SCRIPT_PYTHON}/tests)
+
+INSTALL(DIRECTORY baselines DESTINATION ${SALOME_INSTALL_SCRIPT_PYTHON}/tests)
+
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${CMAKE_CURRENT_SOURCE_DIR}/../model)
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${CMAKE_CURRENT_SOURCE_DIR}/../ui)
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${CMAKE_CURRENT_SOURCE_DIR}/../views)
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${CMAKE_CURRENT_SOURCE_DIR}/../controller)
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${CMAKE_CURRENT_SOURCE_DIR}/..)
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${PROJECT_BINARY_DIR}/src/python/ui)  # for the generated PY files (from UI files)
+SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH ${PROJECT_BINARY_DIR}/src/python/controller)  # for utils.py
+SALOME_GENERATE_TESTS_ENVIRONMENT(tests_env)
+
+ADD_TEST(CurvePlotUnitTests ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/plot_test.py)
+SET_TESTS_PROPERTIES(CurvePlotUnitTests PROPERTIES ENVIRONMENT "${tests_env}")
+
diff --git a/tools/CurvePlot/src/python/test/PlotCurve_Standalone.py b/tools/CurvePlot/src/python/test/PlotCurve_Standalone.py
new file mode 100755 (executable)
index 0000000..ad2886e
--- /dev/null
@@ -0,0 +1,75 @@
+# -*- coding: latin-1 -*-
+#  Copyright (C) 2007-2010  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+#  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+#  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+#  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : A. Bruneton
+#
+from pyqtside.QtGui import QApplication 
+from pyqtside.QtCore import SIGNAL, SLOT, QTimer, QTranslator
+
+from TestDesktop import TestDesktop
+import SalomePyQt_MockUp
+
+desktop = None
+
+def activate():
+    """This method mimicks SALOME's module activation """
+    global desktop
+
+    desktop.showCurveTreeView()
+    return True
+
+def activeViewChanged( viewID ):
+    from curveplot import PlotController
+    PlotController.ActiveViewChanged(viewID)
+
+def main(args) :
+    global desktop
+      
+    app = QApplication(args)
+    ts_files = ["/export/home/adrien/Projets/salome/modules/V7_main/CURVEPLOT_INSTALL/share/salome/resources/curveplot/CURVEPLOT_msg_fr.qm",
+                "/export/home/adrien/Projets/salome/modules/V7_main/CURVEPLOT_INSTALL/share/salome/resources/curveplot/CURVEPLOT_msg_en.qm"
+                ]
+    trans = QTranslator()
+    for f in ts_files:
+      if not trans.load(f):
+        print "could not load translation %s!" % f
+    app.installTranslator(trans)
+    dw = app.desktop()
+    x, y = dw.width()*0.25, dw.height()*0.7
+    
+    desktop = TestDesktop(None)
+    sgPyQt = SalomePyQt_MockUp.SalomePyQt(desktop)
+    sgPyQt.currentTabChanged.connect(activeViewChanged)
+    desktop._sgPyQt = sgPyQt 
+    desktop.initialize()
+    desktop.resize(x,y)
+    desktop.show()
+    activate()
+    #
+    QTimer.singleShot(200, desktop, SLOT("curveSameFig()"))
+    #
+    app.connect(app,SIGNAL("lastWindowClosed()"),app,SLOT("quit()"))
+    app.exec_()
+
+if __name__ == "__main__" :
+    import sys
+    main(sys.argv)
diff --git a/tools/CurvePlot/src/python/test/PlotTestBase.py b/tools/CurvePlot/src/python/test/PlotTestBase.py
new file mode 100644 (file)
index 0000000..361b0d1
--- /dev/null
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+#  Copyright (C) 2007-2010  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+#  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+#  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+#  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : A. Bruneton
+#
+import unittest, sys, os, filecmp, shutil, tempfile
+from pyqtside.QtGui import QApplication, QPixmap, QPainter
+from PlotController import PlotController
+from XYView import XYView
+
+def runOnly(func):
+  func.__runOnly__ = True
+  return func
+
+def processDecorator(mod_name):
+  """ Little trick to be able to mark a test with the decorator
+    @runOnly
+      If one or more tests bear this decorator, only them will be run
+  """
+  import inspect
+  someRunOnly = False
+  for name, obj in inspect.getmembers(sys.modules[mod_name]):
+    if name == "PlotTest" and inspect.isclass(obj):
+      for p in dir(obj):
+        if p.startswith("test") and hasattr(obj.__dict__[p], "__runOnly__"):
+          someRunOnly = True
+          break
+  if someRunOnly:
+    for name, obj in inspect.getmembers(sys.modules[mod_name]):
+      if name == "PlotTest" and inspect.isclass(obj):
+        for p in dir(obj):
+          # Note the "not":
+          if p.startswith("test") and not hasattr(obj.__dict__[p], "__runOnly__"):
+            delattr(obj, p)
+class PlotTestBase(unittest.TestCase):
+  """ Unit test suite for the curve plotter. This class deals with the set up and the screenshot generation/
+  comparison. The tests themselves are stored in the derived class PlotTest below.
+   
+  The class variable below can be turned on to regenerate base line files (reference images).
+  All baselines start with the name of the corresponding test, plus a possible suffix.
+  The baselines directory is set relative to the path of this script.
+  """
+  REBUILD_BASELINES = False
+  
+  __BASE_LINE_DIR = "baselines"
+  __FORMAT = "png"
+  
+  def __init__(self, methodName):
+    unittest.TestCase.__init__(self, methodName)
+     
+    if self.REBUILD_BASELINES:
+      self.tmpBaselineDir = os.path.join(tempfile.gettempdir(), "curveplot_baselines")
+      if not os.path.isdir(self.tmpBaselineDir):
+        os.mkdir(self.tmpBaselineDir)
+      print "### Rebuilding base lines. Reference files will be saved to '%s'" % self.tmpBaselineDir
+       
+    PlotController.WITH_CURVE_BROWSER = True
+    XYView._DEFAULT_LEGEND_STATE = True   # always show legend by default
+    self._this_dir = os.path.dirname(os.path.realpath(__file__))
+    
+#     import matplotlib as mpl
+#     mpl.use('Agg')
+  
+  def setUp(self):
+    from SalomePyQt_MockUp import SalomePyQt
+    from TableModel import TableModel
+    from CurveModel import CurveModel
+    from XYPlotSetModel import XYPlotSetModel
+
+    self.qpixmap = None
+    self.keepDir = False
+    if not self.REBUILD_BASELINES:
+      self.tmpDir = tempfile.mkdtemp(prefix="curveplot_tests")
+    else:
+      self.tmpDir = None
+    # Minimal UI setup:
+    self.sgPyQt = SalomePyQt()
+    # Reinstanciate from scratch the PlotController:
+    self.plotController = PlotController.GetInstance(self.sgPyQt)
+    self.plotController.setFixedSizeWidget()
+    # Reset some class var to make sure IDs appearing in screenshots do not depend on test seq order:
+    CurveModel.START_ID = -1
+    TableModel.START_ID = -1
+    XYPlotSetModel.START_ID = -1
+        
+  def tearDown(self):
+    if not self.REBUILD_BASELINES:
+      # Clean up temp dir where the file comparison has been made:
+      if not self.keepDir:
+        shutil.rmtree(self.tmpDir, False)
+    PlotController.Destroy()
+
+  def getTestName(self):
+    return self.id().split(".")[-1]
+
+  def saveCurrentPix(self, direct, suffix):
+    fileName = os.path.join(direct, self.getTestName() + suffix + "." + self.__FORMAT)
+    self.qpixmap.save(fileName, self.__FORMAT)
+    return fileName
+
+  def areScreenshotEqual(self, widget, suffix=""):
+    """ Test equality between a reference file saved in the baseline directory, and whose name is built as
+          "<test_name><suffix>.png"
+        and the file generated on the fly by taking a snapshot of the widget provided in argument.
+        The comparison is made in a temp dir which is kept if the file differ.
+    """
+    import glob
+    # Smiiiile :-)
+    self.qpixmap = QPixmap.grabWidget(widget)
+    
+    # Nothing to compare if rebuilding base lines, just saving file:
+    if self.REBUILD_BASELINES:
+      self.saveCurrentPix(self.tmpBaselineDir, suffix)
+      return True
+    
+    gen_path = self.saveCurrentPix(self.tmpDir, suffix)
+    base_ref = os.path.join(self._this_dir, self.__BASE_LINE_DIR, self.getTestName() + suffix)
+    ret = False
+    for ref_path in glob.glob("%s_*.%s" % (base_ref, self.__FORMAT)):
+      try:
+        ret = filecmp.cmp(ref_path, gen_path, shallow=False)
+        if ret:
+          break
+      except OSError:
+        ret = False
+    if not ret:
+      # Keep file if assert is false
+      self.keepDir = True
+      print "[%s] -- Failed screenshot equality, or unable to open baseline file - directory is kept alive: %s" % (self.getTestName(), self.tmpDir)
+    return ret 
+  
+  def showTabWidget(self):
+    tabW = self.plotController._sgPyQt._tabWidget
+    # No simpler way found so far:
+    tabW.show()
+    return tabW
+    
+  def getBrowserWidget(self):
+    return self.plotController._curveBrowserView._treeWidget
diff --git a/tools/CurvePlot/src/python/test/README.txt b/tools/CurvePlot/src/python/test/README.txt
new file mode 100644 (file)
index 0000000..bdb4025
--- /dev/null
@@ -0,0 +1,5 @@
+Unit tests are in plot_test.py
+
+PlotCurve_Standalone is a standalone Python executable that shows the
+various functionalities of the package.
+
diff --git a/tools/CurvePlot/src/python/test/SalomePyQt_MockUp.py.in b/tools/CurvePlot/src/python/test/SalomePyQt_MockUp.py.in
new file mode 100644 (file)
index 0000000..70f4f1b
--- /dev/null
@@ -0,0 +1,150 @@
+#  Copyright (C) 2007-2010  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+#  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+#  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+#  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : A. Bruneton
+#
+
+from pyqtside.QtGui import QApplication, QTabWidget 
+from pyqtside.QtGui import QAction, QMenu, QIcon, QPixmap, QDesktopWidget, QFileDialog
+from pyqtside.QtCore import SIGNAL, SLOT, QObject, Slot, Signal
+
+RESOURCE_DIR = "@SGPYQT_RES_DIR@"
+
+class SalomePyQt(QObject):
+  """ A pure Qt implementation of the SgPyQt API (usually provided in the SALOME context)
+  This class can be used to mimick the true SALOME object without having to launch 
+  SALOME
+  """  
+  currentTabChanged = Signal(int)
+  
+  START_VIEW_ID = 0
+  
+  def __init__(self, mainWindow=None):
+    QObject.__init__(self)
+    self._mainWindow = mainWindow
+    self._tabWidget = QTabWidget()
+    self._tabWidget.setObjectName("TabWidget")
+    self._viewIDs = {}
+    self._menuBar = None
+    if self._mainWindow:
+      self._menuBar = self._mainWindow.menuBar()
+      self._mainWindow.setCentralWidget(self._tabWidget)
+    self.connect(self._tabWidget, SIGNAL("currentChanged(int)"), self, SLOT("onTabChanged(int)"))
+    self._blockSignal = False
+  
+  def getDesktop(self):
+    return self._mainWindow
+  
+  def getFileName(self, parent_widget, initial, filters, caption, do_open):
+    fil = ";;".join([str(f) for f in filters])
+    return QFileDialog.getOpenFileName(parent=parent_widget,
+                                       caption=caption, directory=initial, filter=fil);
+  
+  @Slot(int)
+  def onTabChanged(self, index):
+    if self._blockSignal:
+      return
+    invDict = dict([(v, k) for k,v in self._viewIDs.items()])
+    if invDict.has_key(index):
+      self._blockSignal = True
+      self.currentTabChanged.emit(invDict[index])
+      self._blockSignal = False
+  
+  def createView(self, name, widget):
+    self.START_VIEW_ID += 1
+    idx = self._tabWidget.insertTab(-1, widget, name)
+    self._viewIDs[self.START_VIEW_ID] = idx 
+    return self.START_VIEW_ID
+  
+  def activateView(self, viewID):
+    idx = self._viewIDs[viewID]
+    self._tabWidget.setCurrentIndex(idx)
+  
+  def setViewVisible(self, viewID, isVis):
+    ## TODO: improve to really remove tab
+    if isVis:
+      self.activateView(viewID) 
+  
+  def closeView(self, viewID):
+    self._blockSignal = True
+    idxClosed = self._viewIDs[viewID]
+    # QTabWidget doesn't clean after itself when removing a tab
+    w = self._tabWidget.widget(idxClosed)  
+    self._tabWidget.removeTab(idxClosed)
+    try:
+      w.clearAll()
+    except:
+      pass
+    # Update the other tab indices which are now shifted:
+    for k, idx in self._viewIDs.items():
+      if idx > idxClosed:
+        self._viewIDs[k] -= 1
+    self._blockSignal = False
+  
+  def setViewTitle(self, viewID, title):
+    idx = self._viewIDs[viewID]
+    self._tabWidget.setTabText(idx, title)
+  
+  def createAction(self, id, short_name, long_name, tooltip, icon):
+    import os
+    return QAction(QIcon(QPixmap(os.path.normpath(icon))),short_name, None)
+  
+  def defaultMenuGroup(self):
+    return None
+  
+  def createMenu(self, name_or_action, pos_or_menu, menuId=-1, menuGroup=None):
+    if not self._mainWindow is None:
+      if isinstance(name_or_action, str):
+        return self.__createMenu1( name_or_action, pos_or_menu, menuId, menuGroup)
+      else:
+        return self.__createMenu2(name_or_action, pos_or_menu)
+    
+  def __createMenu1(self, name, pos, menuId, menuGroup):
+    menu = QMenu(name, self._menuBar)
+    self._menuBar.addMenu(menu)
+    return menu
+  
+  def __createMenu2(self, action, menu):
+    menu.addAction(action)
+  
+  def createSeparator(self):
+    return None
+  
+  def createTool(self, toolbar_name_or_action, toolbar=None):
+    if not self._mainWindow is None:
+      if isinstance(toolbar_name_or_action, str):
+        return self.__createTool1(toolbar_name_or_action)
+      else:
+        return self.__createTool2(toolbar_name_or_action, toolbar)
+    
+  def __createTool1(self, toolbar_name):
+    return None
+  
+  def __createTool2(self, action, toolbar):
+    return None
+  
+  def loadIcon(self, module_name, file_name):
+    import os
+    mod_dir = os.getenv("%s_ROOT_DIR" % module_name)
+    mod_lc = module_name.lower()
+    res_path = os.path.join(mod_dir, RESOURCE_DIR, mod_lc, file_name)
+    # e.g. MODULE_ROOT_DIR/share/resource/module/image.png
+    return QIcon(res_path)
diff --git a/tools/CurvePlot/src/python/test/TestDesktop.py b/tools/CurvePlot/src/python/test/TestDesktop.py
new file mode 100644 (file)
index 0000000..8d72fd6
--- /dev/null
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+#  Copyright (C) 2007-2010  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+#  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+#  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+#  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : A. Bruneton
+#
+
+from pyqtside.QtCore import SIGNAL, SLOT, Slot, Qt, QTimer
+from pyqtside.QtGui import QMainWindow,QMenu
+import numpy as np
+
+import curveplot
+
+try:
+  import curveplot
+  from curveplot.utils import Logger
+except:
+  from PlotController import PlotController as curveplot
+  from utils import Logger 
+
+class TestDesktop(QMainWindow):
+    """ Dummy desktop for testing purposes.
+    """    
+    def __init__(self, sgPyQt):
+        QMainWindow.__init__(self)
+        self.cnt = -1
+        self.MAX_CNT = 20
+        self.timeLap = 100
+        self._sgPyQt = sgPyQt
+        
+    def initialize(self):
+        """ Initialize is called later than __init__() so that the Desktop and the SgPyQt
+        objects can be properly initialized.
+        """
+        self._currID = 1234
+        
+        self._sgDesktop = self
+        try:
+          self._plotController = curveplot.PlotController.GetInstance(self._sgPyQt)
+        except:
+          self._plotController = curveplot.GetInstance(self._sgPyQt)
+        self.createIDs()
+        self.createActions()
+        
+        context_actions = [self.itemDelAction]
+        menu = QMenu(self)
+        for a in context_actions:
+          menu.addAction(a)
+        self._plotController.setBrowserContextualMenu(menu)
+        
+        self.createToolbars()
+        self.createMenus()
+        self.createView()
+        
+        self.connect(self.curveSameFigAction,SIGNAL("activated()"),self.curveSameFig)
+        self.connect(self.curveNewFigAction,SIGNAL("activated()"),self.curveNewFig)
+        self.connect(self.itemDelAction,SIGNAL("activated()"),self.itemDel)
+        self.connect(self.cpsAction,SIGNAL("activated()"),self.clearPlotSet)
+        self.connect(self.plotTableAction,SIGNAL("activated()"),self.plotTable)
+        self.connect(self.addPSAction,SIGNAL("activated()"),self.addPS)
+        self.connect(self.addTabAction,SIGNAL("activated()"),self.addTab)
+        self.connect(self.memAction,SIGNAL("activated()"),self.memPrint)
+        self.connect(self.perfTestAction,SIGNAL("activated()"),self.perfTest)
+
+    def generateID(self):
+        self._currID += 1
+        return self._currID
+    
+    def createIDs(self):
+        # Actions
+        self.curveSameFigActionID = self.generateID()
+        self.curveNewFigActionID = self.generateID()
+        self.itemDelActionID = self.generateID()
+        self.cpsActionID = self.generateID()
+        self.plotTableActionID = self.generateID()
+        self.addPSActionID = self.generateID()
+        self.addTabActionID = self.generateID()
+        self.memActionID = self.generateID()
+        self.perfActionID = self.generateID()
+        
+        # Menus
+        self.etudeMenuID = self.generateID()
+        self.dummyMenuID = self.generateID()
+
+    def createActions(self):
+        ca = self._sgPyQt.createAction
+        self.curveSameFigAction = ca(self.curveSameFigActionID, "Curve on same fig", "Curve on same fig", "", "")
+        self.curveNewFigAction = ca(self.curveNewFigActionID, "Curve on new fig", "Curve on new fig", "", "")
+        self.itemDelAction = ca(self.itemDelActionID, "Delete selected", "Delete selected", "", "")
+        self.cpsAction = ca(self.cpsActionID, "Clear plot set", "Clear plot set", "", "")
+        self.plotTableAction = ca(self.plotTableActionID, "Plot from table", "Plot from table", "", "")
+        self.addPSAction = ca(self.addPSActionID, "Add plot set", "Add plot set", "", "")
+        self.addTabAction = ca(self.addTabActionID, "Add tab", "Add tab", "", "")
+        self.memAction = ca(self.memActionID, "Display used mem", "Display used mem", "", "")
+        self.perfTestAction = ca(self.perfActionID, "Perf test", "Perf test", "", "")
+
+    def createToolbars(self):
+        pass
+#         self.Toolbar = self._sgPyQt.createTool(self.tr("Toolbar"))
+#         self._sgPyQt.createTool(self.fileNewAction, self.Toolbar)
+#         self._sgPyQt.createTool(self.filePrintAction, self.Toolbar)
+#         sep = self._sgPyQt.createSeparator()
+#         self._sgPyQt.createTool(sep, self.Toolbar)
+#         self._sgPyQt.createTool(self.editUndoAction, self.Toolbar)
+#         self._sgPyQt.createTool(self.editRedoAction, self.Toolbar)
+
+    def createMenus(self):
+        curveMenu = self._sgPyQt.createMenu( "Curve test", -1, self.etudeMenuID, self._sgPyQt.defaultMenuGroup() )
+        self._sgPyQt.createMenu(self.curveSameFigAction, curveMenu)
+        self._sgPyQt.createMenu(self.curveNewFigAction, curveMenu)
+        self._sgPyQt.createMenu(self.itemDelAction, curveMenu)
+        self._sgPyQt.createMenu(self.cpsAction, curveMenu)
+        self._sgPyQt.createMenu(self.plotTableAction, curveMenu)
+        self._sgPyQt.createMenu(self.addPSAction, curveMenu)
+        self._sgPyQt.createMenu(self.memAction, curveMenu)
+        self._sgPyQt.createMenu(self.perfTestAction, curveMenu)
+        
+        dummyMenu = self._sgPyQt.createMenu( "Dummy", -1, self.dummyMenuID, self._sgPyQt.defaultMenuGroup() )
+        self._sgPyQt.createMenu(self.addTabAction, dummyMenu)
+
+    def createView(self):
+        pass
+          
+    def showCurveTreeView(self) :
+        self._dockCurveBrowserView = self._plotController._curveBrowserView
+        self._sgDesktop.addDockWidget(Qt.LeftDockWidgetArea, self._dockCurveBrowserView)
+
+    def __generateRandomData(self, nPoints=100):
+      from random import random
+      x = np.arange(nPoints) / 5.0
+      ampl = 20.0*random() + 1.0
+      y = ampl * np.sin(x*random())
+#       x = np.arange(5e5)
+#       y = x
+      return x, y
+       
+    @Slot()  
+    def curveSameFig(self):
+      x, y = self.__generateRandomData()
+      _, ps_id = curveplot.AddCurve(x, y, x_label="the x axis", y_label="the y axis", append=True)
+      curveplot.SetLegendVisible(ps_id, True)
+      if self.cnt >= 0:
+        QTimer.singleShot(self.timeLap, self, SLOT("itemDel()"))
+            
+    def curveNewFig(self):
+      x, y = self.__generateRandomData()
+      curveplot.AddCurve(x, y, x_label="the x axis", y_label="the y axis", append=False)
+    
+    @Slot()
+    def itemDel(self):
+      curveplot.DeleteCurrentItem()
+      if self.cnt >= 0:
+        QTimer.singleShot(self.timeLap, self, SLOT("memPrint()"))
+
+    @Slot()
+    def perfTest(self):
+      lx, ly = [], []
+      nC = 200
+      for _ in range(nC):
+        x, y = self.__generateRandomData(1000)
+        lx.append(x); ly.append(y)
+      print "Done generating"
+      from time import time
+      t0 = time()
+      curveplot.LockRepaint()
+      for i in range(nC): 
+        curveplot.AddCurve(lx[i], ly[i], append=True)
+      curveplot.UnlockRepaint()
+      print "Elapsed: %.2f" % ( time() - t0)
+
+    def clearPlotSet(self):
+      curveplot.ClearPlotSet()
+      
+    def addPS(self):
+      # Also a test for unicode!
+      curveplot.AddPlotSet(u'ça m embête')
+      
+    def addTab(self):
+      pass
+#      from PyQt4.QtGui import QPushButton
+#      self.qp = QPushButton("Hi!")
+#      self._sgPyQt.createView("Dummy", self.qp)  
+      
+    def plotTable(self):
+      from curveplot import TableModel
+      t = TableModel(None)
+      t.setTitle("coucou")
+      t.addColumn([1.0,2.0,3.0,4.0])
+      t.addColumn([1.0,2.0,3.0,4.0])
+      t.addColumn([1.0,4.0,9.0,16.0])
+      t.setColumnTitle(0, "X-s")
+      t.setColumnTitle(1, "Identity")
+      t.setColumnTitle(2, "Square")
+      cont = curveplot.PlotController.GetInstance()
+      cont.plotCurveFromTable(t, y_col_index=1, append=False)
+      cont.plotCurveFromTable(t, y_col_index=2, append=True)
+    
+    @Slot()
+    def memPrint(self):
+      i, t = curveplot.GetAllPlotSets()
+      print zip(i, t)
+      new_id = curveplot.CopyCurve(curve_id=0, plot_set_id=1)
+      print "created  curve: %d" % new_id
+      import resource
+      m = resource.getrusage(resource.RUSAGE_SELF)[2]*resource.getpagesize()/1e6
+      print "** Used memory: %.2f Mb" % m
+      if self.cnt >= 0 and self.cnt < self.MAX_CNT:
+        self.cnt += 1
+        QTimer.singleShot(self.timeLap, self, SLOT("curveSameFig()"))
diff --git a/tools/CurvePlot/src/python/test/baselines/testAddCurveAppend_a.png b/tools/CurvePlot/src/python/test/baselines/testAddCurveAppend_a.png
new file mode 100644 (file)
index 0000000..be7de68
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testAddCurveAppend_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testAddCurve_a.png b/tools/CurvePlot/src/python/test/baselines/testAddCurve_a.png
new file mode 100644 (file)
index 0000000..69de691
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testAddCurve_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testAddPlotSet_a.png b/tools/CurvePlot/src/python/test/baselines/testAddPlotSet_a.png
new file mode 100644 (file)
index 0000000..4a7417b
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testAddPlotSet_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testClearPlotSet2_a.png b/tools/CurvePlot/src/python/test/baselines/testClearPlotSet2_a.png
new file mode 100644 (file)
index 0000000..4a7417b
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testClearPlotSet2_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testClearPlotSet_a.png b/tools/CurvePlot/src/python/test/baselines/testClearPlotSet_a.png
new file mode 100644 (file)
index 0000000..c9144c2
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testClearPlotSet_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testCopyCurve_a.png b/tools/CurvePlot/src/python/test/baselines/testCopyCurve_a.png
new file mode 100644 (file)
index 0000000..a08cf1b
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testCopyCurve_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_curve_a.png b/tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_curve_a.png
new file mode 100644 (file)
index 0000000..2b16e2c
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_curve_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_plotSet_a.png b/tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_plotSet_a.png
new file mode 100644 (file)
index 0000000..b84993b
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeleteCurrentItem_plotSet_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeleteCurve1_a.png b/tools/CurvePlot/src/python/test/baselines/testDeleteCurve1_a.png
new file mode 100644 (file)
index 0000000..1043c94
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeleteCurve1_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeleteCurve2_a.png b/tools/CurvePlot/src/python/test/baselines/testDeleteCurve2_a.png
new file mode 100644 (file)
index 0000000..1043c94
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeleteCurve2_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeleteCurve3_a.png b/tools/CurvePlot/src/python/test/baselines/testDeleteCurve3_a.png
new file mode 100644 (file)
index 0000000..e9f8ed6
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeleteCurve3_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeletePlotSet1_a.png b/tools/CurvePlot/src/python/test/baselines/testDeletePlotSet1_a.png
new file mode 100644 (file)
index 0000000..2489502
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeletePlotSet1_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testDeletePlotSet2_a.png b/tools/CurvePlot/src/python/test/baselines/testDeletePlotSet2_a.png
new file mode 100644 (file)
index 0000000..2489502
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testDeletePlotSet2_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testExtendCurve_a.png b/tools/CurvePlot/src/python/test/baselines/testExtendCurve_a.png
new file mode 100644 (file)
index 0000000..1476e78
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testExtendCurve_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testLockRepaint_a.png b/tools/CurvePlot/src/python/test/baselines/testLockRepaint_a.png
new file mode 100644 (file)
index 0000000..98b1670
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testLockRepaint_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testPlotCurveFromTable_a.png b/tools/CurvePlot/src/python/test/baselines/testPlotCurveFromTable_a.png
new file mode 100644 (file)
index 0000000..91bfc40
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testPlotCurveFromTable_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testResetCurve_a.png b/tools/CurvePlot/src/python/test/baselines/testResetCurve_a.png
new file mode 100644 (file)
index 0000000..ba52552
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testResetCurve_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve2_a.png b/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve2_a.png
new file mode 100644 (file)
index 0000000..01ea93a
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve2_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve3_a.png b/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve3_a.png
new file mode 100644 (file)
index 0000000..2b16e2c
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve3_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve_a.png b/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve_a.png
new file mode 100644 (file)
index 0000000..1d5c921
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetCurrentCurve_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetCurrentPlotSet_a.png b/tools/CurvePlot/src/python/test/baselines/testSetCurrentPlotSet_a.png
new file mode 100644 (file)
index 0000000..e890d57
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetCurrentPlotSet_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetCurveLabel_a.png b/tools/CurvePlot/src/python/test/baselines/testSetCurveLabel_a.png
new file mode 100644 (file)
index 0000000..8d4ffb3
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetCurveLabel_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetCurveMarker_a.png b/tools/CurvePlot/src/python/test/baselines/testSetCurveMarker_a.png
new file mode 100644 (file)
index 0000000..9f6eebc
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetCurveMarker_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetLabelX_a.png b/tools/CurvePlot/src/python/test/baselines/testSetLabelX_a.png
new file mode 100644 (file)
index 0000000..f261e73
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetLabelX_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetLabelY_a.png b/tools/CurvePlot/src/python/test/baselines/testSetLabelY_a.png
new file mode 100644 (file)
index 0000000..4a7417b
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetLabelY_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetLegendVisible_a.png b/tools/CurvePlot/src/python/test/baselines/testSetLegendVisible_a.png
new file mode 100644 (file)
index 0000000..0dae71d
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetLegendVisible_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetPlotSetTitle_a.png b/tools/CurvePlot/src/python/test/baselines/testSetPlotSetTitle_a.png
new file mode 100644 (file)
index 0000000..e805f17
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetPlotSetTitle_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetXSciNotation_a.png b/tools/CurvePlot/src/python/test/baselines/testSetXSciNotation_a.png
new file mode 100644 (file)
index 0000000..336bbeb
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetXSciNotation_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSetYSciNotation_a.png b/tools/CurvePlot/src/python/test/baselines/testSetYSciNotation_a.png
new file mode 100644 (file)
index 0000000..44e29e6
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSetYSciNotation_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSettingsCurveColor_a.png b/tools/CurvePlot/src/python/test/baselines/testSettingsCurveColor_a.png
new file mode 100644 (file)
index 0000000..5dbdbd7
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSettingsCurveColor_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testSettingsCurveMarker_a.png b/tools/CurvePlot/src/python/test/baselines/testSettingsCurveMarker_a.png
new file mode 100644 (file)
index 0000000..e9df517
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testSettingsCurveMarker_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testToggleXLog_a.png b/tools/CurvePlot/src/python/test/baselines/testToggleXLog_a.png
new file mode 100644 (file)
index 0000000..5fb1fcd
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testToggleXLog_a.png differ
diff --git a/tools/CurvePlot/src/python/test/baselines/testToggleYLog_a.png b/tools/CurvePlot/src/python/test/baselines/testToggleYLog_a.png
new file mode 100644 (file)
index 0000000..0db30fb
Binary files /dev/null and b/tools/CurvePlot/src/python/test/baselines/testToggleYLog_a.png differ
diff --git a/tools/CurvePlot/src/python/test/plot_test.py b/tools/CurvePlot/src/python/test/plot_test.py
new file mode 100644 (file)
index 0000000..204406c
--- /dev/null
@@ -0,0 +1,526 @@
+# -*- coding: utf-8 -*-
+#  Copyright (C) 2007-2010  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+#  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+#  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+#  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : A. Bruneton
+#
+
+from PlotTestBase import PlotTestBase, runOnly, processDecorator
+
+from PlotController import PlotController
+from PlotSettings import PlotSettings
+
+from pyqtside.QtGui import QApplication
+import sys
+qapp = QApplication(sys.argv)  
+  
+class PlotTest(PlotTestBase):
+  """ Unit test suite for the curve plotter. The tests themselves are stored in this class,
+  the screenshot comparison logic is in PlotTestBase.
+   
+  The class variable below can be turned on to regenerate base line files (reference images).
+  All baselines start with the name of the corresponding test, plus a possible suffix.
+  The baselines directory is set relative to the path of this script.
+  
+  The decorator @runOnly can be used to run/rebuild a single test.
+  """
+  REBUILD_BASELINES = False
+  
+  def __init__(self, methodName):
+    PlotTestBase.__init__(self, methodName)
+  
+  ###
+  ### Data generation
+  ###
+  def generateSine(self, alpha=1.0):
+    import numpy as np
+    x = np.arange(100)
+    y = np.sin(x*alpha/np.pi)
+    return x, y
+  
+  def generateExp(self, alpha=1.0):
+    import numpy as np
+    x = np.arange(20) + 1.0
+    y = np.exp(x*alpha)
+    return x, y
+   
+  ###
+  ### The tests themselves
+  ###
+    
+  #
+  # Non GUI tests (some of them still need to show the widget to work properly but no
+  # screenshot comparison is made).
+  #
+  def testTableModel(self):
+    import numpy as np
+    from TableModel import TableModel
+    t = TableModel(None)
+    t.setTitle("coucou")
+    t.addColumn([1.0,2.0,3.0,4.0])
+    self.assertRaises(ValueError, t.addColumn, [1.0,2.0])
+    t.addColumn([1.0,2.0,3.0,4.0])
+    t.addColumn([1.0, 4.0, 9.0, 16.0])
+    self.assertEqual((4,3), t.getShape())
+    t.removeValue(0, 1)
+    self.assertTrue(np.isnan(t.getData()[0,1]))
+    t.setColumnTitle(1, "a title")
+    self.assertEqual("a title", t.getColumnTitle(1))
+    self.assertEqual("", t.getColumnTitle(0))
+       
+  def testGetAllPlotSets(self):
+    self.showTabWidget()
+    ids, titles = PlotController.GetAllPlotSets()
+    self.assertEqual([], ids)
+    self.assertEqual([], titles)
+         
+    id1 = PlotController.AddPlotSet("toto")
+    id2 = PlotController.AddPlotSet("tutu")
+    id3 = PlotController.AddPlotSet("titi")
+    ids, titles = PlotController.GetAllPlotSets()
+    self.assertEqual([id1,id2,id3], ids)
+    self.assertEqual(["toto","tutu","titi"], titles)
+     
+  def testGetCurrentXX(self):
+    self.showTabWidget()
+    self.assertEqual(-1, PlotController.GetCurrentCurveID())
+    self.assertEqual(-1, PlotController.GetCurrentPlotSetID())
+      
+    x, y = self.generateSine()
+    _, psID1 = PlotController.AddCurve(x, y, append=False)
+    self.assertEqual(psID1, PlotController.GetCurrentPlotSetID())
+    _, psID2 = PlotController.AddCurve(x, y, append=True)
+    self.assertEqual(psID1, psID2)  # doesn't hurt!
+    self.assertEqual(psID2, PlotController.GetCurrentPlotSetID())
+    psID3 = PlotController.AddPlotSet("ps")
+    self.assertEqual(psID3, PlotController.GetCurrentPlotSetID())
+    PlotController.DeletePlotSet(psID3)
+    PlotController.DeletePlotSet(psID2)
+    self.assertEqual(-1, PlotController.GetCurrentCurveID())
+    self.assertEqual(-1, PlotController.GetCurrentPlotSetID())
+         
+  def testGetPlotSetID(self):
+    self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, psID = PlotController.AddCurve(x, y, append=False)
+    self.assertEqual(psID, PlotController.GetPlotSetID(crvID))
+    self.assertEqual(-1, PlotController.GetPlotSetID(145))  # invalid ID
+    PlotController.DeletePlotSet(psID)
+    self.assertEqual(-1, PlotController.GetPlotSetID(crvID))  # invalid ID
+        
+  def testGetPlotSetIDByName(self):
+    self.showTabWidget()
+    self.assertEqual(-1,PlotController.GetPlotSetIDByName("invalid"))
+    psID = PlotController.AddPlotSet("ps")
+    self.assertEqual(psID,PlotController.GetPlotSetIDByName("ps"))
+    PlotController.DeletePlotSet(psID)
+    self.assertEqual(-1,PlotController.GetPlotSetIDByName("ps"))
+        
+  def testIsValidPlotSetID(self):
+    self.showTabWidget()
+    self.assertEqual(False,PlotController.IsValidPlotSetID(0))
+    psID = PlotController.AddPlotSet("ps")
+    self.assertEqual(True,PlotController.IsValidPlotSetID(psID))
+    PlotController.DeletePlotSet(psID)
+    self.assertEqual(False,PlotController.IsValidPlotSetID(psID))
+      
+  #
+  # GUI tests
+  #    
+  def testAddCurve(self):
+    x, y = self.generateSine()
+    tw = self.showTabWidget()
+    PlotController.AddCurve(x, y, curve_label="My curve", x_label=u"Lèés X (unicode!)", y_label=u"Et des ŷ", append=False)
+    self.assertTrue(self.areScreenshotEqual(tw))
+      
+  def testAddCurveAppend(self):
+    x, y = self.generateSine()
+    tw = self.showTabWidget()
+    PlotController.AddCurve(x, y, curve_label="My curve", x_label="The X-s", y_label="The Y-s", append=False)
+    PlotController.AddCurve(x, y*1.5, curve_label="My curve 2", append=True)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testAddPlotSet(self):
+    tw = self.showTabWidget()
+    PlotController.AddPlotSet("My plotset")
+    self.assertTrue(self.areScreenshotEqual(tw))
+          
+  def testClearPlotSet(self):
+    x, y = self.generateSine()
+    tw = self.showTabWidget()
+    PlotController.AddCurve(x, y, curve_label="My curve", x_label="The X-s", y_label="The Y-s", append=False)
+    _, psID = PlotController.AddCurve(x, y, curve_label="My curve 2", append=True)    
+    clearedID = PlotController.ClearPlotSet()
+    self.assertEqual(clearedID, psID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testClearPlotSet2(self):
+    tw = self.showTabWidget()
+    self.assertRaises(ValueError, PlotController.ClearPlotSet, -789)
+    psID = PlotController.AddPlotSet("My plotset")
+    clearedID = PlotController.ClearPlotSet(psID)
+    self.assertEqual(psID, clearedID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+          
+  def testCopyCurve(self):
+    x, y = self.generateSine()
+    tw = self.showTabWidget()
+    crvID, _ = PlotController.AddCurve(x, y, curve_label="My curve", x_label="The X-s", y_label="The Y-s", append=False)
+    psID = PlotController.AddPlotSet("Another plotset")
+    newID = PlotController.CopyCurve(crvID, psID)
+    PlotController.SetCurrentPlotSet(psID)
+    self.assertNotEqual(crvID, newID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+          
+  def testDeleteCurrentItem_curve(self):
+    x, y = self.generateSine()
+    tw = self.showTabWidget()
+    PlotController.AddCurve(x, y, append=False)
+    crvID, _ = PlotController.AddCurve(x, y*1.5, append=True)
+    PlotController.SetCurrentCurve(crvID)
+    b, anID = PlotController.DeleteCurrentItem()  # currently selected curve
+    self.assertFalse(b)
+    self.assertEqual(crvID, anID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+          
+  def testDeleteCurrentItem_plotSet(self):
+    tw = self.showTabWidget()
+    PlotController.AddPlotSet("tutu")
+    psID = PlotController.AddPlotSet("tata")
+    b, anID = PlotController.DeleteCurrentItem()
+    self.assertTrue(b)
+    self.assertEqual(psID, anID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+            
+  def testDeleteCurrentItem_void(self):
+    self.showTabWidget()
+    b, anID = PlotController.DeleteCurrentItem()  # nothing selected 
+    self.assertTrue(b)
+    self.assertEqual(-1, anID)
+          
+  def testDeleteCurve1(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.AddCurve(x, y*1.5, append=True)
+    cID = PlotController.DeleteCurve(crvID)
+    self.assertEqual(crvID, cID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+  def testDeleteCurve2(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.AddCurve(x, y*1.5, append=True)
+    PlotController.SetCurrentCurve(crvID)
+    cID = PlotController.DeleteCurve()   # current curve
+    self.assertEqual(crvID, cID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+     
+  def testDeleteCurve3(self):
+    """ resulting in an empty plot set, legend should be hidden """
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    cID = PlotController.DeleteCurve(crvID) 
+    self.assertEqual(crvID, cID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+  def testDeletePlotSet1(self):
+    tw = self.showTabWidget()
+    psID = PlotController.AddPlotSet("tutu")
+    PlotController.AddPlotSet("tata")
+    psID2 = PlotController.DeletePlotSet(psID)
+    self.assertEqual(psID2, psID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+  def testDeletePlotSet2(self):
+    tw = self.showTabWidget()
+    psID1 = PlotController.DeletePlotSet()
+    self.assertEqual(-1, psID1)             # nothing selected yet
+    psID2 = PlotController.AddPlotSet("tutu")
+    PlotController.AddPlotSet("tata")
+    PlotController.SetCurrentPlotSet(psID2)
+    psID3 = PlotController.DeletePlotSet()  # current plot set
+    self.assertEqual(psID3, psID2)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+  def testSetCurrentCurve(self):
+    tw = self.showTabWidget()
+    self.assertRaises(ValueError, PlotController.SetCurrentCurve, 23)
+    x, y = self.generateSine()
+    crvID, psID = PlotController.AddCurve(x, y, append=False)
+    _, _ = PlotController.AddCurve(x, y, append=False)  # in a new plot set
+    psID2 = PlotController.SetCurrentCurve(crvID)
+    self.assertEqual(psID, psID2)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+  def testSetCurrentCurve2(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, psID = PlotController.AddCurve(x, y, append=False)
+    PlotController.SetCurrentCurve(crvID)
+    _, crvID2 = PlotController.AddCurve(x, y, append=False)  # in a new plot set
+    PlotController.SetCurrentCurve(crvID2)
+    # on first plot set curve should not be selected anymore
+    PlotController.SetCurrentPlotSet(psID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+  
+  def testSetCurrentCurve3(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    # Selecting and de-selecting
+    PlotController.SetCurrentCurve(crvID)
+    PlotController.SetCurrentCurve(-1)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+  def testSetCurrentPlotSet(self):
+    tw = self.showTabWidget()
+    psID = PlotController.AddPlotSet("tutu")
+    PlotController.AddPlotSet("tata")
+    PlotController.SetCurrentPlotSet(psID)
+    self.assertTrue(self.areScreenshotEqual(tw))
+    self.assertRaises(ValueError, PlotController.SetCurrentPlotSet, 124) # invalid ps_id
+     
+  def testSetLabelX(self):
+    tw = self.showTabWidget()
+    ps_id = PlotController.AddPlotSet("My plotset")
+    PlotController.SetXLabel(u"The X-s éà", ps_id)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testSetLabelY(self):
+    tw = self.showTabWidget()
+    ps_id = PlotController.AddPlotSet("My plotset")
+    PlotController.SetYLabel(u"Tutu", ps_id)
+    PlotController.SetYLabel(u"The Y-s uûàç", ps_id)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testSetPlotSetTitle(self):
+    tw = self.showTabWidget()
+    ps_id = PlotController.AddPlotSet("tutu")
+    PlotController.AddPlotSet("tata")
+    PlotController.SetPlotSetTitle(u"un titre àé", ps_id)
+    PlotController.SetCurrentPlotSet(ps_id)
+    self.assertTrue(self.areScreenshotEqual(tw))
+        
+#   def testToggleCurveBrowser(self):
+#     # hard to test ...
+#     raise NotImplementedError
+          
+  def testPlotCurveFromTable(self):
+    tw = self.showTabWidget()
+    from TableModel import TableModel
+    t = TableModel(None)
+    t.setTitle("coucou")
+    t.addColumn([1.0,2.0,3.0,4.0])
+    t.addColumn([1.0,2.0,3.0,4.0])
+    t.addColumn([1.0,4.0,9.0,16.0])
+    t.setColumnTitle(0, "X-s")
+    t.setColumnTitle(1, "Identity")
+    t.setColumnTitle(2, "Square")
+    cont = PlotController.GetInstance()
+    cont.plotCurveFromTable(t, y_col_index=1, append=False)
+    cont.plotCurveFromTable(t, y_col_index=2, append=True)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testSettingsCurveColor(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.SetCurrentCurve(crvID)
+    # Emulate changing the curve color from the settings box:
+    dlg_test = PlotSettings()
+    def fun():
+      dlg_test.setRGB(0,0,0)
+      dlg_test.showLegendCheckBox.setChecked(True)
+      return True  
+    dlg_test.exec_ = fun
+    t = PlotController.GetInstance()._curveTabsView._XYViews.items()
+    t[0][1].onSettings(dlg_test=dlg_test)  
+    self.assertTrue(self.areScreenshotEqual(tw))
+  def testExtendCurve(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.SetCurrentCurve(crvID)
+    PlotController.ExtendCurve(crvID, x+100.0, y*2.0)
+    # Curve must remain blue, bold and with first marker:      
+    self.assertTrue(self.areScreenshotEqual(tw))
+     
+  def testResetCurve(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.SetCurrentCurve(crvID)
+    PlotController.ResetCurve(crvID)
+    PlotController.ExtendCurve(crvID, x+100.0, y*x)
+    # Curve must remain blue, bold and with first marker:      
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testSettingsCurveMarker(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.SetCurrentCurve(crvID)
+    # Emulate changing the marker from the settings box:
+    dlg_test = PlotSettings()
+    def fun():
+      dlg_test.markerCurve.setCurrentIndex(2)
+      dlg_test.showLegendCheckBox.setChecked(True)
+      return True  
+    dlg_test.exec_ = fun
+    t = PlotController.GetInstance()._curveTabsView._XYViews.items()
+    t[0][1].onSettings(dlg_test=dlg_test)  
+    self.assertTrue(self.areScreenshotEqual(tw))
+    
+  def testSetCurveMarker(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, append=False)
+    PlotController.SetCurveMarker(crvID, "v")
+    self.assertTrue(self.areScreenshotEqual(tw))
+  
+  def testSetCurveLabel(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y, curve_label="titi", append=False)
+    _, _ = PlotController.AddCurve(x, y, curve_label="toto", append=True)
+    PlotController.SetCurrentCurve(crvID)
+    PlotController.SetCurveLabel(crvID, "tata")
+    self.assertTrue(self.areScreenshotEqual(tw))
+    
+  def testToggleXLog(self):
+    tw = self.showTabWidget()
+    x, y = self.generateExp()
+    _, psID = PlotController.AddCurve(x, y, curve_label="titi", append=False)
+    PlotController.SetXLog(psID, True)
+    PlotController.SetYSciNotation(psID, True)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testToggleYLog(self):
+    tw = self.showTabWidget()
+    x, y = self.generateExp()
+    _, psID = PlotController.AddCurve(x, y, curve_label="titi", append=False)
+    PlotController.SetYLog(psID, True)
+    PlotController.SetYSciNotation(psID, True)
+    self.assertTrue(self.areScreenshotEqual(tw))
+  
+  def testSetXSciNotation(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    _, psID = PlotController.AddCurve(x*1.0e6, y*1.0e6, curve_label="titi", append=False)
+    PlotController.SetXSciNotation(psID, True)
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testSetYSciNotation(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    _, psID = PlotController.AddCurve(x*1.0e6, y*1.0e6, curve_label="titi", append=False)
+    PlotController.SetYSciNotation(psID, True)
+    self.assertTrue(self.areScreenshotEqual(tw))
+  
+  def testRegisterCallback(self):
+    global a_callb
+    a_callb = 0
+    def fun(crv_id):
+      global a_callb
+      a_callb = crv_id
+    self.showTabWidget()
+    x, y = self.generateExp()
+    crvId, _ = PlotController.AddCurve(x, y)
+    PlotController.RegisterCallback(fun)
+    PlotController.SetCurrentCurve(crvId)
+    self.assertEqual(crvId, a_callb)
+
+  def testDeleteCallback(self):
+    global a_callb
+    a_callb = 0
+    def fun(crv_id):
+      global a_callb
+      a_callb = crv_id
+    self.showTabWidget()
+    x, y = self.generateExp()
+    crvId, _ = PlotController.AddCurve(x, y)
+    PlotController.RegisterCallback(fun)
+    PlotController.ClearCallbacks()
+    PlotController.SetCurrentCurve(crvId)
+    _, _ = PlotController.AddCurve(x, y)
+    self.assertEqual(crvId, a_callb)
+    
+  def testAddCurveEmptyPs(self):
+    """ Adding a curve when no ps was active was buggy """
+    self.showTabWidget()
+    x, y = self.generateSine()
+    PlotController.AddPlotSet("toto")
+    # No current plot set:
+    PlotController.SetCurrentPlotSet(-1)
+    # Should create a new plot set:
+    PlotController.AddCurve(x, y, append=True)
+    l, _ = PlotController.GetAllPlotSets()
+    self.assertEqual(2, len(l))
+    
+  def test_onCurrentCurveChange(self):
+    self.showTabWidget()
+    x, y = self.generateSine()
+    crvID, _ = PlotController.AddCurve(x, y)
+    PlotController.SetCurrentCurve(crvID)
+    PlotController.DeleteCurve(crvID)
+    crvID2, _ = PlotController.AddCurve(x, y)
+    # was throwing:
+    PlotController.SetCurrentCurve(crvID2)
+
+  def testSetLegendVisible(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    _, psID = PlotController.AddCurve(x, y, curve_label="titi", append=False)
+    PlotController.SetLegendVisible(psID, False)  # by default legend is always visible in the tests
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testLockRepaint(self):
+    tw = self.showTabWidget()
+    x, y = self.generateSine()
+    PlotController.LockRepaint()
+    for i in range(10):
+      _, psID = PlotController.AddCurve(x, y*float(i+1), append=True)
+    PlotController.UnlockRepaint()
+    self.assertTrue(self.areScreenshotEqual(tw))
+
+  def testDelPlotSetSelectPrev(self):
+    """ When deleting a full plot set, the previous plot set should become active """
+    self.showTabWidget()
+    x, y = self.generateSine()
+    _, psID0 = PlotController.AddCurve(x, y, append=True)  # creates a new plot set
+    _, psID1 = PlotController.AddCurve(x, y, append=False)  # creates a new plot set
+    PlotController.DeletePlotSet(psID1)
+    PlotController.AddCurve(x, y, append=True)  # should NOT create a new plot set
+    psID2 = PlotController.GetCurrentPlotSetID()
+    self.assertEqual(psID0, psID2)
+    l, _ = PlotController.GetAllPlotSets()
+    self.assertEqual(1, len(l))
+    
+# Even if not in main:
+processDecorator(__name__)
+
+if __name__ == "__main__":
+  import unittest    
+  unittest.main()
diff --git a/tools/CurvePlot/src/python/ui/CMakeLists.txt b/tools/CurvePlot/src/python/ui/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a52511c
--- /dev/null
@@ -0,0 +1,40 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+# --- resources ---
+
+# uic files 
+SET(_pyuic_files
+  CurveTreeDockWidget.ui
+  PlotWidget.ui
+  PlotSettings.ui
+)
+
+# --- scripts ---
+SET(_all_lib_SCRIPTS
+    CurveTreeDockWidget.py
+    PlotWidget.py   
+    PlotSettings.py   
+)
+
+# --- rules ---
+SALOME_INSTALL_SCRIPTS("${_pyuic_SCRIPTS}" ${SALOME_CURVEPLOT_INSTALL_PYTHON})
+SALOME_INSTALL_SCRIPTS("${_all_lib_SCRIPTS}" ${SALOME_CURVEPLOT_INSTALL_PYTHON})
+
+INSTALL(FILES ${_pyuic_files} DESTINATION ${SALOME_CURVEPLOT_INSTALL_PYTHON})
diff --git a/tools/CurvePlot/src/python/ui/CurveTreeDockWidget.py b/tools/CurvePlot/src/python/ui/CurveTreeDockWidget.py
new file mode 100644 (file)
index 0000000..bda0f5d
--- /dev/null
@@ -0,0 +1,18 @@
+from pyqtside import QtGui, QtCore
+from pyqtside.uic import loadUiGen
+from utils import completeResPath
+
+class CurveTreeDockWidget(QtGui.QDockWidget):
+  def __init__(self):
+    QtGui.QDockWidget.__init__(self)
+    loadUiGen(completeResPath("CurveTreeDockWidget.ui"), self)
+    self.treeWidget.setHeaderLabel ("Plots")
+    self.treeWidget.sortByColumn(0, QtCore.Qt.AscendingOrder)
+    self.treeWidget.setSortingEnabled(True);
+    self.treeWidget.setColumnHidden(1, True);
+            
+  def getTreeWidget(self):
+      """
+      :returns: QTreeWidget -- the (curve) browser
+      """
+      return self.treeWidget
diff --git a/tools/CurvePlot/src/python/ui/CurveTreeDockWidget.ui b/tools/CurvePlot/src/python/ui/CurveTreeDockWidget.ui
new file mode 100644 (file)
index 0000000..b13925a
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CurveTreeDockWidget</class>
+ <widget class="QDockWidget" name="CurveTreeDockWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>342</width>
+    <height>471</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Plot browser</string>
+  </property>
+  <widget class="QWidget" name="container">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QTreeWidget" name="treeWidget">
+      <property name="sortingEnabled">
+       <bool>true</bool>
+      </property>
+      <property name="columnCount">
+       <number>2</number>
+      </property>
+      <column>
+       <property name="text">
+        <string notr="true">1</string>
+       </property>
+      </column>
+      <column>
+       <property name="text">
+        <string notr="true">2</string>
+       </property>
+      </column>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tools/CurvePlot/src/python/ui/PlotSettings.py b/tools/CurvePlot/src/python/ui/PlotSettings.py
new file mode 100644 (file)
index 0000000..bd6aac2
--- /dev/null
@@ -0,0 +1,89 @@
+from pyqtside import QtGui, QtCore
+from pyqtside.uic import loadUiGen
+from utils import completeResPath
+
+class PlotSettings(QtGui.QDialog):
+  def __init__(self):
+    QtGui.QDialog.__init__(self)
+    loadUiGen(completeResPath("PlotSettings.ui"), self)
+    self.initialize()
+
+  def initialize(self):
+    self.legendPositionComboBox.addItem("Bottom")
+    self.legendPositionComboBox.addItem("Right")
+    self._r = 0
+    self._g = 0
+    self._b = 1
+   
+  @QtCore.Slot(int)
+  def onShowLegend(self, index):
+    if index > 0 :
+      self.legendPositionComboBox.setEnabled(True)
+    else :
+      self.legendPositionComboBox.setEnabled(False)
+  
+  @QtCore.Slot() 
+  def onChangeColor(self):
+    col = QtGui.QColorDialog.getColor()
+
+    if col.isValid():
+      r, g, b = [c/255.0 for c in col.getRgb()[:3]]
+      self.setRGB(r, g, b)
+      
+  def setSelectedCurveName(self, name):
+    if name :
+      self.selectedCurvePanel.setTitle("Selected curve : " + name)
+      self.selectedCurvePanel.show()
+    else :
+      self.selectedCurvePanel.hide()
+   
+  def setRGB(self, r, g, b):
+    self._r = r
+    self._g = g
+    self._b = b
+    self.colorCurve.setIcon(QtGui.QIcon(self.drawColorPixmap(int(r*255), int(g*255), int(b*255))))
+   
+  def getRGB(self):
+    return self._r, self._g, self._b
+   
+  def drawColorPixmap(self, r, g, b):
+    pix = QtGui.QPixmap( 16, 16 )
+    color = QtGui.QColor(r, g, b)
+    pix.fill(color)
+    return pix
+
+  def accept(self):
+    xminText = unicode(self.axisXMinEdit.text())
+    xmaxText = unicode(self.axisXMaxEdit.text())
+    yminText = unicode(self.axisYMinEdit.text())
+    ymaxText = unicode(self.axisYMaxEdit.text())
+    if (yminText == "" or ymaxText == "") :
+      QtGui.QMessageBox.critical(self, "Plot settings", "A field \"YMin\" or \"YMax\" is empty")
+    else :
+      try:
+        xmin = float(xminText)
+      except ValueError:
+        QtGui.QMessageBox.critical(self, "Plot settings", "It is not possible to convert XMin")
+      try:
+        xmax = float(xmaxText)
+      except ValueError:
+        QtGui.QMessageBox.critical(self, "Plot settings", "It is not possible to convert XMax")
+      try:
+        ymin = float(yminText)
+      except ValueError:
+        QtGui.QMessageBox.critical(self, "Plot settings", "It is not possible to convert YMin")
+      try:
+        ymax = float(ymaxText)
+      except ValueError:
+        QtGui.QMessageBox.critical(self, "Plot settings", "It is not possible to convert YMax")
+      if ((xmax-xmin) == 0) :
+        QtGui.QMessageBox.critical(self, "Plot settings", "XMax is is equal to XMin.")
+        return
+      if ((ymax-ymin) == 0) :
+        QtGui.QMessageBox.critical(self, "Plot settings", "YMax is is equal to YMin.")
+        return
+      if ((xmax-xmin) < 0) :
+        QtGui.QMessageBox.warning(self, "Plot settings", "XMax is less than XMin.")
+      if ((ymax-ymin) < 0) :
+        QtGui.QMessageBox.warning(self, "Plot settings", "YMax is less than YMin.")
+      super(PlotSettings, self).accept()
diff --git a/tools/CurvePlot/src/python/ui/PlotSettings.ui b/tools/CurvePlot/src/python/ui/PlotSettings.ui
new file mode 100644 (file)
index 0000000..687e47a
--- /dev/null
@@ -0,0 +1,548 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PlotSettings</class>
+ <widget class="QDialog" name="PlotSettings">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>688</width>
+    <height>583</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>CurvePlot settings</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <item>
+        <widget class="QLabel" name="titleLabel">
+         <property name="text">
+          <string>Main title :</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLineEdit" name="titleEdit"/>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="gridPanel">
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>50</height>
+        </size>
+       </property>
+       <property name="title">
+        <string>Grid</string>
+       </property>
+       <property name="flat">
+        <bool>false</bool>
+       </property>
+       <widget class="QCheckBox" name="gridCheckBox">
+        <property name="geometry">
+         <rect>
+          <x>10</x>
+          <y>20</y>
+          <width>771</width>
+          <height>21</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Show grid</string>
+        </property>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="legendPanel">
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>50</height>
+        </size>
+       </property>
+       <property name="title">
+        <string>Legend</string>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout_5">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <item>
+           <widget class="QCheckBox" name="showLegendCheckBox">
+            <property name="text">
+             <string>Show legend</string>
+            </property>
+            <property name="tristate">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <spacer name="horizontalSpacer">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeType">
+             <enum>QSizePolicy::Fixed</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item>
+           <widget class="QLabel" name="legendPositionLabel">
+            <property name="text">
+             <string>Position :</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QComboBox" name="legendPositionComboBox">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="selectedCurvePanel">
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>100</height>
+        </size>
+       </property>
+       <property name="title">
+        <string>Selected curve</string>
+       </property>
+       <widget class="QWidget" name="layoutWidget">
+        <property name="geometry">
+         <rect>
+          <x>11</x>
+          <y>35</y>
+          <width>291</width>
+          <height>57</height>
+         </rect>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout_6">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_6">
+           <item>
+            <widget class="QLabel" name="nameCurveLabel">
+             <property name="text">
+              <string>Name :</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLineEdit" name="nameCurve">
+             <property name="palette">
+              <palette>
+               <active>
+                <colorrole role="WindowText">
+                 <brush brushstyle="SolidPattern">
+                  <color alpha="255">
+                   <red>145</red>
+                   <green>145</green>
+                   <blue>145</blue>
+                  </color>
+                 </brush>
+                </colorrole>
+                <colorrole role="Text">
+                 <brush brushstyle="SolidPattern">
+                  <color alpha="255">
+                   <red>105</red>
+                   <green>105</green>
+                   <blue>105</blue>
+                  </color>
+                 </brush>
+                </colorrole>
+               </active>
+               <inactive>
+                <colorrole role="WindowText">
+                 <brush brushstyle="SolidPattern">
+                  <color alpha="255">
+                   <red>145</red>
+                   <green>145</green>
+                   <blue>145</blue>
+                  </color>
+                 </brush>
+                </colorrole>
+                <colorrole role="Text">
+                 <brush brushstyle="SolidPattern">
+                  <color alpha="255">
+                   <red>105</red>
+                   <green>105</green>
+                   <blue>105</blue>
+                  </color>
+                 </brush>
+                </colorrole>
+               </inactive>
+               <disabled>
+                <colorrole role="WindowText">
+                 <brush brushstyle="SolidPattern">
+                  <color alpha="255">
+                   <red>149</red>
+                   <green>151</green>
+                   <blue>153</blue>
+                  </color>
+                 </brush>
+                </colorrole>
+                <colorrole role="Text">
+                 <brush brushstyle="SolidPattern">
+                  <color alpha="255">
+                   <red>158</red>
+                   <green>158</green>
+                   <blue>158</blue>
+                  </color>
+                 </brush>
+                </colorrole>
+               </disabled>
+              </palette>
+             </property>
+             <property name="readOnly">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_5">
+           <item>
+            <widget class="QLabel" name="colorCurveLabel">
+             <property name="text">
+              <string>Color</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QPushButton" name="colorCurve">
+             <property name="maximumSize">
+              <size>
+               <width>40</width>
+               <height>16777215</height>
+              </size>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_3">
+             <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="markerCurveLabel">
+             <property name="minimumSize">
+              <size>
+               <width>25</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>48</width>
+               <height>16777215</height>
+              </size>
+             </property>
+             <property name="text">
+              <string>Marker</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="markerCurve">
+             <property name="maximumSize">
+              <size>
+               <width>50</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <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>
+          </layout>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTabWidget" name="tabWidget">
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>50</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>16777215</width>
+         <height>423</height>
+        </size>
+       </property>
+       <property name="currentIndex">
+        <number>0</number>
+       </property>
+       <widget class="QWidget" name="axisXtabWidget">
+        <attribute name="title">
+         <string>Axis X</string>
+        </attribute>
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <item>
+          <layout class="QGridLayout" name="gridLayout_2">
+           <item row="0" column="0">
+            <widget class="QLabel" name="axisXTitleLabel">
+             <property name="text">
+              <string>Title :</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLineEdit" name="axisXTitleEdit"/>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="axisXMinLabel">
+             <property name="text">
+              <string>XMin :</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLineEdit" name="axisXMinEdit"/>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="axisXMaxLabel">
+             <property name="text">
+              <string>XMax :</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLineEdit" name="axisXMaxEdit"/>
+           </item>
+           <item row="3" column="0" colspan="2">
+            <widget class="QCheckBox" name="axisXSciCheckBox">
+             <property name="text">
+              <string>Scientific notation</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <spacer name="verticalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>65</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="axisYTabWidget">
+        <attribute name="title">
+         <string>Axis Y</string>
+        </attribute>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <item>
+          <layout class="QGridLayout" name="gridLayout">
+           <item row="0" column="0">
+            <widget class="QLabel" name="axisYTitleLabel">
+             <property name="text">
+              <string>Title :</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLineEdit" name="axisYTitleEdit"/>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="axisYMinLabel">
+             <property name="text">
+              <string>YMin :</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLineEdit" name="axisYMinEdit"/>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="axisYMaxLabel">
+             <property name="text">
+              <string>YMax :</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLineEdit" name="axisYMaxEdit"/>
+           </item>
+           <item row="3" column="0" colspan="2">
+            <widget class="QCheckBox" name="axisYSciCheckBox">
+             <property name="text">
+              <string>Scientific notation</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </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>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+       </property>
+       <property name="centerButtons">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PlotSettings</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>321</x>
+     <y>577</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PlotSettings</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>253</x>
+     <y>577</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>showLegendCheckBox</sender>
+   <signal>stateChanged(int)</signal>
+   <receiver>PlotSettings</receiver>
+   <slot>onShowLegend(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>56</x>
+     <y>130</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>198</x>
+     <y>161</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>colorCurve</sender>
+   <signal>clicked()</signal>
+   <receiver>PlotSettings</receiver>
+   <slot>onChangeColor()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>74</x>
+     <y>232</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>343</x>
+     <y>291</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>onShowLegend(int)</slot>
+  <slot>onChangeColor()</slot>
+ </slots>
+</ui>
diff --git a/tools/CurvePlot/src/python/ui/PlotWidget.py b/tools/CurvePlot/src/python/ui/PlotWidget.py
new file mode 100644 (file)
index 0000000..677c549
--- /dev/null
@@ -0,0 +1,16 @@
+from pyqtside import QtGui
+from pyqtside.uic import loadUiGen
+from utils import completeResPath
+
+class PlotWidget(QtGui.QMainWindow):
+  def __init__(self):
+    QtGui.QMainWindow.__init__(self)
+    loadUiGen(completeResPath("PlotWidget.ui"), self)
+    
+  def clearAll(self):
+    """ In test context, the PlotWidget is never fully deleted (because the PyQt binding
+    of QTabWidget doesn't remove completly the references it holds).
+    So clean up manually. 
+    """
+    self.toolBar = None
+    self.setCentralWidget(None)
diff --git a/tools/CurvePlot/src/python/ui/PlotWidget.ui b/tools/CurvePlot/src/python/ui/PlotWidget.ui
new file mode 100644 (file)
index 0000000..d4bbfe1
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PlotWidget</class>
+ <widget class="QMainWindow" name="PlotWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>598</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralWidget"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tools/CurvePlot/src/python/views/CMakeLists.txt b/tools/CurvePlot/src/python/views/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7186c19
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (C) 2012-2014  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(_all_lib_SCRIPTS
+    View.py
+    CurveBrowserView.py
+    CurveTabsView.py
+    CurveView.py
+    XYView.py
+)
+    
+SALOME_INSTALL_SCRIPTS("${_all_lib_SCRIPTS}" ${SALOME_CURVEPLOT_INSTALL_PYTHON})
diff --git a/tools/CurvePlot/src/python/views/CurveBrowserView.py b/tools/CurvePlot/src/python/views/CurveBrowserView.py
new file mode 100644 (file)
index 0000000..ad2facd
--- /dev/null
@@ -0,0 +1,104 @@
+from pyqtside import QtGui
+from pyqtside.QtGui import QMenu
+from pyqtside.QtCore import Qt
+
+from View import View
+from CurveTreeDockWidget import CurveTreeDockWidget
+from utils import Logger
+
+class CurveBrowserView( View, CurveTreeDockWidget) :
+
+    def __init__( self, controller) :
+        """ Constructor """
+        View.__init__( self, controller)
+        CurveTreeDockWidget.__init__(self)
+        self._noUpdate = False
+        
+        treeWidget = self.getTreeWidget()
+        treeWidget.itemSelectionChanged.connect(self.onItemSelectionChanged)
+        treeWidget.itemDoubleClicked.connect(self.onItemDoubleCliked)
+        treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
+        treeWidget.customContextMenuRequested.connect(self.openMenu)
+            
+    def update(self):
+        """ Update this view due to model change """
+        if self._model == None or self._noUpdate:
+            return
+          
+        # widget clear and repopulation will trigger onItemSelectionChanged(),
+        # which in turns triggers update() (via setCurrentCurve()). So avoid recursion:
+        self._noUpdate = True
+        
+        treeWidget = self.getTreeWidget()
+        treeWidget.clear()
+        
+        plotSets = self._model._plotSets
+            
+        # The second (hidden) column in the tree bares the ID of the object and its nature (plotset or curve)
+        for p in plotSets.values():
+          item = QtGui.QTreeWidgetItem([unicode(p.getTitle()), unicode(p.getID()) + '_set'])
+          treeWidget.addTopLevelItem(item)
+          for c in p._curves.values():
+            chld = QtGui.QTreeWidgetItem([unicode(c.getTitle()), unicode(c.getID()) + '_crv'])
+            item.addChild(chld)
+          
+        treeWidget.expandAll()
+        
+        # Finally select the proper item in the tree:
+        cps = self._model.getCurrentPlotSet()
+        if not cps is None:
+          ccv = cps.getCurrentCurve()
+          if ccv is None:
+            key = str(cps.getID()) + '_set'
+          else:
+            key = str(ccv.getID()) + '_crv'
+          listItems = treeWidget.findItems(key, Qt.MatchExactly | Qt.MatchRecursive,1)
+          if len(listItems) > 0:
+            treeWidget.setCurrentItem(listItems[0])
+            
+        self._noUpdate = False
+    
+    def onItemSelectionChanged(self):
+        """
+        Change the current selected XYplot/curve        
+        """
+        if self._noUpdate:
+          return
+        
+        # setCurrentCurve() and setCurrentPlotSet() will trigger update(),
+        # which in turns triggers onItemSelectionChanged(). So avoid recursion:
+        self._noUpdate = True
+        
+        pm = self._controller._plotManager
+        treeWidget = self.getTreeWidget()
+        it = treeWidget.currentItem()
+        if not it is None:
+          objStr = str(it.text(1))   # no unicode here!
+          objID = int(objStr[0:-4])
+          objTyp = objStr[-3:]
+          if objTyp == 'crv':
+            # Find correct plot set:
+            cps = pm.getPlotSetContainingCurve(objID)
+            if not cps is None:
+              cps.setCurrentCurve(objID)
+              pm.setCurrentPlotSet(cps.getID())
+          elif objTyp == 'set':
+            pm.clearAllCurrentCurve()
+            pm.setCurrentPlotSet(objID)
+          else:
+            raise Exception("Internal error - should not happen")
+        else:
+          ps = pm.getCurrentPlotSet()
+          if not ps is None:
+            ps.setCurrentCurve(-1)
+          pm.setCurrentPlotSet(-1)
+          
+        self._noUpdate = False
+    
+    def onItemDoubleCliked(self):
+        Logger.Debug("item doubled clicked")
+    
+    def openMenu(self, position):
+        menu = self._controller._browserContextualMenu
+        treeWidget = self.getTreeWidget()
+        menu.exec_(treeWidget.mapToGlobal(position))
diff --git a/tools/CurvePlot/src/python/views/CurveTabsView.py b/tools/CurvePlot/src/python/views/CurveTabsView.py
new file mode 100644 (file)
index 0000000..3454837
--- /dev/null
@@ -0,0 +1,76 @@
+from View import View
+from XYView import XYView
+from utils import Logger
+
+class CurveTabsView(View):
+  def __init__(self, controller):
+    View.__init__(self, controller)
+    self._XYViews = {}  # key: SALOME view ID, value: XYView
+      
+  def closeXYView(self, salomeViewID):
+    Logger.Debug("CurveTabsView::closeXYView: %d" % salomeViewID)
+    self._controller.setCurvePlotRequestingClose(True)
+    self._controller._sgPyQt.closeView(salomeViewID)
+    self._controller.setCurvePlotRequestingClose(False)
+    # Destroy the view
+    self._XYViews.pop(salomeViewID)
+    Logger.Debug("CurveTabsView::closeXYView count %d" % len(self._XYViews))
+  
+  def createXYView(self, model):
+    v = XYView(self._controller)
+    self._controller.associate(model, v)
+    return v
+  
+  def onCurrentPlotSetChange(self):
+    """ Avoid a unnecessary call to update() when just switching current plot set! """
+    cps = self._model.getCurrentPlotSet()
+    if not cps is None:
+      mp = self.mapModId2ViewId()
+      salomeViewID = mp[cps.getID()]
+      self._controller._sgPyQt.activateView(salomeViewID)
+  
+  def mapModId2ViewId(self):
+    """ Gives a map from model ID (the model behind the XYView) to view ID
+    """
+    lst = [(v._model.getID(), view_id) for view_id, v in self._XYViews.items()]
+    return dict(lst)
+  
+  def update(self):
+    """
+    Updates the list of tabs shown in the GUI
+    """
+    if self._model is None:
+        return
+    
+    # Check list of tabs:
+    set_mod = set(self._model._plotSets.keys())
+    set_view = set([ v._model.getID() for v in self._XYViews.values() ])
+    mp = self.mapModId2ViewId()
+    
+    # Deleted/Added curves:
+    dels = set_view - set_mod
+    added = set_mod - set_view
+    
+    for d in dels:
+      salomeViewID = mp[d]
+      v = self._XYViews[salomeViewID]
+      v.cleanBeforeClose()
+      self.closeXYView(salomeViewID)
+    
+    newViews = []
+    for a in added:
+      newViews.append(self.createXYView(self._model._plotSets[a]))
+    
+    # Now update all tabs individually (this will trigger creation of new ones if not already there):
+    for v in self._XYViews.values() + newViews:
+      # The update on newViews will trigger the SALOME view creation:
+      v.update() 
+    
+    # And complete internal structure for new views
+    # This is not done in 
+    for v in newViews:
+      self._XYViews[v._salomeViewID] = v
+      
+    # Finally activate the proper tab:
+    self.onCurrentPlotSetChange()
+    
\ No newline at end of file
diff --git a/tools/CurvePlot/src/python/views/CurveView.py b/tools/CurvePlot/src/python/views/CurveView.py
new file mode 100644 (file)
index 0000000..39ae897
--- /dev/null
@@ -0,0 +1,92 @@
+from View import View
+from utils import Logger
+
+class CurveView(View):
+  _PICKER_PRECISION = 20  #pts
+    
+  def __init__(self, controller, parentXYView):
+    View.__init__(self, controller)
+    self._mplAxes = None
+    self._mplLines = None
+    self._isHighlighted = False
+    self._initialLineWidth = None
+    self._parentXYView = parentXYView
+    
+    self._marker = None
+    self._color = None
+    self._lineStyle = None
+    
+  def setMPLAxes(self, axes):
+    self._mplAxes = axes
+        
+  def erase(self):
+    self._mplAxes.lines.remove(self._mplLines[0])
+    self._mplLines = None
+    
+  def draw(self):
+    m = self._model
+    x_idx, y_idx = m.getXAxisIndex(), m.getYAxisIndex()
+    d = self._model.getTable().getData()
+    self._mplLines = self._mplAxes.plot(d[:, x_idx], d[:, y_idx], label=m._title, 
+                                        picker=self._PICKER_PRECISION)
+    self._initialLineWidth = self._mplLines[0].get_linewidth()
+  
+  def onCurveTitleChange(self):
+    if self._mplLines is None:
+      return
+    self._mplLines[0].set_label(self._model._title)
+  
+  def update(self):
+    Logger.Debug("CurveView::udpate")
+    if self._mplLines is None:
+      return
+    lineStyle, marker, color = self.getLineStyle(), self.getMarker(), self.getColor()
+    self.erase()
+    self.draw()
+    # Reset correctly color, marker and highlight state
+    self.setLineStyle(lineStyle)
+    self.setMarker(marker)
+    self.setColor(color)
+    self.toggleHighlight(self._isHighlighted, force=True)
+    
+  def setLineStyle(self, lin_style):
+    lin = self._mplLines[0] 
+    lin.set_linestyle(lin_style)
+    
+  def getLineStyle(self):
+    if self._mplLines is None:
+      return None
+    return self._mplLines[0].get_linestyle()
+    
+  def setMarker(self, marker):
+    lin = self._mplLines[0]
+    lin.set_marker(marker)
+
+  def getMarker(self):
+    if self._mplLines is None:
+      return None
+    return self._mplLines[0].get_marker()
+
+  def toggleHighlight(self, highlight, force=False):
+    lin = self._mplLines[0]
+    if highlight and (force or not self._isHighlighted):
+      lin.set_linewidth(2*self._initialLineWidth)
+      self._isHighlighted = True
+    elif not highlight and (force or self._isHighlighted):
+      lin.set_linewidth(self._initialLineWidth)
+      self._isHighlighted = False
+    else:
+      # Nothing to do, already the correct state
+      return
+    
+  def isHighlighted(self):
+    return self._isHighlighted
+    
+  def setColor(self, rgb_color):
+    lin = self._mplLines[0]
+    lin.set_color(rgb_color)
+    
+  def getColor(self):
+    if self._mplLines is None:
+      return None
+    return self._mplLines[0].get_color()
diff --git a/tools/CurvePlot/src/python/views/View.py b/tools/CurvePlot/src/python/views/View.py
new file mode 100644 (file)
index 0000000..c2afeb7
--- /dev/null
@@ -0,0 +1,53 @@
+class View(object) :
+
+    def __init__( self, controller, model=None ) :
+        """Constructor"""
+        
+        self._model = model
+        self._controller = controller
+        pass
+
+    def getModel( self ) :
+        """
+        :returns: Model -- The view's model. 
+        """
+        return self._model
+
+    def setModel( self, model ) :
+        """
+        Associates a model to the view.
+        
+        :param model: Model -- The model to be associated.
+        
+        """
+        self._model = model
+        pass
+    
+    def getController( self ) :
+        """
+        :returns: Controller -- The controller of the view.
+        """
+        return self._controller
+    
+    def setController( self, controller ) :
+        """
+        Associates a controller to the view.
+        
+        :param controller: Controller -- The controller to be associated.
+        
+        """
+        self._controller = controller
+        pass
+    
+    def update( self) :
+        """
+        Updates the view contents.
+        
+        .. note::
+        
+           Virtual Method.
+           
+        """
+        raise NotImplementedError
+
+pass
diff --git a/tools/CurvePlot/src/python/views/XYView.py b/tools/CurvePlot/src/python/views/XYView.py
new file mode 100644 (file)
index 0000000..e92b7cf
--- /dev/null
@@ -0,0 +1,702 @@
+import matplotlib.pyplot as plt
+import matplotlib.colors as colors
+from View import View
+from CurveView import CurveView
+
+from utils import Logger, trQ
+from PlotWidget import PlotWidget
+from PlotSettings import PlotSettings
+from pyqtside import QtGui, QtCore
+from pyqtside.QtCore import QObject
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg, NavigationToolbar2QT
+
+class EventHandler(QObject):
+  """ Handle the right-click properly so that it only triggers the contextual menu """
+  def __init__(self,parent=None):
+    QObject.__init__(self, parent)
+  
+  def eventFilter(self, obj, event):
+    if event.type() == QtCore.QEvent.MouseButtonPress:
+      if event.button() == 2:
+        # Discarding right button press to only keep context menu display
+        return True # Event handled (and hence not passed to matplotlib)
+    return QObject.eventFilter(self, obj, event)
+
+class XYView(View):
+  AUTOFIT_MARGIN = 0.03  # 3%
+  
+  # See http://matplotlib.org/api/markers_api.html:
+  CURVE_MARKERS = [ "o" ,#  circle
+                    "*",  # star
+                    "+",  # plus
+                    "x",  # x
+                    "s",  # square
+                    "p",  # pentagon
+                    "h",  # hexagon1
+                    "8",  # octagon
+                    "D",  # diamond
+                    "^",  # triangle_up
+                    "<",  # triangle_left
+                    ">",  # triangle_right
+                    "1",  # tri_down
+                    "2",  # tri_up
+                    "3",  # tri_left
+                    "4",  # tri_right
+                    "v",  # triangle_down
+                    "H",  # hexagon2
+                    "d",  # thin diamond
+                    "",   # NO MARKER
+                   ]
+  
+  _DEFAULT_LEGEND_STATE = False   # for test purposes mainly - initial status of the legend
+  
+  def __init__(self, controller):
+    View.__init__(self, controller)
+    self._eventHandler = EventHandler()
+    
+    self._curveViews = {}    # key: curve (model) ID, value: CurveView
+    self._salomeViewID = None
+    self._mplFigure = None
+    self._mplAxes = None
+    self._mplCanvas = None
+    self._plotWidget = None
+    self._sgPyQt = self._controller._sgPyQt
+    self._toolbar = None
+    self._mplNavigationActions = {}
+    self._toobarMPL = None
+    self._grid = None
+    self._currCrv = None   # current curve selected in the view
+    
+    self._legend = None
+    self._legendLoc = "right"  # "right" or "bottom"
+    
+    self._fitArea = False
+    self._zoomPan = False
+    self._dragOnDrop = False
+    self._move = False
+    
+    self._patch = None
+    self._xdata = None
+    self._ydata = None
+    self._defaultLineStyle = None
+    self._last_point = None
+    self._lastMarkerID = -1
+    self._blockLogSignal = False
+    
+    self._axisXSciNotation = False
+    self._axisYSciNotation = False
+    self._prevTitle = None
+    
+  def __repaintOK(self):
+    """ To be called inside XYView each time a low-level expansive matplotlib methods is to be invoked.
+    @return False if painting is currently locked, in which case it will also register the current XYView 
+    as needing a refresh when unlocked
+    """
+    ret = self._controller._plotManager.isRepaintLocked()
+    if ret:
+      self._controller._plotManager.registerRepaint(self._model)
+    return (not ret)
+    
+  def appendCurve(self, curveID):
+    newC = CurveView(self._controller, self)
+    newC.setModel(self._model._curves[curveID])
+    newC.setMPLAxes(self._mplAxes) 
+    newC.draw()
+    newC.setMarker(self.getMarker(go_next=True))
+    self._curveViews[curveID] = newC 
+    
+  def removeCurve(self, curveID):
+    v = self._curveViews.pop(curveID)
+    v.erase()
+    if self._currCrv is not None and self._currCrv.getID() == curveID:
+      self._currCrv = None
+  
+  def cleanBeforeClose(self):
+    """ Clean some items to avoid accumulating stuff in memory """
+    self._mplFigure.clear()
+    plt.close(self._mplFigure)
+    self._plotWidget.clearAll()
+    # For memory debugging only:
+    import gc
+    gc.collect()
+  
+  def repaint(self):
+    if self.__repaintOK():
+      Logger.Debug("XYView::draw")
+      self._mplCanvas.draw()
+
+  def onXLabelChange(self):
+    if self.__repaintOK():
+      self._mplAxes.set_xlabel(self._model._xlabel)
+      self.repaint()
+    
+  def onYLabelChange(self):
+    if self.__repaintOK():
+      self._mplAxes.set_ylabel(self._model._ylabel)
+      self.repaint()
+  
+  def onTitleChange(self):
+    if self.__repaintOK():
+      self._mplAxes.set_title(self._model._title)
+      self.updateViewTitle()
+      self.repaint()
+  
+  def onCurveTitleChange(self):
+    # Updating the legend should suffice
+    self.showHideLegend()
+  
+  def onClearAll(self):
+    """ Just does an update with a reset of the marker cycle. """
+    if self.__repaintOK():
+      self._lastMarkerID = -1
+      self.update()
+  
+  def onPick(self, event):
+    """ MPL callback when picking
+    """
+    if event.mouseevent.button == 1:
+      selected_id = -1
+      a = event.artist
+      for crv_id, cv in self._curveViews.items():
+        if cv._mplLines[0] is a:
+          selected_id = crv_id
+      # Use the plotmanager so that other plot sets get their current reset:
+      self._controller._plotManager.setCurrentCurve(selected_id)
+  
+  def createAndAddLocalAction(self, icon_file, short_name):
+    return self._toolbar.addAction(self._sgPyQt.loadIcon("CURVEPLOT", icon_file), short_name)
+    
+  def createPlotWidget(self):
+    self._mplFigure = Figure((8.0,5.0), dpi=100)
+    self._mplCanvas = FigureCanvasQTAgg(self._mplFigure)
+    self._mplCanvas.installEventFilter(self._eventHandler)
+    self._mplCanvas.mpl_connect('pick_event', self.onPick)
+    self._mplAxes = self._mplFigure.add_subplot(1, 1, 1)
+    self._plotWidget = PlotWidget()
+    self._toobarMPL = NavigationToolbar2QT(self._mplCanvas, None) 
+    for act in self._toobarMPL.actions():
+      actionName = str(act.text()).strip()
+      self._mplNavigationActions[actionName] = act
+    self._plotWidget.setCentralWidget(self._mplCanvas)
+    self._toolbar = self._plotWidget.toolBar
+    self.populateToolbar()
+     
+    self._popupMenu = QtGui.QMenu()
+    self._popupMenu.addAction(self._actionLegend)
+    
+    # Connect evenement for the graphic scene
+    self._mplCanvas.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+    self._mplCanvas.customContextMenuRequested.connect(self.onContextMenu) 
+    self._mplCanvas.mpl_connect('scroll_event', self.onScroll)
+    self._mplCanvas.mpl_connect('button_press_event', self.onMousePress)
+  
+  def populateToolbar(self):
+    # Action to dump view in a file
+    a = self.createAndAddLocalAction("dump_view.png", trQ("DUMP_VIEW_TXT"))
+    a.triggered.connect(self.dumpView)
+    self._toolbar.addSeparator()
+    # Actions to manipulate the scene
+    a = self.createAndAddLocalAction("fit_all.png", trQ("FIT_ALL_TXT"))
+    a.triggered.connect(self.autoFit)
+    #    Zoom and pan are mutually exclusive but can be both de-activated: 
+    self._zoomAction = self.createAndAddLocalAction("fit_area.png", trQ("FIT_AREA_TXT"))
+    self._zoomAction.triggered.connect(self.zoomArea)
+    self._zoomAction.setCheckable(True)
+    self._panAction = self.createAndAddLocalAction("zoom_pan.png", trQ("ZOOM_PAN_TXT"))
+    self._panAction.triggered.connect(self.pan)
+    self._panAction.setCheckable(True)
+    self._toolbar.addSeparator()
+    # Actions to change the representation of curves
+    self._curveActionGroup = QtGui.QActionGroup(self._plotWidget)
+    self._pointsAction = self.createAndAddLocalAction("draw_points.png", trQ("DRAW_POINTS_TXT"))
+    self._pointsAction.setCheckable(True)
+    self._linesAction = self.createAndAddLocalAction("draw_lines.png", trQ("DRAW_LINES_TXT"))
+    self._linesAction.setCheckable(True)
+    self._curveActionGroup.addAction(self._pointsAction)
+    self._curveActionGroup.addAction(self._linesAction)
+    self._linesAction.setChecked(True)
+    self._curveActionGroup.triggered.connect(self.changeModeCurve)
+    self._curveActionGroup.setExclusive(True)
+    self._toolbar.addSeparator()
+    # Actions to draw horizontal curves as linear or logarithmic
+    self._horActionGroup = QtGui.QActionGroup(self._plotWidget)
+    self._horLinearAction = self.createAndAddLocalAction("hor_linear.png", trQ("HOR_LINEAR_TXT"))
+    self._horLinearAction.setCheckable(True)
+    self._horLogarithmicAction = self.createAndAddLocalAction("hor_logarithmic.png", trQ("HOR_LOGARITHMIC_TXT"))
+    self._horLogarithmicAction.setCheckable(True)
+    self._horActionGroup.addAction(self._horLinearAction)
+    self._horActionGroup.addAction(self._horLogarithmicAction)
+    self._horLinearAction.setChecked(True)
+    self._horActionGroup.triggered.connect(self.onViewHorizontalMode)
+    self._toolbar.addSeparator()
+    # Actions to draw vertical curves as linear or logarithmic
+    self._verActionGroup = QtGui.QActionGroup(self._plotWidget)
+    self._verLinearAction = self.createAndAddLocalAction("ver_linear.png", trQ("VER_LINEAR_TXT"))
+    self._verLinearAction.setCheckable(True)
+    self._verLogarithmicAction = self.createAndAddLocalAction("ver_logarithmic.png", trQ("VER_LOGARITHMIC_TXT"))
+    self._verLogarithmicAction.setCheckable(True)
+    self._verActionGroup.addAction(self._verLinearAction)
+    self._verActionGroup.addAction(self._verLogarithmicAction)
+    self._verLinearAction.setChecked(True)
+    self._verActionGroup.triggered.connect(self.onViewVerticalMode)
+    self._verActionGroup.setExclusive(True)
+    self._toolbar.addSeparator()
+    # Action to show or hide the legend 
+    self._actionLegend = self.createAndAddLocalAction("legend.png", trQ("SHOW_LEGEND_TXT"))
+    self._actionLegend.setCheckable(True)
+    self._actionLegend.triggered.connect(self.showHideLegend)
+    if self._DEFAULT_LEGEND_STATE:
+      self._actionLegend.setChecked(True)
+    self._toolbar.addSeparator()
+    # Action to set the preferences
+    a = self.createAndAddLocalAction("settings.png", trQ("SETTINGS_TXT"))
+    a.triggered.connect(self.onSettings)
+    pass
+    
+  def dumpView(self):
+    # Choice of the view backup file
+    filters = []
+    for form in ["IMAGES_FILES", "PDF_FILES", "POSTSCRIPT_FILES", "ENCAPSULATED_POSTSCRIPT_FILES"]:
+      filters.append(trQ(form))
+    fileName = self._sgPyQt.getFileName(self._sgPyQt.getDesktop(),
+                                        "",
+                                        filters,
+                                        trQ("DUMP_VIEW_FILE"),
+                                        False )
+    if not fileName.isEmpty():
+      name = str(fileName)
+      self._mplAxes.figure.savefig(name)
+    pass
+    
+  def autoFit(self, check=True, repaint=True):
+    if self.__repaintOK():
+      self._mplAxes.relim()
+      xm, xM = self._mplAxes.xaxis.get_data_interval()
+      ym, yM = self._mplAxes.yaxis.get_data_interval()
+      i = yM-ym
+      self._mplAxes.axis([xm, xM, ym-i*self.AUTOFIT_MARGIN, yM+i*self.AUTOFIT_MARGIN])
+      if repaint:
+        self.repaint()
+  
+  def zoomArea(self):
+    if self._panAction.isChecked() and self._zoomAction.isChecked():
+      self._panAction.setChecked(False)
+    # Trigger underlying matplotlib action:
+    self._mplNavigationActions["Zoom"].trigger()
+  
+  def pan(self):
+    if self._panAction.isChecked() and self._zoomAction.isChecked():
+      self._zoomAction.setChecked(False)
+    # Trigger underlying matplotlib action:
+    self._mplNavigationActions["Pan"].trigger()
+
+  def getMarker(self, go_next=False):
+    if go_next:
+      self._lastMarkerID = (self._lastMarkerID+1) % len(self.CURVE_MARKERS)
+    return self.CURVE_MARKERS[self._lastMarkerID]
+
+  def changeModeCurve(self, repaint=True):
+    if not self.__repaintOK():
+      return
+    action = self._curveActionGroup.checkedAction()
+    if action is self._pointsAction :
+      for crv_view in self._curveViews.values():
+        crv_view.setLineStyle("None")
+    elif action is self._linesAction :
+      for crv_view in self._curveViews.values():
+        crv_view.setLineStyle("-")
+    else :
+      raise NotImplementedError
+    if repaint:
+      self.repaint()
+  
+  def setXLog(self, log, repaint=True):
+    if not self.__repaintOK():
+      return
+    self._blockLogSignal = True
+    if log:
+      self._mplAxes.set_xscale('log')
+      self._horLogarithmicAction.setChecked(True)
+    else:
+      self._mplAxes.set_xscale('linear')
+      self._horLinearAction.setChecked(True)
+    if repaint:
+      self.autoFit()
+      self.repaint()
+    self._blockLogSignal = False
+
+  def setYLog(self, log, repaint=True):
+    if not self.__repaintOK():
+      return
+    self._blockLogSignal = True
+    if log:
+      self._mplAxes.set_yscale('log')
+      self._verLogarithmicAction.setChecked(True)
+    else:
+      self._mplAxes.set_yscale('linear')
+      self._verLinearAction.setChecked(True)
+    if repaint:
+      self.autoFit()
+      self.repaint()
+    self._blockLogSignal = False
+    
+  def setXSciNotation(self, sciNotation, repaint=True):
+    self._axisXSciNotation = sciNotation
+    self.changeFormatAxis()
+    if repaint:
+      self.repaint()
+   
+  def setYSciNotation(self, sciNotation, repaint=True):
+    self._axisYSciNotation = sciNotation
+    self.changeFormatAxis()
+    if repaint:
+      self.repaint()
+    
+  def onViewHorizontalMode(self, checked=True, repaint=True):
+    if self._blockLogSignal:
+      return
+    action = self._horActionGroup.checkedAction()  
+    if action is self._horLinearAction:
+      self.setXLog(False, repaint)
+    elif action is self._horLogarithmicAction:
+      self.setXLog(True, repaint)
+    else:
+      raise NotImplementedError
+  
+  def onViewVerticalMode(self, checked=True, repaint=True):
+    if self._blockLogSignal:
+      return
+    action = self._verActionGroup.checkedAction()  
+    if action is self._verLinearAction:
+      self.setYLog(False, repaint)
+    elif action is self._verLogarithmicAction:
+      self.setYLog(True, repaint)
+    else:
+      raise NotImplementedError
+    if repaint:
+      self.repaint()
+  
+  def __adjustFigureMargins(self, withLegend):
+    """ Adjust figure margins to make room for the legend """
+    if withLegend:
+      leg = self._legend
+      bbox = leg.get_window_extent()
+      # In axes coordinates: 
+      bbox2 = bbox.transformed(leg.figure.transFigure.inverted())
+      if self._legendLoc == "right":
+        self._mplFigure.subplots_adjust(right=1.0-(bbox2.width+0.02))
+      elif self._legendLoc == "bottom":
+        self._mplFigure.subplots_adjust(bottom=bbox2.height+0.1)
+    else:
+      # Reset to default (rc) values
+      self._mplFigure.subplots_adjust(bottom=0.1, right=0.9)
+  
+  def setLegendVisible(self, visible, repaint=True):
+    if visible and not self._actionLegend.isChecked():
+      self._actionLegend.setChecked(True)
+      self.showHideLegend(repaint=repaint)
+    if not visible and self._actionLegend.isChecked():
+      self._actionLegend.setChecked(False)
+      self.showHideLegend(repaint=repaint)
+  
+  def showHideLegend(self, actionChecked=None, repaint=True):
+    if not self.__repaintOK():  # Show/hide legend is extremely costly
+      return
+    
+    show = self._actionLegend.isChecked()
+    nCurves = len(self._curveViews)
+    if nCurves > 10: fontSize = 'x-small'
+    else:            fontSize = None
+    
+    if nCurves == 0:
+      # Remove legend 
+      leg = self._mplAxes.legend()
+      if leg is not None: leg.remove()
+    if show and nCurves > 0:
+      # Recreate legend from scratch
+      if self._legend is not None:
+        self._legend = None
+        self._mplAxes._legend = None
+      if self._legendLoc == "bottom":
+        self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(0.0, -0.05, 1.0, -0.05), 
+                                            borderaxespad=0.0, mode="expand", fancybox=True, 
+                                            shadow=True, ncol=3, prop={'size':fontSize, 'style': 'italic'})
+      elif self._legendLoc == "right":
+        self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(1.02,1.0), borderaxespad=0.0,
+                                            ncol=1, fancybox=True, shadow=True, prop={'size':fontSize, 'style': 'italic'})
+      else:
+        raise Exception("Invalid legend placement! Must be 'bottom' or 'right'")
+      # Canvas must be drawn so we can adjust the figure placement:
+      self._mplCanvas.draw()
+      self.__adjustFigureMargins(withLegend=True)
+    else:
+      if self._legend is None:
+        # Nothing to do
+        return
+      else:
+        self._legend.set_visible(False)
+        self._legend = None
+        self._mplAxes._legend = None
+        self._mplCanvas.draw()
+        self.__adjustFigureMargins(withLegend=False)
+    
+    curr_crv = self._model._currentCurve
+    if curr_crv is None: curr_title = None
+    else:                curr_title = curr_crv.getTitle()
+    if self._legend is not None:
+      for label in self._legend.get_texts() :
+        text = label.get_text()
+        if (text == curr_title):
+          label.set_backgroundcolor('0.85')
+        else :
+          label.set_backgroundcolor('white')
+        
+    if repaint:
+      self.repaint()
+   
+  def onSettings(self, trigger=False, dlg_test=None):
+    dlg = dlg_test or PlotSettings()
+    dlg.titleEdit.setText(self._mplAxes.get_title())
+    dlg.axisXTitleEdit.setText(self._mplAxes.get_xlabel())
+    dlg.axisYTitleEdit.setText(self._mplAxes.get_ylabel())
+    dlg.gridCheckBox.setChecked(self._mplAxes.xaxis._gridOnMajor)  # could not find a relevant API to check this
+    dlg.axisXSciCheckBox.setChecked(self._axisXSciNotation)
+    dlg.axisYSciCheckBox.setChecked(self._axisYSciNotation)
+    xmin, xmax = self._mplAxes.get_xlim()
+    ymin, ymax = self._mplAxes.get_ylim()
+    xminText = "%g" %xmin
+    xmaxText = "%g" %xmax
+    yminText = "%g" %ymin
+    ymaxText = "%g" %ymax
+    dlg.axisXMinEdit.setText(xminText)
+    dlg.axisXMaxEdit.setText(xmaxText)
+    dlg.axisYMinEdit.setText(yminText)
+    dlg.axisYMaxEdit.setText(ymaxText)
+    # List of markers
+    dlg.markerCurve.clear()
+    for marker in self.CURVE_MARKERS :
+      dlg.markerCurve.addItem(marker)
+    curr_crv = self._model.getCurrentCurve()
+    if not curr_crv is None:
+      dlg.colorCurve.setEnabled(True)
+      dlg.markerCurve.setEnabled(True)
+      name = curr_crv.getTitle()
+      dlg.nameCurve.setText(name)
+      view = self._curveViews[curr_crv.getID()] 
+      marker = view.getMarker()
+      color = view.getColor()
+      index = dlg.markerCurve.findText(marker)
+      dlg.markerCurve.setCurrentIndex(index)
+      rgb = colors.colorConverter.to_rgb(color)
+      dlg.setRGB(rgb[0],rgb[1],rgb[2])
+    else :
+      dlg.colorCurve.setEnabled(False)
+      dlg.markerCurve.setEnabled(False)
+      dlg.nameCurve.setText("")
+      view = None
+    if self._legend is None:
+      dlg.showLegendCheckBox.setChecked(False)
+      dlg.legendPositionComboBox.setEnabled(False)
+    else :
+      if self._legend.get_visible():
+        dlg.showLegendCheckBox.setChecked(True)
+        dlg.legendPositionComboBox.setEnabled(True)
+        if self._legendLoc == "bottom":
+          dlg.legendPositionComboBox.setCurrentIndex(0)
+        elif self._legendLoc == "right" :
+          dlg.legendPositionComboBox.setCurrentIndex(1)
+      else :
+        dlg.showLegendCheckBox.setChecked(False)
+        dlg.legendPositionComboBox.setEnabled(False)    
+             
+    if dlg.exec_():
+      # Title
+      self._model.setTitle(dlg.titleEdit.text())
+      # Axis
+      self._model.setXLabel(dlg.axisXTitleEdit.text())
+      self._model.setYLabel(dlg.axisYTitleEdit.text())
+      # Grid
+      if dlg.gridCheckBox.isChecked() :
+        self._mplAxes.grid(True)
+      else :
+        self._mplAxes.grid(False)
+      # Legend
+      if  dlg.showLegendCheckBox.isChecked():
+        self._actionLegend.setChecked(True)
+        if dlg.legendPositionComboBox.currentIndex() == 0 :
+          self._legendLoc = "bottom"
+        elif dlg.legendPositionComboBox.currentIndex() == 1 :
+          self._legendLoc = "right"
+      else :
+        self._actionLegend.setChecked(False)
+      xminText = dlg.axisXMinEdit.text()
+      xmaxText = dlg.axisXMaxEdit.text()
+      yminText = dlg.axisYMinEdit.text()
+      ymaxText = dlg.axisYMaxEdit.text()
+      self._mplAxes.axis([float(xminText), float(xmaxText), float(yminText), float(ymaxText)] )
+      self._axisXSciNotation = dlg.axisXSciCheckBox.isChecked()
+      self._axisYSciNotation = dlg.axisYSciCheckBox.isChecked()
+      self.changeFormatAxis()
+      # Color and marker of the curve
+      if view:
+        view.setColor(dlg.getRGB()) 
+        view.setMarker(self.CURVE_MARKERS[dlg.markerCurve.currentIndex()])
+      self.showHideLegend(repaint=True)
+      self._mplCanvas.draw()
+    pass
+    
+  def updateViewTitle(self):
+    s = ""
+    if self._model._title != "":
+      s = " - %s" % self._model._title
+    title = "CurvePlot (%d)%s" % (self._model.getID(), s)
+    self._sgPyQt.setViewTitle(self._salomeViewID, title)
+    
+  def onCurrentPlotSetChange(self):
+    """ Avoid a unnecessary call to update() when just switching current plot set! """ 
+    pass
+  
+  def onCurrentCurveChange(self):
+    curr_crv2 = self._model.getCurrentCurve()
+    if curr_crv2 != self._currCrv:
+      if self._currCrv is not None:
+        view = self._curveViews[self._currCrv.getID()]
+        view.toggleHighlight(False)
+      if not curr_crv2 is None:
+        view = self._curveViews[curr_crv2.getID()] 
+        view.toggleHighlight(True)
+      self._currCrv = curr_crv2
+      self.showHideLegend(repaint=False) # redo legend
+      self.repaint() 
+      
+  def changeFormatAxis(self) :
+    if not self.__repaintOK():
+      return
+    
+    # don't try to switch to sci notation if we are not using the 
+    # matplotlib.ticker.ScalarFormatter (i.e. if in Log for ex.)
+    if self._horLinearAction.isChecked():
+      if self._axisXSciNotation :
+        self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='x')
+      else :
+        self._mplAxes.ticklabel_format(style='plain',axis='x')
+    if self._verLinearAction.isChecked():    
+      if self._axisYSciNotation :
+        self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='y')
+      else :
+        self._mplAxes.ticklabel_format(style='plain',axis='y')
+    
+  def update(self):
+    if self._salomeViewID is None:
+      self.createPlotWidget()
+      self._salomeViewID = self._sgPyQt.createView("CurvePlot", self._plotWidget)
+      Logger.Debug("Creating SALOME view ID=%d" % self._salomeViewID)
+      self._sgPyQt.setViewVisible(self._salomeViewID, True)
+    
+    self.updateViewTitle()
+    
+    # Check list of curve views:
+    set_mod = set(self._model._curves.keys())
+    set_view = set(self._curveViews.keys())
+    
+    # Deleted/Added curves:
+    dels = set_view - set_mod
+    added = set_mod - set_view
+    
+    for d in dels:
+      self.removeCurve(d)
+
+    if not len(self._curveViews):
+      # Reset color cycle 
+      self._mplAxes.set_color_cycle(None)
+
+    for a in added:
+      self.appendCurve(a)
+
+    # Axes labels and title
+    self._mplAxes.set_xlabel(self._model._xlabel)
+    self._mplAxes.set_ylabel(self._model._ylabel)
+    self._mplAxes.set_title(self._model._title)
+
+    self.onViewHorizontalMode(repaint=False)
+    self.onViewVerticalMode(repaint=False)
+    self.changeModeCurve(repaint=False)
+    self.showHideLegend(repaint=False)   # The canvas is repainted anyway (needed to get legend bounding box)
+    self.changeFormatAxis()
+
+    # Redo auto-fit
+    self.autoFit(repaint=False)
+    self.repaint()
+  
+  def onDataChange(self):
+    # the rest is done in the CurveView:
+    self.autoFit(repaint=True)
+    
+  def onMousePress(self, event):
+    if event.button == 3 :
+      if self._panAction.isChecked():
+        self._panAction.setChecked(False)
+      if self._zoomAction.isChecked():
+        self._zoomAction.setChecked(False)
+    
+  def onContextMenu(self, position):
+    pos = self._mplCanvas.mapToGlobal(QtCore.QPoint(position.x(),position.y()))
+    self._popupMenu.exec_(pos)
+    
+  def onScroll(self, event):
+    # Event location (x and y)
+    xdata = event.xdata
+    ydata = event.ydata
+    
+    cur_xlim = self._mplAxes.get_xlim()
+    cur_ylim = self._mplAxes.get_ylim()
+    
+    base_scale = 2.
+    if event.button == 'down':
+      # deal with zoom in
+      scale_factor = 1 / base_scale
+    elif event.button == 'up':
+      # deal with zoom out
+      scale_factor = base_scale
+    else:
+      # deal with something that should never happen
+      scale_factor = 1
+    
+    new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
+    new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
+
+    relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
+    rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
+
+    self._mplAxes.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
+    self._mplAxes.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
+    
+    self.repaint()
+    pass
+    
+  def onPressEvent(self, event):
+    if event.button == 3 :
+      #self._mplCanvas.emit(QtCore.SIGNAL("button_release_event()"))
+      canvasSize = event.canvas.geometry()
+      point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
+      self._popupMenu.exec_(point)
+    else :
+      print "Press event on the other button"
+    #if event.button == 3 :
+    #  canvasSize = event.canvas.geometry()
+    #  point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
+    #  self._popupMenu.move(point)
+    #  self._popupMenu.show()
+   
+  def onMotionEvent(self, event):
+    print "OnMotionEvent ",event.button
+    #if event.button == 3 :
+    #  event.button = None
+    #  return True
+   
+  def onReleaseEvent(self, event):
+    print "OnReleaseEvent ",event.button
+    #if event.button == 3 :
+    #  event.button = None
+    #  return False