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.
Text display help
-
@stephen
I don’t know exactly why, but the longer I type for, the slower the program gets. It’s not a big issue, as I can just close the text editor and reopen it. -
@Bumbo-Cactoni, I am sure this was just a quick prototype, as there is a lot happening every time you type a single letter. @stephen, maybe as a quick fix, remove all the history stuff as it is not necessary here? Also, consider saving to file in a background thread, and only whenever there is e.g. a 500 ms break in the typing action.
... Sorry, disregard the second point, as I see the saving only happens when closing.
-
@stephen
I agree with mikael. His point is valid: I don’t really need the history stuff, and just in case I do, I can put all the history stuff in a separate text file, so I can put it in if needed. -
Alright. I have figured out how to make it work faster, without deleting the code. Just make all the update_history, undo, and redo into comments:
import ui def new_button(frame, title, action): btn=ui.Button() btn.frame=frame btn.background_color='#686868' btn.tint_color='#000000' btn.title=title btn.action=action return btn class Text_Editor: def __init__(self, main, tv, text_file, *args, **kwargs): # undo/redo View self.main=main self.text_file=text_file self.active_file=None self.history_view = ui.TextView( text='UNDO HISTORY', frame=(250, 275, 300, 400), editable=False, border_width=5, background_color='#dedede') # Undo Button # undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo) # main.add_subview(undo_btn) # Redo Button # redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo) # main.add_subview(redo_btn) # Close Button close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close) main.add_subview(close_btn) self.history=[] self.current_index=len(self.history) self.textview=tv self.textview_should_begin_editing(self.textview) def append(self, text): self.main.remove_subview(self.history_view) if self.current_index < len(self.history): self.history=self.history[:self.current_index] self.current_index=len(self.history) self.history.append(text) self.current_index+=1 # self.update_history(self.current_index) # def update_history(self, index): # self.history_view.text='' # for i, x in enumerate(self.history): # if i == index: # self.history_view.text+=str(i)+' '+x+' <--'+'\n' # elif x == self.history[-1] and index == len(self.history): # self.history_view.text+=str(i)+' '+x+' <--'+'\n' # else: # self.history_view.text+=str(i)+' '+x+'\n' def close(self, sender): with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) sender.superview.close() # def redo(self, sender): # if self.current_index == len(self.history): # return # if self.current_index < len(self.history): # self.current_index+=1 # self.textview.text = self.history[self.current_index-1] # self.update_history(self.current_index-1) # def undo(self, sender): # if self.current_index == 0: # return # self.main.add_subview(self.history_view) # self.current_index-=1 # self.textview.text = self.history[self.current_index-1] # self.update_history(self.current_index-1) def textview_should_begin_editing(self, textview): if textview.text=='' and self.active_file ==None: if self.text_file: with open(self.text_file, 'r') as f: self.active_file=f for line in f: textview.text+=line self.textview=textview return True def textview_did_change(self, textview): self.append(textview.text) # self.update_history(self.current_index) pass class Main(ui.View): def __init__(self, *args, **kwargs): """THIS IS WHERE YOU EDIT THE FILE!""" text_file='./file.txt' # Main Window self.width = 650 self.height = 1000 self.background_color="#8b8b8b" self.update_interval=1 # Text Input Window self.tv = ui.TextView( text='', frame=(25, 50, 600, 875), background_color='#dedede') self.add_subview(self.tv) self.tv.delegate=Text_Editor(self, self.tv, text_file) if __name__ == '__main__': Main().present('sheet', hide_title_bar=True) ```
-
@Bumbo-Cactoni im not 100% on understanding exactly what you are going for so ive just beenthrowin stuff together to help ya out. ive been running under the assumption of a text editor lol thats why i included the undo funtionality, and currently playing around with a custom file picker.
as for your workaroud, @mikael was correct about history update. but you only needed to remove the history_view stuff you can keep the undo-redo stuff if you like. and i would suggest not storing history in a file unless yourvwanting to keep a log of changes. i say this because history is just the ontainer holding the diferent states of your text for undo-redo. and changes often and you wouldnt want to open and close a file lot to keep it updated.
-
@stephen
Ok, thanks! Also, yes, I was aiming for a text editor! If you figure out the custom file picker, let me know! I think that would be awesome to have in a text editor! -
@Bumbo-Cactoni, we still do not know why you need this type of a text editor, and do not just use Pythonista’s editor.
Use google or other search engine to search for ”omz forum file picker dialog”, and you will find something, I am sure.
-
@Bumbo-Cactoni said:
@stephen
Ok, thanks! Also, yes, I was aiming for a text editor! If you figure out the custom file picker, let me know! I think that would be awesome to have in a text editor!here is the text editor with the file picker added at the start. this was a fun one so i may contenu to add onto this myself. Though i do agree with @mikael.. Im not picturing why you want this unless just for learning? or is this meant for some type of note taking mecanic to a game or app?
import ui import os from objc_util import ObjCInstance, ObjCClass from operator import attrgetter import time import threading import functools import ftplib import re # http://stackoverflow.com/a/6547474 def human_size(size_bytes): '''Helper function for formatting human-readable file sizes''' if size_bytes == 1: return "1 byte" suffixes_table = [('bytes',0),('KB',0),('MB',1),('GB',2),('TB',2), ('PB',2)] num = float(size_bytes) for suffix, precision in suffixes_table: if num < 1024.0: break num /= 1024.0 if precision == 0: formatted_size = "%d" % num else: formatted_size = str(round(num, ndigits=precision)) return "%s %s" % (formatted_size, suffix) class TreeNode (object): def __init__(self): self.expanded = False self.children = None self.leaf = True self.title = '' self.subtitle = '' self.icon_name = None self.level = 0 self.enabled = True def expand_children(self): self.expanded = True self.children = [] def collapse_children(self): self.expanded = False def __repr__(self): return '<TreeNode: "%s"%s>' % (self.title, ' (expanded)' if self.expanded else '') class FileTreeNode (TreeNode): def __init__(self, path, show_size=True, select_dirs=True, file_pattern=None): TreeNode.__init__(self) self.path = path self.title = os.path.split(path)[1] self.select_dirs = select_dirs self.file_pattern = file_pattern is_dir = os.path.isdir(path) self.leaf = not is_dir ext = os.path.splitext(path)[1].lower() if is_dir: self.icon_name = 'iob:filing_24' elif ext == '.py': self.icon_name = 'iob:code_24' elif ext == '.pyui': self.icon_name = 'UI File' elif ext in ('.png', '.jpg', '.jpeg', '.gif'): self.icon_name = 'iob:image_24' elif ext == '.txt': self.icon_name = 'iob:compose_24' elif ext == '.md': self.icon_name = 'iob:ios7_bookmarks_outline_24' elif ext == '.html': self.icon_name = 'iob:leaf_24' elif ext == '.css': self.icon_name = 'CSS File' else: self.icon_name = 'FileOther' self.show_size = show_size if not is_dir and show_size: self.subtitle = human_size((os.stat(self.path).st_size)) if is_dir and not select_dirs: self.enabled = False elif not is_dir: filename = os.path.split(path)[1] for x in file_pattern: if x in filename: self.enabled = True break @property def cmp_title(self): return self.title def expand_children(self): if self.children is not None: self.expanded = True return files = os.listdir(self.path) children = [] for filename in files: if filename.startswith('.'): continue full_path = os.path.join(self.path, filename) node = FileTreeNode(full_path, self.show_size, self.select_dirs, self.file_pattern) node.level = self.level + 1 children.append(node) self.expanded = True self.children = sorted(children, key=attrgetter('leaf', 'cmp_title')) class TreeDialogController (object): def __init__(self, main, root_node, x, y, allow_multi=False, async_mode=False): self.main=main self.async_mode = async_mode self.allow_multi = allow_multi self.selected_entries = None self.table_view = ui.TableView() self.table_view.frame = (0, 0, 500, 500) self.table_view.data_source = self self.table_view.delegate = self self.table_view.flex = 'WH' self.table_view.border_width=5 self.table_view.corner_radius=8.0 self.table_view.allows_multiple_selection = False self.table_view.background_color='#daba8b' self.table_view.tint_color = '#ff00d1' self.view = ui.View(frame=(x, y, 500, 500)) self.view.add_subview(self.table_view) self.view.name = root_node.title self.busy_view = ui.View(frame=self.view.bounds, flex='WH', background_color=(0, 0, 0, 0.35)) hud = ui.View(frame=(self.view.center.x - 50, self.view.center.y - 50, 100, 100)) hud.background_color = (0, 0, 0, 0.7) hud.corner_radius = 8.0 hud.flex = 'TLRB' spinner = ui.ActivityIndicator() spinner.style = ui.ACTIVITY_INDICATOR_STYLE_WHITE_LARGE spinner.center = (50, 50) spinner.start_animating() hud.add_subview(spinner) self.busy_view.add_subview(hud) self.busy_view.alpha = 0.0 self.view.add_subview(self.busy_view) self.done_btn = ui.ButtonItem(title='Done', action=self.done_action) if self.allow_multi: self.view.right_button_items = [self.done_btn] self.done_btn.enabled = False self.root_node = root_node self.entries = [] self.flat_entries = [] if self.async_mode: self.set_busy(True) t = threading.Thread(target=self.expand_root) t.start() else: self.expand_root() def expand_root(self): self.root_node.expand_children() self.set_busy(False) self.entries = self.root_node.children self.flat_entries = self.entries self.table_view.reload() def flatten_entries(self, entries, dest=None): if dest is None: dest = [] for entry in entries: dest.append(entry) if not entry.leaf and entry.expanded: self.flatten_entries(entry.children, dest) return dest def rebuild_flat_entries(self): self.flat_entries = self.flatten_entries(self.entries) def tableview_number_of_rows(self, tv, section): return len(self.flat_entries) def tableview_cell_for_row(self, tv, section, row): cell = ui.TableViewCell() cell.background_color='#daba8b' entry = self.flat_entries[row] level = entry.level - 1 image_view = ui.ImageView(frame=(44 + 20*level, 5, 34, 34)) label_x = 44+34+8+20*level label_w = cell.content_view.bounds.w - label_x - 8 if entry.subtitle: label_frame = (label_x, 0, label_w, 26) sub_label = ui.Label(frame=(label_x, 26, label_w, 14)) sub_label.font = ('<System>', 12) sub_label.text = entry.subtitle sub_label.text_color = '#2a8a99' cell.content_view.add_subview(sub_label) else: label_frame = (label_x, 0, label_w, 44) label = ui.Label(frame=label_frame) if entry.subtitle: label.font = ('<System>', 15) else: label.font = ('<System>', 18) label.text = entry.title label.flex = 'W' cell.content_view.add_subview(label) if entry.leaf and not entry.enabled: label.text_color = '#66006b' cell.content_view.add_subview(image_view) if not entry.leaf: has_children = entry.expanded btn = ui.Button(image=ui.Image.named('iob:minus_round_24' if has_children else 'iob:plus_round_24')) btn.frame = (20*level, 0, 44, 44) btn.action = self.expand_dir_action cell.content_view.add_subview(btn) if entry.icon_name: image_view.image = ui.Image.named(entry.icon_name) else: image_view.image = None cell.selectable = entry.enabled return cell def row_for_view(self, sender): '''Helper to find the row index for an 'expand' button''' cell = ObjCInstance(sender) while not cell.isKindOfClass_(ObjCClass('UITableViewCell')): cell = cell.superview() return ObjCInstance(self.table_view).indexPathForCell_(cell).row() def expand_dir_action(self, sender): '''Invoked by 'expand' button''' row = self.row_for_view(sender) entry = self.flat_entries[row] if entry.expanded: sender.image = ui.Image.named('iob:plus_round_24') else: sender.image = ui.Image.named('iob:minus_round_24') self.toggle_dir(row) self.update_done_btn() def toggle_dir(self, row): '''Expand or collapse a folder node''' entry = self.flat_entries[row] if entry.expanded: entry.collapse_children() old_len = len(self.flat_entries) self.rebuild_flat_entries() num_deleted = old_len - len(self.flat_entries) deleted_rows = range(row + 1, row + num_deleted + 1) self.table_view.delete_rows(deleted_rows) else: if self.async_mode: self.set_busy(True) expand = functools.partial(self.do_expand, entry, row) t = threading.Thread(target=expand) t.start() else: self.do_expand(entry, row) def do_expand(self, entry, row): '''Actual folder expansion (called on background thread if async_mode is enabled)''' entry.expand_children() self.set_busy(False) old_len = len(self.flat_entries) self.rebuild_flat_entries() num_inserted = len(self.flat_entries) - old_len inserted_rows = range(row + 1, row + num_inserted + 1) self.table_view.insert_rows(inserted_rows) def tableview_did_select(self, tv, section, row): self.update_done_btn() def tableview_did_deselect(self, tv, section, row): self.update_done_btn() def update_done_btn(self): '''Deactivate the done button when nothing is selected''' selected = [self.flat_entries[i[1]] for i in self.table_view.selected_rows if self.flat_entries[i[1]].enabled] # print(selected) if selected and not self.allow_multi: self.done_action(None) else: self.done_btn.enabled = len(selected) > 0 def set_busy(self, flag): '''Show/hide spinner overlay''' def anim(): self.busy_view.alpha = 1.0 if flag else 0.0 ui.animate(anim) def done_action(self, sender): self.selected_entries = [self.flat_entries[i[1]] for i in self.table_view.selected_rows if self.flat_entries[i[1]].enabled] self.main.open_file(self, self.selected_entries[0].path) def file_picker_dialog(parent, x, y, title=None, root_dir=None, multiple=False, select_dirs=False, file_pattern=None, show_size=True): if root_dir is None: root_dir = os.path.expanduser('..') if title is None: title = os.path.split(root_dir)[1] root_node = FileTreeNode(root_dir, show_size, select_dirs, file_pattern) root_node.title = title or '' picker = TreeDialogController(parent, root_node, x, y, allow_multi=multiple) parent.add_subview(picker.view) picker.view.wait_modal() if picker.selected_entries is None: return None paths = [e.path for e in picker.selected_entries] if multiple: return paths else: return paths[0] def new_button(frame, title, action): btn=ui.Button() btn.frame=frame btn.background_color='#686868' btn.tint_color='#000000' btn.title=title btn.action=action return btn class Text_Editor: def __init__(self, main, tv, text_file, *args, **kwargs): # undo/redo View self.main=main self.text_file=text_file self.active_file=None # Undo Button undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo) main.add_subview(undo_btn) # Redo Button redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo) main.add_subview(redo_btn) # Close Button close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close) main.add_subview(close_btn) self.history=[] self.current_index=len(self.history) self.textview=tv self.textview_should_begin_editing(self.textview) def append(self, text): if self.current_index < len(self.history): self.history=self.history[:self.current_index] self.current_index=len(self.history) self.history.append(text) self.current_index+=1 def close(self, sender): with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) sender.superview.close() def redo(self, sender): if self.current_index == len(self.history): return if self.current_index < len(self.history): self.current_index+=1 self.textview.text = self.history[self.current_index-1] def undo(self, sender): if self.current_index == 0: return self.current_index-=1 self.textview.text = self.history[self.current_index-1] def textview_should_begin_editing(self, textview): return True def textview_did_change(self, textview): self.append(textview.text) class Main(ui.View): def __init__(self, *args, **kwargs): text_file=None # Main Window self.width = 650 self.height = 1000 self.background_color="#a18967" self.update_interval=1 self.tv = ui.TextView( frame=(25, 50, 600, 875), background_color='#daba8b') self.file_picker=file_picker_dialog( self, x=self.width/2-250, y=self.height/2-250, file_pattern=['.txt', '.md', '.py']) def file_view(self): self.add_subview( FilePicker(frame=(50,100, self.width-100, self.height-200))) def open_file(self, child_view, file): self.add_subview(self.tv) self.tv.delegate=Text_Editor(self, self.tv, file) with open(file, 'r') as f: for line in f: self.tv.text+=line if __name__ == '__main__': Main().present('sheet', hide_title_bar=True)
-
@stephen
Yes, I am going to attempt to implement it in an app I am going to try to make. -
@Bumbo-Cactoni said:
@stephen
Yes, I am going to attempt to implement it in an app I am going to try to make.since ill probably play with it a little bit we can co-op a bit on it if you like
-
@stephen
Yes, I would like that. Right now is not a super great time, though, as I still have to finish my school year. When I am done with that (in about 10 days), I would love to co-op on it.However, I do have a question. How would I create a pushable button in Pythonista? I know tkinter doesn’t work, since it is designed for computer displays. Tkinter is really the only way I know how to create a button, so I may require some assistance.
-
@Bumbo-Cactoni said:
@stephen
Yes, I would like that. Right now is not a super great time, though, as I still have to finish my school year. When I am done with that (in about 10 days), I would love to co-op on it.However, I do have a question. How would I create a pushable button in Pythonista? I know tkinter doesn’t work, since it is designed for computer displays. Tkinter is really the only way I know how to create a button, so I may require some assistance.
do you mean creating a button within Pythonista's Editor View itself or one in a CustomView fom
ui
or in ascene.Node
? iether way i can help you out im sure.ui
has a built-inui.Button
, For Pythonista's EditorUIView
we would useobjc_util
im sure and for scene ive alredy created ownButtonNode
class that i have in my "cook book" -
import ui v = ui.View() btn = ui.Button( title="Push", flex='RTLB', background_color='grey', tint_color='black' ) def action(sender): sender.title = 'Pushed' btn.action = action btn.width = 200 btn.height = 40 btn.center = (50, 50) v.add_subview(btn) v.present('fullscreen')
-
@Bumbo-Cactoni, to explain the obscure part:
- View v is by default initially sized at 100 x 100 pixels
- So placing the button center at (50, 50) means it is in the center of the view at this point
- Then, because the flex is set to ”Right Top Left Bottom”, i.e. all sides, they will scale with the view, effectively keeping the button in center when the view is presented and scaled to fill the screen
- And it will continue to stay in the center if you e.g. rotate the device
I think the initial size 100 x 100 was chosen at least partly so that if you want, you can think the locations of subviews as percentages.