Bläddra i källkod

ENH: Add lib CTK/Scripting/Python/Widgets with class ctkPythonShell

Jean-Christophe Fillion-Robin 15 år sedan
förälder
incheckning
0b359f72e8

+ 1 - 0
CMakeLists.txt

@@ -210,6 +210,7 @@ SET(CTK_LIBS
   DICOM/Core
   DICOM/Widgets
   Scripting/Python/Core
+  Scripting/Python/Widgets
   Visualization/VTK/Core
   Visualization/VTK/Widgets
   Visualization/XIP

+ 58 - 0
Libs/Scripting/Python/Widgets/CMakeLists.txt

@@ -0,0 +1,58 @@
+PROJECT(CTKScriptingPythonWidgets)
+
+#
+# 3rd party dependencies
+#
+
+#
+# See CTK/CMake/ctkMacroBuildLib.cmake for details
+#
+
+SET(KIT_export_directive "CTK_SCRIPTING_PYTHON_WIDGETS_EXPORT")
+
+# Additional directories to include
+SET(KIT_include_directories
+  )
+  
+# Source files
+SET(KIT_SRCS
+  ctkPythonShell.cpp
+  ctkPythonShell.h
+  )
+
+# Headers that should run through moc
+SET(KIT_MOC_SRCS
+  ctkPythonShell.h
+  )
+
+# UI files
+SET(KIT_UI_FORMS
+)
+
+# Resources
+SET(KIT_resources
+)
+
+# Target libraries - See CMake/ctkMacroGetTargetLibraries.cmake
+# The following macro will read the target libraries from the file 'target_libraries.cmake'
+ctkMacroGetTargetLibraries(KIT_target_libraries)
+
+ctkMacroBuildLib(
+  NAME ${PROJECT_NAME}
+  EXPORT_DIRECTIVE ${KIT_export_directive}
+  INCLUDE_DIRECTORIES ${KIT_include_directories}
+  SRCS ${KIT_SRCS}
+  MOC_SRCS ${KIT_MOC_SRCS}
+  UI_FORMS ${KIT_UI_FORMS}
+  TARGET_LIBRARIES ${KIT_target_libraries}
+  RESOURCES ${KIT_resources}
+  LIBRARY_TYPE ${CTK_LIBRARY_MODE}
+  )
+
+# Plugins
+#ADD_SUBDIRECTORY(Plugins)
+
+# Testing
+IF(BUILD_TESTING)
+  #ADD_SUBDIRECTORY(Testing)
+ENDIF(BUILD_TESTING)

+ 1 - 0
Libs/Scripting/Python/Widgets/Testing/CMakeLists.txt

@@ -0,0 +1 @@
+ADD_SUBDIRECTORY(Cpp)

+ 30 - 0
Libs/Scripting/Python/Widgets/Testing/Cpp/CMakeLists.txt

@@ -0,0 +1,30 @@
+SET(KIT ${PROJECT_NAME})
+
+CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
+  
+  #EXTRA_INCLUDE TestingMacros.h
+  )
+
+SET (TestsToRun ${Tests})
+REMOVE (TestsToRun ${KIT}CppTests.cpp)
+
+SET(LIBRARY_NAME ${PROJECT_NAME})
+
+ADD_EXECUTABLE(${KIT}CppTests ${Tests})
+TARGET_LINK_LIBRARIES(${KIT}CppTests ${LIBRARY_NAME} ${CTK_BASE_LIBRARIES})
+
+SET( KIT_TESTS ${CXX_TEST_PATH}/${KIT}CppTests)
+IF(WIN32)
+  SET(KIT_TESTS ${CXX_TEST_PATH}/${CMAKE_BUILD_TYPE}/${KIT}CppTests)
+ENDIF(WIN32)
+
+MACRO( SIMPLE_TEST  TESTNAME )
+  ADD_TEST( ${TESTNAME} ${KIT_TESTS} ${TESTNAME} )
+  SET_PROPERTY(TEST ${TESTNAME} PROPERTY LABELS ${PROJECT_NAME})
+ENDMACRO( SIMPLE_TEST  )
+
+#
+# Add Tests
+#
+
+#SIMPLE_TEST( ctkModelTesterTest1 )

+ 441 - 0
Libs/Scripting/Python/Widgets/ctkPythonShell.cpp

@@ -0,0 +1,441 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc. 
+  All rights reserved.
+  Distributed under a BSD License. See LICENSE.txt file.
+
+  This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
+  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the above copyright notice for more information.
+
+=========================================================================*/
+/*=========================================================================
+
+   Program: ParaView
+   Module:    $RCSfile$
+
+   Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
+   All rights reserved.
+
+   ParaView is a free software; you can redistribute it and/or modify it
+   under the terms of the ParaView license version 1.2. 
+
+   See License_v1.2.txt for the full ParaView license.
+   A copy of this license can be obtained by contacting
+   Kitware Inc.
+   28 Corporate Drive
+   Clifton Park, NY 12065
+   USA
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+//#include <vtkPython.h> // python first
+
+// Qt includes
+#include <QCoreApplication>
+#include <QResizeEvent>
+#include <QScrollBar>
+#include <QStringListModel>
+#include <QTextCharFormat>
+#include <QVBoxLayout>
+
+// PythonQt includes
+#include <PythonQt.h>
+#include <PythonQtObjectPtr.h>
+
+// CTK includes
+#include <ctkConsoleWidget.h>
+#include <ctkAbstractPythonManager.h>
+#include "ctkPythonShell.h"
+
+//----------------------------------------------------------------------------
+class ctkPythonShellCompleter : public ctkConsoleWidgetCompleter
+{
+public:
+  ctkPythonShellCompleter(ctkPythonShell& p) : Parent(p)
+    {
+    this->setParent(&p);
+    }
+
+  virtual void updateCompletionModel(const QString& completion)
+    {
+    // Start by clearing the model
+    this->setModel(0);
+
+    // Don't try to complete the empty string
+    if (completion.isEmpty())
+      {
+      return;
+      }
+
+    // Search backward through the string for usable characters
+    QString textToComplete;
+    for (int i = completion.length()-1; i >= 0; --i)
+      {
+      QChar c = completion.at(i);
+      if (c.isLetterOrNumber() || c == '.' || c == '_')
+        {
+        textToComplete.prepend(c);
+        }
+      else
+        {
+        break;
+        }
+      }
+
+    // Split the string at the last dot, if one exists
+    QString lookup;
+    QString compareText = textToComplete;
+    int dot = compareText.lastIndexOf('.');
+    if (dot != -1)
+      {
+      lookup = compareText.mid(0, dot);
+      compareText = compareText.mid(dot+1);
+      }
+
+    // Lookup python names
+    QStringList attrs;
+    if (!lookup.isEmpty() || !compareText.isEmpty())
+      {
+      attrs = Parent.getPythonAttributes(lookup);
+      }
+
+    // Initialize the completion model
+    if (!attrs.isEmpty())
+      {
+      this->setCompletionMode(QCompleter::PopupCompletion);
+      this->setModel(new QStringListModel(attrs, this));
+      this->setCaseSensitivity(Qt::CaseInsensitive);
+      this->setCompletionPrefix(compareText.toLower());
+      this->popup()->setCurrentIndex(this->completionModel()->index(0, 0));
+      }
+    }
+  ctkPythonShell& Parent;
+};
+
+
+/////////////////////////////////////////////////////////////////////////
+// ctkPythonShell::pqImplementation
+
+struct ctkPythonShell::pqImplementation
+{
+  pqImplementation(QWidget* _parent, ctkAbstractPythonManager* pythonManager)
+    : Console(_parent), PythonManager(pythonManager)
+  {
+  }
+
+//----------------------------------------------------------------------------
+//   void initialize(int argc, char* argv[])
+//   {
+//     // Setup Python's interactive prompts
+//     PyObject* ps1 = PySys_GetObject(const_cast<char*>("ps1"));
+//     if(!ps1)
+//       {
+//       PySys_SetObject(const_cast<char*>("ps1"), ps1 = PyString_FromString(">>> "));
+//       Py_XDECREF(ps1);
+//       }
+// 
+//     PyObject* ps2 = PySys_GetObject(const_cast<char*>("ps2"));
+//     if(!ps2)
+//       {
+//       PySys_SetObject(const_cast<char*>("ps2"), ps2 = PyString_FromString("... "));
+//       Py_XDECREF(ps2);
+//       }
+//     this->MultilineStatement = false;
+//   }
+
+//----------------------------------------------------------------------------
+  ~pqImplementation()
+  {
+//     this->destroyInterpretor();
+  }
+
+//----------------------------------------------------------------------------
+//   void destroyInterpretor()
+//     {
+//     if (this->Interpreter)
+//       {
+//       QTextCharFormat format = this->Console.getFormat();
+//       format.setForeground(QColor(255, 0, 0));
+//       this->Console.setFormat(format);
+//       this->Console.printString("\n... restarting ...\n");
+//       format.setForeground(QColor(0, 0, 0));
+//       this->Console.setFormat(format);
+// 
+//       this->Interpreter->MakeCurrent();
+// 
+//       // Restore Python's original stdout and stderr
+//       PySys_SetObject(const_cast<char*>("stdout"), PySys_GetObject(const_cast<char*>("__stdout__")));
+//       PySys_SetObject(const_cast<char*>("stderr"), PySys_GetObject(const_cast<char*>("__stderr__")));
+//       this->Interpreter->ReleaseControl();
+//       this->Interpreter->Delete();
+//       }
+//     this->Interpreter = 0;
+//     }
+
+//----------------------------------------------------------------------------
+  void executeCommand(const QString& command)
+  {
+//     this->MultilineStatement = 
+//       this->Interpreter->Push(Command.toAscii().data());
+    if (command.length())
+      {
+      Q_ASSERT(this->PythonManager);
+      this->PythonManager->executeString(command);
+      }
+  }
+  
+//----------------------------------------------------------------------------
+  void promptForInput(const QString& indent=QString())
+  {
+    QTextCharFormat format = this->Console.getFormat();
+    format.setForeground(QColor(0, 0, 0));
+    this->Console.setFormat(format);
+
+//     this->Interpreter->MakeCurrent();
+    if(!this->MultilineStatement)
+      {
+      this->Console.prompt(">>> ");
+      //this->Console.prompt(
+      //  PyString_AsString(PySys_GetObject(const_cast<char*>("ps1"))));
+      }
+    else
+      {
+      this->Console.prompt("... ");
+      //this->Console.prompt(
+      //  PyString_AsString(PySys_GetObject(const_cast<char*>("ps2"))));
+      }
+    this->Console.printCommand(indent);
+//     this->Interpreter->ReleaseControl();
+  }
+
+  /// Provides a console for gathering user input and displaying 
+  /// Python output
+  ctkConsoleWidget Console;
+
+  ctkAbstractPythonManager* PythonManager;
+
+  /// Indicates if the last statement processes was incomplete.
+  bool MultilineStatement;
+};
+
+/////////////////////////////////////////////////////////////////////////
+// ctkPythonShell
+
+//----------------------------------------------------------------------------
+ctkPythonShell::ctkPythonShell(ctkAbstractPythonManager* pythonManager, QWidget* _parent):
+  Superclass(_parent),
+  Implementation(new pqImplementation(this, pythonManager))
+{
+  // Layout UI
+  QVBoxLayout* const boxLayout = new QVBoxLayout(this);
+  boxLayout->setMargin(0);
+  boxLayout->addWidget(&this->Implementation->Console);
+
+  this->setObjectName("pythonShell");
+
+  ctkPythonShellCompleter* completer = new ctkPythonShellCompleter(*this);
+  this->Implementation->Console.setCompleter(completer);
+  
+  QObject::connect(
+    &this->Implementation->Console, SIGNAL(executeCommand(const QString&)), 
+    this, SLOT(onExecuteCommand(const QString&)));
+
+  // The call to mainContext() ensures that python has been initialized.
+  Q_ASSERT(this->Implementation->PythonManager);
+  this->Implementation->PythonManager->mainContext();
+
+  QTextCharFormat format = this->Implementation->Console.getFormat();
+  format.setForeground(QColor(0, 0, 255));
+  this->Implementation->Console.setFormat(format);
+  this->Implementation->Console.printString(
+    QString("Python %1 on %2\n").arg(Py_GetVersion()).arg(Py_GetPlatform()));
+  this->promptForInput();
+
+  Q_ASSERT(PythonQt::self());
+
+  this->connect(PythonQt::self(), SIGNAL(pythonStdOut(const QString&)),
+                SLOT(printStdout(const QString&)));
+  this->connect(PythonQt::self(), SIGNAL(pythonStdErr(const QString&)),
+                SLOT(printStderr(const QString&)));
+}
+
+//----------------------------------------------------------------------------
+ctkPythonShell::~ctkPythonShell()
+{
+  delete this->Implementation;
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::clear()
+{
+  this->Implementation->Console.clear();
+  this->Implementation->promptForInput();
+}
+
+// //----------------------------------------------------------------------------
+// void ctkPythonShell::makeCurrent()
+// {
+//   this->Implementation->Interpreter->MakeCurrent();
+// }
+// 
+// //----------------------------------------------------------------------------
+// void ctkPythonShell::releaseControl()
+// {
+//   this->Implementation->Interpreter->ReleaseControl();
+// }
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::executeScript(const QString& script)
+{
+  Q_UNUSED(script);
+  
+  this->printStdout("\n");
+  emit this->executing(true);
+//   this->Implementation->Interpreter->RunSimpleString(
+//     script.toAscii().data());
+  emit this->executing(false);
+  this->Implementation->promptForInput();
+}
+
+//----------------------------------------------------------------------------
+QStringList ctkPythonShell::getPythonAttributes(const QString& pythonVariableName)
+{
+//   this->makeCurrent();
+
+  Q_ASSERT(PyThreadState_GET()->interp);
+  PyObject* dict = PyImport_GetModuleDict();
+  PyObject* object = PyDict_GetItemString(dict, "__main__");
+  Py_INCREF(object);
+
+
+  if (!pythonVariableName.isEmpty())
+    {
+    QStringList tmpNames = pythonVariableName.split('.');
+    for (int i = 0; i < tmpNames.size() && object; ++i)
+      {
+      QByteArray tmpName = tmpNames.at(i).toLatin1();
+      PyObject* prevObj = object;
+      if (PyDict_Check(object))
+        {
+        object = PyDict_GetItemString(object, tmpName.data());
+        Py_XINCREF(object);
+        }
+      else
+        {
+        object = PyObject_GetAttrString(object, tmpName.data());
+        }
+      Py_DECREF(prevObj);
+      }
+    PyErr_Clear();
+    }
+
+  QStringList results;
+  if (object)
+    {
+    PyObject* keys = PyObject_Dir(object);
+    if (keys)
+      {
+      PyObject* key;
+      PyObject* value;
+      QString keystr;
+      int nKeys = PyList_Size(keys);
+      for (int i = 0; i < nKeys; ++i)
+        {
+        key = PyList_GetItem(keys, i);
+        value = PyObject_GetAttr(object, key);
+        if (!value)
+          {
+          continue;
+          }
+
+        results << PyString_AsString(key);
+        Py_DECREF(value);
+        }
+      Py_DECREF(keys);
+      }
+    Py_DECREF(object);
+    }
+
+//   this->releaseControl();
+  return results;
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::printStdout(const QString& text)
+{
+  QTextCharFormat format = this->Implementation->Console.getFormat();
+  format.setForeground(QColor(0, 150, 0));
+  this->Implementation->Console.setFormat(format);
+  
+  this->Implementation->Console.printString(text);
+  
+  QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::printMessage(const QString& text)
+{
+  QTextCharFormat format = this->Implementation->Console.getFormat();
+  format.setForeground(QColor(0, 0, 150));
+  this->Implementation->Console.setFormat(format);
+  
+  this->Implementation->Console.printString(text);
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::printStderr(const QString& text)
+{
+  QTextCharFormat format = this->Implementation->Console.getFormat();
+  format.setForeground(QColor(255, 0, 0));
+  this->Implementation->Console.setFormat(format);
+  
+  this->Implementation->Console.printString(text);
+  
+  QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::onExecuteCommand(const QString& Command)
+{
+  QString command = Command;
+  command.replace(QRegExp("\\s*$"), "");
+  this->internalExecuteCommand(command);
+
+  // Find the indent for the command.
+  QRegExp regExp("^(\\s+)");
+  QString indent;
+  if (regExp.indexIn(command) != -1)
+    {
+    indent = regExp.cap(1);
+    }
+  this->Implementation->promptForInput(indent);
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::promptForInput()
+{
+  this->Implementation->promptForInput();
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonShell::internalExecuteCommand(const QString& command)
+{
+  emit this->executing(true);  
+  this->Implementation->executeCommand(command);
+  emit this->executing(false);
+}

+ 118 - 0
Libs/Scripting/Python/Widgets/ctkPythonShell.h

@@ -0,0 +1,118 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc. 
+  All rights reserved.
+  Distributed under a BSD License. See LICENSE.txt file.
+
+  This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
+  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the above copyright notice for more information.
+
+=========================================================================*/
+/*=========================================================================
+
+   Program: ParaView
+   Module:    $RCSfile$
+
+   Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
+   All rights reserved.
+
+   ParaView is a free software; you can redistribute it and/or modify it
+   under the terms of the ParaView license version 1.2. 
+
+   See License_v1.2.txt for the full ParaView license.
+   A copy of this license can be obtained by contacting
+   Kitware Inc.
+   28 Corporate Drive
+   Clifton Park, NY 12065
+   USA
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+#ifndef __ctkPythonShell_h
+#define __ctkPythonShell_h
+
+// Qt includes
+#include <QWidget>
+
+// CTK includes
+#include "CTKScriptingPythonWidgetsExport.h"
+
+/**
+  Qt widget that provides an interactive "shell" interface to an embedded Python interpreter.
+  You can put an instance of ctkPythonShell in a dialog or a window, and the user will be able
+  to enter Python commands and see their output, while the UI is still responsive.
+  
+  \sa pqConsoleWidget, pqPythonDialog
+*/  
+
+class ctkAbstractPythonManager;
+
+class CTK_SCRIPTING_PYTHON_WIDGETS_EXPORT ctkPythonShell :
+  public QWidget
+{
+  Q_OBJECT
+  
+public:
+  typedef QWidget Superclass; 
+  ctkPythonShell(ctkAbstractPythonManager* pythonManager, QWidget* _parent = 0);
+  ~ctkPythonShell();
+
+
+  /// Initializes the interpretor. If an interpretor is already setup (by an
+  /// earlier call to this method), it will be destroyed.
+//   void initializeInterpretor(int argc, char* argv[]);
+//   void initializeInterpretor();
+
+  /// Prints some text on the shell.
+  void printMessage(const QString&);
+
+  /// Calls MakeCurrent in the internal vtkPVPythonInteractiveInterpretor instance
+//   void makeCurrent();
+
+  /// Calls ReleaseControl in the internal vtkPVPythonInteractiveInterpretor instance
+//   void releaseControl();
+
+  /// Given a python variable name, lookup its attributes and return them in a
+  /// string list.
+  QStringList getPythonAttributes(const QString& pythonVariableName);
+
+signals:
+  void executing(bool);
+
+public slots:
+  void clear();
+  void executeScript(const QString&);
+
+protected slots:
+  void printStderr(const QString&);
+  void printStdout(const QString&);
+
+  void onExecuteCommand(const QString&);
+
+private:
+  ctkPythonShell(const ctkPythonShell&);
+  ctkPythonShell& operator=(const ctkPythonShell&);
+
+  void promptForInput();
+  void internalExecuteCommand(const QString&);
+
+  struct pqImplementation;
+  pqImplementation* const Implementation;
+};
+
+#endif // !__ctkPythonShell_h

+ 10 - 0
Libs/Scripting/Python/Widgets/target_libraries.cmake

@@ -0,0 +1,10 @@
+#
+# See CMake/ctkMacroGetTargetLibraries.cmake
+# 
+# This file should list the libraries required to build the current CTK libraries
+#
+
+SET(target_libraries
+  CTKWidgets
+  CTKScriptingPythonCore
+  )