Skip to content

Commit e8c6d50

Browse files
Sync gramps@maintenance/gramps61 with upstream/gramps-project (2026-07-04)
2 parents cbe5699 + 0d9e148 commit e8c6d50

21 files changed

Lines changed: 1772 additions & 2833 deletions

File tree

gramps/cli/user.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __init__(
7070
self.steps = 0
7171
self.current_step = 0
7272
self._input = input
73+
self._print_func = lambda msg: self._fileout.write(msg + "\n")
7374

7475
def yes(*args, **kwargs):
7576
return True

gramps/gen/filters/_genericfilter.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,13 @@ def apply(
277277
if id_list not given, all items in the database that
278278
match the filter are returned as a list of handles
279279
"""
280-
start_time = time.time()
280+
t0 = time.perf_counter()
281281
for rule in self.flist:
282282
rule.requestprepare(db, user)
283-
LOG.debug("Prepare time: %s seconds", time.time() - start_time)
283+
t1 = time.perf_counter()
284+
LOG.debug("Prepare time: %s seconds", t1 - t0)
285+
if user:
286+
user.notify("Prepare time: %.3fs" % (t1 - t0))
284287

285288
if self.logical_op == "and":
286289
apply_logical_op = self.and_test
@@ -291,8 +294,6 @@ def apply(
291294
else:
292295
raise Exception("invalid operator: %r" % self.logical_op)
293296

294-
start_time = time.time()
295-
296297
# build the starting set of possible_handles to be filtered
297298
possible_handles: Set[PrimaryObjectHandle]
298299
if id_list is not None:
@@ -312,20 +313,22 @@ def apply(
312313
else:
313314
possible_handles = set(self.get_all_handles(db))
314315

316+
t2 = time.perf_counter()
315317
res = self.apply_logical_op_to_all(db, possible_handles, apply_logical_op, user)
318+
t3 = time.perf_counter()
319+
LOG.debug("Apply time: %s seconds", t3 - t2)
320+
if user:
321+
user.notify("Apply time: %.3fs" % (t3 - t2))
316322

317323
# convert the filtered set of handles to the correct result type
318324
if id_list is not None and tupleind is not None:
319-
# convert the final_list of handles back to the corresponding final_list of tuples
320-
res = sorted(
321-
[handle_tuple[handle] for handle in res],
322-
key=lambda x: id_list.index(x),
323-
)
325+
# filter id_list to only matched handles, preserving original order
326+
res_set = set(res)
327+
res = [item for item in id_list if item[tupleind] in res_set]
324328
elif tree:
325329
# sort final_list into the same order as traversed by get_tree_cursor
326-
res = sorted(res, key=lambda x: tree_handles.index(x))
327-
328-
LOG.debug("Apply time: %s seconds", time.time() - start_time)
330+
tree_pos = {h: i for i, h in enumerate(tree_handles)}
331+
res = sorted(res, key=lambda x: tree_pos[x])
329332

330333
for rule in self.flist:
331334
rule.requestreset()

gramps/gen/user.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(self, callback=None, error=None, uistate=None, dbstate=None):
3939
self._fileout = sys.stderr # redirected to mocks by unit tests
4040
self.uistate = uistate
4141
self.dbstate = dbstate
42+
self._print_func = lambda msg: None
4243

4344
@abstractmethod
4445
def begin_progress(self, title, message, steps):
@@ -195,6 +196,20 @@ def info(self, msg1, infotext, parent=None, monospaced=False):
195196
Displays information to the user
196197
"""
197198

199+
def notify(self, msg):
200+
"""Display an informational message via the current print function."""
201+
self._print_func(msg)
202+
203+
@contextmanager
204+
def notifying(self, func):
205+
"""Temporarily override the print function for this User instance."""
206+
old = self._print_func
207+
self._print_func = func
208+
try:
209+
yield
210+
finally:
211+
self._print_func = old
212+
198213

199214
class User(UserBase):
200215
"""

gramps/gui/filters/sidebar/_sidebarfilter.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,33 @@ def clicked(self, obj):
161161
if not self.filter_is_ok():
162162
return
163163
self.uistate.set_busy_cursor(True)
164+
phase_msgs = []
164165
t1 = time.perf_counter()
165-
self.clicked_func()
166-
t2 = time.perf_counter()
167-
msg = _("Elapsed time: %.2fs") % (t2 - t1)
168-
self.msg_label.set_text(msg)
166+
167+
def render(searching=False):
168+
lines = list(phase_msgs)
169+
elapsed = time.perf_counter() - t1
170+
lines.append(_("Elapsed time: %.2fs") % elapsed)
171+
header = _("Searching...") + "\n" if searching else ""
172+
self.msg_label.set_text(header + "\n".join(lines))
173+
174+
def live_print(msg):
175+
phase_msgs.append(msg)
176+
render(searching=True)
177+
while Gtk.events_pending():
178+
Gtk.main_iteration()
179+
180+
def live_step():
181+
render(searching=True)
182+
183+
self.uistate.filter_print_func = live_print
184+
self.uistate.filter_step_func = live_step
185+
try:
186+
self.clicked_func()
187+
finally:
188+
self.uistate.filter_print_func = None
189+
self.uistate.filter_step_func = None
190+
render(searching=False)
169191
self.msg_label.get_style_context().remove_class("error")
170192
self.uistate.set_busy_cursor(False)
171193

gramps/gui/user.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@
2727
#
2828
# -------------------------------------------------------------------------
2929
import sys
30+
import time
3031

3132
# -------------------------------------------------------------------------
3233
#
3334
# Gramps modules
3435
#
3536
# -------------------------------------------------------------------------
37+
from gi.repository import Gtk
38+
3639
from gramps.gen import user
3740
from .utils import ProgressMeter
3841
from .dialog import (
@@ -61,6 +64,9 @@ def __init__(
6164
): # TODO User API: gen==cli==gui
6265
user.UserBase.__init__(self, callback, error, uistate, dbstate)
6366
self._progress = None
67+
self._last_pump = 0.0
68+
self._pulse_progress = False
69+
self._print_func = self._gui_print
6470

6571
if parent:
6672
self.parent = parent
@@ -69,6 +75,26 @@ def __init__(
6975
else:
7076
self.parent = None
7177

78+
def begin_filter_progress(self):
79+
"""Start pulsing the status bar progress indicator during filter application."""
80+
self._pulse_progress = True
81+
if self.uistate is not None:
82+
self.uistate.progress.show()
83+
84+
def end_filter_progress(self):
85+
"""Stop pulsing the status bar progress indicator after filter application."""
86+
self._pulse_progress = False
87+
if self.uistate is not None:
88+
self.uistate.progress.hide()
89+
90+
def _gui_print(self, msg):
91+
if self.uistate is not None:
92+
hook = getattr(self.uistate, "filter_print_func", None)
93+
if hook is not None:
94+
hook(msg)
95+
return
96+
self._fileout.write(msg + "\n")
97+
7298
def begin_progress(self, title, message, steps):
7399
"""
74100
Start showing a progress indicator to the user.
@@ -95,6 +121,18 @@ def step_progress(self):
95121
"""
96122
if self._progress:
97123
self._progress.step()
124+
else:
125+
now = time.monotonic()
126+
if now - self._last_pump > 0.1: # at most ~10 pumps/s
127+
self._last_pump = now
128+
if self._pulse_progress and self.uistate is not None:
129+
self.uistate.progress.pulse()
130+
if self.uistate is not None:
131+
step_hook = getattr(self.uistate, "filter_step_func", None)
132+
if step_hook is not None:
133+
step_hook()
134+
while Gtk.events_pending():
135+
Gtk.main_iteration()
98136

99137
def end_progress(self):
100138
"""

gramps/gui/views/treemodels/flatbasemodel.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
from ...user import User
7676
from gramps.gen.proxy.cache import CacheProxyDb
7777

78+
_ = glocale.translation.gettext
79+
7880
# -------------------------------------------------------------------------
7981
#
8082
# FlatNodeMap
@@ -625,12 +627,21 @@ def _rebuild_filter(self, ignore=None):
625627
allkeys = self.sort_keys()
626628
if self.search:
627629
ident = False
628-
if ignore is None:
629-
dlist = self.search.apply(cdb, allkeys, tupleind=1, user=self.user)
630-
else:
631-
dlist = self.search.apply(
632-
cdb, [k for k in allkeys if k[1] != ignore], tupleind=1
633-
)
630+
self.user.begin_filter_progress()
631+
try:
632+
if ignore is None:
633+
dlist = self.search.apply(
634+
cdb, allkeys, tupleind=1, user=self.user
635+
)
636+
else:
637+
dlist = self.search.apply(
638+
cdb,
639+
[k for k in allkeys if k[1] != ignore],
640+
tupleind=1,
641+
user=self.user,
642+
)
643+
finally:
644+
self.user.end_filter_progress()
634645
elif ignore is None:
635646
ident = True
636647
dlist = allkeys

0 commit comments

Comments
 (0)