From de7e40537d1d532252c7e2839bc8782eaa1bd22a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 31 May 2016 18:51:40 +1000 Subject: [PATCH] port majority of code to qt5.5+ - a few issues to work out still, and editor changes not done yet - for communication between webengine and python code, we set window .location to 'http://anki/' - the leading http is necessary for qt to call the link handler, which was introduced in qt5.5 - the designer files now use a promoted qobject to create instances of AnkiWebView - we use the css zoom property to alter webengine font size based on system dpi - prefs and addons folder stored in new location (at least for now) --- aqt/__init__.py | 26 +++---- aqt/about.py | 6 -- aqt/addcards.py | 13 ++-- aqt/addons.py | 28 ++++---- aqt/browser.py | 164 ++++++++++++++++++------------------------ aqt/clayout.py | 47 +++++------- aqt/customstudy.py | 14 ++-- aqt/deckbrowser.py | 45 ++++++------ aqt/deckchooser.py | 8 +-- aqt/deckconf.py | 24 +++---- aqt/downloader.py | 6 +- aqt/dyndeckconf.py | 4 +- aqt/editcurrent.py | 4 +- aqt/errors.py | 18 +++-- aqt/exporting.py | 5 +- aqt/fields.py | 16 ++--- aqt/importing.py | 12 ++-- aqt/main.py | 99 +++++++++++++------------ aqt/modelchooser.py | 10 ++- aqt/models.py | 31 ++++---- aqt/overview.py | 13 ++-- aqt/preferences.py | 17 ++--- aqt/profiles.py | 21 +++--- aqt/progress.py | 2 +- aqt/qt.py | 32 ++++----- aqt/reviewer.py | 41 +++++------ aqt/stats.py | 24 +++---- aqt/studydeck.py | 14 ++-- aqt/sync.py | 28 ++++---- aqt/tagedit.py | 16 +++-- aqt/toolbar.py | 17 +++-- aqt/update.py | 16 +++-- aqt/utils.py | 31 +++----- aqt/webview.py | 136 ++++++++++++++++++----------------- designer/about.ui | 20 ++++-- designer/browser.ui | 36 +++++++--- designer/finddupes.ui | 20 ++++-- designer/stats.ui | 31 ++++++-- tools/build_ui.sh | 8 +-- 39 files changed, 530 insertions(+), 573 deletions(-) diff --git a/aqt/__init__.py b/aqt/__init__.py index 3eb5d881c..c377a1b92 100644 --- a/aqt/__init__.py +++ b/aqt/__init__.py @@ -1,7 +1,6 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import getpass -import os import sys import optparse import tempfile @@ -118,6 +117,8 @@ class AnkiApp(QApplication): # Single instance support on Win32/Linux ################################################## + appMsg = pyqtSignal(str) + KEY = "anki"+checksum(getpass.getuser()) TMOUT = 5000 @@ -140,7 +141,7 @@ class AnkiApp(QApplication): # previous instance died QLocalServer.removeServer(self.KEY) self._srv = QLocalServer(self) - self.connect(self._srv, SIGNAL("newConnection()"), self.onRecv) + self._srv.newConnection.connect(self.onRecv) self._srv.listen(self.KEY) return False @@ -150,7 +151,7 @@ class AnkiApp(QApplication): if not sock.waitForConnected(self.TMOUT): # first instance or previous instance dead return False - sock.write(txt) + sock.write(txt.encode("utf8")) if not sock.waitForBytesWritten(self.TMOUT): # existing instance running but hung return False @@ -162,9 +163,8 @@ class AnkiApp(QApplication): if not sock.waitForReadyRead(self.TMOUT): sys.stderr.write(sock.errorString()) return - buf = sock.readAll() - buf = str(buf, sys.getfilesystemencoding(), "ignore") - self.emit(SIGNAL("appMsg"), buf) + path = bytes(sock.readAll()).decode("utf8") + self.appMsg.emit(path) sock.disconnectFromServer() # OS X file/url handler @@ -172,7 +172,7 @@ class AnkiApp(QApplication): def event(self, evt): if evt.type() == QEvent.FileOpen: - self.emit(SIGNAL("appMsg"), evt.file() or "raise") + self.appMsg.emit(evt.file() or "raise") return True return QApplication.event(self, evt) @@ -207,12 +207,9 @@ def _run(): # on osx we'll need to add the qt plugins to the search path if isMac and getattr(sys, 'frozen', None): - rd = os.path.abspath(moduleDir + "/../../..") + rd = os.path.abspath(moduleDir + "/../../../plugins") QCoreApplication.setLibraryPaths([rd]) - if isMac: - QFont.insertSubstitution(".Lucida Grande UI", "Lucida Grande") - # create the app app = AnkiApp(sys.argv) QCoreApplication.setApplicationName("Anki") @@ -234,13 +231,6 @@ No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \ environment points to a valid, writable folder.""") return - # qt version must be up to date - if qtmajor <= 4 and qtminor <= 6: - QMessageBox.warning( - None, "Error", "Your Qt version is known to be buggy. Until you " - "upgrade to a newer Qt, you may experience issues such as images " - "failing to show up during review.") - # profile manager from aqt.profiles import ProfileManager pm = ProfileManager(opts.base, opts.profile) diff --git a/aqt/about.py b/aqt/about.py index 0f265fa54..6875c9d07 100644 --- a/aqt/about.py +++ b/aqt/about.py @@ -11,12 +11,6 @@ def show(parent): dialog = QDialog(parent) abt = aqt.forms.about.Ui_About() abt.setupUi(dialog) - abt.label.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) - def onLink(url): - openLink(url.toString()) - parent.connect(abt.label, - SIGNAL("linkClicked(QUrl)"), - onLink) abouttext = "
" abouttext += '

' + _("Anki is a friendly, intelligent spaced learning \ system. It's free and open source.") diff --git a/aqt/addcards.py b/aqt/addcards.py index 306bcdd3b..4effd4b98 100644 --- a/aqt/addcards.py +++ b/aqt/addcards.py @@ -53,20 +53,18 @@ class AddCards(QDialog): ar = QDialogButtonBox.ActionRole # add self.addButton = bb.addButton(_("Add"), ar) + self.addButton.clicked.connect(self.addCards) self.addButton.setShortcut(QKeySequence("Ctrl+Return")) self.addButton.setToolTip(shortcut(_("Add (shortcut: ctrl+enter)"))) - self.connect(self.addButton, SIGNAL("clicked()"), self.addCards) # close self.closeButton = QPushButton(_("Close")) self.closeButton.setAutoDefault(False) - bb.addButton(self.closeButton, - QDialogButtonBox.RejectRole) + bb.addButton(self.closeButton, QDialogButtonBox.RejectRole) # help - self.helpButton = QPushButton(_("Help")) + self.helpButton = QPushButton(_("Help"), clicked=self.helpRequested) self.helpButton.setAutoDefault(False) bb.addButton(self.helpButton, QDialogButtonBox.HelpRole) - self.connect(self.helpButton, SIGNAL("clicked()"), self.helpRequested) # history b = bb.addButton( _("History")+ " "+downArrow(), ar) @@ -76,7 +74,7 @@ class AddCards(QDialog): sc = "Ctrl+H" b.setShortcut(QKeySequence(sc)) b.setToolTip(_("Shortcut: %s") % shortcut(sc)) - self.connect(b, SIGNAL("clicked()"), self.onHistory) + b.clicked.connect(self.onHistory) b.setEnabled(False) self.historyButton = b @@ -145,8 +143,7 @@ class AddCards(QDialog): m = QMenu(self) for nid, txt in self.history: a = m.addAction(_("Edit %s") % txt) - a.connect(a, SIGNAL("triggered()"), - lambda nid=nid: self.editHistory(nid)) + a.triggered.connect(lambda b, nid=nid: self.editHistory(nid)) runHook("AddCards.onHistory", self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0,0))) diff --git a/aqt/addons.py b/aqt/addons.py index 82183045b..9eeebc427 100644 --- a/aqt/addons.py +++ b/aqt/addons.py @@ -21,9 +21,9 @@ class AddonManager(object): def __init__(self, mw): self.mw = mw - f = self.mw.form; s = SIGNAL("triggered()") - self.mw.connect(f.actionOpenPluginFolder, s, self.onOpenAddonFolder) - self.mw.connect(f.actionDownloadSharedPlugin, s, self.onGetAddons) + f = self.mw.form + f.actionOpenPluginFolder.triggered.connect(self.onOpenAddonFolder) + f.actionDownloadSharedPlugin.triggered.connect(self.onGetAddons) self._menus = [] if isWin: self.clearAddonCache() @@ -46,7 +46,7 @@ class AddonManager(object): # Menus ###################################################################### - def onOpenAddonFolder(self, path=None): + def onOpenAddonFolder(self, checked, path=None): if path is None: path = self.addonsFolder() openFolder(path) @@ -58,14 +58,11 @@ class AddonManager(object): m = self.mw.form.menuPlugins.addMenu( os.path.splitext(file)[0]) self._menus.append(m) - a = QAction(_("Edit..."), self.mw) + a = QAction(_("Edit..."), self.mw, triggered=self.onEdit) p = os.path.join(self.addonsFolder(), file) - self.mw.connect(a, SIGNAL("triggered()"), - lambda p=p: self.onEdit(p)) + m.addAction(a) - a = QAction(_("Delete..."), self.mw) - self.mw.connect(a, SIGNAL("triggered()"), - lambda p=p: self.onRem(p)) + a = QAction(_("Delete..."), self.mw, triggered=self.onRem) m.addAction(a) def onEdit(self, path): @@ -74,8 +71,7 @@ class AddonManager(object): frm.setupUi(d) d.setWindowTitle(os.path.basename(path)) frm.text.setPlainText(open(path).read()) - d.connect(frm.buttonBox, SIGNAL("accepted()"), - lambda: self.onAcceptEdit(path, frm)) + frm.buttonBox.accepted.connect(lambda: self.onAcceptEdit(path, frm)) d.exec_() def onAcceptEdit(self, path, frm): @@ -94,8 +90,6 @@ class AddonManager(object): def addonsFolder(self): dir = self.mw.pm.addonFolder() - if isWin: - dir = dir.encode(sys.getfilesystemencoding()) return dir def clearAddonCache(self): @@ -115,7 +109,9 @@ class AddonManager(object): ###################################################################### def onGetAddons(self): - GetAddons(self.mw) + showInfo("Currently disabled, as add-ons built for 2.0.x will need updating") + + # GetAddons(self.mw) def install(self, data, fname): if fname.endswith(".py"): @@ -146,7 +142,7 @@ class GetAddons(QDialog): self.form.setupUi(self) b = self.form.buttonBox.addButton( _("Browse"), QDialogButtonBox.ActionRole) - self.connect(b, SIGNAL("clicked()"), self.onBrowse) + b.clicked.connect(self.onBrowse) restoreGeom(self, "getaddons", adjustSize=True) self.exec_() saveGeom(self, "getaddons") diff --git a/aqt/browser.py b/aqt/browser.py index 7c7e4e3f7..685a8a0de 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -55,7 +55,7 @@ class DataModel(QAbstractTableModel): del self.cardObjs[c.id] refresh = True if refresh: - self.emit(SIGNAL("layoutChanged()")) + self.layoutChanged.emit() # Model interface ###################################################################### @@ -382,61 +382,61 @@ class Browser(QMainWindow): def setupMenus(self): # actions - c = self.connect; f = self.form; s = SIGNAL("triggered()") + f = self.form if not isMac: f.actionClose.setVisible(False) - c(f.actionReposition, s, self.reposition) - c(f.actionReschedule, s, self.reschedule) - c(f.actionCram, s, self.cram) - c(f.actionChangeModel, s, self.onChangeModel) + f.actionReposition.triggered.connect(self.reposition) + f.actionReschedule.triggered.connect(self.reschedule) + f.actionCram.triggered.connect(self.cram) + f.actionChangeModel.triggered.connect(self.onChangeModel) # edit - c(f.actionUndo, s, self.mw.onUndo) - c(f.previewButton, SIGNAL("clicked()"), self.onTogglePreview) + f.actionUndo.triggered.connect(self.mw.onUndo) + f.previewButton.clicked.connect(self.onTogglePreview) f.previewButton.setToolTip(_("Preview Selected Card (%s)") % shortcut(_("Ctrl+Shift+P"))) - c(f.actionInvertSelection, s, self.invertSelection) - c(f.actionSelectNotes, s, self.selectNotes) - c(f.actionFindReplace, s, self.onFindReplace) - c(f.actionFindDuplicates, s, self.onFindDupes) + f.actionInvertSelection.triggered.connect(self.invertSelection) + f.actionSelectNotes.triggered.connect(self.selectNotes) + f.actionFindReplace.triggered.connect(self.onFindReplace) + f.actionFindDuplicates.triggered.connect(self.onFindDupes) # jumps - c(f.actionPreviousCard, s, self.onPreviousCard) - c(f.actionNextCard, s, self.onNextCard) - c(f.actionFirstCard, s, self.onFirstCard) - c(f.actionLastCard, s, self.onLastCard) - c(f.actionFind, s, self.onFind) - c(f.actionNote, s, self.onNote) - c(f.actionTags, s, self.onTags) - c(f.actionCardList, s, self.onCardList) + f.actionPreviousCard.triggered.connect(self.onPreviousCard) + f.actionNextCard.triggered.connect(self.onNextCard) + f.actionFirstCard.triggered.connect(self.onFirstCard) + f.actionLastCard.triggered.connect(self.onLastCard) + f.actionFind.triggered.connect(self.onFind) + f.actionNote.triggered.connect(self.onNote) + f.actionTags.triggered.connect(self.onTags) + f.actionCardList.triggered.connect(self.onCardList) # help - c(f.actionGuide, s, self.onHelp) + f.actionGuide.triggered.connect(self.onHelp) # keyboard shortcut for shift+home/end self.pgUpCut = QShortcut(QKeySequence("Shift+Home"), self) - c(self.pgUpCut, SIGNAL("activated()"), self.onFirstCard) + self.pgUpCut.activated.connect(self.onFirstCard) self.pgDownCut = QShortcut(QKeySequence("Shift+End"), self) - c(self.pgDownCut, SIGNAL("activated()"), self.onLastCard) + self.pgDownCut.activated.connect(self.onLastCard) # add note self.addCut = QShortcut(QKeySequence("Ctrl+E"), self) - c(self.addCut, SIGNAL("activated()"), self.mw.onAddCard) + self.addCut.activated.connect(self.mw.onAddCard) # card info self.infoCut = QShortcut(QKeySequence("Ctrl+Shift+I"), self) - c(self.infoCut, SIGNAL("activated()"), self.showCardInfo) + self.infoCut.activated.connect(self.showCardInfo) # set deck self.changeDeckCut = QShortcut(QKeySequence("Ctrl+D"), self) - c(self.changeDeckCut, SIGNAL("activated()"), self.setDeck) + self.changeDeckCut.activated.connect(self.setDeck) # add/remove tags self.tagCut1 = QShortcut(QKeySequence("Ctrl+Shift+T"), self) - c(self.tagCut1, SIGNAL("activated()"), self.addTags) + self.tagCut1.activated.connect(self.addTags) self.tagCut2 = QShortcut(QKeySequence("Ctrl+Alt+T"), self) - c(self.tagCut2, SIGNAL("activated()"), self.deleteTags) + self.tagCut2.activated.connect(self.deleteTags) self.tagCut3 = QShortcut(QKeySequence("Ctrl+K"), self) - c(self.tagCut3, SIGNAL("activated()"), self.onMark) + self.tagCut3.activated.connect(self.onMark) # suspending self.susCut1 = QShortcut(QKeySequence("Ctrl+J"), self) - c(self.susCut1, SIGNAL("activated()"), self.onSuspend) + self.susCut1.activated.connect(self.onSuspend) # deletion self.delCut1 = QShortcut(QKeySequence("Delete"), self) self.delCut1.setAutoRepeat(False) - c(self.delCut1, SIGNAL("activated()"), self.deleteNotes) + self.delCut1.activated.connect(self.deleteNotes) # add-on hook runHook('browser.setupMenus', self) self.mw.maybeHideAccelerators(self) @@ -507,12 +507,8 @@ class Browser(QMainWindow): def setupSearch(self): self.filterTimer = None self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self)) - self.connect(self.form.searchButton, - SIGNAL("clicked()"), - self.onSearch) - self.connect(self.form.searchEdit.lineEdit(), - SIGNAL("returnPressed()"), - self.onSearch) + self.form.searchButton.clicked.connect(self.onSearch) + self.form.searchEdit.lineEdit().returnPressed.connect(self.onSearch) self.form.searchEdit.setCompleter(None) self.form.searchEdit.addItems(self.mw.pm.profile['searchHistory']) @@ -578,9 +574,7 @@ class Browser(QMainWindow): self.form.tableView.setModel(self.model) self.form.tableView.selectionModel() self.form.tableView.setItemDelegate(StatusDelegate(self, self.model)) - self.connect(self.form.tableView.selectionModel(), - SIGNAL("selectionChanged(QItemSelection,QItemSelection)"), - self.onRowChanged) + self.form.tableView.selectionModel().selectionChanged.connect(self.onRowChanged) def setupEditor(self): self.editor = aqt.editor.Editor( @@ -628,16 +622,13 @@ class Browser(QMainWindow): restoreHeader(hh, "editor") hh.setHighlightSections(False) hh.setMinimumSectionSize(50) - hh.setMovable(True) + hh.setSectionsMovable(True) self.setColumnSizes() hh.setContextMenuPolicy(Qt.CustomContextMenu) - hh.connect(hh, SIGNAL("customContextMenuRequested(QPoint)"), - self.onHeaderContext) + hh.customContextMenuRequested.connect(self.onHeaderContext) self.setSortIndicator() - hh.connect(hh, SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"), - self.onSortChanged) - hh.connect(hh, SIGNAL("sectionMoved(int,int,int)"), - self.onColumnMoved) + hh.sortIndicatorChanged.connect(self.onSortChanged) + hh.sectionMoved.connect(self.onColumnMoved) def onSortChanged(self, idx, ord): type = self.model.activeCols[idx] @@ -692,8 +683,7 @@ by clicking on one on the left.""")) a = m.addAction(name) a.setCheckable(True) a.setChecked(type in self.model.activeCols) - a.connect(a, SIGNAL("toggled(bool)"), - lambda b, t=type: self.toggleField(t)) + a.toggled.connect(lambda b, t=type: self.toggleField(t)) m.exec_(gpos) def toggleField(self, type): @@ -718,8 +708,8 @@ by clicking on one on the left.""")) def setColumnSizes(self): hh = self.form.tableView.horizontalHeader() - hh.setResizeMode(QHeaderView.Interactive) - hh.setResizeMode(hh.logicalIndex(len(self.model.activeCols)-1), + hh.setSectionResizeMode(QHeaderView.Interactive) + hh.setSectionResizeMode(hh.logicalIndex(len(self.model.activeCols)-1), QHeaderView.Stretch) # this must be set post-resize or it doesn't work hh.setCascadingSectionResizes(False) @@ -737,19 +727,13 @@ by clicking on one on the left.""")) self.oncollapse = oncollapse def setupTree(self): - self.connect( - self.form.tree, SIGNAL("itemClicked(QTreeWidgetItem*,int)"), - self.onTreeClick) + self.form.tree.itemClicked.connect(self.onTreeClick) p = QPalette() p.setColor(QPalette.Base, QColor("#d6dde0")) self.form.tree.setPalette(p) self.buildTree() - self.connect( - self.form.tree, SIGNAL("itemExpanded(QTreeWidgetItem*)"), - lambda item: self.onTreeCollapse(item)) - self.connect( - self.form.tree, SIGNAL("itemCollapsed(QTreeWidgetItem*)"), - lambda item: self.onTreeCollapse(item)) + self.form.tree.itemExpanded.connect(lambda item: self.onTreeCollapse(item)) + self.form.tree.itemCollapsed.connect(lambda item: self.onTreeCollapse(item)) def buildTree(self): self.form.tree.clear() @@ -876,18 +860,18 @@ by clicking on one on the left.""")) reps = self._revlogData(cs) d = QDialog(self) l = QVBoxLayout() - l.setMargin(0) + l.setContentsMargins(0,0,0,0) w = AnkiWebView() l.addWidget(w) w.stdHtml(info + "

" + reps) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) - bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) + bb.rejected.connect(d.reject) d.setLayout(l) d.setWindowModality(Qt.WindowModal) d.resize(500, 400) restoreGeom(d, "revlog") - d.exec_() + d.show() saveGeom(d, "revlog") def _cardInfoData(self): @@ -1005,14 +989,13 @@ where id in %s""" % ids2str(sf)) self._openPreview() def _openPreview(self): - c = self.connect self._previewState = "question" self._previewWindow = QDialog(None, Qt.Window) self._previewWindow.setWindowTitle(_("Preview")) - c(self._previewWindow, SIGNAL("finished(int)"), self._onPreviewFinished) + self._previewWindow.finished.connect(self._onPreviewFinished) vbox = QVBoxLayout() - vbox.setMargin(0) + vbox.setContentsMargins(0,0,0,0) self._previewWeb = AnkiWebView() vbox.addWidget(self._previewWeb) bbox = QDialogButtonBox() @@ -1032,9 +1015,9 @@ where id in %s""" % ids2str(sf)) self._previewNext.setShortcut(QKeySequence("Right")) self._previewNext.setToolTip(_("Shortcut key: Right arrow or Enter")) - c(self._previewPrev, SIGNAL("clicked()"), self._onPreviewPrev) - c(self._previewNext, SIGNAL("clicked()"), self._onPreviewNext) - c(self._previewReplay, SIGNAL("clicked()"), self._onReplayAudio) + self._previewPrev.clicked.connect(self._onPreviewPrev) + self._previewNext.clicked.connect(self._onPreviewNext) + self._previewReplay.clicked.connect(self._onReplayAudio) vbox.addWidget(bbox) self._previewWindow.setLayout(vbox) @@ -1349,8 +1332,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, frm.setupUi(d) d.setWindowModality(Qt.WindowModal) frm.field.addItems([_("All Fields")] + fields) - self.connect(frm.buttonBox, SIGNAL("helpRequested()"), - self.onFindReplaceHelp) + frm.buttonBox.helpRequested.connect(self.onFindReplaceHelp) restoreGeom(d, "findreplace") r = d.exec_() saveGeom(d, "findreplace") @@ -1401,20 +1383,16 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, frm.fields.addItems(fields) self._dupesButton = None # links - frm.webView.page().setLinkDelegationPolicy( - QWebPage.DelegateAllLinks) - self.connect(frm.webView, - SIGNAL("linkClicked(QUrl)"), - self.dupeLinkClicked) + frm.webView.onAnkiLink = self.dupeLinkClicked def onFin(code): saveGeom(d, "findDupes") - self.connect(d, SIGNAL("finished(int)"), onFin) + d.finished.connect(onFin) def onClick(): field = fields[frm.fields.currentIndex()] self.duplicatesReport(frm.webView, field, frm.search.text(), frm) search = frm.buttonBox.addButton( _("Search"), QDialogButtonBox.ActionRole) - self.connect(search, SIGNAL("clicked()"), onClick) + search.clicked.connect(onClick) d.show() def duplicatesReport(self, web, fname, search, frm): @@ -1423,7 +1401,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, if not self._dupesButton: self._dupesButton = b = frm.buttonBox.addButton( _("Tag Duplicates"), QDialogButtonBox.ActionRole) - self.connect(b, SIGNAL("clicked()"), lambda: self._onTagDupes(res)) + b.clicked.connect(lambda: self._onTagDupes(res)) t = "" groups = len(res) notes = sum(len(r[1]) for r in res) @@ -1432,7 +1410,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, t += _("Found %(a)s across %(b)s.") % dict(a=part1, b=part2) t += "

    " for val, nids in res: - t += '
  1. %s: %s' % ( + t += '''
  2. %s: %s''' % ( "nid:" + ",".join(str(id) for id in nids), ngettext("%d note", "%d notes", len(nids)) % len(nids), cgi.escape(val)) @@ -1456,7 +1434,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, tooltip(_("Notes tagged.")) def dupeLinkClicked(self, link): - self.form.searchEdit.lineEdit().setText(link.toString()) + self.form.searchEdit.lineEdit().setText(link) self.onSearch() self.onNote() @@ -1552,11 +1530,11 @@ class ChangeModel(QDialog): def setup(self): # maps self.flayout = QHBoxLayout() - self.flayout.setMargin(0) + self.flayout.setContentsMargins(0,0,0,0) self.fwidg = None self.form.fieldMap.setLayout(self.flayout) self.tlayout = QHBoxLayout() - self.tlayout.setMargin(0) + self.tlayout.setContentsMargins(0,0,0,0) self.twidg = None self.form.templateMap.setLayout(self.tlayout) if self.style().objectName() == "gtk+": @@ -1572,8 +1550,7 @@ class ChangeModel(QDialog): self.modelChooser = aqt.modelchooser.ModelChooser( self.browser.mw, self.form.modelChooserWidget, label=False) self.modelChooser.models.setFocus() - self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - self.onHelp) + self.form.buttonBox.helpRequested.connect(self.onHelp) self.modelChanged(self.browser.mw.col.models.current()) self.pauseUpdate = False @@ -1609,8 +1586,8 @@ class ChangeModel(QDialog): idx = min(i, len(targets)-1) cb.setCurrentIndex(idx) indices[cb] = idx - self.connect(cb, SIGNAL("currentIndexChanged(int)"), - lambda i, cb=cb, key=key: self.onComboChanged(i, cb, key)) + cb.currentIndexChanged.connect( + lambda i, cb=cb, key=key: self.onComboChanged(i, cb, key)) combos.append(cb) l.addWidget(cb, i, 1) map.setLayout(l) @@ -1716,11 +1693,11 @@ class BrowserToolbar(Toolbar): def borderImg(link, icon, on, title, tooltip=None): if on: fmt = '''\ -\ +\ %s''' else: fmt = '''\ - %s''' + %s''' return fmt % (tooltip or title, link, icon, title) right = "
    " right += borderImg("add", "add16", False, _("Add"), @@ -1740,8 +1717,9 @@ class BrowserToolbar(Toolbar): "Bulk Remove Tags (Ctrl+Alt+T)"))) right += borderImg("delete", "delete16", False, _("Delete")) right += "
    " - self.web.page().currentFrame().setScrollBarPolicy( - Qt.Horizontal, Qt.ScrollBarAlwaysOff) + # fixme + #self.web.page().currentFrame().setScrollBarPolicy( + # Qt.Horizontal, Qt.ScrollBarAlwaysOff) self.web.stdHtml(self._body % ( "", #", #self._centerLinks(), @@ -1798,8 +1776,8 @@ class FavouritesLineEdit(QLineEdit): # name of current saved filter (if query matches) self.name = None self.buttonClicked.connect(self.onClicked) - self.connect(self, SIGNAL("textChanged(QString)"), self.updateButton) - + self.textChanged.connect(self.updateButton) + def resizeEvent(self, event): buttonSize = self.button.sizeHint() frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) diff --git a/aqt/clayout.py b/aqt/clayout.py index 19e427c53..033c37126 100644 --- a/aqt/clayout.py +++ b/aqt/clayout.py @@ -60,7 +60,6 @@ class CardLayout(QDialog): self.selectCard(idx) def setupTabs(self): - c = self.connect cloze = self.model['type'] == MODEL_CLOZE self.tabs = QTabWidget() self.tabs.setTabsClosable(not cloze) @@ -69,10 +68,10 @@ class CardLayout(QDialog): add = QPushButton("+") add.setFixedWidth(30) add.setToolTip(_("Add new card")) - c(add, SIGNAL("clicked()"), self.onAddCard) + add.clicked.connect(self.onAddCard) self.tabs.setCornerWidget(add) - c(self.tabs, SIGNAL("currentChanged(int)"), self.onCardSelected) - c(self.tabs, SIGNAL("tabCloseRequested(int)"), self.onRemoveTab) + self.tabs.currentChanged.connect(self.onCardSelected) + self.tabs.tabCloseRequested.connect(self.onRemoveTab) def updateTabs(self): self.forms = [] @@ -81,10 +80,9 @@ class CardLayout(QDialog): self.addTab(t) def addTab(self, t): - c = self.connect w = QWidget() l = QHBoxLayout() - l.setMargin(0) + l.setContentsMargins(0,0,0,0) l.setSpacing(3) left = QWidget() # template area @@ -102,9 +100,9 @@ class CardLayout(QDialog): if len(self.cards) > 1: tform.groupBox_3.setTitle(_( "Styling (shared between cards)")) - c(tform.front, SIGNAL("textChanged()"), self.saveCard) - c(tform.css, SIGNAL("textChanged()"), self.saveCard) - c(tform.back, SIGNAL("textChanged()"), self.saveCard) + tform.front.textChanged.connect(self.saveCard) + tform.css.textChanged.connect(self.saveCard) + tform.back.textChanged.connect(self.saveCard) l.addWidget(left, 5) # preview area right = QWidget() @@ -124,9 +122,6 @@ class CardLayout(QDialog): pform.frontPrevBox.addWidget(pform.frontWeb) pform.backWeb = AnkiWebView() pform.backPrevBox.addWidget(pform.backWeb) - for wig in pform.frontWeb, pform.backWeb: - wig.page().setLinkDelegationPolicy( - QWebPage.DelegateExternalLinks) l.addWidget(right, 5) w.setLayout(l) self.forms.append({'tform': tform, 'pform': pform}) @@ -151,31 +146,30 @@ Please create a new card type first.""")) ########################################################################## def setupButtons(self): - c = self.connect l = self.buttons = QHBoxLayout() help = QPushButton(_("Help")) help.setAutoDefault(False) l.addWidget(help) - c(help, SIGNAL("clicked()"), self.onHelp) + help.clicked.connect(self.onHelp) l.addStretch() addField = QPushButton(_("Add Field")) addField.setAutoDefault(False) l.addWidget(addField) - c(addField, SIGNAL("clicked()"), self.onAddField) + addField.clicked.connect(self.onAddField) if self.model['type'] != MODEL_CLOZE: flip = QPushButton(_("Flip")) flip.setAutoDefault(False) l.addWidget(flip) - c(flip, SIGNAL("clicked()"), self.onFlip) + flip.clicked.connect(self.onFlip) more = QPushButton(_("More") + " "+downArrow()) more.setAutoDefault(False) l.addWidget(more) - c(more, SIGNAL("clicked()"), lambda: self.onMore(more)) + more.clicked.connect(lambda: self.onMore(more)) l.addStretch() close = QPushButton(_("Close")) close.setAutoDefault(False) l.addWidget(close) - c(close, SIGNAL("clicked()"), self.accept) + close.clicked.connect(self.accept) # Cards ########################################################################## @@ -335,23 +329,19 @@ adjust the template manually to switch the question and answer.""")) def onMore(self, button): m = QMenu(self) a = m.addAction(_("Rename")) - a.connect(a, SIGNAL("triggered()"), - self.onRename) + a.triggered.connect(self.onRename) if self.model['type'] != MODEL_CLOZE: a = m.addAction(_("Reposition")) - a.connect(a, SIGNAL("triggered()"), - self.onReorder) + a.triggered.connect(self.onReorder) t = self.card.template() if t['did']: s = _(" (on)") else: s = _(" (off)") a = m.addAction(_("Deck Override") + s) - a.connect(a, SIGNAL("triggered()"), - self.onTargetDeck) + a.triggered.connect(self.onTargetDeck) a = m.addAction(_("Browser Appearance")) - a.connect(a, SIGNAL("triggered()"), - self.onBrowserDisplay) + a.triggered.connect(self.onBrowserDisplay) m.exec_(button.mapToGlobal(QPoint(0,0))) def onBrowserDisplay(self): @@ -363,8 +353,7 @@ adjust the template manually to switch the question and answer.""")) f.afmt.setText(t.get('bafmt', "")) f.font.setCurrentFont(QFont(t.get('bfont', "Arial"))) f.fontSize.setValue(t.get('bsize', 12)) - d.connect(f.buttonBox, SIGNAL("accepted()"), - lambda: self.onBrowserDisplayOk(f)) + f.buttonBox.accepted.connect(lambda: self.onBrowserDisplayOk(f)) d.exec_() def onBrowserDisplayOk(self, f): @@ -393,7 +382,7 @@ Enter deck to place new %s cards in, or leave blank:""") % te.setText(self.col.decks.get(t['did'])['name']) te.selectAll() bb = QDialogButtonBox(QDialogButtonBox.Close) - self.connect(bb, SIGNAL("rejected()"), d, SLOT("close()")) + bb.rejected.connect(d.close) l.addWidget(bb) d.setLayout(l) d.exec_() diff --git a/aqt/customstudy.py b/aqt/customstudy.py index a33f728fa..af820be4e 100644 --- a/aqt/customstudy.py +++ b/aqt/customstudy.py @@ -31,13 +31,13 @@ class CustomStudy(QDialog): self.exec_() def setupSignals(self): - f = self.form; c = self.connect; s = SIGNAL("clicked()") - c(f.radio1, s, lambda: self.onRadioChange(1)) - c(f.radio2, s, lambda: self.onRadioChange(2)) - c(f.radio3, s, lambda: self.onRadioChange(3)) - c(f.radio4, s, lambda: self.onRadioChange(4)) - c(f.radio5, s, lambda: self.onRadioChange(5)) - c(f.radio6, s, lambda: self.onRadioChange(6)) + f = self.form + f.radio1.clicked.connect(lambda: self.onRadioChange(1)) + f.radio2.clicked.connect(lambda: self.onRadioChange(2)) + f.radio3.clicked.connect(lambda: self.onRadioChange(3)) + f.radio4.clicked.connect(lambda: self.onRadioChange(4)) + f.radio5.clicked.connect(lambda: self.onRadioChange(5)) + f.radio6.clicked.connect(lambda: self.onRadioChange(6)) def onRadioChange(self, idx): f = self.form; sp = f.spin diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index e145928e6..f895f9f50 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -21,8 +21,8 @@ class DeckBrowser(object): def show(self): clearAudioQueue() - self.web.setLinkHandler(self._linkHandler) - self.web.setKeyHandler(None) + self.web.resetHandlers() + self.web.onAnkiLink = self._linkHandler self.mw.keyHandler = self._keyHandler self._renderPage() @@ -60,13 +60,13 @@ class DeckBrowser(object): self._dragDeckOnto(draggedDeckDid, ontoDeckDid) elif cmd == "collapse": self._collapse(arg) + return False def _keyHandler(self, evt): # currently does nothing key = str(evt.text()) def _selDeck(self, did): - self.scrollPos = self.web.page().mainFrame().scrollPosition() self.mw.col.decks.select(did) self.mw.onOverview() @@ -86,7 +86,8 @@ tr.drag-hover td { border-bottom: %(width)s solid #aaa; } body { margin: 1em; -webkit-user-select: none; } .current { background-color: #e7e7e7; } .decktd { min-width: 15em; } -.count { width: 6em; text-align: right; } +.count { min-width: 4em; text-align: right; } +.optscol { width: 2em; } .collapse { color: #000; text-decoration:none; display:inline-block; width: 1em; } .filtered { color: #00a !important; } @@ -131,7 +132,7 @@ body { margin: 1em; -webkit-user-select: none; } var draggedDeckId = ui.draggable.attr('id'); var ontoDeckId = $(this).attr('id'); - py.link("drag:" + draggedDeckId + "," + ontoDeckId); + openAnkiLink("drag:" + draggedDeckId + "," + ontoDeckId); } """ @@ -142,11 +143,9 @@ body { margin: 1em; -webkit-user-select: none; } self._dueTree = self.mw.col.sched.deckDueTree() tree = self._renderDeckTree(self._dueTree) stats = self._renderStats() - op = self._oldPos() self.web.stdHtml(self._body%dict( tree=tree, stats=stats, countwarn=self._countWarn()), css=css, - js=anki.js.jquery+anki.js.ui, loadCB=lambda ok:\ - self.web.page().mainFrame().setScrollPosition(op)) + js=anki.js.jquery+anki.js.ui) self.web.key = "deckBrowser" self._drawButtons() @@ -173,8 +172,10 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) return "" return "
    "+( _("You have a lot of decks. Please see %(a)s. %(b)s") % dict( - a=("%s" % _("this page")), - b=("
    (%s)" % (_("hide"))+ + a=("%s" % _( + "this page")), + b=("
    (" + "%s)" % (_("hide"))+ " ?""", (self.mw.col.sched.dayCutoff-86400)*1000) if depth == 0: buf = """ %s%s -%s""" % ( +%s""" % ( _("Deck"), _("Due"), _("New")) buf += self._topLevelDragRow() else: @@ -219,7 +220,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) buf = "" % (klass, did) # deck link if children: - collapse = "%s" % (did, prefix) + collapse = "%s" % (did, prefix) else: collapse = "" if deck['dyn']: @@ -228,7 +229,8 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) extraclass = "" buf += """ - %s%s%s"""% ( + %s%s%s"""% ( indent(), collapse, extraclass, did, name) # due counts def nonzeroColour(cnt, colour): @@ -241,8 +243,8 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) nonzeroColour(due, "#007700"), nonzeroColour(new, "#000099")) # options - buf += "%s" % self.mw.button( - link="opts:%d"%did, name=""+downArrow()) + buf += ("" + "" % did) # children buf += self._renderDeckTree(children, depth+1) return buf @@ -265,13 +267,13 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) def _showOptions(self, did): m = QMenu(self.mw) a = m.addAction(_("Rename")) - a.connect(a, SIGNAL("triggered()"), lambda did=did: self._rename(did)) + a.triggered.connect(lambda b, did=did: self._rename(did)) a = m.addAction(_("Options")) - a.connect(a, SIGNAL("triggered()"), lambda did=did: self._options(did)) + a.triggered.connect(lambda b, did=did: self._options(did)) a = m.addAction(_("Export")) - a.connect(a, SIGNAL("triggered()"), lambda did=did: self._export(did)) + a.triggered.connect(lambda b, did=did: self._export(did)) a = m.addAction(_("Delete")) - a.connect(a, SIGNAL("triggered()"), lambda did=did: self._delete(did)) + a.triggered.connect(lambda b, did=did: self._delete(did)) m.exec_(QCursor.pos()) def _export(self, did): @@ -345,14 +347,15 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) if b[0]: b[0] = _("Shortcut key: %s") % shortcut(b[0]) buf += """ -""" % tuple(b) +""" % tuple(b) self.bottom.draw(buf) if isMac: size = 28 else: size = 36 + self.mw.fontHeightDelta*3 self.bottom.web.setFixedHeight(size) - self.bottom.web.setLinkHandler(self._linkHandler) + self.bottom.web.resetHandlers() + self.bottom.web.onAnkiLink = self._linkHandler def _onShared(self): openLink(aqt.appShared+"decks/") diff --git a/aqt/deckchooser.py b/aqt/deckchooser.py index fceff6251..c975f1b12 100644 --- a/aqt/deckchooser.py +++ b/aqt/deckchooser.py @@ -14,7 +14,7 @@ class DeckChooser(QHBoxLayout): self.mw = mw self.deck = mw.col self.label = label - self.setMargin(0) + self.setContentsMargins(0,0,0,0) self.setSpacing(8) self.setupDecks() self.widget.setLayout(self) @@ -25,12 +25,10 @@ class DeckChooser(QHBoxLayout): self.deckLabel = QLabel(_("Deck")) self.addWidget(self.deckLabel) # decks box - self.deck = QPushButton() + self.deck = QPushButton(clicked=self.onDeckChange) self.deck.setToolTip(shortcut(_("Target Deck (Ctrl+D)"))) - s = QShortcut(QKeySequence(_("Ctrl+D")), self.widget) - s.connect(s, SIGNAL("activated()"), self.onDeckChange) + s = QShortcut(QKeySequence(_("Ctrl+D")), self.widget, activated=self.onDeckChange) self.addWidget(self.deck) - self.connect(self.deck, SIGNAL("clicked()"), self.onDeckChange) # starting label if self.mw.col.conf.get("addToCur", True): col = self.mw.col diff --git a/aqt/deckconf.py b/aqt/deckconf.py index b595c8b3e..6784acfe8 100644 --- a/aqt/deckconf.py +++ b/aqt/deckconf.py @@ -24,14 +24,10 @@ class DeckConf(QDialog): self.setupCombos() self.setupConfs() self.setWindowModality(Qt.WindowModal) - self.connect(self.form.buttonBox, - SIGNAL("helpRequested()"), - lambda: openHelp("deckoptions")) - self.connect(self.form.confOpts, SIGNAL("clicked()"), self.confOpts) + self.form.buttonBox.helpRequested.connect(lambda: openHelp("deckoptions")) + self.form.confOpts.clicked.connect(self.confOpts) self.form.confOpts.setText(downArrow()) - self.connect(self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults), - SIGNAL("clicked()"), - self.onRestore) + self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked.connect(self.onRestore) self.setWindowTitle(_("Options for %s") % self.deck['name']) # qt doesn't size properly with altered fonts otherwise restoreGeom(self, "deckconf", adjustSize=True) @@ -43,15 +39,13 @@ class DeckConf(QDialog): import anki.consts as cs f = self.form f.newOrder.addItems(list(cs.newCardOrderLabels().values())) - self.connect(f.newOrder, SIGNAL("currentIndexChanged(int)"), - self.onNewOrderChanged) + f.newOrder.currentIndexChanged.connect(self.onNewOrderChanged) # Conf list ###################################################################### def setupConfs(self): - self.connect(self.form.dconf, SIGNAL("currentIndexChanged(int)"), - self.onConfChange) + self.form.dconf.currentIndexChanged.connect(self.onConfChange) self.conf = None self.loadConfs() @@ -75,13 +69,13 @@ class DeckConf(QDialog): def confOpts(self): m = QMenu(self.mw) a = m.addAction(_("Add")) - a.connect(a, SIGNAL("triggered()"), self.addGroup) + a.triggered.connect(self.addGroup) a = m.addAction(_("Delete")) - a.connect(a, SIGNAL("triggered()"), self.remGroup) + a.triggered.connect(self.remGroup) a = m.addAction(_("Rename")) - a.connect(a, SIGNAL("triggered()"), self.renameGroup) + a.triggered.connect(self.renameGroup) a = m.addAction(_("Set for all subdecks")) - a.connect(a, SIGNAL("triggered()"), self.setChildren) + a.triggered.connect(self.setChildren) if not self.childDids: a.setEnabled(False) m.exec_(QCursor.pos()) diff --git a/aqt/downloader.py b/aqt/downloader.py index f09c1f6c4..d27e92417 100644 --- a/aqt/downloader.py +++ b/aqt/downloader.py @@ -28,7 +28,7 @@ def download(mw, code): # unsure why this is happening, but guard against throwing the # error pass - mw.connect(thread, SIGNAL("recv"), onRecv) + thread.recv.connect(onRecv) thread.start() mw.progress.start(immediate=True) while not thread.isFinished(): @@ -43,6 +43,8 @@ def download(mw, code): class Downloader(QThread): + recv = pyqtSignal() + def __init__(self, code): QThread.__init__(self) self.code = code @@ -59,7 +61,7 @@ class Downloader(QThread): def recvEvent(bytes): self.recvTotal += bytes if canPost(): - self.emit(SIGNAL("recv")) + self.recv.emit() addHook("httpRecv", recvEvent) con = httpCon() try: diff --git a/aqt/dyndeckconf.py b/aqt/dyndeckconf.py index fce28821c..cb03638c7 100644 --- a/aqt/dyndeckconf.py +++ b/aqt/dyndeckconf.py @@ -22,9 +22,7 @@ class DeckConf(QDialog): label, QDialogButtonBox.AcceptRole) self.mw.checkpoint(_("Options")) self.setWindowModality(Qt.WindowModal) - self.connect(self.form.buttonBox, - SIGNAL("helpRequested()"), - lambda: openHelp("filtered")) + self.form.buttonBox.helpRequested.connect(lambda: openHelp("filtered")) self.setWindowTitle(_("Options for %s") % self.deck['name']) restoreGeom(self, "dyndeckconf") self.setupOrder() diff --git a/aqt/editcurrent.py b/aqt/editcurrent.py index d39a049aa..49e7ead10 100644 --- a/aqt/editcurrent.py +++ b/aqt/editcurrent.py @@ -23,9 +23,7 @@ class EditCurrent(QDialog): self.setWindowTitle(_("Edit Current")) self.setMinimumHeight(400) self.setMinimumWidth(500) - self.connect(self, - SIGNAL("rejected()"), - self.onSave) + self.rejected.connect(self.onSave) self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut( QKeySequence("Ctrl+Return")) self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self) diff --git a/aqt/errors.py b/aqt/errors.py index d92099e07..7508a67ae 100644 --- a/aqt/errors.py +++ b/aqt/errors.py @@ -1,22 +1,30 @@ # Copyright: Damien Elmes # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import sys +import sys, traceback import cgi from anki.lang import _ from aqt.qt import * from aqt.utils import showText, showWarning +def excepthook(etype,val,tb): + sys.stderr.write("Caught exception:\n%s%s\n" % ( + ''.join(traceback.format_tb(tb)), + '{0}: {1}'.format(etype, val))) +sys.excepthook = excepthook + class ErrorHandler(QObject): "Catch stderr and write into buffer." ivl = 100 + errorTimer = pyqtSignal() + def __init__(self, mw): QObject.__init__(self, mw) self.mw = mw self.timer = None - self.connect(self, SIGNAL("errorTimer"), self._setTimer) + self.errorTimer.connect(self._setTimer) self.pool = "" sys.stderr = self @@ -31,12 +39,12 @@ class ErrorHandler(QObject): def setTimer(self): # we can't create a timer from a different thread, so we post a # message to the object on the main thread - self.emit(SIGNAL("errorTimer")) + self.errorTimer.emit() def _setTimer(self): if not self.timer: self.timer = QTimer(self.mw) - self.mw.connect(self.timer, SIGNAL("timeout()"), self.onTimeout) + self.timer.timeout.connect(self.onTimeout) self.timer.setInterval(self.ivl) self.timer.setSingleShot(True) self.timer.start() @@ -57,7 +65,7 @@ Anki manual for more information.""") return showWarning(_("Please install PyAudio")) if "install mplayer" in error: return showWarning(_("Please install mplayer")) - if "no default output" in error: + if "no default input" in error.lower(): return showWarning(_("Please connect a microphone, and ensure " "other programs are not using the audio device.")) if "invalidTempFolder" in error: diff --git a/aqt/exporting.py b/aqt/exporting.py index 16caba3e8..3befb85af 100644 --- a/aqt/exporting.py +++ b/aqt/exporting.py @@ -26,9 +26,8 @@ class ExportDialog(QDialog): self.exec_() def setup(self, did): - self.frm.format.insertItems(0, list(zip(*exporters())[0])) - self.connect(self.frm.format, SIGNAL("activated(int)"), - self.exporterChanged) + self.frm.format.insertItems(0, list(zip(*exporters()))[0]) + self.frm.format.activated.connect(self.exporterChanged) self.exporterChanged(0) self.decks = [_("All Decks")] + sorted(self.col.decks.allNames()) self.frm.deck.addItems(self.decks) diff --git a/aqt/fields.py b/aqt/fields.py index b43d25dc8..ba07fc5ab 100644 --- a/aqt/fields.py +++ b/aqt/fields.py @@ -38,16 +38,14 @@ class FieldDialog(QDialog): self.form.fieldList.addItem(f['name']) def setupSignals(self): - c = self.connect - s = SIGNAL f = self.form - c(f.fieldList, s("currentRowChanged(int)"), self.onRowChange) - c(f.fieldAdd, s("clicked()"), self.onAdd) - c(f.fieldDelete, s("clicked()"), self.onDelete) - c(f.fieldRename, s("clicked()"), self.onRename) - c(f.fieldPosition, s("clicked()"), self.onPosition) - c(f.sortField, s("clicked()"), self.onSortField) - c(f.buttonBox, s("helpRequested()"), self.onHelp) + f.fieldList.currentRowChanged.connect(self.onRowChange) + f.fieldAdd.clicked.connect(self.onAdd) + f.fieldDelete.clicked.connect(self.onDelete) + f.fieldRename.clicked.connect(self.onRename) + f.fieldPosition.clicked.connect(self.onPosition) + f.sortField.clicked.connect(self.onSortField) + f.buttonBox.helpRequested.connect(self.onHelp) def onRowChange(self, idx): if idx == -1: diff --git a/aqt/importing.py b/aqt/importing.py index b9de69451..eda89a30a 100644 --- a/aqt/importing.py +++ b/aqt/importing.py @@ -69,15 +69,14 @@ class ImportDialog(QDialog): self.importer = importer self.frm = aqt.forms.importing.Ui_ImportDialog() self.frm.setupUi(self) - self.connect(self.frm.buttonBox.button(QDialogButtonBox.Help), - SIGNAL("clicked()"), self.helpRequested) + self.frm.buttonBox.button(QDialogButtonBox.Help).clicked.connect( + self.helpRequested) self.setupMappingFrame() self.setupOptions() self.modelChanged() self.frm.autoDetect.setVisible(self.importer.needDelimiter) addHook("currentModelChanged", self.modelChanged) - self.connect(self.frm.autoDetect, SIGNAL("clicked()"), - self.onDelimiter) + self.frm.autoDetect.clicked.connect(self.onDelimiter) self.updateDelimiterButtonText() self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True)) self.frm.importMode.setCurrentIndex(self.mw.pm.profile.get('importMode', 1)) @@ -211,7 +210,7 @@ you can enter it here. Use \\t to represent tab."""), self.mapbox.addWidget(self.mapwidget) self.grid = QGridLayout(self.mapwidget) self.mapwidget.setLayout(self.grid) - self.grid.setMargin(3) + self.grid.setContentsMargins(3,3,3,3) self.grid.setSpacing(6) fields = self.importer.fields() for num in range(len(self.mapping)): @@ -226,8 +225,7 @@ you can enter it here. Use \\t to represent tab."""), self.grid.addWidget(QLabel(text), num, 1) button = QPushButton(_("Change")) self.grid.addWidget(button, num, 2) - self.connect(button, SIGNAL("clicked()"), - lambda s=self,n=num: s.changeMappingNum(n)) + button.clicked.connect(lambda s=self,n=num: s.changeMappingNum(n)) def changeMappingNum(self, n): f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField() diff --git a/aqt/main.py b/aqt/main.py index def4f8ec4..2c96d1047 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -59,8 +59,6 @@ class AnkiQt(QMainWindow): self.onAppMsg(args[0]) # Load profile in a timer so we can let the window finish init and not # close on profile load error. - if isMac and qtmajor >= 5: - self.show() self.progress.timer(10, self.setupProfile, False) def setupUI(self): @@ -110,16 +108,14 @@ class AnkiQt(QMainWindow): d = self.profileDiag = QDialog() f = self.profileForm = aqt.forms.profiles.Ui_Dialog() f.setupUi(d) - d.connect(f.login, SIGNAL("clicked()"), self.onOpenProfile) - d.connect(f.profiles, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), - self.onOpenProfile) - d.connect(f.quit, SIGNAL("clicked()"), lambda: sys.exit(0)) - d.connect(f.add, SIGNAL("clicked()"), self.onAddProfile) - d.connect(f.rename, SIGNAL("clicked()"), self.onRenameProfile) - d.connect(f.delete_2, SIGNAL("clicked()"), self.onRemProfile) - d.connect(d, SIGNAL("rejected()"), lambda: d.close()) - d.connect(f.profiles, SIGNAL("currentRowChanged(int)"), - self.onProfileRowChange) + f.login.clicked.connect(self.onOpenProfile) + f.profiles.itemDoubleClicked.connect(self.onOpenProfile) + f.quit.clicked.connect(lambda: sys.exit(0)) + f.add.clicked.connect(self.onAddProfile) + f.rename.clicked.connect(self.onRenameProfile) + f.delete_2.clicked.connect(self.onRemProfile) + d.rejected.connect(d.close) + f.profiles.currentRowChanged.connect(self.onProfileRowChange) self.refreshProfilesList() # raise first, for osx testing d.show() @@ -459,7 +455,8 @@ the manual for information on how to restore from an automatic backup.")) if self.resetModal: # we don't have to change the webview, as we have a covering window return - self.web.setLinkHandler(lambda url: self.delayedMaybeReset()) + self.web.resetHandlers() + self.web.onAnkiLink = lambda url: self.delayedMaybeReset() i = _("Waiting for editing to finish.") b = self.button("refresh", _("Resume Now"), id="resume") self.web.stdHtml(""" @@ -483,16 +480,16 @@ margin: 2em; h1 { margin-bottom: 0.2em; } """ - def button(self, link, name, key=None, class_="", id=""): + def button(self, link, name, key=None, class_="", id="", extra=""): class_ = "but "+ class_ if key: key = _("Shortcut key: %s") % key else: key = "" return ''' -''' % ( - id, class_, link, key, name) +''' % ( + id, class_, link, key, extra, name) # Main window setup ########################################################################## @@ -615,8 +612,7 @@ title="%s">%s''' % ( self.keyHandler = None # debug shortcut self.debugShortcut = QShortcut(QKeySequence("Ctrl+:"), self) - self.connect( - self.debugShortcut, SIGNAL("activated()"), self.onDebug) + self.debugShortcut.activated.connect(self.onDebug) def keyPressEvent(self, evt): # do we have a delegate? @@ -791,23 +787,21 @@ title="%s">%s''' % ( def setupMenus(self): m = self.form - s = SIGNAL("triggered()") - #self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin) - self.connect(m.actionSwitchProfile, s, self.unloadProfile) - self.connect(m.actionImport, s, self.onImport) - self.connect(m.actionExport, s, self.onExport) - self.connect(m.actionExit, s, self, SLOT("close()")) - self.connect(m.actionPreferences, s, self.onPrefs) - self.connect(m.actionAbout, s, self.onAbout) - self.connect(m.actionUndo, s, self.onUndo) - self.connect(m.actionFullDatabaseCheck, s, self.onCheckDB) - self.connect(m.actionCheckMediaDatabase, s, self.onCheckMediaDB) - self.connect(m.actionDocumentation, s, self.onDocumentation) - self.connect(m.actionDonate, s, self.onDonate) - self.connect(m.actionStudyDeck, s, self.onStudyDeck) - self.connect(m.actionCreateFiltered, s, self.onCram) - self.connect(m.actionEmptyCards, s, self.onEmptyCards) - self.connect(m.actionNoteTypes, s, self.onNoteTypes) + m.actionSwitchProfile.triggered.connect(lambda b: self.unloadProfile()) + m.actionImport.triggered.connect(self.onImport) + m.actionExport.triggered.connect(self.onExport) + m.actionExit.triggered.connect(self.close) + m.actionPreferences.triggered.connect(self.onPrefs) + m.actionAbout.triggered.connect(self.onAbout) + m.actionUndo.triggered.connect(self.onUndo) + m.actionFullDatabaseCheck.triggered.connect(self.onCheckDB) + m.actionCheckMediaDatabase.triggered.connect(self.onCheckMediaDB) + m.actionDocumentation.triggered.connect(self.onDocumentation) + m.actionDonate.triggered.connect(self.onDonate) + m.actionStudyDeck.triggered.connect(self.onStudyDeck) + m.actionCreateFiltered.triggered.connect(self.onCram) + m.actionEmptyCards.triggered.connect(self.onEmptyCards) + m.actionNoteTypes.triggered.connect(self.onNoteTypes) def updateTitleBar(self): self.setWindowTitle("Anki") @@ -818,9 +812,9 @@ title="%s">%s''' % ( def setupAutoUpdate(self): import aqt.update self.autoUpdate = aqt.update.LatestVersionFinder(self) - self.connect(self.autoUpdate, SIGNAL("newVerAvail"), self.newVerAvail) - self.connect(self.autoUpdate, SIGNAL("newMsg"), self.newMsg) - self.connect(self.autoUpdate, SIGNAL("clockIsOff"), self.clockIsOff) + self.autoUpdate.newVerAvail.connect(self.newVerAvail) + self.autoUpdate.newMsg.connect(self.newMsg) + self.autoUpdate.clockIsOff.connect(self.clockIsOff) self.autoUpdate.start() def newVerAvail(self, ver): @@ -889,7 +883,7 @@ and if the problem comes up again, please ask on the support site.""")) "select id, mid, flds from notes where id in %s" % ids2str(nids)): fields = splitFields(flds) - f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8")) + f.write(("\t".join([str(id), str(mid)] + fields))) f.write("\n") # Schema modifications @@ -955,9 +949,9 @@ will be lost. Continue?""")) b = QPushButton(_("Delete Unused")) b.setAutoDefault(False) box.addButton(b, QDialogButtonBox.ActionRole) - b.connect( - b, SIGNAL("clicked()"), lambda u=unused, d=diag: self.deleteUnused(u, d)) - diag.connect(box, SIGNAL("rejected()"), diag, SLOT("reject()")) + b.clicked.connect( + lambda c, u=unused, d=diag: self.deleteUnused(u, d)) + box.rejected.connect(diag.reject) diag.setMinimumHeight(400) diag.setMinimumWidth(500) restoreGeom(diag, "checkmediadb") @@ -1006,7 +1000,7 @@ will be lost. Continue?""")) self.col.remCards(cids) tooltip(ngettext("%d card deleted.", "%d cards deleted.", len(cids)) % len(cids)) self.reset() - diag.connect(box, SIGNAL("accepted()"), onDelete) + box.accepted.connect(onDelete) diag.show() # Debugging @@ -1017,12 +1011,10 @@ will be lost. Continue?""")) frm = aqt.forms.debug.Ui_Dialog() frm.setupUi(d) s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+return"), d) - self.connect(s, SIGNAL("activated()"), - lambda: self.onDebugRet(frm)) + s.activated.connect(lambda: self.onDebugRet(frm)) s = self.debugDiagShort = QShortcut( QKeySequence("ctrl+shift+return"), d) - self.connect(s, SIGNAL("activated()"), - lambda: self.onDebugPrint(frm)) + s.activated.connect(lambda: self.onDebugPrint(frm)) d.show() def _captureOutput(self, on): @@ -1080,6 +1072,12 @@ will be lost. Continue?""")) ########################################################################## def setupFonts(self): + print("fixme: setupFonts()") + self.fontHeight = 12 + self.fontFamily = "arial" + self.fontHeightDelta = 0 + + return f = QFontInfo(self.font()) ws = QWebSettings.globalSettings() self.fontHeight = f.pixelSize() @@ -1093,8 +1091,7 @@ will be lost. Continue?""")) if isMac: # mac users expect a minimize option self.minimizeShortcut = QShortcut("Ctrl+M", self) - self.connect(self.minimizeShortcut, SIGNAL("activated()"), - self.onMacMinimize) + self.minimizeShortcut.activated.connect(self.onMacMinimize) self.hideMenuAccels = True self.maybeHideAccelerators() self.hideStatusTips() @@ -1125,7 +1122,7 @@ will be lost. Continue?""")) ########################################################################## def setupAppMsg(self): - self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg) + self.app.appMsg.connect(self.onAppMsg) def onAppMsg(self, buf): if self.state == "startup": diff --git a/aqt/modelchooser.py b/aqt/modelchooser.py index 2d442df00..4eece1c7e 100644 --- a/aqt/modelchooser.py +++ b/aqt/modelchooser.py @@ -15,7 +15,7 @@ class ModelChooser(QHBoxLayout): self.mw = mw self.deck = mw.col self.label = label - self.setMargin(0) + self.setContentsMargins(0,0,0,0) self.setSpacing(8) self.setupModels() addHook('reset', self.onReset) @@ -29,11 +29,10 @@ class ModelChooser(QHBoxLayout): self.models = QPushButton() #self.models.setStyleSheet("* { text-align: left; }") self.models.setToolTip(shortcut(_("Change Note Type (Ctrl+N)"))) - s = QShortcut(QKeySequence(_("Ctrl+N")), self.widget) - s.connect(s, SIGNAL("activated()"), self.onModelChange) + s = QShortcut(QKeySequence(_("Ctrl+N")), self.widget, activated=self.onModelChange) self.models.setAutoDefault(False) self.addWidget(self.models) - self.connect(self.models, SIGNAL("clicked()"), self.onModelChange) + self.models.clicked.connect(self.onModelChange) # layout sizePolicy = QSizePolicy( QSizePolicy.Policy(7), @@ -61,8 +60,7 @@ class ModelChooser(QHBoxLayout): from aqt.studydeck import StudyDeck current = self.deck.models.current()['name'] # edit button - edit = QPushButton(_("Manage")) - self.connect(edit, SIGNAL("clicked()"), self.onEdit) + edit = QPushButton(_("Manage"), clicked=self.onEdit) def nameFunc(): return sorted(self.deck.models.allNames()) ret = StudyDeck( diff --git a/aqt/models.py b/aqt/models.py index 329a39336..c93f1777e 100644 --- a/aqt/models.py +++ b/aqt/models.py @@ -20,8 +20,7 @@ class Models(QDialog): self.mw.checkpoint(_("Note Types")) self.form = aqt.forms.models.Ui_Dialog() self.form.setupUi(self) - self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - lambda: openHelp("notetypes")) + self.form.buttonBox.helpRequested.connect(lambda: openHelp("notetypes")) self.setupModels() restoreGeom(self, "models") self.exec_() @@ -31,25 +30,23 @@ class Models(QDialog): def setupModels(self): self.model = None - c = self.connect; f = self.form; box = f.buttonBox - s = SIGNAL("clicked()") + f = self.form; box = f.buttonBox t = QDialogButtonBox.ActionRole b = box.addButton(_("Add"), t) - c(b, s, self.onAdd) + b.clicked.connect(self.onAdd) b = box.addButton(_("Rename"), t) - c(b, s, self.onRename) + b.clicked.connect(self.onRename) b = box.addButton(_("Delete"), t) - c(b, s, self.onDelete) + b.clicked.connect(self.onDelete) if self.fromMain: b = box.addButton(_("Fields..."), t) - c(b, s, self.onFields) + b.clicked.connect(self.onFields) b = box.addButton(_("Cards..."), t) - c(b, s, self.onCards) + b.clicked.connect(self.onCards) b = box.addButton(_("Options..."), t) - c(b, s, self.onAdvanced) - c(f.modelsList, SIGNAL("currentRowChanged(int)"), self.modelChanged) - c(f.modelsList, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), - self.onRename) + b.clicked.connect(self.onAdvanced) + f.modelsList.currentRowChanged.connect(self.modelChanged) + f.modelsList.itemDoubleClicked.connect(self.onRename) self.updateModelsList() f.modelsList.setCurrentRow(0) maybeHideClose(box) @@ -113,9 +110,7 @@ class Models(QDialog): frm.latexHeader.setText(self.model['latexPre']) frm.latexFooter.setText(self.model['latexPost']) d.setWindowTitle(_("Options for %s") % self.model['name']) - self.connect( - frm.buttonBox, SIGNAL("helpRequested()"), - lambda: openHelp("latex")) + frm.buttonBox.helpRequested.connect(lambda: openHelp("latex")) restoreGeom(d, "modelopts") d.exec_() saveGeom(d, "modelopts") @@ -185,9 +180,9 @@ class AddModel(QDialog): self.dialog.models.setCurrentRow(0) # the list widget will swallow the enter key s = QShortcut(QKeySequence("Return"), self) - self.connect(s, SIGNAL("activated()"), self.accept) + s.activated.connect(self.accept) # help - self.connect(self.dialog.buttonBox, SIGNAL("helpRequested()"), self.onHelp) + self.dialog.buttonBox.helpRequested.connect(self.onHelp) def get(self): self.exec_() diff --git a/aqt/overview.py b/aqt/overview.py index 294c3df15..88f18951e 100644 --- a/aqt/overview.py +++ b/aqt/overview.py @@ -17,16 +17,16 @@ class Overview(object): def show(self): clearAudioQueue() - self.web.setLinkHandler(self._linkHandler) - self.web.setKeyHandler(None) + self.web.resetHandlers() + self.web.onAnkiLink = self._linkHandler self.mw.keyHandler = self._keyHandler - self.mw.web.setFocus() self.refresh() def refresh(self): self.mw.col.reset() self._renderPage() self._renderBottom() + self.mw.web.setFocus() # Handlers ############################################################ @@ -61,6 +61,7 @@ class Overview(object): self.mw.reset() elif url.lower().startswith("http"): openLink(url) + return False def _keyHandler(self, evt): cram = self.mw.col.decks.current()['dyn'] @@ -143,7 +144,7 @@ to their original deck.""") _("New"), counts[0], _("Learning"), counts[1], _("To Review"), counts[2], - but("study", _("Study Now"), id="study")) + but("study", _("Study Now"), id="study",extra=" autofocus")) _body = """ @@ -198,14 +199,14 @@ text-align: center; if b[0]: b[0] = _("Shortcut key: %s") % shortcut(b[0]) buf += """ -""" % tuple(b) +""" % tuple(b) self.bottom.draw(buf) if isMac: size = 28 else: size = 36 + self.mw.fontHeightDelta*3 self.bottom.web.setFixedHeight(size) - self.bottom.web.setLinkHandler(self._linkHandler) + self.bottom.web.onAnkiLink = self._linkHandler # Studying more ###################################################################### diff --git a/aqt/preferences.py b/aqt/preferences.py index 87f6df017..90d11a5ec 100644 --- a/aqt/preferences.py +++ b/aqt/preferences.py @@ -21,8 +21,7 @@ class Preferences(QDialog): self.form.setupUi(self) self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False) self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False) - self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - lambda: openHelp("profileprefs")) + self.form.buttonBox.helpRequested.connect(lambda: openHelp("profileprefs")) self.setupLang() self.setupCollection() self.setupNetwork() @@ -52,8 +51,7 @@ class Preferences(QDialog): f = self.form f.lang.addItems([x[0] for x in anki.lang.langs]) f.lang.setCurrentIndex(self.langIdx()) - self.connect(f.lang, SIGNAL("currentIndexChanged(int)"), - self.onLangIdxChanged) + f.lang.currentIndexChanged.connect(self.onLangIdxChanged) def langIdx(self): codes = [x[1] for x in anki.lang.langs] @@ -113,8 +111,7 @@ class Preferences(QDialog): self._hideAuth() else: self.form.syncUser.setText(self.prof.get('syncUser', "")) - self.connect(self.form.syncDeauth, SIGNAL("clicked()"), - self.onSyncDeauth) + self.form.syncDeauth.clicked.connect(self.onSyncDeauth) def _hideAuth(self): self.form.syncDeauth.setVisible(False) @@ -141,9 +138,7 @@ Not currently enabled; click the sync button in the main window to enable.""")) def setupBackup(self): self.form.numBackups.setValue(self.prof['numBackups']) self.form.compressBackups.setChecked(self.prof.get("compressBackups", True)) - self.connect(self.form.openBackupFolder, - SIGNAL("linkActivated(QString)"), - self.onOpenBackup) + self.form.openBackupFolder.linkActivated.connect(self.onOpenBackup) def onOpenBackup(self): openFolder(self.mw.pm.backupFolder()) @@ -158,9 +153,7 @@ Not currently enabled; click the sync button in the main window to enable.""")) def setupOptions(self): self.form.stripHTML.setChecked(self.prof['stripHTML']) self.form.pastePNG.setChecked(self.prof.get("pastePNG", False)) - self.connect( - self.form.profilePass, SIGNAL("clicked()"), - self.onProfilePass) + self.form.profilePass.clicked.connect(self.onProfilePass) def updateOptions(self): self.prof['stripHTML'] = self.form.stripHTML.isChecked() diff --git a/aqt/profiles.py b/aqt/profiles.py index 900f86338..1b4c2e6a0 100644 --- a/aqt/profiles.py +++ b/aqt/profiles.py @@ -191,7 +191,7 @@ and no other programs are accessing your profile folders, then try again.""")) return path def addonFolder(self): - return self._ensureExists(os.path.join(self.base, "addons")) + return self._ensureExists(os.path.join(self.base, "addons21")) def backupFolder(self): return self._ensureExists( @@ -210,10 +210,7 @@ and no other programs are accessing your profile folders, then try again.""")) def _defaultBase(self): if isWin: - if False: #qtmajor >= 5: - loc = QStandardPaths.writeableLocation(QStandardPaths.DocumentsLocation) - else: - loc = QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation) + loc = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation) return os.path.join(loc, "Anki") elif isMac: return os.path.expanduser("~/Documents/Anki") @@ -223,9 +220,9 @@ and no other programs are accessing your profile folders, then try again.""")) if os.path.exists(p): return p else: - loc = QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation) - if loc[:-1] == QDesktopServices.storageLocation( - QDesktopServices.HomeLocation): + loc = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation) + if loc[:-1] == QStandardPaths.writableLocation( + QStandardPaths.HomeLocation): # occasionally "documentsLocation" will return the home # folder because the Documents folder isn't configured # properly; fall back to an English path @@ -234,7 +231,7 @@ and no other programs are accessing your profile folders, then try again.""")) return os.path.join(loc, "Anki") def _loadMeta(self): - path = os.path.join(self.base, "prefs.db") + path = os.path.join(self.base, "prefs21.db") new = not os.path.exists(path) def recover(): # if we can't load profile, start with a new one @@ -249,7 +246,7 @@ and no other programs are accessing your profile folders, then try again.""")) os.rename(path, broken) QMessageBox.warning( None, "Preferences Corrupt", """\ -Anki's prefs.db file was corrupt and has been recreated. If you were using multiple \ +Anki's prefs21.db file was corrupt and has been recreated. If you were using multiple \ profiles, please add them back using the same names to recover your cards.""") try: self.db = DB(path) @@ -308,8 +305,8 @@ please see: d = self.langDiag = NoCloseDiag() f = self.langForm = aqt.forms.setlang.Ui_Dialog() f.setupUi(d) - d.connect(d, SIGNAL("accepted()"), self._onLangSelected) - d.connect(d, SIGNAL("rejected()"), lambda: True) + d.accepted.connect(self._onLangSelected) + d.rejected.connect(lambda: True) # default to the system language try: (lang, enc) = locale.getdefaultlocale() diff --git a/aqt/progress.py b/aqt/progress.py index b50c2647a..e627c3215 100644 --- a/aqt/progress.py +++ b/aqt/progress.py @@ -70,7 +70,7 @@ Your pysqlite2 is too old. Anki will appear frozen during long operations.""") t = QTimer(self.mw) if not repeat: t.setSingleShot(True) - t.connect(t, SIGNAL("timeout()"), handler) + t.timeout.connect(handler) t.start(ms) return t diff --git a/aqt/qt.py b/aqt/qt.py index 328f82110..8965bb26e 100644 --- a/aqt/qt.py +++ b/aqt/qt.py @@ -1,30 +1,25 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# imports are all in this file to make moving to pyside easier in the future # fixme: make sure not to optimize imports on this file import sip import os +# fix buggy ubuntu12.04 display of language selector +os.environ["LIBOVERLAY_SCROLLBAR"] = "0" + from anki.utils import isWin, isMac -sip.setapi('QString', 2) -sip.setapi('QVariant', 2) -sip.setapi('QUrl', 2) -try: - sip.setdestroyonexit(False) -except: - # missing in older versions - pass -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings -from PyQt4.QtNetwork import QLocalServer, QLocalSocket +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtWebEngineWidgets import * +from PyQt5.QtNetwork import QLocalServer, QLocalSocket def debug(): - from PyQt4.QtCore import pyqtRemoveInputHook + from PyQt5.QtCore import pyqtRemoveInputHook from pdb import set_trace pyqtRemoveInputHook() set_trace() @@ -33,7 +28,7 @@ import sys, traceback if os.environ.get("DEBUG"): def info(type, value, tb): - from PyQt4.QtCore import pyqtRemoveInputHook + from PyQt5.QtCore import pyqtRemoveInputHook for line in traceback.format_exception(type, value, tb): sys.stdout.write(line) pyqtRemoveInputHook() @@ -44,8 +39,5 @@ if os.environ.get("DEBUG"): qtmajor = (QT_VERSION & 0xff0000) >> 16 qtminor = (QT_VERSION & 0x00ff00) >> 8 -# qt4.6 doesn't support ruby tags -if qtmajor <= 4 and qtminor <= 6: - import anki.template.furigana - anki.template.furigana.ruby = r'\2\1' - +if qtmajor < 5 or (qtmajor == 5 and qtminor < 5): + raise Exception("Qt must be 5.5+") diff --git a/aqt/reviewer.py b/aqt/reviewer.py index 8eeec0bab..6c0feaf1b 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -36,19 +36,21 @@ class Reviewer(object): # qshortcut so we don't autorepeat self.delShortcut = QShortcut(QKeySequence("Delete"), self.mw) self.delShortcut.setAutoRepeat(False) - self.mw.connect(self.delShortcut, SIGNAL("activated()"), self.onDelete) + self.delShortcut.activated.connect(self.onDelete) addHook("leech", self.onLeech) def show(self): self.mw.col.reset() + self.web.resetHandlers() self.mw.keyHandler = self._keyHandler - self.web.setLinkHandler(self._linkHandler) + self.web.onAnkiLink = self._linkHandler self.web.setKeyHandler(self._catchEsc) if isMac: self.bottom.web.setFixedHeight(46) else: self.bottom.web.setFixedHeight(52+self.mw.fontHeightDelta*4) - self.bottom.web.setLinkHandler(self._linkHandler) + self.bottom.web.resetHandlers() + self.bottom.web.onAnkiLink = self._linkHandler self._reps = None self.nextCard() @@ -161,12 +163,12 @@ function _toggleStar (show) { function _getTypedText () { if (typeans) { - py.link("typeans:"+typeans.value); + openAnkiLink("typeans:"+typeans.value); } }; function _typeAnsPress() { if (window.event.keyCode === 13) { - py.link("ansHack"); + openAnkiLink("ans"); } } @@ -177,15 +179,14 @@ function _typeAnsPress() { self._bottomReady = False base = getBase(self.mw.col) # main window - self.web.stdHtml(self._revHtml, self._styles(), - loadCB=lambda x: self._showQuestion(), - head=base) + self.web.onLoadFinished = self._showQuestion + self.web.stdHtml(self._revHtml, self._styles(), head=base) # show answer / ease buttons self.bottom.web.show() + self.bottom.web.onLoadFinished = self._showAnswerButton self.bottom.web.stdHtml( self._bottomHTML(), - self.bottom._css + self._bottomCSS, - loadCB=lambda x: self._showAnswerButton()) + self.bottom._css + self._bottomCSS) # Showing the question ########################################################################## @@ -277,19 +278,13 @@ The front of this card is empty. Please run Tools>Empty Cards.""") self.web.eval("$('#typeans').blur();") return True - def _showAnswerHack(self): - # on Empty Cards.""") def _linkHandler(self, url): if url == "ans": self._showAnswer() - elif url == "ansHack": - self.mw.progress.timer(100, self._showAnswerHack, False) elif url.startswith("ease"): self._answerCard(int(url[4:])) elif url == "edit": @@ -540,12 +533,12 @@ min-width: 60px; white-space: nowrap;
    - +
    - + @@ -603,7 +596,7 @@ function showAnswer(txt) { self.bottom.web.setFocus() middle = ''' %s
    -''' % ( +''' % ( self._remaining(), _("Shortcut key: %s") % _("Space"), _("Show Answer")) # wrap it in a table so it has the same top margin as the ease buttons middle = "
    %s
    " % middle @@ -661,7 +654,7 @@ function showAnswer(txt) { extra = "" due = self._buttonTime(i) return ''' -%s''' % (due, extra, _("Shortcut key: %s") % i, i, label) buf = "
    " for ease, label in self._answerButtonList(): @@ -713,7 +706,7 @@ function showAnswer(txt) { label, scut, func = row a = m.addAction(label) a.setShortcut(QKeySequence(scut)) - a.connect(a, SIGNAL("triggered()"), func) + a.triggered.connect(func) runHook("Reviewer.contextMenuEvent",self,m) m.exec_(QCursor.pos()) diff --git a/aqt/stats.py b/aqt/stats.py index 40f062f21..7ef4419e7 100644 --- a/aqt/stats.py +++ b/aqt/stats.py @@ -26,21 +26,19 @@ class DeckStats(QDialog): restoreGeom(self, self.name) b = f.buttonBox.addButton(_("Save Image"), QDialogButtonBox.ActionRole) - b.connect(b, SIGNAL("clicked()"), self.browser) + b.clicked.connect(self.browser) b.setAutoDefault(False) - c = self.connect - s = SIGNAL("clicked()") - c(f.groups, s, lambda: self.changeScope("deck")) + f.groups.clicked.connect(lambda: self.changeScope("deck")) f.groups.setShortcut("g") - c(f.all, s, lambda: self.changeScope("collection")) - c(f.month, s, lambda: self.changePeriod(0)) - c(f.year, s, lambda: self.changePeriod(1)) - c(f.life, s, lambda: self.changePeriod(2)) - c(f.web, SIGNAL("loadFinished(bool)"), self.loadFin) + f.all.clicked.connect(lambda: self.changeScope("collection")) + f.month.clicked.connect(lambda: self.changePeriod(0)) + f.year.clicked.connect(lambda: self.changePeriod(1)) + f.life.clicked.connect(lambda: self.changePeriod(2)) maybeHideClose(self.form.buttonBox) addCloseShortcut(self) self.refresh() - self.exec_() + self.show() + print("fixme: save image support in deck stats") def reject(self): saveGeom(self, self.name) @@ -78,14 +76,10 @@ to your desktop.""")) self.wholeCollection = type == "collection" self.refresh() - def loadFin(self, b): - self.form.web.page().mainFrame().setScrollPosition(self.oldPos) - def refresh(self): self.mw.progress.start(immediate=True) - self.oldPos = self.form.web.page().mainFrame().scrollPosition() stats = self.mw.col.stats() stats.wholeCollection = self.wholeCollection self.report = stats.report(type=self.period) - self.form.web.setHtml(self.report) + self.form.web.stdHtml(""+self.report+"") self.mw.progress.finish() diff --git a/aqt/studydeck.py b/aqt/studydeck.py index 636d6c4e1..d3cdf4c0a 100644 --- a/aqt/studydeck.py +++ b/aqt/studydeck.py @@ -31,7 +31,7 @@ class StudyDeck(QDialog): b.setShortcut(QKeySequence("Ctrl+N")) b.setToolTip(shortcut(_("Add New Deck (Ctrl+N)"))) self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole) - b.connect(b, SIGNAL("clicked()"), self.onAddDeck) + b.clicked.connect(self.onAddDeck) if title: self.setWindowTitle(title) if not names: @@ -45,15 +45,9 @@ class StudyDeck(QDialog): self.ok = self.form.buttonBox.addButton( accept or _("Study"), QDialogButtonBox.AcceptRole) self.setWindowModality(Qt.WindowModal) - self.connect(self.form.buttonBox, - SIGNAL("helpRequested()"), - lambda: openHelp(help)) - self.connect(self.form.filter, - SIGNAL("textEdited(QString)"), - self.redraw) - self.connect(self.form.list, - SIGNAL("itemDoubleClicked(QListWidgetItem*)"), - self.accept) + self.form.buttonBox.helpRequested.connect(lambda: openHelp(help)) + self.form.filter.textEdited.connect(self.redraw) + self.form.list.itemDoubleClicked.connect(self.accept) self.show() # redraw after show so position at center correct self.redraw("", current) diff --git a/aqt/sync.py b/aqt/sync.py index 691f113f3..4002118fd 100644 --- a/aqt/sync.py +++ b/aqt/sync.py @@ -45,7 +45,7 @@ class SyncManager(QObject): t = self.thread = SyncThread( self.pm.collectionPath(), self.pm.profile['syncKey'], auth=auth, media=self.pm.profile['syncMedia']) - self.connect(t, SIGNAL("event"), self.onEvent) + t.event.connect(self.onEvent) self.label = _("Connecting...") self.mw.progress.start(immediate=True, label=self.label) self.sentBytes = self.recvBytes = 0 @@ -136,10 +136,10 @@ sync again to correct the issue.""")) self._confirmFullSync() elif evt == "send": # posted events not guaranteed to arrive in order - self.sentBytes = max(self.sentBytes, args[0]) + self.sentBytes = max(self.sentBytes, int(args[0])) self._updateLabel() elif evt == "recv": - self.recvBytes = max(self.recvBytes, args[0]) + self.recvBytes = max(self.recvBytes, int(args[0])) self._updateLabel() def _rewriteError(self, err): @@ -216,8 +216,8 @@ enter your details below.""") % vbox.addLayout(g) bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.button(QDialogButtonBox.Ok).setAutoDefault(True) - self.connect(bb, SIGNAL("accepted()"), d.accept) - self.connect(bb, SIGNAL("rejected()"), d.reject) + bb.accepted.connect(d.accept) + bb.rejected.connect(d.reject) vbox.addWidget(bb) d.setLayout(vbox) d.show() @@ -275,6 +275,8 @@ Check Database, then sync again.""")) class SyncThread(QThread): + event = pyqtSignal(str, str) + def __init__(self, path, hkey, auth=None, media=True): QThread.__init__(self) self.path = path @@ -309,11 +311,11 @@ class SyncThread(QThread): def sendEvent(bytes): self.sentTotal += bytes if canPost(): - self.fireEvent("send", self.sentTotal) + self.fireEvent("send", str(self.sentTotal)) def recvEvent(bytes): self.recvTotal += bytes if canPost(): - self.fireEvent("recv", self.recvTotal) + self.fireEvent("recv", str(self.recvTotal)) addHook("sync", syncEvent) addHook("syncMsg", syncMsg) addHook("httpSend", sendEvent) @@ -416,8 +418,8 @@ class SyncThread(QThread): else: self.fireEvent("mediaSuccess") - def fireEvent(self, *args): - self.emit(SIGNAL("event"), *args) + def fireEvent(self, cmd, arg=""): + self.event.emit(cmd, arg) # Monkey-patch httplib & httplib2 so we can get progress info @@ -428,9 +430,10 @@ import http.client, httplib2 from io import StringIO from anki.hooks import runHook +print("fixme: _conn_request and _incrementalSend need updating for python3") + # sending in httplib def _incrementalSend(self, data): - print("fixme: _incrementalSend needs updating for python3") """Send `data' to the server.""" if self.sock is None: if self.auto_open: @@ -447,7 +450,7 @@ def _incrementalSend(self, data): self.sock.sendall(block) runHook("httpSend", len(block)) -http.client.HTTPConnection.send = _incrementalSend +#http.client.HTTPConnection.send = _incrementalSend # receiving in httplib2 # this is an augmented version of httplib's request routine that: @@ -455,7 +458,6 @@ http.client.HTTPConnection.send = _incrementalSend # - calls a hook for each chunk of data so we can update the gui # - retries only when keep-alive connection is closed def _conn_request(self, conn, request_uri, method, body, headers): - print("fixme: _conn_request updating for python3") for i in range(2): try: if conn.sock is None: @@ -503,4 +505,4 @@ def _conn_request(self, conn, request_uri, method, body, headers): content = httplib2._decompressContent(response, content) return (response, content) -httplib2.Http._conn_request = _conn_request +#httplib2.Http._conn_request = _conn_request diff --git a/aqt/tagedit.py b/aqt/tagedit.py index 836730543..85c83cb9e 100644 --- a/aqt/tagedit.py +++ b/aqt/tagedit.py @@ -6,6 +6,8 @@ import re class TagEdit(QLineEdit): + lostFocus = pyqtSignal() + # 0 = tags, 1 = decks def __init__(self, parent, type=0): QLineEdit.__init__(self, parent) @@ -53,7 +55,7 @@ class TagEdit(QLineEdit): def focusOutEvent(self, evt): QLineEdit.focusOutEvent(self, evt) - self.emit(SIGNAL("lostFocus")) + self.lostFocus.emit() self.completer.popup().hide() def hideCompleter(self): @@ -67,20 +69,20 @@ class TagCompleter(QCompleter): self.edit = edit self.cursor = None - def splitPath(self, str): - str = str(str).strip() - str = re.sub(" +", " ", str) - self.tags = self.edit.col.tags.split(str) + def splitPath(self, tags): + tags = tags.strip() + tags = re.sub(" +", " ", tags) + self.tags = self.edit.col.tags.split(tags) self.tags.append("") p = self.edit.cursorPosition() - self.cursor = str.count(" ", 0, p) + self.cursor = tags.count(" ", 0, p) return [self.tags[self.cursor]] def pathFromIndex(self, idx): if self.cursor is None: return self.edit.text() ret = QCompleter.pathFromIndex(self, idx) - self.tags[self.cursor] = str(ret) + self.tags[self.cursor] = ret try: self.tags.remove("") except ValueError: diff --git a/aqt/toolbar.py b/aqt/toolbar.py index 085690fec..d5f8a67b4 100644 --- a/aqt/toolbar.py +++ b/aqt/toolbar.py @@ -9,9 +9,8 @@ class Toolbar(object): def __init__(self, mw, web): self.mw = mw self.web = web - self.web.page().mainFrame().setScrollBarPolicy( - Qt.Vertical, Qt.ScrollBarAlwaysOff) - self.web.setLinkHandler(self._linkHandler) + self.web.resetHandlers() + self.web.onAnkiLink = self._linkHandler self.link_handlers = { "decks": self._deckLinkHandler, "study": self._studyLinkHandler, @@ -51,7 +50,8 @@ class Toolbar(object): def _linkHTML(self, links): buf = "" for ln, name, title in links: - buf += '%s' % ( + buf += ''' + %s''' % ( title, ln, name) buf += " "*3 return buf @@ -59,7 +59,8 @@ class Toolbar(object): def _rightIcons(self): buf = "" for ln, icon, title in self._rightIconsList(): - buf += '' % ( + buf += ''' + ''' % ( title, ln, icon) return buf @@ -67,11 +68,9 @@ class Toolbar(object): ###################################################################### def _linkHandler(self, link): - # first set focus back to main window, or we're left with an ugly - # focus ring around the clicked item - self.mw.web.setFocus() if link in self.link_handlers: - self.link_handlers[link]() + self.link_handlers[link]() + return False def _deckLinkHandler(self): self.mw.moveToState("deckBrowser") diff --git a/aqt/update.py b/aqt/update.py index 7a32f0989..b36f9046f 100644 --- a/aqt/update.py +++ b/aqt/update.py @@ -14,6 +14,10 @@ from aqt.utils import showText class LatestVersionFinder(QThread): + newVerAvail = pyqtSignal(str) + newMsg = pyqtSignal(dict) + clockIsOff = pyqtSignal(float) + def __init__(self, main): QThread.__init__(self) self.main = main @@ -32,23 +36,25 @@ class LatestVersionFinder(QThread): return d = self._data() d['proto'] = 1 - d = urllib.parse.urlencode(d) + d = urllib.parse.urlencode(d).encode("utf8") try: f = urllib.request.urlopen(aqt.appUpdate, d) resp = f.read() if not resp: + print("update check load failed") return - resp = json.loads(resp) + resp = json.loads(resp.decode("utf8")) except: # behind proxy, corrupt message, etc + print("update check failed") return if resp['msg']: - self.emit(SIGNAL("newMsg"), resp) + self.newMsg.emit(resp) if resp['ver']: - self.emit(SIGNAL("newVerAvail"), resp['ver']) + self.newVerAvail.emit(resp['ver']) diff = resp['time'] - time.time() if abs(diff) > 300: - self.emit(SIGNAL("clockIsOff"), diff) + self.clockIsOff.emit(diff) def askAndUpdate(mw, ver): baseStr = ( diff --git a/aqt/utils.py b/aqt/utils.py index e193b6af0..ed9ea65d6 100644 --- a/aqt/utils.py +++ b/aqt/utils.py @@ -45,7 +45,7 @@ def showInfo(text, parent=False, help="", type="info", title="Anki"): b.setDefault(True) if help: b = mb.addButton(QMessageBox.Help) - b.connect(b, SIGNAL("clicked()"), lambda: openHelp(help)) + b.clicked.connect(lambda: openHelp(help)) b.setAutoDefault(False) return mb.exec_() @@ -70,7 +70,7 @@ def showText(txt, parent=None, type="text", run=True, geomKey=None, \ if geomKey: saveGeom(diag, geomKey) QDialog.reject(diag) - diag.connect(box, SIGNAL("rejected()"), onReject) + box.rejected.connect(onReject) diag.setMinimumHeight(minHeight) diag.setMinimumWidth(minWidth) if geomKey: @@ -166,13 +166,10 @@ class GetTextDialog(QDialog): b = QDialogButtonBox(buts) v.addWidget(b) self.setLayout(v) - self.connect(b.button(QDialogButtonBox.Ok), - SIGNAL("clicked()"), self.accept) - self.connect(b.button(QDialogButtonBox.Cancel), - SIGNAL("clicked()"), self.reject) + b.button(QDialogButtonBox.Ok).clicked.connect(self.accept) + b.button(QDialogButtonBox.Cancel).clicked.connect(self.reject) if help: - self.connect(b.button(QDialogButtonBox.Help), - SIGNAL("clicked()"), self.helpRequested) + b.button(QDialogButtonBox.Help).clicked.connect(self.helpRequested) def accept(self): return QDialog.accept(self) @@ -214,7 +211,7 @@ def chooseList(prompt, choices, startrow=0, parent=None): c.setCurrentRow(startrow) l.addWidget(c) bb = QDialogButtonBox(QDialogButtonBox.Ok) - bb.connect(bb, SIGNAL("accepted()"), d, SLOT("accept()")) + bb.accepted.connect(d.accept) l.addWidget(bb) d.exec_() return c.currentRow() @@ -239,9 +236,6 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None): else: dirkey = None d = QFileDialog(parent) - # fix #233 crash - if isMac: - d.setOptions(QFileDialog.DontUseNativeDialog) d.setFileMode(QFileDialog.ExistingFile) if os.path.exists(dir): d.setDirectory(dir) @@ -249,8 +243,6 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None): d.setNameFilter(filter) ret = [] def accept(): - # work around an osx crash - #aqt.mw.app.processEvents() file = str(list(d.selectedFiles())[0]) if dirkey: dir = os.path.dirname(file) @@ -258,7 +250,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None): if cb: cb(file) ret.append(file) - d.connect(d, SIGNAL("accepted()"), accept) + d.accepted.connect(accept) d.exec_() return ret and ret[0] @@ -268,9 +260,9 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None): config_key = dir_description + 'Directory' base = aqt.mw.pm.profile.get(config_key, aqt.mw.pm.base) path = os.path.join(base, fname) - file = str(QFileDialog.getSaveFileName( + file = QFileDialog.getSaveFileName( parent, title, path, "{0} (*{1})".format(key, ext), - options=QFileDialog.DontConfirmOverwrite)) + options=QFileDialog.DontConfirmOverwrite)[0] if file: # add extension if not file.lower().endswith(ext): @@ -359,8 +351,6 @@ def getBase(col): def openFolder(path): if isWin: - if isinstance(path, str): - path = path.encode(sys.getfilesystemencoding()) subprocess.Popen(["explorer", path]) else: QDesktopServices.openUrl(QUrl("file://" + path)) @@ -380,8 +370,7 @@ def addCloseShortcut(widg): if not isMac: return widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg) - widg.connect(widg._closeShortcut, SIGNAL("activated()"), - widg, SLOT("reject()")) + widg._closeShortcut.activated.connect(widg.reject) def downArrow(): if isWin: diff --git a/aqt/webview.py b/aqt/webview.py index c9f40ff71..9ee31df29 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -9,53 +9,34 @@ from aqt.utils import openLink from anki.utils import isMac, isWin import anki.js -# Bridge for Qt<->JS -########################################################################## - -class Bridge(QObject): - @pyqtSlot(str, result=str) - def run(self, str): - return self._bridge(str) - @pyqtSlot(str) - def link(self, str): - self._linkHandler(str) - def setBridge(self, func): - self._bridge = func - def setLinkHandler(self, func): - self._linkHandler = func - # Page for debug messages ########################################################################## -class AnkiWebPage(QWebPage): +class AnkiWebPage(QWebEnginePage): - def __init__(self, jsErr): - QWebPage.__init__(self) + def __init__(self, jsErr, acceptNavReq): + QWebEnginePage.__init__(self) self._jsErr = jsErr - def javaScriptConsoleMessage(self, msg, line, srcID): - self._jsErr(msg, line, srcID) + self._acceptNavReq = acceptNavReq + + def javaScriptConsoleMessage(self, lvl, msg, line, srcID): + self._jsErr(lvl, msg, line, srcID) + + def acceptNavigationRequest(self, url, navType, isMainFrame): + return self._acceptNavReq(url, navType, isMainFrame) # Main web view ########################################################################## -class AnkiWebView(QWebView): +class AnkiWebView(QWebEngineView): def __init__(self, canFocus=True): - QWebView.__init__(self) - self.setRenderHints( - QPainter.TextAntialiasing | - QPainter.SmoothPixmapTransform | - QPainter.HighQualityAntialiasing) + QWebEngineView.__init__(self) self.setObjectName("mainText") - self._bridge = Bridge() - self._page = AnkiWebPage(self._jsErr) + self._page = AnkiWebPage(self._jsErr, self._acceptNavReq) self._loadFinishedCB = None self.setPage(self._page) - self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) - self.setLinkHandler() - self.setKeyHandler() - self.connect(self, SIGNAL("linkClicked(QUrl)"), self._linkHandler) - self.connect(self, SIGNAL("loadFinished(bool)"), self._loadFinished) + self.resetHandlers() self.allowDrops = False # reset each time new html is set; used to detect if still in same state self.key = None @@ -63,72 +44,81 @@ class AnkiWebView(QWebView): def keyPressEvent(self, evt): if evt.matches(QKeySequence.Copy): - self.triggerPageAction(QWebPage.Copy) + self.triggerPageAction(QWebEnginePage.Copy) evt.accept() # work around a bug with windows qt where shift triggers buttons if isWin and evt.modifiers() & Qt.ShiftModifier and not evt.text(): evt.accept() return - QWebView.keyPressEvent(self, evt) + QWebEngineView.keyPressEvent(self, evt) def keyReleaseEvent(self, evt): if self._keyHandler: if self._keyHandler(evt): evt.accept() return - QWebView.keyReleaseEvent(self, evt) + QWebEngineView.keyReleaseEvent(self, evt) def contextMenuEvent(self, evt): if not self._canFocus: return m = QMenu(self) a = m.addAction(_("Copy")) - a.connect(a, SIGNAL("triggered()"), - lambda: self.triggerPageAction(QWebPage.Copy)) + a.triggered.connect(lambda: self.triggerPageAction(QWebEnginePage.Copy)) runHook("AnkiWebView.contextMenuEvent", self, m) m.popup(QCursor.pos()) def dropEvent(self, evt): pass - def setLinkHandler(self, handler=None): - if handler: - self.linkHandler = handler - else: - self.linkHandler = self._openLinksExternally - self._bridge.setLinkHandler(self.linkHandler) - def setKeyHandler(self, handler=None): # handler should return true if event should be swallowed self._keyHandler = handler - def setHtml(self, html, loadCB=None): + def setHtml(self, html): self.key = None - self._loadFinishedCB = loadCB - QWebView.setHtml(self, html) + app = QApplication.instance() + oldFocus = app.focusWidget() + self._page.setHtml(html) + # work around webengine stealing focus on setHtml() + if oldFocus: + oldFocus.setFocus() - def stdHtml(self, body, css="", bodyClass="", loadCB=None, js=None, head=""): + def stdHtml(self, body, css="", bodyClass="", js=None, head=""): if isMac: button = "font-weight: bold; height: 24px;" else: button = "font-weight: normal;" + + screen = QApplication.desktop().screen() + dpi = screen.logicalDpiX() + zoomFactor = max(1, dpi / 96.0) + self.setHtml(""" - + %s %s""" % ( - button, css, js or anki.js.jquery+anki.js.browserSel, - head, bodyClass, body), loadCB) - - def setBridge(self, bridge): - self._bridge.setBridge(bridge) + zoomFactor, button, css, js or anki.js.jquery+anki.js.browserSel, + head, bodyClass, body)) def setCanFocus(self, canFocus=False): self._canFocus = canFocus @@ -138,21 +128,39 @@ button { self.setFocusPolicy(Qt.NoFocus) def eval(self, js): - self.page().mainFrame().evaluateJavaScript(js) + self.page().runJavaScript(js) def _openLinksExternally(self, url): openLink(url) - def _jsErr(self, msg, line, srcID): + def _jsErr(self, lvl, msg, line, srcID): sys.stdout.write( (_("JS error on line %(a)d: %(b)s") % dict(a=line, b=msg+"\n"))) - def _linkHandler(self, url): - self.linkHandler(url.toString()) + def _acceptNavReq(self, url, navType, isMainFrame): + # is it an anki link? + urlstr = url.toString() + #print("got url",urlstr) + prefix = "http://anki/" + if urlstr.startswith(prefix): + urlstr = urlstr[len(prefix):] + if urlstr == "domDone": + self.onLoadFinished() + else: + self.onAnkiLink(urlstr) + return False + # load all other links in browser + openLink(url) + return False - def _loadFinished(self): - self.page().mainFrame().addToJavaScriptWindowObject("py", self._bridge) - if self._loadFinishedCB: - self._loadFinishedCB(self) - self._loadFinishedCB = None + def defaultOnAnkiLink(self, link): + print("unhandled anki link:", link) + + def defaultOnLoadFinished(self): + pass + + def resetHandlers(self): + self.setKeyHandler(None) + self.onAnkiLink = self.defaultOnAnkiLink + self.onLoadFinished = self.defaultOnLoadFinished diff --git a/designer/about.ui b/designer/about.ui index bcee19b2f..b736a4b86 100644 --- a/designer/about.ui +++ b/designer/about.ui @@ -20,12 +20,21 @@ About Anki - + + 0 + + + 0 + + + 0 + + 0 - - + + about:blank @@ -46,9 +55,10 @@ - QWebView + AnkiWebView QWidget -
    QtWebKit/QWebView
    +
    aqt/webview
    + 1
    diff --git a/designer/browser.ui b/designer/browser.ui index 5451ffa17..92592f4cb 100644 --- a/designer/browser.ui +++ b/designer/browser.ui @@ -28,7 +28,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -76,17 +85,20 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 - - 6 - - - 0 - 0 @@ -96,6 +108,12 @@ 0 + + 6 + + + 0 + @@ -523,7 +541,7 @@ actionClose - activated() + triggered() Dialog close() diff --git a/designer/finddupes.ui b/designer/finddupes.ui index fea182b11..09c349159 100644 --- a/designer/finddupes.ui +++ b/designer/finddupes.ui @@ -47,12 +47,21 @@ QFrame::Raised - + + 0 + + + 0 + + + 0 + + 0 - - + + about:blank @@ -76,9 +85,10 @@ - QWebView + AnkiWebView QWidget -
    QtWebKit/QWebView
    +
    aqt/webview
    + 1
    diff --git a/designer/stats.ui b/designer/stats.ui index 1b7da307b..86869fedf 100644 --- a/designer/stats.ui +++ b/designer/stats.ui @@ -17,12 +17,21 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 - - + + about:blank @@ -34,7 +43,16 @@ 8 - + + 6 + + + 6 + + + 6 + + 6 @@ -125,9 +143,10 @@ - QWebView + AnkiWebView QWidget -
    QtWebKit/QWebView
    +
    aqt/webview
    + 1
    diff --git a/tools/build_ui.sh b/tools/build_ui.sh index c26521cfd..85feddb24 100755 --- a/tools/build_ui.sh +++ b/tools/build_ui.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# generate python files based on the designer ui files. pyuic4 and pyrcc4 +# generate python files based on the designer ui files. pyuic5 and pyrcc5 # should be on the path. # @@ -24,10 +24,10 @@ do base=$(basename $i .ui) py="aqt/forms/${base}.py" echo " \"$base\"," >> $init - echo "import $base" >> $temp + echo "from . import $base" >> $temp if [ $i -nt $py ]; then echo " * "$py - pyuic4 $i -o $py + pyuic5 --from-imports $i -o $py # munge the output to use gettext perl -pi.bak -e 's/(QtGui\.QApplication\.)?_?translate\(".*?", /_(/; s/, None.*/))/' $py rm $py.bak @@ -38,4 +38,4 @@ cat $temp >> $init rm $temp echo "Building resources.." -pyrcc4 designer/icons.qrc -o aqt/forms/icons_rc.py +pyrcc5 designer/icons.qrc -o aqt/forms/icons_rc.py