From 12d28edd4d453aba7a6263cb76ab017cd932dc5f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 29 Jun 2012 16:19:50 +0530 Subject: [PATCH 01/22] ... --- src/calibre/devices/android/driver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index ad599a9d42..2abfd9bbe9 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -101,6 +101,7 @@ class ANDROID(USBMS): 0x685b : [0x0400, 0x0226], 0x685e : [0x0400], 0x6860 : [0x0400], + 0x6863 : [0x226], 0x6877 : [0x0400], 0x689e : [0x0400], 0xdeed : [0x0222], From 50a13872c84bc12b1388731ac5bec06524772137 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 30 Jun 2012 17:02:37 +0530 Subject: [PATCH 02/22] Paged display: Add support for full screen mode --- src/calibre/ebooks/oeb/display/paged.coffee | 7 ++++- src/calibre/gui2/viewer/documentview.py | 30 ++++++++++++--------- src/calibre/gui2/viewer/main.py | 3 ++- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/calibre/ebooks/oeb/display/paged.coffee b/src/calibre/ebooks/oeb/display/paged.coffee index ff750ff3ba..7221252707 100644 --- a/src/calibre/ebooks/oeb/display/paged.coffee +++ b/src/calibre/ebooks/oeb/display/paged.coffee @@ -66,6 +66,7 @@ class PagedDisplay this.in_paged_mode = false this.current_margin_side = 0 this.is_full_screen_layout = false + this.max_col_width = -1 set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) -> this.margin_top = margin_top @@ -108,6 +109,11 @@ class PagedDisplay # Minimum column width, for the cases when the window is too # narrow col_width = Math.max(100, ((ww - adjust)/n) - 2*sm) + if this.max_col_width > 0 and col_width > this.max_col_width + # Increase the side margin to ensure that col_width is no larger + # than max_col_width + sm += Math.ceil( (col_width - this.max_col_width) / 2.0 ) + col_width = Math.max(100, ((ww - adjust)/n) - 2*sm) this.page_width = col_width + 2*sm this.screen_width = this.page_width * this.cols_per_screen @@ -360,5 +366,4 @@ if window? # TODO: # Resizing of images -# Full screen mode # Highlight on jump_to_anchor diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index ed36bf125c..10d10b7155 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -234,21 +234,27 @@ class Document(QWebPage): # {{{ def switch_to_fullscreen_mode(self): self.in_fullscreen_mode = True - self.javascript(''' - var s = document.body.style; - s.maxWidth = "%dpx"; - s.marginLeft = "auto"; - s.marginRight = "auto"; - '''%self.max_fs_width) + if self.in_paged_mode: + self.javascript('paged_display.max_col_width = %d'%self.max_fs_width) + else: + self.javascript(''' + var s = document.body.style; + s.maxWidth = "%dpx"; + s.marginLeft = "auto"; + s.marginRight = "auto"; + '''%self.max_fs_width) def switch_to_window_mode(self): self.in_fullscreen_mode = False - self.javascript(''' - var s = document.body.style; - s.maxWidth = "none"; - s.marginLeft = "%s"; - s.marginRight = "%s"; - '''%(self.initial_left_margin, self.initial_right_margin)) + if self.in_paged_mode: + self.javascript('paged_display.max_col_width = %d'%-1) + else: + self.javascript(''' + var s = document.body.style; + s.maxWidth = "none"; + s.marginLeft = "%s"; + s.marginRight = "%s"; + '''%(self.initial_left_margin, self.initial_right_margin)) @pyqtSignature("QString") def debug(self, msg): diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 08ab731e51..4a39a6ae8d 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -477,6 +477,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): else: self.view.document.switch_to_window_mode() self.view.document.page_position.restore() + self.scrolled(self.view.scroll_fraction) def goto(self, ref): if ref: @@ -754,12 +755,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer): # There hasn't been a resize event for some time # restore the current page position. self.resize_in_progress = False - self.view.document.after_resize() if self.window_mode_changed: # This resize is part of a window mode change, special case it self.handle_window_mode_toggle() else: self.view.document.page_position.restore() + self.view.document.after_resize() def close_progress_indicator(self): self.pi.stop() From 39e8b5305c3f07daebfb9ddfe5a224a7f439b021 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 30 Jun 2012 20:02:50 +0530 Subject: [PATCH 03/22] Update Heraldo.es --- recipes/heraldo.recipe | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/recipes/heraldo.recipe b/recipes/heraldo.recipe index f3236ec4a9..b00d3f23c8 100644 --- a/recipes/heraldo.recipe +++ b/recipes/heraldo.recipe @@ -3,8 +3,8 @@ __license__ = 'GPL v3' __copyright__ = '04 December 2010, desUBIKado' __author__ = 'desUBIKado' __description__ = 'Daily newspaper from Aragon' -__version__ = 'v0.04' -__date__ = '6, Januery 2011' +__version__ = 'v0.05' +__date__ = '5, Februery 2012' ''' [url]http://www.heraldo.es/[/url] ''' @@ -38,7 +38,7 @@ class heraldo(BasicNewsRecipe): keep_only_tags = [dict(name='div', attrs={'id':['dts','com']})] remove_tags = [dict(name='a', attrs={'class':['com flo-r','enl-if','enl-df']}), - dict(name='div', attrs={'class':['brb-b-s con marg-btt','cnt-rel con']}), + dict(name='div', attrs={'class':['brb-b-s con marg-btt','cnt-rel con','col5-f1']}), dict(name='form', attrs={'class':'form'}), dict(name='ul', attrs={'id':['cont-tags','pag-1']})] @@ -72,6 +72,9 @@ class heraldo(BasicNewsRecipe): preprocess_regexps = [ -# To separate the comments with a blank line +# Para separar los comentarios con una linea en blanco (re.compile(r'
Date: Sat, 30 Jun 2012 21:18:31 +0530 Subject: [PATCH 04/22] API for kiwidude --- src/calibre/library/database2.py | 45 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 18895253d0..f08b7abc3b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1203,7 +1203,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if m: return m['mtime'] - def format_metadata(self, id_, fmt, allow_cache=True): + def format_metadata(self, id_, fmt, allow_cache=True, update_db=False, + commit=False): if not fmt: return {} fmt = fmt.upper() @@ -1218,6 +1219,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ans['size'] = stat.st_size ans['mtime'] = utcfromtimestamp(stat.st_mtime) self.format_metadata_cache[id_][fmt] = ans + if update_db: + self.conn.execute( + 'UPDATE data SET uncompressed_size=? WHERE format=? AND' + ' book=?', (stat.st_size, fmt, id_)) + if commit: + self.conn.commit() return ans def format_hash(self, id_, fmt): @@ -2564,7 +2571,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return [] return result - def rename_series(self, old_id, new_name): + def rename_series(self, old_id, new_name, change_index=True): new_name = new_name.strip() new_id = self.conn.get( '''SELECT id from series @@ -2577,22 +2584,24 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # New series exists. Must update the link, then assign a # new series index to each of the books. - # Get the list of books where we must update the series index - books = self.conn.get('''SELECT books.id - FROM books, books_series_link as lt - WHERE books.id = lt.book AND lt.series=? - ORDER BY books.series_index''', (old_id,)) + if change_index: + # Get the list of books where we must update the series index + books = self.conn.get('''SELECT books.id + FROM books, books_series_link as lt + WHERE books.id = lt.book AND lt.series=? + ORDER BY books.series_index''', (old_id,)) # Now update the link table self.conn.execute('''UPDATE books_series_link SET series=? WHERE series=?''',(new_id, old_id,)) - # Now set the indices - for (book_id,) in books: - # Get the next series index - index = self.get_next_series_num_for(new_name) - self.conn.execute('''UPDATE books - SET series_index=? - WHERE id=?''',(index, book_id,)) + if change_index: + # Now set the indices + for (book_id,) in books: + # Get the next series index + index = self.get_next_series_num_for(new_name) + self.conn.execute('''UPDATE books + SET series_index=? + WHERE id=?''',(index, book_id,)) self.dirty_books_referencing('series', new_id, commit=False) self.conn.commit() @@ -3684,4 +3693,12 @@ books_series_link feeds s = self.conn.get('''SELECT book FROM books_plugin_data WHERE name=?''', (name,)) return [x[0] for x in s] + def get_usage_count_by_id(self, field): + fm = self.field_metadata[field] + if not fm.get('link_column', None): + raise ValueError('%s is not an is_multiple field') + return self.conn.get( + 'SELECT {0}, count(*) FROM books_{1}_link GROUP BY {0}'.format( + fm['link_column'], fm['table'])) + From d20bd1f8b121088d4c4e7e6c0d5525b343e2065b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jul 2012 08:13:03 +0530 Subject: [PATCH 05/22] Updated weblogs_sll and The Age --- recipes/the_age.recipe | 35 ++++++++++++++++++----------------- recipes/weblogs_sl.recipe | 12 ++++++------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/recipes/the_age.recipe b/recipes/the_age.recipe index eddb5e5000..415ff0a25d 100644 --- a/recipes/the_age.recipe +++ b/recipes/the_age.recipe @@ -18,7 +18,7 @@ class TheAge(BasicNewsRecipe): publication_type = 'newspaper' __author__ = 'Matthew Briggs' language = 'en_AU' - + max_articles_per_feed = 1000 recursions = 0 remove_tags = [dict(name=['table', 'script', 'noscript', 'style']), dict(name='a', attrs={'href':'/'}), dict(name='a', attrs={'href':'/text/'})] @@ -47,18 +47,19 @@ class TheAge(BasicNewsRecipe): if url.startswith('/'): url = 'http://www.theage.com.au' + url title = self.tag_to_string(tag) - sections[section].append({ - 'title': title, - 'url' : url, - 'date' : strftime('%a, %d %b'), - 'description' : '', - 'content' : '', - }) - + if url != 'http://www.theage.com.au': + sections[section].append({ + 'title': title, + 'url' : url, + 'date' : strftime('%a, %d %b'), + 'description' : '', + 'content' : '', + }) + feeds = [] # Insert feeds in specified order, if available - + feedSort = [ 'National', 'World', 'Opinion', 'Columns', 'Business', 'Sport', 'Entertainment' ] for i in feedSort: if i in sections: @@ -68,12 +69,12 @@ class TheAge(BasicNewsRecipe): for i in feedSort: del sections[i] - + # Append what is left over... for i in sections: feeds.append((i,sections[i])) - + return feeds def get_cover_url(self): @@ -88,9 +89,9 @@ class TheAge(BasicNewsRecipe): return None def preprocess_html(self,soup): - + for p in soup.findAll('p'): - + # Collapse the paragraph by joining the non-tag contents contents = [i for i in p.contents if isinstance(i,unicode)] @@ -103,10 +104,10 @@ class TheAge(BasicNewsRecipe): p.extract() continue - # Shrink the fine print font + # Shrink the fine print font if contents=='This material is subject to copyright and any unauthorised use, copying or mirroring is prohibited.': p['style'] = 'font-size:small' - continue - + continue + return soup diff --git a/recipes/weblogs_sl.recipe b/recipes/weblogs_sl.recipe index e068544522..8622cccef8 100644 --- a/recipes/weblogs_sl.recipe +++ b/recipes/weblogs_sl.recipe @@ -2,8 +2,8 @@ __license__ = 'GPL v3' __copyright__ = '4 February 2011, desUBIKado' __author__ = 'desUBIKado' -__version__ = 'v0.07' -__date__ = '13, November 2011' +__version__ = 'v0.08' +__date__ = '30, June 2012' ''' http://www.weblogssl.com/ ''' @@ -33,6 +33,7 @@ class weblogssl(BasicNewsRecipe): feeds = [ (u'Xataka', u'http://feeds.weblogssl.com/xataka2') + ,(u'Xataka Smart Home', u'http://feeds.weblogssl.com/Xatakahome') ,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx') ,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil') ,(u'Xataka Android', u'http://feeds.weblogssl.com/xatakandroid') @@ -107,12 +108,14 @@ class weblogssl(BasicNewsRecipe): # Para obtener la url original del articulo a partir de la de "feedsportal" # El siguiente código es gracias al usuario "bosplans" de www.mobileread.com - # http://www.mobileread.com/forums/showthread.php?t=130297 + # http://www.mobileread.com/forums/sho...d.php?t=130297 def get_article_url(self, article): link = article.get('link', None) if link is None: return article + if link.split('/')[-4]=="xataka2": + return article.get('feedburner_origlink', article.get('link', article.get('guid'))) if link.split('/')[-1]=="story01.htm": link=link.split('/')[-2] a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A'] @@ -121,6 +124,3 @@ class weblogssl(BasicNewsRecipe): link=link.replace(a[i],b[i]) link="http://"+link return link - - - From 2b4307fc7fae9f722bf1d2b2ebd53b1d51c00d34 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jul 2012 08:56:15 +0530 Subject: [PATCH 06/22] CHM Input: Fix handling of chm files that split their html into multiple sub-directories. Fixes #1018792 (CHM ToC generated incorrectly) --- .../ebooks/conversion/plugins/chm_input.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py index a674735f1d..30cf984c77 100644 --- a/src/calibre/ebooks/conversion/plugins/chm_input.py +++ b/src/calibre/ebooks/conversion/plugins/chm_input.py @@ -152,27 +152,31 @@ class CHMInput(InputFormatPlugin): #print "=============================" log.debug('Found %d section nodes' % len(chapters)) htmlpath = os.path.splitext(hhcpath)[0] + ".html" - f = open(htmlpath, 'wb') - if chapters: - f.write('\n') - path0 = chapters[0][1] - subpath = os.path.dirname(path0) + with open(htmlpath, 'wb') as f: + if chapters: + f.write('\n') + path0 = chapters[0][1] + subpath = os.path.dirname(path0) + base = os.path.dirname(f.name) - for chapter in chapters: - title = chapter[0] - rsrcname = os.path.basename(chapter[1]) - rsrcpath = os.path.join(subpath, rsrcname) - # title should already be url encoded - url = "
" + title + " \n" - if isinstance(url, unicode): - url = url.encode('utf-8') - f.write(url) + for chapter in chapters: + title = chapter[0] + rsrcname = os.path.basename(chapter[1]) + rsrcpath = os.path.join(subpath, rsrcname) + if (not os.path.exists(os.path.join(base, rsrcpath)) and + os.path.exists(os.path.join(base, chapter[1]))): + rsrcpath = chapter[1] - f.write("") - else: - f.write(hhcdata) - f.close() + # title should already be url encoded + url = "
" + title + " \n" + if isinstance(url, unicode): + url = url.encode('utf-8') + f.write(url) + + f.write("") + else: + f.write(hhcdata) return htmlpath From b128849723b08d2d75ac655b6a32152ee04c8322 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jul 2012 10:03:26 +0530 Subject: [PATCH 07/22] Conversion pipeline: When removing the first image, also remove the html file the image is found in, if that file has no other content --- src/calibre/ebooks/oeb/transforms/jacket.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/transforms/jacket.py b/src/calibre/ebooks/oeb/transforms/jacket.py index 5947087535..8fcddc7080 100644 --- a/src/calibre/ebooks/oeb/transforms/jacket.py +++ b/src/calibre/ebooks/oeb/transforms/jacket.py @@ -13,7 +13,7 @@ from lxml import etree from calibre import guess_type, strftime from calibre.ebooks.BeautifulSoup import BeautifulSoup -from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML +from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML, xml2text, urldefrag from calibre.library.comments import comments_to_html from calibre.utils.date import is_date_undefined from calibre.ebooks.chardet import strip_encoding_declarations @@ -41,11 +41,25 @@ class Jacket(object): return removed def remove_first_image(self): + deleted_item = None for item in self.oeb.spine: removed = self.remove_images(item) if removed > 0: self.log('Removed first image') + body = XPath('//h:body')(item.data) + if body: + raw = xml2text(body[0]).strip() + imgs = XPath('//h:img|//svg:svg')(item.data) + if not raw and not imgs: + self.log('Removing %s as it has no content'%item.href) + self.oeb.manifest.remove(item) + deleted_item = item break + if deleted_item is not None: + for item in list(self.oeb.toc): + href = urldefrag(item.href)[0] + if href == deleted_item.href: + self.oeb.toc.remove(item) def insert_metadata(self, mi): self.log('Inserting metadata into book...') From 1c8a05a11f3e3bab5bff9780446d8957bd494168 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jul 2012 10:20:54 +0530 Subject: [PATCH 08/22] ... --- src/calibre/ebooks/oeb/display/paged.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/display/paged.coffee b/src/calibre/ebooks/oeb/display/paged.coffee index 7221252707..695ca3af7f 100644 --- a/src/calibre/ebooks/oeb/display/paged.coffee +++ b/src/calibre/ebooks/oeb/display/paged.coffee @@ -112,7 +112,7 @@ class PagedDisplay if this.max_col_width > 0 and col_width > this.max_col_width # Increase the side margin to ensure that col_width is no larger # than max_col_width - sm += Math.ceil( (col_width - this.max_col_width) / 2.0 ) + sm += Math.ceil( (col_width - this.max_col_width) / 2*n ) col_width = Math.max(100, ((ww - adjust)/n) - 2*sm) this.page_width = col_width + 2*sm this.screen_width = this.page_width * this.cols_per_screen From c20d82b6c56da645c7b5aa233c3e984c1ea8453d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jul 2012 14:31:05 +0530 Subject: [PATCH 09/22] ... --- resources/compiled_coffeescript.zip | Bin 43091 -> 43332 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index 00d22f259a361ba5f5974678713a2d3fdd814e3b..ae77254da8f689bd2542197920179ffd126ff5c8 100644 GIT binary patch delta 275 zcmcb7f$7L4Cg}igW)=|!5V%UXg z=fsz1rj%qT*ed86T5~A?LG9#)TC$rpxy;z=Gt(3_N-{Ew^-$H?DHtfIsVN{d+bLkG z(p0Dh8wWD7I9Ea2R>3#1BttJbH8V#;1EEzHt5$slBLyu5wmeOclO{_ExH8sGj^mWv pTqy8o8Xu~+CRZ#!hQI(45J0|N+4PxkW=+h}-;ld*R4L_N99o?NDE wlT$f`HYW-GnZ}2zdUDQ+NT%;*ll?tJC*N6N%H-q@WJ*ugTPeVnxEy3S0HTN^ssI20 From 7b98ace36ec064ae39654b65fbc355aa97fd180c Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 1 Jul 2012 11:47:45 +0200 Subject: [PATCH 10/22] Ensure that renaming an item removes the old item from its DB table. Tags, series, and publishers didn't do this before. Authors did, as did custom columns. --- src/calibre/library/database2.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index f08b7abc3b..305e12bfa2 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1455,6 +1455,24 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if notify: self.notify('metadata', [id]) + def clean_standard_field(self, field, commit=False): + # Don't bother with validity checking. Let the exception fly out so + # we can see what happened + def doit(table, ltable_col): + st = ('DELETE FROM books_%s_link WHERE (SELECT COUNT(id) ' + 'FROM books WHERE id=book) < 1;')%table + self.conn.execute(st) + st = ('DELETE FROM %(table)s WHERE (SELECT COUNT(id) ' + 'FROM books_%(table)s_link WHERE ' + '%(ltable_col)s=%(table)s.id) < 1;') % dict( + table=table, ltable_col=ltable_col) + self.conn.execute(st) + + fm = self.field_metadata[field] + doit(fm['table'], fm['link_column']) + if commit: + self.conn.commit() + def clean(self): ''' Remove orphaned entries. @@ -2557,6 +2575,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.set_tags(book_id, new_names, append=True, notify=False, commit=False) self.dirtied(books, commit=False) + self.clean_standard_field('tags', commit=False) self.conn.commit() def delete_tag_using_id(self, id): @@ -2603,6 +2622,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): SET series_index=? WHERE id=?''',(index, book_id,)) self.dirty_books_referencing('series', new_id, commit=False) + self.clean_standard_field('series', commit=False) self.conn.commit() def delete_series_using_id(self, id): @@ -2638,6 +2658,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # Get rid of the no-longer used publisher self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,)) self.dirty_books_referencing('publisher', new_id, commit=False) + self.clean_standard_field('publisher', commit=False) self.conn.commit() def delete_publisher_using_id(self, old_id): @@ -2736,7 +2757,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # metadata. Ignore it. pass # Now delete the old author from the DB - bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', (old_id,)) self.conn.execute('DELETE FROM authors WHERE id=?', (old_id,)) self.dirtied(books, commit=False) self.conn.commit() @@ -2752,6 +2772,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.set_author_sort(book_id, ss) # the caller will do a general refresh, so we don't need to # do one here + # Now delete the old author from the DB return new_id # end convenience methods From 51d866063a59d3cf4c93ef41bd8806b996734423 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 1 Jul 2012 11:49:30 +0200 Subject: [PATCH 11/22] Add new option to the series_index_auto_increment tweak, no_change, that causes calibre not to change the series_index when the series is changed. --- resources/default_tweaks.py | 1 + src/calibre/gui2/dialogs/metadata_bulk.py | 8 ++++++-- src/calibre/gui2/library/models.py | 7 +++++-- src/calibre/gui2/metadata/basic_widgets.py | 2 +- src/calibre/library/cli.py | 4 +++- src/calibre/library/database2.py | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 9f848dc5ce..10ce9b5c2c 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -21,6 +21,7 @@ defaults. # last_free - First available integer smaller than the largest existing number # Return largest existing + 1 if no free number is found # const - Assign the number 1 always +# no_change - Do not change the series index # a number - Assign that number always. The number is not in quotes. Note that # 0.0 can be used here. # Examples: diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index b7af971a63..09a244debd 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -261,8 +261,12 @@ class MyBlockingBusy(QDialog): # {{{ else: next = self.db.get_next_series_num_for(series) self.db.set_series(id, series, notify=False, commit=False) - num = next if do_autonumber and series else 1.0 - self.db.set_series_index(id, num, notify=False, commit=False) + if not series: + self.db.set_series_index(id, 1.0, notify=False, commit=False) + elif do_autonumber: # is True if do_series_restart is True + self.db.set_series_index(id, next, notify=False, commit=False) + elif tweaks['series_index_auto_increment'] != 'no_change': + self.db.set_series_index(id, 1.0, notify=False, commit=False) if do_remove_conv: self.db.delete_conversion_options(id, 'PIPE', commit=False) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index e0047c2a70..e2706c0a54 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -846,7 +846,9 @@ class BooksModel(QAbstractTableModel): # {{{ s_index = float(match.group(1)) val = pat.sub('', val).strip() elif val: - if tweaks['series_index_auto_increment'] != 'const': + # it is OK to leave s_index == None when using 'no_change' + if tweaks['series_index_auto_increment'] != 'const' and \ + tweaks['series_index_auto_increment'] != 'no_change': s_index = self.db.get_next_cc_series_num_for(val, label=label, num=None) elif typ == 'composite': @@ -915,7 +917,8 @@ class BooksModel(QAbstractTableModel): # {{{ self.db.set_series_index(id, float(match.group(1))) val = pat.sub('', val).strip() elif val: - if tweaks['series_index_auto_increment'] != 'const': + if tweaks['series_index_auto_increment'] != 'const' and \ + tweaks['series_index_auto_increment'] != 'no_change': ni = self.db.get_next_series_num_for(val) if ni != 1: self.db.set_series_index(id, ni) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index f152bf6534..250d4ffad2 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -560,7 +560,7 @@ class SeriesIndexEdit(QDoubleSpinBox): return True def increment(self): - if self.db is not None: + if tweaks['series_index_auto_increment'] != 'no_change' and self.db is not None: try: series = self.series_edit.current_val if series and series != self.original_series_name: diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 3a798a961b..6c24a1e455 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -829,7 +829,9 @@ def parse_series_string(db, label, value): val = pat.sub('', val).strip() s_index = float(match.group(1)) elif val: - if tweaks['series_index_auto_increment'] != 'const': + if tweaks['series_index_auto_increment'] == 'no_change': + pass + elif tweaks['series_index_auto_increment'] != 'const': s_index = db.get_next_cc_series_num_for(val, label=label) else: s_index = 1.0 diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 305e12bfa2..a8ad3b5d6b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -2613,7 +2613,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('''UPDATE books_series_link SET series=? WHERE series=?''',(new_id, old_id,)) - if change_index: + if change_index and tweaks['series_index_auto_increment'] != 'no_change': # Now set the indices for (book_id,) in books: # Get the next series index From 8eaecf28ee515f8667a0f3c67739db2ce496a7cd Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 1 Jul 2012 11:51:02 +0200 Subject: [PATCH 12/22] Remove superflous comment in rename_author --- src/calibre/library/database2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index a8ad3b5d6b..32cb6737a8 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -2772,7 +2772,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.set_author_sort(book_id, ss) # the caller will do a general refresh, so we don't need to # do one here - # Now delete the old author from the DB return new_id # end convenience methods From da14165c63f2259292e7de77b2dc8bf2cc5b5ce1 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 1 Jul 2012 13:47:23 -0400 Subject: [PATCH 13/22] Store: Fix Weightless. --- src/calibre/gui2/store/stores/weightless_books_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/stores/weightless_books_plugin.py b/src/calibre/gui2/store/stores/weightless_books_plugin.py index 3fa1c76851..330f3fdf0f 100644 --- a/src/calibre/gui2/store/stores/weightless_books_plugin.py +++ b/src/calibre/gui2/store/stores/weightless_books_plugin.py @@ -41,7 +41,7 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) - for data in doc.xpath('//li[@id="product"]'): + for data in doc.xpath('//li[@class="product"]'): if counter <= 0: break From 67b3e063ce5328df42058dec2cd06a881bf87b84 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 1 Jul 2012 13:49:23 -0400 Subject: [PATCH 14/22] Store: Remove OReilly plugin. --- src/calibre/customize/builtins.py | 10 --- .../gui2/store/stores/oreilly_plugin.py | 81 ------------------- 2 files changed, 91 deletions(-) delete mode 100644 src/calibre/gui2/store/stores/oreilly_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index cced3b194f..495d923289 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1511,15 +1511,6 @@ class StoreOpenBooksStore(StoreBase): drm_free_only = True headquarters = 'US' -class StoreOReillyStore(StoreBase): - name = 'OReilly' - description = u'Programming and tech ebooks from OReilly.' - actual_plugin = 'calibre.gui2.store.stores.oreilly_plugin:OReillyStore' - - drm_free_only = True - headquarters = 'US' - formats = ['APK', 'DAISY', 'EPUB', 'MOBI', 'PDF'] - class StoreOzonRUStore(StoreBase): name = 'OZON.ru' description = u'ebooks from OZON.ru' @@ -1659,7 +1650,6 @@ plugins += [ StoreMobileReadStore, StoreNextoStore, StoreOpenBooksStore, - StoreOReillyStore, StoreOzonRUStore, StorePragmaticBookshelfStore, StoreRW2010Store, diff --git a/src/calibre/gui2/store/stores/oreilly_plugin.py b/src/calibre/gui2/store/stores/oreilly_plugin.py deleted file mode 100644 index e45c072eea..0000000000 --- a/src/calibre/gui2/store/stores/oreilly_plugin.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import (unicode_literals, division, absolute_import, print_function) - -__license__ = 'GPL 3' -__copyright__ = '2011, John Schember ' -__docformat__ = 'restructuredtext en' - -import urllib -from contextlib import closing - -from lxml import html - -from PyQt4.Qt import QUrl - -from calibre import browser, url_slash_cleaner -from calibre.gui2 import open_url -from calibre.gui2.store import StorePlugin -from calibre.gui2.store.basic_config import BasicStoreConfig -from calibre.gui2.store.search_result import SearchResult -from calibre.gui2.store.web_store_dialog import WebStoreDialog - -class OReillyStore(BasicStoreConfig, StorePlugin): - - def open(self, parent=None, detail_item=None, external=False): - url = 'http://oreilly.com/ebooks/' - - if external or self.config.get('open_external', False): - open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) - else: - d = WebStoreDialog(self.gui, url, parent, detail_item) - d.setWindowTitle(self.name) - d.set_tags(self.config.get('tags', '')) - d.exec_() - - def search(self, query, max_results=10, timeout=60): - url = 'http://search.oreilly.com/?t1=Books&t2=Format&t3=Ebook&q=' + urllib.quote_plus(query) - - br = browser() - - counter = max_results - with closing(br.open(url, timeout=timeout)) as f: - doc = html.fromstring(f.read()) - for data in doc.xpath('//div[@class="result"]'): - if counter <= 0: - break - - ebook = ' '.join(data.xpath('.//p[@class="note"]/text()')) - if 'ebook' not in ebook.lower(): - continue - - id = ''.join(data.xpath('./div[@class="book_text"]//p[@class="title"]/a/@href')) - - cover_url = ''.join(data.xpath('./a/img[1]/@src')) - - title = ''.join(data.xpath('./div[@class="book_text"]/p[@class="title"]/a/text()')) - author = ''.join(data.xpath('./div[@class="book_text"]/p[@class="note"][1]/text()')) - author = author.split('By ')[-1].strip() - - # Get the detail here because we need to get the ebook id for the detail_item. - with closing(br.open(id, timeout=timeout)) as nf: - idoc = html.fromstring(nf.read()) - - for td in idoc.xpath('//td[@class="optionsTd"]'): - if 'ebook' in ''.join(td.xpath('.//text()')).lower(): - price = ''.join(td.xpath('.//span[@class="price"]/text()')).strip() - formats = ''.join(td.xpath('.//a[@id="availableFormats"]/text()')).strip() - break - - counter -= 1 - - s = SearchResult() - s.cover_url = cover_url.strip() - s.title = title.strip() - s.author = author.strip() - s.detail_item = id.strip() - s.price = price.strip() - s.drm = SearchResult.DRM_UNLOCKED - s.formats = formats.upper() - - yield s From 340998fbee0fdfdc28e482480cc7f58182f08538 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 1 Jul 2012 13:59:20 -0400 Subject: [PATCH 15/22] Store: ebooks.com fix author and cover display. --- src/calibre/gui2/store/stores/ebooks_com_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/store/stores/ebooks_com_plugin.py b/src/calibre/gui2/store/stores/ebooks_com_plugin.py index 656984a86d..7bf6704d9f 100644 --- a/src/calibre/gui2/store/stores/ebooks_com_plugin.py +++ b/src/calibre/gui2/store/stores/ebooks_com_plugin.py @@ -64,11 +64,11 @@ class EbookscomStore(BasicStoreConfig, StorePlugin): continue id = mo.group() - cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src')) + cover_url = ''.join(data.xpath('.//div[contains(@class, "img")]//img/@src')) title = ''.join(data.xpath( 'descendant::span[@class="book-title"]/a/text()')).strip() - author = ''.join(data.xpath( + author = ', '.join(data.xpath( 'descendant::span[@class="author"]/a/text()')).strip() if not title or not author: continue From 343d8f448c59fdd456c195b56460f75a7b71ebe1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 10:17:54 +0530 Subject: [PATCH 16/22] Get Books: Fix ozon.ru --- src/calibre/ebooks/metadata/sources/ozon.py | 81 ++++++++++--------- .../gui2/store/stores/ozon_ru_plugin.py | 53 +++++++----- 2 files changed, 80 insertions(+), 54 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/ozon.py b/src/calibre/ebooks/metadata/sources/ozon.py index 3845ebf97b..ebb104818f 100644 --- a/src/calibre/ebooks/metadata/sources/ozon.py +++ b/src/calibre/ebooks/metadata/sources/ozon.py @@ -54,30 +54,35 @@ class Ozon(Source): # for ozon.ru search we have to format ISBN with '-' isbn = _format_isbn(log, identifiers.get('isbn', None)) - # TODO: format isbn! - qItems = set([isbn, title]) - if authors: - qItems |= frozenset(authors) - qItems.discard(None) - qItems.discard('') - qItems = map(_quoteString, qItems) - - q = u' '.join(qItems).strip() - log.info(u'search string: ' + q) - - if isinstance(q, unicode): - q = q.encode('utf-8') - if not q: - return None - - search_url += quote_plus(q) + ozonid = identifiers.get('ozon', None) + + unk = unicode(_('Unknown')).upper() + if (title and title != unk) or (authors and authors != [unk]) or isbn or not ozonid: + qItems = set([isbn, title]) + if authors: + qItems |= frozenset(authors) + qItems.discard(None) + qItems.discard('') + qItems = map(_quoteString, qItems) + + q = u' '.join(qItems).strip() + log.info(u'search string: ' + q) + + if isinstance(q, unicode): + q = q.encode('utf-8') + if not q: + return None + + search_url += quote_plus(q) + else: + search_url = self.ozon_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % ozonid + log.debug(u'search url: %r'%search_url) - return search_url # }}} def identify(self, log, result_queue, abort, title=None, authors=None, - identifiers={}, timeout=30): # {{{ + identifiers={}, timeout=60): # {{{ from lxml import etree from calibre.ebooks.chardet import xml_to_unicode @@ -99,7 +104,7 @@ class Ozon(Source): try: parser = etree.XMLParser(recover=True, no_network=True) feed = etree.fromstring(xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True)[0], parser=parser) - entries = feed.xpath('//*[local-name() = "SearchItems"]') + entries = feed.xpath('//*[local-name()="SearchItems" or local-name()="ItemDetail"]') if entries: metadata = self.get_metadata(log, entries, title, authors, identifiers) self.get_all_details(log, metadata, abort, result_queue, identifiers, timeout) @@ -112,8 +117,8 @@ class Ozon(Source): def get_metadata(self, log, entries, title, authors, identifiers): # {{{ # some book titles have extra characters like this # TODO: make a twick - reRemoveFromTitle = None - #reRemoveFromTitle = re.compile(r'[?!:.,;+-/&%"\'=]') + #reRemoveFromTitle = None + reRemoveFromTitle = re.compile(r'[?!:.,;+-/&%"\'=]') title = unicode(title).upper() if title else '' if reRemoveFromTitle: @@ -163,7 +168,7 @@ class Ozon(Source): metadata.append(mi) #log.debug(u'added metadata %s %s.'%(mi.title, mi.authors)) else: - log.debug(u'skipped metadata %s %s. (does not match the query)'%(mi.title, mi.authors)) + log.debug(u'skipped metadata %s %s. (does not match the query)'%(unicode(mi.title), mi.authors)) return metadata # }}} @@ -301,7 +306,7 @@ class Ozon(Source): if series: metadata.series = series - xpt = u'normalize-space(substring-after(//meta[@name="description"]/@content, "ISBN"))' + xpt = u'normalize-space(//*[@class="product-detail"]//text()[starts-with(., "ISBN")])' isbn_str = doc.xpath(xpt) if isbn_str: all_isbns = [check_isbn(isbn) for isbn in self.isbnRegex.findall(isbn_str) if _verifyISBNIntegrity(log, isbn)] @@ -326,7 +331,7 @@ class Ozon(Source): # can be set before from xml search responce if not metadata.pubdate: - xpt = u'normalize-space(//div[@class="product-misc"]//text()[contains(., "г.")])' + xpt = u'normalize-space(substring-after(//div[@class="product-detail"]//text()[contains(., "г.")],";"))' yearIn = doc.xpath(xpt) if yearIn: matcher = re.search(r'\d{4}', yearIn) @@ -334,17 +339,20 @@ class Ozon(Source): metadata.pubdate = toPubdate(log, matcher.group(0)) # overwrite comments from HTML if any - xpt = u'//table[@id="detail_description"]//tr/td' + xpt = u'//*[@id="detail_description"]//*[contains(text(), "От производителя")]/../node()[not(self::comment())][not(self::br)][preceding::*[contains(text(), "От производителя")]]' + from lxml.etree import ElementBase comment_elem = doc.xpath(xpt) if comment_elem: - comments = unicode(etree.tostring(comment_elem[0], encoding=unicode)) - if comments: - # cleanup root tag, TODO: remove tags like object/embeded - comments = re.sub(ur'\A.*?|.*\Z', u'', comments.strip(), re.MULTILINE).strip() - if comments and (not metadata.comments or len(comments) > len(metadata.comments)): - metadata.comments = comments - else: - log.debug('HTML book description skipped in favour of search service xml responce') + comments = u'' + for node in comment_elem: + if isinstance(node, ElementBase): + comments += unicode(etree.tostring(node, encoding=unicode)) + elif isinstance(node, basestring) and node.strip(): + comments += unicode(node) + u'\n' + if comments and (not metadata.comments or len(comments) > len(metadata.comments)): + metadata.comments = comments + else: + log.debug('HTML book description skipped in favour of search service xml responce') else: log.debug('No book description found in HTML') # }}} @@ -430,7 +438,8 @@ def _translageLanguageToCode(displayLang): # {{{ u'Китайский': 'zh', u'Японский': 'ja', u'Финский' : 'fi', - u'Польский' : 'pl',} + u'Польский' : 'pl', + u'Украинский' : 'uk',} return langTbl.get(displayLang, None) # }}} @@ -454,7 +463,7 @@ def toPubdate(log, yearAsString): # {{{ res = None if yearAsString: try: - res = parse_only_date(yearAsString) + res = parse_only_date(u"01.01." + yearAsString) except: log.error('cannot parse to date %s'%yearAsString) return res diff --git a/src/calibre/gui2/store/stores/ozon_ru_plugin.py b/src/calibre/gui2/store/stores/ozon_ru_plugin.py index 5d977700c8..b54bf01daf 100644 --- a/src/calibre/gui2/store/stores/ozon_ru_plugin.py +++ b/src/calibre/gui2/store/stores/ozon_ru_plugin.py @@ -46,30 +46,37 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): d.set_tags(self.config.get('tags', '')) d.exec_() - - def search(self, query, max_results=10, timeout=60): + def search(self, query, max_results=15, timeout=60): search_url = self.shop_url + '/webservice/webservice.asmx/SearchWebService?'\ 'searchText=%s&searchContext=ebook' % urllib2.quote(query) + search_urls = [ search_url ] + + ## add this as the fist try if it looks like ozon ID + if re.match("^\d{6,9}$", query): + ozon_detail = self.shop_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % query + search_urls.insert(0, ozon_detail) + xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())' - counter = max_results br = browser() - with closing(br.open(search_url, timeout=timeout)) as f: - raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] - doc = etree.fromstring(raw) - for data in doc.xpath('//*[local-name() = "SearchItems"]'): - if counter <= 0: - break - counter -= 1 + + for url in search_urls: + with closing(br.open(url, timeout=timeout)) as f: + raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] + doc = etree.fromstring(raw) + for data in doc.xpath('//*[local-name()="SearchItems" or local-name()="ItemDetail"]'): + if counter <= 0: + break + counter -= 1 - s = SearchResult() - s.detail_item = data.xpath(xp_template.format('ID')) - s.title = data.xpath(xp_template.format('Name')) - s.author = data.xpath(xp_template.format('Author')) - s.price = data.xpath(xp_template.format('Price')) - s.cover_url = data.xpath(xp_template.format('Picture')) - s.price = format_price_in_RUR(s.price) - yield s + s = SearchResult() + s.detail_item = data.xpath(xp_template.format('ID')) + s.title = data.xpath(xp_template.format('Name')) + s.author = data.xpath(xp_template.format('Author')) + s.price = data.xpath(xp_template.format('Price')) + s.cover_url = data.xpath(xp_template.format('Picture')) + s.price = format_price_in_RUR(s.price) + yield s def get_details(self, search_result, timeout=60): url = self.shop_url + '/context/detail/id/' + urllib2.quote(search_result.detail_item) @@ -97,6 +104,16 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): search_result.formats = ', '.join(_parse_ebook_formats(formats)) # unfortunately no direct links to download books (only buy link) # search_result.downloads['BF2'] = self.shop_url + '/order/digitalorder.aspx?id=' + + urllib2.quote(search_result.detail_item) + + #

21500 руб.

+ # + # + + # if the price not in the search result (the ID search case) + if not search_result.price: + price = doc.xpath(u'normalize-space(//*[@itemprop="price"]/text())') + search_result.price = format_price_in_RUR(price) + return result def format_price_in_RUR(price): From e6788b5814226fc812447ca258c05c62c2baebbd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 10:21:10 +0530 Subject: [PATCH 17/22] Update FHM UK --- recipes/fhm_uk.recipe | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/recipes/fhm_uk.recipe b/recipes/fhm_uk.recipe index 07f2b4b64e..6ee5ae3fb6 100644 --- a/recipes/fhm_uk.recipe +++ b/recipes/fhm_uk.recipe @@ -2,19 +2,19 @@ from calibre.web.feeds.news import BasicNewsRecipe class AdvancedUserRecipe1325006965(BasicNewsRecipe): title = u'FHM UK' - description = 'Good News for Men' + description = 'Good News for Men.' cover_url = 'http://www.greatmagazines.co.uk/covers/large/w197/current/fhm.jpg' # cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/373529_38324934806_64930243_n.jpg' masthead_url = 'http://www.fhm.com/App_Resources/Images/Site/re-design/logo.gif' __author__ = 'Dave Asbury' - # last updated 14/4/12 + # last updated 1/7/12 language = 'en_GB' oldest_article = 28 - max_articles_per_feed = 12 + max_articles_per_feed = 8 remove_empty_feeds = True no_stylesheets = True #auto_cleanup = True - #articles_are_obfuscated = True + # articles_are_obfuscated = True keep_only_tags = [ dict(name='h1'), dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}), @@ -28,11 +28,18 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe): #] feeds = [ - (u'From the Homepage',u'http://feed43.com/0032328550253453.xml'), - #http://feed43.com/8053226782885416.xml'), - (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), - (u'Upgrade',u'http://feed43.com/0877305847443234.xml'), - #(u'The Final Countdown', u'http://feed43.com/3576106158530118.xml'), - #(u'Gaming',u'http://feed43.com/0755006465351035.xml'), - (u'Gaming',u'http://feed43.com/6537162612465672.xml'), + (u'Homepage 1',u'http://feed43.com/6655867614547036.xml'), + (u'Homepage 2',u'http://feed43.com/4167731873103110.xml'), + (u'Homepage 3',u'http://feed43.com/7667138788771570.xml'), + (u'Homepage 4',u'http://feed43.com/6550421522527341.xml'), + (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), + (u'Gaming',u'http://feed43.com/6537162612465672.xml'), + (u'Girls',u'http://feed43.com/3674777224513254.xml'), ] + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' From c8b5db52098f5a417ecf1f58343db1ef1d3c1668 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 14:12:28 +0530 Subject: [PATCH 18/22] Fix (I hope) crashes in MultiCompleteComboBox. Also the drop down arrow now only shows the items that start with whatever text is currently entered in the box --- src/calibre/gui2/complete.py | 43 ++++++++-------------- src/calibre/gui2/convert/metadata.py | 28 +++----------- src/calibre/gui2/custom_column_widgets.py | 30 ++------------- src/calibre/gui2/dialogs/add_empty_book.py | 12 +----- src/calibre/gui2/dialogs/metadata_bulk.py | 19 ++-------- src/calibre/gui2/dialogs/search.py | 9 +---- src/calibre/gui2/languages.py | 2 - src/calibre/gui2/library/delegates.py | 4 -- src/calibre/gui2/metadata/basic_widgets.py | 43 ++++++---------------- 9 files changed, 41 insertions(+), 149 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index fb1f39dfa3..9d78003231 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, - QApplication, QCompleter, pyqtSignal) + QApplication, QCompleter) from calibre.utils.icu import sort_key, lower from calibre.gui2 import NONE @@ -56,7 +56,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM): to complete non multiple fields as well. ''' - def __init__(self, parent=None): + def __init__(self, parent=None, completer_widget=None): QLineEdit.__init__(self, parent) self.sep = ',' @@ -66,7 +66,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM): self._model = CompleteModel(parent=self) self._completer = c = QCompleter(self._model, self) - c.setWidget(self) + c.setWidget(self if completer_widget is None else completer_widget) c.setCompletionMode(QCompleter.PopupCompletion) c.setCaseSensitivity(Qt.CaseInsensitive) c.setModelSorting(self._model.sorting) @@ -158,21 +158,13 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM): class MultiCompleteComboBox(EnComboBox): - clear_edit_text = pyqtSignal() - def __init__(self, *args): EnComboBox.__init__(self, *args) - self.setLineEdit(MultiCompleteLineEdit(self)) - # Needed to allow changing the case of an existing item - # otherwise on focus out, the text is changed to the - # item that matches case insensitively - c = self.lineEdit().completer() - c.setCaseSensitivity(Qt.CaseSensitive) - self.dummy_model = CompleteModel(self) - c.setModel(self.dummy_model) - self.lineEdit()._completer.setWidget(self) - self.clear_edit_text.connect(self.clearEditText, - type=Qt.QueuedConnection) + self.le = MultiCompleteLineEdit(self, completer_widget=self) + self.setLineEdit(self.le) + + def showPopup(self): + self.le._completer.complete() def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) @@ -187,18 +179,10 @@ class MultiCompleteComboBox(EnComboBox): self.lineEdit().set_add_separator(what) def show_initial_value(self, what): - ''' - Show an initial value. Handle the case of the initial value being blank - correctly (on Qt 4.8.0 having a blank value causes the first value from - the completer to be shown, when the event loop runs). - ''' - what = unicode(what) + what = unicode(what) if what else u'' le = self.lineEdit() - if not what.strip(): - self.clear_edit_text.emit() - else: - self.setEditText(what) - le.selectAll() + self.setEditText(what) + le.selectAll() if __name__ == '__main__': from PyQt4.Qt import QDialog, QVBoxLayout @@ -207,5 +191,8 @@ if __name__ == '__main__': d.setLayout(QVBoxLayout()) le = MultiCompleteComboBox(d) d.layout().addWidget(le) - le.all_items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', 'oothree'] + items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', + 'oothree'] + le.update_items_cache(items) + le.show_initial_value('') d.exec_() diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index ba2fa0713e..3643e5548e 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -12,8 +12,8 @@ from PyQt4.Qt import QPixmap, SIGNAL from calibre.gui2 import choose_images, error_dialog from calibre.gui2.convert.metadata_ui import Ui_Form -from calibre.ebooks.metadata import (authors_to_string, string_to_authors, - MetaInformation, title_sort) +from calibre.ebooks.metadata import (string_to_authors, MetaInformation, + title_sort) from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ptempfile import PersistentTemporaryFile from calibre.gui2.convert import Widget @@ -74,14 +74,12 @@ class MetadataWidget(Widget, Ui_Form): mi = self.db.get_metadata(self.book_id, index_is_id=True) self.title.setText(mi.title) - if mi.publisher: - self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher)) + self.publisher.show_initial_value(mi.publisher if mi.publisher else '') 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_items_cache(self.db.all_tags()) self.comment.html = comments_to_html(mi.comments) if mi.comments else '' - if mi.series: - self.series.setCurrentIndex(self.series.findText(mi.series)) + self.series.show_initial_value(mi.series if mi.series else '') if mi.series_index is not None: try: self.series_index.setValue(mi.series_index) @@ -118,16 +116,11 @@ class MetadataWidget(Widget, Ui_Form): self.author.set_add_separator(tweaks['authors_completer_append_separator']) self.author.update_items_cache(self.db.all_author_names()) - for i in all_authors: - id, name = i - name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')]) - self.author.addItem(name) - au = self.db.authors(self.book_id, True) if not au: au = _('Unknown') au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')]) - self.author.setEditText(au) + self.author.show_initial_value(au) def initialize_series(self): all_series = self.db.all_series() @@ -135,22 +128,12 @@ class MetadataWidget(Widget, Ui_Form): self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) - 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(key=lambda x : sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) - 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: @@ -179,6 +162,7 @@ class MetadataWidget(Widget, Ui_Form): if tags: mi.tags = tags + print (mi) return mi def select_cover(self): diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 654a9f4b5b..c9c8255076 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -314,14 +314,7 @@ class Text(Base): if self.col_metadata['is_multiple']: self.setter(val) else: - idx = None - for i, c in enumerate(values): - if c == val: - idx = i - self.widgets[1].addItem(c) - self.widgets[1].setEditText('') - if idx is not None: - self.widgets[1].setCurrentIndex(idx) + self.widgets[1].show_initial_value(val) def setter(self, val): if self.col_metadata['is_multiple']: @@ -396,16 +389,8 @@ class Series(Base): self.initial_index = s_index self.initial_val = val val = self.normalize_db_val(val) - idx = None - self.name_widget.clear() - for i, c in enumerate(values): - if c == val: - idx = i - self.name_widget.addItem(c) self.name_widget.update_items_cache(values) - self.name_widget.setEditText('') - if idx is not None: - self.widgets[1].setCurrentIndex(idx) + self.name_widget.show_initial_value(val) def getter(self): n = unicode(self.name_widget.currentText()).strip() @@ -860,8 +845,6 @@ class BulkSeries(BulkBase): self.idx_widget.setChecked(False) self.main_widget.set_separator(None) self.main_widget.update_items_cache(self.all_values) - for c in self.all_values: - self.main_widget.addItem(c) self.main_widget.setEditText('') self.a_c_checkbox.setChecked(False) @@ -1005,15 +988,8 @@ class BulkText(BulkBase): if not self.col_metadata['is_multiple']: val = self.get_initial_value(book_ids) self.initial_val = val = self.normalize_db_val(val) - idx = None self.main_widget.blockSignals(True) - for i, c in enumerate(self.all_values): - if c == val: - idx = i - self.main_widget.addItem(c) - self.main_widget.setEditText('') - if idx is not None: - self.main_widget.setCurrentIndex(idx) + self.main_widget.show_initial_value(val) self.main_widget.blockSignals(False) def commit(self, book_ids, notify=False): diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py index d4990e14d4..218bd90483 100644 --- a/src/calibre/gui2/dialogs/add_empty_book.py +++ b/src/calibre/gui2/dialogs/add_empty_book.py @@ -6,8 +6,7 @@ __license__ = 'GPL v3' from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ QApplication, QSpinBox, QToolButton, QIcon -from calibre.ebooks.metadata import authors_to_string, string_to_authors -from calibre.utils.icu import sort_key +from calibre.ebooks.metadata import string_to_authors from calibre.gui2.complete import MultiCompleteComboBox from calibre.utils.config import tweaks @@ -56,17 +55,10 @@ class AddEmptyBookDialog(QDialog): self.authors_combo.setEditText(_('Unknown')) def initialize_authors(self, db, author): - all_authors = db.all_authors() - all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = [name.strip().replace('|', ',') for n in name.split(',')] - self.authors_combo.addItem(authors_to_string(name)) - au = author if not au: au = _('Unknown') - self.authors_combo.setEditText(au.replace('|', ',')) + self.authors_combo.show_initial_value(au.replace('|', ',')) self.authors_combo.set_separator('&') self.authors_combo.set_space_before_sep(True) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 09a244debd..b8f30f3541 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -876,38 +876,25 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): all_authors = self.db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = name.strip().replace('|', ',') - self.authors.addItem(name) - self.authors.setEditText('') - self.authors.set_separator('&') self.authors.set_space_before_sep(True) self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.update_items_cache(self.db.all_author_names()) + self.authors.show_initial_value('') def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) - - for i in all_series: - id, name = i - self.series.addItem(name) - self.series.setEditText('') + self.series.show_initial_value('') def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) - - for i in all_publishers: - id, name = i - self.publisher.addItem(name) - self.publisher.setEditText('') + self.publisher.show_initial_value('') def tag_editor(self, *args): d = TagEditor(self, self.db, None) diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 8736ae2259..cf63d150e6 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -25,10 +25,6 @@ class SearchDialog(QDialog, Ui_Dialog): all_authors = db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = name.strip().replace('|', ',') - self.authors_box.addItem(name) self.authors_box.setEditText('') self.authors_box.set_separator('&') self.authors_box.set_space_before_sep(True) @@ -39,10 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog): all_series.sort(key=lambda x : sort_key(x[1])) self.series_box.set_separator(None) self.series_box.update_items_cache([x[1] for x in all_series]) - for i in all_series: - id, name = i - self.series_box.addItem(name) - self.series_box.setEditText('') + self.series_box.show_initial_value('') all_tags = db.all_tags() self.tags_box.update_items_cache(all_tags) diff --git a/src/calibre/gui2/languages.py b/src/calibre/gui2/languages.py index 0dfbb38b08..f067027097 100644 --- a/src/calibre/gui2/languages.py +++ b/src/calibre/gui2/languages.py @@ -32,8 +32,6 @@ class LanguagesEdit(MultiCompleteComboBox): all_items = sorted(self._lang_map.itervalues(), key=lambda x: (-pmap.get(x, 0), sort_key(x))) self.update_items_cache(all_items) - for item in all_items: - self.addItem(item) @property def vals(self): diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 60b8e3445d..77c3152842 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -125,8 +125,6 @@ class TextDelegate(QStyledItemDelegate): # {{{ editor.set_separator(None) complete_items = [i[1] for i in self.auto_complete_function()] editor.update_items_cache(complete_items) - for item in sorted(complete_items, key=sort_key): - editor.addItem(item) ct = index.data(Qt.DisplayRole).toString() editor.show_initial_value(ct) else: @@ -166,8 +164,6 @@ class CompleteDelegate(QStyledItemDelegate): # {{{ all_items = list(self.db.all_custom( label=self.db.field_metadata.key_to_label(col))) editor.update_items_cache(all_items) - for item in sorted(all_items, key=sort_key): - editor.addItem(item) ct = index.data(Qt.DisplayRole).toString() editor.show_initial_value(ct) else: diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 250d4ffad2..d2a983415f 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -246,14 +246,6 @@ class AuthorsEdit(MultiCompleteComboBox): def initialize(self, db, id_): self.books_to_refresh = set([]) - all_authors = db.all_authors() - all_authors.sort(key=lambda x : sort_key(x[1])) - self.clear() - for i in all_authors: - id, name = i - name = name.strip().replace('|', ',') - self.addItem(name) - self.set_separator('&') self.set_space_before_sep(True) self.set_add_separator(tweaks['authors_completer_append_separator']) @@ -299,7 +291,6 @@ class AuthorsEdit(MultiCompleteComboBox): self.setEditText(' & '.join([x.strip() for x in val])) self.lineEdit().setCursorPosition(0) - return property(fget=fget, fset=fset) def break_cycles(self): @@ -488,19 +479,12 @@ class SeriesEdit(MultiCompleteComboBox): all_series.sort(key=lambda x : sort_key(x[1])) self.update_items_cache([x[1] for x in all_series]) series_id = db.series_id(id_, index_is_id=True) - idx, c = None, 0 - self.clear() + inval = '' for i in all_series: - id, name = i - if id == series_id: - idx = c - self.addItem(name) - c += 1 - - self.lineEdit().setText('') - if idx is not None: - self.setCurrentIndex(idx) - self.original_val = self.current_val + if i[0] == series_id: + inval = i[1] + break + self.original_val = self.current_val = inval def commit(self, db, id_): series = self.current_val @@ -1373,17 +1357,12 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ all_publishers.sort(key=lambda x : sort_key(x[1])) self.update_items_cache([x[1] for x in all_publishers]) publisher_id = db.publisher_id(id_, index_is_id=True) - idx = None - self.clear() - for i, x in enumerate(all_publishers): - id_, name = x - if id_ == publisher_id: - idx = i - self.addItem(name) - - self.setEditText('') - if idx is not None: - self.setCurrentIndex(idx) + inval = '' + for pid, name in all_publishers: + if pid == publisher_id: + inval = name + break + self.original_val = self.current_val = inval def commit(self, db, id_): self.books_to_refresh |= db.set_publisher(id_, self.current_val, From 7ace9fafb29b20a335fc41f98f65c25a2e1d57c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 14:15:48 +0530 Subject: [PATCH 19/22] ... --- setup/installer/windows/notes.rst | 2 +- src/calibre/gui2/convert/metadata.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst index 0a9c904ff7..f987f83a3b 100644 --- a/setup/installer/windows/notes.rst +++ b/setup/installer/windows/notes.rst @@ -97,7 +97,7 @@ Now, run configure and make:: -no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly - configure -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake + configure -ltcg -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake Add the path to the bin folder inside the Qt dir to your system PATH. diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 3643e5548e..6d43abdf63 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -162,7 +162,6 @@ class MetadataWidget(Widget, Ui_Form): if tags: mi.tags = tags - print (mi) return mi def select_cover(self): From 31e3a273927c8da0d31489b5c7db29db699bfb90 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 14:31:19 +0530 Subject: [PATCH 20/22] Create separate driver for Pocketbook 622 rather than using Android driver --- resources/compiled_coffeescript.zip | Bin 43332 -> 43332 bytes src/calibre/customize/builtins.py | 4 ++-- src/calibre/devices/android/driver.py | 1 - src/calibre/devices/eb600/driver.py | 13 +++++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index ae77254da8f689bd2542197920179ffd126ff5c8..d7fad735ba651cfe4b99775b7f301581150e1182 100644 GIT binary patch delta 32 kcmX?diRs8CCf)#VW)=|!5cpg8Xd`dn3T7ZZIdR2f0Jt;^%>V!Z delta 32 kcmX?diRs8CCf)#VW)=|!5V% Date: Mon, 2 Jul 2012 16:09:44 +0530 Subject: [PATCH 21/22] ... --- src/calibre/gui2/complete.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 9d78003231..e66255cc05 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -164,7 +164,13 @@ class MultiCompleteComboBox(EnComboBox): self.setLineEdit(self.le) def showPopup(self): - self.le._completer.complete() + c = self.le._completer + c.setCompletionPrefix('') + c.complete() + + def hidePopup(self): + self.le.update_completions() + EnComboBox.hidePopup(self) def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) From 0201e87440d9a089cd6c6f728a7fc8223837bb48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 16:15:22 +0530 Subject: [PATCH 22/22] ... --- src/calibre/gui2/complete.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index e66255cc05..947493cbb9 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -168,10 +168,6 @@ class MultiCompleteComboBox(EnComboBox): c.setCompletionPrefix('') c.complete() - def hidePopup(self): - self.le.update_completions() - EnComboBox.hidePopup(self) - def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items)