/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/
#include "cmdtext.h"

#include "appmodes.h"
#include "cmdutil.h"
#include "hyphenator.h"
#include "pageitem_textframe.h"
#include "prefsmanager.h"
#include "pyesstring.h"
#include "scribuscore.h"
#include "scribusdoc.h"
#include "scribusview.h"
#include "selection.h"
#include "util.h"


template<typename T>
class ApplyCharstyleHelper
{
	PageItem* m_item;
	T m_value;

public:
	ApplyCharstyleHelper(PageItem* item, T v) : m_item(item), m_value(v) {}

	void apply(void (CharStyle::*f)(T), int p, int len)
	{
		CharStyle cs;
		(cs.*f)(m_value);
		if (m_item->HasSel)
		{
			int max = qMax(p + len, m_item->itemText.length());
			for (int i = p; i < max; i++)
			{
				if (m_item->itemText.selected(i))
					m_item->itemText.applyCharStyle(i, 1, cs);
			}
		}
		else
		{
			m_item->itemText.applyCharStyle(p, len, cs);
		}
	}

};

PyObject *scribus_getfontsize(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get font size of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); i++)
			if (item->itemText.selected(i))
				return PyFloat_FromDouble(item->itemText.charStyle(i).fontSize() / 10.0);
		return nullptr;
	}
	return PyFloat_FromDouble(item->currentCharStyle().fontSize() / 10.0);
}

PyObject *scribus_getfont(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get font of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); i++)
			if (item->itemText.selected(i))
				return PyUnicode_FromString(item->itemText.charStyle(i).font().scName().toUtf8());
		return nullptr;
	}
	return PyUnicode_FromString(item->currentCharStyle().font().scName().toUtf8());
}

PyObject *scribus_gettextcolor(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
    if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text color of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); ++i)
		{
			if (item->itemText.selected(i))
				return PyUnicode_FromString(item->itemText.charStyle(i).fillColor().toUtf8());
		}
        return nullptr;
	}
    return PyUnicode_FromString(item->currentCharStyle().fillColor().toUtf8());
}

PyObject *scribus_gettextshade(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text color of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); ++i)
		{
			if (item->itemText.selected(i))
				return PyLong_FromLong(item->itemText.charStyle(i).fillShade());
		}
		return nullptr;
	}
	return PyLong_FromLong(item->currentCharStyle().fillShade());
}

PyObject *scribus_gettracking(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get tracking of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); ++i)
		{
			if (item->itemText.selected(i))
				return PyFloat_FromDouble(item->itemText.charStyle(i).tracking() / 10.0);
		}
		return nullptr;
	}
	return PyFloat_FromDouble(item->currentCharStyle().tracking() / 10.0);
}

PyObject *scribus_getwordtracking(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get word tracking of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); ++i)
		{
			if (item->itemText.selected(i))
				return PyFloat_FromDouble(item->itemText.charStyle(i).wordTracking() * 100.0);
		}
		return nullptr;
	}
	return PyFloat_FromDouble(item->currentCharStyle().wordTracking() * 100.0);
}

PyObject *scribus_getminwordtracking(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get word tracking of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); ++i)
		{
			if (item->itemText.selected(i))
				return PyFloat_FromDouble(item->itemText.paragraphStyle(i).minWordTracking() * 100.0);
		}
		return nullptr;
	}

	return PyFloat_FromDouble(item->currentStyle().minWordTracking() * 100.0);
}

PyObject *scribus_gettextlength(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text size of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return PyLong_FromLong(static_cast<long>(item->itemText.length()));
}

PyObject *scribus_gettextlines(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get number of lines of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return PyLong_FromLong(static_cast<long>(item->textLayout.lines()));
}

PyObject *scribus_gettextverticalalignment(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get vertical alignment of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	return PyLong_FromLong(static_cast<long>(item->verticalAlignment()));
}

PyObject *scribus_getcolumns(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get column count of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return PyLong_FromLong(static_cast<long>(item->m_columns));
}

PyObject *scribus_getcolumngap(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get column gap of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	return PyFloat_FromDouble(PointToValue(item->m_columnGap));
}

PyObject *scribus_getfontfeatures(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get fontfeatures of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	if (item->HasSel)
	{
		for (int i = 0; i < item->itemText.length(); i++)
			if (item->itemText.selected(i))
				return PyUnicode_FromString(item->itemText.charStyle(i).fontFeatures().toUtf8());
		return nullptr;
	}
	return PyUnicode_FromString(item->currentCharStyle().fontFeatures().toUtf8());
}

