python GUI sample working
This commit is contained in:
parent
906b636a97
commit
fca07e02e2
3 changed files with 197 additions and 77 deletions
4
src/python/samples/recollgui/Makefile
Normal file
4
src/python/samples/recollgui/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
all: rclmain.py
|
||||||
|
|
||||||
|
rclmain.py: rclmain.ui
|
||||||
|
pyuic4 -o rclmain.py rclmain.ui
|
|
@ -15,42 +15,96 @@ from getopt import getopt
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Highlighting methods. Just for showing the groups usage, we add the
|
||||||
|
# original string for the match to the highlighted text. I don't think
|
||||||
|
# you'd want to do this in a real app, but maybe some kind of tooltip?
|
||||||
|
class HlMeths:
|
||||||
|
def __init__(self, groups):
|
||||||
|
self.groups = groups
|
||||||
|
|
||||||
|
def startMatch(self, idx):
|
||||||
|
ugroup = " ".join(self.groups[idx][0])
|
||||||
|
return '<font size="tiny">'+ugroup+'</font><font color="blue">'
|
||||||
|
|
||||||
|
def endMatch(self):
|
||||||
|
return '</font>'
|
||||||
|
|
||||||
|
############
|
||||||
|
# Data extraction. The 2 following methods use the extractor module
|
||||||
|
# and get the data from the original document
|
||||||
|
#
|
||||||
|
# Extract and return document text (in text or html format, indicated
|
||||||
|
# by newdoc.mimetype)
|
||||||
|
def textextract(doc):
|
||||||
|
extractor = rclextract.Extractor(doc)
|
||||||
|
newdoc = extractor.textextract(doc.ipath)
|
||||||
|
return newdoc
|
||||||
|
# Extract document in original format (ie: application/msword) and
|
||||||
|
# save it to a file. This only works if ipath is not null (else just
|
||||||
|
# use the url !)
|
||||||
|
def extractofile(doc, outfilename=""):
|
||||||
|
extractor = rclextract.Extractor(doc)
|
||||||
|
outfilename = extractor.idoctofile(doc.ipath, doc.mimetype, \
|
||||||
|
ofilename=outfilename)
|
||||||
|
return outfilename
|
||||||
|
|
||||||
|
#########
|
||||||
|
# RecollQuery wraps a recoll.query object in a Qt model
|
||||||
class RecollQuery(QtCore.QAbstractTableModel):
|
class RecollQuery(QtCore.QAbstractTableModel):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtCore.QAbstractTableModel.__init__(self)
|
QtCore.QAbstractTableModel.__init__(self)
|
||||||
self.totres = -1
|
self.totres = -1
|
||||||
|
self.db = None
|
||||||
self.query = None
|
self.query = None
|
||||||
|
self.qtext = ""
|
||||||
self.docs = []
|
self.docs = []
|
||||||
self.pagelen = 10
|
self.pagelen = 10
|
||||||
self.attrs = ("filename", "title", "mtime", "url", "ipath")
|
self.attrs = ("filename", "title", "mtime", "url", "ipath")
|
||||||
|
|
||||||
def rowCount(self, parent):
|
def rowCount(self, parent):
|
||||||
ret = len(self.docs)
|
ret = len(self.docs)
|
||||||
#print "RecollQuery.rowCount(): ", ret
|
#print "RecollQuery.rowCount(): ", ret
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
#print "RecollQuery.columnCount()"
|
#print "RecollQuery.columnCount()"
|
||||||
if parent.isValid():
|
if parent.isValid():
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return len(self.attrs)
|
return len(self.attrs)
|
||||||
def setquery(self, db, q):
|
|
||||||
|
def setquery(self, db, q, sortfield="", ascending=True):
|
||||||
"""Parse and execute query on open db"""
|
"""Parse and execute query on open db"""
|
||||||
print "RecollQuery.setquery():"
|
#print "RecollQuery.setquery():"
|
||||||
# Get query object
|
# Get query object
|
||||||
self.query = db.query()
|
self.query = db.query()
|
||||||
|
if sortfield:
|
||||||
|
self.query.sortby(sortfield, ascending)
|
||||||
# Parse/run input query string
|
# Parse/run input query string
|
||||||
self.totres = self.query.execute(q)
|
self.totres = self.query.execute(q)
|
||||||
|
self.qtext = q
|
||||||
|
self.db = db
|
||||||
self.docs = []
|
self.docs = []
|
||||||
self.fetchMore(None)
|
self.fetchMore(None)
|
||||||
|
|
||||||
def getdoc(self, index):
|
def getdoc(self, index):
|
||||||
if index.row() < len(self.docs):
|
if index.row() < len(self.docs):
|
||||||
return self.docs[index.row()]
|
return self.docs[index.row()]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def sort(self, col, order):
|
||||||
|
#print "sort", col, order
|
||||||
|
self.setquery(self.db, self.qtext, sortfield=self.attrs[col],
|
||||||
|
ascending = order)
|
||||||
|
|
||||||
def headerData(self, idx, orient, role):
|
def headerData(self, idx, orient, role):
|
||||||
if orient == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
if orient == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||||
return self.attrs[idx]
|
return self.attrs[idx]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
#print "RecollQuery.data: row %d, role: " % (index.row(),), role
|
#print "RecollQuery.data: row %d, role: " % (index.row(),), role
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
|
@ -70,12 +124,14 @@ class RecollQuery(QtCore.QAbstractTableModel):
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
return QtCore.QVariant()
|
return QtCore.QVariant()
|
||||||
|
|
||||||
def canFetchMore(self, parent):
|
def canFetchMore(self, parent):
|
||||||
#print "RecollQuery.canFetchMore:"
|
#print "RecollQuery.canFetchMore:"
|
||||||
if len(self.docs) < self.totres:
|
if len(self.docs) < self.totres:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def fetchMore(self, parent):
|
def fetchMore(self, parent):
|
||||||
#print "RecollQuery.fetchMore:"
|
#print "RecollQuery.fetchMore:"
|
||||||
self.beginInsertRows(QtCore.QModelIndex(), len(self.docs), \
|
self.beginInsertRows(QtCore.QModelIndex(), len(self.docs), \
|
||||||
|
@ -88,26 +144,9 @@ class RecollQuery(QtCore.QAbstractTableModel):
|
||||||
count += 1
|
count += 1
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
|
|
||||||
class HlMeths:
|
|
||||||
def __init__(self, groups):
|
|
||||||
self.groups = groups
|
|
||||||
def startMatch(self, idx):
|
|
||||||
ugroup = " ".join(self.groups[idx][1])
|
|
||||||
return '<font color="blue">'
|
|
||||||
def endMatch(self):
|
|
||||||
return '</font>'
|
|
||||||
|
|
||||||
def extract(doc):
|
|
||||||
extractor = rclextract.Extractor(doc)
|
|
||||||
newdoc = extractor.textextract(doc.ipath)
|
|
||||||
return newdoc
|
|
||||||
|
|
||||||
def extractofile(doc, outfilename=""):
|
|
||||||
extractor = rclextract.Extractor(doc)
|
|
||||||
outfilename = extractor.idoctofile(doc.ipath, doc.mimetype, \
|
|
||||||
ofilename=outfilename)
|
|
||||||
return outfilename
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# UI interaction code
|
||||||
class RclGui_Main(QtGui.QMainWindow):
|
class RclGui_Main(QtGui.QMainWindow):
|
||||||
def __init__(self, db, parent=None):
|
def __init__(self, db, parent=None):
|
||||||
QtGui.QWidget.__init__(self, parent)
|
QtGui.QWidget.__init__(self, parent)
|
||||||
|
@ -115,53 +154,92 @@ class RclGui_Main(QtGui.QMainWindow):
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.qmodel = RecollQuery()
|
self.qmodel = RecollQuery()
|
||||||
|
scq = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Q"), self);
|
||||||
|
self.connect(scq, QtCore.SIGNAL("activated()"), self.onexit)
|
||||||
|
header = self.ui.resTable.horizontalHeader();
|
||||||
|
header.setSortIndicatorShown(True);
|
||||||
|
header.setSortIndicator(-1, QtCore.Qt.AscendingOrder);
|
||||||
|
self.ui.resTable.setSortingEnabled(True)
|
||||||
|
self.currentindex = -1
|
||||||
|
self.currentdoc = None
|
||||||
|
|
||||||
def on_searchEntry_returnPressed(self):
|
def on_searchEntry_returnPressed(self):
|
||||||
self.startQuery()
|
self.startQuery()
|
||||||
|
|
||||||
def on_resTable_clicked(self, index):
|
def on_resTable_clicked(self, index):
|
||||||
doc = self.qmodel.getdoc(index)
|
doc = self.qmodel.getdoc(index)
|
||||||
query = self.qmodel.query;
|
self.currentindex = index
|
||||||
groups = self.qmodel.query.getgroups()
|
self.currentdoc = doc
|
||||||
|
if doc is None:
|
||||||
|
print "NO DoC"
|
||||||
|
return
|
||||||
|
query = self.qmodel.query
|
||||||
|
groups = query.getgroups()
|
||||||
meths = HlMeths(groups)
|
meths = HlMeths(groups)
|
||||||
if doc is not None:
|
abs = query.makedocabstract(doc, methods=meths)
|
||||||
|
self.ui.resDetail.setText(abs)
|
||||||
|
if hasextract:
|
||||||
ipath = doc.get('ipath')
|
ipath = doc.get('ipath')
|
||||||
print "ipath[", ipath, "]"
|
#print "ipath[%s]" % (ipath,)
|
||||||
if index.column() == 1:
|
self.ui.previewPB.setEnabled(True)
|
||||||
newdoc = extract(doc)
|
if ipath:
|
||||||
print "newdoc.mimetype:", newdoc.mimetype
|
self.ui.savePB.setEnabled(True)
|
||||||
if newdoc.mimetype == 'text/html':
|
|
||||||
ishtml = True
|
|
||||||
else:
|
|
||||||
ishtml = False
|
|
||||||
text = query.highlight(newdoc.text,
|
|
||||||
methods=meths,
|
|
||||||
ishtml=ishtml,
|
|
||||||
eolbr=True)
|
|
||||||
print text
|
|
||||||
|
|
||||||
text = '<qt><head></head><body>' + text + '</body></qt>'
|
|
||||||
self.ui.resDetail.setText(text)
|
|
||||||
elif index.column() == 3 and ipath:
|
|
||||||
fn = QtGui.QFileDialog.getSaveFileName(self)
|
|
||||||
if fn:
|
|
||||||
docitems = doc.items()
|
|
||||||
fn = extractofile(doc, str(fn.toLocal8Bit()))
|
|
||||||
print "Saved as", fn
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, "Canceled"
|
|
||||||
else:
|
else:
|
||||||
abs = query.makedocabstract(doc, methods=meths)
|
self.ui.savePB.setEnabled(False)
|
||||||
self.ui.resDetail.setText(abs)
|
|
||||||
|
# The 'checked' thing is to work around a bug in pyqt?
|
||||||
|
def on_previewPB_clicked(self, checked=True):
|
||||||
|
print "on_previewPB_clicked(self, %s):" % (repr(checked))
|
||||||
|
if checked:
|
||||||
|
return
|
||||||
|
newdoc = textextract(self.currentdoc)
|
||||||
|
query = self.qmodel.query;
|
||||||
|
groups = query.getgroups()
|
||||||
|
meths = HlMeths(groups)
|
||||||
|
#print "newdoc.mimetype:", newdoc.mimetype
|
||||||
|
if newdoc.mimetype == 'text/html':
|
||||||
|
ishtml = True
|
||||||
|
else:
|
||||||
|
ishtml = False
|
||||||
|
text = '<qt><head></head><body>' + \
|
||||||
|
query.highlight(newdoc.text,
|
||||||
|
methods=meths,
|
||||||
|
ishtml=ishtml,
|
||||||
|
eolbr=True)
|
||||||
|
text += '</body></qt>'
|
||||||
|
self.ui.resDetail.setText(text)
|
||||||
|
|
||||||
|
# The 'checked' thing is to work around a bug in pyqt?
|
||||||
|
def on_savePB_clicked(self, checked=True):
|
||||||
|
print "on_savePB_clicked(self, %s):" % (repr(checked))
|
||||||
|
if checked:
|
||||||
|
return
|
||||||
|
doc = self.currentdoc
|
||||||
|
ipath = doc.ipath
|
||||||
|
if not ipath:
|
||||||
|
return
|
||||||
|
fn = QtGui.QFileDialog.getSaveFileName(self)
|
||||||
|
if fn:
|
||||||
|
docitems = doc.items()
|
||||||
|
fn = extractofile(doc, str(fn.toLocal8Bit()))
|
||||||
|
print "Saved as", fn
|
||||||
|
else:
|
||||||
|
print >> sys.stderr, "Canceled"
|
||||||
|
|
||||||
def startQuery(self):
|
def startQuery(self):
|
||||||
self.qmodel.setquery(self.db, self.ui.searchEntry.text())
|
self.qmodel.setquery(self.db, self.ui.searchEntry.text())
|
||||||
self.ui.resTable.setModel(self.qmodel)
|
self.ui.resTable.setModel(self.qmodel)
|
||||||
|
|
||||||
def onexit(self):
|
def onexit(self):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
def Usage():
|
def Usage():
|
||||||
print >> sys.stderr, '''Usage: qt.py [<qword1> [<qword2> ...]]'''
|
print >> sys.stderr, '''Usage: qt.py [<qword1> [<qword2> ...]]'''
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
|
|
||||||
app = QtGui.QApplication(args)
|
app = QtGui.QApplication(args)
|
||||||
|
|
|
@ -30,34 +30,55 @@
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSplitter" name="splitter">
|
<widget class="QTableView" name="resTable">
|
||||||
<property name="orientation">
|
<property name="sizePolicy">
|
||||||
<enum>Qt::Vertical</enum>
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>4</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="cornerButtonEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QTableView" name="resTable">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>4</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="cornerButtonEnabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QTextBrowser" name="resDetail">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="resDetail">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="previewPB">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Preview</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="savePB">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenuBar" name="menubar">
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
@ -85,5 +106,22 @@
|
||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>actionExit</sender>
|
||||||
|
<signal>activated()</signal>
|
||||||
|
<receiver>MainWindow</receiver>
|
||||||
|
<slot>close()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>399</x>
|
||||||
|
<y>299</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue