From d1e49d92678c687976af099fa1d948abe899c49c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 9 Jun 2010 20:04:56 -0600 Subject: [PATCH 1/9] Conversion pipeline: Handle missing/obsolete input/output profiles gracefully --- src/calibre/ebooks/conversion/plumber.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index e227ad2c8e..24b35f804f 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -706,15 +706,22 @@ OptionRecommendation(name='timestamp', for rec in group: setattr(self.opts, rec.option.name, rec.recommended_value) - for x in input_profiles(): - if x.short_name == self.opts.input_profile: - self.opts.input_profile = x - break + def set_profile(profiles, which): + attr = which + '_profile' + sval = getattr(self.opts, attr) + for x in profiles(): + if x.short_name == sval: + setattr(self.opts, attr, x) + return + self.log.warn( + 'Profile (%s) %r is no longer available, using default'%(which, sval)) + for x in profiles(): + if x.short_name == 'default': + setattr(self.opts, attr, x) + break - for x in output_profiles(): - if x.short_name == self.opts.output_profile: - self.opts.output_profile = x - break + set_profile(input_profiles, 'input') + set_profile(output_profiles, 'output') self.read_user_metadata() self.opts.no_inline_navbars = self.opts.output_profile.supports_mobi_indexing \ From a42985c57c0f673fd9216c5ee37c75882bfcf45e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 9 Jun 2010 21:00:36 -0600 Subject: [PATCH 2/9] Fix #5777 (New recipe for blog Reptantes in Spanish) --- resources/recipes/reptantes.recipe | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 resources/recipes/reptantes.recipe diff --git a/resources/recipes/reptantes.recipe b/resources/recipes/reptantes.recipe new file mode 100644 index 0000000000..51b65c6639 --- /dev/null +++ b/resources/recipes/reptantes.recipe @@ -0,0 +1,43 @@ + +__license__ = 'GPL v3' +__copyright__ = '2010, Darko Miletic ' +''' +www.reptantes.com.ar +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Reptantes(BasicNewsRecipe): + title = 'Reptantes' + __author__ = 'Darko Miletic' + description = u"cada vez que te haces acupuntura, tu muñeco vudú sufre en algún lado" + oldest_article = 130 + max_articles_per_feed = 100 + language = 'es' + encoding = 'utf-8' + no_stylesheets = True + use_embedded_content = False + publication_type = 'blog' + extra_css = ' body{font-family: "Palatino Linotype",serif} h2{text-align: center; color:#BE7F8D} img{margin-bottom: 2em} ' + + conversion_options = { + 'comment' : description + , 'tags' : 'literatura' + , 'publisher': 'Hernan Racnati' + , 'language' : language + } + + feeds = [(u'Posts', u'http://www.reptantes.com.ar/?feed=rss2')] + + keep_only_tags = [dict(attrs={'id':'content'})] + remove_tags = [dict(attrs={'class':'iLikeThis'})] + remove_tags_before = dict(name='h2') + remove_tags_after = dict(attrs={'class':'iLikeThis'}) + + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + return self.adeify_images(soup) + + From 624dfff639b159f62b34d9c5e9f2e31f0cf359b1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 10:42:07 -0600 Subject: [PATCH 3/9] Fix #5749 (SMH news feed broken) --- resources/recipes/smh.recipe | 6 +++--- src/calibre/web/feeds/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/recipes/smh.recipe b/resources/recipes/smh.recipe index 21643b9611..b5c7f4d54e 100644 --- a/resources/recipes/smh.recipe +++ b/resources/recipes/smh.recipe @@ -21,7 +21,7 @@ class Smh_au(BasicNewsRecipe): language = 'en_AU' remove_empty_feeds = True masthead_url = 'http://images.smh.com.au/2010/02/02/1087188/smh-620.jpg' - publication_type = 'newspaper' + publication_type = 'newspaper' extra_css = ' h1{font-family: Georgia,"Times New Roman",Times,serif } body{font-family: Arial,Helvetica,sans-serif} .cT-imageLandscape{font-size: x-small} ' conversion_options = { @@ -47,7 +47,7 @@ class Smh_au(BasicNewsRecipe): for itimg in soup.findAll('img',src=True): if itimg['src'].endswith('frontpage.jpg'): self.cover_url = itimg['src'] - + for item in soup.findAll(attrs={'class':'cN-storyHeadlineLead cfix'}): description = '' title_prefix = '' @@ -65,4 +65,4 @@ class Smh_au(BasicNewsRecipe): ,'url' :url ,'description':description }) - return [(soup.head.title.string, articles)] + return [(self.tag_to_string(soup.find('title')), articles)] diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py index da7122c491..c34334ee09 100644 --- a/src/calibre/web/feeds/__init__.py +++ b/src/calibre/web/feeds/__init__.py @@ -137,7 +137,7 @@ class Feed(object): def populate_from_preparsed_feed(self, title, articles, oldest_article=7, max_articles_per_feed=100): - self.title = title if title else _('Unknown feed') + self.title = unicode(title if title else _('Unknown feed')) self.description = '' self.image_url = None self.articles = [] From e355672c7045f5a100e1fcd84e6a5694ab095bb9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 20:45:04 -0600 Subject: [PATCH 4/9] More GUI Refactoring. Cover browser can now be dynamically resized like the other GUI areas --- src/calibre/gui2/__init__.py | 7 + src/calibre/gui2/cover_flow.py | 99 ++++--- src/calibre/gui2/init.py | 123 +++++++- src/calibre/gui2/library/views.py | 21 ++ src/calibre/gui2/main.ui | 297 ------------------- src/calibre/gui2/pictureflow/pictureflow.cpp | 6 +- src/calibre/gui2/sidebar.py | 147 --------- src/calibre/gui2/tag_view.py | 33 ++- src/calibre/gui2/ui.py | 43 ++- src/calibre/gui2/widgets.py | 180 +++++++++-- src/calibre/gui2/wizard/__init__.py | 4 +- 11 files changed, 408 insertions(+), 552 deletions(-) delete mode 100644 src/calibre/gui2/sidebar.py diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index bddefe97f8..cbe9449f1f 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -99,6 +99,8 @@ def _config(): help=_('Limit max simultaneous jobs to number of CPUs')) c.add_opt('tag_browser_hidden_categories', default=set(), help=_('tag browser categories not to display')) + c.add_opt('gui_layout', choices=['wide', 'narrow'], + help=_('The layout of the user interface'), default='narrow') return ConfigProxy(c) config = _config() @@ -125,6 +127,11 @@ def available_width(): desktop = QCoreApplication.instance().desktop() return desktop.availableGeometry().width() +try: + is_widescreen = float(available_width())/available_height() > 1.4 +except: + is_widescreen = True + def extension(path): return os.path.splitext(path)[1][1:].lower() diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index 3bd554e891..c2705e5d9b 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -10,10 +10,11 @@ Module to implement the Cover Flow feature import sys, os, time from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \ - QStackedLayout + QStackedLayout, QLabel from calibre import plugins from calibre.gui2 import config, available_height, available_width + pictureflow, pictureflowerror = plugins['pictureflow'] if pictureflow is not None: @@ -78,12 +79,15 @@ if pictureflow is not None: def __init__(self, parent=None): pictureflow.PictureFlow.__init__(self, parent, config['cover_flow_queue_length']+1) - self.setMinimumSize(QSize(10, 10)) + self.setMinimumSize(QSize(300, 150)) self.setFocusPolicy(Qt.WheelFocus) self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.setZoomFactor(150) + def sizeHint(self): + return self.minimumSize() + def wheelEvent(self, ev): ev.accept() if ev.delta() < 0: @@ -107,56 +111,58 @@ class CoverFlowMixin(object): self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync) self.cover_flow_sync_flag = True self.cover_flow = CoverFlow(parent=self) - self.cover_flow.setVisible(False) - if not config['separate_cover_flow']: - self.cb_layout.addWidget(self.cover_flow) self.cover_flow.currentChanged.connect(self.sync_listview_to_cf) 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) - ah, aw = available_height(), available_width() - self._cb_layout_is_horizontal = float(aw)/ah >= 1.4 - self.cb_layout.setDirection(self.cb_layout.LeftToRight if - self._cb_layout_is_horizontal else - self.cb_layout.TopToBottom) - - def toggle_cover_flow_visibility(self, show): + else: + self.cover_flow = QLabel('

'+_('Cover browser could not be loaded') + +'
'+pictureflowerror) + self.cover_flow.setWordWrap(True) if config['separate_cover_flow']: - if show: - d = QDialog(self) - ah, aw = available_height(), available_width() - d.resize(int(aw/1.5), ah-60) - d._layout = QStackedLayout() - d.setLayout(d._layout) - d.setWindowTitle(_('Browse by covers')) - d.layout().addWidget(self.cover_flow) - self.cover_flow.setVisible(True) - self.cover_flow.setFocus(Qt.OtherFocusReason) - d.show() - d.finished.connect(self.sidebar.external_cover_flow_finished) - self.cf_dialog = d - else: - cfd = getattr(self, 'cf_dialog', None) - if cfd is not None: - self.cover_flow.setVisible(False) - cfd.hide() - self.cf_dialog = None + self.cb_splitter.button.clicked.connect(self.toggle_cover_browser) + if CoverFlow is not None: + self.cover_flow.stop.connect(self.hide_cover_browser) else: - if show: - self.cover_flow.setVisible(True) - self.cover_flow.setFocus(Qt.OtherFocusReason) - else: - self.cover_flow.setVisible(False) + self.cb_splitter.insertWidget(self.cb_splitter.side_index, self.cover_flow) + if CoverFlow is not None: + self.cover_flow.stop.connect(self.cb_splitter.hide_side_pane) - def toggle_cover_flow(self, show): - if show: - self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row()) - self.library_view.setCurrentIndex( - self.library_view.currentIndex()) - self.cover_flow_sync_timer.start(500) - self.library_view.scroll_to_row(self.library_view.currentIndex().row()) + def toggle_cover_browser(self): + cbd = getattr(self, 'cb_dialog', None) + if cbd is not None: + self.hide_cover_browser() else: + self.show_cover_browser() + + def show_cover_browser(self): + if CoverFlow is not None: + self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row()) + self.cover_flow_sync_timer.start(500) + self.library_view.setCurrentIndex( + self.library_view.currentIndex()) + self.library_view.scroll_to_row(self.library_view.currentIndex().row()) + + if config['separate_cover_flow']: + d = QDialog(self) + ah, aw = available_height(), available_width() + d.resize(int(aw/1.5), ah-60) + d._layout = QStackedLayout() + d.setLayout(d._layout) + d.setWindowTitle(_('Browse by covers')) + d.layout().addWidget(self.cover_flow) + self.cover_flow.setVisible(True) + self.cover_flow.setFocus(Qt.OtherFocusReason) + d.show() + self.cb_splitter.button.set_state_to_hide() + d.finished.connect(self.cb_splitter.button.set_state_to_show) + self.cb_dialog = d + else: + self.cover_flow.setFocus(Qt.OtherFocusReason) + + def hide_cover_browser(self): + if CoverFlow is not None: self.cover_flow_sync_timer.stop() idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0) if idx.isValid(): @@ -164,7 +170,12 @@ class CoverFlowMixin(object): sm.select(idx, sm.ClearAndSelect|sm.Rows) self.library_view.setCurrentIndex(idx) self.library_view.scroll_to_row(idx.row()) - self.toggle_cover_flow_visibility(show) + + if config['separate_cover_flow']: + cbd = getattr(self, 'cb_dialog', None) + if cbd is not None: + cbd.accept() + self.cb_dialog = None def sync_cf_to_listview(self, current, previous): if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index a991c4d1f8..9ec37df8ff 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -7,12 +7,16 @@ __docformat__ = 'restructuredtext en' import functools -from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon +from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon, QStackedWidget, \ + QWidget, QHBoxLayout, QToolBar, QSize, QSizePolicy from calibre.utils.config import prefs from calibre.ebooks import BOOK_EXTENSIONS -from calibre.constants import isosx -from calibre.gui2 import config +from calibre.constants import isosx, __appname__ +from calibre.gui2 import config, is_widescreen +from calibre.gui2.library.views import BooksView, DeviceBooksView +from calibre.gui2.widgets import Splitter +from calibre.gui2.tag_view import TagBrowserWidget _keep_refs = [] @@ -279,3 +283,116 @@ class LibraryViewMixin(object): # {{{ # }}} +class LibraryWidget(Splitter): # {{{ + + def __init__(self, parent): + orientation = Qt.Vertical if config['gui_layout'] == 'narrow' and \ + not is_widescreen else Qt.Horizontal + #orientation = Qt.Vertical + idx = 0 if orientation == Qt.Vertical else 1 + Splitter.__init__(self, 'cover_browser_splitter', _('Cover Browser'), + I('cover_flow.svg'), + orientation=orientation, parent=parent, + connect_button=not config['separate_cover_flow'], + side_index=idx, initial_side_size=400, initial_show=False) + parent.library_view = BooksView(parent) + parent.library_view.setObjectName('library_view') + self.addWidget(parent.library_view) +# }}} + +class Stack(QStackedWidget): # {{{ + + def __init__(self, parent): + QStackedWidget.__init__(self, parent) + + parent.cb_splitter = LibraryWidget(parent) + self.tb_widget = TagBrowserWidget(parent) + parent.tb_splitter = Splitter('tag_browser_splitter', + _('Tag Browser'), I('tags.svg'), + parent=parent, side_index=0, initial_side_size=200) + parent.tb_splitter.addWidget(self.tb_widget) + parent.tb_splitter.addWidget(parent.cb_splitter) + parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False) + + self.addWidget(parent.tb_splitter) + for x in ('memory', 'card_a', 'card_b'): + name = x+'_view' + w = DeviceBooksView(parent) + setattr(parent, name, w) + self.addWidget(w) + w.setObjectName(name) + + +# }}} + +class SideBar(QToolBar): # {{{ + + + def __init__(self, splitters, jobs_button, parent=None): + QToolBar.__init__(self, _('Side bar'), parent) + self.setOrientation(Qt.Vertical) + self.setMovable(False) + self.setFloatable(False) + self.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.setIconSize(QSize(48, 48)) + self.spacer = QWidget(self) + self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) + for s in splitters: + self.addWidget(s.button) + self.addWidget(self.spacer) + self.addWidget(jobs_button) + + for ch in self.children(): + if isinstance(ch, QToolButton): + ch.setCursor(Qt.PointingHandCursor) + +# }}} + +class LayoutMixin(object): # {{{ + + def __init__(self): + self.setupUi(self) + self.setWindowTitle(__appname__) + + if config['gui_layout'] == 'narrow': + from calibre.gui2.status import StatusBar + self.status_bar = StatusBar(self) + self.stack = Stack(self) + self.bd_splitter = Splitter('book_details_splitter', + _('Book Details'), I('book.svg'), + orientation=Qt.Vertical, parent=self, side_index=1) + self._layout_mem = [QWidget(self), QHBoxLayout()] + self._layout_mem[0].setLayout(self._layout_mem[1]) + l = self._layout_mem[1] + l.addWidget(self.stack) + self.sidebar = SideBar([getattr(self, x+'_splitter') + for x in ('bd', 'tb', 'cb')], self.jobs_button, parent=self) + l.addWidget(self.sidebar) + self.bd_splitter.addWidget(self._layout_mem[0]) + self.bd_splitter.addWidget(self.status_bar) + self.bd_splitter.setCollapsible((self.bd_splitter.side_index+1)%2, False) + self.centralwidget.layout().addWidget(self.bd_splitter) + + def finalize_layout(self): + m = self.library_view.model() + if m.rowCount(None) > 0: + self.library_view.set_current_row(0) + m.current_changed(self.library_view.currentIndex(), + self.library_view.currentIndex()) + + + def save_layout_state(self): + for x in ('library', 'memory', 'card_a', 'card_b'): + getattr(self, x+'_view').save_state() + + for x in ('cb', 'tb', 'bd'): + getattr(self, x+'_splitter').save_state() + + def read_layout_settings(self): + # View states are restored automatically when set_database is called + + for x in ('cb', 'tb', 'bd'): + getattr(self, x+'_splitter').restore_state() + +# }}} + diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 109b001925..6d8efd68fa 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -26,6 +26,15 @@ class BooksView(QTableView): # {{{ def __init__(self, parent, modelcls=BooksModel): QTableView.__init__(self, parent) + + self.setDragEnabled(True) + self.setDragDropOverwriteMode(False) + self.setDragDropMode(self.DragDrop) + self.setAlternatingRowColors(True) + self.setSelectionBehavior(self.SelectRows) + self.setShowGrid(False) + self.setWordWrap(False) + self.rating_delegate = RatingDelegate(self) self.timestamp_delegate = DateDelegate(self) self.pubdate_delegate = PubDateDelegate(self) @@ -434,6 +443,18 @@ class BooksView(QTableView): # {{{ self.scrollTo(self.model().index(row, i)) break + def set_current_row(self, row, select=True): + if row > -1: + h = self.horizontalHeader() + for i in range(h.count()): + if not h.isSectionHidden(i): + index = self.model().index(row, i) + self.setCurrentIndex(index) + if select: + sm = self.selectionModel() + sm.select(index, sm.ClearAndSelect|sm.Rows) + break + def close(self): self._model.close() diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index b4bafc3b79..819bee7f63 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -304,270 +304,6 @@ - - - - - 0 - 100 - - - - Qt::Vertical - - - - - - - - 100 - 100 - - - - 0 - - - - - - - Qt::Horizontal - - - - - - - true - - - true - - - true - - - true - - - - - - - Sort by &popularity - - - - - - - 0 - - - - Match any - - - - - Match all - - - - - - - - Create, edit, and delete user categories - - - Manage &user categories - - - - - - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 0 - 0 - - - - - 30 - 0 - - - - - - - - - @@ -804,26 +540,11 @@ - - BooksView - QTableView -

calibre/gui2/library/views.h
- LocationView QListView
widgets.h
- - DeviceBooksView - QTableView -
calibre/gui2/library/views.h
-
- - TagsView - QTreeView -
calibre/gui2/tag_view.h
-
SearchBox2 QComboBox @@ -834,24 +555,6 @@ QComboBox
calibre.gui2.search_box
- - StatusBar - QWidget -
calibre/gui2/status.h
- 1 -
- - Splitter - QSplitter -
calibre/gui2/widgets.h
- 1 -
- - SideBar - QWidget -
calibre/gui2/sidebar.h
- 1 -
diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp index 9bb9a0954c..60985a1a12 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.cpp +++ b/src/calibre/gui2/pictureflow/pictureflow.cpp @@ -713,8 +713,9 @@ void PictureFlowPrivate::render() QPainter painter; painter.begin(&buffer); - QFont font("Arial", FONT_SIZE); + QFont font = QFont(); font.setBold(true); + font.setPointSize(FONT_SIZE); painter.setFont(font); painter.setPen(Qt::white); //painter.setPen(QColor(255,255,255,127)); @@ -763,8 +764,9 @@ void PictureFlowPrivate::render() QPainter painter; painter.begin(&buffer); - QFont font("Arial", FONT_SIZE); + QFont font = QFont(); font.setBold(true); + font.setPointSize(FONT_SIZE); painter.setFont(font); int leftTextIndex = (step>0) ? centerIndex : centerIndex-1; diff --git a/src/calibre/gui2/sidebar.py b/src/calibre/gui2/sidebar.py deleted file mode 100644 index 6710b5d471..0000000000 --- a/src/calibre/gui2/sidebar.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -from functools import partial - -from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \ - QSize, QToolButton - -from calibre.gui2 import dynamic - -class SideBar(QToolBar): - - toggle_texts = { - 'book_info' : (_('Show Book Details'), _('Hide Book Details')), - 'tag_browser' : (_('Show Tag Browser'), _('Hide Tag Browser')), - 'cover_browser': (_('Show Cover Browser'), _('Hide Cover Browser')), - } - toggle_icons = { - 'book_info' : 'book.svg', - 'tag_browser' : 'tags.svg', - 'cover_browser': 'cover_flow.svg', - } - - - def __init__(self, parent=None): - QToolBar.__init__(self, _('Side bar'), parent) - self.setOrientation(Qt.Vertical) - self.setMovable(False) - self.setFloatable(False) - self.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.setIconSize(QSize(48, 48)) - - for ac in ('book_info', 'tag_browser', 'cover_browser'): - action = self.addAction(QIcon(I(self.toggle_icons[ac])), - self.toggle_texts[ac][1], getattr(self, '_toggle_'+ac)) - setattr(self, 'action_toggle_'+ac, action) - w = self.widgetForAction(action) - w.setCheckable(True) - setattr(self, 'show_'+ac, partial(getattr(self, '_toggle_'+ac), - show=True)) - setattr(self, 'hide_'+ac, partial(getattr(self, '_toggle_'+ac), - show=False)) - - - self.spacer = QWidget(self) - self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) - self.addWidget(self.spacer) - - self.show_cover_browser = partial(self._toggle_cover_browser, show=True) - self.hide_cover_browser = partial(self._toggle_cover_browser, - show=False) - for ch in self.children(): - if isinstance(ch, QToolButton): - ch.setCursor(Qt.PointingHandCursor) - - def initialize(self, jobs_button, cover_browser, toggle_cover_browser, - cover_browser_error, vertical_splitter, horizontal_splitter): - self.cover_browser, self.do_toggle_cover_browser = cover_browser, \ - toggle_cover_browser - if self.cover_browser is None: - self.action_toggle_cover_browser.setEnabled(False) - self.action_toggle_cover_browser.setText( - _('Cover browser could not be loaded: ') + cover_browser_error) - else: - self.cover_browser.stop.connect(self.hide_cover_browser) - self._toggle_cover_browser(dynamic.get('cover_flow_visible', False)) - - self.horizontal_splitter = horizontal_splitter - self.vertical_splitter = vertical_splitter - - tb_state = dynamic.get('tag_browser_state', None) - if tb_state is not None: - self.horizontal_splitter.restoreState(tb_state) - tb_last_open_state = dynamic.get('tag_browser_last_open_state', None) - if tb_last_open_state is not None and \ - not self.horizontal_splitter.is_side_index_hidden: - self.horizontal_splitter.restoreState(tb_last_open_state) - - bi_state = dynamic.get('book_info_state', None) - if bi_state is not None: - self.vertical_splitter.restoreState(bi_state) - bi_last_open_state = dynamic.get('book_info_last_open_state', None) - if bi_last_open_state is not None and \ - not self.vertical_splitter.is_side_index_hidden: - self.vertical_splitter.restoreState(bi_last_open_state) - - self.horizontal_splitter.initialize(name='tag_browser') - self.vertical_splitter.initialize(name='book_info') - self.view_status_changed('book_info', not - self.vertical_splitter.is_side_index_hidden) - self.view_status_changed('tag_browser', not - self.horizontal_splitter.is_side_index_hidden) - self.vertical_splitter.state_changed.connect(partial(self.view_status_changed, - 'book_info'), type=Qt.QueuedConnection) - self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed, - 'tag_browser'), type=Qt.QueuedConnection) - self.addWidget(jobs_button) - - - - def view_status_changed(self, name, visible): - action = getattr(self, 'action_toggle_'+name) - texts = self.toggle_texts[name] - action.setText(texts[int(visible)]) - w = self.widgetForAction(action) - w.setCheckable(True) - w.setChecked(visible) - - def location_changed(self, location): - is_lib = location == 'library' - for ac in ('cover_browser', 'tag_browser'): - ac = getattr(self, 'action_toggle_'+ac) - ac.setEnabled(is_lib) - self.widgetForAction(ac).setVisible(is_lib) - - def save_state(self): - dynamic.set('cover_flow_visible', self.is_cover_browser_visible) - dynamic.set('tag_browser_state', - str(self.horizontal_splitter.saveState())) - dynamic.set('book_info_state', - str(self.vertical_splitter.saveState())) - - - @property - def is_cover_browser_visible(self): - return self.cover_browser is not None and self.cover_browser.isVisible() - - def _toggle_cover_browser(self, show=None): - if show is None: - show = not self.is_cover_browser_visible - self.do_toggle_cover_browser(show) - self.view_status_changed('cover_browser', show) - - def external_cover_flow_finished(self, *args): - self.view_status_changed('cover_browser', False) - - def _toggle_tag_browser(self, show=None): - self.horizontal_splitter.toggle_side_index() - - def _toggle_book_info(self, show=None): - self.vertical_splitter.toggle_side_index() - - diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index b5ced8d626..8c7d466b29 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -10,9 +10,11 @@ Browsing book collection by tags. from itertools import izip from functools import partial -from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \ - QFont, QSize, QIcon, QPoint, \ - QAbstractItemModel, QVariant, QModelIndex, QMenu +from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \ + QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \ + QAbstractItemModel, QVariant, QModelIndex, QMenu, \ + QPushButton, QWidget + from calibre.gui2 import config, NONE from calibre.utils.config import prefs from calibre.library.field_metadata import TagsIcons @@ -633,3 +635,28 @@ class TagBrowserMixin(object): # {{{ # }}} +class TagBrowserWidget(QWidget): # {{{ + + def __init__(self, parent): + QWidget.__init__(self, parent) + self._layout = QVBoxLayout() + self.setLayout(self._layout) + + parent.tags_view = TagsView(parent) + self._layout.addWidget(parent.tags_view) + + parent.popularity = QCheckBox(parent) + parent.popularity.setText(_('Sort by &popularity')) + self._layout.addWidget(parent.popularity) + + parent.tag_match = QComboBox(parent) + for x in (_('Match any'), _('Match all')): + parent.tag_match.addItem(x) + parent.tag_match.setCurrentIndex(0) + self._layout.addWidget(parent.tag_match) + + parent.edit_categories = QPushButton(_('Manage &user categories'), parent) + self._layout.addWidget(parent.edit_categories) + +# }}} + diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 9ff30aa768..17d3c4d627 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -37,7 +37,7 @@ from calibre.gui2 import warning_dialog, choose_files, error_dialog, \ Dispatcher, gprefs, \ max_available_height, config, info_dialog, \ GetMetadata -from calibre.gui2.cover_flow import pictureflowerror, CoverFlowMixin +from calibre.gui2.cover_flow import CoverFlowMixin from calibre.gui2.widgets import ProgressIndicator, IMAGE_EXTENSIONS from calibre.gui2.wizard import move_library from calibre.gui2.dialogs.scheduler import Scheduler @@ -61,7 +61,7 @@ from calibre.library.caches import CoverCache from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.tag_view import TagBrowserMixin -from calibre.gui2.init import ToolbarMixin, LibraryViewMixin +from calibre.gui2.init import ToolbarMixin, LibraryViewMixin, LayoutMixin class Listener(Thread): # {{{ @@ -106,7 +106,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{ # }}} class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, - TagBrowserMixin, CoverFlowMixin, LibraryViewMixin): + TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, LayoutMixin): 'The main GUI' def set_default_thumbnail(self, height): @@ -143,8 +143,15 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self.check_messages_timer.start(1000) Ui_MainWindow.__init__(self) - self.setupUi(self) - self.setWindowTitle(__appname__) + + # Jobs Button {{{ + self.job_manager = JobManager() + self.jobs_dialog = JobsDialog(self, self.job_manager) + self.jobs_button = JobsButton() + self.jobs_button.initialize(self.jobs_dialog, self.job_manager) + # }}} + + LayoutMixin.__init__(self) self.restriction_count_of_books_in_view = 0 self.restriction_count_of_books_in_library = 0 @@ -167,11 +174,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self.progress_indicator = ProgressIndicator(self) self.verbose = opts.verbose self.get_metadata = GetMetadata() - self.read_settings() - self.job_manager = JobManager() self.emailer = Emailer() self.emailer.start() - self.jobs_dialog = JobsDialog(self, self.job_manager) self.upload_memory = {} self.delete_memory = {} self.conversion_jobs = {} @@ -326,17 +330,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self.resize(self.width(), self._calculated_available_height) self.search.setMaximumWidth(self.width()-150) - # Jobs Button {{{ - self.jobs_button = JobsButton() - self.jobs_button.initialize(self.jobs_dialog, self.job_manager) - # }}} - - ####################### Side Bar ############################### - - self.sidebar.initialize(self.jobs_button, self.cover_flow, - self.toggle_cover_flow, pictureflowerror, - self.vertical_splitter, self.horizontal_splitter) - if config['autolaunch_server']: from calibre.library.server.main import start_threaded_server @@ -362,6 +355,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self._add_filesystem_book = Dispatcher(self.__add_filesystem_book) self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection) + self.read_settings() + self.finalize_layout() def do_saved_search_edit(self, search): d = SavedSearchEditor(self, search) @@ -1934,7 +1929,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3 self.stack.setCurrentIndex(page) self.status_bar.reset_info() - self.sidebar.location_changed(location) + for x in ('tb', 'cb'): + splitter = getattr(self, x+'_splitter') + splitter.button.setEnabled(location == 'library') if location == 'library': self.action_edit.setEnabled(True) self.action_merge.setEnabled(True) @@ -2037,14 +2034,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, if geometry is not None: self.restoreGeometry(geometry) self.read_toolbar_settings() + self.read_layout_settings() def write_settings(self): config.set('main_window_geometry', self.saveGeometry()) dynamic.set('sort_history', self.library_view.model().sort_history) - self.sidebar.save_state() - for view in ('library_view', 'memory_view', 'card_a_view', - 'card_b_view'): - getattr(self, view).save_state() + self.save_layout_state() def restart(self): self.quit(restart=True) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 093fa3fc5c..52bd8eda9a 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -4,16 +4,18 @@ __copyright__ = '2008, Kovid Goyal ' Miscellaneous widgets used in the GUI ''' import re, os, traceback + from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \ QListWidgetItem, QTextCharFormat, QApplication, \ QSyntaxHighlighter, QCursor, QColor, QWidget, \ - QPixmap, QPalette, QSplitterHandle, \ + QPixmap, QPalette, QSplitterHandle, QToolButton, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QRegExp, QSettings, QSize, QModelIndex, QSplitter, \ QAbstractButton, QPainter, QLineEdit, QComboBox, \ - QMenu, QStringListModel, QCompleter, QStringList + QMenu, QStringListModel, QCompleter, QStringList, \ + QTimer -from calibre.gui2 import NONE, error_dialog, pixmap_to_data, dynamic +from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.gui2.filename_pattern_ui import Ui_Form from calibre import fit_image, human_readable @@ -927,6 +929,7 @@ class SplitterHandle(QSplitterHandle): self.double_clicked.connect(splitter.double_clicked, type=Qt.QueuedConnection) self.highlight = False + self.setToolTip(_('Drag to resize')+' '+splitter.label) def splitter_moved(self, *args): oh = self.highlight @@ -944,20 +947,62 @@ class SplitterHandle(QSplitterHandle): def mouseDoubleClickEvent(self, ev): self.double_clicked.emit(self) +class LayoutButton(QToolButton): + + def __init__(self, icon, text, splitter, parent=None): + QToolButton.__init__(self, parent) + self.label = text + self.setIcon(QIcon(icon)) + self.setCheckable(True) + + self.splitter = splitter + splitter.state_changed.connect(self.update_state) + + def set_state_to_show(self, *args): + self.setChecked(False) + label =_('Show') + self.setText(label + ' ' + self.label) + + def set_state_to_hide(self, *args): + self.setChecked(True) + label = _('Hide') + self.setText(label + ' ' + self.label) + + def update_state(self, *args): + if self.splitter.is_side_index_hidden: + self.set_state_to_show() + else: + self.set_state_to_hide() + class Splitter(QSplitter): state_changed = pyqtSignal(object) - def __init__(self, *args): - QSplitter.__init__(self, *args) + def __init__(self, name, label, icon, initial_show=True, + initial_side_size=120, connect_button=True, + orientation=Qt.Horizontal, side_index=0, parent=None): + QSplitter.__init__(self, parent) + self.resize_timer = QTimer(self) + self.resize_timer.setSingleShot(True) + self.desired_side_size = initial_side_size + self.desired_show = initial_show + self.resize_timer.setInterval(5) + self.resize_timer.timeout.connect(self.do_resize) + self.setOrientation(orientation) + self.side_index = side_index + self._name = name + self.label = label + self.initial_side_size = initial_side_size + self.initial_show = initial_show self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection) + self.button = LayoutButton(icon, label, self) + if connect_button: + self.button.clicked.connect(self.double_clicked) def createHandle(self): return SplitterHandle(self.orientation(), self) - def initialize(self, name=None): - if name is not None: - self._name = name + def initialize(self): for i in range(self.count()): h = self.handle(i) if h is not None: @@ -965,40 +1010,115 @@ class Splitter(QSplitter): self.state_changed.emit(not self.is_side_index_hidden) def splitter_moved(self, *args): + self.desired_side_size = self.side_index_size self.state_changed.emit(not self.is_side_index_hidden) - @property - def side_index(self): - return 0 if self.orientation() == Qt.Horizontal else 1 - @property def is_side_index_hidden(self): sizes = list(self.sizes()) return sizes[self.side_index] == 0 - def toggle_side_index(self): - self.double_clicked(None) + @property + def save_name(self): + ori = 'horizontal' if self.orientation() == Qt.Horizontal \ + else 'vertical' + return self._name + '_' + ori - def double_clicked(self, handle): - visible = not self.is_side_index_hidden - sizes = list(self.sizes()) - if 0 in sizes: - idx = sizes.index(0) - sizes[idx] = 80 - else: - sizes[self.side_index] = 0 + def print_sizes(self): + if self.count() > 1: + print self.save_name, 'side:', self.side_index_size, 'other:', + print list(self.sizes())[self.other_index] - if visible: - dynamic.set(self._name + '_last_open_state', str(self.saveState())) + @dynamic_property + def side_index_size(self): + def fget(self): + if self.count() < 2: return 0 + return self.sizes()[self.side_index] + + def fset(self, val): + if self.count() < 2: return + if val == 0 and not self.is_side_index_hidden: + self.save_state() + sizes = list(self.sizes()) + for i in range(len(sizes)): + sizes[i] = val if i == self.side_index else 10 self.setSizes(sizes) + total = sum(self.sizes()) + sizes = list(self.sizes()) + for i in range(len(sizes)): + sizes[i] = val if i == self.side_index else total-val + self.setSizes(sizes) + self.initialize() + + return property(fget=fget, fset=fset) + + def do_resize(self, *args): + orig = self.desired_side_size + QSplitter.resizeEvent(self, self._resize_ev) + if orig > 20 and self.desired_show: + c = 0 + while abs(self.side_index_size - orig) > 10 and c < 5: + self.apply_state(self.get_state(), save_desired=False) + c += 1 + + def resizeEvent(self, ev): + if self.resize_timer.isActive(): + self.resize_timer.stop() + self._resize_ev = ev + self.resize_timer.start() + + def get_state(self): + if self.count() < 2: return (False, 200) + return (self.desired_show, self.desired_side_size) + + def apply_state(self, state, save_desired=True): + if state[0]: + self.side_index_size = state[1] + if save_desired: + self.desired_side_size = self.side_index_size else: - state = dynamic.get(self._name+ '_last_open_state', None) - if state is not None: - self.restoreState(state) - else: - self.setSizes(sizes) - self.initialize() + self.side_index_size = 0 + self.desired_show = state[0] + def default_state(self): + return (self.initial_show, self.initial_side_size) + # Public API {{{ + def save_state(self): + if self.count() > 1: + gprefs[self.save_name+'_state'] = self.get_state() + + @property + def other_index(self): + return (self.side_index+1)%2 + + def restore_state(self): + if self.count() > 1: + state = gprefs.get(self.save_name+'_state', + self.default_state()) + self.apply_state(state, save_desired=False) + self.desired_side_size = state[1] + + def toggle_side_pane(self, hide=None): + if hide is None: + action = 'show' if self.is_side_index_hidden else 'hide' + else: + action = 'hide' if hide else 'show' + getattr(self, action+'_side_pane')() + + def show_side_pane(self): + if self.count() < 2 or not self.is_side_index_hidden: + return + self.apply_state((True, self.desired_side_size)) + + def hide_side_pane(self): + if self.count() < 2 or self.is_side_index_hidden: + return + self.apply_state((False, self.desired_side_size)) + + def double_clicked(self, *args): + self.toggle_side_pane() + + # }}} diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 1fc57e8f4e..b831201f2d 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -96,14 +96,14 @@ class Kobo(Device): class Booq(Device): name = 'Booq Reader' manufacturer = 'Booq' - output_profile = 'prs505' + output_profile = 'sony' output_format = 'EPUB' id = 'booq' class TheBook(Device): name = 'The Book' manufacturer = 'Augen' - output_profile = 'prs505' + output_profile = 'sony' output_format = 'EPUB' id = 'thebook' From e3168f8a8149d414c0bbd2685b774e3a099c805c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 21:06:17 -0600 Subject: [PATCH 5/9] Restore linkages between cover browser and book list --- src/calibre/gui2/cover_flow.py | 55 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index c2705e5d9b..f06d912a5d 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -128,6 +128,7 @@ class CoverFlowMixin(object): self.cb_splitter.insertWidget(self.cb_splitter.side_index, self.cover_flow) if CoverFlow is not None: self.cover_flow.stop.connect(self.cb_splitter.hide_side_pane) + self.cb_splitter.button.toggled.connect(self.cover_browser_toggled) def toggle_cover_browser(self): cbd = getattr(self, 'cb_dialog', None) @@ -136,7 +137,14 @@ class CoverFlowMixin(object): else: self.show_cover_browser() - def show_cover_browser(self): + def cover_browser_toggled(self, *args): + if self.cb_splitter.button.isChecked(): + self.cover_browser_shown() + else: + self.cover_browser_hidden() + + def cover_browser_shown(self): + self.cover_flow.setFocus(Qt.OtherFocusReason) if CoverFlow is not None: self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row()) self.cover_flow_sync_timer.start(500) @@ -144,24 +152,7 @@ class CoverFlowMixin(object): self.library_view.currentIndex()) self.library_view.scroll_to_row(self.library_view.currentIndex().row()) - if config['separate_cover_flow']: - d = QDialog(self) - ah, aw = available_height(), available_width() - d.resize(int(aw/1.5), ah-60) - d._layout = QStackedLayout() - d.setLayout(d._layout) - d.setWindowTitle(_('Browse by covers')) - d.layout().addWidget(self.cover_flow) - self.cover_flow.setVisible(True) - self.cover_flow.setFocus(Qt.OtherFocusReason) - d.show() - self.cb_splitter.button.set_state_to_hide() - d.finished.connect(self.cb_splitter.button.set_state_to_show) - self.cb_dialog = d - else: - self.cover_flow.setFocus(Qt.OtherFocusReason) - - def hide_cover_browser(self): + def cover_browser_hidden(self): if CoverFlow is not None: self.cover_flow_sync_timer.stop() idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0) @@ -171,11 +162,27 @@ class CoverFlowMixin(object): self.library_view.setCurrentIndex(idx) self.library_view.scroll_to_row(idx.row()) - if config['separate_cover_flow']: - cbd = getattr(self, 'cb_dialog', None) - if cbd is not None: - cbd.accept() - self.cb_dialog = None + + def show_cover_browser(self): + d = QDialog(self) + ah, aw = available_height(), available_width() + d.resize(int(aw/1.5), ah-60) + d._layout = QStackedLayout() + d.setLayout(d._layout) + d.setWindowTitle(_('Browse by covers')) + d.layout().addWidget(self.cover_flow) + self.cover_flow.setVisible(True) + self.cover_flow.setFocus(Qt.OtherFocusReason) + d.show() + self.cb_splitter.button.set_state_to_hide() + d.finished.connect(self.cb_splitter.button.set_state_to_show) + self.cb_dialog = d + + def hide_cover_browser(self): + cbd = getattr(self, 'cb_dialog', None) + if cbd is not None: + cbd.accept() + self.cb_dialog = None def sync_cf_to_listview(self, current, previous): if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ From 3d2811a24be2f245ff637dab6e6dfe640bb01ce2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 22:19:02 -0600 Subject: [PATCH 6/9] Fix Email to sub menu in send to device menu --- src/calibre/gui2/device.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 6926c751b2..d806890807 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -391,16 +391,14 @@ class DeviceMenu(QMenu): # {{{ default_account = (dest, False, False, I('mail.svg'), _('Email to')+' '+account) action1 = DeviceAction(dest, False, False, I('mail.svg'), - _('Email to')+' '+account, self) + _('Email to')+' '+account) action2 = DeviceAction(dest, True, False, I('mail.svg'), - _('Email to')+' '+account, self) + _('Email to')+' '+account+ _(' and delete from library')) map(self.email_to_menu.addAction, (action1, action2)) map(self._memory.append, (action1, action2)) self.email_to_menu.addSeparator() - self.connect(action1, SIGNAL('a_s(QAction)'), - self.action_triggered) - self.connect(action2, SIGNAL('a_s(QAction)'), - self.action_triggered) + action1.a_s.connect(self.action_triggered) + action2.a_s.connect(self.action_triggered) basic_actions = [ ('main:', False, False, I('reader.svg'), From efe64efe2559897aea789fa0b1974472b0a54b3d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 22:44:08 -0600 Subject: [PATCH 7/9] Fix #5779 (Check database integrity is broken in 0.7 w/custom fields) --- src/calibre/gui2/tag_view.py | 3 ++- src/calibre/library/database2.py | 2 ++ src/calibre/library/field_metadata.py | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index fe7f4348f8..bc698a3502 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -609,7 +609,8 @@ class TagBrowserMixin(object): # {{{ self.tags_view.saved_search_edit.connect(self.do_saved_search_edit) self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed) self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help) - self.edit_categories.clicked.connect(self.do_user_categories_edit) + self.edit_categories.clicked.connect(lambda x: + self.do_user_categories_edit()) def do_user_categories_edit(self, on_category=None): d = TagCategories(self, self.library_view.model().db, on_category) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 0168737fca..7b98dc4537 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1842,6 +1842,8 @@ books_series_link feeds os.remove(self.dbpath) shutil.copyfile(dest, self.dbpath) self.connect() + self.field_metadata.remove_dynamic_categories() + self.field_metadata.remove_custom_fields() self.initialize_dynamic() self.refresh() if os.path.exists(dest): diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 243e3646da..82e4edfdf2 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -379,6 +379,17 @@ class FieldMetadata(dict): self._add_search_terms_to_map(key, [key]) self.custom_label_to_key_map[label] = key + def remove_custom_fields(self): + for key in self.get_custom_fields(): + del self._tb_cats[key] + + def remove_dynamic_categories(self): + for key in list(self._tb_cats.keys()): + val = self._tb_cats[key] + if val['is_category'] and val['kind'] in ('user', 'search'): + del self._tb_cats[key] + + def add_user_category(self, label, name): if label in self._tb_cats: raise ValueError('Duplicate user field [%s]'%(label)) From 0ed7568ae17f99bace5eda387ebd10f5b4656905 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 23:09:10 -0600 Subject: [PATCH 8/9] Updated Economist recipe for new website layout --- resources/recipes/economist.recipe | 7 ++++--- resources/recipes/economist_free.recipe | 16 +++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/resources/recipes/economist.recipe b/resources/recipes/economist.recipe index 4ae0bb8b05..a6d0e08eea 100644 --- a/resources/recipes/economist.recipe +++ b/resources/recipes/economist.recipe @@ -24,9 +24,10 @@ class Economist(BasicNewsRecipe): oldest_article = 7.0 cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg' remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']), - dict(attrs={'class':['dblClkTrk']})] - remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body') + dict(attrs={'class':['dblClkTrk', 'ec-article-info']})] + keep_only_tags = [dict(id='ec-article-body')] needs_subscription = True + no_stylesheets = True preprocess_regexps = [(re.compile('.*', re.DOTALL), lambda x:'')] @@ -87,7 +88,7 @@ class Economist(BasicNewsRecipe): continue a = tag.find('a', href=True) if a is not None: - url=a['href'].replace('displaystory', 'PrinterFriendly').strip() + url=a['href'].split('?')[0]+'/print' if url.startswith('Printer'): url = '/'+url if url.startswith('/'): diff --git a/resources/recipes/economist_free.recipe b/resources/recipes/economist_free.recipe index cdcd457501..1a783521f6 100644 --- a/resources/recipes/economist_free.recipe +++ b/resources/recipes/economist_free.recipe @@ -17,8 +17,9 @@ class Economist(BasicNewsRecipe): oldest_article = 7.0 cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg' remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']), - dict(attrs={'class':['dblClkTrk']})] - remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body') + dict(attrs={'class':['dblClkTrk', 'ec-article-info']})] + keep_only_tags = [dict(id='ec-article-body')] + no_stylesheets = True preprocess_regexps = [(re.compile('.*', re.DOTALL), lambda x:'')] @@ -88,19 +89,20 @@ class Economist(BasicNewsRecipe): br = browser() ret = br.open(url) raw = ret.read() - url = br.geturl().replace('displaystory', 'PrinterFriendly').strip() + url = br.geturl().split('?')[0]+'/print' root = html.fromstring(raw) - matches = root.xpath('//*[@class = "article-section"]') + matches = root.xpath('//*[@class = "ec-article-info"]') feedtitle = 'Miscellaneous' if matches: - feedtitle = string.capwords(html.tostring(matches[0], method='text', - encoding=unicode)) + feedtitle = string.capwords(html.tostring(matches[-1], method='text', + encoding=unicode).split('|')[-1].strip()) return (i, feedtitle, url, title, description, author, published) def eco_article_found(self, req, result): from calibre.web.feeds import Article i, feedtitle, link, title, description, author, published = result - self.log('Found print version for article:', title) + self.log('Found print version for article:', title, 'in', feedtitle, + 'at', link) a = Article(i, title, link, author, description, published, '') From 1dafae3c4a5a8cb6645ab97b6a5a9b3d0fa4ac89 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 23:16:21 -0600 Subject: [PATCH 9/9] News download: Restore article downloaded by calibre footer --- src/calibre/web/feeds/templates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index e98edd0e95..b64795b816 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -65,6 +65,7 @@ class NavBarTemplate(Template): text = 'This article was downloaded by ' p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') p[0].tail = ' from ' + navbar.append(p) navbar.append(BR()) navbar.append(BR()) else: @@ -111,6 +112,7 @@ class TouchscreenNavBarTemplate(Template): text = 'This article was downloaded by ' p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') p[0].tail = ' from ' + navbar.append(p) navbar.append(BR()) navbar.append(BR()) else: