Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions data/familysearch.css
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,17 @@ box.fs-compare-legend,
background-color: rgba(0,0,0,0.03);
}

.fs-status-row {
padding: 2px;
.fs-name-row {
padding: 2px 2px 0 2px;
}

.fs-link-row {
padding: 0 2px 2px 2px;
}

.fs-bottom-statusbar {
padding: 4px 2px;
opacity: 0.80;
}

.fs-active-label {
Expand Down
183 changes: 133 additions & 50 deletions gramps/gui/fs/tools_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@
from gramps.gen.const import DATA_DIR, GRAMPS_LOCALE as glocale
from gramps.gen.const import IMAGE_DIR as _GRAMPS_IMAGE_DIR
from gramps.gen.display.name import displayer as name_displayer
from gramps.gen.errors import HandleError
from gramps.gen.fs.actions import _get_fs_id
from gramps.gui.dialog import ErrorDialog
from gramps.gui.editors.editperson import EditPerson
from gramps.gen.errors import HandleError

from . import actions
from . import sync_directions as fs_syncdir
Expand Down Expand Up @@ -176,6 +177,19 @@ def notify_from_person_editor(
_dbg(f"notify_from_person_editor: handle={person_handle}")


def _on_editor_destroyed(editor_wr: "weakref.ReferenceType[Any]") -> None:
"""Clear the shared editor context when the tracked editor window is destroyed."""
global _LAST_EDITOR
editor = editor_wr()
current = _LAST_EDITOR.editor_obj()
if current is not None and editor is not None and current is not editor:
return
_LAST_EDITOR = _EditorCtx()
if _SINGLETON is not None and _SINGLETON.is_alive():
GLib.idle_add(_SINGLETON._on_editor_ctx_changed)
_dbg("Editor closed — cleared editor context")


def _install_editperson_hook() -> None:
"""Hook EditPerson once so the tools window can follow focus/context changes."""
global _EDITPERSON_HOOK_INSTALLED
Expand Down Expand Up @@ -221,6 +235,8 @@ def _fire() -> bool:
if window is not None:
window.connect("focus-in-event", lambda *_args: _fire())
window.connect("map-event", lambda *_args: _fire())
editor_wr: weakref.ReferenceType[Any] = weakref.ref(editor)
window.connect("destroy", lambda *_args: _on_editor_destroyed(editor_wr))

def wrapped_post_init(editor: Any, *args: Any, **kwargs: Any) -> Any:
result = original_post_init(editor, *args, **kwargs)
Expand Down Expand Up @@ -319,32 +335,29 @@ def __init__(self, session: Any):

self._install_css()

# layout is simple on purpose: status row, then grouped actions in tabs
# layout is simple on purpose: name + link row, then grouped actions in tabs
outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
self.window.add(outer)

banner = self._build_banner()
if banner is not None:
outer.pack_start(banner, False, False, 0)

status_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
status_row.get_style_context().add_class("fs-status-row")
outer.pack_start(status_row, False, False, 0)

status_widget = None
get_status_widget = getattr(self.session, "get_status_widget", None)
if callable(get_status_widget):
status_widget = get_status_widget()

if status_widget is not None:
status_widget.set_halign(Gtk.Align.START)
status_row.pack_start(status_widget, False, False, 0)
name_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
name_row.get_style_context().add_class("fs-name-row")
outer.pack_start(name_row, False, False, 0)

self.active_label = Gtk.Label(label=_("Editor person: (none)"))
self.active_label.set_xalign(0.0)
self.active_label.set_ellipsize(3)
self.active_label.get_style_context().add_class("fs-active-label")
status_row.pack_start(self.active_label, True, True, 0)
name_row.pack_start(self.active_label, True, True, 0)

self.btn_link = Gtk.Button(label=_("Link FamilySearch ID"))
link_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
link_row.get_style_context().add_class("fs-link-row")
link_row.pack_start(self.btn_link, False, False, 0)
outer.pack_start(link_row, False, False, 0)

outer.pack_start(
Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL),
Expand All @@ -368,37 +381,9 @@ def __init__(self, session: Any):
tab_bulk.set_border_width(2)
notebook.append_page(tab_bulk, Gtk.Label(label=_("Bulk Actions")))

sec_person, box_person = self._make_section(
_("Person actions"), "fs-sec-person"
)
sec_person = self._build_person_actions_section()
tab_single.pack_start(sec_person, False, False, 0)

self.btn_link = Gtk.Button(label=_("Link FamilySearch ID"))
self.btn_cmp = Gtk.Button(label=_("Compare"))
self.btn_sync = Gtk.Button(label=_("Sync from FamilySearch"))
self.btn_sync.get_style_context().add_class("suggested-action")

self.btn_sync_to = Gtk.Button(label=_("Sync to FamilySearch..."))
self.btn_sync_to.set_tooltip_text(
_("Overwrite selected FamilySearch fields with Gramps values (no deletes).")
)

self.btn_export_basic = Gtk.Button(label=_("Export to FamilySearch"))
self.btn_export_basic.set_tooltip_text(
_(
"Create missing people on FamilySearch and link relationships (name + birth/death)."
)
)

for button in (
self.btn_link,
self.btn_cmp,
self.btn_sync,
self.btn_sync_to,
self.btn_export_basic,
):
self._add_btn(box_person, button)

sec_import, box_import = self._make_section(
_("Import relatives"), "fs-sec-import"
)
Expand Down Expand Up @@ -444,6 +429,25 @@ def __init__(self, session: Any):
if isinstance(util_inner, Gtk.Box):
util_inner.pack_start(note, False, False, 0)

outer.pack_start(
Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL),
False,
False,
0,
)

bottom_bar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
bottom_bar.get_style_context().add_class("fs-bottom-statusbar")
outer.pack_start(bottom_bar, False, False, 0)

status_widget = None
get_status_widget = getattr(self.session, "get_status_widget", None)
if callable(get_status_widget):
status_widget = get_status_widget()
if status_widget is not None:
status_widget.set_halign(Gtk.Align.START)
bottom_bar.pack_start(status_widget, False, False, 0)

self.btn_link.connect("clicked", self._on_link)
self.btn_cmp.connect("clicked", self._on_compare)
self.btn_sync.connect("clicked", self._on_sync)
Expand Down Expand Up @@ -512,6 +516,59 @@ def _repo_css_path(filename: str) -> str:
os.path.join(os.path.dirname(__file__), "..", "..", "..", "data", filename)
)

def _build_person_actions_section(self) -> Gtk.Widget:
"""Build the Person actions section with explicit three-row button layout."""
wrapper = Gtk.EventBox()
wrapper.set_visible_window(True)
style = wrapper.get_style_context()
style.add_class("fs-section")
style.add_class("fs-sec-person")

inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
inner.set_border_width(10)
wrapper.add(inner)

lbl = Gtk.Label()
lbl.set_markup(
"<span size='large'><b>"
+ GLib.markup_escape_text(_("Person actions"))
+ "</b></span>"
)
lbl.set_xalign(0.0)
lbl.get_style_context().add_class("fs-section-title")
inner.pack_start(lbl, False, False, 0)
inner.pack_start(
Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0
)

self.btn_cmp = Gtk.Button(label=_("Compare"))
row1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
row1.pack_start(self.btn_cmp, True, True, 0)
inner.pack_start(row1, False, False, 0)

self.btn_sync = Gtk.Button(label=_("Sync from FamilySearch"))
self.btn_sync_to = Gtk.Button(label=_("Sync to FamilySearch..."))
self.btn_sync_to.set_tooltip_text(
_("Overwrite selected FamilySearch fields with Gramps values (no deletes).")
)
row2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
row2.pack_start(self.btn_sync, True, True, 0)
row2.pack_start(self.btn_sync_to, True, True, 0)
inner.pack_start(row2, False, False, 0)

self.btn_export_basic = Gtk.Button(label=_("Export to FamilySearch"))
self.btn_export_basic.set_tooltip_text(
_(
"Create missing people on FamilySearch and link relationships"
" (name + birth/death)."
)
)
row3 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
row3.pack_start(self.btn_export_basic, True, True, 0)
inner.pack_start(row3, False, False, 0)

return wrapper

def _make_section(
self, title: str, css_class: str
) -> tuple[Gtk.Widget, Gtk.FlowBox]:
Expand Down Expand Up @@ -696,6 +753,16 @@ def _editor_person_obj(self) -> Any:

return None

def _live_person_obj(self) -> Any:
"""Return the live in-memory person from the editor (not the DB copy)."""
person = _LAST_EDITOR.person_obj()
if person is not None:
return person
editor = _LAST_EDITOR.editor_obj()
if editor is not None:
return getattr(editor, "obj", None)
return None

def _update_label(self) -> None:
"""Refresh the label that shows which editor person were on"""
person = self._editor_person_obj()
Expand Down Expand Up @@ -724,10 +791,9 @@ def _update_label(self) -> None:
_("Editor person: %(name)s") % {"name": display_name}
)

