KG updates

This commit is contained in:
GRiker 2011-01-29 08:42:42 -07:00
commit 3723cc885a
23 changed files with 746 additions and 185 deletions

View File

@ -583,6 +583,7 @@ class CybookG3Output(OutputProfile):
# Screen size is a best guess # Screen size is a best guess
screen_size = (600, 800) screen_size = (600, 800)
comic_screen_size = (600, 757)
dpi = 168.451 dpi = 168.451
fbase = 16 fbase = 16
fsizes = [12, 12, 14, 16, 18, 20, 22, 24] fsizes = [12, 12, 14, 16, 18, 20, 22, 24]

View File

@ -155,7 +155,7 @@ class HeuristicProcessor(object):
] ]
for word in ITALICIZE_WORDS: for word in ITALICIZE_WORDS:
html = re.sub(r'(?<=\s|>)' + word + r'(?=\s|<)', '<i>%s</i>' % word, html) html = re.sub(r'(?<=\s|>)' + re.escape(word) + r'(?=\s|<)', '<i>%s</i>' % word, html)
for pat in ITALICIZE_STYLE_PATS: for pat in ITALICIZE_STYLE_PATS:
html = re.sub(pat, lambda mo: '<i>%s</i>' % mo.group('words'), html) html = re.sub(pat, lambda mo: '<i>%s</i>' % mo.group('words'), html)

View File

@ -8,7 +8,6 @@ import os
from calibre.customize.conversion import OutputFormatPlugin, \ from calibre.customize.conversion import OutputFormatPlugin, \
OptionRecommendation OptionRecommendation
from calibre.ebooks.txt.markdownml import MarkdownMLizer
from calibre.ebooks.txt.txtml import TXTMLizer from calibre.ebooks.txt.txtml import TXTMLizer
from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines
@ -44,24 +43,32 @@ class TXTOutput(OutputFormatPlugin):
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=False, level=OptionRecommendation.LOW,
help=_('Force splitting on the max-line-length value when no space ' help=_('Force splitting on the max-line-length value when no space '
'is present. Also allows max-line-length to be below the minimum')), 'is present. Also allows max-line-length to be below the minimum')),
OptionRecommendation(name='markdown_format', OptionRecommendation(name='txt_output_formatting',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value='plain',
help=_('Produce Markdown formatted text.')), choices=['plain', 'markdown', 'textile'],
help=_('Formatting used within the document.\n'
'* plain: Produce plain text.\n'
'* markdown: Produce Markdown formatted text.\n'
'* textile: Produce Textile formatted text.')),
OptionRecommendation(name='keep_links', OptionRecommendation(name='keep_links',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=False, level=OptionRecommendation.LOW,
help=_('Do not remove links within the document. This is only ' \ help=_('Do not remove links within the document. This is only ' \
'useful when paired with the markdown-format option because' \ 'useful when paired with a txt-output-formatting option that '
' links are always removed with plain text output.')), 'is not none because links are always removed with plain text output.')),
OptionRecommendation(name='keep_image_references', OptionRecommendation(name='keep_image_references',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=False, level=OptionRecommendation.LOW,
help=_('Do not remove image references within the document. This is only ' \ help=_('Do not remove image references within the document. This is only ' \
'useful when paired with the markdown-format option because' \ 'useful when paired with a txt-output-formatting option that '
' image references are always removed with plain text output.')), 'is not none because links are always removed with plain text output.')),
]) ])
def convert(self, oeb_book, output_path, input_plugin, opts, log): def convert(self, oeb_book, output_path, input_plugin, opts, log):
if opts.markdown_format: if opts.txt_output_formatting.lower() == 'markdown':
from calibre.ebooks.txt.markdownml import MarkdownMLizer
writer = MarkdownMLizer(log) writer = MarkdownMLizer(log)
elif opts.txt_output_formatting.lower() == 'textile':
from calibre.ebooks.txt.textileml import TextileMLizer
writer = TextileMLizer(log)
else: else:
writer = TXTMLizer(log) writer = TXTMLizer(log)

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
'''
Transform OEB content into Textile formatted plain text
'''
import re
from lxml import etree
from calibre.ebooks.oeb.base import XHTML
from calibre.utils.html2textile import html2textile
class TextileMLizer(object):
def __init__(self, log):
self.log = log
def extract_content(self, oeb_book, opts):
self.log.info('Converting XHTML to Textile formatted TXT...')
self.oeb_book = oeb_book
self.opts = opts
return self.mlize_spine()
def mlize_spine(self):
output = [u'']
for item in self.oeb_book.spine:
self.log.debug('Converting %s to Textile formatted TXT...' % item.href)
html = unicode(etree.tostring(item.data.find(XHTML('body')), encoding=unicode))
if not self.opts.keep_links:
html = re.sub(r'<\s*a[^>]*>', '', html)
html = re.sub(r'<\s*/\s*a\s*>', '', html)
if not self.opts.keep_image_references:
html = re.sub(r'<\s*img[^>]*>', '', html)
html = re.sub(r'<\s*img\s*>', '', html)
text = html2textile(html)
# Ensure the section ends with at least two new line characters.
# This is to prevent the last paragraph from a section being
# combined into the fist paragraph of the next.
end_chars = text[-4:]
# Convert all newlines to \n
end_chars = end_chars.replace('\r\n', '\n')
end_chars = end_chars.replace('\r', '\n')
end_chars = end_chars[-2:]
if not end_chars[1] == '\n':
text += '\n\n'
if end_chars[1] == '\n' and not end_chars[0] == '\n':
text += '\n'
output += text
output = u''.join(output)
return output

View File

@ -120,6 +120,8 @@ def _config():
help='Search history for the LRF viewer') help='Search history for the LRF viewer')
c.add_opt('scheduler_search_history', default=[], c.add_opt('scheduler_search_history', default=[],
help='Search history for the recipe scheduler') help='Search history for the recipe scheduler')
c.add_opt('plugin_search_history', default=[],
help='Search history for the recipe scheduler')
c.add_opt('worker_limit', default=6, c.add_opt('worker_limit', default=6,
help=_('Maximum number of waiting worker processes')) help=_('Maximum number of waiting worker processes'))
c.add_opt('get_social_metadata', default=True, c.add_opt('get_social_metadata', default=True,
@ -138,6 +140,7 @@ def _config():
help=_('Show the average rating per item indication in the tag browser')) help=_('Show the average rating per item indication in the tag browser'))
c.add_opt('disable_animations', default=False, c.add_opt('disable_animations', default=False,
help=_('Disable UI animations')) help=_('Disable UI animations'))
c.add_opt
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()
@ -197,14 +200,10 @@ def error_dialog(parent, title, msg, det_msg='', show=False,
return d.exec_() return d.exec_()
return d return d
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False, def question_dialog(parent, title, msg, det_msg='', show_copy_button=False):
buttons=None, yes_button=None):
from calibre.gui2.dialogs.message_box import MessageBox from calibre.gui2.dialogs.message_box import MessageBox
d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent, d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent,
show_copy_button=show_copy_button) show_copy_button=show_copy_button)
if buttons is not None:
d.bb.setStandardButtons(buttons)
return d.exec_() == d.Accepted return d.exec_() == d.Accepted
def info_dialog(parent, title, msg, det_msg='', show=False, def info_dialog(parent, title, msg, det_msg='', show=False,

View File

@ -16,7 +16,6 @@ from calibre.utils.config import prefs
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \ from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
question_dialog, info_dialog question_dialog, info_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck
class LibraryUsageStats(object): # {{{ class LibraryUsageStats(object): # {{{
@ -139,6 +138,12 @@ class ChooseLibraryAction(InterfaceAction):
None, None), attr='action_check_library') None, None), attr='action_check_library')
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection) ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac) self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Restore database'), 'lt.png',
None, None),
attr='action_restore_database')
ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
self.choose_menu.addMenu(self.maintenance_menu) self.choose_menu.addMenu(self.maintenance_menu)
def pick_random(self, *args): def pick_random(self, *args):
@ -267,7 +272,17 @@ class ChooseLibraryAction(InterfaceAction):
_('Metadata will be backed up while calibre is running, at the ' _('Metadata will be backed up while calibre is running, at the '
'rate of approximately 1 book every three seconds.'), show=True) 'rate of approximately 1 book every three seconds.'), show=True)
def restore_database(self):
from calibre.gui2.dialogs.restore_library import restore_database
m = self.gui.library_view.model()
m.stop_metadata_backup()
db = m.db
db.prefs.disable_setting = True
if restore_database(db, self.gui):
self.gui.library_moved(db.library_path, call_close=False)
def check_library(self): def check_library(self):
from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck
self.gui.library_view.save_state() self.gui.library_view.save_state()
m = self.gui.library_view.model() m = self.gui.library_view.model()
m.stop_metadata_backup() m.stop_metadata_backup()

View File

@ -4,7 +4,6 @@ __license__ = 'GPL 3'
__copyright__ = '2009, John Schember <john@nachtimwald.com>' __copyright__ = '2009, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import Qt
from calibre.gui2.convert.txt_output_ui import Ui_Form from calibre.gui2.convert.txt_output_ui import Ui_Form
from calibre.gui2.convert import Widget from calibre.gui2.convert import Widget
@ -21,26 +20,14 @@ class PluginWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, Widget.__init__(self, parent,
['newline', 'max_line_length', 'force_max_line_length', ['newline', 'max_line_length', 'force_max_line_length',
'inline_toc', 'markdown_format', 'keep_links', 'keep_image_references', 'inline_toc', 'txt_output_formatting', 'keep_links', 'keep_image_references',
'txt_output_encoding']) 'txt_output_encoding'])
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
for x in get_option('newline').option.choices: for x in get_option('newline').option.choices:
self.opt_newline.addItem(x) self.opt_newline.addItem(x)
for x in get_option('txt_output_formatting').option.choices:
self.opt_txt_output_formatting.addItem(x)
self.initialize_options(get_option, get_help, db, book_id) self.initialize_options(get_option, get_help, db, book_id)
self.opt_markdown_format.stateChanged.connect(self.enable_markdown_format)
self.enable_markdown_format(self.opt_markdown_format.checkState())
def break_cycles(self): def break_cycles(self):
Widget.break_cycles(self) Widget.break_cycles(self)
try:
self.opt_markdown_format.stateChanged.disconnect()
except:
pass
def enable_markdown_format(self, state):
state = state == Qt.Checked
self.opt_keep_links.setEnabled(state)
self.opt_keep_image_references.setEnabled(state)

