mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Raising the pylint score of libprs500/gui/*.py
This commit is contained in:
parent
97fe142281
commit
18111d2d50
@ -12,6 +12,7 @@
|
|||||||
## You should have received a copy of the GNU General Public License along
|
## 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.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
""" The GUI to libprs500. Also has ebook library management features. """
|
||||||
__docformat__ = "epytext"
|
__docformat__ = "epytext"
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
APP_TITLE = "libprs500"
|
APP_TITLE = "libprs500"
|
||||||
@ -33,7 +34,7 @@ def installErrorHandler(dialog):
|
|||||||
error_dialog.setModal(True)
|
error_dialog.setModal(True)
|
||||||
|
|
||||||
|
|
||||||
def Warning(msg, e):
|
def _Warning(msg, e):
|
||||||
print >> sys.stderr, msg
|
print >> sys.stderr, msg
|
||||||
if e: traceback.print_exc(e)
|
if e: traceback.print_exc(e)
|
||||||
|
|
||||||
@ -53,4 +54,6 @@ def import_ui(name):
|
|||||||
exec code_string.getvalue()
|
exec code_string.getvalue()
|
||||||
return locals()[winfo["uiclass"]]
|
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
|
||||||
|
@ -22,57 +22,72 @@ from cStringIO import StringIO as cStringIO
|
|||||||
class LibraryDatabase(object):
|
class LibraryDatabase(object):
|
||||||
|
|
||||||
BOOKS_SQL = """
|
BOOKS_SQL = """
|
||||||
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT, authors TEXT, publisher TEXT, size INTEGER, tags TEXT,
|
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT,
|
||||||
cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP, comments TEXT, rating INTEGER);
|
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);
|
create table if not exists books_data(id INTEGER, extension TEXT, data BLOB);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, dbpath):
|
def __init__(self, dbpath):
|
||||||
self.con = sqlite.connect(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)
|
self.con.executescript(LibraryDatabase.BOOKS_SQL)
|
||||||
|
|
||||||
def get_cover(self, id):
|
def get_cover(self, _id):
|
||||||
raw = self.con.execute("select cover from books_meta where id=?", (id,)).next()["cover"]
|
raw = self.con.execute("select cover from books_meta where id=?", (_id,))\
|
||||||
|
.next()["cover"]
|
||||||
return decompress(str(raw)) if raw else None
|
return decompress(str(raw)) if raw else None
|
||||||
|
|
||||||
def get_extensions(self, id):
|
def get_extensions(self, _id):
|
||||||
exts = []
|
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:
|
for row in cur:
|
||||||
exts.append(row["extension"])
|
exts.append(row["extension"])
|
||||||
return exts
|
return exts
|
||||||
|
|
||||||
def add_book(self, path):
|
def add_book(self, path):
|
||||||
file = os.path.abspath(path)
|
_file = os.path.abspath(path)
|
||||||
title, author, publisher, size, cover = os.path.basename(file), None, None, os.stat(file)[ST_SIZE], None
|
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
|
ext = title[title.rfind(".")+1:].lower() if title.find(".") > -1 else None
|
||||||
if ext == "lrf":
|
if ext == "lrf":
|
||||||
lrf = LRFMetaFile(open(file, "r+b"))
|
lrf = LRFMetaFile(open(_file, "r+b"))
|
||||||
title, author, cover, publisher = lrf.title, lrf.author.strip(), lrf.thumbnail, lrf.publisher.strip()
|
title, author, cover, publisher = lrf.title, lrf.author.strip(), \
|
||||||
if "unknown" in publisher.lower(): publisher = None
|
lrf.thumbnail, lrf.publisher.strip()
|
||||||
if "unknown" in author.lower(): author = None
|
if "unknown" in publisher.lower():
|
||||||
file = compress(open(file).read())
|
publisher = None
|
||||||
if cover: cover = sqlite.Binary(compress(cover))
|
if "unknown" in author.lower():
|
||||||
self.con.execute("insert into books_meta (title, authors, publisher, size, tags, cover, comments, rating) values (?,?,?,?,?,?,?,?)", (title, author, publisher, size, None, cover, None, None))
|
author = None
|
||||||
id = self.con.execute("select max(id) from books_meta").next()[0]
|
_file = compress(open(_file).read())
|
||||||
self.con.execute("insert into books_data values (?,?,?)", (id, ext, sqlite.Binary(file)))
|
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()
|
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 """
|
""" @param columns: list of column names """
|
||||||
cols = ",".join([ c for c in columns])
|
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(), {}
|
row, r = cur.next(), {}
|
||||||
for c in columns: r[c] = row[c]
|
for c in columns:
|
||||||
|
r[c] = row[c]
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def commit(self): self.con.commit()
|
def commit(self): self.con.commit()
|
||||||
|
|
||||||
def delete_by_id(self, id):
|
def delete_by_id(self, _id):
|
||||||
self.con.execute("delete from books_meta where id=?", (id,))
|
self.con.execute("delete from books_meta where id=?", (_id,))
|
||||||
self.con.execute("delete from books_data where id=?", (id,))
|
self.con.execute("delete from books_data where id=?", (_id,))
|
||||||
|
|
||||||
def get_table(self, columns):
|
def get_table(self, columns):
|
||||||
cols = ",".join([ c for c in columns])
|
cols = ",".join([ c for c in columns])
|
||||||
@ -80,22 +95,29 @@ class LibraryDatabase(object):
|
|||||||
rows = []
|
rows = []
|
||||||
for row in cur:
|
for row in cur:
|
||||||
r = {}
|
r = {}
|
||||||
for c in columns: r[c] = row[c]
|
for c in columns:
|
||||||
|
r[c] = row[c]
|
||||||
rows.append(r)
|
rows.append(r)
|
||||||
return rows
|
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.
|
Return format C{ext} corresponding to the logical book C{id} or
|
||||||
Format is returned as a string of binary data suitable for C{ file.write} operations.
|
None if the format is unavailable.
|
||||||
|
Format is returned as a string of binary data suitable for
|
||||||
|
C{ file.write} operations.
|
||||||
"""
|
"""
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
cur = self.con.execute("select data from books_data where id=? and extension=?",(id, ext))
|
cur = self.con.execute("select data from books_data where id=? and "+\
|
||||||
try: data = cur.next()
|
"extension=?",(_id, ext))
|
||||||
except: pass
|
try:
|
||||||
else: return decompress(str(data["data"]))
|
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
|
If data for format ext already exists, it is replaced
|
||||||
@type ext: string or None
|
@type ext: string or None
|
||||||
@ -107,57 +129,78 @@ class LibraryDatabase(object):
|
|||||||
except AttributeError: pass
|
except AttributeError: pass
|
||||||
if ext: ext = ext.strip().lower()
|
if ext: ext = ext.strip().lower()
|
||||||
data = sqlite.Binary(compress(data))
|
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
|
present = True
|
||||||
try: cur.next()
|
try: cur.next()
|
||||||
except: present = False
|
except: present = False
|
||||||
if present:
|
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:
|
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()
|
self.con.commit()
|
||||||
|
|
||||||
def get_meta_data(self, id):
|
def get_meta_data(self, _id):
|
||||||
try: row = self.con.execute("select * from books_meta where id=?", (id,)).next()
|
try:
|
||||||
except StopIteration: return None
|
row = self.con.execute("select * from books_meta where id=?", \
|
||||||
|
(_id,)).next()
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
data = {}
|
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]
|
data[field] = row[field]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def set_metadata(self, id, title=None, authors=None, rating=None, publisher=None, tags=None, cover=None, comments=None):
|
def set_metadata(self, _id, title=None, authors=None, rating=None, \
|
||||||
if authors and not len(authors): authors = None
|
publisher=None, tags=None, cover=None, \
|
||||||
if publisher and not len(publisher): publisher = None
|
comments=None):
|
||||||
if tags and not len(tags): tags = None
|
if authors and not len(authors):
|
||||||
if comments and not len(comments): comments = None
|
authors = None
|
||||||
if cover: cover = sqlite.Binary(compress(cover))
|
if publisher and not len(publisher):
|
||||||
self.con.execute('update books_meta set title=?, authors=?, publisher=?, tags=?, cover=?, comments=?, rating=? where id=?', (title, authors, publisher, tags, cover, comments, rating, id))
|
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()
|
self.con.commit()
|
||||||
|
|
||||||
def set_metadata_item(self, id, col, val):
|
def set_metadata_item(self, _id, col, val):
|
||||||
self.con.execute('update books_meta set '+col+'=? where id=?',(val, id))
|
self.con.execute('update books_meta set '+col+'=? where id=?', \
|
||||||
|
(val, _id))
|
||||||
if col in ["authors", "title"]:
|
if col in ["authors", "title"]:
|
||||||
lrf = self.get_format(id, "lrf")
|
lrf = self.get_format(_id, "lrf")
|
||||||
if lrf:
|
if lrf:
|
||||||
c = cStringIO()
|
c = cStringIO()
|
||||||
c.write(lrf)
|
c.write(lrf)
|
||||||
lrf = LRFMetaFile(c)
|
lrf = LRFMetaFile(c)
|
||||||
if col == "authors": lrf.authors = val
|
if col == "authors":
|
||||||
|
lrf.authors = val
|
||||||
else: lrf.title = val
|
else: lrf.title = val
|
||||||
self.add_format(id, "lrf", c.getvalue())
|
self.add_format(_id, "lrf", c.getvalue())
|
||||||
self.con.commit()
|
self.con.commit()
|
||||||
|
|
||||||
def update_cover(self, id, cover):
|
def update_cover(self, _id, cover):
|
||||||
data = None
|
data = None
|
||||||
if cover: data = sqlite.Binary(compress(cover))
|
if cover:
|
||||||
self.con.execute('update books_meta set cover=? where id=?', (data, id))
|
data = sqlite.Binary(compress(cover))
|
||||||
lrf = self.get_format(id, "lrf")
|
self.con.execute('update books_meta set cover=? where id=?', (data, _id))
|
||||||
|
lrf = self.get_format(_id, "lrf")
|
||||||
if lrf:
|
if lrf:
|
||||||
c = cStringIO()
|
c = cStringIO()
|
||||||
c.write(lrf)
|
c.write(lrf)
|
||||||
lrf = LRFMetaFile(c)
|
lrf = LRFMetaFile(c)
|
||||||
lrf.thumbnail = cover
|
lrf.thumbnail = cover
|
||||||
self.add_format(id, "lrf", c.getvalue())
|
self.add_format(_id, "lrf", c.getvalue())
|
||||||
self.commit()
|
self.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,40 +11,61 @@
|
|||||||
##
|
##
|
||||||
## You should have received a copy of the GNU General Public License along
|
## 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.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
|
||||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString, QBuffer, QIODevice, QModelIndex
|
""" Create and launch the GUI """
|
||||||
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog, QIcon
|
import sys
|
||||||
from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical
|
import re
|
||||||
from PyQt4 import uic
|
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.communicate import PRS500Device as device
|
||||||
from libprs500.books import fix_ids
|
from libprs500.books import fix_ids
|
||||||
from libprs500.errors import *
|
from libprs500.errors import *
|
||||||
from libprs500.lrf.meta import LRFMetaFile, LRFException
|
from libprs500.gui import import_ui, installErrorHandler, Error, _Warning, \
|
||||||
from libprs500.gui import import_ui, installErrorHandler, Error, Warning, extension, APP_TITLE
|
extension, APP_TITLE
|
||||||
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, TableView
|
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, \
|
||||||
|
DeviceModel
|
||||||
from database import LibraryDatabase
|
from database import LibraryDatabase
|
||||||
from editbook import EditBookDialog
|
from editbook import EditBookDialog
|
||||||
|
|
||||||
import sys, re, os, traceback, tempfile
|
|
||||||
|
|
||||||
DEFAULT_BOOK_COVER = None
|
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>")
|
LIBRARY_BOOK_TEMPLATE = QString("<table><tr><td><b>Formats:</b> %1 \
|
||||||
DEVICE_BOOK_TEMPLATE = QString("<table><tr><td><b>Title: </b>%1</td><td><b> Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b> Type: </b>%4</td></tr></table>")
|
</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> Size:</b> %2</td></tr>\
|
||||||
|
<tr><td><b>Author: </b>%3</td>\
|
||||||
|
<td><b> Type: </b>%4</td></tr></table>")
|
||||||
|
|
||||||
Ui_MainWindow = import_ui("main.ui")
|
Ui_MainWindow = import_ui("main.ui")
|
||||||
class MainWindow(QObject, Ui_MainWindow):
|
class Main(QObject, Ui_MainWindow):
|
||||||
|
""" Create GUI """
|
||||||
def show_device(self, yes):
|
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()
|
If C{yes} show the items on the device otherwise show the items
|
||||||
self.book_cover.hide(), self.book_info.hide()
|
in the library
|
||||||
|
"""
|
||||||
|
self.device_view.selectionModel().reset()
|
||||||
|
self.library_view.selectionModel().reset()
|
||||||
|
self.book_cover.hide()
|
||||||
|
self.book_info.hide()
|
||||||
if yes:
|
if yes:
|
||||||
self.device_view.show(), self.library_view.hide()
|
self.device_view.show()
|
||||||
|
self.library_view.hide()
|
||||||
self.book_cover.setAcceptDrops(False)
|
self.book_cover.setAcceptDrops(False)
|
||||||
self.current_view = self.device_view
|
self.current_view = self.device_view
|
||||||
else:
|
else:
|
||||||
self.device_view.hide(), self.library_view.show()
|
self.device_view.hide()
|
||||||
|
self.library_view.show()
|
||||||
self.book_cover.setAcceptDrops(True)
|
self.book_cover.setAcceptDrops(True)
|
||||||
self.current_view = self.library_view
|
self.current_view = self.library_view
|
||||||
self.current_view.sortByColumn(3, Qt.DescendingOrder)
|
self.current_view.sortByColumn(3, Qt.DescendingOrder)
|
||||||
@ -59,10 +80,14 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
show_dev = False
|
show_dev = False
|
||||||
elif model.is_reader(index):
|
elif model.is_reader(index):
|
||||||
self.device_view.setModel(self.reader_model)
|
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):
|
elif model.is_card(index):
|
||||||
self.device_view.setModel(self.card_model)
|
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)
|
self.show_device(show_dev)
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +101,8 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
|
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
|
||||||
|
|
||||||
def resize_columns(self, topleft, bottomright):
|
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
|
else: view = self.device_view
|
||||||
for c in range(topleft.column(), bottomright.column()+1):
|
for c in range(topleft.column(), bottomright.column()+1):
|
||||||
view.resizeColumnToContents(c)
|
view.resizeColumnToContents(c)
|
||||||
@ -85,7 +111,8 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
if self.library_view.isVisible():
|
if self.library_view.isVisible():
|
||||||
formats, tags, comments, cover = current.model().info(current.row())
|
formats, tags, comments, cover = current.model().info(current.row())
|
||||||
data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
|
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:
|
else:
|
||||||
title, author, size, mime, cover = current.model().info(current.row())
|
title, author, size, mime, cover = current.model().info(current.row())
|
||||||
data = DEVICE_BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)
|
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):
|
def delete(self, action):
|
||||||
rows = self.current_view.selectionModel().selectedRows()
|
rows = self.current_view.selectionModel().selectedRows()
|
||||||
if not len(rows): return
|
if not len(rows):
|
||||||
|
return
|
||||||
count = str(len(rows))
|
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)
|
ret = QMessageBox.question(self.window, self.trUtf8(APP_TITLE + \
|
||||||
if ret != QMessageBox.Yes: return
|
" - 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)
|
self.window.setCursor(Qt.WaitCursor)
|
||||||
if self.library_view.isVisible():
|
if self.library_view.isVisible():
|
||||||
self.library_model.delete(self.library_view.selectionModel().selectedRows())
|
self.library_model.delete(self.library_view.selectionModel()\
|
||||||
|
.selectedRows())
|
||||||
else:
|
else:
|
||||||
self.status("Deleting files from device")
|
self.status("Deleting files from device")
|
||||||
paths = self.device_view.model().delete(rows)
|
paths = self.device_view.model().delete(rows)
|
||||||
@ -129,9 +162,11 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
settings = QSettings()
|
settings = QSettings()
|
||||||
settings.beginGroup("MainWindow")
|
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()
|
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):
|
def write_settings(self):
|
||||||
settings = QSettings()
|
settings = QSettings()
|
||||||
@ -145,8 +180,11 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
|
|
||||||
def add(self, action):
|
def add(self, action):
|
||||||
settings = QSettings()
|
settings = QSettings()
|
||||||
dir = settings.value("add books dialog dir", QVariant(os.path.expanduser("~"))).toString()
|
_dir = settings.value("add books dialog dir", \
|
||||||
files = QFileDialog.getOpenFileNames(self.window, "Choose books to add to library", dir, "Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
|
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():
|
if not files.isEmpty():
|
||||||
x = str(files[0])
|
x = str(files[0])
|
||||||
settings.setValue("add books dialog dir", QVariant(os.path.dirname(x)))
|
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):
|
def add_books(self, files):
|
||||||
self.window.setCursor(Qt.WaitCursor)
|
self.window.setCursor(Qt.WaitCursor)
|
||||||
for file in files:
|
for _file in files:
|
||||||
file = os.path.abspath(file)
|
_file = os.path.abspath(_file)
|
||||||
self.library_view.model().add_book(file)
|
self.library_view.model().add_book(_file)
|
||||||
if self.library_view.isVisible(): self.search.clear()
|
if self.library_view.isVisible(): self.search.clear()
|
||||||
else: self.library_model.search("")
|
else: self.library_model.search("")
|
||||||
hv = self.library_view.horizontalHeader()
|
hv = self.library_view.horizontalHeader()
|
||||||
@ -171,9 +209,9 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
if self.library_view.isVisible():
|
if self.library_view.isVisible():
|
||||||
rows = self.library_view.selectionModel().selectedRows()
|
rows = self.library_view.selectionModel().selectedRows()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
id = self.library_model.id_from_index(row)
|
_id = self.library_model.id_from_index(row)
|
||||||
dialog = QDialog(self.window)
|
dialog = QDialog(self.window)
|
||||||
ed = EditBookDialog(dialog, id, self.library_model.db)
|
EditBookDialog(dialog, _id, self.library_model.db)
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec_() == QDialog.Accepted:
|
||||||
self.library_model.refresh_row(row.row())
|
self.library_model.refresh_row(row.row())
|
||||||
|
|
||||||
@ -181,7 +219,8 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
def update_cover(self, pix):
|
def update_cover(self, pix):
|
||||||
if not pix.isNull():
|
if not pix.isNull():
|
||||||
try:
|
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)
|
self.book_cover.setPixmap(pix)
|
||||||
except Exception, e: Error("Unable to change cover", e)
|
except Exception, e: Error("Unable to change cover", e)
|
||||||
|
|
||||||
@ -194,7 +233,8 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
order = hv.sortIndicatorOrder()
|
order = hv.sortIndicatorOrder()
|
||||||
model = self.card_model if oncard else self.reader_model
|
model = self.card_model if oncard else self.reader_model
|
||||||
model.sort(col, order)
|
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("")
|
else: model.search("")
|
||||||
|
|
||||||
def sync_lists():
|
def sync_lists():
|
||||||
@ -208,61 +248,71 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
ename = "file"
|
ename = "file"
|
||||||
try:
|
try:
|
||||||
if ids:
|
if ids:
|
||||||
for id in ids:
|
for _id in ids:
|
||||||
formats = []
|
formats = []
|
||||||
info = self.library_view.model().book_info(id)
|
info = self.library_view.model().book_info(_id)
|
||||||
if info["cover"]:
|
if info["cover"]:
|
||||||
pix = QPixmap()
|
pix = QPixmap()
|
||||||
pix.loadFromData(str(info["cover"]))
|
pix.loadFromData(str(info["cover"]))
|
||||||
if pix.isNull(): pix = DEFAULT_BOOK_COVER
|
if pix.isNull():
|
||||||
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, Qt.SmoothTransformation)
|
pix = DEFAULT_BOOK_COVER
|
||||||
buffer = QBuffer()
|
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, \
|
||||||
buffer.open(QIODevice.WriteOnly)
|
Qt.SmoothTransformation)
|
||||||
pix.save(buffer, "JPEG")
|
_buffer = QBuffer()
|
||||||
info["cover"] = (pix.width(), pix.height(), str(buffer.buffer()))
|
_buffer.open(QIODevice.WriteOnly)
|
||||||
|
pix.save(_buffer, "JPEG")
|
||||||
|
info["cover"] = (pix.width(), pix.height(), \
|
||||||
|
str(_buffer.buffer()))
|
||||||
ename = info["title"]
|
ename = info["title"]
|
||||||
for f in files:
|
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)
|
formats.append(f)
|
||||||
file = None
|
_file = None
|
||||||
try:
|
try:
|
||||||
for format in self.dev.FORMATS:
|
for format in self.dev.FORMATS:
|
||||||
for f in formats:
|
for f in formats:
|
||||||
if extension(f) == format:
|
if extension(f) == format:
|
||||||
file = f
|
_file = f
|
||||||
raise StopIteration()
|
raise StopIteration()
|
||||||
except StopIteration: pass
|
except StopIteration: pass
|
||||||
if not file:
|
if not _file:
|
||||||
Error("The library does not have any formats that can be viewed on the device for " + ename, None)
|
Error("The library does not have any formats that "+\
|
||||||
|
"can be viewed on the device for " + ename, None)
|
||||||
continue
|
continue
|
||||||
f = open(file, "rb")
|
f = open(_file, "rb")
|
||||||
self.status("Sending "+info["title"]+" to device")
|
self.status("Sending "+info["title"]+" to device")
|
||||||
try:
|
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()
|
update_models()
|
||||||
except PathError, e:
|
except PathError, e:
|
||||||
if "already exists" in str(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)
|
self.progress(100)
|
||||||
continue
|
continue
|
||||||
else: raise
|
else: raise
|
||||||
finally: f.close()
|
finally: f.close()
|
||||||
sync_lists()
|
sync_lists()
|
||||||
else:
|
else:
|
||||||
for file in files:
|
for _file in files:
|
||||||
ename = file
|
ename = _file
|
||||||
if extension(file) not in self.dev.FORMATS:
|
if extension(_file) not in self.dev.FORMATS:
|
||||||
Error(ename + " is not in a supported format")
|
Error(ename + " is not in a supported format")
|
||||||
continue
|
continue
|
||||||
info = { "title":os.path.basename(file), "authors":"Unknown", "cover":(None, None, None) }
|
info = { "title":os.path.basename(_file), \
|
||||||
f = open(file, "rb")
|
"authors":"Unknown", "cover":(None, None, None) }
|
||||||
|
f = open(_file, "rb")
|
||||||
self.status("Sending "+info["title"]+" to device")
|
self.status("Sending "+info["title"]+" to device")
|
||||||
try:
|
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()
|
update_models()
|
||||||
except PathError, e:
|
except PathError, e:
|
||||||
if "already exists" in str(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)
|
self.progress(100)
|
||||||
continue
|
continue
|
||||||
else: raise
|
else: raise
|
||||||
@ -290,21 +340,31 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
self.library_model.set_data(LibraryDatabase(str(self.database_path)))
|
self.library_model.set_data(LibraryDatabase(str(self.database_path)))
|
||||||
self.library_view.setModel(self.library_model)
|
self.library_view.setModel(self.library_model)
|
||||||
self.current_view = self.library_view
|
self.current_view = self.library_view
|
||||||
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), self.library_view.resizeRowsToContents)
|
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), \
|
||||||
QObject.connect(self.library_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
|
self.library_view.resizeRowsToContents)
|
||||||
QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.library_model.search)
|
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("sorted()"), self.model_modified)
|
||||||
QObject.connect(self.library_model, SIGNAL("searched()"), self.model_modified)
|
QObject.connect(self.library_model, SIGNAL("searched()"), \
|
||||||
QObject.connect(self.library_model, SIGNAL("deleted()"), self.model_modified)
|
self.model_modified)
|
||||||
QObject.connect(self.library_model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
|
QObject.connect(self.library_model, SIGNAL("deleted()"), \
|
||||||
QObject.connect(self.library_view, SIGNAL('books_dropped'), self.add_books)
|
self.model_modified)
|
||||||
QObject.connect(self.library_model, SIGNAL('formats_added'), self.formats_added)
|
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()
|
self.library_view.resizeColumnsToContents()
|
||||||
|
|
||||||
# Create Device tree
|
# Create Device tree
|
||||||
model = DeviceModel(self.device_tree)
|
model = DeviceModel(self.device_tree)
|
||||||
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), self.tree_clicked)
|
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), \
|
||||||
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), self.tree_clicked)
|
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('books_dropped'), self.add_books)
|
||||||
QObject.connect(model, SIGNAL('upload_books'), self.upload_books)
|
QObject.connect(model, SIGNAL('upload_books'), self.upload_books)
|
||||||
self.device_tree.setModel(model)
|
self.device_tree.setModel(model)
|
||||||
@ -313,14 +373,18 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
self.reader_model = DeviceBooksModel(window)
|
self.reader_model = DeviceBooksModel(window)
|
||||||
self.card_model = DeviceBooksModel(window)
|
self.card_model = DeviceBooksModel(window)
|
||||||
self.device_view.setModel(self.reader_model)
|
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):
|
for model in (self.reader_model, self. card_model):
|
||||||
QObject.connect(model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents)
|
QObject.connect(model, SIGNAL("layoutChanged()"), \
|
||||||
QObject.connect(self.search, SIGNAL("textChanged(QString)"), model.search)
|
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("sorted()"), self.model_modified)
|
||||||
QObject.connect(model, SIGNAL("searched()"), self.model_modified)
|
QObject.connect(model, SIGNAL("searched()"), self.model_modified)
|
||||||
QObject.connect(model, SIGNAL("deleted()"), 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
|
# Setup book display
|
||||||
self.book_cover.hide()
|
self.book_cover.hide()
|
||||||
@ -332,10 +396,12 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit)
|
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit)
|
||||||
|
|
||||||
# DnD setup
|
# 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.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.connect(self.detector, SIGNAL("device_removed()"), self.device_removed)
|
||||||
self.search.setFocus(Qt.OtherFocusReason)
|
self.search.setFocus(Qt.OtherFocusReason)
|
||||||
self.show_device(False)
|
self.show_device(False)
|
||||||
@ -379,8 +445,10 @@ class MainWindow(QObject, Ui_MainWindow):
|
|||||||
return
|
return
|
||||||
except ProtocolError, e:
|
except ProtocolError, e:
|
||||||
traceback.print_exc(e)
|
traceback.print_exc(e)
|
||||||
qFatal("Unable to connect to device. Please try unplugging and reconnecting it")
|
qFatal("Unable to connect to device. Please try unplugging and"+\
|
||||||
self.df.setText(self.df_template.arg("Connected: "+info[0]).arg(info[1]).arg(info[2]))
|
" 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.update_availabe_space(end_session=False)
|
||||||
self.card = self.dev.card()
|
self.card = self.dev.card()
|
||||||
self.is_connected = True
|
self.is_connected = True
|
||||||
@ -430,7 +498,8 @@ class DeviceConnectDetector(QObject):
|
|||||||
devobj = bus.get_object('org.freedesktop.Hal', udi)
|
devobj = bus.get_object('org.freedesktop.Hal', udi)
|
||||||
dev = dbus.Interface(devobj, "org.freedesktop.Hal.Device")
|
dev = dbus.Interface(devobj, "org.freedesktop.Hal.Device")
|
||||||
properties = dev.GetAllProperties()
|
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
|
if self.dev.signature() == (vendor_id, product_id): ans = True
|
||||||
except:
|
except:
|
||||||
self.device_detector = self.startTimer(1000)
|
self.device_detector = self.startTimer(1000)
|
||||||
@ -451,12 +520,16 @@ class DeviceConnectDetector(QObject):
|
|||||||
raise Exception("DBUS doesn't support the Qt mainloop")
|
raise Exception("DBUS doesn't support the Qt mainloop")
|
||||||
import dbus
|
import dbus
|
||||||
bus = dbus.SystemBus()
|
bus = dbus.SystemBus()
|
||||||
hal_manager_obj = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
|
hal_manager_obj = bus.get_object('org.freedesktop.Hal',\
|
||||||
hal_manager = dbus.Interface(hal_manager_obj, 'org.freedesktop.Hal.Manager')
|
'/org/freedesktop/Hal/Manager')
|
||||||
hal_manager.connect_to_signal('DeviceAdded', self.device_added_callback)
|
hal_manager = dbus.Interface(hal_manager_obj,\
|
||||||
hal_manager.connect_to_signal('DeviceRemoved', self.device_removed_callback)
|
'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:
|
except Exception, e:
|
||||||
#Warning("Could not connect to HAL", e)
|
#_Warning("Could not connect to HAL", e)
|
||||||
self.is_connected = False
|
self.is_connected = False
|
||||||
self.device_detector = self.startTimer(1000)
|
self.device_detector = self.startTimer(1000)
|
||||||
|
|
||||||
@ -466,11 +539,13 @@ def main():
|
|||||||
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
|
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
|
||||||
if os.access(lock, os.F_OK):
|
if os.access(lock, os.F_OK):
|
||||||
print >>sys.stderr, "Another instance of", APP_TITLE, "is running"
|
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)
|
sys.exit(1)
|
||||||
parser = OptionParser(usage="usage: %prog [options]", version=VERSION)
|
parser = OptionParser(usage="usage: %prog [options]", version=VERSION)
|
||||||
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\
|
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)
|
dest="log_packets", action="store_true", default=False)
|
||||||
options, args = parser.parse_args()
|
options, args = parser.parse_args()
|
||||||
from PyQt4.Qt import QApplication, QMainWindow
|
from PyQt4.Qt import QApplication, QMainWindow
|
||||||
@ -483,7 +558,7 @@ def main():
|
|||||||
installErrorHandler(QErrorMessage(window))
|
installErrorHandler(QErrorMessage(window))
|
||||||
QCoreApplication.setOrganizationName("KovidsBrain")
|
QCoreApplication.setOrganizationName("KovidsBrain")
|
||||||
QCoreApplication.setApplicationName(APP_TITLE)
|
QCoreApplication.setApplicationName(APP_TITLE)
|
||||||
gui = MainWindow(window, options.log_packets)
|
Main(window, options.log_packets)
|
||||||
lock = LockFile(lock)
|
lock = LockFile(lock)
|
||||||
return app.exec_()
|
return app.exec_()
|
||||||
|
|
||||||
|
@ -12,7 +12,12 @@
|
|||||||
## You should have received a copy of the GNU General Public License along
|
## 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.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 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 operator import itemgetter, attrgetter
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
from urlparse import urlparse, urlunparse
|
from urlparse import urlparse, urlunparse
|
||||||
@ -20,15 +25,19 @@ from urllib import quote, unquote
|
|||||||
from math import sin, cos, pi
|
from math import sin, cos, pi
|
||||||
|
|
||||||
from libprs500 import TEMPORARY_FILENAME_TEMPLATE as TFT
|
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 import QtGui, QtCore
|
||||||
from PyQt4.QtCore import Qt, SIGNAL
|
from PyQt4.QtCore import Qt, SIGNAL
|
||||||
from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, QVariant, QAbstractTableModel, QTableView, QListView, QLabel,\
|
from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, \
|
||||||
QAbstractItemView, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage, QDialog, QSpinBox, QPoint, QTemporaryFile, QDir, QFile, QIODevice,\
|
QVariant, QAbstractTableModel, QTableView, QListView, \
|
||||||
QPainterPath, QItemDelegate, QPainter, QPen, QColor, QLinearGradient, QBrush, QStyle,\
|
QLabel, QAbstractItemView, QPixmap, QIcon, QSize, \
|
||||||
QStringList, QByteArray, QBuffer, QMimeData, QTextStream, QIODevice, QDrag, QRect
|
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
|
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
|
TIME_WRITE_FMT = "%d %b %Y" #: The display format used to show dates
|
||||||
@ -44,6 +53,10 @@ class FileDragAndDrop(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_r_ok_files(cls, event):
|
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 = []
|
files = []
|
||||||
md = event.mimeData()
|
md = event.mimeData()
|
||||||
if md.hasFormat("text/uri-list"):
|
if md.hasFormat("text/uri-list"):
|
||||||
@ -51,18 +64,20 @@ class FileDragAndDrop(object):
|
|||||||
for url in candidates:
|
for url in candidates:
|
||||||
o = urlparse(url)
|
o = urlparse(url)
|
||||||
if o.scheme and o.scheme != 'file':
|
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
|
continue
|
||||||
path = unquote(o.path)
|
path = unquote(o.path)
|
||||||
if not os.access(path, os.R_OK):
|
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
|
continue
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
root, dirs, files2 = os.walk(path)
|
root, dirs, files2 = os.walk(path)
|
||||||
for file in files2:
|
for _file in files2:
|
||||||
path = root + file
|
path = root + _file
|
||||||
if os.access(path, os.R_OK): files.append(path)
|
if os.access(path, os.R_OK):
|
||||||
else: files.append(path)
|
files.append(path)
|
||||||
|
else:
|
||||||
|
files.append(path)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def __init__(self, QtBaseClass, enable_drag=True):
|
def __init__(self, QtBaseClass, enable_drag=True):
|
||||||
@ -79,15 +94,20 @@ class FileDragAndDrop(object):
|
|||||||
def mouseMoveEvent(self, event):
|
def mouseMoveEvent(self, event):
|
||||||
self.QtBaseClass.mousePressEvent(self, event)
|
self.QtBaseClass.mousePressEvent(self, event)
|
||||||
if self.enable_drag:
|
if self.enable_drag:
|
||||||
if event.buttons() & Qt.LeftButton != Qt.LeftButton: return
|
if event.buttons() & Qt.LeftButton != Qt.LeftButton:
|
||||||
if (event.pos() - self._drag_start_position).manhattanLength() < QApplication.startDragDistance(): return
|
return
|
||||||
|
if (event.pos() - self._drag_start_position).manhattanLength() < \
|
||||||
|
QApplication.startDragDistance():
|
||||||
|
return
|
||||||
self.start_drag(self._drag_start_position)
|
self.start_drag(self._drag_start_position)
|
||||||
|
|
||||||
|
|
||||||
def start_drag(self, pos): pass
|
def start_drag(self, pos):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
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):
|
def dragMoveEvent(self, event):
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
@ -97,25 +117,29 @@ class FileDragAndDrop(object):
|
|||||||
if files:
|
if files:
|
||||||
try:
|
try:
|
||||||
event.setDropAction(Qt.CopyAction)
|
event.setDropAction(Qt.CopyAction)
|
||||||
if self.files_dropped(files, event): event.accept()
|
if self.files_dropped(files, event):
|
||||||
|
event.accept()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
Error("There was an error processing the dropped files.", e)
|
Error("There was an error processing the dropped files.", e)
|
||||||
raise 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):
|
def drag_object_from_files(self, files):
|
||||||
if files:
|
if files:
|
||||||
drag = QDrag(self)
|
drag = QDrag(self)
|
||||||
mime_data = QMimeData()
|
mime_data = QMimeData()
|
||||||
self._dragged_files, urls = [], []
|
self._dragged_files, urls = [], []
|
||||||
for file in files:
|
for _file in files:
|
||||||
urls.append(urlunparse(('file', quote(gethostname()), quote(str(file.name)), '','','')))
|
urls.append(urlunparse(('file', quote(gethostname()), \
|
||||||
self._dragged_files.append(file)
|
quote(str(_file.name)), '','','')))
|
||||||
|
self._dragged_files.append(_file)
|
||||||
mime_data.setData("text/uri-list", QByteArray("\n".join(urls)))
|
mime_data.setData("text/uri-list", QByteArray("\n".join(urls)))
|
||||||
user = os.getenv('USER')
|
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)
|
drag.setMimeData(mime_data)
|
||||||
return drag
|
return drag
|
||||||
|
|
||||||
@ -135,17 +159,23 @@ class TableView(FileDragAndDrop, QTableView):
|
|||||||
QTableView.__init__(self, parent)
|
QTableView.__init__(self, parent)
|
||||||
|
|
||||||
@classmethod
|
@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
|
@classmethod
|
||||||
def human_readable(cls, size):
|
def human_readable(cls, size):
|
||||||
""" Convert a size in bytes into a human readable form """
|
""" Convert a size in bytes into a human readable form """
|
||||||
if size < 1024: divisor, suffix = 1, "B"
|
if size < 1024:
|
||||||
elif size < 1024*1024: divisor, suffix = 1024., "KB"
|
divisor, suffix = 1, "B"
|
||||||
elif size < 1024*1024*1024: divisor, suffix = 1024*1024, "MB"
|
elif size < 1024*1024:
|
||||||
elif size < 1024*1024*1024*1024: divisor, suffix = 1024*1024, "GB"
|
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)
|
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
|
return size + " " + suffix
|
||||||
|
|
||||||
def render_to_pixmap(self, indices):
|
def render_to_pixmap(self, indices):
|
||||||
@ -161,7 +191,8 @@ class TableView(FileDragAndDrop, QTableView):
|
|||||||
option = self.viewOptions()
|
option = self.viewOptions()
|
||||||
option.state |= QStyle.State_Selected
|
option.state |= QStyle.State_Selected
|
||||||
for j in range(len(indices)):
|
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])
|
self.itemDelegate(indices[j]).paint(painter, option, indices[j])
|
||||||
painter.end()
|
painter.end()
|
||||||
return pixmap
|
return pixmap
|
||||||
@ -182,7 +213,8 @@ class TemporaryFile(QTemporaryFile):
|
|||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
ok = QFile.open(self, QIODevice.ReadWrite)
|
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
|
return ok
|
||||||
|
|
||||||
@apply
|
@apply
|
||||||
@ -202,8 +234,8 @@ class CoverDisplay(FileDragAndDrop, QLabel):
|
|||||||
QLabel.__init__(self, parent)
|
QLabel.__init__(self, parent)
|
||||||
def files_dropped(self, files, event):
|
def files_dropped(self, files, event):
|
||||||
pix = QPixmap()
|
pix = QPixmap()
|
||||||
for file in files:
|
for _file in files:
|
||||||
pix = QPixmap(file)
|
pix = QPixmap(_file)
|
||||||
if not pix.isNull(): break
|
if not pix.isNull(): break
|
||||||
if not pix.isNull():
|
if not pix.isNull():
|
||||||
self.emit(SIGNAL("cover_received(QPixmap)"), pix)
|
self.emit(SIGNAL("cover_received(QPixmap)"), pix)
|
||||||
@ -212,10 +244,10 @@ class CoverDisplay(FileDragAndDrop, QLabel):
|
|||||||
def start_drag(self, event):
|
def start_drag(self, event):
|
||||||
drag, files = self.drag_object(["jpeg"])
|
drag, files = self.drag_object(["jpeg"])
|
||||||
if drag and files:
|
if drag and files:
|
||||||
file = files[0]
|
_file = files[0]
|
||||||
drag.setPixmap(self.pixmap())
|
drag.setPixmap(self.pixmap())
|
||||||
self.pixmap().save(file)
|
self.pixmap().save(_file)
|
||||||
file.close()
|
_file.close()
|
||||||
drag.start(Qt.MoveAction)
|
drag.start(Qt.MoveAction)
|
||||||
|
|
||||||
class DeviceView(FileDragAndDrop, QListView):
|
class DeviceView(FileDragAndDrop, QListView):
|
||||||
@ -233,7 +265,8 @@ class DeviceView(FileDragAndDrop, QListView):
|
|||||||
ids = []
|
ids = []
|
||||||
md = event.mimeData()
|
md = event.mimeData()
|
||||||
if md.hasFormat("application/x-libprs500-id"):
|
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())
|
index = self.indexAt(event.pos())
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
return self.model().files_dropped(files, index, ids)
|
return self.model().files_dropped(files, index, ids)
|
||||||
@ -264,7 +297,8 @@ class LibraryBooksView(TableView):
|
|||||||
drag = self.drag_object_from_files(files)
|
drag = self.drag_object_from_files(files)
|
||||||
if drag:
|
if drag:
|
||||||
ids = [ str(self.model().id_from_row(row)) for row in rows ]
|
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()
|
drag.start()
|
||||||
|
|
||||||
|
|
||||||
@ -289,7 +323,8 @@ class LibraryDelegate(QItemDelegate):
|
|||||||
self.star_path = QPainterPath()
|
self.star_path = QPainterPath()
|
||||||
self.star_path.moveTo(90, 50)
|
self.star_path.moveTo(90, 50)
|
||||||
for i in range(1, 5):
|
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.closeSubpath()
|
||||||
self.star_path.setFillRule(Qt.WindingFill)
|
self.star_path.setFillRule(Qt.WindingFill)
|
||||||
gradient = QLinearGradient(0, 0, 0, 100)
|
gradient = QLinearGradient(0, 0, 0, 100)
|
||||||
@ -366,7 +401,8 @@ class LibraryDelegate(QItemDelegate):
|
|||||||
|
|
||||||
|
|
||||||
class LibraryBooksModel(QAbstractTableModel):
|
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"
|
TIME_READ_FMT = "%Y-%m-%d %H:%M:%S"
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QAbstractTableModel.__init__(self, parent)
|
QAbstractTableModel.__init__(self, parent)
|
||||||
@ -377,31 +413,36 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
def extract_formats(self, rows):
|
def extract_formats(self, rows):
|
||||||
files = []
|
files = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
id = self.id_from_row(row)
|
_id = self.id_from_row(row)
|
||||||
au = self._data[row]["authors"] if self._data[row]["authors"] else "Unknown"
|
au = self._data[row]["authors"] if self._data[row]["authors"] \
|
||||||
basename = re.sub("\n", "", "_"+str(id)+"_"+self._data[row]["title"]+" by "+ au)
|
else "Unknown"
|
||||||
exts = self.db.get_extensions(id)
|
basename = re.sub("\n", "", "_"+str(_id)+"_"+\
|
||||||
|
self._data[row]["title"]+" by "+ au)
|
||||||
|
exts = self.db.get_extensions(_id)
|
||||||
for ext in exts:
|
for ext in exts:
|
||||||
fmt = self.db.get_format(id, ext)
|
fmt = self.db.get_format(_id, ext)
|
||||||
if not ext: ext =""
|
if not ext:
|
||||||
else: ext = "."+ext
|
ext =""
|
||||||
|
else:
|
||||||
|
ext = "."+ext
|
||||||
name = basename+ext
|
name = basename+ext
|
||||||
file = NamedTemporaryFile(name)
|
file = NamedTemporaryFile(name)
|
||||||
file.open()
|
file.open()
|
||||||
if not fmt: continue
|
if not fmt:
|
||||||
|
continue
|
||||||
file.write(QByteArray(fmt))
|
file.write(QByteArray(fmt))
|
||||||
file.close()
|
file.close()
|
||||||
files.append(file)
|
files.append(file)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def update_cover(self, index, pix):
|
def update_cover(self, index, pix):
|
||||||
id = self.id_from_index(index)
|
_id = self.id_from_index(index)
|
||||||
qb = QBuffer()
|
qb = QBuffer()
|
||||||
qb.open(QBuffer.ReadWrite);
|
qb.open(QBuffer.ReadWrite)
|
||||||
pix.save(qb, "JPG")
|
pix.save(qb, "JPG")
|
||||||
data = str(qb.data())
|
data = str(qb.data())
|
||||||
qb.close()
|
qb.close()
|
||||||
self.db.update_cover(id, data)
|
self.db.update_cover(_id, data)
|
||||||
|
|
||||||
def add_formats(self, paths, index):
|
def add_formats(self, paths, index):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
@ -412,29 +453,41 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
f.close()
|
f.close()
|
||||||
self.emit(SIGNAL('formats_added'), index)
|
self.emit(SIGNAL('formats_added'), index)
|
||||||
|
|
||||||
def rowCount(self, parent): return len(self._data)
|
def rowCount(self, parent):
|
||||||
def columnCount(self, parent): return len(self.FIELDS)-3
|
return len(self._data)
|
||||||
|
|
||||||
|
def columnCount(self, parent):
|
||||||
|
return len(self.FIELDS)-3
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
def setData(self, index, value, role):
|
||||||
done = False
|
done = False
|
||||||
if role == Qt.EditRole:
|
if role == Qt.EditRole:
|
||||||
row = index.row()
|
row = index.row()
|
||||||
id = self._data[row]["id"]
|
_id = self._data[row]["id"]
|
||||||
col = index.column()
|
col = index.column()
|
||||||
val = str(value.toString())
|
val = str(value.toString())
|
||||||
if col == 0: col = "title"
|
if col == 0:
|
||||||
elif col == 1: col = "authors"
|
col = "title"
|
||||||
elif col == 2: return False
|
elif col == 1:
|
||||||
elif col == 3: return False
|
col = "authors"
|
||||||
|
elif col == 2:
|
||||||
|
return False
|
||||||
|
elif col == 3:
|
||||||
|
return False
|
||||||
elif col == 4:
|
elif col == 4:
|
||||||
col, val = "rating", int(value.toInt()[0])
|
col, val = "rating", int(value.toInt()[0])
|
||||||
if val < 0: val =0
|
if val < 0:
|
||||||
if val > 5: val = 5
|
val = 0
|
||||||
elif col == 5: col = "publisher"
|
if val > 5:
|
||||||
else: return False
|
val = 5
|
||||||
self.db.set_metadata_item(id, col, val)
|
elif col == 5:
|
||||||
|
col = "publisher"
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
self.db.set_metadata_item(_id, col, val)
|
||||||
self._data[row][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)):
|
for i in range(len(self._orig_data)):
|
||||||
if self._orig_data[i]["id"] == self._data[row]["id"]:
|
if self._orig_data[i]["id"] == self._data[row]["id"]:
|
||||||
self._orig_data[i][col] = self._data[row][col]
|
self._orig_data[i][col] = self._data[row][col]
|
||||||
@ -445,7 +498,8 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
flags = QAbstractTableModel.flags(self, index)
|
flags = QAbstractTableModel.flags(self, index)
|
||||||
if index.isValid():
|
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
|
return flags
|
||||||
|
|
||||||
def set_data(self, db):
|
def set_data(self, db):
|
||||||
@ -487,17 +541,19 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
def id_from_row(self, row): return self._data[row]["id"]
|
def id_from_row(self, row): return self._data[row]["id"]
|
||||||
|
|
||||||
def refresh_row(self, row):
|
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)):
|
for i in range(len(self._orig_data)):
|
||||||
if self._orig_data[i]["id"] == self._data[row]["id"]:
|
if self._orig_data[i]["id"] == self._data[row]["id"]:
|
||||||
self._orig_data[i:i+1] = self._data[row]
|
self._orig_data[i:i+1] = self._data[row]
|
||||||
break
|
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 """
|
""" Return title, authors and cover in a dict """
|
||||||
cover = self.db.get_cover(id)
|
cover = self.db.get_cover(_id)
|
||||||
info = self.db.get_row_by_id(id, ["title", "authors"])
|
info = self.db.get_row_by_id(_id, ["title", "authors"])
|
||||||
info["cover"] = cover
|
info["cover"] = cover
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@ -508,19 +564,27 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
row = self._data[row]
|
row = self._data[row]
|
||||||
if col == 4:
|
if col == 4:
|
||||||
r = row["rating"] if row["rating"] else 0
|
r = row["rating"] if row["rating"] else 0
|
||||||
if r < 0: r= 0
|
if r < 0:
|
||||||
if r > 5: r=5
|
r = 0
|
||||||
|
if r > 5:
|
||||||
|
r = 5
|
||||||
return QVariant(r)
|
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:
|
elif col == 1:
|
||||||
au = row["authors"]
|
au = row["authors"]
|
||||||
if au : text = TableView.wrap(re.sub("&", "\n", au), width=25)
|
if au:
|
||||||
elif col == 2: text = TableView.human_readable(row["size"])
|
text = TableView.wrap(re.sub("&", "\n", au), width=25)
|
||||||
elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(row["date"], self.TIME_READ_FMT))
|
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:
|
elif col == 5:
|
||||||
pub = row["publisher"]
|
pub = row["publisher"]
|
||||||
if pub: text = TableView.wrap(pub, 20)
|
if pub: text = TableView.wrap(pub, 20)
|
||||||
if text == None: text = "Unknown"
|
if text == None:
|
||||||
|
text = "Unknown"
|
||||||
return QVariant(text)
|
return QVariant(text)
|
||||||
elif role == Qt.TextAlignmentRole and index.column() in [2,3,4]:
|
elif role == Qt.TextAlignmentRole and index.column() in [2,3,4]:
|
||||||
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
||||||
@ -528,11 +592,14 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
|
|
||||||
def sort(self, col, order):
|
def sort(self, col, order):
|
||||||
descending = order != Qt.AscendingOrder
|
descending = order != Qt.AscendingOrder
|
||||||
def getter(key, func): return lambda x : func(itemgetter(key)(x))
|
def getter(key, func):
|
||||||
if col == 0: key, func = "title", string.lower
|
return lambda x : func(itemgetter(key)(x))
|
||||||
if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower() if x else ""
|
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 == 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 == 4: key, func = "rating", lambda x: x if x else 0
|
||||||
if col == 5: key, func = "publisher", lambda x : x.lower() if x else ""
|
if col == 5: key, func = "publisher", lambda x : x.lower() if x else ""
|
||||||
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
||||||
@ -547,7 +614,8 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
if not au : au = "unknown"
|
if not au : au = "unknown"
|
||||||
pub = book["publisher"]
|
pub = book["publisher"]
|
||||||
if not pub : pub = "unknown"
|
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()
|
queries = unicode(query, 'utf-8').lower().split()
|
||||||
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
||||||
self._data = []
|
self._data = []
|
||||||
@ -566,21 +634,21 @@ class LibraryBooksModel(QAbstractTableModel):
|
|||||||
if len(indices): self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
if len(indices): self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
||||||
items = [ self._data[index.row()] for index in indices ]
|
items = [ self._data[index.row()] for index in indices ]
|
||||||
for item in items:
|
for item in items:
|
||||||
id = item["id"]
|
_id = item["id"]
|
||||||
try:
|
try:
|
||||||
self._data.remove(item)
|
self._data.remove(item)
|
||||||
except ValueError: continue
|
except ValueError: continue
|
||||||
self.db.delete_by_id(id)
|
self.db.delete_by_id(_id)
|
||||||
for x in self._orig_data:
|
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("layoutChanged()"))
|
||||||
self.emit(SIGNAL("deleted()"))
|
self.emit(SIGNAL("deleted()"))
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def add_book(self, path):
|
def add_book(self, path):
|
||||||
""" Must call search and sort on this models view after this """
|
""" Must call search and sort on this models view after this """
|
||||||
id = self.db.add_book(path)
|
_id = self.db.add_book(path)
|
||||||
self._orig_data.append(self.db.get_row_by_id(id, self.FIELDS))
|
self._orig_data.append(self.db.get_row_by_id(_id, self.FIELDS))
|
||||||
|
|
||||||
class DeviceBooksModel(QAbstractTableModel):
|
class DeviceBooksModel(QAbstractTableModel):
|
||||||
@apply
|
@apply
|
||||||
@ -588,7 +656,7 @@ class DeviceBooksModel(QAbstractTableModel):
|
|||||||
doc = """ The booklist this model is based on """
|
doc = """ The booklist this model is based on """
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return self._orig_data
|
return self._orig_data
|
||||||
return property(**locals())
|
return property(doc=doc, fget=fget)
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QAbstractTableModel.__init__(self, parent)
|
QAbstractTableModel.__init__(self, parent)
|
||||||
@ -619,10 +687,14 @@ class DeviceBooksModel(QAbstractTableModel):
|
|||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
row, col = index.row(), index.column()
|
row, col = index.row(), index.column()
|
||||||
book = self._data[row]
|
book = self._data[row]
|
||||||
if col == 0: text = TableView.wrap(book.title, width=40)
|
if col == 0:
|
||||||
elif col == 1: text = re.sub("&\s*","\n", book.author)
|
text = TableView.wrap(book.title, width=40)
|
||||||
elif col == 2: text = TableView.human_readable(book.size)
|
elif col == 1:
|
||||||
elif col == 3: text = time.strftime(TIME_WRITE_FMT, book.datetime)
|
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)
|
return QVariant(text)
|
||||||
elif role == Qt.TextAlignmentRole and index.column() in [2,3]:
|
elif role == Qt.TextAlignmentRole and index.column() in [2,3]:
|
||||||
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
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
|
return row.title, au, TableView.human_readable(row.size), row.mime, cover
|
||||||
|
|
||||||
def sort(self, col, order):
|
def sort(self, col, order):
|
||||||
def getter(key, func): return lambda x : func(attrgetter(key)(x))
|
def getter(key, func):
|
||||||
if col == 0: key, func = "title", string.lower
|
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 == 1: key, func = "author", lambda x : x.split()[-1:][0].lower()
|
||||||
if col == 2: key, func = "size", int
|
if col == 2: key, func = "size", int
|
||||||
if col == 3: key, func = "datetime", lambda x: x
|
if col == 3: key, func = "datetime", lambda x: x
|
||||||
@ -672,20 +745,25 @@ class DeviceBooksModel(QAbstractTableModel):
|
|||||||
def delete(self, indices):
|
def delete(self, indices):
|
||||||
paths = []
|
paths = []
|
||||||
rows = [ index.row() for index in indices ]
|
rows = [ index.row() for index in indices ]
|
||||||
if not rows: return
|
if not rows:
|
||||||
|
return
|
||||||
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
||||||
elems = [ self._data[row] for row in rows ]
|
elems = [ self._data[row] for row in rows ]
|
||||||
for e in elems:
|
for e in elems:
|
||||||
id = e.id
|
_id = e.id
|
||||||
paths.append(e.path)
|
paths.append(e.path)
|
||||||
self._orig_data.delete_book(id)
|
self._orig_data.delete_book(_id)
|
||||||
try: self._data.remove(e)
|
try:
|
||||||
except ValueError: pass
|
self._data.remove(e)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
self.emit(SIGNAL("layoutChanged()"))
|
self.emit(SIGNAL("layoutChanged()"))
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def path(self, index): return self._data[index.row()].path
|
def path(self, index):
|
||||||
def title(self, index): return self._data[index.row()].title
|
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
|
show_card = False
|
||||||
|
|
||||||
def update_devices(self, reader=None, card=None):
|
def update_devices(self, reader=None, card=None):
|
||||||
if reader != None: self.show_reader = reader
|
if reader != None:
|
||||||
if card != None: self.show_card = card
|
self.show_reader = reader
|
||||||
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(1), self.index(2))
|
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 rowCount(self, parent): return 3
|
||||||
|
|
||||||
def update_free_space(self, reader, card):
|
def update_free_space(self, reader, card):
|
||||||
self.memory_free = reader
|
self.memory_free = reader
|
||||||
self.card_free = card
|
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):
|
def data(self, index, role):
|
||||||
row = index.row()
|
row = index.row()
|
||||||
data = NONE
|
data = NONE
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
text = None
|
text = None
|
||||||
if row == 0: text = "Library"
|
if row == 0:
|
||||||
|
text = "Library"
|
||||||
if row == 1 and self.show_reader:
|
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:
|
elif row == 2 and self.show_card:
|
||||||
text = "Card\n" + TableView.human_readable(self.card_free) + " available"
|
text = "Card\n" + TableView.human_readable(self.card_free) \
|
||||||
if text: data = QVariant(text)
|
+ " available"
|
||||||
|
if text:
|
||||||
|
data = QVariant(text)
|
||||||
elif role == Qt.DecorationRole:
|
elif role == Qt.DecorationRole:
|
||||||
icon = None
|
icon = None
|
||||||
if row == 0: icon = QIcon(":/library")
|
if row == 0:
|
||||||
elif row == 1 and self.show_reader: icon = QIcon(":/reader")
|
icon = QIcon(":/library")
|
||||||
elif self.show_card: icon = QIcon(":/card")
|
elif row == 1 and self.show_reader:
|
||||||
if icon: data = QVariant(icon)
|
icon = QIcon(":/reader")
|
||||||
|
elif self.show_card:
|
||||||
|
icon = QIcon(":/card")
|
||||||
|
if icon:
|
||||||
|
data = QVariant(icon)
|
||||||
elif role == Qt.SizeHintRole:
|
elif role == Qt.SizeHintRole:
|
||||||
if row == 1: return QVariant(QSize(150, 70))
|
if row == 1:
|
||||||
|
return QVariant(QSize(150, 70))
|
||||||
elif role == Qt.FontRole:
|
elif role == Qt.FontRole:
|
||||||
font = QFont()
|
font = QFont()
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
data = QVariant(font)
|
data = QVariant(font)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def is_library(self, index): return index.row() == 0
|
def is_library(self, index):
|
||||||
def is_reader(self, index): return index.row() == 1
|
return index.row() == 0
|
||||||
def is_card(self, index): return index.row() == 2
|
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):
|
def files_dropped(self, files, index, ids):
|
||||||
ret = False
|
ret = False
|
||||||
|
1
setup.py
1
setup.py
@ -89,5 +89,6 @@ try:
|
|||||||
except ImportError:
|
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"
|
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:
|
else:
|
||||||
|
import PyQt4.Qt
|
||||||
if PyQt4.Qt.PYQT_VERSION < 0x40101:
|
if PyQt4.Qt.PYQT_VERSION < 0x40101:
|
||||||
print "WARNING: The GUI needs PyQt >= 4.1.1"
|
print "WARNING: The GUI needs PyQt >= 4.1.1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user