diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index ec0f28273f..bdac284af2 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -594,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX -from calibre.devices.nook.driver import NOOK, NOOK_COLOR, NOOK_TSR +from calibre.devices.nook.driver import NOOK, NOOK_COLOR from calibre.devices.prs505.driver import PRS505 from calibre.devices.user_defined.driver import USER_DEFINED from calibre.devices.android.driver import ANDROID, S60 @@ -603,10 +603,11 @@ from calibre.devices.eslick.driver import ESLICK, EBK52 from calibre.devices.nuut2.driver import NUUT2 from calibre.devices.iriver.driver import IRIVER_STORY from calibre.devices.binatone.driver import README -from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK +from calibre.devices.hanvon.driver import (N516, EB511, ALEX, AZBOOKA, THEBOOK, + LIBREAIR) from calibre.devices.edge.driver import EDGE -from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS, \ - SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER +from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS, + SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER) from calibre.devices.sne.driver import SNE from calibre.devices.misc import (PALMPRE, AVANT, SWEEX, PDNOVEL, GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, ALURATEK_COLOR, @@ -693,7 +694,7 @@ plugins += [ KINDLE, KINDLE2, KINDLE_DX, - NOOK, NOOK_COLOR, NOOK_TSR, + NOOK, NOOK_COLOR, PRS505, ANDROID, S60, @@ -716,7 +717,7 @@ plugins += [ EB600, README, N516, - THEBOOK, + THEBOOK, LIBREAIR, EB511, ELONEX, TECLAST_K3, diff --git a/src/calibre/devices/hanvon/driver.py b/src/calibre/devices/hanvon/driver.py index f9dec178c6..3798257c2d 100644 --- a/src/calibre/devices/hanvon/driver.py +++ b/src/calibre/devices/hanvon/driver.py @@ -52,6 +52,18 @@ class THEBOOK(N516): EBOOK_DIR_MAIN = 'My books' WINDOWS_CARD_A_MEM = '_FILE-STOR_GADGE' +class LIBREAIR(N516): + name = 'Libre Air Driver' + gui_name = 'Libre Air' + description = _('Communicate with the Libre Air reader.') + author = 'Kovid Goyal' + FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'rtf', 'txt', 'pdf'] + + BCD = [0x399] + VENDOR_NAME = 'ALURATEK' + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '_FILE-STOR_GADGET' + EBOOK_DIR_MAIN = 'Books' + class ALEX(N516): name = 'Alex driver' diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index e09fb7eaf9..2fe8f667b0 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -81,55 +81,27 @@ class NOOK(USBMS): return [x.replace('#', '_') for x in components] class NOOK_COLOR(NOOK): - gui_name = _('Nook Color') - description = _('Communicate with the Nook Color eBook reader.') + description = _('Communicate with the Nook Color and TSR eBook readers.') - PRODUCT_ID = [0x002] + PRODUCT_ID = [0x002, 0x003] BCD = [0x216] - WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK' + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK' EBOOK_DIR_MAIN = 'My Files' + def upload_cover(self, path, filename, metadata, filepath): + pass + + def get_carda_ebook_dir(self, for_upload=False): + if for_upload: + return self.EBOOK_DIR_MAIN + return '' + def create_upload_path(self, path, mdata, fname, create_dirs=True): - filepath = NOOK.create_upload_path(self, path, mdata, fname, - create_dirs=False) - edm = self.EBOOK_DIR_MAIN - subdir = 'Books' - if mdata.tags: - if _('News') in mdata.tags: - subdir = 'Magazines' - filepath = filepath.replace(os.sep+edm+os.sep, - os.sep+edm+os.sep+subdir+os.sep) - filedir = os.path.dirname(filepath) - if create_dirs and not os.path.exists(filedir): - os.makedirs(filedir) - - return filepath - - def upload_cover(self, path, filename, metadata, filepath): - pass - - def get_carda_ebook_dir(self, for_upload=False): - if for_upload: - return 'My Files/Books' - return '' - -class NOOK_TSR(NOOK): - gui_name = _('Nook Simple') - description = _('Communicate with the Nook TSR eBook reader.') - - PRODUCT_ID = [0x003] - BCD = [0x216] - - EBOOK_DIR_MAIN = 'My Files/Books' - WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK' - - def upload_cover(self, path, filename, metadata, filepath): - pass - - def get_carda_ebook_dir(self, for_upload=False): - if for_upload: - return 'My Files/Books' - return '' + is_news = mdata.tags and _('News') in mdata.tags + subdir = 'Magazines' if is_news else 'Books' + path = os.path.join(path, subdir) + return USBMS.create_upload_path(self, path, mdata, fname, + create_dirs=create_dirs) diff --git a/src/calibre/ebooks/epub/output.py b/src/calibre/ebooks/epub/output.py index bea90eeba8..bb515f95a4 100644 --- a/src/calibre/ebooks/epub/output.py +++ b/src/calibre/ebooks/epub/output.py @@ -394,6 +394,13 @@ class EPUBOutput(OutputFormatPlugin): for tag in XPath('//h:img[@src]')(root): tag.set('src', tag.get('src', '').replace('&', '')) + # ADE whimpers in fright when it encounters a outside a + # + in_table = XPath('ancestor::h:table') + for tag in XPath('//h:td|//h:tr|//h:th')(root): + if not in_table(tag): + tag.tag = XHTML('div') + special_chars = re.compile(u'[\u200b\u00ad]') for elem in root.iterdescendants(): if getattr(elem, 'text', False): @@ -413,7 +420,7 @@ class EPUBOutput(OutputFormatPlugin): rule.style.removeProperty('margin-left') # padding-left breaks rendering in webkit and gecko rule.style.removeProperty('padding-left') - # Change whitespace:pre to pre-line to accommodate readers that + # Change whitespace:pre to pre-wrap to accommodate readers that # cannot scroll horizontally for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE): style = rule.style diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 3d5f6c00ef..ce6c46c6cf 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -455,13 +455,16 @@ class HTMLInput(InputFormatPlugin): bhref = os.path.basename(link) id, href = self.oeb.manifest.generate(id='added', href=bhref) + guessed = self.guess_type(href)[0] + media_type = guessed or self.BINARY_MIME + if 'text' in media_type: + self.log.warn('Ignoring link to text file %r'%link_) + return None + self.oeb.log.debug('Added', link) self.oeb.container = self.DirContainer(os.path.dirname(link), self.oeb.log, ignore_opf=True) # Load into memory - guessed = self.guess_type(href)[0] - media_type = guessed or self.BINARY_MIME - item = self.oeb.manifest.add(id, href, media_type) item.html_input_href = bhref if guessed in self.OEB_STYLES: diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py index 2275552c15..493767e233 100644 --- a/src/calibre/ebooks/mobi/mobiml.py +++ b/src/calibre/ebooks/mobi/mobiml.py @@ -442,9 +442,12 @@ class MobiMLizer(object): if tag in TABLE_TAGS and self.ignore_tables: tag = 'span' if tag == 'td' else 'div' - # GR: Added 'width', 'border' and 'scope' + if tag == 'table': + css = style.cssdict() + if 'border' in css or 'border-width' in css: + elem.set('border', '1') if tag in TABLE_TAGS: - for attr in ('rowspan', 'colspan','width','border','scope'): + for attr in ('rowspan', 'colspan', 'width', 'border', 'scope'): if attr in elem.attrib: istate.attrib[attr] = elem.attrib[attr] if tag == 'q': diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 72655afd12..f49c6db59a 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -1110,6 +1110,8 @@ class DeviceBooksModel(BooksModel): # {{{ if self.last_search: self.searched.emit(True) + def research(self, reset=True): + self.search(self.last_search, reset) def sort(self, col, order, reset=True): descending = order != Qt.AscendingOrder @@ -1171,6 +1173,8 @@ class DeviceBooksModel(BooksModel): # {{{ self.custom_columns = {} self.db = db self.map = list(range(0, len(db))) + self.research(reset=False) + self.resort() def cover(self, row): item = self.db[self.map[row]] @@ -1319,8 +1323,6 @@ class DeviceBooksModel(BooksModel): # {{{ ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname, 'left')] return QVariant(ans) - - return NONE def headerData(self, section, orientation, role): diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index f59473851f..3ca898d15a 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -48,7 +48,7 @@ class BooksView(QTableView): # {{{ files_dropped = pyqtSignal(object) add_column_signal = pyqtSignal() - def __init__(self, parent, modelcls=BooksModel): + def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True): QTableView.__init__(self, parent) self.setEditTriggers(self.EditKeyPressed) @@ -60,8 +60,12 @@ class BooksView(QTableView): # {{{ elif tweaks['doubleclick_on_library_view'] == 'edit_metadata': # Must not enable single-click to edit, or the field will remain # open in edit mode underneath the edit metadata dialog - self.doubleClicked.connect( - partial(parent.iactions['Edit Metadata'].edit_metadata, checked=False)) + if use_edit_metadata_dialog: + self.doubleClicked.connect( + partial(parent.iactions['Edit Metadata'].edit_metadata, + checked=False)) + else: + self.setEditTriggers(self.DoubleClicked|self.editTriggers()) self.drag_allowed = True self.setDragEnabled(True) @@ -792,7 +796,8 @@ class BooksView(QTableView): # {{{ class DeviceBooksView(BooksView): # {{{ def __init__(self, parent): - BooksView.__init__(self, parent, DeviceBooksModel) + BooksView.__init__(self, parent, DeviceBooksModel, + use_edit_metadata_dialog=False) self.can_add_columns = False self.columns_resized = False self.resize_on_select = False diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index b3c7873b45..3b581be701 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -22,11 +22,7 @@ from calibre.library.coloring import (Rule, conditionable_columns, class ConditionEditor(QWidget): # {{{ - def __init__(self, fm, parent=None): - QWidget.__init__(self, parent) - self.fm = fm - - self.action_map = { + ACTION_MAP = { 'bool' : ( (_('is true'), 'is true',), (_('is false'), 'is false'), @@ -61,10 +57,17 @@ class ConditionEditor(QWidget): # {{{ (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), - } + } - for x in ('float', 'rating', 'datetime'): - self.action_map[x] = self.action_map['int'] + for x in ('float', 'rating', 'datetime'): + ACTION_MAP[x] = ACTION_MAP['int'] + + + def __init__(self, fm, parent=None): + QWidget.__init__(self, parent) + self.fm = fm + + self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) @@ -446,9 +449,15 @@ class RulesModel(QAbstractListModel): # {{{ def condition_to_html(self, condition): c, a, v = condition + action_name = a + for actions in ConditionEditor.ACTION_MAP.itervalues(): + for trans, ac in actions: + if ac == a: + action_name = trans + return ( _('
  • If the %s column %s value: %s') % - (c, a, prepare_string_for_xml(v))) + (c, action_name, prepare_string_for_xml(v))) # }}} diff --git a/src/calibre/gui2/store/config/chooser/chooser_widget.py b/src/calibre/gui2/store/config/chooser/chooser_widget.py index a9399028f8..5e7eca8c66 100644 --- a/src/calibre/gui2/store/config/chooser/chooser_widget.py +++ b/src/calibre/gui2/store/config/chooser/chooser_widget.py @@ -6,7 +6,7 @@ __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import (QWidget, QIcon, QDialog) +from PyQt4.Qt import (QWidget, QIcon, QDialog, QComboBox) from calibre.gui2.store.config.chooser.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.config.chooser.chooser_widget_ui import Ui_Form @@ -18,6 +18,8 @@ class StoreChooserWidget(QWidget, Ui_Form): self.setupUi(self) self.query.initialize('store_config_chooser_query') + self.query.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) + self.query.setMinimumContentsLength(25) self.adv_search_builder.setIcon(QIcon(I('search.png'))) diff --git a/src/calibre/gui2/store/mobileread/store_dialog.py b/src/calibre/gui2/store/mobileread/store_dialog.py index 8908c9bb68..749f96a614 100644 --- a/src/calibre/gui2/store/mobileread/store_dialog.py +++ b/src/calibre/gui2/store/mobileread/store_dialog.py @@ -7,7 +7,7 @@ __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import (Qt, QDialog, QIcon) +from PyQt4.Qt import (Qt, QDialog, QIcon, QComboBox) from calibre.gui2.store.mobileread.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.mobileread.models import BooksModel @@ -21,6 +21,8 @@ class MobileReadStoreDialog(QDialog, Ui_Dialog): self.plugin = plugin self.search_query.initialize('store_mobileread_search') + self.search_query.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) + self.search_query.setMinimumContentsLength(25) self.adv_search_button.setIcon(QIcon(I('search.png'))) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index f406b4923e..7db4d1bbaf 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -10,7 +10,8 @@ import re from random import shuffle from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel, - QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout) + QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout, + QComboBox) from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2.progress_indicator import ProgressIndicator @@ -57,6 +58,8 @@ class SearchDialog(QDialog, Ui_Dialog): # Set the search query self.search_edit.setText(query) + self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) + self.search_edit.setMinimumContentsLength(25) # Create and add the progress indicator self.pi = ProgressIndicator(self, 24) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index da5029bab3..a6a852fbdd 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -610,7 +610,7 @@ class TagTreeItem(object): # {{{ self.temporary = temporary self.tag = Tag(data, category=category_key, is_editable=category_key not in ['news', 'search', 'identifiers'], - is_searchable=category_key not in ['news', 'search']) + is_searchable=category_key not in ['search']) elif self.type == self.TAG: self.icon_state_map[0] = QVariant(data.icon) @@ -1642,7 +1642,13 @@ class TagsModel(QAbstractItemModel): # {{{ for node in self.category_nodes: if node.tag.state: - ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state])) + if node.category_key == "news": + if node_searches[node.tag.state] == 'true': + ans.append('tags:=news') + else: + ans.append('( not tags:=news )') + else: + ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state])) key = node.category_key for tag_item in node.child_tags(): diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 079af59286..02a77432c9 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -417,7 +417,7 @@ You might find the following tips useful. * Create a custom composite column to test templates. Once you have the column, you can change its template simply by double-clicking on the column. Hide the column when you are not testing. * Templates can use other templates by referencing a composite custom column. - * In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template ``{null}``. This template will always evaluate to an empty string. + * In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template ``{}``. This template will always evaluate to an empty string. * The technique described above to show numbers even if they have a zero value works with the standard field series_index. .. toctree:: diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py index 1eb504d282..dbedef6dbe 100644 --- a/src/calibre/web/feeds/recipes/collection.py +++ b/src/calibre/web/feeds/recipes/collection.py @@ -101,6 +101,7 @@ def get_custom_recipe_collection(*args): if recipe_class is not None: rmap['custom:%s'%id_] = recipe_class except: + print 'Failed to load recipe from: %r'%fname import traceback traceback.print_exc() continue