unit MainForm;
{*******************************************************************************
  TCP/IP Chat Demo
  Written by David Clegg, davidclegg@optusnet.com.au.
*******************************************************************************}

interface

uses
  System.Drawing, System.ComponentModel,
  System.Windows.Forms, System.Data, System.Net.Sockets,
  System.Resources, SocketManager;

type

  TMainForm = class(System.Windows.Forms.Form)
  {$REGION 'Designer Managed Code'}
  strict private
    /// <summary>
    /// Required designer variable.
    /// </summary>
    components: System.ComponentModel.IContainer;
    tbSaid: System.Windows.Forms.TextBox;
    tbSay: System.Windows.Forms.TextBox;
    mmMain: System.Windows.Forms.MainMenu;
    miFile: System.Windows.Forms.MenuItem;
    miConnect: System.Windows.Forms.MenuItem;
    miListen: System.Windows.Forms.MenuItem;
    MenuItem1: System.Windows.Forms.MenuItem;
    miClose: System.Windows.Forms.MenuItem;
    sbMain: System.Windows.Forms.StatusBar;
    tbMain: System.Windows.Forms.ToolBar;
    tbConnect: System.Windows.Forms.ToolBarButton;
    ilSmall: System.Windows.Forms.ImageList;
    ToolBarButton1: System.Windows.Forms.ToolBarButton;
    ToolBarButton2: System.Windows.Forms.ToolBarButton;
    tbListen: System.Windows.Forms.ToolBarButton;
    MenuItem2: System.Windows.Forms.MenuItem;
    miAbout: System.Windows.Forms.MenuItem;
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    procedure InitializeComponent;
    procedure miClose_Click(sender: System.Object; e: System.EventArgs);
    procedure miConnect_Click(sender: System.Object; e: System.EventArgs);
    procedure miListen_Click(sender: System.Object; e: System.EventArgs);
    procedure tbSay_KeyPress(sender: System.Object; e: System.Windows.Forms.KeyPressEventArgs);
    procedure tbMain_ButtonClick(sender: System.Object; e: System.Windows.Forms.ToolBarButtonClickEventArgs);
    procedure TMainForm_Load(sender: System.Object; e: System.EventArgs);
    procedure TMainForm_Closed(sender: System.Object; e: System.EventArgs);
    procedure miAbout_Click(sender: System.Object; e: System.EventArgs);
  private
    FSocketManager: TSocketManager;
    FAddress: string;
    procedure ClientConnected(Sender: TObject; pSocket: Socket);
    procedure ClientDisconnected(Sender: TObject; pSocket: Socket);
    procedure ShowConnectStatus(const pConnected: boolean);
    procedure ShowListenStatus(const pListening: boolean);
    procedure DoClientConnect;
    procedure DoListen;
    procedure ClientConnect;
    procedure ClientDisconnect;
    procedure Listen;
    procedure AddResponse(const pResult: string);
    procedure ReceiveError(Sender: TObject; e: Exception);
    procedure SendError(Sender: TObject; e: Exception);
    procedure ListenError(Sender: TObject; e: Exception);
    procedure DataReceived(Sender: TObject; const pData: string);
    procedure StopListening;
    procedure ConnectEvents;
    procedure EnableServerControls(const pEnable: boolean);
    procedure EnableClientControls(const pEnable: boolean);
  {$ENDREGION}
  strict protected
    /// <summary>
    /// Clean up any resourFces being used.
    /// </summary>
    procedure Dispose(Disposing: Boolean); override;
  public
    constructor Create;
  end;

implementation

uses
   ConnectForm, SysUtils, AboutForm;

const
  SP_STATUS_INDEX = 0;
  SP_INFO_INDEX = 1;
  IMG_CONNECT_INDEX = 0;
  IMG_DISCONNECT_INDEX = 1;
  IMG_LISTEN_INDEX = 2;
  IMG_STOP_LISTENING_INDEX = 3;
  BTN_CONNECT_INDEX = 1;
  BTN_LISTEN_INDEX = 3;
  CLIENT_PORT = 1024;

