Skip to content

Instantly share code, notes, and snippets.

@zrzka
Last active September 5, 2017 10:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zrzka/e6ef011f112c933dbac8313030561a17 to your computer and use it in GitHub Desktop.
Save zrzka/e6ef011f112c933dbac8313030561a17 to your computer and use it in GitHub Desktop.
Pythonista & Working Copy Drag & Drop Experiments
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