mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
e2038a71dd
@ -21,16 +21,20 @@ class WashingtonPost(BasicNewsRecipe):
|
|||||||
body{font-family:arial,helvetica,sans-serif}
|
body{font-family:arial,helvetica,sans-serif}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
feeds = [ ('Today\'s Highlights', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/03/24/LI2005032400102.xml'),
|
feeds = [ ('Today\'s Highlights', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/03/24/LI2005032400102.xml'),
|
||||||
('Politics', 'http://www.washingtonpost.com/wp-dyn/rss/politics/index.xml'),
|
('Politics', 'http://www.washingtonpost.com/wp-dyn/rss/politics/index.xml'),
|
||||||
('Nation', 'http://www.washingtonpost.com/wp-dyn/rss/nation/index.xml'),
|
('Nation', 'http://www.washingtonpost.com/wp-dyn/rss/nation/index.xml'),
|
||||||
('World', 'http://www.washingtonpost.com/wp-dyn/rss/world/index.xml'),
|
('World', 'http://www.washingtonpost.com/wp-dyn/rss/world/index.xml'),
|
||||||
('Business', 'http://www.washingtonpost.com/wp-dyn/rss/business/index.xml'),
|
('Business', 'http://www.washingtonpost.com/wp-dyn/rss/business/index.xml'),
|
||||||
('Technology', 'http://www.washingtonpost.com/wp-dyn/rss/technology/index.xml'),
|
('Technology', 'http://www.washingtonpost.com/wp-dyn/rss/technology/index.xml'),
|
||||||
('Health', 'http://www.washingtonpost.com/wp-dyn/rss/health/index.xml'),
|
('Health', 'http://www.washingtonpost.com/wp-dyn/rss/health/index.xml'),
|
||||||
('Education', 'http://www.washingtonpost.com/wp-dyn/rss/education/index.xml'),
|
('Education', 'http://www.washingtonpost.com/wp-dyn/rss/education/index.xml'),
|
||||||
('Editorials', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/05/30/LI2005053000331.xml'),
|
('Style',
|
||||||
]
|
'http://www.washingtonpost.com/wp-dyn/rss/print/style/index.xml'),
|
||||||
|
('Sports',
|
||||||
|
'http://feeds.washingtonpost.com/wp-dyn/rss/linkset/2010/08/19/LI2010081904067_xml'),
|
||||||
|
('Editorials', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/05/30/LI2005053000331.xml'),
|
||||||
|
]
|
||||||
|
|
||||||
remove_tags = [{'id':['pfmnav', 'ArticleCommentsWrapper']}]
|
remove_tags = [{'id':['pfmnav', 'ArticleCommentsWrapper']}]
|
||||||
|
|
||||||
|
@ -455,6 +455,24 @@ def prepare_string_for_xml(raw, attribute=False):
|
|||||||
def isbytestring(obj):
|
def isbytestring(obj):
|
||||||
return isinstance(obj, (str, bytes))
|
return isinstance(obj, (str, bytes))
|
||||||
|
|
||||||
|
def force_unicode(obj, enc=preferred_encoding):
|
||||||
|
if isbytestring(obj):
|
||||||
|
try:
|
||||||
|
obj = obj.decode(enc)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
obj = obj.decode(filesystem_encoding if enc ==
|
||||||
|
preferred_encoding else preferred_encoding)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
obj = obj.decode('utf-8')
|
||||||
|
except:
|
||||||
|
obj = repr(obj)
|
||||||
|
if isbytestring(obj):
|
||||||
|
obj = obj.decode('utf-8')
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def human_readable(size):
|
def human_readable(size):
|
||||||
""" Convert a size in bytes into a human readable form """
|
""" Convert a size in bytes into a human readable form """
|
||||||
divisor, suffix = 1, "B"
|
divisor, suffix = 1, "B"
|
||||||
|
@ -707,7 +707,7 @@ OptionRecommendation(name='timestamp',
|
|||||||
if mi.cover.startswith('http:') or mi.cover.startswith('https:'):
|
if mi.cover.startswith('http:') or mi.cover.startswith('https:'):
|
||||||
mi.cover = self.download_cover(mi.cover)
|
mi.cover = self.download_cover(mi.cover)
|
||||||
ext = mi.cover.rpartition('.')[-1].lower().strip()
|
ext = mi.cover.rpartition('.')[-1].lower().strip()
|
||||||
if ext not in ('png', 'jpg', 'jpeg'):
|
if ext not in ('png', 'jpg', 'jpeg', 'gif'):
|
||||||
ext = 'jpg'
|
ext = 'jpg'
|
||||||
mi.cover_data = (ext, open(mi.cover, 'rb').read())
|
mi.cover_data = (ext, open(mi.cover, 'rb').read())
|
||||||
mi.cover = None
|
mi.cover = None
|
||||||
|
@ -654,8 +654,6 @@ class Metadata(object):
|
|||||||
if predicate(x):
|
if predicate(x):
|
||||||
l.remove(x)
|
l.remove(x)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.items[key]
|
return self.items[key]
|
||||||
|
|
||||||
|
@ -132,17 +132,23 @@ class OEBReader(object):
|
|||||||
if not mi.language:
|
if not mi.language:
|
||||||
mi.language = get_lang().replace('_', '-')
|
mi.language = get_lang().replace('_', '-')
|
||||||
self.oeb.metadata.add('language', mi.language)
|
self.oeb.metadata.add('language', mi.language)
|
||||||
if not mi.title:
|
|
||||||
mi.title = self.oeb.translate(__('Unknown'))
|
|
||||||
if not mi.authors:
|
|
||||||
mi.authors = [self.oeb.translate(__('Unknown'))]
|
|
||||||
if not mi.book_producer:
|
if not mi.book_producer:
|
||||||
mi.book_producer = '%(a)s (%(v)s) [http://%(a)s.kovidgoyal.net]'%\
|
mi.book_producer = '%(a)s (%(v)s) [http://%(a)s-ebook.com]'%\
|
||||||
dict(a=__appname__, v=__version__)
|
dict(a=__appname__, v=__version__)
|
||||||
meta_info_to_oeb_metadata(mi, self.oeb.metadata, self.logger)
|
meta_info_to_oeb_metadata(mi, self.oeb.metadata, self.logger)
|
||||||
self.oeb.metadata.add('identifier', str(uuid.uuid4()), id='uuid_id',
|
m = self.oeb.metadata
|
||||||
scheme='uuid')
|
m.add('identifier', str(uuid.uuid4()), id='uuid_id', scheme='uuid')
|
||||||
self.oeb.uid = self.oeb.metadata.identifier[-1]
|
self.oeb.uid = self.oeb.metadata.identifier[-1]
|
||||||
|
if not m.title:
|
||||||
|
m.add('title', self.oeb.translate(__('Unknown')))
|
||||||
|
has_aut = False
|
||||||
|
for x in m.creator:
|
||||||
|
if getattr(x, 'role', '').lower() in ('', 'aut'):
|
||||||
|
has_aut = True
|
||||||
|
break
|
||||||
|
if not has_aut:
|
||||||
|
m.add('creator', self.oeb.translate(__('Unknown')), role='aut')
|
||||||
|
|
||||||
|
|
||||||
def _manifest_prune_invalid(self):
|
def _manifest_prune_invalid(self):
|
||||||
'''
|
'''
|
||||||
|
@ -3,6 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
""" The GUI """
|
""" The GUI """
|
||||||
import os, sys, Queue, threading
|
import os, sys, Queue, threading
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
from urllib import unquote
|
||||||
|
|
||||||
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
||||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
QByteArray, QTranslator, QCoreApplication, QThread, \
|
||||||
@ -505,6 +506,11 @@ class FileDialog(QObject):
|
|||||||
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
|
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
|
||||||
for f in fs:
|
for f in fs:
|
||||||
f = unicode(f)
|
f = unicode(f)
|
||||||
|
if not f: continue
|
||||||
|
if not os.path.exists(f):
|
||||||
|
# QFileDialog for some reason quotes spaces
|
||||||
|
# on linux if there is more than one space in a row
|
||||||
|
f = unquote(f)
|
||||||
if f and os.path.exists(f):
|
if f and os.path.exists(f):
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
else:
|
else:
|
||||||
|
@ -234,13 +234,14 @@ class AddAction(InterfaceAction):
|
|||||||
self.gui.set_books_in_library(booklists=[model.db], reset=True)
|
self.gui.set_books_in_library(booklists=[model.db], reset=True)
|
||||||
self.gui.refresh_ondevice()
|
self.gui.refresh_ondevice()
|
||||||
|
|
||||||
def add_books_from_device(self, view):
|
def add_books_from_device(self, view, paths=None):
|
||||||
rows = view.selectionModel().selectedRows()
|
if paths is None:
|
||||||
if not rows or len(rows) == 0:
|
rows = view.selectionModel().selectedRows()
|
||||||
d = error_dialog(self.gui, _('Add to library'), _('No book selected'))
|
if not rows or len(rows) == 0:
|
||||||
d.exec_()
|
d = error_dialog(self.gui, _('Add to library'), _('No book selected'))
|
||||||
return
|
d.exec_()
|
||||||
paths = [p for p in view._model.paths(rows) if p is not None]
|
return
|
||||||
|
paths = [p for p in view.model().paths(rows) if p is not None]
|
||||||
ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS
|
ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS
|
||||||
def ext(x):
|
def ext(x):
|
||||||
ans = os.path.splitext(x)[1]
|
ans = os.path.splitext(x)[1]
|
||||||
@ -261,7 +262,7 @@ class AddAction(InterfaceAction):
|
|||||||
return
|
return
|
||||||
from calibre.gui2.add import Adder
|
from calibre.gui2.add import Adder
|
||||||
self.__adder_func = partial(self._add_from_device_adder, on_card=None,
|
self.__adder_func = partial(self._add_from_device_adder, on_card=None,
|
||||||
model=view._model)
|
model=view.model())
|
||||||
self._adder = Adder(self.gui, self.gui.library_view.model().db,
|
self._adder = Adder(self.gui, self.gui.library_view.model().db,
|
||||||
self.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server)
|
self.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server)
|
||||||
self._adder.add(paths)
|
self._adder.add(paths)
|
||||||
|
@ -21,7 +21,10 @@ from calibre.gui2.convert import Widget
|
|||||||
def create_opf_file(db, book_id):
|
def create_opf_file(db, book_id):
|
||||||
mi = db.get_metadata(book_id, index_is_id=True)
|
mi = db.get_metadata(book_id, index_is_id=True)
|
||||||
mi.application_id = uuid.uuid4()
|
mi.application_id = uuid.uuid4()
|
||||||
|
old_cover = mi.cover
|
||||||
|
mi.cover = None
|
||||||
raw = metadata_to_opf(mi)
|
raw = metadata_to_opf(mi)
|
||||||
|
mi.cover = old_cover
|
||||||
opf_file = PersistentTemporaryFile('.opf')
|
opf_file = PersistentTemporaryFile('.opf')
|
||||||
opf_file.write(raw)
|
opf_file.write(raw)
|
||||||
opf_file.close()
|
opf_file.close()
|
||||||
|
@ -23,7 +23,7 @@ from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
|
|||||||
warning_dialog, \
|
warning_dialog, \
|
||||||
question_dialog, info_dialog, choose_dir
|
question_dialog, info_dialog, choose_dir
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre import preferred_encoding, prints
|
from calibre import preferred_encoding, prints, force_unicode
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.devices.errors import FreeSpaceError
|
from calibre.devices.errors import FreeSpaceError
|
||||||
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
|
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
|
||||||
@ -964,12 +964,12 @@ class DeviceMixin(object): # {{{
|
|||||||
for jobname, exception, tb in results:
|
for jobname, exception, tb in results:
|
||||||
title = jobname.partition(':')[-1]
|
title = jobname.partition(':')[-1]
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
errors.append([title, exception, tb])
|
errors.append(list(map(force_unicode, [title, exception, tb])))
|
||||||
else:
|
else:
|
||||||
good.append(title)
|
good.append(title)
|
||||||
if errors:
|
if errors:
|
||||||
errors = '\n'.join([
|
errors = u'\n'.join([
|
||||||
'%s\n\n%s\n%s\n' %
|
u'%s\n\n%s\n%s\n' %
|
||||||
(title, e, tb) for \
|
(title, e, tb) for \
|
||||||
title, e, tb in errors
|
title, e, tb in errors
|
||||||
])
|
])
|
||||||
|
@ -375,7 +375,7 @@ p, li { white-space: pre-wrap; }
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_8">
|
<widget class="QLabel" name="label_8">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>For help with writing advanced news recipes, please visit <a href="http://__appname__.kovidgoyal.net/user_manual/news.html">User Recipes</a></string>
|
<string>For help with writing advanced news recipes, please visit <a href="http://__appname__-ebook.com/user_manual/news.html">User Recipes</a></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -56,6 +56,7 @@ class LocationManager(QObject): # {{{
|
|||||||
self._mem.append(a)
|
self._mem.append(a)
|
||||||
else:
|
else:
|
||||||
ac.setToolTip(tooltip)
|
ac.setToolTip(tooltip)
|
||||||
|
ac.calibre_name = name
|
||||||
|
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
@ -112,7 +113,6 @@ class LocationManager(QObject): # {{{
|
|||||||
ac.setWhatsThis(t)
|
ac.setWhatsThis(t)
|
||||||
ac.setStatusTip(t)
|
ac.setStatusTip(t)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_device(self):
|
def has_device(self):
|
||||||
return max(self.free) > -1
|
return max(self.free) > -1
|
||||||
@ -228,6 +228,7 @@ class ToolBar(QToolBar): # {{{
|
|||||||
self.added_actions = []
|
self.added_actions = []
|
||||||
self.build_bar()
|
self.build_bar()
|
||||||
self.preferred_width = self.sizeHint().width()
|
self.preferred_width = self.sizeHint().width()
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
def apply_settings(self):
|
def apply_settings(self):
|
||||||
sz = gprefs['toolbar_icon_size']
|
sz = gprefs['toolbar_icon_size']
|
||||||
@ -317,6 +318,59 @@ class ToolBar(QToolBar): # {{{
|
|||||||
def database_changed(self, db):
|
def database_changed(self, db):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
#support drag&drop from/to library from/to reader/card
|
||||||
|
def dragEnterEvent(self, event):
|
||||||
|
md = event.mimeData()
|
||||||
|
if md.hasFormat("application/calibre+from_library") or \
|
||||||
|
md.hasFormat("application/calibre+from_device"):
|
||||||
|
event.setDropAction(Qt.CopyAction)
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
def dragMoveEvent(self, event):
|
||||||
|
allowed = False
|
||||||
|
md = event.mimeData()
|
||||||
|
#Drop is only allowed in the location manager widget's different from the selected one
|
||||||
|
for ac in self.location_manager.available_actions:
|
||||||
|
w = self.widgetForAction(ac)
|
||||||
|
if w is not None:
|
||||||
|
if ( md.hasFormat("application/calibre+from_library") or \
|
||||||
|
md.hasFormat("application/calibre+from_device") ) and \
|
||||||
|
w.geometry().contains(event.pos()) and \
|
||||||
|
isinstance(w, QToolButton) and not w.isChecked():
|
||||||
|
allowed = True
|
||||||
|
break
|
||||||
|
if allowed:
|
||||||
|
event.acceptProposedAction()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
def dropEvent(self, event):
|
||||||
|
data = event.mimeData()
|
||||||
|
|
||||||
|
mime = 'application/calibre+from_library'
|
||||||
|
if data.hasFormat(mime):
|
||||||
|
ids = list(map(int, str(data.data(mime)).split()))
|
||||||
|
tgt = None
|
||||||
|
for ac in self.location_manager.available_actions:
|
||||||
|
w = self.widgetForAction(ac)
|
||||||
|
if w is not None and w.geometry().contains(event.pos()):
|
||||||
|
tgt = ac.calibre_name
|
||||||
|
if tgt is not None:
|
||||||
|
if tgt == 'main':
|
||||||
|
tgt = None
|
||||||
|
self.gui.sync_to_device(tgt, False, send_ids=ids)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
mime = 'application/calibre+from_device'
|
||||||
|
if data.hasFormat(mime):
|
||||||
|
paths = [unicode(u.toLocalFile()) for u in data.urls()]
|
||||||
|
if paths:
|
||||||
|
self.gui.iactions['Add Books'].add_books_from_device(
|
||||||
|
self.gui.current_view(), paths=paths)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class MainWindowMixin(object): # {{{
|
class MainWindowMixin(object): # {{{
|
||||||
|
@ -1081,12 +1081,11 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
self.db = db
|
self.db = db
|
||||||
self.map = list(range(0, len(db)))
|
self.map = list(range(0, len(db)))
|
||||||
|
|
||||||
def current_changed(self, current, previous):
|
def cover(self, row):
|
||||||
data = {}
|
item = self.db[self.map[row]]
|
||||||
item = self.db[self.map[current.row()]]
|
|
||||||
cdata = item.thumbnail
|
cdata = item.thumbnail
|
||||||
|
img = QImage()
|
||||||
if cdata is not None:
|
if cdata is not None:
|
||||||
img = QImage()
|
|
||||||
if hasattr(cdata, 'image_path'):
|
if hasattr(cdata, 'image_path'):
|
||||||
img.load(cdata.image_path)
|
img.load(cdata.image_path)
|
||||||
elif cdata:
|
elif cdata:
|
||||||
@ -1094,9 +1093,16 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
img.loadFromData(cdata[-1])
|
img.loadFromData(cdata[-1])
|
||||||
else:
|
else:
|
||||||
img.loadFromData(cdata)
|
img.loadFromData(cdata)
|
||||||
if img.isNull():
|
if img.isNull():
|
||||||
img = self.default_image
|
img = self.default_image
|
||||||
data['cover'] = img
|
return img
|
||||||
|
|
||||||
|
def current_changed(self, current, previous):
|
||||||
|
data = {}
|
||||||
|
item = self.db[self.map[current.row()]]
|
||||||
|
cover = self.cover(current.row())
|
||||||
|
if cover is not self.default_image:
|
||||||
|
data['cover'] = cover
|
||||||
type = _('Unknown')
|
type = _('Unknown')
|
||||||
ext = os.path.splitext(item.path)[1]
|
ext = os.path.splitext(item.path)[1]
|
||||||
if ext:
|
if ext:
|
||||||
|
@ -9,7 +9,8 @@ import os
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
|
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
|
||||||
QModelIndex, QIcon, QItemSelection
|
QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, \
|
||||||
|
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect
|
||||||
|
|
||||||
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
|
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
|
||||||
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
|
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
|
||||||
@ -18,7 +19,8 @@ from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
|||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.gui2 import error_dialog, gprefs
|
from calibre.gui2 import error_dialog, gprefs
|
||||||
from calibre.gui2.library import DEFAULT_SORT
|
from calibre.gui2.library import DEFAULT_SORT
|
||||||
|
from calibre.constants import filesystem_encoding
|
||||||
|
from calibre import force_unicode
|
||||||
|
|
||||||
class BooksView(QTableView): # {{{
|
class BooksView(QTableView): # {{{
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ class BooksView(QTableView): # {{{
|
|||||||
self.setDragEnabled(True)
|
self.setDragEnabled(True)
|
||||||
self.setDragDropOverwriteMode(False)
|
self.setDragDropOverwriteMode(False)
|
||||||
self.setDragDropMode(self.DragDrop)
|
self.setDragDropMode(self.DragDrop)
|
||||||
|
self.drag_start_pos = None
|
||||||
self.setAlternatingRowColors(True)
|
self.setAlternatingRowColors(True)
|
||||||
self.setSelectionBehavior(self.SelectRows)
|
self.setSelectionBehavior(self.SelectRows)
|
||||||
self.setShowGrid(False)
|
self.setShowGrid(False)
|
||||||
@ -426,6 +429,69 @@ class BooksView(QTableView): # {{{
|
|||||||
urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
|
urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
|
||||||
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
|
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
|
||||||
|
|
||||||
|
def drag_icon(self, cover, multiple):
|
||||||
|
cover = cover.scaledToHeight(120, Qt.SmoothTransformation)
|
||||||
|
if multiple:
|
||||||
|
base_width = cover.width()
|
||||||
|
base_height = cover.height()
|
||||||
|
base = QImage(base_width+21, base_height+21,
|
||||||
|
QImage.Format_ARGB32_Premultiplied)
|
||||||
|
base.fill(QColor(255, 255, 255, 0).rgba())
|
||||||
|
p = QPainter(base)
|
||||||
|
rect = QRect(20, 0, base_width, base_height)
|
||||||
|
p.fillRect(rect, QColor('white'))
|
||||||
|
p.drawRect(rect)
|
||||||
|
rect.moveLeft(10)
|
||||||
|
rect.moveTop(10)
|
||||||
|
p.fillRect(rect, QColor('white'))
|
||||||
|
p.drawRect(rect)
|
||||||
|
rect.moveLeft(0)
|
||||||
|
rect.moveTop(20)
|
||||||
|
p.fillRect(rect, QColor('white'))
|
||||||
|
p.save()
|
||||||
|
p.setCompositionMode(p.CompositionMode_SourceAtop)
|
||||||
|
p.drawImage(rect.topLeft(), cover)
|
||||||
|
p.restore()
|
||||||
|
p.drawRect(rect)
|
||||||
|
p.end()
|
||||||
|
cover = base
|
||||||
|
return QPixmap.fromImage(cover)
|
||||||
|
|
||||||
|
def drag_data(self):
|
||||||
|
m = self.model()
|
||||||
|
db = m.db
|
||||||
|
rows = self.selectionModel().selectedRows()
|
||||||
|
selected = map(m.id, rows)
|
||||||
|
ids = ' '.join(map(str, selected))
|
||||||
|
md = QMimeData()
|
||||||
|
md.setData('application/calibre+from_library', ids)
|
||||||
|
md.setUrls([QUrl.fromLocalFile(db.abspath(i, index_is_id=True))
|
||||||
|
for i in selected])
|
||||||
|
drag = QDrag(self)
|
||||||
|
drag.setMimeData(md)
|
||||||
|
cover = self.drag_icon(m.cover(self.currentIndex().row()),
|
||||||
|
len(selected) > 1)
|
||||||
|
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
|
||||||
|
drag.setPixmap(cover)
|
||||||
|
return drag
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
if event.button() == Qt.LeftButton:
|
||||||
|
self.drag_start_pos = event.pos()
|
||||||
|
return QTableView.mousePressEvent(self, event)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
if not (event.buttons() & Qt.LeftButton) or self.drag_start_pos is None:
|
||||||
|
return
|
||||||
|
if (event.pos() - self.drag_start_pos).manhattanLength() \
|
||||||
|
< QApplication.startDragDistance():
|
||||||
|
return
|
||||||
|
index = self.indexAt(event.pos())
|
||||||
|
if not index.isValid():
|
||||||
|
return
|
||||||
|
drag = self.drag_data()
|
||||||
|
drag.exec_(Qt.CopyAction)
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
if int(event.possibleActions() & Qt.CopyAction) + \
|
if int(event.possibleActions() & Qt.CopyAction) + \
|
||||||
int(event.possibleActions() & Qt.MoveAction) == 0:
|
int(event.possibleActions() & Qt.MoveAction) == 0:
|
||||||
@ -547,6 +613,21 @@ class DeviceBooksView(BooksView): # {{{
|
|||||||
self.setDragDropMode(self.NoDragDrop)
|
self.setDragDropMode(self.NoDragDrop)
|
||||||
self.setAcceptDrops(False)
|
self.setAcceptDrops(False)
|
||||||
|
|
||||||
|
def drag_data(self):
|
||||||
|
m = self.model()
|
||||||
|
rows = self.selectionModel().selectedRows()
|
||||||
|
paths = [force_unicode(p, enc=filesystem_encoding) for p in m.paths(rows) if p]
|
||||||
|
md = QMimeData()
|
||||||
|
md.setData('application/calibre+from_device', 'dummy')
|
||||||
|
md.setUrls([QUrl.fromLocalFile(p) for p in paths])
|
||||||
|
drag = QDrag(self)
|
||||||
|
drag.setMimeData(md)
|
||||||
|
cover = self.drag_icon(m.cover(self.currentIndex().row()), len(paths) >
|
||||||
|
1)
|
||||||
|
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
|
||||||
|
drag.setPixmap(cover)
|
||||||
|
return drag
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
edit_collections = callable(getattr(self._model.db, 'supports_collections', None)) and \
|
edit_collections = callable(getattr(self._model.db, 'supports_collections', None)) and \
|
||||||
self._model.db.supports_collections() and \
|
self._model.db.supports_collections() and \
|
||||||
|
@ -302,7 +302,7 @@ def do_add_empty(db, title, authors, isbn):
|
|||||||
if isbn:
|
if isbn:
|
||||||
mi.isbn = isbn
|
mi.isbn = isbn
|
||||||
db.import_book(mi, [])
|
db.import_book(mi, [])
|
||||||
write_dirtied()
|
write_dirtied(db)
|
||||||
send_message()
|
send_message()
|
||||||
|
|
||||||
def command_add(args, dbpath):
|
def command_add(args, dbpath):
|
||||||
@ -456,7 +456,7 @@ def do_set_metadata(db, id, stream):
|
|||||||
db.set_metadata(id, mi)
|
db.set_metadata(id, mi)
|
||||||
db.clean()
|
db.clean()
|
||||||
do_show_metadata(db, id, False)
|
do_show_metadata(db, id, False)
|
||||||
write_dirtied()
|
write_dirtied(db)
|
||||||
send_message()
|
send_message()
|
||||||
|
|
||||||
def set_metadata_option_parser():
|
def set_metadata_option_parser():
|
||||||
|
@ -19,7 +19,7 @@ from calibre.ebooks.metadata.meta import set_metadata
|
|||||||
from calibre.constants import preferred_encoding, filesystem_encoding
|
from calibre.constants import preferred_encoding, filesystem_encoding
|
||||||
from calibre.ebooks.metadata import fmt_sidx
|
from calibre.ebooks.metadata import fmt_sidx
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
from calibre import strftime
|
from calibre import strftime, prints
|
||||||
|
|
||||||
plugboard_any_device_value = 'any device'
|
plugboard_any_device_value = 'any device'
|
||||||
plugboard_any_format_value = 'any format'
|
plugboard_any_format_value = 'any format'
|
||||||
|
@ -18,7 +18,7 @@ Editing the metadata of one book at a time
|
|||||||
|
|
||||||
Click the book you want to edit and then click the :guilabel:`Edit metadata` button or press the ``E`` key. A dialog opens that allows you to edit all aspects of the metadata. It has various features to make editing faster and more efficient. A list of the commonly used tips:
|
Click the book you want to edit and then click the :guilabel:`Edit metadata` button or press the ``E`` key. A dialog opens that allows you to edit all aspects of the metadata. It has various features to make editing faster and more efficient. A list of the commonly used tips:
|
||||||
|
|
||||||
* You can click the button in between title and authors to swap them automatically. Or
|
* You can click the button in between title and authors to swap them automatically.
|
||||||
* You can click the button next to author sort to automatically to have |app| automatically fill it from the author name.
|
* You can click the button next to author sort to automatically to have |app| automatically fill it from the author name.
|
||||||
* You can click the button next to tags to use the Tag Editor to manage the tags associated with the book.
|
* You can click the button next to tags to use the Tag Editor to manage the tags associated with the book.
|
||||||
* The ISBN box will have a red background if you enter an invalid ISBN. It will be green for valid ISBNs
|
* The ISBN box will have a red background if you enter an invalid ISBN. It will be green for valid ISBNs
|
||||||
|
@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
Perform various initialization tasks.
|
Perform various initialization tasks.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import locale, sys, os
|
import locale, sys, os, re
|
||||||
|
|
||||||
# Default translation is NOOP
|
# Default translation is NOOP
|
||||||
import __builtin__
|
import __builtin__
|
||||||
@ -114,6 +114,34 @@ if not _run_once:
|
|||||||
r, w, a, rb, wb, ab, r+, w+, a+, r+b, w+b, a+b
|
r, w, a, rb, wb, ab, r+, w+, a+, r+b, w+b, a+b
|
||||||
'''
|
'''
|
||||||
if iswindows:
|
if iswindows:
|
||||||
|
class fwrapper(object):
|
||||||
|
def __init__(self, name, fobject):
|
||||||
|
object.__setattr__(self, 'fobject', fobject)
|
||||||
|
object.__setattr__(self, 'name', name)
|
||||||
|
|
||||||
|
def __getattribute__(self, attr):
|
||||||
|
if attr == 'name':
|
||||||
|
return object.__getattribute__(self, attr)
|
||||||
|
fobject = object.__getattribute__(self, 'fobject')
|
||||||
|
return getattr(fobject, attr)
|
||||||
|
|
||||||
|
def __setattr__(self, attr, val):
|
||||||
|
fobject = object.__getattribute__(self, 'fobject')
|
||||||
|
return setattr(fobject, attr, val)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
fobject = object.__getattribute__(self, 'fobject')
|
||||||
|
name = object.__getattribute__(self, 'name')
|
||||||
|
return re.sub(r'''['"]<fdopen>['"]''', repr(name),
|
||||||
|
repr(fobject))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return repr(self).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
m = mode[0]
|
m = mode[0]
|
||||||
random = len(mode) > 1 and mode[1] == '+'
|
random = len(mode) > 1 and mode[1] == '+'
|
||||||
binary = mode[-1] == 'b'
|
binary = mode[-1] == 'b'
|
||||||
@ -139,6 +167,7 @@ if not _run_once:
|
|||||||
flags |= os.O_NOINHERIT
|
flags |= os.O_NOINHERIT
|
||||||
fd = os.open(name, flags)
|
fd = os.open(name, flags)
|
||||||
ans = os.fdopen(fd, mode, bufsize)
|
ans = os.fdopen(fd, mode, bufsize)
|
||||||
|
ans = fwrapper(name, ans)
|
||||||
else:
|
else:
|
||||||
import fcntl
|
import fcntl
|
||||||
try:
|
try:
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: calibre 0.7.21\n"
|
"Project-Id-Version: calibre 0.7.21\n"
|
||||||
"POT-Creation-Date: 2010-10-01 14:42+MDT\n"
|
"POT-Creation-Date: 2010-10-02 11:26+MDT\n"
|
||||||
"PO-Revision-Date: 2010-10-01 14:42+MDT\n"
|
"PO-Revision-Date: 2010-10-02 11:26+MDT\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: LANGUAGE\n"
|
"Language-Team: LANGUAGE\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -10057,7 +10057,7 @@ msgid ""
|
|||||||
" files in each directory of the calibre library. This is\n"
|
" files in each directory of the calibre library. This is\n"
|
||||||
" useful if your metadata.db file has been corrupted.\n"
|
" useful if your metadata.db file has been corrupted.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" WARNING: This completely regenrates your datbase. You will\n"
|
" WARNING: This completely regenerates your datbase. You will\n"
|
||||||
" lose stored per-book conversion settings and custom recipes.\n"
|
" lose stored per-book conversion settings and custom recipes.\n"
|
||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user