From 118c24c85222ec0f6e5d9b51fa7deab3cb20b39b Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 21 Oct 2018 11:03:25 +0200 Subject: [PATCH 01/13] Commit of current status as one cleaned up patch. --- CMakeLists.txt | 2 + CMakeScripts/DefineDependsandFlags.cmake | 13 + CMakeScripts/Modules/FindEmbPython.cmake | 54 ++ share/CMakeLists.txt | 1 + share/README.md | 1 + .../actions/dialog-console-script.svg | 217 +++++ .../Tango/scalable/actions/dialog-console.svg | 149 ++++ .../actions/dialog-console-script.svg | 217 +++++ .../scalable/actions/dialog-console.svg | 149 ++++ share/keys/inkscape.xml | 2 + share/python/CMakeLists.txt | 6 + share/python/README | 2 + share/python/init.py | 39 + share/python/ipy/CMakeLists.txt | 3 + share/python/ipy/ipy.py | 4 + share/python/tests/CMakeLists.txt | 3 + share/python/tests/exportPNGCMDline.py | 27 + share/python/tests/inkscape_mode.py | 17 + share/python/tests/misc.py | 5 + share/python/tests/openSaveExport.py | 31 + share/python/tests/outputMIMEs.py | 14 + share/python/tests/verbs.py | 19 + share/ui/menus.xml | 1 + src/CMakeLists.txt | 4 + src/desktop.cpp | 1 + src/emb_python.cpp | 778 ++++++++++++++++++ src/emb_python.h | 139 ++++ src/emb_python_doc.h | 159 ++++ src/file.cpp | 141 ++-- src/file.h | 12 +- src/io/resource.cpp | 1 + src/io/resource.h | 1 + src/main.cpp | 128 ++- src/object/sp-line.cpp | 12 + src/object/sp-line.h | 2 + src/path-prefix.h | 4 + src/pyhelper.cpp | 458 +++++++++++ src/pyhelper.h | 71 ++ src/ui/CMakeLists.txt | 1 + src/ui/dialog/console.cpp | 527 ++++++++++++ src/ui/dialog/console.h | 196 +++++ src/ui/dialog/dialog-manager.cpp | 7 + src/verbs.cpp | 11 +- src/verbs.h | 2 + 44 files changed, 3550 insertions(+), 81 deletions(-) create mode 100644 CMakeScripts/Modules/FindEmbPython.cmake create mode 100644 share/icons/Tango/scalable/actions/dialog-console-script.svg create mode 100644 share/icons/Tango/scalable/actions/dialog-console.svg create mode 100644 share/icons/hicolor/scalable/actions/dialog-console-script.svg create mode 100644 share/icons/hicolor/scalable/actions/dialog-console.svg create mode 100644 share/python/CMakeLists.txt create mode 100644 share/python/README create mode 100644 share/python/init.py create mode 100644 share/python/ipy/CMakeLists.txt create mode 100644 share/python/ipy/ipy.py create mode 100644 share/python/tests/CMakeLists.txt create mode 100644 share/python/tests/exportPNGCMDline.py create mode 100644 share/python/tests/inkscape_mode.py create mode 100644 share/python/tests/misc.py create mode 100644 share/python/tests/openSaveExport.py create mode 100644 share/python/tests/outputMIMEs.py create mode 100644 share/python/tests/verbs.py create mode 100644 src/emb_python.cpp create mode 100644 src/emb_python.h create mode 100644 src/emb_python_doc.h create mode 100644 src/pyhelper.cpp create mode 100644 src/pyhelper.h create mode 100644 src/ui/dialog/console.cpp create mode 100644 src/ui/dialog/console.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d80e241321..8416b225de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ option(WITH_LIBWPG "Compile with support of libwpg for WordPerfect Graphics" ON) option(WITH_NLS "Compile with Native Language Support (using gettext)" ON) option(WITH_YAML "Compile with YAML support (enables xverbs)" ON) option(WITH_JEMALLOC "Compile with JEMALLOC support" ON) +option(WITH_EMBPYTHON "Compile with embedded Python support" ON) option(WITH_FUZZ "Compile for fuzzing purpose (use 'make fuzz' only)" OFF) mark_as_advanced(WITH_FUZZ) @@ -297,6 +298,7 @@ message("WITH_OPENMP: ${WITH_OPENMP}") message("WITH_PROFILING: ${WITH_PROFILING}") message("WITH_YAML: ${WITH_YAML}") message("WITH_JEMALLOC: ${WITH_JEMALLOC}") +message("WITH_EMBPYTHON: ${WITH_EMBPYTHON}") if(WIN32) message("") diff --git a/CMakeScripts/DefineDependsandFlags.cmake b/CMakeScripts/DefineDependsandFlags.cmake index a8230fea7e..ff8aca0975 100644 --- a/CMakeScripts/DefineDependsandFlags.cmake +++ b/CMakeScripts/DefineDependsandFlags.cmake @@ -360,6 +360,19 @@ if(WITH_YAML) endif() endif() +if(WITH_EMBPYTHON) + find_package(EmbPython) + if(EMBPYTHON_FOUND) + set (WITH_EMBPYTHON ON) + list(APPEND INKSCAPE_INCS_SYS ${EMBPYTHON_INCLUDE_DIRS}) + list(APPEND INKSCAPE_LIBS ${EMBPYTHON_LIBRARIES}) + add_definitions(-DWITH_EMBPYTHON) + else(EMBPYTHON_FOUND) + set(WITH_EMBPYTHON OFF) + message(STATUS "Could not locate the Python library headers: Embedded Python feature will be disabled") + endif() +endif() + list(REMOVE_DUPLICATES INKSCAPE_CXX_FLAGS) foreach(flag ${INKSCAPE_CXX_FLAGS}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" CACHE STRING "" FORCE) diff --git a/CMakeScripts/Modules/FindEmbPython.cmake b/CMakeScripts/Modules/FindEmbPython.cmake new file mode 100644 index 0000000000..55e9d5ba77 --- /dev/null +++ b/CMakeScripts/Modules/FindEmbPython.cmake @@ -0,0 +1,54 @@ +# - Try to find the Python library +# Once done this will define +# +# EMBPYTHON_FOUND - system has Python +# EMBPYTHON_INCLUDE_DIRS - the Python include directory +# EMBPYTHON_LIBRARIES - the libraries needed to use python3.5 +# +# Based on FindPotrace.cmake in the same directory. + + +if(EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) + # in cache already + set(EMBPYTHON_FOUND TRUE) +else(EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) + FIND_PATH (EMBPYTHON_INCLUDE_DIR + NAMES + Python.h + PATHS + /usr/include + /usr/local/include + $ENV{DEVLIBS_PATH}/include + PATH_SUFFIXES + python3.5 + python3.5m + ) + +message("${EMBPYTHON_INCLUDE_DIR}") + + FIND_LIBRARY (EMBPYTHON_LIBRARY + NAMES + python3.5 + python3.5m + PATHS + /usr/lib + /usr/local/lib + $ENV{DEVLIBS_PATH}/lib + ) + +message("${EMBPYTHON_LIBRARY}") + + if (EMBPYTHON_LIBRARY) + set (EMBPYTHON_FOUND TRUE) + set (EMBPYTHON_INCLUDE_DIRS + ${EMBPYTHON_INCLUDE_DIR} + ) + set(EMBPYTHON_LIBRARIES + ${EMBPYTHON_LIBRARIES} + ${EMBPYTHON_LIBRARY} + ) + message(STATUS "Found Python: ${EMBPYTHON_LIBRARIES}") + endif (EMBPYTHON_LIBRARY) + + +endif (EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt index e32424f4fd..bb4179a2e7 100644 --- a/share/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(keys) add_subdirectory(markers) add_subdirectory(palettes) add_subdirectory(patterns) +add_subdirectory(python) add_subdirectory(screens) add_subdirectory(symbols) add_subdirectory(templates) diff --git a/share/README.md b/share/README.md index cb136774cb..65aceed5cb 100644 --- a/share/README.md +++ b/share/README.md @@ -13,6 +13,7 @@ keys - markers - palettes - patterns - +python - Support file for embedded Python. screens - symbols - templates - diff --git a/share/icons/Tango/scalable/actions/dialog-console-script.svg b/share/icons/Tango/scalable/actions/dialog-console-script.svg new file mode 100644 index 0000000000..3af580818d --- /dev/null +++ b/share/icons/Tango/scalable/actions/dialog-console-script.svg @@ -0,0 +1,217 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/Tango/scalable/actions/dialog-console.svg b/share/icons/Tango/scalable/actions/dialog-console.svg new file mode 100644 index 0000000000..bdac3544da --- /dev/null +++ b/share/icons/Tango/scalable/actions/dialog-console.svg @@ -0,0 +1,149 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/dialog-console-script.svg b/share/icons/hicolor/scalable/actions/dialog-console-script.svg new file mode 100644 index 0000000000..a5a3c0a3f0 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/dialog-console-script.svg @@ -0,0 +1,217 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/dialog-console.svg b/share/icons/hicolor/scalable/actions/dialog-console.svg new file mode 100644 index 0000000000..bdac3544da --- /dev/null +++ b/share/icons/hicolor/scalable/actions/dialog-console.svg @@ -0,0 +1,149 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/keys/inkscape.xml b/share/keys/inkscape.xml index 2f037682c8..88abac1a58 100644 --- a/share/keys/inkscape.xml +++ b/share/keys/inkscape.xml @@ -615,6 +615,8 @@ override) the bindings in the main default.xml. + + diff --git a/share/python/CMakeLists.txt b/share/python/CMakeLists.txt new file mode 100644 index 0000000000..e879eb667b --- /dev/null +++ b/share/python/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB _FILES "README" "*.py") +install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python) + +add_subdirectory(ipy) +add_subdirectory(tests) + diff --git a/share/python/README b/share/python/README new file mode 100644 index 0000000000..a6b1b381c3 --- /dev/null +++ b/share/python/README @@ -0,0 +1,2 @@ +This is the directory for global embedded Python files. + diff --git a/share/python/init.py b/share/python/init.py new file mode 100644 index 0000000000..55725962b7 --- /dev/null +++ b/share/python/init.py @@ -0,0 +1,39 @@ +# Inkscape Python internal initialization +# +# Authors: +# Thomas Wiesner +# +# Copyright (C) 2018 Authors +# +# Released under GNU GPL, read the file 'COPYING' for more information +# + +import sys +import os +import _ipy + +# Functions for stdout and stderr redirection. +class StdOutRedir: + def write(self, line): + _ipy.conAppendStdOut(line) + +class StdErrRedir: + def write(self, line): + _ipy.conAppendStdErr(line) + +print('init.py: Script running in ' + _ipy.getMode() + ' mode') + +# If Inkscape was started in GUI mode, we want to setup the redirection +# for stdout and stderr. We do this very early on, to make possible +# later errors show up in the GUI console. +if _ipy.getMode() == 'gui': + print('init.py: Setting up stdout and stderr redirection.') + sys.stderr = StdErrRedir() + sys.stdout = StdOutRedir() + +# Setup path to additional Python ressources. +ipyPath = os.path.join(_ipy.getPythonPath(), 'ipy') +sys.path.append(ipyPath) +import ipy + + diff --git a/share/python/ipy/CMakeLists.txt b/share/python/ipy/CMakeLists.txt new file mode 100644 index 0000000000..3228de4cb0 --- /dev/null +++ b/share/python/ipy/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB _FILES "*.py") +install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python/ipy) + diff --git a/share/python/ipy/ipy.py b/share/python/ipy/ipy.py new file mode 100644 index 0000000000..f878f5e1b0 --- /dev/null +++ b/share/python/ipy/ipy.py @@ -0,0 +1,4 @@ +# Currently only one simple test function. +def ipytest(): + print('Hello from ipytest') + diff --git a/share/python/tests/CMakeLists.txt b/share/python/tests/CMakeLists.txt new file mode 100644 index 0000000000..64e6f7648c --- /dev/null +++ b/share/python/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB _FILES "*.py" "*.svg") +install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python/ipy) + diff --git a/share/python/tests/exportPNGCMDline.py b/share/python/tests/exportPNGCMDline.py new file mode 100644 index 0000000000..1e1c528e7f --- /dev/null +++ b/share/python/tests/exportPNGCMDline.py @@ -0,0 +1,27 @@ +# Test file demonstating opening and PNG exporting from the command line + +import os +import tempfile + +if _ipy.getMode() == 'gui': + print("This test must be run without GUI."); +else: + fn = _ipy.getScriptParam() + if len(fn) == 0: + print("No filename supplied via --python-script-argument") + else: + # Open the file + _ipy.fileOpen(fn) + + tempdir = tempfile.gettempdir() + + outPNG = os.path.join(tempdir, 'export.png') + + # Export as PNG. + dpi = 150 + area = 'page' + areaSnap = 1 + + _ipy.exportPNG(outPNG, dpi, area, areaSnap) + + print("Exported file saved to " + outPNG) diff --git a/share/python/tests/inkscape_mode.py b/share/python/tests/inkscape_mode.py new file mode 100644 index 0000000000..3421e13c45 --- /dev/null +++ b/share/python/tests/inkscape_mode.py @@ -0,0 +1,17 @@ +# Test for getting the Inkscape mode (GUI or commandline). + +# Test command line mode with +# inkscape --without-gui --python-script=inkscape_mode.py --python-script-argument="Test argument" + +mode = _ipy.getMode() + +if mode == 'gui': + print("Inkscape is running in GUI mode") +elif mode == 'commandline': + print("Inkscape is running in commandline mode") + + param = _ipy.getScriptParam() + print('The script parameter is "' + param + '"') +else: + print('ERROR: Mode should either be "gui" or "commandline" but is "' + mode + '"') + diff --git a/share/python/tests/misc.py b/share/python/tests/misc.py new file mode 100644 index 0000000000..4bad4da9a2 --- /dev/null +++ b/share/python/tests/misc.py @@ -0,0 +1,5 @@ +# Misc tests + +print('Inkscape version: ' + _ipy.getVersion()) +print('Python ressource directory: ' + _ipy.getPythonPath()) + diff --git a/share/python/tests/openSaveExport.py b/share/python/tests/openSaveExport.py new file mode 100644 index 0000000000..ee072e4d77 --- /dev/null +++ b/share/python/tests/openSaveExport.py @@ -0,0 +1,31 @@ +# Test file demonstrating opening, saving and PNG exporting + +import os +import tempfile + +# Get the path to the splash screen +parent = os.path.split(_ipy.getPythonPath()) +parent = parent[0] +fnam = os.path.join(parent, 'screens/about.svg') + +# Open the file +_ipy.fileOpen(fnam) + +tempdir = tempfile.gettempdir() + +outPDF = os.path.join(tempdir, 'about.pdf') +outPNG = os.path.join(tempdir, 'about.png') + +# Try to save as PDF. This may pop up user interaction dialogs. +_ipy.fileSaveCopy(outPDF, 'application/pdf') + +# Export as PNG. +dpi = 150 +area = 'page' +areaSnap = 1 + +_ipy.exportPNG(outPNG, dpi, area, areaSnap) + +print('Wrote about.pdf and about.png to ' + tempdir) + + diff --git a/share/python/tests/outputMIMEs.py b/share/python/tests/outputMIMEs.py new file mode 100644 index 0000000000..6add1356d4 --- /dev/null +++ b/share/python/tests/outputMIMEs.py @@ -0,0 +1,14 @@ +# Dump the available output MIME types + +mimes = _ipy.getOutputMIMEs() + +for i in mimes: + name = _ipy.getOutputFileTypeName(i) + tooltip = _ipy.getOutputFileTooltip(i) + extension = _ipy.getOutputFileExtension(i) + + print('MIME: ' + i) + print(' Name: ' + name) + print(' Extension: ' + extension) + print(' Tooltip: ' + tooltip) + diff --git a/share/python/tests/verbs.py b/share/python/tests/verbs.py new file mode 100644 index 0000000000..40dc146f5b --- /dev/null +++ b/share/python/tests/verbs.py @@ -0,0 +1,19 @@ +# Tests for obtaining and running Inkscape verbs + +def dumpVerbs(): + verbs = _ipy.getVerbs() + + for i in verbs: + name = _ipy.getVerbName(i) + tooltip = _ipy.getVerbTooltip(i) + + print('Verb: ' + i) + print(' Name: ' + name) + print(' Tooltip: ' + tooltip) + +def runVerbTest(): + _ipy.runVerbs(['ToggleGrid']) + +dumpVerbs() + +runVerbTest() diff --git a/share/ui/menus.xml b/share/ui/menus.xml index 03cd74d748..b011d9e3d3 100644 --- a/share/ui/menus.xml +++ b/share/ui/menus.xml @@ -82,6 +82,7 @@ + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e7d78bcb5..c43b40777f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,8 @@ set(inkscape_SRC document-undo.cpp document.cpp ege-color-prof-tracker.cpp + emb_python.cpp + pyhelper.cpp event-log.cpp extract-uri.cpp file.cpp @@ -127,6 +129,8 @@ set(inkscape_SRC document-undo.h document.h ege-color-prof-tracker.h + emb_python.h + emb_python_doc.h enums.h event-log.h event.h diff --git a/src/desktop.cpp b/src/desktop.cpp index 335afa17d1..65f6e7aaed 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -1917,6 +1917,7 @@ SPDesktop::show_dialogs() mapVerbPreference.insert(std::make_pair ("FillAndStroke", "/dialogs/fillstroke") ); mapVerbPreference.insert(std::make_pair ("ExtensionEditor", "/dialogs/extensioneditor") ); mapVerbPreference.insert(std::make_pair ("AlignAndDistribute", "/dialogs/align") ); + mapVerbPreference.insert(std::make_pair ("Console", "/dialogs/console") ); mapVerbPreference.insert(std::make_pair ("DocumentMetadata", "/dialogs/documentmetadata") ); mapVerbPreference.insert(std::make_pair ("DocumentProperties", "/dialogs/documentoptions") ); mapVerbPreference.insert(std::make_pair ("FilterEffectsDialog", "/dialogs/filtereffects") ); diff --git a/src/emb_python.cpp b/src/emb_python.cpp new file mode 100644 index 0000000000..9e71a6d419 --- /dev/null +++ b/src/emb_python.cpp @@ -0,0 +1,778 @@ +/** @file + * @brief Embedded Python stuff + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +/* + * This file contains some code copied/modified from other Inkscape source files since it is a kind of glue + * layer. However, I may have failed to place the correct notice on every location. (I don't know if this is even + * necessary.) + */ + +#if WITH_EMBPYTHON + +#include "emb_python.h" + + +namespace Inkscape { + +// ------------ C wrappers for interfacing Python --------------------------------------- +extern "C" { + +static PyMethodDef ipyMethods[] = { + {"conAppendStdOut", ipyConAppendStdOut, METH_VARARGS, conAppendStdOut__doc__}, + {"conAppendStdErr", ipyConAppendStdErr, METH_VARARGS, conAppendStdErr__doc__}, + {"conClear", ipyConClear, METH_NOARGS, conClear__doc__}, + + {"getVersion", ipyGetVersion, METH_NOARGS, getVersion__doc__}, + {"runVerbs", ipyRunVerbs, METH_VARARGS, runVerbs__doc__}, + + {"getVerbs", ipyGetVerbs, METH_NOARGS, getVerbs__doc__}, + {"getVerbName", ipyGetVerbName, METH_VARARGS, getVerbName__doc__}, + {"getVerbTooltip", ipyGetVerbTooltip, METH_VARARGS, getVerbTooltip__doc__}, + + {"getMode", ipyGetMode, METH_NOARGS, getMode__doc__}, + {"getScriptParam", ipyGetScriptParam, METH_NOARGS, getScriptParam__doc__}, + {"getPythonPath", ipyGetPythonPath, METH_NOARGS, getPythonPath__doc__}, + + {"setWaitingCursor", ipySetWaitingCursor, METH_VARARGS, setWaitingCursor__doc__}, + + {"fileSaveAs", ipyFileSaveAs, METH_VARARGS, fileSaveAs__doc__}, + {"fileSaveCopy", ipyFileSaveCopy, METH_VARARGS, fileSaveCopy__doc__}, + {"getOutputMIMEs", ipyGetOutputMIMES, METH_NOARGS, getOutputMIMEs__doc__}, + {"getOutputFileTypeName", getOutputFileTypeName, METH_VARARGS, getOutputFileTypeName__doc__}, + {"getOutputFileExtension", getOutputFileExtension, METH_VARARGS, getOutputFileExtension__doc__}, + {"getOutputFileTooltip", getOutputFileTooltip, METH_VARARGS, getOutputFileTooltip__doc__}, + + {"fileOpen", ipyFileOpen, METH_VARARGS, fileOpen__doc__}, + + {"exportPNG", ipyExportPNG, METH_VARARGS, exportPNG__doc__}, + + {"getDesktops", ipyGetDesktops, METH_NOARGS, getDesktops__doc__}, + {"setActiveDesktop", ipySetActiveDesktop, METH_VARARGS, setActiveDesktop__doc__}, + + {nullptr, nullptr, 0, nullptr} +}; + + +static struct PyModuleDef ipyModule = { + PyModuleDef_HEAD_INIT, + "_ipy", + nullptr, + -1, + ipyMethods +}; + +PyMODINIT_FUNC +PyInit_ipy(void) { + PyObject *m = nullptr; + + m = PyModule_Create(&ipyModule); + + if(m == nullptr) { + goto except; + } + +// if(PyModule_AddStringConstant(m, "version", Inkscape::version_string)) { +// goto except; +// } + + pyInstance::ipyError = PyErr_NewException("_ipy.Error", NULL, NULL); + + if(pyInstance::ipyError) { + PyModule_AddObject(m, "Error", pyInstance::ipyError); + } else { + goto except; + } + + goto finally; +except: + Py_XDECREF(m); + m = nullptr; +finally: + return m; +} + +// ---------- Inkscape integrated Python console handling ---------- + +static PyObject *ipyConAppendStdOut(PyObject *self, PyObject *args) { + const char *str; + + if(!PyArg_ParseTuple(args, "s", &str)) { + return NULL; + } + + // Handle listeners + pyInstance *pi = pyInstance::getInstance(""); + pi->emitStdOut(str); + + return Py_BuildValue(""); +} + +static PyObject *ipyConAppendStdErr(PyObject *self, PyObject *args) { + const char *str; + + if(!PyArg_ParseTuple(args, "s", &str)) { + return NULL; + } + + // Handle listeners + pyInstance *pi = pyInstance::getInstance(""); + pi->emitStdErr(str); + + return Py_BuildValue(""); +} + +static PyObject *ipyConClear(PyObject *self, PyObject *args) { + // Handle listeners + pyInstance *pi = pyInstance::getInstance(""); + pi->emitClear(); + + return Py_BuildValue(""); +} + +// ---------- Misc utility functions ---------- + +static PyObject *ipyGetVersion(PyObject *self, PyObject *args) { + return PyUnicode_FromString(Inkscape::version_string); +} + +static PyObject *ipyGetMode(PyObject *self, PyObject *args) { + if(INKSCAPE.use_gui()) { + return PyUnicode_FromString(pyInstance::MODE_GUI); + } else { + return PyUnicode_FromString(pyInstance::MODE_COMMANDLINE); + } +} + +static PyObject *ipyGetScriptParam(PyObject *self, PyObject *args) { + if(pyInstance::inkscapeScriptParam) { + return PyUnicode_FromString(pyInstance::inkscapeScriptParam); + } + return PyUnicode_FromString(""); +} + +static PyObject *ipyGetPythonPath(PyObject *self, PyObject *args) { + std::string path = Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::PYTHON, ""); + + return PyUnicode_FromString(path.c_str()); +} + +static PyObject *ipySetWaitingCursor(PyObject *self, PyObject *args) { + int wait; + + if(!PyArg_ParseTuple(args, "i", &wait)) { + return NULL; + } + + pyHelper::setWaitingCursor(wait); + + return Py_BuildValue(""); +} + +// ---------- Getting (information on) verbs and running verbs ---------- + +static PyObject *ipyRunVerbs(PyObject *self, PyObject *args) { + // Used Python's builtin_sum() from bltinmodule.c as a template. + PyObject *seq, *iter, *item; + + int i = 0; + const char *str; + + if(!PyArg_UnpackTuple(args, "runVerbs", 1, 1, &seq)) { + return NULL; + } + + iter = PyObject_GetIter(seq); + if (iter == NULL) { + PyErr_SetString(PyExc_TypeError, _("Argument must be iterable.")); + return NULL; + } + + while(item = PyIter_Next(iter)) { + str = PyUnicode_AsUTF8(item); + + if(str == NULL) { + PyErr_Format(PyExc_TypeError, _("Item %d in iterable is not a string."), i); + Py_DECREF(item); + Py_DECREF(iter); + return NULL; + } + + if(!pyHelper::runVerb(str)) { + PyErr_Format(pyInstance::ipyError, _("Unable to execute verb %s."), str); + Py_DECREF(item); + Py_DECREF(iter); + return NULL; + } + + ++i; // Count objects for error message. + Py_DECREF(item); + } + + Py_DECREF(iter); + + return Py_BuildValue(""); +} + +static PyObject *ipyGetVerbs(PyObject *self, PyObject *args) { + PyObject *list; + PyObject *vid; + + + list = PyList_New(0); + + if(!list) { + return NULL; + } + + std::vector verbs; + std::vector::iterator it; + + verbs = Inkscape::Verb::getList(); + + + for(it = verbs.begin(); it != verbs.end(); ++it) { + if(!(*it)) { + continue; + } + if(!(*it)->get_name()) { + continue; + } + + vid = PyUnicode_FromString((*it)->get_id()); + if(!vid) { + Py_DECREF(list); + list = NULL; + PyErr_SetString(pyInstance::ipyError, _("ipyGetVerbs: Could not create Python string of verb id.")); + break; + } + + if(PyList_Append(list, vid)) { + Py_DECREF(list); + list = NULL; + Py_DECREF(vid); + + PyErr_SetString(pyInstance::ipyError, _("ipyGetVerbs: Could not append verb id to list.")); + break; + } + Py_DECREF(vid); /// @fixme Is this necessary? Can't find it in the docs. + } + + return list; +} + +static PyObject *ipyGetVerbName(PyObject *self, PyObject *args) { + PyObject *name; + const char *str; + Inkscape::Verb *verb; + const char *namestr; + + if(!PyArg_ParseTuple(args, "s", &str)) { + return NULL; + } + + verb = Inkscape::Verb::getbyid(str); + + if(!verb) { + PyErr_Format(pyInstance::ipyError, _("Unknown verb %s."), str); + return NULL; + } + + namestr = verb->get_name(); + + if(namestr) { + name = PyUnicode_FromString(namestr); + } else { + name = PyUnicode_FromString(""); + } + + if(!name) { + PyErr_Format(pyInstance::ipyError, _("Unable to create name string for verb %s."), str); + return NULL; + } + + return name; +} + +static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args) { + PyObject *tooltip; + const char *str; + Inkscape::Verb *verb; + const char *ttstr; + + if(!PyArg_ParseTuple(args, "s", &str)) { + return NULL; + } + + verb = Inkscape::Verb::getbyid(str); + + if(!verb) { + PyErr_Format(pyInstance::ipyError, _("Unknown verb %s."), str); + return NULL; + } + + ttstr = verb->get_short_tip(); + + if(ttstr) { + tooltip = PyUnicode_FromString(ttstr); + } else { + tooltip = PyUnicode_FromString(""); + } + + if(!tooltip) { + PyErr_Format(pyInstance::ipyError, _("Unable to create tooltip string for verb %s."), str); + return NULL; + } + + return tooltip; +} + +// ---------- File opening, saving, closing ---------- + +/** @brief Handle saving files + * + * @todo There should really be some way (or alternative function) to pass the export settings, e.g. for PDF, PNG + * and so on such that saving can be done without any user interaction. + * Note: have a look at the do_export_* functions in main.cpp on how to implement this functionality. + * + */ +static PyObject *ipyFileSaveAs(PyObject *self, PyObject *args) { + const char *filename; + const char *mime; + const char *errmsg = nullptr; + bool success; + + if(!PyArg_ParseTuple(args, "ss", &filename, &mime)) { + return NULL; + } + + success = pyHelper::fileSaveAs(filename, mime, errmsg, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + + if(success) { + return Py_BuildValue(""); + } else { + PyErr_Format(pyInstance::ipyError, errmsg); + return NULL; + } +} + +/** @brief Handle saving a copy of a file + * + * @todo There should really be some way (or alternative function) to pass the export settings, e.g. for PDF, PNG + * and so on such that saving can be done without any user interaction. + * Note: have a look at the do_export_* functions in main.cpp on how to implement this functionality. + * + */ +static PyObject *ipyFileSaveCopy(PyObject *self, PyObject *args) { + const char *filename; + const char *mime; + const char *errmsg = nullptr; + bool success; + + if(!PyArg_ParseTuple(args, "ss", &filename, &mime)) { + return NULL; + } + + success = pyHelper::fileSaveAs(filename, mime, errmsg, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY); + + if(success) { + return Py_BuildValue(""); + } else { + PyErr_Format(pyInstance::ipyError, errmsg); + return NULL; + } +} + +static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args) { + PyObject *list; + PyObject *vid; + const char *mime; + + + list = PyList_New(0); + + if(!list) { + return NULL; + } + + std::map map = pyHelper::buildSaveExtensionMap(); + std::map::iterator it; + + for(it = map.begin(); it != map.end(); ++it) { + Inkscape::Extension::Output *omod = it->second; + + mime = omod->get_mimetype(); + + vid = PyUnicode_FromString(mime); + if(!vid) { + Py_DECREF(list); + list = NULL; + PyErr_SetString(pyInstance::ipyError, _("ipyGetOutputMIMES: Could not create Python string of MIME type.")); + break; + } + + if(PyList_Append(list, vid)) { + Py_DECREF(list); + list = NULL; + Py_DECREF(vid); + + PyErr_SetString(pyInstance::ipyError, _("ipyGetOutputMIMES: Could not append MIME type to list.")); + break; + } + Py_DECREF(vid); /// @fixme Is this necessary? Can't find it in the docs. + } + + return list; +} + +static PyObject *getOutputFileTypeName(PyObject *self, PyObject *args) { + PyObject *nameobj; + const char *mime; + const char *filetypename; + + if(!PyArg_ParseTuple(args, "s", &mime)) { + return NULL; + } + + filetypename = pyHelper::getOutputMIMEFileTypeName(mime); + + if(filetypename == nullptr) { + PyErr_Format(pyInstance::ipyError, _("Unknown output MIME type %s."), mime); + return NULL; + } + + nameobj = PyUnicode_FromString(filetypename); + + if(!nameobj) { + PyErr_Format(pyInstance::ipyError, _("Unable to create name string MIME type %s."), mime); + return NULL; + } + + return nameobj; +} + +static PyObject *getOutputFileExtension(PyObject *self, PyObject *args) { + PyObject *extobj; + const char *mime; + const char *extension; + + if(!PyArg_ParseTuple(args, "s", &mime)) { + return NULL; + } + + extension = pyHelper::getOutputMIMEExtension(mime); + + if(extension == nullptr) { + PyErr_Format(pyInstance::ipyError, _("Unknown output MIME type %s."), mime); + return NULL; + } + + extobj = PyUnicode_FromString(extension); + + if(!extobj) { + PyErr_Format(pyInstance::ipyError, _("Unable to create name string MIME type %s."), mime); + return NULL; + } + + return extobj; +} + +static PyObject *getOutputFileTooltip(PyObject *self, PyObject *args) { + PyObject *tooltipobj; + const char *mime; + const char *tooltip; + + if(!PyArg_ParseTuple(args, "s", &mime)) { + return NULL; + } + + tooltip = pyHelper::getOutputMIMETooltip(mime); + + if(tooltip == nullptr) { + PyErr_Format(pyInstance::ipyError, _("Unknown output MIME type %s."), mime); + return NULL; + } + + tooltipobj = PyUnicode_FromString(tooltip); + + if(!tooltipobj) { + PyErr_Format(pyInstance::ipyError, _("Unable to create name string MIME type %s."), mime); + return NULL; + } + + return tooltipobj; +} + +static PyObject *ipyFileOpen(PyObject *self, PyObject *args) { + const char *filename; + const char *errmsg = nullptr; + bool success; + + if(!PyArg_ParseTuple(args, "s", &filename)) { + return NULL; + } + + + success = pyHelper::fileOpen(filename, errmsg); + + if(success) { + return Py_BuildValue(""); + } else { + PyErr_Format(pyInstance::ipyError, errmsg); + return NULL; + } + + return Py_BuildValue(""); +} + + +// ---------- File exporting ---------- +static PyObject *ipyExportPNG(PyObject *self, PyObject *args) { + const char *filename; + double dpi = 72.0; + int areaSnap = true; + int useDocumentBGColor = true; + guint32 bgColor; + int rr = 0xff, gg = 0xff, bb = 0xff, oo = 0x00; + pyHelper::PNGExportArea earea = pyHelper::PAGE; + char *areaStr; + + const char *errmsg = nullptr; + bool success; + + if(!PyArg_ParseTuple(args, "sdsi|i(iiii)", &filename, &dpi, &areaStr, &areaSnap, &useDocumentBGColor, &rr, &gg, &bb, &oo)) { + return NULL; + } + + // Check if the colors are within range. + if(rr < pyHelper::RGB_MIN || rr > pyHelper::RGB_MAX) { + PyErr_Format(pyInstance::ipyError, "Red color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + return NULL; + } + if(gg < pyHelper::RGB_MIN || gg > pyHelper::RGB_MAX) { + PyErr_Format(pyInstance::ipyError, "Green color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + return NULL; + } + if(bb < pyHelper::RGB_MIN || bb > pyHelper::RGB_MAX) { + PyErr_Format(pyInstance::ipyError, "Blue color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + return NULL; + } + if(oo < pyHelper::RGB_MIN || oo > pyHelper::RGB_MAX) { + PyErr_Format(pyInstance::ipyError, "Opacity value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + return NULL; + } + + // Convert color channels to uint32 value. + if(!useDocumentBGColor) { + bgColor = (rr << 24) | (gg << 16) | (bb << 8) | oo; + } + + // Convert export mode string to enum of pyHelper + if(strcmp(areaStr, "page") == 0) { + earea = pyHelper::PAGE; + } else if(strcmp(areaStr, "drawing") == 0) { + earea = pyHelper::DRAWING; + } else { + PyErr_Format(pyInstance::ipyError, _("Invalid export area. Valid values are 'page' and 'drawing'.")); + return NULL; + } + + if(dpi < pyHelper::PNG_EXPORT_DPI_MIN || dpi > pyHelper::PNG_EXPORT_DPI_MAX) { + PyErr_Format(pyInstance::ipyError, _("Resolution (dpi) out of range.")); /// @fixme Output what the range is, but PyErr_Format has no format string for floating point numbers? + } + + success = pyHelper::exportPNG(filename, earea, dpi, areaSnap, useDocumentBGColor, bgColor, errmsg); + + if(success) { + return Py_BuildValue(""); + } else { + PyErr_Format(pyInstance::ipyError, errmsg); + return NULL; + } + + return Py_BuildValue(""); +} + +// ---------- Desktop handling ---------- +static PyObject *ipyGetDesktops(PyObject *self, PyObject *args) { + PyObject *list; + PyObject *puri; + std::vector uris = pyHelper::getDesktops(); + + + list = PyList_New(0); + + if(!list) { + return NULL; + } + + std::vector::iterator it; + + for(it = uris.begin(); it != uris.end(); ++it) { + Glib::ustring uri = *it; + + puri = PyUnicode_FromString(uri.c_str()); + if(!puri) { + Py_DECREF(list); + list = NULL; + PyErr_SetString(pyInstance::ipyError, _("ipyGetDesktops: Could not create Python string of desktop URI.")); + break; + } + + if(PyList_Append(list, puri)) { + Py_DECREF(list); + list = NULL; + Py_DECREF(puri); + + PyErr_SetString(pyInstance::ipyError, _("ipyGetOutputMIMES: Could not append URI desktop list.")); + break; + } + Py_DECREF(puri); /// @fixme Is this necessary? Can't find it in the docs. + } + + return list; +} + +static PyObject *ipySetActiveDesktop(PyObject *self, PyObject *args) { + int desktopNr; + + if(!PyArg_ParseTuple(args, "i", &desktopNr)) { + return NULL; + } + + if(pyHelper::setActiveDekstop(desktopNr)) { + return Py_BuildValue(""); + } else { + PyErr_SetString(pyInstance::ipyError, _("setActiveDesktop: Desktop index out of range.")); + return NULL; + } +} + + +} // extern "C" + +// ----------------------- Constructors / Destructors and so on ----------------------- +pyInstance *pyInstance::instance = nullptr; +const char * const pyInstance::MODE_COMMANDLINE = "commandline"; +const char * const pyInstance::MODE_GUI = "gui"; +const char * const pyInstance::INIT_FUNCTION = "inkscapeInit"; +const char * pyInstance::inkscapeScriptParam = nullptr; + +PyObject* pyInstance::ipyError; + +pyInstance* pyInstance::getInstance(char const *progname) { + if(!instance) { + instance = new pyInstance(progname); + } + + return instance; +} + +pyInstance::pyInstance(char const *progname) +{ + wchar_t *wprogname = Py_DecodeLocale(progname, NULL); + if(wprogname == NULL) { + std::cerr << _("Error: cannot decode program name. Ignoring.") << std::endl; + } else { + Py_SetProgramName(wprogname); + } + + PyImport_AppendInittab("_ipy", PyInit_ipy); + + Py_Initialize(); +} + +void +pyInstance::runInitFile(void) +{ + std::string initfile = Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::PYTHON, "init.py"); + + // Run init file + runFile(initfile); + } + +pyInstance::~pyInstance() { + Py_Finalize(); +} + + +// ----------------------- Signals ----------------------- +pyInstance::type_sig_stdout +pyInstance::signal_stdout() +{ + return sig_stdout; +} + +pyInstance::type_sig_stderr +pyInstance::signal_stderr() +{ + return sig_stderr; +} + +pyInstance::type_sig_clear +pyInstance::signal_clear() +{ + return sig_clear; +} + +void +pyInstance::emitStdOut(const char *str) { + sig_stdout.emit(str); +} + +void +pyInstance::emitStdErr(const char *str) { + sig_stderr.emit(str); +} + +void +pyInstance::emitClear(void) { + sig_clear.emit(); +} + + +// ----------------------- Misc implementations ----------------------- +int +pyInstance::runString(char const *str) { + return PyRun_SimpleString(str); +} + +bool +pyInstance::runFile(std::string fname) { + // According to + // https://bytes.com/topic/python/answers/840542-pyrun_simplefile-crashes + // and + // https://stackoverflow.com/questions/3654652/why-does-the-python-c-api-crash-on-pyrun-simplefile + // one must take care to get a library compatible version of the FILE structure: + // + // TODO: Correctly cite bytes/stackoverflow post. Is this note enough? + + PyObject *PyObj = Py_BuildValue("s", fname.c_str()); + FILE *file = _Py_fopen_obj(PyObj, "r+"); + bool success = false; + + if(file != NULL) { + // Run script + //PyRun_SimpleFile(file, fname.c_str()); + if(PyRun_SimpleFileEx(file, fname.c_str(), true) >= 0) { + success = true; + } + } + + Py_DECREF(PyObj); + + return success; +} + + +} // namespace Inkscape + + +#endif // WITH_EMBPYTHON diff --git a/src/emb_python.h b/src/emb_python.h new file mode 100644 index 0000000000..09260bd429 --- /dev/null +++ b/src/emb_python.h @@ -0,0 +1,139 @@ +/** @file + * @brief Embedded Python stuff + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#if WITH_EMBPYTHON + +#ifndef INKSCAPE_EMB_PYTHON_H +#define INKSCAPE_EMB_PYTHON_H + +#include +#include +#include + +#include +#include + +#include + +#include "io/resource.h" +#include "inkscape-version.h" +#include "verbs.h" +#include "desktop.h" +#include "inkscape.h" + +#include "helper/action.h" +#include "helper/png-write.h" + +#include "pyhelper.h" +#include "emb_python_doc.h" + + +namespace Inkscape { + +extern "C" { + + +static PyObject *ipyConAppendStdOut(PyObject *self, PyObject *args); +static PyObject *ipyConAppendStdErr(PyObject *self, PyObject *args); +static PyObject *ipyConClear(PyObject *self, PyObject *args); + +static PyObject *ipyGetVersion(PyObject *self, PyObject *args); +static PyObject *ipyRunVerbs(PyObject *self, PyObject *args); + +static PyObject *ipyGetMode(PyObject *self, PyObject *args); +static PyObject *ipyGetScriptParam(PyObject *self, PyObject *args); +static PyObject *ipyGetPythonPath(PyObject *self, PyObject *args); + +static PyObject *ipyGetVerbs(PyObject *self, PyObject *args); +static PyObject *ipyGetVerbName(PyObject *self, PyObject *args); +static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args); + +static PyObject *ipySetWaitingCursor(PyObject *self, PyObject *args); + +static PyObject *ipyFileSaveAs(PyObject *self, PyObject *args); +static PyObject *ipyFileSaveCopy(PyObject *self, PyObject *args); +static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args); +static PyObject *getOutputFileTypeName(PyObject *self, PyObject *args); +static PyObject *getOutputFileExtension(PyObject *self, PyObject *args); +static PyObject *getOutputFileTooltip(PyObject *self, PyObject *args); + +static PyObject *ipyFileOpen(PyObject *self, PyObject *args); + +static PyObject *ipyExportPNG(PyObject *self, PyObject *args); + +static PyObject *ipyGetDesktops(PyObject *self, PyObject *args); +static PyObject *ipySetActiveDesktop(PyObject *self, PyObject *args); + + +PyMODINIT_FUNC PyInit_ipy(void); + +} // extern "C" + + +/* + * Class for handling the Python interpreter. + */ +class pyInstance { +public: + static pyInstance* getInstance(char const *progname); + + ~pyInstance(); + + // Constants + static const char * const MODE_COMMANDLINE; + static const char * const MODE_GUI; + static const char * const INIT_FUNCTION; + + static PyObject* ipyError; + + // Signals + typedef sigc::signal type_sig_stdout; + typedef sigc::signal type_sig_stderr; + typedef sigc::signal type_sig_clear; + + type_sig_stdout signal_stdout(); + type_sig_stderr signal_stderr(); + type_sig_clear signal_clear(); + + void emitStdOut(const char *str); + void emitStdErr(const char *str); + void emitClear(void); + + // Misc functions + void runInitFile(void); + + int runString(char const *str); + bool runFile(std::string fname); + + static const char *inkscapeScriptParam; // Script parameter string if run from command line + +private: + static pyInstance *instance; + + // No default constructor, noncopyable, nonassignable + pyInstance(); + pyInstance(char const *progname); + pyInstance operator=(pyInstance const &d); + + pyInstance(pyInstance const &d); + +protected: + type_sig_stdout sig_stdout; + type_sig_stderr sig_stderr; + type_sig_clear sig_clear; +}; + + +} // namespace Inkscape + +#endif // INKSCAPE_EMB_PYTHON_H + +#endif // WITH_EMBPYTHON diff --git a/src/emb_python_doc.h b/src/emb_python_doc.h new file mode 100644 index 0000000000..c32d76cf1f --- /dev/null +++ b/src/emb_python_doc.h @@ -0,0 +1,159 @@ +/** @file + * @brief Embedded Python doc-strings + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#if WITH_EMBPYTHON + +#ifndef INKSCAPE_EMB_PYTHON_DOC_H +#define INKSCAPE_EMB_PYTHON_DOC_H + +#include + +namespace Inkscape { +extern "C" { + +PyDoc_STRVAR(conAppendStdOut__doc__, + "conAppendStdOut(str)\n" + "--\n\n" + "Append string str to the Inkscape Python console in stdout style.\n"); + +PyDoc_STRVAR(conAppendStdErr__doc__, + "conAppendStdErr(str)\n" + "--\n\n" + "Append string str to the Inkscape Python console in stderr style.\n"); + +PyDoc_STRVAR(conClear__doc__, + "conClear()\n" + "--\n\n" + "Clear the Inkscape Python console.\n"); + +PyDoc_STRVAR(getVersion__doc__, + "getVersion()\n" + "--\n\n" + "Returns the Inkscape version string.\n"); + +PyDoc_STRVAR(runVerbs__doc__, + "runVerbs(iterable)\n" + "--\n\n" + "Run all the Inkscape verb strings contained in the iterable.\n"); + +PyDoc_STRVAR(getMode__doc__, + "getMode()\n" + "--\n\n" + "Returns whether Inkscape runs in GUI or command line mode.\n" + "Return value string is either 'gui' or 'commandline'.\n"); + +PyDoc_STRVAR(getScriptParam__doc__, + "getScriptParam()\n" + "--\n\n" + "Returns the command line parameter string supplied with --python-script-argument\n" + "on the command line.\n" + "An empty string is returned, if no (or an empty) parameter was specified.\n"); + +PyDoc_STRVAR(getPythonPath__doc__, + "getPythonPath()\n" + "--\n\n" + "Returns the path to Inkscape's internal Python resources.\n"); + +PyDoc_STRVAR(getVerbs__doc__, + "getVerbs()\n" + "--\n\n" + "Returns a list with all available verbs.\n"); + +PyDoc_STRVAR(getVerbName__doc__, + "getVerbName(str)\n" + "--\n\n" + "Returns the name string of the verb named str.\n"); + +PyDoc_STRVAR(getVerbTooltip__doc__, + "getVerbTooltip(str)\n" + "--\n\n" + "Returns the tooltip string of the verb named str.\n"); + +PyDoc_STRVAR(setWaitingCursor__doc__, + "setWaitingCursor(enable)\n" + "--\n\n" + "If enable is true, Inkscape's waiting cursor is set and user interaction is disabled.\n"); + +PyDoc_STRVAR(fileSaveAs__doc__, + "fileSaveAs(filename, mime)\n" + "--\n\n" + "Save the active desktop into the file filename using the MIME type mime.\n" + "This is similar to selecting the menu item File => Save As. Consequently, user interaction may be requested " + "for some file types, i.e., dialogs for setting the save options are presented.\n"); + +PyDoc_STRVAR(fileSaveCopy__doc__, + "fileSaveCopy(filename, mime)\n" + "--\n\n" + "Save a copy of the active desktop into the file filename using the MIME type mime.\n" + "This is similar to selecting the menu item File => Save a Copy. Consequently, user interaction may be requested " + "for some file types, i.e., dialogs for setting the save options are presented.\n"); + +PyDoc_STRVAR(getOutputMIMEs__doc__, + "getOutputMIMEs()\n" + "--\n\n" + "Returns a list with all available MIME strings that can be used with fileSaveAs and fileSaveCopy.\n"); + +PyDoc_STRVAR(getOutputFileTypeName__doc__, + "getOutputFileTypeName(mime)\n" + "--\n\n" + "Returns the file type-name for a given output MIME type string.\n" + "For example, querying 'image/x-inkscape-svg' yields 'Inkscape SVG (*.svg)'.\n"); + +PyDoc_STRVAR(getOutputFileExtension__doc__, + "getOutputFileExtension(mime)\n" + "--\n\n" + "Returns the file extension for a given output MIME type string including the extension dot.\n" + "For example, querying 'image/x-inkscape-svg' yields '.svg'.\n"); + +PyDoc_STRVAR(getOutputFileTooltip__doc__, + "getOutputFileTooltip(mime)\n" + "--\n\n" + "Returns the tooltip text for a fiven output MIME type string.\n"); + +PyDoc_STRVAR(fileOpen__doc__, + "fileOpen(filename)\n" + "--\n\n" + "Open file filename.\n"); + +PyDoc_STRVAR(exportPNG__doc__, + "exportPNG(filename, dpi, area, areaSnap, useDocumentBGColor, colorTuple)\n" + "--\n\n" + "Export the current desktop as PNG file.\n" + "Params:\n" + " filename: Namestring of the output file.\n" + " dpi: Resolution (in dpi) of the output file.\n" + " area: Either 'drawing' to export the drawing area or 'page' to export the page area.\n" + " areaSnap: If true, the exported area is snapped outwards to the next integer values.\n" + "\n" + "Optional params:\n" + " useDocumentBGColor: If true, the document settings are used to set the backgound\n" + " color of the exported PNG file. If false, the values in colorTuple are used.\n" + " colorTuple: A tuple in the form of (r, g, b, o) where r, g, b and o are the red, green, blue\n" + " and opacity values in the range of 0 to 255 to be used as PNG background color.\n"); + +PyDoc_STRVAR(getDesktops__doc__, + "getDesktops()\n" + "--\n\n" + "Returns a list with the URI string for each desktop. If the string is not set,\n" + "for example if the document has not been saved, an empty string is included.\n"); + +PyDoc_STRVAR(setActiveDesktop__doc__, + "setActiveDesktop(nr)\n" + "--\n\n" + "Set the desktop nr as active. The number corresponds to index of the list returned\n" + "by getDekstops\n"); + +} // extern "C" +} // namespace Inkscape + +#endif // INKSCAPE_EMB_PYTHON_DOC_H + +#endif // WITH_EMBPYTHON diff --git a/src/file.cpp b/src/file.cpp index e67c79f7b9..83b8856610 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -209,13 +209,27 @@ sp_file_exit() * \param replace_empty if true, and the current desktop is empty, this document * will replace the empty one. */ + bool sp_file_open(const Glib::ustring &uri, Inkscape::Extension::Extension *key, bool add_to_recent, - bool replace_empty) + bool replace_empty, + bool interaction) +{ + const char *msg; + return sp_file_open(uri, key, add_to_recent, replace_empty, interaction, msg); +} + +bool sp_file_open(const Glib::ustring &uri, + Inkscape::Extension::Extension *key, + bool add_to_recent, + bool replace_empty, + bool interaction, + const char *& msg) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; - if (desktop) { + + if (desktop && interaction) { desktop->setWaitingCursor(); } SPDocument *doc = nullptr; @@ -223,81 +237,89 @@ bool sp_file_open(const Glib::ustring &uri, try { doc = Inkscape::Extension::open(key, uri.c_str()); } catch (Inkscape::Extension::Input::no_extension_found &e) { + msg = _("No Inkscape extension found to open the document."); doc = nullptr; } catch (Inkscape::Extension::Input::open_failed &e) { + msg = _("File could not be opened."); doc = nullptr; } catch (Inkscape::Extension::Input::open_cancelled &e) { + msg = _("Open cancelled."); doc = nullptr; cancelled = true; } - if (desktop) { + + if (desktop && interaction) { desktop->clearWaitingCursor(); } if (doc) { + SPDocument *existing = desktop ? desktop->getDocument() : nullptr; + + if(INKSCAPE.use_gui()) { + if (existing && existing->virgin && replace_empty) { + // If the current desktop is empty, open the document there + doc->ensureUpToDate(); // TODO this will trigger broken link warnings, etc. + desktop->change_document(doc); + doc->emitResizedSignal(doc->getWidth().value("px"), doc->getHeight().value("px")); + } else { + // create a whole new desktop and window + SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, nullptr)); // TODO this will trigger broken link warnings, etc. + sp_create_window(dtw, TRUE); + desktop = static_cast(dtw->view); + } + + doc->virgin = FALSE; + + // everyone who cares now has a reference, get rid of our`s + doc->doUnref(); + + SPRoot *root = doc->getRoot(); + + // This is the only place original values should be set. + root->original.inkscape = root->version.inkscape; + root->original.svg = root->version.svg; + + if (INKSCAPE.use_gui()) { + if (sp_version_inside_range(root->version.inkscape, 0, 1, 0, 92)) { + sp_file_convert_dpi(doc); + } + } // If use_gui + + + // resize the window to match the document properties + sp_namedview_window_from_document(desktop); + sp_namedview_update_layers_from_document(desktop); + + if (add_to_recent) { + sp_file_add_recent( doc->getURI() ); + } + + SPNamedView *nv = desktop->namedview; + if (nv->lockguides) { + nv->lockGuides(); + } + // Perform a fixup pass for hrefs. + if ( Inkscape::ResourceManager::getManager().fixupBrokenLinks(doc) ) { + Glib::ustring msg = _("Broken links have been changed to point to existing files."); + desktop->showInfoDialog(msg); + } + + // Check for font substitutions + Inkscape::UI::Dialog::FontSubstitution::getInstance().checkFontSubstitutions(doc); + } - SPDocument *existing = desktop ? desktop->getDocument() : nullptr; - - if (existing && existing->virgin && replace_empty) { - // If the current desktop is empty, open the document there - doc->ensureUpToDate(); // TODO this will trigger broken link warnings, etc. - desktop->change_document(doc); - doc->emitResizedSignal(doc->getWidth().value("px"), doc->getHeight().value("px")); - } else { - // create a whole new desktop and window - SPViewWidget *dtw = sp_desktop_widget_new(sp_document_namedview(doc, nullptr)); // TODO this will trigger broken link warnings, etc. - sp_create_window(dtw, TRUE); - desktop = static_cast(dtw->view); - } - - doc->virgin = FALSE; - - // everyone who cares now has a reference, get rid of our`s - doc->doUnref(); - - SPRoot *root = doc->getRoot(); - - // This is the only place original values should be set. - root->original.inkscape = root->version.inkscape; - root->original.svg = root->version.svg; - - if (INKSCAPE.use_gui()) { - if (sp_version_inside_range(root->version.inkscape, 0, 1, 0, 92)) { - sp_file_convert_dpi(doc); - } - } // If use_gui - - - // resize the window to match the document properties - sp_namedview_window_from_document(desktop); - sp_namedview_update_layers_from_document(desktop); - - if (add_to_recent) { - sp_file_add_recent( doc->getURI() ); - } - - if ( INKSCAPE.use_gui() ) { - - SPNamedView *nv = desktop->namedview; - if (nv->lockguides) { - nv->lockGuides(); - } - // Perform a fixup pass for hrefs. - if ( Inkscape::ResourceManager::getManager().fixupBrokenLinks(doc) ) { - Glib::ustring msg = _("Broken links have been changed to point to existing files."); - desktop->showInfoDialog(msg); - } - - // Check for font substitutions - Inkscape::UI::Dialog::FontSubstitution::getInstance().checkFontSubstitutions(doc); - } // Related bug:#1769679 #18 SPDefs * defs = dynamic_cast(doc->getDefs()); if (defs && !existing) { defs->emitModified(SP_OBJECT_MODIFIED_CASCADE); } + + if(!INKSCAPE.use_gui()) { + INKSCAPE.add_document(doc); + } + return TRUE; - } else if (!cancelled) { + } else if (!cancelled && interaction && INKSCAPE.use_gui()) { gchar *safeUri = Inkscape::IO::sanitizeString(uri.c_str()); gchar *text = g_strdup_printf(_("Failed to load the requested file %s"), safeUri); sp_ui_error_dialog(text); @@ -309,6 +331,7 @@ bool sp_file_open(const Glib::ustring &uri, return FALSE; } + /** * Handle prompting user for "do you want to revert"? Revert on "OK" */ diff --git a/src/file.h b/src/file.h index fadbb09dac..6b78207be3 100644 --- a/src/file.h +++ b/src/file.h @@ -68,7 +68,17 @@ bool sp_file_open( const Glib::ustring &uri, Inkscape::Extension::Extension *key, bool add_to_recent = true, - bool replace_empty = true + bool replace_empty = true, + bool interaction = true + ); + +bool sp_file_open( + const Glib::ustring &uri, + Inkscape::Extension::Extension *key, + bool add_to_recent, + bool replace_empty, + bool interaction, + const char *& msg ); /** diff --git a/src/io/resource.cpp b/src/io/resource.cpp index a373a658fd..c8eacb32c3 100644 --- a/src/io/resource.cpp +++ b/src/io/resource.cpp @@ -60,6 +60,7 @@ gchar *_get_path(Domain domain, Type type, char const *filename) case NONE: g_assert_not_reached(); break; case PALETTES: temp = INKSCAPE_PALETTESDIR; break; case PATTERNS: temp = INKSCAPE_PATTERNSDIR; break; + case PYTHON: temp = INKSCAPE_PYTHONDIR; break; case SCREENS: temp = INKSCAPE_SCREENSDIR; break; case SYMBOLS: temp = INKSCAPE_SYMBOLSDIR; break; case TEMPLATES: temp = INKSCAPE_TEMPLATESDIR; break; diff --git a/src/io/resource.h b/src/io/resource.h index 7a11fd8fa0..c253d6a4d7 100644 --- a/src/io/resource.h +++ b/src/io/resource.h @@ -41,6 +41,7 @@ enum Type { NONE, PALETTES, PATTERNS, + PYTHON, SCREENS, TEMPLATES, TUTORIALS, diff --git a/src/main.cpp b/src/main.cpp index 5de51a60aa..4e1224529a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -115,6 +115,7 @@ #include "main-cmdlineact.h" #include "main-cmdlinexact.h" +#include "emb_python.h" enum { SP_ARG_NONE, @@ -151,6 +152,10 @@ enum { SP_ARG_EXPORT_TEXT_TO_PATH, SP_ARG_EXPORT_IGNORE_FILTERS, SP_ARG_PDF_PAGE, +#if WITH_EMBPYTHON + SP_ARG_PYFILE, + SP_ARG_PYFILE_PARAM, +#endif SP_ARG_EXTENSIONDIR, SP_ARG_QUERY_X, SP_ARG_QUERY_Y, @@ -241,6 +246,13 @@ static gchar *sp_xverbs_yaml_utf8 = nullptr; static gchar *sp_xverbs_yaml = nullptr; #endif // WITH_YAML +#if WITH_EMBPYTHON +static gchar *sp_pyfile = nullptr; +static gchar *sp_pyfile_param = nullptr; +static gchar *sp_pyfile_utf8 = nullptr; +static gchar *sp_pyfile_param_utf8 = nullptr; +#endif + /** * Reset variables to default values. */ @@ -293,6 +305,13 @@ static void resetCommandlineGlobals() { sp_export_svg_utf8 = nullptr; sp_export_inkscape_svg_utf8 = nullptr; sp_global_printer_utf8 = nullptr; + +#if WITH_EMBPYTHON + sp_pyfile = nullptr; + sp_pyfile_param = nullptr; + sp_pyfile_utf8 = nullptr; + sp_pyfile_param_utf8 = nullptr; +#endif } #ifdef _WIN32 @@ -470,6 +489,18 @@ struct poptOption options[] = { N_("Render filtered objects without filters, instead of rasterizing (PS, EPS, PDF)"), nullptr}, +#if WITH_EMBPYTHON + {"python-script", 0, + POPT_ARG_STRING, &sp_pyfile, SP_ARG_PYFILE, + N_("Python script to execute"), + N_("FILENAME")}, + + {"python-script-argument", 0, + POPT_ARG_STRING, &sp_pyfile_param, SP_ARG_PYFILE_PARAM, + N_("Parameter for python script"), + N_("PARAM_STRING")}, +#endif + {"pdf-page", 0, POPT_ARG_INT, &sp_pdf_page, SP_ARG_PDF_PAGE, N_("PDF page to import"), @@ -613,6 +644,21 @@ static void set_extensions_env() // printf("PYTHONPATH = %s\n", g_getenv("PYTHONPATH")); } +#if WITH_EMBPYTHON +void sp_run_cmdline_python_file(void) +{ + if(sp_pyfile) { + if(sp_pyfile_param) { + Inkscape::pyInstance::inkscapeScriptParam = sp_pyfile_param; + } + + Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(""); + if(!ipy->runFile(sp_pyfile)) { + g_error("Could not successfully execute Python script %s.", sp_pyfile); + } + } +} +#endif /** * This is the classic main() entry point of the program, though on some @@ -650,6 +696,8 @@ main(int argc, char **argv) Inkscape::Debug::Logger::init(); bool use_gui; + Inkscape::pyInstance::getInstance(argv[0]); + #if !defined(_WIN32) && !defined(GDK_WINDOWING_QUARTZ) use_gui = (g_getenv("DISPLAY") != nullptr); @@ -795,6 +843,10 @@ static int sp_common_main( int argc, char const **argv, std::vector *flD fixupSingleFilename( &sp_export_svg, &sp_export_svg_utf8 ); fixupSingleFilename( &sp_export_inkscape_svg, &sp_export_inkscape_svg_utf8 ); fixupSingleFilename( &sp_global_printer, &sp_global_printer_utf8 ); +#if WITH_EMBPYTHON + fixupSingleFilename( &sp_pyfile, &sp_pyfile_utf8 ); + fixupSingleFilename( &sp_pyfile_param, &sp_pyfile_param_utf8 ); // FIXME: sp_pyfile_param is actually no filename, but all that fixupSingleFilename seems to do is to apply the locale. +#endif #ifdef WITH_YAML fixupSingleFilename( &sp_xverbs_yaml, &sp_xverbs_yaml_utf8 ); #endif // WITH_YAML @@ -809,6 +861,12 @@ static int sp_common_main( int argc, char const **argv, std::vector *flD sp_export_inkscape_svg_utf8 = g_strdup( sp_export_inkscape_svg ); if ( sp_global_printer ) sp_global_printer_utf8 = g_strdup( sp_global_printer ); +#if WITH_EMBPYTHON + if ( sp_pyfile ) + sp_pyfile_utf8 = g_strdup( sp_pyfile ); + if ( sp_pyfile_param ) + sp_pyfile_param_utf8 = g_strdup( sp_pyfile_param ); +#endif #ifdef WITH_YAML if ( sp_xverbs_yaml ) sp_xverbs_yaml_utf8 = g_strdup( sp_xverbs_yaml ); @@ -958,9 +1016,14 @@ sp_main_gui(int argc, char const **argv) sp_file_new_default(); } +#if WITH_EMBPYTHON + // Initialize Python + Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(argv[0]); + ipy->runInitFile(); +#endif + Glib::signal_idle().connect(sigc::ptr_fun(&Inkscape::CmdLineAction::idle)); main_instance.run(); - #ifdef _WIN32 //We might not need anything here //sp_win32_finish(); <-- this is a NOP func @@ -989,30 +1052,24 @@ static int sp_process_file_list(std::vector fl) for (auto filename:fl) { SPDocument *doc = nullptr; - try { - doc = Inkscape::Extension::open(nullptr, filename); - } catch (Inkscape::Extension::Input::no_extension_found &e) { - doc = nullptr; - } catch (Inkscape::Extension::Input::open_failed &e) { - doc = nullptr; - } - if (doc == nullptr) { - try { - doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), filename); - } catch (Inkscape::Extension::Input::no_extension_found &e) { - doc = nullptr; - } catch (Inkscape::Extension::Input::open_failed &e) { - doc = nullptr; - } + bool sp_file_open(const Glib::ustring &uri, + Inkscape::Extension::Extension *key, + bool add_to_recent, + bool replace_empty, + bool interaction); + + if(!sp_file_open(filename, nullptr, false, true, false)) { + doc = INKSCAPE.active_document(); + } else if(sp_file_open(filename, Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), false, true, false)) { + doc = INKSCAPE.active_document(); } + if (doc == nullptr) { g_warning("Specified document %s cannot be opened (does not exist or not a valid SVG file)", filename); retVal++; } else { - INKSCAPE.add_document(doc); - if (sp_vacuum_defs) { doc->vacuumDocument(); } @@ -1070,6 +1127,10 @@ static int sp_process_file_list(std::vector fl) do_query_dimension (doc, false, sp_query_x? Geom::X : Geom::Y, sp_query_id); } +#if WITH_EMBPYTHON + sp_run_cmdline_python_file(); +#endif + INKSCAPE.remove_document(doc); delete doc; @@ -1175,6 +1236,9 @@ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); g_return_val_if_fail(retVal == 0, 1); if (fl.empty() && !sp_shell +#if WITH_EMBPYTHON + && !sp_pyfile +#endif #ifdef WITH_DBUS && !sp_dbus_listen #endif // WITH_DBUS @@ -1186,14 +1250,24 @@ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Inkscape::Application::create(argv[0], false); INKSCAPE.set_pdf_page(sp_pdf_page); +#if WITH_EMBPYTHON + // Initialize Python + Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(argv[0]); + ipy->runInitFile(); +#endif + if (sp_shell) { int retVal = sp_main_shell(argv[0]); // Run as interactive shell exit((retVal < 0) ? 1 : 0); - } else { + } else if(!fl.empty()) { int retVal = sp_process_file_list(fl); // Normal command line invocation if (retVal){ exit(1); } + } else { +#if WITH_EMBPYTHON + sp_run_cmdline_python_file(); +#endif } return 0; } @@ -2133,6 +2207,22 @@ sp_process_args(poptContext ctx) break; } #endif // WITH_YAML +#if WITH_EMBPYTHON + case SP_ARG_PYFILE: { + gchar const *fn = poptGetOptArg(ctx); + if (fn != nullptr) { + sp_pyfile = g_strdup(fn); + } + break; + } + case SP_ARG_PYFILE_PARAM: { + gchar const *param = poptGetOptArg(ctx); + if (param != nullptr) { + sp_pyfile_param = g_strdup(param); + } + break; + } +#endif // WITH_EMBPYTHON case SP_ARG_VERSION: { printf("Inkscape %s\n", Inkscape::version_string); exit(0); diff --git a/src/object/sp-line.cpp b/src/object/sp-line.cpp index 37b8a1300b..89506e5e01 100644 --- a/src/object/sp-line.cpp +++ b/src/object/sp-line.cpp @@ -157,6 +157,18 @@ void SPLine::set_shape() { c->unref(); } + +void SPLine::setPosition(gdouble x1, gdouble y1, gdouble x2, gdouble y2) +{ + this->x1 = x1; + this->y1 = y1; + this->x2 = x2; + this->y2 = y2; + + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + /* Local Variables: mode:c++ diff --git a/src/object/sp-line.h b/src/object/sp-line.h index f27490c396..9cc6217389 100644 --- a/src/object/sp-line.h +++ b/src/object/sp-line.h @@ -30,6 +30,8 @@ public: SVGLength x2; SVGLength y2; + void setPosition(gdouble x1, gdouble y1, gdouble x2, gdouble y2); + void build(SPDocument *document, Inkscape::XML::Node *repr) override; Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override; void set(SPAttributeEnum key, char const* value) override; diff --git a/src/path-prefix.h b/src/path-prefix.h index 71bbb40fb2..b637904962 100644 --- a/src/path-prefix.h +++ b/src/path-prefix.h @@ -49,6 +49,7 @@ char *get_extensions_path(); # define INKSCAPE_MARKERSDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/markers" ) # define INKSCAPE_PALETTESDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/palettes" ) # define INKSCAPE_PATTERNSDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/patterns" ) +# define INKSCAPE_PYTHONDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/python" ) # define INKSCAPE_SCREENSDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/screens" ) # define INKSCAPE_SYMBOLSDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/symbols" ) # define INKSCAPE_THEMEDIR BR_DATADIR( INKSCAPE_LIBPREFIX "/share/inkscape/themes" ) @@ -73,6 +74,7 @@ char *get_extensions_path(); # define INKSCAPE_KEYSDIR append_inkscape_datadir("keys") # define INKSCAPE_ICONSDIR append_inkscape_datadir("icons") # define INKSCAPE_PIXMAPSDIR append_inkscape_datadir("pixmaps") +# define INKSCAPE_PYTHONDIR append_inkscape_datadir("python") # define INKSCAPE_MARKERSDIR append_inkscape_datadir("markers") # define INKSCAPE_PALETTESDIR append_inkscape_datadir("palettes") # define INKSCAPE_PATTERNSDIR append_inkscape_datadir("patterns") @@ -102,6 +104,7 @@ char *get_extensions_path(); # define INKSCAPE_MARKERSDIR "Contents/Resources/share/inkscape/markers" # define INKSCAPE_PALETTESDIR "Contents/Resources/share/inkscape/palettes" # define INKSCAPE_PATTERNSDIR "Contents/Resources/share/inkscape/patterns" +# define INKSCAPE_PYTHONDIR "Contents/Resources/share/inkscape/python" # define INKSCAPE_SCREENSDIR "Contents/Resources/share/inkscape/screens" # define INKSCAPE_SYMBOLSDIR "Contents/Resources/share/inkscape/symbols" # define INKSCAPE_THEMEDIR "Contents/Resources/share/inkscape/themes" @@ -128,6 +131,7 @@ char *get_extensions_path(); # define INKSCAPE_MARKERSDIR append_inkscape_datadir("inkscape/markers") # define INKSCAPE_PALETTESDIR append_inkscape_datadir("inkscape/palettes") # define INKSCAPE_PATTERNSDIR append_inkscape_datadir("inkscape/patterns") +# define INKSCAPE_PYTHONDIR append_inkscape_datadir("inkscape/python") # define INKSCAPE_SCREENSDIR append_inkscape_datadir("inkscape/screens") # define INKSCAPE_SYMBOLSDIR append_inkscape_datadir("inkscape/symbols") # define INKSCAPE_THEMEDIR append_inkscape_datadir("inkscape/themes") diff --git a/src/pyhelper.cpp b/src/pyhelper.cpp new file mode 100644 index 0000000000..c4ba85d33b --- /dev/null +++ b/src/pyhelper.cpp @@ -0,0 +1,458 @@ +/** @file + * @brief Glue layer between the Python embedding itself and Inkscape internals. + * + * Interface functions for embedding the Python interpreter are located in emb_python.cpp. However, to separate + * Inkscape internal and Python as much as possible, functions that require more than a few lines of Inkscape specific + * code should be located in this helper file. + * + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +/* + * This file contains some code copied/modified from other Inkscape source files since it is a kind of glue + * layer. However, I may have failed to place the correct notice on every location. (I don't know if this is even + * necessary.) + */ + +#if WITH_EMBPYTHON + +#include "pyhelper.h" + +namespace Inkscape { + +/** + * @brief Set or clear Inkscape's waiting cursor + * @return Nothing. + * + * @param[in] wait True enables the waiting cursor, false disables it. + * + * @todo Ideally, enabling the waiting cursor would ensure that the XML tree can only be changed from Python. + * Not sure if the implemented functionality is sufficient. + */ +void +pyHelper::setWaitingCursor(bool wait) { + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if(desktop) { + if(wait) { + desktop->setWaitingCursor(); + desktop->disableInteraction(); + } else { + desktop->clearWaitingCursor(); + desktop->enableInteraction(); + } + } +} + +/** + * @brief Tries to get a verb and run it. + * @return true on successful execution. + * + * @param[in] verbString String of the verb to run. + */ +bool +pyHelper::runVerb(const char *verbString) { + Inkscape::Verb *verb; + ActionContext context; + SPAction *action; + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + if(!desktop) { + std::cerr << _("runVerb: No active desktop.") << std::endl; + return false; + } + + context = ActionContext(desktop); + verb = Inkscape::Verb::getbyid(verbString); + + if (!verb) { + return false; + } + + action = verb->get_action(context); + sp_action_perform(action, NULL); + + return true; +} + +/** + * @brief Saves the active desktop to a file. + * + * Function adapted from main-cmdlinexact.cpp. + * + * @param[in] verbString String of the verb to run. + * @param[in] mime MIME string to use as output file type. + * @param[out] msg In case of an error, the pointer is set to an appropriate static error message string. + * @param[in] fsm Selects whether the function should behave as "Save file as" or "Save a copy". + * + * @return true on success. In case of an error, msg is set to point to the error string. + */ +// Some code copied/based on file.cpp +bool pyHelper::fileSaveAs(const char *filename, const char *mime, const char *& msg, Inkscape::Extension::FileSaveMethod fsm) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + bool success = true; + + std::map extensionsMap = buildSaveExtensionMap(); + std::map::iterator emi; + + // Some sanity checks. Not sure if they are necessary. + if(desktop == nullptr) { + msg = "Desktop is nullptr"; + return false; + } + + SPDocument *doc = desktop->getDocument(); + + if(doc == nullptr) { + msg = "Document is nullptr"; + return false; + } + + emi = extensionsMap.find(Glib::ustring(mime)); + + if(emi == extensionsMap.end()) { + msg = "Unknown mime type"; + return false; + } + + Inkscape::Extension::Extension *ext = emi->second; + + Inkscape::Version save = doc->getRoot()->version.inkscape; + doc->getReprRoot()->setAttribute("inkscape:version", Inkscape::version_string); + + try { + Inkscape::Extension::save(ext, doc, filename, + false, // setextension + false, // check_overwrite + fsm == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS, // official + fsm); + } catch (Inkscape::Extension::Output::no_extension_found &e) { + msg = _("No Inkscape extension found to save the document."); + success = false; + } catch (Inkscape::Extension::Output::file_read_only &e) { + msg = _("File is write protected."); + success = false; + } catch (Inkscape::Extension::Output::save_failed &e) { + msg = _("File could not be saved."); + success = false; + } catch (Inkscape::Extension::Output::save_cancelled &e) { + msg = _("Save cancelled."); + success = false; + } catch (Inkscape::Extension::Output::export_id_not_found &e) { + msg = _("File could not be saved: No object with matching ID found."); + success = false; + } catch (std::exception &e) { + msg = _("File could not be saved."); + success = false; + } catch (...) { + g_critical("Extension '%s' threw an unspecified exception.", ext->get_id()); + msg = _("File could not be saved. Unspecified exception"); + success = false; + } + + if(!success) { + // Restore Inkscape version + doc->getReprRoot()->setAttribute("inkscape:version", sp_version_to_string( save )); + } + + return success; +} + +/** + * @brief Gets the output file type description string for a given MIME type + * + * @param[in] mime MIME string + * + * @example Querying "image/x-inkscape-svg" yields "Inkscape SVG (*.svg)". + * + * @return Pointer to description string, nullptr on error. + */ +const char *pyHelper::getOutputMIMEFileTypeName(const char *mime) +{ + std::map extensionsMap = pyHelper::buildSaveExtensionMap(); + std::map::iterator emi; + + emi = extensionsMap.find(Glib::ustring(mime)); + + if(emi == extensionsMap.end()) { + return nullptr; + } + + Inkscape::Extension::Output *ext = emi->second; + return(ext->get_filetypename()); +} + +/** + * @brief Gets the output file type extension including the extension dot for a given MIME type. + * + * @param[in] mime MIME string + * + * @example Querying "image/x-inkscape-svg" yields ".svg". + * + * @return Pointer to description string, nullptr on error. + */ +const char *pyHelper::getOutputMIMEExtension(const char *mime) +{ + std::map extensionsMap = pyHelper::buildSaveExtensionMap(); + std::map::iterator emi; + + emi = extensionsMap.find(Glib::ustring(mime)); + + if(emi == extensionsMap.end()) { + return nullptr; + } + + Inkscape::Extension::Output *ext = emi->second; + return(ext->get_extension()); +} + +/** + * @brief Gets the tooltip string for a given MIME type. + * + * @param[in] mime MIME string + * + * @example Querying "image/x-inkscape-svg" yields "SVG format with Inkscape extensions". + * + * @return Pointer to description string, nullptr on error. + */ +const char *pyHelper::getOutputMIMETooltip(const char *mime) +{ + std::map extensionsMap = pyHelper::buildSaveExtensionMap(); + std::map::iterator emi; + + emi = extensionsMap.find(Glib::ustring(mime)); + + if(emi == extensionsMap.end()) { + return nullptr; + } + + Inkscape::Extension::Output *ext = emi->second; + return(ext->get_filetypetooltip()); +} + +/** + * @brief Creates a map for translating MIME type strings to Inkscape extensions + * + * Used FileSaveDialogImplGtk::createFileTypeMenu() as template. + * + * @param[in] mime MIME type string + * + * @return Map from MIME type string to extension. + */ +std::map pyHelper::buildSaveExtensionMap(void) +{ + std::map otypes; + + Inkscape::Extension::DB::OutputList extension_list; + Inkscape::Extension::db.get_output_list(extension_list); + + for (Inkscape::Extension::DB::OutputList::iterator current_item = extension_list.begin(); + current_item != extension_list.end(); ++current_item) { + Inkscape::Extension::Output *omod = *current_item; + + if (omod->deactivated()) + continue; + + Glib::ustring mime = omod->get_mimetype(); + otypes.insert(std::pair(mime.casefold(), omod)); + } + + return otypes; +} + +/** + * @brief Opens a file. + * + * @param[in] filename Name of file to load + * @param[out] msg In case of an error, the pointer is set to an appropriate static error message string. + * + * @return true on success + */ +bool pyHelper::fileOpen(const char *filename, const char *& msg) +{ + Glib::ustring fn(filename); + + return sp_file_open(fn, // URI + nullptr, // key + false, // add_to_recent + true, // replace_empty + false, // interaction + msg); +} + +/** + * @brief Export drawing to PNG file. + * + * @param[in] filename Name of the file to write + * @param[in] eArea Selects whether the drawing or page is the area to export + * @param[in] dpi Export resolution. If set to <= 0.0, the default settings are used + * @param[in] areaSnap Snaps the export area outwards to the next integer pixel values if true. + * @param[in] useDocumentBGColor The document background color is used if this is set to true. Otherwise @p bgColor is used a + * output background color. + * @param[in] bgColor PNG background color to use if @p useDocumentBGColor is set to false. + * Format: `bgColor = (red << 24) | (green << 16) | (blue << 8) | opacity` where + * `red`, `green`, `blue` and `opacity` are `uint8_t`. + * @param[out] msg In case of an error, the pointer is set to an appropriate static error message string. + * + * @return true on success + */ +// Based on main.cpp/do_export_png() +bool pyHelper::exportPNG(const char *filename, PNGExportArea eArea, double dpi, bool areaSnap, bool useDocumentBGColor, guint32 bgColor, const char*& msg) +{ + SPDocument *doc = INKSCAPE.active_document(); + + bool filename_from_hint = false; + + if(!doc) { + msg = _("No active document"); + return false; + } + + Geom::Rect area; + + // Check if filename is empty? + if(strlen(filename) < 1) { + msg = _("Empty filename"); + return false; + } + + /** @fixme Oh well. This is now the third implementation of PNG exporting + * (the other ones are the GUI dialog and command line export). The functionality should really be unified into + * one configurable PNG exporter to remove duplicate code. + */ + if(eArea == pyHelper::DRAWING) { + SPObject *o_area = nullptr; + + o_area = doc->getRoot(); + + // write object bbox to area + doc->ensureUpToDate(); + Geom::OptRect areaMaybe = static_cast(o_area)->desktopVisualBounds(); + if(areaMaybe) { + area = *areaMaybe; + } else { + msg = _("Unable to determine a valid bounding box."); + return false; + } + } + + if(eArea == pyHelper::PAGE) { + /* Export the whole page: note: Inkscape uses 'page' in all menus and dialogs, not 'canvas' */ + doc->ensureUpToDate(); + Geom::Point origin(doc->getRoot()->x.computed, doc->getRoot()->y.computed); + area = Geom::Rect(origin, origin + doc->getDimensions()); + } + + if(dpi <= 0.0) { + // default dpi + dpi = Inkscape::Util::Quantity::convert(1, "in", "px"); + } + + // set filename and dpi + if(dpi < pyHelper::PNG_EXPORT_DPI_MIN || dpi > pyHelper::PNG_EXPORT_DPI_MAX) { + msg = _("DPI value out of range."); + return false; + } + + if (areaSnap) { + area = area.roundOutwards(); + } + + unsigned long int width = 0; + unsigned long int height = 0; + + width = (unsigned long int) (Inkscape::Util::Quantity::convert(area.width(), "px", "in") * dpi + 0.5); + height = (unsigned long int) (Inkscape::Util::Quantity::convert(area.height(), "px", "in") * dpi + 0.5); + + if(useDocumentBGColor) { + // read from namedview + Inkscape::XML::Node *nv = sp_repr_lookup_name (doc->rroot, "sodipodi:namedview"); + if (nv && nv->attribute("pagecolor")){ + bgColor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); + } + if (nv && nv->attribute("inkscape:pageopacity")){ + double opacity = 1.0; + sp_repr_get_double (nv, "inkscape:pageopacity", &opacity); + bgColor |= SP_COLOR_F_TO_U(opacity); + } + } + + if ((width >= 1) && (height >= 1) && (width <= PNG_UINT_31_MAX) && (height <= PNG_UINT_31_MAX)) { + if( sp_export_png_file(doc, filename, area, width, height, dpi, + dpi, bgColor, nullptr, nullptr, true, std::vector()) == 1 ) { + } else { + msg = _("Failed to save bitmap"); + return false; + } + } else { + g_warning("Calculated bitmap dimensions %lu %lu are out of range (1 - %lu). Nothing exported.", width, height, (unsigned long int)PNG_UINT_31_MAX); + msg = _("Calculated bitmap dimensions are out of range. Nothing exported."); + return false; + } + + return true; +} + +/** + * @brief Returns a vector with the URI strings of all desktops. + * + * @return Vector with uri strings of all desktops + */ +std::vector pyHelper::getDesktops(void) +{ + std::list desktops; + INKSCAPE.get_all_desktops(desktops); + + std::vector v; + const char *uri; + + for(std::list::iterator i = desktops.begin(); + i != desktops.end(); ++i) { + SPDesktop * desktop = *i; + uri = desktop->getDocument()->getURI(); + if(uri) { + v.push_back(uri); + } else { + v.push_back(""); + } + } + + return v; +} + +/** + * @brief Set the active desktop. + * + * @param[in] n Index of the list returned by get_all_desktops to set active. + * + * @return true on success. + */ +bool pyHelper::setActiveDekstop(int n) +{ + std::list desktops; + INKSCAPE.get_all_desktops(desktops); + + if(n > desktops.size() || n < 0) { + return false; + } + + std::list::iterator it = desktops.begin(); + std::advance(it, n); + + INKSCAPE.activate_desktop(*it); + + return true; +} + +} // namespace Inkscape + +#endif // WITH_EMBPYTHON + diff --git a/src/pyhelper.h b/src/pyhelper.h new file mode 100644 index 0000000000..6157ee819d --- /dev/null +++ b/src/pyhelper.h @@ -0,0 +1,71 @@ +/** @file + * @brief Glue layer between the Python embedding itself and Inkscape internals. + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#if WITH_EMBPYTHON + +#ifndef INKSCAPE_PYHELPER_H +#define INKSCAPE_PYHELPER_H + +#include +#include + +#include "io/resource.h" +#include "inkscape-version.h" +#include "verbs.h" +#include "desktop.h" +#include "inkscape.h" +#include "helper/action.h" + +#include "extension/db.h" +#include "extension/system.h" +#include "file.h" +#include "object/sp-root.h" +#include "extension/output.h" +#include "svg/svg-color.h" +#include "helper/png-write.h" +#include "util/units.h" + +namespace Inkscape { + +class pyHelper { +public: + enum PNGExportArea { + PAGE, + DRAWING + }; + + static constexpr double PNG_EXPORT_DPI_MIN = 0.1; + static constexpr double PNG_EXPORT_DPI_MAX = 10000.0; + static constexpr int RGB_MIN = 0; + static constexpr int RGB_MAX = 0xff; + + static bool runVerb(const char *verbString); + static void setWaitingCursor(bool wait); + static bool fileSaveAs(const char *filename, const char *mime, const char *& msg, Inkscape::Extension::FileSaveMethod fsm = Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + static bool fileOpen(const char *filename, const char *& msg); + + static std::map buildSaveExtensionMap(void); + static const char *getOutputMIMEFileTypeName(const char *mime); + static const char *getOutputMIMEExtension(const char *mime); + static const char *getOutputMIMETooltip(const char *mime); + + static bool exportPNG(const char *filename, PNGExportArea eArea, double dpi, bool areaSnap, bool useDocumentBGColor, guint32 bgColor, const char*& msg); + + static std::vector getDesktops(void); + static bool setActiveDekstop(int n); +}; + +} // namespace Inkscape + + +#endif // #ifdef INKSCAPE_PYHELPER_H + +#endif // WITH_EMBPYTHON diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 3411eeabae..d20bb526d6 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -79,6 +79,7 @@ set(ui_SRC dialog/aboutbox.cpp dialog/align-and-distribute.cpp + dialog/console.cpp dialog/calligraphic-profile-rename.cpp dialog/clonetiler.cpp dialog/color-item.cpp diff --git a/src/ui/dialog/console.cpp b/src/ui/dialog/console.cpp new file mode 100644 index 0000000000..7b4b7366a2 --- /dev/null +++ b/src/ui/dialog/console.cpp @@ -0,0 +1,527 @@ +/** + * @file + * Command line dialog - implementation. + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +/* + * Templates used for this file: + * src/ui/dialog/prototype.[cpp,h] + * src/ui/dialog/align-and-distribute.[cpp,h] + * src/ui/dialog/fill-and-stroke.[cpp,h] + * src/ui/dialog/filedialog.[cpp,h] + * src/ui/dialog/filedialogimpl-* + * ... and probably others I cannot remember. + */ + +#if WITH_EMBPYTHON + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "console.h" +#include "desktop.h" +#include "document.h" +#include "selection.h" +#include "verbs.h" + +#include "helper/icon-loader.h" +#include "ui/icon-names.h" +#include "ui/interface.h" + + +#include "object/sp-line.h" +#include "context-fns.h" +#include "desktop-style.h" +#include "attributes.h" +#include "xml/attribute-record.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +Console::Console() : + UI::Widget::Panel("/dialogs/console", SP_VERB_DIALOG_CONSOLE), + _btnClear(_("Clear")), + _pg_console(Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL))), + _pg_scriptlist(Gtk::manage(new Gtk::ScrolledWindow())), + _desktopChangeConn(), + _deskTrack() +{ + + // Set Fonts + Pango::FontDescription fd(_CMD_FONT); + _cmd.override_font(fd); + _output.override_font(fd); + + // Create tags for TextView + Glib::RefPtr tag; + + _outBuf = _output.get_buffer(); + + _tagInput = _outBuf->create_tag(); + _tagInput->property_foreground() = "#0000ff"; + _tagInput->set_property("weight", Pango::WEIGHT_BOLD); + + _tagOutput = _outBuf->create_tag(); + + _tagError = _outBuf->create_tag(); + _tagError->property_foreground() = "#ff0000"; + + _tagComment = _outBuf->create_tag(); + _tagComment->property_foreground() = "#808080"; + + _outBuf->create_mark("scroll", _outBuf->end(), TRUE); + + // Set tooltips + _cmd.set_tooltip_text(_("Enter command here. Use up/down-arrows keys to access last command.")); + _output.set_tooltip_text(_("Previous commands and output")); + _btnClear.set_tooltip_text(_("Clear previous commands and output window.")); + + // Add the widgets + // Command line page + _output.set_editable(false); + _output.set_border_width(_BORDER_WIDTH); + _scrWin.add(_output); + + _pg_console->pack_start(_scrWin); + _pg_console->pack_start(_cmd, Gtk::PACK_SHRINK); + _pg_console->pack_start(_btnClear, Gtk::PACK_SHRINK); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // Script file page + for(int i = 0; i < Console::nscripts; i++) { + std::string ppath = "/dialogs/console/scriptFile" + std::to_string(i); + + Gtk::Label *scriptFile = new Gtk::Label(prefs->getString(ppath)); + Gtk::Button *btnRun = new Gtk::Button(_("Run!")); + Gtk::Button *btnSet = new Gtk::Button(_("...")); + + scriptFile->set_hexpand(true); + scriptFile->set_halign(Gtk::ALIGN_START); + + btnRun->set_tooltip_text(_("Run Python file")); + btnSet->set_tooltip_text(_("Set Python file")); + scriptFile->set_tooltip_text("Python file"); + + _scriptFiles.push_back(scriptFile); + _scriptRun.push_back(btnRun); + _scriptSet.push_back(btnSet); + + // Attach events + btnRun->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Console::onBtnScriptRun), i)); + btnSet->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Console::onBtnScriptSet), i)); + + _scriptGrid.attach(*scriptFile, 0, i, 1, 1); + _scriptGrid.attach(*btnRun, 2, i, 1, 1); + _scriptGrid.attach(*btnSet, 3, i, 1, 1); + } + _pg_scriptlist->add(_scriptGrid); + + // Add notebook pages + _notebook.append_page(*_pg_console, _createPageTabLabel(_("_Console"), INKSCAPE_ICON("dialog-console"))); + _notebook.append_page(*_pg_scriptlist, _createPageTabLabel(_("Scripts"), INKSCAPE_ICON("dialog-console-script"))); + + _getContents()->pack_start(_notebook); + + // Add widget signals + //_notebook.signal_switch_page().connect(sigc::mem_fun(this, &Console::_onSwitchPage)); + + _cmd.signal_activate().connect(sigc::mem_fun(*this, &Console::onCmdEnter)); + + set_events(Gdk::KEY_PRESS_MASK); + _cmd.signal_key_press_event().connect(sigc::mem_fun(*this, &Console::onKeypress)); + + _btnClear.signal_clicked().connect(sigc::mem_fun(*this, &Console::onBtnClear)); + + // Program name already be initialized by main(), so the argument does not matter. + _python = Inkscape::pyInstance::getInstance(""); + + // Connect to signals for the stdout and stderr redirection + _python->signal_stdout().connect(mem_fun(*this, &Console::onStdOut)); + _python->signal_stderr().connect(mem_fun(*this, &Console::onStdErr)); + _python->signal_clear().connect(mem_fun(*this, &Console::onClear)); + + // Setup desktop connection + _desktopChangeConn = _deskTrack.connectDesktopChanged(sigc::mem_fun(*this, &Console::setTargetDesktop)); + _deskTrack.connect(GTK_WIDGET(gobj())); +} + +Console::~Console() +{ + for(std::vector::iterator it = _scriptFiles.begin(); it != _scriptFiles.end(); ++it) { + delete(*it); + } + for(std::vector::iterator it = _scriptSet.begin(); it != _scriptSet.end(); ++it) { + delete(*it); + } + for(std::vector::iterator it = _scriptRun.begin(); it != _scriptRun.end(); ++it) { + delete(*it); + } + + _desktopChangeConn.disconnect(); + _deskTrack.disconnect(); +} + +/* + * Called when a dialog is displayed, including when a dialog is reopened. + * (When a dialog is closed, it is not destroyed so the contructor is not called. + * This function can handle any reinitialization needed.) + */ +void +Console::present() +{ + UI::Widget::Panel::present(); +} + + +void +Console::onCmdEnter() { + Glib::ustring str; + + str = _cmd.get_text(); + + history.push_back(str); + + _cmd.set_text(""); + + str.append("\n"); + + appendInput(str); + + _python->runString(str.c_str()); +} + +bool +Console::onKeypress(GdkEventKey *key_event) { + if(key_event->keyval == GDK_KEY_Up) { + history.previous_pos(); + _cmd.set_text(history.get()); + + return true; // Don't propagate upwards to prevent selection of Notebook pane. + } + if(key_event->keyval == GDK_KEY_Down) { + history.next_pos(); + _cmd.set_text(history.get()); + + return true; // Don't propagate upwards to prevent selection of Notebook pane. + } + + return false; +} + +void +Console::onStdOut(const char *str) { + Glib::ustring ustr; + + ustr.append(str); + + appendOutput(ustr); +} + +void +Console::onStdErr(const char *str) { + Glib::ustring ustr; + + ustr.append(str); + + appendError(ustr); +} + +void +Console::onClear(void) { + clearOutput(); +} + +void +Console::onBtnClear() { + clearOutput(); +} + +void +Console::clearOutput() { + _outBuf->set_text(""); +} + +void +Console::appendWithTag(Glib::ustring &str, Glib::RefPtr &tag) +{ + // Insert new text + _outBuf->insert_with_tag(_outBuf->end(), str, tag); + + // Scroll to end. See textscroll.c in GTK demos. + /** + * @fixme If we scroll to the end while the notebook page with the scrolled window is not active, i.e., another + * page is selected, the scrollbar disappears. Searched around forever but could not find out what's going wrong. + */ + Gtk::TextBuffer::iterator it = _outBuf->end(); + it.set_line_offset(0); // Move iterator to beginning of line to avoid horizontal scrolling. + Glib::RefPtr mark = _outBuf->get_mark("scroll"); + _outBuf->move_mark(mark, it); + _output.scroll_to(mark); +} + +void +Console::appendOutput(Glib::ustring &str) +{ + appendWithTag(str, _tagOutput); +} + +void +Console::appendInput(Glib::ustring &str) +{ + Glib::ustring str2 = "> " + str; + appendWithTag(str2, _tagInput); +} + +void +Console::appendError(Glib::ustring &str) +{ + appendWithTag(str, _tagError); +} + +void +Console::appendComment(Glib::ustring &str) +{ + appendWithTag(str, _tagComment); +} + +void +Console::onBtnScriptSet(guint nr) { + if(nr < 0 || nr >= Console::nscripts) { + std::cerr << "Script button index out of bounds. Should not happen." << std::endl; /// @todo Use g_assert? + } + + Gtk::FileChooserDialog dlg(_("Please choose a python file"), Gtk::FILE_CHOOSER_ACTION_OPEN); + + sp_transientize(GTK_WIDGET(gobj())); /// @fixme Copy-pasted from Inkscape's file dialog. Don't know what I am doing here. + + dlg.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + dlg.add_button(_("_Select file"), Gtk::RESPONSE_OK); + + auto pyFilter = Gtk::FileFilter::create(); + pyFilter->set_name("Python files"); + pyFilter->add_pattern("*.py"); + dlg.add_filter(pyFilter); + + auto allFilter = Gtk::FileFilter::create(); + allFilter->set_name("All files"); + allFilter->add_pattern("*"); + dlg.add_filter(allFilter); + + int result = dlg.run(); + + switch(result) { + case Gtk::RESPONSE_OK: + break; + case Gtk::RESPONSE_CANCEL: + break; + default: + std::cerr << "Unexpected file chooser result. Should actually not happen." << std::endl; // TODO: Use g_assert + return; + } + + Gtk::Label *lbl = _scriptFiles[nr]; + + lbl->set_text(dlg.get_filename()); + + // Store the filename in the prefences. + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + std::string ppath = "/dialogs/console/scriptFile" + std::to_string(nr); + prefs->setString(ppath, dlg.get_filename()); +} + +void +Console::onBtnScriptRun(guint nr) { + Gtk::Label *lbl = _scriptFiles[nr]; + + Glib::ustring lblTxt = lbl->get_text(); + + if(lblTxt.length() < 1) { + sp_ui_error_dialog(_("Script file not set.\nPlease use the \"...\" button to select a file before clicking \"Run!\".")); + return; + } + + Glib::ustring cStr = "Running " + lblTxt + "\n"; + appendComment(cStr); + _python->runFile(lbl->get_text().c_str()); +} + + +// Copied from fill-and-stroke.cpp +Gtk::HBox& +Console::_createPageTabLabel(const Glib::ustring& label, const char *label_image) +{ + Gtk::HBox *_tab_label_box = Gtk::manage(new Gtk::HBox(false, 4)); + + auto img = Gtk::manage(sp_get_icon_image(label_image, Gtk::ICON_SIZE_MENU)); + _tab_label_box->pack_start(*img); + + Gtk::Label *_tab_label = Gtk::manage(new Gtk::Label(label, true)); + _tab_label_box->pack_start(*_tab_label); + _tab_label_box->show_all(); + + return *_tab_label_box; +} + +//void +//Console::_onSwitchPage(Gtk::Widget * /*page*/, guint pagenum) +//{ +//} + +void +Console::setTargetDesktop(SPDesktop *desktop) +{ + if(targetDesktop != desktop) { + targetDesktop = desktop; + } +} + +entryHistory::entryHistory() { +} + + +/** + * @brief Handles the wrap-around of the ringbuffer index. + * + * Warning: Does not handle the case of going around the whole buffer more than once! + * + * @return Wrapped around value of the ring buffer index. + * + * @param[in] v Ring buffer index to be wrapped around, i.e. exceeding the min/max index of the buffer. + * + */ +int +entryHistory::handleWrap(int v) { + // Mind the order of the if statements. (Neg. value must be treated first.) + + if(v < 0) { + return hEntries.size() + v; + } + + if(v >= hEntries.size()) { + return v - hEntries.size(); + } + + return v; +} + +/** + * @brief Inserts a new command into the ring buffer and resets the reading position to the this (newest) entry. + * + * @return Nothing. + * + * @param[in] s String to insert. + * + */ +void +entryHistory::push_back(Glib::ustring s) { + hEntries.at(wrPos) = s; + + wrPos = handleWrap(wrPos + 1); + + reset_pos(); +} + +/** + * @brief Sets the reading position to the last written position. + * + * @return Nothing. + * + */ +void +entryHistory::reset_pos(void) { + rdPos = wrPos; +} + +/** + * @brief Apply a change of the reading position in the buffer, but only if the selected buffer slot is not empty. + * + * This function is called internally to select a new reading position in the history ring buffer. + * Since it is useless to let the user scroll though empty history slots (slots that haven't been filled with + * commands), the new position is only accepted if it actually contains some data. Otherwise, the old index @p idx is + * returned without change. + * + * @return New reading pointer position. + * + * @param[in] idx Index value to apply offset to. + * @param[in] offset Offset value to apply to @p idx. Can be positive or negative. + * + */ +int +entryHistory::applyWithCheck(int idx, int offset) { + // handleWrap can only handle one wrap around at once at most. + if((offset > 0 && offset > hEntries.size()) || (offset < 0 && -offset > hEntries.size())) { + std::cout << "entryHistory::applyWithCheck: Offset too large!" << std::endl; + return idx; + } + + int newIdx = handleWrap(idx + offset); + + if(!hEntries.at(newIdx).empty()) { // Accept the new value only if there is a stored value + return newIdx; + } + + return idx; +} + +/** + * @brief Select the previous position in the history ring buffer if it is non-empty. + * + * Call this function if the user presses the key to go back (in the direction of older commands) one entry in the history. + * + */ +void +entryHistory::previous_pos(void) { + rdPos = applyWithCheck(rdPos, -1); +} + +/** + * @brief Select the next position in the history ring buffer if it is non-empty. + * + * Call this function if the user presses the key to go forward (in the direction of newer commands) one entry in the history. + * + */ +void +entryHistory::next_pos(void) { + rdPos = applyWithCheck(rdPos, 1); +} + +/** + * @brief Get the currently selected history entry. + * + * @return Currently selected hisory entry. + * + */ +Glib::ustring +entryHistory::get(void) { + return hEntries.at(rdPos); +} + + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + + +#endif // WITH_EMBPYTHON + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/console.h b/src/ui/dialog/console.h new file mode 100644 index 0000000000..416a6c61e7 --- /dev/null +++ b/src/ui/dialog/console.h @@ -0,0 +1,196 @@ +/** @file + * @brief Command line dialog + */ +/* Authors: + * Thomas Wiesner + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +/* + * Templates used for this file: + * src/ui/dialog/prototype.[cpp,h] + * src/ui/dialog/align-and-distribute.[cpp,h] + * src/ui/dialog/fill-and-stroke.[cpp,h] + * src/ui/dialog/filedialog.[cpp,h] + * src/ui/dialog/filedialogimpl-* + * ... and probably others I cannot remember. + */ + + +#if WITH_EMBPYTHON + +#ifndef INKSCAPE_UI_DIALOG_CONSOLE_H +#define INKSCAPE_UI_DIALOG_CONSOLE_H + +#include +#include +#include "ui/widget/panel.h" +#include "ui/widget/frame.h" +#include "ui/widget/notebook-page.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "2geom/rect.h" +#include "ui/dialog/desktop-tracker.h" +#include "ui/dialog-events.h" + +#include "emb_python.h" + + +namespace Inkscape { +namespace UI { +namespace Dialog { + +/** + * @class entryHistory + * @brief Manages the history of commands entered in the Python console. + */ +class entryHistory { +public: + entryHistory(); + + void push_back(Glib::ustring s); // Add a new history entry + void reset_pos(void); // Reset read position to last written + void previous_pos(void); // Move to previous item in history + void next_pos(void); // Move to get item in history + Glib::ustring get(void); // Get current item in history + +protected: + +private: + // Noncopyable, nonassignable + entryHistory(entryHistory const &d); + entryHistory operator=(entryHistory const &d); + + static const int historyDepth = 32; + int rdPos = 0; + int wrPos = 0; + + std::array hEntries; + + int handleWrap(int v); // Handles ring buffer index wrap around + int applyWithCheck(int idx, int offset); // Apply offset to index, but only if the new index points to a non-empty slot. +}; + +/** + * @class Console + * @brief Dialog implementing a Python console. Also has a tab for runnnng script files. + */ +class Console : public Widget::Panel { +public: + virtual ~Console(); + + static Console &getInstance() { return *new Console(); } + + virtual void present(); + +protected: + +private: + // No default constructor, noncopyable, nonassignable + Console(); + Console(Console const &d); + Console operator=(Console const &d); + + const guint _BORDER_WIDTH = 5; + const Glib::ustring _CMD_FONT = "Monospace"; + + const int nscripts = 16; + + entryHistory history; + + // Widgets + Gtk::Notebook _notebook; + Gtk::Box *_pg_console; + Gtk::ScrolledWindow *_pg_scriptlist; + + + Gtk::ScrolledWindow _scrWin; + Gtk::TextView _output; + Glib::RefPtr _outBuf; + Gtk::Entry _cmd; + Gtk::Button _btnClear; + + // Script file widgets + Gtk::Grid _scriptGrid; + std::vector _scriptFiles; + std::vector _scriptSet; + std::vector _scriptRun; + + Glib::RefPtr _tagInput; + Glib::RefPtr _tagOutput; + Glib::RefPtr _tagError; + Glib::RefPtr _tagComment; + + void onBtnScriptSet(guint nr); + void onBtnScriptRun(guint nr); + + void _updateLabel(); + + void onCmdEnter(); + bool onKeypress(GdkEventKey *key_event); + + + void onBtnClear(); + + void onStdOut(const char *); + void onStdErr(const char *); + void onClear(void); + + // Misc member functions + void clearOutput(); + + void appendWithTag(Glib::ustring &str, Glib::RefPtr &tag); + + void appendOutput(Glib::ustring &str); + void appendInput(Glib::ustring &str); + void appendError(Glib::ustring &str); + void appendComment(Glib::ustring &str); + + pyInstance *_python; + + // Widgets helpers + Gtk::HBox& _createPageTabLabel(const Glib::ustring& label, const char *label_image); + void _onSwitchPage(Gtk::Widget * /*page*/, guint pagenum); +// void _savePagePref(guint page_num); + + // Desktop connection + DesktopTracker _deskTrack; + sigc::connection _desktopChangeConn; + SPDesktop *targetDesktop; + + void setTargetDesktop(SPDesktop *desktop); +}; + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_DIALOG_COMMANDLINE_H + +#endif // WITH_EMBPYTHON + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index 28e45d50ad..f26f12babb 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -17,6 +17,7 @@ #include "ui/dialog/prototype.h" #include "ui/dialog/align-and-distribute.h" +#include "ui/dialog/console.h" #include "ui/dialog/document-metadata.h" #include "ui/dialog/document-properties.h" #include "ui/dialog/extension-editor.h" @@ -106,6 +107,9 @@ DialogManager::DialogManager() { registerFactory("Prototype", &create); registerFactory("AlignAndDistribute", &create); registerFactory("DocumentMetadata", &create); +#if WITH_EMBPYTHON + registerFactory("Console", &create); +#endif registerFactory("DocumentProperties", &create); registerFactory("ExtensionEditor", &create); registerFactory("FillAndStroke", &create); @@ -149,6 +153,9 @@ DialogManager::DialogManager() { registerFactory("Prototype", &create); registerFactory("AlignAndDistribute", &create); registerFactory("DocumentMetadata", &create); +#if WITH_EMBPYTHON + registerFactory("Console", &create); +#endif registerFactory("DocumentProperties", &create); registerFactory("ExtensionEditor", &create); registerFactory("FillAndStroke", &create); diff --git a/src/verbs.cpp b/src/verbs.cpp index fe617148a9..ae94fc13da 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -817,7 +817,7 @@ Verb *Verb::get_search(unsigned int code) /** * Find a Verb using it's ID. * - * This function uses the \c _verb_ids has table to find the + * This function uses the \c _verb_ids hash table to find the * verb by it's id. Should be much faster than previous * implementations. * @@ -2214,6 +2214,9 @@ void DialogVerb::perform(SPAction *action, void *data) case SP_VERB_DIALOG_ALIGN_DISTRIBUTE: dt->_dlg_mgr->showDialog("AlignAndDistribute"); break; + case SP_VERB_DIALOG_CONSOLE: + dt->_dlg_mgr->showDialog("Console"); + break; case SP_VERB_DIALOG_SPRAY_OPTION: dt->_dlg_mgr->showDialog("SprayOptionClass"); break; @@ -3209,7 +3212,11 @@ Verb *Verb::_base_verbs[] = { N_("Select which color separations to render in Print Colors Preview rendermode"), nullptr), new DialogVerb(SP_VERB_DIALOG_EXPORT, "DialogExport", N_("_Export PNG Image..."), N_("Export this document or a selection as a PNG image"), INKSCAPE_ICON("document-export")), - // Help +#if WITH_EMBPYTHON + new DialogVerb(SP_VERB_DIALOG_CONSOLE, "DialogConsole", N_("Python console..."), + N_("Python console"), INKSCAPE_ICON("dialog-console")), +#endif + // Help new HelpVerb(SP_VERB_HELP_ABOUT_EXTENSIONS, "HelpAboutExtensions", N_("About E_xtensions"), N_("Information on Inkscape extensions"), nullptr), new HelpVerb(SP_VERB_HELP_MEMORY, "HelpAboutMemory", N_("About _Memory"), N_("Memory usage information"), diff --git a/src/verbs.h b/src/verbs.h index fec42cca24..bcda0947d0 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -342,6 +342,7 @@ enum { SP_VERB_DIALOG_SVG_FONTS, SP_VERB_DIALOG_PRINT_COLORS_PREVIEW, SP_VERB_DIALOG_EXPORT, + SP_VERB_DIALOG_CONSOLE, /* Help */ SP_VERB_HELP_ABOUT_EXTENSIONS, SP_VERB_HELP_MEMORY, @@ -603,6 +604,7 @@ public: return get_search(code); } } + static Verb * getbyid (gchar const * id, bool verbose = true); /** -- GitLab From 467f5ce2d156512fcb5fe70762c915dad840495d Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 21 Oct 2018 14:23:52 +0200 Subject: [PATCH 02/13] Fixed missing #if WITH_EMBPYTHON --- src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 4e1224529a..ba4e2ca4f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -696,7 +696,9 @@ main(int argc, char **argv) Inkscape::Debug::Logger::init(); bool use_gui; +#if WITH_EMBPYTHON Inkscape::pyInstance::getInstance(argv[0]); +#endif #if !defined(_WIN32) && !defined(GDK_WINDOWING_QUARTZ) -- GitLab From 73e8ab709e3a1c31edc3ff1fc58f7479cb272924 Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 4 Nov 2018 14:22:07 +0100 Subject: [PATCH 03/13] Update POTFILES.in --- po/POTFILES.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/po/POTFILES.in b/po/POTFILES.in index 9357b784b8..370702eb1a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,6 +1,6 @@ # List of source files containing translatable strings. # Please keep this file sorted alphabetically. -# Generated by generate_POTFILES.sh at Sat Oct 13 21:35:19 CEST 2018 +# Generated by ./generate_POTFILES.sh at Sun Nov 4 12:53:12 CET 2018 [encoding: UTF-8] inkscape.appdata.xml.in inkscape.desktop.in @@ -16,6 +16,7 @@ src/display/canvas-axonomgrid.cpp src/display/canvas-grid.cpp src/display/snap-indicator.cpp src/document.cpp +src/emb_python.cpp src/event-log.cpp src/extension/dependency.cpp src/extension/effect.cpp @@ -210,6 +211,7 @@ src/object/sp-use.cpp src/path-chemistry.cpp src/preferences-skeleton.h src/preferences.cpp +src/pyhelper.cpp src/rdf.cpp src/selection-chemistry.cpp src/selection-describer.cpp @@ -229,6 +231,7 @@ src/ui/dialog/attrdialog.cpp src/ui/dialog/calligraphic-profile-rename.cpp src/ui/dialog/clonetiler.cpp src/ui/dialog/color-item.cpp +src/ui/dialog/console.cpp src/ui/dialog/debug.cpp src/ui/dialog/document-metadata.cpp src/ui/dialog/document-properties.cpp -- GitLab From 8e324dbf000caf1724c6bd846dac137d3fe494ce Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 11 Nov 2018 11:03:03 +0100 Subject: [PATCH 04/13] Fixed compile error due to merge. --- src/ui/dialog/console.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/dialog/console.cpp b/src/ui/dialog/console.cpp index 7b4b7366a2..d2c73cad35 100644 --- a/src/ui/dialog/console.cpp +++ b/src/ui/dialog/console.cpp @@ -32,7 +32,7 @@ #include "selection.h" #include "verbs.h" -#include "helper/icon-loader.h" +#include "ui/icon-loader.h" #include "ui/icon-names.h" #include "ui/interface.h" -- GitLab From 367938cd69cb43e9d7b60e29aea9f14a6581310e Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Wed, 26 Dec 2018 22:04:16 +0100 Subject: [PATCH 05/13] Running scripts from commandline is possible, again. --- CMakeScripts/Modules/FindEmbPython.cmake | 4 ++ src/CMakeLists.txt | 2 + src/actions/actions-python.cpp | 79 ++++++++++++++++++++++++ src/actions/actions-python.h | 29 +++++++++ src/emb_python.cpp | 2 +- src/inkscape-application.cpp | 69 ++++++++++++++++++++- src/inkscape-application.h | 5 ++ 7 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 src/actions/actions-python.cpp create mode 100644 src/actions/actions-python.h diff --git a/CMakeScripts/Modules/FindEmbPython.cmake b/CMakeScripts/Modules/FindEmbPython.cmake index 55e9d5ba77..384954e10f 100644 --- a/CMakeScripts/Modules/FindEmbPython.cmake +++ b/CMakeScripts/Modules/FindEmbPython.cmake @@ -22,6 +22,8 @@ else(EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) PATH_SUFFIXES python3.5 python3.5m + python3.6 + pythom3.6m ) message("${EMBPYTHON_INCLUDE_DIR}") @@ -30,6 +32,8 @@ message("${EMBPYTHON_INCLUDE_DIR}") NAMES python3.5 python3.5m + python3.6 + python3.6m PATHS /usr/lib /usr/local/lib diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0d2e189379..ee5b41fb09 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,6 +293,8 @@ set(main_SRC actions/actions-helper.cpp actions/actions-output.h actions/actions-output.cpp + actions/actions-python.h + actions/actions-python.cpp actions/actions-selection.h actions/actions-selection.cpp actions/actions-transform.h diff --git a/src/actions/actions-python.cpp b/src/actions/actions-python.cpp new file mode 100644 index 0000000000..4ead0f2326 --- /dev/null +++ b/src/actions/actions-python.cpp @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * actions-python.cpp + * + * Created on: Dec 25, 2018 + * Author: Thomas Wiesner + * + * Used actions-base.cpp and actions-output.cpp as template. + */ + +#include + +#include + +#include "emb_python.h" + +#include "actions-python.h" +#include "actions-helper.h" + +#include "inkscape-application.h" +#include "inkscape.h" // Inkscape::Application + + +void +python_set_script_arg(const Glib::VariantBase& value, InkscapeApplication *app) +{ + + Glib::Variant s = Glib::VariantBase::cast_dynamic >(value); + + // std::cout << "Python script param: " << s.get() << std::endl; + + Inkscape::pyInstance::inkscapeScriptParam = g_strdup(s.get().c_str()); /// @fixme: Duplicated string never gets freed, but should not matter since only called once for command line parameter. +} + +void +python_run_script(const Glib::VariantBase& value, InkscapeApplication *app) +{ + Glib::Variant s = Glib::VariantBase::cast_dynamic >(value); + + // std::cout << "Python script: " << s.get() << std::endl; + + Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(""); + if(!ipy->runFile(s.get())) { + g_warning("Could not successfully execute Python script %s.", s.get().c_str()); + } +} + +template +void +add_actions_python(ConcreteInkscapeApplication* app) +{ + Glib::VariantType String(Glib::VARIANT_TYPE_STRING); + + // Debian 9 has 2.50.0 +#if GLIB_CHECK_VERSION(2, 52, 0) + app->add_action_with_parameter("python-script", String, sigc::bind(sigc::ptr_fun(&python_run_script), app)); + app->add_action_with_parameter("python-script-argument", String, sigc::bind(sigc::ptr_fun(&python_set_script_arg), app)); +#else + std::cerr << "add_actions: Some actions require Glibmm 2.52, compiled with: " << glib_major_version << "." << glib_minor_version << std::endl; +#endif +} + +template void add_actions_python(ConcreteInkscapeApplication* app); +template void add_actions_python(ConcreteInkscapeApplication* app); + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : + + + diff --git a/src/actions/actions-python.h b/src/actions/actions-python.h new file mode 100644 index 0000000000..2cd4169cc8 --- /dev/null +++ b/src/actions/actions-python.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * actions-python.h + * + * Created on: Dec 25, 2018 + * Author: Thomas Wiesner + */ + +#ifndef INK_ACTIONS_PYTHON_H +#define INK_ACTIONS_PYTHON_H + +template class ConcreteInkscapeApplication; + +template +void add_actions_python(ConcreteInkscapeApplication* app); + +#endif // INK_ACTIONS_PYTHON_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : + diff --git a/src/emb_python.cpp b/src/emb_python.cpp index 9e71a6d419..4cfbb4f085 100644 --- a/src/emb_python.cpp +++ b/src/emb_python.cpp @@ -761,11 +761,11 @@ pyInstance::runFile(std::string fname) { if(file != NULL) { // Run script //PyRun_SimpleFile(file, fname.c_str()); + if(PyRun_SimpleFileEx(file, fname.c_str(), true) >= 0) { success = true; } } - Py_DECREF(PyObj); return success; diff --git a/src/inkscape-application.cpp b/src/inkscape-application.cpp index 4c39ee51c7..65cb537bf4 100644 --- a/src/inkscape-application.cpp +++ b/src/inkscape-application.cpp @@ -21,10 +21,13 @@ #include "io/file.h" // File open (command line). #include "desktop.h" // Access to window +#include "emb_python.h" + #include "actions/actions-base.h" // Actions #include "actions/actions-output.h" // Actions #include "actions/actions-selection.h" // Actions #include "actions/actions-transform.h" // Actions +#include "actions/actions-python.h" // Actions #ifdef WITH_DBUS # include "extension/dbus/dbus-init.h" @@ -69,6 +72,7 @@ ConcreteInkscapeApplication::ConcreteInkscapeApplication() add_actions_output(this); // actions for file export add_actions_selection(this); // actions for object selection add_actions_transform(this); // actions for transforming selected objects + add_actions_python(this); // actions for Python // ====================== Command Line ====================== @@ -140,11 +144,18 @@ ConcreteInkscapeApplication::ConcreteInkscapeApplication() this->add_main_option_entry(T::OPTION_TYPE_FILENAME, "xverbs", '\0', N_("Process: xverb command file."), N_("XVERBS-FILENAME")); #endif // WITH_YAML +#if WITH_EMBPYTHON + this->add_main_option_entry(T::OPTION_TYPE_STRING, "python-script", '\0', N_("Process: Python script to execute"), N_("PYTHON-FILENAME")); /// @fixme: OPTION_TYPE_FILENAME would be more correct, but then lookup_value only returns an empty string. + this->add_main_option_entry(T::OPTION_TYPE_STRING, "python-script-argument", '\0', N_("Process: Parameter for Python script"), N_("PYTHON-SCRIPT-ARGUMENT")); +#endif + + #ifdef WITH_DBUS this->add_main_option_entry(T::OPTION_TYPE_BOOL, "dbus-listen", '\0', N_("D-Bus: Enter a listening loop for D-Bus messages in console mode."), ""); this->add_main_option_entry(T::OPTION_TYPE_STRING, "dbus-name", '\0', N_("D-Bus: Specify the D-Bus name (default is 'org.inkscape')."), N_("BUS-NAME")); #endif // WITH_DBUS + Gio::Application::signal_handle_local_options().connect(sigc::mem_fun(*this, &InkscapeApplication::on_handle_local_options)); // This is normally called for us... but after the "handle_local_options" signal is emitted. If @@ -153,6 +164,26 @@ ConcreteInkscapeApplication::ConcreteInkscapeApplication() T::register_application(); } + +template +void +ConcreteInkscapeApplication::handlePythonScript() +{ +#if WITH_EMBPYTHON + if(!_pyScriptName.empty()) { + if(!_pyScriptArg.empty()) { + Inkscape::pyInstance::inkscapeScriptParam = _pyScriptArg.c_str(); + } + + Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(""); + + if(!ipy->runFile(_pyScriptName)) { + g_error("Could not successfully execute Python script %s.", _pyScriptName.c_str()); + } + } +#endif +} + SPDocument* InkscapeApplication::get_active_document() { @@ -194,7 +225,15 @@ ConcreteInkscapeApplication::on_startup2() // This should be completely rewritten. Inkscape::Application::create(nullptr, _with_gui); // argv appears to not be used. +#ifdef WITH_EMBPYTHON + // Initialize Python + Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(Glib::get_prgname().c_str()); + ipy->runInitFile(); +#endif + if (!_with_gui) { + handlePythonScript(); + return; } @@ -227,6 +266,8 @@ ConcreteInkscapeApplication::on_startup2() } else { set_app_menu(menu); } + + handlePythonScript(); } // Open document window with default document. Either this or on_open() is called. @@ -554,6 +595,10 @@ ConcreteInkscapeApplication::on_handle_local_options(const Glib::RefPtrcontains("export-id") || options->contains("export-plain-svg") || options->contains("export-text-to_path") +#if WITH_EMBPYTHON + || options->contains("python-script") || /// @todo: Scripts directly from command line only without GUI for now. (Startup and redirection initialization sequence, etc. must be sorted out.) + options->contains("python-script-argument") +#endif ) { _with_gui = false; } @@ -632,6 +677,29 @@ ConcreteInkscapeApplication::on_handle_local_options(const Glib::RefPtrcontains("python-script-argument")) { + Glib::ustring arg; + options->lookup_value("python-script-argument", _pyScriptArg); + +// if (!arg.empty()) { +// _command_line_actions.push_back( +// std::make_pair("python-script-argument", Glib::Variant::create(arg))); +// } + } + + if (options->contains("python-script")) { + Glib::ustring pyfile; + options->lookup_value("python-script", _pyScriptName); + +// if (!pyfile.empty()) { +// _command_line_actions.push_back( +// std::make_pair("python-script", Glib::Variant::create(pyfile))); +// } + } +#endif // ==================== EXPORT ===================== if (options->contains("export-file")) { @@ -700,7 +768,6 @@ ConcreteInkscapeApplication::on_handle_local_options(const Glib::RefPtrlookup_value("export-background-opacity", _file_export.export_background_opacity); } - // ==================== D-BUS ====================== #ifdef WITH_DBUS diff --git a/src/inkscape-application.h b/src/inkscape-application.h index e8cf12ee9e..951cc6e509 100644 --- a/src/inkscape-application.h +++ b/src/inkscape-application.h @@ -55,6 +55,10 @@ protected: // Actions from the command line or file. action_vector_t _command_line_actions; + + // Members for command line Python script handling. + Glib::ustring _pyScriptName; + Glib::ustring _pyScriptArg; }; // T can be either: @@ -91,6 +95,7 @@ private: Glib::RefPtr _builder; + void handlePythonScript(); }; #endif // INKSCAPE_APPLICATION_H -- GitLab From 6f207ef7b8089f2a172ab8853c98a7dd24a32af0 Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 30 Dec 2018 17:39:27 +0100 Subject: [PATCH 06/13] Use module shipped with CMake for finding Python libs. --- CMakeScripts/DefineDependsandFlags.cmake | 10 ++-- CMakeScripts/Modules/FindEmbPython.cmake | 58 ------------------------ 2 files changed, 5 insertions(+), 63 deletions(-) delete mode 100644 CMakeScripts/Modules/FindEmbPython.cmake diff --git a/CMakeScripts/DefineDependsandFlags.cmake b/CMakeScripts/DefineDependsandFlags.cmake index 6c785081a9..94a056eb94 100644 --- a/CMakeScripts/DefineDependsandFlags.cmake +++ b/CMakeScripts/DefineDependsandFlags.cmake @@ -360,13 +360,13 @@ if(WITH_YAML) endif() if(WITH_EMBPYTHON) - find_package(EmbPython) - if(EMBPYTHON_FOUND) + find_package(PythonLibs) + if(PYTHONLIBS_FOUND) set (WITH_EMBPYTHON ON) - list(APPEND INKSCAPE_INCS_SYS ${EMBPYTHON_INCLUDE_DIRS}) - list(APPEND INKSCAPE_LIBS ${EMBPYTHON_LIBRARIES}) + list(APPEND INKSCAPE_INCS_SYS ${PYTHON_INCLUDE_DIRS}) + list(APPEND INKSCAPE_LIBS ${PYTHON_LIBRARIES}) add_definitions(-DWITH_EMBPYTHON) - else(EMBPYTHON_FOUND) + else(PYTHONLIBS_FOUND) set(WITH_EMBPYTHON OFF) message(STATUS "Could not locate the Python library headers: Embedded Python feature will be disabled") endif() diff --git a/CMakeScripts/Modules/FindEmbPython.cmake b/CMakeScripts/Modules/FindEmbPython.cmake deleted file mode 100644 index 384954e10f..0000000000 --- a/CMakeScripts/Modules/FindEmbPython.cmake +++ /dev/null @@ -1,58 +0,0 @@ -# - Try to find the Python library -# Once done this will define -# -# EMBPYTHON_FOUND - system has Python -# EMBPYTHON_INCLUDE_DIRS - the Python include directory -# EMBPYTHON_LIBRARIES - the libraries needed to use python3.5 -# -# Based on FindPotrace.cmake in the same directory. - - -if(EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) - # in cache already - set(EMBPYTHON_FOUND TRUE) -else(EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) - FIND_PATH (EMBPYTHON_INCLUDE_DIR - NAMES - Python.h - PATHS - /usr/include - /usr/local/include - $ENV{DEVLIBS_PATH}/include - PATH_SUFFIXES - python3.5 - python3.5m - python3.6 - pythom3.6m - ) - -message("${EMBPYTHON_INCLUDE_DIR}") - - FIND_LIBRARY (EMBPYTHON_LIBRARY - NAMES - python3.5 - python3.5m - python3.6 - python3.6m - PATHS - /usr/lib - /usr/local/lib - $ENV{DEVLIBS_PATH}/lib - ) - -message("${EMBPYTHON_LIBRARY}") - - if (EMBPYTHON_LIBRARY) - set (EMBPYTHON_FOUND TRUE) - set (EMBPYTHON_INCLUDE_DIRS - ${EMBPYTHON_INCLUDE_DIR} - ) - set(EMBPYTHON_LIBRARIES - ${EMBPYTHON_LIBRARIES} - ${EMBPYTHON_LIBRARY} - ) - message(STATUS "Found Python: ${EMBPYTHON_LIBRARIES}") - endif (EMBPYTHON_LIBRARY) - - -endif (EMBPYTHON_LIBRARIES AND EMBPYTHON_INCLUDE_DIRS) -- GitLab From 8e70bfeed90e6fa863682a644794649f9128221e Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 30 Dec 2018 18:53:50 +0100 Subject: [PATCH 07/13] Rename of WITH_EMBPYTHON to WITH_PYTHON --- CMakeLists.txt | 4 +- CMakeScripts/DefineDependsandFlags.cmake | 8 +-- src/CMakeLists.txt | 2 - src/actions/actions-python.cpp | 79 ------------------------ src/actions/actions-python.h | 29 --------- src/emb_python.cpp | 4 +- src/emb_python.h | 4 +- src/emb_python_doc.h | 4 +- src/inkscape-application.cpp | 12 ++-- src/pyhelper.cpp | 4 +- src/pyhelper.h | 4 +- src/ui/dialog/console.cpp | 4 +- src/ui/dialog/console.h | 4 +- src/ui/dialog/dialog-manager.cpp | 4 +- src/verbs.cpp | 2 +- 15 files changed, 28 insertions(+), 140 deletions(-) delete mode 100644 src/actions/actions-python.cpp delete mode 100644 src/actions/actions-python.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f868421b7..48b089b24c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ option(WITH_LIBWPG "Compile with support of libwpg for WordPerfect Graphics" ON) option(WITH_NLS "Compile with Native Language Support (using gettext)" ON) option(WITH_YAML "Compile with YAML support (enables xverbs)" ON) option(WITH_JEMALLOC "Compile with JEMALLOC support" ON) -option(WITH_EMBPYTHON "Compile with embedded Python support" ON) +option(WITH_PYTHON "Compile with embedded Python support" ON) option(WITH_FUZZ "Compile for fuzzing purpose (use 'make fuzz' only)" OFF) mark_as_advanced(WITH_FUZZ) @@ -313,7 +313,7 @@ message("WITH_OPENMP: ${WITH_OPENMP}") message("WITH_PROFILING: ${WITH_PROFILING}") message("WITH_YAML: ${WITH_YAML}") message("WITH_JEMALLOC: ${WITH_JEMALLOC}") -message("WITH_EMBPYTHON: ${WITH_EMBPYTHON}") +message("WITH_PYTHON: ${WITH_PYTHON}") if(WIN32) message("") diff --git a/CMakeScripts/DefineDependsandFlags.cmake b/CMakeScripts/DefineDependsandFlags.cmake index 94a056eb94..0570aabbde 100644 --- a/CMakeScripts/DefineDependsandFlags.cmake +++ b/CMakeScripts/DefineDependsandFlags.cmake @@ -359,15 +359,15 @@ if(WITH_YAML) endif() endif() -if(WITH_EMBPYTHON) +if(WITH_PYTHON) find_package(PythonLibs) if(PYTHONLIBS_FOUND) - set (WITH_EMBPYTHON ON) + set (WITH_PYTHON ON) list(APPEND INKSCAPE_INCS_SYS ${PYTHON_INCLUDE_DIRS}) list(APPEND INKSCAPE_LIBS ${PYTHON_LIBRARIES}) - add_definitions(-DWITH_EMBPYTHON) + add_definitions(-DWITH_PYTHON) else(PYTHONLIBS_FOUND) - set(WITH_EMBPYTHON OFF) + set(WITH_PYTHON OFF) message(STATUS "Could not locate the Python library headers: Embedded Python feature will be disabled") endif() endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee5b41fb09..0d2e189379 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,8 +293,6 @@ set(main_SRC actions/actions-helper.cpp actions/actions-output.h actions/actions-output.cpp - actions/actions-python.h - actions/actions-python.cpp actions/actions-selection.h actions/actions-selection.cpp actions/actions-transform.h diff --git a/src/actions/actions-python.cpp b/src/actions/actions-python.cpp deleted file mode 100644 index 4ead0f2326..0000000000 --- a/src/actions/actions-python.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * actions-python.cpp - * - * Created on: Dec 25, 2018 - * Author: Thomas Wiesner - * - * Used actions-base.cpp and actions-output.cpp as template. - */ - -#include - -#include - -#include "emb_python.h" - -#include "actions-python.h" -#include "actions-helper.h" - -#include "inkscape-application.h" -#include "inkscape.h" // Inkscape::Application - - -void -python_set_script_arg(const Glib::VariantBase& value, InkscapeApplication *app) -{ - - Glib::Variant s = Glib::VariantBase::cast_dynamic >(value); - - // std::cout << "Python script param: " << s.get() << std::endl; - - Inkscape::pyInstance::inkscapeScriptParam = g_strdup(s.get().c_str()); /// @fixme: Duplicated string never gets freed, but should not matter since only called once for command line parameter. -} - -void -python_run_script(const Glib::VariantBase& value, InkscapeApplication *app) -{ - Glib::Variant s = Glib::VariantBase::cast_dynamic >(value); - - // std::cout << "Python script: " << s.get() << std::endl; - - Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(""); - if(!ipy->runFile(s.get())) { - g_warning("Could not successfully execute Python script %s.", s.get().c_str()); - } -} - -template -void -add_actions_python(ConcreteInkscapeApplication* app) -{ - Glib::VariantType String(Glib::VARIANT_TYPE_STRING); - - // Debian 9 has 2.50.0 -#if GLIB_CHECK_VERSION(2, 52, 0) - app->add_action_with_parameter("python-script", String, sigc::bind(sigc::ptr_fun(&python_run_script), app)); - app->add_action_with_parameter("python-script-argument", String, sigc::bind(sigc::ptr_fun(&python_set_script_arg), app)); -#else - std::cerr << "add_actions: Some actions require Glibmm 2.52, compiled with: " << glib_major_version << "." << glib_minor_version << std::endl; -#endif -} - -template void add_actions_python(ConcreteInkscapeApplication* app); -template void add_actions_python(ConcreteInkscapeApplication* app); - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : - - - diff --git a/src/actions/actions-python.h b/src/actions/actions-python.h deleted file mode 100644 index 2cd4169cc8..0000000000 --- a/src/actions/actions-python.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * actions-python.h - * - * Created on: Dec 25, 2018 - * Author: Thomas Wiesner - */ - -#ifndef INK_ACTIONS_PYTHON_H -#define INK_ACTIONS_PYTHON_H - -template class ConcreteInkscapeApplication; - -template -void add_actions_python(ConcreteInkscapeApplication* app); - -#endif // INK_ACTIONS_PYTHON_H - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : - diff --git a/src/emb_python.cpp b/src/emb_python.cpp index 4cfbb4f085..515113cb65 100644 --- a/src/emb_python.cpp +++ b/src/emb_python.cpp @@ -15,7 +15,7 @@ * necessary.) */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #include "emb_python.h" @@ -775,4 +775,4 @@ pyInstance::runFile(std::string fname) { } // namespace Inkscape -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON diff --git a/src/emb_python.h b/src/emb_python.h index 09260bd429..1d2a58adb0 100644 --- a/src/emb_python.h +++ b/src/emb_python.h @@ -9,7 +9,7 @@ * Released under GNU GPL. Read the file 'COPYING' for more information. */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #ifndef INKSCAPE_EMB_PYTHON_H #define INKSCAPE_EMB_PYTHON_H @@ -136,4 +136,4 @@ protected: #endif // INKSCAPE_EMB_PYTHON_H -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON diff --git a/src/emb_python_doc.h b/src/emb_python_doc.h index c32d76cf1f..2fa2fc6ca7 100644 --- a/src/emb_python_doc.h +++ b/src/emb_python_doc.h @@ -9,7 +9,7 @@ * Released under GNU GPL. Read the file 'COPYING' for more information. */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #ifndef INKSCAPE_EMB_PYTHON_DOC_H #define INKSCAPE_EMB_PYTHON_DOC_H @@ -156,4 +156,4 @@ PyDoc_STRVAR(setActiveDesktop__doc__, #endif // INKSCAPE_EMB_PYTHON_DOC_H -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON diff --git a/src/inkscape-application.cpp b/src/inkscape-application.cpp index 65cb537bf4..3fb90cb6f7 100644 --- a/src/inkscape-application.cpp +++ b/src/inkscape-application.cpp @@ -27,7 +27,6 @@ #include "actions/actions-output.h" // Actions #include "actions/actions-selection.h" // Actions #include "actions/actions-transform.h" // Actions -#include "actions/actions-python.h" // Actions #ifdef WITH_DBUS # include "extension/dbus/dbus-init.h" @@ -72,7 +71,6 @@ ConcreteInkscapeApplication::ConcreteInkscapeApplication() add_actions_output(this); // actions for file export add_actions_selection(this); // actions for object selection add_actions_transform(this); // actions for transforming selected objects - add_actions_python(this); // actions for Python // ====================== Command Line ====================== @@ -144,7 +142,7 @@ ConcreteInkscapeApplication::ConcreteInkscapeApplication() this->add_main_option_entry(T::OPTION_TYPE_FILENAME, "xverbs", '\0', N_("Process: xverb command file."), N_("XVERBS-FILENAME")); #endif // WITH_YAML -#if WITH_EMBPYTHON +#if WITH_PYTHON this->add_main_option_entry(T::OPTION_TYPE_STRING, "python-script", '\0', N_("Process: Python script to execute"), N_("PYTHON-FILENAME")); /// @fixme: OPTION_TYPE_FILENAME would be more correct, but then lookup_value only returns an empty string. this->add_main_option_entry(T::OPTION_TYPE_STRING, "python-script-argument", '\0', N_("Process: Parameter for Python script"), N_("PYTHON-SCRIPT-ARGUMENT")); #endif @@ -169,7 +167,7 @@ template void ConcreteInkscapeApplication::handlePythonScript() { -#if WITH_EMBPYTHON +#if WITH_PYTHON if(!_pyScriptName.empty()) { if(!_pyScriptArg.empty()) { Inkscape::pyInstance::inkscapeScriptParam = _pyScriptArg.c_str(); @@ -225,7 +223,7 @@ ConcreteInkscapeApplication::on_startup2() // This should be completely rewritten. Inkscape::Application::create(nullptr, _with_gui); // argv appears to not be used. -#ifdef WITH_EMBPYTHON +#ifdef WITH_PYTHON // Initialize Python Inkscape::pyInstance *ipy = Inkscape::pyInstance::getInstance(Glib::get_prgname().c_str()); ipy->runInitFile(); @@ -595,7 +593,7 @@ ConcreteInkscapeApplication::on_handle_local_options(const Glib::RefPtrcontains("export-id") || options->contains("export-plain-svg") || options->contains("export-text-to_path") -#if WITH_EMBPYTHON +#if WITH_PYTHON || options->contains("python-script") || /// @todo: Scripts directly from command line only without GUI for now. (Startup and redirection initialization sequence, etc. must be sorted out.) options->contains("python-script-argument") #endif @@ -677,7 +675,7 @@ ConcreteInkscapeApplication::on_handle_local_options(const Glib::RefPtrcontains("python-script-argument")) { diff --git a/src/pyhelper.cpp b/src/pyhelper.cpp index c4ba85d33b..4793f3a183 100644 --- a/src/pyhelper.cpp +++ b/src/pyhelper.cpp @@ -20,7 +20,7 @@ * necessary.) */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #include "pyhelper.h" @@ -454,5 +454,5 @@ bool pyHelper::setActiveDekstop(int n) } // namespace Inkscape -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON diff --git a/src/pyhelper.h b/src/pyhelper.h index 6157ee819d..2b8ed5093a 100644 --- a/src/pyhelper.h +++ b/src/pyhelper.h @@ -9,7 +9,7 @@ * Released under GNU GPL. Read the file 'COPYING' for more information. */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #ifndef INKSCAPE_PYHELPER_H #define INKSCAPE_PYHELPER_H @@ -68,4 +68,4 @@ public: #endif // #ifdef INKSCAPE_PYHELPER_H -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON diff --git a/src/ui/dialog/console.cpp b/src/ui/dialog/console.cpp index d2c73cad35..87c1a966be 100644 --- a/src/ui/dialog/console.cpp +++ b/src/ui/dialog/console.cpp @@ -20,7 +20,7 @@ * ... and probably others I cannot remember. */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #ifdef HAVE_CONFIG_H #include "config.h" @@ -513,7 +513,7 @@ entryHistory::get(void) { } // namespace Inkscape -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON /* Local Variables: diff --git a/src/ui/dialog/console.h b/src/ui/dialog/console.h index 416a6c61e7..ff82415222 100644 --- a/src/ui/dialog/console.h +++ b/src/ui/dialog/console.h @@ -20,7 +20,7 @@ */ -#if WITH_EMBPYTHON +#if WITH_PYTHON #ifndef INKSCAPE_UI_DIALOG_CONSOLE_H #define INKSCAPE_UI_DIALOG_CONSOLE_H @@ -182,7 +182,7 @@ private: #endif // INKSCAPE_UI_DIALOG_COMMANDLINE_H -#endif // WITH_EMBPYTHON +#endif // WITH_PYTHON /* Local Variables: diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index fe6e652dc7..9fedd9eb25 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -108,7 +108,7 @@ DialogManager::DialogManager() { registerFactory("Prototype", &create); registerFactory("AlignAndDistribute", &create); registerFactory("DocumentMetadata", &create); -#if WITH_EMBPYTHON +#if WITH_PYTHON registerFactory("Console", &create); #endif registerFactory("DocumentProperties", &create); @@ -154,7 +154,7 @@ DialogManager::DialogManager() { registerFactory("Prototype", &create); registerFactory("AlignAndDistribute", &create); registerFactory("DocumentMetadata", &create); -#if WITH_EMBPYTHON +#if WITH_PYTHON registerFactory("Console", &create); #endif registerFactory("DocumentProperties", &create); diff --git a/src/verbs.cpp b/src/verbs.cpp index 8a7a5da5d6..afff8f9890 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -3218,7 +3218,7 @@ Verb *Verb::_base_verbs[] = { N_("Select which color separations to render in Print Colors Preview rendermode"), nullptr), new DialogVerb(SP_VERB_DIALOG_EXPORT, "DialogExport", N_("_Export PNG Image..."), N_("Export this document or a selection as a PNG image"), INKSCAPE_ICON("document-export")), -#if WITH_EMBPYTHON +#if WITH_PYTHON new DialogVerb(SP_VERB_DIALOG_CONSOLE, "DialogConsole", N_("Python console..."), N_("Python console"), INKSCAPE_ICON("dialog-console")), #endif -- GitLab From 5686486a7e4bdbed12a466b7ec1dbaa1a4324148 Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 30 Dec 2018 19:17:29 +0100 Subject: [PATCH 08/13] Use range-based for loops instead of explicit iteration. --- src/emb_python.cpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/emb_python.cpp b/src/emb_python.cpp index 515113cb65..b20f74d8ce 100644 --- a/src/emb_python.cpp +++ b/src/emb_python.cpp @@ -232,21 +232,17 @@ static PyObject *ipyGetVerbs(PyObject *self, PyObject *args) { return NULL; } - std::vector verbs; - std::vector::iterator it; + std::vector verbs = Inkscape::Verb::getList(); - verbs = Inkscape::Verb::getList(); - - - for(it = verbs.begin(); it != verbs.end(); ++it) { - if(!(*it)) { + for(const auto &it : verbs) { + if(!it) { continue; } - if(!(*it)->get_name()) { + if(!it->get_name()) { continue; } - vid = PyUnicode_FromString((*it)->get_id()); + vid = PyUnicode_FromString(it->get_id()); if(!vid) { Py_DECREF(list); list = NULL; @@ -402,14 +398,11 @@ static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args) { return NULL; } - std::map map = pyHelper::buildSaveExtensionMap(); - std::map::iterator it; + auto map = pyHelper::buildSaveExtensionMap(); - for(it = map.begin(); it != map.end(); ++it) { - Inkscape::Extension::Output *omod = it->second; + for(const auto &it : map) { + const char *mime = it.second->get_mimetype(); - mime = omod->get_mimetype(); - vid = PyUnicode_FromString(mime); if(!vid) { Py_DECREF(list); @@ -613,12 +606,8 @@ static PyObject *ipyGetDesktops(PyObject *self, PyObject *args) { return NULL; } - std::vector::iterator it; - - for(it = uris.begin(); it != uris.end(); ++it) { - Glib::ustring uri = *it; - - puri = PyUnicode_FromString(uri.c_str()); + for(const auto &it : uris) { + puri = PyUnicode_FromString(it.c_str()); if(!puri) { Py_DECREF(list); list = NULL; -- GitLab From 0843b9fc53fb390270cc462c3a85b864d344c248 Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 30 Dec 2018 19:25:06 +0100 Subject: [PATCH 09/13] Rearranged code to get rid of static C-functions in header file. --- src/emb_python.cpp | 148 ++++++++++++++++++++++----------------------- src/emb_python.h | 34 ----------- 2 files changed, 73 insertions(+), 109 deletions(-) diff --git a/src/emb_python.cpp b/src/emb_python.cpp index b20f74d8ce..c7d2e3d4b1 100644 --- a/src/emb_python.cpp +++ b/src/emb_python.cpp @@ -24,81 +24,6 @@ namespace Inkscape { // ------------ C wrappers for interfacing Python --------------------------------------- extern "C" { - -static PyMethodDef ipyMethods[] = { - {"conAppendStdOut", ipyConAppendStdOut, METH_VARARGS, conAppendStdOut__doc__}, - {"conAppendStdErr", ipyConAppendStdErr, METH_VARARGS, conAppendStdErr__doc__}, - {"conClear", ipyConClear, METH_NOARGS, conClear__doc__}, - - {"getVersion", ipyGetVersion, METH_NOARGS, getVersion__doc__}, - {"runVerbs", ipyRunVerbs, METH_VARARGS, runVerbs__doc__}, - - {"getVerbs", ipyGetVerbs, METH_NOARGS, getVerbs__doc__}, - {"getVerbName", ipyGetVerbName, METH_VARARGS, getVerbName__doc__}, - {"getVerbTooltip", ipyGetVerbTooltip, METH_VARARGS, getVerbTooltip__doc__}, - - {"getMode", ipyGetMode, METH_NOARGS, getMode__doc__}, - {"getScriptParam", ipyGetScriptParam, METH_NOARGS, getScriptParam__doc__}, - {"getPythonPath", ipyGetPythonPath, METH_NOARGS, getPythonPath__doc__}, - - {"setWaitingCursor", ipySetWaitingCursor, METH_VARARGS, setWaitingCursor__doc__}, - - {"fileSaveAs", ipyFileSaveAs, METH_VARARGS, fileSaveAs__doc__}, - {"fileSaveCopy", ipyFileSaveCopy, METH_VARARGS, fileSaveCopy__doc__}, - {"getOutputMIMEs", ipyGetOutputMIMES, METH_NOARGS, getOutputMIMEs__doc__}, - {"getOutputFileTypeName", getOutputFileTypeName, METH_VARARGS, getOutputFileTypeName__doc__}, - {"getOutputFileExtension", getOutputFileExtension, METH_VARARGS, getOutputFileExtension__doc__}, - {"getOutputFileTooltip", getOutputFileTooltip, METH_VARARGS, getOutputFileTooltip__doc__}, - - {"fileOpen", ipyFileOpen, METH_VARARGS, fileOpen__doc__}, - - {"exportPNG", ipyExportPNG, METH_VARARGS, exportPNG__doc__}, - - {"getDesktops", ipyGetDesktops, METH_NOARGS, getDesktops__doc__}, - {"setActiveDesktop", ipySetActiveDesktop, METH_VARARGS, setActiveDesktop__doc__}, - - {nullptr, nullptr, 0, nullptr} -}; - - -static struct PyModuleDef ipyModule = { - PyModuleDef_HEAD_INIT, - "_ipy", - nullptr, - -1, - ipyMethods -}; - -PyMODINIT_FUNC -PyInit_ipy(void) { - PyObject *m = nullptr; - - m = PyModule_Create(&ipyModule); - - if(m == nullptr) { - goto except; - } - -// if(PyModule_AddStringConstant(m, "version", Inkscape::version_string)) { -// goto except; -// } - - pyInstance::ipyError = PyErr_NewException("_ipy.Error", NULL, NULL); - - if(pyInstance::ipyError) { - PyModule_AddObject(m, "Error", pyInstance::ipyError); - } else { - goto except; - } - - goto finally; -except: - Py_XDECREF(m); - m = nullptr; -finally: - return m; -} - // ---------- Inkscape integrated Python console handling ---------- static PyObject *ipyConAppendStdOut(PyObject *self, PyObject *args) { @@ -644,6 +569,79 @@ static PyObject *ipySetActiveDesktop(PyObject *self, PyObject *args) { } } +static PyMethodDef ipyMethods[] = { + {"conAppendStdOut", ipyConAppendStdOut, METH_VARARGS, conAppendStdOut__doc__}, + {"conAppendStdErr", ipyConAppendStdErr, METH_VARARGS, conAppendStdErr__doc__}, + {"conClear", ipyConClear, METH_NOARGS, conClear__doc__}, + + {"getVersion", ipyGetVersion, METH_NOARGS, getVersion__doc__}, + {"runVerbs", ipyRunVerbs, METH_VARARGS, runVerbs__doc__}, + + {"getVerbs", ipyGetVerbs, METH_NOARGS, getVerbs__doc__}, + {"getVerbName", ipyGetVerbName, METH_VARARGS, getVerbName__doc__}, + {"getVerbTooltip", ipyGetVerbTooltip, METH_VARARGS, getVerbTooltip__doc__}, + + {"getMode", ipyGetMode, METH_NOARGS, getMode__doc__}, + {"getScriptParam", ipyGetScriptParam, METH_NOARGS, getScriptParam__doc__}, + {"getPythonPath", ipyGetPythonPath, METH_NOARGS, getPythonPath__doc__}, + + {"setWaitingCursor", ipySetWaitingCursor, METH_VARARGS, setWaitingCursor__doc__}, + + {"fileSaveAs", ipyFileSaveAs, METH_VARARGS, fileSaveAs__doc__}, + {"fileSaveCopy", ipyFileSaveCopy, METH_VARARGS, fileSaveCopy__doc__}, + {"getOutputMIMEs", ipyGetOutputMIMES, METH_NOARGS, getOutputMIMEs__doc__}, + {"getOutputFileTypeName", getOutputFileTypeName, METH_VARARGS, getOutputFileTypeName__doc__}, + {"getOutputFileExtension", getOutputFileExtension, METH_VARARGS, getOutputFileExtension__doc__}, + {"getOutputFileTooltip", getOutputFileTooltip, METH_VARARGS, getOutputFileTooltip__doc__}, + + {"fileOpen", ipyFileOpen, METH_VARARGS, fileOpen__doc__}, + + {"exportPNG", ipyExportPNG, METH_VARARGS, exportPNG__doc__}, + + {"getDesktops", ipyGetDesktops, METH_NOARGS, getDesktops__doc__}, + {"setActiveDesktop", ipySetActiveDesktop, METH_VARARGS, setActiveDesktop__doc__}, + + {nullptr, nullptr, 0, nullptr} +}; + + +static struct PyModuleDef ipyModule = { + PyModuleDef_HEAD_INIT, + "_ipy", + nullptr, + -1, + ipyMethods +}; + +PyMODINIT_FUNC +PyInit_ipy(void) { + PyObject *m = nullptr; + + m = PyModule_Create(&ipyModule); + + if(m == nullptr) { + goto except; + } + +// if(PyModule_AddStringConstant(m, "version", Inkscape::version_string)) { +// goto except; +// } + + pyInstance::ipyError = PyErr_NewException("_ipy.Error", NULL, NULL); + + if(pyInstance::ipyError) { + PyModule_AddObject(m, "Error", pyInstance::ipyError); + } else { + goto except; + } + + goto finally; +except: + Py_XDECREF(m); + m = nullptr; +finally: + return m; +} } // extern "C" diff --git a/src/emb_python.h b/src/emb_python.h index 1d2a58adb0..4a753e1a2d 100644 --- a/src/emb_python.h +++ b/src/emb_python.h @@ -39,40 +39,6 @@ namespace Inkscape { extern "C" { - - -static PyObject *ipyConAppendStdOut(PyObject *self, PyObject *args); -static PyObject *ipyConAppendStdErr(PyObject *self, PyObject *args); -static PyObject *ipyConClear(PyObject *self, PyObject *args); - -static PyObject *ipyGetVersion(PyObject *self, PyObject *args); -static PyObject *ipyRunVerbs(PyObject *self, PyObject *args); - -static PyObject *ipyGetMode(PyObject *self, PyObject *args); -static PyObject *ipyGetScriptParam(PyObject *self, PyObject *args); -static PyObject *ipyGetPythonPath(PyObject *self, PyObject *args); - -static PyObject *ipyGetVerbs(PyObject *self, PyObject *args); -static PyObject *ipyGetVerbName(PyObject *self, PyObject *args); -static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args); - -static PyObject *ipySetWaitingCursor(PyObject *self, PyObject *args); - -static PyObject *ipyFileSaveAs(PyObject *self, PyObject *args); -static PyObject *ipyFileSaveCopy(PyObject *self, PyObject *args); -static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args); -static PyObject *getOutputFileTypeName(PyObject *self, PyObject *args); -static PyObject *getOutputFileExtension(PyObject *self, PyObject *args); -static PyObject *getOutputFileTooltip(PyObject *self, PyObject *args); - -static PyObject *ipyFileOpen(PyObject *self, PyObject *args); - -static PyObject *ipyExportPNG(PyObject *self, PyObject *args); - -static PyObject *ipyGetDesktops(PyObject *self, PyObject *args); -static PyObject *ipySetActiveDesktop(PyObject *self, PyObject *args); - - PyMODINIT_FUNC PyInit_ipy(void); } // extern "C" -- GitLab From 377db4b6438c34357f74fb3a086ed46f29a97490 Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 30 Dec 2018 20:09:39 +0100 Subject: [PATCH 10/13] Renamed _ipy and ipy to _inkscape and inkscape. --- share/python/CMakeLists.txt | 2 +- share/python/README | 2 + share/python/init.py | 16 +- share/python/{ipy => inkscape}/CMakeLists.txt | 2 +- share/python/inkscape/inkscape.py | 4 + share/python/ipy/ipy.py | 4 - share/python/tests/CMakeLists.txt | 4 +- share/python/tests/README | 3 + share/python/tests/exportPNGCMDline.py | 11 +- share/python/tests/inkscape_mode.py | 4 +- share/python/tests/misc.py | 4 +- share/python/tests/openSaveExport.py | 8 +- share/python/tests/outputMIMEs.py | 8 +- share/python/tests/verbs.py | 8 +- src/emb_python.cpp | 154 +++++++++--------- src/emb_python.h | 4 +- 16 files changed, 122 insertions(+), 116 deletions(-) rename share/python/{ipy => inkscape}/CMakeLists.txt (85%) create mode 100644 share/python/inkscape/inkscape.py delete mode 100644 share/python/ipy/ipy.py create mode 100644 share/python/tests/README diff --git a/share/python/CMakeLists.txt b/share/python/CMakeLists.txt index e879eb667b..d2ae8d566c 100644 --- a/share/python/CMakeLists.txt +++ b/share/python/CMakeLists.txt @@ -1,6 +1,6 @@ file(GLOB _FILES "README" "*.py") install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python) -add_subdirectory(ipy) +add_subdirectory(inkscape) add_subdirectory(tests) diff --git a/share/python/README b/share/python/README index a6b1b381c3..5e4d434750 100644 --- a/share/python/README +++ b/share/python/README @@ -1,2 +1,4 @@ This is the directory for global embedded Python files. +Only Python 3 is supported for the Python code executed from within Inkscape. + diff --git a/share/python/init.py b/share/python/init.py index 55725962b7..6e77ed7d01 100644 --- a/share/python/init.py +++ b/share/python/init.py @@ -10,30 +10,30 @@ import sys import os -import _ipy +import _inkscape # Functions for stdout and stderr redirection. class StdOutRedir: def write(self, line): - _ipy.conAppendStdOut(line) + _inkscape.conAppendStdOut(line) class StdErrRedir: def write(self, line): - _ipy.conAppendStdErr(line) + _inkscape.conAppendStdErr(line) -print('init.py: Script running in ' + _ipy.getMode() + ' mode') +print('init.py: Script running in ' + _inkscape.getMode() + ' mode') # If Inkscape was started in GUI mode, we want to setup the redirection # for stdout and stderr. We do this very early on, to make possible # later errors show up in the GUI console. -if _ipy.getMode() == 'gui': +if _inkscape.getMode() == 'gui': print('init.py: Setting up stdout and stderr redirection.') sys.stderr = StdErrRedir() sys.stdout = StdOutRedir() # Setup path to additional Python ressources. -ipyPath = os.path.join(_ipy.getPythonPath(), 'ipy') -sys.path.append(ipyPath) -import ipy +inkscapePath = os.path.join(_inkscape.getPythonPath(), 'inkscape') +sys.path.append(inkscapePath) +import inkscape diff --git a/share/python/ipy/CMakeLists.txt b/share/python/inkscape/CMakeLists.txt similarity index 85% rename from share/python/ipy/CMakeLists.txt rename to share/python/inkscape/CMakeLists.txt index 3228de4cb0..7c1b3cfe41 100644 --- a/share/python/ipy/CMakeLists.txt +++ b/share/python/inkscape/CMakeLists.txt @@ -1,3 +1,3 @@ file(GLOB _FILES "*.py") -install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python/ipy) +install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python/inkscape) diff --git a/share/python/inkscape/inkscape.py b/share/python/inkscape/inkscape.py new file mode 100644 index 0000000000..808f107f76 --- /dev/null +++ b/share/python/inkscape/inkscape.py @@ -0,0 +1,4 @@ +# Currently only one simple test function. +def inkscapetest(): + print('Hello from inkscapetest') + diff --git a/share/python/ipy/ipy.py b/share/python/ipy/ipy.py deleted file mode 100644 index f878f5e1b0..0000000000 --- a/share/python/ipy/ipy.py +++ /dev/null @@ -1,4 +0,0 @@ -# Currently only one simple test function. -def ipytest(): - print('Hello from ipytest') - diff --git a/share/python/tests/CMakeLists.txt b/share/python/tests/CMakeLists.txt index 64e6f7648c..fb1cc84c50 100644 --- a/share/python/tests/CMakeLists.txt +++ b/share/python/tests/CMakeLists.txt @@ -1,3 +1,3 @@ -file(GLOB _FILES "*.py" "*.svg") -install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python/ipy) +file(GLOB _FILES "README" "*.py" "*.svg") +install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/python/tests) diff --git a/share/python/tests/README b/share/python/tests/README new file mode 100644 index 0000000000..aba991bd54 --- /dev/null +++ b/share/python/tests/README @@ -0,0 +1,3 @@ +This directory contains a collection of Python scripts for testing +the functionality and serving as a starting point for custom scripts. + diff --git a/share/python/tests/exportPNGCMDline.py b/share/python/tests/exportPNGCMDline.py index 1e1c528e7f..ef2b324689 100644 --- a/share/python/tests/exportPNGCMDline.py +++ b/share/python/tests/exportPNGCMDline.py @@ -1,17 +1,17 @@ -# Test file demonstating opening and PNG exporting from the command line +# Test file demonstrating opening and PNG exporting from the command line import os import tempfile -if _ipy.getMode() == 'gui': +if _inkscape.getMode() == 'gui': print("This test must be run without GUI."); else: - fn = _ipy.getScriptParam() + fn = _inkscape.getScriptParam() if len(fn) == 0: print("No filename supplied via --python-script-argument") else: # Open the file - _ipy.fileOpen(fn) + _inkscape.fileOpen(fn) tempdir = tempfile.gettempdir() @@ -22,6 +22,7 @@ else: area = 'page' areaSnap = 1 - _ipy.exportPNG(outPNG, dpi, area, areaSnap) + _inkscape.exportPNG(outPNG, dpi, area, areaSnap) print("Exported file saved to " + outPNG) + diff --git a/share/python/tests/inkscape_mode.py b/share/python/tests/inkscape_mode.py index 3421e13c45..00f26b8ed6 100644 --- a/share/python/tests/inkscape_mode.py +++ b/share/python/tests/inkscape_mode.py @@ -3,14 +3,14 @@ # Test command line mode with # inkscape --without-gui --python-script=inkscape_mode.py --python-script-argument="Test argument" -mode = _ipy.getMode() +mode = _inkscape.getMode() if mode == 'gui': print("Inkscape is running in GUI mode") elif mode == 'commandline': print("Inkscape is running in commandline mode") - param = _ipy.getScriptParam() + param = _inkscape.getScriptParam() print('The script parameter is "' + param + '"') else: print('ERROR: Mode should either be "gui" or "commandline" but is "' + mode + '"') diff --git a/share/python/tests/misc.py b/share/python/tests/misc.py index 4bad4da9a2..ebcb48f9d5 100644 --- a/share/python/tests/misc.py +++ b/share/python/tests/misc.py @@ -1,5 +1,5 @@ # Misc tests -print('Inkscape version: ' + _ipy.getVersion()) -print('Python ressource directory: ' + _ipy.getPythonPath()) +print('Inkscape version: ' + _inkscape.getVersion()) +print('Python ressource directory: ' + _inkscape.getPythonPath()) diff --git a/share/python/tests/openSaveExport.py b/share/python/tests/openSaveExport.py index ee072e4d77..fe031a4cfa 100644 --- a/share/python/tests/openSaveExport.py +++ b/share/python/tests/openSaveExport.py @@ -4,12 +4,12 @@ import os import tempfile # Get the path to the splash screen -parent = os.path.split(_ipy.getPythonPath()) +parent = os.path.split(_inkscape.getPythonPath()) parent = parent[0] fnam = os.path.join(parent, 'screens/about.svg') # Open the file -_ipy.fileOpen(fnam) +_inkscape.fileOpen(fnam) tempdir = tempfile.gettempdir() @@ -17,14 +17,14 @@ outPDF = os.path.join(tempdir, 'about.pdf') outPNG = os.path.join(tempdir, 'about.png') # Try to save as PDF. This may pop up user interaction dialogs. -_ipy.fileSaveCopy(outPDF, 'application/pdf') +_inkscape.fileSaveCopy(outPDF, 'application/pdf') # Export as PNG. dpi = 150 area = 'page' areaSnap = 1 -_ipy.exportPNG(outPNG, dpi, area, areaSnap) +_inkscape.exportPNG(outPNG, dpi, area, areaSnap) print('Wrote about.pdf and about.png to ' + tempdir) diff --git a/share/python/tests/outputMIMEs.py b/share/python/tests/outputMIMEs.py index 6add1356d4..8775c2c746 100644 --- a/share/python/tests/outputMIMEs.py +++ b/share/python/tests/outputMIMEs.py @@ -1,11 +1,11 @@ # Dump the available output MIME types -mimes = _ipy.getOutputMIMEs() +mimes = _inkscape.getOutputMIMEs() for i in mimes: - name = _ipy.getOutputFileTypeName(i) - tooltip = _ipy.getOutputFileTooltip(i) - extension = _ipy.getOutputFileExtension(i) + name = _inkscape.getOutputFileTypeName(i) + tooltip = _inkscape.getOutputFileTooltip(i) + extension = _inkscape.getOutputFileExtension(i) print('MIME: ' + i) print(' Name: ' + name) diff --git a/share/python/tests/verbs.py b/share/python/tests/verbs.py index 40dc146f5b..c50da46f1c 100644 --- a/share/python/tests/verbs.py +++ b/share/python/tests/verbs.py @@ -1,18 +1,18 @@ # Tests for obtaining and running Inkscape verbs def dumpVerbs(): - verbs = _ipy.getVerbs() + verbs = _inkscape.getVerbs() for i in verbs: - name = _ipy.getVerbName(i) - tooltip = _ipy.getVerbTooltip(i) + name = _inkscape.getVerbName(i) + tooltip = _inkscape.getVerbTooltip(i) print('Verb: ' + i) print(' Name: ' + name) print(' Tooltip: ' + tooltip) def runVerbTest(): - _ipy.runVerbs(['ToggleGrid']) + _inkscape.runVerbs(['ToggleGrid']) dumpVerbs() diff --git a/src/emb_python.cpp b/src/emb_python.cpp index c7d2e3d4b1..952b8505d2 100644 --- a/src/emb_python.cpp +++ b/src/emb_python.cpp @@ -26,7 +26,7 @@ namespace Inkscape { extern "C" { // ---------- Inkscape integrated Python console handling ---------- -static PyObject *ipyConAppendStdOut(PyObject *self, PyObject *args) { +static PyObject *inkscapeConAppendStdOut(PyObject *self, PyObject *args) { const char *str; if(!PyArg_ParseTuple(args, "s", &str)) { @@ -40,7 +40,7 @@ static PyObject *ipyConAppendStdOut(PyObject *self, PyObject *args) { return Py_BuildValue(""); } -static PyObject *ipyConAppendStdErr(PyObject *self, PyObject *args) { +static PyObject *inkscapeConAppendStdErr(PyObject *self, PyObject *args) { const char *str; if(!PyArg_ParseTuple(args, "s", &str)) { @@ -54,7 +54,7 @@ static PyObject *ipyConAppendStdErr(PyObject *self, PyObject *args) { return Py_BuildValue(""); } -static PyObject *ipyConClear(PyObject *self, PyObject *args) { +static PyObject *inkscapeConClear(PyObject *self, PyObject *args) { // Handle listeners pyInstance *pi = pyInstance::getInstance(""); pi->emitClear(); @@ -64,11 +64,11 @@ static PyObject *ipyConClear(PyObject *self, PyObject *args) { // ---------- Misc utility functions ---------- -static PyObject *ipyGetVersion(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetVersion(PyObject *self, PyObject *args) { return PyUnicode_FromString(Inkscape::version_string); } -static PyObject *ipyGetMode(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetMode(PyObject *self, PyObject *args) { if(INKSCAPE.use_gui()) { return PyUnicode_FromString(pyInstance::MODE_GUI); } else { @@ -76,20 +76,20 @@ static PyObject *ipyGetMode(PyObject *self, PyObject *args) { } } -static PyObject *ipyGetScriptParam(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetScriptParam(PyObject *self, PyObject *args) { if(pyInstance::inkscapeScriptParam) { return PyUnicode_FromString(pyInstance::inkscapeScriptParam); } return PyUnicode_FromString(""); } -static PyObject *ipyGetPythonPath(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetPythonPath(PyObject *self, PyObject *args) { std::string path = Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::PYTHON, ""); return PyUnicode_FromString(path.c_str()); } -static PyObject *ipySetWaitingCursor(PyObject *self, PyObject *args) { +static PyObject *inkscapeSetWaitingCursor(PyObject *self, PyObject *args) { int wait; if(!PyArg_ParseTuple(args, "i", &wait)) { @@ -103,7 +103,7 @@ static PyObject *ipySetWaitingCursor(PyObject *self, PyObject *args) { // ---------- Getting (information on) verbs and running verbs ---------- -static PyObject *ipyRunVerbs(PyObject *self, PyObject *args) { +static PyObject *inkscapeRunVerbs(PyObject *self, PyObject *args) { // Used Python's builtin_sum() from bltinmodule.c as a template. PyObject *seq, *iter, *item; @@ -131,7 +131,7 @@ static PyObject *ipyRunVerbs(PyObject *self, PyObject *args) { } if(!pyHelper::runVerb(str)) { - PyErr_Format(pyInstance::ipyError, _("Unable to execute verb %s."), str); + PyErr_Format(pyInstance::inkscapeError, _("Unable to execute verb %s."), str); Py_DECREF(item); Py_DECREF(iter); return NULL; @@ -146,7 +146,7 @@ static PyObject *ipyRunVerbs(PyObject *self, PyObject *args) { return Py_BuildValue(""); } -static PyObject *ipyGetVerbs(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetVerbs(PyObject *self, PyObject *args) { PyObject *list; PyObject *vid; @@ -171,7 +171,7 @@ static PyObject *ipyGetVerbs(PyObject *self, PyObject *args) { if(!vid) { Py_DECREF(list); list = NULL; - PyErr_SetString(pyInstance::ipyError, _("ipyGetVerbs: Could not create Python string of verb id.")); + PyErr_SetString(pyInstance::inkscapeError, _("inkscapeGetVerbs: Could not create Python string of verb id.")); break; } @@ -180,7 +180,7 @@ static PyObject *ipyGetVerbs(PyObject *self, PyObject *args) { list = NULL; Py_DECREF(vid); - PyErr_SetString(pyInstance::ipyError, _("ipyGetVerbs: Could not append verb id to list.")); + PyErr_SetString(pyInstance::inkscapeError, _("inkscapeGetVerbs: Could not append verb id to list.")); break; } Py_DECREF(vid); /// @fixme Is this necessary? Can't find it in the docs. @@ -189,7 +189,7 @@ static PyObject *ipyGetVerbs(PyObject *self, PyObject *args) { return list; } -static PyObject *ipyGetVerbName(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetVerbName(PyObject *self, PyObject *args) { PyObject *name; const char *str; Inkscape::Verb *verb; @@ -202,7 +202,7 @@ static PyObject *ipyGetVerbName(PyObject *self, PyObject *args) { verb = Inkscape::Verb::getbyid(str); if(!verb) { - PyErr_Format(pyInstance::ipyError, _("Unknown verb %s."), str); + PyErr_Format(pyInstance::inkscapeError, _("Unknown verb %s."), str); return NULL; } @@ -215,14 +215,14 @@ static PyObject *ipyGetVerbName(PyObject *self, PyObject *args) { } if(!name) { - PyErr_Format(pyInstance::ipyError, _("Unable to create name string for verb %s."), str); + PyErr_Format(pyInstance::inkscapeError, _("Unable to create name string for verb %s."), str); return NULL; } return name; } -static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetVerbTooltip(PyObject *self, PyObject *args) { PyObject *tooltip; const char *str; Inkscape::Verb *verb; @@ -235,7 +235,7 @@ static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args) { verb = Inkscape::Verb::getbyid(str); if(!verb) { - PyErr_Format(pyInstance::ipyError, _("Unknown verb %s."), str); + PyErr_Format(pyInstance::inkscapeError, _("Unknown verb %s."), str); return NULL; } @@ -248,7 +248,7 @@ static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args) { } if(!tooltip) { - PyErr_Format(pyInstance::ipyError, _("Unable to create tooltip string for verb %s."), str); + PyErr_Format(pyInstance::inkscapeError, _("Unable to create tooltip string for verb %s."), str); return NULL; } @@ -264,7 +264,7 @@ static PyObject *ipyGetVerbTooltip(PyObject *self, PyObject *args) { * Note: have a look at the do_export_* functions in main.cpp on how to implement this functionality. * */ -static PyObject *ipyFileSaveAs(PyObject *self, PyObject *args) { +static PyObject *inkscapeFileSaveAs(PyObject *self, PyObject *args) { const char *filename; const char *mime; const char *errmsg = nullptr; @@ -279,7 +279,7 @@ static PyObject *ipyFileSaveAs(PyObject *self, PyObject *args) { if(success) { return Py_BuildValue(""); } else { - PyErr_Format(pyInstance::ipyError, errmsg); + PyErr_Format(pyInstance::inkscapeError, errmsg); return NULL; } } @@ -291,7 +291,7 @@ static PyObject *ipyFileSaveAs(PyObject *self, PyObject *args) { * Note: have a look at the do_export_* functions in main.cpp on how to implement this functionality. * */ -static PyObject *ipyFileSaveCopy(PyObject *self, PyObject *args) { +static PyObject *inkscapeFileSaveCopy(PyObject *self, PyObject *args) { const char *filename; const char *mime; const char *errmsg = nullptr; @@ -306,12 +306,12 @@ static PyObject *ipyFileSaveCopy(PyObject *self, PyObject *args) { if(success) { return Py_BuildValue(""); } else { - PyErr_Format(pyInstance::ipyError, errmsg); + PyErr_Format(pyInstance::inkscapeError, errmsg); return NULL; } } -static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetOutputMIMES(PyObject *self, PyObject *args) { PyObject *list; PyObject *vid; const char *mime; @@ -332,7 +332,7 @@ static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args) { if(!vid) { Py_DECREF(list); list = NULL; - PyErr_SetString(pyInstance::ipyError, _("ipyGetOutputMIMES: Could not create Python string of MIME type.")); + PyErr_SetString(pyInstance::inkscapeError, _("inkscapeGetOutputMIMES: Could not create Python string of MIME type.")); break; } @@ -341,7 +341,7 @@ static PyObject *ipyGetOutputMIMES(PyObject *self, PyObject *args) { list = NULL; Py_DECREF(vid); - PyErr_SetString(pyInstance::ipyError, _("ipyGetOutputMIMES: Could not append MIME type to list.")); + PyErr_SetString(pyInstance::inkscapeError, _("inkscapeGetOutputMIMES: Could not append MIME type to list.")); break; } Py_DECREF(vid); /// @fixme Is this necessary? Can't find it in the docs. @@ -362,14 +362,14 @@ static PyObject *getOutputFileTypeName(PyObject *self, PyObject *args) { filetypename = pyHelper::getOutputMIMEFileTypeName(mime); if(filetypename == nullptr) { - PyErr_Format(pyInstance::ipyError, _("Unknown output MIME type %s."), mime); + PyErr_Format(pyInstance::inkscapeError, _("Unknown output MIME type %s."), mime); return NULL; } nameobj = PyUnicode_FromString(filetypename); if(!nameobj) { - PyErr_Format(pyInstance::ipyError, _("Unable to create name string MIME type %s."), mime); + PyErr_Format(pyInstance::inkscapeError, _("Unable to create name string MIME type %s."), mime); return NULL; } @@ -388,14 +388,14 @@ static PyObject *getOutputFileExtension(PyObject *self, PyObject *args) { extension = pyHelper::getOutputMIMEExtension(mime); if(extension == nullptr) { - PyErr_Format(pyInstance::ipyError, _("Unknown output MIME type %s."), mime); + PyErr_Format(pyInstance::inkscapeError, _("Unknown output MIME type %s."), mime); return NULL; } extobj = PyUnicode_FromString(extension); if(!extobj) { - PyErr_Format(pyInstance::ipyError, _("Unable to create name string MIME type %s."), mime); + PyErr_Format(pyInstance::inkscapeError, _("Unable to create name string MIME type %s."), mime); return NULL; } @@ -414,21 +414,21 @@ static PyObject *getOutputFileTooltip(PyObject *self, PyObject *args) { tooltip = pyHelper::getOutputMIMETooltip(mime); if(tooltip == nullptr) { - PyErr_Format(pyInstance::ipyError, _("Unknown output MIME type %s."), mime); + PyErr_Format(pyInstance::inkscapeError, _("Unknown output MIME type %s."), mime); return NULL; } tooltipobj = PyUnicode_FromString(tooltip); if(!tooltipobj) { - PyErr_Format(pyInstance::ipyError, _("Unable to create name string MIME type %s."), mime); + PyErr_Format(pyInstance::inkscapeError, _("Unable to create name string MIME type %s."), mime); return NULL; } return tooltipobj; } -static PyObject *ipyFileOpen(PyObject *self, PyObject *args) { +static PyObject *inkscapeFileOpen(PyObject *self, PyObject *args) { const char *filename; const char *errmsg = nullptr; bool success; @@ -443,7 +443,7 @@ static PyObject *ipyFileOpen(PyObject *self, PyObject *args) { if(success) { return Py_BuildValue(""); } else { - PyErr_Format(pyInstance::ipyError, errmsg); + PyErr_Format(pyInstance::inkscapeError, errmsg); return NULL; } @@ -452,7 +452,7 @@ static PyObject *ipyFileOpen(PyObject *self, PyObject *args) { // ---------- File exporting ---------- -static PyObject *ipyExportPNG(PyObject *self, PyObject *args) { +static PyObject *inkscapeExportPNG(PyObject *self, PyObject *args) { const char *filename; double dpi = 72.0; int areaSnap = true; @@ -471,19 +471,19 @@ static PyObject *ipyExportPNG(PyObject *self, PyObject *args) { // Check if the colors are within range. if(rr < pyHelper::RGB_MIN || rr > pyHelper::RGB_MAX) { - PyErr_Format(pyInstance::ipyError, "Red color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + PyErr_Format(pyInstance::inkscapeError, "Red color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); return NULL; } if(gg < pyHelper::RGB_MIN || gg > pyHelper::RGB_MAX) { - PyErr_Format(pyInstance::ipyError, "Green color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + PyErr_Format(pyInstance::inkscapeError, "Green color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); return NULL; } if(bb < pyHelper::RGB_MIN || bb > pyHelper::RGB_MAX) { - PyErr_Format(pyInstance::ipyError, "Blue color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + PyErr_Format(pyInstance::inkscapeError, "Blue color value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); return NULL; } if(oo < pyHelper::RGB_MIN || oo > pyHelper::RGB_MAX) { - PyErr_Format(pyInstance::ipyError, "Opacity value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); + PyErr_Format(pyInstance::inkscapeError, "Opacity value out of range (%d, %d)", pyHelper::RGB_MIN, pyHelper::RGB_MAX); return NULL; } @@ -498,12 +498,12 @@ static PyObject *ipyExportPNG(PyObject *self, PyObject *args) { } else if(strcmp(areaStr, "drawing") == 0) { earea = pyHelper::DRAWING; } else { - PyErr_Format(pyInstance::ipyError, _("Invalid export area. Valid values are 'page' and 'drawing'.")); + PyErr_Format(pyInstance::inkscapeError, _("Invalid export area. Valid values are 'page' and 'drawing'.")); return NULL; } if(dpi < pyHelper::PNG_EXPORT_DPI_MIN || dpi > pyHelper::PNG_EXPORT_DPI_MAX) { - PyErr_Format(pyInstance::ipyError, _("Resolution (dpi) out of range.")); /// @fixme Output what the range is, but PyErr_Format has no format string for floating point numbers? + PyErr_Format(pyInstance::inkscapeError, _("Resolution (dpi) out of range.")); /// @fixme Output what the range is, but PyErr_Format has no format string for floating point numbers? } success = pyHelper::exportPNG(filename, earea, dpi, areaSnap, useDocumentBGColor, bgColor, errmsg); @@ -511,7 +511,7 @@ static PyObject *ipyExportPNG(PyObject *self, PyObject *args) { if(success) { return Py_BuildValue(""); } else { - PyErr_Format(pyInstance::ipyError, errmsg); + PyErr_Format(pyInstance::inkscapeError, errmsg); return NULL; } @@ -519,7 +519,7 @@ static PyObject *ipyExportPNG(PyObject *self, PyObject *args) { } // ---------- Desktop handling ---------- -static PyObject *ipyGetDesktops(PyObject *self, PyObject *args) { +static PyObject *inkscapeGetDesktops(PyObject *self, PyObject *args) { PyObject *list; PyObject *puri; std::vector uris = pyHelper::getDesktops(); @@ -536,7 +536,7 @@ static PyObject *ipyGetDesktops(PyObject *self, PyObject *args) { if(!puri) { Py_DECREF(list); list = NULL; - PyErr_SetString(pyInstance::ipyError, _("ipyGetDesktops: Could not create Python string of desktop URI.")); + PyErr_SetString(pyInstance::inkscapeError, _("inkscapeGetDesktops: Could not create Python string of desktop URI.")); break; } @@ -545,7 +545,7 @@ static PyObject *ipyGetDesktops(PyObject *self, PyObject *args) { list = NULL; Py_DECREF(puri); - PyErr_SetString(pyInstance::ipyError, _("ipyGetOutputMIMES: Could not append URI desktop list.")); + PyErr_SetString(pyInstance::inkscapeError, _("inkscapeGetOutputMIMES: Could not append URI desktop list.")); break; } Py_DECREF(puri); /// @fixme Is this necessary? Can't find it in the docs. @@ -554,7 +554,7 @@ static PyObject *ipyGetDesktops(PyObject *self, PyObject *args) { return list; } -static PyObject *ipySetActiveDesktop(PyObject *self, PyObject *args) { +static PyObject *inkscapeSetActiveDesktop(PyObject *self, PyObject *args) { int desktopNr; if(!PyArg_ParseTuple(args, "i", &desktopNr)) { @@ -564,60 +564,60 @@ static PyObject *ipySetActiveDesktop(PyObject *self, PyObject *args) { if(pyHelper::setActiveDekstop(desktopNr)) { return Py_BuildValue(""); } else { - PyErr_SetString(pyInstance::ipyError, _("setActiveDesktop: Desktop index out of range.")); + PyErr_SetString(pyInstance::inkscapeError, _("setActiveDesktop: Desktop index out of range.")); return NULL; } } -static PyMethodDef ipyMethods[] = { - {"conAppendStdOut", ipyConAppendStdOut, METH_VARARGS, conAppendStdOut__doc__}, - {"conAppendStdErr", ipyConAppendStdErr, METH_VARARGS, conAppendStdErr__doc__}, - {"conClear", ipyConClear, METH_NOARGS, conClear__doc__}, +static PyMethodDef inkscapeMethods[] = { + {"conAppendStdOut", inkscapeConAppendStdOut, METH_VARARGS, conAppendStdOut__doc__}, + {"conAppendStdErr", inkscapeConAppendStdErr, METH_VARARGS, conAppendStdErr__doc__}, + {"conClear", inkscapeConClear, METH_NOARGS, conClear__doc__}, - {"getVersion", ipyGetVersion, METH_NOARGS, getVersion__doc__}, - {"runVerbs", ipyRunVerbs, METH_VARARGS, runVerbs__doc__}, + {"getVersion", inkscapeGetVersion, METH_NOARGS, getVersion__doc__}, + {"runVerbs", inkscapeRunVerbs, METH_VARARGS, runVerbs__doc__}, - {"getVerbs", ipyGetVerbs, METH_NOARGS, getVerbs__doc__}, - {"getVerbName", ipyGetVerbName, METH_VARARGS, getVerbName__doc__}, - {"getVerbTooltip", ipyGetVerbTooltip, METH_VARARGS, getVerbTooltip__doc__}, + {"getVerbs", inkscapeGetVerbs, METH_NOARGS, getVerbs__doc__}, + {"getVerbName", inkscapeGetVerbName, METH_VARARGS, getVerbName__doc__}, + {"getVerbTooltip", inkscapeGetVerbTooltip, METH_VARARGS, getVerbTooltip__doc__}, - {"getMode", ipyGetMode, METH_NOARGS, getMode__doc__}, - {"getScriptParam", ipyGetScriptParam, METH_NOARGS, getScriptParam__doc__}, - {"getPythonPath", ipyGetPythonPath, METH_NOARGS, getPythonPath__doc__}, + {"getMode", inkscapeGetMode, METH_NOARGS, getMode__doc__}, + {"getScriptParam", inkscapeGetScriptParam, METH_NOARGS, getScriptParam__doc__}, + {"getPythonPath", inkscapeGetPythonPath, METH_NOARGS, getPythonPath__doc__}, - {"setWaitingCursor", ipySetWaitingCursor, METH_VARARGS, setWaitingCursor__doc__}, + {"setWaitingCursor", inkscapeSetWaitingCursor, METH_VARARGS, setWaitingCursor__doc__}, - {"fileSaveAs", ipyFileSaveAs, METH_VARARGS, fileSaveAs__doc__}, - {"fileSaveCopy", ipyFileSaveCopy, METH_VARARGS, fileSaveCopy__doc__}, - {"getOutputMIMEs", ipyGetOutputMIMES, METH_NOARGS, getOutputMIMEs__doc__}, + {"fileSaveAs", inkscapeFileSaveAs, METH_VARARGS, fileSaveAs__doc__}, + {"fileSaveCopy", inkscapeFileSaveCopy, METH_VARARGS, fileSaveCopy__doc__}, + {"getOutputMIMEs", inkscapeGetOutputMIMES, METH_NOARGS, getOutputMIMEs__doc__}, {"getOutputFileTypeName", getOutputFileTypeName, METH_VARARGS, getOutputFileTypeName__doc__}, {"getOutputFileExtension", getOutputFileExtension, METH_VARARGS, getOutputFileExtension__doc__}, {"getOutputFileTooltip", getOutputFileTooltip, METH_VARARGS, getOutputFileTooltip__doc__}, - {"fileOpen", ipyFileOpen, METH_VARARGS, fileOpen__doc__}, + {"fileOpen", inkscapeFileOpen, METH_VARARGS, fileOpen__doc__}, - {"exportPNG", ipyExportPNG, METH_VARARGS, exportPNG__doc__}, + {"exportPNG", inkscapeExportPNG, METH_VARARGS, exportPNG__doc__}, - {"getDesktops", ipyGetDesktops, METH_NOARGS, getDesktops__doc__}, - {"setActiveDesktop", ipySetActiveDesktop, METH_VARARGS, setActiveDesktop__doc__}, + {"getDesktops", inkscapeGetDesktops, METH_NOARGS, getDesktops__doc__}, + {"setActiveDesktop", inkscapeSetActiveDesktop, METH_VARARGS, setActiveDesktop__doc__}, {nullptr, nullptr, 0, nullptr} }; -static struct PyModuleDef ipyModule = { +static struct PyModuleDef inkscapeModule = { PyModuleDef_HEAD_INIT, - "_ipy", + "_inkscape", nullptr, -1, - ipyMethods + inkscapeMethods }; PyMODINIT_FUNC -PyInit_ipy(void) { +PyInit_inkscape(void) { PyObject *m = nullptr; - m = PyModule_Create(&ipyModule); + m = PyModule_Create(&inkscapeModule); if(m == nullptr) { goto except; @@ -627,10 +627,10 @@ PyInit_ipy(void) { // goto except; // } - pyInstance::ipyError = PyErr_NewException("_ipy.Error", NULL, NULL); + pyInstance::inkscapeError = PyErr_NewException("_inkscape.Error", NULL, NULL); - if(pyInstance::ipyError) { - PyModule_AddObject(m, "Error", pyInstance::ipyError); + if(pyInstance::inkscapeError) { + PyModule_AddObject(m, "Error", pyInstance::inkscapeError); } else { goto except; } @@ -652,7 +652,7 @@ const char * const pyInstance::MODE_GUI = "gui"; const char * const pyInstance::INIT_FUNCTION = "inkscapeInit"; const char * pyInstance::inkscapeScriptParam = nullptr; -PyObject* pyInstance::ipyError; +PyObject* pyInstance::inkscapeError; pyInstance* pyInstance::getInstance(char const *progname) { if(!instance) { @@ -671,7 +671,7 @@ pyInstance::pyInstance(char const *progname) Py_SetProgramName(wprogname); } - PyImport_AppendInittab("_ipy", PyInit_ipy); + PyImport_AppendInittab("_inkscape", PyInit_inkscape); Py_Initialize(); } diff --git a/src/emb_python.h b/src/emb_python.h index 4a753e1a2d..94e80ab633 100644 --- a/src/emb_python.h +++ b/src/emb_python.h @@ -39,7 +39,7 @@ namespace Inkscape { extern "C" { -PyMODINIT_FUNC PyInit_ipy(void); +PyMODINIT_FUNC PyInit_inkscape(void); } // extern "C" @@ -58,7 +58,7 @@ public: static const char * const MODE_GUI; static const char * const INIT_FUNCTION; - static PyObject* ipyError; + static PyObject* inkscapeError; // Signals typedef sigc::signal type_sig_stdout; -- GitLab From a14e7f13053c4480011ff9c5c919d363b780ac91 Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Sun, 30 Dec 2018 20:17:35 +0100 Subject: [PATCH 11/13] Added some more comments to the test/example scripts. --- share/python/tests/README | 2 ++ share/python/tests/exportPNGCMDline.py | 8 ++++++-- share/python/tests/openSaveExport.py | 3 ++- share/python/tests/verbs.py | 3 +++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/share/python/tests/README b/share/python/tests/README index aba991bd54..1660459ac8 100644 --- a/share/python/tests/README +++ b/share/python/tests/README @@ -1,3 +1,5 @@ This directory contains a collection of Python scripts for testing the functionality and serving as a starting point for custom scripts. +Note that Python 3 syntax is required for scripts running within Inkscape. + diff --git a/share/python/tests/exportPNGCMDline.py b/share/python/tests/exportPNGCMDline.py index ef2b324689..d42c665af5 100644 --- a/share/python/tests/exportPNGCMDline.py +++ b/share/python/tests/exportPNGCMDline.py @@ -4,8 +4,11 @@ import os import tempfile if _inkscape.getMode() == 'gui': + # Since this script is for testing the export from the command line, + # it should be called accordingly. print("This test must be run without GUI."); else: + # Script parameter must be the filename to export. Get it and check it. fn = _inkscape.getScriptParam() if len(fn) == 0: print("No filename supplied via --python-script-argument") @@ -13,15 +16,16 @@ else: # Open the file _inkscape.fileOpen(fn) + # Get system directory for temp-files and create an absolute path for the export output. tempdir = tempfile.gettempdir() - outPNG = os.path.join(tempdir, 'export.png') - # Export as PNG. + # Define the export settings dpi = 150 area = 'page' areaSnap = 1 + # Run the export _inkscape.exportPNG(outPNG, dpi, area, areaSnap) print("Exported file saved to " + outPNG) diff --git a/share/python/tests/openSaveExport.py b/share/python/tests/openSaveExport.py index fe031a4cfa..e5b047366f 100644 --- a/share/python/tests/openSaveExport.py +++ b/share/python/tests/openSaveExport.py @@ -19,11 +19,12 @@ outPNG = os.path.join(tempdir, 'about.png') # Try to save as PDF. This may pop up user interaction dialogs. _inkscape.fileSaveCopy(outPDF, 'application/pdf') -# Export as PNG. +# Define the export settings dpi = 150 area = 'page' areaSnap = 1 +# Run the export _inkscape.exportPNG(outPNG, dpi, area, areaSnap) print('Wrote about.pdf and about.png to ' + tempdir) diff --git a/share/python/tests/verbs.py b/share/python/tests/verbs.py index c50da46f1c..045dc3ef2c 100644 --- a/share/python/tests/verbs.py +++ b/share/python/tests/verbs.py @@ -1,8 +1,10 @@ # Tests for obtaining and running Inkscape verbs def dumpVerbs(): + # Get all of Inkscape's verbs verbs = _inkscape.getVerbs() + # Loop over the list, get the names and tooltip texts and produce a nice output. for i in verbs: name = _inkscape.getVerbName(i) tooltip = _inkscape.getVerbTooltip(i) @@ -12,6 +14,7 @@ def dumpVerbs(): print(' Tooltip: ' + tooltip) def runVerbTest(): + # Just toggle the grid to show how to run verbs. _inkscape.runVerbs(['ToggleGrid']) dumpVerbs() -- GitLab From 7ba64a5d63b8188b78de96f8706a1791002c73aa Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Mon, 31 Dec 2018 17:48:04 +0100 Subject: [PATCH 12/13] menus.xml now depends on the build configuration. --- share/ui/CMakeLists.txt | 13 +++++++++++++ share/ui/{menus.xml => menus.xml.in} | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) rename share/ui/{menus.xml => menus.xml.in} (99%) diff --git a/share/ui/CMakeLists.txt b/share/ui/CMakeLists.txt index 7b1450bd59..f42cfa1b7e 100644 --- a/share/ui/CMakeLists.txt +++ b/share/ui/CMakeLists.txt @@ -1,4 +1,17 @@ # SPDX-License-Identifier: GPL-2.0-or-later + file(GLOB _FILES "*.xml" "*.rc" "*.css" "*.ui" "*.glade") install(FILES ${_FILES} DESTINATION ${INKSCAPE_SHARE_INSTALL}/ui) + +# The menus.xml file needs more attention, since the menu entries may need to be +# enabled or disabled depending on the configuration. +if(WITH_PYTHON) + set(dialog_console "") +else(WITH_PYTHON) + set(dialog_console "") +endif() + +configure_file(menus.xml.in menus.xml) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/menus.xml DESTINATION ${INKSCAPE_SHARE_INSTALL}/ui) + diff --git a/share/ui/menus.xml b/share/ui/menus.xml.in similarity index 99% rename from share/ui/menus.xml rename to share/ui/menus.xml.in index 028ac7fe1e..178dbe7ec0 100644 --- a/share/ui/menus.xml +++ b/share/ui/menus.xml.in @@ -82,7 +82,7 @@ - + ${dialog_console} -- GitLab From 597f8887f6d44ba0aed4539392dfeb23aa4302ba Mon Sep 17 00:00:00 2001 From: Thomas Wiesner Date: Mon, 31 Dec 2018 18:22:19 +0100 Subject: [PATCH 13/13] Set the default value of WITH_PYTHON to OFF. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48b089b24c..a1d3a7f780 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ option(WITH_LIBWPG "Compile with support of libwpg for WordPerfect Graphics" ON) option(WITH_NLS "Compile with Native Language Support (using gettext)" ON) option(WITH_YAML "Compile with YAML support (enables xverbs)" ON) option(WITH_JEMALLOC "Compile with JEMALLOC support" ON) -option(WITH_PYTHON "Compile with embedded Python support" ON) +option(WITH_PYTHON "Compile with embedded Python support" OFF) option(WITH_FUZZ "Compile for fuzzing purpose (use 'make fuzz' only)" OFF) mark_as_advanced(WITH_FUZZ) -- GitLab