|  | Home · Examples

# Code for Chat Example

    /****************************************************************************
     **
     ** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
     **
     ** This file is part of Qt Jambi.
     **
     ** ** This file may be used under the terms of the GNU General Public
    ** License version 2.0 as published by the Free Software Foundation
    ** and appearing in the file LICENSE.GPL included in the packaging of
    ** this file.  Please review the following information to ensure GNU
    ** General Public Licensing requirements will be met:
    ** http://www.trolltech.com/products/qt/opensource.html
    **
    ** If you are unsure which license is appropriate for your use, please
    ** review the following information:
    ** http://www.trolltech.com/products/qt/licensing.html or contact the
    ** sales department at sales@trolltech.com.

     **
     ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
     ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     **
     ****************************************************************************/

    package com.trolltech.examples;

    import com.trolltech.qt.core.*;
    import com.trolltech.qt.core.Qt.*;
    import com.trolltech.qt.gui.*;
    import com.trolltech.qt.network.*;

    import java.util.*;

    @QtJambiExample(name = "Chat")
    public class Chat extends QDialog {

        public static void main(String[] args) {

            QApplication.initialize(args);
            Chat chat = new Chat();
            chat.show();
            QApplication.exec();

        }

        private Ui_ChatDialog ui = new Ui_ChatDialog();
        private Client client = new Client();
        private String myNickName;
        private QTextTableFormat tableFormat = new QTextTableFormat();

        public Chat() {
            this(null);
        }

        public Chat(QWidget parent) {
            super(parent);

            ui.setupUi(this);

            ui.lineEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus);
            ui.textEdit.setFocusPolicy(Qt.FocusPolicy.NoFocus);
            ui.textEdit.setReadOnly(true);
            ui.listWidget.setFocusPolicy(Qt.FocusPolicy.NoFocus);

            ui.lineEdit.returnPressed.connect(this, "returnPressed()");

            client.newMessage__from__message.connect(this, "appendMessage(String, String)");
            client.newParticipant__nick.connect(this, "newParticipant(String)");
            client.participantLeft__nick.connect(this, "participantLeft(String)");

            myNickName = client.nickName();
            newParticipant(myNickName);
            tableFormat.setBorder(0);

            setWindowTitle("Chat " + myNickName);

            QTimer.singleShot(10 * 1000, this, "showInformation()");
        }

        public String tr(String str, Object... arguments) {
            return String.format(tr(str), arguments);
        }

        private void appendMessage(final String from, final String message) {
            if (from.equals("") || message.equals(""))
                return;

            QTextCursor cursor = new QTextCursor(ui.textEdit.textCursor());
            cursor.movePosition(QTextCursor.MoveOperation.End);
            QTextTable table = cursor.insertTable(1, 2, tableFormat);
            table.cellAt(0, 0).firstCursorPosition().insertText("<" + from + "> ");
            table.cellAt(0, 1).firstCursorPosition().insertText(message);
            QScrollBar bar = ui.textEdit.verticalScrollBar();
            bar.setValue(bar.maximum());
        }

        void returnPressed() {
            String text = ui.lineEdit.text();
            if (text.equals(""))
                return;

            if (text.startsWith("/")) {
                QColor color = ui.textEdit.textColor();
                ui.textEdit.setTextColor(QColor.red);
                ui.textEdit.append(tr("! Unknown command: ") + text.substring(text.indexOf(' ')));
                ui.textEdit.setTextColor(color);
            } else {
                client.sendMessage(text);
                appendMessage(myNickName, text);
            }

            ui.lineEdit.clear();
        }

        private void newParticipant(final String nick) {
            if (nick.equals(""))
                return;

            QColor color = ui.textEdit.textColor();
            ui.textEdit.setTextColor(QColor.gray);
            ui.textEdit.append(String.format(tr("* %1$s has joined"), nick));
            ui.textEdit.setTextColor(color);
            ui.listWidget.addItem(new QListWidgetItem(nick));
        }

        void participantLeft(final String nick) {
            if (nick.equals(""))
                return;

            List<QListWidgetItem> items = ui.listWidget.findItems(nick, MatchFlag.MatchExactly);

            for (Iterator<QListWidgetItem> iterator = items.iterator(); iterator.hasNext();) {
                QListWidgetItem item = (QListWidgetItem) iterator.next();
                ui.listWidget.removeItemWidget(item);
                ui.listWidget.takeItem(ui.listWidget.row(item));
            }

            QColor color = ui.textEdit.textColor();
            ui.textEdit.setTextColor(QColor.gray);
            ui.textEdit.append(String.format(tr("* %1$s has left"), nick));
            ui.textEdit.setTextColor(color);
        }

        void showInformation() {
            if (ui.listWidget.count() == 1) {
                QMessageBox.information(this, tr("Chat"), tr("Launch several instances of this " + "program on your local network and " + "start chatting!"));
            }
        }

        class Client extends QObject {

            private PeerManager peerManager;
            private Server server = new Server(this);
            private Vector<Connection> peers = new Vector<Connection>();
            private Vector<Connection> inConnection = new Vector<Connection>();

            // from, message
            Signal2<String, String> newMessage__from__message = new Signal2<String, String>();
            // nick
            Signal1<String> newParticipant__nick = new Signal1<String>();
            // nick
            Signal1<String> participantLeft__nick = new Signal1<String>();

            Client() {
                peerManager = new PeerManager(server);
                peerManager.startBroadcasting();

                peerManager.newConnection__hostAddress_port.connect(this, "newConnection(QHostAddress, int)");
                server.newConnection__descriptor.connect(this, "newConnection(int)");
            }

            public synchronized void sendMessage(final String message) {
                if (message.equals(""))
                    return;

                for (Connection connection : peers) {
                    connection.sendMessage(message);
                }
            }

            public String nickName() {
                return peerManager.userName() + "@" + QHostInfo.localHostName() + ":" + (int) server.serverPort();
            }

            protected void newConnection(QHostAddress address, int port) {
                if (inConnection.isEmpty()) {
                    for (Connection conn : peers) {
                        if (conn.equals(address, port))
                            return;
                    }
                    Connection connection = new Connection(this, port, server.serverPort());
                    connection.connectToHost(address, port, new QIODevice.OpenMode(QIODevice.OpenModeFlag.ReadWrite));
                    newConnection(connection);
                }
            }

            protected void newConnection(int descriptor) {
                Connection connection = new Connection(this, -1, server.serverPort());
                connection.setSocketDescriptor(descriptor);
                newConnection(connection);
            }

            private void newConnection(Connection connection) {
                inConnection.add(connection);
                connection.setGreetingMessage(peerManager.userName().toString());

                connection.error.connect(this, "connectionError(QAbstractSocket$SocketError)");
                connection.disconnected.connect(this, "disconnected()");
                connection.readyForUse.connect(this, "readyForUse()");
            }

            protected synchronized void readyForUse() {
                Connection connection = (Connection) signalSender();

                connection.newMessage.connect(this.newMessage__from__message);

                peers.add(connection);

                String nick = connection.name();

                if (!nick.equals(""))
                    newParticipant__nick.emit(nick);

                inConnection.removeElement(connection);
            }

            protected void disconnected() {
                removeConnection((Connection) signalSender());
            }

            protected void connectionError(QAbstractSocket.SocketError socketError) {
                removeConnection((Connection) signalSender());
            }

            protected void removeConnection(Connection con) {
                if (peers.removeElement(con)) {
                    String nick = con.name();
                    if (!nick.equals(""))
                        participantLeft__nick.emit(nick);
                }
            }
        }

        static class Connection extends QTcpSocket {

            static final int MaxBufferSize = 1024000;
            static final int TransferTimeout = 30 * 1000;
            static final int PongTimeout = 60 * 1000;
            static final int PingInterval = 5 * 1000;
            static final char SeparatorToken = ' ';

            private enum ConnectionState {
                WaitingForGreeting, ReadingGreeting, ReadyForUse
            };

            private enum DataType {
                PlainText, Ping, Pong, Greeting, Undefined
            };

            Signal0 readyForUse = new Signal0();
            Signal2<String, String> newMessage = new Signal2<String, String>();

            private QTextCodec codec = QTextCodec.codecForName(new QByteArray("UTF-8"));

            private String greetingMessage;
            private String username;
            private QTimer pingTimer = new QTimer();
            private QTime pongTime = new QTime();
            private QByteArray buffer = new QByteArray();
            private ConnectionState state;
            private DataType currentDataType;
            private int numBytesForCurrentDataType;
            private int transferTimerId;
            private boolean isGreetingMessageSent;
            private int otherServerPort;
            private int thisServerPort;

            Connection(QObject parent, int otherServerPort, int thisServerPort) {
                super(parent);
                this.otherServerPort = otherServerPort;
                this.thisServerPort = thisServerPort;
                greetingMessage = tr("undefined");
                username = tr("unknown");
                state = ConnectionState.WaitingForGreeting;
                currentDataType = DataType.Undefined;
                numBytesForCurrentDataType = -1;
                transferTimerId = 0;
                isGreetingMessageSent = false;
                pingTimer.setInterval(PingInterval);

                readyRead.connect(this, "processReadyRead()");
                disconnected.connect(pingTimer, "stop()");
                pingTimer.timeout.connect(this, "sendPing()");
                connected.connect(this, "sendGreetingMessage()");
            }

            @Override
            public boolean equals(Object other) {
                if (other instanceof Connection) {
                    Connection con = (Connection) other;
                    return (con.peerAddress().equals(peerAddress()) && con.otherServerPort == otherServerPort);
                }
                return false;
            }

            public boolean equals(QHostAddress address, int port) {
                return this.peerAddress().equals(address) && this.otherServerPort == port;
            }

            String name() {
                return username;
            }

            void setGreetingMessage(final String message) {
                greetingMessage = message;
            }

            boolean sendMessage(final String message) {
                if (message.equals(""))
                    return false;

                QByteArray msg = codec.fromUnicode(message);
                QByteArray data = new QByteArray("MESSAGE " + msg.length() + " ");
                data.append(msg);
                return write(data) == data.size();
            }

            public void timerEvent(QTimerEvent timerEvent) {
                if (timerEvent.timerId() == transferTimerId) {
                    abort();
                    killTimer(transferTimerId);
                    transferTimerId = 0;
                }
            }

            void processReadyRead() {
                if (state == ConnectionState.WaitingForGreeting) {
                    if (!readProtocolHeader())
                        return;
                    if (currentDataType != DataType.Greeting) {
                        abort();
                        return;
                    }
                    state = ConnectionState.ReadingGreeting;
                }

                if (state == ConnectionState.ReadingGreeting) {
                    if (!hasEnoughData())
                        return;

                    buffer = read(numBytesForCurrentDataType);
                    if (buffer.size() != numBytesForCurrentDataType) {
                        abort();
                        return;
                    }

                    username = buffer.toString();

                    String[] list = username.split("[:@]");
                    if(list.length!=3) {
                        abort();
                        return;
                    }
                    otherServerPort = Integer.parseInt(list[2]);

                    currentDataType = DataType.Undefined;
                    numBytesForCurrentDataType = 0;
                    buffer.clear();

                    if (!isValid()) {
                        abort();
                        return;
                    }

                    if (!isGreetingMessageSent)
                        sendGreetingMessage();

                    pingTimer.start();
                    pongTime.start();
                    state = ConnectionState.ReadyForUse;

                    readyForUse.emit();
                }

                do {
                    if (currentDataType == DataType.Undefined) {
                        if (!readProtocolHeader())
                            return;
                    }
                    if (!hasEnoughData())
                        return;
                    processData();
                } while (bytesAvailable() > 0);
            }

            void sendPing() {
                if (pongTime.elapsed() > PongTimeout) {
                    abort();
                    return;
                }

                write(new QByteArray("PING 1 p"));
            }

            private void sendGreetingMessage() {
                QByteArray greeting = new QByteArray(greetingMessage + "@" + this.localAddress() + ":" + thisServerPort);
                QByteArray data = new QByteArray("GREETING " + greeting.length() + " " + greeting);

                if (write(data) == data.size())
                    isGreetingMessageSent = true;
            }

            private int readDataIntoBuffer(int maxSize) {
                if (maxSize > MaxBufferSize)
                    return 0;

                int numBytesBeforeRead = buffer.size();
                if (numBytesBeforeRead == MaxBufferSize) {
                    abort();
                    return 0;
                }

                while (bytesAvailable() > 0 && buffer.size() < maxSize) {
                    buffer.append(read(1));
                    if (buffer.endsWith("" + SeparatorToken))
                        break;
                }
                return buffer.size() - numBytesBeforeRead;
            }

            private int dataLengthForCurrentDataType() {
                if (bytesAvailable() <= 0 || readDataIntoBuffer(MaxBufferSize) <= 0 || !buffer.endsWith("" + SeparatorToken))
                    return 0;

                buffer.chop(1);
                int number = buffer.toInt();
                buffer.clear();
                return number;
            }

            private boolean readProtocolHeader() {
                if (transferTimerId != 0) {
                    killTimer(transferTimerId);
                    transferTimerId = 0;
                }

                if (readDataIntoBuffer(MaxBufferSize) <= 0) {
                    transferTimerId = startTimer(TransferTimeout);
                    return false;
                }

                if (buffer.equals("PING ")) {
                    currentDataType = DataType.Ping;
                } else if (buffer.equals("PONG ")) {
                    currentDataType = DataType.Pong;
                } else if (buffer.equals("MESSAGE ")) {
                    currentDataType = DataType.PlainText;
                } else if (buffer.equals("GREETING ")) {
                    currentDataType = DataType.Greeting;
                } else {
                    currentDataType = DataType.Undefined;
                    abort();
                    return false;
                }

                buffer.clear();
                numBytesForCurrentDataType = dataLengthForCurrentDataType();
                return true;
            }

            private boolean hasEnoughData() {
                if (transferTimerId != 0) {
                    killTimer(transferTimerId);
                    transferTimerId = 0;
                }

                if (numBytesForCurrentDataType <= 0)
                    numBytesForCurrentDataType = dataLengthForCurrentDataType();

                if (bytesAvailable() < numBytesForCurrentDataType || numBytesForCurrentDataType <= 0) {
                    transferTimerId = startTimer(TransferTimeout);
                    return false;
                }

                return true;
            }

            private void processData() {
                buffer = read(numBytesForCurrentDataType);
                if (buffer.size() != numBytesForCurrentDataType) {
                    abort();
                    return;
                }

                switch (currentDataType) {
                case PlainText:
                    newMessage.emit(username, codec.toUnicode(buffer));
                    break;
                case Ping:
                    write(new QByteArray("PONG 1 p"));
                    break;
                case Pong:
                    pongTime.restart();
                    break;
                default:
                    break;
                }

                currentDataType = DataType.Undefined;
                numBytesForCurrentDataType = 0;
                buffer.clear();
            }
        }

        class PeerManager extends QObject {
            static final int BroadcastInterval = 2000;
            static final int broadcastPort = 45000;

            private Server server;
            private Vector<QHostAddress> broadcastAddresses = new Vector<QHostAddress>();
            private Vector<QHostAddress> ipAddresses = new Vector<QHostAddress>();
            private QUdpSocket broadcastSocket = new QUdpSocket();
            private QTimer broadcastTimer = new QTimer();
            private String username = "";

            Signal2<QHostAddress, Integer> newConnection__hostAddress_port = new Signal2<QHostAddress, Integer>();

            public PeerManager(Server server) {
                this.server = server;

                List<String> envVariables = new Vector<String>();

                envVariables.add("USERNAME.*");
                envVariables.add("USER.*");
                envVariables.add("USERDOMAIN.*");
                envVariables.add("HOSTNAME.*");
                envVariables.add("DOMAINNAME.*");

                List<String> environment = QProcess.systemEnvironment();

                for (String string : envVariables) {
                    int index = 0;
                    for (String entry : environment) {
                        if (new QRegExp(string).exactMatch(entry))
                            break;
                        index++;
                    }
                    if (index < environment.size()) {
                        String[] stringList = environment.get(index).split("=");
                        if (stringList.length == 2) {
                            username = stringList[1];
                            break;
                        }
                    }
                }

                if (username.equals(""))
                    username = "unknown";

                updateAddresses();

                broadcastSocket.bind(new QHostAddress(QHostAddress.SpecialAddress.Any), broadcastPort, new QUdpSocket.BindMode(QUdpSocket.BindFlag.ShareAddress,
                        QUdpSocket.BindFlag.ReuseAddressHint));

                broadcastSocket.readyRead.connect(this, "readBroadcastDatagram()");

                broadcastTimer.setInterval(BroadcastInterval);

                broadcastTimer.timeout.connect(this, "sendBroadcastDatagram()");

            }

            String userName() {
                return username;
            }

            void startBroadcasting() {
                broadcastTimer.start();
            }

            private boolean isLocalHostAddress(final QHostAddress address) {
                for (QHostAddress localAddress : ipAddresses) {
                    if (address.equals(localAddress)) {
                        return true;
                    }
                }
                return false;
            }

            void sendBroadcastDatagram() {
                QByteArray datagram = new QByteArray(username + "@" + server.serverPort());
                boolean validBroadcastAddresses = true;
                for (QHostAddress address : broadcastAddresses) {
                    if (broadcastSocket.writeDatagram(datagram, address, broadcastPort) == -1)
                        validBroadcastAddresses = false;
                }

                if (!validBroadcastAddresses)
                    updateAddresses();
            }

            void readBroadcastDatagram() {
                while (broadcastSocket.hasPendingDatagrams()) {
                    byte datagram[] = new byte[(int) broadcastSocket.pendingDatagramSize()];
                    QUdpSocket.HostInfo info = new QUdpSocket.HostInfo();
                    if (broadcastSocket.readDatagram(datagram, info) == -1)
                        continue;

                    QByteArray baDatagram = new QByteArray(datagram);
                    List<QByteArray> list = baDatagram.split((byte) '@');
                    if (list.size() != 2)
                        continue;

                    int senderServerPort = Integer.parseInt(list.get(1).toString());

                    if (isLocalHostAddress(info.address) && (senderServerPort == server.serverPort())) {
                        continue;
                    }
                    newConnection__hostAddress_port.emit(info.address, senderServerPort);
                }
            }

            private void updateAddresses() {
                updateAddresses(false);
                if (broadcastAddresses.isEmpty())
                    updateAddresses(true);
            }

            private void updateAddresses(boolean enableLoopBack) {
                broadcastAddresses.clear();
                ipAddresses.clear();
                for (QNetworkInterface networkInterface : QNetworkInterface.allInterfaces()) {
                    if (enableLoopBack || !networkInterface.flags().isSet(QNetworkInterface.InterfaceFlag.IsLoopBack)) {
                        for (QNetworkAddressEntry entry : networkInterface.addressEntries()) {

                            QHostAddress broadcastAddress = entry.broadcast();
                            if (!broadcastAddress.equals(QHostAddress.SpecialAddress.Null)) {
                                broadcastAddresses.add(broadcastAddress);
                            }
                            ipAddresses.add(entry.ip());
                        }
                    }
                }
            }
        }

        class Server extends QTcpServer {
            Signal1<Integer> newConnection__descriptor = new Signal1<Integer>();

            public Server(QObject parent) {
                super(parent);
                listen(new QHostAddress(QHostAddress.SpecialAddress.Any), 0);
            }

            public void incomingConnection(int socketDescriptor) {
                newConnection__descriptor.emit(socketDescriptor);
            }
        }
    }