gui2 now has info display, add, remove and sync functionality.
@ -32,6 +32,7 @@ class Device(object):
|
||||
FORMATS = ["lrf", "rtf", "pdf", "txt"]
|
||||
VENDOR_ID = 0x0000
|
||||
PRODUCT_ID = 0x0000
|
||||
THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None) :
|
||||
"""
|
||||
@ -106,31 +107,48 @@ class Device(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||
sync_booklists=False, end_session=True):
|
||||
"""
|
||||
Add a book to the device. If oncard is True then the book is copied
|
||||
to the card rather than main memory.
|
||||
|
||||
@param infile: The source file, should be opened in "rb" mode
|
||||
@param name: The name of the book file when uploaded to the
|
||||
device. The extension of name must be one of
|
||||
the supported formats for this device.
|
||||
@param info: A dictionary that must have the keys "title", "authors", "cover".
|
||||
C{info["cover"]} should be a three element tuple (width, height, data)
|
||||
where data is the image data in JPEG format as a string
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
"""
|
||||
def upload_books(self, files, names, on_card=False, end_session=True):
|
||||
'''
|
||||
Upload a list of books to the device. If a file already
|
||||
exists on the device, it should be replaced.
|
||||
@param files: A list of paths and/or file-like objects.
|
||||
@param names: A list of file names that the books should have
|
||||
once uploaded to the device. len(names) == len(files)
|
||||
@return: A list of 3-element tuples. The list is meant to be passed
|
||||
to L{add_books_to_metadata}.
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_book(self, paths, booklists, end_session=True):
|
||||
"""
|
||||
Remove the books specified by C{paths} from the device. The metadata
|
||||
cache on the device must also be updated.
|
||||
@classmethod
|
||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||
'''
|
||||
Add locations to the booklists. This function must not communicate with
|
||||
the device.
|
||||
@param locations: Result of a call to L{upload_books}
|
||||
@param metadata: List of dictionaries. Each dictionary must have the
|
||||
keys C{title}, C{authors}, C{cover}. The value of the C{cover} element
|
||||
can be None or a three element tuple (width, height, data)
|
||||
where data is the image data in JPEG format as a string.
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
"""
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_books(self, paths, end_session=True):
|
||||
'''
|
||||
Delete books at paths on device.
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def remove_books_from_metadata(cls, paths, booklists):
|
||||
'''
|
||||
Remove books from the metadata list. This function must not communicate
|
||||
with the device.
|
||||
@param paths: paths to books on the device.
|
||||
@param booklists: A tuple containing the result of calls to
|
||||
(L{books}(oncard=False), L{books}(oncard=True)).
|
||||
'''
|
||||
raise NotImplementedError()
|
||||
|
||||
def sync_booklists(self, booklists, end_session=True):
|
||||
|
@ -193,6 +193,17 @@ class BookList(list):
|
||||
node.elem.parentNode.removeChild(node.elem)
|
||||
node.elem.unlink()
|
||||
|
||||
def remove_book(self, path):
|
||||
node = None
|
||||
for book in self:
|
||||
if book.path == path:
|
||||
node = book
|
||||
self.remove(book)
|
||||
break
|
||||
if node:
|
||||
node.elem.parentNode.removeChild(node.elem)
|
||||
node.elem.unlink()
|
||||
|
||||
def add_book(self, info, name, size, ctime):
|
||||
""" Add a node into DOM tree representing a book """
|
||||
node = self.document.createElement(self.prefix + "text")
|
||||
@ -214,6 +225,7 @@ class BookList(list):
|
||||
w, h, data = info["cover"]
|
||||
except TypeError:
|
||||
w, h, data = None, None, None
|
||||
|
||||
if data:
|
||||
th = self.document.createElement(self.prefix + "thumbnail")
|
||||
th.setAttribute("width", str(w))
|
||||
|
@ -805,7 +805,7 @@ class PRS500(Device):
|
||||
return BookList(prefix=prefix, root=root, sfile=tfile)
|
||||
|
||||
@safe
|
||||
def remove_book(self, paths, booklists, end_session=True):
|
||||
def remove_books(self, paths, booklists, end_session=True):
|
||||
"""
|
||||
Remove the books specified by paths from the device. The metadata
|
||||
cache on the device should also be updated.
|
||||
@ -826,6 +826,58 @@ class PRS500(Device):
|
||||
if len(booklists[1]):
|
||||
self.upload_book_list(booklists[1], end_session=False)
|
||||
|
||||
@safe
|
||||
def upload_books(self, files, names, on_card=False, end_session=True):
|
||||
card = self.card(end_session=False)
|
||||
prefix = card + '/' if on_card else '/Data/media/books/'
|
||||
paths, ctimes, sizes = [], [], []
|
||||
names = iter(names)
|
||||
for file in files:
|
||||
infile = file if hasattr(file, 'read') else open(file, 'rb')
|
||||
infile.seek(0, 2)
|
||||
size = infile.tell()
|
||||
sizes.append(size)
|
||||
infile.seek(0)
|
||||
space = self.free_space(end_session=False)
|
||||
mspace = space[0]
|
||||
cspace = space[1] if space[1] >= space[2] else space[2]
|
||||
if on_card and size > cspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space "+\
|
||||
"on the storage card")
|
||||
if not on_card and size > mspace - 1024*1024:
|
||||
raise FreeSpaceError("There is insufficient free space " +\
|
||||
"in main memory")
|
||||
name = names.next()
|
||||
paths.append(prefix+name)
|
||||
self.put_file(infile, paths[-1], replace_file=True, end_session=False)
|
||||
ctimes.append(self.path_properties(paths[-1], end_session=False).ctime)
|
||||
return zip(paths, sizes, ctimes)
|
||||
|
||||
@classmethod
|
||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||
metadata = iter(metadata)
|
||||
for location in locations:
|
||||
info = metadata.next()
|
||||
path = location[0]
|
||||
on_card = 1 if path[1] == ':' else 0
|
||||
name = path.rpartition('/')[2]
|
||||
if not on_card:
|
||||
name = 'books/' + name
|
||||
booklists[on_card].add_book(info, name, *location[1:])
|
||||
fix_ids(*booklists)
|
||||
|
||||
@safe
|
||||
def delete_books(self, paths, end_session=True):
|
||||
for path in paths:
|
||||
self.del_file(path, end_session=False)
|
||||
|
||||
@classmethod
|
||||
def remove_books_from_metadata(cls, paths, booklists):
|
||||
for path in paths:
|
||||
on_card = 1 if path[1] == ':' else 0
|
||||
booklists[on_card].remove_book(path)
|
||||
fix_ids(*booklists)
|
||||
|
||||
@safe
|
||||
def add_book(self, infile, name, info, booklists, oncard=False, \
|
||||
sync_booklists=False, end_session=True):
|
||||
|
@ -1,13 +1,15 @@
|
||||
all : main_ui.py images_rc.py
|
||||
all : main_ui.py images_rc.pyc
|
||||
|
||||
main_ui.py : main.ui
|
||||
pyuic4 main.ui > main_ui.py
|
||||
|
||||
images_rc.py : images.qrc images
|
||||
images_rc.pyc : images.qrc images
|
||||
pyrcc4 images.qrc > images_rc.py
|
||||
python -c "import compiler; compiler.compileFile('images_rc.py')"
|
||||
rm -f images_rc.py
|
||||
|
||||
clean :
|
||||
rm main_ui.py images_rc.py
|
||||
rm main_ui.py images_rc.pyc
|
||||
|
||||
test : all
|
||||
python main.py
|
||||
|
@ -14,7 +14,8 @@
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
""" The GUI for libprs500. """
|
||||
import sys, os, re, StringIO, traceback
|
||||
from PyQt4.QtCore import QVariant
|
||||
from PyQt4.QtCore import QVariant, QSettings
|
||||
from PyQt4.QtGui import QFileDialog
|
||||
from libprs500 import __appname__ as APP_TITLE
|
||||
from libprs500 import __author__
|
||||
NONE = QVariant() #: Null value to return from the data function of item models
|
||||
@ -62,3 +63,31 @@ def human_readable(size):
|
||||
size = size[:size.find(".")+2]
|
||||
return size + " " + suffix
|
||||
|
||||
def choose_files(window, dialog, title, filetype='',
|
||||
extensions=[], all_files=True):
|
||||
'''
|
||||
Ask user to choose a bunch of files.
|
||||
@param dialog: Unique gialog name used to store the opened directory
|
||||
@param title: Title to show in dialogs titlebar
|
||||
@param filetype: What types of files is this dialog choosing
|
||||
@params extensions: list of allowable extension
|
||||
@params all_files: If True show all files
|
||||
'''
|
||||
settings = QSettings()
|
||||
_dir = settings.value(dialog, QVariant(os.path.expanduser("~"))).toString()
|
||||
books = []
|
||||
extensions = ['*.'+i for i in extensions]
|
||||
if extensions:
|
||||
filter = filetype + ' (' + ' '.join(extensions) + ')'
|
||||
if all_files:
|
||||
filter += ';;All files (*)'
|
||||
else:
|
||||
filter = 'All files (*)'
|
||||
files = QFileDialog.getOpenFileNames(window, title, _dir, filter)
|
||||
for file in files:
|
||||
file = unicode(file.toUtf8(), 'utf8')
|
||||
books.append(os.path.abspath(file))
|
||||
if books:
|
||||
settings.setValue(dialog, QVariant(os.path.dirname(books[0])))
|
||||
return books
|
||||
|
||||
|
@ -110,3 +110,21 @@ class DeviceManager(QObject):
|
||||
self.device.set_progress_reporter(updater)
|
||||
self.device.sync_booklists(booklists)
|
||||
return sync_booklists
|
||||
|
||||
def upload_books_func(self):
|
||||
'''Upload books to device'''
|
||||
def upload_books(updater, files, names, on_card=False):
|
||||
return self.device.upload_books(files, names, on_card, end_session=True)
|
||||
return upload_books
|
||||
|
||||
def add_books_to_metadata(self, locations, metadata, booklists):
|
||||
self.device_class.add_books_to_metadata(locations, metadata, booklists)
|
||||
|
||||
def delete_books_func(self):
|
||||
'''Remove books from device'''
|
||||
def delete_books(updater, paths):
|
||||
self.device.delete_books(paths, end_session=True)
|
||||
return delete_books
|
||||
|
||||
def remove_books_from_metadata(self, paths, booklists):
|
||||
self.device_class.remove_books_from_metadata(paths, booklists)
|
@ -1,18 +1,15 @@
|
||||
<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>images/book.svg</file>
|
||||
<file>images/clear_left.svg</file>
|
||||
<file>images/edit_input.svg</file>
|
||||
<file>images/jobs-animated.mng</file>
|
||||
<file>images/jobs.svg</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 alias="reader" >images/reader.png</file>
|
||||
<file>images/upload.png</file>
|
||||
<file>images/plus.svg</file>
|
||||
<file>images/reader.svg</file>
|
||||
<file>images/sd.svg</file>
|
||||
<file>images/sync.svg</file>
|
||||
<file>images/trash.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
Before Width: | Height: | Size: 8.6 KiB |
3414
src/libprs500/gui2/images/book.svg
Normal file
After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 5.7 KiB |
352
src/libprs500/gui2/images/clear_left.svg
Normal file
@ -0,0 +1,352 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45.1"
|
||||
version="1.0"
|
||||
sodipodi:docbase="/home/david"
|
||||
sodipodi:docname="clear-left.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
y2="-131.93112"
|
||||
x2="-45.096584"
|
||||
y1="-131.93112"
|
||||
x1="-88.058083"
|
||||
gradientTransform="matrix(0,1.022977,-1.022977,0,111.9686,137.8125)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient1980"
|
||||
xlink:href="#linearGradient3711"
|
||||
inkscape:collect="always" />
|
||||
<radialGradient
|
||||
r="36"
|
||||
fy="92"
|
||||
fx="343.99899"
|
||||
cy="92"
|
||||
cx="343.99899"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient1978"
|
||||
xlink:href="#linearGradient3711"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
y2="-383.9971"
|
||||
x2="-11.91648"
|
||||
y1="-383.9971"
|
||||
x1="-70.002899"
|
||||
gradientTransform="matrix(0,1,-1,0,-39.9985,140.0029)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient1976"
|
||||
xlink:href="#linearGradient26907"
|
||||
inkscape:collect="always" />
|
||||
<radialGradient
|
||||
r="63.912209"
|
||||
fy="116.88514"
|
||||
fx="63.975182"
|
||||
cy="115.70919"
|
||||
cx="63.912209"
|
||||
gradientTransform="matrix(1,0,0,0.197802,0,92.82166)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3743"
|
||||
xlink:href="#linearGradient3291"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3291">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3293" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3295" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3711"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-84.002403"
|
||||
y1="-383.9971"
|
||||
x2="-23.516129"
|
||||
y2="-383.9975"
|
||||
gradientTransform="matrix(0,1,-1,0,-39.9985,140.0029)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:white;stop-opacity:1;"
|
||||
id="stop3713" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:white;stop-opacity:0;"
|
||||
id="stop3715" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0,1,-1,0,-39.9985,140.0029)"
|
||||
y2="-383.9971"
|
||||
x2="-12.0029"
|
||||
y1="-383.9971"
|
||||
x1="-84.002403"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient26907">
|
||||
<stop
|
||||
id="stop26909"
|
||||
style="stop-color:#888a85;stop-opacity:1;"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop26911"
|
||||
style="stop-color:#2e3436;stop-opacity:1;"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.197802,0,92.82166)"
|
||||
r="63.912209"
|
||||
fy="115.7093"
|
||||
fx="63.912209"
|
||||
cy="115.70919"
|
||||
cx="63.912209"
|
||||
id="radialGradient3297"
|
||||
xlink:href="#linearGradient3291"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient2871"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop2873"
|
||||
offset="0"
|
||||
style="stop-color:white;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop2875"
|
||||
offset="1"
|
||||
style="stop-color:white;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2898"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop2900"
|
||||
offset="0"
|
||||
style="stop-color:#2c72c7;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop2902"
|
||||
offset="1"
|
||||
style="stop-color:#2c72c7;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2898"
|
||||
id="radialGradient2505"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.113341,0,0,0.899421,144.15267,184.75423)"
|
||||
cx="63.249104"
|
||||
cy="29.640472"
|
||||
fx="63.249104"
|
||||
fy="29.640472"
|
||||
r="58.621283" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2898"
|
||||
id="radialGradient2508"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.113341,0,0,0.899421,144.15267,184.75423)"
|
||||
cx="63.249104"
|
||||
cy="29.640472"
|
||||
fx="63.249104"
|
||||
fy="29.640472"
|
||||
r="58.621283" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2898"
|
||||
id="radialGradient2511"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.113341,0,0,0.899421,144.15267,184.75423)"
|
||||
cx="63.249104"
|
||||
cy="29.640472"
|
||||
fx="63.249104"
|
||||
fy="29.640472"
|
||||
r="58.621283" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2898"
|
||||
id="radialGradient2514"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.113341,0,0,0.899421,144.15267,184.75423)"
|
||||
cx="63.249104"
|
||||
cy="29.640472"
|
||||
fx="63.249104"
|
||||
fy="29.640472"
|
||||
r="58.621283" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2871"
|
||||
id="linearGradient2518"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.908626,0,0,0.908626,131.38537,118.41482)"
|
||||
x1="50.389755"
|
||||
y1="6.6258311"
|
||||
x2="50.389755"
|
||||
y2="56.132732" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2871"
|
||||
id="linearGradient2521"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.908626,0,0,0.908626,131.38537,118.41482)"
|
||||
x1="50.78854"
|
||||
y1="-22.328487"
|
||||
x2="52.001846"
|
||||
y2="98.047089" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="linearGradient2524"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.022977,-1.022977,0,118.90977,143.61542)"
|
||||
x1="-88.058083"
|
||||
y1="-131.93112"
|
||||
x2="-45.096584"
|
||||
y2="-131.93112" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient26907"
|
||||
id="linearGradient3498"
|
||||
x1="105.21252"
|
||||
y1="92.100929"
|
||||
x2="84.151566"
|
||||
y2="28.206367"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="linearGradient3502"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="58.714989"
|
||||
y1="23.999331"
|
||||
x2="58.714989"
|
||||
y2="69.301285"
|
||||
gradientTransform="matrix(0.8571429,0,0,0.8571429,9.1428571,9.150686)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="linearGradient3505"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.591298,-1.591298,0,-12.882452,195.15999)"
|
||||
x1="-88.058083"
|
||||
y1="-131.93112"
|
||||
x2="-45.096584"
|
||||
y2="-131.93112" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="linearGradient3512"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.8571429,0,0,0.8571429,12.305954,9.150686)"
|
||||
x1="58.714989"
|
||||
y1="12.817292"
|
||||
x2="58.714989"
|
||||
y2="74.892502" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="radialGradient3518"
|
||||
cx="76.780853"
|
||||
cy="141.12396"
|
||||
fx="76.780853"
|
||||
fy="141.12396"
|
||||
r="49.162853"
|
||||
gradientTransform="matrix(1.0602856,0,0,0.1805211,-4.233122,72.750927)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="64"
|
||||
inkscape:cy="64"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="128px"
|
||||
height="128px"
|
||||
showgrid="false"
|
||||
gridempspacing="2"
|
||||
gridspacingx="4px"
|
||||
gridspacingy="4px"
|
||||
showborder="false"
|
||||
inkscape:grid-bbox="false"
|
||||
inkscape:window-width="746"
|
||||
inkscape:window-height="712"
|
||||
inkscape:window-x="18"
|
||||
inkscape:window-y="32" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
sodipodi:nodetypes="ccc"
|
||||
id="path2276"
|
||||
d="M 26.78122,97.797937 L 26.78122,95.003228 L 26.78122,97.797937 z "
|
||||
style="fill:#ffffff;fill-opacity:0.75688076;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccc"
|
||||
id="path2481"
|
||||
d="M 57.833968,116.6694 L 57.833968,113.87469 L 57.833968,116.6694 z "
|
||||
style="fill:#ffffff;fill-opacity:0.75688076;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:url(#linearGradient3498);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 117.98502,24 C 119.09959,24 120,24.951655 120,26.129676 L 120,101.97327 C 120,103.15129 119.09959,104.10293 117.98502,104.10293 L 49.810672,104.10293 C 49.753021,104.10293 49.704568,104.10791 49.648173,104.10293 C 49.081965,104.15046 48.49064,103.94409 48.055692,103.48465 L 8.5912566,65.562848 C 7.8029145,64.730097 7.8029145,63.372848 8.5912566,62.540097 L 48.055692,24.652645 C 48.474329,24.210424 49.037322,24.00691 49.583174,24.03435 C 49.661578,24.024618 49.729716,24 49.810672,24 L 117.98502,24 z "
|
||||
id="rect1969"
|
||||
sodipodi:nodetypes="cccccsccccscc" />
|
||||
<path
|
||||
style="opacity:0.8;fill:url(#linearGradient3512);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 52.687242,29.71875 C 52.617851,29.71875 52.566947,29.74166 52.499742,29.75 C 52.031868,29.72648 51.546074,29.902203 51.187242,30.28125 L 17.343492,62.75 C 16.914019,63.203667 16.758289,63.85147 16.874742,64.4375 C 29.612903,69.972662 48.740097,73.343748 67.163097,73.34375 C 85.598588,73.34375 102.42183,69.979357 115.1631,64.4375 L 115.1631,31.5625 C 115.1631,30.552768 114.3997,29.71875 113.44435,29.71875 L 52.687242,29.71875 z "
|
||||
id="path3500"
|
||||
sodipodi:nodetypes="cscccscccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cscccscccc"
|
||||
id="path3514"
|
||||
d="M 52.687242,98.226781 C 52.617851,98.226781 52.566947,98.203871 52.499742,98.195531 C 52.031868,98.219051 51.546074,98.043328 51.187242,97.664281 L 17.343492,65.195531 C 16.914019,64.741864 16.758289,64.094061 16.874742,63.508031 C 29.612903,57.972869 48.740097,54.601783 67.163097,54.601781 C 85.598588,54.601781 102.42183,57.966174 115.1631,63.508031 L 115.1631,96.383031 C 115.1631,97.392763 114.3997,98.226781 113.44435,98.226781 L 52.687242,98.226781 z "
|
||||
style="opacity:0.3;fill:url(#radialGradient3518);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
<g
|
||||
id="g3527"
|
||||
transform="matrix(0.5450885,-0.5450884,0.5450884,0.5450885,-91.905703,107.44253)">
|
||||
<path
|
||||
style="opacity:0.8;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#323232;stroke-width:5.18893194;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1"
|
||||
d="M 189.16642,77.673811 L 189.16642,110.22823 L 157.16642,109.67381 L 157.16642,125.67381 L 189.16642,125.67381 L 189.16642,157.67381 L 205.16642,157.67381 L 205.16642,125.67381 L 237.16642,125.67381 L 237.16642,110.22823 L 205.16642,110.22823 L 205.16642,77.673811 L 189.16642,77.673811 z "
|
||||
id="path1990"
|
||||
sodipodi:nodetypes="ccccccccccccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccccc"
|
||||
id="rect3232"
|
||||
d="M 189.16642,77.673811 L 189.16642,110.22823 L 157.16642,109.67381 L 157.16642,125.67381 L 189.16642,125.67381 L 189.16642,157.67381 L 205.16642,157.67381 L 205.16642,125.67381 L 237.16642,125.67381 L 237.16642,110.22823 L 205.16642,110.22823 L 205.16642,77.673811 L 189.16642,77.673811 z "
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.5 KiB |
1071
src/libprs500/gui2/images/edit_input.svg
Normal file
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 7.2 KiB |
700
src/libprs500/gui2/images/plus.svg
Normal file
@ -0,0 +1,700 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
id="svg1307"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.43"
|
||||
version="1.0"
|
||||
sodipodi:docbase="/home/pinheiro/Documents/pics/new oxygen/svg"
|
||||
sodipodi:docname="plus.svg">
|
||||
<defs
|
||||
id="defs1309">
|
||||
<linearGradient
|
||||
id="linearGradient2403">
|
||||
<stop
|
||||
style="stop-color:#28691f;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2405" />
|
||||
<stop
|
||||
style="stop-color:#42ad33;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop2407" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2389">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="0"
|
||||
id="stop2391" />
|
||||
<stop
|
||||
id="stop2393"
|
||||
offset="0.4375"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="0.56588125"
|
||||
id="stop2395" />
|
||||
<stop
|
||||
id="stop2423"
|
||||
offset="0.76237977"
|
||||
style="stop-color:#000000;stop-opacity:0.24705882;" />
|
||||
<stop
|
||||
id="stop2421"
|
||||
offset="0.77884614"
|
||||
style="stop-color:#000000;stop-opacity:0.49803922;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0.875"
|
||||
id="stop2397" />
|
||||
<stop
|
||||
id="stop2411"
|
||||
offset="0.875"
|
||||
style="stop-color:#000000;stop-opacity:0.49803922;" />
|
||||
<stop
|
||||
id="stop2399"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient2362">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2364" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop2366" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2321">
|
||||
<stop
|
||||
style="stop-color:#c3c3c3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2323" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop2325" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2287">
|
||||
<stop
|
||||
id="stop2299"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="0.4375"
|
||||
id="stop2307" />
|
||||
<stop
|
||||
id="stop2309"
|
||||
offset="0.58240438"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.49803922;"
|
||||
offset="0.76442307"
|
||||
id="stop2419" />
|
||||
<stop
|
||||
id="stop2303"
|
||||
offset="0.875"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.49803922;"
|
||||
offset="0.91826922"
|
||||
id="stop2413" />
|
||||
<stop
|
||||
id="stop2417"
|
||||
offset="0.96048182"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop2291" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3325">
|
||||
<stop
|
||||
id="stop3327"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3329"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3311">
|
||||
<stop
|
||||
style="stop-color:#2d2d2d;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3313" />
|
||||
<stop
|
||||
id="stop3319"
|
||||
offset="0.5"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3315" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3303">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.68345326;"
|
||||
offset="0"
|
||||
id="stop3305" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3307" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3291">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3293" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3295" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3273">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.55035973;"
|
||||
offset="0"
|
||||
id="stop3275" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3277" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3259">
|
||||
<stop
|
||||
id="stop3261"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:0.55035973;" />
|
||||
<stop
|
||||
id="stop3263"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3251">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3253" />
|
||||
<stop
|
||||
style="stop-color:#131313;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3255" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3235">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3237" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3239" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3225">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3227" />
|
||||
<stop
|
||||
style="stop-color:#aeaeae;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3229" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3217">
|
||||
<stop
|
||||
style="stop-color:#252525;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3219" />
|
||||
<stop
|
||||
style="stop-color:#252525;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3221" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3207">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3209" />
|
||||
<stop
|
||||
style="stop-color:#252525;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3211" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2257">
|
||||
<stop
|
||||
style="stop-color:#b4942a;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop2259" />
|
||||
<stop
|
||||
style="stop-color:#e4dcc9;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop2261" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3291"
|
||||
id="radialGradient3297"
|
||||
cx="63.912209"
|
||||
cy="115.70919"
|
||||
fx="63.912209"
|
||||
fy="115.7093"
|
||||
r="63.912209"
|
||||
gradientTransform="matrix(1,0,0,0.197802,0,92.82166)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2257"
|
||||
id="radialGradient1405"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.519831,9.412826e-2,-0.895354,13.78472,115.1882,-1545.166)"
|
||||
cx="42.617531"
|
||||
cy="120.64188"
|
||||
fx="42.617531"
|
||||
fy="120.64188"
|
||||
r="3.406888" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3311"
|
||||
id="radialGradient1407"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(6.22884e-2,-1.47547e-4,1.889714e-3,0.798624,69.12243,5.487066)"
|
||||
cx="95.505852"
|
||||
cy="59.591507"
|
||||
fx="95.505852"
|
||||
fy="59.591507"
|
||||
r="47.746404" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3225"
|
||||
id="radialGradient1409"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.297066,3.012623e-3,-1.134728e-3,0.488669,7.096503,-13.69501)"
|
||||
cx="49.009884"
|
||||
cy="8.4953122"
|
||||
fx="47.370888"
|
||||
fy="6.7701697"
|
||||
r="3.9750405" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3217"
|
||||
id="linearGradient1411"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.29707,-3.693584e-16,3.693584e-16,1.29707,7.064707,-20.57911)"
|
||||
x1="48.914677"
|
||||
y1="2.9719031"
|
||||
x2="48.913002"
|
||||
y2="2.5548496" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3207"
|
||||
id="radialGradient1413"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.29707,-4.501275e-16,6.640356e-17,0.1578,7.064707,-17.56653)"
|
||||
cx="49.011971"
|
||||
cy="2.6743078"
|
||||
fx="49.011971"
|
||||
fy="2.6743078"
|
||||
r="1.7246193" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3235"
|
||||
id="linearGradient1415"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.297066,3.012623e-3,-3.012623e-3,1.297066,7.112448,-20.56258)"
|
||||
x1="48.498562"
|
||||
y1="0.81150496"
|
||||
x2="48.732723"
|
||||
y2="2.3657269" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3251"
|
||||
id="linearGradient1417"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.28993,-5.022494e-16,5.050298e-16,1.29707,7.402337,-20.57911)"
|
||||
x1="46.051746"
|
||||
y1="3.0999987"
|
||||
x2="46.051746"
|
||||
y2="2.395859" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3273"
|
||||
id="radialGradient1419"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.860164,-2.800126e-16,6.473209e-17,0.1578,24.75801,-17.56653)"
|
||||
cx="49.011971"
|
||||
cy="2.6743078"
|
||||
fx="49.011971"
|
||||
fy="2.6743078"
|
||||
r="1.7246193" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3251"
|
||||
id="linearGradient1421"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.279856,4.983275e-16,-5.050298e-16,1.29707,-133.3868,-20.57911)"
|
||||
x1="46.051746"
|
||||
y1="3.0999987"
|
||||
x2="46.051746"
|
||||
y2="2.395859" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3259"
|
||||
id="radialGradient1423"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.853446,3.872019e-16,-5.817635e-17,0.1578,-116.1668,-17.56653)"
|
||||
cx="49.011971"
|
||||
cy="2.6743078"
|
||||
fx="49.011971"
|
||||
fy="2.6743078"
|
||||
r="1.7246193" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3303"
|
||||
id="radialGradient1425"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,7.573576e-17,-1.374554e-18,2.608014e-2,-7.697455e-14,7.26766)"
|
||||
cx="34.677639"
|
||||
cy="7.4622769"
|
||||
fx="34.677639"
|
||||
fy="7.4622769"
|
||||
r="47.595197" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3325"
|
||||
id="radialGradient1427"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.511766,-6.865741e-3,4.187271e-5,-9.110636e-3,87.10184,7.76835)"
|
||||
cx="34.677639"
|
||||
cy="7.4622769"
|
||||
fx="34.677639"
|
||||
fy="7.4622769"
|
||||
r="47.595196" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3259"
|
||||
id="radialGradient1433"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.853446,3.879821e-16,-5.832064e-17,0.1578,-115.9141,-7.300115)"
|
||||
cx="49.011971"
|
||||
cy="2.6743078"
|
||||
fx="49.011971"
|
||||
fy="2.6743078"
|
||||
r="1.7246193" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3251"
|
||||
id="linearGradient1436"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.279856,4.994967e-16,-5.062158e-16,1.29707,-133.1341,-10.31269)"
|
||||
x1="46.051746"
|
||||
y1="3.0999987"
|
||||
x2="46.051746"
|
||||
y2="2.395859" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3273"
|
||||
id="radialGradient1439"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.860164,-2.80798e-16,6.487638e-17,0.1578,24.50481,-7.300115)"
|
||||
cx="49.011971"
|
||||
cy="2.6743078"
|
||||
fx="49.011971"
|
||||
fy="2.6743078"
|
||||
r="1.7246193" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3251"
|
||||
id="linearGradient1442"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.28993,-5.034291e-16,5.062158e-16,1.29707,7.14915,-10.31269)"
|
||||
x1="46.051746"
|
||||
y1="3.0999987"
|
||||
x2="46.051746"
|
||||
y2="2.395859" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3235"
|
||||
id="linearGradient1445"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.297068,-1.880044e-3,1.880044e-3,1.297068,6.796523,-10.3225)"
|
||||
x1="48.498562"
|
||||
y1="0.81150496"
|
||||
x2="48.732723"
|
||||
y2="2.3657269" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3207"
|
||||
id="radialGradient1448"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.29707,-4.513135e-16,6.654785e-17,0.1578,6.81152,-7.300115)"
|
||||
cx="49.011971"
|
||||
cy="2.6743078"
|
||||
fx="49.011971"
|
||||
fy="2.6743078"
|
||||
r="1.7246193" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3217"
|
||||
id="linearGradient1451"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.29707,-3.705444e-16,3.705444e-16,1.29707,6.81152,-10.31269)"
|
||||
x1="48.914677"
|
||||
y1="2.9719031"
|
||||
x2="48.913002"
|
||||
y2="2.5548496" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3225"
|
||||
id="radialGradient1455"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.297068,-1.880044e-3,7.085819e-4,0.48867,6.806484,-3.45491)"
|
||||
cx="49.009884"
|
||||
cy="8.4953122"
|
||||
fx="47.370888"
|
||||
fy="6.7701697"
|
||||
r="3.9750405" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3311"
|
||||
id="radialGradient1462"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(6.228741e-2,-3.825032e-4,4.90218e-3,0.798611,68.90433,5.49306)"
|
||||
cx="95.505852"
|
||||
cy="59.591507"
|
||||
fx="95.505852"
|
||||
fy="59.591507"
|
||||
r="47.746404" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2257"
|
||||
id="radialGradient1466"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.520175,8.839467e-2,-0.843351,13.788,109.1206,-1545.323)"
|
||||
cx="42.617531"
|
||||
cy="120.64188"
|
||||
fx="42.617531"
|
||||
fy="120.64188"
|
||||
r="3.406888" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3325"
|
||||
id="radialGradient1470"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.511766,-6.865741e-3,4.187271e-5,-9.110636e-3,87.10184,7.76835)"
|
||||
cx="34.677639"
|
||||
cy="7.4622769"
|
||||
fx="34.677639"
|
||||
fy="7.4622769"
|
||||
r="47.595196" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2287"
|
||||
id="radialGradient2297"
|
||||
cx="95.796135"
|
||||
cy="56.931728"
|
||||
fx="95.990845"
|
||||
fy="39.602753"
|
||||
r="47.11924"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2321"
|
||||
id="linearGradient2327"
|
||||
x1="-42.789177"
|
||||
y1="82.913582"
|
||||
x2="229.1772"
|
||||
y2="81.155327"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2389"
|
||||
id="radialGradient2340"
|
||||
cx="59.385818"
|
||||
cy="52.046673"
|
||||
fx="59.385818"
|
||||
fy="52.046673"
|
||||
r="43.225086"
|
||||
gradientTransform="matrix(1.165294,-9.905815e-18,-1.210432e-17,1.180294,-9.816118,-9.597466)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2362"
|
||||
id="linearGradient2368"
|
||||
x1="74.332748"
|
||||
y1="17.912012"
|
||||
x2="54.983063"
|
||||
y2="90.126022"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.180422,0,0,1.180422,-10.39088,-10.58642)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2362"
|
||||
id="linearGradient2401"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.258277,0,0,1.258277,-15.29483,-12.98214)"
|
||||
x1="74.514832"
|
||||
y1="17.232468"
|
||||
x2="52.587749"
|
||||
y2="99.06546" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2403"
|
||||
id="linearGradient2409"
|
||||
x1="97.124756"
|
||||
y1="99.590462"
|
||||
x2="33.355057"
|
||||
y2="22.203432"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.065955,0,0,1.065955,-4.218613,-1.697485)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.5452637"
|
||||
inkscape:cx="131.61259"
|
||||
inkscape:cy="22.732118"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:document-units="px"
|
||||
inkscape:grid-bbox="true"
|
||||
guidetolerance="0.1px"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-width="1106"
|
||||
inkscape:window-height="958"
|
||||
inkscape:window-x="468"
|
||||
inkscape:window-y="25">
|
||||
<sodipodi:guide
|
||||
orientation="horizontal"
|
||||
position="32.487481"
|
||||
id="guide2204" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata1312">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>Oxygen team</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/LGPL/2.1/">
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/Notice" />
|
||||
<cc:permits
|
||||
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/ShareAlike" />
|
||||
<cc:requires
|
||||
rdf:resource="http://web.resource.org/cc/SourceCode" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:0.38934422;fill:url(#radialGradient2297);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.71249998;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
id="path1410"
|
||||
sodipodi:cx="95.796135"
|
||||
sodipodi:cy="56.931728"
|
||||
sodipodi:rx="47.11924"
|
||||
sodipodi:ry="47.11924"
|
||||
d="M 142.91537 56.931728 A 47.11924 47.11924 0 1 1 48.676895,56.931728 A 47.11924 47.11924 0 1 1 142.91537 56.931728 z"
|
||||
transform="matrix(1.354468,0,0,1.354468,-65.79139,-14.906)" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccc"
|
||||
id="path2276"
|
||||
d="M 50.892799,3.2812959 L 50.892799,0.48658747 L 50.892799,3.2812959 z "
|
||||
style="fill:#ffffff;fill-opacity:0.75688076;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:0.38139535;fill:url(#radialGradient3297);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
id="path3289"
|
||||
sodipodi:cx="63.912209"
|
||||
sodipodi:cy="115.70919"
|
||||
sodipodi:rx="63.912209"
|
||||
sodipodi:ry="12.641975"
|
||||
d="M 127.82442 115.70919 A 63.912209 12.641975 0 1 1 0,115.70919 A 63.912209 12.641975 0 1 1 127.82442 115.70919 z"
|
||||
transform="matrix(1,0,0,0.416667,0,74.87151)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;fill:url(#linearGradient2327);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.71249998;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
id="path2311"
|
||||
sodipodi:cx="59.385818"
|
||||
sodipodi:cy="53.232281"
|
||||
sodipodi:rx="43.225086"
|
||||
sodipodi:ry="43.809208"
|
||||
d="M 102.6109 53.232281 A 43.225086 43.809208 0 1 1 16.160732,53.232281 A 43.225086 43.809208 0 1 1 102.6109 53.232281 z"
|
||||
transform="matrix(1.303016,0,0,1.325387,-13.29675,-10.91954)" />
|
||||
<path
|
||||
transform="matrix(1.303016,0,0,1.325387,-13.29675,-10.91954)"
|
||||
d="M 102.6109 53.232281 A 43.225086 43.809208 0 1 1 16.160732,53.232281 A 43.225086 43.809208 0 1 1 102.6109 53.232281 z"
|
||||
sodipodi:ry="43.809208"
|
||||
sodipodi:rx="43.225086"
|
||||
sodipodi:cy="53.232281"
|
||||
sodipodi:cx="59.385818"
|
||||
id="path2338"
|
||||
style="opacity:0.38114754;fill:url(#radialGradient2340);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.71249998;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
style="opacity:1;fill:url(#linearGradient2409);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.71249998;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 56.583051,25.393632 C 55.968826,25.454025 55.498698,25.964941 55.498698,26.579648 L 55.498698,49.555934 L 32.522409,49.555934 C 31.866718,49.555934 31.336395,50.102652 31.336395,50.775836 L 31.336395,68.004033 C 31.336395,68.677227 31.86673,69.207007 32.522409,69.207007 L 55.498698,69.207007 L 55.498698,92.200234 C 55.498698,92.855924 56.045416,93.386248 56.71861,93.386248 L 73.946797,93.386248 C 74.61999,93.386248 75.14977,92.855913 75.14977,92.200234 L 75.14977,69.207007 L 98.126047,69.207007 C 98.781717,69.207007 99.312062,68.677238 99.312062,68.004033 L 99.312062,50.775836 C 99.312062,50.102652 98.781717,49.555934 98.126047,49.555934 L 75.14977,49.555934 L 75.14977,26.579648 C 75.14977,25.923961 74.61999,25.393632 73.946797,25.393632 L 56.71861,25.393632 C 56.676526,25.393632 56.624005,25.389606 56.583051,25.393632 z "
|
||||
id="rect2372"
|
||||
sodipodi:nodetypes="cccccccccccccccccccccc" />
|
||||
<path
|
||||
style="opacity:0.84426228;fill:url(#linearGradient2401);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.71249998;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 62.718389,2.7856523 C 34.26943,3.4970625 11.404252,26.4659 11.404251,51.425951 C 11.404251,55.288815 11.945809,59.047238 12.977098,62.632486 C 47.301752,55.986018 81.431322,56.068486 115.44808,61.688778 C 116.31046,58.392831 116.7457,54.948687 116.7457,51.425951 C 116.7457,26.06971 93.170775,2.7856523 64.09463,2.7856523 C 63.640317,2.7856523 63.16996,2.7743606 62.718389,2.7856523 z "
|
||||
id="path2342" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 1.5 KiB |
1624
src/libprs500/gui2/images/reader.svg
Normal file
After Width: | Height: | Size: 62 KiB |
6432
src/libprs500/gui2/images/sd.svg
Normal file
After Width: | Height: | Size: 473 KiB |
1685
src/libprs500/gui2/images/sync.svg
Normal file
After Width: | Height: | Size: 63 KiB |
4202
src/libprs500/gui2/images/trash.svg
Normal file
After Width: | Height: | Size: 245 KiB |
Before Width: | Height: | Size: 10 KiB |
@ -60,6 +60,7 @@ class JobManager(QAbstractTableModel):
|
||||
QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||
slot)
|
||||
job.start()
|
||||
return job.id
|
||||
|
||||
def job_done(self, id, *args, **kwargs):
|
||||
'''
|
||||
|
@ -12,6 +12,7 @@
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
from libprs500.ptempfile import PersistentTemporaryFile
|
||||
import os, textwrap, traceback, time, re
|
||||
from datetime import timedelta, datetime
|
||||
from operator import attrgetter
|
||||
@ -19,9 +20,9 @@ from math import cos, sin, pi
|
||||
from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, \
|
||||
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
||||
QPen, QStyle, QPainter, QLineEdit, QApplication, \
|
||||
QPalette
|
||||
QPalette, QItemSelectionModel
|
||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
||||
QCoreApplication, SIGNAL, QObject, QSize
|
||||
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
|
||||
|
||||
from libprs500.library.database import LibraryDatabase
|
||||
from libprs500.gui2 import NONE
|
||||
@ -47,8 +48,8 @@ class LibraryDelegate(QItemDelegate):
|
||||
self.factor = self.SIZE/100.
|
||||
|
||||
def sizeHint(self, option, index):
|
||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||
return QSize(num*(self.SIZE), self.SIZE+4)
|
||||
#num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||
return QSize(5*(self.SIZE), self.SIZE+4)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||
@ -84,13 +85,37 @@ class BooksModel(QAbstractTableModel):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
self.db = None
|
||||
self.cols = ['title', 'authors', 'size', 'date', 'rating', 'publisher']
|
||||
self.sorted_on = None
|
||||
self.sorted_on = (3, Qt.AscendingOrder)
|
||||
self.last_search = '' # The last search performed on this model
|
||||
|
||||
def set_database(self, db):
|
||||
if isinstance(db, (QString, basestring)):
|
||||
db = LibraryDatabase(os.path.expanduser(str(db)))
|
||||
self.db = db
|
||||
|
||||
def add_books(self, paths, formats, metadata, uris=[]):
|
||||
self.db.add_books(paths, formats, metadata, uris)
|
||||
|
||||
def row_indices(self, index):
|
||||
''' Return list indices of all cells in index.row()'''
|
||||
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
||||
|
||||
def removeRows(self, row, count, parent=QModelIndex()):
|
||||
rows = [row + i for i in range(count)]
|
||||
self.beginRemoveRows(parent, row, row+count-1)
|
||||
self.db.delete_books(rows)
|
||||
self.endRemoveRows()
|
||||
|
||||
def removeRow(self, row, parent=QModelIndex()):
|
||||
self.removeRows(row, 1)
|
||||
|
||||
def delete_books(self, indices):
|
||||
rows = [ i.row() for i in indices ]
|
||||
for row in rows:
|
||||
self.removeRow(row)
|
||||
self.emit(SIGNAL('layoutChanged()'))
|
||||
self.emit(SIGNAL('deleted()'))
|
||||
|
||||
def search_tokens(self, text):
|
||||
tokens = []
|
||||
quot = re.search('"(.*?)"', text)
|
||||
@ -104,8 +129,8 @@ class BooksModel(QAbstractTableModel):
|
||||
def search(self, text, refinement):
|
||||
tokens = self.search_tokens(text)
|
||||
self.db.filter(tokens, refinement)
|
||||
self.last_search = text
|
||||
self.reset()
|
||||
self.emit(SIGNAL('searched()'))
|
||||
|
||||
def sort(self, col, order):
|
||||
if not self.db:
|
||||
@ -113,9 +138,14 @@ class BooksModel(QAbstractTableModel):
|
||||
ascending = order == Qt.AscendingOrder
|
||||
self.db.refresh(self.cols[col], ascending)
|
||||
self.reset()
|
||||
self.emit(SIGNAL('sorted()'))
|
||||
self.sorted_on = (col, order)
|
||||
|
||||
def resort(self):
|
||||
self.sort(*self.sorted_on)
|
||||
|
||||
def research(self):
|
||||
self.search(self.last_search, False)
|
||||
|
||||
def database_needs_migration(self):
|
||||
path = os.path.expanduser('~/library.db')
|
||||
return self.db.is_empty() and \
|
||||
@ -128,6 +158,72 @@ class BooksModel(QAbstractTableModel):
|
||||
def rowCount(self, parent):
|
||||
return self.db.rows() if self.db else 0
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
data = {}
|
||||
idx = current.row()
|
||||
cdata = self.db.cover(idx)
|
||||
if cdata:
|
||||
data['cover'] = cdata
|
||||
tags = self.db.tags(idx)
|
||||
if tags:
|
||||
tags = tags.replace(',', ', ')
|
||||
else:
|
||||
tags = 'None'
|
||||
data['Tags'] = tags
|
||||
formats = self.db.formats(idx)
|
||||
if formats:
|
||||
formats = formats.replace(',', ', ')
|
||||
else:
|
||||
formats = 'None'
|
||||
data['Formats'] = formats
|
||||
comments = self.db.comments(idx)
|
||||
if not comments:
|
||||
comments = 'None'
|
||||
data['Comments'] = comments
|
||||
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
|
||||
|
||||
def get_metadata(self, rows):
|
||||
metadata = []
|
||||
for row in rows:
|
||||
row = row.row()
|
||||
au = self.db.authors(row)
|
||||
if not au:
|
||||
au = 'Unknown'
|
||||
au = au.split(',')
|
||||
if len(au) > 1:
|
||||
t = ', '.join(au[:-1])
|
||||
t += ' & ' + au[-1]
|
||||
au = t
|
||||
else:
|
||||
au = ' & '.join(au)
|
||||
mi = {
|
||||
'title' : self.db.title(row),
|
||||
'authors' : au,
|
||||
'cover' : self.db.cover(row),
|
||||
}
|
||||
metadata.append(mi)
|
||||
return metadata
|
||||
|
||||
def get_preferred_formats(self, rows, formats):
|
||||
ans = []
|
||||
for row in (row.row() for row in rows):
|
||||
format = None
|
||||
for f in self.db.formats(row).split(','):
|
||||
if f.lower() in formats:
|
||||
format = f
|
||||
break
|
||||
if format:
|
||||
pt = PersistentTemporaryFile(suffix='.'+format)
|
||||
pt.write(self.db.format(row, format))
|
||||
pt.seek(0)
|
||||
ans.append(pt)
|
||||
else:
|
||||
ans.append(None)
|
||||
return ans
|
||||
|
||||
def id(self, row):
|
||||
return self.db.id(row.row())
|
||||
|
||||
def data(self, index, role):
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
row, col = index.row(), index.column()
|
||||
@ -231,13 +327,30 @@ class BooksView(QTableView):
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSortingEnabled(True)
|
||||
self.setItemDelegateForColumn(4, LibraryDelegate(self))
|
||||
QObject.connect(self._model, SIGNAL('sorted()'), self.resizeRowsToContents)
|
||||
QObject.connect(self._model, SIGNAL('searched()'), self.resizeRowsToContents)
|
||||
QObject.connect(self._model, SIGNAL('modelReset()'), self.resizeRowsToContents)
|
||||
QObject.connect(self._model, SIGNAL('deleted()'), self.deleted)
|
||||
QObject.connect(self._model, SIGNAL('update_current()'), self.update_current)
|
||||
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
||||
self._model.current_changed)
|
||||
#self.verticalHeader().setVisible(False)
|
||||
|
||||
def set_database(self, db):
|
||||
self._model.set_database(db)
|
||||
|
||||
def update_current(self):
|
||||
'''
|
||||
Clear selection and update durrent index.
|
||||
'''
|
||||
cidx = self.currentIndex()
|
||||
self.selectionModel().clear()
|
||||
for idx in self.model().row_indices(cidx):
|
||||
self.selectionModel().select(idx, QItemSelectionModel.Select)
|
||||
self.selectionModel().setCurrentIndex(cidx, QItemSelectionModel.NoUpdate)
|
||||
|
||||
def deleted(self):
|
||||
self.resizeRowsToContents()
|
||||
self.update_current()
|
||||
|
||||
def migrate_database(self):
|
||||
if self._model.database_needs_migration():
|
||||
print 'Migrating database from pre 0.4.0 version'
|
||||
@ -256,13 +369,20 @@ class BooksView(QTableView):
|
||||
LibraryDatabase.import_old_database(path, self._model.db.conn, meter)
|
||||
|
||||
def connect_to_search_box(self, sb):
|
||||
QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self._model.search)
|
||||
QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
|
||||
self._model.search)
|
||||
|
||||
def connect_to_book_display(self, bd):
|
||||
QObject.connect(self._model, SIGNAL('new_bookdisplay_data(PyQt_PyObject)'),
|
||||
bd)
|
||||
|
||||
|
||||
class DeviceBooksView(BooksView):
|
||||
|
||||
def __init__(self, parent):
|
||||
BooksView.__init__(self, parent, DeviceBooksModel)
|
||||
self.columns_resized = False
|
||||
self.resize_on_select = False
|
||||
|
||||
def resizeColumnsToContents(self):
|
||||
QTableView.resizeColumnsToContents(self)
|
||||
@ -274,11 +394,47 @@ class DeviceBooksView(BooksView):
|
||||
class DeviceBooksModel(BooksModel):
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
BooksModel.__init__(self, parent)
|
||||
self.db = []
|
||||
self.map = []
|
||||
self.sorted_map = []
|
||||
self.unknown = str(self.trUtf8('Unknown'))
|
||||
self.marked_for_deletion = {}
|
||||
|
||||
def mark_for_deletion(self, id, rows):
|
||||
self.marked_for_deletion[id] = self.indices(rows)
|
||||
for row in rows:
|
||||
indices = self.row_indices(row)
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
||||
|
||||
|
||||
def deletion_done(self, id, succeeded=True):
|
||||
if not self.marked_for_deletion.has_key(id):
|
||||
return
|
||||
rows = self.marked_for_deletion.pop(id)
|
||||
for row in rows:
|
||||
if not succeeded:
|
||||
indices = self.row_indices(self.index(row, 0))
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
||||
|
||||
def remap(self):
|
||||
self.set_database(self.db)
|
||||
self.resort()
|
||||
self.research()
|
||||
self.emit(SIGNAL('update_current()'))
|
||||
|
||||
|
||||
def indices_to_be_deleted(self):
|
||||
ans = []
|
||||
for v in self.marked_for_deletion.values():
|
||||
ans.extend(v)
|
||||
return ans
|
||||
|
||||
def flags(self, index):
|
||||
if self.map[index.row()] in self.indices_to_be_deleted():
|
||||
return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python
|
||||
return BooksModel.flags(self, index)
|
||||
|
||||
|
||||
def search(self, text, refinement):
|
||||
tokens = self.search_tokens(text)
|
||||
@ -293,9 +449,9 @@ class DeviceBooksModel(BooksModel):
|
||||
break
|
||||
if add:
|
||||
result.append(i)
|
||||
|
||||
self.map = result
|
||||
self.reset()
|
||||
self.emit(SIGNAL('searched()'))
|
||||
|
||||
def sort(self, col, order):
|
||||
if not self.db:
|
||||
@ -330,7 +486,6 @@ class DeviceBooksModel(BooksModel):
|
||||
self.sorted_map.sort(cmp=fcmp, reverse=descending)
|
||||
self.sorted_on = (col, order)
|
||||
self.reset()
|
||||
self.emit(SIGNAL('sorted()'))
|
||||
|
||||
def columnCount(self, parent):
|
||||
return 4
|
||||
@ -342,6 +497,34 @@ class DeviceBooksModel(BooksModel):
|
||||
self.db = db
|
||||
self.map = list(range(0, len(db)))
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
data = {}
|
||||
item = self.db[self.map[current.row()]]
|
||||
cdata = item.thumbnail
|
||||
if cdata:
|
||||
data['cover'] = cdata
|
||||
type = 'Unknown'
|
||||
ext = os.path.splitext(item.path)[1]
|
||||
if ext:
|
||||
type = ext[1:].lower()
|
||||
data['Format'] = type
|
||||
data['Path'] = item.path
|
||||
dt = item.datetime
|
||||
dt = datetime(*dt[0:6])
|
||||
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
|
||||
data['Timestamp'] = dt.ctime()
|
||||
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
|
||||
|
||||
def paths(self, rows):
|
||||
return [self.db[self.map[r.row()]].path for r in rows ]
|
||||
|
||||
def indices(self, rows):
|
||||
'''
|
||||
Return indices into underlying database from rows
|
||||
'''
|
||||
return [ self.map[r.row()] for r in rows]
|
||||
|
||||
|
||||
def data(self, index, role):
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
row, col = index.row(), index.column()
|
||||
@ -373,6 +556,8 @@ class DeviceBooksModel(BooksModel):
|
||||
elif role == Qt.TextAlignmentRole and index.column() in [2, 3]:
|
||||
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
||||
elif role == Qt.ToolTipRole and index.isValid():
|
||||
if self.map[index.row()] in self.indices_to_be_deleted():
|
||||
return QVariant('Marked for deletion')
|
||||
if index.column() in [0, 1]:
|
||||
return QVariant("Double click to <b>edit</b> me<br><br>")
|
||||
return NONE
|
||||
@ -399,25 +584,32 @@ class DeviceBooksModel(BooksModel):
|
||||
return done
|
||||
|
||||
class SearchBox(QLineEdit):
|
||||
|
||||
INTERVAL = 1000 #: Time to wait before emitting search signal
|
||||
|
||||
def __init__(self, parent):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.help_text = 'Search by title, author, publisher, tags and comments'
|
||||
self.setText(self.help_text)
|
||||
self.home(False)
|
||||
QObject.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
||||
self.default_palette = QApplication.palette(self)
|
||||
gray = QPalette(self.default_palette)
|
||||
gray.setBrush(QPalette.Text, QBrush(QColor('lightgray')))
|
||||
self.setPalette(gray)
|
||||
self.initial_state = True
|
||||
self.default_palette = QApplication.palette(self)
|
||||
self.gray = QPalette(self.default_palette)
|
||||
self.gray.setBrush(QPalette.Text, QBrush(QColor('lightgray')))
|
||||
self.prev_search = ''
|
||||
self.timer = None
|
||||
self.interval = 1000 #: Time to wait before emitting search signal
|
||||
self.clear_to_help()
|
||||
QObject.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
||||
|
||||
|
||||
def normalize_state(self):
|
||||
self.setText('')
|
||||
self.setPalette(self.default_palette)
|
||||
|
||||
def clear_to_help(self):
|
||||
self.setText(self.help_text)
|
||||
self.home(False)
|
||||
self.setPalette(self.gray)
|
||||
self.initial_state = True
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if self.initial_state:
|
||||
self.normalize_state()
|
||||
@ -433,7 +625,7 @@ class SearchBox(QLineEdit):
|
||||
def text_edited_slot(self, text):
|
||||
text = str(text)
|
||||
self.prev_text = text
|
||||
self.timer = self.startTimer(self.interval)
|
||||
self.timer = self.startTimer(self.__class__.INTERVAL)
|
||||
|
||||
def timerEvent(self, event):
|
||||
self.killTimer(event.timerId())
|
||||
|
@ -12,14 +12,18 @@
|
||||
## 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
|
||||
from libprs500.devices.interface import Device
|
||||
from libprs500 import __appname__
|
||||
import os, tempfile, sys
|
||||
|
||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
||||
QSettings, QVariant, QSize, QThread
|
||||
from PyQt4.QtGui import QErrorMessage
|
||||
QSettings, QVariant, QSize, QThread, QBuffer, QByteArray
|
||||
from PyQt4.QtGui import QErrorMessage, QPixmap, QColor, QPainter, QMenu, QIcon
|
||||
from PyQt4.QtSvg import QSvgRenderer
|
||||
|
||||
from libprs500 import __version__ as VERSION
|
||||
from libprs500.gui2 import APP_TITLE, installErrorHandler
|
||||
from libprs500.ebooks.metadata.meta import get_metadata
|
||||
from libprs500.gui2 import APP_TITLE, installErrorHandler, choose_files
|
||||
from libprs500.gui2.main_ui import Ui_MainWindow
|
||||
from libprs500.gui2.device import DeviceDetector, DeviceManager
|
||||
from libprs500.gui2.status import StatusBar
|
||||
@ -27,6 +31,19 @@ from libprs500.gui2.jobs import JobManager, JobException
|
||||
|
||||
class Main(QObject, Ui_MainWindow):
|
||||
|
||||
def set_default_thumbnail(self, height):
|
||||
r = QSvgRenderer(':/images/book.svg')
|
||||
pixmap = QPixmap(height, height)
|
||||
pixmap.fill(QColor(255,255,255))
|
||||
p = QPainter(pixmap)
|
||||
r.render(p)
|
||||
p.end()
|
||||
ba = QByteArray()
|
||||
buf = QBuffer(ba)
|
||||
buf.open(QBuffer.WriteOnly)
|
||||
pixmap.save(buf, 'JPEG')
|
||||
self.default_thumbnail = (pixmap.width(), pixmap.height(), ba.data())
|
||||
|
||||
def __init__(self, window):
|
||||
QObject.__init__(self)
|
||||
Ui_MainWindow.__init__(self)
|
||||
@ -35,8 +52,9 @@ class Main(QObject, Ui_MainWindow):
|
||||
self.read_settings()
|
||||
self.job_manager = JobManager()
|
||||
self.device_manager = None
|
||||
self.temporary_slots = {}
|
||||
|
||||
self.upload_memory = {}
|
||||
self.delete_memory = {}
|
||||
self.default_thumbnail = None
|
||||
####################### Location View ########################
|
||||
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
|
||||
self.location_selected)
|
||||
@ -51,17 +69,36 @@ class Main(QObject, Ui_MainWindow):
|
||||
QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added)
|
||||
QObject.connect(self.job_manager, SIGNAL('no_more_jobs()'), self.status_bar.no_more_jobs)
|
||||
|
||||
####################### Setup Toolbar #####################
|
||||
sm = QMenu()
|
||||
sm.addAction(QIcon(':/images/reader.svg'), 'Send to main memory')
|
||||
sm.addAction(QIcon(':/images/sd.svg'), 'Send to storage card')
|
||||
self.sync_menu = sm # Needed
|
||||
QObject.connect(self.action_add, SIGNAL("triggered(bool)"), self.add_books)
|
||||
QObject.connect(self.action_del, SIGNAL("triggered(bool)"), self.delete_books)
|
||||
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit_metadata)
|
||||
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
|
||||
QObject.connect(sm.actions()[0], SIGNAL('triggered(bool)'), self.sync_to_main_memory)
|
||||
QObject.connect(sm.actions()[1], SIGNAL('triggered(bool)'), self.sync_to_card)
|
||||
self.action_sync.setMenu(sm)
|
||||
self.tool_bar.insertAction(self.action_edit, self.action_sync)
|
||||
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
|
||||
####################### Library view ########################
|
||||
self.library_view.set_database(self.database_path)
|
||||
self.library_view.connect_to_search_box(self.search)
|
||||
self.memory_view.connect_to_search_box(self.search)
|
||||
self.card_view.connect_to_search_box(self.search)
|
||||
for func, target in [
|
||||
('connect_to_search_box', self.search),
|
||||
('connect_to_book_display', self.status_bar.book_info.show_data),
|
||||
]:
|
||||
for view in (self.library_view, self.memory_view, self.card_view):
|
||||
getattr(view, func)(target)
|
||||
|
||||
self.memory_view.connect_dirtied_signal(self.upload_booklists)
|
||||
self.card_view.connect_dirtied_signal(self.upload_booklists)
|
||||
|
||||
window.closeEvent = self.close_event
|
||||
window.show()
|
||||
self.stack.setCurrentIndex(0)
|
||||
self.library_view.migrate_database()
|
||||
self.library_view.sortByColumn(3, Qt.DescendingOrder)
|
||||
self.library_view.resizeColumnsToContents()
|
||||
@ -75,31 +112,36 @@ class Main(QObject, Ui_MainWindow):
|
||||
self.detector.start(QThread.InheritPriority)
|
||||
|
||||
|
||||
def upload_booklists(self):
|
||||
booklists = self.memory_view.model().db, self.card_view.model().db
|
||||
self.job_manager.run_device_job(None, self.device_manager.sync_booklists_func(),
|
||||
booklists)
|
||||
def current_view(self):
|
||||
'''Convenience method that returns the currently visible view '''
|
||||
idx = self.stack.currentIndex()
|
||||
if idx == 0:
|
||||
return self.library_view
|
||||
if idx == 1:
|
||||
return self.memory_view
|
||||
if idx == 2:
|
||||
return self.card_view
|
||||
|
||||
def location_selected(self, location):
|
||||
page = 0 if location == 'library' else 1 if location == 'main' else 2
|
||||
self.stack.setCurrentIndex(page)
|
||||
if page == 1:
|
||||
self.memory_view.resizeRowsToContents()
|
||||
self.memory_view.resizeColumnsToContents()
|
||||
if page == 2:
|
||||
self.card_view.resizeRowsToContents()
|
||||
self.card_view.resizeColumnsToContents()
|
||||
def booklists(self):
|
||||
return self.memory_view.model().db, self.card_view.model().db
|
||||
|
||||
def job_exception(self, id, exception, formatted_traceback):
|
||||
raise JobException, str(exception) + '\n\r' + formatted_traceback
|
||||
|
||||
|
||||
########################## Connect to device ##############################
|
||||
def device_detected(self, cls, connected):
|
||||
'''
|
||||
Called when a device is connected to the computer.
|
||||
'''
|
||||
if connected:
|
||||
self.device_manager = DeviceManager(cls)
|
||||
func = self.device_manager.info_func()
|
||||
self.job_manager.run_device_job(self.info_read, func)
|
||||
self.set_default_thumbnail(cls.THUMBNAIL_HEIGHT)
|
||||
|
||||
def info_read(self, id, result, exception, formatted_traceback):
|
||||
'''
|
||||
Called once device information has been read.
|
||||
'''
|
||||
if exception:
|
||||
self.job_exception(id, exception, formatted_traceback)
|
||||
return
|
||||
@ -107,20 +149,232 @@ class Main(QObject, Ui_MainWindow):
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
self.vanity.setText(self.vanity_template.arg('Connected '+' '.join(info[:-1])))
|
||||
func = self.device_manager.books_func()
|
||||
self.job_manager.run_device_job(self.books_read, func)
|
||||
self.job_manager.run_device_job(self.metadata_downloaded, func)
|
||||
|
||||
def books_read(self, id, result, exception, formatted_traceback):
|
||||
def metadata_downloaded(self, id, result, exception, formatted_traceback):
|
||||
'''
|
||||
Called once metadata has been read for all books on the device.
|
||||
'''
|
||||
if exception:
|
||||
self.job_exception(id, exception, formatted_traceback)
|
||||
return
|
||||
mainlist, cardlist = result
|
||||
self.memory_view.set_database(mainlist)
|
||||
self.card_view.set_database(cardlist)
|
||||
self.memory_view.sortByColumn(3, Qt.DescendingOrder)
|
||||
self.card_view.sortByColumn(3, Qt.DescendingOrder)
|
||||
self.location_selected('main')
|
||||
for view in (self.memory_view, self.card_view):
|
||||
view.sortByColumn(3, Qt.DescendingOrder)
|
||||
view.resizeColumnsToContents()
|
||||
view.resizeRowsToContents()
|
||||
view.resize_on_select = not view.isVisible()
|
||||
#self.location_selected('main')
|
||||
############################################################################
|
||||
|
||||
|
||||
############################# Upload booklists #############################
|
||||
def upload_booklists(self):
|
||||
'''
|
||||
Upload metadata to device.
|
||||
'''
|
||||
self.job_manager.run_device_job(self.metadata_synced,
|
||||
self.device_manager.sync_booklists_func(),
|
||||
self.booklists())
|
||||
|
||||
def metadata_synced(self, id, result, exception, formatted_traceback):
|
||||
'''
|
||||
Called once metadata has been uploaded.
|
||||
'''
|
||||
if exception:
|
||||
self.job_exception(id, exception, formatted_traceback)
|
||||
return
|
||||
############################################################################
|
||||
|
||||
|
||||
################################# Add books ################################
|
||||
def add_books(self, checked):
|
||||
'''
|
||||
Add books from the local filesystem to either the library or the device.
|
||||
'''
|
||||
books = choose_files(self.window, 'add books dialog dir', 'Select books', 'Books',
|
||||
extensions=['lrf', 'lrx', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'html', 'xhtml', 'epub'])
|
||||
if not books:
|
||||
return
|
||||
on_card = False if self.stack.currentIndex() != 2 else True
|
||||
# Get format and metadata information
|
||||
formats, metadata, names, infos = [], [], [], []
|
||||
for book in books:
|
||||
format = os.path.splitext(book)[1]
|
||||
format = format[1:] if format else None
|
||||
stream = open(book, 'rb')
|
||||
mi = get_metadata(stream, stream_type=format)
|
||||
if not mi.title:
|
||||
mi.title = os.path.splitext(os.path.basename(book))[0]
|
||||
formats.append(format)
|
||||
metadata.append(mi)
|
||||
names.append(os.path.basename(book))
|
||||
infos.append({'title':mi.title, 'authors':mi.author, 'cover':self.default_thumbnail})
|
||||
|
||||
if self.stack.currentIndex() == 0:
|
||||
model = self.current_view().model()
|
||||
model.add_books(books, formats, metadata)
|
||||
model.resort()
|
||||
model.research()
|
||||
else:
|
||||
self.upload_books(books, names, infos, on_card=on_card)
|
||||
|
||||
def upload_books(self, files, names, metadata, on_card=False):
|
||||
'''
|
||||
Upload books to device.
|
||||
@param files: List of either paths to files or file like objects
|
||||
'''
|
||||
id = self.job_manager.run_device_job(self.books_uploaded,
|
||||
self.device_manager.upload_books_func(),
|
||||
files, names, on_card=on_card
|
||||
)
|
||||
self.upload_memory[id] = metadata
|
||||
|
||||
def books_uploaded(self, id, result, exception, formatted_traceback):
|
||||
'''
|
||||
Called once books have been uploaded.
|
||||
'''
|
||||
metadata = self.upload_memory.pop(id)
|
||||
if exception:
|
||||
self.job_exception(id, exception, formatted_traceback)
|
||||
return
|
||||
|
||||
self.device_manager.add_books_to_metadata(result, metadata, self.booklists())
|
||||
|
||||
self.upload_booklists()
|
||||
|
||||
for view in (self.memory_view, self.card_view):
|
||||
view.model().resort()
|
||||
view.model().research()
|
||||
|
||||
|
||||
############################################################################
|
||||
|
||||
############################### Delete books ###############################
|
||||
def delete_books(self, checked):
|
||||
'''
|
||||
Delete selected books from device or library.
|
||||
'''
|
||||
view = self.current_view()
|
||||
rows = view.selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
return
|
||||
if self.stack.currentIndex() == 0:
|
||||
view.model().delete_books(rows)
|
||||
else:
|
||||
view = self.memory_view if self.stack.currentIndex() == 1 else self.card_view
|
||||
paths = view.model().paths(rows)
|
||||
id = self.remove_paths(paths)
|
||||
self.delete_memory[id] = paths
|
||||
view.model().mark_for_deletion(id, rows)
|
||||
|
||||
|
||||
def remove_paths(self, paths):
|
||||
return self.job_manager.run_device_job(self.books_deleted,
|
||||
self.device_manager.delete_books_func(), paths)
|
||||
|
||||
|
||||
def books_deleted(self, id, result, exception, formatted_traceback):
|
||||
'''
|
||||
Called once deletion is done on the device
|
||||
'''
|
||||
for view in (self.memory_view, self.card_view):
|
||||
view.model().deletion_done(id, bool(exception))
|
||||
if exception:
|
||||
self.job_exception(id, exception, formatted_traceback)
|
||||
return
|
||||
|
||||
self.upload_booklists()
|
||||
|
||||
if self.delete_memory.has_key(id):
|
||||
paths = self.delete_memory.pop(id)
|
||||
self.device_manager.remove_books_from_metadata(paths, self.booklists())
|
||||
|
||||
for view in (self.memory_view, self.card_view):
|
||||
view.model().remap()
|
||||
|
||||
############################################################################
|
||||
|
||||
############################### Edit metadata ##############################
|
||||
def edit_metadata(self, checked):
|
||||
'''
|
||||
Edit metadata of selected books in library or on device.
|
||||
'''
|
||||
pass
|
||||
############################################################################
|
||||
|
||||
############################# Syncing to device#############################
|
||||
def sync_to_main_memory(self, checked):
|
||||
self.sync_to_device(False)
|
||||
|
||||
def sync_to_card(self, checked):
|
||||
self.sync_to_device(True)
|
||||
|
||||
def cover_to_thumbnail(self, data):
|
||||
p = QPixmap()
|
||||
p.loadFromData(data)
|
||||
if not p.isNull():
|
||||
ht = self.device_manager.device_class.THUMBNAIL_HEIGHT if self.device_manager else \
|
||||
Device.THUMBNAIL_HEIGHT
|
||||
p = p.scaledToHeight(ht)
|
||||
ba = QByteArray()
|
||||
buf = QBuffer(ba)
|
||||
buf.open(QBuffer.WriteOnly)
|
||||
p.save(buf, 'JPEG')
|
||||
return (p.width(), p.height(), ba.data())
|
||||
|
||||
def sync_to_device(self, on_card):
|
||||
rows = self.library_view.selectionModel().selectedRows()
|
||||
if not self.device_manager or not rows or len(rows) == 0:
|
||||
return
|
||||
ids = iter(self.library_view.model().id(r) for r in rows)
|
||||
metadata = self.library_view.model().get_metadata(rows)
|
||||
for mi in metadata:
|
||||
cdata = mi['cover']
|
||||
if cdata:
|
||||
mi['cover'] = self.cover_to_thumbnail(cdata)
|
||||
metadata = iter(metadata)
|
||||
files = self.library_view.model().get_preferred_formats(rows,
|
||||
self.device_manager.device_class.FORMATS)
|
||||
bad, good, gf, names = [], [], [], []
|
||||
for f in files:
|
||||
mi = metadata.next()
|
||||
id = ids.next()
|
||||
if f is None:
|
||||
bad.append(mi)
|
||||
else:
|
||||
good.append(mi)
|
||||
gf.append(f)
|
||||
names.append('%s_%d%s'%(__appname__, id, os.path.splitext(f.name)[1]))
|
||||
self.upload_books(gf, names, good, on_card)
|
||||
|
||||
raise Exception, str(bad)
|
||||
|
||||
|
||||
|
||||
############################################################################
|
||||
|
||||
def location_selected(self, location):
|
||||
'''
|
||||
Called when a location icon is clicked (e.g. Library)
|
||||
'''
|
||||
page = 0 if location == 'library' else 1 if location == 'main' else 2
|
||||
self.stack.setCurrentIndex(page)
|
||||
view = self.memory_view if page == 1 else self.card_view if page == 2 else None
|
||||
if view:
|
||||
if view.resize_on_select:
|
||||
view.resizeRowsToContents()
|
||||
view.resizeColumnsToContents()
|
||||
view.resize_on_select = False
|
||||
|
||||
def job_exception(self, id, exception, formatted_traceback):
|
||||
'''
|
||||
Handle exceptions in threaded jobs.
|
||||
'''
|
||||
raise JobException, str(exception) + '\n\r' + formatted_traceback
|
||||
|
||||
|
||||
def read_settings(self):
|
||||
settings = QSettings()
|
||||
@ -149,9 +403,14 @@ def main():
|
||||
sys.exit(1)
|
||||
from PyQt4.Qt import QApplication, QMainWindow
|
||||
app = QApplication(sys.argv)
|
||||
#from IPython.Shell import IPShellEmbed
|
||||
#ipshell = IPShellEmbed([],
|
||||
# banner = 'Dropping into IPython',
|
||||
# exit_msg = 'Leaving Interpreter, back to program.')
|
||||
#ipshell()
|
||||
#return 0
|
||||
window = QMainWindow()
|
||||
window.setWindowTitle(APP_TITLE)
|
||||
#window.setWindowIcon(QIcon(":/icon"))
|
||||
installErrorHandler(QErrorMessage(window))
|
||||
QCoreApplication.setOrganizationName("KovidsBrain")
|
||||
QCoreApplication.setApplicationName(APP_TITLE)
|
||||
@ -167,4 +426,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(main())
|
@ -163,7 +163,7 @@
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/clear.png</iconset>
|
||||
<iconset resource="images.qrc" >:/images/clear_left.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -328,10 +328,10 @@
|
||||
</widget>
|
||||
<action name="action_add" >
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/addfile.png</iconset>
|
||||
<iconset resource="images.qrc" >:/images/plus.svg</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Add books to Library</string>
|
||||
<string>Add books</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>A</string>
|
||||
@ -342,7 +342,7 @@
|
||||
</action>
|
||||
<action name="action_del" >
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/delfile.png</iconset>
|
||||
<iconset resource="images.qrc" >:/images/trash.svg</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Delete books</string>
|
||||
@ -353,10 +353,10 @@
|
||||
</action>
|
||||
<action name="action_edit" >
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/edit.png</iconset>
|
||||
<iconset resource="images.qrc" >:/images/edit_input.svg</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Edit meta-information</string>
|
||||
<string>Edit meta information</string>
|
||||
</property>
|
||||
<property name="shortcut" >
|
||||
<string>E</string>
|
||||
@ -365,6 +365,14 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_sync" >
|
||||
<property name="icon" >
|
||||
<iconset resource="images.qrc" >:/images/sync.svg</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Send to device</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'main.ui'
|
||||
#
|
||||
# Created: Wed Jun 27 16:19:53 2007
|
||||
# Created: Thu Jul 19 21:07:39 2007
|
||||
# by: PyQt4 UI code generator 4-snapshot-20070606
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
@ -81,7 +81,7 @@ class Ui_MainWindow(object):
|
||||
self.hboxlayout1.addWidget(self.search)
|
||||
|
||||
self.clear_button = QtGui.QToolButton(self.centralwidget)
|
||||
self.clear_button.setIcon(QtGui.QIcon(":/images/clear.png"))
|
||||
self.clear_button.setIcon(QtGui.QIcon(":/images/clear_left.svg"))
|
||||
self.clear_button.setObjectName("clear_button")
|
||||
self.hboxlayout1.addWidget(self.clear_button)
|
||||
self.gridlayout.addLayout(self.hboxlayout1,1,0,1,1)
|
||||
@ -184,18 +184,22 @@ class Ui_MainWindow(object):
|
||||
MainWindow.setStatusBar(self.statusBar)
|
||||
|
||||
self.action_add = QtGui.QAction(MainWindow)
|
||||
self.action_add.setIcon(QtGui.QIcon(":/images/addfile.png"))
|
||||
self.action_add.setIcon(QtGui.QIcon(":/images/plus.svg"))
|
||||
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.setIcon(QtGui.QIcon(":/images/trash.svg"))
|
||||
self.action_del.setObjectName("action_del")
|
||||
|
||||
self.action_edit = QtGui.QAction(MainWindow)
|
||||
self.action_edit.setIcon(QtGui.QIcon(":/images/edit.png"))
|
||||
self.action_edit.setIcon(QtGui.QIcon(":/images/edit_input.svg"))
|
||||
self.action_edit.setAutoRepeat(False)
|
||||
self.action_edit.setObjectName("action_edit")
|
||||
|
||||
self.action_sync = QtGui.QAction(MainWindow)
|
||||
self.action_sync.setIcon(QtGui.QIcon(":/images/sync.svg"))
|
||||
self.action_sync.setObjectName("action_sync")
|
||||
self.tool_bar.addAction(self.action_add)
|
||||
self.tool_bar.addAction(self.action_del)
|
||||
self.tool_bar.addAction(self.action_edit)
|
||||
@ -214,12 +218,13 @@ class Ui_MainWindow(object):
|
||||
self.search.setWhatsThis(QtGui.QApplication.translate("MainWindow", "Search the list of books by title, author, publisher, tags and comments<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.action_add.setText(QtGui.QApplication.translate("MainWindow", "Add books to Library", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.action_add.setText(QtGui.QApplication.translate("MainWindow", "Add books", 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.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))
|
||||
self.action_sync.setText(QtGui.QApplication.translate("MainWindow", "Send to device", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
from widgets import LocationView
|
||||
from library import BooksView, DeviceBooksView, SearchBox
|
||||
|
@ -12,23 +12,24 @@
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
import textwrap
|
||||
|
||||
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap, \
|
||||
QSizePolicy
|
||||
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap
|
||||
from PyQt4.QtCore import Qt, QSize
|
||||
|
||||
class BookInfoDisplay(QFrame):
|
||||
class BookCoverDisplay(QLabel):
|
||||
WIDTH = 60
|
||||
HEIGHT = 80
|
||||
def __init__(self, coverpath=':default_cover'):
|
||||
WIDTH = 80
|
||||
HEIGHT = 100
|
||||
def __init__(self, coverpath=':/images/book.svg'):
|
||||
QLabel.__init__(self)
|
||||
self.default_pixmap = QPixmap(coverpath).scaled(self.__class__.WIDTH,
|
||||
self.__class__.HEIGHT,
|
||||
Qt.IgnoreAspectRatio,
|
||||
Qt.SmoothTransformation)
|
||||
self.setPixmap(self.default_pixmap)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
||||
self.setMaximumSize(QSize(self.WIDTH, self.HEIGHT))
|
||||
self.setScaledContents(True)
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(self.__class__.WIDTH, self.__class__.HEIGHT)
|
||||
@ -38,7 +39,7 @@ class BookInfoDisplay(QFrame):
|
||||
def __init__(self):
|
||||
QLabel.__init__(self)
|
||||
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||
self.setText('TODO')
|
||||
self.setText('')#<table><tr><td>row 1</td><td>row 2</td></tr><tr><td>fsfdsfsfsfsfsfsdfsffsfsd</td></tr></table>')
|
||||
|
||||
def __init__(self):
|
||||
QFrame.__init__(self)
|
||||
@ -48,7 +49,28 @@ class BookInfoDisplay(QFrame):
|
||||
self.layout.addWidget(self.cover_display)
|
||||
self.book_data = BookInfoDisplay.BookDataDisplay()
|
||||
self.layout.addWidget(self.book_data)
|
||||
self.setVisible(False)
|
||||
|
||||
def show_data(self, data):
|
||||
if data.has_key('cover'):
|
||||
cover_data = data.pop('cover')
|
||||
pixmap = QPixmap()
|
||||
pixmap.loadFromData(cover_data)
|
||||
if pixmap.isNull():
|
||||
self.cover_display.setPixmap(self.cover_display.default_pixmap)
|
||||
else:
|
||||
self.cover_display.setPixmap(pixmap)
|
||||
else:
|
||||
self.cover_display.setPixmap(self.cover_display.default_pixmap)
|
||||
|
||||
rows = u''
|
||||
self.book_data.setText('')
|
||||
for key in data.keys():
|
||||
txt = '<br />\n'.join(textwrap.wrap(data[key], 120))
|
||||
rows += '<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
|
||||
self.book_data.setText('<table>'+rows+'</table>')
|
||||
|
||||
self.setVisible(True)
|
||||
|
||||
class MovieButton(QLabel):
|
||||
def __init__(self, movie):
|
||||
|
@ -24,8 +24,8 @@ class LocationModel(QAbstractListModel):
|
||||
def __init__(self, parent):
|
||||
QAbstractListModel.__init__(self, parent)
|
||||
self.icons = [QVariant(QIcon(':/library')),
|
||||
QVariant(QIcon(':/reader')),
|
||||
QVariant(QIcon(':/card'))]
|
||||
QVariant(QIcon(':/images/reader.svg')),
|
||||
QVariant(QIcon(':/images/sd.svg'))]
|
||||
self.text = ['Library',
|
||||
'Reader\n%s available',
|
||||
'Card\n%s available']
|
||||
|
@ -16,9 +16,8 @@
|
||||
Backend that implements storage of ebooks in an sqlite database.
|
||||
"""
|
||||
import sqlite3 as sqlite
|
||||
import os, datetime, re
|
||||
import datetime, re
|
||||
from zlib import compressobj, decompress
|
||||
from stat import ST_SIZE
|
||||
|
||||
class Concatenate(object):
|
||||
'''String concatenation aggregator for sqlite'''
|
||||
@ -506,7 +505,7 @@ class LibraryDatabase(object):
|
||||
text TEXT NON NULL COLLATE NOCASE,
|
||||
UNIQUE(book)
|
||||
);
|
||||
CREATE INDEX comments_idx ON covers (book);
|
||||
CREATE INDEX comments_idx ON comments (book);
|
||||
CREATE TRIGGER fkc_comments_insert
|
||||
BEFORE INSERT ON comments
|
||||
BEGIN
|
||||
@ -572,6 +571,9 @@ class LibraryDatabase(object):
|
||||
return not self.conn.execute('SELECT id FROM books LIMIT 1').fetchone()
|
||||
|
||||
def refresh(self, sort_field, ascending):
|
||||
'''
|
||||
Rebuild self.data and self.cache. Filter results are lost.
|
||||
'''
|
||||
FIELDS = {'title' : 'sort',
|
||||
'authors': 'authors_sort',
|
||||
'publisher': 'publisher_sort',
|
||||
@ -638,6 +640,43 @@ class LibraryDatabase(object):
|
||||
def max_size(self, index):
|
||||
return self.data[index][6]
|
||||
|
||||
def cover(self, index):
|
||||
id = self.id(index)
|
||||
matches = self.conn.execute('SELECT data from covers where id=?', (id,)).fetchall()
|
||||
if not matches:
|
||||
return None
|
||||
raw = matches[0][0]
|
||||
if raw:
|
||||
return decompress(raw)
|
||||
return None
|
||||
|
||||
def tags(self, index):
|
||||
id = self.id(index)
|
||||
matches = self.conn.execute('SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=?)', (id,)).fetchall()
|
||||
if not matches:
|
||||
return None
|
||||
return matches[0][0]
|
||||
|
||||
def comments(self, index):
|
||||
id = self.id(index)
|
||||
matches = self.conn.execute('SELECT text FROM comments WHERE book=?', (id,)).fetchall()
|
||||
if not matches:
|
||||
return None
|
||||
return matches[0][0]
|
||||
|
||||
def formats(self, index):
|
||||
''' Return available formats as a comma separated list '''
|
||||
id = self.id(index)
|
||||
matches = self.conn.execute('SELECT concat(format) FROM data WHERE data.book=?', (id,)).fetchall()
|
||||
if not matches:
|
||||
return None
|
||||
return matches[0][0]
|
||||
|
||||
def format(self, index, format):
|
||||
id = self.id(index)
|
||||
return decompress(self.conn.execute('SELECT data FROM data WHERE book=? AND format=?', (id, format)).fetchone()[0])
|
||||
|
||||
|
||||
def set(self, row, column, val):
|
||||
''' Convenience method for setting the title, authors, publisher or rating '''
|
||||
id = self.data[row][0]
|
||||
@ -709,28 +748,55 @@ class LibraryDatabase(object):
|
||||
self.conn.execute('INSERT INTO books_ratings_link(book, rating) VALUES (?,?)', (id, rat))
|
||||
self.conn.commit()
|
||||
|
||||
def add_book(self, stream, format, mi, uri=None):
|
||||
if not mi.author:
|
||||
mi.author = 'Unknown'
|
||||
obj = self.conn.execute('INSERT INTO books(title, uri, series_index) VALUES (?, ?, ?)',
|
||||
(mi.title, uri, mi.series_index))
|
||||
id = obj.lastrowid
|
||||
self.conn.commit()
|
||||
temp = mi.author.split(',')
|
||||
authors = []
|
||||
for a in temp:
|
||||
authors += a.split('&')
|
||||
self.set_authors(id, authors)
|
||||
if mi.publisher:
|
||||
self.set_publisher(id, mi.publisher)
|
||||
if mi.rating:
|
||||
self.set_rating(id, mi.rating)
|
||||
if mi.series:
|
||||
self.set_series(id, mi.series)
|
||||
stream.seek(0, 2)
|
||||
usize = stream.tell()
|
||||
stream.seek(0)
|
||||
self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)',
|
||||
(id, format, usize, compressobj().compress(stream)))
|
||||
def add_books(self, paths, formats, metadata, uris=[]):
|
||||
'''
|
||||
Add a book to the database. self.data and self.cache are not updated.
|
||||
'''
|
||||
formats, metadata, uris = iter(formats), iter(metadata), iter(uris)
|
||||
for path in paths:
|
||||
mi = metadata.next()
|
||||
try:
|
||||
uri = uris.next()
|
||||
except StopIteration:
|
||||
uri = None
|
||||
obj = self.conn.execute('INSERT INTO books(title, uri, series_index) VALUES (?, ?, ?)',
|
||||
(mi.title, uri, mi.series_index))
|
||||
id = obj.lastrowid
|
||||
self.conn.commit()
|
||||
if not mi.author:
|
||||
mi.author = 'Unknown'
|
||||
temp = mi.author.split(',')
|
||||
authors = []
|
||||
for a in temp:
|
||||
authors += a.split('&')
|
||||
self.set_authors(id, authors)
|
||||
if mi.publisher:
|
||||
self.set_publisher(id, mi.publisher)
|
||||
if mi.rating:
|
||||
self.set_rating(id, mi.rating)
|
||||
if mi.series:
|
||||
self.set_series(id, mi.series)
|
||||
stream = open(path, 'rb')
|
||||
stream.seek(0, 2)
|
||||
usize = stream.tell()
|
||||
stream.seek(0)
|
||||
format = formats.next()
|
||||
self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)',
|
||||
(id, format, usize, compressobj().compress(stream.read())))
|
||||
stream.close()
|
||||
self.conn.commit()
|
||||
|
||||
|
||||
def delete_books(self, indices):
|
||||
'''
|
||||
Removes books from self.cache, self.data and underlying database.
|
||||
'''
|
||||
ids = [ self.id(i) for i in indices ]
|
||||
cache_indices = [ idx for idx in range(len(self.cache)-1, -1, -1) if self.cache[idx][0] in ids ]
|
||||
for idx in cache_indices:
|
||||
self.cache[idx:idx+1] = []
|
||||
for idx in indices:
|
||||
self.data[idx:idx+1] = []
|
||||
for id in ids:
|
||||
self.conn.execute('DELETE FROM books WHERE id=?', (id,))
|
||||
self.conn.commit()
|