Last active
September 5, 2017 10:01
-
-
Save zrzka/e6ef011f112c933dbac8313030561a17 to your computer and use it in GitHub Desktop.
Pythonista & Working Copy Drag & Drop Experiments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ui | |
from objc_util import ObjCClass, ObjCInstance, ns, create_objc_class, on_main_thread, ObjCBlock | |
from ctypes import c_void_p, CFUNCTYPE, Structure, cast, c_int, c_ulong, c_char_p, POINTER | |
import editor | |
import os | |
import zipfile | |
ASYNC = True | |
DRAG_PATH = editor.get_path() # Drag itself, ie. drag_provider.py | |
# DRAG_PATH = os.path.expanduser('~/Documents/Examples') # Drag ZIPpped folder | |
_TMP_DIR = os.environ.get('TMPDIR', os.environ.get('TMP')) | |
NSItemProvider = ObjCClass('NSItemProvider') | |
UIDragItem = ObjCClass('UIDragItem') | |
UIDragInteraction = ObjCClass('UIDragInteraction') | |
NSData = ObjCClass('NSData') | |
NSItemProviderRepresentationVisibilityAll = 0 | |
NSError = ObjCClass('NSError') | |
def _type_identifier(path): | |
if os.path.isdir(path): | |
return 'public.folder' | |
elif os.path.isfile(path): | |
return 'public.data' | |
return None | |
def _suggested_name(path): | |
name = os.path.basename(path) | |
if os.path.isdir(path): | |
name += '.zip' | |
return name | |
def _provide_file_data(path): | |
data = NSData.dataWithContentsOfFile_(path) | |
if not data: | |
print('Failed to read file: {}'.format(path)) | |
return None | |
return data | |
def _provide_folder_data(path): | |
# | |
# Let's say path is ~/Documents/Playground, ZIP content is: | |
# | |
# - Playground/ | |
# - Playground/some.py | |
# - Playground/folder/another.py | |
# | |
# .git folders are not included. | |
# | |
saved_dir = os.getcwd() | |
os.chdir(os.path.dirname(path)) | |
folder_name = os.path.basename(path) | |
zip_file_path = os.path.join(_TMP_DIR, '{}.zip'.format(folder_name)) | |
if os.path.exists(zip_file_path): | |
os.remove(zip_file_path) | |
ignore_folders = ['.git'] | |
ignore_files = ['.DS_Store'] | |
with zipfile.ZipFile(zip_file_path, 'w') as zip: | |
for root, subdirs, files in os.walk(folder_name, topdown=True): | |
if ignore_folders: | |
subdirs[:] = [d for d in subdirs if d not in ignore_folders] | |
for file in files: | |
if file not in ignore_files: | |
zip.write(os.path.join(root, file)) | |
os.chdir(saved_dir) | |
return _provide_file_data(zip_file_path) | |
def _provide_data(path): | |
if os.path.isfile(path): | |
return _provide_file_data(path) | |
elif os.path.isdir(path): | |
return _provide_folder_data(path) | |
return None | |
# - (void)registerDataRepresentationForTypeIdentifier:(NSString *)typeIdentifier | |
# visibility:(NSItemProviderRepresentationVisibility)visibility | |
# loadHandler:(NSProgress * _Nullable (^)(void (^completionHandler)(NSData *data, NSError *error)))loadHandler; | |
# https://clang.llvm.org/docs/Block-ABI-Apple.html | |
class BlockDescriptor (Structure): | |
_fields_ = [('reserved', c_ulong), | |
('size', c_ulong), | |
('copy_helper', c_void_p), | |
('dispose_helper', c_void_p), | |
('signature', c_char_p)] | |
class Block(Structure): | |
_fields_ = [('isa', c_void_p), | |
('flags', c_int), | |
('reserved', c_int), | |
('invoke', c_void_p), | |
('descriptor', BlockDescriptor)] | |
def block_signature(block): | |
copy_dispose_flag = 1 << 25 | |
signature_flag = 1 << 30 | |
if not block.contents.flags & signature_flag: | |
return None | |
if block.contents.flags & copy_dispose_flag: | |
return block.contents.descriptor.signature | |
else: | |
return block.contents.descriptor.copy_helper | |
def _load_data_imp(_cmd, _block_ptr): | |
print('Load data requested...') | |
block = cast(_block_ptr, POINTER(Block)) | |
IMPTYPE = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p) | |
completion_handler = IMPTYPE(block.contents.invoke) | |
data = _provide_data(DRAG_PATH) | |
if not data: | |
print('Failed to load data') | |
error = NSError.errorWithDomain_code_userInfo_('dragprovider', 1, None) | |
completion_handler(_block_ptr, None, error) | |
else: | |
completion_handler(_block_ptr, data, None) | |
load_data = ObjCBlock(_load_data_imp, restype=c_void_p, argtypes=[c_void_p, c_void_p]) | |
def dragInteraction_itemsForBeginningSession_(_self, _cmd, interaction, session): | |
type_identifier = _type_identifier(DRAG_PATH) | |
if not type_identifier: | |
print('Failed to provide data, file does not exists?') | |
return ns([]).ptr | |
suggested_name = _suggested_name(DRAG_PATH) | |
if ASYNC: | |
print('Async dragging {} ({}) ...'.format(suggested_name, type_identifier)) | |
item_provider = NSItemProvider.alloc().init() | |
item_provider.registerDataRepresentationForTypeIdentifier_visibility_loadHandler_(type_identifier, NSItemProviderRepresentationVisibilityAll, load_data) | |
else: | |
data = _provide_data(DRAG_PATH) | |
if not data: | |
print('Unable to load data: {}'.format(DRAG_PATH)) | |
print('Dragging {} ({}) file size: {} bytes'.format(suggested_name, type_identifier, data.length())) | |
item_provider = NSItemProvider.alloc().initWithItem_typeIdentifier_(data, type_identifier) | |
if not item_provider: | |
print('Failed to create item provider.') | |
return ns([]).ptr | |
if suggested_name: | |
item_provider.setSuggestedName(suggested_name) | |
item = UIDragItem.alloc().initWithItemProvider_(item_provider) | |
if not item: | |
print('Failed to create drag item.') | |
return ns([]).ptr | |
return ns([item]).ptr | |
class DragView(ui.View): | |
def __init__(self): | |
self.width = 540 | |
self.height = 540 | |
self.label = ui.Label() | |
self.label.text = 'Drag Me' | |
self.label.alignment = ui.ALIGN_CENTER | |
self.label.frame = self.bounds | |
self.add_subview(self.label) | |
methods = [dragInteraction_itemsForBeginningSession_] | |
protocols = ['UIDragInteractionDelegate'] | |
DragProviderDelegate = create_objc_class('DragProviderDelegate', methods=methods, protocols=protocols) | |
self.delegate = DragProviderDelegate.alloc().init() | |
interaction = UIDragInteraction.alloc().initWithDelegate_(self.delegate) | |
label_objc = ObjCInstance(self.label._objc_ptr) | |
label_objc.addInteraction(interaction) | |
label_objc.setUserInteractionEnabled(True) | |
@on_main_thread | |
def main(): | |
v = DragView() | |
v.present('sheet') | |
v.wait_modal() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment