--- /dev/null
+# 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()
+
--- /dev/null
+# 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)
--- /dev/null
+# 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})
--- /dev/null
+# 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()
+
--- /dev/null
+# 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(UseQt4Ext)
+
+SET(_res
+ CURVEPLOT_msg_en.ts
+ CURVEPLOT_msg_fr.ts
+)
+
+QT4_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()
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+# 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()
+
--- /dev/null
+# 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})
--- /dev/null
+// 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!");
+ }
+
+}
--- /dev/null
+// 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_ */
--- /dev/null
+// 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;
+ };
+}
+
+
+
--- /dev/null
+# 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})
+
--- /dev/null
+// 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;
+}
--- /dev/null
+#include <QObject>
+
+class TestCurvePlot : public QObject
+{
+ Q_OBJECT
+
+public slots:
+ void onClicked();
+
+};
--- /dev/null
+# 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()
--- /dev/null
+# 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})
--- /dev/null
+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()
--- /dev/null
+"""
+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
+
--- /dev/null
+class Logger(object):
+ """
+ Debug Info.
+ """
+ LOG_LEVEL = 0 # 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
--- /dev/null
+# 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})
--- /dev/null
+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
+
+
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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__()
+
--- /dev/null
+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
--- /dev/null
+# 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)
--- /dev/null
+from . import _use_pyqt
+if _use_pyqt:
+ from PyQt4.QtCore import *
+ Slot = pyqtSlot
+ Signal = pyqtSignal
+else:
+ from PySide.QtCore import *
--- /dev/null
+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)
+
--- /dev/null
+"""
+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
--- /dev/null
+"""
+ 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
--- /dev/null
+from . import _use_pyqt
+if _use_pyqt:
+ from PyQt4.uic import loadUi as loadUiGen
+else:
+ from pyside_dynamic import loadUi as loadUiGen
+
+
--- /dev/null
+# 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}")
+
--- /dev/null
+# -*- 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)
--- /dev/null
+# -*- 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
--- /dev/null
+Unit tests are in plot_test.py
+
+PlotCurve_Standalone is a standalone Python executable that shows the
+various functionalities of the package.
+
--- /dev/null
+# 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)
--- /dev/null
+# -*- 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()"))
--- /dev/null
+# -*- 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()
--- /dev/null
+# 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})
--- /dev/null
+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
--- /dev/null
+<?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>
--- /dev/null
+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()
--- /dev/null
+<?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>Plot 2D 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>
--- /dev/null
+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)
--- /dev/null
+<?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>
--- /dev/null
+# 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})
--- /dev/null
+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))
--- /dev/null
+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
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+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