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/<something>' - 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)
This commit is contained in:
Damien Elmes 2016-05-31 18:51:40 +10:00
parent 4d88b62fbf
commit de7e40537d
39 changed files with 530 additions and 573 deletions

View File

@ -1,7 +1,6 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# 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)

View File

@ -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 = "<center><img src='qrc:/icons/anki-logo-thin.png'></center>"
abouttext += '<p>' + _("Anki is a friendly, intelligent spaced learning \
system. It's free and open source.")

View File

@ -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)))

View File

@ -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")

View File

@ -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 + "<p>" + 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 = "<html><body>"
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 += "<p><ol>"
for val, nids in res:
t += '<li><a href="%s">%s</a>: %s</a>' % (
t += '''<li><a href=# onclick="openAnkiLink('%s')">%s</a>: %s</a>''' % (
"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 = '''\
<a class=hitem title="%s" href="%s">\
<a class=hitem title="%s" href=# onclick="openAnkiLink('%s')">\
<img valign=bottom style='border: 1px solid #aaa;' src="qrc:/icons/%s.png"> %s</a>'''
else:
fmt = '''\
<a class=hitem title="%s" href="%s"><img style="padding: 1px;" valign=bottom src="qrc:/icons/%s.png"> %s</a>'''
<a class=hitem title="%s" href=# onclick="openAnkiLink('%s')"><img style="padding: 1px;" valign=bottom src="qrc:/icons/%s.png"> %s</a>'''
return fmt % (tooltip or title, link, icon, title)
right = "<div>"
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 += "</div>"
self.web.page().currentFrame().setScrollBarPolicy(
Qt.Horizontal, Qt.ScrollBarAlwaysOff)
# fixme
#self.web.page().currentFrame().setScrollBarPolicy(
# Qt.Horizontal, Qt.ScrollBarAlwaysOff)
self.web.stdHtml(self._body % (
"", #<span style='display:inline-block; width: 100px;'></span>",
#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)

View File

@ -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_()

View File

@ -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

View File

@ -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);
}
</script>
"""
@ -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 "<br><div style='width:50%;border: 1px solid #000;padding:5px;'>"+(
_("You have a lot of decks. Please see %(a)s. %(b)s") % dict(
a=("<a href=lots>%s</a>" % _("this page")),
b=("<br><small><a href=hidelots>(%s)</a></small>" % (_("hide"))+
a=("<a href=# onclick='openAnkiLink('lots')>%s</a>" % _(
"this page")),
b=("<br><small><a href=# onclick='openAnkiLink(\"hidelots\")'>("
"%s)</a></small>" % (_("hide"))+
"</div")))
def _renderDeckTree(self, nodes, depth=0):
@ -183,7 +184,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
if depth == 0:
buf = """
<tr><th colspan=5 align=left>%s</th><th class=count>%s</th>
<th class=count>%s</th><th class=count></th></tr>""" % (
<th class=count>%s</th><th class=optscol></th></tr>""" % (
_("Deck"), _("Due"), _("New"))
buf += self._topLevelDragRow()
else:
@ -219,7 +220,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
buf = "<tr class='%s' id='%d'>" % (klass, did)
# deck link
if children:
collapse = "<a class=collapse href='collapse:%d'>%s</a>" % (did, prefix)
collapse = "<a class=collapse href=# onclick='openAnkiLink(\"collapse:%d\")'>%s</a>" % (did, prefix)
else:
collapse = "<span class=collapse></span>"
if deck['dyn']:
@ -228,7 +229,8 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
extraclass = ""
buf += """
<td class=decktd colspan=5>%s%s<a class="deck %s" href='open:%d'>%s</a></td>"""% (
<td class=decktd colspan=5>%s%s<a class="deck %s"
href=# onclick="openAnkiLink('open:%d')">%s</a></td>"""% (
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 += "<td align=right class=opts>%s</td></tr>" % self.mw.button(
link="opts:%d"%did, name="<img valign=bottom src='qrc:/icons/gears.png'>"+downArrow())
buf += ("<td align=center class=opts><a onclick='openAnkiLink(\"opts:%d\");'>"
"<img valign=right src='qrc:/icons/gears.png'></a></td></tr>" % 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 += """
<button title='%s' onclick='py.link(\"%s\");'>%s</button>""" % tuple(b)
<button title='%s' onclick='openAnkiLink(\"%s\");'>%s</button>""" % 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/")

View File

@ -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

View File

@ -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())

View File

@ -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:

View File

@ -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()

View File

@ -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)

View File

@ -1,22 +1,30 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# -*- 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:

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

@ -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 '''
<button id="%s" class="%s" onclick="py.link('%s');return false;"
title="%s">%s</button>''' % (
id, class_, link, key, name)
<button id="%s" class="%s" onclick="openAnkiLink('%s');return false;"
title="%s" %s>%s</button>''' % (
id, class_, link, key, extra, name)
# Main window setup
##########################################################################
@ -615,8 +612,7 @@ title="%s">%s</button>''' % (
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</button>''' % (
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</button>''' % (
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":

View File

@ -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(

View File

@ -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_()

View File

@ -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 += """
<button title="%s" onclick='py.link(\"%s\");'>%s</button>""" % tuple(b)
<button title="%s" onclick='openAnkiLink("%s")'>%s</button>""" % 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
######################################################################

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -1,30 +1,25 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# 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'<span style="display: inline-block; text-align: center; line-height: 1; white-space: nowrap; vertical-align: baseline; margin: 0; padding: 0"><span style="display: block; text-decoration: none; line-height: 1.2; font-weight: normal; font-size: 0.64em">\2</span>\1</span>'
if qtmajor < 5 or (qtmajor == 5 and qtminor < 5):
raise Exception("Qt must be 5.5+")

View File

@ -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");
}
}
</script>
@ -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 <qt4.8, calling _showAnswer() directly fails to show images on
# the answer side. But if we trigger it via the bottom web's python
# link, it inexplicably works.
self.bottom.web.eval("py.link('ans');")
def _keyHandler(self, evt):
key = str(evt.text())
if key == "e":
self.mw.onEditCurrent()
elif (key == " " or evt.key() in (Qt.Key_Return, Qt.Key_Enter)):
if self.state == "question":
self._showAnswerHack()
self._showAnswer()
elif self.state == "answer":
self._answerCard(self._defaultEase())
elif key == "r" or evt.key() == Qt.Key_F5:
@ -316,8 +311,6 @@ The front of this card is empty. Please run Tools>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;
<tr>
<td align=left width=50 valign=top class=stat>
<br>
<button title="%(editkey)s" onclick="py.link('edit');">%(edit)s</button></td>
<button title="%(editkey)s" onclick="openAnkiLink('edit');">%(edit)s</button></td>
<td align=center valign=top id=middle>
</td>
<td width=50 align=right valign=top class=stat><span id=time class=stattxt>
</span><br>
<button onclick="py.link('more');">%(more)s %(downArrow)s</button>
<button onclick="openAnkiLink('more');">%(more)s %(downArrow)s</button>
</td>
</tr>
</table>
@ -603,7 +596,7 @@ function showAnswer(txt) {
self.bottom.web.setFocus()
middle = '''
<span class=stattxt>%s</span><br>
<button title="%s" id=ansbut onclick='py.link(\"ans\");'>%s</button>''' % (
<button title="%s" id=ansbut onclick='openAnkiLink("ans");'>%s</button>''' % (
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 = "<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>" % middle
@ -661,7 +654,7 @@ function showAnswer(txt) {
extra = ""
due = self._buttonTime(i)
return '''
<td align=center>%s<button %s title="%s" onclick='py.link("ease%d");'>\
<td align=center>%s<button %s title="%s" onclick='openAnkiLink("ease%d");'>\
%s</button></td>''' % (due, extra, _("Shortcut key: %s") % i, i, label)
buf = "<center><table cellpading=0 cellspacing=0><tr>"
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())

View File

@ -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("<html><body>"+self.report+"</body></html>")
self.mw.progress.finish()

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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 += '<a class=hitem title="%s" href="%s">%s</a>' % (
buf += '''
<a class=hitem title="%s" href=# onclick="openAnkiLink('%s')">%s</a>''' % (
title, ln, name)
buf += "&nbsp;"*3
return buf
@ -59,7 +59,8 @@ class Toolbar(object):
def _rightIcons(self):
buf = ""
for ln, icon, title in self._rightIconsList():
buf += '<a class=hitem title="%s" href="%s"><img width="16px" height="16px" src="%s"></a>' % (
buf += '''
<a class=hitem title="%s" href=# onclick='openAnkiLink("%s")'><img width="16px" height="16px" src="%s"></a>''' % (
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")

View File

@ -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 = (

View File

@ -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:

View File

@ -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("""
<!doctype html>
<html><head><style>
body { zoom: %f; }
button {
%s
}
%s</style>
<script>%s</script>
<script>
%s
openAnkiLink = function(txt) {
window.location = "http://anki/"+txt;
}
document.addEventListener("DOMContentLoaded", function(event) {
openAnkiLink("domDone");
});
</script>
%s
</head>
<body class="%s">%s</body></html>""" % (
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

View File

@ -20,12 +20,21 @@
<string>About Anki</string>
</property>
<layout class="QVBoxLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWebView" name="label">
<property name="url">
<widget class="AnkiWebView" name="label" native="true">
<property name="url" stdset="0">
<url>
<string>about:blank</string>
</url>
@ -46,9 +55,10 @@
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<class>AnkiWebView</class>
<extends>QWidget</extends>
<header>QtWebKit/QWebView</header>
<header location="global">aqt/webview</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>

View File

@ -28,7 +28,16 @@
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
@ -76,17 +85,20 @@
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
@ -96,6 +108,12 @@
<property name="rightMargin">
<number>0</number>
</property>
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QComboBox" name="searchEdit">
<property name="sizePolicy">
@ -523,7 +541,7 @@
</connection>
<connection>
<sender>actionClose</sender>
<signal>activated()</signal>
<signal>triggered()</signal>
<receiver>Dialog</receiver>
<slot>close()</slot>
<hints>

View File

@ -47,12 +47,21 @@
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWebView" name="webView">
<property name="url">
<widget class="AnkiWebView" name="webView" native="true">
<property name="url" stdset="0">
<url>
<string>about:blank</string>
</url>
@ -76,9 +85,10 @@
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<class>AnkiWebView</class>
<extends>QWidget</extends>
<header>QtWebKit/QWebView</header>
<header location="global">aqt/webview</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>

View File

@ -17,12 +17,21 @@
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWebView" name="web">
<property name="url">
<widget class="AnkiWebView" name="web" native="true">
<property name="url" stdset="0">
<url>
<string>about:blank</string>
</url>
@ -34,7 +43,16 @@
<property name="spacing">
<number>8</number>
</property>
<property name="margin">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
@ -125,9 +143,10 @@
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<class>AnkiWebView</class>
<extends>QWidget</extends>
<header>QtWebKit/QWebView</header>
<header location="global">aqt/webview</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>

View File

@ -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