Small fix in scrollbar behavior. Still not cool, but at least not as annoying anymore.
[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) {
59     widget->scrollToEnd();
60     return qobject_cast<QWidget*>(widget);
61   }
62   widget = new BufferWidget(networkName, bufferName, isActive(), ownNick, contents, parent); 
63   widget->setTopic(topic);
64   widget->updateNickList(nicks);
65   //widget->renderContents();
66   widget->scrollToEnd();
67   connect(widget, SIGNAL(userInput(QString)), this, SLOT(userInput(QString)));
68   return qobject_cast<QWidget*>(widget);
69 }
70
71 void Buffer::hideWidget() {
72   delete widget;
73   widget = 0;
74 }
75
76 QWidget * Buffer::getWidget() {
77   return qobject_cast<QWidget*>(widget);
78 }
79
80 void Buffer::setTopic(QString t) {
81   topic = t;
82   if(widget) widget->setTopic(t);
83 }
84
85 void Buffer::addNick(QString nick, VarMap props) {
86   if(nick == ownNick) setActive(true);
87   nicks[nick] = props;
88   if(widget) widget->updateNickList(nicks);
89 }
90
91 void Buffer::updateNick(QString nick, VarMap props) {
92   nicks[nick] = props;
93   if(widget) widget->updateNickList(nicks);
94 }
95
96 void Buffer::renameNick(QString oldnick, QString newnick) {
97   QVariant v = nicks.take(oldnick);
98   nicks[newnick] = v;
99   if(widget) widget->updateNickList(nicks);
100 }
101
102 void Buffer::removeNick(QString nick) {
103   if(nick == ownNick) setActive(false);
104   nicks.remove(nick);
105   if(widget) widget->updateNickList(nicks);
106 }
107
108 void Buffer::setOwnNick(QString nick) {
109   ownNick = nick;
110   if(widget) widget->setOwnNick(nick);
111 }
112
113 /****************************************************************************************/
114
115 BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString own, QList<Message> cont, QWidget *parent) : QWidget(parent) {
116   ui.setupUi(this);
117   networkName = netname;
118   bufferName = bufname;
119   active = act;
120   contents = cont;
121   ui.ownNick->clear();
122   ui.ownNick->addItem(own);
123   if(bufname.isEmpty()) {
124     // Server Buffer
125     ui.nickTree->hide();
126     ui.topicEdit->hide();
127     ui.chanSettingsButton->hide();
128   }
129   connect(ui.nickTree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*)));
130   connect(ui.nickTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*)));
131   connect(ui.inputEdit, SIGNAL(returnPressed()), this, SLOT(enterPressed()));
132
133   ui.chatWidget->setFocusProxy(ui.inputEdit);
134
135   opsExpanded = voicedExpanded = usersExpanded = true;
136
137   // Define standard colors
138   stdCol = "black";
139   inactiveCol = "grey";
140   noticeCol = "darkblue";
141   serverCol = "darkblue";
142   errorCol = "red";
143   joinCol = "green";
144   quitCol = "firebrick";
145   partCol = "firebrick";
146   kickCol = "firebrick";
147   nickCol = "magenta";
148
149   int i = contents.count() - 100;
150   if(i < 0) i = 0;
151   for(int j = 0; j < i; j++) contents.removeAt(0);
152   renderContents();
153   updateTitle();
154   show();
155 }
156
157 void BufferWidget::updateTitle() {
158   QString title = QString("%1 in %2 [%3]: %4").arg(ui.ownNick->currentText()).arg(bufferName).arg(networkName).arg(ui.topicEdit->text());
159   setWindowTitle(title);
160 }
161
162 void BufferWidget::enterPressed() {
163   QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts);
164   foreach(QString msg, lines) {
165     if(msg.isEmpty()) continue;
166     emit userInput(msg);
167   }
168   ui.inputEdit->clear();
169 }
170
171 void BufferWidget::setActive(bool act) {
172   if(act != active) {
173     active = act;
174     renderContents();
175   }
176 }
177
178 void BufferWidget::renderContents() {
179   QString html;
180   for(int i = 0; i < contents.count(); i++) {
181     html += htmlFromMsg(contents[i]);
182   }
183   ui.chatWidget->clear();
184   ui.chatWidget->setHtml(html);
185   scrollToEnd();
186 }
187
188 void BufferWidget::scrollToEnd() {
189   QScrollBar *sb = ui.chatWidget->verticalScrollBar();
190   sb->setValue(sb->maximum());
191   //qDebug() << bufferName << "scrolled" << sb->value() << sb->maximum();
192 }
193
194 QString BufferWidget::htmlFromMsg(Message msg) {
195   QString s, n;
196   QString c = stdCol;
197   QString user = userFromMask(msg.sender);
198   QString host = hostFromMask(msg.sender);
199   QString nick = nickFromMask(msg.sender);
200   switch(msg.type) {
201     case Message::Plain:
202       c = stdCol; n = QString("&lt;%1&gt;").arg(nick); s = msg.text;
203       break;
204     case Message::Server:
205       c = serverCol; s = msg.text;
206       break;
207     case Message::Error:
208       c = errorCol; s = msg.text;
209       break;
210     case Message::Join:
211       c = joinCol;
212       s = QString(tr("--> %1 (%2@%3) has joined %4")).arg(nick).arg(user).arg(host).arg(bufferName);
213       break;
214     case Message::Part:
215       c = partCol;
216       s = QString(tr("<-- %1 (%2@%3) has left %4")).arg(nick).arg(user).arg(host).arg(bufferName);
217       if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text);
218       break;
219     case Message::Kick:
220       { c = kickCol;
221         QString victim = msg.text.section(" ", 0, 0);
222         if(victim == ui.ownNick->currentText()) victim = tr("you");
223         QString kickmsg = msg.text.section(" ", 1);
224         s = QString(tr("--> %1 has kicked %2 from %3")).arg(nick).arg(victim).arg(bufferName);
225         if(!kickmsg.isEmpty()) s = QString("%1 (%2)").arg(s).arg(kickmsg);
226       }
227       break;
228     case Message::Quit:
229       c = quitCol;
230       s = QString(tr("<-- %1 (%2@%3) has quit")).arg(nick).arg(user).arg(host);
231       if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text);
232       break;
233     case Message::Nick:
234       c = nickCol;
235       if(nick == msg.text) s = QString(tr("<-> You are now known as %1")).arg(msg.text);
236       else s = QString(tr("<-> %1 is now known as %2")).arg(nick).arg(msg.text);
237       break;
238     case Message::Mode:
239       c = serverCol;
240       if(nick.isEmpty()) s = tr("*** User mode: %1").arg(msg.text);
241       else s = tr("*** Mode %1 by %2").arg(msg.text).arg(nick);
242       break;
243     default:
244       c = stdCol; n = QString("[%1]").arg(msg.sender); s = msg.text;
245       break;
246   }
247   if(!active) c = inactiveCol;
248   s.replace('&', "&amp;"); s.replace('<', "&lt;"); s.replace('>', "&gt;");
249   QString html = QString("<table cellspacing=0 cellpadding=0><tr>"
250       "<td width=50><div style=\"color:%2;\">[%1]</div></td>")
251       .arg(msg.timeStamp.toLocalTime().toString("hh:mm:ss")).arg("darkblue");
252   if(!n.isEmpty())
253     html += QString("<td width=100><div align=right style=\"white-space:pre;margin-left:6px;color:%2;\">%1</div></td>")
254         .arg(n).arg("royalblue");
255   html += QString("<td><div style=\"white-space:pre-wrap;margin-left:6px;color:%2;\">%1</div></td>""</tr></table>").arg(s).arg(c);
256   return html;
257 }
258
259 void BufferWidget::displayMsg(Message msg) {
260   contents.append(msg);
261   ui.chatWidget->append(htmlFromMsg(msg));
262 }
263
264 void BufferWidget::setOwnNick(QString nick) {
265   ui.ownNick->clear();
266   ui.ownNick->addItem(nick);
267   updateTitle();
268 }
269
270 void BufferWidget::setTopic(QString topic) {
271   ui.topicEdit->setText(topic);
272   updateTitle();
273 }
274
275 void BufferWidget::updateNickList(VarMap nicks) {
276   ui.nickTree->clear();
277   if(nicks.count() != 1) ui.nickTree->setHeaderLabel(tr("%1 Users").arg(nicks.count()));
278   else ui.nickTree->setHeaderLabel(tr("1 User"));
279   QTreeWidgetItem *ops = new QTreeWidgetItem();
280   QTreeWidgetItem *voiced = new QTreeWidgetItem();
281   QTreeWidgetItem *users = new QTreeWidgetItem();
282   // To sort case-insensitive, we have to put all nicks in a map which is sorted by (lowercase) key...
283   QMap<QString, QString> sorted;
284   foreach(QString n, nicks.keys()) { sorted[n.toLower()] = n; }
285   foreach(QString n, sorted.keys()) {
286     QString nick = sorted[n];
287     QString mode = nicks[nick].toMap()["Channels"].toMap()[bufferName].toMap()["Mode"].toString();
288     if(mode.contains('o')) { new QTreeWidgetItem(ops, QStringList(QString("@%1").arg(nick))); }
289     else if(mode.contains('v')) { new QTreeWidgetItem(voiced, QStringList(QString("+%1").arg(nick))); }
290     else new QTreeWidgetItem(users, QStringList(nick));
291   }
292   if(ops->childCount()) {
293     ops->setText(0, tr("%1 Operators").arg(ops->childCount()));
294     ui.nickTree->addTopLevelItem(ops);
295     ops->setExpanded(opsExpanded);
296   } else delete ops;
297   if(voiced->childCount()) {
298     voiced->setText(0, tr("%1 Voiced").arg(voiced->childCount()));
299     ui.nickTree->addTopLevelItem(voiced);
300     voiced->setExpanded(voicedExpanded);
301   } else delete voiced;
302   if(users->childCount()) {
303     users->setText(0, tr("%1 Users").arg(users->childCount()));
304     ui.nickTree->addTopLevelItem(users);
305     users->setExpanded(usersExpanded);
306   } else delete users;
307 }
308
309 void BufferWidget::itemExpansionChanged(QTreeWidgetItem *item) {
310   if(item->child(0)->text(0).startsWith('@')) opsExpanded = item->isExpanded();
311   else if(item->child(0)->text(0).startsWith('+')) voicedExpanded = item->isExpanded();
312   else usersExpanded = item->isExpanded();
313 }
314