From d0b195164604c133ba345e2c008ef5d8abe8db56 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 23 Jan 2011 16:01:34 -0500 Subject: [PATCH 1/4] Implement ticket #8504: Save previous filename import patterns and store them in a combo box. --- src/calibre/ebooks/metadata/meta.py | 8 +++++++- src/calibre/gui2/filename_pattern.ui | 16 +++++++++++++--- src/calibre/gui2/widgets.py | 24 ++++++++++++++++-------- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py index cbd9db3f04..b204e08bed 100644 --- a/src/calibre/ebooks/metadata/meta.py +++ b/src/calibre/ebooks/metadata/meta.py @@ -142,7 +142,13 @@ def metadata_from_filename(name, pat=None): name = name.rpartition('.')[0] mi = MetaInformation(None, None) if pat is None: - pat = re.compile(prefs.get('filename_pattern')) + pat_re = prefs.get('filename_pattern') + if isinstance(pat_re, list): + if pat_re: + pat_re = pat_re[0] + else: + pat_re = '' + pat = re.compile(pat_re) name = name.replace('_', ' ') match = pat.search(name) if match is not None: diff --git a/src/calibre/gui2/filename_pattern.ui b/src/calibre/gui2/filename_pattern.ui index d120ca80b2..e2367c8ceb 100644 --- a/src/calibre/gui2/filename_pattern.ui +++ b/src/calibre/gui2/filename_pattern.ui @@ -43,7 +43,17 @@ p, li { white-space: pre-wrap; } - + + + true + + + 10 + + + QComboBox::InsertAtTop + + @@ -94,8 +104,8 @@ p, li { white-space: pre-wrap; } 0 0 - 301 - 234 + 277 + 276 diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 9e117822e4..a433a6e5d7 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -67,17 +67,21 @@ class FilenamePattern(QWidget, Ui_Form): self.setupUi(self) self.connect(self.test_button, SIGNAL('clicked()'), self.do_test) - self.connect(self.re, SIGNAL('returnPressed()'), self.do_test) - self.initialize() - self.re.textChanged.connect(lambda x: self.changed_signal.emit()) + self.connect(self.re.lineEdit(), SIGNAL('returnPressed()'), self.do_test) + self.re.lineEdit().textChanged.connect(lambda x: self.changed_signal.emit()) def initialize(self, defaults=False): if defaults: val = prefs.defaults['filename_pattern'] else: val = prefs['filename_pattern'] - self.re.setText(val) - + if isinstance(val, list): + if len(val) > 0: + for v in val: + self.re.addItem(v) + self.re.setCurrentIndex(0) + else: + self.re.lineEdit().setText(val if val else '') def do_test(self): try: @@ -110,12 +114,16 @@ class FilenamePattern(QWidget, Ui_Form): def pattern(self): - pat = unicode(self.re.text()) + pat = unicode(self.re.lineEdit().text()) return re.compile(pat) def commit(self): - pat = self.pattern().pattern - prefs['filename_pattern'] = pat + pat = [] + patterns = [unicode(self.re.lineEdit().text())] + [unicode(self.re.itemText(i)) for i in xrange(self.re.count())] + for p in patterns[:14]: + if p not in pat: + pat.append(p) + prefs['filename_pattern'] = pat return pat From 94fb463ab2af8d0350e4d2652a6f13e5adce55d9 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 23 Jan 2011 17:46:41 -0500 Subject: [PATCH 2/4] Heuristics, italicize common cases: Enhance pattern matching to match punctuation after pattern. --- src/calibre/ebooks/conversion/utils.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index aabb1b8bc4..ad7f5f117d 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -137,17 +137,17 @@ class HeuristicProcessor(object): ] ITALICIZE_STYLE_PATS = [ - r'(?msu)(?<=\s)_(?P\S[^_]{0,40}?\S)?_(?=\s)', - r'(?msu)(?<=\s)/(?P\S[^/]{0,40}?\S)?/(?=\s)', - r'(?msu)(?<=\s)~~(?P\S[^~]{0,40}?\S)?~~(?=\s)', - r'(?msu)(?<=\s)\*(?P\S[^\*]{0,40}?\S)?\*(?=\s)', - r'(?msu)(?<=\s)~(?P\S[^~]{0,40}?\S)?~(?=\s)', - r'(?msu)(?<=\s)_/(?P\S[^/_]{0,40}?\S)?/_(?=\s)', - r'(?msu)(?<=\s)_\*(?P\S[^\*_]{0,40}?\S)?\*_(?=\s)', - r'(?msu)(?<=\s)\*/(?P\S[^/\*]{0,40}?\S)?/\*(?=\s)', - r'(?msu)(?<=\s)_\*/(?P\S[^\*_]{0,40}?\S)?/\*_(?=\s)', - r'(?msu)(?<=\s)/:(?P\S[^:/]{0,40}?\S)?:/(?=\s)', - r'(?msu)(?<=\s)\|:(?P\S[^:\|]{0,40}?\S)?:\|(?=\s)', + r'(?msu)(?<=\s)_(?P\S[^_]{0,40}?\S)?_(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)/(?P\S[^/]{0,40}?\S)?/(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)~~(?P\S[^~]{0,40}?\S)?~~(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)\*(?P\S[^\*]{0,40}?\S)?\*(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)~(?P\S[^~]{0,40}?\S)?~(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)_/(?P\S[^/_]{0,40}?\S)?/_(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)_\*(?P\S[^\*_]{0,40}?\S)?\*_(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)\*/(?P\S[^/\*]{0,40}?\S)?/\*(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)_\*/(?P\S[^\*_]{0,40}?\S)?/\*_(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)/:(?P\S[^:/]{0,40}?\S)?:/(?=[\s\.,\!\?])', + r'(?msu)(?<=\s)\|:(?P\S[^:\|]{0,40}?\S)?:\|(?=[\s\.,\!\?])', ] for word in ITALICIZE_WORDS: From 156dc57e9996862a1a41a6ff5d48c286e7bb74e1 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 23 Jan 2011 20:06:59 -0500 Subject: [PATCH 3/4] GUI add regex: Store history in gprefs instead of in filename_pattern. --- src/calibre/ebooks/metadata/meta.py | 8 +----- src/calibre/gui2/widgets.py | 41 ++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py index b204e08bed..cbd9db3f04 100644 --- a/src/calibre/ebooks/metadata/meta.py +++ b/src/calibre/ebooks/metadata/meta.py @@ -142,13 +142,7 @@ def metadata_from_filename(name, pat=None): name = name.rpartition('.')[0] mi = MetaInformation(None, None) if pat is None: - pat_re = prefs.get('filename_pattern') - if isinstance(pat_re, list): - if pat_re: - pat_re = pat_re[0] - else: - pat_re = '' - pat = re.compile(pat_re) + pat = re.compile(prefs.get('filename_pattern')) name = name.replace('_', ' ') match = pat.search(name) if match is not None: diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index a433a6e5d7..6380eab0b2 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -71,17 +71,27 @@ class FilenamePattern(QWidget, Ui_Form): self.re.lineEdit().textChanged.connect(lambda x: self.changed_signal.emit()) def initialize(self, defaults=False): + # Get all itmes in the combobox. If we are resting + # to defaults we don't want to lose what the user + # has added. + val_hist = [unicode(self.re.lineEdit().text())] + [unicode(self.re.itemText(i)) for i in xrange(self.re.count())] + self.re.clear() + if defaults: val = prefs.defaults['filename_pattern'] else: val = prefs['filename_pattern'] - if isinstance(val, list): - if len(val) > 0: - for v in val: - self.re.addItem(v) - self.re.setCurrentIndex(0) - else: - self.re.lineEdit().setText(val if val else '') + self.re.lineEdit().setText(val) + + val_hist += gprefs.get('filename_pattern_history', ['(?P.+)', '(?P<author>[^_-]+) -?\s*(?P<series>[^_0-9-]*)(?P<series_index>[0-9]*)\s*-\s*(?P<title>[^_].+) ?']) + if val in val_hist: + del val_hist[val_hist.index(val)] + val_hist.insert(0, val) + for v in val_hist: + # Ensure we don't have duplicate items. + if v and self.re.findText(v) == -1: + self.re.addItem(v) + self.re.setCurrentIndex(0) def do_test(self): try: @@ -118,12 +128,17 @@ class FilenamePattern(QWidget, Ui_Form): return re.compile(pat) def commit(self): - pat = [] - patterns = [unicode(self.re.lineEdit().text())] + [unicode(self.re.itemText(i)) for i in xrange(self.re.count())] - for p in patterns[:14]: - if p not in pat: - pat.append(p) - prefs['filename_pattern'] = pat + pat = self.pattern().pattern + prefs['filename_pattern'] = pat + + history = [] + history_pats = [unicode(self.re.lineEdit().text())] + [unicode(self.re.itemText(i)) for i in xrange(self.re.count())] + for p in history_pats[:14]: + # Ensure we don't have duplicate items. + if p and p not in history: + history.append(p) + gprefs['filename_pattern_history'] = history + return pat From 66fdb25b288c864c06d6ac4fee07c228a21155c1 Mon Sep 17 00:00:00 2001 From: John Schember <john@nachtimwald.com> Date: Sun, 23 Jan 2011 21:28:44 -0500 Subject: [PATCH 4/4] GUI, Search and Replace: Cache the conversion document used in the wizard so we don't have to run the it multiple times as the user sets sr 1, 2 and 3. --- src/calibre/gui2/convert/regex_builder.py | 29 +++++++++++++++---- .../gui2/convert/search_and_replace.py | 13 +++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/convert/regex_builder.py b/src/calibre/gui2/convert/regex_builder.py index bdd219d733..d3cb82465a 100644 --- a/src/calibre/gui2/convert/regex_builder.py +++ b/src/calibre/gui2/convert/regex_builder.py @@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en' import re -from PyQt4.QtCore import SIGNAL, Qt +from PyQt4.QtCore import SIGNAL, Qt, pyqtSignal from PyQt4.QtGui import QDialog, QWidget, QDialogButtonBox, \ QBrush, QTextCursor, QTextEdit @@ -19,8 +19,8 @@ from calibre.gui2.dialogs.choose_format import ChooseFormatDialog class RegexBuilder(QDialog, Ui_RegexBuilder): - def __init__(self, db, book_id, regex, *args): - QDialog.__init__(self, *args) + def __init__(self, db, book_id, regex, doc=None, parent=None): + QDialog.__init__(self, parent) self.setupUi(self) self.regex.setText(regex) @@ -28,9 +28,13 @@ class RegexBuilder(QDialog, Ui_RegexBuilder): if not db or not book_id: self.button_box.addButton(QDialogButtonBox.Open) - elif not self.select_format(db, book_id): + elif not doc and not self.select_format(db, book_id): self.cancelled = True return + + if doc: + self.preview.setPlainText(doc) + self.cancelled = False self.connect(self.button_box, SIGNAL('clicked(QAbstractButton*)'), self.button_clicked) self.connect(self.regex, SIGNAL('textChanged(QString)'), self.regex_valid) @@ -152,25 +156,37 @@ class RegexBuilder(QDialog, Ui_RegexBuilder): self.open_book(files[0]) if button == self.button_box.button(QDialogButtonBox.Ok): self.accept() + + def doc(self): + return unicode(self.preview.toPlainText()) class RegexEdit(QWidget, Ui_Edit): + doc_update = pyqtSignal(unicode) + def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self.book_id = None self.db = None + self.doc_cache = None self.connect(self.button, SIGNAL('clicked()'), self.builder) def builder(self): - bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self) + bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self.doc_cache, self) if bld.cancelled: return + if not self.doc_cache: + self.doc_cache = bld.doc() + self.doc_update.emit(self.doc_cache) if bld.exec_() == bld.Accepted: self.edit.setText(bld.regex.text()) + def doc(self): + return self.doc_cache + def setObjectName(self, *args): QWidget.setObjectName(self, *args) if hasattr(self, 'edit'): @@ -184,6 +200,9 @@ class RegexEdit(QWidget, Ui_Edit): def set_db(self, db): self.db = db + + def set_doc(self, doc): + self.doc_cache = doc def break_cycles(self): self.db = None diff --git a/src/calibre/gui2/convert/search_and_replace.py b/src/calibre/gui2/convert/search_and_replace.py index ba156c5b2a..9c10ef667f 100644 --- a/src/calibre/gui2/convert/search_and_replace.py +++ b/src/calibre/gui2/convert/search_and_replace.py @@ -34,14 +34,27 @@ class SearchAndReplaceWidget(Widget, Ui_Form): self.opt_sr3_search.set_msg(_('&Search Regular Expression')) self.opt_sr3_search.set_book_id(book_id) self.opt_sr3_search.set_db(db) + + self.opt_sr1_search.doc_update.connect(self.update_doc) + self.opt_sr2_search.doc_update.connect(self.update_doc) + self.opt_sr3_search.doc_update.connect(self.update_doc) def break_cycles(self): Widget.break_cycles(self) + + self.opt_sr1_search.doc_update.disconnect() + self.opt_sr2_search.doc_update.disconnect() + self.opt_sr3_search.doc_update.disconnect() self.opt_sr1_search.break_cycles() self.opt_sr2_search.break_cycles() self.opt_sr3_search.break_cycles() + def update_doc(self, doc): + self.opt_sr1_search.set_doc(doc) + self.opt_sr2_search.set_doc(doc) + self.opt_sr3_search.set_doc(doc) + def pre_commit_check(self): for x in ('sr1_search', 'sr2_search', 'sr3_search'): x = getattr(self, 'opt_'+x)