Bye bye old GUI

This commit is contained in:
Kovid Goyal 2007-09-20 04:29:28 +00:00
parent 50a80a31df
commit 110599454f
23 changed files with 0 additions and 10946 deletions

View File

@ -1,48 +0,0 @@
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" The GUI to libprs500. Also has ebook library management features. """
__docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
APP_TITLE = "libprs500"
import sys, os, re, StringIO, traceback
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(APP_TITLE + " - Error")
error_dialog.setModal(True)
def _Warning(msg, e):
print >> sys.stderr, msg
if e:
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()

View File

@ -1,312 +0,0 @@
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## 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.
"""
Backend that implements storage of ebooks in an sqlite database.
"""
import sqlite3 as sqlite
import os
from zlib import compress, decompress
from stat import ST_SIZE
from libprs500.ebooks.lrf.meta import LRFMetaFile, LRFException
from libprs500.ebooks.metadata.meta import get_metadata
from cStringIO import StringIO as cStringIO
class LibraryDatabase(object):
BOOKS_SQL = \
"""
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT,
authors TEXT, publisher TEXT, size INTEGER, tags TEXT,
date DATE DEFAULT CURRENT_TIMESTAMP,
comments TEXT, rating INTEGER);
create table if not exists books_data(id INTEGER, extension TEXT,
uncompressed_size INTEGER, data BLOB);
create table if not exists books_cover(id INTEGER,
uncompressed_size INTEGER, data BLOB);
"""
def __init__(self, dbpath):
self.con = sqlite.connect(dbpath)
# Allow case insensitive field access by name
self.con.row_factory = sqlite.Row
self.con.executescript(LibraryDatabase.BOOKS_SQL)
def get_cover(self, _id):
raw = self.con.execute("select data from books_cover where id=?", \
(_id,)).next()["data"]
return decompress(str(raw)) if raw else None
def get_extensions(self, _id):
exts = []
cur = self.con.execute("select extension from books_data where id=?", \
(_id,))
for row in cur:
exts.append(row["extension"])
return exts
def add_book(self, path):
_file = os.path.abspath(path)
title, size, cover = os.path.basename(_file), \
os.stat(_file)[ST_SIZE], None
ext = title[title.rfind(".")+1:].lower() if title.find(".") > -1 else None
f = open(_file, "r+b")
mi = get_metadata(f, ext)
tags = []
if not mi.title:
mi.title = title
if mi.category:
tags.append(mi.category)
if tags:
tags = ', '.join(tags)
else:
tags = None
f.seek(0)
data = f.read()
f.close()
usize = len(data)
data = compress(data)
csize = 0
if cover:
csize = len(cover)
cover = sqlite.Binary(compress(cover))
self.con.execute("insert into books_meta (title, authors, publisher, "+\
"size, tags, comments, rating) values "+\
"(?,?,?,?,?,?,?)", \
(mi.title, mi.author, mi.publisher, size, tags, \
mi.comments, None))
_id = self.con.execute("select max(id) from books_meta").next()[0]
self.con.execute("insert into books_data values (?,?,?,?)", \
(_id, ext, usize, sqlite.Binary(data)))
self.con.execute("insert into books_cover values (?,?,?)", \
(_id, csize, cover))
self.con.commit()
return _id
def get_row_by_id(self, _id, columns):
"""
Return C{columns} of meta data as a dict.
@param columns: list of column names
"""
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta where id=?"\
, (_id,))
row, r = cur.next(), {}
for c in columns:
r[c] = row[c]
return r
def commit(self):
self.con.commit()
def delete_by_id(self, _id):
self.con.execute("delete from books_meta where id=?", (_id,))
self.con.execute("delete from books_data where id=?", (_id,))
self.con.execute("delete from books_cover where id=?", (_id,))
self.commit()
def get_table(self, columns):
""" Return C{columns} of the metadata table as a list of dicts. """
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta")
rows = []
for row in cur:
r = {}
for c in columns:
r[c] = row[c]
rows.append(r)
return rows
def get_format(self, _id, ext):
"""
Return format C{ext} corresponding to the logical book C{id} or
None if the format is unavailable.
Format is returned as a string of binary data suitable for
C{ file.write} operations.
"""
ext = ext.lower()
cur = self.con.execute("select data from books_data where id=? and "+\
"extension=?",(_id, ext))
try:
data = cur.next()
except:
pass
else:
return decompress(str(data["data"]))
def remove_format(self, _id, ext):
""" Remove format C{ext} from book C{_id} """
self.con.execute("delete from books_data where id=? and extension=?", \
(_id, ext))
self.update_max_size(_id)
self.con.commit()
def add_format(self, _id, ext, data):
"""
If data for format ext already exists, it is replaced
@type ext: string or None
@type data: string or file object
"""
try:
data.seek(0)
data = data.read()
except AttributeError:
pass
metadata = self.get_metadata(_id)
if ext:
ext = ext.strip().lower()
if ext == "lrf":
s = cStringIO()
print >> s, data
try:
lrf = LRFMetaFile(s)
lrf.author = metadata["authors"]
lrf.title = metadata["title"]
# Not sure if I want to override the lrf freetext field
# with a possibly null value
#lrf.free_text = metadata["comments"]
except LRFException:
pass
data = s.getvalue()
s.close()
size = len(data)
data = sqlite.Binary(compress(data))
cur = self.con.execute("select extension from books_data where id=? "+\
"and extension=?", (_id, ext))
present = True
try:
cur.next()
except:
present = False
if present:
self.con.execute("update books_data set uncompressed_size=? \
where id=? and extension=?", (size, _id, ext))
self.con.execute("update books_data set data=? where id=? "+\
"and extension=?", (data, _id, ext))
else:
self.con.execute("insert into books_data \
(id, extension, uncompressed_size, data) values (?, ?, ?, ?)", \
(_id, ext, size, data))
oldsize = self.get_row_by_id(_id, ['size'])['size']
if size > oldsize:
self.con.execute("update books_meta set size=? where id=? ", \
(size, _id))
self.con.commit()
def get_metadata(self, _id):
""" Return metadata in a dict """
try:
row = self.con.execute("select * from books_meta where id=?", \
(_id,)).next()
except StopIteration:
return None
data = {}
for field in ("id", "title", "authors", "publisher", "size", "tags",
"date", "comments"):
data[field] = row[field]
return data
def set_metadata(self, _id, title=None, authors=None, rating=None, \
publisher=None, tags=None, comments=None):
"""
Update metadata fields for book C{_id}. Metadata is not updated
in formats. See L{set_metadata_item}.
"""
if authors and not len(authors):
authors = None
if publisher and not len(publisher):
publisher = None
if tags and not len(tags):
tags = None
if comments and not len(comments):
comments = None
self.con.execute('update books_meta set title=?, authors=?, '+\
'publisher=?, tags=?, comments=?, rating=? '+\
'where id=?', \
(title, authors, publisher, tags, comments, \
rating, _id))
self.con.commit()
def set_metadata_item(self, _id, col, val):
"""
Convenience method used to set metadata. Metadata is updated
automatically in supported formats.
@param col: If it is either 'title' or 'authors' the value is updated
in supported formats as well.
"""
self.con.execute('update books_meta set '+col+'=? where id=?', \
(val, _id))
if col in ["authors", "title"]:
lrf = self.get_format(_id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
if col == "authors":
lrf.authors = val
else: lrf.title = val
self.add_format(_id, "lrf", c.getvalue())
self.con.commit()
def update_cover(self, _id, cover, scaled=None):
"""
Update the stored cover. The cover is updated in supported formats
as well.
@param cover: The cover data
@param scaled: scaled version of cover that shoould be written to
format files. If None, cover is used.
"""
data = None
size = 0
if cover:
size = len(cover)
data = sqlite.Binary(compress(cover))
self.con.execute('update books_cover set uncompressed_size=?, data=? \
where id=?', (size, data, _id))
if not scaled:
scaled = cover
if scaled:
lrf = self.get_format(_id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
lrf.thumbnail = scaled
self.add_format(_id, "lrf", c.getvalue())
self.update_max_size(_id)
self.commit()
def update_max_size(self, _id):
cur = self.con.execute("select uncompressed_size from books_data \
where id=?", (_id,))
maxsize = 0
for row in cur:
maxsize = row[0] if row[0] > maxsize else maxsize
self.con.execute("update books_meta set size=? where id=? ", \
(maxsize, _id))
self.con.commit()
#if __name__ == "__main__":
# lbm = LibraryDatabase("/home/kovid/library.db")
# lbm.add_book("/home/kovid/documents/ebooks/hobbfar01.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbfar02.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbfar03.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobblive01.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbtawny01.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbtawny02.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbtawny03.lrf")
# print lbm.get_table(["id","title"])

View File

@ -1,166 +0,0 @@
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
The dialog used to edit meta information for a book as well as
add/remove formats
"""
import os
from PyQt4.QtCore import Qt, SIGNAL
from PyQt4.Qt import QObject, QPixmap, QListWidgetItem, QErrorMessage, \
QVariant, QSettings, QFileDialog
from libprs500.gui import extension
from libprs500.gui.editbook_ui import Ui_BookEditDialog
class Format(QListWidgetItem):
def __init__(self, parent, ext, path=None):
self.path = path
self.ext = ext
QListWidgetItem.__init__(self, ext.upper(), parent, \
QListWidgetItem.UserType)
class EditBookDialog(Ui_BookEditDialog):
def select_cover(self, checked):
settings = QSettings()
_dir = settings.value("change cover dir", \
QVariant(os.path.expanduser("~"))).toString()
_file = str(QFileDialog.getOpenFileName(self.parent, \
"Choose cover for " + str(self.title.text()), _dir, \
"Images (*.png *.gif *.jpeg *.jpg);;All files (*)"))
if len(_file):
_file = os.path.abspath(_file)
settings.setValue("change cover dir", \
QVariant(os.path.dirname(_file)))
if not os.access(_file, os.R_OK):
QErrorMessage(self.parent).showMessage("You do not have "+\
"permission to read the file: " + _file)
return
cf, cover = None, None
try:
cf = open(_file, "rb")
cover = cf.read()
except IOError, e:
QErrorMessage(self.parent).showMessage("There was an error"+\
" reading from file: " + _file + "\n"+str(e))
if cover:
pix = QPixmap()
pix.loadFromData(cover, "", Qt.AutoColor)
if pix.isNull():
QErrorMessage(self.parent).showMessage(_file + \
" is not a valid picture")
else:
self.cover_path.setText(_file)
self.cover.setPixmap(pix)
def add_format(self, x):
settings = QSettings()
_dir = settings.value("add formats dialog dir", \
QVariant(os.path.expanduser("~"))).toString()
files = QFileDialog.getOpenFileNames(self.parent, \
"Choose formats for " + str(self.title.text()), _dir, \
"Books (*.lrf *.lrx *.rtf *.txt *.html *.xhtml *.htm *.rar);;"+\
"All files (*)")
if not files.isEmpty():
x = str(files[0])
settings.setValue("add formats dialog dir", \
QVariant(os.path.dirname(x)))
files = str(files.join("|||")).split("|||")
for _file in files:
_file = os.path.abspath(_file)
if not os.access(_file, os.R_OK):
QErrorMessage(self.parent).showMessage("You do not have "+\
"permission to read the file: " + _file)
continue
ext = extension(_file)
for row in range(self.formats.count()):
fmt = self.formats.item(row)
if fmt.ext == ext:
self.formats.takeItem(row)
break
Format(self.formats, ext, path=_file)
self.formats_changed = True
def remove_format(self, x):
rows = self.formats.selectionModel().selectedRows(0)
for row in rows:
self.formats.takeItem(row.row())
self.formats_changed = True
def sync_formats(self):
old_extensions, new_extensions, paths = set(), set(), {}
for row in range(self.formats.count()):
fmt = self.formats.item(row)
ext, path = fmt.ext, fmt.path
if "unknown" in ext.lower():
ext = None
if path:
new_extensions.add(ext)
paths[ext] = path
else:
old_extensions.add(ext)
for ext in new_extensions:
self.db.add_format(self.id, ext, file(paths[ext], "rb"))
db_extensions = self.db.get_extensions(self.id)
extensions = new_extensions.union(old_extensions)
for ext in db_extensions:
if ext not in extensions:
self.db.remove_format(self.id, ext)
self.db.update_max_size(self.id)
def __init__(self, dialog, _id, db):
Ui_BookEditDialog.__init__(self)
self.parent = dialog
self.setupUi(dialog)
self.splitter.setStretchFactor(100, 1)
self.db = db
self.id = _id
self.cover_data = None
self.formats_changed = False
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
self.select_cover)
QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), \
self.add_format)
QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \
self.remove_format)
QObject.connect(self.button_box, SIGNAL("accepted()"), \
self.sync_formats)
data = self.db.get_row_by_id(self.id, \
["title","authors","rating","publisher","tags","comments"])
self.title.setText(data["title"])
self.authors.setText(data["authors"] if data["authors"] else "")
self.publisher.setText(data["publisher"] if data["publisher"] else "")
self.tags.setText(data["tags"] if data["tags"] else "")
if data["rating"] > 0:
self.rating.setValue(data["rating"])
self.comments.setPlainText(data["comments"] if data["comments"] else "")
cover = self.db.get_cover(self.id)
if cover:
pm = QPixmap()
pm.loadFromData(cover, "", Qt.AutoColor)
if not pm.isNull():
self.cover.setPixmap(pm)
else:
self.cover.setPixmap(QPixmap(":/default_cover"))
else:
self.cover.setPixmap(QPixmap(":/default_cover"))
exts = self.db.get_extensions(self.id)
for ext in exts:
if not ext:
ext = "Unknown"
Format(self.formats, ext)

View File

@ -1,427 +0,0 @@
<ui version="4.0" >
<class>BookEditDialog</class>
<widget class="QDialog" name="BookEditDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>865</width>
<height>776</height>
</rect>
</property>
<property name="windowTitle" >
<string>SONY Reader - Edit Meta Information</string>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<number>9</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item row="0" column="0" >
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Meta information</string>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<number>9</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item row="2" column="1" colspan="2" >
<widget class="QSpinBox" name="rating" >
<property name="toolTip" >
<string>Rating of this book. 0-5 stars</string>
</property>
<property name="whatsThis" >
<string>Rating of this book. 0-5 stars</string>
</property>
<property name="buttonSymbols" >
<enum>QAbstractSpinBox::PlusMinus</enum>
</property>
<property name="suffix" >
<string> stars</string>
</property>
<property name="maximum" >
<number>5</number>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_6" >
<property name="text" >
<string>&amp;Rating:</string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2" >
<widget class="QLineEdit" name="publisher" >
<property name="toolTip" >
<string>Change the publisher of this book</string>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>&amp;Publisher: </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>publisher</cstring>
</property>
</widget>
</item>
<item row="4" column="0" >
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>Ta&amp;gs: </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>tags</cstring>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2" >
<widget class="QLineEdit" name="tags" >
<property name="toolTip" >
<string>Tags categorize the book. This is particularly useful while searching. &lt;br>&lt;br>They can be any words or phrases, separated by commas.</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2" >
<widget class="QLineEdit" name="authors" >
<property name="toolTip" >
<string>Change the author(s) of this book. Multiple authors should be separated by the &amp; character</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="title" >
<property name="toolTip" >
<string>Change the title of this book</string>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>&amp;Author(s): </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>authors</cstring>
</property>
</widget>
</item>
<item row="0" column="0" >
<widget class="QLabel" name="label" >
<property name="text" >
<string>&amp;Title: </string>
</property>
<property name="alignment" >
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy" >
<cstring>title</cstring>
</property>
</widget>
</item>
<item row="6" column="2" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Change &amp;cover image:</string>
</property>
<property name="buddy" >
<cstring>cover_path</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<widget class="QLineEdit" name="cover_path" >
<property name="readOnly" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="cover_button" >
<property name="toolTip" >
<string>Browse for an image to use as the cover of this book.</string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="images.qrc" >:/images/fileopen.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item rowspan="3" row="5" column="0" colspan="2" >
<widget class="QLabel" name="cover" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>0</hsizetype>
<vsizetype>0</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>100</width>
<height>120</height>
</size>
</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 row="7" column="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>21</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2" >
<property name="title" >
<string>Comments</string>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<number>9</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item row="0" column="0" >
<widget class="QTextEdit" name="comments" />
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QGroupBox" name="groupBox_3" >
<property name="title" >
<string>Available Formats</string>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<number>9</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item row="0" column="1" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="add_format_button" >
<property name="toolTip" >
<string>Add a new format for this book</string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="images.qrc" >:/images/plus.png</iconset>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" >
<size>
<width>26</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="remove_format_button" >
<property name="toolTip" >
<string>Remove the selected formats for this book from the database.</string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="images.qrc" >:/images/minus.png</iconset>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0" >
<widget class="QListWidget" name="formats" />
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0" >
<widget class="QDialogButtonBox" name="button_box" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="images.qrc" />
</resources>
<connections>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>BookEditDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>BookEditDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,227 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'editbook.ui'
#
# Created: Mon Apr 9 18:48:56 2007
# by: PyQt4 UI code generator 4.1.1
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
class Ui_BookEditDialog(object):
def setupUi(self, BookEditDialog):
BookEditDialog.setObjectName("BookEditDialog")
BookEditDialog.resize(QtCore.QSize(QtCore.QRect(0,0,865,776).size()).expandedTo(BookEditDialog.minimumSizeHint()))
self.gridlayout = QtGui.QGridLayout(BookEditDialog)
self.gridlayout.setMargin(9)
self.gridlayout.setSpacing(6)
self.gridlayout.setObjectName("gridlayout")
self.splitter = QtGui.QSplitter(BookEditDialog)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.widget = QtGui.QWidget(self.splitter)
self.widget.setObjectName("widget")
self.vboxlayout = QtGui.QVBoxLayout(self.widget)
self.vboxlayout.setMargin(0)
self.vboxlayout.setSpacing(6)
self.vboxlayout.setObjectName("vboxlayout")
self.groupBox = QtGui.QGroupBox(self.widget)
self.groupBox.setObjectName("groupBox")
self.gridlayout1 = QtGui.QGridLayout(self.groupBox)
self.gridlayout1.setMargin(9)
self.gridlayout1.setSpacing(6)
self.gridlayout1.setObjectName("gridlayout1")
self.rating = QtGui.QSpinBox(self.groupBox)
self.rating.setButtonSymbols(QtGui.QAbstractSpinBox.PlusMinus)
self.rating.setMaximum(5)
self.rating.setObjectName("rating")
self.gridlayout1.addWidget(self.rating,2,1,1,2)
self.label_6 = QtGui.QLabel(self.groupBox)
self.label_6.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_6.setObjectName("label_6")
self.gridlayout1.addWidget(self.label_6,2,0,1,1)
self.publisher = QtGui.QLineEdit(self.groupBox)
self.publisher.setObjectName("publisher")
self.gridlayout1.addWidget(self.publisher,3,1,1,2)
self.label_3 = QtGui.QLabel(self.groupBox)
self.label_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_3.setObjectName("label_3")
self.gridlayout1.addWidget(self.label_3,3,0,1,1)
self.label_4 = QtGui.QLabel(self.groupBox)
self.label_4.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_4.setObjectName("label_4")
self.gridlayout1.addWidget(self.label_4,4,0,1,1)
self.tags = QtGui.QLineEdit(self.groupBox)
self.tags.setObjectName("tags")
self.gridlayout1.addWidget(self.tags,4,1,1,2)
self.authors = QtGui.QLineEdit(self.groupBox)
self.authors.setObjectName("authors")
self.gridlayout1.addWidget(self.authors,1,1,1,2)
self.title = QtGui.QLineEdit(self.groupBox)
self.title.setObjectName("title")
self.gridlayout1.addWidget(self.title,0,1,1,2)
self.label_2 = QtGui.QLabel(self.groupBox)
self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_2.setObjectName("label_2")
self.gridlayout1.addWidget(self.label_2,1,0,1,1)
self.label = QtGui.QLabel(self.groupBox)
self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label.setObjectName("label")
self.gridlayout1.addWidget(self.label,0,0,1,1)
self.vboxlayout1 = QtGui.QVBoxLayout()
self.vboxlayout1.setMargin(0)
self.vboxlayout1.setSpacing(6)
self.vboxlayout1.setObjectName("vboxlayout1")
self.label_5 = QtGui.QLabel(self.groupBox)
self.label_5.setObjectName("label_5")
self.vboxlayout1.addWidget(self.label_5)
self.hboxlayout = QtGui.QHBoxLayout()
self.hboxlayout.setMargin(0)
self.hboxlayout.setSpacing(6)
self.hboxlayout.setObjectName("hboxlayout")
self.cover_path = QtGui.QLineEdit(self.groupBox)
self.cover_path.setReadOnly(True)
self.cover_path.setObjectName("cover_path")
self.hboxlayout.addWidget(self.cover_path)
self.cover_button = QtGui.QToolButton(self.groupBox)
self.cover_button.setIcon(QtGui.QIcon(":/images/fileopen.png"))
self.cover_button.setObjectName("cover_button")
self.hboxlayout.addWidget(self.cover_button)
self.vboxlayout1.addLayout(self.hboxlayout)
self.gridlayout1.addLayout(self.vboxlayout1,6,2,1,1)
self.cover = QtGui.QLabel(self.groupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(0),QtGui.QSizePolicy.Policy(0))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.cover.sizePolicy().hasHeightForWidth())
self.cover.setSizePolicy(sizePolicy)
self.cover.setMaximumSize(QtCore.QSize(100,120))
self.cover.setPixmap(QtGui.QPixmap(":/images/cherubs.jpg"))
self.cover.setScaledContents(True)
self.cover.setObjectName("cover")
self.gridlayout1.addWidget(self.cover,5,0,3,2)
spacerItem = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
self.gridlayout1.addItem(spacerItem,7,2,1,1)
spacerItem1 = QtGui.QSpacerItem(20,21,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
self.gridlayout1.addItem(spacerItem1,5,2,1,1)
self.vboxlayout.addWidget(self.groupBox)
self.groupBox_2 = QtGui.QGroupBox(self.widget)
self.groupBox_2.setObjectName("groupBox_2")
self.gridlayout2 = QtGui.QGridLayout(self.groupBox_2)
self.gridlayout2.setMargin(9)
self.gridlayout2.setSpacing(6)
self.gridlayout2.setObjectName("gridlayout2")
self.comments = QtGui.QTextEdit(self.groupBox_2)
self.comments.setObjectName("comments")
self.gridlayout2.addWidget(self.comments,0,0,1,1)
self.vboxlayout.addWidget(self.groupBox_2)
self.groupBox_3 = QtGui.QGroupBox(self.splitter)
self.groupBox_3.setObjectName("groupBox_3")
self.gridlayout3 = QtGui.QGridLayout(self.groupBox_3)
self.gridlayout3.setMargin(9)
self.gridlayout3.setSpacing(6)
self.gridlayout3.setObjectName("gridlayout3")
self.vboxlayout2 = QtGui.QVBoxLayout()
self.vboxlayout2.setMargin(0)
self.vboxlayout2.setSpacing(6)
self.vboxlayout2.setObjectName("vboxlayout2")
spacerItem2 = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
self.vboxlayout2.addItem(spacerItem2)
self.add_format_button = QtGui.QToolButton(self.groupBox_3)
self.add_format_button.setIcon(QtGui.QIcon(":/images/plus.png"))
self.add_format_button.setObjectName("add_format_button")
self.vboxlayout2.addWidget(self.add_format_button)
spacerItem3 = QtGui.QSpacerItem(26,10,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Fixed)
self.vboxlayout2.addItem(spacerItem3)
self.remove_format_button = QtGui.QToolButton(self.groupBox_3)
self.remove_format_button.setIcon(QtGui.QIcon(":/images/minus.png"))
self.remove_format_button.setObjectName("remove_format_button")
self.vboxlayout2.addWidget(self.remove_format_button)
spacerItem4 = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
self.vboxlayout2.addItem(spacerItem4)
self.gridlayout3.addLayout(self.vboxlayout2,0,1,1,1)
self.formats = QtGui.QListWidget(self.groupBox_3)
self.formats.setObjectName("formats")
self.gridlayout3.addWidget(self.formats,0,0,1,1)
self.gridlayout.addWidget(self.splitter,0,0,1,1)
self.button_box = QtGui.QDialogButtonBox(BookEditDialog)
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.NoButton|QtGui.QDialogButtonBox.Ok)
self.button_box.setObjectName("button_box")
self.gridlayout.addWidget(self.button_box,1,0,1,1)
self.label_3.setBuddy(self.publisher)
self.label_4.setBuddy(self.tags)
self.label_2.setBuddy(self.authors)
self.label.setBuddy(self.title)
self.label_5.setBuddy(self.cover_path)
self.retranslateUi(BookEditDialog)
QtCore.QObject.connect(self.button_box,QtCore.SIGNAL("rejected()"),BookEditDialog.reject)
QtCore.QObject.connect(self.button_box,QtCore.SIGNAL("accepted()"),BookEditDialog.accept)
QtCore.QMetaObject.connectSlotsByName(BookEditDialog)
def retranslateUi(self, BookEditDialog):
BookEditDialog.setWindowTitle(QtGui.QApplication.translate("BookEditDialog", "SONY Reader - Edit Meta Information", None, QtGui.QApplication.UnicodeUTF8))
self.groupBox.setTitle(QtGui.QApplication.translate("BookEditDialog", "Meta information", None, QtGui.QApplication.UnicodeUTF8))
self.rating.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Rating of this book. 0-5 stars", None, QtGui.QApplication.UnicodeUTF8))
self.rating.setWhatsThis(QtGui.QApplication.translate("BookEditDialog", "Rating of this book. 0-5 stars", None, QtGui.QApplication.UnicodeUTF8))
self.rating.setSuffix(QtGui.QApplication.translate("BookEditDialog", " stars", None, QtGui.QApplication.UnicodeUTF8))
self.label_6.setText(QtGui.QApplication.translate("BookEditDialog", "&Rating:", None, QtGui.QApplication.UnicodeUTF8))
self.publisher.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Change the publisher of this book", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("BookEditDialog", "&Publisher: ", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("BookEditDialog", "Ta&gs: ", None, QtGui.QApplication.UnicodeUTF8))
self.tags.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.", None, QtGui.QApplication.UnicodeUTF8))
self.authors.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Change the author(s) of this book. Multiple authors should be separated by the & character", None, QtGui.QApplication.UnicodeUTF8))
self.title.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Change the title of this book", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("BookEditDialog", "&Author(s): ", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("BookEditDialog", "&Title: ", None, QtGui.QApplication.UnicodeUTF8))
self.label_5.setText(QtGui.QApplication.translate("BookEditDialog", "Change &cover image:", None, QtGui.QApplication.UnicodeUTF8))
self.cover_button.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Browse for an image to use as the cover of this book.", None, QtGui.QApplication.UnicodeUTF8))
self.cover_button.setText(QtGui.QApplication.translate("BookEditDialog", "...", None, QtGui.QApplication.UnicodeUTF8))
self.groupBox_2.setTitle(QtGui.QApplication.translate("BookEditDialog", "Comments", None, QtGui.QApplication.UnicodeUTF8))
self.groupBox_3.setTitle(QtGui.QApplication.translate("BookEditDialog", "Available Formats", None, QtGui.QApplication.UnicodeUTF8))
self.add_format_button.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Add a new format for this book", None, QtGui.QApplication.UnicodeUTF8))
self.add_format_button.setText(QtGui.QApplication.translate("BookEditDialog", "...", None, QtGui.QApplication.UnicodeUTF8))
self.remove_format_button.setToolTip(QtGui.QApplication.translate("BookEditDialog", "Remove the selected formats for this book from the database.", None, QtGui.QApplication.UnicodeUTF8))
self.remove_format_button.setText(QtGui.QApplication.translate("BookEditDialog", "...", None, QtGui.QApplication.UnicodeUTF8))
import images_rc

View File

@ -1,16 +0,0 @@
<RCC>
<qresource prefix="/" >
<file>images/addfile.png</file>
<file alias="default_cover" >images/cherubs.jpg</file>
<file>images/clear.png</file>
<file>images/delfile.png</file>
<file>images/edit.png</file>
<file>images/fileopen.png</file>
<file alias="library" >images/library.png</file>
<file alias="card" >images/memory_stick_unmount.png</file>
<file>images/minus.png</file>
<file>images/plus.png</file>
<file>images/upload.png</file>
<file alias="reader" >images/reader.png</file>
</qresource>
</RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,616 +0,0 @@
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## 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.Warning
""" Create and launch the GUI """
import sys, re, os, traceback, tempfile
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QSettings, QVariant, QSize, QEventLoop, QString, \
QBuffer, QIODevice, QModelIndex
from PyQt4.QtGui import QPixmap, QErrorMessage, QLineEdit, \
QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
try:
from libprs500.devices.prs500.driver import PRS500 as device
except OSError:
device = None
from libprs500.devices.errors import *
from libprs500.gui import installErrorHandler, Error, _Warning, \
extension, APP_TITLE
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, \
DeviceModel
from libprs500.gui.main_ui import Ui_MainWindow
from libprs500.gui.database import LibraryDatabase
from libprs500.gui.editbook import EditBookDialog
from libprs500 import __version__ as VERSION
DEFAULT_BOOK_COVER = None
LIBRARY_BOOK_TEMPLATE = QString("<b>Formats:</b> %1<br><b>Tags:</b> %2<br>%3")
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>")
class Main(QObject, Ui_MainWindow):
def report_error(func):
"""
Decorator to ensure that unhandled exceptions are displayed
to users via the GUI
"""
def function(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception, e:
Error("There was an error calling " + func.__name__, e)
raise
return function
""" Create GUI """
def show_device(self, yes):
"""
If C{yes} show the items on the device otherwise show the items
in the library
"""
self.device_view.selectionModel().reset()
self.library_view.selectionModel().reset()
self.book_cover.hide()
self.book_info.hide()
if yes:
self.action_add.setEnabled(False)
self.action_edit.setEnabled(False)
self.device_view.show()
self.library_view.hide()
self.book_cover.setAcceptDrops(False)
self.device_view.resizeColumnsToContents()
self.device_view.resizeRowsToContents()
else:
self.action_add.setEnabled(True)
self.action_edit.setEnabled(True)
self.device_view.hide()
self.library_view.show()
self.book_cover.setAcceptDrops(True)
self.current_view.sortByColumn(3, Qt.DescendingOrder)
def tree_clicked(self, index):
if index.isValid():
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)
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(show_dev)
def model_modified(self):
if self.current_view.selectionModel():
self.current_view.selectionModel().reset()
self.current_view.resizeColumnsToContents()
self.current_view.resizeRowsToContents()
self.book_cover.hide()
self.book_info.hide()
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def resize_rows_and_columns(self, topleft, bottomright):
for c in range(topleft.column(), bottomright.column()+1):
self.current_view.resizeColumnToContents(c)
for r in range(topleft.row(), bottomright.row()+1):
self.current_view.resizeRowToContents(r)
def show_book(self, current, previous):
if not current.isValid():
return
if self.library_view.isVisible():
formats, tags, comments, cover = self.library_model\
.info(current.row())
comments = re.sub('\n', '<br>', comments)
data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
tooltip = "To save the cover, drag it to the desktop.<br>To \
change the cover drag the new cover onto this picture"
else:
title, author, size, mime, cover = self.device_view.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()
self.current_view.scrollTo(current)
def formats_added(self, index):
if index == self.library_view.currentIndex():
self.show_book(index, index)
@report_error
def delete(self, action):
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:
self.status("Deleting books and updating metadata on device")
paths = self.device_view.model().delete(rows)
self.dev.remove_books(paths, (self.reader_model.booklist, \
self.card_model.booklist), end_session=False)
self.update_availabe_space()
self.model_modified()
self.show_book(self.current_view.currentIndex(), QModelIndex())
self.window.setCursor(Qt.ArrowCursor)
def read_settings(self):
settings = QSettings()
settings.beginGroup("MainWindow")
self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).\
toSize())
settings.endGroup()
self.database_path = settings.value("database path", QVariant(os.path\
.expanduser("~/library.db"))).toString()
def write_settings(self):
settings = QSettings()
settings.beginGroup("MainWindow")
settings.setValue("size", QVariant(self.window.size()))
settings.endGroup()
def close_event(self, e):
self.write_settings()
e.accept()
def add(self, action):
settings = QSettings()
_dir = settings.value("add books dialog dir", \
QVariant(os.path.expanduser("~"))).toString()
files = QFileDialog.getOpenFileNames(self.window, \
"Choose books to add to library", _dir, \
"Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
if not files.isEmpty():
x = unicode(files[0].toUtf8(), 'utf-8')
settings.setValue("add books dialog dir", \
QVariant(os.path.dirname(x)))
files = unicode(files.join("|||").toUtf8(), 'utf-8').split("|||")
self.add_books(files)
@report_error
def add_books(self, files):
self.window.setCursor(Qt.WaitCursor)
try:
for _file in files:
_file = os.path.abspath(_file)
self.library_view.model().add_book(_file)
if self.library_view.isVisible():
if len(str(self.search.text())):
self.search.clear()
else:
self.library_model.search("")
else:
self.library_model.search("")
hv = self.library_view.horizontalHeader()
col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder()
self.library_view.model().sort(col, order)
finally:
self.window.setCursor(Qt.ArrowCursor)
@report_error
def edit(self, action):
if self.library_view.isVisible():
rows = self.library_view.selectionModel().selectedRows()
for row in rows:
_id = self.library_model.id_from_index(row)
dialog = QDialog(self.window)
ebd = EditBookDialog(dialog, _id, self.library_model.db)
if dialog.exec_() == QDialog.Accepted:
title = unicode(ebd.title.text().toUtf8(), 'utf-8').strip()
authors = unicode(ebd.authors.text().toUtf8(), 'utf-8').strip()
rating = ebd.rating.value()
tags = unicode(ebd.tags.text().toUtf8(), 'utf-8').strip()
publisher = unicode(ebd.publisher.text().toUtf8(), \
'utf-8').strip()
comments = unicode(ebd.comments.toPlainText().toUtf8(), \
'utf-8').strip()
pix = ebd.cover.pixmap()
if not pix.isNull():
self.update_cover(pix)
model = self.library_view.model()
if title:
index = model.index(row.row(), 0)
model.setData(index, QVariant(title), Qt.EditRole)
if authors:
index = model.index(row.row(), 1)
model.setData(index, QVariant(authors), Qt.EditRole)
if publisher:
index = model.index(row.row(), 5)
model.setData(index, QVariant(publisher), Qt.EditRole)
index = model.index(row.row(), 4)
model.setData(index, QVariant(rating), Qt.EditRole)
self.update_tags_and_comments(row, tags, comments)
self.library_model.refresh_row(row.row())
self.show_book(self.current_view.currentIndex(), QModelIndex())
def update_tags_and_comments(self, index, tags, comments):
self.library_model.update_tags_and_comments(index, tags, comments)
@report_error
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:
Error("Unable to change cover", e)
@report_error
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()
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:
if len(str(self.search.text())):
self.search.clear()
else:
self.device_view.model().search("")
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)
ename = "file"
try:
if ids:
for _id in ids:
formats = []
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("libprs500_\S+_......_" + \
str(_id) + "_", os.path.basename(f)):
formats.append(f)
_file = None
try:
for format in self.dev.FORMATS:
for f in formats:
if extension(f) == format:
_file = f
raise StopIteration()
except StopIteration: pass
if not _file:
Error("The library does not have any formats that "+\
"can be viewed on the device for " + ename, None)
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, \
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":os.path.basename(_file), \
"authors":"Unknown", "cover":(None, None, None) }
f = open(_file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, os.path.basename(_file), info, \
booklists, oncard=oncard, end_session=False)
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.ArrowCursor)
self.update_availabe_space()
@apply
def current_view():
doc = """ The currently visible view """
def fget(self):
return self.library_view if self.library_view.isVisible() \
else self.device_view
return property(doc=doc, fget=fget)
def __init__(self, window, log_packets):
QObject.__init__(self)
Ui_MainWindow.__init__(self)
self.key = '-1'
self.log_packets = log_packets
if device:
self.dev = device(key=self.key, report_progress=self.progress, \
log_packets=self.log_packets)
else:
self.dev = None
self.setupUi(window)
self.card = None
self.window = window
window.closeEvent = self.close_event
self.read_settings()
# Setup Library Book list
self.library_model = LibraryBooksModel(window)
self.library_model.set_data(LibraryDatabase(str(self.database_path)))
self.library_view.setModel(self.library_model)
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), \
self.library_view.resizeRowsToContents)
QObject.connect(self.library_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), \
self.library_model.search)
QObject.connect(self.library_model, SIGNAL("sorted()"), \
self.model_modified)
QObject.connect(self.library_model, SIGNAL("searched()"), \
self.model_modified)
QObject.connect(self.library_model, SIGNAL("deleted()"), \
self.model_modified)
QObject.connect(self.library_model, \
SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
self.resize_rows_and_columns)
QObject.connect(self.library_view, \
SIGNAL('books_dropped'), self.add_books)
QObject.connect(self.library_model, \
SIGNAL('formats_added'), self.formats_added)
self.library_view.sortByColumn(3, Qt.DescendingOrder)
# Create Device tree
model = DeviceModel(self.device_tree)
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), \
self.tree_clicked)
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), \
self.tree_clicked)
QObject.connect(model, SIGNAL('books_dropped'), self.add_books)
QObject.connect(model, SIGNAL('upload_books'), self.upload_books)
self.device_tree.setModel(model)
# Create Device Book list
self.reader_model = DeviceBooksModel(window)
self.card_model = DeviceBooksModel(window)
self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
for model in (self.reader_model, self. card_model):
QObject.connect(model, SIGNAL("layoutChanged()"), \
self.device_view.resizeRowsToContents)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), \
model.search)
QObject.connect(model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(model, SIGNAL("searched()"), self.model_modified)
QObject.connect(model, SIGNAL("deleted()"), self.model_modified)
QObject.connect(model, \
SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
self.resize_rows_and_columns)
# Setup book display
self.book_cover.hide()
self.book_info.hide()
# Connect actions
QObject.connect(self.action_add, SIGNAL("triggered(bool)"), self.add)
QObject.connect(self.action_del, SIGNAL("triggered(bool)"), self.delete)
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit)
# DnD setup
QObject.connect(self.book_cover, SIGNAL("cover_received(QPixmap)"), \
self.update_cover)
self.detector = DeviceConnectDetector(self.dev)
self.connect(self.detector, SIGNAL("device_connected()"), \
self.establish_connection)
self.connect(self.detector, SIGNAL("device_removed()"), \
self.device_removed)
self.search.setFocus(Qt.OtherFocusReason)
self.show_device(False)
self.df_template = self.df.text().arg(VERSION)
self.df.setText(self.df_template.arg("").arg("").arg(""))
window.show()
self.library_view.resizeColumnsToContents()
self.library_view.resizeRowsToContents()
def device_removed(self):
self.df.setText(self.df_template.arg("").arg("").arg(""))
self.device_tree.hide_reader(True)
self.device_tree.hide_card(True)
self.device_tree.selectionModel().reset()
self.status('SONY Reader disconnected')
self.progress(100)
if self.device_view.isVisible():
self.device_view.hide()
self.library_view.selectionModel().reset()
self.library_view.show()
self.book_cover.hide()
self.book_info.hide()
def progress(self, val):
if val < 0:
self.progress_bar.setMaximum(0)
else: self.progress_bar.setValue(val)
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def status(self, msg):
self.progress_bar.setMaximum(100)
self.progress_bar.reset()
self.progress_bar.setFormat(msg + ": %p%")
self.progress(0)
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def establish_connection(self):
self.window.setCursor(Qt.WaitCursor)
self.status("Connecting to device")
try:
info = self.dev.get_device_information(end_session=False)
except DeviceBusy:
self.status("Device is in use by another application")
self.window.setCursor(Qt.ArrowCursor)
return
except DeviceError, err:
traceback.print_exc(err)
self.dev.reconnect()
self.thread().msleep(100)
return self.establish_connection()
except DeviceLocked:
key, ok = QInputDialog.getText(self.window, 'Unlock device', \
'Key to unlock device:', QLineEdit.Password)
self.key = str(key)
if not ok:
self.status('Device locked')
self.window.setCursor(Qt.ArrowCursor)
return
else:
self.dev.key = key
return self.establish_connection()
except ProtocolError, e:
traceback.print_exc(e)
qFatal("Unable to connect to device. Please try unplugging and"+\
" reconnecting it")
self.update_availabe_space(end_session=False)
self.card = self.dev.card()
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(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.df.setText(self.df_template.arg("Connected: "+info[0])\
.arg(info[1]).arg(info[2]))
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 DeviceConnectDetector(QObject):
def timerEvent(self, e):
if e.timerId() == self.device_detector:
is_connected = self.dev.is_connected() if self.dev else False
if is_connected and not self.is_connected:
self.is_connected = True
self.emit(SIGNAL("device_connected()"))
elif not is_connected and self.is_connected:
self.is_connected = False
self.emit(SIGNAL("device_removed()"))
def __init__(self, dev):
QObject.__init__(self)
self.dev = dev
self.is_connected = False
self.device_detector = self.startTimer(1000)
def main():
from optparse import OptionParser
from libprs500 import __version__ as VERSION
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
if os.access(lock, os.F_OK):
print >>sys.stderr, "Another instance of", APP_TITLE, "is running"
print >>sys.stderr, "If you are sure this is not the case then "+\
"manually delete the file", lock
sys.exit(1)
parser = OptionParser(usage="usage: %prog [options]", version=VERSION)
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\
"The numbers in the left column are byte offsets that allow"+\
" the packet size to be read off easily.", \
dest="log_packets", action="store_true", default=False)
options, args = parser.parse_args()
from PyQt4.Qt import QApplication, QMainWindow
app = QApplication(sys.argv)
global DEFAULT_BOOK_COVER
DEFAULT_BOOK_COVER = QPixmap(":/default_cover")
window = QMainWindow()
window.setWindowTitle(APP_TITLE)
window.setWindowIcon(QIcon(":/icon"))
installErrorHandler(QErrorMessage(window))
QCoreApplication.setOrganizationName("KovidsBrain")
QCoreApplication.setApplicationName(APP_TITLE)
Main(window, options.log_packets)
return app.exec_()
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,399 +0,0 @@
<ui version="4.0" >
<author>Kovid Goyal</author>
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>728</width>
<height>822</height>
</rect>
</property>
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle" >
<string/>
</property>
<property name="windowIcon" >
<iconset resource="images.qrc" >:/images/library.png</iconset>
</property>
<widget class="QWidget" name="centralwidget" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>9</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="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>90</height>
</size>
</property>
<property name="verticalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="dragDropMode" >
<enum>QAbstractItemView::DragDrop</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>5</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>90</height>
</size>
</property>
<property name="text" >
<string>For help visit &lt;a href="https://libprs500.kovidgoyal.net/wiki/GuiUsage">http://libprs500.kovidgoyal.net&lt;/a>&lt;br>&lt;br>&lt;b>libprs500&lt;/b>: %1 by &lt;b>Kovid Goyal&lt;/b> &amp;copy; 2006&lt;br>%2 %3 %4</string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="openExternalLinks" >
<bool>true</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>&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" >
<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="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>&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 name="textFormat" >
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="progress_bar" >
<property name="value" >
<number>100</number>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QToolBar" name="tool_bar" >
<property name="minimumSize" >
<size>
<width>163</width>
<height>100</height>
</size>
</property>
<property name="movable" >
<bool>false</bool>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="iconSize" >
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<attribute name="toolBarArea" >
<number>4</number>
</attribute>
<addaction name="action_add" />
<addaction name="action_del" />
<addaction name="action_edit" />
</widget>
<action name="action_add" >
<property name="icon" >
<iconset resource="images.qrc" >:/images/addfile.png</iconset>
</property>
<property name="text" >
<string>Add books to Library</string>
</property>
<property name="shortcut" >
<string>A</string>
</property>
<property name="autoRepeat" >
<bool>false</bool>
</property>
</action>
<action name="action_del" >
<property name="icon" >
<iconset resource="images.qrc" >:/images/delfile.png</iconset>
</property>
<property name="text" >
<string>Delete books</string>
</property>
<property name="shortcut" >
<string>Del</string>
</property>
</action>
<action name="action_edit" >
<property name="icon" >
<iconset resource="images.qrc" >:/images/edit.png</iconset>
</property>
<property name="text" >
<string>Edit meta-information</string>
</property>
<property name="shortcut" >
<string>E</string>
</property>
<property name="autoRepeat" >
<bool>false</bool>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>CoverDisplay</class>
<extends>QLabel</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>DeviceView</class>
<extends>QListView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>LibraryBooksView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>DeviceBooksView</class>
<extends>QTableView</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="images.qrc" />
</resources>
<connections>
<connection>
<sender>clear_button</sender>
<signal>clicked()</signal>
<receiver>search</receiver>
<slot>clear()</slot>
<hints>
<hint type="sourcelabel" >
<x>853</x>
<y>61</y>
</hint>
<hint type="destinationlabel" >
<x>784</x>
<y>58</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,202 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'src/libprs500/gui/main.ui'
#
# Created: Sat Apr 21 14:42:37 2007
# by: PyQt4 UI code generator 4.1.1
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(QtCore.QSize(QtCore.QRect(0,0,728,822).size()).expandedTo(MainWindow.minimumSizeHint()))
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(5),QtGui.QSizePolicy.Policy(5))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setWindowIcon(QtGui.QIcon(":/images/library.png"))
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.vboxlayout = QtGui.QVBoxLayout(self.centralwidget)
self.vboxlayout.setMargin(9)
self.vboxlayout.setSpacing(6)
self.vboxlayout.setObjectName("vboxlayout")
self.hboxlayout = QtGui.QHBoxLayout()
self.hboxlayout.setMargin(0)
self.hboxlayout.setSpacing(6)
self.hboxlayout.setObjectName("hboxlayout")
self.device_tree = DeviceView(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(5),QtGui.QSizePolicy.Policy(5))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.device_tree.sizePolicy().hasHeightForWidth())
self.device_tree.setSizePolicy(sizePolicy)
self.device_tree.setMaximumSize(QtCore.QSize(10000,90))
self.device_tree.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.device_tree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.device_tree.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.device_tree.setFlow(QtGui.QListView.TopToBottom)
self.device_tree.setSpacing(20)
self.device_tree.setViewMode(QtGui.QListView.IconMode)
self.device_tree.setObjectName("device_tree")
self.hboxlayout.addWidget(self.device_tree)
self.df = QtGui.QLabel(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(5),QtGui.QSizePolicy.Policy(5))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.df.sizePolicy().hasHeightForWidth())
self.df.setSizePolicy(sizePolicy)
self.df.setMaximumSize(QtCore.QSize(16777215,90))
self.df.setTextFormat(QtCore.Qt.RichText)
self.df.setOpenExternalLinks(True)
self.df.setObjectName("df")
self.hboxlayout.addWidget(self.df)
self.vboxlayout.addLayout(self.hboxlayout)
self.hboxlayout1 = QtGui.QHBoxLayout()
self.hboxlayout1.setMargin(0)
self.hboxlayout1.setSpacing(6)
self.hboxlayout1.setObjectName("hboxlayout1")
self.label = QtGui.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.hboxlayout1.addWidget(self.label)
self.search = QtGui.QLineEdit(self.centralwidget)
self.search.setEnabled(True)
self.search.setAcceptDrops(False)
self.search.setAutoFillBackground(False)
self.search.setFrame(True)
self.search.setObjectName("search")
self.hboxlayout1.addWidget(self.search)
self.clear_button = QtGui.QToolButton(self.centralwidget)
self.clear_button.setIcon(QtGui.QIcon(":/images/clear.png"))
self.clear_button.setObjectName("clear_button")
self.hboxlayout1.addWidget(self.clear_button)
self.vboxlayout.addLayout(self.hboxlayout1)
self.gridlayout = QtGui.QGridLayout()
self.gridlayout.setMargin(0)
self.gridlayout.setSpacing(6)
self.gridlayout.setObjectName("gridlayout")
self.device_view = DeviceBooksView(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(5),QtGui.QSizePolicy.Policy(5))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.device_view.sizePolicy().hasHeightForWidth())
self.device_view.setSizePolicy(sizePolicy)
self.device_view.setDragEnabled(True)
self.device_view.setDragDropOverwriteMode(False)
self.device_view.setAlternatingRowColors(True)
self.device_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.device_view.setShowGrid(False)
self.device_view.setObjectName("device_view")
self.gridlayout.addWidget(self.device_view,0,0,1,1)
self.library_view = LibraryBooksView(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(5),QtGui.QSizePolicy.Policy(5))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(10)
sizePolicy.setHeightForWidth(self.library_view.sizePolicy().hasHeightForWidth())
self.library_view.setSizePolicy(sizePolicy)
self.library_view.setAcceptDrops(True)
self.library_view.setDragEnabled(True)
self.library_view.setDragDropOverwriteMode(False)
self.library_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.library_view.setAlternatingRowColors(True)
self.library_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.library_view.setShowGrid(False)
self.library_view.setObjectName("library_view")
self.gridlayout.addWidget(self.library_view,1,0,1,1)
self.vboxlayout.addLayout(self.gridlayout)
self.hboxlayout2 = QtGui.QHBoxLayout()
self.hboxlayout2.setMargin(0)
self.hboxlayout2.setSpacing(6)
self.hboxlayout2.setObjectName("hboxlayout2")
self.book_cover = CoverDisplay(self.centralwidget)
self.book_cover.setMaximumSize(QtCore.QSize(60,80))
self.book_cover.setAcceptDrops(True)
self.book_cover.setPixmap(QtGui.QPixmap(":/images/cherubs.jpg"))
self.book_cover.setScaledContents(True)
self.book_cover.setObjectName("book_cover")
self.hboxlayout2.addWidget(self.book_cover)
self.book_info = QtGui.QLabel(self.centralwidget)
self.book_info.setTextFormat(QtCore.Qt.RichText)
self.book_info.setObjectName("book_info")
self.hboxlayout2.addWidget(self.book_info)
self.vboxlayout.addLayout(self.hboxlayout2)
self.progress_bar = QtGui.QProgressBar(self.centralwidget)
self.progress_bar.setProperty("value",QtCore.QVariant(100))
self.progress_bar.setOrientation(QtCore.Qt.Horizontal)
self.progress_bar.setObjectName("progress_bar")
self.vboxlayout.addWidget(self.progress_bar)
MainWindow.setCentralWidget(self.centralwidget)
self.tool_bar = QtGui.QToolBar(MainWindow)
self.tool_bar.setMinimumSize(QtCore.QSize(163,100))
self.tool_bar.setMovable(False)
self.tool_bar.setOrientation(QtCore.Qt.Horizontal)
self.tool_bar.setIconSize(QtCore.QSize(64,64))
self.tool_bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
self.tool_bar.setObjectName("tool_bar")
MainWindow.addToolBar(self.tool_bar)
self.action_add = QtGui.QAction(MainWindow)
self.action_add.setIcon(QtGui.QIcon(":/images/addfile.png"))
self.action_add.setAutoRepeat(False)
self.action_add.setObjectName("action_add")
self.action_del = QtGui.QAction(MainWindow)
self.action_del.setIcon(QtGui.QIcon(":/images/delfile.png"))
self.action_del.setObjectName("action_del")
self.action_edit = QtGui.QAction(MainWindow)
self.action_edit.setIcon(QtGui.QIcon(":/images/edit.png"))
self.action_edit.setAutoRepeat(False)
self.action_edit.setObjectName("action_edit")
self.tool_bar.addAction(self.action_add)
self.tool_bar.addAction(self.action_del)
self.tool_bar.addAction(self.action_edit)
self.label.setBuddy(self.search)
self.retranslateUi(MainWindow)
QtCore.QObject.connect(self.clear_button,QtCore.SIGNAL("clicked()"),self.search.clear)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
self.df.setText(QtGui.QApplication.translate("MainWindow", "For help visit <a href=\"https://libprs500.kovidgoyal.net/wiki/GuiUsage\">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b>: %1 by <b>Kovid Goyal</b> &copy; 2006<br>%2 %3 %4", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("MainWindow", "&Search:", None, QtGui.QApplication.UnicodeUTF8))
self.search.setToolTip(QtGui.QApplication.translate("MainWindow", "Search the list of books by title or author<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8))
self.search.setWhatsThis(QtGui.QApplication.translate("MainWindow", "Search the list of books by title or author<br><br>Words separated by spaces are ANDed", None, QtGui.QApplication.UnicodeUTF8))
self.clear_button.setToolTip(QtGui.QApplication.translate("MainWindow", "Reset Quick Search", None, QtGui.QApplication.UnicodeUTF8))
self.clear_button.setText(QtGui.QApplication.translate("MainWindow", "...", None, QtGui.QApplication.UnicodeUTF8))
self.book_info.setText(QtGui.QApplication.translate("MainWindow", "<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>", None, QtGui.QApplication.UnicodeUTF8))
self.action_add.setText(QtGui.QApplication.translate("MainWindow", "Add books to Library", None, QtGui.QApplication.UnicodeUTF8))
self.action_add.setShortcut(QtGui.QApplication.translate("MainWindow", "A", None, QtGui.QApplication.UnicodeUTF8))
self.action_del.setText(QtGui.QApplication.translate("MainWindow", "Delete books", None, QtGui.QApplication.UnicodeUTF8))
self.action_del.setShortcut(QtGui.QApplication.translate("MainWindow", "Del", None, QtGui.QApplication.UnicodeUTF8))
self.action_edit.setText(QtGui.QApplication.translate("MainWindow", "Edit meta-information", None, QtGui.QApplication.UnicodeUTF8))
self.action_edit.setShortcut(QtGui.QApplication.translate("MainWindow", "E", None, QtGui.QApplication.UnicodeUTF8))
from widgets import DeviceBooksView, DeviceView, CoverDisplay, LibraryBooksView
import images_rc

View File

@ -1,863 +0,0 @@
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## 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
import os
import textwrap
import time
import traceback
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.gui import Error, _Warning
from libprs500.ptempfile import PersistentTemporaryFile
from libprs500 import iswindows
from PyQt4.QtCore import Qt, SIGNAL
from PyQt4.Qt import QApplication, QString, QFont, QAbstractListModel, \
QVariant, QAbstractTableModel, QTableView, QListView, \
QLabel, QAbstractItemView, QPixmap, QIcon, QSize, \
QSpinBox, QPoint, QPainterPath, QItemDelegate, QPainter, \
QPen, QColor, QLinearGradient, QBrush, QStyle, \
QByteArray, QBuffer, QMimeData, \
QDrag, QRect
NONE = QVariant() #: Null value to return from the data function of item models
TIME_WRITE_FMT = "%d %b %Y" #: The display format used to show dates
class FileDragAndDrop(object):
_drag_start_position = QPoint()
_dragged_files = []
@classmethod
def _bytes_to_string(cls, qba):
"""
Assumes qba is encoded in ASCII which is usually fine, since
this method is used mainly for escaped URIs.
@type qba: QByteArray
"""
return str(QString.fromAscii(qba.data())).strip()
@classmethod
def _get_r_ok_files(cls, event):
"""
Return list of paths from event that point to files to
which the user has read permission.
"""
files = []
md = event.mimeData()
if md.hasFormat("text/uri-list"):
candidates = cls._bytes_to_string(md.data("text/uri-list")).split()
for url in candidates:
o = urlparse(url)
if o.scheme and o.scheme != 'file':
_Warning(o.scheme + " not supported in drop events", None)
continue
path = unquote(o.path)
if iswindows and path.startswith('/'):
path = path[1:]
if not os.access(path, os.R_OK):
_Warning("You do not have read permission for: " + path, None)
continue
if os.path.isdir(path):
root, dirs, files2 = os.walk(path)
for _file in files2:
path = root + _file
if os.access(path, os.R_OK):
files.append(path)
else:
files.append(path)
return files
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 self.enable_drag:
if event.button == Qt.LeftButton:
self._drag_start_position = event.pos()
def mouseMoveEvent(self, event):
self.QtBaseClass.mousePressEvent(self, event)
if self.enable_drag:
if event.buttons() & Qt.LeftButton != Qt.LeftButton:
return
if (event.pos() - self._drag_start_position).manhattanLength() < \
QApplication.startDragDistance():
return
self.start_drag(self._drag_start_position)
def start_drag(self, pos):
raise NotImplementedError()
def dragEnterEvent(self, event):
if event.mimeData().hasFormat("text/uri-list"):
event.acceptProposedAction()
def dragMoveEvent(self, event):
event.acceptProposedAction()
def dropEvent(self, event):
files = self._get_r_ok_files(event)
if files:
try:
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
def files_dropped(self, files, event):
raise NotImplementedError()
def drag_object_from_files(self, files):
if files:
drag = QDrag(self)
mime_data = QMimeData()
self._dragged_files, urls = [], []
for _file in files:
urls.append(urlunparse(('file', quote(gethostname()), \
quote(_file.name.encode('utf-8')), '', '', '')))
self._dragged_files.append(_file)
mime_data.setData("text/uri-list", QByteArray("\n".join(urls)))
user = os.getenv('USER')
if user:
mime_data.setData("text/x-xdnd-username", QByteArray(user))
drag.setMimeData(mime_data)
return drag
def drag_object(self, extensions):
if extensions:
files = []
for ext in extensions:
f = PersistentTemporaryFile(suffix="."+ext)
files.append(f)
return self.drag_object_from_files(files), self._dragged_files
class TableView(FileDragAndDrop, QTableView):
wrapper = textwrap.TextWrapper(width=20)
def __init__(self, parent):
FileDragAndDrop.__init__(self, QTableView)
QTableView.__init__(self, parent)
@classmethod
def wrap(cls, s, width=20):
cls.wrapper.width = width
return cls.wrapper.fill(s)
@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)):
rects.append(self.visualRect(indices[i]))
rect |= rects[i]
rect = rect.intersected(self.viewport().rect())
pixmap = QPixmap(rect.size())
pixmap.fill(self.palette().base().color())
painter = QPainter(pixmap)
option = self.viewOptions()
option.state |= QStyle.State_Selected
for j in range(len(indices)):
option.rect = QRect(rects[j].topLeft() - rect.topLeft(), \
rects[j].size())
self.itemDelegate(indices[j]).paint(painter, option, indices[j])
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 CoverDisplay(FileDragAndDrop, QLabel):
def __init__(self, parent):
FileDragAndDrop.__init__(self, QLabel)
QLabel.__init__(self, parent)
def files_dropped(self, files, event):
pix = QPixmap()
for _file in files:
pix = QPixmap(_file)
if not pix.isNull(): break
if not pix.isNull():
self.emit(SIGNAL("cover_received(QPixmap)"), pix)
return True
def start_drag(self, event):
drag, files = self.drag_object(["jpeg"])
if drag and files:
_file = files[0]
_file.close()
drag.setPixmap(self.pixmap().scaledToHeight(68, \
Qt.SmoothTransformation))
self.pixmap().save(os.path.abspath(_file.name))
drag.start(Qt.MoveAction)
class DeviceView(FileDragAndDrop, QListView):
def __init__(self, parent):
FileDragAndDrop.__init__(self, QListView, enable_drag=False)
QListView.__init__(self, parent)
def hide_reader(self, x):
self.model().update_devices(reader=not x)
def hide_card(self, 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):
TableView.__init__(self, parent)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True)
class LibraryBooksView(TableView):
def __init__(self, parent):
TableView.__init__(self, parent)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True)
self.setItemDelegate(LibraryDelegate(self, rating_column=4))
def dragEnterEvent(self, event):
if not event.mimeData().hasFormat("application/x-libprs500-id"):
FileDragAndDrop.dragEnterEvent(self, event)
def start_drag(self, pos):
index = self.indexAt(pos)
if index.isValid():
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_row(row)) for row in rows ]
drag.mimeData().setData("application/x-libprs500-id", \
QByteArray("\n".join(ids)))
drag.start()
def files_dropped(self, files, event):
if not files: return
index = self.indexAt(event.pos())
if index.isValid():
self.model().add_formats(files, index)
else: self.emit(SIGNAL('books_dropped'), files)
class LibraryDelegate(QItemDelegate):
COLOR = QColor("blue")
SIZE = 16
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
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):
self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
50 + 40 * sin(0.8 * i * pi))
self.star_path.closeSubpath()
self.star_path.setFillRule(Qt.WindingFill)
gradient = QLinearGradient(0, 0, 0, 100)
gradient.setColorAt(0.0, self.COLOR)
gradient.setColorAt(1.0, self.COLOR)
self. brush = QBrush(gradient)
self.factor = self.SIZE/100.
def sizeHint(self, option, index):
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() != self.rating_column:
return QItemDelegate.paint(self, painter, option, index)
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
def draw_star():
painter.save()
painter.scale(self.factor, self.factor)
painter.translate(50.0, 50.0)
painter.rotate(-20)
painter.translate(-50.0, -50.0)
painter.drawPath(self.star_path)
painter.restore()
painter.save()
try:
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
painter.setRenderHint(QPainter.Antialiasing)
y = option.rect.center().y()-self.SIZE/2.
x = option.rect.right() - self.SIZE
painter.setPen(self.PEN)
painter.setBrush(self.brush)
painter.translate(x, y)
for i in range(num):
draw_star()
painter.translate(-self.SIZE, 0)
except Exception, e:
traceback.print_exc(e)
painter.restore()
def createEditor(self, parent, option, index):
if index.column() != 4:
return QItemDelegate.createEditor(self, parent, option, index)
editor = QSpinBox(parent)
editor.setSuffix(" stars")
editor.setMinimum(0)
editor.setMaximum(5)
editor.installEventFilter(self)
return editor
def setEditorData(self, editor, index):
if index.column() != 4:
return QItemDelegate.setEditorData(self, editor, index)
val = index.model()._data[index.row()]["rating"]
if not val: val = 0
editor.setValue(val)
def setModelData(self, editor, model, index):
if index.column() != 4:
return QItemDelegate.setModelData(self, editor, model, index)
editor.interpretText()
index.model().setData(index, QVariant(editor.value()), Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
if index.column() != 4:
return QItemDelegate.updateEditorGeometry(self, editor, option, index)
editor.setGeometry(option.rect)
class LibraryBooksModel(QAbstractTableModel):
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
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)
for ext in exts:
fmt = self.db.get_format(_id, ext)
if not ext:
ext =""
else:
ext = "."+ext
name = basename+ext
file = PersistentTemporaryFile(suffix=name)
if not fmt:
continue
file.write(fmt)
file.close()
files.append(file)
return files
def update_cover(self, index, pix):
spix = pix.scaledToHeight(68, Qt.SmoothTransformation)
_id = self.id_from_index(index)
qb, sqb = QBuffer(), QBuffer()
qb.open(QBuffer.ReadWrite)
sqb.open(QBuffer.ReadWrite)
pix.save(qb, "JPG")
spix.save(sqb, "JPG")
data = str(qb.data())
sdata = str(sqb.data())
qb.close()
sqb.close()
self.db.update_cover(_id, data, scaled=sdata)
self.refresh_row(index.row())
def add_formats(self, paths, index):
for path in paths:
f = open(path, "rb")
title = os.path.basename(path)
ext = title[title.rfind(".")+1:].lower() if "." in title > -1 else None
_id = self.id_from_index(index)
self.db.add_format(_id, ext, f)
f.close()
self.refresh_row(index.row())
self.emit(SIGNAL('formats_added'), index)
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return len(self.FIELDS)-3
def setData(self, index, value, role):
done = False
if role == Qt.EditRole:
row = index.row()
_id = self._data[row]["id"]
col = index.column()
val = unicode(value.toString().toUtf8(), 'utf-8').strip()
if col == 0:
col = "title"
elif col == 1:
col = "authors"
elif col == 2:
return False
elif col == 3:
return False
elif col == 4:
col, val = "rating", int(value.toInt()[0])
if val < 0:
val = 0
if val > 5:
val = 5
elif col == 5:
col = "publisher"
else:
return False
self.db.set_metadata_item(_id, col, val)
self._data[row][col] = val
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
index, index)
for i in range(len(self._orig_data)):
if self._orig_data[i]["id"] == self._data[row]["id"]:
self._orig_data[i][col] = self._data[row][col]
break
done = True
return done
def update_tags_and_comments(self, index, tags, comments):
_id = self.id_from_index(index)
self.db.set_metadata_item(_id, "tags", tags)
self.db.set_metadata_item(_id, "comments", comments)
self.refresh_row(index.row())
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
if index.isValid():
if index.column() not in [2, 3]:
flags |= Qt.ItemIsEditable
return flags
def set_data(self, db):
self.db = db
self._data = self.db.get_table(self.FIELDS)
self._orig_data = self._data
self.sort(0, Qt.DescendingOrder)
self.reset()
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return NONE
text = ""
if orientation == Qt.Horizontal:
if section == 0: text = "Title"
elif section == 1: text = "Author(s)"
elif section == 2: text = "Size"
elif section == 3: text = "Date"
elif section == 4: text = "Rating"
elif section == 5: text = "Publisher"
return QVariant(self.trUtf8(text))
else: return QVariant(str(1+section))
def info(self, row):
row = self._data[row]
cover = self.db.get_cover(row["id"])
exts = ",".join(self.db.get_extensions(row["id"]))
if cover:
pix = QPixmap()
pix.loadFromData(cover, "", Qt.AutoColor)
cover = None if pix.isNull() else pix
tags = row["tags"]
if not tags: tags = ""
comments = row["comments"]
if not comments:
comments = ""
comments = TableView.wrap(comments, width=80)
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):
datum = self.db.get_row_by_id(self._data[row]["id"], self.FIELDS)
self._data[row:row+1] = [datum]
for i in range(len(self._orig_data)):
if self._orig_data[i]["id"] == datum["id"]:
self._orig_data[i:i+1] = [datum]
break
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
self.index(row, 0), self.index(row, self.columnCount(0)-1))
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:
row, col = index.row(), index.column()
text = None
row = self._data[row]
if col == 4:
r = row["rating"] if row["rating"] else 0
if r < 0:
r = 0
if r > 5:
r = 5
return QVariant(r)
if col == 0:
text = TableView.wrap(row["title"], width=35)
elif col == 1:
au = row["authors"]
if au:
au = au.split("&")
jau = [ TableView.wrap(a, width=30).strip() for a in au ]
text = "\n".join(jau)
elif col == 2:
text = TableView.human_readable(row["size"])
elif col == 3:
text = time.strftime(TIME_WRITE_FMT, \
time.strptime(row["date"], self.TIME_READ_FMT))
elif col == 5:
pub = row["publisher"]
if pub:
text = TableView.wrap(pub, 20)
if text == None:
text = "Unknown"
return QVariant(text)
elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]:
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
elif role == Qt.ToolTipRole and index.isValid():
if index.column() in [0, 1, 4, 5]:
edit = "Double click to <b>edit</b> me<br><br>"
else:
edit = ""
return QVariant(edit + "You can <b>drag and drop</b> me to the \
desktop to save all my formats to your hard disk.")
return NONE
def sort(self, col, order):
descending = order != Qt.AscendingOrder
def getter(key, func):
return lambda x : func(itemgetter(key)(x))
if col == 0: key, func = "title", lambda x : x.lower()
if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower()\
if x else ""
if col == 2: key, func = "size", int
if col == 3: key, func = "date", lambda x: time.mktime(\
time.strptime(x, self.TIME_READ_FMT))
if col == 4: key, func = "rating", lambda x: x if x else 0
if col == 5: key, func = "publisher", lambda x : x.lower() if x else ""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data.sort(key=getter(key, func))
if descending: self._data.reverse()
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("sorted()"))
def search(self, query):
def query_in(book, q):
au = book["authors"]
if not au : au = "unknown"
pub = book["publisher"]
if not pub : pub = "unknown"
return q in book["title"].lower() or q in au.lower() or \
q in pub.lower()
queries = unicode(query, 'utf-8').lower().split()
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data = []
for book in self._orig_data:
match = True
for q in queries:
if query_in(book, q) : continue
else:
match = False
break
if match: self._data.append(book)
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("searched()"))
def delete(self, indices):
if len(indices): self.emit(SIGNAL("layoutAboutToBeChanged()"))
items = [ self._data[index.row()] for index in indices ]
for item in items:
_id = item["id"]
try:
self._data.remove(item)
except ValueError: continue
self.db.delete_by_id(_id)
for x in self._orig_data:
if x["id"] == _id: self._orig_data.remove(x)
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("deleted()"))
self.db.commit()
def add_book(self, path):
""" Must call search and sort on this models view after this """
_id = self.db.add_book(path)
self._orig_data.append(self.db.get_row_by_id(_id, self.FIELDS))
class DeviceBooksModel(QAbstractTableModel):
@apply
def booklist():
doc = """ The booklist this model is based on """
def fget(self):
return self._orig_data
return property(doc=doc, fget=fget)
def __init__(self, parent):
QAbstractTableModel.__init__(self, parent)
self._data = []
self._orig_data = []
def set_data(self, book_list):
self._data = book_list
self._orig_data = book_list
self.reset()
def rowCount(self, parent):
return len(self._data)
def columnCount(self, parent):
return 4
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return NONE
text = ""
if orientation == Qt.Horizontal:
if section == 0: text = "Title"
elif section == 1: text = "Author(s)"
elif section == 2: text = "Size"
elif section == 3: text = "Date"
return QVariant(self.trUtf8(text))
else: return QVariant(str(1+section))
def data(self, index, role):
if role == Qt.DisplayRole:
row, col = index.row(), index.column()
book = self._data[row]
if col == 0:
text = TableView.wrap(book.title, width=40)
elif col == 1:
au = book.authors
au = au.split("&")
jau = [ TableView.wrap(a, width=25).strip() for a in au ]
text = "\n".join(jau)
elif col == 2:
text = TableView.human_readable(book.size)
elif col == 3:
text = time.strftime(TIME_WRITE_FMT, book.datetime)
return QVariant(text)
elif role == Qt.TextAlignmentRole and index.column() in [2, 3]:
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
return NONE
def info(self, row):
row = self._data[row]
cover = None
try:
cover = row.thumbnail
pix = QPixmap()
pix.loadFromData(cover, "", Qt.AutoColor)
cover = None if pix.isNull() else pix
except:
traceback.print_exc()
au = row.authors if row.authors else "Unknown"
return row.title, au, TableView.human_readable(row.size), row.mime, cover
def sort(self, col, order):
def getter(key, func):
return lambda x : func(attrgetter(key)(x))
if col == 0: key, func = "title", lambda x : x.lower()
if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower()
if col == 2: key, func = "size", int
if col == 3: key, func = "datetime", lambda x: x
descending = order != Qt.AscendingOrder
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data.sort(key=getter(key, func))
if descending: self._data.reverse()
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("sorted()"))
def search(self, query):
queries = unicode(query, 'utf-8').lower().split()
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data = []
for book in self._orig_data:
match = True
for q in queries:
if q in book.title.lower() or q in book.authors.lower(): continue
else:
match = False
break
if match: self._data.append(book)
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("searched()"))
def delete(self, indices):
paths = []
rows = [ index.row() for index in indices ]
if not rows:
return
self.emit(SIGNAL("layoutAboutToBeChanged()"))
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()"))
return paths
def path(self, index):
return self._data[index.row()].path
def title(self, index):
return self._data[index.row()].title
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("layoutChanged()"))
def rowCount(self, parent):
base = 1
if self.show_reader:
base += 1
if self.show_card:
base += 1
return base
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