mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
4e2e2d11d6
commit
cdf26cf66d
@ -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)
|
||||
|
@ -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): # {{{
|
||||
|
@ -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:
|
||||
|
@ -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 \
|
||||
|
Loading…
x
Reference in New Issue
Block a user