View File

@ -6,100 +6,123 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>477</width> <width>392</width>
<height>300</height> <height>346</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item row="0" column="0"> <item>
<widget class="QLabel" name="label"> <widget class="QGroupBox" name="groupBox">
<property name="text"> <property name="title">
<string>&amp;Line ending style:</string> <string>General</string>
</property>
<property name="buddy">
<cstring>opt_newline</cstring>
</property> </property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Output &amp;Encoding:</string>
</property>
<property name="buddy">
<cstring>opt_txt_output_encoding</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="EncodingComboBox" name="opt_txt_output_encoding">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Line ending style:</string>
</property>
<property name="buddy">
<cstring>opt_newline</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_newline"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Formatting:</string>
</property>
<property name="buddy">
<cstring>opt_txt_output_formatting</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="opt_txt_output_formatting"/>
</item>
</layout>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item>
<widget class="QComboBox" name="opt_newline"/> <widget class="QGroupBox" name="groupBox_2">
</item> <property name="title">
<item row="8" column="0"> <string>Plain</string>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>246</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_inline_toc">
<property name="text">
<string>&amp;Inline TOC</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;Maximum line length:</string>
</property>
<property name="buddy">
<cstring>opt_max_line_length</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="opt_max_line_length"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_force_max_line_length">
<property name="text">
<string>Force maximum line length</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="opt_inline_toc">
<property name="text">
<string>&amp;Inline TOC</string>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item>
<widget class="QSpinBox" name="opt_max_line_length"/> <widget class="QGroupBox" name="groupBox_3">
</item> <property name="title">
<item row="1" column="0"> <string>Markdown, Textile</string>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&amp;Maximum line length:</string>
</property>
<property name="buddy">
<cstring>opt_max_line_length</cstring>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_force_max_line_length">
<property name="text">
<string>Force maximum line length</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="opt_markdown_format">
<property name="text">
<string>Apply Markdown formatting to text</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="opt_keep_links">
<property name="text">
<string>Do not remove links (&lt;a&gt; tags) before processing</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="opt_keep_image_references">
<property name="text">
<string>Do not remove image references before processing</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Output Encoding:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="EncodingComboBox" name="opt_txt_output_encoding">
<property name="editable">
<bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="opt_keep_links">
<property name="text">
<string>Do not remove links (&lt;a&gt; tags) before processing</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="opt_keep_image_references">
<property name="text">
<string>Do not remove image references before processing</string>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -7,7 +7,7 @@ import os, traceback, Queue, time, cStringIO, re, sys
from threading import Thread from threading import Thread
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \ from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
Qt, pyqtSignal, QDialog, QMessageBox Qt, pyqtSignal, QDialog
from calibre.customize.ui import available_input_formats, available_output_formats, \ from calibre.customize.ui import available_input_formats, available_output_formats, \
device_plugins device_plugins
@ -609,10 +609,8 @@ class DeviceMixin(object): # {{{
autos = u'\n'.join(map(unicode, map(force_unicode, autos))) autos = u'\n'.join(map(unicode, map(force_unicode, autos)))
return self.ask_a_yes_no_question( return self.ask_a_yes_no_question(
_('No suitable formats'), msg, _('No suitable formats'), msg,
buttons=QMessageBox.Yes|QMessageBox.Cancel,
ans_when_user_unavailable=True, ans_when_user_unavailable=True,
det_msg=autos, det_msg=autos
show_copy_button=False
) )
def set_default_thumbnail(self, height): def set_default_thumbnail(self, height):

View File

@ -74,21 +74,27 @@ class DBCheck(QDialog):
self.reject() self.reject()
def start_load(self): def start_load(self):
self.conn.close() try:
self.pb.setMaximum(self.count) self.conn.close()
self.pb.setValue(0) self.pb.setMaximum(self.count)
self.msg.setText(_('Loading database from SQL')) self.pb.setValue(0)
self.db.conn.close() self.msg.setText(_('Loading database from SQL'))
self.ndbpath = PersistentTemporaryFile('.db') self.db.conn.close()
self.ndbpath.close() self.ndbpath = PersistentTemporaryFile('.db')
self.ndbpath = self.ndbpath.name self.ndbpath.close()
t = DBThread(self.ndbpath, False) self.ndbpath = self.ndbpath.name
t.connect() t = DBThread(self.ndbpath, False)
self.conn = t.conn t.connect()
self.conn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)') self.conn = t.conn
self.conn.commit() self.conn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
self.conn.commit()
QTimer.singleShot(0, self.do_one_load)
except Exception, e:
import traceback
self.error = (as_unicode(e), traceback.format_exc())
self.reject()
QTimer.singleShot(0, self.do_one_load)
def do_one_load(self): def do_one_load(self):
if self.rejected: if self.rejected:

View File

@ -92,7 +92,10 @@ class MessageBox(QDialog, Ui_Dialog):
def showEvent(self, ev): def showEvent(self, ev):
ret = QDialog.showEvent(self, ev) ret = QDialog.showEvent(self, ev)
if self.is_question: if self.is_question:
self.bb.button(self.bb.Yes).setFocus(Qt.OtherFocusReason) try:
self.bb.button(self.bb.Yes).setFocus(Qt.OtherFocusReason)
except:
pass# Buttons were changed
else: else:
self.bb.button(self.bb.Ok).setFocus(Qt.OtherFocusReason) self.bb.button(self.bb.Ok).setFocus(Qt.OtherFocusReason)
return ret return ret

View File

@ -7,7 +7,7 @@ import re, os
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \ pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \
QMessageBox, QDate QDate
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
@ -15,7 +15,8 @@ from calibre.ebooks.metadata import string_to_authors, authors_to_string
from calibre.ebooks.metadata.book.base import composite_formatter from calibre.ebooks.metadata.book.base import composite_formatter
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, gprefs from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, \
gprefs, question_dialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils.config import dynamic, JSONConfig from calibre.utils.config import dynamic, JSONConfig
from calibre.utils.titlecase import titlecase from calibre.utils.titlecase import titlecase
@ -888,12 +889,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if self.query_field.currentIndex() == 0: if self.query_field.currentIndex() == 0:
return return
ret = QMessageBox.question(self, _("Delete saved search/replace"), if not question_dialog(self, _("Delete saved search/replace"),
_("The selected saved search/replace will be deleted. " _("The selected saved search/replace will be deleted. "
"Are you sure?"), "Are you sure?")):
QMessageBox.Ok, QMessageBox.Cancel)
if ret == QMessageBox.Cancel:
return return
item_id = self.query_field.currentIndex() item_id = self.query_field.currentIndex()
@ -917,11 +915,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
new = True new = True
name = unicode(name) name = unicode(name)
if name in self.queries.keys(): if name in self.queries.keys():
ret = QMessageBox.question(self, _("Save search/replace"), if not question_dialog(self, _("Save search/replace"),
_("That saved search/replace already exists and will be overwritten. " _("That saved search/replace already exists and will be overwritten. "
"Are you sure?"), "Are you sure?")):
QMessageBox.Ok, QMessageBox.Cancel)
if ret == QMessageBox.Cancel:
return return
new = False new = False

View File

@ -11,7 +11,7 @@ from functools import partial
from threading import Thread from threading import Thread
from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QDate, \ from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QDate, \
QPixmap, QListWidgetItem, QDialog, pyqtSignal, QMessageBox, QIcon, \ QPixmap, QListWidgetItem, QDialog, pyqtSignal, QIcon, \
QPushButton QPushButton
from calibre.gui2 import error_dialog, file_icon_provider, dynamic, \ from calibre.gui2 import error_dialog, file_icon_provider, dynamic, \
@ -770,9 +770,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if question_dialog(self, _('Tags changed'), if question_dialog(self, _('Tags changed'),
_('You have changed the tags. In order to use the tags' _('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these ' ' editor, you must either discard or apply these '
'changes'), show_copy_button=False, 'changes. Apply changes?'), show_copy_button=False):
buttons=QMessageBox.Apply|QMessageBox.Discard,
yes_button=QMessageBox.Apply):
self.apply_tags(commit=True, notify=True) self.apply_tags(commit=True, notify=True)
self.original_tags = unicode(self.tags.text()) self.original_tags = unicode(self.tags.text())
else: else:

View File

