Merge branch 'kovidgoyal/master'
@ -20,6 +20,44 @@
|
|||||||
# new recipes:
|
# new recipes:
|
||||||
# - title:
|
# - title:
|
||||||
|
|
||||||
|
- version: 1.10.0
|
||||||
|
date: 2013-11-08
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "Conversion: Treat .docm the same as .docx files, ignoring any macros in the file."
|
||||||
|
tickets: [1247565]
|
||||||
|
|
||||||
|
- title: "EPUB Output: Auto convert CMYK images to RGB. Works around the inability of Adobe Digital Editions to display CMYK images."
|
||||||
|
tickets: [1246710]
|
||||||
|
|
||||||
|
- title: "Quickview: Add a checkbox to lock the quickview window so that it does not change while moving around in the main book list"
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Fix number of marked books shown in the drop down menu of the Mark Books action not being updated when marked books are deleted (as opposed to being unmarked)."
|
||||||
|
tickets: [1248506]
|
||||||
|
|
||||||
|
- title: "Book list: Preserve the current column when using Ctrl+Home or Ctrl+End shortcuts"
|
||||||
|
|
||||||
|
- title: "Booklist: Fix using Page Up/Down keys moving book list by one row too many."
|
||||||
|
tickets: [1248109]
|
||||||
|
|
||||||
|
- title: "Metadata download: Do not auto trim downloaded covers as trimming can sometimes have undesirable effects."
|
||||||
|
|
||||||
|
- title: "Template language: Fix zero valued series indices not formatting correctly"
|
||||||
|
tickets: [1247348]
|
||||||
|
|
||||||
|
- title: "Fix a regression in 1.9 that broke bibtex catalog creation"
|
||||||
|
|
||||||
|
- title: "Quickview: Auto-close the quickview window when changing libraries. You have to open it again in the new library."
|
||||||
|
|
||||||
|
- title: "Linux binary build: Update the bundled version of zlib to fix library compatibility on some newer linux distros that have libpng16 compiled to require newer zlib versions"
|
||||||
|
tickets: [1247315]
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- New York Review of Books
|
||||||
|
- Various Polish news sources
|
||||||
|
|
||||||
|
|
||||||
- version: 1.9.0
|
- version: 1.9.0
|
||||||
date: 2013-11-01
|
date: 2013-11-01
|
||||||
|
|
||||||
|
1566
imgsrc/view-refresh.svg
Normal file
After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.7 KiB |
96
recipes/argnoticias.recipe
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2013, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
|
||||||
|
'''
|
||||||
|
www.argnoticias.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
import time
|
||||||
|
from calibre import strftime
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class ArgNoticias(BasicNewsRecipe):
|
||||||
|
title = 'ARG Noticias'
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'Ultimas noticias de Argentina'
|
||||||
|
publisher = 'ARG Noticias'
|
||||||
|
category = 'news, politics, Argentina'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'utf-8'
|
||||||
|
use_embedded_content = False
|
||||||
|
masthead_url = 'http://www.argnoticias.com/images/arg-logo-footer.png'
|
||||||
|
language = 'es_AR'
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
INDEX = 'http://www.argnoticias.com'
|
||||||
|
extra_css = ''
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher': publisher
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [dict(name='div', attrs={'class':['itemHeader','itemBody','itemAuthorBlock']})]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name=['object','link','base','iframe']),
|
||||||
|
dict(name='div', attrs={'class':['b2jsocial_parent','itemSocialSharing']})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Politica' , u'http://www.argnoticias.com/index.php/politica' )
|
||||||
|
,(u'Economia' , u'http://www.argnoticias.com/index.php/economia' )
|
||||||
|
,(u'Sociedad' , u'http://www.argnoticias.com/index.php/sociedad' )
|
||||||
|
,(u'Mundo' , u'http://www.argnoticias.com/index.php/mundo' )
|
||||||
|
,(u'Deportes' , u'http://www.argnoticias.com/index.php/deportes' )
|
||||||
|
,(u'Espectaculos', u'http://www.argnoticias.com/index.php/espectaculos')
|
||||||
|
,(u'Tendencias' , u'http://www.argnoticias.com/index.php/tendencias' )
|
||||||
|
]
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
totalfeeds = []
|
||||||
|
lfeeds = self.get_feeds()
|
||||||
|
checker = []
|
||||||
|
for feedobj in lfeeds:
|
||||||
|
feedtitle, feedurl = feedobj
|
||||||
|
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
|
||||||
|
articles = []
|
||||||
|
soup = self.index_to_soup(feedurl)
|
||||||
|
for item in soup.findAll('div', attrs={'class':'Nota'}):
|
||||||
|
atag = item.find('a', attrs={'class':'moduleItemTitle'})
|
||||||
|
ptag = item.find('div', attrs={'class':'moduleItemIntrotext'})
|
||||||
|
url = self.INDEX + atag['href']
|
||||||
|
title = self.tag_to_string(atag)
|
||||||
|
description = self.tag_to_string(ptag)
|
||||||
|
date = strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime())
|
||||||
|
if url not in checker:
|
||||||
|
checker.append(url)
|
||||||
|
articles.append({
|
||||||
|
'title' :title
|
||||||
|
,'date' :date
|
||||||
|
,'url' :url
|
||||||
|
,'description':description
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in soup.findAll('li'):
|
||||||
|
atag = item.find('a', attrs={'class':'moduleItemTitle'})
|
||||||
|
if atag:
|
||||||
|
ptag = item.find('div', attrs={'class':'moduleItemIntrotext'})
|
||||||
|
url = self.INDEX + atag['href']
|
||||||
|
title = self.tag_to_string(atag)
|
||||||
|
description = self.tag_to_string(ptag)
|
||||||
|
date = strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime())
|
||||||
|
if url not in checker:
|
||||||
|
checker.append(url)
|
||||||
|
articles.append({
|
||||||
|
'title' :title
|
||||||
|
,'date' :date
|
||||||
|
,'url' :url
|
||||||
|
,'description':description
|
||||||
|
})
|
||||||
|
totalfeeds.append((feedtitle, articles))
|
||||||
|
return totalfeeds
|
BIN
recipes/icons/argnoticias.png
Normal file
After Width: | Height: | Size: 171 B |
@ -6,7 +6,6 @@ __copyright__ = '''2010, matek09, matek09@gmail.com
|
|||||||
Modified 2012, Artur Stachecki <artur.stachecki@gmail.com>'''
|
Modified 2012, Artur Stachecki <artur.stachecki@gmail.com>'''
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
import re
|
|
||||||
|
|
||||||
class Wprost(BasicNewsRecipe):
|
class Wprost(BasicNewsRecipe):
|
||||||
title = u'Wprost (RSS)'
|
title = u'Wprost (RSS)'
|
||||||
|
BIN
resources/images/auto-reload.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
resources/images/embed-fonts.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
resources/images/smarten-punctuation.png
Normal file
After Width: | Height: | Size: 776 B |
BIN
resources/images/subset-fonts.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
resources/images/view-refresh.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
@ -4,7 +4,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__ = u'calibre'
|
__appname__ = u'calibre'
|
||||||
numeric_version = (1, 9, 0)
|
numeric_version = (1, 10, 0)
|
||||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
|
@ -119,65 +119,68 @@ def update_metadata(ebook, new_opf):
|
|||||||
stream.truncate()
|
stream.truncate()
|
||||||
stream.write(opf.render())
|
stream.write(opf.render())
|
||||||
|
|
||||||
def polish(file_map, opts, log, report):
|
def polish_one(ebook, opts, report):
|
||||||
rt = lambda x: report('\n### ' + x)
|
rt = lambda x: report('\n### ' + x)
|
||||||
|
jacket = None
|
||||||
|
|
||||||
|
if opts.subset or opts.embed:
|
||||||
|
stats = StatsCollector(ebook, do_embed=opts.embed)
|
||||||
|
|
||||||
|
if opts.opf:
|
||||||
|
rt(_('Updating metadata'))
|
||||||
|
update_metadata(ebook, opts.opf)
|
||||||
|
jacket = find_existing_jacket(ebook)
|
||||||
|
if jacket is not None:
|
||||||
|
replace_jacket(ebook, jacket)
|
||||||
|
report(_('Updated metadata jacket'))
|
||||||
|
report(_('Metadata updated\n'))
|
||||||
|
|
||||||
|
if opts.cover:
|
||||||
|
rt(_('Setting cover'))
|
||||||
|
set_cover(ebook, opts.cover, report)
|
||||||
|
report('')
|
||||||
|
|
||||||
|
if opts.jacket:
|
||||||
|
rt(_('Inserting metadata jacket'))
|
||||||
|
if jacket is None:
|
||||||
|
if add_or_replace_jacket(ebook):
|
||||||
|
report(_('Existing metadata jacket replaced'))
|
||||||
|
else:
|
||||||
|
report(_('Metadata jacket inserted'))
|
||||||
|
else:
|
||||||
|
report(_('Existing metadata jacket replaced'))
|
||||||
|
report('')
|
||||||
|
|
||||||
|
if opts.remove_jacket:
|
||||||
|
rt(_('Removing metadata jacket'))
|
||||||
|
if remove_jacket(ebook):
|
||||||
|
report(_('Metadata jacket removed'))
|
||||||
|
else:
|
||||||
|
report(_('No metadata jacket found'))
|
||||||
|
report('')
|
||||||
|
|
||||||
|
if opts.smarten_punctuation:
|
||||||
|
rt(_('Smartening punctuation'))
|
||||||
|
smarten_punctuation(ebook, report)
|
||||||
|
report('')
|
||||||
|
|
||||||
|
if opts.embed:
|
||||||
|
rt(_('Embedding referenced fonts'))
|
||||||
|
embed_all_fonts(ebook, stats, report)
|
||||||
|
report('')
|
||||||
|
|
||||||
|
if opts.subset:
|
||||||
|
rt(_('Subsetting embedded fonts'))
|
||||||
|
subset_all_fonts(ebook, stats.font_stats, report)
|
||||||
|
report('')
|
||||||
|
|
||||||
|
|
||||||
|
def polish(file_map, opts, log, report):
|
||||||
st = time.time()
|
st = time.time()
|
||||||
for inbook, outbook in file_map.iteritems():
|
for inbook, outbook in file_map.iteritems():
|
||||||
report(_('## Polishing: %s')%(inbook.rpartition('.')[-1].upper()))
|
report(_('## Polishing: %s')%(inbook.rpartition('.')[-1].upper()))
|
||||||
ebook = get_container(inbook, log)
|
ebook = get_container(inbook, log)
|
||||||
jacket = None
|
polish_one(ebook, opts, report)
|
||||||
|
|
||||||
if opts.subset or opts.embed:
|
|
||||||
stats = StatsCollector(ebook, do_embed=opts.embed)
|
|
||||||
|
|
||||||
if opts.opf:
|
|
||||||
rt(_('Updating metadata'))
|
|
||||||
update_metadata(ebook, opts.opf)
|
|
||||||
jacket = find_existing_jacket(ebook)
|
|
||||||
if jacket is not None:
|
|
||||||
replace_jacket(ebook, jacket)
|
|
||||||
report(_('Updated metadata jacket'))
|
|
||||||
report(_('Metadata updated\n'))
|
|
||||||
|
|
||||||
if opts.cover:
|
|
||||||
rt(_('Setting cover'))
|
|
||||||
set_cover(ebook, opts.cover, report)
|
|
||||||
report('')
|
|
||||||
|
|
||||||
if opts.jacket:
|
|
||||||
rt(_('Inserting metadata jacket'))
|
|
||||||
if jacket is None:
|
|
||||||
if add_or_replace_jacket(ebook):
|
|
||||||
report(_('Existing metadata jacket replaced'))
|
|
||||||
else:
|
|
||||||
report(_('Metadata jacket inserted'))
|
|
||||||
else:
|
|
||||||
report(_('Existing metadata jacket replaced'))
|
|
||||||
report('')
|
|
||||||
|
|
||||||
if opts.remove_jacket:
|
|
||||||
rt(_('Removing metadata jacket'))
|
|
||||||
if remove_jacket(ebook):
|
|
||||||
report(_('Metadata jacket removed'))
|
|
||||||
else:
|
|
||||||
report(_('No metadata jacket found'))
|
|
||||||
report('')
|
|
||||||
|
|
||||||
if opts.smarten_punctuation:
|
|
||||||
rt(_('Smartening punctuation'))
|
|
||||||
smarten_punctuation(ebook, report)
|
|
||||||
report('')
|
|
||||||
|
|
||||||
if opts.embed:
|
|
||||||
rt(_('Embedding referenced fonts'))
|
|
||||||
embed_all_fonts(ebook, stats, report)
|
|
||||||
report('')
|
|
||||||
|
|
||||||
if opts.subset:
|
|
||||||
rt(_('Subsetting embedded fonts'))
|
|
||||||
subset_all_fonts(ebook, stats.font_stats, report)
|
|
||||||
report('')
|
|
||||||
|
|
||||||
ebook.commit(outbook)
|
ebook.commit(outbook)
|
||||||
report('-'*70)
|
report('-'*70)
|
||||||
report(_('Polishing took: %.1f seconds')%(time.time()-st))
|
report(_('Polishing took: %.1f seconds')%(time.time()-st))
|
||||||
@ -204,6 +207,15 @@ def gui_polish(data):
|
|||||||
log(msg)
|
log(msg)
|
||||||
return '\n\n'.join(report)
|
return '\n\n'.join(report)
|
||||||
|
|
||||||
|
def tweak_polish(container, actions):
|
||||||
|
opts = ALL_OPTS.copy()
|
||||||
|
opts.update(actions)
|
||||||
|
O = namedtuple('Options', ' '.join(ALL_OPTS.iterkeys()))
|
||||||
|
opts = O(**opts)
|
||||||
|
report = []
|
||||||
|
polish_one(container, opts, report.append)
|
||||||
|
return report
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from calibre.utils.config import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
USAGE = '%prog [options] input_file [output_file]\n\n' + re.sub(
|
USAGE = '%prog [options] input_file [output_file]\n\n' + re.sub(
|
||||||
|
@ -78,6 +78,7 @@ def replace_links(container, link_map, frag_map=lambda name, frag:frag, replace_
|
|||||||
|
|
||||||
def smarten_punctuation(container, report):
|
def smarten_punctuation(container, report):
|
||||||
from calibre.ebooks.conversion.preprocess import smarten_punctuation
|
from calibre.ebooks.conversion.preprocess import smarten_punctuation
|
||||||
|
smartened = False
|
||||||
for path in container.spine_items:
|
for path in container.spine_items:
|
||||||
name = container.abspath_to_name(path)
|
name = container.abspath_to_name(path)
|
||||||
changed = False
|
changed = False
|
||||||
@ -98,6 +99,9 @@ def smarten_punctuation(container, report):
|
|||||||
for m in root.xpath('descendant::*[local-name()="meta" and @http-equiv]'):
|
for m in root.xpath('descendant::*[local-name()="meta" and @http-equiv]'):
|
||||||
m.getparent().remove(m)
|
m.getparent().remove(m)
|
||||||
container.dirty(name)
|
container.dirty(name)
|
||||||
|
smartened = True
|
||||||
|
if not smartened:
|
||||||
|
report(_('No punctuation that could be smartened found'))
|
||||||
|
|
||||||
def rename_files(container, file_map):
|
def rename_files(container, file_map):
|
||||||
overlap = set(file_map).intersection(set(file_map.itervalues()))
|
overlap = set(file_map).intersection(set(file_map.itervalues()))
|
||||||
|
@ -178,7 +178,7 @@ CODEPOINTS = {
|
|||||||
'x06': [
|
'x06': [
|
||||||
'[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', ',', '[?]', '[?]', '[?]',
|
'[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', ',', '[?]', '[?]', '[?]',
|
||||||
'[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', ';', '[?]', '[?]', '[?]', '?',
|
'[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', ';', '[?]', '[?]', '[?]', '?',
|
||||||
'[?]', '', 'a', '\'', 'w\'', '', 'y\'', '', 'b', '@', 't', 'th', 'j', 'H', 'kh', 'd',
|
'[?]', '', 'a', '\'', 'w\'', '', 'y\'', 'a', 'b', '@', 't', 'th', 'j', 'H', 'kh', 'd',
|
||||||
'dh', 'r', 'z', 's', 'sh', 'S', 'D', 'T', 'Z', '`', 'G', '[?]', '[?]', '[?]', '[?]', '[?]',
|
'dh', 'r', 'z', 's', 'sh', 'S', 'D', 'T', 'Z', '`', 'G', '[?]', '[?]', '[?]', '[?]', '[?]',
|
||||||
'', 'f', 'q', 'k', 'l', 'm', 'n', 'h', 'w', '~', 'y', 'an', 'un', 'in', 'a', 'u',
|
'', 'f', 'q', 'k', 'l', 'm', 'n', 'h', 'w', '~', 'y', 'an', 'un', 'in', 'a', 'u',
|
||||||
'i', 'W', '', '', '\'', '\'', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]',
|
'i', 'W', '', '', '\'', '\'', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]', '[?]',
|
||||||
|
@ -876,6 +876,7 @@ class Application(QApplication):
|
|||||||
'MessageBoxWarning': u'dialog_warning.png',
|
'MessageBoxWarning': u'dialog_warning.png',
|
||||||
'MessageBoxCritical': u'dialog_error.png',
|
'MessageBoxCritical': u'dialog_error.png',
|
||||||
'MessageBoxQuestion': u'dialog_question.png',
|
'MessageBoxQuestion': u'dialog_question.png',
|
||||||
|
'BrowserReload': u'view-refresh.png',
|
||||||
# These two are used to calculate the sizes for the doc widget
|
# These two are used to calculate the sizes for the doc widget
|
||||||
# title bar buttons, therefore, they have to exist. The actual
|
# title bar buttons, therefore, they have to exist. The actual
|
||||||
# icon is not used.
|
# icon is not used.
|
||||||
|
@ -24,5 +24,12 @@ def set_current_container(container):
|
|||||||
global _current_container
|
global _current_container
|
||||||
_current_container = container
|
_current_container = container
|
||||||
|
|
||||||
actions = {}
|
class NonReplaceDict(dict):
|
||||||
editors = {}
|
|
||||||
|
def __setitem__(self, k, v):
|
||||||
|
if k in self:
|
||||||
|
raise ValueError('The key %s is already present' % k)
|
||||||
|
dict.__setitem__(self, k, v)
|
||||||
|
|
||||||
|
actions = NonReplaceDict()
|
||||||
|
editors = NonReplaceDict()
|
||||||
|
@ -11,12 +11,12 @@ from functools import partial
|
|||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt,
|
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt,
|
||||||
QDialogButtonBox, QIcon, QTimer, QPixmap)
|
QDialogButtonBox, QIcon, QTimer, QPixmap, QTextBrowser, QVBoxLayout)
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre.ebooks.oeb.base import urlnormalize
|
from calibre.ebooks.oeb.base import urlnormalize
|
||||||
from calibre.ebooks.oeb.polish.main import SUPPORTED
|
from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish
|
||||||
from calibre.ebooks.oeb.polish.container import get_container as _gc, clone_container, guess_type
|
from calibre.ebooks.oeb.polish.container import get_container as _gc, clone_container, guess_type
|
||||||
from calibre.ebooks.oeb.polish.replace import rename_files
|
from calibre.ebooks.oeb.polish.replace import rename_files
|
||||||
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog
|
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog
|
||||||
@ -25,6 +25,7 @@ from calibre.gui2.tweak_book import set_current_container, current_container, tp
|
|||||||
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
|
from calibre.gui2.tweak_book.undo import GlobalUndoHistory
|
||||||
from calibre.gui2.tweak_book.save import SaveManager
|
from calibre.gui2.tweak_book.save import SaveManager
|
||||||
from calibre.gui2.tweak_book.preview import parse_worker
|
from calibre.gui2.tweak_book.preview import parse_worker
|
||||||
|
from calibre.gui2.tweak_book.toc import TOCEditor
|
||||||
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
|
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
|
||||||
|
|
||||||
def get_container(*args, **kwargs):
|
def get_container(*args, **kwargs):
|
||||||
@ -86,6 +87,9 @@ class Boss(QObject):
|
|||||||
' Convert your book to one of these formats first.') % _(' and ').join(sorted(SUPPORTED)),
|
' Convert your book to one of these formats first.') % _(' and ').join(sorted(SUPPORTED)),
|
||||||
show=True)
|
show=True)
|
||||||
|
|
||||||
|
for name in tuple(editors):
|
||||||
|
self.close_editor(name)
|
||||||
|
self.gui.preview.clear()
|
||||||
self.container_count = -1
|
self.container_count = -1
|
||||||
if self.tdir:
|
if self.tdir:
|
||||||
shutil.rmtree(self.tdir, ignore_errors=True)
|
shutil.rmtree(self.tdir, ignore_errors=True)
|
||||||
@ -97,6 +101,7 @@ class Boss(QObject):
|
|||||||
return error_dialog(self.gui, _('Failed to open book'),
|
return error_dialog(self.gui, _('Failed to open book'),
|
||||||
_('Failed to open book, click Show details for more information.'),
|
_('Failed to open book, click Show details for more information.'),
|
||||||
det_msg=job.traceback, show=True)
|
det_msg=job.traceback, show=True)
|
||||||
|
parse_worker.clear()
|
||||||
container = job.result
|
container = job.result
|
||||||
set_current_container(container)
|
set_current_container(container)
|
||||||
self.current_metadata = self.gui.current_metadata = container.mi
|
self.current_metadata = self.gui.current_metadata = container.mi
|
||||||
@ -106,12 +111,20 @@ class Boss(QObject):
|
|||||||
self.gui.action_save.setEnabled(False)
|
self.gui.action_save.setEnabled(False)
|
||||||
self.update_global_history_actions()
|
self.update_global_history_actions()
|
||||||
|
|
||||||
|
def update_editors_from_container(self, container=None):
|
||||||
|
c = container or current_container()
|
||||||
|
for name, ed in tuple(editors.iteritems()):
|
||||||
|
if c.has_name(name):
|
||||||
|
ed.replace_data(c.raw_data(name))
|
||||||
|
else:
|
||||||
|
self.close_editor(name)
|
||||||
|
|
||||||
def apply_container_update_to_gui(self):
|
def apply_container_update_to_gui(self):
|
||||||
container = current_container()
|
container = current_container()
|
||||||
self.gui.file_list.build(container)
|
self.gui.file_list.build(container)
|
||||||
self.update_global_history_actions()
|
self.update_global_history_actions()
|
||||||
self.gui.action_save.setEnabled(True)
|
self.gui.action_save.setEnabled(True)
|
||||||
# TODO: Apply to other GUI elements
|
self.update_editors_from_container()
|
||||||
|
|
||||||
def delete_requested(self, spine_items, other_items):
|
def delete_requested(self, spine_items, other_items):
|
||||||
if not self.check_dirtied():
|
if not self.check_dirtied():
|
||||||
@ -126,7 +139,8 @@ class Boss(QObject):
|
|||||||
for name in list(spine_items) + list(other_items):
|
for name in list(spine_items) + list(other_items):
|
||||||
if name in editors:
|
if name in editors:
|
||||||
self.close_editor(name)
|
self.close_editor(name)
|
||||||
# TODO: Update other GUI elements
|
if not editors:
|
||||||
|
self.gui.preview.clear()
|
||||||
|
|
||||||
def reorder_spine(self, items):
|
def reorder_spine(self, items):
|
||||||
# TODO: If content.opf is dirty in an editor, abort, calling
|
# TODO: If content.opf is dirty in an editor, abort, calling
|
||||||
@ -138,6 +152,41 @@ class Boss(QObject):
|
|||||||
self.gui.file_list.build(current_container()) # needed as the linear flag may have changed on some items
|
self.gui.file_list.build(current_container()) # needed as the linear flag may have changed on some items
|
||||||
# TODO: If content.opf is open in an editor, reload it
|
# TODO: If content.opf is open in an editor, reload it
|
||||||
|
|
||||||
|
def edit_toc(self):
|
||||||
|
if not self.check_dirtied():
|
||||||
|
return
|
||||||
|
self.add_savepoint(_('Edit Table of Contents'))
|
||||||
|
d = TOCEditor(title=self.current_metadata.title, parent=self.gui)
|
||||||
|
if d.exec_() != d.Accepted:
|
||||||
|
self.rewind_savepoint()
|
||||||
|
return
|
||||||
|
self.update_editors_from_container()
|
||||||
|
|
||||||
|
def polish(self, action, name):
|
||||||
|
if not self.check_dirtied():
|
||||||
|
return
|
||||||
|
self.add_savepoint(name)
|
||||||
|
try:
|
||||||
|
report = tweak_polish(current_container(), {action:True})
|
||||||
|
except:
|
||||||
|
self.rewind_savepoint()
|
||||||
|
raise
|
||||||
|
self.apply_container_update_to_gui()
|
||||||
|
from calibre.ebooks.markdown import markdown
|
||||||
|
report = markdown('# %s\n\n'%self.current_metadata.title + '\n\n'.join(report), output_format='html4')
|
||||||
|
d = QDialog(self.gui)
|
||||||
|
d.l = QVBoxLayout()
|
||||||
|
d.setLayout(d.l)
|
||||||
|
d.e = QTextBrowser(d)
|
||||||
|
d.l.addWidget(d.e)
|
||||||
|
d.e.setHtml(report)
|
||||||
|
d.bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
|
d.l.addWidget(d.bb)
|
||||||
|
d.bb.rejected.connect(d.reject)
|
||||||
|
d.bb.accepted.connect(d.accept)
|
||||||
|
d.resize(600, 400)
|
||||||
|
d.exec_()
|
||||||
|
|
||||||
# Renaming {{{
|
# Renaming {{{
|
||||||
def rename_requested(self, oldname, newname):
|
def rename_requested(self, oldname, newname):
|
||||||
if not self.check_dirtied():
|
if not self.check_dirtied():
|
||||||
@ -172,7 +221,8 @@ class Boss(QObject):
|
|||||||
det_msg=job.traceback, show=True)
|
det_msg=job.traceback, show=True)
|
||||||
self.gui.file_list.build(current_container())
|
self.gui.file_list.build(current_container())
|
||||||
self.gui.action_save.setEnabled(True)
|
self.gui.action_save.setEnabled(True)
|
||||||
# TODO: Update the rest of the GUI
|
# TODO: Update the rest of the GUI. This means renaming open editors and
|
||||||
|
# then calling update_editors_from_container()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Global history {{{
|
# Global history {{{
|
||||||
@ -234,6 +284,7 @@ class Boss(QObject):
|
|||||||
editor = editors[name] = editor_from_syntax(syntax, self.gui.editor_tabs)
|
editor = editors[name] = editor_from_syntax(syntax, self.gui.editor_tabs)
|
||||||
editor.undo_redo_state_changed.connect(self.editor_undo_redo_state_changed)
|
editor.undo_redo_state_changed.connect(self.editor_undo_redo_state_changed)
|
||||||
editor.data_changed.connect(self.editor_data_changed)
|
editor.data_changed.connect(self.editor_data_changed)
|
||||||
|
editor.copy_available_state_changed.connect(self.editor_copy_available_state_changed)
|
||||||
c = current_container()
|
c = current_container()
|
||||||
with c.open(name) as f:
|
with c.open(name) as f:
|
||||||
editor.data = c.decode(f.read())
|
editor.data = c.decode(f.read())
|
||||||
@ -252,6 +303,7 @@ class Boss(QObject):
|
|||||||
_('Editing files of type %s is not supported' % mime), show=True)
|
_('Editing files of type %s is not supported' % mime), show=True)
|
||||||
self.edit_file(name, syntax)
|
self.edit_file(name, syntax)
|
||||||
|
|
||||||
|
# Editor basic controls {{{
|
||||||
def do_editor_undo(self):
|
def do_editor_undo(self):
|
||||||
ed = self.gui.central.current_editor
|
ed = self.gui.central.current_editor
|
||||||
if ed is not None:
|
if ed is not None:
|
||||||
@ -262,16 +314,35 @@ class Boss(QObject):
|
|||||||
if ed is not None:
|
if ed is not None:
|
||||||
ed.redo()
|
ed.redo()
|
||||||
|
|
||||||
|
def do_editor_copy(self):
|
||||||
|
ed = self.gui.central.current_editor
|
||||||
|
if ed is not None:
|
||||||
|
ed.copy()
|
||||||
|
|
||||||
|
def do_editor_cut(self):
|
||||||
|
ed = self.gui.central.current_editor
|
||||||
|
if ed is not None:
|
||||||
|
ed.cut()
|
||||||
|
|
||||||
|
def do_editor_paste(self):
|
||||||
|
ed = self.gui.central.current_editor
|
||||||
|
if ed is not None:
|
||||||
|
ed.paste()
|
||||||
|
|
||||||
def editor_data_changed(self, editor):
|
def editor_data_changed(self, editor):
|
||||||
self.gui.preview.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)
|
self.gui.preview.start_refresh_timer()
|
||||||
|
|
||||||
def editor_undo_redo_state_changed(self, *args):
|
def editor_undo_redo_state_changed(self, *args):
|
||||||
self.apply_current_editor_state(update_keymap=False)
|
self.apply_current_editor_state(update_keymap=False)
|
||||||
|
|
||||||
|
def editor_copy_available_state_changed(self, *args):
|
||||||
|
self.apply_current_editor_state(update_keymap=False)
|
||||||
|
|
||||||
def editor_modification_state_changed(self, is_modified):
|
def editor_modification_state_changed(self, is_modified):
|
||||||
self.apply_current_editor_state(update_keymap=False)
|
self.apply_current_editor_state(update_keymap=False)
|
||||||
if is_modified:
|
if is_modified:
|
||||||
actions['save-book'].setEnabled(True)
|
actions['save-book'].setEnabled(True)
|
||||||
|
# }}}
|
||||||
|
|
||||||
def apply_current_editor_state(self, update_keymap=True):
|
def apply_current_editor_state(self, update_keymap=True):
|
||||||
ed = self.gui.central.current_editor
|
ed = self.gui.central.current_editor
|
||||||
@ -279,6 +350,8 @@ class Boss(QObject):
|
|||||||
actions['editor-undo'].setEnabled(ed.undo_available)
|
actions['editor-undo'].setEnabled(ed.undo_available)
|
||||||
actions['editor-redo'].setEnabled(ed.redo_available)
|
actions['editor-redo'].setEnabled(ed.redo_available)
|
||||||
actions['editor-save'].setEnabled(ed.is_modified)
|
actions['editor-save'].setEnabled(ed.is_modified)
|
||||||
|
actions['editor-cut'].setEnabled(ed.copy_available)
|
||||||
|
actions['editor-copy'].setEnabled(ed.cut_available)
|
||||||
self.gui.keyboard.set_mode(ed.syntax)
|
self.gui.keyboard.set_mode(ed.syntax)
|
||||||
name = None
|
name = None
|
||||||
for n, x in editors.iteritems():
|
for n, x in editors.iteritems():
|
||||||
@ -308,6 +381,8 @@ class Boss(QObject):
|
|||||||
editor = editors.pop(name)
|
editor = editors.pop(name)
|
||||||
self.gui.central.close_editor(editor)
|
self.gui.central.close_editor(editor)
|
||||||
editor.break_cycles()
|
editor.break_cycles()
|
||||||
|
if not editors:
|
||||||
|
self.gui.preview.clear()
|
||||||
|
|
||||||
def do_editor_save(self):
|
def do_editor_save(self):
|
||||||
ed = self.gui.central.current_editor
|
ed = self.gui.central.current_editor
|
||||||
@ -397,7 +472,7 @@ class Boss(QObject):
|
|||||||
QApplication.instance().quit()
|
QApplication.instance().quit()
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.gui.preview.refresh_timer.stop()
|
self.gui.preview.stop_refresh_timer()
|
||||||
self.save_state()
|
self.save_state()
|
||||||
self.save_manager.shutdown()
|
self.save_manager.shutdown()
|
||||||
parse_worker.shutdown()
|
parse_worker.shutdown()
|
||||||
|
@ -14,7 +14,7 @@ from PyQt4.Qt import (
|
|||||||
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect)
|
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect)
|
||||||
|
|
||||||
from calibre.gui2.tweak_book import tprefs
|
from calibre.gui2.tweak_book import tprefs
|
||||||
from calibre.gui2.tweak_book.editor.themes import THEMES, DEFAULT_THEME, theme_color
|
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
|
||||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||||
from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter, XMLHighlighter
|
from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter, XMLHighlighter
|
||||||
from calibre.gui2.tweak_book.editor.syntax.css import CSSHighlighter
|
from calibre.gui2.tweak_book.editor.syntax.css import CSSHighlighter
|
||||||
@ -74,7 +74,7 @@ class TextEdit(QPlainTextEdit):
|
|||||||
self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
|
self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
|
||||||
theme = THEMES.get(prefs['editor_theme'], None)
|
theme = THEMES.get(prefs['editor_theme'], None)
|
||||||
if theme is None:
|
if theme is None:
|
||||||
theme = THEMES[DEFAULT_THEME]
|
theme = THEMES[default_theme()]
|
||||||
self.apply_theme(theme)
|
self.apply_theme(theme)
|
||||||
|
|
||||||
def apply_theme(self, theme):
|
def apply_theme(self, theme):
|
||||||
@ -114,6 +114,18 @@ class TextEdit(QPlainTextEdit):
|
|||||||
self.highlighter.setDocument(self.document())
|
self.highlighter.setDocument(self.document())
|
||||||
self.setPlainText(text)
|
self.setPlainText(text)
|
||||||
|
|
||||||
|
def replace_text(self, text):
|
||||||
|
c = self.textCursor()
|
||||||
|
pos = c.position()
|
||||||
|
c.beginEditBlock()
|
||||||
|
c.clearSelection()
|
||||||
|
c.select(c.Document)
|
||||||
|
c.insertText(text)
|
||||||
|
c.endEditBlock()
|
||||||
|
c.setPosition(min(pos, len(text)))
|
||||||
|
self.setTextCursor(c)
|
||||||
|
self.ensureCursorVisible()
|
||||||
|
|
||||||
# Line numbers and cursor line {{{
|
# Line numbers and cursor line {{{
|
||||||
def highlight_cursor_line(self):
|
def highlight_cursor_line(self):
|
||||||
sel = QTextEdit.ExtraSelection()
|
sel = QTextEdit.ExtraSelection()
|
||||||
|
@ -8,21 +8,67 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from PyQt4.Qt import (QColor, QTextCharFormat, QBrush, QFont)
|
from PyQt4.Qt import (QColor, QTextCharFormat, QBrush, QFont, QApplication, QPalette)
|
||||||
|
|
||||||
underline_styles = {'single', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'wave', 'spell'}
|
underline_styles = {'single', 'dash', 'dot', 'dash_dot', 'dash_dot_dot', 'wave', 'spell'}
|
||||||
|
|
||||||
DEFAULT_THEME = 'calibre-dark'
|
_default_theme = None
|
||||||
|
def default_theme():
|
||||||
|
global _default_theme
|
||||||
|
if _default_theme is None:
|
||||||
|
isdark = QApplication.instance().palette().color(QPalette.WindowText).lightness() > 128
|
||||||
|
_default_theme = 'wombat-dark' if isdark else 'pyte-light'
|
||||||
|
return _default_theme
|
||||||
|
|
||||||
|
# The solarized themes {{{
|
||||||
|
SLDX = {'base03':'1c1c1c', 'base02':'262626', 'base01':'585858', 'base00':'626262', 'base0':'808080', 'base1':'8a8a8a', 'base2':'e4e4e4', 'base3':'ffffd7', 'yellow':'af8700', 'orange':'d75f00', 'red':'d70000', 'magenta':'af005f', 'violet':'5f5faf', 'blue':'0087ff', 'cyan':'00afaf', 'green':'5f8700'} # noqa
|
||||||
|
SLD = {'base03':'002b36', 'base02':'073642', 'base01':'586e75', 'base00':'657b83', 'base0':'839496', 'base1':'93a1a1', 'base2':'eee8d5', 'base3':'fdf6e3', 'yellow':'b58900', 'orange':'cb4b16', 'red':'dc322f', 'magenta':'d33682', 'violet':'6c71c4', 'blue':'268bd2', 'cyan':'2aa198', 'green':'859900'} # noqa
|
||||||
|
m = {'base%d'%n:'base%02d'%n for n in xrange(1, 4)}
|
||||||
|
m.update({'base%02d'%n:'base%d'%n for n in xrange(1, 4)})
|
||||||
|
SLL = {m.get(k, k) : v for k, v in SLD.iteritems()}
|
||||||
|
SLLX = {m.get(k, k) : v for k, v in SLDX.iteritems()}
|
||||||
|
SOLARIZED = \
|
||||||
|
'''
|
||||||
|
CursorLine bg={base02}
|
||||||
|
CursorColumn bg={base02}
|
||||||
|
ColorColumn bg={base02}
|
||||||
|
MatchParen fg={red} bg={base01} bold
|
||||||
|
Pmenu fg={base0} bg={base02}
|
||||||
|
PmenuSel fg={base01} bg={base2}
|
||||||
|
|
||||||
|
Cursor fg={base03} bg={base0}
|
||||||
|
Normal fg={base0} bg={base03}
|
||||||
|
LineNr fg={base01} bg={base02}
|
||||||
|
LineNrC fg={magenta}
|
||||||
|
Visual fg={base01} bg={base03}
|
||||||
|
|
||||||
|
Comment fg={base01} italic
|
||||||
|
Todo fg={magenta} bold
|
||||||
|
String fg={cyan}
|
||||||
|
Constant fg={cyan}
|
||||||
|
Number fg={cyan}
|
||||||
|
PreProc fg={orange}
|
||||||
|
Identifier fg={blue}
|
||||||
|
Function fg={blue}
|
||||||
|
Type fg={yellow}
|
||||||
|
Statement fg={green} bold
|
||||||
|
Keyword fg={green}
|
||||||
|
Special fg={red}
|
||||||
|
|
||||||
|
Error us=wave uc={red}
|
||||||
|
Tooltip fg=black bg=ffffed
|
||||||
|
'''
|
||||||
|
# }}}
|
||||||
|
|
||||||
THEMES = {
|
THEMES = {
|
||||||
'calibre-dark': # {{{ Based on the wombat color scheme for vim
|
'wombat-dark': # {{{
|
||||||
'''
|
'''
|
||||||
CursorLine bg=2d2d2d
|
CursorLine bg={cursor_loc}
|
||||||
CursorColumn bg=2d2d2d
|
CursorColumn bg={cursor_loc}
|
||||||
ColorColumn bg=2d2d2d
|
ColorColumn bg={cursor_loc}
|
||||||
MatchParen fg=f6f3e8 bg=857b6f bold
|
MatchParen fg=f6f3e8 bg=857b6f bold
|
||||||
Pmenu fg=f6f3e8 bg=444444
|
Pmenu fg=f6f3e8 bg=444444
|
||||||
PmenuSel fg=yellow bg=cae682
|
PmenuSel fg=yellow bg={identifier}
|
||||||
Tooltip fg=black bg=ffffed
|
Tooltip fg=black bg=ffffed
|
||||||
|
|
||||||
Cursor bg=656565
|
Cursor bg=656565
|
||||||
@ -31,27 +77,79 @@ THEMES = {
|
|||||||
LineNrC fg=yellow
|
LineNrC fg=yellow
|
||||||
Visual fg=f6f3e8 bg=444444
|
Visual fg=f6f3e8 bg=444444
|
||||||
|
|
||||||
Comment fg=99968b
|
Comment fg={comment}
|
||||||
Todo fg=8f8f8f
|
Todo fg=8f8f8f
|
||||||
String fg=95e454
|
String fg={string}
|
||||||
Identifier fg=cae682
|
Constant fg={constant}
|
||||||
Function fg=cae682
|
Number fg={constant}
|
||||||
Type fg=cae682
|
PreProc fg={constant}
|
||||||
Statement fg=8ac6f2
|
Identifier fg={identifier}
|
||||||
Keyword fg=8ac6f2
|
Function fg={identifier}
|
||||||
Constant fg=e5786d
|
Type fg={identifier}
|
||||||
PreProc fg=e5786d
|
Statement fg={keyword}
|
||||||
Number fg=e5786d
|
Keyword fg={keyword}
|
||||||
Special fg=e7f6da
|
Special fg=e7f6da
|
||||||
Error us=wave uc=red
|
Error us=wave uc=red
|
||||||
|
|
||||||
''', # }}}
|
'''.format(
|
||||||
|
cursor_loc='2d2d2d',
|
||||||
|
identifier='cae682',
|
||||||
|
comment='99968b',
|
||||||
|
string='95e454',
|
||||||
|
keyword='8ac6f2',
|
||||||
|
constant='e5786d'), # }}}
|
||||||
|
|
||||||
|
'pyte-light': # {{{
|
||||||
|
'''
|
||||||
|
CursorLine bg={cursor_loc}
|
||||||
|
CursorColumn bg={cursor_loc}
|
||||||
|
ColorColumn bg={cursor_loc}
|
||||||
|
MatchParen fg=white bg=80a090 bold
|
||||||
|
Pmenu fg=white bg=808080
|
||||||
|
PmenuSel fg=white bg=808080
|
||||||
|
Tooltip fg=black bg=ffffed
|
||||||
|
|
||||||
|
Cursor fg=black bg=b0b4b8
|
||||||
|
Normal fg=404850 bg=f0f0f0
|
||||||
|
LineNr fg=white bg=8090a0
|
||||||
|
LineNrC fg=yellow
|
||||||
|
Visual fg=white bg=8090a0
|
||||||
|
|
||||||
|
Comment fg={comment} italic
|
||||||
|
Todo fg={comment} italic bold
|
||||||
|
String fg={string}
|
||||||
|
Constant fg={constant}
|
||||||
|
Number fg={constant}
|
||||||
|
PreProc fg={constant}
|
||||||
|
Identifier fg={identifier}
|
||||||
|
Function fg={identifier}
|
||||||
|
Type fg={identifier}
|
||||||
|
Statement fg={keyword}
|
||||||
|
Keyword fg={keyword}
|
||||||
|
Special fg=70a0d0 italic
|
||||||
|
Error us=wave uc=red
|
||||||
|
|
||||||
|
'''.format(
|
||||||
|
cursor_loc='white',
|
||||||
|
identifier='7b5694',
|
||||||
|
comment='a0b0c0',
|
||||||
|
string='4070a0',
|
||||||
|
keyword='007020',
|
||||||
|
constant='a07040'), # }}}
|
||||||
|
|
||||||
|
'solarized-x-dark': SOLARIZED.format(**SLDX),
|
||||||
|
'solarized-dark': SOLARIZED.format(**SLD),
|
||||||
|
'solarized-light': SOLARIZED.format(**SLL),
|
||||||
|
'solarized-x-light': SOLARIZED.format(**SLLX),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_color(col):
|
def read_color(col):
|
||||||
if QColor.isValidColor(col):
|
if QColor.isValidColor(col):
|
||||||
return QBrush(QColor(col))
|
return QBrush(QColor(col))
|
||||||
|
if col.startswith('rgb('):
|
||||||
|
r, g, b = map(int, (x.strip() for x in col[4:-1].split(',')))
|
||||||
|
return QBrush(QColor(r, g, b))
|
||||||
try:
|
try:
|
||||||
r, g, b = col[0:2], col[2:4], col[4:6]
|
r, g, b = col[0:2], col[2:4], col[4:6]
|
||||||
r, g, b = int(r, 16), int(g, 16), int(b, 16)
|
r, g, b = int(r, 16), int(g, 16), int(b, 16)
|
||||||
@ -122,5 +220,5 @@ def theme_color(theme, name, attr):
|
|||||||
try:
|
try:
|
||||||
return getattr(theme[name], attr).color()
|
return getattr(theme[name], attr).color()
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
return getattr(THEMES[DEFAULT_THEME], attr).color()
|
return getattr(THEMES[default_theme()], attr).color()
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
from PyQt4.Qt import QMainWindow, Qt, QApplication, pyqtSignal
|
from PyQt4.Qt import QMainWindow, Qt, QApplication, pyqtSignal
|
||||||
|
|
||||||
from calibre import xml_replace_entities
|
from calibre import xml_replace_entities
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.tweak_book import actions
|
from calibre.gui2.tweak_book import actions
|
||||||
from calibre.gui2.tweak_book.editor.text import TextEdit
|
from calibre.gui2.tweak_book.editor.text import TextEdit
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ class Editor(QMainWindow):
|
|||||||
|
|
||||||
modification_state_changed = pyqtSignal(object)
|
modification_state_changed = pyqtSignal(object)
|
||||||
undo_redo_state_changed = pyqtSignal(object, object)
|
undo_redo_state_changed = pyqtSignal(object, object)
|
||||||
|
copy_available_state_changed = pyqtSignal(object)
|
||||||
data_changed = pyqtSignal(object)
|
data_changed = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, syntax, parent=None):
|
def __init__(self, syntax, parent=None):
|
||||||
@ -29,20 +31,11 @@ class Editor(QMainWindow):
|
|||||||
self.create_toolbars()
|
self.create_toolbars()
|
||||||
self.undo_available = False
|
self.undo_available = False
|
||||||
self.redo_available = False
|
self.redo_available = False
|
||||||
|
self.copy_available = self.cut_available = False
|
||||||
self.editor.undoAvailable.connect(self._undo_available)
|
self.editor.undoAvailable.connect(self._undo_available)
|
||||||
self.editor.redoAvailable.connect(self._redo_available)
|
self.editor.redoAvailable.connect(self._redo_available)
|
||||||
self.editor.textChanged.connect(self._data_changed)
|
self.editor.textChanged.connect(self._data_changed)
|
||||||
|
self.editor.copyAvailable.connect(self._copy_available)
|
||||||
def _data_changed(self):
|
|
||||||
self.data_changed.emit(self)
|
|
||||||
|
|
||||||
def _undo_available(self, available):
|
|
||||||
self.undo_available = available
|
|
||||||
self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)
|
|
||||||
|
|
||||||
def _redo_available(self, available):
|
|
||||||
self.redo_available = available
|
|
||||||
self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)
|
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def data(self):
|
def data(self):
|
||||||
@ -58,6 +51,13 @@ class Editor(QMainWindow):
|
|||||||
def get_raw_data(self):
|
def get_raw_data(self):
|
||||||
return unicode(self.editor.toPlainText())
|
return unicode(self.editor.toPlainText())
|
||||||
|
|
||||||
|
def replace_data(self, raw, only_if_different=True):
|
||||||
|
if isinstance(raw, bytes):
|
||||||
|
raw = raw.decode('utf-8')
|
||||||
|
current = self.get_raw_data() if only_if_different else False
|
||||||
|
if current != raw:
|
||||||
|
self.editor.replace_text(raw)
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
self.editor.undo()
|
self.editor.undo()
|
||||||
|
|
||||||
@ -73,22 +73,62 @@ class Editor(QMainWindow):
|
|||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def create_toolbars(self):
|
def create_toolbars(self):
|
||||||
self.action_bar = b = self.addToolBar(_('Edit actions tool bar'))
|
self.action_bar = b = self.addToolBar(_('File actions tool bar'))
|
||||||
b.setObjectName('action_bar') # Needed for saveState
|
b.setObjectName('action_bar') # Needed for saveState
|
||||||
b.addAction(actions['editor-save'])
|
for x in ('save', 'undo', 'redo'):
|
||||||
b.addAction(actions['editor-undo'])
|
try:
|
||||||
b.addAction(actions['editor-redo'])
|
b.addAction(actions['editor-%s' % x])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
|
||||||
|
for x in ('cut', 'copy', 'paste'):
|
||||||
|
try:
|
||||||
|
b.addAction(actions['editor-%s' % x])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def break_cycles(self):
|
def break_cycles(self):
|
||||||
self.modification_state_changed.disconnect()
|
self.modification_state_changed.disconnect()
|
||||||
self.undo_redo_state_changed.disconnect()
|
self.undo_redo_state_changed.disconnect()
|
||||||
|
self.copy_available_state_changed.disconnect()
|
||||||
self.data_changed.disconnect()
|
self.data_changed.disconnect()
|
||||||
self.editor.undoAvailable.disconnect()
|
self.editor.undoAvailable.disconnect()
|
||||||
self.editor.redoAvailable.disconnect()
|
self.editor.redoAvailable.disconnect()
|
||||||
self.editor.modificationChanged.disconnect()
|
self.editor.modificationChanged.disconnect()
|
||||||
self.editor.textChanged.disconnect()
|
self.editor.textChanged.disconnect()
|
||||||
|
self.editor.copyAvailable.disconnect()
|
||||||
self.editor.setPlainText('')
|
self.editor.setPlainText('')
|
||||||
|
|
||||||
|
def _data_changed(self):
|
||||||
|
self.data_changed.emit(self)
|
||||||
|
|
||||||
|
def _undo_available(self, available):
|
||||||
|
self.undo_available = available
|
||||||
|
self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)
|
||||||
|
|
||||||
|
def _redo_available(self, available):
|
||||||
|
self.redo_available = available
|
||||||
|
self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)
|
||||||
|
|
||||||
|
def _copy_available(self, available):
|
||||||
|
self.copy_available = self.cut_available = available
|
||||||
|
self.copy_available_state_changed.emit(available)
|
||||||
|
|
||||||
|
def cut(self):
|
||||||
|
self.editor.cut()
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
self.editor.copy()
|
||||||
|
|
||||||
|
def paste(self):
|
||||||
|
if not self.editor.canPaste():
|
||||||
|
return error_dialog(self, _('No text'), _(
|
||||||
|
'There is no suitable text in the clipboard to paste.'), show=True)
|
||||||
|
self.editor.paste()
|
||||||
|
|
||||||
|
def contextMenuEvent(self, ev):
|
||||||
|
ev.ignore()
|
||||||
|
|
||||||
def launch_editor(path_to_edit, path_is_raw=False, syntax='html'):
|
def launch_editor(path_to_edit, path_is_raw=False, syntax='html'):
|
||||||
if path_is_raw:
|
if path_is_raw:
|
||||||
raw = path_to_edit
|
raw = path_to_edit
|
||||||
@ -102,7 +142,7 @@ def launch_editor(path_to_edit, path_is_raw=False, syntax='html'):
|
|||||||
syntax = 'css'
|
syntax = 'css'
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
t = Editor(syntax)
|
t = Editor(syntax)
|
||||||
t.load_text(raw, syntax=syntax)
|
t.data = raw
|
||||||
t.show()
|
t.show()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
|
@ -11,16 +11,16 @@ from threading import Thread
|
|||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QWidget, QVBoxLayout, QApplication, QSize, QNetworkAccessManager,
|
QWidget, QVBoxLayout, QApplication, QSize, QNetworkAccessManager, QMenu, QIcon,
|
||||||
QNetworkReply, QTimer, QNetworkRequest, QUrl, Qt, QNetworkDiskCache)
|
QNetworkReply, QTimer, QNetworkRequest, QUrl, Qt, QNetworkDiskCache, QToolBar)
|
||||||
from PyQt4.QtWebKit import QWebView
|
from PyQt4.QtWebKit import QWebView, QWebInspector
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.ebooks.oeb.polish.parsing import parse
|
from calibre.ebooks.oeb.polish.parsing import parse
|
||||||
from calibre.ebooks.oeb.base import serialize, OEB_DOCS
|
from calibre.ebooks.oeb.base import serialize, OEB_DOCS
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre.gui2.tweak_book import current_container, editors
|
from calibre.gui2.tweak_book import current_container, editors, tprefs, actions
|
||||||
from calibre.gui2.viewer.documentview import apply_settings
|
from calibre.gui2.viewer.documentview import apply_settings
|
||||||
from calibre.gui2.viewer.config import config
|
from calibre.gui2.viewer.config import config
|
||||||
from calibre.utils.ipc.simple_worker import offload_worker
|
from calibre.utils.ipc.simple_worker import offload_worker
|
||||||
@ -117,6 +117,9 @@ class ParseWorker(Thread):
|
|||||||
def get_data(self, name):
|
def get_data(self, name):
|
||||||
return getattr(self.parse_items.get(name, None), 'parsed_data', None)
|
return getattr(self.parse_items.get(name, None), 'parsed_data', None)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.parse_items.clear()
|
||||||
|
|
||||||
parse_worker = ParseWorker()
|
parse_worker = ParseWorker()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -218,6 +221,7 @@ class WebView(QWebView):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWebView.__init__(self, parent)
|
QWebView.__init__(self, parent)
|
||||||
|
self.inspector = QWebInspector(self)
|
||||||
w = QApplication.instance().desktop().availableGeometry(self).width()
|
w = QApplication.instance().desktop().availableGeometry(self).width()
|
||||||
self._size_hint = QSize(int(w/3), int(w/2))
|
self._size_hint = QSize(int(w/3), int(w/2))
|
||||||
settings = self.page().settings()
|
settings = self.page().settings()
|
||||||
@ -232,10 +236,11 @@ class WebView(QWebView):
|
|||||||
settings.setAttribute(settings.DeveloperExtrasEnabled, True)
|
settings.setAttribute(settings.DeveloperExtrasEnabled, True)
|
||||||
settings.setDefaultTextEncoding('utf-8')
|
settings.setDefaultTextEncoding('utf-8')
|
||||||
|
|
||||||
self.setHtml('<p>')
|
|
||||||
self.page().setNetworkAccessManager(NetworkAccessManager(self))
|
self.page().setNetworkAccessManager(NetworkAccessManager(self))
|
||||||
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
|
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
|
||||||
|
|
||||||
|
self.clear()
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return self._size_hint
|
return self._size_hint
|
||||||
|
|
||||||
@ -253,6 +258,20 @@ class WebView(QWebView):
|
|||||||
mf.setScrollBarValue(Qt.Vertical, val[1])
|
mf.setScrollBarValue(Qt.Vertical, val[1])
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.setHtml('<p>')
|
||||||
|
|
||||||
|
def inspect(self):
|
||||||
|
self.inspector.parent().show()
|
||||||
|
self.inspector.parent().raise_()
|
||||||
|
self.pageAction(self.page().InspectElement).trigger()
|
||||||
|
|
||||||
|
def contextMenuEvent(self, ev):
|
||||||
|
menu = QMenu(self)
|
||||||
|
menu.addAction(actions['reload-preview'])
|
||||||
|
menu.addAction(QIcon(I('debug.png')), _('Inspect element'), self.inspect)
|
||||||
|
menu.exec_(ev.globalPos())
|
||||||
|
|
||||||
class Preview(QWidget):
|
class Preview(QWidget):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -261,7 +280,24 @@ class Preview(QWidget):
|
|||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
l.setContentsMargins(0, 0, 0, 0)
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
self.view = WebView(self)
|
self.view = WebView(self)
|
||||||
|
self.inspector = self.view.inspector
|
||||||
|
self.inspector.setPage(self.view.page())
|
||||||
l.addWidget(self.view)
|
l.addWidget(self.view)
|
||||||
|
self.bar = QToolBar(self)
|
||||||
|
l.addWidget(self.bar)
|
||||||
|
|
||||||
|
ac = actions['auto-reload-preview']
|
||||||
|
ac.setCheckable(True)
|
||||||
|
ac.setChecked(True)
|
||||||
|
ac.toggled.connect(self.auto_reload_toggled)
|
||||||
|
self.auto_reload_toggled(ac.isChecked())
|
||||||
|
self.bar.addAction(ac)
|
||||||
|
|
||||||
|
ac = actions['reload-preview']
|
||||||
|
ac.triggered.connect(self.refresh)
|
||||||
|
self.bar.addAction(ac)
|
||||||
|
|
||||||
|
actions['preview-dock'].toggled.connect(self.visibility_changed)
|
||||||
|
|
||||||
self.current_name = None
|
self.current_name = None
|
||||||
self.last_sync_request = None
|
self.last_sync_request = None
|
||||||
@ -278,9 +314,38 @@ class Preview(QWidget):
|
|||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
if self.current_name:
|
if self.current_name:
|
||||||
|
self.refresh_timer.stop()
|
||||||
# This will check if the current html has changed in its editor,
|
# This will check if the current html has changed in its editor,
|
||||||
# and re-parse it if so
|
# and re-parse it if so
|
||||||
parse_worker.add_request(self.current_name)
|
parse_worker.add_request(self.current_name)
|
||||||
# Tell webkit to reload all html and associated resources
|
# Tell webkit to reload all html and associated resources
|
||||||
self.view.refresh()
|
current_url = QUrl.fromLocalFile(current_container().name_to_abspath(self.current_name))
|
||||||
|
if current_url != self.view.url():
|
||||||
|
# The container was changed
|
||||||
|
self.view.setUrl(current_url)
|
||||||
|
else:
|
||||||
|
self.view.refresh()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.view.clear()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_visible(self):
|
||||||
|
return actions['preview-dock'].isChecked()
|
||||||
|
|
||||||
|
def start_refresh_timer(self):
|
||||||
|
if self.is_visible and actions['auto-reload-preview'].isChecked():
|
||||||
|
self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)
|
||||||
|
|
||||||
|
def stop_refresh_timer(self):
|
||||||
|
self.refresh_timer.stop()
|
||||||
|
|
||||||
|
def auto_reload_toggled(self, checked):
|
||||||
|
actions['auto-reload-preview'].setToolTip(_(
|
||||||
|
'Auto reload preview when text changes in editor') if not checked else _(
|
||||||
|
'Disable auto reload of preview'))
|
||||||
|
|
||||||
|
def visibility_changed(self, is_visible):
|
||||||
|
if is_visible:
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
96
src/calibre/gui2/tweak_book/toc.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QDialog, pyqtSignal, QIcon, QVBoxLayout, QDialogButtonBox, QStackedWidget)
|
||||||
|
|
||||||
|
from calibre.ebooks.oeb.polish.toc import commit_toc
|
||||||
|
from calibre.gui2 import gprefs, error_dialog
|
||||||
|
from calibre.gui2.toc.main import TOCView, ItemEdit
|
||||||
|
from calibre.gui2.tweak_book import current_container
|
||||||
|
|
||||||
|
class TOCEditor(QDialog):
|
||||||
|
|
||||||
|
explode_done = pyqtSignal(object)
|
||||||
|
writing_done = pyqtSignal(object)
|
||||||
|
|
||||||
|
def __init__(self, title=None, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
|
||||||
|
t = title or current_container().mi.title
|
||||||
|
self.book_title = t
|
||||||
|
self.setWindowTitle(_('Edit the ToC in %s')%t)
|
||||||
|
self.setWindowIcon(QIcon(I('toc.png')))
|
||||||
|
|
||||||
|
l = self.l = QVBoxLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self.stacks = s = QStackedWidget(self)
|
||||||
|
l.addWidget(s)
|
||||||
|
self.toc_view = TOCView(self)
|
||||||
|
self.toc_view.add_new_item.connect(self.add_new_item)
|
||||||
|
s.addWidget(self.toc_view)
|
||||||
|
self.item_edit = ItemEdit(self)
|
||||||
|
s.addWidget(self.item_edit)
|
||||||
|
|
||||||
|
bb = self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||||
|
l.addWidget(bb)
|
||||||
|
bb.accepted.connect(self.accept)
|
||||||
|
bb.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
self.read_toc()
|
||||||
|
|
||||||
|
self.resize(950, 630)
|
||||||
|
geom = gprefs.get('toc_editor_window_geom', None)
|
||||||
|
if geom is not None:
|
||||||
|
self.restoreGeometry(bytes(geom))
|
||||||
|
|
||||||
|
def add_new_item(self, item, where):
|
||||||
|
self.item_edit(item, where)
|
||||||
|
self.stacks.setCurrentIndex(1)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
if self.stacks.currentIndex() == 1:
|
||||||
|
self.toc_view.update_item(*self.item_edit.result)
|
||||||
|
gprefs['toc_edit_splitter_state'] = bytearray(self.item_edit.splitter.saveState())
|
||||||
|
self.stacks.setCurrentIndex(0)
|
||||||
|
elif self.stacks.currentIndex() == 0:
|
||||||
|
self.write_toc()
|
||||||
|
super(TOCEditor, self).accept()
|
||||||
|
|
||||||
|
def really_accept(self, tb):
|
||||||
|
gprefs['toc_editor_window_geom'] = bytearray(self.saveGeometry())
|
||||||
|
if tb:
|
||||||
|
error_dialog(self, _('Failed to write book'),
|
||||||
|
_('Could not write %s. Click "Show details" for'
|
||||||
|
' more information.')%self.book_title, det_msg=tb, show=True)
|
||||||
|
gprefs['toc_editor_window_geom'] = bytearray(self.saveGeometry())
|
||||||
|
super(TOCEditor, self).reject()
|
||||||
|
return
|
||||||
|
|
||||||
|
super(TOCEditor, self).accept()
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
if not self.bb.isEnabled():
|
||||||
|
return
|
||||||
|
if self.stacks.currentIndex() == 1:
|
||||||
|
gprefs['toc_edit_splitter_state'] = bytearray(self.item_edit.splitter.saveState())
|
||||||
|
self.stacks.setCurrentIndex(0)
|
||||||
|
else:
|
||||||
|
gprefs['toc_editor_window_geom'] = bytearray(self.saveGeometry())
|
||||||
|
super(TOCEditor, self).reject()
|
||||||
|
|
||||||
|
def read_toc(self):
|
||||||
|
self.toc_view(current_container())
|
||||||
|
self.item_edit.load(current_container())
|
||||||
|
self.stacks.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def write_toc(self):
|
||||||
|
toc = self.toc_view.create_toc()
|
||||||
|
commit_toc(current_container(), toc, lang=self.toc_view.toc_lang,
|
||||||
|
uid=self.toc_view.toc_uid)
|
||||||
|
|
@ -6,6 +6,8 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QDockWidget, Qt, QLabel, QIcon, QAction, QApplication, QWidget,
|
QDockWidget, Qt, QLabel, QIcon, QAction, QApplication, QWidget,
|
||||||
QVBoxLayout, QStackedWidget, QTabWidget, QImage, QPixmap, pyqtSignal)
|
QVBoxLayout, QStackedWidget, QTabWidget, QImage, QPixmap, pyqtSignal)
|
||||||
@ -20,6 +22,7 @@ from calibre.gui2.tweak_book.keyboard import KeyboardManager
|
|||||||
from calibre.gui2.tweak_book.preview import Preview
|
from calibre.gui2.tweak_book.preview import Preview
|
||||||
|
|
||||||
class Central(QStackedWidget):
|
class Central(QStackedWidget):
|
||||||
|
|
||||||
' The central widget, hosts the editors '
|
' The central widget, hosts the editors '
|
||||||
|
|
||||||
current_editor_changed = pyqtSignal()
|
current_editor_changed = pyqtSignal()
|
||||||
@ -106,9 +109,9 @@ class Main(MainWindow):
|
|||||||
self.keyboard = KeyboardManager()
|
self.keyboard = KeyboardManager()
|
||||||
|
|
||||||
self.create_actions()
|
self.create_actions()
|
||||||
self.create_menubar()
|
self.create_toolbars()
|
||||||
self.create_toolbar()
|
|
||||||
self.create_docks()
|
self.create_docks()
|
||||||
|
self.create_menubar()
|
||||||
|
|
||||||
self.status_bar = self.statusBar()
|
self.status_bar = self.statusBar()
|
||||||
self.status_bar.addPermanentWidget(self.boss.save_manager.status_widget)
|
self.status_bar.addPermanentWidget(self.boss.save_manager.status_widget)
|
||||||
@ -138,7 +141,8 @@ class Main(MainWindow):
|
|||||||
def reg(icon, text, target, sid, keys, description):
|
def reg(icon, text, target, sid, keys, description):
|
||||||
ac = actions[sid] = QAction(QIcon(I(icon)), text, self)
|
ac = actions[sid] = QAction(QIcon(I(icon)), text, self)
|
||||||
ac.setObjectName('action-' + sid)
|
ac.setObjectName('action-' + sid)
|
||||||
ac.triggered.connect(target)
|
if target is not None:
|
||||||
|
ac.triggered.connect(target)
|
||||||
if isinstance(keys, type('')):
|
if isinstance(keys, type('')):
|
||||||
keys = (keys,)
|
keys = (keys,)
|
||||||
self.keyboard.register_shortcut(
|
self.keyboard.register_shortcut(
|
||||||
@ -156,12 +160,44 @@ class Main(MainWindow):
|
|||||||
self.action_quit = reg('quit.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit'))
|
self.action_quit = reg('quit.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit'))
|
||||||
|
|
||||||
# Editor actions
|
# Editor actions
|
||||||
|
group = _('Editor actions')
|
||||||
self.action_editor_undo = reg('edit-undo.png', _('&Undo'), self.boss.do_editor_undo, 'editor-undo', 'Ctrl+Z',
|
self.action_editor_undo = reg('edit-undo.png', _('&Undo'), self.boss.do_editor_undo, 'editor-undo', 'Ctrl+Z',
|
||||||
_('Undo typing'))
|
_('Undo typing'))
|
||||||
self.action_editor_redo = reg('edit-redo.png', _('&Redo'), self.boss.do_editor_redo, 'editor-redo', 'Ctrl+Y',
|
self.action_editor_redo = reg('edit-redo.png', _('&Redo'), self.boss.do_editor_redo, 'editor-redo', 'Ctrl+Y',
|
||||||
_('Redo typing'))
|
_('Redo typing'))
|
||||||
self.action_editor_save = reg('save.png', _('&Save'), self.boss.do_editor_save, 'editor-save', 'Ctrl+S',
|
self.action_editor_save = reg('save.png', _('&Save'), self.boss.do_editor_save, 'editor-save', 'Ctrl+S',
|
||||||
_('Save changes to the current file'))
|
_('Save changes to the current file'))
|
||||||
|
self.action_editor_cut = reg('edit-cut.png', _('C&ut text'), self.boss.do_editor_cut, 'editor-cut', ('Ctrl+X', 'Shift+Delete', ),
|
||||||
|
_('Cut text'))
|
||||||
|
self.action_editor_copy = reg('edit-copy.png', _('&Copy text'), self.boss.do_editor_copy, 'editor-copy', ('Ctrl+C', 'Ctrl+Insert'),
|
||||||
|
_('Copy text'))
|
||||||
|
self.action_editor_paste = reg('edit-paste.png', _('&Paste text'), self.boss.do_editor_paste, 'editor-paste', ('Ctrl+V', 'Shift+Insert', ),
|
||||||
|
_('Paste text'))
|
||||||
|
self.action_editor_cut.setEnabled(False)
|
||||||
|
self.action_editor_copy.setEnabled(False)
|
||||||
|
self.action_editor_undo.setEnabled(False)
|
||||||
|
self.action_editor_redo.setEnabled(False)
|
||||||
|
|
||||||
|
# Tool actions
|
||||||
|
group = _('Tools')
|
||||||
|
self.action_toc = reg('toc.png', _('&Edit Table of Contents'), self.boss.edit_toc, 'edit-toc', (), _('Edit Table of Contents'))
|
||||||
|
|
||||||
|
# Polish actions
|
||||||
|
group = _('Polish')
|
||||||
|
self.action_subset_fonts = reg(
|
||||||
|
'subset-fonts.png', _('&Subset embedded fonts'), partial(
|
||||||
|
self.boss.polish, 'subset', _('Subset fonts')), 'subset-fonts', (), _('Subset embedded fonts'))
|
||||||
|
self.action_embed_fonts = reg(
|
||||||
|
'embed-fonts.png', _('&Embed referenced fonts'), partial(
|
||||||
|
self.boss.polish, 'embed', _('Embed fonts')), 'embed-fonts', (), _('Embed referenced fonts'))
|
||||||
|
self.action_smarten_punctuation = reg(
|
||||||
|
'smarten-punctuation.png', _('&Smarten punctuation'), partial(
|
||||||
|
self.boss.polish, 'smarten_punctuation', _('Smarten punctuation')), 'smarten-punctuation', (), _('Smarten punctuation'))
|
||||||
|
|
||||||
|
# Preview actions
|
||||||
|
group = _('Preview')
|
||||||
|
self.action_auto_reload_preview = reg('auto-reload.png', _('Auto reload preview'), None, 'auto-reload-preview', (), _('Auto reload preview'))
|
||||||
|
self.action_reload_preview = reg('view-refresh.png', _('Refresh preview'), None, 'reload-preview', ('F5', 'Ctrl+R'), _('Refresh preview'))
|
||||||
|
|
||||||
def create_menubar(self):
|
def create_menubar(self):
|
||||||
b = self.menuBar()
|
b = self.menuBar()
|
||||||
@ -174,30 +210,78 @@ class Main(MainWindow):
|
|||||||
e = b.addMenu(_('&Edit'))
|
e = b.addMenu(_('&Edit'))
|
||||||
e.addAction(self.action_global_undo)
|
e.addAction(self.action_global_undo)
|
||||||
e.addAction(self.action_global_redo)
|
e.addAction(self.action_global_redo)
|
||||||
|
e.addSeparator()
|
||||||
|
e.addAction(self.action_editor_undo)
|
||||||
|
e.addAction(self.action_editor_redo)
|
||||||
|
e.addSeparator()
|
||||||
|
e.addAction(self.action_editor_cut)
|
||||||
|
e.addAction(self.action_editor_copy)
|
||||||
|
e.addAction(self.action_editor_paste)
|
||||||
|
|
||||||
def create_toolbar(self):
|
e = b.addMenu(_('&Tools'))
|
||||||
self.global_bar = b = self.addToolBar(_('Global tool bar'))
|
e.addAction(self.action_toc)
|
||||||
b.setObjectName('global_bar') # Needed for saveState
|
e.addAction(self.action_embed_fonts)
|
||||||
b.addAction(self.action_open_book)
|
e.addAction(self.action_subset_fonts)
|
||||||
b.addAction(self.action_global_undo)
|
e.addAction(self.action_smarten_punctuation)
|
||||||
b.addAction(self.action_global_redo)
|
|
||||||
b.addAction(self.action_save)
|
e = b.addMenu(_('&View'))
|
||||||
|
t = e.addMenu(_('Tool&bars'))
|
||||||
|
e.addSeparator()
|
||||||
|
for name, ac in actions.iteritems():
|
||||||
|
if name.endswith('-dock'):
|
||||||
|
e.addAction(ac)
|
||||||
|
elif name.endswith('-bar'):
|
||||||
|
t.addAction(ac)
|
||||||
|
|
||||||
|
def create_toolbars(self):
|
||||||
|
def create(text, name):
|
||||||
|
name += '-bar'
|
||||||
|
b = self.addToolBar(text)
|
||||||
|
b.setObjectName(name) # Needed for saveState
|
||||||
|
setattr(self, name.replace('-', '_'), b)
|
||||||
|
actions[name] = b.toggleViewAction()
|
||||||
|
return b
|
||||||
|
|
||||||
|
a = create(_('Book tool bar'), 'global').addAction
|
||||||
|
for x in ('open_book', 'global_undo', 'global_redo', 'save', 'toc'):
|
||||||
|
a(getattr(self, 'action_' + x))
|
||||||
|
|
||||||
|
a = create(_('Polish book tool bar'), 'polish').addAction
|
||||||
|
for x in ('embed_fonts', 'subset_fonts', 'smarten_punctuation'):
|
||||||
|
a(getattr(self, 'action_' + x))
|
||||||
|
|
||||||
def create_docks(self):
|
def create_docks(self):
|
||||||
self.file_list_dock = d = QDockWidget(_('&Files Browser'), self)
|
|
||||||
d.setObjectName('file_list_dock') # Needed for saveState
|
def create(name, oname):
|
||||||
|
oname += '-dock'
|
||||||
|
d = QDockWidget(name, self)
|
||||||
|
d.setObjectName(oname) # Needed for saveState
|
||||||
|
ac = d.toggleViewAction()
|
||||||
|
desc = _('Toggle %s') % name.replace('&', '')
|
||||||
|
self.keyboard.register_shortcut(
|
||||||
|
oname, desc, description=desc, action=ac, group=_('Windows'))
|
||||||
|
actions[oname] = ac
|
||||||
|
setattr(self, oname.replace('-', '_'), d)
|
||||||
|
return d
|
||||||
|
|
||||||
|
d = create(_('&Files Browser'), 'files-browser')
|
||||||
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
||||||
self.file_list = FileListWidget(d)
|
self.file_list = FileListWidget(d)
|
||||||
d.setWidget(self.file_list)
|
d.setWidget(self.file_list)
|
||||||
self.addDockWidget(Qt.LeftDockWidgetArea, d)
|
self.addDockWidget(Qt.LeftDockWidgetArea, d)
|
||||||
|
|
||||||
self.preview_dock = d = QDockWidget(_('&Book preview'), self)
|
d = create(_('File &Preview'), 'preview')
|
||||||
d.setObjectName('file_list_dock') # Needed for saveState
|
|
||||||
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
||||||
self.preview = Preview(d)
|
self.preview = Preview(d)
|
||||||
d.setWidget(self.preview)
|
d.setWidget(self.preview)
|
||||||
self.addDockWidget(Qt.RightDockWidgetArea, d)
|
self.addDockWidget(Qt.RightDockWidgetArea, d)
|
||||||
|
|
||||||
|
d = create(_('&Inspector'), 'inspector')
|
||||||
|
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
|
||||||
|
d.setWidget(self.preview.inspector)
|
||||||
|
self.preview.inspector.setParent(d)
|
||||||
|
self.addDockWidget(Qt.BottomDockWidgetArea, d)
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
self.blocking_job.resize(ev.size())
|
self.blocking_job.resize(ev.size())
|
||||||
return super(Main, self).resizeEvent(ev)
|
return super(Main, self).resizeEvent(ev)
|
||||||
@ -227,3 +311,8 @@ class Main(MainWindow):
|
|||||||
state = tprefs.get('main_window_state', None)
|
state = tprefs.get('main_window_state', None)
|
||||||
if state is not None:
|
if state is not None:
|
||||||
self.restoreState(state, self.STATE_VERSION)
|
self.restoreState(state, self.STATE_VERSION)
|
||||||
|
# We never want to start with the inspector showing
|
||||||
|
self.inspector_dock.close()
|
||||||
|
|
||||||
|
def contextMenuEvent(self, ev):
|
||||||
|
ev.ignore()
|
||||||
|
@ -57,7 +57,7 @@ class GlobalUndoHistory(object):
|
|||||||
revert to state before creating savepoint. '''
|
revert to state before creating savepoint. '''
|
||||||
if self.pos > 0 and self.pos == len(self.states) - 1:
|
if self.pos > 0 and self.pos == len(self.states) - 1:
|
||||||
self.pos -= 1
|
self.pos -= 1
|
||||||
cleanup(self.states.pop())
|
cleanup([self.states.pop().container])
|
||||||
ans = self.current_container
|
ans = self.current_container
|
||||||
ans.message = None
|
ans.message = None
|
||||||
return ans
|
return ans
|
||||||
|