Welcome, guest | Sign In | My Account | Store | Cart

Tag editing widget for tkinter. It's similar to jQuery tag it: http://aehlke.github.io/tag-it/

Python, 152 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Author: Miguel Martinez Lopez
#
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))

try:
    from Tkinter import Text, Frame, Label, PhotoImage, Button
    from ttk import Scrollbar

    from Tkconstants import *
except ImportError:
    from tkinter import Text, Frame, Label, PhotoImage, Button
    from tkinter.ttk import Scrollbar

    from tkinter.constants import *


class Tag(Frame):
    BACKGROUND = "#dddddd"
    PADDING = (5,7,5,7)
    LABEL_AND_BUTTON_SPACING = 2
    
    FOREGROUND_LABEL = "#0073ea"
    def __init__(self, master, text):
        self._text = text
        
        Frame.__init__(self, master, background=self.BACKGROUND, highlightbackground="red", borderwidth=1, relief=SOLID)
        
        Label(self, text=text, background=self.BACKGROUND, foreground=self.FOREGROUND_LABEL).pack(side=LEFT, pady=(self.PADDING[1], self.PADDING[3]), padx=(self.PADDING[0],0))
        
        self._close_IMG = PhotoImage(data="iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAFySURBVDiNpZM9juJAEIU/WxPY/EstQdICEYAQ+A5eX2CjDTgiMRfwzh4BmcAkTgAJiZZFsF7TbvAmeMUKZoKhouqnrqp+9V5bZVnyStgvVQNv94cgCN6EEAsApdQ8DEPzGQ5gVRRul5ae5wW2bbNarUKl1HeAZ3jV5N8LhBCL6XQaSCmdy+WC53lBFEVLgNlsFkgpHYDr9RpEUbQAfjxQMMagtSaOY8bjsTOZTHyAXq/nrNdrRqMRWuvnO1BKzTebzdIYEwwGAyeOY4bDYQ3glpMkSZ4kSaiUmj/s4H4P/X7f73a7td1uh2VZSCnZ7/fZdrt9v+f/QKEKrXWptaZqnuc5xpinhnlQQQjxTQjhnk4n2u02AFWulPqjlPr5oQqtVstvNBpumqa4rsvhcPhdliWdTqeepin1et0tisIHPlYhz3Ns2+Z4PGZZlv264X6z2aydz2eMMfcl/6sALG8TKIrivTLSE/xTFb5m5a/Gy7/xL3CL+6G7+HoOAAAAAElFTkSuQmCC")
        Button(self, highlightthickness=0, borderwidth=0, background=self.BACKGROUND, activebackground=self.BACKGROUND, image=self._close_IMG, command=self.destroy).pack(side=LEFT, pady=(self.PADDING[1], self.PADDING[3]), padx=(self.LABEL_AND_BUTTON_SPACING,self.PADDING[2]))
        
    @property
    def text(self):
        return self._text
        

class Tag_Entry(Frame):
    TAG_ENTRY_PADX = 7
    TAG_ENTRY_PADY = 5
    SPACING_BETWEEN_TAGS = 3

    def __init__(self, master, **kwargs):
        Frame.__init__(self, master)
        
        textarea_frame = Frame(self, bd=1, relief=SOLID,**kwargs)
        textarea_frame.pack(fill=X)
        textarea_frame.pack_propagate(False)
        
        self.textarea = Text(textarea_frame, height=1, pady=self.TAG_ENTRY_PADY, padx=self.TAG_ENTRY_PADX, highlightthickness =0, spacing1=0, spacing2=0, spacing3=0, borderwidth=0, wrap="none")
        self.textarea.pack(expand=True, fill=BOTH, padx=2)

        scrollbar = Scrollbar(self, orient=HORIZONTAL, command=self.textarea.xview)
        scrollbar.pack(fill=X)

        self.textarea.configure(xscrollcommand=scrollbar.set)
        self.textarea.bind("<KeyPress>",self._on_keypress)

        tag = Tag(self.textarea, "")
        self.textarea.window_create("1.0", window=tag)
        self.update_idletasks()

        tag_reqheight = tag.winfo_reqheight()
        textarea_frame.configure(height=tag_reqheight + 2*self.TAG_ENTRY_PADY+2*self.textarea["borderwidth"])

        # I add a hidden frame because I want the cursor centered including when there is no tag
        self.textarea.window_create("1.0", window=Frame(self.textarea, height=tag_reqheight, width=0, borderwidth=0))
        tag.destroy()

    def add_tag(self, text):
        tag = Tag(self.textarea, text)

        self.textarea.window_create("1.%s"% len(self.textarea.window_names()), window=tag, padx=self.SPACING_BETWEEN_TAGS)
    
    def _index_of_first_char(self):
        return self.textarea.index("end-%sc"%len(self.textarea.get("1.0", END)))

    def _on_keypress(self, event):
        if event.keysym == 'BackSpace':
            if self.textarea.index(INSERT) == self._index_of_first_char():
                children = self.textarea.winfo_children()

                child = children[-1]
                if isinstance(child, Tag):
                    child.destroy()
            else:
                self.textarea.delete("%s-1c" % INSERT)
            
        elif event.keysym == 'Delete':
            self.textarea.delete("%s" % INSERT)
        
        elif event.keysym == 'Left':
            if self.textarea.index(INSERT) != self._index_of_first_char():
                self.textarea.mark_set(INSERT, "%s-1c"%insertion_index)

        elif event.keysym == 'Right':
            insertion_index = self.textarea.index(INSERT)
            self.textarea.mark_set(INSERT, "%s+1c"%insertion_index)

        elif event.keysym == 'Home':
            self.textarea.mark_set(INSERT, self._index_of_first_char())

        elif event.keysym == 'End':
            self.textarea.mark_set(INSERT, END)
        # We check whether it is a punctuation or normal key
        elif len(event.char) == 1:
            if event.keysym == 'Return':
                # In this situation ord(event.char)=13, which is the CARRIAGE RETURN character
                # We want instead the new line character with ASCII code 10
                text = self.textarea.get("1.0", END)
                self.textarea.delete("end-%sc"%len(text), END)

                text = text.strip()
                
                if text:           
                    self.add_tag(text)
                    self.update_idletasks()
                    self.textarea.see(END)
            else:
                self.textarea.insert(INSERT, event.char)
                self.textarea.see(END)
        return "break"

    @property
    def list_of_tags(self):
        list_of_tags = []
        for window_name in self.textarea.window_names():
            widget = self.nametowidget(window_name)
            
            if isinstance(widget, Tag):
                list_of_tags.append(widget.text)
            
        return list_of_tags
    
if __name__ == "__main__":
    try:
        from Tkinter import Tk
    except ImportError:
        from tkinter import Tk

    root = Tk()
    root.geometry("300x300")
    tag_entry = Tag_Entry(root)
    tag_entry.pack(fill=X)


    tag_entry.add_tag("hello")
    # print all the tags
    print(tag_entry.list_of_tags)

    root.mainloop()