@ -0,0 +1,115 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QDialog, QLabel, QVBoxLayout, QDialogButtonBox, \
QProgressBar, QSize, QTimer, pyqtSignal, Qt
from calibre.library.restore import Restore
from calibre.gui2 import error_dialog, question_dialog, warning_dialog, \
info_dialog
class DBRestore(QDialog):
update_signal = pyqtSignal(object, object)
def __init__(self, parent, library_path):
QDialog.__init__(self, parent)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.l1 = QLabel('<b>'+_('Restoring database from backups, do not'
' interrupt, this will happen in two stages')+'...')
self.setWindowTitle(_('Restoring database'))
self.l.addWidget(self.l1)
self.pb = QProgressBar(self)
self.l.addWidget(self.pb)
self.pb.setMaximum(0)
self.pb.setMinimum(0)
self.msg = QLabel('')
self.l.addWidget(self.msg)
self.msg.setWordWrap(True)
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
self.l.addWidget(self.bb)
self.bb.rejected.connect(self.reject)
self.resize(self.sizeHint() + QSize(100, 50))
self.error = None
self.rejected = False
self.library_path = library_path
self.update_signal.connect(self.do_update, type=Qt.QueuedConnection)
self.restorer = Restore(library_path, self)
self.restorer.daemon = True
# Give the metadata backup thread time to stop
QTimer.singleShot(2000, self.start)
def start(self):
self.restorer.start()
QTimer.singleShot(10, self.update)
def reject(self):
self.rejected = True
self.restorer.progress_callback = lambda x, y: x
QDialog.rejecet(self)
def update(self):
if self.restorer.is_alive():
QTimer.singleShot(10, self.update)
else:
self.restorer.progress_callback = lambda x, y: x
self.accept()
def __call__(self, msg, step):
self.update_signal.emit(msg, step)
def do_update(self, msg, step):
if msg is None:
self.pb.setMaximum(step)
else:
self.msg.setText(msg)
self.pb.setValue(step)
def restore_database(db, parent=None):
if not question_dialog(parent, _('Are you sure?'), '<p>'+
_('Your list of books, with all their metadata is '
'stored in a single file, called a database. '
'In addition, metadata for each individual '
'book is stored in that books\' folder, as '
'a backup.'
'<p>This operation will rebuild '
'the database from the individual book '
'metadata. This is useful if the '
'database has been corrupted and you get a '
'blank list of books. Note that restoring only '
'restores books, not any settings stored in the '
'database, or any custom recipes.'
'<p>Do you want to restore the database?')):
return False
db.conn.close()
d = DBRestore(parent, db.library_path)
d.exec_()
r = d.restorer
d.restorer = None
if d.rejected:
return True
if r.tb is not None:
error_dialog(parent, _('Failed'),
_('Restoring database failed, click Show details to see details'),
det_msg=r.tb, show=True)
else:
if r.errors_occurred:
warning_dialog(parent, _('Success'),
_('Restoring the database succeeded with some warnings',
' click Show details to see the details.'),
det_msg=r.report, show=True)
else:
info_dialog(parent, _('Success'),
_('Restoring database was successful'), show=True,
show_copy_button=False)
return True

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, time, socket, traceback import sys, os, time, socket, traceback
from functools import partial from functools import partial
from PyQt4.Qt import QCoreApplication, QIcon, QMessageBox, QObject, QTimer, \ from PyQt4.Qt import QCoreApplication, QIcon, QObject, QTimer, \
QThread, pyqtSignal, Qt, QProgressDialog, QString, QPixmap, \ QThread, pyqtSignal, Qt, QProgressDialog, QString, QPixmap, \
QSplashScreen, QApplication QSplashScreen, QApplication
@ -319,9 +319,6 @@ def run_gui(opts, args, actions, listener, app, gui_debug=None):
def cant_start(msg=_('If you are sure it is not running')+', ', def cant_start(msg=_('If you are sure it is not running')+', ',
what=None): what=None):
d = QMessageBox(QMessageBox.Critical, _('Cannot Start ')+__appname__,
'<p>'+(_('%s is already running.')%__appname__)+'</p>',
QMessageBox.Ok)
base = '<p>%s</p><p>%s %s' base = '<p>%s</p><p>%s %s'
where = __appname__ + ' '+_('may be running in the system tray, in the')+' ' where = __appname__ + ' '+_('may be running in the system tray, in the')+' '
if isosx: if isosx:
@ -334,8 +331,10 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
else: else:
what = _('try deleting the file')+': '+ADDRESS what = _('try deleting the file')+': '+ADDRESS
d.setInformativeText(base%(where, msg, what)) info = base%(where, msg, what)
d.exec_() error_dialog(None, _('Cannot Start ')+__appname__,
'<p>'+(_('%s is already running.')%__appname__)+'</p>'+info, show=True)
raise SystemExit(1) raise SystemExit(1)
def communicate(args): def communicate(args):

View File

@ -10,7 +10,7 @@ import textwrap, re, os
from PyQt4.Qt import Qt, QDateEdit, QDate, \ from PyQt4.Qt import Qt, QDateEdit, QDate, \
QIcon, QToolButton, QWidget, QLabel, QGridLayout, \ QIcon, QToolButton, QWidget, QLabel, QGridLayout, \
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \ QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \
QPushButton, QSpinBox, QMessageBox, QLineEdit QPushButton, QSpinBox, QLineEdit
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \ from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
EnComboBox, FormatList, ImageView, CompleteLineEdit EnComboBox, FormatList, ImageView, CompleteLineEdit
@ -848,9 +848,7 @@ class TagsEdit(CompleteLineEdit): # {{{
if question_dialog(self, _('Tags changed'), if question_dialog(self, _('Tags changed'),
_('You have changed the tags. In order to use the tags' _('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these ' ' editor, you must either discard or apply these '
'changes'), show_copy_button=False, 'changes. Apply changes?'), show_copy_button=False):
buttons=QMessageBox.Apply|QMessageBox.Discard,
yes_button=QMessageBox.Apply):
self.commit(db, id_) self.commit(db, id_)
db.commit() db.commit()
self.original_val = self.current_val self.original_val = self.current_val

View File

