~fabrixxm/confy

00e2cc575177d49660f6d12e5f474436eeffee64 — fabrixxm 7 months ago c72b1e3
PageEvent: build listbox in chunk using timeout

let the ui to be somewhat responsive while the listbox is built
giving back the control to the mainloop every N rows added.
2 files changed, 63 insertions(+), 15 deletions(-)

M src/pages.py
M src/utils.py
M src/pages.py => src/pages.py +37 -15
@@ 16,6 16,7 @@
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import datetime
import html
from dataclasses import dataclass

from dateutil.tz import UTC



@@ 34,7 35,7 @@ from .widgets import VBox

from . import models
from .utils import convert_markdown_links, clean_markup, margin

from .utils import InterruptibleTimeout

@Gtk.Template(resource_path="/net/kirgroup/confy/pages/info.ui")
class PageInfo(Gtk.Box):


@@ 210,6 211,17 @@ class PageList(Gtk.Box):
        self.emit("row-activated")


@dataclass
class ListBuilderCtx:
    lastobj = None
    listbox = None
    data = []
    guard = None

    def __repr__(self):
        return f"<ListBuilderCtx lastobj={self.lastobj}, listbox={self.listbox}>"


@Gtk.Template(resource_path="/net/kirgroup/confy/pages/events.ui")
class PageEvents(Gtk.Box):
    __gtype_name__ = "CPageEvents"


@@ 225,6 237,8 @@ class PageEvents(Gtk.Box):
    search_button = Gtk.Template.Child()
    searchbar = Gtk.Template.Child()

    timeout = InterruptibleTimeout()

    def get_value(self):
        return self._value



@@ 333,30 347,38 @@ class PageEvents(Gtk.Box):
    def update(self, *args):
        data = self.get_objects()

        self.timeout.stop()

        # empty box
        w = self.pagebox.get_last_child()
        while w:
            self.pagebox.remove(w)
            w = self.pagebox.get_last_child()

        lastobj = None
        listbox = None
        for idx, obj in enumerate(data):
            header = self.build_header(obj, lastobj)
        ctx = ListBuilderCtx()
        ctx.data = data
        ctx.guard = self.timeout = InterruptibleTimeout(50, self.build_list, ctx)

    def build_list(self, ctx):
        for idx, obj in enumerate(ctx.data):
            if ctx.guard.must_stop():
                return False

            header = self.build_header(obj, ctx.lastobj)
            if header:
                if listbox:
                    self.pagebox.append(listbox)
                    listbox = None
                ctx.listbox = None
                self.pagebox.append(header)

            row = self.build_row(obj)
            if listbox is None:
                listbox = self.build_listbox()
            listbox.append(row)
            lastobj = obj

        if listbox:
            self.pagebox.append(listbox)
            if ctx.listbox is None:
                ctx.listbox = self.build_listbox()
                self.pagebox.append(ctx.listbox)
            ctx.listbox.append(row)
            ctx.lastobj = obj
            if idx >  10:
                return True

        return False

    def build_listbox(self):
        lb = Gtk.ListBox(margin_bottom=20)

M src/utils.py => src/utils.py +26 -0
@@ 1,6 1,8 @@
import re
import html

from gi.repository import GLib

def convert_markdown_links(text):
    """converts markdown links to html hyperlinks"""
    return re.sub("\[([^]]+?)\]\(([^)\s]+?)(?:\s.*?)?\)", r'<a href="\2">\1</a>', text)


@@ 27,3 29,27 @@ def clean_markup(text):
def margin(val):
    return dict(margin_end=val, margin_start=val, margin_top=val, margin_bottom=val)


class InterruptibleTimeout:
    def __init__(self, intervall=0, callback=None, data=None):
        self._running = False
        self._requested_stop = True
        self._callback = callback
        if callback:
            self._running = True
            self._requested_stop = False
            GLib.timeout_add(intervall, self._on_timeout, data)

    def _on_timeout(self, data):
        if self._requested_stop:
            res = False
        else:
            res = self._callback(data)
        self._running = res
        return res

    def stop(self):
        self._requested_stop = True

    def must_stop(self):
        return self._requested_stop