Ejemplo n.º 1
0
    def __init__(
        self,
        parent: QWidget,
        *,
        mw: AnkiQt,
        note_ids: Sequence[NoteId],
        field: str | None = None,
    ) -> None:
        """
        If 'field' is passed, only this is added to the field selector.
        Otherwise, the fields belonging to the 'note_ids' are added.
        """
        super().__init__(parent)
        self.mw = mw
        self.note_ids = note_ids
        self.field_names: list[str] = []
        self._field = field

        if field:
            self._show([field])
        elif note_ids:
            # fetch field names and then show
            QueryOp(
                parent=mw,
                op=lambda col: col.field_names_for_note_ids(note_ids),
                success=self._show,
            ).run_in_background()
        else:
            self._show([])
Ejemplo n.º 2
0
    def __init__(
        self,
        mw: AnkiQt,
        deck_id: DeckId = DeckId(0),
        search: str | None = None,
        search_2: str | None = None,
    ) -> None:
        """If 'deck_id' is non-zero, load and modify its settings.
        Otherwise, build a new deck and derive settings from the current deck.

        If search or search_2 are provided, they will be used as the default
        search text.
        """

        QDialog.__init__(self, mw)
        self.mw = mw
        self.col = self.mw.col
        self._desired_search_1 = search
        self._desired_search_2 = search_2

        self._initial_dialog_setup()

        # set on successful query
        self.deck: FilteredDeckForUpdate

        QueryOp(
            parent=self.mw,
            op=lambda col: col.sched.get_or_create_filtered_deck(deck_id=
                                                                 deck_id),
            success=self.load_deck_and_show,
        ).failure(self.on_fetch_error).run_in_background()
Ejemplo n.º 3
0
    def _on_trash_files(self, fnames: Sequence[str]) -> None:
        if not askUser(tr.media_check_delete_unused_confirm()):
            return

        total = len(fnames)

        def trash(col: Collection) -> None:
            last_progress = 0.0
            remaining = total

            for chunk in chunked_list(fnames, 25):
                col.media.trash_files(chunk)
                remaining -= len(chunk)
                if time.time() - last_progress >= 0.1:
                    self.mw.taskman.run_on_main(
                        lambda: self.mw.progress.update(
                            label=tr.media_check_files_remaining(count=
                                                                 remaining),
                            value=total - remaining,
                            max=total,
                        ))
                    last_progress = time.time()

        QueryOp(
            parent=aqt.mw,
            op=trash,
            success=lambda _: tooltip(
                tr.media_check_delete_unused_complete(count=total)),
        ).with_progress().run_in_background()
Ejemplo n.º 4
0
    def rename_deck(self, item: SidebarItem, new_name: str) -> None:
        if not new_name:
            return

        # update UI immediately, to avoid redraw
        item.name = new_name

        full_name = item.name_prefix + new_name
        deck_id = DeckId(item.id)

        def after_fetch(deck: Deck) -> None:
            if full_name == deck.name:
                return

            rename_deck(
                parent=self,
                deck_id=deck_id,
                new_name=full_name,
            ).run_in_background()

        QueryOp(
            parent=self.browser,
            op=lambda col: col.get_deck(deck_id),
            success=after_fetch,
        ).run_in_background()
Ejemplo n.º 5
0
    def _rename(self, did: DeckId) -> None:
        def prompt(name: str) -> None:
            new_name = getOnlyText(tr.decks_new_deck_name(), default=name)
            if not new_name or new_name == name:
                return
            else:
                rename_deck(parent=self.mw, deck_id=did,
                            new_name=new_name).run_in_background()

        QueryOp(parent=self.mw,
                op=lambda col: col.decks.name(did),
                success=prompt).run_in_background()
Ejemplo n.º 6
0
    def __init__(self, mw: aqt.main.AnkiQt) -> None:
        QDialog.__init__(self, mw, Qt.WindowType.Window)
        self.mw = mw

        # set on success
        self.deck: DeckDict

        QueryOp(
            parent=self.mw,
            op=lambda col: col.decks.current(),
            success=self._setup_and_show,
        ).run_in_background()
Ejemplo n.º 7
0
def full_apkg_import(mw: AnkiQt, file: str) -> None:
    def on_done(success: bool) -> None:
        mw.loadCollection()
        if success:
            tooltip(tr.importing_importing_complete())

    def after_backup(created: bool) -> None:
        mw.unloadCollection(lambda: replace_with_apkg(mw, file, on_done))

    QueryOp(parent=mw,
            op=lambda _: mw.create_backup_now(),
            success=after_backup).with_progress().run_in_background()
Ejemplo n.º 8
0
    def __init__(self, parent: QWidget, *, mw: AnkiQt,
                 note_ids: Sequence[NoteId]) -> None:
        super().__init__(parent)
        self.mw = mw
        self.note_ids = note_ids
        self.field_names: List[str] = []

        # fetch field names and then show
        QueryOp(
            parent=mw,
            op=lambda col: col.field_names_for_note_ids(note_ids),
            success=self._show,
        ).run_in_background()
Ejemplo n.º 9
0
    def fetch_data_and_show(mw: aqt.AnkiQt) -> None:
        def fetch_data(
            col: Collection, ) -> Tuple[DeckId, CustomStudyDefaults]:
            deck_id = mw.col.decks.get_current_id()
            defaults = col.sched.custom_study_defaults(deck_id)
            return (deck_id, defaults)

        def show_dialog(data: Tuple[DeckId, CustomStudyDefaults]) -> None:
            deck_id, defaults = data
            CustomStudy(mw=mw, deck_id=deck_id, defaults=defaults)

        QueryOp(parent=mw, op=fetch_data,
                success=show_dialog).with_progress().run_in_background()
