Bye bye old GUI
@ -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()
|
|
||||||
|
|
@ -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"])
|
|
@ -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)
|
|
@ -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>&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>&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&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. <br><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 & 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>&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>&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 &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>
|
|
@ -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
|
|
@ -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>
|
|
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 10 KiB |
@ -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> Size:</b> %2</td></tr>\
|
|
||||||
<tr><td><b>Author: </b>%3</td>\
|
|
||||||
<td><b> 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())
|
|
@ -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 <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</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>&Search:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy" >
|
|
||||||
<cstring>search</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="search" >
|
|
||||||
<property name="enabled" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="acceptDrops" >
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip" >
|
|
||||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
|
||||||
</property>
|
|
||||||
<property name="whatsThis" >
|
|
||||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoFillBackground" >
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="frame" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="clear_button" >
|
|
||||||
<property name="toolTip" >
|
|
||||||
<string>Reset Quick Search</string>
|
|
||||||
</property>
|
|
||||||
<property name="text" >
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon" >
|
|
||||||
<iconset resource="images.qrc" >:/images/clear.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="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><table><tr><td><b>Title: </b>%1</td><td><b>&nbsp;Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b>&nbsp;Type: </b>%4</td></tr></table></string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat" >
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="progress_bar" >
|
|
||||||
<property name="value" >
|
|
||||||
<number>100</number>
|
|
||||||
</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>
|
|
@ -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> © 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> Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b> 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
|
|
@ -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
|
|