def _set_action_sensitivity(self, enabled: bool) -> None:
def _set_action_sensitivity(self, enabled: bool, has_fsid: bool = True) -> None:
"""Enable/disable buttons based on connection state and editor readiness."""
for button in (
self.btn_link,
other_buttons = (
self.btn_cmp,
self.btn_sync,
self.btn_sync_to,
Expand All @@ -736,8 +802,22 @@ def _set_action_sensitivity(self, enabled: bool) -> None:
self.btn_imp_spo,
self.btn_imp_chi,
self.btn_bulk_import,
):
button.set_sensitive(enabled)
)

if enabled and not has_fsid:
self.btn_link.set_sensitive(True)
self.btn_link.set_label(_("Add FamilySearch ID"))
for button in other_buttons:
button.set_sensitive(False)
else:
self.btn_link.set_sensitive(enabled)
self.btn_link.set_label(
_("Edit FamilySearch ID")
if (enabled and has_fsid)
else _("Link FamilySearch ID")
)
for button in other_buttons:
button.set_sensitive(enabled)

self.btn_tags.set_sensitive(
bool(self._fs_connected() and _LAST_EDITOR.dbstate is not None)
Expand All @@ -755,8 +835,11 @@ def _tick(self, *_args: Any) -> bool:
and _person_exists_in_db(_LAST_EDITOR.dbstate, person_handle)
)

editing = bool(self._fs_connected() and db_ready)
has_fsid = bool(_get_fs_id(self._live_person_obj())) if editing else True

self._update_label()
self._set_action_sensitivity(bool(self._fs_connected() and db_ready))
self._set_action_sensitivity(editing, has_fsid)
return True

def _on_editor_ctx_changed(self) -> bool:
Expand Down