diff --git a/ankiqt/ui/cardlist.py b/ankiqt/ui/cardlist.py index 848b29b3e..7c0069985 100644 --- a/ankiqt/ui/cardlist.py +++ b/ankiqt/ui/cardlist.py @@ -5,8 +5,9 @@ import sre_constants from PyQt4.QtGui import * from PyQt4.QtCore import * +from PyQt4.QtWebKit import QWebPage import time, types, sys, re -from operator import attrgetter +from operator import attrgetter, itemgetter import anki, anki.utils, ankiqt.forms from ankiqt import ui from anki.cards import cardsTable, Card @@ -703,6 +704,7 @@ class EditDeck(QMainWindow): self.connect(self.dialog.actionInvertSelection, SIGNAL("triggered()"), self.invertSelection) self.connect(self.dialog.actionSelectFacts, SIGNAL("triggered()"), self.selectFacts) self.connect(self.dialog.actionFindReplace, SIGNAL("triggered()"), self.onFindReplace) + self.connect(self.dialog.actionFindDuplicates, SIGNAL("triggered()"), self.onFindDupes) # jumps self.connect(self.dialog.actionFirstCard, SIGNAL("triggered()"), self.onFirstCard) self.connect(self.dialog.actionLastCard, SIGNAL("triggered()"), self.onLastCard) @@ -1126,6 +1128,81 @@ where id in %s""" % ids2str(sf)) def onFindReplaceHelp(self): QDesktopServices.openUrl(QUrl(ankiqt.appWiki + "Browser#FindReplace")) + # Edit: finding dupes + ###################################################################### + + def onFindDupes(self): + win = QDialog(self) + dialog = ankiqt.forms.finddupes.Ui_Dialog() + dialog.setupUi(win) + restoreGeom(win, "findDupes") + fields = sorted(self.currentCard.fact.model.fieldModels, key=attrgetter("name")) + # per-model data + data = self.deck.s.all(""" +select fm.id, m.name || '>' || fm.name from fieldmodels fm, models m +where fm.modelId = m.id""") + data.sort(key=itemgetter(1)) + # all-model data + data2 = self.deck.s.all(""" +select fm.id, fm.name from fieldmodels fm""") + byName = {} + for d in data2: + if d[1] in byName: + byName[d[1]].append(d[0]) + else: + byName[d[1]] = [d[0]] + names = byName.keys() + names.sort() + alldata = [(byName[n], n) for n in names] + data + dialog.searchArea.addItems(QStringList([d[1] for d in alldata])) + # links + dialog.webView.page().setLinkDelegationPolicy( + QWebPage.DelegateAllLinks) + self.connect(dialog.webView, + SIGNAL("linkClicked(QUrl)"), + self.dupeLinkClicked) + + def onFin(code): + saveGeom(win, "findDupes") + self.connect(win, SIGNAL("finished(int)"), onFin) + + def onClick(): + idx = dialog.searchArea.currentIndex() + data = alldata[idx] + if isinstance(data[0], list): + # all models + fmids = data[0] + else: + # single model + fmids = [data[0]] + self.duplicatesReport(dialog.webView, fmids) + + self.connect(dialog.searchButton, SIGNAL("clicked()"), + onClick) + win.show() + + def duplicatesReport(self, web, fmids): + self.deck.startProgress(2) + self.deck.updateProgress(_("Finding...")) + res = self.deck.findDuplicates(fmids) + t = "" + t += _("Duplicate Groups: %d") % len(res) + t += "

    " + + for group in res: + t += '
  1. %s' % ( + "fid:" + ",".join(str(id) for id in group[1]), + group[0]) + + t += "
" + t += "" + web.setHtml(t) + self.deck.finishProgress() + + def dupeLinkClicked(self, link): + self.dialog.filterEdit.setText(str(link.toString())) + self.updateSearch() + self.onFact() # Jumping ###################################################################### diff --git a/designer/cardlist.ui b/designer/cardlist.ui index 0b85702da..7e30c8fca 100644 --- a/designer/cardlist.ui +++ b/designer/cardlist.ui @@ -226,6 +226,7 @@ + @@ -585,6 +586,11 @@ Ctrl+L + + + Find &Duplicates... + + diff --git a/designer/finddupes.ui b/designer/finddupes.ui new file mode 100644 index 000000000..850a11757 --- /dev/null +++ b/designer/finddupes.ui @@ -0,0 +1,135 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Find Duplicates + + + + + + + + Find Duplicates in: + + + + + + + + + + &Search + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + + + + about:blank + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + QWebView + QWidget +
QtWebKit/QWebView
+
+
+ + searchArea + searchButton + webView + buttonBox + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +