File: articles_listview.py

package info (click to toggle)
gnome-feeds 2.2.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,520 kB
  • sloc: python: 5,369; sh: 93; xml: 28; makefile: 2
file content (173 lines) | stat: -rw-r--r-- 6,061 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
from typing import Any, Callable, Literal, Optional
from gi.repository import Gtk
from concurrent.futures import ThreadPoolExecutor
from gfeeds.feeds_manager import FeedsManager
from gfeeds.feed_item import FeedItem
from gfeeds.sidebar_row import SidebarRow


class CommonListScrolledWin(Gtk.ScrolledWindow):
    def __init__(self):
        super().__init__(
            hscrollbar_policy=Gtk.PolicyType.NEVER,
            vscrollbar_policy=Gtk.PolicyType.AUTOMATIC
        )

        self.feedman = FeedsManager()
        self.articles_store = self.feedman.article_store

        self.fetch_image_thread_pool = ThreadPoolExecutor(
            max_workers=self.articles_store.confman.nconf.max_refresh_threads
        )

        # API bindings
        self.empty = self.articles_store.empty
        self.populate = self.articles_store.populate
        self.selected_feeds = self.articles_store.selected_feeds
        self.invalidate_filter = self.articles_store.invalidate_filter
        self.invalidate_sort = self.articles_store.invalidate_sort
        self.set_search_term = self.articles_store.set_search_term
        self.set_selected_feeds = self.articles_store.set_selected_feeds
        self.selected_feeds = self.articles_store.selected_feeds
        self.add_new_items = self.articles_store.add_new_items
        self.remove_items = self.articles_store.remove_items
        self.set_all_read_state = self.articles_store.set_all_read_state
        self.all_items_changed = self.articles_store.all_items_changed

    def shutdown_thread_pool(self):
        self.fetch_image_thread_pool.shutdown(wait=False, cancel_futures=True)

    def __del__(self):
        self.shutdown_thread_pool()


class ArticlesListView(CommonListScrolledWin):
    def __init__(self):
        super().__init__()

        # listview and factory
        self.factory = Gtk.SignalListItemFactory()
        self.factory.connect('setup', self._on_setup_listitem)
        self.factory.connect('bind', self._on_bind_listitem)
        self.selection = Gtk.SingleSelection.new(self.articles_store)
        self.selection.set_autoselect(False)
        self.list_view = Gtk.ListView.new(self.selection, self.factory)
        self.list_view.set_vscroll_policy(Gtk.ScrollablePolicy.NATURAL)

        self.list_view.get_style_context().add_class('navigation-sidebar')
        self.set_child(self.list_view)
        self.selection.connect('notify::selected-item', self.on_activate)

    def connect_activate(self, func):
        self.selection.connect(
            'notify::selected-item',
            lambda *_:
                func(self.selection.get_selected_item())
        )

    def get_selected(self) -> int:
        return self.selection.get_selected()

    def get_selected_item(self) -> FeedItem:
        return self.articles_store[self.get_selected()]

    def select_row(self, index):
        self.selection.select_item(index, True)

    # for both select next and prev; increment can be +1 or -1
    def __select_successive(self, increment: Literal[1, -1]):
        index = self.get_selected()
        if increment == -1 and index == 0:
            return
        if index == Gtk.INVALID_LIST_POSITION:
            index = -1 * increment  # so that 0 is selected
        self.select_row(index + increment)

    def select_next(self, *_):
        self.__select_successive(1)

    def select_prev(self, *_):
        self.__select_successive(-1)

    def on_activate(self, *_):
        feed_item = self.selection.get_selected_item()
        if not feed_item:
            return
        feed_item.read = True

    def _on_setup_listitem(
            self, _: Gtk.ListItemFactory, list_item: Gtk.ListItem
    ):
        row_w = SidebarRow(self.fetch_image_thread_pool)
        list_item.set_child(row_w)
        # otherwise row gets garbage collected
        list_item.row_w = row_w  # type: ignore

    def _on_bind_listitem(
            self, _: Gtk.ListItemFactory, list_item: Gtk.ListItem
    ):
        row_w: SidebarRow = list_item.get_child()  # type: ignore
        feed_item: FeedItem = list_item.get_item()  # type: ignore
        row_w.set_feed_item(feed_item)


class ArticlesListBox(CommonListScrolledWin):
    def __init__(self):
        super().__init__()

        self.listbox = Gtk.ListBox(
            vexpand=True, selection_mode=Gtk.SelectionMode.SINGLE,
            activate_on_single_click=True
        )
        self.listbox.get_style_context().add_class('navigation-sidebar')
        self.listbox.bind_model(self.articles_store, self._create_row, None)
        self.set_child(self.listbox)

    def connect_activate(self, func: Callable[[Optional[FeedItem]], Any]):
        self.listbox.connect(
            'row-activated',
            lambda _, row, *__:
                func(row.get_child().feed_item)
                if row else func(None)
        )

    def _create_row(
            self, feed_item: FeedItem, *_
    ) -> Gtk.Widget:
        row_w = SidebarRow(self.fetch_image_thread_pool)
        row_w.set_feed_item(feed_item)
        return row_w

    def get_selected_item(self):
        row = self.listbox.get_selected_row()
        if row:
            return row.get_child()
        return None

    def get_selected_index(self) -> int:
        row = self.listbox.get_selected_row()
        if not row:
            return -1
        index = row.get_index()
        if index is None:
            return 0
        return index

    # for both select next and prev; increment can be +1 or -1
    def __select_successive(self, increment: Literal[1, -1]):
        index = self.get_selected_index()
        if increment == -1 and index == 0:
            return
        if index < 0:
            index = -1 * increment  # so that 0 is selected
        target = self.listbox.get_row_at_index(index + increment)
        if not target:
            return
        self.listbox.select_row(target)
        target.activate()

    def select_next(self, *_):
        self.__select_successive(1)

    def select_prev(self, *_):
        self.__select_successive(-1)