activated()->triggered()
[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   ui.chatWidget->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
135
136   opsExpanded = voicedExpanded = usersExpanded = true;
137
138   // Define standard colors
139   stdCol = "black";
140   inactiveCol = "grey";
141   noticeCol = "darkblue";
142   serverCol = "darkblue";
143   errorCol = "red";
144   joinCol = "green";
145   quitCol = "firebrick";
146   partCol = "firebrick";
147   kickCol = "firebrick";
148   nickCol = "magenta";
149
150   int i = contents.count() - 100;
151   if(i < 0) i = 0;
152   for(int j = 0; j < i; j++) contents.removeAt(0);
153   show();
154   renderContents();
155   updateTitle();
156   //show();
157 }
158
159 void BufferWidget::updateTitle() {
160   QString title = QString("%1 in %2 [%3]: %4").arg(ui.ownNick->currentText()).arg(bufferName).arg(networkName).arg(ui.topicEdit->text());
161   setWindowTitle(title);
162 }
163
164 void BufferWidget::enterPressed() {
165   QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts);
166   foreach(QString msg, lines) {
167     if(msg.isEmpty()) continue;
168     emit userInput(msg);
169   }
170   ui.inputEdit->clear();
171 }
172
173 void BufferWidget::setActive(bool act) {
174   if(act != active) {
175     active = act;
176     renderContents();
177     //scrollToEnd();
178   }
179 }
180
181 void BufferWidget::renderContents() {
182   QString html;
183   //html = "<style type=\"text/css\">"
184   //    ".test { background-color:#339933 }"
185   //    "</style>";
186   for(int i = 0; i < contents.count(); i++) {
187     html += htmlFromMsg(contents[i]);
188   }
189   //ui.chatWidget->clear();
190   hide();
191   ui.chatWidget->setHtml(html); show();
192   //ui.chatWidget->insertHtml("<div />");  // <-- bug that would not reset the scrollbar sizes...
193   scrollToEnd();
194 }
195
196 void BufferWidget::scrollToEnd() {
197   QScrollBar *sb = ui.chatWidget->verticalScrollBar();
198   sb->setValue(sb->maximum());
199   //qDebug() << bufferName << "scrolled" << sb->value() << sb->maximum();
200 }
201
202 QString BufferWidget::htmlFromMsg(Message msg) {
203   QString s, n;
204   QString c = stdCol;
205   QString user = userFromMask(msg.sender);
206   QString host = hostFromMask(msg.sender);
207   QString nick = nickFromMask(msg.sender);
208   switch(msg.type) {
209     case Message::Plain:
210       c = stdCol; n = QString("&lt;%1&gt;").arg(nick); s = msg.text;
211       break;
212     case Message::Server:
213       c = serverCol; s = msg.text;
214       break;
215     case Message::Error:
216       c = errorCol; s = msg.text;
217       break;
218     case Message::Join:
219       c = joinCol;
220       s = QString(tr("--> %1 (%2@%3) has joined %4")).arg(nick).arg(user).arg(host).arg(bufferName);
221       break;
222     case Message::Part:
223       c = partCol;
224       s = QString(tr("<-- %1 (%2@%3) has left %4")).arg(nick).arg(user).arg(host).arg(bufferName);
225       if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text);
226       break;
227     case Message::Kick:
228       { c = kickCol;
229         QString victim = msg.text.section(" ", 0, 0);
230         if(victim == ui.ownNick->currentText()) victim = tr("you");
231         QString kickmsg = msg.text.section(" ", 1);
232         s = QString(tr("--> %1 has kicked %2 from %3")).arg(nick).arg(victim).arg(bufferName);
233         if(!kickmsg.isEmpty()) s = QString("%1 (%2)").arg(s).arg(kickmsg);
234       }
235       break;
236     case Message::Quit:
237       c = quitCol;
238       s = QString(tr("<-- %1 (%2@%3) has quit")).arg(nick).arg(user).arg(host);
239       if(!msg.text.isEmpty()) s = QString("%1 (%2)").arg(s).arg(msg.text);
240       break;
241     case Message::Nick:
242       c = nickCol;
243       if(nick == msg.text) s = QString(tr("<-> You are now known as %1")).arg(msg.text);
244       else s = QString(tr("<-> %1 is now known as %2")).arg(nick).arg(msg.text);
245       break;
246     case Message::Mode:
247       c = serverCol;
248       if(nick.isEmpty()) s = tr("*** User mode: %1").arg(msg.text);
249       else s = tr("*** Mode %1 by %2").arg(msg.text).arg(nick);
250       break;
251     default:
252       c = stdCol; n = QString("[%1]").arg(msg.sender); s = msg.text;
253       break;
254   }
255   if(!active) c = inactiveCol;
256   s.replace('&', "&amp;"); s.replace('<', "&lt;"); s.replace('>', "&gt;");
257   QString html = QString("<table cellspacing=0 cellpadding=0><tr>"
258       "<td width=50><div style=\"color:%2;\">[%1]</div></td>")
259       .arg(msg.timeStamp.toLocalTime().toString("hh:mm:ss")).arg("darkblue");
260   if(!n.isEmpty())
261     html += QString("<td width=100><div align=right style=\"white-space:pre;margin-left:6px;color:%2;\">%1</div></td>")
262         .arg(n).arg("royalblue");
263   html += QString("<td><div style=\"white-space:pre-wrap;margin-left:6px;color:%2;\">%1</div></td>""</tr></table>").arg(s).arg(c);
264   return html;
265 }
266
267 void BufferWidget::displayMsg(Message msg) {
268   contents.append(msg);
269   ui.chatWidget->append(htmlFromMsg(msg));
270 }
271
272 void BufferWidget::setOwnNick(QString nick) {
273   ui.ownNick->clear();
274   ui.ownNick->addItem(nick);
275   updateTitle();
276 }
277
278 void BufferWidget::setTopic(QString topic) {
279   ui.topicEdit->setText(topic);
280   updateTitle();
281 }
282
283 void BufferWidget::updateNickList(VarMap nicks) {
284   ui.nickTree->clear();
285   if(nicks.count() != 1) ui.nickTree->setHeaderLabel(tr("%1 Users").arg(nicks.count()));
286   else ui.nickTree->setHeaderLabel(tr("1 User"));
287   QTreeWidgetItem *ops = new QTreeWidgetItem();
288   QTreeWidgetItem *voiced = new QTreeWidgetItem();
289   QTreeWidgetItem *users = new QTreeWidgetItem();
290   // To sort case-insensitive, we have to put all nicks in a map which is sorted by (lowercase) key...
291   QMap<QString, QString> sorted;
292   foreach(QString n, nicks.keys()) { sorted[n.toLower()] = n; }
293   foreach(QString n, sorted.keys()) {
294     QString nick = sorted[n];
295     QString mode = nicks[nick].toMap()["Channels"].toMap()[bufferName].toMap()["Mode"].toString();
296     if(mode.contains('o')) { new QTreeWidgetItem(ops, QStringList(QString("@%1").arg(nick))); }
297     else if(mode.contains('v')) { new QTreeWidgetItem(voiced, QStringList(QString("+%1").arg(nick))); }
298     else new QTreeWidgetItem(users, QStringList(nick));
299   }
300   if(ops->childCount()) {
301     ops->setText(0, tr("%1 Operators").arg(ops->childCount()));
302     ui.nickTree->addTopLevelItem(ops);
303     ops->setExpanded(opsExpanded);
304   } else delete ops;
305   if(voiced->childCount()) {
306     voiced->setText(0, tr("%1 Voiced").arg(voiced->childCount()));
307     ui.nickTree->addTopLevelItem(voiced);
308     voiced->setExpanded(voicedExpanded);
309   } else delete voiced;
310   if(users->childCount()) {
311     users->setText(0, tr("%1 Users").arg(users->childCount()));
312     ui.nickTree->addTopLevelItem(users);
313     users->setExpanded(usersExpanded);
314   } else delete users;
315 }
316
317 void BufferWidget::itemExpansionChanged(QTreeWidgetItem *item) {
318   if(item->child(0)->text(0).startsWith('@')) opsExpanded = item->isExpanded();
319   else if(item->child(0)->text(0).startsWith('+')) voicedExpanded = item->isExpanded();
320   else usersExpanded = item->isExpanded();
321 }
322