diff --git a/resources/images/user_profile.svg b/resources/images/user_profile.svg index 2fc0eea150..c809eeaeb1 100644 --- a/resources/images/user_profile.svg +++ b/resources/images/user_profile.svg @@ -33,7 +33,7 @@ %s:%s'%(k,t) for k, t in rows]) - if _('Comments') in data and data[_('Comments')]: - comments = comments_to_html(data[_('Comments')]) - rows += u'%s'%comments + if self.vertical: + if _('Comments') in data and data[_('Comments')]: + comments = comments_to_html(data[_('Comments')]) + rows += u'%s'%comments + self.label.setText(u'%s
'%rows) + else: + comments = '' + if _('Comments') in data: + comments = comments_to_html(data[_('Comments')]) + left_pane = u'%s
'%rows + right_pane = u'
%s
'%comments + self.label.setText(u'
%s%s
' + % (left_pane, right_pane)) - self.label.setText(u'%s
'%rows) -class BookDetails(QWidget): +# }}} + +class BookDetails(QWidget): # {{{ resized = pyqtSignal(object) show_book_info = pyqtSignal() @@ -234,20 +255,26 @@ class BookDetails(QWidget): # }}} - def __init__(self, parent=None): + def __init__(self, vertical, parent=None): QWidget.__init__(self, parent) + self.setAcceptDrops(True) self._layout = QVBoxLayout() - + if not vertical: + self._layout.setDirection(self._layout.LeftToRight) self.setLayout(self._layout) - self.cover_view = CoverView(self) + + self.cover_view = CoverView(vertical, self) self.cover_view.relayout(self.size()) self.resized.connect(self.cover_view.relayout, type=Qt.QueuedConnection) - self._layout.addWidget(self.cover_view, alignment=Qt.AlignHCenter) - self.book_info = BookInfo(self) + self._layout.addWidget(self.cover_view) + self.book_info = BookInfo(vertical, self) self._layout.addWidget(self.book_info) self.book_info.link_clicked.connect(self._link_clicked) self.book_info.mr.connect(self.mouseReleaseEvent) - self.setMinimumSize(QSize(190, 200)) + if vertical: + self.setMinimumSize(QSize(190, 200)) + else: + self.setMinimumSize(120, 120) self.setCursor(Qt.PointingHandCursor) def _link_clicked(self, link): @@ -277,5 +304,5 @@ class BookDetails(QWidget): def reset_info(self): self.show_data({}) - +# }}} diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index ba92c0d301..efda00fc97 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -7,7 +7,7 @@ 0 0 - 884 + 1000 730 @@ -89,7 +89,7 @@ 0 0 - 604 + 720 679 @@ -370,7 +370,7 @@ - + Show &average ratings in the tags browser diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 1277cb06c7..f1b989ffd8 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -8,17 +8,18 @@ __docformat__ = 'restructuredtext en' import functools from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon, QStackedWidget, \ - QWidget, QHBoxLayout, QToolBar, QSize, QSizePolicy + QWidget, QHBoxLayout, QToolBar, QSize, QSizePolicy, QStatusBar from calibre.utils.config import prefs from calibre.ebooks import BOOK_EXTENSIONS -from calibre.constants import isosx, __appname__ +from calibre.constants import isosx, __appname__, preferred_encoding 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 -from calibre.gui2.status import StatusBar, HStatusBar from calibre.gui2.book_details import BookDetails +from calibre.gui2.notify import get_notifier + _keep_refs = [] @@ -355,6 +356,27 @@ class SideBar(QToolBar): # {{{ # }}} +class StatusBar(QStatusBar): # {{{ + + def initialize(self, systray=None): + self.systray = systray + self.notifier = get_notifier(systray) + + def show_message(self, msg, timeout=0): + QStatusBar.showMessage(self, msg, timeout) + if self.notifier is not None and not config['disable_tray_notification']: + if isosx and isinstance(msg, unicode): + try: + msg = msg.encode(preferred_encoding) + except UnicodeEncodeError: + msg = msg.encode('utf-8') + self.notifier(msg) + + def clear_message(self): + QStatusBar.clearMessage(self) + +# }}} + class LayoutMixin(object): # {{{ def __init__(self): @@ -362,7 +384,7 @@ class LayoutMixin(object): # {{{ self.setWindowTitle(__appname__) if config['gui_layout'] == 'narrow': - self.status_bar = self.book_details = StatusBar(self) + self.book_details = BookDetails(False, self) self.stack = Stack(self) self.bd_splitter = Splitter('book_details_splitter', _('Book Details'), I('book.svg'), @@ -375,31 +397,37 @@ class LayoutMixin(object): # {{{ 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.addWidget(self.book_details) self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False) self.centralwidget.layout().addWidget(self.bd_splitter) else: - self.status_bar = HStatusBar(self) - self.setStatusBar(self.status_bar) self.bd_splitter = Splitter('book_details_splitter', _('Book Details'), I('book.svg'), initial_side_size=200, orientation=Qt.Horizontal, parent=self, side_index=1) self.stack = Stack(self) self.bd_splitter.addWidget(self.stack) - self.book_details = BookDetails(self) + self.book_details = BookDetails(True, self) self.bd_splitter.addWidget(self.book_details) self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False) self.bd_splitter.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.centralwidget.layout().addWidget(self.bd_splitter) - for x in ('cb', 'tb', 'bd'): - button = getattr(self, x+'_splitter').button - button.setIconSize(QSize(22, 22)) - self.status_bar.addPermanentWidget(button) - self.status_bar.addPermanentWidget(self.jobs_button) + self.status_bar = StatusBar(self) + self.setStatusBar(self.status_bar) + for x in ('cb', 'tb', 'bd'): + button = getattr(self, x+'_splitter').button + button.setIconSize(QSize(22, 22)) + self.status_bar.addPermanentWidget(button) + self.status_bar.addPermanentWidget(self.jobs_button) def finalize_layout(self): + self.status_bar.initialize(self.system_tray_icon) + self.book_details.show_book_info.connect(self.show_book_info) + self.book_details.files_dropped.connect(self.files_dropped_on_book) + self.book_details.open_containing_folder.connect(self.view_folder_for_id) + self.book_details.view_specific_format.connect(self.view_format_by_id) + m = self.library_view.model() if m.rowCount(None) > 0: self.library_view.set_current_row(0) diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py deleted file mode 100644 index 9aa9b8262c..0000000000 --- a/src/calibre/gui2/status.py +++ /dev/null @@ -1,253 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal ' - -import os - -from PyQt4.Qt import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \ - QSizePolicy, QScrollArea, Qt, QSize, pyqtSignal, \ - QPropertyAnimation, QEasingCurve, QDesktopServices, QUrl - - -from calibre import fit_image, preferred_encoding, isosx -from calibre.gui2 import config -from calibre.gui2.widgets import IMAGE_EXTENSIONS -from calibre.gui2.notify import get_notifier -from calibre.ebooks import BOOK_EXTENSIONS -from calibre.library.comments import comments_to_html -from calibre.gui2.book_details import render_rows - -class BookInfoDisplay(QWidget): - - DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS+BOOK_EXTENSIONS - files_dropped = pyqtSignal(object, object) - - @classmethod - def paths_from_event(cls, event): - ''' - Accept a drop event and return a list of paths that can be read from - and represent files with extensions. - ''' - if event.mimeData().hasFormat('text/uri-list'): - urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] - urls = [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] - return [u for u in urls if os.path.splitext(u)[1][1:].lower() in cls.DROPABBLE_EXTENSIONS] - - def dragEnterEvent(self, event): - if int(event.possibleActions() & Qt.CopyAction) + \ - int(event.possibleActions() & Qt.MoveAction) == 0: - return - paths = self.paths_from_event(event) - if paths: - event.acceptProposedAction() - - def dropEvent(self, event): - paths = self.paths_from_event(event) - event.setDropAction(Qt.CopyAction) - self.files_dropped.emit(event, paths) - - def dragMoveEvent(self, event): - event.acceptProposedAction() - - - class BookCoverDisplay(QLabel): # {{{ - - def __init__(self, coverpath=I('book.svg')): - QLabel.__init__(self) - self.animation = QPropertyAnimation(self, 'size', self) - self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo)) - self.animation.setDuration(1000) - self.animation.setStartValue(QSize(0, 0)) - self.setMaximumWidth(81) - self.setMaximumHeight(108) - self.default_pixmap = QPixmap(coverpath) - self.setScaledContents(True) - self.statusbar_height = 120 - self.setPixmap(self.default_pixmap) - - def do_layout(self): - self.animation.stop() - pixmap = self.pixmap() - pwidth, pheight = pixmap.width(), pixmap.height() - width, height = fit_image(pwidth, pheight, - pwidth, self.statusbar_height-20)[1:] - self.setMaximumHeight(height) - try: - aspect_ratio = pwidth/float(pheight) - except ZeroDivisionError: - aspect_ratio = 1 - self.setMaximumWidth(int(aspect_ratio*self.maximumHeight())) - self.animation.setEndValue(self.maximumSize()) - - def setPixmap(self, pixmap): - QLabel.setPixmap(self, pixmap) - self.do_layout() - self.animation.start() - - def sizeHint(self): - return QSize(self.maximumWidth(), self.maximumHeight()) - - def relayout(self, statusbar_size): - self.statusbar_height = statusbar_size.height() - self.do_layout() - - # }}} - - class BookDataDisplay(QLabel): - - mr = pyqtSignal(object) - link_clicked = pyqtSignal(object) - - def __init__(self): - QLabel.__init__(self) - self.setText('') - self.setWordWrap(True) - self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) - self.linkActivated.connect(self.link_activated) - self._link_clicked = False - - def mouseReleaseEvent(self, ev): - QLabel.mouseReleaseEvent(self, ev) - if not self._link_clicked: - self.mr.emit(ev) - self._link_clicked = False - - def link_activated(self, link): - self._link_clicked = True - link = unicode(link) - self.link_clicked.emit(link) - - show_book_info = pyqtSignal() - - def __init__(self, clear_message): - QWidget.__init__(self) - self.setCursor(Qt.PointingHandCursor) - self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) - self._layout = QHBoxLayout() - self.setLayout(self._layout) - self.clear_message = clear_message - self.cover_display = BookInfoDisplay.BookCoverDisplay() - self._layout.addWidget(self.cover_display) - self.book_data = BookInfoDisplay.BookDataDisplay() - 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): - ev.accept() - self.show_book_info.emit() - - def show_data(self, data): - if data.has_key('cover'): - self.cover_display.setPixmap(QPixmap.fromImage(data.pop('cover'))) - else: - self.cover_display.setPixmap(self.cover_display.default_pixmap) - - rows, comments = [], '' - self.book_data.setText('') - self.data = data.copy() - rows = render_rows(self.data) - rows = '\n'.join([u'%s:%s'%(k,t) for - k, t in rows]) - if _('Comments') in self.data: - comments = comments_to_html(self.data[_('Comments')]) - comments = ('%s:'%_('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) - self.setToolTip('

'+_('Click to open Book Details window') + - '

' + _('Path') + ': ' + data.get(_('Path'), '')) - - - -class StatusBarInterface(object): - - def initialize(self, systray=None): - self.systray = systray - self.notifier = get_notifier(systray) - - def show_message(self, msg, timeout=0): - QStatusBar.showMessage(self, msg, timeout) - if self.notifier is not None and not config['disable_tray_notification']: - if isosx and isinstance(msg, unicode): - try: - msg = msg.encode(preferred_encoding) - except UnicodeEncodeError: - msg = msg.encode('utf-8') - self.notifier(msg) - - def clear_message(self): - QStatusBar.clearMessage(self) - -class BookDetailsInterface(object): - - # These signals must be defined in the class implementing this interface - files_dropped = None - show_book_info = None - open_containing_folder = None - view_specific_format = None - - def reset_info(self): - raise NotImplementedError() - - def show_data(self, data): - raise NotImplementedError() - -class HStatusBar(QStatusBar, StatusBarInterface): - pass - -class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface): - - files_dropped = pyqtSignal(object, object) - show_book_info = pyqtSignal() - open_containing_folder = pyqtSignal(int) - view_specific_format = pyqtSignal(int, object) - - resized = pyqtSignal(object) - - def initialize(self, systray=None): - StatusBarInterface.initialize(self, systray=systray) - self.book_info = BookInfoDisplay(self.clear_message) - self.book_info.setAcceptDrops(True) - self.scroll_area = QScrollArea() - self.scroll_area.setWidget(self.book_info) - self.scroll_area.setWidgetResizable(True) - 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.book_info.book_data.link_clicked.connect(self._link_clicked) - self.addWidget(self.scroll_area, 100) - self.setMinimumHeight(120) - self.resized.connect(self.book_info.cover_display.relayout) - self.book_info.cover_display.relayout(self.size()) - - - def _link_clicked(self, link): - typ, _, val = link.partition(':') - if typ == 'path': - self.open_containing_folder.emit(int(val)) - elif typ == 'format': - id_, fmt = val.split(':') - self.view_specific_format.emit(int(id_), fmt) - elif typ == 'devpath': - QDesktopServices.openUrl(QUrl.fromLocalFile(val)) - - - def resizeEvent(self, ev): - self.resized.emit(self.size()) - - def reset_info(self): - self.book_info.show_data({}) - - def show_data(self, data): - self.book_info.show_data(data) - diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 2226520cf2..e9046b7439 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -104,6 +104,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ def __init__(self, opts, parent=None): MainWindow.__init__(self, opts, parent) self.opts = opts + self.setUnifiedTitleAndToolBarOnMac(True) def initialize(self, library_path, db, listener, actions): opts = self.opts @@ -126,8 +127,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ # Jobs Button {{{ self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) - self.jobs_button = JobsButton(horizontal=config['gui_layout'] != - 'narrow') + self.jobs_button = JobsButton(horizontal=True) self.jobs_button.initialize(self.jobs_dialog, self.job_manager) # }}} @@ -216,12 +216,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ self.vanity.setText(self.vanity_template%dict(version=' ', device=' ')) self.device_info = ' ' UpdateMixin.__init__(self, opts) - ####################### Status Bar ##################### - self.status_bar.initialize(self.system_tray_icon) - self.book_details.show_book_info.connect(self.show_book_info) - self.book_details.files_dropped.connect(self.files_dropped_on_book) - self.book_details.open_containing_folder.connect(self.view_folder_for_id) - self.book_details.view_specific_format.connect(self.view_format_by_id) ####################### Setup Toolbar ##################### ToolbarMixin.__init__(self)