From: mpv Date: Wed, 23 Apr 2014 10:24:24 +0000 (+0400) Subject: Make all c++ files "cpp" X-Git-Tag: V_0.2~118^2~9^2 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=912b947536177180af5bd6d620d84e9ebc05be66;p=modules%2Fshaper.git Make all c++ files "cpp" --- diff --git a/src/ConstructionPlugin/CMakeLists.txt b/src/ConstructionPlugin/CMakeLists.txt index fff34c724..430ffb9ab 100644 --- a/src/ConstructionPlugin/CMakeLists.txt +++ b/src/ConstructionPlugin/CMakeLists.txt @@ -7,8 +7,8 @@ SET(PROJECT_HEADERS ) SET(PROJECT_SOURCES - ConstructionPlugin_Plugin.cxx - ConstructionPlugin_Point.cxx + ConstructionPlugin_Plugin.cpp + ConstructionPlugin_Point.cpp ) ADD_DEFINITIONS(-DCONSTRUCTIONPLUGIN_EXPORTS ${BOOST_DEFINITIONS}) diff --git a/src/ConstructionPlugin/ConstructionPlugin_Plugin.cpp b/src/ConstructionPlugin/ConstructionPlugin_Plugin.cpp new file mode 100644 index 000000000..c433491f9 --- /dev/null +++ b/src/ConstructionPlugin/ConstructionPlugin_Plugin.cpp @@ -0,0 +1,24 @@ +#include "ConstructionPlugin_Plugin.h" +#include "ConstructionPlugin_Point.h" +#include +#include + +using namespace std; + +// the only created instance of this plugin +static ConstructionPlugin_Plugin* MY_INSTANCE = new ConstructionPlugin_Plugin(); + +ConstructionPlugin_Plugin::ConstructionPlugin_Plugin() +{ + // register this plugin + ModelAPI_PluginManager::get()->registerPlugin(this); +} + +shared_ptr ConstructionPlugin_Plugin::createFeature(string theFeatureID) +{ + if (theFeatureID == "Point") { + return shared_ptr(new ConstructionPlugin_Point); + } + // feature of such kind is not found + return shared_ptr(); +} diff --git a/src/ConstructionPlugin/ConstructionPlugin_Plugin.cxx b/src/ConstructionPlugin/ConstructionPlugin_Plugin.cxx deleted file mode 100644 index c433491f9..000000000 --- a/src/ConstructionPlugin/ConstructionPlugin_Plugin.cxx +++ /dev/null @@ -1,24 +0,0 @@ -#include "ConstructionPlugin_Plugin.h" -#include "ConstructionPlugin_Point.h" -#include -#include - -using namespace std; - -// the only created instance of this plugin -static ConstructionPlugin_Plugin* MY_INSTANCE = new ConstructionPlugin_Plugin(); - -ConstructionPlugin_Plugin::ConstructionPlugin_Plugin() -{ - // register this plugin - ModelAPI_PluginManager::get()->registerPlugin(this); -} - -shared_ptr ConstructionPlugin_Plugin::createFeature(string theFeatureID) -{ - if (theFeatureID == "Point") { - return shared_ptr(new ConstructionPlugin_Point); - } - // feature of such kind is not found - return shared_ptr(); -} diff --git a/src/ConstructionPlugin/ConstructionPlugin_Point.cpp b/src/ConstructionPlugin/ConstructionPlugin_Point.cpp new file mode 100644 index 000000000..ea613603b --- /dev/null +++ b/src/ConstructionPlugin/ConstructionPlugin_Point.cpp @@ -0,0 +1,31 @@ +// File: ConstructionPlugin_Point.cxx +// Created: 27 Mar 2014 +// Author: Mikhail PONIKAROV + +#include "ConstructionPlugin_Point.h" +#include "ModelAPI_PluginManager.h" +#include "ModelAPI_Document.h" +#include "ModelAPI_Data.h" +#include "ModelAPI_AttributeDouble.h" + +using namespace std; + +ConstructionPlugin_Point::ConstructionPlugin_Point() +{ +} + +void ConstructionPlugin_Point::initAttributes() +{ + data()->addAttribute(POINT_ATTR_X, ModelAPI_AttributeDouble::type()); + data()->addAttribute(POINT_ATTR_Y, ModelAPI_AttributeDouble::type()); + data()->addAttribute(POINT_ATTR_Z, ModelAPI_AttributeDouble::type()); +} + +// this is for debug only +#include +void ConstructionPlugin_Point::execute() +{ + // TODO: create a real shape for the point using OCC layer + cout<<"X="<real(POINT_ATTR_X)->value()<<" Y="<real(POINT_ATTR_Y)->value() + <<" Z="<real(POINT_ATTR_Z)->value()<addAttribute(POINT_ATTR_X, ModelAPI_AttributeDouble::type()); - data()->addAttribute(POINT_ATTR_Y, ModelAPI_AttributeDouble::type()); - data()->addAttribute(POINT_ATTR_Z, ModelAPI_AttributeDouble::type()); -} - -// this is for debug only -#include -void ConstructionPlugin_Point::execute() -{ - // TODO: create a real shape for the point using OCC layer - cout<<"X="<real(POINT_ATTR_X)->value()<<" Y="<real(POINT_ATTR_Y)->value() - <<" Z="<real(POINT_ATTR_Z)->value()< diff --git a/src/Event/Event_Listener.cxx b/src/Event/Event_Listener.cxx deleted file mode 100644 index ffb56c7b6..000000000 --- a/src/Event/Event_Listener.cxx +++ /dev/null @@ -1,5 +0,0 @@ -// File: Event_Listener.cxx -// Created: Thu Mar 13 2014 -// Author: Mikhail PONIKAROV - -#include diff --git a/src/Event/Event_Loop.cpp b/src/Event/Event_Loop.cpp new file mode 100644 index 000000000..c938dbe95 --- /dev/null +++ b/src/Event/Event_Loop.cpp @@ -0,0 +1,82 @@ +// File: Event_Loop.hxx +// Created: Thu Mar 13 2014 +// Author: Mikhail PONIKAROV + +#include + +#include +#include + +using namespace std; + +Event_Loop* Event_Loop::loop() +{ + // initialized on initialization of the application + static Event_Loop MAIN_LOOP; + return &MAIN_LOOP; +} + +Event_ID Event_Loop::eventByName(const char* theName) +{ + ///! All events created in this session, uniquely identified by the text and char pointer + static map CREATED_EVENTS; + char* aResult; + string aName(theName); + map::iterator aFound = CREATED_EVENTS.find(aName); + if (aFound == CREATED_EVENTS.end()) { //not created yet + aResult = strdup(theName); // copy to make unique internal pointer + CREATED_EVENTS[aName] = aResult; + } else + aResult = aFound->second; + + return Event_ID(aResult); +} + +void Event_Loop::send(Event_Message& theMessage) +{ + // TO DO: make it in thread and wit husage of semaphores + + map > >::iterator aFindID = myListeners.find( + theMessage.eventID().eventText()); + if (aFindID != myListeners.end()) { + map >::iterator aFindSender = aFindID->second.find( + theMessage.sender()); + if (aFindSender != aFindID->second.end()) { + list& aListeners = aFindSender->second; + for(list::iterator aL = aListeners.begin(); aL != aListeners.end(); aL++) + (*aL)->processEvent(&theMessage); + } + if (theMessage.sender()) { // also call for NULL senders registered + aFindSender = aFindID->second.find(NULL); + if (aFindSender != aFindID->second.end()) { + list& aListeners = aFindSender->second; + for(list::iterator aL = aListeners.begin(); aL != aListeners.end(); aL++) + (*aL)->processEvent(&theMessage); + } + } + } +} + +void Event_Loop::registerListener(Event_Listener* theListener, const Event_ID theID, + void* theSender) +{ + map > >::iterator aFindID = myListeners.find( + theID.eventText()); + if (aFindID == myListeners.end()) { // create container associated with ID + myListeners[theID.eventText()] = map >(); + aFindID = myListeners.find(theID.eventText()); + } + + map >::iterator aFindSender = aFindID->second.find(theSender); + if (aFindSender == aFindID->second.end()) { // create container associated with sender + aFindID->second[theSender] = list(); + aFindSender = aFindID->second.find(theSender); + } + // check that listener was not registered wit hsuch parameters before + list& aListeners = aFindSender->second; + for(list::iterator aL = aListeners.begin(); aL != aListeners.end(); aL++) + if (*aL == theListener) + return; // avoid duplicates + + aListeners.push_back(theListener); +} diff --git a/src/Event/Event_Loop.cxx b/src/Event/Event_Loop.cxx deleted file mode 100644 index c938dbe95..000000000 --- a/src/Event/Event_Loop.cxx +++ /dev/null @@ -1,82 +0,0 @@ -// File: Event_Loop.hxx -// Created: Thu Mar 13 2014 -// Author: Mikhail PONIKAROV - -#include - -#include -#include - -using namespace std; - -Event_Loop* Event_Loop::loop() -{ - // initialized on initialization of the application - static Event_Loop MAIN_LOOP; - return &MAIN_LOOP; -} - -Event_ID Event_Loop::eventByName(const char* theName) -{ - ///! All events created in this session, uniquely identified by the text and char pointer - static map CREATED_EVENTS; - char* aResult; - string aName(theName); - map::iterator aFound = CREATED_EVENTS.find(aName); - if (aFound == CREATED_EVENTS.end()) { //not created yet - aResult = strdup(theName); // copy to make unique internal pointer - CREATED_EVENTS[aName] = aResult; - } else - aResult = aFound->second; - - return Event_ID(aResult); -} - -void Event_Loop::send(Event_Message& theMessage) -{ - // TO DO: make it in thread and wit husage of semaphores - - map > >::iterator aFindID = myListeners.find( - theMessage.eventID().eventText()); - if (aFindID != myListeners.end()) { - map >::iterator aFindSender = aFindID->second.find( - theMessage.sender()); - if (aFindSender != aFindID->second.end()) { - list& aListeners = aFindSender->second; - for(list::iterator aL = aListeners.begin(); aL != aListeners.end(); aL++) - (*aL)->processEvent(&theMessage); - } - if (theMessage.sender()) { // also call for NULL senders registered - aFindSender = aFindID->second.find(NULL); - if (aFindSender != aFindID->second.end()) { - list& aListeners = aFindSender->second; - for(list::iterator aL = aListeners.begin(); aL != aListeners.end(); aL++) - (*aL)->processEvent(&theMessage); - } - } - } -} - -void Event_Loop::registerListener(Event_Listener* theListener, const Event_ID theID, - void* theSender) -{ - map > >::iterator aFindID = myListeners.find( - theID.eventText()); - if (aFindID == myListeners.end()) { // create container associated with ID - myListeners[theID.eventText()] = map >(); - aFindID = myListeners.find(theID.eventText()); - } - - map >::iterator aFindSender = aFindID->second.find(theSender); - if (aFindSender == aFindID->second.end()) { // create container associated with sender - aFindID->second[theSender] = list(); - aFindSender = aFindID->second.find(theSender); - } - // check that listener was not registered wit hsuch parameters before - list& aListeners = aFindSender->second; - for(list::iterator aL = aListeners.begin(); aL != aListeners.end(); aL++) - if (*aL == theListener) - return; // avoid duplicates - - aListeners.push_back(theListener); -} diff --git a/src/Event/Event_Message.cpp b/src/Event/Event_Message.cpp new file mode 100644 index 000000000..764ed826f --- /dev/null +++ b/src/Event/Event_Message.cpp @@ -0,0 +1,5 @@ +// File: Event_Message.cxx +// Created: Thu Mar 13 2014 +// Author: Mikhail PONIKAROV + +#include diff --git a/src/Event/Event_Message.cxx b/src/Event/Event_Message.cxx deleted file mode 100644 index 764ed826f..000000000 --- a/src/Event/Event_Message.cxx +++ /dev/null @@ -1,5 +0,0 @@ -// File: Event_Message.cxx -// Created: Thu Mar 13 2014 -// Author: Mikhail PONIKAROV - -#include diff --git a/src/Model/CMakeLists.txt b/src/Model/CMakeLists.txt index 1d56def92..abad9811b 100644 --- a/src/Model/CMakeLists.txt +++ b/src/Model/CMakeLists.txt @@ -14,14 +14,14 @@ SET(PROJECT_HEADERS ) SET(PROJECT_SOURCES - Model_Application.cxx - Model_Document.cxx - Model_PluginManager.cxx - Model_Data.cxx - Model_Iterator.cxx - Model_AttributeDouble.cxx - Model_AttributeDocRef.cxx - Model_Events.cxx + Model_Application.cpp + Model_Document.cpp + Model_PluginManager.cpp + Model_Data.cpp + Model_Iterator.cpp + Model_AttributeDouble.cpp + Model_AttributeDocRef.cpp + Model_Events.cpp ) ADD_DEFINITIONS(-DMODEL_EXPORTS ${CAS_DEFINITIONS}) diff --git a/src/Model/Model_Application.cpp b/src/Model/Model_Application.cpp new file mode 100644 index 000000000..40704967e --- /dev/null +++ b/src/Model/Model_Application.cpp @@ -0,0 +1,61 @@ +// File: Model_Application.cxx +// Created: Fri Sep 2 2011 +// Author: Mikhail PONIKAROV + +#include +#include + +IMPLEMENT_STANDARD_HANDLE(Model_Application, TDocStd_Application) +IMPLEMENT_STANDARD_RTTIEXT(Model_Application, TDocStd_Application) + +using namespace std; + +static Handle_Model_Application TheApplication = new Model_Application; + +//======================================================================= +Handle(Model_Application) Model_Application::getApplication() +{ + return TheApplication; +} + +//======================================================================= +const std::shared_ptr& Model_Application::getDocument(string theDocID) +{ + if (myDocs.find(theDocID) != myDocs.end()) + return myDocs[theDocID]; + + std::shared_ptr aNew(new Model_Document(theDocID)); + myDocs[theDocID] = aNew; + return myDocs[theDocID]; +} + +void Model_Application::deleteDocument(string theDocID) +{ + myDocs.erase(theDocID); +} + +//======================================================================= +bool Model_Application::hasDocument(std::string theDocID) +{ + return myDocs.find(theDocID) != myDocs.end(); +} + +//======================================================================= +Model_Application::Model_Application() +{ + // store handle to the application to avoid nullification + static Handle(Model_Application) TheKeepHandle; + TheKeepHandle = this; +} + +//======================================================================= +void Model_Application::Formats(TColStd_SequenceOfExtendedString& theFormats) +{ + theFormats.Append(TCollection_ExtendedString("BinOcaf")); // standard binary schema +} + +//======================================================================= +Standard_CString Model_Application::ResourcesName() +{ + return Standard_CString("Standard"); +} diff --git a/src/Model/Model_Application.cxx b/src/Model/Model_Application.cxx deleted file mode 100644 index 40704967e..000000000 --- a/src/Model/Model_Application.cxx +++ /dev/null @@ -1,61 +0,0 @@ -// File: Model_Application.cxx -// Created: Fri Sep 2 2011 -// Author: Mikhail PONIKAROV - -#include -#include - -IMPLEMENT_STANDARD_HANDLE(Model_Application, TDocStd_Application) -IMPLEMENT_STANDARD_RTTIEXT(Model_Application, TDocStd_Application) - -using namespace std; - -static Handle_Model_Application TheApplication = new Model_Application; - -//======================================================================= -Handle(Model_Application) Model_Application::getApplication() -{ - return TheApplication; -} - -//======================================================================= -const std::shared_ptr& Model_Application::getDocument(string theDocID) -{ - if (myDocs.find(theDocID) != myDocs.end()) - return myDocs[theDocID]; - - std::shared_ptr aNew(new Model_Document(theDocID)); - myDocs[theDocID] = aNew; - return myDocs[theDocID]; -} - -void Model_Application::deleteDocument(string theDocID) -{ - myDocs.erase(theDocID); -} - -//======================================================================= -bool Model_Application::hasDocument(std::string theDocID) -{ - return myDocs.find(theDocID) != myDocs.end(); -} - -//======================================================================= -Model_Application::Model_Application() -{ - // store handle to the application to avoid nullification - static Handle(Model_Application) TheKeepHandle; - TheKeepHandle = this; -} - -//======================================================================= -void Model_Application::Formats(TColStd_SequenceOfExtendedString& theFormats) -{ - theFormats.Append(TCollection_ExtendedString("BinOcaf")); // standard binary schema -} - -//======================================================================= -Standard_CString Model_Application::ResourcesName() -{ - return Standard_CString("Standard"); -} diff --git a/src/Model/Model_AttributeDocRef.cpp b/src/Model/Model_AttributeDocRef.cpp new file mode 100644 index 000000000..07712d0b6 --- /dev/null +++ b/src/Model/Model_AttributeDocRef.cpp @@ -0,0 +1,31 @@ +// File: ModelAPI_AttributeDocRef.cxx +// Created: 2 Apr 2014 +// Author: Mikhail PONIKAROV + +#include "Model_AttributeDocRef.h" +#include "Model_Application.h" + +using namespace std; + +void Model_AttributeDocRef::setValue(std::shared_ptr theDoc) +{ + myComment->Set(TCollection_ExtendedString(theDoc->id().c_str())); +} + +std::shared_ptr Model_AttributeDocRef::value() +{ + if (myComment->Get().Length()) + return Model_Application::getApplication()->getDocument( + TCollection_AsciiString(myComment->Get()).ToCString()); + // not initialized + return std::shared_ptr(); +} + +Model_AttributeDocRef::Model_AttributeDocRef(TDF_Label& theLabel) +{ + // check the attribute could be already presented in this doc (after load document) + if (!theLabel.FindAttribute(TDataStd_Comment::GetID(), myComment)) { + // create attribute: not initialized by value yet, just empty string + myComment = TDataStd_Comment::Set(theLabel, ""); + } +} diff --git a/src/Model/Model_AttributeDocRef.cxx b/src/Model/Model_AttributeDocRef.cxx deleted file mode 100644 index 07712d0b6..000000000 --- a/src/Model/Model_AttributeDocRef.cxx +++ /dev/null @@ -1,31 +0,0 @@ -// File: ModelAPI_AttributeDocRef.cxx -// Created: 2 Apr 2014 -// Author: Mikhail PONIKAROV - -#include "Model_AttributeDocRef.h" -#include "Model_Application.h" - -using namespace std; - -void Model_AttributeDocRef::setValue(std::shared_ptr theDoc) -{ - myComment->Set(TCollection_ExtendedString(theDoc->id().c_str())); -} - -std::shared_ptr Model_AttributeDocRef::value() -{ - if (myComment->Get().Length()) - return Model_Application::getApplication()->getDocument( - TCollection_AsciiString(myComment->Get()).ToCString()); - // not initialized - return std::shared_ptr(); -} - -Model_AttributeDocRef::Model_AttributeDocRef(TDF_Label& theLabel) -{ - // check the attribute could be already presented in this doc (after load document) - if (!theLabel.FindAttribute(TDataStd_Comment::GetID(), myComment)) { - // create attribute: not initialized by value yet, just empty string - myComment = TDataStd_Comment::Set(theLabel, ""); - } -} diff --git a/src/Model/Model_AttributeDouble.cpp b/src/Model/Model_AttributeDouble.cpp new file mode 100644 index 000000000..23e15f0e1 --- /dev/null +++ b/src/Model/Model_AttributeDouble.cpp @@ -0,0 +1,26 @@ +// File: ModelAPI_AttributeDouble.cxx +// Created: 2 Apr 2014 +// Author: Mikhail PONIKAROV + +#include "Model_AttributeDouble.h" + +using namespace std; + +void Model_AttributeDouble::setValue(const double theValue) +{ + myReal->Set(theValue); +} + +double Model_AttributeDouble::value() +{ + return myReal->Get(); +} + +Model_AttributeDouble::Model_AttributeDouble(TDF_Label& theLabel) +{ + // check the attribute could be already presented in this doc (after load document) + if (!theLabel.FindAttribute(TDataStd_Real::GetID(), myReal)) { + // create attribute: not initialized by value yet, just zero + myReal = TDataStd_Real::Set(theLabel, 0.); + } +} diff --git a/src/Model/Model_AttributeDouble.cxx b/src/Model/Model_AttributeDouble.cxx deleted file mode 100644 index 23e15f0e1..000000000 --- a/src/Model/Model_AttributeDouble.cxx +++ /dev/null @@ -1,26 +0,0 @@ -// File: ModelAPI_AttributeDouble.cxx -// Created: 2 Apr 2014 -// Author: Mikhail PONIKAROV - -#include "Model_AttributeDouble.h" - -using namespace std; - -void Model_AttributeDouble::setValue(const double theValue) -{ - myReal->Set(theValue); -} - -double Model_AttributeDouble::value() -{ - return myReal->Get(); -} - -Model_AttributeDouble::Model_AttributeDouble(TDF_Label& theLabel) -{ - // check the attribute could be already presented in this doc (after load document) - if (!theLabel.FindAttribute(TDataStd_Real::GetID(), myReal)) { - // create attribute: not initialized by value yet, just zero - myReal = TDataStd_Real::Set(theLabel, 0.); - } -} diff --git a/src/Model/Model_Data.cpp b/src/Model/Model_Data.cpp new file mode 100644 index 000000000..a9ac29bf0 --- /dev/null +++ b/src/Model/Model_Data.cpp @@ -0,0 +1,77 @@ +// File: Model_Data.hxx +// Created: 21 Mar 2014 +// Author: Mikhail PONIKAROV + +#include +#include +#include +#include + +using namespace std; + +Model_Data::Model_Data() +{ +} + +void Model_Data::setLabel(TDF_Label& theLab) +{ + myLab = theLab; +} + +string Model_Data::getName() +{ + Handle(TDataStd_Name) aName; + if (myLab.FindAttribute(TDataStd_Name::GetID(), aName)) + return string(TCollection_AsciiString(aName->Get()).ToCString()); + return ""; // not defined +} + +void Model_Data::setName(string theName) +{ + TDataStd_Name::Set(myLab, theName.c_str()); +} + +void Model_Data::addAttribute(string theID, string theAttrType) +{ + TDF_Label anAttrLab = myLab.FindChild(myAttrs.size() + 1); + ModelAPI_Attribute* anAttr = 0; + if (theAttrType == ModelAPI_AttributeDocRef::type()) + anAttr = new Model_AttributeDocRef(anAttrLab); + else if (theAttrType == ModelAPI_AttributeDouble::type()) + anAttr = new Model_AttributeDouble(anAttrLab); + + if (anAttr) + myAttrs[theID] = std::shared_ptr(anAttr); + else + ; // TODO: generate error on unknown attribute request and/or add mechanism for customization +} + +shared_ptr Model_Data::docRef(const string theID) +{ + map >::iterator aFound = myAttrs.find(theID); + if (aFound == myAttrs.end()) { + // TODO: generate error on unknown attribute request and/or add mechanism for customization + return std::shared_ptr(); + } + shared_ptr aRes = + dynamic_pointer_cast(aFound->second); + if (!aRes) { + // TODO: generate error on invalid attribute type request + } + return aRes; +} + +shared_ptr Model_Data::real(const string theID) +{ + map >::iterator aFound = myAttrs.find(theID); + if (aFound == myAttrs.end()) { + // TODO: generate error on unknown attribute request and/or add mechanism for customization + return std::shared_ptr(); + } + shared_ptr aRes = + dynamic_pointer_cast(aFound->second); + if (!aRes) { + // TODO: generate error on invalid attribute type request + } + return aRes; +} diff --git a/src/Model/Model_Data.cxx b/src/Model/Model_Data.cxx deleted file mode 100644 index a9ac29bf0..000000000 --- a/src/Model/Model_Data.cxx +++ /dev/null @@ -1,77 +0,0 @@ -// File: Model_Data.hxx -// Created: 21 Mar 2014 -// Author: Mikhail PONIKAROV - -#include -#include -#include -#include - -using namespace std; - -Model_Data::Model_Data() -{ -} - -void Model_Data::setLabel(TDF_Label& theLab) -{ - myLab = theLab; -} - -string Model_Data::getName() -{ - Handle(TDataStd_Name) aName; - if (myLab.FindAttribute(TDataStd_Name::GetID(), aName)) - return string(TCollection_AsciiString(aName->Get()).ToCString()); - return ""; // not defined -} - -void Model_Data::setName(string theName) -{ - TDataStd_Name::Set(myLab, theName.c_str()); -} - -void Model_Data::addAttribute(string theID, string theAttrType) -{ - TDF_Label anAttrLab = myLab.FindChild(myAttrs.size() + 1); - ModelAPI_Attribute* anAttr = 0; - if (theAttrType == ModelAPI_AttributeDocRef::type()) - anAttr = new Model_AttributeDocRef(anAttrLab); - else if (theAttrType == ModelAPI_AttributeDouble::type()) - anAttr = new Model_AttributeDouble(anAttrLab); - - if (anAttr) - myAttrs[theID] = std::shared_ptr(anAttr); - else - ; // TODO: generate error on unknown attribute request and/or add mechanism for customization -} - -shared_ptr Model_Data::docRef(const string theID) -{ - map >::iterator aFound = myAttrs.find(theID); - if (aFound == myAttrs.end()) { - // TODO: generate error on unknown attribute request and/or add mechanism for customization - return std::shared_ptr(); - } - shared_ptr aRes = - dynamic_pointer_cast(aFound->second); - if (!aRes) { - // TODO: generate error on invalid attribute type request - } - return aRes; -} - -shared_ptr Model_Data::real(const string theID) -{ - map >::iterator aFound = myAttrs.find(theID); - if (aFound == myAttrs.end()) { - // TODO: generate error on unknown attribute request and/or add mechanism for customization - return std::shared_ptr(); - } - shared_ptr aRes = - dynamic_pointer_cast(aFound->second); - if (!aRes) { - // TODO: generate error on invalid attribute type request - } - return aRes; -} diff --git a/src/Model/Model_Document.cpp b/src/Model/Model_Document.cpp new file mode 100644 index 000000000..22641f4b3 --- /dev/null +++ b/src/Model/Model_Document.cpp @@ -0,0 +1,445 @@ +// File: Model_Document.cxx +// Created: 28 Feb 2014 +// Author: Mikhail PONIKAROV + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static const int UNDO_LIMIT = 10; // number of possible undo operations + +static const int TAG_GENERAL = 1; // general properties tag +static const int TAG_OBJECTS = 2; // tag of the objects sub-tree (Root for Model_DatasMgr) +static const int TAG_HISTORY = 3; // tag of the history sub-tree (Root for Model_History) + +using namespace std; + +bool Model_Document::load(const char* theFileName) +{ + bool myIsError = Standard_False; + /* + TCollection_ExtendedString aPath ((const Standard_CString)theFileName); + PCDM_ReaderStatus aStatus = (PCDM_ReaderStatus) -1; + try + { + Handle(TDocStd_Document) aDoc = this; + aStatus = Model_Application::GetApplication()->Open(aPath, aDoc); + } + catch (Standard_Failure) + {} + myIsError = aStatus != PCDM_RS_OK; + if (myIsError) + { + switch (aStatus) + { + case PCDM_RS_UnknownDocument: cout<<"OCAFApp_Appl_RUnknownDocument"<SaveAs (aDoc, aPath); + } + catch (Standard_Failure) { + Handle(Standard_Failure) aFail = Standard_Failure::Caught(); + cout<<"OCAFApp_Engine:save Error: "<GetMessageString()<::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + subDocument(*aSubIter)->close(); + mySubs.clear(); + // close this + myDoc->Close(); + Model_Application::getApplication()->deleteDocument(myID); +} + +void Model_Document::startOperation() +{ + // new command for this + myDoc->NewCommand(); + // new command for all subs + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + subDocument(*aSubIter)->startOperation(); +} + +void Model_Document::finishOperation() +{ + // returns false if delta is empty and no transaction was made + myIsEmptyTr[myTransactionsAfterSave] = !myDoc->CommitCommand(); + myTransactionsAfterSave++; + // finish for all subs + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + subDocument(*aSubIter)->finishOperation(); +} + +void Model_Document::abortOperation() +{ + myDoc->AbortCommand(); + // abort for all subs + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + subDocument(*aSubIter)->abortOperation(); +} + +bool Model_Document::isOperation() +{ + // operation is opened for all documents: no need to check subs + return myDoc->HasOpenCommand() == Standard_True ; +} + +bool Model_Document::isModified() +{ + // is modified if at least one operation was commited and not undoed + return myTransactionsAfterSave > 0; +} + +bool Model_Document::canUndo() +{ + if (myDoc->GetAvailableUndos() > 0) + return true; + // check other subs contains operation that can be undoed + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + if (subDocument(*aSubIter)->canUndo()) + return true; + return false; +} + +void Model_Document::undo() +{ + myTransactionsAfterSave--; + if (!myIsEmptyTr[myTransactionsAfterSave]) + myDoc->Undo(); + synchronizeFeatures(); + // undo for all subs + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + subDocument(*aSubIter)->undo(); +} + +bool Model_Document::canRedo() +{ + if (myDoc->GetAvailableRedos() > 0) + return true; + // check other subs contains operation that can be redoed + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + if (subDocument(*aSubIter)->canRedo()) + return true; + return false; +} + +void Model_Document::redo() +{ + if (!myIsEmptyTr[myTransactionsAfterSave]) + myDoc->Redo(); + myTransactionsAfterSave++; + synchronizeFeatures(); + // redo for all subs + set::iterator aSubIter = mySubs.begin(); + for(; aSubIter != mySubs.end(); aSubIter++) + subDocument(*aSubIter)->redo(); +} + +shared_ptr Model_Document::addFeature(string theID) +{ + shared_ptr aFeature = ModelAPI_PluginManager::get()->createFeature(theID); + if (aFeature) { + dynamic_pointer_cast(aFeature->documentToAdd())->addFeature(aFeature); + } else { + // TODO: generate error that feature is not created + } + return aFeature; +} + +void Model_Document::addFeature(const std::shared_ptr theFeature) +{ + const std::string& aGroup = theFeature->getGroup(); + TDF_Label aGroupLab = groupLabel(aGroup); + TDF_Label anObjLab = aGroupLab.NewChild(); + std::shared_ptr aData(new Model_Data); + aData->setLabel(anObjLab); + shared_ptr aThis = Model_Application::getApplication()->getDocument(myID); + aData->setDocument(aThis); + theFeature->setData(aData); + setUniqueName(theFeature); + theFeature->initAttributes(); + // keep the feature ID to restore document later correctly + TDataStd_Comment::Set(anObjLab, theFeature->getKind().c_str()); + // put index of the feature in the group in the tree + TDataStd_Integer::Set(anObjLab, myFeatures[aGroup].size()); + myFeatures[aGroup].push_back(theFeature); + + // event: feature is added + static Event_ID anEvent = Event_Loop::eventByName(EVENT_FEATURE_CREATED); + ModelAPI_FeatureUpdatedMessage aMsg(aThis, theFeature, anEvent); + Event_Loop::loop()->send(aMsg); +} + +shared_ptr Model_Document::feature(TDF_Label& theLabel) +{ + Handle(TDataStd_Integer) aFeatureIndex; + if (theLabel.FindAttribute(TDataStd_Integer::GetID(), aFeatureIndex)) { + Handle(TDataStd_Comment) aGroupID; + if (theLabel.Father().FindAttribute(TDataStd_Comment::GetID(), aGroupID)) { + string aGroup = TCollection_AsciiString(aGroupID->Get()).ToCString(); + return myFeatures[aGroup][aFeatureIndex->Get()]; + } + } + return std::shared_ptr(); // not found +} + +int Model_Document::featureIndex(shared_ptr theFeature) +{ + if (theFeature->data()->document().get() != this) { + return theFeature->data()->document()->featureIndex(theFeature); + } + shared_ptr aData = dynamic_pointer_cast(theFeature->data()); + Handle(TDataStd_Integer) aFeatureIndex; + if (aData->label().FindAttribute(TDataStd_Integer::GetID(), aFeatureIndex)) { + return aFeatureIndex->Get(); + } + return -1; // not found +} + +shared_ptr Model_Document::subDocument(string theDocID) +{ + // just store sub-document identifier here to manage it later + if (mySubs.find(theDocID) == mySubs.end()) + mySubs.insert(theDocID); + return Model_Application::getApplication()->getDocument(theDocID); +} + +shared_ptr Model_Document::featuresIterator(const string theGroup) +{ + shared_ptr aThis(Model_Application::getApplication()->getDocument(myID)); + // create an empty iterator for not existing group + // (to avoidance of attributes management outside the transaction) + if (myGroups.find(theGroup) == myGroups.end()) + return shared_ptr(new Model_Iterator()); + return shared_ptr(new Model_Iterator(aThis, groupLabel(theGroup))); +} + +shared_ptr Model_Document::feature(const string& theGroupID, const int theIndex) +{ + // TODO: optimize this method + shared_ptr anIter = featuresIterator(theGroupID); + for(int a = 0; a != theIndex && anIter->more(); anIter->next()) a++; + return anIter->more() ? anIter->current() : shared_ptr(); +} + +const vector& Model_Document::getGroups() const +{ + return myGroupsNames; +} + +Model_Document::Model_Document(const std::string theID) + : myID(theID), myDoc(new TDocStd_Document("BinOcaf")) // binary OCAF format +{ + myDoc->SetUndoLimit(UNDO_LIMIT); + myTransactionsAfterSave = 0; + // to avoid creation of tag outside of the transaction (by iterator, for example) + /* + if (!myDoc->Main().FindChild(TAG_OBJECTS).IsAttribute(TDF_TagSource::GetID())) + TDataStd_Comment::Set(myDoc->Main().FindChild(TAG_OBJECTS).NewChild(), ""); + */ +} + +TDF_Label Model_Document::groupLabel(const string theGroup) +{ + if (myGroups.find(theGroup) == myGroups.end()) { + myGroups[theGroup] = myDoc->Main().FindChild(TAG_OBJECTS).NewChild(); + myGroupsNames.push_back(theGroup); + // set to the group label the group idntifier to restore on "open" + TDataStd_Comment::Set(myGroups[theGroup], theGroup.c_str()); + myFeatures[theGroup] = vector >(); + } + return myGroups[theGroup]; +} + +void Model_Document::setUniqueName( + shared_ptr theFeature) +{ + // first count all objects of such kind to start with index = count + 1 + int aNumObjects = 0; + shared_ptr anIter = featuresIterator(theFeature->getGroup()); + for(; anIter->more(); anIter->next()) { + if (anIter->currentKind() == theFeature->getKind()) + aNumObjects++; + } + // generate candidate name + stringstream aNameStream; + aNameStream<getKind()<<"_"<getGroup()); anIter->more();) { + if (anIter->currentName() == aName) { + aNumObjects++; + stringstream aNameStream; + aNameStream<getKind()<<"_"<getGroup()); + } else anIter->next(); + } + + theFeature->data()->setName(aName); +} + +void Model_Document::synchronizeFeatures() +{ + shared_ptr aThis = Model_Application::getApplication()->getDocument(myID); + // iterate groups labels + TDF_ChildIDIterator aGroupsIter(myDoc->Main().FindChild(TAG_OBJECTS), + TDataStd_Comment::GetID(), Standard_False); + vector::iterator aGroupNamesIter = myGroupsNames.begin(); + for(; aGroupsIter.More() && aGroupNamesIter != myGroupsNames.end(); + aGroupsIter.Next(), aGroupNamesIter++) { + string aGroupName = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( + aGroupsIter.Value())->Get()).ToCString(); + if (*aGroupNamesIter != aGroupName) + break; // all since there is a change this must be recreated from scratch + } + // delete all groups left after the data model groups iteration + while(aGroupNamesIter != myGroupsNames.end()) { + string aGroupName = *aGroupNamesIter; + myFeatures.erase(aGroupName); + myGroups.erase(aGroupName); + aGroupNamesIter = myGroupsNames.erase(aGroupNamesIter); + // say that features were deleted from group + ModelAPI_FeatureDeletedMessage aMsg(aThis, aGroupName); + Event_Loop::loop()->send(aMsg); + } + // create new groups basing on the following data model update + for(; aGroupsIter.More(); aGroupsIter.Next()) { + string aGroupName = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( + aGroupsIter.Value())->Get()).ToCString(); + myGroupsNames.push_back(aGroupName); + myGroups[aGroupName] = aGroupsIter.Value()->Label(); + myFeatures[aGroupName] = vector >(); + } + // update features group by group + aGroupsIter.Initialize(myDoc->Main().FindChild(TAG_OBJECTS), + TDataStd_Comment::GetID(), Standard_False); + for(; aGroupsIter.More(); aGroupsIter.Next()) { + string aGroupName = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( + aGroupsIter.Value())->Get()).ToCString(); + // iterate features in internal container + vector >& aFeatures = myFeatures[aGroupName]; + vector >::iterator aFIter = aFeatures.begin(); + // and in parallel iterate labels of features + TDF_ChildIDIterator aFLabIter( + aGroupsIter.Value()->Label(), TDataStd_Comment::GetID(), Standard_False); + while(aFIter != aFeatures.end() || aFLabIter.More()) { + static const int INFINITE_TAG = INT_MAX; // no label means that it exists somwhere in infinite + int aFeatureTag = INFINITE_TAG; + if (aFIter != aFeatures.end()) { // existing tag for feature + shared_ptr aData = dynamic_pointer_cast((*aFIter)->data()); + aFeatureTag = aData->label().Tag(); + } + int aDSTag = INFINITE_TAG; + if (aFLabIter.More()) { // next label in DS is existing + aDSTag = aFLabIter.Value()->Label().Tag(); + } + if (aDSTag > aFeatureTag) { // feature is removed + aFIter = aFeatures.erase(aFIter); + // event: model is updated + ModelAPI_FeatureDeletedMessage aMsg(aThis, aGroupName); + Event_Loop::loop()->send(aMsg); + } else if (aDSTag < aFeatureTag) { // a new feature is inserted + // create a feature + shared_ptr aFeature = ModelAPI_PluginManager::get()->createFeature( + TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( + aFLabIter.Value())->Get()).ToCString()); + + std::shared_ptr aData(new Model_Data); + TDF_Label aLab = aFLabIter.Value()->Label(); + aData->setLabel(aLab); + aData->setDocument(Model_Application::getApplication()->getDocument(myID)); + aFeature->setData(aData); + aFeature->initAttributes(); + // event: model is updated + static Event_ID anEvent = Event_Loop::eventByName(EVENT_FEATURE_CREATED); + ModelAPI_FeatureUpdatedMessage aMsg(aThis, aFeature, anEvent); + Event_Loop::loop()->send(aMsg); + + if (aFIter == aFeatures.end()) { + aFeatures.push_back(aFeature); + aFIter = aFeatures.end(); + } else { + aFIter++; + aFeatures.insert(aFIter, aFeature); + } + // feature for this label is added, so go to the next label + aFLabIter.Next(); + } else { // nothing is changed, both iterators are incremented + aFIter++; + aFLabIter.Next(); + } + } + } +} diff --git a/src/Model/Model_Document.cxx b/src/Model/Model_Document.cxx deleted file mode 100644 index 22641f4b3..000000000 --- a/src/Model/Model_Document.cxx +++ /dev/null @@ -1,445 +0,0 @@ -// File: Model_Document.cxx -// Created: 28 Feb 2014 -// Author: Mikhail PONIKAROV - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -static const int UNDO_LIMIT = 10; // number of possible undo operations - -static const int TAG_GENERAL = 1; // general properties tag -static const int TAG_OBJECTS = 2; // tag of the objects sub-tree (Root for Model_DatasMgr) -static const int TAG_HISTORY = 3; // tag of the history sub-tree (Root for Model_History) - -using namespace std; - -bool Model_Document::load(const char* theFileName) -{ - bool myIsError = Standard_False; - /* - TCollection_ExtendedString aPath ((const Standard_CString)theFileName); - PCDM_ReaderStatus aStatus = (PCDM_ReaderStatus) -1; - try - { - Handle(TDocStd_Document) aDoc = this; - aStatus = Model_Application::GetApplication()->Open(aPath, aDoc); - } - catch (Standard_Failure) - {} - myIsError = aStatus != PCDM_RS_OK; - if (myIsError) - { - switch (aStatus) - { - case PCDM_RS_UnknownDocument: cout<<"OCAFApp_Appl_RUnknownDocument"<SaveAs (aDoc, aPath); - } - catch (Standard_Failure) { - Handle(Standard_Failure) aFail = Standard_Failure::Caught(); - cout<<"OCAFApp_Engine:save Error: "<GetMessageString()<::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - subDocument(*aSubIter)->close(); - mySubs.clear(); - // close this - myDoc->Close(); - Model_Application::getApplication()->deleteDocument(myID); -} - -void Model_Document::startOperation() -{ - // new command for this - myDoc->NewCommand(); - // new command for all subs - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - subDocument(*aSubIter)->startOperation(); -} - -void Model_Document::finishOperation() -{ - // returns false if delta is empty and no transaction was made - myIsEmptyTr[myTransactionsAfterSave] = !myDoc->CommitCommand(); - myTransactionsAfterSave++; - // finish for all subs - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - subDocument(*aSubIter)->finishOperation(); -} - -void Model_Document::abortOperation() -{ - myDoc->AbortCommand(); - // abort for all subs - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - subDocument(*aSubIter)->abortOperation(); -} - -bool Model_Document::isOperation() -{ - // operation is opened for all documents: no need to check subs - return myDoc->HasOpenCommand() == Standard_True ; -} - -bool Model_Document::isModified() -{ - // is modified if at least one operation was commited and not undoed - return myTransactionsAfterSave > 0; -} - -bool Model_Document::canUndo() -{ - if (myDoc->GetAvailableUndos() > 0) - return true; - // check other subs contains operation that can be undoed - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - if (subDocument(*aSubIter)->canUndo()) - return true; - return false; -} - -void Model_Document::undo() -{ - myTransactionsAfterSave--; - if (!myIsEmptyTr[myTransactionsAfterSave]) - myDoc->Undo(); - synchronizeFeatures(); - // undo for all subs - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - subDocument(*aSubIter)->undo(); -} - -bool Model_Document::canRedo() -{ - if (myDoc->GetAvailableRedos() > 0) - return true; - // check other subs contains operation that can be redoed - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - if (subDocument(*aSubIter)->canRedo()) - return true; - return false; -} - -void Model_Document::redo() -{ - if (!myIsEmptyTr[myTransactionsAfterSave]) - myDoc->Redo(); - myTransactionsAfterSave++; - synchronizeFeatures(); - // redo for all subs - set::iterator aSubIter = mySubs.begin(); - for(; aSubIter != mySubs.end(); aSubIter++) - subDocument(*aSubIter)->redo(); -} - -shared_ptr Model_Document::addFeature(string theID) -{ - shared_ptr aFeature = ModelAPI_PluginManager::get()->createFeature(theID); - if (aFeature) { - dynamic_pointer_cast(aFeature->documentToAdd())->addFeature(aFeature); - } else { - // TODO: generate error that feature is not created - } - return aFeature; -} - -void Model_Document::addFeature(const std::shared_ptr theFeature) -{ - const std::string& aGroup = theFeature->getGroup(); - TDF_Label aGroupLab = groupLabel(aGroup); - TDF_Label anObjLab = aGroupLab.NewChild(); - std::shared_ptr aData(new Model_Data); - aData->setLabel(anObjLab); - shared_ptr aThis = Model_Application::getApplication()->getDocument(myID); - aData->setDocument(aThis); - theFeature->setData(aData); - setUniqueName(theFeature); - theFeature->initAttributes(); - // keep the feature ID to restore document later correctly - TDataStd_Comment::Set(anObjLab, theFeature->getKind().c_str()); - // put index of the feature in the group in the tree - TDataStd_Integer::Set(anObjLab, myFeatures[aGroup].size()); - myFeatures[aGroup].push_back(theFeature); - - // event: feature is added - static Event_ID anEvent = Event_Loop::eventByName(EVENT_FEATURE_CREATED); - ModelAPI_FeatureUpdatedMessage aMsg(aThis, theFeature, anEvent); - Event_Loop::loop()->send(aMsg); -} - -shared_ptr Model_Document::feature(TDF_Label& theLabel) -{ - Handle(TDataStd_Integer) aFeatureIndex; - if (theLabel.FindAttribute(TDataStd_Integer::GetID(), aFeatureIndex)) { - Handle(TDataStd_Comment) aGroupID; - if (theLabel.Father().FindAttribute(TDataStd_Comment::GetID(), aGroupID)) { - string aGroup = TCollection_AsciiString(aGroupID->Get()).ToCString(); - return myFeatures[aGroup][aFeatureIndex->Get()]; - } - } - return std::shared_ptr(); // not found -} - -int Model_Document::featureIndex(shared_ptr theFeature) -{ - if (theFeature->data()->document().get() != this) { - return theFeature->data()->document()->featureIndex(theFeature); - } - shared_ptr aData = dynamic_pointer_cast(theFeature->data()); - Handle(TDataStd_Integer) aFeatureIndex; - if (aData->label().FindAttribute(TDataStd_Integer::GetID(), aFeatureIndex)) { - return aFeatureIndex->Get(); - } - return -1; // not found -} - -shared_ptr Model_Document::subDocument(string theDocID) -{ - // just store sub-document identifier here to manage it later - if (mySubs.find(theDocID) == mySubs.end()) - mySubs.insert(theDocID); - return Model_Application::getApplication()->getDocument(theDocID); -} - -shared_ptr Model_Document::featuresIterator(const string theGroup) -{ - shared_ptr aThis(Model_Application::getApplication()->getDocument(myID)); - // create an empty iterator for not existing group - // (to avoidance of attributes management outside the transaction) - if (myGroups.find(theGroup) == myGroups.end()) - return shared_ptr(new Model_Iterator()); - return shared_ptr(new Model_Iterator(aThis, groupLabel(theGroup))); -} - -shared_ptr Model_Document::feature(const string& theGroupID, const int theIndex) -{ - // TODO: optimize this method - shared_ptr anIter = featuresIterator(theGroupID); - for(int a = 0; a != theIndex && anIter->more(); anIter->next()) a++; - return anIter->more() ? anIter->current() : shared_ptr(); -} - -const vector& Model_Document::getGroups() const -{ - return myGroupsNames; -} - -Model_Document::Model_Document(const std::string theID) - : myID(theID), myDoc(new TDocStd_Document("BinOcaf")) // binary OCAF format -{ - myDoc->SetUndoLimit(UNDO_LIMIT); - myTransactionsAfterSave = 0; - // to avoid creation of tag outside of the transaction (by iterator, for example) - /* - if (!myDoc->Main().FindChild(TAG_OBJECTS).IsAttribute(TDF_TagSource::GetID())) - TDataStd_Comment::Set(myDoc->Main().FindChild(TAG_OBJECTS).NewChild(), ""); - */ -} - -TDF_Label Model_Document::groupLabel(const string theGroup) -{ - if (myGroups.find(theGroup) == myGroups.end()) { - myGroups[theGroup] = myDoc->Main().FindChild(TAG_OBJECTS).NewChild(); - myGroupsNames.push_back(theGroup); - // set to the group label the group idntifier to restore on "open" - TDataStd_Comment::Set(myGroups[theGroup], theGroup.c_str()); - myFeatures[theGroup] = vector >(); - } - return myGroups[theGroup]; -} - -void Model_Document::setUniqueName( - shared_ptr theFeature) -{ - // first count all objects of such kind to start with index = count + 1 - int aNumObjects = 0; - shared_ptr anIter = featuresIterator(theFeature->getGroup()); - for(; anIter->more(); anIter->next()) { - if (anIter->currentKind() == theFeature->getKind()) - aNumObjects++; - } - // generate candidate name - stringstream aNameStream; - aNameStream<getKind()<<"_"<getGroup()); anIter->more();) { - if (anIter->currentName() == aName) { - aNumObjects++; - stringstream aNameStream; - aNameStream<getKind()<<"_"<getGroup()); - } else anIter->next(); - } - - theFeature->data()->setName(aName); -} - -void Model_Document::synchronizeFeatures() -{ - shared_ptr aThis = Model_Application::getApplication()->getDocument(myID); - // iterate groups labels - TDF_ChildIDIterator aGroupsIter(myDoc->Main().FindChild(TAG_OBJECTS), - TDataStd_Comment::GetID(), Standard_False); - vector::iterator aGroupNamesIter = myGroupsNames.begin(); - for(; aGroupsIter.More() && aGroupNamesIter != myGroupsNames.end(); - aGroupsIter.Next(), aGroupNamesIter++) { - string aGroupName = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( - aGroupsIter.Value())->Get()).ToCString(); - if (*aGroupNamesIter != aGroupName) - break; // all since there is a change this must be recreated from scratch - } - // delete all groups left after the data model groups iteration - while(aGroupNamesIter != myGroupsNames.end()) { - string aGroupName = *aGroupNamesIter; - myFeatures.erase(aGroupName); - myGroups.erase(aGroupName); - aGroupNamesIter = myGroupsNames.erase(aGroupNamesIter); - // say that features were deleted from group - ModelAPI_FeatureDeletedMessage aMsg(aThis, aGroupName); - Event_Loop::loop()->send(aMsg); - } - // create new groups basing on the following data model update - for(; aGroupsIter.More(); aGroupsIter.Next()) { - string aGroupName = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( - aGroupsIter.Value())->Get()).ToCString(); - myGroupsNames.push_back(aGroupName); - myGroups[aGroupName] = aGroupsIter.Value()->Label(); - myFeatures[aGroupName] = vector >(); - } - // update features group by group - aGroupsIter.Initialize(myDoc->Main().FindChild(TAG_OBJECTS), - TDataStd_Comment::GetID(), Standard_False); - for(; aGroupsIter.More(); aGroupsIter.Next()) { - string aGroupName = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( - aGroupsIter.Value())->Get()).ToCString(); - // iterate features in internal container - vector >& aFeatures = myFeatures[aGroupName]; - vector >::iterator aFIter = aFeatures.begin(); - // and in parallel iterate labels of features - TDF_ChildIDIterator aFLabIter( - aGroupsIter.Value()->Label(), TDataStd_Comment::GetID(), Standard_False); - while(aFIter != aFeatures.end() || aFLabIter.More()) { - static const int INFINITE_TAG = INT_MAX; // no label means that it exists somwhere in infinite - int aFeatureTag = INFINITE_TAG; - if (aFIter != aFeatures.end()) { // existing tag for feature - shared_ptr aData = dynamic_pointer_cast((*aFIter)->data()); - aFeatureTag = aData->label().Tag(); - } - int aDSTag = INFINITE_TAG; - if (aFLabIter.More()) { // next label in DS is existing - aDSTag = aFLabIter.Value()->Label().Tag(); - } - if (aDSTag > aFeatureTag) { // feature is removed - aFIter = aFeatures.erase(aFIter); - // event: model is updated - ModelAPI_FeatureDeletedMessage aMsg(aThis, aGroupName); - Event_Loop::loop()->send(aMsg); - } else if (aDSTag < aFeatureTag) { // a new feature is inserted - // create a feature - shared_ptr aFeature = ModelAPI_PluginManager::get()->createFeature( - TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast( - aFLabIter.Value())->Get()).ToCString()); - - std::shared_ptr aData(new Model_Data); - TDF_Label aLab = aFLabIter.Value()->Label(); - aData->setLabel(aLab); - aData->setDocument(Model_Application::getApplication()->getDocument(myID)); - aFeature->setData(aData); - aFeature->initAttributes(); - // event: model is updated - static Event_ID anEvent = Event_Loop::eventByName(EVENT_FEATURE_CREATED); - ModelAPI_FeatureUpdatedMessage aMsg(aThis, aFeature, anEvent); - Event_Loop::loop()->send(aMsg); - - if (aFIter == aFeatures.end()) { - aFeatures.push_back(aFeature); - aFIter = aFeatures.end(); - } else { - aFIter++; - aFeatures.insert(aFIter, aFeature); - } - // feature for this label is added, so go to the next label - aFLabIter.Next(); - } else { // nothing is changed, both iterators are incremented - aFIter++; - aFLabIter.Next(); - } - } - } -} diff --git a/src/Model/Model_Events.cpp b/src/Model/Model_Events.cpp new file mode 100644 index 000000000..dc35573eb --- /dev/null +++ b/src/Model/Model_Events.cpp @@ -0,0 +1,25 @@ +// File: Model_Events.cxx +// Created: 10 Apr 2014 +// Author: Mikhail PONIKAROV + +#include +#include + +ModelAPI_FeatureUpdatedMessage::ModelAPI_FeatureUpdatedMessage( + const std::shared_ptr& theDoc, + const std::shared_ptr& theFeature, const Event_ID& theEvent) + : Event_Message(theEvent, 0), myFeature(theFeature), myDoc(theDoc) +{} + +ModelAPI_FeatureDeletedMessage::ModelAPI_FeatureDeletedMessage( + const std::shared_ptr& theDoc, const std::string& theGroup) + : Event_Message(messageId(), 0), myDoc(theDoc), myGroup(theGroup) + +{ +} + +const Event_ID ModelAPI_FeatureDeletedMessage::messageId() +{ + static Event_ID MY_ID = Event_Loop::eventByName(EVENT_FEATURE_DELETED); + return MY_ID; +} diff --git a/src/Model/Model_Events.cxx b/src/Model/Model_Events.cxx deleted file mode 100644 index dc35573eb..000000000 --- a/src/Model/Model_Events.cxx +++ /dev/null @@ -1,25 +0,0 @@ -// File: Model_Events.cxx -// Created: 10 Apr 2014 -// Author: Mikhail PONIKAROV - -#include -#include - -ModelAPI_FeatureUpdatedMessage::ModelAPI_FeatureUpdatedMessage( - const std::shared_ptr& theDoc, - const std::shared_ptr& theFeature, const Event_ID& theEvent) - : Event_Message(theEvent, 0), myFeature(theFeature), myDoc(theDoc) -{} - -ModelAPI_FeatureDeletedMessage::ModelAPI_FeatureDeletedMessage( - const std::shared_ptr& theDoc, const std::string& theGroup) - : Event_Message(messageId(), 0), myDoc(theDoc), myGroup(theGroup) - -{ -} - -const Event_ID ModelAPI_FeatureDeletedMessage::messageId() -{ - static Event_ID MY_ID = Event_Loop::eventByName(EVENT_FEATURE_DELETED); - return MY_ID; -} diff --git a/src/Model/Model_Iterator.cpp b/src/Model/Model_Iterator.cpp new file mode 100644 index 000000000..001ebdff8 --- /dev/null +++ b/src/Model/Model_Iterator.cpp @@ -0,0 +1,67 @@ +// File: Model_Iterator.hxx +// Created: 1 Apr 2014 +// Author: Mikhail PONIKAROV + +#include "Model_Iterator.h" +#include "Model_Document.h" +#include "ModelAPI_Feature.h" +#include "Model_Data.h" +#include +#include + +using namespace std; + +void Model_Iterator::next() +{ + return myIter.Next(); +} + +bool Model_Iterator::more() +{ + return myIter.More() == Standard_True; +} + +shared_ptr Model_Iterator::current() +{ + TDF_Label aLab = myIter.Value()->Label(); + return myDoc->feature(aLab); +} + +string Model_Iterator::currentKind() +{ + return string(TCollection_AsciiString( + Handle(TDataStd_Comment)::DownCast(myIter.Value())->Get()).ToCString()); +} + +string Model_Iterator::currentName() +{ + TDF_Label aLab = myIter.Value()->Label(); + Handle(TDataStd_Name) aName; + if (aLab.FindAttribute(TDataStd_Name::GetID(), aName)) + return string(TCollection_AsciiString(aName->Get()).ToCString()); + return ""; // name is not found +} + +int Model_Iterator::numIterationsLeft() +{ + int aResult = 0; + TDF_ChildIDIterator aTempIter(myIter); + for(; aTempIter.More(); aTempIter.Next()) + aResult++; + return aResult; +} + +bool Model_Iterator::isEqual(std::shared_ptr theFeature) +{ + return (myIter.Value()->Label() == + dynamic_pointer_cast(theFeature->data())->label()) == Standard_True; + +} + +Model_Iterator::Model_Iterator() +{ +} + +Model_Iterator::Model_Iterator(std::shared_ptr theDoc, TDF_Label theLab) + : myDoc(theDoc), myIter(theLab, TDataStd_Comment::GetID(), Standard_False) +{} diff --git a/src/Model/Model_Iterator.cxx b/src/Model/Model_Iterator.cxx deleted file mode 100644 index 001ebdff8..000000000 --- a/src/Model/Model_Iterator.cxx +++ /dev/null @@ -1,67 +0,0 @@ -// File: Model_Iterator.hxx -// Created: 1 Apr 2014 -// Author: Mikhail PONIKAROV - -#include "Model_Iterator.h" -#include "Model_Document.h" -#include "ModelAPI_Feature.h" -#include "Model_Data.h" -#include -#include - -using namespace std; - -void Model_Iterator::next() -{ - return myIter.Next(); -} - -bool Model_Iterator::more() -{ - return myIter.More() == Standard_True; -} - -shared_ptr Model_Iterator::current() -{ - TDF_Label aLab = myIter.Value()->Label(); - return myDoc->feature(aLab); -} - -string Model_Iterator::currentKind() -{ - return string(TCollection_AsciiString( - Handle(TDataStd_Comment)::DownCast(myIter.Value())->Get()).ToCString()); -} - -string Model_Iterator::currentName() -{ - TDF_Label aLab = myIter.Value()->Label(); - Handle(TDataStd_Name) aName; - if (aLab.FindAttribute(TDataStd_Name::GetID(), aName)) - return string(TCollection_AsciiString(aName->Get()).ToCString()); - return ""; // name is not found -} - -int Model_Iterator::numIterationsLeft() -{ - int aResult = 0; - TDF_ChildIDIterator aTempIter(myIter); - for(; aTempIter.More(); aTempIter.Next()) - aResult++; - return aResult; -} - -bool Model_Iterator::isEqual(std::shared_ptr theFeature) -{ - return (myIter.Value()->Label() == - dynamic_pointer_cast(theFeature->data())->label()) == Standard_True; - -} - -Model_Iterator::Model_Iterator() -{ -} - -Model_Iterator::Model_Iterator(std::shared_ptr theDoc, TDF_Label theLab) - : myDoc(theDoc), myIter(theLab, TDataStd_Comment::GetID(), Standard_False) -{} diff --git a/src/Model/Model_PluginManager.cpp b/src/Model/Model_PluginManager.cpp new file mode 100644 index 000000000..eb279208a --- /dev/null +++ b/src/Model/Model_PluginManager.cpp @@ -0,0 +1,103 @@ +// File: Model_PluginManager.cxx +// Created: 20 Mar 2014 +// Author: Mikhail PONIKAROV + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +static Model_PluginManager* myImpl = new Model_PluginManager(); + +shared_ptr Model_PluginManager::createFeature(string theFeatureID) +{ + if (this != myImpl) return myImpl->createFeature(theFeatureID); + + LoadPluginsInfo(); + if (myPlugins.find(theFeatureID) != myPlugins.end()) { + if (myPluginObjs.find(myPlugins[theFeatureID]) == myPluginObjs.end()) { + // load plugin library if not yet done + myCurrentPluginName = myPlugins[theFeatureID]; + loadLibrary(myCurrentPluginName); + } + if (myPluginObjs.find(myCurrentPluginName) != myPluginObjs.end()) { + std::shared_ptr aCreated = + myPluginObjs[myCurrentPluginName]->createFeature(theFeatureID); + return aCreated; + } + } + + return std::shared_ptr(); // return nothing +} + +std::shared_ptr Model_PluginManager::rootDocument() +{ + return std::shared_ptr( + Model_Application::getApplication()->getDocument("root")); +} + +bool Model_PluginManager::hasRootDocument() +{ + return Model_Application::getApplication()->hasDocument("root"); +} + +shared_ptr Model_PluginManager::currentDocument() +{ + if (!myCurrentDoc) + myCurrentDoc = rootDocument(); + return myCurrentDoc; +} + +void Model_PluginManager::setCurrentDocument(shared_ptr theDoc) +{ + myCurrentDoc = theDoc; +} + +Model_PluginManager::Model_PluginManager() +{ + myPluginsInfoLoaded = false; + //TODO(sbh): Implement static method to extract event id [SEID] + static Event_ID aFeatureEvent = Event_Loop::eventByName("FeatureRegisterEvent"); + + ModelAPI_PluginManager::SetPluginManager(std::shared_ptr(this)); + // register the configuration reading listener + Event_Loop* aLoop = Event_Loop::loop(); + aLoop->registerListener(this, aFeatureEvent); +} + +void Model_PluginManager::processEvent(const Event_Message* theMessage) +{ + const Config_FeatureMessage* aMsg = + dynamic_cast(theMessage); + if (aMsg) { + // proccess the plugin info, load plugin + if (myPlugins.find(aMsg->id()) == myPlugins.end()) { + myPlugins[aMsg->id()] = aMsg->pluginLibrary(); + } + } + // plugins information was started to load, so, it will be loaded + myPluginsInfoLoaded = true; +} + +void Model_PluginManager::LoadPluginsInfo() +{ + if (myPluginsInfoLoaded) // nothing to do + return; + + // Read plugins information from XML files + Config_ModuleReader aXMLReader("FeatureRegisterEvent"); + aXMLReader.setAutoImport(true); + aXMLReader.readAll(); +} + +void Model_PluginManager::registerPlugin(ModelAPI_Plugin* thePlugin) +{ + myPluginObjs[myCurrentPluginName] = thePlugin; +} diff --git a/src/Model/Model_PluginManager.cxx b/src/Model/Model_PluginManager.cxx deleted file mode 100644 index eb279208a..000000000 --- a/src/Model/Model_PluginManager.cxx +++ /dev/null @@ -1,103 +0,0 @@ -// File: Model_PluginManager.cxx -// Created: 20 Mar 2014 -// Author: Mikhail PONIKAROV - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -static Model_PluginManager* myImpl = new Model_PluginManager(); - -shared_ptr Model_PluginManager::createFeature(string theFeatureID) -{ - if (this != myImpl) return myImpl->createFeature(theFeatureID); - - LoadPluginsInfo(); - if (myPlugins.find(theFeatureID) != myPlugins.end()) { - if (myPluginObjs.find(myPlugins[theFeatureID]) == myPluginObjs.end()) { - // load plugin library if not yet done - myCurrentPluginName = myPlugins[theFeatureID]; - loadLibrary(myCurrentPluginName); - } - if (myPluginObjs.find(myCurrentPluginName) != myPluginObjs.end()) { - std::shared_ptr aCreated = - myPluginObjs[myCurrentPluginName]->createFeature(theFeatureID); - return aCreated; - } - } - - return std::shared_ptr(); // return nothing -} - -std::shared_ptr Model_PluginManager::rootDocument() -{ - return std::shared_ptr( - Model_Application::getApplication()->getDocument("root")); -} - -bool Model_PluginManager::hasRootDocument() -{ - return Model_Application::getApplication()->hasDocument("root"); -} - -shared_ptr Model_PluginManager::currentDocument() -{ - if (!myCurrentDoc) - myCurrentDoc = rootDocument(); - return myCurrentDoc; -} - -void Model_PluginManager::setCurrentDocument(shared_ptr theDoc) -{ - myCurrentDoc = theDoc; -} - -Model_PluginManager::Model_PluginManager() -{ - myPluginsInfoLoaded = false; - //TODO(sbh): Implement static method to extract event id [SEID] - static Event_ID aFeatureEvent = Event_Loop::eventByName("FeatureRegisterEvent"); - - ModelAPI_PluginManager::SetPluginManager(std::shared_ptr(this)); - // register the configuration reading listener - Event_Loop* aLoop = Event_Loop::loop(); - aLoop->registerListener(this, aFeatureEvent); -} - -void Model_PluginManager::processEvent(const Event_Message* theMessage) -{ - const Config_FeatureMessage* aMsg = - dynamic_cast(theMessage); - if (aMsg) { - // proccess the plugin info, load plugin - if (myPlugins.find(aMsg->id()) == myPlugins.end()) { - myPlugins[aMsg->id()] = aMsg->pluginLibrary(); - } - } - // plugins information was started to load, so, it will be loaded - myPluginsInfoLoaded = true; -} - -void Model_PluginManager::LoadPluginsInfo() -{ - if (myPluginsInfoLoaded) // nothing to do - return; - - // Read plugins information from XML files - Config_ModuleReader aXMLReader("FeatureRegisterEvent"); - aXMLReader.setAutoImport(true); - aXMLReader.readAll(); -} - -void Model_PluginManager::registerPlugin(ModelAPI_Plugin* thePlugin) -{ - myPluginObjs[myCurrentPluginName] = thePlugin; -} diff --git a/src/ModelAPI/CMakeLists.txt b/src/ModelAPI/CMakeLists.txt index 075928b17..b5a717b02 100644 --- a/src/ModelAPI/CMakeLists.txt +++ b/src/ModelAPI/CMakeLists.txt @@ -16,7 +16,7 @@ SET(PROJECT_HEADERS ) SET(PROJECT_SOURCES - ModelAPI_PluginManager.cxx + ModelAPI_PluginManager.cpp ) ADD_DEFINITIONS(-DMODELAPI_EXPORTS) diff --git a/src/ModelAPI/ModelAPI_PluginManager.cpp b/src/ModelAPI/ModelAPI_PluginManager.cpp new file mode 100644 index 000000000..f3e7e9b9d --- /dev/null +++ b/src/ModelAPI/ModelAPI_PluginManager.cpp @@ -0,0 +1,94 @@ +// File: ModelAPI_PluginManager.hxx +// Created: 20 Mar 2014 +// Author: Mikhail PONIKAROV + +#include +// to avoid unresolved ModelAPI_Document() +#include +// to avoid unresolved ModelAPI_Feature() +#include +// to avoid unresolved ModelAPI_Data() +#include +// to avoid unresolved ModelAPI_Plugin() +#include +// to avoid unresolved ModelAPI_Iterator() +#include +// to avoid unresolved ModelAPI_Iterator() +#include +#include +#include +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +using namespace std; + +/// Converts library name to the operation system file name +string library(const string& theLibName); + +/// Manager that will be initialized from Model package, one per application +std::shared_ptr MY_MANAGER; + +ModelAPI_PluginManager::ModelAPI_PluginManager() +{ +} + +void ModelAPI_PluginManager::SetPluginManager( + std::shared_ptr theManager) +{ + MY_MANAGER = theManager; +} + +std::shared_ptr ModelAPI_PluginManager::get() +{ + if (!MY_MANAGER) { // import Model library that implements this interface of ModelAPI + loadLibrary("Model"); + } + return MY_MANAGER; +} + +string library(const string& theLibName) +{ + string aLibName = theLibName; + +#ifndef WIN32 + static string aLibExt( ".so" ); + if (aLibName.size() < 3 || aLibName.substr(0, 3) !="lib") + aLibName = "lib" + aLibName; +#else + static string aLibExt( ".dll" ); +#endif + + string anExt = aLibName.substr(aLibName.size() - 4); + + if ( anExt != aLibExt) + aLibName += aLibExt; + + return aLibName; +} + +// for debug purpose only (cerr), before the error management system is implemented +#include +void ModelAPI_PluginManager::loadLibrary(const string theLibName) +{ + string aFileName = library(theLibName); + if ( aFileName.empty() ) + { + cerr<<"Library "< -// to avoid unresolved ModelAPI_Document() -#include -// to avoid unresolved ModelAPI_Feature() -#include -// to avoid unresolved ModelAPI_Data() -#include -// to avoid unresolved ModelAPI_Plugin() -#include -// to avoid unresolved ModelAPI_Iterator() -#include -// to avoid unresolved ModelAPI_Iterator() -#include -#include -#include -#include - -#ifdef WIN32 -#include -#else -#include -#endif - -using namespace std; - -/// Converts library name to the operation system file name -string library(const string& theLibName); - -/// Manager that will be initialized from Model package, one per application -std::shared_ptr MY_MANAGER; - -ModelAPI_PluginManager::ModelAPI_PluginManager() -{ -} - -void ModelAPI_PluginManager::SetPluginManager( - std::shared_ptr theManager) -{ - MY_MANAGER = theManager; -} - -std::shared_ptr ModelAPI_PluginManager::get() -{ - if (!MY_MANAGER) { // import Model library that implements this interface of ModelAPI - loadLibrary("Model"); - } - return MY_MANAGER; -} - -string library(const string& theLibName) -{ - string aLibName = theLibName; - -#ifndef WIN32 - static string aLibExt( ".so" ); - if (aLibName.size() < 3 || aLibName.substr(0, 3) !="lib") - aLibName = "lib" + aLibName; -#else - static string aLibExt( ".dll" ); -#endif - - string anExt = aLibName.substr(aLibName.size() - 4); - - if ( anExt != aLibExt) - aLibName += aLibExt; - - return aLibName; -} - -// for debug purpose only (cerr), before the error management system is implemented -#include -void ModelAPI_PluginManager::loadLibrary(const string theLibName) -{ - string aFileName = library(theLibName); - if ( aFileName.empty() ) - { - cerr<<"Library "<addAttribute(PART_ATTR_DOC_REF, ModelAPI_AttributeDocRef::type()); +} + +void PartSetPlugin_Part::execute() +{ + shared_ptr aDocRef = data()->docRef(PART_ATTR_DOC_REF); + if (!aDocRef->value()) { // create a document if not yet created + shared_ptr aPartSetDoc = ModelAPI_PluginManager::get()->rootDocument(); + aDocRef->setValue(aPartSetDoc->subDocument(data()->getName())); + } +} + +shared_ptr PartSetPlugin_Part::documentToAdd() { + return ModelAPI_PluginManager::get()->rootDocument(); +} diff --git a/src/PartSetPlugin/PartSetPlugin_Part.cxx b/src/PartSetPlugin/PartSetPlugin_Part.cxx deleted file mode 100644 index 440eb6f25..000000000 --- a/src/PartSetPlugin/PartSetPlugin_Part.cxx +++ /dev/null @@ -1,34 +0,0 @@ -// File: PartSetPlugin_Part.cxx -// Created: 27 Mar 2014 -// Author: Mikhail PONIKAROV - -#include "PartSetPlugin_Part.h" -#include "ModelAPI_PluginManager.h" -#include "ModelAPI_Document.h" -#include "ModelAPI_Data.h" -#include "ModelAPI_Iterator.h" -#include "ModelAPI_AttributeDocRef.h" - -using namespace std; - -PartSetPlugin_Part::PartSetPlugin_Part() -{ -} - -void PartSetPlugin_Part::initAttributes() -{ - data()->addAttribute(PART_ATTR_DOC_REF, ModelAPI_AttributeDocRef::type()); -} - -void PartSetPlugin_Part::execute() -{ - shared_ptr aDocRef = data()->docRef(PART_ATTR_DOC_REF); - if (!aDocRef->value()) { // create a document if not yet created - shared_ptr aPartSetDoc = ModelAPI_PluginManager::get()->rootDocument(); - aDocRef->setValue(aPartSetDoc->subDocument(data()->getName())); - } -} - -shared_ptr PartSetPlugin_Part::documentToAdd() { - return ModelAPI_PluginManager::get()->rootDocument(); -} diff --git a/src/PartSetPlugin/PartSetPlugin_Plugin.cpp b/src/PartSetPlugin/PartSetPlugin_Plugin.cpp new file mode 100644 index 000000000..7ef8f66e1 --- /dev/null +++ b/src/PartSetPlugin/PartSetPlugin_Plugin.cpp @@ -0,0 +1,24 @@ +#include "PartSetPlugin_Plugin.h" +#include "PartSetPlugin_Part.h" +#include +#include + +using namespace std; + +// the only created instance of this plugin +static PartSetPlugin_Plugin* MY_INSTANCE = new PartSetPlugin_Plugin(); + +PartSetPlugin_Plugin::PartSetPlugin_Plugin() +{ + // register this plugin + ModelAPI_PluginManager::get()->registerPlugin(this); +} + +shared_ptr PartSetPlugin_Plugin::createFeature(string theFeatureID) +{ + if (theFeatureID == "Part") { + return shared_ptr(new PartSetPlugin_Part); + } + // feature of such kind is not found + return shared_ptr(); +} diff --git a/src/PartSetPlugin/PartSetPlugin_Plugin.cxx b/src/PartSetPlugin/PartSetPlugin_Plugin.cxx deleted file mode 100644 index 7ef8f66e1..000000000 --- a/src/PartSetPlugin/PartSetPlugin_Plugin.cxx +++ /dev/null @@ -1,24 +0,0 @@ -#include "PartSetPlugin_Plugin.h" -#include "PartSetPlugin_Part.h" -#include -#include - -using namespace std; - -// the only created instance of this plugin -static PartSetPlugin_Plugin* MY_INSTANCE = new PartSetPlugin_Plugin(); - -PartSetPlugin_Plugin::PartSetPlugin_Plugin() -{ - // register this plugin - ModelAPI_PluginManager::get()->registerPlugin(this); -} - -shared_ptr PartSetPlugin_Plugin::createFeature(string theFeatureID) -{ - if (theFeatureID == "Part") { - return shared_ptr(new PartSetPlugin_Part); - } - // feature of such kind is not found - return shared_ptr(); -} diff --git a/src/PyConsole/CMakeLists.txt b/src/PyConsole/CMakeLists.txt index 0ee0c35cf..4ba2ef124 100644 --- a/src/PyConsole/CMakeLists.txt +++ b/src/PyConsole/CMakeLists.txt @@ -25,13 +25,13 @@ SET(TEXT_RESOURCES ) # sources / static SET(PROJECT_SOURCES - PyConsole_Console.cxx - PyConsole_Editor.cxx - PyConsole_EnhEditor.cxx - PyConsole_EnhInterp.cxx - PyConsole_Event.cxx - PyConsole_Interp.cxx - PyConsole_Request.cxx + PyConsole_Console.cpp + PyConsole_Editor.cpp + PyConsole_EnhEditor.cpp + PyConsole_EnhInterp.cpp + PyConsole_Event.cpp + PyConsole_Interp.cpp + PyConsole_Request.cpp ) SET(PROJECT_LIBRARIES diff --git a/src/PyConsole/PyConsole_Console.cpp b/src/PyConsole/PyConsole_Console.cpp new file mode 100644 index 000000000..54391474f --- /dev/null +++ b/src/PyConsole/PyConsole_Console.cpp @@ -0,0 +1,341 @@ + +/*! + \class PyConsole_Console + \brief Python console widget. +*/ + +#include "PyConsole_Interp.h" /// !!! WARNING !!! THIS INCLUDE MUST BE VERY FIRST !!! +#include "PyConsole_Console.h" +#include "PyConsole_EnhEditor.h" +#include "PyConsole_EnhInterp.h" + +//#include + +#include +#include +#include +#include +#include +#include + +/*! + \brief Constructor. + + Creates new python console widget. + \param parent parent widget + \param interp python interpreter +*/ +PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* interp ) +: QWidget( parent ) +{ + // create python interpreter + myInterp = interp; + if ( !myInterp ) + myInterp = new PyConsole_Interp(); + + // initialize Python interpretator + myInterp->initialize(); + + // create editor console + QVBoxLayout* lay = new QVBoxLayout( this ); + lay->setMargin( 0 ); + myEditor = new PyConsole_Editor( myInterp, this ); + char* synchronous = getenv("PYTHON_CONSOLE_SYNC"); + if (synchronous && atoi(synchronous)) + { + myEditor->setIsSync(true); + } + myEditor->viewport()->installEventFilter( this ); + lay->addWidget( myEditor ); + + createActions(); +} + +/** + * Protected constructor. + */ +PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* i, PyConsole_Editor* e) + : QWidget (parent), myEditor(e), myInterp(i) +{} + +/*! + \brief Destructor. + + Does nothing for the moment. +*/ +PyConsole_Console::~PyConsole_Console() +{ +} + +/*! + \brief Execute python command in the interpreter. + \param command string with command and arguments +*/ +void PyConsole_Console::exec( const QString& command ) +{ + if ( myEditor ) + myEditor->exec( command ); +} + +/*! + \brief Execute python command in the interpreter + and wait until it is finished. + + Block execution of main application until the python command is executed. + \param command string with command and arguments +*/ +void PyConsole_Console::execAndWait( const QString& command ) +{ + if ( myEditor ) + myEditor->execAndWait( command ); +} + +/*! + \brief Get synchronous mode flag value. + + \sa setIsSync() + \return True if python console works in synchronous mode +*/ +bool PyConsole_Console::isSync() const +{ + return myEditor->isSync(); +} + +/*! + \brief Set synchronous mode flag value. + + In synhronous mode the Python commands are executed in the GUI thread + and the GUI is blocked until the command is finished. In the asynchronous + mode each Python command is executed in the separate thread that does not + block the main GUI loop. + + \param on synhronous mode flag +*/ +void PyConsole_Console::setIsSync( const bool on ) +{ + myEditor->setIsSync( on ); +} + +/*! + \brief Get suppress output flag value. + + \sa setIsSuppressOutput() + \return True if python console output is suppressed. +*/ +bool PyConsole_Console::isSuppressOutput() const +{ + return myEditor->isSuppressOutput(); +} + +/*! + \brief Set suppress output flag value. + + In case if suppress output flag is true, the python + console output suppressed. + + \param on suppress output flag +*/ +void PyConsole_Console::setIsSuppressOutput( const bool on ) +{ + myEditor->setIsSuppressOutput(on); +} + +/*! + \brief Get 'show banner' flag value. + + \sa setIsShowBanner() + \return \c true if python console shows banner +*/ +bool PyConsole_Console::isShowBanner() const +{ + return myEditor->isShowBanner(); +} + +/*! + \brief Set 'show banner' flag value. + + The banner is shown in the top of the python console window. + + \sa isShowBanner() + \param on 'show banner' flag +*/ +void PyConsole_Console::setIsShowBanner( const bool on ) +{ + myEditor->setIsShowBanner( on ); +} + +/*! + \brief Change the python console's font. + \param f new font +*/ +void PyConsole_Console::setFont( const QFont& f ) +{ + if( myEditor ) + myEditor->setFont( f ); +} + +/*! + \brief Get python console font. + \return current python console's font +*/ +QFont PyConsole_Console::font() const +{ + QFont res; + if( myEditor ) + res = myEditor->font(); + return res; +} + +/*! + \brief Event handler. + + Handles context menu request event. + + \param o object + \param e event + \return True if the event is processed and further processing should be stopped +*/ +bool PyConsole_Console::eventFilter( QObject* o, QEvent* e ) +{ + if ( o == myEditor->viewport() && e->type() == QEvent::ContextMenu ) + { + //contextMenuRequest( (QContextMenuEvent*)e ); + return true; + } + return QWidget::eventFilter( o, e ); +} + +/*! + \brief Create the context popup menu. + + Fill in the popup menu with the commands. + + \param menu context popup menu +*/ +void PyConsole_Console::contextMenuPopup( QMenu* menu ) +{ + if ( myEditor->isReadOnly() ) + return; + + menu->addAction( myActions[CopyId] ); + menu->addAction( myActions[PasteId] ); + menu->addAction( myActions[ClearId] ); + menu->addSeparator(); + menu->addAction( myActions[SelectAllId] ); + menu->addSeparator(); + menu->addAction( myActions[DumpCommandsId] ); + + //Qtx::simplifySeparators( menu ); + + updateActions(); +} + +/*! + \brief Set actions to be visible in the context popup menu. + + Actions, which IDs are set in \a flags parameter, will be shown in the + context popup menu. Other actions will not be shown. + + \param flags ORed together actions flags +*/ +void PyConsole_Console::setMenuActions( const int flags ) +{ + myActions[CopyId]->setVisible( flags & CopyId ); + myActions[PasteId]->setVisible( flags & PasteId ); + myActions[ClearId]->setVisible( flags & ClearId ); + myActions[SelectAllId]->setVisible( flags & SelectAllId ); + myActions[DumpCommandsId]->setVisible( flags & DumpCommandsId ); +} + +/*! + \brief Get menu actions which are currently visible in the context popup menu. + \return ORed together actions flags + \sa setMenuActions() +*/ +int PyConsole_Console::menuActions() const +{ + int ret = 0; + ret = ret | ( myActions[CopyId]->isVisible() ? CopyId : 0 ); + ret = ret | ( myActions[PasteId]->isVisible() ? PasteId : 0 ); + ret = ret | ( myActions[ClearId]->isVisible() ? ClearId : 0 ); + ret = ret | ( myActions[SelectAllId]->isVisible() ? SelectAllId : 0 ); + ret = ret | ( myActions[DumpCommandsId]->isVisible() ? DumpCommandsId : 0 ); + return ret; +} + +/*! + \brief Create menu actions. + + Create context popup menu actions. +*/ +void PyConsole_Console::createActions() +{ + QAction* a = new QAction( tr( "EDIT_COPY_CMD" ), this ); + a->setStatusTip( tr( "EDIT_COPY_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) ); + myActions.insert( CopyId, a ); + + a = new QAction( tr( "EDIT_PASTE_CMD" ), this ); + a->setStatusTip( tr( "EDIT_PASTE_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) ); + myActions.insert( PasteId, a ); + + a = new QAction( tr( "EDIT_CLEAR_CMD" ), this ); + a->setStatusTip( tr( "EDIT_CLEAR_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( clear() ) ); + myActions.insert( ClearId, a ); + + a = new QAction( tr( "EDIT_SELECTALL_CMD" ), this ); + a->setStatusTip( tr( "EDIT_SELECTALL_CMD" ) ); + connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) ); + myActions.insert( SelectAllId, a ); + +// a = new QAction( tr( "EDIT_DUMPCOMMANDS_CMD" ), this ); +// a->setStatusTip( tr( "EDIT_DUMPCOMMANDS_CMD" ) ); +// connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( dump() ) ); +// myActions.insert( DumpCommandsId, a ); +} + +/*! + \brief Update menu actions. + + Update context popup menu action state. +*/ +void PyConsole_Console::updateActions() +{ + myActions[CopyId]->setEnabled( myEditor->textCursor().hasSelection() ); + myActions[PasteId]->setEnabled( !myEditor->isReadOnly() && !QApplication::clipboard()->text().isEmpty() ); + myActions[SelectAllId]->setEnabled( !myEditor->document()->isEmpty() ); +} + +/** + * Similar to constructor of the base class but using enhanced objects. + * TODO: this should really be done in a factory to avoid code duplication. + * @param parent + * @param interp + */ +PyConsole_EnhConsole::PyConsole_EnhConsole( QWidget* parent, PyConsole_EnhInterp* interp) + : PyConsole_Console(parent, interp, 0) +{ + // create python interpreter + myInterp = interp; + if ( !myInterp ) + myInterp = new PyConsole_EnhInterp(); + + // initialize Python interpretator + myInterp->initialize(); + + // create editor console + QVBoxLayout* lay = new QVBoxLayout( this ); + lay->setMargin( 0 ); + myEditor = new PyConsole_EnhEditor( static_cast(myInterp), this ); + char* synchronous = getenv("PYTHON_CONSOLE_SYNC"); + if (synchronous && atoi(synchronous)) + { + myEditor->setIsSync(true); + } + myEditor->viewport()->installEventFilter( this ); + lay->addWidget( myEditor ); + + createActions(); +} diff --git a/src/PyConsole/PyConsole_Console.cxx b/src/PyConsole/PyConsole_Console.cxx deleted file mode 100644 index 54391474f..000000000 --- a/src/PyConsole/PyConsole_Console.cxx +++ /dev/null @@ -1,341 +0,0 @@ - -/*! - \class PyConsole_Console - \brief Python console widget. -*/ - -#include "PyConsole_Interp.h" /// !!! WARNING !!! THIS INCLUDE MUST BE VERY FIRST !!! -#include "PyConsole_Console.h" -#include "PyConsole_EnhEditor.h" -#include "PyConsole_EnhInterp.h" - -//#include - -#include -#include -#include -#include -#include -#include - -/*! - \brief Constructor. - - Creates new python console widget. - \param parent parent widget - \param interp python interpreter -*/ -PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* interp ) -: QWidget( parent ) -{ - // create python interpreter - myInterp = interp; - if ( !myInterp ) - myInterp = new PyConsole_Interp(); - - // initialize Python interpretator - myInterp->initialize(); - - // create editor console - QVBoxLayout* lay = new QVBoxLayout( this ); - lay->setMargin( 0 ); - myEditor = new PyConsole_Editor( myInterp, this ); - char* synchronous = getenv("PYTHON_CONSOLE_SYNC"); - if (synchronous && atoi(synchronous)) - { - myEditor->setIsSync(true); - } - myEditor->viewport()->installEventFilter( this ); - lay->addWidget( myEditor ); - - createActions(); -} - -/** - * Protected constructor. - */ -PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* i, PyConsole_Editor* e) - : QWidget (parent), myEditor(e), myInterp(i) -{} - -/*! - \brief Destructor. - - Does nothing for the moment. -*/ -PyConsole_Console::~PyConsole_Console() -{ -} - -/*! - \brief Execute python command in the interpreter. - \param command string with command and arguments -*/ -void PyConsole_Console::exec( const QString& command ) -{ - if ( myEditor ) - myEditor->exec( command ); -} - -/*! - \brief Execute python command in the interpreter - and wait until it is finished. - - Block execution of main application until the python command is executed. - \param command string with command and arguments -*/ -void PyConsole_Console::execAndWait( const QString& command ) -{ - if ( myEditor ) - myEditor->execAndWait( command ); -} - -/*! - \brief Get synchronous mode flag value. - - \sa setIsSync() - \return True if python console works in synchronous mode -*/ -bool PyConsole_Console::isSync() const -{ - return myEditor->isSync(); -} - -/*! - \brief Set synchronous mode flag value. - - In synhronous mode the Python commands are executed in the GUI thread - and the GUI is blocked until the command is finished. In the asynchronous - mode each Python command is executed in the separate thread that does not - block the main GUI loop. - - \param on synhronous mode flag -*/ -void PyConsole_Console::setIsSync( const bool on ) -{ - myEditor->setIsSync( on ); -} - -/*! - \brief Get suppress output flag value. - - \sa setIsSuppressOutput() - \return True if python console output is suppressed. -*/ -bool PyConsole_Console::isSuppressOutput() const -{ - return myEditor->isSuppressOutput(); -} - -/*! - \brief Set suppress output flag value. - - In case if suppress output flag is true, the python - console output suppressed. - - \param on suppress output flag -*/ -void PyConsole_Console::setIsSuppressOutput( const bool on ) -{ - myEditor->setIsSuppressOutput(on); -} - -/*! - \brief Get 'show banner' flag value. - - \sa setIsShowBanner() - \return \c true if python console shows banner -*/ -bool PyConsole_Console::isShowBanner() const -{ - return myEditor->isShowBanner(); -} - -/*! - \brief Set 'show banner' flag value. - - The banner is shown in the top of the python console window. - - \sa isShowBanner() - \param on 'show banner' flag -*/ -void PyConsole_Console::setIsShowBanner( const bool on ) -{ - myEditor->setIsShowBanner( on ); -} - -/*! - \brief Change the python console's font. - \param f new font -*/ -void PyConsole_Console::setFont( const QFont& f ) -{ - if( myEditor ) - myEditor->setFont( f ); -} - -/*! - \brief Get python console font. - \return current python console's font -*/ -QFont PyConsole_Console::font() const -{ - QFont res; - if( myEditor ) - res = myEditor->font(); - return res; -} - -/*! - \brief Event handler. - - Handles context menu request event. - - \param o object - \param e event - \return True if the event is processed and further processing should be stopped -*/ -bool PyConsole_Console::eventFilter( QObject* o, QEvent* e ) -{ - if ( o == myEditor->viewport() && e->type() == QEvent::ContextMenu ) - { - //contextMenuRequest( (QContextMenuEvent*)e ); - return true; - } - return QWidget::eventFilter( o, e ); -} - -/*! - \brief Create the context popup menu. - - Fill in the popup menu with the commands. - - \param menu context popup menu -*/ -void PyConsole_Console::contextMenuPopup( QMenu* menu ) -{ - if ( myEditor->isReadOnly() ) - return; - - menu->addAction( myActions[CopyId] ); - menu->addAction( myActions[PasteId] ); - menu->addAction( myActions[ClearId] ); - menu->addSeparator(); - menu->addAction( myActions[SelectAllId] ); - menu->addSeparator(); - menu->addAction( myActions[DumpCommandsId] ); - - //Qtx::simplifySeparators( menu ); - - updateActions(); -} - -/*! - \brief Set actions to be visible in the context popup menu. - - Actions, which IDs are set in \a flags parameter, will be shown in the - context popup menu. Other actions will not be shown. - - \param flags ORed together actions flags -*/ -void PyConsole_Console::setMenuActions( const int flags ) -{ - myActions[CopyId]->setVisible( flags & CopyId ); - myActions[PasteId]->setVisible( flags & PasteId ); - myActions[ClearId]->setVisible( flags & ClearId ); - myActions[SelectAllId]->setVisible( flags & SelectAllId ); - myActions[DumpCommandsId]->setVisible( flags & DumpCommandsId ); -} - -/*! - \brief Get menu actions which are currently visible in the context popup menu. - \return ORed together actions flags - \sa setMenuActions() -*/ -int PyConsole_Console::menuActions() const -{ - int ret = 0; - ret = ret | ( myActions[CopyId]->isVisible() ? CopyId : 0 ); - ret = ret | ( myActions[PasteId]->isVisible() ? PasteId : 0 ); - ret = ret | ( myActions[ClearId]->isVisible() ? ClearId : 0 ); - ret = ret | ( myActions[SelectAllId]->isVisible() ? SelectAllId : 0 ); - ret = ret | ( myActions[DumpCommandsId]->isVisible() ? DumpCommandsId : 0 ); - return ret; -} - -/*! - \brief Create menu actions. - - Create context popup menu actions. -*/ -void PyConsole_Console::createActions() -{ - QAction* a = new QAction( tr( "EDIT_COPY_CMD" ), this ); - a->setStatusTip( tr( "EDIT_COPY_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) ); - myActions.insert( CopyId, a ); - - a = new QAction( tr( "EDIT_PASTE_CMD" ), this ); - a->setStatusTip( tr( "EDIT_PASTE_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) ); - myActions.insert( PasteId, a ); - - a = new QAction( tr( "EDIT_CLEAR_CMD" ), this ); - a->setStatusTip( tr( "EDIT_CLEAR_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( clear() ) ); - myActions.insert( ClearId, a ); - - a = new QAction( tr( "EDIT_SELECTALL_CMD" ), this ); - a->setStatusTip( tr( "EDIT_SELECTALL_CMD" ) ); - connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) ); - myActions.insert( SelectAllId, a ); - -// a = new QAction( tr( "EDIT_DUMPCOMMANDS_CMD" ), this ); -// a->setStatusTip( tr( "EDIT_DUMPCOMMANDS_CMD" ) ); -// connect( a, SIGNAL( triggered( bool ) ), myEditor, SLOT( dump() ) ); -// myActions.insert( DumpCommandsId, a ); -} - -/*! - \brief Update menu actions. - - Update context popup menu action state. -*/ -void PyConsole_Console::updateActions() -{ - myActions[CopyId]->setEnabled( myEditor->textCursor().hasSelection() ); - myActions[PasteId]->setEnabled( !myEditor->isReadOnly() && !QApplication::clipboard()->text().isEmpty() ); - myActions[SelectAllId]->setEnabled( !myEditor->document()->isEmpty() ); -} - -/** - * Similar to constructor of the base class but using enhanced objects. - * TODO: this should really be done in a factory to avoid code duplication. - * @param parent - * @param interp - */ -PyConsole_EnhConsole::PyConsole_EnhConsole( QWidget* parent, PyConsole_EnhInterp* interp) - : PyConsole_Console(parent, interp, 0) -{ - // create python interpreter - myInterp = interp; - if ( !myInterp ) - myInterp = new PyConsole_EnhInterp(); - - // initialize Python interpretator - myInterp->initialize(); - - // create editor console - QVBoxLayout* lay = new QVBoxLayout( this ); - lay->setMargin( 0 ); - myEditor = new PyConsole_EnhEditor( static_cast(myInterp), this ); - char* synchronous = getenv("PYTHON_CONSOLE_SYNC"); - if (synchronous && atoi(synchronous)) - { - myEditor->setIsSync(true); - } - myEditor->viewport()->installEventFilter( this ); - lay->addWidget( myEditor ); - - createActions(); -} diff --git a/src/PyConsole/PyConsole_Editor.cpp b/src/PyConsole/PyConsole_Editor.cpp new file mode 100644 index 000000000..62ef9e8c3 --- /dev/null +++ b/src/PyConsole/PyConsole_Editor.cpp @@ -0,0 +1,1105 @@ + +/*! + \class PyConsole_Editor + \brief Python command line interpreter front-end GUI widget. + + This class provides simple GUI interface to the Python interpreter, including basic + navigation operations, executing commands (both interactively and programmatically), + copy-paste operations, history of the commands and so on. + + Here below is the shortcut keyboard boundings used for navigation and other operations: + - : execute current command + - : clear current command + - : clear current command + - : previous command in the history + - : move cursor one row up with selection + - : move cursor one row up without selection + - : move cursor one row up with selection + - : next command in the history + - : move cursor one row down with selection + - : move cursor one row down without selection + - : move cursor one row down with selection + - : move one symbol left without selection + - : move one symbol left with selection + - : move one word left without selection + - : move one word left with selection + - : move one symbol right without selection + - : move one symbol right with selection + - : move one word right without selection + - : move one word right with selection + - : first command in the history + - : move one page up with selection + - : move one page up without selection + - : scroll one page up + - : last command in the history + - : move one page down with selection + - : move one page down without selection + - : scroll one page down + - : move to the beginning of the line without selection + - : move to the beginning of the line with selection + - : move to the very first symbol without selection + - : move to the very first symbol with selection + - : move to the end of the line without selection + - : move to the end of the line with selection + - : move to the very last symbol without selection + - : move to the very last symbol with selection + - : delete symbol before the cursor + / remove selected text and put it to the clipboard (cut) + - : delete previous word + / remove selected text and put it to the clipboard (cut) + - : delete text from the cursor to the beginning of the line + / remove selected text and put it to the clipboard (cut) + - : delete symbol after the cursor + / remove selected text and put it to the clipboard (cut) + - : delete next word + / remove selected text and put it to the clipboard (cut) + - : delete text from the cursor to the end of the line + / remove selected text and put it to the clipboard (cut) + - : copy + - : paste + - : paste + - : copy + - : cut + - : paste +*/ + +#include "PyConsole_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! +#include "PyConsole_Editor.h" +#include "PyConsole_Event.h" +#include "PyInterp_Event.h" +#include "PyInterp_Dispatcher.h" +#include "PyConsole_Request.h" + +//#include +//#include +//#include +//#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QString READY_PROMPT = ">>> "; +static QString DOTS_PROMPT = "... "; + +/*class DumpCommandsFileValidator : public SUIT_FileValidator +{ + public: + DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {}; + virtual ~DumpCommandsFileValidator() {}; + virtual bool canSave( const QString& file, bool permissions ); +}; + +bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions) +{ + QFileInfo fi( file ); + if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) { + SUIT_MessageBox::critical( parent(), + QObject::tr("WRN_WARNING"), + QObject::tr("WRN_FILE_NAME_BAD") ); + return false; + } + return SUIT_FileValidator::canSave( file, permissions); +} +*/ +void staticCallbackStdout( void* data, char* c ) +{ + if(!((PyConsole_Editor*)data)->isSuppressOutput()) + QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c, false ) ); +} + +void staticCallbackStderr( void* data, char* c ) +{ + if(!((PyConsole_Editor*)data)->isSuppressOutput()) + QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c, true ) ); +} + + +/*! + \brief Constructor. + + Creates python editor window. + \param theInterp python interper + \param theParent parent widget +*/ +PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, + QWidget* theParent ) +: QTextEdit( theParent ), + myInterp( 0 ), + myCmdInHistory( -1 ), + myEventLoop( 0 ), + myShowBanner( true ), + myIsSync( false ), + myIsSuppressOutput( false ) +{ + //QString fntSet( "" ); + QFont aFont = QFont( "Courier", 11 );//SUIT_Tools::stringToFont( fntSet ); + setFont( aFont ); + setUndoRedoEnabled( false ); + + myPrompt = READY_PROMPT; + setLineWrapMode( QTextEdit::WidgetWidth ); + setWordWrapMode( QTextOption::WrapAnywhere ); + setAcceptRichText( false ); + + theInterp->setvoutcb( staticCallbackStdout, this ); + theInterp->setverrcb( staticCallbackStderr, this ); + + // san - This is necessary for troubleless initialization + onPyInterpChanged( theInterp ); +} + +/*! + \brief Destructor. + + Does nothing for the moment. +*/ +PyConsole_Editor::~PyConsole_Editor() +{ +} + +/*! + \brief Get synchronous mode flag value. + + \sa setIsSync() + \return True if python console works in synchronous mode +*/ +bool PyConsole_Editor::isSync() const +{ + return myIsSync; +} + +/*! + \brief Set synchronous mode flag value. + + In synhronous mode the Python commands are executed in the GUI thread + and the GUI is blocked until the command is finished. In the asynchronous + mode each Python command is executed in the separate thread that does not + block the main GUI loop. + + \param on synhronous mode flag +*/ +void PyConsole_Editor::setIsSync( const bool on ) +{ + myIsSync = on; +} + +/*! + \brief Get suppress output flag value. + + \sa setIsSuppressOutput() + \return \c true if python console output is suppressed. +*/ +bool PyConsole_Editor::isSuppressOutput() const +{ + return myIsSuppressOutput; +} + +/*! + \brief Set suppress output flag value. + + In case if suppress output flag is true, the python + console output suppressed. + + \param on suppress output flag +*/ +void PyConsole_Editor::setIsSuppressOutput( const bool on ) +{ + myIsSuppressOutput = on; +} + +/*! + \brief Get 'show banner' flag value. + + \sa setIsShowBanner() + \return \c true if python console shows banner +*/ +bool PyConsole_Editor::isShowBanner() const +{ + return myShowBanner; +} + +/*! + \brief Set 'show banner' flag value. + + The banner is shown in the top of the python console window. + + \sa isShowBanner() + \param on 'show banner' flag +*/ +void PyConsole_Editor::setIsShowBanner( const bool on ) +{ + if ( myShowBanner != on ) { + myShowBanner = on; + clear(); + } +} + +/*! + \brief Get size hint for the Python console window + \return size hint value +*/ +QSize PyConsole_Editor::sizeHint() const +{ + QFontMetrics fm( font() ); + int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1; + QSize s(100, fm.lineSpacing()*nbLines); + return s; +} + +/*! + \brief Put the string \a str to the python editor. + \param str string to be put in the command line of the editor + \param newBlock if True, then the string is printed on a new line + \param isError if true, the text is printed in dark red +*/ +void PyConsole_Editor::addText( const QString& str, + const bool newBlock, + const bool isError) +{ + QTextCursor theCursor(textCursor()); + QTextCharFormat cf; + + moveCursor( QTextCursor::End ); + if ( newBlock ) + theCursor.insertBlock(); + if (isError) + cf.setForeground(QBrush(Qt::red)); + else + cf.setForeground(QBrush(Qt::black)); + theCursor.insertText( str, cf); + moveCursor( QTextCursor::End ); + ensureCursorVisible(); +} + +/*! + \brief Convenient method for executing a Python command, + as if the user typed it manually. + \param command python command to be executed + + !!! WARNING: doesn't work properly with multi-line commands. !!! +*/ +void PyConsole_Editor::exec( const QString& command ) +{ + if ( isReadOnly() ) { + // some interactive command is being executed in this editor... + // shedule the command to the queue + myQueue.push_back( command ); + return; + } + // remove last line + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + // set "ready" prompt + myPrompt = READY_PROMPT; + // clear command buffer + myCommandBuffer.truncate( 0 ); + // unset history browsing mode + myCmdInHistory = -1; + // print command line by line + QString cmd = command; + if ( !cmd.endsWith( "\n" ) ) cmd += "\n"; + QStringList lines = command.split( "\n" ); + for ( int i = 0; i < lines.size(); i++ ) { + if ( !lines[i].trimmed().isEmpty() ) + myHistory.push_back( lines[i] ); + addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 ); + } + // IPAL20182 + addText( "", true ); + // set read-only mode + setReadOnly( true ); + // set busy cursor + setCursor( Qt::BusyCursor ); + + // post a request to execute Python command; + // editor will be informed via a custom event that execution has been completed + PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) ); +} + +/*! + \brief Create request to the python dispatcher for the command execution. + + \param command python command to be executed + */ +PyInterp_Request* PyConsole_Editor::createRequest( const QString& command ) +{ + return new ExecCommand( myInterp, command, this, isSync() ); +} + +/*! + \brief Execute command in the python interpreter + and wait until it is finished. + + \param command python command to be executed + */ +void PyConsole_Editor::execAndWait( const QString& command ) +{ + // already running ? + if( myEventLoop ) + return; + + // create new event loop + myEventLoop = new QEventLoop( this ); + // execute command + exec( command ); + // run event loop + myEventLoop->exec(); + // delete event loop after command is processed + delete myEventLoop; + myEventLoop = 0; +} + +/*! + \brief Process "Enter" key press event. + + Execute the command entered by the user. +*/ +void PyConsole_Editor::handleReturn() +{ + // Position cursor at the end + QTextCursor curs(textCursor()); + curs.movePosition(QTextCursor::End); + setTextCursor(curs); + + // get last line + QTextBlock par = document()->end().previous(); + if ( !par.isValid() ) return; + + // get command + QString cmd = par.text().remove( 0, promptSize() ); + // extend the command buffer with the current command + myCommandBuffer.append( cmd ); + // add command to the history + if ( !cmd.trimmed().isEmpty() ) + myHistory.push_back( cmd ); + + // IPAL19397 + addText( "", true ); + + // set read-only mode + setReadOnly( true ); + // set busy cursor + setCursor( Qt::BusyCursor ); + + // post a request to execute Python command; + // editor will be informed via a custom event that execution has been completed + PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) ); +} + +/*! + \brief Process drop event. + + Paste dragged text. + \param event drop event +*/ +void PyConsole_Editor::dropEvent( QDropEvent* event ) +{ + // get the initial drop position + QPoint pos = event->pos(); + QTextCursor cur = cursorForPosition( event->pos() ); + // if the position is not in the last line move it to the end of the command line + if ( cur.position() < document()->end().previous().position() + promptSize() ) { + moveCursor( QTextCursor::End ); + pos = cursorRect().center(); + } + // create new drop event and use it instead of the original + QDropEvent de( pos, + event->possibleActions(), + event->mimeData(), + event->mouseButtons(), + event->keyboardModifiers(), + event->type() ); + QTextEdit::dropEvent( &de ); + // accept the original event + event->acceptProposedAction(); +} + +/*! + \brief Process mouse button release event. + + Left mouse button: copy selection to the clipboard. + Middle mouse button: paste clipboard's contents. + \param event mouse event +*/ +void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event ) +{ + if ( event->button() == Qt::LeftButton ) { + QTextEdit::mouseReleaseEvent( event ); + //copy(); + } + else if ( event->button() == Qt::MidButton ) { + QTextCursor cur = cursorForPosition( event->pos() ); + // if the position is not in the last line move it to the end of the command line + if ( cur.position() < document()->end().previous().position() + promptSize() ) { + moveCursor( QTextCursor::End ); + } + else { + setTextCursor( cur ); + } + const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? + QClipboard::Selection : QClipboard::Clipboard ); + if ( md ) + insertFromMimeData( md ); + } + else { + QTextEdit::mouseReleaseEvent( event ); + } +} + +/*! + \brief Check if the string is command. + + Return True if the string \a str is likely to be the command + (i.e. it is started from the '>>>' or '...'). + \param str string to be checked +*/ +bool PyConsole_Editor::isCommand( const QString& str ) const +{ + return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT ); +} + +/*! + \brief Handle keyboard event. + + Implement navigation, history browsing, copy/paste and other common + operations. + \param event keyboard event +*/ +void PyConsole_Editor::keyPressEvent( QKeyEvent* event ) +{ + // get cursor position + QTextCursor cur = textCursor(); + int curLine = cur.blockNumber(); + int curCol = cur.columnNumber(); + + // get last edited line + int endLine = document()->blockCount()-1; + + // get pressed key code + int aKey = event->key(); + + // check if is pressed + bool ctrlPressed = event->modifiers() & Qt::ControlModifier; + // check if is pressed + bool shftPressed = event->modifiers() & Qt::ShiftModifier; + + if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) { + // process + key-binding and key: clear current command + myCommandBuffer.truncate( 0 ); + myPrompt = READY_PROMPT; + addText( myPrompt, true ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + return; + } + else if ( ctrlPressed && aKey == Qt::Key_C ) { + // process + key-binding : copy + copy(); + return; + } + else if ( ctrlPressed && aKey == Qt::Key_X ) { + // process + key-binding : cut + cut(); + return; + } + else if ( ctrlPressed && aKey == Qt::Key_V ) { + // process + key-binding : paste + paste(); + return; + } + + // check for printed key + // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0; + // Better: + aKey = !(QChar(aKey).isPrint()) ? aKey : 0; + + switch ( aKey ) { + case 0 : + // any printed key: just print it + { + if ( curLine < endLine || curCol < promptSize() ) { + moveCursor( QTextCursor::End ); + } + QTextEdit::keyPressEvent( event ); + break; + } + case Qt::Key_Return: + case Qt::Key_Enter: + // key: process the current command + { + handleReturn(); + break; + } + case Qt::Key_Up: + // arrow key: process as follows: + // - without , modifiers: previous command in history + // - with modifier key pressed: move cursor one row up without selection + // - with modifier key pressed: move cursor one row up with selection + // - with + modifier keys pressed: scroll one row up + { + if ( ctrlPressed && shftPressed ) { + int value = verticalScrollBar()->value(); + int spacing = fontMetrics().lineSpacing(); + verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 ); + } + else if ( shftPressed || ctrlPressed ) { + if ( curLine > 0 ) + moveCursor( QTextCursor::Up, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + else { + if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { + // set history browsing mode + myCmdInHistory = myHistory.count(); + // remember current command + QTextBlock par = document()->end().previous(); + myCurrentCommand = par.text().remove( 0, promptSize() ); + } + if ( myCmdInHistory > 0 ) { + myCmdInHistory--; + // get previous command in the history + QString previousCommand = myHistory.at( myCmdInHistory ); + // print previous command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + previousCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_Down: + // arrow key: process as follows: + // - without , modifiers: next command in history + // - with modifier key pressed: move cursor one row down without selection + // - with modifier key pressed: move cursor one row down with selection + // - with + modifier keys pressed: scroll one row down + { + if ( ctrlPressed && shftPressed ) { + int value = verticalScrollBar()->value(); + int maxval = verticalScrollBar()->maximum(); + int spacing = fontMetrics().lineSpacing(); + verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval ); + } + else if ( shftPressed || ctrlPressed) { + if ( curLine < endLine ) + moveCursor( QTextCursor::Down, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + else { + if ( myCmdInHistory >= 0 ) { + // get next command in the history + myCmdInHistory++; + QString nextCommand; + if ( myCmdInHistory < myHistory.count() ) { + // next command in history + nextCommand = myHistory.at( myCmdInHistory ); + } + else { + // end of history is reached + // last printed command + nextCommand = myCurrentCommand; + // unset history browsing mode + myCmdInHistory = -1; + } + // print next or current command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + nextCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_Left: + // arrow key: process as follows: + // - without , modifiers: move one symbol left (taking into account prompt) + // - with modifier key pressed: move one word left (taking into account prompt) + // - with modifier key pressed: move one symbol left with selection + // - with + modifier keys pressed: move one word left with selection + { + QString txt = textCursor().block().text(); + if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) { + moveCursor( QTextCursor::Up ); + moveCursor( QTextCursor::EndOfBlock ); + } + else { + QTextEdit::keyPressEvent( event ); + } + break; + } + case Qt::Key_Right: + // arrow key: process as follows: + // - without , modifiers: move one symbol right (taking into account prompt) + // - with modifier key pressed: move one word right (taking into account prompt) + // - with modifier key pressed: move one symbol right with selection + // - with + modifier keys pressed: move one word right with selection + { + QString txt = textCursor().block().text(); + if ( !shftPressed ) { + if ( curCol < txt.length() ) { + if ( isCommand( txt ) && curCol < promptSize() ) { + cur.setPosition( cur.block().position() + promptSize() ); + setTextCursor( cur ); + break; + } + } + else { + if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) { + cur.setPosition( cur.position() + promptSize()+1 ); + setTextCursor( cur ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + break; + } + } + } + QTextEdit::keyPressEvent( event ); + break; + } + case Qt::Key_PageUp: + // key: process as follows: + // - without , modifiers: first command in history + // - with modifier key pressed: move cursor one page up without selection + // - with modifier key pressed: move cursor one page up with selection + // - with + modifier keys pressed: scroll one page up + { + if ( ctrlPressed && shftPressed ) { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); + } + else if ( shftPressed || ctrlPressed ) { + bool moved = false; + qreal lastY = cursorRect( cur ).top(); + qreal distance = 0; + // move using movePosition to keep the cursor's x + do { + qreal y = cursorRect( cur ).top(); + distance += qAbs( y - lastY ); + lastY = y; + moved = cur.movePosition( QTextCursor::Up, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + } while ( moved && distance < viewport()->height() ); + if ( moved ) { + cur.movePosition( QTextCursor::Down, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); + } + setTextCursor( cur ); + } + else { + if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { + // set history browsing mode + myCmdInHistory = myHistory.count(); + // remember current command + QTextBlock par = document()->end().previous(); + myCurrentCommand = par.text().remove( 0, promptSize() ); + } + if ( myCmdInHistory > 0 ) { + myCmdInHistory = 0; + // get very first command in the history + QString firstCommand = myHistory.at( myCmdInHistory ); + // print first command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + firstCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_PageDown: + // key: process as follows: + // - without , modifiers: last command in history + // - with modifier key pressed: move cursor one page down without selection + // - with modifier key pressed: move cursor one page down with selection + // - with + modifier keys pressed: scroll one page down + { + if ( ctrlPressed && shftPressed ) { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); + } + else if ( shftPressed || ctrlPressed ) { + bool moved = false; + qreal lastY = cursorRect( cur ).top(); + qreal distance = 0; + // move using movePosition to keep the cursor's x + do { + qreal y = cursorRect( cur ).top(); + distance += qAbs( y - lastY ); + lastY = y; + moved = cur.movePosition( QTextCursor::Down, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + } while ( moved && distance < viewport()->height() ); + if ( moved ) { + cur.movePosition( QTextCursor::Up, + shftPressed ? QTextCursor::KeepAnchor : + QTextCursor::MoveAnchor ); + verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); + } + setTextCursor( cur ); + } + else { + if ( myCmdInHistory >= 0 ) { + // unset history browsing mode + myCmdInHistory = -1; + // print current command + moveCursor( QTextCursor::End ); + moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + addText( myPrompt + myCurrentCommand ); + // move cursor to the end + moveCursor( QTextCursor::End ); + } + } + break; + } + case Qt::Key_Home: + // key: process as follows: + // - without , modifiers: move cursor to the beginning of the current line without selection + // - with modifier key pressed: move cursor to the very first symbol without selection + // - with modifier key pressed: move cursor to the beginning of the current line with selection + // - with + modifier keys pressed: move cursor to the very first symbol with selection + { + if ( ctrlPressed ) { + moveCursor( QTextCursor::Start, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + else { + QString txt = textCursor().block().text(); + if ( isCommand( txt ) ) { + if ( shftPressed ) { + if ( curCol > promptSize() ) { + cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor ); + cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() ); + } + } + else { + cur.movePosition( QTextCursor::StartOfLine ); + cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() ); + } + setTextCursor( cur ); + } + else { + moveCursor( QTextCursor::StartOfBlock, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + } + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + } + break; + } + case Qt::Key_End: + // key: process as follows: + // - without , modifiers: move cursor to the end of the current line without selection + // - with modifier key pressed: move cursor to the very last symbol without selection + // - with modifier key pressed: move cursor to the end of the current line with selection + // - with + modifier keys pressed: move cursor to the very last symbol with selection + { + moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, + shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); + break; + } + case Qt::Key_Backspace : + // key: process as follows + // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt) + // - with modifier key pressed: delete previous word + // - with modifier key pressed: delete text from the cursor to the line beginning + // works only for last (command) line + { + if ( cur.hasSelection() ) { + cut(); + } + else if ( cur.position() > document()->end().previous().position() + promptSize() ) { + if ( shftPressed ) { + moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + } + else if ( ctrlPressed ) { + cur.setPosition( document()->end().previous().position() + promptSize(), + QTextCursor::KeepAnchor ); + setTextCursor( cur ); + textCursor().removeSelectedText(); + } + else { + QTextEdit::keyPressEvent( event ); + } + } + else { + cur.setPosition( document()->end().previous().position() + promptSize() ); + setTextCursor( cur ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + } + break; + } + case Qt::Key_Delete : + // key: process as follows + // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt) + // - with modifier key pressed: delete next word + // - with modifier key pressed: delete text from the cursor to the end of line + // works only for last (command) line + { + if ( cur.hasSelection() ) { + cut(); + } + else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) { + if ( shftPressed ) { + moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + } + else if ( ctrlPressed ) { + moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor ); + textCursor().removeSelectedText(); + } + else { + QTextEdit::keyPressEvent( event ); + } + } + else { + cur.setPosition( document()->end().previous().position() + promptSize() ); + setTextCursor( cur ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + } + break; + } + case Qt::Key_Insert : + // key: process as follows + // - with modifier key pressed: copy() + // - with modifier key pressed: paste() to the command line + { + if ( ctrlPressed ) { + copy(); + } + else if ( shftPressed ) { + paste(); + } + else + QTextEdit::keyPressEvent( event ); + break; + } + } +} + +/*! + \brief Handle notification event coming from Python dispatcher. + \param event notification event +*/ +void PyConsole_Editor::customEvent( QEvent* event ) +{ + switch( event->type() ) + { + case PrintEvent::EVENT_ID: + { + PrintEvent* pe=(PrintEvent*)event; + addText( pe->text(), false, pe->isError()); + return; + } + case PyInterp_Event::ES_OK: + case PyInterp_Event::ES_ERROR: + { + // clear command buffer + myCommandBuffer.truncate( 0 ); + // add caret return line if necessary + QTextBlock par = document()->end().previous(); + QString txt = par.text(); + txt.truncate( txt.length() - 1 ); + // IPAL19397 : addText moved to handleReturn() method + //if ( !txt.isEmpty() ) + // addText( "", true ); + // set "ready" prompt + myPrompt = READY_PROMPT; + addText( myPrompt ); + // unset busy cursor + unsetCursor(); + // stop event loop (if running) + if ( myEventLoop ) + myEventLoop->exit(); + break; + } + case PyInterp_Event::ES_INCOMPLETE: + { + // extend command buffer (multi-line command) + myCommandBuffer.append( "\n" ); + // add caret return line if necessary + QTextBlock par = document()->end().previous(); + QString txt = par.text(); + txt.truncate( txt.length() - 1 ); + // IPAL19397 : addText moved to handleReturn() method + //if ( !txt.isEmpty() ) + // addText( "", true ); + // set "dot" prompt + myPrompt = DOTS_PROMPT; + addText( myPrompt/*, true*/ ); // IPAL19397 + // unset busy cursor + unsetCursor(); + // stop event loop (if running) + if ( myEventLoop ) + myEventLoop->exit(); + break; + } + default: + QTextEdit::customEvent( event ); + } + + // unset read-only state + setReadOnly( false ); + // unset history browsing mode + myCmdInHistory = -1; + + if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 ) + { + // process the next sheduled command from the queue (if there is any) + QString nextcmd = myQueue[0]; + myQueue.pop_front(); + exec( nextcmd ); + } +} + +/*! + \brief Handle Python interpreter change. + + Perform initialization actions if the interpreter is changed. + \param interp python interpreter is being set +*/ +void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp ) +{ + if ( myInterp != interp + // Force read-only state and wait cursor when myInterp is NULL + || !myInterp ) { + myInterp = interp; + if ( myInterp ) { + // print banner + myBanner = myInterp->getbanner().c_str(); + if ( isShowBanner() ) + addText( myBanner ); + // clear command buffer + myCommandBuffer.truncate(0); + // unset read-only state + setReadOnly( false ); + // unset history browsing mode + myCmdInHistory = -1; + // add prompt + addText( myPrompt ); + // unset busy cursor + viewport()->unsetCursor(); + // stop event loop (if running) + if( myEventLoop) + myEventLoop->exit(); + } + else { + // clear contents + clear(); + // set read-only state + setReadOnly( true ); + // set busy cursor + setCursor( Qt::WaitCursor ); + } + } +} + +/*! + \brief "Copy" operation. + + Reimplemented from Qt. + Warning! In Qt4 this method is not virtual. + */ +void PyConsole_Editor::cut() +{ + QTextCursor cur = textCursor(); + if ( cur.hasSelection() ) { + QApplication::clipboard()->setText( cur.selectedText() ); + int startSelection = cur.selectionStart(); + if ( startSelection < document()->end().previous().position() + promptSize() ) + startSelection = document()->end().previous().position() + promptSize(); + int endSelection = cur.selectionEnd(); + if ( endSelection < document()->end().previous().position() + promptSize() ) + endSelection = document()->end().previous().position() + promptSize(); + cur.setPosition( startSelection ); + cur.setPosition( endSelection, QTextCursor::KeepAnchor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + setTextCursor( cur ); + textCursor().removeSelectedText(); + } +} + +/*! + \brief "Paste" operation. + + Reimplemented from Qt. + Warning! In Qt4 this method is not virtual. + */ +void PyConsole_Editor::paste() +{ + QTextCursor cur = textCursor(); + if ( cur.hasSelection() ) { + int startSelection = cur.selectionStart(); + if ( startSelection < document()->end().previous().position() + promptSize() ) + startSelection = document()->end().previous().position() + promptSize(); + int endSelection = cur.selectionEnd(); + if ( endSelection < document()->end().previous().position() + promptSize() ) + endSelection = document()->end().previous().position() + promptSize(); + cur.setPosition( startSelection ); + cur.setPosition( endSelection, QTextCursor::KeepAnchor ); + horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); + setTextCursor( cur ); + textCursor().removeSelectedText(); + } + if ( textCursor().position() < document()->end().previous().position() + promptSize() ) + moveCursor( QTextCursor::End ); + QTextEdit::paste(); +} + +/*! + \brief "Clear" operation. + + Reimplemented from Qt. + Warning! In Qt4 this method is not virtual. + */ +void PyConsole_Editor::clear() +{ + QTextEdit::clear(); + if ( isShowBanner() ) + addText( myBanner ); + myPrompt = READY_PROMPT; + addText( myPrompt ); +} + +/*! + \brief "Dump commands" operation. + */ +/*void PyConsole_Editor::dump() +{ + QStringList aFilters; + aFilters.append( tr( "PYTHON_FILES_FILTER" ) ); + + QString fileName = SUIT_FileDlg::getFileName( this, QString(), + aFilters, tr( "TOT_DUMP_PYCOMMANDS" ), + false, true, new DumpCommandsFileValidator( this ) ); + if ( fileName != "" ) { + QFile file( fileName ); + if ( !file.open( QFile::WriteOnly ) ) + return; + + QTextStream out (&file); + + for( int i=0; i : execute current command - - : clear current command - - : clear current command - - : previous command in the history - - : move cursor one row up with selection - - : move cursor one row up without selection - - : move cursor one row up with selection - - : next command in the history - - : move cursor one row down with selection - - : move cursor one row down without selection - - : move cursor one row down with selection - - : move one symbol left without selection - - : move one symbol left with selection - - : move one word left without selection - - : move one word left with selection - - : move one symbol right without selection - - : move one symbol right with selection - - : move one word right without selection - - : move one word right with selection - - : first command in the history - - : move one page up with selection - - : move one page up without selection - - : scroll one page up - - : last command in the history - - : move one page down with selection - - : move one page down without selection - - : scroll one page down - - : move to the beginning of the line without selection - - : move to the beginning of the line with selection - - : move to the very first symbol without selection - - : move to the very first symbol with selection - - : move to the end of the line without selection - - : move to the end of the line with selection - - : move to the very last symbol without selection - - : move to the very last symbol with selection - - : delete symbol before the cursor - / remove selected text and put it to the clipboard (cut) - - : delete previous word - / remove selected text and put it to the clipboard (cut) - - : delete text from the cursor to the beginning of the line - / remove selected text and put it to the clipboard (cut) - - : delete symbol after the cursor - / remove selected text and put it to the clipboard (cut) - - : delete next word - / remove selected text and put it to the clipboard (cut) - - : delete text from the cursor to the end of the line - / remove selected text and put it to the clipboard (cut) - - : copy - - : paste - - : paste - - : copy - - : cut - - : paste -*/ - -#include "PyConsole_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! -#include "PyConsole_Editor.h" -#include "PyConsole_Event.h" -#include "PyInterp_Event.h" -#include "PyInterp_Dispatcher.h" -#include "PyConsole_Request.h" - -//#include -//#include -//#include -//#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static QString READY_PROMPT = ">>> "; -static QString DOTS_PROMPT = "... "; - -/*class DumpCommandsFileValidator : public SUIT_FileValidator -{ - public: - DumpCommandsFileValidator( QWidget* parent = 0 ) : SUIT_FileValidator ( parent ) {}; - virtual ~DumpCommandsFileValidator() {}; - virtual bool canSave( const QString& file, bool permissions ); -}; - -bool DumpCommandsFileValidator::canSave(const QString& file, bool permissions) -{ - QFileInfo fi( file ); - if ( !QRegExp( "[A-Za-z_][A-Za-z0-9_]*" ).exactMatch( fi.completeBaseName() ) ) { - SUIT_MessageBox::critical( parent(), - QObject::tr("WRN_WARNING"), - QObject::tr("WRN_FILE_NAME_BAD") ); - return false; - } - return SUIT_FileValidator::canSave( file, permissions); -} -*/ -void staticCallbackStdout( void* data, char* c ) -{ - if(!((PyConsole_Editor*)data)->isSuppressOutput()) - QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c, false ) ); -} - -void staticCallbackStderr( void* data, char* c ) -{ - if(!((PyConsole_Editor*)data)->isSuppressOutput()) - QApplication::postEvent( (PyConsole_Editor*)data, new PrintEvent( c, true ) ); -} - - -/*! - \brief Constructor. - - Creates python editor window. - \param theInterp python interper - \param theParent parent widget -*/ -PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, - QWidget* theParent ) -: QTextEdit( theParent ), - myInterp( 0 ), - myCmdInHistory( -1 ), - myEventLoop( 0 ), - myShowBanner( true ), - myIsSync( false ), - myIsSuppressOutput( false ) -{ - //QString fntSet( "" ); - QFont aFont = QFont( "Courier", 11 );//SUIT_Tools::stringToFont( fntSet ); - setFont( aFont ); - setUndoRedoEnabled( false ); - - myPrompt = READY_PROMPT; - setLineWrapMode( QTextEdit::WidgetWidth ); - setWordWrapMode( QTextOption::WrapAnywhere ); - setAcceptRichText( false ); - - theInterp->setvoutcb( staticCallbackStdout, this ); - theInterp->setverrcb( staticCallbackStderr, this ); - - // san - This is necessary for troubleless initialization - onPyInterpChanged( theInterp ); -} - -/*! - \brief Destructor. - - Does nothing for the moment. -*/ -PyConsole_Editor::~PyConsole_Editor() -{ -} - -/*! - \brief Get synchronous mode flag value. - - \sa setIsSync() - \return True if python console works in synchronous mode -*/ -bool PyConsole_Editor::isSync() const -{ - return myIsSync; -} - -/*! - \brief Set synchronous mode flag value. - - In synhronous mode the Python commands are executed in the GUI thread - and the GUI is blocked until the command is finished. In the asynchronous - mode each Python command is executed in the separate thread that does not - block the main GUI loop. - - \param on synhronous mode flag -*/ -void PyConsole_Editor::setIsSync( const bool on ) -{ - myIsSync = on; -} - -/*! - \brief Get suppress output flag value. - - \sa setIsSuppressOutput() - \return \c true if python console output is suppressed. -*/ -bool PyConsole_Editor::isSuppressOutput() const -{ - return myIsSuppressOutput; -} - -/*! - \brief Set suppress output flag value. - - In case if suppress output flag is true, the python - console output suppressed. - - \param on suppress output flag -*/ -void PyConsole_Editor::setIsSuppressOutput( const bool on ) -{ - myIsSuppressOutput = on; -} - -/*! - \brief Get 'show banner' flag value. - - \sa setIsShowBanner() - \return \c true if python console shows banner -*/ -bool PyConsole_Editor::isShowBanner() const -{ - return myShowBanner; -} - -/*! - \brief Set 'show banner' flag value. - - The banner is shown in the top of the python console window. - - \sa isShowBanner() - \param on 'show banner' flag -*/ -void PyConsole_Editor::setIsShowBanner( const bool on ) -{ - if ( myShowBanner != on ) { - myShowBanner = on; - clear(); - } -} - -/*! - \brief Get size hint for the Python console window - \return size hint value -*/ -QSize PyConsole_Editor::sizeHint() const -{ - QFontMetrics fm( font() ); - int nbLines = ( isShowBanner() ? myBanner.split("\n").count() : 0 ) + 1; - QSize s(100, fm.lineSpacing()*nbLines); - return s; -} - -/*! - \brief Put the string \a str to the python editor. - \param str string to be put in the command line of the editor - \param newBlock if True, then the string is printed on a new line - \param isError if true, the text is printed in dark red -*/ -void PyConsole_Editor::addText( const QString& str, - const bool newBlock, - const bool isError) -{ - QTextCursor theCursor(textCursor()); - QTextCharFormat cf; - - moveCursor( QTextCursor::End ); - if ( newBlock ) - theCursor.insertBlock(); - if (isError) - cf.setForeground(QBrush(Qt::red)); - else - cf.setForeground(QBrush(Qt::black)); - theCursor.insertText( str, cf); - moveCursor( QTextCursor::End ); - ensureCursorVisible(); -} - -/*! - \brief Convenient method for executing a Python command, - as if the user typed it manually. - \param command python command to be executed - - !!! WARNING: doesn't work properly with multi-line commands. !!! -*/ -void PyConsole_Editor::exec( const QString& command ) -{ - if ( isReadOnly() ) { - // some interactive command is being executed in this editor... - // shedule the command to the queue - myQueue.push_back( command ); - return; - } - // remove last line - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - // set "ready" prompt - myPrompt = READY_PROMPT; - // clear command buffer - myCommandBuffer.truncate( 0 ); - // unset history browsing mode - myCmdInHistory = -1; - // print command line by line - QString cmd = command; - if ( !cmd.endsWith( "\n" ) ) cmd += "\n"; - QStringList lines = command.split( "\n" ); - for ( int i = 0; i < lines.size(); i++ ) { - if ( !lines[i].trimmed().isEmpty() ) - myHistory.push_back( lines[i] ); - addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 ); - } - // IPAL20182 - addText( "", true ); - // set read-only mode - setReadOnly( true ); - // set busy cursor - setCursor( Qt::BusyCursor ); - - // post a request to execute Python command; - // editor will be informed via a custom event that execution has been completed - PyInterp_Dispatcher::Get()->Exec( createRequest( cmd ) ); -} - -/*! - \brief Create request to the python dispatcher for the command execution. - - \param command python command to be executed - */ -PyInterp_Request* PyConsole_Editor::createRequest( const QString& command ) -{ - return new ExecCommand( myInterp, command, this, isSync() ); -} - -/*! - \brief Execute command in the python interpreter - and wait until it is finished. - - \param command python command to be executed - */ -void PyConsole_Editor::execAndWait( const QString& command ) -{ - // already running ? - if( myEventLoop ) - return; - - // create new event loop - myEventLoop = new QEventLoop( this ); - // execute command - exec( command ); - // run event loop - myEventLoop->exec(); - // delete event loop after command is processed - delete myEventLoop; - myEventLoop = 0; -} - -/*! - \brief Process "Enter" key press event. - - Execute the command entered by the user. -*/ -void PyConsole_Editor::handleReturn() -{ - // Position cursor at the end - QTextCursor curs(textCursor()); - curs.movePosition(QTextCursor::End); - setTextCursor(curs); - - // get last line - QTextBlock par = document()->end().previous(); - if ( !par.isValid() ) return; - - // get command - QString cmd = par.text().remove( 0, promptSize() ); - // extend the command buffer with the current command - myCommandBuffer.append( cmd ); - // add command to the history - if ( !cmd.trimmed().isEmpty() ) - myHistory.push_back( cmd ); - - // IPAL19397 - addText( "", true ); - - // set read-only mode - setReadOnly( true ); - // set busy cursor - setCursor( Qt::BusyCursor ); - - // post a request to execute Python command; - // editor will be informed via a custom event that execution has been completed - PyInterp_Dispatcher::Get()->Exec( createRequest( myCommandBuffer ) ); -} - -/*! - \brief Process drop event. - - Paste dragged text. - \param event drop event -*/ -void PyConsole_Editor::dropEvent( QDropEvent* event ) -{ - // get the initial drop position - QPoint pos = event->pos(); - QTextCursor cur = cursorForPosition( event->pos() ); - // if the position is not in the last line move it to the end of the command line - if ( cur.position() < document()->end().previous().position() + promptSize() ) { - moveCursor( QTextCursor::End ); - pos = cursorRect().center(); - } - // create new drop event and use it instead of the original - QDropEvent de( pos, - event->possibleActions(), - event->mimeData(), - event->mouseButtons(), - event->keyboardModifiers(), - event->type() ); - QTextEdit::dropEvent( &de ); - // accept the original event - event->acceptProposedAction(); -} - -/*! - \brief Process mouse button release event. - - Left mouse button: copy selection to the clipboard. - Middle mouse button: paste clipboard's contents. - \param event mouse event -*/ -void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event ) -{ - if ( event->button() == Qt::LeftButton ) { - QTextEdit::mouseReleaseEvent( event ); - //copy(); - } - else if ( event->button() == Qt::MidButton ) { - QTextCursor cur = cursorForPosition( event->pos() ); - // if the position is not in the last line move it to the end of the command line - if ( cur.position() < document()->end().previous().position() + promptSize() ) { - moveCursor( QTextCursor::End ); - } - else { - setTextCursor( cur ); - } - const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ? - QClipboard::Selection : QClipboard::Clipboard ); - if ( md ) - insertFromMimeData( md ); - } - else { - QTextEdit::mouseReleaseEvent( event ); - } -} - -/*! - \brief Check if the string is command. - - Return True if the string \a str is likely to be the command - (i.e. it is started from the '>>>' or '...'). - \param str string to be checked -*/ -bool PyConsole_Editor::isCommand( const QString& str ) const -{ - return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT ); -} - -/*! - \brief Handle keyboard event. - - Implement navigation, history browsing, copy/paste and other common - operations. - \param event keyboard event -*/ -void PyConsole_Editor::keyPressEvent( QKeyEvent* event ) -{ - // get cursor position - QTextCursor cur = textCursor(); - int curLine = cur.blockNumber(); - int curCol = cur.columnNumber(); - - // get last edited line - int endLine = document()->blockCount()-1; - - // get pressed key code - int aKey = event->key(); - - // check if is pressed - bool ctrlPressed = event->modifiers() & Qt::ControlModifier; - // check if is pressed - bool shftPressed = event->modifiers() & Qt::ShiftModifier; - - if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) { - // process + key-binding and key: clear current command - myCommandBuffer.truncate( 0 ); - myPrompt = READY_PROMPT; - addText( myPrompt, true ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - return; - } - else if ( ctrlPressed && aKey == Qt::Key_C ) { - // process + key-binding : copy - copy(); - return; - } - else if ( ctrlPressed && aKey == Qt::Key_X ) { - // process + key-binding : cut - cut(); - return; - } - else if ( ctrlPressed && aKey == Qt::Key_V ) { - // process + key-binding : paste - paste(); - return; - } - - // check for printed key - // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0; - // Better: - aKey = !(QChar(aKey).isPrint()) ? aKey : 0; - - switch ( aKey ) { - case 0 : - // any printed key: just print it - { - if ( curLine < endLine || curCol < promptSize() ) { - moveCursor( QTextCursor::End ); - } - QTextEdit::keyPressEvent( event ); - break; - } - case Qt::Key_Return: - case Qt::Key_Enter: - // key: process the current command - { - handleReturn(); - break; - } - case Qt::Key_Up: - // arrow key: process as follows: - // - without , modifiers: previous command in history - // - with modifier key pressed: move cursor one row up without selection - // - with modifier key pressed: move cursor one row up with selection - // - with + modifier keys pressed: scroll one row up - { - if ( ctrlPressed && shftPressed ) { - int value = verticalScrollBar()->value(); - int spacing = fontMetrics().lineSpacing(); - verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 ); - } - else if ( shftPressed || ctrlPressed ) { - if ( curLine > 0 ) - moveCursor( QTextCursor::Up, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - else { - if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { - // set history browsing mode - myCmdInHistory = myHistory.count(); - // remember current command - QTextBlock par = document()->end().previous(); - myCurrentCommand = par.text().remove( 0, promptSize() ); - } - if ( myCmdInHistory > 0 ) { - myCmdInHistory--; - // get previous command in the history - QString previousCommand = myHistory.at( myCmdInHistory ); - // print previous command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + previousCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_Down: - // arrow key: process as follows: - // - without , modifiers: next command in history - // - with modifier key pressed: move cursor one row down without selection - // - with modifier key pressed: move cursor one row down with selection - // - with + modifier keys pressed: scroll one row down - { - if ( ctrlPressed && shftPressed ) { - int value = verticalScrollBar()->value(); - int maxval = verticalScrollBar()->maximum(); - int spacing = fontMetrics().lineSpacing(); - verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval ); - } - else if ( shftPressed || ctrlPressed) { - if ( curLine < endLine ) - moveCursor( QTextCursor::Down, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - else { - if ( myCmdInHistory >= 0 ) { - // get next command in the history - myCmdInHistory++; - QString nextCommand; - if ( myCmdInHistory < myHistory.count() ) { - // next command in history - nextCommand = myHistory.at( myCmdInHistory ); - } - else { - // end of history is reached - // last printed command - nextCommand = myCurrentCommand; - // unset history browsing mode - myCmdInHistory = -1; - } - // print next or current command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + nextCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_Left: - // arrow key: process as follows: - // - without , modifiers: move one symbol left (taking into account prompt) - // - with modifier key pressed: move one word left (taking into account prompt) - // - with modifier key pressed: move one symbol left with selection - // - with + modifier keys pressed: move one word left with selection - { - QString txt = textCursor().block().text(); - if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) { - moveCursor( QTextCursor::Up ); - moveCursor( QTextCursor::EndOfBlock ); - } - else { - QTextEdit::keyPressEvent( event ); - } - break; - } - case Qt::Key_Right: - // arrow key: process as follows: - // - without , modifiers: move one symbol right (taking into account prompt) - // - with modifier key pressed: move one word right (taking into account prompt) - // - with modifier key pressed: move one symbol right with selection - // - with + modifier keys pressed: move one word right with selection - { - QString txt = textCursor().block().text(); - if ( !shftPressed ) { - if ( curCol < txt.length() ) { - if ( isCommand( txt ) && curCol < promptSize() ) { - cur.setPosition( cur.block().position() + promptSize() ); - setTextCursor( cur ); - break; - } - } - else { - if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) { - cur.setPosition( cur.position() + promptSize()+1 ); - setTextCursor( cur ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - break; - } - } - } - QTextEdit::keyPressEvent( event ); - break; - } - case Qt::Key_PageUp: - // key: process as follows: - // - without , modifiers: first command in history - // - with modifier key pressed: move cursor one page up without selection - // - with modifier key pressed: move cursor one page up with selection - // - with + modifier keys pressed: scroll one page up - { - if ( ctrlPressed && shftPressed ) { - verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); - } - else if ( shftPressed || ctrlPressed ) { - bool moved = false; - qreal lastY = cursorRect( cur ).top(); - qreal distance = 0; - // move using movePosition to keep the cursor's x - do { - qreal y = cursorRect( cur ).top(); - distance += qAbs( y - lastY ); - lastY = y; - moved = cur.movePosition( QTextCursor::Up, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - } while ( moved && distance < viewport()->height() ); - if ( moved ) { - cur.movePosition( QTextCursor::Down, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); - } - setTextCursor( cur ); - } - else { - if ( myCmdInHistory < 0 && myHistory.count() > 0 ) { - // set history browsing mode - myCmdInHistory = myHistory.count(); - // remember current command - QTextBlock par = document()->end().previous(); - myCurrentCommand = par.text().remove( 0, promptSize() ); - } - if ( myCmdInHistory > 0 ) { - myCmdInHistory = 0; - // get very first command in the history - QString firstCommand = myHistory.at( myCmdInHistory ); - // print first command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + firstCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_PageDown: - // key: process as follows: - // - without , modifiers: last command in history - // - with modifier key pressed: move cursor one page down without selection - // - with modifier key pressed: move cursor one page down with selection - // - with + modifier keys pressed: scroll one page down - { - if ( ctrlPressed && shftPressed ) { - verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); - } - else if ( shftPressed || ctrlPressed ) { - bool moved = false; - qreal lastY = cursorRect( cur ).top(); - qreal distance = 0; - // move using movePosition to keep the cursor's x - do { - qreal y = cursorRect( cur ).top(); - distance += qAbs( y - lastY ); - lastY = y; - moved = cur.movePosition( QTextCursor::Down, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - } while ( moved && distance < viewport()->height() ); - if ( moved ) { - cur.movePosition( QTextCursor::Up, - shftPressed ? QTextCursor::KeepAnchor : - QTextCursor::MoveAnchor ); - verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); - } - setTextCursor( cur ); - } - else { - if ( myCmdInHistory >= 0 ) { - // unset history browsing mode - myCmdInHistory = -1; - // print current command - moveCursor( QTextCursor::End ); - moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - addText( myPrompt + myCurrentCommand ); - // move cursor to the end - moveCursor( QTextCursor::End ); - } - } - break; - } - case Qt::Key_Home: - // key: process as follows: - // - without , modifiers: move cursor to the beginning of the current line without selection - // - with modifier key pressed: move cursor to the very first symbol without selection - // - with modifier key pressed: move cursor to the beginning of the current line with selection - // - with + modifier keys pressed: move cursor to the very first symbol with selection - { - if ( ctrlPressed ) { - moveCursor( QTextCursor::Start, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - else { - QString txt = textCursor().block().text(); - if ( isCommand( txt ) ) { - if ( shftPressed ) { - if ( curCol > promptSize() ) { - cur.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor ); - cur.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() ); - } - } - else { - cur.movePosition( QTextCursor::StartOfLine ); - cur.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() ); - } - setTextCursor( cur ); - } - else { - moveCursor( QTextCursor::StartOfBlock, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - } - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - } - break; - } - case Qt::Key_End: - // key: process as follows: - // - without , modifiers: move cursor to the end of the current line without selection - // - with modifier key pressed: move cursor to the very last symbol without selection - // - with modifier key pressed: move cursor to the end of the current line with selection - // - with + modifier keys pressed: move cursor to the very last symbol with selection - { - moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock, - shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor ); - break; - } - case Qt::Key_Backspace : - // key: process as follows - // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt) - // - with modifier key pressed: delete previous word - // - with modifier key pressed: delete text from the cursor to the line beginning - // works only for last (command) line - { - if ( cur.hasSelection() ) { - cut(); - } - else if ( cur.position() > document()->end().previous().position() + promptSize() ) { - if ( shftPressed ) { - moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - } - else if ( ctrlPressed ) { - cur.setPosition( document()->end().previous().position() + promptSize(), - QTextCursor::KeepAnchor ); - setTextCursor( cur ); - textCursor().removeSelectedText(); - } - else { - QTextEdit::keyPressEvent( event ); - } - } - else { - cur.setPosition( document()->end().previous().position() + promptSize() ); - setTextCursor( cur ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - } - break; - } - case Qt::Key_Delete : - // key: process as follows - // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt) - // - with modifier key pressed: delete next word - // - with modifier key pressed: delete text from the cursor to the end of line - // works only for last (command) line - { - if ( cur.hasSelection() ) { - cut(); - } - else if ( cur.position() > document()->end().previous().position() + promptSize()-1 ) { - if ( shftPressed ) { - moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - } - else if ( ctrlPressed ) { - moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor ); - textCursor().removeSelectedText(); - } - else { - QTextEdit::keyPressEvent( event ); - } - } - else { - cur.setPosition( document()->end().previous().position() + promptSize() ); - setTextCursor( cur ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - } - break; - } - case Qt::Key_Insert : - // key: process as follows - // - with modifier key pressed: copy() - // - with modifier key pressed: paste() to the command line - { - if ( ctrlPressed ) { - copy(); - } - else if ( shftPressed ) { - paste(); - } - else - QTextEdit::keyPressEvent( event ); - break; - } - } -} - -/*! - \brief Handle notification event coming from Python dispatcher. - \param event notification event -*/ -void PyConsole_Editor::customEvent( QEvent* event ) -{ - switch( event->type() ) - { - case PrintEvent::EVENT_ID: - { - PrintEvent* pe=(PrintEvent*)event; - addText( pe->text(), false, pe->isError()); - return; - } - case PyInterp_Event::ES_OK: - case PyInterp_Event::ES_ERROR: - { - // clear command buffer - myCommandBuffer.truncate( 0 ); - // add caret return line if necessary - QTextBlock par = document()->end().previous(); - QString txt = par.text(); - txt.truncate( txt.length() - 1 ); - // IPAL19397 : addText moved to handleReturn() method - //if ( !txt.isEmpty() ) - // addText( "", true ); - // set "ready" prompt - myPrompt = READY_PROMPT; - addText( myPrompt ); - // unset busy cursor - unsetCursor(); - // stop event loop (if running) - if ( myEventLoop ) - myEventLoop->exit(); - break; - } - case PyInterp_Event::ES_INCOMPLETE: - { - // extend command buffer (multi-line command) - myCommandBuffer.append( "\n" ); - // add caret return line if necessary - QTextBlock par = document()->end().previous(); - QString txt = par.text(); - txt.truncate( txt.length() - 1 ); - // IPAL19397 : addText moved to handleReturn() method - //if ( !txt.isEmpty() ) - // addText( "", true ); - // set "dot" prompt - myPrompt = DOTS_PROMPT; - addText( myPrompt/*, true*/ ); // IPAL19397 - // unset busy cursor - unsetCursor(); - // stop event loop (if running) - if ( myEventLoop ) - myEventLoop->exit(); - break; - } - default: - QTextEdit::customEvent( event ); - } - - // unset read-only state - setReadOnly( false ); - // unset history browsing mode - myCmdInHistory = -1; - - if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 ) - { - // process the next sheduled command from the queue (if there is any) - QString nextcmd = myQueue[0]; - myQueue.pop_front(); - exec( nextcmd ); - } -} - -/*! - \brief Handle Python interpreter change. - - Perform initialization actions if the interpreter is changed. - \param interp python interpreter is being set -*/ -void PyConsole_Editor::onPyInterpChanged( PyConsole_Interp* interp ) -{ - if ( myInterp != interp - // Force read-only state and wait cursor when myInterp is NULL - || !myInterp ) { - myInterp = interp; - if ( myInterp ) { - // print banner - myBanner = myInterp->getbanner().c_str(); - if ( isShowBanner() ) - addText( myBanner ); - // clear command buffer - myCommandBuffer.truncate(0); - // unset read-only state - setReadOnly( false ); - // unset history browsing mode - myCmdInHistory = -1; - // add prompt - addText( myPrompt ); - // unset busy cursor - viewport()->unsetCursor(); - // stop event loop (if running) - if( myEventLoop) - myEventLoop->exit(); - } - else { - // clear contents - clear(); - // set read-only state - setReadOnly( true ); - // set busy cursor - setCursor( Qt::WaitCursor ); - } - } -} - -/*! - \brief "Copy" operation. - - Reimplemented from Qt. - Warning! In Qt4 this method is not virtual. - */ -void PyConsole_Editor::cut() -{ - QTextCursor cur = textCursor(); - if ( cur.hasSelection() ) { - QApplication::clipboard()->setText( cur.selectedText() ); - int startSelection = cur.selectionStart(); - if ( startSelection < document()->end().previous().position() + promptSize() ) - startSelection = document()->end().previous().position() + promptSize(); - int endSelection = cur.selectionEnd(); - if ( endSelection < document()->end().previous().position() + promptSize() ) - endSelection = document()->end().previous().position() + promptSize(); - cur.setPosition( startSelection ); - cur.setPosition( endSelection, QTextCursor::KeepAnchor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - setTextCursor( cur ); - textCursor().removeSelectedText(); - } -} - -/*! - \brief "Paste" operation. - - Reimplemented from Qt. - Warning! In Qt4 this method is not virtual. - */ -void PyConsole_Editor::paste() -{ - QTextCursor cur = textCursor(); - if ( cur.hasSelection() ) { - int startSelection = cur.selectionStart(); - if ( startSelection < document()->end().previous().position() + promptSize() ) - startSelection = document()->end().previous().position() + promptSize(); - int endSelection = cur.selectionEnd(); - if ( endSelection < document()->end().previous().position() + promptSize() ) - endSelection = document()->end().previous().position() + promptSize(); - cur.setPosition( startSelection ); - cur.setPosition( endSelection, QTextCursor::KeepAnchor ); - horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() ); - setTextCursor( cur ); - textCursor().removeSelectedText(); - } - if ( textCursor().position() < document()->end().previous().position() + promptSize() ) - moveCursor( QTextCursor::End ); - QTextEdit::paste(); -} - -/*! - \brief "Clear" operation. - - Reimplemented from Qt. - Warning! In Qt4 this method is not virtual. - */ -void PyConsole_Editor::clear() -{ - QTextEdit::clear(); - if ( isShowBanner() ) - addText( myBanner ); - myPrompt = READY_PROMPT; - addText( myPrompt ); -} - -/*! - \brief "Dump commands" operation. - */ -/*void PyConsole_Editor::dump() -{ - QStringList aFilters; - aFilters.append( tr( "PYTHON_FILES_FILTER" ) ); - - QString fileName = SUIT_FileDlg::getFileName( this, QString(), - aFilters, tr( "TOT_DUMP_PYCOMMANDS" ), - false, true, new DumpCommandsFileValidator( this ) ); - if ( fileName != "" ) { - QFile file( fileName ); - if ( !file.open( QFile::WriteOnly ) ) - return; - - QTextStream out (&file); - - for( int i=0; i + +#include +#include +#include +#include +#include +#include + +#include "PyConsole_EnhEditor.h" +#include "PyConsole_EnhInterp.h" +#include "PyConsole_Request.h" +#include "PyInterp_Dispatcher.h" + +// Initialize list of valid separators +static const char * tmp_a[] = {" ", "(", "[","+", "-", "*", "/", ";", "^", "="}; +const std::vector PyConsole_EnhEditor::SEPARATORS = \ + std::vector(tmp_a, tmp_a + sizeof(tmp_a)/sizeof(tmp_a[0])); + +/** + * Constructor. + * @param interp the interpreter linked to the editor + * @param parent parent widget + */ +PyConsole_EnhEditor::PyConsole_EnhEditor(PyConsole_EnhInterp * interp, QWidget * parent) : + PyConsole_Editor(interp, parent), + _tab_mode(false), + _cursor_pos(-1), + _multi_line_paste(false), + _multi_line_content() +{ + document()->setUndoRedoEnabled(true); +} + +/** + * Overrides. Catches the TAB and Ctrl+TAB combinations. + * @param event + */ +void PyConsole_EnhEditor::keyPressEvent ( QKeyEvent* event) +{ + // check if is pressed + bool ctrlPressed = event->modifiers() & Qt::ControlModifier; + // check if is pressed + bool shftPressed = event->modifiers() & Qt::ShiftModifier; + + if (event->key() == Qt::Key_Tab && !shftPressed) + { + if (!ctrlPressed) + handleTab(); + else + { + clearCompletion(); + handleBackTab(); + } + PyConsole_Editor::keyPressEvent(event); + } + else + { + // If ctrl is not pressed (and sth else is pressed with it), + // or if ctrl is not pressed alone + if (!ctrlPressed || (ctrlPressed && event->key() != Qt::Key_Control)) + { + clearCompletion(); + _cursor_pos = -1; + } + // Discard ctrl pressed alone: + if (event->key() != Qt::Key_Control) + PyConsole_Editor::keyPressEvent(event); + } +} + +/** + * Whenever the mouse is clicked, clear the completion. + * @param e + */ +void PyConsole_EnhEditor::mousePressEvent(QMouseEvent* e) +{ + clearCompletion(); + _cursor_pos = -1; + PyConsole_Editor::mousePressEvent(e); +} + +/** + * Clear in the editor the block of text displayed after having hit . + */ +void PyConsole_EnhEditor::clearCompletion() +{ + // Delete completion text if present + if (_tab_mode) + { + // Remove completion display + document()->undo(); + // Remove trailing line return: + QTextCursor tc(textCursor()); + tc.setPosition(document()->characterCount()-1); + setTextCursor(tc); + textCursor().deletePreviousChar(); + // TODO: before wait for any TAB event to be completed + static_cast(myInterp)->clearCompletion(); + } + _tab_mode = false; +} + +/** + * Handle the sequence of events after having hit + */ +void PyConsole_EnhEditor::handleTab() +{ + if (_tab_mode) + { + // Already tab mode - nothing to do ! + return; + } + + QTextCursor cursor(textCursor()); + + // Cursor at end of input + cursor.movePosition(QTextCursor::End); + setTextCursor(cursor); + + // Save cursor position if needed + if (_cursor_pos == -1) + _cursor_pos = textCursor().position(); + + // get last line + QTextBlock par = document()->end().previous(); + if ( !par.isValid() ) return; + + // Switch to completion mode + _tab_mode = true; + + QString cmd = par.text().mid(promptSize()); + + // Post completion request + // Editor will be informed via a custom event that completion has been run + PyInterp_Request* req = createTabRequest(cmd); + PyInterp_Dispatcher::Get()->Exec(req); +} + +/** + * Handles what happens after hitting Ctrl-TAB + */ +void PyConsole_EnhEditor::handleBackTab() +{ + QTextCursor cursor(textCursor()); + + if (_cursor_pos == -1) + { + // Invalid cursor position - we can't do anything: + return; + } + // Ensure cursor is at the end of command line + cursor.setPosition(_cursor_pos); + cursor.movePosition(QTextCursor::EndOfLine); + //setCursor(cursor); + + // Delete last completed text + int i = cursor.position() - _cursor_pos; + cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i); + cursor.removeSelectedText(); + _cursor_pos = -1; +} + +/** + * Create the Python requested that will be posted to the interpreter to + * get the completions. + * @param input line typed by the user at the time TAB was hit + * @return a CompletionCommand + * @sa CompletionCommand + */ +PyInterp_Request* PyConsole_EnhEditor::createTabRequest( const QString& input ) +{ + // Parse input to extract on what part the dir() has to be executed + QString input2(input); + + // Split up to the last syntaxical separator + int lastSp = -1; + for (std::vector::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++) + { + int j = input2.lastIndexOf(*i); + if (j > lastSp) + lastSp = j; + } + if (lastSp >= 0) + input2 = input.mid(lastSp+1); + + // Detect a qualified name (with a point) + int lastPt = input2.lastIndexOf(QString(".")); + + // Split the 2 surrounding parts of the qualified name + if (lastPt != -1) + { + _compl_before_point = input2.left(lastPt); + _compl_after_point = input2.mid(lastPt+1); + } + else + { + // No point found - do a global matching - + // (the following will call dir() with an empty string) + _compl_after_point = input2; + _compl_before_point = QString(""); + } + + return new CompletionCommand( static_cast(myInterp), _compl_before_point, + _compl_after_point, this, isSync() ); +} + +/** + * Format completion results - this is where we should create 3 columns etc ... + * @param matches list of possible completions + * @param result return value + */ +void PyConsole_EnhEditor::formatCompletion(const std::vector & matches, QString & result) const +{ + int sz = matches.size(); + + if (sz > MAX_COMPLETIONS) + { + sz = MAX_COMPLETIONS; + result.append("[Too many matches! Displaying first ones only ...]\n"); + } + + for (int i = 0; i < sz; ++i) + { + result.append(matches[i]); + result.append("\n"); + } +} + +/** + * Override. Catches the events generated by the enhanced interpreter after the execution + * of a completion request. + * @param event + */ +void PyConsole_EnhEditor::customEvent( QEvent* event ) +{ + std::vector matches; + QString first_match, comple_text, doc, base; + QTextCursor cursor(textCursor()); + QTextBlockFormat bf; + QTextCharFormat cf; + PyConsole_EnhInterp * interp = static_cast(myInterp); + int cursorPos; + + switch( event->type() ) + { + case PyInterp_Event::ES_TAB_COMPLETE_OK: + // Extract corresponding matches from the interpreter + matches = interp->getLastMatches(); + + if (matches.size() == 0) + { + // Completion successful but nothing returned. + _tab_mode = false; + _cursor_pos = -1; + return; + } + + // Only one match - complete directly and update doc string window + doc = interp->getDocStr(); + if (matches.size() == 1) + { + first_match = matches[0].mid(_compl_after_point.size()); + cursor.insertText(first_match); + _tab_mode = false; + if (doc == QString("")) + emit updateDoc(formatDocHTML("(no documentation available)\n")); + else + emit updateDoc(formatDocHTML(doc)); + } + else + { + // Detect if there is a common base to all available completion + // In this case append this base to the text already + extractCommon(matches, base); + first_match = base.mid(_compl_after_point.size()); + cursor.insertText(first_match); + + // If this happens to match exaclty the first completion + // also provide doc + if (base == matches[0]) + { + doc = formatDocHTML(doc); + emit updateDoc(doc); + } + + // Print all matching completion in a "undo-able" block + cursorPos = cursor.position(); + cursor.insertBlock(); + cursor.beginEditBlock(); + + // Insert all matches + QTextCharFormat cf; + cf.setForeground(QBrush(Qt::darkGreen)); + cursor.setCharFormat(cf); + formatCompletion(matches, comple_text); + cursor.insertText(comple_text); + cursor.endEditBlock(); + + // Position cursor where it was before inserting the completion list: + cursor.setPosition(cursorPos); + setTextCursor(cursor); + } + break; + case PyInterp_Event::ES_TAB_COMPLETE_ERR: + // Tab completion was unsuccessful, switch off mode: + _tab_mode = false; + _cursor_pos = -1; + break; + case PyInterp_Event::ES_OK: + case PyInterp_Event::ES_ERROR: + case PyInterp_Event::ES_INCOMPLETE: + // Before everything else, call super() + PyConsole_Editor::customEvent(event); + // If we are in multi_paste_mode, process the next item: + multiLineProcessNextLine(); + break; + default: + PyConsole_Editor::customEvent( event ); + break; + } +} + +/** + * Extract the common leading part of all strings in matches. + * @param matches + * @param result + */ +void PyConsole_EnhEditor::extractCommon(const std::vector & matches, QString & result) const +{ + result = ""; + int charIdx = 0; + + if (matches.size() < 2) + return; + + while (true) + { + if (charIdx >= matches[0].size()) + return; + QChar ch = matches[0][charIdx]; + for (size_t j = 1; j < matches.size(); j++) + if (charIdx >= matches[j].size() || matches[j][charIdx] != ch) + return; + result += ch; + charIdx++; + } +} + +/** + * Format the doc string in HTML format with the first line in bold blue + * @param doc initial doc string + * @return HTML string + */ +QString PyConsole_EnhEditor::formatDocHTML(const QString & doc) const +{ + QString templ = QString("\n ") + + QString(" ") + + QString(" ") + + QString("\n") + + QString("

