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
ac159887ed
38
resources/recipes/alternet.recipe
Normal file
38
resources/recipes/alternet.recipe
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class Alternet(BasicNewsRecipe):
|
||||||
|
title = u'Alternet'
|
||||||
|
__author__= 'rty'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
publisher = 'alternet.org'
|
||||||
|
category = 'News, Magazine'
|
||||||
|
description = 'News magazine and online community'
|
||||||
|
feeds = [
|
||||||
|
(u'Front Page', u'http://feeds.feedblitz.com/alternet'),
|
||||||
|
(u'Breaking News', u'http://feeds.feedblitz.com/alternet_breaking_news'),
|
||||||
|
(u'Top Ten Campaigns', u'http://feeds.feedblitz.com/alternet_top_10_campaigns'),
|
||||||
|
(u'Special Coverage Areas', u'http://feeds.feedblitz.com/alternet_coverage')
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_javascript = True
|
||||||
|
use_embedded_content = False
|
||||||
|
no_stylesheets = True
|
||||||
|
language = 'en'
|
||||||
|
encoding = 'UTF-8'
|
||||||
|
temp_files = []
|
||||||
|
articles_are_obfuscated = True
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('link', None)
|
||||||
|
|
||||||
|
def get_obfuscated_article(self, url):
|
||||||
|
br = self.get_browser()
|
||||||
|
br.open(url)
|
||||||
|
response = br.follow_link(url_regex = r'/printversion/[0-9]+', nr = 0)
|
||||||
|
html = response.read()
|
||||||
|
self.temp_files.append(PersistentTemporaryFile('_fa.html'))
|
||||||
|
self.temp_files[-1].write(html)
|
||||||
|
self.temp_files[-1].close()
|
||||||
|
return self.temp_files[-1].name
|
@ -265,9 +265,20 @@
|
|||||||
<xsl:value-of select="@line-height"/>
|
<xsl:value-of select="@line-height"/>
|
||||||
<xsl:text>pt;</xsl:text>
|
<xsl:text>pt;</xsl:text>
|
||||||
</xsl:if>
|
</xsl:if>
|
||||||
|
<xsl:if test="(@align = 'just')">
|
||||||
|
<xsl:text>text-align: justify;</xsl:text>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:if test="(@align = 'cent')">
|
||||||
|
<xsl:text>text-align: center;</xsl:text>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:if test="(@align = 'left')">
|
||||||
|
<xsl:text>text-align: left;</xsl:text>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:if test="(@align = 'right')">
|
||||||
|
<xsl:text>text-align: right;</xsl:text>
|
||||||
|
</xsl:if>
|
||||||
</xsl:template>
|
</xsl:template>
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="rtf:inline">
|
<xsl:template match="rtf:inline">
|
||||||
<xsl:variable name="num-attrs" select="count(@*)"/>
|
<xsl:variable name="num-attrs" select="count(@*)"/>
|
||||||
<xsl:choose>
|
<xsl:choose>
|
||||||
|
@ -361,6 +361,8 @@ def strftime(fmt, t=None):
|
|||||||
before 1900 '''
|
before 1900 '''
|
||||||
if t is None:
|
if t is None:
|
||||||
t = time.localtime()
|
t = time.localtime()
|
||||||
|
if hasattr(t, 'timetuple'):
|
||||||
|
t = t.timetuple()
|
||||||
early_year = t[0] < 1900
|
early_year = t[0] < 1900
|
||||||
if early_year:
|
if early_year:
|
||||||
replacement = 1900 if t[0]%4 == 0 else 1901
|
replacement = 1900 if t[0]%4 == 0 else 1901
|
||||||
|
@ -446,7 +446,7 @@ from calibre.devices.eb600.driver import EB600, COOL_ER, SHINEBOOK, \
|
|||||||
BOOQ, ELONEX, POCKETBOOK301, MENTOR
|
BOOQ, ELONEX, POCKETBOOK301, MENTOR
|
||||||
from calibre.devices.iliad.driver import ILIAD
|
from calibre.devices.iliad.driver import ILIAD
|
||||||
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||||
from calibre.devices.jetbook.driver import JETBOOK
|
from calibre.devices.jetbook.driver import JETBOOK, MIBUK
|
||||||
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
||||||
from calibre.devices.nook.driver import NOOK
|
from calibre.devices.nook.driver import NOOK
|
||||||
from calibre.devices.prs505.driver import PRS505
|
from calibre.devices.prs505.driver import PRS505
|
||||||
@ -467,12 +467,12 @@ from calibre.devices.kobo.driver import KOBO
|
|||||||
from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \
|
from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \
|
||||||
LibraryThing
|
LibraryThing
|
||||||
from calibre.ebooks.metadata.douban import DoubanBooks
|
from calibre.ebooks.metadata.douban import DoubanBooks
|
||||||
from calibre.library.catalog import CSV_XML, EPUB_MOBI
|
from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX
|
||||||
from calibre.ebooks.epub.fix.unmanifested import Unmanifested
|
from calibre.ebooks.epub.fix.unmanifested import Unmanifested
|
||||||
from calibre.ebooks.epub.fix.epubcheck import Epubcheck
|
from calibre.ebooks.epub.fix.epubcheck import Epubcheck
|
||||||
|
|
||||||
plugins = [HTML2ZIP, PML2PMLZ, ArchiveExtract, GoogleBooks, ISBNDB, Amazon,
|
plugins = [HTML2ZIP, PML2PMLZ, ArchiveExtract, GoogleBooks, ISBNDB, Amazon,
|
||||||
LibraryThing, DoubanBooks, CSV_XML, EPUB_MOBI, Unmanifested, Epubcheck]
|
LibraryThing, DoubanBooks, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, Epubcheck]
|
||||||
plugins += [
|
plugins += [
|
||||||
ComicInput,
|
ComicInput,
|
||||||
EPUBInput,
|
EPUBInput,
|
||||||
@ -517,6 +517,7 @@ plugins += [
|
|||||||
IREXDR1000,
|
IREXDR1000,
|
||||||
IREXDR800,
|
IREXDR800,
|
||||||
JETBOOK,
|
JETBOOK,
|
||||||
|
MIBUK,
|
||||||
SHINEBOOK,
|
SHINEBOOK,
|
||||||
POCKETBOOK360,
|
POCKETBOOK360,
|
||||||
POCKETBOOK301,
|
POCKETBOOK301,
|
||||||
|
@ -80,3 +80,21 @@ class JETBOOK(USBMS):
|
|||||||
|
|
||||||
return mi
|
return mi
|
||||||
|
|
||||||
|
class MIBUK(USBMS):
|
||||||
|
|
||||||
|
name = 'MiBuk Wolder Device Interface'
|
||||||
|
description = _('Communicate with the MiBuk Wolder reader.')
|
||||||
|
author = 'Kovid Goyal'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
|
||||||
|
FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'txt', 'rtf', 'pdf']
|
||||||
|
|
||||||
|
VENDOR_ID = [0x0525]
|
||||||
|
PRODUCT_ID = [0xa4a5]
|
||||||
|
BCD = [0x314]
|
||||||
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
|
VENDOR_NAME = 'LINUX'
|
||||||
|
WINDOWS_MAIN_MEM = 'WOLDERMIBUK'
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,12 +192,18 @@ class RTFInput(InputFormatPlugin):
|
|||||||
from calibre.ebooks.rtf2xml.ParseRtf import RtfInvalidCodeException
|
from calibre.ebooks.rtf2xml.ParseRtf import RtfInvalidCodeException
|
||||||
self.log = log
|
self.log = log
|
||||||
self.log('Converting RTF to XML...')
|
self.log('Converting RTF to XML...')
|
||||||
|
#Name of the preprocesssed RTF file
|
||||||
fname = self.preprocess(stream.name)
|
fname = self.preprocess(stream.name)
|
||||||
try:
|
try:
|
||||||
xml = self.generate_xml(fname)
|
xml = self.generate_xml(fname)
|
||||||
except RtfInvalidCodeException, e:
|
except RtfInvalidCodeException, e:
|
||||||
raise ValueError(_('This RTF file has a feature calibre does not '
|
raise ValueError(_('This RTF file has a feature calibre does not '
|
||||||
'support. Convert it to HTML first and then try it.\n%s')%e)
|
'support. Convert it to HTML first and then try it.\n%s')%e)
|
||||||
|
|
||||||
|
'''dataxml = open('dataxml.xml', 'w')
|
||||||
|
dataxml.write(xml)
|
||||||
|
dataxml.close'''
|
||||||
|
|
||||||
d = glob.glob(os.path.join('*_rtf_pict_dir', 'picts.rtf'))
|
d = glob.glob(os.path.join('*_rtf_pict_dir', 'picts.rtf'))
|
||||||
if d:
|
if d:
|
||||||
imap = {}
|
imap = {}
|
||||||
@ -205,6 +211,7 @@ class RTFInput(InputFormatPlugin):
|
|||||||
imap = self.extract_images(d[0])
|
imap = self.extract_images(d[0])
|
||||||
except:
|
except:
|
||||||
self.log.exception('Failed to extract images...')
|
self.log.exception('Failed to extract images...')
|
||||||
|
|
||||||
self.log('Parsing XML...')
|
self.log('Parsing XML...')
|
||||||
parser = etree.XMLParser(recover=True, no_network=True)
|
parser = etree.XMLParser(recover=True, no_network=True)
|
||||||
doc = etree.fromstring(xml, parser=parser)
|
doc = etree.fromstring(xml, parser=parser)
|
||||||
@ -214,10 +221,10 @@ class RTFInput(InputFormatPlugin):
|
|||||||
name = imap.get(num, None)
|
name = imap.get(num, None)
|
||||||
if name is not None:
|
if name is not None:
|
||||||
pict.set('num', name)
|
pict.set('num', name)
|
||||||
|
|
||||||
self.log('Converting XML to HTML...')
|
self.log('Converting XML to HTML...')
|
||||||
inline_class = InlineClass(self.log)
|
inline_class = InlineClass(self.log)
|
||||||
styledoc = etree.fromstring(P('templates/rtf.xsl', data=True))
|
styledoc = etree.fromstring(P('templates/rtf.xsl', data=True))
|
||||||
|
|
||||||
extensions = { ('calibre', 'inline-class') : inline_class }
|
extensions = { ('calibre', 'inline-class') : inline_class }
|
||||||
transform = etree.XSLT(styledoc, extensions=extensions)
|
transform = etree.XSLT(styledoc, extensions=extensions)
|
||||||
result = transform(doc)
|
result = transform(doc)
|
||||||
|
@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import os, sys
|
import os, sys
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
||||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
QByteArray, QTranslator, QCoreApplication, QThread, \
|
||||||
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
||||||
QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||||
@ -33,10 +33,6 @@ def _config():
|
|||||||
help=_('Send file to storage card instead of main memory by default'))
|
help=_('Send file to storage card instead of main memory by default'))
|
||||||
c.add_opt('confirm_delete', default=False,
|
c.add_opt('confirm_delete', default=False,
|
||||||
help=_('Confirm before deleting'))
|
help=_('Confirm before deleting'))
|
||||||
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
|
||||||
help=_('Toolbar icon size')) # value QVariant.toSize
|
|
||||||
c.add_opt('show_text_in_toolbar', default=True,
|
|
||||||
help=_('Show button labels in the toolbar'))
|
|
||||||
c.add_opt('main_window_geometry', default=None,
|
c.add_opt('main_window_geometry', default=None,
|
||||||
help=_('Main window geometry')) # value QVariant.toByteArray
|
help=_('Main window geometry')) # value QVariant.toByteArray
|
||||||
c.add_opt('new_version_notification', default=True,
|
c.add_opt('new_version_notification', default=True,
|
||||||
|
84
src/calibre/gui2/catalog/catalog_bibtex.py
Normal file
84
src/calibre/gui2/catalog/catalog_bibtex.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
from calibre.gui2 import gprefs
|
||||||
|
from calibre.gui2.catalog.catalog_bibtex_ui import Ui_Form
|
||||||
|
from PyQt4.Qt import QWidget, QListWidgetItem
|
||||||
|
|
||||||
|
class PluginWidget(QWidget, Ui_Form):
|
||||||
|
|
||||||
|
TITLE = _('BibTeX Options')
|
||||||
|
HELP = _('Options specific to')+' BibTeX '+_('output')
|
||||||
|
OPTION_FIELDS = [('bib_cit','{authors}{id}'),
|
||||||
|
('bib_entry', 0), #mixed
|
||||||
|
('bibfile_enc', 0), #utf-8
|
||||||
|
('bibfile_enctag', 0), #strict
|
||||||
|
('impcit', True) ]
|
||||||
|
|
||||||
|
sync_enabled = False
|
||||||
|
formats = set(['bib'])
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
from calibre.library.catalog import FIELDS
|
||||||
|
self.all_fields = []
|
||||||
|
for x in FIELDS :
|
||||||
|
if x != 'all':
|
||||||
|
self.all_fields.append(x)
|
||||||
|
QListWidgetItem(x, self.db_fields)
|
||||||
|
|
||||||
|
def initialize(self, name): #not working properly to update
|
||||||
|
self.name = name
|
||||||
|
fields = gprefs.get(name+'_db_fields', self.all_fields)
|
||||||
|
# Restore the activated db_fields from last use
|
||||||
|
for x in xrange(self.db_fields.count()):
|
||||||
|
item = self.db_fields.item(x)
|
||||||
|
item.setSelected(unicode(item.text()) in fields)
|
||||||
|
# Update dialog fields from stored options
|
||||||
|
for opt in self.OPTION_FIELDS:
|
||||||
|
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
|
||||||
|
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||||
|
getattr(self, opt[0]).setCurrentIndex(opt_value)
|
||||||
|
elif opt[0] == 'impcit' :
|
||||||
|
getattr(self, opt[0]).setChecked(opt_value)
|
||||||
|
else:
|
||||||
|
getattr(self, opt[0]).setText(opt_value)
|
||||||
|
|
||||||
|
def options(self):
|
||||||
|
|
||||||
|
# Save the currently activated fields
|
||||||
|
fields = []
|
||||||
|
for x in xrange(self.db_fields.count()):
|
||||||
|
item = self.db_fields.item(x)
|
||||||
|
if item.isSelected():
|
||||||
|
fields.append(unicode(item.text()))
|
||||||
|
gprefs.set(self.name+'_db_fields', fields)
|
||||||
|
|
||||||
|
# Dictionary currently activated fields
|
||||||
|
if len(self.db_fields.selectedItems()):
|
||||||
|
opts_dict = {'fields':[unicode(item.text()) for item in self.db_fields.selectedItems()]}
|
||||||
|
else:
|
||||||
|
opts_dict = {'fields':['all']}
|
||||||
|
|
||||||
|
# Save/return the current options
|
||||||
|
# bib_cit stores as text
|
||||||
|
# 'bibfile_enc','bibfile_enctag' stores as int (Indexes)
|
||||||
|
for opt in self.OPTION_FIELDS:
|
||||||
|
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||||
|
opt_value = getattr(self,opt[0]).currentIndex()
|
||||||
|
elif opt[0] == 'impcit' :
|
||||||
|
opt_value = getattr(self, opt[0]).isChecked()
|
||||||
|
else :
|
||||||
|
opt_value = unicode(getattr(self, opt[0]).text())
|
||||||
|
gprefs.set(self.name + '_' + opt[0], opt_value)
|
||||||
|
|
||||||
|
opts_dict[opt[0]] = opt_value
|
||||||
|
|
||||||
|
return opts_dict
|
173
src/calibre/gui2/catalog/catalog_bibtex.ui
Normal file
173
src/calibre/gui2/catalog/catalog_bibtex.ui
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>579</width>
|
||||||
|
<height>411</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Bib file encoding:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fields to include in output:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QComboBox" name="bibfile_enc">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">utf-8</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">cp1252</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ascii/LaTeX</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" rowspan="12">
|
||||||
|
<widget class="QListWidget" name="db_fields">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string extracomment="Select all fields to be exported"/>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::MultiSelection</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Encoding configuration (change if you have errors) :</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QComboBox" name="bibfile_enctag">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>strict</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>replace</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ignore</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>backslashreplace</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>60</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>BibTeX entry type:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QComboBox" name="bib_entry">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>mixed</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>misc</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>book</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QCheckBox" name="impcit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Create a citation tag?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Expression to form the BibTeX citation tag:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0">
|
||||||
|
<widget class="QLineEdit" name="bib_cit"/>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Some explanation about this template:
|
||||||
|
-The fields availables are 'author_sort', 'authors', 'id',
|
||||||
|
'isbn', 'pubdate', 'publisher', 'series_index', 'series',
|
||||||
|
'tags', 'timestamp', 'title', 'uuid'
|
||||||
|
-For list types ie authors and tags, only the first element
|
||||||
|
wil be selected.
|
||||||
|
-For time field, only the date will be used. </string>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -20,6 +20,30 @@
|
|||||||
<string>Book Cover</string>
|
<string>Book Cover</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="_2">
|
<layout class="QGridLayout" name="_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QHBoxLayout" name="_3">
|
||||||
|
<item>
|
||||||
|
<widget class="ImageView" name="cover" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="opt_prefer_metadata_cover">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use cover from &source file</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<layout class="QVBoxLayout" name="_4">
|
<layout class="QVBoxLayout" name="_4">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
@ -71,30 +95,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QCheckBox" name="opt_prefer_metadata_cover">
|
|
||||||
<property name="text">
|
|
||||||
<string>Use cover from &source file</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<layout class="QHBoxLayout" name="_3">
|
|
||||||
<item>
|
|
||||||
<widget class="ImageView" name="cover" native="true">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
<zorder>opt_prefer_metadata_cover</zorder>
|
<zorder>opt_prefer_metadata_cover</zorder>
|
||||||
<zorder></zorder>
|
<zorder></zorder>
|
||||||
@ -232,9 +232,6 @@
|
|||||||
<property name="insertPolicy">
|
<property name="insertPolicy">
|
||||||
<enum>QComboBox::InsertAlphabetically</enum>
|
<enum>QComboBox::InsertAlphabetically</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QComboBox::AdjustToContents</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="6" column="1">
|
||||||
|
@ -638,7 +638,6 @@ class DeviceMixin(object): # {{{
|
|||||||
self.device_error_dialog = error_dialog(self, _('Error'),
|
self.device_error_dialog = error_dialog(self, _('Error'),
|
||||||
_('Error communicating with device'), ' ')
|
_('Error communicating with device'), ' ')
|
||||||
self.device_error_dialog.setModal(Qt.NonModal)
|
self.device_error_dialog.setModal(Qt.NonModal)
|
||||||
self.device_connected = None
|
|
||||||
self.emailer = Emailer()
|
self.emailer = Emailer()
|
||||||
self.emailer.start()
|
self.emailer.start()
|
||||||
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
|
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
|
||||||
@ -755,17 +754,14 @@ class DeviceMixin(object): # {{{
|
|||||||
self.device_manager.device.__class__.get_gui_name()+\
|
self.device_manager.device.__class__.get_gui_name()+\
|
||||||
_(' detected.'), 3000)
|
_(' detected.'), 3000)
|
||||||
self.device_connected = device_kind
|
self.device_connected = device_kind
|
||||||
self.location_view.model().device_connected(self.device_manager.device)
|
|
||||||
self.refresh_ondevice_info (device_connected = True, reset_only = True)
|
self.refresh_ondevice_info (device_connected = True, reset_only = True)
|
||||||
else:
|
else:
|
||||||
self.device_connected = None
|
self.device_connected = None
|
||||||
self.status_bar.device_disconnected()
|
self.status_bar.device_disconnected()
|
||||||
self.location_view.model().update_devices()
|
|
||||||
if self.current_view() != self.library_view:
|
if self.current_view() != self.library_view:
|
||||||
self.book_details.reset_info()
|
self.book_details.reset_info()
|
||||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
self.location_manager.update_devices()
|
||||||
self.refresh_ondevice_info (device_connected = False)
|
self.refresh_ondevice_info(device_connected=False)
|
||||||
self.tool_bar.device_status_changed(bool(connected))
|
|
||||||
|
|
||||||
def info_read(self, job):
|
def info_read(self, job):
|
||||||
'''
|
'''
|
||||||
@ -774,7 +770,8 @@ class DeviceMixin(object): # {{{
|
|||||||
if job.failed:
|
if job.failed:
|
||||||
return self.device_job_exception(job)
|
return self.device_job_exception(job)
|
||||||
info, cp, fs = job.result
|
info, cp, fs = job.result
|
||||||
self.location_view.model().update_devices(cp, fs)
|
self.location_manager.update_devices(cp, fs,
|
||||||
|
self.device_manager.device.icon)
|
||||||
self.status_bar.device_connected(info[0])
|
self.status_bar.device_connected(info[0])
|
||||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||||
|
|
||||||
@ -1076,9 +1073,9 @@ class DeviceMixin(object): # {{{
|
|||||||
dynamic.set('catalogs_to_be_synced', set([]))
|
dynamic.set('catalogs_to_be_synced', set([]))
|
||||||
if files:
|
if files:
|
||||||
remove = []
|
remove = []
|
||||||
space = { self.location_view.model().free[0] : None,
|
space = { self.location_manager.free[0] : None,
|
||||||
self.location_view.model().free[1] : 'carda',
|
self.location_manager.free[1] : 'carda',
|
||||||
self.location_view.model().free[2] : 'cardb' }
|
self.location_manager.free[2] : 'cardb' }
|
||||||
on_card = space.get(sorted(space.keys(), reverse=True)[0], None)
|
on_card = space.get(sorted(space.keys(), reverse=True)[0], None)
|
||||||
self.upload_books(files, names, metadata,
|
self.upload_books(files, names, metadata,
|
||||||
on_card=on_card,
|
on_card=on_card,
|
||||||
@ -1140,9 +1137,9 @@ class DeviceMixin(object): # {{{
|
|||||||
dynamic.set('news_to_be_synced', set([]))
|
dynamic.set('news_to_be_synced', set([]))
|
||||||
if config['upload_news_to_device'] and files:
|
if config['upload_news_to_device'] and files:
|
||||||
remove = ids if del_on_upload else []
|
remove = ids if del_on_upload else []
|
||||||
space = { self.location_view.model().free[0] : None,
|
space = { self.location_manager.free[0] : None,
|
||||||
self.location_view.model().free[1] : 'carda',
|
self.location_manager.free[1] : 'carda',
|
||||||
self.location_view.model().free[2] : 'cardb' }
|
self.location_manager.free[2] : 'cardb' }
|
||||||
on_card = space.get(sorted(space.keys(), reverse=True)[0], None)
|
on_card = space.get(sorted(space.keys(), reverse=True)[0], None)
|
||||||
self.upload_books(files, names, metadata,
|
self.upload_books(files, names, metadata,
|
||||||
on_card=on_card,
|
on_card=on_card,
|
||||||
@ -1263,7 +1260,8 @@ class DeviceMixin(object): # {{{
|
|||||||
self.device_job_exception(job)
|
self.device_job_exception(job)
|
||||||
return
|
return
|
||||||
cp, fs = job.result
|
cp, fs = job.result
|
||||||
self.location_view.model().update_devices(cp, fs)
|
self.location_manager.update_devices(cp, fs,
|
||||||
|
self.device_manager.device.icon)
|
||||||
# reset the views so that up-to-date info is shown. These need to be
|
# reset the views so that up-to-date info is shown. These need to be
|
||||||
# here because the sony driver updates collections in sync_booklists
|
# here because the sony driver updates collections in sync_booklists
|
||||||
self.memory_view.reset()
|
self.memory_view.reset()
|
||||||
|
82
src/calibre/gui2/dialogs/choose_library.py
Normal file
82
src/calibre/gui2/dialogs/choose_library.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from PyQt4.Qt import QDialog
|
||||||
|
|
||||||
|
from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog
|
||||||
|
from calibre.gui2 import error_dialog, choose_dir
|
||||||
|
from calibre.constants import filesystem_encoding
|
||||||
|
from calibre import isbytestring, patheq
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.gui2.wizard import move_library
|
||||||
|
|
||||||
|
class ChooseLibrary(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
|
def __init__(self, db, callback, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
self.db = db
|
||||||
|
self.new_db = None
|
||||||
|
self.callback = callback
|
||||||
|
|
||||||
|
lp = db.library_path
|
||||||
|
if isbytestring(lp):
|
||||||
|
lp = lp.decode(filesystem_encoding)
|
||||||
|
loc = unicode(self.old_location.text()).format(lp)
|
||||||
|
self.old_location.setText(loc)
|
||||||
|
self.browse_button.clicked.connect(self.choose_loc)
|
||||||
|
|
||||||
|
def choose_loc(self, *args):
|
||||||
|
loc = choose_dir(self, 'choose library location',
|
||||||
|
_('Choose location for calibre library'))
|
||||||
|
if loc is not None:
|
||||||
|
self.location.setText(loc)
|
||||||
|
|
||||||
|
def check_action(self, ac, loc):
|
||||||
|
exists = self.db.exists_at(loc)
|
||||||
|
if patheq(loc, self.db.library_path):
|
||||||
|
error_dialog(self, _('Same as current'),
|
||||||
|
_('The location %s contains the current calibre'
|
||||||
|
' library')%loc, show=True)
|
||||||
|
return False
|
||||||
|
empty = not os.listdir(loc)
|
||||||
|
if ac == 'existing' and not exists:
|
||||||
|
error_dialog(self, _('No existing library found'),
|
||||||
|
_('There is no existing calibre library at %s')%loc,
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
|
if ac in ('new', 'move') and not empty:
|
||||||
|
error_dialog(self, _('Not empty'),
|
||||||
|
_('The folder %s is not empty. Please choose an empty'
|
||||||
|
' folder')%loc,
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def perform_action(self, ac, loc):
|
||||||
|
if ac in ('new', 'existing'):
|
||||||
|
prefs['library_path'] = loc
|
||||||
|
self.callback(loc)
|
||||||
|
else:
|
||||||
|
move_library(self.db.library_path, loc, self.parent(),
|
||||||
|
self.callback)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
action = 'move'
|
||||||
|
if self.existing_library.isChecked():
|
||||||
|
action = 'existing'
|
||||||
|
elif self.empty_library.isChecked():
|
||||||
|
action = 'new'
|
||||||
|
loc = os.path.abspath(unicode(self.location.text()).strip())
|
||||||
|
if not loc or not os.path.exists(loc) or not self.check_action(action,
|
||||||
|
loc):
|
||||||
|
return
|
||||||
|
QDialog.accept(self)
|
||||||
|
self.perform_action(action, loc)
|
171
src/calibre/gui2/dialogs/choose_library.ui
Normal file
171
src/calibre/gui2/dialogs/choose_library.ui
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>602</width>
|
||||||
|
<height>245</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Choose your calibre library</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="old_location">
|
||||||
|
<property name="text">
|
||||||
|
<string>Your calibre library is currently located at {0}</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>New &Location:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>location</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="location">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="3">
|
||||||
|
<widget class="QRadioButton" name="existing_library">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use &existing library at the new location</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0" colspan="2">
|
||||||
|
<widget class="QRadioButton" name="empty_library">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Create an empty library at the new location</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" colspan="2">
|
||||||
|
<widget class="QRadioButton" name="move_library">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Move current library to new location</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QToolButton" name="browse_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../../../../resources/images.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
@ -14,7 +14,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
|
|||||||
from calibre.constants import iswindows, isosx
|
from calibre.constants import iswindows, isosx
|
||||||
from calibre.gui2.dialogs.config.config_ui import Ui_Dialog
|
from calibre.gui2.dialogs.config.config_ui import Ui_Dialog
|
||||||
from calibre.gui2.dialogs.config.create_custom_column import CreateCustomColumn
|
from calibre.gui2.dialogs.config.create_custom_column import CreateCustomColumn
|
||||||
from calibre.gui2 import choose_dir, error_dialog, config, gprefs, \
|
from calibre.gui2 import error_dialog, config, gprefs, \
|
||||||
open_url, open_local_file, \
|
open_url, open_local_file, \
|
||||||
ALL_COLUMNS, NONE, info_dialog, choose_files, \
|
ALL_COLUMNS, NONE, info_dialog, choose_files, \
|
||||||
warning_dialog, ResizableDialog, question_dialog
|
warning_dialog, ResizableDialog, question_dialog
|
||||||
@ -343,9 +343,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
|||||||
self.model = library_view.model()
|
self.model = library_view.model()
|
||||||
self.db = self.model.db
|
self.db = self.model.db
|
||||||
self.server = server
|
self.server = server
|
||||||
path = prefs['library_path']
|
|
||||||
self.location.setText(path if path else '')
|
|
||||||
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
|
||||||
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
||||||
|
|
||||||
input_map = prefs['input_format_order']
|
input_map = prefs['input_format_order']
|
||||||
@ -808,12 +805,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
|||||||
d = CheckIntegrity(self.db, self)
|
d = CheckIntegrity(self.db, self)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def browse(self):
|
|
||||||
dir = choose_dir(self, 'database location dialog',
|
|
||||||
_('Select location for books'))
|
|
||||||
if dir:
|
|
||||||
self.location.setText(dir)
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
mcs = unicode(self.max_cover_size.text()).strip()
|
mcs = unicode(self.max_cover_size.text()).strip()
|
||||||
if not re.match(r'\d+x\d+', mcs):
|
if not re.match(r'\d+x\d+', mcs):
|
||||||
@ -834,7 +825,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
|||||||
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
||||||
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||||
prefs['network_timeout'] = int(self.timeout.value())
|
prefs['network_timeout'] = int(self.timeout.value())
|
||||||
path = unicode(self.location.text())
|
|
||||||
input_cols = [unicode(self.input_order.item(i).data(Qt.UserRole).toString()) for i in range(self.input_order.count())]
|
input_cols = [unicode(self.input_order.item(i).data(Qt.UserRole).toString()) for i in range(self.input_order.count())]
|
||||||
prefs['input_format_order'] = input_cols
|
prefs['input_format_order'] = input_cols
|
||||||
|
|
||||||
@ -875,17 +865,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
|||||||
val = self.opt_gui_layout.itemData(self.opt_gui_layout.currentIndex()).toString()
|
val = self.opt_gui_layout.itemData(self.opt_gui_layout.currentIndex()).toString()
|
||||||
config['gui_layout'] = unicode(val)
|
config['gui_layout'] = unicode(val)
|
||||||
|
|
||||||
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
|
||||||
d = error_dialog(self, _('Invalid database location'),
|
|
||||||
_('Invalid database location ')+path+
|
|
||||||
_('<br>Must be a directory.'))
|
|
||||||
d.exec_()
|
|
||||||
elif not os.access(path, os.W_OK):
|
|
||||||
d = error_dialog(self, _('Invalid database location'),
|
|
||||||
_('Invalid database location.<br>Cannot write to ')+path)
|
|
||||||
d.exec_()
|
|
||||||
else:
|
|
||||||
self.database_location = os.path.abspath(path)
|
|
||||||
if must_restart:
|
if must_restart:
|
||||||
warning_dialog(self, _('Must restart'),
|
warning_dialog(self, _('Must restart'),
|
||||||
_('The changes you made require that Calibre be '
|
_('The changes you made require that Calibre be '
|
||||||
|
@ -113,50 +113,6 @@
|
|||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="page_3">
|
<widget class="QWidget" name="page_3">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>70</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Location of ebooks (The ebooks are stored in folders sorted by author and metadata is stored in the file metadata.db)</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>location</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="_3">
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="location"/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="browse_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Browse for the new database location</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/mimetypes/dir.svg</normaloff>:/images/mimetypes/dir.svg</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="new_version_notification">
|
<widget class="QCheckBox" name="new_version_notification">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -277,12 +277,6 @@
|
|||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="EnComboBox" name="series">
|
<widget class="EnComboBox" name="series">
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>List of known series. You can add new series.</string>
|
<string>List of known series. You can add new series.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -295,9 +289,6 @@
|
|||||||
<property name="insertPolicy">
|
<property name="insertPolicy">
|
||||||
<enum>QComboBox::InsertAlphabetically</enum>
|
<enum>QComboBox::InsertAlphabetically</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QComboBox::AdjustToContents</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -7,14 +7,13 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import functools, sys, os
|
import functools, sys, os
|
||||||
|
|
||||||
from PyQt4.Qt import QMenu, Qt, pyqtSignal, QIcon, QStackedWidget, \
|
from PyQt4.Qt import QMenu, Qt, QStackedWidget, \
|
||||||
QSize, QSizePolicy, QStatusBar, QUrl, QLabel, QFont
|
QSize, QSizePolicy, QStatusBar, QLabel, QFont
|
||||||
|
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
|
||||||
from calibre.constants import isosx, __appname__, preferred_encoding, \
|
from calibre.constants import isosx, __appname__, preferred_encoding, \
|
||||||
__version__
|
__version__
|
||||||
from calibre.gui2 import config, is_widescreen, open_url
|
from calibre.gui2 import config, is_widescreen
|
||||||
from calibre.gui2.library.views import BooksView, DeviceBooksView
|
from calibre.gui2.library.views import BooksView, DeviceBooksView
|
||||||
from calibre.gui2.widgets import Splitter
|
from calibre.gui2.widgets import Splitter
|
||||||
from calibre.gui2.tag_view import TagBrowserWidget
|
from calibre.gui2.tag_view import TagBrowserWidget
|
||||||
@ -28,157 +27,6 @@ def partial(*args, **kwargs):
|
|||||||
_keep_refs.append(ans)
|
_keep_refs.append(ans)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
class SaveMenu(QMenu): # {{{
|
|
||||||
|
|
||||||
save_fmt = pyqtSignal(object)
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
|
||||||
for ext in sorted(BOOK_EXTENSIONS):
|
|
||||||
action = self.addAction(ext.upper())
|
|
||||||
setattr(self, 'do_'+ext, partial(self.do, ext))
|
|
||||||
action.triggered.connect(
|
|
||||||
getattr(self, 'do_'+ext))
|
|
||||||
|
|
||||||
def do(self, ext, *args):
|
|
||||||
self.save_fmt.emit(ext)
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class ToolbarMixin(object): # {{{
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.action_help.triggered.connect(self.show_help)
|
|
||||||
md = QMenu()
|
|
||||||
md.addAction(_('Edit metadata individually'),
|
|
||||||
partial(self.edit_metadata, False, bulk=False))
|
|
||||||
md.addSeparator()
|
|
||||||
md.addAction(_('Edit metadata in bulk'),
|
|
||||||
partial(self.edit_metadata, False, bulk=True))
|
|
||||||
md.addSeparator()
|
|
||||||
md.addAction(_('Download metadata and covers'),
|
|
||||||
partial(self.download_metadata, False, covers=True),
|
|
||||||
Qt.ControlModifier+Qt.Key_D)
|
|
||||||
md.addAction(_('Download only metadata'),
|
|
||||||
partial(self.download_metadata, False, covers=False))
|
|
||||||
md.addAction(_('Download only covers'),
|
|
||||||
partial(self.download_metadata, False, covers=True,
|
|
||||||
set_metadata=False, set_social_metadata=False))
|
|
||||||
md.addAction(_('Download only social metadata'),
|
|
||||||
partial(self.download_metadata, False, covers=False,
|
|
||||||
set_metadata=False, set_social_metadata=True))
|
|
||||||
self.metadata_menu = md
|
|
||||||
|
|
||||||
mb = QMenu()
|
|
||||||
mb.addAction(_('Merge into first selected book - delete others'),
|
|
||||||
self.merge_books)
|
|
||||||
mb.addSeparator()
|
|
||||||
mb.addAction(_('Merge into first selected book - keep others'),
|
|
||||||
partial(self.merge_books, safe_merge=True))
|
|
||||||
self.merge_menu = mb
|
|
||||||
self.action_merge.setMenu(mb)
|
|
||||||
md.addSeparator()
|
|
||||||
md.addAction(self.action_merge)
|
|
||||||
|
|
||||||
self.add_menu = QMenu()
|
|
||||||
self.add_menu.addAction(_('Add books from a single directory'),
|
|
||||||
self.add_books)
|
|
||||||
self.add_menu.addAction(_('Add books from directories, including '
|
|
||||||
'sub-directories (One book per directory, assumes every ebook '
|
|
||||||
'file is the same book in a different format)'),
|
|
||||||
self.add_recursive_single)
|
|
||||||
self.add_menu.addAction(_('Add books from directories, including '
|
|
||||||
'sub directories (Multiple books per directory, assumes every '
|
|
||||||
'ebook file is a different book)'), self.add_recursive_multiple)
|
|
||||||
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
|
||||||
'formats)'), self.add_empty)
|
|
||||||
self.action_add.setMenu(self.add_menu)
|
|
||||||
self.action_add.triggered.connect(self.add_books)
|
|
||||||
self.action_del.triggered.connect(self.delete_books)
|
|
||||||
self.action_edit.triggered.connect(self.edit_metadata)
|
|
||||||
self.action_merge.triggered.connect(self.merge_books)
|
|
||||||
|
|
||||||
self.action_save.triggered.connect(self.save_to_disk)
|
|
||||||
self.save_menu = QMenu()
|
|
||||||
self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
|
|
||||||
False))
|
|
||||||
self.save_menu.addAction(_('Save to disk in a single directory'),
|
|
||||||
partial(self.save_to_single_dir, False))
|
|
||||||
self.save_menu.addAction(_('Save only %s format to disk')%
|
|
||||||
prefs['output_format'].upper(),
|
|
||||||
partial(self.save_single_format_to_disk, False))
|
|
||||||
self.save_menu.addAction(
|
|
||||||
_('Save only %s format to disk in a single directory')%
|
|
||||||
prefs['output_format'].upper(),
|
|
||||||
partial(self.save_single_fmt_to_single_dir, False))
|
|
||||||
self.save_sub_menu = SaveMenu(self)
|
|
||||||
self.save_menu.addMenu(self.save_sub_menu)
|
|
||||||
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
|
|
||||||
|
|
||||||
self.action_view.triggered.connect(self.view_book)
|
|
||||||
self.view_menu = QMenu()
|
|
||||||
self.view_menu.addAction(_('View'), partial(self.view_book, False))
|
|
||||||
ac = self.view_menu.addAction(_('View specific format'))
|
|
||||||
ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
|
|
||||||
self.action_view.setMenu(self.view_menu)
|
|
||||||
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
|
|
||||||
|
|
||||||
self.delete_menu = QMenu()
|
|
||||||
self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove files of a specific format from selected books..'),
|
|
||||||
self.delete_selected_formats)
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove all formats from selected books, except...'),
|
|
||||||
self.delete_all_but_selected_formats)
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove covers from selected books'), self.delete_covers)
|
|
||||||
self.delete_menu.addSeparator()
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove matching books from device'),
|
|
||||||
self.remove_matching_books_from_device)
|
|
||||||
self.action_del.setMenu(self.delete_menu)
|
|
||||||
|
|
||||||
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
|
||||||
self.addAction(self.action_open_containing_folder)
|
|
||||||
self.action_open_containing_folder.triggered.connect(self.view_folder)
|
|
||||||
self.action_sync.setShortcut(Qt.Key_D)
|
|
||||||
self.action_sync.setEnabled(True)
|
|
||||||
self.create_device_menu()
|
|
||||||
self.action_sync.triggered.connect(
|
|
||||||
self._sync_action_triggered)
|
|
||||||
|
|
||||||
self.action_edit.setMenu(md)
|
|
||||||
self.action_save.setMenu(self.save_menu)
|
|
||||||
|
|
||||||
cm = QMenu()
|
|
||||||
cm.addAction(_('Convert individually'), partial(self.convert_ebook,
|
|
||||||
False, bulk=False))
|
|
||||||
cm.addAction(_('Bulk convert'),
|
|
||||||
partial(self.convert_ebook, False, bulk=True))
|
|
||||||
cm.addSeparator()
|
|
||||||
ac = cm.addAction(
|
|
||||||
_('Create catalog of books in your calibre library'))
|
|
||||||
ac.triggered.connect(self.generate_catalog)
|
|
||||||
self.action_convert.setMenu(cm)
|
|
||||||
self.action_convert.triggered.connect(self.convert_ebook)
|
|
||||||
self.convert_menu = cm
|
|
||||||
|
|
||||||
pm = QMenu()
|
|
||||||
pm.addAction(QIcon(I('config.svg')), _('Preferences'), self.do_config)
|
|
||||||
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
|
||||||
self.run_wizard)
|
|
||||||
self.action_preferences.setMenu(pm)
|
|
||||||
self.preferences_menu = pm
|
|
||||||
for x in (self.preferences_action, self.action_preferences):
|
|
||||||
x.triggered.connect(self.do_config)
|
|
||||||
|
|
||||||
def show_help(self, *args):
|
|
||||||
open_url(QUrl('http://calibre-ebook.com/user_manual'))
|
|
||||||
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class LibraryViewMixin(object): # {{{
|
class LibraryViewMixin(object): # {{{
|
||||||
|
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
|
@ -6,108 +6,105 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, QVariant, \
|
from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, \
|
||||||
QAbstractListModel, QFont, QApplication, QPalette, pyqtSignal, QToolButton, \
|
pyqtSignal, QToolButton, \
|
||||||
QModelIndex, QListView, QAbstractButton, QPainter, QPixmap, QColor, \
|
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup, \
|
||||||
QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout
|
QMenu, QUrl
|
||||||
|
|
||||||
from calibre.constants import __appname__, filesystem_encoding
|
from calibre.constants import __appname__, isosx
|
||||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||||
from calibre.gui2.throbber import ThrobbingButton
|
from calibre.gui2.throbber import ThrobbingButton
|
||||||
from calibre.gui2 import NONE, config
|
from calibre.gui2 import config, open_url
|
||||||
from calibre.gui2.widgets import ComboBoxWithHelp
|
from calibre.gui2.widgets import ComboBoxWithHelp
|
||||||
from calibre import human_readable
|
from calibre import human_readable
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
|
||||||
ICON_SIZE = 48
|
ICON_SIZE = 48
|
||||||
|
|
||||||
# Location View {{{
|
class SaveMenu(QMenu): # {{{
|
||||||
|
|
||||||
class LocationModel(QAbstractListModel): # {{{
|
save_fmt = pyqtSignal(object)
|
||||||
|
|
||||||
devicesChanged = pyqtSignal()
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QAbstractListModel.__init__(self, parent)
|
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
||||||
self.icons = [QVariant(QIcon(I('library.png'))),
|
for ext in sorted(BOOK_EXTENSIONS):
|
||||||
QVariant(QIcon(I('reader.svg'))),
|
action = self.addAction(ext.upper())
|
||||||
QVariant(QIcon(I('sd.svg'))),
|
setattr(self, 'do_'+ext, partial(self.do, ext))
|
||||||
QVariant(QIcon(I('sd.svg')))]
|
action.triggered.connect(
|
||||||
self.text = [_('Library\n%d books'),
|
getattr(self, 'do_'+ext))
|
||||||
_('Reader\n%s'),
|
|
||||||
_('Card A\n%s'),
|
def do(self, ext, *args):
|
||||||
_('Card B\n%s')]
|
self.save_fmt.emit(ext)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class LocationManager(QObject): # {{{
|
||||||
|
|
||||||
|
locations_changed = pyqtSignal()
|
||||||
|
unmount_device = pyqtSignal()
|
||||||
|
location_selected = pyqtSignal(object)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QObject.__init__(self, parent)
|
||||||
self.free = [-1, -1, -1]
|
self.free = [-1, -1, -1]
|
||||||
self.count = 0
|
self.count = 0
|
||||||
self.highlight_row = 0
|
self.location_actions = QActionGroup(self)
|
||||||
self.library_tooltip = _('Click to see the books available on your computer')
|
self.location_actions.setExclusive(True)
|
||||||
self.tooltips = [
|
self.current_location = 'library'
|
||||||
self.library_tooltip,
|
self._mem = []
|
||||||
_('Click to see the books in the main memory of your reader'),
|
self.tooltips = {}
|
||||||
_('Click to see the books on storage card A in your reader'),
|
|
||||||
_('Click to see the books on storage card B in your reader')
|
|
||||||
]
|
|
||||||
|
|
||||||
def database_changed(self, db):
|
def ac(name, text, icon, tooltip):
|
||||||
lp = db.library_path
|
icon = QIcon(I(icon))
|
||||||
if not isinstance(lp, unicode):
|
ac = self.location_actions.addAction(icon, text)
|
||||||
lp = lp.decode(filesystem_encoding, 'replace')
|
setattr(self, 'location_'+name, ac)
|
||||||
self.tooltips[0] = self.library_tooltip + '\n\n' + \
|
ac.setAutoRepeat(False)
|
||||||
_('Books located at') + ' ' + lp
|
ac.setCheckable(True)
|
||||||
self.dataChanged.emit(self.index(0), self.index(0))
|
receiver = partial(self._location_selected, name)
|
||||||
|
ac.triggered.connect(receiver)
|
||||||
|
self.tooltips[name] = tooltip
|
||||||
|
if name != 'library':
|
||||||
|
m = QMenu(parent)
|
||||||
|
self._mem.append(m)
|
||||||
|
a = m.addAction(icon, tooltip)
|
||||||
|
a.triggered.connect(receiver)
|
||||||
|
self._mem.append(a)
|
||||||
|
a = m.addAction(QIcon(I('eject.svg')), _('Eject this device'))
|
||||||
|
a.triggered.connect(self._eject_requested)
|
||||||
|
ac.setMenu(m)
|
||||||
|
self._mem.append(a)
|
||||||
|
else:
|
||||||
|
ac.setToolTip(tooltip)
|
||||||
|
|
||||||
def rowCount(self, *args):
|
return ac
|
||||||
return 1 + len([i for i in self.free if i >= 0])
|
|
||||||
|
|
||||||
def get_device_row(self, row):
|
ac('library', _('Library'), 'lt.png',
|
||||||
if row == 2 and self.free[1] == -1 and self.free[2] > -1:
|
_('Show books in calibre library'))
|
||||||
row = 3
|
ac('main', _('Main'), 'reader.svg',
|
||||||
return row
|
_('Show books in the main memory of the device'))
|
||||||
|
ac('carda', _('Card A'), 'sd.svg',
|
||||||
|
_('Show books in storage card A'))
|
||||||
|
ac('cardb', _('Card B'), 'sd.svg',
|
||||||
|
_('Show books in storage card B'))
|
||||||
|
|
||||||
def get_tooltip(self, row, drow):
|
def _location_selected(self, location, *args):
|
||||||
ans = self.tooltips[row]
|
if location != self.current_location and hasattr(self,
|
||||||
if row > 0:
|
'location_'+location):
|
||||||
fs = self.free[drow-1]
|
self.current_location = location
|
||||||
if fs > -1:
|
self.location_selected.emit(location)
|
||||||
ans += '\n\n%s '%(human_readable(fs)) + _('free')
|
getattr(self, 'location_'+location).setChecked(True)
|
||||||
return ans
|
|
||||||
|
|
||||||
def data(self, index, role):
|
def _eject_requested(self, *args):
|
||||||
row = index.row()
|
self.unmount_device.emit()
|
||||||
drow = self.get_device_row(row)
|
|
||||||
data = NONE
|
|
||||||
if role == Qt.DisplayRole:
|
|
||||||
text = self.text[drow]%(human_readable(self.free[drow-1])) if row > 0 \
|
|
||||||
else self.text[drow]%self.count
|
|
||||||
data = QVariant(text)
|
|
||||||
elif role == Qt.DecorationRole:
|
|
||||||
data = self.icons[drow]
|
|
||||||
elif role in (Qt.ToolTipRole, Qt.StatusTipRole):
|
|
||||||
ans = self.get_tooltip(row, drow)
|
|
||||||
data = QVariant(ans)
|
|
||||||
elif role == Qt.SizeHintRole:
|
|
||||||
data = QVariant(QSize(155, 90))
|
|
||||||
elif role == Qt.FontRole:
|
|
||||||
font = QFont('monospace')
|
|
||||||
font.setBold(row == self.highlight_row)
|
|
||||||
data = QVariant(font)
|
|
||||||
elif role == Qt.ForegroundRole and row == self.highlight_row:
|
|
||||||
return QVariant(QApplication.palette().brush(
|
|
||||||
QPalette.HighlightedText))
|
|
||||||
elif role == Qt.BackgroundRole and row == self.highlight_row:
|
|
||||||
return QVariant(QApplication.palette().brush(
|
|
||||||
QPalette.Highlight))
|
|
||||||
|
|
||||||
return data
|
def update_devices(self, cp=(None, None), fs=[-1, -1, -1], icon=None):
|
||||||
|
if icon is None:
|
||||||
def device_connected(self, dev):
|
icon = I('reader.svg')
|
||||||
self.icons[1] = QIcon(dev.icon)
|
self.location_main.setIcon(QIcon(icon))
|
||||||
self.dataChanged.emit(self.index(1), self.index(1))
|
had_device = self.has_device
|
||||||
|
|
||||||
def headerData(self, section, orientation, role):
|
|
||||||
return NONE
|
|
||||||
|
|
||||||
def update_devices(self, cp=(None, None), fs=[-1, -1, -1]):
|
|
||||||
if cp is None:
|
if cp is None:
|
||||||
cp = (None, None)
|
cp = (None, None)
|
||||||
if isinstance(cp, (str, unicode)):
|
if isinstance(cp, (str, unicode)):
|
||||||
@ -120,137 +117,34 @@ class LocationModel(QAbstractListModel): # {{{
|
|||||||
cpa, cpb = cp
|
cpa, cpb = cp
|
||||||
self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1
|
self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1
|
||||||
self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1
|
self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1
|
||||||
self.reset()
|
self.update_tooltips()
|
||||||
self.devicesChanged.emit()
|
if self.has_device != had_device:
|
||||||
|
self.locations_changed.emit()
|
||||||
|
if not self.has_device:
|
||||||
|
self.location_library.trigger()
|
||||||
|
|
||||||
def location_changed(self, row):
|
def update_tooltips(self):
|
||||||
self.highlight_row = row
|
for i, loc in enumerate(('main', 'carda', 'cardb')):
|
||||||
self.dataChanged.emit(
|
t = self.tooltips[loc]
|
||||||
self.index(0), self.index(self.rowCount(QModelIndex())-1))
|
if self.free[i] > -1:
|
||||||
|
t += u'\n\n%s '%human_readable(self.free[i]) + _('available')
|
||||||
|
ac = getattr(self, 'location_'+loc)
|
||||||
|
ac.setToolTip(t)
|
||||||
|
ac.setWhatsThis(t)
|
||||||
|
ac.setStatusTip(t)
|
||||||
|
|
||||||
def location_for_row(self, row):
|
|
||||||
if row == 0: return 'library'
|
|
||||||
if row == 1: return 'main'
|
|
||||||
if row == 3: return 'cardb'
|
|
||||||
return 'carda' if self.free[1] > -1 else 'cardb'
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class LocationView(QListView):
|
|
||||||
|
|
||||||
umount_device = pyqtSignal()
|
|
||||||
location_selected = pyqtSignal(object)
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
QListView.__init__(self, parent)
|
|
||||||
self.setModel(LocationModel(self))
|
|
||||||
self.reset()
|
|
||||||
self.currentChanged = self.current_changed
|
|
||||||
|
|
||||||
self.eject_button = EjectButton(self)
|
|
||||||
self.eject_button.hide()
|
|
||||||
|
|
||||||
self.entered.connect(self.item_entered)
|
|
||||||
self.viewportEntered.connect(self.viewport_entered)
|
|
||||||
self.eject_button.clicked.connect(self.eject_clicked)
|
|
||||||
self.model().devicesChanged.connect(self.eject_button.hide)
|
|
||||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
|
||||||
QSizePolicy.Expanding))
|
|
||||||
self.setMouseTracking(True)
|
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
||||||
self.setEditTriggers(self.NoEditTriggers)
|
|
||||||
self.setTabKeyNavigation(True)
|
|
||||||
self.setProperty("showDropIndicator", True)
|
|
||||||
self.setSelectionMode(self.SingleSelection)
|
|
||||||
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
|
||||||
self.setMovement(self.Static)
|
|
||||||
self.setFlow(self.LeftToRight)
|
|
||||||
self.setGridSize(QSize(175, ICON_SIZE))
|
|
||||||
self.setViewMode(self.ListMode)
|
|
||||||
self.setWordWrap(True)
|
|
||||||
self.setObjectName("location_view")
|
|
||||||
self.setMaximumSize(QSize(600, ICON_SIZE+16))
|
|
||||||
self.setMinimumWidth(400)
|
|
||||||
|
|
||||||
def eject_clicked(self, *args):
|
|
||||||
self.umount_device.emit()
|
|
||||||
|
|
||||||
def count_changed(self, new_count):
|
|
||||||
self.model().count = new_count
|
|
||||||
self.model().reset()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def book_count(self):
|
def has_device(self):
|
||||||
return self.model().count
|
return max(self.free) > -1
|
||||||
|
|
||||||
def current_changed(self, current, previous):
|
|
||||||
if current.isValid():
|
|
||||||
i = current.row()
|
|
||||||
location = self.model().location_for_row(i)
|
|
||||||
self.location_selected.emit(location)
|
|
||||||
self.model().location_changed(i)
|
|
||||||
|
|
||||||
def location_changed(self, row):
|
|
||||||
if 0 <= row and row <= 3:
|
|
||||||
self.model().location_changed(row)
|
|
||||||
|
|
||||||
def leaveEvent(self, event):
|
|
||||||
self.unsetCursor()
|
|
||||||
self.eject_button.hide()
|
|
||||||
|
|
||||||
def item_entered(self, location):
|
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
|
||||||
self.eject_button.hide()
|
|
||||||
|
|
||||||
if location.row() == 1:
|
|
||||||
rect = self.visualRect(location)
|
|
||||||
|
|
||||||
self.eject_button.resize(rect.height()/2, rect.height()/2)
|
|
||||||
|
|
||||||
x, y = rect.left(), rect.top()
|
|
||||||
x = x + (rect.width() - self.eject_button.width() - 2)
|
|
||||||
y += 6
|
|
||||||
|
|
||||||
self.eject_button.move(x, y)
|
|
||||||
self.eject_button.show()
|
|
||||||
|
|
||||||
def viewport_entered(self):
|
|
||||||
self.unsetCursor()
|
|
||||||
self.eject_button.hide()
|
|
||||||
|
|
||||||
|
|
||||||
class EjectButton(QAbstractButton):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
QAbstractButton.__init__(self, parent)
|
|
||||||
self.mouse_over = False
|
|
||||||
self.setMouseTracking(True)
|
|
||||||
|
|
||||||
def enterEvent(self, event):
|
|
||||||
self.mouse_over = True
|
|
||||||
QAbstractButton.enterEvent(self, event)
|
|
||||||
|
|
||||||
def leaveEvent(self, event):
|
|
||||||
self.mouse_over = False
|
|
||||||
QAbstractButton.leaveEvent(self, event)
|
|
||||||
|
|
||||||
def paintEvent(self, event):
|
|
||||||
painter = QPainter(self)
|
|
||||||
painter.setClipRect(event.rect())
|
|
||||||
image = QPixmap(I('eject')).scaledToHeight(event.rect().height(),
|
|
||||||
Qt.SmoothTransformation)
|
|
||||||
|
|
||||||
if not self.mouse_over:
|
|
||||||
alpha_mask = QPixmap(image.width(), image.height())
|
|
||||||
color = QColor(128, 128, 128)
|
|
||||||
alpha_mask.fill(color)
|
|
||||||
image.setAlphaChannel(alpha_mask)
|
|
||||||
|
|
||||||
painter.drawPixmap(0, 0, image)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_actions(self):
|
||||||
|
ans = [self.location_library]
|
||||||
|
for i, loc in enumerate(('main', 'carda', 'cardb')):
|
||||||
|
if self.free[i] > -1:
|
||||||
|
ans.append(getattr(self, 'location_'+loc))
|
||||||
|
return ans
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -326,7 +220,7 @@ class SearchBar(QWidget): # {{{
|
|||||||
|
|
||||||
class ToolBar(QToolBar): # {{{
|
class ToolBar(QToolBar): # {{{
|
||||||
|
|
||||||
def __init__(self, actions, donate, location_view, parent=None):
|
def __init__(self, actions, donate, location_manager, parent=None):
|
||||||
QToolBar.__init__(self, parent)
|
QToolBar.__init__(self, parent)
|
||||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
self.setMovable(False)
|
self.setMovable(False)
|
||||||
@ -335,11 +229,12 @@ class ToolBar(QToolBar): # {{{
|
|||||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||||
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||||
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||||
|
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
||||||
|
|
||||||
self.showing_device = False
|
|
||||||
self.all_actions = actions
|
self.all_actions = actions
|
||||||
self.donate = donate
|
self.donate = donate
|
||||||
self.location_view = location_view
|
self.location_manager = location_manager
|
||||||
|
self.location_manager.locations_changed.connect(self.build_bar)
|
||||||
self.d_widget = QWidget()
|
self.d_widget = QWidget()
|
||||||
self.d_widget.setLayout(QVBoxLayout())
|
self.d_widget.setLayout(QVBoxLayout())
|
||||||
self.d_widget.layout().addWidget(donate)
|
self.d_widget.layout().addWidget(donate)
|
||||||
@ -350,40 +245,45 @@ class ToolBar(QToolBar): # {{{
|
|||||||
def contextMenuEvent(self, *args):
|
def contextMenuEvent(self, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def device_status_changed(self, connected):
|
|
||||||
self.showing_device = connected
|
|
||||||
self.build_bar()
|
|
||||||
|
|
||||||
def build_bar(self):
|
def build_bar(self):
|
||||||
order_field = 'device' if self.showing_device else 'normal'
|
showing_device = self.location_manager.has_device
|
||||||
|
order_field = 'device' if showing_device else 'normal'
|
||||||
o = attrgetter(order_field+'_order')
|
o = attrgetter(order_field+'_order')
|
||||||
sepvals = [2] if self.showing_device else [1]
|
sepvals = [2] if showing_device else [1]
|
||||||
sepvals += [3]
|
sepvals += [3]
|
||||||
actions = [x for x in self.all_actions if o(x) > -1]
|
actions = [x for x in self.all_actions if o(x) > -1]
|
||||||
actions.sort(cmp=lambda x,y : cmp(o(x), o(y)))
|
actions.sort(cmp=lambda x,y : cmp(o(x), o(y)))
|
||||||
self.clear()
|
self.clear()
|
||||||
for x in actions:
|
|
||||||
self.addAction(x)
|
|
||||||
ch = self.widgetForAction(x)
|
def setup_tool_button(ac):
|
||||||
|
ch = self.widgetForAction(ac)
|
||||||
ch.setCursor(Qt.PointingHandCursor)
|
ch.setCursor(Qt.PointingHandCursor)
|
||||||
ch.setAutoRaise(True)
|
ch.setAutoRaise(True)
|
||||||
|
if ac.menu() is not None:
|
||||||
if x.action_name == 'choose_library':
|
|
||||||
self.location_action = self.addWidget(self.location_view)
|
|
||||||
self.choose_action = x
|
|
||||||
if config['show_donate_button']:
|
|
||||||
self.addWidget(self.d_widget)
|
|
||||||
if x.action_name not in ('choose_library', 'help'):
|
|
||||||
ch.setPopupMode(ch.MenuButtonPopup)
|
ch.setPopupMode(ch.MenuButtonPopup)
|
||||||
|
|
||||||
|
for x in actions:
|
||||||
|
self.addAction(x)
|
||||||
|
setup_tool_button(x)
|
||||||
|
|
||||||
|
if x.action_name == 'choose_library':
|
||||||
|
self.choose_action = x
|
||||||
|
if showing_device:
|
||||||
|
self.addSeparator()
|
||||||
|
for ac in self.location_manager.available_actions:
|
||||||
|
self.addAction(ac)
|
||||||
|
setup_tool_button(ac)
|
||||||
|
self.addSeparator()
|
||||||
|
self.location_manager.location_library.trigger()
|
||||||
|
elif config['show_donate_button']:
|
||||||
|
self.addWidget(self.d_widget)
|
||||||
|
|
||||||
for x in actions:
|
for x in actions:
|
||||||
if x.separator_before in sepvals:
|
if x.separator_before in sepvals:
|
||||||
self.insertSeparator(x)
|
self.insertSeparator(x)
|
||||||
|
|
||||||
|
self.choose_action.setVisible(not showing_device)
|
||||||
self.location_action.setVisible(self.showing_device)
|
|
||||||
self.choose_action.setVisible(not self.showing_device)
|
|
||||||
|
|
||||||
def count_changed(self, new_count):
|
def count_changed(self, new_count):
|
||||||
text = _('%d books')%new_count
|
text = _('%d books')%new_count
|
||||||
@ -397,6 +297,9 @@ class ToolBar(QToolBar): # {{{
|
|||||||
self.setToolButtonStyle(style)
|
self.setToolButtonStyle(style)
|
||||||
QToolBar.resizeEvent(self, ev)
|
QToolBar.resizeEvent(self, ev)
|
||||||
|
|
||||||
|
def database_changed(self, db):
|
||||||
|
pass
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class Action(QAction):
|
class Action(QAction):
|
||||||
@ -405,6 +308,7 @@ class Action(QAction):
|
|||||||
class MainWindowMixin(object):
|
class MainWindowMixin(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.device_connected = None
|
||||||
self.setObjectName('MainWindow')
|
self.setObjectName('MainWindow')
|
||||||
self.setWindowIcon(QIcon(I('library.png')))
|
self.setWindowIcon(QIcon(I('library.png')))
|
||||||
self.setWindowTitle(__appname__)
|
self.setWindowTitle(__appname__)
|
||||||
@ -417,9 +321,30 @@ class MainWindowMixin(object):
|
|||||||
self.resize(1012, 740)
|
self.resize(1012, 740)
|
||||||
self.donate_button = ThrobbingButton(self.centralwidget)
|
self.donate_button = ThrobbingButton(self.centralwidget)
|
||||||
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
|
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
|
||||||
|
self.location_manager = LocationManager(self)
|
||||||
|
|
||||||
# Actions {{{
|
all_actions = self.setup_actions()
|
||||||
|
|
||||||
|
self.search_bar = SearchBar(self)
|
||||||
|
self.tool_bar = ToolBar(all_actions, self.donate_button,
|
||||||
|
self.location_manager, self)
|
||||||
|
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||||
|
self.tool_bar.choose_action.triggered.connect(self.choose_library)
|
||||||
|
|
||||||
|
l = self.centralwidget.layout()
|
||||||
|
l.addWidget(self.search_bar)
|
||||||
|
|
||||||
|
|
||||||
|
def read_toolbar_settings(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def choose_library(self, *args):
|
||||||
|
from calibre.gui2.dialogs.choose_library import ChooseLibrary
|
||||||
|
db = self.library_view.model().db
|
||||||
|
c = ChooseLibrary(db, self.library_moved, self)
|
||||||
|
c.exec_()
|
||||||
|
|
||||||
|
def setup_actions(self): # {{{
|
||||||
all_actions = []
|
all_actions = []
|
||||||
|
|
||||||
def ac(normal_order, device_order, separator_before,
|
def ac(normal_order, device_order, separator_before,
|
||||||
@ -467,17 +392,135 @@ class MainWindowMixin(object):
|
|||||||
ac(-1, -1, 0, 'books_with_the_same_tags', _('Books with the same tags'),
|
ac(-1, -1, 0, 'books_with_the_same_tags', _('Books with the same tags'),
|
||||||
'tags.svg')
|
'tags.svg')
|
||||||
|
|
||||||
|
self.action_help.triggered.connect(self.show_help)
|
||||||
|
md = QMenu()
|
||||||
|
md.addAction(_('Edit metadata individually'),
|
||||||
|
partial(self.edit_metadata, False, bulk=False))
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(_('Edit metadata in bulk'),
|
||||||
|
partial(self.edit_metadata, False, bulk=True))
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(_('Download metadata and covers'),
|
||||||
|
partial(self.download_metadata, False, covers=True),
|
||||||
|
Qt.ControlModifier+Qt.Key_D)
|
||||||
|
md.addAction(_('Download only metadata'),
|
||||||
|
partial(self.download_metadata, False, covers=False))
|
||||||
|
md.addAction(_('Download only covers'),
|
||||||
|
partial(self.download_metadata, False, covers=True,
|
||||||
|
set_metadata=False, set_social_metadata=False))
|
||||||
|
md.addAction(_('Download only social metadata'),
|
||||||
|
partial(self.download_metadata, False, covers=False,
|
||||||
|
set_metadata=False, set_social_metadata=True))
|
||||||
|
self.metadata_menu = md
|
||||||
|
|
||||||
|
mb = QMenu()
|
||||||
|
mb.addAction(_('Merge into first selected book - delete others'),
|
||||||
|
self.merge_books)
|
||||||
|
mb.addSeparator()
|
||||||
|
mb.addAction(_('Merge into first selected book - keep others'),
|
||||||
|
partial(self.merge_books, safe_merge=True))
|
||||||
|
self.merge_menu = mb
|
||||||
|
self.action_merge.setMenu(mb)
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(self.action_merge)
|
||||||
|
|
||||||
|
self.add_menu = QMenu()
|
||||||
|
self.add_menu.addAction(_('Add books from a single directory'),
|
||||||
|
self.add_books)
|
||||||
|
self.add_menu.addAction(_('Add books from directories, including '
|
||||||
|
'sub-directories (One book per directory, assumes every ebook '
|
||||||
|
'file is the same book in a different format)'),
|
||||||
|
self.add_recursive_single)
|
||||||
|
self.add_menu.addAction(_('Add books from directories, including '
|
||||||
|
'sub directories (Multiple books per directory, assumes every '
|
||||||
|
'ebook file is a different book)'), self.add_recursive_multiple)
|
||||||
|
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
||||||
|
'formats)'), self.add_empty)
|
||||||
|
self.action_add.setMenu(self.add_menu)
|
||||||
|
self.action_add.triggered.connect(self.add_books)
|
||||||
|
self.action_del.triggered.connect(self.delete_books)
|
||||||
|
self.action_edit.triggered.connect(self.edit_metadata)
|
||||||
|
self.action_merge.triggered.connect(self.merge_books)
|
||||||
|
|
||||||
|
self.action_save.triggered.connect(self.save_to_disk)
|
||||||
|
self.save_menu = QMenu()
|
||||||
|
self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
|
||||||
|
False))
|
||||||
|
self.save_menu.addAction(_('Save to disk in a single directory'),
|
||||||
|
partial(self.save_to_single_dir, False))
|
||||||
|
self.save_menu.addAction(_('Save only %s format to disk')%
|
||||||
|
prefs['output_format'].upper(),
|
||||||
|
partial(self.save_single_format_to_disk, False))
|
||||||
|
self.save_menu.addAction(
|
||||||
|
_('Save only %s format to disk in a single directory')%
|
||||||
|
prefs['output_format'].upper(),
|
||||||
|
partial(self.save_single_fmt_to_single_dir, False))
|
||||||
|
self.save_sub_menu = SaveMenu(self)
|
||||||
|
self.save_menu.addMenu(self.save_sub_menu)
|
||||||
|
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
|
||||||
|
|
||||||
|
self.action_view.triggered.connect(self.view_book)
|
||||||
|
self.view_menu = QMenu()
|
||||||
|
self.view_menu.addAction(_('View'), partial(self.view_book, False))
|
||||||
|
ac = self.view_menu.addAction(_('View specific format'))
|
||||||
|
ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
|
||||||
|
self.action_view.setMenu(self.view_menu)
|
||||||
|
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
self.delete_menu = QMenu()
|
||||||
|
self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove files of a specific format from selected books..'),
|
||||||
|
self.delete_selected_formats)
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove all formats from selected books, except...'),
|
||||||
|
self.delete_all_but_selected_formats)
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove covers from selected books'), self.delete_covers)
|
||||||
|
self.delete_menu.addSeparator()
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove matching books from device'),
|
||||||
|
self.remove_matching_books_from_device)
|
||||||
|
self.action_del.setMenu(self.delete_menu)
|
||||||
|
|
||||||
|
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
||||||
|
self.addAction(self.action_open_containing_folder)
|
||||||
|
self.action_open_containing_folder.triggered.connect(self.view_folder)
|
||||||
|
self.action_sync.setShortcut(Qt.Key_D)
|
||||||
|
self.action_sync.setEnabled(True)
|
||||||
|
self.create_device_menu()
|
||||||
|
self.action_sync.triggered.connect(
|
||||||
|
self._sync_action_triggered)
|
||||||
|
|
||||||
|
self.action_edit.setMenu(md)
|
||||||
|
self.action_save.setMenu(self.save_menu)
|
||||||
|
|
||||||
|
cm = QMenu()
|
||||||
|
cm.addAction(_('Convert individually'), partial(self.convert_ebook,
|
||||||
|
False, bulk=False))
|
||||||
|
cm.addAction(_('Bulk convert'),
|
||||||
|
partial(self.convert_ebook, False, bulk=True))
|
||||||
|
cm.addSeparator()
|
||||||
|
ac = cm.addAction(
|
||||||
|
_('Create catalog of books in your calibre library'))
|
||||||
|
ac.triggered.connect(self.generate_catalog)
|
||||||
|
self.action_convert.setMenu(cm)
|
||||||
|
self.action_convert.triggered.connect(self.convert_ebook)
|
||||||
|
self.convert_menu = cm
|
||||||
|
|
||||||
|
pm = QMenu()
|
||||||
|
pm.addAction(QIcon(I('config.svg')), _('Preferences'), self.do_config)
|
||||||
|
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
||||||
|
self.run_wizard)
|
||||||
|
self.action_preferences.setMenu(pm)
|
||||||
|
self.preferences_menu = pm
|
||||||
|
for x in (self.preferences_action, self.action_preferences):
|
||||||
|
x.triggered.connect(self.do_config)
|
||||||
|
|
||||||
|
return all_actions
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
self.location_view = LocationView(self.centralwidget)
|
def show_help(self, *args):
|
||||||
self.search_bar = SearchBar(self)
|
open_url(QUrl('http://calibre-ebook.com/user_manual'))
|
||||||
self.tool_bar = ToolBar(all_actions, self.donate_button, self.location_view, self)
|
|
||||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
|
||||||
|
|
||||||
l = self.centralwidget.layout()
|
|
||||||
l.addWidget(self.search_bar)
|
|
||||||
|
|
||||||
|
|
||||||
def read_toolbar_settings(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
@ -12,13 +12,13 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import collections, os, sys, textwrap, time
|
import collections, os, sys, textwrap, time
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from PyQt4.Qt import Qt, SIGNAL, QObject, QTimer, \
|
from PyQt4.Qt import Qt, SIGNAL, QTimer, \
|
||||||
QPixmap, QMenu, QIcon, pyqtSignal, \
|
QPixmap, QMenu, QIcon, pyqtSignal, \
|
||||||
QDialog, \
|
QDialog, \
|
||||||
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
||||||
QMessageBox, QHelpEvent
|
QMessageBox, QHelpEvent
|
||||||
|
|
||||||
from calibre import prints, patheq
|
from calibre import prints
|
||||||
from calibre.constants import __appname__, isosx
|
from calibre.constants import __appname__, isosx
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import prefs, dynamic
|
from calibre.utils.config import prefs, dynamic
|
||||||
@ -27,7 +27,6 @@ from calibre.gui2 import error_dialog, GetMetadata, open_local_file, \
|
|||||||
gprefs, max_available_height, config, info_dialog
|
gprefs, max_available_height, config, info_dialog
|
||||||
from calibre.gui2.cover_flow import CoverFlowMixin
|
from calibre.gui2.cover_flow import CoverFlowMixin
|
||||||
from calibre.gui2.widgets import ProgressIndicator
|
from calibre.gui2.widgets import ProgressIndicator
|
||||||
from calibre.gui2.wizard import move_library
|
|
||||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||||
from calibre.gui2.update import UpdateMixin
|
from calibre.gui2.update import UpdateMixin
|
||||||
from calibre.gui2.main_window import MainWindow
|
from calibre.gui2.main_window import MainWindow
|
||||||
@ -38,7 +37,7 @@ from calibre.gui2.dialogs.config import ConfigDialog
|
|||||||
|
|
||||||
from calibre.gui2.dialogs.book_info import BookInfo
|
from calibre.gui2.dialogs.book_info import BookInfo
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.gui2.init import ToolbarMixin, LibraryViewMixin, LayoutMixin
|
from calibre.gui2.init import LibraryViewMixin, LayoutMixin
|
||||||
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
|
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
|
||||||
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
|
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
|
||||||
from calibre.gui2.tag_view import TagBrowserMixin
|
from calibre.gui2.tag_view import TagBrowserMixin
|
||||||
@ -91,7 +90,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
||||||
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
|
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
|
||||||
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
|
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
|
||||||
AnnotationsAction, AddAction, DeleteAction,
|
AnnotationsAction, AddAction, DeleteAction,
|
||||||
@ -192,21 +191,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
|||||||
####################### Start spare job server ########################
|
####################### Start spare job server ########################
|
||||||
QTimer.singleShot(1000, self.add_spare_server)
|
QTimer.singleShot(1000, self.add_spare_server)
|
||||||
|
|
||||||
####################### Location View ########################
|
####################### Location Manager ########################
|
||||||
QObject.connect(self.location_view,
|
self.location_manager.location_selected.connect(self.location_selected)
|
||||||
SIGNAL('location_selected(PyQt_PyObject)'),
|
self.location_manager.unmount_device.connect(self.device_manager.umount_device)
|
||||||
self.location_selected)
|
|
||||||
QObject.connect(self.location_view,
|
|
||||||
SIGNAL('umount_device()'),
|
|
||||||
self.device_manager.umount_device)
|
|
||||||
self.eject_action.triggered.connect(self.device_manager.umount_device)
|
self.eject_action.triggered.connect(self.device_manager.umount_device)
|
||||||
|
|
||||||
#################### Update notification ###################
|
#################### Update notification ###################
|
||||||
UpdateMixin.__init__(self, opts)
|
UpdateMixin.__init__(self, opts)
|
||||||
|
|
||||||
####################### Setup Toolbar #####################
|
|
||||||
ToolbarMixin.__init__(self)
|
|
||||||
|
|
||||||
####################### Search boxes ########################
|
####################### Search boxes ########################
|
||||||
SavedSearchBoxMixin.__init__(self)
|
SavedSearchBoxMixin.__init__(self)
|
||||||
SearchBoxMixin.__init__(self)
|
SearchBoxMixin.__init__(self)
|
||||||
@ -218,7 +210,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
|||||||
|
|
||||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||||
self.hide_windows()
|
self.hide_windows()
|
||||||
for t in (self.location_view, self.tool_bar):
|
for t in (self.tool_bar, ):
|
||||||
self.library_view.model().count_changed_signal.connect \
|
self.library_view.model().count_changed_signal.connect \
|
||||||
(t.count_changed)
|
(t.count_changed)
|
||||||
if not gprefs.get('quick_start_guide_added', False):
|
if not gprefs.get('quick_start_guide_added', False):
|
||||||
@ -235,8 +227,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
|||||||
self.db_images.reset()
|
self.db_images.reset()
|
||||||
|
|
||||||
self.library_view.model().count_changed()
|
self.library_view.model().count_changed()
|
||||||
self.location_view.model().database_changed(self.library_view.model().db)
|
self.tool_bar.database_changed(self.library_view.model().db)
|
||||||
self.library_view.model().database_changed.connect(self.location_view.model().database_changed,
|
self.library_view.model().database_changed.connect(self.tool_bar.database_changed,
|
||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
|
|
||||||
########################### Tags Browser ##############################
|
########################### Tags Browser ##############################
|
||||||
@ -396,10 +388,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
|||||||
self.tags_view.recount()
|
self.tags_view.recount()
|
||||||
self.create_device_menu()
|
self.create_device_menu()
|
||||||
self.set_device_menu_items_state(bool(self.device_connected))
|
self.set_device_menu_items_state(bool(self.device_connected))
|
||||||
if not patheq(self.library_path, d.database_location):
|
|
||||||
newloc = d.database_location
|
|
||||||
move_library(self.library_path, newloc, self,
|
|
||||||
self.library_moved)
|
|
||||||
|
|
||||||
def library_moved(self, newloc):
|
def library_moved(self, newloc):
|
||||||
if newloc is None: return
|
if newloc is None: return
|
||||||
|
@ -490,6 +490,7 @@ class EnComboBox(QComboBox):
|
|||||||
QComboBox.__init__(self, *args)
|
QComboBox.__init__(self, *args)
|
||||||
self.setLineEdit(EnLineEdit(self))
|
self.setLineEdit(EnLineEdit(self))
|
||||||
self.setAutoCompletionCaseSensitivity(Qt.CaseSensitive)
|
self.setAutoCompletionCaseSensitivity(Qt.CaseSensitive)
|
||||||
|
self.setMinimumContentsLength(20)
|
||||||
|
|
||||||
def text(self):
|
def text(self):
|
||||||
return unicode(self.currentText())
|
return unicode(self.currentText())
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2010, Greg Riker <griker at hotmail.com>'
|
__copyright__ = '2010, Greg Riker <griker at hotmail.com>'
|
||||||
|
|
||||||
import datetime, htmlentitydefs, os, re, shutil
|
import datetime, htmlentitydefs, os, re, shutil, codecs
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@ -9,6 +11,7 @@ from copy import deepcopy
|
|||||||
from xml.sax.saxutils import escape
|
from xml.sax.saxutils import escape
|
||||||
|
|
||||||
from calibre import filesystem_encoding, prints, prepare_string_for_xml, strftime
|
from calibre import filesystem_encoding, prints, prepare_string_for_xml, strftime
|
||||||
|
from calibre.constants import preferred_encoding
|
||||||
from calibre.customize import CatalogPlugin
|
from calibre.customize import CatalogPlugin
|
||||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
||||||
@ -21,6 +24,10 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments',
|
|||||||
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
||||||
'uuid']
|
'uuid']
|
||||||
|
|
||||||
|
#Allowed fields for template
|
||||||
|
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
||||||
|
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
|
||||||
|
|
||||||
class CSV_XML(CatalogPlugin):
|
class CSV_XML(CatalogPlugin):
|
||||||
'CSV/XML catalog generator'
|
'CSV/XML catalog generator'
|
||||||
|
|
||||||
@ -89,17 +96,20 @@ class CSV_XML(CatalogPlugin):
|
|||||||
fields = self.get_output_fields(opts)
|
fields = self.get_output_fields(opts)
|
||||||
|
|
||||||
if self.fmt == 'csv':
|
if self.fmt == 'csv':
|
||||||
outfile = open(path_to_output, 'w')
|
outfile = codecs.open(path_to_output, 'w', 'utf8')
|
||||||
|
|
||||||
# Output the field headers
|
# Output the field headers
|
||||||
outfile.write(u'%s\n' % u','.join(fields))
|
outfile.write(u'%s\n' % u','.join(fields))
|
||||||
|
|
||||||
# Output the entry fields
|
# Output the entry fields
|
||||||
for entry in data:
|
for entry in data:
|
||||||
outstr = ''
|
outstr = []
|
||||||
for (x, field) in enumerate(fields):
|
for field in fields:
|
||||||
item = entry[field]
|
item = entry[field]
|
||||||
if field == 'formats':
|
if item is None:
|
||||||
|
outstr.append('""')
|
||||||
|
continue
|
||||||
|
elif field == 'formats':
|
||||||
fmt_list = []
|
fmt_list = []
|
||||||
for format in item:
|
for format in item:
|
||||||
fmt_list.append(format.rpartition('.')[2].lower())
|
fmt_list.append(format.rpartition('.')[2].lower())
|
||||||
@ -111,18 +121,13 @@ class CSV_XML(CatalogPlugin):
|
|||||||
item = u'%s' % re.sub(r'[\D]', '', item)
|
item = u'%s' % re.sub(r'[\D]', '', item)
|
||||||
elif field in ['pubdate', 'timestamp']:
|
elif field in ['pubdate', 'timestamp']:
|
||||||
item = isoformat(item)
|
item = isoformat(item)
|
||||||
|
elif field == 'comments':
|
||||||
|
item = item.replace(u'\r\n',u' ')
|
||||||
|
item = item.replace(u'\n',u' ')
|
||||||
|
|
||||||
if x < len(fields) - 1:
|
outstr.append(u'"%s"' % unicode(item).replace('"','""'))
|
||||||
if item is not None:
|
|
||||||
outstr += u'"%s",' % unicode(item).replace('"','""')
|
outfile.write(u','.join(outstr) + u'\n')
|
||||||
else:
|
|
||||||
outstr += '"",'
|
|
||||||
else:
|
|
||||||
if item is not None:
|
|
||||||
outstr += u'"%s"\n' % unicode(item).replace('"','""')
|
|
||||||
else:
|
|
||||||
outstr += '""\n'
|
|
||||||
outfile.write(outstr.encode('utf-8'))
|
|
||||||
outfile.close()
|
outfile.close()
|
||||||
|
|
||||||
elif self.fmt == 'xml':
|
elif self.fmt == 'xml':
|
||||||
@ -181,6 +186,329 @@ class CSV_XML(CatalogPlugin):
|
|||||||
f.write(etree.tostring(root, encoding='utf-8',
|
f.write(etree.tostring(root, encoding='utf-8',
|
||||||
xml_declaration=True, pretty_print=True))
|
xml_declaration=True, pretty_print=True))
|
||||||
|
|
||||||
|
class BIBTEX(CatalogPlugin):
|
||||||
|
'BIBTEX catalog generator'
|
||||||
|
|
||||||
|
Option = namedtuple('Option', 'option, default, dest, action, help')
|
||||||
|
|
||||||
|
name = 'Catalog_BIBTEX'
|
||||||
|
description = 'BIBTEX catalog generator'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
author = 'Sengian'
|
||||||
|
version = (1, 0, 0)
|
||||||
|
file_types = set(['bib'])
|
||||||
|
|
||||||
|
cli_options = [
|
||||||
|
Option('--fields',
|
||||||
|
default = 'all',
|
||||||
|
dest = 'fields',
|
||||||
|
action = None,
|
||||||
|
help = _('The fields to output when cataloging books in the '
|
||||||
|
'database. Should be a comma-separated list of fields.\n'
|
||||||
|
'Available fields: %s.\n'
|
||||||
|
"Default: '%%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")%', '.join(FIELDS)),
|
||||||
|
|
||||||
|
Option('--sort-by',
|
||||||
|
default = 'id',
|
||||||
|
dest = 'sort_by',
|
||||||
|
action = None,
|
||||||
|
help = _('Output field to sort on.\n'
|
||||||
|
'Available fields: author_sort, id, rating, size, timestamp, title.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--create-citation',
|
||||||
|
default = 'True',
|
||||||
|
dest = 'impcit',
|
||||||
|
action = None,
|
||||||
|
help = _('Create a citation for BibTeX entries.\n'
|
||||||
|
'Boolean value: True, False\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--citation-template',
|
||||||
|
default = '{authors}{id}',
|
||||||
|
dest = 'bib_cit',
|
||||||
|
action = None,
|
||||||
|
help = _('The template for citation creation from database fields.\n'
|
||||||
|
' Should be a template with {} enclosed fields.\n'
|
||||||
|
'Available fields: %s.\n'
|
||||||
|
"Default: '%%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")%', '.join(TEMPLATE_ALLOWED_FIELDS)),
|
||||||
|
|
||||||
|
Option('--choose-encoding',
|
||||||
|
default = 'utf8',
|
||||||
|
dest = 'bibfile_enc',
|
||||||
|
action = None,
|
||||||
|
help = _('BibTeX file encoding output.\n'
|
||||||
|
'Available types: utf8, cp1252, ascii.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--choose-encoding-configuration',
|
||||||
|
default = 'strict',
|
||||||
|
dest = 'bibfile_enctag',
|
||||||
|
action = None,
|
||||||
|
help = _('BibTeX file encoding flag.\n'
|
||||||
|
'Available types: strict, replace, ignore, backslashreplace.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--entry-type',
|
||||||
|
default = 'book',
|
||||||
|
dest = 'bib_entry',
|
||||||
|
action = None,
|
||||||
|
help = _('Entry type for BibTeX catalog.\n'
|
||||||
|
'Available types: book, misc, mixed.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format"))]
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||||
|
|
||||||
|
from types import StringType, UnicodeType
|
||||||
|
|
||||||
|
from calibre.library.save_to_disk import preprocess_template
|
||||||
|
#Bibtex functions
|
||||||
|
from calibre.utils.bibtex import bibtex_author_format, utf8ToBibtex, ValidateCitationKey
|
||||||
|
|
||||||
|
def create_bibtex_entry(entry, fields, mode, template_citation,
|
||||||
|
asccii_bibtex = True, citation_bibtex = True):
|
||||||
|
|
||||||
|
#Bibtex doesn't like UTF-8 but keep unicode until writing
|
||||||
|
#Define starting chain or if book valid strict and not book return a Fail string
|
||||||
|
|
||||||
|
bibtex_entry = []
|
||||||
|
if mode != "misc" and check_entry_book_valid(entry) :
|
||||||
|
bibtex_entry.append(u'@book{')
|
||||||
|
elif mode != "book" :
|
||||||
|
bibtex_entry.append(u'@misc{')
|
||||||
|
else :
|
||||||
|
#case strict book
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if citation_bibtex :
|
||||||
|
# Citation tag
|
||||||
|
bibtex_entry.append(make_bibtex_citation(entry, template_citation, asccii_bibtex))
|
||||||
|
bibtex_entry = [u' '.join(bibtex_entry)]
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
item = entry[field]
|
||||||
|
#check if the field should be included (none or empty)
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if len(item) == 0 :
|
||||||
|
continue
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if field == 'authors' :
|
||||||
|
bibtex_entry.append(u'author = "%s"' % bibtex_author_format(item))
|
||||||
|
|
||||||
|
elif field in ['title', 'publisher', 'cover', 'uuid',
|
||||||
|
'author_sort', 'series'] :
|
||||||
|
bibtex_entry.append(u'%s = "%s"' % (field, utf8ToBibtex(item, asccii_bibtex)))
|
||||||
|
|
||||||
|
elif field == 'id' :
|
||||||
|
bibtex_entry.append(u'calibreid = "%s"' % int(item))
|
||||||
|
|
||||||
|
elif field == 'rating' :
|
||||||
|
bibtex_entry.append(u'rating = "%s"' % int(item))
|
||||||
|
|
||||||
|
elif field == 'size' :
|
||||||
|
bibtex_entry.append(u'%s = "%s octets"' % (field, int(item)))
|
||||||
|
|
||||||
|
elif field == 'tags' :
|
||||||
|
#A list to flatten
|
||||||
|
bibtex_entry.append(u'tags = "%s"' % utf8ToBibtex(u', '.join(item), asccii_bibtex))
|
||||||
|
|
||||||
|
elif field == 'comments' :
|
||||||
|
#\n removal
|
||||||
|
item = item.replace(u'\r\n',u' ')
|
||||||
|
item = item.replace(u'\n',u' ')
|
||||||
|
bibtex_entry.append(u'note = "%s"' % utf8ToBibtex(item, asccii_bibtex))
|
||||||
|
|
||||||
|
elif field == 'isbn' :
|
||||||
|
# Could be 9, 10 or 13 digits
|
||||||
|
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item))
|
||||||
|
|
||||||
|
elif field == 'formats' :
|
||||||
|
item = u', '.join([format.rpartition('.')[2].lower() for format in item])
|
||||||
|
bibtex_entry.append(u'formats = "%s"' % item)
|
||||||
|
|
||||||
|
elif field == 'series_index' :
|
||||||
|
bibtex_entry.append(u'volume = "%s"' % int(item))
|
||||||
|
|
||||||
|
elif field == 'timestamp' :
|
||||||
|
bibtex_entry.append(u'timestamp = "%s"' % isoformat(item).partition('T')[0])
|
||||||
|
|
||||||
|
elif field == 'pubdate' :
|
||||||
|
bibtex_entry.append(u'year = "%s"' % item.year)
|
||||||
|
bibtex_entry.append(u'month = "%s"' % utf8ToBibtex(strftime("%b", item),
|
||||||
|
asccii_bibtex))
|
||||||
|
|
||||||
|
bibtex_entry = u',\n '.join(bibtex_entry)
|
||||||
|
bibtex_entry += u' }\n\n'
|
||||||
|
|
||||||
|
return bibtex_entry
|
||||||
|
|
||||||
|
def check_entry_book_valid(entry):
|
||||||
|
#Check that the required fields are ok for a book entry
|
||||||
|
for field in ['title', 'authors', 'publisher'] :
|
||||||
|
if entry[field] is None or len(entry[field]) == 0 :
|
||||||
|
return False
|
||||||
|
if entry['pubdate'] is None :
|
||||||
|
return False
|
||||||
|
else :
|
||||||
|
return True
|
||||||
|
|
||||||
|
def make_bibtex_citation(entry, template_citation, asccii_bibtex):
|
||||||
|
|
||||||
|
#define a function to replace the template entry by its value
|
||||||
|
def tpl_replace(objtplname) :
|
||||||
|
|
||||||
|
tpl_field = re.sub(u'[\{\}]', u'', objtplname.group())
|
||||||
|
|
||||||
|
if tpl_field in TEMPLATE_ALLOWED_FIELDS :
|
||||||
|
if tpl_field in ['pubdate', 'timestamp'] :
|
||||||
|
tpl_field = isoformat(entry[tpl_field]).partition('T')[0]
|
||||||
|
elif tpl_field in ['tags', 'authors'] :
|
||||||
|
tpl_field =entry[tpl_field][0]
|
||||||
|
elif tpl_field in ['id', 'series_index'] :
|
||||||
|
tpl_field = str(entry[tpl_field])
|
||||||
|
else :
|
||||||
|
tpl_field = entry[tpl_field]
|
||||||
|
return tpl_field
|
||||||
|
else:
|
||||||
|
return u''
|
||||||
|
|
||||||
|
if len(template_citation) >0 :
|
||||||
|
tpl_citation = utf8ToBibtex(ValidateCitationKey(re.sub(u'\{[^{}]*\}',
|
||||||
|
tpl_replace, template_citation)), asccii_bibtex)
|
||||||
|
|
||||||
|
if len(tpl_citation) >0 :
|
||||||
|
return tpl_citation
|
||||||
|
|
||||||
|
if len(entry["isbn"]) > 0 :
|
||||||
|
template_citation = u'%s' % re.sub(u'[\D]',u'', entry["isbn"])
|
||||||
|
|
||||||
|
else :
|
||||||
|
template_citation = u'%s' % str(entry["id"])
|
||||||
|
|
||||||
|
if asccii_bibtex :
|
||||||
|
return ValidateCitationKey(template_citation.encode('ascii', 'replace'))
|
||||||
|
else :
|
||||||
|
return ValidateCitationKey(template_citation)
|
||||||
|
|
||||||
|
self.fmt = path_to_output.rpartition('.')[2]
|
||||||
|
self.notification = notification
|
||||||
|
|
||||||
|
# Combobox options
|
||||||
|
bibfile_enc = ['utf8', 'cp1252', 'ascii']
|
||||||
|
bibfile_enctag = ['strict', 'replace', 'ignore', 'backslashreplace']
|
||||||
|
bib_entry = ['mixed', 'misc', 'book']
|
||||||
|
|
||||||
|
# Needed beacause CLI return str vs int by widget
|
||||||
|
try:
|
||||||
|
bibfile_enc = bibfile_enc[opts.bibfile_enc]
|
||||||
|
bibfile_enctag = bibfile_enctag[opts.bibfile_enctag]
|
||||||
|
bib_entry = bib_entry[opts.bib_entry]
|
||||||
|
except:
|
||||||
|
if opts.bibfile_enc in bibfile_enc :
|
||||||
|
bibfile_enc = opts.bibfile_enc
|
||||||
|
else :
|
||||||
|
log(" WARNING: incorrect --choose-encoding flag, revert to default")
|
||||||
|
bibfile_enc = bibfile_enc[0]
|
||||||
|
if opts.bibfile_enctag in bibfile_enctag :
|
||||||
|
bibfile_enctag = opts.bibfile_enctag
|
||||||
|
else :
|
||||||
|
log(" WARNING: incorrect --choose-encoding-configuration flag, revert to default")
|
||||||
|
bibfile_enctag = bibfile_enctag[0]
|
||||||
|
if opts.bib_entry in bib_entry :
|
||||||
|
bib_entry = opts.bib_entry
|
||||||
|
else :
|
||||||
|
log(" WARNING: incorrect --entry-type flag, revert to default")
|
||||||
|
bib_entry = bib_entry[0]
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
opts_dict = vars(opts)
|
||||||
|
log("%s(): Generating %s" % (self.name,self.fmt))
|
||||||
|
if opts_dict['search_text']:
|
||||||
|
log(" --search='%s'" % opts_dict['search_text'])
|
||||||
|
|
||||||
|
if opts_dict['ids']:
|
||||||
|
log(" Book count: %d" % len(opts_dict['ids']))
|
||||||
|
if opts_dict['search_text']:
|
||||||
|
log(" (--search ignored when a subset of the database is specified)")
|
||||||
|
|
||||||
|
if opts_dict['fields']:
|
||||||
|
if opts_dict['fields'] == 'all':
|
||||||
|
log(" Fields: %s" % ', '.join(FIELDS[1:]))
|
||||||
|
else:
|
||||||
|
log(" Fields: %s" % opts_dict['fields'])
|
||||||
|
|
||||||
|
log(" Output file will be encoded in %s with %s flag" % (bibfile_enc, bibfile_enctag))
|
||||||
|
|
||||||
|
log(" BibTeX entry type is %s with a citation like '%s' flag" % (bib_entry, opts_dict['bib_cit']))
|
||||||
|
|
||||||
|
# If a list of ids are provided, don't use search_text
|
||||||
|
if opts.ids:
|
||||||
|
opts.search_text = None
|
||||||
|
|
||||||
|
data = self.search_sort_db(db, opts)
|
||||||
|
|
||||||
|
if not len(data):
|
||||||
|
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||||
|
|
||||||
|
# Get the requested output fields as a list
|
||||||
|
fields = self.get_output_fields(opts)
|
||||||
|
|
||||||
|
if not len(data):
|
||||||
|
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||||
|
|
||||||
|
#Entries writing after Bibtex formating (or not)
|
||||||
|
if bibfile_enc != 'ascii' :
|
||||||
|
asccii_bibtex = False
|
||||||
|
else :
|
||||||
|
asccii_bibtex = True
|
||||||
|
|
||||||
|
#Check and go to default in case of bad CLI
|
||||||
|
if isinstance(opts.impcit, (StringType, UnicodeType)) :
|
||||||
|
if opts.impcit == 'False' :
|
||||||
|
citation_bibtex= False
|
||||||
|
elif opts.impcit == 'True' :
|
||||||
|
citation_bibtex= True
|
||||||
|
else :
|
||||||
|
log(" WARNING: incorrect --create-citation, revert to default")
|
||||||
|
citation_bibtex= True
|
||||||
|
else :
|
||||||
|
citation_bibtex= opts.impcit
|
||||||
|
|
||||||
|
template_citation = preprocess_template(opts.bib_cit)
|
||||||
|
|
||||||
|
#Open output and write entries
|
||||||
|
outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)
|
||||||
|
|
||||||
|
#File header
|
||||||
|
nb_entries = len(data)
|
||||||
|
|
||||||
|
#check in book strict if all is ok else throw a warning into log
|
||||||
|
if bib_entry == 'book' :
|
||||||
|
nb_books = len(filter(check_entry_book_valid, data))
|
||||||
|
if nb_books < nb_entries :
|
||||||
|
log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries))
|
||||||
|
nb_entries = nb_books
|
||||||
|
|
||||||
|
outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
|
||||||
|
outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n'
|
||||||
|
% (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding)))
|
||||||
|
|
||||||
|
for entry in data:
|
||||||
|
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
||||||
|
asccii_bibtex, citation_bibtex))
|
||||||
|
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
class EPUB_MOBI(CatalogPlugin):
|
class EPUB_MOBI(CatalogPlugin):
|
||||||
'ePub catalog generator'
|
'ePub catalog generator'
|
||||||
|
@ -116,6 +116,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
# missing functions
|
# missing functions
|
||||||
self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter')
|
self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def exists_at(cls, path):
|
||||||
|
return path and os.path.exists(os.path.join(path, 'metadata.db'))
|
||||||
|
|
||||||
def __init__(self, library_path, row_factory=False):
|
def __init__(self, library_path, row_factory=False):
|
||||||
self.field_metadata = FieldMetadata()
|
self.field_metadata = FieldMetadata()
|
||||||
if not os.path.exists(library_path):
|
if not os.path.exists(library_path):
|
||||||
|
2539
src/calibre/utils/bibtex.py
Normal file
2539
src/calibre/utils/bibtex.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user