Changed GUI to only show devices. No subsets.

Added a warning about PyQt4 too setup script
Added initial code for Dropping on reader (not tested)
This commit is contained in:
Kovid Goyal 2006-12-09 05:55:36 +00:00
parent 8e4010e0f3
commit 876f994a4a
13 changed files with 4665 additions and 628 deletions

View File

@ -32,7 +32,7 @@ the following rule in C{/etc/udev/rules.d/90-local.rules} ::
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev" BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
You may have to adjust the GROUP and the location of the rules file to suit your distribution. You may have to adjust the GROUP and the location of the rules file to suit your distribution.
""" """
__version__ = "0.2.1" __version__ = "0.3.0"
__docformat__ = "epytext" __docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
TEMPORARY_FILENAME_TEMPLATE = "libprs500_"+__version__+"_temp" TEMPORARY_FILENAME_TEMPLATE = "libprs500_"+__version__+"_temp"

View File

@ -16,16 +16,24 @@
from xml.dom.ext import PrettyPrint as PrettyPrint from xml.dom.ext import PrettyPrint as PrettyPrint
import xml.dom.minidom as dom import xml.dom.minidom as dom
from base64 import b64decode as decode from base64 import b64decode as decode
from base64 import b64encode as encode
import time import time
MIME_MAP = { "lrf":"application/x-sony-bbeb", "rtf":"application/rtf", "pdf":"application/pdf", "txt":"text/plain" }
class book_metadata_field(object): class book_metadata_field(object):
def __init__(self, attr, formatter=None): def __init__(self, attr, formatter=None, setter=None):
self.attr = attr self.attr = attr
self.formatter = formatter self.formatter = formatter
def __get__(self, obj, typ=None): def __get__(self, obj, typ=None):
""" Return a string. String may be empty if self.attr is absent """ """ Return a string. String may be empty if self.attr is absent """
return self.formatter(obj.elem.getAttribute(self.attr)) if self.formatter else obj.elem.getAttribute(self.attr).strip() return self.formatter(obj.elem.getAttribute(self.attr)) if self.formatter else obj.elem.getAttribute(self.attr).strip()
def __set__(self, obj, val):
val = self.setter(val) if self.setter else val
obj.elem.setAttribute(self.attr, str(val))
class Book(object): class Book(object):
title = book_metadata_field("title") title = book_metadata_field("title")
author = book_metadata_field("author", formatter=lambda x: x if x.strip() else "Unknown") author = book_metadata_field("author", formatter=lambda x: x if x.strip() else "Unknown")
@ -33,7 +41,7 @@ class Book(object):
rpath = book_metadata_field("path") rpath = book_metadata_field("path")
id = book_metadata_field("id", formatter=int) id = book_metadata_field("id", formatter=int)
size = book_metadata_field("size", formatter=int) size = book_metadata_field("size", formatter=int)
datetime = book_metadata_field("date", formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z")) datetime = book_metadata_field("date", formatter=lambda x: time.strptime(x, "%a, %d %b %Y %H:%M:%S %Z"), setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S %Z", x))
@apply @apply
def thumbnail(): def thumbnail():
@ -78,4 +86,23 @@ class BookList(list):
file.seek(0) file.seek(0)
self.document = dom.parse(file) self.document = dom.parse(file)
for book in self.document.getElementsByTagName(self.prefix + "text"): self.append(Book(book, root=root, prefix=prefix)) for book in self.document.getElementsByTagName(self.prefix + "text"): self.append(Book(book, root=root, prefix=prefix))
self._file = file
def add_book(self, info, name):
root = self.document.documentElement
node = self.document.createElement(self.prefix + "text")
mime = MIME_MAP[name[name.rfind(".")+1:]]
id = 0
for book in self:
if book.id > id:
id = book.id
break
attrs = { "title":info["title"], "author":info["authors"], "page":"0", "part":"0", "scale":"0", "sourceid":"1", "id":str(id), "date":"", "mime":mime, "path":name, "size":str(size)}
for attr in attrs.keys():
node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr])
book = Book(node, root=self.root, prefix=self.prefix)
book.datetime = time.gmtime()
self.append(book)

View File