") + + QString("%1

") + + QString("

%2

") + + QString(""); + + QString fst, rest(""); + + // Extract first line of doc + int idx = doc.indexOf("\n"); + if (idx > 0) + { + fst = doc.left(idx); + rest = doc.mid(idx+1); + } + else + { + fst = doc; + } + + fst = fst.replace("\n", " "); + rest = rest.replace("\n", " "); + return templ.arg(fst).arg(rest); +} + +/** + * Handle properly multi-line pasting. Qt4 doc recommends overriding this function. + * If the pasted text doesn't contain a line return, no special treatment is done. + * @param source + */ +void PyConsole_EnhEditor::insertFromMimeData(const QMimeData * source) +{ + if (_multi_line_paste) + return; + + if (source->hasText()) + { + QString s = source->text(); + if (s.contains("\n")) + multilinePaste(s); + else + PyConsole_Editor::insertFromMimeData(source); + } + else + { + PyConsole_Editor::insertFromMimeData(source); + } +} + + +void PyConsole_EnhEditor::multilinePaste(const QString & s) +{ + // Turn on multi line pasting mode + _multi_line_paste = true; + + // Split the string: + QString s2 = s; + s2.replace("\r", ""); // Windows string format converted to Unix style + + QStringList lst = s2.split(QChar('\n'), QString::KeepEmptyParts); + + // Perform the proper paste operation for the first line to handle the case where + // sth was already there: + QMimeData source; + source.setText(lst[0]); + PyConsole_Editor::insertFromMimeData(&source); + + // Prepare what will have to be executed after the first line: + _multi_line_content = std::queue(); + for (int i = 1; i < lst.size(); ++i) + _multi_line_content.push(lst[i]); + + // Trigger the execution of the first (mixed) line + handleReturn(); + + // See customEvent() and multiLineProcessNext() for the rest of the handling. +} + +/** + * Process the next line in the queue of a multiple copy/paste: + */ +void PyConsole_EnhEditor::multiLineProcessNextLine() +{ + if (!_multi_line_paste) + return; + + QString line(_multi_line_content.front()); + _multi_line_content.pop(); + if (!_multi_line_content.size()) + { + // last line in the queue, just paste it + addText(line, false, false); + _multi_line_paste = false; + } + else + { + // paste the line and simulate a key stroke + addText(line, false, false); + handleReturn(); + } +} diff --git a/src/PyConsole/PyConsole_EnhEditor.cxx b/src/PyConsole/PyConsole_EnhEditor.cxx deleted file mode 100644 index d5c47dff3..000000000 --- a/src/PyConsole/PyConsole_EnhEditor.cxx +++ /dev/null @@ -1,464 +0,0 @@ - - -#include "PyConsole.h" -#include - -#include -#include -#include -#include -#include -#include - -#include "PyConsole_EnhEditor.h" -#include "PyConsole_EnhInterp.h" -#include "PyConsole_Request.h" -#include "PyInterp_Dispatcher.h" - -// Initialize list of valid separators -static const char * tmp_a[] = {" ", "(", "[","+", "-", "*", "/", ";", "^", "="}; -const std::vector PyConsole_EnhEditor::SEPARATORS = \ - std::vector(tmp_a, tmp_a + sizeof(tmp_a)/sizeof(tmp_a[0])); - -/** - * Constructor. - * @param interp the interpreter linked to the editor - * @param parent parent widget - */ -PyConsole_EnhEditor::PyConsole_EnhEditor(PyConsole_EnhInterp * interp, QWidget * parent) : - PyConsole_Editor(interp, parent), - _tab_mode(false), - _cursor_pos(-1), - _multi_line_paste(false), - _multi_line_content() -{ - document()->setUndoRedoEnabled(true); -} - -/** - * Overrides. Catches the TAB and Ctrl+TAB combinations. - * @param event - */ -void PyConsole_EnhEditor::keyPressEvent ( QKeyEvent* event) -{ - // check if is pressed - bool ctrlPressed = event->modifiers() & Qt::ControlModifier; - // check if is pressed - bool shftPressed = event->modifiers() & Qt::ShiftModifier; - - if (event->key() == Qt::Key_Tab && !shftPressed) - { - if (!ctrlPressed) - handleTab(); - else - { - clearCompletion(); - handleBackTab(); - } - PyConsole_Editor::keyPressEvent(event); - } - else - { - // If ctrl is not pressed (and sth else is pressed with it), - // or if ctrl is not pressed alone - if (!ctrlPressed || (ctrlPressed && event->key() != Qt::Key_Control)) - { - clearCompletion(); - _cursor_pos = -1; - } - // Discard ctrl pressed alone: - if (event->key() != Qt::Key_Control) - PyConsole_Editor::keyPressEvent(event); - } -} - -/** - * Whenever the mouse is clicked, clear the completion. - * @param e - */ -void PyConsole_EnhEditor::mousePressEvent(QMouseEvent* e) -{ - clearCompletion(); - _cursor_pos = -1; - PyConsole_Editor::mousePressEvent(e); -} - -/** - * Clear in the editor the block of text displayed after having hit . - */ -void PyConsole_EnhEditor::clearCompletion() -{ - // Delete completion text if present - if (_tab_mode) - { - // Remove completion display - document()->undo(); - // Remove trailing line return: - QTextCursor tc(textCursor()); - tc.setPosition(document()->characterCount()-1); - setTextCursor(tc); - textCursor().deletePreviousChar(); - // TODO: before wait for any TAB event to be completed - static_cast(myInterp)->clearCompletion(); - } - _tab_mode = false; -} - -/** - * Handle the sequence of events after having hit - */ -void PyConsole_EnhEditor::handleTab() -{ - if (_tab_mode) - { - // Already tab mode - nothing to do ! - return; - } - - QTextCursor cursor(textCursor()); - - // Cursor at end of input - cursor.movePosition(QTextCursor::End); - setTextCursor(cursor); - - // Save cursor position if needed - if (_cursor_pos == -1) - _cursor_pos = textCursor().position(); - - // get last line - QTextBlock par = document()->end().previous(); - if ( !par.isValid() ) return; - - // Switch to completion mode - _tab_mode = true; - - QString cmd = par.text().mid(promptSize()); - - // Post completion request - // Editor will be informed via a custom event that completion has been run - PyInterp_Request* req = createTabRequest(cmd); - PyInterp_Dispatcher::Get()->Exec(req); -} - -/** - * Handles what happens after hitting Ctrl-TAB - */ -void PyConsole_EnhEditor::handleBackTab() -{ - QTextCursor cursor(textCursor()); - - if (_cursor_pos == -1) - { - // Invalid cursor position - we can't do anything: - return; - } - // Ensure cursor is at the end of command line - cursor.setPosition(_cursor_pos); - cursor.movePosition(QTextCursor::EndOfLine); - //setCursor(cursor); - - // Delete last completed text - int i = cursor.position() - _cursor_pos; - cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i); - cursor.removeSelectedText(); - _cursor_pos = -1; -} - -/** - * Create the Python requested that will be posted to the interpreter to - * get the completions. - * @param input line typed by the user at the time TAB was hit - * @return a CompletionCommand - * @sa CompletionCommand - */ -PyInterp_Request* PyConsole_EnhEditor::createTabRequest( const QString& input ) -{ - // Parse input to extract on what part the dir() has to be executed - QString input2(input); - - // Split up to the last syntaxical separator - int lastSp = -1; - for (std::vector::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++) - { - int j = input2.lastIndexOf(*i); - if (j > lastSp) - lastSp = j; - } - if (lastSp >= 0) - input2 = input.mid(lastSp+1); - - // Detect a qualified name (with a point) - int lastPt = input2.lastIndexOf(QString(".")); - - // Split the 2 surrounding parts of the qualified name - if (lastPt != -1) - { - _compl_before_point = input2.left(lastPt); - _compl_after_point = input2.mid(lastPt+1); - } - else - { - // No point found - do a global matching - - // (the following will call dir() with an empty string) - _compl_after_point = input2; - _compl_before_point = QString(""); - } - - return new CompletionCommand( static_cast(myInterp), _compl_before_point, - _compl_after_point, this, isSync() ); -} - -/** - * Format completion results - this is where we should create 3 columns etc ... - * @param matches list of possible completions - * @param result return value - */ -void PyConsole_EnhEditor::formatCompletion(const std::vector & matches, QString & result) const -{ - int sz = matches.size(); - - if (sz > MAX_COMPLETIONS) - { - sz = MAX_COMPLETIONS; - result.append("[Too many matches! Displaying first ones only ...]\n"); - } - - for (int i = 0; i < sz; ++i) - { - result.append(matches[i]); - result.append("\n"); - } -} - -/** - * Override. Catches the events generated by the enhanced interpreter after the execution - * of a completion request. - * @param event - */ -void PyConsole_EnhEditor::customEvent( QEvent* event ) -{ - std::vector matches; - QString first_match, comple_text, doc, base; - QTextCursor cursor(textCursor()); - QTextBlockFormat bf; - QTextCharFormat cf; - PyConsole_EnhInterp * interp = static_cast(myInterp); - int cursorPos; - - switch( event->type() ) - { - case PyInterp_Event::ES_TAB_COMPLETE_OK: - // Extract corresponding matches from the interpreter - matches = interp->getLastMatches(); - - if (matches.size() == 0) - { - // Completion successful but nothing returned. - _tab_mode = false; - _cursor_pos = -1; - return; - } - - // Only one match - complete directly and update doc string window - doc = interp->getDocStr(); - if (matches.size() == 1) - { - first_match = matches[0].mid(_compl_after_point.size()); - cursor.insertText(first_match); - _tab_mode = false; - if (doc == QString("")) - emit updateDoc(formatDocHTML("(no documentation available)\n")); - else - emit updateDoc(formatDocHTML(doc)); - } - else - { - // Detect if there is a common base to all available completion - // In this case append this base to the text already - extractCommon(matches, base); - first_match = base.mid(_compl_after_point.size()); - cursor.insertText(first_match); - - // If this happens to match exaclty the first completion - // also provide doc - if (base == matches[0]) - { - doc = formatDocHTML(doc); - emit updateDoc(doc); - } - - // Print all matching completion in a "undo-able" block - cursorPos = cursor.position(); - cursor.insertBlock(); - cursor.beginEditBlock(); - - // Insert all matches - QTextCharFormat cf; - cf.setForeground(QBrush(Qt::darkGreen)); - cursor.setCharFormat(cf); - formatCompletion(matches, comple_text); - cursor.insertText(comple_text); - cursor.endEditBlock(); - - // Position cursor where it was before inserting the completion list: - cursor.setPosition(cursorPos); - setTextCursor(cursor); - } - break; - case PyInterp_Event::ES_TAB_COMPLETE_ERR: - // Tab completion was unsuccessful, switch off mode: - _tab_mode = false; - _cursor_pos = -1; - break; - case PyInterp_Event::ES_OK: - case PyInterp_Event::ES_ERROR: - case PyInterp_Event::ES_INCOMPLETE: - // Before everything else, call super() - PyConsole_Editor::customEvent(event); - // If we are in multi_paste_mode, process the next item: - multiLineProcessNextLine(); - break; - default: - PyConsole_Editor::customEvent( event ); - break; - } -} - -/** - * Extract the common leading part of all strings in matches. - * @param matches - * @param result - */ -void PyConsole_EnhEditor::extractCommon(const std::vector & matches, QString & result) const -{ - result = ""; - int charIdx = 0; - - if (matches.size() < 2) - return; - - while (true) - { - if (charIdx >= matches[0].size()) - return; - QChar ch = matches[0][charIdx]; - for (size_t j = 1; j < matches.size(); j++) - if (charIdx >= matches[j].size() || matches[j][charIdx] != ch) - return; - result += ch; - charIdx++; - } -} - -/** - * Format the doc string in HTML format with the first line in bold blue - * @param doc initial doc string - * @return HTML string - */ -QString PyConsole_EnhEditor::formatDocHTML(const QString & doc) const -{ - QString templ = QString("\n ") + - QString(" ") + - QString(" ") + - QString("\n") + - QString("

") + - QString("%1

") + - QString("

%2

") + - QString(""); - - QString fst, rest(""); - - // Extract first line of doc - int idx = doc.indexOf("\n"); - if (idx > 0) - { - fst = doc.left(idx); - rest = doc.mid(idx+1); - } - else - { - fst = doc; - } - - fst = fst.replace("\n", " "); - rest = rest.replace("\n", " "); - return templ.arg(fst).arg(rest); -} - -/** - * Handle properly multi-line pasting. Qt4 doc recommends overriding this function. - * If the pasted text doesn't contain a line return, no special treatment is done. - * @param source - */ -void PyConsole_EnhEditor::insertFromMimeData(const QMimeData * source) -{ - if (_multi_line_paste) - return; - - if (source->hasText()) - { - QString s = source->text(); - if (s.contains("\n")) - multilinePaste(s); - else - PyConsole_Editor::insertFromMimeData(source); - } - else - { - PyConsole_Editor::insertFromMimeData(source); - } -} - - -void PyConsole_EnhEditor::multilinePaste(const QString & s) -{ - // Turn on multi line pasting mode - _multi_line_paste = true; - - // Split the string: - QString s2 = s; - s2.replace("\r", ""); // Windows string format converted to Unix style - - QStringList lst = s2.split(QChar('\n'), QString::KeepEmptyParts); - - // Perform the proper paste operation for the first line to handle the case where - // sth was already there: - QMimeData source; - source.setText(lst[0]); - PyConsole_Editor::insertFromMimeData(&source); - - // Prepare what will have to be executed after the first line: - _multi_line_content = std::queue(); - for (int i = 1; i < lst.size(); ++i) - _multi_line_content.push(lst[i]); - - // Trigger the execution of the first (mixed) line - handleReturn(); - - // See customEvent() and multiLineProcessNext() for the rest of the handling. -} - -/** - * Process the next line in the queue of a multiple copy/paste: - */ -void PyConsole_EnhEditor::multiLineProcessNextLine() -{ - if (!_multi_line_paste) - return; - - QString line(_multi_line_content.front()); - _multi_line_content.pop(); - if (!_multi_line_content.size()) - { - // last line in the queue, just paste it - addText(line, false, false); - _multi_line_paste = false; - } - else - { - // paste the line and simulate a key stroke - addText(line, false, false); - handleReturn(); - } -} diff --git a/src/PyConsole/PyConsole_EnhInterp.cpp b/src/PyConsole/PyConsole_EnhInterp.cpp new file mode 100644 index 000000000..4332f3112 --- /dev/null +++ b/src/PyConsole/PyConsole_EnhInterp.cpp @@ -0,0 +1,135 @@ + + + +#include "PyConsole.h" + +#include "PyConsole_EnhInterp.h" + +#include +#include +#include + +static const char * tmp_k[] = {"and", "as", "assert", "break", "class", + "continue", "def", "del", + "elif", "else", "except", "exec", "finally", "for", "from", "global", "if", + "import", "in", "is", "lambda", "not", "or", "pass", "print", "raise", + "return", "try", "while", "with", "yield"}; + +const std::vector PyConsole_EnhInterp::PYTHON_KEYWORDS = \ + std::vector(tmp_k, tmp_k+sizeof(tmp_k)/sizeof(tmp_k[0])); + +/*! + \brief Run Python dir() command and saves the result internally in _lastPy + \param dirArgument Python expression to pass to the dir command. The parsing of what the + user actually started typing is dedicated to the caller + \param startMatch string representing the begining of the patter to be completed. For example when + the user types "a_string_variable.rsp ", this is "rsp". + \return command exit status - 0 = success +*/ +int PyConsole_EnhInterp::runDirCommand(const QString & dirArgument, const QString & startMatch) +{ + int ret; + std::vector v; + + clearCompletion(); + if ( (ret = runDirAndExtract(dirArgument, startMatch, _last_matches)) ) + return ret; + + // If dirArgument is empty, we append the __builtins__ + if (dirArgument.isEmpty()) + { + if ( (ret = runDirAndExtract(QString("__builtins__"), startMatch, _last_matches, false)) ) + return ret; + + // ... and we match on Python's keywords as well: + for (std::vector::const_iterator it = PYTHON_KEYWORDS.begin(); it != PYTHON_KEYWORDS.end(); it++) + if ((*it).startsWith(startMatch)) + _last_matches.push_back(*it); + } + + // Try to get doc string of the first match + if (_last_matches.size() > 0) + { + QString cmd(""); + if (dirArgument.trimmed() != "") + cmd = dirArgument + "."; + cmd += _last_matches[0] + ".__doc__"; + PyObject * str = PyRun_String(cmd.toStdString().c_str(), Py_eval_input, _g, _g); + if (!str || str == Py_None || !PyString_Check(str)) + { + if (!str) + PyErr_Clear(); + _doc_str = ""; + } + else + _doc_str = QString(PyString_AsString(str)); + Py_XDECREF(str); + } + + // The command has been successfully executed + return 0; +} + +/** + * See runDirCommand(). + * @param dirArgument see runDirCommand() + * @param startMatch see runDirCommand() + * @param[out] result the list of matches + * @param discardSwig if true a regular expression is used to discard all static method generated + * by SWIG. typically: MEDCouplingUMesh_Blabla + * @return -1 if the call to dir() or the parsing of the result failed, 0 otherwise. + */ +int PyConsole_EnhInterp::runDirAndExtract(const QString& dirArgument, + const QString & startMatch, std::vector & result, + bool discardSwig) const +{ + QRegExp re("^[A-Z].+_[A-Z]+[a-z]+.+$"); // discard SWIG static method, e.g. MEDCouplingUMesh_Blabla + QString command("dir(" + dirArgument + ")"); + PyObject * plst = PyRun_String(command.toStdString().c_str(), Py_eval_input, _g, _g); + if(!plst || plst == Py_None) { + if(!plst) + PyErr_Clear(); + + Py_XDECREF(plst); + return -1; + } + + // Extract the returned list and convert it to a vector<> + if (!PySequence_Check(plst)) + { + // Should never happen ... + //std::cerr << "not a list!" << std::endl; + Py_XDECREF(plst); + return -1; + } + + // Convert plst to a vector of QString + int n = PySequence_Length(plst); + for (int i = 0; i < n; i++) + { + PyObject * it; + it = PySequence_GetItem(plst, i); + QString s(PyString_AsString(it)); + // if the method is not from swig, not static (guessed from the reg exp) and matches + // what is already there + if (s.startsWith(startMatch)) + if(!discardSwig || (!re.exactMatch(s) && !s.contains("swig"))) + result.push_back(s); + Py_DECREF(it); + } + Py_DECREF(plst); + + return 0; +} + +/** + * Clear internal members containing the last completion results. + */ +void PyConsole_EnhInterp::clearCompletion() +{ + _last_matches.resize(0); + _doc_str = QString(""); +} + + + diff --git a/src/PyConsole/PyConsole_EnhInterp.cxx b/src/PyConsole/PyConsole_EnhInterp.cxx deleted file mode 100644 index 4332f3112..000000000 --- a/src/PyConsole/PyConsole_EnhInterp.cxx +++ /dev/null @@ -1,135 +0,0 @@ - - - -#include "PyConsole.h" - -#include "PyConsole_EnhInterp.h" - -#include -#include -#include - -static const char * tmp_k[] = {"and", "as", "assert", "break", "class", - "continue", "def", "del", - "elif", "else", "except", "exec", "finally", "for", "from", "global", "if", - "import", "in", "is", "lambda", "not", "or", "pass", "print", "raise", - "return", "try", "while", "with", "yield"}; - -const std::vector PyConsole_EnhInterp::PYTHON_KEYWORDS = \ - std::vector(tmp_k, tmp_k+sizeof(tmp_k)/sizeof(tmp_k[0])); - -/*! - \brief Run Python dir() command and saves the result internally in _lastPy - \param dirArgument Python expression to pass to the dir command. The parsing of what the - user actually started typing is dedicated to the caller - \param startMatch string representing the begining of the patter to be completed. For example when - the user types "a_string_variable.rsp ", this is "rsp". - \return command exit status - 0 = success -*/ -int PyConsole_EnhInterp::runDirCommand(const QString & dirArgument, const QString & startMatch) -{ - int ret; - std::vector v; - - clearCompletion(); - if ( (ret = runDirAndExtract(dirArgument, startMatch, _last_matches)) ) - return ret; - - // If dirArgument is empty, we append the __builtins__ - if (dirArgument.isEmpty()) - { - if ( (ret = runDirAndExtract(QString("__builtins__"), startMatch, _last_matches, false)) ) - return ret; - - // ... and we match on Python's keywords as well: - for (std::vector::const_iterator it = PYTHON_KEYWORDS.begin(); it != PYTHON_KEYWORDS.end(); it++) - if ((*it).startsWith(startMatch)) - _last_matches.push_back(*it); - } - - // Try to get doc string of the first match - if (_last_matches.size() > 0) - { - QString cmd(""); - if (dirArgument.trimmed() != "") - cmd = dirArgument + "."; - cmd += _last_matches[0] + ".__doc__"; - PyObject * str = PyRun_String(cmd.toStdString().c_str(), Py_eval_input, _g, _g); - if (!str || str == Py_None || !PyString_Check(str)) - { - if (!str) - PyErr_Clear(); - _doc_str = ""; - } - else - _doc_str = QString(PyString_AsString(str)); - Py_XDECREF(str); - } - - // The command has been successfully executed - return 0; -} - -/** - * See runDirCommand(). - * @param dirArgument see runDirCommand() - * @param startMatch see runDirCommand() - * @param[out] result the list of matches - * @param discardSwig if true a regular expression is used to discard all static method generated - * by SWIG. typically: MEDCouplingUMesh_Blabla - * @return -1 if the call to dir() or the parsing of the result failed, 0 otherwise. - */ -int PyConsole_EnhInterp::runDirAndExtract(const QString& dirArgument, - const QString & startMatch, std::vector & result, - bool discardSwig) const -{ - QRegExp re("^[A-Z].+_[A-Z]+[a-z]+.+$"); // discard SWIG static method, e.g. MEDCouplingUMesh_Blabla - QString command("dir(" + dirArgument + ")"); - PyObject * plst = PyRun_String(command.toStdString().c_str(), Py_eval_input, _g, _g); - if(!plst || plst == Py_None) { - if(!plst) - PyErr_Clear(); - - Py_XDECREF(plst); - return -1; - } - - // Extract the returned list and convert it to a vector<> - if (!PySequence_Check(plst)) - { - // Should never happen ... - //std::cerr << "not a list!" << std::endl; - Py_XDECREF(plst); - return -1; - } - - // Convert plst to a vector of QString - int n = PySequence_Length(plst); - for (int i = 0; i < n; i++) - { - PyObject * it; - it = PySequence_GetItem(plst, i); - QString s(PyString_AsString(it)); - // if the method is not from swig, not static (guessed from the reg exp) and matches - // what is already there - if (s.startsWith(startMatch)) - if(!discardSwig || (!re.exactMatch(s) && !s.contains("swig"))) - result.push_back(s); - Py_DECREF(it); - } - Py_DECREF(plst); - - return 0; -} - -/** - * Clear internal members containing the last completion results. - */ -void PyConsole_EnhInterp::clearCompletion() -{ - _last_matches.resize(0); - _doc_str = QString(""); -} - - - diff --git a/src/PyConsole/PyConsole_Event.cpp b/src/PyConsole/PyConsole_Event.cpp new file mode 100644 index 000000000..a3e2b05ae --- /dev/null +++ b/src/PyConsole/PyConsole_Event.cpp @@ -0,0 +1,3 @@ + + +#include "PyConsole_Event.h" diff --git a/src/PyConsole/PyConsole_Event.cxx b/src/PyConsole/PyConsole_Event.cxx deleted file mode 100644 index a3e2b05ae..000000000 --- a/src/PyConsole/PyConsole_Event.cxx +++ /dev/null @@ -1,3 +0,0 @@ - - -#include "PyConsole_Event.h" diff --git a/src/PyConsole/PyConsole_Interp.cpp b/src/PyConsole/PyConsole_Interp.cpp new file mode 100644 index 000000000..21af783d1 --- /dev/null +++ b/src/PyConsole/PyConsole_Interp.cpp @@ -0,0 +1,118 @@ + +#include "PyConsole_Interp.h" + +/*! + \class PyConsole_Interp + \brief Python interpreter to be embedded to the SALOME study's GUI. + + Python interpreter is created one per SALOME study. + + Call initialize method defined in the base class PyInterp_Interp, + to intialize interpreter after instance creation. + + The method initialize() calls virtuals methods + - initPython() to initialize global Python interpreter + - initState() to initialize embedded interpreter state + - initContext() to initialize interpreter internal context + - initRun() to prepare interpreter for running commands + + /EDF-CCAR/ + When SALOME uses multi Python interpreter feature, + every study has its own interpreter and thread state (_tstate = Py_NewInterpreter()). + This is fine because every study has its own modules (sys.modules) stdout and stderr. + + But some Python modules must be imported only once. In multi interpreter + context Python modules (*.py) are imported several times. + For example, the PyQt module must be imported only once because + it registers classes in a C module. + + It's quite the same with omniorb modules (internals and generated with omniidl). + + This problem is handled with "shared modules" defined in salome_shared_modules.py. + These "shared modules" are imported only once and only copied in all + the other interpreters. + + But it's not the only problem. Every interpreter has its own + __builtin__ module. That's fine but if we have copied some modules + and imported others problems may arise with operations that are not allowed + in restricted execution environment. So we must impose that all interpreters + have identical __builtin__ module. +*/ + +/*! + \brief Constructor. + + Creates new python interpreter. +*/ +PyConsole_Interp::PyConsole_Interp(): PyInterp_Interp() +{ +} + +/*! + \brief Destructor. + + Does nothing for the moment. +*/ +PyConsole_Interp::~PyConsole_Interp() +{ +} + +/*! + \brief Initialize internal Python interpreter state. + + When calling initState the GIL is not held + It must not be held on exit + + \return \c true on success +*/ +bool PyConsole_Interp::initState() +{ + PyEval_AcquireLock(); + _tstate = Py_NewInterpreter(); // create an interpreter and save current state + PySys_SetArgv(PyInterp_Interp::_argc,PyInterp_Interp::_argv); // initialize sys.argv + + if(!builtinmodule) // PAL18041: deepcopy function don't work in Salome + { + //builtinmodule is static member of PyInterp class + //If it is not NULL (initialized to the builtin module of the main interpreter + //all the sub interpreters will have the same builtin + //_interp is a static member and is the main interpreter + //The first time we initialized it to the builtin of main interpreter + builtinmodule=PyDict_GetItemString(_interp->modules, "__builtin__"); + } + + //If builtinmodule has been initialized all the sub interpreters + // will have the same __builtin__ module + if(builtinmodule){ + PyObject *m = PyImport_GetModuleDict(); + PyDict_SetItemString(m, "__builtin__", builtinmodule); + _tstate->interp->builtins = PyModule_GetDict(builtinmodule); + Py_INCREF(_tstate->interp->builtins); + } + PyEval_ReleaseThread(_tstate); + return true; +} + +/*! + \brief Initialize python interpeter context. + + The GIL is assumed to be held. + It is the caller responsability to acquire the GIL. + It must still be held on initContext() exit. + + \return \c true on success +*/ +bool PyConsole_Interp::initContext() +{ + PyObject *m = PyImport_AddModule("__main__"); // interpreter main module (module context) + if(!m){ + PyErr_Print(); + return false; + } + _g = PyModule_GetDict(m); // get interpreter dictionnary context + + if(builtinmodule){ + PyDict_SetItemString(_g, "__builtins__", builtinmodule); // assign singleton __builtin__ module + } + return true; +} diff --git a/src/PyConsole/PyConsole_Interp.cxx b/src/PyConsole/PyConsole_Interp.cxx deleted file mode 100644 index 21af783d1..000000000 --- a/src/PyConsole/PyConsole_Interp.cxx +++ /dev/null @@ -1,118 +0,0 @@ - -#include "PyConsole_Interp.h" - -/*! - \class PyConsole_Interp - \brief Python interpreter to be embedded to the SALOME study's GUI. - - Python interpreter is created one per SALOME study. - - Call initialize method defined in the base class PyInterp_Interp, - to intialize interpreter after instance creation. - - The method initialize() calls virtuals methods - - initPython() to initialize global Python interpreter - - initState() to initialize embedded interpreter state - - initContext() to initialize interpreter internal context - - initRun() to prepare interpreter for running commands - - /EDF-CCAR/ - When SALOME uses multi Python interpreter feature, - every study has its own interpreter and thread state (_tstate = Py_NewInterpreter()). - This is fine because every study has its own modules (sys.modules) stdout and stderr. - - But some Python modules must be imported only once. In multi interpreter - context Python modules (*.py) are imported several times. - For example, the PyQt module must be imported only once because - it registers classes in a C module. - - It's quite the same with omniorb modules (internals and generated with omniidl). - - This problem is handled with "shared modules" defined in salome_shared_modules.py. - These "shared modules" are imported only once and only copied in all - the other interpreters. - - But it's not the only problem. Every interpreter has its own - __builtin__ module. That's fine but if we have copied some modules - and imported others problems may arise with operations that are not allowed - in restricted execution environment. So we must impose that all interpreters - have identical __builtin__ module. -*/ - -/*! - \brief Constructor. - - Creates new python interpreter. -*/ -PyConsole_Interp::PyConsole_Interp(): PyInterp_Interp() -{ -} - -/*! - \brief Destructor. - - Does nothing for the moment. -*/ -PyConsole_Interp::~PyConsole_Interp() -{ -} - -/*! - \brief Initialize internal Python interpreter state. - - When calling initState the GIL is not held - It must not be held on exit - - \return \c true on success -*/ -bool PyConsole_Interp::initState() -{ - PyEval_AcquireLock(); - _tstate = Py_NewInterpreter(); // create an interpreter and save current state - PySys_SetArgv(PyInterp_Interp::_argc,PyInterp_Interp::_argv); // initialize sys.argv - - if(!builtinmodule) // PAL18041: deepcopy function don't work in Salome - { - //builtinmodule is static member of PyInterp class - //If it is not NULL (initialized to the builtin module of the main interpreter - //all the sub interpreters will have the same builtin - //_interp is a static member and is the main interpreter - //The first time we initialized it to the builtin of main interpreter - builtinmodule=PyDict_GetItemString(_interp->modules, "__builtin__"); - } - - //If builtinmodule has been initialized all the sub interpreters - // will have the same __builtin__ module - if(builtinmodule){ - PyObject *m = PyImport_GetModuleDict(); - PyDict_SetItemString(m, "__builtin__", builtinmodule); - _tstate->interp->builtins = PyModule_GetDict(builtinmodule); - Py_INCREF(_tstate->interp->builtins); - } - PyEval_ReleaseThread(_tstate); - return true; -} - -/*! - \brief Initialize python interpeter context. - - The GIL is assumed to be held. - It is the caller responsability to acquire the GIL. - It must still be held on initContext() exit. - - \return \c true on success -*/ -bool PyConsole_Interp::initContext() -{ - PyObject *m = PyImport_AddModule("__main__"); // interpreter main module (module context) - if(!m){ - PyErr_Print(); - return false; - } - _g = PyModule_GetDict(m); // get interpreter dictionnary context - - if(builtinmodule){ - PyDict_SetItemString(_g, "__builtins__", builtinmodule); // assign singleton __builtin__ module - } - return true; -} diff --git a/src/PyConsole/PyConsole_Request.cpp b/src/PyConsole/PyConsole_Request.cpp new file mode 100644 index 000000000..3e31b90eb --- /dev/null +++ b/src/PyConsole/PyConsole_Request.cpp @@ -0,0 +1,99 @@ + + +#include "PyConsole_Request.h" + +#include "PyInterp_Event.h" +#include "PyConsole_Event.h" +#include "PyConsole_EnhInterp.h" +#include "PyConsole_EnhEditor.h" + +#include + +/** + * Constructor. + * @param theInterp interpreter that will execute the command + * @param theCommand command text + * @param theListener editor object that will receive the response events after execution + * of the request + * @param sync + */ +ExecCommand::ExecCommand( PyInterp_Interp* theInterp, + const QString& theCommand, + PyConsole_Editor* theListener, + bool sync ) + : PyInterp_LockRequest( theInterp, theListener, sync ), + myCommand( theCommand ), myState( PyInterp_Event::ES_OK ) + {} + +/** + * Execute the command by calling the run() method of the embedded interpreter. + */ +void ExecCommand::execute() +{ + if ( myCommand != "" ) + { + int ret = getInterp()->run( myCommand.toUtf8().data() ); + if ( ret < 0 ) + myState = PyInterp_Event::ES_ERROR; + else if ( ret > 0 ) + myState = PyInterp_Event::ES_INCOMPLETE; + } +} + +/** + * Create the event indicating the status of the request execution. + * @return a QEvent + */ +QEvent* ExecCommand::createEvent() +{ + if ( IsSync() ) + QCoreApplication::sendPostedEvents( listener(), PrintEvent::EVENT_ID ); + return new PyInterp_Event( myState, this ); +} + + +/*! + Constructor. + Creates a new python completion request. + \param theInterp python interpreter + \param input string containing the dir() command to be executed + \param startMatch part to be matched with the results of the dir() command + \param theListener widget to get the notification messages + \param sync if True the request is processed synchronously +*/ +CompletionCommand::CompletionCommand( PyConsole_EnhInterp* theInterp, + const QString& input, + const QString& startMatch, + PyConsole_EnhEditor* theListener, + bool sync) + : PyInterp_LockRequest( theInterp, theListener, sync ), + _tabSuccess(false), _dirArg(input), _startMatch(startMatch) +{} + +/** + * Execute the completion command by wrapping the runDirCommand() of the + * embedded enhanced interpreter. + */ +void CompletionCommand::execute() +{ + PyConsole_EnhInterp * interp = static_cast(getInterp()); + int ret = interp->runDirCommand( _dirArg, _startMatch); + if (ret == 0) + _tabSuccess = true; + else + _tabSuccess = false; +} + +/** + * Create the event indicating the return value of the completion command. + * @return + */ +QEvent* CompletionCommand::createEvent() +{ + int typ = _tabSuccess ? PyInterp_Event::ES_TAB_COMPLETE_OK : PyInterp_Event::ES_TAB_COMPLETE_ERR; + + return new PyInterp_Event( typ, this); +} + + + diff --git a/src/PyConsole/PyConsole_Request.cxx b/src/PyConsole/PyConsole_Request.cxx deleted file mode 100644 index 3e31b90eb..000000000 --- a/src/PyConsole/PyConsole_Request.cxx +++ /dev/null @@ -1,99 +0,0 @@ - - -#include "PyConsole_Request.h" - -#include "PyInterp_Event.h" -#include "PyConsole_Event.h" -#include "PyConsole_EnhInterp.h" -#include "PyConsole_EnhEditor.h" - -#include - -/** - * Constructor. - * @param theInterp interpreter that will execute the command - * @param theCommand command text - * @param theListener editor object that will receive the response events after execution - * of the request - * @param sync - */ -ExecCommand::ExecCommand( PyInterp_Interp* theInterp, - const QString& theCommand, - PyConsole_Editor* theListener, - bool sync ) - : PyInterp_LockRequest( theInterp, theListener, sync ), - myCommand( theCommand ), myState( PyInterp_Event::ES_OK ) - {} - -/** - * Execute the command by calling the run() method of the embedded interpreter. - */ -void ExecCommand::execute() -{ - if ( myCommand != "" ) - { - int ret = getInterp()->run( myCommand.toUtf8().data() ); - if ( ret < 0 ) - myState = PyInterp_Event::ES_ERROR; - else if ( ret > 0 ) - myState = PyInterp_Event::ES_INCOMPLETE; - } -} - -/** - * Create the event indicating the status of the request execution. - * @return a QEvent - */ -QEvent* ExecCommand::createEvent() -{ - if ( IsSync() ) - QCoreApplication::sendPostedEvents( listener(), PrintEvent::EVENT_ID ); - return new PyInterp_Event( myState, this ); -} - - -/*! - Constructor. - Creates a new python completion request. - \param theInterp python interpreter - \param input string containing the dir() command to be executed - \param startMatch part to be matched with the results of the dir() command - \param theListener widget to get the notification messages - \param sync if True the request is processed synchronously -*/ -CompletionCommand::CompletionCommand( PyConsole_EnhInterp* theInterp, - const QString& input, - const QString& startMatch, - PyConsole_EnhEditor* theListener, - bool sync) - : PyInterp_LockRequest( theInterp, theListener, sync ), - _tabSuccess(false), _dirArg(input), _startMatch(startMatch) -{} - -/** - * Execute the completion command by wrapping the runDirCommand() of the - * embedded enhanced interpreter. - */ -void CompletionCommand::execute() -{ - PyConsole_EnhInterp * interp = static_cast(getInterp()); - int ret = interp->runDirCommand( _dirArg, _startMatch); - if (ret == 0) - _tabSuccess = true; - else - _tabSuccess = false; -} - -/** - * Create the event indicating the return value of the completion command. - * @return - */ -QEvent* CompletionCommand::createEvent() -{ - int typ = _tabSuccess ? PyInterp_Event::ES_TAB_COMPLETE_OK : PyInterp_Event::ES_TAB_COMPLETE_ERR; - - return new PyInterp_Event( typ, this); -} - - - diff --git a/src/PyEvent/CMakeLists.txt b/src/PyEvent/CMakeLists.txt index 2d5d28bd8..0b54ed93e 100644 --- a/src/PyEvent/CMakeLists.txt +++ b/src/PyEvent/CMakeLists.txt @@ -6,8 +6,8 @@ SET(PROJECT_HEADERS ) SET(PROJECT_SOURCES - PyEvent_Event.cxx - PyEvent_EventFilter.cxx + PyEvent_Event.cpp + PyEvent_EventFilter.cpp ) #INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDES}) diff --git a/src/PyEvent/PyEvent_Event.cpp b/src/PyEvent/PyEvent_Event.cpp new file mode 100644 index 000000000..fef847f5c --- /dev/null +++ b/src/PyEvent/PyEvent_Event.cpp @@ -0,0 +1,287 @@ + +#include "PyEvent_Event.h" + +#include +#include + +// asv 21.02.05 : introducing multi-platform approach of thread comparison +// - on Unix using pthread_t type for storing ThreadId +// - on Win32 using integer type for storing ThreadId +// NOT using integer ThreadId on both Unix and Win32 because (from documentation): +// "...Do not allow your program to rely on the internal structure or size of the pthread_t..." + +#ifdef WIN32 +#include +static DWORD myThread; +#else +#include +static pthread_t myThread; +#endif + +/*! + \class InitEvent + \brief Helper event class responsible for initializing PyEvent_Event + mechanism by the main thread ID + */ +class InitEvent : public PyEvent_Event +{ +public: + InitEvent(); + virtual ~InitEvent(); + virtual void Execute(); +}; + +/*! + \brief Constructor, initializes the event mechanism by the current thread ID. + It is asssumed to be the main thread ID, so be careful! +*/ +InitEvent::InitEvent() +{ + GetSessionThread(); +} + +/*! + \brief Destructor, does nothing. +*/ +InitEvent::~InitEvent() +{ +} + +/*! + \brief Nothing to be executed for this kind of event. +*/ +void InitEvent::Execute() +{ +} + +// NOTE: Here the SALOME event mechanism is initalized by the +// current thread ID that is always assumed to be the main thread ID. +// This should be revised as soon as the application library is no longer +// linked against the Event library (i.e. this static object is not created or created +// outside the main thread). +static InitEvent myInitEvent; + +/*! + \class PyEvent_CustomEvent + \brief Generic event class for user-defined events + + This class contains a generic void* data member that may be used + for transferring event-specific data to the receiver. + + \warning The internal data is not destroyed by the class destructor. +*/ + +/*! + \brief Constructor. + \param type event type +*/ +PyEvent_CustomEvent::PyEvent_CustomEvent( int type ) +: QEvent( (QEvent::Type)type ), d( 0 ) +{ +} + +/*! + \brief Constructor. + \param type event type + \param data custom data +*/ +PyEvent_CustomEvent::PyEvent_CustomEvent( QEvent::Type type, void* data ) +: QEvent( type ), d( data ) +{ +} + +/*! + \brief Get custom data. + \return pointer to the internal data +*/ +void* PyEvent_CustomEvent::data() const +{ + return d; +} + +/*! + \brief Set custom data. + \param data pointer to the internal data +*/ +void PyEvent_CustomEvent::setData( void* data ) +{ + d = data; +} + +/*! + \class PyEvent_Event + \brief The class which encapsulates data and functionality required for + posting component-specific events to perform arbitrary operations + in the main GUI thread. + + PyEvent_Event objects can be posted by any thread belonging to the GUI process. + + It is necessary to derive a custom event class from PyEvent_Event and + re-implement virtual Execute() method. This method should actually perform + the desirable operation. To pass all the required data to Execute() and + store a return value, arbitrary data fields can be added to the custom + event class. There is no need to protect such fields with a mutex, for only + one thread working with a PyEvent_Event object is active at any moment. + + Usage: + - Create PyEvent_Event. Components can derive their own event class from + PyEvent_Event in order to pass custom data to the event handler. + - Call process() method to post the event. After process() execution + it is possible to examine fields of your custom event object. + - Perform delete operator on the event to wake up the desktop (you can also + set parameter to \c true to automatically wake up desktop after + process(). + + The method processed() is used by the desktop to signal that event processing + has been completed. + + To make all this work, it is necessary to call static method GetSessionThread() + during the application initialization, i.e. from main() function. + It is important to call this method from the primary application thread. + + Caveats: + - there are no. +*/ + +//! Total number of semaphore resources +const int NumberOfResources = 2; + +/*! + \brief Initialize event mechanism. + + This function sets up the main application thread. It should be called + during the application initialization, i.e. main() function. +*/ +void PyEvent_Event::GetSessionThread(){ +#ifdef WIN32 + myThread = ::GetCurrentThreadId(); +#else + myThread = pthread_self(); +#endif +} + +/*! + \brief Check if the processing is in the main application thread. + \return \c true if this method is called from the main application thread +*/ +bool PyEvent_Event::IsSessionThread(){ + bool aResult = false; +#ifdef WIN32 + aResult = myThread == ::GetCurrentThreadId(); +#else + aResult = myThread == pthread_self(); +#endif + return aResult; +} + +/*! + \brief Constructor. +*/ +PyEvent_Event::PyEvent_Event(){ + // Prepare the semaphore + mySemaphore = new QSemaphore( NumberOfResources ); + mySemaphore->acquire( NumberOfResources ); +} + +/*! + \brief Destructor. +*/ +PyEvent_Event::~PyEvent_Event(){ + if ( mySemaphore->available() < NumberOfResources ) + mySemaphore->release( NumberOfResources - mySemaphore->available() ); + delete mySemaphore; +} + +/*! + \brief This method should be called by the main GUI thread + in order to execute the code specific for this event and finally + to inform the calling thread that the event + has been processed waking it up with help of the semaphore . + */ +void PyEvent_Event::ExecutePostedEvent() +{ + // Diagnose incorrect usage of PyEvent_Event API + if ( !IsSessionThread() ){ + qWarning( "PyEvent_Event::ExecutePostedEvent() is called from a secondary thread that might mean an error in application logic!" ); + } + // Actual execution specific for particular kind of event + Execute(); + // Signal the calling thread that the event has been processed + processed(); +} + +/*! + \brief Post the event and wait for its completion. + process() should be called from a secondary thread only. + \sa processed() +*/ +void PyEvent_Event::process() +{ + // Diagnose incorrect usage of PyEvent_Event API + if ( IsSessionThread() ){ + qWarning( "PyEvent_Event::process() is called from the main GUI thread that might mean an error in application logic!" ); + } + + QApplication::postEvent( qApp, new PyEvent_CustomEvent( PyEvent_EVENT, (void*)this ) ); + mySemaphore->acquire( 1 ); +} + +/*! + \brief Use this method to signal that this event has been processed. +*/ +void PyEvent_Event::processed() +{ + mySemaphore->release( 1 ); +} + +/*! + \fn virtual void PyEvent_Event::Execute(); + \brief This method should be redefined in the successor classes + to do real work. +*/ + +/*! + \class TMemFunEvent + \brief Template class for event which calls the function + without arguments and returning result. +*/ + +/*! + \class TVoidMemFunEvent + \brief Template class for event which calls the function + without arguments and without return value. +*/ + +/*! + \class TMemFun1ArgEvent + \brief Template class for event which calls the function + with one argument and returning result. +*/ + +/*! + \class TVoidMemFun1ArgEvent + \brief Template class for event which calls the function + with one argument and without return value. +*/ + +/*! + \class TMemFun2ArgEvent + \brief Template class for event which calls the function + with two arguments and returning result. +*/ + +/*! + \class TVoidMemFun2ArgEvent + \brief Template class for event which calls the function + with two arguments and without return value. +*/ + +/*! + \fn ProcessEvent + \brief Template function for processing events with return value. +*/ + +/*! + \fn ProcessVoidEvent + \brief Template function for processing events without return value. +*/ diff --git a/src/PyEvent/PyEvent_Event.cxx b/src/PyEvent/PyEvent_Event.cxx deleted file mode 100644 index fef847f5c..000000000 --- a/src/PyEvent/PyEvent_Event.cxx +++ /dev/null @@ -1,287 +0,0 @@ - -#include "PyEvent_Event.h" - -#include -#include - -// asv 21.02.05 : introducing multi-platform approach of thread comparison -// - on Unix using pthread_t type for storing ThreadId -// - on Win32 using integer type for storing ThreadId -// NOT using integer ThreadId on both Unix and Win32 because (from documentation): -// "...Do not allow your program to rely on the internal structure or size of the pthread_t..." - -#ifdef WIN32 -#include -static DWORD myThread; -#else -#include -static pthread_t myThread; -#endif - -/*! - \class InitEvent - \brief Helper event class responsible for initializing PyEvent_Event - mechanism by the main thread ID - */ -class InitEvent : public PyEvent_Event -{ -public: - InitEvent(); - virtual ~InitEvent(); - virtual void Execute(); -}; - -/*! - \brief Constructor, initializes the event mechanism by the current thread ID. - It is asssumed to be the main thread ID, so be careful! -*/ -InitEvent::InitEvent() -{ - GetSessionThread(); -} - -/*! - \brief Destructor, does nothing. -*/ -InitEvent::~InitEvent() -{ -} - -/*! - \brief Nothing to be executed for this kind of event. -*/ -void InitEvent::Execute() -{ -} - -// NOTE: Here the SALOME event mechanism is initalized by the -// current thread ID that is always assumed to be the main thread ID. -// This should be revised as soon as the application library is no longer -// linked against the Event library (i.e. this static object is not created or created -// outside the main thread). -static InitEvent myInitEvent; - -/*! - \class PyEvent_CustomEvent - \brief Generic event class for user-defined events - - This class contains a generic void* data member that may be used - for transferring event-specific data to the receiver. - - \warning The internal data is not destroyed by the class destructor. -*/ - -/*! - \brief Constructor. - \param type event type -*/ -PyEvent_CustomEvent::PyEvent_CustomEvent( int type ) -: QEvent( (QEvent::Type)type ), d( 0 ) -{ -} - -/*! - \brief Constructor. - \param type event type - \param data custom data -*/ -PyEvent_CustomEvent::PyEvent_CustomEvent( QEvent::Type type, void* data ) -: QEvent( type ), d( data ) -{ -} - -/*! - \brief Get custom data. - \return pointer to the internal data -*/ -void* PyEvent_CustomEvent::data() const -{ - return d; -} - -/*! - \brief Set custom data. - \param data pointer to the internal data -*/ -void PyEvent_CustomEvent::setData( void* data ) -{ - d = data; -} - -/*! - \class PyEvent_Event - \brief The class which encapsulates data and functionality required for - posting component-specific events to perform arbitrary operations - in the main GUI thread. - - PyEvent_Event objects can be posted by any thread belonging to the GUI process. - - It is necessary to derive a custom event class from PyEvent_Event and - re-implement virtual Execute() method. This method should actually perform - the desirable operation. To pass all the required data to Execute() and - store a return value, arbitrary data fields can be added to the custom - event class. There is no need to protect such fields with a mutex, for only - one thread working with a PyEvent_Event object is active at any moment. - - Usage: - - Create PyEvent_Event. Components can derive their own event class from - PyEvent_Event in order to pass custom data to the event handler. - - Call process() method to post the event. After process() execution - it is possible to examine fields of your custom event object. - - Perform delete operator on the event to wake up the desktop (you can also - set parameter to \c true to automatically wake up desktop after - process(). - - The method processed() is used by the desktop to signal that event processing - has been completed. - - To make all this work, it is necessary to call static method GetSessionThread() - during the application initialization, i.e. from main() function. - It is important to call this method from the primary application thread. - - Caveats: - - there are no. -*/ - -//! Total number of semaphore resources -const int NumberOfResources = 2; - -/*! - \brief Initialize event mechanism. - - This function sets up the main application thread. It should be called - during the application initialization, i.e. main() function. -*/ -void PyEvent_Event::GetSessionThread(){ -#ifdef WIN32 - myThread = ::GetCurrentThreadId(); -#else - myThread = pthread_self(); -#endif -} - -/*! - \brief Check if the processing is in the main application thread. - \return \c true if this method is called from the main application thread -*/ -bool PyEvent_Event::IsSessionThread(){ - bool aResult = false; -#ifdef WIN32 - aResult = myThread == ::GetCurrentThreadId(); -#else - aResult = myThread == pthread_self(); -#endif - return aResult; -} - -/*! - \brief Constructor. -*/ -PyEvent_Event::PyEvent_Event(){ - // Prepare the semaphore - mySemaphore = new QSemaphore( NumberOfResources ); - mySemaphore->acquire( NumberOfResources ); -} - -/*! - \brief Destructor. -*/ -PyEvent_Event::~PyEvent_Event(){ - if ( mySemaphore->available() < NumberOfResources ) - mySemaphore->release( NumberOfResources - mySemaphore->available() ); - delete mySemaphore; -} - -/*! - \brief This method should be called by the main GUI thread - in order to execute the code specific for this event and finally - to inform the calling thread that the event - has been processed waking it up with help of the semaphore . - */ -void PyEvent_Event::ExecutePostedEvent() -{ - // Diagnose incorrect usage of PyEvent_Event API - if ( !IsSessionThread() ){ - qWarning( "PyEvent_Event::ExecutePostedEvent() is called from a secondary thread that might mean an error in application logic!" ); - } - // Actual execution specific for particular kind of event - Execute(); - // Signal the calling thread that the event has been processed - processed(); -} - -/*! - \brief Post the event and wait for its completion. - process() should be called from a secondary thread only. - \sa processed() -*/ -void PyEvent_Event::process() -{ - // Diagnose incorrect usage of PyEvent_Event API - if ( IsSessionThread() ){ - qWarning( "PyEvent_Event::process() is called from the main GUI thread that might mean an error in application logic!" ); - } - - QApplication::postEvent( qApp, new PyEvent_CustomEvent( PyEvent_EVENT, (void*)this ) ); - mySemaphore->acquire( 1 ); -} - -/*! - \brief Use this method to signal that this event has been processed. -*/ -void PyEvent_Event::processed() -{ - mySemaphore->release( 1 ); -} - -/*! - \fn virtual void PyEvent_Event::Execute(); - \brief This method should be redefined in the successor classes - to do real work. -*/ - -/*! - \class TMemFunEvent - \brief Template class for event which calls the function - without arguments and returning result. -*/ - -/*! - \class TVoidMemFunEvent - \brief Template class for event which calls the function - without arguments and without return value. -*/ - -/*! - \class TMemFun1ArgEvent - \brief Template class for event which calls the function - with one argument and returning result. -*/ - -/*! - \class TVoidMemFun1ArgEvent - \brief Template class for event which calls the function - with one argument and without return value. -*/ - -/*! - \class TMemFun2ArgEvent - \brief Template class for event which calls the function - with two arguments and returning result. -*/ - -/*! - \class TVoidMemFun2ArgEvent - \brief Template class for event which calls the function - with two arguments and without return value. -*/ - -/*! - \fn ProcessEvent - \brief Template function for processing events with return value. -*/ - -/*! - \fn ProcessVoidEvent - \brief Template function for processing events without return value. -*/ diff --git a/src/PyEvent/PyEvent_EventFilter.cpp b/src/PyEvent/PyEvent_EventFilter.cpp new file mode 100644 index 000000000..ac12e2013 --- /dev/null +++ b/src/PyEvent/PyEvent_EventFilter.cpp @@ -0,0 +1,60 @@ + +#include "PyEvent_EventFilter.h" +#include "PyEvent_Event.h" + +#include + +PyEvent_EventFilter* PyEvent_EventFilter::myFilter = NULL; + +/*!Constructor.*/ +PyEvent_EventFilter::PyEvent_EventFilter() +: QObject() +{ + /* VSR 13/01/03 : installing global event filter for the application */ + qApp->installEventFilter( this ); +} + +/*!Destructor.*/ +PyEvent_EventFilter::~PyEvent_EventFilter() +{ + qApp->removeEventFilter( this ); +} + +/*! + Custom event filter +*/ +bool PyEvent_EventFilter::eventFilter( QObject* o, QEvent* e ) +{ + if ( e->type() == PyEvent_EVENT ) + { + PyEvent_Event* aSE = (PyEvent_Event*)((PyEvent_CustomEvent*)e)->data(); + processEvent(aSE); + ((PyEvent_CustomEvent*)e)->setData( 0 ); + return true; + } + return QObject::eventFilter( o, e ); +} + +/*!Process event.*/ +void PyEvent_EventFilter::processEvent( PyEvent_Event* theEvent ) +{ + if(theEvent) + theEvent->ExecutePostedEvent(); +} + +/*!Create new instance of PyEvent_EventFilter*/ +void PyEvent_EventFilter::Init() +{ + if( myFilter==NULL ) + myFilter = new PyEvent_EventFilter(); +} + +/*!Destroy filter.*/ +void PyEvent_EventFilter::Destroy() +{ + if( myFilter ) + { + delete myFilter; + myFilter = NULL; + } +} diff --git a/src/PyEvent/PyEvent_EventFilter.cxx b/src/PyEvent/PyEvent_EventFilter.cxx deleted file mode 100644 index ac12e2013..000000000 --- a/src/PyEvent/PyEvent_EventFilter.cxx +++ /dev/null @@ -1,60 +0,0 @@ - -#include "PyEvent_EventFilter.h" -#include "PyEvent_Event.h" - -#include - -PyEvent_EventFilter* PyEvent_EventFilter::myFilter = NULL; - -/*!Constructor.*/ -PyEvent_EventFilter::PyEvent_EventFilter() -: QObject() -{ - /* VSR 13/01/03 : installing global event filter for the application */ - qApp->installEventFilter( this ); -} - -/*!Destructor.*/ -PyEvent_EventFilter::~PyEvent_EventFilter() -{ - qApp->removeEventFilter( this ); -} - -/*! - Custom event filter -*/ -bool PyEvent_EventFilter::eventFilter( QObject* o, QEvent* e ) -{ - if ( e->type() == PyEvent_EVENT ) - { - PyEvent_Event* aSE = (PyEvent_Event*)((PyEvent_CustomEvent*)e)->data(); - processEvent(aSE); - ((PyEvent_CustomEvent*)e)->setData( 0 ); - return true; - } - return QObject::eventFilter( o, e ); -} - -/*!Process event.*/ -void PyEvent_EventFilter::processEvent( PyEvent_Event* theEvent ) -{ - if(theEvent) - theEvent->ExecutePostedEvent(); -} - -/*!Create new instance of PyEvent_EventFilter*/ -void PyEvent_EventFilter::Init() -{ - if( myFilter==NULL ) - myFilter = new PyEvent_EventFilter(); -} - -/*!Destroy filter.*/ -void PyEvent_EventFilter::Destroy() -{ - if( myFilter ) - { - delete myFilter; - myFilter = NULL; - } -} diff --git a/src/PyInterp/CMakeLists.txt b/src/PyInterp/CMakeLists.txt index 6373f65ef..b540f098b 100644 --- a/src/PyInterp/CMakeLists.txt +++ b/src/PyInterp/CMakeLists.txt @@ -17,10 +17,10 @@ SET(PROJECT_AUTOMOC # sources / static SET(PROJECT_SOURCES - PyInterp_Dispatcher.cxx - PyInterp_Event.cxx - PyInterp_Interp.cxx - PyInterp_Request.cxx + PyInterp_Dispatcher.cpp + PyInterp_Event.cpp + PyInterp_Interp.cpp + PyInterp_Request.cpp ) SET(PROJECT_LIBRARIES diff --git a/src/PyInterp/PyInterp_Dispatcher.cpp b/src/PyInterp/PyInterp_Dispatcher.cpp new file mode 100644 index 000000000..1999e716f --- /dev/null +++ b/src/PyInterp/PyInterp_Dispatcher.cpp @@ -0,0 +1,199 @@ + +#include "PyInterp_Dispatcher.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! +#include "PyInterp_Interp.h" +#include "PyInterp_Watcher.h" +#include "PyInterp_Request.h" + +#include +#include + +PyInterp_Dispatcher* PyInterp_Dispatcher::myInstance = 0; + +void PyInterp_Request::process() +{ + safeExecute(); + + bool isSync = IsSync(); + + if ( !isSync ) + myMutex.lock(); + + if ( listener() ) + processEvent( listener() ); + + if ( !isSync ) + myMutex.unlock(); +} + +void PyInterp_Request::safeExecute() +{ + //ProcessVoidEvent( new PyInterp_ExecuteEvent( this ) ); + execute(); +} + +void PyInterp_Request::Destroy( PyInterp_Request* request ) +{ + // Lock and unlock the mutex to avoid errors on its deletion + request->myMutex.lock(); + request->myMutex.unlock(); + delete request; +} + +QEvent* PyInterp_Request::createEvent() +{ + return new PyInterp_Event( PyInterp_Event::ES_NOTIFY, this ); +} + +void PyInterp_Request::processEvent( QObject* o ) +{ + if ( !o ) + return; + + QEvent* e = createEvent(); + if ( !e ) + return; + + if ( !IsSync() ) + QCoreApplication::postEvent( o, e ); + else + { + QCoreApplication::sendEvent( o, e ); + delete e; + } +} + +void PyInterp_Request::setListener( QObject* o ) +{ + myMutex.lock(); + myListener = o; + myMutex.unlock(); +} + +void PyInterp_LockRequest::safeExecute() +{ + if ( getInterp() ){ + PyLockWrapper aLock = getInterp()->GetLockWrapper(); + //ProcessVoidEvent( new PyInterp_ExecuteEvent( this ) ); + execute(); + } +} + +PyInterp_Event::~PyInterp_Event() +{ + PyInterp_Request::Destroy( myRequest ); + myRequest = 0; +} + +PyInterp_Dispatcher* PyInterp_Dispatcher::Get() +{ + if ( !myInstance ) + myInstance = new PyInterp_Dispatcher(); + return myInstance; +} + +PyInterp_Dispatcher::PyInterp_Dispatcher() +: QThread() +{ + myWatcher = new PyInterp_Watcher(); +} + +PyInterp_Dispatcher::~PyInterp_Dispatcher() +{ + // Clear the request queue + myQueueMutex.lock(); + + QListIterator it( myQueue ); + while ( it.hasNext() ) + PyInterp_Request::Destroy( it.next() ); + myQueue.clear(); + + myQueueMutex.unlock(); + + // Wait for run() to finish + wait(); + + delete myWatcher; + myWatcher = 0; +} + +bool PyInterp_Dispatcher::IsBusy() const +{ + return isRunning(); +} + +void PyInterp_Dispatcher::Exec( PyInterp_Request* theRequest ) +{ + if ( !theRequest ) + return; + + //if ( theRequest->IsSync() && !IsBusy() ) // synchronous processing - nothing is done if dispatcher is busy! + if ( theRequest->IsSync() ) // synchronous processing + processRequest( theRequest ); + else // asynchronous processing + { + myQueueMutex.lock(); + myQueue.enqueue( theRequest ); + if ( theRequest->listener() ) + QObject::connect( theRequest->listener(), SIGNAL( destroyed( QObject* ) ), myWatcher, SLOT( onDestroyed( QObject* ) ) ); + myQueueMutex.unlock(); + + if ( !IsBusy() ) + start(); + } +} + +void PyInterp_Dispatcher::run() +{ +// MESSAGE("*** PyInterp_Dispatcher::run(): STARTED") + PyInterp_Request* aRequest; + + // prepare for queue size check + myQueueMutex.lock(); + + while( myQueue.size() ) { +// MESSAGE("*** PyInterp_Dispatcher::run(): next request taken from the queue") + aRequest = myQueue.head(); + + // let other threads append their requests to the end of the queue + myQueueMutex.unlock(); + + // processRequest() may delete a request, so this pointer must not be used + // after request is processed! + processRequest( aRequest ); + + // prepare for removal of the first request in the queue + myQueueMutex.lock(); + // IMPORTANT: the first item could have been removed by objectDestroyed() --> we have to check it + if ( myQueue.head() == aRequest ) // It's still here --> remove it + myQueue.dequeue(); + +// MESSAGE("*** PyInterp_Dispatcher::run(): request processed") + } + + myQueueMutex.unlock(); +// MESSAGE("*** PyInterp_Dispatcher::run(): FINISHED") +} + +void PyInterp_Dispatcher::processRequest( PyInterp_Request* theRequest ) +{ + theRequest->process(); +} + +void PyInterp_Dispatcher::objectDestroyed( const QObject* o ) +{ + // prepare for modification of the queue + myQueueMutex.lock(); + + QMutableListIterator it( myQueue ); + while ( it.hasNext() ) + { + RequestPtr r = it.next(); + if ( o == r->listener() ) + { + r->setListener( 0 ); // to prevent event posting + it.remove(); + } + } + + myQueueMutex.unlock(); +} diff --git a/src/PyInterp/PyInterp_Dispatcher.cxx b/src/PyInterp/PyInterp_Dispatcher.cxx deleted file mode 100644 index 1999e716f..000000000 --- a/src/PyInterp/PyInterp_Dispatcher.cxx +++ /dev/null @@ -1,199 +0,0 @@ - -#include "PyInterp_Dispatcher.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! -#include "PyInterp_Interp.h" -#include "PyInterp_Watcher.h" -#include "PyInterp_Request.h" - -#include -#include - -PyInterp_Dispatcher* PyInterp_Dispatcher::myInstance = 0; - -void PyInterp_Request::process() -{ - safeExecute(); - - bool isSync = IsSync(); - - if ( !isSync ) - myMutex.lock(); - - if ( listener() ) - processEvent( listener() ); - - if ( !isSync ) - myMutex.unlock(); -} - -void PyInterp_Request::safeExecute() -{ - //ProcessVoidEvent( new PyInterp_ExecuteEvent( this ) ); - execute(); -} - -void PyInterp_Request::Destroy( PyInterp_Request* request ) -{ - // Lock and unlock the mutex to avoid errors on its deletion - request->myMutex.lock(); - request->myMutex.unlock(); - delete request; -} - -QEvent* PyInterp_Request::createEvent() -{ - return new PyInterp_Event( PyInterp_Event::ES_NOTIFY, this ); -} - -void PyInterp_Request::processEvent( QObject* o ) -{ - if ( !o ) - return; - - QEvent* e = createEvent(); - if ( !e ) - return; - - if ( !IsSync() ) - QCoreApplication::postEvent( o, e ); - else - { - QCoreApplication::sendEvent( o, e ); - delete e; - } -} - -void PyInterp_Request::setListener( QObject* o ) -{ - myMutex.lock(); - myListener = o; - myMutex.unlock(); -} - -void PyInterp_LockRequest::safeExecute() -{ - if ( getInterp() ){ - PyLockWrapper aLock = getInterp()->GetLockWrapper(); - //ProcessVoidEvent( new PyInterp_ExecuteEvent( this ) ); - execute(); - } -} - -PyInterp_Event::~PyInterp_Event() -{ - PyInterp_Request::Destroy( myRequest ); - myRequest = 0; -} - -PyInterp_Dispatcher* PyInterp_Dispatcher::Get() -{ - if ( !myInstance ) - myInstance = new PyInterp_Dispatcher(); - return myInstance; -} - -PyInterp_Dispatcher::PyInterp_Dispatcher() -: QThread() -{ - myWatcher = new PyInterp_Watcher(); -} - -PyInterp_Dispatcher::~PyInterp_Dispatcher() -{ - // Clear the request queue - myQueueMutex.lock(); - - QListIterator it( myQueue ); - while ( it.hasNext() ) - PyInterp_Request::Destroy( it.next() ); - myQueue.clear(); - - myQueueMutex.unlock(); - - // Wait for run() to finish - wait(); - - delete myWatcher; - myWatcher = 0; -} - -bool PyInterp_Dispatcher::IsBusy() const -{ - return isRunning(); -} - -void PyInterp_Dispatcher::Exec( PyInterp_Request* theRequest ) -{ - if ( !theRequest ) - return; - - //if ( theRequest->IsSync() && !IsBusy() ) // synchronous processing - nothing is done if dispatcher is busy! - if ( theRequest->IsSync() ) // synchronous processing - processRequest( theRequest ); - else // asynchronous processing - { - myQueueMutex.lock(); - myQueue.enqueue( theRequest ); - if ( theRequest->listener() ) - QObject::connect( theRequest->listener(), SIGNAL( destroyed( QObject* ) ), myWatcher, SLOT( onDestroyed( QObject* ) ) ); - myQueueMutex.unlock(); - - if ( !IsBusy() ) - start(); - } -} - -void PyInterp_Dispatcher::run() -{ -// MESSAGE("*** PyInterp_Dispatcher::run(): STARTED") - PyInterp_Request* aRequest; - - // prepare for queue size check - myQueueMutex.lock(); - - while( myQueue.size() ) { -// MESSAGE("*** PyInterp_Dispatcher::run(): next request taken from the queue") - aRequest = myQueue.head(); - - // let other threads append their requests to the end of the queue - myQueueMutex.unlock(); - - // processRequest() may delete a request, so this pointer must not be used - // after request is processed! - processRequest( aRequest ); - - // prepare for removal of the first request in the queue - myQueueMutex.lock(); - // IMPORTANT: the first item could have been removed by objectDestroyed() --> we have to check it - if ( myQueue.head() == aRequest ) // It's still here --> remove it - myQueue.dequeue(); - -// MESSAGE("*** PyInterp_Dispatcher::run(): request processed") - } - - myQueueMutex.unlock(); -// MESSAGE("*** PyInterp_Dispatcher::run(): FINISHED") -} - -void PyInterp_Dispatcher::processRequest( PyInterp_Request* theRequest ) -{ - theRequest->process(); -} - -void PyInterp_Dispatcher::objectDestroyed( const QObject* o ) -{ - // prepare for modification of the queue - myQueueMutex.lock(); - - QMutableListIterator it( myQueue ); - while ( it.hasNext() ) - { - RequestPtr r = it.next(); - if ( o == r->listener() ) - { - r->setListener( 0 ); // to prevent event posting - it.remove(); - } - } - - myQueueMutex.unlock(); -} diff --git a/src/PyInterp/PyInterp_Event.cpp b/src/PyInterp/PyInterp_Event.cpp new file mode 100644 index 000000000..524d06a08 --- /dev/null +++ b/src/PyInterp/PyInterp_Event.cpp @@ -0,0 +1,9 @@ + + +#include "PyInterp_Event.h" +#include "PyInterp_Request.h" + +void PyInterp_ExecuteEvent::Execute() +{ + myRequest->execute(); +} diff --git a/src/PyInterp/PyInterp_Event.cxx b/src/PyInterp/PyInterp_Event.cxx deleted file mode 100644 index 524d06a08..000000000 --- a/src/PyInterp/PyInterp_Event.cxx +++ /dev/null @@ -1,9 +0,0 @@ - - -#include "PyInterp_Event.h" -#include "PyInterp_Request.h" - -void PyInterp_ExecuteEvent::Execute() -{ - myRequest->execute(); -} diff --git a/src/PyInterp/PyInterp_Interp.cpp b/src/PyInterp/PyInterp_Interp.cpp new file mode 100644 index 000000000..7e633b59f --- /dev/null +++ b/src/PyInterp/PyInterp_Interp.cpp @@ -0,0 +1,533 @@ + +#include "PyInterp_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define TOP_HISTORY_PY "--- top of history ---" +#define BEGIN_HISTORY_PY "--- begin of history ---" + +// a map to store python thread states that have been created for a given system thread (key=thread id,value=thread state) +std::map currentThreadMap; + +/*! + \class PyLockWrapper + \brief Python GIL wrapper. +*/ + +/*! + \brief Constructor. Automatically acquires GIL. + \param theThreadState python thread state +*/ +PyLockWrapper::PyLockWrapper(PyThreadState* theThreadState): + myThreadState(theThreadState), + mySaveThreadState(0) +{ + if (myThreadState->interp == PyInterp_Interp::_interp) + _savestate = PyGILState_Ensure(); + else + PyEval_AcquireThread(myThreadState); +} + +/*! + \brief Destructor. Automatically releases GIL. +*/ +PyLockWrapper::~PyLockWrapper() +{ + if (myThreadState->interp == PyInterp_Interp::_interp) + PyGILState_Release(_savestate); + else + PyEval_ReleaseThread(myThreadState); +} + +/*! + \brief Get Python GIL wrapper. + \return GIL lock wrapper (GIL is automatically acquired here) +*/ +PyLockWrapper PyInterp_Interp::GetLockWrapper() +{ + if (_tstate->interp == PyInterp_Interp::_interp) + return _tstate; + + // If we are here, we have a secondary python interpreter. Try to get a thread state synchronized with the system thread + long currentThreadid=PyThread_get_thread_ident(); // the system thread id + PyThreadState* theThreadState; + if(currentThreadMap.count(currentThreadid) != 0) + { + //a thread state exists for this thread id + PyThreadState* oldThreadState=currentThreadMap[currentThreadid]; + if(_tstate->interp ==oldThreadState->interp) + { + //The old thread state has the same python interpreter as this one : reuse the threadstate + theThreadState=oldThreadState; + } + else + { + //The old thread state has not the same python interpreter as this one : delete the old threadstate and create a new one + PyEval_AcquireLock(); + PyThreadState_Clear(oldThreadState); + PyThreadState_Delete(oldThreadState); + PyEval_ReleaseLock(); + theThreadState=PyThreadState_New(_tstate->interp); + currentThreadMap[currentThreadid]=theThreadState; + } + } + else + { + // no old thread state for this thread id : create a new one + theThreadState=PyThreadState_New(_tstate->interp); + currentThreadMap[currentThreadid]=theThreadState; + } + return theThreadState; +} + +/* + The following functions are used to hook the Python + interpreter output. +*/ + +static void +PyStdOut_dealloc(PyStdOut *self) +{ + PyObject_Del(self); +} + +static PyObject* +PyStdOut_write(PyStdOut *self, PyObject *args) +{ + char *c; + int l; + if (!PyArg_ParseTuple(args, "t#:write",&c, &l)) + return NULL; + if(self->_cb==NULL) { + if ( self->_iscerr ) + std::cerr << c ; + else + std::cout << c ; + } + else { + self->_cb(self->_data,c); + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +PyStdOut_flush(PyStdOut *self) +{ + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef PyStdOut_methods[] = { + {"write", (PyCFunction)PyStdOut_write, METH_VARARGS, PyDoc_STR("write(string) -> None")}, + {"flush", (PyCFunction)PyStdOut_flush, METH_NOARGS, PyDoc_STR("flush() -> None")}, + {NULL, NULL} /* sentinel */ +}; + +static PyMemberDef PyStdOut_memberlist[] = { + {(char*)"softspace", T_INT, offsetof(PyStdOut, softspace), 0, + (char*)"flag indicating that a space needs to be printed; used by print"}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject PyStdOut_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "PyOut", /*tp_name*/ + sizeof(PyStdOut), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)PyStdOut_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + PyObject_GenericGetAttr, /*tp_getattro*/ + /* softspace is writable: we must supply tp_setattro */ + PyObject_GenericSetAttr, /* tp_setattro */ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + PyStdOut_methods, /*tp_methods*/ + PyStdOut_memberlist, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +#define PyStdOut_Check(v) ((v)->ob_type == &PyStdOut_Type) + +static PyStdOut* newPyStdOut( bool iscerr ) +{ + PyStdOut *self; + self = PyObject_New(PyStdOut, &PyStdOut_Type); + if (self == NULL) + return NULL; + self->softspace = 0; + self->_cb = NULL; + self->_iscerr = iscerr; + return self; +} + +/*! + \class PyInterp_Interp + \brief Generic embedded Python interpreter. +*/ + +int PyInterp_Interp::_argc = 1; +char* PyInterp_Interp::_argv[] = {(char*)""}; +PyObject* PyInterp_Interp::builtinmodule = NULL; +PyThreadState* PyInterp_Interp::_gtstate = NULL; +PyInterpreterState* PyInterp_Interp::_interp = NULL; + +/*! + \brief Basic constructor. + + After construction the interpreter instance successor classes + must call virtual method initalize(). +*/ +PyInterp_Interp::PyInterp_Interp(): + _tstate(0), _vout(0), _verr(0), _g(0) +{ +} + +/*! + \brief Destructor. +*/ +PyInterp_Interp::~PyInterp_Interp() +{ +} + +/*! + \brief Initialize embedded interpreter. + + This method shoud be called after construction of the interpreter. + The method initialize() calls virtuals methods + - initPython() to initialize global Python interpreter + - initState() to initialize embedded interpreter state + - initContext() to initialize interpreter internal context + - initRun() to prepare interpreter for running commands + which should be implemented in the successor classes, according to the + embedded Python interpreter policy (mono or multi interpreter, etc). +*/ +void PyInterp_Interp::initialize() +{ + _history.clear(); // start a new list of user's commands + _ith = _history.begin(); + + initPython(); + // Here the global lock is released + + initState(); + + PyEval_AcquireThread(_tstate); + + initContext(); + + // used to interpret & compile commands + PyObjWrapper m(PyImport_ImportModule("codeop")); + if(!m) { + PyErr_Print(); + PyEval_ReleaseThread(_tstate); + return; + } + + // Create python objects to capture stdout and stderr + _vout=(PyObject*)newPyStdOut( false ); // stdout + _verr=(PyObject*)newPyStdOut( true ); // stderr + + // All the initRun outputs are redirected to the standard output (console) + initRun(); + PyEval_ReleaseThread(_tstate); +} + +/*! + \brief Initialize Python interpreter. + + In case if Python is not initialized, it sets program name, initializes the interpreter, sets program arguments, + initializes threads. + Otherwise, it just obtains the global interpreter and thread states. This is important for light SALOME configuration, + as in full SALOME this is done at SalomeApp level. + \sa SalomeApp_PyInterp class + */ +void PyInterp_Interp::initPython() +{ + if (!Py_IsInitialized()){ + // Python is not initialized + Py_SetProgramName(_argv[0]); + Py_Initialize(); // Initialize the interpreter + PySys_SetArgv(_argc, _argv); + PyEval_InitThreads(); // Create (and acquire) the interpreter lock + } + + if ( _interp == NULL ) + _interp = PyThreadState_Get()->interp; + if (PyType_Ready(&PyStdOut_Type) < 0) { + PyErr_Print(); + } + if ( _gtstate == NULL ) + _gtstate = PyEval_SaveThread(); // Release global thread state +} + +/*! + \brief Get embedded Python interpreter banner. + \return banner string + */ +std::string PyInterp_Interp::getbanner() +{ + // Should we take the lock ? + // PyEval_RestoreThread(_tstate); + std::string aBanner("Python "); + aBanner = aBanner + Py_GetVersion() + " on " + Py_GetPlatform() ; + aBanner = aBanner + "\ntype help to get general information on environment\n"; + //PyEval_SaveThread(); + return aBanner; +} + +/*! + \brief Initialize run command. + + This method is used to prepare interpreter for running + Python commands. + + \return \c true on success and \c false on error +*/ +bool PyInterp_Interp::initRun() +{ + // + // probably all below code isn't required + // + /* + PySys_SetObject("stderr",_verr); + PySys_SetObject("stdout",_vout); + + //PyObject *m = PyImport_GetModuleDict(); + + PySys_SetObject("stdout",PySys_GetObject("__stdout__")); + PySys_SetObject("stderr",PySys_GetObject("__stderr__")); + */ + return true; +} + +/*! + \brief Compile Python command and evaluate it in the + python dictionary context if possible. + \internal + \param command Python command string + \param context Python context (dictionary) + \return -1 on fatal error, 1 if command is incomplete and 0 + if command is executed successfully + */ +static int run_command(const char *command, PyObject *context) +{ + PyObject *m = PyImport_AddModule("codeop"); + if(!m) { // Fatal error. No way to go on. + PyErr_Print(); + return -1; + } + PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command)); + if(!v) { + // Error encountered. It should be SyntaxError, + //so we don't write out traceback + PyObjWrapper exception, value, tb; + PyErr_Fetch(&exception, &value, &tb); + PyErr_NormalizeException(&exception, &value, &tb); + PyErr_Display(exception, value, NULL); + return -1; + } + else if (v == Py_None) { + // Incomplete text we return 1 : we need a complete text to execute + return 1; + } + else { + // Complete and correct text. We evaluate it. + //#if PY_VERSION_HEX < 0x02040000 // python version earlier than 2.4.0 + // PyObjWrapper r(PyEval_EvalCode(v,context,context)); + //#else + PyObjWrapper r(PyEval_EvalCode((PyCodeObject *)(void *)v,context,context)); + //#endif + if(!r) { + // Execution error. We return -1 + PyErr_Print(); + return -1; + } + // The command has been successfully executed. Return 0 + return 0; + } +} + +void replaceAll(std::string& str, const std::string& from, const std::string& to) { + if(from.empty()) + return; + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } +} +/*! + \brief Compile Python command and evaluate it in the + python dictionary context if possible. Command might correspond to + the execution of a script with optional arguments. + In this case, command is: + execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") + \internal + \param command Python command string + \param context Python context (dictionary) + \return -1 on fatal error, 1 if command is incomplete and 0 + if command is executed successfully + */ +static int compile_command(const char *command,PyObject *context) +{ + // First guess if command is execution of a script with args, or a simple Python command + std::string singleCommand = command; + std::string commandArgs = ""; + + std::size_t pos = std::string(command).find("args:"); + if (pos != std::string::npos) { + commandArgs = singleCommand.substr(pos+5); + commandArgs = commandArgs.substr(0, commandArgs.length()-3); + singleCommand = singleCommand.substr(0, pos-1)+"\")"; + } + + if (commandArgs.empty()) { + // process command: expression + // process command: execfile(r"/absolute/path/to/script.py") (no args) + return run_command(singleCommand.c_str(), context); + } + else { + // process command: execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") + std::string script = singleCommand.substr(11); // remove leading execfile(r" + script = script.substr(0, script.length()-2); // remove trailing ") + + std::string preCommandBegin = "import sys; save_argv = sys.argv; sys.argv=["; + std::string preCommandEnd = "];"; + replaceAll(commandArgs, ",", "\",\""); + commandArgs = "\""+commandArgs+"\""; + std::string completeCommand = preCommandBegin+"\""+script+"\","+commandArgs+preCommandEnd+singleCommand+";sys.argv=save_argv"; + return run_command(completeCommand.c_str(), context); + } +} + +/*! + \brief Run Python command. + \param command Python command + \return command status +*/ +int PyInterp_Interp::run(const char *command) +{ + beforeRun(); + return simpleRun(command); +} + +/*! + \brief Run Python command (used internally). + \param command Python command + \param addToHistory if \c true (default), the command is added to the commands history + \return command status +*/ +int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) +{ + if( addToHistory && strcmp(command,"") != 0 ) { + _history.push_back(command); + _ith = _history.end(); + } + + // We come from C++ to enter Python world + // We need to acquire the Python global lock + //PyLockWrapper aLock(_tstate); // san - lock is centralized now + + // Reset redirected outputs before treatment + PySys_SetObject((char*)"stderr",_verr); + PySys_SetObject((char*)"stdout",_vout); + + int ier = compile_command(command,_g); + + // Outputs are redirected on standards outputs (console) + PySys_SetObject((char*)"stdout",PySys_GetObject((char*)"__stdout__")); + PySys_SetObject((char*)"stderr",PySys_GetObject((char*)"__stderr__")); + + return ier; +} + +/*! + \brief Get previous command in the commands history. + \return previous command +*/ +const char * PyInterp_Interp::getPrevious() +{ + if(_ith != _history.begin()){ + _ith--; + return (*_ith).c_str(); + } + else + return BEGIN_HISTORY_PY; +} + +/*! + \brief Get next command in the commands history. + \return next command +*/ +const char * PyInterp_Interp::getNext() +{ + if(_ith != _history.end()){ + _ith++; + } + if (_ith == _history.end()) + return TOP_HISTORY_PY; + else + return (*_ith).c_str(); +} + +/*! + \brief Set Python standard output device hook. + \param cb callback function + \param data callback function parameters +*/ +void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data) +{ + ((PyStdOut*)_vout)->_cb=cb; + ((PyStdOut*)_vout)->_data=data; +} + +/*! + \brief Set Python standard error device hook. + \param cb callback function + \param data callback function parameters +*/ +void PyInterp_Interp::setverrcb(PyOutChanged* cb, void* data) +{ + ((PyStdOut*)_verr)->_cb=cb; + ((PyStdOut*)_verr)->_data=data; +} diff --git a/src/PyInterp/PyInterp_Interp.cxx b/src/PyInterp/PyInterp_Interp.cxx deleted file mode 100644 index 7e633b59f..000000000 --- a/src/PyInterp/PyInterp_Interp.cxx +++ /dev/null @@ -1,533 +0,0 @@ - -#include "PyInterp_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! -#include - -#include -#include - -#include -#include -#include -#include -#include - -#define TOP_HISTORY_PY "--- top of history ---" -#define BEGIN_HISTORY_PY "--- begin of history ---" - -// a map to store python thread states that have been created for a given system thread (key=thread id,value=thread state) -std::map currentThreadMap; - -/*! - \class PyLockWrapper - \brief Python GIL wrapper. -*/ - -/*! - \brief Constructor. Automatically acquires GIL. - \param theThreadState python thread state -*/ -PyLockWrapper::PyLockWrapper(PyThreadState* theThreadState): - myThreadState(theThreadState), - mySaveThreadState(0) -{ - if (myThreadState->interp == PyInterp_Interp::_interp) - _savestate = PyGILState_Ensure(); - else - PyEval_AcquireThread(myThreadState); -} - -/*! - \brief Destructor. Automatically releases GIL. -*/ -PyLockWrapper::~PyLockWrapper() -{ - if (myThreadState->interp == PyInterp_Interp::_interp) - PyGILState_Release(_savestate); - else - PyEval_ReleaseThread(myThreadState); -} - -/*! - \brief Get Python GIL wrapper. - \return GIL lock wrapper (GIL is automatically acquired here) -*/ -PyLockWrapper PyInterp_Interp::GetLockWrapper() -{ - if (_tstate->interp == PyInterp_Interp::_interp) - return _tstate; - - // If we are here, we have a secondary python interpreter. Try to get a thread state synchronized with the system thread - long currentThreadid=PyThread_get_thread_ident(); // the system thread id - PyThreadState* theThreadState; - if(currentThreadMap.count(currentThreadid) != 0) - { - //a thread state exists for this thread id - PyThreadState* oldThreadState=currentThreadMap[currentThreadid]; - if(_tstate->interp ==oldThreadState->interp) - { - //The old thread state has the same python interpreter as this one : reuse the threadstate - theThreadState=oldThreadState; - } - else - { - //The old thread state has not the same python interpreter as this one : delete the old threadstate and create a new one - PyEval_AcquireLock(); - PyThreadState_Clear(oldThreadState); - PyThreadState_Delete(oldThreadState); - PyEval_ReleaseLock(); - theThreadState=PyThreadState_New(_tstate->interp); - currentThreadMap[currentThreadid]=theThreadState; - } - } - else - { - // no old thread state for this thread id : create a new one - theThreadState=PyThreadState_New(_tstate->interp); - currentThreadMap[currentThreadid]=theThreadState; - } - return theThreadState; -} - -/* - The following functions are used to hook the Python - interpreter output. -*/ - -static void -PyStdOut_dealloc(PyStdOut *self) -{ - PyObject_Del(self); -} - -static PyObject* -PyStdOut_write(PyStdOut *self, PyObject *args) -{ - char *c; - int l; - if (!PyArg_ParseTuple(args, "t#:write",&c, &l)) - return NULL; - if(self->_cb==NULL) { - if ( self->_iscerr ) - std::cerr << c ; - else - std::cout << c ; - } - else { - self->_cb(self->_data,c); - } - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject* -PyStdOut_flush(PyStdOut *self) -{ - Py_INCREF(Py_None); - return Py_None; -} - -static PyMethodDef PyStdOut_methods[] = { - {"write", (PyCFunction)PyStdOut_write, METH_VARARGS, PyDoc_STR("write(string) -> None")}, - {"flush", (PyCFunction)PyStdOut_flush, METH_NOARGS, PyDoc_STR("flush() -> None")}, - {NULL, NULL} /* sentinel */ -}; - -static PyMemberDef PyStdOut_memberlist[] = { - {(char*)"softspace", T_INT, offsetof(PyStdOut, softspace), 0, - (char*)"flag indicating that a space needs to be printed; used by print"}, - {NULL} /* Sentinel */ -}; - -static PyTypeObject PyStdOut_Type = { - /* The ob_type field must be initialized in the module init function - * to be portable to Windows without using C++. */ - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ - "PyOut", /*tp_name*/ - sizeof(PyStdOut), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)PyStdOut_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr, /*tp_getattro*/ - /* softspace is writable: we must supply tp_setattro */ - PyObject_GenericSetAttr, /* tp_setattro */ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - PyStdOut_methods, /*tp_methods*/ - PyStdOut_memberlist, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - 0, /*tp_alloc*/ - 0, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - -#define PyStdOut_Check(v) ((v)->ob_type == &PyStdOut_Type) - -static PyStdOut* newPyStdOut( bool iscerr ) -{ - PyStdOut *self; - self = PyObject_New(PyStdOut, &PyStdOut_Type); - if (self == NULL) - return NULL; - self->softspace = 0; - self->_cb = NULL; - self->_iscerr = iscerr; - return self; -} - -/*! - \class PyInterp_Interp - \brief Generic embedded Python interpreter. -*/ - -int PyInterp_Interp::_argc = 1; -char* PyInterp_Interp::_argv[] = {(char*)""}; -PyObject* PyInterp_Interp::builtinmodule = NULL; -PyThreadState* PyInterp_Interp::_gtstate = NULL; -PyInterpreterState* PyInterp_Interp::_interp = NULL; - -/*! - \brief Basic constructor. - - After construction the interpreter instance successor classes - must call virtual method initalize(). -*/ -PyInterp_Interp::PyInterp_Interp(): - _tstate(0), _vout(0), _verr(0), _g(0) -{ -} - -/*! - \brief Destructor. -*/ -PyInterp_Interp::~PyInterp_Interp() -{ -} - -/*! - \brief Initialize embedded interpreter. - - This method shoud be called after construction of the interpreter. - The method initialize() calls virtuals methods - - initPython() to initialize global Python interpreter - - initState() to initialize embedded interpreter state - - initContext() to initialize interpreter internal context - - initRun() to prepare interpreter for running commands - which should be implemented in the successor classes, according to the - embedded Python interpreter policy (mono or multi interpreter, etc). -*/ -void PyInterp_Interp::initialize() -{ - _history.clear(); // start a new list of user's commands - _ith = _history.begin(); - - initPython(); - // Here the global lock is released - - initState(); - - PyEval_AcquireThread(_tstate); - - initContext(); - - // used to interpret & compile commands - PyObjWrapper m(PyImport_ImportModule("codeop")); - if(!m) { - PyErr_Print(); - PyEval_ReleaseThread(_tstate); - return; - } - - // Create python objects to capture stdout and stderr - _vout=(PyObject*)newPyStdOut( false ); // stdout - _verr=(PyObject*)newPyStdOut( true ); // stderr - - // All the initRun outputs are redirected to the standard output (console) - initRun(); - PyEval_ReleaseThread(_tstate); -} - -/*! - \brief Initialize Python interpreter. - - In case if Python is not initialized, it sets program name, initializes the interpreter, sets program arguments, - initializes threads. - Otherwise, it just obtains the global interpreter and thread states. This is important for light SALOME configuration, - as in full SALOME this is done at SalomeApp level. - \sa SalomeApp_PyInterp class - */ -void PyInterp_Interp::initPython() -{ - if (!Py_IsInitialized()){ - // Python is not initialized - Py_SetProgramName(_argv[0]); - Py_Initialize(); // Initialize the interpreter - PySys_SetArgv(_argc, _argv); - PyEval_InitThreads(); // Create (and acquire) the interpreter lock - } - - if ( _interp == NULL ) - _interp = PyThreadState_Get()->interp; - if (PyType_Ready(&PyStdOut_Type) < 0) { - PyErr_Print(); - } - if ( _gtstate == NULL ) - _gtstate = PyEval_SaveThread(); // Release global thread state -} - -/*! - \brief Get embedded Python interpreter banner. - \return banner string - */ -std::string PyInterp_Interp::getbanner() -{ - // Should we take the lock ? - // PyEval_RestoreThread(_tstate); - std::string aBanner("Python "); - aBanner = aBanner + Py_GetVersion() + " on " + Py_GetPlatform() ; - aBanner = aBanner + "\ntype help to get general information on environment\n"; - //PyEval_SaveThread(); - return aBanner; -} - -/*! - \brief Initialize run command. - - This method is used to prepare interpreter for running - Python commands. - - \return \c true on success and \c false on error -*/ -bool PyInterp_Interp::initRun() -{ - // - // probably all below code isn't required - // - /* - PySys_SetObject("stderr",_verr); - PySys_SetObject("stdout",_vout); - - //PyObject *m = PyImport_GetModuleDict(); - - PySys_SetObject("stdout",PySys_GetObject("__stdout__")); - PySys_SetObject("stderr",PySys_GetObject("__stderr__")); - */ - return true; -} - -/*! - \brief Compile Python command and evaluate it in the - python dictionary context if possible. - \internal - \param command Python command string - \param context Python context (dictionary) - \return -1 on fatal error, 1 if command is incomplete and 0 - if command is executed successfully - */ -static int run_command(const char *command, PyObject *context) -{ - PyObject *m = PyImport_AddModule("codeop"); - if(!m) { // Fatal error. No way to go on. - PyErr_Print(); - return -1; - } - PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command)); - if(!v) { - // Error encountered. It should be SyntaxError, - //so we don't write out traceback - PyObjWrapper exception, value, tb; - PyErr_Fetch(&exception, &value, &tb); - PyErr_NormalizeException(&exception, &value, &tb); - PyErr_Display(exception, value, NULL); - return -1; - } - else if (v == Py_None) { - // Incomplete text we return 1 : we need a complete text to execute - return 1; - } - else { - // Complete and correct text. We evaluate it. - //#if PY_VERSION_HEX < 0x02040000 // python version earlier than 2.4.0 - // PyObjWrapper r(PyEval_EvalCode(v,context,context)); - //#else - PyObjWrapper r(PyEval_EvalCode((PyCodeObject *)(void *)v,context,context)); - //#endif - if(!r) { - // Execution error. We return -1 - PyErr_Print(); - return -1; - } - // The command has been successfully executed. Return 0 - return 0; - } -} - -void replaceAll(std::string& str, const std::string& from, const std::string& to) { - if(from.empty()) - return; - size_t start_pos = 0; - while((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' - } -} -/*! - \brief Compile Python command and evaluate it in the - python dictionary context if possible. Command might correspond to - the execution of a script with optional arguments. - In this case, command is: - execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") - \internal - \param command Python command string - \param context Python context (dictionary) - \return -1 on fatal error, 1 if command is incomplete and 0 - if command is executed successfully - */ -static int compile_command(const char *command,PyObject *context) -{ - // First guess if command is execution of a script with args, or a simple Python command - std::string singleCommand = command; - std::string commandArgs = ""; - - std::size_t pos = std::string(command).find("args:"); - if (pos != std::string::npos) { - commandArgs = singleCommand.substr(pos+5); - commandArgs = commandArgs.substr(0, commandArgs.length()-3); - singleCommand = singleCommand.substr(0, pos-1)+"\")"; - } - - if (commandArgs.empty()) { - // process command: expression - // process command: execfile(r"/absolute/path/to/script.py") (no args) - return run_command(singleCommand.c_str(), context); - } - else { - // process command: execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") - std::string script = singleCommand.substr(11); // remove leading execfile(r" - script = script.substr(0, script.length()-2); // remove trailing ") - - std::string preCommandBegin = "import sys; save_argv = sys.argv; sys.argv=["; - std::string preCommandEnd = "];"; - replaceAll(commandArgs, ",", "\",\""); - commandArgs = "\""+commandArgs+"\""; - std::string completeCommand = preCommandBegin+"\""+script+"\","+commandArgs+preCommandEnd+singleCommand+";sys.argv=save_argv"; - return run_command(completeCommand.c_str(), context); - } -} - -/*! - \brief Run Python command. - \param command Python command - \return command status -*/ -int PyInterp_Interp::run(const char *command) -{ - beforeRun(); - return simpleRun(command); -} - -/*! - \brief Run Python command (used internally). - \param command Python command - \param addToHistory if \c true (default), the command is added to the commands history - \return command status -*/ -int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) -{ - if( addToHistory && strcmp(command,"") != 0 ) { - _history.push_back(command); - _ith = _history.end(); - } - - // We come from C++ to enter Python world - // We need to acquire the Python global lock - //PyLockWrapper aLock(_tstate); // san - lock is centralized now - - // Reset redirected outputs before treatment - PySys_SetObject((char*)"stderr",_verr); - PySys_SetObject((char*)"stdout",_vout); - - int ier = compile_command(command,_g); - - // Outputs are redirected on standards outputs (console) - PySys_SetObject((char*)"stdout",PySys_GetObject((char*)"__stdout__")); - PySys_SetObject((char*)"stderr",PySys_GetObject((char*)"__stderr__")); - - return ier; -} - -/*! - \brief Get previous command in the commands history. - \return previous command -*/ -const char * PyInterp_Interp::getPrevious() -{ - if(_ith != _history.begin()){ - _ith--; - return (*_ith).c_str(); - } - else - return BEGIN_HISTORY_PY; -} - -/*! - \brief Get next command in the commands history. - \return next command -*/ -const char * PyInterp_Interp::getNext() -{ - if(_ith != _history.end()){ - _ith++; - } - if (_ith == _history.end()) - return TOP_HISTORY_PY; - else - return (*_ith).c_str(); -} - -/*! - \brief Set Python standard output device hook. - \param cb callback function - \param data callback function parameters -*/ -void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data) -{ - ((PyStdOut*)_vout)->_cb=cb; - ((PyStdOut*)_vout)->_data=data; -} - -/*! - \brief Set Python standard error device hook. - \param cb callback function - \param data callback function parameters -*/ -void PyInterp_Interp::setverrcb(PyOutChanged* cb, void* data) -{ - ((PyStdOut*)_verr)->_cb=cb; - ((PyStdOut*)_verr)->_data=data; -} diff --git a/src/PyInterp/PyInterp_Request.cpp b/src/PyInterp/PyInterp_Request.cpp new file mode 100644 index 000000000..a4ce0992f --- /dev/null +++ b/src/PyInterp/PyInterp_Request.cpp @@ -0,0 +1,4 @@ + + +#include "PyInterp_Request.h" + diff --git a/src/PyInterp/PyInterp_Request.cxx b/src/PyInterp/PyInterp_Request.cxx deleted file mode 100644 index a4ce0992f..000000000 --- a/src/PyInterp/PyInterp_Request.cxx +++ /dev/null @@ -1,4 +0,0 @@ - - -#include "PyInterp_Request.h" -