def lueosallistujatshelve(self): self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes2) file = self.openDialog.show() if not file: print("plaa") return if not os.path.isfile(file): self.writeToLog( 'Ajanotto by Juha Viitanen Tiedostoa ei voitu avata ' + file) try: print("tuliko" + os.path.splitext(file)[0]) self.dbase = shelve.open(os.path.splitext(file)[0]) #print(list(self.dbase.keys())) for key in list(self.dbase.keys()): #print(self.dbase[key].bibnumber) self.competitors.append(self.dbase[key]) self.__kilpailijoita = self.__kilpailijoita + 1 for luokka in self.luokat: if luokka == self.dbase[key].kilpasarja: self.luokkafound = 1 break if self.luokkafound == 0: self.luokat.append(self.dbase[key].kilpasarja) else: self.luokkafound = 0 print(list(self.luokat)) self.writeToLog("Ladattiin " + str(self.__kilpailijoita) + " kilpailijaa järjestelmään") except: print("Jotain meni pieleen")
def onOpen(self): ftypes = {('All files', '*'), ('Text files', '*.txt'), ('jpg file', '*.jpg'), ('gif file', '*.gif')} dlg = Open(self, filetypes=ftypes) fl = dlg.show() if fl != '': images = fl if images.endswith("png") \ or images.endswith("jpg") \ or images.endswith("jpeg") \ or images.endswith("gif") \ or images.endswith("tiff") \ or images.endswith("bmp") \ or images.endswith("PNG") \ or images.endswith("JPG") \ or images.endswith("JPEG") \ or images.endswith("GIF") \ or images.endswith("TIFF") \ or images.endswith("BMP"): window = tk.Toplevel() str = fl.split('/', 100) number_last = len(fl.split('/', 100)) - 1 window.title(str[number_last]) self.mg = Image.open(fl) w, h = Image.open(fl).size window.geometry(("%dx%d + 300 + 300") % (w, h)) window.configure(background='grey') path = fl self.img = Image.open(path) img = ImageTk.PhotoImage(Image.open(path)) panel = tk.Label(window, image=img) panel.pack(side="bottom", fill="both", expand=True) window.mainloop() else: mbox.showerror("Error", "Could not open file")
def ask_file(title, message, filetypes, directory, mult): dlg = Open(title=title, filetypes=filetypes, initialdir=directory, multiple=mult) out = dlg.show() return out
def callback_excl(): ftypes = [('Excl files', '*.excl'), ('All files', '*')] dlg = Open(self, filetypes=ftypes) fl = dlg.show() self.label_exclfile['text'] = fl # save used paths to file self.write_input_hist(self.label_exclfile['text'], self.label_ddir['text'])
def _getPicture(self): self.bgcolor = None ftypes = [('Image files', '*.gif *.png *.jpeg'), ('All files', '*')] dlg = Open(self._master, filetypes = ftypes) fl = dlg.show() if fl != '': img = Image.open(fl) img = img.resize((670,520), Image.ANTIALIAS) self.bgpic = 'Image/'+fl.split('/')[-1] img.save(self.bgpic) self._demo_turtle_screen.bgpic(self.bgpic)
def onOpen(self): self.image = None self.rects_img = None ftypes = [('Image files', '*.jpg *.png *.jpeg *.JPG'), ('All files', '*')] dlg = Open(self, filetypes=ftypes) self.img_dir = dlg.show() if self.img_dir != '': self.image = self.openImage(self.img_dir) label = Label(self, image=self.image) label.grid(row=0, column=0) self.text_frame.delete(1.0, 'end')
def lueosallistujat(self): self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) file = self.openDialog.show() self.vaihelaskuri[0] = 0 self.vaihelaskuri[1] = 0 self.vaihelaskuri[2] = 0 self.vaihelaskuri[3] = 0 if not file: return if not os.path.isfile(file): self.writeToLog( 'Ajanotto by Juha Viitanen 2016 Tiedostoa ei voitu avata ' + file) try: self.dbase = shelve.open('backlog/backlog' + time.strftime( "%d_%m_%y_%H_%M_%S", time.localtime(time.time())) + 'shv') with open(file, newline='') as csvfile: spamreader = csv.reader(csvfile, delimiter=';', quotechar='|') """print(spamreader)""" for row in spamreader: self.competitors.append( kilpailija(row[2], row[1], '+358........', row[3], row[4], row[0])) self.__kilpailijoita = self.__kilpailijoita + 1 for luokka in self.luokat: if luokka == row[4]: self.luokkafound = 1 break if self.luokkafound == 0: self.luokat.append(row[4]) else: self.luokkafound = 0 #self.writeToLog(', '.join(row)) #self.writeToLog(self.luokat) self.writeToLog("Ladattiin " + str(self.__kilpailijoita) + " kilpailijaa järjestelmään") for obj in self.competitors: self.dbase[obj.bibnumber] = obj self.dbase.close() """self.writeToLog(('%s'%self.__kilpailijoita))""" except: self.writeToLog( "CSV-tiedostoa ei löytynyt tai sitä ei voi avatata.")
def onBrowse(self): """Present the File Open dialog. Parameters: self: This object. Return value: String containing the path to the selected file, or None if no file was selected. Description: Display the File/Open dialog box and return the path to the selected file. """ # Present the file/open dialog. path = Open().show() # If no file was selected, return. if path == '': return # Save the current path in the file entry field. self._fileEntryField.setvalue(path)
def askopenfile(mode="r", **options): "Ask for a filename to open, and returned the opened file" filename = Open(**options).show() if filename: return open(filename, mode) return None
def importSudoku(self): # List lưu các định dạng file khác nhau để đọc ftypes = [('All files','*')] # Hiển thị hộp thoại chọn đường dẫn file dialog = Open(self,filetypes = ftypes) path = dialog.show() # Đọc file intput_ = "" with open(path,"r") as ins: for line in ins: intput_ += line self.input = intput_ self.createSquares(9) self.setNumbers(9,intput_)
def askopenfilenames(**options): """Ask for multiple filenames to open. Returns a list of filenames or empty list if cancel button selected """ options["multiple"] = 1 return Open(**options).show()
def onOpen(): global ftypes ftypes = [('Images', '*.jpg *.tif *.bmp *.gif *.png')] dlg = Open(filetypes = ftypes) fl = dlg.show() if fl != '': global imgin global imgincv imgin = skimage.io.imread(fl) #imgin = cv2.imread(fl,cv2.IMREAD_COLOR); cv2.namedWindow("ImageIn", cv2.WINDOW_AUTOSIZE) imgincv = cv2.cvtColor(imgin, cv2.COLOR_BGR2RGB) cv2.imshow("ImageIn", imgincv) cv2.waitKey(1000) cv2.destroyAllWindows()
def cll_abrir_arquivo(self): #b 0: abrindo arquivo arquivos = [( Gui.JSON["open_save_file"]["format_description"], Gui.JSON["open_save_file"]["allowed_extension"], )] caminho_arquivo = Open( filetypes=arquivos, defaultextension=arquivos, ) #e 0: obtemos uma string com o caminho completo do arquivo #b 1: se a abertura do arquivo for cancelada (string vazia) if len(caminho_arquivo) == 0: return None #e 1: encerramos a função sem fazer mais nada #b 2: pegando nome do arquivo para colocar no label self.nome_arquivo = caminho_arquivo.split("/")[-1] f = open(caminho_arquivo, "r") self.url_list = list(map( lambda l: l.strip(), f.readlines(), )) self.links_as_texto = "\n".join(self.url_list) f.close() #e 2: os links estão em self.url_list #b 3: chama a interface de novo arquivo self.cll_novo_arquivo() self.l_nome_arquivo["text"] = self.nome_arquivo self.t_text_area.insert(0.0, self.links_as_texto)
def callback_dis(): ftypes = [('Disco files', '*.dst'), ('All files', '*')] dlg = Open(self, filetypes=ftypes) fl = dlg.show() self.label_discofile['text'] = fl # load stations to the listbox discos_dates = velocities.load_discofile(fl) all_stations = sorted(list(discos_dates)) self.lb.configure(state=NORMAL) self.lb.delete(0, END) for st in all_stations: self.lb.insert(END, st) # disable listbox again if multistation mode if self.solution_mode.get() == 0: self.lb.configure(state='disabled')
def choose(): if (PDF_Path != ""): PDF_Path.set( Open(title="Choose File", initialdir="C:/", filetypes=(("PDF File", "*.pdf"), ("All Files", "*.*")))) else: PDF_Path.set("Please Choose PDF File Again...:(")
def openfile(): ftypes = [('Python files', '*.*'), ('All files', '*')] #Loại file dlg = Open(filetypes=ftypes) fl = dlg.show() global f, frame, labelsize if fl != '': im = readFile(fl) f = fl show(im) im = cv2.imread(f) #Lấy kích thước labelsize = Label(frame, text="", font="Times 18 bold italic") labelsize.place(x=560, y=195) height, width, channels = im.shape stringsize = "-Kích thước ảnh: " + str(height) + "x" + str(width) labelsize.config(text=stringsize) print(stringsize)
def onOpen(self): """ Event handler for clicking the Open button Args: None Returns: None """ ftypes = [('all files', '.*'), ('text files', '.txt'), ('ann files', '.ann')] dlg = Open(self, filetypes=ftypes) fl = dlg.show() if fl != '': self.text.delete("1.0", END) text = self.readFile(fl) self.text.insert(END, text) self.lbl.config(text="File: " + fl) self.autoLoadNewFile(self.fileName, "1.0") self.text.mark_set(INSERT, "1.0")
def inputImage(self): ftypes = [('Image files', '*.gif *.png *.jpeg'), ('All files', '*')] dlg = Open(self._master, filetypes=ftypes) fl = dlg.show() if fl != '': photo = Image.open(fl) if self._is_int(self._subframe.shape_width.get()) == True: photo = photo.resize( (int(self._subframe.shape_width.get()), photo.height), Image.ANTIALIAS) if self._is_int(self._subframe.shape_height.get()) == True: photo = photo.resize( (photo.width, int(self._subframe.shape_height.get())), Image.ANTIALIAS) photo = ImageTk.PhotoImage(photo) self._shape_image = photo self._subframe.shape_image.configure(image=photo) self._subframe.shape_image.photo = photo self._subframe.shape_image.place(x=500, y=250)
def _onFileOpen(self): if debug: print ('File/Open...') # If the current document has changed, prompt to save it. self._saveIfChanged() # Fetch the path to the new file. If found, load the document and # save the path. path = Open().show() if path == (): return self._open(path)
def onOpen(self): if self.a != 0: self.lable2.destroy() ftypes = [('Python files', '*.jpg'), ('All files', '*')] dlg = Open(self, filetypes=ftypes) fl = dlg.show() if fl != '': self.liv = Image.open(fl) lbl5 = Label(self, text="%f MB" % (os.path.getsize(fl) / 1048576.0), width=10) lbl5.place(x=80, y=300) self.liv.thumbnail((200, 200), Image.ANTIALIAS) liverpool = ImageTk.PhotoImage(self.liv) self.lable2 = Label(self, image=liverpool) self.lable2.image = liverpool self.lable2.place(x=20, y=80) self.a = 1
def importGrid(self): #List lưu các định dạng file khác nhau để đọc file ftypes = [('All files', '*')] #Hiển thị file dialog chúng ta cần gọi hàm Open(). Hàm này trả về đường dẫn file dlg = Open(self, filetypes=ftypes) fl = dlg.show() with open(fl) as textFile: lines = [line.split() for line in textFile] self.gridWidth = len(lines) self.gridHeight = len(lines[0]) results = [] for i in range(self.gridWidth): results.append([]) for j in range(self.gridHeight): if lines[i][j] == "1": results[i].append(1) else: results[i].append(0) self.MAP = results # Đưa grid lên form self.updateUI()
def onOpen(self): if self.a == 0: self.liv = Image.open("1.png") self.liv.thumbnail((200, 200), Image.ANTIALIAS) liverpool = ImageTk.PhotoImage(self.liv) self.lable2 = Label(self, image=liverpool) self.lable2.image = liverpool self.lable2.place(x=20, y=80) self.lable2.destroy() else: self.lable2.destroy() ftypes = [('Python files', '*.jpg'), ('All files', '*')] dlg = Open(self, filetypes=ftypes) fl = dlg.show() if fl != '': self.liv = Image.open(fl) self.liv.thumbnail((200, 200), Image.ANTIALIAS) liverpool = ImageTk.PhotoImage(self.liv) self.lable2 = Label(self, image=liverpool) self.lable2.image = liverpool self.lable2.place(x=20, y=80) self.a = 1
def loadImage(self): """Load an image into ds9 Parameters: - self - This DS9Connector object Return value: - ra - the ROI center RA coordinate - dec - the ROI center Dec coordinate Description: After checking for a connection to ds9, if connected the method launches and Open File dialog to allow the user to select an image file to display. Once selected it attempts to display the file in ds9. If the file type is unsupported, the user is warned of such. If the file is supported, the program attempts to load and extract the images ROI data and passes it back to the calling method. """ noROIData = (-1, 0, 0) self._checkConnection() if (self._isConnected()): # open file dialog to get name path = Open().show() if path == (): return noROIData # open the file in window try: self.ds9.set("fits " + path) file = FitsFile(path) if file.hasROIData(): return file.getROIData() else: return noROIData except: showwarning("File Type Error!", "The specified file is not a valid FITS image") return noROIData
class TextEditor: # mix with menu/toolbar Frame class startfiledir = '.' # for dialogs editwindows = [] # for process-wide quit check # Unicode configurations # imported in class to allow overrides in subclass or self if __name__ == '__main__': pass else: pass ftypes = [('All files', '*'), # for file open dialog ('Text files', '.txt'), # customize in subclass ('Python files', '.py')] # or set in each instance colors = [{'fg': 'black', 'bg': 'white'}, # color pick list {'fg': 'yellow', 'bg': 'black'}, # first item is default {'fg': 'white', 'bg': 'blue'}, # tailor me as desired {'fg': 'black', 'bg': 'beige'}, # or do PickBg/Fg chooser {'fg': 'yellow', 'bg': 'purple'}, {'fg': 'black', 'bg': 'brown'}, {'fg': 'lightgreen', 'bg': 'darkgreen'}, {'fg': 'darkblue', 'bg': 'orange'}, {'fg': 'orange', 'bg': 'darkblue'}] fonts = [('courier', 9 + FontScale, 'normal'), # platform-neutral fonts ('courier', 12 + FontScale, 'normal'), # (family, size, style) ('courier', 10 + FontScale, 'bold'), # or pop up a listbox ('courier', 10 + FontScale, 'italic'), # make bigger on Linux ('times', 10 + FontScale, 'normal'), # use 'bold italic' for 2 ('helvetica', 10 + FontScale, 'normal'), # also 'underline', etc. ('ariel', 10 + FontScale, 'normal'), ('system', 10 + FontScale, 'normal'), ('courier', 20 + FontScale, 'normal')] def __init__(self, loadFirst='', loadEncode=''): if not isinstance(self, GuiMaker): raise TypeError('TextEditor needs a GuiMaker mixin') self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.knownEncoding = None # 2.1 Unicode: till Open or Save self.text.focus() # else must click in text if loadFirst: self.update() # 2.1: else @ line 2; see book self.onOpen(loadFirst, loadEncode) def start(self): # run by GuiMaker.__init__ self.menuBar = [ # configure menu/toolbar ('File', 0, # a GuiMaker menu def tree [('Open...', 0, self.onOpen), # build in method for self ('Save', 0, self.onSave), # label, shortcut, callback ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Quit...', 0, self.onQuit)] ), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)] ), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Replace...', 0, self.onChange), ('Grep...', 3, self.onGrep)] ), ('Tools', 0, [('Pick Font...', 6, self.onPickFont), ('Font List', 0, self.onFontList), 'separator', ('Pick Bg...', 3, self.onPickBg), ('Pick Fg...', 0, self.onPickFg), ('Color List', 0, self.onColorList), 'separator', ('Info...', 0, self.onInfo), ('Clone', 1, self.onClone), ('Run Code', 0, self.onRunCode)] )] self.toolBar = [ ('Save', self.onSave, {'side': LEFT}), ('Cut', self.onCut, {'side': LEFT}), ('Copy', self.onCopy, {'side': LEFT}), ('Paste', self.onPaste, {'side': LEFT}), ('Find', self.onRefind, {'side': LEFT}), ('Help', self.help, {'side': RIGHT}), ('Quit', self.onQuit, {'side': RIGHT})] def makeWidgets(self): # run by GuiMaker.__init__ name = Label(self, bg='black', fg='white') # add below menu, above tool name.pack(side=TOP, fill=X) # menu/toolbars are packed # GuiMaker frame packs itself vbar = Scrollbar(self) hbar = Scrollbar(self, orient='horizontal') text = Text(self, padx=5, wrap='none') # disable line wrapping text.config(undo=1, autoseparators=1) # 2.0, default is 0, 1 vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) # pack text last text.pack(side=TOP, fill=BOTH, expand=YES) # else sbars clipped text.config(yscrollcommand=vbar.set) # call vbar.set on text move text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) # call text.yview on scroll move hbar.config(command=text.xview) # or hbar['command']=text.xview # 2.0: apply user configs or defaults startfont = configs.get('font', self.fonts[0]) startbg = configs.get('bg', self.colors[0]['bg']) startfg = configs.get('fg', self.colors[0]['fg']) text.config(font=startfont, bg=startbg, fg=startfg) if 'height' in configs: text.config(height=configs['height']) if 'width' in configs: text.config(width=configs['width']) self.text = text self.filelabel = name ############################################################################ # File menu commands ############################################################################ def my_askopenfilename(self): # objects remember last result dir/file if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): # objects remember last result dir/file if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst='', loadEncode=''): """ tests if file is okay ahead of time to try to avoid opens; we could also load and manually decode bytes to str to avoid multiple open attempts, but this is unlikely to try all cases; encoding behavior is configurable in the local textConfig.py: 1) tries known type first if passed in by client (email_self charsets) 2) if opensAskUser True, try user input next (prefill wih defaults) 3) if opensEncoding nonempty, try this encoding next: 'latin-1', etc. 4) tries sys.getdefaultencoding() platform default next 5) uses binary mode bytes and Tk policy as the last resort """ if self.text_edit_modified(): # 2.0 if not askyesno('PyEdit', 'Text has changed: discard changes?'): return file = loadFirst or self.my_askopenfilename() if not file: return if not os.path.isfile(file): showerror('PyEdit', 'Could not open file ' + file) return # try known encoding if passed and accurate (e.g., email_self) text = None # empty file = '' = False: test for None! if loadEncode: try: text = open(file, 'r', encoding=loadEncode).read() self.knownEncoding = loadEncode except (UnicodeError, LookupError, IOError): # lookup: bad name pass # try user input, prefill with next choice as default if text is None and self.opensAskUser: self.update() # else dialog doesn't appear in rare cases askuser = askstring('PyEdit', 'Enter Unicode encoding for open', initialvalue=(self.opensEncoding or sys.getdefaultencoding() or '')) self.text.focus() # else must click if askuser: try: text = open(file, 'r', encoding=askuser).read() self.knownEncoding = askuser except (UnicodeError, LookupError, IOError): pass # try config file (or before ask user?) if text is None and self.opensEncoding: try: text = open(file, 'r', encoding=self.opensEncoding).read() self.knownEncoding = self.opensEncoding except (UnicodeError, LookupError, IOError): pass # try platform default (utf-8 on windows; try utf8 always?) if text is None: try: text = open(file, 'r', encoding=sys.getdefaultencoding()).read() self.knownEncoding = sys.getdefaultencoding() except (UnicodeError, LookupError, IOError): pass # last resort: use binary bytes and rely on Tk to decode if text is None: try: text = open(file, 'rb').read() # bytes for Unicode text = text.replace(b'\r\n', b'\n') # for display, saves self.knownEncoding = None except IOError: pass if text is None: showerror('PyEdit', 'Could not decode and open file ' + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onSave(self): self.onSaveAs(self.currfile) # may be None def onSaveAs(self, forcefile=None): """ retains successful encoding name here for next save, because this may be the first Save after New or a manual text insertion; Save and SaveAs may both use last known encoding, per config file (it probably should be used for Save, but SaveAs usage is unclear); gui prompts are prefilled with the known encoding if there is one; does manual text.encode() to avoid creating file; text mode files perform platform specific end-line conversion: Windows \r dropped if present on open by text mode (auto) and binary mode (manually); if manual content inserts, must delete \r else duplicates here; knownEncoding=None before first Open or Save, after New, if binary Open; encoding behavior is configurable in the local textConfig.py: 1) if savesUseKnownEncoding > 0, try encoding from last open or save 2) if savesAskUser True, try user input next (prefill with known?) 3) if savesEncoding nonempty, try this encoding next: 'utf-8', etc 4) tries sys.getdefaultencoding() as a last resort """ filename = forcefile or self.my_asksaveasfilename() if not filename: return text = self.getAllText() # 2.1: a str string, with \n eolns, encpick = None # even if read/inserted as bytes # try known encoding at latest Open or Save, if any if self.knownEncoding and ( # enc known? (forcefile and self.savesUseKnownEncoding >= 1) or # on Save? (not forcefile and self.savesUseKnownEncoding >= 2)): # on SaveAs? try: text.encode(self.knownEncoding) encpick = self.knownEncoding except UnicodeError: pass # try user input, prefill with known type, else next choice if not encpick and self.savesAskUser: self.update() # else dialog doesn't appear in rare cases askuser = askstring('PyEdit', 'Enter Unicode encoding for save', initialvalue=(self.knownEncoding or self.savesEncoding or sys.getdefaultencoding() or '')) self.text.focus() # else must click if askuser: try: text.encode(askuser) encpick = askuser except (UnicodeError, LookupError): # LookupError: bad name pass # UnicodeError: can't encode # try config file if not encpick and self.savesEncoding: try: text.encode(self.savesEncoding) encpick = self.savesEncoding except (UnicodeError, LookupError): pass # try platform default (utf8 on windows) if not encpick: try: text.encode(sys.getdefaultencoding()) encpick = sys.getdefaultencoding() except (UnicodeError, LookupError): pass # open in text mode for endlines + encoding if not encpick: showerror('PyEdit', 'Could not encode for file ' + filename) else: try: file = open(filename, 'w', encoding=encpick) file.write(text) file.close() except: showerror('PyEdit', 'Could not write file ' + filename) else: self.setFileName(filename) # may be newly created self.text.edit_modified(0) # 2.0: clear modified flag self.knownEncoding = encpick # 2.1: keep enc for next save # don't clear undo/redo stks! def onNew(self): """ start editing a new file from scratch in current window; see onClone to pop-up a new independent edit window instead; """ if self.text_edit_modified(): # 2.0 if not askyesno('PyEdit', 'Text has changed: discard changes?'): return self.setFileName(None) self.clearAllText() self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag self.knownEncoding = None # 2.1: Unicode type unknown def onQuit(self): """ on Quit menu/toolbar select and wm border X button in toplevel windows; 2.1: don't exit app if others changed; 2.0: don't ask if self unchanged; moved to the top-level window classes at the end since may vary per usage: a Quit in GUI might quit() to exit, destroy() just one Toplevel, Tk, or edit frame, or not be provided at all when run as an attached component; check self for changes, and if might quit(), main windows should check other windows in the process-wide list to see if they have changed too; """ assert False, 'onQuit must be defined in window-specific sublass' def text_edit_modified(self): """ 2.1: this now works! seems to have been a bool result type issue in tkinter; 2.0: self.text.edit_modified() broken in Python 2.4: do manually for now; """ return self.text.edit_modified() # return self.tk.call((self.text._w, 'edit') + ('modified', None)) ############################################################################ # Edit menu commands ############################################################################ def onUndo(self): # 2.0 try: # tk8.4 keeps undo/redo stacks self.text.edit_undo() # exception if stacks empty except TclError: # menu tear-offs for quick undo showinfo('PyEdit', 'Nothing to undo') def onRedo(self): # 2.0: redo an undone try: self.text.edit_redo() except TclError: showinfo('PyEdit', 'Nothing to redo') def onCopy(self): # get text selected by mouse, etc. if not self.text.tag_ranges(SEL): # save in cross-app clipboard showerror('PyEdit', 'No text selected') else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): # delete selected text, no save if not self.text.tag_ranges(SEL): showerror('PyEdit', 'No text selected') else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror('PyEdit', 'No text selected') else: self.onCopy() # save and delete selected text self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror('PyEdit', 'Nothing to paste') return self.text.insert(INSERT, text) # add at current insert cursor self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT + '-%dc' % len(text), INSERT) self.text.see(INSERT) # select it, so it can be cut def onSelectAll(self): self.text.tag_add(SEL, '1.0', END + '-1c') # select entire text self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top ############################################################################ # Search menu commands ############################################################################ def onGoto(self, forceline=None): line = forceline or askinteger('PyEdit', 'Enter line number') self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END + '-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, '%d.0' % line) # goto line self.text.tag_remove(SEL, '1.0', END) # delete selects self.text.tag_add(SEL, INSERT, 'insert + 1l') # select line self.text.see(INSERT) # scroll to line else: showerror('PyEdit', 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring('PyEdit', 'Enter search string') self.text.update() self.text.focus() self.lastfind = key if key: # 2.0: nocase nocase = configs.get('caseinsens', True) # 2.0: config where = self.text.search(key, INSERT, END, nocase=nocase) if not where: # don't wrap showerror('PyEdit', 'String not found') else: pastkey = where + '+%dc' % len(key) # index past key self.text.tag_remove(SEL, '1.0', END) # remove any sel self.text.tag_add(SEL, where, pastkey) # select key self.text.mark_set(INSERT, pastkey) # for next find self.text.see(where) # scroll display def onRefind(self): self.onFind(self.lastfind) def onChange(self): """ non-modal find/change dialog 2.1: pass per-dialog inputs to callbacks, may be > 1 change dialog open """ new = Toplevel(self) new.title('PyEdit - change') Label(new, text='Find text?', relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text='Change to?', relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): # use my entry in enclosing scope self.onFind(entry1.get()) # runs normal find dialog callback def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text='Find', command=onFind).grid(row=0, column=2, sticky=EW) Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) # expandable entries def onDoChange(self, findtext, changeto): # on Apply in change dialog: change and refind if self.text.tag_ranges(SEL): # must find first self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) # deletes if empty self.text.see(INSERT) self.onFind(findtext) # goto next appear self.text.update() # force refresh def onGrep(self): """ TBD: better to issue an error if any file fails to decode? but utf-16 2-bytes/char format created in Notepad may decode without error per utf-8, and search strings won't be found; TBD: could allow input of multiple encoding names, split on comma, try each one for every file, without open loadEncode? """ from minghu6.gui.formrows import makeFormRow # nonmodal dialog: get dirnname, filenamepatt, grepkey popup = Toplevel() popup.title('PyEdit - grep') var1 = makeFormRow(popup, label='Directory root', width=18, browse=False) var2 = makeFormRow(popup, label='Filename pattern', width=18, browse=False) var3 = makeFormRow(popup, label='Search string', width=18, browse=False) var4 = makeFormRow(popup, label='Content encoding', width=18, browse=False) var1.set('.') # current dir var2.set('*.py') # initial values var4.set(sys.getdefaultencoding()) # for file content, not filenames cb = lambda: self.onDoGrep(var1.get(), var2.get(), var3.get(), var4.get()) Button(popup, text='Go', command=cb).pack() def onDoGrep(self, dirname, filenamepatt, grepkey, encoding): """ on Go in grep dialog: populate scrolled list with matches tbd: should producer thread be daemon so it dies with app? """ import threading, queue # make non-modal un-closeable dialog mypopup = Tk() mypopup.title('PyEdit - grepping') status = Label(mypopup, text='Grep thread searching for: %r...' % grepkey) status.pack(padx=20, pady=20) mypopup.protocol('WM_DELETE_WINDOW', lambda: None) # ignore X close # start producer thread, consumer loop myqueue = queue.Queue() threadargs = (filenamepatt, dirname, grepkey, encoding, myqueue) threading.Thread(target=self.grepThreadProducer, args=threadargs).start() self.grepThreadConsumer(grepkey, encoding, myqueue, mypopup) def grepThreadProducer(self, filenamepatt, dirname, grepkey, encoding, myqueue): """ in a non-GUI parallel thread: queue find.find results list; could also queue matches as found, but need to keep window; file content and file names may both fail to decode here; TBD: could pass encoded bytes to find() to avoid filename decoding excs in os.walk/listdir, but which encoding to use: sys.getfilesystemencoding() if not None? see also Chapter6 footnote issue: 3.1 fnmatch always converts bytes per Latin-1; """ from minghu6.etc.find import find matches = [] try: for filepath in find(pattern=filenamepatt, startdir=dirname): try: textfile = open(filepath, encoding=encoding) for (linenum, linestr) in enumerate(textfile): if grepkey in linestr: msg = '%s@%d [%s]' % (filepath, linenum + 1, linestr) matches.append(msg) except UnicodeError as X: print('Unicode error in:', filepath, X) # eg: decode, bom except IOError as X: print('IO error in:', filepath, X) # eg: permission finally: myqueue.put(matches) # stop consumer loop on find excs: filenames? def grepThreadConsumer(self, grepkey, encoding, myqueue, mypopup): """ in the main GUI thread: watch queue for results or []; there may be multiple active grep threads/loops/queues; there may be other types of threads/checkers in process, especially when PyEdit is attached component (PyMailGUI); """ import queue try: matches = myqueue.get(block=False) except queue.Empty: myargs = (grepkey, encoding, myqueue, mypopup) self.after(250, self.grepThreadConsumer, *myargs) else: mypopup.destroy() # close status self.update() # erase it now if not matches: showinfo('PyEdit', 'Grep found no matches for: %r' % grepkey) else: self.grepMatchesList(matches, grepkey, encoding) def grepMatchesList(self, matches, grepkey, encoding): """ populate list after successful matches; we already know Unicode encoding from the search: use it here when filename clicked, so open doesn't ask user; """ from minghu6.gui.scrolledlist import ScrolledList print('Matches for %s: %s' % (grepkey, len(matches))) # catch list double-click class ScrolledFilenames(ScrolledList): def runCommand(self, selection): file, line = selection.split(' [', 1)[0].split('@') editor = TextEditorMainPopup( loadFirst=file, winTitle=' grep match', loadEncode=encoding) editor.onGoto(int(line)) editor.text.focus_force() # no, really # new non-modal widnow popup = Tk() popup.title('PyEdit - grep matches: %r (%s)' % (grepkey, encoding)) ScrolledFilenames(parent=popup, options=matches) ############################################################################ # Tools menu commands ############################################################################ def onFontList(self): self.fonts.append(self.fonts[0]) # pick next font in list del self.fonts[0] # resizes the text area self.text.config(font=self.fonts[0]) def onColorList(self): self.colors.append(self.colors[0]) # pick next color in list del self.colors[0] # move current to end self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg']) def onPickFg(self): self.pickColor('fg') # added on 10/02/00 def onPickBg(self): # select arbitrary color self.pickColor('bg') # in standard color dialog def pickColor(self, part): # this is too easy (triple, hexstr) = askcolor() if hexstr: self.text.config(**{part: hexstr}) def onInfo(self): """ pop-up dialog giving text statistics and cursor location; caveat : Tk insert position column counts a tab as one character: translate to next multiple of 8 to match visual? """ text = self.getAllText() # added on 5/3/00 in 15 mins bytes = len(text) # words uses a simple guess: lines = len(text.split('\n')) # any separated by whitespace words = len(text.split()) # 3.x: bytes is really chars index = self.text.index(INSERT) # str is unicode code points where = tuple(index.split('.')) showinfo('PyEdit Information', 'Current location:\n\n' + 'line:\t%s\ncolumn:\t%s\n\n' % where + 'File text statistics:\n\n' + 'chars:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words)) def onClone(self, makewindow=True): """ open a new edit window without changing one already open (onNew); inherits quit and other behavior of the window that it clones; """ if not makewindow: new = None # assume class makes its own window else: new = Toplevel() # a new edit window in same process myclass = self.__class__ # instance's (lowest) class object myclass(new) # attach/run instance of my class def onRunCode(self, parallelmode=True): """ run Python code being edited--not an IDE, but handy; tries to run in file's dir, not cwd (may be PP4E root); inputs and adds command-line arguments for script files; code's stdin/out/err = editor's start window, if any: run with a console window to see code's print outputs; but parallelmode uses start to open a DOS box for I/O; module search path will include '.' dir where started; in non-file mode, code's Tk root may be PyEdit's window; subprocess or multiprocessing modules may work here too; sometimes does not appear in rare cases; """ def askcmdargs(): return askstring('PyEdit', 'Commandline arguments?') or '' from launchmods import System, Start, StartArgs, Fork filemode = False thefile = str(self.getFileName()) if os.path.exists(thefile): filemode = askyesno('PyEdit', 'Run from file?') self.update() # 2.1: run update() if not filemode: # run text string cmdargs = askcmdargs() namespace = {'__name__': '__main__'} # run as top-level sys.argv = [thefile] + cmdargs.split() # could use threads exec(self.getAllText() + '\n', namespace) # exceptions ignored elif self.text_edit_modified(): # 2.0: changed test showerror('PyEdit', 'Text changed: you must save before run') else: cmdargs = askcmdargs() mycwd = os.getcwd() # cwd may be root dirname, filename = os.path.split(thefile) # get dir, base os.chdir(dirname or mycwd) # cd for filenames thecmd = filename + ' ' + cmdargs # 2.1: not theFile if not parallelmode: # run as file System(thecmd, thecmd)() # block editor else: if sys.platform[:3] == 'win': # spawn in parallel run = StartArgs if cmdargs else Start # 2.1: support args run(thecmd, thecmd)() # or always Spawn else: Fork(thecmd, thecmd)() # spawn in parallel os.chdir(mycwd) # go back to my dir def onPickFont(self): """ """ from minghu6.gui.formrows import makeFormRow popup = Toplevel(self) popup.title('PyEdit - font') var1 = makeFormRow(popup, label='Family', browse=False) var2 = makeFormRow(popup, label='Size', browse=False) var3 = makeFormRow(popup, label='Style', browse=False) var1.set('courier') var2.set('14') # suggested vals var3.set('bold italic') # see pick list for valid inputs Button(popup, text='Apply', command= lambda: self.onDoFont(var1.get(), var2.get(), var3.get())).pack() def onDoFont(self, family, size, style): try: self.text.config(font=(family, int(size), style)) except: showerror('PyEdit', 'Bad font specification') ############################################################################ # Utilities, useful outside this class ############################################################################ def isEmpty(self): return not self.getAllText() def getAllText(self): return self.text.get('1.0', END + '-1c') # extract text as str string def setAllText(self, text): """ caller: call self.update() first if just packed, else the initial position may be at line 2, not line 1 (2.1; Tk bug?) """ self.text.delete('1.0', END) # store text string in widget self.text.insert(END, text) # or '1.0'; text=bytes or str self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top, insert set def clearAllText(self): self.text.delete('1.0', END) # clear text in widget def getFileName(self): return self.currfile def setFileName(self, name): # see also: onGoto(linenum) self.currfile = name # for save self.filelabel.config(text=str(name)) def setKnownEncoding(self, encoding='utf-8'): # for saves if inserted self.knownEncoding = encoding # else saves use config, ask? def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) # 'black', hexstring def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo('About PyEdit', helptext)
class Kilpailu(Frame): luokkafound = 0 startfiledir = '.' ftypes = [('CSV', '.csv')] ftypes2 = [('DAT', '.dat')] """ Implements a stop watch frame widget. """ def __init__(self, parent=None, **kw): self.__startTime = 0 self.__endTime = 0 self.__competition_running = False self.__kilpailijoita = 0 self._competitors_finished = 0 self.__competiontName = "lobby" self.__matka = [] self.temppi = [] self.competitors = [] self.found = 0 Frame.__init__(self, parent, kw) # Frame.__init__(self, parent, kw) """self.pack(expand=YES,fill=BOTH)""" self._start = 0.0 self._elapsedtime = 0.0 self._pausestart = 0.0 self._elapsedpause = 0.0 self._running = 0 self.timestr = StringVar() self.strfinished = StringVar() self.syotanumero = StringVar() self.competitionphase = StringVar() self.makeWidgets() self.temporary = [] self.temporary2 = [] self.luokat = [] self.vaihelaskuri = [0, 0, 0, 0, 0, 0, 0] self._timeamount = 1 self.timeupdateinterval = Timer(0.01, self._update) def lueosallistujat(self): self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) file = self.openDialog.show() self.vaihelaskuri[0] = 0 self.vaihelaskuri[1] = 0 self.vaihelaskuri[2] = 0 self.vaihelaskuri[3] = 0 if not file: return if not os.path.isfile(file): self.writeToLog( 'Ajanotto by Juha Viitanen 2016 Tiedostoa ei voitu avata ' + file) try: self.dbase = shelve.open('backlog/backlog' + time.strftime( "%d_%m_%y_%H_%M_%S", time.localtime(time.time())) + 'shv') with open(file, newline='') as csvfile: spamreader = csv.reader(csvfile, delimiter=';', quotechar='|') """print(spamreader)""" for row in spamreader: self.competitors.append( kilpailija(row[2], row[1], '+358........', row[3], row[4], row[0])) self.__kilpailijoita = self.__kilpailijoita + 1 for luokka in self.luokat: if luokka == row[4]: self.luokkafound = 1 break if self.luokkafound == 0: self.luokat.append(row[4]) else: self.luokkafound = 0 #self.writeToLog(', '.join(row)) #self.writeToLog(self.luokat) self.writeToLog("Ladattiin " + str(self.__kilpailijoita) + " kilpailijaa järjestelmään") for obj in self.competitors: self.dbase[obj.bibnumber] = obj self.dbase.close() """self.writeToLog(('%s'%self.__kilpailijoita))""" except: self.writeToLog( "CSV-tiedostoa ei löytynyt tai sitä ei voi avatata.") def lueosallistujatshelve(self): self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes2) file = self.openDialog.show() if not file: print("plaa") return if not os.path.isfile(file): self.writeToLog( 'Ajanotto by Juha Viitanen Tiedostoa ei voitu avata ' + file) try: print("tuliko" + os.path.splitext(file)[0]) self.dbase = shelve.open(os.path.splitext(file)[0]) #print(list(self.dbase.keys())) for key in list(self.dbase.keys()): #print(self.dbase[key].bibnumber) self.competitors.append(self.dbase[key]) self.__kilpailijoita = self.__kilpailijoita + 1 for luokka in self.luokat: if luokka == self.dbase[key].kilpasarja: self.luokkafound = 1 break if self.luokkafound == 0: self.luokat.append(self.dbase[key].kilpasarja) else: self.luokkafound = 0 print(list(self.luokat)) self.writeToLog("Ladattiin " + str(self.__kilpailijoita) + " kilpailijaa järjestelmään") except: print("Jotain meni pieleen") def makeWidgets(self): """ Make the time label. """ self.makemenu() l = Label(self, textvariable=self.timestr, font=('arial', 16, 'bold'), bg="black", fg="yellow") self.dnfbutton = Button(self, text='DNF', command=self.didnotfinish, font=('arial', 16, 'bold')) self.dnsbutton = Button(self, text='DNS', command=self.didnotstart, font=('arial', 16, 'bold')) self.startbutton = Button(self, text='START', command=self.aloitakilpailu, font=('arial', 16, 'bold')) self.strfinished.set('') self.competitionphase.set('Phase') l.config(font=('arial', 24, 'bold')) self.timestr.set('Kilpailu ei ole käynnissä') l.pack() """ log = Text(self,relief=SUNKEN).grid(row=2) """ """ self.log = log """ #Button(self, text='Stop', command=self.lopetakilpailu,font=('arial',16,'bold')).pack(side=RIGHT, expand=YES, fill=BOTH) if xlsxsupport == True: Button(self, text='XLSX', command=self.writetoxlsx, font=('arial', 16, 'bold')).pack(side=RIGHT, expand=YES, fill=BOTH) #Button(self, text='TXT', command=self.writeOfficialTimes,font=('arial',16,'bold')).pack(side=RIGHT, expand=YES, fill=BOTH) #Button(self, text='HTML', command=self.writeHTML,font=('arial',16,'bold')).pack(side=RIGHT, expand=YES, fill=BOTH) #Button(self, text='Startlist', command=self.writetotxt,font=('arial',16,'bold')).pack(side=BOTTOM, expand=YES, fill=BOTH) self.scroll = Scrollbar(self) self.log = Text(self, state='disabled', width=120, height=30, wrap='none', bg="black", fg="yellow", font=('Courier', 10, 'bold')) finished = Label(self, textvariable=self.strfinished) finished.config(font=('arial', 16, 'bold')) phase = Label(self, textvariable=self.competitionphase) phase.config(font=('arial', 16, 'bold')) self.numero = Entry(self, textvariable=self.syotanumero, font=('arial', 40, 'bold'), width=5, justify="center", bg="yellow", fg="black") self.scroll.config(command=self.log.yview) self.scroll.pack(side=RIGHT, expand=YES, fill=BOTH) self.log.pack() self.log.config(yscrollcommand=self.scroll.set) self.numero.pack() finished.pack() phase.pack() self.dnfbutton.pack(side=LEFT, expand=NO, fill=BOTH) self.dnsbutton.pack(side=LEFT, expand=NO, fill=BOTH) self.startbutton.pack(side=LEFT, expand=YES, fill=BOTH) """self.scroll.pack(side=RIGHT,fill=Y)""" """slf.log.pack(side=LEFT,expand=YES, fill=BOTH)""" def makemenu(self): self.menubar = Menu(self.master) self.master.config(menu=self.menubar) file = Menu(self.menubar) file.add_command(label='TXT virallinen', command=lambda: self.writeOfficialTimes(0, False)) file.add_command(label='TXT kilpanumerolla', command=lambda: self.writeOfficialTimes(1, False)) file.add_command(label='HTML', command=self.writeHTML) if xlsxsupport == True: file.add_command(label='XLSX') self.menubar.add_cascade(label='Vie tiedot', menu=file) kilpailu = Menu(self.master) kilpailu.add_command(label='Kilpailun tiedot', command=self.syotatiedot) kilpailu.add_command(label='Start', command=self.aloitakilpailu) kilpailu.add_command(label='Stop', command=self.lopetakilpailu) self.menubar.add_cascade(label='Kilpailu', menu=kilpailu) tiedot = Menu(self.master) tiedot.add_command(label='Lue kilpanauhoite .dat', command=self.lueosallistujatshelve) tiedot.add_command(label='Lue kilpailijat .csv', command=self.lueosallistujat) self.menubar.add_cascade(label='Datat', menu=tiedot) tallenna = Menu(self.master) if xlsxsupport == True: tallenna.add_command(label='XLSX', command=self.lueosallistujatshelve) tallenna.add_command(label='TXT virallinen', command=lambda: self.writeOfficialTimes(0, True)) tallenna.add_command(label='TXT kilpanumerolla', command=lambda: self.writeOfficialTimes(1, True)) tallenna.add_command(label='dat-tiedosto', command=self.lueosallistujatshelve) tallenna.add_command(label='JSON', command=self.lueosallistujatshelve) self.menubar.add_cascade(label='Tallenna', menu=tallenna) def _update(self): self.timestr.set( time.strftime("%H:%M:%S", time.gmtime(self.getKisaaika())) + (',%02d' % int(( (self.getKisaaika() - int(self.getKisaaika())) * 100)))) self._timer = self.after(100, self._update) def _setTime(self, elap): print("update request") self.timestr.set( time.strftime("%H:%M:%S", time.gmtime(self.getKisaaika()))) def status(self): return self.__competition_running def aloitusaika(self): return self.__startTime def lopetusaika(self): return self.__endTime def syotatiedot(self): # i = 0 self.__competiontName = askstring( 'Kilpailun nimi', 'Anna kilpailllu nimi') #input("Kilpailun nimi: ") self._timeamount = askinteger('Väliaikojen määrä', 'Anna väliaikojen määrä') '''while i < self._timeamount: self.__matka.append(askfloat('Matka: '+ str(i+1),'Vaiheen: '+ str(i+1)+' matka')) i +=1''' def getKisaaika(self): return time.time() - self.__startTime def aloitakilpailu(self): if self.__competition_running == False: self.__startTime = time.time() self.__competition_running = True self.startbutton['state'] = tk.DISABLED #print("Kilpailu käynnistetty: ",time.strftime("%H:%M:%S",time.localtime(self.__startTime))) self.writeToLog( ('Kilpailu käynnistetty: ' + time.strftime('%H:%M:%S', time.localtime(self.__startTime)))) self.writeCompetitionTimes(( ('<h1>Kilpailu käynnistetty: ' + time.strftime('%H:%M:%S', time.localtime(self.__startTime))) + "</h1>").replace("ä", "ä")) self._update() #self.timeupdateinterval.start() # f.write("Kilpailu käynnistetty: ") #f.write(time.strftime("%H:%M:%S",time.localtime(self.__startTime))+"\n") #f.close() else: #print("Kilpailu on jo käynnissä!") self.writeToLog("Kilpailu on jo käynnissä!") def lopetakilpailu(self): #return if self.__competition_running == True: ans = askyesno('Veryfy exit', 'Oletko varma, että haluat pysäyttää ajastimen?') else: return if ans == False: return try: self.dbase.close() except: print("Ei määritettyä tietokantaa") if self.__competition_running == True: self.startbutton['state'] = tk.NORMAL self.__endTime = time.time() self.__competition_running = False #print("Kilpailu pysäytetty: "+time.strftime("%H:%M:%S",time.localtime(self.__endTime))) self.writeToLog( ("Kilpailu pysäytetty: " + time.strftime("%H:%M:%S", time.localtime(self.__endTime)))) kesto = self.__endTime - self.__startTime #print("Kilpailu kesti: ", time.strftime("%H:%M:%S",time.gmtime(kesto))) self.writeToLog(("Kilpailu kesti: " + time.strftime("%H:%M:%S", time.gmtime(kesto)))) self.timestr.set(time.strftime("%H:%M:%S", time.gmtime(kesto))) self.after_cancel(self._timer) #print("Kilpailu kesti ",kesto/60.0/60," tuntia ",kesto/60.0," minuuttia ja ",kesto," sekuntia") else: self.writeToLog("Kilpailu on jo pysäytetty") print( json.dumps(self.luokat, default=lambda o: o.__dict__, sort_keys=True, indent=4, ensure_ascii=True)) def writeToLog(self, msg): self.numlines = self.log.index('end - 1 line').split('.')[0] self.log['state'] = 'normal' if self.numlines == 24: self.log.delete('1.0', '2.0') if self.log.index('end-1c') != '1.0': self.log.insert('1.0', '\n') self.log.insert('1.0', msg) self.log['state'] = 'disabled' def writeCompetitionTimes(self, msg): f = open('export.txt', 'a') f.write(msg + "\n") f.close() def writeToLogXLSX(self, msg): self.numlines = self.log.index('end - 1 line').split('.')[0] self.log['state'] = 'normal' if self.numlines == 24: self.log.delete('1.0', '2.0') if self.log.index('end-1c') != '1.0': self.log.insert('1.0', '\n') self.log.insert('1.0', msg) self.log['state'] = 'disabled' def ConvertTimeToString(self, aika): return time.strftime("%H:%M:%S", time.gmtime(aika)) def ConvertTimeToStringAccurate(self, aika): return time.strftime("%H:%M:%S", time.gmtime(aika)) + (',%02d' % int( ((aika - int(aika)) * 100))) def didnotfinish(self): if len(self.syotanumero.get()) > 0: for obj in self.competitors: """print(obj.bibnumber)""" if obj.bibnumber == self.syotanumero.get(): """print('sama numero jo kirjattu')""" obj.DNF() self.writeToLog(("%s" % self._competitors_finished) + "\t" + self.syotanumero.get() + "\t" + "DNF" + "\t" + obj.etunimi + "\t" + obj.sukunimi + "\t" + obj.seura + "\t" + obj.kilpasarja) self.syotanumero.set('') break def didnotstart(self): if len(self.syotanumero.get()) > 0: for obj in self.competitors: """print(obj.bibnumber)""" if obj.bibnumber == self.syotanumero.get(): """print('sama numero jo kirjattu')""" obj.DNS() self.writeToLog(("%s" % self._competitors_finished) + "\t" + self.syotanumero.get() + "\t" + "DNS" + "\t" + obj.etunimi + "\t" + obj.sukunimi + "\t" + obj.seura + "\t" + obj.kilpasarja) self.syotanumero.set('') break def getpositionetc(self, competitionnumber): contents = '' retobj = 0 self.temporary2 = sorted(self.competitors, key=attrgetter('totaltime')) self.temporary = sorted(self.temporary2, key=attrgetter('valiaikamaara'), reverse=True) for luokka in self.luokat: position = 0 #contents = str(contents + luokka+"\n") for obj in self.temporary: if luokka == obj.kilpasarja: if (obj.totaltime != 9999999999): position = position + 1 if (position == 1): nr1pos = obj if competitionnumber == obj.bibnumber: columniterator = 0 if (obj.totaltime != 9999999999): contents = str(contents + "(" + str(self._competitors_finished) + "/" + str(self.__kilpailijoita) + ") ") contents = str(contents + " ") else: contents = str(contents + "- ") contents += str("(" + obj.bibnumber + ") ") contents = str(contents + obj.etunimi + " ") contents = str(contents + obj.sukunimi + " ") contents = str(contents + obj.seura + " |") contents = (contents + "sij. " + str(position) + ". " + luokka + " ") + "|" obj.Sijoitus(position) retobj = obj # f.write(obj.bibnumber+" ") if obj.lasttime != 0: #if(position == 1): # nr1pos = obj; if obj.dnf == False: if obj.GetTimeAmount() > 1: if position == 1: contents = str( contents + self.ConvertTimeToString( obj.totaltime) + " ") self.writeCompetitionTimes( ("<dt><strong>" + str(obj.bibnumber) + " " + obj.etunimi + " " + obj.sukunimi + "</strong></dt><dd>" + luokka + "</dd>" + "<dd>Sija: " + str(position) + "</dd><dd>" + obj.seura + "</dd><dd>" + self.ConvertTimeToStringAccurate( obj.totaltime) + "</dd>").replace("ä", "ä")) else: contents = str( contents + self.ConvertTimeToString( obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime) + " ") self.writeCompetitionTimes( ("<dt><strong>" + str(obj.bibnumber) + " " + obj.etunimi + " " + obj.sukunimi + "</strong></dt><dd>" + luokka + "</dd>" + "<dd>Sija: " + str(position) + "</dd><dd>" + obj.seura + "</dd><dd>" + self.ConvertTimeToStringAccurate( obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime) + "</dd>").replace("ä", "ä")) for aika in obj.valiajat: contents = str( contents + self.ConvertTimeToString(aika) + " ") columniterator = columniterator + 1 if len(obj.valiajat) == 1: contents = str(contents + ' ') contents = str(contents + ' ') if len(obj.valiajat) == 2: contents = str(contents + ' ') columniterator = 0 else: if (position == 1): contents = str( contents + self.ConvertTimeToString( obj.totaltime) + " ") self.writeCompetitionTimes( ("<dt><strong>" + str(obj.bibnumber) + " " + obj.etunimi + " " + obj.sukunimi + "</strong></dt><dd>" + luokka + "</dd>" + "<dd>Sija: " + str(position) + "</dd><dd>" + obj.seura + "</dd><dd>" + self.ConvertTimeToStringAccurate( obj.totaltime) + "</dd>").replace("ä", "ä")) else: contents = str( contents + self.ConvertTimeToString( obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime) + " ") self.writeCompetitionTimes( ("<dt><strong>" + str(obj.bibnumber) + " " + obj.etunimi + " " + obj.sukunimi + "</strong></dt><dd>" + luokka + "</dd>" + "<dd>Sija: " + str(position) + "</dd><dd>" + obj.seura + "</dd><dd>" + self.ConvertTimeToStringAccurate( obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime) + "</dd>").replace("ä", "ä")) #print("+" + self.ConvertTimeToString(obj.totaltime - nr1pos.totaltime)) contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + ' ') else: contents = str(contents + "DNF") else: if obj.dnf == True: contents = str(contents + "DNF") else: contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents) #print(contents) self.writeToLog(contents) if mqttsupport == True: if connflag == True: mqttc.publish("competitions/" + self.__competiontName, retobj.toJSON(), qos=1) print(retobj) #print(json.dumps(retobj)) print(retobj.toJSON()) '''print(contents)''' else: print("waiting for connection...") contents = '0' return contents def setfocus(self): """print(len(self.syotanumero.get())) print(self.numero.focus_set())""" self.numero.focus_set() if self.__competition_running == True: if len(self.syotanumero.get()) > 0: self.found = 0 for obj in self.competitors: """print(obj.bibnumber)""" if obj.bibnumber == self.syotanumero.get(): #print("Tasta tuli") """print('sama numero jo kirjattu')""" try: obj.kirjaaAika(self.getKisaaika()) self.found = 1 """self.vaihelaskuri[obj.GetTimeAmount()] = self.vaihelaskuri[obj.GetTimeAmount()]+1""" """self.competitionphase.set("1:"+str(self.vaihelaskuri[1])+"2:"+str(self.vaihelaskuri[2])+"3"+str(self.vaihelaskuri[3]))""" except: print("ei numeroa") break else: self.found = 0 """elif obj.bibnumber != self.syotanumero.get(): kilpatemp = kilpailija('Etunimi','Sukunimi','Puhelinnumero','Seura','M',self.syotanumero.get()) kilpatemp.kirjaaAika(time.strftime("%H:%M:%S",time.gmtime(self.getKisaaika()))) self.competitors.append(kilpatemp) print('kilpailijaa ei ole, lisätään') print(len(self.competitors)) break""" if self.found == 0: self.kilpatemp = kilpailija('Etunimi', 'Sukunimi', '-', '-', 'Tarkistettavat:', self.syotanumero.get()) self.__kilpailijoita = self.__kilpailijoita + 1 for luokka in self.luokat: if luokka == 'Tarkistettavat:': self.luokkafound = 1 break if self.luokkafound == 0: self.luokat.append('Tarkistettavat:') else: self.luokkafound = 0 self.kilpatemp.kirjaaAika(self.getKisaaika()) obj = self.kilpatemp self.competitors.append(self.kilpatemp) if obj.GetTimeAmount() > 0: self._competitors_finished = self._competitors_finished + 1 #self.writeToLog(self.syotanumero.get()+"\t"+self.ConvertTimeToStringAccurate(obj.totaltime)+"\t"+obj.etunimi+"\t"+obj.sukunimi+"\t"+obj.seura+"\t"+obj.kilpasarja) #self.writeCompetitionTimes(self.syotanumero.get()+"\t"+self.ConvertTimeToString(obj.totaltime)+"\t"+self.ConvertTimeToStringAccurate(obj.totaltime)+"\t"+obj.etunimi+"\t"+obj.sukunimi+"\t"+obj.seura+"\t"+obj.kilpasarja) self.strfinished.set(("#%s" % self._competitors_finished)) self.getpositionetc(self.syotanumero.get()) try: self.dbase = shelve.open('backlog/backlog') self.dbase[self.syotanumero.get()] = obj print("db update") except: print("Tietokannanlatausvirhe") #self.writeCompetitionTimes(("<dt><strong>"+self.syotanumero.get()+" "+obj.etunimi+" "+obj.sukunimi+"</strong></dt><dd>"+obj.kilpasarja+"</dd><dd>"+obj.seura+"</dd><dd>"+self.ConvertTimeToStringAccurate(obj.totaltime)+"</dd>").replace("ä","ä")) self.syotanumero.set('') self.strfinished.set('') #self. '''self.competitionphase.set('narf')''' self.writeHTML() """ self.competitors.append(kilpailija('Etunimi','Sukunimi','Puhelinnumero','Seura','Sarja','M',time.strftime("%H:%M:%S",time.gmtime(self.getKisaaika()))))""" """ for obj in self.competitors: print(obj)""" else: self.writeToLog("Kilpailu ei ole käynnissä") def addline(self): self.writeToLog('-------------------------------------') #print(self.log.get(END)) def getname(self): kirjauslkm = 0 if len(self.syotanumero.get()) > 0: self.found = 0 for obj in self.competitors: """print(obj.bibnumber)""" if obj.bibnumber == self.syotanumero.get(): kirjauslkm = len(obj.ajat) + 1 if kirjauslkm == self._timeamount: self.strfinished.set(('MAALIKIRJAUS! V.aika %d:' % kirjauslkm) + obj.etunimi + " " + obj.sukunimi + ", " + obj.seura + ", " + obj.kilpasarja) else: self.strfinished.set(('V.aika %d:' % kirjauslkm) + obj.etunimi + " " + obj.sukunimi + ", " + obj.seura + ", " + obj.kilpasarja) break else: self.strfinished.set('Numeroa ei ole') else: self.strfinished.set('') def writeHTML(self): return 0 h = HTML() h.h1(self.__competiontName) h.p("Ajat") t = h.table(border='1', width="100%") # for i in range(2): r = t.tr() r.td('') r.td('') r.td('') r.td('') r.td('#') r.td('Total') r.td('Time1') r.td('Time2') r.td('Time3') self.temporary2 = sorted(self.competitors, key=attrgetter('totaltime')) self.temporary = sorted(self.temporary2, key=attrgetter('valiaikamaara'), reverse=True) for luokka in self.luokat: r = t.tr r.td(luokka, colspan="9", bgcolor="grey", style="color:white") position = 0 for obj in self.temporary: if luokka == obj.kilpasarja: r = t.tr columniterator = 0 if obj.totaltime != 9999999999: #print(obj.totaltime) position = position + 1 r.td(str(position)) else: r.td("-") r.td(obj.etunimi) r.td(obj.sukunimi) r.td(obj.seura) r.td(obj.bibnumber) if obj.lasttime != 0: if (position == 1): nr1pos = obj if obj.dnf == False: if obj.GetTimeAmount() > 1: if (position == 1): r.td( self.ConvertTimeToString( obj.totaltime)) else: r.td( self.ConvertTimeToString(obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime)) for aika in obj.valiajat: r.td(self.ConvertTimeToString(aika)) columniterator = columniterator + 1 if (len(obj.valiajat) == 1): r.td('') r.td('') if (len(obj.valiajat) == 2): r.td('') columniterator = 0 else: if (position == 1): r.td( self.ConvertTimeToString( obj.totaltime)) else: r.td( self.ConvertTimeToString(obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime)) #print("+" + self.ConvertTimeToString(obj.totaltime - nr1pos.totaltime)) r.td('') r.td('') r.td('') else: r.td("DNF") else: if obj.dnf == True: r.td("DNF") else: r.td('') r.td('') r.td('') r.td('') # ws2.cell('F%d'%(rowiterator+6)).value = "DNF" # rowiterator = rowiterator+1 # print(fpart + str(h) + lpart) f = open('data.html', 'w') # f.write(fpart + script + str(h) + lpart) unikoodi = str(h).replace("ä", "ä") f.write(unikoodi.replace("ö", "ö")) f.close() def writetoxlsx(self): rowiterator = 1 columniterator = 0 wb = Workbook() #ws = wb.get_active_sheet() #ws2 = wb.get_active_sheet() #wb.create_sheet(0) # insert at first position ws2 = wb.active ws2.title = "Kilpailudatat" ws2['A1'].value = 'Paikkakunta' ws2.cell(1, 2, time.strftime("%d.%m.%Y", time.localtime(time.time()))) ws2.cell(3, 1, self.__competiontName) ws2.cell( 4, 1, 'Seinäjoki ' + time.strftime("%d.%m.%Y", time.localtime(time.time()))) ws2.cell(5, 5, 'Bibnumber') ws2.cell(5, 6, 'Total') '''ws2.cell('A4').value = 'Seinäjoki ' + time.strftime("%d.%m.%Y",time.localtime(time.time())) ws2.cell('E5').value = 'Numero' ws2.cell('F5').value = 'Total' ''' #ws2.cell('G5').value = 'Time1' #ws2.cell('H5').value = 'Time2' #ws2.cell('I5').value = 'Time3' #self.temporary = sorted(self.competitors, key=lambda kilpailija: kilpailija.totaltime) #self.temporary = sorted(self.competitors, key=lambda kilpailija: kilpailija.totaltime) #self.temporary2 = sorted(self.competitors, key=lambda kilpailija: kilpailija.valiaikamaara, reverse=True) self.temporary2 = sorted(self.competitors, key=attrgetter('totaltime')) self.temporary = sorted(self.temporary2, key=attrgetter('valiaikamaara'), reverse=True) for luokka in self.luokat: ws2.cell(row=(rowiterator + 6), column=1, value=luokka) rowiterator = rowiterator + 1 position = 0 for obj in self.temporary: if luokka == obj.kilpasarja: columniterator = 0 if obj.totaltime != 9999999999: position = position + 1 ws2.cell((rowiterator + 6), 1, position) else: ws2.cell((rowiterator + 6), 1, "-") ws2.cell((rowiterator + 6), 2, obj.etunimi) ws2.cell((rowiterator + 6), 3, obj.sukunimi) ws2.cell((rowiterator + 6), 4, obj.seura) ws2.cell((rowiterator + 6), 5, obj.bibnumber) if obj.lasttime != 0: if (position == 1): nr1pos = obj if obj.dnf == False: if obj.GetTimeAmount() > 1: ws2.cell(row=6, column=obj.GetTimeAmount() + 6).value = obj.GetTimeAmount() ws2.cell( row=rowiterator + 6, column=6 ).value = self.ConvertTimeToStringAccurate( obj.totaltime) for aika in obj.valiajat: ws2.cell( row=rowiterator + 6, column=columniterator + 7 ).value = self.ConvertTimeToStringAccurate( aika) columniterator = columniterator + 1 columniterator = 0 else: ws2.cell((rowiterator + 6), 5, self.ConvertTimeToStringAccurate( obj.totaltime)) else: ws2.cell((rowiterator + 6), 5, "DNF") else: if obj.dnf == True: ws2.cell((rowiterator + 6), 5, "DNF") rowiterator = rowiterator + 1 wb.save( "xlsx/" + self.__competiontName + '_' + time.strftime("%d_%m_%y_%H_%M_%S", time.localtime(time.time())) + '.xlsx') def reallyquit(self): if self.__competition_running == False: ans = askyesno('Veryfy exit', 'Oletko varma, että haluat lopettaa ohjelman.') if ans == True: # if xlsxsupport == True: # self.writetoxlsx() self.writeHTML() self.writeOfficialTimes(0, False) self.dbase = shelve.open('backlog/backlog') for obj in self.competitors: self.dbase[obj.bibnumber] = obj self.dbase.close() exit(0) else: self.writeToLog( 'Ohjelmaa ei voida sammuttaa kilpailun ollessa käynnissä') def writetotxt(self): f = open( self.__competiontName + '_' + time.strftime("%d_%m_%y_%H_%M_%S", time.localtime(time.time())) + '.txt', 'a') f.write(self.log.get('1.0', END)) f.close() def writeOfficialTimes(self, tyyppi, query): if query == False: f = open( "results/" + str(self.__competiontName) + 'results_' + time.strftime("%d_%m_%y_%H_%M_%S", time.localtime( time.time())) + '.txt', 'a') if query == True: f = asksaveasfilename() #contents = str('Tilasto_' + time.strftime("%d_%m_%y_%H_%M_%S",time.localtime(time.time())) + '.txt') contents = str(self.__competiontName) + str(' ' + time.strftime( "%y_%m_%d %H.%M.%S", time.localtime(time.time())) + '\n\n') self.temporary2 = sorted(self.competitors, key=attrgetter('totaltime')) self.temporary = sorted(self.temporary2, key=attrgetter('valiaikamaara'), reverse=True) for luokka in self.luokat: position = 0 contents = str(contents + luokka + "\n") for obj in self.temporary: if luokka == obj.kilpasarja: columniterator = 0 if (obj.totaltime != 9999999999): position = position + 1 contents = str(contents + str(position)) contents = str(contents + " ") else: contents = str(contents + "- ") if tyyppi == 1: contents += str("(" + obj.bibnumber + ") ") contents = str(contents + obj.etunimi + " ") contents = str(contents + obj.sukunimi + " ") contents = str(contents + obj.seura + " ") # f.write(obj.bibnumber+" ") if obj.lasttime != 0: if (position == 1): nr1pos = obj if obj.dnf == False: if obj.GetTimeAmount() > 1: contents = str( contents + self.ConvertTimeToString(obj.totaltime) + " ") for aika in obj.valiajat: contents = str( contents + self.ConvertTimeToString(aika) + " ") columniterator = columniterator + 1 if (len(obj.valiajat) == 1): contents = str(contents + ' ') contents = str(contents + ' ') if (len(obj.valiajat) == 2): contents = str(contents + ' ') columniterator = 0 else: if (position == 1): contents = str(contents + self.ConvertTimeToString( obj.totaltime) + " ") else: contents = str(contents + self.ConvertTimeToString( obj.totaltime) + " +" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime) + " ") print("+" + self.ConvertTimeToString( obj.totaltime - nr1pos.totaltime)) contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + ' ') else: contents = str(contents + "DNF") else: if obj.dnf == True: contents = str(contents + "DNF") else: contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + ' ') contents = str(contents + '\n') contents = str(contents + '\n') print(contents) self.writeToLog(contents) f.write(contents) contents = '0' f.close()
class TextEditor: # mix with menu/toolbar Frame class startfiledir = "." # for dialogs editwindows = [] # for process-wide quit check ftypes = [ ("All files", "*"), # for file open dialog ("Text files", ".txt"), # customize in subclass ("Python files", ".py"), ] # or set in each instance colors = [ {"fg": "black", "bg": "white"}, # color pick list {"fg": "yellow", "bg": "black"}, # first item is default {"fg": "white", "bg": "blue"}, # tailor me as desired {"fg": "black", "bg": "beige"}, # or do PickBg/Fg chooser {"fg": "yellow", "bg": "purple"}, {"fg": "black", "bg": "brown"}, {"fg": "lightgreen", "bg": "darkgreen"}, {"fg": "darkblue", "bg": "orange"}, {"fg": "orange", "bg": "darkblue"}, ] fonts = [ ("courier", 9 + FontScale, "normal"), # platform-neutral fonts ("courier", 12 + FontScale, "normal"), # (family, size, style) ("courier", 10 + FontScale, "bold"), # or pop up a listbox ("courier", 10 + FontScale, "italic"), # make bigger on Linux ("times", 10 + FontScale, "normal"), # use 'bold italic' for 2 ("helvetica", 10 + FontScale, "normal"), # also 'underline', etc. ("ariel", 10 + FontScale, "normal"), ("system", 10 + FontScale, "normal"), ("courier", 20 + FontScale, "normal"), ] def __init__(self, loadFirst=""): if not isinstance(self, GuiMaker): raise TypeError("TextEditor needs a GuiMaker mixin") self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.text.focus() # else must click in text if loadFirst: self.update() # 2.1: else @ line 2; see book self.onOpen(loadFirst) def start(self): # run by GuiMaker.__init__ self.menuBar = [ # configure menu/toolbar ( "File", 0, # a GuiMaker menu def tree [ ("Open...", 0, self.onOpen), # build in method for self ("Save", 0, self.onSave), # label, shortcut, callback ("Save As...", 5, self.onSaveAs), ("New", 0, self.onNew), "separator", ("Quit...", 0, self.onQuit), ], ), ( "Edit", 0, [ ("Undo", 0, self.onUndo), ("Redo", 0, self.onRedo), "separator", ("Cut", 0, self.onCut), ("Copy", 1, self.onCopy), ("Paste", 0, self.onPaste), "separator", ("Delete", 0, self.onDelete), ("Select All", 0, self.onSelectAll), ], ), ( "Search", 0, [ ("Goto...", 0, self.onGoto), ("Find...", 0, self.onFind), ("Refind", 0, self.onRefind), ("Change...", 0, self.onChange), ("Grep...", 3, self.onGrep), ], ), ( "Tools", 0, [ ("Pick Font...", 6, self.onPickFont), ("Font List", 0, self.onFontList), "separator", ("Pick Bg...", 3, self.onPickBg), ("Pick Fg...", 0, self.onPickFg), ("Color List", 0, self.onColorList), "separator", ("Info...", 0, self.onInfo), ("Clone", 1, self.onClone), ("Run Code", 0, self.onRunCode), ], ), ] self.toolBar = [ ("Save", self.onSave, {"side": LEFT}), ("Cut", self.onCut, {"side": LEFT}), ("Copy", self.onCopy, {"side": LEFT}), ("Paste", self.onPaste, {"side": LEFT}), ("Find", self.onRefind, {"side": LEFT}), ("Help", self.help, {"side": RIGHT}), ("Quit", self.onQuit, {"side": RIGHT}), ] def makeWidgets(self): # run by GuiMaker.__init__ name = Label(self, bg="black", fg="white") # add below menu, above tool name.pack(side=TOP, fill=X) # menu/toolbars are packed # GuiMaker frame packs itself vbar = Scrollbar(self) hbar = Scrollbar(self, orient="horizontal") text = Text(self, padx=5, wrap="none") # disable line wrapping text.config(undo=1, autoseparators=1) # 2.0, default is 0, 1 vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) # pack text last text.pack(side=TOP, fill=BOTH, expand=YES) # else sbars clipped text.config(yscrollcommand=vbar.set) # call vbar.set on text move text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) # call text.yview on scroll move hbar.config(command=text.xview) # or hbar['command']=text.xview # 2.0: apply user configs or defaults startfont = configs.get("font", self.fonts[0]) startbg = configs.get("bg", self.colors[0]["bg"]) startfg = configs.get("fg", self.colors[0]["fg"]) text.config(font=startfont, bg=startbg, fg=startfg) if "height" in configs: text.config(height=configs["height"]) if "width" in configs: text.config(width=configs["width"]) self.text = text self.filelabel = name ############################################################################ # File menu commands ############################################################################ def my_askopenfilename(self): # objects remember last result dir/file if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): # objects remember last result dir/file if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst=""): doit = not self.text_edit_modified() or askyesno("PyEdit", "Text has changed: discard changes?") # 2.0 if doit: file = loadFirst or self.my_askopenfilename() if file: try: text = open(file, "r").read() except: showerror("PyEdit", "Could not open file " + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onSave(self): self.onSaveAs(self.currfile) # may be None def onSaveAs(self, forcefile=None): file = forcefile or self.my_asksaveasfilename() if file: text = self.getAllText() try: open(file, "w").write(text) except: showerror("PyEdit", "Could not write file " + file) else: self.setFileName(file) # may be newly created self.text.edit_modified(0) # 2.0: clear modified flag # don't clear undo/redo stks def onNew(self): doit = not self.text_edit_modified() or askyesno("PyEdit", "Text has changed: discard changes?") # 2.0 if doit: self.setFileName(None) self.clearAllText() self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onQuit(self): """ on Quit menu/toolbar select and wm border X button in toplevel windows; 2.1: don't exit app if others changed; 2.0: don't ask if self unchanged; moved to the top-level window classes at the end since may vary per usage: a Quit in GUI might quit() to exit, destroy() just one Toplevel, Tk, or edit frame, or not be provided at all when run as an attached component; check self for changes, and if might quit(), main windows should check other windows in the process-wide list to see if they have changed too; """ assert False, "onQuit must be defined in window-specific sublass" def text_edit_modified(self): """ 2.1: this now works! seems to have been a bool result type issue in tkinter; 2.0: self.text.edit_modified() broken in Python 2.4: do manually for now; """ return self.text.edit_modified() # return self.tk.call((self.text._w, 'edit') + ('modified', None)) ############################################################################ # Edit menu commands ############################################################################ def onUndo(self): # 2.0 try: # tk8.4 keeps undo/redo stacks self.text.edit_undo() # exception if stacks empty except TclError: # menu tear-offs for quick undo showinfo("PyEdit", "Nothing to undo") def onRedo(self): # 2.0: redo an undone try: self.text.edit_redo() except TclError: showinfo("PyEdit", "Nothing to redo") def onCopy(self): # get text selected by mouse, etc. if not self.text.tag_ranges(SEL): # save in cross-app clipboard showerror("PyEdit", "No text selected") else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): # delete selected text, no save if not self.text.tag_ranges(SEL): showerror("PyEdit", "No text selected") else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror("PyEdit", "No text selected") else: self.onCopy() # save and delete selected text self.onDelete() def onPaste(self): try: text = self.selection_get(selection="CLIPBOARD") except TclError: showerror("PyEdit", "Nothing to paste") return self.text.insert(INSERT, text) # add at current insert cursor self.text.tag_remove(SEL, "1.0", END) self.text.tag_add(SEL, INSERT + "-%dc" % len(text), INSERT) self.text.see(INSERT) # select it, so it can be cut def onSelectAll(self): self.text.tag_add(SEL, "1.0", END + "-1c") # select entire text self.text.mark_set(INSERT, "1.0") # move insert point to top self.text.see(INSERT) # scroll to top ############################################################################ # Search menu commands ############################################################################ def onGoto(self, forceline=None): line = forceline or askinteger("PyEdit", "Enter line number") self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END + "-1c") maxline = int(maxindex.split(".")[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, "%d.0" % line) # goto line self.text.tag_remove(SEL, "1.0", END) # delete selects self.text.tag_add(SEL, INSERT, "insert + 1l") # select line self.text.see(INSERT) # scroll to line else: showerror("PyEdit", "Bad line number") def onFind(self, lastkey=None): key = lastkey or askstring("PyEdit", "Enter search string") self.text.update() self.text.focus() self.lastfind = key if key: # 2.0: nocase nocase = configs.get("caseinsens", True) # 2.0: config where = self.text.search(key, INSERT, END, nocase=nocase) if not where: # don't wrap showerror("PyEdit", "String not found") else: pastkey = where + "+%dc" % len(key) # index past key self.text.tag_remove(SEL, "1.0", END) # remove any sel self.text.tag_add(SEL, where, pastkey) # select key self.text.mark_set(INSERT, pastkey) # for next find self.text.see(where) # scroll display def onRefind(self): self.onFind(self.lastfind) def onChange(self): """ non-modal find/change dialog 2.1: pass per-dialog inputs to callbacks, may be > 1 change dialog open """ new = Toplevel(self) new.title("PyEdit - change") Label(new, text="Find text?", relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text="Change to?", relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): # use my entry in enclosing scope self.onFind(entry1.get()) # runs normal find dialog callback def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text="Find", command=onFind).grid(row=0, column=2, sticky=EW) Button(new, text="Apply", command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) # expandable entries def onDoChange(self, findtext, changeto): # on Apply in change dialog: change and refind if self.text.tag_ranges(SEL): # must find first self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) # deletes if empty self.text.see(INSERT) self.onFind(findtext) # goto next appear self.text.update() # force refresh def onGrep(self): """ new in version 2.1: external file search search matched filenames in directory tree for string; listbox clicks open matched file at line of occurrence; caveat: search blocks GUI - should use thread and queue; caveat: this is not very robust - exceptions not caught; """ from PP4E.Gui.ShellGui.formrows import makeFormRow # nonmodal dialog: get dirnname, filenamepatt, grepkey popup = Toplevel() popup.title("PyEdit - grep") var1 = makeFormRow(popup, label="Directory root", width=18, browse=False) var2 = makeFormRow(popup, label="Filename pattern", width=18, browse=False) var3 = makeFormRow(popup, label="Search string", width=18, browse=False) var1.set(".") # current dir var2.set("*.py") # initial values Button(popup, text="Go", command=lambda: self.onDoGrep(var1.get(), var2.get(), var3.get())).pack() def onDoGrep(self, dirname, filenamepatt, grepkey): # on Go in grep dialog: populate scrolled list with matches from PP4E.Tools.find import find from PP4E.Gui.Tour.scrolledlist import ScrolledList class ScrolledFilenames(ScrolledList): def runCommand(self, selection): # on list double-click file, line = selection.split(" [", 1)[0].split("@") editor = TextEditorMainPopup(loadFirst=file, winTitle=" grep match") editor.onGoto(int(line)) editor.text.focus_force() # no, really # should thread/queue/after me showinfo("PyEdit Wait", "Ready to search files (a pause may follow)...") matches = [] for filepath in find(pattern=filenamepatt, startdir=dirname): try: for (linenum, linestr) in enumerate(open(filepath)): if grepkey in linestr: matches.append("%s@%d [%s]" % (filepath, linenum + 1, linestr)) except: print("Failed:", filepath) # Unicode errors, probably if not matches: showinfo("PyEdit", "No matches found") else: popup = Tk() popup.title("PyEdit - grep matches: %r" % grepkey) ScrolledFilenames(parent=popup, options=matches) ############################################################################ # Tools menu commands ############################################################################ def onFontList(self): self.fonts.append(self.fonts[0]) # pick next font in list del self.fonts[0] # resizes the text area self.text.config(font=self.fonts[0]) def onColorList(self): self.colors.append(self.colors[0]) # pick next color in list del self.colors[0] # move current to end self.text.config(fg=self.colors[0]["fg"], bg=self.colors[0]["bg"]) def onPickFg(self): self.pickColor("fg") # added on 10/02/00 def onPickBg(self): # select arbitrary color self.pickColor("bg") # in standard color dialog def pickColor(self, part): # this is too easy (triple, hexstr) = askcolor() if hexstr: self.text.config(**{part: hexstr}) def onInfo(self): text = self.getAllText() # added on 5/3/00 in 15 mins bytes = len(text) # words uses a simple guess: lines = len(text.split("\n")) # any separated by whitespace words = len(text.split()) # 3.x: bytes is really chars index = self.text.index(INSERT) # str is unicode code points where = tuple(index.split(".")) showinfo( "PyEdit Information", "Current location:\n\n" + "line:\t%s\ncolumn:\t%s\n\n" % where + "File text statistics:\n\n" + "chars:\t%d\nlines:\t%d\nwords:\t%d\n" % (bytes, lines, words), ) def onClone(self): """ open a new edit window without changing one already open inherits quit and other behavior of window that it clones """ new = Toplevel() # a new edit window in same process myclass = self.__class__ # instance's (lowest) class object myclass(new) # attach/run instance of my class def onRunCode(self, parallelmode=True): """ run Python code being edited--not an IDE, but handy; tries to run in file's dir, not cwd (may be PP4E root); inputs and adds command-line arguments for script files; code's stdin/out/err = editor's start window, if any: run with a console window to see code's print outputs; but parallelmode uses start to open a DOS box for I/O; module search path will include '.' dir where started; in non-file mode, code's Tk root may be PyEdit's window; subprocess or multiprocessing modules may work here too; 2.1: fixed to use base file name after chdir, not path; 2.1: use StartArs to allow args in file mode on Windows; """ def askcmdargs(): return askstring("PyEdit", "Commandline arguments?") or "" from PP4E.launchmodes import System, Start, StartArgs, Fork filemode = False thefile = str(self.getFileName()) if os.path.exists(thefile): filemode = askyesno("PyEdit", "Run from file?") if not filemode: # run text string cmdargs = askcmdargs() namespace = {"__name__": "__main__"} # run as top-level sys.argv = [thefile] + cmdargs.split() # could use threads exec(self.getAllText() + "\n", namespace) # exceptions ignored elif self.text_edit_modified(): # 2.0: changed test showerror("PyEdit", "Text changed: save before run") else: cmdargs = askcmdargs() mycwd = os.getcwd() # cwd may be root dirname, filename = os.path.split(thefile) # get dir, base os.chdir(dirname or mycwd) # cd for filenames thecmd = filename + " " + cmdargs # 2.1: not theFile if not parallelmode: # run as file System(thecmd, thecmd)() # block editor else: if sys.platform[:3] == "win": # spawn in parallel run = StartArgs if cmdargs else Start # 2.1: support args run(thecmd, thecmd)() # or always Spawn else: Fork(thecmd, thecmd)() # spawn in parallel os.chdir(mycwd) # go back to my dir def onPickFont(self): """ 2.0 non-modal font spec dialog 2.1: pass per-dialog inputs to callback, may be > 1 font dialog open """ from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel(self) popup.title("PyEdit - font") var1 = makeFormRow(popup, label="Family", browse=False) var2 = makeFormRow(popup, label="Size", browse=False) var3 = makeFormRow(popup, label="Style", browse=False) var1.set("courier") var2.set("12") # suggested vals var3.set("bold italic") # see pick list for valid inputs Button(popup, text="Apply", command=lambda: self.onDoFont(var1.get(), var2.get(), var3.get())).pack() def onDoFont(self, family, size, style): try: self.text.config(font=(family, int(size), style)) except: showerror("PyEdit", "Bad font specification") ############################################################################ # Utilities, useful outside this class ############################################################################ def isEmpty(self): return not self.getAllText() def getAllText(self): return self.text.get("1.0", END + "-1c") # extract text as a string def setAllText(self, text): self.text.delete("1.0", END) # store text string in widget self.text.insert(END, text) # or '1.0' self.text.mark_set(INSERT, "1.0") # move insert point to top self.text.see(INSERT) # scroll to top, insert set def clearAllText(self): self.text.delete("1.0", END) # clear text in widget def getFileName(self): return self.currfile def setFileName(self, name): # also: onGoto(linenum) self.currfile = name # for save self.filelabel.config(text=str(name)) def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) # 'black', hexstring def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo("About PyEdit", helptext % ((Version,) * 2))
class TextEditor: # mix with menu/toolbar Frame class startfiledir = '.' # for dialogs editwindows = [] # for process-wide quit check ftypes = [('All files', '*'), # for file open dialog ('Text files', '.txt'), # customize in subclass ('Python files', '.py')] # or set in each instance colors = [{'fg':'black', 'bg':'white'}, # color pick list {'fg':'yellow', 'bg':'black'}, # first item is default {'fg':'white', 'bg':'blue'}, # tailor me as desired {'fg':'black', 'bg':'beige'}, # or do PickBg/Fg chooser {'fg':'yellow', 'bg':'purple'}, {'fg':'black', 'bg':'brown'}, {'fg':'lightgreen', 'bg':'darkgreen'}, {'fg':'darkblue', 'bg':'orange'}, {'fg':'orange', 'bg':'darkblue'}] fonts = [('courier', 9+FontScale, 'normal'), # platform-neutral fonts ('courier', 12+FontScale, 'normal'), # (family, size, style) ('courier', 10+FontScale, 'bold'), # or pop up a listbox ('courier', 10+FontScale, 'italic'), # make bigger on Linux ('times', 10+FontScale, 'normal'), # use 'bold italic' for 2 ('helvetica', 10+FontScale, 'normal'), # also 'underline', etc. ('ariel', 10+FontScale, 'normal'), ('system', 10+FontScale, 'normal'), ('courier', 20+FontScale, 'normal')] def __init__(self, loadFirst=''): if not isinstance(self, GuiMaker): raise TypeError('TextEditor needs a GuiMaker mixin') self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.text.focus() if loadFirst: self.update() self.onOpen(loadFirst) def start(self): self.menuBar = [ ('File', 0, [('Open...', 0, self.onOpen), ('Save', 0, self.onSave), ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Quit...', 0, self.onQuit)] ), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)] ), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Change...', 0, self.onChange), ('Grep...', 3, self.onGrep)] ), ('Tools', 0, [('Pick Font...', 6, self.onPickFont), ('Font List', 0, self.onFontList), 'separator', ('Pick Bg...', 3, self.onPickBg), ('Pick Fg...', 0, self.onPickFg), ('Color List', 0, self.onColorList), 'separator', ('Info...', 0, self.onInfo), ('Clone', 1, self.onClone), ('Run Code', 0, self.onRunCode)] )] self.toolBar = [ ('Save', self.onSave, {'side': LEFT}), ('Cut', self.onCut, {'side': LEFT}), ('Copy', self.onCopy, {'side': LEFT}), ('Paste', self.onPaste, {'side': LEFT}), ('Find', self.onRefind, {'side': LEFT}), ('Help', self.help, {'side': RIGHT}), ('Quit', self.onQuit, {'side': RIGHT})] def makeWidgets(self): name = Label(self, bg='black', fg='white') name.pack(side=TOP, fill=X) vbar = Scrollbar(self) hbar = Scrollbar(self, orient='horizontal') text = Text(self, padx=5, wrap='none') text.config(undo=1, autoseparators=1) vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) text.pack(side=TOP, fill=BOTH, expand=YES) text.config(yscrollcommand=vbar.set) text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) hbar.config(command=text.xview) startfont = configs.get('font', self.fonts[0]) startbg = configs.get('bg', self.colors[0]['bg']) startfg = configs.get('fg', self.colors[0]['fg']) text.config(font=startfont, bg=startbg, fg=startfg) if 'height' in configs: text.config(height=configs['height']) if 'width' in configs: text.config(width =configs['width']) self.text = text self.filelabel = name def my_askopenfilename(self): if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst=''): doit = (not self.text_edit_modified() or askyesno('SimpleEditor', 'Text has changed: discard changes?')) if doit: file = loadFirst or self.my_askopenfilename() if file: try: text = open(file, 'r').read() except: showerror('SimpleEditor', 'Could not open file ' + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() self.text.edit_modified(0) def onSave(self): self.onSaveAs(self.currfile) def onSaveAs(self, forcefile=None): file = forcefile or self.my_asksaveasfilename() if file: text = self.getAllText() try: open(file, 'w').write(text) except: showerror('SimpleEditor', 'Could not write file ' + file) else: self.setFileName(file) # may be newly created self.text.edit_modified(0) def onNew(self): doit = (not self.text_edit_modified() or askyesno('SimpleEditor', 'Text has changed: discard changes?')) if doit: self.setFileName(None) self.clearAllText() self.text.edit_reset() self.text.edit_modified(0) def onQuit(self): assert False, 'onQuit must be defined in window-specific sublass' def text_edit_modified(self): return self.text.edit_modified() def onUndo(self): try: self.text.edit_undo() except TclError: showinfo('SimpleEditor', 'Nothing to undo') def onRedo(self): try: self.text.edit_redo() except TclError: showinfo('SimpleEditor', 'Nothing to redo') def onCopy(self): if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: self.onCopy() self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror('SimpleEditor', 'Nothing to paste') return self.text.insert(INSERT, text) # add at current insert cursor self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT+'-%dc' % len(text), INSERT) self.text.see(INSERT) # select it, so it can be cut def onSelectAll(self): self.text.tag_add(SEL, '1.0', END+'-1c') self.text.mark_set(INSERT, '1.0') self.text.see(INSERT) def onGoto(self, forceline=None): line = forceline or askinteger('SimpleEditor', 'Enter line number') self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END+'-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, '%d.0' % line) self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT, 'insert + 1l') self.text.see(INSERT) else: showerror('SimpleEditor', 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring('SimpleEditor', 'Enter search string') self.text.update() self.text.focus() self.lastfind = key if key: nocase = configs.get('caseinsens', True) where = self.text.search(key, INSERT, END, nocase=nocase) if not where: showerror('SimpleEditor', 'String not found') else: pastkey = where + '+%dc' % len(key) self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, where, pastkey) self.text.mark_set(INSERT, pastkey) self.text.see(where) def onRefind(self): self.onFind(self.lastfind) def onChange(self): new = Toplevel(self) new.title('SimpleEditor - change') Label(new, text='Find text?', relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text='Change to?', relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): self.onFind(entry1.get()) def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text='Find', command=onFind ).grid(row=0, column=2, sticky=EW) Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) def onDoChange(self, findtext, changeto): if self.text.tag_ranges(SEL): self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) self.text.see(INSERT) self.onFind(findtext) self.text.update() def onGrep(self): from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel() popup.title('SimpleEditor - grep') var1 = makeFormRow(popup, label='Directory root', width=18, browse=False) var2 = makeFormRow(popup, label='Filename pattern', width=18, browse=False) var3 = makeFormRow(popup, label='Search string', width=18, browse=False) var1.set('.') var2.set('*.py') Button(popup, text='Go', command=lambda: self.onDoGrep(var1.get(), var2.get(), var3.get())).pack() def onDoGrep(self, dirname, filenamepatt, grepkey): from PP4E.Tools.find import find from PP4E.Gui.Tour.scrolledlist import ScrolledList class ScrolledFilenames(ScrolledList): def runCommand(self, selection): file, line = selection.split(' [', 1)[0].split('@') editor = TextEditorMainPopup(loadFirst=file, winTitle=' grep match') editor.onGoto(int(line)) editor.text.focus_force() showinfo('SimpleEditor Wait', 'Ready to search files (a pause may follow)...') matches = [] for filepath in find(pattern=filenamepatt, startdir=dirname): try: for (linenum, linestr) in enumerate(open(filepath)): if grepkey in linestr: matches.append('%s@%d [%s]' % (filepath, linenum + 1, linestr)) except: print('Failed:', filepath) # Unicode errors, probably if not matches: showinfo('SimpleEditor', 'No matches found') else: popup = Tk() popup.title('SimpleEditor - grep matches: %r' % grepkey) ScrolledFilenames(parent=popup, options=matches) def onFontList(self): self.fonts.append(self.fonts[0]) del self.fonts[0] self.text.config(font=self.fonts[0]) def onColorList(self): self.colors.append(self.colors[0]) del self.colors[0] self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg']) def onPickFg(self): self.pickColor('fg') def onPickBg(self): # select arbitrary color self.pickColor('bg') # in standard color dialog def pickColor(self, part): (triple, hexstr) = askcolor() if hexstr: self.text.config(**{part: hexstr}) def onInfo(self): text = self.getAllText() bytes = len(text) lines = len(text.split('\n')) # any separated by whitespace words = len(text.split()) index = self.text.index(INSERT) # str is unicode code points where = tuple(index.split('.')) showinfo('SimpleEditor Information', 'Current location:\n\n' + 'line:\t%s\ncolumn:\t%s\n\n' % where + 'File text statistics:\n\n' + 'chars:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words)) def onClone(self): """ open a new edit window without changing one already open inherits quit and other behavior of window that it clones """ new = Toplevel() # a new edit window in same process myclass = self.__class__ # instance's (lowest) class object myclass(new) # attach/run instance of my class def onRunCode(self, parallelmode=True): def askcmdargs(): return askstring('SimpleEditor', 'Commandline arguments?') or '' from PP4E.launchmodes import System, Start, StartArgs, Fork filemode = False thefile = str(self.getFileName()) if os.path.exists(thefile): filemode = askyesno('SimpleEditor', 'Run from file?') if not filemode: cmdargs = askcmdargs() namespace = {'__name__': '__main__'} sys.argv = [thefile] + cmdargs.split() exec(self.getAllText() + '\n', namespace) elif self.text_edit_modified(): showerror('SimpleEditor', 'Text changed: save before run') else: cmdargs = askcmdargs() mycwd = os.getcwd() # cwd may be root dirname, filename = os.path.split(thefile) # get dir, base os.chdir(dirname or mycwd) thecmd = filename + ' ' + cmdargs if not parallelmode: # run as file System(thecmd, thecmd)() # block editor else: if sys.platform[:3] == 'win': # spawn in parallel run = StartArgs if cmdargs else Start run(thecmd, thecmd)() # or always Spawn else: Fork(thecmd, thecmd)() # spawn in parallel os.chdir(mycwd) def onPickFont(self): from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel(self) popup.title('SimpleEditor - font') var1 = makeFormRow(popup, label='Family', browse=False) var2 = makeFormRow(popup, label='Size', browse=False) var3 = makeFormRow(popup, label='Style', browse=False) var1.set('courier') var2.set('12') # suggested vals var3.set('bold italic') # see pick list for valid inputs Button(popup, text='Apply', command= lambda: self.onDoFont(var1.get(), var2.get(), var3.get())).pack() def onDoFont(self, family, size, style): try: self.text.config(font=(family, int(size), style)) except: showerror('SimpleEditor', 'Bad font specification') def isEmpty(self): return not self.getAllText() def getAllText(self): return self.text.get('1.0', END+'-1c') # extract text as a string def setAllText(self, text): self.text.delete('1.0', END) # store text string in widget self.text.insert(END, text) # or '1.0' self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top, insert set def clearAllText(self): self.text.delete('1.0', END) # clear text in widget def getFileName(self): return self.currfile def setFileName(self, name): # also: onGoto(linenum) self.currfile = name # for save self.filelabel.config(text=str(name)) def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) # 'black', hexstring def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo('About ', helptext % ((Version,)*2))
class TextEditor: startfiledir = '.' editwindows = [] if __name__ == '__main__': from textConfig import ( opensAskUser, opensEncoding, savesUseKnownEncoding, savesAskUser, savesEncoding) else: from .textConfig import ( opensAskUser, opensEncoding, savesUseKnownEncoding, savesAskUser, savesEncoding) ftypes = [('All files', '*'), ('Text files', '.txt'), ('Python files', '.py')] colors = [{'fg':'black', 'bg':'white'}, {'fg':'yellow', 'bg':'black'}, {'fg':'white', 'bg':'blue'}, {'fg':'black', 'bg':'beige'}, {'fg':'yellow', 'bg':'purple'}, {'fg':'black', 'bg':'brown'}, {'fg':'lightgreen', 'bg':'darkgreen'}, {'fg':'darkblue', 'bg':'orange'}, {'fg':'orange', 'bg':'darkblue'}] fonts = [('courier', 9+FontScale, 'normal'), ('courier', 12+FontScale, 'normal'), ('courier', 10+FontScale, 'bold'), ('courier', 10+FontScale, 'italic'), ('times', 10+FontScale, 'normal'), ('helvetica', 10+FontScale, 'normal'), ('ariel', 10+FontScale, 'normal'), ('system', 10+FontScale, 'normal'), ('courier', 20+FontScale, 'normal')] def __init__(self, loadFirst='', loadEncode=''): if not isinstance(self, GuiMaker): raise TypeError('TextEditor needs a GuiMaker mixin') self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.knownEncoding = None self.text.focus() if loadFirst: self.update() self.onOpen(loadFirst, loadEncode) def start(self): self.menuBar = [ ('File', 0, [('Open...', 0, self.onOpen), ('Save', 0, self.onSave), ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Quit...', 0, self.onQuit)] ), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)] ), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Change...', 0, self.onChange), ('Grep...', 3, self.onGrep)] ), ('Tools', 0, [('Pick Font...', 6, self.onPickFont), ('Font List', 0, self.onFontList), 'separator', ('Pick Bg...', 3, self.onPickBg), ('Pick Fg...', 0, self.onPickFg), ('Color List', 0, self.onColorList), 'separator', ('Info...', 0, self.onInfo), ('Clone', 1, self.onClone), ('Run Code', 0, self.onRunCode)] )] self.toolBar = [ ('Save', self.onSave, {'side': LEFT}), ('Cut', self.onCut, {'side': LEFT}), ('Copy', self.onCopy, {'side': LEFT}), ('Paste', self.onPaste, {'side': LEFT}), ('Find', self.onRefind, {'side': LEFT}), ('Help', self.help, {'side': RIGHT}), ('Quit', self.onQuit, {'side': RIGHT})] def makeWidgets(self): name = Label(self, bg='black', fg='white') name.pack(side=TOP, fill=X) vbar = Scrollbar(self) hbar = Scrollbar(self, orient='horizontal') text = Text(self, padx=5, wrap='none') text.config(undo=1, autoseparators=1) vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) text.pack(side=TOP, fill=BOTH, expand=YES) text.config(yscrollcommand=vbar.set) text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) hbar.config(command=text.xview) startfont = configs.get('font', self.fonts[0]) startbg = configs.get('bg', self.colors[0]['bg']) startfg = configs.get('fg', self.colors[0]['fg']) text.config(font=startfont, bg=startbg, fg=startfg) if 'height' in configs: text.config(height=configs['height']) if 'width' in configs: text.config(width =configs['width']) self.text = text self.filelabel = name def my_askopenfilename(self): if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst='', loadEncode=''): if self.text_edit_modified(): if not askyesno('SimpleEditor', 'Text has changed: discard changes?'): return file = loadFirst or self.my_askopenfilename() if not file: return if not os.path.isfile(file): showerror('SimpleEditor', 'Could not open file ' + file) return text = None if loadEncode: try: text = open(file, 'r', encoding=loadEncode).read() self.knownEncoding = loadEncode except (UnicodeError, LookupError, IOError): pass # try user input, prefill with next choice as default if text == None and self.opensAskUser: self.update() askuser = askstring('SimpleEditor', 'Enter Unicode encoding for open', initialvalue=(self.opensEncoding or sys.getdefaultencoding() or '')) if askuser: try: text = open(file, 'r', encoding=askuser).read() self.knownEncoding = askuser except (UnicodeError, LookupError, IOError): pass if text == None and self.opensEncoding: try: text = open(file, 'r', encoding=self.opensEncoding).read() self.knownEncoding = self.opensEncoding except (UnicodeError, LookupError, IOError): pass if text == None: try: text = open(file, 'r', encoding=sys.getdefaultencoding()).read() self.knownEncoding = sys.getdefaultencoding() except (UnicodeError, LookupError, IOError): pass if text == None: try: text = open(file, 'rb').read() text = text.replace(b'\r\n', b'\n') # for display, saves self.knownEncoding = None except IOError: pass if text == None: showerror('SimpleEditor', 'Could not decode and open file ' + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() self.text.edit_modified(0) def onSave(self): self.onSaveAs(self.currfile) # may be None def onSaveAs(self, forcefile=None): filename = forcefile or self.my_asksaveasfilename() if not filename: return text = self.getAllText() encpick = None # even if read/inserted as bytes # try known encoding at latest Open or Save, if any if self.knownEncoding and ( # enc known? (forcefile and self.savesUseKnownEncoding >= 1) or # on Save? (not forcefile and self.savesUseKnownEncoding >= 2)): # on SaveAs? try: text.encode(self.knownEncoding) encpick = self.knownEncoding except UnicodeError: pass if not encpick and self.savesAskUser: self.update() # else dialog doesn't appear in rare cases askuser = askstring('SimpleEditor', 'Enter Unicode encoding for save', initialvalue=(self.knownEncoding or self.savesEncoding or sys.getdefaultencoding() or '')) if askuser: try: text.encode(askuser) encpick = askuser except (UnicodeError, LookupError): # LookupError: bad name pass if not encpick and self.savesEncoding: try: text.encode(self.savesEncoding) encpick = self.savesEncoding except (UnicodeError, LookupError): pass # try platform default (utf8 on windows) if not encpick: try: text.encode(sys.getdefaultencoding()) encpick = sys.getdefaultencoding() except (UnicodeError, LookupError): pass if not encpick: showerror('SimpleEditor', 'Could not encode for file ' + filename) else: try: file = open(filename, 'w', encoding=encpick) file.write(text) file.close() except: showerror('SimpleEditor', 'Could not write file ' + filename) else: self.setFileName(filename) self.text.edit_modified(0) self.knownEncoding = encpick def onNew(self): if self.text_edit_modified(): if not askyesno('SimpleEditor', 'Text has changed: discard changes?'): return self.setFileName(None) self.clearAllText() self.text.edit_reset() self.text.edit_modified(0) self.knownEncoding = None # def onQuit(self): assert False, 'onQuit must be defined in window-specific sublass' def text_edit_modified(self): return self.text.edit_modified() def onUndo(self): try: self.text.edit_undo() # exception if stacks empty except TclError: # menu tear-offs for quick undo showinfo('SimpleEditor', 'Nothing to undo') def onRedo(self): try: self.text.edit_redo() except TclError: showinfo('SimpleEditor', 'Nothing to redo') def onCopy(self): # get text selected by mouse, etc. if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): # delete selected text, no save if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: self.onCopy() # save and delete selected text self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror('SimpleEditor', 'Nothing to paste') return self.text.insert(INSERT, text) # add at current insert cursor self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT+'-%dc' % len(text), INSERT) self.text.see(INSERT) # select it, so it can be cut def onSelectAll(self): self.text.tag_add(SEL, '1.0', END+'-1c') # select entire text self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top def onGoto(self, forceline=None): line = forceline or askinteger('SimpleEditor', 'Enter line number') self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END+'-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, '%d.0' % line) # goto line self.text.tag_remove(SEL, '1.0', END) # delete selects self.text.tag_add(SEL, INSERT, 'insert + 1l') # select line self.text.see(INSERT) # scroll to line else: showerror('SimpleEditor', 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring('SimpleEditor', 'Enter search string') self.text.update() self.text.focus() self.lastfind = key if key: nocase = configs.get('caseinsens', True) where = self.text.search(key, INSERT, END, nocase=nocase) if not where: showerror('SimpleEditor', 'String not found') else: pastkey = where + '+%dc' % len(key) self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, where, pastkey) self.text.mark_set(INSERT, pastkey) self.text.see(where) def onRefind(self): self.onFind(self.lastfind) def onChange(self): new = Toplevel(self) new.title('SimpleEditor - change') Label(new, text='Find text?', relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text='Change to?', relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): # use my entry in enclosing scope self.onFind(entry1.get()) # runs normal find dialog callback def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text='Find', command=onFind ).grid(row=0, column=2, sticky=EW) Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) # expandable entries def onDoChange(self, findtext, changeto): if self.text.tag_ranges(SEL): self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) self.text.see(INSERT) self.onFind(findtext) self.text.update() def onGrep(self): from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel() popup.title('SimpleEditor - grep') var1 = makeFormRow(popup, label='Directory root', width=18, browse=False) var2 = makeFormRow(popup, label='Filename pattern', width=18, browse=False) var3 = makeFormRow(popup, label='Search string', width=18, browse=False) var1.set('.') var2.set('*.py') Button(popup, text='Go', command=lambda: self.onDoGrep(var1.get(), var2.get(), var3.get())).pack() def onDoGrep(self, dirname, filenamepatt, grepkey): import threading, queue mypopup = Tk() mypopup.title('SimpleEditor - grepping') status = Label(mypopup, text='Grep thread searching for: %r...' % grepkey) status.pack(padx=20, pady=20) mypopup.protocol('WM_DELETE_WINDOW', lambda: None) # ignore X close myqueue = queue.Queue() threadargs = (filenamepatt, dirname, grepkey, myqueue) threading.Thread(target=self.grepThreadProducer, args=threadargs).start() self.grepThreadConsumer(grepkey, myqueue, mypopup) def grepThreadProducer(self, filenamepatt, dirname, grepkey, myqueue): from PP4E.Tools.find import find matches = [] for filepath in find(pattern=filenamepatt, startdir=dirname): try: for (linenum, linestr) in enumerate(open(filepath)): if grepkey in linestr: message = '%s@%d [%s]' % (filepath, linenum + 1, linestr) matches.append(message) except UnicodeDecodeError: print('Unicode error in:', filepath) myqueue.put(matches) def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) # 'black', hexstring def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo('About SimpleEditor', helptext % ((Version,)*2))
def my_askopenfilename(self): if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show()
class TextEditor: # mix with menu/toolbar Frame class startfiledir = '.' editwindows = [] if __name__ == '__main__': from textConfig import ( opensAskUser, opensEncoding, savesUseKnownEncoding, savesAskUser, savesEncoding) else: from .textConfig import ( opensAskUser, opensEncoding, savesUseKnownEncoding, savesAskUser, savesEncoding) ftypes = [('All files', '*'), ('Text files', '.txt'), ('Python files', '.py')] colors = [{'fg':'black', 'bg':'white'}, {'fg':'yellow', 'bg':'black'}, {'fg':'white', 'bg':'blue'}, {'fg':'black', 'bg':'beige'}, {'fg':'yellow', 'bg':'purple'}, {'fg':'black', 'bg':'brown'}, {'fg':'lightgreen', 'bg':'darkgreen'}, {'fg':'darkblue', 'bg':'orange'}, {'fg':'orange', 'bg':'darkblue'}] fonts = [('courier', 9+FontScale, 'normal'), ('courier', 12+FontScale, 'normal'), ('courier', 10+FontScale, 'bold'), ('courier', 10+FontScale, 'italic'), ('times', 10+FontScale, 'normal'), ('helvetica', 10+FontScale, 'normal'), ('ariel', 10+FontScale, 'normal'), ('system', 10+FontScale, 'normal'), ('courier', 20+FontScale, 'normal')] def __init__(self, loadFirst='', loadEncode=''): if not isinstance(self, GuiMaker): raise TypeError('TextEditor needs a GuiMaker mixin') self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.knownEncoding = None self.text.focus() if loadFirst: self.update() self.onOpen(loadFirst, loadEncode) def start(self): self.menuBar = [ ('File', 0, [('Open...', 0, self.onOpen), ('Save', 0, self.onSave), ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Quit...', 0, self.onQuit)] ), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)] ), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Change...', 0, self.onChange), ('Grep...', 3, self.onGrep)] ), ('Tools', 0, [('Pick Font...', 6, self.onPickFont), ('Font List', 0, self.onFontList), 'separator', ('Pick Bg...', 3, self.onPickBg), ('Pick Fg...', 0, self.onPickFg), ('Color List', 0, self.onColorList), 'separator', ('Info...', 0, self.onInfo), ('Clone', 1, self.onClone), ('Run Code', 0, self.onRunCode)] )] self.toolBar = [ ('Save', self.onSave, {'side': LEFT}), ('Cut', self.onCut, {'side': LEFT}), ('Copy', self.onCopy, {'side': LEFT}), ('Paste', self.onPaste, {'side': LEFT}), ('Find', self.onRefind, {'side': LEFT}), ('Help', self.help, {'side': RIGHT}), ('Quit', self.onQuit, {'side': RIGHT})] def makeWidgets(self): # run by GuiMaker.__init__ name = Label(self, bg='black', fg='white') # add below menu, above tool name.pack(side=TOP, fill=X) # menu/toolbars are packed vbar = Scrollbar(self) hbar = Scrollbar(self, orient='horizontal') text = Text(self, padx=5, wrap='none') # disable line wrapping text.config(undo=1, autoseparators=1) vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) # pack text last text.pack(side=TOP, fill=BOTH, expand=YES) # else sbars clipped text.config(yscrollcommand=vbar.set) text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) hbar.config(command=text.xview) startfont = configs.get('font', self.fonts[0]) startbg = configs.get('bg', self.colors[0]['bg']) startfg = configs.get('fg', self.colors[0]['fg']) text.config(font=startfont, bg=startbg, fg=startfg) if 'height' in configs: text.config(height=configs['height']) if 'width' in configs: text.config(width =configs['width']) self.text = text self.filelabel = name def my_askopenfilename(self): if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): # objects remember last result dir/file if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst='', loadEncode=''): if self.text_edit_modified(): if not askyesno('SimpleEditor', 'Text has changed: discard changes?'): return file = loadFirst or self.my_askopenfilename() if not file: return if not os.path.isfile(file): showerror('SimpleEditor', 'Could not open file ' + file) return text = None if loadEncode: try: text = open(file, 'r', encoding=loadEncode).read() self.knownEncoding = loadEncode except (UnicodeError, LookupError, IOError): # lookup: bad name pass if text == None and self.opensAskUser: self.update() # else dialog doesn't appear in rare cases askuser = askstring('SimpleEditor', 'Enter Unicode encoding for open', initialvalue=(self.opensEncoding or sys.getdefaultencoding() or '')) if askuser: try: text = open(file, 'r', encoding=askuser).read() self.knownEncoding = askuser except (UnicodeError, LookupError, IOError): pass if text == None and self.opensEncoding: try: text = open(file, 'r', encoding=self.opensEncoding).read() self.knownEncoding = self.opensEncoding except (UnicodeError, LookupError, IOError): pass if text == None: try: text = open(file, 'r', encoding=sys.getdefaultencoding()).read() self.knownEncoding = sys.getdefaultencoding() except (UnicodeError, LookupError, IOError): pass if text == None: try: text = open(file, 'rb').read() text = text.replace(b'\r\n', b'\n') self.knownEncoding = None except IOError: pass if text == None: showerror('SimpleEditor', 'Could not decode and open file ' + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() self.text.edit_modified(0) def onSave(self): self.onSaveAs(self.currfile) def onSaveAs(self, forcefile=None): filename = forcefile or self.my_asksaveasfilename() if not filename: return text = self.getAllText() encpick = None if self.knownEncoding and ( (forcefile and self.savesUseKnownEncoding >= 1) or (not forcefile and self.savesUseKnownEncoding >= 2)): try: text.encode(self.knownEncoding) encpick = self.knownEncoding except UnicodeError: pass if not encpick and self.savesAskUser: self.update() # else dialog doesn't appear in rare cases askuser = askstring('SimpleEditor', 'Enter Unicode encoding for save', initialvalue=(self.knownEncoding or self.savesEncoding or sys.getdefaultencoding() or '')) if askuser: try: text.encode(askuser) encpick = askuser except (UnicodeError, LookupError): pass if not encpick and self.savesEncoding: try: text.encode(self.savesEncoding) encpick = self.savesEncoding except (UnicodeError, LookupError): pass if not encpick: try: text.encode(sys.getdefaultencoding()) encpick = sys.getdefaultencoding() except (UnicodeError, LookupError): pass if not encpick: showerror('SimpleEditor', 'Could not encode for file ' + filename) else: try: file = open(filename, 'w', encoding=encpick) file.write(text) file.close() except: showerror('SimpleEditor', 'Could not write file ' + filename) else: self.setFileName(filename) self.text.edit_modified(0) self.knownEncoding = encpick def onNew(self): if self.text_edit_modified(): if not askyesno('SimpleEditor', 'Text has changed: discard changes?'): return self.setFileName(None) self.clearAllText() self.text.edit_reset() self.text.edit_modified(0) self.knownEncoding = None def onQuit(self): assert False, 'onQuit must be defined in window-specific sublass' def text_edit_modified(self): return self.text.edit_modified() def onUndo(self): try: self.text.edit_undo() except TclError: showinfo('SimpleEditor', 'Nothing to undo') def onRedo(self): try: self.text.edit_redo() except TclError: showinfo('SimpleEditor', 'Nothing to redo') def onCopy(self): if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): # delete selected text, no save if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror('SimpleEditor', 'No text selected') else: self.onCopy() # save and delete selected text self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror('SimpleEditor', 'Nothing to paste') return self.text.insert(INSERT, text) self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT+'-%dc' % len(text), INSERT) self.text.see(INSERT) def onSelectAll(self): self.text.tag_add(SEL, '1.0', END+'-1c') self.text.mark_set(INSERT, '1.0') self.text.see(INSERT) def onGoto(self, forceline=None): line = forceline or askinteger('SimpleEditor', 'Enter line number') self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END+'-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, '%d.0' % line) # goto line self.text.tag_remove(SEL, '1.0', END) # delete selects self.text.tag_add(SEL, INSERT, 'insert + 1l') # select line self.text.see(INSERT) # scroll to line else: showerror('SimpleEditor', 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring('SimpleEditor', 'Enter search string') self.text.update() self.text.focus() self.lastfind = key if key: nocase = configs.get('caseinsens', True) where = self.text.search(key, INSERT, END, nocase=nocase) if not where: # don't wrap showerror('SimpleEditor', 'String not found') else: pastkey = where + '+%dc' % len(key) # index past key self.text.tag_remove(SEL, '1.0', END) # remove any sel self.text.tag_add(SEL, where, pastkey) # select key self.text.mark_set(INSERT, pastkey) # for next find self.text.see(where) # scroll display def onRefind(self): self.onFind(self.lastfind) def onChange(self): new = Toplevel(self) new.title('SimpleEditor - change') Label(new, text='Find text?', relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text='Change to?', relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): self.onFind(entry1.get()) def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text='Find', command=onFind ).grid(row=0, column=2, sticky=EW) Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) # expandable entries def onDoChange(self, findtext, changeto): # on Apply in change dialog: change and refind if self.text.tag_ranges(SEL): # must find first self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) # deletes if empty self.text.see(INSERT) self.onFind(findtext) # goto next appear self.text.update() # force refresh def onGrep(self): from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel() popup.title('SimpleEditor - grep') var1 = makeFormRow(popup, label='Directory root', width=18, browse=False) var2 = makeFormRow(popup, label='Filename pattern', width=18, browse=False) var3 = makeFormRow(popup, label='Search string', width=18, browse=False) var4 = makeFormRow(popup, label='Content encoding', width=18, browse=False) var1.set('.') # current dir var2.set('*.py') # initial values var4.set(sys.getdefaultencoding()) cb = lambda: self.onDoGrep(var1.get(), var2.get(), var3.get(), var4.get()) Button(popup, text='Go',command=cb).pack() def onDoGrep(self, dirname, filenamepatt, grepkey, encoding): import threading, queue mypopup = Tk() mypopup.title('SimpleEditor - grepping') status = Label(mypopup, text='Grep thread searching for: %r...' % grepkey) status.pack(padx=20, pady=20) mypopup.protocol('WM_DELETE_WINDOW', lambda: None) # ignore X close # start producer thread, consumer loop myqueue = queue.Queue() threadargs = (filenamepatt, dirname, grepkey, encoding, myqueue) threading.Thread(target=self.grepThreadProducer, args=threadargs).start() self.grepThreadConsumer(grepkey, encoding, myqueue, mypopup) def grepThreadProducer(self, filenamepatt, dirname, grepkey, encoding, myqueue): from PP4E.Tools.find import find matches = [] try: for filepath in find(pattern=filenamepatt, startdir=dirname): try: textfile = open(filepath, encoding=encoding) for (linenum, linestr) in enumerate(textfile): if grepkey in linestr: msg = '%s@%d [%s]' % (filepath, linenum + 1, linestr) matches.append(msg) except UnicodeError as X: print('Unicode error in:', filepath, X) # eg: decode, bom except IOError as X: print('IO error in:', filepath, X) # eg: permission finally: myqueue.put(matches) # stop consumer loop on find excs: filenames? def grepThreadConsumer(self, grepkey, encoding, myqueue, mypopup): import queue try: matches = myqueue.get(block=False) except queue.Empty: myargs = (grepkey, encoding, myqueue, mypopup) self.after(250, self.grepThreadConsumer, *myargs) else: mypopup.destroy() self.update() if not matches: showinfo('SimpleEditor', 'Grep found no matches for: %r' % grepkey) else: self.grepMatchesList(matches, grepkey, encoding) def grepMatchesList(self, matches, grepkey, encoding): from PP4E.Gui.Tour.scrolledlist import ScrolledList print('Matches for %s: %s' % (grepkey, len(matches))) class ScrolledFilenames(ScrolledList): def runCommand(self, selection): file, line = selection.split(' [', 1)[0].split('@') editor = TextEditorMainPopup( loadFirst=file, winTitle=' grep match', loadEncode=encoding) editor.onGoto(int(line)) editor.text.focus_force() popup = Tk() popup.title('SimpleEditor - grep matches: %r (%s)' % (grepkey, encoding)) ScrolledFilenames(parent=popup, options=matches) def onFontList(self): self.fonts.append(self.fonts[0]) del self.fonts[0] self.text.config(font=self.fonts[0]) def onColorList(self): self.colors.append(self.colors[0]) del self.colors[0] self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg']) def onPickFg(self): self.pickColor('fg') def onPickBg(self): self.pickColor('bg') def pickColor(self, part): (triple, hexstr) = askcolor() if hexstr: self.text.config(**{part: hexstr}) def onInfo(self): text = self.getAllText() bytes = len(text) lines = len(text.split('\n')) words = len(text.split()) index = self.text.index(INSERT) where = tuple(index.split('.')) showinfo('SimpleEditor Information', 'Current location:\n\n' + 'line:\t%s\ncolumn:\t%s\n\n' % where + 'File text statistics:\n\n' + 'chars:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words)) def onClone(self, makewindow=True): if not makewindow: new = None else: new = Toplevel() myclass = self.__class__ myclass(new) def onRunCode(self, parallelmode=True): def askcmdargs(): return askstring('SimpleEditor', 'Commandline arguments?') or '' from PP4E.launchmodes import System, Start, StartArgs, Fork filemode = False thefile = str(self.getFileName()) if os.path.exists(thefile): filemode = askyesno('SimpleEditor', 'Run from file?') self.update() if not filemode: # run text string cmdargs = askcmdargs() namespace = {'__name__': '__main__'} # run as top-level sys.argv = [thefile] + cmdargs.split() # could use threads exec(self.getAllText() + '\n', namespace) elif self.text_edit_modified(): showerror('SimpleEditor', 'Text changed: you must save before run') else: cmdargs = askcmdargs() mycwd = os.getcwd() # cwd may be root dirname, filename = os.path.split(thefile) # get dir, base os.chdir(dirname or mycwd) # cd for filenames thecmd = filename + ' ' + cmdargs if not parallelmode: # run as file System(thecmd, thecmd)() # block editor else: if sys.platform[:3] == 'win': # spawn in parallel run = StartArgs if cmdargs else Start run(thecmd, thecmd)() # or always Spawn else: Fork(thecmd, thecmd)() # spawn in parallel os.chdir(mycwd) # go back to my dir def onPickFont(self): from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel(self) popup.title('SimpleEditor - font') var1 = makeFormRow(popup, label='Family', browse=False) var2 = makeFormRow(popup, label='Size', browse=False) var3 = makeFormRow(popup, label='Style', browse=False) var1.set('courier') var2.set('12') var3.set('bold italic') Button(popup, text='Apply', command= lambda: self.onDoFont(var1.get(), var2.get(), var3.get())).pack() def onDoFont(self, family, size, style): try: self.text.config(font=(family, int(size), style)) except: showerror('SimpleEditor', 'Bad font specification') def isEmpty(self): return not self.getAllText() def getAllText(self): return self.text.get('1.0', END+'-1c') def setAllText(self, text): self.text.delete('1.0', END) self.text.insert(END, text) self.text.mark_set(INSERT, '1.0') self.text.see(INSERT) def clearAllText(self): self.text.delete('1.0', END) def getFileName(self): return self.currfile def setFileName(self, name): # see also: onGoto(linenum) self.currfile = name # for save self.filelabel.config(text=str(name)) def setKnownEncoding(self, encoding='utf-8'): self.knownEncoding = encoding def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo('About SimpleEditor', helptext % ((Version,)*2))
class CutplaceFrame(Frame): """ Tk frame to validate a CID and data file. """ def __init__(self, master, cid_path=None, data_path=None, config=dict(), **keywords): """ Set up a frame with widgets to validate ``id_path`` and ``data_path``. :param master: Tk master or root in which the frame should show up :param cid_path: optional preset for :guilabel:`CID` widget :type cid_path: str or None :param data_path: optional preset for :guilabel:`Data` widget :type data_path: str or None :param config: Tk configuration :param keywords: Tk keywords """ assert has_tk assert master is not None if six.PY2: # In Python 2, Frame is an old style class. Frame.__init__(self, master, config, **keywords) else: super().__init__(master, config, **keywords) self._master = master # Define basic layout. self.grid(padx=_PADDING, pady=_PADDING) # self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(_VALIDATION_REPORT_ROW, weight=1) # Choose CID. self._cid_label = Label(self, text='CID:') self._cid_label.grid(row=_CID_ROW, column=0, sticky=E) self._cid_path_entry = Entry(self, width=55) self._cid_path_entry.grid(row=_CID_ROW, column=1, sticky=E + W) self._choose_cid_button = Button(self, command=self.choose_cid, text='Choose...') self._choose_cid_button.grid(row=_CID_ROW, column=2) self.cid_path = cid_path # Choose data. self._data_label = Label(self, text='Data:') self._data_label.grid(row=_DATA_ROW, column=0, sticky=E) self._data_path_entry = Entry(self, width=55) self._data_path_entry.grid(row=_DATA_ROW, column=1, sticky=E + W) self._choose_data_button = Button(self, command=self.choose_data, text='Choose...') self._choose_data_button.grid(row=_DATA_ROW, column=2) self.data_path = data_path # Validate. self._validate_button = Button(self, command=self.validate, text='Validate') self._validate_button.grid(row=_VALIDATE_BUTTON_ROW, column=0, padx=_PADDING, pady=_PADDING) # Validation status text. self._validation_status_text = StringVar() validation_status_label = Label( self, textvariable=self._validation_status_text) validation_status_label.grid(row=_VALIDATE_BUTTON_ROW, column=1) # Validation result. validation_report_frame = LabelFrame(self, text='Validation report') validation_report_frame.grid(row=_VALIDATION_REPORT_ROW, columnspan=3, sticky=E + N + S + W) validation_report_frame.grid_columnconfigure(0, weight=1) validation_report_frame.grid_rowconfigure(0, weight=1) self._validation_report_text = Text(validation_report_frame) self._validation_report_text.grid(column=0, row=0, sticky=E + N + S) _validation_report_scrollbar = Scrollbar(validation_report_frame) _validation_report_scrollbar.grid(column=1, row=0, sticky=N + S + W) _validation_report_scrollbar.config( command=self._validation_report_text.yview) self._validation_report_text.config( yscrollcommand=_validation_report_scrollbar.set) # Set up file dialogs. self._choose_cid_dialog = Open( initialfile=self.cid_path, title='Choose CID', ) self._choose_data_dialog = Open( initialfile=self.data_path, title='Choose data', ) self._save_log_as_dialog = SaveAs( defaultextension='.log', initialfile='cutplace.log', title='Save validation result', ) menubar = Menu(master) master.config(menu=menubar) self._file_menu = Menu(menubar, tearoff=False) self._file_menu.add_command(command=self.choose_cid, label='Choose CID...') self._file_menu.add_command(command=self.choose_data, label='Choose data...') self._file_menu.add_command(command=self.save_validation_report_as, label='Save validation report as...') self._file_menu.add_command(command=self.quit, label='Quit') menubar.add_cascade(label='File', menu=self._file_menu) help_menu = Menu(menubar, tearoff=False) help_menu.add_command(command=self.show_about, label='About') menubar.add_cascade(label='Help', menu=help_menu) self._enable_usable_widgets() def _enable_usable_widgets(self): def state_for(possibly_empty_text): if (possibly_empty_text is not None) and (possibly_empty_text.rstrip() != ''): result = 'normal' else: result = 'disabled' return result def set_state(widget_to_set_state_for, possibly_empty_text): widget_to_set_state_for.config( state=state_for(possibly_empty_text)) set_state(self._validate_button, self.cid_path) set_state(self._validation_report_text, self.validation_report) set_state(self._data_path_entry, self.cid_path) set_state(self._choose_data_button, self.cid_path) cid_path_state = state_for(self.cid_path) self._file_menu.entryconfig(_CHOOSE_DATA_PATH_MENU_INDEX, state=cid_path_state) self._file_menu.entryconfig(_SAVE_VALIDATION_REPORT_AS_MENU_INDEX, state=state_for( self.validation_report)) def choose_cid(self): """ Open a dialog to set the CID path. """ cid_path = self._choose_cid_dialog.show() if cid_path != '': self.cid_path = cid_path self._enable_usable_widgets() def choose_data(self): """ Open a dialog to set the data path. """ data_path = self._choose_data_dialog.show() if data_path != '': self.data_path = data_path self._enable_usable_widgets() def save_validation_report_as(self): """ Open a dialog to set specify where the validation results should be stored and write to this file. """ validation_report_path = self._save_log_as_dialog.show() if validation_report_path != '': try: with io.open(validation_report_path, 'w', encoding='utf-8') as validation_result_file: validation_result_file.write( self._validation_report_text.get(1.0, END)) except Exception as error: showerror('Cutplace error', 'Cannot save validation results:\n%s' % error) def quit(self): self._master.destroy() def show_about(self): showinfo('Cutplace', 'Version ' + __version__) def clear_validation_report_text(self): """ Clear the text area containing the validation results. """ self._validation_report_text.configure(state='normal') self._validation_report_text.delete(1.0, END) self._validation_report_text.see(END) self._enable_usable_widgets() def _cid_path(self): return self._cid_path_entry.get() def _set_cid_path(self, value): self._cid_path_entry.delete(0, END) if value is not None: self._cid_path_entry.insert(0, value) cid_path = property(_cid_path, _set_cid_path, None, 'Path of the CID to use for validation') def _data_path(self): return self._data_path_entry.get() def _set_data_path(self, value): self._data_path_entry.delete(0, END) if value is not None: self._data_path_entry.insert(0, value) data_path = property(_data_path, _set_data_path, None, 'Path of the data to validate') @property def validation_report(self): return self._validation_report_text.get(0.0, END) def validate(self): """ Validate the CID and (if specified) data file and update the :py:attr:`validation_result`. Show any errors unrelated to data in a dialog. """ assert self.cid_path != '' def add_log_line(line): self._validation_report_text.config(state=NORMAL) try: self._validation_report_text.insert(END, line + '\n') self._validation_report_text.see(END) finally: self._validation_report_text.config(state=DISABLED) def add_log_error_line(line_or_error): add_log_line('ERROR: %s' % line_or_error) def show_status_line(line): self._validation_status_text.set(line) self.master.update() assert self.cid_path != '' cid_name = os.path.basename(self.cid_path) self.clear_validation_report_text() add_log_line('%s: validating' % cid_name) self._enable_usable_widgets() cid = None try: cid = interface.Cid(self.cid_path) add_log_line('%s: ok' % cid_name) except errors.InterfaceError as error: add_log_error_line(error) except Exception as error: add_log_error_line('cannot read CID: %s' % error) if (cid is not None) and (self.data_path != ''): try: data_name = os.path.basename(self.data_path) add_log_line('%s: validating' % data_name) validator = validio.Reader(cid, self.data_path, on_error='yield') show_status_line('Validation started') last_update_time = time.time() for row_or_error in validator.rows(): now = time.time() if (now - last_update_time) >= 3: last_update_time = now show_status_line('%d rows validated' % (validator.accepted_rows_count + validator.rejected_rows_count)) if isinstance(row_or_error, errors.DataError): add_log_error_line(row_or_error) show_status_line('%d rows validated - finished' % (validator.accepted_rows_count + validator.rejected_rows_count)) add_log_line('%s: %d rows accepted, %d rows rejected' % (data_name, validator.accepted_rows_count, validator.rejected_rows_count)) except Exception as error: add_log_error_line('cannot validate data: %s' % error)
def __init__(self, master, cid_path=None, data_path=None, config=dict(), **keywords): """ Set up a frame with widgets to validate ``id_path`` and ``data_path``. :param master: Tk master or root in which the frame should show up :param cid_path: optional preset for :guilabel:`CID` widget :type cid_path: str or None :param data_path: optional preset for :guilabel:`Data` widget :type data_path: str or None :param config: Tk configuration :param keywords: Tk keywords """ assert has_tk assert master is not None if six.PY2: # In Python 2, Frame is an old style class. Frame.__init__(self, master, config, **keywords) else: super().__init__(master, config, **keywords) self._master = master # Define basic layout. self.grid(padx=_PADDING, pady=_PADDING) # self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(_VALIDATION_REPORT_ROW, weight=1) # Choose CID. self._cid_label = Label(self, text='CID:') self._cid_label.grid(row=_CID_ROW, column=0, sticky=E) self._cid_path_entry = Entry(self, width=55) self._cid_path_entry.grid(row=_CID_ROW, column=1, sticky=E + W) self._choose_cid_button = Button(self, command=self.choose_cid, text='Choose...') self._choose_cid_button.grid(row=_CID_ROW, column=2) self.cid_path = cid_path # Choose data. self._data_label = Label(self, text='Data:') self._data_label.grid(row=_DATA_ROW, column=0, sticky=E) self._data_path_entry = Entry(self, width=55) self._data_path_entry.grid(row=_DATA_ROW, column=1, sticky=E + W) self._choose_data_button = Button(self, command=self.choose_data, text='Choose...') self._choose_data_button.grid(row=_DATA_ROW, column=2) self.data_path = data_path # Validate. self._validate_button = Button(self, command=self.validate, text='Validate') self._validate_button.grid(row=_VALIDATE_BUTTON_ROW, column=0, padx=_PADDING, pady=_PADDING) # Validation status text. self._validation_status_text = StringVar() validation_status_label = Label(self, textvariable=self._validation_status_text) validation_status_label.grid(row=_VALIDATE_BUTTON_ROW, column=1) # Validation result. validation_report_frame = LabelFrame(self, text='Validation report') validation_report_frame.grid(row=_VALIDATION_REPORT_ROW, columnspan=3, sticky=E + N + S + W) validation_report_frame.grid_columnconfigure(0, weight=1) validation_report_frame.grid_rowconfigure(0, weight=1) self._validation_report_text = Text(validation_report_frame) self._validation_report_text.grid(column=0, row=0, sticky=E + N + S) _validation_report_scrollbar = Scrollbar(validation_report_frame) _validation_report_scrollbar.grid(column=1, row=0, sticky=N + S + W) _validation_report_scrollbar.config(command=self._validation_report_text.yview) self._validation_report_text.config(yscrollcommand=_validation_report_scrollbar.set) # Set up file dialogs. self._choose_cid_dialog = Open( initialfile=self.cid_path, title='Choose CID', ) self._choose_data_dialog = Open( initialfile=self.data_path, title='Choose data', ) self._save_log_as_dialog = SaveAs( defaultextension='.log', initialfile='cutplace.log', title='Save validation result', ) menubar = Menu(master) master.config(menu=menubar) self._file_menu = Menu(menubar, tearoff=False) self._file_menu.add_command(command=self.choose_cid, label='Choose CID...') self._file_menu.add_command(command=self.choose_data, label='Choose data...') self._file_menu.add_command(command=self.save_validation_report_as, label='Save validation report as...') self._file_menu.add_command(command=self.quit, label='Quit') menubar.add_cascade(label='File', menu=self._file_menu) help_menu = Menu(menubar, tearoff=False) help_menu.add_command(command=self.show_about, label='About') menubar.add_cascade(label='Help', menu=help_menu) self._enable_usable_widgets()
class CutplaceFrame(Frame): """ Tk frame to validate a CID and data file. """ def __init__(self, master, cid_path=None, data_path=None, config=dict(), **keywords): """ Set up a frame with widgets to validate ``id_path`` and ``data_path``. :param master: Tk master or root in which the frame should show up :param cid_path: optional preset for :guilabel:`CID` widget :type cid_path: str or None :param data_path: optional preset for :guilabel:`Data` widget :type data_path: str or None :param config: Tk configuration :param keywords: Tk keywords """ assert has_tk assert master is not None if six.PY2: # In Python 2, Frame is an old style class. Frame.__init__(self, master, config, **keywords) else: super().__init__(master, config, **keywords) self._master = master # Define basic layout. self.grid(padx=_PADDING, pady=_PADDING) # self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(_VALIDATION_REPORT_ROW, weight=1) # Choose CID. self._cid_label = Label(self, text='CID:') self._cid_label.grid(row=_CID_ROW, column=0, sticky=E) self._cid_path_entry = Entry(self, width=55) self._cid_path_entry.grid(row=_CID_ROW, column=1, sticky=E + W) self._choose_cid_button = Button(self, command=self.choose_cid, text='Choose...') self._choose_cid_button.grid(row=_CID_ROW, column=2) self.cid_path = cid_path # Choose data. self._data_label = Label(self, text='Data:') self._data_label.grid(row=_DATA_ROW, column=0, sticky=E) self._data_path_entry = Entry(self, width=55) self._data_path_entry.grid(row=_DATA_ROW, column=1, sticky=E + W) self._choose_data_button = Button(self, command=self.choose_data, text='Choose...') self._choose_data_button.grid(row=_DATA_ROW, column=2) self.data_path = data_path # Validate. self._validate_button = Button(self, command=self.validate, text='Validate') self._validate_button.grid(row=_VALIDATE_BUTTON_ROW, column=0, padx=_PADDING, pady=_PADDING) # Validation status text. self._validation_status_text = StringVar() validation_status_label = Label(self, textvariable=self._validation_status_text) validation_status_label.grid(row=_VALIDATE_BUTTON_ROW, column=1) # Validation result. validation_report_frame = LabelFrame(self, text='Validation report') validation_report_frame.grid(row=_VALIDATION_REPORT_ROW, columnspan=3, sticky=E + N + S + W) validation_report_frame.grid_columnconfigure(0, weight=1) validation_report_frame.grid_rowconfigure(0, weight=1) self._validation_report_text = Text(validation_report_frame) self._validation_report_text.grid(column=0, row=0, sticky=E + N + S) _validation_report_scrollbar = Scrollbar(validation_report_frame) _validation_report_scrollbar.grid(column=1, row=0, sticky=N + S + W) _validation_report_scrollbar.config(command=self._validation_report_text.yview) self._validation_report_text.config(yscrollcommand=_validation_report_scrollbar.set) # Set up file dialogs. self._choose_cid_dialog = Open( initialfile=self.cid_path, title='Choose CID', ) self._choose_data_dialog = Open( initialfile=self.data_path, title='Choose data', ) self._save_log_as_dialog = SaveAs( defaultextension='.log', initialfile='cutplace.log', title='Save validation result', ) menubar = Menu(master) master.config(menu=menubar) self._file_menu = Menu(menubar, tearoff=False) self._file_menu.add_command(command=self.choose_cid, label='Choose CID...') self._file_menu.add_command(command=self.choose_data, label='Choose data...') self._file_menu.add_command(command=self.save_validation_report_as, label='Save validation report as...') self._file_menu.add_command(command=self.quit, label='Quit') menubar.add_cascade(label='File', menu=self._file_menu) help_menu = Menu(menubar, tearoff=False) help_menu.add_command(command=self.show_about, label='About') menubar.add_cascade(label='Help', menu=help_menu) self._enable_usable_widgets() def _enable_usable_widgets(self): def state_for(possibly_empty_text): if (possibly_empty_text is not None) and (possibly_empty_text.rstrip() != ''): result = 'normal' else: result = 'disabled' return result def set_state(widget_to_set_state_for, possibly_empty_text): widget_to_set_state_for.config(state=state_for(possibly_empty_text)) set_state(self._validate_button, self.cid_path) set_state(self._validation_report_text, self.validation_report) set_state(self._data_path_entry, self.cid_path) set_state(self._choose_data_button, self.cid_path) cid_path_state = state_for(self.cid_path) self._file_menu.entryconfig(_CHOOSE_DATA_PATH_MENU_INDEX, state=cid_path_state) self._file_menu.entryconfig(_SAVE_VALIDATION_REPORT_AS_MENU_INDEX, state=state_for(self.validation_report)) def choose_cid(self): """ Open a dialog to set the CID path. """ cid_path = self._choose_cid_dialog.show() if cid_path != '': self.cid_path = cid_path self._enable_usable_widgets() def choose_data(self): """ Open a dialog to set the data path. """ data_path = self._choose_data_dialog.show() if data_path != '': self.data_path = data_path self._enable_usable_widgets() def save_validation_report_as(self): """ Open a dialog to set specify where the validation results should be stored and write to this file. """ validation_report_path = self._save_log_as_dialog.show() if validation_report_path != '': try: with io.open(validation_report_path, 'w', encoding='utf-8') as validation_result_file: validation_result_file.write(self._validation_report_text.get(1.0, END)) except Exception as error: showerror('Cutplace error', 'Cannot save validation results:\n%s' % error) def quit(self): self._master.destroy() def show_about(self): showinfo('Cutplace', 'Version ' + __version__) def clear_validation_report_text(self): """ Clear the text area containing the validation results. """ self._validation_report_text.configure(state='normal') self._validation_report_text.delete(1.0, END) self._validation_report_text.see(END) self._enable_usable_widgets() def _cid_path(self): return self._cid_path_entry.get() def _set_cid_path(self, value): self._cid_path_entry.delete(0, END) if value is not None: self._cid_path_entry.insert(0, value) cid_path = property(_cid_path, _set_cid_path, None, 'Path of the CID to use for validation') def _data_path(self): return self._data_path_entry.get() def _set_data_path(self, value): self._data_path_entry.delete(0, END) if value is not None: self._data_path_entry.insert(0, value) data_path = property(_data_path, _set_data_path, None, 'Path of the data to validate') @property def validation_report(self): return self._validation_report_text.get(0.0, END) def validate(self): """ Validate the CID and (if specified) data file and update the :py:attr:`validation_result`. Show any errors unrelated to data in a dialog. """ assert self.cid_path != '' def add_log_line(line): self._validation_report_text.config(state=NORMAL) try: self._validation_report_text.insert(END, line + '\n') self._validation_report_text.see(END) finally: self._validation_report_text.config(state=DISABLED) def add_log_error_line(line_or_error): add_log_line('ERROR: %s' % line_or_error) def show_status_line(line): self._validation_status_text.set(line) self.master.update() assert self.cid_path != '' cid_name = os.path.basename(self.cid_path) self.clear_validation_report_text() add_log_line('%s: validating' % cid_name) self._enable_usable_widgets() cid = None try: cid = interface.Cid(self.cid_path) add_log_line('%s: ok' % cid_name) except errors.InterfaceError as error: add_log_error_line(error) except Exception as error: add_log_error_line('cannot read CID: %s' % error) if (cid is not None) and (self.data_path != ''): try: data_name = os.path.basename(self.data_path) add_log_line('%s: validating' % data_name) validator = validio.Reader(cid, self.data_path, on_error='yield') show_status_line('Validation started') last_update_time = time.time() for row_or_error in validator.rows(): now = time.time() if (now - last_update_time) >= 3: last_update_time = now show_status_line( '%d rows validated' % (validator.accepted_rows_count + validator.rejected_rows_count)) if isinstance(row_or_error, errors.DataError): add_log_error_line(row_or_error) show_status_line( '%d rows validated - finished' % (validator.accepted_rows_count + validator.rejected_rows_count)) add_log_line( '%s: %d rows accepted, %d rows rejected' % (data_name, validator.accepted_rows_count, validator.rejected_rows_count)) except Exception as error: add_log_error_line('cannot validate data: %s' % error)
class TextEditor: # mix with menu/toolbar Frame class startfiledir = '.' # for dialogs editwindows = [] # for process-wide quit check ftypes = [ ('All files', '*'), # for file open dialog ('Text files', '.txt'), # customize in subclass ('Python files', '.py') ] # or set in each instance colors = [ { 'fg': 'black', 'bg': 'white' }, # color pick list { 'fg': 'yellow', 'bg': 'black' }, # first item is default { 'fg': 'white', 'bg': 'blue' }, # tailor me as desired { 'fg': 'black', 'bg': 'beige' }, # or do PickBg/Fg chooser { 'fg': 'yellow', 'bg': 'purple' }, { 'fg': 'black', 'bg': 'brown' }, { 'fg': 'lightgreen', 'bg': 'darkgreen' }, { 'fg': 'darkblue', 'bg': 'orange' }, { 'fg': 'orange', 'bg': 'darkblue' } ] fonts = [ ('courier', 9 + FontScale, 'normal'), # platform-neutral fonts ('courier', 12 + FontScale, 'normal'), # (family, size, style) ('courier', 10 + FontScale, 'bold'), # or pop up a listbox ('courier', 10 + FontScale, 'italic'), # make bigger on Linux ('times', 10 + FontScale, 'normal'), # use 'bold italic' for 2 ('helvetica', 10 + FontScale, 'normal'), # also 'underline', etc. ('ariel', 10 + FontScale, 'normal'), ('system', 10 + FontScale, 'normal'), ('courier', 20 + FontScale, 'normal') ] def __init__(self, loadFirst=''): if not isinstance(self, GuiMaker): raise TypeError('TextEditor needs a GuiMaker mixin') self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.text.focus() # else must click in text if loadFirst: self.update() # 2.1: else @ line 2; see book self.onOpen(loadFirst) def start(self): # run by GuiMaker.__init__ self.menuBar = [ # configure menu/toolbar ( 'File', 0, # a GuiMaker menu def tree [ ('Open...', 0, self.onOpen), # build in method for self ('Save', 0, self.onSave), # label, shortcut, callback ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Quit...', 0, self.onQuit) ]), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)]), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Change...', 0, self.onChange), ('Grep...', 3, self.onGrep)]), ('Tools', 0, [('Pick Font...', 6, self.onPickFont), ('Font List', 0, self.onFontList), 'separator', ('Pick Bg...', 3, self.onPickBg), ('Pick Fg...', 0, self.onPickFg), ('Color List', 0, self.onColorList), 'separator', ('Info...', 0, self.onInfo), ('Clone', 1, self.onClone), ('Run Code', 0, self.onRunCode)]) ] self.toolBar = [('Save', self.onSave, { 'side': LEFT }), ('Cut', self.onCut, { 'side': LEFT }), ('Copy', self.onCopy, { 'side': LEFT }), ('Paste', self.onPaste, { 'side': LEFT }), ('Find', self.onRefind, { 'side': LEFT }), ('Help', self.help, { 'side': RIGHT }), ('Quit', self.onQuit, { 'side': RIGHT })] def makeWidgets(self): # run by GuiMaker.__init__ name = Label(self, bg='black', fg='white') # add below menu, above tool name.pack(side=TOP, fill=X) # menu/toolbars are packed # GuiMaker frame packs itself vbar = Scrollbar(self) hbar = Scrollbar(self, orient='horizontal') text = Text(self, padx=5, wrap='none') # disable line wrapping text.config(undo=1, autoseparators=1) # 2.0, default is 0, 1 vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) # pack text last text.pack(side=TOP, fill=BOTH, expand=YES) # else sbars clipped text.config(yscrollcommand=vbar.set) # call vbar.set on text move text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) # call text.yview on scroll move hbar.config(command=text.xview) # or hbar['command']=text.xview # 2.0: apply user configs or defaults startfont = configs.get('font', self.fonts[0]) startbg = configs.get('bg', self.colors[0]['bg']) startfg = configs.get('fg', self.colors[0]['fg']) text.config(font=startfont, bg=startbg, fg=startfg) if 'height' in configs: text.config(height=configs['height']) if 'width' in configs: text.config(width=configs['width']) self.text = text self.filelabel = name ############################################################################ # File menu commands ############################################################################ def my_askopenfilename(self): # objects remember last result dir/file if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): # objects remember last result dir/file if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst=''): doit = ( not self.text_edit_modified() or # 2.0 askyesno('PyEdit', 'Text has changed: discard changes?')) if doit: file = loadFirst or self.my_askopenfilename() if file: try: text = open(file, 'r').read() except: showerror('PyEdit', 'Could not open file ' + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onSave(self): self.onSaveAs(self.currfile) # may be None def onSaveAs(self, forcefile=None): file = forcefile or self.my_asksaveasfilename() if file: text = self.getAllText() try: open(file, 'w').write(text) except: showerror('PyEdit', 'Could not write file ' + file) else: self.setFileName(file) # may be newly created self.text.edit_modified(0) # 2.0: clear modified flag # don't clear undo/redo stks def onNew(self): doit = ( not self.text_edit_modified() or # 2.0 askyesno('PyEdit', 'Text has changed: discard changes?')) if doit: self.setFileName(None) self.clearAllText() self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onQuit(self): """ on Quit menu/toolbar select and wm border X button in toplevel windows; 2.1: don't exit app if others changed; 2.0: don't ask if self unchanged; moved to the top-level window classes at the end since may vary per usage: a Quit in GUI might quit() to exit, destroy() just one Toplevel, Tk, or edit frame, or not be provided at all when run as an attached component; check self for changes, and if might quit(), main windows should check other windows in the process-wide list to see if they have changed too; """ assert False, 'onQuit must be defined in window-specific sublass' def text_edit_modified(self): """ 2.1: this now works! seems to have been a bool result type issue in tkinter; 2.0: self.text.edit_modified() broken in Python 2.4: do manually for now; """ return self.text.edit_modified() #return self.tk.call((self.text._w, 'edit') + ('modified', None)) ############################################################################ # Edit menu commands ############################################################################ def onUndo(self): # 2.0 try: # tk8.4 keeps undo/redo stacks self.text.edit_undo() # exception if stacks empty except TclError: # menu tear-offs for quick undo showinfo('PyEdit', 'Nothing to undo') def onRedo(self): # 2.0: redo an undone try: self.text.edit_redo() except TclError: showinfo('PyEdit', 'Nothing to redo') def onCopy(self): # get text selected by mouse, etc. if not self.text.tag_ranges(SEL): # save in cross-app clipboard showerror('PyEdit', 'No text selected') else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): # delete selected text, no save if not self.text.tag_ranges(SEL): showerror('PyEdit', 'No text selected') else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror('PyEdit', 'No text selected') else: self.onCopy() # save and delete selected text self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror('PyEdit', 'Nothing to paste') return self.text.insert(INSERT, text) # add at current insert cursor self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT + '-%dc' % len(text), INSERT) self.text.see(INSERT) # select it, so it can be cut def onSelectAll(self): self.text.tag_add(SEL, '1.0', END + '-1c') # select entire text self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top ############################################################################ # Search menu commands ############################################################################ def onGoto(self, forceline=None): line = forceline or askinteger('PyEdit', 'Enter line number') self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END + '-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, '%d.0' % line) # goto line self.text.tag_remove(SEL, '1.0', END) # delete selects self.text.tag_add(SEL, INSERT, 'insert + 1l') # select line self.text.see(INSERT) # scroll to line else: showerror('PyEdit', 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring('PyEdit', 'Enter search string') self.text.update() self.text.focus() self.lastfind = key if key: # 2.0: nocase nocase = configs.get('caseinsens', True) # 2.0: config where = self.text.search(key, INSERT, END, nocase=nocase) if not where: # don't wrap showerror('PyEdit', 'String not found') else: pastkey = where + '+%dc' % len(key) # index past key self.text.tag_remove(SEL, '1.0', END) # remove any sel self.text.tag_add(SEL, where, pastkey) # select key self.text.mark_set(INSERT, pastkey) # for next find self.text.see(where) # scroll display def onRefind(self): self.onFind(self.lastfind) def onChange(self): """ non-modal find/change dialog 2.1: pass per-dialog inputs to callbacks, may be > 1 change dialog open """ new = Toplevel(self) new.title('PyEdit - change') Label(new, text='Find text?', relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text='Change to?', relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): # use my entry in enclosing scope self.onFind(entry1.get()) # runs normal find dialog callback def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text='Find', command=onFind).grid(row=0, column=2, sticky=EW) Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) # expandable entries def onDoChange(self, findtext, changeto): # on Apply in change dialog: change and refind if self.text.tag_ranges(SEL): # must find first self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) # deletes if empty self.text.see(INSERT) self.onFind(findtext) # goto next appear self.text.update() # force refresh def onGrep(self): """ new in version 2.1: threaded external file search; search matched filenames in directory tree for string; listbox clicks open matched file at line of occurrence; search is threaded so the GUI remains active and is not blocked, and to allow multiple greps to overlap in time; could use threadtools, but avoid loop in no active grep; """ from PP4E.Gui.ShellGui.formrows import makeFormRow # nonmodal dialog: get dirnname, filenamepatt, grepkey popup = Toplevel() popup.title('PyEdit - grep') var1 = makeFormRow(popup, label='Directory root', width=18, browse=False) var2 = makeFormRow(popup, label='Filename pattern', width=18, browse=False) var3 = makeFormRow(popup, label='Search string', width=18, browse=False) var1.set('.') # current dir var2.set('*.py') # initial values Button(popup, text='Go', command=lambda: self.onDoGrep(var1.get(), var2.get(), var3.get( ))).pack() def onDoGrep(self, dirname, filenamepatt, grepkey): # on Go in grep dialog: populate scrolled list with matches # tbd: should producer thread be daemon so dies with app? import threading, queue # make non-modal un-closeable dialog mypopup = Tk() mypopup.title('PyEdit - grepping') status = Label(mypopup, text='Grep thread searching for: %r...' % grepkey) status.pack(padx=20, pady=20) mypopup.protocol('WM_DELETE_WINDOW', lambda: None) # ignore X close # start producer thread, consumer loop myqueue = queue.Queue() threadargs = (filenamepatt, dirname, grepkey, myqueue) threading.Thread(target=self.grepThreadProducer, args=threadargs).start() self.grepThreadConsumer(grepkey, myqueue, mypopup) def grepThreadProducer(self, filenamepatt, dirname, grepkey, myqueue): """ in a non-GUI parallel thread: queue find.find results list; could also queue matches as found, but need to keep window; """ from PP4E.Tools.find import find matches = [] for filepath in find(pattern=filenamepatt, startdir=dirname): try: for (linenum, linestr) in enumerate(open(filepath)): if grepkey in linestr: message = '%s@%d [%s]' % (filepath, linenum + 1, linestr) matches.append(message) except UnicodeDecodeError: print('Unicode error in:', filepath) myqueue.put(matches) def grepThreadConsumer(self, grepkey, myqueue, mypopup): """ in the main GUI thread: watch queue for results or []; there may be multiple active grep threads/loops/queues; there may be other types of threads/checkers in process, especially when PyEdit is attached component (PyMailGUI); """ import queue try: matches = myqueue.get(block=False) except queue.Empty: self.after(250, self.grepThreadConsumer, grepkey, myqueue, mypopup) else: mypopup.destroy() # close status self.update() # erase it now if not matches: showinfo('PyEdit', 'Grep found no matches for: %r' % grepkey) else: self.grepMatchesList(matches, grepkey) def grepMatchesList(self, matches, grepkey): # populate list after successful matches from PP4E.Gui.Tour.scrolledlist import ScrolledList print('Matches for %s: %s' % (grepkey, len(matches))) # catch list double-click class ScrolledFilenames(ScrolledList): def runCommand(self, selection): file, line = selection.split(' [', 1)[0].split('@') editor = TextEditorMainPopup(loadFirst=file, winTitle=' grep match') editor.onGoto(int(line)) editor.text.focus_force() # no, really # new non-modal widnow popup = Tk() popup.title('PyEdit - grep matches: %r' % grepkey) ScrolledFilenames(parent=popup, options=matches) ############################################################################ # Tools menu commands ############################################################################ def onFontList(self): self.fonts.append(self.fonts[0]) # pick next font in list del self.fonts[0] # resizes the text area self.text.config(font=self.fonts[0]) def onColorList(self): self.colors.append(self.colors[0]) # pick next color in list del self.colors[0] # move current to end self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg']) def onPickFg(self): self.pickColor('fg') # added on 10/02/00 def onPickBg(self): # select arbitrary color self.pickColor('bg') # in standard color dialog def pickColor(self, part): # this is too easy (triple, hexstr) = askcolor() if hexstr: self.text.config(**{part: hexstr}) def onInfo(self): text = self.getAllText() # added on 5/3/00 in 15 mins bytes = len(text) # words uses a simple guess: lines = len(text.split('\n')) # any separated by whitespace words = len(text.split()) # 3.x: bytes is really chars index = self.text.index(INSERT) # str is unicode code points where = tuple(index.split('.')) showinfo( 'PyEdit Information', 'Current location:\n\n' + 'line:\t%s\ncolumn:\t%s\n\n' % where + 'File text statistics:\n\n' + 'chars:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words)) def onClone(self): """ open a new edit window without changing one already open inherits quit and other behavior of window that it clones """ new = Toplevel() # a new edit window in same process myclass = self.__class__ # instance's (lowest) class object myclass(new) # attach/run instance of my class def onRunCode(self, parallelmode=True): """ run Python code being edited--not an IDE, but handy; tries to run in file's dir, not cwd (may be PP4E root); inputs and adds command-line arguments for script files; code's stdin/out/err = editor's start window, if any: run with a console window to see code's print outputs; but parallelmode uses start to open a DOS box for I/O; module search path will include '.' dir where started; in non-file mode, code's Tk root may be PyEdit's window; subprocess or multiprocessing modules may work here too; 2.1: fixed to use base file name after chdir, not path; 2.1: use StartArs to allow args in file mode on Windows; """ def askcmdargs(): return askstring('PyEdit', 'Commandline arguments?') or '' from PP4E.launchmodes import System, Start, StartArgs, Fork filemode = False thefile = str(self.getFileName()) if os.path.exists(thefile): filemode = askyesno('PyEdit', 'Run from file?') if not filemode: # run text string cmdargs = askcmdargs() namespace = {'__name__': '__main__'} # run as top-level sys.argv = [thefile] + cmdargs.split() # could use threads exec(self.getAllText() + '\n', namespace) # exceptions ignored elif self.text_edit_modified(): # 2.0: changed test showerror('PyEdit', 'Text changed: save before run') else: cmdargs = askcmdargs() mycwd = os.getcwd() # cwd may be root dirname, filename = os.path.split(thefile) # get dir, base os.chdir(dirname or mycwd) # cd for filenames thecmd = filename + ' ' + cmdargs # 2.1: not theFile if not parallelmode: # run as file System(thecmd, thecmd)() # block editor else: if sys.platform[:3] == 'win': # spawn in parallel run = StartArgs if cmdargs else Start # 2.1: support args run(thecmd, thecmd)() # or always Spawn else: Fork(thecmd, thecmd)() # spawn in parallel os.chdir(mycwd) # go back to my dir def onPickFont(self): """ 2.0 non-modal font spec dialog 2.1: pass per-dialog inputs to callback, may be > 1 font dialog open """ from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel(self) popup.title('PyEdit - font') var1 = makeFormRow(popup, label='Family', browse=False) var2 = makeFormRow(popup, label='Size', browse=False) var3 = makeFormRow(popup, label='Style', browse=False) var1.set('courier') var2.set('12') # suggested vals var3.set('bold italic') # see pick list for valid inputs Button(popup, text='Apply', command=lambda: self.onDoFont(var1.get(), var2.get(), var3.get( ))).pack() def onDoFont(self, family, size, style): try: self.text.config(font=(family, int(size), style)) except: showerror('PyEdit', 'Bad font specification') ############################################################################ # Utilities, useful outside this class ############################################################################ def isEmpty(self): return not self.getAllText() def getAllText(self): return self.text.get('1.0', END + '-1c') # extract text as a string def setAllText(self, text): self.text.delete('1.0', END) # store text string in widget self.text.insert(END, text) # or '1.0' self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top, insert set def clearAllText(self): self.text.delete('1.0', END) # clear text in widget def getFileName(self): return self.currfile def setFileName(self, name): # also: onGoto(linenum) self.currfile = name # for save self.filelabel.config(text=str(name)) def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) # 'black', hexstring def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo('About PyEdit', helptext % ((Version, ) * 2))
class Controller(): startfiledir = '.' ftypes = [('All files', '*'), ('Text files', '.txt'), ('Python files', '.py')] def __init__(self, noteName): menuList = [ ( 'File', 0, # label, shortcut, callback [('Open Note...', 0, self.onOpenNote), ('Save', 0, self.onSave), ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Open VOA', 0, self.onOpenVOA), ('Open Audio', 0, self.onOpenAudio), 'separator', ('Quit...', 0, self.onQuit)]), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)]), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Change...', 0, self.onChange)]) ] btnAttr = [('>II', self.onPlayOrStop, { 'side': tk.LEFT }), ('<<', self.onReplay, { 'side': tk.LEFT }), ('>>', self.onFwplay, { 'side': tk.LEFT })] self.root = tk.Tk() self.noteName = noteName self.view = View(self.root, menuList, btnAttr, self.goPos) self.model = ModelWavePlayer(self.view) self.root.title(Prog + ' ' + Version) self.root.iconname(Prog) self.root.protocol('WM_DELETE_WINDOW', self.onQuit) keystr = '<Control-space>' if sys.platform == 'linux' else '<Control-7>' self.keyList = [(keystr, self.keyPlayOrStop), ('<Control-bracketleft>', self.keyReplay), ('<Control-bracketright>', self.keyFwplay)] for keyStr, act in self.keyList: self.root.bind(keyStr, act) def keyPlayOrStop(self, evt): self.onPlayOrStop() def onReplay(self): self.model.goPos(0, -2) def onFwplay(self): self.model.goPos(0, +2) def keyReplay(self, evt): self.onReplay() def keyFwplay(self, evt): self.onFwplay() def goPos(self, value): self.model.goPos(1, int(value)) def my_askopenfilename(self): self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def onOpenNote(self): fname = self.noteName if not fname: fname = self.my_askopenfilename() if not fname or not os.path.isfile(fname): showerror(Prog, 'Cannot open file ' + fname) return contents = '' with open(fname, 'rb') as f: contents = f.read() contents = contents.replace(b'\r\n', b'\n') self.view.setAllText(contents) self.currfile = fname self.view.text.edit_reset() self.view.text.edit_modified(0) def onSave(self): #print('-->'+self.currfile) if self.currfile: self.onSaveAs(self.currfile) def onSaveAs(self, forcefile=None): fname = forcefile or self.my_asksaveasfname() if not fname: return try: with open(fname, 'w') as f: f.write(self.view.getAllText()) except: showerror(Prog, 'cannot write the file ' + fname) else: self.currfile = fname self.view.text.edit_modified(0) def clearAllText(self): self.view.text.delete('1.0', END) def onNew(self): if self.view.text.edit_modified(): if not askyesno(Prog, 'Text has changed: discard changes?'): return self.currfile = None self.view.clearAllText() self.view.text.edit_reset() self.view.text.edit_modified(0) #self.knownEncoding = None def run(self): self.root.title('VOAdict') self.root.mainloop() def getContentsList(self): page = requests.get('https://learningenglish.voanews.com/z/952') soup = bs4.BeautifulSoup(page.text, 'html.parser') voalist = [] for tag in soup.find_all('div', class_='content'): href = tag.find('a') if not href: continue href = href.attrs['href'] date = tag.find('span', attrs={'class': 'date'}) if not date: continue date = date.text.strip() title = tag.find('span', attrs={'class': 'title'}) if not title: continue title = title.text.strip() voalist.append((href, date, title)) return voalist def getCurrentURL(self): i = self.listb.curselection()[0] return 'https://learningenglish.voanews.com' + self.voalist[i][0] def openURL(self): import webbrowser webbrowser.open_new(self.getCurrentURL()) def mp3towav(self, filename): import pydub sound = pydub.AudioSegment.from_mp3(filename) wavfile = filename.split('.')[0] + '.wav' sound.export(wavfile, format="wav") return wavfile def openWavFile(self, wavfile): def errorForAudioFile(fname): showerror(Prog, 'cannot open audio file ' + fname) if not wavfile or not os.path.isfile(wavfile): errorForAudioFile(wavfile) return False if not self.model.onOpen(wavfile): errorForAudioFile(wavfile) return False return True def openVOAAudio(self): url = self.getCurrentURL() page = requests.get(url) soup = bs4.BeautifulSoup(page.text, 'html.parser') tag = soup.find_all('a', attrs={ 'class': 'handler', 'title': '64 kbps | MP3' }) href = tag[0].attrs['href'] filename = 'today.mp3' mp3file = requests.get(href) with open(filename, 'wb') as f: f.write(mp3file.content) wavfile = self.mp3towav(filename) if wavfile: self.openWavFile(wavfile) self.win_voa.destroy() def onOpenVOA(self): self.voalist = self.getContentsList() if not self.voalist: showerror(Prog, 'Cannot find VOA site') return False self.win_voa = tk.Toplevel() self.win_voa.title(Prog + 'Contents List') sbar = tk.Scrollbar(self.win_voa) sbar.pack(side=tk.RIGHT, fill=tk.Y) self.listb = tk.Listbox(self.win_voa, width=80, yscrollcommand=sbar.set) #self.listb.bind('<Double-1>', self.listb_doubleclick) self.listb.pack(fill=tk.BOTH, expand=True) sbar.config(command=self.listb.yview) tk.Button(self.win_voa, text='Open Audio', command=self.openVOAAudio).pack(side=tk.RIGHT) tk.Button(self.win_voa, text='Open URL', command=self.openURL).pack(side=tk.RIGHT) for i, t in enumerate(self.voalist): txt = t[1] + ' ' + t[2] self.listb.insert(i, txt) self.listb.selection_set(first=0) self.listb.focus_set() return True def onOpenAudio(self): wave_file = self.my_askopenfilename() return self.openWavFile(wave_file) def onPlayOrStop(self): if not self.model.isOpened(): showinfo(Prog, 'Open audio file (File->OpenAuduio)') return #if self.model.isPaused(): # self.view.BTN_PNS.configure(text='->') #else: # self.view.BTN_PNS.configure(text='II') self.model.togglePause() def onQuit(self): if self.view.text.edit_modified(): if not askyesno(Prog, 'Discard changes?'): return self.model.onClose() self.root.quit() def onUndo(self): try: self.view.text.edit_undo() except TclError: showinfo(Prog, 'Nothing to undo') def onRedo(self): try: self.view.text.edit_redo() except TclError: showinfo(Prog, 'Nothing to redo') def onCopy(self): if not self.view.text.tag_ranges(tk.SEL): showerror(Prog, 'No text selected') else: text = self.view.text.get(tk.SEL_FIRST, tk.SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): if not self.view.text.tag_ranges(tk.SEL): showerror(Prog, 'No text selected') else: self.view.text.delete(tk.SEL_FIRST, tk.SEL_LAST) def onCut(self): if not self.view.text.tag_ranges(tk.SEL): showerror(Prog, 'No text selected') else: self.opCopy() self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror(Prog, 'Nothing to paste') return self.view.text.insert(tk.INSERT, text) self.view.text.tag_remove(tk.SEL, '1.0', tk.END) self.view.text.tag_add(tk.SEL, tk.INSERT + '-%dc' % len(text), tk.INSERT) self.view.text.see(tk.INSERT) def onSelectAll(self): self.view.text.tag_add(tk.SEL, '1.0', tk.END + '-1c') #end-1c: 1 ch back from end self.view.text.mark_set(tk.INSERT, '1.0') self.view.text.see(tk.INSERT) def onGoto(self, forceline=None): line = forceline or askinteger(Prog, 'Enter line number') self.view.text.update() self.view.text.focus() if line is not None: maxindex = self.view.text.index(tk.END + '-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.view.text.mark_set(tk.INSERT, '%d.0' % line) #goto line self.view.text.tag_remove(tk.SEL, '1.0', tk.END) #delete selects self.view.text.tag_add(tk.SEL, tk.INSERT, 'insert + 1l') #select line self.view.text.see(tk.INSERT) #scroll to line else: showerror(Prog, 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring(Prog, 'Enter search string') self.view.text.update() self.view.text.focus() self.lastfind = key if key: nocase = configs.get('caseinsens', True) where = self.view.text.search(key, tk.INSERT, tk.END, nocase=nocase) if not where: showerror(Prog, 'String not found') else: pastkey = where + '+%dc' % len(key) self.view.text.tag_remove(tk.SEL, '1.0', tk.END) self.view.text.tag_add(tk.SEL, where, pastkey) self.view.text.mark_set(tk.INSERT, pastkey) self.view.text.see(where) def onRefind(self): self.onFind(self.lastfind) def onDoChange(self, findtext, changeto): if self.view.text.tag_ranges(tk.SEL): self.view.text.delete(tk.SEL_FIRST, tk.SEL_LAST) self.view.text.insert(tk.INSERT, changeto) self.view.text.see(tk.INSERT) self.onFind(findtext) self.view.text.update() def onChange(self): new = tk.Toplevel() new.title(Prog + '- change') tk.Label(new, text='Find text', width=15).grid(row=0, column=0) tk.Label(new, text='Change to', width=15).grid(row=1, column=0) entry1 = tk.Entry(new) entry2 = tk.Entry(new) entry1.grid(row=0, column=1, sticky=tk.EW) entry2.grid(row=1, column=1, sticky=tk.EW) def onFind(): self.onFind(entry1.get()) def onApply(): self.onDoChange(entry1.get(), entry2.get()) tk.Button(new, text='Find', command=onFind).grid(row=0, column=2, sticky=tk.EW) tk.Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=tk.EW) new.columnconfigure(1, weight=1)
def __init__(self, master, cid_path=None, data_path=None, config=dict(), **keywords): """ Set up a frame with widgets to validate ``id_path`` and ``data_path``. :param master: Tk master or root in which the frame should show up :param cid_path: optional preset for :guilabel:`CID` widget :type cid_path: str or None :param data_path: optional preset for :guilabel:`Data` widget :type data_path: str or None :param config: Tk configuration :param keywords: Tk keywords """ assert has_tk assert master is not None if six.PY2: # In Python 2, Frame is an old style class. Frame.__init__(self, master, config, **keywords) else: super().__init__(master, config, **keywords) self._master = master # Define basic layout. self.grid(padx=_PADDING, pady=_PADDING) # self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(_VALIDATION_REPORT_ROW, weight=1) # Choose CID. self._cid_label = Label(self, text='CID:') self._cid_label.grid(row=_CID_ROW, column=0, sticky=E) self._cid_path_entry = Entry(self, width=55) self._cid_path_entry.grid(row=_CID_ROW, column=1, sticky=E + W) self._choose_cid_button = Button(self, command=self.choose_cid, text='Choose...') self._choose_cid_button.grid(row=_CID_ROW, column=2) self.cid_path = cid_path # Choose data. self._data_label = Label(self, text='Data:') self._data_label.grid(row=_DATA_ROW, column=0, sticky=E) self._data_path_entry = Entry(self, width=55) self._data_path_entry.grid(row=_DATA_ROW, column=1, sticky=E + W) self._choose_data_button = Button(self, command=self.choose_data, text='Choose...') self._choose_data_button.grid(row=_DATA_ROW, column=2) self.data_path = data_path # Validate. self._validate_button = Button(self, command=self.validate, text='Validate') self._validate_button.grid(row=_VALIDATE_BUTTON_ROW, column=0, padx=_PADDING, pady=_PADDING) # Validation status text. self._validation_status_text = StringVar() validation_status_label = Label( self, textvariable=self._validation_status_text) validation_status_label.grid(row=_VALIDATE_BUTTON_ROW, column=1) # Validation result. validation_report_frame = LabelFrame(self, text='Validation report') validation_report_frame.grid(row=_VALIDATION_REPORT_ROW, columnspan=3, sticky=E + N + S + W) validation_report_frame.grid_columnconfigure(0, weight=1) validation_report_frame.grid_rowconfigure(0, weight=1) self._validation_report_text = Text(validation_report_frame) self._validation_report_text.grid(column=0, row=0, sticky=E + N + S) _validation_report_scrollbar = Scrollbar(validation_report_frame) _validation_report_scrollbar.grid(column=1, row=0, sticky=N + S + W) _validation_report_scrollbar.config( command=self._validation_report_text.yview) self._validation_report_text.config( yscrollcommand=_validation_report_scrollbar.set) # Set up file dialogs. self._choose_cid_dialog = Open( initialfile=self.cid_path, title='Choose CID', ) self._choose_data_dialog = Open( initialfile=self.data_path, title='Choose data', ) self._save_log_as_dialog = SaveAs( defaultextension='.log', initialfile='cutplace.log', title='Save validation result', ) menubar = Menu(master) master.config(menu=menubar) self._file_menu = Menu(menubar, tearoff=False) self._file_menu.add_command(command=self.choose_cid, label='Choose CID...') self._file_menu.add_command(command=self.choose_data, label='Choose data...') self._file_menu.add_command(command=self.save_validation_report_as, label='Save validation report as...') self._file_menu.add_command(command=self.quit, label='Quit') menubar.add_cascade(label='File', menu=self._file_menu) help_menu = Menu(menubar, tearoff=False) help_menu.add_command(command=self.show_about, label='About') menubar.add_cascade(label='Help', menu=help_menu) self._enable_usable_widgets()
class TextEditor: # mix with menu/toolbar Frame class startfiledir = '.' # for dialogs editwindows = [] # for process-wide quit check ftypes = [('All files', '*'), # for file open dialog ('Text files', '.txt'), # customize in subclass ('Python files', '.py')] # or set in each instance colors = [{'fg':'black', 'bg':'white'}, # color pick list {'fg':'yellow', 'bg':'black'}, # first item is default {'fg':'white', 'bg':'blue'}, # tailor me as desired {'fg':'black', 'bg':'beige'}, # or do PickBg/Fg chooser {'fg':'yellow', 'bg':'purple'}, {'fg':'black', 'bg':'brown'}, {'fg':'lightgreen', 'bg':'darkgreen'}, {'fg':'darkblue', 'bg':'orange'}, {'fg':'orange', 'bg':'darkblue'}] fonts = [('courier', 9+FontScale, 'normal'), # platform-neutral fonts ('courier', 12+FontScale, 'normal'), # (family, size, style) ('courier', 10+FontScale, 'bold'), # or pop up a listbox ('courier', 10+FontScale, 'italic'), # make bigger on Linux ('times', 10+FontScale, 'normal'), # use 'bold italic' for 2 ('helvetica', 10+FontScale, 'normal'), # also 'underline', etc. ('ariel', 10+FontScale, 'normal'), ('system', 10+FontScale, 'normal'), ('courier', 20+FontScale, 'normal')] def __init__(self, loadFirst=''): if not isinstance(self, GuiMaker): raise TypeError('TextEditor needs a GuiMaker mixin') self.setFileName(None) self.lastfind = None self.openDialog = None self.saveDialog = None self.text.focus() # else must click in text if loadFirst: self.update() # 2.1: else @ line 2; see book self.onOpen(loadFirst) def start(self): # run by GuiMaker.__init__ self.menuBar = [ # configure menu/toolbar ('File', 0, # a GuiMaker menu def tree [('Open...', 0, self.onOpen), # build in method for self ('Save', 0, self.onSave), # label, shortcut, callback ('Save As...', 5, self.onSaveAs), ('New', 0, self.onNew), 'separator', ('Quit...', 0, self.onQuit)] ), ('Edit', 0, [('Undo', 0, self.onUndo), ('Redo', 0, self.onRedo), 'separator', ('Cut', 0, self.onCut), ('Copy', 1, self.onCopy), ('Paste', 0, self.onPaste), 'separator', ('Delete', 0, self.onDelete), ('Select All', 0, self.onSelectAll)] ), ('Search', 0, [('Goto...', 0, self.onGoto), ('Find...', 0, self.onFind), ('Refind', 0, self.onRefind), ('Change...', 0, self.onChange), ('Grep...', 3, self.onGrep)] ), ('Tools', 0, [('Pick Font...', 6, self.onPickFont), ('Font List', 0, self.onFontList), 'separator', ('Pick Bg...', 3, self.onPickBg), ('Pick Fg...', 0, self.onPickFg), ('Color List', 0, self.onColorList), 'separator', ('Info...', 0, self.onInfo), ('Clone', 1, self.onClone), ('Run Code', 0, self.onRunCode)] )] self.toolBar = [ ('Save', self.onSave, {'side': LEFT}), ('Cut', self.onCut, {'side': LEFT}), ('Copy', self.onCopy, {'side': LEFT}), ('Paste', self.onPaste, {'side': LEFT}), ('Find', self.onRefind, {'side': LEFT}), ('Help', self.help, {'side': RIGHT}), ('Quit', self.onQuit, {'side': RIGHT})] def makeWidgets(self): # run by GuiMaker.__init__ name = Label(self, bg='black', fg='white') # add below menu, above tool name.pack(side=TOP, fill=X) # menu/toolbars are packed # GuiMaker frame packs itself vbar = Scrollbar(self) hbar = Scrollbar(self, orient='horizontal') text = Text(self, padx=5, wrap='none') # disable line wrapping text.config(undo=1, autoseparators=1) # 2.0, default is 0, 1 vbar.pack(side=RIGHT, fill=Y) hbar.pack(side=BOTTOM, fill=X) # pack text last text.pack(side=TOP, fill=BOTH, expand=YES) # else sbars clipped text.config(yscrollcommand=vbar.set) # call vbar.set on text move text.config(xscrollcommand=hbar.set) vbar.config(command=text.yview) # call text.yview on scroll move hbar.config(command=text.xview) # or hbar['command']=text.xview # 2.0: apply user configs or defaults startfont = configs.get('font', self.fonts[0]) startbg = configs.get('bg', self.colors[0]['bg']) startfg = configs.get('fg', self.colors[0]['fg']) text.config(font=startfont, bg=startbg, fg=startfg) if 'height' in configs: text.config(height=configs['height']) if 'width' in configs: text.config(width =configs['width']) self.text = text self.filelabel = name ############################################################################ # File menu commands ############################################################################ def my_askopenfilename(self): # objects remember last result dir/file if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show() def my_asksaveasfilename(self): # objects remember last result dir/file if not self.saveDialog: self.saveDialog = SaveAs(initialdir=self.startfiledir, filetypes=self.ftypes) return self.saveDialog.show() def onOpen(self, loadFirst=''): doit = (not self.text_edit_modified() or # 2.0 askyesno('PyEdit', 'Text has changed: discard changes?')) if doit: file = loadFirst or self.my_askopenfilename() if file: try: text = open(file, 'r').read() except: showerror('PyEdit', 'Could not open file ' + file) else: self.setAllText(text) self.setFileName(file) self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onSave(self): self.onSaveAs(self.currfile) # may be None def onSaveAs(self, forcefile=None): file = forcefile or self.my_asksaveasfilename() if file: text = self.getAllText() try: open(file, 'w').write(text) except: showerror('PyEdit', 'Could not write file ' + file) else: self.setFileName(file) # may be newly created self.text.edit_modified(0) # 2.0: clear modified flag # don't clear undo/redo stks def onNew(self): doit = (not self.text_edit_modified() or # 2.0 askyesno('PyEdit', 'Text has changed: discard changes?')) if doit: self.setFileName(None) self.clearAllText() self.text.edit_reset() # 2.0: clear undo/redo stks self.text.edit_modified(0) # 2.0: clear modified flag def onQuit(self): """ on Quit menu/toolbar select and wm border X button in toplevel windows; 2.1: don't exit app if others changed; 2.0: don't ask if self unchanged; moved to the top-level window classes at the end since may vary per usage: a Quit in GUI might quit() to exit, destroy() just one Toplevel, Tk, or edit frame, or not be provided at all when run as an attached component; check self for changes, and if might quit(), main windows should check other windows in the process-wide list to see if they have changed too; """ assert False, 'onQuit must be defined in window-specific sublass' def text_edit_modified(self): """ 2.1: this now works! seems to have been a bool result type issue in tkinter; 2.0: self.text.edit_modified() broken in Python 2.4: do manually for now; """ return self.text.edit_modified() #return self.tk.call((self.text._w, 'edit') + ('modified', None)) ############################################################################ # Edit menu commands ############################################################################ def onUndo(self): # 2.0 try: # tk8.4 keeps undo/redo stacks self.text.edit_undo() # exception if stacks empty except TclError: # menu tear-offs for quick undo showinfo('PyEdit', 'Nothing to undo') def onRedo(self): # 2.0: redo an undone try: self.text.edit_redo() except TclError: showinfo('PyEdit', 'Nothing to redo') def onCopy(self): # get text selected by mouse, etc. if not self.text.tag_ranges(SEL): # save in cross-app clipboard showerror('PyEdit', 'No text selected') else: text = self.text.get(SEL_FIRST, SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def onDelete(self): # delete selected text, no save if not self.text.tag_ranges(SEL): showerror('PyEdit', 'No text selected') else: self.text.delete(SEL_FIRST, SEL_LAST) def onCut(self): if not self.text.tag_ranges(SEL): showerror('PyEdit', 'No text selected') else: self.onCopy() # save and delete selected text self.onDelete() def onPaste(self): try: text = self.selection_get(selection='CLIPBOARD') except TclError: showerror('PyEdit', 'Nothing to paste') return self.text.insert(INSERT, text) # add at current insert cursor self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(SEL, INSERT+'-%dc' % len(text), INSERT) self.text.see(INSERT) # select it, so it can be cut def onSelectAll(self): self.text.tag_add(SEL, '1.0', END+'-1c') # select entire text self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top ############################################################################ # Search menu commands ############################################################################ def onGoto(self, forceline=None): line = forceline or askinteger('PyEdit', 'Enter line number') self.text.update() self.text.focus() if line is not None: maxindex = self.text.index(END+'-1c') maxline = int(maxindex.split('.')[0]) if line > 0 and line <= maxline: self.text.mark_set(INSERT, '%d.0' % line) # goto line self.text.tag_remove(SEL, '1.0', END) # delete selects self.text.tag_add(SEL, INSERT, 'insert + 1l') # select line self.text.see(INSERT) # scroll to line else: showerror('PyEdit', 'Bad line number') def onFind(self, lastkey=None): key = lastkey or askstring('PyEdit', 'Enter search string') self.text.update() self.text.focus() self.lastfind = key if key: # 2.0: nocase nocase = configs.get('caseinsens', True) # 2.0: config where = self.text.search(key, INSERT, END, nocase=nocase) if not where: # don't wrap showerror('PyEdit', 'String not found') else: pastkey = where + '+%dc' % len(key) # index past key self.text.tag_remove(SEL, '1.0', END) # remove any sel self.text.tag_add(SEL, where, pastkey) # select key self.text.mark_set(INSERT, pastkey) # for next find self.text.see(where) # scroll display def onRefind(self): self.onFind(self.lastfind) def onChange(self): """ non-modal find/change dialog 2.1: pass per-dialog inputs to callbacks, may be > 1 change dialog open """ new = Toplevel(self) new.title('PyEdit - change') Label(new, text='Find text?', relief=RIDGE, width=15).grid(row=0, column=0) Label(new, text='Change to?', relief=RIDGE, width=15).grid(row=1, column=0) entry1 = Entry(new) entry2 = Entry(new) entry1.grid(row=0, column=1, sticky=EW) entry2.grid(row=1, column=1, sticky=EW) def onFind(): # use my entry in enclosing scope self.onFind(entry1.get()) # runs normal find dialog callback def onApply(): self.onDoChange(entry1.get(), entry2.get()) Button(new, text='Find', command=onFind ).grid(row=0, column=2, sticky=EW) Button(new, text='Apply', command=onApply).grid(row=1, column=2, sticky=EW) new.columnconfigure(1, weight=1) # expandable entries def onDoChange(self, findtext, changeto): # on Apply in change dialog: change and refind if self.text.tag_ranges(SEL): # must find first self.text.delete(SEL_FIRST, SEL_LAST) self.text.insert(INSERT, changeto) # deletes if empty self.text.see(INSERT) self.onFind(findtext) # goto next appear self.text.update() # force refresh def onGrep(self): """ new in version 2.1: threaded external file search; search matched filenames in directory tree for string; listbox clicks open matched file at line of occurrence; search is threaded so the GUI remains active and is not blocked, and to allow multiple greps to overlap in time; could use threadtools, but avoid loop in no active grep; """ from PP4E.Gui.ShellGui.formrows import makeFormRow # nonmodal dialog: get dirnname, filenamepatt, grepkey popup = Toplevel() popup.title('PyEdit - grep') var1 = makeFormRow(popup, label='Directory root', width=18, browse=False) var2 = makeFormRow(popup, label='Filename pattern', width=18, browse=False) var3 = makeFormRow(popup, label='Search string', width=18, browse=False) var1.set('.') # current dir var2.set('*.py') # initial values Button(popup, text='Go', command=lambda: self.onDoGrep(var1.get(), var2.get(), var3.get())).pack() def onDoGrep(self, dirname, filenamepatt, grepkey): # on Go in grep dialog: populate scrolled list with matches # tbd: should producer thread be daemon so dies with app? import threading, queue # make non-modal un-closeable dialog mypopup = Tk() mypopup.title('PyEdit - grepping') status = Label(mypopup, text='Grep thread searching for: %r...' % grepkey) status.pack(padx=20, pady=20) mypopup.protocol('WM_DELETE_WINDOW', lambda: None) # ignore X close # start producer thread, consumer loop myqueue = queue.Queue() threadargs = (filenamepatt, dirname, grepkey, myqueue) threading.Thread(target=self.grepThreadProducer, args=threadargs).start() self.grepThreadConsumer(grepkey, myqueue, mypopup) def grepThreadProducer(self, filenamepatt, dirname, grepkey, myqueue): """ in a non-GUI parallel thread: queue find.find results list; could also queue matches as found, but need to keep window; """ from PP4E.Tools.find import find matches = [] for filepath in find(pattern=filenamepatt, startdir=dirname): try: for (linenum, linestr) in enumerate(open(filepath)): if grepkey in linestr: message = '%s@%d [%s]' % (filepath, linenum + 1, linestr) matches.append(message) except UnicodeDecodeError: print('Unicode error in:', filepath) myqueue.put(matches) def grepThreadConsumer(self, grepkey, myqueue, mypopup): """ in the main GUI thread: watch queue for results or []; there may be multiple active grep threads/loops/queues; there may be other types of threads/checkers in process, especially when PyEdit is attached component (PyMailGUI); """ import queue try: matches = myqueue.get(block=False) except queue.Empty: self.after(250, self.grepThreadConsumer, grepkey, myqueue, mypopup) else: mypopup.destroy() # close status self.update() # erase it now if not matches: showinfo('PyEdit', 'Grep found no matches for: %r' % grepkey) else: self.grepMatchesList(matches, grepkey) def grepMatchesList(self, matches, grepkey): # populate list after successful matches from PP4E.Gui.Tour.scrolledlist import ScrolledList print('Matches for %s: %s' % (grepkey, len(matches))) # catch list double-click class ScrolledFilenames(ScrolledList): def runCommand(self, selection): file, line = selection.split(' [', 1)[0].split('@') editor = TextEditorMainPopup(loadFirst=file, winTitle=' grep match') editor.onGoto(int(line)) editor.text.focus_force() # no, really # new non-modal widnow popup = Tk() popup.title('PyEdit - grep matches: %r' % grepkey) ScrolledFilenames(parent=popup, options=matches) ############################################################################ # Tools menu commands ############################################################################ def onFontList(self): self.fonts.append(self.fonts[0]) # pick next font in list del self.fonts[0] # resizes the text area self.text.config(font=self.fonts[0]) def onColorList(self): self.colors.append(self.colors[0]) # pick next color in list del self.colors[0] # move current to end self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg']) def onPickFg(self): self.pickColor('fg') # added on 10/02/00 def onPickBg(self): # select arbitrary color self.pickColor('bg') # in standard color dialog def pickColor(self, part): # this is too easy (triple, hexstr) = askcolor() if hexstr: self.text.config(**{part: hexstr}) def onInfo(self): text = self.getAllText() # added on 5/3/00 in 15 mins bytes = len(text) # words uses a simple guess: lines = len(text.split('\n')) # any separated by whitespace words = len(text.split()) # 3.x: bytes is really chars index = self.text.index(INSERT) # str is unicode code points where = tuple(index.split('.')) showinfo('PyEdit Information', 'Current location:\n\n' + 'line:\t%s\ncolumn:\t%s\n\n' % where + 'File text statistics:\n\n' + 'chars:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words)) def onClone(self): """ open a new edit window without changing one already open inherits quit and other behavior of window that it clones """ new = Toplevel() # a new edit window in same process myclass = self.__class__ # instance's (lowest) class object myclass(new) # attach/run instance of my class def onRunCode(self, parallelmode=True): """ run Python code being edited--not an IDE, but handy; tries to run in file's dir, not cwd (may be PP4E root); inputs and adds command-line arguments for script files; code's stdin/out/err = editor's start window, if any: run with a console window to see code's print outputs; but parallelmode uses start to open a DOS box for I/O; module search path will include '.' dir where started; in non-file mode, code's Tk root may be PyEdit's window; subprocess or multiprocessing modules may work here too; 2.1: fixed to use base file name after chdir, not path; 2.1: use StartArs to allow args in file mode on Windows; """ def askcmdargs(): return askstring('PyEdit', 'Commandline arguments?') or '' from PP4E.launchmodes import System, Start, StartArgs, Fork filemode = False thefile = str(self.getFileName()) if os.path.exists(thefile): filemode = askyesno('PyEdit', 'Run from file?') if not filemode: # run text string cmdargs = askcmdargs() namespace = {'__name__': '__main__'} # run as top-level sys.argv = [thefile] + cmdargs.split() # could use threads exec(self.getAllText() + '\n', namespace) # exceptions ignored elif self.text_edit_modified(): # 2.0: changed test showerror('PyEdit', 'Text changed: save before run') else: cmdargs = askcmdargs() mycwd = os.getcwd() # cwd may be root dirname, filename = os.path.split(thefile) # get dir, base os.chdir(dirname or mycwd) # cd for filenames thecmd = filename + ' ' + cmdargs # 2.1: not theFile if not parallelmode: # run as file System(thecmd, thecmd)() # block editor else: if sys.platform[:3] == 'win': # spawn in parallel run = StartArgs if cmdargs else Start # 2.1: support args run(thecmd, thecmd)() # or always Spawn else: Fork(thecmd, thecmd)() # spawn in parallel os.chdir(mycwd) # go back to my dir def onPickFont(self): """ 2.0 non-modal font spec dialog 2.1: pass per-dialog inputs to callback, may be > 1 font dialog open """ from PP4E.Gui.ShellGui.formrows import makeFormRow popup = Toplevel(self) popup.title('PyEdit - font') var1 = makeFormRow(popup, label='Family', browse=False) var2 = makeFormRow(popup, label='Size', browse=False) var3 = makeFormRow(popup, label='Style', browse=False) var1.set('courier') var2.set('12') # suggested vals var3.set('bold italic') # see pick list for valid inputs Button(popup, text='Apply', command= lambda: self.onDoFont(var1.get(), var2.get(), var3.get())).pack() def onDoFont(self, family, size, style): try: self.text.config(font=(family, int(size), style)) except: showerror('PyEdit', 'Bad font specification') ############################################################################ # Utilities, useful outside this class ############################################################################ def isEmpty(self): return not self.getAllText() def getAllText(self): return self.text.get('1.0', END+'-1c') # extract text as a string def setAllText(self, text): self.text.delete('1.0', END) # store text string in widget self.text.insert(END, text) # or '1.0' self.text.mark_set(INSERT, '1.0') # move insert point to top self.text.see(INSERT) # scroll to top, insert set def clearAllText(self): self.text.delete('1.0', END) # clear text in widget def getFileName(self): return self.currfile def setFileName(self, name): # also: onGoto(linenum) self.currfile = name # for save self.filelabel.config(text=str(name)) def setBg(self, color): self.text.config(bg=color) # to set manually from code def setFg(self, color): self.text.config(fg=color) # 'black', hexstring def setFont(self, font): self.text.config(font=font) # ('family', size, 'style') def setHeight(self, lines): # default = 24h x 80w self.text.config(height=lines) # may also be from textCongif.py def setWidth(self, chars): self.text.config(width=chars) def clearModified(self): self.text.edit_modified(0) # clear modified flag def isModified(self): return self.text_edit_modified() # changed since last reset? def help(self): showinfo('About PyEdit', helptext % ((Version,)*2))
def my_askopenfilename(self): # objects remember last result dir/file if not self.openDialog: self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show()
def my_askopenfilename(self): self.openDialog = Open(initialdir=self.startfiledir, filetypes=self.ftypes) return self.openDialog.show()