From 7e493d1b01abb73d15211a582f78c50a53962dad Mon Sep 17 00:00:00 2001
From: John Schember
Date: Mon, 29 Jun 2009 07:05:47 -0400
Subject: [PATCH 01/31] Search for empty fields using location:none.
---
src/calibre/library/database2.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 3550253ffa..1f52f55526 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -208,6 +208,9 @@ class ResultCache(SearchQueryParser):
for item in self._data:
if item is None: continue
for loc in location:
+ if (not item[loc] or item[loc] == [] or item[loc] == 0 or item[loc] == '') and query == 'none':
+ matches.add(item[0])
+ break
if item[loc] and query in item[loc].lower():
matches.add(item[0])
break
From 6b8905d272ca7d4231292b41c0098b2337e32827 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Mon, 29 Jun 2009 21:28:23 -0400
Subject: [PATCH 02/31] Search by rating:#. Also include rating in default
search.
---
src/calibre/library/database2.py | 13 +++++++++++--
src/calibre/utils/search_query_parser.py | 1 +
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 1f52f55526..389cdba8a0 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -198,22 +198,31 @@ class ResultCache(SearchQueryParser):
query = query.decode('utf-8')
if location in ('tag', 'author', 'format'):
location += 's'
- all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', 'formats', 'isbn')
+ all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', 'formats', 'isbn', 'rating')
MAP = {}
for x in all:
MAP[x] = FIELD_MAP[x]
+ EXCLUDE_FIELDS = [MAP['rating']]
location = [location] if location != 'all' else list(MAP.keys())
for i, loc in enumerate(location):
location[i] = MAP[loc]
+ try:
+ rating_query = int(query) * 2
+ except:
+ rating_query = None
for item in self._data:
if item is None: continue
for loc in location:
if (not item[loc] or item[loc] == [] or item[loc] == 0 or item[loc] == '') and query == 'none':
matches.add(item[0])
break
- if item[loc] and query in item[loc].lower():
+ if rating_query and item[loc] and loc == MAP['rating'] and rating_query == int(item[loc]):
matches.add(item[0])
break
+ if item[loc] and loc not in EXCLUDE_FIELDS and query in item[loc].lower():
+ matches.add(item[0])
+ break
+
return matches
def remove(self, id):
diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py
index 35241c89c4..369afed9b5 100644
--- a/src/calibre/utils/search_query_parser.py
+++ b/src/calibre/utils/search_query_parser.py
@@ -50,6 +50,7 @@ class SearchQueryParser(object):
'author',
'publisher',
'series',
+ 'rating',
'comments',
'format',
'isbn',
From 840d5670785e714b6370c9c03105fb5b5996cc9a Mon Sep 17 00:00:00 2001
From: John Schember
Date: Tue, 30 Jun 2009 21:14:07 -0400
Subject: [PATCH 03/31] Search for empty covers using cover:none
---
src/calibre/library/database2.py | 39 +++++++++++++-----------
src/calibre/utils/search_query_parser.py | 1 +
2 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 389cdba8a0..651d9788d7 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -51,7 +51,7 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15,
- 'lccn':16, 'pubdate':17, 'flags':18}
+ 'lccn':16, 'pubdate':17, 'flags':18, 'cover':19}
INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys()))
@@ -198,11 +198,11 @@ class ResultCache(SearchQueryParser):
query = query.decode('utf-8')
if location in ('tag', 'author', 'format'):
location += 's'
- all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', 'formats', 'isbn', 'rating')
+ all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', 'formats', 'isbn', 'rating', 'cover')
MAP = {}
for x in all:
MAP[x] = FIELD_MAP[x]
- EXCLUDE_FIELDS = [MAP['rating']]
+ EXCLUDE_FIELDS = [MAP['rating'], MAP['cover']]
location = [location] if location != 'all' else list(MAP.keys())
for i, loc in enumerate(location):
location[i] = MAP[loc]
@@ -254,15 +254,16 @@ class ResultCache(SearchQueryParser):
pass
return False
- def refresh_ids(self, conn, ids):
+ def refresh_ids(self, db, ids):
'''
Refresh the data in the cache for books identified by ids.
Returns a list of affected rows or None if the rows are filtered.
'''
for id in ids:
try:
- self._data[id] = conn.get('SELECT * from meta WHERE id=?',
+ self._data[id] = db.conn.get('SELECT * from meta WHERE id=?',
(id,))[0]
+ self._data[id].append(db.cover(id, index_is_id=True, as_path=True))
except IndexError:
return None
try:
@@ -271,12 +272,13 @@ class ResultCache(SearchQueryParser):
pass
return None
- def books_added(self, ids, conn):
+ def books_added(self, ids, db):
if not ids:
return
self._data.extend(repeat(None, max(ids)-len(self._data)+2))
for id in ids:
- self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
+ self._data[id] = db.conn.get('SELECT * from meta WHERE id=?', (id,))[0]
+ self._data[id].append(db.cover(id, index_is_id=True, as_path=True))
self._map[0:0] = ids
self._map_filtered[0:0] = ids
@@ -294,6 +296,9 @@ class ResultCache(SearchQueryParser):
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
for r in temp:
self._data[r[0]] = r
+ for item in self._data:
+ if item is not None:
+ item.append(db.cover(item[0], index_is_id=True, as_path=True))
self._map = [i[0] for i in self._data if i is not None]
if field is not None:
self.sort(field, ascending)
@@ -412,7 +417,7 @@ class LibraryDatabase2(LibraryDatabase):
self.refresh = functools.partial(self.data.refresh, self)
self.sort = self.data.sort
self.index = self.data.index
- self.refresh_ids = functools.partial(self.data.refresh_ids, self.conn)
+ self.refresh_ids = functools.partial(self.data.refresh_ids, self)
self.row = self.data.row
self.has_id = self.data.has_id
self.count = self.data.count
@@ -1024,7 +1029,7 @@ class LibraryDatabase2(LibraryDatabase):
self.set_rating(id, val, notify=False)
elif column == 'tags':
self.set_tags(id, val.split(','), append=False, notify=False)
- self.data.refresh_ids(self.conn, [id])
+ self.data.refresh_ids(self, [id])
self.set_path(id, True)
self.notify('metadata', [id])
@@ -1203,7 +1208,7 @@ class LibraryDatabase2(LibraryDatabase):
if id:
self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id))
self.conn.commit()
- self.data.refresh_ids(self.conn, [book_id])
+ self.data.refresh_ids(self, [book_id])
if notify:
self.notify('metadata', [id])
@@ -1308,7 +1313,7 @@ class LibraryDatabase2(LibraryDatabase):
obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)',
(mi.title, mi.authors[0]))
id = obj.lastrowid
- self.data.books_added([id], self.conn)
+ self.data.books_added([id], self)
self.set_path(id, index_is_id=True)
self.conn.commit()
self.set_metadata(id, mi)
@@ -1317,7 +1322,7 @@ class LibraryDatabase2(LibraryDatabase):
if not hasattr(path, 'read'):
stream.close()
self.conn.commit()
- self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size
+ self.data.refresh_ids(self, [id]) # Needed to update format list and size
return id
def run_import_plugins(self, path_or_stream, format):
@@ -1345,7 +1350,7 @@ class LibraryDatabase2(LibraryDatabase):
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
(title, series_index, aus))
id = obj.lastrowid
- self.data.books_added([id], self.conn)
+ self.data.books_added([id], self)
self.set_path(id, True)
self.conn.commit()
self.set_metadata(id, mi)
@@ -1378,7 +1383,7 @@ class LibraryDatabase2(LibraryDatabase):
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
(title, series_index, aus))
id = obj.lastrowid
- self.data.books_added([id], self.conn)
+ self.data.books_added([id], self)
ids.append(id)
self.set_path(id, True)
self.conn.commit()
@@ -1389,7 +1394,7 @@ class LibraryDatabase2(LibraryDatabase):
self.add_format(id, format, stream, index_is_id=True)
stream.close()
self.conn.commit()
- self.data.refresh_ids(self.conn, ids) # Needed to update format list and size
+ self.data.refresh_ids(self, ids) # Needed to update format list and size
if duplicates:
paths = list(duplicate[0] for duplicate in duplicates)
formats = list(duplicate[1] for duplicate in duplicates)
@@ -1411,7 +1416,7 @@ class LibraryDatabase2(LibraryDatabase):
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
(title, series_index, aus))
id = obj.lastrowid
- self.data.books_added([id], self.conn)
+ self.data.books_added([id], self)
self.set_path(id, True)
self.set_metadata(id, mi)
for path in formats:
@@ -1420,7 +1425,7 @@ class LibraryDatabase2(LibraryDatabase):
continue
self.add_format_with_hooks(id, ext, path, index_is_id=True)
self.conn.commit()
- self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size
+ self.data.refresh_ids(self, [id]) # Needed to update format list and size
if notify:
self.notify('add', [id])
diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py
index 369afed9b5..425b4c2d49 100644
--- a/src/calibre/utils/search_query_parser.py
+++ b/src/calibre/utils/search_query_parser.py
@@ -51,6 +51,7 @@ class SearchQueryParser(object):
'publisher',
'series',
'rating',
+ 'cover',
'comments',
'format',
'isbn',
From 4bf1a46acd25346272a53e171176640ad181e0bc Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 1 Jul 2009 06:42:12 -0400
Subject: [PATCH 04/31] Enhance used and empty fields search with :false and
:true instead of :none
---
src/calibre/library/database2.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 651d9788d7..edf0071a20 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -213,7 +213,10 @@ class ResultCache(SearchQueryParser):
for item in self._data:
if item is None: continue
for loc in location:
- if (not item[loc] or item[loc] == [] or item[loc] == 0 or item[loc] == '') and query == 'none':
+ if query == 'false' and (not item[loc] or item[loc].strip() == ''):
+ matches.add(item[0])
+ break
+ if query == 'true' and (item[loc] and item[loc].strip() != ''):
matches.add(item[0])
break
if rating_query and item[loc] and loc == MAP['rating'] and rating_query == int(item[loc]):
From 88beebd0a3a389fcc5c51ed8dcddd87c6d017694 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 1 Jul 2009 06:54:13 -0400
Subject: [PATCH 05/31] Search: use has_cover instead of cover.
---
src/calibre/library/database2.py | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index edf0071a20..43bd6e6434 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -213,10 +213,16 @@ class ResultCache(SearchQueryParser):
for item in self._data:
if item is None: continue
for loc in location:
- if query == 'false' and (not item[loc] or item[loc].strip() == ''):
+ if query == 'false' and not item[loc]:
+ if isinstance(item[loc], basestring):
+ if item[loc].strip() != '':
+ continue
matches.add(item[0])
break
- if query == 'true' and (item[loc] and item[loc].strip() != ''):
+ if query == 'true' and item[loc]:
+ if isinstance(item[loc], basestring):
+ if item[loc].strip() == '':
+ continue
matches.add(item[0])
break
if rating_query and item[loc] and loc == MAP['rating'] and rating_query == int(item[loc]):
@@ -266,7 +272,7 @@ class ResultCache(SearchQueryParser):
try:
self._data[id] = db.conn.get('SELECT * from meta WHERE id=?',
(id,))[0]
- self._data[id].append(db.cover(id, index_is_id=True, as_path=True))
+ self._data[id].append(db.has_cover(id, index_is_id=True))
except IndexError:
return None
try:
@@ -281,7 +287,7 @@ class ResultCache(SearchQueryParser):
self._data.extend(repeat(None, max(ids)-len(self._data)+2))
for id in ids:
self._data[id] = db.conn.get('SELECT * from meta WHERE id=?', (id,))[0]
- self._data[id].append(db.cover(id, index_is_id=True, as_path=True))
+ self._data[id].append(db.has_cover(id, index_is_id=True))
self._map[0:0] = ids
self._map_filtered[0:0] = ids
@@ -301,7 +307,7 @@ class ResultCache(SearchQueryParser):
self._data[r[0]] = r
for item in self._data:
if item is not None:
- item.append(db.cover(item[0], index_is_id=True, as_path=True))
+ item.append(db.has_cover(item[0], index_is_id=True))
self._map = [i[0] for i in self._data if i is not None]
if field is not None:
self.sort(field, ascending)
From be516ba66b35fa5f37a85f1db8c98b94cd77073d Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 1 Jul 2009 07:42:39 -0400
Subject: [PATCH 06/31] GUI: bulk convert when multiple books selected.
---
src/calibre/gui2/main.py | 31 ++++++++-----------------------
1 file changed, 8 insertions(+), 23 deletions(-)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index e3aa0e6b8c..1fad75fb96 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -312,11 +312,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
cm.addAction(_('Bulk convert'))
self.action_convert.setMenu(cm)
QObject.connect(cm.actions()[0],
- SIGNAL('triggered(bool)'), self.convert_single)
+ SIGNAL('triggered(bool)'), partial(self.convert_ebook, bulk=False))
QObject.connect(cm.actions()[1],
- SIGNAL('triggered(bool)'), self.convert_bulk)
+ SIGNAL('triggered(bool)'), partial(self.convert_ebook, bulk=True))
QObject.connect(self.action_convert,
- SIGNAL('triggered(bool)'), self.convert_single)
+ SIGNAL('triggered(bool)'), partial(self.convert_ebook, bulk=False))
self.convert_menu = cm
pm = QMenu()
@@ -1156,32 +1156,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
return None
return [self.library_view.model().db.id(r) for r in rows]
- def convert_bulk(self, checked):
+ def convert_ebook(self, checked, bulk=None):
book_ids = self.get_books_for_conversion()
if book_ids is None: return
previous = self.library_view.currentIndex()
rows = [x.row() for x in \
self.library_view.selectionModel().selectedRows()]
- jobs, changed, bad = convert_bulk_ebook(self,
+ if bulk or (not bulk and len(book_ids) > 1):
+ jobs, changed, bad = convert_bulk_ebook(self,
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
- for func, args, desc, fmt, id, temp_files in jobs:
- if id not in bad:
- job = self.job_manager.run_job(Dispatcher(self.book_converted),
- func, args=args, description=desc)
- self.conversion_jobs[job] = (temp_files, fmt, id)
-
- if changed:
- self.library_view.model().refresh_rows(rows)
- current = self.library_view.currentIndex()
- self.library_view.model().current_changed(current, previous)
-
- def convert_single(self, checked):
- book_ids = self.get_books_for_conversion()
- if book_ids is None: return
- previous = self.library_view.currentIndex()
- rows = [x.row() for x in \
- self.library_view.selectionModel().selectedRows()]
- jobs, changed, bad = convert_single_ebook(self,
+ else:
+ jobs, changed, bad = convert_single_ebook(self,
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
for func, args, desc, fmt, id, temp_files in jobs:
if id not in bad:
From 03fe8b6261c6b0fd804b33f57fa5466774d21623 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 1 Jul 2009 07:52:57 -0400
Subject: [PATCH 07/31] GUI: Convert, allow single convert when specifically
selected.
---
src/calibre/gui2/main.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 1fad75fb96..b1e75a7a9f 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -316,7 +316,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
QObject.connect(cm.actions()[1],
SIGNAL('triggered(bool)'), partial(self.convert_ebook, bulk=True))
QObject.connect(self.action_convert,
- SIGNAL('triggered(bool)'), partial(self.convert_ebook, bulk=False))
+ SIGNAL('triggered(bool)'), partial(self.convert_ebook))
self.convert_menu = cm
pm = QMenu()
@@ -1162,7 +1162,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
previous = self.library_view.currentIndex()
rows = [x.row() for x in \
self.library_view.selectionModel().selectedRows()]
- if bulk or (not bulk and len(book_ids) > 1):
+ if bulk or (bulk is None and len(book_ids) > 1):
jobs, changed, bad = convert_bulk_ebook(self,
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
else:
From 8c265f4bea2dd2d3da21601825bf97fdebf8df5f Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 1 Jul 2009 08:17:40 -0400
Subject: [PATCH 08/31] Implement bug #2754: Authors drop down list in edit
metadata.
---
src/calibre/gui2/dialogs/metadata_single.py | 38 ++++++++++++++-------
src/calibre/gui2/dialogs/metadata_single.ui | 14 ++++----
src/calibre/gui2/widgets.py | 2 ++
src/calibre/library/database.py | 4 +++
4 files changed, 39 insertions(+), 19 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index d25d0609c8..2ddbec7f20 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -265,12 +265,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if not isbn:
isbn = ''
self.isbn.setText(isbn)
- au = self.db.authors(row)
- if au:
- au = [a.strip().replace('|', ',') for a in au.split(',')]
- self.authors.setText(authors_to_string(au))
- else:
- self.authors.setText('')
aus = self.db.author_sort(row)
self.author_sort.setText(aus if aus else '')
tags = self.db.tags(row)
@@ -295,7 +289,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
Format(self.formats, ext, size)
- self.initialize_series_and_publisher()
+ self.initialize_combos()
self.series_index.setValue(self.db.series_index(row))
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index)
@@ -331,6 +325,30 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def cover_dropped(self):
self.cover_changed = True
+ def initialize_combos(self):
+ self.initalize_authors()
+ self.initialize_series()
+ self.initialize_publisher()
+
+ self.layout().activate()
+
+ def initalize_authors(self):
+ all_authors = self.db.all_authors()
+ all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+ author_id = self.db.author_id(self.row)
+ idx, c = None, 0
+ for i in all_authors:
+ id, name = i
+ if id == author_id:
+ idx = c
+ name = [name.strip().replace('|', ',') for n in name.split(',')]
+ self.authors.addItem(authors_to_string(name))
+ c += 1
+
+ self.authors.setEditText('')
+ if idx is not None:
+ self.authors.setCurrentIndex(idx)
+
def initialize_series(self):
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
all_series = self.db.all_series()
@@ -349,8 +367,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.series.setCurrentIndex(idx)
self.enable_series_index()
- def initialize_series_and_publisher(self):
- self.initialize_series()
+ def initialize_publisher(self):
all_publishers = self.db.all_publishers()
all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
publisher_id = self.db.publisher_id(self.row)
@@ -366,9 +383,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if idx is not None:
self.publisher.setCurrentIndex(idx)
-
- self.layout().activate()
-
def edit_tags(self):
d = TagEditor(self, self.db, self.row)
d.exec_()
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui
index bbf1bb0f7b..ff98d22ad3 100644
--- a/src/calibre/gui2/dialogs/metadata_single.ui
+++ b/src/calibre/gui2/dialogs/metadata_single.ui
@@ -121,9 +121,6 @@
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
- authors
-
-
@@ -345,9 +342,6 @@
- -
-
-
-
@@ -371,6 +365,13 @@
+ -
+
+
+ true
+
+
+
@@ -655,7 +656,6 @@
title
swap_button
- authors
author_sort
auto_author_sort
rating
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index 3f7734f8c9..abfe137b99 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -493,6 +493,8 @@ class EnComboBox(QComboBox):
QComboBox.__init__(self, *args)
self.setLineEdit(EnLineEdit(self))
+ def text(self):
+ return qstring_to_unicode(self.currentText())
class PythonHighlighter(QSyntaxHighlighter):
diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py
index 72b629db0b..ed92853df2 100644
--- a/src/calibre/library/database.py
+++ b/src/calibre/library/database.py
@@ -928,6 +928,10 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
except:
pass
+ def author_id(self, index, index_is_id=False):
+ id = index if index_is_id else self.id(index)
+ return self.conn.get('SELECT author from books_authors_link WHERE book=?', (id,), all=False)
+
def isbn(self, idx, index_is_id=False):
id = idx if index_is_id else self.id(idx)
return self.conn.get('SELECT isbn FROM books WHERE id=?',(id,), all=False)
From 0427dd55c1e1025527835611f2f77b67cbd54a06 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 1 Jul 2009 20:43:11 -0400
Subject: [PATCH 09/31] Additional options for case menu in line edits.
---
src/calibre/gui2/widgets.py | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index abfe137b99..159101f04d 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -10,7 +10,8 @@ from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
QPixmap, QMovie, QPalette, QTimer, QDialog, \
QAbstractListModel, QVariant, Qt, SIGNAL, \
QRegExp, QSettings, QSize, QModelIndex, \
- QAbstractButton, QPainter, QLineEdit, QComboBox
+ QAbstractButton, QPainter, QLineEdit, QComboBox, \
+ QMenu
from calibre.gui2 import human_readable, NONE, TableView, \
qstring_to_unicode, error_dialog
@@ -460,12 +461,30 @@ class LineEditECM(object):
def contextMenuEvent(self, event):
menu = self.createStandardContextMenu()
menu.addSeparator()
- action_title_case = menu.addAction('Title Case')
+ case_menu = QMenu('Change Case')
+ action_upper_case = case_menu.addAction('Upper Case')
+ action_lower_case = case_menu.addAction('Lower Case')
+ action_swap_case = case_menu.addAction('Swap Case')
+ action_title_case = case_menu.addAction('Title Case')
+
+ self.connect(action_upper_case, SIGNAL('triggered()'), self.upper_case)
+ self.connect(action_lower_case, SIGNAL('triggered()'), self.lower_case)
+ self.connect(action_swap_case, SIGNAL('triggered()'), self.swap_case)
self.connect(action_title_case, SIGNAL('triggered()'), self.title_case)
+ menu.addMenu(case_menu)
menu.exec_(event.globalPos())
+ def upper_case(self):
+ self.setText(qstring_to_unicode(self.text()).upper())
+
+ def lower_case(self):
+ self.setText(qstring_to_unicode(self.text()).lower())
+
+ def swap_case(self):
+ self.setText(qstring_to_unicode(self.text()).swapcase())
+
def title_case(self):
self.setText(qstring_to_unicode(self.text()).title())
From f72f353ec5f6c6b52d1a86073f74a6f32d5209af Mon Sep 17 00:00:00 2001
From: John Schember
Date: Thu, 2 Jul 2009 06:10:27 -0400
Subject: [PATCH 10/31] Make case menu translatable.
---
src/calibre/gui2/widgets.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index 159101f04d..3bcd1f5d3b 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -462,11 +462,11 @@ class LineEditECM(object):
menu = self.createStandardContextMenu()
menu.addSeparator()
- case_menu = QMenu('Change Case')
- action_upper_case = case_menu.addAction('Upper Case')
- action_lower_case = case_menu.addAction('Lower Case')
- action_swap_case = case_menu.addAction('Swap Case')
- action_title_case = case_menu.addAction('Title Case')
+ case_menu = QMenu(_('Change Case'))
+ action_upper_case = case_menu.addAction(_('Upper Case'))
+ action_lower_case = case_menu.addAction(_('Lower Case'))
+ action_swap_case = case_menu.addAction(_('Swap Case'))
+ action_title_case = case_menu.addAction(_('Title Case'))
self.connect(action_upper_case, SIGNAL('triggered()'), self.upper_case)
self.connect(action_lower_case, SIGNAL('triggered()'), self.lower_case)
From 4ad6939e1709bddbcd0433208d8e9053eb175155 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Thu, 2 Jul 2009 18:43:07 -0400
Subject: [PATCH 11/31] Use EnLineEdit in library and device view.
---
src/calibre/gui2/library.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index c3134de917..878f7ddc2f 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -18,6 +18,7 @@ from calibre.ptempfile import PersistentTemporaryFile
from calibre.library.database2 import FIELD_MAP
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \
error_dialog
+from calibre.gui2.widgets import EnLineEdit
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.ebooks.metadata import string_to_authors, fmt_sidx
@@ -110,6 +111,11 @@ class PubDateDelegate(QStyledItemDelegate):
qde.setCalendarPopup(True)
return qde
+class TextDelegate(QStyledItemDelegate):
+
+ def createEditor(self, parent, option, index):
+ editor = EnLineEdit(parent)
+ return editor
class BooksModel(QAbstractTableModel):
headers = {
@@ -659,6 +665,8 @@ class BooksView(TableView):
self.setModel(self._model)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True)
+ for i in range(10):
+ self.setItemDelegateForColumn(i, TextDelegate(self))
try:
cm = self._model.column_map
self.columns_sorted(cm.index('rating') if 'rating' in cm else -1,
@@ -768,7 +776,7 @@ class DeviceBooksView(BooksView):
self.resize_on_select = False
self.rating_delegate = None
for i in range(10):
- self.setItemDelegateForColumn(i, self.itemDelegate())
+ self.setItemDelegateForColumn(i, TextDelegate(self))
self.setDragDropMode(self.NoDragDrop)
self.setAcceptDrops(False)
From 4594402dfc945b364da39c56be85778d7230ad03 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Thu, 2 Jul 2009 21:03:56 -0400
Subject: [PATCH 12/31] Fix bug #2764: Don't append ttt to end of tags for
Kindle metadata page.
---
src/calibre/customize/profiles.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py
index 4d8b8a0113..f641984124 100644
--- a/src/calibre/customize/profiles.py
+++ b/src/calibre/customize/profiles.py
@@ -233,7 +233,7 @@ class KindleOutput(OutputProfile):
@classmethod
def tags_to_string(cls, tags):
- return 'ttt '.join(tags)+'ttt '
+ return ', '.join(tags)
class KindleDXOutput(OutputProfile):
@@ -248,7 +248,7 @@ class KindleDXOutput(OutputProfile):
@classmethod
def tags_to_string(cls, tags):
- return 'ttt '.join(tags)+'ttt '
+ return ', '.join(tags)
output_profiles = [OutputProfile, SonyReaderOutput, MSReaderOutput,
From 8e1f51d8cb907bff4da1215cc505e3524e30795c Mon Sep 17 00:00:00 2001
From: John Schember
Date: Fri, 3 Jul 2009 08:02:52 -0400
Subject: [PATCH 13/31] Revert tag change. The ttt is used for searching.
---
src/calibre/customize/profiles.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py
index f641984124..4d8b8a0113 100644
--- a/src/calibre/customize/profiles.py
+++ b/src/calibre/customize/profiles.py
@@ -233,7 +233,7 @@ class KindleOutput(OutputProfile):
@classmethod
def tags_to_string(cls, tags):
- return ', '.join(tags)
+ return 'ttt '.join(tags)+'ttt '
class KindleDXOutput(OutputProfile):
@@ -248,7 +248,7 @@ class KindleDXOutput(OutputProfile):
@classmethod
def tags_to_string(cls, tags):
- return ', '.join(tags)
+ return 'ttt '.join(tags)+'ttt '
output_profiles = [OutputProfile, SonyReaderOutput, MSReaderOutput,
From eb896d010f4e1661b35664fa51f04e94ac3fa5f3 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sat, 4 Jul 2009 14:14:53 -0400
Subject: [PATCH 14/31] PDF Input: User can specify regex to use to remove
header and footer. Preprocessor: Able to use options from input plugins.
---
src/calibre/ebooks/conversion/plumber.py | 2 +-
src/calibre/ebooks/conversion/preprocess.py | 33 +++++++++++++--------
src/calibre/ebooks/pdf/input.py | 19 +++++++-----
src/calibre/gui2/convert/pdf_input.py | 32 +++++++++++++++++++-
src/calibre/gui2/convert/pdf_input.ui | 30 +++++++++++++++----
5 files changed, 90 insertions(+), 26 deletions(-)
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index 11975094e3..77ae507867 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -694,7 +694,7 @@ def create_oebbook(log, path_or_stream, opts, input_plugin, reader=None,
'''
from calibre.ebooks.oeb.base import OEBBook
html_preprocessor = HTMLPreProcessor(input_plugin.preprocess_html,
- opts.preprocess_html, getattr(opts, 'pdf_line_length', 0.5))
+ opts.preprocess_html, opts)
oeb = OEBBook(log, html_preprocessor,
pretty_print=opts.pretty_print, input_encoding=encoding)
if not populate:
diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py
index 43bb52b8ad..f9788fdba8 100644
--- a/src/calibre/ebooks/conversion/preprocess.py
+++ b/src/calibre/ebooks/conversion/preprocess.py
@@ -140,8 +140,6 @@ class HTMLPreProcessor(object):
(re.compile(u'(?<=[\.,;\?!”"\'])[\s^ ]*(?=<)'), lambda match: ' '),
# Connect paragraphs split by -
(re.compile(u'(?<=[^\s][-–])[\s]*(
)*[\s]*()*\s*(?=[^\s])'), lambda match: ''),
- # Remove - that splits words
- (re.compile(u'(?<=[^\s])[-–]+(?=[^\s])'), lambda match: ''),
# Add space before and after italics
(re.compile(u'(?'), lambda match: ' '),
(re.compile(r'(?=\w)'), lambda match: ' '),
@@ -163,10 +161,10 @@ class HTMLPreProcessor(object):
lambda match : '
%s
'%(match.group(1),)),
]
def __init__(self, input_plugin_preprocess, plugin_preprocess,
- pdf_line_length):
+ extra_opts=None):
self.input_plugin_preprocess = input_plugin_preprocess
self.plugin_preprocess = plugin_preprocess
- self.pdf_line_length = pdf_line_length
+ self.extra_opts = extra_opts
def is_baen(self, src):
return re.compile(r'(i|b|u)>)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines),
- ]
+ start_rules = []
+ end_rules = []
- rules = self.PDFTOHTML + line_length_rules
+ if getattr(self.extra_opts, 'remove_header', None):
+ start_rules.append(
+ (re.compile(getattr(self.extra_opts, 'header_regex')), lambda match : '')
+ )
+ if getattr(self.extra_opts, 'remove_footer', None):
+ start_rules.append(
+ (re.compile(getattr(self.extra_opts, 'footer_regex')), lambda match : '')
+ )
+ if getattr(self.extra_opts, 'unwrap_factor', None):
+ length = line_length(html, getattr(self.extra_opts, 'unwrap_factor'))
+ if length:
+ end_rules.append(
+ # Un wrap using punctuation
+ (re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P(i|b|u)>)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines),
+ )
+
+ rules = start_rules + self.PDFTOHTML + end_rules
else:
rules = []
for rule in self.PREPROCESS + rules:
diff --git a/src/calibre/ebooks/pdf/input.py b/src/calibre/ebooks/pdf/input.py
index 3b82becc1f..e17d50869e 100644
--- a/src/calibre/ebooks/pdf/input.py
+++ b/src/calibre/ebooks/pdf/input.py
@@ -20,10 +20,20 @@ class PDFInput(InputFormatPlugin):
options = set([
OptionRecommendation(name='no_images', recommended_value=False,
help=_('Do not extract images from the document')),
- OptionRecommendation(name='pdf_line_length', recommended_value=0.5,
+ OptionRecommendation(name='unwrap_factor', recommended_value=0.5,
help=_('Scale used to determine the length at which a line should '
'be unwrapped. Valid values are a decimal between 0 and 1. The '
'default is 0.5, this is the median line length.')),
+ OptionRecommendation(name='remove_header', recommended_value=False,
+ help=_('Use a regular expression to try and remove the header.')),
+ OptionRecommendation(name='header_regex',
+ recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)',
+ help=_('The regular expression to use to remove the header.')),
+ OptionRecommendation(name='remove_footer', recommended_value=False,
+ help=_('Use a regular expression to try and remove the footer.')),
+ OptionRecommendation(name='footer_regex',
+ recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)',
+ help=_('The regular expression to use to remove the footer.')),
])
def convert(self, stream, options, file_ext, log,
@@ -42,12 +52,7 @@ class PDFInput(InputFormatPlugin):
images = os.listdir(os.getcwd())
images.remove('index.html')
for i in images:
- # Remove the - from the file name because it causes problems.
- # The reference to the image with the - will be changed to not
- # include it later in the conversion process.
- new_i = i.replace('-', '')
- os.rename(i, new_i)
- manifest.append((new_i, None))
+ manifest.append((i, None))
log.debug('Generating manifest...')
opf.create_manifest(manifest)
diff --git a/src/calibre/gui2/convert/pdf_input.py b/src/calibre/gui2/convert/pdf_input.py
index 71e4bc0ef3..bfd658526c 100644
--- a/src/calibre/gui2/convert/pdf_input.py
+++ b/src/calibre/gui2/convert/pdf_input.py
@@ -4,8 +4,13 @@ __license__ = 'GPL 3'
__copyright__ = '2009, John Schember '
__docformat__ = 'restructuredtext en'
+import re
+
+from PyQt4.Qt import SIGNAL
+
from calibre.gui2.convert.pdf_input_ui import Ui_Form
from calibre.gui2.convert import Widget
+from calibre.gui2 import qstring_to_unicode, error_dialog
class PluginWidget(Widget, Ui_Form):
@@ -14,6 +19,31 @@ class PluginWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, 'pdf_input',
- ['no_images', 'pdf_line_length'])
+ ['no_images', 'unwrap_factor', 'remove_header', 'header_regex',
+ 'remove_footer', 'footer_regex'])
self.db, self.book_id = db, book_id
self.initialize_options(get_option, get_help, db, book_id)
+
+ self.opt_header_regex.setEnabled(self.opt_remove_header.isChecked())
+ self.opt_footer_regex.setEnabled(self.opt_remove_footer.isChecked())
+
+ self.connect(self.opt_remove_header, SIGNAL('stateChanged(int)'), self.header_regex_state)
+ self.connect(self.opt_remove_footer, SIGNAL('stateChanged(int)'), self.footer_regex_state)
+
+ def header_regex_state(self, state):
+ self.opt_header_regex.setEnabled(state)
+
+ def footer_regex_state(self, state):
+ self.opt_footer_regex.setEnabled(state)
+
+ def pre_commit_check(self):
+ for x in ('header_regex', 'footer_regex'):
+ x = getattr(self, 'opt_'+x)
+ try:
+ pat = qstring_to_unicode(x.text())
+ re.compile(pat)
+ except Exception, err:
+ error_dialog(self, _('Invalid regular expression'),
+ _('Invalid regular expression: %s')%err).exec_()
+ return False
+ return True
diff --git a/src/calibre/gui2/convert/pdf_input.ui b/src/calibre/gui2/convert/pdf_input.ui
index 35b840ded0..d34c6d404b 100644
--- a/src/calibre/gui2/convert/pdf_input.ui
+++ b/src/calibre/gui2/convert/pdf_input.ui
@@ -14,14 +14,14 @@
Form
- -
+
-
Line Un-Wrapping Factor:
- -
+
-
Qt::Vertical
@@ -34,8 +34,8 @@
- -
-
+
-
+
1.000000000000000
@@ -47,13 +47,33 @@
- -
+
-
No Images
+ -
+
+
+ Remove Header
+
+
+
+ -
+
+
+ Remove Footer
+
+
+
+ -
+
+
+ -
+
+
From 5dbc4252a77b7a0f42e79d91f6a7e6193dd8c012 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sat, 4 Jul 2009 14:51:05 -0400
Subject: [PATCH 15/31] Plumber: Dummy mode that loads all options from all
formats so that the default values will be set in preferences.
---
src/calibre/ebooks/conversion/plumber.py | 31 ++++++++++++++++++------
src/calibre/gui2/dialogs/config.py | 2 +-
2 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index 77ae507867..e33df27412 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -7,7 +7,8 @@ import os, re, sys
from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.customize.ui import input_profiles, output_profiles, \
- plugin_for_input_format, plugin_for_output_format
+ plugin_for_input_format, plugin_for_output_format, \
+ available_input_formats, available_output_formats
from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre import extract, walk
@@ -50,7 +51,7 @@ class Plumber(object):
'tags', 'book_producer', 'language'
]
- def __init__(self, input, output, log, report_progress=DummyReporter()):
+ def __init__(self, input, output, log, report_progress=DummyReporter(), dummy=False):
'''
:param input: Path to input file.
:param output: Path to output file/directory
@@ -419,12 +420,28 @@ OptionRecommendation(name='list_recipes',
self.input_fmt = input_fmt
self.output_fmt = output_fmt
+
+ self.all_format_options = set()
+ self.input_options = set()
+ self.output_options = set()
# Build set of all possible options. Two options are equal if their
# names are the same.
- self.input_options = self.input_plugin.options.union(
- self.input_plugin.common_options)
- self.output_options = self.output_plugin.options.union(
+ if not dummy:
+ self.input_options = self.input_plugin.options.union(
+ self.input_plugin.common_options)
+ self.output_options = self.output_plugin.options.union(
self.output_plugin.common_options)
+ else:
+ for fmt in available_input_formats():
+ input_plugin = plugin_for_input_format(fmt)
+ if input_plugin:
+ self.all_format_options = self.all_format_options.union(
+ input_plugin.options.union(input_plugin.common_options))
+ for fmt in available_output_formats():
+ output_plugin = plugin_for_output_format(fmt)
+ if output_plugin:
+ self.all_format_options = self.all_format_options.union(
+ output_plugin.options.union(output_plugin.common_options))
# Remove the options that have been disabled by recommendations from the
# plugins.
@@ -469,7 +486,7 @@ OptionRecommendation(name='list_recipes',
def get_option_by_name(self, name):
for group in (self.input_options, self.pipeline_options,
- self.output_options):
+ self.output_options, self.all_format_options):
for rec in group:
if rec.option == name:
return rec
@@ -535,7 +552,7 @@ OptionRecommendation(name='list_recipes',
'''
self.opts = OptionValues()
for group in (self.input_options, self.pipeline_options,
- self.output_options):
+ self.output_options, self.all_format_options):
for rec in group:
setattr(self.opts, rec.option.name, rec.recommended_value)
diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py
index 2b5e9d4cf2..aacccbd6b9 100644
--- a/src/calibre/gui2/dialogs/config.py
+++ b/src/calibre/gui2/dialogs/config.py
@@ -39,7 +39,7 @@ class ConfigTabs(QTabWidget):
log = Log()
log.outputs = []
- self.plumber = Plumber('dummt.epub', 'dummy.epub', log)
+ self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True)
def widget_factory(cls):
return cls(self, self.plumber.get_option_by_name,
From 00bfb182ea93367fab1b59f3c82998e4476d7c67 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sat, 4 Jul 2009 20:51:22 -0400
Subject: [PATCH 16/31] Implement bug #2775: Auto complete tags in booksview.
---
src/calibre/gui2/library.py | 89 ++++++++++++++++++++++++++++++++++---
1 file changed, 84 insertions(+), 5 deletions(-)
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index 878f7ddc2f..086d62962c 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -9,7 +9,8 @@ from math import cos, sin, pi
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QLineEdit, \
- QPalette, QImage, QApplication, QMenu, QStyledItemDelegate
+ QPalette, QImage, QApplication, QMenu, \
+ QStyledItemDelegate, QCompleter, QStringListModel
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
SIGNAL, QObject, QSize, QModelIndex, QDate
@@ -117,6 +118,74 @@ class TextDelegate(QStyledItemDelegate):
editor = EnLineEdit(parent)
return editor
+class CompleterLineEdit(EnLineEdit):
+
+ def __init__(self, *args):
+ EnLineEdit.__init__(self, *args)
+
+ QObject.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
+
+ def text_changed(self, text):
+ all_text = qstring_to_unicode(text)
+ text = all_text[:self.cursorPosition()]
+ prefix = text.split(',')[-1].strip()
+
+ text_tags = []
+ for t in all_text.split(','):
+ t1 = qstring_to_unicode(t).strip()
+ if t1 != '':
+ text_tags.append(t)
+ text_tags = list(set(text_tags))
+
+ self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), text_tags, prefix)
+
+ def complete_text(self, text):
+ cursor_pos = self.cursorPosition()
+ before_text = qstring_to_unicode(self.text())[:cursor_pos]
+ after_text = qstring_to_unicode(self.text())[cursor_pos:]
+ prefix_len = len(before_text.split(',')[-1].strip())
+ self.setText('%s%s, %s' % (before_text[:cursor_pos - prefix_len], text, after_text))
+ self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
+
+class TagsCompleter(QCompleter):
+
+ def __init__(self, parent, all_tags):
+ QCompleter.__init__(self, all_tags, parent)
+ self.all_tags = set(all_tags)
+
+ def update(self, text_tags, completion_prefix):
+ tags = list(self.all_tags.difference(text_tags))
+ model = QStringListModel(tags, self)
+ self.setModel(model)
+
+ self.setCompletionPrefix(completion_prefix)
+ if completion_prefix.strip() != '':
+ self.complete()
+
+class TagsDelegate(QStyledItemDelegate):
+
+ def __init__(self, parent):
+ QStyledItemDelegate.__init__(self, parent)
+ self.db = None
+
+ def set_database(self, db):
+ self.db = db
+
+ def createEditor(self, parent, option, index):
+ editor = CompleterLineEdit(parent)
+ if self.db:
+ completer = TagsCompleter(editor, self.db.all_tags())
+ completer.setCaseSensitivity(Qt.CaseInsensitive)
+
+ QObject.connect(editor,
+ SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
+ completer.update)
+ QObject.connect(completer, SIGNAL('activated(QString)'),
+ editor.complete_text)
+
+ completer.setWidget(editor)
+ return editor
+
class BooksModel(QAbstractTableModel):
headers = {
'title' : _("Title"),
@@ -165,8 +234,13 @@ class BooksModel(QAbstractTableModel):
pidx = self.column_map.index('pubdate')
except ValueError:
pidx = -1
+ try:
+ taidx = self.column_map.index('tags')
+ except ValueError:
+ taidx = -1
- self.emit(SIGNAL('columns_sorted(int,int,int)'), idx, tidx, pidx)
+ self.emit(SIGNAL('columns_sorted(int,int,int,int)'), idx, tidx, pidx,
+ taidx)
def set_database(self, db):
@@ -660,6 +734,7 @@ class BooksView(TableView):
self.rating_delegate = LibraryDelegate(self)
self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self)
+ self.tags_delegate = TagsDelegate(self)
self.display_parent = parent
self._model = modelcls(self)
self.setModel(self._model)
@@ -671,15 +746,16 @@ class BooksView(TableView):
cm = self._model.column_map
self.columns_sorted(cm.index('rating') if 'rating' in cm else -1,
cm.index('timestamp') if 'timestamp' in cm else -1,
- cm.index('pubdate') if 'pubdate' in cm else -1)
+ cm.index('pubdate') if 'pubdate' in cm else -1,
+ cm.index('tags') if 'tags' in cm else -1)
except ValueError:
pass
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
self._model.current_changed)
- self.connect(self._model, SIGNAL('columns_sorted(int,int,int)'),
+ self.connect(self._model, SIGNAL('columns_sorted(int,int,int,int)'),
self.columns_sorted, Qt.QueuedConnection)
- def columns_sorted(self, rating_col, timestamp_col, pubdate_col):
+ def columns_sorted(self, rating_col, timestamp_col, pubdate_col, tags_col):
for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate, self.pubdate_delegate):
@@ -690,6 +766,8 @@ class BooksView(TableView):
self.setItemDelegateForColumn(timestamp_col, self.timestamp_delegate)
if pubdate_col > -1:
self.setItemDelegateForColumn(pubdate_col, self.pubdate_delegate)
+ if tags_col > -1:
+ self.setItemDelegateForColumn(tags_col, self.tags_delegate)
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, similar_menu=None):
@@ -752,6 +830,7 @@ class BooksView(TableView):
def set_database(self, db):
self._model.set_database(db)
+ self.tags_delegate.set_database(db)
def close(self):
self._model.close()
From 74dc8d5b1d6e26ad117db2c30a9614d51db6769f Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 10:08:08 -0400
Subject: [PATCH 17/31] Preferred input format: User orderable list of input
formats to prefer.
---
src/calibre/customize/ui.py | 7 ++++
src/calibre/gui2/__init__.py | 7 +++-
src/calibre/gui2/dialogs/config.py | 28 +++++++++++++-
src/calibre/gui2/dialogs/config.ui | 62 ++++++++++++++++++++++++++++++
4 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py
index 78afa3be15..04f9b80529 100644
--- a/src/calibre/customize/ui.py
+++ b/src/calibre/customize/ui.py
@@ -276,6 +276,13 @@ def plugin_for_input_format(fmt):
if fmt.lower() in plugin.file_types:
return plugin
+def all_input_formats():
+ formats = set([])
+ for plugin in input_format_plugins():
+ for format in plugin.file_types:
+ formats.add(format)
+ return formats
+
def available_input_formats():
formats = set([])
for plugin in input_format_plugins():
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index af4ca16eac..a7e088ee1c 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -11,6 +11,7 @@ ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500'
from calibre import islinux, iswindows
from calibre.startup import get_lang
+from calibre.customize.ui import all_input_formats
from calibre.utils.config import Config, ConfigProxy, dynamic
import calibre.resources as resources
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
@@ -48,9 +49,11 @@ def _config():
help=_('Defaults for conversion to LRF'))
c.add_opt('LRF_ebook_viewer_options', default=None,
help=_('Options for the LRF ebook viewer'))
- c.add_opt('internally_viewed_formats', default=['LRF', 'EPUB', 'LIT',
- 'MOBI', 'PRC', 'HTML', 'FB2', 'PDB', 'RB'],
+ c.add_opt('internally_viewed_formats', default=all_input_formats(),
help=_('Formats that are viewed using the internal viewer'))
+ c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'PRC', 'LIT',
+ 'HTML', 'FB2', 'PDB', 'RB'],
+ help=_('Order list of formats to prefer for input.'))
c.add_opt('column_map', default=ALL_COLUMNS,
help=_('Columns to be displayed in the book list'))
c.add_opt('autolaunch_server', default=False, help=_('Automatically launch content server on application startup'))
diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py
index aacccbd6b9..20ade05846 100644
--- a/src/calibre/gui2/dialogs/config.py
+++ b/src/calibre/gui2/dialogs/config.py
@@ -22,7 +22,8 @@ from calibre.library import server_config
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
disable_plugin, customize_plugin, \
plugin_customization, add_plugin, \
- remove_plugin, input_format_plugins, \
+ remove_plugin, all_input_formats, \
+ input_format_plugins, \
output_format_plugins, available_output_formats
from calibre.utils.smtp import config as smtp_prefs
from calibre.gui2.convert.look_and_feel import LookAndFeelWidget
@@ -337,6 +338,18 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
+ input_map = config['input_format_order']
+ all_formats = set()
+ for fmt in all_input_formats():
+ all_formats.add(fmt.upper())
+ for format in input_map + list(all_formats.difference(input_map)):
+ item = QListWidgetItem(format, self.input_order)
+ item.setData(Qt.UserRole, QVariant(format))
+ item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
+
+ self.connect(self.input_up, SIGNAL('clicked()'), self.up_input)
+ self.connect(self.input_down, SIGNAL('clicked()'), self.down_input)
+
dirs = config['frequently_used_directories']
rn = config['use_roman_numerals_for_series_number']
self.timeout.setValue(prefs['network_timeout'])
@@ -553,6 +566,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
plugin.name + _(' cannot be removed. It is a '
'builtin plugin. Try disabling it instead.')).exec_()
+ def up_input(self):
+ idx = self.input_order.currentRow()
+ if idx > 0:
+ self.input_order.insertItem(idx-1, self.input_order.takeItem(idx))
+ self.input_order.setCurrentRow(idx-1)
+
+ def down_input(self):
+ idx = self.input_order.currentRow()
+ if idx < self.input_order.count()-1:
+ self.input_order.insertItem(idx+1, self.input_order.takeItem(idx))
+ self.input_order.setCurrentRow(idx+1)
def up_column(self):
idx = self.columns.currentRow()
@@ -656,6 +680,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
prefs['network_timeout'] = int(self.timeout.value())
path = qstring_to_unicode(self.location.text())
+ input_cols = [unicode(self.input_order.item(i).data(Qt.UserRole).toString()) for i in range(self.input_order.count())]
+ config['input_format_order'] = input_cols
cols = [unicode(self.columns.item(i).data(Qt.UserRole).toString()) for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked]
if not cols:
cols = ['title']
diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui
index 90a53364ca..f2cf9b3202 100644
--- a/src/calibre/gui2/dialogs/config.ui
+++ b/src/calibre/gui2/dialogs/config.ui
@@ -232,6 +232,68 @@
+ -
+
+
+ Preferred &input format order:
+
+
+
-
+
+
-
+
+
+ true
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ -
+
+
-
+
+
+ ...
+
+
+
+ :/images/arrow-up.svg:/images/arrow-up.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/images/arrow-down.svg:/images/arrow-down.svg
+
+
+
+
+
+
+
+
+
+
-
From a3361e36f69855dbe4f549d4e0e8b3dc59f0fc12 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 10:38:57 -0400
Subject: [PATCH 18/31] Use preferred output format and input format order when
converting via gui.
---
src/calibre/ebooks/conversion/plumber.py | 4 ----
src/calibre/gui2/__init__.py | 4 ++--
src/calibre/gui2/convert/bulk.py | 6 +++---
src/calibre/gui2/convert/single.py | 15 +++++++--------
4 files changed, 12 insertions(+), 17 deletions(-)
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index e33df27412..fa807eb24f 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -20,10 +20,6 @@ def supported_input_formats():
fmts.add(x)
return fmts
-INPUT_FORMAT_PREFERENCES = ['cbr', 'cbz', 'cbc', 'lit', 'mobi', 'prc', 'azw', 'fb2', 'html',
- 'rtf', 'pdf', 'txt', 'pdb']
-OUTPUT_FORMAT_PREFERENCES = ['epub', 'mobi', 'lit', 'pdf', 'pdb', 'txt']
-
class OptionValues(object):
pass
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index a7e088ee1c..934d0b8f2f 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -11,7 +11,6 @@ ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500'
from calibre import islinux, iswindows
from calibre.startup import get_lang
-from calibre.customize.ui import all_input_formats
from calibre.utils.config import Config, ConfigProxy, dynamic
import calibre.resources as resources
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
@@ -49,7 +48,8 @@ def _config():
help=_('Defaults for conversion to LRF'))
c.add_opt('LRF_ebook_viewer_options', default=None,
help=_('Options for the LRF ebook viewer'))
- c.add_opt('internally_viewed_formats', default=all_input_formats(),
+ c.add_opt('internally_viewed_formats', default=['LRF', 'EPUB', 'LIT',
+ 'MOBI', 'PRC', 'HTML', 'FB2', 'PDB', 'RB'],
help=_('Formats that are viewed using the internal viewer'))
c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'PRC', 'LIT',
'HTML', 'FB2', 'PDB', 'RB'],
diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py
index 0b48f2521b..393e005e5c 100644
--- a/src/calibre/gui2/convert/bulk.py
+++ b/src/calibre/gui2/convert/bulk.py
@@ -15,7 +15,8 @@ from calibre.gui2.convert.page_setup import PageSetupWidget
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
from calibre.gui2.convert.toc import TOCWidget
from calibre.gui2.convert import GuiRecommendations
-from calibre.ebooks.conversion.plumber import Plumber, OUTPUT_FORMAT_PREFERENCES
+from calibre.ebooks.conversion.plumber import Plumber
+from calibre.utils.config import prefs
from calibre.utils.logging import Log
class BulkConfig(Config):
@@ -102,7 +103,7 @@ class BulkConfig(Config):
preferred_output_format = preferred_output_format if \
preferred_output_format and preferred_output_format \
in output_formats else sort_formats_by_preference(output_formats,
- OUTPUT_FORMAT_PREFERENCES)[0]
+ prefs['output_format'])[0]
self.output_formats.addItems(list(map(QString, [x.upper() for x in
output_formats])))
self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format))
@@ -117,4 +118,3 @@ class BulkConfig(Config):
self._recommendations = recs
ResizableDialog.accept(self)
-
diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py
index ea839e6e80..6d04c7ab04 100644
--- a/src/calibre/gui2/convert/single.py
+++ b/src/calibre/gui2/convert/single.py
@@ -10,7 +10,7 @@ import sys, cPickle
from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont
-from calibre.gui2 import ResizableDialog, NONE
+from calibre.gui2 import ResizableDialog, NONE, config
from calibre.ebooks.conversion.config import GuiRecommendations, save_specifics, \
load_specifics
from calibre.gui2.convert.single_ui import Ui_Dialog
@@ -20,11 +20,10 @@ from calibre.gui2.convert.page_setup import PageSetupWidget
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
from calibre.gui2.convert.toc import TOCWidget
-
-from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats, \
- INPUT_FORMAT_PREFERENCES, OUTPUT_FORMAT_PREFERENCES
+from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats
from calibre.customize.ui import available_output_formats
from calibre.customize.conversion import OptionRecommendation
+from calibre.utils.config import prefs
from calibre.utils.logging import Log
class NoSupportedInputFormats(Exception):
@@ -33,11 +32,11 @@ class NoSupportedInputFormats(Exception):
def sort_formats_by_preference(formats, prefs):
def fcmp(x, y):
try:
- x = prefs.index(x)
+ x = prefs.index(x.upper())
except ValueError:
x = sys.maxint
try:
- y = prefs.index(y)
+ y = prefs.index(y.upper())
except ValueError:
y = sys.maxint
return cmp(x, y)
@@ -206,11 +205,11 @@ class Config(ResizableDialog, Ui_Dialog):
preferred_input_format = preferred_input_format if \
preferred_input_format in input_formats else \
sort_formats_by_preference(input_formats,
- INPUT_FORMAT_PREFERENCES)[0]
+ config['input_format_order'])[0]
preferred_output_format = preferred_output_format if \
preferred_output_format in output_formats else \
sort_formats_by_preference(output_formats,
- OUTPUT_FORMAT_PREFERENCES)[0]
+ prefs['output_format'])[0]
self.input_formats.addItems(list(map(QString, [x.upper() for x in
input_formats])))
self.output_formats.addItems(list(map(QString, [x.upper() for x in
From 83abb08b90b4141ecc1699d7b8c2ffa7bf157e31 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 10:48:21 -0400
Subject: [PATCH 19/31] use prefers for input format order instead of config.
---
src/calibre/gui2/__init__.py | 3 ---
src/calibre/gui2/convert/single.py | 4 ++--
src/calibre/gui2/dialogs/config.py | 4 ++--
src/calibre/utils/config.py | 2 ++
4 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 934d0b8f2f..af4ca16eac 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -51,9 +51,6 @@ def _config():
c.add_opt('internally_viewed_formats', default=['LRF', 'EPUB', 'LIT',
'MOBI', 'PRC', 'HTML', 'FB2', 'PDB', 'RB'],
help=_('Formats that are viewed using the internal viewer'))
- c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'PRC', 'LIT',
- 'HTML', 'FB2', 'PDB', 'RB'],
- help=_('Order list of formats to prefer for input.'))
c.add_opt('column_map', default=ALL_COLUMNS,
help=_('Columns to be displayed in the book list'))
c.add_opt('autolaunch_server', default=False, help=_('Automatically launch content server on application startup'))
diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py
index 6d04c7ab04..b995c3e3a2 100644
--- a/src/calibre/gui2/convert/single.py
+++ b/src/calibre/gui2/convert/single.py
@@ -10,7 +10,7 @@ import sys, cPickle
from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont
-from calibre.gui2 import ResizableDialog, NONE, config
+from calibre.gui2 import ResizableDialog, NONE
from calibre.ebooks.conversion.config import GuiRecommendations, save_specifics, \
load_specifics
from calibre.gui2.convert.single_ui import Ui_Dialog
@@ -205,7 +205,7 @@ class Config(ResizableDialog, Ui_Dialog):
preferred_input_format = preferred_input_format if \
preferred_input_format in input_formats else \
sort_formats_by_preference(input_formats,
- config['input_format_order'])[0]
+ prefs['input_format_order'])[0]
preferred_output_format = preferred_output_format if \
preferred_output_format in output_formats else \
sort_formats_by_preference(output_formats,
diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py
index 20ade05846..1c337788c6 100644
--- a/src/calibre/gui2/dialogs/config.py
+++ b/src/calibre/gui2/dialogs/config.py
@@ -338,7 +338,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
- input_map = config['input_format_order']
+ input_map = prefs['input_format_order']
all_formats = set()
for fmt in all_input_formats():
all_formats.add(fmt.upper())
@@ -681,7 +681,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
prefs['network_timeout'] = int(self.timeout.value())
path = qstring_to_unicode(self.location.text())
input_cols = [unicode(self.input_order.item(i).data(Qt.UserRole).toString()) for i in range(self.input_order.count())]
- config['input_format_order'] = input_cols
+ prefs['input_format_order'] = input_cols
cols = [unicode(self.columns.item(i).data(Qt.UserRole).toString()) for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked]
if not cols:
cols = ['title']
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index 0925d8667d..e225406dff 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -548,6 +548,8 @@ def _prefs():
help=_('The language in which to display the user interface'))
c.add_opt('output_format', default='EPUB',
help=_('The default output format for ebook conversions.'))
+ c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'PRC', 'LIT'],
+ help=_('Order list of formats to prefer for input.'))
c.add_opt('read_file_metadata', default=True,
help=_('Read metadata from files'))
c.add_opt('worker_process_priority', default='normal',
From 78b3b9ba29ae4c01bdba71730d2ca97e752bf008 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 11:47:25 -0400
Subject: [PATCH 20/31] GUI: View Book respects input plugins and can be used
to view multiple books. Also, respects input format preferrence ordering.
---
src/calibre/gui2/main.py | 68 +++++++++++++++++++---------------------
1 file changed, 33 insertions(+), 35 deletions(-)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index b1e75a7a9f..968bc016fd 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -47,6 +47,7 @@ from calibre.gui2.dialogs.book_info import BookInfo
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.library.database2 import LibraryDatabase2, CoverCache
from calibre.gui2.dialogs.confirm_delete import confirm
+from calibre.customize.ui import available_input_formats
ADDRESS = r'\\.\pipe\CalibreGUI' if iswindows else \
os.path.expanduser('~/.calibre-gui.socket')
@@ -1349,51 +1350,48 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def view_book(self, triggered):
rows = self.current_view().selectionModel().selectedRows()
- if self.current_view() is self.library_view:
- if not rows or len(rows) == 0:
- self._launch_viewer()
- return
- row = rows[0].row()
- formats = self.library_view.model().db.formats(row).upper()
- formats = formats.split(',')
- title = self.library_view.model().db.title(row)
- id = self.library_view.model().db.id(row)
- format = None
- if len(formats) == 1:
- format = formats[0]
- if 'LRF' in formats:
- format = 'LRF'
- if 'EPUB' in formats:
- format = 'EPUB'
- if 'MOBI' in formats:
- format = 'MOBI'
- if not formats:
- d = error_dialog(self, _('Cannot view'),
- _('%s has no available formats.')%(title,))
- d.exec_()
- return
- if format is None:
- d = ChooseFormatDialog(self, _('Choose the format to view'),
- formats)
- d.exec_()
- if d.result() == QDialog.Accepted:
- format = d.format()
- else:
+ if not rows or len(rows) == 0:
+ self._launch_viewer()
+ return
+
+ if len(rows) >= 3:
+ if not question_dialog(self, _('Multiple Books Selected'),
+ _('You are attempting to open %i books. Opening to many '
+ 'books at once can be slow and have an negative effect on the '
+ 'responsiveness of your computer. Once started the process '
+ 'cannot be stopped until complete. Do you wish to continue?'
+ % len(rows))):
return
+
+ if self.current_view() is self.library_view:
+ for row in rows:
+ row = row.row()
- self.view_format(row, format)
+ formats = self.library_view.model().db.formats(row).lower()
+ formats = set(formats.split(',')).intersection(available_input_formats())
+ title = self.library_view.model().db.title(row)
+
+ if not formats:
+ error_dialog(self, _('Cannot view'),
+ _('%s has no available formats.')%(title,), show=True)
+ continue
+
+ print prefs['input_format_order']
+ for format in prefs['input_format_order']:
+ if format.lower() in formats:
+ self.view_format(row, format)
+ break
else:
paths = self.current_view().model().paths(rows)
- if paths:
+ for path in paths:
pt = PersistentTemporaryFile('_viewer_'+\
- os.path.splitext(paths[0])[1])
+ os.path.splitext(path)[1])
self.persistent_files.append(pt)
pt.close()
self.device_manager.view_book(\
Dispatcher(self.book_downloaded_for_viewing),
- paths[0], pt.name)
-
+ path, pt.name)
############################################################################
From 768f2e5cbf029c960b1aab3fbcf73bab9edab939 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 11:54:46 -0400
Subject: [PATCH 21/31] Remove testing print.
---
src/calibre/gui2/main.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 968bc016fd..927e73b519 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -1377,7 +1377,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
_('%s has no available formats.')%(title,), show=True)
continue
- print prefs['input_format_order']
for format in prefs['input_format_order']:
if format.lower() in formats:
self.view_format(row, format)
From 60bc41b33ef0a25237ccd28e5d37143b6c562e2a Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 12:43:26 -0400
Subject: [PATCH 22/31] GUI: view ebook, open formats that do not have an input
plugin because those can be opened with an external viewer.
---
src/calibre/gui2/main.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 927e73b519..cd1e545a29 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -1368,8 +1368,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
for row in rows:
row = row.row()
- formats = self.library_view.model().db.formats(row).lower()
- formats = set(formats.split(',')).intersection(available_input_formats())
+ formats = self.library_view.model().db.formats(row).upper()
+ formats = formats.split(',')
title = self.library_view.model().db.title(row)
if not formats:
@@ -1377,10 +1377,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
_('%s has no available formats.')%(title,), show=True)
continue
+ in_prefs = False
for format in prefs['input_format_order']:
- if format.lower() in formats:
+ if format in formats:
+ in_prefs = True
self.view_format(row, format)
break
+ if not in_prefs:
+ self.view_format(row, format[0])
else:
paths = self.current_view().model().paths(rows)
for path in paths:
From b8fc9de71d09392a07119f6ce03f2f6cc08d2232 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 15:39:35 -0400
Subject: [PATCH 23/31] Make tags completing line edit a stand alone widget.
Use tag completing line edit in metadata single dialog.
---
src/calibre/gui2/dialogs/metadata_single.py | 4 +-
src/calibre/gui2/dialogs/metadata_single.ui | 7 +-
src/calibre/gui2/library.py | 60 ++--------------
src/calibre/gui2/widgets.py | 80 ++++++++++++++++++++-
4 files changed, 92 insertions(+), 59 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 2ddbec7f20..68cce6ec5f 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -268,7 +268,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
aus = self.db.author_sort(row)
self.author_sort.setText(aus if aus else '')
tags = self.db.tags(row)
- self.tags.setText(tags if tags else '')
+ self.tags.setText(', '.join(tags.split(',')) if tags else '')
+ self.tags.update_tags_cache(self.db.all_tags())
rating = self.db.rating(row)
if rating > 0:
self.rating.setValue(int(rating/2.))
@@ -389,6 +390,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if d.result() == QDialog.Accepted:
tag_string = ', '.join(d.tags)
self.tags.setText(tag_string)
+ self.tags.update_tags_cache(self.db.all_tags())
def fetch_cover(self):
isbn = unicode(self.isbn.text()).strip()
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui
index ff98d22ad3..14191f2851 100644
--- a/src/calibre/gui2/dialogs/metadata_single.ui
+++ b/src/calibre/gui2/dialogs/metadata_single.ui
@@ -222,7 +222,7 @@
-
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -652,6 +652,11 @@
QComboBox
+
+ TagsLineEdit
+ QLineEdit
+
+
title
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index 086d62962c..b7dd91271b 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -19,7 +19,7 @@ from calibre.ptempfile import PersistentTemporaryFile
from calibre.library.database2 import FIELD_MAP
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \
error_dialog
-from calibre.gui2.widgets import EnLineEdit
+from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.ebooks.metadata import string_to_authors, fmt_sidx
@@ -118,50 +118,6 @@ class TextDelegate(QStyledItemDelegate):
editor = EnLineEdit(parent)
return editor
-class CompleterLineEdit(EnLineEdit):
-
- def __init__(self, *args):
- EnLineEdit.__init__(self, *args)
-
- QObject.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
-
- def text_changed(self, text):
- all_text = qstring_to_unicode(text)
- text = all_text[:self.cursorPosition()]
- prefix = text.split(',')[-1].strip()
-
- text_tags = []
- for t in all_text.split(','):
- t1 = qstring_to_unicode(t).strip()
- if t1 != '':
- text_tags.append(t)
- text_tags = list(set(text_tags))
-
- self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), text_tags, prefix)
-
- def complete_text(self, text):
- cursor_pos = self.cursorPosition()
- before_text = qstring_to_unicode(self.text())[:cursor_pos]
- after_text = qstring_to_unicode(self.text())[cursor_pos:]
- prefix_len = len(before_text.split(',')[-1].strip())
- self.setText('%s%s, %s' % (before_text[:cursor_pos - prefix_len], text, after_text))
- self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
-
-class TagsCompleter(QCompleter):
-
- def __init__(self, parent, all_tags):
- QCompleter.__init__(self, all_tags, parent)
- self.all_tags = set(all_tags)
-
- def update(self, text_tags, completion_prefix):
- tags = list(self.all_tags.difference(text_tags))
- model = QStringListModel(tags, self)
- self.setModel(model)
-
- self.setCompletionPrefix(completion_prefix)
- if completion_prefix.strip() != '':
- self.complete()
-
class TagsDelegate(QStyledItemDelegate):
def __init__(self, parent):
@@ -172,18 +128,10 @@ class TagsDelegate(QStyledItemDelegate):
self.db = db
def createEditor(self, parent, option, index):
- editor = CompleterLineEdit(parent)
if self.db:
- completer = TagsCompleter(editor, self.db.all_tags())
- completer.setCaseSensitivity(Qt.CaseInsensitive)
-
- QObject.connect(editor,
- SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
- completer.update)
- QObject.connect(completer, SIGNAL('activated(QString)'),
- editor.complete_text)
-
- completer.setWidget(editor)
+ editor = TagsLineEdit(parent, self.db.all_tags())
+ else:
+ editor = EnLineEdit(parent)
return editor
class BooksModel(QAbstractTableModel):
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index 3bcd1f5d3b..f4b9130fc8 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -11,7 +11,7 @@ from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
QAbstractListModel, QVariant, Qt, SIGNAL, \
QRegExp, QSettings, QSize, QModelIndex, \
QAbstractButton, QPainter, QLineEdit, QComboBox, \
- QMenu
+ QMenu, QStringListModel, QCompleter
from calibre.gui2 import human_readable, NONE, TableView, \
qstring_to_unicode, error_dialog
@@ -500,6 +500,84 @@ class EnLineEdit(LineEditECM, QLineEdit):
pass
+class TagsCompleter(QCompleter):
+
+ '''
+ A completer object that completes a list of tags. It is used in conjunction
+ with a CompleterLineEdit.
+ '''
+
+ def __init__(self, parent, all_tags):
+ QCompleter.__init__(self, all_tags, parent)
+ self.all_tags = set(all_tags)
+
+ def update(self, text_tags, completion_prefix):
+ tags = list(self.all_tags.difference(text_tags))
+ model = QStringListModel(tags, self)
+ self.setModel(model)
+
+ self.setCompletionPrefix(completion_prefix)
+ if completion_prefix.strip() != '':
+ self.complete()
+
+ def update_tags_cache(self, tags):
+ self.all_tags = set(tags)
+ model = QStringListModel(tags, self)
+ self.setModel(model)
+
+
+class TagsLineEdit(EnLineEdit):
+
+ '''
+ A QLineEdit that can complete parts of text separated by separator.
+ '''
+
+ def __init__(self, parent=0, tags=[]):
+ EnLineEdit.__init__(self, parent)
+
+ self.separator = ','
+
+ self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
+
+ self.completer = TagsCompleter(self, tags)
+ self.completer.setCaseSensitivity(Qt.CaseInsensitive)
+
+ self.connect(self,
+ SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
+ self.completer.update)
+ self.connect(self.completer, SIGNAL('activated(QString)'),
+ self.complete_text)
+
+ self.completer.setWidget(self)
+
+ def update_tags_cache(self, tags):
+ self.completer.update_tags_cache(tags)
+
+ def text_changed(self, text):
+ all_text = qstring_to_unicode(text)
+ text = all_text[:self.cursorPosition()]
+ prefix = text.split(',')[-1].strip()
+
+ text_tags = []
+ for t in all_text.split(self.separator):
+ t1 = qstring_to_unicode(t).strip()
+ if t1 != '':
+ text_tags.append(t)
+ text_tags = list(set(text_tags))
+
+ self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
+ text_tags, prefix)
+
+ def complete_text(self, text):
+ cursor_pos = self.cursorPosition()
+ before_text = qstring_to_unicode(self.text())[:cursor_pos]
+ after_text = qstring_to_unicode(self.text())[cursor_pos:]
+ prefix_len = len(before_text.split(',')[-1].strip())
+ self.setText('%s%s%s %s' % (before_text[:cursor_pos - prefix_len],
+ text, self.separator, after_text))
+ self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
+
+
class EnComboBox(QComboBox):
'''
From cf8137ec853788edbd2c3ca58660b44b6d0905d5 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 15:49:53 -0400
Subject: [PATCH 24/31] Metadata single, remove author completer because
authors is now a combobox.
---
src/calibre/gui2/dialogs/metadata_single.py | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 68cce6ec5f..7894bb05bb 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -80,13 +80,6 @@ class Format(QListWidgetItem):
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
text, parent, QListWidgetItem.UserType)
-class AuthorCompleter(QCompleter):
-
- def __init__(self, db):
- all_authors = db.all_authors()
- all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
- QCompleter.__init__(self, [x[1] for x in all_authors])
-
class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
COVER_FETCH_TIMEOUT = 240 # seconds
@@ -233,8 +226,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.cover_changed = False
self.cpixmap = None
self.cover.setAcceptDrops(True)
- self._author_completer = AuthorCompleter(self.db)
- self.authors.setCompleter(self._author_completer)
self.pubdate.setMinimumDate(QDate(100,1,1))
self.connect(self.cover, SIGNAL('cover_changed()'), self.cover_dropped)
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
From b70c1d1231c6c81b7dcbaa2f6f5fb22ce51926fa Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 16:03:03 -0400
Subject: [PATCH 25/31] Metadata Bulk: Auto completing for various inputs.
---
src/calibre/gui2/dialogs/metadata_bulk.py | 48 ++++++++++++++++++++---
src/calibre/gui2/dialogs/metadata_bulk.ui | 46 ++++++++++------------
2 files changed, 63 insertions(+), 31 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 9b8810b3a4..5b3b7fa3d4 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -9,7 +9,8 @@ from PyQt4.QtGui import QDialog
from calibre.gui2 import qstring_to_unicode
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
-from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string
+from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string, \
+ authors_to_string
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
@@ -25,29 +26,64 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync)
QObject.connect(self.rating, SIGNAL('valueChanged(int)'), self.rating_changed)
- all_series = self.db.all_series()
+ self.tags.update_tags_cache(self.db.all_tags())
+ self.remove_tags.update_tags_cache(self.db.all_tags())
- for i in all_series:
- id, name = i
- self.series.addItem(name)
+ self.initialize_combos()
for f in self.db.all_formats():
self.remove_format.addItem(f)
self.remove_format.setCurrentIndex(-1)
- self.series.lineEdit().setText('')
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.series_changed)
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.series_changed)
QObject.connect(self.tag_editor_button, SIGNAL('clicked()'), self.tag_editor)
+
self.exec_()
+
+ def initialize_combos(self):
+ self.initalize_authors()
+ self.initialize_series()
+ self.initialize_publisher()
+
+ def initalize_authors(self):
+ all_authors = self.db.all_authors()
+ all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+
+ for i in all_authors:
+ id, name = i
+ name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')])
+ self.authors.addItem(name)
+ self.authors.setEditText('')
+
+ def initialize_series(self):
+ all_series = self.db.all_series()
+ all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+
+ for i in all_series:
+ id, name = i
+ self.series.addItem(name)
+ self.series.setEditText('')
+
+ def initialize_publisher(self):
+ all_publishers = self.db.all_publishers()
+ all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+
+ for i in all_publishers:
+ id, name = i
+ self.publisher.addItem(name)
+ self.publisher.setEditText('')
+
def tag_editor(self):
d = TagEditor(self, self.db, None)
d.exec_()
if d.result() == QDialog.Accepted:
tag_string = ', '.join(d.tags)
self.tags.setText(tag_string)
+ self.tags.update_tags_cache(self.db.all_tags())
+ self.remove_tags.update_tags_cache(self.db.all_tags())
def sync(self):
for id in self.ids:
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index beea2cace3..f2bcc3cd93 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -45,16 +45,6 @@
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
- authors
-
-
-
- -
-
-
- Change the author(s) of this book. Multiple authors should be separated by an &. If the author name contains an &, use && to represent it.
-
-
@@ -65,9 +55,6 @@
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
- authors
-
-
@@ -117,16 +104,6 @@
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
- publisher
-
-
-
- -
-
-
- Change the publisher of this book
-
-
@@ -143,7 +120,7 @@
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -174,7 +151,7 @@
-
-
+
Comma separated list of tags to remove from the books.
@@ -235,6 +212,20 @@
+ -
+
+
+ true
+
+
+
+ -
+
+
+ true
+
+
+
@@ -265,6 +256,11 @@
QComboBox
+
+ TagsLineEdit
+ QLineEdit
+
+
From 4b338c527419a9e6ccd5780f3d9e815b46cb611f Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 18:35:23 -0400
Subject: [PATCH 26/31] Auto completes in convert dialog.
---
src/calibre/gui2/convert/metadata.py | 47 ++++++++++++++++++-----
src/calibre/gui2/convert/metadata.ui | 43 ++++++++++-----------
src/calibre/gui2/dialogs/metadata_bulk.py | 1 -
3 files changed, 59 insertions(+), 32 deletions(-)
diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py
index 01eb5bee1c..82e7b21148 100644
--- a/src/calibre/gui2/convert/metadata.py
+++ b/src/calibre/gui2/convert/metadata.py
@@ -35,21 +35,17 @@ class MetadataWidget(Widget, Ui_Form):
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
def initialize_metadata_options(self):
- all_series = self.db.all_series()
- all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
- for series in all_series:
- self.series.addItem(series[1])
- self.series.setCurrentIndex(-1)
+ self.initialize_combos()
mi = self.db.get_metadata(self.book_id, index_is_id=True)
self.title.setText(mi.title)
if mi.authors:
- self.author.setText(authors_to_string(mi.authors))
- else:
- self.author.setText('')
- self.publisher.setText(mi.publisher if mi.publisher else '')
+ self.author.setCurrentIndex(self.author.findText(authors_to_string(mi.authors)))
+ if mi.publisher:
+ self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
self.tags.setText(', '.join(mi.tags if mi.tags else []))
+ self.tags.update_tags_cache(self.db.all_tags())
self.comment.setText(mi.comments if mi.comments else '')
if mi.series:
self.series.setCurrentIndex(self.series.findText(mi.series))
@@ -66,6 +62,39 @@ class MetadataWidget(Widget, Ui_Form):
if not pm.isNull():
self.cover.setPixmap(pm)
+ def initialize_combos(self):
+ self.initalize_authors()
+ self.initialize_series()
+ self.initialize_publisher()
+
+ def initalize_authors(self):
+ all_authors = self.db.all_authors()
+ all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+
+ for i in all_authors:
+ id, name = i
+ name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')])
+ self.author.addItem(name)
+ self.author.setCurrentIndex(-1)
+
+ def initialize_series(self):
+ all_series = self.db.all_series()
+ all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+
+ for i in all_series:
+ id, name = i
+ self.series.addItem(name)
+ self.series.setCurrentIndex(-1)
+
+ def initialize_publisher(self):
+ all_publishers = self.db.all_publishers()
+ all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
+
+ for i in all_publishers:
+ id, name = i
+ self.publisher.addItem(name)
+ self.publisher.setCurrentIndex(-1)
+
def get_title_and_authors(self):
title = unicode(self.title.text()).strip()
if not title:
diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui
index e4533de24c..3abe8ece55 100644
--- a/src/calibre/gui2/convert/metadata.ui
+++ b/src/calibre/gui2/convert/metadata.ui
@@ -143,19 +143,6 @@
- -
-
-
-
- 1
- 0
-
-
-
- Change the author(s) of this book. Multiple authors should be separated by an &. If the author name contains an &, use && to represent it.
-
-
-
-
@@ -195,13 +182,6 @@
- -
-
-
- Change the publisher of this book
-
-
-
-
@@ -216,7 +196,7 @@
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -276,6 +256,20 @@
+ -
+
+
+ true
+
+
+
+ -
+
+
+ true
+
+
+
-
@@ -329,11 +323,16 @@
QComboBox
+
+ TagsLineEdit
+ QLineEdit
+
+
-
+
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 5b3b7fa3d4..622ea95a2b 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -42,7 +42,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.exec_()
-
def initialize_combos(self):
self.initalize_authors()
self.initialize_series()
From a2457cb9396b52f1048a8aa7ba99b607f7fec959 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Sun, 5 Jul 2009 21:16:21 -0400
Subject: [PATCH 27/31] Auto complete author, series, publisher in library_view
---
src/calibre/gui2/library.py | 85 ++++++++++++++++++++-----------------
1 file changed, 46 insertions(+), 39 deletions(-)
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index a795573cb6..2197c79b50 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -10,7 +10,7 @@ from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QLineEdit, \
QPalette, QImage, QApplication, QMenu, \
- QStyledItemDelegate, QCompleter, QStringListModel
+ QStyledItemDelegate, QCompleter
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
SIGNAL, QObject, QSize, QModelIndex, QDate
@@ -114,8 +114,26 @@ class PubDateDelegate(QStyledItemDelegate):
class TextDelegate(QStyledItemDelegate):
+ def __init__(self, parent):
+ '''
+ Delegate for text data. If auto_complete_function needs to return a list
+ of text items to auto-complete with. The funciton is None no
+ auto-complete will be used.
+ '''
+ QStyledItemDelegate.__init__(self, parent)
+ self.auto_complete_function = None
+
+ def set_auto_complete_function(self, f):
+ self.auto_complete_function = f
+
def createEditor(self, parent, option, index):
editor = EnLineEdit(parent)
+ if self.auto_complete_function:
+ complete_items = [i[1] for i in self.auto_complete_function()]
+ completer = QCompleter(complete_items, self)
+ completer.setCaseSensitivity(Qt.CaseInsensitive)
+ completer.setCompletionMode(QCompleter.InlineCompletion)
+ editor.setCompleter(completer)
return editor
class TagsDelegate(QStyledItemDelegate):
@@ -170,26 +188,7 @@ class BooksModel(QAbstractTableModel):
if cols != self.column_map:
self.column_map = cols
self.reset()
- try:
- idx = self.column_map.index('rating')
- except ValueError:
- idx = -1
- try:
- tidx = self.column_map.index('timestamp')
- except ValueError:
- tidx = -1
- try:
- pidx = self.column_map.index('pubdate')
- except ValueError:
- pidx = -1
- try:
- taidx = self.column_map.index('tags')
- except ValueError:
- taidx = -1
-
- self.emit(SIGNAL('columns_sorted(int,int,int,int)'), idx, tidx, pidx,
- taidx)
-
+ self.emit(SIGNAL('columns_sorted()'))
def set_database(self, db):
self.db = db
@@ -670,6 +669,9 @@ class BooksView(TableView):
self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self)
self.tags_delegate = TagsDelegate(self)
+ self.authors_delegate = TextDelegate(self)
+ self.series_delegate = TextDelegate(self)
+ self.publisher_delegate = TextDelegate(self)
self.display_parent = parent
self._model = modelcls(self)
self.setModel(self._model)
@@ -677,32 +679,34 @@ class BooksView(TableView):
self.setSortingEnabled(True)
for i in range(10):
self.setItemDelegateForColumn(i, TextDelegate(self))
- try:
- cm = self._model.column_map
- self.columns_sorted(cm.index('rating') if 'rating' in cm else -1,
- cm.index('timestamp') if 'timestamp' in cm else -1,
- cm.index('pubdate') if 'pubdate' in cm else -1,
- cm.index('tags') if 'tags' in cm else -1)
- except ValueError:
- pass
+ self.columns_sorted()
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
self._model.current_changed)
- self.connect(self._model, SIGNAL('columns_sorted(int,int,int,int)'),
+ self.connect(self._model, SIGNAL('columns_sorted()'),
self.columns_sorted, Qt.QueuedConnection)
- def columns_sorted(self, rating_col, timestamp_col, pubdate_col, tags_col):
+ def columns_sorted(self):
for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate, self.pubdate_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate())
- if rating_col > -1:
- self.setItemDelegateForColumn(rating_col, self.rating_delegate)
- if timestamp_col > -1:
- self.setItemDelegateForColumn(timestamp_col, self.timestamp_delegate)
- if pubdate_col > -1:
- self.setItemDelegateForColumn(pubdate_col, self.pubdate_delegate)
- if tags_col > -1:
- self.setItemDelegateForColumn(tags_col, self.tags_delegate)
+
+ cm = self._model.column_map
+
+ if 'rating' in cm:
+ self.setItemDelegateForColumn(cm.index('rating'), self.rating_delegate)
+ if 'timestamp' in cm:
+ self.setItemDelegateForColumn(cm.index('timestamp'), self.timestamp_delegate)
+ if 'pubdate' in cm:
+ self.setItemDelegateForColumn(cm.index('pubdate'), self.pubdate_delegate)
+ if 'tags' in cm:
+ self.setItemDelegateForColumn(cm.index('tags'), self.tags_delegate)
+ if 'authors' in cm:
+ self.setItemDelegateForColumn(cm.index('authors'), self.authors_delegate)
+ if 'publisher' in cm:
+ self.setItemDelegateForColumn(cm.index('publisher'), self.publisher_delegate)
+ if 'series' in cm:
+ self.setItemDelegateForColumn(cm.index('series'), self.series_delegate)
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, similar_menu=None):
@@ -766,6 +770,9 @@ class BooksView(TableView):
def set_database(self, db):
self._model.set_database(db)
self.tags_delegate.set_database(db)
+ self.authors_delegate.set_auto_complete_function(db.all_authors)
+ self.series_delegate.set_auto_complete_function(db.all_series)
+ self.publisher_delegate.set_auto_complete_function(db.all_publishers)
def close(self):
self._model.close()
From 768979168ccacf8d815fb110fc1f617d4caae28b Mon Sep 17 00:00:00 2001
From: John Schember
Date: Tue, 7 Jul 2009 21:14:38 -0400
Subject: [PATCH 28/31] DeviceBooksView does not need auto complete.
---
src/calibre/gui2/library.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index 627de41c74..3d5a30ec05 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -806,6 +806,9 @@ class DeviceBooksView(BooksView):
self.setDragDropMode(self.NoDragDrop)
self.setAcceptDrops(False)
+ def set_database(self, db):
+ self._model.set_database(db)
+
def resizeColumnsToContents(self):
QTableView.resizeColumnsToContents(self)
self.columns_resized = True
From f86229c72dd4a7e2d5a1ea389220876aa5f972ff Mon Sep 17 00:00:00 2001
From: John Schember
Date: Tue, 7 Jul 2009 21:19:03 -0400
Subject: [PATCH 29/31] Cybook Opus profile.
---
src/calibre/customize/profiles.py | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py
index 45026fcb5c..9114dc9eee 100644
--- a/src/calibre/customize/profiles.py
+++ b/src/calibre/customize/profiles.py
@@ -110,6 +110,18 @@ class CybookG3Input(InputProfile):
fbase = 16
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
+class CybookOpusInput(InputProfile):
+
+ name = 'Cybook Opus'
+ short_name = 'cybook_opus'
+ description = _('This profile is intended for the Cybook Opus.')
+
+ # Screen size is a best guess
+ screen_size = (600, 800)
+ dpi = 200
+ fbase = 16
+ fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
+
class KindleInput(InputProfile):
name = 'Kindle'
@@ -219,6 +231,18 @@ class CybookG3Output(OutputProfile):
fbase = 16
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
+class CybookOpusOutput(OutputProfile):
+
+ name = 'Cybook Opus'
+ short_name = 'cybook_opus'
+ description = _('This profile is intended for the Cybook Opus.')
+
+ # Screen size is a best guess
+ screen_size = (600, 800)
+ dpi = 200
+ fbase = 16
+ fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
+
class KindleOutput(OutputProfile):
name = 'Kindle'
From 21140bc72b319c62faaef1ba3762516401679c5f Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 8 Jul 2009 07:31:37 -0400
Subject: [PATCH 30/31] Implement bug #810: Search can be as you type or only
on Enter/Return.
---
src/calibre/gui2/__init__.py | 3 +++
src/calibre/gui2/dialogs/config.py | 2 ++
src/calibre/gui2/dialogs/config.ui | 21 +++++++++++----------
src/calibre/gui2/library.py | 25 ++++++++++++++++++-------
src/calibre/gui2/main.py | 2 ++
5 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index af4ca16eac..dc1ca7f2bd 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -71,6 +71,9 @@ def _config():
help='Show donation button')
c.add_opt('asked_library_thing_password', default=False,
help='Asked library thing password at least once.')
+ c.add_opt('search_as_you_type', default=True,
+ help='Start searching as you type. If this is disabled then seaerch will '
+ 'only take place when the Enter or Return key is pressed.')
return ConfigProxy(c)
config = _config()
diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py
index 1c337788c6..52b2caeaea 100644
--- a/src/calibre/gui2/dialogs/config.py
+++ b/src/calibre/gui2/dialogs/config.py
@@ -437,6 +437,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.password.setText(opts.password if opts.password else '')
self.auto_launch.setChecked(config['autolaunch_server'])
self.systray_icon.setChecked(config['systray_icon'])
+ self.search_as_you_type.setChecked(config['search_as_you_type'])
self.sync_news.setChecked(config['upload_news_to_device'])
self.delete_news.setChecked(config['delete_news_from_library_on_upload'])
p = {'normal':0, 'high':1, 'low':2}[prefs['worker_process_priority']]
@@ -707,6 +708,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
sc.set('max_cover', mcs)
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
config['upload_news_to_device'] = self.sync_news.isChecked()
+ config['search_as_you_type'] = self.search_as_you_type.isChecked()
fmts = []
for i in range(self.viewer.count()):
if self.viewer.item(i).checkState() == Qt.Checked:
diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui
index f2cf9b3202..55e5ce0552 100644
--- a/src/calibre/gui2/dialogs/config.ui
+++ b/src/calibre/gui2/dialogs/config.ui
@@ -8,7 +8,7 @@
0
0
800
- 557
+ 583
@@ -426,6 +426,16 @@
+ -
+
+
+ Search as you type
+
+
+ true
+
+
+
-
@@ -591,15 +601,6 @@
- roman_numerals
- groupBox_2
- systray_icon
- sync_news
- delete_news
- separate_cover_flow
- systray_notifications
-
-
diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py
index 3d5a30ec05..266a878440 100644
--- a/src/calibre/gui2/library.py
+++ b/src/calibre/gui2/library.py
@@ -1095,6 +1095,7 @@ class SearchBox(QLineEdit):
QLineEdit.__init__(self, parent)
self.help_text = help_text
self.initial_state = True
+ self.as_you_type = True
self.default_palette = QApplication.palette(self)
self.gray = QPalette(self.default_palette)
self.gray.setBrush(QPalette.Text, QBrush(QColor('gray')))
@@ -1123,6 +1124,9 @@ class SearchBox(QLineEdit):
if self.initial_state:
self.normalize_state()
self.initial_state = False
+ if not self.as_you_type:
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
+ self.do_search()
QLineEdit.keyPressEvent(self, event)
def mouseReleaseEvent(self, event):
@@ -1132,17 +1136,21 @@ class SearchBox(QLineEdit):
QLineEdit.mouseReleaseEvent(self, event)
def text_edited_slot(self, text):
- text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
- self.prev_text = text
- self.timer = self.startTimer(self.__class__.INTERVAL)
+ if self.as_you_type:
+ text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
+ self.prev_text = text
+ self.timer = self.startTimer(self.__class__.INTERVAL)
def timerEvent(self, event):
self.killTimer(event.timerId())
if event.timerId() == self.timer:
- text = qstring_to_unicode(self.text())
- refinement = text.startswith(self.prev_search) and ':' not in text
- self.prev_search = text
- self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
+ self.do_search()
+
+ def do_search(self):
+ text = qstring_to_unicode(self.text())
+ refinement = text.startswith(self.prev_search) and ':' not in text
+ self.prev_search = text
+ self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
def search_from_tokens(self, tokens, all):
ans = u' '.join([u'%s:%s'%x for x in tokens])
@@ -1161,3 +1169,6 @@ class SearchBox(QLineEdit):
self.end(False)
self.initial_state = False
+ def search_as_you_type(self, enabled):
+ self.as_you_type = enabled
+
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index cd1e545a29..4384121418 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -148,6 +148,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.system_tray_icon.hide()
else:
self.system_tray_icon.show()
+ self.search.search_as_you_type(config['search_as_you_type'])
self.system_tray_menu = QMenu(self)
self.restore_action = self.system_tray_menu.addAction(
QIcon(':/images/page.svg'), _('&Restore'))
@@ -1422,6 +1423,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.content_server = d.server
if d.result() == d.Accepted:
self.tool_bar.setIconSize(config['toolbar_icon_size'])
+ self.search.search_as_you_type(config['search_as_you_type'])
self.tool_bar.setToolButtonStyle(
Qt.ToolButtonTextUnderIcon if \
config['show_text_in_toolbar'] else \
From 794eba4b46fa0764a6a6dd0b3b1175a34574d39b Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 8 Jul 2009 07:55:08 -0400
Subject: [PATCH 31/31] Header and footer removal by regex moved to from pdf
input to structure detection in plumber.
---
src/calibre/ebooks/conversion/plumber.py | 25 +++++++++
src/calibre/ebooks/conversion/preprocess.py | 25 ++++-----
src/calibre/ebooks/pdf/input.py | 10 ----
src/calibre/gui2/convert/pdf_input.py | 32 +-----------
src/calibre/gui2/convert/pdf_input.ui | 28 ++--------
.../gui2/convert/structure_detection.py | 18 +++++--
.../gui2/convert/structure_detection.ui | 52 ++++++++++++++++---
7 files changed, 104 insertions(+), 86 deletions(-)
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index dc6c0f8b52..3c52ec2d7b 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -315,6 +315,31 @@ OptionRecommendation(name='preprocess_html',
)
),
+OptionRecommendation(name='remove_header',
+ recommended_value=False, level=OptionRecommendation.LOW,
+ help=_('Use a regular expression to try and remove the header.'
+ )
+ ),
+
+OptionRecommendation(name='header_regex',
+ recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)',
+ level=OptionRecommendation.LOW,
+ help=_('The regular expression to use to remove the header.'
+ )
+ ),
+
+OptionRecommendation(name='remove_footer',
+ recommended_value=False, level=OptionRecommendation.LOW,
+ help=_('Use a regular expression to try and remove the footer.'
+ )
+ ),
+
+OptionRecommendation(name='footer_regex',
+ recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)',
+ level=OptionRecommendation.LOW,
+ help=_('The regular expression to use to remove the footer.'
+ )
+ ),
OptionRecommendation(name='read_metadata_from_opf',
recommended_value=None, level=OptionRecommendation.LOW,
diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py
index f9788fdba8..69d6f1e511 100644
--- a/src/calibre/ebooks/conversion/preprocess.py
+++ b/src/calibre/ebooks/conversion/preprocess.py
@@ -185,17 +185,7 @@ class HTMLPreProcessor(object):
elif self.is_book_designer(html):
rules = self.BOOK_DESIGNER
elif self.is_pdftohtml(html):
- start_rules = []
end_rules = []
-
- if getattr(self.extra_opts, 'remove_header', None):
- start_rules.append(
- (re.compile(getattr(self.extra_opts, 'header_regex')), lambda match : '')
- )
- if getattr(self.extra_opts, 'remove_footer', None):
- start_rules.append(
- (re.compile(getattr(self.extra_opts, 'footer_regex')), lambda match : '')
- )
if getattr(self.extra_opts, 'unwrap_factor', None):
length = line_length(html, getattr(self.extra_opts, 'unwrap_factor'))
if length:
@@ -204,10 +194,21 @@ class HTMLPreProcessor(object):
(re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P(i|b|u)>)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines),
)
- rules = start_rules + self.PDFTOHTML + end_rules
+ rules = self.PDFTOHTML + end_rules
else:
rules = []
- for rule in self.PREPROCESS + rules:
+
+ pre_rules = []
+ if getattr(self.extra_opts, 'remove_header', None):
+ pre_rules.append(
+ (re.compile(getattr(self.extra_opts, 'header_regex')), lambda match : '')
+ )
+ if getattr(self.extra_opts, 'remove_footer', None):
+ pre_rules.append(
+ (re.compile(getattr(self.extra_opts, 'footer_regex')), lambda match : '')
+ )
+
+ for rule in self.PREPROCESS + pre_rules + rules:
html = rule[0].sub(rule[1], html)
# Handle broken XHTML w/ SVG (ugh)
diff --git a/src/calibre/ebooks/pdf/input.py b/src/calibre/ebooks/pdf/input.py
index e17d50869e..58abbd635c 100644
--- a/src/calibre/ebooks/pdf/input.py
+++ b/src/calibre/ebooks/pdf/input.py
@@ -24,16 +24,6 @@ class PDFInput(InputFormatPlugin):
help=_('Scale used to determine the length at which a line should '
'be unwrapped. Valid values are a decimal between 0 and 1. The '
'default is 0.5, this is the median line length.')),
- OptionRecommendation(name='remove_header', recommended_value=False,
- help=_('Use a regular expression to try and remove the header.')),
- OptionRecommendation(name='header_regex',
- recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)',
- help=_('The regular expression to use to remove the header.')),
- OptionRecommendation(name='remove_footer', recommended_value=False,
- help=_('Use a regular expression to try and remove the footer.')),
- OptionRecommendation(name='footer_regex',
- recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)',
- help=_('The regular expression to use to remove the footer.')),
])
def convert(self, stream, options, file_ext, log,
diff --git a/src/calibre/gui2/convert/pdf_input.py b/src/calibre/gui2/convert/pdf_input.py
index bfd658526c..e4a9541823 100644
--- a/src/calibre/gui2/convert/pdf_input.py
+++ b/src/calibre/gui2/convert/pdf_input.py
@@ -4,13 +4,8 @@ __license__ = 'GPL 3'
__copyright__ = '2009, John Schember '
__docformat__ = 'restructuredtext en'
-import re
-
-from PyQt4.Qt import SIGNAL
-
from calibre.gui2.convert.pdf_input_ui import Ui_Form
from calibre.gui2.convert import Widget
-from calibre.gui2 import qstring_to_unicode, error_dialog
class PluginWidget(Widget, Ui_Form):
@@ -19,31 +14,6 @@ class PluginWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, 'pdf_input',
- ['no_images', 'unwrap_factor', 'remove_header', 'header_regex',
- 'remove_footer', 'footer_regex'])
+ ['no_images', 'unwrap_factor'])
self.db, self.book_id = db, book_id
self.initialize_options(get_option, get_help, db, book_id)
-
- self.opt_header_regex.setEnabled(self.opt_remove_header.isChecked())
- self.opt_footer_regex.setEnabled(self.opt_remove_footer.isChecked())
-
- self.connect(self.opt_remove_header, SIGNAL('stateChanged(int)'), self.header_regex_state)
- self.connect(self.opt_remove_footer, SIGNAL('stateChanged(int)'), self.footer_regex_state)
-
- def header_regex_state(self, state):
- self.opt_header_regex.setEnabled(state)
-
- def footer_regex_state(self, state):
- self.opt_footer_regex.setEnabled(state)
-
- def pre_commit_check(self):
- for x in ('header_regex', 'footer_regex'):
- x = getattr(self, 'opt_'+x)
- try:
- pat = qstring_to_unicode(x.text())
- re.compile(pat)
- except Exception, err:
- error_dialog(self, _('Invalid regular expression'),
- _('Invalid regular expression: %s')%err).exec_()
- return False
- return True
diff --git a/src/calibre/gui2/convert/pdf_input.ui b/src/calibre/gui2/convert/pdf_input.ui
index d34c6d404b..40f480b15d 100644
--- a/src/calibre/gui2/convert/pdf_input.ui
+++ b/src/calibre/gui2/convert/pdf_input.ui
@@ -14,14 +14,14 @@
Form
- -
+
-
Line Un-Wrapping Factor:
- -
+
-
Qt::Vertical
@@ -34,7 +34,7 @@
- -
+
-
1.000000000000000
@@ -47,33 +47,13 @@
- -
+
-
No Images
- -
-
-
- Remove Header
-
-
-
- -
-
-
- Remove Footer
-
-
-
- -
-
-
- -
-
-
diff --git a/src/calibre/gui2/convert/structure_detection.py b/src/calibre/gui2/convert/structure_detection.py
index 66dff86aca..ee0a389478 100644
--- a/src/calibre/gui2/convert/structure_detection.py
+++ b/src/calibre/gui2/convert/structure_detection.py
@@ -6,10 +6,13 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
+import re
+
+from PyQt4.Qt import SIGNAL
from calibre.gui2.convert.structure_detection_ui import Ui_Form
from calibre.gui2.convert import Widget
-from calibre.gui2 import error_dialog
+from calibre.gui2 import error_dialog, qstring_to_unicode
class StructureDetectionWidget(Widget, Ui_Form):
@@ -23,7 +26,8 @@ class StructureDetectionWidget(Widget, Ui_Form):
['chapter', 'chapter_mark',
'remove_first_image',
'insert_metadata', 'page_breaks_before',
- 'preprocess_html']
+ 'preprocess_html', 'remove_header', 'header_regex',
+ 'remove_footer', 'footer_regex']
)
self.db, self.book_id = db, book_id
self.initialize_options(get_option, get_help, db, book_id)
@@ -31,8 +35,16 @@ class StructureDetectionWidget(Widget, Ui_Form):
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
'(XPath expression):'))
-
def pre_commit_check(self):
+ for x in ('header_regex', 'footer_regex'):
+ x = getattr(self, 'opt_'+x)
+ try:
+ pat = qstring_to_unicode(x.text())
+ re.compile(pat)
+ except Exception, err:
+ error_dialog(self, _('Invalid regular expression'),
+ _('Invalid regular expression: %s')%err).exec_()
+ return False
for x in ('chapter', 'page_breaks_before'):
x = getattr(self, 'opt_'+x)
if not x.check():
diff --git a/src/calibre/gui2/convert/structure_detection.ui b/src/calibre/gui2/convert/structure_detection.ui
index 768b430c5a..eebc0f0d53 100644
--- a/src/calibre/gui2/convert/structure_detection.ui
+++ b/src/calibre/gui2/convert/structure_detection.ui
@@ -14,6 +14,9 @@
Form
+ -
+
+
-
@@ -62,20 +65,27 @@
- -
+
-
+
+
+ &Footer regular expression:
+
+
+ opt_footer_regex
+
+
+
+ -
&Preprocess input file to possibly improve structure detection
- -
+
-
- -
-
-
- -
+
-
Qt::Vertical
@@ -88,6 +98,36 @@
+ -
+
+
+ &Header regular expression:
+
+
+ opt_header_regex
+
+
+
+ -
+
+
+ Remove F&ooter
+
+
+
+ -
+
+
+ Remove H&eader
+
+
+
+ -
+
+
+ -
+
+