/********************************************************************** PythonExtension - PythonExtension Copyright (C) 2008 by Donald Ephraim Curtis Copyright (C) 2008,2009 by Tim Vandermeersch This file is part of the Avogadro molecular editor project. For more information, see Avogadro is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Avogadro is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ***********************************************************************/ #include "pythonextension.h" #include #include #include #include #include #include #include #include using namespace std; using namespace boost::python; namespace Avogadro { class PythonExtensionPrivate { public: PythonExtensionPrivate() : script(0), dockWidget(0) {} PythonInterpreter interpreter; PythonScript *script; boost::python::object instance; QDockWidget *dockWidget; }; PythonExtension::PythonExtension(QObject *parent, const QString &filename) : Extension(parent), d(new PythonExtensionPrivate) { loadScript(filename); } PythonExtension::~PythonExtension() { if (d->script) delete d->script; if (d->dockWidget) d->dockWidget->deleteLater(); delete d; } QString PythonExtension::identifier() const { if (!PyObject_HasAttrString(d->instance.ptr(), "identifier")) return "Unknown Python Extension"; try { prepareToCatchError(); const char *name = extract(d->instance.attr("identifier")()); return QString(name); } catch(error_already_set const &) { catchError(); } return "Unknown Python Extension"; } QString PythonExtension::name() const { if (!PyObject_HasAttrString(d->instance.ptr(), "name")) return tr("Unknown Python Extension"); try { prepareToCatchError(); const char *name = extract(d->instance.attr("name")()); return QString(name); } catch(error_already_set const &) { catchError(); } return tr("Unknown Python Extension"); } QString PythonExtension::description() const { if (!PyObject_HasAttrString(d->instance.ptr(), "description")) return tr("N/A"); try { prepareToCatchError(); const char *name = extract(d->instance.attr("description")()); return QString(name); } catch(error_already_set const &) { catchError(); } return tr("N/A"); } QList PythonExtension::actions() const { QList actions; if (!d->script) return actions; try { prepareToCatchError(); actions = extract< QList >(d->instance.attr("actions")()); } catch (error_already_set const &) { catchError(); } // this will make the MainWindow call performAction on this extension foreach (QAction *action, actions) action->setParent( (PythonExtension*)this ); return actions; } // allows us to set the intended menu path for each action QString PythonExtension::menuPath(QAction *action) const { if (!d->script || !PyObject_HasAttrString(d->instance.ptr(), "menuPath")) return tr("&Scripts"); try { prepareToCatchError(); boost::python::return_by_value::apply::type qconverter; PyObject *qobj = qconverter(action); object real_qobj = object(handle<>(qobj)); return extract(d->instance.attr("menuPath")(real_qobj)); } catch(error_already_set const &) { catchError(); } return tr("&Scripts"); } class PythonCommand : public QUndoCommand { public: PythonCommand(QUndoCommand *command) : m_command(command) { setText(m_command->text()); } ~PythonCommand() { delete m_command; } void redo() { try { prepareToCatchError(); m_command->redo(); catchError(); } catch(error_already_set const &) { catchError(); } } void undo() { try { prepareToCatchError(); m_command->undo(); catchError(); } catch(error_already_set const &) { catchError(); } } private: QUndoCommand *m_command; }; QUndoCommand* PythonExtension::performAction( QAction *action, GLWidget *widget ) { if (!d->script) return 0; // Let's just catch the exception and print the error... //if (!PyObject_HasAttrString(d->instance.ptr(), "performAction")) // return 0; try { prepareToCatchError(); boost::python::reference_existing_object::apply::type converter; PyObject *obj = converter(widget); object real_obj = object(handle<>(obj)); boost::python::return_by_value::apply::type qconverter; PyObject *qobj = qconverter(action); object real_qobj = object(handle<>(qobj)); object pyObj(d->instance.attr("performAction")(real_qobj, real_obj)); // new reference QUndoCommand *command = extract(pyObj); if (!command) return 0; return new PythonCommand(command); } catch(error_already_set const &) { catchError(); } return 0; } QDockWidget* PythonExtension::dockWidget() { if (!d->script) return 0; // nothing we can do if(!d->dockWidget) { if (PyObject_HasAttrString(d->instance.ptr(), "dockWidget")) { try { prepareToCatchError(); d->dockWidget = extract(d->instance.attr("dockWidget")()); d->dockWidget->setObjectName(d->dockWidget->windowTitle()); } catch (error_already_set const &) { d->dockWidget = 0; catchError(); } } if (d->dockWidget) connect(d->dockWidget, SIGNAL(destroyed()), this, SLOT(dockWidgetDestroyed())); } return d->dockWidget; } void PythonExtension::dockWidgetDestroyed() { d->dockWidget = 0; } void PythonExtension::readSettings(QSettings &settings) { Extension::readSettings(settings); if (!d->script) return; if (!PyObject_HasAttrString(d->instance.ptr(), "readSettings")) return; try { prepareToCatchError(); boost::python::return_by_value::apply::type qconverter; PyObject *qobj = qconverter(&settings); object real_qobj = object(handle<>(qobj)); d->instance.attr("readSettings")(real_qobj); } catch(error_already_set const &) { catchError(); } } void PythonExtension::writeSettings(QSettings &settings) const { Extension::writeSettings(settings); if (!d->script) return; if (!PyObject_HasAttrString(d->instance.ptr(), "writeSettings")) return; try { prepareToCatchError(); boost::python::return_by_value::apply::type qconverter; PyObject *qobj = qconverter(&settings); object real_qobj = object(handle<>(qobj)); d->instance.attr("writeSettings")(real_qobj); } catch(error_already_set const &) { catchError(); } } void PythonExtension::loadScript(const QString &filename) { QFileInfo info(filename); d->interpreter.addSearchPath(info.canonicalPath()); PythonScript *script = new PythonScript(filename); if (script->module()) { // make sure there is an Extension class defined if (PyObject_HasAttrString(script->module().ptr(), "Extension")) { try { prepareToCatchError(); d->instance = script->module().attr("Extension")(); } catch (error_already_set const &) { catchError(); return; } // connect signal(s) if (PyObject_HasAttrString(d->instance.ptr(), "__pyqtSignals__")) { QObject *obj = extract(d->instance); connect(obj, SIGNAL(message(const QString&)), this, SIGNAL(message(const QString&))); } d->script = script; } else { delete script; pythonError()->append(tr("PythonExtension: checking ") + filename + "..."); pythonError()->append(tr(" - script has no 'Extension' class defined")); } } else { delete script; pythonError()->append(tr("PythonExtension: checking ") + filename + "..."); pythonError()->append(tr(" - no module")); } } } #include "pythonextension.moc"