mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
move "find duplicates" into separate file
As far as I'm aware, no add-ons were extending that screen, so I've dropped the old context object and the export in browser/__init__.py
This commit is contained in:
parent
220e14bf0f
commit
78a3536bfc
4 changed files with 139 additions and 123 deletions
|
@ -7,7 +7,7 @@ import sys
|
||||||
import aqt
|
import aqt
|
||||||
|
|
||||||
from .browser import Browser
|
from .browser import Browser
|
||||||
from .dialogs import CardInfoDialog, ChangeModel, FindAndReplaceDialog, FindDupesDialog
|
from .dialogs import CardInfoDialog, ChangeModel, FindAndReplaceDialog
|
||||||
from .previewer import BrowserPreviewer, MultiCardPreviewer, Previewer
|
from .previewer import BrowserPreviewer, MultiCardPreviewer, Previewer
|
||||||
from .sidebar import (
|
from .sidebar import (
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import html
|
from typing import Callable, Optional, Sequence, Tuple, Union
|
||||||
from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
|
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
|
@ -18,19 +17,13 @@ from anki.stats import CardStats
|
||||||
from anki.tags import MARKED_TAG
|
from anki.tags import MARKED_TAG
|
||||||
from anki.utils import ids2str, isMac
|
from anki.utils import ids2str, isMac
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.browser.dialogs import (
|
from aqt.browser.dialogs import CardInfoDialog, ChangeModel, FindAndReplaceDialog
|
||||||
CardInfoDialog,
|
|
||||||
ChangeModel,
|
|
||||||
FindAndReplaceDialog,
|
|
||||||
FindDupesDialog,
|
|
||||||
)
|
|
||||||
from aqt.browser.previewer import BrowserPreviewer as PreviewDialog
|
from aqt.browser.previewer import BrowserPreviewer as PreviewDialog
|
||||||
from aqt.browser.previewer import Previewer
|
from aqt.browser.previewer import Previewer
|
||||||
from aqt.browser.sidebar import SidebarTreeView
|
from aqt.browser.sidebar import SidebarTreeView
|
||||||
from aqt.browser.table import Table
|
from aqt.browser.table import Table
|
||||||
from aqt.editor import Editor
|
from aqt.editor import Editor
|
||||||
from aqt.exporting import ExportDialog
|
from aqt.exporting import ExportDialog
|
||||||
from aqt.main import ResetReason
|
|
||||||
from aqt.operations.card import set_card_deck, set_card_flag
|
from aqt.operations.card import set_card_deck, set_card_flag
|
||||||
from aqt.operations.collection import undo
|
from aqt.operations.collection import undo
|
||||||
from aqt.operations.note import remove_notes
|
from aqt.operations.note import remove_notes
|
||||||
|
@ -52,26 +45,20 @@ from aqt.utils import (
|
||||||
HelpPage,
|
HelpPage,
|
||||||
KeyboardModifiersPressed,
|
KeyboardModifiersPressed,
|
||||||
current_top_level_widget,
|
current_top_level_widget,
|
||||||
disable_help_button,
|
|
||||||
ensure_editor_saved,
|
ensure_editor_saved,
|
||||||
getTag,
|
getTag,
|
||||||
no_arg_trigger,
|
no_arg_trigger,
|
||||||
openHelp,
|
openHelp,
|
||||||
qtMenuShortcutWorkaround,
|
qtMenuShortcutWorkaround,
|
||||||
restore_combo_history,
|
|
||||||
restore_combo_index_for_session,
|
|
||||||
restoreGeom,
|
restoreGeom,
|
||||||
restoreSplitter,
|
restoreSplitter,
|
||||||
restoreState,
|
restoreState,
|
||||||
save_combo_history,
|
|
||||||
save_combo_index_for_session,
|
|
||||||
saveGeom,
|
saveGeom,
|
||||||
saveSplitter,
|
saveSplitter,
|
||||||
saveState,
|
saveState,
|
||||||
showInfo,
|
showInfo,
|
||||||
showWarning,
|
showWarning,
|
||||||
skip_if_selection_is_empty,
|
skip_if_selection_is_empty,
|
||||||
tooltip,
|
|
||||||
tr,
|
tr,
|
||||||
)
|
)
|
||||||
from aqt.webview import AnkiWebView
|
from aqt.webview import AnkiWebView
|
||||||
|
@ -864,107 +851,9 @@ where id in %s"""
|
||||||
@no_arg_trigger
|
@no_arg_trigger
|
||||||
@ensure_editor_saved
|
@ensure_editor_saved
|
||||||
def onFindDupes(self) -> None:
|
def onFindDupes(self) -> None:
|
||||||
import anki.find
|
from aqt.browser.find_duplicates import FindDuplicatesDialog
|
||||||
|
|
||||||
d = QDialog(self)
|
FindDuplicatesDialog(browser=self, mw=self.mw)
|
||||||
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 += "<p><ol>"
|
|
||||||
for val, nids in res:
|
|
||||||
t += (
|
|
||||||
"""<li><a href=# onclick="pycmd('%s');return false;">%s</a>: %s</a>"""
|
|
||||||
% (
|
|
||||||
html.escape(
|
|
||||||
self.col.build_search_string(
|
|
||||||
SearchNode(nids=SearchNode.IdList(ids=nids))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
tr.browsing_note_count(count=len(nids)),
|
|
||||||
html.escape(val),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
t += "</ol>"
|
|
||||||
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()
|
|
||||||
|
|
||||||
# Jumping
|
# Jumping
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Any, Dict, List, Optional, Sequence
|
from typing import Any, Dict, List, Optional, Sequence
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
@ -32,12 +31,6 @@ from aqt.utils import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class FindDupesDialog:
|
|
||||||
dialog: QDialog
|
|
||||||
browser: aqt.browser.Browser
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeModel(QDialog):
|
class ChangeModel(QDialog):
|
||||||
def __init__(self, browser: aqt.browser.Browser, nids: Sequence[NoteId]) -> None:
|
def __init__(self, browser: aqt.browser.Browser, nids: Sequence[NoteId]) -> None:
|
||||||
QDialog.__init__(self, browser)
|
QDialog.__init__(self, browser)
|
||||||
|
|
134
qt/aqt/browser/find_duplicates.py
Normal file
134
qt/aqt/browser/find_duplicates.py
Normal file
|
@ -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 += "<p><ol>"
|
||||||
|
for val, nids in res:
|
||||||
|
t += (
|
||||||
|
"""<li><a href=# onclick="pycmd('%s');return false;">%s</a>: %s</a>"""
|
||||||
|
% (
|
||||||
|
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 += "</ol>"
|
||||||
|
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()
|
Loading…
Reference in a new issue