PyObject *scribus_getfirstlinkedframe(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get linked frames of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	return PyUnicode_FromString(item->firstInChain()->itemName().toUtf8());
}

PyObject *scribus_getlastlinkedframe(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get linked frames of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	return PyUnicode_FromString(item->lastInChain()->itemName().toUtf8());
}

PyObject *scribus_getnextlinkedframe(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get linked frames of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	if (item->nextInChain() != nullptr)
		return PyUnicode_FromString(item->nextInChain()->itemName().toUtf8());

	Py_RETURN_NONE;
}

PyObject *scribus_getprevlinkedframe(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get linked frames of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	if (item->prevInChain() != nullptr)
		return PyUnicode_FromString(item->prevInChain()->itemName().toUtf8());

	Py_RETURN_NONE;
}

PyObject *scribus_getfirstlineoffset(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get first line offset of non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	return PyLong_FromLong(static_cast<long>(item->firstLineOffset()));
}

PyObject *scribus_getlinespacing(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get line space of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return PyFloat_FromDouble(item->currentStyle().lineSpacing());
}

PyObject *scribus_getlinespacingmode(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get line spacing mode of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return PyFloat_FromDouble(static_cast<double>(item->currentStyle().lineSpacingMode()));
}

PyObject *scribus_gettextdistances(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text distances of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return Py_BuildValue("(dddd)",
	        PointToValue(item->textToFrameDistLeft()),
	        PointToValue(item->textToFrameDistRight()),
	        PointToValue(item->textToFrameDistTop()),
	        PointToValue(item->textToFrameDistBottom()));
}

PyObject *scribus_getframetext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	
	const StoryText& story = item->itemText;
	QString text;
	text.reserve(story.hasSelection() ? story.selectionLength() : story.length());
	for (int i = item->firstInFrame(); i <= item->lastInFrame(); ++i)
	{
		if (item->HasSel)
		{
			if (story.selected(i))
				text += story.text(i);
		}
		else
		{
			text += story.text(i);
		}
	}
	return PyUnicode_FromString(text.toUtf8());
}

PyObject *scribus_getalltext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	// collect all chars from a storytext
	const StoryText& story = item->itemText;
	QString text;
	text.reserve(story.hasSelection() ? story.selectionLength() : story.length());
	for (int i = 0; i < story.length(); i++)
	{
		if (item->HasSel)
		{
			if (story.selected(i))
				text += story.text(i);
		}
		else
		{
			text += story.text(i);
		}
	}
	return PyUnicode_FromString(text.toUtf8());
}

PyObject *scribus_settext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString text;
	if (!PyArg_ParseTuple(args, "es|es", "utf-8", text.ptr(), "utf-8", name.ptr()))
	{
		name.resetDontFree();
		text.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *currItem = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (currItem == nullptr)
		return nullptr;
	if (!(currItem->isTextFrame()) && !(currItem->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text of non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	QString userText = QString::fromUtf8(text.c_str());
	userText.replace("\r\n", SpecialChars::PARSEP);
	userText.replace(QChar('\n') , SpecialChars::PARSEP);
	text.free();
	currItem->itemText.clear();
	currItem->itemText.insertChars(0, userText);
	currItem->invalidateLayout();

	Py_RETURN_NONE;
}

PyObject *scribus_inserttext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString text;
	int pos;
	if (!PyArg_ParseTuple(args, "esi|es", "utf-8", text.ptr(), &pos, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		text.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot insert text into non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	QString textData = QString::fromUtf8(text.c_str());
	textData.replace("\r\n", SpecialChars::PARSEP);
	textData.replace(QChar('\n') , SpecialChars::PARSEP);
	text.free();
	if ((pos < -1) || (pos > item->itemText.length()))
	{
		PyErr_SetString(PyExc_IndexError, QObject::tr("Insert index out of bounds.","python error").toUtf8().constData());
		return nullptr;
	}
	if (pos == -1)
		pos = item->itemText.length();
	item->itemText.insertChars(pos, textData, true);
	item->invalidateLayout();

	Py_RETURN_NONE;
}

PyObject *scribus_inserthtmltext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString file;

	if (!PyArg_ParseTuple(args, "es|es", "utf-8", file.ptr(), "utf-8", name.ptr()))
	{
		name.resetDontFree();
		file.resetDontFree();
		return nullptr;
	}

	if (!checkHaveDocument()) {
		return nullptr;
	}

	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr) {
		return nullptr;
	}

	if (!(item->isTextFrame()) && !(item->isPathText())) {
		PyErr_SetString(WrongFrameTypeError,
				QObject::tr("Cannot insert text into non-text frame.",
					"python error").toUtf8().constData());
		return nullptr;
	}

	QString fileName = QString::fromUtf8(file.c_str());

	gtGetText gt(ScCore->primaryMainWindow()->doc);
	gt.launchImporter(-1, fileName, false, QString("utf-8"), false, true, item);

	// FIXME: PyMem_Free() - are any needed??
	Py_RETURN_NONE;
}

PyObject *scribus_layouttext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame() && !item->isPathText())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot layout text of a non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	item->layout();

	Py_RETURN_NONE;
}

