Example #1
0
class ChatForm(tk.Frame):
    font_color = "#000000"
    font_size = 10
    user_list = []
    tag_i = 0

    def remove_listener_and_close(self):
        remove_message_listener(self.message_listener)
        client.util.socket_listener.remove_listener(self.socket_listener)
        self.master.destroy()
        if self.target['id'] in client.memory.window_instance[self.target['type']]:
            del client.memory.window_instance[self.target['type']][self.target['id']]

    def message_listener(self, data):
        self.digest_message(data)

    def socket_listener(self, data):
        if data['type'] == MessageType.query_room_users_result:
            if data['parameters'][1] != self.target['id']:
                return
            # [id, nickname, online, username]
            self.user_list = data['parameters'][0]
            self.refresh_user_listbox()

        if data['type'] == MessageType.room_user_on_off_line:
            # [room_id, user_id, online]
            if data['parameters'][0] != self.target['id']:
                return
            for i in range(0, len(self.user_list)):
                if self.user_list[i][0] == data['parameters'][1]:
                    self.user_list[i][2] = data['parameters'][2]

            self.refresh_user_listbox()

    def refresh_user_listbox(self):
        # [id, nickname, online, username]
        self.user_listbox.delete(0, END)
        self.user_list.sort(key=lambda x: x[2])

        for user in self.user_list:
            self.user_listbox.insert(0, user[1] + ("(在线)" if user[2] else "(离线)"))
            self.user_listbox.itemconfig(0, {'fg': ("green" if user[2] else "#999")})

    def digest_message(self, data):
        time = datetime.datetime.fromtimestamp(
            int(data['time']) / 1000
        ).strftime('%Y-%m-%d %H:%M:%S')
        self.append_to_chat_box(data['sender_name'] + "  " + time + '\n',
                                ('me' if client.memory.current_user['id'] == data[
                                    'sender_id'] else 'them'))
        # type 0 - 文字消息 1 - 图片消息
        if data['message']['type'] == 0:
            self.tag_i += 1
            self.chat_box.tag_config('new' + str(self.tag_i),
                                     lmargin1=16,
                                     lmargin2=16,
                                     foreground=data['message']['fontcolor'],
                                     font=(None, data['message']['fontsize']))
            self.append_to_chat_box(data['message']['data'] + '\n',
                                    'new' + str(self.tag_i))
        if data['message']['type'] == 1:
            client.memory.tk_img_ref.append(ImageTk.PhotoImage(data=data['message']['data']))
            self.chat_box.image_create(END, image=client.memory.tk_img_ref[-1], padx=16, pady=5)
            self.append_to_chat_box('\n', '')

    def user_listbox_double_click(self, _):
        if len(self.user_listbox.curselection()) == 0:
            return None
        index = self.user_listbox.curselection()[0]
        selected_user_id = self.user_list[len(self.user_list) - 1 - index][0]
        selected_user_nickname = self.user_list[len(self.user_list) - 1 - index][1]
        selected_user_username = self.user_list[len(self.user_list) - 1 - index][3]
        if selected_user_id == client.memory.current_user['id']:
            return
        client.memory.contact_window[0].try_open_user_id(selected_user_id, selected_user_nickname,
                                                         selected_user_username)
        # pprint(selected_user_id)
        return

    def __init__(self, target, master=None):
        super().__init__(master)
        self.master = master
        self.target = target
        self.user_listbox = tk.Listbox(self, bg='#EEE')
        client.util.socket_listener.add_listener(self.socket_listener)
        client.memory.unread_message_count[self.target['type']][self.target['id']] = 0
        client.memory.contact_window[0].refresh_contacts()
        master.resizable(width=True, height=True)
        master.geometry('660x500')
        master.minsize(520, 370)
        self.sc = client.memory.sc

        if self.target['type'] == 0:
            self.master.title(self.target['nickname'])

        if self.target['type'] == 1:
            self.master.title("群:" + str(self.target['id']) + " " + self.target['room_name'])
            self.sc.send(MessageType.query_room_users, self.target['id'])

        self.right_frame = tk.Frame(self, bg='white')

        self.user_listbox.bind('<Double-Button-1>', self.user_listbox_double_click)
        if self.target['type'] == 1:
            self.user_listbox.pack(side=LEFT, expand=False, fill=BOTH)
        self.right_frame.pack(side=LEFT, expand=True, fill=BOTH)

        self.input_frame = tk.Frame(self.right_frame, bg='white')

        self.input_textbox = ScrolledText(self.right_frame, height=10)
        self.input_textbox.bind("<Control-Return>", self.send_message)
        self.input_textbox.bind_all('<Key>', self.apply_font_change)

        self.send_btn = tk.Button(self.input_frame, text='发送消息(Ctrl+Enter)', command=self.send_message)
        self.send_btn.pack(side=RIGHT, expand=False)

        self.font_btn = tk.Button(self.input_frame, text='字体颜色', command=self.choose_color)
        self.font_btn.pack(side=LEFT, expand=False)

        self.font_btn = tk.Button(self.input_frame, text='字体大小', command=self.choose_font_size)
        self.font_btn.pack(side=LEFT, expand=False)

        self.image_btn = tk.Button(self.input_frame, text='发送图片', command=self.send_image)
        self.image_btn.pack(side=LEFT, expand=False)

        self.chat_box = ScrolledText(self.right_frame, bg='white')
        self.input_frame.pack(side=BOTTOM, fill=X, expand=False)
        self.input_textbox.pack(side=BOTTOM, fill=X, expand=False, padx=(0, 0), pady=(0, 0))
        self.chat_box.pack(side=BOTTOM, fill=BOTH, expand=True)
        self.chat_box.bind("<Key>", lambda e: "break")
        self.chat_box.tag_config("default", lmargin1=10, lmargin2=10, rmargin=10)
        self.chat_box.tag_config("me", foreground="green", spacing1='5')
        self.chat_box.tag_config("them", foreground="blue", spacing1='5')
        self.chat_box.tag_config("message", foreground="black", spacing1='0')
        self.chat_box.tag_config("system", foreground="grey", spacing1='0',
                                 justify='center',
                                 font=(None, 8))

        self.pack(expand=True, fill=BOTH)

        add_message_listener(self.target['type'], self.target['id'], self.message_listener)
        master.protocol("WM_DELETE_WINDOW", self.remove_listener_and_close)

        # 历史消息显示
        if target['id'] in client.memory.chat_history[self.target['type']]:
            for msg in client.memory.chat_history[self.target['type']][target['id']]:
                self.digest_message(msg)

            self.append_to_chat_box('- 以上是历史消息 -\n', 'system')

    def append_to_chat_box(self, message, tags):
        self.chat_box.insert(tk.END, message, [tags, 'default'])
        self.chat_box.update()
        self.chat_box.see(tk.END)

    def send_message(self, _=None):
        message = self.input_textbox.get("1.0", END)
        if not message or message.replace(" ", "").replace("\r", "").replace("\n", "") == '':
            return
        self.sc.send(MessageType.send_message,
                     {'target_type': self.target['type'], 'target_id': self.target['id'],
                      'message': {
                          'type': 0,
                          'data': message.strip().strip('\n'),
                          'fontsize': self.font_size,
                          'fontcolor': self.font_color
                      }
                      })
        self.input_textbox.delete("1.0", END)
        return 'break'

    def choose_color(self):
        _, self.font_color = colorchooser.askcolor(initialcolor=self.font_color)
        self.apply_font_change(None)

    def choose_font_size(self):
        result = simpledialog.askinteger("设置", "请输入字体大小", initialvalue=self.font_size)
        if result is None:
            return
        self.font_size = result
        self.apply_font_change(None)

    def apply_font_change(self, _):
        try:
            self.input_textbox.tag_config('new', foreground=self.font_color, font=(None, self.font_size))
            self.input_textbox.tag_add('new', '1.0', END)
        except:
            pass

    def send_image(self):
        filename = filedialog.askopenfilename(filetypes=[("Image Files",
                                                          ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.JPG", "*.JPEG",
                                                           "*.PNG", "*.GIF"]),
                                                         ("All Files", ["*.*"])])
        if filename is None or filename == '':
            return
        with open(filename, "rb") as imageFile:
            f = imageFile.read()
            b = bytearray(f)

            self.sc.send(MessageType.send_message,
                         {'target_type': self.target['type'], 'target_id': self.target['id'],
                          'message': {'type': 1, 'data': b}})
