From 9975995ed2e57c3abcf627353f15a0216afeb0a6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jul 2010 22:58:52 -0600 Subject: [PATCH 1/5] New toolbar layout --- src/calibre/gui2/init.py | 18 +- src/calibre/gui2/layout.py | 457 +++++++++++++++++++++++++++++ src/calibre/gui2/main.ui | 556 ------------------------------------ src/calibre/gui2/ui.py | 7 +- src/calibre/gui2/widgets.py | 203 +------------ 5 files changed, 466 insertions(+), 775 deletions(-) create mode 100644 src/calibre/gui2/layout.py delete mode 100644 src/calibre/gui2/main.ui diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 8aaeab3a8a..9a4bb22f82 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import functools, sys, os -from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon, QStackedWidget, \ +from PyQt4.Qt import QMenu, Qt, pyqtSignal, QIcon, QStackedWidget, \ QSize, QSizePolicy, QStatusBar, QUrl, QLabel, QFont from calibre.utils.config import prefs @@ -173,20 +173,6 @@ class ToolbarMixin(object): # {{{ for x in (self.preferences_action, self.action_preferences): x.triggered.connect(self.do_config) - for x in ('news', 'edit', 'sync', 'convert', 'save', 'add', 'view', - 'del', 'preferences'): - w = self.tool_bar.widgetForAction(getattr(self, 'action_'+x)) - w.setPopupMode(w.MenuButtonPopup) - - self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu) - - for ch in self.tool_bar.children(): - if isinstance(ch, QToolButton): - ch.setCursor(Qt.PointingHandCursor) - ch.setStatusTip(ch.toolTip()) - - self.tool_bar.contextMenuEvent = self.no_op - def show_help(self, *args): open_url(QUrl('http://calibre-ebook.com/user_manual')) @@ -435,8 +421,6 @@ class StatusBar(QStatusBar): # {{{ class LayoutMixin(object): # {{{ def __init__(self): - self.setupUi(self) - self.setWindowTitle(__appname__) if config['gui_layout'] == 'narrow': # narrow {{{ self.book_details = BookDetails(False, self) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py new file mode 100644 index 0000000000..623d194d41 --- /dev/null +++ b/src/calibre/gui2/layout.py @@ -0,0 +1,457 @@ +#!/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 PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, QVariant, \ + QAbstractListModel, QFont, QApplication, QPalette, pyqtSignal, QToolButton, \ + QModelIndex, QListView, QAbstractButton, QPainter, QPixmap, QColor, \ + QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QComboBox + +from calibre.constants import __appname__, filesystem_encoding +from calibre.gui2.search_box import SearchBox2, SavedSearchBox +from calibre.gui2.throbber import ThrobbingButton +from calibre.gui2 import NONE +from calibre import human_readable + +class ToolBar(QToolBar): # {{{ + + def __init__(self, parent=None): + QToolBar.__init__(self, parent) + self.setContextMenuPolicy(Qt.PreventContextMenu) + self.setMovable(False) + self.setOrientation(Qt.Horizontal) + self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) + self.setIconSize(QSize(48, 48)) + self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + + def add_actions(self, *args): + self.left_space = QWidget(self) + self.left_space.setSizePolicy(QSizePolicy.Expanding, + QSizePolicy.Minimum) + self.addWidget(self.left_space) + for action in args: + if action is None: + self.addSeparator() + else: + self.addAction(action) + self.right_space = QWidget(self) + self.right_space.setSizePolicy(QSizePolicy.Expanding, + QSizePolicy.Minimum) + self.addWidget(self.right_space) + + def contextMenuEvent(self, *args): + pass + +# }}} + +# Location View {{{ + +class LocationModel(QAbstractListModel): + + devicesChanged = pyqtSignal() + + def __init__(self, parent): + QAbstractListModel.__init__(self, parent) + self.icons = [QVariant(QIcon(I('library.png'))), + QVariant(QIcon(I('reader.svg'))), + QVariant(QIcon(I('sd.svg'))), + QVariant(QIcon(I('sd.svg')))] + self.text = [_('Library\n%d books'), + _('Reader\n%s'), + _('Card A\n%s'), + _('Card B\n%s')] + self.free = [-1, -1, -1] + self.count = 0 + self.highlight_row = 0 + self.library_tooltip = _('Click to see the books available on your computer') + self.tooltips = [ + self.library_tooltip, + _('Click to see the books in the main memory of your reader'), + _('Click to see the books on storage card A in your reader'), + _('Click to see the books on storage card B in your reader') + ] + + def database_changed(self, db): + lp = db.library_path + if not isinstance(lp, unicode): + lp = lp.decode(filesystem_encoding, 'replace') + self.tooltips[0] = self.library_tooltip + '\n\n' + \ + _('Books located at') + ' ' + lp + self.dataChanged.emit(self.index(0), self.index(0)) + + def rowCount(self, *args): + return 1 + len([i for i in self.free if i >= 0]) + + def get_device_row(self, row): + if row == 2 and self.free[1] == -1 and self.free[2] > -1: + row = 3 + return row + + def get_tooltip(self, row, drow): + ans = self.tooltips[row] + if row > 0: + fs = self.free[drow-1] + if fs > -1: + ans += '\n\n%s '%(human_readable(fs)) + _('free') + return ans + + def data(self, index, role): + row = index.row() + drow = self.get_device_row(row) + data = NONE + if role == Qt.DisplayRole: + text = self.text[drow]%(human_readable(self.free[drow-1])) if row > 0 \ + else self.text[drow]%self.count + data = QVariant(text) + elif role == Qt.DecorationRole: + data = self.icons[drow] + elif role in (Qt.ToolTipRole, Qt.StatusTipRole): + ans = self.get_tooltip(row, drow) + data = QVariant(ans) + elif role == Qt.SizeHintRole: + data = QVariant(QSize(155, 90)) + elif role == Qt.FontRole: + font = QFont('monospace') + font.setBold(row == self.highlight_row) + data = QVariant(font) + elif role == Qt.ForegroundRole and row == self.highlight_row: + return QVariant(QApplication.palette().brush( + QPalette.HighlightedText)) + elif role == Qt.BackgroundRole and row == self.highlight_row: + return QVariant(QApplication.palette().brush( + QPalette.Highlight)) + + return data + + def device_connected(self, dev): + self.icons[1] = QIcon(dev.icon) + self.dataChanged.emit(self.index(1), self.index(1)) + + def headerData(self, section, orientation, role): + return NONE + + def update_devices(self, cp=(None, None), fs=[-1, -1, -1]): + if cp is None: + cp = (None, None) + if isinstance(cp, (str, unicode)): + cp = (cp, None) + if len(fs) < 3: + fs = list(fs) + [0] + self.free[0] = fs[0] + self.free[1] = fs[1] + self.free[2] = fs[2] + cpa, cpb = cp + self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1 + self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1 + self.reset() + self.devicesChanged.emit() + + def location_changed(self, row): + self.highlight_row = row + self.dataChanged.emit( + self.index(0), self.index(self.rowCount(QModelIndex())-1)) + + def location_for_row(self, row): + if row == 0: return 'library' + if row == 1: return 'main' + if row == 3: return 'cardb' + return 'carda' if self.free[1] > -1 else 'cardb' + +class LocationView(QListView): + + unmount_device = pyqtSignal() + location_selected = pyqtSignal(object) + + def __init__(self, parent): + QListView.__init__(self, parent) + self.setModel(LocationModel(self)) + self.reset() + self.currentChanged = self.current_changed + + self.eject_button = EjectButton(self) + self.eject_button.hide() + + self.entered.connect(self.item_entered) + self.viewportEntered.connect(self.viewport_entered) + self.eject_button.clicked.connect(self.eject_clicked) + self.model().devicesChanged.connect(self.eject_button.hide) + self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, + QSizePolicy.Expanding)) + self.setMouseTracking(True) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setEditTriggers(self.NoEditTriggers) + self.setTabKeyNavigation(True) + self.setProperty("showDropIndicator", True) + self.setSelectionMode(self.SingleSelection) + self.setIconSize(QSize(40, 40)) + self.setMovement(self.Static) + self.setFlow(self.LeftToRight) + self.setGridSize(QSize(175, 90)) + self.setViewMode(self.ListMode) + self.setWordWrap(True) + self.setObjectName("location_view") + + def eject_clicked(self, *args): + self.unmount_device.emit() + + def count_changed(self, new_count): + self.model().count = new_count + self.model().reset() + + def current_changed(self, current, previous): + if current.isValid(): + i = current.row() + location = self.model().location_for_row(i) + self.location_selected.emit(location) + self.model().location_changed(i) + + def location_changed(self, row): + if 0 <= row and row <= 3: + self.model().location_changed(row) + + def leaveEvent(self, event): + self.unsetCursor() + self.eject_button.hide() + + def item_entered(self, location): + self.setCursor(Qt.PointingHandCursor) + self.eject_button.hide() + + if location.row() == 1: + rect = self.visualRect(location) + + self.eject_button.resize(rect.height()/2, rect.height()/2) + + x, y = rect.left(), rect.top() + x = x + (rect.width() - self.eject_button.width() - 2) + y += 6 + + self.eject_button.move(x, y) + self.eject_button.show() + + def viewport_entered(self): + self.unsetCursor() + self.eject_button.hide() + + +class EjectButton(QAbstractButton): + + def __init__(self, parent): + QAbstractButton.__init__(self, parent) + self.mouse_over = False + + def enterEvent(self, event): + self.mouse_over = True + + def leaveEvent(self, event): + self.mouse_over = False + + def paintEvent(self, event): + painter = QPainter(self) + painter.setClipRect(event.rect()) + image = QPixmap(I('eject')).scaledToHeight(event.rect().height(), + Qt.SmoothTransformation) + + if not self.mouse_over: + alpha_mask = QPixmap(image.width(), image.height()) + color = QColor(128, 128, 128) + alpha_mask.fill(color) + image.setAlphaChannel(alpha_mask) + + painter.drawPixmap(0, 0, image) + + + + +# }}} + +class SearchBar(QWidget): # {{{ + + def __init__(self, parent): + QWidget.__init__(self, parent) + self._layout = l = QHBoxLayout() + self.setLayout(self._layout) + + self.restriction_label = QLabel(_("&Restrict to:")) + l.addWidget(self.restriction_label) + self.restriction_label.setSizePolicy(QSizePolicy.Minimum, + QSizePolicy.Minimum) + + x = QComboBox(self) + x.setMaximumSize(QSize(150, 16777215)) + x.setObjectName("search_restriction") + x.setToolTip(_("Books display will be restricted to those matching the selected saved search")) + l.addWidget(x) + parent.search_restriction = x + + x = QLabel(self) + x.setObjectName("search_count") + l.addWidget(x) + parent.search_count = x + x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + + parent.advanced_search_button = x = QToolButton(self) + x.setIcon(QIcon(I('search.svg'))) + l.addWidget(x) + x.setToolTip(_("Advanced search")) + + self.label = x = QLabel('&Search:') + l.addWidget(self.label) + x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + + x = parent.search = SearchBox2(self) + x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) + x.setObjectName("search") + x.setToolTip(_("

Search the list of books by title, author, publisher, tags, comments, etc.

Words separated by spaces are ANDed")) + l.addWidget(x) + + x = parent.clear_button = QToolButton(self) + x.setIcon(QIcon(I('clear_left.svg'))) + x.setObjectName("clear_button") + l.addWidget(x) + x.setToolTip(_("Reset Quick Search")) + + x = parent.saved_search = SavedSearchBox(self) + x.setMaximumSize(QSize(150, 16777215)) + x.setMinimumContentsLength(15) + x.setObjectName("saved_search") + l.addWidget(x) + + x = parent.copy_search_button = QToolButton(self) + x.setIcon(QIcon(I("search_copy_saved.svg"))) + x.setObjectName("copy_search_button") + l.addWidget(x) + x.setToolTip(_("Copy current search text (instead of search name)")) + + x = parent.save_search_button = QToolButton(self) + x.setIcon(QIcon(I("search_add_saved.svg"))) + x.setObjectName("save_search_button") + l.addWidget(x) + x.setToolTip(_("Save current search under the name shown in the box")) + + x = parent.delete_search_button = QToolButton(self) + x.setIcon(QIcon(I("search_delete_saved.svg"))) + x.setObjectName("delete_search_button") + l.addWidget(x) + x.setToolTip(_("Delete current saved search")) + + self.label.setBuddy(parent.search) + self.restriction_label.setBuddy(parent.search_restriction) + + +# }}} + +class LocationBar(QWidget): # {{{ + + def __init__(self, actions, donate, location_view, parent=None): + QWidget.__init__(self, parent) + self._layout = QHBoxLayout() + self.setLayout(self._layout) + + for ac in actions: + but = self.button_for_action(ac) + self._layout.addWidget(but) + + self._layout.addWidget(donate) + + self._layout.insertWidget(3, location_view) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) + self.setMaximumHeight(but.sizeHint().height()+5) + + def button_for_action(self, ac): + b = QToolButton(self) + b.setDefaultAction(ac) + for x in ('ToolTip', 'StatusTip', 'WhatsThis'): + getattr(b, 'set'+x)(b.text()) + b.setIconSize(QSize(50, 50)) + b.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + + return b +# }}} + +class MainWindowMixin(object): + + def __init__(self): + self.setObjectName('MainWindow') + self.setWindowIcon(QIcon(I('library.png'))) + self.setWindowTitle(__appname__) + + self.setContextMenuPolicy(Qt.NoContextMenu) + self.centralwidget = QWidget(self) + self.setCentralWidget(self.centralwidget) + self._central_widget_layout = QVBoxLayout() + self.centralwidget.setLayout(self._central_widget_layout) + self.resize(1012, 740) + self.donate_button = ThrobbingButton(self.centralwidget) + self.donate_button.set_normal_icon_size(64, 64) + + # Actions {{{ + + def ac(name, text, icon, shortcut=None, tooltip=None): + action = QAction(QIcon(I(icon)), text, self) + text = tooltip if tooltip else text + action.setToolTip(text) + action.setStatusTip(text) + action.setWhatsThis(text) + action.setAutoRepeat(False) + action.setObjectName('action_'+name) + if shortcut: + action.setShortcut(shortcut) + setattr(self, 'action_'+name, action) + + ac('add', _('Add books'), 'add_book.svg', _('A')) + ac('del', _('Remove books'), 'trash.svg', _('Del')) + ac('edit', _('Edit meta info'), 'edit_input.svg', _('E')) + ac('merge', _('Merge book records'), 'merge_books.svg', _('M')) + ac('sync', _('Send to device'), 'sync.svg') + ac('save', _('Save to disk'), 'save.svg', _('S')) + ac('news', _('Fetch news'), 'news.svg', _('F')) + ac('convert', _('Convert books'), 'convert.svg', _('C')) + ac('view', _('View'), 'view.svg', _('V')) + ac('open_containing_folder', _('Open containing folder'), + 'document_open.svg') + ac('show_book_details', _('Show book details'), + 'dialog_information.svg') + ac('books_by_same_author', _('Books by same author'), + 'user_profile.svg') + ac('books_in_this_series', _('Books in this series'), + 'books_in_series.svg') + ac('books_by_this_publisher', _('Books by this publisher'), + 'publisher.png') + ac('books_with_the_same_tags', _('Books with the same tags'), + 'tags.svg') + ac('preferences', _('Preferences'), 'config.svg', _('Ctrl+P')) + ac('help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual")) + + # }}} + + self.tool_bar = ToolBar(self) + self.addToolBar(Qt.BottomToolBarArea, self.tool_bar) + self.tool_bar.add_actions(self.action_convert, self.action_view, + None, self.action_edit, None, + self.action_save, self.action_del, + None, + self.action_help, None, self.action_preferences) + + self.location_view = LocationView(self.centralwidget) + self.search_bar = SearchBar(self) + self.location_bar = LocationBar([self.action_add, self.action_sync, + self.action_news], self.donate_button, self.location_view, self) + + l = self.centralwidget.layout() + l.addWidget(self.location_bar) + l.addWidget(self.search_bar) + + for ch in list(self.tool_bar.children()) + list(self.location_bar.children()): + if isinstance(ch, QToolButton): + ch.setCursor(Qt.PointingHandCursor) + ch.setAutoRaise(True) + if ch is not self.donate_button: + ch.setPopupMode(ch.MenuButtonPopup) + + + diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui deleted file mode 100644 index d89a451cda..0000000000 --- a/src/calibre/gui2/main.ui +++ /dev/null @@ -1,556 +0,0 @@ - - - Kovid Goyal - MainWindow - - - - 0 - 0 - 1012 - 822 - - - - - 0 - 0 - - - - Qt::NoContextMenu - - - __appname__ - - - - :/images/library.png:/images/library.png - - - - - - - - - - 0 - 0 - - - - - 16777215 - 75 - - - - true - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAsNeeded - - - QAbstractItemView::NoEditTriggers - - - true - - - true - - - QAbstractItemView::NoSelection - - - QAbstractItemView::SelectRows - - - - 40 - 40 - - - - QListView::Static - - - QListView::LeftToRight - - - - 175 - 90 - - - - QListView::ListMode - - - true - - - - - - - ... - - - - :/images/donate.svg:/images/donate.svg - - - true - - - - - - - - - - - - 6 - - - 0 - - - - - &Restrict to: - - - search_restriction - - - - - - - - 150 - 16777215 - - - - Books display will be restricted to those matching the selected saved search - - - - - - - set in ui.py - - - - - - - Advanced search - - - ... - - - - :/images/search.svg:/images/search.svg - - - Alt+S - - - - - - - &Search: - - - search - - - - - - - - 0 - 0 - - - - - 700 - 16777215 - - - - <p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed - - - <p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed - - - - - - - Reset Quick Search - - - ... - - - - :/images/clear_left.svg:/images/clear_left.svg - - - - - - - - 150 - 16777215 - - - - - - - 15 - - - - - - - Copy current search text (instead of search name) - - - ... - - - - :/images/search_copy_saved.svg:/images/search_copy_saved.svg - - - - - - - Save current search under the name shown in the box - - - ... - - - - :/images/search_add_saved.svg:/images/search_add_saved.svg - - - - - - - Delete current saved search - - - ... - - - - :/images/search_delete_saved.svg:/images/search_delete_saved.svg - - - - - - - - - - - 0 - 0 - - - - Qt::PreventContextMenu - - - false - - - Qt::Horizontal - - - - 48 - 48 - - - - Qt::ToolButtonTextUnderIcon - - - TopToolBarArea - - - false - - - - - - - - - - - - - - - - - - - :/images/add_book.svg:/images/add_book.svg - - - Add books - - - A - - - false - - - - - - :/images/trash.svg:/images/trash.svg - - - Remove books - - - Remove books - - - Del - - - - - - :/images/edit_input.svg:/images/edit_input.svg - - - Edit meta information - - - E - - - false - - - - - - :/images/merge_books.svg:/images/merge_books.svg - - - Merge book records - - - M - - - false - - - - - false - - - - :/images/sync.svg:/images/sync.svg - - - Send to device - - - - - - :/images/save.svg:/images/save.svg - - - Save to disk - - - S - - - - - - :/images/news.svg:/images/news.svg - - - Fetch news - - - F - - - - - - :/images/convert.svg:/images/convert.svg - - - Convert E-books - - - C - - - - - - :/images/view.svg:/images/view.svg - - - View - - - V - - - - - - :/images/document_open.svg:/images/document_open.svg - - - Open containing folder - - - - - - :/images/dialog_information.svg:/images/dialog_information.svg - - - Show book details - - - - - - :/images/user_profile.svg:/images/user_profile.svg - - - Books by same author - - - - - - :/images/books_in_series.svg:/images/books_in_series.svg - - - Books in this series - - - - - - :/images/publisher.png:/images/publisher.png - - - Books by this publisher - - - - - - :/images/tags.svg:/images/tags.svg - - - Books with the same tags - - - - - - :/images/config.svg:/images/config.svg - - - Preferences - - - Configure calibre - - - Ctrl+P - - - - - - :/images/help.svg:/images/help.svg - - - Help - - - Browse the calibre User Manual - - - F1 - - - - - - LocationView - QListView -

widgets.h
- - - SearchBox2 - QComboBox -
calibre.gui2.search_box
-
- - SavedSearchBox - QComboBox -
calibre.gui2.search_box
-
- - ThrobbingButton - QToolButton -
calibre/gui2/throbber.h
-
- - - - - - diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 80a7e6e004..6bd7b2b502 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -31,7 +31,7 @@ from calibre.gui2.wizard import move_library from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import UpdateMixin from calibre.gui2.main_window import MainWindow -from calibre.gui2.main_ui import Ui_MainWindow +from calibre.gui2.layout import MainWindowMixin from calibre.gui2.device import DeviceMixin from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton from calibre.gui2.dialogs.config import ConfigDialog @@ -91,7 +91,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{ # }}} -class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ +class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{ TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, AnnotationsAction, AddAction, DeleteAction, @@ -120,7 +120,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ self.another_instance_wants_to_talk) self.check_messages_timer.start(1000) - Ui_MainWindow.__init__(self) + MainWindowMixin.__init__(self) # Jobs Button {{{ self.job_manager = JobManager() @@ -281,7 +281,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ self.read_settings() self.finalize_layout() - self.donate_button.set_normal_icon_size(64, 64) self.donate_button.start_animation() def resizeEvent(self, ev): diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index d94d8e7292..e3fd503872 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -5,26 +5,25 @@ Miscellaneous widgets used in the GUI ''' import re, os, traceback -from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \ +from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, \ QListWidgetItem, QTextCharFormat, QApplication, \ QSyntaxHighlighter, QCursor, QColor, QWidget, \ - QPixmap, QPalette, QSplitterHandle, QToolButton, \ + QPixmap, QSplitterHandle, QToolButton, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ - QRegExp, QSettings, QSize, QModelIndex, QSplitter, \ - QAbstractButton, QPainter, QLineEdit, QComboBox, \ + QRegExp, QSettings, QSize, QSplitter, \ + QPainter, QLineEdit, QComboBox, \ QMenu, QStringListModel, QCompleter, QStringList, \ QTimer, QRect 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 +from calibre import fit_image from calibre.utils.fonts import fontconfig from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.utils.config import prefs, XMLConfig from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator -from calibre.constants import filesystem_encoding history = XMLConfig('history') @@ -259,198 +258,6 @@ class ImageView(QWidget): # }}} -class LocationModel(QAbstractListModel): - - def __init__(self, parent): - QAbstractListModel.__init__(self, parent) - self.icons = [QVariant(QIcon(I('library.png'))), - QVariant(QIcon(I('reader.svg'))), - QVariant(QIcon(I('sd.svg'))), - QVariant(QIcon(I('sd.svg')))] - self.text = [_('Library\n%d books'), - _('Reader\n%s'), - _('Card A\n%s'), - _('Card B\n%s')] - self.free = [-1, -1, -1] - self.count = 0 - self.highlight_row = 0 - self.library_tooltip = _('Click to see the books available on your computer') - self.tooltips = [ - self.library_tooltip, - _('Click to see the books in the main memory of your reader'), - _('Click to see the books on storage card A in your reader'), - _('Click to see the books on storage card B in your reader') - ] - - def database_changed(self, db): - lp = db.library_path - if not isinstance(lp, unicode): - lp = lp.decode(filesystem_encoding, 'replace') - self.tooltips[0] = self.library_tooltip + '\n\n' + \ - _('Books located at') + ' ' + lp - self.dataChanged.emit(self.index(0), self.index(0)) - - def rowCount(self, *args): - return 1 + len([i for i in self.free if i >= 0]) - - def get_device_row(self, row): - if row == 2 and self.free[1] == -1 and self.free[2] > -1: - row = 3 - return row - - def get_tooltip(self, row, drow): - ans = self.tooltips[row] - if row > 0: - fs = self.free[drow-1] - if fs > -1: - ans += '\n\n%s '%(human_readable(fs)) + _('free') - return ans - - def data(self, index, role): - row = index.row() - drow = self.get_device_row(row) - data = NONE - if role == Qt.DisplayRole: - text = self.text[drow]%(human_readable(self.free[drow-1])) if row > 0 \ - else self.text[drow]%self.count - data = QVariant(text) - elif role == Qt.DecorationRole: - data = self.icons[drow] - elif role in (Qt.ToolTipRole, Qt.StatusTipRole): - ans = self.get_tooltip(row, drow) - data = QVariant(ans) - elif role == Qt.SizeHintRole: - data = QVariant(QSize(155, 90)) - elif role == Qt.FontRole: - font = QFont('monospace') - font.setBold(row == self.highlight_row) - data = QVariant(font) - elif role == Qt.ForegroundRole and row == self.highlight_row: - return QVariant(QApplication.palette().brush( - QPalette.HighlightedText)) - elif role == Qt.BackgroundRole and row == self.highlight_row: - return QVariant(QApplication.palette().brush( - QPalette.Highlight)) - - return data - - def device_connected(self, dev): - self.icons[1] = QIcon(dev.icon) - self.dataChanged.emit(self.index(1), self.index(1)) - - def headerData(self, section, orientation, role): - return NONE - - def update_devices(self, cp=(None, None), fs=[-1, -1, -1]): - if cp is None: - cp = (None, None) - if isinstance(cp, (str, unicode)): - cp = (cp, None) - if len(fs) < 3: - fs = list(fs) + [0] - self.free[0] = fs[0] - self.free[1] = fs[1] - self.free[2] = fs[2] - cpa, cpb = cp - self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1 - self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1 - self.reset() - self.emit(SIGNAL('devicesChanged()')) - - def location_changed(self, row): - self.highlight_row = row - self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'), - self.index(0), self.index(self.rowCount(QModelIndex())-1)) - - def location_for_row(self, row): - if row == 0: return 'library' - if row == 1: return 'main' - if row == 3: return 'cardb' - return 'carda' if self.free[1] > -1 else 'cardb' - -class LocationView(QListView): - - def __init__(self, parent): - QListView.__init__(self, parent) - self.setModel(LocationModel(self)) - self.reset() - self.currentChanged = self.current_changed - - self.eject_button = EjectButton(self) - self.eject_button.hide() - - self.connect(self, SIGNAL('entered(QModelIndex)'), self.item_entered) - self.connect(self, SIGNAL('viewportEntered()'), self.viewport_entered) - self.connect(self.eject_button, SIGNAL('clicked()'), lambda: self.emit(SIGNAL('umount_device()'))) - self.connect(self.model(), SIGNAL('devicesChanged()'), self.eject_button.hide) - - def count_changed(self, new_count): - self.model().count = new_count - self.model().reset() - - def current_changed(self, current, previous): - if current.isValid(): - i = current.row() - location = self.model().location_for_row(i) - self.emit(SIGNAL('location_selected(PyQt_PyObject)'), location) - self.model().location_changed(i) - - def location_changed(self, row): - if 0 <= row and row <= 3: - self.model().location_changed(row) - - def leaveEvent(self, event): - self.unsetCursor() - self.eject_button.hide() - - def item_entered(self, location): - self.setCursor(Qt.PointingHandCursor) - self.eject_button.hide() - - if location.row() == 1: - rect = self.visualRect(location) - - self.eject_button.resize(rect.height()/2, rect.height()/2) - - x, y = rect.left(), rect.top() - x = x + (rect.width() - self.eject_button.width() - 2) - y += 6 - - self.eject_button.move(x, y) - self.eject_button.show() - - def viewport_entered(self): - self.unsetCursor() - self.eject_button.hide() - - -class EjectButton(QAbstractButton): - - def __init__(self, parent): - QAbstractButton.__init__(self, parent) - self.mouse_over = False - - def enterEvent(self, event): - self.mouse_over = True - - def leaveEvent(self, event): - self.mouse_over = False - - def paintEvent(self, event): - painter = QPainter(self) - painter.setClipRect(event.rect()) - image = QPixmap(I('eject')).scaledToHeight(event.rect().height(), - Qt.SmoothTransformation) - - if not self.mouse_over: - alpha_mask = QPixmap(image.width(), image.height()) - color = QColor(128, 128, 128) - alpha_mask.fill(color) - image.setAlphaChannel(alpha_mask) - - painter.drawPixmap(0, 0, image) - - class FontFamilyModel(QAbstractListModel): From a073f050ec60754e9615ef75eb9ee3c8fa2cb9cf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jul 2010 23:06:07 -0600 Subject: [PATCH 2/5] ... --- src/calibre/gui2/layout.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 623d194d41..b3b98208f7 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -49,7 +49,7 @@ class ToolBar(QToolBar): # {{{ # Location View {{{ -class LocationModel(QAbstractListModel): +class LocationModel(QAbstractListModel): # {{{ devicesChanged = pyqtSignal() @@ -160,6 +160,8 @@ class LocationModel(QAbstractListModel): if row == 3: return 'cardb' return 'carda' if self.free[1] > -1 else 'cardb' +# }}} + class LocationView(QListView): unmount_device = pyqtSignal() @@ -194,6 +196,7 @@ class LocationView(QListView): self.setViewMode(self.ListMode) self.setWordWrap(True) self.setObjectName("location_view") + self.setMaximumHeight(74) def eject_clicked(self, *args): self.unmount_device.emit() @@ -359,8 +362,7 @@ class LocationBar(QWidget): # {{{ self._layout.addWidget(donate) self._layout.insertWidget(3, location_view) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) - self.setMaximumHeight(but.sizeHint().height()+5) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) def button_for_action(self, ac): b = QToolButton(self) From b5dc2db700ff4d7a2ca178a25afe03aa6a1d456c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jul 2010 23:24:02 -0600 Subject: [PATCH 3/5] ... --- src/calibre/gui2/layout.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index b3b98208f7..038c7c4ec2 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -22,6 +22,7 @@ class ToolBar(QToolBar): # {{{ QToolBar.__init__(self, parent) self.setContextMenuPolicy(Qt.PreventContextMenu) self.setMovable(False) + self.setFloatable(False) self.setOrientation(Qt.Horizontal) self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) self.setIconSize(QSize(48, 48)) @@ -348,29 +349,29 @@ class SearchBar(QWidget): # {{{ # }}} -class LocationBar(QWidget): # {{{ +class LocationBar(ToolBar): # {{{ def __init__(self, actions, donate, location_view, parent=None): - QWidget.__init__(self, parent) - self._layout = QHBoxLayout() - self.setLayout(self._layout) + ToolBar.__init__(self, parent) for ac in actions: - but = self.button_for_action(ac) - self._layout.addWidget(but) + self.addAction(ac) - self._layout.addWidget(donate) - - self._layout.insertWidget(3, location_view) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) + self.addWidget(location_view) + self.w = QWidget() + self.w.setLayout(QVBoxLayout()) + self.w.layout().addWidget(donate) + donate.setAutoRaise(True) + donate.setCursor(Qt.PointingHandCursor) + self.addWidget(self.w) + self.setIconSize(QSize(50, 50)) + self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) def button_for_action(self, ac): b = QToolButton(self) b.setDefaultAction(ac) for x in ('ToolTip', 'StatusTip', 'WhatsThis'): getattr(b, 'set'+x)(b.text()) - b.setIconSize(QSize(50, 50)) - b.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) return b # }}} From 3bcf57e81497f014b1a7c4d2feb00ea7e8f75397 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jul 2010 23:28:15 -0600 Subject: [PATCH 4/5] ... --- src/calibre/gui2/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 038c7c4ec2..f3b650f531 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -444,9 +444,9 @@ class MainWindowMixin(object): self.search_bar = SearchBar(self) self.location_bar = LocationBar([self.action_add, self.action_sync, self.action_news], self.donate_button, self.location_view, self) + self.addToolBar(Qt.TopToolBarArea, self.location_bar) l = self.centralwidget.layout() - l.addWidget(self.location_bar) l.addWidget(self.search_bar) for ch in list(self.tool_bar.children()) + list(self.location_bar.children()): From 3bf6888b3423f8d2479ce59673a0a596a63e9f2c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 10 Jul 2010 06:37:49 -0600 Subject: [PATCH 5/5] Fix #6136 (epub-fix fails on ePub check) --- src/calibre/ebooks/epub/fix/container.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/epub/fix/container.py b/src/calibre/ebooks/epub/fix/container.py index 7a7c17427a..b9af66d708 100644 --- a/src/calibre/ebooks/epub/fix/container.py +++ b/src/calibre/ebooks/epub/fix/container.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, posixpath, urllib, sys +import os, posixpath, urllib, sys, re from lxml import etree @@ -160,8 +160,26 @@ class Container(object): mt = mimetype.lower() if mt.endswith('+xml'): parser = etree.XMLParser(no_network=True, huge_tree=not iswindows) - return etree.fromstring(xml_to_unicode(raw, - strip_encoding_pats=True, assume_utf8=True)[0], parser=parser) + raw = xml_to_unicode(raw, + strip_encoding_pats=True, assume_utf8=True, + resolve_entities=True)[0].strip() + idx = raw.find(' -1: + pre = raw[:idx] + raw = raw[idx:] + if ']+)', pre): + val = match.group(2) + if val.startswith('"') and val.endswith('"'): + val = val[1:-1] + user_entities[match.group(1)] = val + if user_entities: + pat = re.compile(r'&(%s);'%('|'.join(user_entities.keys()))) + raw = pat.sub(lambda m:user_entities[m.group(1)], raw) + return etree.fromstring(raw, parser=parser) return raw def write(self, path):