mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Merge from trunk
This commit is contained in:
commit
18ee0e2357
@ -19,6 +19,48 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.8.68
|
||||
date: 2012-09-07
|
||||
|
||||
new features:
|
||||
- title: "Drivers for the Nokia N9, Viewsonic 7e, Prestigio PER3274B and Coby Kyros 7035 "
|
||||
tickets: [1046794,1046544]
|
||||
|
||||
- title: "Add a tutorial on creating catalogs to the User Manual and a link to it in the create catalogs dialog"
|
||||
|
||||
- title: "Wireless device connections: Add an option to force calibre to listen on a particular IP address. Access it by customizing the plugin in Preferences->Plugins"
|
||||
|
||||
- title: "Android driver: Add an extra customization option to configure the directory to which ebooks are sent on the storage cards."
|
||||
tickets: [1045045]
|
||||
|
||||
- title: "Add an option under Preferences->Look & Feel->Book Details to hide the cover in the book details panel"
|
||||
|
||||
- title: "The Calibre Companion Android app that allows wireless connection of Android device to calibre is out of beta. See https://play.google.com/stor/apps/details?id=com.multipie.calibreandroid"
|
||||
|
||||
bug fixes:
|
||||
- title: "Fix sorting by author not working in the device view in calibre when connected to iTunes"
|
||||
tickets: [1044619]
|
||||
|
||||
- title: "Fix using the 'configure this device' menu action not validating settings"
|
||||
|
||||
- title: "Device drivers: Ignore corrupted entries in metadata.calibre, instead of raising an error"
|
||||
|
||||
- title: "PDF Output: Do not error out when generating an outline which points to pages that have been removed."
|
||||
tickets: [1044799]
|
||||
|
||||
- title: "PDF Output: Fix incorrect page numbers being generated in the outline when converting some books"
|
||||
|
||||
- title: "PDF Output: Reduce memory consumption when writing out the PDF file, by using a stream"
|
||||
|
||||
- title: "EPUB metadata: When there are multiple <dc:date> tags use the one with the earliest date as the published date"
|
||||
|
||||
improved recipes:
|
||||
- Wall Street journal (subscription version)
|
||||
- Houston Chronicle
|
||||
- Various Romanian news sources
|
||||
- Business Week Magazine
|
||||
- Arcamax
|
||||
|
||||
- version: 0.8.67
|
||||
date: 2012-08-31
|
||||
|
||||
|
@ -31,15 +31,13 @@ class Arcamax(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['comics-header']}),
|
||||
dict(name='b', attrs={'class':['current']}),
|
||||
dict(name='article', attrs={'class':['comic']}),
|
||||
keep_only_tags = [dict(name='article', attrs={'class':['comic']}),
|
||||
]
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'id':['comicfull' ]}),
|
||||
dict(name='div', attrs={'class':['calendar' ]}),
|
||||
dict(name='nav', attrs={'class':['calendar-nav' ]}),
|
||||
]
|
||||
#remove_tags = [dict(name='div', attrs={'id':['comicfull' ]}),
|
||||
#dict(name='div', attrs={'class':['calendar' ]}),
|
||||
#dict(name='nav', attrs={'class':['calendar-nav' ]}),
|
||||
#]
|
||||
|
||||
def parse_index(self):
|
||||
feeds = []
|
||||
@ -48,20 +46,20 @@ class Arcamax(BasicNewsRecipe):
|
||||
#(u"9 Chickweed Lane", u"http://www.arcamax.com/ninechickweedlane"),
|
||||
#(u"Agnes", u"http://www.arcamax.com/agnes"),
|
||||
#(u"Andy Capp", u"http://www.arcamax.com/andycapp"),
|
||||
(u"BC", u"http://www.arcamax.com/bc"),
|
||||
(u"BC", u"http://www.arcamax.com/thefunnies/bc"),
|
||||
#(u"Baby Blues", u"http://www.arcamax.com/babyblues"),
|
||||
#(u"Beetle Bailey", u"http://www.arcamax.com/beetlebailey"),
|
||||
(u"Blondie", u"http://www.arcamax.com/blondie"),
|
||||
(u"Blondie", u"http://www.arcamax.com/thefunnies/blondie"),
|
||||
#u"Boondocks", u"http://www.arcamax.com/boondocks"),
|
||||
#(u"Cathy", u"http://www.arcamax.com/cathy"),
|
||||
#(u"Daddys Home", u"http://www.arcamax.com/daddyshome"),
|
||||
(u"Dilbert", u"http://www.arcamax.com/dilbert"),
|
||||
(u"Dilbert", u"http://www.arcamax.com/thefunnies/dilbert"),
|
||||
#(u"Dinette Set", u"http://www.arcamax.com/thedinetteset"),
|
||||
(u"Dog Eat Doug", u"http://www.arcamax.com/dogeatdoug"),
|
||||
(u"Doonesbury", u"http://www.arcamax.com/doonesbury"),
|
||||
(u"Dog Eat Doug", u"http://www.arcamax.com/thefunnies/dogeatdoug"),
|
||||
(u"Doonesbury", u"http://www.arcamax.com/thefunnies/doonesbury"),
|
||||
#(u"Dustin", u"http://www.arcamax.com/dustin"),
|
||||
(u"Family Circus", u"http://www.arcamax.com/familycircus"),
|
||||
(u"Garfield", u"http://www.arcamax.com/garfield"),
|
||||
(u"Family Circus", u"http://www.arcamax.com/thefunnies/familycircus"),
|
||||
(u"Garfield", u"http://www.arcamax.com/thefunnies/garfield"),
|
||||
#(u"Get Fuzzy", u"http://www.arcamax.com/getfuzzy"),
|
||||
#(u"Girls and Sports", u"http://www.arcamax.com/girlsandsports"),
|
||||
#(u"Hagar the Horrible", u"http://www.arcamax.com/hagarthehorrible"),
|
||||
@ -70,16 +68,16 @@ class Arcamax(BasicNewsRecipe):
|
||||
#(u"Luann", u"http://www.arcamax.com/luann"),
|
||||
#(u"Momma", u"http://www.arcamax.com/momma"),
|
||||
#(u"Mother Goose and Grimm", u"http://www.arcamax.com/mothergooseandgrimm"),
|
||||
(u"Mutts", u"http://www.arcamax.com/mutts"),
|
||||
(u"Mutts", u"http://www.arcamax.com/thefunnies/mutts"),
|
||||
#(u"Non Sequitur", u"http://www.arcamax.com/nonsequitur"),
|
||||
#(u"Pearls Before Swine", u"http://www.arcamax.com/pearlsbeforeswine"),
|
||||
#(u"Pickles", u"http://www.arcamax.com/pickles"),
|
||||
#(u"Red and Rover", u"http://www.arcamax.com/redandrover"),
|
||||
#(u"Rubes", u"http://www.arcamax.com/rubes"),
|
||||
#(u"Rugrats", u"http://www.arcamax.com/rugrats"),
|
||||
(u"Speed Bump", u"http://www.arcamax.com/speedbump"),
|
||||
(u"Wizard of Id", u"http://www.arcamax.com/wizardofid"),
|
||||
(u"Zits", u"http://www.arcamax.com/zits"),
|
||||
(u"Speed Bump", u"http://www.arcamax.com/thefunnies/speedbump"),
|
||||
(u"Wizard of Id", u"http://www.arcamax.com/thefunnies/wizardofid"),
|
||||
(u"Zits", u"http://www.arcamax.com/thefunnies/zits"),
|
||||
]:
|
||||
articles = self.make_links(url)
|
||||
if articles:
|
||||
@ -93,11 +91,11 @@ class Arcamax(BasicNewsRecipe):
|
||||
for page in pages:
|
||||
page_soup = self.index_to_soup(url)
|
||||
if page_soup:
|
||||
title = self.tag_to_string(page_soup.find(name='div', attrs={'class':'comics-header'}).h1.contents[0])
|
||||
title = self.tag_to_string(page_soup.find(name='div', attrs={'class':'columnheader'}).h1.contents[0])
|
||||
page_url = url
|
||||
# orig prev_page_url = 'http://www.arcamax.com' + page_soup.find('a', attrs={'class':'prev'}, text='Previous').parent['href']
|
||||
prev_page_url = 'http://www.arcamax.com' + page_soup.find('span', text='Previous').parent.parent['href']
|
||||
date = self.tag_to_string(page_soup.find(name='b', attrs={'class':['current']}))
|
||||
prev_page_url = 'http://www.arcamax.com' + page_soup.find(name='a', attrs={'class':['prev']})['href']
|
||||
date = self.tag_to_string(page_soup.find(name='span', attrs={'class':['cur']}))
|
||||
current_articles.append({'title': title, 'url': page_url, 'description':'', 'date': date})
|
||||
url = prev_page_url
|
||||
current_articles.reverse()
|
||||
@ -126,4 +124,5 @@ class Arcamax(BasicNewsRecipe):
|
||||
img {max-width:100%; min-width:100%;}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
'''
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 67)
|
||||
numeric_version = (0, 8, 68)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
@ -291,7 +291,7 @@ class ANDROID(USBMS):
|
||||
@classmethod
|
||||
def configure_for_kindle_app(cls):
|
||||
proxy = cls._configProxy()
|
||||
proxy['format_map'] = ['mobi', 'azw', 'azw1', 'azw4', 'pdf']
|
||||
proxy['format_map'] = ['azw3', 'mobi', 'azw', 'azw1', 'azw4', 'pdf']
|
||||
proxy['use_subdirs'] = False
|
||||
proxy['extra_customization'] = [
|
||||
','.join(['kindle']+cls.EBOOK_DIR_MAIN), '']
|
||||
|
@ -13,7 +13,8 @@ from calibre.constants import isosx, iswindows
|
||||
from calibre.devices.errors import OpenFeedback, UserFeedback
|
||||
from calibre.devices.usbms.deviceconfig import DeviceConfig
|
||||
from calibre.devices.interface import DevicePlugin
|
||||
from calibre.ebooks.metadata import authors_to_string, MetaInformation, title_sort
|
||||
from calibre.ebooks.metadata import (author_to_author_sort, authors_to_string,
|
||||
MetaInformation, title_sort)
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.utils.config import config_dir, dynamic, prefs
|
||||
from calibre.utils.date import now, parse_date
|
||||
@ -3478,6 +3479,7 @@ class Book(Metadata):
|
||||
'''
|
||||
def __init__(self,title,author):
|
||||
Metadata.__init__(self, title, authors=author.split(' & '))
|
||||
self.author_sort = author_to_author_sort(author)
|
||||
|
||||
@property
|
||||
def title_sorter(self):
|
||||
|
@ -46,9 +46,8 @@ class MTPDeviceBase(DevicePlugin):
|
||||
def set_progress_reporter(self, report_progress):
|
||||
self.report_progress = report_progress
|
||||
|
||||
@classmethod
|
||||
def get_gui_name(cls):
|
||||
return getattr(cls, 'current_friendly_name', cls.gui_name)
|
||||
def get_gui_name(self):
|
||||
return getattr(self, 'current_friendly_name', self.gui_name)
|
||||
|
||||
def is_usb_connected(self, devices_on_system, debug=False,
|
||||
only_presence=False):
|
||||
@ -60,13 +59,4 @@ class MTPDeviceBase(DevicePlugin):
|
||||
from calibre.devices.utils import build_template_regexp
|
||||
return build_template_regexp(self.save_template)
|
||||
|
||||
@property
|
||||
def default_save_template(cls):
|
||||
from calibre.library.save_to_disk import config
|
||||
return config().parse().send_template
|
||||
|
||||
@property
|
||||
def save_template(self):
|
||||
# TODO: Use the device specific template here
|
||||
return self.default_save_template
|
||||
|
||||
|
@ -15,7 +15,7 @@ from calibre import prints
|
||||
from calibre.constants import iswindows, numeric_version
|
||||
from calibre.devices.mtp.base import debug
|
||||
from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory
|
||||
from calibre.utils.config import from_json, to_json
|
||||
from calibre.utils.config import from_json, to_json, JSONConfig
|
||||
from calibre.utils.date import now, isoformat
|
||||
|
||||
BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%(
|
||||
@ -39,9 +39,40 @@ class MTP_DEVICE(BASE):
|
||||
def __init__(self, *args, **kwargs):
|
||||
BASE.__init__(self, *args, **kwargs)
|
||||
self.plugboards = self.plugboard_func = None
|
||||
self._prefs = None
|
||||
|
||||
@property
|
||||
def prefs(self):
|
||||
if self._prefs is None:
|
||||
from calibre.library.save_to_disk import config
|
||||
self._prefs = p = JSONConfig('mtp_devices')
|
||||
p.defaults['format_map'] = self.FORMATS
|
||||
p.defaults['send_to'] = ['eBooks/import',
|
||||
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
|
||||
'eBooks', 'kindle']
|
||||
p.defaults['send_template'] = config().parse().send_template
|
||||
|
||||
return self._prefs
|
||||
|
||||
def configure_for_kindle_app(self):
|
||||
proxy = self.prefs
|
||||
with proxy:
|
||||
proxy['format_map'] = ['azw3', 'mobi', 'azw', 'azw1', 'azw4', 'pdf']
|
||||
proxy['send_template'] = '{title} - {authors}'
|
||||
orig = list(proxy['send_to'])
|
||||
if 'kindle' in orig:
|
||||
orig.remove('kindle')
|
||||
orig.insert(0, 'kindle')
|
||||
proxy['send_to'] = orig
|
||||
|
||||
def configure_for_generic_epub_app(self):
|
||||
with self.prefs:
|
||||
for x in ('format_map', 'send_template', 'send_to'):
|
||||
del self.prefs[x]
|
||||
|
||||
def open(self, devices, library_uuid):
|
||||
self.current_library_uuid = library_uuid
|
||||
self.location_paths = None
|
||||
BASE.open(self, devices, library_uuid)
|
||||
|
||||
# Device information {{{
|
||||
@ -248,8 +279,24 @@ class MTP_DEVICE(BASE):
|
||||
return tuple(x for x in filepath.split('/'))
|
||||
|
||||
def prefix_for_location(self, on_card):
|
||||
# TODO: Implement this
|
||||
return 'calibre'
|
||||
if self.location_paths is None:
|
||||
self.location_paths = {}
|
||||
for sid, loc in ( (self._main_id, None), (self._carda_id, 'carda'),
|
||||
(self._cardb_id, 'cardb') ):
|
||||
if sid is not None:
|
||||
storage = self.filesystem_cache.storage(sid)
|
||||
prefixes = self.get_pref('send_to')
|
||||
p = None
|
||||
for path in prefixes:
|
||||
path = path.replace(os.sep, '/')
|
||||
if storage.find_path(path.split('/')) is not None:
|
||||
p = path
|
||||
break
|
||||
if p is None:
|
||||
p = 'eBooks'
|
||||
self.location_paths[loc] = p
|
||||
|
||||
return self.location_paths[on_card]
|
||||
|
||||
def ensure_parent(self, storage, path):
|
||||
parent = storage
|
||||
@ -366,14 +413,28 @@ class MTP_DEVICE(BASE):
|
||||
# }}}
|
||||
|
||||
# Settings {{{
|
||||
@classmethod
|
||||
|
||||
def get_pref(self, key):
|
||||
return self.prefs.get('device-%s'%self.current_serial_num, {}).get(key,
|
||||
self.prefs[key])
|
||||
|
||||
def config_widget(self):
|
||||
from calibre.gui2.device_drivers.mtp_config import MTPConfig
|
||||
return MTPConfig(self)
|
||||
|
||||
def save_settings(self, cw):
|
||||
cw.commit()
|
||||
|
||||
def settings(self):
|
||||
# TODO: Implement this
|
||||
class Opts(object):
|
||||
def __init__(s):
|
||||
s.format_map = self.FORMATS
|
||||
s.format_map = self.get_pref('format_map')
|
||||
return Opts()
|
||||
|
||||
@property
|
||||
def save_template(self):
|
||||
return self.prefs['send_template']
|
||||
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
@ -390,6 +451,7 @@ if __name__ == '__main__':
|
||||
dev.set_progress_reporter(prints)
|
||||
dev.open(cd, None)
|
||||
dev.filesystem_cache.dump()
|
||||
print ('Prefix for main mem:', dev.prefix_for_location(None))
|
||||
finally:
|
||||
dev.shutdown()
|
||||
|
||||
|
@ -36,13 +36,13 @@ class N770(USBMS):
|
||||
|
||||
class N810(N770):
|
||||
name = 'Nokia 810 Device Interface'
|
||||
gui_name = 'Nokia 810/900'
|
||||
gui_name = 'Nokia 810/900/9'
|
||||
description = _('Communicate with the Nokia 810/900 internet tablet.')
|
||||
|
||||
PRODUCT_ID = [0x96, 0x1c7]
|
||||
PRODUCT_ID = [0x96, 0x1c7, 0x0518]
|
||||
BCD = [0x316]
|
||||
|
||||
WINDOWS_MAIN_MEM = ['N810', 'N900']
|
||||
WINDOWS_MAIN_MEM = ['N810', 'N900', 'NOKIA_N9']
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'Nokia Tablet Main Memory'
|
||||
|
||||
|
@ -328,6 +328,19 @@ def info_dialog(parent, title, msg, det_msg='', show=False,
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def show_restart_warning(msg, parent=None):
|
||||
d = warning_dialog(parent, _('Restart needed'), msg,
|
||||
show_copy_button=False)
|
||||
b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole)
|
||||
b.setIcon(QIcon(I('lt.png')))
|
||||
d.do_restart = False
|
||||
def rf():
|
||||
d.do_restart = True
|
||||
b.clicked.connect(rf)
|
||||
d.set_details('')
|
||||
d.exec_()
|
||||
b.clicked.disconnect()
|
||||
return d.do_restart
|
||||
|
||||
|
||||
class Dispatcher(QObject):
|
||||
|
@ -67,7 +67,7 @@ class GenerateCatalogAction(InterfaceAction):
|
||||
# jobs.results is a list - the first entry is the intended title for the dialog
|
||||
# Subsequent strings are error messages
|
||||
dialog_title = job.result.pop(0)
|
||||
if re.match('warning:', job.result[0].lower()):
|
||||
if re.search('warning', job.result[0].lower()):
|
||||
msg = _("Catalog generation complete, with warnings.")
|
||||
warning_dialog(self.gui, dialog_title, msg, det_msg='\n'.join(job.result), show=True)
|
||||
else:
|
||||
|
@ -6,17 +6,19 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from copy import copy
|
||||
import re, sys
|
||||
|
||||
from functools import partial
|
||||
|
||||
from calibre.ebooks.conversion.config import load_defaults
|
||||
from calibre.gui2 import gprefs, question_dialog
|
||||
from calibre.gui2 import gprefs, open_url, question_dialog
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
from catalog_epub_mobi_ui import Ui_Form
|
||||
from PyQt4.Qt import (Qt, QAbstractItemView, QCheckBox, QComboBox,
|
||||
QDoubleSpinBox, QIcon, QLineEdit, QObject, QRadioButton, QSize, QSizePolicy,
|
||||
QTableWidget, QTableWidgetItem, QToolButton, QVBoxLayout, QWidget,
|
||||
QTableWidget, QTableWidgetItem, QTextEdit, QToolButton, QUrl,
|
||||
QVBoxLayout, QWidget,
|
||||
SIGNAL)
|
||||
|
||||
class PluginWidget(QWidget,Ui_Form):
|
||||
@ -44,6 +46,7 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
LineEditControls = []
|
||||
RadioButtonControls = []
|
||||
TableWidgetControls = []
|
||||
TextEditControls = []
|
||||
|
||||
for item in self.__dict__:
|
||||
if type(self.__dict__[item]) is QCheckBox:
|
||||
@ -58,6 +61,8 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
RadioButtonControls.append(str(self.__dict__[item].objectName()))
|
||||
elif type(self.__dict__[item]) is QTableWidget:
|
||||
TableWidgetControls.append(str(self.__dict__[item].objectName()))
|
||||
elif type(self.__dict__[item]) is QTextEdit:
|
||||
TextEditControls.append(str(self.__dict__[item].objectName()))
|
||||
|
||||
option_fields = zip(CheckBoxControls,
|
||||
[True for i in CheckBoxControls],
|
||||
@ -72,20 +77,23 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
# LineEditControls
|
||||
option_fields += zip(['exclude_genre'],['\[.+\]|\+'],['line_edit'])
|
||||
|
||||
# TextEditControls
|
||||
#option_fields += zip(['exclude_genre_results'],['excluded genres will appear here'],['text_edit'])
|
||||
|
||||
# SpinBoxControls
|
||||
option_fields += zip(['thumb_width'],[1.00],['spin_box'])
|
||||
|
||||
# Exclusion rules
|
||||
option_fields += zip(['exclusion_rules_tw','exclusion_rules_tw'],
|
||||
option_fields += zip(['exclusion_rules_tw'],
|
||||
[{'ordinal':0,
|
||||
'enabled':True,
|
||||
'name':'Catalogs',
|
||||
'field':'Tags',
|
||||
'pattern':'Catalog'},],
|
||||
['table_widget','table_widget'])
|
||||
['table_widget'])
|
||||
|
||||
# Prefix rules
|
||||
option_fields += zip(['prefix_rules_tw','prefix_rules_tw','prefix_rules_tw'],
|
||||
option_fields += zip(['prefix_rules_tw','prefix_rules_tw'],
|
||||
[{'ordinal':0,
|
||||
'enabled':True,
|
||||
'name':'Read book',
|
||||
@ -98,7 +106,7 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
'field':'Tags',
|
||||
'pattern':'Wishlist',
|
||||
'prefix':u'\u00d7'},],
|
||||
['table_widget','table_widget','table_widget'])
|
||||
['table_widget','table_widget'])
|
||||
|
||||
self.OPTION_FIELDS = option_fields
|
||||
|
||||
@ -110,13 +118,13 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
'''
|
||||
rule_set = []
|
||||
for stored_rule in opt_value:
|
||||
rule = copy(stored_rule)
|
||||
rule = stored_rule.copy()
|
||||
# Skip disabled and incomplete rules
|
||||
if not rule['enabled']:
|
||||
continue
|
||||
elif not rule['field'] or not rule['pattern']:
|
||||
continue
|
||||
elif 'prefix' in rule and not rule['prefix']:
|
||||
elif 'prefix' in rule and rule['prefix'] is None:
|
||||
continue
|
||||
else:
|
||||
if rule['field'] != 'Tags':
|
||||
@ -130,12 +138,58 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
pr = (rule['name'],rule['field'],rule['pattern'],rule['prefix'])
|
||||
else:
|
||||
pr = (rule['name'],rule['field'],rule['pattern'])
|
||||
|
||||
rule_set.append(pr)
|
||||
opt_value = tuple(rule_set)
|
||||
# Strip off the trailing '_tw'
|
||||
opts_dict[c_name[:-3]] = opt_value
|
||||
|
||||
def fetchEligibleCustomFields(self):
|
||||
def exclude_genre_changed(self, regex):
|
||||
""" Dynamically compute excluded genres.
|
||||
|
||||
Run exclude_genre regex against db.all_tags() to show excluded tags.
|
||||
PROVISIONAL CODE, NEEDS TESTING
|
||||
|
||||
Args:
|
||||
regex (QLineEdit.text()): regex to compile, compute
|
||||
|
||||
Output:
|
||||
self.exclude_genre_results (QLabel): updated to show tags to be excluded as genres
|
||||
"""
|
||||
results = _('No genres will be excluded')
|
||||
if not regex:
|
||||
self.exclude_genre_results.clear()
|
||||
self.exclude_genre_results.setText(results)
|
||||
return
|
||||
|
||||
try:
|
||||
pattern = re.compile((str(regex)))
|
||||
except:
|
||||
results = _("regex error: %s") % sys.exc_info()[1]
|
||||
else:
|
||||
excluded_tags = []
|
||||
for tag in self.all_tags:
|
||||
hit = pattern.search(tag)
|
||||
if hit:
|
||||
excluded_tags.append(hit.string)
|
||||
if excluded_tags:
|
||||
if set(excluded_tags) == set(self.all_tags):
|
||||
results = _("All genres will be excluded")
|
||||
else:
|
||||
results = ', '.join(sorted(excluded_tags))
|
||||
finally:
|
||||
if self.DEBUG:
|
||||
print(results)
|
||||
self.exclude_genre_results.clear()
|
||||
self.exclude_genre_results.setText(results)
|
||||
|
||||
def exclude_genre_reset(self):
|
||||
for default in self.OPTION_FIELDS:
|
||||
if default[0] == 'exclude_genre':
|
||||
self.exclude_genre.setText(default[1])
|
||||
break
|
||||
|
||||
def fetch_eligible_custom_fields(self):
|
||||
self.all_custom_fields = self.db.custom_field_keys()
|
||||
custom_fields = {}
|
||||
custom_fields['Tags'] = {'field':'tag', 'datatype':u'text'}
|
||||
@ -146,28 +200,42 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
'datatype':field_md['datatype']}
|
||||
self.eligible_custom_fields = custom_fields
|
||||
|
||||
def generate_descriptions_changed(self, enabled):
|
||||
'''
|
||||
Toggle Description-related controls
|
||||
'''
|
||||
self.header_note_source_field.setEnabled(enabled)
|
||||
self.thumb_width.setEnabled(enabled)
|
||||
self.merge_source_field.setEnabled(enabled)
|
||||
self.merge_before.setEnabled(enabled)
|
||||
self.merge_after.setEnabled(enabled)
|
||||
self.include_hr.setEnabled(enabled)
|
||||
|
||||
def initialize(self, name, db):
|
||||
'''
|
||||
|
||||
CheckBoxControls (c_type: check_box):
|
||||
['generate_titles','generate_series','generate_genres',
|
||||
'generate_recently_added','generate_descriptions','include_hr']
|
||||
'generate_recently_added','generate_descriptions','include_hr']
|
||||
ComboBoxControls (c_type: combo_box):
|
||||
['exclude_source_field','header_note_source_field',
|
||||
'merge_source_field']
|
||||
'merge_source_field']
|
||||
LineEditControls (c_type: line_edit):
|
||||
['exclude_genre']
|
||||
RadioButtonControls (c_type: radio_button):
|
||||
['merge_before','merge_after']
|
||||
['merge_before','merge_after','generate_new_cover', 'use_existing_cover']
|
||||
SpinBoxControls (c_type: spin_box):
|
||||
['thumb_width']
|
||||
TableWidgetControls (c_type: table_widget):
|
||||
['exclusion_rules_tw','prefix_rules_tw']
|
||||
TextEditControls (c_type: text_edit):
|
||||
['exclude_genre_results']
|
||||
|
||||
'''
|
||||
self.name = name
|
||||
self.db = db
|
||||
self.fetchEligibleCustomFields()
|
||||
self.all_tags = db.all_tags()
|
||||
self.fetch_eligible_custom_fields()
|
||||
self.populate_combo_boxes()
|
||||
|
||||
# Update dialog fields from stored options
|
||||
@ -200,9 +268,16 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
if opt_value not in prefix_rules:
|
||||
prefix_rules.append(opt_value)
|
||||
|
||||
# Add icon to the reset button
|
||||
# Add icon to the reset button, hook textChanged signal
|
||||
self.reset_exclude_genres_tb.setIcon(QIcon(I('trash.png')))
|
||||
self.reset_exclude_genres_tb.clicked.connect(self.reset_exclude_genres)
|
||||
self.reset_exclude_genres_tb.clicked.connect(self.exclude_genre_reset)
|
||||
|
||||
# Hook textChanged event for exclude_genre QLineEdit
|
||||
self.exclude_genre.textChanged.connect(self.exclude_genre_changed)
|
||||
|
||||
# Hook Descriptions checkbox for related options, init
|
||||
self.generate_descriptions.clicked.connect(self.generate_descriptions_changed)
|
||||
self.generate_descriptions_changed(self.generate_descriptions.isChecked())
|
||||
|
||||
# Init self.merge_source_field_name
|
||||
self.merge_source_field_name = ''
|
||||
@ -226,6 +301,9 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
self.prefix_rules_table = PrefixRules(self.prefix_rules_gb,
|
||||
"prefix_rules_tw",prefix_rules, self.eligible_custom_fields,self.db)
|
||||
|
||||
# Initialize excluded genres preview
|
||||
self.exclude_genre_changed(unicode(getattr(self, 'exclude_genre').text()).strip())
|
||||
|
||||
def options(self):
|
||||
# Save/return the current options
|
||||
# exclude_genre stores literally
|
||||
@ -275,16 +353,21 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
elif self.merge_after.isChecked():
|
||||
checked = 'after'
|
||||
include_hr = self.include_hr.isChecked()
|
||||
opts_dict['merge_comments'] = "%s:%s:%s" % \
|
||||
opts_dict['merge_comments_rule'] = "%s:%s:%s" % \
|
||||
(self.merge_source_field_name, checked, include_hr)
|
||||
|
||||
opts_dict['header_note_source_field'] = self.header_note_source_field_name
|
||||
|
||||
# Fix up exclude_genre regex if blank. Assume blank = no exclusions
|
||||
if opts_dict['exclude_genre'] == '':
|
||||
opts_dict['exclude_genre'] = 'a^'
|
||||
|
||||
# Append the output profile
|
||||
try:
|
||||
opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']]
|
||||
except:
|
||||
opts_dict['output_profile'] = ['default']
|
||||
|
||||
if self.DEBUG:
|
||||
print "opts_dict"
|
||||
for opt in sorted(opts_dict.keys(), key=sort_key):
|
||||
@ -377,11 +460,11 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
self.merge_after.setEnabled(False)
|
||||
self.include_hr.setEnabled(False)
|
||||
|
||||
def reset_exclude_genres(self):
|
||||
for default in self.OPTION_FIELDS:
|
||||
if default[0] == 'exclude_genre':
|
||||
self.exclude_genre.setText(default[1])
|
||||
break
|
||||
def show_help(self):
|
||||
'''
|
||||
Display help file
|
||||
'''
|
||||
open_url(QUrl('http://manual.calibre-ebook.com/catalogs.html'))
|
||||
|
||||
class CheckableTableWidgetItem(QTableWidgetItem):
|
||||
'''
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>650</width>
|
||||
<width>658</width>
|
||||
<height>603</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -41,151 +41,74 @@
|
||||
<string>Included sections</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="generate_genres">
|
||||
<property name="text">
|
||||
<string>Books by &Genre</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="generate_recently_added">
|
||||
<property name="text">
|
||||
<string>Recently &Added</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="generate_descriptions">
|
||||
<property name="text">
|
||||
<string>&Descriptions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="generate_series">
|
||||
<property name="text">
|
||||
<string>Books by &Series</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="generate_titles">
|
||||
<property name="text">
|
||||
<string>Books by &Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="generate_authors">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Books by Author</string>
|
||||
<string>&Authors</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QCheckBox" name="generate_titles">
|
||||
<property name="text">
|
||||
<string>&Titles</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="generate_series">
|
||||
<property name="text">
|
||||
<string>&Series</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="generate_genres">
|
||||
<property name="text">
|
||||
<string>&Genres</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QCheckBox" name="generate_recently_added">
|
||||
<property name="text">
|
||||
<string>&Recently Added</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QCheckBox" name="generate_descriptions">
|
||||
<property name="text">
|
||||
<string>&Descriptions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="excludedGenres">
|
||||
<widget class="QGroupBox" name="prefix_rules_gb">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>A regular expression describing genres to be excluded from the generated catalog. Genres are derived from the tags applied to your books.
|
||||
The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book], and '+', the default tag for a read book.</string>
|
||||
<string>The first matching prefix rule applies a prefix to book listings in the generated catalog.</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Excluded genres</string>
|
||||
<string>Prefixes</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Tags to &exclude</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::AutoText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>exclude_genre</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="exclude_genre">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string extracomment="Default: \[[\w]*\]"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="reset_exclude_genres_tb">
|
||||
<property name="toolTip">
|
||||
<string>Reset to default</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -218,22 +141,148 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="prefix_rules_gb">
|
||||
<widget class="QGroupBox" name="excludedGenres">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The first matching prefix rule applies a prefix to book listings in the generated catalog.</string>
|
||||
<string>A regular expression describing genres to be excluded from the generated catalog. Genres are derived from the tags applied to your books.
|
||||
The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book], and '+', the default tag for a read book.</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Prefixes</string>
|
||||
<string>Excluded genres</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6"/>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Tags to &exclude (regex):</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::AutoText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>exclude_genre</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="exclude_genre">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string extracomment="Default: \[[\w]*\]"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="reset_exclude_genres_tb">
|
||||
<property name="toolTip">
|
||||
<string>Reset to default</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Results of regex:</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::AutoText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>exclude_genre</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="exclude_genre_results">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Tags that will be excluded as genres</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -255,142 +304,9 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
||||
<property name="title">
|
||||
<string>Other options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Thumb width</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>merge_source_field</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="thumb_width">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>137</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Size hint for cover thumbnails included in Descriptions section.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> inch</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>2.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>&Extra note</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>header_note_source_field</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="header_note_source_field">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Custom column source for text to include in Description section.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Merge with Comments</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>merge_source_field</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="merge_with_comments_hl">
|
||||
<item>
|
||||
<widget class="QComboBox" name="merge_source_field">
|
||||
<property name="minimumSize">
|
||||
@ -419,6 +335,9 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
||||
<property name="text">
|
||||
<string>&Before</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string>merge_options_bg</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -429,6 +348,9 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
||||
<property name="text">
|
||||
<string>&After</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string>merge_options_bg</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -444,7 +366,196 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
||||
<string>Separate Comments metadata and additional content with a horizontal rule.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Separator</string>
|
||||
<string>Include &Separator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Merge with Comments:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>merge_source_field</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>175</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Catalog cover:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="replace_cover_hl">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="generate_new_cover">
|
||||
<property name="text">
|
||||
<string>Generate new cover</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string>cover_options_bg</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="use_existing_cover">
|
||||
<property name="text">
|
||||
<string>Use existing cover</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string>cover_options_bg</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="spacer_label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>E&xtra Description note:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>header_note_source_field</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="header_note_source_field">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Custom column source for text to include in Description section.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Thumb width:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>merge_source_field</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="thumb_width">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Size hint for cover thumbnails included in Descriptions section.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> inch</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>2.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -457,4 +568,8 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="cover_options_bg"/>
|
||||
<buttongroup name="merge_options_bg"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
@ -19,7 +19,8 @@ from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog
|
||||
from calibre.utils.ipc.job import BaseJob
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
from calibre.gui2 import (config, error_dialog, Dispatcher, dynamic,
|
||||
warning_dialog, info_dialog, choose_dir, FunctionDispatcher)
|
||||
warning_dialog, info_dialog, choose_dir, FunctionDispatcher,
|
||||
show_restart_warning)
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre import preferred_encoding, prints, force_unicode, as_unicode
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
@ -889,12 +890,16 @@ class DeviceMixin(object): # {{{
|
||||
bb.rejected.connect(d.reject)
|
||||
l.addWidget(cw)
|
||||
l.addWidget(bb)
|
||||
def validate():
|
||||
if cw.validate():
|
||||
QDialog.accept(d)
|
||||
d.accept = validate
|
||||
if d.exec_() == d.Accepted:
|
||||
dev.save_settings(cw)
|
||||
warning_dialog(self, _('Disconnect device'),
|
||||
_('Disconnect and re-connect the %s for your changes to'
|
||||
' be applied.')%dev.get_gui_name(), show=True,
|
||||
show_copy_button=False)
|
||||
do_restart = show_restart_warning(_('Restart calibre for the changes to %s'
|
||||
' to be applied.')%dev.get_gui_name(), parent=self)
|
||||
if do_restart:
|
||||
self.quit(restart=True)
|
||||
|
||||
def _sync_action_triggered(self, *args):
|
||||
m = getattr(self, '_sync_menu', None)
|
||||
@ -972,6 +977,7 @@ class DeviceMixin(object): # {{{
|
||||
connected = False
|
||||
self.set_device_menu_items_state(connected)
|
||||
if connected:
|
||||
self.device_connected = device_kind
|
||||
self.device_manager.get_device_information(\
|
||||
FunctionDispatcher(self.info_read))
|
||||
self.set_default_thumbnail(\
|
||||
@ -979,9 +985,8 @@ class DeviceMixin(object): # {{{
|
||||
self.status_bar.show_message(_('Device: ')+\
|
||||
self.device_manager.device.get_gui_name()+\
|
||||
_(' detected.'), 3000)
|
||||
self.device_connected = device_kind
|
||||
self.library_view.set_device_connected(self.device_connected)
|
||||
self.refresh_ondevice (reset_only = True)
|
||||
self.refresh_ondevice(reset_only=True)
|
||||
else:
|
||||
self.device_connected = None
|
||||
self.status_bar.device_disconnected()
|
||||
|
@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap
|
||||
|
||||
from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL, \
|
||||
QLabel, QLineEdit, QCheckBox
|
||||
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QVariant, QLabel,
|
||||
QLineEdit, QCheckBox)
|
||||
|
||||
from calibre.gui2 import error_dialog, question_dialog
|
||||
from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
|
||||
@ -40,8 +40,8 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
||||
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
|
||||
item.setCheckState(Qt.Checked if format in format_map else Qt.Unchecked)
|
||||
|
||||
self.connect(self.column_up, SIGNAL('clicked()'), self.up_column)
|
||||
self.connect(self.column_down, SIGNAL('clicked()'), self.down_column)
|
||||
self.column_up.clicked.connect(self.up_column)
|
||||
self.column_down.clicked.connect(self.down_column)
|
||||
|
||||
if device.HIDE_FORMATS_CONFIG_BOX:
|
||||
self.groupBox.hide()
|
||||
@ -157,3 +157,5 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
||||
'<br>'+unicode(err), show=True)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
239
src/calibre/gui2/device_drivers/mtp_config.py
Normal file
239
src/calibre/gui2/device_drivers/mtp_config.py
Normal file
@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import weakref
|
||||
|
||||
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
|
||||
QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout,
|
||||
QPushButton)
|
||||
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
|
||||
class FormatsConfig(QWidget): # {{{
|
||||
|
||||
def __init__(self, all_formats, format_map):
|
||||
QWidget.__init__(self)
|
||||
self.l = l = QGridLayout()
|
||||
self.setLayout(l)
|
||||
|
||||
self.f = f = QListWidget(self)
|
||||
l.addWidget(f, 0, 0, 3, 1)
|
||||
unchecked_formats = sorted(all_formats - set(format_map))
|
||||
for fmt in format_map + unchecked_formats:
|
||||
item = QListWidgetItem(fmt, f)
|
||||
item.setData(Qt.UserRole, fmt)
|
||||
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
|
||||
item.setCheckState(Qt.Checked if fmt in format_map else Qt.Unchecked)
|
||||
|
||||
self.button_up = b = QToolButton(self)
|
||||
b.setIcon(QIcon(I('arrow-up.png')))
|
||||
l.addWidget(b, 0, 1)
|
||||
b.clicked.connect(self.up)
|
||||
|
||||
self.button_down = b = QToolButton(self)
|
||||
b.setIcon(QIcon(I('arrow-down.png')))
|
||||
l.addWidget(b, 2, 1)
|
||||
b.clicked.connect(self.down)
|
||||
|
||||
@property
|
||||
def format_map(self):
|
||||
return [unicode(self.f.item(i).data(Qt.UserRole).toString()) for i in
|
||||
xrange(self.f.count()) if self.f.item(i).checkState()==Qt.Checked]
|
||||
|
||||
def validate(self):
|
||||
if not self.format_map:
|
||||
error_dialog(self, _('No formats selected'),
|
||||
_('You must choose at least one format to send to the'
|
||||
' device'), show=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
def up(self):
|
||||
idx = self.f.currentRow()
|
||||
if idx > 0:
|
||||
self.f.insertItem(idx-1, self.f.takeItem(idx))
|
||||
self.f.setCurrentRow(idx-1)
|
||||
|
||||
def down(self):
|
||||
idx = self.f.currentRow()
|
||||
if idx < self.f.count()-1:
|
||||
self.f.insertItem(idx+1, self.f.takeItem(idx))
|
||||
self.f.setCurrentRow(idx+1)
|
||||
# }}}
|
||||
|
||||
class TemplateConfig(QWidget): # {{{
|
||||
|
||||
def __init__(self, val):
|
||||
QWidget.__init__(self)
|
||||
self.t = t = QLineEdit(self)
|
||||
t.setText(val or '')
|
||||
t.setCursorPosition(0)
|
||||
self.setMinimumWidth(400)
|
||||
self.l = l = QGridLayout(self)
|
||||
self.setLayout(l)
|
||||
self.m = m = QLabel('<p>'+_('''<b>Save &template</b> to control the filename and
|
||||
location of files sent to the device:'''))
|
||||
m.setWordWrap(True)
|
||||
m.setBuddy(t)
|
||||
l.addWidget(m, 0, 0, 1, 2)
|
||||
l.addWidget(t, 1, 0, 1, 1)
|
||||
b = self.b = QPushButton(_('Template editor'))
|
||||
l.addWidget(b, 1, 1, 1, 1)
|
||||
b.clicked.connect(self.edit_template)
|
||||
|
||||
@property
|
||||
def template(self):
|
||||
return unicode(self.t.text()).strip()
|
||||
|
||||
def edit_template(self):
|
||||
t = TemplateDialog(self, self.template)
|
||||
t.setWindowTitle(_('Edit template'))
|
||||
if t.exec_():
|
||||
self.t.setText(t.rule[1])
|
||||
|
||||
def validate(self):
|
||||
from calibre.utils.formatter import validation_formatter
|
||||
tmpl = self.template
|
||||
try:
|
||||
validation_formatter.validate(tmpl)
|
||||
return True
|
||||
except Exception as err:
|
||||
error_dialog(self, _('Invalid template'),
|
||||
'<p>'+_('The template %s is invalid:')%tmpl + \
|
||||
'<br>'+unicode(err), show=True)
|
||||
|
||||
return False
|
||||
# }}}
|
||||
|
||||
class SendToConfig(QWidget): # {{{
|
||||
|
||||
def __init__(self, val):
|
||||
QWidget.__init__(self)
|
||||
self.t = t = QLineEdit(self)
|
||||
t.setText(', '.join(val or []))
|
||||
t.setCursorPosition(0)
|
||||
self.l = l = QVBoxLayout(self)
|
||||
self.setLayout(l)
|
||||
self.m = m = QLabel('<p>'+_('''A <b>list of &folders</b> on the device to
|
||||
which to send ebooks. The first one that exists will be used:'''))
|
||||
m.setWordWrap(True)
|
||||
m.setBuddy(t)
|
||||
l.addWidget(m)
|
||||
l.addWidget(t)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
ans = [x.strip() for x in unicode(self.t.text()).strip().split(',')]
|
||||
return [x for x in ans if x]
|
||||
|
||||
# }}}
|
||||
|
||||
class MTPConfig(QTabWidget):
|
||||
|
||||
def __init__(self, device, parent=None):
|
||||
QTabWidget.__init__(self, parent)
|
||||
self._device = weakref.ref(device)
|
||||
|
||||
cd = msg = None
|
||||
if device.current_friendly_name is not None:
|
||||
if device.current_serial_num is None:
|
||||
msg = '<p>' + _('The <b>%s</b> device has no serial number, '
|
||||
'it cannot be configured'%device.current_friendly_name)
|
||||
else:
|
||||
cd = 'device-'+device.current_serial_num
|
||||
else:
|
||||
msg = '<p>' + _('<b>No MTP device connected.</b><p>'
|
||||
' You can only configure the MTP device plugin when a device'
|
||||
' is connected.')
|
||||
|
||||
self.current_device_key = cd
|
||||
|
||||
if msg:
|
||||
msg += '<p>' + _('If you want to un-ignore a previously'
|
||||
' ignored MTP device, use the "Ignored devices" tab.')
|
||||
l = QLabel(msg)
|
||||
l.setWordWrap(True)
|
||||
l.setStyleSheet('QLabel { margin-left: 2em }')
|
||||
self.insertTab(0, l, _('Cannot configure'))
|
||||
else:
|
||||
self.base = QWidget(self)
|
||||
self.insertTab(0, self.base, _('Configure %s')%self.device.current_friendly_name)
|
||||
l = self.base.l = QGridLayout(self.base)
|
||||
self.base.setLayout(l)
|
||||
|
||||
self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
|
||||
self.get_pref('format_map'))
|
||||
self.send_to = SendToConfig(self.get_pref('send_to'))
|
||||
self.template = TemplateConfig(self.get_pref('send_template'))
|
||||
self.base.la = la = QLabel(_('Choose the formats to send to the %s')%self.device.current_friendly_name)
|
||||
la.setWordWrap(True)
|
||||
l.addWidget(la, 0, 0, 1, 1)
|
||||
l.addWidget(self.formats, 1, 0, 3, 1)
|
||||
l.addWidget(self.send_to, 1, 1, 1, 1)
|
||||
l.addWidget(self.template, 2, 1, 1, 1)
|
||||
l.setRowStretch(2, 10)
|
||||
|
||||
self.setCurrentIndex(0)
|
||||
|
||||
def get_pref(self, key):
|
||||
p = self.device.prefs.get(self.current_device_key, {})
|
||||
if not p:
|
||||
self.device.prefs[self.current_device_key] = p
|
||||
return p.get(key, self.device.prefs[key])
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
return self._device()
|
||||
|
||||
def validate(self):
|
||||
if not self.formats.validate():
|
||||
return False
|
||||
if not self.template.validate():
|
||||
return False
|
||||
return True
|
||||
|
||||
def commit(self):
|
||||
p = self.device.prefs.get(self.current_device_key, {})
|
||||
|
||||
p.pop('format_map', None)
|
||||
f = self.formats.format_map
|
||||
if f and f != self.device.prefs['format_map']:
|
||||
p['format_map'] = f
|
||||
|
||||
p.pop('send_template', None)
|
||||
t = self.template.template
|
||||
if t and t != self.device.prefs['send_template']:
|
||||
p['send_template'] = t
|
||||
|
||||
p.pop('send_to', None)
|
||||
s = self.send_to.value
|
||||
if s and s != self.device.prefs['send_to']:
|
||||
p['send_to'] = s
|
||||
|
||||
self.device.prefs[self.current_device_key] = p
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2 import Application
|
||||
from calibre.devices.mtp.driver import MTP_DEVICE
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
s = DeviceScanner()
|
||||
s.scan()
|
||||
app = Application([])
|
||||
dev = MTP_DEVICE(None)
|
||||
dev.startup()
|
||||
cd = dev.detect_managed_devices(s.devices)
|
||||
dev.open(cd, 'test')
|
||||
cw = dev.config_widget()
|
||||
cw.show()
|
||||
app.exec_()
|
||||
dev.shutdown()
|
||||
|
||||
|
@ -10,7 +10,7 @@ import os, sys, importlib
|
||||
|
||||
from calibre.customize.ui import config
|
||||
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
|
||||
from calibre.gui2 import dynamic, ResizableDialog
|
||||
from calibre.gui2 import dynamic, ResizableDialog, info_dialog
|
||||
from calibre.customize.ui import catalog_plugins
|
||||
|
||||
class Catalog(ResizableDialog, Ui_Dialog):
|
||||
@ -22,7 +22,6 @@ class Catalog(ResizableDialog, Ui_Dialog):
|
||||
from PyQt4.uic import compileUi
|
||||
|
||||
ResizableDialog.__init__(self, parent)
|
||||
|
||||
self.dbspec, self.ids = dbspec, ids
|
||||
|
||||
# Display the number of books we've been passed
|
||||
@ -115,6 +114,7 @@ class Catalog(ResizableDialog, Ui_Dialog):
|
||||
|
||||
self.format.currentIndexChanged.connect(self.show_plugin_tab)
|
||||
self.buttonBox.button(self.buttonBox.Apply).clicked.connect(self.apply)
|
||||
self.buttonBox.button(self.buttonBox.Help).clicked.connect(self.help)
|
||||
self.show_plugin_tab(None)
|
||||
|
||||
geom = dynamic.get('catalog_window_geom', None)
|
||||
@ -129,6 +129,10 @@ class Catalog(ResizableDialog, Ui_Dialog):
|
||||
if cf in pw.formats:
|
||||
self.tabs.addTab(pw, pw.TITLE)
|
||||
break
|
||||
if hasattr(self.tabs.widget(1),'show_help'):
|
||||
self.buttonBox.button(self.buttonBox.Help).setVisible(True)
|
||||
else:
|
||||
self.buttonBox.button(self.buttonBox.Help).setVisible(False)
|
||||
|
||||
def format_changed(self, idx):
|
||||
cf = unicode(self.format.currentText())
|
||||
@ -165,6 +169,29 @@ class Catalog(ResizableDialog, Ui_Dialog):
|
||||
self.save_catalog_settings()
|
||||
return ResizableDialog.accept(self)
|
||||
|
||||
def help(self):
|
||||
'''
|
||||
To add help functionality for a specific format:
|
||||
In gui2.catalog.catalog_<format>.py, add the following:
|
||||
from calibre.gui2 import open_url
|
||||
from PyQt4.Qt import QUrl
|
||||
|
||||
In the PluginWidget() class, add this method:
|
||||
def show_help(self):
|
||||
url = 'file:///' + P('catalog/help_<format>.html')
|
||||
open_url(QUrl(url))
|
||||
|
||||
Create the help file at resources/catalog/help_<format>.html
|
||||
'''
|
||||
if self.tabs.count() > 1 and hasattr(self.tabs.widget(1),'show_help'):
|
||||
try:
|
||||
self.tabs.widget(1).show_help()
|
||||
except:
|
||||
info_dialog(self, _('No help available'),
|
||||
_('No help available for this output format.'),
|
||||
show_copy_button=False,
|
||||
show=True)
|
||||
|
||||
def reject(self):
|
||||
dynamic.set('catalog_window_geom', bytearray(self.saveGeometry()))
|
||||
ResizableDialog.reject(self)
|
||||
|
@ -14,7 +14,7 @@
|
||||
<string>Generate catalog</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
@ -37,7 +37,7 @@
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -54,8 +54,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>666</width>
|
||||
<height>599</height>
|
||||
<width>650</width>
|
||||
<height>575</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
@ -9,14 +9,14 @@ import textwrap
|
||||
from functools import partial
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt4.Qt import QMainWindow, Qt, QIcon, QStatusBar, QFont, QWidget, \
|
||||
QScrollArea, QStackedWidget, QVBoxLayout, QLabel, QFrame, QKeySequence, \
|
||||
QToolBar, QSize, pyqtSignal, QPixmap, QToolButton, QAction, \
|
||||
QDialogButtonBox, QHBoxLayout
|
||||
from PyQt4.Qt import (QMainWindow, Qt, QIcon, QStatusBar, QFont, QWidget,
|
||||
QScrollArea, QStackedWidget, QVBoxLayout, QLabel, QFrame, QKeySequence,
|
||||
QToolBar, QSize, pyqtSignal, QPixmap, QToolButton, QAction,
|
||||
QDialogButtonBox, QHBoxLayout)
|
||||
|
||||
from calibre.constants import __appname__, __version__, islinux
|
||||
from calibre.gui2 import gprefs, min_available_height, available_width, \
|
||||
warning_dialog
|
||||
from calibre.gui2 import (gprefs, min_available_height, available_width,
|
||||
show_restart_warning)
|
||||
from calibre.gui2.preferences import init_gui, AbortCommit, get_plugin
|
||||
from calibre.customize.ui import preferences_plugins
|
||||
|
||||
@ -346,19 +346,8 @@ class Preferences(QMainWindow):
|
||||
'restarted immediately. You will not be allowed to '
|
||||
'set any more preferences, until you restart.')
|
||||
|
||||
do_restart = show_restart_warning(msg, parent=self)
|
||||
|
||||
d = warning_dialog(self, _('Restart needed'), msg,
|
||||
show_copy_button=False)
|
||||
b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole)
|
||||
b.setIcon(QIcon(I('lt.png')))
|
||||
d.do_restart = False
|
||||
def rf():
|
||||
d.do_restart = True
|
||||
b.clicked.connect(rf)
|
||||
d.set_details('')
|
||||
d.exec_()
|
||||
b.clicked.disconnect()
|
||||
do_restart = d.do_restart
|
||||
self.showing_widget.refresh_gui(self.gui)
|
||||
self.hide_plugin()
|
||||
if self.close_after_initial or (must_restart and rc) or do_restart:
|
||||
|
@ -267,7 +267,7 @@ class Android(Device):
|
||||
def commit(cls):
|
||||
super(Android, cls).commit()
|
||||
for plugin in device_plugins(include_disabled=True):
|
||||
if plugin.name == 'Android driver':
|
||||
if hasattr(plugin, 'configure_for_generic_epub_app'):
|
||||
plugin.configure_for_generic_epub_app()
|
||||
|
||||
class AndroidTablet(Android):
|
||||
@ -287,7 +287,7 @@ class AndroidPhoneWithKindle(Android):
|
||||
def commit(cls):
|
||||
super(Android, cls).commit()
|
||||
for plugin in device_plugins(include_disabled=True):
|
||||
if plugin.name == 'Android driver':
|
||||
if hasattr(plugin, 'configure_for_kindle_app'):
|
||||
plugin.configure_for_kindle_app()
|
||||
|
||||
class AndroidTabletWithKindle(AndroidPhoneWithKindle):
|
||||
|
@ -17,4 +17,6 @@ FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments',
|
||||
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', 'title_sort',
|
||||
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
|
||||
|
||||
class AuthorSortMismatchException(Exception): pass
|
||||
class EmptyCatalogException(Exception): pass
|
||||
|
||||
|
@ -7,13 +7,14 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
import os, shutil
|
||||
from collections import namedtuple
|
||||
|
||||
from calibre import strftime
|
||||
from calibre.customize import CatalogPlugin
|
||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||
from calibre.ebooks import calibre_cover
|
||||
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
Option = namedtuple('Option', 'option, default, dest, action, help')
|
||||
@ -120,9 +121,9 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
help=_("Custom field containing note text to insert in Description header.\n"
|
||||
"Default: '%default'\n"
|
||||
"Applies to: AZW3, ePub, MOBI output formats")),
|
||||
Option('--merge-comments',
|
||||
Option('--merge-comments-rule',
|
||||
default='::',
|
||||
dest='merge_comments',
|
||||
dest='merge_comments_rule',
|
||||
action = None,
|
||||
help=_("#<custom field>:[before|after]:[True|False] specifying:\n"
|
||||
" <custom field> Custom field containing notes to merge with Comments\n"
|
||||
@ -146,6 +147,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
"When multiple rules are defined, the first matching rule will be used.\n"
|
||||
"Default:\n" + '"' + '%default' + '"' + "\n"
|
||||
"Applies to AZW3, ePub, MOBI output formats")),
|
||||
Option('--use-existing-cover',
|
||||
default=False,
|
||||
dest='use_existing_cover',
|
||||
action = 'store_true',
|
||||
help=_("Replace existing cover when generating the catalog.\n"
|
||||
"Default: '%default'\n"
|
||||
"Applies to: AZW3, ePub, MOBI output formats")),
|
||||
Option('--thumb-width',
|
||||
default='1.0',
|
||||
dest='thumb_width',
|
||||
@ -182,8 +190,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
else:
|
||||
op = "kindle"
|
||||
|
||||
opts.descriptionClip = 380 if op.endswith('dx') or 'kindle' not in op else 100
|
||||
opts.authorClip = 100 if op.endswith('dx') or 'kindle' not in op else 60
|
||||
opts.description_clip = 380 if op.endswith('dx') or 'kindle' not in op else 100
|
||||
opts.author_clip = 100 if op.endswith('dx') or 'kindle' not in op else 60
|
||||
opts.output_profile = op
|
||||
|
||||
opts.basename = "Catalog"
|
||||
@ -198,11 +206,12 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
(self.name,self.fmt,'for %s ' % opts.output_profile if opts.output_profile else '',
|
||||
'CLI' if opts.cli_environment else 'GUI'))
|
||||
|
||||
# If exclude_genre is blank, assume user wants all genre tags included
|
||||
# If exclude_genre is blank, assume user wants all tags as genres
|
||||
if opts.exclude_genre.strip() == '':
|
||||
opts.exclude_genre = '\[^.\]'
|
||||
build_log.append(" converting empty exclude_genre to '\[^.\]'")
|
||||
|
||||
#opts.exclude_genre = '\[^.\]'
|
||||
#build_log.append(" converting empty exclude_genre to '\[^.\]'")
|
||||
opts.exclude_genre = 'a^'
|
||||
build_log.append(" converting empty exclude_genre to 'a^'")
|
||||
if opts.connected_device['is_device_connected'] and \
|
||||
opts.connected_device['kind'] == 'device':
|
||||
if opts.connected_device['serial']:
|
||||
@ -304,13 +313,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
keys.sort()
|
||||
build_log.append(" opts:")
|
||||
for key in keys:
|
||||
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
||||
if key in ['catalog_title','author_clip','connected_kindle','description_clip',
|
||||
'exclude_book_marker','exclude_genre','exclude_tags',
|
||||
'exclusion_rules',
|
||||
'header_note_source_field','merge_comments',
|
||||
'exclusion_rules', 'fmt',
|
||||
'header_note_source_field','merge_comments_rule',
|
||||
'output_profile','prefix_rules','read_book_marker',
|
||||
'search_text','sort_by','sort_descriptions_by_author','sync',
|
||||
'thumb_width','wishlist_tag']:
|
||||
'thumb_width','use_existing_cover','wishlist_tag']:
|
||||
build_log.append(" %s: %s" % (key, repr(opts_dict[key])))
|
||||
|
||||
if opts.verbose:
|
||||
@ -323,26 +332,30 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
if opts.verbose:
|
||||
log.info(" Begin catalog source generation")
|
||||
catalog.createDirectoryStructure()
|
||||
catalog.copyResources()
|
||||
catalog.calculateThumbnailSize()
|
||||
catalog_source_built = catalog.buildSources()
|
||||
|
||||
if opts.verbose:
|
||||
if catalog_source_built:
|
||||
try:
|
||||
catalog.build_sources()
|
||||
if opts.verbose:
|
||||
log.info(" Completed catalog source generation\n")
|
||||
else:
|
||||
log.error(" *** Terminated catalog generation, check log for details ***")
|
||||
except (AuthorSortMismatchException, EmptyCatalogException), e:
|
||||
log.error(" *** Terminated catalog generation: %s ***" % e)
|
||||
except:
|
||||
log.error(" unhandled exception in catalog generator")
|
||||
raise
|
||||
|
||||
if catalog_source_built:
|
||||
else:
|
||||
recommendations = []
|
||||
recommendations.append(('remove_fake_margins', False,
|
||||
OptionRecommendation.HIGH))
|
||||
recommendations.append(('comments', '', OptionRecommendation.HIGH))
|
||||
|
||||
# >>> Use to debug generated catalog code before conversion <<<
|
||||
if False:
|
||||
setattr(opts,'debug_pipeline',os.path.expanduser("~/Desktop/Catalog debug"))
|
||||
"""
|
||||
>>> Use to debug generated catalog code before pipeline conversion <<<
|
||||
"""
|
||||
GENERATE_DEBUG_EPUB = False
|
||||
if GENERATE_DEBUG_EPUB:
|
||||
catalog_debug_path = os.path.join(os.path.expanduser('~'),'Desktop','Catalog debug')
|
||||
setattr(opts,'debug_pipeline',os.path.expanduser(catalog_debug_path))
|
||||
|
||||
dp = getattr(opts, 'debug_pipeline', None)
|
||||
if dp is not None:
|
||||
@ -357,9 +370,9 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
recommendations.append(('book_producer',opts.output_profile,
|
||||
OptionRecommendation.HIGH))
|
||||
|
||||
# If cover exists, use it
|
||||
# Use existing cover or generate new cover
|
||||
cpath = None
|
||||
generate_new_cover = False
|
||||
existing_cover = False
|
||||
try:
|
||||
search_text = 'title:"%s" author:%s' % (
|
||||
opts.catalog_title.replace('"', '\\"'), 'calibre')
|
||||
@ -367,19 +380,18 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
if matches:
|
||||
cpath = db.cover(matches[0], index_is_id=True, as_path=True)
|
||||
if cpath and os.path.exists(cpath):
|
||||
recommendations.append(('cover', cpath,
|
||||
OptionRecommendation.HIGH))
|
||||
log.info("using existing cover")
|
||||
else:
|
||||
log.info("no existing cover, generating new cover")
|
||||
generate_new_cover = True
|
||||
else:
|
||||
log.info("no existing cover, generating new cover")
|
||||
generate_new_cover = True
|
||||
existing_cover = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if generate_new_cover:
|
||||
if self.opts.use_existing_cover and not existing_cover:
|
||||
log.warning("no existing catalog cover found")
|
||||
|
||||
if self.opts.use_existing_cover and existing_cover:
|
||||
recommendations.append(('cover', cpath, OptionRecommendation.HIGH))
|
||||
log.info("using existing catalog cover")
|
||||
else:
|
||||
log.info("replacing catalog cover")
|
||||
new_cover_path = PersistentTemporaryFile(suffix='.jpg')
|
||||
new_cover = calibre_cover(opts.catalog_title.replace('"', '\\"'), 'calibre')
|
||||
new_cover_path.write(new_cover)
|
||||
@ -388,7 +400,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
# Run ebook-convert
|
||||
from calibre.ebooks.conversion.plumber import Plumber
|
||||
plumber = Plumber(os.path.join(catalog.catalogPath,
|
||||
plumber = Plumber(os.path.join(catalog.catalog_path,
|
||||
opts.basename + '.opf'), path_to_output, log, report_progress=notification,
|
||||
abort_after_input_dump=False)
|
||||
plumber.merge_ui_recommendations(recommendations)
|
||||
@ -399,6 +411,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
except:
|
||||
pass
|
||||
|
||||
if GENERATE_DEBUG_EPUB:
|
||||
from calibre.ebooks.tweak import zip_rebuilder
|
||||
input_path = os.path.join(catalog_debug_path,'input')
|
||||
shutil.copy(P('catalog/mimetype'),input_path)
|
||||
shutil.copytree(P('catalog/META-INF'),os.path.join(input_path,'META-INF'))
|
||||
zip_rebuilder(input_path, os.path.join(catalog_debug_path,'input.epub'))
|
||||
|
||||
# returns to gui2.actions.catalog:catalog_generated()
|
||||
return catalog.error
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user