Drag and drop support: You can now darg and drop books from the calibre library to your computer and to the device directly

This commit is contained in:
Kovid Goyal 2010-10-02 19:23:35 -06:00
parent 4e2e2d11d6
commit cdf26cf66d
4 changed files with 134 additions and 18 deletions

View File

@ -234,13 +234,14 @@ class AddAction(InterfaceAction):
self.gui.set_books_in_library(booklists=[model.db], reset=True)
self.gui.refresh_ondevice()
def add_books_from_device(self, view):
rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
d = error_dialog(self.gui, _('Add to library'), _('No book selected'))
d.exec_()
return
paths = [p for p in view._model.paths(rows) if p is not None]
def add_books_from_device(self, view, paths=None):
if paths is None:
rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
d = error_dialog(self.gui, _('Add to library'), _('No book selected'))
d.exec_()
return
paths = [p for p in view.model().paths(rows) if p is not None]
ve = self.gui.device_manager.device.VIRTUAL_BOOK_EXTENSIONS
def ext(x):
ans = os.path.splitext(x)[1]
@ -261,7 +262,7 @@ class AddAction(InterfaceAction):
return
from calibre.gui2.add import Adder
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.Dispatcher(self.__adder_func), spare_server=self.gui.spare_server)
self._adder.add(paths)

View File

@ -56,6 +56,7 @@ class LocationManager(QObject): # {{{
self._mem.append(a)
else:
ac.setToolTip(tooltip)
ac.calibre_name = name
return ac
@ -112,7 +113,6 @@ class LocationManager(QObject): # {{{
ac.setWhatsThis(t)
ac.setStatusTip(t)
@property
def has_device(self):
return max(self.free) > -1
@ -228,6 +228,7 @@ class ToolBar(QToolBar): # {{{
self.added_actions = []
self.build_bar()
self.preferred_width = self.sizeHint().width()
self.setAcceptDrops(True)
def apply_settings(self):
sz = gprefs['toolbar_icon_size']
@ -317,6 +318,59 @@ class ToolBar(QToolBar): # {{{
def database_changed(self, db):
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): # {{{

View File

@ -1081,12 +1081,11 @@ class DeviceBooksModel(BooksModel): # {{{
self.db = db
self.map = list(range(0, len(db)))
def current_changed(self, current, previous):
data = {}
item = self.db[self.map[current.row()]]
def cover(self, row):
item = self.db[self.map[row]]
cdata = item.thumbnail
img = QImage()
if cdata is not None:
img = QImage()
if hasattr(cdata, 'image_path'):
img.load(cdata.image_path)
elif cdata:
@ -1094,9 +1093,16 @@ class DeviceBooksModel(BooksModel): # {{{
img.loadFromData(cdata[-1])
else:
img.loadFromData(cdata)
if img.isNull():
img = self.default_image
data['cover'] = img
if img.isNull():
img = self.default_image
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')
ext = os.path.splitext(item.path)[1]
if ext:

View File

@ -9,7 +9,8 @@ import os
from functools import partial
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
QModelIndex, QIcon, QItemSelection
QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, \
QPoint, QPixmap, QUrl
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
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.gui2 import error_dialog, gprefs
from calibre.gui2.library import DEFAULT_SORT
from calibre.constants import filesystem_encoding
from calibre import force_unicode
class BooksView(QTableView): # {{{
@ -31,6 +33,7 @@ class BooksView(QTableView): # {{{
self.setDragEnabled(True)
self.setDragDropOverwriteMode(False)
self.setDragDropMode(self.DragDrop)
self.drag_start_pos = None
self.setAlternatingRowColors(True)
self.setSelectionBehavior(self.SelectRows)
self.setShowGrid(False)
@ -426,6 +429,44 @@ class BooksView(QTableView): # {{{
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)]
def drag_icon(self, cover):
cover = cover.scaledToHeight(120, Qt.SmoothTransformation)
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()))
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):
if int(event.possibleActions() & Qt.CopyAction) + \
int(event.possibleActions() & Qt.MoveAction) == 0:
@ -547,6 +588,20 @@ class DeviceBooksView(BooksView): # {{{
self.setDragDropMode(self.NoDragDrop)
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()))
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
drag.setPixmap(cover)
return drag
def contextMenuEvent(self, event):
edit_collections = callable(getattr(self._model.db, 'supports_collections', None)) and \
self._model.db.supports_collections() and \