diff --git a/Changelog.yaml b/Changelog.yaml index c986b51486..f71bdd5907 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -19,6 +19,50 @@ # new recipes: # - title: +- version: 0.8.62 + date: 2012-07-27 + + new features: + - title: "Book details panel: Allow right clicking on a format to delete it." + + - title: "When errors occur in lots of background jobs, add an option to the error message to temporarily suppress subsequent error messages." + tickets: [886904] + + - title: "E-book viewer full screen mode: Allow clicking in the left and right page margins to turn pages." + tickets: [1024819] + + - title: "Drivers for various Android devices" + tickets: [1028690,1027431] + + - title: "Advanced search dialog: When starting on the title/author/etc. tab, restore the previously used search kind as well." + tickets: [1029745] + + - title: "When presenting the calibre must be restarted warning after installing a new plugin, add a restart now button so that the user can conveniently restart calibre. Currently only works when going vie Preferences->Plugins->Get new plugins" + + bug fixes: + - title: "Fix main window layout state being saved incorrectly if calibre is killed without a proper shutdown" + + - title: "Fix boolean and date searching in non english calibre installs." + + - title: "Conversion: Ignore invalid chapter detection and level n ToC expressions instead of erroring out" + + improved recipes: + - Psychology Today + - The Smithsonian + - The New Republic + - Various updated Polish news sources + - The Sun + - San Francisco Bay Guardian + - AnandTech + - Smashing Magazine + + new recipes: + - title: Linux Journal and Conowego.pl + author: fenuks + + - title: A list apart and .net magazine + author: Marc Busque + - version: 0.8.61 date: 2012-07-20 diff --git a/manual/conversion.rst b/manual/conversion.rst index 5eaca5a469..a4ecd902cc 100644 --- a/manual/conversion.rst +++ b/manual/conversion.rst @@ -710,3 +710,31 @@ EPUB from the ZIP file are:: Note that because this file explores the potential of EPUB, most of the advanced formatting is not going to work on readers less capable than |app|'s built-in EPUB viewer. + +Convert ODT documents +~~~~~~~~~~~~~~~~~~~~~ + +|app| can directly convert ODT (OpenDocument Text) files. You should use styles to format your document and minimize the use of direct formatting. +When inserting images into your document you need to anchor them to the paragraph, images anchored to a page will all end up in the front of the conversion. + +To enable automatic detection of chapters, you need to mark them with the build-in styles called 'Heading 1', 'Heading 2', ..., 'Heading 6' ('Heading 1' equates to the HTML tag

, 'Heading 2' to

etc). When you convert in |app| you can enter which style you used into the 'Detect chapters at' box. Example: + + * If you mark Chapters with style 'Heading 2', you have to set the 'Detect chapters at' box to ``//h:h2`` + * For a nested TOC with Sections marked with 'Heading 2' and the Chapters marked with 'Heading 3' you need to enter ``//h:h2|//h:h3``. On the Convert - TOC page set the 'Level 1 TOC' box to ``//h:h2`` and the 'Level 2 TOC' box to ``//h:h3``. + +Well-known document properties (Title, Keywords, Description, Creator) are recognized and |app| will use the first image (not to small, and with good aspect-ratio) as the cover image. + +There is also an advanced property conversion mode, which is activated by setting the custom property ``opf.metadata`` ('Yes or No' type) to Yes in your ODT document (File->Properties->Custom Properties). +If this property is detected by |app|, the following custom properties are recognized (``opf.authors`` overrides document creator):: + + opf.titlesort + opf.authors + opf.authorsort + opf.publisher + opf.pubdate + opf.isbn + opf.language + +In addition to this, you can specify the picture to use as the cover by naming it ``opf.cover`` (right click, Picture->Options->Name) in the ODT. If no picture with this name is found, the 'smart' method is used. +To prevent this you can set the custom property ``opf.nocover`` ('Yes or No' type) to Yes. + diff --git a/manual/develop.rst b/manual/develop.rst old mode 100755 new mode 100644 index ce8b02a70d..d59c315951 --- a/manual/develop.rst +++ b/manual/develop.rst @@ -6,9 +6,9 @@ Setting up a |app| development environment =========================================== |app| is completely open source, licensed under the `GNU GPL v3 `_. -This means that you are free to download and modify the program to your heart's content. In this section, -you will learn how to get a |app| development environment set up on the operating system of your choice. -|app| is written primarily in `Python `_ with some C/C++ code for speed and system interfacing. +This means that you are free to download and modify the program to your heart's content. In this section, +you will learn how to get a |app| development environment set up on the operating system of your choice. +|app| is written primarily in `Python `_ with some C/C++ code for speed and system interfacing. Note that |app| is not compatible with Python 3 and requires at least Python 2.7. .. contents:: Contents @@ -20,14 +20,14 @@ Design philosophy |app| has its roots in the Unix world, which means that its design is highly modular. The modules interact with each other via well defined interfaces. This makes adding new features and fixing -bugs in |app| very easy, resulting in a frenetic pace of development. Because of its roots, |app| has a +bugs in |app| very easy, resulting in a frenetic pace of development. Because of its roots, |app| has a comprehensive command line interface for all its functions, documented in :ref:`cli`. The modular design of |app| is expressed via ``Plugins``. There is a :ref:`tutorial ` on writing |app| plugins. For example, adding support for a new device to |app| typically involves writing less than a 100 lines of code in the form of -a device driver plugin. You can browse the -`built-in drivers `_. Similarly, adding support -for new conversion formats involves writing input/output format plugins. Another example of the modular design is the :ref:`recipe system ` for +a device driver plugin. You can browse the +`built-in drivers `_. Similarly, adding support +for new conversion formats involves writing input/output format plugins. Another example of the modular design is the :ref:`recipe system ` for fetching news. For more examples of plugins designed to add features to |app|, see the `plugin index `_. Code layout @@ -91,15 +91,15 @@ this, make your changes, then run:: This will create a :file:`my-changes` file in the current directory, simply attach that to a ticket on the |app| `bug tracker `_. -If you plan to do a lot of development on |app|, then the best method is to create a +If you plan to do a lot of development on |app|, then the best method is to create a `Launchpad `_ account. Once you have an account, you can use it to register your bzr branch created by the `bzr branch` command above. First run the following command to tell bzr about your launchpad account:: bzr launchpad-login your_launchpad_username -Now, you have to setup SSH access to Launchpad. First create an SSH public/private keypair. Then upload -the public key to Launchpad by going to your Launchpad account page. Instructions for setting up the +Now, you have to setup SSH access to Launchpad. First create an SSH public/private keypair. Then upload +the public key to Launchpad by going to your Launchpad account page. Instructions for setting up the private key in bzr are at http://bazaar-vcs.org/Bzr_and_SSH. Now you can upload your branch to the |app| project in Launchpad by following the instructions at https://help.launchpad.net/Code/UploadingABranch. Whenever you commit changes to your branch with the command:: @@ -108,7 +108,7 @@ Whenever you commit changes to your branch with the command:: Kovid can merge it directly from your branch into the main |app| source tree. You should also keep an eye on the |app| `development forum `. Before making major changes, you should -discuss them in the forum or contact Kovid directly (his email address is all over the source code). +discuss them in the forum or contact Kovid directly (his email address is all over the source code). Windows development environment --------------------------------- @@ -118,12 +118,12 @@ the previously checked out |app| code directory. For example:: cd C:\Users\kovid\work\calibre -calibre is the directory that contains the src and resources sub-directories. +calibre is the directory that contains the src and resources sub-directories. The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. So, following the example above, it would be ``C:\Users\kovid\work\calibre\src``. `Here is a short guide `_ to setting environment -variables on Windows. +variables on Windows. Once you have set the environment variable, open a new command prompt and check that it was correctly set by using the command:: @@ -134,7 +134,7 @@ Setting this environment variable means that |app| will now load all its Python That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src\\calibre\\__init__.py` in your favorite editor and add the line:: - + print ("Hello, world!") near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. @@ -149,23 +149,25 @@ the previously checked out |app| code directory, for example:: calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:`Preferences->Advanced->Miscellaneous` in the |app| GUI. -The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. -So, following the example above, it would be ``/Users/kovid/work/calibre/src``. -`How to set environment variables `_. +The next step is to create a bash script that will set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory when running calibre in debug mode. -Once you have set the environment variable, open a new Terminal and check that it was correctly set by using -the command:: +Create a plain text file:: - echo $CALIBRE_DEVELOP_FROM + #!/bin/sh + export CALIBRE_DEVELOP_FROM="/Users/kovid/work/calibre/src" + calibre-debug -g -Setting this environment variable means that |app| will now load all its Python code from the specified location. +Save this file as ``/usr/bin/calibre-develop``, then set its permissions so that it can be executed:: -That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src/calibre/__init__.py` -in your favorite editor and add the line:: - - print ("Hello, world!") + chmod +x /usr/bin/calibre-develop -near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. +Once you have done this, run:: + + calibre-develop + +You should see some diagnostic information in the Terminal window as calibre +starts up, and you should see an asterisk after the version number in the GUI +window, indicating that you are running from source. Linux development environment ------------------------------ @@ -180,11 +182,11 @@ Install the |app| using the binary installer. Then open a terminal and change to cd /home/kovid/work/calibre -calibre is the directory that contains the src and resources sub-directories. +calibre is the directory that contains the src and resources sub-directories. The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. So, following the example above, it would be ``/home/kovid/work/calibre/src``. How to set environment variables depends on -your Linux distribution and what shell you are using. +your Linux distribution and what shell you are using. Once you have set the environment variable, open a new terminal and check that it was correctly set by using the command:: @@ -195,7 +197,7 @@ Setting this environment variable means that |app| will now load all its Python That's it! You are now ready to start hacking on the |app| code. For example, open the file :file:`src/calibre/__init__.py` in your favorite editor and add the line:: - + print ("Hello, world!") near the top of the file. Now run the command :command:`calibredb`. The very first line of output should be ``Hello, world!``. diff --git a/recipes/benchmark_pl.recipe b/recipes/benchmark_pl.recipe index 00eea1be68..9544abdfcf 100644 --- a/recipes/benchmark_pl.recipe +++ b/recipes/benchmark_pl.recipe @@ -1,6 +1,6 @@ from calibre.web.feeds.news import BasicNewsRecipe import re -class Benchmark_pl(BasicNewsRecipe): +class BenchmarkPl(BasicNewsRecipe): title = u'Benchmark.pl' __author__ = 'fenuks' description = u'benchmark.pl -IT site' @@ -14,7 +14,7 @@ class Benchmark_pl(BasicNewsRecipe): preprocess_regexps = [(re.compile(ur'

 Zobacz poprzednie Opinie dnia:.*', re.DOTALL|re.IGNORECASE), lambda match: ''), (re.compile(ur'Więcej o .*?', re.DOTALL|re.IGNORECASE), lambda match: '')] keep_only_tags=[dict(name='div', attrs={'class':['m_zwykly', 'gallery']})] remove_tags_after=dict(name='div', attrs={'class':'body'}) - remove_tags=[dict(name='div', attrs={'class':['kategoria', 'socialize', 'thumb', 'panelOcenaObserwowane', 'categoryNextToSocializeGallery']}), dict(name='table', attrs={'background':'http://www.benchmark.pl/uploads/backend_img/a/fotki_newsy/opinie_dnia/bg.png'}), dict(name='table', attrs={'width':'210', 'cellspacing':'1', 'cellpadding':'4', 'border':'0', 'align':'right'})] + remove_tags=[dict(name='div', attrs={'class':['kategoria', 'socialize', 'thumb', 'panelOcenaObserwowane', 'categoryNextToSocializeGallery', 'breadcrumb']}), dict(name='table', attrs={'background':'http://www.benchmark.pl/uploads/backend_img/a/fotki_newsy/opinie_dnia/bg.png'}), dict(name='table', attrs={'width':'210', 'cellspacing':'1', 'cellpadding':'4', 'border':'0', 'align':'right'})] INDEX= 'http://www.benchmark.pl' feeds = [(u'Aktualności', u'http://www.benchmark.pl/rss/aktualnosci-pliki.xml'), (u'Testy i recenzje', u'http://www.benchmark.pl/rss/testy-recenzje-minirecenzje.xml')] diff --git a/recipes/conowego_pl.recipe b/recipes/conowego_pl.recipe new file mode 100755 index 0000000000..8b4288ddcd --- /dev/null +++ b/recipes/conowego_pl.recipe @@ -0,0 +1,38 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup +class CoNowegoPl(BasicNewsRecipe): + title = u'conowego.pl' + __author__ = 'fenuks' + description = u'Nowy wortal technologiczny oraz gazeta internetowa. Testy najnowszych produktów, fachowe porady i recenzje. U nas znajdziesz wszystko o elektronice użytkowej !' + cover_url = 'http://www.conowego.pl/fileadmin/templates/main/images/logo_top.png' + category = 'IT, news' + language = 'pl' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + remove_empty_feeds = True + use_embedded_content = False + keep_only_tags = [dict(name='div', attrs={'class':'news_list single_view'})] + remove_tags = [dict(name='div', attrs={'class':['ni_bottom', 'ni_rank', 'ni_date']})] + feeds = [(u'Aktualno\u015bci', u'http://www.conowego.pl/rss/aktualnosci-5/?type=100'), (u'Gaming', u'http://www.conowego.pl/rss/gaming-6/?type=100'), (u'Porady', u'http://www.conowego.pl/rss/porady-3/?type=100'), (u'Testy', u'http://www.conowego.pl/rss/testy-2/?type=100')] + + def preprocess_html(self, soup): + for i in soup.findAll('img'): + i.parent.insert(0, BeautifulSoup('
')) + i.insert(len(i), BeautifulSoup('
')) + self.append_page(soup, soup.body) + return soup + + + def append_page(self, soup, appendtag): + tag = appendtag.find('div', attrs={'class':'pages'}) + if tag: + nexturls=tag.findAll('a') + for nexturl in nexturls[:-1]: + soup2 = self.index_to_soup('http://www.conowego.pl/' + nexturl['href']) + pagetext = soup2.find(attrs={'class':'ni_content'}) + pos = len(appendtag.contents) + appendtag.insert(pos, pagetext) + + for r in appendtag.findAll(attrs={'class':['pages', 'paginationWrap']}): + r.extract() diff --git a/recipes/film_web.recipe b/recipes/film_web.recipe index 2a6e00d501..ba34c9ff63 100644 --- a/recipes/film_web.recipe +++ b/recipes/film_web.recipe @@ -1,6 +1,7 @@ from calibre.web.feeds.news import BasicNewsRecipe - -class Filmweb_pl(BasicNewsRecipe): +import re +from calibre.ebooks.BeautifulSoup import BeautifulSoup +class FilmWebPl(BasicNewsRecipe): title = u'FilmWeb' __author__ = 'fenuks' description = 'FilmWeb - biggest polish movie site' @@ -12,8 +13,9 @@ class Filmweb_pl(BasicNewsRecipe): max_articles_per_feed = 100 no_stylesheets= True remove_empty_feeds=True + preprocess_regexps = [(re.compile(u'\(kliknij\,\ aby powiększyć\)', re.IGNORECASE), lambda m: ''), ]#(re.compile(ur' | ', re.IGNORECASE), lambda m: '')] extra_css = '.hdrBig {font-size:22px;} ul {list-style-type:none; padding: 0; margin: 0;}' - remove_tags= [dict(name='div', attrs={'class':['recommendOthers']}), dict(name='ul', attrs={'class':'fontSizeSet'})] + remove_tags= [dict(name='div', attrs={'class':['recommendOthers']}), dict(name='ul', attrs={'class':'fontSizeSet'}), dict(attrs={'class':'userSurname anno'})] keep_only_tags= [dict(name='h1', attrs={'class':['hdrBig', 'hdrEntity']}), dict(name='div', attrs={'class':['newsInfo', 'newsInfoSmall', 'reviewContent description']})] feeds = [(u'Wszystkie newsy', u'http://www.filmweb.pl/feed/news/latest'), (u'News / Filmy w produkcji', 'http://www.filmweb.pl/feed/news/category/filminproduction'), @@ -31,18 +33,22 @@ class Filmweb_pl(BasicNewsRecipe): (u'News / Kino polskie', u'http://www.filmweb.pl/feed/news/category/polish.cinema'), (u'News / Telewizja', u'http://www.filmweb.pl/feed/news/category/tv'), (u'Recenzje redakcji', u'http://www.filmweb.pl/feed/reviews/latest'), - (u'Recenzje użytkowników', u'http://www.filmweb.pl/feed/user-reviews/latest')] + (u'Recenzje użytkowników', u'http://www.filmweb.pl/feed/user-reviews/latest') + ] - def skip_ad_pages(self, soup): + def skip_ad_pages(self, soup): skip_tag = soup.find('a', attrs={'class':'welcomeScreenButton'}) if skip_tag is not None: - self.log.warn('skip_tag') - self.log.warn(skip_tag) return self.index_to_soup(skip_tag['href'], raw=True) - + def preprocess_html(self, soup): for a in soup('a'): if a.has_key('href') and 'http://' not in a['href'] and 'https://' not in a['href']: a['href']=self.index + a['href'] - return soup \ No newline at end of file + for i in soup.findAll('a', attrs={'class':'fn'}): + i.insert(len(i), BeautifulSoup('
')) + for i in soup.findAll('sup'): + if not i.string or i.string.startswith('(kliknij'): + i.extract() + return soup diff --git a/recipes/gry_online_pl.recipe b/recipes/gry_online_pl.recipe index e188e4988c..fce9674081 100644 --- a/recipes/gry_online_pl.recipe +++ b/recipes/gry_online_pl.recipe @@ -1,6 +1,6 @@ from calibre.web.feeds.recipes import BasicNewsRecipe -class Gry_online_pl(BasicNewsRecipe): +class GryOnlinePl(BasicNewsRecipe): title = u'Gry-Online.pl' __author__ = 'fenuks' description = 'Gry-Online.pl - computer games' @@ -21,17 +21,18 @@ class Gry_online_pl(BasicNewsRecipe): tag = appendtag.find('div', attrs={'class':'n5p'}) if tag: nexturls=tag.findAll('a') - for nexturl in nexturls[1:]: - try: - soup2 = self.index_to_soup('http://www.gry-online.pl/S020.asp'+ nexturl['href']) - except: - soup2 = self.index_to_soup('http://www.gry-online.pl/S022.asp'+ nexturl['href']) + url_part = soup.find('link', attrs={'rel':'canonical'})['href'] + url_part = url_part[25:].rpartition('?')[0] + for nexturl in nexturls[1:-1]: + soup2 = self.index_to_soup('http://www.gry-online.pl/' + url_part + nexturl['href']) pagetext = soup2.find(attrs={'class':'gc660'}) for r in pagetext.findAll(name='header'): r.extract() + for r in pagetext.findAll(attrs={'itemprop':'description'}): + r.extract() pos = len(appendtag.contents) appendtag.insert(pos, pagetext) - for r in appendtag.findAll(attrs={'class':['n5p', 'add-info', 'twitter-share-button']}): + for r in appendtag.findAll(attrs={'class':['n5p', 'add-info', 'twitter-share-button', 'lista lista3 lista-gry']}): r.extract() diff --git a/recipes/icons/conowego_pl.png b/recipes/icons/conowego_pl.png new file mode 100644 index 0000000000..3bc8f2c672 Binary files /dev/null and b/recipes/icons/conowego_pl.png differ diff --git a/recipes/icons/linux_journal.png b/recipes/icons/linux_journal.png new file mode 100644 index 0000000000..ed0092bd1d Binary files /dev/null and b/recipes/icons/linux_journal.png differ diff --git a/recipes/linux_journal.recipe b/recipes/linux_journal.recipe new file mode 100755 index 0000000000..99b1a570dc --- /dev/null +++ b/recipes/linux_journal.recipe @@ -0,0 +1,36 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class LinuxJournal(BasicNewsRecipe): + title = u'Linux Journal' + __author__ = 'fenuks' + description = u'The monthly magazine of the Linux community, promoting the use of Linux worldwide.' + cover_url = 'http://www.linuxjournal.com/files/linuxjournal.com/ufiles/logo-lj.jpg' + category = 'IT, Linux' + language = 'en' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + keep_only_tags=[dict(id='content-inner')] + remove_tags_after= dict(attrs={'class':'user-signature clear-block'}) + remove_tags=[dict(attrs={'class':['user-signature clear-block', 'breadcrumb', 'terms terms-inline']})] + feeds = [(u'Front Page', u'http://feeds.feedburner.com/linuxjournalcom'), (u'News', u'http://feeds.feedburner.com/LinuxJournal-BreakingNews'), (u'Blogs', u'http://www.linuxjournal.com/blog/feed'), (u'Audio/Video', u'http://www.linuxjournal.com/taxonomy/term/28/0/feed'), (u'Community', u'http://www.linuxjournal.com/taxonomy/term/18/0/feed'), (u'Education', u'http://www.linuxjournal.com/taxonomy/term/25/0/feed'), (u'Embedded', u'http://www.linuxjournal.com/taxonomy/term/27/0/feed'), (u'Hardware', u'http://www.linuxjournal.com/taxonomy/term/23/0/feed'), (u'HOWTOs', u'http://www.linuxjournal.com/taxonomy/term/19/0/feed'), (u'International', u'http://www.linuxjournal.com/taxonomy/term/30/0/feed'), (u'Security', u'http://www.linuxjournal.com/taxonomy/term/31/0/feed'), (u'Software', u'http://www.linuxjournal.com/taxonomy/term/17/0/feed'), (u'Sysadmin', u'http://www.linuxjournal.com/taxonomy/term/21/0/feed'), (u'Webmaster', u'http://www.linuxjournal.com/taxonomy/term/24/0/feed')] + + def append_page(self, soup, appendtag): + next = appendtag.find('li', attrs={'class':'pager-next'}) + while next: + nexturl = next.a['href'] + appendtag.find('div', attrs={'class':'links'}).extract() + soup2 = self.index_to_soup('http://www.linuxjournal.com'+ nexturl) + pagetext = soup2.find(attrs={'class':'node-inner'}).find(attrs={'class':'content'}) + next = appendtag.find('li', attrs={'class':'pager-next'}) + pos = len(appendtag.contents) + appendtag.insert(pos, pagetext) + tag = appendtag.find('div', attrs={'class':'links'}) + if tag: + tag.extract() + + def preprocess_html(self, soup): + self.append_page(soup, soup.body) + return soup \ No newline at end of file diff --git a/recipes/natemat_pl.recipe b/recipes/natemat_pl.recipe index faa1b341a0..d6db93dad7 100644 --- a/recipes/natemat_pl.recipe +++ b/recipes/natemat_pl.recipe @@ -1,3 +1,4 @@ +import re from calibre.web.feeds.news import BasicNewsRecipe class NaTemat(BasicNewsRecipe): @@ -8,8 +9,9 @@ class NaTemat(BasicNewsRecipe): description = u'informacje, komentarze, opinie' category = 'news' language = 'pl' + preprocess_regexps = [(re.compile(ur'Czytaj też\:.*?', re.IGNORECASE), lambda m: ''), (re.compile(ur'Zobacz też\:.*?', re.IGNORECASE), lambda m: ''), (re.compile(ur'Czytaj więcej\:.*?', re.IGNORECASE), lambda m: ''), (re.compile(ur'Czytaj również\:.*?', re.IGNORECASE), lambda m: '')] cover_url= 'http://blog.plona.pl/wp-content/uploads/2012/05/natemat.png' no_stylesheets = True keep_only_tags= [dict(id='main')] - remove_tags= [dict(attrs={'class':['button', 'block-inside style_default', 'article-related']})] + remove_tags= [dict(attrs={'class':['button', 'block-inside style_default', 'article-related', 'user-header', 'links']}), dict(name='img', attrs={'class':'indent'})] feeds = [(u'Artyku\u0142y', u'http://natemat.pl/rss/wszystkie')] diff --git a/recipes/psych.recipe b/recipes/psych.recipe index 3fc940b4a2..a21acefe30 100644 --- a/recipes/psych.recipe +++ b/recipes/psych.recipe @@ -1,44 +1,79 @@ +import re +from calibre.web.feeds.recipes import BasicNewsRecipe -from calibre.ptempfile import PersistentTemporaryFile -from calibre.web.feeds.news import BasicNewsRecipe -class AdvancedUserRecipe1275708473(BasicNewsRecipe): - title = u'Psychology Today' - _author__ = 'rty' - publisher = u'www.psychologytoday.com' - category = u'Psychology' - max_articles_per_feed = 100 - remove_javascript = True - use_embedded_content = False - no_stylesheets = True +class PsychologyToday(BasicNewsRecipe): + + title = 'Psychology Today' + __author__ = 'Rick Shang' + + description = 'This magazine takes information from the latest research in the field of psychology and makes it useful to people in their everyday lives. Its coverage encompasses self-improvement, relationships, the mind-body connection, health, family, the workplace and culture.' language = 'en' - temp_files = [] - articles_are_obfuscated = True - remove_tags = [ - dict(name='div', attrs={'class':['print-source_url','field-items','print-footer']}), - dict(name='span', attrs={'class':'print-footnote'}), - ] - remove_tags_before = dict(name='h1', attrs={'class':'print-title'}) - remove_tags_after = dict(name='div', attrs={'class':['field-items','print-footer']}) + category = 'news' + encoding = 'UTF-8' + keep_only_tags = [dict(attrs={'class':['print-title', 'print-submitted', 'print-content', 'print-footer', 'print-source_url', 'print-links']})] + no_javascript = True + no_stylesheets = True - feeds = [(u'Contents', u'http://www.psychologytoday.com/articles/index.rss')] - def get_article_url(self, article): - return article.get('link', None) + def parse_index(self): + articles = [] + soup = self.index_to_soup('http://www.psychologytoday.com/magazine') + + + #Go to the main body + div = soup.find('div',attrs={'id':'content-content'}) + #Find cover & date + cover_item = div.find('div', attrs={'class':'collections-header-image'}) + cover = cover_item.find('img',src=True) + self.cover_url = cover['src'] + date = self.tag_to_string(cover['title']) + self.timefmt = u' [%s]'%date + + articles = [] + for post in div.findAll('div', attrs={'class':'collections-node-feature-info'}): + title = self.tag_to_string(post.find('h2')) + author_item=post.find('div', attrs={'class':'collection-node-byline'}) + author = re.sub(r'.*by\s',"",self.tag_to_string(author_item).strip()) + title = title + u' (%s)'%author + article_page= self.index_to_soup('http://www.psychologytoday.com'+post.find('a', href=True)['href']) + print_page=article_page.find('li', attrs={'class':'print_html first'}) + url='http://www.psychologytoday.com'+print_page.find('a',href=True)['href'] + desc = self.tag_to_string(post.find('div', attrs={'class':'collection-node-description'})).strip() + self.log('Found article:', title) + self.log('\t', url) + self.log('\t', desc) + articles.append({'title':title, 'url':url, 'date':'','description':desc}) + + for post in div.findAll('div', attrs={'class':'collections-node-thumbnail-info'}): + title = self.tag_to_string(post.find('h2')) + author_item=post.find('div', attrs={'class':'collection-node-byline'}) + article_page= self.index_to_soup('http://www.psychologytoday.com'+post.find('a', href=True)['href']) + print_page=article_page.find('li', attrs={'class':'print_html first'}) + description = post.find('div', attrs={'class':'collection-node-description'}) + author = re.sub(r'.*by\s',"",self.tag_to_string(description.nextSibling).strip()) + desc = self.tag_to_string(description).strip() + url='http://www.psychologytoday.com'+print_page.find('a',href=True)['href'] + title = title + u' (%s)'%author + self.log('Found article:', title) + self.log('\t', url) + self.log('\t', desc) + articles.append({'title':title, 'url':url, 'date':'','description':desc}) + + for post in div.findAll('li', attrs={'class':['collection-item-list-odd','collection-item-list-even']}): + title = self.tag_to_string(post.find('h2')) + author_item=post.find('div', attrs={'class':'collection-node-byline'}) + author = re.sub(r'.*by\s',"",self.tag_to_string(author_item).strip()) + title = title + u' (%s)'%author + article_page= self.index_to_soup('http://www.psychologytoday.com'+post.find('a', href=True)['href']) + print_page=article_page.find('li', attrs={'class':'print_html first'}) + url='http://www.psychologytoday.com'+print_page.find('a',href=True)['href'] + desc = self.tag_to_string(post.find('div', attrs={'class':'collection-node-description'})).strip() + self.log('Found article:', title) + self.log('\t', url) + self.log('\t', desc) + articles.append({'title':title, 'url':url, 'date':'','description':desc}) + + return [('Current Issue', articles)] - def get_obfuscated_article(self, url): - br = self.get_browser() - br.open(url) - response = br.follow_link(url_regex = r'/print/[0-9]+', nr = 0) - html = response.read() - self.temp_files.append(PersistentTemporaryFile('_fa.html')) - self.temp_files[-1].write(html) - self.temp_files[-1].close() - return self.temp_files[-1].name - def get_cover_url(self): - index = 'http://www.psychologytoday.com/magazine/' - soup = self.index_to_soup(index) - for image in soup.findAll('img',{ "class" : "imagefield imagefield-field_magazine_cover" }): - return image['src'] + '.jpg' - return None diff --git a/recipes/smith.recipe b/recipes/smith.recipe index 8bf60a227a..3d6a95c494 100644 --- a/recipes/smith.recipe +++ b/recipes/smith.recipe @@ -1,61 +1,67 @@ import re -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup +from calibre.web.feeds.recipes import BasicNewsRecipe +from collections import OrderedDict -class SmithsonianMagazine(BasicNewsRecipe): - title = u'Smithsonian Magazine' - language = 'en' - __author__ = 'Krittika Goyal and TerminalVeracity' - oldest_article = 31#days - max_articles_per_feed = 50 - use_embedded_content = False - recursions = 1 - cover_url = 'http://sphotos.xx.fbcdn.net/hphotos-snc7/431147_10150602715983253_764313347_n.jpg' - match_regexps = ['&page=[2-9]$'] - preprocess_regexps = [ - (re.compile(r'for more of Smithsonian\'s coverage on history, science and nature.', re.DOTALL), lambda m: '') - ] - extra_css = """ - h1{font-size: large; margin: .2em 0} - h2{font-size: medium; margin: .2em 0} - h3{font-size: medium; margin: .2em 0} - #byLine{margin: .2em 0} - .articleImageCaptionwide{font-style: italic} - .wp-caption-text{font-style: italic} - img{display: block} - """ +class Smithsonian(BasicNewsRecipe): + title = 'Smithsonian Magazine' + __author__ = 'Rick Shang' - remove_stylesheets = True - remove_tags_after = dict(name='div', attrs={'class':['post','articlePaginationWrapper']}) - remove_tags = [ - dict(name='iframe'), - dict(name='div', attrs={'class':['article_sidebar_border','viewMorePhotos','addtoany_share_save_container','meta','social','OUTBRAIN','related-articles-inpage']}), - dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large','comment_section','article-related']}), - dict(name='ul', attrs={'class':'cat-breadcrumb col three last'}), - dict(name='h4', attrs={'id':'related-topics'}), - dict(name='table'), - dict(name='a', attrs={'href':['/subArticleBottomWeb','/subArticleTopWeb','/subArticleTopMag','/subArticleBottomMag']}), - dict(name='a', attrs={'name':'comments_shaded'}), - ] + description = 'This magazine chronicles the arts, environment, sciences and popular culture of the times. It is edited for modern, well-rounded individuals with diverse, general interests. With your order, you become a National Associate Member of the Smithsonian. Membership benefits include your subscription to Smithsonian magazine, a personalized membership card, discounts from the Smithsonian catalog, and more.' + language = 'en' + category = 'news' + encoding = 'UTF-8' + keep_only_tags = [dict(attrs={'id':['articleTitle', 'subHead', 'byLine', 'articleImage', 'article-text']})] + remove_tags = [dict(attrs={'class':['related-articles-inpage', 'viewMorePhotos']})] + no_javascript = True + no_stylesheets = True + def parse_index(self): + #Go to the issue + soup0 = self.index_to_soup('http://www.smithsonianmag.com/issue/archive/') + div = soup0.find('div',attrs={'id':'archives'}) + issue = div.find('ul',attrs={'class':'clear-both'}) + current_issue_url = issue.find('a', href=True)['href'] + soup = self.index_to_soup(current_issue_url) - feeds = [ -('History and Archeology', - 'http://feeds.feedburner.com/smithsonianmag/history-archaeology'), -('People and Places', - 'http://feeds.feedburner.com/smithsonianmag/people-places'), -('Science and Nature', - 'http://feeds.feedburner.com/smithsonianmag/science-nature'), -('Arts and Culture', - 'http://feeds.feedburner.com/smithsonianmag/arts-culture'), -('Travel', - 'http://feeds.feedburner.com/smithsonianmag/travel'), -] + #Go to the main body + div = soup.find ('div', attrs={'id':'content-inset'}) + + #Find date + date = re.sub('.*\:\W*', "", self.tag_to_string(div.find('h2')).strip()) + self.timefmt = u' [%s]'%date + + #Find cover + self.cover_url = div.find('img',src=True)['src'] + + feeds = OrderedDict() + section_title = '' + subsection_title = '' + for post in div.findAll('div', attrs={'class':['plainModule', 'departments plainModule']}): + articles = [] + prefix = '' + h3=post.find('h3') + if h3 is not None: + section_title = self.tag_to_string(h3) + else: + subsection=post.find('p',attrs={'class':'article-cat'}) + link=post.find('a',href=True) + url=link['href']+'?c=y&story=fullstory' + if subsection is not None: + subsection_title = self.tag_to_string(subsection) + prefix = (subsection_title+': ') + description=self.tag_to_string(post('p', limit=2)[1]).strip() + else: + description=self.tag_to_string(post.find('p')).strip() + desc=re.sub('\sBy\s.*', '', description, re.DOTALL) + author=re.sub('.*By\s', '', description, re.DOTALL) + title=prefix + self.tag_to_string(link).strip()+ u' (%s)'%author + articles.append({'title':title, 'url':url, 'description':desc, 'date':''}) + + if articles: + if section_title not in feeds: + feeds[section_title] = [] + feeds[section_title] += articles + ans = [(key, val) for key, val in feeds.iteritems()] + return ans - def preprocess_html(self, soup): - story = soup.find(name='div', attrs={'id':'article-body'}) - soup = BeautifulSoup('t') - body = soup.find(name='body') - body.insert(0, story) - return soup diff --git a/recipes/the_new_republic.recipe b/recipes/the_new_republic.recipe index 59ccef3607..649a8c46f3 100644 --- a/recipes/the_new_republic.recipe +++ b/recipes/the_new_republic.recipe @@ -1,45 +1,64 @@ -from calibre.web.feeds.news import BasicNewsRecipe +import re +from calibre.web.feeds.recipes import BasicNewsRecipe +from collections import OrderedDict -class The_New_Republic(BasicNewsRecipe): - title = 'The New Republic' - __author__ = 'cix3' +class TNR(BasicNewsRecipe): + + title = 'The New Republic' + __author__ = 'Rick Shang' + + description = 'The New Republic is a journal of opinion with an emphasis on politics and domestic and international affairs. It carries feature articles by staff and contributing editors. The second half of each issue is devoted to book and the arts, theater, motion pictures, music and art.' language = 'en' - description = 'Intelligent, stimulating and rigorous examination of American politics, foreign policy and culture' - timefmt = ' [%b %d, %Y]' - - oldest_article = 7 - max_articles_per_feed = 100 + category = 'news' + encoding = 'UTF-8' + remove_tags = [dict(attrs={'class':['print-logo','print-site_name','print-hr']})] + no_javascript = True no_stylesheets = True - remove_tags = [ - dict(name='div', attrs={'class':['print-logo', 'print-site_name', 'img-left', 'print-source_url']}), - dict(name='hr', attrs={'class':'print-hr'}), dict(name='img') - ] - feeds = [ - ('Politics', 'http://www.tnr.com/rss/articles/Politics'), - ('Books and Arts', 'http://www.tnr.com/rss/articles/Books-and-Arts'), - ('Economy', 'http://www.tnr.com/rss/articles/Economy'), - ('Environment and Energy', 'http://www.tnr.com/rss/articles/Environment-%2526-Energy'), - ('Health Care', 'http://www.tnr.com/rss/articles/Health-Care'), - ('Metro Policy', 'http://www.tnr.com/rss/articles/Metro-Policy'), - ('World', 'http://www.tnr.com/rss/articles/World'), - ('Film', 'http://www.tnr.com/rss/articles/Film'), - ('Books', 'http://www.tnr.com/rss/articles/books'), - ('The Book', 'http://www.tnr.com/rss/book'), - ('Jonathan Chait', 'http://www.tnr.com/rss/blogs/Jonathan-Chait'), - ('The Plank', 'http://www.tnr.com/rss/blogs/The-Plank'), - ('The Treatment', 'http://www.tnr.com/rss/blogs/The-Treatment'), - ('The Spine', 'http://www.tnr.com/rss/blogs/The-Spine'), - ('The Vine', 'http://www.tnr.com/rss/blogs/The-Vine'), - ('The Avenue', 'http://www.tnr.com/rss/blogs/The-Avenue'), - ('William Galston', 'http://www.tnr.com/rss/blogs/William-Galston'), - ('Simon Johnson', 'http://www.tnr.com/rss/blogs/Simon-Johnson'), - ('Ed Kilgore', 'http://www.tnr.com/rss/blogs/Ed-Kilgore'), - ('Damon Linker', 'http://www.tnr.com/rss/blogs/Damon-Linker'), - ('John McWhorter', 'http://www.tnr.com/rss/blogs/John-McWhorter') - ] + def parse_index(self): - def print_version(self, url): - return url.replace('http://www.tnr.com/', 'http://www.tnr.com/print/') + #Go to the issue + soup0 = self.index_to_soup('http://www.tnr.com/magazine-issues') + issue = soup0.find('div',attrs={'id':'current_issue'}) + #Find date + date = self.tag_to_string(issue.find('div',attrs={'class':'date'})).strip() + self.timefmt = u' [%s]'%date + + #Go to the main body + current_issue_url = 'http://www.tnr.com' + issue.find('a', href=True)['href'] + soup = self.index_to_soup(current_issue_url) + div = soup.find ('div', attrs={'class':'article_detail_body'}) + + + + #Find cover + self.cover_url = div.find('img',src=True)['src'] + + feeds = OrderedDict() + section_title = '' + subsection_title = '' + for post in div.findAll('p'): + articles = [] + em=post.find('em') + b=post.find('b') + a=post.find('a',href=True) + if em is not None: + section_title = self.tag_to_string(em).strip() + subsection_title = '' + elif b is not None: + subsection_title=self.tag_to_string(b).strip() + elif a is not None: + prefix = (subsection_title+': ') if subsection_title else '' + url=re.sub('www.tnr.com','www.tnr.com/print', a['href']) + author=re.sub('.*by\s', '', self.tag_to_string(post), re.DOTALL) + title=prefix + self.tag_to_string(a).strip()+ u' (%s)'%author + articles.append({'title':title, 'url':url, 'description':'', 'date':''}) + + if articles: + if section_title not in feeds: + feeds[section_title] = [] + feeds[section_title] += articles + ans = [(key, val) for key, val in feeds.iteritems()] + return ans diff --git a/recipes/wnp.recipe b/recipes/wnp.recipe index ee87112437..ec0d012733 100644 --- a/recipes/wnp.recipe +++ b/recipes/wnp.recipe @@ -1,7 +1,7 @@ from calibre.web.feeds.news import BasicNewsRecipe import re -class AdvancedUserRecipe1312886443(BasicNewsRecipe): +class WNP(BasicNewsRecipe): title = u'WNP' cover_url= 'http://k.wnp.pl/images/wnpLogo.gif' __author__ = 'fenuks' @@ -12,7 +12,7 @@ class AdvancedUserRecipe1312886443(BasicNewsRecipe): oldest_article = 8 max_articles_per_feed = 100 no_stylesheets= True - remove_tags=[dict(attrs={'class':'printF'})] + remove_tags=[dict(attrs={'class':['printF', 'border3B2 clearfix', 'articleMenu clearfix']})] feeds = [(u'Wiadomości gospodarcze', u'http://www.wnp.pl/rss/serwis_rss.xml'), (u'Serwis Energetyka - Gaz', u'http://www.wnp.pl/rss/serwis_rss_1.xml'), (u'Serwis Nafta - Chemia', u'http://www.wnp.pl/rss/serwis_rss_2.xml'), diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index ed0bce537b..1b8af62d52 100644 Binary files a/resources/compiled_coffeescript.zip and b/resources/compiled_coffeescript.zip differ diff --git a/src/calibre/constants.py b/src/calibre/constants.py index ecad6b5cc2..33feddefee 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -4,7 +4,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = u'calibre' -numeric_version = (0, 8, 61) +numeric_version = (0, 8, 62) __version__ = u'.'.join(map(unicode, numeric_version)) __author__ = u"Kovid Goyal " diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py index ee8656f0ca..50bceb4def 100644 --- a/src/calibre/customize/conversion.py +++ b/src/calibre/customize/conversion.py @@ -91,6 +91,37 @@ class DummyReporter(object): def __call__(self, percent, msg=''): pass +def gui_configuration_widget(name, parent, get_option_by_name, + get_option_help, db, book_id, for_output=True): + import importlib + + def widget_factory(cls): + return cls(parent, get_option_by_name, + get_option_help, db, book_id) + + if for_output: + try: + output_widget = importlib.import_module( + 'calibre.gui2.convert.'+name) + pw = output_widget.PluginWidget + pw.ICON = I('back.png') + pw.HELP = _('Options specific to the output format.') + return widget_factory(pw) + except ImportError: + pass + else: + try: + input_widget = importlib.import_module( + 'calibre.gui2.convert.'+name) + pw = input_widget.PluginWidget + pw.ICON = I('forward.png') + pw.HELP = _('Options specific to the input format.') + return widget_factory(pw) + except ImportError: + pass + return None + + class InputFormatPlugin(Plugin): ''' InputFormatPlugins are responsible for converting a document into @@ -225,6 +256,17 @@ class InputFormatPlugin(Plugin): ''' pass + def gui_configuration_widget(self, parent, get_option_by_name, + get_option_help, db, book_id): + ''' + Called to create the widget used for configuring this plugin in the + calibre GUI. The widget must be an instance of the PluginWidget class. + See the builting input plugins for examples. + ''' + name = self.name.lower().replace(' ', '_') + return gui_configuration_widget(name, parent, get_option_by_name, + get_option_help, db, book_id, for_output=False) + class OutputFormatPlugin(Plugin): ''' @@ -308,4 +350,16 @@ class OutputFormatPlugin(Plugin): ''' pass + def gui_configuration_widget(self, parent, get_option_by_name, + get_option_help, db, book_id): + ''' + Called to create the widget used for configuring this plugin in the + calibre GUI. The widget must be an instance of the PluginWidget class. + See the builtin output plugins for examples. + ''' + name = self.name.lower().replace(' ', '_') + return gui_configuration_widget(name, parent, get_option_by_name, + get_option_help, db, book_id, for_output=True) + + diff --git a/src/calibre/ebooks/conversion/plugins/epub_input.py b/src/calibre/ebooks/conversion/plugins/epub_input.py index 27263a2690..f0af2d28c5 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_input.py +++ b/src/calibre/ebooks/conversion/plugins/epub_input.py @@ -198,11 +198,13 @@ class EPUBInput(InputFormatPlugin): ('application/vnd.adobe-page-template+xml','application/text'): not_for_spine.add(id_) + seen = set() for x in list(opf.iterspine()): ref = x.get('idref', None) - if ref is None or ref in not_for_spine: + if not ref or ref in not_for_spine or ref in seen: x.getparent().remove(x) continue + seen.add(ref) if len(list(opf.iterspine())) == 0: raise ValueError('No valid entries in the spine of this EPUB') diff --git a/src/calibre/ebooks/metadata/odt.py b/src/calibre/ebooks/metadata/odt.py index bf30dfd5f7..a4371a4506 100644 --- a/src/calibre/ebooks/metadata/odt.py +++ b/src/calibre/ebooks/metadata/odt.py @@ -1,5 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +# # Copyright (C) 2006 Søren Roug, European Environment Agency # # This is free software. You may redistribute it under the terms @@ -17,12 +19,20 @@ # # Contributor(s): # +from __future__ import division + import zipfile, re import xml.sax.saxutils from cStringIO import StringIO from odf.namespaces import OFFICENS, DCNS, METANS -from calibre.ebooks.metadata import MetaInformation, string_to_authors +from odf.opendocument import load as odLoad +from odf.draw import Image as odImage, Frame as odFrame + +from calibre.ebooks.metadata import MetaInformation, string_to_authors, check_isbn +from calibre.utils.magick.draw import identify_data +from calibre.utils.date import parse_date +from calibre.utils.localization import canonicalize_lang whitespace = re.compile(r'\s+') @@ -125,6 +135,10 @@ class odfmetaparser(xml.sax.saxutils.XMLGenerator): else: texttag = self._tag self.seenfields[texttag] = self.data() + # OpenOffice has the habit to capitalize custom properties, so we add a + # lowercase version for easy access + if texttag[:4].lower() == u'opf.': + self.seenfields[texttag.lower()] = self.data() if field in self.deletefields: self.output.dowrite = True @@ -141,7 +155,7 @@ class odfmetaparser(xml.sax.saxutils.XMLGenerator): def data(self): return normalize(''.join(self._data)) -def get_metadata(stream): +def get_metadata(stream, extract_cover=True): zin = zipfile.ZipFile(stream, 'r') odfs = odfmetaparser() parser = xml.sax.make_parser() @@ -162,7 +176,82 @@ def get_metadata(stream): if data.has_key('language'): mi.language = data['language'] if data.get('keywords', ''): - mi.tags = data['keywords'].split(',') + mi.tags = [x.strip() for x in data['keywords'].split(',') if x.strip()] + opfmeta = False # we need this later for the cover + opfnocover = False + if data.get('opf.metadata','') == 'true': + # custom metadata contains OPF information + opfmeta = True + if data.get('opf.titlesort', ''): + mi.title_sort = data['opf.titlesort'] + if data.get('opf.authors', ''): + mi.authors = string_to_authors(data['opf.authors']) + if data.get('opf.authorsort', ''): + mi.author_sort = data['opf.authorsort'] + if data.get('opf.isbn', ''): + isbn = check_isbn(data['opf.isbn']) + if isbn is not None: + mi.isbn = isbn + if data.get('opf.publisher', ''): + mi.publisher = data['opf.publisher'] + if data.get('opf.pubdate', ''): + mi.pubdate = parse_date(data['opf.pubdate'], assume_utc=True) + if data.get('opf.language', ''): + cl = canonicalize_lang(data['opf.language']) + if cl: + mi.languages = [cl] + opfnocover = data.get('opf.nocover', 'false') == 'true' + if not opfnocover: + try: + read_cover(stream, zin, mi, opfmeta, extract_cover) + except: + pass # Do not let an error reading the cover prevent reading other data return mi +def read_cover(stream, zin, mi, opfmeta, extract_cover): + # search for an draw:image in a draw:frame with the name 'opf.cover' + # if opf.metadata prop is false, just use the first image that + # has a proper size (borrowed from docx) + otext = odLoad(stream) + cover_href = None + cover_data = None + # check that it's really a ODT + if otext.mimetype == u'application/vnd.oasis.opendocument.text': + for elem in otext.text.getElementsByType(odFrame): + img = elem.getElementsByType(odImage) + if len(img) > 0: # there should be only one + i_href = img[0].getAttribute('href') + try: + raw = zin.read(i_href) + except KeyError: + continue + try: + width, height, fmt = identify_data(raw) + except: + continue + else: + continue + if opfmeta and elem.getAttribute('name').lower() == u'opf.cover': + cover_href = i_href + cover_data = (fmt, raw) + break + if cover_href is None and 0.8 <= height/width <= 1.8 and height*width >= 12000: + cover_href = i_href + cover_data = (fmt, raw) + if not opfmeta: + break + + if cover_href is not None: + mi.cover = cover_href + if extract_cover: + if not cover_data: + raw = zin.read(cover_href) + try: + width, height, fmt = identify_data(raw) + except: + pass + else: + cover_data = (fmt, raw) + mi.cover_data = cover_data + diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index debd69b281..d132fe15d0 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -286,15 +286,17 @@ class Spine(ResourceCollection): # {{{ @staticmethod def from_opf_spine_element(itemrefs, manifest): s = Spine(manifest) + seen = set() for itemref in itemrefs: idref = itemref.get('idref', None) if idref is not None: path = s.manifest.path_for_id(idref) - if path: + if path and path not in seen: r = Spine.Item(lambda x:idref, path, is_path=True) r.is_linear = itemref.get('linear', 'yes') == 'yes' r.idref = idref s.append(r) + seen.add(path) return s @staticmethod diff --git a/src/calibre/ebooks/mobi/writer2/serializer.py b/src/calibre/ebooks/mobi/writer2/serializer.py index 2dda657a93..5251bf934f 100644 --- a/src/calibre/ebooks/mobi/writer2/serializer.py +++ b/src/calibre/ebooks/mobi/writer2/serializer.py @@ -12,7 +12,7 @@ import re from calibre.ebooks.oeb.base import (OEB_DOCS, XHTML, XHTML_NS, XML_NS, namespace, prefixname, urlnormalize) from calibre.ebooks.mobi.mobiml import MBP_NS -from calibre.ebooks.mobi.utils import is_guide_ref_start +from calibre.ebooks.mobi.utils import is_guide_ref_start, utf8_text from collections import defaultdict from urlparse import urldefrag @@ -355,7 +355,7 @@ class Serializer(object): text = text.replace(u'\u00AD', '') # Soft-hyphen if quot: text = text.replace('"', '"') - self.buf.write(text.encode('utf-8')) + self.buf.write(utf8_text(text)) def fixup_links(self): ''' diff --git a/src/calibre/ebooks/odt/input.py b/src/calibre/ebooks/odt/input.py index 14e1ff5892..1a70335a13 100644 --- a/src/calibre/ebooks/odt/input.py +++ b/src/calibre/ebooks/odt/input.py @@ -142,7 +142,7 @@ class Extract(ODF2XHTML): from calibre.utils.zipfile import ZipFile from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.opf2 import OPFCreator - + from calibre.customize.ui import quick_metadata if not os.path.exists(odir): os.makedirs(odir) @@ -163,7 +163,10 @@ class Extract(ODF2XHTML): zf = ZipFile(stream, 'r') self.extract_pictures(zf) stream.seek(0) - mi = get_metadata(stream, 'odt') + with quick_metadata: + # We dont want the cover, as it will lead to a duplicated image + # if no external cover is specified. + mi = get_metadata(stream, 'odt') if not mi.title: mi.title = _('Unknown') if not mi.authors: diff --git a/src/calibre/ebooks/oeb/display/paged.coffee b/src/calibre/ebooks/oeb/display/paged.coffee index c2a09b5587..de6645ce88 100644 --- a/src/calibre/ebooks/oeb/display/paged.coffee +++ b/src/calibre/ebooks/oeb/display/paged.coffee @@ -26,6 +26,7 @@ class PagedDisplay this.current_margin_side = 0 this.is_full_screen_layout = false this.max_col_width = -1 + this.current_page_height = null this.document_margins = null this.use_document_margins = false @@ -74,25 +75,12 @@ class PagedDisplay # start_time = new Date().getTime() body_style = window.getComputedStyle(document.body) bs = document.body.style - # When laying body out in columns, webkit bleeds the top margin of the - # first block element out above the columns, leading to an extra top - # margin for the page. We compensate for that here. Computing the - # boundingrect of body is very expensive with column layout, so we do - # it before the column layout is applied. first_layout = false if not this.in_paged_mode - bs.setProperty('margin-top', '0px') - extra_margin = document.body.getBoundingClientRect().top - if extra_margin <= this.margin_top - extra_margin = 0 - margin_top = (this.margin_top - extra_margin) + 'px' # Check if the current document is a full screen layout like # cover, if so we treat it specially. single_screen = (document.body.scrollWidth < window.innerWidth + 25 and document.body.scrollHeight < window.innerHeight + 25) first_layout = true - else - # resize event - margin_top = body_style.marginTop ww = window.innerWidth @@ -116,16 +104,23 @@ class PagedDisplay 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 + this.current_page_height = window.innerHeight - this.margin_top - this.margin_bottom fgcolor = body_style.getPropertyValue('color') bs.setProperty('-webkit-column-gap', (2*sm)+'px') bs.setProperty('-webkit-column-width', col_width+'px') bs.setProperty('-webkit-column-rule-color', fgcolor) + + # Without this, webkit bleeds the margin of the first block(s) of body + # above the columns, which causes them to effectively be added to the + # page margins (the margin collapse algorithm) + bs.setProperty('-webkit-margin-collapse', 'separate') + bs.setProperty('overflow', 'visible') bs.setProperty('height', (window.innerHeight - this.margin_top - this.margin_bottom) + 'px') bs.setProperty('width', (window.innerWidth - 2*sm)+'px') - bs.setProperty('margin-top', margin_top) + bs.setProperty('margin-top', this.margin_top + 'px') bs.setProperty('margin-bottom', this.margin_bottom+'px') bs.setProperty('margin-left', sm+'px') bs.setProperty('margin-right', sm+'px') @@ -167,9 +162,15 @@ class PagedDisplay # that this method use getBoundingClientRect() which means it will # force a relayout if the render tree is dirty. images = [] + vimages = [] + maxh = this.current_page_height for img in document.getElementsByTagName('img') previously_limited = calibre_utils.retrieve(img, 'width-limited', false) + data = calibre_utils.retrieve(img, 'img-data', null) br = img.getBoundingClientRect() + if data == null + data = {'left':br.left, 'right':br.right, 'height':br.height, 'display': img.style.display} + calibre_utils.store(img, 'img-data', data) left = calibre_utils.viewport_to_document(br.left, 0, doc=img.ownerDocument)[0] col = this.column_at(left) * this.page_width rleft = left - col - this.current_margin_side @@ -178,23 +179,28 @@ class PagedDisplay col_width = this.page_width - 2*this.current_margin_side if previously_limited or rright > col_width images.push([img, col_width - rleft]) + previously_limited = calibre_utils.retrieve(img, 'height-limited', false) + if previously_limited or br.height > maxh + vimages.push(img) + if previously_limited + img.style.setProperty('-webkit-column-break-before', 'auto') + img.style.setProperty('display', data.display) + img.style.setProperty('-webkit-column-break-inside', 'avoid') for [img, max_width] in images img.style.setProperty('max-width', max_width+'px') calibre_utils.store(img, 'width-limited', true) - check_top_margin: () -> - # This is needed to handle the case when a descendant of body specifies - # a top margin as a percentage, which messes up the top margin - # calculations above - tm = document.body.getBoundingClientRect().top - if tm != this.margin_top - document.body.style.setProperty('margin-top', '0px') - tm = document.body.getBoundingClientRect().top - if tm <= this.margin_top - tm = 0 - m = this.margin_top - tm - document.body.style.setProperty('margin-top', m+'px') + for img in vimages + data = calibre_utils.retrieve(img, 'img-data', null) + img.style.setProperty('-webkit-column-break-before', 'always') + img.style.setProperty('max-height', maxh+'px') + if data.height > maxh + # This is needed to force the image onto a new page, without + # it, the webkit algorithm may still decide to split the image + # by keeping it part of its parent block + img.style.setProperty('display', 'block') + calibre_utils.store(img, 'height-limited', true) scroll_to_pos: (frac) -> # Scroll to the position represented by frac (number between 0 and 1) diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 2b3cfd7a4a..7e179cef48 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -202,7 +202,6 @@ class PDFWriter(QObject): # {{{ paged_display.set_geometry(1, 0, 0, 0); paged_display.layout(); paged_display.fit_images(); - paged_display.check_top_margin(); ''') mf = self.view.page().mainFrame() while True: @@ -221,7 +220,7 @@ class PDFWriter(QObject): # {{{ self.tmp_path = PersistentTemporaryDirectory('_pdf_output_parts') def insert_cover(self): - if self.cover_data is None: + if not isinstance(self.cover_data, bytes): return item_path = os.path.join(self.tmp_path, 'cover.pdf') printer = get_pdf_printer(self.opts, output_file_name=item_path, diff --git a/src/calibre/gui2/actions/plugin_updates.py b/src/calibre/gui2/actions/plugin_updates.py index d5da12df71..6973e80d8a 100644 --- a/src/calibre/gui2/actions/plugin_updates.py +++ b/src/calibre/gui2/actions/plugin_updates.py @@ -31,3 +31,5 @@ class PluginUpdaterAction(InterfaceAction): d = PluginUpdaterDialog(self.gui, initial_filter=initial_filter) d.exec_() + if d.do_restart: + self.gui.quit(restart=True) diff --git a/src/calibre/gui2/actions/preferences.py b/src/calibre/gui2/actions/preferences.py index cd9de36fce..bbb86b573e 100644 --- a/src/calibre/gui2/actions/preferences.py +++ b/src/calibre/gui2/actions/preferences.py @@ -45,6 +45,8 @@ class PreferencesAction(InterfaceAction): d = PluginUpdaterDialog(self.gui, initial_filter=FILTER_NOT_INSTALLED) d.exec_() + if d.do_restart: + self.gui.quit(restart=True) def do_config(self, checked=False, initial_plugin=None, close_after_initial=False): diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 1d5e914d5f..10bcbf6218 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en' import re, os from lxml import html +import sip from PyQt4.Qt import (QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit, QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, @@ -42,6 +43,7 @@ class PageAction(QAction): # {{{ self.page_action.trigger() def update_state(self, *args): + if sip.isdeleted(self) or sip.isdeleted(self.page_action): return if self.isCheckable(): self.setChecked(self.page_action.isChecked()) self.setEnabled(self.page_action.isEnabled()) diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index 5324a83865..3a65a4617e 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -4,7 +4,7 @@ __license__ = 'GPL 3' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' -import shutil, importlib +import shutil from PyQt4.Qt import QString, SIGNAL @@ -86,17 +86,9 @@ class BulkConfig(Config): sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) - output_widget = None - name = self.plumber.output_plugin.name.lower().replace(' ', '_') - try: - output_widget = importlib.import_module( - 'calibre.gui2.convert.'+name) - pw = output_widget.PluginWidget - pw.ICON = I('back.png') - pw.HELP = _('Options specific to the output format.') - output_widget = widget_factory(pw) - except ImportError: - pass + output_widget = self.plumber.output_plugin.gui_configuration_widget( + self.stack, self.plumber.get_option_by_name, + self.plumber.get_option_help, self.db) while True: c = self.stack.currentWidget() diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 9160c820bd..4d13ce371b 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import cPickle, shutil, importlib +import cPickle, shutil from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont @@ -187,29 +187,12 @@ class Config(ResizableDialog, Ui_Dialog): toc = widget_factory(TOCWidget) debug = widget_factory(DebugWidget) - output_widget = None - name = self.plumber.output_plugin.name.lower().replace(' ', '_') - try: - output_widget = importlib.import_module( - 'calibre.gui2.convert.'+name) - pw = output_widget.PluginWidget - pw.ICON = I('back.png') - pw.HELP = _('Options specific to the output format.') - output_widget = widget_factory(pw) - except ImportError: - pass - input_widget = None - name = self.plumber.input_plugin.name.lower().replace(' ', '_') - try: - input_widget = importlib.import_module( - 'calibre.gui2.convert.'+name) - pw = input_widget.PluginWidget - pw.ICON = I('forward.png') - pw.HELP = _('Options specific to the input format.') - input_widget = widget_factory(pw) - except ImportError: - pass - + output_widget = self.plumber.output_plugin.gui_configuration_widget( + self.stack, self.plumber.get_option_by_name, + self.plumber.get_option_help, self.db, self.book_id) + input_widget = self.plumber.input_plugin.gui_configuration_widget( + self.stack, self.plumber.get_option_by_name, + self.plumber.get_option_help, self.db, self.book_id) while True: c = self.stack.currentWidget() if not c: break diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 748d917146..d3a5df7269 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -30,7 +30,6 @@ from calibre.constants import DEBUG from calibre.utils.config import prefs, tweaks from calibre.utils.magick.draw import thumbnail from calibre.library.save_to_disk import find_plugboard -from calibre.gui2 import is_gui_thread # }}} class DeviceJob(BaseJob): # {{{ diff --git a/src/calibre/gui2/dialogs/message_box.py b/src/calibre/gui2/dialogs/message_box.py index a107bf985b..9979350a42 100644 --- a/src/calibre/gui2/dialogs/message_box.py +++ b/src/calibre/gui2/dialogs/message_box.py @@ -9,7 +9,7 @@ import sys from PyQt4.Qt import (QDialog, QIcon, QApplication, QSize, QKeySequence, QAction, Qt, QTextBrowser, QDialogButtonBox, QVBoxLayout, QGridLayout, - QLabel, QPlainTextEdit, QTextDocument) + QLabel, QPlainTextEdit, QTextDocument, QCheckBox, pyqtSignal) from calibre.constants import __version__, isfrozen from calibre.gui2.dialogs.message_box_ui import Ui_Dialog @@ -270,21 +270,23 @@ class ErrorNotification(MessageBox): # {{{ class JobError(QDialog): # {{{ WIDTH = 600 + do_pop = pyqtSignal() - def __init__(self, gui): - QDialog.__init__(self, gui) + def __init__(self, parent): + QDialog.__init__(self, parent) self.setAttribute(Qt.WA_DeleteOnClose, False) - self.gui = gui self.queue = [] + self.do_pop.connect(self.pop, type=Qt.QueuedConnection) self._layout = l = QGridLayout() self.setLayout(l) self.icon = QIcon(I('dialog_error.png')) self.setWindowIcon(self.icon) self.icon_label = QLabel() - self.icon_label.setPixmap(self.icon.pixmap(128, 128)) - self.icon_label.setMaximumSize(QSize(128, 128)) + self.icon_label.setPixmap(self.icon.pixmap(68, 68)) + self.icon_label.setMaximumSize(QSize(68, 68)) self.msg_label = QLabel('

 ') + self.msg_label.setStyleSheet('QLabel { margin-top: 1ex; }') self.msg_label.setWordWrap(True) self.msg_label.setTextFormat(Qt.RichText) self.det_msg = QPlainTextEdit(self) @@ -302,15 +304,23 @@ class JobError(QDialog): # {{{ self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) + self.suppress = QCheckBox(self) l.addWidget(self.icon_label, 0, 0, 1, 1) - l.addWidget(self.msg_label, 0, 1, 1, 1, Qt.AlignLeft|Qt.AlignTop) - l.addWidget(self.det_msg, 1, 0, 1, 2) - - l.addWidget(self.bb, 2, 0, 1, 2, Qt.AlignRight|Qt.AlignBottom) + l.addWidget(self.msg_label, 0, 1, 1, 1) + l.addWidget(self.det_msg, 1, 0, 1, 2) + l.addWidget(self.suppress, 2, 0, 1, 2, Qt.AlignLeft|Qt.AlignBottom) + l.addWidget(self.bb, 3, 0, 1, 2, Qt.AlignRight|Qt.AlignBottom) + l.setColumnStretch(1, 100) self.setModal(False) - self.base_height = max(200, self.sizeHint().height() + 20) + self.suppress.setVisible(False) + self.do_resize() + + def update_suppress_state(self): + self.suppress.setText(_( + 'Hide the remaining %d error messages'%len(self.queue))) + self.suppress.setVisible(len(self.queue) > 3) self.do_resize() def copy_to_clipboard(self, *args): @@ -332,9 +342,11 @@ class JobError(QDialog): # {{{ self.do_resize() def do_resize(self): - h = self.base_height - if self.det_msg.isVisible(): - h += 250 + h = self.sizeHint().height() + self.setMinimumHeight(0) # Needed as this gets set if det_msg is shown + # Needed otherwise re-showing the box after showing det_msg causes the box + # to not reduce in height + self.setMaximumHeight(h) self.resize(QSize(self.WIDTH, h)) def showEvent(self, ev): @@ -342,16 +354,50 @@ class JobError(QDialog): # {{{ self.bb.button(self.bb.Close).setFocus(Qt.OtherFocusReason) return ret + def show_error(self, title, msg, det_msg=u''): + self.queue.append((title, msg, det_msg)) + self.update_suppress_state() + self.pop() + + def pop(self): + if not self.queue or self.isVisible(): return + title, msg, det_msg = self.queue.pop(0) + self.setWindowTitle(title) + self.msg_label.setText(msg) + self.det_msg.setPlainText(det_msg) + self.det_msg.setVisible(False) + self.det_msg_toggle.setText(self.show_det_msg) + self.det_msg_toggle.setVisible(True) + self.suppress.setChecked(False) + self.update_suppress_state() + if not det_msg: + self.det_msg_toggle.setVisible(False) + self.do_resize() + self.show() + + def done(self, r): + if self.suppress.isChecked(): + self.queue = [] + QDialog.done(self, r) + self.do_pop.emit() + # }}} if __name__ == '__main__': app = QApplication([]) - from calibre.gui2.preferences import init_gui - gui = init_gui() - d = JobError(gui) - d.show() + d = JobError(None) + d.show_error('test title', 'some long meaningless test message', 'det msg') + d.show_error('test title', 'some long meaningless test message', 'det msg') + d.show_error('test title', 'some long meaningless test message', 'det msg') + d.show_error('test title', 'some long meaningless test message', 'det msg') + d.show_error('test title', 'some long meaningless test message', 'det msg') + d.show_error('test title', 'some long meaningless test message', 'det msg') + app.setQuitOnLastWindowClosed(False) + def checkd(): + if not d.queue: + app.quit() + app.lastWindowClosed.connect(checkd) app.exec_() - gui.shutdown() # if __name__ == '__main__': # app = QApplication([]) diff --git a/src/calibre/gui2/dialogs/plugin_updater.py b/src/calibre/gui2/dialogs/plugin_updater.py index 2a90f63995..4e56db9016 100644 --- a/src/calibre/gui2/dialogs/plugin_updater.py +++ b/src/calibre/gui2/dialogs/plugin_updater.py @@ -456,6 +456,7 @@ class PluginUpdaterDialog(SizePersistedDialog): self.gui = gui self.forum_link = None self.model = None + self.do_restart = False self._initialize_controls() self._create_context_menu() @@ -720,6 +721,7 @@ class PluginUpdaterDialog(SizePersistedDialog): prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) + do_restart = False try: try: plugin = add_plugin(zip_path) @@ -731,11 +733,21 @@ class PluginUpdaterDialog(SizePersistedDialog): widget.gui = self.gui widget.check_for_add_to_toolbars(plugin) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) - info_dialog(self.gui, _('Success'), + d = info_dialog(self.gui, _('Success'), _('Plugin {0} successfully installed under ' ' {1} plugins. You may have to restart calibre ' 'for the plugin to take effect.').format(plugin.name, plugin.type), - show=True, show_copy_button=False) + show_copy_button=False) + b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole) + b.setIcon(QIcon(I('lt.png'))) + d.do_restart = False + def rf(): + d.do_restart = True + b.clicked.connect(rf) + d.set_details('') + d.exec_() + b.clicked.disconnect() + do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet @@ -762,6 +774,9 @@ class PluginUpdaterDialog(SizePersistedDialog): else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) + if do_restart: + self.do_restart = True + self.accept() def _history_clicked(self): display_plugin = self._selected_display_plugin() diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index cf63d150e6..86d18e6145 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -12,6 +12,7 @@ from calibre.utils.icu import sort_key from calibre.utils.config import tweaks box_values = {} +last_matchkind = CONTAINS_MATCH class SearchDialog(QDialog, Ui_Dialog): @@ -57,6 +58,9 @@ class SearchDialog(QDialog, Ui_Dialog): current_tab = gprefs.get('advanced search dialog current tab', 0) self.tabWidget.setCurrentIndex(current_tab) + if current_tab == 1: + self.matchkind.setCurrentIndex(last_matchkind) + self.tabWidget.currentChanged[int].connect(self.tab_changed) self.tab_changed(current_tab) @@ -173,7 +177,9 @@ class SearchDialog(QDialog, Ui_Dialog): general_index = unicode(self.general_combo.currentText()) self.box_last_values['general_index'] = general_index global box_values + global last_matchkind box_values = copy.deepcopy(self.box_last_values) + last_matchkind = mk if general: ans.append(unicode(self.general_combo.currentText()) + ':"' + self.mc + general + '"') diff --git a/src/calibre/gui2/dialogs/smartdevice.py b/src/calibre/gui2/dialogs/smartdevice.py index 15b40d1077..ebdacc7781 100644 --- a/src/calibre/gui2/dialogs/smartdevice.py +++ b/src/calibre/gui2/dialogs/smartdevice.py @@ -1,11 +1,10 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import re + from PyQt4.QtGui import QDialog, QLineEdit from PyQt4.QtCore import SIGNAL, Qt from calibre.gui2.dialogs.smartdevice_ui import Ui_Dialog -from calibre.gui2 import dynamic class SmartdeviceDialog(QDialog, Ui_Dialog): diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py index d724e49b46..733d5b2255 100644 --- a/src/calibre/gui2/preferences/__init__.py +++ b/src/calibre/gui2/preferences/__init__.py @@ -236,6 +236,7 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface): ''' changed_signal = pyqtSignal() + restart_now = pyqtSignal() supports_restoring_to_defaults = True restart_critical = False diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index 98b5f168b3..4c879c7883 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -290,6 +290,7 @@ class Preferences(QMainWindow): self.apply_action.setEnabled(False) self.showing_widget.changed_signal.connect(lambda : self.apply_action.setEnabled(True)) + self.showing_widget.restart_now.connect(self.restart_now) self.restore_action.setEnabled(self.showing_widget.supports_restoring_to_defaults) tt = self.showing_widget.restore_defaults_desc if not self.restore_action.isEnabled(): @@ -319,6 +320,15 @@ class Preferences(QMainWindow): elif self.stack.currentIndex() == 0: self.close() + def restart_now(self): + try: + self.showing_widget.commit() + except AbortCommit: + return + self.hide_plugin() + self.close() + self.gui.quit(restart=True) + def commit(self, *args): try: must_restart = self.showing_widget.commit() diff --git a/src/calibre/gui2/preferences/plugins.py b/src/calibre/gui2/preferences/plugins.py index 2d89b187bf..1a6e58d6bd 100644 --- a/src/calibre/gui2/preferences/plugins.py +++ b/src/calibre/gui2/preferences/plugins.py @@ -384,6 +384,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self._plugin_model.populate() self._plugin_model.reset() self.changed_signal.emit() + if d.do_restart: + self.restart_now.emit() def reload_store_plugins(self): self.gui.load_store_plugins() diff --git a/src/calibre/gui2/store/stores/sony_plugin.py b/src/calibre/gui2/store/stores/sony_plugin.py index 7022287794..2ad344e82c 100644 --- a/src/calibre/gui2/store/stores/sony_plugin.py +++ b/src/calibre/gui2/store/stores/sony_plugin.py @@ -32,6 +32,8 @@ class SonyStore(BasicStoreConfig, StorePlugin): d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) d.exec_() + else: + open_url(QUrl('http://ebookstore.sony.com')) def search(self, query, max_results=10, timeout=60): url = 'http://ebookstore.sony.com/search?keyword=%s'%urllib.quote_plus( diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 3490c63215..f07fb0d211 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -44,6 +44,7 @@ from calibre.gui2.keyboard import Manager from calibre.gui2.auto_add import AutoAdder from calibre.library.sqlite import sqlite, DatabaseException from calibre.gui2.proceed import ProceedQuestion +from calibre.gui2.dialogs.message_box import JobError class Listener(Thread): # {{{ @@ -111,6 +112,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.proceed_requested.connect(self.do_proceed, type=Qt.QueuedConnection) self.proceed_question = ProceedQuestion(self) + self.job_error_dialog = JobError(self) self.keyboard = Manager(self) _gui = self self.opts = opts @@ -690,12 +692,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ except: pass if not minz: - d = error_dialog(self, dialog_title, + self.job_error_dialog.show_error(dialog_title, _('Failed')+': '+unicode(job.description), det_msg=job.details) - d.setModal(False) - d.show() - self._modeless_dialogs.append(d) def read_settings(self): geometry = config['main_window_geometry'] diff --git a/src/calibre/gui2/update.py b/src/calibre/gui2/update.py index 3ff977f554..42d41e6d72 100644 --- a/src/calibre/gui2/update.py +++ b/src/calibre/gui2/update.py @@ -175,6 +175,8 @@ class UpdateMixin(object): d = PluginUpdaterDialog(self, initial_filter=FILTER_UPDATE_AVAILABLE) d.exec_() + if d.do_restart: + self.quit(restart=True) def plugin_update_found(self, number_of_updates): # Change the plugin icon to indicate there are updates available diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index dac9a05113..2b494d4ecd 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -243,7 +243,6 @@ class Document(QWebPage): # {{{ sz.setWidth(scroll_width+side_margin) self.setPreferredContentsSize(sz) self.javascript('window.paged_display.fit_images()') - self.javascript('window.paged_display.check_top_margin()') @property def column_boundaries(self): diff --git a/src/calibre/gui2/viewer/printing.py b/src/calibre/gui2/viewer/printing.py index 476cd949f5..f860edb12c 100644 --- a/src/calibre/gui2/viewer/printing.py +++ b/src/calibre/gui2/viewer/printing.py @@ -71,7 +71,6 @@ class Printing(QObject): paged_display.set_geometry(1, 0, 0, 0); paged_display.layout(); paged_display.fit_images(); - paged_display.check_top_margin(); ''') while True: diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index 73a782ab13..d5f6847a15 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: calibre 0.8.61\n" -"POT-Creation-Date: 2012-07-20 09:12+IST\n" -"PO-Revision-Date: 2012-07-20 09:12+IST\n" +"Project-Id-Version: calibre 0.8.62\n" +"POT-Creation-Date: 2012-07-27 11:37+IST\n" +"PO-Revision-Date: 2012-07-27 11:37+IST\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -24,8 +24,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/db/cache.py:106 #: /home/kovid/work/calibre/src/calibre/db/cache.py:109 #: /home/kovid/work/calibre/src/calibre/db/cache.py:120 -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:341 -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:342 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:343 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:344 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:100 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:101 #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74 @@ -36,8 +36,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:661 -#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:463 +#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:464 +#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:465 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:497 #: /home/kovid/work/calibre/src/calibre/ebooks/chm/metadata.py:57 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/chm_input.py:109 @@ -151,13 +151,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:400 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:159 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:166 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:616 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:655 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1210 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1213 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1292 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1295 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:128 @@ -218,8 +218,8 @@ msgid "Customize" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:52 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:59 msgid "Cannot configure" msgstr "" @@ -248,7 +248,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:28 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:288 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:310 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:311 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:213 msgid "Preferences" msgstr "" @@ -911,15 +911,15 @@ msgstr "" msgid "Communicate with Android phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:186 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:187 msgid "Comma separated list of directories to send e-books to on the device. The first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:281 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:283 msgid "Communicate with S60 phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:300 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:302 msgid "Communicate with WebOS tablets." msgstr "" @@ -993,15 +993,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:500 #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1115 #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1161 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3244 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3286 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3245 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3287 #, python-format msgid "%(num)d of %(tot)d" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:508 #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1166 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3293 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3294 #: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:110 msgid "finished" msgstr "" @@ -1019,12 +1019,12 @@ msgid "" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2767 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2768 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:103 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:448 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:471 -#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:767 -#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:786 +#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:769 +#: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:788 #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:1052 #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:1058 #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:1093 @@ -1039,7 +1039,7 @@ msgstr "" msgid "News" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2768 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2769 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:64 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3205 @@ -1047,7 +1047,7 @@ msgstr "" msgid "Catalog" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3136 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3137 msgid "Communicate with iTunes." msgstr "" @@ -1292,7 +1292,7 @@ msgstr "" msgid "John Schember" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/interface.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/interface.py:59 msgid "Cannot get files from this device" msgstr "" @@ -1786,7 +1786,7 @@ msgid "Place files in sub directories if the device supports them" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:83 msgid "Read metadata from files on device" msgstr "" @@ -2553,7 +2553,7 @@ msgid "A comma separated list of CSS properties that will be removed from all CS msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:328 -msgid "An XPath expression. Page breaks are inserted before the specified elements." +msgid "An XPath expression. Page breaks are inserted before the specified elements. To disable use the expression: /" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:334 @@ -4055,7 +4055,7 @@ msgid "Merging user annotations into database" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:602 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:683 msgid "Fetch annotations (experimental)" msgstr "" @@ -4223,7 +4223,7 @@ msgid "Note that the actual library folder will be renamed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:302 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:727 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:729 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:204 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:308 msgid "Already exists" @@ -4286,7 +4286,7 @@ msgid "Path to library too long. Must be less than %d characters. Move your libr msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:397 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:734 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:736 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:83 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:88 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:314 @@ -4300,7 +4300,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:403 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:777 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:859 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1003 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:128 @@ -4362,7 +4362,7 @@ msgid "Create a catalog of the books in your calibre library" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:614 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:627 msgid "Cannot convert" msgstr "" @@ -4435,9 +4435,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:31 #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:161 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:675 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:676 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:93 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:239 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:374 @@ -4503,127 +4503,136 @@ msgstr "" msgid "Cannot delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:145 +#, python-format +msgid "The %(fmt)s format will be permanently deleted from %(title)s. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:162 msgid "Choose formats to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:180 msgid "Choose formats not to be deleted.

Note that this will never remove all formats from a book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:207 msgid "All formats for the selected books will be deleted from your library.
The book metadata will be kept. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:227 msgid "Cannot delete books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:228 msgid "No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:238 msgid "Main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:527 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:617 msgid "Storage Card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:225 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:529 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:610 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:619 msgid "Storage Card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:245 msgid "No books to delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:246 msgid "None of the selected books are on the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:248 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:356 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:311 msgid "Some of the selected books are on the attached device. Where do you want the selected files deleted from?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:308 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:323 msgid "The selected books will be permanently deleted and the files removed from your calibre library. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:348 msgid "The selected books will be permanently deleted from your device. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:33 msgid "Connect to folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:38 msgid "Connect to iTunes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:44 msgid "Connect to Bambook" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:85 msgid "Start Content Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:73 -msgid "Start/stop content server" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:63 +msgid "Control Smart Device Connections" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:80 +msgid "Start/stop content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:87 msgid "Stop Content Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:91 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:120 msgid "Email to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:105 msgid "Email to and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:114 msgid "(delete from library)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:129 msgid "Setup email based sharing of books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:147 msgid "D" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:147 msgid "Send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:165 msgid "Connect/share" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:203 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:85 msgid "Stopping" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:193 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:204 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:86 msgid "Stopping server, this could take upto a minute, please wait..." msgstr "" @@ -4866,11 +4875,11 @@ msgstr "" msgid "Restart in debug mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:55 msgid "Cannot configure while there are running jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:60 msgid "Cannot configure before calibre is restarted." msgstr "" @@ -5291,7 +5300,7 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:931 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1013 msgid "No books" msgstr "" @@ -5421,8 +5430,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:194 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:82 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor_ui.py:82 @@ -5544,6 +5553,11 @@ msgstr "" msgid "Cover size: %(width)d x %(height)d" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:453 +#, python-format +msgid "Delete the %s format" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex.py:16 msgid "BibTeX Options" msgstr "" @@ -5623,7 +5637,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 #: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:122 @@ -7304,10 +7318,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:62 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:76 msgid "TextLabel" msgstr "" @@ -7490,226 +7505,226 @@ msgstr "" msgid "No details available." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:189 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:192 msgid "Device no longer connected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:332 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:375 msgid "Get device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:390 msgid "Get list of books on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:357 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:400 msgid "Get annotations from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:369 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:412 msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:374 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:417 msgid "Send collections to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 #, python-format msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:440 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:483 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:501 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:512 msgid "View book on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:591 msgid "Set default send to device action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:597 msgid "Send to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:599 msgid "Send to storage card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:520 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:601 msgid "Send to storage card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:525 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:534 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:606 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:615 msgid "Main Memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627 msgid "Send specific format to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:628 msgid "Send and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:671 msgid "Eject device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:671 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:752 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:332 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:58 msgid "Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:753 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:691 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1260 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:773 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1342 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:260 msgid "No suitable formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:707 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:789 msgid "Select folder to open as device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:807 msgid "Running jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:808 msgid "Cannot configure the device while there are running device jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:731 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813 #, python-format msgid "Configure %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:742 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:824 msgid "Disconnect device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:743 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:825 #, python-format msgid "Disconnect and re-connect the %s for your changes to be applied." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:783 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:784 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:866 msgid "There was a temporary error talking to the device. Please unplug and reconnect the device or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:909 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:911 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1014 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:939 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:969 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1051 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:940 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1022 msgid "No device connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:956 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1038 #, python-format msgid "%(num)i of %(total)i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:960 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1042 #, python-format msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:961 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1043 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:970 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1052 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:973 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:977 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1055 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1059 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:974 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1060 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1039 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1122 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1254 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1121 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1204 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1336 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1068 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1150 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1167 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1249 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1221 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1303 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1261 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1343 msgid "Could not upload the following books to the device, as no suitable formats were found. Convert the book(s) to a format supported by your device first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1334 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1416 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1335 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1417 msgid "

Cannot upload books to device there is no more free space available " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:140 msgid "Unknown formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:141 msgid "You have enabled the {0} formats for your {1}. The {1} may not support them. If you send these formats to your {1} they may not work. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:153 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:449 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:279 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:70 msgid "Invalid template" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:154 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:450 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:280 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:71 @@ -7717,23 +7732,23 @@ msgstr "" msgid "The template %s is invalid:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 msgid "Select available formats and their order for this device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84 msgid "If checked, books are placed into sub directories based on their metadata on the device. If unchecked, books are all put into the top level directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85 msgid "Use sub directories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:86 msgid "Use author sort for author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:87 msgid "Save &template:" msgstr "" @@ -8140,7 +8155,7 @@ msgstr "" msgid "Library and Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:61 msgid "&Show this warning again" msgstr "" @@ -8259,7 +8274,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:523 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:668 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:670 msgid "No matches found" msgstr "" @@ -8404,31 +8419,31 @@ msgid "&Hide all jobs" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:52 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:298 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:51 msgid "&Copy to clipboard" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:301 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:54 msgid "Show &details" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:300 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:302 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:55 msgid "Hide &details" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:304 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:306 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:59 msgid "Show detailed information about this error" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:103 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:335 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:596 msgid "Copied" @@ -8449,6 +8464,11 @@ msgstr "" msgid "View log" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:322 +#, python-format +msgid "Hide the remaining %d error messages" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60 msgid "Title/Author" msgstr "" @@ -8923,6 +8943,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:64 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:139 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:135 msgid "&Password:" @@ -8930,6 +8951,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:78 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:148 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:81 msgid "&Show password" @@ -9057,183 +9079,188 @@ msgstr "" msgid "This plugin is installed and up-to-date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:475 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:476 msgid "Update Check Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:477 msgid "Unable to reach the MobileRead plugins forum index page." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:483 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:484 msgid "User plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:488 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:489 msgid "User Plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:496 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:497 msgid "Filter list of plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:515 msgid "Description" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:526 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:538 -msgid "&Install" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:527 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:539 +msgid "&Install" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:540 msgid "Install the selected plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:531 msgid "&Customize plugin " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:531 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:583 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:584 msgid "Customize the options for this plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:543 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:544 msgid "Version &History" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:545 msgid "Show history of changes to this plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:548 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:549 msgid "Plugin &Forum Thread" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:558 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:114 msgid "Enable/&Disable plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:559 msgid "Enable or disable this plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:562 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:563 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:116 msgid "&Remove plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:563 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:564 msgid "Uninstall the selected plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:573 msgid "Donate to developer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:573 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:574 msgid "Donate to the developer of this plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:583 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:115 msgid "&Customize plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:676 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:677 #, python-format msgid "Are you sure you want to uninstall the %s plugin?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:688 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:689 #, python-format msgid "Install %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:690 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:299 msgid "Installing plugins is a security risk. Plugins can contain a virus/malware. Only install it if you got it from a trusted source. Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:706 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:707 #, python-format msgid "Locating zip file for %(name)s: %(link)s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:710 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:747 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:711 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:759 msgid "Install Plugin Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:711 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:712 #, python-format msgid "Unable to locate a plugin zip file for %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:717 #, python-format msgid "Downloading plugin zip attachment: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:721 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:722 #, python-format msgid "Installing plugin: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:733 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:735 #, python-format msgid "Plugin installed: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:735 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:737 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:315 msgid "Plugin {0} successfully installed under {1} plugins. You may have to restart calibre for the plugin to take effect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:748 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:352 +msgid "Restart calibre now" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:760 msgid "A problem occurred while installing this plugin. This plugin will now be uninstalled. Please post the error message in details below into the forum thread for this plugin and restart Calibre." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:773 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:788 msgid "Version history missing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:774 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:789 #, python-format msgid "Unable to find the version history for %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:781 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:796 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:350 msgid "Plugin not customizable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:797 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:351 #, python-format msgid "Plugin: %s does not need customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:786 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:801 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:357 msgid "Must restart" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:787 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:802 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:358 #, python-format msgid "You must restart calibre before you can configure the %s plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:795 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:810 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:339 msgid "Plugin cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:796 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:811 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:340 #, python-format msgid "The plugin: %s cannot be disabled" @@ -9769,6 +9796,38 @@ msgstr "" msgid "Choose formats" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice.py:17 +msgid "This dialog starts and stops the smart device app interface. When you start the interface, you might see some messages from your computer's firewall or anti-virus manager asking you if it is OK for calibre to connect to the network. Please answer yes. If you do not, the app will not work. It will be unable to connect to calibre." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice.py:25 +msgid "Use a password if calibre is running on a network that is not secure. For example, if you run calibre on a laptop, use that laptop in an airport, and want to connect your smart device to calibre, you should use a password." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice.py:31 +msgid "Check this box to allow calibre to accept connections from the smart device. Uncheck the box to prevent connections." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice.py:35 +msgid "Check this box if you want calibre to automatically start the smart device interface when calibre starts. You should not do this if you are using a network that is not secure and you are not setting a password." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:72 +msgid "Smart device control" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:73 +msgid "&Automatically allow connections at startup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:74 +msgid "Optional password for security" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/smartdevice_ui.py:75 +msgid "&Allow connections" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:164 msgid "Publishers" @@ -10837,12 +10896,12 @@ msgid "LRF Viewer toolbar" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:504 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:493 msgid "Next Page" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:494 msgid "Previous Page" msgstr "" @@ -10910,7 +10969,7 @@ msgid "Failed to create calibre library at: %r." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:172 msgid "Choose a location for your new calibre e-book library" msgstr "" @@ -10918,87 +10977,87 @@ msgstr "" msgid "Initializing user interface..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:166 msgid "Repairing failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:167 msgid "The database repair failed. Starting with a new empty library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:206 msgid "Bad database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:182 #, python-format msgid "Bad database location %r. calibre will now quit." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:193 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:194 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:530 msgid "Corrupted database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:194 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:195 #, python-format msgid "The library database at %s appears to be corrupted. Do you want calibre to try and rebuild it automatically? The rebuild may not be completely successful. If you say No, a new empty calibre library will be created." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:207 #, python-format msgid "Bad database location %r. Will start with a new, empty calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217 #, python-format msgid "Starting %s: Loading books..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:297 msgid "If you are sure it is not running" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:300 msgid "may be running in the system tray, in the" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:302 msgid "upper right region of the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:304 msgid "lower right region of the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:307 msgid "try rebooting your computer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:326 msgid "try deleting the file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:311 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:312 msgid "Cannot Start " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:313 #, python-format msgid "%s is already running." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334 msgid "No running calibre found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:337 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:338 msgid "Shutdown command sent, waiting for shutdown..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:343 msgid "Failed to shutdown running calibre instance" msgstr "" @@ -11500,7 +11559,7 @@ msgstr "" msgid "Restore settings to default values. You have to click Apply to actually save the default settings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:340 msgid "Configure " msgstr "" @@ -12652,27 +12711,23 @@ msgstr "" msgid "Cancel and return to overview" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:297 msgid "Restoring to defaults not supported for" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:332 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:342 msgid "Some of the changes you made require a restart. Please restart calibre as soon as possible." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:335 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:345 msgid "The changes you have made require calibre be restarted immediately. You will not be allowed to set any more preferences, until you restart." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:340 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:350 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134 msgid "Restart needed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:342 -msgid "Restart calibre now" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:48 msgid "Source" msgstr "" @@ -13289,7 +13344,7 @@ msgid "Here you can control how calibre will save your books when you click the msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:467 msgid "Failed to start content server" msgstr "" @@ -14476,40 +14531,40 @@ msgstr "" msgid "The following books have already been converted to %s format. Do you wish to reconvert them?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:207 msgid "&Donate to support calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:243 msgid "&Restore" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:248 msgid "&Eject connected device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:252 msgid "Quit calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:265 msgid "Clear the current search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:365 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:378 msgid "Debug mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:379 #, python-format msgid "You have started calibre in debug mode. After you quit calibre, the debug log will be available in the file: %s

The log will be displayed automatically." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:386 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:399 msgid "Failed to start Content Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:400 #, python-format msgid "" "Could not start the content server. Error:\n" @@ -14517,21 +14572,21 @@ msgid "" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:531 #, python-format msgid "The library database at %s appears to be corrupted. Do you want calibre to try and rebuild it automatically? The rebuild may not be completely successful." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:602 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:615 msgid "Conversion Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:623 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:636 #, python-format msgid "

Failed to convert: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:624 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:637 msgid "" "\n" " Many older ebook reader devices are incapable of displaying\n" @@ -14548,34 +14603,34 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:638 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:651 msgid "Conversion Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:650 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:663 msgid "Recipe Disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:683 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:696 msgid "Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:717 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:728 msgid "There are active jobs. Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:720 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:731 msgid "" " is communicating with the device!
\n" " Quitting may cause corruption on the device.
\n" " Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:735 msgid "Active jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:794 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:805 msgid "will keep running in the system tray. To close it, choose Quit in the context menu of the system tray." msgstr "" @@ -14681,7 +14736,7 @@ msgid "Options to customize the ebook viewer" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1044 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1046 msgid "Remember last used window size" msgstr "" @@ -14950,40 +15005,40 @@ msgstr "" msgid "No results found for:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:473 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:462 msgid "&Lookup in dictionary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:478 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:467 msgid "&Search for next occurrence" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:483 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:472 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:138 msgid "Go to..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:484 msgid "Next Section" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:496 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:485 msgid "Previous Section" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:487 msgid "Document Start" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:499 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:488 msgid "Document End" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:490 msgid "Section Start" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:491 msgid "Section End" msgstr "" @@ -15068,126 +15123,130 @@ msgstr "" msgid "Toggle full screen (%s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:277 msgid "Full screen mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:278 msgid "Right click to show controls" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:279 +msgid "Tap in the left or right page margin to turn pages" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:280 msgid "Press Esc to quit" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:294 msgid "Show/hide controls" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:321 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:323 msgid "Print Preview" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:333 msgid "Clear list of recently opened books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:442 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:444 #, python-format msgid "Connecting to dict.org to lookup: %s…" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:576 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:578 msgid "No such location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:577 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:579 msgid "The location pointed to by this item does not exist." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:630 msgid "Choose ebook" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:629 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:631 msgid "Ebooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:649 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:651 #, python-format msgid "" "Make font size %(which)s\n" "Current magnification: %(mag).1f" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:651 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:653 msgid "larger" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:653 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:655 msgid "smaller" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:669 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:671 #, python-format msgid "No matches found for: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:718 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:720 msgid "Loading flow..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:796 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:798 #, python-format msgid "Laying out %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:848 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:850 #, python-format msgid "Bookmark #%d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:852 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:854 msgid "Add bookmark" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:855 msgid "Enter title for bookmark:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:864 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:866 msgid "Manage Bookmarks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:908 msgid "Loading ebook..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:918 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:920 msgid "Could not open ebook" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1031 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1033 msgid "Options to control the ebook viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1038 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1040 msgid "If specified, viewer window will try to come to the front when started." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1041 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1043 msgid "If specified, viewer window will try to open full screen when started." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1046 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1048 msgid "Print javascript alert and console messages to the console" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1048 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1050 msgid "The position at which to open the specified book. The position is a location as displayed in the top left corner of the viewer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1055 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1057 msgid "" "%prog [options] file\n" "\n" @@ -15607,63 +15666,52 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/library/caches.py:177 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:600 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:614 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:624 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:612 msgid "checked" msgstr "" #: /home/kovid/work/calibre/src/calibre/library/caches.py:177 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:600 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:614 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:624 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:610 #: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:223 msgid "yes" msgstr "" #: /home/kovid/work/calibre/src/calibre/library/caches.py:179 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:599 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:611 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:621 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:609 #: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:223 msgid "no" msgstr "" #: /home/kovid/work/calibre/src/calibre/library/caches.py:179 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:599 #: /home/kovid/work/calibre/src/calibre/library/caches.py:611 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:621 msgid "unchecked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:393 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:355 msgid "today" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:396 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:356 msgid "yesterday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:399 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:357 msgid "thismonth" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:402 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:403 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:358 msgid "daysago" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:601 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:618 -msgid "blank" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:601 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:618 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:613 msgid "empty" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:602 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:614 +msgid "blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:626 msgid "Invalid boolean query \"{0}\"" msgstr "" diff --git a/src/calibre/utils/icu.c b/src/calibre/utils/icu.c index c451e9cdac..dfaf2dd53e 100644 --- a/src/calibre/utils/icu.c +++ b/src/calibre/utils/icu.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -310,6 +311,41 @@ icu_Collator_startswith(icu_Collator *self, PyObject *args, PyObject *kwargs) { Py_RETURN_FALSE; } // }}} +// Collator.startswith {{{ +static PyObject * +icu_Collator_collation_order(icu_Collator *self, PyObject *args, PyObject *kwargs) { + PyObject *a_; + size_t asz; + int32_t actual_a; + UChar *a; + wchar_t *aw; + UErrorCode status = U_ZERO_ERROR; + UCollationElements *iter = NULL; + int order = 0, len = -1; + + if (!PyArg_ParseTuple(args, "U", &a_)) return NULL; + asz = PyUnicode_GetSize(a_); + + a = (UChar*)calloc(asz*4 + 2, sizeof(UChar)); + aw = (wchar_t*)calloc(asz*4 + 2, sizeof(wchar_t)); + + if (a == NULL || aw == NULL ) return PyErr_NoMemory(); + + actual_a = (int32_t)PyUnicode_AsWideChar((PyUnicodeObject*)a_, aw, asz*4+1); + if (actual_a > -1) { + u_strFromWCS(a, asz*4 + 1, &actual_a, aw, -1, &status); + iter = ucol_openElements(self->collator, a, actual_a, &status); + if (iter != NULL && U_SUCCESS(status)) { + order = ucol_next(iter, &status); + len = ucol_getOffset(iter); + ucol_closeElements(iter); iter = NULL; + } + } + + free(a); free(aw); + return Py_BuildValue("ii", order, len); +} // }}} + static PyObject* icu_Collator_clone(icu_Collator *self, PyObject *args, PyObject *kwargs); @@ -338,6 +374,10 @@ static PyMethodDef icu_Collator_methods[] = { "startswith(a, b) -> returns True iff a startswith b, following the current collation rules." }, + {"collation_order", (PyCFunction)icu_Collator_collation_order, METH_VARARGS, + "collation_order(string) -> returns (order, length) where order is an integer that gives the position of string in a list. length gives the number of characters used for order." + }, + {NULL} /* Sentinel */ }; diff --git a/src/calibre/utils/icu.py b/src/calibre/utils/icu.py index 0dab76cd30..93f4d7b1da 100644 --- a/src/calibre/utils/icu.py +++ b/src/calibre/utils/icu.py @@ -75,6 +75,7 @@ def icu_sort_key(collator, obj): except AttributeError: return secondary_collator().sort_key(obj) + def py_find(pattern, source): pos = source.find(pattern) if pos > -1: @@ -126,6 +127,12 @@ def icu_contractions(collator): _cmap[collator] = ans return ans +def icu_collation_order(collator, a): + try: + return collator.collation_order(a) + except TypeError: + return collator.collation_order(unicode(a)) + load_icu() load_collator() _icu_not_ok = _icu is None or _collator is None @@ -205,6 +212,14 @@ def primary_startswith(a, b): except AttributeError: return icu_startswith(primary_collator(), a, b) +def collation_order(a): + if _icu_not_ok: + return (ord(a[0]), 1) if a else (0, 0) + try: + return icu_collation_order(_secondary_collator, a) + except AttributeError: + return icu_collation_order(secondary_collator(), a) + ################################################################################ def test(): # {{{