{$REGION 'Windows Form Designer generated code'}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
procedure TMainForm.InitializeComponent;
type
  TSystem_Windows_Forms_MenuItemArray = array of System.Windows.Forms.MenuItem;
  TSystem_Windows_Forms_ToolBarButtonArray = array of System.Windows.Forms.ToolBarButton;
var
  resources: System.Resources.ResourceManager;
begin
  Self.components := System.ComponentModel.Container.Create;
  resources := System.Resources.ResourceManager.Create(TypeOf(TMainForm));
  Self.mmMain := System.Windows.Forms.MainMenu.Create;
  Self.miFile := System.Windows.Forms.MenuItem.Create;
  Self.miConnect := System.Windows.Forms.MenuItem.Create;
  Self.miListen := System.Windows.Forms.MenuItem.Create;
  Self.MenuItem1 := System.Windows.Forms.MenuItem.Create;
  Self.miClose := System.Windows.Forms.MenuItem.Create;
  Self.MenuItem2 := System.Windows.Forms.MenuItem.Create;
  Self.miAbout := System.Windows.Forms.MenuItem.Create;
  Self.sbMain := System.Windows.Forms.StatusBar.Create;
  Self.tbMain := System.Windows.Forms.ToolBar.Create;
  Self.ToolBarButton1 := System.Windows.Forms.ToolBarButton.Create;
  Self.tbConnect := System.Windows.Forms.ToolBarButton.Create;
  Self.ToolBarButton2 := System.Windows.Forms.ToolBarButton.Create;
  Self.tbListen := System.Windows.Forms.ToolBarButton.Create;
  Self.tbSay := System.Windows.Forms.TextBox.Create;
  Self.tbSaid := System.Windows.Forms.TextBox.Create;
  Self.ilSmall := System.Windows.Forms.ImageList.Create(Self.components);
  Self.SuspendLayout;
  // 
  // mmMain
  // 
  Self.mmMain.MenuItems.AddRange(TSystem_Windows_Forms_MenuItemArray.Create(Self.miFile, Self.MenuItem2));
  // 
  // miFile
  // 
  Self.miFile.Index := 0;
  Self.miFile.MenuItems.AddRange(TSystem_Windows_Forms_MenuItemArray.Create(Self.miConnect, Self.miListen, Self.MenuItem1, Self.miClose));
  Self.miFile.Text := 'File';
  // 
  // miConnect
  // 
  Self.miConnect.Index := 0;
  Self.miConnect.Text := 'Connect...';
  Include(Self.miConnect.Click, Self.miConnect_Click);
  // 
  // miListen
  // 
  Self.miListen.Index := 1;
  Self.miListen.Text := 'Listen';
  Include(Self.miListen.Click, Self.miListen_Click);
  // 
  // MenuItem1
  // 
  Self.MenuItem1.Index := 2;
  Self.MenuItem1.Text := '-';
  // 
  // miClose
  // 
  Self.miClose.Index := 3;
  Self.miClose.Text := 'Close';
  Include(Self.miClose.Click, Self.miClose_Click);
  // 
  // MenuItem2
  // 
  Self.MenuItem2.Index := 1;
  Self.MenuItem2.MenuItems.AddRange(TSystem_Windows_Forms_MenuItemArray.Create(Self.miAbout));
  Self.MenuItem2.Text := 'Help';
  // 
  // miAbout
  // 
  Self.miAbout.Index := 0;
  Self.miAbout.Text := 'About...';
  Include(Self.miAbout.Click, Self.miAbout_Click);
  // 
  // sbMain
  // 
  Self.sbMain.Location := System.Drawing.Point.Create(0, 321);
  Self.sbMain.Name := 'sbMain';
  Self.sbMain.Size := System.Drawing.Size.Create(392, 22);
  Self.sbMain.TabIndex := 4;
  // 
  // tbMain
  // 
  Self.tbMain.Buttons.AddRange(TSystem_Windows_Forms_ToolBarButtonArray.Create(Self.ToolBarButton1, Self.tbConnect, Self.ToolBarButton2, Self.tbListen));
  Self.tbMain.DropDownArrows := True;
  Self.tbMain.Location := System.Drawing.Point.Create(0, 0);
  Self.tbMain.Name := 'tbMain';
  Self.tbMain.ShowToolTips := True;
  Self.tbMain.Size := System.Drawing.Size.Create(392, 28);
  Self.tbMain.TabIndex := 7;
  Include(Self.tbMain.ButtonClick, Self.tbMain_ButtonClick);
  // 
  // ToolBarButton1
  // 
  Self.ToolBarButton1.Style := System.Windows.Forms.ToolBarButtonStyle.Separator;
  // 
  // tbConnect
  // 
  Self.tbConnect.ImageIndex := 0;
  // 
  // ToolBarButton2
  // 
  Self.ToolBarButton2.Style := System.Windows.Forms.ToolBarButtonStyle.Separator;
  // 
  // tbListen
  // 
  Self.tbListen.ImageIndex := 2;
  // 
  // tbSay
  // 
  Self.tbSay.Dock := System.Windows.Forms.DockStyle.Top;
  Self.tbSay.Location := System.Drawing.Point.Create(0, 28);
  Self.tbSay.Multiline := True;
  Self.tbSay.Name := 'tbSay';
  Self.tbSay.ScrollBars := System.Windows.Forms.ScrollBars.Vertical;
  Self.tbSay.Size := System.Drawing.Size.Create(392, 132);
  Self.tbSay.TabIndex := 8;
  Self.tbSay.Text := '';
  Include(Self.tbSay.KeyPress, Self.tbSay_KeyPress);
  // 
  // tbSaid
  // 
  Self.tbSaid.BackColor := System.Drawing.SystemColors.Control;
  Self.tbSaid.Dock := System.Windows.Forms.DockStyle.Fill;
  Self.tbSaid.Location := System.Drawing.Point.Create(0, 160);
  Self.tbSaid.Multiline := True;
  Self.tbSaid.Name := 'tbSaid';
  Self.tbSaid.ScrollBars := System.Windows.Forms.ScrollBars.Vertical;
  Self.tbSaid.Size := System.Drawing.Size.Create(392, 161);
  Self.tbSaid.TabIndex := 9;
  Self.tbSaid.Text := '';
  // 
  // ilSmall
  // 
  Self.ilSmall.ColorDepth := System.Windows.Forms.ColorDepth.Depth32Bit;
  Self.ilSmall.ImageSize := System.Drawing.Size.Create(16, 16);
  Self.ilSmall.ImageStream := (System.Windows.Forms.ImageListStreamer(resources.GetObject('ilSmall.ImageStream')));
  Self.ilSmall.TransparentColor := System.Drawing.Color.Fuchsia;
  // 
  // TMainForm
  // 
  Self.AutoScaleBaseSize := System.Drawing.Size.Create(5, 13);
  Self.ClientSize := System.Drawing.Size.Create(392, 343);
  Self.Controls.Add(Self.tbSaid);
  Self.Controls.Add(Self.tbSay);
  Self.Controls.Add(Self.tbMain);
  Self.Controls.Add(Self.sbMain);
  Self.Menu := Self.mmMain;
  Self.Name := 'TMainForm';
  Self.StartPosition := System.Windows.Forms.FormStartPosition.CenterScreen;
  Self.Text := 'TCP/IP Chat Demo';
  Include(Self.Load, Self.TMainForm_Load);
  Include(Self.Closed, Self.TMainForm_Closed);
  Self.ResumeLayout(False);
end;
{$ENDREGION}

procedure TMainForm.Dispose(Disposing: Boolean);
begin
  if Disposing then
  begin
    if Components <> nil then
      Components.Dispose();
  end;
  inherited Dispose(Disposing);
end;

constructor TMainForm.Create;
begin
  inherited Create;
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent;
end;

procedure TMainForm.miAbout_Click(sender: System.Object; e: System.EventArgs);
begin
  TAboutForm.ShowAboutForm;
end;

procedure TMainForm.TMainForm_Closed(sender: System.Object; e: System.EventArgs);
begin
  //Ensure we close the socket connections.
  if FSocketManager.Connected then
    FSocketManager.Disconnect;
  if FSocketManager.Listening then
    FSocketManager.StopListening;
end;

procedure TMainForm.TMainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  tbMain.ImageList := ilSmall;
  tbMain.Buttons[0].ImageIndex := 0;
  FSocketManager := TSocketManager.Create;
  ConnectEvents;
  FAddress := Environment.MachineName;
end;

procedure TMainForm.tbMain_ButtonClick(sender: System.Object; e: System.Windows.Forms.ToolBarButtonClickEventArgs);
begin
  if e.Button = tbConnect then
    DoClientConnect
  else if e.Button = tbListen then
    DoListen;
end;

/// <summary>
/// When the Enter key is pressed, send the last line in tbSay.
/// </summary>
procedure TMainForm.tbSay_KeyPress(sender: System.Object; e: System.Windows.Forms.KeyPressEventArgs);
begin
  if e.KeyChar = Char(13) then
    FSocketManager.SendText(tbSay.Lines[Length(tbSay.Lines) -1]);
end;

procedure TMainForm.miListen_Click(sender: System.Object; e: System.EventArgs);
begin
  DoListen;
end;

/// <summary>
/// Listen or stop listening on the server socket, dependant on the current
/// state.
/// </summary>
procedure TMainForm.DoListen;
begin
  if miListen.Checked then
    StopListening
  else
    Listen;
end;

/// <summary>
/// Listen on the server socket.
/// </summary>
procedure TMainForm.Listen;
begin
  FSocketManager.Listen;
  ShowListenStatus(True);
end;

/// <summary>
/// Stop listening on the server socket.
/// </summary>
procedure TMainForm.StopListening;
begin
  FSocketManager.StopListening;
  miListen.Checked := False;
  ShowListenStatus(False);
end;

procedure TMainForm.miConnect_Click(sender: System.Object; e: System.EventArgs);
begin
  DoClientConnect;
end;

/// <summary>
/// Connect or disconnect the client socket, dependant on the current state.
/// </summary>
procedure TMainForm.DoClientConnect;
begin
  if miConnect.Text = 'Connect...' then
    ClientConnect
  else
    ClientDisconnect;
end;

/// <summary>
/// Connect the client socket, after prompting for the server address.
/// </summary>
procedure TMainForm.ClientConnect;
begin
  if TConnectForm.ShowConnectForm(FAddress) = System.Windows.Forms.DialogResult.OK then
  begin
    sbMain.Text := 'Connecting...';
    try
      FSocketManager.Connect(FAddress, CLIENT_PORT);
      if FSocketManager.ClientSocket.Connected then
      begin
        sbMain.Text := Format('Connected to %s', [FSocketManager.ClientSocket.RemoteEndPoint.ToString]);
        ShowConnectStatus(true);
      end
      else
        ShowConnectStatus(false);
    except on e: Exception do
      begin
        sbMain.Text := Format('Connect Error: %s', [e.Message]);
      end;
    end;
  end;
end;

/// <summary>
/// Initialize the event handlers
/// </summary>
procedure TMainForm.ConnectEvents;
begin
  FSocketManager.OnDataReceived := DataReceived;
  FSocketManager.OnSendError := SendError;
  FSocketManager.OnReceiveError := ReceiveError;
  FSocketManager.OnListenError := ListenError;
  FSocketManager.OnClientConnected := ClientConnected;
  FSocketManager.OnClientDisconnected := ClientDisconnected;
end;

/// <summary>
/// Event fired when a client has connected.
/// </summary>
procedure TMainForm.ClientConnected(Sender: TObject; pSocket: Socket);
begin
  sbMain.Text := Format('Connected to %s', [pSocket.RemoteEndPoint.ToString]);
end;

/// <summary>
/// Event fired when a client has disconected.
/// </summary>
procedure TMainForm.ClientDisconnected(Sender: TObject; pSocket: Socket);
begin
  ShowListenStatus(true);
end;

/// <summary>
/// Event fired when data has been received.
/// </summary>
procedure TMainForm.DataReceived(Sender: TObject; const pData: string);
begin
  AddResponse(pData);
end;

/// <summary>
/// Event fired when there is an error sending data.
/// </summary>
procedure TMainForm.SendError(Sender: TObject; e: Exception);
begin
  sbMain.Text := Format('Send Error: %s', [e.Message]);
end;

/// <summary>
/// Event fired when there is an error receiving data.
/// </summary>
procedure TMainForm.ReceiveError(Sender: TObject; e: Exception);
begin
  sbMain.Text := Format('Receive Error: %s', [e.Message]);
end;

/// <summary>
/// Event fired when the server encounters an error listening for client
/// connections.
/// </summary>
procedure TMainForm.ListenError(Sender: TObject; e: Exception);
begin
  sbMain.Text := Format('Listen Error: %s', [e.Message]);
end;

/// <summary>
/// Disconnect the clients connection to the server.
/// </summary>
procedure TMainForm.ClientDisconnect;
begin
  FSocketManager.Disconnect;
  ShowConnectStatus(false);
end;

/// <summary>
/// Update the GUI to reflect the clients connection status.
/// </summary>
procedure TMainForm.ShowConnectStatus(const pConnected: boolean);
begin
  if pConnected then
  begin
    miConnect.Text := 'Disconnect';
    tbMain.Buttons[BTN_CONNECT_INDEX].ImageIndex := IMG_DISCONNECT_INDEX;
    EnableServerControls(False);
  end
  else
  begin
    miConnect.Text := 'Connect...';
    tbMain.Buttons[BTN_CONNECT_INDEX].ImageIndex := IMG_CONNECT_INDEX;
    sbMain.Text := 'Idle';
    EnableServerControls(True);
  end;
end;

/// <summary>
/// Enable or disable the controls allowing the TCP/IP Server to start
/// listening.
/// </summary>
procedure TMainForm.EnableServerControls(const pEnable: boolean);
begin
  miListen.Enabled := pEnable;
  tbMain.Buttons[BTN_LISTEN_INDEX].Enabled := pEnable;
end;

/// <summary>
/// Update the GUI to reflect the Servers listening status.
/// </summary>
procedure TMainForm.ShowListenStatus(const pListening: boolean);
begin
  if pListening then
  begin
    sbMain.Text := 'Listening';
    miListen.Checked := True;
    tbMain.Buttons[BTN_LISTEN_INDEX].ImageIndex := IMG_STOP_LISTENING_INDEX;
    EnableClientControls(False);
  end
  else
  begin
    sbMain.Text := 'Idle';
    miListen.Checked := False;
    tbMain.Buttons[BTN_LISTEN_INDEX].ImageIndex := IMG_LISTEN_INDEX;
    EnableClientControls(True);
  end;
end;

/// <summary>
/// Enable or disable the controls allowing the TCP/IP client to connect to
/// the server.
/// </summary>
procedure TMainForm.EnableClientControls(const pEnable: boolean);
begin
  miConnect.Enabled := pEnable;
  tbMain.Buttons[BTN_CONNECT_INDEX].Enabled := pEnable;
end;

/// <summary>
/// Add text to tbSaid, after appending CRLF control characters.
/// </summary>
procedure TMainForm.AddResponse(const pResult: string);
begin
  tbSaid.AppendText(pResult + #13#10);
end;

procedure TMainForm.miClose_Click(sender: System.Object; e: System.EventArgs);
begin
  Close;
end;

end.
