diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 7e6ef88d8a..e213fd162c 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -177,7 +177,7 @@ class ANDROID(USBMS): 'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA', 'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON', 'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP', - 'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C'] + 'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD'] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', @@ -193,7 +193,7 @@ class ANDROID(USBMS): 'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855', 'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW', 'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER', - 'GT-S5830L_CARD'] + 'GT-S5830L_CARD', 'UNIVERSE'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index 877b15c24a..f6975b3cb8 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -304,7 +304,10 @@ def read_sr_patterns(path, log=None): def main(args=sys.argv): log = Log() parser, plumber = create_option_parser(args, log) - opts = parser.parse_args(args)[0] + opts, leftover_args = parser.parse_args(args) + if len(leftover_args) > 3: + log.error('Extra arguments not understood:', u', '.join(leftover_args[3:])) + return 1 for x in ('read_metadata_from_opf', 'cover'): if getattr(opts, x, None) is not None: setattr(opts, x, abspath(getattr(opts, x))) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index 86a4668b9b..ddeba79365 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -148,7 +148,7 @@ class HeuristicProcessor(object): return wordcount.words def markup_italicis(self, html): - self.log.debug("\n\n\nitalicize debugging \n\n\n") + #self.log.debug("\n\n\nitalicize debugging \n\n\n") ITALICIZE_WORDS = [ 'Etc.', 'etc.', 'viz.', 'ie.', 'i.e.', 'Ie.', 'I.e.', 'eg.', 'e.g.', 'Eg.', 'E.g.', 'et al.', 'et cetera', 'n.b.', 'N.b.', @@ -184,6 +184,9 @@ class HeuristicProcessor(object): except OverflowError: # match.group(0) was too large to be compiled into a regex continue + except re.error: + # the match was not a valid regular expression + continue return html diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 6cacb34edc..c2ec6f9bce 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -113,6 +113,11 @@ class HTMLFile(object): raise IOError(msg) raise IgnoreFile(msg, err.errno) + if not src: + if level == 0: + raise ValueError('The file %s is empty'%self.path) + self.is_binary = True + if not self.is_binary: if not encoding: encoding = detect_xml_encoding(src[:4096], verbose=verbose)[1] diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index a18e528a51..252d5b34b3 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -81,6 +81,23 @@ _css_url_re = re.compile(r'url\s*\([\'"]{0,1}(.*?)[\'"]{0,1}\)', re.I) _css_import_re = re.compile(r'@import "(.*?)"') _archive_re = re.compile(r'[^ ]+') +# Tags that should not be self closed in epub output +self_closing_bad_tags = {'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', +'bdo', 'blockquote', 'body', 'button', 'cite', 'code', 'dd', 'del', 'details', +'dfn', 'div', 'dl', 'dt', 'em', 'fieldset', 'figcaption', 'figure', 'footer', +'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'i', 'ins', 'kbd', +'label', 'legend', 'li', 'map', 'mark', 'meter', 'nav', 'ol', 'output', 'p', +'pre', 'progress', 'q', 'rp', 'rt', 'samp', 'section', 'select', 'small', +'span', 'strong', 'sub', 'summary', 'sup', 'textarea', 'time', 'ul', 'var', +'video'} + +_self_closing_pat = re.compile( + r'<(?P%s)(?=[\s/])(?P[^>]*)/>'%('|'.join(self_closing_bad_tags)), + re.IGNORECASE) + +def close_self_closing_tags(raw): + return _self_closing_pat.sub(r'<\g\g>>', raw) + def iterlinks(root, find_links_in_css=True): ''' Iterate over all links in a OEB Document. @@ -938,13 +955,10 @@ class Manifest(object): if isinstance(data, etree._Element): ans = xml2str(data, pretty_print=self.oeb.pretty_print) if self.media_type in OEB_DOCS: - # Convert self closing div|span|a|video|audio|iframe tags + # Convert self closing div|span|a|video|audio|iframe|etc tags # to normally closed ones, as they are interpreted # incorrectly by some browser based renderers - ans = re.sub( - # tag name followed by either a space or a / - r'<(?Pdiv|a|span|video|audio|iframe)(?=[\s/])(?P[^>]*)/>', - r'<\g\g>>', ans) + ans = close_self_closing_tags(ans) return ans if isinstance(data, unicode): return data.encode('utf-8') diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 00b8b176c3..0a361209e6 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -10,7 +10,7 @@ from functools import partial from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QIcon, QSize, - QCoreApplication) + QCoreApplication, pyqtSignal) from calibre import isbytestring, sanitize_file_name_unicode from calibre.constants import filesystem_encoding, iswindows @@ -142,6 +142,7 @@ class ChooseLibraryAction(InterfaceAction): dont_add_to = frozenset(['context-menu-device']) action_add_menu = True action_menu_clone_qaction = _('Switch/create library...') + restore_view_state = pyqtSignal(object) def genesis(self): self.base_text = _('%d books') @@ -206,6 +207,17 @@ class ChooseLibraryAction(InterfaceAction): self.maintenance_menu.addAction(ac) self.choose_menu.addMenu(self.maintenance_menu) + self.view_state_map = {} + self.restore_view_state.connect(self._restore_view_state, + type=Qt.QueuedConnection) + + @property + def preserve_state_on_switch(self): + ans = getattr(self, '_preserve_state_on_switch', None) + if ans is None: + self._preserve_state_on_switch = ans = \ + self.gui.library_view.preserve_state(require_selected_ids=False) + return ans def pick_random(self, *args): self.gui.iactions['Pick Random Book'].pick_random() @@ -221,6 +233,13 @@ class ChooseLibraryAction(InterfaceAction): def library_changed(self, db): self.stats.library_used(db) self.build_menus() + state = self.view_state_map.get(self.stats.canonicalize_path( + db.library_path), None) + if state is not None: + self.restore_view_state.emit(state) + + def _restore_view_state(self, state): + self.preserve_state_on_switch.state = state def initialization_complete(self): self.library_changed(self.gui.library_view.model().db) @@ -401,8 +420,11 @@ class ChooseLibraryAction(InterfaceAction): def switch_requested(self, location): if not self.change_library_allowed(): return + db = self.gui.library_view.model().db + current_lib = self.stats.canonicalize_path(db.library_path) + self.view_state_map[current_lib] = self.preserve_state_on_switch.state loc = location.replace('/', os.sep) - exists = self.gui.library_view.model().db.exists_at(loc) + exists = db.exists_at(loc) if not exists: d = MovedDialog(self.stats, location, self.gui) ret = d.exec_() diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index abb66c1fd8..5324a83865 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -126,7 +126,8 @@ class BulkConfig(Config): def setup_output_formats(self, db, preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.lower() - output_formats = sorted(available_output_formats()) + output_formats = sorted(available_output_formats(), + key=lambda x:{'EPUB':'!A', 'MOBI':'!B'}.get(x.upper(), x)) output_formats.remove('oeb') preferred_output_format = preferred_output_format if \ preferred_output_format and preferred_output_format \ diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index c889df71fd..9160c820bd 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -242,7 +242,8 @@ class Config(ResizableDialog, Ui_Dialog): preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.lower() - output_formats = sorted(available_output_formats()) + output_formats = sorted(available_output_formats(), + key=lambda x:{'EPUB':'!A', 'MOBI':'!B'}.get(x.upper(), x)) output_formats.remove('oeb') input_format, input_formats = get_input_format_for_book(db, book_id, preferred_input_format) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 5e394fbadb..106ee89808 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -32,8 +32,10 @@ class PreserveViewState(object): # {{{ and dont affect the scroll position. ''' - def __init__(self, view, preserve_hpos=True, preserve_vpos=True): + def __init__(self, view, preserve_hpos=True, preserve_vpos=True, + require_selected_ids=True): self.view = view + self.require_selected_ids = require_selected_ids self.selected_ids = set() self.current_id = None self.preserve_hpos = preserve_hpos @@ -51,15 +53,28 @@ class PreserveViewState(object): # {{{ traceback.print_exc() def __exit__(self, *args): - if self.selected_ids: + if self.selected_ids or not self.require_selected_ids: if self.current_id is not None: self.view.current_id = self.current_id - self.view.select_rows(self.selected_ids, using_ids=True, - scroll=False, change_current=self.current_id is None) + if self.selected_ids: + self.view.select_rows(self.selected_ids, using_ids=True, + scroll=False, change_current=self.current_id is None) if self.preserve_vpos: self.view.verticalScrollBar().setValue(self.vscroll) if self.preserve_hpos: self.view.horizontalScrollBar().setValue(self.hscroll) + + @dynamic_property + def state(self): + def fget(self): + self.__enter__() + return {x:getattr(self, x) for x in ('selected_ids', 'current_id', + 'vscroll', 'hscroll')} + def fset(self, state): + for k, v in state.iteritems(): setattr(self, k, v) + self.__exit__() + return property(fget=fget, fset=fset) + # }}} class BooksView(QTableView): # {{{ diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index 22bd94c595..46295bb9d4 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -10,7 +10,8 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import (QWidget, QDialog, QLabel, QGridLayout, QComboBox, QSize, QLineEdit, QIntValidator, QDoubleValidator, QFrame, QColor, Qt, QIcon, QScrollArea, QPushButton, QVBoxLayout, QDialogButtonBox, QToolButton, - QListView, QAbstractListModel, pyqtSignal, QSizePolicy, QSpacerItem) + QListView, QAbstractListModel, pyqtSignal, QSizePolicy, QSpacerItem, + QApplication) from calibre import prepare_string_for_xml from calibre.utils.icu import sort_key @@ -259,33 +260,36 @@ class RuleEditor(QDialog): # {{{ l.addWidget(l3, 2, 2) self.color_box = QComboBox(self) + self.color_label = QLabel('Sample text Sample text') + self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 3) - l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 4) + l.addWidget(self.color_label, 2, 4) + l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 5) self.l4 = l4 = QLabel( _('Only if the following conditions are all satisfied:')) - l.addWidget(l4, 3, 0, 1, 5) + l.addWidget(l4, 3, 0, 1, 6) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) - l.addWidget(sa, 4, 0, 1, 5) + l.addWidget(sa, 4, 0, 1, 6) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) - l.addWidget(b, 5, 0, 1, 5) + l.addWidget(b, 5, 0, 1, 6) b.clicked.connect(self.add_blank_condition) self.l5 = l5 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) - l.addWidget(l5, 6, 0, 1, 5) + l.addWidget(l5, 6, 0, 1, 6) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) - l.addWidget(bb, 7, 0, 1, 5) + l.addWidget(bb, 7, 0, 1, 6) self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) @@ -308,8 +312,21 @@ class RuleEditor(QDialog): # {{{ self.color_box.addItems(QColor.colorNames()) self.color_box.setCurrentIndex(0) + self.update_color_label() + self.color_box.currentIndexChanged.connect(self.update_color_label) self.resize(self.sizeHint()) + def update_color_label(self): + pal = QApplication.palette() + bg1 = unicode(pal.color(pal.Base).name()) + bg2 = unicode(pal.color(pal.AlternateBase).name()) + c = unicode(self.color_box.currentText()) + self.color_label.setText(''' +  {st}  +  {st}  + '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) + + def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) @@ -610,14 +627,13 @@ class EditRules(QWidget): # {{{ # }}} if __name__ == '__main__': - from PyQt4.Qt import QApplication app = QApplication([]) from calibre.library import db db = db() - if False: + if True: d = RuleEditor(db.field_metadata) d.add_blank_condition() d.exec_()