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'
|
||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
blic.rs
|
||||
'''
|
||||
@ -21,21 +21,53 @@ class Blic(BasicNewsRecipe):
|
||||
masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png'
|
||||
language = 'sr'
|
||||
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 = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
remove_tags_before = dict(name='div', attrs={'id':'article_info'})
|
||||
remove_tags = [dict(name=['object','link'])]
|
||||
remove_attributes = ['width','height']
|
||||
remove_tags = [dict(name=['object','link','meta','base','object','embed'])]
|
||||
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):
|
||||
@ -44,4 +76,4 @@ class Blic(BasicNewsRecipe):
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
||||
return soup
|
||||
|
@ -241,7 +241,7 @@ def get_parsed_proxy(typ='http', debug=True):
|
||||
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,
|
||||
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.set_handle_refresh(True, max_time=max_time, honor_time=honor_time)
|
||||
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 \
|
||||
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13')]
|
||||
if user_agent is None:
|
||||
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)
|
||||
if http_proxy:
|
||||
opener.set_proxies({'http':http_proxy})
|
||||
|
@ -21,7 +21,7 @@ class ANDROID(USBMS):
|
||||
# HTC
|
||||
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
||||
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
||||
0xc92 : [0x100], 0xc97: [0x226]},
|
||||
0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]},
|
||||
|
||||
# Eken
|
||||
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.
|
||||
'''
|
||||
|
||||
import sys, socket, os, re
|
||||
import sys, socket, os, re, random
|
||||
|
||||
from lxml import html
|
||||
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'
|
||||
|
||||
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):
|
||||
|
||||
def get_method(self):
|
||||
return 'HEAD'
|
||||
|
||||
def check_for_cover(isbn, timeout=5.):
|
||||
br = browser()
|
||||
br = browser(user_agent=get_ua())
|
||||
br.set_handle_redirect(False)
|
||||
try:
|
||||
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):
|
||||
src = None
|
||||
br = browser()
|
||||
br = browser(user_agent=get_ua())
|
||||
try:
|
||||
return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg'
|
||||
except:
|
||||
@ -100,7 +113,7 @@ def get_social_metadata(title, authors, publisher, isbn, username=None,
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
mi = MetaInformation(title, authors)
|
||||
if isbn:
|
||||
br = browser()
|
||||
br = browser(user_agent=get_ua())
|
||||
if username and password:
|
||||
try:
|
||||
login(br, username, password, force=False)
|
||||
|
@ -775,7 +775,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
self.original_tags = unicode(self.tags.text())
|
||||
else:
|
||||
self.tags.setText(self.original_tags)
|
||||
d = TagEditor(self, self.db, self.row)
|
||||
d = TagEditor(self, self.db, self.id)
|
||||
d.exec_()
|
||||
if d.result() == QDialog.Accepted:
|
||||
tag_string = ', '.join(d.tags)
|
||||
|
@ -10,13 +10,13 @@ from calibre.utils.icu import sort_key
|
||||
|
||||
class TagEditor(QDialog, Ui_TagEditor):
|
||||
|
||||
def __init__(self, window, db, index=None):
|
||||
def __init__(self, window, db, id_=None):
|
||||
QDialog.__init__(self, window)
|
||||
Ui_TagEditor.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
self.db = db
|
||||
self.index = index
|
||||
self.index = db.row(id_)
|
||||
if self.index is not None:
|
||||
tags = self.db.tags(self.index)
|
||||
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>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap, re
|
||||
|
||||
from PyQt4.Qt import QDialogButtonBox, Qt, QTabWidget, QScrollArea, \
|
||||
QVBoxLayout, QIcon, QToolButton, QWidget, QLabel, QGridLayout, \
|
||||
QDoubleSpinBox
|
||||
from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \
|
||||
QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \
|
||||
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.utils.icu import sort_key
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
|
||||
EnComboBox
|
||||
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)
|
||||
from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \
|
||||
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \
|
||||
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \
|
||||
BuddyLabel, DateEdit, PubdateEdit
|
||||
|
||||
class MetadataSingleDialog(ResizableDialog):
|
||||
|
||||
view_format = pyqtSignal(object)
|
||||
|
||||
def __init__(self, db, parent=None):
|
||||
self.db = db
|
||||
ResizableDialog.__init__(self, parent)
|
||||
@ -427,9 +55,9 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
self.do_layout()
|
||||
# }}}
|
||||
|
||||
def create_basic_metadata_widgets(self):
|
||||
def create_basic_metadata_widgets(self): # {{{
|
||||
self.basic_metadata_widgets = []
|
||||
# Title
|
||||
|
||||
self.title = TitleEdit(self)
|
||||
self.deduce_title_sort_button = QToolButton(self)
|
||||
self.deduce_title_sort_button.setToolTip(
|
||||
@ -442,7 +70,6 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
self.deduce_title_sort_button)
|
||||
self.basic_metadata_widgets.extend([self.title, self.title_sort])
|
||||
|
||||
# Authors
|
||||
self.authors = AuthorsEdit(self)
|
||||
self.deduce_author_sort_button = QToolButton(self)
|
||||
self.deduce_author_sort_button.setToolTip(_(
|
||||
@ -468,7 +95,45 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
self.series_index = SeriesIndexEdit(self, self.series)
|
||||
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.tabs = []
|
||||
self.labels = []
|
||||
@ -499,8 +164,58 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
create_row(2, self.series, self.remove_unused_series_button,
|
||||
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):
|
||||
# TODO: Next and previous buttons
|
||||
self.book_id = id_
|
||||
for widget in self.basic_metadata_widgets:
|
||||
widget.initialize(self.db, id_)
|
||||
@ -523,6 +238,12 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
self.series.setCurrentIndex(i)
|
||||
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__':
|
||||
from PyQt4.Qt import QApplication
|
||||
|
@ -449,7 +449,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
|
||||
|
||||
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):
|
||||
'''
|
||||
|
@ -163,6 +163,7 @@ class FormatList(QListWidget):
|
||||
class ImageView(QWidget):
|
||||
|
||||
BORDER_WIDTH = 1
|
||||
cover_changed = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
@ -202,8 +203,7 @@ class ImageView(QWidget):
|
||||
if not pmap.isNull():
|
||||
self.setPixmap(pmap)
|
||||
event.accept()
|
||||
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), open(path,
|
||||
'rb').read())
|
||||
self.cover_changed.emit(open(path, 'rb').read())
|
||||
break
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
@ -272,7 +272,7 @@ class ImageView(QWidget):
|
||||
pmap = cb.pixmap(cb.Selection)
|
||||
if not pmap.isNull():
|
||||
self.setPixmap(pmap)
|
||||
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'),
|
||||
self.cover_changed.emit(
|
||||
pixmap_to_data(pmap))
|
||||
# }}}
|
||||
|
||||
|
@ -580,7 +580,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
"pipeline to the specified "
|
||||
"directory. Useful if you are unsure at which stage "
|
||||
"of the conversion process a bug is occurring.\n"
|
||||
"Default: '%default'None\n"
|
||||
"Default: '%default'\n"
|
||||
"Applies to: ePub, MOBI output formats")),
|
||||
Option('--exclude-book-marker',
|
||||
default=':',
|
||||
@ -1370,7 +1370,9 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.generateHTMLByTags()
|
||||
# If this is the only Section, and there are no genres, bail
|
||||
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.error.append(_('No books available to catalog'))
|
||||
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)
|
||||
|
||||
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)))
|
||||
|
||||
for genre in genre_list:
|
||||
for key in genre:
|
||||
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
|
||||
len(genre[key]),
|
||||
'titles' if len(genre[key]) > 1 else 'title'))
|
||||
for genre in genre_list:
|
||||
for key in genre:
|
||||
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
|
||||
len(genre[key]),
|
||||
'titles' if len(genre[key]) > 1 else 'title'))
|
||||
|
||||
|
||||
# Write the results
|
||||
# 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)
|
||||
elif self.opts.generate_genres:
|
||||
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)
|
||||
elif self.opts.generate_recently_added:
|
||||
contentTag = Tag(soup, 'content')
|
||||
contentTag['src'] = "content/ByDateAdded.html"
|
||||
navPointTag.insert(1, contentTag)
|
||||
else:
|
||||
# Descriptions only
|
||||
sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \
|
||||
else self.booksByTitle
|
||||
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)
|
||||
|
||||
ncx.insert(0,navMapTag)
|
||||
|
||||
self.ncxSoup = soup
|
||||
|
||||
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
|
||||
body.insert(btc, navPointTag)
|
||||
btc += 1
|
||||
|
||||
self.ncxSoup = ncx_soup
|
||||
|
||||
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,
|
||||
# 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 = []
|
||||
friendly_tags = []
|
||||
excluded_tags = []
|
||||
for tag in tags:
|
||||
if tag[0] in self.markerTags:
|
||||
if tag in self.markerTags:
|
||||
excluded_tags.append(tag)
|
||||
continue
|
||||
if re.search(self.opts.exclude_genre, tag):
|
||||
excluded_tags.append(tag)
|
||||
continue
|
||||
if tag == ' ':
|
||||
continue
|
||||
@ -4079,32 +4105,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
||||
if genre_tags_dict[key] == normalized:
|
||||
self.opts.log.warn(" %s" % key)
|
||||
if self.verbose:
|
||||
def next_tag(tags):
|
||||
for (i, tag) in enumerate(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)
|
||||
self.opts.log.info('%s' % format_tag_list(genre_tags_dict, header="enabled genre tags in database"))
|
||||
self.opts.log.info('%s' % format_tag_list(excluded_tags, header="excluded genre tags"))
|
||||
|
||||
return genre_tags_dict
|
||||
|
||||
@ -4969,7 +4971,13 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
||||
else:
|
||||
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"]
|
||||
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
|
||||
|
||||
# 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:
|
||||
log.info(" Completed catalog source generation\n")
|
||||
else:
|
||||
log.warn(" *** Errors during catalog generation, check log for details ***")
|
||||
log.error(" *** Terminated catalog generation, check log for details ***")
|
||||
|
||||
if catalog_source_built:
|
||||
recommendations = []
|
||||
|
@ -441,7 +441,7 @@ menu, choose "Validate fonts".
|
||||
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?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
Loading…
x
Reference in New Issue
Block a user