Greatly simplify the wrap point computation
[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
35 InputWidget::InputWidget(QWidget *parent)
36   : AbstractItemView(parent),
37     _networkId(0)
38 {
39   ui.setupUi(this);
40   connect(ui.inputEdit, SIGNAL(textEntered(QString)), this, SLOT(sendText(QString)));
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
46   setFocusProxy(ui.inputEdit);
47   ui.ownNick->setFocusProxy(ui.inputEdit);
48
49   ui.ownNick->setSizeAdjustPolicy(QComboBox::AdjustToContents);
50   ui.ownNick->installEventFilter(new MouseWheelFilter(this));
51   ui.inputEdit->installEventFilter(new JumpKeyHandler(this));
52   ui.inputEdit->installEventFilter(this);
53
54   ui.inputEdit->setMinHeight(1);
55   ui.inputEdit->setMaxHeight(5);
56   ui.inputEdit->setMode(MultiLineEdit::MultiLine);
57   ui.inputEdit->setPasteProtectionEnabled(true);
58
59   new TabCompleter(ui.inputEdit);
60
61   UiStyleSettings fs("Fonts");
62   fs.notify("UseCustomInputWidgetFont", this, SLOT(setUseCustomFont(QVariant)));
63   fs.notify("InputWidget", this, SLOT(setCustomFont(QVariant)));
64   if(fs.value("UseCustomInputWidgetFont", false).toBool())
65     setCustomFont(fs.value("InputWidget", QFont()));
66
67   UiSettings s("InputWidget");
68
69 #ifdef HAVE_KDE
70   s.notify("EnableSpellCheck", this, SLOT(setEnableSpellCheck(QVariant)));
71   setEnableSpellCheck(s.value("EnableSpellCheck", false));
72 #endif
73
74   s.notify("ShowNickSelector", this, SLOT(setShowNickSelector(QVariant)));
75   setShowNickSelector(s.value("ShowNickSelector", true));
76
77   s.notify("MaxNumLines", this, SLOT(setMaxLines(QVariant)));
78   setMaxLines(s.value("MaxNumLines", 5));
79
80   s.notify("EnableScrollBars", this, SLOT(setScrollBarsEnabled(QVariant)));
81   setScrollBarsEnabled(s.value("EnableScrollBars", true));
82
83   s.notify("EnableMultiLine", this, SLOT(setMultiLineEnabled(QVariant)));
84   setMultiLineEnabled(s.value("EnableMultiLine", true));
85
86   ActionCollection *coll = QtUi::actionCollection();
87
88   Action *activateInputline = coll->add<Action>("FocusInputLine");
89   connect(activateInputline, SIGNAL(triggered()), SLOT(setFocus()));
90   activateInputline->setText(tr("Focus Input Line"));
91   activateInputline->setShortcut(tr("Ctrl+L"));
92 }
93
94 InputWidget::~InputWidget() {
95 }
96
97 void InputWidget::setUseCustomFont(const QVariant &v) {
98   if(v.toBool()) {
99     UiStyleSettings fs("Fonts");
100     setCustomFont(fs.value("InputWidget"));
101   } else
102     setCustomFont(QFont());
103 }
104
105 void InputWidget::setCustomFont(const QVariant &v) {
106   QFont font = v.value<QFont>();
107   if(font.family().isEmpty())
108     font = QApplication::font();
109   ui.inputEdit->setCustomFont(font);
110 }
111
112 void InputWidget::setEnableSpellCheck(const QVariant &v) {
113   ui.inputEdit->setSpellCheckEnabled(v.toBool());
114 }
115
116 void InputWidget::setShowNickSelector(const QVariant &v) {
117   ui.ownNick->setVisible(v.toBool());
118 }
119
120 void InputWidget::setMaxLines(const QVariant &v) {
121   ui.inputEdit->setMaxHeight(v.toInt());
122 }
123
124 void InputWidget::setScrollBarsEnabled(const QVariant &v) {
125   ui.inputEdit->setScrollBarsEnabled(v.toBool());
126 }
127
128 void InputWidget::setMultiLineEnabled(const QVariant &v) {
129   ui.inputEdit->setMode(v.toBool()? MultiLineEdit::MultiLine : MultiLineEdit::SingleLine);
130 }
131
132 bool InputWidget::eventFilter(QObject *watched, QEvent *event) {
133   if(event->type() != QEvent::KeyPress)
134     return false;
135
136   QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
137
138   // keys from BufferView should be sent to (and focus) the input line
139   BufferView *view = qobject_cast<BufferView *>(watched);
140   if(view) {
141     if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press
142       QChar c = keyEvent->text().at(0);
143       if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) {
144         setFocus();
145         QCoreApplication::sendEvent(inputLine(), keyEvent);
146         return true;
147       }
148     }
149     return false;
150   } else if(watched == ui.inputEdit) {
151     if(keyEvent->matches(QKeySequence::Find)) {
152       QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar");
153       if(act) {
154         act->toggle();
155         return true;
156       }
157     }
158     return false;
159   }
160   return false;
161 }
162
163 void InputWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
164   Q_UNUSED(previous)
165   NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value<NetworkId>();
166   if(networkId == _networkId)
167     return;
168
169   setNetwork(networkId);
170   updateNickSelector();
171   updateEnabledState();
172 }
173
174 void InputWidget::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
175   QItemSelectionRange changedArea(topLeft, bottomRight);
176   if(changedArea.contains(selectionModel()->currentIndex())) {
177     updateEnabledState();
178   }
179 };
180
181 void InputWidget::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
182   NetworkId networkId;
183   QModelIndex child;
184   for(int row = start; row <= end; row++) {
185     child = model()->index(row, 0, parent);
186     if(NetworkModel::NetworkItemType != child.data(NetworkModel::ItemTypeRole).toInt())
187       continue;
188     networkId = child.data(NetworkModel::NetworkIdRole).value<NetworkId>();
189     if(networkId == _networkId) {
190       setNetwork(0);
191       updateNickSelector();
192       return;
193     }
194   }
195 }
196
197 void InputWidget::updateEnabledState() {
198   QModelIndex currentIndex = selectionModel()->currentIndex();
199
200   const Network *net = Client::networkModel()->networkByIndex(currentIndex);
201   bool enabled = false;
202   if(net) {
203     // disable inputline if it's a channelbuffer we parted from or...
204     enabled = (currentIndex.data(NetworkModel::ItemActiveRole).value<bool>() || (currentIndex.data(NetworkModel::BufferTypeRole).toInt() != BufferInfo::ChannelBuffer));
205     // ... if we're not connected to the network at all
206     enabled &= net->isConnected();
207   }
208   ui.inputEdit->setEnabled(enabled);
209 }
210
211 const Network *InputWidget::currentNetwork() const {
212   return Client::network(_networkId);
213 }
214
215 BufferInfo InputWidget::currentBufferInfo() const {
216   return selectionModel()->currentIndex().data(NetworkModel::BufferInfoRole).value<BufferInfo>();
217 };
218
219 void InputWidget::setNetwork(NetworkId networkId) {
220   if(_networkId == networkId)
221     return;
222
223   const Network *previousNet = Client::network(_networkId);
224   if(previousNet) {
225     disconnect(previousNet, 0, this, 0);
226     if(previousNet->me())
227       disconnect(previousNet->me(), 0, this, 0);
228   }
229
230   _networkId = networkId;
231
232   const Network *network = Client::network(networkId);
233   if(network) {
234     connect(network, SIGNAL(identitySet(IdentityId)), this, SLOT(setIdentity(IdentityId)));
235     connectMyIrcUser();
236     setIdentity(network->identity());
237   } else {
238     setIdentity(0);
239     _networkId = 0;
240   }
241 }
242
243 void InputWidget::connectMyIrcUser() {
244   const Network *network = currentNetwork();
245   if(network->me()) {
246     connect(network->me(), SIGNAL(nickSet(const QString &)), this, SLOT(updateNickSelector()));
247     connect(network->me(), SIGNAL(userModesSet(QString)), this, SLOT(updateNickSelector()));
248     connect(network->me(), SIGNAL(userModesAdded(QString)), this, SLOT(updateNickSelector()));
249     connect(network->me(), SIGNAL(userModesRemoved(QString)), this, SLOT(updateNickSelector()));
250     connect(network->me(), SIGNAL(awaySet(bool)), this, SLOT(updateNickSelector()));
251     disconnect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
252     updateNickSelector();
253   } else {
254     connect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
255   }
256 }
257
258 void InputWidget::setIdentity(IdentityId identityId) {
259   if(_identityId == identityId)
260     return;
261
262   const Identity *previousIdentity = Client::identity(_identityId);
263   if(previousIdentity)
264     disconnect(previousIdentity, 0, this, 0);
265
266   _identityId = identityId;
267
268   const Identity *identity = Client::identity(identityId);
269   if(identity) {
270     connect(identity, SIGNAL(nicksSet(QStringList)), this, SLOT(updateNickSelector()));
271   } else {
272     _identityId = 0;
273   }
274   updateNickSelector();
275 }
276
277 void InputWidget::updateNickSelector() const {
278   ui.ownNick->clear();
279
280   const Network *net = currentNetwork();
281   if(!net)
282     return;
283
284   const Identity *identity = Client::identity(net->identity());
285   if(!identity) {
286     qWarning() << "InputWidget::updateNickSelector(): can't find Identity for Network" << net->networkId() << "IdentityId:" << net->identity();
287     return;
288   }
289
290   int nickIdx;
291   QStringList nicks = identity->nicks();
292   if((nickIdx = nicks.indexOf(net->myNick())) == -1) {
293     nicks.prepend(net->myNick());
294     nickIdx = 0;
295   }
296
297   if(nicks.isEmpty())
298     return;
299
300   IrcUser *me = net->me();
301   if(me) {
302     nicks[nickIdx] = net->myNick();
303     if(!me->userModes().isEmpty())
304       nicks[nickIdx] += QString(" (+%1)").arg(me->userModes());
305   }
306
307   ui.ownNick->addItems(nicks);
308
309   if(me && me->isAway())
310     ui.ownNick->setItemData(nickIdx, SmallIcon("user-away"), Qt::DecorationRole);
311
312   ui.ownNick->setCurrentIndex(nickIdx);
313 }
314
315 void InputWidget::changeNick(const QString &newNick) const {
316   const Network *net = currentNetwork();
317   if(!net || net->isMyNick(newNick))
318     return;
319
320   // we reset the nick selecter as we have no confirmation yet, that this will succeed.
321   // if the action succeeds it will be properly updated anyways.
322   updateNickSelector();
323   Client::userInput(BufferInfo::fakeStatusBuffer(net->networkId()), QString("/NICK %1").arg(newNick));
324 }
325
326 void InputWidget::sendText(const QString &text) const {
327   Client::userInput(currentBufferInfo(), text);
328 }
329
330
331 // MOUSE WHEEL FILTER
332 MouseWheelFilter::MouseWheelFilter(QObject *parent)
333   : QObject(parent)
334 {
335 }
336
337 bool MouseWheelFilter::eventFilter(QObject *obj, QEvent *event) {
338   if(event->type() != QEvent::Wheel)
339     return QObject::eventFilter(obj, event);
340   else
341     return true;
342 }