Example #2
0
class ChatForm(tk.Frame):

    font_color = "#000000"
    font_size = 16
    user_list = []
    tag_i = 0

    """将监听事件移除并关闭该窗口"""
    def remove_listener_and_close(self):
        remove_message_listener(self.message_listener)
        client.util.socket_listener.remove_listener(self.socket_listener)
        self.master.destroy()
        if self.target['id'] in client.memory.window_instance[self.target['type']]:
            del client.memory.window_instance[self.target['type']][self.target['id']]

    """定义监听事件"""
    def message_listener(self, data):
        self.digest_message(data)

    """监听socket传来的数据"""
    def socket_listener(self, data):
        init_time = int(time.time())
        dirname = "send_msg_log"
        filename = str(init_time)
        dir_flag = os.path.exists(dirname)
        if dir_flag == False:
            os.mkdir(dirname)
        if data['parameters']['message']['type'] == 1:
            with open(dirname + '/' + filename, 'wb') as f:
                contents = data['parameters']['message']['data']
                f.write(contents)
                f.close()
            with open(dirname + '/' + filename, 'rb') as f:
                file_format = filetype.guess(dirname + '/' + filename)
                file_format = file_format.extension
                if file_format == None:
                    file_format = "txt"
                f.close()
            os.rename(dirname + '/' + filename, (str(dirname + '/' + filename) + '_.' + file_format))
        if data['type'] == MessageType.query_room_users_result:
            if data['parameters'][1] != self.target['id']:
                return
            self.user_list = data['parameters'][0]
            self.refresh_user_listbox()
        if data['type'] == MessageType.room_user_on_off_line:
            if data['parameters'][0] != self.target['id']:
                return
            for i in range(0, len(self.user_list)):
                if self.user_list[i][0] == data['parameters'][1]:
                    self.user_list[i][2] = data['parameters'][2]
            self.refresh_user_listbox()

    """更新好友列表"""
    def refresh_user_listbox(self):
        self.user_listbox.delete(0, END)
        self.user_list.sort(key=lambda x: x[2])
        for user in self.user_list:
            self.user_listbox.insert(0, user[1] + ("(在线)" if user[2] else "(离线)"))
            self.user_listbox.itemconfig(0, {'fg': ("blue" if user[2] else "#505050")})

    """处理消息并将其展示出来"""
    def digest_message(self, data):
        time = datetime.datetime.fromtimestamp(
            int(data['time']) / 1000
        ).strftime('%Y-%m-%d %H:%M:%S')
        self.append_to_chat_box(data['sender_name'] + "  " + time + '\n',
                                ('me' if client.memory.current_user['id'] == data[
                                    'sender_id'] else 'them'))
        # type 0 - 文字消息 1 - 图片消息
        if data['message']['type'] == 0:
            self.tag_i += 1
            self.chat_box.tag_config('new' + str(self.tag_i),
                                     lmargin1=16,
                                     lmargin2=16,
                                     foreground=data['message']['fontcolor'],
                                     font=(None, data['message']['fontsize']))
            self.append_to_chat_box(data['message']['data'] + '\n',
                                    'new' + str(self.tag_i))
        if data['message']['type'] == 1:
            client.memory.tk_img_ref.append(ImageTk.PhotoImage(data=data['message']['data']))
            self.chat_box.image_create(END, image=client.memory.tk_img_ref[-1], padx=16, pady=5)
            self.append_to_chat_box('\n', '')

    """ 双击聊天框 """
    def user_listbox_double_click(self, _):
        if len(self.user_listbox.curselection()) == 0:
            return None
        index = self.user_listbox.curselection()[0]
        selected_user_id = self.user_list[len(self.user_list) - 1 - index][0]
        selected_user_username = self.user_list[len(self.user_list) - 1 - index][3]
        if selected_user_id == client.memory.current_user['id']:
            return
        client.memory.contact_window[0].try_open_user_id(selected_user_id,
                                                         selected_user_username)
        return

    def __init__(self, target, master=None):
        super().__init__(master)
        self.master = master
        self.target = target
        self.user_listbox = tk.Listbox(self, bg='#63d5eb', width=0, bd=0)
        client.util.socket_listener.add_listener(self.socket_listener)
        client.memory.unread_message_count[self.target['type']][self.target['id']] = 0
        client.memory.contact_window[0].refresh_contacts()
        master.resizable(width=False, height=False)
        master.geometry('580x500')
        self.sc = client.memory.sc
        # 私人聊天
        if self.target['type'] == 0:
            self.master.title(self.target['username'])
        # 群组聊天
        if self.target['type'] == 1:
            self.master.title("[群:" + str(self.target['id']) + "] " + self.target['room_name'])
            self.sc.send(MessageType.query_room_users, self.target['id'])

        self.right_frame = tk.Frame(self)

        self.user_listbox.bind('<Double-Button-1>', self.user_listbox_double_click)
        if self.target['type'] == 1:
            self.user_listbox.pack(side=LEFT, expand=False, fill=BOTH)

        self.right_frame.pack(side=LEFT, expand=True, fill=BOTH)
        self.input_frame = tk.Frame(self.right_frame, bg='#63d5eb')
        self.input_textbox = ScrolledText(self.right_frame, bg='#63d5eb', font=("楷书", 16), height=5)
        self.input_textbox.bind("<Control-Return>", self.send_message)
        self.input_textbox.bind_all('<Key>', self.apply_font_change)
        self.send_btn = tk.Button(self.input_frame, text='发送消息(Ctrl+Enter)', font=("仿宋", 16, 'bold'), fg="black",
                                  bg="#35d1e9",activebackground="#6cdcf0", relief=GROOVE, command=self.send_message)
        self.send_btn.pack(side=RIGHT, expand=False)
        self.font_btn = tk.Button(self.input_frame, text='字体颜色', font=("仿宋", 16, 'bold'), fg="black", bg="#35d1e9",
                                  activebackground="#6cdcf0", relief=GROOVE, command=self.choose_color)
        self.font_btn.pack(side=LEFT, expand=False)
        self.font_btn = tk.Button(self.input_frame, text='字体大小', font=("仿宋", 16, 'bold'), fg="black", bg="#35d1e9",
                                  activebackground="#6cdcf0", relief=GROOVE, command=self.choose_font_size)
        self.font_btn.pack(side=LEFT, expand=False)
        self.image_btn = tk.Button(self.input_frame, text='发送文件', font=("仿宋", 16, 'bold'), fg="black", bg="#35d1e9",
                                   activebackground="#6cdcf0", relief=GROOVE, command=self.send_image)
        self.image_btn.pack(side=LEFT, expand=False)
        self.chat_box = ScrolledText(self.right_frame, bg='#70d5eb')
        self.input_frame.pack(side=BOTTOM, fill=X, expand=False)
        self.input_textbox.pack(side=BOTTOM, fill=X, expand=False, padx=(0, 0), pady=(0, 0))
        self.chat_box.pack(side=BOTTOM, fill=BOTH, expand=True)
        self.chat_box.bind("<Key>", lambda e: "break")
        self.chat_box.tag_config("default", lmargin1=10, lmargin2=10, rmargin=10, font=("仿宋", 15))
        self.chat_box.tag_config("me", foreground="green", spacing1='0', font=("仿宋", 15))
        self.chat_box.tag_config("them", foreground="blue", spacing1='0', font=("仿宋", 15))
        self.chat_box.tag_config("message", foreground="black", spacing1='0', font=("楷体", 15))
        self.chat_box.tag_config("system", foreground="#505050", spacing1='0', justify='center', font=("新宋体", 10))

        self.pack(expand=True, fill=BOTH)

        add_message_listener(self.target['type'], self.target['id'], self.message_listener)
        master.protocol("WM_DELETE_WINDOW", self.remove_listener_and_close)

        # 历史消息显示
        if target['id'] in client.memory.chat_history[self.target['type']]:
            for msg in client.memory.chat_history[self.target['type']][target['id']]:
                self.digest_message(msg)

            self.append_to_chat_box('- 以上是历史消息 -\n', 'system')

    """ 附加聊天框 """
    def append_to_chat_box(self, message, tags):
        self.chat_box.insert(tk.END, message, [tags, 'default'])
        self.chat_box.update()
        self.chat_box.see(tk.END)

    """ 发送消息 """
    def send_message(self, _=None):
        message = self.input_textbox.get("1.0", END)
        if not message or message.replace(" ", "").replace("\r", "").replace("\n", "") == '':
            return
        self.sc.send(MessageType.send_message,
                     {'target_type': self.target['type'], 'target_id': self.target['id'],
                      'message': {
                          'type': 0,
                          'data': message.strip().strip('\n'),
                          'fontsize': self.font_size,
                          'fontcolor': self.font_color
                      }
                      })
        self.input_textbox.delete("1.0", END)
        return 'break'

    """ 选择字体颜色 """
    def choose_color(self):
        _, self.font_color = colorchooser.askcolor(initialcolor=self.font_color)
        self.apply_font_change(None)

    """ 选择字体大小 """
    def choose_font_size(self):
        result = simpledialog.askinteger("设置", "请输入字体大小", initialvalue=self.font_size)
        if result is None:
            return
        self.font_size = result
        self.apply_font_change(None)

    """" 更新字体 """
    def apply_font_change(self, _):
        try:
            self.input_textbox.tag_config('new', foreground=self.font_color, font=(None, self.font_size))
            self.input_textbox.tag_add('new', '1.0', END)
        except:
            pass

    """" 发送图片 """
    def send_image(self):
        filename = filedialog.askopenfilename(filetypes=[("Image Files",
                                                          ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.JPG", "*.JPEG",
                                                           "*.PNG", "*.GIF"]),
                                                         ("All Files", ["*.*"])])
        if filename is None or filename == '':
            return
        with open(filename, "rb") as imageFile:
            f = imageFile.read()
            b = bytearray(f)
            self.sc.send(MessageType.send_message,
                         {'target_type': self.target['type'], 'target_id': self.target['id'],
                          'message': {'type': 1, 'data': b}})
