1 /*
2 SPDX-License-Identifier: GPL-2.0-or-later
3
4 SPDX-FileCopyrightText: 2002 Dario Abatianni <eisfuchs@tigress.com>
5 SPDX-FileCopyrightText: 2005-2008 Eike Hein <hein@kde.org>
6 */
7
8 #include "query.h"
9
10 #include "channel.h"
11 #include "server.h"
12 #include "application.h"
13 #include "mainwindow.h"
14 #include "viewcontainer.h"
15 #include "ircinput.h"
16 #include "ircview.h"
17 #include "ircviewbox.h"
18 #include "awaylabel.h"
19 #include "common.h"
20
21 #include <QSplitter>
22
23 #include <KMessageBox>
24 #include <KSqueezedTextLabel>
25
26 using namespace Konversation;
27
Query(QWidget * parent,const QString & _name)28 Query::Query(QWidget* parent, const QString& _name) : ChatWindow(parent)
29 {
30 name=_name; // need the name a little bit earlier for setServer
31 // don't setName here! It will break logfiles!
32 // setName("QueryWidget");
33 setType(ChatWindow::Query);
34 m_isTopLevelView = false;
35
36 setChannelEncodingSupported(true);
37
38 m_headerSplitter = new QSplitter(Qt::Vertical, this);
39
40 m_initialShow = true;
41 awayChanged=false;
42 awayState=false;
43
44 queryHostmask=new KSqueezedTextLabel(m_headerSplitter);
45 m_headerSplitter->setStretchFactor(m_headerSplitter->indexOf(queryHostmask), 0);
46 queryHostmask->setTextElideMode(Qt::ElideRight);
47 queryHostmask->setObjectName(QStringLiteral("query_hostmask"));
48
49 QString whatsthis = i18n("<qt><p>Some details of the person you are talking to in this query is shown in this bar. The full name and hostmask is shown.</p><p>See the <i>Konversation Handbook</i> for an explanation of what the hostmask is.</p></qt>");
50 queryHostmask->setWhatsThis(whatsthis);
51
52 auto* ircViewBox = new IRCViewBox(m_headerSplitter);
53 m_headerSplitter->setStretchFactor(m_headerSplitter->indexOf(ircViewBox), 1);
54 setTextView(ircViewBox->ircView()); // Server will be set later in setServer();
55 ircViewBox->ircView()->setContextMenuOptions(IrcContextMenus::ShowNickActions, true);
56 textView->setAcceptDrops(true);
57 connect(textView,&IRCView::urlsDropped,this,&Query::urlsDropped);
58
59 // This box holds the input line
60 auto* inputBox=new QWidget(this);
61 auto* inputBoxLayout = new QHBoxLayout(inputBox);
62 inputBox->setObjectName(QStringLiteral("input_log_box"));
63 inputBoxLayout->setSpacing(spacing());
64 inputBoxLayout->setContentsMargins(0, 0, 0, 0);
65
66 awayLabel=new AwayLabel(inputBox);
67 inputBoxLayout->addWidget(awayLabel);
68 awayLabel->hide();
69 blowfishLabel = new QLabel(inputBox);
70 inputBoxLayout->addWidget(blowfishLabel);
71 blowfishLabel->hide();
72 const int toolBarIconSize = blowfishLabel->style()->pixelMetric(QStyle::PixelMetric::PM_ToolBarIconSize);
73 blowfishLabel->setPixmap(QIcon::fromTheme(QStringLiteral("document-encrypt")).pixmap(toolBarIconSize));
74 m_inputBar=new IRCInput(inputBox);
75 inputBoxLayout->addWidget(m_inputBar);
76
77 getTextView()->installEventFilter(m_inputBar);
78 m_inputBar->installEventFilter(this);
79
80 // connect the signals and slots
81 connect(m_inputBar, &IRCInput::submit, this, &Query::queryTextEntered);
82 connect(m_inputBar, &IRCInput::envelopeCommand, this, &Query::queryPassthroughCommand);
83 connect(m_inputBar, &IRCInput::textPasted, this, &Query::textPasted);
84 connect(getTextView(), &IRCView::textPasted, m_inputBar, &IRCInput::paste);
85 connect(getTextView(), &IRCView::gotFocus, m_inputBar, QOverload<>::of(&IRCInput::setFocus));
86
87 connect(textView,&IRCView::sendFile,this,&Query::sendFileMenu );
88 connect(textView, &IRCView::autoText, this, &Query::sendText);
89
90 updateAppearance();
91
92 #ifdef HAVE_QCA2
93 m_cipher = nullptr;
94 #endif
95 }
96
~Query()97 Query::~Query()
98 {
99 if (m_recreationScheduled)
100 {
101 qRegisterMetaType<NickInfoPtr>("NickInfoPtr");
102
103 QMetaObject::invokeMethod(m_server, "addQuery", Qt::QueuedConnection,
104 Q_ARG(NickInfoPtr, m_nickInfo), Q_ARG(bool, true));
105 }
106 }
107
setServer(Server * newServer)108 void Query::setServer(Server* newServer)
109 {
110 if (m_server != newServer)
111 {
112 connect(newServer, &Server::connectionStateChanged,
113 this, &Query::connectionStateChanged);
114 connect(newServer, QOverload<Server*,NickInfoPtr>::of(&Server::nickInfoChanged),
115 this, &Query::updateNickInfo);
116 }
117
118 ChatWindow::setServer(newServer);
119
120 if (!(newServer->getKeyForRecipient(getName()).isEmpty()))
121 blowfishLabel->show();
122
123 connect(awayLabel, &AwayLabel::unaway, m_server, &Server::requestUnaway);
124 connect(awayLabel, &AwayLabel::awayMessageChanged, m_server, &Server::requestAway);
125 }
126
connectionStateChanged(Server * server,Konversation::ConnectionState state)127 void Query::connectionStateChanged(Server* server, Konversation::ConnectionState state)
128 {
129 if (server == m_server)
130 {
131 ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer();
132
133 if (state == Konversation::SSConnected)
134 {
135 //HACK the way the notification priorities work sucks, this forces the tab text color to ungray right now.
136 if (viewContainer->getFrontView() == this
137 || m_currentTabNotify == Konversation::tnfNone
138 || !Preferences::self()->tabNotificationsEvents())
139 {
140 viewContainer->unsetViewNotification(this);
141 }
142 }
143 else
144 {
145 //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now.
146 if (viewContainer->getFrontView() == this
147 || m_currentTabNotify == Konversation::tnfNone
148 || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl))
149 {
150 viewContainer->unsetViewNotification(this);
151 }
152 }
153 }
154 }
155
setName(const QString & newName)156 void Query::setName(const QString& newName)
157 {
158 //if(ChatWindow::getName() == newName) return; // no change, so return
159
160
161 if(ChatWindow::getName() != newName)
162 {
163 appendCommandMessage(i18n("Nick"),i18n("%1 is now known as %2.", getName(), newName));
164 }
165
166
167 ChatWindow::setName(newName);
168
169 // don't change logfile name if query name changes
170 // This will prevent Nick-Changers to create more than one log file,
171 if (logName.isEmpty())
172 {
173 QString logName = (Preferences::self()->lowerLog()) ? getName().toLower() : getName() ;
174
175 if(Preferences::self()->addHostnameToLog())
176 {
177 if(m_nickInfo)
178 logName += m_nickInfo->getHostmask();
179 }
180
181 setLogfileName(logName);
182 }
183 }
184
setEncryptedOutput(bool e)185 void Query::setEncryptedOutput(bool e)
186 {
187 if (e)
188 blowfishLabel->show();
189 else
190 blowfishLabel->hide();
191 }
192
queryTextEntered()193 void Query::queryTextEntered()
194 {
195 QString line=m_inputBar->toPlainText();
196
197 m_inputBar->clear();
198
199 if (!line.isEmpty()) sendText(sterilizeUnicode(line));
200 }
201
queryPassthroughCommand()202 void Query::queryPassthroughCommand()
203 {
204 QString commandChar = Preferences::self()->commandChar();
205 QString line = m_inputBar->toPlainText();
206
207 m_inputBar->clear();
208
209 if(!line.isEmpty())
210 {
211 // Prepend commandChar on Ctrl+Enter to bypass outputfilter command recognition
212 if (line.startsWith(commandChar))
213 {
214 line = commandChar + line;
215 }
216 sendText(sterilizeUnicode(line));
217 }
218 }
219
sendText(const QString & sendLine)220 void Query::sendText(const QString& sendLine)
221 {
222 // create a work copy
223 QString outputAll(sendLine);
224
225 // replace aliases and wildcards
226 OutputFilter::replaceAliases(outputAll, this);
227
228 // Send all strings, one after another
229 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
230 const QStringList outList = outputAll.split(QLatin1Char('\n'), QString::SkipEmptyParts);
231 #else
232 const QStringList outList = outputAll.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
233 #endif
234 for (const QString& output : outList) {
235
236 // encoding stuff is done in Server()
237 Konversation::OutputFilterResult result = m_server->getOutputFilter()->parse(m_server->getNickname(), output, getName(), this);
238
239 if(!result.output.isEmpty())
240 {
241 if(result.type == Konversation::Action) appendAction(m_server->getNickname(), result.output);
242 else if(result.type == Konversation::Command) appendCommandMessage(result.typeString, result.output);
243 else if(result.type == Konversation::Program) appendServerMessage(result.typeString, result.output);
244 else if(result.type == Konversation::PrivateMessage) msgHelper(result.typeString, result.output);
245 else if(!result.typeString.isEmpty()) appendQuery(result.typeString, result.output);
246 else appendQuery(m_server->getNickname(), result.output);
247 }
248 else if (!result.outputList.isEmpty()) {
249 if (result.type == Konversation::Message)
250 {
251 for (const QString& out : qAsConst(result.outputList)) {
252 appendQuery(m_server->getNickname(), out);
253 }
254 }
255 else if (result.type == Konversation::Action)
256 {
257 for (int i = 0; i < result.outputList.count(); ++i)
258 {
259 if (i == 0)
260 appendAction(m_server->getNickname(), result.outputList.at(i));
261 else
262 appendQuery(m_server->getNickname(), result.outputList.at(i));
263 }
264 }
265 }
266
267 // Send anything else to the server
268 if (!result.toServerList.empty())
269 m_server->queueList(result.toServerList);
270 else
271 m_server->queue(result.toServer);
272 } // for
273 }
274
textPasted(const QString & text)275 void Query::textPasted(const QString& text)
276 {
277 if(m_server)
278 {
279 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
280 const QStringList multiline = text.split(QLatin1Char('\n'), QString::SkipEmptyParts);
281 #else
282 const QStringList multiline = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
283 #endif
284 for (QString line : multiline) {
285 QString cChar(Preferences::self()->commandChar());
286 // make sure that lines starting with command char get escaped
287 if(line.startsWith(cChar)) line=cChar+line;
288 sendText(line);
289 }
290 }
291 }
292
indicateAway(bool show)293 void Query::indicateAway(bool show)
294 {
295 // QT does not redraw the label properly when they are not on screen
296 // while getting hidden, so we remember the "soon to be" state here.
297 if(isHidden())
298 {
299 awayChanged=true;
300 awayState=show;
301 }
302 else
303 {
304 if(show)
305 awayLabel->show();
306 else
307 awayLabel->hide();
308 }
309 }
310
311 // fix QTs broken behavior on hidden QListView pages
showEvent(QShowEvent *)312 void Query::showEvent(QShowEvent*)
313 {
314 if(awayChanged)
315 {
316 awayChanged=false;
317 indicateAway(awayState);
318 }
319
320 if(m_initialShow) {
321 m_initialShow = false;
322 QList<int> sizes;
323 sizes << queryHostmask->sizeHint().height() << (height() - queryHostmask->sizeHint().height());
324 m_headerSplitter->setSizes(sizes);
325 }
326 }
327
sendFileMenu()328 void Query::sendFileMenu()
329 {
330 Q_EMIT sendFile(getName());
331 }
332
childAdjustFocus()333 void Query::childAdjustFocus()
334 {
335 m_inputBar->setFocus();
336 }
337
setNickInfo(const NickInfoPtr & nickInfo)338 void Query::setNickInfo(const NickInfoPtr & nickInfo)
339 {
340 m_nickInfo = nickInfo;
341 Q_ASSERT(m_nickInfo); if(!m_nickInfo) return;
342 nickInfoChanged();
343 }
344
updateNickInfo(Server * server,const NickInfoPtr & nickInfo)345 void Query::updateNickInfo(Server* server, const NickInfoPtr &nickInfo)
346 {
347 if (!m_nickInfo || server != m_server || nickInfo != m_nickInfo)
348 return;
349
350 nickInfoChanged();
351 }
352
nickInfoChanged()353 void Query::nickInfoChanged()
354 {
355 if (m_nickInfo)
356 {
357 setName(m_nickInfo->getNickname());
358 QString text = m_nickInfo->getBestAddresseeName();
359 if(!m_nickInfo->getHostmask().isEmpty() && !text.isEmpty())
360 text += QStringLiteral(" - ");
361 text += m_nickInfo->getHostmask();
362 if(m_nickInfo->isAway() && !m_nickInfo->getAwayMessage().isEmpty())
363 text += QLatin1String(" (") + m_nickInfo->getAwayMessage() + QLatin1String(") ");
364 queryHostmask->setText(Konversation::removeIrcMarkup(text));
365
366 QString strTooltip;
367 QTextStream tooltip( &strTooltip, QIODevice::WriteOnly );
368
369 tooltip << "<qt>";
370
371 tooltip << R"(<table cellspacing="5" cellpadding="0">)";
372
373 m_nickInfo->tooltipTableData(tooltip);
374
375 tooltip << "</table></qt>";
376 queryHostmask->setToolTip(strTooltip);
377 }
378
379 Q_EMIT updateQueryChrome(this,getName());
380 emitUpdateInfo();
381 }
382
getNickInfo() const383 NickInfoPtr Query::getNickInfo() const
384 {
385 return m_nickInfo;
386 }
387
canBeFrontView() const388 bool Query::canBeFrontView() const { return true; }
searchView() const389 bool Query::searchView() const { return true; }
390 // virtual
setChannelEncoding(const QString & encoding)391 void Query::setChannelEncoding(const QString& encoding)
392 {
393 if(m_server->getServerGroup())
394 Preferences::setChannelEncoding(m_server->getServerGroup()->id(), getName(), encoding);
395 else
396 Preferences::setChannelEncoding(m_server->getDisplayName(), getName(), encoding);
397 }
398
getChannelEncoding() const399 QString Query::getChannelEncoding() const // virtual
400 {
401 if(m_server->getServerGroup())
402 return Preferences::channelEncoding(m_server->getServerGroup()->id(), getName());
403 return Preferences::channelEncoding(m_server->getDisplayName(), getName());
404 }
405
getChannelEncodingDefaultDesc() const406 QString Query::getChannelEncodingDefaultDesc() const // virtual
407 {
408 return i18n("Identity Default ( %1 )",getServer()->getIdentity()->getCodecName());
409 }
410
closeYourself(bool confirm)411 bool Query::closeYourself(bool confirm)
412 {
413 int result = KMessageBox::Continue;
414
415 if (confirm)
416 result=KMessageBox::warningContinueCancel(
417 this,
418 i18n("Do you want to close your query with %1?", getName()),
419 i18n("Close Query"),
420 KStandardGuiItem::close(),
421 KStandardGuiItem::cancel(),
422 QStringLiteral("QuitQueryTab"));
423
424 if (result == KMessageBox::Continue)
425 {
426 m_server->removeQuery(this);
427
428 return true;
429 }
430 else
431 m_recreationScheduled = false;
432
433 return false;
434 }
435
urlsDropped(const QList<QUrl> & urls)436 void Query::urlsDropped(const QList<QUrl>& urls)
437 {
438 m_server->sendURIs(urls, getName());
439 }
440
emitUpdateInfo()441 void Query::emitUpdateInfo()
442 {
443 QString info;
444 if(m_nickInfo->loweredNickname() == m_server->loweredNickname())
445 info = i18n("Talking to yourself");
446 else if(m_nickInfo)
447 info = m_nickInfo->getBestAddresseeName();
448 else
449 info = getName();
450
451 Q_EMIT updateInfo(info);
452 }
453
454 // show quit message of nick if we see it
quitNick(const QString & reason,const QHash<QString,QString> & messageTags)455 void Query::quitNick(const QString& reason, const QHash<QString, QString> &messageTags)
456 {
457 QString displayReason = reason;
458
459 if (displayReason.isEmpty())
460 {
461 appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostmask", "%1 (%2) has left this server.",
462 getName(), getNickInfo()->getHostmask()), messageTags, false);
463 }
464 else
465 {
466 if (hasIRCMarkups(displayReason))
467 displayReason+=QStringLiteral("\017");
468
469 appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostmask, %3 = reason", "%1 (%2) has left this server (%3).",
470 getName(), getNickInfo()->getHostmask(), displayReason), messageTags, false);
471 }
472 }
473
474 #ifdef HAVE_QCA2
getCipher() const475 Konversation::Cipher* Query::getCipher() const
476 {
477 if(!m_cipher)
478 m_cipher = new Konversation::Cipher();
479 return m_cipher;
480 }
481 #endif
482