diff --git a/qt/aqt/browser/__init__.py b/qt/aqt/browser/__init__.py
index 6d1de886b..ad12ca68b 100644
--- a/qt/aqt/browser/__init__.py
+++ b/qt/aqt/browser/__init__.py
@@ -7,7 +7,7 @@ import sys
import aqt
from .browser import Browser
-from .dialogs import CardInfoDialog, ChangeModel, FindAndReplaceDialog, FindDupesDialog
+from .dialogs import CardInfoDialog, ChangeModel, FindAndReplaceDialog
from .previewer import BrowserPreviewer, MultiCardPreviewer, Previewer
from .sidebar import (
SidebarItem,
diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py
index 443168bb2..d4910231c 100644
--- a/qt/aqt/browser/browser.py
+++ b/qt/aqt/browser/browser.py
@@ -3,8 +3,7 @@
from __future__ import annotations
-import html
-from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
+from typing import Callable, Optional, Sequence, Tuple, Union
import aqt
import aqt.forms
@@ -18,19 +17,13 @@ from anki.stats import CardStats
from anki.tags import MARKED_TAG
from anki.utils import ids2str, isMac
from aqt import AnkiQt, gui_hooks
-from aqt.browser.dialogs import (
- CardInfoDialog,
- ChangeModel,
- FindAndReplaceDialog,
- FindDupesDialog,
-)
+from aqt.browser.dialogs import CardInfoDialog, ChangeModel, FindAndReplaceDialog
from aqt.browser.previewer import BrowserPreviewer as PreviewDialog
from aqt.browser.previewer import Previewer
from aqt.browser.sidebar import SidebarTreeView
from aqt.browser.table import Table
from aqt.editor import Editor
from aqt.exporting import ExportDialog
-from aqt.main import ResetReason
from aqt.operations.card import set_card_deck, set_card_flag
from aqt.operations.collection import undo
from aqt.operations.note import remove_notes
@@ -52,26 +45,20 @@ from aqt.utils import (
HelpPage,
KeyboardModifiersPressed,
current_top_level_widget,
- disable_help_button,
ensure_editor_saved,
getTag,
no_arg_trigger,
openHelp,
qtMenuShortcutWorkaround,
- restore_combo_history,
- restore_combo_index_for_session,
restoreGeom,
restoreSplitter,
restoreState,
- save_combo_history,
- save_combo_index_for_session,
saveGeom,
saveSplitter,
saveState,
showInfo,
showWarning,
skip_if_selection_is_empty,
- tooltip,
tr,
)
from aqt.webview import AnkiWebView
@@ -864,107 +851,9 @@ where id in %s"""
@no_arg_trigger
@ensure_editor_saved
def onFindDupes(self) -> None:
- import anki.find
+ from aqt.browser.find_duplicates import FindDuplicatesDialog
- d = QDialog(self)
- self.mw.garbage_collect_on_dialog_finish(d)
- frm = aqt.forms.finddupes.Ui_Dialog()
- frm.setupUi(d)
- restoreGeom(d, "findDupes")
- disable_help_button(d)
- searchHistory = restore_combo_history(frm.search, "findDupesFind")
-
- fields = sorted(
- anki.find.fieldNames(self.col, downcase=False), key=lambda x: x.lower()
- )
- frm.fields.addItems(fields)
- restore_combo_index_for_session(frm.fields, fields, "findDupesFields")
- self._dupesButton: Optional[QPushButton] = None
-
- # links
- frm.webView.set_title("find duplicates")
- web_context = FindDupesDialog(dialog=d, browser=self)
- frm.webView.set_bridge_command(self.dupeLinkClicked, web_context)
- frm.webView.stdHtml("", context=web_context)
-
- def onFin(code: Any) -> None:
- saveGeom(d, "findDupes")
-
- qconnect(d.finished, onFin)
-
- def onClick() -> None:
- search_text = save_combo_history(frm.search, searchHistory, "findDupesFind")
- save_combo_index_for_session(frm.fields, "findDupesFields")
- field = fields[frm.fields.currentIndex()]
- self.duplicatesReport(frm.webView, field, search_text, frm, web_context)
-
- search = frm.buttonBox.addButton(
- tr.actions_search(), QDialogButtonBox.ActionRole
- )
- qconnect(search.clicked, onClick)
- d.show()
-
- def duplicatesReport(
- self,
- web: AnkiWebView,
- fname: str,
- search: str,
- frm: aqt.forms.finddupes.Ui_Dialog,
- web_context: FindDupesDialog,
- ) -> None:
- self.mw.progress.start()
- try:
- res = self.mw.col.findDupes(fname, search)
- except Exception as e:
- self.mw.progress.finish()
- showWarning(str(e))
- return
- if not self._dupesButton:
- self._dupesButton = b = frm.buttonBox.addButton(
- tr.browsing_tag_duplicates(), QDialogButtonBox.ActionRole
- )
- qconnect(b.clicked, lambda: self._onTagDupes(res))
- t = ""
- groups = len(res)
- notes = sum(len(r[1]) for r in res)
- part1 = tr.browsing_group(count=groups)
- part2 = tr.browsing_note_count(count=notes)
- t += tr.browsing_found_as_across_bs(part=part1, whole=part2)
- t += "
"
- for val, nids in res:
- t += (
- """- %s: %s"""
- % (
- html.escape(
- self.col.build_search_string(
- SearchNode(nids=SearchNode.IdList(ids=nids))
- )
- ),
- tr.browsing_note_count(count=len(nids)),
- html.escape(val),
- )
- )
- t += "
"
- web.stdHtml(t, context=web_context)
- self.mw.progress.finish()
-
- def _onTagDupes(self, res: List[Any]) -> None:
- if not res:
- return
- self.begin_reset()
- self.mw.checkpoint(tr.browsing_tag_duplicates())
- nids = set()
- for _, nidlist in res:
- nids.update(nidlist)
- self.col.tags.bulk_add(list(nids), tr.browsing_duplicate())
- self.mw.progress.finish()
- self.end_reset()
- self.mw.requireReset(reason=ResetReason.BrowserTagDupes, context=self)
- tooltip(tr.browsing_notes_tagged())
-
- def dupeLinkClicked(self, link: str) -> None:
- self.search_for(link)
- self.onNote()
+ FindDuplicatesDialog(browser=self, mw=self.mw)
# Jumping
######################################################################
diff --git a/qt/aqt/browser/dialogs.py b/qt/aqt/browser/dialogs.py
index 7ed59237d..eff6675ee 100644
--- a/qt/aqt/browser/dialogs.py
+++ b/qt/aqt/browser/dialogs.py
@@ -3,7 +3,6 @@
from __future__ import annotations
-from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Sequence
import aqt
@@ -32,12 +31,6 @@ from aqt.utils import (
)
-@dataclass
-class FindDupesDialog:
- dialog: QDialog
- browser: aqt.browser.Browser
-
-
class ChangeModel(QDialog):
def __init__(self, browser: aqt.browser.Browser, nids: Sequence[NoteId]) -> None:
QDialog.__init__(self, browser)
diff --git a/qt/aqt/browser/find_duplicates.py b/qt/aqt/browser/find_duplicates.py
new file mode 100644
index 000000000..283c06a7a
--- /dev/null
+++ b/qt/aqt/browser/find_duplicates.py
@@ -0,0 +1,134 @@
+# Copyright: Ankitects Pty Ltd and contributors
+# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+from __future__ import annotations
+
+import html
+from typing import Any, List, Optional
+
+import anki
+import anki.find
+import aqt
+from anki.collection import SearchNode
+from aqt.qt import *
+
+from ..main import ResetReason
+from ..utils import (
+ disable_help_button,
+ restore_combo_history,
+ restore_combo_index_for_session,
+ restoreGeom,
+ save_combo_history,
+ save_combo_index_for_session,
+ saveGeom,
+ showWarning,
+ tooltip,
+ tr,
+)
+from ..webview import AnkiWebView
+from . import Browser
+
+
+class FindDuplicatesDialog(QDialog):
+ def __init__(self, browser: Browser, mw: aqt.AnkiQt):
+ super().__init__(parent=browser)
+ self.browser = browser
+ self.mw = mw
+ self.mw.garbage_collect_on_dialog_finish(self)
+ form = aqt.forms.finddupes.Ui_Dialog()
+ form.setupUi(self)
+ restoreGeom(self, "findDupes")
+ disable_help_button(self)
+ searchHistory = restore_combo_history(form.search, "findDupesFind")
+
+ fields = sorted(
+ anki.find.fieldNames(self.mw.col, downcase=False), key=lambda x: x.lower()
+ )
+ form.fields.addItems(fields)
+ restore_combo_index_for_session(form.fields, fields, "findDupesFields")
+ self._dupesButton: Optional[QPushButton] = None
+
+ # links
+ form.webView.set_title("find duplicates")
+ form.webView.set_bridge_command(self.dupeLinkClicked, context=self)
+ form.webView.stdHtml("", context=self)
+
+ def onFin(code: Any) -> None:
+ saveGeom(self, "findDupes")
+
+ qconnect(self.finished, onFin)
+
+ def onClick() -> None:
+ search_text = save_combo_history(
+ form.search, searchHistory, "findDupesFind"
+ )
+ save_combo_index_for_session(form.fields, "findDupesFields")
+ field = fields[form.fields.currentIndex()]
+ self.duplicatesReport(form.webView, field, search_text, form)
+
+ search = form.buttonBox.addButton(
+ tr.actions_search(), QDialogButtonBox.ActionRole
+ )
+ qconnect(search.clicked, onClick)
+ self.show()
+
+ def duplicatesReport(
+ self,
+ web: AnkiWebView,
+ fname: str,
+ search: str,
+ frm: aqt.forms.finddupes.Ui_Dialog,
+ ) -> None:
+ self.mw.progress.start()
+ try:
+ res = self.mw.col.findDupes(fname, search)
+ except Exception as e:
+ self.mw.progress.finish()
+ showWarning(str(e))
+ return
+ if not self._dupesButton:
+ self._dupesButton = b = frm.buttonBox.addButton(
+ tr.browsing_tag_duplicates(), QDialogButtonBox.ActionRole
+ )
+ qconnect(b.clicked, lambda: self._onTagDupes(res))
+ t = ""
+ groups = len(res)
+ notes = sum(len(r[1]) for r in res)
+ part1 = tr.browsing_group(count=groups)
+ part2 = tr.browsing_note_count(count=notes)
+ t += tr.browsing_found_as_across_bs(part=part1, whole=part2)
+ t += ""
+ for val, nids in res:
+ t += (
+ """- %s: %s"""
+ % (
+ html.escape(
+ self.mw.col.build_search_string(
+ SearchNode(nids=SearchNode.IdList(ids=nids))
+ )
+ ),
+ tr.browsing_note_count(count=len(nids)),
+ html.escape(val),
+ )
+ )
+ t += "
"
+ web.stdHtml(t, context=self)
+ self.mw.progress.finish()
+
+ def _onTagDupes(self, res: List[Any]) -> None:
+ if not res:
+ return
+ self.browser.begin_reset()
+ self.mw.checkpoint(tr.browsing_tag_duplicates())
+ nids = set()
+ for _, nidlist in res:
+ nids.update(nidlist)
+ self.mw.col.tags.bulk_add(list(nids), tr.browsing_duplicate())
+ self.mw.progress.finish()
+ self.browser.end_reset()
+ self.mw.requireReset(reason=ResetReason.BrowserTagDupes, context=self)
+ tooltip(tr.browsing_notes_tagged())
+
+ def dupeLinkClicked(self, link: str) -> None:
+ self.browser.search_for(link)
+ self.browser.onNote()