1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 #include "scriptercore.h"
8 
9 #include <QGlobalStatic>
10 #include <QWidget>
11 #include <QString>
12 #include <QStringList>
13 #include <QApplication>
14 #include <QMessageBox>
15 #include <QTextCodec>
16 #include <QByteArray>
17 #include <QPixmap>
18 #include <cstdlib>
19 
20 #include "cmdutil.h"
21 #include "runscriptdialog.h"
22 #include "ui/helpbrowser.h"
23 #include "ui/marksmanager.h"
24 #include "ui/notesstyleseditor.h"
25 #include "ui/propertiespalette.h" //TODO Move the calls to this to a signal
26 #include "ui/contentpalette.h" //TODO Move the calls to this to a signal
27 #include "ui/pagepalette.h" //TODO Move the calls to this to a signal
28 #include "ui/layers.h" //TODO Move the calls to this to a signal
29 #include "ui/outlinepalette.h" //TODO Move the calls to this to a signal
30 #include "ui/scmessagebox.h"
31 #include "ui/scmwmenumanager.h"
32 #include "pconsole.h"
33 #include "scraction.h"
34 #include "scribuscore.h"
35 #include "scribusdoc.h"
36 #include "scribusview.h"
37 #include "scpaths.h"
38 #include "selection.h"
39 #include "prefsfile.h"
40 #include "prefscontext.h"
41 #include "prefstable.h"
42 #include "prefsmanager.h"
43 #include "scribusapp.h" // need it to acces ScQApp->pythonScript & ScQApp->pythonScriptArgs
44 
ScripterCore(QWidget * parent)45 ScripterCore::ScripterCore(QWidget* parent)
46 {
47 	m_pyConsole = new PythonConsole(parent);
48 	m_scripterActions.clear();
49 	m_recentScriptActions.clear();
50 	returnString = "init";
51 
52 	m_scripterActions.insert("scripterExecuteScript", new ScrAction(QObject::tr("&Execute Script..."), QKeySequence(), this));
53 	m_scripterActions.insert("scripterShowConsole", new ScrAction(QObject::tr("Show &Console"), QKeySequence(), this));
54 	m_scripterActions.insert("scripterAboutScript", new ScrAction(QObject::tr("&About Script..."), QKeySequence(), this));
55 
56 	m_scripterActions["scripterExecuteScript"]->setMenuRole(QAction::NoRole);
57 	m_scripterActions["scripterShowConsole"]->setMenuRole(QAction::NoRole);
58 	m_scripterActions["scripterAboutScript"]->setMenuRole(QAction::NoRole);
59 
60 	m_scripterActions["scripterShowConsole"]->setToggleAction(true);
61 	m_scripterActions["scripterShowConsole"]->setChecked(false);
62 
63 	QObject::connect( m_scripterActions["scripterExecuteScript"], SIGNAL(triggered()) , this, SLOT(runScriptDialog()) );
64 	QObject::connect( m_scripterActions["scripterShowConsole"], SIGNAL(toggled(bool)) , this, SLOT(slotInteractiveScript(bool)) );
65 	QObject::connect( m_scripterActions["scripterAboutScript"], SIGNAL(triggered()) , this, SLOT(aboutScript()) );
66 
67 	m_savedRecentScripts.clear();
68 	readPlugPrefs();
69 
70 	QObject::connect(m_pyConsole, SIGNAL(runCommand()), this, SLOT(slotExecute()));
71 	QObject::connect(m_pyConsole, SIGNAL(paletteShown(bool)), this, SLOT(slotInteractiveScript(bool)));
72 
73 	QObject::connect(ScQApp, SIGNAL(appStarted()) , this, SLOT(runStartupScript()) );
74 	QObject::connect(ScQApp, SIGNAL(appStarted()) , this, SLOT(slotRunPythonScript()) );
75 }
76 
~ScripterCore()77 ScripterCore::~ScripterCore()
78 {
79 	savePlugPrefs();
80 	delete m_pyConsole;
81 }
82 
addToMainWindowMenu(ScribusMainWindow * mw)83 void ScripterCore::addToMainWindowMenu(ScribusMainWindow *mw)
84 {
85 	m_menuMgr = mw->scrMenuMgr;
86 	m_menuMgr->createMenu("Scripter", QObject::tr("&Script"));
87 	m_menuMgr->createMenu("ScribusScripts", QObject::tr("&Scribus Scripts"), "Scripter");
88 	m_menuMgr->addMenuItemString("ScribusScripts", "Scripter");
89 	m_menuMgr->addMenuItemString("scripterExecuteScript", "Scripter");
90 	m_menuMgr->createMenu("RecentScripts", QObject::tr("&Recent Scripts"), "Scripter", false, true);
91 	m_menuMgr->addMenuItemString("RecentScripts", "Scripter");
92 	m_menuMgr->addMenuItemString("scripterExecuteScript", "Scripter");
93 	m_menuMgr->addMenuItemString("SEPARATOR", "Scripter");
94 	m_menuMgr->addMenuItemString("scripterShowConsole", "Scripter");
95 	m_menuMgr->addMenuItemString("scripterAboutScript", "Scripter");
96 
97 	buildScribusScriptsMenu();
98 
99 	m_menuMgr->addMenuStringToMenuBarBefore("Scripter", "Windows");
100 	m_menuMgr->addMenuItemStringsToMenuBar("Scripter", m_scripterActions);
101 	m_recentScripts = m_savedRecentScripts;
102 	rebuildRecentScriptsMenu();
103 }
104 
enableMainWindowMenu()105 void ScripterCore::enableMainWindowMenu()
106 {
107 	if (!m_menuMgr)
108 		return;
109 	m_menuMgr->setMenuEnabled("ScribusScripts", true);
110 	m_menuMgr->setMenuEnabled("RecentScripts", true);
111 	m_scripterActions["scripterExecuteScript"]->setEnabled(true);
112 }
113 
disableMainWindowMenu()114 void ScripterCore::disableMainWindowMenu()
115 {
116 	if (!m_menuMgr)
117 		return;
118 	m_menuMgr->setMenuEnabled("ScribusScripts", false);
119 	m_menuMgr->setMenuEnabled("RecentScripts", false);
120 	m_scripterActions["scripterExecuteScript"]->setEnabled(false);
121 }
122 
buildScribusScriptsMenu()123 void ScripterCore::buildScribusScriptsMenu()
124 {
125 	QString pfad = ScPaths::instance().scriptDir();
126 	QString pfad2 = QDir::toNativeSeparators(pfad);
127 	QDir ds(pfad2, "*.py", QDir::Name | QDir::IgnoreCase, QDir::Files | QDir::NoSymLinks);
128 	if ((!ds.exists()) || (ds.count() == 0))
129 		return;
130 
131 	for (uint dc = 0; dc < ds.count(); ++dc)
132 	{
133 		QFileInfo fs(ds[dc]);
134 		QString strippedName=fs.baseName();
135 		m_scripterActions.insert(strippedName, new ScrAction( ScrAction::RecentScript, strippedName, QKeySequence(), this, strippedName));
136 		connect( m_scripterActions[strippedName], SIGNAL(triggeredData(QString)), this, SLOT(StdScript(QString)) );
137 		m_menuMgr->addMenuItemString(strippedName, "ScribusScripts");
138 	}
139 }
140 
rebuildRecentScriptsMenu()141 void ScripterCore::rebuildRecentScriptsMenu()
142 {
143 	m_menuMgr->clearMenuStrings("RecentScripts");
144 	m_recentScriptActions.clear();
145 	uint max = qMin(PrefsManager::instance().appPrefs.uiPrefs.recentDocCount, m_recentScripts.count());
146 	for (uint m = 0; m < max; ++m)
147 	{
148 		QString strippedName=m_recentScripts[m];
149 		strippedName.remove(QDir::separator());
150 		m_recentScriptActions.insert(strippedName, new ScrAction( ScrAction::RecentScript, m_recentScripts[m], QKeySequence(), this, m_recentScripts[m]));
151 		connect( m_recentScriptActions[strippedName], SIGNAL(triggeredData(QString)), this, SLOT(RecentScript(QString)) );
152 		m_menuMgr->addMenuItemString(strippedName, "RecentScripts");
153 	}
154 	m_menuMgr->addMenuItemStringsToRememberedMenu("RecentScripts", m_recentScriptActions);
155 }
156 
finishScriptRun()157 void ScripterCore::finishScriptRun()
158 {
159 	ScribusMainWindow* mainWin = ScCore->primaryMainWindow();
160 	if (!mainWin->HaveDoc)
161 		return;
162 
163 	mainWin->propertiesPalette->setDoc(mainWin->doc);
164 	mainWin->contentPalette->setDoc(mainWin->doc);
165 	mainWin->marksManager->setDoc(mainWin->doc);
166 	mainWin->nsEditor->setDoc(mainWin->doc);
167 	mainWin->layerPalette->setDoc(mainWin->doc);
168 	mainWin->outlinePalette->setDoc(mainWin->doc);
169 	mainWin->outlinePalette->BuildTree();
170 	mainWin->pagePalette->setView(mainWin->view);
171 	mainWin->pagePalette->rebuild();
172 	mainWin->doc->RePos = false;
173 	if (mainWin->doc->m_Selection->count() != 0)
174 		mainWin->doc->m_Selection->itemAt(0)->emitAllToGUI();
175 	mainWin->HaveNewSel();
176 	mainWin->view->DrawNew();
177 	//CB Really only need (want?) this for new docs, but we need it after a call to ScMW doFileNew.
178 	//We don't want it in cmddoc calls as itll interact with the GUI before a script may be finished.
179 	mainWin->HaveNewDoc();
180 }
181 
runScriptDialog()182 void ScripterCore::runScriptDialog()
183 {
184 	QString fileName;
185 	RunScriptDialog dia( ScCore->primaryMainWindow(), m_enableExtPython );
186 	if (dia.exec())
187 	{
188 		fileName = dia.selectedFile();
189 		slotRunScriptFile(fileName, dia.extensionRequested());
190 
191 		if (m_recentScripts.indexOf(fileName) == -1)
192 			m_recentScripts.prepend(fileName);
193 		else
194 		{
195 			m_recentScripts.removeAll(fileName);
196 			m_recentScripts.prepend(fileName);
197 		}
198 		rebuildRecentScriptsMenu();
199 	}
200 	finishScriptRun();
201 }
202 
StdScript(const QString & baseFilename)203 void ScripterCore::StdScript(const QString& baseFilename)
204 {
205 	QString pfad = ScPaths::instance().scriptDir();
206 	QString pfad2;
207 	pfad2 = QDir::toNativeSeparators(pfad);
208 	QString fn = pfad2+baseFilename+".py";
209 	QFileInfo fd(fn);
210 	if (!fd.exists())
211 		return;
212 	slotRunScriptFile(fn);
213 	finishScriptRun();
214 }
215 
RecentScript(const QString & fn)216 void ScripterCore::RecentScript(const QString& fn)
217 {
218 	QFileInfo fd(fn);
219 	if (!fd.exists())
220 	{
221 		m_recentScripts.removeAll(fn);
222 		rebuildRecentScriptsMenu();
223 		return;
224 	}
225 	slotRunScriptFile(fn);
226 	finishScriptRun();
227 }
228 
slotRunScriptFile(const QString & fileName,bool inMainInterpreter)229 void ScripterCore::slotRunScriptFile(const QString& fileName, bool inMainInterpreter)
230 {
231 	slotRunScriptFile(fileName, QStringList(), inMainInterpreter);
232 }
233 
slotRunScriptFile(const QString & fileName,QStringList arguments,bool inMainInterpreter)234 void ScripterCore::slotRunScriptFile(const QString& fileName, QStringList arguments, bool inMainInterpreter)
235 /** run "filename" python script with the additional arguments provided in "arguments" */
236 {
237 	// Prevent two scripts to be run concurrently or face crash!
238 	if (ScCore->primaryMainWindow()->scriptIsRunning())
239 		return;
240 	disableMainWindowMenu();
241 
242 	PyThreadState *state = nullptr;
243 	QFileInfo fi(fileName);
244 	QByteArray na = fi.fileName().toLocal8Bit();
245 	// Set up a sub-interpreter if needed:
246 	PyThreadState* global_state = nullptr;
247 	if (!inMainInterpreter)
248 	{
249 		ScCore->primaryMainWindow()->propertiesPalette->unsetDoc();
250 		ScCore->primaryMainWindow()->contentPalette->unsetDoc();
251 		ScCore->primaryMainWindow()->pagePalette->setView(nullptr);
252 		ScCore->primaryMainWindow()->setScriptRunning(true);
253 		qApp->setOverrideCursor(QCursor(Qt::WaitCursor));
254 		// Create the sub-interpreter
255 		// FIXME: This calls abort() in a Python debug build. We're doing something wrong.
256 		//stateo = PyEval_SaveThread();
257 		global_state = PyThreadState_Get();
258 		state = Py_NewInterpreter();
259 		// Init the scripter module in the sub-interpreter
260 		//initscribus(ScCore->primaryMainWindow());
261 	}
262 
263 	// Make sure sys.argv[0] is the path to the script
264 	arguments.prepend(na.data());
265 	//convert arguments (QListString) to char** for Python bridge
266 	/* typically arguments == ['path/to/script.py','--argument1','valueforarg1','--flag']*/
267 	wchar_t **comm = new wchar_t*[arguments.size()];
268 	for (int i = 0; i < arguments.size(); i++)
269 	{
270 		const QString& argStr = arguments.at(i);
271 		comm[i] = new wchar_t[argStr.size() + 1]; //+1 to allow adding '\0'. may be useless, don't know how to check.
272 		comm[i][argStr.size()] = 0;
273 		argStr.toWCharArray(comm[i]);
274 	}
275 	PySys_SetArgv(arguments.size(), comm);
276 
277 	for (int i = 0; i < arguments.size(); i++)
278 		delete[] comm[i];
279 	delete[] comm;
280 
281 	// call python script
282 	PyObject* m = PyImport_AddModule((char*)"__main__");
283 	if (m == nullptr)
284 		qDebug("Failed to get __main__ - aborting script");
285 	else
286 	{
287 		// Path separators need to be escaped on Windows
288 		QString escapedAbsPath  = QDir::toNativeSeparators(fi.absolutePath()).replace("\\", "\\\\");
289 		QString escapedAbsFilePath  = QDir::toNativeSeparators(fi.absoluteFilePath()).replace("\\", "\\\\");
290 		QString escapedFileName = QDir::toNativeSeparators(fileName).replace("\\", "\\\\");
291 		// FIXME: If filename contains chars outside 7bit ascii, might be problems
292 		PyObject* globals = PyModule_GetDict(m);
293 		// Build the Python code to run the script
294 		//QString cm = QString("from __future__ import division\n"); removed due #5252 PV
295 		QString cm = QString("import sys\n");
296 		cm        += QString("import io\n");
297 		/* Implementation of the help() in pydoc.py reads some OS variables
298 		 * for output settings. I use ugly hack to stop freezing calling help()
299 		 * in script. pv. */
300 		cm        += QString("import os\nos.environ['PAGER'] = '/bin/false'\n"); // HACK
301 		cm        += QString("sys.path[0] = \"%1\"\n").arg(escapedAbsPath);
302 		// Replace sys.stdin with a dummy StringIO that always returns
303 		// "" for read
304 		cm        += QString("sys.stdin = io.StringIO()\n");
305 		// Provide script path to the interpreter
306 		cm        += QString("__file__ = \"%1\"\n").arg(escapedAbsFilePath);
307 		// tell the script if it's running in the main intepreter or a subinterpreter
308 		cm        += QString("import scribus\n");
309 		if (inMainInterpreter)
310 			cm+= QString("scribus.mainInterpreter = True\n");
311 		else
312 			cm+= QString("scribus.mainInterpreter = False\n");
313 		cm        += QString("try:\n");
314 		cm        += QString("    exec(open(\"%1\", \"rb\").read())\n").arg(escapedFileName);
315 		cm        += QString("except SystemExit:\n");
316 		cm        += QString("    pass\n");
317 		// Capture the text of any other exception that's raised by the interpreter
318 		// into a StringIO buffer for later extraction.
319 		cm        += QString("except:\n");
320 		cm        += QString("    import traceback\n");
321 		cm        += QString("    _errorMsg = traceback.format_exc()\n");
322 		if (!ScCore->usingGUI())
323 			cm += QString("    traceback.print_exc()\n");
324 		// We re-raise the exception so the return value of PyRun_StringFlags reflects
325 		// the fact that an exception has ocurred.
326 		cm        += QString("    raise\n");
327 		// FIXME: if cmd contains chars outside 7bit ascii, might be problems
328 		QByteArray cmd = cm.toUtf8();
329 		// Now run the script in the interpreter's global scope. It'll run in a
330 		// sub-interpreter if we created and switched to one earlier, otherwise
331 		// it'll run in the main interpreter.
332 		PyObject* result = PyRun_String(cmd.data(), Py_file_input, globals, globals);
333 		// nullptr is returned if an exception is set. We don't care about any
334 		// other return value (most likely None anyway) and can ignore it.
335 		if (result == nullptr)
336 		{
337 			PyObject* errorMsgPyStr = PyMapping_GetItemString(globals, (char*)"_errorMsg");
338 			if (errorMsgPyStr == nullptr)
339 			{
340 				// It's rather unlikely that this will ever be reached - to get here
341 				// we'd have to fail to retrive the string we just created.
342 				qDebug("Error retrieving error message content after script exception!");
343 				qDebug("Exception was:");
344 				PyErr_Print();
345 			}
346 			else if (ScCore->usingGUI())
347 			{
348 				QString errorMsg = PyUnicode_asQString(errorMsgPyStr);
349 				// Display a dialog to the user with the exception
350 				QClipboard *cp = QApplication::clipboard();
351 				cp->setText(errorMsg);
352 				ScCore->closeSplash();
353 				qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
354 				ScMessageBox::warning(ScCore->primaryMainWindow(),
355 									tr("Script error"),
356 									"<qt><p>"
357 									+ tr("If you are running an official script report it at <a href=\"http://bugs.scribus.net\">bugs.scribus.net</a> please.")
358 									+ "</p><pre>" + errorMsg.toHtmlEscaped() + "</pre><p>"
359 									+ tr("This message is in your clipboard too. Use Ctrl+V to paste it into bug tracker.")
360 									+ "</p></qt>");
361 			}
362 			// We've already processed the exception text, so clear the exception
363 			PyErr_Clear();
364 		} // end if result == nullptr
365 		// Because 'result' may be nullptr, not a PyObject*, we must call PyXDECREF not Py_DECREF
366 		Py_XDECREF(result);
367 	} // end if m == nullptr
368 	if (!inMainInterpreter)
369 	{
370 		Py_EndInterpreter(state);
371 		PyThreadState_Swap(global_state);
372 		//PyEval_RestoreThread(stateo);
373 		qApp->restoreOverrideCursor();
374 		ScCore->primaryMainWindow()->setScriptRunning(false);
375 	}
376 
377 	enableMainWindowMenu();
378 }
379 
380 // needed for running script from CLI
slotRunPythonScript()381 void ScripterCore::slotRunPythonScript()
382 {
383 	if (!ScQApp->pythonScript.isNull())
384 	{
385 		slotRunScriptFile(ScQApp->pythonScript, ScQApp->pythonScriptArgs, true);
386 		finishScriptRun();
387 	}
388 }
389 
slotRunScript(const QString & Script)390 void ScripterCore::slotRunScript(const QString& Script)
391 {
392 	// Prevent two scripts to be run concurrently or face crash!
393 	if (ScCore->primaryMainWindow()->scriptIsRunning())
394 		return;
395 	disableMainWindowMenu();
396 
397 	ScCore->primaryMainWindow()->propertiesPalette->unsetDoc();
398 	ScCore->primaryMainWindow()->contentPalette->unsetDoc();
399 	ScCore->primaryMainWindow()->pagePalette->setView(nullptr);
400 	ScCore->primaryMainWindow()->setScriptRunning(true);
401 	inValue = Script;
402 	QString cm;
403 	cm = "# -*- coding: utf8 -*- \n";
404 	if (PyThreadState_Get() != nullptr)
405 	{
406 		//initscribus(ScCore->primaryMainWindow());
407 		/* HACK: following loop handles all input line by line.
408 		It *should* use I.C. because of docstrings etc. I.I. cannot
409 		handle docstrings right.
410 		Calling all code in one command:
411 		ia = code.InteractiveInterpreter() ia.runsource(getval())
412 		works fine in plain Python. Not here. WTF? */
413 		cm += (
414 				"try:\n"
415 				"    import io\n"
416 				"    scribus._bu = io.StringIO()\n"
417 				"    sys.stdout = scribus._bu\n"
418 				"    sys.stderr = scribus._bu\n"
419 				"    sys.argv = ['scribus']\n" // this is the PySys_SetArgv replacement
420 				"    scribus.mainInterpreter = True\n" // the scripter console runs everything in the main interpreter
421 				"    for scribus._i_str in scribus.getval().splitlines():\n"
422 				"        scribus._ia.push(scribus._i_str)\n"
423 				"    scribus.retval(scribus._bu.getvalue())\n"
424 				"    sys.stdout = sys.__stdout__\n"
425 				"    sys.stderr = sys.__stderr__\n"
426 				"except SystemExit:\n"
427 				"    print ('Catched SystemExit - it is not good for Scribus')\n"
428 				"except KeyboardInterrupt:\n"
429 				"    print ('Catched KeyboardInterrupt - it is not good for Scribus')\n"
430 			  );
431 	}
432 	// Set up sys.argv
433 	/* PV - WARNING: THIS IS EVIL! This code summons a crash - see
434 	bug #3510. I don't know why as the Python C API is a little
435 	bit magic for me. It looks like it replaces the cm QString or what...
436 	"In file tools/qgarray.cpp, line 147: Out of memory"
437 	Anyway - sys.argv is set above
438 	char* comm[1];
439 	comm[0] = const_cast<char*>("scribus");
440 	PySys_SetArgv(1, comm); */
441 	// then run the code
442 	PyObject* m = PyImport_AddModule((char*)"__main__");
443 	if (m == nullptr)
444 		qDebug("Failed to get __main__ - aborting script");
445 	else
446 	{
447 		PyObject* globals = PyModule_GetDict(m);
448 		PyObject* result = PyRun_String(cm.toUtf8().data(), Py_file_input, globals, globals);
449 		if (result == nullptr)
450 		{
451 			PyErr_Print();
452 			ScMessageBox::warning(ScCore->primaryMainWindow(), tr("Script error"),
453 					"<qt>" + tr("There was an internal error while trying the "
454 					   "command you entered. Details were printed to "
455 					   "stderr. ") + "</qt>");
456 		}
457 		else
458 		// Because 'result' may be nullptr, not a PyObject*, we must call PyXDECREF not Py_DECREF
459 			Py_XDECREF(result);
460 	}
461 	ScCore->primaryMainWindow()->setScriptRunning(false);
462 
463 	enableMainWindowMenu();
464 }
465 
slotInteractiveScript(bool visible)466 void ScripterCore::slotInteractiveScript(bool visible)
467 {
468 	QObject::disconnect( m_scripterActions["scripterShowConsole"], SIGNAL(toggled(bool)) , this, SLOT(slotInteractiveScript(bool)) );
469 
470 	m_scripterActions["scripterShowConsole"]->setChecked(visible);
471 	m_pyConsole->setFonts();
472 	m_pyConsole->setVisible(visible);
473 
474 	QObject::connect( m_scripterActions["scripterShowConsole"], SIGNAL(toggled(bool)) , this, SLOT(slotInteractiveScript(bool)) );
475 }
476 
slotExecute()477 void ScripterCore::slotExecute()
478 {
479 	slotRunScript(m_pyConsole->command());
480 	m_pyConsole->outputEdit->append(returnString);
481 	m_pyConsole->commandEdit->ensureCursorVisible();
482 	finishScriptRun();
483 }
484 
readPlugPrefs()485 void ScripterCore::readPlugPrefs()
486 {
487 	PrefsContext* prefs = PrefsManager::instance().prefsFile->getPluginContext("scriptplugin");
488 	if (!prefs)
489 	{
490 		qDebug("scriptplugin: Unable to load prefs");
491 		return;
492 	}
493 	PrefsTable* prefRecentScripts = prefs->getTable("recentscripts");
494 	if (!prefRecentScripts)
495 	{
496 		qDebug("scriptplugin: Unable to get recent scripts");
497 		return;
498 	}
499 	// Load recent scripts from the prefs
500 	for (int i = 0; i < prefRecentScripts->getRowCount(); i++)
501 	{
502 		QString rs(prefRecentScripts->get(i,0));
503 		m_savedRecentScripts.append(rs);
504 	}
505 	// then get more general preferences
506 	m_enableExtPython = prefs->getBool("extensionscripts",false);
507 	m_importAllNames = prefs->getBool("importall",true);
508 	m_startupScript = prefs->get("startupscript", QString());
509 	// and have the console window set up its position
510 }
511 
savePlugPrefs()512 void ScripterCore::savePlugPrefs()
513 {
514 	PrefsContext* prefs = PrefsManager::instance().prefsFile->getPluginContext("scriptplugin");
515 	if (!prefs)
516 	{
517 		qDebug("scriptplugin: Unable to load prefs");
518 		return;
519 	}
520 	PrefsTable* prefRecentScripts = prefs->getTable("recentscripts");
521 	if (!prefRecentScripts)
522 	{
523 		qDebug("scriptplugin: Unable to get recent scripts");
524 		return;
525 	}
526 	for (int i = 0; i < m_recentScripts.count(); i++)
527 	{
528 		prefRecentScripts->set(i, 0, m_recentScripts[i]);
529 	}
530 	// then save more general preferences
531 	prefs->set("extensionscripts", m_enableExtPython);
532 	prefs->set("importall", m_importAllNames);
533 	prefs->set("startupscript", m_startupScript);
534 }
535 
aboutScript()536 void ScripterCore::aboutScript()
537 {
538 	QString fname = ScCore->primaryMainWindow()->CFileDialog(".", tr("Examine Script"), tr("Python Scripts (*.py *.PY);;All Files (*)"), "", fdNone);
539 	if (fname.isNull())
540 		return;
541 	QString html("<html><body>");
542 	QFileInfo fi = QFileInfo(fname);
543 	QFile input(fname);
544 	if (!input.open(QIODevice::ReadOnly))
545 		return;
546 	QTextStream intputstream(&input);
547 	QString content = intputstream.readAll();
548 	QString docstring = content.section(R"(""")", 1, 1);
549 	if (!docstring.isEmpty())
550 	{
551 		html += QString("<h1>%1 %2</h1>").arg( tr("Documentation for:"), fi.fileName());
552 		html += QString("<p>%1</p>").arg(docstring.replace("\n\n", "<br><br>"));
553 	}
554 	else
555 	{
556 		html += QString("<p><b>%1 %2 %3</b></p>").arg( tr("Script"), fi.fileName(), tr(" doesn't contain any docstring!"));
557 		html += QString("<pre>%4</pre>").arg(content);
558 	}
559 	html += "</body></html>";
560 	input.close();
561 	HelpBrowser *dia = new HelpBrowser(nullptr, QObject::tr("About Script") + " " + fi.fileName(), "en");
562 	dia->setHtml(html);
563 	dia->show();
564 }
565 
566 void ScripterCore::initExtensionScripts()
567 {
568 	// Nothing to do currently
569 }
570 
571 void ScripterCore::runStartupScript()
572 {
573 	if ((m_enableExtPython) && (!m_startupScript.isEmpty()))
574 	{
575 		if (QFile::exists(this->m_startupScript))
576 		{
577 			// run the script in the main interpreter. The user will be informed
578 			// with a dialog if something has gone wrong.
579 			this->slotRunScriptFile(this->m_startupScript, true);
580 		}
581 		else
582 			ScMessageBox::warning(ScCore->primaryMainWindow(), tr("Startup Script error"),
583 					      tr("Could not find script: %1.").arg( m_startupScript));
584 	}
585 }
586 
587 void ScripterCore::languageChange()
588 {
589 	m_scripterActions["scripterExecuteScript"]->setText(QObject::tr("&Execute Script..."));
590 	m_scripterActions["scripterShowConsole"]->setText(QObject::tr("Show &Console"));
591 	m_scripterActions["scripterAboutScript"]->setText(QObject::tr("&About Script..."));
592 
593 	m_menuMgr->setText("Scripter", QObject::tr("&Script"));
594 	m_menuMgr->setText("ScribusScripts", QObject::tr("&Scribus Scripts"));
595 	m_menuMgr->setText("RecentScripts", QObject::tr("&Recent Scripts"));
596 }
597 
598 bool ScripterCore::setupMainInterpreter()
599 {
600 	QString cm = QString(
601 		"# -*- coding: utf-8 -*-\n"
602 		"import scribus\n"
603 		"import sys\n"
604 		"import code\n"
605 		"sys.path.insert(0, \"%1\")\n"
606 		"import io\n"
607 		"sys.stdin = io.StringIO()\n"
608 		"scribus._ia = code.InteractiveConsole(globals())\n"
609 		).arg(ScPaths::instance().scriptDir());
610 	if (m_importAllNames)
611 		cm += "from scribus import *\n";
612 	QByteArray cmd = cm.toUtf8();
613 	if (PyRun_SimpleString(cmd.data()))
614 	{
615 		PyErr_Print();
616 		ScMessageBox::warning(ScCore->primaryMainWindow(), tr("Script error"),
617 				tr("Setting up the Python plugin failed. "
618 				   "Error details were printed to stderr. "));
619 		return false;
620 	}
621 	return true;
622 }
623 
624 void ScripterCore::setStartupScript(const QString& newScript)
625 {
626 	m_startupScript = newScript;
627 }
628 
629 void ScripterCore::setExtensionsEnabled(bool enable)
630 {
631 	m_enableExtPython = enable;
632 }
633 
634 void ScripterCore::updateSyntaxHighlighter()
635 {
636 	m_pyConsole->updateSyntaxHighlighter();
637 }
638 
639 const QString & ScripterCore::startupScript() const
640 {
641 	return m_startupScript;
642 }
643 
644 bool ScripterCore::extensionsEnabled() const
645 {
646 	return m_enableExtPython;
647 }
648