fixed uninitialized qint32
[quassel.git] / src / qtui / inputwidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2010 by the Quassel Project                        *
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) version 3.                                           *
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 "inputwidget.h"
22
23 #include "action.h"
24 #include "actioncollection.h"
25 #include "bufferview.h"
26 #include "client.h"
27 #include "iconloader.h"
28 #include "ircuser.h"
29 #include "jumpkeyhandler.h"
30 #include "networkmodel.h"
31 #include "qtui.h"
32 #include "qtuisettings.h"
33 #include "tabcompleter.h"
34 #include <QPainter>
35
36 InputWidget::InputWidget(QWidget *parent)
37   : AbstractItemView(parent),
38     _networkId(0)
39 {
40   ui.setupUi(this);
41   connect(ui.ownNick, SIGNAL(activated(QString)), this, SLOT(changeNick(QString)));
42
43   layout()->setAlignment(ui.ownNick, Qt::AlignBottom);
44   layout()->setAlignment(ui.inputEdit, Qt::AlignBottom);
45   layout()->setAlignment(ui.showStyleButton, Qt::AlignBottom);
46   layout()->setAlignment(ui.styleFrame, Qt::AlignBottom);
47
48   ui.styleFrame->setVisible(false);
49
50   setFocusProxy(ui.inputEdit);
51   ui.ownNick->setFocusProxy(ui.inputEdit);
52
53   ui.ownNick->setSizeAdjustPolicy(QComboBox::AdjustToContents);
54   ui.ownNick->installEventFilter(new MouseWheelFilter(this));
55   ui.inputEdit->installEventFilter(new JumpKeyHandler(this));
56   ui.inputEdit->installEventFilter(this);
57
58   ui.inputEdit->setMinHeight(1);
59   ui.inputEdit->setMaxHeight(5);
60   ui.inputEdit->setMode(MultiLineEdit::MultiLine);
61   ui.inputEdit->setPasteProtectionEnabled(true);
62
63   ui.boldButton->setIcon(SmallIcon("format-text-bold"));
64   ui.italicButton->setIcon(SmallIcon("format-text-italic"));
65   ui.underlineButton->setIcon(SmallIcon("format-text-underline"));
66   ui.textcolorButton->setIcon(SmallIcon("format-text-color"));
67   ui.highlightcolorButton->setIcon(SmallIcon("format-fill-color"));
68
69   _colorMenu = new QMenu();
70   _colorFillMenu = new QMenu();
71
72   QStringList names;
73   names << tr("White") << tr("Black") << tr("Dark blue") << tr("Dark green") << tr("Red") << tr("Dark red") << tr("Dark magenta")  << tr("Orange")
74   << tr("Yellow") << tr("Green") << tr("Dark cyan") << tr("Cyan") << tr("Blue") << tr("Magenta") << tr("Dark gray") << tr("Light gray");
75
76   QPixmap pix(16, 16);
77   for (int i = 0; i < inputLine()->mircColorMap().count(); i++) {
78     pix.fill(inputLine()->mircColorMap().values()[i]);
79     _colorMenu->addAction(pix, names[i])->setData(inputLine()->mircColorMap().keys()[i]);
80     _colorFillMenu->addAction(pix, names[i])->setData(inputLine()->mircColorMap().keys()[i]);
81   }
82
83   pix.fill(Qt::transparent);
84   _colorMenu->addAction(pix, tr("Clear Color"))->setData("");
85   _colorFillMenu->addAction(pix, tr("Clear Color"))->setData("");
86
87   ui.textcolorButton->setMenu(_colorMenu);
88   connect(_colorMenu, SIGNAL(triggered(QAction*)), this, SLOT(colorChosen(QAction*)));
89   ui.highlightcolorButton->setMenu(_colorFillMenu);
90   connect(_colorFillMenu, SIGNAL(triggered(QAction*)), this, SLOT(colorHighlightChosen(QAction*)));
91
92   new TabCompleter(ui.inputEdit);
93
94   UiStyleSettings fs("Fonts");
95   fs.notify("UseCustomInputWidgetFont", this, SLOT(setUseCustomFont(QVariant)));
96   fs.notify("InputWidget", this, SLOT(setCustomFont(QVariant)));
97   if(fs.value("UseCustomInputWidgetFont", false).toBool())
98     setCustomFont(fs.value("InputWidget", QFont()));
99
100   UiSettings s("InputWidget");
101
102 #ifdef HAVE_KDE
103   s.notify("EnableSpellCheck", this, SLOT(setEnableSpellCheck(QVariant)));
104   setEnableSpellCheck(s.value("EnableSpellCheck", false));
105 #endif
106
107   s.notify("ShowNickSelector", this, SLOT(setShowNickSelector(QVariant)));
108   setShowNickSelector(s.value("ShowNickSelector", true));
109
110   s.notify("ShowStyleButtons", this, SLOT(setShowStyleButtons(QVariant)));
111   setShowStyleButtons(s.value("ShowStyleButtons", true));
112
113   s.notify("EnablePerChatHistory", this, SLOT(setEnablePerChatHistory(QVariant)));
114   setEnablePerChatHistory(s.value("EnablePerChatHistory", true));
115
116   s.notify("MaxNumLines", this, SLOT(setMaxLines(QVariant)));
117   setMaxLines(s.value("MaxNumLines", 5));
118
119   s.notify("EnableScrollBars", this, SLOT(setScrollBarsEnabled(QVariant)));
120   setScrollBarsEnabled(s.value("EnableScrollBars", true));
121
122   s.notify("EnableMultiLine", this, SLOT(setMultiLineEnabled(QVariant)));
123   setMultiLineEnabled(s.value("EnableMultiLine", true));
124
125   ActionCollection *coll = QtUi::actionCollection();
126
127   Action *activateInputline = coll->add<Action>("FocusInputLine");
128   connect(activateInputline, SIGNAL(triggered()), SLOT(setFocus()));
129   activateInputline->setText(tr("Focus Input Line"));
130   activateInputline->setShortcut(tr("Ctrl+L"));
131
132   connect(inputLine(), SIGNAL(currentCharFormatChanged(QTextCharFormat)), this, SLOT(currentCharFormatChanged(QTextCharFormat)));
133 }
134
135 InputWidget::~InputWidget() {
136 }
137
138 void InputWidget::setUseCustomFont(const QVariant &v) {
139   if(v.toBool()) {
140     UiStyleSettings fs("Fonts");
141     setCustomFont(fs.value("InputWidget"));
142   } else
143     setCustomFont(QFont());
144 }
145
146 void InputWidget::setCustomFont(const QVariant &v) {
147   QFont font = v.value<QFont>();
148   if(font.family().isEmpty())
149     font = QApplication::font();
150   ui.inputEdit->setCustomFont(font);
151 }
152
153 void InputWidget::setEnableSpellCheck(const QVariant &v) {
154   ui.inputEdit->setSpellCheckEnabled(v.toBool());
155 }
156
157 void InputWidget::setShowNickSelector(const QVariant &v) {
158   ui.ownNick->setVisible(v.toBool());
159 }
160
161 void InputWidget::setShowStyleButtons(const QVariant &v) {
162   ui.showStyleButton->setVisible(v.toBool());
163 }
164
165 void InputWidget::setEnablePerChatHistory(const QVariant &v) {
166   _perChatHistory = v.toBool();
167 }
168
169 void InputWidget::setMaxLines(const QVariant &v) {
170   ui.inputEdit->setMaxHeight(v.toInt());
171 }
172
173 void InputWidget::setScrollBarsEnabled(const QVariant &v) {
174   ui.inputEdit->setScrollBarsEnabled(v.toBool());
175 }
176
177 void InputWidget::setMultiLineEnabled(const QVariant &v) {
178   ui.inputEdit->setMode(v.toBool()? MultiLineEdit::MultiLine : MultiLineEdit::SingleLine);
179 }
180
181 bool InputWidget::eventFilter(QObject *watched, QEvent *event) {
182   if(event->type() != QEvent::KeyPress)
183     return false;
184
185   QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
186
187   // keys from BufferView should be sent to (and focus) the input line
188   BufferView *view = qobject_cast<BufferView *>(watched);
189   if(view) {
190     if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press
191       QChar c = keyEvent->text().at(0);
192       if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) {
193         setFocus();
194         QCoreApplication::sendEvent(inputLine(), keyEvent);
195         return true;
196       }
197     }
198     return false;
199   } else if(watched == ui.inputEdit) {
200     if(keyEvent->matches(QKeySequence::Find)) {
201       QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar");
202       if(act) {
203         act->toggle();
204         return true;
205       }
206     }
207     return false;
208   }
209   return false;
210 }
211
212 void InputWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
213   BufferId currentBufferId = current.data(NetworkModel::BufferIdRole).value<BufferId>();
214   BufferId previousBufferId = previous.data(NetworkModel::BufferIdRole).value<BufferId>();
215
216   if (_perChatHistory) {
217     //backup
218     historyMap[previousBufferId].history = inputLine()->history();
219     historyMap[previousBufferId].tempHistory = inputLine()->tempHistory();
220     historyMap[previousBufferId].idx = inputLine()->idx();
221     historyMap[previousBufferId].inputLine = inputLine()->html();
222
223     //restore
224     inputLine()->setHistory(historyMap[currentBufferId].history);
225     inputLine()->setTempHistory(historyMap[currentBufferId].tempHistory);
226     inputLine()->setIdx(historyMap[currentBufferId].idx);
227     inputLine()->setHtml(historyMap[currentBufferId].inputLine);
228     inputLine()->moveCursor(QTextCursor::End,QTextCursor::MoveAnchor);
229   }
230
231   NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value<NetworkId>();
232   if(networkId == _networkId)
233     return;
234
235   setNetwork(networkId);
236   updateNickSelector();
237   updateEnabledState();
238 }
239
240 void InputWidget::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
241   QItemSelectionRange changedArea(topLeft, bottomRight);
242   if(changedArea.contains(selectionModel()->currentIndex())) {
243     updateEnabledState();
244   }
245 };
246
247 void InputWidget::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
248   NetworkId networkId;
249   QModelIndex child;
250   for(int row = start; row <= end; row++) {
251     child = model()->index(row, 0, parent);
252     if(NetworkModel::NetworkItemType != child.data(NetworkModel::ItemTypeRole).toInt())
253       continue;
254     networkId = child.data(NetworkModel::NetworkIdRole).value<NetworkId>();
255     if(networkId == _networkId) {
256       setNetwork(0);
257       updateNickSelector();
258       return;
259     }
260   }
261 }
262
263 void InputWidget::updateEnabledState() {
264   QModelIndex currentIndex = selectionModel()->currentIndex();
265
266   const Network *net = Client::networkModel()->networkByIndex(currentIndex);
267   bool enabled = false;
268   if(net) {
269     // disable inputline if it's a channelbuffer we parted from or...
270     enabled = (currentIndex.data(NetworkModel::ItemActiveRole).value<bool>() || (currentIndex.data(NetworkModel::BufferTypeRole).toInt() != BufferInfo::ChannelBuffer));
271     // ... if we're not connected to the network at all
272     enabled &= net->isConnected();
273   }
274   ui.inputEdit->setEnabled(enabled);
275 }
276
277 const Network *InputWidget::currentNetwork() const {
278   return Client::network(_networkId);
279 }
280
281 BufferInfo InputWidget::currentBufferInfo() const {
282   return selectionModel()->currentIndex().data(NetworkModel::BufferInfoRole).value<BufferInfo>();
283 };
284
285 void InputWidget::setNetwork(NetworkId networkId) {
286   if(_networkId == networkId)
287     return;
288
289   const Network *previousNet = Client::network(_networkId);
290   if(previousNet) {
291     disconnect(previousNet, 0, this, 0);
292     if(previousNet->me())
293       disconnect(previousNet->me(), 0, this, 0);
294   }
295
296   _networkId = networkId;
297
298   const Network *network = Client::network(networkId);
299   if(network) {
300     connect(network, SIGNAL(identitySet(IdentityId)), this, SLOT(setIdentity(IdentityId)));
301     connectMyIrcUser();
302     setIdentity(network->identity());
303   } else {
304     setIdentity(0);
305     _networkId = 0;
306   }
307 }
308
309 void InputWidget::connectMyIrcUser() {
310   const Network *network = currentNetwork();
311   if(network->me()) {
312     connect(network->me(), SIGNAL(nickSet(const QString &)), this, SLOT(updateNickSelector()));
313     connect(network->me(), SIGNAL(userModesSet(QString)), this, SLOT(updateNickSelector()));
314     connect(network->me(), SIGNAL(userModesAdded(QString)), this, SLOT(updateNickSelector()));
315     connect(network->me(), SIGNAL(userModesRemoved(QString)), this, SLOT(updateNickSelector()));
316     connect(network->me(), SIGNAL(awaySet(bool)), this, SLOT(updateNickSelector()));
317     disconnect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
318     updateNickSelector();
319   } else {
320     connect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
321   }
322 }
323
324 void InputWidget::setIdentity(IdentityId identityId) {
325   if(_identityId == identityId)
326     return;
327
328   const Identity *previousIdentity = Client::identity(_identityId);
329   if(previousIdentity)
330     disconnect(previousIdentity, 0, this, 0);
331
332   _identityId = identityId;
333
334   const Identity *identity = Client::identity(identityId);
335   if(identity) {
336     connect(identity, SIGNAL(nicksSet(QStringList)), this, SLOT(updateNickSelector()));
337   } else {
338     _identityId = 0;
339   }
340   updateNickSelector();
341 }
342
343 void InputWidget::updateNickSelector() const {
344   ui.ownNick->clear();
345
346   const Network *net = currentNetwork();
347   if(!net)
348     return;
349
350   const Identity *identity = Client::identity(net->identity());
351   if(!identity) {
352     qWarning() << "InputWidget::updateNickSelector(): can't find Identity for Network" << net->networkId() << "IdentityId:" << net->identity();
353     return;
354   }
355
356   int nickIdx;
357   QStringList nicks = identity->nicks();
358   if((nickIdx = nicks.indexOf(net->myNick())) == -1) {
359     nicks.prepend(net->myNick());
360     nickIdx = 0;
361   }
362
363   if(nicks.isEmpty())
364     return;
365
366   IrcUser *me = net->me();
367   if(me) {
368     nicks[nickIdx] = net->myNick();
369     if(!me->userModes().isEmpty())
370       nicks[nickIdx] += QString(" (+%1)").arg(me->userModes());
371   }
372
373   ui.ownNick->addItems(nicks);
374
375   if(me && me->isAway())
376     ui.ownNick->setItemData(nickIdx, SmallIcon("user-away"), Qt::DecorationRole);
377
378   ui.ownNick->setCurrentIndex(nickIdx);
379 }
380
381 void InputWidget::changeNick(const QString &newNick) const {
382   const Network *net = currentNetwork();
383   if(!net || net->isMyNick(newNick))
384     return;
385
386   // we reset the nick selecter as we have no confirmation yet, that this will succeed.
387   // if the action succeeds it will be properly updated anyways.
388   updateNickSelector();
389   Client::userInput(BufferInfo::fakeStatusBuffer(net->networkId()), QString("/NICK %1").arg(newNick));
390 }
391
392 void InputWidget::on_inputEdit_textEntered(const QString &text) {
393   Client::userInput(currentBufferInfo(), text);
394   ui.boldButton->setChecked(false);
395   ui.underlineButton->setChecked(false);
396   ui.italicButton->setChecked(false);
397
398   QTextCharFormat fmt;
399   fmt.setFontWeight(QFont::Normal);
400   fmt.setFontUnderline(false);
401   fmt.setFontItalic(false);
402   fmt.clearForeground();
403   fmt.clearBackground();
404   inputLine()->setCurrentCharFormat(fmt);
405
406 #ifdef HAVE_KDE
407   // Set highlighter back to active in case it was deactivated by too many errors.
408   if(ui.inputEdit->highlighter())
409     ui.inputEdit->highlighter()->setActive(true);
410 #endif
411 }
412
413 void InputWidget::mergeFormatOnSelection(const QTextCharFormat &format) {
414   QTextCursor cursor = inputLine()->textCursor();
415   cursor.mergeCharFormat(format);
416   inputLine()->mergeCurrentCharFormat(format);
417 }
418
419 void InputWidget::setFormatOnSelection(const QTextCharFormat &format) {
420   QTextCursor cursor = inputLine()->textCursor();
421   cursor.setCharFormat(format);
422   inputLine()->setCurrentCharFormat(format);
423 }
424
425 QTextCharFormat InputWidget::getFormatOfWordOrSelection() {
426   QTextCursor cursor = inputLine()->textCursor();
427   return cursor.charFormat();
428 }
429
430 void InputWidget::currentCharFormatChanged(const QTextCharFormat &format) {
431   fontChanged(format.font());
432 }
433
434 void InputWidget::on_boldButton_clicked(bool checked) {
435   QTextCharFormat fmt;
436   fmt.setFontWeight(checked ? QFont::Bold : QFont::Normal);
437   mergeFormatOnSelection(fmt);
438 }
439
440 void InputWidget::on_underlineButton_clicked(bool checked) {
441   QTextCharFormat fmt;
442   fmt.setFontUnderline(checked);
443   mergeFormatOnSelection(fmt);
444 }
445
446 void InputWidget::on_italicButton_clicked(bool checked) {
447   QTextCharFormat fmt;
448   fmt.setFontItalic(checked);
449   mergeFormatOnSelection(fmt);
450 }
451
452 void InputWidget::fontChanged(const QFont &f)
453 {
454   ui.boldButton->setChecked(f.bold());
455   ui.italicButton->setChecked(f.italic());
456   ui.underlineButton->setChecked(f.underline());
457 }
458
459 void InputWidget::colorChosen(QAction *action) {
460   QTextCharFormat fmt;
461   QColor color;
462   if (qVariantValue<QString>(action->data()) == "") {
463     color = Qt::transparent;
464     fmt = getFormatOfWordOrSelection();
465     fmt.clearForeground();
466     setFormatOnSelection(fmt);
467   }
468   else {
469     color = QColor(inputLine()->rgbColorFromMirc(qVariantValue<QString>(action->data())));
470     fmt.setForeground(color);
471     mergeFormatOnSelection(fmt);
472   }
473   ui.textcolorButton->setDefaultAction(action);
474   ui.textcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-text-color"), color));
475 }
476
477 void InputWidget::colorHighlightChosen(QAction *action) {
478   QTextCharFormat fmt;
479   QColor color;
480   if (qVariantValue<QString>(action->data()) == "") {
481     color = Qt::transparent;
482     fmt = getFormatOfWordOrSelection();
483     fmt.clearBackground();
484     setFormatOnSelection(fmt);
485   }
486   else {
487     color = QColor(inputLine()->rgbColorFromMirc(qVariantValue<QString>(action->data())));
488     fmt.setBackground(color);
489     mergeFormatOnSelection(fmt);
490   }
491   ui.highlightcolorButton->setDefaultAction(action);
492   ui.highlightcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-fill-color"), color));
493 }
494
495 void InputWidget::on_showStyleButton_toggled(bool checked) {
496   ui.styleFrame->setVisible(checked);
497   if (checked) {
498     ui.showStyleButton->setArrowType(Qt::LeftArrow);
499   }
500   else {
501     ui.showStyleButton->setArrowType(Qt::RightArrow);
502   }
503 }
504
505 QIcon InputWidget::createColorToolButtonIcon(const QIcon &icon, const QColor &color) {
506   QPixmap pixmap(16, 16);
507   pixmap.fill(Qt::transparent);
508   QPainter painter(&pixmap);
509   QPixmap image = icon.pixmap(16,16);
510   QRect target(0, 0, 16, 14);
511   QRect source(0, 0, 16, 14);
512   painter.fillRect(QRect(0, 14, 16, 16), color);
513   painter.drawPixmap(target, image, source);
514
515   return QIcon(pixmap);
516 }
517
518 // MOUSE WHEEL FILTER
519 MouseWheelFilter::MouseWheelFilter(QObject *parent)
520   : QObject(parent)
521 {
522 }
523
524 bool MouseWheelFilter::eventFilter(QObject *obj, QEvent *event) {
525   if(event->type() != QEvent::Wheel)
526     return QObject::eventFilter(obj, event);
527   else
528     return true;
529 }