1 #include "psichatdlg.h"
2 
3 #include <QLabel>
4 #include <QCursor>
5 #include <QLineEdit>
6 #include <QToolButton>
7 #include <QLayout>
8 #include <QSplitter>
9 #include <QToolBar>
10 #include <QPixmap>
11 #include <QColor>
12 #include <QCloseEvent>
13 #include <QHBoxLayout>
14 #include <QVBoxLayout>
15 #include <QContextMenuEvent>
16 #include <QResizeEvent>
17 #include <QMenu>
18 #include <QDragEnterEvent>
19 #include <QMessageBox>
20 #include <QDebug>
21 #include <QClipboard>
22 
23 #include "psicon.h"
24 #include "psiaccount.h"
25 #include "iconaction.h"
26 #include "stretchwidget.h"
27 #include "psiiconset.h"
28 #include "iconwidget.h"
29 #include "fancylabel.h"
30 #include "textutil.h"
31 #include "msgmle.h"
32 #include "messageview.h"
33 #include "iconselect.h"
34 #include "avatars.h"
35 #include "activecontactsmenu.h"
36 #include "psitooltip.h"
37 #include "psioptions.h"
38 #include "coloropt.h"
39 #include "shortcutmanager.h"
40 #include "accountlabel.h"
41 #include "iconlabel.h"
42 #include "psicontactlist.h"
43 #include "userlist.h"
44 #include "psicontact.h"
45 #include "jidutil.h"
46 #include "xmpp_tasks.h"
47 #include "xmpp_caps.h"
48 #include "lastactivitytask.h"
49 #include "avcall/avcall.h"
50 #include "actionlist.h"
51 #include "psiactionlist.h"
52 #ifdef PSI_PLUGINS
53 #include "pluginmanager.h"
54 #endif
55 
56 #define MCMDCHAT		"http://psi-im.org/ids/mcmd#chatmain"
57 
58 PsiIcon* PsiChatDlg::throbber_icon = 0;
59 
60 class PsiChatDlg::ChatDlgMCmdProvider : public QObject, public MCmdProviderIface {
61 	Q_OBJECT
62 public:
ChatDlgMCmdProvider(PsiChatDlg * dlg)63 	ChatDlgMCmdProvider(PsiChatDlg *dlg) : dlg_(dlg) {};
64 
mCmdTryStateTransit(MCmdStateIface * oldstate,QStringList command,MCmdStateIface * & newstate,QStringList & preset)65 	virtual bool mCmdTryStateTransit(MCmdStateIface *oldstate, QStringList command, MCmdStateIface *&newstate, QStringList &preset) {
66 		Q_UNUSED(preset);
67 		if (oldstate->getName() == MCMDCHAT) {
68 			QString cmd;
69 			if (command.count() > 0) cmd = command[0].toLower();
70 			if (cmd == "version") {
71 				JT_ClientVersion *version = new JT_ClientVersion(dlg_->account()->client()->rootTask());
72 				connect(version, SIGNAL(finished()), SLOT(version_finished()));
73 
74 				//qDebug() << "querying: " << dlg_->jid().full();
75 				version->get(dlg_->jid());
76 				version->go();
77 				newstate = 0;
78 			} else if (cmd == "idle") {
79 				LastActivityTask *idle = new LastActivityTask(dlg_->jid(), dlg_->account()->client()->rootTask());
80 				connect(idle, SIGNAL(finished()), SLOT(lastactivity_finished()));
81 				idle->go();
82 				newstate = 0;
83 			} else if (cmd == "clear") {
84 				dlg_->doClear();
85 				newstate = 0;
86 			} else if (cmd == "vcard") {
87 				dlg_->doInfo();
88 				newstate = 0;
89 			} else if (cmd == "auth") {
90 				if (command.count() == 2) {
91 					if (command[1].toLower() == "request") {
92 						dlg_->account()->actionAuthRequest(dlg_->jid());
93 					}
94 				}
95 				newstate = 0;
96 			} else if (cmd == "compact") {
97 				if (command.count() == 2) {
98 					QString sub = command[1].toLower();
99 					if ("on" == sub) {
100 						dlg_->smallChat_ = true;
101 					} else if ("off" == sub) {
102 						dlg_->smallChat_ = false;
103 					} else {
104 						dlg_->appendSysMsg("usage: compact {on,off}");
105 					}
106 				} else {
107 					dlg_->smallChat_ = !dlg_->smallChat_;
108 				}
109 				dlg_->setLooks();
110 				newstate = 0;
111 			} else if (!cmd.isEmpty()) {
112 				return false;
113 			}
114 			return true;
115 		} else {
116 			return false;
117 		}
118 	};
119 
mCmdTryCompleteCommand(MCmdStateIface * state,QString query,QStringList partcommand,int item)120 	virtual QStringList mCmdTryCompleteCommand(MCmdStateIface *state, QString query, QStringList partcommand, int item) {
121 		Q_UNUSED(partcommand);
122 		QStringList all;
123 		if (state->getName() == MCMDCHAT) {
124 			if (item == 0) {
125 				all << "version" << "idle" << "clear" << "vcard" << "auth" << "compact";
126 			}
127 		}
128 		QStringList res;
129 		foreach(QString cmd, all) {
130 			if (cmd.startsWith(query)) {
131 				res << cmd;
132 			}
133 		}
134 		return res;
135 	};
136 
mCmdSiteDestroyed()137 	virtual void mCmdSiteDestroyed() {};
~ChatDlgMCmdProvider()138 	virtual ~ChatDlgMCmdProvider() {};
139 
140 
141 public slots:
version_finished()142 	void version_finished() {
143 		JT_ClientVersion *version = qobject_cast<JT_ClientVersion*>(sender());
144 		if (!version) {
145 			dlg_->appendSysMsg("No version information available.");
146 			return;
147 		}
148 		dlg_->appendSysMsg(TextUtil::escape(QString("Version response: N: %2 V: %3 OS: %4")
149 			.arg(version->name(), version->version(), version->os())));
150 	};
151 
lastactivity_finished()152 	void lastactivity_finished()
153 	{
154 		LastActivityTask *idle = (LastActivityTask *)sender();
155 
156 		if (!idle->success()) {
157 			dlg_->appendSysMsg("Could not determine time of last activity.");
158 			return;
159 		}
160 
161 		if (idle->status().isEmpty()) {
162 			dlg_->appendSysMsg(QString("Last activity at %1")
163 				.arg(idle->time().toString()));
164 		} else {
165 			dlg_->appendSysMsg(QString("Last activity at %1 (%2)")
166 				.arg(idle->time().toString(), TextUtil::escape(idle->status())));
167 		}
168 	}
169 
170 private:
171 	PsiChatDlg *dlg_;
172 };
173 
174 
175 
PsiChatDlg(const Jid & jid,PsiAccount * pa,TabManager * tabManager)176 PsiChatDlg::PsiChatDlg(const Jid& jid, PsiAccount* pa, TabManager* tabManager)
177 	: ChatDlg(jid, pa, tabManager), actions_(new ActionList("", 0, false)), mCmdManager_(&mCmdSite_), tabCompletion(&mCmdManager_)
178 	, autoPGP_(true)
179 {
180 	connect(account()->psi(), SIGNAL(accountCountChanged()), this, SLOT(updateIdentityVisibility()));
181 	connect(account(), SIGNAL(addedContact(PsiContact*)), SLOT(updateContactAdding(PsiContact*)));
182 	connect(account(), SIGNAL(removedContact(PsiContact*)), SLOT(updateContactAdding(PsiContact*)));
183 	connect(account(), SIGNAL(updateContact(const Jid &)), SLOT(updateContactAdding(const Jid &)));
184 	mCmdManager_.registerProvider(new ChatDlgMCmdProvider(this));
185 }
186 
~PsiChatDlg()187 PsiChatDlg::~PsiChatDlg()
188 {
189 	delete actions_;
190 }
191 
initUi()192 void PsiChatDlg::initUi()
193 {
194 	ui_.setupUi(this);
195 
196 	le_autojid = new ActionLineEdit(ui_.le_jid);
197 	ui_.le_jid->setLineEdit(le_autojid);
198 	ui_.le_jid->lineEdit()->setReadOnly(true);
199 	if (autoSelectContact_) {
200 		QStringList excl = PsiOptions::instance()->getOption("options.ui.chat.default-jid-mode-ignorelist").toString().toLower().split(",", QString::SkipEmptyParts);
201 		if (excl.indexOf(jid().bare()) == -1) {
202 			ui_.le_jid->insertItem(0, "auto", jid().full());
203 			ui_.le_jid->setCurrentIndex(0);
204 		} else {
205 			autoSelectContact_ = false;
206 		}
207 	}
208 	connect(ui_.le_jid, SIGNAL(activated(int)), this, SLOT(contactChanged()));
209 	UserListItem *ul = account()->findFirstRelevant(jid());
210 	if (!ul || !ul->isPrivate()) {
211 		act_autojid = new IconAction(this);
212 		updateAutojidIcon();
213 		connect(act_autojid, SIGNAL(triggered()), SLOT(doSwitchJidMode()));
214 		le_autojid->addAction(act_autojid);
215 
216 		QAction *act_copy_user_jid = new QAction(tr("Copy user JID"), this);
217 		le_autojid->addAction(act_copy_user_jid);
218 		connect(act_copy_user_jid, SIGNAL(triggered()), SLOT(copyUserJid()));
219 	}
220 
221 	ui_.lb_ident->setAccount(account());
222 	ui_.lb_ident->setShowJid(false);
223 
224 	PsiToolTip::install(ui_.lb_status);
225 	ui_.lb_status->setPsiIcon(IconsetFactory::iconPtr("status/noauth"));
226 	setTabIcon(IconsetFactory::iconPtr("status/noauth")->icon());
227 
228 	ui_.tb_emoticons->setIcon(IconsetFactory::icon("psi/smile").icon());
229 
230 	connect(ui_.mle, SIGNAL(textEditCreated(QTextEdit*)), SLOT(chatEditCreated()));
231 	chatEditCreated();
232 
233 #ifdef Q_OS_MAC
234 	//connect(chatView(), SIGNAL(selectionChanged()), SLOT(logSelectionChanged()));
235 #endif
236 
237 	initToolButtons();
238 	initToolBar();
239 	updateAvatar();
240 
241 	PsiToolTip::install(ui_.avatar);
242 
243 	UserListItem* u = account()->findFirstRelevant(jid());
244 	if (u && u->isSecure(jid().resource())) {
245 		setPGPEnabled(true);
246 	}
247 
248 	connect(account()->avatarFactory(), SIGNAL(avatarChanged(const Jid&)), this, SLOT(updateAvatar(const Jid&)));
249 
250 	pm_settings_ = new QMenu(this);
251 	connect(pm_settings_, SIGNAL(aboutToShow()), SLOT(buildMenu()));
252 	ui_.tb_actions->setMenu(pm_settings_);
253 	ui_.tb_actions->setIcon(IconsetFactory::icon("psi/select").icon());
254 	ui_.tb_actions->setStyleSheet(" QToolButton::menu-indicator { image:none } ");
255 
256 	connect(account()->client()->capsManager(), SIGNAL(capsChanged(const Jid&)), SLOT(capsChanged(const Jid&)));
257 
258 	QList<int> list;
259 	list << 324;
260 	list << 96;
261 	ui_.splitter->setSizes(list);
262 
263 	smallChat_ = PsiOptions::instance()->getOption("options.ui.chat.use-small-chats").toBool();
264  	ui_.pb_send->setIcon(IconsetFactory::icon("psi/action_button_send").icon());
265 	connect(ui_.pb_send, SIGNAL(clicked()), this, SLOT(doSend()));
266 
267 	act_mini_cmd_ = new QAction(this);
268 	act_mini_cmd_->setText(tr("Input command..."));
269 	connect(act_mini_cmd_, SIGNAL(triggered()), SLOT(doMiniCmd()));
270 	addAction(act_mini_cmd_);
271 
272 	ui_.log->realTextWidget()->installEventFilter(this);
273 	ui_.mini_prompt->hide();
274 
275 	if (throbber_icon == 0) {
276 		throbber_icon = (PsiIcon *)IconsetFactory::iconPtr("psi/throbber");
277 	}
278 #ifdef PSI_PLUGINS
279 	PluginManager::instance()->setupChatTab(this, account(), jid().full());
280 #endif
281 }
282 
updateCountVisibility()283 void PsiChatDlg::updateCountVisibility()
284 {
285 	if (PsiOptions::instance()->getOption("options.ui.message.show-character-count").toBool() && !smallChat_) {
286 		ui_.lb_count->show();
287 	}
288 	else {
289 		ui_.lb_count->hide();
290 	}
291 }
292 
setLooks()293 void PsiChatDlg::setLooks()
294 {
295 	ChatDlg::setLooks();
296 
297 	const QString css = PsiOptions::instance()->getOption("options.ui.chat.css").toString();
298 	if (!css.isEmpty()) {
299 		setStyleSheet(css);
300 		chatEdit()->setCssString(css);
301 	}
302 	ui_.splitter->optionsChanged();
303 	ui_.mle->optionsChanged();
304 
305 	int s = PsiIconset::instance()->system().iconSize();
306 	ui_.lb_status->setFixedSize(s,s);
307 	ui_.lb_client->setFixedSize(s,s);
308 
309 	ui_.tb_pgp->hide();
310 	if (smallChat_) {
311 		ui_.lb_status->hide();
312 		ui_.le_jid->hide();
313 		ui_.tb_actions->hide();
314 		ui_.tb_emoticons->hide();
315 		ui_.toolbar->hide();
316 		ui_.tb_voice->hide();
317 		ui_.lb_client->hide();
318 	}
319 	else {
320 		ui_.lb_client->show();
321 		ui_.lb_status->show();
322 		ui_.le_jid->show();
323 		if (PsiOptions::instance()->getOption("options.ui.contactlist.toolbars.m0.visible").toBool()) {
324 			ui_.toolbar->show();
325 			ui_.tb_actions->hide();
326 			ui_.tb_emoticons->hide();
327 			ui_.tb_voice->hide();
328 		}
329 		else {
330 			ui_.toolbar->hide();
331 			ui_.tb_emoticons->setVisible(PsiOptions::instance()->getOption("options.ui.emoticons.use-emoticons").toBool());
332 			ui_.tb_actions->show();
333 			ui_.tb_voice->setVisible(AvCallManager::isSupported());
334 		}
335 	}
336 
337 	updateIdentityVisibility();
338 	updateCountVisibility();
339 	updateContactAdding();
340 
341 	// toolbuttons
342 	QIcon i;
343 	i.addPixmap(IconsetFactory::icon("psi/cryptoNo").impix(),  QIcon::Normal, QIcon::Off);
344 	i.addPixmap(IconsetFactory::icon("psi/cryptoYes").impix(), QIcon::Normal, QIcon::On);
345 	actions_->action("chat_pgp")->setPsiIcon(0);
346 	actions_->action("chat_pgp")->setIcon(i);
347 }
348 
setShortcuts()349 void PsiChatDlg::setShortcuts()
350 {
351 	ChatDlg::setShortcuts();
352 
353 	actions_->action("chat_clear")->setShortcuts(ShortcutManager::instance()->shortcuts("chat.clear"));
354 // typeahead find bar
355 	actions_->action("chat_find")->setShortcuts(ShortcutManager::instance()->shortcuts("chat.find"));
356 // -- typeahead
357 	actions_->action("chat_info")->setShortcuts(ShortcutManager::instance()->shortcuts("common.user-info"));
358 	actions_->action("chat_history")->setShortcuts(ShortcutManager::instance()->shortcuts("common.history"));
359 
360 	act_mini_cmd_->setShortcuts(ShortcutManager::instance()->shortcuts("chat.quick-command"));
361 
362 }
363 
updateIdentityVisibility()364 void PsiChatDlg::updateIdentityVisibility()
365 {
366 	if (!smallChat_) {
367 		bool visible = account()->psi()->contactList()->enabledAccounts().count() > 1;
368 		ui_.lb_ident->setVisible(visible);
369 	}
370 	else {
371 		ui_.lb_ident->setVisible(false);
372 	}
373 
374 	if (PsiOptions::instance()->getOption("options.ui.disable-send-button").toBool()) {
375 		ui_.pb_send->hide();
376 	}
377 	else {
378 		ui_.pb_send->show();
379 	}
380 }
381 
updateContactAdding(PsiContact * c)382 void PsiChatDlg::updateContactAdding(PsiContact* c)
383 {
384 	if (!c || realJid().compare(c->jid(), false)) {
385 		Jid rj = realJid();
386 		UserListItem *uli;
387 		if (rj.isNull() || ((uli = account()->findFirstRelevant(rj)) && (uli->inList() || uli->isSelf()))) {
388 			actions_->action("chat_add_contact")->setVisible(false);
389 		} else {
390 			actions_->action("chat_add_contact")->setVisible(true);
391 		}
392 	}
393 }
394 
updateToolbuttons()395 void PsiChatDlg::updateToolbuttons()
396 {
397 	ui_.toolbar->clear();
398 	PsiOptions *options = PsiOptions::instance();
399 	QStringList actionsNames = options->getOption("options.ui.contactlist.toolbars.m0.actions").toStringList();
400 	foreach (const QString &actionName, actionsNames) {
401 		if (actionName == "chat_voice" && !AvCallManager::isSupported()) {
402 			continue;
403 		}
404 		if (actionName == "chat_pgp" && !options->getOption("options.pgp.enable").toBool()) {
405 			continue;
406 		}
407 
408 #ifdef PSI_PLUGINS
409 		if (actionName.endsWith("-plugin")) {
410 			QString name = PluginManager::instance()->nameByShortName(actionName.mid(0, actionName.length() - 7));
411 			if (!name.isEmpty())
412 				PluginManager::instance()->addToolBarButton(this, ui_.toolbar, account(), jid().full(), name);
413 			continue;
414 		}
415 #endif
416 
417 		// Hack. separator action can be added only once.
418 		if (actionName == "separator") {
419 			ui_.toolbar->addSeparator();
420 			continue;
421 		}
422 
423 		IconAction *action = actions_->action(actionName);
424 		if (action) {
425 			action->addTo(ui_.toolbar);
426 			if (actionName == "chat_icon") {
427 				((QToolButton *)ui_.toolbar->widgetForAction(action))->setPopupMode(QToolButton::InstantPopup);
428 			}
429 		}
430 	}
431 
432 }
433 
copyUserJid()434 void PsiChatDlg::copyUserJid()
435 {
436 	QApplication::clipboard()->setText(jid().bare());
437 }
438 
updateContactAdding(const Jid & j)439 void PsiChatDlg::updateContactAdding(const Jid &j)
440 {
441 	if (realJid().compare(j, false)) {
442 		updateContactAdding();
443 	}
444 }
445 
initToolButtons()446 void PsiChatDlg::initToolButtons()
447 {
448 // typeahead find
449 	QHBoxLayout *hb3a = new QHBoxLayout();
450 	typeahead_ = new TypeAheadFindBar(ui_.log->textWidget(), tr("Find toolbar"), 0);
451 	hb3a->addWidget( typeahead_ );
452 	ui_.vboxLayout1->addLayout(hb3a);
453 // -- typeahead
454 
455 	ActionList* list = account()->psi()->actionList()->actionLists(PsiActionList::Actions_Chat).at(0);
456 	foreach (const QString &name, list->actions()) {
457 		IconAction *action = list->action(name)->copy();
458 		action->setParent(this);
459 		actions_->addAction(name, action);
460 		if (name == "chat_clear") {
461 			connect(action, SIGNAL(triggered()), SLOT(doClearButton()));
462 		}
463 		else if (name == "chat_find") {
464 			// typeahead find
465 			connect(action, SIGNAL(triggered()), typeahead_, SLOT(toggleVisibility()));
466 		// -- typeahead
467 		}
468 		else if (name == "chat_html_text") {
469 			connect(action, SIGNAL(triggered()), chatEdit(), SLOT(doHTMLTextMenu()));
470 		}
471 		else if (name == "chat_add_contact") {
472 			connect(action, SIGNAL(triggered()), SLOT(addContact()));
473 		}
474 		else if (name == "chat_icon") {
475 			connect(account()->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), this, SLOT(addEmoticon(QString)));
476 			action->setMenu(account()->psi()->iconSelectPopup());
477 			ui_.tb_emoticons->setMenu(account()->psi()->iconSelectPopup());
478 		}
479 		else if (name == "chat_voice") {
480 			connect(action, SIGNAL(triggered()), SLOT(doVoice()));
481 			//act_voice_->setEnabled(false);
482 			ui_.tb_voice->setDefaultAction(actions_->action("chat_voice"));
483 		}
484 		else if (name == "chat_file") {
485 			connect(action, SIGNAL(triggered()), SLOT(doFile()));
486 		}
487 		else if (name == "chat_pgp") {
488 			ui_.tb_pgp->setDefaultAction(actions_->action("chat_pgp"));
489 			connect(action, SIGNAL(triggered(bool)), SLOT(actPgpToggled(bool)));
490 		}
491 		else if (name == "chat_info") {
492 			connect(action, SIGNAL(triggered()), SLOT(doInfo()));
493 		}
494 		else if (name == "chat_history") {
495 			connect(action, SIGNAL(triggered()), SLOT(doHistory()));
496 		}
497 		else if (name == "chat_compact") {
498 			connect(action, SIGNAL(triggered()), SLOT(toggleSmallChat()));
499 		}
500 		else if (name == "chat_active_contacts") {
501 			connect(action, SIGNAL(triggered()), SLOT(actActiveContacts()));
502 		}
503 	}
504 
505 	list = account()->psi()->actionList()->actionLists(PsiActionList::Actions_Common).at(0);
506 	foreach (const QString &name, list->actions()) {
507 		IconAction *action = list->action(name)->copy();
508 		action->setParent(this);
509 		actions_->addAction(name, action);
510 	}
511 }
512 
initToolBar()513 void PsiChatDlg::initToolBar()
514 {
515 	ui_.toolbar->setWindowTitle(tr("Chat Toolbar"));
516 	int s = PsiIconset::instance()->system().iconSize();
517 	ui_.toolbar->setIconSize(QSize(s, s));
518 
519 	updateToolbuttons();
520 }
521 
contextMenuEvent(QContextMenuEvent *)522 void PsiChatDlg::contextMenuEvent(QContextMenuEvent *)
523 {
524 	pm_settings_->exec(QCursor::pos());
525 }
526 
capsChanged()527 void PsiChatDlg::capsChanged()
528 {
529 	ChatDlg::capsChanged();
530 
531 	QString resource = jid().resource();
532 	UserListItem *ul = account()->findFirstRelevant(jid());
533 	if (resource.isEmpty() && ul && !ul->userResourceList().isEmpty()) {
534 		resource = (*(ul->userResourceList().priority())).name();
535 	}
536 	//act_voice_->setEnabled(!account()->capsManager()->isEnabled() || (ul && ul->isAvailable() && account()->capsManager()->features(jid().withResource(resource)).canVoice()));
537 }
538 
activated()539 void PsiChatDlg::activated()
540 {
541 	ChatDlg::activated();
542 
543 	updateCountVisibility();
544 }
545 
setContactToolTip(QString text)546 void PsiChatDlg::setContactToolTip(QString text)
547 {
548 	ui_.lb_status->setToolTip(text);
549 	ui_.avatar->setToolTip(text);
550 }
551 
updateJidWidget(const QList<UserListItem * > & ul,int status,bool fromPresence)552 void PsiChatDlg::updateJidWidget(const QList<UserListItem*> &ul, int status, bool fromPresence)
553 {
554 	static bool internal_change = false;
555 	if (!internal_change) {
556 		// Filling jid's combobox
557 		const UserListItem *u = ul.first();
558 		if (!u)
559 			return;
560 		UserResourceList resList = u->userResourceList();
561 		const QString name = u->name();
562 		QComboBox *jidCombo = ui_.le_jid;
563 		if (!u->isPrivate()) {
564 			// If no conference private chat
565 			const int combo_idx = jidCombo->currentIndex();
566 			Jid old_jid = (combo_idx != -1) ? Jid(jidCombo->itemData(combo_idx).toString()) : Jid();
567 			//if (fromPresence || jid() != old_jid) {
568 				bool auto_mode = autoSelectContact_;
569 				Jid new_auto_jid = jid();
570 				if (auto_mode) {
571 					if (fromPresence && !resList.isEmpty()) {
572 						UserResourceList::ConstIterator it = resList.priority();
573 						new_auto_jid = jid().withResource((*it).name());
574 					}
575 				}
576 				// Filling address combobox
577 				QString iconStr;
578 				const int resCnt = resList.size();
579 				if (resCnt == 1) {
580 					UserResourceList::ConstIterator it = resList.begin();
581 					if (it != resList.end() && (*it).name().isEmpty()) {
582 						// Empty resource,  but online. Transport?
583 						QString client(u->findClient(*it));
584 						if (!client.isEmpty()) {
585 							iconStr = "clients/" + client;
586 						}
587 					}
588 				}
589 				setJidComboItem(0, makeContactName(name, u->jid().bare()), u->jid().bare(), iconStr);
590 				int new_index = -1;
591 				int curr_index = 1;
592 				for (UserResourceList::ConstIterator it = resList.begin(); it != resList.end(); it++) {
593 					UserResource r = *it;
594 					if (!r.name().isEmpty()) {
595 						Jid tmp_jid(u->jid().withResource(r.name()));
596 						QString client(u->findClient(r));
597 						QString iconStr2;
598 						if (!client.isEmpty()) {
599 							iconStr2 = "clients/" + client;
600 						}
601 						setJidComboItem(curr_index, makeContactName(name, tmp_jid), tmp_jid, iconStr2);
602 						if (new_index == -1 && tmp_jid == new_auto_jid) {
603 							new_index = curr_index;
604 						}
605 						curr_index++;
606 					}
607 				}
608 				if (new_index == -1) {
609 					new_index = 0;
610 					if (autoSelectContact_) {
611 						new_auto_jid = jid().bare();
612 					} else {
613 						if (!jid().resource().isEmpty()) {
614 							new_index = jidCombo->count();
615 							setJidComboItem(curr_index, makeContactName(name, jid()), jid(), iconStr);
616 							new_index = curr_index++;
617 						}
618 					}
619 				}
620 				// Сlean combobox's tail
621 				while (curr_index < jidCombo->count())
622 					jidCombo->removeItem(curr_index);
623 
624 				ui_.le_jid->setCurrentIndex(new_index);
625 				if (new_auto_jid != jid()) {
626 					internal_change = true;
627 					setJid(new_auto_jid);
628 					if (old_jid != new_auto_jid) {
629 						if (autoSelectContact_ && (status != XMPP::Status::Offline || !new_auto_jid.resource().isEmpty())) {
630 							appendSysMsg(tr("Contact has been switched: %1").arg(TextUtil::escape(JIDUtil::toString(new_auto_jid, true))));
631 						}
632 					}
633 				}
634 			//}
635 		} else {
636 			// Conference private chat
637 			QString iconStr;
638 			Jid tmp_jid = jid();
639 			UserResourceList::ConstIterator it = resList.begin();
640 			if (it != resList.end()) {
641 				QString client(u->findClient(*it));
642 				if (!client.isEmpty()) {
643 					iconStr = "clients/" + client;
644 				}
645 				tmp_jid = u->jid().withResource((*it).name());
646 			} else if (jidCombo->count() > 0) {
647 				tmp_jid = Jid(jidCombo->itemData(0).toString());
648 			}
649 			if (tmp_jid.isValid()) {
650 				setJidComboItem(0, makeContactName(name, tmp_jid), tmp_jid, iconStr);
651 			}
652 			// Clean combobox's tail
653 			while (jidCombo->count() > 1)
654 				jidCombo->removeItem(1);
655 			//-
656 			jidCombo->setCurrentIndex(0);
657 		}
658 		jidCombo->setToolTip(jidCombo->currentText());
659 	}
660 	internal_change = false;
661 }
662 
actActiveContacts()663 void PsiChatDlg::actActiveContacts()
664 {
665 	ActiveContactsMenu* acm = new ActiveContactsMenu(account()->psi(), this);
666 	if(!acm->actions().isEmpty())
667 		acm->exec(QCursor::pos());
668 	delete acm;
669 }
670 
contactUpdated(UserListItem * u,int status,const QString & statusString)671 void PsiChatDlg::contactUpdated(UserListItem* u, int status, const QString& statusString)
672 {
673 	Q_UNUSED(statusString);
674 
675 	if (status == -1 || !u) {
676 		ui_.lb_status->setPsiIcon(IconsetFactory::iconPtr("status/noauth"));
677 		setTabIcon(IconsetFactory::iconPtr("status/noauth")->icon());
678 	}
679 	else {
680 		ui_.lb_status->setPsiIcon(PsiIconset::instance()->statusPtr(jid(), status));
681 		setTabIcon(PsiIconset::instance()->statusPtr(jid(), status)->icon());
682 	}
683 
684 	if (u) {
685 		setContactToolTip(u->makeTip(true, false));
686 	}
687 	else {
688 		setContactToolTip(QString());
689 	}
690 
691 	if (u) {
692 		UserResourceList srl = u->userResourceList();
693 		if(!srl.isEmpty()) {
694 			UserResource r;
695 			if(!jid().resource().isEmpty()) {
696 				QString res = jid().resource();
697 				UserResourceList::ConstIterator it = srl.find(res);
698 				if(it != srl.end())
699 					r = *it;
700 			}
701 			if(r.clientName().isEmpty()) {
702 				srl.sort();
703 				r = srl.first();
704 			}
705 			QString client(u->findClient(r));
706 			if (!client.isEmpty()) {
707 				const QPixmap &pix = IconsetFactory::iconPixmap("clients/" + client );
708 				ui_.lb_client->setPixmap(pix);
709 			}
710 			ui_.lb_client->setToolTip(r.versionString());
711 		}
712 	}
713 }
714 
updateAvatar()715 void PsiChatDlg::updateAvatar()
716 {
717 	QString res;
718 	QString client;
719 
720 	if (!PsiOptions::instance()->getOption("options.ui.chat.avatars.show").toBool()) {
721 		ui_.avatar->hide();
722 		return;
723 	}
724 
725 	UserListItem *ul = account()->findFirstRelevant(jid());
726 	bool private_ = false;
727 	if (ul && !ul->userResourceList().isEmpty()) {
728 		UserResourceList::Iterator it = ul->userResourceList().find(jid().resource());
729 		if (it == ul->userResourceList().end())
730 			it = ul->userResourceList().priority();
731 
732 		res = (*it).name();
733 		client = (*it).clientName();
734 		private_ = ul->isPrivate();
735 	}
736 	//QPixmap p = account()->avatarFactory()->getAvatar(jid().withResource(res),client);
737 	QPixmap p = private_ ?
738 			account()->avatarFactory()->getMucAvatar(jid().withResource(res)) :
739 			account()->avatarFactory()->getAvatar(jid().withResource(res));
740 	if (p.isNull()) {
741 		if (!PsiOptions::instance()->getOption("options.ui.contactlist.avatars.use-default-avatar").toBool()) {
742 			ui_.avatar->hide();
743 			return;
744 		}
745 		p = IconsetFactory::iconPixmap("psi/default_avatar");
746 	}
747 	int optSize = PsiOptions::instance()->getOption("options.ui.chat.avatars.size").toInt();
748 	ui_.avatar->setFixedSize(optSize, optSize);
749 	int avatarSize = p.width(); //qMax(p.width(), p.height());
750 	if (avatarSize > optSize)
751 		avatarSize = optSize;
752 	ui_.avatar->setPixmap(p.scaled(QSize(avatarSize, avatarSize), Qt::KeepAspectRatio, Qt::SmoothTransformation));
753 	ui_.avatar->show();
754 }
755 
optionsUpdate()756 void PsiChatDlg::optionsUpdate()
757 {
758 	smallChat_ = PsiOptions::instance()->getOption("options.ui.chat.use-small-chats").toBool();
759 
760 	ChatDlg::optionsUpdate();
761 // typeahead find bar
762 	typeahead_->optionsUpdate();
763 }
764 
updatePGP()765 void PsiChatDlg::updatePGP()
766 {
767 	if (account()->hasPGP()) {
768 		actions_->action("chat_pgp")->setEnabled(true);
769 	}
770 	else {
771 		setPGPEnabled(false);
772 		actions_->action("chat_pgp")->setEnabled(false);
773 	}
774 
775 	checkPGPAutostart();
776 
777 	ui_.tb_pgp->setVisible(account()->hasPGP() &&
778 						   !smallChat_ &&
779 						   !PsiOptions::instance()->getOption("options.ui.contactlist.toolbars.m0.visible").toBool());
780 	ui_.log->setEncryptionEnabled(isEncryptionEnabled());
781 }
782 
checkPGPAutostart()783 void PsiChatDlg::checkPGPAutostart()
784 {
785 	if(account()->hasPGP() && autoPGP_ && PsiOptions::instance()->getOption("options.pgp.auto-start").toBool()) {
786 		UserListItem *item = account()->findFirstRelevant(jid());
787 		if(item && !item->publicKeyID().isEmpty()) {
788 			if(!jid().resource().isEmpty()) {
789 				UserResourceList::Iterator rit = item->userResourceList().find(jid().resource());
790 				if(rit !=item->userResourceList().end()) {
791 					UserResource r = *rit;
792 					if(r.pgpVerifyStatus() != 0) {
793 						setPGPEnabled(false);
794 						return;
795 					}
796 				}
797 			}
798 			setPGPEnabled(true);
799 		}
800 	}
801 }
802 
actPgpToggled(bool b)803 void PsiChatDlg::actPgpToggled(bool b)
804 {
805 	autoPGP_ = false;
806 	ui_.log->setEncryptionEnabled(b);
807 }
808 
doClearButton()809 void PsiChatDlg::doClearButton()
810 {
811 	if (PsiOptions::instance()->getOption("options.ui.chat.warn-before-clear").toBool()) {
812 		switch (
813 			QMessageBox::warning(
814 				this,
815 				tr("Warning"),
816 				tr("Are you sure you want to clear the chat window?\n(note: does not affect saved history)"),
817 				QMessageBox::Yes, QMessageBox::YesAll, QMessageBox::No
818 			)
819 		) {
820 		case QMessageBox::No:
821 			break;
822 		case QMessageBox::YesAll:
823 			PsiOptions::instance()->setOption("options.ui.chat.warn-before-clear", false);
824 			// fall-through
825 		case QMessageBox::Yes:
826 			doClear();
827 		}
828 	} else {
829 		doClear();
830 	}
831 }
832 
setPGPEnabled(bool enabled)833 void PsiChatDlg::setPGPEnabled(bool enabled)
834 {
835 	actions_->action("chat_pgp")->setChecked(enabled);
836 	ui_.log->setEncryptionEnabled(enabled);
837 }
838 
toggleSmallChat()839 void PsiChatDlg::toggleSmallChat()
840 {
841 	smallChat_ = !smallChat_;
842 	setLooks();
843 }
844 
buildMenu()845 void PsiChatDlg::buildMenu()
846 {
847 	// Dialog menu
848 	pm_settings_->clear();
849 	pm_settings_->addAction(actions_->action("chat_compact"));
850 	pm_settings_->addAction(actions_->action("chat_clear"));
851 	pm_settings_->addSeparator();
852 
853 	pm_settings_->addAction(actions_->action("chat_icon"));
854 	pm_settings_->addAction(actions_->action("chat_file"));
855 	if (AvCallManager::isSupported()) {
856 		pm_settings_->addAction(actions_->action("chat_voice"));
857 	}
858 	pm_settings_->addAction(actions_->action("chat_pgp"));
859 	pm_settings_->addSeparator();
860 
861 	pm_settings_->addAction(actions_->action("chat_info"));
862 	pm_settings_->addAction(actions_->action("chat_history"));
863 #ifdef PSI_PLUGINS
864 	if(!PsiOptions::instance()->getOption("options.ui.contactlist.toolbars.m0.visible").toBool()) {
865 		pm_settings_->addSeparator();
866 		PluginManager::instance()->addToolBarButton(this, pm_settings_, account(), jid().full());
867 	}
868 #endif
869 }
870 
updateCounter()871 void PsiChatDlg::updateCounter()
872 {
873 	ui_.lb_count->setNum(chatEdit()->toPlainText().length());
874 }
875 
isEncryptionEnabled() const876 bool PsiChatDlg::isEncryptionEnabled() const
877 {
878 	return actions_->action("chat_pgp")->isChecked();
879 }
880 
appendSysMsg(const QString & str)881 void PsiChatDlg::appendSysMsg(const QString &str)
882 {
883 	chatView()->dispatchMessage(MessageView::fromHtml(str, MessageView::System));
884 }
885 
chatView() const886 ChatView* PsiChatDlg::chatView() const
887 {
888 	return ui_.log;
889 }
890 
chatEdit() const891 ChatEdit* PsiChatDlg::chatEdit() const
892 {
893 	return ui_.mle->chatEdit();
894 }
895 
chatEditCreated()896 void PsiChatDlg::chatEditCreated()
897 {
898 	ChatDlg::chatEditCreated();
899 
900 	connect(chatEdit(), SIGNAL(textChanged()), this, SLOT(updateCounter()));
901 	chatEdit()->installEventFilter(this);
902 
903 	mCmdSite_.setInput(chatEdit());
904 	mCmdSite_.setPrompt(ui_.mini_prompt);
905 	tabCompletion.setTextEdit(chatEdit());
906 }
907 
908 
doSend()909 void PsiChatDlg::doSend() {
910 	tabCompletion.reset();
911 	if (mCmdSite_.isActive()) {
912 		QString str = chatEdit()->toPlainText();
913 		if (!mCmdManager_.processCommand(str)) {
914 			appendSysMsg(tr("Error: Can not parse command: ") + TextUtil::escape(str));
915 		}
916 	} else {
917 		ChatDlg::doSend();
918 	}
919 }
920 
doMiniCmd()921 void PsiChatDlg::doMiniCmd()
922 {
923 	mCmdManager_.open(new MCmdSimpleState(MCMDCHAT, tr("Command>")), QStringList() );
924 }
925 
addContact()926 void PsiChatDlg::addContact()
927 {
928 	Jid j(realJid());
929 	UserListItem *uli = account()->findFirstRelevant(jid());
930 	QString name = uli && !uli->name().isEmpty()? uli->name() : j.node();
931 	account()->openAddUserDlg(j.withResource(""), name.isEmpty()?j.node():name, "");
932 }
933 
eventFilter(QObject * obj,QEvent * ev)934 bool PsiChatDlg::eventFilter( QObject *obj, QEvent *ev ) {
935 	if ( obj == chatEdit() ) {
936 		if ( ev->type() == QEvent::KeyPress ) {
937 			QKeyEvent *e = (QKeyEvent *)ev;
938 
939 			if ( e->key() == Qt::Key_Tab ) {
940 				tabCompletion.tryComplete();
941 				return true;
942 			}
943 
944 			tabCompletion.reset();
945 		}
946 	}
947 
948 	else if ( obj == ui_.log->realTextWidget() ) {
949 		if ( ev->type() == QEvent::MouseButtonPress )
950 			chatEdit()->setFocus();
951 	}
952 
953 	return ChatDlg::eventFilter( obj, ev );
954 }
955 
956 
makeContactName(const QString & name,const Jid & jid) const957 QString PsiChatDlg::makeContactName(const QString &name, const Jid &jid) const
958 {
959 	QString name_;
960 	QString j = JIDUtil::toString(jid, true);
961 	if (!name.isEmpty())
962 		name_ = name + QString(" <%1>").arg(j);
963 	else
964 		name_ = j;
965 	return name_;
966 }
967 
contactChanged()968 void PsiChatDlg::contactChanged() /* current jid was chanegd in Jid combobox.TODO rename this func*/
969 {
970 	int curr_index = ui_.le_jid->currentIndex();
971 	Jid jid_(ui_.le_jid->itemData(curr_index).toString());
972 	if (jid_ != jid()) {
973 		autoSelectContact_ = false;
974 		setJid(jid_);
975 		updateAutojidIcon();
976 	}
977 }
978 
updateAutojidIcon()979 void PsiChatDlg::updateAutojidIcon()
980 {
981 	QIcon icon(IconsetFactory::iconPixmap("psi/autojid"));
982 	QPixmap pix;
983 	QString text;
984 	if (autoSelectContact_) {
985 		pix = icon.pixmap(QSize(16, 16), QIcon::Normal, QIcon::Off);
986 		text = tr("turn off autojid");
987 	} else {
988 		pix = icon.pixmap(QSize(16, 16), QIcon::Disabled, QIcon::Off);
989 		text = tr("turn on autojid");
990 	}
991 	act_autojid->setIcon(QIcon(pix));
992 	act_autojid->setText(text);
993 	act_autojid->setToolTip(text);
994 	act_autojid->setStatusTip(text);
995 }
996 
setJidComboItem(int pos,const QString & text,const Jid & jid,const QString & icon_str)997 void PsiChatDlg::setJidComboItem(int pos, const QString &text, const Jid &jid, const QString &icon_str)
998 {
999 	// Warning! If pos >= items count, the element will be added in a list tail
1000 	//-
1001 	QIcon icon;
1002 	QComboBox *jid_combo = ui_.le_jid;
1003 	if (!icon_str.isEmpty()) {
1004 		const PsiIcon picon = IconsetFactory::icon(icon_str);
1005 		icon = picon.icon();
1006 	}
1007 	if (jid_combo->count() > pos) {
1008 		jid_combo->setItemText(pos, text);
1009 		jid_combo->setItemData(pos, JIDUtil::toString(jid, true));
1010 		jid_combo->setItemIcon(pos, icon);
1011 	} else {
1012 		jid_combo->addItem(icon, text, JIDUtil::toString(jid, true));
1013 	}
1014 }
1015 
doSwitchJidMode()1016 void PsiChatDlg::doSwitchJidMode()
1017 {
1018 	autoSelectContact_ = ! autoSelectContact_;
1019 	updateAutojidIcon();
1020 	if (autoSelectContact_) {
1021 		const QList<UserListItem*> ul = account()->findRelevant(jid().bare());
1022 		UserStatus userStatus = userStatusFor(jid(), ul, false);
1023 		updateJidWidget(ul, userStatus.statusType, true);
1024 		userStatus = userStatusFor(jid(), ul, false);
1025 		contactUpdated(userStatus.userListItem, userStatus.statusType, userStatus.status);
1026 	}
1027 }
1028 
1029 #include "psichatdlg.moc"
1030