@ -17,11 +17,14 @@ from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin
remove_plugin remove_plugin
from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \ from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \
question_dialog question_dialog
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.icu import lower
class PluginModel(QAbstractItemModel): # {{{ class PluginModel(QAbstractItemModel, SearchQueryParser): # {{{
def __init__(self, *args): def __init__(self, *args):
QAbstractItemModel.__init__(self, *args) QAbstractItemModel.__init__(self, *args)
SearchQueryParser.__init__(self, ['all'])
self.icon = QVariant(QIcon(I('plugins.png'))) self.icon = QVariant(QIcon(I('plugins.png')))
p = QIcon(self.icon).pixmap(32, 32, QIcon.Disabled, QIcon.On) p = QIcon(self.icon).pixmap(32, 32, QIcon.Disabled, QIcon.On)
self.disabled_icon = QVariant(QIcon(p)) self.disabled_icon = QVariant(QIcon(p))
@ -40,6 +43,72 @@ class PluginModel(QAbstractItemModel): # {{{
for plugins in self._data.values(): for plugins in self._data.values():
plugins.sort(cmp=lambda x, y: cmp(x.name.lower(), y.name.lower())) plugins.sort(cmp=lambda x, y: cmp(x.name.lower(), y.name.lower()))
def universal_set(self):
ans = set([])
for c, category in enumerate(self.categories):
ans.add((c, -1))
for p, plugin in enumerate(self._data[category]):
ans.add((c, p))
return ans
def get_matches(self, location, query, candidates=None):
if candidates is None:
candidates = self.universal_set()
ans = set([])
if not query:
return ans
query = lower(query)
for c, p in candidates:
if p < 0:
if query in lower(self.categories[c]):
ans.add((c, p))
else:
try:
plugin = self._data[self.categories[c]][p]
except:
continue
if query in lower(plugin.name) or query in lower(plugin.author) or \
query in lower(plugin.description):
ans.add((c, p))
return ans
def find(self, query):
query = query.strip()
matches = self.parse(query)
if not matches:
return QModelIndex()
matches = list(sorted(matches))
c, p = matches[0]
cat_idx = self.index(c, 0, QModelIndex())
if p == -1:
return cat_idx
return self.index(p, 0, cat_idx)
def find_next(self, idx, query, backwards=False):
query = query.strip()
matches = self.parse(query)
if not matches:
return idx
if idx.parent().isValid():
loc = (idx.parent().row(), idx.row())
else:
loc = (idx.row(), -1)
if loc not in matches:
return self.find(query)
if len(matches) == 1:
return QModelIndex()
matches = list(sorted(matches))
i = matches.index(loc)
if backwards:
ans = i - 1 if i - 1 >= 0 else len(matches)-1
else:
ans = i + 1 if i + 1 < len(matches) else 0
ans = matches[ans]
return self.index(ans[0], 0, QModelIndex()) if ans[1] < 0 else \
self.index(ans[1], 0, self.index(ans[0], 0, QModelIndex()))
def index(self, row, column, parent): def index(self, row, column, parent):
if not self.hasIndex(row, column, parent): if not self.hasIndex(row, column, parent):
return QModelIndex() return QModelIndex()
@ -127,6 +196,7 @@ class PluginModel(QAbstractItemModel): # {{{
return plugin return plugin
return NONE return NONE
# }}} # }}}
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
@ -144,6 +214,42 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.customize_plugin_button.clicked.connect(self.customize_plugin) self.customize_plugin_button.clicked.connect(self.customize_plugin)
self.remove_plugin_button.clicked.connect(self.remove_plugin) self.remove_plugin_button.clicked.connect(self.remove_plugin)
self.button_plugin_add.clicked.connect(self.add_plugin) self.button_plugin_add.clicked.connect(self.add_plugin)
self.search.initialize('plugin_search_history',
help_text=_('Search for plugin'))
self.search.search.connect(self.find)
self.next_button.clicked.connect(self.find_next)
self.previous_button.clicked.connect(self.find_previous)
def find(self, query):
idx = self._plugin_model.find(query)
if not idx.isValid():
return info_dialog(self, _('No matches'),
_('Could not find any matching plugins'), show=True,
show_copy_button=False)
self.highlight_index(idx)
def highlight_index(self, idx):
self.plugin_view.scrollTo(idx)
self.plugin_view.selectionModel().select(idx,
self.plugin_view.selectionModel().ClearAndSelect)
self.plugin_view.setCurrentIndex(idx)
def find_next(self, *args):
idx = self.plugin_view.currentIndex()
if not idx.isValid():
idx = self._plugin_model.index(0, 0)
idx = self._plugin_model.find_next(idx,
unicode(self.search.currentText()))
self.highlight_index(idx)
def find_previous(self, *args):
idx = self.plugin_view.currentIndex()
if not idx.isValid():
idx = self._plugin_model.index(0, 0)
idx = self._plugin_model.find_next(idx,
unicode(self.search.currentText()), backwards=True)
self.highlight_index(idx)
def toggle_plugin(self, *args): def toggle_plugin(self, *args):
self.modify_plugin(op='toggle') self.modify_plugin(op='toggle')
@ -184,13 +290,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
show=True, show_copy_button=False) show=True, show_copy_button=False)
idx = self._plugin_model.plugin_to_index_by_properties(plugin) idx = self._plugin_model.plugin_to_index_by_properties(plugin)
if idx.isValid(): if idx.isValid():
self.plugin_view.scrollTo(idx, self.highlight_index(idx)
self.plugin_view.PositionAtCenter)
self.plugin_view.scrollTo(idx,
self.plugin_view.PositionAtCenter)
self.plugin_view.selectionModel().select(idx,
self.plugin_view.selectionModel().ClearAndSelect)
self.plugin_view.setCurrentIndex(idx)
else: else:
error_dialog(self, _('No valid plugin path'), error_dialog(self, _('No valid plugin path'),
_('%s is not a valid plugin path')%path).exec_() _('%s is not a valid plugin path')%path).exec_()

