diff --git a/encyclopaedia/actions_ren.py b/encyclopaedia/actions_ren.py index d9581a4..2d259e2 100644 --- a/encyclopaedia/actions_ren.py +++ b/encyclopaedia/actions_ren.py @@ -1,16 +1,14 @@ +from typing import TYPE_CHECKING + from .constants_ren import SortMode +from .types_ren import ENTRY_TYPE import renpy.exports as renpy from renpy.store import DictEquality from renpy.ui import Action -from typing import TYPE_CHECKING - if TYPE_CHECKING: # pragma: no cover from .encyclopaedia_ren import Encyclopaedia - from .encentry_ren import EncEntry - -from .book import Book """renpy init python: @@ -38,54 +36,14 @@ class SetEntry(EncyclopaediaAction): encyclopaedia: The Encyclopaedia instance to use. entry: The entry to be made active. """ - def __init__(self, encyclopaedia: 'Encyclopaedia', entry: 'EncEntry') -> None: + def __init__(self, encyclopaedia: 'Encyclopaedia', entry: ENTRY_TYPE) -> None: super().__init__(encyclopaedia) self.entry = entry - def _get_entry_index(self) -> int: - # Find the position of the entry - if self.enc.show_locked_entry: - target_position = self.enc.all_entries.index(self.entry) - else: - target_position = self.enc.unlocked_entries.index(self.entry) - - return target_position - - def set_entry(self) -> None: - """Set the Entry as active and update the Encyclopaeda's internal state.""" - target_position = self._get_entry_index() - - # The active entry is set to whichever list position was found. - self.enc.active = self.entry - - if self.enc.active.locked is False: - if not isinstance(self.entry, Book): - if self.entry.viewed is False: - # Run the callback, if provided. - self.entry.emit("viewed") - # Mark the entry as viewed. - self.enc.active.viewed = True - - # When setting a Book, set the first page to viewed, not the Book. - elif isinstance(self.entry, Book): - self.entry.active.viewed = True - self.entry.active.emit("viewed") - - # When sorting by Unread, setting an entry marks is as read. - # Thus we have to resort the entries to ensure they appear in the - # correct order. - if self.enc.sorting_mode.value == SortMode.UNREAD.value: - self.enc.sort_entries( - entries=self.enc.current_entries, - sorting=self.enc.sorting_mode.value, - ) - - self.enc.current_position = target_position - def __call__(self) -> None: """Used by Ren'Py to invoke this Action.""" - self.set_entry() + self.enc.set_entry(self.entry) # Show the entry screen associated with the encyclopaedia. renpy.show_screen(self.enc.entry_screen, enc=self.enc) diff --git a/encyclopaedia/encentry_ren.py b/encyclopaedia/encentry_ren.py index 6eb4885..62e1e24 100644 --- a/encyclopaedia/encentry_ren.py +++ b/encyclopaedia/encentry_ren.py @@ -182,8 +182,12 @@ def viewed(self, new_value: bool) -> None: if self.viewed_persistent: setattr(persistent, self._name + "_viewed", new_value) + if self._viewed is False and new_value is True: + self.emit("viewed") + self._viewed = new_value + @property def current_page(self) -> 'EncEntry': """Get the sub-page that's currently viewing viewed. diff --git a/encyclopaedia/encyclopaedia_ren.py b/encyclopaedia/encyclopaedia_ren.py index d17b505..bce14cd 100644 --- a/encyclopaedia/encyclopaedia_ren.py +++ b/encyclopaedia/encyclopaedia_ren.py @@ -17,11 +17,10 @@ ToggleShowLockedEntryAction, ) from .entry_sorting_ren import push_locked_to_bottom -from .exceptions_ren import AddEntryError +from .exceptions_ren import AddEntryError, UnknownEntryError from .eventemitter_ren import EventEmitter from .constants_ren import Direction, SortMode from .book import Book - from .types_ren import ENTRY_TYPE if TYPE_CHECKING: # pragma: no cover @@ -115,12 +114,17 @@ def __str__(self) -> str: # NOQA D105 def __len__(self) -> int: """The total number of entries, relative to if locked ones are shown or not.""" - rv = len(self.unlocked_entries) - if self.show_locked_entry: - rv = len(self.all_entries) - + rv = len(self.viewable_entries) return rv + @property + def viewable_entries(self): + """Get the list of entries which are currently viewable.""" + if self.show_locked_entry: + return self.all_entries + else: + return self.unlocked_entries + @property def current_entries(self) -> list[ENTRY_TYPE]: """Get all the entries which should be visible to the user. @@ -148,10 +152,7 @@ def current_entry(self) -> ENTRY_TYPE: Return: EncEntry """ - entry = self.unlocked_entries[self.current_position] - if self.show_locked_entry: - entry = self.all_entries[self.current_position] - + entry = self.viewable_entries[self.current_position] return entry @property @@ -311,8 +312,73 @@ def _build_subject_filter(self, subject: str) -> None: self.filtered_entries = [i for i in entries if i.subject == subject] + def _change_active_entry_viewed_status(self) -> bool: + """Change the viewed status of the active Entry. + + Return: + True if status was changed, else False + """ + if self.active is None: + raise ValueError( + 'Tried to change active entry viewed status with no active entry.', + ) + if self.active.locked is False: + entry = None + + if isinstance(self.active, Book): + # When Book, set the first page to viewed, not the Book. + entry = self.active.active + else: + entry = self.active + + # Mark the entry as viewed. + entry.viewed = True + + return True + + return False + + def set_entry(self, entry: ENTRY_TYPE) -> None: + """Set an Entry as active. + + Args: + entry: The Entry to set. + + Raises: + ValueError: If the entry cannot be set. + """ + try: + target_position = self.viewable_entries.index(entry) + except ValueError as e: + if entry not in self.all_entries: + raise UnknownEntryError(f"{entry} is not in this Encyclopaedia") from e + else: + raise ValueError( + f"{entry} cannot be set because it is locked and 'show_locked_entry' is False", + ) from e + + self.current_position = target_position + + self.active = entry + self._change_active_entry_viewed_status() + + # When sorting by Unread, setting an entry marks is as read. + # Thus we have to resort the entries to ensure they appear in the + # correct order. + if self.sorting_mode.value == SortMode.UNREAD.value: + self.sort_entries( + entries=self.current_entries, + sorting=self.sorting_mode.value, + ) + def _change_entry(self, direction: Direction) -> bool: - """Change the current active EncEntry.""" + """Change the active entry by changing the index. + + This is relative to the current sorting. + + Args: + direction: The direction to move. + """ test_position = self.current_position + direction.value # Boundary check @@ -328,18 +394,7 @@ def _change_entry(self, direction: Direction) -> bool: # Update the active entry. self.active = self.current_entry - if self.active.locked is False: - if not isinstance(self.active, Book): - # Run the callback, if provided. - self.active.emit("viewed") - - # Mark the entry as viewed. - self.active.viewed = True - - # When setting a Book, set the first page to viewed, not the Book. - elif isinstance(self.active, Book): - self.active.active.viewed = True - self.active.active.emit("viewed") + self._change_active_entry_viewed_status() # When changing an entry, the current entry page number is reset. self.active._unlocked_page_index = 0 diff --git a/encyclopaedia/exceptions_ren.py b/encyclopaedia/exceptions_ren.py index 36bef60..2a6f726 100644 --- a/encyclopaedia/exceptions_ren.py +++ b/encyclopaedia/exceptions_ren.py @@ -9,3 +9,9 @@ class AddEntryError(Exception): class GetEntryError(Exception): """Raised when getting an entry fails.""" + pass + + +class UnknownEntryError(Exception): + """Raised when looking for an entry that is not in an Encyclopaedia.""" + pass diff --git a/tests/actions/test_set_entry.py b/tests/actions/test_SetEntry.py similarity index 100% rename from tests/actions/test_set_entry.py rename to tests/actions/test_SetEntry.py diff --git a/tests/encyclopaedia/test_set_entry.py b/tests/encyclopaedia/test_set_entry.py new file mode 100644 index 0000000..255d60a --- /dev/null +++ b/tests/encyclopaedia/test_set_entry.py @@ -0,0 +1,48 @@ +from encyclopaedia import Encyclopaedia +from encyclopaedia import EncEntry +from encyclopaedia.exceptions_ren import UnknownEntryError + +import pytest + + +def test__change_active_entry_viewed_status_no_active(): + enc = Encyclopaedia() + + EncEntry( + parent=enc, + name="Test Name", + text=["Test Text"], + ) + + with pytest.raises(ValueError): + enc._change_active_entry_viewed_status() + + +def test__change_active_entry_viewed_status_locked_entry(): + enc = Encyclopaedia() + + e = EncEntry( + parent=enc, + name="Test Name", + text=["Test Text"], + locked=True, + ) + + enc.active = e + + result = enc._change_active_entry_viewed_status() + assert result is False + + +def test_set_entry_unknown_entry(): + enc = Encyclopaedia() + another_enc = Encyclopaedia() + + e = EncEntry( + parent=enc, + name="Test Name", + text=["Test Text"], + ) + + with pytest.raises(UnknownEntryError): + another_enc.set_entry(e)