From 9c0d9aa7241233b26620f16edcfd735e398c4be9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 10:54:43 -0600 Subject: [PATCH 01/31] National Geographic by Anonymous --- recipes/national_geographic_de.recipe | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 recipes/national_geographic_de.recipe diff --git a/recipes/national_geographic_de.recipe b/recipes/national_geographic_de.recipe new file mode 100644 index 0000000000..46f4783f41 --- /dev/null +++ b/recipes/national_geographic_de.recipe @@ -0,0 +1,25 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305567197(BasicNewsRecipe): + title = u'National Geographic (DE)' + __author__ = 'Anonymous' + language = 'de' + oldest_article = 7 + max_articles_per_feed = 1000 + no_stylesheets = True + use_embedded_content = False + remove_javascript = True + cover_url = 'http://www.nationalgeographic.de/images/national-geographic-logo.jpg' + keep_only_tags = [ + dict(name='div', attrs={'class':['contentbox_no_top_border']}) ] + + remove_tags = [ + dict(name='div', attrs={'class':'related'}), + dict(name='li', attrs={'class':'first'}), + dict(name='div', attrs={'class':'extrasbox_inner'}), + + ] + + feeds = [ (u'National Geographic', u'http://feeds.nationalgeographic.de/ng-neueste-artikel'), + + ] From 87efd04aa1becb598d054a910cb8088ec779aa5b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 11:10:38 -0600 Subject: [PATCH 02/31] Focus (DE) by Anonymous --- recipes/focus_de.recipe | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 recipes/focus_de.recipe diff --git a/recipes/focus_de.recipe b/recipes/focus_de.recipe new file mode 100644 index 0000000000..d0b0f4aef8 --- /dev/null +++ b/recipes/focus_de.recipe @@ -0,0 +1,48 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305567197(BasicNewsRecipe): + title = u'Focus (DE)' + __author__ = 'Anonymous' + language = 'de' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + remove_javascript = True + + def print_version(self, url): + return url + '?drucken=1' + + keep_only_tags = [ + dict(name='div', attrs={'id':['article']}) ] + + remove_tags = [dict(name='div', attrs={'class':'sidebar'}), + dict(name='div', attrs={'class':'commentForm'}), + dict(name='div', attrs={'class':'comment clearfix oid-3534591 open'}), + dict(name='div', attrs={'class':'similarityBlock'}), + dict(name='div', attrs={'class':'footer'}), + dict(name='div', attrs={'class':'getMoreComments'}), + dict(name='div', attrs={'class':'moreComments'}), + dict(name='div', attrs={'class':'ads'}), + dict(name='div', attrs={'class':'articleContent'}), + + + ] + remove_tags_after = [ + dict(name='div',attrs={'class':['commentForm','title', 'actions clearfix']}) + ] + + + feeds = [ (u'Eilmeldungen', u'http://rss2.focus.de/c/32191/f/533875/index.rss'), + (u'Auto-News', u'http://rss2.focus.de/c/32191/f/443320/index.rss'), + (u'Digital-News', u'http://rss2.focus.de/c/32191/f/443315/index.rss'), + (u'Finanzen-News', u'http://rss2.focus.de/c/32191/f/443317/index.rss'), + (u'Gesundheit-News', u'http://rss2.focus.de/c/32191/f/443314/index.rss'), + (u'Immobilien-News', u'http://rss2.focus.de/c/32191/f/443318/index.rss'), + (u'Kultur-News', u'http://rss2.focus.de/c/32191/f/443321/index.rss'), + (u'Panorama-News', u'http://rss2.focus.de/c/32191/f/533877/index.rss'), + (u'Politik-News', u'http://rss2.focus.de/c/32191/f/443313/index.rss'), + (u'Reisen-News', u'http://rss2.focus.de/c/32191/f/443316/index.rss'), + (u'Sport-News', u'http://rss2.focus.de/c/32191/f/443319/index.rss'), + (u'Wissen-News', u'http://rss2.focus.de/c/32191/f/533876/index.rss'), + ] From 224968f239436ad14f8d8bb3f481cddb07f01744 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 11:40:45 -0600 Subject: [PATCH 03/31] Windows: If creating a bytestring temp dir fails, create a unicode one and hope the rest of calibre can handle it --- src/calibre/ptempfile.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py index ac7df1c4e3..bff13dd248 100644 --- a/src/calibre/ptempfile.py +++ b/src/calibre/ptempfile.py @@ -39,8 +39,20 @@ def base_dir(): if td and os.path.exists(td): _base_dir = td else: - _base_dir = tempfile.mkdtemp(prefix='%s_%s_tmp_'%(__appname__, - __version__), dir=os.environ.get('CALIBRE_TEMP_DIR', None)) + base = os.environ.get('CALIBRE_TEMP_DIR', None) + prefix = u'%s_%s_tmp_'%(__appname__, __version__) + try: + # First try an ascii path as that is what was done historically + # and we dont want to break working code + # _base_dir will be a bytestring + _base_dir = tempfile.mkdtemp(prefix=prefix.encode('ascii'), dir=base) + except: + # Failed to create tempdir (probably localized windows) + # Try unicode. This means that all temp paths created by this + # module will be unicode, this may cause problems elsewhere, if + # so, hopefully people will open tickets and they can be fixed. + _base_dir = tempfile.mkdtemp(prefix=prefix, dir=base) + atexit.register(remove_dir, _base_dir) return _base_dir From c3a2c3f458304abaef826e08f61265a235df5d50 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 21 May 2011 19:24:04 +0100 Subject: [PATCH 04/31] Add option to color custom enumeration values in the library view --- src/calibre/gui2/library/delegates.py | 25 ++++++++++++++++ .../gui2/preferences/create_custom_column.py | 21 ++++++++++++-- .../gui2/preferences/create_custom_column.ui | 29 ++++++++++++++++--- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index e2234f6df5..ae01081736 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -274,6 +274,31 @@ class CcEnumDelegate(QStyledItemDelegate): # {{{ Delegate for text/int/float data. ''' + def __init__(self, parent): + QStyledItemDelegate.__init__(self, parent) + self.document = QTextDocument() + + def paint(self, painter, option, index): + style = self.parent().style() + txt = unicode(index.data(Qt.DisplayRole).toString()) + self.document.setPlainText(txt) + painter.save() + if hasattr(QStyle, 'CE_ItemViewItem'): + style.drawControl(QStyle.CE_ItemViewItem, option, + painter, self.parent()) + elif option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + m = index.model() + col = m.column_map[index.column()] + colors = m.custom_columns[col]['display'].get('enum_colors', []) + values = m.custom_columns[col]['display']['enum_values'] + if len(colors) > 0 and txt in values: + painter.fillRect(option.rect, QColor(colors[values.index(txt)])) + painter.setClipRect(option.rect) + painter.translate(option.rect.topLeft()) + self.document.drawContents(painter) + painter.restore() + def createEditor(self, parent, option, index): m = index.model() col = m.column_map[index.column()] diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 7b891b782c..180c3aed7a 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal ' import re from functools import partial -from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant +from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant, QColor from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn from calibre.gui2 import error_dialog @@ -126,6 +126,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): c['display'].get('make_category', False)) elif ct == 'enumeration': self.enum_box.setText(','.join(c['display'].get('enum_values', []))) + self.enum_colors.setText(','.join(c['display'].get('enum_colors', []))) self.datatype_changed() if ct in ['text', 'composite', 'enumeration']: self.use_decorations.setChecked(c['display'].get('use_decorations', False)) @@ -170,7 +171,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label', 'make_category'): getattr(self, 'composite_'+x).setVisible(col_type in ['composite', '*composite']) - for x in ('box', 'default_label', 'label'): + for x in ('box', 'default_label', 'label', 'colors', 'colors_label'): getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration') self.use_decorations.setVisible(col_type in ['text', 'composite', 'enumeration']) self.is_names.setVisible(col_type == '*text') @@ -247,7 +248,21 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): if l[i] in l[i+1:]: return self.simple_error('', _('The value "{0}" is in the ' 'list more than once').format(l[i])) - display_dict = {'enum_values': l} + c = unicode(self.enum_colors.text()) + if c: + c = [v.strip() for v in unicode(self.enum_colors.text()).split(',')] + else: + c = [] + print c, len(c) + if len(c) != 0 and len(c) != len(l): + return self.simple_error('', _('The colors box must be empty or ' + 'contain the same number of items as the value box')) + for tc in c: + if tc not in QColor.colorNames(): + return self.simple_error('', + _('The color {0} is unknown').format(tc)) + + display_dict = {'enum_values': l, 'enum_colors': c} elif col_type == 'text' and is_multiple: display_dict = {'is_names': self.is_names.isChecked()} diff --git a/src/calibre/gui2/preferences/create_custom_column.ui b/src/calibre/gui2/preferences/create_custom_column.ui index 619b0c6212..2bdadd4b9d 100644 --- a/src/calibre/gui2/preferences/create_custom_column.ui +++ b/src/calibre/gui2/preferences/create_custom_column.ui @@ -304,8 +304,8 @@ Everything else will show nothing. - - + + @@ -320,13 +320,34 @@ four values, the first of them being the empty value. - + The empty string is always the first value - Default: (nothing) + Values + + + + + + + + 0 + 0 + + + + A list of color names to use when displaying an item. The +list must be empty or contain a color for each value. + + + + + + + Colors From 994974fb59afea4e781c99b0019c4d425f4ee714 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 21 May 2011 19:34:23 +0100 Subject: [PATCH 05/31] Add tooltip listing all colors available --- src/calibre/gui2/preferences/create_custom_column.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 180c3aed7a..3a245580dd 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -132,6 +132,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): self.use_decorations.setChecked(c['display'].get('use_decorations', False)) elif ct == '*text': self.is_names.setChecked(c['display'].get('is_names', False)) + + all_colors = [unicode(s) for s in list(QColor.colorNames())] + self.enum_colors_label.setToolTip('

' + ', '.join(all_colors) + '

') self.exec_() def shortcut_activated(self, url): @@ -253,7 +256,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): c = [v.strip() for v in unicode(self.enum_colors.text()).split(',')] else: c = [] - print c, len(c) if len(c) != 0 and len(c) != len(l): return self.simple_error('', _('The colors box must be empty or ' 'contain the same number of items as the value box')) From 6eec4fa410eddb376b62d064549460ee754031bb Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 21 May 2011 19:40:27 +0100 Subject: [PATCH 06/31] More robust coloring code in the CC Enum Delegate --- src/calibre/gui2/library/delegates.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index ae01081736..1af0482a31 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -289,11 +289,14 @@ class CcEnumDelegate(QStyledItemDelegate): # {{{ elif option.state & QStyle.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) m = index.model() - col = m.column_map[index.column()] - colors = m.custom_columns[col]['display'].get('enum_colors', []) - values = m.custom_columns[col]['display']['enum_values'] + cc = m.custom_columns[m.column_map[index.column()]]['display'] + colors = cc.get('enum_colors', []) + values = cc.get('enum_values', []) if len(colors) > 0 and txt in values: - painter.fillRect(option.rect, QColor(colors[values.index(txt)])) + try: + painter.fillRect(option.rect, QColor(colors[values.index(txt)])) + except: + pass painter.setClipRect(option.rect) painter.translate(option.rect.topLeft()) self.document.drawContents(painter) From 9b52e0d4e8dfb9e5782fa48cffba3fc1d9272962 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 12:49:17 -0600 Subject: [PATCH 07/31] Fix #786264 (external IP and Ubuntu's 127.0.1.1 addr) --- src/calibre/library/server/base.py | 2 +- src/calibre/utils/mdns.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index eea28469a9..862e724809 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -218,7 +218,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, cherrypy.engine.start() except: ip = get_external_ip() - if not ip or ip == '127.0.0.1': + if not ip or ip.startswith('127.'): raise cherrypy.log('Trying to bind to single interface: '+ip) cherrypy.config.update({'server.socket_host' : ip}) diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py index b7cc8757d3..2be6bef49b 100644 --- a/src/calibre/utils/mdns.py +++ b/src/calibre/utils/mdns.py @@ -13,16 +13,17 @@ def _get_external_ip(): ipaddr = socket.gethostbyname(socket.gethostname()) except: ipaddr = '127.0.0.1' - if ipaddr == '127.0.0.1': + if ipaddr.startswith('127.'): for addr in ('192.0.2.0', '198.51.100.0', 'google.com'): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect((addr, 0)) ipaddr = s.getsockname()[0] - if ipaddr != '127.0.0.1': - return ipaddr + if not ipaddr.startswith('127.'): + break except: time.sleep(0.3) + #print 'ipaddr: %s' % ipaddr return ipaddr _ext_ip = None From d4f79884a4d931bf59f462a5196032fdf7577a9a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 14:16:49 -0600 Subject: [PATCH 08/31] Fix #786287 (Updated recipe for The Nation magazine) --- recipes/icons/the_nation.png | Bin 0 -> 925 bytes recipes/the_nation.recipe | 44 +++++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 recipes/icons/the_nation.png diff --git a/recipes/icons/the_nation.png b/recipes/icons/the_nation.png new file mode 100644 index 0000000000000000000000000000000000000000..fd6e6ebfb445f3beccce4f5dfb69435c7a43ee3e GIT binary patch literal 925 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl4m>B|mLR{Y*KXGHy)E{r&e7t%4 z|NsB{BcgZs_+M;lJzZQnTTJr);w5WLEfy*$A5G0zuB8jqI|@caU^s=q{_ai7f&Pi` zba4!+nA3aSo9}=E2TQ;f4WEU3{}* Date: Sat, 21 May 2011 14:19:26 -0600 Subject: [PATCH 09/31] Fix #786293 (Updated recipe for Marca website) --- recipes/icons/marca.png | Bin 0 -> 293 bytes recipes/marca.recipe | 46 +++++++++++++++++----------------------- 2 files changed, 19 insertions(+), 27 deletions(-) create mode 100644 recipes/icons/marca.png diff --git a/recipes/icons/marca.png b/recipes/icons/marca.png new file mode 100644 index 0000000000000000000000000000000000000000..e9231176b66532319eae2266c74d9d1c5ac30c1c GIT binary patch literal 293 zcmV+=0owkFP)|1AyZ(#|t z;UK+U Date: Sat, 21 May 2011 14:20:45 -0600 Subject: [PATCH 10/31] Fix #786299 (Updated recipe for El Mundo) --- recipes/elmundo.recipe | 48 ++++++++++++++++++++++++++------------ recipes/icons/elmundo.png | Bin 550 -> 1082 bytes 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/recipes/elmundo.recipe b/recipes/elmundo.recipe index aea60de304..dedd449f93 100644 --- a/recipes/elmundo.recipe +++ b/recipes/elmundo.recipe @@ -1,6 +1,6 @@ __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2009-2011, Darko Miletic ' ''' elmundo.es ''' @@ -10,15 +10,24 @@ from calibre.web.feeds.news import BasicNewsRecipe class ElMundo(BasicNewsRecipe): title = 'El Mundo' __author__ = 'Darko Miletic' - description = 'News from Spain' - publisher = 'El Mundo' + description = 'Lider de informacion en espaniol' + publisher = 'Unidad Editorial Informacion General S.L.U.' category = 'news, politics, Spain' oldest_article = 2 max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False encoding = 'iso8859_15' - language = 'es' + language = 'es_ES' + masthead_url = 'http://estaticos03.elmundo.es/elmundo/iconos/v4.x/v4.01/bg_h1.png' + publication_type = 'newspaper' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif} + .metadata_noticia{font-size: small} + h1,h2,h3,h4,h5,h6,.subtitulo {color: #3F5974} + .hora{color: red} + .update{color: gray} + """ conversion_options = { 'comments' : description @@ -30,22 +39,31 @@ class ElMundo(BasicNewsRecipe): keep_only_tags = [dict(name='div', attrs={'class':'noticia'})] remove_tags_before = dict(attrs={'class':['titular','antetitulo'] }) remove_tags_after = dict(name='div' , attrs={'id':['desarrollo_noticia','tamano']}) - + remove_attributes = ['lang','border'] remove_tags = [ dict(name='div', attrs={'class':['herramientas','publicidad_google']}) ,dict(name='div', attrs={'id':'modulo_multimedia' }) ,dict(name='ul', attrs={'class':'herramientas' }) - ,dict(name=['object','link']) + ,dict(name=['object','link','embed','iframe','base','meta']) ] feeds = [ - (u'Portada' , u'http://rss.elmundo.es/rss/descarga.htm?data2=4' ) - ,(u'Deportes' , u'http://rss.elmundo.es/rss/descarga.htm?data2=14') - ,(u'Economia' , u'http://rss.elmundo.es/rss/descarga.htm?data2=7' ) - ,(u'Espana' , u'http://rss.elmundo.es/rss/descarga.htm?data2=8' ) - ,(u'Internacional' , u'http://rss.elmundo.es/rss/descarga.htm?data2=9' ) - ,(u'Cultura' , u'http://rss.elmundo.es/rss/descarga.htm?data2=6' ) - ,(u'Ciencia/Ecologia', u'http://rss.elmundo.es/rss/descarga.htm?data2=5' ) - ,(u'Comunicacion' , u'http://rss.elmundo.es/rss/descarga.htm?data2=26') - ,(u'Television' , u'http://rss.elmundo.es/rss/descarga.htm?data2=76') + (u'Portada' , u'http://estaticos.elmundo.es/elmundo/rss/portada.xml' ) + ,(u'Deportes' , u'http://estaticos.elmundo.es/elmundodeporte/rss/portada.xml') + ,(u'Economia' , u'http://estaticos.elmundo.es/elmundo/rss/economia.xml' ) + ,(u'Espana' , u'http://estaticos.elmundo.es/elmundo/rss/espana.xml' ) + ,(u'Internacional' , u'http://estaticos.elmundo.es/elmundo/rss/internacional.xml' ) + ,(u'Cultura' , u'http://estaticos.elmundo.es/elmundo/rss/cultura.xml' ) + ,(u'Ciencia/Ecologia', u'http://estaticos.elmundo.es/elmundo/rss/ciencia.xml' ) + ,(u'Comunicacion' , u'http://estaticos.elmundo.es/elmundo/rss/comunicacion.xml' ) + ,(u'Television' , u'http://estaticos.elmundo.es/elmundo/rss/television.xml' ) ] + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + return soup + + def get_article_url(self, article): + return article.get('guid', None) + diff --git a/recipes/icons/elmundo.png b/recipes/icons/elmundo.png index 754b3d0e154df866a55866b5b4174ad3e1ea1630..24467eac80e2881abcb243aa7cf797fd41050c6a 100644 GIT binary patch literal 1082 zcmdr~{ZkSK0HxHmNU!b2cAe!fJ1@1Zwf(TVwKQx{DxI>_kZj08@UuCYD5i4e+3oCu zlrbpzNaZs>5~mWOTuV@p_>%aXVu^qPf}W9m!?A^Wh?)~`gy%u=pg&1Hg5C8zg zq^ChHh5e&lei#wf#N!9E0e}bq{9+C?96=es23FUaVqp~)8$?Wz1xD+b$wlY@0Z5AMBaqOWVTrC?F`nCc(}P=PocEFo_9K3 z^Arjd+4h&)z0lhyjLW?tS3F>Qg0TSUt~V zw$=(J<#PFAFj!k#dq<>Au3~63njg-A5xJLT2CG0I*xcCmxZGr!71R2Z(mT@E|D=LG zAQFj`N~PZJTwYoI{tQ_CQj2H5km&3NgMl(^2BluhZ63-3sv5dFiE2TV?XP%4sM!aE*bjf8J+{d{i-lG1ye;l%T(g+|gq9pV z9K8ySrHl7QQNMlaHYa`sH-1LL5PsYTRFp^09b5N(%E~UtJ!**pr4{H7UFn!A7$ABg z*&Zl(Pm3CiOORm)#9y4Yt_T2862jF%(B%VB7t0RNP8CG}LEHG81SCY0haHNqERBc% zjqe-g-Y&1(%-}~>9!%)~+eV)?;6jyrW*p;v=}q?J)$WnZG76yX{Hw7^0^lJ-=sKo94f9k^KPZG_gHK+fdwdVh$ev?7MqUx!j^S73@Jl@ye z|L~v2$O8oZakVKLGd49RevI;Qp7HPBZ~q=ip7r)M$r|(aO-}r~tyV*7hQ*Bti5n3! zC2vGVay&AZ-)C5|*5XjXixY)A6nkcuz4`zD`F(pfH?~88|K~rnKm6XlUh>f2I~{)N z5eFwGs|)B|;9mXr|HIGxY;JCkc>jL>F5kSpRqODYV6qdUr9emXsF8NKy z;?w-j-l;2Q9AS8G|3AT^_OWE)O9pX^^52romw_TD%pTloHs{fqbjXG$XU!QN9~BlQ zW@hHY`~Ux!oH0{ElHu^D;w38SFB*X1tXkq4QIcGgnpl#mn*t;lj0_Acbq$PwD8$gx z%Fx2fz*yVBz{ Date: Sat, 21 May 2011 14:27:25 -0600 Subject: [PATCH 11/31] ... --- setup/installer/windows/wix-template.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/installer/windows/wix-template.xml b/setup/installer/windows/wix-template.xml index 5de08e155f..9835ae8f6b 100644 --- a/setup/installer/windows/wix-template.xml +++ b/setup/installer/windows/wix-template.xml @@ -115,7 +115,7 @@ + Message="This application is only supported on Windows XP SP3, or higher."> = 501)]]> From fb26c223a7346ea215b160c1df8b373a40b47a4a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 16:29:57 -0600 Subject: [PATCH 12/31] Fix #786290 (Enhancements for building on NetBSD) --- setup/__init__.py | 4 +++- setup/extensions.py | 6 +++--- setup/install.py | 8 ++++---- src/calibre/__init__.py | 10 +++++----- src/calibre/constants.py | 4 +++- src/calibre/devices/libusb.py | 4 ++-- src/calibre/ebooks/html/input.py | 4 ++-- src/calibre/ebooks/pdf/pdftohtml.py | 6 +++--- src/calibre/gui2/__init__.py | 6 +++--- src/calibre/gui2/lrf_renderer/main.py | 6 +++--- src/calibre/gui2/viewer/main.py | 4 ++-- src/calibre/linux.py | 17 ++++++++++------- src/calibre/utils/fonts/__init__.py | 8 ++++---- src/calibre/utils/help2man.py | 4 ++-- src/calibre/utils/network.py | 4 ++-- 15 files changed, 51 insertions(+), 44 deletions(-) diff --git a/setup/__init__.py b/setup/__init__.py index 61bafd2282..4a8d9870be 100644 --- a/setup/__init__.py +++ b/setup/__init__.py @@ -12,7 +12,9 @@ is64bit = platform.architecture()[0] == '64bit' iswindows = re.search('win(32|64)', sys.platform) isosx = 'darwin' in sys.platform isfreebsd = 'freebsd' in sys.platform -islinux = not isosx and not iswindows and not isfreebsd +isnetbsd = 'netbsd' in sys.platform +isbsd = isnetbsd or isfreebsd +islinux = not isosx and not iswindows and not isbsd SRC = os.path.abspath('src') sys.path.insert(0, SRC) sys.resources_location = os.path.join(os.path.dirname(SRC), 'resources') diff --git a/setup/extensions.py b/setup/extensions.py index 6e8e7ce4b7..678859432d 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -11,7 +11,7 @@ from distutils import sysconfig from PyQt4.pyqtconfig import QtGuiModuleMakefile -from setup import Command, islinux, isfreebsd, isosx, SRC, iswindows +from setup import Command, islinux, isfreebsd, isbsd, isosx, SRC, iswindows from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \ fc_error, poppler_libs, poppler_lib_dirs, poppler_inc_dirs, podofo_inc, \ podofo_lib, podofo_error, poppler_error, pyqt, OSX_SDK, NMAKE, \ @@ -21,7 +21,7 @@ from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \ jpg_lib_dirs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs, \ icu_lib_dirs MT -isunix = islinux or isosx or isfreebsd +isunix = islinux or isosx or isbsd make = 'make' if isunix else NMAKE @@ -205,7 +205,7 @@ if islinux: ldflags.append('-lpython'+sysconfig.get_python_version()) -if isfreebsd: +if isbsd: cflags.append('-pthread') ldflags.append('-shared') cflags.append('-I'+sysconfig.get_python_inc()) diff --git a/setup/install.py b/setup/install.py index e1a1190ff9..7290dbd799 100644 --- a/setup/install.py +++ b/setup/install.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import sys, os, textwrap, subprocess, shutil, tempfile, atexit, stat, shlex -from setup import Command, islinux, isfreebsd, basenames, modules, functions, \ +from setup import Command, islinux, isfreebsd, isbsd, basenames, modules, functions, \ __appname__, __version__ HEADER = '''\ @@ -116,7 +116,7 @@ class Develop(Command): def pre_sub_commands(self, opts): - if not (islinux or isfreebsd): + if not (islinux or isbsd): self.info('\nSetting up a source based development environment is only ' 'supported on linux. On other platforms, see the User Manual' ' for help with setting up a development environment.') @@ -156,7 +156,7 @@ class Develop(Command): self.warn('Failed to compile mount helper. Auto mounting of', ' devices will not work') - if not isfreebsd and os.geteuid() != 0: + if not isbsd and os.geteuid() != 0: return self.warn('Must be run as root to compile mount helper. Auto ' 'mounting of devices will not work.') src = os.path.join(self.SRC, 'calibre', 'devices', 'linux_mount_helper.c') @@ -168,7 +168,7 @@ class Develop(Command): ret = p.wait() if ret != 0: return warn() - if not isfreebsd: + if not isbsd: os.chown(dest, 0, 0) os.chmod(dest, stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|\ stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index b82ea984ec..3a35feb66f 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -12,10 +12,10 @@ from functools import partial warnings.simplefilter('ignore', DeprecationWarning) -from calibre.constants import (iswindows, isosx, islinux, isfreebsd, isfrozen, - preferred_encoding, __appname__, __version__, __author__, - win32event, win32api, winerror, fcntl, - filesystem_encoding, plugins, config_dir) +from calibre.constants import (iswindows, isosx, islinux, isfrozen, + isbsd, preferred_encoding, __appname__, __version__, __author__, + win32event, win32api, winerror, fcntl, + filesystem_encoding, plugins, config_dir) from calibre.startup import winutil, winutilerror if False and islinux and not getattr(sys, 'frozen', False): @@ -31,7 +31,7 @@ if False: # Prevent pyflakes from complaining winutil, winutilerror, __appname__, islinux, __version__ fcntl, win32event, isfrozen, __author__ - winerror, win32api, isfreebsd + winerror, win32api, isbsd _mt_inited = False def _init_mimetypes(): diff --git a/src/calibre/constants.py b/src/calibre/constants.py index b22739cbb2..ef46b0bef2 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -27,7 +27,9 @@ iswindows = 'win32' in _plat or 'win64' in _plat isosx = 'darwin' in _plat isnewosx = isosx and getattr(sys, 'new_app_bundle', False) isfreebsd = 'freebsd' in _plat -islinux = not(iswindows or isosx or isfreebsd) +isnetbsd = 'netbsd' in _plat +isbsd = isfreebsd or isnetbsd +islinux = not(iswindows or isosx or isbsd) isfrozen = hasattr(sys, 'frozen') isunix = isosx or islinux diff --git a/src/calibre/devices/libusb.py b/src/calibre/devices/libusb.py index 2d4911c799..016a6b18aa 100644 --- a/src/calibre/devices/libusb.py +++ b/src/calibre/devices/libusb.py @@ -8,10 +8,10 @@ from ctypes import cdll, POINTER, byref, pointer, Structure as _Structure, \ c_ubyte, c_ushort, c_int, c_char, c_void_p, c_byte, c_uint from errno import EBUSY, ENOMEM -from calibre import iswindows, isosx, isfreebsd, load_library +from calibre import iswindows, isosx, isbsd, load_library _libusb_name = 'libusb' -PATH_MAX = 511 if iswindows else 1024 if (isosx or isfreebsd) else 4096 +PATH_MAX = 511 if iswindows else 1024 if (isosx or isbsd) else 4096 if iswindows: class Structure(_Structure): _pack_ = 1 diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index b22f7d2791..3d5f6c00ef 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -20,7 +20,7 @@ from itertools import izip from calibre.customize.conversion import InputFormatPlugin from calibre.ebooks.chardet import xml_to_unicode from calibre.customize.conversion import OptionRecommendation -from calibre.constants import islinux, isfreebsd, iswindows +from calibre.constants import islinux, isbsd, iswindows from calibre import unicode_path, as_unicode from calibre.utils.localization import get_lang from calibre.utils.filenames import ascii_filename @@ -302,7 +302,7 @@ class HTMLInput(InputFormatPlugin): if getattr(self, '_is_case_sensitive', None) is not None: return self._is_case_sensitive if not path or not os.path.exists(path): - return islinux or isfreebsd + return islinux or isbsd self._is_case_sensitive = not (os.path.exists(path.lower()) \ and os.path.exists(path.upper())) return self._is_case_sensitive diff --git a/src/calibre/ebooks/pdf/pdftohtml.py b/src/calibre/ebooks/pdf/pdftohtml.py index 4aa0953738..a791dab48a 100644 --- a/src/calibre/ebooks/pdf/pdftohtml.py +++ b/src/calibre/ebooks/pdf/pdftohtml.py @@ -13,7 +13,7 @@ from functools import partial from calibre.ebooks import ConversionError, DRMError from calibre.ptempfile import PersistentTemporaryFile -from calibre.constants import isosx, iswindows, islinux, isfreebsd +from calibre.constants import isosx, iswindows, islinux, isbsd from calibre import CurrentDir PDFTOHTML = 'pdftohtml' @@ -23,7 +23,7 @@ if isosx and hasattr(sys, 'frameworks_dir'): if iswindows and hasattr(sys, 'frozen'): PDFTOHTML = os.path.join(os.path.dirname(sys.executable), 'pdftohtml.exe') popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up -if (islinux or isfreebsd) and getattr(sys, 'frozen', False): +if (islinux or isbsd) and getattr(sys, 'frozen', False): PDFTOHTML = os.path.join(sys.executables_location, 'bin', 'pdftohtml') def pdftohtml(output_dir, pdf_path, no_images): @@ -43,7 +43,7 @@ def pdftohtml(output_dir, pdf_path, no_images): # This is neccessary as pdftohtml doesn't always (linux) respect absolute paths pdf_path = os.path.abspath(pdf_path) cmd = [PDFTOHTML, '-enc', 'UTF-8', '-noframes', '-p', '-nomerge', '-nodrm', '-q', pdf_path, os.path.basename(index)] - if isfreebsd: + if isbsd: cmd.remove('-nodrm') if no_images: cmd.append('-i') diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 8007b6cd19..2cb18f3bda 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -12,7 +12,7 @@ from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' -from calibre.constants import islinux, iswindows, isfreebsd, isfrozen, isosx +from calibre.constants import islinux, iswindows, isbsd, isfrozen, isosx from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig from calibre.utils.localization import set_qt_translator from calibre.ebooks.metadata import MetaInformation @@ -628,7 +628,7 @@ class Application(QApplication): st = self.style() if st is not None: st = unicode(st.objectName()).lower() - if (islinux or isfreebsd) and st in ('windows', 'motif', 'cde'): + if (islinux or isbsd) and st in ('windows', 'motif', 'cde'): from PyQt4.Qt import QStyleFactory styles = set(map(unicode, QStyleFactory.keys())) if 'Plastique' in styles and os.environ.get('KDE_FULL_SESSION', @@ -697,7 +697,7 @@ def open_local_file(path): def is_ok_to_use_qt(): global gui_thread, _store_app - if (islinux or isfreebsd) and ':' not in os.environ.get('DISPLAY', ''): + if (islinux or isbsd) and ':' not in os.environ.get('DISPLAY', ''): return False if _store_app is None and QApplication.instance() is None: _store_app = QApplication([]) diff --git a/src/calibre/gui2/lrf_renderer/main.py b/src/calibre/gui2/lrf_renderer/main.py index e68e04adcf..0575d106f4 100644 --- a/src/calibre/gui2/lrf_renderer/main.py +++ b/src/calibre/gui2/lrf_renderer/main.py @@ -5,7 +5,7 @@ import sys, logging, os, traceback, time from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread -from calibre import __appname__, setup_cli_handlers, islinux, isfreebsd +from calibre import __appname__, setup_cli_handlers, islinux, isbsd from calibre.ebooks.lrf.lrfparser import LRFDocument from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, \ @@ -258,7 +258,7 @@ def file_renderer(stream, opts, parent=None, logger=None): level = logging.DEBUG if opts.verbose else logging.INFO logger = logging.getLogger('lrfviewer') setup_cli_handlers(logger, level) - if islinux or isfreebsd: + if islinux or isbsd: try: # Set lrfviewer as the default for LRF files for this user from subprocess import call call('xdg-mime default calibre-lrfviewer.desktop application/lrf', shell=True) @@ -307,7 +307,7 @@ def main(args=sys.argv, logger=None): if hasattr(opts, 'help'): parser.print_help() return 1 - pid = os.fork() if (islinux or isfreebsd) else -1 + pid = os.fork() if (islinux or isbsd) else -1 if pid <= 0: app = Application(args) app.setWindowIcon(QIcon(I('viewer.png'))) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 303d73dc11..e25d59c5ad 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -20,7 +20,7 @@ from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \ info_dialog, error_dialog, open_url, available_height from calibre.ebooks.oeb.iterator import EbookIterator from calibre.ebooks import DRMError -from calibre.constants import islinux, isfreebsd, isosx, filesystem_encoding +from calibre.constants import islinux, isbsd, isosx, filesystem_encoding from calibre.utils.config import Config, StringConfig, JSONConfig from calibre.gui2.search_box import SearchBox2 from calibre.ebooks.metadata import MetaInformation @@ -801,7 +801,7 @@ def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) - pid = os.fork() if False and (islinux or isfreebsd) else -1 + pid = os.fork() if False and (islinux or isbsd) else -1 if pid <= 0: app = Application(args) app.setWindowIcon(QIcon(I('viewer.png'))) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index d83bba061f..1e7a62b869 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -7,7 +7,7 @@ import sys, os, cPickle, textwrap, stat, importlib from subprocess import check_call from calibre import __appname__, prints, guess_type -from calibre.constants import islinux, isfreebsd +from calibre.constants import islinux, isnetbsd, isbsd from calibre.customize.ui import all_input_formats from calibre.ptempfile import TemporaryDirectory from calibre import CurrentDir @@ -136,17 +136,17 @@ class PostInstall: self.icon_resources = [] self.menu_resources = [] self.mime_resources = [] - if islinux or isfreebsd: + if islinux or isbsd: self.setup_completion() self.install_man_pages() - if islinux or isfreebsd: + if islinux or isbsd: self.setup_desktop_integration() self.create_uninstaller() from calibre.utils.config import config_dir if os.path.exists(config_dir): os.chdir(config_dir) - if islinux or isfreebsd: + if islinux or isbsd: for f in os.listdir('.'): if os.stat(f).st_uid == 0: import shutil @@ -196,7 +196,10 @@ class PostInstall: if os.path.exists(bc): f = os.path.join(bc, 'calibre') else: - f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre') + if isnetbsd: + f = os.path.join(self.opts.staging_root, 'share/bash_completion.d/calibre') + else: + f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre') if not os.path.exists(os.path.dirname(f)): os.makedirs(os.path.dirname(f)) self.manifest.append(f) @@ -300,7 +303,7 @@ class PostInstall: def install_man_pages(self): # {{{ try: from calibre.utils.help2man import create_man_page - if isfreebsd: + if isbsd: manpath = os.path.join(self.opts.staging_root, 'man/man1') else: manpath = os.path.join(self.opts.staging_sharedir, 'man/man1') @@ -316,7 +319,7 @@ class PostInstall: continue parser = parser() raw = create_man_page(prog, parser) - if isfreebsd: + if isbsd: manfile = os.path.join(manpath, prog+'.1') else: manfile = os.path.join(manpath, prog+'.1'+__appname__+'.bz2') diff --git a/src/calibre/utils/fonts/__init__.py b/src/calibre/utils/fonts/__init__.py index 3db3a1b285..7b4f0abea4 100644 --- a/src/calibre/utils/fonts/__init__.py +++ b/src/calibre/utils/fonts/__init__.py @@ -8,14 +8,14 @@ __docformat__ = 'restructuredtext en' import os, sys -from calibre.constants import plugins, iswindows, islinux, isfreebsd +from calibre.constants import plugins, iswindows, islinux, isbsd _fc, _fc_err = plugins['fontconfig'] if _fc is None: raise RuntimeError('Failed to load fontconfig with error:'+_fc_err) -if islinux or isfreebsd: +if islinux or isbsd: Thread = object else: from threading import Thread @@ -49,7 +49,7 @@ class FontConfig(Thread): self.failed = True def wait(self): - if not (islinux or isfreebsd): + if not (islinux or isbsd): self.join() if self.failed: raise RuntimeError('Failed to initialize fontconfig') @@ -149,7 +149,7 @@ class FontConfig(Thread): return fonts if all else (fonts[0] if fonts else None) fontconfig = FontConfig() -if islinux or isfreebsd: +if islinux or isbsd: # On X11 Qt also uses fontconfig, so initialization must happen in the # main thread. In any case on X11 initializing fontconfig should be very # fast diff --git a/src/calibre/utils/help2man.py b/src/calibre/utils/help2man.py index 7acf3e0c21..00989a168d 100644 --- a/src/calibre/utils/help2man.py +++ b/src/calibre/utils/help2man.py @@ -4,7 +4,7 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import time, bz2 -from calibre.constants import isfreebsd +from calibre.constants import isbsd from calibre.constants import __version__, __appname__, __author__ @@ -58,7 +58,7 @@ def create_man_page(prog, parser): lines = [x if isinstance(x, unicode) else unicode(x, 'utf-8', 'replace') for x in lines] - if not isfreebsd: + if not isbsd: return bz2.compress((u'\n'.join(lines)).encode('utf-8')) else: return (u'\n'.join(lines)).encode('utf-8') diff --git a/src/calibre/utils/network.py b/src/calibre/utils/network.py index 7e840207cf..c47eacbe3f 100644 --- a/src/calibre/utils/network.py +++ b/src/calibre/utils/network.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from calibre.constants import iswindows, islinux, isfreebsd +from calibre.constants import iswindows, islinux, isbsd class LinuxNetworkStatus(object): @@ -47,7 +47,7 @@ class DummyNetworkStatus(object): return True _network_status = WindowsNetworkStatus() if iswindows else \ - LinuxNetworkStatus() if (islinux or isfreebsd) else \ + LinuxNetworkStatus() if (islinux or isbsd) else \ DummyNetworkStatus() def internet_connected(): From a6ece012ea51b4a5a07522e6823f909ac61f51f0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 21 May 2011 18:02:37 -0600 Subject: [PATCH 13/31] Windows installer: Remember and use previous installation folder when upgrading --- setup/installer/windows/wix-template.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup/installer/windows/wix-template.xml b/setup/installer/windows/wix-template.xml index 9835ae8f6b..8526c27720 100644 --- a/setup/installer/windows/wix-template.xml +++ b/setup/installer/windows/wix-template.xml @@ -26,6 +26,11 @@ + + + + @@ -43,6 +48,9 @@ + + + @@ -87,7 +95,8 @@ ConfigurableDirectory="APPLICATIONFOLDER"> + Description="All the files needed to run {app}" Absent="disallow"> + From c3688278d0ac265fa4d53a084ca1b855300c9dcc Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 13:00:30 +0100 Subject: [PATCH 14/31] First cut at template-based column coloring --- .../gui2/dialogs/template_line_editor.py | 31 +++++++ src/calibre/gui2/init.py | 1 + src/calibre/gui2/library/delegates.py | 79 +++++++----------- src/calibre/gui2/library/models.py | 36 +++++++- src/calibre/gui2/preferences/look_feel.py | 8 ++ src/calibre/gui2/preferences/look_feel.ui | 83 +++++++++++++++++++ src/calibre/library/database2.py | 4 + 7 files changed, 191 insertions(+), 51 deletions(-) create mode 100644 src/calibre/gui2/dialogs/template_line_editor.py diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py new file mode 100644 index 0000000000..d7ba8e4900 --- /dev/null +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (SIGNAL, QLineEdit) +from calibre.gui2.dialogs.template_dialog import TemplateDialog + +class LineEditWithTextBox(QLineEdit): + + ''' + Extend the context menu of a QLineEdit to include more actions. + ''' + + def contextMenuEvent(self, event): + menu = self.createStandardContextMenu() + menu.addSeparator() + + action_open_editor = menu.addAction(_('Open Editor')) + + self.connect(action_open_editor, SIGNAL('triggered()'), self.open_editor) + menu.exec_(event.globalPos()) + + def open_editor(self): + t = TemplateDialog(self, self.text()) + if t.exec_(): + self.setText(t.textbox.toPlainText()) + + diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index a75ff01b21..079e1814c3 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -65,6 +65,7 @@ class LibraryViewMixin(object): # {{{ self.build_context_menus() self.library_view.model().set_highlight_only(config['highlight_search_matches']) + self.library_view.model().set_color_templates() def build_context_menus(self): lm = QMenu(self) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 1af0482a31..042e568d8b 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -7,11 +7,12 @@ __docformat__ = 'restructuredtext en' from math import cos, sin, pi -from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \ - QPainterPath, QLinearGradient, QBrush, \ +from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, QPalette, \ + QPainterPath, QLinearGradient, QBrush, QApplication, \ QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ QIcon, QDoubleSpinBox, QVariant, QSpinBox, \ - QStyledItemDelegate, QComboBox, QTextDocument + QStyledItemDelegate, QComboBox, QTextDocument, \ + QAbstractTextDocumentLayout from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2.widgets import EnLineEdit @@ -51,9 +52,7 @@ class RatingDelegate(QStyledItemDelegate): # {{{ return QSize(5*(self.SIZE), self.SIZE+4) def paint(self, painter, option, index): - style = self._parent.style() - option = QStyleOptionViewItemV4(option) - self.initStyleOption(option, self.dummy) + self.initStyleOption(option, index) num = index.model().data(index, Qt.DisplayRole).toInt()[0] def draw_star(): painter.save() @@ -65,18 +64,24 @@ class RatingDelegate(QStyledItemDelegate): # {{{ painter.restore() painter.save() - if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self._parent) - elif option.state & QStyle.State_Selected: + if option.state & QStyle.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) + else: + painter.fillRect(option.rect, option.backgroundBrush) + try: painter.setRenderHint(QPainter.Antialiasing) painter.setClipRect(option.rect) y = option.rect.center().y()-self.SIZE/2. x = option.rect.left() - painter.setPen(self.PEN) - painter.setBrush(self.brush) + brush = index.model().data(index, role=Qt.ForegroundRole) + if brush is None: + pen = self.PEN + painter.setBrush(self.COLOR) + else: + pen = QPen(brush, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) + painter.setBrush(brush) + painter.setPen(pen) painter.translate(x, y) i = 0 while i < num: @@ -274,34 +279,6 @@ class CcEnumDelegate(QStyledItemDelegate): # {{{ Delegate for text/int/float data. ''' - def __init__(self, parent): - QStyledItemDelegate.__init__(self, parent) - self.document = QTextDocument() - - def paint(self, painter, option, index): - style = self.parent().style() - txt = unicode(index.data(Qt.DisplayRole).toString()) - self.document.setPlainText(txt) - painter.save() - if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self.parent()) - elif option.state & QStyle.State_Selected: - painter.fillRect(option.rect, option.palette.highlight()) - m = index.model() - cc = m.custom_columns[m.column_map[index.column()]]['display'] - colors = cc.get('enum_colors', []) - values = cc.get('enum_values', []) - if len(colors) > 0 and txt in values: - try: - painter.fillRect(option.rect, QColor(colors[values.index(txt)])) - except: - pass - painter.setClipRect(option.rect) - painter.translate(option.rect.topLeft()) - self.document.drawContents(painter) - painter.restore() - def createEditor(self, parent, option, index): m = index.model() col = m.column_map[index.column()] @@ -339,17 +316,19 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{ self.document = QTextDocument() def paint(self, painter, option, index): - style = self.parent().style() - self.document.setHtml(index.data(Qt.DisplayRole).toString()) + self.initStyleOption(option, index) + style = QApplication.style() if option.widget is None \ + else option.widget.style() + self.document.setHtml(option.text) + option.text = "" + style.drawControl(QStyle.CE_ItemViewItem, option, painter); + ctx = QAbstractTextDocumentLayout.PaintContext() + ctx.palette = option.palette #.setColor(QPalette.Text, QColor("red")); + textRect = style.subElementRect(QStyle.SE_ItemViewItemText, option) painter.save() - if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self.parent()) - elif option.state & QStyle.State_Selected: - painter.fillRect(option.rect, option.palette.highlight()) - painter.setClipRect(option.rect) - painter.translate(option.rect.topLeft()) - self.document.drawContents(painter) + painter.translate(textRect.topLeft()) + painter.setClipRect(textRect.translated(-textRect.topLeft())) + self.document.documentLayout().draw(painter, ctx) painter.restore() def createEditor(self, parent, option, index): diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index fc1117167d..7d6cfadacb 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -14,6 +14,7 @@ from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, from calibre.gui2 import NONE, UNDEFINED_QDATE from calibre.utils.pyparsing import ParseException from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors +from calibre.ebooks.metadata.book.base import composite_formatter from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import tweaks, prefs from calibre.utils.date import dt_factory, qt_to_dt @@ -96,6 +97,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.ids_to_highlight_set = set() self.current_highlighted_idx = None self.highlight_only = False + self.column_color_map = {} self.read_config() def change_alignment(self, colname, alignment): @@ -532,6 +534,16 @@ class BooksModel(QAbstractTableModel): # {{{ img = self.default_image return img + def set_color_templates(self): + print 'here' + self.column_color_map = {} + for i in range(1,self.db.column_color_count+1): + name = self.db.prefs.get('column_color_name_'+str(i)) + if name: + print name, self.db.prefs.get('column_color_template_'+str(i)) + self.column_color_map[name] = \ + self.db.prefs.get('column_color_template_'+str(i)) + self.refresh() def build_data_convertors(self): def authors(r, idx=-1): @@ -693,9 +705,31 @@ class BooksModel(QAbstractTableModel): # {{{ return NONE if role in (Qt.DisplayRole, Qt.EditRole): return self.column_to_dc_map[col](index.row()) - elif role == Qt.BackgroundColorRole: + elif role == Qt.BackgroundRole: if self.id(index) in self.ids_to_highlight_set: return QColor('lightgreen') + elif role == Qt.ForegroundRole: + key = self.column_map[col] + if key in self.column_color_map: + mi = self.db.get_metadata(self.id(index), index_is_id=True) + fmt = self.column_color_map[key] + try: + color = composite_formatter.safe_format(fmt, mi, '', mi) + return QColor(color) + except: + return None + elif self.is_custom_column(key) and \ + self.custom_columns[key]['datatype'] == 'enumeration': + cc = self.custom_columns[self.column_map[col]]['display'] + colors = cc.get('enum_colors', []) + values = cc.get('enum_values', []) + txt = unicode(index.data(Qt.DisplayRole).toString()) + if len(colors) > 0 and txt in values: + try: + return QColor(colors[values.index(txt)]) + except: + pass + return None elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: return self.column_to_dc_decorator_map[index.column()](index.row()) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index ee2d7a5428..fc6990fcc9 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -159,6 +159,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.df_up_button.clicked.connect(self.move_df_up) self.df_down_button.clicked.connect(self.move_df_down) + choices = db.field_metadata.displayable_field_keys() + choices.sort(key=sort_key) + choices.insert(0, '') + for i in range(1, db.column_color_count+1): + r('column_color_name_'+str(i), db.prefs, choices=choices) + r('column_color_template_'+str(i), db.prefs) + def initialize(self): ConfigWidgetBase.initialize(self) font = gprefs['font'] @@ -238,6 +245,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): return rr def refresh_gui(self, gui): + gui.library_view.model().set_color_templates() self.update_font_display() gui.tags_view.reread_collapse_parameters() gui.library_view.refresh_book_details() diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 244b811cbd..d7fca70c08 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -407,6 +407,84 @@ then the tags will be displayed each on their own line.
+ + + + :/images/cover_flow.png:/images/cover_flow.png + + + Column Coloring + + + + + + Column name + + + + + + + Selection template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 690 + 283 + + + + + +
@@ -417,6 +495,11 @@ then the tags will be displayed each on their own line. QLineEdit
calibre/gui2/complete.h
+ + LineEditWithTextBox + QLineEdit +
calibre/gui2/dialogs/template_line_editor.h
+
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 9a740a08b7..819ac2cd24 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -211,6 +211,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): defs = self.prefs.defaults defs['gui_restriction'] = defs['cs_restriction'] = '' defs['categories_using_hierarchy'] = [] + self.column_color_count = 5 + for i in range(1,self.column_color_count+1): + defs['column_color_name_'+str(i)] = '' + defs['column_color_template_'+str(i)] = '' # Migrate the bool tristate tweak defs['bools_are_tristate'] = \ From 3c92c4a988eca819c813baf2f306cc0cfbff69c9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 14:14:23 +0100 Subject: [PATCH 15/31] More work on coloring columns. Refactor the template editor, add some documentation for the new template function first_non_empty, add help text to the configuration dialog. --- .../gui2/dialogs/template_line_editor.py | 2 +- src/calibre/gui2/library/models.py | 2 - src/calibre/gui2/preferences/look_feel.py | 26 +++++++- src/calibre/gui2/preferences/look_feel.ui | 66 +++++++++++-------- src/calibre/gui2/preferences/plugboard.py | 26 +------- src/calibre/manual/template_lang.rst | 1 + src/calibre/utils/formatter_functions.py | 19 +++++- 7 files changed, 85 insertions(+), 57 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py index d7ba8e4900..69999f59a0 100644 --- a/src/calibre/gui2/dialogs/template_line_editor.py +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import (SIGNAL, QLineEdit) from calibre.gui2.dialogs.template_dialog import TemplateDialog -class LineEditWithTextBox(QLineEdit): +class TemplateLineEditor(QLineEdit): ''' Extend the context menu of a QLineEdit to include more actions. diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 7d6cfadacb..83bf5868ba 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -535,12 +535,10 @@ class BooksModel(QAbstractTableModel): # {{{ return img def set_color_templates(self): - print 'here' self.column_color_map = {} for i in range(1,self.db.column_color_count+1): name = self.db.prefs.get('column_color_name_'+str(i)) if name: - print name, self.db.prefs.get('column_color_template_'+str(i)) self.column_color_map[name] = \ self.db.prefs.get('column_color_template_'+str(i)) self.refresh() diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index fc6990fcc9..97400c45bd 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog, - QAbstractListModel, Qt) + QAbstractListModel, Qt, QColor) from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form @@ -159,12 +159,36 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.df_up_button.clicked.connect(self.move_df_up) self.df_down_button.clicked.connect(self.move_df_down) + self.color_help_text.setWordWrap(True) + self.color_help_text.setText('

' + + _('Here you can specify coloring rules for fields shown in the ' + 'library view. Choose the field you wish to color, then ' + 'supply a template that specifies the color to use.') + + '

' + + _('The template must evaluate to one of the color names shown ' + 'below. You can use any legal template expression. ' + 'For example, you can set the title to always display in ' + 'green using the template "green" (without the quotes). ' + 'To show the title in blue if the book has the tag "Science ' + 'Fiction", red if the book has the tag "Mystery", or black if ' + 'the book has neither tag, use ' + '"{tags:switch(Science Fiction,blue,Mystery,red,)}" ' + 'To show the title in green if it has one format, blue if it ' + 'two formats, and red if more, use ' + "\"program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')\"") + + '

' + + _('Note: if you want to color a "custom column with a fixed set ' + 'of values", it is possible and often easier to specify the ' + 'colors in the column definition dialog. There you can ' + 'provide a color for each value without using a template.')+ '

') choices = db.field_metadata.displayable_field_keys() choices.sort(key=sort_key) choices.insert(0, '') for i in range(1, db.column_color_count+1): r('column_color_name_'+str(i), db.prefs, choices=choices) r('column_color_template_'+str(i), db.prefs) + all_colors = [unicode(s) for s in list(QColor.colorNames())] + self.colors_box.setText(', '.join(all_colors)) def initialize(self): ConfigWidgetBase.initialize(self) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index d7fca70c08..aa5afe26dd 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -416,72 +416,80 @@ then the tags will be displayed each on their own line. Column Coloring - + Column name - + + + + + Selection template - + - - - - - - - - - + - + - + - + - + - + - + - - - - Qt::Vertical + + + + + + + + + + + + Color names - - - 690 - 283 - + + + + + + + 0 + 1 + - + @@ -496,7 +504,7 @@ then the tags will be displayed each on their own line.
calibre/gui2/complete.h
- LineEditWithTextBox + TemplateLineEditor QLineEdit
calibre/gui2/dialogs/template_line_editor.h
diff --git a/src/calibre/gui2/preferences/plugboard.py b/src/calibre/gui2/preferences/plugboard.py index b4b1d4e08e..cf632c04c0 100644 --- a/src/calibre/gui2/preferences/plugboard.py +++ b/src/calibre/gui2/preferences/plugboard.py @@ -7,12 +7,12 @@ __docformat__ = 'restructuredtext en' import copy -from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem +from PyQt4.Qt import Qt, QComboBox, QListWidgetItem from calibre.customize.ui import is_disabled from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.device import device_name_for_plugboards -from calibre.gui2.dialogs.template_dialog import TemplateDialog +from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.plugboard_ui import Ui_Form from calibre.customize.ui import metadata_writers, device_plugins @@ -24,26 +24,6 @@ from calibre.library.server.content import plugboard_content_server_value, \ from calibre.utils.formatter import validation_formatter -class LineEditWithTextBox(QLineEdit): - - ''' - Extend the context menu of a QLineEdit to include more actions. - ''' - - def contextMenuEvent(self, event): - menu = self.createStandardContextMenu() - menu.addSeparator() - - action_open_editor = menu.addAction(_('Open Editor')) - - self.connect(action_open_editor, SIGNAL('triggered()'), self.open_editor) - menu.exec_(event.globalPos()) - - def open_editor(self): - t = TemplateDialog(self, self.text()) - if t.exec_(): - self.setText(t.textbox.toPlainText()) - class ConfigWidget(ConfigWidgetBase, Ui_Form): def genesis(self, gui): @@ -107,7 +87,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.source_widgets = [] self.dest_widgets = [] for i in range(0, len(self.dest_fields)-1): - w = LineEditWithTextBox(self) + w = TemplateLineEditor(self) self.source_widgets.append(w) self.fields_layout.addWidget(w, 5+i, 0, 1, 1) w = QComboBox(self) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 0fd396fb64..9b5fe63f25 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -234,6 +234,7 @@ The following functions are available in addition to those described in single-f * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. * ``field(name)`` -- returns the metadata field named by ``name``. + * ``first_non_empty(value, value, ...) -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. * ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are:: d : the day as number without a leading zero (1 to 31) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index aa8e4fb3a3..59a750bcc5 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -562,6 +562,22 @@ class BuiltinBooksize(BuiltinFormatterFunction): pass return '' +class BuiltinFirstNonEmpty(BuiltinFormatterFunction): + name = 'first_non_empty' + arg_count = -1 + doc = _('first_non_empty(value, value, ...) -- ' + 'returns the first value that is not empty. If all values are ' + 'empty, then the empty value is returned.' + 'You can have as many values as you want.') + + def evaluate(self, formatter, kwargs, mi, locals, *args): + i = 0 + while i < len(args): + if args[i]: + return args[i] + i += 1 + return '' + builtin_add = BuiltinAdd() builtin_assign = BuiltinAssign() builtin_booksize = BuiltinBooksize() @@ -571,8 +587,9 @@ builtin_contains = BuiltinContains() builtin_count = BuiltinCount() builtin_divide = BuiltinDivide() builtin_eval = BuiltinEval() -builtin_format_date = BuiltinFormat_date() +builtin_first_non_empty = BuiltinFirstNonEmpty() builtin_field = BuiltinField() +builtin_format_date = BuiltinFormat_date() builtin_ifempty = BuiltinIfempty() builtin_list_item = BuiltinListitem() builtin_lookup = BuiltinLookup() From 4ddb1e852ba181d6cfd03dcb8bd31aae758475d1 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 15:22:33 +0100 Subject: [PATCH 16/31] Improvements on coloring: add an in_list formatter function. Add more documentation in the preference screen. Add a scroll bar to the doc in the preferences screen. --- src/calibre/gui2/preferences/look_feel.py | 24 ++++++++++++++++------- src/calibre/gui2/preferences/look_feel.ui | 10 +++++++++- src/calibre/utils/formatter_functions.py | 17 ++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 97400c45bd..c96d980505 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -159,7 +159,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.df_up_button.clicked.connect(self.move_df_up) self.df_down_button.clicked.connect(self.move_df_down) - self.color_help_text.setWordWrap(True) self.color_help_text.setText('

' + _('Here you can specify coloring rules for fields shown in the ' 'library view. Choose the field you wish to color, then ' @@ -169,14 +168,25 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): 'below. You can use any legal template expression. ' 'For example, you can set the title to always display in ' 'green using the template "green" (without the quotes). ' - 'To show the title in blue if the book has the tag "Science ' - 'Fiction", red if the book has the tag "Mystery", or black if ' - 'the book has neither tag, use ' - '"{tags:switch(Science Fiction,blue,Mystery,red,)}" ' + 'To show the title in the color named in the custom column ' + '#column, use "{#column}". To show the title in blue if the ' + 'custom column #column contains the value "foo", in red if the ' + 'column contains the value "bar", otherwise in black, use ' + '

{#column:switch(foo,blue,bar,red,black)}
' + 'To show the title in blue if the book has the exact tag ' + '"Science Fiction", red if the book has the exact tag ' + '"Mystery", or black if the book has neither tag, use' + "
program: \n"
+                  "    t = field('tags'); \n"
+                  "    first_non_empty(\n"
+                  "        in_list(t, ',', '^Science Fiction$', 'blue', ''), \n"
+                  "        in_list(t, ',', '^Mystery$', 'red', 'black'))
" 'To show the title in green if it has one format, blue if it ' - 'two formats, and red if more, use ' - "\"program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')\"") + + 'two formats, and red if more, use' + "
program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')
") + '

' + + _('You can access a multi-line template editor from the ' + 'context menu (right-click).') + '

' + _('Note: if you want to color a "custom column with a fixed set ' 'of values", it is possible and often easier to specify the ' 'colors in the column definition dialog. There you can ' diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index aa5afe26dd..1194109c6c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -424,7 +424,12 @@ then the tags will be displayed each on their own line. - + + + true + + + @@ -483,6 +488,9 @@ then the tags will be displayed each on their own line. + + true + 0 diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 59a750bcc5..c53277f3ce 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -327,6 +327,22 @@ class BuiltinSwitch(BuiltinFormatterFunction): return args[i+1] i += 2 +class BuiltinInList(BuiltinFormatterFunction): + name = 'in_list' + arg_count = 5 + doc = _('in_list(val, separator, pattern, found_val, not_found_val) -- ' + 'treat val as a list of items separated by separator, ' + 'comparing the pattern against each value in the list. If the ' + 'pattern matches a value, return found_val, otherwise return ' + 'not_found_val.') + + def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv): + l = [v.strip() for v in val.split(sep) if v.strip()] + for v in l: + if re.search(pat, v): + return fv + return nfv + class BuiltinRe(BuiltinFormatterFunction): name = 're' arg_count = 3 @@ -591,6 +607,7 @@ builtin_first_non_empty = BuiltinFirstNonEmpty() builtin_field = BuiltinField() builtin_format_date = BuiltinFormat_date() builtin_ifempty = BuiltinIfempty() +builtin_in_list = BuiltinInList() builtin_list_item = BuiltinListitem() builtin_lookup = BuiltinLookup() builtin_lowercase = BuiltinLowercase() From ca5dc817c22e70ada00dd0c9feb7ad8ea0ea0238 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 15:26:32 +0100 Subject: [PATCH 17/31] Add the new in_list function to the documentation --- src/calibre/manual/template_lang.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 9b5fe63f25..69c77e5bfd 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -123,7 +123,8 @@ The functions available are: * ``contains(pattern, text if match, text if not match`` -- checks if field contains matches for the regular expression `pattern`. Returns `text if match` if matches are found, otherwise it returns `text if no match`. * ``count(separator)`` -- interprets the value as a list of items separated by `separator`, returning the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand. Examples: `{tags:count(,)}`, `{authors:count(&)}` * ``ifempty(text)`` -- if the field is not empty, return the value of the field. Otherwise return `text`. - * ``list_item(index, separator)`` -- interpret the value as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. + * ``in_list(separator, pattern, found_val, not_found_val)`` -- interpret the field as a list of items separated by `separator`, comparing the `pattern` against each value in the list. If the pattern matches a value, return `found_val`, otherwise return `not_found_val`. + * ``list_item(index, separator)`` -- interpret the field as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed. * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want. From f97bcfb1a8799d685722c56f76b6b659836c8810 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 May 2011 10:46:50 -0600 Subject: [PATCH 18/31] Try harder to recover on systems where people run temp file cleaners that delete the temp files of running programs --- src/calibre/ptempfile.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py index bff13dd248..01e8f18339 100644 --- a/src/calibre/ptempfile.py +++ b/src/calibre/ptempfile.py @@ -29,6 +29,10 @@ def remove_dir(x): def base_dir(): global _base_dir + if _base_dir is not None and not os.path.exists(_base_dir): + # Some people seem to think that running temp file cleaners that + # delete the temp dirs of running programs is a good idea! + _base_dir = None if _base_dir is None: td = os.environ.get('CALIBRE_WORKER_TEMP_DIR', None) if td is not None: From 8336bd6e4986effa3a184a410d885f95989c4d29 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 May 2011 10:48:45 -0600 Subject: [PATCH 19/31] Update Washington Post. Fixes #786609 (Updated recipe for The Washington Post) --- recipes/icons/wash_post.png | Bin 0 -> 1158 bytes recipes/wash_post.recipe | 119 ++++++++++++++++++++---------------- 2 files changed, 65 insertions(+), 54 deletions(-) create mode 100644 recipes/icons/wash_post.png diff --git a/recipes/icons/wash_post.png b/recipes/icons/wash_post.png new file mode 100644 index 0000000000000000000000000000000000000000..e392e4c3ff7b37e4106fb51359242e524fd80413 GIT binary patch literal 1158 zcmV;11bO?3P)000C{Nklx)Fm$tUHn1T`pB@Ttt(^HNa$8s*_kt_87BKouTIfwzs!0FE4{^j5W{W#bFs^Z;zT_ZyXZCJZLmrU0w0j zSh7mSkA^Uoi({0u6+Aj+Y7Z9EpgicPifQm2DiQfXt5x!c7O{l@*avq)a4=tkhW8JH zpoW-3O@SRt2}`BjOf?D~9v*N(j0Nv1d`)P^(jOko)QVFg)7wEy@C0MqXvhn|3xmDC zzsH7D`0`;ooxZ%h#59=XGOd)VWU{Xj(=v&;*CH-qsZQJk_qqD~MdGzf=> zhw?N2MbPH!>x;j*k^zsz+~x%wA0L~~Xb6_52r%9mQi2zl0S+j$vor?mWB*U^|+4{1moQ9d{{v}EeboxQU>u->~!YsiL0n};OsC12){Sjcl{yI?m zL>-Yt-e5?3xlBXA0e;f#ZJ3t_rwUX?cmGF*Q@^`rL!6kBL{*GJMh))Tjd-uhK%3ZU z%Q_$|)oXD>Nbwq*WA>t6>e-RVh=n!mxf^7!=}a&y0aIhbGO!0=Ih{`ZgzxlxV!^$U z>>#y#{ZQE0Qxo3Ava?C(ibL0ziUC&=Z9{Vgu+->G)I~&rlYjV1<>p+BosxKL2 zZ-!BAY|Ijz)U3yhIMf6s&Q^IgZ74fzY~+6{;Wx>GU|EroHdC|K7Fm*sdCFf6gQDJO zNdb(2teH-Ty5%$k5{ Date: Sun, 22 May 2011 18:32:03 +0100 Subject: [PATCH 20/31] Make coloring work across change_libraries --- src/calibre/gui2/init.py | 1 - src/calibre/gui2/library/models.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 079e1814c3..a75ff01b21 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -65,7 +65,6 @@ class LibraryViewMixin(object): # {{{ self.build_context_menus() self.library_view.model().set_highlight_only(config['highlight_search_matches']) - self.library_view.model().set_color_templates() def build_context_menus(self): lm = QMenu(self) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 83bf5868ba..a3e7438908 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -157,6 +157,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.database_changed.emit(db) self.stop_metadata_backup() self.start_metadata_backup() + self.set_color_templates() def start_metadata_backup(self): self.metadata_backup = MetadataBackup(self.db) @@ -535,6 +536,7 @@ class BooksModel(QAbstractTableModel): # {{{ return img def set_color_templates(self): + print 'here' self.column_color_map = {} for i in range(1,self.db.column_color_count+1): name = self.db.prefs.get('column_color_name_'+str(i)) From 6087a6a6603868fa752b15a30b074ebd7e7e7243 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 18:34:34 +0100 Subject: [PATCH 21/31] Remove print statement --- src/calibre/gui2/library/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index a3e7438908..b378256a42 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -536,7 +536,6 @@ class BooksModel(QAbstractTableModel): # {{{ return img def set_color_templates(self): - print 'here' self.column_color_map = {} for i in range(1,self.db.column_color_count+1): name = self.db.prefs.get('column_color_name_'+str(i)) From b0db4939688f0b7d737feee16a50087ee2f775e7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 May 2011 12:29:31 -0600 Subject: [PATCH 22/31] ... --- src/calibre/gui2/actions/copy_to_library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index 2e4d0380be..7190d1486f 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -16,7 +16,7 @@ from calibre.gui2 import error_dialog, Dispatcher, warning_dialog from calibre.gui2.dialogs.progress import ProgressDialog from calibre.utils.config import prefs, tweaks -class Worker(Thread): +class Worker(Thread): # {{{ def __init__(self, ids, db, loc, progress, done, delete_after): Thread.__init__(self) @@ -75,7 +75,7 @@ class Worker(Thread): if co is not None: newdb.set_conversion_options(x, 'PIPE', co) self.processed.add(x) - +# }}} class CopyToLibraryAction(InterfaceAction): From 3487ed762ea084d6d44d9e3a9446f6e87e87e1a8 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 19:34:03 +0100 Subject: [PATCH 23/31] Switch refresh to reset when loading column color templates --- src/calibre/gui2/library/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index b378256a42..2576518d92 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -153,11 +153,11 @@ class BooksModel(QAbstractTableModel): # {{{ self.headers[col] = self.custom_columns[col]['name'] self.build_data_convertors() + self.set_color_templates(reset=False) self.reset() self.database_changed.emit(db) self.stop_metadata_backup() self.start_metadata_backup() - self.set_color_templates() def start_metadata_backup(self): self.metadata_backup = MetadataBackup(self.db) @@ -535,14 +535,15 @@ class BooksModel(QAbstractTableModel): # {{{ img = self.default_image return img - def set_color_templates(self): + def set_color_templates(self, reset=True): self.column_color_map = {} for i in range(1,self.db.column_color_count+1): name = self.db.prefs.get('column_color_name_'+str(i)) if name: self.column_color_map[name] = \ self.db.prefs.get('column_color_template_'+str(i)) - self.refresh() + if reset: + self.reset() def build_data_convertors(self): def authors(r, idx=-1): From 8a4ceb206d57b8e133cefec77736930c1d94e418 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 May 2011 12:43:25 -0600 Subject: [PATCH 24/31] Fix ratings deleegate to use model supplied color --- src/calibre/gui2/library/delegates.py | 20 +++++++++++++------- src/calibre/gui2/library/models.py | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index e2234f6df5..b3012a7211 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -40,10 +40,7 @@ class RatingDelegate(QStyledItemDelegate): # {{{ 50 + 40 * sin(0.8 * i * pi)) self.star_path.closeSubpath() self.star_path.setFillRule(Qt.WindingFill) - gradient = QLinearGradient(0, 0, 0, 100) - gradient.setColorAt(0.0, self.COLOR) - gradient.setColorAt(1.0, self.COLOR) - self.brush = QBrush(gradient) + self.gradient = QLinearGradient(0, 0, 0, 100) self.factor = self.SIZE/100. def sizeHint(self, option, index): @@ -53,7 +50,8 @@ class RatingDelegate(QStyledItemDelegate): # {{{ def paint(self, painter, option, index): style = self._parent.style() option = QStyleOptionViewItemV4(option) - self.initStyleOption(option, self.dummy) + self.initStyleOption(option, index) + option.text = u'' num = index.model().data(index, Qt.DisplayRole).toInt()[0] def draw_star(): painter.save() @@ -65,6 +63,7 @@ class RatingDelegate(QStyledItemDelegate): # {{{ painter.restore() painter.save() + if hasattr(QStyle, 'CE_ItemViewItem'): style.drawControl(QStyle.CE_ItemViewItem, option, painter, self._parent) @@ -75,8 +74,15 @@ class RatingDelegate(QStyledItemDelegate): # {{{ painter.setClipRect(option.rect) y = option.rect.center().y()-self.SIZE/2. x = option.rect.left() - painter.setPen(self.PEN) - painter.setBrush(self.brush) + color = index.data(Qt.ForegroundRole) + if color.isNull() or not color.isValid(): + color = self.COLOR + else: + color = QColor(color) + painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) + self.gradient.setColorAt(0.0, color) + self.gradient.setColorAt(1.0, color) + painter.setBrush(QBrush(self.gradient)) painter.translate(x, y) i = 0 while i < num: diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index fc1117167d..0baf98ecdd 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -693,9 +693,9 @@ class BooksModel(QAbstractTableModel): # {{{ return NONE if role in (Qt.DisplayRole, Qt.EditRole): return self.column_to_dc_map[col](index.row()) - elif role == Qt.BackgroundColorRole: + elif role == Qt.BackgroundRole: if self.id(index) in self.ids_to_highlight_set: - return QColor('lightgreen') + return QVariant(QColor('lightgreen')) elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: return self.column_to_dc_decorator_map[index.column()](index.row()) From c90382e059de39be3da1778862f42cec995e60ca Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 May 2011 12:46:06 -0600 Subject: [PATCH 25/31] ... --- src/calibre/gui2/library/delegates.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index b3012a7211..7265be89c8 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -7,11 +7,11 @@ __docformat__ = 'restructuredtext en' from math import cos, sin, pi -from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \ - QPainterPath, QLinearGradient, QBrush, \ - QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ - QIcon, QDoubleSpinBox, QVariant, QSpinBox, \ - QStyledItemDelegate, QComboBox, QTextDocument +from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize, + QPainterPath, QLinearGradient, QBrush, + QPen, QStyle, QPainter, QStyleOptionViewItemV4, + QIcon, QDoubleSpinBox, QVariant, QSpinBox, + QStyledItemDelegate, QComboBox, QTextDocument) from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2.widgets import EnLineEdit @@ -27,7 +27,6 @@ from calibre.gui2.dialogs.template_dialog import TemplateDialog class RatingDelegate(QStyledItemDelegate): # {{{ COLOR = QColor("blue") SIZE = 16 - PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) From 0bd580f572cd5b5a10c9167adc6f8f5c717d0dc3 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 19:53:26 +0100 Subject: [PATCH 26/31] Remove unused PEN declaration --- src/calibre/gui2/library/delegates.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 6d962c9129..4f002c2c48 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -28,7 +28,6 @@ from calibre.gui2.dialogs.template_dialog import TemplateDialog class RatingDelegate(QStyledItemDelegate): # {{{ COLOR = QColor("blue") SIZE = 16 - PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) From 29b453ba23177adc9cd740cada0f6b7ba08e23f4 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 19:55:46 +0100 Subject: [PATCH 27/31] Check for color valid when using column coloring --- src/calibre/gui2/library/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 2576518d92..6e8e79d3b3 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -726,7 +726,9 @@ class BooksModel(QAbstractTableModel): # {{{ txt = unicode(index.data(Qt.DisplayRole).toString()) if len(colors) > 0 and txt in values: try: - return QColor(colors[values.index(txt)]) + color = colors[values.index(txt)] + if QColor.isValid(color): + return QColor(color) except: pass return None From de969af505aeaf32507c19229081371a850549b9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 20:12:47 +0100 Subject: [PATCH 28/31] Improve robustness in column coloring. --- src/calibre/gui2/library/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 6e8e79d3b3..9d90c44f18 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -715,7 +715,8 @@ class BooksModel(QAbstractTableModel): # {{{ fmt = self.column_color_map[key] try: color = composite_formatter.safe_format(fmt, mi, '', mi) - return QColor(color) + if QColor.isValid(color): + return QColor(color) except: return None elif self.is_custom_column(key) and \ From 478369c7cba88cf04d8778d8d4b2ea14c40872ed Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 20:47:47 +0100 Subject: [PATCH 29/31] Add colors icon to preferences dialog. Correctly clean template values when the column is set to empty. --- src/calibre/gui2/preferences/look_feel.py | 8 +++++++- src/calibre/gui2/preferences/look_feel.ui | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index c96d980505..ffbc82eefd 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -194,7 +194,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): choices = db.field_metadata.displayable_field_keys() choices.sort(key=sort_key) choices.insert(0, '') - for i in range(1, db.column_color_count+1): + self.column_color_count = db.column_color_count+1 + for i in range(1, self.column_color_count): r('column_color_name_'+str(i), db.prefs, choices=choices) r('column_color_template_'+str(i), db.prefs) all_colors = [unicode(s) for s in list(QColor.colorNames())] @@ -267,6 +268,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.changed_signal.emit() def commit(self, *args): + for i in range(1, self.column_color_count): + col = getattr(self, 'opt_column_color_name_'+str(i)) + if not col.currentText(): + temp = getattr(self, 'opt_column_color_template_'+str(i)) + temp.setText('') rr = ConfigWidgetBase.commit(self, *args) if self.current_font != self.initial_font: gprefs['font'] = (self.current_font[:4] if self.current_font else diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 1194109c6c..9dedcf4f8c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -410,7 +410,7 @@ then the tags will be displayed each on their own line. - :/images/cover_flow.png:/images/cover_flow.png + :/images/format-fill-color.png:/images/format-fill-color.png Column Coloring From ed7a6180aeca7aef8a93bc5738855f8d048cff4b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 22:30:35 +0100 Subject: [PATCH 30/31] Change pathname generation to use the correct value for {id} --- src/calibre/devices/usbms/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index c46e9539c9..45b72abe74 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -926,8 +926,8 @@ class Device(DeviceConfig, DevicePlugin): if not isinstance(template, unicode): template = template.decode('utf-8') app_id = str(getattr(mdata, 'application_id', '')) - # The db id will be in the created filename - extra_components = get_components(template, mdata, fname, + id_ = mdata.get('id', fname) + extra_components = get_components(template, mdata, id_, timefmt=opts.send_timefmt, length=maxlen-len(app_id)-1) if not extra_components: extra_components.append(sanitize(self.filename_callback(fname, From a74ee044bb1dec7b164060fffacb496641aec634 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 May 2011 20:11:06 -0600 Subject: [PATCH 31/31] ... --- src/calibre/gui2/preferences/look_feel.ui | 51 ++++++++++++++--------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 970052ad2e..ad990a1586 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -467,25 +467,6 @@ then the tags will be displayed each on their own line. - - - - - 0 - 1 - - - - - 16777215 - 100 - - - - true - - - @@ -524,6 +505,38 @@ then the tags will be displayed each on their own line. + + + + + 16777215 + 120 + + + + true + + + + + 0 + 0 + 687 + 61 + + + + + + + true + + + + + + +