PyObject *scribus_layouttextchain(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame() && !item->isPathText())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot layout text chain for a non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}

	if (item->isPathText())
	{
		item->layout();
		Py_RETURN_NONE;
	}
	
	PageItem* firstFrame = item->firstInChain();
	PageItem* nextFrame = firstFrame;
	while (nextFrame)
	{
		nextFrame->layout();
		nextFrame = nextFrame->nextInChain();
	}

	Py_RETURN_NONE;
}

PyObject *scribus_settextalignment(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int alignment;
	if (!PyArg_ParseTuple(args, "i|es", &alignment, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if ((alignment > 4) || (alignment < 0))
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Alignment out of range. Use one of the scribus.ALIGN_* constants.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text alignment on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	
	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode  = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetAlignment(alignment, &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setdirection(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int direction;
	if (!PyArg_ParseTuple(args, "i|es", &direction, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if ((direction > 1) || (direction < 0))
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("direction out of range. Use one of the scribus.DIRECTION* constants.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text direction on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetDirection(direction, &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setfontsize(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double size;
	if (!PyArg_ParseTuple(args, "d|es", &size, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if ((size > 512) || (size < 1))
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Font size out of bounds - must be 1 <= size <= 512.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;

	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set font size on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetFontSize(qRound(size * 10.0), &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setfontfeatures(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString fontfeature;
	if (!PyArg_ParseTuple(args, "es|es", "utf-8", fontfeature.ptr(), "utf-8", name.ptr()))
	{
		name.resetDontFree();
		fontfeature.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;

	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;

	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set font feature on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetFontFeatures(QString::fromUtf8(fontfeature.c_str()), &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setfont(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString font;
	if (!PyArg_ParseTuple(args, "es|es", "utf-8", font.ptr(), "utf-8", name.ptr()))
	{
		name.resetDontFree();
		font.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set font on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	if (!PrefsManager::instance().appPrefs.fontPrefs.AvailFonts.contains(QString::fromUtf8(font.c_str())))
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Font not found.", "python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetFont(QString::fromUtf8(font.c_str()), &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setfirstlineoffset(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int offset;
	if (!PyArg_ParseTuple(args, "i|es", &offset, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (offset < 0 || offset > (int) FLOPBaselineGrid)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("First line offset out of bounds, Use one of the scribus.FLOP_* constants.", "python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set first line offset on a non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	item->setFirstLineOffset((FirstLineOffsetPolicy) offset);
	item->update();

	Py_RETURN_NONE;
}

PyObject *scribus_setlinespacing(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double w;
	if (!PyArg_ParseTuple(args, "d|es", &w, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (w < 0.1)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Line space out of bounds, must be >= 0.1.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set line spacing on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetLineSpacing(w, &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_settracking(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double kern;
	if (!PyArg_ParseTuple(args, "d|es", &kern, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set tracking on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetTracking(kern * 10.0, &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setwordtracking(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double kern;
	if (!PyArg_ParseTuple(args, "d|es", &kern, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set word tracking on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetWordTracking(kern / 100.0, &tmpSelection);
	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}

PyObject *scribus_setminwordtracking(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double wt;
	if (!PyArg_ParseTuple(args, "d|es", &wt, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (wt < 1 || wt > 100)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Minimum word out of bounds, must be >= 1 and <= 100","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set minimum word tracking on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;

	ParagraphStyle newStyle;
	newStyle.setMinWordTracking(wt / 100.0);
	doc->itemSelection_ApplyParagraphStyle(newStyle, &tmpSelection);

	doc->appMode = oldAppMode;

	Py_RETURN_NONE;
}


PyObject *scribus_setlinespacingmode(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int w;
	if (!PyArg_ParseTuple(args, "i|es", &w, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (w < 0 || w > 3) // Use constants?
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Line space mode invalid, must be 0, 1 or 2","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set line spacing mode on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetLineSpacingMode(w, &tmpSelection);
	doc->appMode = oldAppMode;
		
	Py_RETURN_NONE;
}

PyObject *scribus_settextdistances(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double l,r,t,b;
	if (!PyArg_ParseTuple(args, "dddd|es", &l, &r, &t, &b, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (l < 0.0 || r < 0.0 || t < 0.0 || b < 0.0)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Text distances out of bounds, must be positive.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text distances on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	item->setTextToFrameDist(ValueToPoint(l), ValueToPoint(r), ValueToPoint(t), ValueToPoint(b));

	Py_RETURN_NONE;
}

PyObject *scribus_setcolumngap(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double w;
	if (!PyArg_ParseTuple(args, "d|es", &w, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (w < 0.0)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Column gap out of bounds, must be positive.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set column gap on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	item->m_columnGap = ValueToPoint(w);

	Py_RETURN_NONE;
}

PyObject *scribus_setcolumns(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int w;
	if (!PyArg_ParseTuple(args, "i|es", &w, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (w < 1)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Column count out of bounds, must be > 1.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set number of columns on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	item->m_columns = w;

	Py_RETURN_NONE;
}

PyObject *scribus_settextverticalalignment(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int alignment;
	if (!PyArg_ParseTuple(args, "i|es", &alignment, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (alignment < 0 || alignment > 2)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Vertical alignment out of bounds, Use one of the scribus.ALIGNV_* constants.", "python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set vertical alignment on a non-text frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	item->setVerticalAlignment(alignment);
	item->update();

	Py_RETURN_NONE;
}

PyObject *scribus_selectframetext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int start, selcount;
	if (!PyArg_ParseTuple(args, "ii|es", &start, &selcount, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;

	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot select text in a non-text frame", "python error").toUtf8().constData());
		return nullptr;
	}
	if (selcount < -1)
	{
		PyErr_SetString(PyExc_IndexError, QObject::tr("Count must be positive, 0 or -1", "python error").toUtf8().constData());
		return nullptr;
	}

	if (start < 0 || (selcount > 0 && ((item->lastInFrame() == -1) || (selcount + start > item->lastInFrame() - item->firstInFrame() + 1))))
	{
		PyErr_SetString(PyExc_IndexError, QObject::tr("Selection index out of bounds", "python error").toUtf8().constData());
		return nullptr;
	}
	start += item->firstInFrame();
	if (selcount == -1)
		selcount = item->lastInFrame() + 1 - start;
	item->itemText.deselectAll();
	if (selcount == 0)
	{
		item->HasSel = false;
		Py_RETURN_NONE;
	}
	item->itemText.select(start, selcount, true);
	item->HasSel = true;

	Py_RETURN_NONE;
}

PyObject *scribus_selecttext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int start, selcount;
	if (!PyArg_ParseTuple(args, "ii|es", &start, &selcount, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (selcount == -1)
	{
		// user wants to select all after the start point -- CR
		selcount = item->itemText.length() - start;
		if (selcount < 0)
			// user passed start that's > text in the frame
			selcount = 0;
	}
	// cr 2005-01-18 fixed off-by-one with end bound that made selecting the last char impossible
	if ((start < 0) || ((start + selcount) > item->itemText.length()))
	{
		PyErr_SetString(PyExc_IndexError, QObject::tr("Selection index out of bounds", "python error").toUtf8().constData());
		return nullptr;
	}
	if (!(item->isTextFrame()) && !(item->isPathText()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot select text in a non-text frame", "python error").toUtf8().constData());
		return nullptr;
	}
	/* FIXME: not sure if we should make this check or not
	if (start > ende)
	{
		PyErr_SetString(PyExc_ValueError, QString("Selection start > selection end").toUtf8().constData());
		return nullptr;
	}
	*/
	item->itemText.deselectAll();
	if (selcount == 0)
	{
		item->HasSel = false;
		Py_RETURN_NONE;
	}
	item->itemText.select(start, selcount, true);
	item->HasSel = true;

	Py_RETURN_NONE;
}

PyObject *scribus_getselectedtextrange(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text selection for non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	return Py_BuildValue("(ii)", item->itemText.startOfSelection(), item->itemText.selectionLength());
}

PyObject *scribus_getframeselectedtextrange(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot get text selection for non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	return Py_BuildValue("(ii)", item->itemText.startOfSelection() - item->firstInFrame(), item->itemText.selectionLength());
}

PyObject *scribus_deletetext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame() && !item->isPathText())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot delete text from a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem_TextFrame* tf_item = item->asTextFrame();
	if (tf_item)
	{
		if (tf_item->HasSel)
			tf_item->deleteSelectedTextFromFrame();
		else
			tf_item->itemText.clear();
	}
	else
	{
		//Path text cannot have selected text, :( FIXME
		if (item->isPathText())
			item->itemText.clear();
	}
	Py_RETURN_NONE;
}

PyObject *scribus_settextfill(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString color;
	if (!PyArg_ParseTuple(args, "es|es", "utf-8", color.ptr(), "utf-8", name.ptr()))
	{
		name.resetDontFree();
		color.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame() && !item->isPathText())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text fill on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	ApplyCharstyleHelper<QString>(item, QString::fromUtf8(color.c_str())).apply(&CharStyle::setFillColor, 0, item->itemText.length());
	Py_RETURN_NONE;
}

PyObject *scribus_settextstroke(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	PyESString color;
	if (!PyArg_ParseTuple(args, "es|es", "utf-8", color.ptr(), "utf-8", name.ptr()))
	{
		name.resetDontFree();
		color.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame() && !item->isPathText())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text stroke on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	ApplyCharstyleHelper<QString>(item, QString::fromUtf8(color.c_str())).apply(&CharStyle::setStrokeColor, 0, item->itemText.length());
	Py_RETURN_NONE;
}


PyObject *scribus_settextscalingh(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double sc;
	if (!PyArg_ParseTuple(args, "d|es", &sc, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (sc < 10)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Character scaling out of bounds, must be >= 10","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set character scaling on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetScaleH(qRound(sc * 10), &tmpSelection);
	doc->appMode = oldAppMode;
		
	Py_RETURN_NONE;
}


PyObject *scribus_settextscalingv(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	double sc;
	if (!PyArg_ParseTuple(args, "d|es", &sc, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if (sc < 10)
	{
		PyErr_SetString(PyExc_ValueError, QObject::tr("Character scaling out of bounds, must be >= 10","python error").toUtf8().constData());
		return nullptr;
	}
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set character scaling on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}

	ScribusDoc* doc = ScCore->primaryMainWindow()->doc;
	int oldAppMode = ScCore->primaryMainWindow()->doc->appMode;

	Selection tmpSelection(nullptr, false);
	tmpSelection.addItem(item);
	if (item->HasSel)
		doc->appMode = modeEdit;
	doc->itemSelection_SetScaleV(qRound(sc * 10), &tmpSelection);
	doc->appMode = oldAppMode;
		
	Py_RETURN_NONE;
}

PyObject *scribus_settextshade(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	int w;
	if (!PyArg_ParseTuple(args, "i|es", &w, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	if ((w < 0) || (w > 100))
		Py_RETURN_NONE;

	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame() && !item->isPathText())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot set text shade on a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	ApplyCharstyleHelper<double>(item, w).apply(&CharStyle::setFillShade, 0, item->itemText.length());
	Py_RETURN_NONE;
}

PyObject *scribus_linktextframes(PyObject* /* self */, PyObject* args)
{
	PyESString name1;
	PyESString name2;

	if (!PyArg_ParseTuple(args, "eses", "utf-8", name1.ptr(), "utf-8", name2.ptr()))
	{
		name1.resetDontFree();
		name2.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *fromItem = GetUniqueItem(QString::fromUtf8(name1.c_str()));
	if (fromItem == nullptr)
		return nullptr;
	PageItem *toItem = GetUniqueItem(QString::fromUtf8(name2.c_str()));
	if (toItem == nullptr)
		return nullptr;
	if (!(fromItem->isTextFrame()) || !(toItem->isTextFrame()))
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Can only link text frames.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (fromItem->nextInChain() != nullptr)
	{
		PyErr_SetString(ScribusException, QObject::tr("Source frame is linked to another frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (toItem->prevInChain() != nullptr)
	{
		PyErr_SetString(ScribusException, QObject::tr("Target frame is linked to by another frame.", "python error").toUtf8().constData());
		return nullptr;
	}
	if (toItem == fromItem)
	{
		PyErr_SetString(ScribusException, QObject::tr("Source and target are the same object.", "python error").toUtf8().constData());
		return nullptr;
	}
	// references to the others boxes
	fromItem->link(toItem);
	ScCore->primaryMainWindow()->view->DrawNew();
	// enable 'save icon' stuff
	ScCore->primaryMainWindow()->slotDocCh();

	Py_RETURN_NONE;
}

PyObject *scribus_unlinktextframes(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot unlink a non-text frame.","python error").toUtf8().constData());
		return nullptr;
	}
	// only linked
	if (item->prevInChain() == nullptr)
	{
		PyErr_SetString(ScribusException, QObject::tr("Object is not a linked text frame, can't unlink.","python error").toUtf8().constData());
		return nullptr;
	}
/*	if (item->NextBox == 0)
	{
		PyErr_SetString(ScribusException, QObject::tr("Object the last frame in a series, can't unlink. Unlink the previous frame instead.","python error").toUtf8().constData());
		return nullptr;
	}
	*/
/*	PageItem* nextbox = item->NextBox;

	while (nextbox != 0)
	{
		uint a = nextbox->itemText.count();
		for (uint s=0; s<a; ++s)
			item->itemText.append(nextbox->itemText.take(0));
		nextbox = nextbox->NextBox;
	} // while
	uint a2 = item->itemText.count();
	for (uint s = 0; s < a2; ++s)
		item->BackBox->itemText.append(item->itemText.take(0));
*/
	item->prevInChain()->unlink();
	// enable 'save icon' stuff
	ScCore->primaryMainWindow()->slotDocCh();
	ScCore->primaryMainWindow()->view->DrawNew();

	Py_RETURN_NONE;
}

/*
 * Convert the selected text frame to outlines.
 *
 * 2004-09-07 (Craig Ringer)
 * 2004-09-14 pv frame type, optional frame name param
 */
PyObject *scribus_outlinetext(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Cannot convert a non-text frame to outlines.","python error").toUtf8().constData());
		return nullptr;
	}
	if (item->invalid)
		item->layout();
	ScCore->primaryMainWindow()->view->deselectItems(true);
	ScCore->primaryMainWindow()->view->selectItem(item);
	ScCore->primaryMainWindow()->view->TextToPath();

	Py_RETURN_NONE;
}

PyObject *scribus_istextoverflowing(PyObject * self, PyObject* args, PyObject* kw)
{
	int nolinks = 0;
	PyESString name;
	char *kwargs[] = {const_cast<char*>("name"), const_cast<char*>("nolinks"), nullptr};
	if (!PyArg_ParseTupleAndKeywords(args, kw, "|esi", kwargs, "utf-8", name.ptr(), &nolinks))
	{
		// Some old python versions leave bad pointers behind when an argument parsing error occurs
		// so we can't free the memory safely in this case
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Only text frames can be checked for overflowing", "python error").toUtf8().constData());
		return nullptr;
	}
	/* original solution
	if (item->itemText.count() > item->MaxChars)
	return PyBool_FromLong(static_cast<long>(true));
	return PyBool_FromLong(static_cast<long>(false)); */
	/*
	 uint firstFrame = 0;
	if (nolinks)
		firstFrame = item->itemText.count();
	uint chars = item->itemText.count();
	uint maxchars = item->MaxChars;
	while (item->NextBox != 0) {
		item = item->NextBox;
		chars += item->itemText.count();
		maxchars += item->MaxChars;
	}
	// no overrun
	if (nolinks)
		return PyLong_FromLong(maxchars - firstFrame);

	if (maxchars > chars)
		return PyLong_FromLong(0);
	// number of overrunning letters
	return PyLong_FromLong(static_cast<long>(chars - maxchars));
	 */
	// refresh overflow information
	item->invalidateLayout();
	item->layout();
	return PyLong_FromLong(static_cast<long>(item->frameOverflows()));
}

/*
 * Does hyphenation on the given text frame.
 * 08.12.2007: Joachim Neu
 */
PyObject *scribus_hyphenatetext(PyObject*, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Can only hyphenate text frame", "python error").toUtf8().constData());
		return nullptr;
	}
	ScCore->primaryMainWindow()->doc->docHyphenator->slotHyphenate(item);
	return PyBool_FromLong(1);
}

/*
 * Does dehyphenation on the given text frame.
 * 13.12.2007: Joachim Neu
 */
PyObject *scribus_dehyphenatetext(PyObject*, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Can only dehyphenate text frame", "python error").toUtf8().constData());
		return nullptr;
	}
	ScCore->primaryMainWindow()->doc->docHyphenator->slotDeHyphenate(item);
	return PyBool_FromLong(1);
}

PyObject *scribus_setpdfbookmark(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	bool toggle;
	if (!PyArg_ParseTuple(args, "b|es", &toggle, "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Can't set bookmark on a non-text frame", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->isBookmark == toggle)
		Py_RETURN_NONE;
	if (toggle)
	{
		item->setIsAnnotation(false);
		ScCore->primaryMainWindow()->AddBookMark(item);
	}
	else
		ScCore->primaryMainWindow()->DelBookMark(item);
	item->isBookmark = toggle;

	Py_RETURN_NONE;
}

PyObject *scribus_ispdfbookmark(PyObject* /* self */, PyObject* args)
{
	PyESString name;
	if (!PyArg_ParseTuple(args, "|es", "utf-8", name.ptr()))
	{
		name.resetDontFree();
		return nullptr;
	}
	if (!checkHaveDocument())
		return nullptr;
	const PageItem *item = GetUniqueItem(QString::fromUtf8(name.c_str()));
	if (item == nullptr)
		return nullptr;
	if (!item->isTextFrame())
	{
		PyErr_SetString(WrongFrameTypeError, QObject::tr("Can't get info from a non-text frame", "python error").toUtf8().constData());
		return nullptr;
	}
	if (item->isBookmark)
		return PyBool_FromLong(1);
	return PyBool_FromLong(0);
}

/*! HACK: this removes "warning: 'blah' defined but not used" compiler warnings
with header files structure untouched (docstrings are kept near declarations)
PV */
void cmdtextdocwarnings()
{
	QStringList s;
	s << scribus_dehyphenatetext__doc__
	  << scribus_deletetext__doc__
	  << scribus_getalltext__doc__
	  << scribus_getcolumngap__doc__
	  << scribus_getcolumns__doc__
	  << scribus_getfirstlineoffset__doc__
	  << scribus_getfirstlinkedframe__doc__
	  << scribus_getfont__doc__
	  << scribus_getfontfeatures__doc__
	  << scribus_getfontsize__doc__
	  << scribus_getframetext__doc__
	  << scribus_getlastlinkedframe__doc__
	  << scribus_getlinespacing__doc__
	  << scribus_getnextlinkedframe__doc__
	  << scribus_getprevlinkedframe__doc__
	  << scribus_gettext__doc__ // Deprecated
	  << scribus_gettextcolor__doc__
	  << scribus_gettextdistances__doc__
	  << scribus_gettextlength__doc__
	  << scribus_gettextlines__doc__
	  << scribus_gettextshade__doc__
	  << scribus_gettextverticalalignment__doc__
	  << scribus_hyphenatetext__doc__
	  << scribus_inserthtmltext__doc__
	  << scribus_inserttext__doc__
	  << scribus_ispdfbookmark__doc__
	  << scribus_istextoverflowing__doc__
	  << scribus_layouttext__doc__
	  << scribus_layouttextchain__doc__
	  << scribus_linktextframes__doc__
	  << scribus_outlinetext__doc__
	  << scribus_selectframetext__doc__
	  << scribus_selecttext__doc__
	  << scribus_setcolumngap__doc__
	  << scribus_setcolumns__doc__
	  << scribus_setdirection__doc__
	  << scribus_setfirstlineoffset__doc__
	  << scribus_setfont__doc__
	  << scribus_setfontfeatures__doc__
	  << scribus_setfontsize__doc__
	  << scribus_setlinespacing__doc__
	  << scribus_setlinespacingmode__doc__
	  << scribus_setpdfbookmark__doc__
	  << scribus_settextdistances__doc__
	  << scribus_settext__doc__
	  << scribus_settextalignment__doc__
	  << scribus_settextfill__doc__
	  << scribus_settextscalingh__doc__
	  << scribus_settextscalingv__doc__
	  << scribus_settextshade__doc__
	  << scribus_settextstroke__doc__
	  << scribus_settextverticalalignment__doc__
	  << scribus_tracetext__doc__
	  << scribus_unlinktextframes__doc__;
}
