merge from trunk

This commit is contained in:
Lee 2011-04-27 23:56:06 +08:00
commit 052b0044e2
40 changed files with 440 additions and 316 deletions

View File

@ -48,7 +48,7 @@ class LeMonde(BasicNewsRecipe):
if alink.string is not None: if alink.string is not None:
tstr = alink.string tstr = alink.string
alink.replaceWith(tstr) alink.replaceWith(tstr)
return soup return self.adeify_images(soup)
preprocess_regexps = [ preprocess_regexps = [
(re.compile(r'([0-9])%'), lambda m: m.group(1) + ' %'), (re.compile(r'([0-9])%'), lambda m: m.group(1) + ' %'),

View File

@ -118,6 +118,7 @@ sort_columns_at_startup = None
# timestamp default if not set: dd MMM yyyy # timestamp default if not set: dd MMM yyyy
gui_pubdate_display_format = 'MMM yyyy' gui_pubdate_display_format = 'MMM yyyy'
gui_timestamp_display_format = 'dd MMM yyyy' gui_timestamp_display_format = 'dd MMM yyyy'
gui_last_modified_display_format = 'dd MMM yyyy'
#: Control sorting of titles and series in the library display #: Control sorting of titles and series in the library display
# Control title and series sorting in the library view. If set to # Control title and series sorting in the library view. If set to

View File

@ -69,7 +69,24 @@ nmake -f ms\ntdll.mak install
Qt Qt
-------- --------
Extract Qt sourcecode to C:\Qt\4.x.x. Run configure and make:: Extract Qt sourcecode to C:\Qt\4.x.x.
Qt uses its own routine to locate and load "system libraries" including the openssl libraries needed for "Get Books". This means that we have to apply the following patch to have Qt load the openssl libraries bundled with calibre:
--- src/corelib/plugin/qsystemlibrary.cpp 2011-02-22 05:04:00.000000000 -0700
+++ src/corelib/plugin/qsystemlibrary.cpp 2011-04-25 20:53:13.635247466 -0600
@@ -110,7 +110,7 @@ HINSTANCE QSystemLibrary::load(const wch
#if !defined(QT_BOOTSTRAPPED)
if (!onlySystemDirectory)
- searchOrder << QFileInfo(qAppFileName()).path();
+ searchOrder << (QFileInfo(qAppFileName()).path().replace(QLatin1Char('/'), QLatin1Char('\\')) + QString::fromLatin1("\\DLLs\\"));
#endif
searchOrder << qSystemDirectory();
Now, run configure and make::
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake

View File

@ -11,7 +11,10 @@
SummaryCodepage='1252' /> SummaryCodepage='1252' />
<Media Id="1" Cabinet="{app}.cab" CompressionLevel="{compression}" EmbedCab="yes" /> <Media Id="1" Cabinet="{app}.cab" CompressionLevel="{compression}" EmbedCab="yes" />
<!-- The following line is needed because of the patch to QtCore4.dll. You can remove this line
after you update Qt beyond 4.7.2. 'emus' means re-install even if version is the same not just if it is older. -->
<Property Id='REINSTALLMODE' Value='emus'/>
<Upgrade Id="{upgrade_code}"> <Upgrade Id="{upgrade_code}">
<UpgradeVersion Maximum="{version}" <UpgradeVersion Maximum="{version}"
IncludeMaximum="yes" IncludeMaximum="yes"

View File

@ -347,9 +347,10 @@ class UploadUserManual(Command): # {{{
with NamedTemporaryFile(suffix='.zip') as f: with NamedTemporaryFile(suffix='.zip') as f:
os.fchmod(f.fileno(), os.fchmod(f.fileno(),
stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH|stat.S_IWRITE) stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH|stat.S_IWRITE)
with CurrentDir(self.d(path)): with CurrentDir(path):
with ZipFile(f, 'w') as zf: with ZipFile(f, 'w') as zf:
for x in os.listdir('.'): for x in os.listdir('.'):
if x.endswith('.swp'): continue
zf.write(x) zf.write(x)
if os.path.isdir(x): if os.path.isdir(x):
for y in os.listdir(x): for y in os.listdir(x):

View File

@ -388,7 +388,11 @@ class CurrentDir(object):
return self.cwd return self.cwd
def __exit__(self, *args): def __exit__(self, *args):
os.chdir(self.cwd) try:
os.chdir(self.cwd)
except:
# The previous CWD no longer exists
pass
class StreamReadWrapper(object): class StreamReadWrapper(object):

View File

@ -460,7 +460,7 @@ class ITUNES(DriverBase):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':book.Name, 'title':book.Name,
'author':book.artist().split(' & '), 'author':book.Artist.split(' & '),
'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'lib_book':library_books[this_book.path] if this_book.path in library_books else None,
'uuid': book.Composer, 'uuid': book.Composer,
'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'

View File

@ -402,7 +402,7 @@ class HTMLPreProcessor(object):
(re.compile(r'((?<=</a>)\s*file:/{2,4}[A-Z].*<br>|file:////?[A-Z].*<br>(?=\s*<hr>))', re.IGNORECASE), lambda match: ''), (re.compile(r'((?<=</a>)\s*file:/{2,4}[A-Z].*<br>|file:////?[A-Z].*<br>(?=\s*<hr>))', re.IGNORECASE), lambda match: ''),
# Center separator lines # Center separator lines
(re.compile(u'<br>\s*(?P<break>([*#•✦=]+\s*)+)\s*<br>'), lambda match: '<p>\n<p style="text-align:center">' + match.group(1) + '</p>'), (re.compile(u'<br>\s*(?P<break>([*#•✦=] *){3,})\s*<br>'), lambda match: '<p>\n<p style="text-align:center">' + match.group('break') + '</p>'),
# Remove page links # Remove page links
(re.compile(r'<a name=\d+></a>', re.IGNORECASE), lambda match: ''), (re.compile(r'<a name=\d+></a>', re.IGNORECASE), lambda match: ''),

View File

@ -120,7 +120,11 @@ class Metadata(object):
_('TEMPLATE ERROR'), _('TEMPLATE ERROR'),
self).strip() self).strip()
return val return val
if field.startswith('#') and field.endswith('_index'):
try:
return self.get_extra(field[:-6])
except:
pass
raise AttributeError( raise AttributeError(
'Metadata object has no attribute named: '+ repr(field)) 'Metadata object has no attribute named: '+ repr(field))
@ -170,11 +174,6 @@ class Metadata(object):
try: try:
return self.__getattribute__(field) return self.__getattribute__(field)
except AttributeError: except AttributeError:
if field.startswith('#') and field.endswith('_index'):
try:
return self.get_extra(field[:-6])
except:
pass
return default return default
def get_extra(self, field, default=None): def get_extra(self, field, default=None):
@ -631,7 +630,7 @@ class Metadata(object):
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy')) res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'rating': elif datatype == 'rating':
res = res/2.0 res = res/2.0
elif key in ('book_size', 'size'): elif key == 'size':
res = human_readable(res) res = human_readable(res)
return (name, unicode(res), orig_res, fmeta) return (name, unicode(res), orig_res, fmeta)

View File

@ -400,7 +400,8 @@ class MetadataUpdater(object):
if getattr(self, 'exth', None) is None: if getattr(self, 'exth', None) is None:
raise MobiError('No existing EXTH record. Cannot update metadata.') raise MobiError('No existing EXTH record. Cannot update metadata.')
self.record0[92:96] = iana2mobi(mi.language) if not mi.is_null('language'):
self.record0[92:96] = iana2mobi(mi.language)
self.create_exth(exth=exth, new_title=mi.title) self.create_exth(exth=exth, new_title=mi.title)
# Fetch updated timestamp, cover_record, thumbnail_record # Fetch updated timestamp, cover_record, thumbnail_record

View File

@ -301,7 +301,7 @@ class Amazon(Source):
if asin is None: if asin is None:
asin = identifiers.get('asin', None) asin = identifiers.get('asin', None)
if asin: if asin:
return 'http://amzn.com/%s'%asin return ('amazon', asin, 'http://amzn.com/%s'%asin)
# }}} # }}}
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ def create_query(self, log, title=None, authors=None, identifiers={}): # {{{

View File

@ -56,7 +56,8 @@ class InternalMetadataCompareKeyGen(object):
''' '''
Generate a sort key for comparison of the relevance of Metadata objects, Generate a sort key for comparison of the relevance of Metadata objects,
given a search query. given a search query. This is used only to compare results from the same
metadata source, not across different sources.
The sort key ensures that an ascending order sort is a sort by order of The sort key ensures that an ascending order sort is a sort by order of
decreasing relevance. decreasing relevance.
@ -374,7 +375,11 @@ class Source(Plugin):
def get_book_url(self, identifiers): def get_book_url(self, identifiers):
''' '''
Return the URL for the book identified by identifiers at this source. Return a 3-tuple or None. The 3-tuple is of the form:
(identifier_type, identifier_value, URL).
The URL is the URL for the book identified by identifiers at this
source. identifier_type, identifier_value specify the identifier
corresponding to the URL.
This URL must be browseable to by a human using a browser. It is meant This URL must be browseable to by a human using a browser. It is meant
to provide a clickable link for the user to easily visit the books page to provide a clickable link for the user to easily visit the books page
at this source. at this source.

View File

@ -173,7 +173,7 @@ class GoogleBooks(Source):
def get_book_url(self, identifiers): # {{{ def get_book_url(self, identifiers): # {{{
goog = identifiers.get('google', None) goog = identifiers.get('google', None)
if goog is not None: if goog is not None:
return 'http://books.google.com/books?id=%s'%goog return ('google', goog, 'http://books.google.com/books?id=%s'%goog)
# }}} # }}}
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ def create_query(self, log, title=None, authors=None, identifiers={}): # {{{

View File

@ -435,18 +435,30 @@ def identify(log, abort, # {{{
# }}} # }}}
def urls_from_identifiers(identifiers): # {{{ def urls_from_identifiers(identifiers): # {{{
identifiers = dict([(k.lower(), v) for k, v in identifiers.iteritems()])
ans = [] ans = []
for plugin in all_metadata_plugins(): for plugin in all_metadata_plugins():
try: try:
url = plugin.get_book_url(identifiers) id_type, id_val, url = plugin.get_book_url(identifiers)
if url is not None: ans.append((plugin.name, id_type, id_val, url))
ans.append((plugin.name, url))
except: except:
pass pass
isbn = identifiers.get('isbn', None) isbn = identifiers.get('isbn', None)
if isbn: if isbn:
ans.append((isbn, ans.append((isbn, 'isbn', isbn,
'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn)) 'http://www.worldcat.org/isbn/'+isbn))
doi = identifiers.get('doi', None)
if doi:
ans.append(('DOI', 'doi', doi,
'http://dx.doi.org/'+doi))
arxiv = identifiers.get('arxiv', None)
if arxiv:
ans.append(('arXiv', 'arxiv', arxiv,
'http://arxiv.org/abs/'+arxiv))
oclc = identifiers.get('oclc', None)
if oclc:
ans.append(('OCLC', 'oclc', oclc,
'http://www.worldcat.org/oclc/'+oclc))
return ans return ans
# }}} # }}}

View File

@ -272,7 +272,7 @@ class OverDrive(Source):
creators = creators.split(', ') creators = creators.split(', ')
# if an exact match in a preferred format occurs # if an exact match in a preferred format occurs
if ((author and creators[0] == author[0]) or (not author and not creators)) and od_title.lower() == title.lower() and int(formatid) in [1, 50, 410, 900] and thumbimage: if ((author and creators and creators[0] == author[0]) or (not author and not creators)) and od_title.lower() == title.lower() and int(formatid) in [1, 50, 410, 900] and thumbimage:
return self.format_results(reserveid, od_title, subtitle, series, publisher, return self.format_results(reserveid, od_title, subtitle, series, publisher,
creators, thumbimage, worldcatlink, formatid) creators, thumbimage, worldcatlink, formatid)
else: else:
@ -298,7 +298,7 @@ class OverDrive(Source):
close_matches.insert(0, self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) close_matches.insert(0, self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
else: else:
close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
elif close_title_match and close_author_match and int(formatid) in [1, 50, 410, 900]: elif close_title_match and close_author_match and int(formatid) in [1, 50, 410, 900]:
close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))

View File

@ -222,7 +222,7 @@ class SaveWorker(Thread):
if isbytestring(fpath): if isbytestring(fpath):
fpath = fpath.decode(filesystem_encoding) fpath = fpath.decode(filesystem_encoding)
formats[fmt.lower()] = fpath formats[fmt.lower()] = fpath
data[i] = [opf, cpath, formats] data[i] = [opf, cpath, formats, mi.last_modified.isoformat()]
return data return data
def run(self): def run(self):

View File

@ -16,6 +16,7 @@ from calibre import CurrentDir
from calibre.ebooks.pdb.formatreader import FormatReader from calibre.ebooks.pdb.formatreader import FormatReader
from calibre.ptempfile import TemporaryFile from calibre.ptempfile import TemporaryFile
from calibre.utils.magick import Image, create_canvas from calibre.utils.magick import Image, create_canvas
from calibre.ebooks.compression.palmdoc import decompress_doc
DATATYPE_PHTML = 0 DATATYPE_PHTML = 0
DATATYPE_PHTML_COMPRESSED = 1 DATATYPE_PHTML_COMPRESSED = 1

View File

@ -739,12 +739,6 @@ def build_forms(srcdir, info=None):
dat = dat.replace('from QtWebKit.QWebView import QWebView', dat = dat.replace('from QtWebKit.QWebView import QWebView',
'from PyQt4 import QtWebKit\nfrom PyQt4.QtWebKit import QWebView') 'from PyQt4 import QtWebKit\nfrom PyQt4.QtWebKit import QWebView')
if form.endswith('viewer%smain.ui'%os.sep):
info('\t\tPromoting WebView')
dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(')
dat = dat.replace('self.view = QWebView(', 'self.view = DocumentView(')
dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView'
open(compiled_form, 'wb').write(dat) open(compiled_form, 'wb').write(dat)
_df = os.environ.get('CALIBRE_DEVELOP_FROM', None) _df = os.environ.get('CALIBRE_DEVELOP_FROM', None)

View File

@ -17,13 +17,13 @@ from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.metadata.book.base import (field_metadata, Metadata) from calibre.ebooks.metadata.book.base import (field_metadata, Metadata)
from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import fmt_sidx
from calibre.ebooks.metadata.sources.identify import urls_from_identifiers
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
from calibre.library.comments import comments_to_html from calibre.library.comments import comments_to_html
from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data, from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data,
gprefs) gprefs)
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
def render_html(mi, css, vertical, widget, all_fields=False): # {{{ def render_html(mi, css, vertical, widget, all_fields=False): # {{{
table = render_data(mi, all_fields=all_fields, table = render_data(mi, all_fields=all_fields,
use_roman_numbers=config['use_roman_numerals_for_series_number']) use_roman_numbers=config['use_roman_numerals_for_series_number'])
@ -114,17 +114,20 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
u', '.join(fmts)))) u', '.join(fmts))))
elif field == 'identifiers': elif field == 'identifiers':
pass # TODO urls = urls_from_identifiers(mi.identifiers)
links = [u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name)
for name, id_typ, id_val, url in urls]
links = u', '.join(links)
if links:
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(
_('Ids')+':', links)))
else: else:
val = mi.format_field(field)[-1] val = mi.format_field(field)[-1]
if val is None: if val is None:
continue continue
val = prepare_string_for_xml(val) val = prepare_string_for_xml(val)
if metadata['datatype'] == 'series': if metadata['datatype'] == 'series':
if metadata['is_custom']: sidx = mi.get(field+'_index')
sidx = mi.get_extra(field)
else:
sidx = getattr(mi, field+'_index')
if sidx is None: if sidx is None:
sidx = 1.0 sidx = 1.0
val = _('Book %s of <span class="series_name">%s</span>')%(fmt_sidx(sidx, val = _('Book %s of <span class="series_name">%s</span>')%(fmt_sidx(sidx,
@ -292,6 +295,8 @@ class BookInfo(QWebView):
def link_activated(self, link): def link_activated(self, link):
self._link_clicked = True self._link_clicked = True
if unicode(link.scheme()) in ('http', 'https'):
return open_url(link)
link = unicode(link.toString()) link = unicode(link.toString())
self.link_clicked.emit(link) self.link_clicked.emit(link)

View File

@ -22,6 +22,12 @@
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="pixmap"> <property name="pixmap">
<pixmap resource="../../../../resources/images.qrc">:/images/dialog_warning.png</pixmap> <pixmap resource="../../../../resources/images.qrc">:/images/dialog_warning.png</pixmap>
</property> </property>
@ -46,6 +52,10 @@
<property name="text"> <property name="text">
<string>Library</string> <string>Library</string>
</property> </property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -53,6 +63,10 @@
<property name="text"> <property name="text">
<string>Device</string> <string>Device</string>
</property> </property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/reader.png</normaloff>:/images/reader.png</iconset>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -60,6 +74,10 @@
<property name="text"> <property name="text">
<string>Library and Device</string> <string>Library and Device</string>
</property> </property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -214,7 +214,6 @@ class SearchBar(QWidget): # {{{
x.setIcon(QIcon(I("search_add_saved.png"))) x.setIcon(QIcon(I("search_add_saved.png")))
x.setObjectName("save_search_button") x.setObjectName("save_search_button")
l.addWidget(x) l.addWidget(x)
x.setToolTip(_("Save current search under the name shown in the box"))
# }}} # }}}

View File

@ -97,18 +97,25 @@ class RatingDelegate(QStyledItemDelegate): # {{{
class DateDelegate(QStyledItemDelegate): # {{{ class DateDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent, tweak_name='gui_timestamp_display_format',
default_format='dd MMM yyyy', editor_format='dd MMM yyyy'):
QStyledItemDelegate.__init__(self, parent)
self.tweak_name = tweak_name
self.default_format = default_format
self.editor_format = editor_format
def displayText(self, val, locale): def displayText(self, val, locale):
d = val.toDate() d = val.toDate()
if d <= UNDEFINED_QDATE: if d <= UNDEFINED_QDATE:
return '' return ''
format = tweaks['gui_timestamp_display_format'] format = tweaks[self.tweak_name]
if format is None: if format is None:
format = 'dd MMM yyyy' format = self.default_format
return format_date(d.toPyDate(), format) return format_date(d.toPyDate(), format)
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index) qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('dd MMM yyyy') qde.setDisplayFormat(self.editor_format)
qde.setMinimumDate(UNDEFINED_QDATE) qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined')) qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True) qde.setCalendarPopup(True)

View File

@ -70,6 +70,7 @@ class BooksModel(QAbstractTableModel): # {{{
'publisher' : _("Publisher"), 'publisher' : _("Publisher"),
'tags' : _("Tags"), 'tags' : _("Tags"),
'series' : _("Series"), 'series' : _("Series"),
'last_modified' : _('Modified'),
} }
def __init__(self, parent=None, buffer=40): def __init__(self, parent=None, buffer=40):
@ -620,6 +621,8 @@ class BooksModel(QAbstractTableModel): # {{{
idx=self.db.field_metadata['timestamp']['rec_index']), idx=self.db.field_metadata['timestamp']['rec_index']),
'pubdate' : functools.partial(datetime_type, 'pubdate' : functools.partial(datetime_type,
idx=self.db.field_metadata['pubdate']['rec_index']), idx=self.db.field_metadata['pubdate']['rec_index']),
'last_modified': functools.partial(datetime_type,
idx=self.db.field_metadata['last_modified']['rec_index']),
'rating' : functools.partial(rating_type, 'rating' : functools.partial(rating_type,
idx=self.db.field_metadata['rating']['rec_index']), idx=self.db.field_metadata['rating']['rec_index']),
'publisher': functools.partial(text_type, 'publisher': functools.partial(text_type,

View File

@ -76,6 +76,8 @@ class BooksView(QTableView): # {{{
self.rating_delegate = RatingDelegate(self) self.rating_delegate = RatingDelegate(self)
self.timestamp_delegate = DateDelegate(self) self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self) self.pubdate_delegate = PubDateDelegate(self)
self.last_modified_delegate = DateDelegate(self,
tweak_name='gui_last_modified_display_format')
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags') self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True) self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True) self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
@ -296,6 +298,7 @@ class BooksView(QTableView): # {{{
state = {} state = {}
state['hidden_columns'] = [cm[i] for i in range(h.count()) state['hidden_columns'] = [cm[i] for i in range(h.count())
if h.isSectionHidden(i) and cm[i] != 'ondevice'] if h.isSectionHidden(i) and cm[i] != 'ondevice']
state['last_modified_injected'] = True
state['sort_history'] = \ state['sort_history'] = \
self.cleanup_sort_history(self.model().sort_history) self.cleanup_sort_history(self.model().sort_history)
state['column_positions'] = {} state['column_positions'] = {}
@ -380,7 +383,7 @@ class BooksView(QTableView): # {{{
def get_default_state(self): def get_default_state(self):
old_state = { old_state = {
'hidden_columns': [], 'hidden_columns': ['last_modified'],
'sort_history':[DEFAULT_SORT], 'sort_history':[DEFAULT_SORT],
'column_positions': {}, 'column_positions': {},
'column_sizes': {}, 'column_sizes': {},
@ -388,6 +391,7 @@ class BooksView(QTableView): # {{{
'size':'center', 'size':'center',
'timestamp':'center', 'timestamp':'center',
'pubdate':'center'}, 'pubdate':'center'},
'last_modified_injected': True,
} }
h = self.column_header h = self.column_header
cm = self.column_map cm = self.column_map
@ -398,7 +402,7 @@ class BooksView(QTableView): # {{{
old_state['column_sizes'][name] = \ old_state['column_sizes'][name] = \
min(350, max(self.sizeHintForColumn(i), min(350, max(self.sizeHintForColumn(i),
h.sectionSizeHint(i))) h.sectionSizeHint(i)))
if name == 'timestamp': if name in ('timestamp', 'last_modified'):
old_state['column_sizes'][name] += 12 old_state['column_sizes'][name] += 12
return old_state return old_state
@ -418,6 +422,13 @@ class BooksView(QTableView): # {{{
pass pass
if ans is not None: if ans is not None:
db.prefs[name] = ans db.prefs[name] = ans
else:
if not ans.get('last_modified_injected', False):
ans['last_modified_injected'] = True
hc = ans.get('hidden_columns', [])
if 'last_modified' not in hc:
hc.append('last_modified')
db.prefs[name] = ans
return ans return ans
@ -459,7 +470,8 @@ class BooksView(QTableView): # {{{
def database_changed(self, db): def database_changed(self, db):
for i in range(self.model().columnCount(None)): for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate, if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate, self.pubdate_delegate): self.timestamp_delegate, self.pubdate_delegate,
self.last_modified_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate()) self.setItemDelegateForColumn(i, self.itemDelegate())
cm = self.column_map cm = self.column_map

View File

@ -18,11 +18,11 @@ from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks, prefs
from calibre.ebooks.metadata import title_sort, authors_to_string, \ from calibre.ebooks.metadata import (title_sort, authors_to_string,
string_to_authors, check_isbn string_to_authors, check_isbn)
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \ from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE,
choose_files, error_dialog, choose_images, question_dialog choose_files, error_dialog, choose_images, question_dialog)
from calibre.utils.date import local_tz, qt_to_dt from calibre.utils.date import local_tz, qt_to_dt
from calibre import strftime from calibre import strftime
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
@ -280,11 +280,16 @@ class AuthorSortEdit(EnLineEdit):
aus = self.current_val aus = self.current_val
meth = tweaks['author_sort_copy_method'] meth = tweaks['author_sort_copy_method']
if aus: if aus:
ln, _, rest = aus.partition(',') ans = []
if rest: for one in [a.strip() for a in aus.split('&')]:
if meth in ('invert', 'nocomma', 'comma'): if not one:
aus = rest.strip() + ' ' + ln.strip() continue
self.authors_edit.current_val = [aus] ln, _, rest = one.partition(',')
if rest:
if meth in ('invert', 'nocomma', 'comma'):
one = rest.strip() + ' ' + ln.strip()
ans.append(one)
self.authors_edit.current_val = ans
def auto_generate(self, *args): def auto_generate(self, *args):
au = unicode(self.authors_edit.text()) au = unicode(self.authors_edit.text())
@ -805,6 +810,7 @@ class CommentsEdit(Editor): # {{{
else: else:
val = comments_to_html(val) val = comments_to_html(val)
self.html = val self.html = val
self.wyswyg_dirtied()
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def initialize(self, db, id_): def initialize(self, db, id_):
@ -936,7 +942,11 @@ class IdentifiersEdit(QLineEdit): # {{{
ans = {} ans = {}
for x in parts: for x in parts:
c = x.split(':') c = x.split(':')
if len(c) == 2: if len(c) > 1:
if c[0] == 'isbn':
v = check_isbn(c[1])
if v is not None:
c[1] = v
ans[c[0]] = c[1] ans[c[0]] = c[1]
return ans return ans
def fset(self, val): def fset(self, val):
@ -947,6 +957,11 @@ class IdentifiersEdit(QLineEdit): # {{{
if x == 'isbn': if x == 'isbn':
x = '00isbn' x = '00isbn'
return x return x
for k in list(val):
if k == 'isbn':
v = check_isbn(k)
if v is not None:
val[k] = v
ids = sorted(val.iteritems(), key=keygen) ids = sorted(val.iteritems(), key=keygen)
txt = ', '.join(['%s:%s'%(k, v) for k, v in ids]) txt = ', '.join(['%s:%s'%(k, v) for k, v in ids])
self.setText(txt.strip()) self.setText(txt.strip())
@ -954,8 +969,8 @@ class IdentifiersEdit(QLineEdit): # {{{
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def initialize(self, db, id_): def initialize(self, db, id_):
self.current_val = db.get_identifiers(id_, index_is_id=True) self.original_val = db.get_identifiers(id_, index_is_id=True)
self.original_val = self.current_val self.current_val = self.original_val
def commit(self, db, id_): def commit(self, db, id_):
if self.original_val != self.current_val: if self.original_val != self.current_val:

View File

@ -41,8 +41,11 @@ class FieldsModel(FM): # {{{
self.reset() self.reset()
def commit(self): def commit(self):
val = [k for k, v in self.overrides.iteritems() if v == Qt.Unchecked] ignored_fields = set([x for x in self.prefs['ignore_fields'] if x not in
self.prefs['ignore_fields'] = val self.overrides])
changed = set([k for k, v in self.overrides.iteritems() if v ==
Qt.Unchecked])
self.prefs['ignore_fields'] = list(ignored_fields.union(changed))
# }}} # }}}

View File

@ -253,7 +253,7 @@ class ResultsView(QTableView): # {{{
parts.append('</center>') parts.append('</center>')
if book.identifiers: if book.identifiers:
urls = urls_from_identifiers(book.identifiers) urls = urls_from_identifiers(book.identifiers)
ids = ['<a href="%s">%s</a>'%(url, name) for name, url in urls] ids = ['<a href="%s">%s</a>'%(url, name) for name, ign, ign, url in urls]
if ids: if ids:
parts.append('<div><b>%s:</b> %s</div><br>'%(_('See at'), ', '.join(ids))) parts.append('<div><b>%s:</b> %s</div><br>'%(_('See at'), ', '.join(ids)))
if book.tags: if book.tags:

View File

@ -63,7 +63,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
self.shortcuts.linkActivated.connect(self.shortcut_activated) self.shortcuts.linkActivated.connect(self.shortcut_activated)
text = '<p>'+_('Quick create:') text = '<p>'+_('Quick create:')
for col, name in [('isbn', _('ISBN')), ('formats', _('Formats')), for col, name in [('isbn', _('ISBN')), ('formats', _('Formats')),
('last_modified', _('Modified Date')), ('yesno', _('Yes/No')), ('yesno', _('Yes/No')),
('tags', _('Tags')), ('series', _('Series')), ('rating', ('tags', _('Tags')), ('series', _('Series')), ('rating',
_('Rating')), ('people', _("People's names"))]: _('Rating')), ('people', _("People's names"))]:
text += ' <a href="col:%s">%s</a>,'%(col, name) text += ' <a href="col:%s">%s</a>,'%(col, name)
@ -150,7 +150,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
'tags': _('My Tags'), 'tags': _('My Tags'),
'series': _('My Series'), 'series': _('My Series'),
'rating': _('My Rating'), 'rating': _('My Rating'),
'last_modified':_('Modified Date'),
'people': _('People')}[which]) 'people': _('People')}[which])
self.is_names.setChecked(which == 'people') self.is_names.setChecked(which == 'people')
if self.composite_box.isVisible(): if self.composite_box.isVisible():
@ -158,9 +157,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
{ {
'isbn': '{identifiers:select(isbn)}', 'isbn': '{identifiers:select(isbn)}',
'formats': '{formats}', 'formats': '{formats}',
'last_modified':'''{last_modified:'format_date($, "dd MMM yyyy")'}'''
}[which]) }[which])
self.composite_sort_by.setCurrentIndex(2 if which == 'last_modified' else 0) self.composite_sort_by.setCurrentIndex(0)
def datatype_changed(self, *args): def datatype_changed(self, *args):
try: try:

View File

@ -235,6 +235,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
gui.library_view.refresh_book_details() gui.library_view.refresh_book_details()
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication([]) from calibre.gui2 import Application
app = Application([])
test_widget('Interface', 'Look & Feel') test_widget('Interface', 'Look & Feel')

View File

@ -7,28 +7,27 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>717</width> <width>717</width>
<height>444</height> <height>390</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2"> <item row="0" column="0">
<widget class="QToolBox" name="toolBox"> <widget class="QTabWidget" name="tabWidget">
<widget class="QWidget" name="page"> <property name="currentIndex">
<property name="geometry"> <number>0</number>
<rect> </property>
<x>0</x> <widget class="QWidget" name="tab">
<y>0</y> <attribute name="icon">
<width>699</width> <iconset resource="../../../../resources/images.qrc">
<height>306</height> <normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
</rect>
</property>
<attribute name="label">
<string>Main interface</string>
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_4"> <attribute name="title">
<string>Main Interface</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_17"> <widget class="QLabel" name="label_17">
<property name="text"> <property name="text">
@ -75,6 +74,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QCheckBox" name="opt_systray_icon">
<property name="text">
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QCheckBox" name="opt_disable_animations"> <widget class="QCheckBox" name="opt_disable_animations">
<property name="toolTip"> <property name="toolTip">
@ -85,6 +91,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QCheckBox" name="opt_disable_tray_notification">
<property name="text">
<string>Disable &amp;notifications in system tray</string>
</property>
</widget>
</item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QCheckBox" name="opt_show_splash_screen"> <widget class="QCheckBox" name="opt_show_splash_screen">
<property name="text"> <property name="text">
@ -97,12 +110,12 @@
<property name="title"> <property name="title">
<string>&amp;Toolbar</string> <string>&amp;Toolbar</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="opt_toolbar_icon_size"/> <widget class="QComboBox" name="opt_toolbar_icon_size"/>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>&amp;Icon size:</string> <string>&amp;Icon size:</string>
</property> </property>
@ -115,7 +128,7 @@
<widget class="QComboBox" name="opt_toolbar_text"/> <widget class="QComboBox" name="opt_toolbar_text"/>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>Show &amp;text under icons:</string> <string>Show &amp;text under icons:</string>
</property> </property>
@ -127,20 +140,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="4" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
@ -161,135 +161,15 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="6" column="1"> <item row="4" column="1">
<widget class="QPushButton" name="change_font_button"> <widget class="QPushButton" name="change_font_button">
<property name="text"> <property name="text">
<string>Change &amp;font (needs restart)</string> <string>Change &amp;font (needs restart)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="6" column="0">
<widget class="QCheckBox" name="opt_systray_icon"> <spacer name="verticalSpacer_3">
<property name="text">
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="opt_disable_tray_notification">
<property name="text">
<string>Disable &amp;notifications in system tray</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>649</width>
<height>96</height>
</rect>
</property>
<attribute name="label">
<string>Tag Browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="opt_show_avg_rating">
<property name="text">
<string>Show &amp;average ratings in the tags browser</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tags browser category &amp;partitioning method:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_partition_method</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_tags_browser_partition_method">
<property name="toolTip">
<string>Choose how tag browser subcategories are displayed when
there are more items than the limit. Select by first
letter to see an A, B, C list. Choose partitioned to
have a list of fixed-sized groups. Set to disabled
if you never want subcategories</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Collapse when more items than:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="opt_tags_browser_collapse_at">
<property name="toolTip">
<string>If a Tag Browser category has more than this number of items, it is divided
up into sub-categories. If the partition method is set to disable, this value is ignored.</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_81">
<property name="text">
<string>Categories with &amp;hierarchical items:</string>
</property>
<property name="buddy">
<cstring>opt_categories_using_hierarchy</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of columns in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
@ -303,77 +183,15 @@ then the tags will be displayed each on their own line.</string>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page_3"> <widget class="QWidget" name="tab_4">
<property name="geometry"> <attribute name="icon">
<rect> <iconset resource="../../../../resources/images.qrc">
<x>0</x> <normaloff>:/images/book.png</normaloff>:/images/book.png</iconset>
<y>0</y>
<width>429</width>
<height>63</height>
</rect>
</property>
<attribute name="label">
<string>Cover Browser</string>
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_6"> <attribute name="title">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_cover_flow_queue_length</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item>
<item row="2" column="0" colspan="2">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>699</width>
<height>306</height>
</rect>
</property>
<attribute name="label">
<string>Book Details</string> <string>Book Details</string>
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_7"> <layout class="QGridLayout" name="gridLayout_12">
<item row="0" column="1">
<widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number">
<property name="text">
<string>Use &amp;Roman numerals for series</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2"> <item row="0" column="0" rowspan="2">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
@ -425,6 +243,16 @@ then the tags will be displayed each on their own line.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="1">
<widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number">
<property name="text">
<string>Use &amp;Roman numerals for series</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
@ -437,6 +265,148 @@ then the tags will be displayed each on their own line.</string>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_2">
<attribute name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/tags.png</normaloff>:/images/tags.png</iconset>
</attribute>
<attribute name="title">
<string>Tag Browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Tags browser category &amp;partitioning method:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_partition_method</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="opt_tags_browser_partition_method">
<property name="toolTip">
<string>Choose how tag browser subcategories are displayed when
there are more items than the limit. Select by first
letter to see an A, B, C list. Choose partitioned to
have a list of fixed-sized groups. Set to disabled
if you never want subcategories</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_10">
<property name="text">
<string>&amp;Collapse when more items than:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QSpinBox" name="opt_tags_browser_collapse_at">
<property name="toolTip">
<string>If a Tag Browser category has more than this number of items, it is divided
up into sub-categories. If the partition method is set to disable, this value is ignored.</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item row="1" column="0" colspan="5">
<widget class="QCheckBox" name="opt_show_avg_rating">
<property name="text">
<string>Show &amp;average ratings in the tags browser</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_81">
<property name="text">
<string>Categories with &amp;hierarchical items:</string>
</property>
<property name="buddy">
<cstring>opt_categories_using_hierarchy</cstring>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>252</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2" colspan="3">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of columns in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/cover_flow.png</normaloff>:/images/cover_flow.png</iconset>
</attribute>
<attribute name="title">
<string>Cover Browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="opt_separate_cover_flow">
<property name="text">
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_cover_flow_queue_length</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="opt_cover_flow_queue_length"/>
</item>
<item row="2" column="0" colspan="2">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>283</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -89,7 +89,7 @@ class Category(QWidget): # {{{
self.bar = QToolBar(self) self.bar = QToolBar(self)
self.bar.setStyleSheet( self.bar.setStyleSheet(
'QToolBar { border: none; background: none }') 'QToolBar { border: none; background: none }')
self.bar.setIconSize(QSize(48, 48)) self.bar.setIconSize(QSize(32, 32))
self.bar.setMovable(False) self.bar.setMovable(False)
self.bar.setFloatable(False) self.bar.setFloatable(False)
self.bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

View File

@ -209,8 +209,11 @@ class FieldsModel(QAbstractListModel): # {{{
return ret return ret
def commit(self): def commit(self):
val = [k for k, v in self.overrides.iteritems() if v == Qt.Unchecked] ignored_fields = set([x for x in msprefs['ignore_fields'] if x not in
msprefs['ignore_fields'] = val self.overrides])
changed = set([k for k, v in self.overrides.iteritems() if v ==
Qt.Unchecked])
msprefs['ignore_fields'] = list(ignored_fields.union(changed))
# }}} # }}}

View File

@ -7,12 +7,15 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re import re
from functools import partial
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, QDialog, \ from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, QDialog, \
pyqtSignal, QCompleter, QAction, QKeySequence, QTimer, \ pyqtSignal, QCompleter, QAction, QKeySequence, QTimer, \
QString, QIcon QString, QIcon, QMenu
from calibre.gui2 import config from calibre.gui2 import config, error_dialog
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
from calibre.gui2.dialogs.search import SearchDialog from calibre.gui2.dialogs.search import SearchDialog
from calibre.utils.search_query_parser import saved_searches from calibre.utils.search_query_parser import saved_searches
@ -330,6 +333,24 @@ class SavedSearchBox(QComboBox): # {{{
self.saved_search_selected (name) self.saved_search_selected (name)
self.changed.emit() self.changed.emit()
def delete_current_search(self):
idx = self.currentIndex()
if idx <= 0:
error_dialog(self, _('Delete current search'),
_('No search is selected'), show=True)
return
if not confirm('<p>'+_('The selected search will be '
'<b>permanently deleted</b>. Are you sure?')
+'</p>', 'saved_search_delete', self):
return
ss = saved_searches().lookup(unicode(self.currentText()))
if ss is None:
return
saved_searches().delete(unicode(self.currentText()))
self.clear()
self.search_box.clear()
self.changed.emit()
# SIGNALed from the main UI # SIGNALed from the main UI
def copy_search_button_clicked (self): def copy_search_button_clicked (self):
idx = self.currentIndex(); idx = self.currentIndex();
@ -428,6 +449,22 @@ class SavedSearchBoxMixin(object): # {{{
for x in ('copy', 'save'): for x in ('copy', 'save'):
b = getattr(self, x+'_search_button') b = getattr(self, x+'_search_button')
b.setStatusTip(b.toolTip()) b.setStatusTip(b.toolTip())
self.save_search_button.setToolTip('<p>' +
_("Save current search under the name shown in the box. "
"Press and hold for a pop-up options menu.") + '</p>')
self.save_search_button.setMenu(QMenu())
self.save_search_button.menu().addAction(
QIcon(I('plus.png')),
_('Create saved search'),
self.saved_search.save_search_button_clicked)
self.save_search_button.menu().addAction(
QIcon(I('trash.png')),
_('Delete saved search'),
self.saved_search.delete_current_search)
self.save_search_button.menu().addAction(
QIcon(I('search.png')),
_('Manage saved searches'),
partial(self.do_saved_search_edit, None))
def saved_searches_changed(self, set_restriction=None, recount=True): def saved_searches_changed(self, set_restriction=None, recount=True):
p = sorted(saved_searches().names(), key=sort_key) p = sorted(saved_searches().names(), key=sort_key)

View File

@ -33,24 +33,21 @@
<enum>QFrame::Raised</enum> <enum>QFrame::Raised</enum>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="1" column="1">
<widget class="QWebView" name="view"/>
</item>
<item row="0" column="1">
<widget class="QScrollBar" name="vertical_scrollbar"> <widget class="QScrollBar" name="vertical_scrollbar">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="2" column="0">
<widget class="QScrollBar" name="horizontal_scrollbar"> <widget class="QScrollBar" name="horizontal_scrollbar">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="3" column="0" colspan="2">
<widget class="QFrame" name="dictionary_box"> <widget class="QFrame" name="dictionary_box">
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>
@ -91,6 +88,9 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="DocumentView" name="view" native="true"/>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@ -108,7 +108,7 @@
</size> </size>
</property> </property>
<attribute name="toolBarArea"> <attribute name="toolBarArea">
<enum>Qt::LeftToolBarArea</enum> <enum>LeftToolBarArea</enum>
</attribute> </attribute>
<attribute name="toolBarBreak"> <attribute name="toolBarBreak">
<bool>false</bool> <bool>false</bool>
@ -136,7 +136,7 @@
</widget> </widget>
<widget class="QToolBar" name="tool_bar2"> <widget class="QToolBar" name="tool_bar2">
<attribute name="toolBarArea"> <attribute name="toolBarArea">
<enum>Qt::TopToolBarArea</enum> <enum>TopToolBarArea</enum>
</attribute> </attribute>
<attribute name="toolBarBreak"> <attribute name="toolBarBreak">
<bool>false</bool> <bool>false</bool>
@ -316,6 +316,12 @@
<extends>QWidget</extends> <extends>QWidget</extends>
<header>QtWebKit/QWebView</header> <header>QtWebKit/QWebView</header>
</customwidget> </customwidget>
<customwidget>
<class>DocumentView</class>
<extends>QWidget</extends>
<header>calibre/gui2/viewer/documentview.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -406,11 +406,9 @@ class ResultCache(SearchQueryParser): # {{{
if val_func is None: if val_func is None:
loc = self.field_metadata[location]['rec_index'] loc = self.field_metadata[location]['rec_index']
val_func = lambda item, loc=loc: item[loc] val_func = lambda item, loc=loc: item[loc]
dt = self.field_metadata[location]['datatype']
q = '' q = ''
val_func = lambda item, loc=loc: item[loc]
cast = adjust = lambda x: x cast = adjust = lambda x: x
dt = self.field_metadata[location]['datatype']
if query == 'false': if query == 'false':
if dt == 'rating' or location == 'cover': if dt == 'rating' or location == 'cover':

