merge from trunk

This commit is contained in:
ldolse 2011-01-21 08:33:52 +08:00
commit d5f485d71b
12 changed files with 1209 additions and 449 deletions

View File

@ -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

View File

@ -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})

View File

@ -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] },

View File

@ -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)

View File

@ -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)

View File

@ -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:

View 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'
# }}}

View File

@ -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

View File

@ -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):
'''

View File

@ -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))
# }}}

View File

@ -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 = []

View File

@ -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?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~