Pull from trunk

This commit is contained in:
Kovid Goyal 2010-08-13 13:37:10 -06:00
commit 3a06faa17d
50 changed files with 39701 additions and 37363 deletions

View File

@ -4,6 +4,64 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.14
date: 2010-08-13
new features:
- title: "Device drivers for the: Teclast K-5, Samsung SNE-60 and Samsung i7500"
- title: "When showing cover browser in a separate window, remember the last used window size"
- title: "Add keyboard shortcuts to show/hide the Tag Browser, Book details and Cover Browser panels. Hover your mouse over the buttons that toggle them to see the shortcuts."
- title: "Calibre library: When the case of title or author is changed, automatically rename the folders to reflect the new case, even on case insensitive filesystems"
- title: "Metadata download: If downloaded title or author is all upper case, automatically fix the case"
- title: "Add method to add books by ISBN. Click the arrow next to Add Books to add from a list of ISBNs."
tickets: [6327]
- title: "Allow editing of tweaks via Preferences->Advanced"
- title: "Add button to manage authors dialog to automatically reset all author sort values"
bug fixes:
- title: "Fix regression in 0.7.13 that broke changing libraries"
- title: "MOBI Output: When processing an input document that specifies non-existant files in the OPF guide, don't crash."
tickets: [6490]
- title: "E-book viewer: When opening consecutive documents in the same viewer, show the correct title in the titlebar"
- title: "Set screen size to 540x718 in Kobo output profile"
- title: "Dont allow calibredb to create custom columns with invalid labels."
tickets: [6487]
- title: "Fix preference to 'search as you type' not working"
- title: "iTunes driver: Fixed bug in PDF file name searching after adding to iTunes database (Windows only)"
- title: "Displaying HTML comments: Do not start a new paragraph at the period in words like Ph.D"
tickets: [6462]
- title: "Respect restriction in effect when refreshing book list"
- title: "Fix drives being reversed for softrooted nook"
new recipes:
- title: "Yahoo News, Skeptical Enquirer and Skeptic"
author: Startson17
- title: "Bolivian newspapers"
author: Darko Miletic
improved recipes:
- Esquire
- Big Oven
- NSPM
- version: 0.7.13 - version: 0.7.13
date: 2010-08-06 date: 2010-08-06

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

View File

