mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement a 'Browse books by cover' feature
This commit is contained in:
parent
d669477b23
commit
fcf35ac0b3
10
Makefile
10
Makefile
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -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 = ''
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
199
src/calibre/gui2/images/cover_flow.svg
Normal file
199
src/calibre/gui2/images/cover_flow.svg
Normal 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 |
@ -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:
|
||||
|
@ -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_()
|
||||
|
@ -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
|
||||
|
@ -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({})
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user