diff --git a/labeler/views/gui.py b/labeler/views/gui.py index b048e9f..4e833d0 100644 --- a/labeler/views/gui.py +++ b/labeler/views/gui.py @@ -210,8 +210,9 @@ def __init__(self, *args, **kwargs): # Listener for label type self.type_variable.trace_add("write", lambda *args: self.save_type()) self.label_type = ttk.Combobox( - self.top_frame, textvariable=self.type_variable, values=self.type_options, state="readonly" + self.top_frame, textvariable=self.type_variable, values=self.type_options, state="normal" ) + self.label_type.bind("", self._filter_label_type_values) self.progress_bar = ttk.Progressbar(self.top_frame, orient="horizontal", length=100, mode="determinate") # Canvas @@ -373,7 +374,15 @@ def show_buttons(self): self.draw_poly_button.configure(state="normal") self.make_tight_button.configure(state="normal") self.label_text.configure(state="normal") - self.label_type.configure(state="readonly") + self.label_type.configure(state="normal") + + def _filter_label_type_values(self, event: tk.Event | None = None): + current_text = self.type_variable.get().strip().lower() + if not current_text: + self.label_type["values"] = self.type_options + return + filtered = [t for t in self.type_options if current_text in t.lower()] + self.label_type["values"] = filtered or self.type_options def select_all(self, event: tk.Event | None = None): """ @@ -413,10 +422,13 @@ def save_type(self, *args): if not self.img_cnv or not hasattr(self, "last_selected_polygon"): return + new_type = self.type_variable.get().strip() + if new_type not in self.type_options: + return + selected_polys = [poly for poly in self.img_cnv.polygons if poly.select_poly] if selected_polys: with self.img_cnv.polygons_mutex: - new_type = self.type_variable.get().strip() for poly in selected_polys: poly.poly_type = new_type self.show_case_type_variable.set(new_type) diff --git a/setup.py b/setup.py index 84894d6..f9abdb2 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup PKG_NAME = "doctr-labeler" -VERSION = os.getenv("BUILD_VERSION", "0.2.4") +VERSION = os.getenv("BUILD_VERSION", "0.3.0") if __name__ == "__main__": diff --git a/tests/common/test_gui.py b/tests/common/test_gui.py index bf18bc1..e254007 100644 --- a/tests/common/test_gui.py +++ b/tests/common/test_gui.py @@ -45,7 +45,7 @@ def test_show_buttons(gui_app): assert str(gui_app.draw_poly_button["state"]) == "normal" assert str(gui_app.make_tight_button["state"]) == "normal" assert str(gui_app.label_text["state"]) == "normal" - assert str(gui_app.label_type["state"]) == "readonly" + assert str(gui_app.label_type["state"]) == "normal" def test_toggle_keep_drawing(gui_app): @@ -154,7 +154,7 @@ def test_show_buttons_enables_buttons(gui_app): ] for button in buttons: assert str(button["state"]) == "normal" - assert str(gui_app.label_type["state"]) == "readonly" + assert str(gui_app.label_type["state"]) == "normal" def test_select_all(gui_app): @@ -215,7 +215,7 @@ def test_save_type(gui_app): mock_polygon.select_poly = True gui_app.img_cnv.polygons = [mock_polygon] gui_app.type_variable = Mock() - gui_app.type_variable.get.return_value = " polygon_type " + gui_app.type_variable.get.return_value = " words " gui_app.save_image_button = Mock() # Mock the save_image_button gui_app.save_image_button.configure = Mock() # Mock the configure method @@ -223,11 +223,28 @@ def test_save_type(gui_app): gui_app.save_type() gui_app.img_cnv.polygons_mutex.__enter__.assert_called_once() - assert mock_polygon.poly_type == "polygon_type" + assert mock_polygon.poly_type == "words" assert not gui_app.img_cnv.current_saved gui_app.save_image_button.configure.assert_called_once_with(state="normal") +def test_save_type_new_type_check_coverage(gui_app): + gui_app.img_cnv = Mock() + mutex_mock = Mock() + mutex_mock.__enter__ = Mock() + mutex_mock.__exit__ = Mock() + gui_app.img_cnv.polygons_mutex = mutex_mock + + gui_app.last_selected_polygon = "123" + gui_app.type_options = ["valid"] + gui_app.type_variable = Mock() + gui_app.type_variable.get.return_value = "invalid_xyz" + + gui_app.save_type() + + gui_app.img_cnv.polygons_mutex.__enter__.assert_not_called() + + def test_load_new_img(gui_app): gui_app.canvas = Mock() gui_app.save_image_button = Mock() @@ -436,3 +453,32 @@ def test_update_color_palette_with_mapping_and_new_types(gui_app): assert gui_app.color_palette[1] == "#123456" assert len(gui_app.color_palette) == 4 assert gui_app.color_palette[3].startswith("#") + + +def test_filter_label_type_values(gui_app): + gui_app.type_options = ["words", "lines", "header", "paragraph", "footer"] + + gui_app.type_variable.set("") + gui_app._filter_label_type_values(None) + assert tuple(gui_app.label_type["values"]) == tuple(gui_app.type_options) + + gui_app.type_variable.set("he") + gui_app._filter_label_type_values(None) + assert tuple(gui_app.label_type["values"]) == ("header",) + + gui_app.type_variable.set("par") + gui_app._filter_label_type_values(None) + assert tuple(gui_app.label_type["values"]) == ("paragraph",) + + gui_app.type_variable.set("xyz") + gui_app._filter_label_type_values(None) + assert tuple(gui_app.label_type["values"]) == tuple(gui_app.type_options) + + gui_app.type_variable.set("HEAD") + gui_app._filter_label_type_values(None) + assert tuple(gui_app.label_type["values"]) == ("header",) + + gui_app.type_options = ["words"] + gui_app.type_variable.set("w") + gui_app._filter_label_type_values(None) + assert tuple(gui_app.label_type["values"]) == ("words",)