Example #3
0
class ChatForm(tk.Frame):
    font_color = "#000000"
    font_size = 12

    def on_list_click(self, e):
        name = self.chatroom_user_list.get(
            self.chatroom_user_list.curselection())
        for tmp in memory.chatroom_user_list[self.username]:
            if tmp[1] == name:
                uname = tmp[0]

        for fn in memory.friend_list:
            if uname == fn[1]:
                # It's friend...
                uname = memory.friend_list[fn] + "  (" + uname + ")"
                run(uname)
                return
        # Not friend...
        result = messagebox.askokcancel("还不是好友?",
                                        "你和" + name + "还不是好友,是否立即添加?")
        if result:
            friend_name = uname.encode()
            serializeMessage = common_handler.pack_message(
                common_handler.MessageType.add_friend, friend_name)
            client_socket.send_msg(serializeMessage)
            messagebox.showinfo('添加好友', '好友请求已发送')

    def __init__(self, master=None, username=None, nickname="Unkown"):
        super().__init__(master)
        self.master = master
        self.username = username
        self.nickname = nickname
        self.master.resizable(width=True, height=True)
        self.master.geometry('660x500')
        self.master.minsize(420, 370)

        self.master.title("与 {} 聊天中...".format(self.nickname))
        memory.Chat_window[self.username] = self
        print(memory.Chat_window)

        # Chatroom window

        for v in memory.friend_list:
            if v[1] == self.username:
                if v[0] == 2:
                    self.left_frame = tk.Frame(self)

                    self.scroll = Scrollbar(self.left_frame)
                    self.scroll.pack(side=RIGHT, fill=Y)
                    self.chatroom_user_list = Listbox(
                        self.left_frame, yscrollcommand=self.scroll.set)
                    self.chatroom_user_list.bind("<Double-Button-1>",
                                                 self.on_list_click)
                    self.scroll.config(command=self.chatroom_user_list.yview)
                    self.chatroom_user_list.pack(expand=True, fill=BOTH)
                    self.update_chatroom_user_list(v[1])
                    self.left_frame.pack(side=RIGHT, expand=True, fill=BOTH)

        # self.friend_name = tk.Label(
        #     self.left_frame, text=nickname, bg='#EEE', width=15)
        # self.friend_name.pack(expand=True, fill=BOTH, ipadx=5, ipady=5)

        self.right_frame = tk.Frame(self, bg='white')
        self.right_frame.pack(side=LEFT, expand=True, fill=BOTH)
        self.input_frame = tk.Frame(self.right_frame)
        self.input_textbox = ScrolledText(self.right_frame, height=7)
        self.input_textbox.bind("<Control-Return>", self.send_message)
        self.input_textbox.bind_all('<Key>', self.apply_font_change)

        self.send_btn = tk.Button(self.input_frame,
                                  text='发送消息(Ctrl+Enter)',
                                  command=self.send_message)
        self.send_btn.pack(side=RIGHT, expand=False)

        self.font_btn = tk.Button(self.input_frame,
                                  text='字体颜色',
                                  command=self.choose_color)
        self.font_btn.pack(side=LEFT, expand=False)

        self.font_btn = tk.Button(self.input_frame,
                                  text='字体大小',
                                  command=self.choose_font_size)
        self.font_btn.pack(side=LEFT, expand=False)

        # self.image_btn = tk.Button(
        #     self.input_frame, text='发送图片', command=self.send_image)
        # self.image_btn.pack(side=LEFT, expand=False)

        self.chat_box = ScrolledText(self.right_frame, bg='white')
        self.input_frame.pack(side=BOTTOM, fill=X, expand=False)
        self.input_textbox.pack(side=BOTTOM,
                                fill=X,
                                expand=False,
                                padx=(0, 0),
                                pady=(0, 0))
        self.chat_box.pack(side=BOTTOM, fill=BOTH, expand=True)
        self.chat_box.bind("<Key>", lambda e: "break")
        self.chat_box.tag_config("default",
                                 lmargin1=10,
                                 lmargin2=10,
                                 rmargin=10)
        self.chat_box.tag_config("me", foreground="green", spacing1='5')
        self.chat_box.tag_config("them", foreground="blue", spacing1='5')
        self.chat_box.tag_config("message", foreground="black", spacing1='0')
        self.chat_box.tag_config("system",
                                 foreground="grey",
                                 spacing1='0',
                                 justify='center',
                                 font=(None, 8))

        self.pack(expand=True, fill=BOTH, padx=5, pady=5, ipadx=5, ipady=5)

    def append_to_chat_box(self, time, user, message, tags):
        if user == memory.username:
            user = "******"
        time_info = "%s  %s 说:\n" % (time, user)
        self.chat_box.insert(tk.END, time_info, [tags, 'message'])
        self.chat_box.insert(tk.END, message, [tags, 'default'])
        self.chat_box.insert(tk.END, "\n", [tags, 'message'])
        self.chat_box.update()
        self.chat_box.see(tk.END)

    def send_message(self, _=None):
        stime = dtime.datetime.now()
        time_info = "%s年%s月%s日 %s时%s分%s秒" % (stime.year, stime.month,
                                             stime.day, stime.hour,
                                             stime.minute, stime.second)
        message = self.input_textbox.get("1.0", END)
        if not message or message.replace(" ", "").\
                replace("\r", "").replace("\n", "") == '':
            return
        for k1 in memory.friend_list:
            if k1 == (1, self.username):
                self.append_to_chat_box(time_info, "我", message, 'me')
        self.input_textbox.delete("1.0", END)

        # format datetime
        send_message_handler(time_info, message, self.username)
        return 'break'

    def choose_color(self):
        _, self.font_color = colorchooser.askcolor(
            initialcolor=self.font_color)
        self.apply_font_change(None)

    def choose_font_size(self):
        result = simpledialog.askinteger("设置",
                                         "请输入字体大小",
                                         initialvalue=self.font_size)
        if result is None:
            return
        self.font_size = result
        self.apply_font_change(None)

    def apply_font_change(self, _):
        try:
            self.input_textbox.tag_config('new',
                                          foreground=self.font_color,
                                          font=(None, self.font_size))
            self.input_textbox.tag_add('new', '1.0', END)
        except Exception:
            pass

    def close_window(self):
        del memory.Chat_window[self.username]
        self.master.destroy()

    def update_chatroom_user_list(self, chatroom_name):
        cn = chatroom_name.encode()
        serializeMessage = common_handler.pack_message(
            common_handler.MessageType.query_room_users, cn)
        client_socket.send_msg(serializeMessage)
Example #4
0
class TitlerApp:
    def __init__(self, root):
        self.file = open('title.html', 'w')
        self.ready = False

        self.root = root

        frame = Frame(root)
        frame.pack(fill=BOTH, expand=True)

        notebook = Notebook(frame)
        notebook.pack(fill=BOTH, expand=True)

        textFrame = Frame()
        notebook.add(textFrame, text="Text")

        Grid.columnconfigure(textFrame, 0, weight=1)

        textTopPanel = Frame(textFrame)
        textTopPanel.grid(row=0, sticky=W)

        openBrowserButton = Button(textTopPanel,
                                   text="Open Browser",
                                   command=self._openBrowser)
        openBrowserButton.grid(row=0, column=0)

        self.previewVar = IntVar()
        previewCheckbutton = Checkbutton(textTopPanel,
                                         text="Preview",
                                         command=self.updateFile,
                                         variable=self.previewVar)
        previewCheckbutton.grid(row=0, column=1)
        self.previewVar.set(1)

        self.statusLabel = Label(textTopPanel)
        self.statusLabel.grid(row=0, column=2)

        self.textBox = ScrolledText(textFrame, width=40, height=10)
        self.textBox.grid(row=1, sticky=N + S + E + W)
        self.textBox.bind_all('<<Modified>>', self._textboxCallback)

        propertiesFrame = Frame(textFrame)
        propertiesFrame.grid(row=2, sticky=E + W)
        self._makePropertiesFrame(propertiesFrame)

        layoutFrame = Frame()
        notebook.add(layoutFrame, text="Layout")

        layoutTopPanel = Frame(layoutFrame)
        layoutTopPanel.grid(row=0, sticky=W)

        getFromClipboardButton = Button(layoutTopPanel,
                                        text="Get from clipboard",
                                        command=self._getImageFromClipboard)
        getFromClipboardButton.grid(row=0, column=0)

        saveButton = Button(layoutTopPanel,
                            text="Save Image",
                            command=self._saveImage)
        saveButton.grid(row=0, column=1)

        self.imageLabel = Label(layoutFrame)
        self.imageLabel.grid(row=1, column=0)

        self.processedImage = None
        self.imageLabelPhoto = None  # thumbnail

    def _makePropertiesFrame(self, frame):
        #Grid.columnconfigure(frame, 0, weight=1)
        Grid.columnconfigure(frame, 1, weight=1)

        row = 0

        self.textColor = "#ffffff"
        self.backgroundColor = "#000000"

        textColorLabel = Label(frame, text="Text color:")
        textColorLabel.grid(row=row, column=0, sticky=E)

        self.textColorButton = tkinter.Button(frame,
                                              width=5,
                                              command=self._pickTextColor)
        self.textColorButton.grid(row=row, column=1, sticky=W)
        row += 1

        backgroundColorLabel = Label(frame, text="Background color:")
        backgroundColorLabel.grid(row=row, column=0, sticky=E)

        self.backgroundColorButton = tkinter.Button(
            frame, width=5, command=self._pickBackgroundColor)
        self.backgroundColorButton.grid(row=row, column=1, sticky=W)
        row += 1

        fontLabel = Label(frame, text="Font:")
        fontLabel.grid(row=row, column=0, sticky=E)

        self.fontModeVar = StringVar()

        websafeFontFrame = Frame(frame)
        websafeFontFrame.grid(row=row, column=1, sticky=W)
        row += 1

        Radiobutton(websafeFontFrame, text="Web Safe:",
                    variable=self.fontModeVar, value="websafe",
                    command=self.updateFile) \
            .grid(row=0, column=0)

        self.websafeFontVar = StringVar()
        self.websafeFontVar.set(fonts[0])
        fontOptionMenu = \
            OptionMenu(*([websafeFontFrame, self.websafeFontVar]
                         + fonts))
        fontOptionMenu.grid(row=0, column=1)
        self.websafeFontVar.trace(mode="w", callback=self.updateFile)

        googleFontFrame = Frame(frame)
        googleFontFrame.grid(row=row, column=1, sticky=W)
        row += 1

        Radiobutton(googleFontFrame, text="Google:",
                    variable=self.fontModeVar, value="google",
                    command=self.updateFile) \
            .grid(row=0, column=0)

        self.googleFontVar = StringVar()
        googleFontEntry = Entry(googleFontFrame,
                                width=22,
                                textvariable=self.googleFontVar)
        googleFontEntry.grid(row=0, column=1)
        self.googleFontVar.set("")
        self.googleFontVar.trace(mode="w", callback=self.updateFile)

        self.fontModeVar.set("websafe")

        fontSizeLabel = Label(frame, text="Font size:")
        fontSizeLabel.grid(row=row, column=0, sticky=E)

        # has to be self. , otherwise it will be garbage collected
        self.fontSizeVar = StringVar()
        fontSizeBox = Spinbox(frame,
                              from_=0,
                              to=65536,
                              width=5,
                              textvariable=self.fontSizeVar)
        fontSizeBox.grid(row=row, column=1, sticky=W)
        self.fontSizeVar.set('12')
        self.fontSizeVar.trace(mode="w", callback=self.updateFile)
        row += 1

        letterSpacingLabel = Label(frame, text="Letter spacing:")
        letterSpacingLabel.grid(row=row, column=0, sticky=E)

        self.letterSpacingVar = StringVar()
        letterSpacingBox = Spinbox(frame,
                                   from_=-999,
                                   to=999,
                                   width=5,
                                   textvariable=self.letterSpacingVar)
        letterSpacingBox.grid(row=row, column=1, sticky=W)
        self.letterSpacingVar.set('0')
        self.letterSpacingVar.trace(mode="w", callback=self.updateFile)
        row += 1

        wordSpacingLabel = Label(frame, text="Word spacing:")
        wordSpacingLabel.grid(row=row, column=0, sticky=E)

        self.wordSpacingVar = StringVar()
        wordSpacingBox = Spinbox(frame,
                                 from_=-999,
                                 to=999,
                                 width=5,
                                 textvariable=self.wordSpacingVar)
        wordSpacingBox.grid(row=row, column=1, sticky=W)
        self.wordSpacingVar.set('0')
        self.wordSpacingVar.trace(mode="w", callback=self.updateFile)
        row += 1

        lineHeightLabel = Label(frame, text="Line height:")
        lineHeightLabel.grid(row=row, column=0, sticky=E)

        self.lineHeightVar = StringVar()
        lineHeightBox = Spinbox(frame,
                                from_=0,
                                to=9999,
                                width=5,
                                textvariable=self.lineHeightVar)
        lineHeightBox.grid(row=row, column=1, sticky=W)
        self.lineHeightVar.set('100')
        self.lineHeightVar.trace(mode="w", callback=self.updateFile)
        row += 1

        paragraphMarginLabel = Label(frame, text="Paragraph break height:")
        paragraphMarginLabel.grid(row=row, column=0, sticky=E)

        self.paragraphMarginVar = StringVar()
        paragraphMarginBox = Spinbox(frame,
                                     from_=0,
                                     to=9999,
                                     width=5,
                                     textvariable=self.paragraphMarginVar)
        paragraphMarginBox.grid(row=row, column=1, sticky=W)
        self.paragraphMarginVar.set('0')
        self.paragraphMarginVar.trace(mode="w", callback=self.updateFile)
        row += 1

        wrapWidthLabel = Label(frame, text="Wrap width:")
        wrapWidthLabel.grid(row=row, column=0, sticky=E)

        self.wrapWidthVar = StringVar()
        wrapWidthBox = Spinbox(frame,
                               from_=0,
                               to=9999,
                               width=5,
                               textvariable=self.wrapWidthVar)
        wrapWidthBox.grid(row=row, column=1, sticky=W)
        self.wrapWidthVar.set('0')
        self.wrapWidthVar.trace(mode="w", callback=self.updateFile)
        row += 1

        textAlignLabel = Label(frame, text="Text align:")
        textAlignLabel.grid(row=row, column=0, sticky=E)

        self.textAlignVar = StringVar()
        Radiobutton(frame, text="Left", variable=self.textAlignVar,
                    value="left", command=self.updateFile)\
            .grid(row=row, column=1, sticky=W)
        row += 1
        Radiobutton(frame, text="Center", variable=self.textAlignVar,
                    value="center", command=self.updateFile)\
            .grid(row=row, column=1, sticky=W)
        row += 1
        Radiobutton(frame, text="Right", variable=self.textAlignVar,
                    value="right", command=self.updateFile)\
            .grid(row=row, column=1, sticky=W)
        row += 1
        Radiobutton(frame, text="Justify", variable=self.textAlignVar,
                    value="justify", command=self.updateFile)\
            .grid(row=row, column=1, sticky=W)
        row += 1
        self.textAlignVar.set('left')

        fontWeightLabel = Label(frame, text="Font weight:")
        fontWeightLabel.grid(row=row, column=0, sticky=E)

        fontWeightFrame = Frame(frame)
        fontWeightFrame.grid(row=row, column=1, sticky=W)

        self.fontWeightVar = StringVar()
        self.fontWeightBox = Spinbox(fontWeightFrame,
                                     from_=1,
                                     to=9,
                                     width=5,
                                     textvariable=self.fontWeightVar)
        self.fontWeightBox.pack(side=LEFT)
        self.fontWeightVar.set('4')
        self.fontWeightVar.trace(mode="w", callback=self.updateFile)

        fontWeightValuesLabel = Label(fontWeightFrame,
                                      text="1 - 9; 4: Normal, 7: Bold")
        fontWeightValuesLabel.pack(side=LEFT)
        row += 1

        self.italicsVar = IntVar()
        italicsLabel = Label(frame, text="Italics:")
        italicsLabel.grid(row=row, column=0, sticky=E)
        italicsCheckbutton = Checkbutton(frame,
                                         command=self.updateFile,
                                         variable=self.italicsVar)
        italicsCheckbutton.grid(row=row, column=1, sticky=W)
        row += 1

        self.underlineVar = IntVar()
        underlineLabel = Label(frame, text="Underline:")
        underlineLabel.grid(row=row, column=0, sticky=E)
        underlineCheckbutton = Checkbutton(frame,
                                           command=self.updateFile,
                                           variable=self.underlineVar)
        underlineCheckbutton.grid(row=row, column=1, sticky=W)
        row += 1

        self.strikethroughVar = IntVar()
        strikethroughLabel = Label(frame, text="Strikethrough:")
        strikethroughLabel.grid(row=row, column=0, sticky=E)
        strikethroughCheckbutton = Checkbutton(frame,
                                               command=self.updateFile,
                                               variable=self.strikethroughVar)
        strikethroughCheckbutton.grid(row=row, column=1, sticky=W)
        row += 1

        capsModeLabel = Label(frame, text="Caps:")
        capsModeLabel.grid(row=row, column=0, sticky=E)

        self.capsModeVar = StringVar()
        Radiobutton(frame, text="Normal", variable=self.capsModeVar,
                    value="normal", command=self.updateFile) \
            .grid(row=row, column=1, sticky=W)
        row += 1
        Radiobutton(frame, text="ALL CAPS", variable=self.capsModeVar,
                    value="upper", command=self.updateFile) \
            .grid(row=row, column=1, sticky=W)
        row += 1
        Radiobutton(frame, text="all lowercase", variable=self.capsModeVar,
                    value="lower", command=self.updateFile) \
            .grid(row=row, column=1, sticky=W)
        row += 1
        Radiobutton(frame, text="Small Caps", variable=self.capsModeVar,
                    value="small", command=self.updateFile) \
            .grid(row=row, column=1, sticky=W)
        row += 1
        self.capsModeVar.set("normal")

        self.ready = True
        self.updateFile()

    def _pickTextColor(self):
        color = colorchooser.askcolor()[1]
        if color is not None:
            self.textColor = color
            self.updateFile()

    def _pickBackgroundColor(self):
        color = colorchooser.askcolor()[1]
        if color is not None:
            self.backgroundColor = color
            self.updateFile()

    def _openBrowser(self):
        webbrowser.open(self.file.name)

    def _textboxCallback(self, *args, **kwargs):
        self.textBox.edit_modified(0)  # allow <<Modified>> event to run again
        self.updateFile()

    def updateFile(self, *args, **kwargs):
        if not self.ready:
            return

        # update gui
        self.textColorButton.configure(background=self.textColor)
        self.backgroundColorButton.configure(background=self.backgroundColor)

        try:
            text = self._generateHTML()
        except BaseException:
            self.statusLabel.config(text="ERROR", foreground="#FF0000")
            return

        self._writeFile(text)
        self.statusLabel.config(text="Updated", foreground="#000000")

    def _generateHTML(self):
        preview = self.previewVar.get() == 1

        fontName = ""
        fontLink = ""
        fontMode = self.fontModeVar.get()
        if fontMode == 'websafe':
            fontName = self.websafeFontVar.get()
        elif fontMode == 'google':
            fontName = self.googleFontVar.get()
            fontLink = '<link rel="stylesheet" type="text/css" ' \
                'href="https://fonts.googleapis.com/css?family=' \
                + (fontName.replace(' ', '+')) + ':' \
                + self.fontWeightVar.get() + '00' \
                + ('i' if self.italicsVar.get()==1 else '') \
                + '">'
            fontName = "'" + fontName + "'"

        rules = [
            ('*', {
                'margin': '0pt',
                'padding': '0pt'
            }),
            ('body', {
                'background-color': self.backgroundColor if preview \
                    else "#000000"
            }),
            ('pre', {
                'display': 'table',
                'border-style': 'none' if preview else 'solid',
                'border-color': "#FF0000",
                'border-width': '1px',
                'color': self.textColor if preview else "#FFFFFF",
                'font-family': fontName,
                'font-size': self.fontSizeVar.get() + 'pt',
                'letter-spacing': self.letterSpacingVar.get() + 'pt',
                'word-spacing': self.wordSpacingVar.get() + 'pt',
                'line-height': self.lineHeightVar.get() + '%',
                'margin-bottom': self.paragraphMarginVar.get() + 'pt',
                'width': 'auto' if float(self.wrapWidthVar.get()) == 0 \
                    else (self.wrapWidthVar.get() + 'pt'),
                'white-space': 'normal' if self.textAlignVar.get() == 'justify'\
                    else ('pre' if float(self.wrapWidthVar.get()) == 0
                    else 'pre-wrap'),
                'text-align': self.textAlignVar.get(),
                'font-weight': int(self.fontWeightVar.get()) * 100,
                'font-style': "italic" if self.italicsVar.get()==1 \
                    else "normal",
                'text-decoration':
                    ("underline " if self.underlineVar.get()==1 else "") \
                  + ("line-through " if self.strikethroughVar.get()==1 else ""),
                'font-variant': \
                    "small-caps" if self.capsModeVar.get() == "small" \
                    else "normal"
            })
        ]

        styleStr = generateCSS(rules)

        htmlStr = \
