After quite a while, we have another big SVN update now.
[quassel.git] / gui / buffer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005/06 by The Quassel Team                             *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "buffer.h"
22 #include "util.h"
23
24 Buffer::Buffer(QString netname, QString bufname) {
25   networkName = netname;
26   bufferName = bufname;
27
28   widget = 0;
29   active = false;
30 }
31
32 Buffer::~Buffer() {
33   delete widget;
34 }
35
36 void Buffer::setActive(bool a) {
37   if(a != active) {
38     active = a;
39     if(widget) widget->setActive(a);
40   }
41 }
42
43 void Buffer::displayMsg(Message msg) {
44   contents.append(msg);
45   if(widget) widget->displayMsg(msg);
46 }
47
48 void Buffer::userInput(QString msg) {
49   emit userInput(networkName, bufferName, msg);
50 }
51
52 void Buffer::scrollToEnd() {
53   if(!widget) return;
54   widget->scrollToEnd();
55 }
56
57 QWidget * Buffer::showWidget(QWidget *parent) {
58   if(widget) return qobject_cast<QWidget*>(widget);
59   widget = new BufferWidget(networkName, bufferName, isActive(), ownNick, contents, parent); 
60   widget->setTopic(topic);
61   widget->updateNickList(nicks);
62   //widget->renderContents();
63   widget->scrollToEnd();
64   connect(widget, SIGNAL(userInput(QString)), this, SLOT(userInput(QString)));
65   return qobject_cast<QWidget*>(widget);
66 }
67
68 void Buffer::hideWidget() {
69   delete widget;
70   widget = 0;
71 }
72
73 QWidget * Buffer::getWidget() {
74   return qobject_cast<QWidget*>(widget);
75 }
76
77 void Buffer::setTopic(QString t) {
78   topic = t;
79   if(widget) widget->setTopic(t);
80 }
81
82 void Buffer::addNick(QString nick, VarMap props) {
83   if(nick == ownNick) setActive(true);
84   nicks[nick] = props;
85   if(widget) widget->updateNickList(nicks);
86 }
87
88 void Buffer::updateNick(QString nick, VarMap props) {
89   nicks[nick] = props;
90   if(widget) widget->updateNickList(nicks);
91 }
92
93 void Buffer::renameNick(QString oldnick, QString newnick) {
94   QVariant v = nicks.take(oldnick);
95   nicks[newnick] = v;
96   if(widget) widget->updateNickList(nicks);
97 }
98
99 void Buffer::removeNick(QString nick) {
100   if(nick == ownNick) setActive(false);
101   nicks.remove(nick);
102   if(widget) widget->updateNickList(nicks);
103 }
104
105 void Buffer::setOwnNick(QString nick) {
106   ownNick = nick;
107   if(widget) widget->setOwnNick(nick);
108 }
109
110 /****************************************************************************************/
111
112 BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString own, QList<Message> cont, QWidget *parent) : QWidget(parent) {
113   ui.setupUi(this);
114   networkName = netname;
115   bufferName = bufname;
116   active = act;
117   contents = cont;
118   ui.ownNick->clear();
119   ui.ownNick->addItem(own);
120   if(bufname.isEmpty()) {
121     // Server Buffer
122     ui.nickTree->hide();
123     ui.topicEdit->hide();
124     ui.chanSettingsButton->hide();
125   }
126   connect(ui.nickTree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*)));
127   connect(ui.nickTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*)));
128   connect(ui.inputEdit, SIGNAL(returnPressed()), this, SLOT(enterPressed()));
129
130   ui.chatWidget->setFocusProxy(ui.inputEdit);
131
132   opsExpanded = voicedExpanded = usersExpanded = true;
133
134   // Define standard colors
135   stdCol = "black";
136   inactiveCol = "grey";
137   noticeCol = "darkblue";
138   serverCol = "darkblue";
139   errorCol = "red";
140   joinCol = "green";
141   quitCol = "firebrick";
142   partCol = "firebrick";
143   kickCol = "firebrick";
144   nickCol = "magenta";
145
146   int i = contents.count() - 100;
147   if(i < 0) i = 0;
148   for(int j = 0; j < i; j++) contents.removeAt(0);
149   renderContents();
150   updateTitle();
151   show();
152 }
153
154 void BufferWidget::updateTitle() {
155   QString title = QString("%1 in %2 [%3]: %4").arg(ui.ownNick->currentText()).arg(bufferName).arg(networkName).arg(ui.topicEdit->text());
156   setWindowTitle(title);
157 }
158
159 void BufferWidget::enterPressed() {
160   QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts);
161   foreach(QString msg, lines) {
162     if(msg.isEmpty()) continue;
163     emit userInput(msg);
164   }
165   ui.inputEdit->clear();
166 }
167
168 void BufferWidget::setActive(bool act) {
169   if(act != active) {
170     active = act;
171     renderContents();
172   }
173 }
174
175 void BufferWidget::renderContents() {
176   QString html;
177   for(int i = 0; i < contents.count(); i++) {
178     html += htmlFromMsg(contents[i]);
179   }
180   ui.chatWidget->clear();
181   ui.chatWidget->setHtml(html);
182   scrollToEnd();
183 }
184
185 void BufferWidget::scrollToEnd() {
186   QScrollBar *sb = ui.chatWidget->verticalScrollBar();
187   sb->setValue(sb->maximum());
188   qDebug() << bufferName << "scrolled" << sb->value() << sb->maximum();
189 }
190
191 QString BufferWidget::htmlFromMsg(Message msg) {
192   QString s, n;
193   QString c = stdCol;
194   QString user = userFromMask(msg.sender);
195   QString host = hostFromMask(msg.sender);
196   QString nick = nickFromMask(msg.sender);
197   switch(msg.type) {
198     case Message::Plain:
199       c = stdCol; n = QString("&lt;%1&gt;").arg(nick); s = msg.text;
200       break;
201     case Message::Server:
202       c = serverCol; s = msg.text;
203       break;
204     case Message::Error:
205       c = errorCol; s = msg.text;
206       break;
207     case Message::Join:
208       c = joinCol;
209       s = QString(tr("--> %1 (%2@%3) has joined %4")).arg(nick).arg(user).arg(host).arg(bufferName);
210       break;
211     case Message::Part:
212       c = partCol;
213       s = QString(tr("<-- %1 (%2@%3) has left %4")).arg(nick).arg(user).arg(host).arg(bufferName);
214       if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text);
215       break;
216     case Message::Kick:
217       { c = kickCol;
218         QString victim = msg.text.section(" ", 0, 0);
219         if(victim == ui.ownNick->currentText()) victim = tr("you");
220         QString kickmsg = msg.text.section(" ", 1);
221         s = QString(tr("--> %1 has kicked %2 from %3")).arg(nick).arg(victim).arg(bufferName);
222         if(!kickmsg.isEmpty()) s = QString("%1 (%2)").arg(s).arg(kickmsg);
223       }
224       break;
225     case Message::Quit:
226       c = quitCol;
227       s = QString(tr("<-- %1 (%2@%3) has quit")).arg(nick).arg(user).arg(host);
228       if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text);
229       break;
230     case Message::Nick:
231       c = nickCol;
232       if(nick == msg.text) s = QString(tr("<-> You are now known as %1")).arg(msg.text);
233       else s = QString(tr("<-> %1 is now known as %2")).arg(nick).arg(msg.text);
234       break;
235     case Message::Mode:
236       c = serverCol;
237       if(nick.isEmpty()) s = tr("*** User mode: %1").arg(msg.text);
238       else s = tr("*** Mode %1 by %2").arg(msg.text).arg(nick);
239       break;
240     default:
241       c = stdCol; n = QString("[%1]").arg(msg.sender); s = msg.text;
242       break;
243   }
244   if(!active) c = inactiveCol;
245   s.replace('&', "&amp;"); s.replace('<', "&lt;"); s.replace('>', "&gt;");
246   QString html = QString("<table cellspacing=0 cellpadding=0><tr>"
247       "<td width=50><div style=\"color:%2;\">[%1]</div></td>")
248       .arg(msg.timeStamp.toLocalTime().toString("hh:mm:ss")).arg("darkblue");
249   if(!n.isEmpty())
250     html += QString("<td width=100><div align=right style=\"white-space:pre;margin-left:6px;color:%2;\">%1</div></td>")
251         .arg(n).arg("royalblue");
252   html += QString("<td><div style=\"white-space:pre-wrap;margin-left:6px;color:%2;\">%1</div></td>""</tr></table>").arg(s).arg(c);
253   return html;
254 }
255
256 void BufferWidget::displayMsg(Message msg) {
257   contents.append(msg);
258   ui.chatWidget->append(htmlFromMsg(msg));
259 }
260
261 void BufferWidget::setOwnNick(QString nick) {
262   ui.ownNick->clear();
263   ui.ownNick->addItem(nick);
264   updateTitle();
265 }
266
267 void BufferWidget::setTopic(QString topic) {
268   ui.topicEdit->setText(topic);
269   updateTitle();
270 }
271
272 void BufferWidget::updateNickList(VarMap nicks) {
273   ui.nickTree->clear();
274   if(nicks.count() != 1) ui.nickTree->setHeaderLabel(tr("%1 Users").arg(nicks.count()));
275   else ui.nickTree->setHeaderLabel(tr("1 User"));
276   QTreeWidgetItem *ops = new QTreeWidgetItem();
277   QTreeWidgetItem *voiced = new QTreeWidgetItem();
278   QTreeWidgetItem *users = new QTreeWidgetItem();
279   // To sort case-insensitive, we have to put all nicks in a map which is sorted by (lowercase) key...
280   QMap<QString, QString> sorted;
281   foreach(QString n, nicks.keys()) { sorted[n.toLower()] = n; }
282   foreach(QString n, sorted.keys()) {
283     QString nick = sorted[n];
284     QString mode = nicks[nick].toMap()["Channels"].toMap()[bufferName].toMap()["Mode"].toString();
285     if(mode.contains('o')) { new QTreeWidgetItem(ops, QStringList(QString("@%1").arg(nick))); }
286     else if(mode.contains('v')) { new QTreeWidgetItem(voiced, QStringList(QString("+%1").arg(nick))); }
287     else new QTreeWidgetItem(users, QStringList(nick));
288   }
289   if(ops->childCount()) {
290     ops->setText(0, tr("%1 Operators").arg(ops->childCount()));
291     ui.nickTree->addTopLevelItem(ops);
292     ops->setExpanded(opsExpanded);
293   } else delete ops;
294   if(voiced->childCount()) {
295     voiced->setText(0, tr("%1 Voiced").arg(voiced->childCount()));
296     ui.nickTree->addTopLevelItem(voiced);
297     voiced->setExpanded(voicedExpanded);
298   } else delete voiced;
299   if(users->childCount()) {
300     users->setText(0, tr("%1 Users").arg(users->childCount()));
301     ui.nickTree->addTopLevelItem(users);
302     users->setExpanded(usersExpanded);
303   } else delete users;
304 }
305
306 void BufferWidget::itemExpansionChanged(QTreeWidgetItem *item) {
307   if(item->child(0)->text(0).startsWith('@')) opsExpanded = item->isExpanded();
308   else if(item->child(0)->text(0).startsWith('+')) voicedExpanded = item->isExpanded();
309   else usersExpanded = item->isExpanded();
310 }
311