mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
8e4010e0f3
commit
876f994a4a
@ -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"
|
||||
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"
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
TEMPORARY_FILENAME_TEMPLATE = "libprs500_"+__version__+"_temp"
|
||||
|
@ -16,16 +16,24 @@
|
||||
from xml.dom.ext import PrettyPrint as PrettyPrint
|
||||
import xml.dom.minidom as dom
|
||||
from base64 import b64decode as decode
|
||||
from base64 import b64encode as encode
|
||||
import time
|
||||
|
||||
MIME_MAP = { "lrf":"application/x-sony-bbeb", "rtf":"application/rtf", "pdf":"application/pdf", "txt":"text/plain" }
|
||||
|
||||
class book_metadata_field(object):
|
||||
def __init__(self, attr, formatter=None):
|
||||
def __init__(self, attr, formatter=None, setter=None):
|
||||
self.attr = attr
|
||||
self.formatter = formatter
|
||||
|
||||
def __get__(self, obj, typ=None):
|
||||
""" 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()
|
||||
|
||||
def __set__(self, obj, val):
|
||||
val = self.setter(val) if self.setter else val
|
||||
obj.elem.setAttribute(self.attr, str(val))
|
||||
|
||||
class Book(object):
|
||||
title = book_metadata_field("title")
|
||||
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")
|
||||
id = book_metadata_field("id", 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
|
||||
def thumbnail():
|
||||
@ -78,4 +86,23 @@ class BookList(list):
|
||||
file.seek(0)
|
||||
self.document = dom.parse(file)
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
@ -134,6 +134,7 @@ class PRS500Device(object):
|
||||
PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
|
||||
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
|
||||
FORMATS = ["lrf", "rtf", "pdf", "txt"] #: Ordered list of supported formats
|
||||
|
||||
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))
|
||||
|
||||
@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.
|
||||
@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
|
||||
else: self.get_file(self.MEDIA_XML, file, end_session=False)
|
||||
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)
|
||||
|
||||
|
@ -37,6 +37,9 @@ class DeviceError(ProtocolError):
|
||||
class PacketError(ProtocolError):
|
||||
""" Errors with creating/interpreting packets """
|
||||
|
||||
class FreeSpaceError(ProtocolError):
|
||||
""" Errors caused when trying to put files onto an overcrowded device """
|
||||
|
||||
class ArgumentError(ProtocolError):
|
||||
""" Errors caused by invalid arguments to a public interface function """
|
||||
|
||||
|
@ -15,11 +15,35 @@
|
||||
__docformat__ = "epytext"
|
||||
__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 libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed for import_ui
|
||||
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):
|
||||
uifile = pkg_resources.resource_stream(__name__, name)
|
||||
code_string = StringIO.StringIO()
|
||||
@ -27,3 +51,5 @@ def import_ui(name):
|
||||
ui = pkg_resources.resource_filename(__name__, name)
|
||||
exec code_string.getvalue()
|
||||
return locals()[winfo["uiclass"]]
|
||||
|
||||
from libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed for import_ui
|
||||
|
@ -1,7 +1,8 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="icon">images/library.png</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="card">images/memory_stick_unmount.png</file>
|
||||
<file>images/clear.png</file>
|
||||
|
BIN
libprs500/gui/images/library.png
Normal file
BIN
libprs500/gui/images/library.png
Normal 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
@ -12,22 +12,24 @@
|
||||
## 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
|
||||
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog
|
||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString
|
||||
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog, QIcon
|
||||
from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical
|
||||
from PyQt4 import uic
|
||||
|
||||
from libprs500.communicate import PRS500Device as device
|
||||
from libprs500.errors import *
|
||||
from libprs500.lrf.meta import LRFMetaFile, LRFException
|
||||
from libprs500.gui import import_ui
|
||||
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, human_readable
|
||||
from libprs500.gui import import_ui, installErrorHandler, Error, Warning, extension
|
||||
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, TableView
|
||||
from database import LibraryDatabase
|
||||
from editbook import EditBookDialog
|
||||
|
||||
import sys, re, os, traceback
|
||||
|
||||
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>")
|
||||
|
||||
Ui_MainWindow = import_ui("main.ui")
|
||||
class MainWindow(QObject, Ui_MainWindow):
|
||||
@ -49,18 +51,19 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
|
||||
def tree_clicked(self, index):
|
||||
if index.isValid():
|
||||
text = (index.data(Qt.DisplayRole).toString())
|
||||
if "Books" in text: text = str(index.parent().data(Qt.DisplayRole).toString())
|
||||
if "Library" in text:
|
||||
self.show_device(False)
|
||||
elif "SONY Reader" in text:
|
||||
self.search.clear()
|
||||
show_dev = True
|
||||
model = self.device_tree.model()
|
||||
if model.is_library(index):
|
||||
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)
|
||||
self.show_device(True)
|
||||
elif "Storage Card" in text:
|
||||
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)
|
||||
self.show_device(True)
|
||||
self.show_device(show_dev)
|
||||
|
||||
|
||||
def model_modified(self):
|
||||
if self.library_view.isVisible(): view = self.library_view
|
||||
@ -78,14 +81,18 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
view.resizeColumnToContents(c)
|
||||
|
||||
def show_book(self, current, previous):
|
||||
title, author, size, mime, thumbnail = current.model().info(current.row())
|
||||
self.book_info.setText(self.BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime))
|
||||
if not thumbnail: thumbnail = DEFAULT_BOOK_COVER
|
||||
self.book_cover.setPixmap(thumbnail)
|
||||
try:
|
||||
name = os.path.abspath(current.model().image_file.name)
|
||||
self.book_cover.setToolTip('<img src="'+name+'">')
|
||||
except Exception, e: self.book_cover.setToolTip('<img src=":/default_cover">')
|
||||
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"
|
||||
else:
|
||||
title, author, size, mime, cover = current.model().info(current.row())
|
||||
data = DEVICE_BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)
|
||||
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_info.show()
|
||||
|
||||
@ -95,7 +102,7 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
|
||||
def delete(self, action):
|
||||
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
|
||||
self.window.setCursor(Qt.WaitCursor)
|
||||
if self.library_view.isVisible():
|
||||
@ -168,14 +175,17 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
self.add_books(files)
|
||||
|
||||
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)
|
||||
self.search.clear()
|
||||
if self.library_view.isVisible(): self.search.clear()
|
||||
else: self.library_model.search("")
|
||||
hv = self.library_view.horizontalHeader()
|
||||
col = hv.sortIndicatorSection()
|
||||
order = hv.sortIndicatorOrder()
|
||||
self.library_view.model().sort(col, order)
|
||||
self.window.setCursor(Qt.ArrowCursor)
|
||||
|
||||
|
||||
def edit(self, action):
|
||||
@ -189,15 +199,69 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
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):
|
||||
if not pix.isNull():
|
||||
try:
|
||||
self.library_view.model().update_cover(self.library_view.currentIndex(), 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):
|
||||
QObject.__init__(self)
|
||||
@ -228,15 +292,12 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
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)
|
||||
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.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
|
||||
self.reader_model = DeviceBooksModel(window)
|
||||
@ -252,8 +313,6 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
QObject.connect(model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
|
||||
|
||||
# Setup book display
|
||||
self.BOOK_TEMPLATE = self.book_info.text()
|
||||
self.BOOK_IMAGE = DEFAULT_BOOK_COVER
|
||||
self.book_cover.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)
|
||||
|
||||
self.device_detector = self.startTimer(1000)
|
||||
self.splitter.setStretchFactor(1,100)
|
||||
self.search.setFocus(Qt.OtherFocusReason)
|
||||
self.show_device(False)
|
||||
self.df_template = self.df.text()
|
||||
self.df.setText(self.df_template.arg("").arg("").arg(""))
|
||||
window.show()
|
||||
|
||||
def timerEvent(self, e):
|
||||
@ -307,26 +367,27 @@ class MainWindow(QObject, Ui_MainWindow):
|
||||
self.window.setCursor(Qt.WaitCursor)
|
||||
self.status("Connecting to device")
|
||||
try:
|
||||
space = self.dev.available_space()
|
||||
info = self.dev.get_device_information(end_session=False)
|
||||
except DeviceError:
|
||||
self.dev.reconnect()
|
||||
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(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]
|
||||
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
|
||||
if space[1][2] > 0: card = "a:"
|
||||
elif space[2][2] > 0: card = "b:"
|
||||
else: card = None
|
||||
if card: self.device_tree.hide_card(False)
|
||||
if space[1][2] > 0: self.card = "a:"
|
||||
elif space[2][2] > 0: self.card = "b:"
|
||||
else: self.card = None
|
||||
if self.card: self.device_tree.hide_card(False)
|
||||
else: self.device_tree.hide_card(True)
|
||||
self.device_tree.hide_reader(False)
|
||||
self.status("Loading media list from SONY Reader")
|
||||
self.reader_model.set_data(self.dev.books())
|
||||
if card: self.status("Loading media list from Storage Card")
|
||||
self.reader_model.set_data(self.dev.books(end_session=False))
|
||||
if self.card: self.status("Loading media list from Storage Card")
|
||||
self.card_model.set_data(self.dev.books(oncard=True))
|
||||
self.progress(100)
|
||||
self.window.setCursor(Qt.ArrowCursor)
|
||||
@ -344,6 +405,8 @@ def main():
|
||||
global DEFAULT_BOOK_COVER
|
||||
DEFAULT_BOOK_COVER = QPixmap(":/default_cover")
|
||||
window = QMainWindow()
|
||||
window.setWindowIcon(QIcon(":/icon"))
|
||||
installErrorHandler(QErrorMessage(window))
|
||||
QCoreApplication.setOrganizationName("KovidsBrain")
|
||||
QCoreApplication.setApplicationName("SONY Reader")
|
||||
gui = MainWindow(window, options.log_packets)
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>878</width>
|
||||
<height>759</height>
|
||||
<width>728</width>
|
||||
<height>711</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy" >
|
||||
@ -22,14 +22,245 @@
|
||||
<string>SONY Reader</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget" >
|
||||
<layout class="QGridLayout" >
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="1" column="0" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="DeviceView" name="device_tree" >
|
||||
<property name="sizePolicy" >
|
||||
<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 name="dragDropMode" >
|
||||
<enum>QAbstractItemView::DropOnly</enum>
|
||||
</property>
|
||||
<property name="flow" >
|
||||
<enum>QListView::TopToBottom</enum>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="viewMode" >
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="df" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy>
|
||||
<hsizetype>5</hsizetype>
|
||||
<vsizetype>0</vsizetype>
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string><b>libprs500</b> was created by <b>Kovid Goyal</b> &copy; 2006<br><br>%1 %2 %3</string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>&Search:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>search</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="whatsThis" >
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="autoFillBackground" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
<property name="frame" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clear_button" >
|
||||
<property name="toolTip" >
|
||||
<string>Reset Quick Search</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/clear.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="CoverDisplay" name="book_cover" >
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="pixmap" >
|
||||
<pixmap resource="images.qrc" >:/images/cherubs.jpg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="book_info" >
|
||||
<property name="text" >
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progress_bar" >
|
||||
<property name="value" >
|
||||
<number>100</number>
|
||||
@ -39,246 +270,6 @@
|
||||
</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" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="DeviceView" name="device_tree" >
|
||||
<property name="acceptDrops" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode" >
|
||||
<enum>QAbstractItemView::DropOnly</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="animated" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="df" >
|
||||
<property name="autoFillBackground" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Main Memory:<br><br>Storage card:</string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget" >
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>&Search:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>search</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="whatsThis" >
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="autoFillBackground" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
<property name="frame" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clear_button" >
|
||||
<property name="toolTip" >
|
||||
<string>Reset Quick Search</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/clear.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="CoverDisplay" name="book_cover" >
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>80</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap" >
|
||||
<pixmap resource="images.qrc" >:/images/cherubs.jpg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<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" >
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="tool_bar" >
|
||||
@ -348,23 +339,23 @@
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DeviceBooksView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>CoverDisplay</class>
|
||||
<extends>QLabel</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LibraryBooksView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DeviceView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<extends>QListView</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DeviceBooksView</class>
|
||||
<class>LibraryBooksView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
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,\
|
||||
QPainterPath, QItemDelegate, QPainter, QPen, QColor, QLinearGradient, QBrush, QStyle,\
|
||||
QStringList, QByteArray, QBuffer, QMimeData, QTextStream, QIODevice, QDrag, QRect
|
||||
@ -28,25 +28,12 @@ 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
|
||||
|
||||
NONE = QVariant()
|
||||
TIME_WRITE_FMT = "%d %b %Y"
|
||||
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):
|
||||
@ -67,11 +54,11 @@ class FileDragAndDrop(object):
|
||||
for url in candidates:
|
||||
o = urlparse(url)
|
||||
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
|
||||
path = unquote(o.path)
|
||||
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
|
||||
if os.path.isdir(path):
|
||||
root, dirs, files2 = os.walk(path)
|
||||
@ -81,20 +68,23 @@ class FileDragAndDrop(object):
|
||||
else: files.append(path)
|
||||
return files
|
||||
|
||||
def __init__(self, QtBaseClass):
|
||||
def __init__(self, QtBaseClass, enable_drag=True):
|
||||
self.QtBaseClass = QtBaseClass
|
||||
self.enable_drag = enable_drag
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.QtBaseClass.mousePressEvent(self, event)
|
||||
if event.button == Qt.LeftButton:
|
||||
self._drag_start_position = event.pos()
|
||||
if self.enable_drag:
|
||||
if event.button == Qt.LeftButton:
|
||||
self._drag_start_position = event.pos()
|
||||
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
self.QtBaseClass.mousePressEvent(self, event)
|
||||
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)
|
||||
if self.enable_drag:
|
||||
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
|
||||
@ -108,9 +98,14 @@ class FileDragAndDrop(object):
|
||||
def dropEvent(self, event):
|
||||
files = self._get_r_ok_files(event)
|
||||
if files:
|
||||
if self.files_dropped(files, event): event.acceptProposedAction()
|
||||
try:
|
||||
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):
|
||||
if files:
|
||||
@ -138,8 +133,26 @@ class FileDragAndDrop(object):
|
||||
return self.drag_object_from_files(files), self._dragged_files
|
||||
|
||||
|
||||
class TableView(QTableView):
|
||||
def renderToPixmap(self, indices):
|
||||
class TableView(FileDragAndDrop, QTableView):
|
||||
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])
|
||||
rects = []
|
||||
for i in range(len(indices)):
|
||||
@ -157,8 +170,15 @@ class TableView(QTableView):
|
||||
painter.end()
|
||||
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):
|
||||
_file_name = ""
|
||||
def __del__(self):
|
||||
if os.access(self.name, os.F_OK): os.remove(self.name)
|
||||
def __init__(self, ext=""):
|
||||
if ext: ext = "." + ext
|
||||
path = QDir.tempPath() + "/" + TFT + "_XXXXXX"+ext
|
||||
@ -202,31 +222,38 @@ class CoverDisplay(FileDragAndDrop, QLabel):
|
||||
file.close()
|
||||
drag.start(Qt.MoveAction)
|
||||
|
||||
class DeviceView(QTreeView):
|
||||
class DeviceView(FileDragAndDrop, QListView):
|
||||
def __init__(self, parent):
|
||||
QTreeView.__init__(self, parent)
|
||||
self.header().hide()
|
||||
self.setIconSize(QSize(32,32))
|
||||
FileDragAndDrop.__init__(self, QListView, enable_drag=False)
|
||||
QListView.__init__(self, parent)
|
||||
|
||||
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):
|
||||
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):
|
||||
def __init__(self, parent):
|
||||
QTableView.__init__(self, parent)
|
||||
TableView.__init__(self, parent)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSortingEnabled(True)
|
||||
|
||||
class LibraryBooksView(FileDragAndDrop, TableView):
|
||||
class LibraryBooksView(TableView):
|
||||
def __init__(self, parent):
|
||||
FileDragAndDrop.__init__(self, QTableView)
|
||||
QTableView.__init__(self, parent)
|
||||
TableView.__init__(self, parent)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSortingEnabled(True)
|
||||
self.setItemDelegate(LibraryDelegate(self))
|
||||
self.setItemDelegate(LibraryDelegate(self, rating_column=4))
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
if not event.mimeData().hasFormat("application/x-libprs500-id"):
|
||||
@ -242,7 +269,6 @@ class LibraryBooksView(FileDragAndDrop, TableView):
|
||||
if drag:
|
||||
ids = [ str(self.model().id_from_index(index)) for index in indexes ]
|
||||
drag.mimeData().setData("application/x-libprs500-id", QByteArray("\n".join(ids)))
|
||||
drag.setPixmap(self.renderToPixmap(indexes))
|
||||
drag.start()
|
||||
|
||||
|
||||
@ -261,8 +287,9 @@ class LibraryDelegate(QItemDelegate):
|
||||
SIZE = 16
|
||||
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
|
||||
|
||||
def __init__(self, parent):
|
||||
QItemDelegate.__init__(self, parent)
|
||||
def __init__(self, parent, rating_column=-1):
|
||||
QItemDelegate.__init__(self, parent )
|
||||
self.rating_column = rating_column
|
||||
self.star_path = QPainterPath()
|
||||
self.star_path.moveTo(90, 50)
|
||||
for i in range(1, 5):
|
||||
@ -277,13 +304,13 @@ class LibraryDelegate(QItemDelegate):
|
||||
|
||||
|
||||
def sizeHint(self, option, index):
|
||||
if index.column() != 4:
|
||||
if index.column() != self.rating_column:
|
||||
return QItemDelegate.sizeHint(self, option, index)
|
||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||
return QSize(num*(self.SIZE), self.SIZE+4)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
if index.column() != 4:
|
||||
if index.column() != self.rating_column:
|
||||
return QItemDelegate.paint(self, painter, option, index)
|
||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||
def draw_star():
|
||||
@ -343,14 +370,13 @@ class LibraryDelegate(QItemDelegate):
|
||||
|
||||
|
||||
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"
|
||||
def __init__(self, parent):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
self.db = None
|
||||
self._data = None
|
||||
self._orig_data = None
|
||||
self.image_file = None
|
||||
|
||||
def extract_formats(self, indices):
|
||||
files, rows = [], []
|
||||
@ -359,7 +385,8 @@ class LibraryBooksModel(QAbstractTableModel):
|
||||
if row in rows: continue
|
||||
else: rows.append(row)
|
||||
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)
|
||||
for ext in exts:
|
||||
fmt = self.db.get_format(id, ext)
|
||||
@ -393,7 +420,7 @@ class LibraryBooksModel(QAbstractTableModel):
|
||||
self.emit(SIGNAL('formats_added'), index)
|
||||
|
||||
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):
|
||||
done = False
|
||||
@ -457,9 +484,11 @@ class LibraryBooksModel(QAbstractTableModel):
|
||||
pix = QPixmap()
|
||||
pix.loadFromData(cover, "", Qt.AutoColor)
|
||||
cover = None if pix.isNull() else pix
|
||||
au = row["authors"]
|
||||
if not au: au = "Unknown"
|
||||
return row["title"], au, human_readable(int(row["size"])), exts, cover
|
||||
tags = row["tags"]
|
||||
if not tags: tags = ""
|
||||
comments = row["comments"]
|
||||
if not comments: comments = ""
|
||||
return exts, tags, comments, cover
|
||||
|
||||
def id_from_index(self, index): return self._data[index.row()]["id"]
|
||||
|
||||
@ -471,6 +500,9 @@ class LibraryBooksModel(QAbstractTableModel):
|
||||
break
|
||||
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):
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
row, col = index.row(), index.column()
|
||||
@ -481,15 +513,15 @@ class LibraryBooksModel(QAbstractTableModel):
|
||||
if r < 0: r= 0
|
||||
if r > 5: r=5
|
||||
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:
|
||||
au = row["authors"]
|
||||
if au : text = wrap(re.sub("&", "\n", au), width=25)
|
||||
elif col == 2: text = human_readable(row["size"])
|
||||
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 = wrap(pub, 20)
|
||||
if pub: text = TableView.wrap(pub, 20)
|
||||
if text == None: text = "Unknown"
|
||||
return QVariant(text)
|
||||
elif role == Qt.TextAlignmentRole and index.column() in [2,3,4]:
|
||||
@ -582,9 +614,9 @@ class DeviceBooksModel(QAbstractTableModel):
|
||||
if role == Qt.DisplayRole:
|
||||
row, col = index.row(), index.column()
|
||||
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 == 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)
|
||||
return QVariant(text)
|
||||
elif role == Qt.TextAlignmentRole and index.column() in [2,3]:
|
||||
@ -602,7 +634,7 @@ class DeviceBooksModel(QAbstractTableModel):
|
||||
except:
|
||||
traceback.print_exc()
|
||||
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 getter(key, func): return lambda x : func(attrgetter(key)(x))
|
||||
@ -653,30 +685,61 @@ class DeviceBooksModel(QAbstractTableModel):
|
||||
|
||||
|
||||
|
||||
class DeviceModel(QStandardItemModel):
|
||||
def __init__(self, parent):
|
||||
QStandardItemModel.__init__(self, parent)
|
||||
root = self.invisibleRootItem()
|
||||
font = QFont()
|
||||
font.setBold(True)
|
||||
self.library = QStandardItem(QIcon(":/library"), QString("Library"))
|
||||
self.reader = QStandardItem(QIcon(":/reader"), "SONY Reader")
|
||||
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"))
|
||||
class DeviceModel(QAbstractListModel):
|
||||
|
||||
memory_free = 0
|
||||
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.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 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user