"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
{link}
<style>
{style}
</style>
</head>
<body>
<pre>
{text}
</pre>
</body>
</html>
"""

        text = self.textBox.get("1.0", END)
        text = text.rstrip()
        if self.capsModeVar.get() == "upper":
            text = text.upper()
        elif self.capsModeVar.get() == "lower":
            text = text.lower()
        text = html.escape(text)
        text = text.replace('\n\n', '</pre><pre>')
        text = text.replace('\n', '<br>')
        htmlStr = htmlStr.format(link=fontLink, style=styleStr, text=text)
        return htmlStr

    def _writeFile(self, text):
        self.file.seek(0)
        self.file.truncate(0)
        self.file.write(text)
        self.file.flush()

    def _getImageFromClipboard(self):
        clipboardImage = ImageGrab.grabclipboard()
        if clipboardImage == None:
            return
        self.processedImage = clipboardImage.convert("RGB")

        bbox = self.processedImage.getbbox()
        self.processedImage = self.processedImage.crop(bbox)

        data = self.processedImage.getdata()

        textColor = struct.unpack('BBB', bytes.fromhex(self.textColor[1:]))

        newData = bytes()
        for pixel in data:
            if pixel == (255, 0, 0):
                newData += bytes([0, 0, 0, 0])
            else:
                alpha = pixel[0]
                newData += bytes(textColor + (alpha, ))

        self.processedImage = \
            Image.frombytes("RGBA", (self.processedImage.size), newData)

        thumbnail = self.processedImage.resize(
            (384,
             int(self.processedImage.height / self.processedImage.width *
                 384.0)), Image.BICUBIC)
        self.imageLabelPhoto = ImageTk.PhotoImage(thumbnail)
        self.imageLabel.config(image=self.imageLabelPhoto)

    def _saveImage(self):
        if self.processedImage == None:
            return
        file = filedialog.asksaveasfile()
        if file == None:
            return
        try:
            try:
                self.processedImage.save(file.name)
            except KeyError as e:
                self.processedImage.save(file.name + ".png")
        except BaseException as e:
            messagebox.showerror("Error saving image!", str(e))