From 97536e397fc138b581e04ebb1f26c8e41efdf38f Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 13 Dec 2010 18:38:37 -0500 Subject: [PATCH 1/6] FB2 Output: add blank line after paragraphs when insert-blank-line option used. Use instead of CSS because not many readers support CSS in FB2 files. --- src/calibre/ebooks/fb2/fb2ml.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py index 51bfaa7293..89c12db103 100644 --- a/src/calibre/ebooks/fb2/fb2ml.py +++ b/src/calibre/ebooks/fb2/fb2ml.py @@ -73,6 +73,10 @@ class FB2MLizer(object): text = re.sub(r'(?miu)

\s*

', '', text) text = re.sub(r'(?miu)\s+

', '

', text) text = re.sub(r'(?miu)

', '

\n\n

', text) + + if self.opts.insert_blank_line: + text = re.sub(r'(?miu)

', '

', text) + return text def fb2_header(self): From 3df1780251d802d7de47bbf0484a932e0bf945bf Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 13 Dec 2010 18:57:05 -0500 Subject: [PATCH 2/6] FB2 Output: Add support for some 2.1 style tags. --- src/calibre/ebooks/fb2/fb2ml.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py index 89c12db103..5efc360f1f 100644 --- a/src/calibre/ebooks/fb2/fb2ml.py +++ b/src/calibre/ebooks/fb2/fb2ml.py @@ -297,6 +297,18 @@ class FB2MLizer(object): s_out, s_tags = self.handle_simple_tag('emphasis', tag_stack+tags) fb2_out += s_out tags += s_tags + elif tag in ('del', 'strike'): + s_out, s_tags = self.handle_simple_tag('strikethrough', tag_stack+tags) + fb2_out += s_out + tags += s_tags + elif tag == 'sub': + s_out, s_tags = self.handle_simple_tag('sub', tag_stack+tags) + fb2_out += s_out + tags += s_tags + elif tag == 'sup': + s_out, s_tags = self.handle_simple_tag('sup', tag_stack+tags) + fb2_out += s_out + tags += s_tags # Processes style information. if style['font-style'] == 'italic': @@ -307,6 +319,10 @@ class FB2MLizer(object): s_out, s_tags = self.handle_simple_tag('strong', tag_stack+tags) fb2_out += s_out tags += s_tags + elif style['text-decoration'] == 'line-through': + s_out, s_tags = self.handle_simple_tag('strikethrough', tag_stack+tags) + fb2_out += s_out + tags += s_tags # Process element text. if hasattr(elem_tree, 'text') and elem_tree.text: From acbbc16bf3af727e317efd87d9d4b146950747ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Dec 2010 17:07:00 -0700 Subject: [PATCH 3/6] Infrastructure to detect mem leaks in the edit metadata dialog --- src/calibre/gui2/actions/edit_metadata.py | 8 ++- src/calibre/gui2/dialogs/metadata_single.py | 69 ++++++++++++++++++++- src/calibre/utils/mem.py | 55 ++++++++++++++++ 3 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 src/calibre/utils/mem.py diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 60a943ccb9..11949632e9 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -154,15 +154,17 @@ class EditMetadataAction(InterfaceAction): d.view_format.connect(lambda fmt:self.gui.iactions['View'].view_format(row_list[current_row], fmt)) - if d.exec_() != d.Accepted: - d.view_format.disconnect() + ret = d.exec_() + d.break_cycles() + if ret != d.Accepted: break - d.view_format.disconnect() + changed.add(d.id) if d.row_delta == 0: break current_row += d.row_delta + if changed: self.gui.library_view.model().refresh_ids(list(changed)) current = self.gui.library_view.currentIndex() diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index d9bb1c2a33..9e3a8c7eda 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -293,7 +293,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): finally: self.fetch_cover_button.setEnabled(True) self.unsetCursor() - self.pi.stop() + if self.pi is not None: + self.pi.stop() # }}} @@ -442,7 +443,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): ResizableDialog.__init__(self, window) self.cover_fetcher = None self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter) - self.cancel_all = False base = unicode(self.author_sort.toolTip()) self.ok_aus_tooltip = '

' + textwrap.fill(base+'

'+ _(' The green color indicates that the current ' @@ -573,7 +573,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index) self.series.lineEdit().editingFinished.connect(self.increment_series_index) - self.show() pm = QPixmap() if cover: pm.loadFromData(cover) @@ -593,6 +592,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.original_author = unicode(self.authors.text()).strip() self.original_title = unicode(self.title.text()).strip() + self.show() + def create_custom_column_editors(self): w = self.central_widget.widget(1) layout = w.layout() @@ -907,3 +908,65 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry())) dynamic.set('metasingle_splitter_state', bytes(self.splitter.saveState())) + + def break_cycles(self): + try: + self.view_format.disconnect() + except: + pass # Fails if view format was never connected + self.view_format = None + self.db = None + self.pi = None + self.cover_data = self.cpixmap = None + +if __name__ == '__main__': + from calibre.library import db + from PyQt4.Qt import QApplication + from calibre.utils.mem import memory + import gc + + + app = QApplication([]) + db = db() + + # Initialize all Qt Objects once + d = MetadataSingleDialog(None, 4, db) + d.break_cycles() + d.reject() + del d + + for i in range(3): + gc.collect() + before = memory() + + gc.collect() + d = MetadataSingleDialog(None, 4, db) + d.break_cycles() + d.reject() + del d + + for i in range(3): + gc.collect() + print 'Used memory:', memory(before)/1024.**2, 'MB' + gc.collect() + + ''' + nmap, omap = {}, {} + for x in objects: + omap[id(x)] = x + for x in nobjects: + nmap[id(x)] = x + + new_ids = set(nmap.keys()) - set(omap.keys()) + print "New ids:", len(new_ids) + for i in new_ids: + o = nmap[i] + if o is objects: + continue + print repr(o)[:1050] + refs = gc.get_referrers(o) + for r in refs: + if r is objects or r is nobjects: + continue + print '\t', r + ''' diff --git a/src/calibre/utils/mem.py b/src/calibre/utils/mem.py new file mode 100644 index 0000000000..f48aec34c6 --- /dev/null +++ b/src/calibre/utils/mem.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +## {{{ http://code.activestate.com/recipes/286222/ (r1) +import os + +_proc_status = '/proc/%d/status' % os.getpid() + +_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0, + 'KB': 1024.0, 'MB': 1024.0*1024.0} + +def _VmB(VmKey): + '''Private. + ''' + global _proc_status, _scale + # get pseudo file /proc//status + try: + t = open(_proc_status) + v = t.read() + t.close() + except: + return 0.0 # non-Linux? + # get VmKey line e.g. 'VmRSS: 9999 kB\n ...' + i = v.index(VmKey) + v = v[i:].split(None, 3) # whitespace + if len(v) < 3: + return 0.0 # invalid format? + # convert Vm value to bytes + return float(v[1]) * _scale[v[2]] + + +def memory(since=0.0): + '''Return memory usage in bytes. + ''' + return _VmB('VmSize:') - since + + +def resident(since=0.0): + '''Return resident memory usage in bytes. + ''' + return _VmB('VmRSS:') - since + + +def stacksize(since=0.0): + '''Return stack size in bytes. + ''' + return _VmB('VmStk:') - since +## end of http://code.activestate.com/recipes/286222/ }}} + + + From f71f60ab0cb84364a876dc7ee3950474e115d338 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Dec 2010 19:02:14 -0700 Subject: [PATCH 4/6] Edit metadata dialog: Fix memory leak caused by Next/Previous buttons --- src/calibre/gui2/dialogs/metadata_single.py | 47 +++++++-------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 9e3a8c7eda..4a9bb784c8 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -910,14 +910,18 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): bytes(self.splitter.saveState())) def break_cycles(self): - try: - self.view_format.disconnect() - except: - pass # Fails if view format was never connected - self.view_format = None - self.db = None - self.pi = None - self.cover_data = self.cpixmap = None + # Break any reference cycles that could prevent python + # from garbage collecting this dialog + def disconnect(signal): + try: + signal.disconnect() + except: + pass # Fails if view format was never connected + disconnect(self.view_format) + for b in ('next_button', 'prev_button'): + x = getattr(self, b, None) + if x is not None: + disconnect(x.clicked) if __name__ == '__main__': from calibre.library import db @@ -935,38 +939,17 @@ if __name__ == '__main__': d.reject() del d - for i in range(3): + for i in range(5): gc.collect() before = memory() - gc.collect() d = MetadataSingleDialog(None, 4, db) - d.break_cycles() d.reject() + d.break_cycles() del d - for i in range(3): + for i in range(5): gc.collect() print 'Used memory:', memory(before)/1024.**2, 'MB' - gc.collect() - ''' - nmap, omap = {}, {} - for x in objects: - omap[id(x)] = x - for x in nobjects: - nmap[id(x)] = x - new_ids = set(nmap.keys()) - set(omap.keys()) - print "New ids:", len(new_ids) - for i in new_ids: - o = nmap[i] - if o is objects: - continue - print repr(o)[:1050] - refs = gc.get_referrers(o) - for r in refs: - if r is objects or r is nobjects: - continue - print '\t', r - ''' From c11bbe3eea7c6003787b66b44e19a280686228ee Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Dec 2010 22:06:37 -0700 Subject: [PATCH 5/6] Fix string.letters in non english locales --- src/calibre/library/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/__init__.py b/src/calibre/library/__init__.py index 8ff23c0a0a..177c5063ac 100644 --- a/src/calibre/library/__init__.py +++ b/src/calibre/library/__init__.py @@ -19,12 +19,15 @@ def generate_test_db(library_path, max_tags=10 ): import random, string, os, sys, time + from calibre.constants import preferred_encoding if not os.path.exists(library_path): os.makedirs(library_path) + letters = string.letters.decode(preferred_encoding) + def randstr(length): - return ''.join(random.choice(string.letters) for i in + return ''.join(random.choice(letters) for i in xrange(length)) all_tags = [randstr(tag_length) for j in xrange(num_of_tags)] From d6f5f634eacced09f7208613df58952f1bfe95e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Dec 2010 23:17:13 -0700 Subject: [PATCH 6/6] Allow changing the font used in the calibre interface via Preferences->Look and feel --- src/calibre/gui2/__init__.py | 7 ++- src/calibre/gui2/preferences/look_feel.py | 59 +++++++++++++++++++++-- src/calibre/gui2/preferences/look_feel.ui | 16 +++++- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 57ca2a1880..f96c64080d 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -9,7 +9,7 @@ from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \ QByteArray, QTranslator, QCoreApplication, QThread, \ QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \ QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ - QIcon, QApplication, QDialog, QPushButton, QUrl + QIcon, QApplication, QDialog, QPushButton, QUrl, QFont ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' @@ -52,6 +52,7 @@ gprefs.defaults['show_splash_screen'] = True gprefs.defaults['toolbar_icon_size'] = 'medium' gprefs.defaults['toolbar_text'] = 'auto' gprefs.defaults['show_child_bar'] = False +gprefs.defaults['font'] = None # }}} @@ -613,6 +614,10 @@ class Application(QApplication): qt_app = self self._file_open_paths = [] self._file_open_lock = RLock() + self.original_font = QFont(QApplication.font()) + fi = gprefs['font'] + if fi is not None: + QApplication.setFont(QFont(*fi)) def _send_file_open_events(self): with self._file_open_lock: diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 10c2fcfe95..b2ba87d1e0 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -5,10 +5,11 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from PyQt4.Qt import QApplication, QFont, QFontInfo, QFontDialog from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.look_feel_ui import Ui_Form -from calibre.gui2 import config, gprefs +from calibre.gui2 import config, gprefs, qt_app from calibre.utils.localization import available_translations, \ get_language, get_lang from calibre.utils.config import prefs @@ -56,12 +57,64 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): (_('Never'), 'never')] r('toolbar_text', gprefs, choices=choices) + self.current_font = None + self.change_font_button.clicked.connect(self.change_font) + + + def initialize(self): + ConfigWidgetBase.initialize(self) + self.current_font = gprefs['font'] + self.update_font_display() + + def restore_defaults(self): + ConfigWidgetBase.restore_defaults(self) + ofont = self.current_font + self.current_font = None + if ofont is not None: + self.changed_signal.emit() + self.update_font_display() + + def build_font_obj(self): + font_info = self.current_font + if font_info is not None: + font = QFont(*font_info) + else: + font = qt_app.original_font + return font + + def update_font_display(self): + font = self.build_font_obj() + fi = QFontInfo(font) + name = unicode(fi.family()) + + self.font_display.setFont(font) + self.font_display.setText(_('Current font:') + ' ' + name + + ' [%dpt]'%fi.pointSize()) + + def change_font(self, *args): + fd = QFontDialog(self.build_font_obj(), self) + if fd.exec_() == fd.Accepted: + font = fd.selectedFont() + fi = QFontInfo(font) + self.current_font = (unicode(fi.family()), fi.pointSize(), + fi.weight(), fi.italic()) + self.update_font_display() + self.changed_signal.emit() + + def commit(self, *args): + rr = ConfigWidgetBase.commit(self, *args) + if self.current_font != gprefs['font']: + gprefs['font'] = self.current_font + QApplication.setFont(self.font_display.font()) + rr = True + return rr + + def refresh_gui(self, gui): gui.search.search_as_you_type(config['search_as_you_type']) - + self.update_font_display() if __name__ == '__main__': - from PyQt4.Qt import QApplication app = QApplication([]) test_widget('Interface', 'Look & Feel') diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 1de55d51ef..91f45a155f 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -183,7 +183,7 @@ - + Qt::Vertical @@ -196,6 +196,20 @@ + + + + true + + + + + + + Change &font (needs restart) + + +