From 3941dbac1350e7b01c1ed204b9c638d8f02b5d10 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 25 Apr 2011 11:10:14 +0100 Subject: [PATCH 01/37] Add a popup options menu to the save-a-search button. --- src/calibre/gui2/layout.py | 1 - src/calibre/gui2/search_box.py | 41 ++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) 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/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) From c7f16d75f90e395865945e358811d7e2ac867702 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 08:41:24 -0600 Subject: [PATCH 02/37] ... --- src/calibre/ebooks/metadata/book/base.py | 2 +- src/calibre/gui2/book_details.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 5f1841d518..6c360bad96 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -631,7 +631,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/gui2/book_details.py b/src/calibre/gui2/book_details.py index a2ee6d343b..2eefe3cb54 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -121,10 +121,7 @@ def render_data(mi, use_roman_numbers=True, all_fields=False): 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 = getattr(mi, field+'_index') if sidx is None: sidx = 1.0 val = _('Book %s of %s')%(fmt_sidx(sidx, From c8aedc1f174e24ecabd01540d7c3ab354e57dc54 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 09:00:22 -0600 Subject: [PATCH 03/37] Fix QToolBox on windows --- src/calibre/gui2/preferences/look_feel.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index bae08f5455..0383009381 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -11,6 +11,7 @@ from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog, from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2 import config, gprefs, qt_app +from calibre.constants import iswindows from calibre.utils.localization import (available_translations, get_language, get_lang) from calibre.utils.config import prefs @@ -158,6 +159,20 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.field_display_order.setModel(self.display_model) self.df_up_button.clicked.connect(self.move_df_up) self.df_down_button.clicked.connect(self.move_df_down) + if iswindows: + self.toolBox.setStyleSheet( + ''' + QToolBox::tab { + background: none; + border: none; + border-bottom: 2px solid black; + font-weight: bold; + } + + QToolBox::tab:selected { /* italicize selected tabs */ + font-style: italic; + } + ''') def initialize(self): ConfigWidgetBase.initialize(self) @@ -235,6 +250,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') From 0dadc113d9134410626071b1a167c8ab4da1591b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 09:09:51 -0600 Subject: [PATCH 04/37] ... --- src/calibre/gui2/__init__.py | 6 ------ src/calibre/gui2/viewer/main.ui | 22 ++++++++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) 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/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 +
From 10dd2f00d4cb65a4e8430d76d0b1b84269e755a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 09:55:49 -0600 Subject: [PATCH 05/37] Fix #770306 (Next button does not update html source) --- src/calibre/gui2/metadata/basic_widgets.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index d34be6c564..f7872b94b9 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 @@ -805,6 +805,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_): From 2421195416d75e6ff8e4a60bf91f96720fb1045a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 10:14:22 -0600 Subject: [PATCH 06/37] Windows strikes again --- src/calibre/gui2/preferences/look_feel.ui | 402 ++++++++++------------ 1 file changed, 178 insertions(+), 224 deletions(-) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 3d8a818e1e..4f0b8251b0 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -7,28 +7,23 @@ 0 0 717 - 444 + 390 Form - - - - - - 0 - 0 - 699 - 306 - - - - Main interface + + + + 0 + + + + Main Interface - + @@ -75,6 +70,13 @@ + + + + Enable system &tray icon (needs restart) + + + @@ -85,6 +87,13 @@ + + + + Disable &notifications in system tray + + + @@ -97,12 +106,12 @@ &Toolbar - + - + &Icon size: @@ -115,7 +124,7 @@ - + Show &text under icons: @@ -127,20 +136,7 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - + @@ -161,135 +157,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 +179,11 @@ then the tags will be displayed each on their own line. - - - - 0 - 0 - 429 - 63 - - - - 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 - - - - 20 - 40 - - - - - - - - - - 0 - 0 - 699 - 306 - - - + + Book Details - - - - - Use &Roman numerals for series - - - true - - - + @@ -425,6 +235,16 @@ then the tags will be displayed each on their own line. + + + + Use &Roman numerals for series + + + true + + + @@ -437,6 +257,140 @@ then the tags will be displayed each on their own line.
+ + + 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. + + + + + + + + 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 + + + + + + From 3995b9386dd41d444f0aa3bef6441c00c1feff7e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 10:16:21 -0600 Subject: [PATCH 07/37] ... --- src/calibre/gui2/preferences/look_feel.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 0383009381..620113cc3f 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -11,7 +11,6 @@ from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog, from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2 import config, gprefs, qt_app -from calibre.constants import iswindows from calibre.utils.localization import (available_translations, get_language, get_lang) from calibre.utils.config import prefs @@ -159,20 +158,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.field_display_order.setModel(self.display_model) self.df_up_button.clicked.connect(self.move_df_up) self.df_down_button.clicked.connect(self.move_df_down) - if iswindows: - self.toolBox.setStyleSheet( - ''' - QToolBox::tab { - background: none; - border: none; - border-bottom: 2px solid black; - font-weight: bold; - } - - QToolBox::tab:selected { /* italicize selected tabs */ - font-style: italic; - } - ''') def initialize(self): ConfigWidgetBase.initialize(self) From ebce952f7f0d980fc3844d873bb077240fbef047 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 10:40:22 -0600 Subject: [PATCH 08/37] ... --- src/calibre/gui2/preferences/look_feel.ui | 16 ++++++++++++++++ src/calibre/gui2/preferences/main.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 4f0b8251b0..244b811cbd 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -20,6 +20,10 @@ 0 + + + :/images/lt.png:/images/lt.png + Main Interface @@ -180,6 +184,10 @@ + + + :/images/book.png:/images/book.png + Book Details @@ -258,6 +266,10 @@ + + + :/images/tags.png:/images/tags.png + Tag Browser @@ -352,6 +364,10 @@ then the tags will be displayed each on their own line. + + + :/images/cover_flow.png:/images/cover_flow.png + Cover Browser 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) From d7a124178c4ac36bcc61e4e40234fc025919744c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 10:49:07 -0600 Subject: [PATCH 09/37] ... --- src/calibre/gui2/book_details.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 2eefe3cb54..f1fd2c1575 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -23,7 +23,6 @@ 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']) @@ -121,7 +120,7 @@ def render_data(mi, use_roman_numbers=True, all_fields=False): continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': - sidx = getattr(mi, field+'_index') + sidx = getattr(mi, field+'_index', None) if sidx is None: sidx = 1.0 val = _('Book %s of %s')%(fmt_sidx(sidx, From 1243bc0c2dd59bc101e0e0b50f9f22ddac3c343f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 25 Apr 2011 17:54:17 +0100 Subject: [PATCH 10/37] Make getattr(mi, '#series_index') work, in addition to mi.get( '#series_index') which already worked. --- src/calibre/ebooks/metadata/book/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 6c360bad96..ae75072761 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)) From 90326b1baaba9e8b43c7131e006982fa19c0ad91 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 11:01:21 -0600 Subject: [PATCH 11/37] ... --- src/calibre/gui2/book_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index f1fd2c1575..6b15df9a76 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -120,7 +120,7 @@ def render_data(mi, use_roman_numbers=True, all_fields=False): continue val = prepare_string_for_xml(val) if metadata['datatype'] == 'series': - sidx = getattr(mi, field+'_index', None) + sidx = mi.get(field+'_index') if sidx is None: sidx = 1.0 val = _('Book %s of %s')%(fmt_sidx(sidx, From da057256123814a4c3b622a9fbfcbc2c6945ce43 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 11:12:47 -0600 Subject: [PATCH 12/37] Small change to the API for medata source plugins. get_book_url must now return a tuple --- src/calibre/ebooks/metadata/sources/amazon.py | 2 +- src/calibre/ebooks/metadata/sources/base.py | 6 +++++- src/calibre/ebooks/metadata/sources/google.py | 2 +- src/calibre/ebooks/metadata/sources/identify.py | 7 +++---- src/calibre/gui2/metadata/single_download.py | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) 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..f386489fcd 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -374,7 +374,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..335d741fd2 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -438,14 +438,13 @@ def urls_from_identifiers(identifiers): # {{{ 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, + ans.append((isbn, 'isbn', isbn, 'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn)) return ans # }}} 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: From 262d1a9e931c120b290dca8195ebf7fd57abda0e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 11:33:26 -0600 Subject: [PATCH 13/37] Show identifiers in books details --- src/calibre/gui2/book_details.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 6b15df9a76..80d3c1636e 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -17,6 +17,7 @@ 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, @@ -113,7 +114,12 @@ 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) + ans.append((field, u'%s%s'%( + _('Ids')+':', links))) else: val = mi.format_field(field)[-1] if val is None: @@ -288,6 +294,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) From e1c950b7bb883ae6ab00c84ff7cecc49b8b0524a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 11:42:56 -0600 Subject: [PATCH 14/37] Add support for DOI and arXiv identifiers --- src/calibre/ebooks/metadata/sources/identify.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 335d741fd2..fd71e650a0 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -435,6 +435,7 @@ 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: @@ -446,6 +447,14 @@ def urls_from_identifiers(identifiers): # {{{ if isbn: ans.append((isbn, 'isbn', isbn, 'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%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)) return ans # }}} From 31341a16aad3c25439b3c3d076bcc01d8a51b050 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 13:54:32 -0600 Subject: [PATCH 15/37] ... --- src/calibre/ebooks/metadata/sources/overdrive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/overdrive.py b/src/calibre/ebooks/metadata/sources/overdrive.py index 759da45610..ad570a8b28 100755 --- a/src/calibre/ebooks/metadata/sources/overdrive.py +++ b/src/calibre/ebooks/metadata/sources/overdrive.py @@ -265,7 +265,7 @@ class OverDrive(Source): if creators: 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: @@ -291,7 +291,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)) From 1ec25e442bcae43a1431064e3616681e5dd36930 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 13:58:38 -0600 Subject: [PATCH 16/37] Spruce up the confirm delete dialog --- .../gui2/dialogs/confirm_delete_location.ui | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 + From b76159f31cf2b45fd4a75c65ec8f1cd6627a422e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 15:06:06 -0600 Subject: [PATCH 17/37] CurrentDir context now ignores the case when the original working dir no longer exists --- src/calibre/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 29c69a6799..bc99947345 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -388,7 +388,11 @@ class CurrentDir(object): return self.cwd def __exit__(self, *args): - os.chdir(self.cwd) + try: + os.chdir(self.cwd) + except: + # The previous CWD no longer exists + pass class StreamReadWrapper(object): From aced39619e9dfea8c3a58fe48eaa396039f4052f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 15:47:50 -0600 Subject: [PATCH 18/37] Add OCLC identifier to URL and use the direct ISBN link instead of worldcat search of isbn->URL --- src/calibre/ebooks/metadata/sources/identify.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index fd71e650a0..9a9e5aa164 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -446,7 +446,7 @@ def urls_from_identifiers(identifiers): # {{{ isbn = identifiers.get('isbn', None) if 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, @@ -455,6 +455,10 @@ def urls_from_identifiers(identifiers): # {{{ 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 # }}} From 50334bf1a5d5ce6579848f875a7a4063181ac10d Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 25 Apr 2011 21:27:52 -0400 Subject: [PATCH 19/37] Fix bug #770534: Problem converting pdf containing the string ******************/ --- src/calibre/ebooks/conversion/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 8822a39b87..885d0621e0 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -402,7 +402,7 @@ class HTMLPreProcessor(object): (re.compile(r'((?<=)\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: ''), From 16a1048522bc4ec7ff28907ec1aa3d7663e8c98f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 21:00:11 -0600 Subject: [PATCH 20/37] Document patch used on Qt for the calibre windows build to enable loading of OpenSSL dlls from the Dlls subdirectory --- setup/installer/windows/notes.rst | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 From 98b2dba03ff4b2a107e7f7b048bd62e46a3be60a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 22:09:37 -0600 Subject: [PATCH 21/37] Windows build: Have the installer overwrite dlls even if their version is the same to ensure the patched QtCore4.dll is installed --- setup/installer/windows/wix-template.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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' /> - + + + Date: Mon, 25 Apr 2011 22:19:09 -0600 Subject: [PATCH 22/37] Fix the example plugin upload script --- setup/upload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup/upload.py b/setup/upload.py index 57c8e4cd54..e448bc11e4 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -347,9 +347,10 @@ class UploadUserManual(Command): # {{{ with NamedTemporaryFile(suffix='.zip') as f: os.fchmod(f.fileno(), 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: for x in os.listdir('.'): + if x.endswith('.swp'): continue zf.write(x) if os.path.isdir(x): for y in os.listdir(x): From 80878bf7cf5379d768f514b4f3a5d665d6a44812 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 25 Apr 2011 23:48:40 -0600 Subject: [PATCH 23/37] ... --- src/calibre/ebooks/metadata/sources/base.py | 3 ++- src/calibre/manual/faq.rst | 7 ++++--- src/calibre/manual/plugins.rst | 9 +++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index f386489fcd..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. 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/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 -------------------- From ec4bd6d1b30cdbd6805a153c70265a78dd957a9b Mon Sep 17 00:00:00 2001 From: GRiker Date: Tue, 26 Apr 2011 06:06:08 -0600 Subject: [PATCH 24/37] GwR patch for artist name split/windows --- src/calibre/devices/apple/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index d7811f0a22..b11a3f298c 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -460,7 +460,7 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { '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, 'uuid': book.Composer, 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' From e765da76f61b7afe39f74ea4b2da9a489603f6a9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 26 Apr 2011 15:06:30 +0100 Subject: [PATCH 25/37] Fix 'count-of' searches (e.g., tags:#>3). Add a small blurb to the manual. --- src/calibre/library/caches.py | 4 +--- src/calibre/manual/gui.rst | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) 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/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``. From e3de77792edf1479cb1e1ed3ccc595a547f49a85 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 09:07:16 -0600 Subject: [PATCH 26/37] ... --- src/calibre/ebooks/pdb/plucker/reader.py | 1 + 1 file changed, 1 insertion(+) 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 From 15bc2500d16c8cae18015f755b6703dfbef2df51 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 10:34:28 -0600 Subject: [PATCH 27/37] Add a column that shows the date when the metadata of a book record was alst modified in calibre. To see the column, right click on the column headers in calibre and select Show column->Modified --- resources/default_tweaks.py | 1 + src/calibre/gui2/library/delegates.py | 13 ++++++++++--- src/calibre/gui2/library/models.py | 3 +++ src/calibre/gui2/library/views.py | 18 +++++++++++++++--- .../gui2/preferences/create_custom_column.py | 6 ++---- 5 files changed, 31 insertions(+), 10 deletions(-) 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/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/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: From f11ab2f63d6c4f0e17afde49b598dd2cff56d149 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 10:58:49 -0600 Subject: [PATCH 28/37] ... --- recipes/le_monde.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) + ' %'), From 4eba1acad2c9b1064e409ff7905e157a12f81696 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 12:03:16 -0600 Subject: [PATCH 29/37] Allow use of {last_modified} in save to disk templates --- src/calibre/ebooks/metadata/worker.py | 2 +- src/calibre/library/save_to_disk.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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/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, From 688c1a3f64b79f0de28839a16b70dcd8b1eff529 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 26 Apr 2011 19:11:30 +0100 Subject: [PATCH 30/37] Make field_metadata put last_modified format tweak into the display dict so the template formatter will see it. --- src/calibre/library/field_metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From e8592b2f9037386385c4b87b6bb55e7fd56d8a6e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 26 Apr 2011 19:41:56 +0100 Subject: [PATCH 31/37] Make the new author_sort -> author try to split the author_sort into its individual authors, then handle them one by one. --- src/calibre/gui2/metadata/basic_widgets.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index f7872b94b9..35d50c3d66 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -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()) From 3435831d521b064faf77c33f60c3626e7ded1336 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 14:29:18 -0600 Subject: [PATCH 32/37] Fix new edit metadata dialog not automatically fixing isbns --- src/calibre/gui2/metadata/basic_widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 35d50c3d66..86d2a3544f 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -943,6 +943,10 @@ class IdentifiersEdit(QLineEdit): # {{{ for x in parts: c = x.split(':') if len(c) == 2: + 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): From 2ac771e1dc004e2e3d5e0159974ba2141b7dfe39 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 14:32:30 -0600 Subject: [PATCH 33/37] ... --- src/calibre/gui2/metadata/basic_widgets.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 86d2a3544f..6b10448c50 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -957,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()) @@ -964,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: From 7f133692efacb83c866fb9fe5858e833a2763d9e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 19:43:16 -0600 Subject: [PATCH 34/37] Only set language in MOBI metadata is it is not null --- src/calibre/ebooks/metadata/mobi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From ff3c857c80a2061734d7ed49d98c3ac715b47e49 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 21:53:08 -0600 Subject: [PATCH 35/37] ... --- src/calibre/gui2/book_details.py | 5 +++-- src/calibre/gui2/metadata/basic_widgets.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 80d3c1636e..f94e179166 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -118,8 +118,9 @@ def render_data(mi, use_roman_numbers=True, all_fields=False): links = [u'%s' % (url, id_typ, id_val, name) for name, id_typ, id_val, url in urls] links = u', '.join(links) - ans.append((field, u'%s%s'%( - _('Ids')+':', links))) + if links: + ans.append((field, u'%s%s'%( + _('Ids')+':', links))) else: val = mi.format_field(field)[-1] if val is None: diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 6b10448c50..1620734209 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -942,7 +942,7 @@ 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: From e60506d355a497c6ff5fe9d380ed5085d2a5ffce Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 22:41:01 -0600 Subject: [PATCH 36/37] Fix bug in applying changes to the downloaded fields in the metadata sources preferences --- src/calibre/gui2/preferences/metadata_sources.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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)) # }}} From 78f33e3d3592b7a8a24347b93ba6db0e587080f1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Apr 2011 22:43:44 -0600 Subject: [PATCH 37/37] ... --- src/calibre/gui2/metadata/config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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)) # }}}