From b8a446f311a6c785f0dbfd0ad1c63bde928e1653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20D=C5=82ugosz?= Date: Tue, 28 Jun 2011 21:11:16 +0200 Subject: [PATCH 01/24] fixed typo in config/chooser/models.py --- src/calibre/gui2/store/config/chooser/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/config/chooser/models.py b/src/calibre/gui2/store/config/chooser/models.py index d2c6bcd8d3..24f6bdfc25 100644 --- a/src/calibre/gui2/store/config/chooser/models.py +++ b/src/calibre/gui2/store/config/chooser/models.py @@ -126,7 +126,7 @@ class Matches(QAbstractItemModel): elif role == Qt.ToolTipRole: if col == 0: if is_disabled(result): - return QVariant('

' + _('This store is currently diabled and cannot be used in other parts of calibre.') + '

') + return QVariant('

' + _('This store is currently disabled and cannot be used in other parts of calibre.') + '

') else: return QVariant('

' + _('This store is currently enabled and can be used in other parts of calibre.') + '

') elif col == 1: From e762f41d46d4058164cf11304c56c667fdc6fcab Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 28 Jun 2011 21:40:44 +0100 Subject: [PATCH 02/24] Add edit link context menu item to tag browser Make edit_authors remember geometry --- .../gui2/dialogs/edit_authors_dialog.py | 40 +++++++++++++++++-- src/calibre/gui2/tag_browser/ui.py | 4 +- src/calibre/gui2/tag_browser/view.py | 10 ++++- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index 1087c3cb82..300715c6e0 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -4,10 +4,11 @@ __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' from PyQt4.Qt import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, - QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication) + QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication, + QByteArray) from calibre.ebooks.metadata import author_to_author_sort -from calibre.gui2 import error_dialog +from calibre.gui2 import error_dialog, gprefs from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog from calibre.utils.icu import sort_key @@ -20,7 +21,7 @@ class tableItem(QTableWidgetItem): class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): - def __init__(self, parent, db, id_to_select, select_sort): + def __init__(self, parent, db, id_to_select, select_sort, select_link): QDialog.__init__(self, parent) Ui_EditAuthorsDialog.__init__(self) self.setupUi(self) @@ -29,6 +30,14 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) + try: + self.table_column_widths = \ + gprefs.get('manage_authors_table_widths', None) + geom = gprefs.get('manage_authors_dialog_geometry', bytearray('')) + self.restoreGeometry(QByteArray(geom)) + except: + pass + self.buttonBox.accepted.connect(self.accepted) # Set up the column headings @@ -65,6 +74,8 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): if id == id_to_select: if select_sort: select_item = sort + elif select_link: + select_item = link else: select_item = aut self.table.resizeColumnsToContents() @@ -122,6 +133,28 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.customContextMenuRequested .connect(self.show_context_menu) + def save_state(self): + self.table_column_widths = [] + for c in range(0, self.table.columnCount()): + self.table_column_widths.append(self.table.columnWidth(c)) + gprefs['manage_authors_table_widths'] = self.table_column_widths + gprefs['manage_authors_dialog_geometry'] = bytearray(self.saveGeometry()) + + def resizeEvent(self, *args): + QDialog.resizeEvent(self, *args) + if self.table_column_widths is not None: + for c,w in enumerate(self.table_column_widths): + self.table.setColumnWidth(c, w) + else: + # the vertical scroll bar might not be rendered, so might not yet + # have a width. Assume 25. Not a problem because user-changed column + # widths will be remembered + w = self.table.width() - 25 - self.table.verticalHeader().width() + w /= self.table.columnCount() + for c in range(0, self.table.columnCount()): + self.table.setColumnWidth(c, w) + self.save_state() + def show_context_menu(self, point): self.context_item = self.table.itemAt(point) case_menu = QMenu(_('Change Case')) @@ -238,6 +271,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.auth_col.setIcon(self.blank_icon) def accepted(self): + self.save_state() self.result = [] for row in range(0,self.table.rowCount()): id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0] diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 509f923be1..d7e504b3e9 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -262,12 +262,12 @@ class TagBrowserMixin(object): # {{{ self.library_view.select_rows(ids) # refreshing the tags view happens at the emit()/call() site - def do_author_sort_edit(self, parent, id, select_sort=True): + def do_author_sort_edit(self, parent, id, select_sort=True, select_link=False): ''' Open the manage authors dialog ''' db = self.library_view.model().db - editor = EditAuthorsDialog(parent, db, id, select_sort) + editor = EditAuthorsDialog(parent, db, id, select_sort, select_link) d = editor.exec_() if d: for (id, old_author, new_author, new_sort, new_link) in editor.result: diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 586d01ff87..788d85f79e 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -66,7 +66,7 @@ class TagsView(QTreeView): # {{{ tag_list_edit = pyqtSignal(object, object) saved_search_edit = pyqtSignal(object) rebuild_saved_searches = pyqtSignal() - author_sort_edit = pyqtSignal(object, object) + author_sort_edit = pyqtSignal(object, object, object, object) tag_item_renamed = pyqtSignal() search_item_renamed = pyqtSignal() drag_drop_finished = pyqtSignal(object) @@ -277,7 +277,10 @@ class TagsView(QTreeView): # {{{ self.saved_search_edit.emit(category) return if action == 'edit_author_sort': - self.author_sort_edit.emit(self, index) + self.author_sort_edit.emit(self, index, True, False) + return + if action == 'edit_author_link': + self.author_sort_edit.emit(self, index, False, True) return reset_filter_categories = True @@ -346,6 +349,9 @@ class TagsView(QTreeView): # {{{ self.context_menu.addAction(_('Edit sort for %s')%display_name(tag), partial(self.context_menu_handler, action='edit_author_sort', index=tag.id)) + self.context_menu.addAction(_('Edit link for %s')%display_name(tag), + partial(self.context_menu_handler, + action='edit_author_link', index=tag.id)) # is_editable is also overloaded to mean 'can be added # to a user category' From d5d48c3f1d742de8f1e8eee2ebbfc688da1f5b71 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 28 Jun 2011 22:37:13 +0100 Subject: [PATCH 03/24] Redo changes to fix #802773. They somehow got lost. --- src/calibre/gui2/tag_browser/view.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 788d85f79e..d53167591e 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -12,7 +12,7 @@ from functools import partial from itertools import izip from PyQt4.Qt import (QItemDelegate, Qt, QTreeView, pyqtSignal, QSize, QIcon, - QApplication, QMenu, QPoint, QModelIndex) + QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor) from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES, TagsModel) @@ -493,10 +493,25 @@ class TagsView(QTreeView): # {{{ pa.setCheckable(True) pa.setChecked(True) + if config['sort_tags_by'] != "name": + fla.setEnabled(False) + m.hovered.connect(self.collapse_menu_hovered) + fla.setToolTip(_('First letter is usable only when sorting by name')) + # Apparently one cannot set a tooltip to empty, so use a star and + # deal with it in the hover method + da.setToolTip('*') + pa.setToolTip('*') + if not self.context_menu.isEmpty(): self.context_menu.popup(self.mapToGlobal(point)) return True + def collapse_menu_hovered(self, action): + tip = action.toolTip() + if tip == '*': + tip = '' + QToolTip.showText(QCursor.pos(), tip) + def dragMoveEvent(self, event): QTreeView.dragMoveEvent(self, event) self.setDropIndicatorShown(False) From ac1abe55c23057b22722eb74d8526962ac9ac837 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 28 Jun 2011 19:41:36 -0600 Subject: [PATCH 04/24] Driver for HTC G2 --- src/calibre/devices/android/driver.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 2c840c644a..08ecbd1ee3 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -19,10 +19,11 @@ class ANDROID(USBMS): VENDOR_ID = { # HTC - 0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226, 0x222], - 0x0c01 : [0x100, 0x0227, 0x0226], - 0x0ff9 : [0x0100, 0x0227, 0x0226], - 0x0c87 : [0x0100, 0x0227, 0x0226], + 0x0bb4 : { 0xc02 : [0x100, 0x0227, 0x0226, 0x222], + 0xc01 : [0x100, 0x0227, 0x0226], + 0xff9 : [0x0100, 0x0227, 0x0226], + 0xc87 : [0x0100, 0x0227, 0x0226], + 0xc91 : [0x0100, 0x0227, 0x0226], 0xc92 : [0x100], 0xc97 : [0x226], 0xc99 : [0x0100], From cd70deb4394dc01636cbfc7ee291350da89ee009 Mon Sep 17 00:00:00 2001 From: John Schember Date: Wed, 29 Jun 2011 07:44:07 -0400 Subject: [PATCH 05/24] Store: Update B&N plugin to work with new affiliate program. --- src/calibre/gui2/store/stores/bn_plugin.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/store/stores/bn_plugin.py b/src/calibre/gui2/store/stores/bn_plugin.py index 94e498bb44..abe789ad76 100644 --- a/src/calibre/gui2/store/stores/bn_plugin.py +++ b/src/calibre/gui2/store/stores/bn_plugin.py @@ -24,18 +24,16 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class BNStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - pub_id = '21000000000352219' + pub_id = 'sHa5EXvYOwA' # Use Kovid's affiliate id 30% of the time. if random.randint(1, 10) in (1, 2, 3): - pub_id = '21000000000352583' + pub_id = '0dsO3kDu/AU' - url = 'http://gan.doubleclick.net/gan_click?lid=41000000028437369&pubid=' + pub_id + base_url = 'http://click.linksynergy.com/fs-bin/click?id=%s&subid=&offerid=229293.1&type=10&tmpid=8433&RD_PARM1=' % pub_id + url = base_url + 'http%253A%252F%252Fwww.barnesandnoble.com%252F' if detail_item: - mo = re.search(r'(?<=/)(?P\d+)(?=/|$)', detail_item) - if mo: - isbn = mo.group('isbn') - detail_item = 'http://gan.doubleclick.net/gan_click?lid=41000000012871747&pid=' + isbn + '&adurl=' + detail_item + '&pubid=' + pub_id + detail_item = base_url + detail_item if external or self.config.get('open_external', False): open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) From e769f20a5b13e9d01fc6297cb970e1b1457a9256 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 08:22:48 -0600 Subject: [PATCH 06/24] ... --- src/calibre/manual/customize.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/customize.rst b/src/calibre/manual/customize.rst index fe33100576..9c9aacb737 100644 --- a/src/calibre/manual/customize.rst +++ b/src/calibre/manual/customize.rst @@ -56,7 +56,7 @@ You should not change the files in this resources folder, as your changes will g |app| will automatically use your custom file in preference to the builtin one the next time it is started. For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is -:file:`resources/images/trash.svg`. Assuming you have an alternate icon in svg format called :file:`mytrash.svg` you would save it in the configuration directory as :file:`resources/images/trash.svg`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders. +:file:`resources/images/trash.png`. Assuming you have an alternate icon in PNG format called :file:`mytrash.png` you would save it in the configuration directory as :file:`resources/images/trash.png`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders. Customizing |app| with plugins -------------------------------- From e84ce5e089026ef4a455979091c38b94e4249682 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 29 Jun 2011 15:49:58 +0100 Subject: [PATCH 07/24] Minor changes to quickview: show help text when nothing is selected, show selected item in book label. --- src/calibre/gui2/dialogs/quickview.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 30b68a7b7d..2215a3cc95 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -73,6 +73,7 @@ class Quickview(QDialog, Ui_Quickview): self.last_search = None self.current_column = None self.current_item = None + self.no_valid_items = False self.items.setSelectionMode(QAbstractItemView.SingleSelection) self.items.currentTextChanged.connect(self.item_selected) @@ -119,6 +120,8 @@ class Quickview(QDialog, Ui_Quickview): # search button def do_search(self): + if self.no_valid_items: + return if self.last_search is not None: self.gui.search.set_search_string(self.last_search) @@ -132,6 +135,8 @@ class Quickview(QDialog, Ui_Quickview): # clicks on the items listWidget def item_selected(self, txt): + if self.no_valid_items: + return self.fill_in_books_box(unicode(txt)) # Given a cell in the library view, display the information @@ -144,6 +149,7 @@ class Quickview(QDialog, Ui_Quickview): # Only show items for categories if not self.db.field_metadata[key]['is_category']: if self.current_key is None: + self.indicate_no_items() return key = self.current_key self.items_label.setText('{0} ({1})'.format( @@ -157,6 +163,7 @@ class Quickview(QDialog, Ui_Quickview): vals = mi.get(key, None) if vals: + self.no_valid_items = False if not isinstance(vals, list): vals = [vals] vals.sort(key=sort_key) @@ -170,8 +177,19 @@ class Quickview(QDialog, Ui_Quickview): self.current_key = key self.fill_in_books_box(vals[0]) + else: + self.indicate_no_items() + self.items.blockSignals(False) + def indicate_no_items(self): + print 'no items' + self.no_valid_items = True + self.items.clear() + self.items.addItem(QListWidgetItem(_('**No items found**'))) + self.books_label.setText(_('Click in a column in the library view ' + 'to see the information for that book')) + def fill_in_books_box(self, selected_item): self.current_item = selected_item # Do a bit of fix-up on the items so that the search works. @@ -185,7 +203,8 @@ class Quickview(QDialog, Ui_Quickview): self.db.data.search_restriction) self.books_table.setRowCount(len(books)) - self.books_label.setText(_('Books with selected item: {0}').format(len(books))) + self.books_label.setText(_('Books with selected item "{0}": {1}'). + format(selected_item, len(books))) select_item = None self.books_table.setSortingEnabled(False) @@ -235,6 +254,8 @@ class Quickview(QDialog, Ui_Quickview): self.save_state() def book_doubleclicked(self, row, column): + if self.no_valid_items: + return book_id = self.books_table.item(row, 0).data(Qt.UserRole).toInt()[0] self.view.select_rows([book_id]) modifiers = int(QApplication.keyboardModifiers()) From 5798d82a26515ae783d29b7b6e0b9f1b1dbce6dd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 09:03:24 -0600 Subject: [PATCH 08/24] Arizona Republic by Jim Olo --- recipes/arizona_republic.recipe | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 recipes/arizona_republic.recipe diff --git a/recipes/arizona_republic.recipe b/recipes/arizona_republic.recipe new file mode 100644 index 0000000000..5bc2140946 --- /dev/null +++ b/recipes/arizona_republic.recipe @@ -0,0 +1,68 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, jolo' +''' +azrepublic.com +''' +from calibre.web.feeds.recipes import BasicNewsRecipe + +class AdvancedUserRecipe1307301031(BasicNewsRecipe): + title = u'AZRepublic' + __author__ = 'Jim Olo' + language = 'en' + description = "The Arizona Republic is Arizona's leading provider of news and information, and has published a daily newspaper in Phoenix for more than 110 years" + publisher = 'AZRepublic/AZCentral' + masthead_url = 'http://freedom2t.com/wp-content/uploads/press_az_republic_v2.gif' + cover_url = 'http://www.valleyleadership.org/Common/Img/2line4c_AZRepublic%20with%20azcentral%20logo.jpg' + category = 'news, politics, USA, AZ, Arizona' + + oldest_article = 7 + max_articles_per_feed = 100 + remove_empty_feeds = True + no_stylesheets = True + remove_javascript = True +# extra_css = '.headline {font-size: medium;} \n .fact { padding-top: 10pt }' + extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif } .headline {font-size: medium} .introduction{font-weight: bold} .story-feature{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} ' + + remove_attributes = ['width','height','h2','subHeadline','style'] + remove_tags = [ + dict(name='div', attrs={'id':['slidingBillboard', 'top728x90', 'subindex-header', 'topSearch']}), + dict(name='div', attrs={'id':['simplesearch', 'azcLoginBox', 'azcLoginBoxInner', 'topNav']}), + dict(name='div', attrs={'id':['carsDrop', 'homesDrop', 'rentalsDrop', 'classifiedDrop']}), + dict(name='div', attrs={'id':['nav', 'mp', 'subnav', 'jobsDrop']}), + dict(name='h6', attrs={'class':['section-header']}), + dict(name='a', attrs={'href':['#comments']}), + dict(name='div', attrs={'class':['articletools clearfix', 'floatRight']}), + dict(name='div', attrs={'id':['fbFrame', 'ob', 'storyComments', 'storyGoogleAdBox']}), + dict(name='div', attrs={'id':['storyTopHomes', 'openRight', 'footerwrap', 'copyright']}), + dict(name='div', attrs={'id':['blogsHed', 'blog_comments', 'blogByline','blogTopics']}), + dict(name='div', attrs={'id':['membersRightMain', 'dealsfooter', 'azrTopHed', 'azrRightCol']}), + dict(name='div', attrs={'id':['ttdHeader', 'ttdTimeWeather']}), + dict(name='div', attrs={'id':['membersRightMain', 'deals-header-wrap']}), + dict(name='div', attrs={'id':['todoTopSearchBar', 'byline clearfix', 'subdex-topnav']}), + dict(name='h1', attrs={'id':['SEOtext']}), + dict(name='table', attrs={'class':['ap-mediabox-table']}), + dict(name='p', attrs={'class':['ap_para']}), + dict(name='span', attrs={'class':['source-org vcard', 'org fn']}), + dict(name='a', attrs={'href':['http://hosted2.ap.org/APDEFAULT/privacy']}), + dict(name='a', attrs={'href':['http://hosted2.ap.org/APDEFAULT/terms']}), + dict(name='div', attrs={'id':['onespot_nextclick']}), + ] + + feeds = [ + (u'FrontPage', u'http://www.azcentral.com/rss/feeds/republicfront.xml'), + (u'TopUS-News', u'http://hosted.ap.org/lineups/USHEADS.rss?SITE=AZPHG&SECTION=HOME'), + (u'WorldNews', u'http://hosted.ap.org/lineups/WORLDHEADS.rss?SITE=AZPHG&SECTION=HOME'), + (u'TopBusiness', u'http://hosted.ap.org/lineups/BUSINESSHEADS.rss?SITE=AZPHG&SECTION=HOME'), + (u'Entertainment', u'http://hosted.ap.org/lineups/ENTERTAINMENT.rss?SITE=AZPHG&SECTION=HOME'), + (u'ArizonaNews', u'http://www.azcentral.com/rss/feeds/news.xml'), + (u'Gilbert', u'http://www.azcentral.com/rss/feeds/gilbert.xml'), + (u'Chandler', u'http://www.azcentral.com/rss/feeds/chandler.xml'), + (u'DiningReviews', u'http://www.azcentral.com/rss/feeds/diningreviews.xml'), + (u'AZBusiness', u'http://www.azcentral.com/rss/feeds/business.xml'), + (u'ArizonaDeals', u'http://www.azcentral.com/members/Blog%7E/RealDealsblog'), + (u'GroceryDeals', u'http://www.azcentral.com/members/Blog%7E/RealDealsblog/tag/2646') + ] + + + + From 5137de4393864bbd700aff4705890d9f43463932 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 09:51:23 -0600 Subject: [PATCH 09/24] ... --- src/calibre/manual/gui.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index f4b04f6e9d..974efa03ce 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -164,13 +164,16 @@ Library .. |lii| image:: images/library.png :class: float-right-img -|lii| The :guilabel: `Library` action allows you to create, switch between, rename or delete a Library. |app| allows you to create as many libraries as you wish. You could for instance create a fiction library, a non fiction library, a foreign language library a project library, basically any structure that suits your needs. Libraries are the highest organizational structure within |app|, each library has its own set of books, tags, categories and base storage location. +|lii| The :guilabel:`Library` action allows you to create, switch between, rename or delete a Library. |app| allows you to create as many libraries as you wish. You could for instance create a fiction library, a non fiction library, a foreign language library, a project library, basically any structure that suits your needs. Libraries are the highest organizational structure within |app|, each library has its own set of books, tags, categories and base storage location. - 1. **Switch\Create library..**: This action allows you to; a) connect to a pre-existing |app| library at another location from your currently open library, b) Create and empty library at a nw location or, c) Move the current Library to a newly specified location. - 2. **Quick Switch>**: This action allows you to switch between libraries that have been registered or created within |app|. - 3. **Rename Library>**: This action allows you to rename a Library. - 4. **Delete Library>**: This action allows you to **permanenetly delete** a Library. - 5. ****: Actions 5, 6 etc .. give you immediate switch access between multiple Libraries that you have created or attached to. + 1. **Switch\Create library**: This action allows you to; a) connect to a pre-existing |app| library at another location from your currently open library, b) Create and empty library at a new location or, c) Move the current Library to a newly specified location. + 2. **Quick Switch**: This action allows you to switch between libraries that have been registered or created within |app|. + 3. **Rename Library**: This action allows you to rename a Library. + 4. **Remove Library**: This action allows you to unregister a library from |app|. + 5. ****: Actions 5, 6 etc .. give you immediate switch access between multiple Libraries that you have created or attached to. This list contains only the 5 most frequently used libraries. For the complete list, use the Quick Switch menu. + 6. **Library Maintenance**: This action allows you to check the current library for data consistency issues and restore the current libraries' database from backups. + +.. note:: Metadata about your ebooks like title/author/tags/etc. is stored in a single file in your |app| library folder called metadata.db. If this file gets corrupted (a very rare event), you can lose the metadata. Fortunately, |app| automatically backs up the metadata for every individual book in the book's folder as an .opf file. By using the Restore Library action under Library Maintenance described above, you can have |app| rebuild the metadata.db file from the individual .opf files for you. .. _device: From 6ec4b8e7030b5c8cf38dc9ba5abc915618d557a1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 09:52:24 -0600 Subject: [PATCH 10/24] ... --- src/calibre/manual/gui.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 974efa03ce..e5e789d9dd 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -166,7 +166,7 @@ Library |lii| The :guilabel:`Library` action allows you to create, switch between, rename or delete a Library. |app| allows you to create as many libraries as you wish. You could for instance create a fiction library, a non fiction library, a foreign language library, a project library, basically any structure that suits your needs. Libraries are the highest organizational structure within |app|, each library has its own set of books, tags, categories and base storage location. - 1. **Switch\Create library**: This action allows you to; a) connect to a pre-existing |app| library at another location from your currently open library, b) Create and empty library at a new location or, c) Move the current Library to a newly specified location. + 1. **Switch/Create library**: This action allows you to; a) connect to a pre-existing |app| library at another location from your currently open library, b) Create and empty library at a new location or, c) Move the current Library to a newly specified location. 2. **Quick Switch**: This action allows you to switch between libraries that have been registered or created within |app|. 3. **Rename Library**: This action allows you to rename a Library. 4. **Remove Library**: This action allows you to unregister a library from |app|. From 54eda115567267d43ce63d9b868c54c88be0422a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 10:32:24 -0600 Subject: [PATCH 11/24] Catholic News Agency by Jetkey --- recipes/catholic_news_agency.recipe | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 recipes/catholic_news_agency.recipe diff --git a/recipes/catholic_news_agency.recipe b/recipes/catholic_news_agency.recipe new file mode 100644 index 0000000000..43b7755f07 --- /dev/null +++ b/recipes/catholic_news_agency.recipe @@ -0,0 +1,13 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1301972345(BasicNewsRecipe): + title = u'Catholic News Agency' + language = 'en' + __author__ = 'Jetkey' + oldest_article = 5 + max_articles_per_feed = 20 + + feeds = [(u'U.S. News', u'http://feeds.feedburner.com/catholicnewsagency/dailynews-us'), + (u'Vatican', u'http://feeds.feedburner.com/catholicnewsagency/dailynews-vatican'), + (u'Bishops Corner', u'http://feeds.feedburner.com/catholicnewsagency/columns/bishopscorner'), + (u'Saint of the Day', u'http://feeds.feedburner.com/catholicnewsagency/saintoftheday')] From 9c8a7db894a1c9068f1abf900ef11c8e4d818ad7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 10:54:09 -0600 Subject: [PATCH 12/24] Fix #802251 (Add support for mpman MOOVYBOOK7 ebook reader) --- src/calibre/customize/builtins.py | 5 +++-- src/calibre/devices/misc.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 333a5baaa4..4858b585ae 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -611,7 +611,7 @@ from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS, from calibre.devices.sne.driver import SNE from calibre.devices.misc import (PALMPRE, AVANT, SWEEX, PDNOVEL, GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, ALURATEK_COLOR, - TREKSTOR, EEEREADER, NEXTBOOK, ADAM) + TREKSTOR, EEEREADER, NEXTBOOK, ADAM, MOOVYBOOK) from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.kobo.driver import KOBO from calibre.devices.bambook.driver import BAMBOOK @@ -746,6 +746,7 @@ plugins += [ EEEREADER, NEXTBOOK, ADAM, + MOOVYBOOK, ITUNES, BOEYE_BEX, BOEYE_BDX, @@ -1382,7 +1383,7 @@ class StoreOpenBooksStore(StoreBase): name = 'Open Books' description = u'Comprehensive listing of DRM free ebooks from a variety of sources provided by users of calibre.' actual_plugin = 'calibre.gui2.store.stores.open_books_plugin:OpenBooksStore' - + drm_free_only = True headquarters = 'US' diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 2a6a76719d..6c5706f039 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -329,3 +329,25 @@ class NEXTBOOK(USBMS): f.write(metadata.thumbnail[-1]) ''' +class MOOVYBOOK(USBMS): + + name = 'Moovybook device interface' + gui_name = 'Moovybook' + description = _('Communicate with the Moovybook Reader') + author = 'Kovid Goyal' + supported_platforms = ['windows', 'osx', 'linux'] + + # Ordered list of supported formats + FORMATS = ['epub', 'txt', 'pdf'] + + VENDOR_ID = [0x1cae] + PRODUCT_ID = [0x9b08] + BCD = [0x02] + + EBOOK_DIR_MAIN = '' + + SUPPORTS_SUB_DIRS = True + + def get_main_ebook_dir(self, for_upload=False): + return 'Books' if for_upload else self.EBOOK_DIR_MAIN + From d14db5cda71d9764e9fe4c4c549f114d4a1cbad4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 11:20:42 -0600 Subject: [PATCH 13/24] Fix #803544 (Updated recipe for Buenos Aires Economico) --- recipes/buenosaireseconomico.recipe | 88 +++++++++++-------------- recipes/icons/buenosaireseconomico.png | Bin 0 -> 400 bytes 2 files changed, 38 insertions(+), 50 deletions(-) create mode 100644 recipes/icons/buenosaireseconomico.png diff --git a/recipes/buenosaireseconomico.recipe b/recipes/buenosaireseconomico.recipe index 782358e6d3..2de02c7c10 100644 --- a/recipes/buenosaireseconomico.recipe +++ b/recipes/buenosaireseconomico.recipe @@ -1,72 +1,60 @@ -#!/usr/bin/env python - __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2009-2011, Darko Miletic ' ''' -elargentino.com +www.diariobae.com ''' - +from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import Tag class BsAsEconomico(BasicNewsRecipe): title = 'Buenos Aires Economico' __author__ = 'Darko Miletic' - description = 'Revista Argentina' - publisher = 'ElArgentino.com' + description = 'Diario BAE es el diario economico-politico con mas influencia en la Argentina. Fuente de empresarios y politicos del pais y el exterior. El pozo estaria aportando en periodos breves un volumen equivalente a 800m3 diarios. Pero todavia deben efectuarse otras perforaciones adicionales.' + publisher = 'Diario BAE' category = 'news, politics, economy, Argentina' oldest_article = 2 max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False encoding = 'utf-8' - language = 'es_AR' + language = 'es_AR' + cover_url = strftime('http://www.diariobae.com/imgs_portadas/%Y%m%d_portadasBAE.jpg') + masthead_url = 'http://www.diariobae.com/img/logo_bae.png' + remove_empty_feeds = True + publication_type = 'newspaper' + extra_css = """ + body{font-family: Georgia,"Times New Roman",Times,serif} + #titulo{font-size: x-large} + #epi{font-size: small; font-style: italic; font-weight: bold} + img{display: block; margin-top: 1em} + """ + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } - lang = 'es-AR' - direction = 'ltr' - INDEX = 'http://www.elargentino.com/medios/121/Buenos-Aires-Economico.html' - extra_css = ' .titulo{font-size: x-large; font-weight: bold} .volantaImp{font-size: small; font-weight: bold} ' - - html2lrf_options = [ - '--comment' , description - , '--category' , category - , '--publisher', publisher + remove_tags_before= dict(attrs={'id':'titulo'}) + remove_tags_after = dict(attrs={'id':'autor' }) + remove_tags = [ + dict(name=['meta','base','iframe','link','lang']) + ,dict(attrs={'id':'barra_tw'}) ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0cm; margin-top: 0em; margin-bottom: 0.5em} "' - - keep_only_tags = [dict(name='div', attrs={'class':'ContainerPop'})] - - remove_tags = [dict(name='link')] - - feeds = [(u'Articulos', u'http://www.elargentino.com/Highlights.aspx?ParentType=Section&ParentId=121&Content-Type=text/xml&ChannelDesc=Buenos%20Aires%20Econ%C3%B3mico')] - - def print_version(self, url): - main, sep, article_part = url.partition('/nota-') - article_id, rsep, rrest = article_part.partition('-') - return u'http://www.elargentino.com/Impresion.aspx?Id=' + article_id + remove_attributes = ['data-count','data-via'] + + feeds = [ + (u'Argentina' , u'http://www.diariobae.com/rss/argentina.xml' ) + ,(u'Valores' , u'http://www.diariobae.com/rss/valores.xml' ) + ,(u'Finanzas' , u'http://www.diariobae.com/rss/finanzas.xml' ) + ,(u'Negocios' , u'http://www.diariobae.com/rss/negocios.xml' ) + ,(u'Mundo' , u'http://www.diariobae.com/rss/mundo.xml' ) + ,(u'5 dias' , u'http://www.diariobae.com/rss/5dias.xml' ) + ,(u'Espectaculos', u'http://www.diariobae.com/rss/espectaculos.xml') + ] def preprocess_html(self, soup): for item in soup.findAll(style=True): del item['style'] - soup.html['lang'] = self.lang - soup.html['dir' ] = self.direction - mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) - mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")]) - soup.head.insert(0,mlang) - soup.head.insert(1,mcharset) return soup - - def get_cover_url(self): - cover_url = None - soup = self.index_to_soup(self.INDEX) - cover_item = soup.find('div',attrs={'class':'colder'}) - if cover_item: - clean_url = self.image_url_processor(None,cover_item.div.img['src']) - cover_url = 'http://www.elargentino.com' + clean_url + '&height=600' - return cover_url - - def image_url_processor(self, baseurl, url): - base, sep, rest = url.rpartition('?Id=') - img, sep2, rrest = rest.partition('&') - return base + sep + img diff --git a/recipes/icons/buenosaireseconomico.png b/recipes/icons/buenosaireseconomico.png new file mode 100644 index 0000000000000000000000000000000000000000..d84f7483ae1e8dec1a65dc4c3ad72b4228666530 GIT binary patch literal 400 zcmV;B0dM|^P)$xiz5^x5Y#7ns=D82S_IU^A^*u7cUwAF?_jj@%fB-oJz_+pFLw05$2GS;nLCkdi6Tc_dgy!{P*=I(6*QJ u7k@f-ipDW5C@4r;Ov}i~knSW>H2?q-oS-}y%$=A30000 Date: Wed, 29 Jun 2011 11:21:15 -0600 Subject: [PATCH 14/24] Fix #803543 (Remove recipe for Critica de la Argentina) --- recipes/criticadigital.recipe | 69 ----------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 recipes/criticadigital.recipe diff --git a/recipes/criticadigital.recipe b/recipes/criticadigital.recipe deleted file mode 100644 index 3cb72e6be4..0000000000 --- a/recipes/criticadigital.recipe +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python - -__license__ = 'GPL v3' -__copyright__ = '2008, Darko Miletic ' -''' -criticadigital.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class CriticaDigital(BasicNewsRecipe): - title = 'Critica de la Argentina' - __author__ = 'Darko Miletic and Sujata Raman' - description = 'Noticias de Argentina' - oldest_article = 2 - max_articles_per_feed = 100 - language = 'es_AR' - - no_stylesheets = True - use_embedded_content = False - encoding = 'cp1252' - - extra_css = ''' - h1{font-family:"Trebuchet MS";} - h3{color:#9A0000; font-family:Tahoma; font-size:x-small;} - h2{color:#504E53; font-family:Arial,Helvetica,sans-serif ;font-size:small;} - #epigrafe{font-family:Arial,Helvetica,sans-serif ;color:#666666 ; font-size:x-small;} - p {font-family:Arial,Helvetica,sans-serif;} - #fecha{color:#858585; font-family:Tahoma; font-size:x-small;} - #autor{color:#858585; font-family:Tahoma; font-size:x-small;} - #hora{color:#F00000;font-family:Tahoma; font-size:x-small;} - ''' - keep_only_tags = [ - dict(name='div', attrs={'class':['bloqueTitulosNoticia','cfotonota']}) - ,dict(name='div', attrs={'id':'boxautor'}) - ,dict(name='p', attrs={'id':'textoNota'}) - ] - - remove_tags = [ - dict(name='div', attrs={'class':'box300' }) - ,dict(name='div', style=True ) - ,dict(name='div', attrs={'class':'titcomentario'}) - ,dict(name='div', attrs={'class':'comentario' }) - ,dict(name='div', attrs={'class':'paginador' }) - ] - - feeds = [ - (u'Politica', u'http://www.criticadigital.com/herramientas/rss.php?ch=politica' ) - ,(u'Economia', u'http://www.criticadigital.com/herramientas/rss.php?ch=economia' ) - ,(u'Deportes', u'http://www.criticadigital.com/herramientas/rss.php?ch=deportes' ) - ,(u'Espectaculos', u'http://www.criticadigital.com/herramientas/rss.php?ch=espectaculos') - ,(u'Mundo', u'http://www.criticadigital.com/herramientas/rss.php?ch=mundo' ) - ,(u'Policiales', u'http://www.criticadigital.com/herramientas/rss.php?ch=policiales' ) - ,(u'Sociedad', u'http://www.criticadigital.com/herramientas/rss.php?ch=sociedad' ) - ,(u'Salud', u'http://www.criticadigital.com/herramientas/rss.php?ch=salud' ) - ,(u'Tecnologia', u'http://www.criticadigital.com/herramientas/rss.php?ch=tecnologia' ) - ,(u'Santa Fe', u'http://www.criticadigital.com/herramientas/rss.php?ch=santa_fe' ) - ] - - def get_cover_url(self): - cover_url = None - index = 'http://www.criticadigital.com/impresa/' - soup = self.index_to_soup(index) - link_item = soup.find('div',attrs={'class':'tapa'}) - if link_item: - cover_url = index + link_item.img['src'] - return cover_url - - From ca69bebe03f5367891bd7afe1b67ec3f8972ab59 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 11:39:28 -0600 Subject: [PATCH 15/24] Fix #803562 (Updated recipe for El Cronista) --- recipes/elcronista.recipe | 99 +++++++++++++++-------------------- recipes/icons/elcronista.png | Bin 770 -> 1143 bytes 2 files changed, 43 insertions(+), 56 deletions(-) diff --git a/recipes/elcronista.recipe b/recipes/elcronista.recipe index 93615f8f42..f8da81c4bb 100644 --- a/recipes/elcronista.recipe +++ b/recipes/elcronista.recipe @@ -1,72 +1,59 @@ -#!/usr/bin/env python - __license__ = 'GPL v3' -__copyright__ = '2008, Darko Miletic ' +__copyright__ = '2008-2011, Darko Miletic ' ''' -cronista.com +www.cronista.com ''' from calibre.web.feeds.news import BasicNewsRecipe -class ElCronista(BasicNewsRecipe): - title = 'El Cronista' +class Pagina12(BasicNewsRecipe): + title = 'El Cronista Comercial' __author__ = 'Darko Miletic' - description = 'Noticias de Argentina' + description = 'El Cronista Comercial es el Diario economico-politico mas valorado. Es la fuente mas confiable de informacion en temas de economia, finanzas y negocios enmarcados politicamente.' + publisher = 'Cronista.com' + category = 'news, politics, economy, finances, Argentina' oldest_article = 2 - language = 'es_AR' - - max_articles_per_feed = 100 + max_articles_per_feed = 200 no_stylesheets = True + encoding = 'utf8' use_embedded_content = False - encoding = 'cp1252' + language = 'es_AR' + remove_empty_feeds = True + publication_type = 'newspaper' + masthead_url = 'http://www.cronista.com/export/sites/diarioelcronista/arte/header-logo.gif' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif } + h2{font-family: Georgia,"Times New Roman",Times,serif } + img{margin-bottom: 0.4em; display:block} + .nom{font-weight: bold; vertical-align: baseline} + .autor-cfoto{border-bottom: 1px solid #D2D2D2; + border-top: 1px solid #D2D2D2; + display: inline-block; + margin: 0 10px 10px 0; + padding: 10px; + width: 210px} + .under{font-weight: bold} + .time{font-size: small} + """ - html2lrf_options = [ - '--comment' , description - , '--category' , 'news, Argentina' - , '--publisher' , title - ] + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } - keep_only_tags = [ - dict(name='table', attrs={'width':'100%' }) - ,dict(name='h1' , attrs={'class':'Arialgris16normal'}) - ] + remove_tags = [ + dict(name=['meta','link','base','iframe','object','embed']) + ,dict(attrs={'class':['user-tools','tabsmedia']}) + ] + remove_attributes = ['lang'] + remove_tags_before = dict(attrs={'class':'top'}) + remove_tags_after = dict(attrs={'class':'content-nota'}) + feeds = [(u'Ultimas noticias', u'http://www.cronista.com/rss.html')] - remove_tags = [dict(name='a', attrs={'class':'Arialazul12'})] - - feeds = [ - (u'Economia' , u'http://www.cronista.com/adjuntos/8/rss/Economia_EI.xml' ) - ,(u'Negocios' , u'http://www.cronista.com/adjuntos/8/rss/negocios_EI.xml' ) - ,(u'Ultimo momento' , u'http://www.cronista.com/adjuntos/8/rss/ultimo_momento.xml' ) - ,(u'Finanzas y Mercados' , u'http://www.cronista.com/adjuntos/8/rss/Finanzas_Mercados_EI.xml' ) - ,(u'Financial Times' , u'http://www.cronista.com/adjuntos/8/rss/FT_EI.xml' ) - ,(u'Opinion edicion impresa' , u'http://www.cronista.com/adjuntos/8/rss/opinion_edicion_impresa.xml' ) - ,(u'Socialmente Responsables', u'http://www.cronista.com/adjuntos/8/rss/Socialmente_Responsables.xml') - ,(u'Asuntos Legales' , u'http://www.cronista.com/adjuntos/8/rss/asuntoslegales.xml' ) - ,(u'IT Business' , u'http://www.cronista.com/adjuntos/8/rss/itbusiness.xml' ) - ,(u'Management y RR.HH.' , u'http://www.cronista.com/adjuntos/8/rss/management.xml' ) - ,(u'Inversiones Personales' , u'http://www.cronista.com/adjuntos/8/rss/inversionespersonales.xml' ) - ] - - def print_version(self, url): - main, sep, rest = url.partition('.com/notas/') - article_id, lsep, rrest = rest.partition('-') - return 'http://www.cronista.com/interior/index.php?p=imprimir_nota&idNota=' + article_id def preprocess_html(self, soup): - mtag = '' - soup.head.insert(0,mtag) - soup.head.base.extract() - htext = soup.find('h1',attrs={'class':'Arialgris16normal'}) - htext.name = 'p' - soup.prettify() + for item in soup.findAll(style=True): + del item['style'] return soup - - def get_cover_url(self): - cover_url = None - index = 'http://www.cronista.com/contenidos/' - soup = self.index_to_soup(index + 'ee.html') - link_item = soup.find('a',attrs={'href':"javascript:Close()"}) - if link_item: - cover_url = index + link_item.img['src'] - return cover_url - diff --git a/recipes/icons/elcronista.png b/recipes/icons/elcronista.png index 0be856345ed80377ab3da76701ecfca025005b05..ca64756de125ca72157416b01a0ecb90c47468ca 100644 GIT binary patch literal 1143 zcmV--1c>{IP)xPX9EL16fXsLZ;}xwW>gi))*jCQIFty>xAsCAP_46911SOOq{4mi@3L zYtk6gWNK&Ey;rHaNn45F)T*=yR(VsdT;cM5U)Vt?TyABs$oIEK znh(VScqmCH!FZ)qEU&Jrsnw~JHENZ@{@D86wZR}B8u;|`>9c*s0ZE$L@ovm5EI(XW za6DP}czv;WVqZ^qLNr23s2W|3?~a+x|b3N;s^G zL&IZc%Ocn!6ToG;Vt#aZWc2U*mTYtZf}A|jHSpQTg`%gH2_w4pBWHWh9Fj>{raS;i z5)_}t1=B>Uy zRb4X&V$Xb56+$8K*JI|Ps}MffN^#{7K<|{w6-)i==3E&gc3W4jSX@}#A0YAPE5+r^{#2LL2Iq$wMK zq^Z&i!~as|9{l#}?d#VEL*WS1Ha0c}F6RLOnH{K=3aO-mW!tb?4j4TLkW`2e1Yy}W zU7oT5o~y0TR?(~;uG-25kjUgMIvu#>Oxud}aR7Cf2LN>JXa}fvOq&lyXHBNE0Xn;S z+S~G8o=`YyxI5`{IZBKbi$=U2x69>>gzMzjnlmf6>m%hIG z<&`f!Uo=11AJBUErE|S+z;JF56#YN$jNhNROAW@=A$VrIbs=sdNBuuxASm1aoNvUY&tYF1`BN zsUiR%&?j%XovSNL7PEPNY1NMf3VCu;9Ri|n)^@z0Z)ty}?fF(oWp!~t21Ajd5FQNT zn~&xl8!ne;3mgeSQfaG3rKpit>6)9g`c83$NGcHvL}KRje*tCv1Erl<39bMD002ov JPDHLkV1kXGCHVjV literal 770 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87?`v@T^vI!PA{G8?-3Fxa;*OQUGw**i+}2y zDtUEf1WM^T`S=JdRN!hB$qCe5bkpU6)}m0yfD7`AQU$hX8g-bl3FdJrI$qr566G?{ z?PWn)4*dpItau^Vj`7%~#p1-WG0K zJ@@zX>WXh=bKYt5FKXYxePI1V&tuEeUSwuB&Cog5uq3ZVcpt$H(u0xl21lC+~XA4vEyMMsoe7oJIN@vyW z=Vcmm_(WTDu50I>Xji=ZTiS8Mp=gFfLJ8h}_o~iZKKNaQf9r*ET~Fs)(_7~k{%MdX zSXC^4JbLy8$9)wOQ{IOjp41=3<*3=W?cKvW55rz*o&NFRD|>NfKhXU$$Dchsz@hK1 zwm7;#cOTMz!QWXO7*eIsP=ygk>Jh;2G^?i{lM_v_x-5BQa@+&FXmyu@W-8d5EB zjVMVjN=+ Date: Wed, 29 Jun 2011 12:23:05 -0600 Subject: [PATCH 16/24] Fix #803589 (Updated recipe for Infobae) --- recipes/infobae.recipe | 64 +++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/recipes/infobae.recipe b/recipes/infobae.recipe index 9553746449..b577988347 100644 --- a/recipes/infobae.recipe +++ b/recipes/infobae.recipe @@ -1,5 +1,5 @@ __license__ = 'GPL v3' -__copyright__ = '2008-2010, Darko Miletic ' +__copyright__ = '2008-2011, Darko Miletic ' ''' infobae.com ''' @@ -9,7 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class Infobae(BasicNewsRecipe): title = 'Infobae.com' __author__ = 'Darko Miletic and Sujata Raman' - description = 'Informacion Libre las 24 horas' + description = 'Infobae.com es el sitio de noticias con mayor actualizacion de Latinoamérica. Noticias actualizadas las 24 horas, los 365 días del año.' publisher = 'Infobae.com' category = 'news, politics, Argentina' oldest_article = 1 @@ -17,13 +17,13 @@ class Infobae(BasicNewsRecipe): no_stylesheets = True use_embedded_content = False language = 'es_AR' - encoding = 'cp1252' - masthead_url = 'http://www.infobae.com/imgs/header/header.gif' - remove_javascript = True + encoding = 'utf8' + masthead_url = 'http://www.infobae.com/media/img/static/logo-infobae.gif' remove_empty_feeds = True extra_css = ''' - body{font-family:Arial,Helvetica,sans-serif;} - .popUpTitulo{color:#0D4261; font-size: xx-large} + body{font-family: Arial,Helvetica,sans-serif} + img{display: block} + .categoria{font-size: small; text-transform: uppercase} ''' conversion_options = { @@ -31,26 +31,44 @@ class Infobae(BasicNewsRecipe): , 'tags' : category , 'publisher' : publisher , 'language' : language - , 'linearize_tables' : True } - - + + keep_only_tags = [dict(attrs={'class':['titularnota','nota','post-title','post-entry','entry-title','entry-info','entry-content']})] + remove_tags_after = dict(attrs={'class':['interior-noticia','nota-desc','tags']}) + remove_tags = [ + dict(name=['base','meta','link','iframe','object','embed','ins']) + ,dict(attrs={'class':['barranota','tags']}) + ] + feeds = [ - (u'Noticias' , u'http://www.infobae.com/adjuntos/html/RSS/hoy.xml' ) - ,(u'Salud' , u'http://www.infobae.com/adjuntos/html/RSS/salud.xml' ) - ,(u'Tecnologia', u'http://www.infobae.com/adjuntos/html/RSS/tecnologia.xml') - ,(u'Deportes' , u'http://www.infobae.com/adjuntos/html/RSS/deportes.xml' ) + (u'Saludable' , u'http://www.infobae.com/rss/saludable.xml') + ,(u'Economia' , u'http://www.infobae.com/rss/economia.xml' ) + ,(u'En Numeros', u'http://www.infobae.com/rss/rating.xml' ) + ,(u'Finanzas' , u'http://www.infobae.com/rss/finanzas.xml' ) + ,(u'Mundo' , u'http://www.infobae.com/rss/mundo.xml' ) + ,(u'Sociedad' , u'http://www.infobae.com/rss/sociedad.xml' ) + ,(u'Politica' , u'http://www.infobae.com/rss/politica.xml' ) + ,(u'Deportes' , u'http://www.infobae.com/rss/deportes.xml' ) ] - def print_version(self, url): - article_part = url.rpartition('/')[2] - article_id= article_part.partition('-')[0] - return 'http://www.infobae.com/notas/nota_imprimir.php?Idx=' + article_id - - def postprocess_html(self, soup, first): - for tag in soup.findAll(name='strong'): - tag.name = 'b' + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for item in soup.findAll('a'): + limg = item.find('img') + if item.string is not None: + str = item.string + item.replaceWith(str) + else: + if limg: + item.name = 'div' + item.attrs = [] + else: + str = self.tag_to_string(item) + item.replaceWith(str) + for item in soup.findAll('img'): + if not item.has_key('alt'): + item['alt'] = 'image' return soup - From 85e1bac13bf189e02e189d71fb32fb9d0f3ebcd4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 13:00:38 -0600 Subject: [PATCH 17/24] ... --- src/calibre/manual/develop.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/calibre/manual/develop.rst b/src/calibre/manual/develop.rst index fecdf28a47..506615914c 100644 --- a/src/calibre/manual/develop.rst +++ b/src/calibre/manual/develop.rst @@ -187,6 +187,26 @@ in your favorite editor and add the line:: near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. +Having separate "normal" and "development" |app| installs on the same computer +------------------------------------------------------------------------------- + +The calibre source tree is very stable, it rarely breaks, but if you feel the need to run from source on a separate +test library and run the released calibre version with your everyday library, you can achieve this easily using +.bat files or shell scripts to launch |app|. The example below shows how to do this on windows using .bat files (the +instructions for other platforms are the same, just use a BASh script instead of a .bat file) + +To launch the relase version of |app| with your everyday library: + +calibre-normal.bat:: + + calibre.exe "--with-library=C:\path\to\everyday\library folder" + +calibre-dev.bat:: + + set CALIBRE_DEVELOP_FROM=C:\path\to\calibre\checkout\src + calibre.exe "--with-library=C:\path\to\test\library folder" + + Debugging tips ---------------- From bd4462cfb7c6d5ff3e6d04e0099cdc818b278354 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Jun 2011 16:07:20 -0600 Subject: [PATCH 18/24] Athens News by DM. Fixes #803663 (New recipe for Greek newspaper in English - Athens News) --- recipes/athens_news.recipe | 70 ++++++++++++++++++++++++++++++ recipes/icons/athens_news.png | Bin 0 -> 514 bytes src/calibre/db/tables.py | 19 ++++++++ src/calibre/utils/localization.py | 1 + 4 files changed, 90 insertions(+) create mode 100644 recipes/athens_news.recipe create mode 100644 recipes/icons/athens_news.png diff --git a/recipes/athens_news.recipe b/recipes/athens_news.recipe new file mode 100644 index 0000000000..6667faaf0c --- /dev/null +++ b/recipes/athens_news.recipe @@ -0,0 +1,70 @@ +__license__ = 'GPL v3' +__copyright__ = '2011, Darko Miletic ' +''' +www.athensnews.gr +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class AthensNews(BasicNewsRecipe): + title = 'Athens News' + __author__ = 'Darko Miletic' + description = 'Greece in English since 1952' + publisher = 'NEP Publishing Company SA' + category = 'news, politics, Greece, Athens' + oldest_article = 1 + max_articles_per_feed = 200 + no_stylesheets = True + encoding = 'utf8' + use_embedded_content = False + language = 'en_GR' + remove_empty_feeds = True + publication_type = 'newspaper' + masthead_url = 'http://www.athensnews.gr/sites/athensnews/themes/athensnewsv3/images/logo.jpg' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif } + img{margin-bottom: 0.4em; display:block} + .big{font-size: xx-large; font-family: Georgia,serif} + .articlepubdate{font-size: small; color: gray; font-family: Georgia,serif} + .lezanta{font-size: x-small; font-weight: bold; text-align: left; margin-bottom: 1em; display: block} + """ + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + , 'linearize_tables' : True + } + + remove_tags = [ + dict(name=['meta','link']) + ] + keep_only_tags=[ + dict(name='span',attrs={'class':'big'}) + ,dict(name='td', attrs={'class':['articlepubdate','text']}) + ] + remove_attributes=['lang'] + + + feeds = [ + (u'News' , u'http://www.athensnews.gr/category/1/feed' ) + ,(u'Politics' , u'http://www.athensnews.gr/category/8/feed' ) + ,(u'Business' , u'http://www.athensnews.gr/category/2/feed' ) + ,(u'Economy' , u'http://www.athensnews.gr/category/11/feed') + ,(u'Community' , u'http://www.athensnews.gr/category/5/feed' ) + ,(u'Arts' , u'http://www.athensnews.gr/category/3/feed' ) + ,(u'Living in Athens', u'http://www.athensnews.gr/category/7/feed' ) + ,(u'Sports' , u'http://www.athensnews.gr/category/4/feed' ) + ,(u'Travel' , u'http://www.athensnews.gr/category/6/feed' ) + ,(u'Letters' , u'http://www.athensnews.gr/category/44/feed') + ,(u'Media' , u'http://www.athensnews.gr/multimedia/feed' ) + ] + + def print_version(self, url): + return url + '?action=print' + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + return soup diff --git a/recipes/icons/athens_news.png b/recipes/icons/athens_news.png new file mode 100644 index 0000000000000000000000000000000000000000..499a11dbe24f8c806fe4bee918a01d9a2d875b13 GIT binary patch literal 514 zcmV+d0{#7oP)f#j5ZzrP%$Mzgi4V{oIB=*B70SKJk54a&gmNJy2jnQKFdXa}Yz+8gcl*e;4lsw+ zv-iE3H#19xEGW?kp;Ain5df4@0Ck>+AO$=B1~rPJIF99IoO3P&NNJkpa=GN3&*$?X z2mr2Rw6atcK?Q)_Zr5x!arSz>R;#7bNm5=5gb>H$vC(KCx29<-MUo_CS=zP@0aMkg zibYX`VF&=WqI%xjVzKCUyZ?KTB+D}866wK)6JX!>@9%$wLG=FqdpsUj6Qi7GQIvoL z8~gp<^E}seaj{yhEz5#?v}BqFEY)f?rb)_0P9*dF)n>C%P7K4W*Xy&{%(A}R-cHm2 z*6a0XG@=j)kxQwaCLkp)0A5~R<2V5iz9*9(j4@Qk3=G4N6iuJ)0nMao>bl?W?ta>K z4<_LRjVaB=;*cQEbNq#4OomYg9{xiBZ`dGas`_Ttn|Xf5YX1OtIvoz9d5MMSdc$#? z;c$qKub+ml>tPVK+nu5)r_-mwVBk167&ZNf&*y_p6M5Rv;c$3-mn(nvTEiU1qe{W|47sN_U-(C=1djJ3c07*qoM6N<$ Ef+Otj6#xJL literal 0 HcmV?d00001 diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py index 735d2f69a0..edca43528a 100644 --- a/src/calibre/db/tables.py +++ b/src/calibre/db/tables.py @@ -48,6 +48,12 @@ class Table(object): class OneToOneTable(Table): + ''' + Represents data that is unique per book (it may not actually be unique) but + each item is assigned to a book in a one-to-one mapping. For example: uuid, + timestamp, size, etc. + ''' + def read(self, db): self.book_col_map = {} idcol = 'id' if self.metadata['table'] == 'books' else 'book' @@ -66,6 +72,13 @@ class SizeTable(OneToOneTable): class ManyToOneTable(Table): + ''' + Represents data where one data item can map to many books, for example: + series or publisher. + + Each book however has only one value for data of this type. + ''' + def read(self, db): self.id_map = {} self.extra_map = {} @@ -91,6 +104,12 @@ class ManyToOneTable(Table): class ManyToManyTable(ManyToOneTable): + ''' + Represents data that has a many-to-many mapping with books. i.e. each book + can have more than one value and each value can be mapped to more than one + book. For example: tags or authors. + ''' + def read_maps(self, db): for row in db.conn.execute( 'SELECT book, {0} FROM books_{1}_link'.format( diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index f14858c3b6..cdb20b4d6e 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -109,6 +109,7 @@ _extra_lang_codes = { 'en_AU' : _('English (Australia)'), 'en_NZ' : _('English (New Zealand)'), 'en_CA' : _('English (Canada)'), + 'en_GR' : _('English (Greece)'), 'en_IN' : _('English (India)'), 'en_TH' : _('English (Thailand)'), 'en_CY' : _('English (Cyprus)'), From b32521b6bed743594c4c8ffeff9b29ef5c1f5e9e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 10:33:39 +0100 Subject: [PATCH 19/24] Fix problem where row numbers in selections are incorrect after a sort --- src/calibre/gui2/library/views.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index d25325be17..665112005c 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -235,13 +235,8 @@ class BooksView(QTableView): # {{{ self.selected_ids = [idc(r) for r in selected_rows] def sorting_done(self, indexc): - if self.selected_ids: - indices = [self.model().index(indexc(i), 0) for i in - self.selected_ids] - sm = self.selectionModel() - for idx in indices: - sm.select(idx, sm.Select|sm.Rows) - self.scroll_to_row(indices[0].row()) + self.select_rows(self.selected_ids, using_ids=True, change_current=True, + scroll=True) self.selected_ids = [] def sort_by_named_field(self, field, order, reset=True): From 53a4516337c6b5a6dc243e22e15e430e2ddfda71 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 11:10:37 +0100 Subject: [PATCH 20/24] Add the swap_around_comma function. Update template language documentation. --- src/calibre/manual/template_lang.rst | 9 +++++++-- src/calibre/utils/formatter_functions.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 02a77432c9..f9824187e5 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -116,7 +116,7 @@ If you have programming experience, please note that the syntax in this mode (si Many functions use regular expressions. In all cases, regular expression matching is case-insensitive. -The functions available are: +The functions available are listed below. Note that the definitive documentation for functions is available in the section :ref:`Function classification `: * ``lowercase()`` -- return value of the field in lower case. * ``uppercase()`` -- return the value of the field in upper case. @@ -129,6 +129,7 @@ The functions available are: * ``list_item(index, separator)`` -- interpret the field as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed. + * ``swap_around_comma(val) `` -- given a value of the form ``B, A``, return ``A B``. This is most useful for converting names in LN, FN format to FN LN. If there is no comma, the function returns val unchanged. * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want. * ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later). * ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book. @@ -230,13 +231,14 @@ For various values of series_index, the program returns: **All the functions listed under single-function mode can be used in program mode**. To do so, you must supply the value that the function is to act upon as the first parameter, in addition to the parameters documented above. For example, in program mode the parameters of the `test` function are ``test(x, text_if_not_empty, text_if_empty)``. The `x` parameter, which is the value to be tested, will almost always be a variable or a function call, often `field()`. -The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions): +The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions). Note that the definitive documentation for functions is available in the section :ref:`Function classification `: * ``and(value, value, ...)`` -- returns the string "1" if all values are not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers. * ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression * ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats. * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. + * ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. * ``field(name)`` -- returns the metadata field named by ``name``. * ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. @@ -266,7 +268,10 @@ The following functions are available in addition to those described in single-f * ``strcmp(x, y, lt, eq, gt)`` -- does a case-insensitive comparison x and y as strings. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. * ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers. + * ``today()`` -- return a date string for today. This value is designed for use in format_date or days_between, but can be manipulated like any other string. The date is in ISO format. * ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value. + +.. _template_functions_reference: Function classification --------------------------- diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index c6f4bd1b0e..1684b9f85b 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -417,6 +417,18 @@ class BuiltinRe(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement): return re.sub(pattern, replacement, val, flags=re.I) +class BuiltinSwapAroundComma(BuiltinFormatterFunction): + name = 'swap_around_comma' + arg_count = 1 + category = 'String Manipulation' + __doc__ = doc = _('swap_around_comma(val) -- given a value of the form ' + '"B, A", return "A B". This is most useful for converting names ' + 'in LN, FN format to FN LN. If there is no comma, the function ' + 'returns val unchanged') + + def evaluate(self, formatter, kwargs, mi, locals, val): + return re.sub(r'^(.*?),(.*$)', r'\2 \1', val, flags=re.I) + class BuiltinIfempty(BuiltinFormatterFunction): name = 'ifempty' arg_count = 2 @@ -825,6 +837,7 @@ builtin_subitems = BuiltinSubitems() builtin_sublist = BuiltinSublist() builtin_substr = BuiltinSubstr() builtin_subtract = BuiltinSubtract() +builtin_swaparound = BuiltinSwapAroundComma() builtin_switch = BuiltinSwitch() builtin_template = BuiltinTemplate() builtin_test = BuiltinTest() From 3bae54056ff680c4a20f63eaca1cc87274eb64fa Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 11:23:12 +0100 Subject: [PATCH 21/24] Prevent exception if removing from calibre a library that does not exist on the file system. --- src/calibre/gui2/actions/choose_library.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index f96a261790..b233575fa2 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -260,7 +260,8 @@ class ChooseLibraryAction(InterfaceAction): 'The files remain on your computer, if you want ' 'to delete them, you will have to do so manually.') % loc, show=True) - open_local_file(loc) + if os.path.exists(loc): + open_local_file(loc) def backup_status(self, location): dirty_text = 'no' From d16df15022b1791b5c052f4f2aab7ee6d18d696b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 12:40:01 +0100 Subject: [PATCH 22/24] Ensure that a column in a book view cannot be sized to bigger than the displayable area. --- src/calibre/gui2/library/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 665112005c..4ba8b0edcf 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -110,6 +110,7 @@ class BooksView(QTableView): # {{{ self.column_header.sectionMoved.connect(self.save_state) self.column_header.setContextMenuPolicy(Qt.CustomContextMenu) self.column_header.customContextMenuRequested.connect(self.show_column_header_context_menu) + self.column_header.sectionResized.connect(self.column_resized) # }}} self._model.database_changed.connect(self.database_changed) @@ -451,7 +452,9 @@ class BooksView(QTableView): # {{{ traceback.print_exc() old_state['sort_history'] = sh + self.column_header.blockSignals(True) self.apply_state(old_state) + self.column_header.blockSignals(False) # Resize all rows to have the correct height if self.model().rowCount(QModelIndex()) > 0: @@ -460,6 +463,15 @@ class BooksView(QTableView): # {{{ self.was_restored = True + def column_resized(self, col, old_size, new_size): + # arbitrary: scroll bar + header + some + max_width = self.width() - (self.verticalScrollBar().width() + + self.verticalHeader().width() + 10) + if new_size > max_width: + self.column_header.blockSignals(True) + self.setColumnWidth(col, max_width) + self.column_header.blockSignals(False) + # }}} # Initialization/Delegate Setup {{{ From 0ee4cbe48deb729123d62b5d24c9c760a6f4670a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 15:52:24 +0100 Subject: [PATCH 23/24] Better implementation of resize column limit --- src/calibre/gui2/library/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 4ba8b0edcf..377ca4a9b6 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -110,7 +110,7 @@ class BooksView(QTableView): # {{{ self.column_header.sectionMoved.connect(self.save_state) self.column_header.setContextMenuPolicy(Qt.CustomContextMenu) self.column_header.customContextMenuRequested.connect(self.show_column_header_context_menu) - self.column_header.sectionResized.connect(self.column_resized) + self.column_header.sectionResized.connect(self.column_resized, Qt.QueuedConnection) # }}} self._model.database_changed.connect(self.database_changed) From 509285373e185b6d33703354b48db3387c9d915c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Jun 2011 09:24:52 -0600 Subject: [PATCH 24/24] Scroll per pixel --- src/calibre/gui2/library/views.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 377ca4a9b6..54ef308f65 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -51,6 +51,8 @@ class BooksView(QTableView): # {{{ def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True): QTableView.__init__(self, parent) + self.setHorizontalScrollMode(self.ScrollPerPixel) + self.setEditTriggers(self.EditKeyPressed) if tweaks['doubleclick_on_library_view'] == 'edit_cell': self.setEditTriggers(self.DoubleClicked|self.editTriggers()) @@ -110,7 +112,6 @@ class BooksView(QTableView): # {{{ self.column_header.sectionMoved.connect(self.save_state) self.column_header.setContextMenuPolicy(Qt.CustomContextMenu) self.column_header.customContextMenuRequested.connect(self.show_column_header_context_menu) - self.column_header.sectionResized.connect(self.column_resized, Qt.QueuedConnection) # }}} self._model.database_changed.connect(self.database_changed) @@ -463,15 +464,6 @@ class BooksView(QTableView): # {{{ self.was_restored = True - def column_resized(self, col, old_size, new_size): - # arbitrary: scroll bar + header + some - max_width = self.width() - (self.verticalScrollBar().width() + - self.verticalHeader().width() + 10) - if new_size > max_width: - self.column_header.blockSignals(True) - self.setColumnWidth(col, max_width) - self.column_header.blockSignals(False) - # }}} # Initialization/Delegate Setup {{{