@ -134,6 +134,7 @@ class PRS500Device(object):
PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
MEDIA_XML = "/Data/database/cache/media.xml" #: Location of media.xml file on device MEDIA_XML = "/Data/database/cache/media.xml" #: Location of media.xml file on device
CACHE_XML = "/Sony Reader/database/cache.xml" #: Location of cache.xml on storage card in device CACHE_XML = "/Sony Reader/database/cache.xml" #: Location of cache.xml on storage card in device
FORMATS = ["lrf", "rtf", "pdf", "txt"] #: Ordered list of supported formats
device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, PRS500_PRODUCT_ID, PRS500_INTERFACE_ID) device_descriptor = DeviceDescriptor(SONY_VENDOR_ID, PRS500_PRODUCT_ID, PRS500_INTERFACE_ID)
@ -572,7 +573,14 @@ class PRS500Device(object):
raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code)) raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code))
@safe @safe
def books(self, oncard=False): def card(self, end_session=True):
card = None
if self._exists("a:/")[0]: card = "a:"
if self._exists("b:/")[0]: card = "b:"
return card
@safe
def books(self, oncard=False, end_session=True):
""" """
Return a list of ebooks on the device. Return a list of ebooks on the device.
@param oncard: If True return a list of ebookson the storage card, otherwise return list of ebooks in main memory of device @param oncard: If True return a list of ebookson the storage card, otherwise return list of ebooks in main memory of device
@ -595,3 +603,32 @@ class PRS500Device(object):
if file.tell() == 0: file = None if file.tell() == 0: file = None
else: self.get_file(self.MEDIA_XML, file, end_session=False) else: self.get_file(self.MEDIA_XML, file, end_session=False)
return BookList(prefix=prefix, root=root, file=file) return BookList(prefix=prefix, root=root, file=file)
@safe
def add_book(self, infile, name, info, booklists, oncard=False, end_session=True):
"""
Add a book to the device. If oncard is True then the book is copied to the card rather than main memory.
@param infile: The source file, should be opened in "rb" mode
@param name: The name of the book file when uploaded to the device. The extension of name must be one of the supported formats for this device.
@param info: A dictionary that must have the keys "title", "authors", "cover". C{info["cover"]} should be the data from a 60x80 image file or None. If it is something else, results are undefined.
@param booklists: A tuple containing the result of calls to (L{books}(oncard=False), L{books}(oncard=True)).
@todo: Implement syncing the booklists to the device. This would mean juggling with the nextId attribute in media.xml and renumbering ids in cache.xml?
"""
infile.seek(0,2)
size = infile.tell()
infile.seek(0)
card = self.card(end_session=False)
space = self.available_space(end_session=False)
mspace = space[0][1]
cspace = space[1][1] if space[1][1] >= space[2][1] else space[2][1]
if oncard and size > cspace - 1024*1024: raise FreeSpaceError("There is insufficient free space on the storage card")
if not oncard and size > mspace - 1024*1024: raise FreeSpaceError("There is insufficient free space in main memory")
prefix = "/Data/media/"
if oncard: prefix = card + "/"
else: name = "books/"+name
path = prefix + name
self.put_file(infile, path, end_session=False)
if oncard: booklists[1].add_book(info, name, size)
else: booklists[0].add_book(info, name, size)

View File

@ -37,6 +37,9 @@ class DeviceError(ProtocolError):
class PacketError(ProtocolError): class PacketError(ProtocolError):
""" Errors with creating/interpreting packets """ """ Errors with creating/interpreting packets """
class FreeSpaceError(ProtocolError):
""" Errors caused when trying to put files onto an overcrowded device """
class ArgumentError(ProtocolError): class ArgumentError(ProtocolError):
""" Errors caused by invalid arguments to a public interface function """ """ Errors caused by invalid arguments to a public interface function """

View File

@ -15,11 +15,35 @@
__docformat__ = "epytext" __docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import pkg_resources, sys, os, StringIO import pkg_resources, sys, os, re, StringIO, traceback
from PyQt4 import QtCore, QtGui # Needed for classes imported with import_ui from PyQt4 import QtCore, QtGui # Needed for classes imported with import_ui
from libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed for import_ui
from PyQt4.uic.Compiler import compiler from PyQt4.uic.Compiler import compiler
error_dialog = None
def extension(path):
return os.path.splitext(path)[1][1:].lower()
def installErrorHandler(dialog):
global error_dialog
error_dialog = dialog
error_dialog.resize(600, 400)
error_dialog.setWindowTitle("SONY Reader - Error")
error_dialog.setModal(True)
def Warning(msg, e):
print >> sys.stderr, msg
traceback.print_exc(e)
def Error(msg, e):
if error_dialog:
if e: msg += "<br>" + traceback.format_exc(e)
msg = re.sub("Traceback", "<b>Traceback</b>", msg)
msg = re.sub(r"\n", "<br>", msg)
error_dialog.showMessage(msg)
error_dialog.show()
def import_ui(name): def import_ui(name):
uifile = pkg_resources.resource_stream(__name__, name) uifile = pkg_resources.resource_stream(__name__, name)
code_string = StringIO.StringIO() code_string = StringIO.StringIO()
@ -27,3 +51,5 @@ def import_ui(name):
ui = pkg_resources.resource_filename(__name__, name) ui = pkg_resources.resource_filename(__name__, 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 for import_ui

View File

@ -1,7 +1,8 @@
<!DOCTYPE RCC><RCC version="1.0"> <!DOCTYPE RCC><RCC version="1.0">
<qresource> <qresource>
<file alias="icon">images/library.png</file>
<file alias="default_cover">images/cherubs.jpg</file> <file alias="default_cover">images/cherubs.jpg</file>
<file alias="library">images/mycomputer.png</file> <file alias="library">images/library.png</file>
<file alias="reader">images/reader.png</file> <file alias="reader">images/reader.png</file>
<file alias="card">images/memory_stick_unmount.png</file> <file alias="card">images/memory_stick_unmount.png</file>
<file>images/clear.png</file> <file>images/clear.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

View File

@ -12,22 +12,24 @@
## 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.
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog, QIcon
from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical
from PyQt4 import uic from PyQt4 import uic
from libprs500.communicate import PRS500Device as device from libprs500.communicate import PRS500Device as device
from libprs500.errors import * from libprs500.errors import *
from libprs500.lrf.meta import LRFMetaFile, LRFException from libprs500.lrf.meta import LRFMetaFile, LRFException
from libprs500.gui import import_ui from libprs500.gui import import_ui, installErrorHandler, Error, Warning, extension
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, human_readable from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, TableView
from database import LibraryDatabase from database import LibraryDatabase
from editbook import EditBookDialog from editbook import EditBookDialog
import sys, re, os, traceback import sys, re, os, traceback
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>")
DEVICE_BOOK_TEMPLATE = QString("<table><tr><td><b>Title: </b>%1</td><td><b>&nbsp;Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b>&nbsp;Type: </b>%4</td></tr></table>")
Ui_MainWindow = import_ui("main.ui") Ui_MainWindow = import_ui("main.ui")
class MainWindow(QObject, Ui_MainWindow): class MainWindow(QObject, Ui_MainWindow):
@ -49,18 +51,19 @@ class MainWindow(QObject, Ui_MainWindow):
def tree_clicked(self, index): def tree_clicked(self, index):
if index.isValid(): if index.isValid():
text = (index.data(Qt.DisplayRole).toString()) self.search.clear()
if "Books" in text: text = str(index.parent().data(Qt.DisplayRole).toString()) show_dev = True
if "Library" in text: model = self.device_tree.model()
self.show_device(False) if model.is_library(index):
elif "SONY Reader" in text: show_dev = False
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)
self.show_device(True) elif model.is_card(index):
elif "Storage Card" in text:
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(True) self.show_device(show_dev)
def model_modified(self): def model_modified(self):
if self.library_view.isVisible(): view = self.library_view if self.library_view.isVisible(): view = self.library_view
@ -78,14 +81,18 @@ class MainWindow(QObject, Ui_MainWindow):
view.resizeColumnToContents(c) view.resizeColumnToContents(c)
def show_book(self, current, previous): def show_book(self, current, previous):
title, author, size, mime, thumbnail = current.model().info(current.row()) if self.library_view.isVisible():
self.book_info.setText(self.BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)) formats, tags, comments, cover = current.model().info(current.row())
if not thumbnail: thumbnail = DEFAULT_BOOK_COVER data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
self.book_cover.setPixmap(thumbnail) tooltip = "To save the cover, drag it to the desktop.<br>To change the cover drag the new cover onto this picture"
try: else:
name = os.path.abspath(current.model().image_file.name) title, author, size, mime, cover = current.model().info(current.row())
self.book_cover.setToolTip('<img src="'+name+'">') data = DEVICE_BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)
except Exception, e: self.book_cover.setToolTip('<img src=":/default_cover">') tooltip = "To save the cover, drag it to the desktop."
self.book_info.setText(data)
self.book_cover.setToolTip(tooltip)
if not cover: cover = DEFAULT_BOOK_COVER
self.book_cover.setPixmap(cover)
self.book_cover.show() self.book_cover.show()
self.book_info.show() self.book_info.show()
@ -95,7 +102,7 @@ class MainWindow(QObject, Ui_MainWindow):
def delete(self, action): def delete(self, action):
count = str(len(self.current_view.selectionModel().selectedRows())) count = str(len(self.current_view.selectionModel().selectedRows()))
ret = QMessageBox.question(self.window, self.trUtf8("SONY Reader - confirm"), self.trUtf8("Are you sure you want to <b>permanently delete</b> these ") +count+self.trUtf8(" items?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) ret = QMessageBox.question(self.window, self.trUtf8("SONY Reader - 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 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():
@ -168,14 +175,17 @@ class MainWindow(QObject, Ui_MainWindow):
self.add_books(files) self.add_books(files)
def add_books(self, files): def add_books(self, files):
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)
self.search.clear() if self.library_view.isVisible(): self.search.clear()
else: self.library_model.search("")
hv = self.library_view.horizontalHeader() hv = self.library_view.horizontalHeader()
col = hv.sortIndicatorSection() col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder() order = hv.sortIndicatorOrder()
self.library_view.model().sort(col, order) self.library_view.model().sort(col, order)
self.window.setCursor(Qt.ArrowCursor)
def edit(self, action): def edit(self, action):
@ -189,15 +199,69 @@ class MainWindow(QObject, Ui_MainWindow):
self.library_model.refresh_row(row.row()) self.library_model.refresh_row(row.row())
def show_error(self, e, msg):
QErrorMessage(self.window).showMessage(msg+"<br><b>Error: </b>"+str(e)+"<br><br>"+re.sub("\n","<br>", traceback.format_exc(e)))
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: self.show_error(e, "Unable to change cover") except Exception, e: Error("Unable to change cover", e)
def upload_books(self, to, files, ids):
def update_models():
hv = self.device_view.horizontalHeader()
col = hv.sortIndicatorSection()
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()
else: model.search("")
self.window.setCursor(Qt.WaitCursor)
oncard = False if to == "reader" else True
ename = "file"
booklists = (self.reader_model._orig_data, self.card_model._orig_data)
try:
if ids:
for id in ids:
formats = []
info = self.library_view.model().book_info(id, ["title", "authors", "cover"])
ename = info["title"]
for f in files:
if re.match("......_"+str(id)+"_", f):
formats.append(f)
file = None
try:
for format in self.dev.FORMATS:
for f in formats:
if extension(format) == format:
file = f
raise StopIteration()
except StopIteration: pass
if not file:
Error("The library does not have any compatible formats for " + ename)
continue
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)
update_models()
finally: f.close()
else:
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":file, "authors":"Unknown", cover: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)
update_models()
finally: f.close()
except Exception, e:
Error("Unable to send "+ename+" to device", e)
finally: self.window.setCursor(Qt.WaitCursor)
def __init__(self, window, log_packets): def __init__(self, window, log_packets):
QObject.__init__(self) QObject.__init__(self)
@ -228,15 +292,12 @@ class MainWindow(QObject, Ui_MainWindow):
self.library_view.resizeColumnsToContents() self.library_view.resizeColumnsToContents()
# Create Device tree # 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("activated(QModelIndex)"), self.tree_clicked)
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), self.tree_clicked) QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), self.tree_clicked)
model = DeviceModel(self.device_tree) QObject.connect(model, SIGNAL('books_dropped'), self.add_books)
QObject.connect(model, SIGNAL('upload_books'), self.upload_books)
self.device_tree.setModel(model) self.device_tree.setModel(model)
self.device_tree.expand(model.indexFromItem(model.library))
self.device_tree.expand(model.indexFromItem(model.reader))
self.device_tree.expand(model.indexFromItem(model.card))
self.device_tree.hide_reader(True)
self.device_tree.hide_card(True)
# Create Device Book list # Create Device Book list
self.reader_model = DeviceBooksModel(window) self.reader_model = DeviceBooksModel(window)
@ -252,8 +313,6 @@ class MainWindow(QObject, Ui_MainWindow):
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_TEMPLATE = self.book_info.text()
self.BOOK_IMAGE = DEFAULT_BOOK_COVER
self.book_cover.hide() self.book_cover.hide()
self.book_info.hide() self.book_info.hide()
@ -266,9 +325,10 @@ class MainWindow(QObject, Ui_MainWindow):
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.device_detector = self.startTimer(1000) self.device_detector = self.startTimer(1000)
self.splitter.setStretchFactor(1,100)
self.search.setFocus(Qt.OtherFocusReason) self.search.setFocus(Qt.OtherFocusReason)
self.show_device(False) self.show_device(False)
self.df_template = self.df.text()
self.df.setText(self.df_template.arg("").arg("").arg(""))
window.show() window.show()
def timerEvent(self, e): def timerEvent(self, e):
@ -307,26 +367,27 @@ class MainWindow(QObject, Ui_MainWindow):
self.window.setCursor(Qt.WaitCursor) self.window.setCursor(Qt.WaitCursor)
self.status("Connecting to device") self.status("Connecting to device")
try: try:
space = self.dev.available_space() info = self.dev.get_device_information(end_session=False)
except DeviceError: except DeviceError:
self.dev.reconnect() self.dev.reconnect()
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 reconnecting it")
self.df.setText(self.df_template.arg(info[0]).arg(info[1]).arg(info[2]))
space = self.dev.available_space(end_session=False)
sc = space[1][1] if space[1][1] else space[2][1] sc = space[1][1] if space[1][1] else space[2][1]
self.df.setText("SONY Reader: " + human_readable(space[0][1]) + "<br><br>Storage card: " + human_readable(sc)) self.device_tree.model().update_free_space(space[0][1], sc)
self.is_connected = True self.is_connected = True
if space[1][2] > 0: card = "a:" if space[1][2] > 0: self.card = "a:"
elif space[2][2] > 0: card = "b:" elif space[2][2] > 0: self.card = "b:"
else: card = None else: self.card = None
if card: self.device_tree.hide_card(False) if self.card: self.device_tree.hide_card(False)
else: self.device_tree.hide_card(True) else: self.device_tree.hide_card(True)
self.device_tree.hide_reader(False) self.device_tree.hide_reader(False)
self.status("Loading media list from SONY Reader") self.status("Loading media list from SONY Reader")
self.reader_model.set_data(self.dev.books()) self.reader_model.set_data(self.dev.books(end_session=False))
if card: self.status("Loading media list from Storage Card") if self.card: self.status("Loading media list from Storage Card")
self.card_model.set_data(self.dev.books(oncard=True)) self.card_model.set_data(self.dev.books(oncard=True))
self.progress(100) self.progress(100)
self.window.setCursor(Qt.ArrowCursor) self.window.setCursor(Qt.ArrowCursor)
@ -344,6 +405,8 @@ def main():
global DEFAULT_BOOK_COVER global DEFAULT_BOOK_COVER
DEFAULT_BOOK_COVER = QPixmap(":/default_cover") DEFAULT_BOOK_COVER = QPixmap(":/default_cover")
window = QMainWindow() window = QMainWindow()
window.setWindowIcon(QIcon(":/icon"))
installErrorHandler(QErrorMessage(window))
QCoreApplication.setOrganizationName("KovidsBrain") QCoreApplication.setOrganizationName("KovidsBrain")
QCoreApplication.setApplicationName("SONY Reader") QCoreApplication.setApplicationName("SONY Reader")
gui = MainWindow(window, options.log_packets) gui = MainWindow(window, options.log_packets)

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>878</width> <width>728</width>
<height>759</height> <height>711</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy" > <property name="sizePolicy" >
@ -22,30 +22,15 @@
<string>SONY Reader</string> <string>SONY Reader</string>
</property> </property>
<widget class="QWidget" name="centralwidget" > <widget class="QWidget" name="centralwidget" >
<layout class="QGridLayout" > <layout class="QVBoxLayout" >
<property name="margin" > <property name="margin" >
<number>9</number> <number>9</number>
</property> </property>
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
<item row="1" column="0" > <item>
<widget class="QProgressBar" name="progress_bar" > <layout class="QHBoxLayout" >
<property name="value" >
<number>100</number>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="0" >
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget" >
<layout class="QVBoxLayout" >
<property name="margin" > <property name="margin" >
<number>0</number> <number>0</number>
</property> </property>
@ -54,33 +39,52 @@
</property> </property>
<item> <item>
<widget class="DeviceView" name="device_tree" > <widget class="DeviceView" name="device_tree" >
<property name="acceptDrops" > <property name="sizePolicy" >
<bool>true</bool> <sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>10000</width>
<height>95</height>
</size>
</property>
<property name="verticalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
<property name="dragDropMode" > <property name="dragDropMode" >
<enum>QAbstractItemView::DropOnly</enum> <enum>QAbstractItemView::DropOnly</enum>
</property> </property>
<property name="alternatingRowColors" > <property name="flow" >
<bool>true</bool> <enum>QListView::TopToBottom</enum>
</property> </property>
<property name="rootIsDecorated" > <property name="spacing" >
<bool>false</bool> <number>20</number>
</property> </property>
<property name="animated" > <property name="viewMode" >
<bool>true</bool> <enum>QListView::IconMode</enum>
</property>
<property name="allColumnsShowFocus" >
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="df" > <widget class="QLabel" name="df" >
<property name="autoFillBackground" > <property name="sizePolicy" >
<bool>false</bool> <sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>0</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="text" > <property name="text" >
<string>Main Memory:&lt;br>&lt;br>Storage card:</string> <string>&lt;b>libprs500&lt;/b> was created by &lt;b>Kovid Goyal&lt;/b> &amp;copy; 2006&lt;br>&lt;br>%1 %2 %3</string>
</property> </property>
<property name="textFormat" > <property name="textFormat" >
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
@ -88,23 +92,77 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </item>
<widget class="QWidget" name="layoutWidget" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item> <item>
<layout class="QVBoxLayout" > <layout class="QGridLayout" >
<property name="margin" > <property name="margin" >
<number>0</number> <number>0</number>
</property> </property>
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
<item row="0" column="0" >
<widget class="DeviceBooksView" name="device_view" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="dragEnabled" >
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode" >
<bool>false</bool>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid" >
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="LibraryBooksView" name="library_view" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<horstretch>0</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops" >
<bool>true</bool>
</property>
<property name="dragEnabled" >
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode" >
<bool>false</bool>
</property>
<property name="dragDropMode" >
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" >
<property name="margin" > <property name="margin" >
@ -163,68 +221,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="DeviceBooksView" name="device_view" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>13</hsizetype>
<vsizetype>13</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="dragEnabled" >
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode" >
<bool>false</bool>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid" >
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="LibraryBooksView" name="library_view" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>13</hsizetype>
<vsizetype>13</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops" >
<bool>true</bool>
</property>
<property name="dragEnabled" >
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode" >
<bool>false</bool>
</property>
<property name="dragDropMode" >
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" >
<property name="margin" > <property name="margin" >
@ -244,9 +240,6 @@
<property name="acceptDrops" > <property name="acceptDrops" >
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text" >
<string/>
</property>
<property name="pixmap" > <property name="pixmap" >
<pixmap resource="images.qrc" >:/images/cherubs.jpg</pixmap> <pixmap resource="images.qrc" >:/images/cherubs.jpg</pixmap>
</property> </property>
@ -257,16 +250,8 @@
</item> </item>
<item> <item>
<widget class="QLabel" name="book_info" > <widget class="QLabel" name="book_info" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" > <property name="text" >
<string>&lt;table>&lt;tr>&lt;td>&lt;b>Title: &lt;/b>%1&lt;/td>&lt;td>&lt;b>&amp;nbsp;Size:&lt;/b> %2&lt;/td>&lt;/tr>&lt;tr>&lt;td>&lt;b>Author: &lt;/b>%3&lt;/td>&lt;td>&lt;b>&amp;nbsp;Type: &lt;/b>%4&lt;/td>&lt;/tr></string> <string>&lt;table>&lt;tr>&lt;td>&lt;b>Title: &lt;/b>%1&lt;/td>&lt;td>&lt;b>&amp;nbsp;Size:&lt;/b> %2&lt;/td>&lt;/tr>&lt;tr>&lt;td>&lt;b>Author: &lt;/b>%3&lt;/td>&lt;td>&lt;b>&amp;nbsp;Type: &lt;/b>%4&lt;/td>&lt;/tr>&lt;/table></string>
</property> </property>
<property name="textFormat" > <property name="textFormat" >
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
@ -275,8 +260,14 @@
</item> </item>
</layout> </layout>
</item> </item>
</layout> <item>
</widget> <widget class="QProgressBar" name="progress_bar" >
<property name="value" >
<number>100</number>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -348,23 +339,23 @@
</action> </action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>DeviceBooksView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget> <customwidget>
<class>CoverDisplay</class> <class>CoverDisplay</class>
<extends>QLabel</extends> <extends>QLabel</extends>
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget>
<class>LibraryBooksView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget> <customwidget>
<class>DeviceView</class> <class>DeviceView</class>
<extends>QTreeView</extends> <extends>QListView</extends>
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>DeviceBooksView</class> <class>LibraryBooksView</class>
<extends>QTableView</extends> <extends>QTableView</extends>
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>

