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
|
||||
## 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
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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> Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b> 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> 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")
|
||||
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_()
|
||||
|
||||
|
@ -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
|
||||
|
1
setup.py
1
setup.py
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user