# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details.
#
# Distributed under the terms of the BSD license.
#
# The full license is in the file LICENCE, distributed with this software.
# -----------------------------------------------------------------------------
"""Implements a wrapper around the PyQt clipboard that handles Python objects
using pickle.
This has been ported from Enthought TraitsUI.
"""
from __future__ import (division, unicode_literals, print_function,
absolute_import)
try:
from cPickle import dumps, load, loads, PickleError
from cStringIO import StringIO
except ImportError:
from pickle import dumps, load, loads, PickleError
from io import StringIO
import warnings
from future.builtins import bytes
from enaml.qt import QtCore, QtWidgets
from atom.api import Atom, Property
[docs]class PyMimeData(QtCore.QMimeData):
""" The PyMimeData wraps a Python instance as MIME data.
Parameters
----------
data :
Object to copy to the clipboard.
pickle :
Whether or not to pickle the data.
"""
# The MIME type for instances.
MIME_TYPE = 'application/exopy-qt4-instance'
NOPICKLE_MIME_TYPE = 'application/exopy-qt4-instance'
def __init__(self, data=None, pickle=False):
QtCore.QMimeData.__init__(self)
# Keep a local reference to be returned if possible.
self._local_instance = data
if pickle:
if data is not None:
# We may not be able to pickle the data.
try:
pdata = dumps(data, -1)
# This format (as opposed to using a single sequence)
# allows the type to be extracted without unpickling
# the data.
self.setData(self.MIME_TYPE, dumps(data.__class__) + pdata)
except (PickleError, TypeError):
# if pickle fails, still try to create a draggable
warnings.warn(("Could not pickle dragged object %s, " +
"using %s mimetype instead") % (repr(data),
self.NOPICKLE_MIME_TYPE), RuntimeWarning)
self.setData(self.NOPICKLE_MIME_TYPE,
str(id(data)).encode('utf8'))
else:
self.setData(self.NOPICKLE_MIME_TYPE, str(id(data)).encode('utf8'))
[docs] @classmethod
def coerce(cls, md):
""" Wrap a QMimeData or a python object to a PyMimeData.
"""
# See if the data is already of the right type. If it is then we know
# we are in the same process.
if isinstance(md, cls):
return md
if isinstance(md, PyMimeData):
# If it is a PyMimeData, migrate all its data, subclasses should
# override this method if it doesn't do things correctly for them
data = md.instance()
nmd = cls()
nmd._local_instance = data
for format in md.formats():
nmd.setData(format, md.data(format))
elif isinstance(md, QtCore.QMimeData):
# If it is a QMimeData, migrate all its data
nmd = cls()
for format in md.formats():
nmd.setData(format, md.data(format))
else:
# By default, don't try to pickle the coerced object
pickle = False
# See if the data is a list, if so check for any items which are
# themselves of the right type. If so, extract the instance and
# track whether we should pickle.
# HINT lists should suffice for now, but may want other containers
if isinstance(md, list):
md = [item.instance() if isinstance(item, PyMimeData) else item
for item in md]
# Arbitrary python object, wrap it into PyMimeData
nmd = cls(md, pickle)
return nmd
[docs] def instance(self):
""" Return the instance.
"""
if self._local_instance is not None:
return self._local_instance
if not self.hasFormat(self.MIME_TYPE):
# We have no pickled python data defined.
return None
io = StringIO(bytes(self.data(self.MIME_TYPE)))
try:
# Skip the type.
load(io)
# Recreate the instance.
return load(io)
except PickleError:
pass
return None
[docs] def instance_type(self):
""" Return the type of the instance.
"""
if self._local_instance is not None:
return self._local_instance.__class__
try:
if self.hasFormat(self.MIME_TYPE):
return loads(bytes(self.data(self.MIME_TYPE)))
except PickleError:
pass
return None
[docs] def local_paths(self):
""" The list of local paths from url list, if any.
"""
ret = []
for url in self.urls():
if url.scheme() == 'file':
ret.append(url.toLocalFile())
return ret
class _Clipboard(Atom):
""" The _Clipboard class provides a wrapper around the PyQt clipboard.
"""
# --- Members definitions -------------------------------------------------
#: The instance on the clipboard (if any).
instance = Property()
#: Set if the clipboard contains an instance.
has_instance = Property()
#: The type of the instance on the clipboard (if any).
instance_type = Property()
# --- Instance property methods -------------------------------------------
@instance.getter
def _instance_getter(self):
""" The instance getter.
"""
md = PyMimeData.coerce(QtWidgets.QApplication.clipboard().mimeData())
if md is None:
return None
return md.instance()
@instance.setter
def _instance_setter(self, data):
""" The instance setter.
"""
QtWidgets.QApplication.clipboard().setMimeData(PyMimeData(data))
@has_instance.getter
def _has_instance_getter(self):
""" The has_instance getter.
"""
clipboard = QtWidgets.QApplication.clipboard()
return clipboard.mimeData().hasFormat(PyMimeData.MIME_TYPE)
@instance_type.getter
def _instance_type_getter(self):
""" The instance_type getter.
"""
md = PyMimeData.coerce(QtWidgets.QApplication.clipboard().mimeData())
if md is None:
return None
return md.instance_type()
#: The singleton clipboard instance.
CLIPBOARD = _Clipboard()