mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 08:46:37 -04:00
added find duplicate tool to the browser
This commit is contained in:
parent
7d799d8b57
commit
1be2176b8e
3 changed files with 219 additions and 1 deletions
|
@ -5,8 +5,9 @@
|
||||||
import sre_constants
|
import sre_constants
|
||||||
from PyQt4.QtGui import *
|
from PyQt4.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
from PyQt4.QtWebKit import QWebPage
|
||||||
import time, types, sys, re
|
import time, types, sys, re
|
||||||
from operator import attrgetter
|
from operator import attrgetter, itemgetter
|
||||||
import anki, anki.utils, ankiqt.forms
|
import anki, anki.utils, ankiqt.forms
|
||||||
from ankiqt import ui
|
from ankiqt import ui
|
||||||
from anki.cards import cardsTable, Card
|
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.actionInvertSelection, SIGNAL("triggered()"), self.invertSelection)
|
||||||
self.connect(self.dialog.actionSelectFacts, SIGNAL("triggered()"), self.selectFacts)
|
self.connect(self.dialog.actionSelectFacts, SIGNAL("triggered()"), self.selectFacts)
|
||||||
self.connect(self.dialog.actionFindReplace, SIGNAL("triggered()"), self.onFindReplace)
|
self.connect(self.dialog.actionFindReplace, SIGNAL("triggered()"), self.onFindReplace)
|
||||||
|
self.connect(self.dialog.actionFindDuplicates, SIGNAL("triggered()"), self.onFindDupes)
|
||||||
# jumps
|
# jumps
|
||||||
self.connect(self.dialog.actionFirstCard, SIGNAL("triggered()"), self.onFirstCard)
|
self.connect(self.dialog.actionFirstCard, SIGNAL("triggered()"), self.onFirstCard)
|
||||||
self.connect(self.dialog.actionLastCard, SIGNAL("triggered()"), self.onLastCard)
|
self.connect(self.dialog.actionLastCard, SIGNAL("triggered()"), self.onLastCard)
|
||||||
|
@ -1126,6 +1128,81 @@ where id in %s""" % ids2str(sf))
|
||||||
def onFindReplaceHelp(self):
|
def onFindReplaceHelp(self):
|
||||||
QDesktopServices.openUrl(QUrl(ankiqt.appWiki +
|
QDesktopServices.openUrl(QUrl(ankiqt.appWiki +
|
||||||
"Browser#FindReplace"))
|
"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 = "<html><body>"
|
||||||
|
t += _("Duplicate Groups: %d") % len(res)
|
||||||
|
t += "<p><ol>"
|
||||||
|
|
||||||
|
for group in res:
|
||||||
|
t += '<li><a href="%s">%s</a>' % (
|
||||||
|
"fid:" + ",".join(str(id) for id in group[1]),
|
||||||
|
group[0])
|
||||||
|
|
||||||
|
t += "</ol>"
|
||||||
|
t += "</body></html>"
|
||||||
|
web.setHtml(t)
|
||||||
|
self.deck.finishProgress()
|
||||||
|
|
||||||
|
def dupeLinkClicked(self, link):
|
||||||
|
self.dialog.filterEdit.setText(str(link.toString()))
|
||||||
|
self.updateSearch()
|
||||||
|
self.onFact()
|
||||||
|
|
||||||
# Jumping
|
# Jumping
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
@ -226,6 +226,7 @@
|
||||||
<addaction name="actionInvertSelection"/>
|
<addaction name="actionInvertSelection"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionFindReplace"/>
|
<addaction name="actionFindReplace"/>
|
||||||
|
<addaction name="actionFindDuplicates"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionFont"/>
|
<addaction name="actionFont"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -585,6 +586,11 @@
|
||||||
<string>Ctrl+L</string>
|
<string>Ctrl+L</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionFindDuplicates">
|
||||||
|
<property name="text">
|
||||||
|
<string>Find &Duplicates...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../icons.qrc"/>
|
<include location="../icons.qrc"/>
|
||||||
|
|
135
designer/finddupes.ui
Normal file
135
designer/finddupes.ui
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Find Duplicates</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Find Duplicates in:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QComboBox" name="searchArea"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="4">
|
||||||
|
<widget class="QPushButton" name="searchButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="5">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QFrame" name="frame">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QWebView" name="webView">
|
||||||
|
<property name="url">
|
||||||
|
<url>
|
||||||
|
<string>about:blank</string>
|
||||||
|
</url>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>QWebView</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>QtWebKit/QWebView</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>searchArea</tabstop>
|
||||||
|
<tabstop>searchButton</tabstop>
|
||||||
|
<tabstop>webView</tabstop>
|
||||||
|
<tabstop>buttonBox</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
Loading…
Reference in a new issue