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> # Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import getpass import getpass
import os
import sys import sys
import optparse import optparse
import tempfile import tempfile
@ -118,6 +117,8 @@ class AnkiApp(QApplication):
# Single instance support on Win32/Linux # Single instance support on Win32/Linux
################################################## ##################################################
appMsg = pyqtSignal(str)
KEY = "anki"+checksum(getpass.getuser()) KEY = "anki"+checksum(getpass.getuser())
TMOUT = 5000 TMOUT = 5000
@ -140,7 +141,7 @@ class AnkiApp(QApplication):
# previous instance died # previous instance died
QLocalServer.removeServer(self.KEY) QLocalServer.removeServer(self.KEY)
self._srv = QLocalServer(self) self._srv = QLocalServer(self)
self.connect(self._srv, SIGNAL("newConnection()"), self.onRecv) self._srv.newConnection.connect(self.onRecv)
self._srv.listen(self.KEY) self._srv.listen(self.KEY)
return False return False
@ -150,7 +151,7 @@ class AnkiApp(QApplication):
if not sock.waitForConnected(self.TMOUT): if not sock.waitForConnected(self.TMOUT):
# first instance or previous instance dead # first instance or previous instance dead
return False return False
sock.write(txt) sock.write(txt.encode("utf8"))
if not sock.waitForBytesWritten(self.TMOUT): if not sock.waitForBytesWritten(self.TMOUT):
# existing instance running but hung # existing instance running but hung
return False return False
@ -162,9 +163,8 @@ class AnkiApp(QApplication):
if not sock.waitForReadyRead(self.TMOUT): if not sock.waitForReadyRead(self.TMOUT):
sys.stderr.write(sock.errorString()) sys.stderr.write(sock.errorString())
return return
buf = sock.readAll() path = bytes(sock.readAll()).decode("utf8")
buf = str(buf, sys.getfilesystemencoding(), "ignore") self.appMsg.emit(path)
self.emit(SIGNAL("appMsg"), buf)
sock.disconnectFromServer() sock.disconnectFromServer()
# OS X file/url handler # OS X file/url handler
@ -172,7 +172,7 @@ class AnkiApp(QApplication):
def event(self, evt): def event(self, evt):
if evt.type() == QEvent.FileOpen: if evt.type() == QEvent.FileOpen:
self.emit(SIGNAL("appMsg"), evt.file() or "raise") self.appMsg.emit(evt.file() or "raise")
return True return True
return QApplication.event(self, evt) 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 # on osx we'll need to add the qt plugins to the search path
if isMac and getattr(sys, 'frozen', None): if isMac and getattr(sys, 'frozen', None):
rd = os.path.abspath(moduleDir + "/../../..") rd = os.path.abspath(moduleDir + "/../../../plugins")
QCoreApplication.setLibraryPaths([rd]) QCoreApplication.setLibraryPaths([rd])
if isMac:
QFont.insertSubstitution(".Lucida Grande UI", "Lucida Grande")
# create the app # create the app
app = AnkiApp(sys.argv) app = AnkiApp(sys.argv)
QCoreApplication.setApplicationName("Anki") 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.""") environment points to a valid, writable folder.""")
return 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 # profile manager
from aqt.profiles import ProfileManager from aqt.profiles import ProfileManager
pm = ProfileManager(opts.base, opts.profile) pm = ProfileManager(opts.base, opts.profile)

View File

@ -11,12 +11,6 @@ def show(parent):
dialog = QDialog(parent) dialog = QDialog(parent)
abt = aqt.forms.about.Ui_About() abt = aqt.forms.about.Ui_About()
abt.setupUi(dialog) 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 = "<center><img src='qrc:/icons/anki-logo-thin.png'></center>"
abouttext += '<p>' + _("Anki is a friendly, intelligent spaced learning \ abouttext += '<p>' + _("Anki is a friendly, intelligent spaced learning \
system. It's free and open source.") system. It's free and open source.")

View File

@ -53,20 +53,18 @@ class AddCards(QDialog):
ar = QDialogButtonBox.ActionRole ar = QDialogButtonBox.ActionRole
# add # add
self.addButton = bb.addButton(_("Add"), ar) self.addButton = bb.addButton(_("Add"), ar)
self.addButton.clicked.connect(self.addCards)
self.addButton.setShortcut(QKeySequence("Ctrl+Return")) self.addButton.setShortcut(QKeySequence("Ctrl+Return"))
self.addButton.setToolTip(shortcut(_("Add (shortcut: ctrl+enter)"))) self.addButton.setToolTip(shortcut(_("Add (shortcut: ctrl+enter)")))
self.connect(self.addButton, SIGNAL("clicked()"), self.addCards)
# close # close
self.closeButton = QPushButton(_("Close")) self.closeButton = QPushButton(_("Close"))
self.closeButton.setAutoDefault(False) self.closeButton.setAutoDefault(False)
bb.addButton(self.closeButton, bb.addButton(self.closeButton, QDialogButtonBox.RejectRole)
QDialogButtonBox.RejectRole)
# help # help
self.helpButton = QPushButton(_("Help")) self.helpButton = QPushButton(_("Help"), clicked=self.helpRequested)
self.helpButton.setAutoDefault(False) self.helpButton.setAutoDefault(False)
bb.addButton(self.helpButton, bb.addButton(self.helpButton,
QDialogButtonBox.HelpRole) QDialogButtonBox.HelpRole)
self.connect(self.helpButton, SIGNAL("clicked()"), self.helpRequested)
# history # history
b = bb.addButton( b = bb.addButton(
_("History")+ " "+downArrow(), ar) _("History")+ " "+downArrow(), ar)
@ -76,7 +74,7 @@ class AddCards(QDialog):
sc = "Ctrl+H" sc = "Ctrl+H"
b.setShortcut(QKeySequence(sc)) b.setShortcut(QKeySequence(sc))
b.setToolTip(_("Shortcut: %s") % shortcut(sc)) b.setToolTip(_("Shortcut: %s") % shortcut(sc))
self.connect(b, SIGNAL("clicked()"), self.onHistory) b.clicked.connect(self.onHistory)
b.setEnabled(False) b.setEnabled(False)
self.historyButton = b self.historyButton = b
@ -145,8 +143,7 @@ class AddCards(QDialog):
m = QMenu(self) m = QMenu(self)
for nid, txt in self.history: for nid, txt in self.history:
a = m.addAction(_("Edit %s") % txt) a = m.addAction(_("Edit %s") % txt)
a.connect(a, SIGNAL("triggered()"), a.triggered.connect(lambda b, nid=nid: self.editHistory(nid))
lambda nid=nid: self.editHistory(nid))
runHook("AddCards.onHistory", self, m) runHook("AddCards.onHistory", self, m)
m.exec_(self.historyButton.mapToGlobal(QPoint(0,0))) m.exec_(self.historyButton.mapToGlobal(QPoint(0,0)))

View File

@ -21,9 +21,9 @@ class AddonManager(object):
def __init__(self, mw): def __init__(self, mw):
self.mw = mw self.mw = mw
f = self.mw.form; s = SIGNAL("triggered()") f = self.mw.form
self.mw.connect(f.actionOpenPluginFolder, s, self.onOpenAddonFolder) f.actionOpenPluginFolder.triggered.connect(self.onOpenAddonFolder)
self.mw.connect(f.actionDownloadSharedPlugin, s, self.onGetAddons) f.actionDownloadSharedPlugin.triggered.connect(self.onGetAddons)
self._menus = [] self._menus = []
if isWin: if isWin:
self.clearAddonCache() self.clearAddonCache()
@ -46,7 +46,7 @@ class AddonManager(object):
# Menus # Menus
###################################################################### ######################################################################
def onOpenAddonFolder(self, path=None): def onOpenAddonFolder(self, checked, path=None):
if path is None: if path is None:
path = self.addonsFolder() path = self.addonsFolder()
openFolder(path) openFolder(path)
@ -58,14 +58,11 @@ class AddonManager(object):
m = self.mw.form.menuPlugins.addMenu( m = self.mw.form.menuPlugins.addMenu(
os.path.splitext(file)[0]) os.path.splitext(file)[0])
self._menus.append(m) self._menus.append(m)
a = QAction(_("Edit..."), self.mw) a = QAction(_("Edit..."), self.mw, triggered=self.onEdit)
p = os.path.join(self.addonsFolder(), file) p = os.path.join(self.addonsFolder(), file)
self.mw.connect(a, SIGNAL("triggered()"),
lambda p=p: self.onEdit(p))
m.addAction(a) m.addAction(a)
a = QAction(_("Delete..."), self.mw) a = QAction(_("Delete..."), self.mw, triggered=self.onRem)
self.mw.connect(a, SIGNAL("triggered()"),
lambda p=p: self.onRem(p))
m.addAction(a) m.addAction(a)
def onEdit(self, path): def onEdit(self, path):
@ -74,8 +71,7 @@ class AddonManager(object):
frm.setupUi(d) frm.setupUi(d)
d.setWindowTitle(os.path.basename(path)) d.setWindowTitle(os.path.basename(path))
frm.text.setPlainText(open(path).read()) frm.text.setPlainText(open(path).read())
d.connect(frm.buttonBox, SIGNAL("accepted()"), frm.buttonBox.accepted.connect(lambda: self.onAcceptEdit(path, frm))
lambda: self.onAcceptEdit(path, frm))
d.exec_() d.exec_()
def onAcceptEdit(self, path, frm): def onAcceptEdit(self, path, frm):
@ -94,8 +90,6 @@ class AddonManager(object):
def addonsFolder(self): def addonsFolder(self):
dir = self.mw.pm.addonFolder() dir = self.mw.pm.addonFolder()
if isWin:
dir = dir.encode(sys.getfilesystemencoding())
return dir return dir
def clearAddonCache(self): def clearAddonCache(self):
@ -115,7 +109,9 @@ class AddonManager(object):
###################################################################### ######################################################################
def onGetAddons(self): 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): def install(self, data, fname):
if fname.endswith(".py"): if fname.endswith(".py"):
@ -146,7 +142,7 @@ class GetAddons(QDialog):
self.form.setupUi(self) self.form.setupUi(self)
b = self.form.buttonBox.addButton( b = self.form.buttonBox.addButton(
_("Browse"), QDialogButtonBox.ActionRole) _("Browse"), QDialogButtonBox.ActionRole)
self.connect(b, SIGNAL("clicked()"), self.onBrowse) b.clicked.connect(self.onBrowse)
restoreGeom(self, "getaddons", adjustSize=True) restoreGeom(self, "getaddons", adjustSize=True)
self.exec_() self.exec_()
saveGeom(self, "getaddons") saveGeom(self, "getaddons")

View File

@ -55,7 +55,7 @@ class DataModel(QAbstractTableModel):
del self.cardObjs[c.id] del self.cardObjs[c.id]
refresh = True refresh = True
if refresh: if refresh:
self.emit(SIGNAL("layoutChanged()")) self.layoutChanged.emit()
# Model interface # Model interface
###################################################################### ######################################################################
@ -382,61 +382,61 @@ class Browser(QMainWindow):
def setupMenus(self): def setupMenus(self):
# actions # actions
c = self.connect; f = self.form; s = SIGNAL("triggered()") f = self.form
if not isMac: if not isMac:
f.actionClose.setVisible(False) f.actionClose.setVisible(False)
c(f.actionReposition, s, self.reposition) f.actionReposition.triggered.connect(self.reposition)
c(f.actionReschedule, s, self.reschedule) f.actionReschedule.triggered.connect(self.reschedule)
c(f.actionCram, s, self.cram) f.actionCram.triggered.connect(self.cram)
c(f.actionChangeModel, s, self.onChangeModel) f.actionChangeModel.triggered.connect(self.onChangeModel)
# edit # edit
c(f.actionUndo, s, self.mw.onUndo) f.actionUndo.triggered.connect(self.mw.onUndo)
c(f.previewButton, SIGNAL("clicked()"), self.onTogglePreview) f.previewButton.clicked.connect(self.onTogglePreview)
f.previewButton.setToolTip(_("Preview Selected Card (%s)") % f.previewButton.setToolTip(_("Preview Selected Card (%s)") %
shortcut(_("Ctrl+Shift+P"))) shortcut(_("Ctrl+Shift+P")))
c(f.actionInvertSelection, s, self.invertSelection) f.actionInvertSelection.triggered.connect(self.invertSelection)
c(f.actionSelectNotes, s, self.selectNotes) f.actionSelectNotes.triggered.connect(self.selectNotes)
c(f.actionFindReplace, s, self.onFindReplace) f.actionFindReplace.triggered.connect(self.onFindReplace)
c(f.actionFindDuplicates, s, self.onFindDupes) f.actionFindDuplicates.triggered.connect(self.onFindDupes)
# jumps # jumps
c(f.actionPreviousCard, s, self.onPreviousCard) f.actionPreviousCard.triggered.connect(self.onPreviousCard)
c(f.actionNextCard, s, self.onNextCard) f.actionNextCard.triggered.connect(self.onNextCard)
c(f.actionFirstCard, s, self.onFirstCard) f.actionFirstCard.triggered.connect(self.onFirstCard)
c(f.actionLastCard, s, self.onLastCard) f.actionLastCard.triggered.connect(self.onLastCard)
c(f.actionFind, s, self.onFind) f.actionFind.triggered.connect(self.onFind)
c(f.actionNote, s, self.onNote) f.actionNote.triggered.connect(self.onNote)
c(f.actionTags, s, self.onTags) f.actionTags.triggered.connect(self.onTags)
c(f.actionCardList, s, self.onCardList) f.actionCardList.triggered.connect(self.onCardList)
# help # help
c(f.actionGuide, s, self.onHelp) f.actionGuide.triggered.connect(self.onHelp)
# keyboard shortcut for shift+home/end # keyboard shortcut for shift+home/end
self.pgUpCut = QShortcut(QKeySequence("Shift+Home"), self) 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) self.pgDownCut = QShortcut(QKeySequence("Shift+End"), self)
c(self.pgDownCut, SIGNAL("activated()"), self.onLastCard) self.pgDownCut.activated.connect(self.onLastCard)
# add note # add note
self.addCut = QShortcut(QKeySequence("Ctrl+E"), self) self.addCut = QShortcut(QKeySequence("Ctrl+E"), self)
c(self.addCut, SIGNAL("activated()"), self.mw.onAddCard) self.addCut.activated.connect(self.mw.onAddCard)
# card info # card info
self.infoCut = QShortcut(QKeySequence("Ctrl+Shift+I"), self) self.infoCut = QShortcut(QKeySequence("Ctrl+Shift+I"), self)
c(self.infoCut, SIGNAL("activated()"), self.showCardInfo) self.infoCut.activated.connect(self.showCardInfo)
# set deck # set deck
self.changeDeckCut = QShortcut(QKeySequence("Ctrl+D"), self) self.changeDeckCut = QShortcut(QKeySequence("Ctrl+D"), self)
c(self.changeDeckCut, SIGNAL("activated()"), self.setDeck) self.changeDeckCut.activated.connect(self.setDeck)
# add/remove tags # add/remove tags
self.tagCut1 = QShortcut(QKeySequence("Ctrl+Shift+T"), self) 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) 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) self.tagCut3 = QShortcut(QKeySequence("Ctrl+K"), self)
c(self.tagCut3, SIGNAL("activated()"), self.onMark) self.tagCut3.activated.connect(self.onMark)
# suspending # suspending
self.susCut1 = QShortcut(QKeySequence("Ctrl+J"), self) self.susCut1 = QShortcut(QKeySequence("Ctrl+J"), self)
c(self.susCut1, SIGNAL("activated()"), self.onSuspend) self.susCut1.activated.connect(self.onSuspend)
# deletion # deletion
self.delCut1 = QShortcut(QKeySequence("Delete"), self) self.delCut1 = QShortcut(QKeySequence("Delete"), self)
self.delCut1.setAutoRepeat(False) self.delCut1.setAutoRepeat(False)
c(self.delCut1, SIGNAL("activated()"), self.deleteNotes) self.delCut1.activated.connect(self.deleteNotes)
# add-on hook # add-on hook
runHook('browser.setupMenus', self) runHook('browser.setupMenus', self)
self.mw.maybeHideAccelerators(self) self.mw.maybeHideAccelerators(self)
@ -507,12 +507,8 @@ class Browser(QMainWindow):
def setupSearch(self): def setupSearch(self):
self.filterTimer = None self.filterTimer = None
self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self)) self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self))
self.connect(self.form.searchButton, self.form.searchButton.clicked.connect(self.onSearch)
SIGNAL("clicked()"), self.form.searchEdit.lineEdit().returnPressed.connect(self.onSearch)
self.onSearch)
self.connect(self.form.searchEdit.lineEdit(),
SIGNAL("returnPressed()"),
self.onSearch)
self.form.searchEdit.setCompleter(None) self.form.searchEdit.setCompleter(None)
self.form.searchEdit.addItems(self.mw.pm.profile['searchHistory']) 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.setModel(self.model)
self.form.tableView.selectionModel() self.form.tableView.selectionModel()
self.form.tableView.setItemDelegate(StatusDelegate(self, self.model)) self.form.tableView.setItemDelegate(StatusDelegate(self, self.model))
self.connect(self.form.tableView.selectionModel(), self.form.tableView.selectionModel().selectionChanged.connect(self.onRowChanged)
SIGNAL("selectionChanged(QItemSelection,QItemSelection)"),
self.onRowChanged)
def setupEditor(self): def setupEditor(self):
self.editor = aqt.editor.Editor( self.editor = aqt.editor.Editor(
@ -628,16 +622,13 @@ class Browser(QMainWindow):
restoreHeader(hh, "editor") restoreHeader(hh, "editor")
hh.setHighlightSections(False) hh.setHighlightSections(False)
hh.setMinimumSectionSize(50) hh.setMinimumSectionSize(50)
hh.setMovable(True) hh.setSectionsMovable(True)
self.setColumnSizes() self.setColumnSizes()
hh.setContextMenuPolicy(Qt.CustomContextMenu) hh.setContextMenuPolicy(Qt.CustomContextMenu)
hh.connect(hh, SIGNAL("customContextMenuRequested(QPoint)"), hh.customContextMenuRequested.connect(self.onHeaderContext)
self.onHeaderContext)
self.setSortIndicator() self.setSortIndicator()
hh.connect(hh, SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"), hh.sortIndicatorChanged.connect(self.onSortChanged)
self.onSortChanged) hh.sectionMoved.connect(self.onColumnMoved)
hh.connect(hh, SIGNAL("sectionMoved(int,int,int)"),
self.onColumnMoved)
def onSortChanged(self, idx, ord): def onSortChanged(self, idx, ord):
type = self.model.activeCols[idx] type = self.model.activeCols[idx]
@ -692,8 +683,7 @@ by clicking on one on the left."""))
a = m.addAction(name) a = m.addAction(name)
a.setCheckable(True) a.setCheckable(True)
a.setChecked(type in self.model.activeCols) a.setChecked(type in self.model.activeCols)
a.connect(a, SIGNAL("toggled(bool)"), a.toggled.connect(lambda b, t=type: self.toggleField(t))
lambda b, t=type: self.toggleField(t))
m.exec_(gpos) m.exec_(gpos)
def toggleField(self, type): def toggleField(self, type):
@ -718,8 +708,8 @@ by clicking on one on the left."""))
def setColumnSizes(self): def setColumnSizes(self):
hh = self.form.tableView.horizontalHeader() hh = self.form.tableView.horizontalHeader()
hh.setResizeMode(QHeaderView.Interactive) hh.setSectionResizeMode(QHeaderView.Interactive)
hh.setResizeMode(hh.logicalIndex(len(self.model.activeCols)-1), hh.setSectionResizeMode(hh.logicalIndex(len(self.model.activeCols)-1),
QHeaderView.Stretch) QHeaderView.Stretch)
# this must be set post-resize or it doesn't work # this must be set post-resize or it doesn't work
hh.setCascadingSectionResizes(False) hh.setCascadingSectionResizes(False)
@ -737,19 +727,13 @@ by clicking on one on the left."""))
self.oncollapse = oncollapse self.oncollapse = oncollapse
def setupTree(self): def setupTree(self):
self.connect( self.form.tree.itemClicked.connect(self.onTreeClick)
self.form.tree, SIGNAL("itemClicked(QTreeWidgetItem*,int)"),
self.onTreeClick)
p = QPalette() p = QPalette()
p.setColor(QPalette.Base, QColor("#d6dde0")) p.setColor(QPalette.Base, QColor("#d6dde0"))
self.form.tree.setPalette(p) self.form.tree.setPalette(p)
self.buildTree() self.buildTree()
self.connect( self.form.tree.itemExpanded.connect(lambda item: self.onTreeCollapse(item))
self.form.tree, SIGNAL("itemExpanded(QTreeWidgetItem*)"), self.form.tree.itemCollapsed.connect(lambda item: self.onTreeCollapse(item))
lambda item: self.onTreeCollapse(item))
self.connect(
self.form.tree, SIGNAL("itemCollapsed(QTreeWidgetItem*)"),
lambda item: self.onTreeCollapse(item))
def buildTree(self): def buildTree(self):
self.form.tree.clear() self.form.tree.clear()
@ -876,18 +860,18 @@ by clicking on one on the left."""))
reps = self._revlogData(cs) reps = self._revlogData(cs)
d = QDialog(self) d = QDialog(self)
l = QVBoxLayout() l = QVBoxLayout()
l.setMargin(0) l.setContentsMargins(0,0,0,0)
w = AnkiWebView() w = AnkiWebView()
l.addWidget(w) l.addWidget(w)
w.stdHtml(info + "<p>" + reps) w.stdHtml(info + "<p>" + reps)
bb = QDialogButtonBox(QDialogButtonBox.Close) bb = QDialogButtonBox(QDialogButtonBox.Close)
l.addWidget(bb) l.addWidget(bb)
bb.connect(bb, SIGNAL("rejected()"), d, SLOT("reject()")) bb.rejected.connect(d.reject)
d.setLayout(l) d.setLayout(l)
d.setWindowModality(Qt.WindowModal) d.setWindowModality(Qt.WindowModal)
d.resize(500, 400) d.resize(500, 400)
restoreGeom(d, "revlog") restoreGeom(d, "revlog")
d.exec_() d.show()
saveGeom(d, "revlog") saveGeom(d, "revlog")
def _cardInfoData(self): def _cardInfoData(self):
@ -1005,14 +989,13 @@ where id in %s""" % ids2str(sf))
self._openPreview() self._openPreview()
def _openPreview(self): def _openPreview(self):
c = self.connect
self._previewState = "question" self._previewState = "question"
self._previewWindow = QDialog(None, Qt.Window) self._previewWindow = QDialog(None, Qt.Window)
self._previewWindow.setWindowTitle(_("Preview")) self._previewWindow.setWindowTitle(_("Preview"))
c(self._previewWindow, SIGNAL("finished(int)"), self._onPreviewFinished) self._previewWindow.finished.connect(self._onPreviewFinished)
vbox = QVBoxLayout() vbox = QVBoxLayout()
vbox.setMargin(0) vbox.setContentsMargins(0,0,0,0)
self._previewWeb = AnkiWebView() self._previewWeb = AnkiWebView()
vbox.addWidget(self._previewWeb) vbox.addWidget(self._previewWeb)
bbox = QDialogButtonBox() bbox = QDialogButtonBox()
@ -1032,9 +1015,9 @@ where id in %s""" % ids2str(sf))
self._previewNext.setShortcut(QKeySequence("Right")) self._previewNext.setShortcut(QKeySequence("Right"))
self._previewNext.setToolTip(_("Shortcut key: Right arrow or Enter")) self._previewNext.setToolTip(_("Shortcut key: Right arrow or Enter"))
c(self._previewPrev, SIGNAL("clicked()"), self._onPreviewPrev) self._previewPrev.clicked.connect(self._onPreviewPrev)
c(self._previewNext, SIGNAL("clicked()"), self._onPreviewNext) self._previewNext.clicked.connect(self._onPreviewNext)
c(self._previewReplay, SIGNAL("clicked()"), self._onReplayAudio) self._previewReplay.clicked.connect(self._onReplayAudio)
vbox.addWidget(bbox) vbox.addWidget(bbox)
self._previewWindow.setLayout(vbox) self._previewWindow.setLayout(vbox)
@ -1349,8 +1332,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
frm.setupUi(d) frm.setupUi(d)
d.setWindowModality(Qt.WindowModal) d.setWindowModality(Qt.WindowModal)
frm.field.addItems([_("All Fields")] + fields) frm.field.addItems([_("All Fields")] + fields)
self.connect(frm.buttonBox, SIGNAL("helpRequested()"), frm.buttonBox.helpRequested.connect(self.onFindReplaceHelp)
self.onFindReplaceHelp)
restoreGeom(d, "findreplace") restoreGeom(d, "findreplace")
r = d.exec_() r = d.exec_()
saveGeom(d, "findreplace") saveGeom(d, "findreplace")
@ -1401,20 +1383,16 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
frm.fields.addItems(fields) frm.fields.addItems(fields)
self._dupesButton = None self._dupesButton = None
# links # links
frm.webView.page().setLinkDelegationPolicy( frm.webView.onAnkiLink = self.dupeLinkClicked
QWebPage.DelegateAllLinks)
self.connect(frm.webView,
SIGNAL("linkClicked(QUrl)"),
self.dupeLinkClicked)
def onFin(code): def onFin(code):
saveGeom(d, "findDupes") saveGeom(d, "findDupes")
self.connect(d, SIGNAL("finished(int)"), onFin) d.finished.connect(onFin)
def onClick(): def onClick():
field = fields[frm.fields.currentIndex()] field = fields[frm.fields.currentIndex()]
self.duplicatesReport(frm.webView, field, frm.search.text(), frm) self.duplicatesReport(frm.webView, field, frm.search.text(), frm)
search = frm.buttonBox.addButton( search = frm.buttonBox.addButton(
_("Search"), QDialogButtonBox.ActionRole) _("Search"), QDialogButtonBox.ActionRole)
self.connect(search, SIGNAL("clicked()"), onClick) search.clicked.connect(onClick)
d.show() d.show()
def duplicatesReport(self, web, fname, search, frm): 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: if not self._dupesButton:
self._dupesButton = b = frm.buttonBox.addButton( self._dupesButton = b = frm.buttonBox.addButton(
_("Tag Duplicates"), QDialogButtonBox.ActionRole) _("Tag Duplicates"), QDialogButtonBox.ActionRole)
self.connect(b, SIGNAL("clicked()"), lambda: self._onTagDupes(res)) b.clicked.connect(lambda: self._onTagDupes(res))
t = "<html><body>" t = "<html><body>"
groups = len(res) groups = len(res)
notes = sum(len(r[1]) for r in 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 += _("Found %(a)s across %(b)s.") % dict(a=part1, b=part2)
t += "<p><ol>" t += "<p><ol>"
for val, nids in res: 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), "nid:" + ",".join(str(id) for id in nids),
ngettext("%d note", "%d notes", len(nids)) % len(nids), ngettext("%d note", "%d notes", len(nids)) % len(nids),
cgi.escape(val)) cgi.escape(val))
@ -1456,7 +1434,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
tooltip(_("Notes tagged.")) tooltip(_("Notes tagged."))
def dupeLinkClicked(self, link): def dupeLinkClicked(self, link):
self.form.searchEdit.lineEdit().setText(link.toString()) self.form.searchEdit.lineEdit().setText(link)
self.onSearch() self.onSearch()
self.onNote() self.onNote()
@ -1552,11 +1530,11 @@ class ChangeModel(QDialog):
def setup(self): def setup(self):
# maps # maps
self.flayout = QHBoxLayout() self.flayout = QHBoxLayout()
self.flayout.setMargin(0) self.flayout.setContentsMargins(0,0,0,0)
self.fwidg = None self.fwidg = None
self.form.fieldMap.setLayout(self.flayout) self.form.fieldMap.setLayout(self.flayout)
self.tlayout = QHBoxLayout() self.tlayout = QHBoxLayout()
self.tlayout.setMargin(0) self.tlayout.setContentsMargins(0,0,0,0)
self.twidg = None self.twidg = None
self.form.templateMap.setLayout(self.tlayout) self.form.templateMap.setLayout(self.tlayout)
if self.style().objectName() == "gtk+": if self.style().objectName() == "gtk+":
@ -1572,8 +1550,7 @@ class ChangeModel(QDialog):
self.modelChooser = aqt.modelchooser.ModelChooser( self.modelChooser = aqt.modelchooser.ModelChooser(
self.browser.mw, self.form.modelChooserWidget, label=False) self.browser.mw, self.form.modelChooserWidget, label=False)
self.modelChooser.models.setFocus() self.modelChooser.models.setFocus()
self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), self.form.buttonBox.helpRequested.connect(self.onHelp)
self.onHelp)
self.modelChanged(self.browser.mw.col.models.current()) self.modelChanged(self.browser.mw.col.models.current())
self.pauseUpdate = False self.pauseUpdate = False
@ -1609,8 +1586,8 @@ class ChangeModel(QDialog):
idx = min(i, len(targets)-1) idx = min(i, len(targets)-1)
cb.setCurrentIndex(idx) cb.setCurrentIndex(idx)
indices[cb] = idx indices[cb] = idx
self.connect(cb, SIGNAL("currentIndexChanged(int)"), cb.currentIndexChanged.connect(
lambda i, cb=cb, key=key: self.onComboChanged(i, cb, key)) lambda i, cb=cb, key=key: self.onComboChanged(i, cb, key))
combos.append(cb) combos.append(cb)
l.addWidget(cb, i, 1) l.addWidget(cb, i, 1)
map.setLayout(l) map.setLayout(l)
@ -1716,11 +1693,11 @@ class BrowserToolbar(Toolbar):
def borderImg(link, icon, on, title, tooltip=None): def borderImg(link, icon, on, title, tooltip=None):
if on: if on:
fmt = '''\ 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>''' <img valign=bottom style='border: 1px solid #aaa;' src="qrc:/icons/%s.png"> %s</a>'''
else: else:
fmt = '''\ 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) return fmt % (tooltip or title, link, icon, title)
right = "<div>" right = "<div>"
right += borderImg("add", "add16", False, _("Add"), right += borderImg("add", "add16", False, _("Add"),
@ -1740,8 +1717,9 @@ class BrowserToolbar(Toolbar):
"Bulk Remove Tags (Ctrl+Alt+T)"))) "Bulk Remove Tags (Ctrl+Alt+T)")))
right += borderImg("delete", "delete16", False, _("Delete")) right += borderImg("delete", "delete16", False, _("Delete"))
right += "</div>" right += "</div>"
self.web.page().currentFrame().setScrollBarPolicy( # fixme
Qt.Horizontal, Qt.ScrollBarAlwaysOff) #self.web.page().currentFrame().setScrollBarPolicy(
# Qt.Horizontal, Qt.ScrollBarAlwaysOff)
self.web.stdHtml(self._body % ( self.web.stdHtml(self._body % (
"", #<span style='display:inline-block; width: 100px;'></span>", "", #<span style='display:inline-block; width: 100px;'></span>",
#self._centerLinks(), #self._centerLinks(),
@ -1798,8 +1776,8 @@ class FavouritesLineEdit(QLineEdit):
# name of current saved filter (if query matches) # name of current saved filter (if query matches)
self.name = None self.name = None
self.buttonClicked.connect(self.onClicked) self.buttonClicked.connect(self.onClicked)
self.connect(self, SIGNAL("textChanged(QString)"), self.updateButton) self.textChanged.connect(self.updateButton)
def resizeEvent(self, event): def resizeEvent(self, event):
buttonSize = self.button.sizeHint() buttonSize = self.button.sizeHint()
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)

View File

@ -60,7 +60,6 @@ class CardLayout(QDialog):
self.selectCard(idx) self.selectCard(idx)
def setupTabs(self): def setupTabs(self):
c = self.connect
cloze = self.model['type'] == MODEL_CLOZE cloze = self.model['type'] == MODEL_CLOZE
self.tabs = QTabWidget() self.tabs = QTabWidget()
self.tabs.setTabsClosable(not cloze) self.tabs.setTabsClosable(not cloze)
@ -69,10 +68,10 @@ class CardLayout(QDialog):
add = QPushButton("+") add = QPushButton("+")
add.setFixedWidth(30) add.setFixedWidth(30)
add.setToolTip(_("Add new card")) add.setToolTip(_("Add new card"))
c(add, SIGNAL("clicked()"), self.onAddCard) add.clicked.connect(self.onAddCard)
self.tabs.setCornerWidget(add) self.tabs.setCornerWidget(add)
c(self.tabs, SIGNAL("currentChanged(int)"), self.onCardSelected) self.tabs.currentChanged.connect(self.onCardSelected)
c(self.tabs, SIGNAL("tabCloseRequested(int)"), self.onRemoveTab) self.tabs.tabCloseRequested.connect(self.onRemoveTab)
def updateTabs(self): def updateTabs(self):
self.forms = [] self.forms = []
@ -81,10 +80,9 @@ class CardLayout(QDialog):
self.addTab(t) self.addTab(t)
def addTab(self, t): def addTab(self, t):
c = self.connect
w = QWidget() w = QWidget()
l = QHBoxLayout() l = QHBoxLayout()
l.setMargin(0) l.setContentsMargins(0,0,0,0)
l.setSpacing(3) l.setSpacing(3)
left = QWidget() left = QWidget()
# template area # template area
@ -102,9 +100,9 @@ class CardLayout(QDialog):
if len(self.cards) > 1: if len(self.cards) > 1:
tform.groupBox_3.setTitle(_( tform.groupBox_3.setTitle(_(
"Styling (shared between cards)")) "Styling (shared between cards)"))
c(tform.front, SIGNAL("textChanged()"), self.saveCard) tform.front.textChanged.connect(self.saveCard)
c(tform.css, SIGNAL("textChanged()"), self.saveCard) tform.css.textChanged.connect(self.saveCard)
c(tform.back, SIGNAL("textChanged()"), self.saveCard) tform.back.textChanged.connect(self.saveCard)
l.addWidget(left, 5) l.addWidget(left, 5)
# preview area # preview area
right = QWidget() right = QWidget()
@ -124,9 +122,6 @@ class CardLayout(QDialog):
pform.frontPrevBox.addWidget(pform.frontWeb) pform.frontPrevBox.addWidget(pform.frontWeb)
pform.backWeb = AnkiWebView() pform.backWeb = AnkiWebView()
pform.backPrevBox.addWidget(pform.backWeb) pform.backPrevBox.addWidget(pform.backWeb)
for wig in pform.frontWeb, pform.backWeb:
wig.page().setLinkDelegationPolicy(
QWebPage.DelegateExternalLinks)
l.addWidget(right, 5) l.addWidget(right, 5)
w.setLayout(l) w.setLayout(l)
self.forms.append({'tform': tform, 'pform': pform}) self.forms.append({'tform': tform, 'pform': pform})
@ -151,31 +146,30 @@ Please create a new card type first."""))
########################################################################## ##########################################################################
def setupButtons(self): def setupButtons(self):
c = self.connect
l = self.buttons = QHBoxLayout() l = self.buttons = QHBoxLayout()
help = QPushButton(_("Help")) help = QPushButton(_("Help"))
help.setAutoDefault(False) help.setAutoDefault(False)
l.addWidget(help) l.addWidget(help)
c(help, SIGNAL("clicked()"), self.onHelp) help.clicked.connect(self.onHelp)
l.addStretch() l.addStretch()
addField = QPushButton(_("Add Field")) addField = QPushButton(_("Add Field"))
addField.setAutoDefault(False) addField.setAutoDefault(False)
l.addWidget(addField) l.addWidget(addField)
c(addField, SIGNAL("clicked()"), self.onAddField) addField.clicked.connect(self.onAddField)
if self.model['type'] != MODEL_CLOZE: if self.model['type'] != MODEL_CLOZE:
flip = QPushButton(_("Flip")) flip = QPushButton(_("Flip"))
flip.setAutoDefault(False) flip.setAutoDefault(False)
l.addWidget(flip) l.addWidget(flip)
c(flip, SIGNAL("clicked()"), self.onFlip) flip.clicked.connect(self.onFlip)
more = QPushButton(_("More") + " "+downArrow()) more = QPushButton(_("More") + " "+downArrow())
more.setAutoDefault(False) more.setAutoDefault(False)
l.addWidget(more) l.addWidget(more)
c(more, SIGNAL("clicked()"), lambda: self.onMore(more)) more.clicked.connect(lambda: self.onMore(more))
l.addStretch() l.addStretch()
close = QPushButton(_("Close")) close = QPushButton(_("Close"))
close.setAutoDefault(False) close.setAutoDefault(False)
l.addWidget(close) l.addWidget(close)
c(close, SIGNAL("clicked()"), self.accept) close.clicked.connect(self.accept)
# Cards # Cards
########################################################################## ##########################################################################
@ -335,23 +329,19 @@ adjust the template manually to switch the question and answer."""))
def onMore(self, button): def onMore(self, button):
m = QMenu(self) m = QMenu(self)
a = m.addAction(_("Rename")) a = m.addAction(_("Rename"))
a.connect(a, SIGNAL("triggered()"), a.triggered.connect(self.onRename)
self.onRename)
if self.model['type'] != MODEL_CLOZE: if self.model['type'] != MODEL_CLOZE:
a = m.addAction(_("Reposition")) a = m.addAction(_("Reposition"))
a.connect(a, SIGNAL("triggered()"), a.triggered.connect(self.onReorder)
self.onReorder)
t = self.card.template() t = self.card.template()
if t['did']: if t['did']:
s = _(" (on)") s = _(" (on)")
else: else:
s = _(" (off)") s = _(" (off)")
a = m.addAction(_("Deck Override") + s) a = m.addAction(_("Deck Override") + s)
a.connect(a, SIGNAL("triggered()"), a.triggered.connect(self.onTargetDeck)
self.onTargetDeck)
a = m.addAction(_("Browser Appearance")) a = m.addAction(_("Browser Appearance"))
a.connect(a, SIGNAL("triggered()"), a.triggered.connect(self.onBrowserDisplay)
self.onBrowserDisplay)
m.exec_(button.mapToGlobal(QPoint(0,0))) m.exec_(button.mapToGlobal(QPoint(0,0)))
def onBrowserDisplay(self): def onBrowserDisplay(self):
@ -363,8 +353,7 @@ adjust the template manually to switch the question and answer."""))
f.afmt.setText(t.get('bafmt', "")) f.afmt.setText(t.get('bafmt', ""))
f.font.setCurrentFont(QFont(t.get('bfont', "Arial"))) f.font.setCurrentFont(QFont(t.get('bfont', "Arial")))
f.fontSize.setValue(t.get('bsize', 12)) f.fontSize.setValue(t.get('bsize', 12))
d.connect(f.buttonBox, SIGNAL("accepted()"), f.buttonBox.accepted.connect(lambda: self.onBrowserDisplayOk(f))
lambda: self.onBrowserDisplayOk(f))
d.exec_() d.exec_()
def onBrowserDisplayOk(self, f): 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.setText(self.col.decks.get(t['did'])['name'])
te.selectAll() te.selectAll()
bb = QDialogButtonBox(QDialogButtonBox.Close) bb = QDialogButtonBox(QDialogButtonBox.Close)
self.connect(bb, SIGNAL("rejected()"), d, SLOT("close()")) bb.rejected.connect(d.close)
l.addWidget(bb) l.addWidget(bb)
d.setLayout(l) d.setLayout(l)
d.exec_() d.exec_()

View File

@ -31,13 +31,13 @@ class CustomStudy(QDialog):
self.exec_() self.exec_()
def setupSignals(self): def setupSignals(self):
f = self.form; c = self.connect; s = SIGNAL("clicked()") f = self.form
c(f.radio1, s, lambda: self.onRadioChange(1)) f.radio1.clicked.connect(lambda: self.onRadioChange(1))
c(f.radio2, s, lambda: self.onRadioChange(2)) f.radio2.clicked.connect(lambda: self.onRadioChange(2))
c(f.radio3, s, lambda: self.onRadioChange(3)) f.radio3.clicked.connect(lambda: self.onRadioChange(3))
c(f.radio4, s, lambda: self.onRadioChange(4)) f.radio4.clicked.connect(lambda: self.onRadioChange(4))
c(f.radio5, s, lambda: self.onRadioChange(5)) f.radio5.clicked.connect(lambda: self.onRadioChange(5))
c(f.radio6, s, lambda: self.onRadioChange(6)) f.radio6.clicked.connect(lambda: self.onRadioChange(6))
def onRadioChange(self, idx): def onRadioChange(self, idx):
f = self.form; sp = f.spin f = self.form; sp = f.spin

View File

@ -21,8 +21,8 @@ class DeckBrowser(object):
def show(self): def show(self):
clearAudioQueue() clearAudioQueue()
self.web.setLinkHandler(self._linkHandler) self.web.resetHandlers()
self.web.setKeyHandler(None) self.web.onAnkiLink = self._linkHandler
self.mw.keyHandler = self._keyHandler self.mw.keyHandler = self._keyHandler
self._renderPage() self._renderPage()
@ -60,13 +60,13 @@ class DeckBrowser(object):
self._dragDeckOnto(draggedDeckDid, ontoDeckDid) self._dragDeckOnto(draggedDeckDid, ontoDeckDid)
elif cmd == "collapse": elif cmd == "collapse":
self._collapse(arg) self._collapse(arg)
return False
def _keyHandler(self, evt): def _keyHandler(self, evt):
# currently does nothing # currently does nothing
key = str(evt.text()) key = str(evt.text())
def _selDeck(self, did): def _selDeck(self, did):
self.scrollPos = self.web.page().mainFrame().scrollPosition()
self.mw.col.decks.select(did) self.mw.col.decks.select(did)
self.mw.onOverview() self.mw.onOverview()
@ -86,7 +86,8 @@ tr.drag-hover td { border-bottom: %(width)s solid #aaa; }
body { margin: 1em; -webkit-user-select: none; } body { margin: 1em; -webkit-user-select: none; }
.current { background-color: #e7e7e7; } .current { background-color: #e7e7e7; }
.decktd { min-width: 15em; } .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; .collapse { color: #000; text-decoration:none; display:inline-block;
width: 1em; } width: 1em; }
.filtered { color: #00a !important; } .filtered { color: #00a !important; }
@ -131,7 +132,7 @@ body { margin: 1em; -webkit-user-select: none; }
var draggedDeckId = ui.draggable.attr('id'); var draggedDeckId = ui.draggable.attr('id');
var ontoDeckId = $(this).attr('id'); var ontoDeckId = $(this).attr('id');
py.link("drag:" + draggedDeckId + "," + ontoDeckId); openAnkiLink("drag:" + draggedDeckId + "," + ontoDeckId);
} }
</script> </script>
""" """
@ -142,11 +143,9 @@ body { margin: 1em; -webkit-user-select: none; }
self._dueTree = self.mw.col.sched.deckDueTree() self._dueTree = self.mw.col.sched.deckDueTree()
tree = self._renderDeckTree(self._dueTree) tree = self._renderDeckTree(self._dueTree)
stats = self._renderStats() stats = self._renderStats()
op = self._oldPos()
self.web.stdHtml(self._body%dict( self.web.stdHtml(self._body%dict(
tree=tree, stats=stats, countwarn=self._countWarn()), css=css, tree=tree, stats=stats, countwarn=self._countWarn()), css=css,
js=anki.js.jquery+anki.js.ui, loadCB=lambda ok:\ js=anki.js.jquery+anki.js.ui)
self.web.page().mainFrame().setScrollPosition(op))
self.web.key = "deckBrowser" self.web.key = "deckBrowser"
self._drawButtons() self._drawButtons()
@ -173,8 +172,10 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
return "" return ""
return "<br><div style='width:50%;border: 1px solid #000;padding:5px;'>"+( 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( _("You have a lot of decks. Please see %(a)s. %(b)s") % dict(
a=("<a href=lots>%s</a>" % _("this page")), a=("<a href=# onclick='openAnkiLink('lots')>%s</a>" % _(
b=("<br><small><a href=hidelots>(%s)</a></small>" % (_("hide"))+ "this page")),
b=("<br><small><a href=# onclick='openAnkiLink(\"hidelots\")'>("
"%s)</a></small>" % (_("hide"))+
"</div"))) "</div")))
def _renderDeckTree(self, nodes, depth=0): def _renderDeckTree(self, nodes, depth=0):
@ -183,7 +184,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
if depth == 0: if depth == 0:
buf = """ buf = """
<tr><th colspan=5 align=left>%s</th><th class=count>%s</th> <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")) _("Deck"), _("Due"), _("New"))
buf += self._topLevelDragRow() buf += self._topLevelDragRow()
else: else:
@ -219,7 +220,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
buf = "<tr class='%s' id='%d'>" % (klass, did) buf = "<tr class='%s' id='%d'>" % (klass, did)
# deck link # deck link
if children: 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: else:
collapse = "<span class=collapse></span>" collapse = "<span class=collapse></span>"
if deck['dyn']: if deck['dyn']:
@ -228,7 +229,8 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
extraclass = "" extraclass = ""
buf += """ 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) indent(), collapse, extraclass, did, name)
# due counts # due counts
def nonzeroColour(cnt, colour): def nonzeroColour(cnt, colour):
@ -241,8 +243,8 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
nonzeroColour(due, "#007700"), nonzeroColour(due, "#007700"),
nonzeroColour(new, "#000099")) nonzeroColour(new, "#000099"))
# options # options
buf += "<td align=right class=opts>%s</td></tr>" % self.mw.button( buf += ("<td align=center class=opts><a onclick='openAnkiLink(\"opts:%d\");'>"
link="opts:%d"%did, name="<img valign=bottom src='qrc:/icons/gears.png'>"+downArrow()) "<img valign=right src='qrc:/icons/gears.png'></a></td></tr>" % did)
# children # children
buf += self._renderDeckTree(children, depth+1) buf += self._renderDeckTree(children, depth+1)
return buf return buf
@ -265,13 +267,13 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
def _showOptions(self, did): def _showOptions(self, did):
m = QMenu(self.mw) m = QMenu(self.mw)
a = m.addAction(_("Rename")) 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 = 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 = 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 = 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()) m.exec_(QCursor.pos())
def _export(self, did): def _export(self, did):
@ -345,14 +347,15 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
if b[0]: if b[0]:
b[0] = _("Shortcut key: %s") % shortcut(b[0]) b[0] = _("Shortcut key: %s") % shortcut(b[0])
buf += """ 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) self.bottom.draw(buf)
if isMac: if isMac:
size = 28 size = 28
else: else:
size = 36 + self.mw.fontHeightDelta*3 size = 36 + self.mw.fontHeightDelta*3
self.bottom.web.setFixedHeight(size) self.bottom.web.setFixedHeight(size)
self.bottom.web.setLinkHandler(self._linkHandler) self.bottom.web.resetHandlers()
self.bottom.web.onAnkiLink = self._linkHandler
def _onShared(self): def _onShared(self):
openLink(aqt.appShared+"decks/") openLink(aqt.appShared+"decks/")

View File

@ -14,7 +14,7 @@ class DeckChooser(QHBoxLayout):
self.mw = mw self.mw = mw
self.deck = mw.col self.deck = mw.col
self.label = label self.label = label
self.setMargin(0) self.setContentsMargins(0,0,0,0)
self.setSpacing(8) self.setSpacing(8)
self.setupDecks() self.setupDecks()
self.widget.setLayout(self) self.widget.setLayout(self)
@ -25,12 +25,10 @@ class DeckChooser(QHBoxLayout):
self.deckLabel = QLabel(_("Deck")) self.deckLabel = QLabel(_("Deck"))
self.addWidget(self.deckLabel) self.addWidget(self.deckLabel)
# decks box # decks box
self.deck = QPushButton() self.deck = QPushButton(clicked=self.onDeckChange)
self.deck.setToolTip(shortcut(_("Target Deck (Ctrl+D)"))) self.deck.setToolTip(shortcut(_("Target Deck (Ctrl+D)")))
s = QShortcut(QKeySequence(_("Ctrl+D")), self.widget) s = QShortcut(QKeySequence(_("Ctrl+D")), self.widget, activated=self.onDeckChange)
s.connect(s, SIGNAL("activated()"), self.onDeckChange)
self.addWidget(self.deck) self.addWidget(self.deck)
self.connect(self.deck, SIGNAL("clicked()"), self.onDeckChange)
# starting label # starting label
if self.mw.col.conf.get("addToCur", True): if self.mw.col.conf.get("addToCur", True):
col = self.mw.col col = self.mw.col

View File

@ -24,14 +24,10 @@ class DeckConf(QDialog):
self.setupCombos() self.setupCombos()
self.setupConfs() self.setupConfs()
self.setWindowModality(Qt.WindowModal) self.setWindowModality(Qt.WindowModal)
self.connect(self.form.buttonBox, self.form.buttonBox.helpRequested.connect(lambda: openHelp("deckoptions"))
SIGNAL("helpRequested()"), self.form.confOpts.clicked.connect(self.confOpts)
lambda: openHelp("deckoptions"))
self.connect(self.form.confOpts, SIGNAL("clicked()"), self.confOpts)
self.form.confOpts.setText(downArrow()) self.form.confOpts.setText(downArrow())
self.connect(self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults), self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked.connect(self.onRestore)
SIGNAL("clicked()"),
self.onRestore)
self.setWindowTitle(_("Options for %s") % self.deck['name']) self.setWindowTitle(_("Options for %s") % self.deck['name'])
# qt doesn't size properly with altered fonts otherwise # qt doesn't size properly with altered fonts otherwise
restoreGeom(self, "deckconf", adjustSize=True) restoreGeom(self, "deckconf", adjustSize=True)
@ -43,15 +39,13 @@ class DeckConf(QDialog):
import anki.consts as cs import anki.consts as cs
f = self.form f = self.form
f.newOrder.addItems(list(cs.newCardOrderLabels().values())) f.newOrder.addItems(list(cs.newCardOrderLabels().values()))
self.connect(f.newOrder, SIGNAL("currentIndexChanged(int)"), f.newOrder.currentIndexChanged.connect(self.onNewOrderChanged)
self.onNewOrderChanged)
# Conf list # Conf list
###################################################################### ######################################################################
def setupConfs(self): def setupConfs(self):
self.connect(self.form.dconf, SIGNAL("currentIndexChanged(int)"), self.form.dconf.currentIndexChanged.connect(self.onConfChange)
self.onConfChange)
self.conf = None self.conf = None
self.loadConfs() self.loadConfs()
@ -75,13 +69,13 @@ class DeckConf(QDialog):
def confOpts(self): def confOpts(self):
m = QMenu(self.mw) m = QMenu(self.mw)
a = m.addAction(_("Add")) a = m.addAction(_("Add"))
a.connect(a, SIGNAL("triggered()"), self.addGroup) a.triggered.connect(self.addGroup)
a = m.addAction(_("Delete")) a = m.addAction(_("Delete"))
a.connect(a, SIGNAL("triggered()"), self.remGroup) a.triggered.connect(self.remGroup)
a = m.addAction(_("Rename")) a = m.addAction(_("Rename"))
a.connect(a, SIGNAL("triggered()"), self.renameGroup) a.triggered.connect(self.renameGroup)
a = m.addAction(_("Set for all subdecks")) a = m.addAction(_("Set for all subdecks"))
a.connect(a, SIGNAL("triggered()"), self.setChildren) a.triggered.connect(self.setChildren)
if not self.childDids: if not self.childDids:
a.setEnabled(False) a.setEnabled(False)
m.exec_(QCursor.pos()) m.exec_(QCursor.pos())

View File

@ -28,7 +28,7 @@ def download(mw, code):
# unsure why this is happening, but guard against throwing the # unsure why this is happening, but guard against throwing the
# error # error
pass pass
mw.connect(thread, SIGNAL("recv"), onRecv) thread.recv.connect(onRecv)
thread.start() thread.start()
mw.progress.start(immediate=True) mw.progress.start(immediate=True)
while not thread.isFinished(): while not thread.isFinished():
@ -43,6 +43,8 @@ def download(mw, code):
class Downloader(QThread): class Downloader(QThread):
recv = pyqtSignal()
def __init__(self, code): def __init__(self, code):
QThread.__init__(self) QThread.__init__(self)
self.code = code self.code = code
@ -59,7 +61,7 @@ class Downloader(QThread):
def recvEvent(bytes): def recvEvent(bytes):
self.recvTotal += bytes self.recvTotal += bytes
if canPost(): if canPost():
self.emit(SIGNAL("recv")) self.recv.emit()
addHook("httpRecv", recvEvent) addHook("httpRecv", recvEvent)
con = httpCon() con = httpCon()
try: try:

View File

@ -22,9 +22,7 @@ class DeckConf(QDialog):
label, QDialogButtonBox.AcceptRole) label, QDialogButtonBox.AcceptRole)
self.mw.checkpoint(_("Options")) self.mw.checkpoint(_("Options"))
self.setWindowModality(Qt.WindowModal) self.setWindowModality(Qt.WindowModal)
self.connect(self.form.buttonBox, self.form.buttonBox.helpRequested.connect(lambda: openHelp("filtered"))
SIGNAL("helpRequested()"),
lambda: openHelp("filtered"))
self.setWindowTitle(_("Options for %s") % self.deck['name']) self.setWindowTitle(_("Options for %s") % self.deck['name'])
restoreGeom(self, "dyndeckconf") restoreGeom(self, "dyndeckconf")
self.setupOrder() self.setupOrder()

View File

@ -23,9 +23,7 @@ class EditCurrent(QDialog):
self.setWindowTitle(_("Edit Current")) self.setWindowTitle(_("Edit Current"))
self.setMinimumHeight(400) self.setMinimumHeight(400)
self.setMinimumWidth(500) self.setMinimumWidth(500)
self.connect(self, self.rejected.connect(self.onSave)
SIGNAL("rejected()"),
self.onSave)
self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut( self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
QKeySequence("Ctrl+Return")) QKeySequence("Ctrl+Return"))
self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self) self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)

View File

@ -1,22 +1,30 @@
# Copyright: Damien Elmes <anki@ichi2.net> # Copyright: Damien Elmes <anki@ichi2.net>
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import sys import sys, traceback
import cgi import cgi
from anki.lang import _ from anki.lang import _
from aqt.qt import * from aqt.qt import *
from aqt.utils import showText, showWarning 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): class ErrorHandler(QObject):
"Catch stderr and write into buffer." "Catch stderr and write into buffer."
ivl = 100 ivl = 100
errorTimer = pyqtSignal()
def __init__(self, mw): def __init__(self, mw):
QObject.__init__(self, mw) QObject.__init__(self, mw)
self.mw = mw self.mw = mw
self.timer = None self.timer = None
self.connect(self, SIGNAL("errorTimer"), self._setTimer) self.errorTimer.connect(self._setTimer)
self.pool = "" self.pool = ""
sys.stderr = self sys.stderr = self
@ -31,12 +39,12 @@ class ErrorHandler(QObject):
def setTimer(self): def setTimer(self):
# we can't create a timer from a different thread, so we post a # we can't create a timer from a different thread, so we post a
# message to the object on the main thread # message to the object on the main thread
self.emit(SIGNAL("errorTimer")) self.errorTimer.emit()
def _setTimer(self): def _setTimer(self):
if not self.timer: if not self.timer:
self.timer = QTimer(self.mw) 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.setInterval(self.ivl)
self.timer.setSingleShot(True) self.timer.setSingleShot(True)
self.timer.start() self.timer.start()
@ -57,7 +65,7 @@ Anki manual for more information.""")
return showWarning(_("Please install PyAudio")) return showWarning(_("Please install PyAudio"))
if "install mplayer" in error: if "install mplayer" in error:
return showWarning(_("Please install mplayer")) 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 " return showWarning(_("Please connect a microphone, and ensure "
"other programs are not using the audio device.")) "other programs are not using the audio device."))
if "invalidTempFolder" in error: if "invalidTempFolder" in error:

