Browse Source

Added /msg and MODE Parser and stuff

Niklas Boelter 12 years ago
parent
commit
258969f103
9 changed files with 495 additions and 100 deletions
  1. 0 1
      Connection.cpp
  2. 1 1
      Connection.h
  3. 373 45
      Controller.cpp
  4. 12 0
      Controller.h
  5. 5 3
      HistoryLineEdit.cpp
  6. 5 5
      MainWindow.ui
  7. 55 8
      UserLists.cpp
  8. 8 2
      UserLists.h
  9. 36 35
      main.cpp

+ 0 - 1
Connection.cpp

@@ -120,7 +120,6 @@ void Connection::splitLines()
 }
 bool Connection::hasLines()
 {
-	splitLines();
 	return !lines.isEmpty();
 }
 QString Connection::nextLine()

+ 1 - 1
Connection.h

@@ -44,11 +44,11 @@ public:
 	Connection(int cid);
 	int getID();
 	void open(ConnectionSettings settings);
+	void splitLines();
 	bool hasLines();
 	void sendLine(QString line);
 	QString nextLine();
 private:
-	void splitLines();
 	int id;
 	bool ssl;
 	bool ignoreSSLErrors;

+ 373 - 45
Controller.cpp

@@ -22,8 +22,14 @@ Controller::Controller(MainWindow *mw)
 	this->mw = mw;
 	userlists = new UserLists();
 	connect(this->mw, SIGNAL( sendLine(QString) ), this, SLOT( readInput(QString) ));
+	// handle user input
 	connect(this->mw, SIGNAL( tabChanged(QString) ), this, SLOT( tabChanged(QString) ));
+	// user selected a different tab in the tab list (Tree View) - change the rest of the window
 	
+	
+	// These are signals that are emitted when the corresponding Command is received from the IRC Server,
+	// Parameters are int cid (The Connection ID), QStringList params (The Parameters of the Command)
+	// and Prefix prefix, which contains info about the sender of the command (nick/user/hostname or servername respectively)
 	connect(this, SIGNAL(gotJoin(int, QStringList, Prefix)), this, SLOT(handleJoin(int, QStringList, Prefix)));
 	connect(this, SIGNAL(gotPart(int, QStringList, Prefix)), this, SLOT(handlePart(int, QStringList, Prefix)));
 	connect(this, SIGNAL(gotQuit(int, QStringList, Prefix)), this, SLOT(handleQuit(int, QStringList, Prefix)));
@@ -32,6 +38,7 @@ Controller::Controller(MainWindow *mw)
 	connect(this, SIGNAL(gotPing(int, QStringList, Prefix)), this, SLOT(handlePing(int, QStringList, Prefix)));
 	connect(this, SIGNAL(gotNick(int, QStringList, Prefix)), this, SLOT(handleNick(int, QStringList, Prefix)));
 	connect(this, SIGNAL(gotNotice(int, QStringList, Prefix)), this, SLOT(handleNotice(int, QStringList, Prefix)));
+	connect(this, SIGNAL(gotMode(int, QStringList, Prefix)), this, SLOT(handleMode(int, QStringList, Prefix)));
 	connect(this, SIGNAL(got001(int, QStringList, Prefix)), this, SLOT(handle001(int, QStringList, Prefix)));
 	connect(this, SIGNAL(got002(int, QStringList, Prefix)), this, SLOT(handle002(int, QStringList, Prefix)));
 	connect(this, SIGNAL(got003(int, QStringList, Prefix)), this, SLOT(handle003(int, QStringList, Prefix)));
@@ -41,9 +48,11 @@ Controller::Controller(MainWindow *mw)
 	connect(this, SIGNAL(got353(int, QStringList, Prefix)), this, SLOT(handle353(int, QStringList, Prefix)));
 	connect(this, SIGNAL(got366(int, QStringList, Prefix)), this, SLOT(handle366(int, QStringList, Prefix)));
 	
+	// Allow the user to provide his own CA Certificates for SSL Verification
 	QSslSocket::addDefaultCaCertificates(QString("%1/.nirc/ca.pem").arg(QDir::homePath()),QSsl::Pem);
 	QSslSocket::addDefaultCaCertificates(QString("%1/.nirc/ca.der").arg(QDir::homePath()),QSsl::Der);
 	
+	// Parse the Connection Information in Connectionsettings objects (which is required by the Connection class)
 	int size = settings.beginReadArray("connections");
 	for(int i = 0; i < size; i++)
 	{
@@ -59,11 +68,13 @@ Controller::Controller(MainWindow *mw)
 	}
 	settings.endArray();
 	
+	// Each class has its own namespace in the settings, we use controller for our settings
 	settings.beginGroup("controller");
 	
 }
 void Controller::replaceHTML(QString & line)
 {
+	// Warning: This uses references! Make sure you use it AFTER you sent the data to the server
 	line.replace("&","&amp;");
 	line.replace("<","&lt;");
 	line.replace(">","&gt;");
@@ -72,36 +83,57 @@ void Controller::replaceHTML(QString & line)
 }
 void Controller::setup()
 {
+	// Initialize the Connections
 	openConnections();
+	// Display main window
 	mw->show();
 }
 void Controller::openConnections()
 {
-	for(int i = 0; i < connectionsettings.size(); i++)
+	int cid;
+	for(cid = 0; cid < connectionsettings.size(); cid++)
 	{
-		QHash<QChar, QChar> prefix;
-		prefix.insert('+', 'v');
-		prefix.insert('@', 'o');
-		support_prefix.insert(i, prefix);
+		// cid is the connection id, which is always needed do distinguish between networks
+		
+		// This takes care of the prefixes for nicks in the NAMES reply numeric, so we can find out 
+		// who has which priviledge in the channel
+		// (Usually overriden by RPL_ISUPPORT, but we use this as default as defined in RFC 1459)
+		parseISupportPrefix(cid, "(ov)@+");
+		
+		// RFC 2811 channel modes - hopefully overriden by RPL_ISUPPORT
+		parseISupportChanmodes(cid, "beI,k,l,aimnpstr"); 
 		
+		// This is the list of joined channels for this network - it's empty for now
 		channels.append(QSet<QString>());
 		
-		registered.insert(i, false);
+		// We are not yet registered with the Network, wait until RPL_WELCOME
+		registered.insert(cid, false);
 		
-		titlebars.insert(QString("%1").arg(i), "Connecting ...");
+		// Show a nice and dandy status info in the titlebar for the connection
+		titlebars.insert(QString("%1").arg(cid), "Connecting ...");
+		updateTitleBar();
+		
+		Connection *connection = new Connection(cid);
 		
-		Connection *connection = new Connection(i);
+		// We received data form the server!
 		connect(connection, SIGNAL( dataReady(int) ), this, SLOT( dataReady(int) ));
+		
 		connect(connection, SIGNAL( connected(int) ), this, SLOT( connected(int) ));
 		connect(connection, SIGNAL( disconnected(int) ), this, SLOT( disconnected(int) ));
+		
+		// We got a status message from the socket (errors etc) - just show it to the user 
+		// (We only care about connect/disconnect and data events)
 		connect(connection, SIGNAL( message(int, QString) ), this, SLOT( socketmessage(int, QString) ));
-		connections.insert(i, connection);
-		connection->open(connectionsettings.at(i));
+		connections.insert(cid, connection);
+		
+		// open the connection with specified settings
+		connection->open(connectionsettings.at(cid));
 	}
 }
 void Controller::dataReady(int cid)
 {
 	Connection *c = connections.at(cid);
+	c->splitLines(); // Split lines (we do this one time now so that hasLines() and nextLine() do not have to do it each time)
 	while (c->hasLines())
 	{
 		parse(cid, c->nextLine());
@@ -109,13 +141,17 @@ void Controller::dataReady(int cid)
 }
 void Controller::sendLine(int cid, QString line) // Sends stuff to the server
 {
+	// First send it to the server
 	connections.at(cid)->sendLine(line);
+	// Then sanitize the HTML 
 	replaceHTML(line);
 	Q_ASSERT(tabs.contains(QString("%1").arg(cid)));
+	// And append it to the connection tab
 	addLine(getTab(cid), QString("<span style=\"color:green;white-space:pre;\">%1</grey>").arg(line));
 }
 void Controller::addLine(QTextCursor *cursor, QString line) // Sends stuff to the tab
 {
+	// cursor is a textcursor pointing to the textdocument which holds the tabs content
 	bool atBottom = mw->contentAtBottom();
 	if(!cursor->atStart()) cursor->insertBlock(); // No newline in front of first line
 	cursor->insertHtml(line);
@@ -127,22 +163,27 @@ void Controller::addLine(QTextCursor *cursor, QString line) // Sends stuff to th
 void Controller::socketmessage(int cid, QString message)
 {
 	QTextCursor *cursor = getTab(cid);
-	if(cursor == 0)
+	if(cursor == 0) // We don't have a connection tab yet - create it 
 	{
 		QString name = connectionsettings.at(cid).name;
 		QString identifier = QString("%1").arg(cid);
+		// Create the textdocument and textcursor
 		addTab(identifier, settings.value("maxConsoleHistory").toInt());
+		// Tell the MainWindow that we have a new tab which it can request via tabChanged()
 		mw->addTab(name, identifier, "", true);
 		Q_ASSERT(tabs.contains(QString("%1").arg(cid)));
 		cursor = getTab(cid);
 	}
+	// Finally add the message to the tabs content
 	addLine(cursor, message);
 }
 void Controller::connected(int cid)
 {
+	
 	titlebars.insert(QString("%1").arg(cid), 
 					 QString("Connected to %1:%2").arg(connectionsettings.at(cid).host).arg(connectionsettings.at(cid).port));
 	updateTitleBar();
+	
 	socketmessage(cid, QString("Connected to %1").arg(connectionsettings.at(cid).name));
 	// TODO
 	mynicks.insert(cid, settings.value("nickname").toString());
@@ -263,30 +304,84 @@ bool Controller::isServer(QString prefix)
 	}
 	return false;
 }
-void Controller::readInput(QString line)
+void Controller::parseInputCommand(QString line)
 {
+	QStringList params;
 	QString tmp;
+	QString command;
+	QString target;
+	QTextCursor *cursor;
+	QString identifier;
 	if(line.size() == 0) return;
-	if(line.toLower().startsWith("/me ") && tab.name != "")
+	params = line.split(' ');
+	command = params.takeFirst().toLower();
+	if(command.size() == 0) return;
+	if(command == "me" && tab.name != "")
 	{
 		line = line.mid(3);
-		tmp = QString("PRIVMSG %1 :%2ACTION%3%4").arg(tab.name).arg(char(1)).arg(line).arg(char(1));
+		tmp = QString("PRIVMSG %1 :%2ACTION %3%4").arg(tab.name).arg(char(1)).arg(line).arg(char(1));
 		sendLine(tab.cid, tmp);
 		replaceHTML(line);
-		addLine(getTab(tab.identifier), QString("<strong style=\"color:blue\">*%1</strong><span style=\"white-space:pre\">%2</span>").arg(mynicks.value(tab.cid)).arg(line));
+		Q_ASSERT(tabs.contains(tab.identifier));
+		addLine(getTab(tab.identifier), QString("%1 <strong style=\"color:blue\">*&nbsp;%2</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(mynicks.value(tab.cid)).arg(line));
 		return;
 	}
+	else if(command == "msg")
+	{
+		if(params.count() < 2)
+		{
+			addLine(getTab(tab.identifier), "<span style=\"color:red\">Not enough parameters for /msg</span>");
+			return;
+		}
+		else
+		{
+			target = params.takeFirst();
+			tmp = QString("PRIVMSG %2 :%3").arg(target).arg(params.join(" "));
+			sendLine(tab.cid, tmp);
+			cursor = getTab(tab.cid, target);
+			if(cursor != 0)
+			{
+				QString("%1 <strong style=\"color:blue\">&lt;%2&gt;</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(mynicks.value(tab.cid)).arg(params.join(" "));
+			}
+		}
+	}
+	else if(command == "query")
+	{
+		if(params.count() < 1)
+		{
+			addLine(getTab(tab.identifier), "<span style=\"color:red\">Not enough parameters for /query</span>");
+		}
+		else
+		{
+			identifier = QString("%1/%2").arg(tab.cid).arg(params.at(0));
+			addTab(identifier, settings.value("maxPrivateHistory").toInt());
+			mw->addTab(params.at(0), identifier, QString("%1").arg(tab.cid), true);
+			cursor = getTab(tab.cid, params.at(0));
+		}
+
+	}
+	else
+	{
+		sendLine(tab.cid, line);
+	}
+
+}
+void Controller::readInput(QString line)
+{
+	QString tmp;
+	if(line.size() == 0) return;
 	if(line.startsWith("/"))
 	{
 			line = line.mid(1);
-			if(!line.startsWith("//"))
+			if(!line.startsWith("/"))
 			{
-				sendLine(tab.cid, line);
+				parseInputCommand(line);
 				return;
 			}
 	}
 	if(tab.name == "")
 	{
+		Q_ASSERT(tabs.contains(QString("%1").arg(tab.cid)));
 		sendLine(tab.cid, line);
 	}
 	else
@@ -294,7 +389,7 @@ void Controller::readInput(QString line)
 		sendLine(tab.cid, QString("PRIVMSG %1 :%2").arg(tab.name).arg(line));
 		replaceHTML(line);
 		Q_ASSERT(tabs.contains(tab.identifier));
-		addLine(getTab(tab.identifier), QString("<strong style=\"color:blue\">&lt;%1&gt;</strong>&nbsp;<span style=\"white-space:pre\">%2</span>").arg(mynicks.value(tab.cid)).arg(line));
+		addLine(getTab(tab.identifier), QString("%1 <strong style=\"color:blue\">&lt;%2&gt;</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(mynicks.value(tab.cid)).arg(line));
 	}
 }
 void Controller::addTab(QString key, quint16 maximumBlockCount)
@@ -340,20 +435,12 @@ void Controller::delTab(QString key)
 QTextCursor * Controller::getTab(int cid)
 {
 	QString identifier = QString("%1").arg(cid);
-	if(!tabs.contains(identifier))
-	{
-		return 0;
-	}
-	return tabs.value(identifier);
+	return getTab(identifier);
 }
 QTextCursor * Controller::getTab(int cid, QString name)
 {
 	QString identifier = QString("%1/%2").arg(cid).arg(name);
-	if (!tabs.contains(identifier))
-	{
-		return 0;
-	}
-	return tabs.value(identifier);
+	return getTab(identifier);
 }
 QTextCursor * Controller::getTab(QString identifier)
 {
@@ -407,6 +494,180 @@ void Controller::updateTitleBar()
 		mw->setTitleBarContent("");
 	}
 
+}
+void Controller::parseISupportChanmodes(int cid, QString chanmodes)
+{
+	QStringList sections;
+	sections = chanmodes.split(',');
+	if(sections.count() != 4)
+	{
+		qWarning() << "Received malformed CHANMODES argument in RPL_ISUPPORT - ignoring";
+		return;
+	}
+	support_chanmodes.insert(cid, sections);
+}
+void Controller::parseISupportPrefix(int cid, QString prefix)
+{
+	QStringList sections;
+	QChar mode;
+	QChar pref;
+	QHash<QChar, QChar> prefhash;
+	QHash<QChar, QChar> prefhash_reverse;
+	QList<QChar> preforder;
+	int i;
+	int size;
+	if(!prefix.startsWith('('))
+	{
+		qWarning() << "Received malformed PREFIX in RPL_ISUPPORT: PREFIX=" << prefix;
+		return;
+	}
+	prefix = prefix.mid(1);
+	sections = prefix.split(')');
+	if(sections.count() != 2 || sections.at(0).size() != sections.at(1).size())
+	{
+		qWarning() << "Received malformed PREFIX in RPL_ISUPPORT: PREFIX=" << prefix;	
+		return;
+	}
+	size = sections.at(0).size();
+	for(i = 0; i < size; i++)
+	{
+		mode = sections.at(0).at(i);
+		pref = sections.at(1).at(i);
+		prefhash.insert(pref, mode);
+		prefhash_reverse.insert(mode, pref);
+		preforder.append(pref);
+	}
+	
+	support_prefix.insert(cid, prefhash);
+	support_prefix_reverse.insert(cid, prefhash_reverse);
+	support_prefix_order.insert(cid, preforder);
+}
+void Controller::parseChannelModes(int cid, QStringList params)
+{
+	bool set = true; // When this is true we set the mode, else we unset the mode
+	QStringList chanmodes = support_chanmodes.at(cid);
+	// chanmodes[0] -> Mode that adds or removes a nick or address to a list. Always has a parameter. 
+	// chanmodes[1] -> Mode that changes a setting and always has a parameter. 
+	// chanmodes[2] -> Mode that changes a setting and only has a parameter when set.
+	// chanmodes[3] -> Mode that changes a setting and never has a parameter.
+	// http://www.irc.org/tech_docs/005.html
+	QString name = params.takeAt(0);
+	QString modes = params.takeAt(0);
+	QChar cur;
+	QChar oldprefix;
+	QChar newprefix;
+	QString arg;
+	int j = 0;
+	int k;
+	for(int i = 0; i < modes.length(); i++)
+	{
+		cur = modes.at(i);
+		if(cur == '+')
+		{
+			set = true;
+			continue;
+		}
+		else if(cur == '-')
+		{
+			set = false;
+			continue;
+		}
+		else if(support_prefix_reverse.at(cid).contains(cur))
+		{
+			arg = params.at(j);
+			qDebug() << "MODE:";
+			qDebug() << arg;
+			qDebug() << cur;
+			if(set)
+			{
+				qDebug() << "userlists->addPriv(" << cid << "," << name << "," << arg << "," << cur << ");";
+				userlists->addPriv(cid, name, arg, cur);
+				oldprefix = userlists->getPrefix(cid, name, arg);
+				newprefix = support_prefix_reverse.at(cid).value(cur);
+				for(k = 0; k < support_prefix_order.at(cid).count(); k++)
+				{
+					if(support_prefix_order.at(cid).at(k) == oldprefix)
+					{
+						break; // The current prefix is more important than the user mode that is unset
+					}
+					else if(support_prefix_order.at(cid).at(k) == newprefix)
+					{
+						// The new prefix is more important then the old, replace it
+						userlists->setPrefix(cid, name, arg, newprefix);
+					}
+				}
+			}
+			else
+			{
+				qDebug() << "userlists->removePriv(" << cid << "," << name << "," << arg << "," << cur << ");";
+				userlists->removePriv(cid, name, arg, cur);
+				oldprefix = userlists->getPrefix(cid, name, arg);
+				qDebug() << "oldprefix:" << oldprefix;
+				qDebug() << "oldprefix' priv:" << support_prefix.at(cid).value(oldprefix);
+				qDebug() << "current priv:" << cur;
+				if(support_prefix.at(cid).value(oldprefix) == cur) // The current prefix shows the user mode
+				{
+					qDebug() << "Remove the prefix";
+					userlists->setPrefix(cid, name, arg, 0); // Unset the prefix first
+					// Now we have no idea what the actual mode on that user could be, but at least we can guess
+					for(k = 0; k < support_prefix_order.at(cid).count(); k++)
+					{
+						// support_prefix_order.at(cid).at(k) << this is the PREFIX we are currently at, we are going from
+						// MOST important to LEAST important, as they override each other in that order
+						// support_prefix.at(cid).value() << this gives us the channel mode that corresponds to that prefix,
+						// eg, @ -> o, + -> v
+						// userlists->hasPriv(cid, name, arg, ) -> this checks if the user has the right to have that prefix,
+						// eg, if he possesses that channel mode in that channel
+						qDebug() << "Current Prefix:" << support_prefix_order.at(cid).at(k);
+						qDebug() << "Current priv:" << support_prefix.at(cid).value(support_prefix_order.at(cid).at(k));
+						qDebug() << "Current privs:" << userlists->getPrivs(cid, name, arg);
+						if(userlists->hasPriv(cid, name, arg, support_prefix.at(cid).value(support_prefix_order.at(cid).at(k))))
+						{
+							qDebug() << "User has the right priv, set the Prefix to: " << support_prefix_order.at(cid).at(k);
+							// The user has the privilege with this prefix needs to be, as it is the most important, we set the prefix
+							userlists->setPrefix(cid, name, arg, support_prefix_order.at(cid).at(k));
+							// and break the loop
+							break;
+						}
+					}
+					// Now we did everything possible to guess the right prefix, it's time to ask the server
+					//sendLine(cid, QString("NAMES %1").arg(name));
+				}
+			}
+
+			j++;
+		}
+		else if(chanmodes.at(0).contains(cur))
+		{
+			j++;
+		}
+		else if(chanmodes.at(1).contains(cur))
+		{
+			j++;
+		}
+		else if(chanmodes.at(2).contains(cur))
+		{
+			if(set)
+			{
+				j++;
+			}
+			else
+			{
+				
+			}
+		}
+		else if(chanmodes.at(3).contains(cur))
+		{
+		}
+	}
+	if(cid == tab.cid && tab.name == name)
+	{
+		updateChannelList();
+
+	}
+}
+void Controller::parseUserModes(int, QStringList)
+{
 }
 void Controller::handleJoin(int cid, QStringList params, Prefix prefix)
 {
@@ -506,7 +767,14 @@ void Controller::handlePrivmsg(int cid, QStringList params, Prefix prefix)
 	}
 	replaceHTML(line);
 	line = line.replace(QRegExp("((http|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&amp;:/~\\+#]*[\\w\\-\\@?^=%&amp;/~\\+#])?)"),"<a href=\"\\1\">\\1</a>");
-	line = QString("<strong>&lt;%1&gt;</strong>&nbsp;<span style=\"white-space:pre\">%2</span>").arg(prefix.nickname).arg(line);
+	if(line.startsWith(QString("%1ACTION").arg(char(1))) and line.endsWith(char(1)))
+	{
+		line = QString("%1 <strong>*&nbsp;%2</strong><span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(prefix.nickname).arg(line.mid(7, line.size()-8));
+	}
+	else
+	{
+		line = QString("%1 <strong>&lt;%2&gt;</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(prefix.nickname).arg(line);
+	}
 	if(params.at(0) != mynicks.value(cid))
 	{
 		cursor = getTab(cid, params.at(0));
@@ -630,7 +898,7 @@ void Controller::handleNotice(int cid, QStringList params, Prefix prefix)
 	replaceHTML(line);
 	QTextCursor *cursor;
 	line = line.replace(QRegExp("((http|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&amp;:/~\\+#]*[\\w\\-\\@?^=%&amp;/~\\+#])?)"),"<a href=\"\\1\">\\1</a>");
-	line = QString("<strong>-%1-</strong>&nbsp;<span style=\"white-space:pre\">%2</span>").arg(prefix.nickname).arg(line);
+	line = QString("%1 <strong>-%2-</strong>&nbsp;<span style=\"white-space:pre\">%3</span>").arg(QDateTime::currentDateTime().toString("[hh:mm]")).arg(prefix.nickname).arg(line);
 	if(target == mynicks.value(cid)) // Private Notice to me
 	{
 		cursor = getTab(cid, prefix.nickname); // Get Query Tab
@@ -648,6 +916,47 @@ void Controller::handleNotice(int cid, QStringList params, Prefix prefix)
 		addLine(cursor, line);
 	}
 }
+void Controller::handlePing(int cid, QStringList params, Prefix)
+{
+	if (params.isEmpty())
+	{
+		sendLine(cid, "PONG");
+	}
+	else
+	{
+		sendLine(cid, QString("PONG :%1").arg(params.at(0)));
+	}
+}
+void Controller::handleMode(int cid, QStringList params, Prefix prefix)
+{
+	QTextCursor *cursor;
+	QStringList mode;
+	mode = params;
+	mode.removeFirst();
+	if(params.size() < 2)
+	{
+		qWarning() << "Not enough Parameters for MODE";
+		return;
+	}
+	if(params.at(0) == prefix.nickname and 
+	   params.at(0) == mynicks.value(cid)) // We are the target - user mode change
+	{
+		parseUserModes(cid, params);
+	}
+	else if(channels.at(cid).contains(params.at(0)))
+	{
+		cursor = getTab(cid, params.at(0));
+		if(cursor != 0)
+		{
+			addLine(cursor, QString("! <strong>%1</strong> sets mode %2.").arg(prefix.nickname).arg(mode.join(" ")));
+		}
+		parseChannelModes(cid, params);
+	}
+	else
+	{
+		qWarning() << "Could not parse MODE: " << prefix.nickname << params.join(" ");
+	}
+}
 void Controller::handle001(int cid, QStringList params, Prefix prefix)
 {
 	if(!prefix.isServer)
@@ -662,17 +971,38 @@ void Controller::handle001(int cid, QStringList params, Prefix prefix)
 		mynicks.insert(cid, nick);
 	}
 }
-void Controller::handle002(int cid, QStringList params, Prefix prefix)
+void Controller::handle002(int, QStringList, Prefix)
 {
+	
 }
-void Controller::handle003(int cid, QStringList params, Prefix prefix)
+void Controller::handle003(int, QStringList, Prefix)
 {
+	
 }
-void Controller::handle004(int cid, QStringList params, Prefix prefix)
+void Controller::handle004(int, QStringList, Prefix)
 {
+	
 }
 void Controller::handle005(int cid, QStringList params, Prefix prefix)
 {
+	QString param;
+	if(!prefix.isServer)
+	{
+		qWarning() << "Received RPL_ISUPPORT from USER";
+		return;
+	}
+	for(int i = 0; i < params.size(); i++)
+	{
+		param = params.at(i);
+		if(param.startsWith("CHANMODES="))
+		{
+			parseISupportChanmodes(cid, param.mid(10));
+		}
+		else if(param.startsWith("PREFIX="))
+		{
+			parseISupportPrefix(cid, param.mid(7));
+		}
+	}
 }
 void Controller::handle332(int cid, QStringList params, Prefix prefix)
 {
@@ -681,6 +1011,11 @@ void Controller::handle332(int cid, QStringList params, Prefix prefix)
 		qWarning() << "Not enough parameters for RPL_TOPIC";
 		return;
 	}
+	if(!prefix.isServer)
+	{
+		qWarning() << "Received RPL_TOPIC from USER";
+		return;
+	}
 	QString identifier = QString("%1/%2").arg(cid).arg(params.at(1));
 	titlebars.insert(identifier, params.at(2));
 	if(tab.identifier == identifier)
@@ -715,7 +1050,10 @@ void Controller::handle353(int cid, QStringList params, Prefix prefix)
 	identifier = QString("%1/%2").arg(cid).arg(name);
 	if(!namreply.contains(identifier))
 	{
-		userlists->clearChannel(cid, name);
+		//userlists->clearChannel(cid, name);
+		// if the server and client go out of sync, this will not safe us any longer - probably
+		// implement a mechanism that removed nicks that were not part of the last NAMREPLYs :/
+		// userlists->startUpdating(cid, name);
 		namreply.insert(identifier);
 	}
 	names = params.at(3).split(" ");
@@ -745,6 +1083,7 @@ void Controller::handle366(int cid, QStringList params, Prefix prefix)
 	identifier = QString("%1/%2").arg(cid).arg(params.at(1));
 	if(namreply.contains(identifier)) 
 	{
+		// userlists->stopUpdating(cid, name);
 		namreply.remove(identifier);
 		if(tab.identifier == identifier)
 		{
@@ -755,15 +1094,4 @@ void Controller::handle366(int cid, QStringList params, Prefix prefix)
 	{
 		qWarning() << "Server sent RPL_ENDOFNAMES before RPL_NAMREPLY";
 	}
-}
-void Controller::handlePing(int cid, QStringList params, Prefix prefix)
-{
-	if (params.isEmpty())
-	{
-		sendLine(cid, "PONG");
-	}
-	else
-	{
-		sendLine(cid, QString("PONG :%1").arg(params.at(0)));
-	}
 }

+ 12 - 0
Controller.h

@@ -26,6 +26,7 @@
 #include <QTextDocument>
 #include <QMetaObject>
 #include <QSound>
+#include <QDateTime>
 #include "MainWindow.h"
 #include "Connection.h"
 #include "Numerics.h"
@@ -65,6 +66,7 @@ signals:
 	void gotPing(int cid, QStringList params, Prefix prefix);
 	void gotNick(int cid, QStringList params, Prefix prefix);
 	void gotNotice(int cid, QStringList params, Prefix prefix);
+	void gotMode(int cid, QStringList params, Prefix prefix);
 	void got001(int cid, QStringList params, Prefix prefix);
 	void got002(int cid, QStringList params, Prefix prefix);
 	void got003(int cid, QStringList params, Prefix prefix);
@@ -92,8 +94,17 @@ private:
 	QList<bool> registered;
 	QSet<QString> namreply; // This tells us that a NAMREPLY is currently active for that channel, removed when we receive an ENDOFNAMES 
 	bool isServer(QString prefix);
+	void parseISupportPrefix(int cid, QString prefix);
+	void parseISupportChanmodes(int cid, QString prefix);
+	void parseChannelModes(int cid, QStringList params);
+	void parseUserModes(int cid, QStringList params);
 	QList<QHash<QChar, QChar> > support_prefix; // ISUPPORT PREFIX argument
+	QList<QHash<QChar, QChar> > support_prefix_reverse;
+	QList<QList<QChar> >		support_prefix_order; 
+	QList<QStringList> support_chanmodes; // ISUPPORT CHANMODES argument
 	QList<QSet<QString> > channels;
+	// User Input Parsing stuff
+	void parseInputCommand(QString line);
 	// Tab Stuff
 	TabRef tab; // Stores the currently viewed tab - probably move to MainWindow :?
 	void addTab(QString key, quint16 maximumBlockCount);
@@ -130,6 +141,7 @@ private slots:
 	void handlePing(int cid, QStringList params, Prefix prefix);
 	void handleNick(int cid, QStringList params, Prefix prefix);
 	void handleNotice(int cid, QStringList params, Prefix prefix);
+	void handleMode(int cid, QStringList params, Prefix prefix);
 	void handle001(int cid, QStringList params, Prefix prefix);
 	void handle002(int cid, QStringList params, Prefix prefix);
 	void handle003(int cid, QStringList params, Prefix prefix);

+ 5 - 3
HistoryLineEdit.cpp

@@ -24,14 +24,16 @@ void HistoryLineEdit::keyPressEvent(QKeyEvent *event)
 		case Qt::Key_Enter:
 		case Qt::Key_Return:
 			emit returnPressed();
-			event->accept();
+			event->accept();	// This fixes an issue on Mac OS X which makes a weird noise 
+								//because LineEdit does not allow return as a valid input
+								// Should be fixed in the next Release of QT ( > 4.6 )
 			break;
 		case Qt::Key_Up:
-			emit upPressed();
+			emit upPressed(); // Scroll back in the input history
 			event->accept();
 			break;
 		case Qt::Key_Down:
-			emit downPressed();
+			emit downPressed(); // Scroll forward in the input history
 			event->accept();
 			break;
 		case Qt::Key_Tab:

+ 5 - 5
MainWindow.ui

@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>641</width>
-    <height>480</height>
+    <width>934</width>
+    <height>465</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -31,7 +31,7 @@
       </property>
       <widget class="QTreeWidget" name="serverobjects">
        <property name="sizePolicy">
-        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+        <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
@@ -76,7 +76,7 @@
       </widget>
       <widget class="QTreeView" name="channelobjects">
        <property name="sizePolicy">
-        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+        <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
@@ -106,7 +106,7 @@
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>641</width>
+     <width>934</width>
      <height>22</height>
     </rect>
    </property>

+ 55 - 8
UserLists.cpp

@@ -44,20 +44,33 @@ void UserList::addUser(QString channel, QString nickname, QHash<QChar, QChar> ch
 	int id;
 	int pos;
 	QChar first;
+	QChar pref;
+	QChar priv;
 	id = getId(channel);
 	first = nickname.at(0);
-	pos = nicks.at(id).size();
 	if(chanprefix.contains(first))
 	{
-		nicks[id].insert(pos, nickname.mid(1));
-		prefix[id].insert(pos, first);
-		privs[id].insert(pos, chanprefix.value(first));
+		nickname = nickname.mid(1);
+		pref = first;
+		priv = chanprefix.value(first);
+	}
+	else
+	{
+		pref = 0;
+		priv = 0;
+	}
+	if(nicks.at(id).contains(nickname))
+	{
+		pos = nicks.at(id).indexOf(nickname);
+		prefix[id].replace(pos, pref);
+		addPriv(channel, nickname, priv);
 	}
 	else
 	{
+		pos = nicks.at(id).size();
 		nicks[id].insert(pos, nickname);
-		prefix[id].insert(pos, 0);
-		privs[id].insert(pos, "");
+		prefix[id].insert(pos, pref);
+		privs[id].insert(pos, priv);
 	}
 }
 void UserList::changeNick(QString channel, QString oldnick, QString newnick)
@@ -149,7 +162,7 @@ void UserList::setPrivs(QString channel, QString nickname, QString newprivs)
 	pos = nicks[id].indexOf(nickname);
 	if(pos != -1)
 	{
-		privs[id][pos] = newprivs;
+		privs[id].replace(pos, newprivs);
 	}
 }
 bool UserList::hasPriv(QString channel, QString nickname, QChar priv)
@@ -168,11 +181,37 @@ void UserList::addPriv(QString channel, QString nickname, QChar priv)
 void UserList::removePriv(QString channel, QString nickname, QChar priv)
 {
 	QString privs = getPrivs(channel, nickname);
-	if(!privs.contains(priv))
+	if(privs.contains(priv))
 	{
 		setPrivs(channel, nickname, privs.replace(priv, ""));
 	}
 }
+QChar UserList::getPrefix(QString channel, QString nickname)
+{
+	int id;
+	int pos;
+	id = getId(channel);
+	pos = nicks.at(id).indexOf(nickname);
+	if(pos == -1)
+	{
+		return 0;
+	}
+	else
+	{
+		return prefix.at(id).at(pos);
+	}
+}
+void UserList::setPrefix(QString channel, QString nickname, QChar pref)
+{
+	int id;
+	int pos;
+	id = getId(channel);
+	pos = nicks.at(id).indexOf(nickname);
+	if(pos != -1)
+	{
+		prefix[id].replace(pos, pref);
+	}
+}
 int UserList::getId(QString channel)
 {
 	if(channels.contains(channel))
@@ -248,6 +287,14 @@ void UserLists::removePriv(int cid, QString channel, QString nickname, QChar pri
 {
 	getList(cid)->removePriv(channel, nickname, priv);
 }
+QChar UserLists::getPrefix(int cid, QString channel, QString nickname)
+{
+	return getList(cid)->getPrefix(channel, nickname);
+}
+void UserLists::setPrefix(int cid, QString channel, QString nickname, QChar prefix)
+{
+	getList(cid)->setPrefix(channel, nickname, prefix);
+}
 UserList * UserLists::getList(int cid)
 {
 	UserList *list;

+ 8 - 2
UserLists.h

@@ -15,8 +15,8 @@
  *
  */
 
-#import <QHash>
-#import <QStringList>
+#include <QHash>
+#include <QStringList>
 
 class UserList : public QObject
 {
@@ -39,6 +39,9 @@ public:
 	bool hasPriv(QString channel, QString nickname, QChar priv);
 	void addPriv(QString channel, QString nickname, QChar priv);
 	void removePriv(QString channel, QString nickname, QChar priv);
+	
+	QChar getPrefix(QString channel, QString nickname);
+	void setPrefix(QString channel, QString nickname, QChar pref);
 private:
 	QHash<QString, int> channels;
 	QList<QStringList> nicks;
@@ -68,6 +71,9 @@ public:
 	bool hasPriv(int cid, QString channel, QString nickname, QChar priv);
 	void addPriv(int cid, QString channel, QString nickname, QChar priv);
 	void removePriv(int cid, QString channel, QString nickname, QChar priv);
+	
+	QChar getPrefix(int cid, QString channel, QString nickname);
+	void setPrefix(int cid, QString channel, QString nickname, QChar pref);
 private:
 	QList<UserList *> lists;
 	UserList * getList(int cid);

+ 36 - 35
main.cpp

@@ -31,43 +31,44 @@ int main(int argc, char *argv[])
 	QSettings settings;
 	
 	/* TESTING */
-	
-	settings.clear();
-	QList<ConnectionSettings> connections;
-	ConnectionSettings xchannel;
-	xchannel.host = "irc.xchannel.org";
-	xchannel.port = 7000;
-	xchannel.name = "XChannel";
-	xchannel.isSSL = true;
-	xchannel.ignoreSSLErrors = true;
-	connections.append(xchannel);
-	ConnectionSettings freenode;
-	freenode.host = "irc.freenode.net";
-	freenode.port = 7000;
-	freenode.name = "Freenode";
-	freenode.isSSL = true;
-	freenode.ignoreSSLErrors = true;
-	connections.append(freenode);
-	settings.beginWriteArray("connections", connections.size());
-	for(int i = 0; i < connections.size(); i++)
+	if(!settings.contains("controller/nickname"))
 	{
-		settings.setArrayIndex(i);
-		settings.setValue("host", connections.at(i).host);
-		settings.setValue("port", connections.at(i).port);
-		settings.setValue("name", connections.at(i).name);
-		settings.setValue("isSSL", connections.at(i).isSSL);
-		settings.setValue("ignoreSSLErrors", connections.at(i).ignoreSSLErrors);
+		settings.clear();
+		QList<ConnectionSettings> connections;
+		ConnectionSettings xchannel;
+		xchannel.host = "irc.xchannel.org";
+		xchannel.port = 7000;
+		xchannel.name = "XChannel";
+		xchannel.isSSL = true;
+		xchannel.ignoreSSLErrors = true;
+		connections.append(xchannel);
+		ConnectionSettings freenode;
+		freenode.host = "irc.freenode.net";
+		freenode.port = 7000;
+		freenode.name = "Freenode";
+		freenode.isSSL = true;
+		freenode.ignoreSSLErrors = true;
+		connections.append(freenode);
+		settings.beginWriteArray("connections", connections.size());
+		for(int i = 0; i < connections.size(); i++)
+		{
+			settings.setArrayIndex(i);
+			settings.setValue("host", connections.at(i).host);
+			settings.setValue("port", connections.at(i).port);
+			settings.setValue("name", connections.at(i).name);
+			settings.setValue("isSSL", connections.at(i).isSSL);
+			settings.setValue("ignoreSSLErrors", connections.at(i).ignoreSSLErrors);
+		}
+		settings.endArray();
+		settings.setValue("controller/maxConsoleHistory", 5000);
+		settings.setValue("controller/maxChannelHistory", 300);
+		settings.setValue("controller/maxPrivateHistory", 1000);
+		settings.setValue("controller/nickname", "niki");
+		settings.setValue("controller/username", "niklas");
+		settings.setValue("controller/realname", "nIRC user");
+		settings.setValue("mainwindow/maxInputHistory", 10);
+		settings.setValue("mainwindow/inputHistoryAppendAlways", false);
 	}
-	settings.endArray();
-	settings.setValue("controller/maxConsoleHistory", 5000);
-	settings.setValue("controller/maxChannelHistory", 300);
-	settings.setValue("controller/maxPrivateHistory", 1000);
-	settings.setValue("controller/nickname", "niki");
-	settings.setValue("controller/username", "niklas");
-	settings.setValue("controller/realname", "nIRC user");
-	settings.setValue("mainwindow/maxInputHistory", 10);
-	settings.setValue("mainwindow/inputHistoryAppendAlways", false);
-	
 	
 	/* END */