Example #1
0
    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")
Example #2
0
 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")
Example #3
0
def ask_file(title, message, filetypes, directory, mult):
    dlg = Open(title=title,
               filetypes=filetypes,
               initialdir=directory,
               multiple=mult)
    out = dlg.show()
    return out
Example #4
0
File: TSPRO.py Project: whigg/TSPRO
 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'])
Example #5
0
 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)
Example #6
0
    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')
Example #7
0
    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.")
Example #8
0
    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)
Example #9
0
    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
Example #10
0
	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_)
Example #11
0
 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()
Example #12
0
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()
Example #13
0
    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)
Example #14
0
File: TSPRO.py Project: whigg/TSPRO
        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')
Example #15
0
 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)
Example #17
0
    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)
Example #19
0
    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)
Example #20
0
    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
Example #21
0
    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
Example #23
0
    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
Example #24
0
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)
Example #25
0
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("ä", "&auml"))
            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("ä", "&auml"))
                                    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("ä", "&auml"))
                                    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("ä", "&auml"))
                                    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("ä", "&auml"))
                                        #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("ä","&auml"))
                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("ä", "&auml;")
        f.write(unikoodi.replace("ö", "&ouml;"))
        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()
Example #26
0
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))
Example #31
0
    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)
Example #32
0
        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()
Example #33
0
    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))
Example #35
0
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)
Example #36
0
        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):      # objects remember last result dir/file
     if not self.openDialog:
        self.openDialog = Open(initialdir=self.startfiledir,
                               filetypes=self.ftypes)
     return self.openDialog.show()
Example #40
0
 def my_askopenfilename(self):
     self.openDialog = Open(initialdir=self.startfiledir,
                            filetypes=self.ftypes)
     return self.openDialog.show()
Example #41
0
def ask_file(title, message, filetypes, directory, mult):
    dlg = Open(title=title, filetypes=filetypes, initialdir=directory,
               multiple=mult)
    out = dlg.show()
    return out