From 33b9f22282a462578a010d63d9402761ecfdd9d4 Mon Sep 17 00:00:00 2001 From: Raphael Matto Date: Fri, 13 May 2016 14:47:40 -0400 Subject: [PATCH] For #36436, merged Amy's drag/drop fix from the wb framework. --- .../tk_multi_importcut/widgets/drop_area.py | 107 ++++++++++++------ .../widgets/translate_file_reference.so | Bin 0 -> 10692 bytes .../widgets/translate_file_reference/setup.py | 86 ++++++++++++++ .../tests/__init__.py | 30 +++++ .../translate_file_reference.m | 76 +++++++++++++ .../translate_file_reference_module.c | 82 ++++++++++++++ .../translate_file_reference_module.h | 55 +++++++++ 7 files changed, 399 insertions(+), 37 deletions(-) create mode 100755 python/tk_multi_importcut/widgets/translate_file_reference.so create mode 100644 python/tk_multi_importcut/widgets/translate_file_reference/setup.py create mode 100644 python/tk_multi_importcut/widgets/translate_file_reference/tests/__init__.py create mode 100644 python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference.m create mode 100644 python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.c create mode 100644 python/tk_multi_importcut/widgets/translate_file_reference/translate_file_reference_module.h 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 0000000000000000000000000000000000000000..ef1449a21b4fad455fe4c851c3a4bf696a025197 GIT binary patch literal 10692 zcmeHNZEPGz8J>%iG`LOU#Bmi=8ZO9AozUFHZ5$j6qdTANtcjCcKJ1ikTxMbhQc&*k`E$XUBejX zL%pLzma6>HP!5^I;AHENh?9hKQ=KqHS>STMxetgs6Mc~oUMq^?x|U5jA?JpfiyA8` zw|~tyiK^Ywg0N@B8h%N>^-?GJctTBORUJAvUs&>uNPEK4RiRnsUrfswr2)J7<|N-a z$w#8O; zMGS)&-Fz=eYy18ZcJhJ6#TSp5k(J@E@^4vM2c!w%>gzz%D6M*{$Gdm4AaXk4^v}5l zk|jMC3KHi7{YMV<_Vn9pycbFmo5*V+c3D0MG*6sdA7N~2FPV5nlLq7qfVM1qv8Drj zWd~ykKp`(7bpMLz_ zYlqr?@y$P7Y<%LcZ5N5p8rph1J@xh0oFA38)_mLCgQ>BH`3ENrQ%(B^l2JXPPx||4 zNxEOtPaFPVHhU;(`m9BYH2x7)H#`nkhXW!%;K z2>DC(lD~ZXzk#-V{PYC-g3i%1wi8x#e>Px#&|0udVwx}i&oZp!f2+)PX?UiU*>)cu zLuIz<98lXc!KeO$d@+UTqU6_|psMM4a|G50EwlCnnZ2-h7a5@D7GMa&^`z2T?vxgVIuX z7js?UdIHL}XL`xOD@y+5!}&LhO*fL^(?ucYl>=9E>y+si%!6p0>z?!UA9&I1dhGxj z>o6RcX@3QeN3i%JjRD-yN1?YA-$&#&QV*biRmuNokvwdh-}K}>hT3o<_$kD!Ajj9A ze;clI>U*ZWm6-Ey6`Rh3+X~Jv7yo>{SbT`?-b`0eSk+UOf4SInFIge3ooMN($LrAD%LEvbSIT)=~`-~(#NT^ol3N{FKwmLDJs#I zOJbTzNNiyCwpnx(94xjxfV`0SGS&PJw}xtk4C#@Kk&2kAH>alzuc>**k}1`jjhG3e zzTUKJIcYgw;Hs`>VydrHfo82pyXw8(5|^1!z2nKVYQ{T!Y=B537Oi2e{T8{8l_<)& zS&63PN=0jjC*?Fe6b$!QY$j?sk83f`9ab2LqyGp6g&8cQVBdIY>Ppffi+UdQ$#kifN)Xcb_4}k#%hKz|=(H?4SxSpVQps~_yhk-+deW+7IYZU^ z>4$>&v?TW7p}r2uz;F{tWkU*77&jPd7Ks{KDrc&qj+I(rdSD*256OAr%(0MJzbE%0 zr_>@paH))WhIefp-m_F<+;$;hXRH6!oPO_;9G7`q<{6n!$oxT>e@W(|74{JD4k7uu{GGa6z?G~0`|?)# zJF!FN5t*Nr`FWXtUglqu`JBwZC-W6zSK-^T{{SsL5W2x6m!w{x+O-yeS_Enls70U_ zfm#G=5vWC=7J*s>Y7wYKpcaAutq5%1(RxpJw&8f_SnzHd*(U-#JlQ)D<0Fw&PVJ@5 z9(MN`RtRwD`4KHcJ1@DI$qHKnIIf6W$M8IglV2R0Pi-hP2KdqLPlkACAUHV4oB5GJ z+O?Uw>LCUm>I>1ScqC(r{hJUSvmLW{>DbgVc0Dce6t)K4L_LoEC^nS^p{pmkCsb$( z$ir=8d{E8A+0@rdv}ana5eD^f9**dSI+V+%R90YT_FDST(da4ckMTZTOOI&Dc-Yi~ z8Efl`Y&t}Abe;EM&n$uS#xRskv92`V<*rM{}9008t{~=%l8#o*x+Ze0~hDi zY`BiiHDar)(1gqKEx63l2_W{z8VjEAg2y^*ii@7D>wa9FcocZ7&clMi-w%@k?VtE? zvHySLIpAgorh3|wS{V+?N1$|KYH#nyydm4$*lr($o`f*9w{a87UaFAd1>V`fn0>C= zO&DjQ_POYk#2wI)0J1n;q|jcWbG(nB&WXPY{9fZP$o-^75YYH-Ow=^~1tb!}Z1zo{Cg;ji>~`yGb? zzcFcXqk5MQ>XI2R>U|FMDXJyyofir}Tm+RQ@pp&8n9@bixDYYaUAX3*P+= R_5NQ( +#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