diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index bf408f834..8ce372e8f 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -24,6 +24,8 @@ browsing-change-to = Change { $val } to: browsing-clear-unused = Clear Unused browsing-clear-unused-tags = Clear Unused Tags browsing-created = Created +browsing-create-filtered-deck = Create Filtered Deck... +browsing-create-filtered-deck_2 = Create Filtered Deck (2nd Filter)... browsing-ctrlandshiftande = Ctrl+Shift+E browsing-current-deck = Current Deck browsing-current-note-type = Current note type: diff --git a/ftl/core/search.ftl b/ftl/core/search.ftl index 4f7fc10bc..768d77109 100644 --- a/ftl/core/search.ftl +++ b/ftl/core/search.ftl @@ -35,3 +35,5 @@ search-card-modified = Card Modified ## +# Tooltip for search lines outside browser +search-view-in-browser = View in browser diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 03187804b..dd493ccf8 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -69,7 +69,7 @@ except ImportError as e: # - make preferences modal? cmd+q does wrong thing -from aqt import addcards, addons, browser, editcurrent # isort:skip +from aqt import addcards, addons, browser, editcurrent, dyndeckconf # isort:skip from aqt import stats, about, preferences, mediasync # isort:skip @@ -80,6 +80,7 @@ class DialogManager: "AddonsDialog": [addons.AddonsDialog, None], "Browser": [browser.Browser, None], "EditCurrent": [editcurrent.EditCurrent, None], + "DynDeckConfDialog": [dyndeckconf.DeckConf, None], "DeckStats": [stats.DeckStats, None], "NewDeckStats": [stats.NewDeckStats, None], "About": [about.show, None], @@ -87,7 +88,7 @@ class DialogManager: "sync_log": [mediasync.MediaSyncDialog, None], } - def open(self, name: str, *args: Any) -> Any: + def open(self, name: str, *args: Any, **kwargs: Any) -> Any: (creator, instance) = self._dialogs[name] if instance: if instance.windowState() & Qt.WindowMinimized: @@ -95,12 +96,11 @@ class DialogManager: instance.activateWindow() instance.raise_() if hasattr(instance, "reopen"): - instance.reopen(*args) - return instance + instance.reopen(*args, **kwargs) else: - instance = creator(*args) + instance = creator(*args, **kwargs) self._dialogs[name][1] = instance - return instance + return instance def markClosed(self, name: str) -> None: self._dialogs[name] = [self._dialogs[name][0], None] diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index bb1a80820..ec2605022 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -162,7 +162,7 @@ class AddCards(QDialog): m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0))) def editHistory(self, nid) -> None: - self.mw.browser_search(SearchTerm(nid=nid)) + aqt.dialogs.open("Browser", self.mw, search=(SearchTerm(nid=nid),)) def addNote(self, note) -> Optional[Note]: note.model()["did"] = self.deckChooser.selectedId() diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 0b85bbccc..ca51dce3d 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -444,7 +444,12 @@ class Browser(QMainWindow): col: Collection editor: Optional[Editor] - def __init__(self, mw: AnkiQt) -> None: + def __init__( + self, + mw: AnkiQt, + card: Optional[Card] = None, + search: Optional[Tuple[Union[str, SearchTerm]]] = None, + ) -> None: QMainWindow.__init__(self, None, Qt.Window) self.mw = mw self.col = self.mw.col @@ -468,7 +473,7 @@ class Browser(QMainWindow): self.setupEditor() self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) - self.setupSearch() + self.setupSearch(card, search) gui_hooks.browser_will_show(self) self.show() @@ -483,6 +488,10 @@ class Browser(QMainWindow): qconnect(f.actionSelectNotes.triggered, self.selectNotes) if not isMac: f.actionClose.setVisible(False) + qconnect(f.actionCreateFilteredDeck.triggered, self.createFilteredDeck) + qconnect(f.actionCreateFilteredDeck2.triggered, self.createFilteredDeck2) + if self.mw.col.schedVer() == 1: + f.menuEdit.removeAction(f.actionCreateFilteredDeck2) # notes qconnect(f.actionAdd.triggered, self.mw.onAddCard) qconnect(f.actionAdd_Tags.triggered, lambda: self.addTags()) @@ -606,17 +615,41 @@ class Browser(QMainWindow): ] self.columns.sort(key=itemgetter(1)) + def reopen( + self, + _mw: AnkiQt, + card: Optional[Card] = None, + search: Optional[Tuple[Union[str, SearchTerm]]] = None, + ) -> None: + if search is not None: + self.search_for_terms(*search) + self.form.searchEdit.setFocus() + elif card: + self.show_single_card(card) + self.form.searchEdit.setFocus() + # Searching ###################################################################### - def setupSearch(self) -> None: + def setupSearch( + self, + card: Optional[Card] = None, + search: Optional[Tuple[Union[str, SearchTerm]]] = None, + ) -> None: qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated) self.form.searchEdit.setCompleter(None) self.form.searchEdit.lineEdit().setPlaceholderText( tr(TR.BROWSING_SEARCH_BAR_HINT) ) self.form.searchEdit.addItems(self.mw.pm.profile["searchHistory"]) - self.search_for(self.col.build_search_string(SearchTerm(deck="current")), "") + if search is not None: + self.search_for_terms(*search) + elif card: + self.show_single_card(card) + else: + self.search_for( + self.col.build_search_string(SearchTerm(deck="current")), "" + ) self.form.searchEdit.setFocus() # search triggered by user @@ -675,15 +708,17 @@ class Browser(QMainWindow): ) return selected - def show_single_card(self, card: Optional[Card]) -> None: - """Try to search for the according note and select the given card.""" + def search_for_terms(self, *search_terms: Union[str, SearchTerm]) -> None: + search = self.col.build_search_string(*search_terms) + self.form.searchEdit.setEditText(search) + self.onSearchActivated() - nid: Optional[int] = card and card.nid or 0 - if nid: + def show_single_card(self, card: Card) -> None: + if card.nid: def on_show_single_card() -> None: self.card = card - search = self.col.build_search_string(SearchTerm(nid=nid)) + search = self.col.build_search_string(SearchTerm(nid=card.nid)) search = gui_hooks.default_search(search, card) self.search_for(search, "") self.focusCid(card.id) @@ -1162,6 +1197,14 @@ where id in %s""" if nids: ChangeModel(self, nids) + def createFilteredDeck(self) -> None: + search = self.form.searchEdit.lineEdit().text() + aqt.dialogs.open("DynDeckConfDialog", self.mw, search=search) + + def createFilteredDeck2(self) -> None: + search = self.form.searchEdit.lineEdit().text() + aqt.dialogs.open("DynDeckConfDialog", self.mw, search_2=search) + # Preview ###################################################################### diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index b63c638c7..c32a6e3f2 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -1,14 +1,16 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -from typing import Dict, List, Optional +from typing import Callable, List, Optional import aqt from anki.collection import SearchTerm +from anki.decks import Deck, DeckRenameError from anki.errors import InvalidInput from anki.lang import without_unicode_isolation -from aqt.main import AnkiQt +from aqt import AnkiQt, gui_hooks from aqt.qt import * +from aqt.theme import theme_manager from aqt.utils import ( TR, HelpPage, @@ -24,25 +26,55 @@ from aqt.utils import ( class DeckConf(QDialog): + """Dialogue to modify and build a filtered deck.""" + def __init__( self, mw: AnkiQt, - first: bool = False, - search: str = "", - deck: Optional[Dict] = None, + search: Optional[str] = None, + search_2: Optional[str] = None, + deck: Optional[Deck] = None, ) -> None: + """If 'deck' is an existing filtered deck, load and modify its settings. + Otherwise, build a new one and derive settings from the current deck. + """ + QDialog.__init__(self, mw) self.mw = mw - self.deck = deck or self.mw.col.decks.current() - self.search = search + self.did: Optional[int] = None self.form = aqt.forms.dyndconf.Ui_Dialog() self.form.setupUi(self) - if first: - label = tr(TR.DECKS_BUILD) - else: - label = tr(TR.ACTIONS_REBUILD) - self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) + self.initialSetup() + self.old_deck = self.mw.col.decks.current() + + if deck and deck["dyn"]: + # modify existing dyn deck + label = tr(TR.ACTIONS_REBUILD) + self.deck = deck + self.loadConf() + elif self.old_deck["dyn"]: + # create new dyn deck from other dyn deck + label = tr(TR.DECKS_BUILD) + self.loadConf(deck=self.old_deck) + self.new_dyn_deck() + else: + # create new dyn deck from regular deck + label = tr(TR.DECKS_BUILD) + self.new_dyn_deck() + self.loadConf() + self.set_default_searches(self.old_deck["name"]) + + self.form.name.setText(self.deck["name"]) + self.form.name.setPlaceholderText(self.deck["name"]) + self.set_custom_searches(search, search_2) + qconnect(self.form.search_button.clicked, self.on_search_button) + qconnect(self.form.search_button_2.clicked, self.on_search_button_2) + color = theme_manager.str_color("link") + self.setStyleSheet( + f"""QPushButton[flat=true] {{ text-align: left; color: {color}; padding: 0; border: 0 }} + QPushButton[flat=true]:hover {{ text-decoration: underline }}""" + ) disable_help_button(self) self.setWindowModality(Qt.WindowModal) qconnect( @@ -51,26 +83,63 @@ class DeckConf(QDialog): self.setWindowTitle( without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) - restoreGeom(self, "dyndeckconf") - self.initialSetup() - self.loadConf() - if search: - search = self.mw.col.build_search_string( - search, SearchTerm(card_state=SearchTerm.CARD_STATE_DUE) - ) - self.form.search.setText(search) - search_2 = self.mw.col.build_search_string( - search, SearchTerm(card_state=SearchTerm.CARD_STATE_NEW) - ) - self.form.search_2.setText(search_2) - self.form.search.selectAll() - + self.form.buttonBox.button(QDialogButtonBox.Ok).setText(label) + self.form.buttonBox.button(QDialogButtonBox.Cancel).setText( + tr(TR.ACTIONS_CANCEL) + ) + self.form.buttonBox.button(QDialogButtonBox.Help).setText(tr(TR.ACTIONS_HELP)) if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) + restoreGeom(self, "dyndeckconf") self.show() - self.exec_() - saveGeom(self, "dyndeckconf") + + def reopen( + self, + _mw: AnkiQt, + search: Optional[str] = None, + search_2: Optional[str] = None, + _deck: Optional[Deck] = None, + ) -> None: + self.set_custom_searches(search, search_2) + + def new_dyn_deck(self) -> None: + suffix: int = 1 + while self.mw.col.decks.id_for_name( + without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=suffix)) + ): + suffix += 1 + name: str = without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=suffix)) + self.did = self.mw.col.decks.new_filtered(name) + self.deck = self.mw.col.decks.current() + + def set_default_searches(self, deck_name: str) -> None: + self.form.search.setText( + self.mw.col.build_search_string( + SearchTerm(deck=deck_name), + SearchTerm(card_state=SearchTerm.CARD_STATE_DUE), + ) + ) + self.form.search_2.setText( + self.mw.col.build_search_string( + SearchTerm(deck=deck_name), + SearchTerm(card_state=SearchTerm.CARD_STATE_NEW), + ) + ) + + def set_custom_searches( + self, search: Optional[str], search_2: Optional[str] + ) -> None: + if search is not None: + self.form.search.setText(search) + self.form.search.setFocus() + self.form.search.selectAll() + if search_2 is not None: + self.form.secondFilter.setChecked(True) + self.form.filter2group.setVisible(True) + self.form.search_2.setText(search_2) + self.form.search_2.setFocus() + self.form.search_2.selectAll() def initialSetup(self) -> None: import anki.consts as cs @@ -80,14 +149,30 @@ class DeckConf(QDialog): qconnect(self.form.resched.stateChanged, self._onReschedToggled) + def on_search_button(self) -> None: + self._on_search_button(self.form.search) + + def on_search_button_2(self) -> None: + self._on_search_button(self.form.search_2) + + def _on_search_button(self, line: QLineEdit) -> None: + try: + search = self.mw.col.build_search_string(line.text()) + except InvalidInput as err: + line.setFocus() + line.selectAll() + show_invalid_search_error(err) + else: + aqt.dialogs.open("Browser", self.mw, search=(search,)) + def _onReschedToggled(self, _state: int) -> None: self.form.previewDelayWidget.setVisible( not self.form.resched.isChecked() and self.mw.col.schedVer() > 1 ) - def loadConf(self) -> None: + def loadConf(self, deck: Optional[Deck] = None) -> None: f = self.form - d = self.deck + d = deck or self.deck f.resched.setChecked(d["resched"]) self._onReschedToggled(0) @@ -123,6 +208,11 @@ class DeckConf(QDialog): def saveConf(self) -> None: f = self.form d = self.deck + + if f.name.text() and d["name"] != f.name.text(): + self.mw.col.decks.rename(d, f.name.text()) + gui_hooks.sidebar_should_refresh_decks() + d["resched"] = f.resched.isChecked() d["delays"] = None @@ -146,29 +236,45 @@ class DeckConf(QDialog): self.mw.col.decks.save(d) def reject(self) -> None: - self.ok = False + if self.did: + self.mw.col.decks.rem(self.did) + self.mw.col.decks.select(self.old_deck["id"]) + saveGeom(self, "dyndeckconf") QDialog.reject(self) + aqt.dialogs.markClosed("DynDeckConfDialog") def accept(self) -> None: try: self.saveConf() except InvalidInput as err: show_invalid_search_error(err) - return - if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): - if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): - return - self.mw.reset() - QDialog.accept(self) + except DeckRenameError as err: + showWarning(err.description) + else: + if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): + if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): + return + saveGeom(self, "dyndeckconf") + self.mw.reset() + QDialog.accept(self) + aqt.dialogs.markClosed("DynDeckConfDialog") + + def closeWithCallback(self, callback: Callable) -> None: + self.reject() + callback() # Step load/save - fixme: share with std options screen ######################################################## - def listToUser(self, l) -> str: - return " ".join([str(x) for x in l]) + def listToUser(self, values: List[Union[float, int]]) -> str: + return " ".join( + [str(int(val)) if int(val) == val else str(val) for val in values] + ) - def userToList(self, w, minSize=1) -> Optional[List[Union[float, int]]]: - items = str(w.text()).split(" ") + def userToList( + self, line: QLineEdit, minSize: int = 1 + ) -> Optional[List[Union[float, int]]]: + items = str(line.text()).split(" ") ret = [] for item in items: if not item: diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 88aadb8d1..b0ba24857 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -542,12 +542,17 @@ class Editor: self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) def showDupes(self) -> None: - self.mw.browser_search( - SearchTerm( - dupe=SearchTerm.Dupe( - notetype_id=self.note.model()["id"], first_field=self.note.fields[0] - ) - ) + aqt.dialogs.open( + "Browser", + self.mw, + search=( + SearchTerm( + dupe=SearchTerm.Dupe( + notetype_id=self.note.model()["id"], + first_field=self.note.fields[0], + ) + ), + ), ) def fieldsAreBlank(self, previousNote: Optional[Note] = None) -> bool: diff --git a/qt/aqt/emptycards.py b/qt/aqt/emptycards.py index 0c64e2e0b..98f7b800b 100644 --- a/qt/aqt/emptycards.py +++ b/qt/aqt/emptycards.py @@ -66,7 +66,7 @@ class EmptyCardsDialog(QDialog): self._delete_button.clicked.connect(self._on_delete) def _on_note_link_clicked(self, link) -> None: - self.mw.browser_search(link) + aqt.dialogs.open("Browser", self.mw, search=(link,)) def _on_delete(self) -> None: self.mw.progress.start() diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index ba6a48980..72d24f97c 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -230,6 +230,9 @@ + + + @@ -583,6 +586,22 @@ Ctrl+Shift+E + + + BROWSING_CREATE_FILTERED_DECK + + + Ctrl+G + + + + + BROWSING_CREATE_FILTERED_DECK_2 + + + Ctrl+Shift+G + + diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index 163fd84b1..6e9eaa441 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -6,14 +6,56 @@ 0 0 - 392 - 472 + 757 + 589 Dialog + + + + DECKS_DECK + + + + + + ACTIONS_NAME + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 40 + 20 + + + + + + + @@ -21,20 +63,28 @@ - - - DECKS_LIMIT_TO + + + Qt::NoFocus + + + SEARCH_VIEW_IN_BROWSER - - - - ACTIONS_SEARCH + + false + + + true + - + + + + @@ -50,18 +100,22 @@ - + + + + DECKS_CARDS_SELECTED_BY - - - - - + + + + DECKS_LIMIT_TO + + @@ -72,23 +126,32 @@ DECKS_FILTER_2 - - + + + + Qt::NoFocus + + + SEARCH_VIEW_IN_BROWSER + - DECKS_LIMIT_TO + ACTIONS_SEARCH + + + false + + + true - - - - - + + - ACTIONS_SEARCH + DECKS_LIMIT_TO @@ -108,6 +171,9 @@ + + + @@ -203,16 +269,19 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Help + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + name + search_button search limit order + search_button_2 search_2 limit_2 order_2 diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 17e9619dd..28cd61113 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -14,7 +14,7 @@ import weakref import zipfile from argparse import Namespace from threading import Thread -from typing import Any, Callable, List, Optional, Sequence, TextIO, Tuple, Union, cast +from typing import Any, Callable, List, Optional, Sequence, TextIO, Tuple, cast import anki import aqt @@ -27,10 +27,9 @@ import aqt.toolbar import aqt.webview from anki import hooks from anki._backend import RustBackend as _RustBackend -from anki.collection import Collection, SearchTerm +from anki.collection import Collection from anki.decks import Deck from anki.hooks import runHook -from anki.lang import without_unicode_isolation from anki.sound import AVTag, SoundOrVideoTag from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields from aqt import gui_hooks @@ -1055,22 +1054,19 @@ title="%s" %s>%s""" % ( aqt.dialogs.open("AddCards", self) def onBrowse(self) -> None: - browser = aqt.dialogs.open("Browser", self) - browser.show_single_card(self.reviewer.card) + aqt.dialogs.open("Browser", self, card=self.reviewer.card) def onEditCurrent(self) -> None: aqt.dialogs.open("EditCurrent", self) - def onDeckConf(self, deck=None) -> None: + def onDeckConf(self, deck: Optional[Deck] = None) -> None: + import aqt.deckconf + if not deck: deck = self.col.decks.current() if deck["dyn"]: - import aqt.dyndeckconf - - aqt.dyndeckconf.DeckConf(self, deck=deck) + aqt.dialogs.open("DynDeckConfDialog", self, deck=deck) else: - import aqt.deckconf - aqt.deckconf.DeckConf(self, deck) def onOverview(self) -> None: @@ -1145,25 +1141,8 @@ title="%s" %s>%s""" % ( # Cramming ########################################################################## - def onCram(self, search: str = "") -> None: - import aqt.dyndeckconf - - n = 1 - deck = self.col.decks.current() - if not search: - if not deck["dyn"]: - search = self.col.build_search_string(SearchTerm(deck=deck["name"])) - while self.col.decks.id_for_name( - without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=n)) - ): - n += 1 - name = without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=n)) - did = self.col.decks.new_filtered(name) - diag = aqt.dyndeckconf.DeckConf(self, first=True, search=search) - if not diag.ok: - # user cancelled first config - self.col.decks.rem(did) - self.col.decks.select(deck["id"]) + def onCram(self) -> None: + aqt.dialogs.open("DynDeckConfDialog", self) # Menu, title bar & status ########################################################################## @@ -1630,14 +1609,3 @@ title="%s" %s>%s""" % ( def serverURL(self) -> str: return "http://127.0.0.1:%d/" % self.mediaServer.getPort() - - # Helpers for all windows - ########################################################################## - - def browser_search(self, *terms: Union[str, SearchTerm]) -> None: - """Wrapper for col.build_search_string() to look up the result in the browser.""" - - search = self.col.build_search_string(*terms) - browser = aqt.dialogs.open("Browser", self) - browser.form.searchEdit.lineEdit().setText(search) - browser.onSearchActivated() diff --git a/qt/aqt/mediacheck.py b/qt/aqt/mediacheck.py index 42cd96a60..0d6695548 100644 --- a/qt/aqt/mediacheck.py +++ b/qt/aqt/mediacheck.py @@ -148,7 +148,7 @@ class MediaChecker: if out is not None: nid, err = out - self.mw.browser_search(SearchTerm(nid=nid)) + aqt.dialogs.open("Browser", self.mw, search=(SearchTerm(nid=nid),)) showText(err, type="html") else: tooltip(tr(TR.MEDIA_CHECK_ALL_LATEX_RENDERED)) diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index 1bd461de4..7bb8b57e6 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -7,7 +7,6 @@ from dataclasses import dataclass from typing import Any, Callable, Dict, List, Optional, Tuple import aqt -from anki.collection import SearchTerm from aqt import gui_hooks from aqt.sound import av_player from aqt.toolbar import BottomBar @@ -72,8 +71,7 @@ class Overview: elif url == "opts": self.mw.onDeckConf() elif url == "cram": - deck = self.mw.col.decks.current()["name"] - self.mw.onCram(self.mw.col.build_search_string(SearchTerm(deck=deck))) + aqt.dialogs.open("DynDeckConfDialog", self.mw) elif url == "refresh": self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected()) self.mw.reset()