View File

@ -26,9 +26,8 @@ class ExportDialog(QDialog):
self.exec_() self.exec_()
def setup(self, did): def setup(self, did):
self.frm.format.insertItems(0, list(zip(*exporters())[0])) self.frm.format.insertItems(0, list(zip(*exporters()))[0])
self.connect(self.frm.format, SIGNAL("activated(int)"), self.frm.format.activated.connect(self.exporterChanged)
self.exporterChanged)
self.exporterChanged(0) self.exporterChanged(0)
self.decks = [_("All Decks")] + sorted(self.col.decks.allNames()) self.decks = [_("All Decks")] + sorted(self.col.decks.allNames())
self.frm.deck.addItems(self.decks) self.frm.deck.addItems(self.decks)

View File

@ -38,16 +38,14 @@ class FieldDialog(QDialog):
self.form.fieldList.addItem(f['name']) self.form.fieldList.addItem(f['name'])
def setupSignals(self): def setupSignals(self):
c = self.connect
s = SIGNAL
f = self.form f = self.form
c(f.fieldList, s("currentRowChanged(int)"), self.onRowChange) f.fieldList.currentRowChanged.connect(self.onRowChange)
c(f.fieldAdd, s("clicked()"), self.onAdd) f.fieldAdd.clicked.connect(self.onAdd)
c(f.fieldDelete, s("clicked()"), self.onDelete) f.fieldDelete.clicked.connect(self.onDelete)
c(f.fieldRename, s("clicked()"), self.onRename) f.fieldRename.clicked.connect(self.onRename)
c(f.fieldPosition, s("clicked()"), self.onPosition) f.fieldPosition.clicked.connect(self.onPosition)
c(f.sortField, s("clicked()"), self.onSortField) f.sortField.clicked.connect(self.onSortField)
c(f.buttonBox, s("helpRequested()"), self.onHelp) f.buttonBox.helpRequested.connect(self.onHelp)
def onRowChange(self, idx): def onRowChange(self, idx):
if idx == -1: if idx == -1:

View File

@ -69,15 +69,14 @@ class ImportDialog(QDialog):
self.importer = importer self.importer = importer
self.frm = aqt.forms.importing.Ui_ImportDialog() self.frm = aqt.forms.importing.Ui_ImportDialog()
self.frm.setupUi(self) self.frm.setupUi(self)
self.connect(self.frm.buttonBox.button(QDialogButtonBox.Help), self.frm.buttonBox.button(QDialogButtonBox.Help).clicked.connect(
SIGNAL("clicked()"), self.helpRequested) self.helpRequested)
self.setupMappingFrame() self.setupMappingFrame()
self.setupOptions() self.setupOptions()
self.modelChanged() self.modelChanged()
self.frm.autoDetect.setVisible(self.importer.needDelimiter) self.frm.autoDetect.setVisible(self.importer.needDelimiter)
addHook("currentModelChanged", self.modelChanged) addHook("currentModelChanged", self.modelChanged)
self.connect(self.frm.autoDetect, SIGNAL("clicked()"), self.frm.autoDetect.clicked.connect(self.onDelimiter)
self.onDelimiter)
self.updateDelimiterButtonText() self.updateDelimiterButtonText()
self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True)) self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True))
self.frm.importMode.setCurrentIndex(self.mw.pm.profile.get('importMode', 1)) 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.mapbox.addWidget(self.mapwidget)
self.grid = QGridLayout(self.mapwidget) self.grid = QGridLayout(self.mapwidget)
self.mapwidget.setLayout(self.grid) self.mapwidget.setLayout(self.grid)
self.grid.setMargin(3) self.grid.setContentsMargins(3,3,3,3)
self.grid.setSpacing(6) self.grid.setSpacing(6)
fields = self.importer.fields() fields = self.importer.fields()
for num in range(len(self.mapping)): 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) self.grid.addWidget(QLabel(text), num, 1)
button = QPushButton(_("Change")) button = QPushButton(_("Change"))
self.grid.addWidget(button, num, 2) self.grid.addWidget(button, num, 2)
self.connect(button, SIGNAL("clicked()"), button.clicked.connect(lambda s=self,n=num: s.changeMappingNum(n))
lambda s=self,n=num: s.changeMappingNum(n))
def changeMappingNum(self, n): def changeMappingNum(self, n):
f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField() f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField()

View File

@ -59,8 +59,6 @@ class AnkiQt(QMainWindow):
self.onAppMsg(args[0]) self.onAppMsg(args[0])
# Load profile in a timer so we can let the window finish init and not # Load profile in a timer so we can let the window finish init and not
# close on profile load error. # close on profile load error.
if isMac and qtmajor >= 5:
self.show()
self.progress.timer(10, self.setupProfile, False) self.progress.timer(10, self.setupProfile, False)
def setupUI(self): def setupUI(self):
@ -110,16 +108,14 @@ class AnkiQt(QMainWindow):
d = self.profileDiag = QDialog() d = self.profileDiag = QDialog()
f = self.profileForm = aqt.forms.profiles.Ui_Dialog() f = self.profileForm = aqt.forms.profiles.Ui_Dialog()
f.setupUi(d) f.setupUi(d)
d.connect(f.login, SIGNAL("clicked()"), self.onOpenProfile) f.login.clicked.connect(self.onOpenProfile)
d.connect(f.profiles, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), f.profiles.itemDoubleClicked.connect(self.onOpenProfile)
self.onOpenProfile) f.quit.clicked.connect(lambda: sys.exit(0))
d.connect(f.quit, SIGNAL("clicked()"), lambda: sys.exit(0)) f.add.clicked.connect(self.onAddProfile)
d.connect(f.add, SIGNAL("clicked()"), self.onAddProfile) f.rename.clicked.connect(self.onRenameProfile)
d.connect(f.rename, SIGNAL("clicked()"), self.onRenameProfile) f.delete_2.clicked.connect(self.onRemProfile)
d.connect(f.delete_2, SIGNAL("clicked()"), self.onRemProfile) d.rejected.connect(d.close)
d.connect(d, SIGNAL("rejected()"), lambda: d.close()) f.profiles.currentRowChanged.connect(self.onProfileRowChange)
d.connect(f.profiles, SIGNAL("currentRowChanged(int)"),
self.onProfileRowChange)
self.refreshProfilesList() self.refreshProfilesList()
# raise first, for osx testing # raise first, for osx testing
d.show() d.show()
@ -459,7 +455,8 @@ the manual for information on how to restore from an automatic backup."))
if self.resetModal: if self.resetModal:
# we don't have to change the webview, as we have a covering window # we don't have to change the webview, as we have a covering window
return return
self.web.setLinkHandler(lambda url: self.delayedMaybeReset()) self.web.resetHandlers()
self.web.onAnkiLink = lambda url: self.delayedMaybeReset()
i = _("Waiting for editing to finish.") i = _("Waiting for editing to finish.")
b = self.button("refresh", _("Resume Now"), id="resume") b = self.button("refresh", _("Resume Now"), id="resume")
self.web.stdHtml(""" self.web.stdHtml("""
@ -483,16 +480,16 @@ margin: 2em;
h1 { margin-bottom: 0.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_ class_ = "but "+ class_
if key: if key:
key = _("Shortcut key: %s") % key key = _("Shortcut key: %s") % key
else: else:
key = "" key = ""
return ''' return '''
<button id="%s" class="%s" onclick="py.link('%s');return false;" <button id="%s" class="%s" onclick="openAnkiLink('%s');return false;"
title="%s">%s</button>''' % ( title="%s" %s>%s</button>''' % (
id, class_, link, key, name) id, class_, link, key, extra, name)
# Main window setup # Main window setup
########################################################################## ##########################################################################
@ -615,8 +612,7 @@ title="%s">%s</button>''' % (
self.keyHandler = None self.keyHandler = None
# debug shortcut # debug shortcut
self.debugShortcut = QShortcut(QKeySequence("Ctrl+:"), self) self.debugShortcut = QShortcut(QKeySequence("Ctrl+:"), self)
self.connect( self.debugShortcut.activated.connect(self.onDebug)
self.debugShortcut, SIGNAL("activated()"), self.onDebug)
def keyPressEvent(self, evt): def keyPressEvent(self, evt):
# do we have a delegate? # do we have a delegate?
@ -791,23 +787,21 @@ title="%s">%s</button>''' % (
def setupMenus(self): def setupMenus(self):
m = self.form m = self.form
s = SIGNAL("triggered()") m.actionSwitchProfile.triggered.connect(lambda b: self.unloadProfile())
#self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin) m.actionImport.triggered.connect(self.onImport)
self.connect(m.actionSwitchProfile, s, self.unloadProfile) m.actionExport.triggered.connect(self.onExport)
self.connect(m.actionImport, s, self.onImport) m.actionExit.triggered.connect(self.close)
self.connect(m.actionExport, s, self.onExport) m.actionPreferences.triggered.connect(self.onPrefs)
self.connect(m.actionExit, s, self, SLOT("close()")) m.actionAbout.triggered.connect(self.onAbout)
self.connect(m.actionPreferences, s, self.onPrefs) m.actionUndo.triggered.connect(self.onUndo)
self.connect(m.actionAbout, s, self.onAbout) m.actionFullDatabaseCheck.triggered.connect(self.onCheckDB)
self.connect(m.actionUndo, s, self.onUndo) m.actionCheckMediaDatabase.triggered.connect(self.onCheckMediaDB)
self.connect(m.actionFullDatabaseCheck, s, self.onCheckDB) m.actionDocumentation.triggered.connect(self.onDocumentation)
self.connect(m.actionCheckMediaDatabase, s, self.onCheckMediaDB) m.actionDonate.triggered.connect(self.onDonate)
self.connect(m.actionDocumentation, s, self.onDocumentation) m.actionStudyDeck.triggered.connect(self.onStudyDeck)
self.connect(m.actionDonate, s, self.onDonate) m.actionCreateFiltered.triggered.connect(self.onCram)
self.connect(m.actionStudyDeck, s, self.onStudyDeck) m.actionEmptyCards.triggered.connect(self.onEmptyCards)
self.connect(m.actionCreateFiltered, s, self.onCram) m.actionNoteTypes.triggered.connect(self.onNoteTypes)
self.connect(m.actionEmptyCards, s, self.onEmptyCards)
self.connect(m.actionNoteTypes, s, self.onNoteTypes)
def updateTitleBar(self): def updateTitleBar(self):
self.setWindowTitle("Anki") self.setWindowTitle("Anki")
@ -818,9 +812,9 @@ title="%s">%s</button>''' % (
def setupAutoUpdate(self): def setupAutoUpdate(self):
import aqt.update import aqt.update
self.autoUpdate = aqt.update.LatestVersionFinder(self) self.autoUpdate = aqt.update.LatestVersionFinder(self)
self.connect(self.autoUpdate, SIGNAL("newVerAvail"), self.newVerAvail) self.autoUpdate.newVerAvail.connect(self.newVerAvail)
self.connect(self.autoUpdate, SIGNAL("newMsg"), self.newMsg) self.autoUpdate.newMsg.connect(self.newMsg)
self.connect(self.autoUpdate, SIGNAL("clockIsOff"), self.clockIsOff) self.autoUpdate.clockIsOff.connect(self.clockIsOff)
self.autoUpdate.start() self.autoUpdate.start()
def newVerAvail(self, ver): 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" % "select id, mid, flds from notes where id in %s" %
ids2str(nids)): ids2str(nids)):
fields = splitFields(flds) 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") f.write("\n")
# Schema modifications # Schema modifications
@ -955,9 +949,9 @@ will be lost. Continue?"""))
b = QPushButton(_("Delete Unused")) b = QPushButton(_("Delete Unused"))
b.setAutoDefault(False) b.setAutoDefault(False)
box.addButton(b, QDialogButtonBox.ActionRole) box.addButton(b, QDialogButtonBox.ActionRole)
b.connect( b.clicked.connect(
b, SIGNAL("clicked()"), lambda u=unused, d=diag: self.deleteUnused(u, d)) lambda c, u=unused, d=diag: self.deleteUnused(u, d))
diag.connect(box, SIGNAL("rejected()"), diag, SLOT("reject()")) box.rejected.connect(diag.reject)
diag.setMinimumHeight(400) diag.setMinimumHeight(400)
diag.setMinimumWidth(500) diag.setMinimumWidth(500)
restoreGeom(diag, "checkmediadb") restoreGeom(diag, "checkmediadb")
@ -1006,7 +1000,7 @@ will be lost. Continue?"""))
self.col.remCards(cids) self.col.remCards(cids)
tooltip(ngettext("%d card deleted.", "%d cards deleted.", len(cids)) % len(cids)) tooltip(ngettext("%d card deleted.", "%d cards deleted.", len(cids)) % len(cids))
self.reset() self.reset()
diag.connect(box, SIGNAL("accepted()"), onDelete) box.accepted.connect(onDelete)
diag.show() diag.show()
# Debugging # Debugging
@ -1017,12 +1011,10 @@ will be lost. Continue?"""))
frm = aqt.forms.debug.Ui_Dialog() frm = aqt.forms.debug.Ui_Dialog()
frm.setupUi(d) frm.setupUi(d)
s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+return"), d) s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+return"), d)
self.connect(s, SIGNAL("activated()"), s.activated.connect(lambda: self.onDebugRet(frm))
lambda: self.onDebugRet(frm))
s = self.debugDiagShort = QShortcut( s = self.debugDiagShort = QShortcut(
QKeySequence("ctrl+shift+return"), d) QKeySequence("ctrl+shift+return"), d)
self.connect(s, SIGNAL("activated()"), s.activated.connect(lambda: self.onDebugPrint(frm))
lambda: self.onDebugPrint(frm))
d.show() d.show()
def _captureOutput(self, on): def _captureOutput(self, on):
@ -1080,6 +1072,12 @@ will be lost. Continue?"""))
########################################################################## ##########################################################################
def setupFonts(self): def setupFonts(self):
print("fixme: setupFonts()")
self.fontHeight = 12
self.fontFamily = "arial"
self.fontHeightDelta = 0
return
f = QFontInfo(self.font()) f = QFontInfo(self.font())
ws = QWebSettings.globalSettings() ws = QWebSettings.globalSettings()
self.fontHeight = f.pixelSize() self.fontHeight = f.pixelSize()
@ -1093,8 +1091,7 @@ will be lost. Continue?"""))
if isMac: if isMac:
# mac users expect a minimize option # mac users expect a minimize option
self.minimizeShortcut = QShortcut("Ctrl+M", self) self.minimizeShortcut = QShortcut("Ctrl+M", self)
self.connect(self.minimizeShortcut, SIGNAL("activated()"), self.minimizeShortcut.activated.connect(self.onMacMinimize)
self.onMacMinimize)
self.hideMenuAccels = True self.hideMenuAccels = True
self.maybeHideAccelerators() self.maybeHideAccelerators()
self.hideStatusTips() self.hideStatusTips()
@ -1125,7 +1122,7 @@ will be lost. Continue?"""))
########################################################################## ##########################################################################
def setupAppMsg(self): def setupAppMsg(self):
self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg) self.app.appMsg.connect(self.onAppMsg)
def onAppMsg(self, buf): def onAppMsg(self, buf):
if self.state == "startup": if self.state == "startup":

View File

@ -15,7 +15,7 @@ class ModelChooser(QHBoxLayout):
self.mw = mw self.mw = mw
self.deck = mw.col self.deck = mw.col
self.label = label self.label = label
self.setMargin(0) self.setContentsMargins(0,0,0,0)
self.setSpacing(8) self.setSpacing(8)
self.setupModels() self.setupModels()
addHook('reset', self.onReset) addHook('reset', self.onReset)
@ -29,11 +29,10 @@ class ModelChooser(QHBoxLayout):
self.models = QPushButton() self.models = QPushButton()
#self.models.setStyleSheet("* { text-align: left; }") #self.models.setStyleSheet("* { text-align: left; }")
self.models.setToolTip(shortcut(_("Change Note Type (Ctrl+N)"))) self.models.setToolTip(shortcut(_("Change Note Type (Ctrl+N)")))
s = QShortcut(QKeySequence(_("Ctrl+N")), self.widget) s = QShortcut(QKeySequence(_("Ctrl+N")), self.widget, activated=self.onModelChange)
s.connect(s, SIGNAL("activated()"), self.onModelChange)
self.models.setAutoDefault(False) self.models.setAutoDefault(False)
self.addWidget(self.models) self.addWidget(self.models)
self.connect(self.models, SIGNAL("clicked()"), self.onModelChange) self.models.clicked.connect(self.onModelChange)
# layout # layout
sizePolicy = QSizePolicy( sizePolicy = QSizePolicy(
QSizePolicy.Policy(7), QSizePolicy.Policy(7),
@ -61,8 +60,7 @@ class ModelChooser(QHBoxLayout):
from aqt.studydeck import StudyDeck from aqt.studydeck import StudyDeck
current = self.deck.models.current()['name'] current = self.deck.models.current()['name']
# edit button # edit button
edit = QPushButton(_("Manage")) edit = QPushButton(_("Manage"), clicked=self.onEdit)
self.connect(edit, SIGNAL("clicked()"), self.onEdit)
def nameFunc(): def nameFunc():
return sorted(self.deck.models.allNames()) return sorted(self.deck.models.allNames())
ret = StudyDeck( ret = StudyDeck(

View File

@ -20,8 +20,7 @@ class Models(QDialog):
self.mw.checkpoint(_("Note Types")) self.mw.checkpoint(_("Note Types"))
self.form = aqt.forms.models.Ui_Dialog() self.form = aqt.forms.models.Ui_Dialog()
self.form.setupUi(self) self.form.setupUi(self)
self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), self.form.buttonBox.helpRequested.connect(lambda: openHelp("notetypes"))
lambda: openHelp("notetypes"))
self.setupModels() self.setupModels()
restoreGeom(self, "models") restoreGeom(self, "models")
self.exec_() self.exec_()
@ -31,25 +30,23 @@ class Models(QDialog):
def setupModels(self): def setupModels(self):
self.model = None self.model = None
c = self.connect; f = self.form; box = f.buttonBox f = self.form; box = f.buttonBox
s = SIGNAL("clicked()")
t = QDialogButtonBox.ActionRole t = QDialogButtonBox.ActionRole
b = box.addButton(_("Add"), t) b = box.addButton(_("Add"), t)
c(b, s, self.onAdd) b.clicked.connect(self.onAdd)
b = box.addButton(_("Rename"), t) b = box.addButton(_("Rename"), t)
c(b, s, self.onRename) b.clicked.connect(self.onRename)
b = box.addButton(_("Delete"), t) b = box.addButton(_("Delete"), t)
c(b, s, self.onDelete) b.clicked.connect(self.onDelete)
if self.fromMain: if self.fromMain:
b = box.addButton(_("Fields..."), t) b = box.addButton(_("Fields..."), t)
c(b, s, self.onFields) b.clicked.connect(self.onFields)
b = box.addButton(_("Cards..."), t) b = box.addButton(_("Cards..."), t)
c(b, s, self.onCards) b.clicked.connect(self.onCards)
b = box.addButton(_("Options..."), t) b = box.addButton(_("Options..."), t)
c(b, s, self.onAdvanced) b.clicked.connect(self.onAdvanced)
c(f.modelsList, SIGNAL("currentRowChanged(int)"), self.modelChanged) f.modelsList.currentRowChanged.connect(self.modelChanged)
c(f.modelsList, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), f.modelsList.itemDoubleClicked.connect(self.onRename)
self.onRename)
self.updateModelsList() self.updateModelsList()
f.modelsList.setCurrentRow(0) f.modelsList.setCurrentRow(0)
maybeHideClose(box) maybeHideClose(box)
@ -113,9 +110,7 @@ class Models(QDialog):
frm.latexHeader.setText(self.model['latexPre']) frm.latexHeader.setText(self.model['latexPre'])
frm.latexFooter.setText(self.model['latexPost']) frm.latexFooter.setText(self.model['latexPost'])
d.setWindowTitle(_("Options for %s") % self.model['name']) d.setWindowTitle(_("Options for %s") % self.model['name'])
self.connect( frm.buttonBox.helpRequested.connect(lambda: openHelp("latex"))
frm.buttonBox, SIGNAL("helpRequested()"),
lambda: openHelp("latex"))
restoreGeom(d, "modelopts") restoreGeom(d, "modelopts")
d.exec_() d.exec_()
saveGeom(d, "modelopts") saveGeom(d, "modelopts")
@ -185,9 +180,9 @@ class AddModel(QDialog):
self.dialog.models.setCurrentRow(0) self.dialog.models.setCurrentRow(0)
# the list widget will swallow the enter key # the list widget will swallow the enter key
s = QShortcut(QKeySequence("Return"), self) s = QShortcut(QKeySequence("Return"), self)
self.connect(s, SIGNAL("activated()"), self.accept) s.activated.connect(self.accept)
# help # help
self.connect(self.dialog.buttonBox, SIGNAL("helpRequested()"), self.onHelp) self.dialog.buttonBox.helpRequested.connect(self.onHelp)
def get(self): def get(self):
self.exec_() self.exec_()

View File

@ -17,16 +17,16 @@ class Overview(object):
def show(self): def show(self):
clearAudioQueue() clearAudioQueue()
self.web.setLinkHandler(self._linkHandler) self.web.resetHandlers()
self.web.setKeyHandler(None) self.web.onAnkiLink = self._linkHandler
self.mw.keyHandler = self._keyHandler self.mw.keyHandler = self._keyHandler
self.mw.web.setFocus()
self.refresh() self.refresh()
def refresh(self): def refresh(self):
self.mw.col.reset() self.mw.col.reset()
self._renderPage() self._renderPage()
self._renderBottom() self._renderBottom()
self.mw.web.setFocus()
# Handlers # Handlers
############################################################ ############################################################
@ -61,6 +61,7 @@ class Overview(object):
self.mw.reset() self.mw.reset()
elif url.lower().startswith("http"): elif url.lower().startswith("http"):
openLink(url) openLink(url)
return False
def _keyHandler(self, evt): def _keyHandler(self, evt):
cram = self.mw.col.decks.current()['dyn'] cram = self.mw.col.decks.current()['dyn']
@ -143,7 +144,7 @@ to their original deck.""")
_("New"), counts[0], _("New"), counts[0],
_("Learning"), counts[1], _("Learning"), counts[1],
_("To Review"), counts[2], _("To Review"), counts[2],
but("study", _("Study Now"), id="study")) but("study", _("Study Now"), id="study",extra=" autofocus"))
_body = """ _body = """
@ -198,14 +199,14 @@ text-align: center;
if b[0]: if b[0]:
b[0] = _("Shortcut key: %s") % shortcut(b[0]) b[0] = _("Shortcut key: %s") % shortcut(b[0])
buf += """ 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) self.bottom.draw(buf)
if isMac: if isMac:
size = 28 size = 28
else: else:
size = 36 + self.mw.fontHeightDelta*3 size = 36 + self.mw.fontHeightDelta*3
self.bottom.web.setFixedHeight(size) self.bottom.web.setFixedHeight(size)
self.bottom.web.setLinkHandler(self._linkHandler) self.bottom.web.onAnkiLink = self._linkHandler
# Studying more # Studying more
###################################################################### ######################################################################

View File

@ -21,8 +21,7 @@ class Preferences(QDialog):
self.form.setupUi(self) self.form.setupUi(self)
self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False) self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False)
self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False) self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False)
self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), self.form.buttonBox.helpRequested.connect(lambda: openHelp("profileprefs"))
lambda: openHelp("profileprefs"))
self.setupLang() self.setupLang()
self.setupCollection() self.setupCollection()
self.setupNetwork() self.setupNetwork()
@ -52,8 +51,7 @@ class Preferences(QDialog):
f = self.form f = self.form
f.lang.addItems([x[0] for x in anki.lang.langs]) f.lang.addItems([x[0] for x in anki.lang.langs])
f.lang.setCurrentIndex(self.langIdx()) f.lang.setCurrentIndex(self.langIdx())
self.connect(f.lang, SIGNAL("currentIndexChanged(int)"), f.lang.currentIndexChanged.connect(self.onLangIdxChanged)
self.onLangIdxChanged)
def langIdx(self): def langIdx(self):
codes = [x[1] for x in anki.lang.langs] codes = [x[1] for x in anki.lang.langs]
@ -113,8 +111,7 @@ class Preferences(QDialog):
self._hideAuth() self._hideAuth()
else: else:
self.form.syncUser.setText(self.prof.get('syncUser', "")) self.form.syncUser.setText(self.prof.get('syncUser', ""))
self.connect(self.form.syncDeauth, SIGNAL("clicked()"), self.form.syncDeauth.clicked.connect(self.onSyncDeauth)
self.onSyncDeauth)
def _hideAuth(self): def _hideAuth(self):
self.form.syncDeauth.setVisible(False) 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): def setupBackup(self):
self.form.numBackups.setValue(self.prof['numBackups']) self.form.numBackups.setValue(self.prof['numBackups'])
self.form.compressBackups.setChecked(self.prof.get("compressBackups", True)) self.form.compressBackups.setChecked(self.prof.get("compressBackups", True))
self.connect(self.form.openBackupFolder, self.form.openBackupFolder.linkActivated.connect(self.onOpenBackup)
SIGNAL("linkActivated(QString)"),
self.onOpenBackup)
def onOpenBackup(self): def onOpenBackup(self):
openFolder(self.mw.pm.backupFolder()) 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): def setupOptions(self):
self.form.stripHTML.setChecked(self.prof['stripHTML']) self.form.stripHTML.setChecked(self.prof['stripHTML'])
self.form.pastePNG.setChecked(self.prof.get("pastePNG", False)) self.form.pastePNG.setChecked(self.prof.get("pastePNG", False))
self.connect( self.form.profilePass.clicked.connect(self.onProfilePass)
self.form.profilePass, SIGNAL("clicked()"),
self.onProfilePass)
def updateOptions(self): def updateOptions(self):
self.prof['stripHTML'] = self.form.stripHTML.isChecked() 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 return path
def addonFolder(self): def addonFolder(self):
return self._ensureExists(os.path.join(self.base, "addons")) return self._ensureExists(os.path.join(self.base, "addons21"))
def backupFolder(self): def backupFolder(self):
return self._ensureExists( return self._ensureExists(
@ -210,10 +210,7 @@ and no other programs are accessing your profile folders, then try again."""))
def _defaultBase(self): def _defaultBase(self):
if isWin: if isWin:
if False: #qtmajor >= 5: loc = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
loc = QStandardPaths.writeableLocation(QStandardPaths.DocumentsLocation)
else:
loc = QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation)
return os.path.join(loc, "Anki") return os.path.join(loc, "Anki")
elif isMac: elif isMac:
return os.path.expanduser("~/Documents/Anki") 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): if os.path.exists(p):
return p return p
else: else:
loc = QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation) loc = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
if loc[:-1] == QDesktopServices.storageLocation( if loc[:-1] == QStandardPaths.writableLocation(
QDesktopServices.HomeLocation): QStandardPaths.HomeLocation):
# occasionally "documentsLocation" will return the home # occasionally "documentsLocation" will return the home
# folder because the Documents folder isn't configured # folder because the Documents folder isn't configured
# properly; fall back to an English path # 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") return os.path.join(loc, "Anki")
def _loadMeta(self): 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) new = not os.path.exists(path)
def recover(): def recover():
# if we can't load profile, start with a new one # 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) os.rename(path, broken)
QMessageBox.warning( QMessageBox.warning(
None, "Preferences Corrupt", """\ 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.""") profiles, please add them back using the same names to recover your cards.""")
try: try:
self.db = DB(path) self.db = DB(path)
@ -308,8 +305,8 @@ please see:
d = self.langDiag = NoCloseDiag() d = self.langDiag = NoCloseDiag()
f = self.langForm = aqt.forms.setlang.Ui_Dialog() f = self.langForm = aqt.forms.setlang.Ui_Dialog()
f.setupUi(d) f.setupUi(d)
d.connect(d, SIGNAL("accepted()"), self._onLangSelected) d.accepted.connect(self._onLangSelected)
d.connect(d, SIGNAL("rejected()"), lambda: True) d.rejected.connect(lambda: True)
# default to the system language # default to the system language
try: try:
(lang, enc) = locale.getdefaultlocale() (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) t = QTimer(self.mw)
if not repeat: if not repeat:
t.setSingleShot(True) t.setSingleShot(True)
t.connect(t, SIGNAL("timeout()"), handler) t.timeout.connect(handler)
t.start(ms) t.start(ms)
return t return t

View File

@ -1,30 +1,25 @@
# Copyright: Damien Elmes <anki@ichi2.net> # Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # 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 # fixme: make sure not to optimize imports on this file
import sip import sip
import os import os
# fix buggy ubuntu12.04 display of language selector
os.environ["LIBOVERLAY_SCROLLBAR"] = "0"
from anki.utils import isWin, isMac from anki.utils import isWin, isMac
sip.setapi('QString', 2) from PyQt5.QtCore import *
sip.setapi('QVariant', 2) from PyQt5.QtGui import *
sip.setapi('QUrl', 2) from PyQt5.QtWidgets import *
try: from PyQt5.QtWebEngineWidgets import *
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.QtNetwork import QLocalServer, QLocalSocket
def debug(): def debug():
from PyQt4.QtCore import pyqtRemoveInputHook from PyQt5.QtCore import pyqtRemoveInputHook
from pdb import set_trace from pdb import set_trace
pyqtRemoveInputHook() pyqtRemoveInputHook()
set_trace() set_trace()
@ -33,7 +28,7 @@ import sys, traceback
if os.environ.get("DEBUG"): if os.environ.get("DEBUG"):
def info(type, value, tb): def info(type, value, tb):
from PyQt4.QtCore import pyqtRemoveInputHook from PyQt5.QtCore import pyqtRemoveInputHook
for line in traceback.format_exception(type, value, tb): for line in traceback.format_exception(type, value, tb):
sys.stdout.write(line) sys.stdout.write(line)
pyqtRemoveInputHook() pyqtRemoveInputHook()
@ -44,8 +39,5 @@ if os.environ.get("DEBUG"):
qtmajor = (QT_VERSION & 0xff0000) >> 16 qtmajor = (QT_VERSION & 0xff0000) >> 16
qtminor = (QT_VERSION & 0x00ff00) >> 8 qtminor = (QT_VERSION & 0x00ff00) >> 8
# qt4.6 doesn't support ruby tags if qtmajor < 5 or (qtmajor == 5 and qtminor < 5):
if qtmajor <= 4 and qtminor <= 6: raise Exception("Qt must be 5.5+")
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>'

View File

@ -36,19 +36,21 @@ class Reviewer(object):
# qshortcut so we don't autorepeat # qshortcut so we don't autorepeat
self.delShortcut = QShortcut(QKeySequence("Delete"), self.mw) self.delShortcut = QShortcut(QKeySequence("Delete"), self.mw)
self.delShortcut.setAutoRepeat(False) self.delShortcut.setAutoRepeat(False)
self.mw.connect(self.delShortcut, SIGNAL("activated()"), self.onDelete) self.delShortcut.activated.connect(self.onDelete)
addHook("leech", self.onLeech) addHook("leech", self.onLeech)
def show(self): def show(self):
self.mw.col.reset() self.mw.col.reset()
self.web.resetHandlers()
self.mw.keyHandler = self._keyHandler self.mw.keyHandler = self._keyHandler
self.web.setLinkHandler(self._linkHandler) self.web.onAnkiLink = self._linkHandler
self.web.setKeyHandler(self._catchEsc) self.web.setKeyHandler(self._catchEsc)
if isMac: if isMac:
self.bottom.web.setFixedHeight(46) self.bottom.web.setFixedHeight(46)
else: else:
self.bottom.web.setFixedHeight(52+self.mw.fontHeightDelta*4) 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._reps = None
self.nextCard() self.nextCard()
@ -161,12 +163,12 @@ function _toggleStar (show) {
function _getTypedText () { function _getTypedText () {
if (typeans) { if (typeans) {
py.link("typeans:"+typeans.value); openAnkiLink("typeans:"+typeans.value);
} }
}; };
function _typeAnsPress() { function _typeAnsPress() {
if (window.event.keyCode === 13) { if (window.event.keyCode === 13) {
py.link("ansHack"); openAnkiLink("ans");
} }
} }
</script> </script>
@ -177,15 +179,14 @@ function _typeAnsPress() {
self._bottomReady = False self._bottomReady = False
base = getBase(self.mw.col) base = getBase(self.mw.col)
# main window # main window
self.web.stdHtml(self._revHtml, self._styles(), self.web.onLoadFinished = self._showQuestion
loadCB=lambda x: self._showQuestion(), self.web.stdHtml(self._revHtml, self._styles(), head=base)
head=base)
# show answer / ease buttons # show answer / ease buttons
self.bottom.web.show() self.bottom.web.show()
self.bottom.web.onLoadFinished = self._showAnswerButton
self.bottom.web.stdHtml( self.bottom.web.stdHtml(
self._bottomHTML(), self._bottomHTML(),
self.bottom._css + self._bottomCSS, self.bottom._css + self._bottomCSS)
loadCB=lambda x: self._showAnswerButton())
# Showing the question # Showing the question
########################################################################## ##########################################################################
@ -277,19 +278,13 @@ The front of this card is empty. Please run Tools>Empty Cards.""")
self.web.eval("$('#typeans').blur();") self.web.eval("$('#typeans').blur();")
return True 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): def _keyHandler(self, evt):
key = str(evt.text()) key = str(evt.text())
if key == "e": if key == "e":
self.mw.onEditCurrent() self.mw.onEditCurrent()
elif (key == " " or evt.key() in (Qt.Key_Return, Qt.Key_Enter)): elif (key == " " or evt.key() in (Qt.Key_Return, Qt.Key_Enter)):
if self.state == "question": if self.state == "question":
self._showAnswerHack() self._showAnswer()
elif self.state == "answer": elif self.state == "answer":
self._answerCard(self._defaultEase()) self._answerCard(self._defaultEase())
elif key == "r" or evt.key() == Qt.Key_F5: 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): def _linkHandler(self, url):
if url == "ans": if url == "ans":
self._showAnswer() self._showAnswer()
elif url == "ansHack":
self.mw.progress.timer(100, self._showAnswerHack, False)
elif url.startswith("ease"): elif url.startswith("ease"):
self._answerCard(int(url[4:])) self._answerCard(int(url[4:]))
elif url == "edit": elif url == "edit":
@ -540,12 +533,12 @@ min-width: 60px; white-space: nowrap;
<tr> <tr>
<td align=left width=50 valign=top class=stat> <td align=left width=50 valign=top class=stat>
<br> <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 align=center valign=top id=middle>
</td> </td>
<td width=50 align=right valign=top class=stat><span id=time class=stattxt> <td width=50 align=right valign=top class=stat><span id=time class=stattxt>
</span><br> </span><br>
<button onclick="py.link('more');">%(more)s %(downArrow)s</button> <button onclick="openAnkiLink('more');">%(more)s %(downArrow)s</button>
</td> </td>
</tr> </tr>
</table> </table>
@ -603,7 +596,7 @@ function showAnswer(txt) {
self.bottom.web.setFocus() self.bottom.web.setFocus()
middle = ''' middle = '''
<span class=stattxt>%s</span><br> <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")) self._remaining(), _("Shortcut key: %s") % _("Space"), _("Show Answer"))
# wrap it in a table so it has the same top margin as the ease buttons # 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 middle = "<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>" % middle
@ -661,7 +654,7 @@ function showAnswer(txt) {
extra = "" extra = ""
due = self._buttonTime(i) due = self._buttonTime(i)
return ''' 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) %s</button></td>''' % (due, extra, _("Shortcut key: %s") % i, i, label)
buf = "<center><table cellpading=0 cellspacing=0><tr>" buf = "<center><table cellpading=0 cellspacing=0><tr>"
for ease, label in self._answerButtonList(): for ease, label in self._answerButtonList():
@ -713,7 +706,7 @@ function showAnswer(txt) {
label, scut, func = row label, scut, func = row
a = m.addAction(label) a = m.addAction(label)
a.setShortcut(QKeySequence(scut)) a.setShortcut(QKeySequence(scut))
a.connect(a, SIGNAL("triggered()"), func) a.triggered.connect(func)
runHook("Reviewer.contextMenuEvent",self,m) runHook("Reviewer.contextMenuEvent",self,m)
m.exec_(QCursor.pos()) m.exec_(QCursor.pos())

View File

@ -26,21 +26,19 @@ class DeckStats(QDialog):
restoreGeom(self, self.name) restoreGeom(self, self.name)
b = f.buttonBox.addButton(_("Save Image"), b = f.buttonBox.addButton(_("Save Image"),
QDialogButtonBox.ActionRole) QDialogButtonBox.ActionRole)
b.connect(b, SIGNAL("clicked()"), self.browser) b.clicked.connect(self.browser)
b.setAutoDefault(False) b.setAutoDefault(False)
c = self.connect f.groups.clicked.connect(lambda: self.changeScope("deck"))
s = SIGNAL("clicked()")
c(f.groups, s, lambda: self.changeScope("deck"))
f.groups.setShortcut("g") f.groups.setShortcut("g")
c(f.all, s, lambda: self.changeScope("collection")) f.all.clicked.connect(lambda: self.changeScope("collection"))
c(f.month, s, lambda: self.changePeriod(0)) f.month.clicked.connect(lambda: self.changePeriod(0))
c(f.year, s, lambda: self.changePeriod(1)) f.year.clicked.connect(lambda: self.changePeriod(1))
c(f.life, s, lambda: self.changePeriod(2)) f.life.clicked.connect(lambda: self.changePeriod(2))
c(f.web, SIGNAL("loadFinished(bool)"), self.loadFin)
maybeHideClose(self.form.buttonBox) maybeHideClose(self.form.buttonBox)
addCloseShortcut(self) addCloseShortcut(self)
self.refresh() self.refresh()
self.exec_() self.show()
print("fixme: save image support in deck stats")
def reject(self): def reject(self):
saveGeom(self, self.name) saveGeom(self, self.name)
@ -78,14 +76,10 @@ to your desktop."""))
self.wholeCollection = type == "collection" self.wholeCollection = type == "collection"
self.refresh() self.refresh()
def loadFin(self, b):
self.form.web.page().mainFrame().setScrollPosition(self.oldPos)
def refresh(self): def refresh(self):
self.mw.progress.start(immediate=True) self.mw.progress.start(immediate=True)
self.oldPos = self.form.web.page().mainFrame().scrollPosition()
stats = self.mw.col.stats() stats = self.mw.col.stats()
stats.wholeCollection = self.wholeCollection stats.wholeCollection = self.wholeCollection
self.report = stats.report(type=self.period) 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() self.mw.progress.finish()

View File

@ -31,7 +31,7 @@ class StudyDeck(QDialog):
b.setShortcut(QKeySequence("Ctrl+N")) b.setShortcut(QKeySequence("Ctrl+N"))
b.setToolTip(shortcut(_("Add New Deck (Ctrl+N)"))) b.setToolTip(shortcut(_("Add New Deck (Ctrl+N)")))
self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole) self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole)
b.connect(b, SIGNAL("clicked()"), self.onAddDeck) b.clicked.connect(self.onAddDeck)
if title: if title:
self.setWindowTitle(title) self.setWindowTitle(title)
if not names: if not names:
@ -45,15 +45,9 @@ class StudyDeck(QDialog):
self.ok = self.form.buttonBox.addButton( self.ok = self.form.buttonBox.addButton(
accept or _("Study"), QDialogButtonBox.AcceptRole) accept or _("Study"), QDialogButtonBox.AcceptRole)
self.setWindowModality(Qt.WindowModal) self.setWindowModality(Qt.WindowModal)
self.connect(self.form.buttonBox, self.form.buttonBox.helpRequested.connect(lambda: openHelp(help))
SIGNAL("helpRequested()"), self.form.filter.textEdited.connect(self.redraw)
lambda: openHelp(help)) self.form.list.itemDoubleClicked.connect(self.accept)
self.connect(self.form.filter,
SIGNAL("textEdited(QString)"),
self.redraw)
self.connect(self.form.list,
SIGNAL("itemDoubleClicked(QListWidgetItem*)"),
self.accept)
self.show() self.show()
# redraw after show so position at center correct # redraw after show so position at center correct
self.redraw("", current) self.redraw("", current)

View File

@ -45,7 +45,7 @@ class SyncManager(QObject):
t = self.thread = SyncThread( t = self.thread = SyncThread(
self.pm.collectionPath(), self.pm.profile['syncKey'], self.pm.collectionPath(), self.pm.profile['syncKey'],
auth=auth, media=self.pm.profile['syncMedia']) auth=auth, media=self.pm.profile['syncMedia'])
self.connect(t, SIGNAL("event"), self.onEvent) t.event.connect(self.onEvent)
self.label = _("Connecting...") self.label = _("Connecting...")
self.mw.progress.start(immediate=True, label=self.label) self.mw.progress.start(immediate=True, label=self.label)
self.sentBytes = self.recvBytes = 0 self.sentBytes = self.recvBytes = 0
@ -136,10 +136,10 @@ sync again to correct the issue."""))
self._confirmFullSync() self._confirmFullSync()
elif evt == "send": elif evt == "send":
# posted events not guaranteed to arrive in order # 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() self._updateLabel()
elif evt == "recv": elif evt == "recv":
self.recvBytes = max(self.recvBytes, args[0]) self.recvBytes = max(self.recvBytes, int(args[0]))
self._updateLabel() self._updateLabel()
def _rewriteError(self, err): def _rewriteError(self, err):
@ -216,8 +216,8 @@ enter your details below.""") %
vbox.addLayout(g) vbox.addLayout(g)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.button(QDialogButtonBox.Ok).setAutoDefault(True) bb.button(QDialogButtonBox.Ok).setAutoDefault(True)
self.connect(bb, SIGNAL("accepted()"), d.accept) bb.accepted.connect(d.accept)
self.connect(bb, SIGNAL("rejected()"), d.reject) bb.rejected.connect(d.reject)
vbox.addWidget(bb) vbox.addWidget(bb)
d.setLayout(vbox) d.setLayout(vbox)
d.show() d.show()
@ -275,6 +275,8 @@ Check Database, then sync again."""))
class SyncThread(QThread): class SyncThread(QThread):
event = pyqtSignal(str, str)
def __init__(self, path, hkey, auth=None, media=True): def __init__(self, path, hkey, auth=None, media=True):
QThread.__init__(self) QThread.__init__(self)
self.path = path self.path = path
@ -309,11 +311,11 @@ class SyncThread(QThread):
def sendEvent(bytes): def sendEvent(bytes):
self.sentTotal += bytes self.sentTotal += bytes
if canPost(): if canPost():
self.fireEvent("send", self.sentTotal) self.fireEvent("send", str(self.sentTotal))
def recvEvent(bytes): def recvEvent(bytes):
self.recvTotal += bytes self.recvTotal += bytes
if canPost(): if canPost():
self.fireEvent("recv", self.recvTotal) self.fireEvent("recv", str(self.recvTotal))
addHook("sync", syncEvent) addHook("sync", syncEvent)
addHook("syncMsg", syncMsg) addHook("syncMsg", syncMsg)
addHook("httpSend", sendEvent) addHook("httpSend", sendEvent)
@ -416,8 +418,8 @@ class SyncThread(QThread):
else: else:
self.fireEvent("mediaSuccess") self.fireEvent("mediaSuccess")
def fireEvent(self, *args): def fireEvent(self, cmd, arg=""):
self.emit(SIGNAL("event"), *args) self.event.emit(cmd, arg)
# Monkey-patch httplib & httplib2 so we can get progress info # Monkey-patch httplib & httplib2 so we can get progress info
@ -428,9 +430,10 @@ import http.client, httplib2
from io import StringIO from io import StringIO
from anki.hooks import runHook from anki.hooks import runHook
print("fixme: _conn_request and _incrementalSend need updating for python3")
# sending in httplib # sending in httplib
def _incrementalSend(self, data): def _incrementalSend(self, data):
print("fixme: _incrementalSend needs updating for python3")
"""Send `data' to the server.""" """Send `data' to the server."""
if self.sock is None: if self.sock is None:
if self.auto_open: if self.auto_open:
@ -447,7 +450,7 @@ def _incrementalSend(self, data):
self.sock.sendall(block) self.sock.sendall(block)
runHook("httpSend", len(block)) runHook("httpSend", len(block))
http.client.HTTPConnection.send = _incrementalSend #http.client.HTTPConnection.send = _incrementalSend
# receiving in httplib2 # receiving in httplib2
# this is an augmented version of httplib's request routine that: # 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 # - calls a hook for each chunk of data so we can update the gui
# - retries only when keep-alive connection is closed # - retries only when keep-alive connection is closed
def _conn_request(self, conn, request_uri, method, body, headers): def _conn_request(self, conn, request_uri, method, body, headers):
print("fixme: _conn_request updating for python3")
for i in range(2): for i in range(2):
try: try:
if conn.sock is None: if conn.sock is None:
@ -503,4 +505,4 @@ def _conn_request(self, conn, request_uri, method, body, headers):
content = httplib2._decompressContent(response, content) content = httplib2._decompressContent(response, content)
return (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): class TagEdit(QLineEdit):
lostFocus = pyqtSignal()
# 0 = tags, 1 = decks # 0 = tags, 1 = decks
def __init__(self, parent, type=0): def __init__(self, parent, type=0):
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
@ -53,7 +55,7 @@ class TagEdit(QLineEdit):
def focusOutEvent(self, evt): def focusOutEvent(self, evt):
QLineEdit.focusOutEvent(self, evt) QLineEdit.focusOutEvent(self, evt)
self.emit(SIGNAL("lostFocus")) self.lostFocus.emit()
self.completer.popup().hide() self.completer.popup().hide()
def hideCompleter(self): def hideCompleter(self):
@ -67,20 +69,20 @@ class TagCompleter(QCompleter):
self.edit = edit self.edit = edit
self.cursor = None self.cursor = None
def splitPath(self, str): def splitPath(self, tags):
str = str(str).strip() tags = tags.strip()
str = re.sub(" +", " ", str) tags = re.sub(" +", " ", tags)
self.tags = self.edit.col.tags.split(str) self.tags = self.edit.col.tags.split(tags)
self.tags.append("") self.tags.append("")
p = self.edit.cursorPosition() p = self.edit.cursorPosition()
self.cursor = str.count(" ", 0, p) self.cursor = tags.count(" ", 0, p)
return [self.tags[self.cursor]] return [self.tags[self.cursor]]
def pathFromIndex(self, idx): def pathFromIndex(self, idx):
if self.cursor is None: if self.cursor is None:
return self.edit.text() return self.edit.text()
ret = QCompleter.pathFromIndex(self, idx) ret = QCompleter.pathFromIndex(self, idx)
self.tags[self.cursor] = str(ret) self.tags[self.cursor] = ret
try: try:
self.tags.remove("") self.tags.remove("")
except ValueError: except ValueError:

View File

@ -9,9 +9,8 @@ class Toolbar(object):
def __init__(self, mw, web): def __init__(self, mw, web):
self.mw = mw self.mw = mw
self.web = web self.web = web
self.web.page().mainFrame().setScrollBarPolicy( self.web.resetHandlers()
Qt.Vertical, Qt.ScrollBarAlwaysOff) self.web.onAnkiLink = self._linkHandler
self.web.setLinkHandler(self._linkHandler)
self.link_handlers = { self.link_handlers = {
"decks": self._deckLinkHandler, "decks": self._deckLinkHandler,
"study": self._studyLinkHandler, "study": self._studyLinkHandler,
@ -51,7 +50,8 @@ class Toolbar(object):
def _linkHTML(self, links): def _linkHTML(self, links):
buf = "" buf = ""
for ln, name, title in links: 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) title, ln, name)
buf += "&nbsp;"*3 buf += "&nbsp;"*3
return buf return buf
@ -59,7 +59,8 @@ class Toolbar(object):
def _rightIcons(self): def _rightIcons(self):
buf = "" buf = ""
for ln, icon, title in self._rightIconsList(): 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) title, ln, icon)
return buf return buf
@ -67,11 +68,9 @@ class Toolbar(object):
###################################################################### ######################################################################
def _linkHandler(self, link): 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: if link in self.link_handlers:
self.link_handlers[link]() self.link_handlers[link]()
return False
def _deckLinkHandler(self): def _deckLinkHandler(self):
self.mw.moveToState("deckBrowser") self.mw.moveToState("deckBrowser")

View File

@ -14,6 +14,10 @@ from aqt.utils import showText
class LatestVersionFinder(QThread): class LatestVersionFinder(QThread):
newVerAvail = pyqtSignal(str)
newMsg = pyqtSignal(dict)
clockIsOff = pyqtSignal(float)
def __init__(self, main): def __init__(self, main):
QThread.__init__(self) QThread.__init__(self)
self.main = main self.main = main
@ -32,23 +36,25 @@ class LatestVersionFinder(QThread):
return return
d = self._data() d = self._data()
d['proto'] = 1 d['proto'] = 1
d = urllib.parse.urlencode(d) d = urllib.parse.urlencode(d).encode("utf8")
try: try:
f = urllib.request.urlopen(aqt.appUpdate, d) f = urllib.request.urlopen(aqt.appUpdate, d)
resp = f.read() resp = f.read()
if not resp: if not resp:
print("update check load failed")
return return
resp = json.loads(resp) resp = json.loads(resp.decode("utf8"))
except: except:
# behind proxy, corrupt message, etc # behind proxy, corrupt message, etc
print("update check failed")
return return
if resp['msg']: if resp['msg']:
self.emit(SIGNAL("newMsg"), resp) self.newMsg.emit(resp)
if resp['ver']: if resp['ver']:
self.emit(SIGNAL("newVerAvail"), resp['ver']) self.newVerAvail.emit(resp['ver'])
diff = resp['time'] - time.time() diff = resp['time'] - time.time()
if abs(diff) > 300: if abs(diff) > 300:
self.emit(SIGNAL("clockIsOff"), diff) self.clockIsOff.emit(diff)
def askAndUpdate(mw, ver): def askAndUpdate(mw, ver):
baseStr = ( baseStr = (

View File

@ -45,7 +45,7 @@ def showInfo(text, parent=False, help="", type="info", title="Anki"):
b.setDefault(True) b.setDefault(True)
if help: if help:
b = mb.addButton(QMessageBox.Help) b = mb.addButton(QMessageBox.Help)
b.connect(b, SIGNAL("clicked()"), lambda: openHelp(help)) b.clicked.connect(lambda: openHelp(help))
b.setAutoDefault(False) b.setAutoDefault(False)
return mb.exec_() return mb.exec_()
@ -70,7 +70,7 @@ def showText(txt, parent=None, type="text", run=True, geomKey=None, \
if geomKey: if geomKey:
saveGeom(diag, geomKey) saveGeom(diag, geomKey)
QDialog.reject(diag) QDialog.reject(diag)
diag.connect(box, SIGNAL("rejected()"), onReject) box.rejected.connect(onReject)
diag.setMinimumHeight(minHeight) diag.setMinimumHeight(minHeight)
diag.setMinimumWidth(minWidth) diag.setMinimumWidth(minWidth)
if geomKey: if geomKey:
@ -166,13 +166,10 @@ class GetTextDialog(QDialog):
b = QDialogButtonBox(buts) b = QDialogButtonBox(buts)
v.addWidget(b) v.addWidget(b)
self.setLayout(v) self.setLayout(v)
self.connect(b.button(QDialogButtonBox.Ok), b.button(QDialogButtonBox.Ok).clicked.connect(self.accept)
SIGNAL("clicked()"), self.accept) b.button(QDialogButtonBox.Cancel).clicked.connect(self.reject)
self.connect(b.button(QDialogButtonBox.Cancel),
SIGNAL("clicked()"), self.reject)
if help: if help:
self.connect(b.button(QDialogButtonBox.Help), b.button(QDialogButtonBox.Help).clicked.connect(self.helpRequested)
SIGNAL("clicked()"), self.helpRequested)
def accept(self): def accept(self):
return QDialog.accept(self) return QDialog.accept(self)
@ -214,7 +211,7 @@ def chooseList(prompt, choices, startrow=0, parent=None):
c.setCurrentRow(startrow) c.setCurrentRow(startrow)
l.addWidget(c) l.addWidget(c)
bb = QDialogButtonBox(QDialogButtonBox.Ok) bb = QDialogButtonBox(QDialogButtonBox.Ok)
bb.connect(bb, SIGNAL("accepted()"), d, SLOT("accept()")) bb.accepted.connect(d.accept)
l.addWidget(bb) l.addWidget(bb)
d.exec_() d.exec_()
return c.currentRow() return c.currentRow()
@ -239,9 +236,6 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
else: else:
dirkey = None dirkey = None
d = QFileDialog(parent) d = QFileDialog(parent)
# fix #233 crash
if isMac:
d.setOptions(QFileDialog.DontUseNativeDialog)
d.setFileMode(QFileDialog.ExistingFile) d.setFileMode(QFileDialog.ExistingFile)
if os.path.exists(dir): if os.path.exists(dir):
d.setDirectory(dir) d.setDirectory(dir)
@ -249,8 +243,6 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
d.setNameFilter(filter) d.setNameFilter(filter)
ret = [] ret = []
def accept(): def accept():
# work around an osx crash
#aqt.mw.app.processEvents()
file = str(list(d.selectedFiles())[0]) file = str(list(d.selectedFiles())[0])
if dirkey: if dirkey:
dir = os.path.dirname(file) dir = os.path.dirname(file)
@ -258,7 +250,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
if cb: if cb:
cb(file) cb(file)
ret.append(file) ret.append(file)
d.connect(d, SIGNAL("accepted()"), accept) d.accepted.connect(accept)
d.exec_() d.exec_()
return ret and ret[0] return ret and ret[0]
@ -268,9 +260,9 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None):
config_key = dir_description + 'Directory' config_key = dir_description + 'Directory'
base = aqt.mw.pm.profile.get(config_key, aqt.mw.pm.base) base = aqt.mw.pm.profile.get(config_key, aqt.mw.pm.base)
path = os.path.join(base, fname) path = os.path.join(base, fname)
file = str(QFileDialog.getSaveFileName( file = QFileDialog.getSaveFileName(
parent, title, path, "{0} (*{1})".format(key, ext), parent, title, path, "{0} (*{1})".format(key, ext),
options=QFileDialog.DontConfirmOverwrite)) options=QFileDialog.DontConfirmOverwrite)[0]
if file: if file:
# add extension # add extension
if not file.lower().endswith(ext): if not file.lower().endswith(ext):
@ -359,8 +351,6 @@ def getBase(col):
def openFolder(path): def openFolder(path):
if isWin: if isWin:
if isinstance(path, str):
path = path.encode(sys.getfilesystemencoding())
subprocess.Popen(["explorer", path]) subprocess.Popen(["explorer", path])
else: else:
QDesktopServices.openUrl(QUrl("file://" + path)) QDesktopServices.openUrl(QUrl("file://" + path))
@ -380,8 +370,7 @@ def addCloseShortcut(widg):
if not isMac: if not isMac:
return return
widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg) widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
widg.connect(widg._closeShortcut, SIGNAL("activated()"), widg._closeShortcut.activated.connect(widg.reject)
widg, SLOT("reject()"))
def downArrow(): def downArrow():
if isWin: if isWin:

View File

@ -9,53 +9,34 @@ from aqt.utils import openLink
from anki.utils import isMac, isWin from anki.utils import isMac, isWin
import anki.js 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 # Page for debug messages
########################################################################## ##########################################################################
class AnkiWebPage(QWebPage): class AnkiWebPage(QWebEnginePage):
def __init__(self, jsErr): def __init__(self, jsErr, acceptNavReq):
QWebPage.__init__(self) QWebEnginePage.__init__(self)
self._jsErr = jsErr self._jsErr = jsErr
def javaScriptConsoleMessage(self, msg, line, srcID): self._acceptNavReq = acceptNavReq
self._jsErr(msg, line, srcID)
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 # Main web view
########################################################################## ##########################################################################
class AnkiWebView(QWebView): class AnkiWebView(QWebEngineView):
def __init__(self, canFocus=True): def __init__(self, canFocus=True):
QWebView.__init__(self) QWebEngineView.__init__(self)
self.setRenderHints(
QPainter.TextAntialiasing |
QPainter.SmoothPixmapTransform |
QPainter.HighQualityAntialiasing)
self.setObjectName("mainText") self.setObjectName("mainText")
self._bridge = Bridge() self._page = AnkiWebPage(self._jsErr, self._acceptNavReq)
self._page = AnkiWebPage(self._jsErr)
self._loadFinishedCB = None self._loadFinishedCB = None
self.setPage(self._page) self.setPage(self._page)
self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.resetHandlers()
self.setLinkHandler()
self.setKeyHandler()
self.connect(self, SIGNAL("linkClicked(QUrl)"), self._linkHandler)
self.connect(self, SIGNAL("loadFinished(bool)"), self._loadFinished)
self.allowDrops = False self.allowDrops = False
# reset each time new html is set; used to detect if still in same state # reset each time new html is set; used to detect if still in same state
self.key = None self.key = None
@ -63,72 +44,81 @@ class AnkiWebView(QWebView):
def keyPressEvent(self, evt): def keyPressEvent(self, evt):
if evt.matches(QKeySequence.Copy): if evt.matches(QKeySequence.Copy):
self.triggerPageAction(QWebPage.Copy) self.triggerPageAction(QWebEnginePage.Copy)
evt.accept() evt.accept()
# work around a bug with windows qt where shift triggers buttons # work around a bug with windows qt where shift triggers buttons
if isWin and evt.modifiers() & Qt.ShiftModifier and not evt.text(): if isWin and evt.modifiers() & Qt.ShiftModifier and not evt.text():
evt.accept() evt.accept()
return return
QWebView.keyPressEvent(self, evt) QWebEngineView.keyPressEvent(self, evt)
def keyReleaseEvent(self, evt): def keyReleaseEvent(self, evt):
if self._keyHandler: if self._keyHandler:
if self._keyHandler(evt): if self._keyHandler(evt):
evt.accept() evt.accept()
return return
QWebView.keyReleaseEvent(self, evt) QWebEngineView.keyReleaseEvent(self, evt)
def contextMenuEvent(self, evt): def contextMenuEvent(self, evt):
if not self._canFocus: if not self._canFocus:
return return
m = QMenu(self) m = QMenu(self)
a = m.addAction(_("Copy")) a = m.addAction(_("Copy"))
a.connect(a, SIGNAL("triggered()"), a.triggered.connect(lambda: self.triggerPageAction(QWebEnginePage.Copy))
lambda: self.triggerPageAction(QWebPage.Copy))
runHook("AnkiWebView.contextMenuEvent", self, m) runHook("AnkiWebView.contextMenuEvent", self, m)
m.popup(QCursor.pos()) m.popup(QCursor.pos())
def dropEvent(self, evt): def dropEvent(self, evt):
pass 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): def setKeyHandler(self, handler=None):
# handler should return true if event should be swallowed # handler should return true if event should be swallowed
self._keyHandler = handler self._keyHandler = handler
def setHtml(self, html, loadCB=None): def setHtml(self, html):
self.key = None self.key = None
self._loadFinishedCB = loadCB app = QApplication.instance()
QWebView.setHtml(self, html) 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: if isMac:
button = "font-weight: bold; height: 24px;" button = "font-weight: bold; height: 24px;"
else: else:
button = "font-weight: normal;" button = "font-weight: normal;"
screen = QApplication.desktop().screen()
dpi = screen.logicalDpiX()
zoomFactor = max(1, dpi / 96.0)
self.setHtml(""" self.setHtml("""
<!doctype html> <!doctype html>
<html><head><style> <html><head><style>
body { zoom: %f; }
button { button {
%s %s
} }
%s</style> %s</style>
<script>%s</script> <script>
%s
openAnkiLink = function(txt) {
window.location = "http://anki/"+txt;
}
document.addEventListener("DOMContentLoaded", function(event) {
openAnkiLink("domDone");
});
</script>
%s %s
</head> </head>
<body class="%s">%s</body></html>""" % ( <body class="%s">%s</body></html>""" % (
button, css, js or anki.js.jquery+anki.js.browserSel, zoomFactor, button, css, js or anki.js.jquery+anki.js.browserSel,
head, bodyClass, body), loadCB) head, bodyClass, body))
def setBridge(self, bridge):
self._bridge.setBridge(bridge)
def setCanFocus(self, canFocus=False): def setCanFocus(self, canFocus=False):
self._canFocus = canFocus self._canFocus = canFocus
@ -138,21 +128,39 @@ button {
self.setFocusPolicy(Qt.NoFocus) self.setFocusPolicy(Qt.NoFocus)
def eval(self, js): def eval(self, js):
self.page().mainFrame().evaluateJavaScript(js) self.page().runJavaScript(js)
def _openLinksExternally(self, url): def _openLinksExternally(self, url):
openLink(url) openLink(url)
def _jsErr(self, msg, line, srcID): def _jsErr(self, lvl, msg, line, srcID):
sys.stdout.write( sys.stdout.write(
(_("JS error on line %(a)d: %(b)s") % (_("JS error on line %(a)d: %(b)s") %
dict(a=line, b=msg+"\n"))) dict(a=line, b=msg+"\n")))
def _linkHandler(self, url): def _acceptNavReq(self, url, navType, isMainFrame):
self.linkHandler(url.toString()) # 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): def defaultOnAnkiLink(self, link):
self.page().mainFrame().addToJavaScriptWindowObject("py", self._bridge) print("unhandled anki link:", link)
if self._loadFinishedCB:
self._loadFinishedCB(self) def defaultOnLoadFinished(self):
self._loadFinishedCB = None 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> <string>About Anki</string>
</property> </property>
<layout class="QVBoxLayout"> <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> <number>0</number>
</property> </property>
<item> <item>
<widget class="QWebView" name="label"> <widget class="AnkiWebView" name="label" native="true">
<property name="url"> <property name="url" stdset="0">
<url> <url>
<string>about:blank</string> <string>about:blank</string>
</url> </url>
@ -46,9 +55,10 @@
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>QWebView</class> <class>AnkiWebView</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>QtWebKit/QWebView</header> <header location="global">aqt/webview</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/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. # should be on the path.
# #
@ -24,10 +24,10 @@ do
base=$(basename $i .ui) base=$(basename $i .ui)
py="aqt/forms/${base}.py" py="aqt/forms/${base}.py"
echo " \"$base\"," >> $init echo " \"$base\"," >> $init
echo "import $base" >> $temp echo "from . import $base" >> $temp
if [ $i -nt $py ]; then if [ $i -nt $py ]; then
echo " * "$py echo " * "$py
pyuic4 $i -o $py pyuic5 --from-imports $i -o $py
# munge the output to use gettext # munge the output to use gettext
perl -pi.bak -e 's/(QtGui\.QApplication\.)?_?translate\(".*?", /_(/; s/, None.*/))/' $py perl -pi.bak -e 's/(QtGui\.QApplication\.)?_?translate\(".*?", /_(/; s/, None.*/))/' $py
rm $py.bak rm $py.bak
@ -38,4 +38,4 @@ cat $temp >> $init
rm $temp rm $temp
echo "Building resources.." echo "Building resources.."
pyrcc4 designer/icons.qrc -o aqt/forms/icons_rc.py pyrcc5 designer/icons.qrc -o aqt/forms/icons_rc.py