Welcome, guest | Sign In | My Account | Store | Cart
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))

import datetime
import collections

try:
    from Tkinter import StringVar, Text, Frame, PanedWindow, Scrollbar, Label, Entry
    from Tkconstants import *
    import ttk
except ImportError:
    from tkinter import StringVar, Text, Frame, PanedWindow, Scrollbar, Label, Entry
    from tkinter.constants import *
    import tkinter.ttk as ttk

User_Message = collections.namedtuple('User_Message', 'nick content')
Notification_Message = collections.namedtuple('Notification_Message', 'content tag')
Notification_Message.__new__.__defaults__ = ('notification',)

Notification_Of_Private_Message = collections.namedtuple('Notification_Message', 'content from_ to')

# TODO: Add frame topic
class Chatbox(object):
    def __init__(self, master, my_nick=None, command=None, topic=None, entry_controls=None, maximum_lines=None, timestamp_template=None, scrollbar_background=None, scrollbar_troughcolor=None, history_background=None, history_font=None, history_padx=None, history_pady=None, history_width=None, entry_font=None, entry_background=None, entry_foreground=None, label_template=u"{nick}", label_font=None, logging_file=None, tags=None):
        self.interior = Frame(master, class_="Chatbox")

        self._command = command

        self._is_empty = True

        self._maximum_lines = maximum_lines
        self._timestamp_template = timestamp_template
        
        self._command = command

        self._label_template = label_template
        
        self._logging_file = logging_file
        
        if logging_file is None:
            self._log = None
        else:
            try:
                self._log = open(logging_file, "r")
            except:
                self._log = None
        
        top_frame = Frame(self.interior, class_="Top")
        top_frame.pack(expand=True, fill=BOTH)
                
        self._textarea = Text(top_frame, state=DISABLED)

        self._vsb = Scrollbar(top_frame, takefocus=0, command=self._textarea.yview)
        self._vsb.pack(side=RIGHT, fill=Y)

        self._textarea.pack(side=RIGHT, expand=YES, fill=BOTH)
        self._textarea["yscrollcommand"]=self._vsb.set
        
        entry_frame = Frame(self.interior, class_="Chatbox_Entry")
        entry_frame.pack(fill=X, anchor=N)
        
        if entry_controls is not None:
            controls_frame = Frame(entry_frame, class_="Controls")
            controls_frame.pack(fill=X)
            entry_controls(controls_frame, chatbox=self)
            
            bottom_of_entry_frame = Frame(entry_frame)
            self._entry_label = Label(bottom_of_entry_frame)
            self._entry = Entry(bottom_of_entry_frame)
        else:            
            self._entry_label = Label(entry_frame)
            self._entry = Entry(entry_frame)
        
        self._entry.pack(side=LEFT, expand=YES, fill = X)
        self._entry.bind("<Return>", self._on_message_sent)
        
        self._entry.focus()

        if history_background:
            self._textarea.configure(background=history_background)
        
        if history_font:
            self._textarea.configure(font=history_font)

        if history_padx:
             self._textarea.configure(padx=history_padx)
             
        if history_width:
             self._textarea.configure(width=history_width)

        if history_pady:
            self._textarea.configure(pady=history_pady)

        if scrollbar_background:
            self._vsb.configure(background = scrollbar_background)

        if scrollbar_troughcolor:
            self._vsb.configure(troughcolor = scrollbar_troughcolor)

        if entry_font:
            self._entry.configure(font=entry_font)

        if entry_background:
            self._entry.configure(background=entry_background)
            
        if entry_foreground:
            self._entry.configure(foreground=entry_foreground)
        
        if label_font:
            self._entry_label.configure(font=label_font)

        if tags:
            for tag, tag_config in tags.items():
                self._textarea.tag_config(tag, **tag_config)
                
        self.set_nick(my_nick)

    @property
    def topic(self):
        return
        
    @topic.setter
    def topic(self, topic):
        return
        
    def focus_entry(self):
        self._entry.focus()

    def bind_entry(self, event, handler):
        self._entry.bind(event, handler)
        
    def bind_textarea(self, event, handler):
        self._textarea.bind(event, handler)
        
    def bind_tag(self, tagName, sequence, func, add=None):
        self._textarea.tag_bind(tagName, sequence, func, add=add) 
        
    def focus(self):
        self._entry.focus()

    def user_message(self, nick, content):
        if self._timestamp_template is None:
            self._write((u"%s:"%nick, "nick"), " ", (content, "user_message"))
        else:
            timestamp = datetime.datetime.now().strftime(self._timestamp_template)
            self._write((timestamp, "timestamp"), " ", (u"%s:"%nick, "nick"), " ", (content, "user_message"))

    def notification_message(self, content, tag=None):
        if tag is None:
            tag = "notification"

        self._write((content, tag))
        
    notification = notification_message
    
    def notification_of_private_message(self, content, from_, to):
        if self._timestamp_template is None:
            self.notification_message(u"{from_} -> {to}: {content}".format(from_=from_, to=to, content=content), "notification_of_private_message")
        else:
            timestamp = datetime.datetime.now().strftime(self._timestamp_template)
            self.notification_message(u"{timestamp} {from_} -> {to}: {content}".format(timestamp=timestamp, from_=from_, to=to, content=content), "notification_of_private_message")
        
    def new_message(self, message):
        if isinstance(message, User_Message):
            self.user_message(message.content, message.nick)
        elif isinstance(message, Notification_Message):
            self.notification(message.content, message.tag)
        elif isinstance(message, Notification_Of_Private_Message):
            self.notification_of_private_message(message.from_, message.to, message.content)
        else:
            raise Exception("Bad message")

    def tag(self, tag_name, **kwargs):
        self._textarea.tag_config(tag_name, **kwargs)

    def clear(self):
        self._is_empty = True
        self._textarea.delete('1.0', END)

    @property
    def logging_file(self):
        return self._logging_file

    def send(self, content):
        if self._my_nick is None:
            raise Exception("Nick not set")

        self.user_message(self._my_nick, content)

    def _filter_text(self, text):
        return "".join(ch for ch in text if ch <= u"\uFFFF")
    
    def _write(self, *args):
        if len(args) == 0: return
            
        relative_position_of_scrollbar = self._vsb.get()[1]
        
        self._textarea.config(state=NORMAL)
        
        if self._is_empty:
            self._is_empty = False
        else:
            self._textarea.insert(END, "\n")
            if self._log is not None:
                self._log.write("\n")

        for arg in args:
            if isinstance(arg, tuple):
                text, tag = arg
                        # Parsing not allowed characters
                text = self._filter_text(text)
                self._textarea.insert(END, text, tag)
            else:
                text = arg

                text = self._filter_text(text)
                self._textarea.insert(END, text)
            
            if self._log is not None:
                self._log.write(text)

        if self._maximum_lines is not None:
            start_line = int(self._textarea.index('end-1c').split('.')[0]) -self._maximum_lines 
            
            if lines_to_delete >= 1:
                self._textarea.delete('%s.0'%start_line, END)

        self._textarea.config(state=DISABLED)
        
        if relative_position_of_scrollbar == 1:
            self._textarea.yview_moveto(1)

    def _on_message_sent(self, event):
        message = self._entry.get()
        self._entry.delete(0, END)
        
        self.send(message)

        if self._command:
            self._command(message)

    def set_nick(self, my_nick):
        self._my_nick = my_nick

        if my_nick:
            text = self._label_template.format(nick=my_nick)

            self._entry_label["text"] = text
            self._entry_label.pack(side=LEFT,padx=(5,5), before=self._entry)
        else:
            self._entry_label.pack_forget()

if __name__ == "__main__":

    try:
        from Tkinter import Tk
    except ImportError:
        from tkinter import Tk

    root = Tk()
    root.title("Chat megawidget")

    def command(txt):
        print(txt)

    chatbox = Chatbox(root, my_nick="user1", command=command)
    chatbox.user_message("user2", "hello guys")
    
    chatbox.send("Hi, you are welcome!")
    chatbox.interior.pack(expand=True, fill=BOTH)

    root.mainloop()

Diff to Previous Revision

--- revision 2 2017-02-23 22:46:27
+++ revision 3 2017-02-23 22:47:16
@@ -1,898 +1,257 @@
-import functools
-from collections import defaultdict
-
-from chatbox import Chatbox, User_Message, Notification_Message, Notification_Of_Private_Message
-from ordered_listbox import Tagged_and_Ordered_Dictbox
+# Author: Miguel Martinez Lopez
+# Uncomment the next line to see my email
+# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
+
+import datetime
+import collections
 
 try:
-    from Tkinter import StringVar, Text, Frame, Button, PanedWindow, Scrollbar, Label, Entry, Menu, TclError
+    from Tkinter import StringVar, Text, Frame, PanedWindow, Scrollbar, Label, Entry
     from Tkconstants import *
     import ttk
-    import tkFont
 except ImportError:
-    from tkinter import StringVar, Text, Frame, Button, PanedWindow, Scrollbar, Label, Entry, Menu, TclError
+    from tkinter import StringVar, Text, Frame, PanedWindow, Scrollbar, Label, Entry
     from tkinter.constants import *
     import tkinter.ttk as ttk
-    from tkinter import font as tkFont
-
-
-def autoscroll(sbar, first, last):
-    """Hide and show scrollbar as needed."""
-    first, last = float(first), float(last)
-    if first <= 0 and last >= 1:
-        sbar.grid_remove()
-    else:
-        sbar.grid()
-    sbar.set(first, last)
-
-def to_tkFont(font_spec):
-    if isinstance(font_spec, tkFont.Font):
-        return font_spec
-
-    font_spec_length = len(font_spec)
+
+User_Message = collections.namedtuple('User_Message', 'nick content')
+Notification_Message = collections.namedtuple('Notification_Message', 'content tag')
+Notification_Message.__new__.__defaults__ = ('notification',)
+
+Notification_Of_Private_Message = collections.namedtuple('Notification_Message', 'content from_ to')
+
+# TODO: Add frame topic
+class Chatbox(object):
+    def __init__(self, master, my_nick=None, command=None, topic=None, entry_controls=None, maximum_lines=None, timestamp_template=None, scrollbar_background=None, scrollbar_troughcolor=None, history_background=None, history_font=None, history_padx=None, history_pady=None, history_width=None, entry_font=None, entry_background=None, entry_foreground=None, label_template=u"{nick}", label_font=None, logging_file=None, tags=None):
+        self.interior = Frame(master, class_="Chatbox")
+
+        self._command = command
+
+        self._is_empty = True
+
+        self._maximum_lines = maximum_lines
+        self._timestamp_template = timestamp_template
+        
+        self._command = command
+
+        self._label_template = label_template
+        
+        self._logging_file = logging_file
+        
+        if logging_file is None:
+            self._log = None
+        else:
+            try:
+                self._log = open(logging_file, "r")
+            except:
+                self._log = None
+        
+        top_frame = Frame(self.interior, class_="Top")
+        top_frame.pack(expand=True, fill=BOTH)
+                
+        self._textarea = Text(top_frame, state=DISABLED)
+
+        self._vsb = Scrollbar(top_frame, takefocus=0, command=self._textarea.yview)
+        self._vsb.pack(side=RIGHT, fill=Y)
+
+        self._textarea.pack(side=RIGHT, expand=YES, fill=BOTH)
+        self._textarea["yscrollcommand"]=self._vsb.set
+        
+        entry_frame = Frame(self.interior, class_="Chatbox_Entry")
+        entry_frame.pack(fill=X, anchor=N)
+        
+        if entry_controls is not None:
+            controls_frame = Frame(entry_frame, class_="Controls")
+            controls_frame.pack(fill=X)
+            entry_controls(controls_frame, chatbox=self)
+            
+            bottom_of_entry_frame = Frame(entry_frame)
+            self._entry_label = Label(bottom_of_entry_frame)
+            self._entry = Entry(bottom_of_entry_frame)
+        else:            
+            self._entry_label = Label(entry_frame)
+            self._entry = Entry(entry_frame)
+        
+        self._entry.pack(side=LEFT, expand=YES, fill = X)
+        self._entry.bind("<Return>", self._on_message_sent)
+        
+        self._entry.focus()
+
+        if history_background:
+            self._textarea.configure(background=history_background)
+        
+        if history_font:
+            self._textarea.configure(font=history_font)
+
+        if history_padx:
+             self._textarea.configure(padx=history_padx)
+             
+        if history_width:
+             self._textarea.configure(width=history_width)
+
+        if history_pady:
+            self._textarea.configure(pady=history_pady)
+
+        if scrollbar_background:
+            self._vsb.configure(background = scrollbar_background)
+
+        if scrollbar_troughcolor:
+            self._vsb.configure(troughcolor = scrollbar_troughcolor)
+
+        if entry_font:
+            self._entry.configure(font=entry_font)
+
+        if entry_background:
+            self._entry.configure(background=entry_background)
+            
+        if entry_foreground:
+            self._entry.configure(foreground=entry_foreground)
+        
+        if label_font:
+            self._entry_label.configure(font=label_font)
+
+        if tags:
+            for tag, tag_config in tags.items():
+                self._textarea.tag_config(tag, **tag_config)
+                
+        self.set_nick(my_nick)
+
+    @property
+    def topic(self):
+        return
+        
+    @topic.setter
+    def topic(self, topic):
+        return
+        
+    def focus_entry(self):
+        self._entry.focus()
+
+    def bind_entry(self, event, handler):
+        self._entry.bind(event, handler)
+        
+    def bind_textarea(self, event, handler):
+        self._textarea.bind(event, handler)
+        
+    def bind_tag(self, tagName, sequence, func, add=None):
+        self._textarea.tag_bind(tagName, sequence, func, add=add) 
+        
+    def focus(self):
+        self._entry.focus()
+
+    def user_message(self, nick, content):
+        if self._timestamp_template is None:
+            self._write((u"%s:"%nick, "nick"), " ", (content, "user_message"))
+        else:
+            timestamp = datetime.datetime.now().strftime(self._timestamp_template)
+            self._write((timestamp, "timestamp"), " ", (u"%s:"%nick, "nick"), " ", (content, "user_message"))
+
+    def notification_message(self, content, tag=None):
+        if tag is None:
+            tag = "notification"
+
+        self._write((content, tag))
+        
+    notification = notification_message
     
-    if font_spec_length == 1:
-        family, = font
-        return tkFont.Font(family=family)
-    elif font_spec_length == 2:
-        family, size = font_spec
-        return tkFont.Font(family=family, size=size)
-    elif font_spec_length == 3:
-        family, size, weight = font_spec
-        return tkFont.Font(family=family, size=size, weight=weight)
-
-def _nested_value(obj, *list_of_keys):
-    for key in list_of_keys:
-        if isinstance(obj, dict):            
-            if key in obj:
-                obj = obj[key]
-            else:
-                return None
-        else:
-            return None
-    return obj
-
-# TODO: Add frame topic
-class Channel(object):
-    def __init__(self, master, connection, messenger, channel_ID, channel_name, my_nick_var, entry_controls, style):
-        self._my_nick_var = my_nick_var
-        my_nick_var.trace_variable('w', lambda name, index, mode: self._chatbox.set_nick(self._my_nick_var.get()))
-        
-        self._channel_ID = channel_ID
-        self._name = channel_name
-        self._messenger = messenger
-
-        self.interior = Frame(master, class_="Channel")
-        self.interior.channel = self
-        
-        self._is_closed = False
-        
-        self._connection = connection
-
-    def _build_chatbox(self, master, logging_file, style):
-        kwargs = _nested_value(style, "Channel", "Chatbox")
-        
-        if not kwargs:
-            kwargs = {}
-
-        scrollbar_style = _nested_value(style, "Scrollbar")
-        
-        if scrollbar_style:
-            for style_name in ("background", "troughcolor"):
-                if style_name in scrollbar_style:
-                    kwargs.setdefault("scrollbar_" +style_name, scrollbar_style[style_name])
-
-        kwargs["logging_file"]=logging_file
-        kwargs["my_nick"] = self._my_nick_var.get()
-        kwargs["command"] = self._on_message_sent
-
-        self._chatbox = Chatbox(master, **kwargs)
-        return self._chatbox
-
-    @property
-    def channel_ID(self):
-        return self._channel_ID
-    
-    @property
-    def name(self):
-        return self._name
-    
-    @property
-    def messenger(self):
-        return self._messenger
+    def notification_of_private_message(self, content, from_, to):
+        if self._timestamp_template is None:
+            self.notification_message(u"{from_} -> {to}: {content}".format(from_=from_, to=to, content=content), "notification_of_private_message")
+        else:
+            timestamp = datetime.datetime.now().strftime(self._timestamp_template)
+            self.notification_message(u"{timestamp} {from_} -> {to}: {content}".format(timestamp=timestamp, from_=from_, to=to, content=content), "notification_of_private_message")
+        
+    def new_message(self, message):
+        if isinstance(message, User_Message):
+            self.user_message(message.content, message.nick)
+        elif isinstance(message, Notification_Message):
+            self.notification(message.content, message.tag)
+        elif isinstance(message, Notification_Of_Private_Message):
+            self.notification_of_private_message(message.from_, message.to, message.content)
+        else:
+            raise Exception("Bad message")
+
+    def tag(self, tag_name, **kwargs):
+        self._textarea.tag_config(tag_name, **kwargs)
+
+    def clear(self):
+        self._is_empty = True
+        self._textarea.delete('1.0', END)
 
     @property
     def logging_file(self):
