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