diff --git a/src/libprs500/devices/interface.py b/src/libprs500/devices/interface.py
index da825c9ed0..63294830fc 100644
--- a/src/libprs500/devices/interface.py
+++ b/src/libprs500/devices/interface.py
@@ -102,7 +102,7 @@ class Device(object):
otherwise return list of ebooks in main memory of device.
If True and no books on card return empty list.
@return: A list of Books. Each Book object must have the fields:
- title, author, size, datetime (a time tuple), path, thumbnail (can be None).
+ title, authors, size, datetime (a UTC time tuple), path, thumbnail (can be None).
"""
raise NotImplementedError()
@@ -131,5 +131,13 @@ class Device(object):
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=False), L{books}(oncard=True)).
"""
- raise NotImplementedError()
+ raise NotImplementedError()
+
+ def sync_booklists(self, booklists, end_session=True):
+ '''
+ Update metadata on device.
+ @param booklists: A tuple containing the result of calls to
+ (L{books}(oncard=False), L{books}(oncard=True)).
+ '''
+ raise NotImplementedError()
diff --git a/src/libprs500/devices/prs500/books.py b/src/libprs500/devices/prs500/books.py
index 524298cf49..7cdee8a678 100644
--- a/src/libprs500/devices/prs500/books.py
+++ b/src/libprs500/devices/prs500/books.py
@@ -19,7 +19,7 @@ in the reader cache.
import xml.dom.minidom as dom
from base64 import b64decode as decode
from base64 import b64encode as encode
-import time
+import time, re
MIME_MAP = { \
"lrf":"application/x-sony-bbeb", \
@@ -28,6 +28,9 @@ MIME_MAP = { \
"txt":"text/plain" \
}
+def sortable_title(title):
+ return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', ' An Crum').rstrip()
+
class book_metadata_field(object):
""" Represents metadata stored as an attribute """
def __init__(self, attr, formatter=None, setter=None):
@@ -48,7 +51,7 @@ class book_metadata_field(object):
class Book(object):
""" Provides a view onto the XML element that represents a book """
title = book_metadata_field("title")
- author = book_metadata_field("author", \
+ authors = book_metadata_field("author", \
formatter=lambda x: x if x and x.strip() else "Unknown")
mime = book_metadata_field("mime")
rpath = book_metadata_field("path")
@@ -60,6 +63,18 @@ class Book(object):
formatter=lambda x: time.strptime(x.strip(), "%a, %d %b %Y %H:%M:%S %Z"),
setter=lambda x: time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(x)))
+ @apply
+ def title_sorter():
+ doc = '''String to sort the title. If absent, title is returned'''
+ def fget(self):
+ src = self.elem.getAttribute('titleSorter').strip()
+ if not src:
+ src = self.title
+ return src
+ def fset(self, val):
+ self.elem.setAttribute('titleSorter', sortable_title(str(val)))
+ return property(doc=doc, fget=fget, fset=fset)
+
@apply
def thumbnail():
doc = \
@@ -186,6 +201,7 @@ class BookList(list):
sourceid = str(self[0].sourceid) if len(self) else "1"
attrs = {
"title" : info["title"],
+ 'titleSorter' : info['title'],
"author" : info["authors"] if info['authors'] else 'Unknown', \
"page":"0", "part":"0", "scale":"0", \
"sourceid":sourceid, "id":str(cid), "date":"", \
diff --git a/src/libprs500/devices/prs500/driver.py b/src/libprs500/devices/prs500/driver.py
index 3d9ae5b900..abe42a3684 100755
--- a/src/libprs500/devices/prs500/driver.py
+++ b/src/libprs500/devices/prs500/driver.py
@@ -813,10 +813,19 @@ class PRS500(Device):
for path in paths:
self.del_file(path, end_session=False)
fix_ids(booklists[0], booklists[1])
+ self.sync_booklists(booklists, end_session=False)
+
+ @safe
+ def sync_booklists(self, booklists, end_session=True):
+ '''
+ Upload bookslists to device.
+ @param booklists: A tuple containing the result of calls to
+ (L{books}(oncard=False), L{books}(oncard=True)).
+ '''
self.upload_book_list(booklists[0], end_session=False)
if len(booklists[1]):
self.upload_book_list(booklists[1], end_session=False)
-
+
@safe
def add_book(self, infile, name, info, booklists, oncard=False, \
sync_booklists=False, end_session=True):
@@ -858,9 +867,7 @@ class PRS500(Device):
bkl.add_book(info, name, size, ctime)
fix_ids(booklists[0], booklists[1])
if sync_booklists:
- self.upload_book_list(booklists[0], end_session=False)
- if len(booklists[1]):
- self.upload_book_list(booklists[1], end_session=False)
+ self.sync_booklists(booklists, end_session=False)
@safe
def upload_book_list(self, booklist, end_session=True):
diff --git a/src/libprs500/ebooks/lrf/__init__.py b/src/libprs500/ebooks/lrf/__init__.py
index 96729b81aa..a1a1f51834 100644
--- a/src/libprs500/ebooks/lrf/__init__.py
+++ b/src/libprs500/ebooks/lrf/__init__.py
@@ -16,13 +16,17 @@
This package contains logic to read and write LRF files.
The LRF file format is documented at U{http://www.sven.de/librie/Librie/LrfFormat}.
"""
-
+import sys, os
from optparse import OptionParser, OptionValueError
+from ttfquery import describe, findsystem
+from fontTools.ttLib import TTLibError
from libprs500.ebooks.lrf.pylrs.pylrs import Book as _Book
from libprs500.ebooks.lrf.pylrs.pylrs import TextBlock, Header, PutObj, \
Paragraph, TextStyle, BlockStyle
+from libprs500.ebooks.lrf.fonts import FONT_FILE_MAP
from libprs500 import __version__ as VERSION
+from libprs500 import iswindows
__docformat__ = "epytext"
__author__ = "Kovid Goyal "
@@ -33,11 +37,13 @@ class PRS500_PROFILE(object):
dpi = 166
# Number of pixels to subtract from screen_height when calculating height of text area
fudge = 18
- font_size = 10 #: Default (in pt)
- parindent = 80 #: Default (in px)
- line_space = 1.2 #: Default (in pt)
+ font_size = 10 #: Default (in pt)
+ parindent = 80 #: Default (in px)
+ line_space = 1.2 #: Default (in pt)
header_font_size = 6 #: In pt
header_height = 30 #: In px
+ default_fonts = { 'sans': "Swis721 BT Roman", 'mono': "Courier10 BT Roman",
+ 'serif': "Dutch801 Rm BT Roman"}
@@ -47,6 +53,20 @@ def profile_from_string(option, opt_str, value, parser):
else:
raise OptionValueError('Profile: '+value+' is not implemented')
+def font_family(option, opt_str, value, parser):
+ if value:
+ value = value.split(',')
+ if len(value) != 2:
+ raise OptionValueError('Font family specification must be of the form'+\
+ ' "path to font directory, font family"')
+ path, family = tuple(value)
+ if not os.path.isdir(path) or not os.access(path, os.R_OK|os.X_OK):
+ raise OptionValueError('Cannot read from ' + path)
+ setattr(parser.values, option.dest, (path, family))
+ else:
+ setattr(parser.values, option.dest, tuple())
+
+
class ConversionError(Exception):
pass
@@ -98,6 +118,24 @@ def option_parser(usage):
page.add_option('--bottom-margin', default=0, dest='bottom_margin', type='int',
help='''Bottom margin of page. Default is %default px.''')
+ fonts = parser.add_option_group('FONT FAMILIES',
+ '''Specify trutype font families for serif, sans-serif and monospace fonts. '''
+ '''These fonts will be embedded in the LRF file. Note that custom fonts lead to '''
+ '''slower page turns. Each family specification is of the form: '''
+ '''"path to fonts directory, family" '''
+ '''For example: '''
+ '''--serif-family "%s, Times New Roman"
+ ''' % ('C:\Windows\Fonts' if iswindows else '/usr/share/fonts/corefonts'))
+ fonts.add_option('--serif-family', action='callback', callback=font_family,
+ default=None, dest='serif_family', type='string',
+ help='The serif family of fonts to embed')
+ fonts.add_option('--sans-family', action='callback', callback=font_family,
+ default=None, dest='sans_family', type='string',
+ help='The sans-serif family of fonts to embed')
+ fonts.add_option('--mono-family', action='callback', callback=font_family,
+ default=None, dest='mono_family', type='string',
+ help='The monospace family of fonts to embed')
+
debug = parser.add_option_group('DEBUG OPTIONS')
debug.add_option('--verbose', dest='verbose', action='store_true', default=False,
help='''Be verbose while processing''')
@@ -105,6 +143,42 @@ def option_parser(usage):
help='Convert to LRS', default=False)
return parser
+def find_custom_fonts(options):
+ fonts = {'serif' : None, 'sans' : None, 'mono' : None}
+ def find_family(option):
+ path, family = option
+ paths = findsystem.findFonts([path])
+ results = {}
+ for path in paths:
+ if len(results.keys()) == 4:
+ break
+ f = describe.openFont(path)
+ name, cfamily = describe.shortName(f)
+ if cfamily.lower().strip() != family.lower().strip():
+ continue
+ try:
+ wt, italic = describe.modifiers(f)
+ except TTLibError:
+ print >>sys.stderr, 'Could not process', path
+ result = (path, name)
+ if wt == 400 and italic == 0:
+ results['normal'] = result
+ elif wt == 400 and italic > 0:
+ results['italic'] = result
+ elif wt >= 700 and italic == 0:
+ results['bold'] = result
+ elif wt >= 700 and italic > 0:
+ results['bi'] = result
+ return results
+ if options.serif_family:
+ fonts['serif'] = find_family(options.serif_family)
+ if options.sans_family:
+ fonts['sans'] = find_family(options.sans_family)
+ if options.mono_family:
+ fonts['mono'] = find_family(options.mono_family)
+ return fonts
+
+
def Book(options, font_delta=0, header=None,
profile=PRS500_PROFILE, **settings):
ps = {}
@@ -126,9 +200,27 @@ def Book(options, font_delta=0, header=None,
ps['textheight'] = profile.screen_height - (options.bottom_margin + ps['topmargin'] + ps['headheight'] + profile.fudge)
fontsize = int(10*profile.font_size+font_delta*20)
baselineskip = fontsize + 20
- return _Book(textstyledefault=dict(fontsize=fontsize,
- parindent=int(profile.parindent),
- linespace=int(10*profile.line_space),
- baselineskip=baselineskip), \
- pagestyledefault=ps, blockstyledefault=dict(blockwidth=ps['textwidth']),
- **settings)
\ No newline at end of file
+ fonts = find_custom_fonts(options)
+ tsd = dict(fontsize=fontsize,
+ parindent=int(profile.parindent),
+ linespace=int(10*profile.line_space),
+ baselineskip=baselineskip)
+ if fonts['serif'] and fonts['serif'].has_key('normal'):
+ tsd['fontfacename'] = fonts['serif']['normal'][1]
+
+ book = _Book(textstyledefault=tsd,
+ pagestyledefault=ps,
+ blockstyledefault=dict(blockwidth=ps['textwidth']),
+ **settings)
+ for family in fonts.keys():
+ if fonts[family]:
+ for font in fonts[family].values():
+ book.embed_font(*font)
+ FONT_FILE_MAP[font[1]] = font[0]
+
+ for family in ['serif', 'sans', 'mono']:
+ if not fonts[family]:
+ fonts[family] = { 'normal' : (None, profile.default_fonts[family]) }
+ elif not fonts[family].has_key('normal'):
+ raise ConversionError, 'Could not find the normal version of the ' + family + ' font'
+ return book, fonts
\ No newline at end of file
diff --git a/src/libprs500/ebooks/lrf/fonts/__init__.py b/src/libprs500/ebooks/lrf/fonts/__init__.py
index 6487758aee..2587e2808d 100644
--- a/src/libprs500/ebooks/lrf/fonts/__init__.py
+++ b/src/libprs500/ebooks/lrf/fonts/__init__.py
@@ -56,4 +56,5 @@ def get_font(name, size, encoding='unic'):
if name in FONT_MAP.keys():
path = get_font_path(name)
return ImageFont.truetype(path, size, encoding=encoding)
-
\ No newline at end of file
+ elif name in FONT_FILE_MAP.keys():
+ return ImageFont.truetype(FONT_FILE_MAP[name], size, encoding=encoding)
\ No newline at end of file
diff --git a/src/libprs500/ebooks/lrf/html/convert_from.py b/src/libprs500/ebooks/lrf/html/convert_from.py
index adad211332..143bf760b0 100644
--- a/src/libprs500/ebooks/lrf/html/convert_from.py
+++ b/src/libprs500/ebooks/lrf/html/convert_from.py
@@ -81,29 +81,46 @@ class Span(_Span):
return result
@staticmethod
- def translate_attrs(d, dpi, font_delta=0, memory=None):
+ def translate_attrs(d, dpi, fonts, font_delta=0, memory=None):
"""
Receives a dictionary of html attributes and styles and returns
approximate Xylog equivalents in a new dictionary
"""
def font_weight(val):
- ans = None
+ ans = 0
m = re.search("([0-9]+)", val)
if m:
- ans = str(int(m.group(1)))
+ ans = int(m.group(1))
elif val.find("bold") >= 0 or val.find("strong") >= 0:
- ans = "1000"
+ ans = 700
+ return 'bold' if ans >= 700 else 'normal'
+
+ def font_style(val):
+ ans = 'normal'
+ if 'italic' in val or 'oblique' in val:
+ ans = 'italic'
return ans
def font_family(val):
- ans = None
+ ans = 'serif'
if max(val.find("courier"), val.find("mono"), val.find("fixed"), val.find("typewriter"))>=0:
- ans = "Courier10 BT Roman"
+ ans = 'mono'
elif max(val.find("arial"), val.find("helvetica"), val.find("verdana"),
val.find("trebuchet"), val.find("sans")) >= 0:
- ans = "Swis721 BT Roman"
+ ans = 'sans'
return ans
+ def font_key(family, style, weight):
+ key = 'normal'
+ if style == 'italic' and weight == 'normal':
+ key = 'italic'
+ elif style == 'normal' and weight == 'bold':
+ key = 'bold'
+ elif style == 'italic' and weight == 'bold':
+ key = 'bi'
+ return key
+
+
def font_size(val):
ans = None
unit = Span.unit_convert(val, dpi, 14)
@@ -129,37 +146,38 @@ class Span(_Span):
return ans
t = dict()
+ family, weight, style = 'serif', 'normal', 'normal'
for key in d.keys():
val = d[key].lower()
if key == 'font':
- val = val.split()
- val.reverse()
- for sval in val:
- ans = font_family(sval)
- if ans:
- t['fontfacename'] = ans
- else:
- ans = font_size(sval)
- if ans:
- t['fontsize'] = ans
- else:
- ans = font_weight(sval)
- if ans:
- t['fontweight'] = ans
+ vals = val.split()
+ for val in vals:
+ family = font_family(val)
+ if family != 'serif':
+ break
+ for val in vals:
+ weight = font_weight(val)
+ if weight != 'normal':
+ break
+ for val in vals:
+ style = font_style(val)
+ if style != 'normal':
+ break
+ for val in vals:
+ sz = font_size(val)
+ if sz:
+ t['fontsize'] = sz
+ break
elif key in ['font-family', 'font-name']:
- ans = font_family(val)
- if ans:
- t['fontfacename'] = ans
+ family = font_family(val)
elif key == "font-size":
ans = font_size(val)
if ans:
t['fontsize'] = ans
elif key == 'font-weight':
- ans = font_weight(val)
- if ans:
- t['fontweight'] = ans
- if int(ans) > 140:
- t['wordspace'] = '50'
+ weight = font_weight(val)
+ elif key == 'font-style':
+ style = font_style(val)
else:
report = True
if memory != None:
@@ -169,22 +187,32 @@ class Span(_Span):
memory.append(key)
if report:
print >>sys.stderr, 'Unhandled/malformed CSS key:', key, d[key]
- return t
+ t['fontfacename'] = (family, font_key(family, style, weight))
+ if t.has_key('fontsize') and int(t['fontsize']) > 120:
+ t['wordspace'] = 50
+ return t
- def __init__(self, ns, css, memory, dpi, font_delta=0):
+ def __init__(self, ns, css, memory, dpi, fonts, font_delta=0):
src = ns.string if hasattr(ns, 'string') else ns
src = re.sub(r'\s{2,}', ' ', src) # Remove multiple spaces
for pat, repl in Span.rules:
src = pat.sub(repl, src)
if not src:
raise ConversionError('No point in adding an empty string to a Span')
- if 'font-style' in css.keys():
- fs = css.pop('font-style')
- if fs.lower() == 'italic':
+ attrs = Span.translate_attrs(css, dpi, fonts, font_delta=font_delta, memory=memory)
+ family, key = attrs['fontfacename']
+ if fonts[family].has_key(key):
+ attrs['fontfacename'] = fonts[family][key][1]
+ else:
+ attrs['fontfacename'] = fonts[family]['normal'][1]
+ if key in ['bold', 'bi']:
+ attrs['fontweight'] = 700
+ if key in ['italic', 'bi']:
src = Italic(src)
- attrs = Span.translate_attrs(css, dpi, font_delta=font_delta, memory=memory)
if 'fontsize' in attrs.keys():
attrs['baselineskip'] = int(attrs['fontsize']) + 20
+ if attrs['fontfacename'] == fonts['serif']['normal'][1]:
+ attrs.pop('fontfacename')
_Span.__init__(self, text=src, **attrs)
@@ -214,7 +242,7 @@ class HTMLConverter(object):
processed_files = {} #: Files that have been processed
- def __init__(self, book, path,
+ def __init__(self, book, fonts, path,
font_delta=0, verbose=False, cover=None,
max_link_levels=sys.maxint, link_level=0,
is_root=True, baen=False, chapter_detection=True,
@@ -231,6 +259,7 @@ class HTMLConverter(object):
@param book: The LRF book
@type book: L{libprs500.lrf.pylrs.Book}
+ @param fonts: dict specifying the font families to use
@param path: path to the HTML file to process
@type path: C{str}
@param width: Width of the device on which the LRF file is to be read
@@ -278,6 +307,7 @@ class HTMLConverter(object):
th = {'font-size' : 'large', 'font-weight':'bold'},
big = {'font-size' : 'large', 'font-weight':'bold'},
)
+ self.fonts = fonts #: dict specifting font families to use
self.profile = profile #: Defines the geometry of the display device
self.chapter_detection = chapter_detection #: Flag to toggle chapter detection
self.chapter_regex = chapter_regex #: Regex used to search for chapter titles
@@ -553,7 +583,8 @@ class HTMLConverter(object):
path = os.path.abspath(path)
if not path in HTMLConverter.processed_files.keys():
try:
- self.files[path] = HTMLConverter(self.book, path,
+ self.files[path] = HTMLConverter(
+ self.book, self.fonts, path,
profile=self.profile,
font_delta=self.font_delta, verbose=self.verbose,
link_level=self.link_level+1,
@@ -690,7 +721,7 @@ class HTMLConverter(object):
self.process_alignment(css)
try:
self.current_para.append(Span(src, self.sanctify_css(css), self.memory,\
- self.profile.dpi, font_delta=self.font_delta))
+ self.profile.dpi, self.fonts, font_delta=self.font_delta))
except ConversionError, err:
if self.verbose:
print >>sys.stderr, err
@@ -949,7 +980,8 @@ class HTMLConverter(object):
elif tagname == 'pre':
self.end_current_para()
self.current_block.append_to(self.current_page)
- attrs = Span.translate_attrs(tag_css, self.profile.dpi, self.font_delta, self.memory)
+ attrs = Span.translate_attrs(tag_css, self.profile.dpi, self.fonts, self.font_delta, self.memory)
+ attrs['fontfacename'] = self.fonts['mono']['normal'][1]
ts = self.book.create_text_style(**self.unindented_style.attrs)
ts.attrs.update(attrs)
self.current_block = self.book.create_text_block(
@@ -959,7 +991,7 @@ class HTMLConverter(object):
lines = src.split('\n')
for line in lines:
try:
- self.current_para.append(Span(line, tag_css, self.memory, self.profile.dpi))
+ self.current_para.append(Span(line, tag_css, self.memory, self.profile.dpi, self.fonts))
self.current_para.CR()
except ConversionError:
pass
@@ -1145,14 +1177,14 @@ def process_file(path, options):
header.append(Bold(options.title))
header.append(' by ')
header.append(Italic(options.author+" "))
- book = Book(options, header=header, **args)
+ book, fonts = Book(options, header=header, **args)
le = re.compile(options.link_exclude) if options.link_exclude else \
re.compile('$')
pb = re.compile(options.page_break, re.IGNORECASE) if options.page_break else \
re.compile('$')
fpb = re.compile(options.force_page_break, re.IGNORECASE) if options.force_page_break else \
re.compile('$')
- conv = HTMLConverter(book, path, profile=options.profile,
+ conv = HTMLConverter(book, fonts, path, profile=options.profile,
font_delta=options.font_delta,
cover=cpath, max_link_levels=options.link_levels,
verbose=options.verbose, baen=options.baen,
diff --git a/src/libprs500/ebooks/lrf/html/demo/demo.html b/src/libprs500/ebooks/lrf/html/demo/demo.html
index b48c2f3acc..a79aabb94c 100644
--- a/src/libprs500/ebooks/lrf/html/demo/demo.html
+++ b/src/libprs500/ebooks/lrf/html/demo/demo.html
@@ -8,7 +8,7 @@
Demo of html2lrf
- This file contains a demonstration of the capabilities of html2lrf, the HTML to LRF converter from libprs500. To obtain libprs500 visit
https://libprs500.kovidgoyal.net
+ This file contains a demonstration of the capabilities of html2lrf, the HTML to LRF converter from libprs500. To obtain libprs500 visit
https://libprs500.kovidgoyal.net
@@ -17,18 +17,21 @@
Tables
Text formatting and ruled lines
Inline images
+ Embedded Fonts
Recursive link following
The HTML used to create this file
- Unordered lists
+
+ Unordered lists
- Ordered lists
+
+Ordered lists
- Item 1
- Item 2
@@ -36,7 +39,7 @@
- Note that nested lists are not supported.
+ Note that nested lists are not supported.
@@ -94,7 +97,7 @@
A simple paragraph of formatted
- text with a ruled line following it.
+ text, with a ruled line following it.
A
@@ -113,7 +116,7 @@
A very indented paragraph
An unindented paragraph
- A default indented paragrpah
+ A default indented paragrpah
Table of Contents
@@ -128,9 +131,29 @@
Table of Contents
+
+ This LRF file has been prepared by embedding Times New Roman and Andale Mono
+ as the default serif and monospace fonts. This allows it to correctly display
+ non English characters such as:
+
+ - mouse in German: mūs
+ - mouse in Russian: мышь
+
+
+ Note that embedding fonts in LRF files slows down page turns slightly.
+
+
+
+
+
+ Table of Contents
+
+
+
html2lrf follows links in HTML files that point to other files, recursively. Thus it can be used to convert a whole tree of HTML files into a single LRF file.
+
diff --git a/src/libprs500/ebooks/lrf/pylrs/pylrs.py b/src/libprs500/ebooks/lrf/pylrs/pylrs.py
index 06ecda8238..54ed51776c 100644
--- a/src/libprs500/ebooks/lrf/pylrs/pylrs.py
+++ b/src/libprs500/ebooks/lrf/pylrs/pylrs.py
@@ -503,6 +503,10 @@ class Book(Delegator):
if isinstance(candidate, Page):
return candidate
+ def embed_font(self, file, facename):
+ f = Font(file, facename)
+ self.append(f)
+
def getSettings(self):
return ["sourceencoding"]
diff --git a/src/libprs500/gui/widgets.py b/src/libprs500/gui/widgets.py
index a23d47eb9c..7c2f304da3 100644
--- a/src/libprs500/gui/widgets.py
+++ b/src/libprs500/gui/widgets.py
@@ -700,7 +700,7 @@ class DeviceBooksModel(QAbstractTableModel):
if col == 0:
text = TableView.wrap(book.title, width=40)
elif col == 1:
- au = book.author
+ au = book.authors
au = au.split("&")
jau = [ TableView.wrap(a, width=25).strip() for a in au ]
text = "\n".join(jau)
@@ -723,7 +723,7 @@ class DeviceBooksModel(QAbstractTableModel):
cover = None if pix.isNull() else pix
except:
traceback.print_exc()
- au = row.author if row.author else "Unknown"
+ au = row.authors if row.authors else "Unknown"
return row.title, au, TableView.human_readable(row.size), row.mime, cover
def sort(self, col, order):
diff --git a/src/libprs500/gui2/device.py b/src/libprs500/gui2/device.py
index 0b29580984..6df91b58f3 100644
--- a/src/libprs500/gui2/device.py
+++ b/src/libprs500/gui2/device.py
@@ -75,8 +75,7 @@ class DeviceJob(QThread):
self.id, self.result, exception, last_traceback)
def progress_update(self, val):
- print val
- self.emit(SIGNAL('status_update(int)'), int(val), Qt.QueuedConnection)
+ self.emit(SIGNAL('status_update(int)'), int(val))
class DeviceManager(QObject):
@@ -85,7 +84,7 @@ class DeviceManager(QObject):
self.device_class = device_class
self.device = device_class()
- def get_info_func(self):
+ def info_func(self):
''' Return callable that returns device information and free space on device'''
def get_device_information(updater):
self.device.set_progress_reporter(updater)
@@ -102,5 +101,12 @@ class DeviceManager(QObject):
self.device.set_progress_reporter(updater)
mainlist = self.device.books(oncard=False, end_session=False)
cardlist = self.device.books(oncard=True)
- return mainlist, cardlist
- return books
\ No newline at end of file
+ return (mainlist, cardlist)
+ return books
+
+ def sync_booklists_func(self):
+ '''Upload booklists to device'''
+ def sync_booklists(updater, booklists):
+ self.device.set_progress_reporter(updater)
+ self.device.sync_booklists(booklists)
+ return sync_booklists
\ No newline at end of file
diff --git a/src/libprs500/gui2/jobs.py b/src/libprs500/gui2/jobs.py
index 96b05e3433..fb2684907a 100644
--- a/src/libprs500/gui2/jobs.py
+++ b/src/libprs500/gui2/jobs.py
@@ -39,6 +39,7 @@ class JobManager(QAbstractTableModel):
job = job_class(self.next_id, lock, *args, **kwargs)
QObject.connect(job, SIGNAL('finished()'), self.cleanup_jobs)
self.jobs[self.next_id] = job
+ self.emit(SIGNAL('job_added(int)'), self.next_id)
return job
finally:
self.job_create_lock.unlock()
@@ -55,8 +56,9 @@ class JobManager(QAbstractTableModel):
job = self.create_job(DeviceJob, self.device_lock, callable, *args, **kwargs)
QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self.job_done)
- QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
- slot)
+ if slot:
+ QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
+ slot)
job.start()
def job_done(self, id, *args, **kwargs):
@@ -69,6 +71,8 @@ class JobManager(QAbstractTableModel):
self.cleanup_lock.lock()
self.cleanup[id] = job
self.cleanup_lock.unlock()
+ if len(self.jobs.keys()) == 0:
+ self.emit(SIGNAL('no_more_jobs()'))
finally:
self.job_remove_lock.unlock()
diff --git a/src/libprs500/gui2/library.py b/src/libprs500/gui2/library.py
index f8ad7c4a68..e8ac781046 100644
--- a/src/libprs500/gui2/library.py
+++ b/src/libprs500/gui2/library.py
@@ -13,7 +13,8 @@
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os, textwrap, traceback, time, re
-from datetime import timedelta
+from datetime import timedelta, datetime
+from operator import attrgetter
from math import cos, sin, pi
from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, \
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
@@ -76,66 +77,6 @@ class LibraryDelegate(QItemDelegate):
except Exception, e:
traceback.print_exc(e)
painter.restore()
-
-class BooksView(QTableView):
- wrapper = textwrap.TextWrapper(width=20)
-
- @classmethod
- def wrap(cls, s, width=20):
- cls.wrapper.width = width
- return cls.wrapper.fill(s)
-
- @classmethod
- def human_readable(cls, size):
- """ Convert a size in bytes into a human readable form """
- if size < 1024:
- divisor, suffix = 1, "B"
- elif size < 1024*1024:
- divisor, suffix = 1024., "KB"
- elif size < 1024*1024*1024:
- divisor, suffix = 1024*1024, "MB"
- elif size < 1024*1024*1024*1024:
- divisor, suffix = 1024*1024, "GB"
- size = str(size/divisor)
- if size.find(".") > -1:
- size = size[:size.find(".")+2]
- return size + " " + suffix
-
- def __init__(self, parent):
- QTableView.__init__(self, parent)
- self.display_parent = parent
- self.model = BooksModel(self)
- self.setModel(self.model)
- 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)
- self.verticalHeader().setVisible(False)
-
- def set_database(self, db):
- self.model.set_database(db)
-
- def migrate_database(self):
- if self.model.database_needs_migration():
- print 'Migrating database from pre 0.4.0 version'
- path = os.path.abspath(os.path.expanduser('~/library.db'))
- progress = QProgressDialog('Upgrading database from pre 0.4.0 version.
'+\
- 'The new database is stored in the file '+self.model.db.dbpath,
- QString(), 0, LibraryDatabase.sizeof_old_database(path),
- self)
- progress.setModal(True)
- app = QCoreApplication.instance()
- def meter(count):
- progress.setValue(count)
- app.processEvents()
- progress.setWindowTitle('Upgrading database')
- progress.show()
- 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)
-
class BooksModel(QAbstractTableModel):
@@ -150,7 +91,7 @@ class BooksModel(QAbstractTableModel):
db = LibraryDatabase(os.path.expanduser(str(db)))
self.db = db
- def search(self, text, refinement):
+ def search_tokens(self, text):
tokens = []
quot = re.search('"(.*?)"', text)
while quot:
@@ -158,6 +99,10 @@ class BooksModel(QAbstractTableModel):
text = text.replace('"'+quot.group(1)+'"', '')
quot = re.search('"(.*?)"', text)
tokens += text.split(' ')
+ return [re.compile(i, re.IGNORECASE) for i in tokens]
+
+ def search(self, text, refinement):
+ tokens = self.search_tokens(text)
self.db.filter(tokens, refinement)
self.reset()
self.emit(SIGNAL('searched()'))
@@ -204,7 +149,7 @@ class BooksModel(QAbstractTableModel):
dt = self.db.timestamp(row)
if dt:
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
- return QVariant(dt.strftime('%d %b %Y'))
+ return QVariant(dt.strftime(BooksView.TIME_FMT))
elif col == 4:
r = self.db.rating(row)
r = r/2 if r else 0
@@ -216,13 +161,9 @@ class BooksModel(QAbstractTableModel):
return NONE
elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]:
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
- elif role == Qt.ToolTipRole and index.isValid():
+ elif role == Qt.ToolTipRole and index.isValid():
if index.column() in [0, 1, 4, 5]:
- edit = "Double click to edit me
"
- else:
- edit = ""
- return QVariant(edit + "You can drag and drop me to the \
- desktop to save all my formats to your hard disk.")
+ return QVariant("Double click to edit me
")
return NONE
def headerData(self, section, orientation, role):
@@ -232,13 +173,13 @@ class BooksModel(QAbstractTableModel):
if orientation == Qt.Horizontal:
if section == 0: text = "Title"
elif section == 1: text = "Author(s)"
- elif section == 2: text = "Size"
+ elif section == 2: text = "Size (MB)"
elif section == 3: text = "Date"
elif section == 4: text = "Rating"
elif section == 5: text = "Publisher"
return QVariant(self.trUtf8(text))
else:
- return NONE
+ return QVariant(section+1)
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
@@ -266,11 +207,202 @@ class BooksModel(QAbstractTableModel):
self.sort(col, self.sorted_on[1])
done = True
return done
+
+
+class BooksView(QTableView):
+ TIME_FMT = '%d %b %Y'
+ wrapper = textwrap.TextWrapper(width=20)
+ @classmethod
+ def wrap(cls, s, width=20):
+ cls.wrapper.width = width
+ return cls.wrapper.fill(s)
+
+ @classmethod
+ def human_readable(cls, size, precision=1):
+ """ Convert a size in bytes into megabytes """
+ return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
+
+ def __init__(self, parent, modelcls=BooksModel):
+ QTableView.__init__(self, parent)
+ self.display_parent = parent
+ self._model = modelcls(self)
+ self.setModel(self._model)
+ 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)
+ #self.verticalHeader().setVisible(False)
+
+ def set_database(self, db):
+ self._model.set_database(db)
+
+ def migrate_database(self):
+ if self._model.database_needs_migration():
+ print 'Migrating database from pre 0.4.0 version'
+ path = os.path.abspath(os.path.expanduser('~/library.db'))
+ progress = QProgressDialog('Upgrading database from pre 0.4.0 version.
'+\
+ 'The new database is stored in the file '+self._model.db.dbpath,
+ QString(), 0, LibraryDatabase.sizeof_old_database(path),
+ self)
+ progress.setModal(True)
+ app = QCoreApplication.instance()
+ def meter(count):
+ progress.setValue(count)
+ app.processEvents()
+ progress.setWindowTitle('Upgrading database')
+ progress.show()
+ 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)
+
+class DeviceBooksView(BooksView):
+
+ def __init__(self, parent):
+ BooksView.__init__(self, parent, DeviceBooksModel)
+ self.columns_resized = False
+
+ def resizeColumnsToContents(self):
+ QTableView.resizeColumnsToContents(self)
+ self.columns_resized = True
+
+ def connect_dirtied_signal(self, slot):
+ QObject.connect(self._model, SIGNAL('booklist_dirtied()'), slot)
+
+class DeviceBooksModel(BooksModel):
+
+ def __init__(self, parent):
+ QAbstractTableModel.__init__(self, parent)
+ self.db = []
+ self.map = []
+ self.sorted_map = []
+ self.unknown = str(self.trUtf8('Unknown'))
+
+ def search(self, text, refinement):
+ tokens = self.search_tokens(text)
+ base = self.map if refinement else self.sorted_map
+ result = []
+ for i in base:
+ add = True
+ q = self.db[i].title + ' ' + self.db[i].authors
+ for token in tokens:
+ if not token.search(q):
+ add = False
+ break
+ if add:
+ result.append(i)
+ self.map = result
+ self.reset()
+ self.emit(SIGNAL('searched()'))
+
+ def sort(self, col, order):
+ if not self.db:
+ return
+ descending = order != Qt.AscendingOrder
+ def strcmp(attr):
+ ag = attrgetter(attr)
+ def _strcmp(x, y):
+ x = ag(self.db[x])
+ y = ag(self.db[y])
+ if x == None:
+ x = ''
+ if y == None:
+ y = ''
+ x, y = x.strip().lower(), y.strip().lower()
+ return cmp(x, y)
+ return _strcmp
+ def datecmp(x, y):
+ x = self.db[x].datetime
+ y = self.db[y].datetime
+ return cmp(datetime(*x[0:6]), datetime(*y[0:6]))
+ def sizecmp(x, y):
+ x, y = int(self.db[x].size), int(self.db[y].size)
+ return cmp(x, y)
+ fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \
+ sizecmp if col == 2 else datecmp
+ self.map.sort(cmp=fcmp, reverse=descending)
+ if len(self.map) == len(self.db):
+ self.sorted_map = list(self.map)
+ else:
+ self.sorted_map = list(range(len(self.db)))
+ 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
+
+ def rowCount(self, parent):
+ return len(self.map)
+
+ def set_database(self, db):
+ self.db = db
+ self.map = list(range(0, len(db)))
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole or role == Qt.EditRole:
+ row, col = index.row(), index.column()
+ if col == 0:
+ text = self.db[self.map[row]].title
+ if not text:
+ text = self.unknown
+ return QVariant(BooksView.wrap(text, width=35))
+ elif col == 1:
+ au = self.db[self.map[row]].authors
+ if not au:
+ au = self.unknown
+ if role == Qt.EditRole:
+ return QVariant(au)
+ au = au.split(',')
+ authors = []
+ for i in au:
+ authors += i.strip().split('&')
+ jau = [ BooksView.wrap(a.strip(), width=30).strip() for a in authors ]
+ return QVariant("\n".join(jau))
+ elif col == 2:
+ size = self.db[self.map[row]].size
+ return QVariant(BooksView.human_readable(size))
+ elif col == 3:
+ dt = self.db[self.map[row]].datetime
+ dt = datetime(*dt[0:6])
+ dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
+ return QVariant(dt.strftime(BooksView.TIME_FMT))
+ elif role == Qt.TextAlignmentRole and index.column() in [2, 3]:
+ return QVariant(Qt.AlignRight | Qt.AlignVCenter)
+ elif role == Qt.ToolTipRole and index.isValid():
+ if index.column() in [0, 1]:
+ return QVariant("Double click to edit me
")
+ return NONE
+
+ def setData(self, index, value, role):
+ done = False
+ if role == Qt.EditRole:
+ row, col = index.row(), index.column()
+ if col in [2, 3]:
+ return False
+ val = unicode(value.toString().toUtf8(), 'utf-8').strip()
+ idx = self.map[row]
+ if col == 0:
+ self.db.title = val
+ self.db.title_sorter = val
+ elif col == 1:
+ self.db.authors = val
+ self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
+ index, index)
+ self.emit(SIGNAL('booklist_dirtied()'))
+ if col == self.sorted_on[0]:
+ self.sort(col, self.sorted_on[1])
+ done = True
+ return done
+
class SearchBox(QLineEdit):
def __init__(self, parent):
QLineEdit.__init__(self, parent)
- self.setText('Search by title, author, publisher, tags and comments')
+ 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)
@@ -282,12 +414,21 @@ class SearchBox(QLineEdit):
self.timer = None
self.interval = 1000 #: Time to wait before emitting search signal
+ def normalize_state(self):
+ self.setText('')
+ self.setPalette(self.default_palette)
+
def keyPressEvent(self, event):
if self.initial_state:
- self.setText('')
+ self.normalize_state()
self.initial_state = False
- self.setPalette(self.default_palette)
QLineEdit.keyPressEvent(self, event)
+
+ def mouseReleaseEvent(self, event):
+ if self.initial_state:
+ self.normalize_state()
+ self.initial_state = False
+ QLineEdit.mouseReleaseEvent(self, event)
def text_edited_slot(self, text):
text = str(text)
diff --git a/src/libprs500/gui2/main.py b/src/libprs500/gui2/main.py
index c3d6ade3aa..6326e81e90 100644
--- a/src/libprs500/gui2/main.py
+++ b/src/libprs500/gui2/main.py
@@ -15,11 +15,8 @@
import os, tempfile, sys
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
- QSettings, QVariant, QSize, QEventLoop, QString, \
- QBuffer, QIODevice, QModelIndex, QThread
-from PyQt4.QtGui import QPixmap, QErrorMessage, QLineEdit, \
- QMessageBox, QFileDialog, QIcon, QDialog, QInputDialog
-from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
+ QSettings, QVariant, QSize, QThread
+from PyQt4.QtGui import QErrorMessage
from libprs500 import __version__ as VERSION
from libprs500.gui2 import APP_TITLE, installErrorHandler
@@ -40,6 +37,10 @@ class Main(QObject, Ui_MainWindow):
self.device_manager = None
self.temporary_slots = {}
+ ####################### Location View ########################
+ QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
+ self.location_selected)
+
####################### Vanity ########################
self.vanity_template = self.vanity.text().arg(VERSION)
self.vanity.setText(self.vanity_template.arg(' '))
@@ -47,10 +48,17 @@ class Main(QObject, Ui_MainWindow):
####################### Status Bar #####################
self.status_bar = StatusBar()
self.window.setStatusBar(self.status_bar)
+ 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 books view ########################
+
+ ####################### 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)
+ 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()
@@ -67,16 +75,28 @@ 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 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 job_exception(self, id, exception, formatted_traceback):
raise JobException, str(exception) + '\n\r' + formatted_traceback
def device_detected(self, cls, connected):
- if connected:
-
-
+ if connected:
self.device_manager = DeviceManager(cls)
- func = self.device_manager.get_info_func()
-
+ func = self.device_manager.info_func()
self.job_manager.run_device_job(self.info_read, func)
def info_read(self, id, result, exception, formatted_traceback):
@@ -86,6 +106,20 @@ class Main(QObject, Ui_MainWindow):
info, cp, fs = result
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)
+
+ def books_read(self, id, result, exception, formatted_traceback):
+ 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')
+
def read_settings(self):
diff --git a/src/libprs500/gui2/main.ui b/src/libprs500/gui2/main.ui
index 8382fc0374..7f2f9d4d0e 100644
--- a/src/libprs500/gui2/main.ui
+++ b/src/libprs500/gui2/main.ui
@@ -170,7 +170,7 @@
-
-
+
100
@@ -178,7 +178,7 @@
- 0
+ 2
@@ -218,7 +218,42 @@
-
-
+
+
+
+ 100
+ 10
+
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::DragDrop
+
+
+ true
+
+
+ QAbstractItemView::SelectRows
+
+
+ false
+
+
+
+
+
+
+
+ -
+
100
@@ -347,6 +382,11 @@
QListView
+
+ DeviceBooksView
+ QTableView
+
+
diff --git a/src/libprs500/gui2/main_ui.py b/src/libprs500/gui2/main_ui.py
index 784cb071da..eb1bc7cb04 100644
--- a/src/libprs500/gui2/main_ui.py
+++ b/src/libprs500/gui2/main_ui.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'main.ui'
#
-# Created: Fri Jun 22 16:40:15 2007
+# Created: Wed Jun 27 16:19:53 2007
# by: PyQt4 UI code generator 4-snapshot-20070606
#
# WARNING! All changes made in this file will be lost!
@@ -86,14 +86,14 @@ class Ui_MainWindow(object):
self.hboxlayout1.addWidget(self.clear_button)
self.gridlayout.addLayout(self.hboxlayout1,1,0,1,1)
- self.stacks = QtGui.QStackedWidget(self.centralwidget)
+ self.stack = QtGui.QStackedWidget(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(100)
sizePolicy.setVerticalStretch(100)
- sizePolicy.setHeightForWidth(self.stacks.sizePolicy().hasHeightForWidth())
- self.stacks.setSizePolicy(sizePolicy)
- self.stacks.setObjectName("stacks")
+ sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth())
+ self.stack.setSizePolicy(sizePolicy)
+ self.stack.setObjectName("stack")
self.library = QtGui.QWidget()
self.library.setObjectName("library")
@@ -117,7 +117,7 @@ class Ui_MainWindow(object):
self.library_view.setShowGrid(False)
self.library_view.setObjectName("library_view")
self.vboxlayout.addWidget(self.library_view)
- self.stacks.addWidget(self.library)
+ self.stack.addWidget(self.library)
self.main_memory = QtGui.QWidget()
self.main_memory.setObjectName("main_memory")
@@ -125,24 +125,48 @@ class Ui_MainWindow(object):
self.gridlayout1 = QtGui.QGridLayout(self.main_memory)
self.gridlayout1.setObjectName("gridlayout1")
- self.main_memory_view = BooksView(self.main_memory)
+ self.memory_view = DeviceBooksView(self.main_memory)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(100)
sizePolicy.setVerticalStretch(10)
- sizePolicy.setHeightForWidth(self.main_memory_view.sizePolicy().hasHeightForWidth())
- self.main_memory_view.setSizePolicy(sizePolicy)
- self.main_memory_view.setAcceptDrops(True)
- self.main_memory_view.setDragEnabled(True)
- self.main_memory_view.setDragDropOverwriteMode(False)
- self.main_memory_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
- self.main_memory_view.setAlternatingRowColors(True)
- self.main_memory_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
- self.main_memory_view.setShowGrid(False)
- self.main_memory_view.setObjectName("main_memory_view")
- self.gridlayout1.addWidget(self.main_memory_view,0,0,1,1)
- self.stacks.addWidget(self.main_memory)
- self.gridlayout.addWidget(self.stacks,2,0,1,1)
+ sizePolicy.setHeightForWidth(self.memory_view.sizePolicy().hasHeightForWidth())
+ self.memory_view.setSizePolicy(sizePolicy)
+ self.memory_view.setAcceptDrops(True)
+ self.memory_view.setDragEnabled(True)
+ self.memory_view.setDragDropOverwriteMode(False)
+ self.memory_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
+ self.memory_view.setAlternatingRowColors(True)
+ self.memory_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
+ self.memory_view.setShowGrid(False)
+ self.memory_view.setObjectName("memory_view")
+ self.gridlayout1.addWidget(self.memory_view,0,0,1,1)
+ self.stack.addWidget(self.main_memory)
+
+ self.page = QtGui.QWidget()
+ self.page.setObjectName("page")
+
+ self.gridlayout2 = QtGui.QGridLayout(self.page)
+ self.gridlayout2.setObjectName("gridlayout2")
+
+ self.card_view = DeviceBooksView(self.page)
+
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
+ sizePolicy.setHorizontalStretch(100)
+ sizePolicy.setVerticalStretch(10)
+ sizePolicy.setHeightForWidth(self.card_view.sizePolicy().hasHeightForWidth())
+ self.card_view.setSizePolicy(sizePolicy)
+ self.card_view.setAcceptDrops(True)
+ self.card_view.setDragEnabled(True)
+ self.card_view.setDragDropOverwriteMode(False)
+ self.card_view.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
+ self.card_view.setAlternatingRowColors(True)
+ self.card_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
+ self.card_view.setShowGrid(False)
+ self.card_view.setObjectName("card_view")
+ self.gridlayout2.addWidget(self.card_view,0,0,1,1)
+ self.stack.addWidget(self.page)
+ self.gridlayout.addWidget(self.stack,2,0,1,1)
MainWindow.setCentralWidget(self.centralwidget)
self.tool_bar = QtGui.QToolBar(MainWindow)
@@ -178,7 +202,7 @@ class Ui_MainWindow(object):
self.label.setBuddy(self.search)
self.retranslateUi(MainWindow)
- self.stacks.setCurrentIndex(0)
+ self.stack.setCurrentIndex(2)
QtCore.QObject.connect(self.clear_button,QtCore.SIGNAL("clicked()"),self.search.clear)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
@@ -198,5 +222,5 @@ class Ui_MainWindow(object):
self.action_edit.setShortcut(QtGui.QApplication.translate("MainWindow", "E", None, QtGui.QApplication.UnicodeUTF8))
from widgets import LocationView
-from library import BooksView, SearchBox
+from library import BooksView, DeviceBooksView, SearchBox
import images_rc
diff --git a/src/libprs500/gui2/status.py b/src/libprs500/gui2/status.py
index 432413d05a..552c58573c 100644
--- a/src/libprs500/gui2/status.py
+++ b/src/libprs500/gui2/status.py
@@ -56,7 +56,7 @@ class MovieButton(QLabel):
self.movie = movie
self.setMovie(movie)
self.movie.start()
- self.movie.stop()
+ self.movie.setPaused(True)
class StatusBar(QStatusBar):
def __init__(self):
@@ -66,6 +66,16 @@ class StatusBar(QStatusBar):
self.book_info = BookInfoDisplay()
self.addWidget(self.book_info)
+ def job_added(self, id):
+ if self.movie_button.movie.state() == QMovie.Paused:
+ self.movie_button.movie.setPaused(False)
+
+ def no_more_jobs(self):
+ if self.movie_button.movie.state() == QMovie.Running:
+ self.movie_button.movie.setPaused(True)
+ self.movie_button.movie.jumpToFrame(0) # This causes MNG error 11, but seems to work regardless
+
+
if __name__ == '__main__':
# Used to create the animated status icon
from PyQt4.Qt import QApplication, QPainter, QSvgRenderer, QPixmap, QColor
@@ -99,7 +109,6 @@ if __name__ == '__main__':
pixmaps[i].save(name, 'PNG')
filesc = ' '.join(filesl)
cmd = 'convert -dispose Background -delay '+str(delay)+ ' ' + filesc + ' -loop 0 animated.mng'
- print cmd
try:
check_call(cmd, shell=True)
finally:
diff --git a/src/libprs500/gui2/widgets.py b/src/libprs500/gui2/widgets.py
index 5a9b92722c..45e6388e9e 100644
--- a/src/libprs500/gui2/widgets.py
+++ b/src/libprs500/gui2/widgets.py
@@ -16,7 +16,7 @@
Miscellanous widgets used in the GUI
'''
from PyQt4.QtGui import QListView, QIcon, QFont
-from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QSize, SIGNAL
+from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QSize, SIGNAL, QObject
from libprs500.gui2 import human_readable, NONE
@@ -60,8 +60,7 @@ class LocationModel(QAbstractListModel):
self.free[1] = max(fs[1:])
if cp == None:
self.free[1] = -1
- print self.free, self.rowCount(None)
- self.reset()
+ self.reset()
class LocationView(QListView):
@@ -69,4 +68,12 @@ class LocationView(QListView):
QListView.__init__(self, parent)
self.setModel(LocationModel(self))
self.reset()
+ QObject.connect(self.selectionModel(), SIGNAL('currentChanged(QModelIndex, QModelIndex)'), self.current_changed)
+
+
+ def current_changed(self, current, previous):
+ i = current.row()
+ location = 'library' if i == 0 else 'main' if i == 1 else 'card'
+ self.emit(SIGNAL('location_selected(PyQt_PyObject)'), location)
+
diff --git a/src/libprs500/library/database.py b/src/libprs500/library/database.py
index 01cabfc34e..c366829abd 100644
--- a/src/libprs500/library/database.py
+++ b/src/libprs500/library/database.py
@@ -595,14 +595,13 @@ class LibraryDatabase(object):
'''
Filter data based on filters. All the filters must match for an item to
be accepted. Matching is case independent regexp matching.
- @param filters: A list of strings suitable for compilation into regexps
+ @param filters: A list of compiled regexps
@param refilter: If True filters are applied to the results of the previous
filtering.
'''
if not filters:
self.data = self.data if refilter else self.cache
else:
- filters = [re.compile(i, re.IGNORECASE) for i in filters if i]
matches = []
for item in self.data if refilter else self.cache:
keep = True