Serialize Gtk TreeStore / ListStore using JSON
Asked Answered
W

1

13

I made a new example which shows much better what I am trying to do. The new example gives the following ouput. Is there a way that the data can go into the respective store key (the {} brackets)?

{
    "copy": [
        [
            [
                5.0,
                8.0,
                9.0
            ]
        ],
        [
            [
                4.0,
                0.0,
                1.0
            ]
        ]
    ],
    "name": "dataset1",
    "sets": [
        {
            "store": {},
            "type": "vector"
        },
        {
            "store": {},
            "type": "vector"
        }
    ]
}

New example

from gi.repository import Gtk
import json
import random

class Vector(object):

    def __init__(self, data):
        self.store = Gtk.ListStore(float, float, float)
        self.store.append([data[0], data[1], data[2]])
        self.type = "vector"

    def return_data(self):
        store_data = []

        def iterate_over_data(model, path, itr):
            row = model[path]
            store_data.append([row[0], row[1], row[2]])

        self.store.foreach(iterate_over_data)

        return store_data

class DataSet(object):

    def __init__(self, name):
        self.name = name
        self.sets = []

    def add_vector(self):
        data = [random.randint(0,9) for x in range(3)]
        self.sets.append(Vector(data))

    def to_json(self):
        self.copy = []
        for s in self.sets:
            self.copy.append(s.return_data())

        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

obj1 = DataSet("dataset1")
for x in range(2):
    obj1.add_vector()

print(obj1.to_json())

Old example

I am currently figuring out how to serialize a Gtk ListStore that is nested in a Gtk TreeStore. I got a small example to work, but am not sure if this approach will scale for programs that have more data attached (For example the layer object could hold a color or a date of creation). Is there maybe another way to to this?

My current approach is to gather the data in list and dictionary form myself and then just create the JSON-dump. I have the feeling that this would be rather difficult to maintain if I need to attach 25 values to each layer-object.

from gi.repository import Gtk, Gdk
import json
import random


class LayerTreeView(Gtk.TreeView):

    def __init__(self, store):
        Gtk.TreeView.__init__(self, store)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Name", renderer, text=0)
        self.append_column(column)


class DataTreeView(Gtk.TreeView):

    def __init__(self, store):
        Gtk.TreeView.__init__(self, store)
        self.store = store
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Data", renderer, text=0)
        self.append_column(column)


class MainWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="TreeView Serialize")
        self.connect("delete-event", Gtk.main_quit)
        self.set_border_width(10)
        self.set_default_size(400, 300)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True)
        self.add(vbox)

        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)

        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        button = Gtk.Button("Cut")
        button.connect("clicked", self.on_cut_clicked)
        hbox.pack_start(button, True, True, 0)

        button = Gtk.Button(stock=Gtk.STOCK_COPY)
        button.connect("clicked", self.on_copy_clicked)
        hbox.pack_start(button, True, True, 0)

        button = Gtk.Button(stock=Gtk.STOCK_PASTE)
        button.connect("clicked", self.on_paste_clicked)
        hbox.pack_start(button, True, True, 0)
        vbox.add(hbox)

        self.layer_store = Gtk.TreeStore(str, object, object)
        self.layer_view = LayerTreeView(self.layer_store)

        self.layer_sw = Gtk.ScrolledWindow()
        self.data_sw = Gtk.ScrolledWindow()
        self.layer_sw.add(self.layer_view)

        treebox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, expand=True)
        treebox.pack_start(self.layer_sw, True, True, 0)
        treebox.pack_start(self.data_sw, True, True, 0)
        vbox.add(treebox)

        self.select = self.layer_view.get_selection()
        self.select.connect("changed", self.on_selection_changed)

        self.add_test_data()

    def add_test_data(self):
        for x in range(3):
            data_store = Gtk.ListStore(str)
            data_view = DataTreeView(data_store)
            for y in range(5):
                data_store.append([str(y+x)])
            self.layer_store.append(None, ["Data {}".format(x), data_store, data_view])

    def on_selection_changed(self, selection):
        """
        When layer is switched load respective data
        """
        model, treeiter = selection.get_selected()
        if treeiter != None:
            data_view = model[treeiter][2]
            child = self.data_sw.get_child()
            if child != None:
                self.data_sw.remove(self.data_sw.get_child())
            self.data_sw.add(data_view)
            self.show_all()

    def on_cut_clicked(self, button):
        pass

    def on_copy_clicked(self, button):
        copy_list = ["safe-to-paste"]
        data_dict = {}
        for row in self.layer_store:
            name = row[0]
            data_obj = row[1]
            value_list = []
            for datarow in data_obj:
                value = datarow[0]
                value_list.append(value)
            data_dict[name] = value_list

        copy_list.append(data_dict)
        data = json.dumps(copy_list)
        self.clipboard.set_text(data, -1)

    def on_paste_clicked(self, button):
        paste_str = self.clipboard.wait_for_text()
        try:
            parse = json.loads(paste_str)
            json_str = True
        except:
            json_str = False

        if json_str is False:
            return

        keyword = parse[0]
        if keyword != "safe-to-paste":
            return

        data_dict = parse[1]
        for x in data_dict:
            data_list = data_dict[x]
            data_store = Gtk.ListStore(str)
            data_view = DataTreeView(data_store)
            for y in data_list:
                data_store.append([str(y)])
            self.layer_store.append(None, [x, data_store, data_view])

