View Issue Details

IDProjectCategoryView StatusLast Update
0017052ScribusScripterpublic2024-01-07 16:30
Reporterale Assigned Toale  
PrioritynormalSeverityfeatureReproducibilityN/A
Status closedResolutionfixed 
Product Version1.7.0.svn 
Fixed in Version1.6.1.svn 
Summary0017052: Scripter: add functions to get the items in a group
Descriptionit should be possible to manipulate the items in a group without ungrouping it.

two functions are needed:

- get all direct items in the group: getGroupItems()
- get all items in the group recursively: getAllGroupItems()
TagsNo tags attached.
PatchYes

Activities

ale

2023-11-18 21:34

manager   ~0050487

Last edited: 2023-11-18 21:34

i have created

getGroupItems(["name", recursive=False, type=0]) -> [dict]


i will be creating a few tests and if everything is fine i can soon propose a patch for it.

ale

2023-11-18 22:53

manager   ~0050488

here it is. with a test file that tests a few use cases and shows how to use the new function.
get-group-items.diff (4,969 bytes)   
diff --git a/scribus/plugins/scriptplugin/cmdmani.cpp b/scribus/plugins/scriptplugin/cmdmani.cpp
index ce0ea2ba3eee7cc56b3bba9b615ac95df82a8a9f..4c6ceec57659598fcbb232d2ca9193fcea6278c8 100644
--- a/scribus/plugins/scriptplugin/cmdmani.cpp
+++ b/scribus/plugins/scriptplugin/cmdmani.cpp
@@ -12,6 +12,7 @@ for which a new license (GPL+exception) is in place.
 #include "sctextstream.h"
 #include "selection.h"
 #include "undomanager.h"
+#include <QQueue>
 
 PyObject *scribus_loadimage(PyObject* /* self */, PyObject* args)
 {
@@ -451,6 +452,62 @@ PyObject *scribus_scalegroup(PyObject* /* self */, PyObject* args)
 	Py_RETURN_NONE;
 }
 
+PyObject *scribus_getGroupItems(PyObject * /*self*/, PyObject* args, PyObject* kw)
+{
+	if (!checkHaveDocument())
+		return nullptr;
+
+	char *name = const_cast<char*>("");
+	unsigned int recursive = 0;
+	unsigned int type = 0;
+	char* kwargs[] = {const_cast<char*>("name"), const_cast<char*>("recursive"), const_cast<char*>("type"), nullptr};
+	if (!PyArg_ParseTupleAndKeywords(args, kw, "|espi", kwargs, "utf-8", &name, &recursive, &type))
+		return nullptr;
+
+	PageItem *pageItem = GetUniqueItem(QString::fromUtf8(name));
+	if (pageItem == nullptr)
+		return nullptr;
+
+	if (!pageItem->isGroup())
+		return nullptr;
+
+	auto items = PyList_New(0);
+
+	QQueue<PageItem*> queue;
+	queue.enqueue(pageItem);
+
+	while (!queue.isEmpty())
+	{
+		const auto pageItem = queue.dequeue();
+		for (auto groupItem: pageItem->groupItemList)
+		{
+			if (recursive && groupItem->isGroup())
+			{
+				queue.enqueue(groupItem);
+				continue;
+			}
+			if (type == 0 || groupItem->itemType() == type) {
+				auto itemDict = PyDict_New();
+				PyDict_SetItem(itemDict,
+					PyUnicode_FromString("name"),
+					PyUnicode_FromString(groupItem->itemName().toUtf8().constData())
+				);
+				PyDict_SetItem(itemDict,
+					PyUnicode_FromString("type"),
+					PyLong_FromUnsignedLong((unsigned long) groupItem->itemType())
+				);
+				PyDict_SetItem(itemDict,
+					PyUnicode_FromString("order"),
+					PyLong_FromUnsignedLong((unsigned long) groupItem->uniqueNr)
+				);
+				PyList_Append(items, itemDict);
+			}
+		}
+	}
+
+	return items;
+}
+
 PyObject *scribus_getselectedobject(PyObject* /* self */, PyObject* args)
 {
 	int i = 0;
@@ -707,6 +764,7 @@ void cmdmanidocwarnings()
 	s << scribus_combinepolygons__doc__
 	  << scribus_deselectall__doc__
 	  << scribus_flipobject__doc__
+	  << scribus_getGroupItems__doc__
 	  << scribus_getselectedobject__doc__
 	  << scribus_groupobjects__doc__
 	  << scribus_islocked__doc__
diff --git a/scribus/plugins/scriptplugin/cmdmani.h b/scribus/plugins/scriptplugin/cmdmani.h
index e4d4253a74c17fd8b3f2c3de175932446c1439a3..f42d53bc83dbf2cc9463cc89b0c37451d78894c4 100644
--- a/scribus/plugins/scriptplugin/cmdmani.h
+++ b/scribus/plugins/scriptplugin/cmdmani.h
@@ -148,6 +148,21 @@ May raise ValueError if an invalid scale factor is passed.\n\
 /*! Scale group with object name */
 PyObject *scribus_scalegroup(PyObject * /*self*/, PyObject* args);
 
+/*! docstring */
+PyDoc_STRVAR(scribus_getGroupItems__doc__,
+QT_TR_NOOP("getGroupItems([\"name\", recursive=False, type=0]) -> [dict]\n\n\
+Return the list of items in the group.\n\
+\n\
+`recursive`: if True and some of the items are groups, also include their items (recursively).\n\
+`type`: if not 0, only retain items of this type.\n\
+\n\
+Each item is defined as a dictionary containing:\n\
+`{'name': str, 'type': int, 'order': int}`\n\
+\n\
+If \"name\" is not given the currently selected item is used."));
+/*! List the items in a group. */
+PyObject *scribus_getGroupItems(PyObject * /*self*/, PyObject* args, PyObject* kw);
+
 /*! docstring */
 PyDoc_STRVAR(scribus_loadimage__doc__,
 QT_TR_NOOP("loadImage(\"filename\" [, \"name\"])\n\
diff --git a/scribus/plugins/scriptplugin/scriptplugin.cpp b/scribus/plugins/scriptplugin/scriptplugin.cpp
index 2836dfb9672dd54a06ee5ff98f8084e54a9b9f7c..a5ca29ba30deefebb0b42374c4154a98eb65366b 100644
--- a/scribus/plugins/scriptplugin/scriptplugin.cpp
+++ b/scribus/plugins/scriptplugin/scriptplugin.cpp
@@ -364,6 +364,7 @@ PyMethodDef scribus_methods[] = {
 	{const_cast<char*>("getFontNames"), (PyCFunction)scribus_getfontnames, METH_NOARGS, tr(scribus_getfontnames__doc__)},
 	{const_cast<char*>("getFontSize"), scribus_getfontsize, METH_VARARGS, tr(scribus_getfontsize__doc__)},
 	{const_cast<char*>("getFrameText"), scribus_getframetext, METH_VARARGS, tr(scribus_getframetext__doc__)},
+	{const_cast<char*>("getGroupItems"), (PyCFunction)scribus_getGroupItems, METH_VARARGS|METH_KEYWORDS, tr(scribus_getGroupItems__doc__)},
 	{const_cast<char*>("getGuiLanguage"), (PyCFunction)scribus_getlanguage, METH_NOARGS, tr(scribus_getlanguage__doc__)},
 	{const_cast<char*>("getHGuides"), (PyCFunction)scribus_getHguides, METH_NOARGS, tr(scribus_getHguides__doc__)},
 	{const_cast<char*>("getImageColorSpace"), scribus_getimagecolorspace, METH_VARARGS, tr(scribus_getimagecolorspace__doc__) },
get-group-items.diff (4,969 bytes)   
getGroupItems.py (3,838 bytes)   
#!/usr/bin/env python3
import sys
import subprocess

try:
    import scribus
except ImportError:
    pass

def run_script() :
    scribus.newDocument(scribus.PAPER_A4_MM, (10, 10, 10, 10), \
        scribus.PORTRAIT, 1, scribus.UNIT_MILLIMETERS, scribus.PAGE_1, 0, 1)

    # Test a group with text and an image frames

    scribus.createText(10, 10, 20, 20, 'text-01')
    scribus.insertText('some text', 0, 'text-01')
    scribus.createImage(10, 30, 20, 20, 'image-02')
    group_name = scribus.groupObjects(['text-01', 'image-02'])
    scribus.setItemName('group-03', group_name)

    group_items = scribus.getGroupItems('group-03')
    assert sorted([item['name'] for item in group_items]) == \
        sorted(['text-01', 'image-02'])
    assert sorted([item['type'] for item in group_items]) == sorted([4, 2])

    group_items = scribus.getGroupItems('group-03', type=4)
    assert sorted([item['name'] for item in group_items]) == \
        ['text-01']

    scribus.unGroupObjects('group-03')

    # the group does not exist anymore: this should now fail
    try:
        group_items = scribus.getGroupItems('group-03')
        assert False
    except scribus.NoValidObjectError:
        assert True

    # Test recursivity with a group with text frame, an image one and a group

    scribus.createEllipse(10, 50, 20, 20, 'ellipse-04')
    scribus.createRect(10, 70, 20, 20, 'square-05')

    group_name = scribus.groupObjects(['ellipse-04', 'square-05'])
    scribus.setItemName('group-06', group_name)

    group_name = scribus.groupObjects(['text-01', 'image-02', 'group-06'])
    scribus.setItemName('group-07', group_name)

    group_items = scribus.getGroupItems('group-07')
    assert sorted([item['name'] for item in group_items]) == \
        sorted(['text-01', 'image-02', 'group-06'])

    group_items = scribus.getGroupItems('group-07', recursive=True)
    assert sorted([item['name'] for item in group_items]) == \
        sorted(['text-01', 'image-02', 'ellipse-04', 'square-05'])
    assert sorted([item['type'] for item in group_items]) == sorted([4, 2, 6, 6])

    group_items = scribus.getGroupItems('group-07')
    assert sorted([item['name'] for item in group_items]) == \
        sorted(['text-01', 'image-02', 'group-06'])

    group_items = scribus.getGroupItems('group-07', recursive=True, type=4)
    assert sorted([item['name'] for item in group_items]) == \
        ['text-01']

    group_items = scribus.getGroupItems('group-07', recursive=True, type=6)
    assert sorted([item['name'] for item in group_items]) == \
        sorted(['ellipse-04', 'square-05'])

    scribus.unGroupObjects('group-07')

    # Test getting by type with multiple of the same

    scribus.createText(10, 90, 20, 20, 'text-08')
    scribus.insertText('more text', 0, 'text-08')

    group_name = scribus.groupObjects(['text-01', 'image-02', 'text-08'])
    scribus.setItemName('group-09', group_name)

    group_items = scribus.getGroupItems('group-09', type=4)
    assert sorted([item['name'] for item in group_items]) == \
        ['text-01', 'text-08']

    # use the current selection
    
    scribus.deselectAll()
    scribus.selectObject('group-09')

    group_items = scribus.getGroupItems()
    assert sorted([item['name'] for item in group_items]) == \
        sorted(['text-01', 'image-02', 'text-08'])

    scribus.unGroupObjects('group-09')

    # scribus.saveDocAs('getGroupItems.sla') # enable for starting manual testing

def run_scribus():
    # if len(sys.argv) < 2:
    #     print(f'Usage: python3 {sys.argv[0]} filename')
    #     return

    call_args = ['scribus', '-g', '-py', sys.argv[0]]
    # call_args += ['--', sys.argv[1]]
    subprocess.call(call_args)

def main():
    try:
        scribus
        run_script()
    except NameError:
        run_scribus()

if __name__ == "__main__":
    main()
getGroupItems.py (3,838 bytes)   

ale

2023-12-27 22:04

manager   ~0050705

any reason why this function has not been considered?

i could have used it in a script that i'm writing today.

it's basically necessary, for every script that goes through all items on a page or in a document.

cbradney

2023-12-27 22:47

administrator   ~0050706

Nope, but we will review it. Thanks

jghali

2024-01-03 00:51

administrator   ~0050777

@ale, i don't think that the "order" key is set to what you want : groupItem->uniqueNr is just a unique ID used to identify an item in the document, not the item index in the group stack. In order to get the item order in the group stack, you should just iterate pageItem->groupItemList using a traditional index-based loop and not a range-for loop.

jghali

2024-01-03 21:58

administrator   ~0050790

If you want to be consistent with what scribus_getpageitems returns (and I think we should), you may also want to use Py_BuildValue instead of creating dictionaries.

ale

2024-01-04 17:40

manager   ~0050797

1. getPageItems seems to be using uniqueNr for the order and i copied that... but, indeed, both might be wrong. i did not check.
2. personally, for the results, i'd like to introduce more and more dictionaries rather than relying on the position in a list to give a meaning to the values...
    if both function should return the same "type" of result, i'd prefer getPageItems to return {0: name, 1: type, 2: order, 'name': name, 'type': type, 'order': order}, with a deprecation warning for the usage of 0, 1, and 2 as keys (that is, removing that from the docstring).

if you agree with what i wrote above, i can make a new patch, that also modifies getPageItems() and its docstring.

jghali

2024-01-07 16:16

administrator   ~0050844

So I committed a version of the patch which returns items as tuples for consistency with getPageItems(). I also fixed a bug where groups embedded inside groups would not be listed in the returned list.

For now I kept the uniqueNr returned for "order" as this is what getPageItems() already returns. But this uniqueNr is an indication of item creation order, and not an order in the page item list or in the group stack. So I am not sure how this "order" can be used currently.

ale

2024-01-07 16:30

manager   ~0050845

ok, thanks!

Issue History

Date Modified Username Field Change
2023-11-18 08:21 ale New Issue
2023-11-18 21:34 ale Note Added: 0050487
2023-11-18 21:34 ale Note Edited: 0050487
2023-11-18 22:53 ale Note Added: 0050488
2023-11-18 22:53 ale File Added: get-group-items.diff
2023-11-18 22:53 ale File Added: getGroupItems.py
2023-11-18 22:53 ale Summary scripter: add functions to get the items in a group => [PATCH] scripter: add functions to get the items in a group
2023-11-18 22:53 ale Patch No => Yes
2023-12-27 22:04 ale Note Added: 0050705
2023-12-27 22:47 cbradney Note Added: 0050706
2024-01-03 00:51 jghali Note Added: 0050777
2024-01-03 21:58 jghali Note Added: 0050790
2024-01-04 17:40 ale Note Added: 0050797
2024-01-07 16:16 jghali Note Added: 0050844
2024-01-07 16:16 jghali Summary [PATCH] scripter: add functions to get the items in a group => Scripter: add functions to get the items in a group
2024-01-07 16:17 jghali Assigned To => ale
2024-01-07 16:17 jghali Status new => resolved
2024-01-07 16:17 jghali Resolution open => fixed
2024-01-07 16:17 jghali Fixed in Version => 1.6.1.svn
2024-01-07 16:24 cbradney Status resolved => closed
2024-01-07 16:30 ale Note Added: 0050845