Skip to content

Instantly share code, notes, and snippets.

@cvpe
Created March 14, 2019 14:41
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 cvpe/63698c70debe62cf064b90ba4b5313e7 to your computer and use it in GitHub Desktop.
Save cvpe/63698c70debe62cf064b90ba4b5313e7 to your computer and use it in GitHub Desktop.
Move cursor in TextView.py
import ui
from objc_util import *
import clipboard
import speech
v = ui.View()
v.frame = (0,0,500,320)
v.name = 'Move cursor in TextView'
tv = ui.TextView()
tv.name = 'TextView'
tv.frame = (120,10,370,140)
tv.font = ('Arial Rounded MT Bold',24)
tv.text = 'aรฉ๐Ÿ˜ข๐Ÿ‡ฏ๐Ÿ‡ต๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฉโ€๐ŸŽจ'
v.add_subview(tv)
tv2 = ui.TextView()
tv2.name = 'TextView2'
tv2.frame = (120,160,370,140)
tv2.font = ('Arial Rounded MT Bold',24)
tv2.text = 'second'
v.add_subview(tv2)
def say_char(tv):
# test speech character at cursor
idxtopos = IndexToPos(tv,'') # list index to position
i = tv.selected_range[0]
#print(i,idxtopos)
i = idxtopos[i] # used to check if same base character
if i < len(tv.text):
c = tv.text[i]
if c == ' ':
c ='space'
speech.say(c,'jp-JP')
def selected_range(tv,i):
tvo = ObjCInstance(tv)
p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i)
p2 = p1
#p2 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i+1)
tvo.selectedTextRange = tvo.textRangeFromPosition_toPosition_(p1, p2)
say_char(tv)
return
# some emoji like flags count as 2 for len but as 4 for selected_range
def IndexToPos(tv,type):
tvo = ObjCInstance(tv)
# build array index -> position in range
idxtopos = []
pre_x = -1
#print(tv.text)
i = 0
for c in tv.text:
# nbr characters used e=1 รฉ=1 ๐Ÿ˜‚=1 ๐Ÿ‡ฏ๐Ÿ‡ต=2 ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง=7
# some emoji generate more than one character,
# sometimes counted for more than one in range
# 1,2,3->1 4->2
nb = 1 + int(len(c.encode('utf-8'))/4)
for j in range(0,nb):
p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), len(idxtopos))
p2 = p1
rge = tvo.textRangeFromPosition_toPosition_(p1, p2)
rect = tvo.firstRectForRange_(rge) # CGRect
x = rect.origin.x
if x == float('inf') or x == pre_x:
# same x as previous one, composed character
pass
else:
pre_x = x
i = len(idxtopos)
idxtopos.append(i) # start position of c
#print(c,nb,len(idxtopos)-1,i,x)
idxtopos.append(i+1) # end position of last c
#print(idxtopos)
# get index of actual cursor
i = tv.selected_range[0] # actual position of cursor
# often p is one of sub_chars, not always the first one
p = idxtopos[i] # used to check if same base character
#print(p,i,idxtopos)
if type == 'left':
if i == 0:
return # already before character
while True:
i = i - 1
if idxtopos[i] != p:
q = idxtopos[i]
# seach first sub-character
while i > 0:
if idxtopos[i-1] != q:
break
i = i - 1
break
elif type == 'right':
if i == (len(idxtopos)-1):
return # already after last character
while True:
i = i + 1
if idxtopos[i] != p:
break
elif type == 'end':
i = len(idxtopos)-1
else:
return idxtopos
r = idxtopos[i]
selected_range(tv,i)
return idxtopos
b_top = ui.Button()
b_top.frame = (10,10,100,32)
b_top.title = 'begin'
b_top.background_color = 'white'
b_top.border_width = 1
def b_top_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
tv.selected_range = (0,0)
say_char(tv)
b_top.action = b_top_action
v.add_subview(b_top)
b_left = ui.Button()
b_left.frame = (10,50,100,32)
b_left.title = 'left'
b_left.background_color = 'white'
b_left.border_width = 1
def b_left_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
idxtopos = IndexToPos(tv,'left') # list index to position
b_left.action = b_left_action
v.add_subview(b_left)
b_right = ui.Button()
b_right.frame = (10,90,100,32)
b_right.title = 'right'
b_right.background_color = 'white'
b_right.border_width = 1
def b_right_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
idxtopos = IndexToPos(tv,'right') # list index to position
b_right.action = b_right_action
v.add_subview(b_right)
b_bottom = ui.Button()
b_bottom.frame = (10,130,100,32)
b_bottom.title = 'end'
b_bottom.background_color = 'white'
b_bottom.border_width = 1
def b_bottom_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
idxtopos = IndexToPos(tv,'end') # list index to position
b_bottom.action = b_bottom_action
v.add_subview(b_bottom)
def get_xy(tv):
idxtopos = IndexToPos(tv,'') # list index to position
tvo = ObjCInstance(tv)
x_y = []
for i in range(0,len(idxtopos)+1): # x,y of each character
p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i)
rge = tvo.textRangeFromPosition_toPosition_(p1,p1)
rect = tvo.firstRectForRange_(rge) # CGRect
x,y = rect.origin.x,rect.origin.y
if i == len(idxtopos):
if i > 0:
x,y = x_y[i-1]
else:
# text is empty
x,y = 0,0
if x == float('inf'):
x,y = x_prec+15,y_prec
x_prec,y_prec = x,y
x_y.append((x,y))
return x_y
b_up = ui.Button()
b_up.frame = (10,170,100,32)
b_up.title = 'up'
b_up.background_color = 'white'
b_up.border_width = 1
def b_up_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
x_y = get_xy(tv)
c = tv.selected_range[0]
xc,yc = x_y[c]
i = c - 1
while i >= 0:
x,y = x_y[i]
if y < yc:
# previous row
if x <= xc:
selected_range(tv,i)
return
i = i - 1
b_up.action = b_up_action
v.add_subview(b_up)
b_down = ui.Button()
b_down.frame = (10,210,100,32)
b_down.title = 'down'
b_down.background_color = 'white'
b_down.border_width = 1
def b_down_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
idxtopos = IndexToPos(tv,'') # list index to position
x_y = get_xy(tv)
c = tv.selected_range[0]
#print(x_y,c)
xc,yc = x_y[c]
i = c# - 1 # I don't remember why this "- 1"
while i < len(idxtopos):
x,y = x_y[i]
if y > yc:
# next row
if x >= xc:
selected_range(tv,i)
return
else:
if (i+1) < len(idxtopos):
if x_y[i+1][1] > y: # i = last character of row under cursor
selected_range(tv,i)
return
else:
pass # try next x
else:
# last character of last row
selected_range(tv,i)
return
i = i + 1
b_down.action = b_down_action
v.add_subview(b_down)
b_copy = ui.Button()
b_copy.frame = (10,250,100,32)
b_copy.title = 'copy'
b_copy.background_color = 'white'
b_copy.border_width = 1
def b_copy_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
clipboard.set(tv.text)
b_copy.action = b_copy_action
v.add_subview(b_copy)
b_clear = ui.Button()
b_clear.frame = (10,290,100,32)
b_clear.title = 'clear'
b_clear.background_color = 'white'
b_clear.border_width = 1
def b_clear_action(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = sender.superview[name]
tv.text = ''
b_clear.action = b_clear_action
v.add_subview(b_clear)
def typeChar(sender):
'''finds active textinput, and types the button's title'''
tf=sender.objc_instance.firstResponder()
tf.insertText_(sender.title)
#create special keys
def prev(sender):
'''simulates 'tab' key, go to next field '''
s=sender.objc_instance.firstResponder()._previousKeyResponder().becomeFirstResponder()
buttons.append(ui.Button(image=ui.Image.named('iob:ios7_arrow_back_32'),action=prev))
def next(sender):
'''simulates 'tab' key, go to next field '''
s=sender.objc_instance.firstResponder()._nextKeyResponder().becomeFirstResponder()
buttons.append(ui.Button(image=ui.Image.named('iob:ios7_arrow_forward_32'),action=next))
#create normal keys
d = 32
dd = 4
emojis = '๐Ÿ˜Š๐Ÿ˜œ๐Ÿ˜ฑ๐Ÿ’ฆโ˜”๏ธ๐Ÿ˜€๐Ÿ˜ƒ๐Ÿ˜„๐Ÿ˜๐Ÿ˜†๐Ÿ˜…๐Ÿ˜‚๐Ÿคฃโ˜บ๏ธ๐Ÿ˜Š๐Ÿ˜‡๐Ÿ™‚๐Ÿ™ƒ๐Ÿ˜‰๐Ÿ˜Œ๐Ÿ˜๐Ÿฅฐ๐Ÿ˜˜๐Ÿ˜—๐Ÿ˜™๐Ÿ˜š๐Ÿ˜‹๐Ÿ˜›๐Ÿ˜๐Ÿ˜œ๐Ÿคช๐Ÿคจ๐Ÿง๐Ÿค“๐Ÿ˜Ž๐Ÿคฉ๐Ÿฅณ๐Ÿ˜๐Ÿ˜’๐Ÿ˜ž๐Ÿ˜”๐Ÿ˜Ÿ๐Ÿ˜•๐Ÿ™โ˜น๏ธ๐Ÿ˜ฃ๐Ÿ˜–๐Ÿ˜ซ๐Ÿ˜ฉ๐Ÿฅบ๐Ÿ˜ข๐Ÿ˜ญ๐Ÿ˜ค๐Ÿ˜ ๐Ÿ˜ก๐Ÿคฌ๐Ÿคฏ๐Ÿ˜ณ๐Ÿฅต๐Ÿฅถ๐Ÿ˜ฑ๐Ÿ˜จ๐Ÿ˜ฐ๐Ÿ˜ฅ๐Ÿ˜“๐Ÿค—๐Ÿค”๐Ÿคญ๐Ÿคซ๐Ÿคฅ๐Ÿ˜ถ๐Ÿ˜๐Ÿ˜‘๐Ÿ˜ฌ๐Ÿ˜ฆ๐Ÿ˜ง๐Ÿ˜ฎ๐Ÿ˜ฒ๐Ÿ˜ด๐Ÿคค๐Ÿ˜ช๐Ÿ˜ต๐Ÿค๐Ÿฅด๐Ÿคข๐Ÿคฎ๐Ÿคง๐Ÿ˜ท๐Ÿค’๐Ÿค•๐Ÿค‘๐Ÿค ๐Ÿ˜ˆ'
n_emojis_in_set = 20
n_sets = 1 + int((len(emojis)-1)/n_emojis_in_set)
tv.i_set = 0
tv.n_sets = n_sets
def nextSet(sender):
name = str(ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder().name())
tv = v[name]
tv.i_set = tv.i_set + 1
if tv.i_set == tv.n_sets:
tv.i_set = 0
#attach our accessory to the textfield, and textview
ww = vv_array[tv.i_set]
tvo = tv.objc_instance
#print(dir(tvo))
tvo.setInputAccessoryView_(ObjCInstance(ww))
tvo.reloadInputViews()
vv_array = []
for i_set in range(0,n_sets):
l = int(len(emojis)/n_sets)
i = i_set * l
set_emojis = emojis[i:i+l]
w, h = ui.get_screen_size()
vv = ui.View(name='set'+str(i_set))
vv.background_color = 'lightgray'
h = 0
x = dd
y = dd
for button_title in set_emojis:
b = ui.Button(title=button_title)
b_action = typeChar
b.action=b_action
b.frame = (x,y,d,d)
b.font = ('.SFUIText', d)
if (y+d+dd) > h:
h = y + d + dd
vv.add_subview(b)
x = x + d + dd
if (x+d+dd) > w:
x = dd
y = y + d + dd
device = ObjCClass('UIDevice').currentDevice().model()
if str(device) == 'iPhone':
bb_target = ui.Button()
bb_target.title = 'next emojis'
wb,hb = ui.measure_string(bb_target.title,font=bb_target.font)
bb_target.action = nextSet
bb_target.frame = (dd,h,wb+2,d)
vv.add_subview(bb_target)
h = h + d + dd
vv.frame = (0,0,w,h)
vv_array.append(vv)
#nextSet(vv_array[n_sets-1]['nextSet']) # display 1st set
device = ObjCClass('UIDevice').currentDevice().model()
if str(device) == 'iPad':
# add a button at right of "typing suggestions", just above the keyboard
bb_target = ui.Button()
bb_target.action = nextSet
UIBarButtonItem = ObjCClass('UIBarButtonItem').alloc().initWithTitle_style_target_action_('next emojis',0,bb_target,sel('invokeAction:')).autorelease()
#UIBarButtonItem = ObjCClass('UIBarButtonItem').alloc().initWithImage_style_target_action_(ns(ui.Image.named('emj:Bicycle').with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)),0,bb_target,sel('invokeAction:')).autorelease()
UIBarButtonItemGroup = ObjCClass('UIBarButtonItemGroup').alloc().initWithBarButtonItems_representativeItem_([UIBarButtonItem],None)
for tv in v.subviews:
if 'TextView' in str(type(tv)):
tv.i_set = 0
tv.n_sets = n_sets
tvo = tv.objc_instance
#tvo.inputAssistantItem().setTrailingBarButtonGroups([UIBarButtonItemGroup])
tvo.inputAssistantItem().setLeadingBarButtonGroups([UIBarButtonItemGroup])
#print(dir(tvo))
#print(dir(tvo.inputAssistantItem()))
ww = vv_array[tv.i_set]
tvo.setInputAccessoryView_(ObjCInstance(ww))
tvo.reloadInputViews()
v.present('sheet')
v['TextView'].selected_range = (0,0)
v['TextView'].begin_editing()
#nextSet(vv_array[n_sets-1]['nextSet']) # display 1st set
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment