Okay, I've spent some more time playing with this program.  There are a bunch
of changes but the biggest is probably my switch from IDLE to LEO for
development.  I don't want to proselytize too much but Leo is a great tool. 
It allows you to easily organize a large amount of data, notes, example, and
code in a logical way, and also to generate a number of documents from one
leo file.  If anyone is interested I could post my leo file for IdleCE.  Some
stuff is missing since I lost things before moving to Leo, but it still could
be insightful.

Anyway this release added/improved on the editing features, incorporates
Sabastion's menus and his fix for the selection bug, and just some general
repairs.  I've also added a new bug, don't try to indent a selection if the
selection includes the last line in a file.  Its a strange bug but all you
need is a extra newline at the end of the file to avoid it.

I can't remember if I said anything about it before but one of my favorite
features is the 'context' menu.  Select some text and tap on the selection, a
menu will appear with common editing options.  It might be cool if I could
expand this to be truly context sensitive.  Maybe some kind of word
completion/insertion...  I also thought a special clipboard might be cool,
something like kclipper on KDE.  Oh well, I have some other stuff to try
before I get to the fancy stuff.
#IdleCE

import sys
for p in sys.path:
    if p[-12:].lower() == "python23.zip":
        sys.path.append(p + "\\lib-tk")
        break
import os
#import re
from Tkinter import *
import tkMessageBox
import tkFileDialog

import keyword
from string import ascii_letters, digits, punctuation

OS = os.name.lower() #Should be 'ce' on WinCE


class Idle:
    """The base class for the mini idle."""
    def __init__(self,root):
        self.root = root
        root.grid_rowconfigure(1, weight=1)
        root.grid_columnconfigure(0, weight=1)

        frame = Frame(root)
        frame.grid(row=0,column=0,columnspan=2,sticky=NSEW)

        self.editor = SyntaxHighlightingText(root)
        self.editor.grid(row=1,column=0,columnspan=2,sticky=N+S)

        panbar = Scrollbar(root,orient=HORIZONTAL)
        panbar.grid(row=2,column=0,columnspan=2,sticky=E+W)

        scrollbar = Scrollbar(root)
        scrollbar.grid(row=1,column=2,sticky=N+S)

        self.editor.configure(xscrollcommand=panbar.set)
        self.editor.configure(yscrollcommand=scrollbar.set)

        scrollbar.config(command=self.editor.yview)
        panbar.config(command=self.editor.xview)

        btn = Menubutton(frame,text="File",padx=1,pady=1)
        btn.pack(side=LEFT)
        submenu = Menu(btn,tearoff=False)
        btn["menu"] = submenu
        submenu.add_command(label="New",command=self.new)
        submenu.add_command(label="Open",command=self.open)
        submenu.add_command(label="Save",command=self.save)
        submenu.add_command(label="Save as",command=self.saveas)
        submenu.add_command(label="Exit",command=self.exit)

        btn = Menubutton(frame,text="Edit",padx=1,pady=1)
        btn.pack(side=LEFT)
        submenu = Menu(btn,tearoff=False)
        btn["menu"] = submenu
        submenu.add_command(label="Undo",command=self.editor.edit_undo)
        submenu.add_command(label="Redo",command=self.editor.edit_redo)
        submenu.add_separator()
        submenu.add_command(label="Cut",command=self.editor.cut)
        submenu.add_command(label="Copy",command=self.editor.copy)
        submenu.add_command(label="Paste",command=self.editor.paste)
        submenu.add_separator()
        submenu.add_command(label="Indent",command=self.editor.indent_region)
        submenu.add_command(label="Dedent",command=self.editor.dedent_region)
        submenu.add_command(label="Comment",command=self.editor.comment_region)
        submenu.add_command(label="UnComment",command=self.editor.uncomment_region)

        btn = Menubutton(frame,text="Help",padx=1,pady=1)
        btn.pack(side=LEFT)
        submenu = Menu(btn,tearoff=False)
        btn["menu"] = submenu
        submenu.add_command(label="About",command=self.about_dialog)

        self.root.protocol("WM_DELETE_WINDOW", self.exit)

    def about_dialog(self):
        """Sillyness"""
        top = Toplevel()
        top.title("about")
        top.resizable(0,0)
        top.focus_set()

        about = """

        IdleCE v0.7

    A miniaturized imitation of
    the python ide: idle.

    This software is distibuted
    under the Gnu-GPL. Please Visit
    http://www.gnu.org/licenses/gpl.txt
    to see the license.
        """
        info = Label(top,text=about)
        info.pack(side=TOP,padx=6)

        button = Button(top, text="Dismiss", command=top.destroy)
        button.pack(side=BOTTOM,fill=X)

    def open(self):
        # Opens a file and colorizes it
        self.filename = tkFileDialog.askopenfilename()
        if OS == 'ce': # Just passing filename fails...
            self.filename = self.filename.replace('/','\\')
        try:
            file = open(self.filename)
            self.editor.delete("1.0",END)
            text = file.readlines()
            file.close()
            for i in range(len(text)-1):
                self.editor.insert(END,text[i])
                self.editor.colorize(i+1,len(text[i])) #colorize(textline,lastcolumn)
            self.editor.see("1.0")
        except IOError:
            print self.filename

    def saveas(self):
        # Called if no filename is set or Saveas is picked
        self.filename = tkFileDialog.asksaveasfilename()
        if OS == 'ce':
            self.filename = self.filename.replace('/','\\')
        try:
            file = open(self.filename,'w')
            text = self.editor.get("1.0",END)
            file.write(text)
            file.flush()
            file.close()
        except Exception, info:
            tkMessageBox.showerror('Exception!',info)

    def save(self):
        try:
            file = open(self.filename,'w')
            text = self.editor.get("1.0",END)
            file.write(text)
            file.flush()
            file.close()
        except:
            self.saveas() # If no file has been accessed

    def new(self):
        if len(self.editor.get("1.0",END)) >= 2:
            if tkMessageBox.askokcancel("New File","Discard current file?"):
                self.editor.delete("1.0",END)
                self.filename = ""
        else:
            self.editor.delete("1.0",END)
            self.filename = ""

    def exit(self):
        # Ask the user to save if the file isn't empty
        if len(self.editor.get("1.0",END)) >= 2:
            answer = tkMessageBox._show("Save File","Save the current file?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL)
            print answer
            if answer == 'yes':
                self.save()
            elif answer == 'cancel':
                return
        # End the program firmly.
        root.destroy()
        root.quit()



class SyntaxHighlightingText(Text):
    """A syntax highlighting text widget from the web customized with
some methods from Idle and some special methods.

This could be moved to a module and generalized.  Then imported and
sub-classed here for specialization."""
    tags = {'com':'#C00',   #comment
            'str':'#0A0',   #string
            'kw': 'orange', #keyword
            'obj':'#00F',   #function/class name
            'int': 'blue'   #integers
            }


    def __init__(self, root):
        if OS == 'ce':
            w = 40
            h = 10
        else:
            w = 80
            h = 25
        Text.__init__(self,root,wrap=NONE,bd=0,width=w,height=h,undo=1,maxundo=50)
        # Non-wrapping, no border, undo turned on, max undo 50
        self.text = self # For the methods taken from IDLE
        self.root = root
        self.config_tags()
        self.characters = ascii_letters + digits + punctuation
        self.tabwidth = 8    # for IDLE use, must remain 8 until Tk is fixed
        self.indentwidth = 4 # Should perhaps be 2 due to the small screen??
        self.indention = 0   # The current indention level
        self.set_tabwidth(self.indentwidth) # IDLE...
        self.sel_store = []

        # create a popup menu
        self.menu = Menu(root, tearoff=0)
        self.menu.add_command(label="Undo", command=self.edit_undo)
        self.menu.add_command(label="Redo", command=self.edit_redo)
        self.menu.add_separator()
        self.menu.add_command(label="Cut", command=self.cut)
        self.menu.add_command(label="Copy", command=self.copy)
        self.menu.add_command(label="Paste", command=self.paste)
        self.menu.add_separator()
        self.menu.add_command(label="Indent", command=self.indent_region)
        self.menu.add_command(label="Dedent", command=self.dedent_region)
        self.menu.add_command(label="Comment", command=self.comment_region)
        self.menu.add_command(label="UnComment", command=self.uncomment_region)

        self.bind('<Key>', self.key_press)      # For scanning input
        self.bind('<Return>',self.autoindent)   # Overides default binding
        self.bind('<Tab>',self.autoindent)      # increments self.indention
        self.bind('<BackSpace>',self.autoindent)# decrements self.indention

        self.bind_all('<Control-C>',self.copy)
        self.bind_all('<Control-X>',self.cut)
        self.bind_all('<Control-V>',self.paste)
        self.bind_all('<Control-Z>',self.edit_undo)
        self.bind_all('<Control-Y>',self.edit_redo)

        self.bind('<Double-Button-1>',self.paste)# pastes text
        self.tag_bind(SEL,'<Button-1>',self.popup)
        self.bind('<Control-i>',self.indent_region)
        self.bind('<Control-d>',self.dedent_region)

    def popup(self, event):
        """Edit popup menu with a special attribute for the selection."""
        self.sel_store = self.get_region() # returns head, tail, chars, lines
        self.menu.post(event.x_root, event.y_root)
        return "break"

    def getline(self,row):
        """the string 'row.end' is an alias for the last index in a row."""
        line = ''
        lastcol = 0
        char = self.get(self.to_index(row, lastcol))
        while char != '\n':
            line += char
            lastcol += 1
            char = self.get(self.to_index(row, lastcol))

        return line, lastcol


    def to_index(self, row,column):
        """The <'%s.%d'%(row, col)> pattern was repeated all over the place,
        Now it should be replaced with a call to self.to_index(row,col)."""
        return '%s.%d'%(row, column)

    def get_tabwidth(self):
        # From IDLE
        current = self['tabs'] or 5000
        return int(current)

    def set_tabwidth(self, newtabwidth):
        # From IDLE
        text = self
        if self.get_tabwidth() != newtabwidth:
            pixels = text.tk.call("font", "measure", text["font"],
                                  "-displayof", text.master,
                                  "n" * newtabwidth)
            text.configure(tabs=pixels)

    def config_tags(self):
        # Sets up the tags and their colors
        for tag, val in self.tags.items():
            self.tag_config(tag, foreground=val)

    def remove_tags(self, start, end):
        for tag in self.tags.keys():
            self.tag_remove(tag, start, end)

    def get_selection_indices(self):
         # If a selection is defined in the text widget, return (start,
        # end) as Tkinter text indices, otherwise return (None, None)
        try:
            first = self.text.index("sel.first")
            last = self.text.index("sel.last")
            return first, last
        except TclError:
            return None, None

    def get_region(self):
        """Code directly from IDLE, should help with my indent/dedent problems."""
        text = self.text
        first, last = self.get_selection_indices()
        if first and last:
            head = text.index(first + " linestart")
            tail = text.index(last + "-1c lineend +1c")
        else:
            head = text.index("insert linestart")
            tail = text.index("insert lineend +1c")
        chars = text.get(head, tail)
        lines = chars.split("\n")
        return head, tail, chars, lines

    def set_region(self, head, tail, chars, lines):
        """Mate to get_region, should be very helpfull."""
        text = self.text
        newchars = "\n".join(lines)
        if newchars == chars:
            text.bell()
            return
        text.tag_remove("sel", "1.0", "end")
        text.mark_set("insert", head)
        self.edit_separator()
        text.delete(head, tail)
        text.insert(head, newchars)
        self.edit_separator()
        text.tag_add("sel", head, "insert")


    def cut(self,event=0):
        self.clipboard_clear()
        if self.sel_store: # Sent by the popup
            self.delete(self.sel_store[0],self.sel_store[1])
            self.clipboard_append(self.sel_store[2])
            self.sel_store = []
        else: # Sent by menu
            Selection=self.get_selection_indices()
            if len(Selection)>0:
                SelectedText = self.get(Selection[0],Selection[1])
            self.delete(Selection[0],Selection[1])
            self.clipboard_append(SelectedText)
        return "break"

    def copy(self,event=0):
        self.clipboard_clear()
        if self.sel_store: # Sent by the popup
            self.clipboard_append(self.sel_store[2])
            self.sel_store = []
        else: # Sent by menu
            Selection=self.get_selection_indices()
            if len(Selection)>0:
                SelectedText = self.get(Selection[0],Selection[1])
            self.clipboard_append(SelectedText)
        return "break"


    def paste(self,event=0):
        # This calls colorize for the pasted lines, on windows ctrl-v
        # never reaches this code...
        SelectedText = self.root.selection_get(selection='CLIPBOARD')
        cline = int(self.index(INSERT).split('.')[0])
        self.insert(INSERT, SelectedText)
        lastcol = int(self.index(str(cline)+'.end').split('.')[1])
        print 'colorizing ' + self.to_index(cline,lastcol)
        self.colorize(cline,lastcol)
        return "break"

    def autoindent(self,event):
        if event.keysym == 'Return':
            self.edit_separator() # For undo/redo
            index = self.index(INSERT).split('.')
            print index
            line = int(index[0])
            column = int(index[1])
            if self.get(self.to_index(line, column-1)) == ':':
                self.indention += 1
            self.insert(INSERT,'\n')
            self.insert(INSERT,(' '*self.indentwidth)*self.indention)
            return 'break' # Overides standard bindings
        elif event.keysym == 'Tab':
            self.edit_separator()
            first,last = self.get_selection_indices()
            if first and last:
                if event.state == 9:
                    #this is a <Shift-Tab>, a dedent
                    self.dedent_region()
                else:
                    self.indent_region()
                return "break"
            prefix = self.get("insert linestart", "insert")
            if prefix.lstrip() != '':
                self.insert(INSERT,' '*self.indentwidth)
            else:
                self.indention += 1
                self.insert(INSERT,' '*self.indentwidth)
            return "break"
        elif event.keysym == 'BackSpace':
            # The current idea of virtual tabstops is very vaguely implimented
            self.edit_separator()
            index = self.index(INSERT).split('.')
            print index
            line = int(index[0])
            column = int(index[1])
            if self.indention > 0:
                prev = ''
                for i in range(self.indentwidth):
                    # Get the item starting at the column preceeding
                    # the current to the column on indents width left.
                    prev += self.get(self.to_index(line, column-(i+1)))
                print len(prev)
                if prev == ' '*self.indentwidth:
                    self.delete(self.to_index(line,(column)-self.indentwidth),\
                                self.to_index(line,column))
                    self.indention -= 1
                    return "break" # Don't want the backspace handled twice.
    def indent_region(self, event = ''):
        """This code taken from IDLE works almost always.
        The only problem I've found occurs if you try to
        indent the last line in a file...

        Code added for popup menu call..."""
        if self.sel_store: # Sent by the popup
            head, tail, chars, lines = self.sel_store
            self.sel_store = []
        else:
            head, tail, chars, lines = self.get_region()
        for pos in range(len(lines)):
            line = lines[pos]
            if line:
                line = ' '*self.indentwidth + line
                lines[pos] = line
        self.set_region(head, tail, chars, lines)
        return "break"

    def dedent_region(self, event = ''):
        """Code taken from IDLE, edited for use with
        popup menu."""
        if self.sel_store: # Sent by the popup
            head, tail, chars, lines = self.sel_store
            self.sel_store = []
        else:
            head, tail, chars, lines = self.get_region()
        for pos in range(len(lines)):
            line = lines[pos]
            if line and line[:self.indentwidth] == ' '*self.indentwidth:
                line = line[self.indentwidth:]
                print line
                lines[pos] = line
        self.set_region(head, tail, chars, lines)
        return "break"

    def comment_region(self, event = ''):
        """Code taken from IDLE, edited for use with
        popup menu."""
        if self.sel_store: # Sent by the popup
            head, tail, chars, lines = self.sel_store
            self.sel_store = []
        else:
            head, tail, chars, lines = self.get_region()
        for pos in range(len(lines) - 1):
            line = lines[pos]
            lines[pos] = '##' + line
        self.set_region(head, tail, chars, lines)

    def uncomment_region(self, event = ''):
        """Code taken from IDLE, edited for use with
        popup menu."""
        if self.sel_store: # Sent by the popup
            head, tail, chars, lines = self.sel_store
            self.sel_store = []
        else:
            head, tail, chars, lines = self.get_region()
        for pos in range(len(lines)):
            line = lines[pos]
            if not line:
                continue
            if line[:2] == '##':
                line = line[2:]
            elif line[:1] == '#':
                line = line[1:]
            lines[pos] = line
        self.set_region(head, tail, chars, lines)

    def key_press(self, key):
        """This function was origonaly the home of the colorize code.
    Now it is mostly useless unless you want to watch for a certain character."""
        if key.char in ' :[(]),"\'':
            self.edit_separator() # For undo/redo

        cline = self.index(INSERT).split('.')[0]
        lastcol = self.getline(cline)[1]
        self.colorize(cline,lastcol)

    def colorize(self,cline,lastcol):
        """Not so simple syntax highlighting."""
        buffer = self.get(self.to_index(cline,0),self.to_index(cline,lastcol))
        tokenized = buffer.split(' ')

        self.remove_tags(self.to_index(cline, 0), self.to_index(cline, lastcol))

        quotes = 0
        start = 0
        for i in range(len(buffer)):
            if buffer[i] in ['"',"'"]: # Doesn't distinguish between single and double quotes...
                if quotes:
                   self.tag_add('str', self.to_index(cline, start), self.to_index(cline, i+1))
                   quotes = 0
                else:
                    start = i
                    quotes = 1
            elif buffer[i] == '#':
                self.tag_add('com', self.to_index(cline, i), self.to_index(cline, len(buffer)))
                break

        start, end = 0, 0
        obj_flag = 0
        for token in tokenized:
            end = start + len(token)
            if obj_flag:
                self.tag_add('obj', self.to_index(cline, start), self.to_index(cline, end))
                obj_flag = 0
            if token.strip() in keyword.kwlist:
                self.tag_add('kw', self.to_index(cline, start), self.to_index(cline, end))
                if token.strip() in ['def','class']:
                    obj_flag = 1
            else:
                for index in range(len(token)):
                    try:
                        int(token[index])
                    except ValueError:
                        pass
                    else:
                        self.tag_add('int', self.to_index(cline, start+index))

            start += len(token)+1





if __name__ == '__main__':
    root = Tk()
    root.title('IdleCE')
    if OS=='ce':
        sizex, sizey = root.wm_maxsize()
        root.wm_geometry("%dx%d+0+%d"%(sizex-6,sizey*37/64+1,sizey/90))
        b1motion = root.bind_class('Text','<B1-Motion>')
        # Deep magic by Sebastian, fixes text selection issues.
        root.bind_class('Text','<B1-Motion>','if {![info exists ::tk::Priv(ignoreB1Motion)]} {%s}'%b1motion)
        root.bind_class('Text','<B1-Leave>','set ::tk::Priv(ignoreB1Motion) 1')
        root.bind_class('Text','<B1-Enter>','array unset ::tk::Priv ignoreB1Motion')
    app = Idle(root)
    root.mainloop()
_______________________________________________
PythonCE mailing list
PythonCE@python.org
http://mail.python.org/mailman/listinfo/pythonce

Reply via email to