diff --git a/python/tk_multi_importcut/widgets/drop_area.py b/python/tk_multi_importcut/widgets/drop_area.py index 517cb2ff..1bca9af1 100644 --- a/python/tk_multi_importcut/widgets/drop_area.py +++ b/python/tk_multi_importcut/widgets/drop_area.py @@ -56,21 +56,46 @@ def dragEnterEvent(self, e): # } if e.mimeData().hasFormat("text/plain"): e.accept() - elif e.mimeData().hasFormat("text/uri-list"): - for url in e.mimeData().urls(): - _, ext = os.path.splitext(url.path()) - # Accept anything if no extentions are specified. - if not self._restrict_to_ext or ext.lower() in self._restrict_to_ext: + return + else: + urls = e.mimeData().urls() + if sys.platform == "darwin": + # Fix for Yosemite, file paths are not actual file paths + # but weird /.file/id=6571367.18855673 values + # https://bugreports.qt-project.org/browse/QTBUG-24379 + # https://gist.github.com/wedesoft/3216298 + import translate_file_reference + for url in urls: + _, ext = os.path.splitext(url.path()) + # Accept anything if no extentions are specified. + if not self._restrict_to_ext or ext.lower() in self._restrict_to_ext: + # We don't activate the dragging state unless ext is valid. + self._set_property("dragging", True) + str_url = url.toString() + if str_url.startswith("file:///.file"): + # for post-Yosemite OSX versions we need to translate to an actual path + new_url = QtCore.QUrl( + str(translate_file_reference.translate_url(url.toString()))) + # Accept if there is at least one local file + if new_url.isLocalFile(): + e.accept() + return + # Accept if there is at least one local file + elif url.isLocalFile(): + e.accept() + return + elif e.mimeData().hasFormat("text/uri-list"): + for url in e.mimeData().urls(): # We don't activate the dragging state unless ext is valid. self._set_property("dragging", True) - # Accept if there is at least one local file - if url.isLocalFile(): - e.accept() - break - else: - e.ignore() - else: - e.ignore() + _, ext = os.path.splitext(url.path()) + # Accept anything if no extentions are specified. + if not self._restrict_to_ext or ext.lower() in self._restrict_to_ext: + # Accept if there is at least one local file + if url.isLocalFile(): + e.accept() + return + e.ignore() # Override dragLeaveEvent def dragLeaveEvent(self, e): @@ -85,35 +110,35 @@ def dropEvent(self, e): Process a drop event, build a list of local files and emit somethingDropped if not empty """ self._set_property("dragging", False) - if e.mimeData().hasFormat("text/plain"): - contents = [e.mimeData().text()] - else: - urls = e.mimeData().urls() + + urls = e.mimeData().urls() + if urls : if sys.platform == "darwin": - # Fix for Yosemite and later, file paths are not actual file paths + # Fix for Yosemite, file paths are not actual file paths # but weird /.file/id=6571367.18855673 values # https://bugreports.qt-project.org/browse/QTBUG-24379 # https://gist.github.com/wedesoft/3216298 - try: - # Custom Python (e.g. brewed ones) might not be able to - # import Foundation. In that case we do nothing and keep - # urls as they are, assuming the problem should be fixed - # in custom Python / PyQt / PySide - import Foundation - fixed_urls = [] - for url in urls: - # It is fine to pass a regular file url to this method - # e.g. file:///foo/bar/blah.ext - fu = Foundation.NSURL.URLWithString_(url.toString()).filePathURL() - fixed_urls.append(QtCore.QUrl(str(fu))) - urls = fixed_urls - except: - pass - contents = [x.toLocalFile() for x in urls if x.isLocalFile()] - if contents: + import translate_file_reference + fixed_urls = [] + for url in urls: + str_url = url.toString() + if str_url.startswith("file:///.file"): + # for post-Yosemite OSX versions we need to translate to an actual path + new_url = QtCore.QUrl(str(translate_file_reference.translate_url(url.toString()))) + fixed_urls.append(new_url) + else: + # otherwise we can add the url as-is + fixed_urls.append(url) + urls = fixed_urls + + contents = [ x.toLocalFile() for x in urls if x.isLocalFile() ] + elif e.mimeData().hasFormat("text/plain") : + contents = [ e.mimeData().text() ] + + if contents : e.accept() - self.something_dropped.emit(contents) - else: + self.something_dropped.emit( contents ) + else : e.ignore() def _set_property(self, name, value): @@ -163,3 +188,11 @@ class DropAreaScrollArea(QtGui.QScrollArea): Custom scroll area widget so we can override drop callback and add a somethingDropped signal """ pass + + +@drop_area +class DropAreaTextEdit(QtGui.QTextEdit): + """ + Custom text edit widget so we can override the drop callback and add a somethingDropped signal + """ + pass diff --git a/python/tk_multi_importcut/widgets/translate_file_reference.so b/python/tk_multi_importcut/widgets/translate_file_reference.so new file mode 100755 index 00000000..ef1449a2 Binary files /dev/null and b/python/tk_multi_importcut/widgets/translate_file_reference.so differ diff --git a/python/tk_multi_importcut/widgets/translate_file_reference/setup.py b/python/tk_multi_importcut/widgets/translate_file_reference/setup.py new file mode 100644 index 00000000..0b6942c3 --- /dev/null +++ b/python/tk_multi_importcut/widgets/translate_file_reference/setup.py @@ -0,0 +1,86 @@ +# Copyright (c) 2015 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os +import sys + +from distutils import log +from distutils.errors import DistutilsSetupError +from distutils.core import setup, Extension +from distutils.command.build_clib import build_clib + + +# need to override build_clib to build a shared library +class build_shared_clib(build_clib): + def finalize_options(self): + build_clib.finalize_options(self) + + # override default build_clib to be the build directory + self.build_clib = None + self.set_undefined_options('build', ("build_lib", "build_clib")) + + def build_libraries(self, libraries): + for (lib_name, build_info) in libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError, \ + ("in 'libraries' option (library '%s'), " + + "'sources' must be present and must be " + + "a list of source filenames") % lib_name + sources = list(sources) + + log.info("building '%s' library", lib_name) + + # First, compile the source code to object files in the library + # directory. (This should probably change to putting object + # files in a temporary build directory.) + macros = build_info.get('macros') + include_dirs = build_info.get('include_dirs') + objects = self.compiler.compile(sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + debug=self.debug, + ) + + # Now "link" the object files together into a shared library. + # Make sure to add in the path for the python library by default + self.compiler.link_shared_lib(objects, lib_name, + library_dirs=[os.path.join(sys.exec_prefix, "libs")], + libraries=build_info.get("libraries", []), + output_dir=self.build_clib, + debug=self.debug, + extra_postargs=["/DELAYLOAD:python27.dll"], + ) + +sources = ["translate_file_reference_module.c"] +modules = [] +libraries = [] +libarary_builds = [] + +if sys.platform == "darwin": + os.environ["LDFLAGS"] = "-framework AppKit" + sources.extend(["translate_file_reference.m"]) +elif sys.platform == "win32": + raise NotImplementedError +elif sys.platform.startswith("linux"): + raise NotImplementedError + +module = Extension("translate_file_reference", sources=sources, libraries=libraries) +modules.append(module) + +setup( + name="translate_file_reference", + version="1.0", + description="Module to expose os level functionality.", + ext_modules=modules, + libraries=libarary_builds, + cmdclass={'build_clib': build_shared_clib}, +) diff --git a/python/tk_multi_importcut/widgets/translate_file_reference/tests/__init__.py b/python/tk_multi_importcut/widgets/translate_file_reference/tests/__init__.py new file mode 100644 index 00000000..94c22950 --- /dev/null +++ b/python/tk_multi_importcut/widgets/translate_file_reference/tests/__init__.py @@ -0,0 +1,30 @@ +# Copyright (c) 2015 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import sys +import translate_file_reference + +from PySide import QtGui + + +class View(QtGui.QWidget): + def __init__(self): + QtGui.QWidget.__init__(self) + self.setAcceptDrops(True) + + def dragEnterEvent(self, e): + urls = e.mimeData().urls() + print urls + print translate_file_reference.translate_url(urls[0].toString()) + +app = QtGui.QApplication(sys.argv) +view = View() +view.show() +app.exec_() diff --git a/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference.m b/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference.m new file mode 100644 index 00000000..29192011 --- /dev/null +++ b/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference.m @@ -0,0 +1,76 @@ +// Copyright (c) 2015 Shotgun Software Inc. + +// CONFIDENTIAL AND PROPRIETARY + +// This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +// Source Code License included in this distribution package. See LICENSE. +// By accessing, using, copying or modifying this work you indicate your +// agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +// not expressly granted therein are reserved by Shotgun Software Inc. + +#include +#include +#include + +BOOL +is_valid_url(NSString *url) +{ + NSUInteger length = [url length]; + // Empty strings should return NO + if (length > 0) { + NSError *error = nil; + NSDataDetector *dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error]; + if (dataDetector && !error) { + NSRange range = NSMakeRange(0, length); + NSRange notFoundRange = (NSRange){NSNotFound, 0}; + NSRange linkRange = [dataDetector rangeOfFirstMatchInString:url options:0 range:range]; + if (!NSEqualRanges(notFoundRange, linkRange) && NSEqualRanges(range, linkRange)) { + return true; + } + } else { + NSLog(@"Could not create link data detector: %@ %@", [error localizedDescription], [error userInfo]); + } + } + return false; +} + +/* register_global_hotkey *****************************************************/ +PyObject * +translate_url(PyObject *module, const char *url) +{ + + NSURL *url_as_url = NULL; + NSString *url_as_string = NULL; + NSString *translated_string = NULL; + const char *translation; + + PyObject *ret = NULL; + + url_as_string = [NSString stringWithUTF8String: url]; + if (!is_valid_url(url_as_string)) + return PyErr_Format(PyExc_ValueError, "malformed url: '%s'", url); + + // Convert the UTF-8 encoded url into an NSURL + url_as_url = [NSURL URLWithString: url_as_string]; + + // The string could not be turned into a URL + if (url_as_url == nil) + return PyErr_Format(PyExc_ValueError, "malformed url: '%s'", url); + + // Get the actual file path + translated_string = [[url_as_url filePathURL] absoluteString]; + + // Convert it into a python string + translation = [translated_string UTF8String]; + + // If there was no answer then return this + if (translation == NULL) + Py_RETURN_NONE; + + // We have a translation, return it + ret = PyString_FromString(translation); + Py_INCREF(ret); + + // Return the results + return ret; +} diff --git a/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.c b/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.c new file mode 100644 index 00000000..8d3b63db --- /dev/null +++ b/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.c @@ -0,0 +1,82 @@ +// Copyright (c) 2015 Shotgun Software Inc. + +// CONFIDENTIAL AND PROPRIETARY + +// This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +// Source Code License included in this distribution package. See LICENSE. +// By accessing, using, copying or modifying this work you indicate your +// agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +// not expressly granted therein are reserved by Shotgun Software Inc. + +#include + +#define TRANSLATE_FILE_REFERENCE_MODULE +#include "translate_file_reference_module.h" + +/******************************************************************************* + * @file translate_file_reference_module.c + * @brief Python bindings for translating file references + ******************************************************************************/ + +const char docstring[] = +"Translate urls to file paths\n" \ +"\n" \ +"translate_file_reference.translate_url(url)\n" \ +" Translate the given URL to a filesystem path."; + +static PyObject *module; + +/* translate_file_reference_translate_url *****************************************************/ +static PyObject * +translate_file_reference_translate_url(PyObject *self, PyObject *args) +{ + int results; + PyObject *translation; + + // Required arguments + const char *url; + + // Parse arguments + results = PyArg_ParseTuple(args, "s", &url); + if (!results) + return NULL; + + // Call the implementation + translation = translate_url(module, url); + + return translation; +} + +/* module *********************************************************************/ +static PyMethodDef translate_file_reference_methods[] = { + /* {name, meth, flags, doc} */ + {"translate_url", (PyCFunction)translate_file_reference_translate_url, METH_VARARGS, "Translate a url"}, + {NULL} +}; + +#ifndef PyMODINIT_FUNC +#define PyMODINIT_FUNC void +#endif + +PyMODINIT_FUNC +inittranslate_file_reference(void) +{ + PyObject *docs; + PyObject *c_api_object; + static Py_TRANSLATE_FILE_REFERENCE_CAPI Py_translate_file_reference_api; + + // Initialize the module + module = Py_InitModule3("translate_file_reference", translate_file_reference_methods, "Translate file paths"); + if (module == NULL) + return; + + // Create the c object for the C API + c_api_object = PyCObject_FromVoidPtrAndDesc((void *)&Py_translate_file_reference_api, "translate_file_reference.translate_file_reference_CAPI", NULL); + if (c_api_object != NULL) + PyModule_AddObject(module, "translate_file_reference_CAPI", c_api_object); + + // Add docstring + docs = PyString_FromString(docstring); + Py_INCREF(docs); + PyModule_AddObject(module, "__doc__", docs); +} diff --git a/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.h b/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.h new file mode 100644 index 00000000..5e529c86 --- /dev/null +++ b/python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.h @@ -0,0 +1,55 @@ +// Copyright (c) 2015 Shotgun Software Inc. + +// CONFIDENTIAL AND PROPRIETARY + +// This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +// Source Code License included in this distribution package. See LICENSE. +// By accessing, using, copying or modifying this work you indicate your +// agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +// not expressly granted therein are reserved by Shotgun Software Inc. + +#ifndef Py_TRANSLATE_FILE_REFERENCE_MODULE_H +#define Py_TRANSLATE_FILE_REFERENCE_MODULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Structure for the C API +// +// Put anything accessible to other modules at the C level +// into this structure. +typedef struct { + char filler; // Need to have at least one member +} Py_TRANSLATE_FILE_REFERENCE_CAPI; + + +#ifdef TRANSLATE_FILE_REFERENCE_MODULE +// Used when building the module + +/******************************************************************************* + * @brief Translate a given url into the real file path + * + * @param module The python object for the current module + * @param url The url to translate + * + * @return translated url + ******************************************************************************/ +PyObject *translate_url(PyObject *module, const char *url); + +#else // #ifdef TRANSLATE_FILE_REFERENCE_MODULE + +// Used when including outside the module +static Py_TRANSLATE_FILE_REFERENCE_CAPI *Py_TRANSLATE_FILE_REFERENCEAPI; + +// Macro to pull the c api out of the module +#define Py_TRANSLATE_FILE_REFERENCE_IMPORT \ + Py_TRANSLATE_FILE_REFERENCE_API = (Py_TRANSLATE_FILE_REFERENCE_CAPI *)PyCObject_Import("translate_file_reference", "translate_file_reference_CAPI") + +#endif // TRANSLATE_FILE_REFERENCE_MODULE + +#ifdef __cplusplus +} +#endif + +#endif // Py_TRANSLATE_FILE_REFERENCE_MODULE_H