Implement a 'Browse books by cover' feature

This commit is contained in:
Kovid Goyal 2008-05-23 19:07:41 -07:00
parent d669477b23
commit fcf35ac0b3
11 changed files with 372 additions and 97 deletions

View File

@ -25,15 +25,17 @@ manual:
pictureflow :
mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \
cd src/calibre/gui2/pictureflow && rm -f *.o *.so* && \
cd src/calibre/gui2/pictureflow && rm -f *.o && \
mkdir -p .build && cd .build && rm -f * && \
qmake ../pictureflow-lib.pro && make && \
qmake ../pictureflow.pro && make && \
cd ../PyQt && \
mkdir -p .build && \
cd .build && rm -f * && \
python ../configure.py && make && \
cd ../../../../../.. && \
cp src/calibre/gui2/pictureflow/libpictureflow.so.?.?.? src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \
python -c "import os, glob; lp = glob.glob('src/calibre/plugins/libpictureflow.so.*')[0]; os.rename(lp, lp[:-4])"
cp src/calibre/gui2/pictureflow/.build/libpictureflow.so.?.?.? src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \
python -c "import os, glob; lp = glob.glob('src/calibre/plugins/libpictureflow.so.*')[0]; os.rename(lp, lp[:-4])" && \
rm -rf src/calibre/gui2/pictureflow/.build rm -rf src/calibre/gui2/pictureflow/PyQt/.build

View File

@ -177,11 +177,18 @@ _check_symlinks_prescript()
try:
print 'Building pictureflow'
os.chdir('src/calibre/gui2/pictureflow')
for f in glob.glob('*.o'): os.unlink(f)
subprocess.check_call([qmake, 'pictureflow.pro'])
if not os.path.exists('.build'):
os.mkdir('.build')
os.chdir('.build')
for f in glob.glob('*'): os.unlink(f)
subprocess.check_call([qmake, '../pictureflow.pro'])
subprocess.check_call(['make'])
files.append((os.path.abspath(os.path.realpath('libpictureflow.dylib')), 'libpictureflow.dylib'))
os.chdir('PyQt/.build')
os.chdir('../PyQt')
if not os.path.exists('.build'):
os.mkdir('.build')
os.chdir('.build')
for f in glob.glob('*'): os.unlink(f)
subprocess.check_call([PYTHON, '../configure.py'])
subprocess.check_call(['/usr/bin/make'])
files.append((os.path.abspath('pictureflow.so'), 'pictureflow.so'))

View File

@ -555,6 +555,9 @@ if islinux:
sys.path.insert(1, plugins)
cwd = os.getcwd()
os.chdir(plugins)
if iswindows and hasattr(sys, 'frozen'):
sys.path.insert(1, os.path.dirname(sys.executable))
try:
import pictureflow
pictureflowerror = ''

View File

@ -8,14 +8,17 @@ Module to implement the Cover Flow feature
'''
import sys, os
from collections import deque
from PyQt4.QtGui import QImage
from PyQt4.QtCore import Qt, QSize, QTimer, SIGNAL
from PyQt4.QtGui import QImage, QSizePolicy
from PyQt4.QtCore import Qt, QSize, SIGNAL
from calibre import pictureflow
if pictureflow is not None:
class EmptyImageList(pictureflow.FlowImages):
def __init__(self):
pictureflow.FlowImages.__init__(self)
class FileSystemImages(pictureflow.FlowImages):
def __init__(self, dirpath):
@ -46,60 +49,35 @@ if pictureflow is not None:
def __init__(self, model, buffer=20):
pictureflow.FlowImages.__init__(self)
self.model = model
self.default_image = QImage(':/images/book.svg')
self.buffer_size = buffer
self.timer = QTimer()
self.connect(self.timer, SIGNAL('timeout()'), self.load)
self.timer.start(50)
self.clear()
self.connect(self.model, SIGNAL('modelReset()'), self.reset)
def count(self):
return self.model.rowCount(None)
return self.model.count()
def caption(self, index):
return self.model.title(index)
def clear(self):
self.buffer = {}
self.load_queue = deque()
def reset(self):
self.emit(SIGNAL('dataChanged()'))
def load(self):
if self.load_queue:
index = self.load_queue.popleft()
if self.buffer.has_key(index):
return
img = QImage()
img.loadFromData(self.model.cover(index))
if img.isNull():
img = self.default_image
self.buffer[index] = img
def image(self, index):
img = self.buffer.get(index)
if img is None:
img = QImage()
img.loadFromData(self.model.cover(index))
if img.isNull():
img = self.default_image
self.buffer[index] = img
return img
return self.model.cover(index)
def currentChanged(self, index):
for key in self.buffer.keys():
if abs(key - index) > self.buffer_size:
self.buffer.pop(key)
for i in range(max(0, index-self.buffer_size), min(self.count(), index+self.buffer_size)):
if not self.buffer.has_key(i):
self.load_queue.append(i)
class CoverFlow(pictureflow.PictureFlow):
def __init__(self, height=300, parent=None):
pictureflow.PictureFlow.__init__(self, parent)
self.setSlideSize(QSize(int(2/3. * height), height))
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height))
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
self.setFocusPolicy(Qt.WheelFocus)
self.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
else:
CoverFlow = None
DatabaseImages = None
FileSystemImages = None
def main(args=sys.argv):
return 0
@ -112,16 +90,7 @@ if __name__ == '__main__':
cf.resize(cf.minimumSize())
w.resize(cf.minimumSize()+QSize(30, 20))
path = sys.argv[1]
if path.endswith('.db'):
from calibre.library.database import LibraryDatabase
from calibre.gui2.library import BooksModel
from calibre.gui2 import images_rc
bm = BooksModel()
bm.set_database(LibraryDatabase(path))
bm.sort(1, Qt.AscendingOrder)
model = DatabaseImages(bm)
else:
model = FileSystemImages(sys.argv[1])
model = FileSystemImages(sys.argv[1])
cf.setImages(model)
cf.connect(cf, SIGNAL('currentChanged(int)'), model.currentChanged)
w.setCentralWidget(cf)

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
'''
import textwrap
from PyQt4.QtCore import Qt, QCoreApplication
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
@ -19,11 +19,6 @@ class BookInfo(QDialog, Ui_BookInfo):
Ui_BookInfo.__init__(self)
self.setupUi(self)
self.default_pixmap = QPixmap(':/images/book.svg').scaled(80,
100,
Qt.IgnoreAspectRatio,
Qt.SmoothTransformation)
self.setWindowTitle(info[_('Title')])
desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry().height() - 100
@ -32,11 +27,7 @@ class BookInfo(QDialog, Ui_BookInfo):
self.comments.setText(info.pop(_('Comments'), ''))
cdata = info.pop('cover', '')
pixmap = QPixmap()
pixmap.loadFromData(cdata)
if pixmap.isNull():
pixmap = self.default_pixmap
pixmap = QPixmap.fromImage(cdata)
self.setWindowIcon(QIcon(pixmap))
self.scene = QGraphicsScene()

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.45"
version="1.0"
sodipodi:docname="emblem-link.svgz"
sodipodi:docbase="/home/david/Oxygen/Oxygen/scalable/emblems"
inkscape:output_extension="org.inkscape.output.svgz.inkscape"
inkscape:export-filename="/home/david/Oxygen/Oxygen/scalable/emblems/emblem-link.png"
inkscape:export-xdpi="33.75"
inkscape:export-ydpi="33.75"
sodipodi:modified="TRUE">
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient3185">
<stop
style="stop-color:#eeeeee;stop-opacity:1;"
offset="0"
id="stop3187" />
<stop
style="stop-color:#eeeeee;stop-opacity:0;"
offset="1"
id="stop3189" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient3133">
<stop
style="stop-color:#646661;stop-opacity:1"
offset="0"
id="stop3135" />
<stop
style="stop-color:#111111;stop-opacity:1"
offset="1"
id="stop3137" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3133"
id="radialGradient3139"
cx="64"
cy="35.686314"
fx="64"
fy="35.686314"
r="40"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.1360441,0,0,1.446027,-72.706823,-26.184217)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3185"
id="linearGradient3191"
x1="112"
y1="98.41069"
x2="61.978939"
y2="11.771669"
gradientUnits="userSpaceOnUse" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3393">
<path
style="opacity:1;fill:#dbdbdb;fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 13.8125,8 C 10.584167,8 8,10.584167 8,13.8125 L 8,114.1875 C 8,117.41583 10.584167,120 13.8125,120 L 114.1875,120 C 117.41583,120 120,117.41583 120,114.1875 L 120,13.8125 C 120,10.584167 117.41583,8 114.1875,8 L 13.8125,8 z M 21.8125,16 L 106.1875,16 C 109.41583,16 112,18.584167 112,21.8125 L 112,106.1875 C 112,109.41583 109.41583,112 106.1875,112 L 21.8125,112 C 18.584166,112 16,109.41583 16,106.1875 L 16,21.8125 C 16,18.584166 18.584167,16 21.8125,16 z "
id="path3395" />
</clipPath>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3133"
id="radialGradient3413"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.1360441,0,0,-1.446027,-72.706823,154.18422)"
cx="64"
cy="35.686314"
fx="64"
fy="35.686314"
r="40" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3133"
id="radialGradient3417"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.1360441,0,0,1.446027,-72.706823,-25.184217)"
cx="64"
cy="35.686314"
fx="64"
fy="35.686314"
r="40" />
<filter
inkscape:collect="always"
id="filter3351">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="2"
id="feGaussianBlur3353" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="4.9765625"
inkscape:cx="64"
inkscape:cy="64"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="128px"
height="128px"
gridempspacing="2"
gridspacingx="4px"
gridspacingy="4px"
showgrid="false"
inkscape:window-width="748"
inkscape:window-height="734"
inkscape:window-x="400"
inkscape:window-y="0"
inkscape:grid-points="true" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="opacity:0.7;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3351)"
id="rect3305"
width="100"
height="100"
x="14"
y="14"
rx="7.4348507"
ry="7.4348507"
clip-path="url(#clipPath3393)" />
<rect
ry="5.6263733"
rx="5.6263733"
y="16"
x="16"
height="96"
width="96"
id="rect3141"
style="opacity:0.7;fill:#eeeeee;fill-opacity:1;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="opacity:1;fill:url(#radialGradient3139);fill-opacity:1.0;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect2160"
width="80"
height="80"
x="24"
y="24"
rx="5.6263733"
ry="5.6263733" />
<rect
style="opacity:0.7;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3178"
width="95"
height="95"
x="16.5"
y="16.5"
rx="5.6263733"
ry="5.6263733" />
<g
id="g3160"
transform="matrix(1.6656201,0,0,1.6656201,-62.574569,-26.624804)"
style="fill:#ffffff;fill-opacity:1">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 44.03125 40 L 55.34375 51.3125 C 43.467571 59.035057 35.734919 71.703669 57.34375 93.3125 C 52.734046 74.873684 60.878036 66.021115 70.75 66.71875 L 84 79.96875 L 84 73.3125 L 84 40 L 44.03125 40 z "
transform="matrix(0.600377,0,0,0.600377,37.568332,15.98492)"
id="path2179" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -3,13 +3,15 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, textwrap, traceback, time, re, sre_constants
from datetime import timedelta, datetime
from operator import attrgetter
from collections import deque
from math import cos, sin, pi
from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, \
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QLineEdit, QApplication, \
QPalette
QPalette, QImage
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex, \
QTimer
from calibre import Settings, preferred_encoding
from calibre.ptempfile import PersistentTemporaryFile
@ -92,15 +94,25 @@ class BooksModel(QAbstractTableModel):
num -= d
return ''.join(result)
def __init__(self, parent=None):
def __init__(self, parent=None, buffer=20):
QAbstractTableModel.__init__(self, parent)
self.db = None
self.cols = ['title', 'authors', 'size', 'date', 'rating', 'publisher', 'tags', 'series']
self.editable_cols = [0, 1, 4, 5, 6]
self.default_image = QImage(':/images/book.svg')
self.sorted_on = (3, Qt.AscendingOrder)
self.last_search = '' # The last search performed on this model
self.read_config()
self.buffer_size = buffer
self.clear_caches()
self.load_timer = QTimer()
self.connect(self.load_timer, SIGNAL('timeout()'), self.load)
self.load_timer.start(50)
def clear_caches(self):
self.buffer = {}
self.load_queue = deque()
def read_config(self):
self.use_roman_numbers = bool(Settings().value('use roman numerals for series number',
QVariant(True)).toBool())
@ -164,6 +176,7 @@ class BooksModel(QAbstractTableModel):
self.db.filter(tokens, refilter=refinement, OR=OR)
self.last_search = text
if reset:
self.clear_caches()
self.reset()
def sort(self, col, order, reset=True):
@ -173,6 +186,7 @@ class BooksModel(QAbstractTableModel):
self.db.refresh(self.cols[col], ascending)
self.research()
if reset:
self.clear_caches()
self.reset()
self.sorted_on = (col, order)
@ -194,10 +208,32 @@ class BooksModel(QAbstractTableModel):
def rowCount(self, parent):
return self.db.rows() if self.db else 0
def count(self):
return self.rowCount(None)
def load(self):
if self.load_queue:
index = self.load_queue.popleft()
if self.buffer.has_key(index):
return
data = self.db.cover(index)
img = QImage()
img.loadFromData(data)
if img.isNull():
img = self.default_image
self.buffer[index] = img
def current_changed(self, current, previous, emit_signal=True):
data = {}
idx = current.row()
cdata = self.db.cover(idx)
cdata = self.cover(idx)
for key in self.buffer.keys():
if abs(key - idx) > self.buffer_size:
self.buffer.pop(key)
for i in range(max(0, idx-self.buffer_size), min(self.count(), idx+self.buffer_size)):
if not self.buffer.has_key(i):
self.load_queue.append(i)
if cdata:
data['cover'] = cdata
tags = self.db.tags(idx)
@ -296,7 +332,15 @@ class BooksModel(QAbstractTableModel):
return self.db.title(row_number)
def cover(self, row_number):
return self.db.cover(row_number)
img = self.buffer.get(row_number, -1)
if img == -1:
data = self.db.cover(row_number)
img = QImage()
img.loadFromData(data)
if img.isNull():
img = self.default_image
self.buffer[row_number] = img
return img
def data(self, index, role):
if role == Qt.DisplayRole or role == Qt.EditRole:

View File

@ -5,7 +5,7 @@ from xml.parsers.expat import ExpatError
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QVariant, QThread, QString
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
QToolButton, QDialog
QToolButton, QDialog, QSizePolicy
from PyQt4.QtSvg import QSvgRenderer
from calibre import __version__, __appname__, islinux, sanitize_file_name, launch, \
@ -19,7 +19,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
pixmap_to_data, choose_dir, ORG_NAME, \
qstring_to_unicode, set_sidebar_directories, \
SingleApplication, Application
from calibre.gui2.cover_flow import CoverFlow
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages
from calibre.library.database import LibraryDatabase
from calibre.gui2.update import CheckForUpdates
from calibre.gui2.main_window import MainWindow
@ -199,6 +199,25 @@ class Main(MainWindow, Ui_MainWindow):
self.library_view.resizeRowsToContents()
self.search.setFocus(Qt.OtherFocusReason)
########################### Cover Flow ################################
self.cover_flow = None
if CoverFlow is not None:
self.cover_flow = CoverFlow(height=220)
self.cover_flow.setVisible(False)
self.library.layout().addWidget(self.cover_flow)
self.connect(self.cover_flow, SIGNAL('currentChanged(int)'), self.sync_cf_to_listview)
self.library_view.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding))
self.connect(self.cover_flow, SIGNAL('itemActivated(int)'), self.show_book_info)
self.connect(self.status_bar.cover_flow_button, SIGNAL('toggled(bool)'), self.toggle_cover_flow)
QObject.connect(self.library_view.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
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)
####################### Setup device detection ########################
self.detector = DeviceDetector(sleep_time=2000)
QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'),
@ -207,6 +226,29 @@ class Main(MainWindow, Ui_MainWindow):
self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds())
def toggle_cover_flow(self, show):
if show:
self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
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())
else:
self.cover_flow.setVisible(False)
self.status_bar.book_info.book_data.setMaximumHeight(1000)
self.status_bar.setMaximumHeight(1200)
def sync_cf_to_listview(self, index, *args):
if not hasattr(index, 'row') and self.library_view.currentIndex().row() != index:
index = self.library_view.model().index(index, 0)
self.library_view.setCurrentIndex(index)
if hasattr(index, 'row') and self.cover_flow.isVisible() and self.cover_flow.currentSlide() != index.row():
self.cover_flow.setCurrentSlide(index.row())
def another_instance_wants_to_talk(self, msg):
if msg.startswith('launched:'):
argv = eval(msg[len('launched:'):])
@ -942,7 +984,7 @@ class Main(MainWindow, Ui_MainWindow):
################################ Book info #################################
def show_book_info(self):
def show_book_info(self, *args):
if self.current_view() is not self.library_view:
error_dialog(self, _('No detailed info available'),
_('No detailed information is available for books on the device.')).exec_()

View File

@ -30,7 +30,7 @@ makefile = pyqtconfig.QtGuiModuleMakefile (
# Add the library we are wrapping. The name doesn't include any platform
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows).
makefile.extra_lib_dirs = ['../..', '..\\..\\release']
makefile.extra_lib_dirs = ['..\\..\\.build\\release', '../../.build']
makefile.extra_libs = ['pictureflow0' if 'win' in sys.platform and 'darwin' not in sys.platform else "pictureflow"]
makefile.extra_cflags = ['-arch i386', '-arch ppc'] if 'darwin' in sys.platform else []
makefile.extra_cxxflags = makefile.extra_cflags

View File

@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import textwrap
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap, \
QVBoxLayout, QSizePolicy
QVBoxLayout, QSizePolicy, QToolButton, QIcon
from PyQt4.QtCore import Qt, QSize, SIGNAL
from calibre import fit_image
from calibre.gui2 import qstring_to_unicode
@ -64,13 +64,7 @@ class BookInfoDisplay(QFrame):
def show_data(self, data):
if data.has_key('cover'):
cover_data = data.pop('cover')
pixmap = QPixmap()
pixmap.loadFromData(cover_data)
if pixmap.isNull():
self.cover_display.setPixmap(self.cover_display.default_pixmap)
else:
self.cover_display.setPixmap(pixmap)
self.cover_display.setPixmap(QPixmap.fromImage(data.pop('cover')))
else:
self.cover_display.setPixmap(self.cover_display.default_pixmap)
@ -120,16 +114,39 @@ class MovieButton(QFrame):
self.jobs_dialog.jobs_view.read_settings()
self.jobs_dialog.show()
self.jobs_dialog.jobs_view.restore_column_widths()
class CoverFlowButton(QToolButton):
def __init__(self, parent=None):
QToolButton.__init__(self, parent)
self.setIconSize(QSize(80, 80))
self.setIcon(QIcon(':/images/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)
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(_('<p>Browsing books by their covers is disabled.<br>Import of pictureflow module failed:<br>')+reason)
class StatusBar(QStatusBar):
def __init__(self, jobs_dialog):
QStatusBar.__init__(self)
self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'), 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.connect(self.book_info, SIGNAL('show_book_info()'), self.show_book_info)
self.addWidget(self.book_info)
self.setMinimumHeight(120)
def reset_info(self):
self.book_info.show_data({})

View File

@ -539,26 +539,27 @@ class BuildEXE(build_exe):
'''
def build_plugins(self):
cwd = os.getcwd()
dd = os.path.join(cwd, self.dist_dir)
try:
os.chdir(os.path.join('src', 'calibre', 'gui2', 'pictureflow'))
if os.path.exists('release'):
shutil.rmtree('release')
if os.path.exists('debug'):
shutil.rmtree('debug')
subprocess.check_call(['qmake', 'pictureflow.pro'])
if os.path.exists('.build'):
shutil.rmtree('.build')
os.mkdir('.build')
os.chdir('.build')
subprocess.check_call(['qmake', '../pictureflow.pro'])
subprocess.check_call(['mingw32-make', '-f', 'Makefile.Release'])
os.chdir('PyQt')
shutil.copyfile('release\\pictureflow0.dll', os.path.join(dd, 'pictureflow0.dll'))
os.chdir('..\\PyQt')
if not os.path.exists('.build'):
os.mkdir('.build')
os.chdir('.build')
subprocess.check_call(['python', '..\\configure.py'])
subprocess.check_call(['mingw32-make', '-f', 'Makefile'])
dd = os.path.join(cwd, self.dist_dir)
shutil.copyfile('pictureflow.pyd', os.path.join(dd, 'pictureflow.pyd'))
os.chdir('..\\..')
shutil.copyfile('release\\pictureflow0.dll', os.path.join(dd, 'pictureflow0.dll'))
shutil.rmtree('Release', True)
shutil.rmtree('Debug', True)
os.chdir('..')
shutil.rmtree('.build')
os.chdir('..')
shutil.rmtree('.build')
finally:
os.chdir(cwd)