diff --git a/recipes/zeitde_sub.recipe b/recipes/zeitde_sub.recipe index 583e86e7ac..dfa52e8504 100644 --- a/recipes/zeitde_sub.recipe +++ b/recipes/zeitde_sub.recipe @@ -131,7 +131,7 @@ class ZeitEPUBAbo(BasicNewsRecipe): browser.form['pass']=self.password browser.submit() # now find the correct file, we will still use the ePub file - epublink = browser.find_link(text_regex=re.compile('.*Ausgabe als Datei im ePub-Format.*')) + epublink = browser.find_link(text_regex=re.compile('.*Download als Datei im ePub-Format für eReader.*')) response = browser.follow_link(epublink) self.report_progress(1,_('next step')) diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 4f3e914f75..742ea6b0f1 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -224,7 +224,7 @@ class TREKSTOR(USBMS): FORMATS = ['epub', 'txt', 'pdf'] VENDOR_ID = [0x1e68] - PRODUCT_ID = [0x0041, 0x0042, 0x0052, + PRODUCT_ID = [0x0041, 0x0042, 0x0052, 0x004e, 0x003e # This is for the EBOOK_PLAYER_5M https://bugs.launchpad.net/bugs/792091 ] BCD = [0x0002] diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py index ca48fb0a7f..38d4cdde06 100644 --- a/src/calibre/ebooks/mobi/mobiml.py +++ b/src/calibre/ebooks/mobi/mobiml.py @@ -30,6 +30,8 @@ CONTENT_TAGS = set(['img', 'hr', 'br']) NOT_VTAGS = HEADER_TAGS | NESTABLE_TAGS | TABLE_TAGS | SPECIAL_TAGS | \ CONTENT_TAGS +LEAF_TAGS = set(['base', 'basefont', 'frame', 'link', 'meta', 'area', 'br', +'col', 'hr', 'img', 'input', 'param']) PAGE_BREAKS = set(['always', 'left', 'right']) COLLAPSE = re.compile(r'[ \t\r\n\v]+') @@ -246,7 +248,17 @@ class MobiMLizer(object): last.text = None else: last = bstate.body[-1] - last.addprevious(anchor) + # We use append instead of addprevious so that inline + # anchors in large blocks point to the correct place. See + # https://bugs.launchpad.net/calibre/+bug/899831 + # This could potentially break if inserting an anchor at + # this point in the markup is illegal, but I cannot think + # of such a case offhand. + if barename(last.tag) in LEAF_TAGS: + last.addprevious(anchor) + else: + last.append(anchor) + istate.ids.clear() if not text: return @@ -528,7 +540,11 @@ class MobiMLizer(object): old_mim = self.opts.mobi_ignore_margins self.opts.mobi_ignore_margins = False - if text or tag in CONTENT_TAGS or tag in NESTABLE_TAGS: + if (text or tag in CONTENT_TAGS or tag in NESTABLE_TAGS or ( + # We have an id but no text and no children, the id should still + # be added. + istate.ids and tag in ('a', 'span', 'i', 'b', 'u') and + len(elem)==0)): self.mobimlize_content(tag, text, bstate, istates) for child in elem: self.mobimlize_elem(child, stylizer, bstate, istates) diff --git a/src/calibre/ebooks/oeb/iterator.py b/src/calibre/ebooks/oeb/iterator.py index 2e423a25a1..bfd2954cd1 100644 --- a/src/calibre/ebooks/oeb/iterator.py +++ b/src/calibre/ebooks/oeb/iterator.py @@ -18,7 +18,8 @@ from calibre.ebooks.chardet import xml_to_unicode from calibre.utils.zipfile import safe_replace from calibre.utils.config import DynamicConfig from calibre.utils.logging import Log -from calibre import guess_type, prints, prepare_string_for_xml +from calibre import (guess_type, prints, prepare_string_for_xml, + xml_replace_entities) from calibre.ebooks.oeb.transforms.cover import CoverManager from calibre.constants import filesystem_encoding @@ -96,13 +97,19 @@ class EbookIterator(object): self.ebook_ext = ext.replace('original_', '') def search(self, text, index, backwards=False): - text = text.lower() + text = prepare_string_for_xml(text.lower()) pmap = [(i, path) for i, path in enumerate(self.spine)] if backwards: pmap.reverse() for i, path in pmap: if (backwards and i < index) or (not backwards and i > index): - if text in open(path, 'rb').read().decode(path.encoding).lower(): + with open(path, 'rb') as f: + raw = f.read().decode(path.encoding) + try: + raw = xml_replace_entities(raw) + except: + pass + if text in raw.lower(): return i def find_missing_css_files(self): diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 928f68573b..2fc14c8238 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -9,7 +9,7 @@ from PyQt4.Qt import QThread, QObject, Qt, QProgressDialog, pyqtSignal, QTimer from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2 import (question_dialog, error_dialog, info_dialog, gprefs, - warning_dialog) + warning_dialog, available_width) from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata import MetaInformation from calibre.constants import preferred_encoding, filesystem_encoding, DEBUG @@ -244,6 +244,7 @@ class Adder(QObject): # {{{ def __init__(self, parent, db, callback, spare_server=None): QObject.__init__(self, parent) self.pd = ProgressDialog(_('Adding...'), parent=parent) + self.pd.setMaximumWidth(min(600, int(available_width()*0.75))) self.spare_server = spare_server self.db = db self.pd.setModal(True) diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index 576b3ca3e7..abb66c1fd8 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -25,7 +25,8 @@ from calibre.utils.logging import Log class BulkConfig(Config): - def __init__(self, parent, db, preferred_output_format=None): + def __init__(self, parent, db, preferred_output_format=None, + has_saved_settings=True): ResizableDialog.__init__(self, parent) self.setup_output_formats(db, preferred_output_format) @@ -54,6 +55,12 @@ class BulkConfig(Config): rb = self.buttonBox.button(self.buttonBox.RestoreDefaults) rb.setVisible(False) self.groups.setMouseTracking(True) + if not has_saved_settings: + o = self.opt_individual_saved_settings + o.setEnabled(False) + o.setToolTip(_('None of the selected books have saved conversion ' + 'settings.')) + o.setChecked(False) def setup_pipeline(self, *args): diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index 0352c9603f..4ba6c198f2 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -70,7 +70,7 @@ if pictureflow is not None: ans = '' except: ans = '' - return ans + return ans.replace('&', '&&') def subtitle(self, index): try: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 6d04cbd443..3244a35545 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -5,8 +5,9 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, itertools, operator from functools import partial +from future_builtins import map from PyQt4.Qt import (QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, @@ -793,8 +794,13 @@ class BooksView(QTableView): # {{{ sel = QItemSelection() m = self.model() max_col = m.columnCount(QModelIndex()) - 1 - for row in rows: - sel.select(m.index(row, 0), m.index(row, max_col)) + # Create a range based selector for each set of contiguous rows + # as supplying selectors for each individual row causes very poor + # performance if a large number of rows has to be selected. + for k, g in itertools.groupby(enumerate(rows), lambda (i,x):i-x): + group = list(map(operator.itemgetter(1), g)) + sel.merge(QItemSelection(m.index(min(group), 0), + m.index(max(group), max_col)), sm.Select) sm.select(sel, sm.ClearAndSelect) def get_selected_ids(self): diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 9413c51a6e..c8eb53ade9 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -252,7 +252,7 @@ class SearchDialog(QDialog, Ui_Dialog): # Milliseconds self.hang_time = self.config.get('hang_time', 75) * 1000 - self.max_results = self.config.get('max_results', 10) + self.max_results = self.config.get('max_results', 15) self.should_open_external = self.config.get('open_external', True) # Number of threads to run for each type of operation diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui index ba5cb2b975..83f42cd0d1 100644 --- a/src/calibre/gui2/store/search/search.ui +++ b/src/calibre/gui2/store/search/search.ui @@ -14,7 +14,7 @@ Get Books - + :/images/store.png:/images/store.png @@ -82,8 +82,8 @@ 0 0 - 173 - 106 + 193 + 127 @@ -254,6 +254,19 @@
widgets.h
+ + search_edit + search + results_view + store_list + select_all_stores + select_invert_stores + select_none_stores + configure + open_external + close + adv_search_button + diff --git a/src/calibre/gui2/store/stores/chitanka_plugin.py b/src/calibre/gui2/store/stores/chitanka_plugin.py index a1a22797c8..58ef109dba 100644 --- a/src/calibre/gui2/store/stores/chitanka_plugin.py +++ b/src/calibre/gui2/store/stores/chitanka_plugin.py @@ -40,9 +40,9 @@ class ChitankaStore(BasicStoreConfig, StorePlugin): d.exec_() def search(self, query, max_results=10, timeout=60): - # check for cyrilic symbols before performing search + # check for cyrillic symbols before performing search uquery = unicode(query.strip(), 'utf-8') - reObj = re.search(u'^[а-яА-Я\\d]{4,}[а-яА-Я\\d\\s]*$', uquery) + reObj = re.search(u'^[а-яА-Я\\d\\s]{3,}$', uquery) if not reObj: return diff --git a/src/calibre/gui2/store/stores/eknigi_plugin.py b/src/calibre/gui2/store/stores/eknigi_plugin.py index ee87b771be..7d88465f62 100644 --- a/src/calibre/gui2/store/stores/eknigi_plugin.py +++ b/src/calibre/gui2/store/stores/eknigi_plugin.py @@ -46,9 +46,9 @@ class eKnigiStore(BasicStoreConfig, StorePlugin): d.exec_() def search(self, query, max_results=10, timeout=60): - # check for cyrilic symbols before performing search + # check for cyrillic symbols before performing search uquery = unicode(query.strip(), 'utf-8') - reObj = re.search(u'^[а-яА-Я\\d]{2,}[а-яА-Я\\d\\s]*$', uquery) + reObj = re.search(u'^[а-яА-Я\\d\\s]{2,}$', uquery) if not reObj: return diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 021617aa4a..f1df707ad4 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -112,7 +112,10 @@ def convert_bulk_ebook(parent, queue, db, book_ids, out_format=None, args=[]): if total == 0: return None, None, None - d = BulkConfig(parent, db, out_format) + has_saved_settings = db.has_conversion_options(book_ids) + + d = BulkConfig(parent, db, out_format, + has_saved_settings=has_saved_settings) if d.exec_() != QDialog.Accepted: return None diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 63db7a561c..e035777c5e 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -359,7 +359,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ 'log will be displayed automatically.')%self.gui_debug, show=True) def esc(self, *args): - self.search.clear() + self.clear_button.click() def start_content_server(self, check_started=True): from calibre.library.server.main import start_threaded_server diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index 2138b2f1eb..fc29df5f02 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -1085,6 +1085,14 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; return cPickle.loads(str(data)) return None + def has_conversion_options(self, ids, format='PIPE'): + ids = tuple(ids) + if len(ids) > 50000: + return True + return self.conn.get(''' + SELECT data FROM conversion_options WHERE book IN %r AND + format=? LIMIT 1'''%(ids,), (format,), all=False) is not None + def delete_conversion_options(self, id, format, commit=True): self.conn.execute('DELETE FROM conversion_options WHERE book=? AND format=?', (id, format.upper()))