@ -1,4 +1,5 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re
class BigOven(BasicNewsRecipe): class BigOven(BasicNewsRecipe):
title = 'BigOven' title = 'BigOven'
@ -22,43 +23,42 @@ class BigOven(BasicNewsRecipe):
, 'publisher' : publisher , 'publisher' : publisher
, 'language' : language , 'language' : language
} }
def get_browser(self): def get_browser(self):
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None: if self.username is not None and self.password is not None:
br.open('http://www.bigoven.com/') br.open('http://www.bigoven.com/account/login?ReturnUrl=/')
br.select_form(name='form1') br.select_form(nr=1)
br['TopMenu_bo1$email'] = self.username br['Email'] = self.username
br['TopMenu_bo1$password'] = self.password br['Password'] = self.password
br.submit() br.submit()
return br return br
remove_attributes = ['style', 'font'] remove_attributes = ['style', 'font']
keep_only_tags = [dict(name='h1') remove_tags = [dict(name='div', attrs={'class':['ppy-caption']})
,dict(name='div', attrs={'class':'img'}) ,dict(name='div', attrs={'id':['float_corner']})
,dict(name='div', attrs={'id':'intro'}) ]
]
remove_tags = [dict(name='div', attrs={'style':["overflow: visible;"]})
,dict(name='div', attrs={'class':['ctas']})
#,dict(name='a', attrs={'class':['edit']})
,dict(name='p', attrs={'class':['byline']})
]
feeds = [(u'4 & 5 Star Rated Recipes', u'http://feeds.feedburner.com/Bigovencom-RecipeRaves?format=xml')]
def preprocess_html(self, soup): def preprocess_html(self, soup):
for tag in soup.findAll(name='a', attrs={'class':['edit']}):
tag.parent.extract()
for tag in soup.findAll(name='a', attrs={'class':['deflink']}): for tag in soup.findAll(name='a', attrs={'class':['deflink']}):
tag.replaceWith(tag.string) tag.replaceWith(tag.string)
for tag in soup.findAll(name='a', text=re.compile(r'.*View Metric.*', re.DOTALL)):
tag.parent.parent.extract()
for tag in soup.findAll(name='a', text=re.compile(r'.*Add my own photo.*', re.DOTALL)):
tag.parent.parent.extract()
for tag in soup.findAll(name='div', attrs={'class':['container']}):
if tag.find(name='h1'):
continue
if tag.find(name='h2', text=re.compile(r'.*Ingredients.*', re.DOTALL)):
print 'tag found Ingred h2'
continue
if tag.find(name='h2', text=re.compile(r'Preparation.*', re.DOTALL)):
print 'tag found Prep h2'
continue
tag.extract()
return soup return soup
extra_css = ''' feeds = [(u'4 & 5 Star Rated Recipes', u'http://feeds.feedburner.com/Bigovencom-RecipeRaves?format=xml')]
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:medium;}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''

View File

@ -9,9 +9,9 @@ from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class ElPaisImpresa(BasicNewsRecipe): class ElPaisImpresa(BasicNewsRecipe):
title = 'El País - edicion impresa' title = u'El Pa\xeds - edicion impresa'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'el periodico global en Español' description = u'el periodico global en Espa\xf1ol'
publisher = 'EDICIONES EL PAIS, S.L.' publisher = 'EDICIONES EL PAIS, S.L.'
category = 'news, politics,Spain,actualidad,noticias,informacion,videos,fotografias,audios,graficos,nacional,internacional,deportes,economia,tecnologia,cultura,gente,television,sociedad,opinion,blogs,foros,chats,encuestas,entrevistas,participacion' category = 'news, politics,Spain,actualidad,noticias,informacion,videos,fotografias,audios,graficos,nacional,internacional,deportes,economia,tecnologia,cultura,gente,television,sociedad,opinion,blogs,foros,chats,encuestas,entrevistas,participacion'
no_stylesheets = True no_stylesheets = True
@ -32,10 +32,10 @@ class ElPaisImpresa(BasicNewsRecipe):
feeds = [ feeds = [
(u'Internacional' , index + u'internacional/' ) (u'Internacional' , index + u'internacional/' )
,(u'España' , index + u'espana/' ) ,(u'Espa\xf1a' , index + u'espana/' )
,(u'Economia' , index + u'economia/' ) ,(u'Economia' , index + u'economia/' )
,(u'Opinion' , index + u'opinion/' ) ,(u'Opinion' , index + u'opinion/' )
,(u'Viñetas' , index + u'vineta/' ) ,(u'Vi\xf1etas' , index + u'vineta/' )
,(u'Sociedad' , index + u'sociedad/' ) ,(u'Sociedad' , index + u'sociedad/' )
,(u'Cultura' , index + u'cultura/' ) ,(u'Cultura' , index + u'cultura/' )
,(u'Tendencias' , index + u'tendencias/' ) ,(u'Tendencias' , index + u'tendencias/' )

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
www.esquire.com www.esquire.com
@ -9,7 +7,6 @@ www.esquire.com
from calibre import strftime from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class Esquire(BasicNewsRecipe): class Esquire(BasicNewsRecipe):
title = 'Esquire' title = 'Esquire'
@ -22,23 +19,21 @@ class Esquire(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
encoding = 'cp1250' encoding = 'cp1250'
use_embedded_content = False use_embedded_content = False
language = 'en' language = 'en'
publication_type = 'magazine'
lang = 'en-US' masthead_url = 'http://www.esquire.com/cm/shared/site_images/print_this/esquire_logo.gif'
cover_url = strftime('http://www.esquire.com/cm/esquire/cover-images/%Y_') + strftime('%m').strip('0') + '.jpg'
conversion_options = { conversion_options = {
'comment' : description 'comment' : description
, 'tags' : category , 'tags' : category
, 'publisher' : publisher , 'publisher' : publisher
, 'language' : lang , 'language' : language
, 'pretty_print' : True
} }
keep_only_tags = [dict(name='div', attrs={'id':'content'})] keep_only_tags = [dict(name='div', attrs={'id':['article_header','article_content']})]
remove_tags = [dict(name=['object','link','embed','iframe','base'])]
remove_tags = [dict(name=['object','link','embed','iframe'])] remove_attributes = ['width','height']
feeds = [ feeds = [
(u'Style' , u'http://www.esquire.com/style/rss/' ) (u'Style' , u'http://www.esquire.com/style/rss/' )
,(u'Women' , u'http://www.esquire.com/women/rss/' ) ,(u'Women' , u'http://www.esquire.com/women/rss/' )
@ -47,17 +42,7 @@ class Esquire(BasicNewsRecipe):
,(u'Frontpage', u'http://www.esquire.com/rss/' ) ,(u'Frontpage', u'http://www.esquire.com/rss/' )
] ]
def print_version(self, url):
rest = url.rpartition('?')[0]
article = rest.rpartition('/')[2]
return 'http://www.esquire.com/print-this/' + article
def preprocess_html(self, soup): def preprocess_html(self, soup):
soup.html['xml:lang'] = self.lang
soup.html['lang'] = self.lang
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
soup.head.insert(0,mlang)
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return soup return soup

View File

@ -0,0 +1,46 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
financialexpress.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class FE_India(BasicNewsRecipe):
title = 'The Financial Express'
__author__ = 'Darko Miletic'
description = 'Financial news from India'
publisher = 'The Indian Express Limited'
category = 'news, politics, finances, India'
oldest_article = 30
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
language = 'en_IN'
remove_empty_feeds = True
masthead_url = 'http://static.expressindia.com/frontend/fe/images/fe_logo.jpg'
publication_type = 'magazine'
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } '
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(attrs={'class':'txt'})]
remove_attributes = ['width','height']
feeds = [(u'Articles', u'http://www.expressindia.com/syndications/fe.xml')]
def print_version(self, url):
article_raw = url.rpartition('/')[0]
article_id = article_raw.rpartition('/')[2]
return 'http://www.financialexpress.com/printer/news/' + article_id + '/'
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -1,13 +1,10 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
economictimes.indiatimes.com economictimes.indiatimes.com
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class TheEconomicTimes(BasicNewsRecipe): class TheEconomicTimes(BasicNewsRecipe):
title = 'The Economic Times India' title = 'The Economic Times India'
@ -21,18 +18,21 @@ class TheEconomicTimes(BasicNewsRecipe):
use_embedded_content = False use_embedded_content = False
simultaneous_downloads = 1 simultaneous_downloads = 1
encoding = 'utf-8' encoding = 'utf-8'
lang = 'en-IN' language = 'en_IN'
language = 'en_IN' publication_type = 'newspaper'
masthead_url = 'http://economictimes.indiatimes.com/photo/2676871.cms'
extra_css = """ body{font-family: Arial,Helvetica,sans-serif}
html2lrf_options = [ .heading1{font-size: xx-large; font-weight: bold} """
'--comment', description
, '--category', category conversion_options = {
, '--publisher', publisher 'comment' : description
, '--ignore-tables' , 'tags' : category
] , 'publisher' : publisher
, 'language' : language
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True' }
keep_only_tags = [dict(attrs={'class':['heading1','headingnext','Normal']})]
remove_tags = [dict(name=['object','link','embed','iframe','base','table','meta'])]
feeds = [(u'All articles', u'http://economictimes.indiatimes.com/rssfeedsdefault.cms')] feeds = [(u'All articles', u'http://economictimes.indiatimes.com/rssfeedsdefault.cms')]
@ -47,11 +47,6 @@ class TheEconomicTimes(BasicNewsRecipe):
return rurl return rurl
def preprocess_html(self, soup): def preprocess_html(self, soup):
soup.html['xml:lang'] = self.lang for item in soup.findAll(style=True):
soup.html['lang'] = self.lang del item['style']
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")])
soup.head.insert(0,mlang)
soup.head.insert(1,mcharset)
return self.adeify_images(soup) return self.adeify_images(soup)

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.13' __version__ = '0.7.14'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -426,7 +426,7 @@ class KoboReaderOutput(OutputProfile):
description = _('This profile is intended for the Kobo Reader.') description = _('This profile is intended for the Kobo Reader.')
screen_size = (590, 775) screen_size = (540, 718)
comic_screen_size = (540, 718) comic_screen_size = (540, 718)
dpi = 168.451 dpi = 168.451
fbase = 12 fbase = 12

View File

@ -33,7 +33,9 @@ class ANDROID(USBMS):
# Samsung # Samsung
0x04e8 : { 0x681d : [0x0222, 0x0400], 0x04e8 : { 0x681d : [0x0222, 0x0400],
0x681c : [0x0222, 0x0224, 0x0400]}, 0x681c : [0x0222, 0x0224, 0x0400],
0x6640 : [0x0100],
},
# Acer # Acer
0x502 : { 0x3203 : [0x0100]}, 0x502 : { 0x3203 : [0x0100]},
@ -54,9 +56,9 @@ class ANDROID(USBMS):
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX'] 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959'] 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD',
'FILE-STOR_GADGET', 'SGH-T959'] 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID']
OSX_MAIN_MEM = 'HTC Android Phone Media' OSX_MAIN_MEM = 'HTC Android Phone Media'

View File

@ -56,6 +56,8 @@ class WinPNPScanner(object):
def drive_order(self, pnp_id): def drive_order(self, pnp_id):
order = 0 order = 0
match = re.search(r'REV_.*?&(\d+)#', pnp_id) match = re.search(r'REV_.*?&(\d+)#', pnp_id)
if match is None:
match = re.search(r'REV_.*?&(\d+)', pnp_id)
if match is not None: if match is not None:
order = int(match.group(1)) order = int(match.group(1))
return order return order

View File

@ -20,20 +20,19 @@ class SNE(USBMS):
# Ordered list of supported formats # Ordered list of supported formats
# Be sure these have an entry in calibre.devices.mime # Be sure these have an entry in calibre.devices.mime
FORMATS = ['epub', 'txt'] FORMATS = ['epub', 'pdf', 'txt']
VENDOR_ID = [0x04e8] VENDOR_ID = [0x04e8]
PRODUCT_ID = [0x2051] PRODUCT_ID = [0x2051, 0x2053]
BCD = [0x0323] BCD = [0x0323]
VENDOR_NAME = '' VENDOR_NAME = 'SAMSUNG'
#WINDOWS_MAIN_MEM = 'MASS_STORAGE' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'SNE-60'
#WINDOWS_CARD_A_MEM = 'MASS_STORAGE'
MAIN_MEMORY_VOLUME_LABEL = 'SNE Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'SNE Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'SNE Storage Card' STORAGE_CARD_VOLUME_LABEL = 'SNE Storage Card'
EBOOK_DIR_MAIN = 'Book' EBOOK_DIR_MAIN = 'Books'
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True

View File

@ -6,9 +6,9 @@ from calibre.devices.usbms.driver import USBMS
class TECLAST_K3(USBMS): class TECLAST_K3(USBMS):
name = 'Teclast K3 Device Interface' name = 'Teclast K3/K5 Device Interface'
gui_name = 'K3' gui_name = 'K3/K5'
description = _('Communicate with the Teclast K3 reader.') description = _('Communicate with the Teclast K3/K5 reader.')
author = 'Kovid Goyal' author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux'] supported_platforms = ['windows', 'osx', 'linux']
@ -17,11 +17,10 @@ class TECLAST_K3(USBMS):
VENDOR_ID = [0x071b] VENDOR_ID = [0x071b]
PRODUCT_ID = [0x3203] PRODUCT_ID = [0x3203]
BCD = [0x0000] BCD = [0x0000, 0x0100]
VENDOR_NAME = 'TECLAST' VENDOR_NAME = 'TECLAST'
WINDOWS_MAIN_MEM = 'DIGITAL_PLAYER' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5']
WINDOWS_CARD_A_MEM = 'DIGITAL_PLAYER'
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'

View File

@ -185,7 +185,7 @@ class Serializer(object):
buffer.write('<guide>') buffer.write('<guide>')
for ref in self.oeb.guide.values(): for ref in self.oeb.guide.values():
path = urldefrag(ref.href)[0] path = urldefrag(ref.href)[0]
if hrefs[path].media_type not in OEB_DOCS: if path not in hrefs or hrefs[path].media_type not in OEB_DOCS:
continue continue
buffer.write('<reference type="') buffer.write('<reference type="')

View File

@ -10,10 +10,10 @@ Module to implement the Cover Flow feature
import sys, os, time import sys, os, time
from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \ from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \
QStackedLayout, QLabel QStackedLayout, QLabel, QByteArray, pyqtSignal
from calibre import plugins from calibre import plugins
from calibre.gui2 import config, available_height, available_width from calibre.gui2 import config, available_height, available_width, gprefs
pictureflow, pictureflowerror = plugins['pictureflow'] pictureflow, pictureflowerror = plugins['pictureflow']
@ -107,6 +107,28 @@ else:
DatabaseImages = None DatabaseImages = None
FileSystemImages = None FileSystemImages = None
class CBDialog(QDialog):
closed = pyqtSignal()
def __init__(self, parent, cover_flow):
QDialog.__init__(self, parent)
self._layout = QStackedLayout()
self.setLayout(self._layout)
self.setWindowTitle(_('Browse by covers'))
self.layout().addWidget(cover_flow)
geom = gprefs.get('cover_browser_dialog_geometry', bytearray(''))
geom = QByteArray(geom)
if not self.restoreGeometry(geom):
h, w = available_height()-60, int(available_width()/1.5)
self.resize(w, h)
def closeEvent(self, *args):
geom = bytearray(self.saveGeometry())
gprefs['cover_browser_dialog_geometry'] = geom
self.closed.emit()
class CoverFlowMixin(object): class CoverFlowMixin(object):
def __init__(self): def __init__(self):
@ -129,6 +151,8 @@ class CoverFlowMixin(object):
self.cover_flow.setWordWrap(True) self.cover_flow.setWordWrap(True)
if config['separate_cover_flow']: if config['separate_cover_flow']:
self.cb_splitter.button.clicked.connect(self.toggle_cover_browser) self.cb_splitter.button.clicked.connect(self.toggle_cover_browser)
self.cb_splitter.button.set_state_to_show()
self.cb_splitter.action_toggle.triggered.connect(self.toggle_cover_browser)
if CoverFlow is not None: if CoverFlow is not None:
self.cover_flow.stop.connect(self.hide_cover_browser) self.cover_flow.stop.connect(self.hide_cover_browser)
else: else:
@ -137,7 +161,7 @@ class CoverFlowMixin(object):
self.cover_flow.stop.connect(self.cb_splitter.hide_side_pane) self.cover_flow.stop.connect(self.cb_splitter.hide_side_pane)
self.cb_splitter.button.toggled.connect(self.cover_browser_toggled) self.cb_splitter.button.toggled.connect(self.cover_browser_toggled)
def toggle_cover_browser(self): def toggle_cover_browser(self, *args):
cbd = getattr(self, 'cb_dialog', None) cbd = getattr(self, 'cb_dialog', None)
if cbd is not None: if cbd is not None:
self.hide_cover_browser() self.hide_cover_browser()
@ -171,25 +195,26 @@ class CoverFlowMixin(object):
def show_cover_browser(self): def show_cover_browser(self):
d = QDialog(self) d = CBDialog(self, self.cover_flow)
ah, aw = available_height(), available_width()
d.resize(int(aw/1.5), ah-60)
d._layout = QStackedLayout()
d.setLayout(d._layout)
d.setWindowTitle(_('Browse by covers'))
d.layout().addWidget(self.cover_flow)
self.cover_flow.setVisible(True) self.cover_flow.setVisible(True)
self.cover_flow.setFocus(Qt.OtherFocusReason) self.cover_flow.setFocus(Qt.OtherFocusReason)
d.show() d.show()
self.cb_splitter.button.set_state_to_hide() self.cb_splitter.button.set_state_to_hide()
d.finished.connect(self.cb_splitter.button.set_state_to_show) d.closed.connect(self.cover_browser_closed)
self.cb_dialog = d self.cb_dialog = d
self.cb_splitter.button.set_state_to_hide()
def hide_cover_browser(self): def cover_browser_closed(self, *args):
self.cb_dialog = None
self.cb_splitter.button.set_state_to_show()
def hide_cover_browser(self, *args):
cbd = getattr(self, 'cb_dialog', None) cbd = getattr(self, 'cb_dialog', None)
if cbd is not None: if cbd is not None:
cbd.accept() cbd.accept()
self.cb_dialog = None self.cb_dialog = None
self.cb_splitter.button.set_state_to_show()
def sync_cf_to_listview(self, current, previous): def sync_cf_to_listview(self, current, previous):
if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \

View File

@ -515,15 +515,15 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.reset_confirmation_button.clicked.connect(self.reset_confirmation) self.reset_confirmation_button.clicked.connect(self.reset_confirmation)
deft, curt = read_raw_tweaks() deft, curt = read_raw_tweaks()
self.current_tweaks.setPlainText(curt) self.current_tweaks.setPlainText(curt.decode('utf-8'))
self.default_tweaks.setPlainText(deft) self.default_tweaks.setPlainText(deft.decode('utf-8'))
self.restore_tweaks_to_default_button.clicked.connect(self.restore_tweaks_to_default) self.restore_tweaks_to_default_button.clicked.connect(self.restore_tweaks_to_default)
self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category)) self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category))
def restore_tweaks_to_default(self, *args): def restore_tweaks_to_default(self, *args):
deft, curt = read_raw_tweaks() deft, curt = read_raw_tweaks()
self.current_tweaks.setPlainText(deft) self.current_tweaks.setPlainText(deft.decode('utf-8'))
def reset_confirmation(self): def reset_confirmation(self):
@ -698,8 +698,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.input_order.setCurrentRow(idx-1) self.input_order.setCurrentRow(idx-1)
def set_tweaks(self): def set_tweaks(self):
raw = unicode(self.current_tweaks.toPlainText()) raw = unicode(self.current_tweaks.toPlainText()).encode('utf-8')
raw = re.sub(r'(?m)^#.*fileencoding.*', '# ', raw)
try: try:
exec raw exec raw
except: except:

View File

@ -100,11 +100,11 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
def accept(self): def accept(self):
col = unicode(self.column_name_box.text()).lower() col = unicode(self.column_name_box.text())
if not col: if not col:
return self.simple_error('', _('No lookup name was provided')) return self.simple_error('', _('No lookup name was provided'))
if re.match('^\w*$', col) is None or not col[0].isalpha(): if re.match('^\w*$', col) is None or not col[0].isalpha() or col.lower() != col:
return self.simple_error('', _('The label must contain only letters, digits and underscores, and start with a letter')) return self.simple_error('', _('The lookup name must contain only lower case letters, digits and underscores, and start with a letter'))
col_heading = unicode(self.column_heading_box.text()) col_heading = unicode(self.column_heading_box.text())
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype'] col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
if col_type == '*text': if col_type == '*text':
@ -130,8 +130,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
bad_head = True bad_head = True
if bad_head: if bad_head:
return self.simple_error('', _('The heading %s is already used')%col_heading) return self.simple_error('', _('The heading %s is already used')%col_heading)
if ':' in col or ' ' in col or col.lower() != col:
return self.simple_error('', _('The lookup name must be lower case and cannot contain ":"s or spaces'))
date_format = {} date_format = {}
if col_type == 'datetime': if col_type == 'datetime':

View File

@ -166,7 +166,8 @@ class LibraryWidget(Splitter): # {{{
I('cover_flow.svg'), I('cover_flow.svg'),
orientation=orientation, parent=parent, orientation=orientation, parent=parent,
connect_button=not config['separate_cover_flow'], connect_button=not config['separate_cover_flow'],
side_index=idx, initial_side_size=size, initial_show=False) side_index=idx, initial_side_size=size, initial_show=False,
shortcut=_('Shift+Alt+B'))
parent.library_view = BooksView(parent) parent.library_view = BooksView(parent)
parent.library_view.setObjectName('library_view') parent.library_view.setObjectName('library_view')
self.addWidget(parent.library_view) self.addWidget(parent.library_view)
@ -181,7 +182,8 @@ class Stack(QStackedWidget): # {{{
self.tb_widget = TagBrowserWidget(parent) self.tb_widget = TagBrowserWidget(parent)
parent.tb_splitter = Splitter('tag_browser_splitter', parent.tb_splitter = Splitter('tag_browser_splitter',
_('Tag Browser'), I('tags.svg'), _('Tag Browser'), I('tags.svg'),
parent=parent, side_index=0, initial_side_size=200) parent=parent, side_index=0, initial_side_size=200,
shortcut=_('Shift+Alt+T'))
parent.tb_splitter.addWidget(self.tb_widget) parent.tb_splitter.addWidget(self.tb_widget)
parent.tb_splitter.addWidget(parent.cb_splitter) parent.tb_splitter.addWidget(parent.cb_splitter)
parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False) parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False)
@ -274,7 +276,8 @@ class LayoutMixin(object): # {{{
self.stack = Stack(self) self.stack = Stack(self)
self.bd_splitter = Splitter('book_details_splitter', self.bd_splitter = Splitter('book_details_splitter',
_('Book Details'), I('book.svg'), _('Book Details'), I('book.svg'),
orientation=Qt.Vertical, parent=self, side_index=1) orientation=Qt.Vertical, parent=self, side_index=1,
shortcut=_('Alt+D'))
self.bd_splitter.addWidget(self.stack) self.bd_splitter.addWidget(self.stack)
self.bd_splitter.addWidget(self.book_details) self.bd_splitter.addWidget(self.book_details)
self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False) self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False)
@ -283,7 +286,8 @@ class LayoutMixin(object): # {{{
else: # wide {{{ else: # wide {{{
self.bd_splitter = Splitter('book_details_splitter', self.bd_splitter = Splitter('book_details_splitter',
_('Book Details'), I('book.svg'), initial_side_size=200, _('Book Details'), I('book.svg'), initial_side_size=200,
orientation=Qt.Horizontal, parent=self, side_index=1) orientation=Qt.Horizontal, parent=self, side_index=1,
shortcut=_('Shift+Alt+D'))
self.stack = Stack(self) self.stack = Stack(self)
self.bd_splitter.addWidget(self.stack) self.bd_splitter.addWidget(self.stack)
self.book_details = BookDetails(True, self) self.book_details = BookDetails(True, self)

View File

@ -166,6 +166,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def __init__(self, pathtoebook=None, debug_javascript=False): def __init__(self, pathtoebook=None, debug_javascript=False):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
self.setupUi(self) self.setupUi(self)
self.base_window_title = unicode(self.windowTitle())
self.iterator = None self.iterator = None
self.current_page = None self.current_page = None
self.pending_search = None self.pending_search = None
@ -602,7 +603,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.toc_model = TOC(self.iterator.toc) self.toc_model = TOC(self.iterator.toc)
self.toc.setModel(self.toc_model) self.toc.setModel(self.toc_model)
self.current_title = title self.current_title = title
self.setWindowTitle(unicode(self.windowTitle())+' - '+title) self.setWindowTitle(self.base_window_title+' - '+title)
self.pos.setMaximum(sum(self.iterator.pages)) self.pos.setMaximum(sum(self.iterator.pages))
self.pos.setSuffix(' / %d'%sum(self.iterator.pages)) self.pos.setSuffix(' / %d'%sum(self.iterator.pages))
self.vertical_scrollbar.setMinimum(100) self.vertical_scrollbar.setMinimum(100)

View File

@ -5,7 +5,7 @@ Miscellaneous widgets used in the GUI
''' '''
import re, os, traceback import re, os, traceback
from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, \ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \
QListWidgetItem, QTextCharFormat, QApplication, \ QListWidgetItem, QTextCharFormat, QApplication, \
QSyntaxHighlighter, QCursor, QColor, QWidget, \ QSyntaxHighlighter, QCursor, QColor, QWidget, \
QPixmap, QSplitterHandle, QToolButton, \ QPixmap, QSplitterHandle, QToolButton, \
@ -855,7 +855,7 @@ class SplitterHandle(QSplitterHandle):
class LayoutButton(QToolButton): class LayoutButton(QToolButton):
def __init__(self, icon, text, splitter, parent=None): def __init__(self, icon, text, splitter, parent=None, shortcut=None):
QToolButton.__init__(self, parent) QToolButton.__init__(self, parent)
self.label = text self.label = text
self.setIcon(QIcon(icon)) self.setIcon(QIcon(icon))
@ -864,18 +864,21 @@ class LayoutButton(QToolButton):
self.splitter = splitter self.splitter = splitter
splitter.state_changed.connect(self.update_state) splitter.state_changed.connect(self.update_state)
self.setCursor(Qt.PointingHandCursor) self.setCursor(Qt.PointingHandCursor)
self.shortcut = ''
if shortcut:
self.shortcut = shortcut
def set_state_to_show(self, *args): def set_state_to_show(self, *args):
self.setChecked(False) self.setChecked(False)
label =_('Show') label =_('Show')
self.setText(label + ' ' + self.label) self.setText(label + ' ' + self.label + ' ' + self.shortcut)
self.setToolTip(self.text()) self.setToolTip(self.text())
self.setStatusTip(self.text()) self.setStatusTip(self.text())
def set_state_to_hide(self, *args): def set_state_to_hide(self, *args):
self.setChecked(True) self.setChecked(True)
label = _('Hide') label = _('Hide')
self.setText(label + ' ' + self.label) self.setText(label + ' ' + self.label+ ' ' + self.shortcut)
self.setToolTip(self.text()) self.setToolTip(self.text())
self.setStatusTip(self.text()) self.setStatusTip(self.text())
@ -891,7 +894,7 @@ class Splitter(QSplitter):
def __init__(self, name, label, icon, initial_show=True, def __init__(self, name, label, icon, initial_show=True,
initial_side_size=120, connect_button=True, initial_side_size=120, connect_button=True,
orientation=Qt.Horizontal, side_index=0, parent=None): orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None):
QSplitter.__init__(self, parent) QSplitter.__init__(self, parent)
self.resize_timer = QTimer(self) self.resize_timer = QTimer(self)
self.resize_timer.setSingleShot(True) self.resize_timer.setSingleShot(True)
@ -906,10 +909,21 @@ class Splitter(QSplitter):
self.initial_side_size = initial_side_size self.initial_side_size = initial_side_size
self.initial_show = initial_show self.initial_show = initial_show
self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection) self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection)
self.button = LayoutButton(icon, label, self) self.button = LayoutButton(icon, label, self, shortcut=shortcut)
if connect_button: if connect_button:
self.button.clicked.connect(self.double_clicked) self.button.clicked.connect(self.double_clicked)
if shortcut is not None:
self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label,
self)
self.action_toggle.triggered.connect(self.toggle_triggered)
self.action_toggle.setShortcut(shortcut)
if parent is not None:
parent.addAction(self.action_toggle)
def toggle_triggered(self, *args):
self.toggle_side_pane()
def createHandle(self): def createHandle(self):
return SplitterHandle(self.orientation(), self) return SplitterHandle(self.orientation(), self)

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import json import json, re
from functools import partial from functools import partial
from math import floor from math import floor
@ -430,6 +430,10 @@ class CustomColumns(object):
def create_custom_column(self, label, name, datatype, is_multiple, def create_custom_column(self, label, name, datatype, is_multiple,
editable=True, display={}): editable=True, display={}):
if not label:
raise ValueError(_('No label was provided'))
if re.match('^\w*$', label) is None or not label[0].isalpha() or label.lower() != label:
raise ValueError(_('The label must contain only lower case letters, digits and underscores, and start with a letter'))
if datatype not in self.CUSTOM_DATA_TYPES: if datatype not in self.CUSTOM_DATA_TYPES:
raise ValueError('%r is not a supported data type'%datatype) raise ValueError('%r is not a supported data type'%datatype)
normalized = datatype not in ('datetime', 'comments', 'int', 'bool', normalized = datatype not in ('datetime', 'comments', 'int', 'bool',

View File

@ -419,7 +419,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for format in formats: for format in formats:
# Get data as string (can't use file as source and target files may be the same) # Get data as string (can't use file as source and target files may be the same)
f = self.format(id, format, index_is_id=True, as_file=False) f = self.format(id, format, index_is_id=True, as_file=False)
if not f: if not f:
continue continue
stream = cStringIO.StringIO(f) stream = cStringIO.StringIO(f)
self.add_format(id, format, stream, index_is_id=True, path=tpath) self.add_format(id, format, stream, index_is_id=True, path=tpath)
@ -430,9 +430,31 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if current_path and os.path.exists(spath): if current_path and os.path.exists(spath):
if self.normpath(spath) != self.normpath(tpath): if self.normpath(spath) != self.normpath(tpath):
self.rmtree(spath, permanent=True) self.rmtree(spath, permanent=True)
parent = os.path.dirname(spath) parent = os.path.dirname(spath)
if len(os.listdir(parent)) == 0: if len(os.listdir(parent)) == 0:
self.rmtree(parent, permanent=True) self.rmtree(parent, permanent=True)
curpath = self.library_path
c1, c2 = current_path.split('/'), path.split('/')
if not self.is_case_sensitive and len(c1) == len(c2):
# On case-insensitive systems, title and author renames that only
# change case don't cause any changes to the directories in the file
# system. This can lead to having the directory names not match the
# title/author, which leads to trouble when libraries are copied to
# a case-sensitive system. The following code fixes this by checking
# each segment. If they are different because of case, then rename
# the segment to some temp file name, then rename it back to the
# correct name. Note that the code above correctly handles files in
# the directories, so no need to do them here.
for oldseg, newseg in zip(c1, c2):
if oldseg.lower() == newseg.lower() and oldseg != newseg:
while True:
# need a temp name in the current segment for renames
tempname = os.path.join(curpath, 'TEMP.%f'%time.time())
if not os.path.exists(tempname):
break
os.rename(os.path.join(curpath, oldseg), tempname)
os.rename(tempname, os.path.join(curpath, newseg))
curpath = os.path.join(curpath, newseg)
def add_listener(self, listener): def add_listener(self, listener):
''' '''

View File

@ -30,8 +30,8 @@ Environment variables
Tweaks Tweaks
------------ ------------
Tweaks are small changes that you can specify to control various aspects of |app|'s behavior. You specify them by editing the 2tweaks.py file in the config directory. Tweaks are small changes that you can specify to control various aspects of |app|'s behavior. You can change them by going to Preferences->Advanced->Tweaks.
The default tweaks.py file is reproduced below The default values for the tweaks are reproduced below
.. literalinclude:: ../../../resources/default_tweaks.py .. literalinclude:: ../../../resources/default_tweaks.py

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff