diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index cd43e2f38a..beed7c679d 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -18,7 +18,8 @@ class ANDROID(USBMS): FORMATS = ['epub', 'pdf'] VENDOR_ID = { - 0x0bb4 : { 0x0c02 : [0x100], 0x0c01 : [0x100]}, + # HTC + 0x0bb4 : { 0x0c02 : [0x100], 0x0c01 : [0x100], 0x0ff9 : [0x0100]}, # Motorola 0x22b8 : { 0x41d9 : [0x216], 0x2d67 : [0x100], 0x41db : [0x216]}, diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 7facd4d3bb..e73a341909 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -69,13 +69,15 @@ class PRS505(CLI, Device): def write_cache(prefix): try: - cachep = os.path.join(prefix, self.CACHE_XML) + cachep = os.path.join(prefix, *(self.CACHE_XML.split('/'))) if not os.path.exists(cachep): - try: - os.makedirs(os.path.dirname(cachep), mode=0777) - except: - time.sleep(5) - os.makedirs(os.path.dirname(cachep), mode=0777) + dname = os.path.dirname(cachep) + if not os.path.exists(dname): + try: + os.makedirs(dname, mode=0777) + except: + time.sleep(5) + os.makedirs(dname, mode=0777) with open(cachep, 'wb') as f: f.write(u''' diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 2aabbf2e95..79f9f15248 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -1190,7 +1190,8 @@ class Manifest(object): if item in self.ids: item = self.ids[item] del self.ids[item.id] - del self.hrefs[item.href] + if item.href in self.hrefs: + del self.hrefs[item.href] self.items.remove(item) if item in self.oeb.spine: self.oeb.spine.remove(item) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 78b68a8bfb..876b2cc74c 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -184,11 +184,14 @@ class MessageBox(QMessageBox): -def warning_dialog(parent, title, msg, det_msg='', show=False): +def warning_dialog(parent, title, msg, det_msg='', show=False, + show_copy_button=True): d = MessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok, parent, det_msg) d.setEscapeButton(QMessageBox.Ok) d.setIconPixmap(QPixmap(I('dialog_warning.svg'))) + if not show_copy_button: + d.cb.setVisible(False) if show: return d.exec_() return d @@ -205,11 +208,14 @@ def error_dialog(parent, title, msg, det_msg='', show=False, return d.exec_() return d -def question_dialog(parent, title, msg, det_msg=''): +def question_dialog(parent, title, msg, det_msg='', show_copy_button=True): d = MessageBox(QMessageBox.Question, title, msg, QMessageBox.Yes|QMessageBox.No, parent, det_msg) d.setIconPixmap(QPixmap(I('dialog_information.svg'))) d.setEscapeButton(QMessageBox.No) + if not show_copy_button: + d.cb.setVisible(False) + return d.exec_() == QMessageBox.Yes def info_dialog(parent, title, msg, det_msg='', show=False): diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index b5d145dfc5..e9f551af48 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -656,7 +656,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog): _('The selected column is not a custom column'), show=True) if not question_dialog(self, _('Are you sure?'), _('Do you really want to delete column %s and all its data?') % - self.custcols[col]['name']): + self.custcols[col]['name'], show_copy_button=False): return self.columns.item(idx).setCheckState(False) self.columns.takeItem(idx) @@ -831,7 +831,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog): warning_dialog(self, _('Must restart'), _('The changes you made require that Calibre be ' 'restarted. Please restart as soon as practical.'), - show=True) + show=True, show_copy_button=False) self.parent.must_restart_before_config = True QDialog.accept(self) diff --git a/src/calibre/gui2/dialogs/config/create_custom_column.ui b/src/calibre/gui2/dialogs/config/create_custom_column.ui index 3e0556b815..247fbd9537 100644 --- a/src/calibre/gui2/dialogs/config/create_custom_column.ui +++ b/src/calibre/gui2/dialogs/config/create_custom_column.ui @@ -20,7 +20,7 @@ - Create a custom column + Create or edit custom columns @@ -126,7 +126,7 @@ - Create and edit custom columns + Create or edit custom columns diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 570143f520..3df197e6a5 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -180,27 +180,34 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.formats_changed = True def get_selected_format_metadata(self): - row = self.formats.currentRow() - fmt = self.formats.item(row) - if fmt is None: - if self.formats.count() == 1: - fmt = self.formats.item(0) - if fmt is None: - error_dialog(self, _('No format selected'), - _('No format selected')).exec_() - return None, None - ext = fmt.ext.lower() - if fmt.path is None: - stream = self.db.format(self.row, ext, as_file=True) - else: - stream = open(fmt.path, 'r+b') + old = prefs['read_file_metadata'] + if not old: + prefs['read_file_metadata'] = True try: - mi = get_metadata(stream, ext) - return mi, ext - except: - error_dialog(self, _('Could not read metadata'), - _('Could not read metadata from %s format')%ext).exec_() - return None, None + row = self.formats.currentRow() + fmt = self.formats.item(row) + if fmt is None: + if self.formats.count() == 1: + fmt = self.formats.item(0) + if fmt is None: + error_dialog(self, _('No format selected'), + _('No format selected')).exec_() + return None, None + ext = fmt.ext.lower() + if fmt.path is None: + stream = self.db.format(self.row, ext, as_file=True) + else: + stream = open(fmt.path, 'r+b') + try: + mi = get_metadata(stream, ext) + return mi, ext + except: + error_dialog(self, _('Could not read metadata'), + _('Could not read metadata from %s format')%ext).exec_() + return None, None + finally: + if old != prefs['read_file_metadata']: + prefs['read_file_metadata'] = old def set_metadata_from_format(self): mi, ext = self.get_selected_format_metadata() diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 68f2b8b6ba..8dcb0e6d75 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -150,7 +150,7 @@ - + 6 @@ -288,271 +288,285 @@ - - - - 0 - 100 - - - - Qt::Vertical - - - - - 100 - 100 - - - - 0 - - - - - - - Qt::Horizontal - - - - - - - true - - - true - - - true - - - true - - - - - - - Sort by &popularity - - - - - + + + + + + 0 + 100 + + + + Qt::Vertical + + + + + 100 + 100 + + + + 0 + + + + + + + Qt::Horizontal + + + - - - 0 + + + true + + true + + + true + + + true + + + + + + + Sort by &popularity + + + + + - - Match any - + + + 0 + + + + Match any + + + + + Match all + + + - - Match all - + + + Create, edit, and delete user categories + + + Manage &user categories + + - + - - - Create, edit, and delete user categories - - - Manage &user categories - - + + + + + &Restrict to: + + + search_restriction + + + + + + + + 50 + 0 + + + + Books display will be restricted to those matching the selected saved search + + + + - - - - - - - &Restrict to: - - - search_restriction - - - - - - - - 50 - 0 - - - - Books display will be restricted to those matching the selected saved search - - - - - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - + + + + + 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 + + + + + + + - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - + + + + + + 0 + 0 + + - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - + + @@ -832,6 +846,12 @@
calibre/gui2/widgets.h
1 + + SideBar + QWidget +
calibre/gui2/sidebar.h
+ 1 +
diff --git a/src/calibre/gui2/sidebar.py b/src/calibre/gui2/sidebar.py new file mode 100644 index 0000000000..375aafbaa2 --- /dev/null +++ b/src/calibre/gui2/sidebar.py @@ -0,0 +1,235 @@ +#!/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' + +import re +from functools import partial + +from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \ + QFrame, QVBoxLayout, QLabel, QSize, QCoreApplication, QToolButton + +from calibre.gui2.progress_indicator import ProgressIndicator +from calibre.gui2 import dynamic + +class JobsButton(QFrame): + + def __init__(self, parent): + QFrame.__init__(self, parent) + self.setLayout(QVBoxLayout()) + self.pi = ProgressIndicator(self) + self.layout().addWidget(self.pi) + self.jobs = QLabel(''+_('Jobs:')+' 0') + self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom) + self.layout().addWidget(self.jobs) + self.layout().setAlignment(self.jobs, Qt.AlignHCenter) + self.jobs.setMargin(0) + self.layout().setMargin(0) + self.jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + self.setCursor(Qt.PointingHandCursor) + self.setToolTip(_('Click to see list of active jobs.')) + + def initialize(self, jobs_dialog): + self.jobs_dialog = jobs_dialog + self.jobs_dialog.jobs_view.restore_column_widths() + + def mouseReleaseEvent(self, event): + if self.jobs_dialog.isVisible(): + self.jobs_dialog.jobs_view.write_settings() + self.jobs_dialog.hide() + else: + self.jobs_dialog.jobs_view.read_settings() + self.jobs_dialog.show() + self.jobs_dialog.jobs_view.restore_column_widths() + + @property + def is_running(self): + return self.pi.isAnimated() + + def start(self): + self.pi.startAnimation() + + def stop(self): + self.pi.stopAnimation() + + +class Jobs(ProgressIndicator): + + def initialize(self, jobs_dialog): + self.jobs_dialog = jobs_dialog + + def mouseClickEvent(self, event): + if self.jobs_dialog.isVisible(): + self.jobs_dialog.jobs_view.write_settings() + self.jobs_dialog.hide() + else: + self.jobs_dialog.jobs_view.read_settings() + self.jobs_dialog.show() + self.jobs_dialog.jobs_view.restore_column_widths() + + @property + def is_running(self): + return self.isAnimated() + + def start(self): + self.startAnimation() + + def stop(self): + self.stopAnimation() + + + +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.jobs_button = JobsButton(self) + self.addWidget(self.jobs_button) + + 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_dialog, cover_browser, toggle_cover_browser, + cover_browser_error, vertical_splitter, horizontal_splitter): + self.jobs_button.initialize(jobs_dialog) + 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) + + bi_state = dynamic.get('book_info_state', None) + if bi_state is not None: + self.vertical_splitter.restoreState(bi_state) + self.horizontal_splitter.initialize() + self.vertical_splitter.initialize() + 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) + + + + 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() + + def jobs(self): + src = unicode(self.jobs_button.jobs.text()) + return int(re.search(r'\d+', src).group()) + + def job_added(self, nnum): + jobs = self.jobs_button.jobs + src = unicode(jobs.text()) + num = self.jobs() + text = src.replace(str(num), str(nnum)) + jobs.setText(text) + self.jobs_button.start() + + def job_done(self, nnum): + jobs = self.jobs_button.jobs + src = unicode(jobs.text()) + num = self.jobs() + text = src.replace(str(num), str(nnum)) + jobs.setText(text) + if nnum == 0: + self.no_more_jobs() + + def no_more_jobs(self): + if self.jobs_button.is_running: + self.jobs_button.stop() + QCoreApplication.instance().alert(self, 5000) + + diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py index a66b903a5e..f7bafacf8b 100644 --- a/src/calibre/gui2/status.py +++ b/src/calibre/gui2/status.py @@ -1,15 +1,14 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import os, re, collections +import os, collections from PyQt4.QtGui import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \ - QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame -from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication, pyqtSignal + QSizePolicy, QScrollArea +from PyQt4.QtCore import Qt, QSize, pyqtSignal from calibre import fit_image, preferred_encoding, isosx from calibre.gui2 import config from calibre.gui2.widgets import IMAGE_EXTENSIONS -from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.notify import get_notifier from calibre.ebooks import BOOK_EXTENSIONS from calibre.library.comments import comments_to_html @@ -17,6 +16,7 @@ from calibre.library.comments import comments_to_html class BookInfoDisplay(QWidget): DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS+BOOK_EXTENSIONS + files_dropped = pyqtSignal(object, object) @classmethod def paths_from_event(cls, event): @@ -40,8 +40,7 @@ class BookInfoDisplay(QWidget): def dropEvent(self, event): paths = self.paths_from_event(event) event.setDropAction(Qt.CopyAction) - self.emit(SIGNAL('files_dropped(PyQt_PyObject, PyQt_PyObject)'), event, - paths) + self.files_dropped.emit(event, paths) def dragMoveEvent(self, event): event.acceptProposedAction() @@ -87,6 +86,9 @@ class BookInfoDisplay(QWidget): class BookDataDisplay(QLabel): + + mr = pyqtSignal(int) + def __init__(self): QLabel.__init__(self) self.setText('') @@ -94,7 +96,7 @@ class BookInfoDisplay(QWidget): self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) def mouseReleaseEvent(self, ev): - self.emit(SIGNAL('mr(int)'), 1) + self.mr.emit(1) WEIGHTS = collections.defaultdict(lambda : 100) WEIGHTS[_('Path')] = 0 @@ -103,6 +105,8 @@ class BookInfoDisplay(QWidget): WEIGHTS[_('Series')] = 2 WEIGHTS[_('Tags')] = 3 + show_book_info = pyqtSignal() + def __init__(self, clear_message): QWidget.__init__(self) self.setCursor(Qt.PointingHandCursor) @@ -113,14 +117,14 @@ class BookInfoDisplay(QWidget): self.cover_display = BookInfoDisplay.BookCoverDisplay() self._layout.addWidget(self.cover_display) self.book_data = BookInfoDisplay.BookDataDisplay() - self.connect(self.book_data, SIGNAL('mr(int)'), self.mouseReleaseEvent) + self.book_data.mr.connect(self.mouseReleaseEvent) self._layout.addWidget(self.book_data) self.data = {} self.setVisible(False) self._layout.setAlignment(self.cover_display, Qt.AlignTop|Qt.AlignLeft) def mouseReleaseEvent(self, ev): - self.emit(SIGNAL('show_book_info()')) + self.show_book_info.emit() def show_data(self, data): if data.has_key('cover'): @@ -128,7 +132,7 @@ class BookInfoDisplay(QWidget): else: self.cover_display.setPixmap(self.cover_display.default_pixmap) - rows = u'' + rows, comments = [], '' self.book_data.setText('') self.data = data.copy() keys = data.keys() @@ -142,97 +146,43 @@ class BookInfoDisplay(QWidget): if isinstance(txt, str): txt = txt.decode(preferred_encoding, 'replace') if key == _('Comments'): - txt = comments_to_html(txt) - rows += u'%s:%s'%(key, txt) - self.book_data.setText(u''+rows+u'
') + comments = comments_to_html(txt) + else: + rows.append((key, txt)) + rows = '\n'.join([u'%s:%s'%(k,t) for + k, t in rows]) + if comments: + comments = 'Comments:'+comments + left_pane = u'%s
'%rows + right_pane = u'
%s
'%comments + self.book_data.setText(u'
%s%s
' + % (left_pane, right_pane)) self.clear_message() self.book_data.updateGeometry() self.updateGeometry() self.setVisible(True) -class MovieButton(QFrame): - - def __init__(self, jobs_dialog): - QFrame.__init__(self) - self.setLayout(QVBoxLayout()) - self.pi = ProgressIndicator(self) - self.layout().addWidget(self.pi) - self.jobs = QLabel(''+_('Jobs:')+' 0') - self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom) - self.layout().addWidget(self.jobs) - self.layout().setAlignment(self.jobs, Qt.AlignHCenter) - self.jobs.setMargin(0) - self.layout().setMargin(0) - self.jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) - self.jobs_dialog = jobs_dialog - self.setCursor(Qt.PointingHandCursor) - self.setToolTip(_('Click to see list of active jobs.')) - self.jobs_dialog.jobs_view.restore_column_widths() - - def mouseReleaseEvent(self, event): - if self.jobs_dialog.isVisible(): - self.jobs_dialog.jobs_view.write_settings() - self.jobs_dialog.hide() - else: - self.jobs_dialog.jobs_view.read_settings() - self.jobs_dialog.show() - self.jobs_dialog.jobs_view.restore_column_widths() - - @property - def is_running(self): - return self.pi.isAnimated() - - def start(self): - self.pi.startAnimation() - - def stop(self): - self.pi.stopAnimation() - - -class CoverFlowButton(QToolButton): - - def __init__(self, parent=None): - QToolButton.__init__(self, parent) - self.setIconSize(QSize(80, 80)) - self.setIcon(QIcon(I('cover_flow.svg'))) - self.setCheckable(True) - self.setChecked(False) - self.setAutoRaise(True) - self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)) - self.connect(self, SIGNAL('toggled(bool)'), self.adjust_tooltip) - self.adjust_tooltip(False) - self.setCursor(Qt.PointingHandCursor) - - def adjust_tooltip(self, on): - tt = _('Click to turn off Cover Browsing') if on else _('Click to browse books by their covers') - self.setToolTip(tt) - - def disable(self, reason): - self.setDisabled(True) - self.setToolTip(_('

Browsing books by their covers is disabled.
Import of pictureflow module failed:
')+reason) - class StatusBar(QStatusBar): resized = pyqtSignal(object) + files_dropped = pyqtSignal(object, object) + show_book_info = pyqtSignal() - def initialize(self, jobs_dialog, systray=None): + def initialize(self, systray=None): self.systray = systray self.notifier = get_notifier(systray) - self.movie_button = MovieButton(jobs_dialog) - self.cover_flow_button = CoverFlowButton() - self.addPermanentWidget(self.cover_flow_button) - self.addPermanentWidget(self.movie_button) self.book_info = BookInfoDisplay(self.clearMessage) self.book_info.setAcceptDrops(True) self.scroll_area = QScrollArea() self.scroll_area.setWidget(self.book_info) self.scroll_area.setWidgetResizable(True) - self.connect(self.book_info, SIGNAL('show_book_info()'), self.show_book_info) - self.connect(self.book_info, - SIGNAL('files_dropped(PyQt_PyObject,PyQt_PyObject)'), - self.files_dropped, Qt.QueuedConnection) + self.book_info.show_book_info.connect(self.show_book_info.emit, + type=Qt.QueuedConnection) + self.book_info.files_dropped.connect(self.files_dropped.emit, + type=Qt.QueuedConnection) self.addWidget(self.scroll_area, 100) self.setMinimumHeight(120) self.resized.connect(self.book_info.cover_display.relayout) @@ -241,10 +191,6 @@ class StatusBar(QStatusBar): def resizeEvent(self, ev): self.resized.emit(self.size()) - def files_dropped(self, event, paths): - self.emit(SIGNAL('files_dropped(PyQt_PyObject, PyQt_PyObject)'), event, - paths) - def reset_info(self): self.book_info.show_data({}) @@ -259,33 +205,4 @@ class StatusBar(QStatusBar): self.notifier(msg) return ret - def jobs(self): - src = unicode(self.movie_button.jobs.text()) - return int(re.search(r'\d+', src).group()) - - def show_book_info(self): - self.emit(SIGNAL('show_book_info()')) - - def job_added(self, nnum): - jobs = self.movie_button.jobs - src = unicode(jobs.text()) - num = self.jobs() - text = src.replace(str(num), str(nnum)) - jobs.setText(text) - self.movie_button.start() - - def job_done(self, nnum): - jobs = self.movie_button.jobs - src = unicode(jobs.text()) - num = self.jobs() - text = src.replace(str(num), str(nnum)) - jobs.setText(text) - if nnum == 0: - self.no_more_jobs() - - def no_more_jobs(self): - if self.movie_button.is_running: - self.movie_button.stop() - QCoreApplication.instance().alert(self, 5000) - diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 5cce38bd4f..2671b16580 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -169,7 +169,7 @@ class TagTreeItem(object): def category_data(self, role): if role == Qt.DisplayRole: - return self.name + return QVariant(self.py_name + ' [%d]'%len(self.children)) if role == Qt.DecorationRole: return self.icon if role == Qt.FontRole: diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index a36a7535ab..b35270f963 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -262,16 +262,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): SIGNAL('update_found(PyQt_PyObject)'), self.update_found) self.update_checker.start(2000) ####################### Status Bar ##################### - self.status_bar.initialize(self.jobs_dialog, self.system_tray_icon) - #self.setStatusBar(self.status_bar) - QObject.connect(self.job_manager, SIGNAL('job_added(int)'), - self.status_bar.job_added, Qt.QueuedConnection) - QObject.connect(self.job_manager, SIGNAL('job_done(int)'), - self.status_bar.job_done, Qt.QueuedConnection) - QObject.connect(self.status_bar, SIGNAL('show_book_info()'), - self.show_book_info) - QObject.connect(self.status_bar, SIGNAL('files_dropped(PyQt_PyObject,PyQt_PyObject)'), - self.files_dropped_on_book) + self.status_bar.initialize(self.system_tray_icon) + self.status_bar.show_book_info.connect(self.show_book_info) + self.status_bar.files_dropped.connect(self.files_dropped_on_book) + ####################### Setup Toolbar ##################### md = QMenu() md.addAction(_('Edit metadata individually')) @@ -459,6 +453,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search) + for ch in self.tool_bar.children(): + if isinstance(ch, QToolButton): + ch.setCursor(Qt.PointingHandCursor) + ####################### Library view ######################## similar_menu = QMenu(_('Similar books...')) similar_menu.addAction(self.action_books_by_same_author) @@ -554,12 +552,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.cover_cache = CoverCache(self.library_path) self.cover_cache.start() self.library_view.model().cover_cache = self.cover_cache - self.tags_view.setVisible(False) - self.tag_match.setVisible(False) - self.popularity.setVisible(False) - self.restriction_label.setVisible(False) - self.edit_categories.setVisible(False) - self.search_restriction.setVisible(False) self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_edit_categories) self.tags_view.set_database(db, self.tag_match, self.popularity, self.search_restriction) self.connect(self.tags_view, @@ -626,22 +618,28 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): if not config['separate_cover_flow']: self.library.layout().addWidget(self.cover_flow) 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) 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: - self.status_bar.cover_flow_button.disable(pictureflowerror) self._calculated_available_height = min(max_available_height()-15, self.height()) self.resize(self.width(), self._calculated_available_height) self.search.setMaximumWidth(self.width()-150) + ####################### Side Bar ############################### + + self.sidebar.initialize(self.jobs_dialog, self.cover_flow, + self.toggle_cover_flow, pictureflowerror, + self.vertical_splitter, self.horizontal_splitter) + QObject.connect(self.job_manager, SIGNAL('job_added(int)'), + self.sidebar.job_added, Qt.QueuedConnection) + QObject.connect(self.job_manager, SIGNAL('job_done(int)'), + self.sidebar.job_done, Qt.QueuedConnection) + + + if config['autolaunch_server']: from calibre.library.server import start_threaded_server from calibre.library import server_config @@ -668,19 +666,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.location_view.setCurrentIndex(self.location_view.model().index(0)) - if self.cover_flow is not None and dynamic.get('cover_flow_visible', False): - self.status_bar.cover_flow_button.toggle() - - tb_state = dynamic.get('tag_browser_state', None) - if tb_state is not None: - self.horizontal_splitter.restoreState(tb_state) - self.toggle_tags_view(True) - - bi_state = dynamic.get('book_info_state', None) - if bi_state is not None: - self.vertical_splitter.restoreState(bi_state) - self.horizontal_splitter.initialize() - self.vertical_splitter.initialize() self._add_filesystem_book = Dispatcher(self.__add_filesystem_book) v = self.library_view @@ -782,11 +767,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): if search: self.search.set_search_string(join.join(search)) - - - def uncheck_cover_button(self, *args): - self.status_bar.cover_flow_button.setChecked(False) - def toggle_cover_flow(self, show): if config['separate_cover_flow']: if show: @@ -802,8 +782,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.cover_flow.setFocus(Qt.OtherFocusReason) self.library_view.scrollTo(self.library_view.currentIndex()) d.show() - self.connect(d, SIGNAL('finished(int)'), - self.uncheck_cover_button) + d.finished.connect(self.sidebar.external_cover_flow_finished) self.cf_dialog = d self.cover_flow_sync_timer.start(500) else: @@ -825,8 +804,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.library_view.currentIndex()) self.cover_flow.setVisible(True) self.cover_flow.setFocus(Qt.OtherFocusReason) - #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: @@ -837,26 +814,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): sm = self.library_view.selectionModel() sm.select(idx, sm.ClearAndSelect|sm.Rows) self.library_view.setCurrentIndex(idx) - #self.status_bar.book_info.book_data.setMaximumHeight(1000) - #self.resize(self.width(), self._calculated_available_height) - #self.setMaximumHeight(available_height()) - def toggle_tags_view(self, show): - if show: - self.tags_view.setVisible(True) - self.tag_match.setVisible(True) - self.popularity.setVisible(True) - self.restriction_label.setVisible(True) - self.edit_categories.setVisible(True) - self.search_restriction.setVisible(True) - self.tags_view.setFocus(Qt.OtherFocusReason) - else: - self.tags_view.setVisible(False) - self.tag_match.setVisible(False) - self.popularity.setVisible(False) - self.restriction_label.setVisible(False) - self.edit_categories.setVisible(False) - self.search_restriction.setVisible(False) + ''' Handling of the count of books in a restricted view requires that @@ -887,7 +846,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.restriction_count_of_books_in_view = self.current_view().row_count() t = _("({0} of {1})").format(self.current_view().row_count(), self.restriction_count_of_books_in_view) - self.search_count.setStyleSheet('QLabel { background-color: yellow; }') + self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }') else: # No restriction if all == 'yes': t = _("(all books)") @@ -2330,6 +2289,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): view.resizeColumnsToContents() view.resize_on_select = False self.status_bar.reset_info() + self.sidebar.location_changed(location) if location == 'library': self.action_edit.setEnabled(True) self.action_merge.setEnabled(True) @@ -2337,7 +2297,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.view_menu.actions()[1].setEnabled(True) self.action_open_containing_folder.setEnabled(True) self.action_sync.setEnabled(True) - self.status_bar.cover_flow_button.setEnabled(True) for action in list(self.delete_menu.actions())[1:]: action.setEnabled(True) else: @@ -2347,7 +2306,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.view_menu.actions()[1].setEnabled(False) self.action_open_containing_folder.setEnabled(False) self.action_sync.setEnabled(False) - self.status_bar.cover_flow_button.setEnabled(False) for action in list(self.delete_menu.actions())[1:]: action.setEnabled(False) @@ -2463,11 +2421,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def write_settings(self): config.set('main_window_geometry', self.saveGeometry()) dynamic.set('sort_history', self.library_view.model().sort_history) - dynamic.set('cover_flow_visible', self.cover_flow.isVisible()) - dynamic.set('tag_browser_state', - str(self.horizontal_splitter.saveState())) - dynamic.set('book_info_state', - str(self.vertical_splitter.saveState())) + self.sidebar.save_state() self.library_view.write_settings() if self.device_connected: self.save_device_view_settings() diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index e39b06ea54..db5f222408 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -982,6 +982,12 @@ class SplitterHandle(QSplitterHandle): class Splitter(QSplitter): + state_changed = pyqtSignal(object) + + def __init__(self, *args): + QSplitter.__init__(self, *args) + self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection) + def createHandle(self): return SplitterHandle(self.orientation(), self) @@ -990,6 +996,22 @@ class Splitter(QSplitter): h = self.handle(i) if h is not None: h.splitter_moved() + self.state_changed.emit(not self.is_side_index_hidden) + + def splitter_moved(self, *args): + 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) def double_clicked(self, handle): sizes = list(self.sizes()) @@ -997,8 +1019,7 @@ class Splitter(QSplitter): idx = sizes.index(0) sizes[idx] = 80 else: - idx = 0 if self.orientation() == Qt.Horizontal else 1 - sizes[idx] = 0 + sizes[self.side_index] = 0 self.setSizes(sizes) self.initialize() diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 8a20e66a60..e721a825c8 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -145,6 +145,8 @@ class CustomColumns(object): if v['normalized']: tn = 'custom_column_{0}'.format(i) self.tag_browser_categories[tn] = [v['label'], 'value'] + if v['datatype'] == 'rating': + self.tag_browser_formatters[tn] = lambda x:u'\u2605'*int(round(x/2.)) def get_custom(self, idx, label=None, num=None, index_is_id=False): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index e024638ae3..8e51143ef2 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -127,6 +127,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): 'authors' : ['author', 'name'], 'news' : ['news', 'name'], } + self.tag_browser_formatters = {} self.connect() self.is_case_sensitive = not iswindows and not isosx and \ @@ -632,11 +633,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): else: icon = icon_map['*custom'] tooltip = self.custom_column_label_map[category]['name'] - if ids is None: # no filtering - categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon, tooltip = tooltip) - for r in data if r[2] > 0] - else: # filter out zero-count tags - categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon, tooltip = tooltip) + formatter = self.tag_browser_formatters.get(tn, lambda x: x) + categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0], icon=icon, tooltip = tooltip) for r in data if r[2] > 0] categories['format'] = [] for fmt in self.conn.get('SELECT DISTINCT format FROM data'):