The basic workflow using drag and drop is in place. You can now drop files from your file manager into the library and drop files from the library into either the main memory or the card.

I've lowered the version number from 0.3.0 to 0.3.0a1 as the GUI still has many bugs, so you need to do a python setup.py develop --uninstall and then re-issue the develop command.
This commit is contained in:
Kovid Goyal 2006-12-14 03:07:40 +00:00
parent 8e9194a575
commit 2f0de66c96
7 changed files with 276 additions and 196 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"
You may have to adjust the GROUP and the location of the rules file to suit your distribution.
"""
__version__ = "0.3.0"
__version__ = "0.3.0a1"
__docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
TEMPORARY_FILENAME_TEMPLATE = "libprs500_"+__version__+"_temp"

View File

@ -13,7 +13,7 @@
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" This module contains the logic for dealing with XML book lists found in the reader cache """
from xml.dom.ext import PrettyPrint as PrettyPrint
from xml.dom.ext import PrettyPrint
import xml.dom.minidom as dom
from base64 import b64decode as decode
from base64 import b64encode as encode
@ -25,6 +25,7 @@ class book_metadata_field(object):
def __init__(self, attr, formatter=None, setter=None):
self.attr = attr
self.formatter = formatter
self.setter = setter
def __get__(self, obj, typ=None):
""" Return a string. String may be empty if self.attr is absent """
@ -40,8 +41,10 @@ class Book(object):
mime = book_metadata_field("mime")
rpath = book_metadata_field("path")
id = book_metadata_field("id", formatter=int)
sourceid = book_metadata_field("sourceid", 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"), setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S %Z", x))
# When setting this attribute you must use an epoch
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 GMT", time.gmtime(x)))
@apply
def thumbnail():
@ -74,6 +77,23 @@ class Book(object):
def __str__(self):
return self.__repr__()
def fix_ids(media, cache):
id = 0
for child in media.root.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
child.setAttribute("id", str(id))
id += 1
mmaxid = id - 1
id = mmaxid + 2
if len(cache):
for child in cache.root.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("sourceid"):
child.setAttribute("sourceid", str(mmaxid+1))
child.setAttribute("id", str(id))
id += 1
media.document.documentElement.setAttribute("nextID", str(id))
class BookList(list):
__getslice__ = None
__setslice__ = None
@ -82,27 +102,62 @@ class BookList(list):
list.__init__(self)
if file:
self.prefix = prefix
self.root = root
self.proot = root
file.seek(0)
self.document = dom.parse(file)
self.root = self.document.documentElement #: The root element containing all records
if prefix == "xs1:": self.root = self.root.getElementsByTagName("records")[0]
for book in self.document.getElementsByTagName(self.prefix + "text"): self.append(Book(book, root=root, prefix=prefix))
def add_book(self, info, name):
root = self.document.documentElement
def max_id(self):
id = -1
for child in self.root.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.hasAttribute("id"):
c = int(child.getAttribute("id"))
if c > id: id = c
return id
def has_id(self, id):
""" Check if a book with id C{ == id} exists already. This *does not* check if id exists in the underlying XML file """
ans = False
for book in self:
if book.id == id:
ans = True
break
return ans
def delete_book(self, id):
node = None
for book in self:
if book.id == id:
node = book
self.remove(book)
break
node.elem.parentNode.removeChild(node.elem)
node.elem.unlink()
def add_book(self, info, name, size, ctime):
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():
id = self.max_id()+1
sourceid = str(self[0].sourceid) if len(self) else "1"
attrs = { "title":info["title"], "author":info["authors"], "page":"0", "part":"0", "scale":"0", "sourceid":sourceid, "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()
w, h, data = info["cover"]
if data:
th = self.document.createElement(self.prefix + "thumbnail")
th.setAttribute("width", str(w))
th.setAttribute("height", str(h))
jpeg = self.document.createElement(self.prefix + "jpeg")
jpeg.appendChild(self.document.createTextNode(encode(data)))
th.appendChild(jpeg)
node.appendChild(th)
self.root.appendChild(node)
book = Book(node, root=self.proot, prefix=self.prefix)
book.datetime = ctime
self.append(book)
def write(self, stream):
PrettyPrint(self.document, stream)

View File

@ -651,9 +651,9 @@ class PRS500Device(object):
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]
space = self.free_space(end_session=False)
mspace = space[0]
cspace = space[1] if space[1] >= space[2] else space[2]
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/"

View File

@ -17,8 +17,8 @@ __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
APP_TITLE = "libprs500"
import pkg_resources, sys, os, re, StringIO, traceback
from PyQt4 import QtCore, QtGui # Needed for classes imported with import_ui
from PyQt4.uic.Compiler import compiler
from PyQt4 import QtCore, QtGui # Needed in globals() for import_ui
error_dialog = None
@ -45,7 +45,7 @@ def Error(msg, e):
error_dialog.showMessage(msg)
error_dialog.show()
def import_ui(name):
def import_ui(name):
uifile = pkg_resources.resource_stream(__name__, name)
code_string = StringIO.StringIO()
winfo = compiler.UICompiler().compileUi(uifile, code_string)
@ -53,4 +53,4 @@ def import_ui(name):
exec code_string.getvalue()
return locals()[winfo["uiclass"]]
from libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed for import_ui
from libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed in globals() for import_ui

View File

@ -12,12 +12,13 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString, QBuffer, QIODevice, QModelIndex
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog, QIcon
from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical
from PyQt4 import uic
from libprs500.communicate import PRS500Device as device
from libprs500.books import fix_ids
from libprs500.errors import *
from libprs500.lrf.meta import LRFMetaFile, LRFException
from libprs500.gui import import_ui, installErrorHandler, Error, Warning, extension, APP_TITLE
@ -36,7 +37,7 @@ class MainWindow(QObject, Ui_MainWindow):
def show_device(self, yes):
""" If C{yes} show the items on the device otherwise show the items in the library """
self.device_view.clearSelection(), self.library_view.clearSelection()
self.device_view.selectionModel().reset(), self.library_view.selectionModel().reset()
self.book_cover.hide(), self.book_info.hide()
if yes:
self.device_view.show(), self.library_view.hide()
@ -80,7 +81,7 @@ class MainWindow(QObject, Ui_MainWindow):
for c in range(topleft.column(), bottomright.column()+1):
view.resizeColumnToContents(c)
def show_book(self, current, previous):
def show_book(self, current, previous):
if self.library_view.isVisible():
formats, tags, comments, cover = current.model().info(current.row())
data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
@ -101,50 +102,28 @@ class MainWindow(QObject, Ui_MainWindow):
self.show_book(index, index)
def delete(self, action):
count = str(len(self.current_view.selectionModel().selectedRows()))
rows = self.current_view.selectionModel().selectedRows()
if not len(rows): return
count = str(len(rows))
ret = QMessageBox.question(self.window, self.trUtf8(APP_TITLE + " - confirm"), self.trUtf8("Are you sure you want to <b>permanently delete</b> these ") +count+self.trUtf8(" item(s)?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret != QMessageBox.Yes: return
self.window.setCursor(Qt.WaitCursor)
if self.library_view.isVisible():
self.library_model.delete(self.library_view.selectionModel().selectedRows())
else:
rows = self.device_view.selectionModel().selectedRows()
items = [ row.model().title(row) + ": " + row.model().path(row)[row.model().path(row).rfind("/")+1:] for row in rows ]
if ret == QMessageBox.YesToAll:
paths, mc, cc = [], False, False
for book in rows:
path = book.model().path(book)
if path[0] == "/": file, prefix, mc = self.main_xml, "xs1:", True
else: file, prefix, cc = self.cache_xml, "", True
file.seek(0)
document = dom.parse(file)
books = document.getElementsByTagName(prefix + "text")
for candidate in books:
if candidate.attributes["path"].value in path:
paths.append(path)
candidate.parentNode.removeChild(candidate)
break
file.close()
file = TemporaryFile()
PrettyPrint(document, file)
if len(prefix) > 0: self.main_xml = file
else: self.cache_xml = file
for path in paths:
self.dev.del_file(path)
model.delete_by_path(path)
self.cache_xml.seek(0)
self.main_xml.seek(0)
self.status("Files deleted. Updating media list on device")
if mc:
self.dev.del_file(self.dev.MEDIA_XML)
self.dev.put_file(self.main_xml, self.dev.MEDIA_XML)
if cc:
self.dev.del_file(self.card+self.dev.CACHE_XML)
self.dev.put_file(self.cache_xml, self.card+self.dev.CACHE_XML)
self.status("Deleting files from device")
paths = self.device_view.model().delete(rows)
for path in paths:
self.status("Deleting "+path[path.rfind("/")+1:])
self.dev.del_file(path, end_session=False)
fix_ids(self.reader_model.booklist, self.card_model.booklist)
self.status("Syncing media list to reader")
self.dev.upload_book_list(self.reader_model.booklist)
if len(self.card_model.booklist):
self.status("Syncing media list to card")
self.dev.upload_book_list(self.card_model.booklist)
self.update_availabe_space()
self.show_book(self.current_view.currentIndex(), QModelIndex())
self.window.setCursor(Qt.ArrowCursor)
def read_settings(self):
@ -207,6 +186,8 @@ class MainWindow(QObject, Ui_MainWindow):
except Exception, e: Error("Unable to change cover", e)
def upload_books(self, to, files, ids):
oncard = False if to == "reader" else True
booklists = (self.reader_model.booklist, self.card_model.booklist)
def update_models():
hv = self.device_view.horizontalHeader()
col = hv.sortIndicatorSection()
@ -216,52 +197,82 @@ class MainWindow(QObject, Ui_MainWindow):
if self.device_view.isVisible() and self.device_view.model() == model: self.search.clear()
else: model.search("")
def sync_lists():
self.status("Syncing media list to device main memory")
self.dev.upload_book_list(booklists[0])
if len(booklists[1]):
self.status("Syncing media list to storage card")
self.dev.upload_book_list(booklists[1])
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)
ename = "file"
try:
if ids:
for id in ids:
formats = []
info = self.library_view.model().book_info(id, ["title", "authors", "cover"])
info = self.library_view.model().book_info(id)
if info["cover"]:
pix = QPixmap()
pix.loadFromData(str(info["cover"]))
if pix.isNull(): pix = DEFAULT_BOOK_COVER
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, Qt.SmoothTransformation)
buffer = QBuffer()
buffer.open(QIODevice.WriteOnly)
pix.save(buffer, "JPEG")
info["cover"] = (pix.width(), pix.height(), str(buffer.buffer()))
ename = info["title"]
for f in files:
if re.match("......_"+str(id)+"_", f):
if re.match("......_"+str(id)+"_", os.path.basename(f)):
formats.append(f)
file = None
try:
for format in self.dev.FORMATS:
for f in formats:
if extension(format) == format:
if extension(f) == format:
file = f
raise StopIteration()
except StopIteration: pass
if not file:
Error("The library does not have any compatible formats for " + ename)
Error("The library does not have any formats that can be viewed on the device for " + ename, None)
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)
self.dev.add_book(f, "libprs500_"+str(id)+"."+extension(file), info, booklists, oncard=oncard, end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + " already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
else:
for file in files:
ename = file
if extension(file) not in self.dev.FORMATS:
Error(ename + " is not in a supported format")
continue
info = { "title":file, "authors":"Unknown", cover:None }
info = { "title":os.path.basename(file), "authors":"Unknown", "cover":(None, None, None) }
f = open(file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, os.path.basename(file), info, booklists, oncard=oncard)
self.dev.add_book(f, os.path.basename(file), info, booklists, oncard=oncard, end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + " already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
except Exception, e:
Error("Unable to send "+ename+" to device", e)
finally: self.window.setCursor(Qt.WaitCursor)
finally:
self.window.setCursor(Qt.ArrowCursor)
self.update_availabe_space()
def __init__(self, window, log_packets):
QObject.__init__(self)
@ -377,13 +388,9 @@ class MainWindow(QObject, Ui_MainWindow):
traceback.print_exc(e)
qFatal("Unable to connect to device. Please try unplugging and reconnecting it")
self.df.setText(self.df_template.arg("Connected: "+info[0]).arg(info[1]).arg(info[2]))
space = self.dev.available_space(end_session=False)
sc = space[1][1] if space[1][1] else space[2][1]
self.device_tree.model().update_free_space(space[0][1], sc)
self.is_connected = True
if space[1][2] > 0: self.card = "a:"
elif space[2][2] > 0: self.card = "b:"
else: self.card = None
self.update_availabe_space(end_session=False)
self.card = self.dev.card()
self.is_connected = True
if self.card: self.device_tree.hide_card(False)
else: self.device_tree.hide_card(True)
self.device_tree.hide_reader(False)
@ -394,6 +401,20 @@ class MainWindow(QObject, Ui_MainWindow):
self.progress(100)
self.window.setCursor(Qt.ArrowCursor)
def update_availabe_space(self, end_session=True):
space = self.dev.free_space(end_session=end_session)
sc = space[1] if int(space[1])>0 else space[2]
self.device_tree.model().update_free_space(space[0], sc)
class LockFile(object):
def __init__(self, path):
self.path = path
f =open(path, "w")
f.close()
def __del__(self):
if os.access(self.path, os.F_OK): os.remove(self.path)
def main():
from optparse import OptionParser
from libprs500 import __version__ as VERSION
@ -418,11 +439,7 @@ def main():
QCoreApplication.setOrganizationName("KovidsBrain")
QCoreApplication.setApplicationName(APP_TITLE)
gui = MainWindow(window, options.log_packets)
f = open(lock, "w")
f.close()
try:
ret = app.exec_()
finally: os.remove(lock)
return ret
lock = LockFile(lock)
return app.exec_()
if __name__ == "__main__": main()
if __name__ == "__main__": sys.exit(main())

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>728</width>
<height>711</height>
<height>822</height>
</rect>
</property>
<property name="sizePolicy" >
@ -99,6 +99,64 @@
</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>&amp;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&lt;br>&lt;br>Words separated by spaces are ANDed</string>
</property>
<property name="whatsThis" >
<string>Search the list of books by title or author&lt;br>&lt;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="QGridLayout" >
<property name="margin" >
@ -169,64 +227,6 @@
</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>&amp;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&lt;br>&lt;br>Words separated by spaces are ANDed</string>
</property>
<property name="whatsThis" >
<string>Search the list of books by title or author&lt;br>&lt;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" >
@ -348,6 +348,11 @@
</action>
</widget>
<customwidgets>
<customwidget>
<class>LibraryBooksView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>DeviceBooksView</class>
<extends>QTableView</extends>
@ -363,11 +368,6 @@
<extends>QListView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>LibraryBooksView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="images.qrc" />

View File

@ -12,6 +12,16 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import re, os, string, textwrap, time, traceback, sys
from operator import itemgetter, attrgetter
from socket import gethostname
from urlparse import urlparse, urlunparse
from urllib import quote, unquote
from math import sin, cos, pi
from libprs500 import TEMPORARY_FILENAME_TEMPLATE as TFT
from libprs500.lrf.meta import LRFMetaFile
from libprs500.gui import Error, Warning
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import Qt, SIGNAL
@ -19,22 +29,9 @@ from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, QVariant,
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
import re, os, string, textwrap, time, traceback, sys
from operator import itemgetter, attrgetter
from socket import gethostname
from urlparse import urlparse, urlunparse
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
NONE = QVariant() #: Null value to return from the data function of item models
TIME_WRITE_FMT = "%d %b %Y" #: The display format used to show dates
class FileDragAndDrop(object):
_drag_start_position = QPoint()
@ -99,7 +96,8 @@ class FileDragAndDrop(object):
files = self._get_r_ok_files(event)
if files:
try:
if self.files_dropped(files, event): event.acceptProposedAction()
event.setDropAction(Qt.CopyAction)
if self.files_dropped(files, event): event.accept()
except Exception, e:
Error("There was an error processing the dropped files.", e)
raise e
@ -116,7 +114,7 @@ class FileDragAndDrop(object):
urls.append(urlunparse(('file', quote(gethostname()), quote(str(file.name)), '','','')))
self._dragged_files.append(file)
mime_data.setData("text/uri-list", QByteArray("\n".join(urls)))
user = os.getenv['USER']
user = os.getenv('USER')
if user: mime_data.setData("text/x-xdnd-username", QByteArray(user))
drag.setMimeData(mime_data)
return drag
@ -131,7 +129,7 @@ class FileDragAndDrop(object):
return self.drag_object_from_files(files), self._dragged_files
class TableView(FileDragAndDrop, QTableView):
class TableView(FileDragAndDrop, QTableView):
def __init__(self, parent):
FileDragAndDrop.__init__(self, QTableView)
QTableView.__init__(self, parent)
@ -261,11 +259,11 @@ class LibraryBooksView(TableView):
def start_drag(self, pos):
index = self.indexAt(pos)
if index.isValid():
indexes = self.selectedIndexes()
files = self.model().extract_formats(indexes)
rows = frozenset([ index.row() for index in self.selectedIndexes() ])
files = self.model().extract_formats(rows)
drag = self.drag_object_from_files(files)
if drag:
ids = [ str(self.model().id_from_index(index)) for index in indexes ]
ids = [ str(self.model().id_from_row(row)) for row in rows ]
drag.mimeData().setData("application/x-libprs500-id", QByteArray("\n".join(ids)))
drag.start()
@ -376,13 +374,10 @@ class LibraryBooksModel(QAbstractTableModel):
self._data = None
self._orig_data = None
def extract_formats(self, indices):
files, rows = [], []
for index in indices:
row = index.row()
if row in rows: continue
else: rows.append(row)
id = self.id_from_index(index)
def extract_formats(self, rows):
files = []
for row in rows:
id = self.id_from_row(row)
au = self._data[row]["authors"] if self._data[row]["authors"] else "Unknown"
basename = re.sub("\n", "", "_"+str(id)+"_"+self._data[row]["title"]+" by "+ au)
exts = self.db.get_extensions(id)
@ -489,6 +484,7 @@ class LibraryBooksModel(QAbstractTableModel):
return exts, tags, comments, cover
def id_from_index(self, index): return self._data[index.row()]["id"]
def id_from_row(self, row): return self._data[row]["id"]
def refresh_row(self, row):
self._data[row] = self.db.get_row_by_id(self._data[row]["id"], self.FIELDS)
@ -498,8 +494,12 @@ 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 book_info(self, id):
""" Return title, authors and cover in a dict """
cover = self.db.get_cover(id)
info = self.db.get_row_by_id(id, ["title", "authors"])
info["cover"] = cover
return info
def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole:
@ -583,6 +583,13 @@ class LibraryBooksModel(QAbstractTableModel):
self._orig_data.append(self.db.get_row_by_id(id, self.FIELDS))
class DeviceBooksModel(QAbstractTableModel):
@apply
def booklist():
doc = """ The booklist this model is based on """
def fget(self):
return self._orig_data
return property(**locals())
def __init__(self, parent):
QAbstractTableModel.__init__(self, parent)
self._data = []
@ -662,19 +669,20 @@ class DeviceBooksModel(QAbstractTableModel):
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("searched()"))
def delete_by_path(self, path):
def delete(self, indices):
paths = []
rows = [ index.row() for index in indices ]
if not rows: return
self.emit(SIGNAL("layoutAboutToBeChanged()"))
index = -1
for book in self._data:
if path in book["path"]:
self._data.remove(book)
break
for book in self._orig_data:
if path in book["path"]:
self._orig_data.remove(book)
break
elems = [ self._data[row] for row in rows ]
for e in elems:
id = e.id
paths.append(e.path)
self._orig_data.delete_book(id)
try: self._data.remove(e)
except ValueError: pass
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("deleted()"))
return paths
def path(self, index): return self._data[index.row()].path
def title(self, index): return self._data[index.row()].title
@ -709,9 +717,9 @@ class DeviceModel(QAbstractListModel):
text = None
if row == 0: text = "Library"
if row == 1 and self.show_reader:
text = "Reader\n" + TableView.human_readable(self.memory_free) + " available"
text = "Reader\n" + TableView.human_readable(self.memory_free) + " available"
elif row == 2 and self.show_card:
text = "Card\n" + TableView.human_readable(self.card_free) + " available"
text = "Card\n" + TableView.human_readable(self.card_free) + " available"
if text: data = QVariant(text)
elif role == Qt.DecorationRole:
icon = None