Skip to content

Commit

Permalink
main_entry - add BinomialSearch completions
Browse files Browse the repository at this point in the history
Change from inline to popup completion.
  • Loading branch information
RoDuth committed Nov 20, 2024
1 parent d9d3e37 commit 2353045
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 24 deletions.
4 changes: 4 additions & 0 deletions bauble/plugins/plants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
from .species import VernacularNameInfoBox
from .species import add_accession_action
from .species import edit_callback as species_edit_callback
from .species import get_binomial_completions
from .species import species_context_menu
from .species import vernname_context_menu
from .species_model import SpeciesPicture
Expand Down Expand Up @@ -789,6 +790,9 @@ def on_view_box_added(_container, obj):
bauble.gui.add_to_insert_menu(FamilyEditor, _("Family"))
bauble.gui.add_to_insert_menu(GenusEditor, _("Genus"))
bauble.gui.add_to_insert_menu(SpeciesEditor, _("Species"))
bauble.gui.main_entry_completion_callbacks.add(
get_binomial_completions
)

note_query = "{table} where notes.id = {obj_id}"
HistoryView.add_translation_query("family_note", "family", note_query)
Expand Down
60 changes: 60 additions & 0 deletions bauble/plugins/plants/species.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,66 @@ def search(self, text: str, session: Session) -> list[Query]:
return queries


def get_binomial_completions(text: str) -> set[str]:
parts = text.split()
sp_part = ""
cv_part = ""

if not db.Session:
return set()

with db.Session() as session:
epithets = (
session.query(
Genus.epithet,
Species.epithet,
Species.cultivar_epithet,
Species.trade_name,
)
.join(Genus)
.filter(Genus.epithet.startswith(parts[0]))
)
if len(parts) == 2:
if parts[1].startswith("'"):
cv_part = parts[1][1:]
epithets = epithets.filter(
or_(
Species.cultivar_epithet.startswith(cv_part),
Species.trade_name.startswith(cv_part),
)
)
else:
sp_part = parts[1]
epithets = epithets.filter(Species.epithet.startswith(sp_part))
elif len(parts) == 3:
sp_part = parts[1]
epithets = epithets.filter(Species.epithet.startswith(sp_part))
if parts[2].startswith("'"):
cv_part = parts[2][1:]
epithets = epithets.filter(
or_(
Species.cultivar_epithet.startswith(cv_part),
Species.trade_name.startswith(cv_part),
)
)

binomial_completions = set()
for gen, sp, cv, trade_name in epithets.limit(10):
string = f"{gen}"
if sp and (sp_part or not cv_part):
string += f"{' ' + sp.split()[0] if sp else ''}"
if not cv_part:
binomial_completions.add(string)
if cv and cv.startswith(cv_part):
cv_string = string + f" '{cv}'"
binomial_completions.add(cv_string)
if trade_name and trade_name.startswith(cv_part):
t_string = string + f" '{trade_name}'"
binomial_completions.add(t_string)

return binomial_completions


