mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge
This commit is contained in:
commit
7fc2bbc947
@ -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',
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(self_closing_bad_tags)),
|
||||
re.IGNORECASE)
|
||||
|
||||
def close_self_closing_tags(raw):
|
||||
return _self_closing_pat.sub(r'<\g<tag>\g<arg>></\g<tag>>', 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'<(?P<tag>div|a|span|video|audio|iframe)(?=[\s/])(?P<arg>[^>]*)/>',
|
||||
r'<\g<tag>\g<arg>></\g<tag>>', ans)
|
||||
ans = close_self_closing_tags(ans)
|
||||
return ans
|
||||
if isinstance(data, unicode):
|
||||
return data.encode('utf-8')
|
||||
|
@ -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_()
|
||||
|
@ -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 \
|
||||
|
@ -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)
|
||||
|
@ -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): # {{{
|
||||
|
@ -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('''
|
||||
<span style="color: {c}; background-color: {bg1}"> {st} </span>
|
||||
<span style="color: {c}; background-color: {bg2}"> {st} </span>
|
||||
'''.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_()
|
||||
|
Loading…
x
Reference in New Issue
Block a user