View File

@ -24,6 +24,47 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="SearchBox2" name="search"/>
</item>
<item>
<widget class="QPushButton" name="next_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Next</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="previous_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Previous</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QTreeView" name="plugin_view"> <widget class="QTreeView" name="plugin_view">
<property name="alternatingRowColors"> <property name="alternatingRowColors">
@ -84,6 +125,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>SearchBox2</class>
<extends>QComboBox</extends>
<header>calibre/gui2/search_box.h</header>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>
</resources> </resources>

View File

@ -275,7 +275,7 @@ def generate_catalog(parent, dbspec, ids, device_manager, db):
if device_manager.is_device_connected: if device_manager.is_device_connected:
device = device_manager.device device = device_manager.device
connected_device['name'] = device.gui_name connected_device['name'] = device.get_gui_name()
try: try:
storage = [] storage = []
if device._main_prefix: if device._main_prefix:

View File

@ -12,11 +12,9 @@ __docformat__ = 'restructuredtext en'
import collections, os, sys, textwrap, time import collections, os, sys, textwrap, time
from Queue import Queue, Empty from Queue import Queue, Empty
from threading import Thread from threading import Thread
from PyQt4.Qt import Qt, SIGNAL, QTimer, \ from PyQt4.Qt import Qt, SIGNAL, QTimer, QHelpEvent, QAction, \
QPixmap, QMenu, QIcon, pyqtSignal, \ QMenu, QIcon, pyqtSignal, \
QDialog, \ QDialog, QSystemTrayIcon, QApplication, QKeySequence
QSystemTrayIcon, QApplication, QKeySequence, \
QMessageBox, QHelpEvent, QAction
from calibre import prints from calibre import prints
from calibre.constants import __appname__, isosx from calibre.constants import __appname__, isosx
@ -357,11 +355,12 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def is_minimized_to_tray(self): def is_minimized_to_tray(self):
return getattr(self, '__systray_minimized', False) return getattr(self, '__systray_minimized', False)
def ask_a_yes_no_question(self, title, msg, **kwargs): def ask_a_yes_no_question(self, title, msg, det_msg='',
awu = kwargs.pop('ans_when_user_unavailable', True) show_copy_button=False, ans_when_user_unavailable=True):
if self.is_minimized_to_tray: if self.is_minimized_to_tray:
return awu return ans_when_user_unavailable
return question_dialog(self, title, msg, **kwargs) return question_dialog(self, title, msg, det_msg=det_msg,
show_copy_button=show_copy_button)
def hide_windows(self): def hide_windows(self):
for window in QApplication.topLevelWidgets(): for window in QApplication.topLevelWidgets():
@ -601,11 +600,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
Quitting may cause corruption on the device.<br> Quitting may cause corruption on the device.<br>
Are you sure you want to quit?''')+'</p>' Are you sure you want to quit?''')+'</p>'
d = QMessageBox(QMessageBox.Warning, _('WARNING: Active jobs'), msg, if not question_dialog(self, _('Active jobs'), msg):
QMessageBox.Yes|QMessageBox.No, self)
d.setIconPixmap(QPixmap(I('dialog_warning.png')))
d.setDefaultButton(QMessageBox.No)
if d.exec_() != QMessageBox.Yes:
return False return False
return True return True

View File

@ -356,9 +356,9 @@ class BrowseServer(object):
if category in category_icon_map: if category in category_icon_map:
icon = category_icon_map[category] icon = category_icon_map[category]
elif meta['is_custom']: elif meta['is_custom']:
icon = category_icon_map[':custom'] icon = category_icon_map['custom:']
elif meta['kind'] == 'user': elif meta['kind'] == 'user':
icon = category_icon_map[':user'] icon = category_icon_map['user:']
else: else:
icon = 'blank.png' icon = 'blank.png'
cats.append((meta['name'], category, icon)) cats.append((meta['name'], category, icon))

View File

