diff --git a/recipes/le_monde.recipe b/recipes/le_monde.recipe index 3c47d81ee1..cf1f858dfe 100644 --- a/recipes/le_monde.recipe +++ b/recipes/le_monde.recipe @@ -48,7 +48,7 @@ class LeMonde(BasicNewsRecipe): if alink.string is not None: tstr = alink.string alink.replaceWith(tstr) - return soup + return self.adeify_images(soup) preprocess_regexps = [ (re.compile(r'([0-9])%'), lambda m: m.group(1) + ' %'), diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 08017b5c98..e91b4a62d5 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -118,6 +118,7 @@ sort_columns_at_startup = None # timestamp default if not set: dd MMM yyyy gui_pubdate_display_format = '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 title and series sorting in the library view. If set to diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst index ce6ca650a4..11b5bccf79 100644 --- a/setup/installer/windows/notes.rst +++ b/setup/installer/windows/notes.rst @@ -69,7 +69,24 @@ nmake -f ms\ntdll.mak install 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 diff --git a/setup/installer/windows/wix-template.xml b/setup/installer/windows/wix-template.xml index 37dd8b25a8..b5d2f4b292 100644 --- a/setup/installer/windows/wix-template.xml +++ b/setup/installer/windows/wix-template.xml @@ -11,7 +11,10 @@ SummaryCodepage='1252' /> - + + + )\s*file:/{2,4}[A-Z].*
|file:////?[A-Z].*
(?=\s*
))', re.IGNORECASE), lambda match: ''), # Center separator lines - (re.compile(u'
\s*(?P([*#•✦=]+\s*)+)\s*
'), lambda match: '

\n

' + match.group(1) + '

'), + (re.compile(u'
\s*(?P([*#•✦=] *){3,})\s*
'), lambda match: '

\n

' + match.group('break') + '

'), # Remove page links (re.compile(r'', re.IGNORECASE), lambda match: ''), diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 5f1841d518..3099de12e4 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -120,7 +120,11 @@ class Metadata(object): _('TEMPLATE ERROR'), self).strip() return val - + if field.startswith('#') and field.endswith('_index'): + try: + return self.get_extra(field[:-6]) + except: + pass raise AttributeError( 'Metadata object has no attribute named: '+ repr(field)) @@ -170,11 +174,6 @@ class Metadata(object): try: return self.__getattribute__(field) except AttributeError: - if field.startswith('#') and field.endswith('_index'): - try: - return self.get_extra(field[:-6]) - except: - pass return default 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')) elif datatype == 'rating': res = res/2.0 - elif key in ('book_size', 'size'): + elif key == 'size': res = human_readable(res) return (name, unicode(res), orig_res, fmeta) diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index 15fdceff15..74db3b3a58 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -400,7 +400,8 @@ class MetadataUpdater(object): if getattr(self, 'exth', None) is None: 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) # Fetch updated timestamp, cover_record, thumbnail_record diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 24df68e51d..8483698e28 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -301,7 +301,7 @@ class Amazon(Source): if asin is None: asin = identifiers.get('asin', None) 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={}): # {{{ diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index eb0277bd3f..e67b87efbd 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -56,7 +56,8 @@ class InternalMetadataCompareKeyGen(object): ''' 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 decreasing relevance. @@ -374,7 +375,11 @@ class Source(Plugin): 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 to provide a clickable link for the user to easily visit the books page at this source. diff --git a/src/calibre/ebooks/metadata/sources/google.py b/src/calibre/ebooks/metadata/sources/google.py index 4133d4d527..b479368bac 100644 --- a/src/calibre/ebooks/metadata/sources/google.py +++ b/src/calibre/ebooks/metadata/sources/google.py @@ -173,7 +173,7 @@ class GoogleBooks(Source): def get_book_url(self, identifiers): # {{{ goog = identifiers.get('google', 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={}): # {{{ diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 8771274f92..9a9e5aa164 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -435,18 +435,30 @@ def identify(log, abort, # {{{ # }}} def urls_from_identifiers(identifiers): # {{{ + identifiers = dict([(k.lower(), v) for k, v in identifiers.iteritems()]) ans = [] for plugin in all_metadata_plugins(): try: - url = plugin.get_book_url(identifiers) - if url is not None: - ans.append((plugin.name, url)) + id_type, id_val, url = plugin.get_book_url(identifiers) + ans.append((plugin.name, id_type, id_val, url)) except: pass isbn = identifiers.get('isbn', None) if isbn: - ans.append((isbn, - 'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn)) + ans.append((isbn, 'isbn', 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 # }}} diff --git a/src/calibre/ebooks/metadata/sources/overdrive.py b/src/calibre/ebooks/metadata/sources/overdrive.py index 67eac7e337..b2ea36c0ac 100755 --- a/src/calibre/ebooks/metadata/sources/overdrive.py +++ b/src/calibre/ebooks/metadata/sources/overdrive.py @@ -272,7 +272,7 @@ class OverDrive(Source): creators = creators.split(', ') # 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, creators, thumbimage, worldcatlink, formatid) 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)) else: 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]: close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) diff --git a/src/calibre/ebooks/metadata/worker.py b/src/calibre/ebooks/metadata/worker.py index d059d7e34c..c335cc4c13 100644 --- a/src/calibre/ebooks/metadata/worker.py +++ b/src/calibre/ebooks/metadata/worker.py @@ -222,7 +222,7 @@ class SaveWorker(Thread): if isbytestring(fpath): fpath = fpath.decode(filesystem_encoding) formats[fmt.lower()] = fpath - data[i] = [opf, cpath, formats] + data[i] = [opf, cpath, formats, mi.last_modified.isoformat()] return data def run(self): diff --git a/src/calibre/ebooks/pdb/plucker/reader.py b/src/calibre/ebooks/pdb/plucker/reader.py index 39ceb33b13..d782e4e97c 100644 --- a/src/calibre/ebooks/pdb/plucker/reader.py +++ b/src/calibre/ebooks/pdb/plucker/reader.py @@ -16,6 +16,7 @@ from calibre import CurrentDir from calibre.ebooks.pdb.formatreader import FormatReader from calibre.ptempfile import TemporaryFile from calibre.utils.magick import Image, create_canvas +from calibre.ebooks.compression.palmdoc import decompress_doc DATATYPE_PHTML = 0 DATATYPE_PHTML_COMPRESSED = 1 diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index f1357728ec..60d2a0a7dd 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -739,12 +739,6 @@ def build_forms(srcdir, info=None): dat = dat.replace('from QtWebKit.QWebView 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) _df = os.environ.get('CALIBRE_DEVELOP_FROM', None) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index a2ee6d343b..f94e179166 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -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.metadata.book.base import (field_metadata, Metadata) 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.library.comments import comments_to_html from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data, gprefs) from calibre.utils.icu import sort_key - def render_html(mi, css, vertical, widget, all_fields=False): # {{{ table = render_data(mi, all_fields=all_fields, 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'%s%s'%(name, u', '.join(fmts)))) elif field == 'identifiers': - pass # TODO + urls = urls_from_identifiers(mi.identifiers) + links = [u'%s' % (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'%s%s'%( + _('Ids')+':', links))) else: val = mi.format_field(field)[-1] if val is None: continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': - if metadata['is_custom']: - sidx = mi.get_extra(field) - else: - sidx = getattr(mi, field+'_index') + sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 val = _('Book %s of %s')%(fmt_sidx(sidx, @@ -292,6 +295,8 @@ class BookInfo(QWebView): def link_activated(self, link): self._link_clicked = True + if unicode(link.scheme()) in ('http', 'https'): + return open_url(link) link = unicode(link.toString()) self.link_clicked.emit(link) diff --git a/src/calibre/gui2/dialogs/confirm_delete_location.ui b/src/calibre/gui2/dialogs/confirm_delete_location.ui index 9d70388627..212d96584f 100644 --- a/src/calibre/gui2/dialogs/confirm_delete_location.ui +++ b/src/calibre/gui2/dialogs/confirm_delete_location.ui @@ -22,6 +22,12 @@ + + + 0 + 0 + + :/images/dialog_warning.png @@ -46,6 +52,10 @@ Library + + + :/images/library.png:/images/library.png + @@ -53,6 +63,10 @@ Device + + + :/images/reader.png:/images/reader.png + @@ -60,6 +74,10 @@ Library and Device + + + :/images/trash.png:/images/trash.png + diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 7250103615..b5cc0163ed 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -214,7 +214,6 @@ class SearchBar(QWidget): # {{{ x.setIcon(QIcon(I("search_add_saved.png"))) x.setObjectName("save_search_button") l.addWidget(x) - x.setToolTip(_("Save current search under the name shown in the box")) # }}} diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index ac8568af07..e2234f6df5 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -97,18 +97,25 @@ class RatingDelegate(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): d = val.toDate() if d <= UNDEFINED_QDATE: return '' - format = tweaks['gui_timestamp_display_format'] + format = tweaks[self.tweak_name] if format is None: - format = 'dd MMM yyyy' + format = self.default_format return format_date(d.toPyDate(), format) def 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.setSpecialValueText(_('Undefined')) qde.setCalendarPopup(True) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 8b830d2ec2..dd5082c27f 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -70,6 +70,7 @@ class BooksModel(QAbstractTableModel): # {{{ 'publisher' : _("Publisher"), 'tags' : _("Tags"), 'series' : _("Series"), + 'last_modified' : _('Modified'), } def __init__(self, parent=None, buffer=40): @@ -620,6 +621,8 @@ class BooksModel(QAbstractTableModel): # {{{ idx=self.db.field_metadata['timestamp']['rec_index']), 'pubdate' : functools.partial(datetime_type, 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, idx=self.db.field_metadata['rating']['rec_index']), 'publisher': functools.partial(text_type, diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 921e62d4c3..26f6788a75 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -76,6 +76,8 @@ class BooksView(QTableView): # {{{ self.rating_delegate = RatingDelegate(self) self.timestamp_delegate = DateDelegate(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.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True) self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True) @@ -296,6 +298,7 @@ class BooksView(QTableView): # {{{ state = {} state['hidden_columns'] = [cm[i] for i in range(h.count()) if h.isSectionHidden(i) and cm[i] != 'ondevice'] + state['last_modified_injected'] = True state['sort_history'] = \ self.cleanup_sort_history(self.model().sort_history) state['column_positions'] = {} @@ -380,7 +383,7 @@ class BooksView(QTableView): # {{{ def get_default_state(self): old_state = { - 'hidden_columns': [], + 'hidden_columns': ['last_modified'], 'sort_history':[DEFAULT_SORT], 'column_positions': {}, 'column_sizes': {}, @@ -388,6 +391,7 @@ class BooksView(QTableView): # {{{ 'size':'center', 'timestamp':'center', 'pubdate':'center'}, + 'last_modified_injected': True, } h = self.column_header cm = self.column_map @@ -398,7 +402,7 @@ class BooksView(QTableView): # {{{ old_state['column_sizes'][name] = \ min(350, max(self.sizeHintForColumn(i), h.sectionSizeHint(i))) - if name == 'timestamp': + if name in ('timestamp', 'last_modified'): old_state['column_sizes'][name] += 12 return old_state @@ -418,6 +422,13 @@ class BooksView(QTableView): # {{{ pass if ans is not None: 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 @@ -459,7 +470,8 @@ class BooksView(QTableView): # {{{ def database_changed(self, db): for i in range(self.model().columnCount(None)): 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()) cm = self.column_map diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index d34be6c564..1620734209 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -18,11 +18,11 @@ from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.utils.icu import sort_key from calibre.utils.config import tweaks, prefs -from calibre.ebooks.metadata import title_sort, authors_to_string, \ - string_to_authors, check_isbn +from calibre.ebooks.metadata import (title_sort, authors_to_string, + string_to_authors, check_isbn) from calibre.ebooks.metadata.meta import get_metadata -from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \ - choose_files, error_dialog, choose_images, question_dialog +from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, + choose_files, error_dialog, choose_images, question_dialog) from calibre.utils.date import local_tz, qt_to_dt from calibre import strftime from calibre.ebooks import BOOK_EXTENSIONS @@ -280,11 +280,16 @@ class AuthorSortEdit(EnLineEdit): aus = self.current_val meth = tweaks['author_sort_copy_method'] if aus: - ln, _, rest = aus.partition(',') - if rest: - if meth in ('invert', 'nocomma', 'comma'): - aus = rest.strip() + ' ' + ln.strip() - self.authors_edit.current_val = [aus] + ans = [] + for one in [a.strip() for a in aus.split('&')]: + if not one: + continue + 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): au = unicode(self.authors_edit.text()) @@ -805,6 +810,7 @@ class CommentsEdit(Editor): # {{{ else: val = comments_to_html(val) self.html = val + self.wyswyg_dirtied() return property(fget=fget, fset=fset) def initialize(self, db, id_): @@ -936,7 +942,11 @@ class IdentifiersEdit(QLineEdit): # {{{ ans = {} for x in parts: 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] return ans def fset(self, val): @@ -947,6 +957,11 @@ class IdentifiersEdit(QLineEdit): # {{{ if x == 'isbn': x = '00isbn' 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) txt = ', '.join(['%s:%s'%(k, v) for k, v in ids]) self.setText(txt.strip()) @@ -954,8 +969,8 @@ class IdentifiersEdit(QLineEdit): # {{{ return property(fget=fget, fset=fset) def initialize(self, db, id_): - self.current_val = db.get_identifiers(id_, index_is_id=True) - self.original_val = self.current_val + self.original_val = db.get_identifiers(id_, index_is_id=True) + self.current_val = self.original_val def commit(self, db, id_): if self.original_val != self.current_val: diff --git a/src/calibre/gui2/metadata/config.py b/src/calibre/gui2/metadata/config.py index abb45faa46..cb51324ae0 100644 --- a/src/calibre/gui2/metadata/config.py +++ b/src/calibre/gui2/metadata/config.py @@ -41,8 +41,11 @@ class FieldsModel(FM): # {{{ self.reset() def commit(self): - val = [k for k, v in self.overrides.iteritems() if v == Qt.Unchecked] - self.prefs['ignore_fields'] = val + ignored_fields = set([x for x in self.prefs['ignore_fields'] if x not in + 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)) # }}} diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 06ea8cf76a..cc89ef2259 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -253,7 +253,7 @@ class ResultsView(QTableView): # {{{ parts.append('') if book.identifiers: urls = urls_from_identifiers(book.identifiers) - ids = ['%s'%(url, name) for name, url in urls] + ids = ['%s'%(url, name) for name, ign, ign, url in urls] if ids: parts.append('
%s: %s

'%(_('See at'), ', '.join(ids))) if book.tags: diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 433f8fd222..7b891b782c 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -63,7 +63,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): self.shortcuts.linkActivated.connect(self.shortcut_activated) text = '

'+_('Quick create:') for col, name in [('isbn', _('ISBN')), ('formats', _('Formats')), - ('last_modified', _('Modified Date')), ('yesno', _('Yes/No')), + ('yesno', _('Yes/No')), ('tags', _('Tags')), ('series', _('Series')), ('rating', _('Rating')), ('people', _("People's names"))]: text += ' %s,'%(col, name) @@ -150,7 +150,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 'tags': _('My Tags'), 'series': _('My Series'), 'rating': _('My Rating'), - 'last_modified':_('Modified Date'), 'people': _('People')}[which]) self.is_names.setChecked(which == 'people') if self.composite_box.isVisible(): @@ -158,9 +157,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): { 'isbn': '{identifiers:select(isbn)}', 'formats': '{formats}', - 'last_modified':'''{last_modified:'format_date($, "dd MMM yyyy")'}''' }[which]) - self.composite_sort_by.setCurrentIndex(2 if which == 'last_modified' else 0) + self.composite_sort_by.setCurrentIndex(0) def datatype_changed(self, *args): try: diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index bae08f5455..620113cc3f 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -235,6 +235,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): gui.library_view.refresh_book_details() if __name__ == '__main__': - app = QApplication([]) + from calibre.gui2 import Application + app = Application([]) test_widget('Interface', 'Look & Feel') diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 3d8a818e1e..244b811cbd 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -7,28 +7,27 @@ 0 0 717 - 444 + 390 Form - - - - - - 0 - 0 - 699 - 306 - - - - Main interface + + + + 0 + + + + + :/images/lt.png:/images/lt.png - + + Main Interface + + @@ -75,6 +74,13 @@ + + + + Enable system &tray icon (needs restart) + + + @@ -85,6 +91,13 @@ + + + + Disable &notifications in system tray + + + @@ -97,12 +110,12 @@ &Toolbar - + - + &Icon size: @@ -115,7 +128,7 @@ - + Show &text under icons: @@ -127,20 +140,7 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - + @@ -161,135 +161,15 @@ - + Change &font (needs restart) - - - - Enable system &tray icon (needs restart) - - - - - - - Disable &notifications in system tray - - - - - - - - - 0 - 0 - 649 - 96 - - - - Tag Browser - - - - - - Show &average ratings in the tags browser - - - true - - - - - - - - - Tags browser category &partitioning method: - - - opt_tags_browser_partition_method - - - - - - - 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 - - - - - - - &Collapse when more items than: - - - opt_tags_browser_collapse_at - - - - - - - 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. - - - 10000 - - - - - - - Qt::Horizontal - - - - 20 - 5 - - - - - - - - - - Categories with &hierarchical items: - - - opt_categories_using_hierarchy - - - - - - - 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. - - - - - + + Qt::Vertical @@ -303,77 +183,15 @@ then the tags will be displayed each on their own line. - - - - 0 - 0 - 429 - 63 - - - - Cover Browser + + + + :/images/book.png:/images/book.png - - - - - Show cover &browser in a separate window (needs restart) - - - - - - - &Number of covers to show in browse mode (needs restart): - - - opt_cover_flow_queue_length - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - 0 - 0 - 699 - 306 - - - + Book Details - - - - - Use &Roman numerals for series - - - true - - - + @@ -425,6 +243,16 @@ then the tags will be displayed each on their own line. + + + + Use &Roman numerals for series + + + true + + + @@ -437,6 +265,148 @@ then the tags will be displayed each on their own line. + + + + :/images/tags.png:/images/tags.png + + + Tag Browser + + + + + + Tags browser category &partitioning method: + + + opt_tags_browser_partition_method + + + + + + + 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 + + + + + + + &Collapse when more items than: + + + opt_tags_browser_collapse_at + + + + + + + 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. + + + 10000 + + + + + + + Show &average ratings in the tags browser + + + true + + + + + + + Categories with &hierarchical items: + + + opt_categories_using_hierarchy + + + + + + + Qt::Vertical + + + + 690 + 252 + + + + + + + + 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. + + + + + + + + + :/images/cover_flow.png:/images/cover_flow.png + + + Cover Browser + + + + + + Show cover &browser in a separate window (needs restart) + + + + + + + &Number of covers to show in browse mode (needs restart): + + + opt_cover_flow_queue_length + + + + + + + + + + Qt::Vertical + + + + 690 + 283 + + + + + + diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index e760aa018a..c5f9a11d16 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -89,7 +89,7 @@ class Category(QWidget): # {{{ self.bar = QToolBar(self) self.bar.setStyleSheet( 'QToolBar { border: none; background: none }') - self.bar.setIconSize(QSize(48, 48)) + self.bar.setIconSize(QSize(32, 32)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) diff --git a/src/calibre/gui2/preferences/metadata_sources.py b/src/calibre/gui2/preferences/metadata_sources.py index 4a6c7cfc06..f487051d07 100644 --- a/src/calibre/gui2/preferences/metadata_sources.py +++ b/src/calibre/gui2/preferences/metadata_sources.py @@ -209,8 +209,11 @@ class FieldsModel(QAbstractListModel): # {{{ return ret def commit(self): - val = [k for k, v in self.overrides.iteritems() if v == Qt.Unchecked] - msprefs['ignore_fields'] = val + ignored_fields = set([x for x in msprefs['ignore_fields'] if x not in + self.overrides]) + changed = set([k for k, v in self.overrides.iteritems() if v == + Qt.Unchecked]) + msprefs['ignore_fields'] = list(ignored_fields.union(changed)) # }}} diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index c349d84a68..19cfb7417e 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -7,12 +7,15 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import re +from functools import partial + from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, QDialog, \ 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.search import SearchDialog from calibre.utils.search_query_parser import saved_searches @@ -330,6 +333,24 @@ class SavedSearchBox(QComboBox): # {{{ self.saved_search_selected (name) 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('

'+_('The selected search will be ' + 'permanently deleted. Are you sure?') + +'

', '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 def copy_search_button_clicked (self): idx = self.currentIndex(); @@ -428,6 +449,22 @@ class SavedSearchBoxMixin(object): # {{{ for x in ('copy', 'save'): b = getattr(self, x+'_search_button') b.setStatusTip(b.toolTip()) + self.save_search_button.setToolTip('

' + + _("Save current search under the name shown in the box. " + "Press and hold for a pop-up options menu.") + '

') + 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): p = sorted(saved_searches().names(), key=sort_key) diff --git a/src/calibre/gui2/viewer/main.ui b/src/calibre/gui2/viewer/main.ui index 04166fe2cf..3137ad2e07 100644 --- a/src/calibre/gui2/viewer/main.ui +++ b/src/calibre/gui2/viewer/main.ui @@ -33,24 +33,21 @@ QFrame::Raised
- - - - + Qt::Vertical - + Qt::Horizontal - + QFrame::StyledPanel @@ -91,6 +88,9 @@ + + + @@ -108,7 +108,7 @@ - Qt::LeftToolBarArea + LeftToolBarArea false @@ -136,7 +136,7 @@ - Qt::TopToolBarArea + TopToolBarArea false @@ -316,6 +316,12 @@ QWidget
QtWebKit/QWebView
+ + DocumentView + QWidget +
calibre/gui2/viewer/documentview.h
+ 1 +
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index ca256e0350..543c6eab96 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -406,11 +406,9 @@ class ResultCache(SearchQueryParser): # {{{ if val_func is None: loc = self.field_metadata[location]['rec_index'] val_func = lambda item, loc=loc: item[loc] - dt = self.field_metadata[location]['datatype'] - q = '' - val_func = lambda item, loc=loc: item[loc] cast = adjust = lambda x: x + dt = self.field_metadata[location]['datatype'] if query == 'false': if dt == 'rating' or location == 'cover': diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 0ae4d74242..979e98a819 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -368,7 +368,8 @@ class FieldMetadata(dict): 'date_format': tweaks['gui_timestamp_display_format']} self._tb_cats['pubdate']['display'] = { '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.get = self._tb_cats.get diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index f7f5559412..709e3645a6 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -17,6 +17,7 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.constants import preferred_encoding from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import title_sort +from calibre.utils.date import parse_date from calibre import strftime, prints, sanitize_file_name_unicode plugboard_any_device_value = 'any device' @@ -42,6 +43,8 @@ FORMAT_ARG_DESCS = dict( publisher=_('The publisher'), timestamp=_('The date'), pubdate=_('The published date'), + last_modified=_('The date when the metadata for this book record' + ' was last modified'), 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()) if hasattr(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) # Now format the custom fields 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) failures = [] for x in ids: - opf, cover, format_map = data[x] + opf, cover, format_map, last_modified = data[x] if isinstance(opf, unicode): opf = opf.encode('utf-8') mi = OPF(cStringIO.StringIO(opf)).to_book_metadata() + try: + mi.last_modified = parse_date(last_modified) + except: + pass tb = '' try: failed, id, title = do_save_book_to_disk(x, mi, cover, plugboards, diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index c281773660..56d1832440 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -20,13 +20,14 @@ What formats does |app| support conversion to/from? |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. -*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 -** 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: diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 7b6e60c93a..a4e18c2e07 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -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 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 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``. diff --git a/src/calibre/manual/plugins.rst b/src/calibre/manual/plugins.rst index 0a62218fb9..1ebb180d46 100644 --- a/src/calibre/manual/plugins.rst +++ b/src/calibre/manual/plugins.rst @@ -65,17 +65,14 @@ Catalog plugins Metadata download plugins -------------------------- -.. module:: calibre.ebooks.metadata.fetch +.. module:: calibre.ebooks.metadata.sources.base -.. autoclass:: MetadataSource +.. autoclass:: Source :show-inheritance: :members: :member-order: bysource -.. autoclass:: calibre.ebooks.metadata.covers.CoverDownload - :show-inheritance: - :members: - :member-order: bysource +.. autoclass:: InternalMetadataCompareKeyGen Conversion plugins --------------------