View File

@ -15,7 +15,7 @@
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, QStandardItemModel, QStandardItem, QVariant, QAbstractTableModel, QTableView, QTreeView, QLabel,\ 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,\ QAbstractItemView, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage, QDialog, QSpinBox, QPoint, QTemporaryFile, QDir, QFile, QIODevice,\
QPainterPath, QItemDelegate, QPainter, QPen, QColor, QLinearGradient, QBrush, QStyle,\ QPainterPath, QItemDelegate, QPainter, QPen, QColor, QLinearGradient, QBrush, QStyle,\
QStringList, QByteArray, QBuffer, QMimeData, QTextStream, QIODevice, QDrag, QRect QStringList, QByteArray, QBuffer, QMimeData, QTextStream, QIODevice, QDrag, QRect
@ -28,25 +28,12 @@ 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.lrf.meta import LRFMetaFile
from libprs500.gui import Error, Warning
NONE = QVariant() NONE = QVariant()
TIME_WRITE_FMT = "%d %b %Y" TIME_WRITE_FMT = "%d %b %Y"
COVER_HEIGHT = 80 COVER_HEIGHT = 80
def human_readable(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"
size = str(size/divisor)
if size.find(".") > -1: size = size[:size.find(".")+2]
return size + " " + suffix
def wrap(s, width=20):
return textwrap.fill(str(s), width)
class FileDragAndDrop(object): class FileDragAndDrop(object):
@ -67,11 +54,11 @@ 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':
print >>sys.stderr, o.scheme, " not supported in drop events" Warning(o.scheme + " not supported in drop events")
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):
print >>sys.stderr, "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)
@ -81,17 +68,20 @@ class FileDragAndDrop(object):
else: files.append(path) else: files.append(path)
return files return files
def __init__(self, QtBaseClass): def __init__(self, QtBaseClass, enable_drag=True):
self.QtBaseClass = QtBaseClass self.QtBaseClass = QtBaseClass
self.enable_drag = enable_drag
def mousePressEvent(self, event): def mousePressEvent(self, event):
self.QtBaseClass.mousePressEvent(self, event) self.QtBaseClass.mousePressEvent(self, event)
if self.enable_drag:
if event.button == Qt.LeftButton: if event.button == Qt.LeftButton:
self._drag_start_position = event.pos() self._drag_start_position = event.pos()
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
self.QtBaseClass.mousePressEvent(self, event) self.QtBaseClass.mousePressEvent(self, event)
if self.enable_drag:
if event.buttons() & Qt.LeftButton != Qt.LeftButton: return if event.buttons() & Qt.LeftButton != Qt.LeftButton: return
if (event.pos() - self._drag_start_position).manhattanLength() < QApplication.startDragDistance(): 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)
@ -108,9 +98,14 @@ class FileDragAndDrop(object):
def dropEvent(self, event): def dropEvent(self, event):
files = self._get_r_ok_files(event) files = self._get_r_ok_files(event)
if files: if files:
try:
if self.files_dropped(files, event): event.acceptProposedAction() if self.files_dropped(files, event): event.acceptProposedAction()
except Exception, e:
Error("There was an error processing the dropped files.", e)
raise e
def files_dropped(self, files): return False
def files_dropped(self, files, event): return False
def drag_object_from_files(self, files): def drag_object_from_files(self, files):
if files: if files:
@ -138,8 +133,26 @@ class FileDragAndDrop(object):
return self.drag_object_from_files(files), self._dragged_files return self.drag_object_from_files(files), self._dragged_files
class TableView(QTableView): class TableView(FileDragAndDrop, QTableView):
def renderToPixmap(self, indices): def __init__(self, parent):
FileDragAndDrop.__init__(self, QTableView)
QTableView.__init__(self, parent)
@classmethod
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"
size = str(size/divisor)
if size.find(".") > -1: size = size[:size.find(".")+2]
return size + " " + suffix
def render_to_pixmap(self, indices):
rect = self.visualRect(indices[0]) rect = self.visualRect(indices[0])
rects = [] rects = []
for i in range(len(indices)): for i in range(len(indices)):
@ -157,8 +170,15 @@ class TableView(QTableView):
painter.end() painter.end()
return pixmap return pixmap
def drag_object_from_files(self, files):
drag = FileDragAndDrop.drag_object_from_files(self, files)
drag.setPixmap(self.render_to_pixmap(self.selectedIndexes()))
return drag
class TemporaryFile(QTemporaryFile): class TemporaryFile(QTemporaryFile):
_file_name = "" _file_name = ""
def __del__(self):
if os.access(self.name, os.F_OK): os.remove(self.name)
def __init__(self, ext=""): def __init__(self, ext=""):
if ext: ext = "." + ext if ext: ext = "." + ext
path = QDir.tempPath() + "/" + TFT + "_XXXXXX"+ext path = QDir.tempPath() + "/" + TFT + "_XXXXXX"+ext
@ -202,31 +222,38 @@ class CoverDisplay(FileDragAndDrop, QLabel):
file.close() file.close()
drag.start(Qt.MoveAction) drag.start(Qt.MoveAction)
class DeviceView(QTreeView): class DeviceView(FileDragAndDrop, QListView):
def __init__(self, parent): def __init__(self, parent):
QTreeView.__init__(self, parent) FileDragAndDrop.__init__(self, QListView, enable_drag=False)
self.header().hide() QListView.__init__(self, parent)
self.setIconSize(QSize(32,32))
def hide_reader(self, x): def hide_reader(self, x):
self.setRowHidden(2, self.model().indexFromItem(self.model().invisibleRootItem()), x) self.model().update_devices(reader=not x)
def hide_card(self, x): def hide_card(self, x):
self.setRowHidden(4, self.model().indexFromItem(self.model().invisibleRootItem()), x) self.model().update_devices(card=not x)
def files_dropped(self, files, event):
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()]
index = self.indexAt(event.pos())
if index.isValid():
return self.model().files_dropped(files, index, ids)
class DeviceBooksView(TableView): class DeviceBooksView(TableView):
def __init__(self, parent): def __init__(self, parent):
QTableView.__init__(self, parent) TableView.__init__(self, parent)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True) self.setSortingEnabled(True)
class LibraryBooksView(FileDragAndDrop, TableView): class LibraryBooksView(TableView):
def __init__(self, parent): def __init__(self, parent):
FileDragAndDrop.__init__(self, QTableView) TableView.__init__(self, parent)
QTableView.__init__(self, parent)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.setItemDelegate(LibraryDelegate(self)) self.setItemDelegate(LibraryDelegate(self, rating_column=4))
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
if not event.mimeData().hasFormat("application/x-libprs500-id"): if not event.mimeData().hasFormat("application/x-libprs500-id"):
@ -242,7 +269,6 @@ class LibraryBooksView(FileDragAndDrop, TableView):
if drag: if drag:
ids = [ str(self.model().id_from_index(index)) for index in indexes ] ids = [ str(self.model().id_from_index(index)) for index in indexes ]
drag.mimeData().setData("application/x-libprs500-id", QByteArray("\n".join(ids))) drag.mimeData().setData("application/x-libprs500-id", QByteArray("\n".join(ids)))
drag.setPixmap(self.renderToPixmap(indexes))
drag.start() drag.start()
@ -261,8 +287,9 @@ class LibraryDelegate(QItemDelegate):
SIZE = 16 SIZE = 16
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
def __init__(self, parent): def __init__(self, parent, rating_column=-1):
QItemDelegate.__init__(self, parent ) QItemDelegate.__init__(self, parent )
self.rating_column = rating_column
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):
@ -277,13 +304,13 @@ class LibraryDelegate(QItemDelegate):
def sizeHint(self, option, index): def sizeHint(self, option, index):
if index.column() != 4: if index.column() != self.rating_column:
return QItemDelegate.sizeHint(self, option, index) return QItemDelegate.sizeHint(self, option, index)
num = index.model().data(index, Qt.DisplayRole).toInt()[0] num = index.model().data(index, Qt.DisplayRole).toInt()[0]
return QSize(num*(self.SIZE), self.SIZE+4) return QSize(num*(self.SIZE), self.SIZE+4)
def paint(self, painter, option, index): def paint(self, painter, option, index):
if index.column() != 4: if index.column() != self.rating_column:
return QItemDelegate.paint(self, painter, option, index) return QItemDelegate.paint(self, painter, option, index)
num = index.model().data(index, Qt.DisplayRole).toInt()[0] num = index.model().data(index, Qt.DisplayRole).toInt()[0]
def draw_star(): def draw_star():
@ -343,14 +370,13 @@ class LibraryDelegate(QItemDelegate):
class LibraryBooksModel(QAbstractTableModel): class LibraryBooksModel(QAbstractTableModel):
FIELDS = ["id", "title", "authors", "size", "date", "rating", "publisher", "tags"] 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)
self.db = None self.db = None
self._data = None self._data = None
self._orig_data = None self._orig_data = None
self.image_file = None
def extract_formats(self, indices): def extract_formats(self, indices):
files, rows = [], [] files, rows = [], []
@ -359,7 +385,8 @@ class LibraryBooksModel(QAbstractTableModel):
if row in rows: continue if row in rows: continue
else: rows.append(row) else: rows.append(row)
id = self.id_from_index(index) id = self.id_from_index(index)
basename = re.sub("\n", "", self._data[row]["title"]+" by "+ self._data[row]["authors"]) 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) 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)
@ -393,7 +420,7 @@ class LibraryBooksModel(QAbstractTableModel):
self.emit(SIGNAL('formats_added'), index) self.emit(SIGNAL('formats_added'), index)
def rowCount(self, parent): return len(self._data) def rowCount(self, parent): return len(self._data)
def columnCount(self, parent): return len(self.FIELDS)-2 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
@ -457,9 +484,11 @@ class LibraryBooksModel(QAbstractTableModel):
pix = QPixmap() pix = QPixmap()
pix.loadFromData(cover, "", Qt.AutoColor) pix.loadFromData(cover, "", Qt.AutoColor)
cover = None if pix.isNull() else pix cover = None if pix.isNull() else pix
au = row["authors"] tags = row["tags"]
if not au: au = "Unknown" if not tags: tags = ""
return row["title"], au, human_readable(int(row["size"])), exts, cover comments = row["comments"]
if not comments: comments = ""
return exts, tags, comments, cover
def id_from_index(self, index): return self._data[index.row()]["id"] def id_from_index(self, index): return self._data[index.row()]["id"]
@ -471,6 +500,9 @@ class LibraryBooksModel(QAbstractTableModel):
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, cols=["title", "authors", "cover"]):
return self.db.get_row_by_id(id, cols)
def data(self, index, role): def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole: if role == Qt.DisplayRole or role == Qt.EditRole:
row, col = index.row(), index.column() row, col = index.row(), index.column()
@ -481,15 +513,15 @@ class LibraryBooksModel(QAbstractTableModel):
if r < 0: r= 0 if r < 0: r= 0
if r > 5: r=5 if r > 5: r=5
return QVariant(r) return QVariant(r)
if col == 0: text = 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 = wrap(re.sub("&", "\n", au), width=25) if au : text = TableView.wrap(re.sub("&", "\n", au), width=25)
elif col == 2: text = human_readable(row["size"]) 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 == 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 = 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]:
@ -582,9 +614,9 @@ 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 = wrap(book.title, width=40) if col == 0: text = TableView.wrap(book.title, width=40)
elif col == 1: text = re.sub("&\s*","\n", book.author) elif col == 1: text = re.sub("&\s*","\n", book.author)
elif col == 2: text = human_readable(book.size) elif col == 2: text = TableView.human_readable(book.size)
elif col == 3: text = time.strftime(TIME_WRITE_FMT, book.datetime) 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]:
@ -602,7 +634,7 @@ class DeviceBooksModel(QAbstractTableModel):
except: except:
traceback.print_exc() traceback.print_exc()
au = row.author if row.author else "Unknown" au = row.author if row.author else "Unknown"
return row.title, au, 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): return lambda x : func(attrgetter(key)(x))
@ -653,30 +685,61 @@ class DeviceBooksModel(QAbstractTableModel):
class DeviceModel(QStandardItemModel): class DeviceModel(QAbstractListModel):
def __init__(self, parent):
QStandardItemModel.__init__(self, parent) memory_free = 0
root = self.invisibleRootItem() card_free = 0
show_reader = False
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))
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))
def data(self, index, role):
row = index.row()
data = NONE
if role == Qt.DisplayRole:
text = None
if row == 0: text = "Library"
if row == 1 and self.show_reader:
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)
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)
elif role == Qt.SizeHintRole:
if row == 1: return QVariant(QSize(150, 70))
elif role == Qt.FontRole:
font = QFont() font = QFont()
font.setBold(True) font.setBold(True)
self.library = QStandardItem(QIcon(":/library"), QString("Library")) data = QVariant(font)
self.reader = QStandardItem(QIcon(":/reader"), "SONY Reader") return data
self.card = QStandardItem(QIcon(":/card"), "Storage Card")
self.library.setFont(font)
self.reader.setFont(font)
self.card.setFont(font)
self.blank = QStandardItem("")
self.blank.setFlags(Qt.ItemFlags())
root.appendRow(self.library)
root.appendRow(self.blank)
root.appendRow(self.reader)
root.appendRow(self.blank.clone())
root.appendRow(self.card)
self.library.appendRow(QStandardItem("Books"))
self.reader.appendRow(QStandardItem("Books"))
self.card.appendRow(QStandardItem("Books"))
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
if self.is_library(index) and not ids:
self.emit(SIGNAL("books_dropped"), files)
ret = True
elif self.is_reader(index):
self.emit(SIGNAL("upload_books"), "reader", files, ids)
elif self.is_card(index):
self.emit(SIGNAL("upload_books"), "card", files, ids)
return ret