win = MainWindow()
win.show_all()
Gtk.main()
Went answered 26/4, 2015 at 12:9 Comment(2)
Do you use the clipboard and the json for anything else? Otherwise, there are probably simpler approaches.Josuejosy
I would also like to do loading/saving and drag&drop using this. But I just started working on this, and am unaware of better solutions.Went
J
3

I have an improved version of your code with dict comprehension and @staticmethod that makes the signal callbacks more readable and shorter. Nevertheless, this does not really solve your problem as it still generates the json manually. If the ListStore gets more complex, it would probably be better to let the DataListStore class generate its own json with a corresponding method.

from gi.repository import Gtk, Gdk
import json


class LayerTreeView(Gtk.TreeView):

    def __init__(self, store):
        Gtk.TreeView.__init__(self, store)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Name", renderer, text=0)
        self.append_column(column)


class DataTreeView(Gtk.TreeView):

    def __init__(self):
        Gtk.TreeView.__init__(self)
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Data", renderer, text=0)
        self.append_column(column)

class DataListStore(Gtk.ListStore):

    @staticmethod
    def from_json(*args, values=[]):
        store = DataListStore(*args)
        for value in values:
            store.append((value,))
        return store


class MainWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="TreeView Serialize")
        self.connect("delete-event", Gtk.main_quit)
        self.set_border_width(10)
        self.set_default_size(400, 300)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True)
        self.add(vbox)

        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)

        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        button = Gtk.Button("Cut")
        button.connect("clicked", self.on_cut_clicked)
        hbox.pack_start(button, True, True, 0)

        button = Gtk.Button(stock=Gtk.STOCK_COPY)
        button.connect("clicked", self.on_copy_clicked)
        hbox.pack_start(button, True, True, 0)

        button = Gtk.Button(stock=Gtk.STOCK_PASTE)
        button.connect("clicked", self.on_paste_clicked)
        hbox.pack_start(button, True, True, 0)
        vbox.add(hbox)

        self.layer_store = Gtk.TreeStore(str, object)
        self.layer_view = LayerTreeView(self.layer_store)
        self.data_view = DataTreeView()

        layer_sw = Gtk.ScrolledWindow()
        layer_sw.add(self.layer_view)
        data_sw = Gtk.ScrolledWindow()
        data_sw.add(self.data_view)

        treebox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, expand=True)
        treebox.pack_start(layer_sw, True, True, 0)
        treebox.pack_start(data_sw, True, True, 0)
        vbox.add(treebox)

        select = self.layer_view.get_selection()
        select.connect("changed", self.on_selection_changed)

        self.add_test_data()

    def add_test_data(self):
        for x in range(3):
            data_list = [str(y+x) for y in range(5)]
            self.layer_store.append(None, ["Data {}".format(x), data_list])

    def on_selection_changed(self, selection):
        """
        When layer is switched load respective data
        """
        model, treeiter = selection.get_selected()
        if treeiter != None:
            self.data_view.set_model(
                DataListStore.from_json(str, values=model[treeiter][1])
            )

    def on_cut_clicked(self, button):
        pass

    def on_copy_clicked(self, button):
        copy_list = [
            'safe-to-paste',
            {row[0]: row[1] for row in self.layer_store},
        ]
        data = json.dumps(copy_list)
        self.clipboard.set_text(data, -1)

    def on_paste_clicked(self, button):
        paste_str = self.clipboard.wait_for_text()
        try:
            parse = json.loads(paste_str)
        except:
            return

        if parse[0] != "safe-to-paste":
            return

        data_dict = parse[1]
        for x in data_dict:
            self.layer_store.append(None, [x, data_dict[x]])

win = MainWindow()
win.show_all()
Gtk.main()
Josuejosy answered 6/5, 2015 at 10:4 Comment(2)
Really nice improvement. Do you think it would be better to store the data in a dictionary and repopulate the liststore from that dictionary? I was thinking that using native Python datatypes might just be easier, than to teach the serialization how to deal with the Gtk Liststore?Went
The json would make things easier if you want to save your data to a file but right now it is not very useful.Josuejosy

© 2022 - 2024 — McMap. All rights reserved.