Merge from trunk

This commit is contained in:
Charles Haley 2010-07-10 13:58:34 +01:00
commit 4782888466
6 changed files with 490 additions and 773 deletions

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, posixpath, urllib, sys import os, posixpath, urllib, sys, re
from lxml import etree from lxml import etree
@ -160,8 +160,26 @@ class Container(object):
mt = mimetype.lower() mt = mimetype.lower()
if mt.endswith('+xml'): if mt.endswith('+xml'):
parser = etree.XMLParser(no_network=True, huge_tree=not iswindows) parser = etree.XMLParser(no_network=True, huge_tree=not iswindows)
return etree.fromstring(xml_to_unicode(raw, raw = xml_to_unicode(raw,
strip_encoding_pats=True, assume_utf8=True)[0], parser=parser) strip_encoding_pats=True, assume_utf8=True,
resolve_entities=True)[0].strip()
idx = raw.find('<html')
if idx == -1:
idx = raw.find('<HTML')
if idx > -1:
pre = raw[:idx]
raw = raw[idx:]
if '<!DOCTYPE' in pre:
user_entities = {}
for match in re.finditer(r'<!ENTITY\s+(\S+)\s+([^>]+)', 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 return raw
def write(self, path): def write(self, path):

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
import functools, sys, os 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 QSize, QSizePolicy, QStatusBar, QUrl, QLabel, QFont
from calibre.utils.config import prefs from calibre.utils.config import prefs
@ -173,20 +173,6 @@ class ToolbarMixin(object): # {{{
for x in (self.preferences_action, self.action_preferences): for x in (self.preferences_action, self.action_preferences):
x.triggered.connect(self.do_config) 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): def show_help(self, *args):
open_url(QUrl('http://calibre-ebook.com/user_manual')) open_url(QUrl('http://calibre-ebook.com/user_manual'))
@ -435,8 +421,6 @@ class StatusBar(QStatusBar): # {{{
class LayoutMixin(object): # {{{ class LayoutMixin(object): # {{{
def __init__(self): def __init__(self):
self.setupUi(self)
self.setWindowTitle(__appname__)
if config['gui_layout'] == 'narrow': # narrow {{{ if config['gui_layout'] == 'narrow': # narrow {{{
self.book_details = BookDetails(False, self) self.book_details = BookDetails(False, self)

460
src/calibre/gui2/layout.py Normal file
View File

@ -0,0 +1,460 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__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.setFloatable(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")
self.setMaximumHeight(74)
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(_("<p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>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(ToolBar): # {{{
def __init__(self, actions, donate, location_view, parent=None):
ToolBar.__init__(self, parent)
for ac in actions:
self.addAction(ac)
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())
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)
self.addToolBar(Qt.TopToolBarArea, self.location_bar)
l = self.centralwidget.layout()
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)

View File

@ -1,551 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>Kovid Goyal</author>
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1012</width>
<height>822</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="windowTitle">
<string>__appname__</string>
</property>
<property name="windowIcon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="LocationView" name="location_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>75</height>
</size>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
<property name="showDropIndicator" stdset="0">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="movement">
<enum>QListView::Static</enum>
</property>
<property name="flow">
<enum>QListView::LeftToRight</enum>
</property>
<property name="gridSize">
<size>
<width>175</width>
<height>90</height>
</size>
</property>
<property name="viewMode">
<enum>QListView::ListMode</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ThrobbingButton" name="donate_button">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/donate.svg</normaloff>:/images/donate.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="hl234">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="ComboBoxWithHelp" name="search_restriction">
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Books display will be restricted to those matching the selected saved search</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="search_count">
<property name="text">
<string>set in ui.py</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="advanced_search_button">
<property name="toolTip">
<string>Advanced search</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/search.svg</normaloff>:/images/search.svg</iconset>
</property>
<property name="shortcut">
<string>Alt+S</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Search:</string>
</property>
<property name="buddy">
<cstring>search</cstring>
</property>
</widget>
</item>
<item>
<widget class="SearchBox2" name="search">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>700</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>&lt;p&gt;Search the list of books by title, author, publisher, tags, comments, etc.&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property>
<property name="whatsThis">
<string>&lt;p&gt;Search the list of books by title, author, publisher, tags, comments, etc.&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="clear_button">
<property name="toolTip">
<string>Reset Quick Search</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/clear_left.svg</normaloff>:/images/clear_left.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="SavedSearchBox" name="saved_search">
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
<property name="minimumContentsLength">
<number>15</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="copy_search_button">
<property name="toolTip">
<string>Copy current search text (instead of search name)</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/search_copy_saved.svg</normaloff>:/images/search_copy_saved.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="save_search_button">
<property name="toolTip">
<string>Save current search under the name shown in the box</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/search_add_saved.svg</normaloff>:/images/search_add_saved.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="delete_search_button">
<property name="toolTip">
<string>Delete current saved search</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/search_delete_saved.svg</normaloff>:/images/search_delete_saved.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QToolBar" name="tool_bar">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_edit"/>
<addaction name="action_convert"/>
<addaction name="action_view"/>
<addaction name="action_news"/>
<addaction name="separator"/>
<addaction name="action_sync"/>
<addaction name="action_save"/>
<addaction name="action_del"/>
<addaction name="separator"/>
<addaction name="action_help"/>
<addaction name="separator"/>
<addaction name="action_preferences"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/add_book.svg</normaloff>:/images/add_book.svg</iconset>
</property>
<property name="text">
<string>Add books</string>
</property>
<property name="shortcut">
<string>A</string>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
</action>
<action name="action_del">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/trash.svg</normaloff>:/images/trash.svg</iconset>
</property>
<property name="text">
<string>Remove books</string>
</property>
<property name="toolTip">
<string>Remove books</string>
</property>
<property name="shortcut">
<string>Del</string>
</property>
</action>
<action name="action_edit">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/edit_input.svg</normaloff>:/images/edit_input.svg</iconset>
</property>
<property name="text">
<string>Edit meta information</string>
</property>
<property name="shortcut">
<string>E</string>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
</action>
<action name="action_merge">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/merge_books.svg</normaloff>:/images/merge_books.svg</iconset>
</property>
<property name="text">
<string>Merge book records</string>
</property>
<property name="shortcut">
<string>M</string>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
</action>
<action name="action_sync">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/sync.svg</normaloff>:/images/sync.svg</iconset>
</property>
<property name="text">
<string>Send to device</string>
</property>
</action>
<action name="action_save">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/save.svg</normaloff>:/images/save.svg</iconset>
</property>
<property name="text">
<string>Save to disk</string>
</property>
<property name="shortcut">
<string>S</string>
</property>
</action>
<action name="action_news">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/news.svg</normaloff>:/images/news.svg</iconset>
</property>
<property name="text">
<string>Fetch news</string>
</property>
<property name="shortcut">
<string>F</string>
</property>
</action>
<action name="action_convert">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/convert.svg</normaloff>:/images/convert.svg</iconset>
</property>
<property name="text">
<string>Convert E-books</string>
</property>
<property name="shortcut">
<string>C</string>
</property>
</action>
<action name="action_view">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/view.svg</normaloff>:/images/view.svg</iconset>
</property>
<property name="text">
<string>View</string>
</property>
<property name="shortcut">
<string>V</string>
</property>
</action>
<action name="action_open_containing_folder">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
</property>
<property name="text">
<string>Open containing folder</string>
</property>
</action>
<action name="action_show_book_details">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/dialog_information.svg</normaloff>:/images/dialog_information.svg</iconset>
</property>
<property name="text">
<string>Show book details</string>
</property>
</action>
<action name="action_books_by_same_author">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
</property>
<property name="text">
<string>Books by same author</string>
</property>
</action>
<action name="action_books_in_this_series">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/books_in_series.svg</normaloff>:/images/books_in_series.svg</iconset>
</property>
<property name="text">
<string>Books in this series</string>
</property>
</action>
<action name="action_books_by_this_publisher">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/publisher.png</normaloff>:/images/publisher.png</iconset>
</property>
<property name="text">
<string>Books by this publisher</string>
</property>
</action>
<action name="action_books_with_the_same_tags">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/tags.svg</normaloff>:/images/tags.svg</iconset>
</property>
<property name="text">
<string>Books with the same tags</string>
</property>
</action>
<action name="action_preferences">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset>
</property>
<property name="text">
<string>Preferences</string>
</property>
<property name="toolTip">
<string>Configure calibre</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
<action name="action_help">
<property name="icon">
<iconset resource="../../../resources/images.qrc">
<normaloff>:/images/help.svg</normaloff>:/images/help.svg</iconset>
</property>
<property name="text">
<string>Help</string>
</property>
<property name="toolTip">
<string>Browse the calibre User Manual</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>LocationView</class>
<extends>QListView</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>SearchBox2</class>
<extends>QComboBox</extends>
<header>calibre.gui2.search_box</header>
</customwidget>
<customwidget>
<class>SavedSearchBox</class>
<extends>QComboBox</extends>
<header>calibre.gui2.search_box</header>
</customwidget>
<customwidget>
<class>ComboBoxWithHelp</class>
<extends>QComboBox</extends>
<header>calibre.gui2.widgets</header>
</customwidget>
<customwidget>
<class>ThrobbingButton</class>
<extends>QToolButton</extends>
<header>calibre/gui2/throbber.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -31,7 +31,7 @@ from calibre.gui2.wizard import move_library
from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import UpdateMixin from calibre.gui2.update import UpdateMixin
from calibre.gui2.main_window import MainWindow 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.device import DeviceMixin
from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
from calibre.gui2.dialogs.config import ConfigDialog 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, TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
AnnotationsAction, AddAction, DeleteAction, AnnotationsAction, AddAction, DeleteAction,
@ -120,7 +120,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
self.another_instance_wants_to_talk) self.another_instance_wants_to_talk)
self.check_messages_timer.start(1000) self.check_messages_timer.start(1000)
Ui_MainWindow.__init__(self) MainWindowMixin.__init__(self)
# Jobs Button {{{ # Jobs Button {{{
self.job_manager = JobManager() self.job_manager = JobManager()
@ -281,7 +281,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
self.read_settings() self.read_settings()
self.finalize_layout() self.finalize_layout()
self.donate_button.set_normal_icon_size(64, 64)
self.donate_button.start_animation() self.donate_button.start_animation()
def resizeEvent(self, ev): def resizeEvent(self, ev):