View File

@ -368,7 +368,8 @@ class FieldMetadata(dict):
'date_format': tweaks['gui_timestamp_display_format']} 'date_format': tweaks['gui_timestamp_display_format']}
self._tb_cats['pubdate']['display'] = { self._tb_cats['pubdate']['display'] = {
'date_format': tweaks['gui_pubdate_display_format']} 'date_format': tweaks['gui_pubdate_display_format']}
self._tb_cats['last_modified']['display'] = {'date_format': 'iso'} self._tb_cats['last_modified']['display'] = {
'date_format': tweaks['gui_last_modified_display_format']}
self.custom_field_prefix = '#' self.custom_field_prefix = '#'
self.get = self._tb_cats.get self.get = self._tb_cats.get

View File

@ -17,6 +17,7 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.constants import preferred_encoding from calibre.constants import preferred_encoding
from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import fmt_sidx
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort
from calibre.utils.date import parse_date
from calibre import strftime, prints, sanitize_file_name_unicode from calibre import strftime, prints, sanitize_file_name_unicode
plugboard_any_device_value = 'any device' plugboard_any_device_value = 'any device'
@ -42,6 +43,8 @@ FORMAT_ARG_DESCS = dict(
publisher=_('The publisher'), publisher=_('The publisher'),
timestamp=_('The date'), timestamp=_('The date'),
pubdate=_('The published date'), pubdate=_('The published date'),
last_modified=_('The date when the metadata for this book record'
' was last modified'),
id=_('The calibre internal id') id=_('The calibre internal id')
) )
@ -191,6 +194,9 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple()) format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple())
if hasattr(mi.pubdate, 'timetuple'): if hasattr(mi.pubdate, 'timetuple'):
format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple()) format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple())
if hasattr(mi, 'last_modified') and hasattr(mi.last_modified, 'timetuple'):
format_args['last_modified'] = strftime(timefmt, mi.last_modified.timetuple())
format_args['id'] = str(id) format_args['id'] = str(id)
# Now format the custom fields # Now format the custom fields
custom_metadata = mi.get_all_user_metadata(make_copy=False) custom_metadata = mi.get_all_user_metadata(make_copy=False)
@ -373,10 +379,14 @@ def save_serialized_to_disk(ids, data, plugboards, root, opts, callback):
root, opts, length = _sanitize_args(root, opts) root, opts, length = _sanitize_args(root, opts)
failures = [] failures = []
for x in ids: for x in ids:
opf, cover, format_map = data[x] opf, cover, format_map, last_modified = data[x]
if isinstance(opf, unicode): if isinstance(opf, unicode):
opf = opf.encode('utf-8') opf = opf.encode('utf-8')
mi = OPF(cStringIO.StringIO(opf)).to_book_metadata() mi = OPF(cStringIO.StringIO(opf)).to_book_metadata()
try:
mi.last_modified = parse_date(last_modified)
except:
pass
tb = '' tb = ''
try: try:
failed, id, title = do_save_book_to_disk(x, mi, cover, plugboards, failed, id, title = do_save_book_to_disk(x, mi, cover, plugboards,

View File

@ -20,13 +20,14 @@ What formats does |app| support conversion to/from?
|app| supports the conversion of many input formats to many output formats. |app| supports the conversion of many input formats to many output formats.
It can convert every input format in the following list, to every output format. It can convert every input format in the following list, to every output format.
*Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC**, PDB***, PML, RB, RTF, SNB, TCR, TXT, TXTZ *Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ
*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, SNB, TCR, TXT, TXTZ *Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, SNB, TCR, TXT, TXTZ
** PRC is a generic format, |app| supports PRC files with TextRead and MOBIBook headers .. note ::
*** PDB is also a generic format. |app| supports eReder, Plucker, PML and zTxt PDB files. PRC is a generic format, |app| supports PRC files with TextRead and MOBIBook headers.
PDB is also a generic format. |app| supports eReder, Plucker, PML and zTxt PDB files.
.. _best-source-formats: .. _best-source-formats:

View File

@ -365,6 +365,8 @@ Dates and numeric fields support the relational operators ``=`` (equals), ``>``
Rating fields are considered to be numeric. For example, the search ``rating:>=3`` will find all books rated 3 Rating fields are considered to be numeric. For example, the search ``rating:>=3`` will find all books rated 3
or higher. or higher.
You can search for the number of items in multiple-valued fields such as tags). These searches begin with the character ``#``, then use the same syntax as numeric fields. For example, to find all books with more than 4 tags, use ``tags:#>4``. To find all books with exactly 10 tags, use ``tags:#=10``.
Series indices are searchable. For the standard series, the search name is 'series_index'. For Series indices are searchable. For the standard series, the search name is 'series_index'. For
custom series columns, use the column search name followed by _index. For example, to search the indices for a custom series columns, use the column search name followed by _index. For example, to search the indices for a
custom series column named ``#my_series``, you would use the search name ``#my_series_index``. custom series column named ``#my_series``, you would use the search name ``#my_series_index``.

View File

@ -65,17 +65,14 @@ Catalog plugins
Metadata download plugins Metadata download plugins
-------------------------- --------------------------
.. module:: calibre.ebooks.metadata.fetch .. module:: calibre.ebooks.metadata.sources.base
.. autoclass:: MetadataSource .. autoclass:: Source
:show-inheritance: :show-inheritance:
:members: :members:
:member-order: bysource :member-order: bysource
.. autoclass:: calibre.ebooks.metadata.covers.CoverDownload .. autoclass:: InternalMetadataCompareKeyGen
:show-inheritance:
:members:
:member-order: bysource
Conversion plugins Conversion plugins
-------------------- --------------------