Ejemplo n.º 10
0
    def _check_and_update_duplicate_display_async(self) -> None:
        note = self.note

        def on_done(result: DuplicateOrEmptyResult.V) -> None:
            if self.note != note:
                return
            self._update_duplicate_display(result)

        QueryOp(
            parent=self.parentWindow,
            op=lambda _: self.note.duplicate_or_empty(),
            success=on_done,
        ).run_in_background()
Ejemplo n.º 11
0
    def _check_and_update_duplicate_display_async(self) -> None:
        note = self.note
        if not note:
            return

        def on_done(result: NoteFieldsCheckResult.V) -> None:
            if self.note != note:
                return
            self._update_duplicate_display(result)

        QueryOp(
            parent=self.parentWindow,
            op=lambda _: note.fields_check(),
            success=on_done,
        ).run_in_background()
Ejemplo n.º 12
0
 def refresh_list(self, *ignored_args: Any) -> None:
     QueryOp(
         parent=self,
         op=lambda col: col.models.all_use_counts(),
         success=self.updateModelsList,
     ).run_in_background()
Ejemplo n.º 13
0
class SidebarTreeView(QTreeView):
    def __init__(self, browser: aqt.browser.Browser) -> None:
        super().__init__()
        self.browser = browser
        self.mw = browser.mw
        self.col = self.mw.col
        self.current_search: str | None = None
        self.valid_drop_types: tuple[SidebarItemType, ...] = ()
        self._refresh_needed = False

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(
            self.onContextMenu)  # type: ignore
        self.setUniformRowHeights(True)
        self.setHeaderHidden(True)
        self.setIndentation(15)
        self.setAutoExpandDelay(600)
        self.setDragDropOverwriteMode(False)
        self.setEditTriggers(QAbstractItemView.EditKeyPressed)

        qconnect(self.expanded, self._on_expansion)
        qconnect(self.collapsed, self._on_collapse)

        # match window background color and tweak style
        bgcolor = QPalette().window().color().name()
        border = theme_manager.color(colors.MEDIUM_BORDER)
        styles = [
            "padding: 3px",
            "padding-right: 0px",
            "border: 0",
            f"background: {bgcolor}",
        ]
        if _want_right_border():
            styles.append(f"border-right: 1px solid {border}")

        self.setStyleSheet("QTreeView { %s }" % ";".join(styles))

        # these do not really belong here, they should be in a higher-level class
        self.toolbar = SidebarToolbar(self)
        self.searchBar = SidebarSearchBar(self)

        gui_hooks.flag_label_did_change.append(self.refresh)

    def cleanup(self) -> None:
        gui_hooks.flag_label_did_change.remove(self.refresh)

    @property
    def tool(self) -> SidebarTool:
        return self._tool

    @tool.setter
    def tool(self, tool: SidebarTool) -> None:
        self._tool = tool
        if tool == SidebarTool.SEARCH:
            selection_mode = QAbstractItemView.SingleSelection
            drag_drop_mode = QAbstractItemView.NoDragDrop
            double_click_expands = False
        else:
            selection_mode = QAbstractItemView.ExtendedSelection
            drag_drop_mode = QAbstractItemView.InternalMove
            double_click_expands = True
        self.setSelectionMode(selection_mode)
        self.setDragDropMode(drag_drop_mode)
        self.setExpandsOnDoubleClick(double_click_expands)

    def model(self) -> SidebarModel:
        return cast(SidebarModel, super().model())

    # Refreshing
    ###########################

    def op_executed(self, changes: OpChanges, handler: object | None,
                    focused: bool) -> None:
        if changes.browser_sidebar and not handler is self:
            self._refresh_needed = True
        if focused:
            self.refresh_if_needed()

    def refresh_if_needed(self) -> None:
        if self._refresh_needed:
            self.refresh()
            self._refresh_needed = False

    def refresh(self, new_current: SidebarItem = None) -> None:
        "Refresh list. No-op if sidebar is not visible."
        if not self.isVisible():
            return

        if not new_current and self.model() and (idx := self.currentIndex()):
            new_current = self.model().item_for_index(idx)

        def on_done(root: SidebarItem) -> None:
            # user may have closed browser
            if sip.isdeleted(self):
                return

            # block repainting during refreshing to avoid flickering
            self.setUpdatesEnabled(False)

            model = SidebarModel(self, root)
            self.setModel(model)

            if self.current_search:
                self.search_for(self.current_search)
            else:
                self._expand_where_necessary(model)
            if new_current:
                self.restore_current(new_current)

            self.setUpdatesEnabled(True)

            # needs to be set after changing model
            qconnect(self.selectionModel().selectionChanged,
                     self._on_selection_changed)

        QueryOp(parent=self.browser,
                op=lambda _: self._root_tree(),
                success=on_done).run_in_background()
Ejemplo n.º 14
0
            self.setModel(model)

            if self.current_search:
                self.search_for(self.current_search)
            else:
                self._expand_where_necessary(model)
            if current_item:
                self.restore_current(current_item)

            self.setUpdatesEnabled(True)

            # needs to be set after changing model
            qconnect(self.selectionModel().selectionChanged, self._on_selection_changed)

        QueryOp(
            parent=self.browser, op=lambda _: self._root_tree(), success=on_done
        ).run_in_background()

    def restore_current(self, current: SidebarItem) -> None:
        if current := self.find_item(current.has_same_id):
            index = self.model().index_for_item(current)
            self.selectionModel().setCurrentIndex(
                index, QItemSelectionModel.SelectCurrent
            )
            self.scrollTo(index)

    def find_item(
        self,
        is_target: Callable[[SidebarItem], bool],
        parent: Optional[SidebarItem] = None,
    ) -> Optional[SidebarItem]: