diff --git a/resources/recipes/cnn.recipe b/resources/recipes/cnn.recipe index 21c4139d6e..8c3cfa6de8 100644 --- a/resources/recipes/cnn.recipe +++ b/resources/recipes/cnn.recipe @@ -4,13 +4,14 @@ __copyright__ = '2008, Kovid Goyal ' Profile to download CNN ''' from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup class CNN(BasicNewsRecipe): title = 'CNN' description = 'Global news' timefmt = ' [%d %b %Y]' - __author__ = 'Kovid Goyal and Sujata Raman' + __author__ = 'Krittika Goyal and Sujata Raman' language = 'en' no_stylesheets = True @@ -18,7 +19,8 @@ class CNN(BasicNewsRecipe): oldest_article = 15 recursions = 1 match_regexps = [r'http://sportsillustrated.cnn.com/.*/[1-9].html'] - + max_articles_per_feed = 25 + extra_css = ''' .cnn_strycntntlft{font-family :Arial,Helvetica,sans-serif;} h2{font-family :Arial,Helvetica,sans-serif; font-size:x-small} @@ -44,7 +46,7 @@ class CNN(BasicNewsRecipe): .cnnInlineT1Caption{font-family :Arial,Helvetica,sans-serif; font-size:x-small;font-weight:bold;} .cnnInlineT1Credit{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#333333;} .col10{color:#5A637E;} - .cnnInlineRailBulletList{color:black;} + .cnnInlineRailBulletList{color:black;} .cnnLine0{font-family :Arial,Helvetica,sans-serif; color:#666666;font-weight:bold;} .cnnTimeStamp{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#333333;} .galleryhedDek{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#575757;} @@ -55,31 +57,22 @@ class CNN(BasicNewsRecipe): .captionname{font-family :Arial,Helvetica,sans-serif; font-size:x-small;color:#575757;} inStoryIE{{font-family :Arial,Helvetica,sans-serif; font-size:x-small;} ''' - keep_only_tags = [ - dict(name='div', attrs={'class':["cnnWCBoxContent","cnnContent","cnnMainBodySecs","cnn_storyarea","cnn_strycntntlft","cnn_strytmstmp"]}), - dict(name='div', attrs={'id':["contentBody","content"]}), - dict(name='td', attrs={'id':["cnnRecapStory"]}), - dict(name='td', attrs={'class':["cnnBigSideLeft"]}), - ] - remove_tags = [ - dict(name='div', attrs={'class':["cnnInlineRailSelectList","cnn_strycntntrgt","cnn_strybtntools","cnn_strylctcntr cnn_strylctcqrelt","cnn_strylceclbtn","cnn_stryftsbttm","cnn_strybtmmorebx","storyLink","article-tools clearfix","widget video related-video vList","cnnFooterBox","scrollArrows","boxHeading","cnnInlineMailbag","mainCol_lastBlock","cnn_bookmarks","cnnFooterBox","cnnEndOfStory","cnnInlineSL","cnnStoryHighlights","cnnFooterClick","cnnSnapShotHeader","cnnStoryToolsFooter","cnnWsnr","cnnUGCBox","cnnTopNewsModule","cnnStoryElementBox","cnnStoryPhotoBoxNavigation"]}), - dict(name='span', attrs={'class':["cnnEmbeddedMosLnk"]}), - dict(name='div', attrs={'id':["cnnIncldHlder","articleCommentsContainer","featuredContent","superstarsWidget","shareMenuContainer","rssMenuContainer","storyBrandingBanner","cnnRightCol","siteFeatures","quigo628","rightColumn","clickIncludeBox","cnnHeaderRightCol","cnnSCFontLabel","cnnSnapShotBottomRight","cnnSCFontButtons","rightColumn"]}), - dict(name='p', attrs={'class':["cnnTopics"]}), - dict(name='td', attrs={'class':["cnnRightRail"]}), - dict(name='table', attrs={'class':["cnnTMbox"]}), - dict(name='ul', attrs={'id':["cnnTopNav","cnnBotNav","cnnSBNav"]}), - dict(name='div', attrs={'id':["cnn_ftrcntnt"]}) - ] - # def print_version(self, url): - # return 'http://www.printthis.clickability.com/pt/printThis?clickMap=printThis&fb=Y&url=' + url + #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) + #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) + remove_tags = [ + dict(name='iframe'), + dict(name='div', attrs={'class':['cnnEndOfStory', 'cnnShareThisItem', 'cnn_strylctcntr cnn_strylctcqrelt', 'cnnShareBoxContent', 'cnn_strybtmcntnt', 'cnn_strycntntrgt']}), + dict(name='div', attrs={'id':['IEContainer', 'clickIncludeBox']}), + #dict(name='ul', attrs={'class':'article-tools'}), + #dict(name='ul', attrs={'class':'articleTools'}), + ] feeds = [ ('Top News', 'http://rss.cnn.com/rss/cnn_topstories.rss'), ('World', 'http://rss.cnn.com/rss/cnn_world.rss'), ('U.S.', 'http://rss.cnn.com/rss/cnn_us.rss'), - ('Sports', 'http://rss.cnn.com/rss/si_topstories.rss'), + #('Sports', 'http://rss.cnn.com/rss/si_topstories.rss'), ('Business', 'http://rss.cnn.com/rss/money_latest.rss'), ('Politics', 'http://rss.cnn.com/rss/cnn_allpolitics.rss'), ('Law', 'http://rss.cnn.com/rss/cnn_law.rss'), @@ -91,10 +84,15 @@ class CNN(BasicNewsRecipe): ('Offbeat', 'http://rss.cnn.com/rss/cnn_offbeat.rss'), ('Most Popular', 'http://rss.cnn.com/rss/cnn_mostpopular.rss') ] - def preprocess_html(self, soup): - - for tag in soup.findAll(name=['ul','li']): - tag.name = 'div' - - return soup + story = soup.find(name='div', attrs={'class':'cnnBody_Left'}) + if story is None: + story = soup.find(name='div', attrs={'id':'cnnContentContainer'}) + soup = BeautifulSoup('t') + body = soup.find(name='body') + body.insert(0, story) + else: + soup = BeautifulSoup('t') + body = soup.find(name='body') + body.insert(0, story) + return soup diff --git a/resources/recipes/people_us_mashup.recipe b/resources/recipes/people_us_mashup.recipe new file mode 100644 index 0000000000..2631ac5184 --- /dev/null +++ b/resources/recipes/people_us_mashup.recipe @@ -0,0 +1,93 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +''' +''' +from calibre.web.feeds.recipes import BasicNewsRecipe + +class PeopleMag(BasicNewsRecipe): + + title = 'People/US Magazine Mashup' + __author__ = 'BrianG' + language = 'en' + description = 'Headlines from People and US Magazine' + no_stylesheets = True + use_embedded_content = False + oldest_article = 2 + max_articles_per_feed = 50 + + extra_css = ''' + h1{font-family:verdana,arial,helvetica,sans-serif; font-size: large;} + h2{font-family:verdana,arial,helvetica,sans-serif; font-size: small;} + .body-content{font-family:verdana,arial,helvetica,sans-serif; font-size: small;} + .byline {font-size: small; color: #666666; font-style:italic; } + .lastline {font-size: small; color: #666666; font-style:italic;} + .contact {font-size: small; color: #666666;} + .contact p {font-size: small; color: #666666;} + .photoCaption { font-family:verdana,arial,helvetica,sans-serif; font-size:x-small;} + .photoCredit{ font-family:verdana,arial,helvetica,sans-serif; font-size:x-small; color:#666666;} + .article_timestamp{font-size:x-small; color:#666666;} + a {font-family:verdana,arial,helvetica,sans-serif; font-size: x-small;} + ''' + + + keep_only_tags = [ + dict(name='div', attrs={'class': 'panel_news_article_main'}), + dict(name='div', attrs={'class':'article_content'}), + dict(name='div', attrs={'class': 'headline'}), + dict(name='div', attrs={'class': 'post'}), + dict(name='div', attrs={'class': 'packageheadlines'}), + dict(name='div', attrs={'class': 'snap_preview'}), + dict(name='div', attrs={'id': 'articlebody'}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':'share_comments'}), + dict(name='p', attrs={'class':'twitter_facebook'}), + dict(name='div', attrs={'class':'share_comments_bottom'}), + dict(name='h2', attrs={'id':'related_content'}), + dict(name='div', attrs={'class':'next_article'}), + dict(name='div', attrs={'class':'prev_article'}), + dict(name='ul', attrs={'id':'sharebar'}), + dict(name='div', attrs={'class':'sharelinkcont'}), + dict(name='div', attrs={'class':'categories'}), + dict(name='ul', attrs={'class':'categories'}), + dict(name='div', attrs={'id':'promo'}), + dict(name='div', attrs={'class':'linksWrapper'}), + dict(name='p', attrs={'class':'tag tvnews'}), + dict(name='p', attrs={'class':'tag movienews'}), + dict(name='p', attrs={'class':'tag musicnews'}), + dict(name='p', attrs={'class':'tag couples'}), + dict(name='p', attrs={'class':'tag gooddeeds'}), + dict(name='p', attrs={'class':'tag weddings'}), + dict(name='p', attrs={'class':'tag health'}) +] + + + feeds = [ + ('PEOPLE Headlines', 'http://feeds.people.com/people/headlines'), + ('US Headlines', 'http://www.usmagazine.com/celebrity_news/rss') + ] + + def get_article_url(self, article): + ans = article.link + + try: + self.log('Looking for full story link in', ans) + soup = self.index_to_soup(ans) + x = soup.find(text="View All") + + if x is not None: + ans = ans + '?viewAll=y' + self.log('Found full story link', ans) + except: + pass + return ans + + def postprocess_html(self, soup,first): + + for tag in soup.findAll(name='div',attrs={'class':"container_ate_qandatitle"}): + tag.extract() + for tag in soup.findAll(name='br'): + tag.extract() + + return soup diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index c04a200671..e2291aa427 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -464,6 +464,7 @@ plugins += [ IREXDR1000, JETBOOK, SHINEBOOK, + POCKETBOOK360, KINDLE, KINDLE2, KINDLE_DX, @@ -479,7 +480,6 @@ plugins += [ ESLICK, NUUT2, IRIVER_STORY, - POCKETBOOK360, GER2, ITALICA, ECLICTO, diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index 0e246f5c54..d84f3c3e77 100644 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -16,7 +16,6 @@ Windows PNP strings: ''' from calibre.devices.usbms.driver import USBMS -from calibre.constants import iswindows class EB600(USBMS): @@ -88,17 +87,15 @@ class SHINEBOOK(EB600): @classmethod def can_handle(cls, dev, debug=False): - try: - if not iswindows: - return dev[4] == 'ShineBook' - except: - pass - return True + return dev[4] == 'ShineBook' class POCKETBOOK360(EB600): + # Device info on OS X + # (8069L, 5768L, 272L, u'', u'', u'1.00') + name = 'PocketBook 360 Device Interface' gui_name = 'PocketBook 360' @@ -112,6 +109,10 @@ class POCKETBOOK360(EB600): OSX_MAIN_MEM = 'Philips Mass Storge Media' OSX_CARD_A_MEM = 'Philips Mass Storge Media' + @classmethod + def can_handle(cls, dev, debug=False): + return dev[-1] == '1.00' and not dev[-2] and not dev[-3] + class GER2(EB600): name = 'Ganaxa GeR2 Device Interface' diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index acaf1e6267..439a950f14 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -56,8 +56,9 @@ class PRS505(CLI, Device): EBOOK_DIR_MAIN = 'database/media/books' EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of metadata fields ' - 'to turn into collections on the device.') - EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags', 'authors']) + 'to turn into collections on the device. Posiibilities include: ')+\ + 'series, tags, authors' + EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags']) def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 317fe9c94a..82f65f8c42 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -529,6 +529,18 @@ class Application(QApplication): self.load_translations() qt_app = self + if islinux: + self.setStyleSheet(''' + QToolTip { + border: 2px solid black; + padding: 5px; + border-radius: 10px; + opacity: 200; + background-color: #e1e1ff; + } + ''') + + def load_translations(self): if self._translator is not None: self.removeTranslator(self._translator) diff --git a/src/calibre/gui2/convert/__init__.py b/src/calibre/gui2/convert/__init__.py index 383fe2d36f..34940f081a 100644 --- a/src/calibre/gui2/convert/__init__.py +++ b/src/calibre/gui2/convert/__init__.py @@ -145,10 +145,8 @@ class Widget(QWidget): help = help_provider(name) if not help: continue g._help = help - g.setToolTip('\n'.join(w.wrap(help.replace('<', '<').replace('>', - '>')))) - g.setWhatsThis('\n'.join(w.wrap(help.replace('<', '<').replace('>', - '>')))) + g.setToolTip('\n'.join(w.wrap(help))) + g.setWhatsThis('\n'.join(w.wrap(help))) g.__class__.enterEvent = lambda obj, event: self.set_help(getattr(obj, '_help', obj.toolTip())) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index 0e326ba239..81d67ff7dd 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -57,7 +57,13 @@ if pictureflow is not None: return self.model.count() def caption(self, index): - return self.model.title(index) + try: + ans = self.model.title(index) + if not ans: + ans = '' + except: + ans = '' + return ans def reset(self): self.emit(SIGNAL('dataChanged()')) diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index ea9ab1af50..156faec7ce 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -177,7 +177,7 @@ class PluginModel(QAbstractItemModel): ans='%s (%s) %s %s\n%s'%(plugin.name, ver, _('by'), plugin.author, desc) c = plugin_customization(plugin) if c: - ans += '\nCustomization: '+c + ans += _('\nCustomization: ')+c return QVariant(ans) if role == Qt.DecorationRole: return self.disabled_icon if is_disabled(plugin) else self.icon @@ -437,6 +437,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog): self.priority_label.setVisible(iswindows) self._plugin_model = PluginModel() self.plugin_view.setModel(self._plugin_model) + self.plugin_view.setStyleSheet( + "QTreeView::item { padding-bottom: 10px;}") self.connect(self.toggle_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='toggle')) self.connect(self.customize_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='customize')) self.connect(self.remove_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='remove')) diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index f593df5e14..e638bab1c9 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -15,7 +15,7 @@ Preferences - + :/images/config.svg:/images/config.svg @@ -148,7 +148,7 @@ ... - + :/images/mimetypes/dir.svg:/images/mimetypes/dir.svg @@ -285,7 +285,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -309,7 +309,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -473,7 +473,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -497,7 +497,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -557,7 +557,7 @@ &Add email - + :/images/plus.svg:/images/plus.svg @@ -584,7 +584,7 @@ &Remove email - + :/images/minus.svg:/images/minus.svg @@ -907,6 +907,9 @@ + + true + 32 @@ -916,6 +919,9 @@ true + + true + true @@ -973,7 +979,7 @@ ... - + :/images/document_open.svg:/images/document_open.svg @@ -1044,7 +1050,7 @@ - + diff --git a/src/calibre/gui2/shortcuts.py b/src/calibre/gui2/shortcuts.py index a617b6897f..5c4ebcc2fe 100644 --- a/src/calibre/gui2/shortcuts.py +++ b/src/calibre/gui2/shortcuts.py @@ -152,9 +152,9 @@ class Delegate(QStyledItemDelegate): class Shortcuts(QAbstractListModel): - TEMPLATE = ''' + TEMPLATE = u'''

{0}
- Keys: {1}

+ {2}: {1}

''' def __init__(self, shortcuts, config_file_base_name, parent=None): @@ -212,7 +212,7 @@ class Shortcuts(QAbstractListModel): key = self.order[row] if role == Qt.DisplayRole: return QVariant(self.TEMPLATE.format(self.descriptions[key], - _(' or ').join(self.get_shortcuts(key)))) + _(' or ').join(self.get_shortcuts(key)), _('Keys'))) if role == Qt.ToolTipRole: return QVariant(_('Double click to change')) if role == DEFAULTS: diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 39df62c74e..4d40dccfcd 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -518,6 +518,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ########################### Cover Flow ################################ self.cover_flow = None if CoverFlow is not None: + self.cf_last_updated_at = None + self.cover_flow_sync_timer = QTimer(self) + self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync) + self.cover_flow_sync_flag = True text_height = 40 if config['separate_cover_flow'] else 25 ah = available_height() cfh = ah-100 @@ -528,17 +532,13 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.cover_flow.setVisible(False) if not config['separate_cover_flow']: self.library.layout().addWidget(self.cover_flow) - #self.connect(self.cover_flow, SIGNAL('currentChanged(int)'), - # self.sync_cf_to_listview) - #self.connect(self.cover_flow, SIGNAL('itemActivated(int)'), - # self.show_book_info) + self.cover_flow.currentChanged.connect(self.sync_listview_to_cf) self.connect(self.status_bar.cover_flow_button, SIGNAL('toggled(bool)'), self.toggle_cover_flow) self.connect(self.cover_flow, SIGNAL('stop()'), self.status_bar.cover_flow_button.toggle) - #QObject.connect(self.library_view.selectionModel(), - # SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'), - # self.sync_cf_to_listview) + self.library_view.selectionModel().currentRowChanged.connect( + self.sync_cf_to_listview) self.db_images = DatabaseImages(self.library_view.model()) self.cover_flow.setImages(self.db_images) else: @@ -684,7 +684,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.connect(d, SIGNAL('finished(int)'), self.uncheck_cover_button) self.cf_dialog = d + self.cover_flow_sync_timer.start(500) else: + self.cover_flow_sync_timer.stop() idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0) if idx.isValid(): sm = self.library_view.selectionModel() @@ -705,7 +707,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): #self.status_bar.book_info.book_data.setMaximumHeight(100) #self.status_bar.setMaximumHeight(120) self.library_view.scrollTo(self.library_view.currentIndex()) + self.cover_flow_sync_timer.start(500) else: + self.cover_flow_sync_timer.stop() self.cover_flow.setVisible(False) idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0) if idx.isValid(): @@ -731,14 +735,32 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): if view is self.current_view(): self.search.search_done(ok) - def sync_cf_to_listview(self, index, *args): - if not hasattr(index, 'row') and \ - self.library_view.currentIndex().row() != index: - index = self.library_view.model().index(index, 0) - self.library_view.setCurrentIndex(index) - if hasattr(index, 'row') and self.cover_flow.isVisible() and \ - self.cover_flow.currentSlide() != index.row(): - self.cover_flow.setCurrentSlide(index.row()) + def sync_cf_to_listview(self, current, previous): + if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ + self.cover_flow.currentSlide() != current.row(): + self.cover_flow.setCurrentSlide(current.row()) + self.cover_flow_sync_flag = True + + def cover_flow_do_sync(self): + self.cover_flow_sync_flag = True + try: + if self.cover_flow.isVisible() and self.cf_last_updated_at is not None and \ + time.time() - self.cf_last_updated_at > 0.5: + self.cf_last_updated_at = None + row = self.cover_flow.currentSlide() + m = self.library_view.model() + index = m.index(row, 0) + if self.library_view.currentIndex().row() != row and index.isValid(): + self.cover_flow_sync_flag = False + sm = self.library_view.selectionModel() + sm.select(index, sm.ClearAndSelect|sm.Rows) + self.library_view.setCurrentIndex(index) + except: + pass + + + def sync_listview_to_cf(self, row): + self.cf_last_updated_at = time.time() def another_instance_wants_to_talk(self): try: