tonycpsu / panwid

A collection of widgets for urwid.
GNU Lesser General Public License v2.1
116 stars 8 forks source link

Access object behind row #18

Closed white-gecko closed 3 years ago

white-gecko commented 3 years ago

How can I access the object behind a row and call a method on it, that will also update the data displayed in the table.

I have created the following MWE to show what I want.

Checked on current unstable branch (17d1fcab44a47e486320b140654976de4546265c)

#!/usr/bin/env python3

import urwid
from panwid.datatable import *
from panwid.listbox import ScrollingListBox
from urwid_utils.palette import Palette

def unhandled_input(key):
    if key in ("q", "Q"):
        raise urwid.ExitMainLoop()

class my_object():
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
        self.this = self

    def action(self):
        self.foo += 1

class MyDataTable(DataTable):
    def __init__(self, *args, **kwargs):
        super(MyDataTable, self).__init__(*args, **kwargs)

    def keypress(self, size, key):
        if key == "r":
            self.refresh()
        elif key == "s":
            self.selection.data_source.this.action()
            self.selection.update()
        else:
            return super(MyDataTable, self).keypress(size, key)

def main():
    entries = ScrollingListBox.get_palette_entries()
    entries.update(DataTable.get_palette_entries())
    palette = Palette("default", **entries)

    screen = urwid.raw_display.Screen()
    screen.set_terminal_properties(256)

    data_table = MyDataTable(
        columns = [
            DataTableColumn("foo"),
            DataTableColumn("bar")
        ],
        data=[
            my_object(foo=1, bar="a"),
            my_object(foo=2, bar="b")
        ],
        cell_selection=True,
        with_scrollbar=True,
    )

    loop = urwid.MainLoop(
        urwid.Frame(data_table),
        palette = palette,
        screen=screen,
        unhandled_input=unhandled_input
    )
    loop.run()

if __name__ == "__main__":
    main()

Currently the best I can do is to set self.this = self and then call this.action() subsequently I need to perform a complete refresh (key r) to display the updated data.

Did I miss something, or how can I access the object behind the row.

white-gecko commented 3 years ago

Or should I better use query for the whole situation?

tonycpsu commented 3 years ago

The row's data_source is just a reference to the object that was used to create the dataframe row, while the values that are displayed in the table come from the dataframe object (data_table.df). Early on in the datatable module's development it was more like what you're looking for where the objects themselves serve as the storage for the values, and I can see the case for that, but it ended up being a significant performance issue as tables got larger.

As things are right now, the workaround is to update the dataframe separately, e.g.:

        elif key == "s":
            self.selection.data_source.this.action()
            self.df.set(self.selection.index, "foo", self.selection.data_source.foo)
            self.refresh()
white-gecko commented 3 years ago

Ok, so if this is outside of the scope and contradicts your design decisions, its fine. I will use the proposed workaround (:+1:)

white-gecko commented 3 years ago

By the way

in your workaround

        elif key == "s":
            self.selection.data_source.this.action()
            self.df.set(self.selection.index, "foo", self.selection.data_source.foo)
            self.refresh()

the line self.df.set(self.selection.index, "foo", self.selection.data_source.foo) doesn't do anything relevant, as self.selection.data_source.foo is not updated at that time.

I've changed my workaround to

        elif key == "s":
            self.selection.data.this.action()
            self.df.set(self.selection.index, "foo", self.selection.data.this.foo)
            self.selection.update()