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)
|