@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2010, Webreactor - Marcin Lulek <info@webreactor.eu>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from lxml import etree
from calibre.ebooks.oeb.base import barename
class EchoTarget:
def __init__(self):
self.final_output = []
self.block = False
self.ol_ident = 0
self.ul_ident = 0
self.list_types = []
self.haystack = []
def start(self, tag, attrib):
tag = barename(tag)
newline = '\n'
dot = ''
new_tag = ''
if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
new_tag = tag
dot = '. '
elif tag == 'p':
new_tag = ''
dot = ''
elif tag == 'blockquote':
new_tag = 'bq'
dot = '. '
elif tag in ('b', 'strong'):
new_tag = '*'
newline = ''
elif tag in ('em', 'i'):
new_tag = '_'
newline = ''
elif tag == 'cite':
new_tag = '??'
newline = ''
elif tag == 'del':
new_tag = '-'
newline = ''
elif tag == 'ins':
new_tag = '+'
newline = ''
elif tag == 'sup':
new_tag = '^'
newline = ''
elif tag == 'sub':
new_tag = '~'
newline = ''
elif tag == 'span':
new_tag = '%'
newline = ''
elif tag == 'a':
self.block = True
if 'title' in attrib:
self.a_part = {'title':attrib.get('title'),
'href':attrib.get('href', '')}
else:
self.a_part = {'title':None, 'href':attrib.get('href', '')}
new_tag = ''
newline = ''
elif tag == 'img':
if 'alt' in attrib:
new_tag = ' !%s(%s)' % (attrib.get('src'), attrib.get('title'),)
else:
new_tag = ' !%s' % attrib.get('src')
newline = ''
elif tag in ('ul', 'ol'):
new_tag = ''
newline = ''
self.list_types.append(tag)
if tag == 'ul':
self.ul_ident += 1
else:
self.ol_ident += 1
elif tag == 'li':
indent = self.ul_ident + self.ol_ident
if self.list_types[-1] == 'ul':
new_tag = '*' * indent + ' '
newline = '\n'
else:
new_tag = '#' * indent + ' '
newline = '\n'
if tag not in ('ul', 'ol'):
textile = '%(newline)s%(tag)s%(dot)s' % \
{
'newline':newline,
'tag':new_tag,
'dot':dot
}
if not self.block:
self.final_output.append(textile)
else:
self.haystack.append(textile)
def end(self, tag):
tag = barename(tag)
if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'):
self.final_output.append('\n')
elif tag in ('b', 'strong'):
self.final_output.append('*')
elif tag in ('em', 'i'):
self.final_output.append('_')
elif tag == 'cite':
self.final_output.append('??')
elif tag == 'del':
self.final_output.append('-')
elif tag == 'ins':
self.final_output.append('+')
elif tag == 'sup':
self.final_output.append('^')
elif tag == 'sub':
self.final_output.append('~')
elif tag == 'span':
self.final_output.append('%')
elif tag == 'a':
if self.a_part['title']:
textilized = ' "%s (%s)":%s ' % (
''.join(self.haystack),
self.a_part.get('title'),
self.a_part.get('href'),
)
self.haystack = []
else:
textilized = ' "%s":%s ' % (
''.join(self.haystack),
self.a_part.get('href'),
)
self.haystack = []
self.final_output.append(textilized)
self.block = False
elif tag == 'img':
self.final_output.append('!')
elif tag == 'ul':
self.ul_ident -= 1
self.list_types.pop()
if len(self.list_types) == 0:
self.final_output.append('\n')
elif tag == 'ol':
self.ol_ident -= 1
self.list_types.pop()
if len(self.list_types) == 0:
self.final_output.append('\n')
def data(self, data):
#we dont want any linebreaks inside our tags
node_data = data.replace('\n','')
if not self.block:
self.final_output.append(node_data)
else:
self.haystack.append(node_data)
def comment(self, text):
pass
def close(self):
return "closed!"
def html2textile(html):
#1st pass
#clean the whitespace and convert html to xhtml
parser = etree.HTMLParser()
tree = etree.fromstring(html, parser)
xhtml = etree.tostring(tree, method="xml")
parser = etree.XMLParser(remove_blank_text=True)
root = etree.XML(xhtml, parser)
cleaned_html = etree.tostring(root)
#2nd pass build textile
target = EchoTarget()
parser = etree.XMLParser(target=target)
root = etree.fromstring(cleaned_html, parser)
textilized_text = ''.join(target.final_output).lstrip().rstrip()
return textilized_text

View File

@ -260,12 +260,12 @@ class SearchQueryParser(object):
''' '''
Should return the set of matches for :param:'location` and :param:`query`. Should return the set of matches for :param:'location` and :param:`query`.
The search must be performed over all entries is :param:`candidates` is The search must be performed over all entries if :param:`candidates` is
None otherwise only over the items in candidates. None otherwise only over the items in candidates.
:param:`location` is one of the items in :member:`SearchQueryParser.DEFAULT_LOCATIONS`. :param:`location` is one of the items in :member:`SearchQueryParser.DEFAULT_LOCATIONS`.
:param:`query` is a string literal. :param:`query` is a string literal.
:param: None or a subset of the set returned by :meth:`universal_set`. :return: None or a subset of the set returned by :meth:`universal_set`.
''' '''
return set([]) return set([])