from kivy.uix.label import Label from kivy.core.window import Window from kivy.graphics import Color,Rectangle import glob import os.path import filecheck class Command_prompt(Label): def __init__(self,app,**kwargs): # app is used to share information between widgets self.app=app # insert mode self.insert=False # width of a letter self.char_width=9 # cursor position self.cursor=0 # the text, with no color information self.raw_text="" # a one-time message that shows up over the bar self.message="" # array of command with arguments self.argv=[] # display characters from window_offset onwards (for text wrapping) self.window_offset=0 # init Label super(Command_prompt,self).__init__(**kwargs) self.keyboard = Window.request_keyboard(None,self,"text") self.keyboard.bind(on_textinput=self.on_textinput,on_key_down=self.on_key_down) self.draw() def draw(self): self.canvas.before.clear() # background with self.canvas.before: Color(0,0,0) Rectangle(pos=self.pos,size=self.size) # if message is not empty, draw message instead if self.message!="": # do not wrap self.text=self.message[:min(len(self.message),int(self.width/self.char_width))] self.message="" return # wrap text window_size=int(self.width/self.char_width) if self.cursor>=self.window_offset+window_size: self.window_offset=self.cursor-window_size+1 elif self.cursor0 and self.cursor==len(self.raw_text)) or (n<0 and self.cursor==0): return self.cursor=max(1,min(len(self.raw_text),self.cursor+n)) self.draw() # move cursor to absolute position def move_cursor(self,n): self.cursor=max(1,min(len(self.raw_text),n)) self.draw() # parse text as argv def parse_argv(self): # init self.argv=[""] # return position of cursor cursor=(0,0) # whether inside quotes single_quoted=False double_quoted=False # whether after '\' backslashed=False # start after ':' # proceed one character at a time for i in range(1,len(self.raw_text)): char=self.raw_text[i] # split argument if single_quoted==False and double_quoted==False and backslashed==False and char==' ': # new argv self.argv.append("") # quotes or '\' elif double_quoted==False and backslashed==False and char=='\'': single_quoted=not single_quoted elif single_quoted==False and backslashed==False and char=='"': double_quoted=not double_quoted elif single_quoted==False and backslashed==False and char=='\\': backslashed=True # write character else: self.argv[len(self.argv)-1]+=char # reset backslash backslashed=False # record position of cursor if self.cursor==i+1: cursor=(len(self.argv)-1,len(self.argv[len(self.argv)-1])) return cursor # tab completion def tab_complete(self): # parse argv cursor=self.parse_argv() # write and edit commands if self.argv[0]=="w" or self.argv[0]=="e" or self.argv[0]=="export": # check that cursor is in first argument if cursor[0]==1: # complete filesystem path self.append_text_at_cursor(self.complete_path(self.argv[cursor[0]],cursor[1])) # complete filesystem path def complete_path(self,base,end): paths=glob.glob(glob.escape(base[:end])+"*") print(glob.escape(base[:end])+"*", paths) if len(paths)==0: return "" elif len(paths)==1: # append '/' to directories if os.path.isdir(paths[0]): return paths[0][end:]+"/" else: return paths[0][end:] else: # display in status bar self.app.status_bar.message="" for path in paths: name=os.path.basename(path) # add quotes if needed if " " in name: name="\""+name+"\"" self.app.status_bar.message+=name+" " self.app.status_bar.draw() return os.path.commonprefix(paths)[end:] # run command def run_command(self): # parse command line self.parse_argv() # commands that cannot be compounded if self.argv[0]=="set": self.run_set(self.argv) return # single letter commands (which can be combined) # write if self.argv[0]=="w" or self.argv[0]=="w!" or self.argv[0]=="wq": self.run_write(self.argv) # edit file if self.argv[0]=="e": self.run_edit(self.argv) # export to file if self.argv[0]=="export": self.run_export(self.argv) # quit if self.argv[0]=="q" or self.argv=="wq": self.app.stop() # set properties of particles def run_set(self,argv): if len(argv)<2: self.message="error: 'set' command was run with too few arguments -- usage: 'set '" return if argv[1]=="color": self.run_set_color(argv) return elif argv[1]=="grid": self.run_set_grid(argv) return elif argv[1]=="zoom": self.run_set_zoom(argv) else: self.message="error: unrecognized command '"+argv[1]+"'" return # set color def run_set_color(self,argv): if len(argv)<3: self.message="error: 'set color' command was run with without anargument -- usage: 'set color '" return if argv[2]=="blue": self.app.painter.set_color((0,0,1)) elif argv[2]=="green": self.app.painter.set_color((0,1,0)) elif argv[2]=="red": self.app.painter.set_color((1,0,0)) elif argv[2]=="cyan": self.app.painter.set_color((0,1,1)) elif argv[2]=="magenta": self.app.painter.set_color((1,0,1)) elif argv[2]=="yellow": self.app.painter.set_color((1,1,0)) elif argv[2]=="white": self.app.painter.set_color((1,1,1)) elif argv[2]=="black": self.app.painter.set_color((0,0,0)) elif argv[2]=="gray": self.app.painter.set_color((0.5,0.5,0.5)) else: color_str=argv[2].split(",") # error if improperly formatted if len(color_str)!=3: self.message="error: unrecognized color specification '"+argv[2]+"'; supported format is 'r,g,b' or one of red|green|blue|cyan|magenta|yellow|white|black|gray" return try: color=(float(color_str[0]),float(color_str[1]),float(color_str[2])) except: self.message="error: unrecognized color specification '"+argv[2]+"'; supported format is 'r,g,b' or one of red|green|blue|cyan|magenta|yellow|white|black|gray" return self.app.painter.set_color(color) # toggle grid def run_set_grid(self,argv): if len(argv)==2: # no argument: set to toggle self.app.painter.set_grid(-1) elif argv[2]=="on": self.app.painter.set_grid(1) elif argv[2]=="off": self.app.painter.set_grid(0) else: try: mesh=float(argv[2]) except: self.message="error: unrecognized argument '"+argv[2]+"' -- usage 'set grid [on|off|]'" return if mesh<0: self.message="error: grid size cannot be negative: '"+argv[2]+"'" return self.app.painter.set_grid(mesh) # set zoom level (changes size of elements) def run_set_zoom(self,argv): if len(argv)==2: self.message="error: missing argument in 'set zoom'" return else: try: zoom_level=float(argv[2]) except: self.message="error: unrecognized argument '"+argv[2]+"' -- usage 'set zoom '" return if zoom_level<0: self.message="error: zoom level cannot be negative: '"+argv[2]+"'" return self.app.painter.set_zoom(zoom_level) # write to file def run_write(self,argv): if len(argv)>2: self.message="error: could not write to file: too many arguments -- usage: ':w [path to file]'" return elif len(argv)==1: if self.app.openfile!="": if self.app.readonly: self.message="error: open file is readonly" return self.app.painter.write(self.app.openfile) return else: self.message="error: no file is open for editing, specify a path" return (ret,self.message)=filecheck.check_write(argv[1],argv[0]=="w!") # add comment if no overwrite if ret==-2: self.message+=" (use ':w!' to overwrite)" if ret<0: return self.app.openfile=argv[1] self.app.readonly=False self.app.painter.write(argv[1]) # update status bar self.app.status_bar.draw() # edit file def run_edit(self,argv): if len(argv)>2: self.message="error: could not open file: too many arguments -- usage: ':e '" return elif len(argv)==1: self.message="error: could not open file: no argument found -- usage: ':e '" return # check that the file can be edited (ret,self.message)=filecheck.check_edit(argv[1]) # error if ret<0: return if os.path.isfile(argv[1]): # read the file self.app.painter.read(argv[1]) # set readonly mode self.app.readonly=not os.access(argv[1],os.W_OK) else: # new file, reset painter self.app.painter.reset() self.app.readonly=False # select openfile in app self.app.openfile=argv[1] # update status bar self.app.status_bar.draw() # export to file in Tikz format def run_export(self,argv): if len(argv)>2: self.message="error: could not open file: too many arguments -- usage: ':export '" return elif len(argv)==1: self.message="error: could not open file: no argument found -- usage: ':export '" return # check that the file can be edited (ret,self.message)=filecheck.check_edit(argv[1]) # error if ret<0: return # export self.app.painter.export_tikz(argv[1])