Raising the pylint score of libprs500/gui/*.py

This commit is contained in:
Kovid Goyal 2006-12-20 02:08:57 +00:00
parent 97fe142281
commit 18111d2d50
6 changed files with 1576 additions and 1360 deletions

View File

@ -12,6 +12,7 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" The GUI to libprs500. Also has ebook library management features. """
__docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
APP_TITLE = "libprs500"
@ -33,7 +34,7 @@ def installErrorHandler(dialog):
error_dialog.setModal(True)
def Warning(msg, e):
def _Warning(msg, e):
print >> sys.stderr, msg
if e: traceback.print_exc(e)
@ -53,4 +54,6 @@ def import_ui(name):
exec code_string.getvalue()
return locals()[winfo["uiclass"]]
from libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed in globals() for import_ui
# Needed in globals() for import_ui
from libprs500.gui.widgets import LibraryBooksView, \
DeviceBooksView, CoverDisplay, DeviceView

View File

@ -22,57 +22,72 @@ from cStringIO import StringIO as cStringIO
class LibraryDatabase(object):
BOOKS_SQL = """
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT, authors TEXT, publisher TEXT, size INTEGER, tags TEXT,
cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP, comments TEXT, rating INTEGER);
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT,
authors TEXT, publisher TEXT, size INTEGER, tags TEXT,
cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP,
comments TEXT, rating INTEGER);
create table if not exists books_data(id INTEGER, extension TEXT, data BLOB);
"""
def __init__(self, dbpath):
self.con = sqlite.connect(dbpath)
self.con.row_factory = sqlite.Row # Allow case insensitive field access by name
# Allow case insensitive field access by name
self.con.row_factory = sqlite.Row
self.con.executescript(LibraryDatabase.BOOKS_SQL)
def get_cover(self, id):
raw = self.con.execute("select cover from books_meta where id=?", (id,)).next()["cover"]
def get_cover(self, _id):
raw = self.con.execute("select cover from books_meta where id=?", (_id,))\
.next()["cover"]
return decompress(str(raw)) if raw else None
def get_extensions(self, id):
def get_extensions(self, _id):
exts = []
cur = self.con.execute("select extension from books_data where id=?", (id,))
cur = self.con.execute("select extension from books_data where id=?", (_id,))
for row in cur:
exts.append(row["extension"])
return exts
def add_book(self, path):
file = os.path.abspath(path)
title, author, publisher, size, cover = os.path.basename(file), None, None, os.stat(file)[ST_SIZE], None
_file = os.path.abspath(path)
title, author, publisher, size, cover = os.path.basename(_file), \
None, None, os.stat(_file)[ST_SIZE], None
ext = title[title.rfind(".")+1:].lower() if title.find(".") > -1 else None
if ext == "lrf":
lrf = LRFMetaFile(open(file, "r+b"))
title, author, cover, publisher = lrf.title, lrf.author.strip(), lrf.thumbnail, lrf.publisher.strip()
if "unknown" in publisher.lower(): publisher = None
if "unknown" in author.lower(): author = None
file = compress(open(file).read())
if cover: cover = sqlite.Binary(compress(cover))
self.con.execute("insert into books_meta (title, authors, publisher, size, tags, cover, comments, rating) values (?,?,?,?,?,?,?,?)", (title, author, publisher, size, None, cover, None, None))
id = self.con.execute("select max(id) from books_meta").next()[0]
self.con.execute("insert into books_data values (?,?,?)", (id, ext, sqlite.Binary(file)))
lrf = LRFMetaFile(open(_file, "r+b"))
title, author, cover, publisher = lrf.title, lrf.author.strip(), \
lrf.thumbnail, lrf.publisher.strip()
if "unknown" in publisher.lower():
publisher = None
if "unknown" in author.lower():
author = None
_file = compress(open(_file).read())
if cover:
cover = sqlite.Binary(compress(cover))
self.con.execute("insert into books_meta (title, authors, publisher, "+\
"size, tags, cover, comments, rating) values "+\
"(?,?,?,?,?,?,?,?)", \
(title, author, publisher, size, None, cover, None, None))
_id = self.con.execute("select max(id) from books_meta").next()[0]
self.con.execute("insert into books_data values (?,?,?)", \
(_id, ext, sqlite.Binary(_file)))
self.con.commit()
return id
return _id
def get_row_by_id(self, id, columns):
def get_row_by_id(self, _id, columns):
""" @param columns: list of column names """
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta where id=?", (id,))
cur = self.con.execute("select " + cols + " from books_meta where id=?"\
, (_id,))
row, r = cur.next(), {}
for c in columns: r[c] = row[c]
for c in columns:
r[c] = row[c]
return r
def commit(self): self.con.commit()
def delete_by_id(self, id):
self.con.execute("delete from books_meta where id=?", (id,))
self.con.execute("delete from books_data where id=?", (id,))
def delete_by_id(self, _id):
self.con.execute("delete from books_meta where id=?", (_id,))
self.con.execute("delete from books_data where id=?", (_id,))
def get_table(self, columns):
cols = ",".join([ c for c in columns])
@ -80,22 +95,29 @@ class LibraryDatabase(object):
rows = []
for row in cur:
r = {}
for c in columns: r[c] = row[c]
for c in columns:
r[c] = row[c]
rows.append(r)
return rows
def get_format(self, id, ext):
def get_format(self, _id, ext):
"""
Return format C{ext} corresponding to the logical book C{id} or None if the format is unavailable.
Format is returned as a string of binary data suitable for C{ file.write} operations.
Return format C{ext} corresponding to the logical book C{id} or
None if the format is unavailable.
Format is returned as a string of binary data suitable for
C{ file.write} operations.
"""
ext = ext.lower()
cur = self.con.execute("select data from books_data where id=? and extension=?",(id, ext))
try: data = cur.next()
except: pass
else: return decompress(str(data["data"]))
cur = self.con.execute("select data from books_data where id=? and "+\
"extension=?",(_id, ext))
try:
data = cur.next()
except:
pass
else:
return decompress(str(data["data"]))
def add_format(self, id, ext, data):
def add_format(self, _id, ext, data):
"""
If data for format ext already exists, it is replaced
@type ext: string or None
@ -107,57 +129,78 @@ class LibraryDatabase(object):
except AttributeError: pass
if ext: ext = ext.strip().lower()
data = sqlite.Binary(compress(data))
cur = self.con.execute("select extension from books_data where id=? and extension=?", (id, ext))
cur = self.con.execute("select extension from books_data where id=? "+\
"and extension=?", (_id, ext))
present = True
try: cur.next()
except: present = False
if present:
self.con.execute("update books_data set data=? where id=? and extension=?", (data, id, ext))
self.con.execute("update books_data set data=? where id=? "+\
"and extension=?", (data, _id, ext))
else:
self.con.execute("insert into books_data (id, extension, data) values (?, ?, ?)", (id, ext, data))
self.con.execute("insert into books_data (id, extension, data) "+\
"values (?, ?, ?)", (_id, ext, data))
self.con.commit()
def get_meta_data(self, id):
try: row = self.con.execute("select * from books_meta where id=?", (id,)).next()
except StopIteration: return None
def get_meta_data(self, _id):
try:
row = self.con.execute("select * from books_meta where id=?", \
(_id,)).next()
except StopIteration:
return None
data = {}
for field in ("id", "title", "authors", "publisher", "size", "tags", "cover", "date"):
for field in ("id", "title", "authors", "publisher", "size", "tags",
"cover", "date"):
data[field] = row[field]
return data
def set_metadata(self, id, title=None, authors=None, rating=None, publisher=None, tags=None, cover=None, comments=None):
if authors and not len(authors): authors = None
if publisher and not len(publisher): publisher = None
if tags and not len(tags): tags = None
if comments and not len(comments): comments = None
if cover: cover = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set title=?, authors=?, publisher=?, tags=?, cover=?, comments=?, rating=? where id=?', (title, authors, publisher, tags, cover, comments, rating, id))
def set_metadata(self, _id, title=None, authors=None, rating=None, \
publisher=None, tags=None, cover=None, \
comments=None):
if authors and not len(authors):
authors = None
if publisher and not len(publisher):
publisher = None
if tags and not len(tags):
tags = None
if comments and not len(comments):
comments = None
if cover:
cover = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set title=?, authors=?, '+\
'publisher=?, tags=?, cover=?, comments=?, rating=? '+\
'where id=?', \
(title, authors, publisher, tags, cover, comments, \
rating, _id))
self.con.commit()
def set_metadata_item(self, id, col, val):
self.con.execute('update books_meta set '+col+'=? where id=?',(val, id))
def set_metadata_item(self, _id, col, val):
self.con.execute('update books_meta set '+col+'=? where id=?', \
(val, _id))
if col in ["authors", "title"]:
lrf = self.get_format(id, "lrf")
lrf = self.get_format(_id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
if col == "authors": lrf.authors = val
if col == "authors":
lrf.authors = val
else: lrf.title = val
self.add_format(id, "lrf", c.getvalue())
self.add_format(_id, "lrf", c.getvalue())
self.con.commit()
def update_cover(self, id, cover):
def update_cover(self, _id, cover):
data = None
if cover: data = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set cover=? where id=?', (data, id))
lrf = self.get_format(id, "lrf")
if cover:
data = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set cover=? where id=?', (data, _id))
lrf = self.get_format(_id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
lrf.thumbnail = cover
self.add_format(id, "lrf", c.getvalue())
self.add_format(_id, "lrf", c.getvalue())
self.commit()

View File

@ -11,40 +11,61 @@
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString, QBuffer, QIODevice, QModelIndex
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog, QIcon
from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical
from PyQt4 import uic
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
""" Create and launch the GUI """
import sys
import re
import os
import traceback
import tempfile
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QSettings, QVariant, QSize, QEventLoop, QString, \
QBuffer, QIODevice, QModelIndex
from PyQt4.QtGui import QPixmap, QErrorMessage, \
QMessageBox, QFileDialog, QIcon, QDialog
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
from libprs500.communicate import PRS500Device as device
from libprs500.books import fix_ids
from libprs500.errors import *
from libprs500.lrf.meta import LRFMetaFile, LRFException
from libprs500.gui import import_ui, installErrorHandler, Error, Warning, extension, APP_TITLE
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, TableView
from libprs500.gui import import_ui, installErrorHandler, Error, _Warning, \
extension, APP_TITLE
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, \
DeviceModel
from database import LibraryDatabase
from editbook import EditBookDialog
import sys, re, os, traceback, tempfile
DEFAULT_BOOK_COVER = None
LIBRARY_BOOK_TEMPLATE = QString("<table><tr><td><b>Formats:</b> %1 </td><td><b>Tags:</b> %2</td></tr><tr><td><b>Comments:</b>%3</td></tr></table>")
DEVICE_BOOK_TEMPLATE = QString("<table><tr><td><b>Title: </b>%1</td><td><b>&nbsp;Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b>&nbsp;Type: </b>%4</td></tr></table>")
LIBRARY_BOOK_TEMPLATE = QString("<table><tr><td><b>Formats:</b> %1 \
</td><td><b>Tags:</b> %2</td></tr> \
<tr><td><b>Comments:</b>%3</td></tr></table>")
DEVICE_BOOK_TEMPLATE = QString("<table><tr><td><b>Title: </b>%1</td><td> \
<b>&nbsp;Size:</b> %2</td></tr>\
<tr><td><b>Author: </b>%3</td>\
<td><b>&nbsp;Type: </b>%4</td></tr></table>")
Ui_MainWindow = import_ui("main.ui")
class MainWindow(QObject, Ui_MainWindow):
class Main(QObject, Ui_MainWindow):
""" Create GUI """
def show_device(self, yes):
""" If C{yes} show the items on the device otherwise show the items in the library """
self.device_view.selectionModel().reset(), self.library_view.selectionModel().reset()
self.book_cover.hide(), self.book_info.hide()
"""
If C{yes} show the items on the device otherwise show the items
in the library
"""
self.device_view.selectionModel().reset()
self.library_view.selectionModel().reset()
self.book_cover.hide()
self.book_info.hide()
if yes:
self.device_view.show(), self.library_view.hide()
self.device_view.show()
self.library_view.hide()
self.book_cover.setAcceptDrops(False)
self.current_view = self.device_view
else:
self.device_view.hide(), self.library_view.show()
self.device_view.hide()
self.library_view.show()
self.book_cover.setAcceptDrops(True)
self.current_view = self.library_view
self.current_view.sortByColumn(3, Qt.DescendingOrder)
@ -59,10 +80,14 @@ class MainWindow(QObject, Ui_MainWindow):
show_dev = False
elif model.is_reader(index):
self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.device_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), \
self.show_book)
elif model.is_card(index):
self.device_view.setModel(self.card_model)
QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.device_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), \
self.show_book)
self.show_device(show_dev)
@ -76,7 +101,8 @@ class MainWindow(QObject, Ui_MainWindow):
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def resize_columns(self, topleft, bottomright):
if self.library_view.isVisible(): view = self.library_view
if self.library_view.isVisible():
view = self.library_view
else: view = self.device_view
for c in range(topleft.column(), bottomright.column()+1):
view.resizeColumnToContents(c)
@ -85,7 +111,8 @@ class MainWindow(QObject, Ui_MainWindow):
if self.library_view.isVisible():
formats, tags, comments, cover = current.model().info(current.row())
data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
tooltip = "To save the cover, drag it to the desktop.<br>To change the cover drag the new cover onto this picture"
tooltip = "To save the cover, drag it to the desktop.<br>To \
change the cover drag the new cover onto this picture"
else:
title, author, size, mime, cover = current.model().info(current.row())
data = DEVICE_BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)
@ -103,13 +130,19 @@ class MainWindow(QObject, Ui_MainWindow):
def delete(self, action):
rows = self.current_view.selectionModel().selectedRows()
if not len(rows): return
if not len(rows):
return
count = str(len(rows))
ret = QMessageBox.question(self.window, self.trUtf8(APP_TITLE + " - confirm"), self.trUtf8("Are you sure you want to <b>permanently delete</b> these ") +count+self.trUtf8(" item(s)?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret != QMessageBox.Yes: return
ret = QMessageBox.question(self.window, self.trUtf8(APP_TITLE + \
" - confirm"), self.trUtf8("Are you sure you want to \
<b>permanently delete</b> these ") +count+self.trUtf8(" item(s)?"), \
QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret != QMessageBox.Yes:
return
self.window.setCursor(Qt.WaitCursor)
if self.library_view.isVisible():
self.library_model.delete(self.library_view.selectionModel().selectedRows())
self.library_model.delete(self.library_view.selectionModel()\
.selectedRows())
else:
self.status("Deleting files from device")
paths = self.device_view.model().delete(rows)
@ -129,9 +162,11 @@ class MainWindow(QObject, Ui_MainWindow):
def read_settings(self):
settings = QSettings()
settings.beginGroup("MainWindow")
self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).toSize())
self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).\
toSize())
settings.endGroup()
self.database_path = settings.value("database path", QVariant(os.path.expanduser("~/library.db"))).toString()
self.database_path = settings.value("database path", QVariant(os.path\
.expanduser("~/library.db"))).toString()
def write_settings(self):
settings = QSettings()
@ -145,8 +180,11 @@ class MainWindow(QObject, Ui_MainWindow):
def add(self, action):
settings = QSettings()
dir = settings.value("add books dialog dir", QVariant(os.path.expanduser("~"))).toString()
files = QFileDialog.getOpenFileNames(self.window, "Choose books to add to library", dir, "Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
_dir = settings.value("add books dialog dir", \
QVariant(os.path.expanduser("~"))).toString()
files = QFileDialog.getOpenFileNames(self.window, \
"Choose books to add to library", _dir, \
"Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
if not files.isEmpty():
x = str(files[0])
settings.setValue("add books dialog dir", QVariant(os.path.dirname(x)))
@ -155,9 +193,9 @@ class MainWindow(QObject, Ui_MainWindow):
def add_books(self, files):
self.window.setCursor(Qt.WaitCursor)
for file in files:
file = os.path.abspath(file)
self.library_view.model().add_book(file)
for _file in files:
_file = os.path.abspath(_file)
self.library_view.model().add_book(_file)
if self.library_view.isVisible(): self.search.clear()
else: self.library_model.search("")
hv = self.library_view.horizontalHeader()
@ -171,9 +209,9 @@ class MainWindow(QObject, Ui_MainWindow):
if self.library_view.isVisible():
rows = self.library_view.selectionModel().selectedRows()
for row in rows:
id = self.library_model.id_from_index(row)
_id = self.library_model.id_from_index(row)
dialog = QDialog(self.window)
ed = EditBookDialog(dialog, id, self.library_model.db)
EditBookDialog(dialog, _id, self.library_model.db)
if dialog.exec_() == QDialog.Accepted:
self.library_model.refresh_row(row.row())
@ -181,7 +219,8 @@ class MainWindow(QObject, Ui_MainWindow):
def update_cover(self, pix):
if not pix.isNull():
try:
self.library_view.model().update_cover(self.library_view.currentIndex(), pix)
self.library_view.model().update_cover(self.library_view\
.currentIndex(), pix)
self.book_cover.setPixmap(pix)
except Exception, e: Error("Unable to change cover", e)
@ -194,7 +233,8 @@ class MainWindow(QObject, Ui_MainWindow):
order = hv.sortIndicatorOrder()
model = self.card_model if oncard else self.reader_model
model.sort(col, order)
if self.device_view.isVisible() and self.device_view.model() == model: self.search.clear()
if self.device_view.isVisible() and self.device_view.model()\
== model: self.search.clear()
else: model.search("")
def sync_lists():
@ -208,61 +248,71 @@ class MainWindow(QObject, Ui_MainWindow):
ename = "file"
try:
if ids:
for id in ids:
for _id in ids:
formats = []
info = self.library_view.model().book_info(id)
info = self.library_view.model().book_info(_id)
if info["cover"]:
pix = QPixmap()
pix.loadFromData(str(info["cover"]))
if pix.isNull(): pix = DEFAULT_BOOK_COVER
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, Qt.SmoothTransformation)
buffer = QBuffer()
buffer.open(QIODevice.WriteOnly)
pix.save(buffer, "JPEG")
info["cover"] = (pix.width(), pix.height(), str(buffer.buffer()))
if pix.isNull():
pix = DEFAULT_BOOK_COVER
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, \
Qt.SmoothTransformation)
_buffer = QBuffer()
_buffer.open(QIODevice.WriteOnly)
pix.save(_buffer, "JPEG")
info["cover"] = (pix.width(), pix.height(), \
str(_buffer.buffer()))
ename = info["title"]
for f in files:
if re.match("......_"+str(id)+"_", os.path.basename(f)):
if re.match("......_"+str(_id)+"_", os.path.basename(f)):
formats.append(f)
file = None
_file = None
try:
for format in self.dev.FORMATS:
for f in formats:
if extension(f) == format:
file = f
_file = f
raise StopIteration()
except StopIteration: pass
if not file:
Error("The library does not have any formats that can be viewed on the device for " + ename, None)
if not _file:
Error("The library does not have any formats that "+\
"can be viewed on the device for " + ename, None)
continue
f = open(file, "rb")
f = open(_file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, "libprs500_"+str(id)+"."+extension(file), info, booklists, oncard=oncard, end_session=False)
self.dev.add_book(f, "libprs500_"+str(_id)+"."+\
extension(_file), info, booklists, oncard=oncard, \
end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + " already exists on the device", None)
Error(info["title"] + \
" already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
else:
for file in files:
ename = file
if extension(file) not in self.dev.FORMATS:
for _file in files:
ename = _file
if extension(_file) not in self.dev.FORMATS:
Error(ename + " is not in a supported format")
continue
info = { "title":os.path.basename(file), "authors":"Unknown", "cover":(None, None, None) }
f = open(file, "rb")
info = { "title":os.path.basename(_file), \
"authors":"Unknown", "cover":(None, None, None) }
f = open(_file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, os.path.basename(file), info, booklists, oncard=oncard, end_session=False)
self.dev.add_book(f, os.path.basename(_file), info, \
booklists, oncard=oncard, end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + " already exists on the device", None)
Error(info["title"] + \
" already exists on the device", None)
self.progress(100)
continue
else: raise
@ -290,21 +340,31 @@ class MainWindow(QObject, Ui_MainWindow):
self.library_model.set_data(LibraryDatabase(str(self.database_path)))
self.library_view.setModel(self.library_model)
self.current_view = self.library_view
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), self.library_view.resizeRowsToContents)
QObject.connect(self.library_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.library_model.search)
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), \
self.library_view.resizeRowsToContents)
QObject.connect(self.library_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), \
self.library_model.search)
QObject.connect(self.library_model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("searched()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("deleted()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
QObject.connect(self.library_view, SIGNAL('books_dropped'), self.add_books)
QObject.connect(self.library_model, SIGNAL('formats_added'), self.formats_added)
QObject.connect(self.library_model, SIGNAL("searched()"), \
self.model_modified)
QObject.connect(self.library_model, SIGNAL("deleted()"), \
self.model_modified)
QObject.connect(self.library_model, \
SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
QObject.connect(self.library_view, \
SIGNAL('books_dropped'), self.add_books)
QObject.connect(self.library_model, \
SIGNAL('formats_added'), self.formats_added)
self.library_view.resizeColumnsToContents()
# Create Device tree
model = DeviceModel(self.device_tree)
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), self.tree_clicked)
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), self.tree_clicked)
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), \
self.tree_clicked)
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), \
self.tree_clicked)
QObject.connect(model, SIGNAL('books_dropped'), self.add_books)
QObject.connect(model, SIGNAL('upload_books'), self.upload_books)
self.device_tree.setModel(model)
@ -313,14 +373,18 @@ class MainWindow(QObject, Ui_MainWindow):
self.reader_model = DeviceBooksModel(window)
self.card_model = DeviceBooksModel(window)
self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.device_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
for model in (self.reader_model, self. card_model):
QObject.connect(model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), model.search)
QObject.connect(model, SIGNAL("layoutChanged()"), \
self.device_view.resizeRowsToContents)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), \
model.search)
QObject.connect(model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(model, SIGNAL("searched()"), self.model_modified)
QObject.connect(model, SIGNAL("deleted()"), self.model_modified)
QObject.connect(model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
QObject.connect(model, SIGNAL("dataChanged(QModelIndex, QModelIndex)")\
, self.resize_columns)
# Setup book display
self.book_cover.hide()
@ -332,10 +396,12 @@ class MainWindow(QObject, Ui_MainWindow):
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit)
# DnD setup
QObject.connect(self.book_cover, SIGNAL("cover_received(QPixmap)"), self.update_cover)
QObject.connect(self.book_cover, SIGNAL("cover_received(QPixmap)"), \
self.update_cover)
self.detector = DeviceConnectDetector(self.dev)
self.connect(self.detector, SIGNAL("device_connected()"), self.establish_connection)
self.connect(self.detector, SIGNAL("device_connected()"), \
self.establish_connection)
self.connect(self.detector, SIGNAL("device_removed()"), self.device_removed)
self.search.setFocus(Qt.OtherFocusReason)
self.show_device(False)
@ -379,8 +445,10 @@ class MainWindow(QObject, Ui_MainWindow):
return
except ProtocolError, e:
traceback.print_exc(e)
qFatal("Unable to connect to device. Please try unplugging and reconnecting it")
self.df.setText(self.df_template.arg("Connected: "+info[0]).arg(info[1]).arg(info[2]))
qFatal("Unable to connect to device. Please try unplugging and"+\
" reconnecting it")
self.df.setText(self.df_template.arg("Connected: "+info[0])\
.arg(info[1]).arg(info[2]))
self.update_availabe_space(end_session=False)
self.card = self.dev.card()
self.is_connected = True
@ -430,7 +498,8 @@ class DeviceConnectDetector(QObject):
devobj = bus.get_object('org.freedesktop.Hal', udi)
dev = dbus.Interface(devobj, "org.freedesktop.Hal.Device")
properties = dev.GetAllProperties()
vendor_id, product_id = int(properties["usb_device.vendor_id"]), int(properties["usb_device.product_id"])
vendor_id = int(properties["usb_device.vendor_id"]),
product_id = int(properties["usb_device.product_id"])
if self.dev.signature() == (vendor_id, product_id): ans = True
except:
self.device_detector = self.startTimer(1000)
@ -451,12 +520,16 @@ class DeviceConnectDetector(QObject):
raise Exception("DBUS doesn't support the Qt mainloop")
import dbus
bus = dbus.SystemBus()
hal_manager_obj = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hal_manager = dbus.Interface(hal_manager_obj, 'org.freedesktop.Hal.Manager')
hal_manager.connect_to_signal('DeviceAdded', self.device_added_callback)
hal_manager.connect_to_signal('DeviceRemoved', self.device_removed_callback)
hal_manager_obj = bus.get_object('org.freedesktop.Hal',\
'/org/freedesktop/Hal/Manager')
hal_manager = dbus.Interface(hal_manager_obj,\
'org.freedesktop.Hal.Manager')
hal_manager.connect_to_signal('DeviceAdded', \
self.device_added_callback)
hal_manager.connect_to_signal('DeviceRemoved', \
self.device_removed_callback)
except Exception, e:
#Warning("Could not connect to HAL", e)
#_Warning("Could not connect to HAL", e)
self.is_connected = False
self.device_detector = self.startTimer(1000)
@ -466,11 +539,13 @@ def main():
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
if os.access(lock, os.F_OK):
print >>sys.stderr, "Another instance of", APP_TITLE, "is running"
print >>sys.stderr, "If you are sure this is not the case then manually delete the file", lock
print >>sys.stderr, "If you are sure this is not the case then "+\
"manually delete the file", lock
sys.exit(1)
parser = OptionParser(usage="usage: %prog [options]", version=VERSION)
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\
"The numbers in the left column are byte offsets that allow the packet size to be read off easily.", \
"The numbers in the left column are byte offsets that allow"+\
" the packet size to be read off easily.", \
dest="log_packets", action="store_true", default=False)
options, args = parser.parse_args()
from PyQt4.Qt import QApplication, QMainWindow
@ -483,7 +558,7 @@ def main():
installErrorHandler(QErrorMessage(window))
QCoreApplication.setOrganizationName("KovidsBrain")
QCoreApplication.setApplicationName(APP_TITLE)
gui = MainWindow(window, options.log_packets)
Main(window, options.log_packets)
lock = LockFile(lock)
return app.exec_()

View File

@ -12,7 +12,12 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import re, os, string, textwrap, time, traceback, sys
import re
import os
import textwrap
import time
import traceback
import sys
from operator import itemgetter, attrgetter
from socket import gethostname
from urlparse import urlparse, urlunparse
@ -20,15 +25,19 @@ from urllib import quote, unquote
from math import sin, cos, pi
from libprs500 import TEMPORARY_FILENAME_TEMPLATE as TFT
from libprs500.lrf.meta import LRFMetaFile
from libprs500.gui import Error, Warning
from libprs500.gui import Error, _Warning
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import Qt, SIGNAL
from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, QVariant, QAbstractTableModel, QTableView, QListView, QLabel,\
QAbstractItemView, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage, QDialog, QSpinBox, QPoint, QTemporaryFile, QDir, QFile, QIODevice,\
QPainterPath, QItemDelegate, QPainter, QPen, QColor, QLinearGradient, QBrush, QStyle,\
QStringList, QByteArray, QBuffer, QMimeData, QTextStream, QIODevice, QDrag, QRect
from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, \
QVariant, QAbstractTableModel, QTableView, QListView, \
QLabel, QAbstractItemView, QPixmap, QIcon, QSize, \
QMessageBox, QSettings, QFileDialog, QErrorMessage, \
QSpinBox, QPoint, QTemporaryFile, QDir, QFile, \
QIODevice, QPainterPath, QItemDelegate, QPainter, QPen, \
QColor, QLinearGradient, QBrush, QStyle, QStringList, \
QByteArray, QBuffer, QMimeData, QTextStream, QIODevice, \
QDrag, QRect
NONE = QVariant() #: Null value to return from the data function of item models
TIME_WRITE_FMT = "%d %b %Y" #: The display format used to show dates
@ -44,6 +53,10 @@ class FileDragAndDrop(object):
@classmethod
def _get_r_ok_files(cls, event):
"""
Return list of paths from event that point to files to
which the user has read permission.
"""
files = []
md = event.mimeData()
if md.hasFormat("text/uri-list"):
@ -51,18 +64,20 @@ class FileDragAndDrop(object):
for url in candidates:
o = urlparse(url)
if o.scheme and o.scheme != 'file':
Warning(o.scheme + " not supported in drop events", None)
_Warning(o.scheme + " not supported in drop events", None)
continue
path = unquote(o.path)
if not os.access(path, os.R_OK):
Warning("You do not have read permission for: " + path)
_Warning("You do not have read permission for: " + path)
continue
if os.path.isdir(path):
root, dirs, files2 = os.walk(path)
for file in files2:
path = root + file
if os.access(path, os.R_OK): files.append(path)
else: files.append(path)
for _file in files2:
path = root + _file
if os.access(path, os.R_OK):
files.append(path)
else:
files.append(path)
return files
def __init__(self, QtBaseClass, enable_drag=True):
@ -79,15 +94,20 @@ class FileDragAndDrop(object):
def mouseMoveEvent(self, event):
self.QtBaseClass.mousePressEvent(self, event)
if self.enable_drag:
if event.buttons() & Qt.LeftButton != Qt.LeftButton: return
if (event.pos() - self._drag_start_position).manhattanLength() < QApplication.startDragDistance(): return
if event.buttons() & Qt.LeftButton != Qt.LeftButton:
return
if (event.pos() - self._drag_start_position).manhattanLength() < \
QApplication.startDragDistance():
return
self.start_drag(self._drag_start_position)
def start_drag(self, pos): pass
def start_drag(self, pos):
raise NotImplementedError()
def dragEnterEvent(self, event):
if event.mimeData().hasFormat("text/uri-list"): event.acceptProposedAction()
if event.mimeData().hasFormat("text/uri-list"):
event.acceptProposedAction()
def dragMoveEvent(self, event):
event.acceptProposedAction()
@ -97,25 +117,29 @@ class FileDragAndDrop(object):
if files:
try:
event.setDropAction(Qt.CopyAction)
if self.files_dropped(files, event): event.accept()
if self.files_dropped(files, event):
event.accept()
except Exception, e:
Error("There was an error processing the dropped files.", e)
raise e
def files_dropped(self, files, event): return False
def files_dropped(self, files, event):
raise NotImplementedError()
def drag_object_from_files(self, files):
if files:
drag = QDrag(self)
mime_data = QMimeData()
self._dragged_files, urls = [], []
for file in files:
urls.append(urlunparse(('file', quote(gethostname()), quote(str(file.name)), '','','')))
self._dragged_files.append(file)
for _file in files:
urls.append(urlunparse(('file', quote(gethostname()), \
quote(str(_file.name)), '','','')))
self._dragged_files.append(_file)
mime_data.setData("text/uri-list", QByteArray("\n".join(urls)))
user = os.getenv('USER')
if user: mime_data.setData("text/x-xdnd-username", QByteArray(user))
if user:
mime_data.setData("text/x-xdnd-username", QByteArray(user))
drag.setMimeData(mime_data)
return drag
@ -135,17 +159,23 @@ class TableView(FileDragAndDrop, QTableView):
QTableView.__init__(self, parent)
@classmethod
def wrap(cls, s, width=20): return textwrap.fill(str(s), width)
def wrap(cls, s, width=20):
return textwrap.fill(str(s), width)
@classmethod
def human_readable(cls, size):
""" Convert a size in bytes into a human readable form """
if size < 1024: divisor, suffix = 1, "B"
elif size < 1024*1024: divisor, suffix = 1024., "KB"
elif size < 1024*1024*1024: divisor, suffix = 1024*1024, "MB"
elif size < 1024*1024*1024*1024: divisor, suffix = 1024*1024, "GB"
if size < 1024:
divisor, suffix = 1, "B"
elif size < 1024*1024:
divisor, suffix = 1024., "KB"
elif size < 1024*1024*1024:
divisor, suffix = 1024*1024, "MB"
elif size < 1024*1024*1024*1024:
divisor, suffix = 1024*1024, "GB"
size = str(size/divisor)
if size.find(".") > -1: size = size[:size.find(".")+2]
if size.find(".") > -1:
size = size[:size.find(".")+2]
return size + " " + suffix
def render_to_pixmap(self, indices):
@ -161,7 +191,8 @@ class TableView(FileDragAndDrop, QTableView):
option = self.viewOptions()
option.state |= QStyle.State_Selected
for j in range(len(indices)):
option.rect = QRect(rects[j].topLeft() - rect.topLeft(), rects[j].size())
option.rect = QRect(rects[j].topLeft() - rect.topLeft(), \
rects[j].size())
self.itemDelegate(indices[j]).paint(painter, option, indices[j])
painter.end()
return pixmap
@ -182,7 +213,8 @@ class TemporaryFile(QTemporaryFile):
def open(self):
ok = QFile.open(self, QIODevice.ReadWrite)
self._file_name = os.path.normpath(os.path.abspath(str(QTemporaryFile.fileName(self))))
self._file_name = os.path.normpath(os.path.abspath(\
str(QTemporaryFile.fileName(self))))
return ok
@apply
@ -202,8 +234,8 @@ class CoverDisplay(FileDragAndDrop, QLabel):
QLabel.__init__(self, parent)
def files_dropped(self, files, event):
pix = QPixmap()
for file in files:
pix = QPixmap(file)
for _file in files:
pix = QPixmap(_file)
if not pix.isNull(): break
if not pix.isNull():
self.emit(SIGNAL("cover_received(QPixmap)"), pix)
@ -212,10 +244,10 @@ class CoverDisplay(FileDragAndDrop, QLabel):
def start_drag(self, event):
drag, files = self.drag_object(["jpeg"])
if drag and files:
file = files[0]
_file = files[0]
drag.setPixmap(self.pixmap())
self.pixmap().save(file)
file.close()
self.pixmap().save(_file)
_file.close()
drag.start(Qt.MoveAction)
class DeviceView(FileDragAndDrop, QListView):
@ -233,7 +265,8 @@ class DeviceView(FileDragAndDrop, QListView):
ids = []
md = event.mimeData()
if md.hasFormat("application/x-libprs500-id"):
ids = [ int(id) for id in FileDragAndDrop._bytes_to_string(md.data("application/x-libprs500-id")).split()]
ids = [ int(id) for id in FileDragAndDrop._bytes_to_string(\
md.data("application/x-libprs500-id")).split()]
index = self.indexAt(event.pos())
if index.isValid():
return self.model().files_dropped(files, index, ids)
@ -264,7 +297,8 @@ class LibraryBooksView(TableView):
drag = self.drag_object_from_files(files)
if drag:
ids = [ str(self.model().id_from_row(row)) for row in rows ]
drag.mimeData().setData("application/x-libprs500-id", QByteArray("\n".join(ids)))
drag.mimeData().setData("application/x-libprs500-id", \
QByteArray("\n".join(ids)))
drag.start()
@ -289,7 +323,8 @@ class LibraryDelegate(QItemDelegate):
self.star_path = QPainterPath()
self.star_path.moveTo(90, 50)
for i in range(1, 5):
self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), 50 + 40 * sin(0.8 * i * pi))
self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
50 + 40 * sin(0.8 * i * pi))
self.star_path.closeSubpath()
self.star_path.setFillRule(Qt.WindingFill)
gradient = QLinearGradient(0, 0, 0, 100)
@ -366,7 +401,8 @@ class LibraryDelegate(QItemDelegate):
class LibraryBooksModel(QAbstractTableModel):
FIELDS = ["id", "title", "authors", "size", "date", "rating", "publisher", "tags", "comments"]
FIELDS = ["id", "title", "authors", "size", "date", "rating", "publisher", \
"tags", "comments"]
TIME_READ_FMT = "%Y-%m-%d %H:%M:%S"
def __init__(self, parent):
QAbstractTableModel.__init__(self, parent)
@ -377,31 +413,36 @@ class LibraryBooksModel(QAbstractTableModel):
def extract_formats(self, rows):
files = []
for row in rows:
id = self.id_from_row(row)
au = self._data[row]["authors"] if self._data[row]["authors"] else "Unknown"
basename = re.sub("\n", "", "_"+str(id)+"_"+self._data[row]["title"]+" by "+ au)
exts = self.db.get_extensions(id)
_id = self.id_from_row(row)
au = self._data[row]["authors"] if self._data[row]["authors"] \
else "Unknown"
basename = re.sub("\n", "", "_"+str(_id)+"_"+\
self._data[row]["title"]+" by "+ au)
exts = self.db.get_extensions(_id)
for ext in exts:
fmt = self.db.get_format(id, ext)
if not ext: ext =""
else: ext = "."+ext
fmt = self.db.get_format(_id, ext)
if not ext:
ext =""
else:
ext = "."+ext
name = basename+ext
file = NamedTemporaryFile(name)
file.open()
if not fmt: continue
if not fmt:
continue
file.write(QByteArray(fmt))
file.close()
files.append(file)
return files
def update_cover(self, index, pix):
id = self.id_from_index(index)
_id = self.id_from_index(index)
qb = QBuffer()
qb.open(QBuffer.ReadWrite);
qb.open(QBuffer.ReadWrite)
pix.save(qb, "JPG")
data = str(qb.data())
qb.close()
self.db.update_cover(id, data)
self.db.update_cover(_id, data)
def add_formats(self, paths, index):
for path in paths:
@ -412,29 +453,41 @@ class LibraryBooksModel(QAbstractTableModel):
f.close()
self.emit(SIGNAL('formats_added'), index)
def rowCount(self, parent): return len(self._data)
def columnCount(self, parent): return len(self.FIELDS)-3
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self.FIELDS)-3
def setData(self, index, value, role):
done = False
if role == Qt.EditRole:
row = index.row()
id = self._data[row]["id"]
_id = self._data[row]["id"]
col = index.column()
val = str(value.toString())
if col == 0: col = "title"
elif col == 1: col = "authors"
elif col == 2: return False
elif col == 3: return False
if col == 0:
col = "title"
elif col == 1:
col = "authors"
elif col == 2:
return False
elif col == 3:
return False
elif col == 4:
col, val = "rating", int(value.toInt()[0])
if val < 0: val =0
if val > 5: val = 5
elif col == 5: col = "publisher"
else: return False
self.db.set_metadata_item(id, col, val)
if val < 0:
val = 0
if val > 5:
val = 5
elif col == 5:
col = "publisher"
else:
return False
self.db.set_metadata_item(_id, col, val)
self._data[row][col] = val
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
index, index)
for i in range(len(self._orig_data)):
if self._orig_data[i]["id"] == self._data[row]["id"]:
self._orig_data[i][col] = self._data[row][col]
@ -445,7 +498,8 @@ class LibraryBooksModel(QAbstractTableModel):
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
if index.isValid():
if index.column() not in [2,3]: flags |= Qt.ItemIsEditable
if index.column() not in [2, 3]:
flags |= Qt.ItemIsEditable
return flags
def set_data(self, db):
@ -487,17 +541,19 @@ class LibraryBooksModel(QAbstractTableModel):
def id_from_row(self, row): return self._data[row]["id"]
def refresh_row(self, row):
self._data[row] = self.db.get_row_by_id(self._data[row]["id"], self.FIELDS)
self._data[row] = self.db.get_row_by_id(self._data[row]["id"], \
self.FIELDS)
for i in range(len(self._orig_data)):
if self._orig_data[i]["id"] == self._data[row]["id"]:
self._orig_data[i:i+1] = self._data[row]
break
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(row, 0), self.index(row, self.columnCount(0)-1))
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
self.index(row, 0), self.index(row, self.columnCount(0)-1))
def book_info(self, id):
def book_info(self, _id):
""" Return title, authors and cover in a dict """
cover = self.db.get_cover(id)
info = self.db.get_row_by_id(id, ["title", "authors"])
cover = self.db.get_cover(_id)
info = self.db.get_row_by_id(_id, ["title", "authors"])
info["cover"] = cover
return info
@ -508,19 +564,27 @@ class LibraryBooksModel(QAbstractTableModel):
row = self._data[row]
if col == 4:
r = row["rating"] if row["rating"] else 0
if r < 0: r= 0
if r > 5: r=5
if r < 0:
r = 0
if r > 5:
r = 5
return QVariant(r)
if col == 0: text = TableView.wrap(row["title"], width=25)
if col == 0:
text = TableView.wrap(row["title"], width=25)
elif col == 1:
au = row["authors"]
if au : text = TableView.wrap(re.sub("&", "\n", au), width=25)
elif col == 2: text = TableView.human_readable(row["size"])
elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(row["date"], self.TIME_READ_FMT))
if au:
text = TableView.wrap(re.sub("&", "\n", au), width=25)
elif col == 2:
text = TableView.human_readable(row["size"])
elif col == 3:
text = time.strftime(TIME_WRITE_FMT, \
time.strptime(row["date"], self.TIME_READ_FMT))
elif col == 5:
pub = row["publisher"]
if pub: text = TableView.wrap(pub, 20)
if text == None: text = "Unknown"
if text == None:
text = "Unknown"
return QVariant(text)
elif role == Qt.TextAlignmentRole and index.column() in [2,3,4]:
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
@ -528,11 +592,14 @@ class LibraryBooksModel(QAbstractTableModel):
def sort(self, col, order):
descending = order != Qt.AscendingOrder
def getter(key, func): return lambda x : func(itemgetter(key)(x))
if col == 0: key, func = "title", string.lower
if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower() if x else ""
def getter(key, func):
return lambda x : func(itemgetter(key)(x))
if col == 0: key, func = "title", lambda x : x.lower()
if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower()\
if x else ""
if col == 2: key, func = "size", int
if col == 3: key, func = "date", lambda x: time.mktime(time.strptime(x, self.TIME_READ_FMT))
if col == 3: key, func = "date", lambda x: time.mktime(\
time.strptime(x, self.TIME_READ_FMT))
if col == 4: key, func = "rating", lambda x: x if x else 0
if col == 5: key, func = "publisher", lambda x : x.lower() if x else ""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
@ -547,7 +614,8 @@ class LibraryBooksModel(QAbstractTableModel):
if not au : au = "unknown"
pub = book["publisher"]
if not pub : pub = "unknown"
return q in book["title"].lower() or q in au.lower() or q in pub.lower()
return q in book["title"].lower() or q in au.lower() or \
q in pub.lower()
queries = unicode(query, 'utf-8').lower().split()
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data = []
@ -566,21 +634,21 @@ class LibraryBooksModel(QAbstractTableModel):
if len(indices): self.emit(SIGNAL("layoutAboutToBeChanged()"))
items = [ self._data[index.row()] for index in indices ]
for item in items:
id = item["id"]
_id = item["id"]
try:
self._data.remove(item)
except ValueError: continue
self.db.delete_by_id(id)
self.db.delete_by_id(_id)
for x in self._orig_data:
if x["id"] == id: self._orig_data.remove(x)
if x["id"] == _id: self._orig_data.remove(x)
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("deleted()"))
self.db.commit()
def add_book(self, path):
""" Must call search and sort on this models view after this """
id = self.db.add_book(path)
self._orig_data.append(self.db.get_row_by_id(id, self.FIELDS))
_id = self.db.add_book(path)
self._orig_data.append(self.db.get_row_by_id(_id, self.FIELDS))
class DeviceBooksModel(QAbstractTableModel):
@apply
@ -588,7 +656,7 @@ class DeviceBooksModel(QAbstractTableModel):
doc = """ The booklist this model is based on """
def fget(self):
return self._orig_data
return property(**locals())
return property(doc=doc, fget=fget)
def __init__(self, parent):
QAbstractTableModel.__init__(self, parent)
@ -619,10 +687,14 @@ class DeviceBooksModel(QAbstractTableModel):
if role == Qt.DisplayRole:
row, col = index.row(), index.column()
book = self._data[row]
if col == 0: text = TableView.wrap(book.title, width=40)
elif col == 1: text = re.sub("&\s*","\n", book.author)
elif col == 2: text = TableView.human_readable(book.size)
elif col == 3: text = time.strftime(TIME_WRITE_FMT, book.datetime)
if col == 0:
text = TableView.wrap(book.title, width=40)
elif col == 1:
text = re.sub("&\s*", "\n", book.author)
elif col == 2:
text = TableView.human_readable(book.size)
elif col == 3:
text = time.strftime(TIME_WRITE_FMT, book.datetime)
return QVariant(text)
elif role == Qt.TextAlignmentRole and index.column() in [2,3]:
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
@ -642,8 +714,9 @@ class DeviceBooksModel(QAbstractTableModel):
return row.title, au, TableView.human_readable(row.size), row.mime, cover
def sort(self, col, order):
def getter(key, func): return lambda x : func(attrgetter(key)(x))
if col == 0: key, func = "title", string.lower
def getter(key, func):
return lambda x : func(attrgetter(key)(x))
if col == 0: key, func = "title", lambda x : x.lower()
if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower()
if col == 2: key, func = "size", int
if col == 3: key, func = "datetime", lambda x: x
@ -672,20 +745,25 @@ class DeviceBooksModel(QAbstractTableModel):
def delete(self, indices):
paths = []
rows = [ index.row() for index in indices ]
if not rows: return
if not rows:
return
self.emit(SIGNAL("layoutAboutToBeChanged()"))
elems = [ self._data[row] for row in rows ]
for e in elems:
id = e.id
_id = e.id
paths.append(e.path)
self._orig_data.delete_book(id)
try: self._data.remove(e)
except ValueError: pass
self._orig_data.delete_book(_id)
try:
self._data.remove(e)
except ValueError:
pass
self.emit(SIGNAL("layoutChanged()"))
return paths
def path(self, index): return self._data[index.row()].path
def title(self, index): return self._data[index.row()].title
def path(self, index):
return self._data[index.row()].path
def title(self, index):
return self._data[index.row()].title
@ -699,45 +777,61 @@ class DeviceModel(QAbstractListModel):
show_card = False
def update_devices(self, reader=None, card=None):
if reader != None: self.show_reader = reader
if card != None: self.show_card = card
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(1), self.index(2))
if reader != None:
self.show_reader = reader
if card != None:
self.show_card = card
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
self.index(1), self.index(2))
def rowCount(self, parent): return 3
def update_free_space(self, reader, card):
self.memory_free = reader
self.card_free = card
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(1), self.index(2))
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
self.index(1), self.index(2))
def data(self, index, role):
row = index.row()
data = NONE
if role == Qt.DisplayRole:
text = None
if row == 0: text = "Library"
if row == 0:
text = "Library"
if row == 1 and self.show_reader:
text = "Reader\n" + TableView.human_readable(self.memory_free) + " available"
text = "Reader\n" + TableView.human_readable(self.memory_free) \
+ " available"
elif row == 2 and self.show_card:
text = "Card\n" + TableView.human_readable(self.card_free) + " available"
if text: data = QVariant(text)
text = "Card\n" + TableView.human_readable(self.card_free) \
+ " available"
if text:
data = QVariant(text)
elif role == Qt.DecorationRole:
icon = None
if row == 0: icon = QIcon(":/library")
elif row == 1 and self.show_reader: icon = QIcon(":/reader")
elif self.show_card: icon = QIcon(":/card")
if icon: data = QVariant(icon)
if row == 0:
icon = QIcon(":/library")
elif row == 1 and self.show_reader:
icon = QIcon(":/reader")
elif self.show_card:
icon = QIcon(":/card")
if icon:
data = QVariant(icon)
elif role == Qt.SizeHintRole:
if row == 1: return QVariant(QSize(150, 70))
if row == 1:
return QVariant(QSize(150, 70))
elif role == Qt.FontRole:
font = QFont()
font.setBold(True)
data = QVariant(font)
return data
def is_library(self, index): return index.row() == 0
def is_reader(self, index): return index.row() == 1
def is_card(self, index): return index.row() == 2
def is_library(self, index):
return index.row() == 0
def is_reader(self, index):
return index.row() == 1
def is_card(self, index):
return index.row() == 2
def files_dropped(self, files, index, ids):
ret = False

View File

@ -89,5 +89,6 @@ try:
except ImportError:
print "You do not have PyQt4 installed. The GUI will not work. You can obtain PyQt4 from http://www.riverbankcomputing.co.uk/pyqt/download.php"
else:
import PyQt4.Qt
if PyQt4.Qt.PYQT_VERSION < 0x40101:
print "WARNING: The GUI needs PyQt >= 4.1.1"