View File

@ -5,26 +5,25 @@ Miscellaneous widgets used in the GUI
''' '''
import re, os, traceback import re, os, traceback
from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, \
QListWidgetItem, QTextCharFormat, QApplication, \ QListWidgetItem, QTextCharFormat, QApplication, \
QSyntaxHighlighter, QCursor, QColor, QWidget, \ QSyntaxHighlighter, QCursor, QColor, QWidget, \
QPixmap, QPalette, QSplitterHandle, QToolButton, \ QPixmap, QSplitterHandle, QToolButton, \
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \
QRegExp, QSettings, QSize, QModelIndex, QSplitter, \ QRegExp, QSettings, QSize, QSplitter, \
QAbstractButton, QPainter, QLineEdit, QComboBox, \ QPainter, QLineEdit, QComboBox, \
QMenu, QStringListModel, QCompleter, QStringList, \ QMenu, QStringListModel, QCompleter, QStringList, \
QTimer, QRect QTimer, QRect
from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
from calibre.gui2.filename_pattern_ui import Ui_Form 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.utils.fonts import fontconfig
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.ebooks.metadata.meta import metadata_from_filename
from calibre.utils.config import prefs, XMLConfig from calibre.utils.config import prefs, XMLConfig
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
from calibre.constants import filesystem_encoding
history = XMLConfig('history') 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): class FontFamilyModel(QAbstractListModel):