mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge from trunk
This commit is contained in:
commit
d5f485d71b
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
blic.rs
|
blic.rs
|
||||||
'''
|
'''
|
||||||
@ -21,21 +21,53 @@ class Blic(BasicNewsRecipe):
|
|||||||
masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png'
|
masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png'
|
||||||
language = 'sr'
|
language = 'sr'
|
||||||
publication_type = 'newspaper'
|
publication_type = 'newspaper'
|
||||||
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Georgia, serif1, serif} .article_description{font-family: Arial, sans1, sans-serif} .img_full{float: none} img{margin-bottom: 0.8em} '
|
extra_css = """
|
||||||
|
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||||
|
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||||
|
body{font-family: Georgia, serif1, serif}
|
||||||
|
.articledescription,#nadnaslov,.article_info{font-family: Arial, sans1, sans-serif}
|
||||||
|
.img_full{float: none}
|
||||||
|
#nadnaslov{font-size: small}
|
||||||
|
#article_lead{font-size: 1.5em}
|
||||||
|
h1{color: red}
|
||||||
|
.potpis{font-size: x-small; color: gray}
|
||||||
|
.article_info{font-size: small}
|
||||||
|
img{margin-bottom: 0.8em; margin-top: 0.8em; display: block}
|
||||||
|
"""
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
'comment' : description
|
'comment' : description
|
||||||
, 'tags' : category
|
, 'tags' : category
|
||||||
, 'publisher': publisher
|
, 'publisher': publisher
|
||||||
, 'language' : language
|
, 'language' : language
|
||||||
|
, 'linearize_tables' : True
|
||||||
}
|
}
|
||||||
|
|
||||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||||
remove_tags_before = dict(name='div', attrs={'id':'article_info'})
|
remove_tags_before = dict(name='div', attrs={'id':'article_info'})
|
||||||
remove_tags = [dict(name=['object','link'])]
|
remove_tags = [dict(name=['object','link','meta','base','object','embed'])]
|
||||||
remove_attributes = ['width','height']
|
remove_attributes = ['width','height','m_id','m_ext','mlg_id','poll_id','v_id']
|
||||||
|
|
||||||
feeds = [(u'Danasnje Vesti', u'http://www.blic.rs/rss/danasnje-vesti')]
|
feeds = [
|
||||||
|
(u'Politika' , u'http://www.blic.rs/rss/Vesti/Politika')
|
||||||
|
,(u'Tema Dana' , u'http://www.blic.rs/rss/Vesti/Tema-Dana')
|
||||||
|
,(u'Svet' , u'http://www.blic.rs/rss/Vesti/Svet')
|
||||||
|
,(u'Drustvo' , u'http://www.blic.rs/rss/Vesti/Drustvo')
|
||||||
|
,(u'Ekonomija' , u'http://www.blic.rs/rss/Vesti/Ekonomija')
|
||||||
|
,(u'Hronika' , u'http://www.blic.rs/rss/Vesti/Hronika')
|
||||||
|
,(u'Beograd' , u'http://www.blic.rs/rss/Vesti/Beograd')
|
||||||
|
,(u'Srbija' , u'http://www.blic.rs/rss/Vesti/Srbija')
|
||||||
|
,(u'Vojvodina' , u'http://www.blic.rs/rss/Vesti/Vojvodina')
|
||||||
|
,(u'Republika Srpska' , u'http://www.blic.rs/rss/Vesti/Republika-Srpska')
|
||||||
|
,(u'Reportaza' , u'http://www.blic.rs/rss/Vesti/Reportaza')
|
||||||
|
,(u'Dodatak' , u'http://www.blic.rs/rss/Vesti/Dodatak')
|
||||||
|
,(u'Zabava' , u'http://www.blic.rs/rss/Zabava')
|
||||||
|
,(u'Kultura' , u'http://www.blic.rs/rss/Kultura')
|
||||||
|
,(u'Slobodno Vreme' , u'http://www.blic.rs/rss/Slobodno-vreme')
|
||||||
|
,(u'IT' , u'http://www.blic.rs/rss/IT')
|
||||||
|
,(u'Komentar' , u'http://www.blic.rs/rss/Komentar')
|
||||||
|
,(u'Intervju' , u'http://www.blic.rs/rss/Intervju')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
@ -44,4 +76,4 @@ class Blic(BasicNewsRecipe):
|
|||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
for item in soup.findAll(style=True):
|
for item in soup.findAll(style=True):
|
||||||
del item['style']
|
del item['style']
|
||||||
return self.adeify_images(soup)
|
return soup
|
||||||
|
@ -241,7 +241,7 @@ def get_parsed_proxy(typ='http', debug=True):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def browser(honor_time=True, max_time=2, mobile_browser=False):
|
def browser(honor_time=True, max_time=2, mobile_browser=False, user_agent=None):
|
||||||
'''
|
'''
|
||||||
Create a mechanize browser for web scraping. The browser handles cookies,
|
Create a mechanize browser for web scraping. The browser handles cookies,
|
||||||
refresh requests and ignores robots.txt. Also uses proxy if avaialable.
|
refresh requests and ignores robots.txt. Also uses proxy if avaialable.
|
||||||
@ -253,8 +253,10 @@ def browser(honor_time=True, max_time=2, mobile_browser=False):
|
|||||||
opener = Browser()
|
opener = Browser()
|
||||||
opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time)
|
opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time)
|
||||||
opener.set_handle_robots(False)
|
opener.set_handle_robots(False)
|
||||||
opener.addheaders = [('User-agent', ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \
|
if user_agent is None:
|
||||||
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13')]
|
user_agent = ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \
|
||||||
|
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13'
|
||||||
|
opener.addheaders = [('User-agent', user_agent)]
|
||||||
http_proxy = get_proxies().get('http', None)
|
http_proxy = get_proxies().get('http', None)
|
||||||
if http_proxy:
|
if http_proxy:
|
||||||
opener.set_proxies({'http':http_proxy})
|
opener.set_proxies({'http':http_proxy})
|
||||||
|
@ -21,7 +21,7 @@ class ANDROID(USBMS):
|
|||||||
# HTC
|
# HTC
|
||||||
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
||||||
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
||||||
0xc92 : [0x100], 0xc97: [0x226]},
|
0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]},
|
||||||
|
|
||||||
# Eken
|
# Eken
|
||||||
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] },
|
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] },
|
||||||
|
@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
Fetch cover from LibraryThing.com based on ISBN number.
|
Fetch cover from LibraryThing.com based on ISBN number.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, socket, os, re
|
import sys, socket, os, re, random
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
import mechanize
|
import mechanize
|
||||||
@ -16,13 +16,26 @@ from calibre.ebooks.chardet import strip_encoding_declarations
|
|||||||
|
|
||||||
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
|
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
|
||||||
|
|
||||||
|
def get_ua():
|
||||||
|
choices = [
|
||||||
|
'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11'
|
||||||
|
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
|
||||||
|
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'
|
||||||
|
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)'
|
||||||
|
'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16'
|
||||||
|
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.2.153.1 Safari/525.19'
|
||||||
|
'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11'
|
||||||
|
]
|
||||||
|
return choices[random.randint(0, len(choices)-1)]
|
||||||
|
|
||||||
|
|
||||||
class HeadRequest(mechanize.Request):
|
class HeadRequest(mechanize.Request):
|
||||||
|
|
||||||
def get_method(self):
|
def get_method(self):
|
||||||
return 'HEAD'
|
return 'HEAD'
|
||||||
|
|
||||||
def check_for_cover(isbn, timeout=5.):
|
def check_for_cover(isbn, timeout=5.):
|
||||||
br = browser()
|
br = browser(user_agent=get_ua())
|
||||||
br.set_handle_redirect(False)
|
br.set_handle_redirect(False)
|
||||||
try:
|
try:
|
||||||
br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout)
|
br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout)
|
||||||
@ -51,7 +64,7 @@ def login(br, username, password, force=True):
|
|||||||
|
|
||||||
def cover_from_isbn(isbn, timeout=5., username=None, password=None):
|
def cover_from_isbn(isbn, timeout=5., username=None, password=None):
|
||||||
src = None
|
src = None
|
||||||
br = browser()
|
br = browser(user_agent=get_ua())
|
||||||
try:
|
try:
|
||||||
return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg'
|
return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg'
|
||||||
except:
|
except:
|
||||||
@ -100,7 +113,7 @@ def get_social_metadata(title, authors, publisher, isbn, username=None,
|
|||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
mi = MetaInformation(title, authors)
|
mi = MetaInformation(title, authors)
|
||||||
if isbn:
|
if isbn:
|
||||||
br = browser()
|
br = browser(user_agent=get_ua())
|
||||||
if username and password:
|
if username and password:
|
||||||
try:
|
try:
|
||||||
login(br, username, password, force=False)
|
login(br, username, password, force=False)
|
||||||
|
@ -775,7 +775,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.original_tags = unicode(self.tags.text())
|
self.original_tags = unicode(self.tags.text())
|
||||||
else:
|
else:
|
||||||
self.tags.setText(self.original_tags)
|
self.tags.setText(self.original_tags)
|
||||||
d = TagEditor(self, self.db, self.row)
|
d = TagEditor(self, self.db, self.id)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
tag_string = ', '.join(d.tags)
|
tag_string = ', '.join(d.tags)
|
||||||
|
@ -10,13 +10,13 @@ from calibre.utils.icu import sort_key
|
|||||||
|
|
||||||
class TagEditor(QDialog, Ui_TagEditor):
|
class TagEditor(QDialog, Ui_TagEditor):
|
||||||
|
|
||||||
def __init__(self, window, db, index=None):
|
def __init__(self, window, db, id_=None):
|
||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_TagEditor.__init__(self)
|
Ui_TagEditor.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.db = db
|
self.db = db
|
||||||
self.index = index
|
self.index = db.row(id_)
|
||||||
if self.index is not None:
|
if self.index is not None:
|
||||||
tags = self.db.tags(self.index)
|
tags = self.db.tags(self.index)
|
||||||
else:
|
else:
|
||||||
|
984
src/calibre/gui2/metadata/basic_widgets.py
Normal file
984
src/calibre/gui2/metadata/basic_widgets.py
Normal file
@ -0,0 +1,984 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import textwrap, re, os
|
||||||
|
|
||||||
|
from PyQt4.Qt import Qt, QDateEdit, QDate, \
|
||||||
|
QIcon, QToolButton, QWidget, QLabel, QGridLayout, \
|
||||||
|
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
|
||||||
|
QPushButton, QSpinBox, QMessageBox, QLineEdit
|
||||||
|
|
||||||
|
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
|
||||||
|
EnComboBox, FormatList, ImageView, CompleteLineEdit
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.config import tweaks
|
||||||
|
from calibre.ebooks.metadata import title_sort, authors_to_string, \
|
||||||
|
string_to_authors, check_isbn
|
||||||
|
from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \
|
||||||
|
choose_files, error_dialog, choose_images, question_dialog
|
||||||
|
from calibre.utils.date import local_tz, qt_to_dt
|
||||||
|
from calibre import strftime
|
||||||
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
|
from calibre.utils.date import utcfromtimestamp
|
||||||
|
from calibre.gui2.comments_editor import Editor
|
||||||
|
from calibre.library.comments import comments_to_html
|
||||||
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
|
|
||||||
|
'''
|
||||||
|
The interface common to all widgets used to set basic metadata
|
||||||
|
class BasicMetadataWidget(object):
|
||||||
|
|
||||||
|
LABEL = "label text"
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
# Present in most but not all basic metadata widgets
|
||||||
|
def fget(self):
|
||||||
|
return None
|
||||||
|
def fset(self, val):
|
||||||
|
pass
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Title {{{
|
||||||
|
class TitleEdit(EnLineEdit):
|
||||||
|
|
||||||
|
TITLE_ATTR = 'title'
|
||||||
|
COMMIT = True
|
||||||
|
TOOLTIP = _('Change the title of this book')
|
||||||
|
LABEL = _('&Title:')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.dialog = parent
|
||||||
|
EnLineEdit.__init__(self, parent)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
|
||||||
|
def get_default(self):
|
||||||
|
return _('Unknown')
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
title = getattr(db, self.TITLE_ATTR)(id_, index_is_id=True)
|
||||||
|
self.current_val = title
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
title = self.current_val
|
||||||
|
if self.COMMIT:
|
||||||
|
getattr(db, 'set_', self.TITLE_ATTR)(id_, title, notify=False)
|
||||||
|
else:
|
||||||
|
getattr(db, 'set_', self.TITLE_ATTR)(id_, title, notify=False,
|
||||||
|
commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
title = unicode(self.text()).strip()
|
||||||
|
if not title:
|
||||||
|
title = self.get_default()
|
||||||
|
return title
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if hasattr(val, 'strip'):
|
||||||
|
val = val.strip()
|
||||||
|
if not val:
|
||||||
|
val = self.get_default()
|
||||||
|
self.setText(val)
|
||||||
|
self.setCursorPosition(0)
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
class TitleSortEdit(TitleEdit):
|
||||||
|
|
||||||
|
TITLE_ATTR = 'title_sort'
|
||||||
|
COMMIT = False
|
||||||
|
TOOLTIP = _('Specify how this book should be sorted when by title.'
|
||||||
|
' For example, The Exorcist might be sorted as Exorcist, The.')
|
||||||
|
LABEL = _('Title &sort:')
|
||||||
|
|
||||||
|
def __init__(self, parent, title_edit, autogen_button):
|
||||||
|
TitleEdit.__init__(self, parent)
|
||||||
|
self.title_edit = title_edit
|
||||||
|
|
||||||
|
base = self.TOOLTIP
|
||||||
|
ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
||||||
|
_(' The green color indicates that the current '
|
||||||
|
'title sort matches the current title'))
|
||||||
|
bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+
|
||||||
|
_(' The red color warns that the current '
|
||||||
|
'title sort does not match the current title. '
|
||||||
|
'No action is required if this is what you want.'))
|
||||||
|
self.tooltips = (ok_tooltip, bad_tooltip)
|
||||||
|
|
||||||
|
self.title_edit.textChanged.connect(self.update_state)
|
||||||
|
self.textChanged.connect(self.update_state)
|
||||||
|
|
||||||
|
autogen_button.clicked.connect(self.auto_generate)
|
||||||
|
self.update_state()
|
||||||
|
|
||||||
|
def update_state(self, *args):
|
||||||
|
ts = title_sort(self.title_edit.current_val)
|
||||||
|
normal = ts == self.current_val
|
||||||
|
if normal:
|
||||||
|
col = 'rgb(0, 255, 0, 20%)'
|
||||||
|
else:
|
||||||
|
col = 'rgb(255, 0, 0, 20%)'
|
||||||
|
self.setStyleSheet('QLineEdit { color: black; '
|
||||||
|
'background-color: %s; }'%col)
|
||||||
|
tt = self.tooltips[0 if normal else 1]
|
||||||
|
self.setToolTip(tt)
|
||||||
|
self.setWhatsThis(tt)
|
||||||
|
|
||||||
|
def auto_generate(self, *args):
|
||||||
|
self.current_val = title_sort(self.title_edit.current_val)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Authors {{{
|
||||||
|
class AuthorsEdit(CompleteComboBox):
|
||||||
|
|
||||||
|
TOOLTIP = ''
|
||||||
|
LABEL = _('&Author(s):')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.dialog = parent
|
||||||
|
CompleteComboBox.__init__(self, parent)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
self.setEditable(True)
|
||||||
|
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
|
||||||
|
|
||||||
|
def get_default(self):
|
||||||
|
return _('Unknown')
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
all_authors = db.all_authors()
|
||||||
|
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
for i in all_authors:
|
||||||
|
id, name = i
|
||||||
|
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
||||||
|
self.addItem(authors_to_string(name))
|
||||||
|
|
||||||
|
self.set_separator('&')
|
||||||
|
self.set_space_before_sep(True)
|
||||||
|
self.update_items_cache(db.all_author_names())
|
||||||
|
|
||||||
|
au = db.authors(id_, index_is_id=True)
|
||||||
|
if not au:
|
||||||
|
au = _('Unknown')
|
||||||
|
self.current_val = [a.strip().replace('|', ',') for a in au.split(',')]
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
authors = self.current_val
|
||||||
|
db.set_authors(id_, authors, notify=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
au = unicode(self.text()).strip()
|
||||||
|
if not au:
|
||||||
|
au = self.get_default()
|
||||||
|
return string_to_authors(au)
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if not val:
|
||||||
|
val = [self.get_default()]
|
||||||
|
self.setEditText(' & '.join([x.strip() for x in val]))
|
||||||
|
self.lineEdit().setCursorPosition(0)
|
||||||
|
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
class AuthorSortEdit(EnLineEdit):
|
||||||
|
|
||||||
|
TOOLTIP = _('Specify how the author(s) of this book should be sorted. '
|
||||||
|
'For example Charles Dickens should be sorted as Dickens, '
|
||||||
|
'Charles.\nIf the box is colored green, then text matches '
|
||||||
|
'the individual author\'s sort strings. If it is colored '
|
||||||
|
'red, then the authors and this text do not match.')
|
||||||
|
LABEL = _('Author s&ort:')
|
||||||
|
|
||||||
|
def __init__(self, parent, authors_edit, autogen_button, db):
|
||||||
|
EnLineEdit.__init__(self, parent)
|
||||||
|
self.authors_edit = authors_edit
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
base = self.TOOLTIP
|
||||||
|
ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
||||||
|
_(' The green color indicates that the current '
|
||||||
|
'author sort matches the current author'))
|
||||||
|
bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+
|
||||||
|
_(' The red color indicates that the current '
|
||||||
|
'author sort does not match the current author. '
|
||||||
|
'No action is required if this is what you want.'))
|
||||||
|
self.tooltips = (ok_tooltip, bad_tooltip)
|
||||||
|
|
||||||
|
self.authors_edit.editTextChanged.connect(self.update_state)
|
||||||
|
self.textChanged.connect(self.update_state)
|
||||||
|
|
||||||
|
autogen_button.clicked.connect(self.auto_generate)
|
||||||
|
self.update_state()
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return unicode(self.text()).strip()
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if not val:
|
||||||
|
val = ''
|
||||||
|
self.setText(val.strip())
|
||||||
|
self.setCursorPosition(0)
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def update_state(self, *args):
|
||||||
|
au = unicode(self.authors_edit.text())
|
||||||
|
au = re.sub(r'\s+et al\.$', '', au)
|
||||||
|
au = self.db.author_sort_from_authors(string_to_authors(au))
|
||||||
|
|
||||||
|
normal = au == self.current_val
|
||||||
|
if normal:
|
||||||
|
col = 'rgb(0, 255, 0, 20%)'
|
||||||
|
else:
|
||||||
|
col = 'rgb(255, 0, 0, 20%)'
|
||||||
|
self.setStyleSheet('QLineEdit { color: black; '
|
||||||
|
'background-color: %s; }'%col)
|
||||||
|
tt = self.tooltips[0 if normal else 1]
|
||||||
|
self.setToolTip(tt)
|
||||||
|
self.setWhatsThis(tt)
|
||||||
|
|
||||||
|
def auto_generate(self, *args):
|
||||||
|
au = unicode(self.authors_edit.text())
|
||||||
|
au = re.sub(r'\s+et al\.$', '', au)
|
||||||
|
authors = string_to_authors(au)
|
||||||
|
self.current_val = self.db.author_sort_from_authors(authors)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self.current_val = db.author_sort(id_, index_is_id=True)
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
aus = self.current_val
|
||||||
|
db.set_author_sort(id_, aus, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Series {{{
|
||||||
|
class SeriesEdit(EnComboBox):
|
||||||
|
|
||||||
|
TOOLTIP = _('List of known series. You can add new series.')
|
||||||
|
LABEL = _('&Series:')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
EnComboBox.__init__(self, parent)
|
||||||
|
self.dialog = parent
|
||||||
|
self.setSizeAdjustPolicy(
|
||||||
|
self.AdjustToMinimumContentsLengthWithIcon)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
self.setEditable(True)
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return unicode(self.currentText()).strip()
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if not val:
|
||||||
|
val = ''
|
||||||
|
self.setEditText(val.strip())
|
||||||
|
self.setCursorPosition(0)
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
all_series = db.all_series()
|
||||||
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
series_id = db.series_id(id_, index_is_id=True)
|
||||||
|
idx, c = None, 0
|
||||||
|
for i in all_series:
|
||||||
|
id, name = i
|
||||||
|
if id == series_id:
|
||||||
|
idx = c
|
||||||
|
self.addItem(name)
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
self.lineEdit().setText('')
|
||||||
|
if idx is not None:
|
||||||
|
self.setCurrentIndex(idx)
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
series = self.current_val
|
||||||
|
db.set_series(id_, series, notify=False, commit=True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
class SeriesIndexEdit(QDoubleSpinBox):
|
||||||
|
|
||||||
|
TOOLTIP = ''
|
||||||
|
LABEL = _('&Number:')
|
||||||
|
|
||||||
|
def __init__(self, parent, series_edit):
|
||||||
|
QDoubleSpinBox.__init__(self, parent)
|
||||||
|
self.dialog = parent
|
||||||
|
self.db = self.original_series_name = None
|
||||||
|
self.setMaximum(1000000)
|
||||||
|
self.series_edit = series_edit
|
||||||
|
series_edit.currentIndexChanged.connect(self.enable)
|
||||||
|
series_edit.editTextChanged.connect(self.enable)
|
||||||
|
series_edit.lineEdit().editingFinished.connect(self.increment)
|
||||||
|
self.enable()
|
||||||
|
|
||||||
|
def enable(self, *args):
|
||||||
|
self.setEnabled(bool(self.series_edit.current_val))
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.value()
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = 1.0
|
||||||
|
val = float(val)
|
||||||
|
self.setValue(val)
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self.db = db
|
||||||
|
if self.series_edit.current_val:
|
||||||
|
val = db.series_index(id_, index_is_id=True)
|
||||||
|
else:
|
||||||
|
val = 1.0
|
||||||
|
self.current_val = val
|
||||||
|
self.original_val = self.current_val
|
||||||
|
self.original_series_name = self.series_edit.original_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
db.set_series_index(id_, self.current_val, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def increment(self):
|
||||||
|
if self.db is not None:
|
||||||
|
try:
|
||||||
|
series = self.series_edit.current_val
|
||||||
|
if series and series != self.original_series_name:
|
||||||
|
ns = 1.0
|
||||||
|
if tweaks['series_index_auto_increment'] != 'const':
|
||||||
|
ns = self.db.get_next_series_num_for(series)
|
||||||
|
self.current_val = ns
|
||||||
|
self.original_series_name = series
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class BuddyLabel(QLabel): # {{{
|
||||||
|
|
||||||
|
def __init__(self, buddy):
|
||||||
|
QLabel.__init__(self, buddy.LABEL)
|
||||||
|
self.setBuddy(buddy)
|
||||||
|
self.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class Format(QListWidgetItem): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent, ext, size, path=None, timestamp=None):
|
||||||
|
self.path = path
|
||||||
|
self.ext = ext
|
||||||
|
self.size = float(size)/(1024*1024)
|
||||||
|
text = '%s (%.2f MB)'%(self.ext.upper(), self.size)
|
||||||
|
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
|
||||||
|
text, parent, QListWidgetItem.UserType)
|
||||||
|
if timestamp is not None:
|
||||||
|
ts = timestamp.astimezone(local_tz)
|
||||||
|
t = strftime('%a, %d %b %Y [%H:%M:%S]', ts.timetuple())
|
||||||
|
text = _('Last modified: %s')%t
|
||||||
|
self.setToolTip(text)
|
||||||
|
self.setStatusTip(text)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class FormatsManager(QWidget): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.dialog = parent
|
||||||
|
self.changed = False
|
||||||
|
|
||||||
|
self.l = l = QGridLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
self.cover_from_format_button = QToolButton(self)
|
||||||
|
self.cover_from_format_button.setToolTip(
|
||||||
|
_('Set the cover for the book from the selected format'))
|
||||||
|
self.cover_from_format_button.setIcon(QIcon(I('book.png')))
|
||||||
|
self.cover_from_format_button.setIconSize(QSize(32, 32))
|
||||||
|
|
||||||
|
self.metadata_from_format_button = QToolButton(self)
|
||||||
|
self.metadata_from_format_button.setIcon(QIcon(I('edit_input.png')))
|
||||||
|
self.metadata_from_format_button.setIconSize(QSize(32, 32))
|
||||||
|
# TODO: Implement the *_from_format buttons
|
||||||
|
|
||||||
|
self.add_format_button = QToolButton(self)
|
||||||
|
self.add_format_button.setIcon(QIcon(I('add_book.png')))
|
||||||
|
self.add_format_button.setIconSize(QSize(32, 32))
|
||||||
|
self.add_format_button.clicked.connect(self.add_format)
|
||||||
|
|
||||||
|
self.remove_format_button = QToolButton(self)
|
||||||
|
self.remove_format_button.setIcon(QIcon(I('trash.png')))
|
||||||
|
self.remove_format_button.setIconSize(QSize(32, 32))
|
||||||
|
self.remove_format_button.clicked.connect(self.remove_format)
|
||||||
|
|
||||||
|
self.formats = FormatList(self)
|
||||||
|
self.formats.setAcceptDrops(True)
|
||||||
|
self.formats.formats_dropped.connect(self.formats_dropped)
|
||||||
|
self.formats.delete_format.connect(self.remove_format)
|
||||||
|
self.formats.itemDoubleClicked.connect(self.show_format)
|
||||||
|
self.formats.setDragDropMode(self.formats.DropOnly)
|
||||||
|
self.formats.setIconSize(QSize(32, 32))
|
||||||
|
self.formats.setMaximumWidth(200)
|
||||||
|
|
||||||
|
l.addWidget(self.cover_from_format_button, 0, 0, 1, 1)
|
||||||
|
l.addWidget(self.metadata_from_format_button, 2, 0, 1, 1)
|
||||||
|
l.addWidget(self.add_format_button, 0, 2, 1, 1)
|
||||||
|
l.addWidget(self.remove_format_button, 2, 2, 1, 1)
|
||||||
|
l.addWidget(self.formats, 0, 1, 3, 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self.changed = False
|
||||||
|
exts = db.formats(id_, index_is_id=True)
|
||||||
|
if exts:
|
||||||
|
exts = exts.split(',')
|
||||||
|
for ext in exts:
|
||||||
|
if not ext:
|
||||||
|
ext = ''
|
||||||
|
size = db.sizeof_format(id_, ext, index_is_id=True)
|
||||||
|
timestamp = db.format_last_modified(id_, ext)
|
||||||
|
if size is None:
|
||||||
|
continue
|
||||||
|
Format(self.formats, ext, size, timestamp=timestamp)
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
if not self.changed:
|
||||||
|
return True
|
||||||
|
old_extensions, new_extensions, paths = set(), set(), {}
|
||||||
|
for row in range(self.formats.count()):
|
||||||
|
fmt = self.formats.item(row)
|
||||||
|
ext, path = fmt.ext.lower(), fmt.path
|
||||||
|
if 'unknown' in ext.lower():
|
||||||
|
ext = None
|
||||||
|
if path:
|
||||||
|
new_extensions.add(ext)
|
||||||
|
paths[ext] = path
|
||||||
|
else:
|
||||||
|
old_extensions.add(ext)
|
||||||
|
for ext in new_extensions:
|
||||||
|
db.add_format(id_, ext, open(paths[ext], 'rb'), notify=False,
|
||||||
|
index_is_id=True)
|
||||||
|
db_extensions = set([f.lower() for f in db.formats(id_,
|
||||||
|
index_is_id=True).split(',')])
|
||||||
|
extensions = new_extensions.union(old_extensions)
|
||||||
|
for ext in db_extensions:
|
||||||
|
if ext not in extensions:
|
||||||
|
db.remove_format(id_, ext, notify=False, index_is_id=True)
|
||||||
|
|
||||||
|
self.changed = False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def add_format(self, *args):
|
||||||
|
files = choose_files(self, 'add formats dialog',
|
||||||
|
_("Choose formats for ") +
|
||||||
|
self.dialog.title.current_val,
|
||||||
|
[(_('Books'), BOOK_EXTENSIONS)])
|
||||||
|
self._add_formats(files)
|
||||||
|
|
||||||
|
def _add_formats(self, paths):
|
||||||
|
added = False
|
||||||
|
if not paths:
|
||||||
|
return added
|
||||||
|
bad_perms = []
|
||||||
|
for _file in paths:
|
||||||
|
_file = os.path.abspath(_file)
|
||||||
|
if not os.access(_file, os.R_OK):
|
||||||
|
bad_perms.append(_file)
|
||||||
|
continue
|
||||||
|
|
||||||
|
nfile = run_plugins_on_import(_file)
|
||||||
|
if nfile is not None:
|
||||||
|
_file = nfile
|
||||||
|
stat = os.stat(_file)
|
||||||
|
size = stat.st_size
|
||||||
|
ext = os.path.splitext(_file)[1].lower().replace('.', '')
|
||||||
|
timestamp = utcfromtimestamp(stat.st_mtime)
|
||||||
|
for row in range(self.formats.count()):
|
||||||
|
fmt = self.formats.item(row)
|
||||||
|
if fmt.ext.lower() == ext:
|
||||||
|
self.formats.takeItem(row)
|
||||||
|
break
|
||||||
|
Format(self.formats, ext, size, path=_file, timestamp=timestamp)
|
||||||
|
self.changed = True
|
||||||
|
added = True
|
||||||
|
if bad_perms:
|
||||||
|
error_dialog(self, _('No permission'),
|
||||||
|
_('You do not have '
|
||||||
|
'permission to read the following files:'),
|
||||||
|
det_msg='\n'.join(bad_perms), show=True)
|
||||||
|
|
||||||
|
return added
|
||||||
|
|
||||||
|
def formats_dropped(self, event, paths):
|
||||||
|
if self._add_formats(paths):
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def remove_format(self, *args):
|
||||||
|
rows = self.formats.selectionModel().selectedRows(0)
|
||||||
|
for row in rows:
|
||||||
|
self.formats.takeItem(row.row())
|
||||||
|
self.changed = True
|
||||||
|
|
||||||
|
def show_format(self, item, *args):
|
||||||
|
fmt = item.ext
|
||||||
|
self.dialog.view_format.emit(fmt)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class Cover(ImageView): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
ImageView.__init__(self, parent)
|
||||||
|
self.dialog = parent
|
||||||
|
self._cdata = None
|
||||||
|
self.cover_changed.connect(self.set_pixmap_from_data)
|
||||||
|
|
||||||
|
self.select_cover_button = QPushButton(QIcon(I('document_open.png')),
|
||||||
|
_('&Browse'), parent)
|
||||||
|
self.trim_cover_button = QPushButton(QIcon(I('trim.png')),
|
||||||
|
_('T&rim'), parent)
|
||||||
|
self.remove_cover_button = QPushButton(QIcon(I('trash.png')),
|
||||||
|
_('&Remove'), parent)
|
||||||
|
|
||||||
|
self.select_cover_button.clicked.connect(self.select_cover)
|
||||||
|
self.remove_cover_button.clicked.connect(self.remove_cover)
|
||||||
|
self.trim_cover_button.clicked.connect(self.trim_cover)
|
||||||
|
|
||||||
|
self.download_cover_button = QPushButton(_('Download co&ver'), parent)
|
||||||
|
self.generate_cover_button = QPushButton(_('&Generate cover'), parent)
|
||||||
|
|
||||||
|
self.download_cover_button.clicked.connect(self.download_cover)
|
||||||
|
self.generate_cover_button.clicked.connect(self.generate_cover)
|
||||||
|
|
||||||
|
self.buttons = [self.select_cover_button, self.remove_cover_button,
|
||||||
|
self.trim_cover_button, self.download_cover_button,
|
||||||
|
self.generate_cover_button]
|
||||||
|
|
||||||
|
def select_cover(self, *args):
|
||||||
|
files = choose_images(self, 'change cover dialog',
|
||||||
|
_('Choose cover for ') +
|
||||||
|
self.dialog.title.current_val)
|
||||||
|
if not files:
|
||||||
|
return
|
||||||
|
_file = files[0]
|
||||||
|
if _file:
|
||||||
|
_file = os.path.abspath(_file)
|
||||||
|
if not os.access(_file, os.R_OK):
|
||||||
|
d = error_dialog(self, _('Cannot read'),
|
||||||
|
_('You do not have permission to read the file: ') + _file)
|
||||||
|
d.exec_()
|
||||||
|
return
|
||||||
|
cf, cover = None, None
|
||||||
|
try:
|
||||||
|
cf = open(_file, "rb")
|
||||||
|
cover = cf.read()
|
||||||
|
except IOError, e:
|
||||||
|
d = error_dialog(self, _('Error reading file'),
|
||||||
|
_("<p>There was an error reading from file: <br /><b>")
|
||||||
|
+ _file + "</b></p><br />"+str(e))
|
||||||
|
d.exec_()
|
||||||
|
if cover:
|
||||||
|
orig = self.current_val
|
||||||
|
self.current_val = cover
|
||||||
|
if self.current_val is None:
|
||||||
|
self.current_val = orig
|
||||||
|
error_dialog(self,
|
||||||
|
_("Not a valid picture"),
|
||||||
|
_file + _(" is not a valid picture"), show=True)
|
||||||
|
|
||||||
|
def remove_cover(self, *args):
|
||||||
|
self.current_val = None
|
||||||
|
|
||||||
|
def trim_cover(self, *args):
|
||||||
|
from calibre.utils.magick import Image
|
||||||
|
cdata = self.current_val
|
||||||
|
if not cdata:
|
||||||
|
return
|
||||||
|
im = Image()
|
||||||
|
im.load(cdata)
|
||||||
|
im.trim(10)
|
||||||
|
cdata = im.export('png')
|
||||||
|
self.current_val = cdata
|
||||||
|
|
||||||
|
def download_cover(self, *args):
|
||||||
|
pass # TODO: Implement this
|
||||||
|
|
||||||
|
def generate_cover(self, *args):
|
||||||
|
from calibre.ebooks import calibre_cover
|
||||||
|
from calibre.ebooks.metadata import fmt_sidx
|
||||||
|
from calibre.gui2 import config
|
||||||
|
title = self.dialog.title.current_val
|
||||||
|
author = authors_to_string(self.dialog.authors.current_val)
|
||||||
|
if not title or not author:
|
||||||
|
return error_dialog(self, _('Specify title and author'),
|
||||||
|
_('You must specify a title and author before generating '
|
||||||
|
'a cover'), show=True)
|
||||||
|
series = self.dialog.series.current_val
|
||||||
|
series_string = None
|
||||||
|
if series:
|
||||||
|
series_string = _('Book %s of %s')%(
|
||||||
|
fmt_sidx(self.dialog.series_index.current_val,
|
||||||
|
use_roman=config['use_roman_numerals_for_series_number']), series)
|
||||||
|
self.current_val = calibre_cover(title, author,
|
||||||
|
series_string=series_string)
|
||||||
|
|
||||||
|
def set_pixmap_from_data(self, data):
|
||||||
|
if not data:
|
||||||
|
self.current_val = None
|
||||||
|
return
|
||||||
|
orig = self.current_val
|
||||||
|
self.current_val = data
|
||||||
|
if self.current_val is None:
|
||||||
|
error_dialog(self, _('Invalid cover'),
|
||||||
|
_('Could not change cover as the image is invalid.'),
|
||||||
|
show=True)
|
||||||
|
self.current_val = orig
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self._cdata = None
|
||||||
|
self.current_val = db.cover(id_, index_is_id=True)
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def changed(self):
|
||||||
|
return self.current_val != self.original_val
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self):
|
||||||
|
return self._cdata
|
||||||
|
def fset(self, cdata):
|
||||||
|
self._cdata = None
|
||||||
|
pm = QPixmap()
|
||||||
|
if cdata:
|
||||||
|
pm.loadFromData(cdata)
|
||||||
|
if pm.isNull():
|
||||||
|
pm = QPixmap(I('default_cover.png'))
|
||||||
|
else:
|
||||||
|
self._cdata = cdata
|
||||||
|
self.setPixmap(pm)
|
||||||
|
tt = _('This book has no cover')
|
||||||
|
if self._cdata:
|
||||||
|
tt = _('Cover size: %dx%d pixels') % \
|
||||||
|
(pm.width(), pm.height())
|
||||||
|
self.setToolTip(tt)
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
if self.changed:
|
||||||
|
if self.current_val:
|
||||||
|
db.set_cover(id_, self.current_val, notify=False, commit=False)
|
||||||
|
else:
|
||||||
|
db.remove_cover(id_, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class CommentsEdit(Editor): # {{{
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self):
|
||||||
|
return self.html
|
||||||
|
def fset(self, val):
|
||||||
|
if not val or not val.strip():
|
||||||
|
val = ''
|
||||||
|
else:
|
||||||
|
val = comments_to_html(val)
|
||||||
|
self.html = val
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self.current_val = db.comments(id_, index_is_id=True)
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
db.set_comment(id_, self.current_val, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class RatingEdit(QSpinBox): # {{{
|
||||||
|
LABEL = _('&Rating:')
|
||||||
|
TOOLTIP = _('Rating of this book. 0-5 stars')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QSpinBox.__init__(self, parent)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
self.setMaximum(5)
|
||||||
|
self.setSuffix(' ' + _('stars'))
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self):
|
||||||
|
return self.value()
|
||||||
|
def fset(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = 0
|
||||||
|
val = int(val)
|
||||||
|
if val < 0:
|
||||||
|
val = 0
|
||||||
|
if val > 5:
|
||||||
|
val = 5
|
||||||
|
self.setValue(val)
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
val = db.rating(id_, index_is_id=True)
|
||||||
|
if val > 0:
|
||||||
|
val = int(val/2.)
|
||||||
|
else:
|
||||||
|
val = 0
|
||||||
|
self.current_val = val
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
db.set_rating(id_, 2*self.current_val, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class TagsEdit(CompleteLineEdit): # {{{
|
||||||
|
LABEL = _('Ta&gs:')
|
||||||
|
TOOLTIP = '<p>'+_('Tags categorize the book. This is particularly '
|
||||||
|
'useful while searching. <br><br>They can be any words'
|
||||||
|
'or phrases, separated by commas.')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
CompleteLineEdit.__init__(self, parent)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self):
|
||||||
|
return [x.strip() for x in unicode(self.text()).split(',')]
|
||||||
|
def fset(self, val):
|
||||||
|
if not val:
|
||||||
|
val = []
|
||||||
|
self.setText(', '.join([x.strip() for x in val]))
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
tags = db.tags(id_, index_is_id=True)
|
||||||
|
tags = tags.split(',') if tags else []
|
||||||
|
self.current_val = tags
|
||||||
|
self.update_items_cache(db.all_tags())
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def changed(self):
|
||||||
|
return self.current_val != self.original_val
|
||||||
|
|
||||||
|
def edit(self, db, id_):
|
||||||
|
if self.changed:
|
||||||
|
if question_dialog(self, _('Tags changed'),
|
||||||
|
_('You have changed the tags. In order to use the tags'
|
||||||
|
' editor, you must either discard or apply these '
|
||||||
|
'changes'), show_copy_button=False,
|
||||||
|
buttons=QMessageBox.Apply|QMessageBox.Discard,
|
||||||
|
yes_button=QMessageBox.Apply):
|
||||||
|
self.commit(db, id_)
|
||||||
|
db.commit()
|
||||||
|
self.original_val = self.current_val
|
||||||
|
else:
|
||||||
|
self.current_val = self.original_val
|
||||||
|
d = TagEditor(self, db, id_)
|
||||||
|
if d.exec_() == TagEditor.Accepted:
|
||||||
|
self.current_val = d.tags
|
||||||
|
self.update_items_cache(db.all_tags())
|
||||||
|
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
db.set_tags(id_, self.current_val, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ISBNEdit(QLineEdit): # {{{
|
||||||
|
LABEL = _('IS&BN:')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QLineEdit.__init__(self, parent)
|
||||||
|
self.pat = re.compile(r'[^0-9a-zA-Z]')
|
||||||
|
self.textChanged.connect(self.validate)
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self):
|
||||||
|
return self.pat.sub('', unicode(self.text()).strip())
|
||||||
|
def fset(self, val):
|
||||||
|
if not val:
|
||||||
|
val = ''
|
||||||
|
self.setText(val.strip())
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self.current_val = db.isbn(id_, index_is_id=True)
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
db.set_isbn(id_, self.current_val, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate(self, *args):
|
||||||
|
isbn = self.current_val
|
||||||
|
tt = _('This ISBN number is valid')
|
||||||
|
if not isbn:
|
||||||
|
col = 'rgba(0,255,0,0%)'
|
||||||
|
elif check_isbn(isbn) is not None:
|
||||||
|
col = 'rgba(0,255,0,20%)'
|
||||||
|
else:
|
||||||
|
col = 'rgba(255,0,0,20%)'
|
||||||
|
tt = _('This ISBN number is invalid')
|
||||||
|
self.setToolTip(tt)
|
||||||
|
self.setStyleSheet('QLineEdit { background-color: %s }'%col)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class PublisherEdit(EnComboBox): # {{{
|
||||||
|
LABEL = _('&Publisher:')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
EnComboBox.__init__(self, parent)
|
||||||
|
self.setSizeAdjustPolicy(
|
||||||
|
self.AdjustToMinimumContentsLengthWithIcon)
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return unicode(self.currentText()).strip()
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if not val:
|
||||||
|
val = ''
|
||||||
|
self.setEditText(val.strip())
|
||||||
|
self.setCursorPosition(0)
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
all_publishers = db.all_publishers()
|
||||||
|
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
publisher_id = db.publisher_id(id_, index_is_id=True)
|
||||||
|
idx, c = None, 0
|
||||||
|
for i in all_publishers:
|
||||||
|
id, name = i
|
||||||
|
if id == publisher_id:
|
||||||
|
idx = c
|
||||||
|
self.addItem(name)
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
self.setEditText('')
|
||||||
|
if idx is not None:
|
||||||
|
self.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
db.set_publisher(id_, self.current_val, notify=False, commit=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class DateEdit(QDateEdit): # {{{
|
||||||
|
|
||||||
|
TOOLTIP = ''
|
||||||
|
LABEL = _('&Date:')
|
||||||
|
FMT = 'd MMM yyyy'
|
||||||
|
ATTR = 'timestamp'
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QDateEdit.__init__(self, parent)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
|
fmt = self.FMT
|
||||||
|
if fmt is None:
|
||||||
|
fmt = tweaks['gui_pubdate_display_format']
|
||||||
|
if fmt is None:
|
||||||
|
fmt = 'MMM yyyy'
|
||||||
|
self.setDisplayFormat(fmt)
|
||||||
|
self.setCalendarPopup(True)
|
||||||
|
self.setMinimumDate(UNDEFINED_QDATE)
|
||||||
|
self.setSpecialValueText(_('Undefined'))
|
||||||
|
self.clear_button = QToolButton(parent)
|
||||||
|
self.clear_button.setIcon(QIcon(I('trash.png')))
|
||||||
|
self.clear_button.setToolTip(_('Clear date'))
|
||||||
|
self.clear_button.clicked.connect(self.reset_date)
|
||||||
|
|
||||||
|
def reset_date(self, *args):
|
||||||
|
self.current_val = None
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self):
|
||||||
|
return qt_to_dt(self.date())
|
||||||
|
def fset(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = UNDEFINED_DATE
|
||||||
|
self.setDate(QDate(val.year, val.month, val.day))
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
self.current_val = getattr(db, self.ATTR)(id_, index_is_id=True)
|
||||||
|
self.original_val = self.current_val
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
if self.changed:
|
||||||
|
getattr(db, 'set_'+self.ATTR)(id_, self.current_val, commit=False,
|
||||||
|
notify=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def changed(self):
|
||||||
|
o, c = self.original_val, self.current_val
|
||||||
|
return o.year != c.year or o.month != c.month or o.day != c.day
|
||||||
|
|
||||||
|
class PubdateEdit(DateEdit):
|
||||||
|
LABEL = _('Publishe&d:')
|
||||||
|
FMT = None
|
||||||
|
ATTR = 'pubdate'
|
||||||
|
|
||||||
|
# }}}
|
@ -5,395 +5,23 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import textwrap, re
|
|
||||||
|
|
||||||
from PyQt4.Qt import QDialogButtonBox, Qt, QTabWidget, QScrollArea, \
|
from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \
|
||||||
QVBoxLayout, QIcon, QToolButton, QWidget, QLabel, QGridLayout, \
|
QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \
|
||||||
QDoubleSpinBox
|
QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, \
|
||||||
|
QSizePolicy
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||||
from calibre.gui2 import ResizableDialog
|
from calibre.gui2 import ResizableDialog
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \
|
||||||
from calibre.utils.config import tweaks
|
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \
|
||||||
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
|
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \
|
||||||
EnComboBox
|
BuddyLabel, DateEdit, PubdateEdit
|
||||||
from calibre.ebooks.metadata import title_sort, authors_to_string, \
|
|
||||||
string_to_authors
|
|
||||||
|
|
||||||
'''
|
|
||||||
The interface common to all widgets used to set basic metadata
|
|
||||||
class BasicMetadataWidget(object):
|
|
||||||
|
|
||||||
LABEL = "label text"
|
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def current_val(self):
|
|
||||||
def fget(self):
|
|
||||||
return None
|
|
||||||
def fset(self, val):
|
|
||||||
pass
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Title {{{
|
|
||||||
class TitleEdit(EnLineEdit):
|
|
||||||
|
|
||||||
TITLE_ATTR = 'title'
|
|
||||||
COMMIT = True
|
|
||||||
TOOLTIP = _('Change the title of this book')
|
|
||||||
LABEL = _('&Title:')
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
self.dialog = parent
|
|
||||||
EnLineEdit.__init__(self, parent)
|
|
||||||
self.setToolTip(self.TOOLTIP)
|
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
|
||||||
|
|
||||||
def get_default(self):
|
|
||||||
return _('Unknown')
|
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
|
||||||
title = getattr(db, self.TITLE_ATTR)(id_, index_is_id=True)
|
|
||||||
self.current_val = title
|
|
||||||
self.original_val = self.current_val
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
|
||||||
title = self.current_val
|
|
||||||
if self.COMMIT:
|
|
||||||
getattr(db, 'set_', self.TITLE_ATTR)(id_, title, notify=False)
|
|
||||||
else:
|
|
||||||
getattr(db, 'set_', self.TITLE_ATTR)(id_, title, notify=False,
|
|
||||||
commit=False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def current_val(self):
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
title = unicode(self.text()).strip()
|
|
||||||
if not title:
|
|
||||||
title = self.get_default()
|
|
||||||
return title
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if hasattr(val, 'strip'):
|
|
||||||
val = val.strip()
|
|
||||||
if not val:
|
|
||||||
val = self.get_default()
|
|
||||||
self.setText(val)
|
|
||||||
self.setCursorPosition(0)
|
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
|
|
||||||
class TitleSortEdit(TitleEdit):
|
|
||||||
|
|
||||||
TITLE_ATTR = 'title_sort'
|
|
||||||
COMMIT = False
|
|
||||||
TOOLTIP = _('Specify how this book should be sorted when by title.'
|
|
||||||
' For example, The Exorcist might be sorted as Exorcist, The.')
|
|
||||||
LABEL = _('Title &sort:')
|
|
||||||
|
|
||||||
def __init__(self, parent, title_edit, autogen_button):
|
|
||||||
TitleEdit.__init__(self, parent)
|
|
||||||
self.title_edit = title_edit
|
|
||||||
|
|
||||||
base = self.TOOLTIP
|
|
||||||
ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
|
||||||
_(' The green color indicates that the current '
|
|
||||||
'title sort matches the current title'))
|
|
||||||
bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+
|
|
||||||
_(' The red color warns that the current '
|
|
||||||
'title sort does not match the current title. '
|
|
||||||
'No action is required if this is what you want.'))
|
|
||||||
self.tooltips = (ok_tooltip, bad_tooltip)
|
|
||||||
|
|
||||||
self.title_edit.textChanged.connect(self.update_state)
|
|
||||||
self.textChanged.connect(self.update_state)
|
|
||||||
|
|
||||||
autogen_button.clicked.connect(self.auto_generate)
|
|
||||||
self.update_state()
|
|
||||||
|
|
||||||
def update_state(self, *args):
|
|
||||||
ts = title_sort(self.title_edit.current_val)
|
|
||||||
normal = ts == self.current_val
|
|
||||||
if normal:
|
|
||||||
col = 'rgb(0, 255, 0, 20%)'
|
|
||||||
else:
|
|
||||||
col = 'rgb(255, 0, 0, 20%)'
|
|
||||||
self.setStyleSheet('QLineEdit { color: black; '
|
|
||||||
'background-color: %s; }'%col)
|
|
||||||
tt = self.tooltips[0 if normal else 1]
|
|
||||||
self.setToolTip(tt)
|
|
||||||
self.setWhatsThis(tt)
|
|
||||||
|
|
||||||
def auto_generate(self, *args):
|
|
||||||
self.current_val = title_sort(self.title_edit.current_val)
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
# Authors {{{
|
|
||||||
class AuthorsEdit(CompleteComboBox):
|
|
||||||
|
|
||||||
TOOLTIP = ''
|
|
||||||
LABEL = _('&Author(s):')
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
self.dialog = parent
|
|
||||||
CompleteComboBox.__init__(self, parent)
|
|
||||||
self.setToolTip(self.TOOLTIP)
|
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
|
||||||
self.setEditable(True)
|
|
||||||
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
|
|
||||||
|
|
||||||
def get_default(self):
|
|
||||||
return _('Unknown')
|
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
|
||||||
all_authors = db.all_authors()
|
|
||||||
all_authors.sort(key=lambda x : sort_key(x[1]))
|
|
||||||
for i in all_authors:
|
|
||||||
id, name = i
|
|
||||||
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
|
||||||
self.addItem(authors_to_string(name))
|
|
||||||
|
|
||||||
self.set_separator('&')
|
|
||||||
self.set_space_before_sep(True)
|
|
||||||
self.update_items_cache(db.all_author_names())
|
|
||||||
|
|
||||||
au = db.authors(id_, index_is_id=True)
|
|
||||||
if not au:
|
|
||||||
au = _('Unknown')
|
|
||||||
self.current_val = [a.strip().replace('|', ',') for a in au.split(',')]
|
|
||||||
self.original_val = self.current_val
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
|
||||||
authors = self.current_val
|
|
||||||
db.set_authors(id_, authors, notify=False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def current_val(self):
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
au = unicode(self.text()).strip()
|
|
||||||
if not au:
|
|
||||||
au = self.get_default()
|
|
||||||
return string_to_authors(au)
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if not val:
|
|
||||||
val = [self.get_default()]
|
|
||||||
self.setEditText(' & '.join([x.strip() for x in val]))
|
|
||||||
self.lineEdit().setCursorPosition(0)
|
|
||||||
|
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
|
|
||||||
class AuthorSortEdit(EnLineEdit):
|
|
||||||
|
|
||||||
TOOLTIP = _('Specify how the author(s) of this book should be sorted. '
|
|
||||||
'For example Charles Dickens should be sorted as Dickens, '
|
|
||||||
'Charles.\nIf the box is colored green, then text matches '
|
|
||||||
'the individual author\'s sort strings. If it is colored '
|
|
||||||
'red, then the authors and this text do not match.')
|
|
||||||
LABEL = _('Author s&ort:')
|
|
||||||
|
|
||||||
def __init__(self, parent, authors_edit, autogen_button, db):
|
|
||||||
EnLineEdit.__init__(self, parent)
|
|
||||||
self.authors_edit = authors_edit
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
base = self.TOOLTIP
|
|
||||||
ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
|
||||||
_(' The green color indicates that the current '
|
|
||||||
'author sort matches the current author'))
|
|
||||||
bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+
|
|
||||||
_(' The red color indicates that the current '
|
|
||||||
'author sort does not match the current author. '
|
|
||||||
'No action is required if this is what you want.'))
|
|
||||||
self.tooltips = (ok_tooltip, bad_tooltip)
|
|
||||||
|
|
||||||
self.authors_edit.editTextChanged.connect(self.update_state)
|
|
||||||
self.textChanged.connect(self.update_state)
|
|
||||||
|
|
||||||
autogen_button.clicked.connect(self.auto_generate)
|
|
||||||
self.update_state()
|
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def current_val(self):
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return unicode(self.text()).strip()
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if not val:
|
|
||||||
val = ''
|
|
||||||
self.setText(val.strip())
|
|
||||||
self.setCursorPosition(0)
|
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
|
|
||||||
def update_state(self, *args):
|
|
||||||
au = unicode(self.authors_edit.text())
|
|
||||||
au = re.sub(r'\s+et al\.$', '', au)
|
|
||||||
au = self.db.author_sort_from_authors(string_to_authors(au))
|
|
||||||
|
|
||||||
normal = au == self.current_val
|
|
||||||
if normal:
|
|
||||||
col = 'rgb(0, 255, 0, 20%)'
|
|
||||||
else:
|
|
||||||
col = 'rgb(255, 0, 0, 20%)'
|
|
||||||
self.setStyleSheet('QLineEdit { color: black; '
|
|
||||||
'background-color: %s; }'%col)
|
|
||||||
tt = self.tooltips[0 if normal else 1]
|
|
||||||
self.setToolTip(tt)
|
|
||||||
self.setWhatsThis(tt)
|
|
||||||
|
|
||||||
def auto_generate(self, *args):
|
|
||||||
au = unicode(self.authors_edit.text())
|
|
||||||
au = re.sub(r'\s+et al\.$', '', au)
|
|
||||||
authors = string_to_authors(au)
|
|
||||||
self.current_val = self.db.author_sort_from_authors(authors)
|
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
|
||||||
self.current_val = db.author_sort(id_, index_is_id=True)
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
|
||||||
aus = self.current_val
|
|
||||||
db.set_author_sort(id_, aus, notify=False, commit=False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
# Series {{{
|
|
||||||
class SeriesEdit(EnComboBox):
|
|
||||||
|
|
||||||
TOOLTIP = _('List of known series. You can add new series.')
|
|
||||||
LABEL = _('&Series:')
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
EnComboBox.__init__(self, parent)
|
|
||||||
self.dialog = parent
|
|
||||||
self.setSizeAdjustPolicy(
|
|
||||||
self.AdjustToMinimumContentsLengthWithIcon)
|
|
||||||
self.setToolTip(self.TOOLTIP)
|
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
|
||||||
self.setEditable(True)
|
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def current_val(self):
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return unicode(self.currentText()).strip()
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if not val:
|
|
||||||
val = ''
|
|
||||||
self.setEditText(val.strip())
|
|
||||||
self.setCursorPosition(0)
|
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
|
||||||
all_series = db.all_series()
|
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
|
||||||
series_id = db.series_id(id_, index_is_id=True)
|
|
||||||
idx, c = None, 0
|
|
||||||
for i in all_series:
|
|
||||||
id, name = i
|
|
||||||
if id == series_id:
|
|
||||||
idx = c
|
|
||||||
self.addItem(name)
|
|
||||||
c += 1
|
|
||||||
|
|
||||||
self.lineEdit().setText('')
|
|
||||||
if idx is not None:
|
|
||||||
self.setCurrentIndex(idx)
|
|
||||||
self.original_val = self.current_val
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
|
||||||
series = self.current_val
|
|
||||||
db.set_series(id_, series, notify=False, commit=True)
|
|
||||||
return True
|
|
||||||
|
|
||||||
class SeriesIndexEdit(QDoubleSpinBox):
|
|
||||||
|
|
||||||
TOOLTIP = ''
|
|
||||||
LABEL = _('&Number:')
|
|
||||||
|
|
||||||
def __init__(self, parent, series_edit):
|
|
||||||
QDoubleSpinBox.__init__(self, parent)
|
|
||||||
self.dialog = parent
|
|
||||||
self.db = self.original_series_name = None
|
|
||||||
self.setMaximum(1000000)
|
|
||||||
self.series_edit = series_edit
|
|
||||||
series_edit.currentIndexChanged.connect(self.enable)
|
|
||||||
series_edit.editTextChanged.connect(self.enable)
|
|
||||||
series_edit.lineEdit().editingFinished.connect(self.increment)
|
|
||||||
self.enable()
|
|
||||||
|
|
||||||
def enable(self, *args):
|
|
||||||
self.setEnabled(bool(self.series_edit.current_val))
|
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def current_val(self):
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.value()
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if val is None:
|
|
||||||
val = 1.0
|
|
||||||
val = float(val)
|
|
||||||
self.setValue(val)
|
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
|
||||||
self.db = db
|
|
||||||
if self.series_edit.current_val:
|
|
||||||
val = db.series_index(id_, index_is_id=True)
|
|
||||||
else:
|
|
||||||
val = 1.0
|
|
||||||
self.current_val = val
|
|
||||||
self.original_val = self.current_val
|
|
||||||
self.original_series_name = self.series_edit.original_val
|
|
||||||
|
|
||||||
def commit(self, db, id_):
|
|
||||||
db.set_series_index(id_, self.current_val, notify=False, commit=False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def increment(self):
|
|
||||||
if self.db is not None:
|
|
||||||
try:
|
|
||||||
series = self.series_edit.current_val
|
|
||||||
if series and series != self.original_series_name:
|
|
||||||
ns = 1.0
|
|
||||||
if tweaks['series_index_auto_increment'] != 'const':
|
|
||||||
ns = self.db.get_next_series_num_for(series)
|
|
||||||
self.current_val = ns
|
|
||||||
self.original_series_name = series
|
|
||||||
except:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class BuddyLabel(QLabel):
|
|
||||||
|
|
||||||
def __init__(self, buddy):
|
|
||||||
QLabel.__init__(self, buddy.LABEL)
|
|
||||||
self.setBuddy(buddy)
|
|
||||||
self.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
|
|
||||||
|
|
||||||
class MetadataSingleDialog(ResizableDialog):
|
class MetadataSingleDialog(ResizableDialog):
|
||||||
|
|
||||||
|
view_format = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, db, parent=None):
|
def __init__(self, db, parent=None):
|
||||||
self.db = db
|
self.db = db
|
||||||
ResizableDialog.__init__(self, parent)
|
ResizableDialog.__init__(self, parent)
|
||||||
@ -427,9 +55,9 @@ class MetadataSingleDialog(ResizableDialog):
|
|||||||
self.do_layout()
|
self.do_layout()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def create_basic_metadata_widgets(self):
|
def create_basic_metadata_widgets(self): # {{{
|
||||||
self.basic_metadata_widgets = []
|
self.basic_metadata_widgets = []
|
||||||
# Title
|
|
||||||
self.title = TitleEdit(self)
|
self.title = TitleEdit(self)
|
||||||
self.deduce_title_sort_button = QToolButton(self)
|
self.deduce_title_sort_button = QToolButton(self)
|
||||||
self.deduce_title_sort_button.setToolTip(
|
self.deduce_title_sort_button.setToolTip(
|
||||||
@ -442,7 +70,6 @@ class MetadataSingleDialog(ResizableDialog):
|
|||||||
self.deduce_title_sort_button)
|
self.deduce_title_sort_button)
|
||||||
self.basic_metadata_widgets.extend([self.title, self.title_sort])
|
self.basic_metadata_widgets.extend([self.title, self.title_sort])
|
||||||
|
|
||||||
# Authors
|
|
||||||
self.authors = AuthorsEdit(self)
|
self.authors = AuthorsEdit(self)
|
||||||
self.deduce_author_sort_button = QToolButton(self)
|
self.deduce_author_sort_button = QToolButton(self)
|
||||||
self.deduce_author_sort_button.setToolTip(_(
|
self.deduce_author_sort_button.setToolTip(_(
|
||||||
@ -468,7 +95,45 @@ class MetadataSingleDialog(ResizableDialog):
|
|||||||
self.series_index = SeriesIndexEdit(self, self.series)
|
self.series_index = SeriesIndexEdit(self, self.series)
|
||||||
self.basic_metadata_widgets.extend([self.series, self.series_index])
|
self.basic_metadata_widgets.extend([self.series, self.series_index])
|
||||||
|
|
||||||
def do_layout(self):
|
self.formats_manager = FormatsManager(self)
|
||||||
|
self.basic_metadata_widgets.append(self.formats_manager)
|
||||||
|
|
||||||
|
self.cover = Cover(self)
|
||||||
|
self.basic_metadata_widgets.append(self.cover)
|
||||||
|
|
||||||
|
self.comments = CommentsEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.comments)
|
||||||
|
|
||||||
|
self.rating = RatingEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.rating)
|
||||||
|
|
||||||
|
self.tags = TagsEdit(self)
|
||||||
|
self.tags_editor_button = QToolButton(self)
|
||||||
|
self.tags_editor_button.setToolTip(_('Open Tag Editor'))
|
||||||
|
self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
|
||||||
|
self.tags_editor_button.clicked.connect(self.tags_editor)
|
||||||
|
self.basic_metadata_widgets.append(self.tags)
|
||||||
|
|
||||||
|
self.isbn = ISBNEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.isbn)
|
||||||
|
|
||||||
|
self.publisher = PublisherEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.publisher)
|
||||||
|
|
||||||
|
self.timestamp = DateEdit(self)
|
||||||
|
self.pubdate = PubdateEdit(self)
|
||||||
|
self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
|
||||||
|
|
||||||
|
self.fetch_metadata_button = QPushButton(
|
||||||
|
_('&Fetch metadata from server'), self)
|
||||||
|
self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
|
||||||
|
font = self.fmb_font = QFont()
|
||||||
|
font.setBold(True)
|
||||||
|
self.fetch_metadata_button.setFont(font)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def do_layout(self): # {{{
|
||||||
self.central_widget.clear()
|
self.central_widget.clear()
|
||||||
self.tabs = []
|
self.tabs = []
|
||||||
self.labels = []
|
self.labels = []
|
||||||
@ -499,8 +164,58 @@ class MetadataSingleDialog(ResizableDialog):
|
|||||||
create_row(2, self.series, self.remove_unused_series_button,
|
create_row(2, self.series, self.remove_unused_series_button,
|
||||||
self.series_index, icon='trash.png')
|
self.series_index, icon='trash.png')
|
||||||
|
|
||||||
|
tl.addWidget(self.formats_manager, 0, 6, 3, 1)
|
||||||
|
|
||||||
|
self.splitter = QSplitter(Qt.Horizontal, self)
|
||||||
|
self.splitter.addWidget(self.cover)
|
||||||
|
l.addWidget(self.splitter)
|
||||||
|
self.tabs[0].gb = gb = QGroupBox(_('Change cover'), self)
|
||||||
|
gb.l = l = QGridLayout()
|
||||||
|
gb.setLayout(l)
|
||||||
|
for i, b in enumerate(self.cover.buttons[:3]):
|
||||||
|
l.addWidget(b, 0, i, 1, 1)
|
||||||
|
gb.hl = QHBoxLayout()
|
||||||
|
for b in self.cover.buttons[3:]:
|
||||||
|
gb.hl.addWidget(b)
|
||||||
|
l.addLayout(gb.hl, 1, 0, 1, 3)
|
||||||
|
self.tabs[0].middle = w = QWidget(self)
|
||||||
|
w.l = l = QGridLayout()
|
||||||
|
w.setLayout(w.l)
|
||||||
|
l.setMargin(0)
|
||||||
|
self.splitter.addWidget(w)
|
||||||
|
def create_row2(row, widget, button=None):
|
||||||
|
row += 1
|
||||||
|
ql = BuddyLabel(widget)
|
||||||
|
l.addWidget(ql, row, 0, 1, 1)
|
||||||
|
l.addWidget(widget, row, 1, 1, 2 if button is None else 1)
|
||||||
|
if button is not None:
|
||||||
|
l.addWidget(button, row, 2, 1, 1)
|
||||||
|
|
||||||
|
l.addWidget(gb, 0, 0, 1, 3)
|
||||||
|
self.tabs[0].spc_one = QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
|
QSizePolicy.Expanding)
|
||||||
|
l.addItem(self.tabs[0].spc_one, 1, 0, 1, 3)
|
||||||
|
create_row2(1, self.rating)
|
||||||
|
create_row2(2, self.tags, self.tags_editor_button)
|
||||||
|
create_row2(3, self.isbn)
|
||||||
|
create_row2(4, self.timestamp, self.timestamp.clear_button)
|
||||||
|
create_row2(5, self.pubdate, self.pubdate.clear_button)
|
||||||
|
create_row2(6, self.publisher)
|
||||||
|
self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
|
QSizePolicy.Expanding)
|
||||||
|
l.addItem(self.tabs[0].spc_two, 8, 0, 1, 3)
|
||||||
|
l.addWidget(self.fetch_metadata_button, 9, 0, 1, 3)
|
||||||
|
|
||||||
|
self.tabs[0].gb2 = gb = QGroupBox(_('&Comments'), self)
|
||||||
|
gb.l = l = QVBoxLayout()
|
||||||
|
gb.setLayout(l)
|
||||||
|
l.addWidget(self.comments)
|
||||||
|
self.splitter.addWidget(gb)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
def __call__(self, id_, has_next=False, has_previous=False):
|
def __call__(self, id_, has_next=False, has_previous=False):
|
||||||
|
# TODO: Next and previous buttons
|
||||||
self.book_id = id_
|
self.book_id = id_
|
||||||
for widget in self.basic_metadata_widgets:
|
for widget in self.basic_metadata_widgets:
|
||||||
widget.initialize(self.db, id_)
|
widget.initialize(self.db, id_)
|
||||||
@ -523,6 +238,12 @@ class MetadataSingleDialog(ResizableDialog):
|
|||||||
self.series.setCurrentIndex(i)
|
self.series.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def tags_editor(self, *args):
|
||||||
|
self.tags.edit(self.db, self.book_id)
|
||||||
|
|
||||||
|
def fetch_metadata(self, *args):
|
||||||
|
pass # TODO: fetch metadata
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from PyQt4.Qt import QApplication
|
from PyQt4.Qt import QApplication
|
||||||
|
@ -449,7 +449,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
|
|
||||||
def set_window_title(self):
|
def set_window_title(self):
|
||||||
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name())
|
self.setWindowTitle(__appname__ + u' - || %s ||'%self.iactions['Choose Library'].library_name())
|
||||||
|
|
||||||
def location_selected(self, location):
|
def location_selected(self, location):
|
||||||
'''
|
'''
|
||||||
|
@ -163,6 +163,7 @@ class FormatList(QListWidget):
|
|||||||
class ImageView(QWidget):
|
class ImageView(QWidget):
|
||||||
|
|
||||||
BORDER_WIDTH = 1
|
BORDER_WIDTH = 1
|
||||||
|
cover_changed = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
@ -202,8 +203,7 @@ class ImageView(QWidget):
|
|||||||
if not pmap.isNull():
|
if not pmap.isNull():
|
||||||
self.setPixmap(pmap)
|
self.setPixmap(pmap)
|
||||||
event.accept()
|
event.accept()
|
||||||
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), open(path,
|
self.cover_changed.emit(open(path, 'rb').read())
|
||||||
'rb').read())
|
|
||||||
break
|
break
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
@ -272,7 +272,7 @@ class ImageView(QWidget):
|
|||||||
pmap = cb.pixmap(cb.Selection)
|
pmap = cb.pixmap(cb.Selection)
|
||||||
if not pmap.isNull():
|
if not pmap.isNull():
|
||||||
self.setPixmap(pmap)
|
self.setPixmap(pmap)
|
||||||
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'),
|
self.cover_changed.emit(
|
||||||
pixmap_to_data(pmap))
|
pixmap_to_data(pmap))
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -580,7 +580,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
"pipeline to the specified "
|
"pipeline to the specified "
|
||||||
"directory. Useful if you are unsure at which stage "
|
"directory. Useful if you are unsure at which stage "
|
||||||
"of the conversion process a bug is occurring.\n"
|
"of the conversion process a bug is occurring.\n"
|
||||||
"Default: '%default'None\n"
|
"Default: '%default'\n"
|
||||||
"Applies to: ePub, MOBI output formats")),
|
"Applies to: ePub, MOBI output formats")),
|
||||||
Option('--exclude-book-marker',
|
Option('--exclude-book-marker',
|
||||||
default=':',
|
default=':',
|
||||||
@ -1370,7 +1370,9 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.generateHTMLByTags()
|
self.generateHTMLByTags()
|
||||||
# If this is the only Section, and there are no genres, bail
|
# If this is the only Section, and there are no genres, bail
|
||||||
if self.opts.section_list == ['Genres'] and not self.genres:
|
if self.opts.section_list == ['Genres'] and not self.genres:
|
||||||
error_msg = _("No Genres found to catalog.\nCheck 'Excluded genres'\nin E-book options.\n")
|
error_msg = _("No enabled genres found to catalog.\n")
|
||||||
|
if not self.opts.cli_environment:
|
||||||
|
error_msg += "Check 'Excluded genres'\nin E-book options.\n"
|
||||||
self.opts.log.error(error_msg)
|
self.opts.log.error(error_msg)
|
||||||
self.error.append(_('No books available to catalog'))
|
self.error.append(_('No books available to catalog'))
|
||||||
self.error.append(error_msg)
|
self.error.append(error_msg)
|
||||||
@ -2792,14 +2794,16 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
genre_list.append(tag_list)
|
genre_list.append(tag_list)
|
||||||
|
|
||||||
if self.opts.verbose:
|
if self.opts.verbose:
|
||||||
self.opts.log.info(" Genre summary: %d active genre tags used in generating catalog with %d titles" %
|
if len(genre_list):
|
||||||
|
self.opts.log.info(" Genre summary: %d active genre tags used in generating catalog with %d titles" %
|
||||||
(len(genre_list), len(self.booksByTitle)))
|
(len(genre_list), len(self.booksByTitle)))
|
||||||
|
|
||||||
for genre in genre_list:
|
for genre in genre_list:
|
||||||
for key in genre:
|
for key in genre:
|
||||||
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
|
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
|
||||||
len(genre[key]),
|
len(genre[key]),
|
||||||
'titles' if len(genre[key]) > 1 else 'title'))
|
'titles' if len(genre[key]) > 1 else 'title'))
|
||||||
|
|
||||||
|
|
||||||
# Write the results
|
# Write the results
|
||||||
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
|
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
|
||||||
@ -3105,13 +3109,15 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
navPointTag.insert(1, contentTag)
|
navPointTag.insert(1, contentTag)
|
||||||
elif self.opts.generate_genres:
|
elif self.opts.generate_genres:
|
||||||
contentTag = Tag(soup, 'content')
|
contentTag = Tag(soup, 'content')
|
||||||
contentTag['src'] = "content/ByGenres.html"
|
#contentTag['src'] = "content/ByGenres.html"
|
||||||
|
contentTag['src'] = "%s" % self.genres[0]['file']
|
||||||
navPointTag.insert(1, contentTag)
|
navPointTag.insert(1, contentTag)
|
||||||
elif self.opts.generate_recently_added:
|
elif self.opts.generate_recently_added:
|
||||||
contentTag = Tag(soup, 'content')
|
contentTag = Tag(soup, 'content')
|
||||||
contentTag['src'] = "content/ByDateAdded.html"
|
contentTag['src'] = "content/ByDateAdded.html"
|
||||||
navPointTag.insert(1, contentTag)
|
navPointTag.insert(1, contentTag)
|
||||||
else:
|
else:
|
||||||
|
# Descriptions only
|
||||||
sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \
|
sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \
|
||||||
else self.booksByTitle
|
else self.booksByTitle
|
||||||
contentTag = Tag(soup, 'content')
|
contentTag = Tag(soup, 'content')
|
||||||
@ -3125,7 +3131,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
navMapTag.insert(0,navPointTag)
|
navMapTag.insert(0,navPointTag)
|
||||||
|
|
||||||
ncx.insert(0,navMapTag)
|
ncx.insert(0,navMapTag)
|
||||||
|
|
||||||
self.ncxSoup = soup
|
self.ncxSoup = soup
|
||||||
|
|
||||||
def generateNCXDescriptions(self, tocTitle):
|
def generateNCXDescriptions(self, tocTitle):
|
||||||
@ -3911,7 +3916,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Add this section to the body
|
# Add this section to the body
|
||||||
body.insert(btc, navPointTag)
|
body.insert(btc, navPointTag)
|
||||||
btc += 1
|
btc += 1
|
||||||
|
|
||||||
self.ncxSoup = ncx_soup
|
self.ncxSoup = ncx_soup
|
||||||
|
|
||||||
def writeNCX(self):
|
def writeNCX(self):
|
||||||
@ -4055,12 +4059,34 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
# Remove the special marker tags from the database's tag list,
|
# Remove the special marker tags from the database's tag list,
|
||||||
# return sorted list of normalized genre tags
|
# return sorted list of normalized genre tags
|
||||||
|
|
||||||
|
def format_tag_list(tags, indent=5, line_break=70, header='Tag list'):
|
||||||
|
def next_tag(sorted_tags):
|
||||||
|
for (i, tag) in enumerate(sorted_tags):
|
||||||
|
if i < len(tags) - 1:
|
||||||
|
yield tag + ", "
|
||||||
|
else:
|
||||||
|
yield tag
|
||||||
|
|
||||||
|
ans = '%s%d %s:\n' % (' ' * indent, len(tags), header)
|
||||||
|
ans += ' ' * (indent + 1)
|
||||||
|
out_str = ''
|
||||||
|
sorted_tags = sorted(tags)
|
||||||
|
for tag in next_tag(sorted_tags):
|
||||||
|
out_str += tag
|
||||||
|
if len(out_str) >= line_break:
|
||||||
|
ans += out_str + '\n'
|
||||||
|
out_str = ' ' * (indent + 1)
|
||||||
|
return ans + out_str
|
||||||
|
|
||||||
normalized_tags = []
|
normalized_tags = []
|
||||||
friendly_tags = []
|
friendly_tags = []
|
||||||
|
excluded_tags = []
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
if tag[0] in self.markerTags:
|
if tag in self.markerTags:
|
||||||
|
excluded_tags.append(tag)
|
||||||
continue
|
continue
|
||||||
if re.search(self.opts.exclude_genre, tag):
|
if re.search(self.opts.exclude_genre, tag):
|
||||||
|
excluded_tags.append(tag)
|
||||||
continue
|
continue
|
||||||
if tag == ' ':
|
if tag == ' ':
|
||||||
continue
|
continue
|
||||||
@ -4079,32 +4105,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
if genre_tags_dict[key] == normalized:
|
if genre_tags_dict[key] == normalized:
|
||||||
self.opts.log.warn(" %s" % key)
|
self.opts.log.warn(" %s" % key)
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
def next_tag(tags):
|
self.opts.log.info('%s' % format_tag_list(genre_tags_dict, header="enabled genre tags in database"))
|
||||||
for (i, tag) in enumerate(tags):
|
self.opts.log.info('%s' % format_tag_list(excluded_tags, header="excluded genre tags"))
|
||||||
if i < len(tags) - 1:
|
|
||||||
yield tag + ", "
|
|
||||||
else:
|
|
||||||
yield tag
|
|
||||||
|
|
||||||
self.opts.log.info(u' %d genre tags in database (excluding genres matching %s):' % \
|
|
||||||
(len(genre_tags_dict), self.opts.exclude_genre))
|
|
||||||
|
|
||||||
# Display friendly/normalized genres
|
|
||||||
# friendly => normalized
|
|
||||||
if False:
|
|
||||||
sorted_tags = ['%s => %s' % (key, genre_tags_dict[key]) for key in sorted(genre_tags_dict.keys())]
|
|
||||||
for tag in next_tag(sorted_tags):
|
|
||||||
self.opts.log(u' %s' % tag)
|
|
||||||
else:
|
|
||||||
sorted_tags = ['%s' % (key) for key in sorted(genre_tags_dict.keys())]
|
|
||||||
out_str = ''
|
|
||||||
line_break = 70
|
|
||||||
for tag in next_tag(sorted_tags):
|
|
||||||
out_str += tag
|
|
||||||
if len(out_str) >= line_break:
|
|
||||||
self.opts.log.info(' %s' % out_str)
|
|
||||||
out_str = ''
|
|
||||||
self.opts.log.info(' %s' % out_str)
|
|
||||||
|
|
||||||
return genre_tags_dict
|
return genre_tags_dict
|
||||||
|
|
||||||
@ -4969,7 +4971,13 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
else:
|
else:
|
||||||
opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***')
|
opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***')
|
||||||
return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"]
|
return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"]
|
||||||
build_log.append(u" Sections: %s" % ', '.join(sections_list))
|
if opts.fmt == 'mobi' and sections_list == ['Descriptions']:
|
||||||
|
warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***")
|
||||||
|
opts.log.warn(warning)
|
||||||
|
sections_list.insert(0,'Authors')
|
||||||
|
opts.generate_authors = True
|
||||||
|
|
||||||
|
opts.log(u" Sections: %s" % ', '.join(sections_list))
|
||||||
opts.section_list = sections_list
|
opts.section_list = sections_list
|
||||||
|
|
||||||
# Limit thumb_width to 1.0" - 2.0"
|
# Limit thumb_width to 1.0" - 2.0"
|
||||||
@ -5017,7 +5025,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
log.info(" Completed catalog source generation\n")
|
log.info(" Completed catalog source generation\n")
|
||||||
else:
|
else:
|
||||||
log.warn(" *** Errors during catalog generation, check log for details ***")
|
log.error(" *** Terminated catalog generation, check log for details ***")
|
||||||
|
|
||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
recommendations = []
|
recommendations = []
|
||||||
|
@ -441,7 +441,7 @@ menu, choose "Validate fonts".
|
|||||||
I downloaded the installer, but it is not working?
|
I downloaded the installer, but it is not working?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location <http://sourceforge.net/projects/calibre/files/>`_. If the installer still doesn't work, then something on your computer is preventing it from running. Best place to ask for more help is in the `forums <http://www.mobileread.com/forums/usercp.php>`_.
|
Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location <http://sourceforge.net/projects/calibre/files/>`_. If the installer still doesn't work, then something on your computer is preventing it from running. Try rebooting your computer and running a registry cleaner like `Wise registry cleaner <http://www.wisecleaner.com>`_. Best place to ask for more help is in the `forums <http://www.mobileread.com/forums/usercp.php>`_.
|
||||||
|
|
||||||
My antivirus program claims |app| is a virus/trojan?
|
My antivirus program claims |app| is a virus/trojan?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Loading…
x
Reference in New Issue
Block a user