Source code for exopy.utils.widgets.qt_clipboard

# -*- 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.

"""
import warnings
from pickle import dumps, load, loads, PickleError
from io import StringIO

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()