a87717212a2e9922b56b74f95e3f374fe4e70568
[quassel.git] / src / qtui / inputwidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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(colorChoosen(QAction*)));
89   ui.highlightcolorButton->setMenu(_colorFillMenu);
90   connect(_colorFillMenu, SIGNAL(triggered(QAction*)), this, SLOT(colorHighlightChoosen(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("MaxNumLines", this, SLOT(setMaxLines(QVariant)));
111   setMaxLines(s.value("MaxNumLines", 5));
112
113   s.notify("EnableScrollBars", this, SLOT(setScrollBarsEnabled(QVariant)));
114   setScrollBarsEnabled(s.value("EnableScrollBars", true));
115
116   s.notify("EnableMultiLine", this, SLOT(setMultiLineEnabled(QVariant)));
117   setMultiLineEnabled(s.value("EnableMultiLine", true));
118
119   ActionCollection *coll = QtUi::actionCollection();
120
121   Action *activateInputline = coll->add<Action>("FocusInputLine");
122   connect(activateInputline, SIGNAL(triggered()), SLOT(setFocus()));
123   activateInputline->setText(tr("Focus Input Line"));
124   activateInputline->setShortcut(tr("Ctrl+L"));
125
126   connect(inputLine(), SIGNAL(currentCharFormatChanged(QTextCharFormat)), this, SLOT(currentCharFormatChanged(QTextCharFormat)));
127 }
128
129 InputWidget::~InputWidget() {
130 }
131
132 void InputWidget::setUseCustomFont(const QVariant &v) {
133   if(v.toBool()) {
134     UiStyleSettings fs("Fonts");
135     setCustomFont(fs.value("InputWidget"));
136   } else
137     setCustomFont(QFont());
138 }
139
140 void InputWidget::setCustomFont(const QVariant &v) {
141   QFont font = v.value<QFont>();
142   if(font.family().isEmpty())
143     font = QApplication::font();
144   ui.inputEdit->setCustomFont(font);
145 }
146
147 void InputWidget::setEnableSpellCheck(const QVariant &v) {
148   ui.inputEdit->setSpellCheckEnabled(v.toBool());
149 }
150
151 void InputWidget::setShowNickSelector(const QVariant &v) {
152   ui.ownNick->setVisible(v.toBool());
153 }
154
155 void InputWidget::setMaxLines(const QVariant &v) {
156   ui.inputEdit->setMaxHeight(v.toInt());
157 }
158
159 void InputWidget::setScrollBarsEnabled(const QVariant &v) {
160   ui.inputEdit->setScrollBarsEnabled(v.toBool());
161 }
162
163 void InputWidget::setMultiLineEnabled(const QVariant &v) {
164   ui.inputEdit->setMode(v.toBool()? MultiLineEdit::MultiLine : MultiLineEdit::SingleLine);
165 }
166
167 bool InputWidget::eventFilter(QObject *watched, QEvent *event) {
168   if(event->type() != QEvent::KeyPress)
169     return false;
170
171   QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
172
173   // keys from BufferView should be sent to (and focus) the input line
174   BufferView *view = qobject_cast<BufferView *>(watched);
175   if(view) {
176     if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press
177       QChar c = keyEvent->text().at(0);
178       if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) {
179         setFocus();
180         QCoreApplication::sendEvent(inputLine(), keyEvent);
181         return true;
182       }
183     }
184     return false;
185   } else if(watched == ui.inputEdit) {
186     if(keyEvent->matches(QKeySequence::Find)) {
187       QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar");
188       if(act) {
189         act->toggle();
190         return true;
191       }
192     }
193     return false;
194   }
195   return false;
196 }
197
198 void InputWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
199   Q_UNUSED(previous)
200   NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value<NetworkId>();
201   if(networkId == _networkId)
202     return;
203
204   setNetwork(networkId);
205   updateNickSelector();
206   updateEnabledState();
207 }
208
209 void InputWidget::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
210   QItemSelectionRange changedArea(topLeft, bottomRight);
211   if(changedArea.contains(selectionModel()->currentIndex())) {
212     updateEnabledState();
213   }
214 };
215
216 void InputWidget::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
217   NetworkId networkId;
218   QModelIndex child;
219   for(int row = start; row <= end; row++) {
220     child = model()->index(row, 0, parent);
221     if(NetworkModel::NetworkItemType != child.data(NetworkModel::ItemTypeRole).toInt())
222       continue;
223     networkId = child.data(NetworkModel::NetworkIdRole).value<NetworkId>();
224     if(networkId == _networkId) {
225       setNetwork(0);
226       updateNickSelector();
227       return;
228     }
229   }
230 }
231
232 void InputWidget::updateEnabledState() {
233   QModelIndex currentIndex = selectionModel()->currentIndex();
234
235   const Network *net = Client::networkModel()->networkByIndex(currentIndex);
236   bool enabled = false;
237   if(net) {
238     // disable inputline if it's a channelbuffer we parted from or...
239     enabled = (currentIndex.data(NetworkModel::ItemActiveRole).value<bool>() || (currentIndex.data(NetworkModel::BufferTypeRole).toInt() != BufferInfo::ChannelBuffer));
240     // ... if we're not connected to the network at all
241     enabled &= net->isConnected();
242   }
243   ui.inputEdit->setEnabled(enabled);
244 }
245
246 const Network *InputWidget::currentNetwork() const {
247   return Client::network(_networkId);
248 }
249
250 BufferInfo InputWidget::currentBufferInfo() const {
251   return selectionModel()->currentIndex().data(NetworkModel::BufferInfoRole).value<BufferInfo>();
252 };
253
254 void InputWidget::setNetwork(NetworkId networkId) {
255   if(_networkId == networkId)
256     return;
257
258   const Network *previousNet = Client::network(_networkId);
259   if(previousNet) {
260     disconnect(previousNet, 0, this, 0);
261     if(previousNet->me())
262       disconnect(previousNet->me(), 0, this, 0);
263   }
264
265   _networkId = networkId;
266
267   const Network *network = Client::network(networkId);
268   if(network) {
269     connect(network, SIGNAL(identitySet(IdentityId)), this, SLOT(setIdentity(IdentityId)));
270     connectMyIrcUser();
271     setIdentity(network->identity());
272   } else {
273     setIdentity(0);
274     _networkId = 0;
275   }
276 }
277
278 void InputWidget::connectMyIrcUser() {
279   const Network *network = currentNetwork();
280   if(network->me()) {
281     connect(network->me(), SIGNAL(nickSet(const QString &)), this, SLOT(updateNickSelector()));
282     connect(network->me(), SIGNAL(userModesSet(QString)), this, SLOT(updateNickSelector()));
283     connect(network->me(), SIGNAL(userModesAdded(QString)), this, SLOT(updateNickSelector()));
284     connect(network->me(), SIGNAL(userModesRemoved(QString)), this, SLOT(updateNickSelector()));
285     connect(network->me(), SIGNAL(awaySet(bool)), this, SLOT(updateNickSelector()));
286     disconnect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
287     updateNickSelector();
288   } else {
289     connect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
290   }
291 }
292
293 void InputWidget::setIdentity(IdentityId identityId) {
294   if(_identityId == identityId)
295     return;
296
297   const Identity *previousIdentity = Client::identity(_identityId);
298   if(previousIdentity)
299     disconnect(previousIdentity, 0, this, 0);
300
301   _identityId = identityId;
302
303   const Identity *identity = Client::identity(identityId);
304   if(identity) {
305     connect(identity, SIGNAL(nicksSet(QStringList)), this, SLOT(updateNickSelector()));
306   } else {
307     _identityId = 0;
308   }
309   updateNickSelector();
310 }
311
312 void InputWidget::updateNickSelector() const {
313   ui.ownNick->clear();
314
315   const Network *net = currentNetwork();
316   if(!net)
317     return;
318
319   const Identity *identity = Client::identity(net->identity());
320   if(!identity) {
321     qWarning() << "InputWidget::updateNickSelector(): can't find Identity for Network" << net->networkId() << "IdentityId:" << net->identity();
322     return;
323   }
324
325   int nickIdx;
326   QStringList nicks = identity->nicks();
327   if((nickIdx = nicks.indexOf(net->myNick())) == -1) {
328     nicks.prepend(net->myNick());
329     nickIdx = 0;
330   }
331
332   if(nicks.isEmpty())
333     return;
334
335   IrcUser *me = net->me();
336   if(me) {
337     nicks[nickIdx] = net->myNick();
338     if(!me->userModes().isEmpty())
339       nicks[nickIdx] += QString(" (+%1)").arg(me->userModes());
340   }
341
342   ui.ownNick->addItems(nicks);
343
344   if(me && me->isAway())
345     ui.ownNick->setItemData(nickIdx, SmallIcon("user-away"), Qt::DecorationRole);
346
347   ui.ownNick->setCurrentIndex(nickIdx);
348 }
349
350 void InputWidget::changeNick(const QString &newNick) const {
351   const Network *net = currentNetwork();
352   if(!net || net->isMyNick(newNick))
353     return;
354
355   // we reset the nick selecter as we have no confirmation yet, that this will succeed.
356   // if the action succeeds it will be properly updated anyways.
357   updateNickSelector();
358   Client::userInput(BufferInfo::fakeStatusBuffer(net->networkId()), QString("/NICK %1").arg(newNick));
359 }
360
361 void InputWidget::on_inputEdit_textEntered(const QString &text) const {
362   Client::userInput(currentBufferInfo(), text);
363   ui.boldButton->setChecked(false);
364   ui.underlineButton->setChecked(false);
365   ui.italicButton->setChecked(false);
366
367   QTextCharFormat fmt;
368   fmt.setFontWeight(QFont::Normal);
369   fmt.setFontUnderline(false);
370   fmt.setFontItalic(false);
371   fmt.clearForeground();
372   fmt.clearBackground();
373   inputLine()->setCurrentCharFormat(fmt);
374 }
375
376 void InputWidget::mergeFormatOnWordOrSelection(const QTextCharFormat &format) {
377   QTextCursor cursor = inputLine()->textCursor();
378   if (!cursor.hasSelection())
379     cursor.select(QTextCursor::WordUnderCursor);
380   cursor.mergeCharFormat(format);
381   inputLine()->mergeCurrentCharFormat(format);
382 }
383
384 void InputWidget::setFormatOnWordOrSelection(const QTextCharFormat &format) {
385   QTextCursor cursor = inputLine()->textCursor();
386   if (!cursor.hasSelection())
387     cursor.select(QTextCursor::WordUnderCursor);
388   cursor.setCharFormat(format);
389   inputLine()->setCurrentCharFormat(format);
390 }
391
392 QTextCharFormat InputWidget::getFormatOfWordOrSelection() {
393   QTextCursor cursor = inputLine()->textCursor();
394   return cursor.charFormat();
395 }
396
397 void InputWidget::currentCharFormatChanged(const QTextCharFormat &format) {
398   fontChanged(format.font());
399
400   if (format.foreground().isOpaque())
401     colorChanged(format.foreground().color());
402   else
403     colorChanged(Qt::transparent);
404   if (format.background().isOpaque())
405     colorHighlightChanged(format.background().color());
406   else
407     colorHighlightChanged(Qt::transparent);
408 }
409
410 void InputWidget::on_boldButton_clicked(bool checked) {
411   QTextCharFormat fmt;
412   fmt.setFontWeight(checked ? QFont::Bold : QFont::Normal);
413   mergeFormatOnWordOrSelection(fmt);
414 }
415
416 void InputWidget::on_underlineButton_clicked(bool checked) {
417   QTextCharFormat fmt;
418   fmt.setFontUnderline(checked);
419   mergeFormatOnWordOrSelection(fmt);
420 }
421
422 void InputWidget::on_italicButton_clicked(bool checked) {
423   QTextCharFormat fmt;
424   fmt.setFontItalic(checked);
425   mergeFormatOnWordOrSelection(fmt);
426 }
427
428 void InputWidget::fontChanged(const QFont &f)
429 {
430   ui.boldButton->setChecked(f.bold());
431   ui.italicButton->setChecked(f.italic());
432   ui.underlineButton->setChecked(f.underline());
433 }
434
435 void InputWidget::colorChoosen(QAction * action) {
436   QTextCharFormat fmt;
437   QColor color;
438   if (qVariantValue<QString>(action->data()) == "") {
439     color = Qt::transparent;
440     fmt = getFormatOfWordOrSelection();
441     fmt.clearForeground();
442     setFormatOnWordOrSelection(fmt);
443   }
444   else {
445     color = QColor(inputLine()->rgbColorFromMirc(qVariantValue<QString>(action->data())));
446     fmt.setForeground(color);
447     mergeFormatOnWordOrSelection(fmt);
448   }
449   ui.textcolorButton->setDefaultAction(action);
450   ui.textcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-text-color"), color));
451 }
452
453 void InputWidget::colorHighlightChoosen(QAction * action) {
454   QTextCharFormat fmt;
455   QColor color;
456   if (qVariantValue<QString>(action->data()) == "") {
457     color = Qt::transparent;
458     fmt = getFormatOfWordOrSelection();
459     fmt.clearBackground();
460     setFormatOnWordOrSelection(fmt);
461   }
462   else {
463     color = QColor(inputLine()->rgbColorFromMirc(qVariantValue<QString>(action->data())));
464     fmt.setBackground(color);
465     mergeFormatOnWordOrSelection(fmt);
466   }
467   ui.highlightcolorButton->setDefaultAction(action);
468   ui.highlightcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-fill-color"), color));
469 }
470
471 void InputWidget::colorChanged(const QColor &fg) {
472   if (fg == Qt::transparent)
473     ui.textcolorButton->setDefaultAction(_colorMenu->actions().last());
474   else
475     ui.textcolorButton->setDefaultAction(_colorMenu->actions().value(inputLine()->mircColorFromRGB(fg.name()).toInt()));
476
477   ui.textcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-text-color"), fg));
478 }
479
480 void InputWidget::colorHighlightChanged(const QColor &bg) {
481   if (bg == Qt::transparent)
482     ui.highlightcolorButton->setDefaultAction(_colorFillMenu->actions().last());
483   else
484     ui.highlightcolorButton->setDefaultAction(_colorFillMenu->actions().value(inputLine()->mircColorFromRGB(bg.name()).toInt()));
485
486   ui.highlightcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-fill-color"), bg));
487 }
488
489 void InputWidget::on_showStyleButton_toggled(bool checked) {
490   ui.styleFrame->setVisible(checked);
491   if (checked) {
492     ui.showStyleButton->setArrowType(Qt::LeftArrow);
493   }
494   else {
495     ui.showStyleButton->setArrowType(Qt::RightArrow);
496   }
497 }
498
499 QIcon InputWidget::createColorToolButtonIcon(const QIcon &icon, QColor color) {
500   QPixmap pixmap(16, 16);
501   pixmap.fill(Qt::transparent);
502   QPainter painter(&pixmap);
503   QPixmap image = icon.pixmap(16,16);
504   QRect target(0, 0, 16, 14);
505   QRect source(0, 0, 16, 14);
506   painter.fillRect(QRect(0, 14, 16, 16), color);
507   painter.drawPixmap(target, image, source);
508
509   return QIcon(pixmap);
510 }
511
512 // MOUSE WHEEL FILTER
513 MouseWheelFilter::MouseWheelFilter(QObject *parent)
514   : QObject(parent)
515 {
516 }
517
518 bool MouseWheelFilter::eventFilter(QObject *obj, QEvent *event) {
519   if(event->type() != QEvent::Wheel)
520     return QObject::eventFilter(obj, event);
521   else
522     return true;
523 }