class SynonymSearch(SearchStrategy):
"""Adds queries that will return the accepted names for any synonyms that
previous strategies may have returned.
Expand Down
39 changes: 39 additions & 0 deletions bauble/plugins/plants/test_plants.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
from .species import SpeciesNote
from .species import SpeciesSynonym
from .species import VernacularName
from .species import get_binomial_completions
from .species_editor import DistributionPresenter
from .species_editor import InfraspPresenter
from .species_editor import InfraspRow
Expand Down Expand Up @@ -7046,6 +7047,44 @@ def test_species_cell_data_func(self):
"text", "Syzygium australe (Myrtaceae)"
)

def test_get_binomial_completions(self):
self.assertEqual(
get_binomial_completions("cyn"),
{
"Cynodon dactylon 'TifTuf'",
"Cynodon dactylon 'DT-1'",
"Cynodon dactylon",
},
)
self.assertEqual(
get_binomial_completions("cynodon dact"),
{
"Cynodon dactylon 'TifTuf'",
"Cynodon dactylon 'DT-1'",
"Cynodon dactylon",
},
)
self.assertEqual(
get_binomial_completions("Cynodon 'T"),
{
"Cynodon 'TifTuf'",
},
)
self.assertEqual(
get_binomial_completions("Cynodon dactylon 'DT"),
{
"Cynodon dactylon 'DT-1'",
},
)
self.assertEqual(
get_binomial_completions("Buty"),
{
"Butyagrus nabonnandii",
},
)
db.Session = None
self.assertEqual(get_binomial_completions("cyn"), set())


class GenusCompletionTests(PlantTestCase):
def test_genus_to_string_matcher(self):
Expand Down
28 changes: 27 additions & 1 deletion bauble/test/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,11 +418,37 @@ def test_populate_main_entry(self):

gui.populate_main_entry()

self.assertEqual([v[0] for v in comp_model], ["test2", "test1"])
self.assertEqual(gui.hist_completions, ["test2", "test1"])
self.assertEqual(
[v[0] for v in model], ["test2", "--separator--", "test1"]
)

def test_on_main_entry_changed(self):
gui = GUI()
gui.hist_completions = ["test1", "test2", "test3"]
entry = gui.widgets.main_comboentry.get_child()
# less than minimum key length does not populate
entry.set_text("t")
gui.on_main_entry_changed(entry)
completion = entry.get_completion()
comp_model = completion.get_model()
self.assertEqual([v[0] for v in comp_model], [])

entry.set_text("tes")
gui.on_main_entry_changed(entry)
completion = entry.get_completion()
comp_model = completion.get_model()
self.assertEqual(
[v[0] for v in comp_model], ["test1", "test2", "test3"]
)

gui.main_entry_completion_callbacks.add(lambda t: ["test1", "test4"])
gui.on_main_entry_changed(entry)
comp_model = completion.get_model()
self.assertEqual(
[v[0] for v in comp_model], ["test1", "test2", "test3", "test4"]
)

def test_title(self):
import bauble

Expand Down
59 changes: 37 additions & 22 deletions bauble/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ class GUI:
"""Any callbacks added to this list will be called each time the set_view
is called.
"""
main_entry_completion_callbacks: set[Callable] = set()
"""Callbacks added here will be called to generate completion strings for
the main entry
"""
disable_on_busy_actions: set[Gio.SimpleAction] = set()
"""Any Gio.Action added to this will be enabled/disabled when the gui
window is set_busy.
Expand Down Expand Up @@ -361,6 +365,7 @@ def __init__(self) -> None:
self.widgets.main_comboentry_entry.connect(
"icon-press", self.on_history_pinned_clicked
)
self.hist_completions: list[str] = []
self.populate_main_entry()

main_entry = combo.get_child()
Expand Down Expand Up @@ -506,9 +511,9 @@ def history_size(self):
def history_pins_size(self):
pins = prefs.prefs[self.history_pins_size_pref]
if pins is None:
prefs.prefs[
self.history_pins_size_pref
] = self._default_history_pin_size
prefs.prefs[self.history_pins_size_pref] = (
self._default_history_pin_size
)
return int(prefs.prefs[self.history_pins_size_pref])

def send_command(self, command):
Expand Down Expand Up @@ -674,36 +679,46 @@ def separate(model, tree_iter):
main_entry.set_completion(completion)
compl_model = Gtk.ListStore(str)
completion.set_model(compl_model)
completion.set_popup_completion(False)
completion.set_inline_completion(True)
completion.set_minimum_key_length(2)
main_entry.connect("changed", self.on_main_entry_changed)
else:
compl_model = completion.get_model()

self.hist_completions.clear()

if history_pins is not None:
for pin in history_pins:
logger.debug("adding pin to main entry: %s", pin)
model.append(
[
pin,
]
)
compl_model.append([pin])
model.append([pin])
self.hist_completions.append(pin)

model.append(
[
"--separator--",
]
)
model.append(["--separator--"])

if history is not None:
for herstory in history:
model.append(
[
herstory,
]
)
compl_model.append([herstory])
model.append([herstory])
self.hist_completions.append(herstory)

def on_main_entry_changed(self, entry: Gtk.Entry) -> None:
text = entry.get_text()
completion = entry.get_completion()
key_length = completion.get_minimum_key_length()

if len(text) < key_length:
return

utils.clear_model(completion)
completion_model = Gtk.ListStore(str)

for i in self.hist_completions:
completion_model.append([i])

for callback in self.main_entry_completion_callbacks:
for i in sorted(callback(text)):
if i not in self.hist_completions:
completion_model.append([i])

completion.set_model(completion_model)

@property
def title(self):
Expand Down
2 changes: 1 addition & 1 deletion bauble/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,7 @@ class SearchView(pluginmgr.View, Gtk.Box):
"""The SearchView is the main view for Ghini.
Manages the search results returned when search strings are entered into
the main text entry.
the main text entry (found in ui.GUI).
"""

__gtype_name__ = "SearchView"
Expand Down

0 comments on commit 2353045

Please sign in to comment.