diff --git a/naturtag/controllers/image_controller.py b/naturtag/controllers/image_controller.py index d683f20d..0694201c 100644 --- a/naturtag/controllers/image_controller.py +++ b/naturtag/controllers/image_controller.py @@ -2,10 +2,10 @@ from typing import Optional from pyinaturalist import Observation, Taxon -from PySide6.QtCore import Qt, Signal, Slot +from PySide6.QtCore import Qt, QThread, Signal, Slot from PySide6.QtWidgets import QApplication, QGroupBox, QLabel, QSizePolicy -from naturtag.controllers import BaseController, ImageGallery +from naturtag.controllers import BaseController, ImageGallery, get_app from naturtag.metadata import MetaMetadata, _refresh_tags, tag_images from naturtag.utils import get_ids_from_url from naturtag.widgets import ( @@ -24,8 +24,8 @@ class ImageController(BaseController): """Controller for selecting and tagging local image files""" on_new_metadata = Signal(MetaMetadata) #: Metadata for an image was updated - on_view_taxon_id = Signal() #: Request to switch to taxon tab - on_view_observation_id = Signal() #: Request to switch to observation tab + on_view_taxon_id = Signal(int) #: Request to switch to taxon tab + on_view_observation_id = Signal(int) #: Request to switch to observation tab def __init__(self): super().__init__() @@ -43,16 +43,14 @@ def __init__(self): # Input fields inputs_layout = VerticalLayout(group_box) - self.input_obs_id = IdInput() - inputs_layout.addWidget(QLabel('Observation ID:')) - inputs_layout.addWidget(self.input_obs_id) self.input_taxon_id = IdInput() + self.input_taxon_id.on_select.connect(self.select_taxon_by_id) inputs_layout.addWidget(QLabel('Taxon ID:')) inputs_layout.addWidget(self.input_taxon_id) - - # Notify other controllers when an ID is selected from input text + self.input_obs_id = IdInput() self.input_obs_id.on_select.connect(self.select_observation_by_id) - self.input_taxon_id.on_select.connect(self.select_taxon_by_id) + inputs_layout.addWidget(QLabel('Observation ID:')) + inputs_layout.addWidget(self.input_obs_id) # Selected taxon/observation info group_box = QGroupBox('Metadata source') @@ -61,10 +59,8 @@ def __init__(self): group_box.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) top_section_layout.addWidget(group_box) self.data_source_card = HorizontalLayout(group_box) - - # Clear info when clearing an input field - self.input_obs_id.on_clear.connect(self.data_source_card.clear) - self.input_taxon_id.on_clear.connect(self.data_source_card.clear) + self.selected_taxon_id: Optional[int] = None + self.selected_observation_id: Optional[int] = None # Image gallery self.gallery = ImageGallery() @@ -77,7 +73,7 @@ def run(self): self.info('Select images to tag') return - obs_id, taxon_id = self.input_obs_id.text(), self.input_taxon_id.text() + obs_id, taxon_id = self.selected_taxon_id, self.selected_observation_id if not (obs_id or taxon_id): self.info('Select either an observation or an organism to tag images with') return @@ -142,22 +138,46 @@ def paste(self): else: self.gallery.load_images(text.splitlines()) - # TODO + # Note: These methods duplicate "display_x_by_id" controller methods, but attempts at code reuse + # added too much spaghetti def select_taxon_by_id(self, taxon_id: int): - pass + """Load a taxon by ID (pasted or directly entered)""" + if self.selected_taxon_id == taxon_id: + return + + app = get_app() + logger.info(f'Loading taxon {taxon_id}') + future = app.threadpool.schedule( + lambda: app.client.taxa(taxon_id, locale=app.settings.locale), + priority=QThread.HighPriority, + ) + future.on_result.connect(self.select_taxon) - # TODO def select_observation_by_id(self, observation_id: int): - pass + """Load an observation by ID (pasted or directly entered)""" + if self.selected_observation_id == observation_id: + return + + app = get_app() + logger.info(f'Loading observation {observation_id}') + future = app.threadpool.schedule( + lambda: app.client.observations(observation_id, taxonomy=True), + priority=QThread.HighPriority, + ) + future.on_result.connect(self.select_observation) @Slot(Taxon) def select_taxon(self, taxon: Taxon): - """Update input info from a taxon object""" - if self.input_taxon_id.text() == str(taxon.id): + """Update metadata info from a taxon object""" + if self.selected_taxon_id == taxon.id: return - self.input_taxon_id.set_id(taxon.id) + self.selected_taxon_id = taxon.id + self.selected_observation_id = None + self.input_obs_id.clear() + self.input_taxon_id.clear() self.data_source_card.clear() + card = TaxonInfoCard(taxon=taxon, delayed_load=False) card.on_click.connect(self.on_view_taxon_id) self.data_source_card.addWidget(card) @@ -165,12 +185,15 @@ def select_taxon(self, taxon: Taxon): @Slot(Observation) def select_observation(self, observation: Observation): """Update input info from an observation object""" - if self.input_obs_id.text() == str(observation.id): + if self.selected_observation_id == observation.id: return - self.input_obs_id.set_id(observation.id) - self.input_taxon_id.set_id(observation.taxon.id) + self.selected_taxon_id = None + self.selected_observation_id = observation.id + self.input_obs_id.clear() + self.input_taxon_id.clear() self.data_source_card.clear() + card = ObservationInfoCard(obs=observation, delayed_load=False) card.on_click.connect(self.on_view_observation_id) self.data_source_card.addWidget(card) diff --git a/naturtag/controllers/observation_controller.py b/naturtag/controllers/observation_controller.py index f4db4caf..607150df 100644 --- a/naturtag/controllers/observation_controller.py +++ b/naturtag/controllers/observation_controller.py @@ -8,18 +8,13 @@ from naturtag.app.style import fa_icon from naturtag.constants import DEFAULT_PAGE_SIZE from naturtag.controllers import BaseController, ObservationInfoSection -from naturtag.widgets import ( - HorizontalLayout, - ObservationInfoCard, - ObservationList, - VerticalLayout, -) +from naturtag.widgets import HorizontalLayout, ObservationInfoCard, ObservationList, VerticalLayout logger = getLogger(__name__) class ObservationController(BaseController): - on_view_taxon = Signal(Taxon) #: A taxon was selected for viewing + on_view_taxon = Signal(Taxon) #: Request to switch to taxon tab def __init__(self): super().__init__() @@ -93,7 +88,7 @@ def display_observation_by_id(self, observation_id: int): if self.displayed_observation and self.displayed_observation.id == observation_id: return - logger.info(f'Viewing observation {observation_id}') + logger.info(f'Loading observation {observation_id}') future = self.app.threadpool.schedule( lambda: self.app.client.observations(observation_id, taxonomy=True), priority=QThread.HighPriority, diff --git a/naturtag/controllers/taxon_controller.py b/naturtag/controllers/taxon_controller.py index ed355549..f97e7925 100644 --- a/naturtag/controllers/taxon_controller.py +++ b/naturtag/controllers/taxon_controller.py @@ -76,7 +76,7 @@ def display_taxon_by_id(self, taxon_id: int): return # Fetch taxon record - logger.info(f'Selecting taxon {taxon_id}') + logger.info(f'Loading taxon {taxon_id}') client = self.app.client if self.tabs._init_complete: self.app.threadpool.cancel() @@ -84,7 +84,7 @@ def display_taxon_by_id(self, taxon_id: int): lambda: client.taxa(taxon_id, locale=self.app.settings.locale), priority=QThread.HighPriority, ) - future.on_result.connect(lambda taxon: self.display_taxon(taxon)) + future.on_result.connect(self.display_taxon) @Slot(Taxon) def display_taxon(self, taxon: Taxon, notify: bool = True): diff --git a/naturtag/controllers/taxon_view.py b/naturtag/controllers/taxon_view.py index 725cc55a..bf890692 100644 --- a/naturtag/controllers/taxon_view.py +++ b/naturtag/controllers/taxon_view.py @@ -30,7 +30,7 @@ class TaxonInfoSection(HorizontalLayout): """Section to display selected taxon photo and basic info""" on_select = Signal(Taxon) #: A taxon was selected for tagging - on_view_observations = Signal(Taxon) #: A taxon was selected for filtering observations + on_view_observations = Signal(Taxon) #: Request to switch to observations tab # When selecting a taxon for viewing, a signal is sent to controller instead of handling here, # since there are multiple sections to load (not just this class) diff --git a/naturtag/widgets/inputs.py b/naturtag/widgets/inputs.py index 94ad6fb9..9f6a97a1 100644 --- a/naturtag/widgets/inputs.py +++ b/naturtag/widgets/inputs.py @@ -31,7 +31,10 @@ def focusOutEvent(self, event: Optional[QEvent] = None): def select(self): if self.text(): - self.on_select.emit(int(self.text())) + self.on_select.emit(self.get_id()) + + def get_id(self) -> int: + return int(self.text()) if self.text() else 0 def set_id(self, id: int): self.setText(str(id))