-        return self._chatbox.logging_file
-        
-    @property
-    def is_closed(self):
-        return self._is_closed
-        
-    def bind_entry(self, event, handler):
-        self._chatbox.bind_entry(event, handler)
-        
-    def bind_textarea(self, event, handler):
-        self._chatbox.bind_textarea(event, handler)
-        
-    def bind_tag(self, tagName, sequence, func, add=None):
-        self._chatbox.bind_tag(tagName, sequence, func, add=add) 
-
-    def user_message(self, content, user):
-        if self._is_closed:
-            raise Exception("Channel is closed: %s"%self._channel_ID)
-        else:
-            self._chatbox.user_message(content, user)
+        return self._logging_file
+
+    def send(self, content):
+        if self._my_nick is None:
+            raise Exception("Nick not set")
+
+        self.user_message(self._my_nick, content)
+
+    def _filter_text(self, text):
+        return "".join(ch for ch in text if ch <= u"\uFFFF")
     
-    def notification_message(self, content, tag=None):
-        if self._is_closed:
-            raise Exception("Channel is closed: %s"%self._channel_ID)
-        else:
-            self._chatbox.notification(content, tag)
-            
-    def notification_of_private_message(self, content, from_, to):
-        if self._is_closed:
-            raise Exception("Channel is closed: %s"%self._channel_ID)
-        else:
-            if to is None:
-                to = self._my_nick_var.get()
-                
-            if from_ is None:
-                from_ = self._my_nick_var.get()
-
-            self._chatbox.notification_of_private_message(content, from_, to)
-
-    def send(self, content):
-        if self._is_closed:
-            raise Exception("Channel is closed: %s"%self.channel_ID)
-        else:
-            self._chatbox.send(content)
-
-    def _on_message_sent(self, content):
-        self._connection.trigger("i-send-a-message", {"channel_ID":self._channel_ID, "channel_name":self._name, "content":content})
-
-    def add_messages(self, list_of_messages):
-        if self._is_closed:
-            raise Exception("Channel is closed: %s"%self.channel_ID)
-        else:
-            self._history.add_list_of_messages(list_of_messages)
-
-    def close(self):
-        self._is_closed = True
-        self._connection.trigger("channel-closed", {"channel_ID":self._channel_ID, "channel_name":self._name})
-        
-    def set_nick(self, nick):
-        self._my_nick_var.set(nick)
-        
-    def get_nick(self):
-        return self._my_nick_var.get()
-    
-    def focus_entry(self):
-        self._chatbox.focus_entry()
-
-class Public_Channel(Channel):
-    def __init__(self, master, connection, messenger, channel_ID, channel_name, my_nick_var, panedwindow_ratio=0.8, topic=None, logging_file=None, maximum_lines=None, entry_controls=None, style=None):
-        Channel.__init__(self, master, connection, messenger, channel_ID, channel_name, my_nick_var, entry_controls, style)
-
-        self._panedwindow = panedwindow = PanedWindow(self.interior, orient=HORIZONTAL, opaqueresize= False, sashpad =1)
-        panedwindow.pack(expand=True, fill=BOTH)
-
-        chatbox = self._build_chatbox(panedwindow, logging_file=logging_file, style=style)
-        left_pane = chatbox.interior
-            
-        chatbox.interior.pack(expand=True, fill=BOTH)
-
-        self._user_list = User_List(panedwindow, connection, style=style)
-        right_pane = self._user_list.interior
-    
-        panedwindow.add(left_pane)    
-        panedwindow.add(right_pane)
-        
-        panedwindow.update_idletasks()
-        panedwindow.sash_place(0, int(round(panedwindow.winfo_reqwidth()*panedwindow_ratio)), 1)
-
-        self.interior.bind('<Configure>', self._adjust_panedwindow)
-        
-    def _adjust_panedwindow(self, event):
-        x = event.width - self._user_list.interior.winfo_reqwidth()
-
-        self.interior.update_idletasks()
-        self._panedwindow.sash_place(0, x, 1)
-        
-    def add_user(self, user):
-        self._user_list.add_user(user)
-        
-    def delete_user(self, user):
-        self._user_list.delete_user(user)
-    
-    def deselect_user(self):
-        self._user_list.deselect()
-
-    def set_tag(self, user, tag):
-        self._user_list.set_tag(user, tag)
-
-    def delete_tag(self, user, tag):
-        self._user_list.delete_tag(user, tag)
-
-    @property
-    def users(self):
-        return self._user_list.user_names
-
-    @property
-    def topic(self):
-        return self._chatbox.topic
-
-    @topic.setter
-    def topic(self, topic):
-        self._chatbox.topic = topic
-
-class Private_Channel(Channel):
-    def __init__(self, master, connection, messenger, channel_ID,  channel_name, my_nick_var, logging_file=None, entry_controls=None, style=None):
-        Channel.__init__(self, master, connection, messenger, channel_ID, channel_name, my_nick_var, entry_controls, style)
-
-        chatbox = self._build_chatbox(self.interior, logging_file=logging_file, style=style)
-        chatbox.interior.pack(expand=True, fill=BOTH)
-
-# Modificar envio de mensajes. COntrolar mensajes no leidos
-# NO existe mensajes no leidos
-# Llevar registro de canales que son mostrados
-# Modificar mensaje en entry
-
-class Panel_Of_Channel_Names(object):
-
-    def __init__(self, master, connection, style=None, add_at_the_beginning=True):
-        self.interior = Frame(master, takefocus=1, class_="Panel_Of_Channel_Names")
-        
-        self._connection = connection
-        
-        self.interior.grid_columnconfigure(0, weight=1)
-        self.interior.grid_rowconfigure(0, weight=1)
-
-        self._vsb = Scrollbar(self.interior, takefocus=0)
-        self._vsb.grid(column=1, row=0, sticky=N+S)
-
-        #self._treeview = ttk.Treeview(self.interior, yscrollcommand=lambda f, l: autoscroll(self._vsb, f, l), takefocus=0,selectmode="browse", show="headings", columns=("#1"), style="Panel_Of_Channels.Treeview")
-        self._treeview = ttk.Treeview(self.interior, yscrollcommand=lambda f, l: autoscroll(self._vsb, f, l), takefocus=0,selectmode="browse", show="headings", columns=("#1"), style="Panel_Of_Channels.Treeview")
-        self._treeview.grid(column=0, row=0, sticky=N+S+W+E)
-
-        self._treeview.column("#0", stretch= True, anchor="w")
-        self._treeview.heading("#0",text="messenger")
-
-        self._treeview.column("#1", stretch= True, anchor="w")
-        self._treeview.heading("#1",text="channels")
-
-        self._treeview.bind("<Double-Button-1>", self._on_select_item)
-        self._treeview.bind('<Return>', self._on_select_item)
-
-        self._treeview.bind('<Escape>', lambda event: self.deselect())
-
-        self._vsb["command"]=self._treeview.yview
-        
-        if style is not None:
-            ttk_style = ttk.Style()
-
-            if "Panel_Of_Channel_Names" in style:
-                panel_style = style["Panel_Of_Channel_Names"]
-
-                if "messenger_column" in panel_style:
-                    messenger_column_style = panel_style["messenger_column"]
-                    self._treeview.column("#0", **messenger_column_style)
-                    
-                if "channel_column" in panel_style:
-                    channel_column_style = panel_style["channel_column"]
-                    self._treeview.column("#1", **channel_column_style)
-
-                if "background" in panel_style:
-                    background = panel_style["background"]
-                    ttk_style.configure("Panel_Of_Channels.Treeview", background=background, fieldbackground=background)
-                
-                if "foreground" in panel_style:
-                    foreground = panel_style["foreground"]
-                    ttk_style.configure("Panel_Of_Channels.Treeview", foreground=foreground)
-                
-                if "font" in panel_style:
-                    font = panel_style["font"]
-
-                    ttk_style.configure("Panel_Of_Channels.Treeview", font=font, rowheight=to_tkFont(font).metrics("linespace"))
-                    
-                if "tags" in panel_style:
-                   style_tags = panel_style["tags"]
-                   for tag, config_tag in style_tags.items():
-                       self._treeview.tag_configure(tag, **config_tag)
-            
-            if "Scrollbar" in style:
-                scrollbar_style = style["Scrollbar"]
-                
-                if "background" in style:
-                    self._vsb.configure(background=scrollbar_style["background"])
-                
-                if "throughcolor" in style:
-                    self._vsb.configure(throughcolor=scrollbar_style["throughcolor"])
-
-        self._unread_messages = defaultdict(int)
-        
-        self._show_messengers = False
-        self._add_at_the_beginning = add_at_the_beginning
-
-        self._treeview.bind("<Button-3>", self._generate_context_menu_event)
-
-    def add_messenger(self, messenger, open=True):
-        if not self._show_messengers:
-            self._treeview.configure(show="tree headings")
-            self._show_messengers = True
-
-        messenger_IID = self._messenger_IID(messenger)
-
-        self._treeview.insert("", END, messenger_IID, text=messenger)
-        self._treeview.item(messenger_IID, open=open)
-
-        return messenger_IID
-
-    def delete_messenger(self, messenger):
-        messenger_IID = self._messenger_IID(messenger)
-        self._treeview.delete(messenger_IID)
-        
-    def _messenger_IID(self, messenger):
-        return "+"+messenger
-
-    def add_channel_name(self, channel_name, channel_ID, messenger=None, tags=None, image=None):
-        if channel_ID[0] == "+":
-            raise ValueError("Channel ID can't start with '+'")
-
-        kwargs = {}
-        if tags is not None:
-            kwargs["tags"] = tags
-            
-        if image is not None:
-            kwargs["image"] = image
-
-        if messenger is None:
-            iid = ""
-        else:
-            iid = self.add_messenger(messenger)
-
-        self._treeview.insert(iid, END, channel_ID, values=(channel_name,), **kwargs)
-        self._treeview.update_idletasks()
-
-    def delete_channel_name(self, channel_ID):
-        if channel_ID[0] == "+":
-            raise ValueError("Channel ID can't start with '+'")
-
-        self._treeview.delete(channel_ID)
-
-    def set_tag(self, channel_ID, tag):
-        tags = list(self._treeview.item(channel_ID, option="tags"))
-        if not tags:
-            tags = []
-
-        if not tag in tags:
-            tags.append(tag)
-        
-        self._treeview.item(channel_ID, tags=tags)
-
-    def delete_tag(self, channel_ID, tag):
-        tags = list(self._treeview.item(channel_ID, option="tags"))
-        
-        try:
-            index = tags.index(tag)
-        except ValueError:
-            return
-
-        tags.pop(index)
-        self._treeview.item(channel_ID, tags=tags)
-
-    def select_first_on_list(self):
-        list_of_items = self._treeview.get_children()
-        if len(list_of_items) ==0:
-            return
-            
-        first_item = list_of_items[0]
-        self.select(first_item)
-
-    def select_next(self):
-        selection = self._treeview.selection()
-    
-        if len(selection) ==0:
-            self.select_first_on_list()
-        else:
-            item = selection[0]
-            
-            next_item = self._treeview.next(item)
-            if next_item != "":
-                self.select(next_item)
-        
-    def select_previous(self):
-        selection = self._treeview.selection()
-    
-        if len(selection) ==0:
-            self.select_first_on_list()
-        else:
-            item = selection[0]
-            
-            prev_item = self._treeview.prev(item)
-            if prev_item != "":
-                self.select(prev_item)
-
-    def deselect(self):
-        selection = self._treeview.selection()
-    
-        for item in selection:
-            self._treeview.selection_remove(item)
-            
-    def select(self, channel_ID):
-        self._treeview.focus_set()
-        self._treeview.selection_set((channel_ID,channel_ID))
-        self._treeview.focus(channel_ID)
-
-    def clear(self):
-        self._treeview.delete(*self._treeview.get_children())
-        
-    def bind(self, event, handler):
-        self._treeview.bind(event, handler)
-
-    def __iter__(self):
-        return self._treeview.get(0, END)
-
-    def _on_select_item(self, event):
-        selected_items = self._treeview.selection()
-        if selected_items == "": return
-
-        item_ID = selected_items[0]
-        if item_ID[0] == "+":
-            self._connection.trigger('messenger-selected',item_ID[1:])
-        else:
-            self._connection.trigger('channel-selected',item_ID)
-        
-    def _generate_context_menu_event(self, event):
-        # http://stackoverflow.com/questions/12014210/python-tkinter-app-adding-a-right-click-context-menu
-        iid = self.tree.identify('item',event.x, event.y)
-        # se tiene que capturar el channel ID
-        # channel_ID = 
-
-        self._connection.trigger('panel-of-channel-names-context-menu',{"metadata":{"iid": iid}, "x_root":event.x_root, "y_root":event.y_root})
-
-    def notifify_messages_are_unread(self, channel_ID):
-        if self._unread_messages[channel_ID] == 0:
-            self.set_tag(channel_ID, "unread_messages")
-
-        self._unread_messages[channel_ID] += 1
-
-        if self._add_at_the_beginning:
-            self._treeview.move(channel_ID,self._treeview.parent(channel_ID),0)
-
-    def notifify_messages_are_read(self, channel_ID):
-        if self._unread_messages[channel_ID] > 0:
-            self.delete_tag(channel_ID, "unread_messages")
-
-        self._unread_messages[channel_ID] = 0
-
-def validate_channel(func):
-    @functools.wraps(func)
-    def wrapped(self, channel_ID, *args, **kwargs):
-        if channel_ID in self._channels:
-            return func(self, channel_ID, *args, **kwargs)
-        else:
-            raise ValueError("Not a valid channel ID: %s"%channel_ID)
-    return wrapped
-
-# Guardar tambien variable nick por cada messenger
-# Se debe retocar la parte del nick
-
-class Messenger(object):
-    def __init__(self, name=None, nick=None):
-        self.name = name
-        self.nick_var = StringVar()
-        self.channels = set()
-        
-        if nick is not None:
-            self.set_nick(nick)
-
-    def get_nick(self, nick):
-        return self.nick_var.get()
-
-    def set_nick(self, nick):
-        self.nick_var.set(nick)
-        
-    def add_channel(self, channel_ID):
-        self.channels.add(channel_ID)
-        
-    def delete_channel(self, channel_ID):
-        self.channels.remove(channel_ID)
-        
-    def __iter__(self):
-        return self.channels
-
-class Connection(object):
-    def __init__(self):
-        self._callbacks = defaultdict(list)
-
-    def on(self, event_name, callback):
-        self._callbacks[event_name].append(callback)
-        
-    def off(self, event_name, *args):
-        if len(args) == 0:
-            del self._callbacks[event_name]
-        else:
-            list_of_callbacks = self._callbacks[event_name]
-            for callback in args:
-                try:
-                    index = list_of_callbacks.index(callback)
-                except ValueError:
-                    continue
-                    
-                list_of_callbacks.pop(index)
-                
-    def trigger(self, event_name, data=None):
-        for callback in self._callbacks[event_name]:
-            callback(data)
-
-def _menu_builder(f):
-    
-    def wrapped(data):
-    
-        menu = Menu(tearoff=0)
-        f(menu, data["metadata"])
-
-        menu.tk_popup(data["x_root"], data["y_root"], 0)
-        menu.destroy()
-    
-    return wrapped
-
-# Utilizar conexion para notificar mensajes leidos
-
-class Messenger_MegaWidget(object):
-    _ID = 0
-
-    def __init__(self, master, my_nick, style=None, entry_controls=None):
-        self.interior = interior = Frame(master, class_="Chat_MegaWidget")
-        
-        self._style = style
-        
-        self._entry_controls = entry_controls
-
-        self._connection = Connection()
-        self._connection.on("channel-selected", self._on_select_channel)
-
-        top_panel = PanedWindow(interior, orient=HORIZONTAL, opaqueresize= False, sashpad =1)
-        top_panel.pack(expand=YES, fill=BOTH)
-
-        self._notebook = ttk.Notebook(top_panel, style="Channels_Notebook.TNotebook")
-        self._notebook.enable_traversal()
-        self._notebook.bind('<3>', self._open_tab_context_menu)
-
-        if "Tabs" in style:
-            notebook_style = style["Tabs"]
-            if "width" in notebook_style:
-                self._notebook.configure(width=notebook_style["width"])
-                
-            if "height" in notebook_style:
-                self._notebook.configure(height=notebook_style["height"])
-            
-            if "font" in notebook_style:
-                ttk.Style().configure("Channels_Notebook.TNotebook.Tab", font=notebook_style["font"])
-
-        self._notebook.bind("<<NotebookTabChanged>>", lambda event: self._on_tab_changed())
-
-        self._panel_of_channel_names = Panel_Of_Channel_Names(top_panel, self._connection, style=style)
-        self._panel_of_channel_names.interior.pack(expand=True, fill=BOTH)
-
-
-        top_panel.add(self._panel_of_channel_names.interior)
-        top_panel.add(self._notebook)
-
-        self._channels = {}
-        
-        self._panel_of_channel_names.bind("<Escape>", lambda event: (self.deselect_chats(),self._entry.focus()))
-
-        interior.bind_all('<Control-KeyPress-m>', lambda event: self._entry.focus())
-        interior.bind_all('<Control-KeyPress-M>', lambda event: self._entry.focus())
-
-        self._messengers = {
-            None: Messenger(nick=my_nick)
-        }
-
-    def _open_tab_context_menu(self, event):
-        if event.widget.identify(event.x, event.y) == 'label':
-            index = event.widget.index('@%d,%d' % (event.x, event.y))
-            #obtener aqui el canal
-            #tab_rint event.widget.tab(index, 'text')
-            self._connection.on("tab-context-menu", {"x_root":event.x_root, "y_root":event.y_root})
-        return "break"
-
-    def _on_select_channel(self, channel_ID):
-        channel = self._channels[channel_ID]
-        
-        try:
-            self._notebook.select(channel.interior)
-        except TclError:
-            self._notebook.add(channel.interior, text=channel.name)
-            self._notebook.select(channel.interior)
-
-        channel.focus_entry()
-
-    def deselect_channel_on_panel(self):
-        self._panel_of_channel_names.deselect()
-        
-    @validate_channel
-    def select_channel_on_panel(self, channel_ID):
-        self._panel_of_channel_names.select(channel_ID)
-    
-    @validate_channel        
-    def untag_channel(self, channel_ID, tag):
-        self._panel_of_channel_names.delete_tag(channel_ID, tag)
-
-    @validate_channel
-    def tag_channel(self, channel_ID, tag):
-        self._panel_of_channel_names.set_tag(channel_ID, tag)
-
-    @validate_channel
-    def select_channel_on_notebook(self, channel_ID):
-        channel = self._channels[channel_ID]
-        self._notebook.select(channel.interior)
-        
-    def add_messenger(self, name):
-        if name in self._messengers:
-            raise ValueError("Messenger already created: %s"%name)
-
-        self._messengers[name] = Messenger(name=name)
-        self._panel_of_channel_names.add_messenger(name)
-
-    def delete_messenger(self, name):
-        if name not in self._messengers:
-            raise ValueError("Messenger not exists: %s"%name)
-
-        for channel_ID in self._messengers[name]:
-            self.close_channel(channel_ID)
-
-        del self._messengers[name]
-        self._panel_of_channel_names.delete_messenger(name)
-
-    def new_channel(self, channel_name, channel_ID=None, messenger=None, start_open=None, tags=None, image=None, public=False):
-        if channel_ID is None:
-            channel_ID = "channel"+str(self._ID)
-            self._ID += 1
-        else:
-            if channel_ID[0] == "+":
-                raise ValueError("Channel ID can not start with '+': %s"%channel_ID)
-            
-            if channel_ID in self._channels:
-                raise ValueError("Channel is already created: %s"%channel_ID)
-
-        if messenger in self._messengers:
-            messenger_instance = self._messengers[messenger]
-        else:
-            messenger_instance = self._messengers[messenger] = Messenger(name=messenger)
-            self._panel_of_channel_names.add_messenger(messenger)
-
-        my_nick_var = messenger_instance.nick_var
-
-        if public:
-            if start_open is None:
-                start_open = True
-            channel = Public_Channel(self._notebook, self._connection, messenger_instance, channel_ID, channel_name, my_nick_var, style=self._style, entry_controls=self._entry_controls)
-        else:
-            if start_open is None:
-                start_open = False
-
-            channel = Private_Channel(self._notebook, self._connection, messenger_instance, channel_ID, channel_name, my_nick_var, style=self._style, entry_controls=self._entry_controls)
-
-        channel.bind_entry("<Control-W>", lambda event, channel_ID=channel.channel_ID: self.hide_channel(channel_ID))
-        channel.bind_entry("<Control-w>", lambda event, channel_ID=channel.channel_ID: self.hide_channel(channel_ID))
-        channel.bind_entry('<Up>', lambda event:  self._panel_of_channel_names.select_previous())
-        channel.bind_entry('<Down>', lambda event: self._panel_of_channel_names.select_next())
-
-        self._channels[channel_ID] = channel
-
-        self._panel_of_channel_names.add_channel_name(channel_name, channel_ID, messenger=messenger, tags=tags, image=image)
-        messenger_instance.add_channel(channel_ID)
-
-        if start_open:
-            self._notebook.add(channel.interior, text=channel_name)
-
-        return channel_ID
-
-    def on(self, event_name, callback):
-        self._connection.on(event_name, callback)
-        
-    def trigger(self, event_name, data):
-        self._connection.trigger(event_name, data)
-
-    @validate_channel
-    def show_channel(self, channel_ID, select=False):
-        channel = self._channels[channel_ID]
-        self._notebook.add(channel.interior)
-
-        if select:
-            self._notebook.select(channel.interior)                
-
-    def hide_channel(self, channel_ID=None):
-        if channel_ID is None:
-            channel = self.current_channel
-        else:
-            if channel_ID in self._channels:
-                channel = self._channels[channel_ID]
+    def _write(self, *args):
+        if len(args) == 0: return
+            
+        relative_position_of_scrollbar = self._vsb.get()[1]
+        
+        self._textarea.config(state=NORMAL)
+        
+        if self._is_empty:
+            self._is_empty = False
+        else:
+            self._textarea.insert(END, "\n")
+            if self._log is not None:
+                self._log.write("\n")
+
+        for arg in args:
+            if isinstance(arg, tuple):
+                text, tag = arg
+                        # Parsing not allowed characters
+                text = self._filter_text(text)
+                self._textarea.insert(END, text, tag)
             else:
-                raise ValueError("Not a valid channel ID: %s"%channel_ID)
-
-        self._notebook.hide(channel.interior)
-
-    @validate_channel
-    def close_channel(self, channel_ID=None):
-        if channel_ID is None:
-            channel = self.current_channel
-            channel_ID = channel.channel_ID
-        else:
-            channel = self._channels[channel_ID]
-
-        self._panel_of_channel_names.delete_channel_name(channel_ID)
-
-        messenger = channel.messenger
-        channel.interior.destroy()
-        del self._channels[channel_ID]
-        self._messengers[messenger].delete_channel(channel_ID)
-        
-        channel.close()
-
-    def focus_entry(self):
-        channel = self.current_channel
-        if channel is not None:
-            channel.focus_entry_set()
-
-    @property
-    def my_nick(self):
-        return self._messengers[None].get_nick()
-
-    @my_nick.setter
-    def my_nick(self, new_nick):
-        return self._messengers[None].set_nick(new_nick)
-
-    def set_nick_on_messenger(self, messenger, nick):
-        for channel_ID in self._messengers[messenger]:
-            channel = self._channels[channel_ID]
-            channel.set_nick(nick)
-        
-    def send(self, message, channel_ID=None):
-        if channel_ID is None:
-            channel = self.current_channel
-            if self.current_channel is None:
-                return
-        else:
-            channel = self._channels[channel_ID]
-
-        channel.send(message)
-
-    @validate_channel
-    def user_message(self, channel_ID, nick, content):
-        channel = self._channels[channel_ID]
-        channel.user_message(nick, content)
-        
-        if channel != self.current_channel:
-            self._notifify_messages_are_unread(channel_ID)
-
-    @validate_channel
-    def notification_message(self, channel_ID, content, notification_type=None):
-        channel = self._channels[channel_ID]
-        channel.notification_message(content, notification_type)
-        
-        if channel != self.current_channel:
-            self._notifify_messages_are_unread(channel_ID)
-
-    @validate_channel
-    def notification_of_private_message(self, channel_ID, content, from_=None, to=None):
-        channel = self._channels[channel_ID]
-        channel.notification_of_private_message(content, from_=from_, to=to)
-        
-        if channel != self.current_channel:
-            self._notifify_messages_are_unread(channel_ID)
-
-    def change_nick(self, new_nick, messenger=None):
-        self._messengers[messenger].set_nick(new_nick)
-
-    @validate_channel
-    def __getitem__(self, channel_ID):
-        return self._channels[channel_ID]
-
-    @validate_channel
-    def __delitem__(self, channel_ID):
-        self.close_channel(channel_ID)
-        
-    def __contains__(self, channel_ID):
-        return channel_ID in self._channels
-        
-    @property
-    def current_channel(self):
-        widget_name = self._notebook.select()
-        if widget_name:
-            return self.interior.nametowidget(widget_name).channel
-        else:
-            return None
-
-    def _on_tab_changed(self):
-        channel = self.current_channel
-        channel_ID = channel.channel_ID
-        
-        channel.focus_entry()
-        
-        self._notifify_messages_are_read(channel_ID)
-
-    def _notifify_messages_are_read(self, channel_ID):
-        self._panel_of_channel_names.notifify_messages_are_read(channel_ID)
-        
-        channel = self._channels[channel_ID]
-        self._notebook.tab(channel.interior, text= channel.name)
-
-    def _notifify_messages_are_unread(self, channel_ID):
-        self._panel_of_channel_names.notifify_messages_are_unread(channel_ID)
-        
-        channel = self._channels[channel_ID]
-        try:
-            self._notebook.tab(channel.interior, text= "[*] "+channel.name)
-        except TclError:
-            pass
-        
+                text = arg
+
+                text = self._filter_text(text)
+                self._textarea.insert(END, text)
+            
+            if self._log is not None:
+                self._log.write(text)
+
+        if self._maximum_lines is not None:
+            start_line = int(self._textarea.index('end-1c').split('.')[0]) -self._maximum_lines 
+            
+            if lines_to_delete >= 1:
+                self._textarea.delete('%s.0'%start_line, END)
+
+        self._textarea.config(state=DISABLED)
+        
+        if relative_position_of_scrollbar == 1:
+            self._textarea.yview_moveto(1)
+
+    def _on_message_sent(self, event):
+        message = self._entry.get()
+        self._entry.delete(0, END)
+        
+        self.send(message)
+
+        if self._command:
+            self._command(message)
+
+    def set_nick(self, my_nick):
+        self._my_nick = my_nick
+
+        if my_nick:
+            text = self._label_template.format(nick=my_nick)
+
+            self._entry_label["text"] = text
+            self._entry_label.pack(side=LEFT,padx=(5,5), before=self._entry)
+        else:
+            self._entry_label.pack_forget()
+
 if __name__ == "__main__":
-    
-    style = {
-        "Tabs":{
-            "width":600,
-            "height": 600,
-            "font": ("TkDefaultFont", 11)
-        },
-        "Scrollbar": {
-            "background":"#7f0303", 
-            "troughcolor" :"#cc2b00"
-        },
-        "Panel_Of_Channel_Names": {
-            "messenger_column": {
-                "width": 100
-            },
-            "channel_column": {
-                "width": 100
-            },
-            "background":"#676760", 
-            "foreground":"#f7f7f7",
-            "font": ("Courier New", 12),
-            "tags": {
-                "unread_messages": {
-                    "foreground": "red"
-                }
-            }
-        },
-        "Channel": {
-            "Chatbox": {
-                "timestamp_template": "[%H:%M:%S]",
-                "history_background":"#676760",
-                "history_font": ("Courier New", 12),
-                "label_font": ("TkDefaultFont", 10, "bold"),
-                "entry_font": ("Courier New", 12),
-                "entry_background": "#676760", 
-                "entry_foreground":"#f7f7f7",
-                "tags": {
-                    "timestamp": {
-                        "foreground":"#f7f7f7"
-                    },
-                    "nick": {
-                        "foreground":"#f7f7f7"
-                    },
-                    "notification": {
-                        "foreground":"#f7f7f7"
-                    },
-                    "notification_of_private_message": {
-                        "foreground":"#f7f7f7"
-                    },
-                    "user_message": {
-                        "foreground":"#f7f7f7"
-                    }
-                }
-            },
-            "User_List": {
-                "width": 100,
-                "background":"#676760", 
-                "foreground":"#f7f7f7",
-                "font": ("Courier New", 12)
-            }
-        }
-    }
 
     try:
         from Tkinter import Tk
@@ -902,16 +261,13 @@
     root = Tk()
     root.title("Chat megawidget")
 
-    chat = Messenger_MegaWidget(root, my_nick="user123", style=style)
-    chat.interior.pack(expand=True, fill=BOTH)
-
-
-    channel_ID = chat.new_channel("#barcelona", start_open=True)
-
-    chat.new_channel("johny23")
-    chat.new_channel("Samatha32")
-
-    chat.user_message(channel_ID, "john", "hi everybody")
-    for i in range(100): chat.send("hola caracola")
+    def command(txt):
+        print(txt)
+
+    chatbox = Chatbox(root, my_nick="user1", command=command)
+    chatbox.user_message("user2", "hello guys")
+    
+    chatbox.send("Hi, you are welcome!")
+    chatbox.interior.pack(expand=True, fill=BOTH)
 
     root.mainloop()

History