Commit 5fafb3dc authored by Taddeus Kroes's avatar Taddeus Kroes

Created base classes (for windows and bars) and improved CLI.

parent 4835e157
......@@ -14,9 +14,18 @@ class ClientConnection(object, asyncore.dispatcher):
self.user = 'Anonymous'
# Connection/chat event callbacks
self.event_list = { }
self.send_queue = Queue()
self.send_buffer = ''
def debug_log(self, msg):
if 'debug' in self.event_list:
self.event_list['debug'](msg)
else:
print msg
def connect(self, addr):
self.host = addr[0]
self.port = addr[1]
......@@ -28,10 +37,7 @@ class ClientConnection(object, asyncore.dispatcher):
self.user = name
def handle_connect(self):
"""
Called when a connection is established. This method will/should be
overwritten by the Command Line Interface or an other implementation.
"""
"""Called when a connection is established."""
pass
def handle_close(self):
......@@ -57,7 +63,7 @@ class ClientConnection(object, asyncore.dispatcher):
buf += chunk
buf = buf[:-1]
print '< %s' % buf
self.debug_log('< %s' % buf)
if not self.verified:
self.verify_server(buf)
......@@ -69,7 +75,7 @@ class ClientConnection(object, asyncore.dispatcher):
def handle_write(self):
if not self.send_buffer:
self.send_buffer = self.send_queue.get() + '\r\n'
print '> %s' % self.send_buffer[:-1]
self.debug_log('> %s' % self.send_buffer[:-2])
sent = self.send(self.send_buffer)
self.send_buffer = self.send_buffer[sent:]
......@@ -77,10 +83,14 @@ class ClientConnection(object, asyncore.dispatcher):
match = re.match('^CHAT/(\d\.\d)/([^\x00-\x1F/:]+)$', buf)
if not match:
print 'Failed to verify connection'
self.debug_log('Failed to verify connection')
sys.exit(-1)
print 'Connected to server, server version is', match.group(1)
if 'verify' in self.event_list:
self.event_list['verify'](buf, match.group(1))
else:
self.debug_log('Server version is %s' % match.group(1))
self.verified = True
self.send_queue.put('CHAT')
......@@ -91,7 +101,8 @@ class ClientConnection(object, asyncore.dispatcher):
self.authentification_sent = True
def parse_notification(self, buf):
pass
if 'notify' in self.event_list:
self.event_list['notify'](msg)
def writable(self):
return not self.send_queue.empty() or len(self.send_buffer)
......
import curses
class BaseBar(object):
def __init__(self, main, top, left, width):
# Reference to the main window
self.main = main
# Bar position
self.top = top
self.left = left
self.width = width
# Initialise bar
self.bar = curses.newwin(1, width, top, left)
import curses
class BaseWindow(object):
def __init__(self, main, top, left, height, width):
# Reference to the main window
self.main = main
# Window position
self.top = top
self.left = left
self.height = height
self.width = width
self.lines = []
# Initialise window
self.window = curses.newwin(height, width, top, left)
def clear(self):
self.lines = []
self.window.clear()
self.window.refresh()
def redraw(self):
for y in range(self.height):
self.window.move(y, 0)
for y, line in enumerate(self.lines[-self.height:]):
try:
self.window.addstr(y, 0, line.ljust(self.width))
except curses.error:
pass
self.window.refresh()
def append(self, msg):
while msg:
self.lines += [msg[:self.width]]
msg = msg[self.width:]
self.redraw()
import curses
from base_window import BaseWindow
class ChatWindow:
class ChatWindow(BaseWindow):
def __init__(self, main, top, left, height, width):
# Reference to the main window
self.main = main
# Command bar position
self.top = top
self.left = left
self.height = height
self.width = width
# Initialise window
self.window = curses.newwin(height, width, top, left)
super(ChatWindow, self).__init__(main, top, left, height, width)
# True, if help screen is currently displayed.
self.displayed_help = False
def clear(self):
self.window.clear()
self.window.refresh()
import curses
import sys
import threading
from async import ClientConnection
from chat_window import ChatWindow
from debug_window import DebugWindow
from info_bar import InfoBar
from command_bar import CommandBar
......@@ -67,37 +69,41 @@ class CLI:
"""
Initialise the window of this command line interface. The user interface
of this chat application consists of a main chat window, an optional
user list window and a command bar. The chat window does also view the
help screen, when the help screen is requested by the user.
user list window, an optional debug window, an info bar and at the
bottom a command bar. The chat window does also view the help screen,
when the help screen is requested by the user.
"""
self.max_y, self.max_x = self.stdscr.getmaxyx()
self.stdscr.refresh()
# Initialise chat window.
self.chat_window = ChatWindow(self, 0, 0, self.max_y - 2, self.max_x)
self.chat_window = ChatWindow(self, 0, 0, self.max_y - 8, self.max_x)
# Info bar between chat window and command bar
self.info_bar = InfoBar(self, self.max_y - 2, 0, 1, self.max_x)
# Debug bar between chat window and info bar
self.debug_window = DebugWindow(self, self.max_y - 5, 0, 3, self.max_x)
# Info bar between debug window and command bar
self.info_bar = InfoBar(self, self.max_y - 2, 0, self.max_x)
# Command bar at the bottom of the screen.
self.command_bar = CommandBar(self, self.max_y - 1, 0, 1, self.max_x)
self.command_bar = CommandBar(self, self.max_y - 1, 0, self.max_x)
# set cursor position to bottom of screen.
self.stdscr.move(self.max_y - 1, 0)
self.stdscr.refresh()
def init_input_handler(self):
while True:
# KeyboardInterrupt is raised when an user presses ^C. This
# try/except block will catch the exception and shutdown the
# application gracefully.
try:
# KeyboardInterrupt is raised when an user presses ^C. This
# try/except block will catch the exception and shutdown the
# application gracefully.
try:
while True:
c = self.stdscr.getch()
if c != curses.ERR and self.command_bar.handle_input(c):
break
except KeyboardInterrupt:
self.execute('quit')
except KeyboardInterrupt:
self.execute('quit')
def init_command_handler(self):
......@@ -115,20 +121,24 @@ class CLI:
if getattr(port, '__class__') != '<type \'int\'>':
port = int(port)
# Disconnect an active connection.
if main.connection:
main.execute('disconnect')
main.display_info('Connecting to %s:%d...' % (host, port))
def handle_connect(conn):
"""Called when a connection is established."""
conn.main.display_info('Connected to %s' % conn.host)
# Initialise the connection and bind event handlers.
main.connection = ClientConnection()
main.connection.main = main
main.connection.handle_connect = handle_connect
main.connection.event_list['debug'] = main.debug_log
main.connection.connect((host, port))
# The chat connection is ran in a separate thread.
import asyncore
self.connection_thread = threading.Thread()
self.connection_thread.run = asyncore.loop
self.connection_thread.start()
def disconnect(main):
"""
Disconnect from chat server. When there is no active connection,
......@@ -140,6 +150,7 @@ class CLI:
self.connection = None
self.display_info('Offline. Type "/connect HOST" to connect' \
+ ' to another chat server.')
self.debug_window.clear()
def help(main):
if main.chat_window.displayed_help:
......@@ -179,6 +190,9 @@ All commands listed below should be preceded by a slash:
main.chat_window.window.refresh()
def quit(main):
# Disconnect the connection
del self.connection
# Reverse the curses-friendly terminal settings.
curses.nocbreak();
self.stdscr.keypad(0);
......@@ -203,6 +217,9 @@ All commands listed below should be preceded by a slash:
'quit': quit
}
def debug_log(self, msg):
self.debug_window.append(msg)
def display_info(self, msg):
"""
Display a message on the information bar (info_bar). This will erase the
......
......@@ -2,26 +2,19 @@ import curses
from curses.textpad import Textbox
import re
class CommandBar:
from base_bar import BaseBar
class CommandBar(BaseBar):
"""
Command bar is part of the Command Line Interface (CLI) of the chat
application. The command bar is responsible for handling the user input.
This inclues executing commands and (re)drawing the command line.
"""
def __init__(self, main, top, left, width):
super(CommandBar, self).__init__(main, top, left, width)
def __init__(self, main, top, left, height, width):
# Reference to the main window
self.main = main
# Command bar position
self.top = top
self.left = left
self.height = height
self.width = width
# Initialise window and textbox
self.window = curses.newwin(height, width, top, left)
self.textbox = Textbox(self.window)
# Initialise textbox
self.textbox = Textbox(self.bar)
def handle_input(self, c):
#if c == curses.KEY_HOME:
......@@ -33,14 +26,14 @@ class CommandBar:
def append_command(self, c):
if not self.textbox.do_command(c):
self.execute_command()
self.window.refresh()
self.bar.refresh()
def execute_command(self, command=''):
if not command:
# Clear command bar, reset cursor and redraw the command bar.
command = self.textbox.gather()
self.window.deleteln()
self.window.move(0, 0)
self.bar.deleteln()
self.bar.move(0, 0)
command = command.strip()
......
import curses
from base_window import BaseWindow
class DebugWindow(BaseWindow):
pass
import curses
class InfoBar:
def __init__(self, main, top, left, height, width):
# Reference to the main window
self.main = main
import curses
# Command bar position
self.top = top
self.left = left
self.height = height
self.width = width
from base_bar import BaseBar
# Initialise window
self.window = curses.newwin(height, width, top, left)
class InfoBar(BaseBar):
def __init__(self, main, top, left, width):
super(InfoBar, self).__init__(main, top, left, width)
# Display start message
self.color_pair = curses.color_pair(1)
start_message = 'Type "/connect HOST" to connect to a chat server' \
+ ' or "/help" for help.'
self.display(start_message)
def display(self, msg):
color_pair = curses.color_pair(1)
try:
self.window.addstr(0, 0, msg.ljust(self.width), color_pair)
self.bar.addstr(0, 0, msg.ljust(self.width), self.color_pair)
except curses.error:
pass
self.window.refresh()
self.bar.refresh()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment