Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello β I'm @olemoritz on Twitter.
Pythonista special key row problems in IOS 14
-
@Enez-Houad said:
. I regret that itβs not possible to assign the fonction to a normal ui button.
I think it is also possible, let me some time today
-
@Enez-Houad much easier, if I would know π
import editor from objc_util import * import ui @on_main_thread def test(tv): import console import re import ui tv.strfind = console.input_alert('text or regex to search', '', tv.strfind, hide_cancel_button=True) txt = tv.strfind for sv in tv.subviews(): if 'SUIButton_PY3' in str(sv._get_objc_classname()): sv.removeFromSuperview() if txt == '': return t = str(tv.text()) #print('search',txt,'in',t) #for m in re.finditer('(?i)'+txt, t): for m in re.finditer(txt, t): st,en = m.span() p1 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), st) p2 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), en) rge = tv.textRangeFromPosition_toPosition_(p1,p2) rect = tv.firstRectForRange_(rge) # CGRect x,y = rect.origin.x,rect.origin.y w,h = rect.size.width,rect.size.height #print(x,y,w,h) l = ui.Button() l.frame = (x,y,w,h) if '|' not in txt: l.background_color = (1,0,0,0.2) else: # search multiple strings wrds = txt.split('|') idx = wrds.index(t[st:en]) cols = [(1,0,0,0.2), (0,1,0,0.2), (0,0,1,0.2), (1,1,0,0.2), (1,0,1,0.2), (0,1,1,0.2)] col = cols[idx % len(cols)] l.background_color = col l.corner_radius = 4 l.border_width = 1 tv.addSubview_(l) def key_pressed(sender): import ui from objc_util import ObjCClass tv = sender.objc_instance.firstResponder() # associated TextView # get actual cursor position cursor = tv.offsetFromPosition_toPosition_(tv.beginningOfDocument(), tv.selectedTextRange().start()) if sender.name == 'left': if cursor == 0: return # already first character cursor = cursor - 1 elif sender.name == 'right': if cursor == (len(str(tv.text()))-1): return # already after last character cursor = cursor + 1 elif sender.name == 'down': # surely not correct because not tested in limit cases, # just to show for topic https://forum.omz-software.com/topic/6408/defining-a-line-up-line-down-keyboard-function # count position in line c = str(tv.text()).rfind('\n',0,cursor) # begin of current line p = cursor - c e = str(tv.text()).find('\n',cursor) # end of current line cursor = e + 1 # begin of next line e = str(tv.text()).find('\n',cursor) # end of next line cursor = cursor + min(p-1,e-cursor) elif sender.name == 'next word': t = str(tv.text())[cursor+1:] for i in range(0,len(t)): # search 1st separator if t[i] in ' _.\n\t': # search 1st not separator ch_found = False for j in range(i+1,len(t)): if t[j] not in ' _.\n\t': ch_found = True cursor = cursor + 1 + j break if ch_found: break elif sender.name == 'delete': # delete at right = delete at left of next character if cursor == (len(str(tv.text()))-1): return # already after last character cursor_position = tv.positionFromPosition_offset_(tv.beginningOfDocument(), cursor+1) tv.selectedTextRange = tv.textRangeFromPosition_toPosition_(cursor_position, cursor_position) tv.deleteBackward() elif sender.name == 'find': test(tv) elif sender.name == 'undo': #tv = ObjCClass('UIApplication').sharedApplication().keyWindow().firstResponder() tv.undoManager().undo() elif sender.name == 'redo': tv.undoManager().redo() else: # normal key tv.insertText_(sender.title) return # set cursor cursor_position = tv.positionFromPosition_offset_(tv.beginningOfDocument(), cursor) tv.selectedTextRange = tv.textRangeFromPosition_toPosition_(cursor_position, cursor_position) class MyView(ui.View): def __init__(self, pad, *args, **kwargs): #super().__init__(self, *args, **kwargs) self.width = ui.get_screen_size()[0] # width of keyboard = screen self.background_color = 'lightgray'#(0,1,0,0.2) self.h_button = 32 self.pad = pad #================================================ for the fun begin # cable for road self.road = ui.Label() self.road.frame = (0,40,self.width,1) self.road.border_width = 1 self.road.border_color = 'green' self.road.flex = 'W' self.add_subview(self.road) # cable for tramway self.line = ui.Label() self.line.frame = (0,12,self.width,1) self.line.border_width = 1 self.line.border_color = 'gray' self.line.flex = 'W' self.line.hidden = True self.add_subview(self.line) # moving emoji behind buttons self.moving = ui.Button() self.moving.font = ('<system>',self.h_button-4) self.moving.frame = (0,10,self.h_button,self.h_button) self.moving.icons = ['emj:Delivery_Truck', 'emj:Car_1','emj:Car_2', 'emj:Bus', 'emj:Police_Car', 'emj:Railway_Car','emj:Speedboat'] self.moving.action = self.fun self.moving.index = 0 self.add_subview(self.moving) self.update_interval = 0.06 #================================================ for the fun end # build buttons for pad_elem in self.pad: if pad_elem['key'] in ('nul', 'new row'): # free space or new row continue button = ui.Button() # Button for user functionnality button.name = pad_elem['key'] button.background_color = 'white' # or any other color button.tint_color = 'black' button.corner_radius = 5 button.font = ('<system>',self.h_button - 8) button.title = '' if 'title' in pad_elem: button.title = pad_elem['title'] elif 'icon' in pad_elem: button.image = ui.Image.named(pad_elem['icon']).with_rendering_mode(ui.RENDERING_MODE_ORIGINAL) else: button.title = pad_elem['key'] button.action = key_pressed retain_global(button) # see https://forum.omz-software.com/topic/4653/button-action-not-called-when-view-is-added-to-native-view self.add_subview(button) self.layout() #================================================ for the fun begin def fun(self,sender): self.update_interval = 0.06 - self.update_interval def update(self): import ui x = self.moving.x - 5 if x < -self.moving.width: x = ui.get_screen_size()[0] self.moving.index = self.moving.index+1 if self.moving.index == len(self.moving.icons): self.moving.index = 0 emoji = self.moving.icons[self.moving.index] self.moving.image = ui.Image.named(emoji).with_rendering_mode(ui.RENDERING_MODE_ORIGINAL) self.line.hidden = not (emoji == 'emj:Railway_Car') self.road.border_color = 'blue' if emoji == 'emj:Speedboat' else 'green' self.moving.x = x #================================================ for the fun end def layout(self): import ui #print('layout') # supports changing orientation #print(ui.get_screen_size()) dx = 8 dy = 2 x0 = 15 y0 = 10 dx_middle = 25 y = y0 x = x0 w_button = (ui.get_screen_size()[0] - 2*x0 - 17*dx - dx_middle)/18 for pad_elem in self.pad: nw = pad_elem.get('width', 1) wb = w_button*nw + dx*(nw-1) if (x + wb + dx) > self.width: y = y + self.h_button + dy x = x0 if pad_elem['key'] == 'nul': # let free space x = x + wb + dx continue elif pad_elem['key'] == 'new row': # new row y = y + self.h_button + dy x = x0 continue button = self[pad_elem['key']] xb = x + dx_middle if (x+wb) > self.width/2 else x button.frame = (xb,y,wb,self.h_button) if button.title != '': font_size = self.h_button - 8 while True: d = ui.measure_string(button.title,font=(button.font[0],font_size))[0]+4 if d <= wb: break font_size = font_size - 1 button.font = (button.font[0],font_size) x = x + wb + dx self.height = y + self.h_button + dy @on_main_thread def AddButtonsToPythonistaKeyboard(pad=None): if not pad: pad = [ {'key':'undo','title':'βͺοΈ'}, {'key':'redo','title':'β©οΈ'}, {'key':'next word','width':2}, {'key':'find','icon':'iob:ios7_search_32'}, {'key':'nul'}, {'key':'>'}, # {'key':'new row'}, # {'key':'nul'}, # {'key':'nul'}, {'key':'#'}, {'key':'nul'}, {'key':'"'}, {'key':'nul'}, {'key':'left','icon':'iob:arrow_left_a_32'}, {'key':'right','icon':'iob:arrow_right_a_32'}, {'key':'down','icon':'iob:arrow_down_a_32'}, {'key':'\\'}, {'key':'nul'}, {'key':'delete','title':'Del'}] ev = editor._get_editor_tab().editorView() tv = ev.textView() #print(tv._get_objc_classname()) #print(dir(tv)) # create ui.View for InputAccessoryView above keyboard v = MyView(pad) # view above keyboard vo = ObjCInstance(v) # get ObjectiveC object of v retain_global(v) # see https://forum.omz-software.com/topic/4653/button-action-not-called-when-view-is-added-to-native-view tv.setInputAccessoryView_(vo) # attach accessory to textview tv.strfind = '' if __name__ == '__main__': AddButtonsToPythonistaKeyboard()
-
@cvp Thankβs a lot Magic cvpπππ π
-
Here is my scrolling special key row to replace the standard one.
This script is a mix between @cvpe's script AddButtonsToPythonistaKeyboard https://github.com/cvpe/Pythonista-scripts/blob/master/AddButtonsToPythonistaKeyboard.py
and my own work.
It uses module ui3.sfsymbol of @mikaelho https://github.com/mikaelho/ui3
Thanks for all their work for Pythonista's community.
It certainly contains errors as I am not an expert coder like the majority of you but it does work for me π
Feel free to improve and adapt it to your needs!# ================================================================= # This script is a mix between @cvpe's script AddButtonsToPythonistaKeyboard # https://github.com/cvpe/Pythonista-scripts/blob/master/AddButtonsToPythonistaKeyboard.py # and my own work. # It uses module ui3.sfsymbol of @mikaelho # https://github.com/mikaelho/ui3 # # enez.houad@free.fr # ================================================================= import ui, editor, clipboard from objc_util import * from ui3.sfsymbol import * @on_main_thread def tv_find(tv): import console, re tv.find = console.input_alert('Expression Γ rechercher :', '', tv.find, hide_cancel_button=True) txt = tv.find for sv in tv.subviews(): if 'SUIButton_PY3' in str(sv._get_objc_classname()): sv.removeFromSuperview() if txt == '': return t = str(tv.text()) for m in re.finditer(txt, t): st,en = m.span() p1 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), st) p2 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), en) rge = tv.textRangeFromPosition_toPosition_(p1,p2) rect = tv.firstRectForRange_(rge) x,y = rect.origin.x,rect.origin.y w,h = rect.size.width,rect.size.height l = ui.Button() l.frame = (x,y,w,h) if '|' not in txt: l.background_color = (1,0,0,0.2) else: # search multiple strings wrds = txt.split('|') idx = wrds.index(t[st:en]) cols = [(1,0,0,0.2), (0,1,0,0.2), (0,0,1,0.2), (1,1,0,0.2), (1,0,1,0.2), (0,1,1,0.2)] col = cols[idx % len(cols)] l.background_color = col l.corner_radius = 4 l.border_width = 1 tv.addSubview_(l) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def key_pressed(sender): tv = sender.objc_instance.firstResponder() # associated TextView cursor = tv.offsetFromPosition_toPosition_(tv.beginningOfDocument(), tv.selectedTextRange().start()) # get actual cursor position if sender.name == 'tab': tv.insertText_('\t') elif sender.name == 'paste': tv.insertText_(clipboard.get()) elif sender.name == 'undo': tv.undoManager().undo() elif sender.name == 'redo': tv.undoManager().redo() elif sender.name == 'del_right': # delete at right = delete at left of next if cursor == (len(str(tv.text()))-1): # already after last character return cursor_position = tv.positionFromPosition_offset_(tv.beginningOfDocument(), cursor+1) tv.selectedTextRange = tv.textRangeFromPosition_toPosition_(cursor_position, cursor_position) tv.deleteBackward() elif sender.name == 'find': tv_find(tv) elif sender.name == 'line-': tv.insertText_('# ' + 65 * 'β') elif sender.name == 'line=': tv.insertText_('# ' + 65 * '=') elif sender.name == 'line#': tv.insertText_(67 * '#') else: # all other keys > insert button title tv.insertText_(sender.title) # =================================================================== class SpecialKeyRow(ui.View): def __init__(self, pad, *args, **kwargs): self.pad = pad sw, sh = ui.get_screen_size() self.uiStyle = ui.get_ui_style() self.buttonsList = [] self.buttonWidth = (sw - (2*8) - (24*4)) / 25 self.buttonHeight = 40 # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # MAIN VIEW self.width, self.height = sw, 50 self.alpha = 0.98 # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # SCROLL VIEW sv = ui.ScrollView(name='scrollview') sv.width, sv.height = (sw, 50) sv.content_size = (2*sw, 50) sv.bounces = False sv.shows_horizontal_scroll_indicator = False sv.paging_enabled = True sv.x, sv.y = (0, 0) colorDict = {'light':'#D6D8DD', 'dark':'#343639'} sv.background_color = colorDict[self.uiStyle] self.add_subview(sv) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # BUTTONS IN SCROLL VIEW # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ for pad_elem in self.pad: if not 'style' in pad_elem: bStyle = 'light' else: bStyle = pad_elem['style'] if 'title' in pad_elem: b = self.add_text_button(name=pad_elem['key'], title=pad_elem['title'], style=bStyle) elif 'symbol' in pad_elem: b = self.add_symbol_button(name=pad_elem['key'], symbol_name=pad_elem['symbol'], style=bStyle) self.add_scrollview_button(b) # =================================================================== def add_scrollview_button(self, b): b.y = 10 if self.buttonsList == []: b.x = 8 # 1er bouton else: lastButton = self.buttonsList[-1] b.x = lastButton.x + lastButton.width + 4 # intervalle 4 px if len(self.buttonsList) == 25: b.x += 12 # 2e page b.action = key_pressed retain_global(b) self['scrollview'].add_subview(b) self.buttonsList.append(b) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def add_text_button(self, name='', title='', width=40, style='light'): b = self.add_button(name, style) b.title = title b.font = ('<system>', 18) if width == None: b.width = ui.measure_string(b.title,font=b.font)[0] + 28 else: b.width = self.buttonWidth return b # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def add_symbol_button(self, name='', symbol_name='', style='light'): b = self.add_button(name, style) symbol_image = SymbolImage(symbol_name, point_size=11, weight=LIGHT, scale=SMALL) b.image = symbol_image b.width = self.buttonWidth return b # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def add_button(self, name='', backgroundStyle='light'): b = ui.Button(name=name) b.corner_radius = 8 colorsDict = { 'light' :({'light':'#FFFFFF', 'dark':'#B4B9C1'}, 'black'), 'dark' :({'light':'#717274', 'dark':'#4D4F50'}, 'white') } b.background_color = colorsDict[self.uiStyle][0][backgroundStyle] b.tint_color = colorsDict[self.uiStyle][1] b.alpha = self.alpha b.font = ('<system>', 18) b.height = self.buttonHeight return b # =================================================================== @on_main_thread def AddButtonsToPythonistaKeyboard(pad=None): def numeric_keys(): list = [] for i in range(1, 10): list.append({'key':str(i), 'title':str(i)}) list.append({'key':'0', 'title':'0'}) return list if not pad: pad = [ {'key':'tab', 'symbol':'arrow.right.to.line.alt'}, {'key':'undo', 'symbol':'arrow.uturn.left', 'style':'dark'}, {'key':'redo','symbol':'arrow.uturn.right', 'style':'dark'}, {'key':'paste', 'symbol':'doc.on.clipboard'}, {'key':'#', 'title':'#'}, {'key':'_', 'title':'_'}, {'key':"'", 'title':"'"}, {'key':'"', 'title':'"'}, {'key':"'''", 'title':"'''"}, {'key':'(', 'title':'('}, {'key':')', 'title':')'}, {'key':'[', 'title':'['}, {'key':']', 'title':']'}, {'key':'{', 'title':'{'}, {'key':'}', 'title':'}'}, {'key':'+', 'title':'+'}, {'key':'-', 'title':'-'}, {'key':'*', 'title':'*'}, {'key':'/', 'title':'/'}, {'key':"\\", 'title':"\\"}, {'key':'<', 'title':'<'}, {'key':'>', 'title':'>'}, {'key':'=', 'title':'='}, {'key':':', 'title':':'}, {'key':'del_right', 'symbol':'delete.right', 'style':'dark'}, {'key':'find', 'symbol':'magnifyingglass', 'style':'dark'} ] pad += numeric_keys() pad += [ {'key':'+', 'title':'+'}, {'key':'-', 'title':'-'}, {'key':'*', 'title':'*'}, {'key':'/', 'title':'/'}, {'key':'<', 'title':'<'}, {'key':'>', 'title':'>'}, {'key':'=', 'title':'='}, {'key':'line-', 'title':'---'}, {'key':'line=', 'title':'==='}, {'key':'line#', 'title':'###'} ] ev = editor._get_editor_tab().editorView() tv = ev.textView() v = SpecialKeyRow(pad) vo = ObjCInstance(v) retain_global(v) tv.setInputAccessoryView_(vo) # attach accessory to textview tv.find = '' # =================================================================== if __name__ == '__main__': AddButtonsToPythonistaKeyboard() ```
-
@Enez-Houad Good job. I let real Python specialists like @ccc, @mikael and other ones advice you about Python it-self, they are far ahead of me
For those who want to see the keyboard
And even horizontally scroll this first row, whaaaa
-
@cvp I learn a lot adapting your scripts to my needs !
You will find after this message my/your script to add a keyboard icon in the titlebar to call my script to add my special key row to the keyboard.
The system is rather practical, but to make the handling as transparent as possible : when I add my key row, I hide the keyboard withtv.endEditing(True)
, and I would like to make it reappear with my special key row : but Iβve not found a command as simple as tv.startEditing π ; itβs perharps possible to simulate a touch in the TextView, but I neither canβt find a solution to do it π₯Ά
After several hours of searching, I throw in the towel and ask for helpβ¦π₯΅from objc_util import * import ui from ui3.sfsymbol import * w = ObjCClass('UIApplication').sharedApplication().keyWindow() main_view = w.rootViewController().view() def get_toolbar(view): # get main editor toolbar, by recursively walking the view sv = view.subviews() for v in sv: if v._get_objc_classname().startswith(b'OMTabViewToolbar'): return v tb = get_toolbar(v) if tb: return tb # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def keyboard_btn_action(sender): def run_script(scriptPath): import os from objc_util import ObjCInstance,ObjCClass dir = os.path.expanduser(scriptPath) I3=ObjCClass('PYK3Interpreter').sharedInterpreter() I3.runScriptAtPath_argv_resetEnvironment_(dir, [''], True) iCloudPath = "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/" iPadPath = '~/Documents/' scriptPath = 'PROJETS/KEYBOARD/my_special_key_row.py' run_script(iCloudPath + scriptPath) # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def create_keyboard_button(action,index=0): global __persistent_views assert(callable(action)) tb = get_toolbar(main_view) try: __persistent_views except NameError: __persistent_views={} #check for existing button in this index and delete if needed remove_toolbar_button(index) btn = ui.Button() btn.frame = (110, 24, 40, 40) if ui.get_ui_style() == 'dark': btn.tint_color = 'white' else: btn.tint_color = '#0D89B5' btn.image = SymbolImage('keyboard', point_size=14, weight=THIN, scale=SMALL) btn.image = btn.image.with_rendering_mode(ui.RENDERING_MODE_AUTOMATIC) btn.action=action btn_obj=ObjCInstance(btn) __persistent_views[index]=(btn,action) tb.superview().superview().addSubview_(btn_obj) # in front of all buttons return btn # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ def remove_toolbar_button(index): global __persistent_views try: btn,action = __persistent_views.pop(index) btn.action= None ObjCInstance(btn).removeFromSuperview() except KeyError: pass # =================================================================== #if __name__=='__main__': # if imported by pythonista startup create_keyboard_button(keyboard_btn_action) create_keyboard_button(keyboard_btn_action)
-
@Enez-Houad try
def keyboard_btn_action(sender): def run_script(scriptPath): import os from objc_util import ObjCInstance,ObjCClass dir = os.path.expanduser(scriptPath) I3=ObjCClass('PYK3Interpreter').sharedInterpreter() I3.runScriptAtPath_argv_resetEnvironment_(dir, [''], True) iCloudPath = "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/" iPadPath = '~/Documents/' scriptPath = 'PROJETS/KEYBOARD/my_special_key_row.py' run_script(iCloudPath + scriptPath) @on_main_thread def disp_kbd(): from editor import _get_editor_tab tab = _get_editor_tab() if tab: tab.editorView().textView().becomeFirstResponder() disp_kbd()
-
@Enez-Houad said:
my/your script to add a keyboard icon in the titlebar
I must admit with humulity that the most complex part of this code comes from @JonB π€«
-
@cvp Thanks a lot ! Iβm happy to see that I progress slowlyβ¦
I had foundtextView().becomeFirstResponder()
but couldnβt find to witch element it had to be applied. The_get_editor_tab
is not documented ! To find it, do you openeditor.py
in thesite-packages
ofStandard Library
?
It's very instructive π€, I'm going to end up managing on my own. π -
-
the problem gets worse, and don't ask me how it happens...
-
I begin to panic
-
With me on iOS 14.3 / iPad it seems to have something to do with the βdarkmodeβ setting. When running in βlightβ mode the problem did not occur yet. When I switch back to dark mode, the extra keyboard row splits again after switching between file tabs in the editor.
so, maybe use light mode for Pythonista? -
@janplxt said:
iPad it seems to have something to do with the βdarkmodeβ setting.
I don't think so, see my first examples in the topic.