start porting to libotr4
[dyskinesia.git] / src / chatform.cpp
blob7e2a2681100c1c071783f1a754cff9e88f22c374
1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software. It comes without any warranty, to
5 * the extent permitted by applicable law. You can redistribute it
6 * and/or modify it under the terms of the Do What The Fuck You Want
7 * To Public License, Version 2, as published by Sam Hocevar. See
8 * http://sam.zoy.org/wtfpl/COPYING for more details.
10 * we are the Borg.
12 #include <ctype.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <unistd.h>
17 #include <dysversion.h>
19 //#include <QtCore>
20 #include <QDebug>
23 #ifdef WIN32
24 # include <QMessageBox>
25 #endif
28 #include <QApplication>
29 #include <QClipboard>
30 #include <QDateTime>
31 #include <QDir>
32 #include <QFile>
33 #include <QInputDialog>
34 //#include <QHttp>
35 #include <QMetaMethod>
36 #include <QMessageBox>
37 #include <QPainter>
38 #include <QProcess>
39 #include <QRegExp>
40 #include <QShortcut>
41 #include <QScrollBar>
42 #include <QTextCodec>
43 #include <QTextStream>
44 #include <QTcpSocket>
45 #include <QThread>
46 #include <QUrl>
47 #include <QWebFrame>
48 #include <QWebHitTestResult>
49 #include <QWebPage>
50 #include <QWebSettings>
52 #include "ghotkeyman.h"
54 #include "k8tpl.h"
55 #include "k8strutils.h"
56 #include "k8ascii85.h"
58 #include "settings.h"
59 #include "k8sdb.h"
60 #include "k8history.h"
62 #include "psycvars.h"
63 #include "psycpkt.h"
64 #include "psycproto.h"
66 #include "psyccontact.h"
68 #include "k8popups.h"
70 #include "authwin.h"
71 #include "accwin.h"
72 #include "conwin.h"
73 #ifdef USE_OTR
74 # include "genkeywin.h"
75 #endif
76 #include "pktwin.h"
77 #include "joinwin.h"
79 #include "chatform.h"
81 #include "eng_commands.h"
82 #include "eng_bindings.h"
83 #include "floatman.h"
84 #include "chatjsapi.h"
87 #define DYSKINESIA "Dyskinesia"
90 ///////////////////////////////////////////////////////////////////////////////
91 static const QString bool2js (bool b) {
92 if (b) return QString("true");
93 return QString("false");
97 static QString urlStr (const QString &spUrl) {
98 return "<a href='"+K8Str::escapeStr(spUrl)+"'>"+K8Str::escapeStr(spUrl)+"</a>";
102 static QString loadFile (const QString &fileName, bool *found=0) {
103 if (found) *found = false;
104 if (fileName.isEmpty()) return QString();
106 // try local file first
107 if (fileName[0] == ':' && fileName.length() > 1) {
108 QString fn(fileName);
109 if (fileName[1] == '/') fn.remove(0, 2); else fn.remove(0, 1);
110 bool ff;
111 QString r;
113 r = loadFile(settingsPrefsPath()+fn, &ff);
114 if (ff) {
115 if (found) *found = true;
116 return r;
119 r = loadFile(settingsAppPath()+fn, &ff);
120 if (ff) {
121 if (found) *found = true;
122 return r;
126 QFile file(fileName);
127 QString res;
128 if (file.open(QIODevice::ReadOnly)) {
129 QTextStream stream;
130 stream.setDevice(&file);
131 stream.setCodec("UTF-8");
132 res = stream.readAll();
133 file.close();
134 if (found) *found = true;
136 return res;
140 ///////////////////////////////////////////////////////////////////////////////
141 #include "chat_otr.cpp"
144 ///////////////////////////////////////////////////////////////////////////////
145 Option::Option (const QString &def, const QString &aHelp) {
146 mValid = parseDef(def);
147 help = aHelp;
151 bool Option::parseDef (const QString &def) {
152 QStringList pd(def.split(':'));
153 if (pd.count() < 4) return false;
154 // [0]: name
155 // [1]: type
156 // [2]: dbname
157 // [3]: default
158 // [4]: scope (can be omited)
160 //name
161 name = pd[0].trimmed();
162 if (name.isEmpty()) return false;
163 //type
164 QString tp(pd[1].trimmed().toLower());
165 if (tp == "bool" || tp == "boolean") type = K8SDB::Boolean;
166 else if (tp == "int" || tp == "integer") type = K8SDB::Integer;
167 else if (tp == "str" || tp == "string") type = K8SDB::String;
168 else return false;
169 //dbname
170 dbName = pd[2].trimmed();
171 if (dbName.isEmpty()) return false;
172 //default
173 defVal = pd[3].trimmed();
174 //scope
175 scope = Both; //default scope
176 if (pd.count() < 5) return true;
177 QString sc(pd[4].trimmed().toLower());
178 if (sc == "nolocal") scope = OnlyGlobal;
179 else if (sc == "onlylocal" || sc == "noglobal") scope = OnlyLocal;
180 else if (!sc.isEmpty()) return false;
181 return true;
185 ///////////////////////////////////////////////////////////////////////////////
186 NetworkAccessManager::NetworkAccessManager (QObject *parent) : QNetworkAccessManager(parent) {
190 QNetworkReply *NetworkAccessManager::createRequest (Operation op, const QNetworkRequest &req, QIODevice *outgoingData) {
191 QNetworkRequest newR(req);
192 if (newR.url().scheme().toLower() == "dys") {
193 QString pt(settingsHtmlPath());
194 pt.chop(1);
195 if (!pt.isEmpty() && pt[0] == '/') pt.remove(0, 1);
196 QString host;
197 QString path(newR.url().path());
198 QString p("file://localhost/"+pt+host+path);
199 newR.setUrl(QUrl(p));
201 QNetworkReply *res = QNetworkAccessManager::createRequest(op, newR, outgoingData);
202 return res;
206 ///////////////////////////////////////////////////////////////////////////////
207 EnterFilter::EnterFilter (QObject *parent) : QObject(parent), mCtrlDown(false) {
208 mForm = dynamic_cast<ChatForm *>(parent);
212 EnterFilter::~EnterFilter () {
216 bool EnterFilter::eventFilter (QObject *obj, QEvent *event) {
217 bool down;
219 switch (event->type()) {
220 case QEvent::KeyPress: down = true; break;
221 case QEvent::KeyRelease: down = false; break;
222 case QEvent::WindowActivate:
223 //qDebug() << "activated!";
224 mForm->mUnreadInChat = false;
225 if (mForm->mChatUser) {
226 mForm->mPopMan->removeById("", mForm->mChatUser->uni().toLower());
227 if (mForm->mFMan) mForm->mFMan->noMessages(mForm->mChatUser->uni());
229 mForm->checkBlink();
230 return false;
231 default: return false; // don't steal
234 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
235 if (keyEvent->key() == Qt::Key_Control) {
236 if (!down) {
237 if (mCtrlDown) mForm->switchTab(true);
239 mCtrlDown = down;
240 return false;
243 if (!down) return false;
245 if (keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_Tab) {
246 mForm->switchTab(false);
247 //mForm->switchTab(true);
248 return true;
251 //qDebug() << "key!";
252 QPlainTextEdit *e = dynamic_cast<QPlainTextEdit *>(obj);
253 if (mForm->processKeyCombo(keyEvent)) return true;
255 if (!e) return false;
256 //qDebug() << " EDITOR";
258 return false;
262 ///////////////////////////////////////////////////////////////////////////////
263 ChatForm::ChatForm (QWidget *parent) : QWidget(parent),
264 mConForm(0),
265 mTrayIcon(new QSystemTrayIcon(QIcon(MAIN_TRAY_ICON))),
266 mAccName(QString()),
267 mProto(new PsycProto(this)), mPassword(QString("")),
268 mMyStatus(PsycProto::Offline), mMyMood(PsycProto::Unspecified),
269 mChBlock(false),
270 mEFilter(new EnterFilter(this)),
271 mChatUser(0), mSelText(QString()),
272 mPopMan(0),
273 mPopPos(K8PopupManager::RightBottom),
274 mCloseWaiting(false), mServerStore(false),
275 mJSAPI(0), mCLJSAPI(0),
276 mSDB(0),
277 mBlinkTimer(new QTimer(this)),
278 mBlinkMsg(false), mTrayMsgNoteActive(false), mUnreadInChat(false),
279 mCListOnRight(false),
280 mDoTypo(true),
281 mChatLoaded(false), mCListLoaded(false),
282 mTabDown(false), mTabSwitchNo(-1),
283 mComboList(0)
285 #ifdef USE_OTR
286 mOTRState = 0;
287 mOTRTimer = new QTimer(this);
288 connect(mOTRTimer, SIGNAL(timeout()), this, SLOT(onOTRTimer()));
289 #endif
290 mFMan = 0;
292 mComboList = new KeyComboList;
293 mEditComboList = new KeyComboList;
294 mCurCombos = new KeyComboList;
296 mEngCmds = new EngineCommands(this);
297 mEngBinds = new EngineBindings(this);
299 mVerStr = QString("%1.%2.%3.%4").arg(DYS_VERSION_HI).arg(DYS_VERSION_MID).
300 arg(DYS_VERSION_LO).arg(DYS_BUILD);
302 setupUi(this);
303 setAttribute(Qt::WA_DeleteOnClose, true);
304 setAttribute(Qt::WA_QuitOnClose, true);
306 edChat->installEventFilter(mEFilter);
307 this->installEventFilter(mEFilter);
309 connect(this, SIGNAL(destroyed()), this, SLOT(windowDestroyed()));
311 mTrayIcon->setContextMenu(menu_tray);
312 connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(onTrayActivate(QSystemTrayIcon::ActivationReason)));
313 connect(mBlinkTimer, SIGNAL(timeout()), this, SLOT(onTrayBlink()));
315 loadRecodeMap();
317 QDir d;
318 d.mkdir(settingsPrefsPath());
320 QString css = loadFile(":/html/qss/default.qss");
321 css = css.trimmed();
322 if (!css.isEmpty()) qApp->setStyleSheet(css);
323 //DEBUG//
325 mConForm = new ConsoleForm(this);
326 connect(mConForm, SIGNAL(destroyed()), this, SLOT(consoleDestroyed()));
327 mConForm->cbActive->setChecked(true);
328 mConForm->hide();
330 //DEBUG//
332 NetworkAccessManager *mm = new NetworkAccessManager(this);
333 webChat->page()->setNetworkAccessManager(mm);
334 webChat->setAllowContextMenu(false);
335 webCList->page()->setNetworkAccessManager(mm);
337 QWebFrame *fr = webChat->page()->mainFrame();
338 mJSAPI = new ChatJSAPI(fr, this);
339 connect(fr, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(javaScriptWindowObjectCleared()));
341 fr = webCList->page()->mainFrame();
342 mCLJSAPI = new ChatJSAPI(fr, this);
343 connect(fr, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(javaScriptWindowObjectCleared()));
345 connect(webChat, SIGNAL(webContextMenu(const QWebHitTestResult &, const QPoint &)), this, SLOT(onWebContextMenuNone(const QWebHitTestResult &, const QPoint &)));
346 connect(webCList, SIGNAL(webContextMenu(const QWebHitTestResult &, const QPoint &)), this, SLOT(onWebContextMenuNone(const QWebHitTestResult &, const QPoint &)));
348 connect(webChat, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinishedChat(bool)));
349 connect(webCList, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinishedCList(bool)));
351 connect(webChat, SIGNAL(linkClicked(const QUrl &)), this, SLOT(onLinkClicked(const QUrl &)));
352 connect(webCList, SIGNAL(linkClicked(const QUrl &)), this, SLOT(onLinkClicked(const QUrl &)));
354 mProto->setUserAgent("Dyskinesia/"+mVerStr);
355 connect(mProto, SIGNAL(packetComes(const PsycPacket &)), this, SLOT(onPacket(const PsycPacket &)));
356 connect(mProto, SIGNAL(packetSent(const PsycPacket &)), this, SLOT(onPacketSent(const PsycPacket &)));
357 connect(mProto, SIGNAL(netError(const QString &)), this, SLOT(onNetError(const QString &)));
358 connect(mProto, SIGNAL(connected()), this, SLOT(onConnected()));
359 connect(mProto, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
360 connect(mProto, SIGNAL(needPassword()), this, SLOT(onNeedPassword()));
361 connect(mProto, SIGNAL(loginComplete()), this, SLOT(onLoginComplete()));
362 connect(mProto, SIGNAL(invalidPassword()), this, SLOT(onInvalidPassword()));
363 connect(mProto, SIGNAL(authRequested(const PsycEntity &)), this, SLOT(onAuthRequested(const PsycEntity &)));
364 connect(mProto, SIGNAL(authGranted(const PsycEntity &)), this, SLOT(onAuthGranted(const PsycEntity &)));
365 connect(mProto, SIGNAL(authDenied(const PsycEntity &)), this, SLOT(onAuthDenied(const PsycEntity &)));
366 connect(mProto, SIGNAL(message(const PsycEntity &, const PsycEntity &, const QDateTime &, const QString &, const QString &)), this, SLOT(onMessage(const PsycEntity &, const PsycEntity &, const QDateTime &, const QString &, const QString &)));
367 connect(mProto, SIGNAL(messageEcho(const PsycEntity &, const PsycEntity &, const PsycEntity &, const QDateTime &, const QString &, const QString &, const QString &)), this, SLOT(onMessageEcho(const PsycEntity &, const PsycEntity &, const PsycEntity &, const QDateTime &, const QString &, const QString &, const QString &)));
368 connect(mProto, SIGNAL(historyMessage(const PsycEntity &, const PsycEntity &, const QDateTime &, const QString &, const QString &)), this, SLOT(onHistoryMessage(const PsycEntity &, const PsycEntity &, const QDateTime &, const QString &, const QString &)));
370 connect(mProto, SIGNAL(scratchpadUpdated(const QString &)), this, SLOT(onScratchpadUpdated(const QString &)));
371 connect(mProto, SIGNAL(placeWebExamined(const PsycEntity &, const PsycPacket &)), this, SLOT(onPlaceWebExam(const PsycEntity &, const PsycPacket &)));
372 connect(mProto, SIGNAL(placeNews(const PsycEntity &, const QString &, const QString &, const QString &)), this, SLOT(onPlaceNews(const PsycEntity &, const QString &, const QString &, const QString &)));
374 connect(mProto, SIGNAL(noticeUpdate(const PsycEntity &, const PsycPacket &)), this, SLOT(onNoticeUpdate(const PsycEntity &, const PsycPacket &)));
376 connect(mProto, SIGNAL(userStatusChanged(const PsycEntity &)), this, SLOT(onUserStatusChanged(const PsycEntity &)));
377 connect(mProto, SIGNAL(selfStatusChanged()), this, SLOT(onSelfStatusChanged()));
378 connect(mProto, SIGNAL(notificationType(const PsycEntity &)), this, SLOT(onNotificationType(const PsycEntity &)));
380 connect(mProto, SIGNAL(placeEntered(const PsycEntity &)), this, SLOT(onPlaceEntered(const PsycEntity &)));
381 connect(mProto, SIGNAL(placeLeaved(const PsycEntity &)), this, SLOT(onPlaceLeaved(const PsycEntity &)));
382 connect(mProto, SIGNAL(userEntersPlace(const PsycEntity &, const PsycEntity &)), this, SLOT(onUserEntersPlace(const PsycEntity &, const PsycEntity &)));
383 connect(mProto, SIGNAL(userLeavesPlace(const PsycEntity &, const PsycEntity &)), this, SLOT(onUserLeavesPlace(const PsycEntity &, const PsycEntity &)));
385 //connect(splitter0, SIGNAL(splitterMoved(int, int)), this, SLOT(splitterMoved(int, int)));
386 //connect(splitter1, SIGNAL(splitterMoved(int, int)), this, SLOT(splitterMoved(int, int)));
388 btStatus->setMenu(menu_status);
389 btMainMenu->setMenu(menu_main);
391 //connect(mSaveTimer, SIGNAL(timeout()), this, SLOT(doAutosave()));
393 //mPopMan = new K8PopupManager(300, 120, 6, mPopPos);
394 //connect(mPopMan, SIGNAL(click(const QString &, const QString &, Qt::MouseButton)), this, SLOT(onPopupClick(const QString &, const QString &, Qt::MouseButton)));
395 recreatePopMan();
397 loadAccount();
399 edChat->setTabChangesFocus(false);
403 void ChatForm::onGShortcutShow () {
404 if (isMinimized() || !isVisible()) {
405 showNormal();
406 activateWindow();
407 } else hide();
411 void ChatForm::onGShortcutHidePopups () {
412 mPopMan->removeAll();
416 ChatForm::~ChatForm () {
417 GHotKeyMan::instance()->clear();
419 if (mProto->isLoggedIn()) storeAppData();
420 if (mProto->isLinked()) mProto->logoff();
422 if (mSDB) {
423 QRect geo(geometry());
424 mSDB->set("/chatwindow/x", geo.x());
425 mSDB->set("/chatwindow/y", geo.y());
426 mSDB->set("/chatwindow/w", geo.width());
427 mSDB->set("/chatwindow/h", geo.height());
428 mSDB->set("/chatwindow/saved", true);
429 if (mConForm) {
430 QRect geo(mConForm->geometry());
431 mSDB->set("/pktconsolewindow/x", geo.x());
432 mSDB->set("/pktconsolewindow/y", geo.y());
433 mSDB->set("/pktconsolewindow/w", geo.width());
434 mSDB->set("/pktconsolewindow/h", geo.height());
435 mSDB->set("/pktconsolewindow/active", mConForm->cbActive->isChecked());
436 mSDB->set("/pktconsolewindow/saved", true);
439 //doAutosave();
440 clear();
441 clearOptionList();
442 clearBindings();
443 delete mPopMan;
444 mTrayIcon->hide();
446 delete mComboList;
447 delete mEditComboList;
448 delete mCurCombos;
450 #ifdef USE_OTR
451 dlogfDeinit();
452 #endif
456 void ChatForm::windowDestroyed () {
457 if (mProto->isLoggedIn()) storeAppData();
458 if (mProto->isLinked()) mProto->logoff();
459 //doAutosave();
463 void ChatForm::consoleDestroyed () {
464 mConForm = 0;
468 void ChatForm::recreatePopMan () {
469 delete mPopMan;
470 mPopMan = new K8PopupManager(300, 120, 6, mPopPos);
471 connect(mPopMan, SIGNAL(click(const QString &, const QString &, Qt::MouseButton)), this, SLOT(onPopupClick(const QString &, const QString &, Qt::MouseButton)));
475 void ChatForm::javaScriptWindowObjectCleared () {
476 //qDebug() << "***";
477 webChat->page()->mainFrame()->addToJavaScriptWindowObject("api", mJSAPI);
478 webCList->page()->mainFrame()->addToJavaScriptWindowObject("api", mCLJSAPI);
482 void ChatForm::runChatJS (const QString &js) {
483 QWebFrame *fr = webChat->page()->mainFrame();
484 fr->evaluateJavaScript(js);
488 void ChatForm::runCListJS (const QString &js) {
489 QWebFrame *fr = webCList->page()->mainFrame();
490 fr->evaluateJavaScript(js);
494 void ChatForm::clear () {
495 mChatUser = 0;
496 disconnectAllOTR();
497 if (mProto->isLoggedIn()) storeAppData();
498 if (mProto->isLinked()) mProto->logoff();
499 delete mSDB; mSDB = 0;
500 mProto->clear();
501 foreach (PsycContact *cc, mContactList) delete cc;
502 mContactList.clear();
503 mTabList.clear();
504 clearCList();
505 clearChat();
506 #ifdef USE_OTR
507 mOTRTimer->stop();
508 if (mOTRState) otrl_userstate_free(mOTRState);
509 mOTRState = 0;
510 #endif
514 void ChatForm::showPopup (const PsycEntity &place, const PsycEntity &from,
515 const QString &type, const QString &id, const QString &msg, const QString &action, bool update)
517 //qDebug() << "showPopup:" << from.uni() << msg << action;
518 mPopMan->setFloatHeight(toBoolOpt("/popup/dynsize", true));
520 QString pt(settingsHtmlPath()+"popups/"+type);
521 mPopMan->addType(type, loadFile(pt+"/style.qss"));
522 K8Template tpl;
523 QString fn(pt);
524 if (action.trimmed().isEmpty()) fn += "/template.html";
525 else fn += "/template_action.html";
526 tpl.loadFromFile(fn);
527 tpl.setVar("uni", K8Popups::escapeStr(from.uni()));
528 tpl.setVar("nick", K8Popups::escapeStr(from.nick()));
529 tpl.setVar("verbatim", K8Popups::escapeStr(from.verbatim()));
530 tpl.setVar("placeuni", K8Popups::escapeStr(place.uni()));
531 tpl.setVar("placenick", K8Popups::escapeStr(place.nick()));
532 tpl.setVar("placeverbatim", K8Popups::escapeStr(place.verbatim()));
533 tpl.setVar("action", K8Popups::escapeStr(action));
534 if (!msg.isEmpty() && msg[0] == '\x1') pt = msg.mid(1);
535 else {
536 pt = K8Popups::escapeStr(msg.trimmed()); pt.replace("\n", "<br />");
538 tpl.setVar("text", pt);
539 pt = tpl.result();
540 if (update) mPopMan->updateMessage(type, id, pt);
541 else mPopMan->showMessage(type, id, pt);
545 void ChatForm::showPrivatePopup (const PsycEntity &from, const QString &msg, const QString &action) {
546 if (!from.isValid()) return;
547 if (!toBoolOpt("/popup/active", true, from.uni())) return;
548 if (!toBoolOpt("/notifications/message", true, from.uni())) return;
549 PsycEntity place;
550 PsycEntity fr(from);
551 PsycContact *cc = findContact(from);
552 /*if (cc && cc->isHidden() && !toBoolOpt("/contactlist/showhidden", false)) return;*/
553 if (cc) fr.setVerbatim(cc->verbatim());
554 showPopup(place, fr, "private", fr.uni().toLower(), msg, action);
558 void ChatForm::showPublicPopup (const PsycEntity &place, const PsycEntity &from, const QString &msg, const QString &action) {
559 if (!from.isValid() || !place.isValid()) return;
560 if (!toBoolOpt("/popup/active", true, from.uni())) return;
561 if (!toBoolOpt("/popup/active", true, place.uni())) return;
562 if (!toBoolOpt("/notifications/message", true, from.uni())) return;
563 if (!toBoolOpt("/notifications/message", true, place.uni())) return;
564 PsycEntity plc(place);
565 PsycContact *cc = findContact(place);
566 if (cc && cc->isHidden() && !toBoolOpt("/contactlist/showhidden", false)) return;
567 if (cc) plc.setVerbatim(cc->verbatim());
568 PsycEntity fr(from);
569 cc = findContact(from);
570 if (cc) fr.setVerbatim(cc->verbatim());
571 showPopup(place, fr, "public", plc.uni().toLower(), msg, action);
575 //FIXME: generalize showPopup()?
576 void ChatForm::showStatusPopup (const PsycEntity &user) {
577 if (mMyStatus == PsycProto::Offline) return;
578 if (!user.isValid()) return;
579 if (!toBoolOpt("/popup/active", true, user.uni())) return;
580 if (!toBoolOpt("/notifications/status", true, user.uni())) return;
582 mPopMan->setFloatHeight(toBoolOpt("/popup/dynsize", true));
584 //qDebug() << "showStatus:" << user.uni();
585 PsycEntity fr(user);
586 PsycContact *cc = findContact(user);
587 if (cc && cc->isHidden() && !toBoolOpt("/contactlist/showhidden", false)) return;
588 if (cc) fr.setVerbatim(cc->verbatim());
590 QString type("status");
591 QString pt(settingsHtmlPath()+"popups/"+type);
592 mPopMan->addType(type, loadFile(pt+"/style.qss"));
593 K8Template tpl;
594 tpl.loadFromFile(pt+"/template.html");
595 tpl.setVar("uni", K8Popups::escapeStr(fr.uni()));
596 tpl.setVar("nick", K8Popups::escapeStr(fr.nick()));
597 tpl.setVar("verbatim", K8Popups::escapeStr(fr.verbatim()));
598 tpl.setVar("status", K8Popups::escapeStr(PsycProto::statusName(fr.status())));
599 tpl.setVar("mood", K8Popups::escapeStr(PsycProto::moodName(fr.mood())));
600 pt = K8Popups::escapeStr(user.statusText().simplified()); pt.replace("\n", "<br />");
601 tpl.setVar("text", pt);
602 pt = tpl.result();
603 mPopMan->updateMessage(type, user.uni().toLower(), pt);
607 void ChatForm::showInfoPopup (const QString &msg) {
608 if (!toBoolOpt("/popup/active", true)) return;
609 PsycEntity me(mProto->uni());
610 PsycEntity place;
611 showPopup(place, me, "info", "infopopup", msg, "");
615 void ChatForm::showErrorPopup (const QString &msg) {
616 if (!toBoolOpt("/popup/active", true)) return;
617 PsycEntity me(mProto->uni());
618 PsycEntity place;
619 showPopup(place, me, "error", "errorpopup", msg, "");
623 void ChatForm::onLoadFinishedChat (bool ok) {
624 Q_UNUSED(ok)
625 //qDebug() << "onLoadFinishedChat" << ok;
626 mChatLoaded = true;
630 void ChatForm::onLoadFinishedCList (bool ok) {
631 //qDebug() << "onLoadFinishedCList" << ok;
632 Q_UNUSED(ok)
633 mCListLoaded = true;
637 void ChatForm::clearChat () {
638 mChatLoaded = false;
639 mSelText = QString();
640 webChat->load(QUrl("dys://theme/chat.html"));
641 while (!mChatLoaded) {
642 qApp->processEvents();
644 //qDebug("ch...");
646 lbChatDest->setText("...");
647 if (mChatUser) {
648 setOptCU("/editor/state/text", edChat->toPlainText());
649 QTextCursor cur = edChat->textCursor();
650 int cpos = cur.position();
651 setOptCU("/editor/state/curpos", cpos);
652 PsycContact *cc = mChatUser;
653 mChatUser = 0;
654 redrawContact(cc);
657 runChatJS("setMyUNI("+K8Str::jsStr(mProto->uni())+");");
658 //qDebug() << "clearChat()";
660 mStartingNickCompletion.clear();
661 mLatestNickCompletion.clear();
665 void ChatForm::clearCList () {
666 mCListLoaded = false;
667 webCList->load(QUrl("dys://theme/clist.html"));
668 while (!mCListLoaded) {
669 qApp->processEvents();
671 //qDebug("cl...");
673 runCListJS("setMyUNI("+K8Str::jsStr(mProto->uni())+");");
674 //qDebug() << "clearCList()";
678 ///////////////////////////////////////////////////////////////////////////////
679 void ChatForm::onTrayActivate (QSystemTrayIcon::ActivationReason reason) {
680 if ((toBoolOpt("/tray/ondoubleclick") && reason == QSystemTrayIcon::DoubleClick) ||
681 reason == QSystemTrayIcon::Trigger) {
682 if (isMinimized() || !isVisible()) {
683 showNormal();
684 activateWindow();
685 } else hide();
686 return;
688 if (toBoolOpt("/tray/middleexit", true) && reason == QSystemTrayIcon::MiddleClick) {
689 close();
694 void ChatForm::checkBlink () {
695 bool doBlink = mUnreadInChat;
696 if (mFMan && !mUnreadInChat && mChatUser) mFMan->noMessages(mChatUser->uni());
697 QString tip;
698 #ifndef WIN32
699 QString br("<br />");
700 #else
701 QString br("\n");
702 #endif
704 if (mChatUser && mUnreadInChat) {
705 tip += K8Str::escapeStr(mChatUser->verbatim())+br+"written something";
706 if (toBoolOpt("/tray/notify", true, mChatUser->uni())) doBlink = true;
709 foreach (PsycContact *cc, mContactList) {
710 if (cc->isTemp()) continue;
711 if (!cc->messageCount()) continue;
712 if (!tip.isEmpty()) {
713 #ifndef WIN32
714 tip += "<hr>";
715 #else
716 tip += "\n";
717 #endif
719 tip += K8Str::escapeStr(cc->verbatim())+br;
720 tip += QString::number(cc->messageCount())+" unread message";
721 if (cc->messageCount() > 0) tip += "s";
722 if (toBoolOpt("/tray/notify", true, cc->uni())) doBlink = true;
725 //qDebug() << mTrayMsgNoteActive;
726 if (tip.isEmpty()) tip = "Dyskinesia v"+mVerStr;
727 mTrayIcon->setToolTip(tip);
728 if (!doBlink) {
729 if (mTrayMsgNoteActive) {
730 mBlinkTimer->stop();
731 if (toBoolOpt("/status/trayicon")) {
732 #ifndef WIN32
733 mTrayIcon->setIcon(LinuxizeTrayIcon(statusIcon(mProto->status())));
734 #else
735 mTrayIcon->setIcon(statusIcon(mProto->status()));
736 #endif
737 } else mTrayIcon->setIcon(QIcon(MAIN_TRAY_ICON));
738 mTrayMsgNoteActive = false;
740 } else {
741 if (!mTrayMsgNoteActive) {
742 int bt = toIntOpt("/tray/blinktime", 500);
743 if (bt > 0) {
744 if (bt < 10) bt = 10;
745 mBlinkTimer->start(bt);
747 mBlinkMsg = true;
748 if (toBoolOpt("/status/trayicon")) {
749 #ifndef WIN32
750 mTrayIcon->setIcon(LinuxizeTrayIcon(statusIcon(mProto->status())));
751 #else
752 mTrayIcon->setIcon(statusIcon(mProto->status()));
753 #endif
754 } else mTrayIcon->setIcon(QIcon(MAIN_TRAY_ICON));
755 //mTrayIcon->setIcon(QIcon(MSG_TRAY_ICON));
756 mTrayMsgNoteActive = true;
762 void ChatForm::onTrayBlink () {
763 mBlinkMsg = !mBlinkMsg;
764 if (mBlinkMsg) mTrayIcon->setIcon(QIcon(MSG_TRAY_ICON));
765 else {
766 //mTrayIcon->setIcon(QIcon(MAIN_TRAY_ICON));
767 if (toBoolOpt("/status/trayicon")) {
768 #ifndef WIN32
769 mTrayIcon->setIcon(LinuxizeTrayIcon(statusIcon(mProto->status())));
770 #else
771 mTrayIcon->setIcon(statusIcon(mProto->status()));
772 #endif
773 } else mTrayIcon->setIcon(QIcon(MAIN_TRAY_ICON));
778 ///////////////////////////////////////////////////////////////////////////////
779 void ChatForm::requestStoredAppData () {
780 if (!mProto->isLoggedIn()) return;
781 PsycPacket pkt("_request_retrieve");
782 pkt.put("_target", mProto->uni());
783 pkt.put("_source_identification", mProto->uni());
784 pkt.endRoute();
785 mProto->sendPacket(pkt);
790 * syntax for _request_store:
792 * :_application_pypsyc_window_size>3000x2000
793 * :_application_pypsyc_skin<><>darkstar
794 * :_character_command<><><>%
795 * _request_store
798 * so whenever the client starts up it can connect
799 * the UNI and ask for its settings by issuing an
800 * empty _request_retrieve. one day we may specify
801 * subsets of the data pool.. using families i guess.
803 void ChatForm::storeAppData () {
804 if (!mProto->isLoggedIn()) return;
805 PsycPacket pkt("_request_store");
806 pkt.put("_target", mProto->uni());
807 pkt.put("_source_identification", mProto->uni());
808 pkt.endRoute();
809 if (mServerStore) {
810 pkt.putInt("_application_window_x", pos().x());
811 pkt.putInt("_application_window_y", pos().y());
812 pkt.putInt("_application_window_w", size().width());
813 pkt.putInt("_application_window_h", size().height());
814 if (mConForm) {
815 pkt.putInt("_application_console_x", mConForm->pos().x());
816 pkt.putInt("_application_console_y", mConForm->pos().y());
817 pkt.putInt("_application_console_w", mConForm->size().width());
818 pkt.putInt("_application_console_h", mConForm->size().height());
819 int act = mConForm->cbActive->isChecked()?1:0;
820 pkt.putInt("_application_console_active", act);
821 int vis = (mConForm->isMinimized() || !mConForm->isVisible())?0:1;
822 pkt.putInt("_application_console_visible", vis);
824 if (mChatUser) pkt.put("_application_chatwith", mChatUser->uni());
825 else pkt.put("_application_chatwith", ".");
827 mProto->sendPacket(pkt);
831 void ChatForm::parseStoredAppData (const PsycPacket &pkt) {
832 if (mServerStore) {
833 if (pkt.has("_application_window_x") && pkt.has("_application_window_y")) {
834 int x = pkt.getInt("_application_window_x", 42);
835 int y = pkt.getInt("_application_window_y", 42);
836 move(x, y);
838 if (pkt.has("_application_window_w") && pkt.has("_application_window_h")) {
839 int w = pkt.getInt("_application_window_w", 606);
840 int h = pkt.getInt("_application_window_h", 500);
841 resize(w, h);
843 if (pkt.has("_application_console_x") && pkt.has("_application_console_y")) {
844 on_action_showconsole_triggered(true);
845 if (pkt.getInt("_application_console_visible", 0)) mConForm->show(); else mConForm->hide();
846 mConForm->cbActive->setChecked(pkt.getInt("_application_console_active", 0) != 0);
847 activateWindow(); // get focus back
848 int x = pkt.getInt("_application_console_x", 0);
849 int y = pkt.getInt("_application_console_y", 0);
850 mConForm->move(x, y);
852 if (mConForm) {
853 if (pkt.has("_application_console_w") && pkt.has("_application_console_h")) {
854 int w = pkt.getInt("_application_console_w", 0);
855 int h = pkt.getInt("_application_console_h", 0);
856 mConForm->resize(w, h);
859 QString cw(pkt.get("_application_chatwith", ""));
860 if (!cw.isEmpty()) {
861 PsycEntity u(cw);
862 if (u.isValid()) startChatWith(cw);
865 //knockAutosave();
869 ///////////////////////////////////////////////////////////////////////////////
870 static QString optNameEx (const QString &name, const QString &aUNI=QString()) {
871 QString res(name);
872 if (!res.isEmpty() && res[0] != '/') res.prepend('/');
873 if (aUNI.isEmpty()) return res;
874 return "*<"+aUNI.toLower()+">"+res;
878 bool ChatForm::removeOpt (const QString &name, const QString &aUNI) {
879 if (!mSDB) return false;
880 if (mSDB->remove(optNameEx(name, aUNI))) {
881 runCListJS("optionRemoved("+K8Str::jsStr(name)+");");
882 runChatJS("optionRemoved("+K8Str::jsStr(name)+");");
883 return true;
885 return false;
889 bool ChatForm::hasKeyOpt (const QString &name, const QString &aUNI) const {
890 if (!mSDB) return false;
891 return mSDB->hasKey(optNameEx(name, aUNI));
895 K8SDB::Type ChatForm::typeOpt (const QString &name, const QString &aUNI) {
896 if (!mSDB) return K8SDB::Invalid;
897 return mSDB->type(optNameEx(name, aUNI));
901 void ChatForm::optionChangedSignal (const QString &name, const QString &uni) {
902 //qDebug() << "optionChangedSignal:" << name << uni;
903 char mtName[256];
904 QString nn("onOptChanged_"+name); nn.replace("/", "_");
905 QByteArray bt(nn.toAscii());
906 sprintf(mtName, "%s(QString)", bt.constData());
907 //fprintf(stderr, " [%s]\n", mtName);
908 if (metaObject()->indexOfSlot(mtName) >= 0) {
909 sprintf(mtName, "%s", bt.constData());
910 //fprintf(stderr, " [%s]\n", mtName);
911 QMetaObject::invokeMethod(this, mtName, Qt::AutoConnection, Q_ARG(QString, uni));
913 runCListJS("optionChanged("+K8Str::jsStr(name)+");");
914 runChatJS("optionChanged("+K8Str::jsStr(name)+");");
915 if (name.left(14) == "/app/shortcut/") {
916 //qDebug() << "shortcuts changed";
917 setAccountShortcuts();
922 bool ChatForm::setOpt (const QString &name, bool v, const QString &aUNI) {
923 if (!mSDB) return false;
924 if (mSDB->set(optNameEx(name, aUNI), v)) {
925 optionChangedSignal(name, aUNI);
926 return true;
928 return false;
932 bool ChatForm::setOpt (const QString &name, qint32 v, const QString &aUNI) {
933 if (!mSDB) return false;
934 if (mSDB->set(optNameEx(name, aUNI), v)) {
935 optionChangedSignal(name, aUNI);
936 return true;
938 return false;
942 bool ChatForm::setOpt (const QString &name, const QString &v, const QString &aUNI) {
943 if (!mSDB) return false;
944 if (mSDB->set(optNameEx(name, aUNI), v)) {
945 optionChangedSignal(name, aUNI);
946 return true;
948 return false;
952 bool ChatForm::setOpt (const QString &name, const QKeySequence &v, const QString &aUNI) {
953 if (!mSDB) return false;
954 if (mSDB->set(optNameEx(name, aUNI), v)) {
955 optionChangedSignal(name, aUNI);
956 return true;
958 return false;
962 bool ChatForm::setOpt (const QString &name, K8SDB::Type type, const QString &v, const QString &aUNI) {
963 if (!mSDB) return false;
964 if (mSDB->set(optNameEx(name, aUNI), type, v)) {
965 //qDebug() << "option:" << name;
966 if (name == "/popup/position") {
967 int pp = toIntOpt("/popup/position", 3);
968 if (pp < 0 || pp > 3) pp = 3;
969 mPopPos = (K8PopupManager::Position)(pp);
970 mPopMan->setPosition(mPopPos);
971 //recreatePopMan();
973 optionChangedSignal(name, aUNI);
974 return true;
976 return false;
980 bool ChatForm::toBoolOpt (const QString &name, bool def, const QString &aUNI) {
981 if (!mSDB) return def;
982 bool ok = false;
983 bool res = mSDB->toBool(optNameEx(name, aUNI), &ok);
984 if (!ok && !aUNI.isEmpty()) res = mSDB->toBool(optNameEx(name), &ok);
985 if (!ok) return def;
986 return res;
990 qint32 ChatForm::toIntOpt (const QString &name, qint32 def, const QString &aUNI) {
991 if (!mSDB) return def;
992 bool ok = false;
993 int res = mSDB->toInt(optNameEx(name, aUNI), &ok);
994 if (!ok && !aUNI.isEmpty()) res = mSDB->toInt(optNameEx(name), &ok);
995 if (!ok) return def;
996 return res;
1000 QString ChatForm::toStringOpt (const QString &name, const QString &def, const QString &aUNI) {
1001 if (!mSDB) return def;
1002 bool ok = false;
1003 QString res = mSDB->toString(optNameEx(name, aUNI), &ok);
1004 if (!ok && !aUNI.isEmpty()) res = mSDB->toString(optNameEx(name), &ok);
1005 if (!ok) return def;
1006 return res;
1010 QByteArray ChatForm::toBAOpt (const QString &name, const QByteArray &def, const QString &aUNI) {
1011 if (!mSDB) return def;
1012 bool ok = false;
1013 QByteArray res = mSDB->toByteArray(optNameEx(name, aUNI), &ok);
1014 if (!ok && !aUNI.isEmpty()) res = mSDB->toByteArray(optNameEx(name), &ok);
1015 if (!ok) return def;
1016 return res;
1020 QKeySequence ChatForm::toKeySequenceOpt (const QString &name, const QKeySequence &def, const QString &aUNI) {
1021 if (!mSDB) return def;
1022 bool ok = false;
1023 QKeySequence res = mSDB->toKeySequence(optNameEx(name, aUNI), &ok);
1024 if (!ok && !aUNI.isEmpty()) res = mSDB->toKeySequence(optNameEx(name), &ok);
1025 if (!ok) return def;
1026 return res;
1031 QString ChatForm::asStringOpt (const QString &name, const QString &aUNI) {
1032 if (!mSDB) return QString();
1033 bool ok = false;
1034 QString res = mSDB->asString(optNameEx(name, aUNI), &ok);
1035 if (!ok && !aUNI.isEmpty()) res = mSDB->asString(optNameEx(name), &ok);
1036 if (!ok) return QString();
1037 return res;
1041 ///////////////////////////////////////////////////////////////////////////////
1042 bool ChatForm::removeOptCU (const QString &name) {
1043 if (mChatUser) return removeOpt(name, mChatUser->uni());
1044 return removeOpt(name);
1048 bool ChatForm::hasKeyOptCU (const QString &name) const {
1049 if (mChatUser) return hasKeyOpt(name, mChatUser->uni());
1050 return hasKeyOpt(name);
1054 K8SDB::Type ChatForm::typeOptCU (const QString &name) {
1055 if (mChatUser) return typeOpt(name, mChatUser->uni());
1056 return typeOpt(name);
1060 bool ChatForm::setOptCU (const QString &name, bool v) {
1061 if (mChatUser) return setOpt(name, v, mChatUser->uni());
1062 return setOpt(name, v);
1066 bool ChatForm::setOptCU (const QString &name, qint32 v) {
1067 if (mChatUser) return setOpt(name, v, mChatUser->uni());
1068 return setOpt(name, v);
1072 bool ChatForm::setOptCU (const QString &name, const QString &v) {
1073 if (mChatUser) return setOpt(name, v, mChatUser->uni());
1074 return setOpt(name, v);
1078 bool ChatForm::setOptCU (const QString &name, const QKeySequence &v) {
1079 if (mChatUser) return setOpt(name, v, mChatUser->uni());
1080 return setOpt(name, v);
1084 bool ChatForm::setOptCU (const QString &name, K8SDB::Type type, const QString &v) {
1085 if (mChatUser) return setOpt(name, type, v, mChatUser->uni());
1086 return setOpt(name, type, v);
1090 bool ChatForm::toBoolOptCU (const QString &name, bool def) {
1091 if (mChatUser) return toBoolOpt(name, def, mChatUser->uni());
1092 return toBoolOpt(name, def);
1096 qint32 ChatForm::toIntOptCU (const QString &name, qint32 def) {
1097 if (mChatUser) return toIntOpt(name, def, mChatUser->uni());
1098 return toIntOpt(name, def);
1102 QString ChatForm::toStringOptCU (const QString &name, const QString &def) {
1103 if (mChatUser) return toStringOpt(name, def, mChatUser->uni());
1104 return toStringOpt(name, def);
1108 QByteArray ChatForm::toBAOptCU (const QString &name, const QByteArray &def) {
1109 if (mChatUser) return toBAOpt(name, def, mChatUser->uni());
1110 return toBAOpt(name, def);
1114 QKeySequence ChatForm::toKeySequenceOptCU (const QString &name, const QKeySequence &def) {
1115 if (mChatUser) return toKeySequenceOpt(name, def, mChatUser->uni());
1116 return toKeySequenceOpt(name, def);
1120 QString ChatForm::asStringOptCU (const QString &name) {
1121 if (mChatUser) return asStringOpt(name, mChatUser->uni());
1122 return asStringOpt(name);
1126 ///////////////////////////////////////////////////////////////////////////////
1127 void ChatForm::setControlsAccActive (bool accAct) {
1128 action_stoffline->setEnabled(accAct);
1129 action_stvacation->setEnabled(accAct);
1130 action_staway->setEnabled(accAct);
1131 action_stdnd->setEnabled(accAct);
1132 action_stnear->setEnabled(accAct);
1133 action_stbusy->setEnabled(accAct);
1134 action_sthere->setEnabled(accAct);
1135 action_stffc->setEnabled(accAct);
1136 action_strtime->setEnabled(accAct);
1138 action_quit->setEnabled(true);
1139 action_accedit->setEnabled(true);
1140 action_showconsole->setEnabled(true);
1141 action_sendpacket->setEnabled(accAct);
1142 action_join->setEnabled(accAct);
1146 void ChatForm::setAccountShortcuts () {
1147 //qDebug() << "setting shortcuts...";
1148 GHotKeyMan::instance()->clear();
1149 GHotKeyMan::instance()->connect(QKeySequence(toStringOpt("/app/shortcut/global/showwindow", "Alt+Meta+D")), this, SLOT(onGShortcutShow()));
1150 GHotKeyMan::instance()->connect(QKeySequence(toStringOpt("/app/shortcut/global/hidepopups", "Alt+Space")), this, SLOT(onGShortcutHidePopups()));
1151 //qDebug() << "shortcuts set";
1155 void ChatForm::loadAccount () {
1156 delete mFMan; mFMan = 0;
1158 setStatus(PsycProto::Offline, false);
1159 clear();
1160 loadOptionList();
1162 #ifdef USE_OTR
1163 if (mOTRState) otrl_userstate_free(mOTRState);
1164 mOTRState = 0;
1165 mOTRState = otrl_userstate_create();
1166 #endif
1168 delete mSDB; mSDB = 0;
1170 K8SDB *mainDB = new K8SDB(settingsMainDBFile(), K8SDB::OpenExisting);
1171 if (mainDB->isOpen()) {
1172 bool ok;
1173 QString actAcc = mainDB->toString("/active", &ok);
1174 if (!ok || actAcc.isEmpty()) mAccName = QString(); else mAccName = actAcc;
1176 delete mainDB;
1178 if (mAccName.isEmpty()) {
1179 setControlsAccActive(false);
1180 return;
1183 setControlsAccActive(true);
1185 QDir d;
1186 d.mkdir(settingsDBPath(mAccName));
1187 d.mkdir(settingsHistoryPath(mAccName));
1189 mSDB = new K8SDB(settingsDBFile(mAccName), K8SDB::OpenAlways);
1191 QString host = toStringOpt("/server/host", "127.0.0.1");
1192 int port = toIntOpt("/server/port", 0);
1193 if (port < 1 || port > 65536) port = 0;
1194 QString nick = toStringOpt("/login/nick", "guest");
1195 QString pass = K8Str::dePass(toStringOpt("/login/password", ""));
1196 bool seclogin = toBoolOpt("/login/secure", true);
1197 mServerStore = false;//toBoolOpt("/server/storedata", false);
1198 bool useSSL = toBoolOpt("/login/useSSL", false);
1200 setAccDefaultOptions(mSDB); // if i added something new...
1202 int pp = toIntOpt("/popup/position", 3);
1203 if (pp < 0 || pp > 3) pp = 3;
1204 mPopPos = (K8PopupManager::Position)(pp);
1205 mPopMan->setPosition(mPopPos);
1207 mPopMan->setFloatHeight(toBoolOpt("/popup/dynsize", true));
1209 PsycEntity uu(QString("psyc://%1:%2/~%3").arg(host).arg(port).arg(nick));
1210 mProto->clear();
1211 mProto->setUNI(uu.uni());
1212 mPassword = pass;
1213 mProto->setSecureLogin(seclogin);
1214 mProto->setUseSSL(useSSL);
1215 if (useSSL) qDebug() << "SSL";
1217 mMyStatus = PsycProto::Offline;
1218 setStatus(PsycProto::Offline, false);
1219 mDoTypo = toBoolOptCU("/editor/typography");
1221 #ifdef USE_OTR
1223 FILE *fl = qxfopen(getOTRKeyFile(), true);
1224 if (fl) {
1225 otrl_privkey_read_FILEp(mOTRState, fl);
1226 fclose(fl);
1228 fl = qxfopen(getOTRFingerFile(), true);
1229 if (fl) {
1230 otrl_privkey_read_fingerprints_FILEp(mOTRState, fl, 0, 0);
1231 fclose(fl);
1234 #endif
1236 if (!loadBindings(settingsDBPath(mAccName)+"keybinds.txt")) loadBindings(":/data/keybinds.txt");
1237 setAccountShortcuts();
1238 loadAutosaved();
1242 void ChatForm::loadContactUnread (PsycContact *cc) {
1243 cc->clearMessages();
1244 bool wasMsg = false;
1245 HistoryFile hf(settingsUnreadHistoryFile(mAccName, cc->uni()), mProto->uni());
1246 if (hf.open(HistoryFile::Read)) {
1247 //qDebug() << "unread for:" << cc->uni();
1248 HistoryMessage msg;
1249 int mNo = 0;
1250 while (hf.read(mNo++, msg)) {
1251 if (!msg.valid) continue;
1252 if (msg.type == HistoryMessage::Incoming || msg.type == HistoryMessage::Outgoing) {
1253 PsycEntity u(msg.uni);
1254 if (u.isValid()) {
1255 cc->addMessage(u, msg.text, msg.action, msg.date);
1256 wasMsg = true;
1260 hf.close();
1262 if (wasMsg && !cc->isPlace()) mFMan->newMessage(cc->uni().toLower());
1266 void ChatForm::loadContactList () {
1267 // do this faster/another way?
1268 QStringList keys(mSDB->keysPfx("/contacts/unis/"));
1269 foreach (const QString &key, keys) {
1270 // found *XXX/uni key
1271 QString kk(key.mid(15));
1272 if (kk.isEmpty()) continue;
1273 //qDebug() << kk;
1274 PsycEntity ux(kk);
1275 if (!ux.isValid()) continue;
1276 kk = optNameEx("/clist/", kk);
1277 bool ok;
1278 QString uni = mSDB->toString(kk+"uni", &ok);
1279 if (!ok) continue;
1280 PsycEntity user(uni);
1281 if (!user.isValid()) continue;
1282 QString verb = mSDB->toString(kk+"verbatim", &ok);
1283 if (!ok) verb = QString();
1284 QString group = mSDB->toString(kk+"group", &ok);
1285 if (!ok) group = QString();
1287 PsycContact *cc = addContact(uni, group, 0, DontSaveContact);
1288 if (cc) {
1289 cc->setStatus(PsycProto::Offline);
1290 if (!verb.isEmpty()) cc->setVerbatim(verb);
1291 bool hid = mSDB->toBool(kk+"hidden", &ok);
1292 cc->setHidden(hid);
1293 if (!cc->isPlace()) {
1294 int auth = mSDB->toInt(kk+"authorized", &ok);
1295 qDebug() << kk << uni << auth << ok;
1296 //if (uni.toLower().indexOf("kol_panic") >= 0) qDebug() << "!!!!!!";
1297 if (!ok) auth = 0;
1298 cc->setAuthorized(auth != 0);
1299 int lasts = mSDB->toInt(kk+"lastseen", &ok);
1300 if (lasts < 0 || !ok) lasts = 0;
1301 cc->setLastSeen(lasts);
1302 QString lst = mSDB->toString(kk+"laststatustext", &ok);
1303 if (!ok) lst = QString();
1304 cc->setStatusText(lst);
1309 // this will add temporary 'me' contact if it isn't exist
1310 addContact(mProto->uni(), "", 0, TempContact);
1312 if (!mFMan) {
1313 mFMan = new FloatersManager(this,
1314 toIntOpt("/floaty/intervals/title", 500),
1315 toIntOpt("/floaty/intervals/icon", 300)
1317 connect(mFMan, SIGNAL(mouseDblClicked(int, int, const QString &)), this, SLOT(onFloatyDblClicked(int, int, const QString &)));
1320 // load unread messages
1321 foreach (PsycContact *cc, mContactList) {
1322 loadContactUnread(cc);
1323 redrawContact(cc);
1328 void ChatForm::loadAutosaved () {
1329 if (toBoolOpt("/contactlist/onright") != mCListOnRight) {
1330 mCListOnRight = toBoolOpt("/contactlist/onright");
1331 if (mCListOnRight) {
1332 // was on the left
1333 splitter0->addWidget(layoutWidget0);
1334 } else {
1335 // was on the right
1336 splitter0->addWidget(layoutWidget1);
1340 QByteArray sp = toBAOpt("/chatwindow/splitters/0");
1341 if (!sp.isEmpty()) splitter0->restoreState(sp);
1342 sp = toBAOpt("/chatwindow/splitters/1");
1343 if (!sp.isEmpty()) splitter1->restoreState(sp);
1345 if (toBoolOpt("/chatwindow/saved")) {
1347 if (mSDB->hasKey("/chatwindow/geometry")) {
1348 QByteArray geo(mSDB->toByteArray("/chatwindow/geometry"));
1349 restoreGeometry(geo);
1350 } else
1353 int x = toIntOpt("/chatwindow/x");
1354 int y = toIntOpt("/chatwindow/y");
1355 int w = toIntOpt("/chatwindow/w");
1356 int h = toIntOpt("/chatwindow/h");
1357 setGeometry(x, y, w, h);
1361 mBlinkMsg = false;
1362 mTrayIcon->setIcon(QIcon(MAIN_TRAY_ICON));
1363 if (toBoolOpt("/tray/visible", true)) mTrayIcon->show();
1364 else mTrayIcon->hide();
1366 clearCList();
1367 clearChat();
1368 //startChatWith("");
1370 loadContactList();
1371 checkBlink();
1373 mProto->setStatusText(toStringOpt("/status/text"));
1375 startChatWith(toStringOpt("/chatwindow/uni"));
1379 ///////////////////////////////////////////////////////////////////////////////
1380 void ChatForm::saveOneContact (PsycContact *cc) {
1381 if (!cc) return;
1382 mSDB->set("/contacts/unis/"+cc->uni().toLower(), 1); // just a flag
1383 QString kk(optNameEx("/clist/", cc->uni()));
1384 mSDB->set(kk+"uni", cc->uni());
1385 mSDB->set(kk+"verbatim", cc->verbatim());
1386 QString grp(cc->groupName());
1387 if (!grp.isEmpty()) mSDB->set(kk+"group", grp); else mSDB->remove(kk+"group");
1388 mSDB->set(kk+"hidden", cc->isHidden());
1389 if (!cc->isPlace()) {
1390 qint32 af = 1;
1391 if (!cc->authorized()) af = 0;
1392 qDebug() << "***uni:" << cc->uni() << "auth:" << af;
1393 mSDB->setInt(kk+"authorized", af);
1394 mSDB->setInt(kk+"lastseen", cc->lastSeenUnix());
1395 mSDB->set(kk+"laststatustext", cc->statusText());
1400 void ChatForm::on_splitter0_splitterMoved (int pos, int index) {
1401 Q_UNUSED(pos)
1402 Q_UNUSED(index)
1403 if (!mSDB) return;
1404 mSDB->set("/chatwindow/splitters/0", splitter0->saveState());
1408 void ChatForm::on_splitter1_splitterMoved (int pos, int index) {
1409 Q_UNUSED(pos)
1410 Q_UNUSED(index)
1411 if (!mSDB) return;
1412 mSDB->set("/chatwindow/splitters/1", splitter1->saveState());
1416 ///////////////////////////////////////////////////////////////////////////////
1417 void ChatForm::loadRecodeMap (void) {
1418 QFile file(settingsAppPath()+"data/recode.txt");
1419 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1420 file.setFileName(":/data/recode.txt");
1421 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return;
1424 QTextStream stream;
1425 QTextCodec *codec = QTextCodec::codecForName("UTF-8");
1426 if (codec) {
1427 stream.setDevice(&file);
1428 stream.setCodec(codec);
1429 mRecodeMap.clear();
1430 while (!stream.atEnd()) {
1431 QString line(stream.readLine());
1432 QString cFrom(line.section('=', 0, 0)), cTo(line.section('=', 1, 1));
1433 if (cFrom.length() != 1 || cTo.length() != 1) continue;
1434 mRecodeMap.insert(cFrom[0], cTo[0]);
1435 mRecodeMap.insert(cTo[0], cFrom[0]);
1438 file.close();
1442 QString ChatForm::recodeMsg (const QString &msg) {
1443 QString res = msg;
1444 for (int f = 0; f < res.length(); f++) {
1445 QChar ch = res.at(f);
1446 res[f] = mRecodeMap.value(ch, ch);
1448 return res;
1452 ///////////////////////////////////////////////////////////////////////////////
1453 void ChatForm::redrawContact (PsycContact *cc) {
1454 if (!cc || cc->isTemp()) return;
1455 runCListJS("redrawContact("+K8Str::jsStr(cc->uni())+");");
1459 PsycContact *ChatForm::findContact (const QString &uni) {
1460 QString u(uni.toLower());
1461 if (!mContactList.contains(u)) return 0;
1462 return mContactList[u];
1466 PsycContact *ChatForm::findContact (const PsycEntity &cont) {
1467 if (!cont.isValid()) return 0;
1468 return findContact(cont.uni());
1472 PsycContact *ChatForm::addContact (const PsycEntity &user, const QString &group, bool *isNew, ACSaveMode mode) {
1473 if (!user.isValid()) return 0;
1474 PsycContact *cc = findContact(user);
1475 if (cc) {
1476 if (isNew) *isNew = false;
1477 } else {
1478 if (isNew) *isNew = true;
1479 cc = new PsycContact(user.uni());
1480 cc->setStatus(user.status());
1481 cc->setStatusText(user.statusText());
1482 cc->setMood(user.mood());
1483 cc->setGroupName(group);
1484 mContactList[cc->uni().toLower()] = cc;
1485 switch (mode) {
1486 case SaveContact: saveOneContact(cc); break;
1487 case TempContact: cc->setTemp(true); break;
1488 default: ;
1491 return cc;
1495 void ChatForm::deleteContact (PsycContact *cc) {
1496 if (!cc) return;
1497 if (mChatUser == cc) startChatWith("");
1498 QString uc(cc->uni().toLower());
1499 if (!mContactList.contains(uc)) return;
1501 mContactList.remove(uc);
1503 int idx = mTabList.indexOf(cc);
1504 if (idx >= 0) mTabList.removeAt(idx);
1506 delete cc;
1508 mSDB->removeAll(optNameEx("/", uc));
1509 mSDB->remove("/contacts/unis/"+uc);
1511 mSDB->removeAll("/floaty/contacts/"+uc);
1512 mFMan->remove(uc.toLower());
1516 ///////////////////////////////////////////////////////////////////////////////
1517 void ChatForm::on_action_quit_triggered (bool) {
1518 close();
1522 ///////////////////////////////////////////////////////////////////////////////
1523 void ChatForm::onUpdateAccountInfo (AccWindow *au, const QString &text) {
1524 //qDebug() << "update info for account" << text;
1525 QString odb;
1527 K8SDB *db = 0;
1528 QDir ppdir(settingsPrefsPath()+text.toLower());
1530 QString pass, host;
1531 int p;
1533 if (text.isEmpty() || !ppdir.exists()) goto clear;
1535 if (mSDB) {
1536 odb = mSDB->pathname();
1537 delete mSDB;
1538 mSDB = 0;
1541 db = new K8SDB(settingsDBFile(text.toLower()), K8SDB::OpenExisting);
1542 if (!db->isOpen()) goto clear;
1545 PsycEntity u(QString("psyc://%1:%2/%3"
1546 .arg(db->toString("/server/host"))
1547 .arg(db->toInt("/server/port"))
1548 .arg(db->toString("/login/nick"))
1549 .arg(db->toString("/login/password"))
1553 au->leNick->setText(db->toString("/login/nick"));
1555 host = db->toString("/server/host");
1556 p = db->toInt("/server/port");
1557 if (p > 0 && p != 4404) host += QString(":%1").arg(p);
1558 au->leHost->setText(host);
1560 pass = K8Str::dePass(db->toString("/login/password"));
1561 au->lePass->setText(pass);
1563 au->cbSecure->setChecked(db->toBool("/login/secure"));
1564 #ifdef USE_SSL
1565 au->cbSSL->setChecked(db->toBool("/login/useSSL"));
1566 #else
1567 au->cbSSL->setChecked(false);
1568 au->cbSSL->setEnabled(false);
1569 #endif
1571 au->btDelAcc->setEnabled(true);
1572 goto done;
1574 clear:
1575 au->btDelAcc->setEnabled(false);
1576 au->leNick->setText("");
1577 au->leHost->setText("");
1578 au->lePass->setText("");
1579 au->cbSecure->setChecked(true);
1581 done:
1582 delete db;
1583 if (!odb.isEmpty()) {
1584 mSDB = new K8SDB(odb, K8SDB::OpenAlways);
1589 QString ChatForm::defAccount () {
1590 QString res;
1591 K8SDB *mainDB = new K8SDB(settingsMainDBFile(), K8SDB::OpenExisting);
1592 if (mainDB->isOpen()) {
1593 bool ok;
1594 res = mainDB->toString("/active", &ok);
1595 if (!ok || res.isEmpty()) res = QString();
1597 delete mainDB;
1598 return res;
1602 void ChatForm::setDefAccount (const QString &name) {
1603 if (name.isEmpty()) return;
1605 K8SDB *mainDB = new K8SDB(settingsMainDBFile(), K8SDB::OpenAlways);
1606 if (mainDB->isOpen()) {
1607 mainDB->set("/active", name);
1609 delete mainDB;
1613 void ChatForm::accountSetDefaults (K8SDB *db) {
1614 // defaults
1615 if (!db || !db->isOpen()) return;
1616 setAccDefaultOptions(db);
1620 //FIXME: check for errors!
1621 void ChatForm::onAccountCreated (AccWindow *au, const QString &text) {
1622 Q_UNUSED(au)
1623 qDebug() << "account created:" << text;
1625 K8SDB *db = 0;
1626 QDir ppdir(settingsPrefsPath());
1627 ppdir.mkdir(text.toLower());
1629 db = new K8SDB(settingsDBFile(text.toLower()), K8SDB::OpenAlways);
1630 if (!db->isOpen()) { delete db; return; }
1633 au->btDelAcc->setEnabled(true);
1634 au->leNick->setText("");
1635 au->leHost->setText("");
1636 au->lePass->setText("");
1637 au->cbSecure->setChecked(true);
1640 accountSetDefaults(db);
1642 delete db;
1646 void ChatForm::onAccountDeleted (AccWindow *au, const QString &text) {
1647 Q_UNUSED(au)
1648 qDebug() << "account deleted:" << text;
1650 K8SDB *db = new K8SDB(settingsMainDBFile(), K8SDB::OpenAlways);
1651 if (!db->isOpen()) { delete db; return; }
1653 db->remove(text.toLower());
1654 db->removeAll(text.toLower()+"/");
1656 bool ok = false;
1657 QString act = db->toString("/active", &ok);
1658 if (ok && !act.compare(text, Qt::CaseInsensitive)) db->remove("/active");
1660 QDir ppdir;
1661 ppdir.rename(settingsPrefsPath()+text.toLower(), settingsPrefsPath()+"_"+text.toLower());
1663 delete db;
1667 void ChatForm::on_action_accedit_triggered (bool) {
1669 K8SDB *mainDB = new K8SDB(settingsMainDBFile(), K8SDB::OpenExisting);
1670 if (mainDB->isOpen()) {
1671 bool ok;
1672 QString actAcc = mainDB->toString("/active", &ok);
1673 if (!ok || actAcc.isEmpty()) mAccName = QString(); else mAccName = actAcc;
1675 delete mainDB;
1677 QDir ppdir(settingsPrefsPath());
1678 QStringList accList(ppdir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Writable,
1679 QDir::Name | QDir::IgnoreCase | QDir::LocaleAware));
1680 for (int f = 0; f < accList.count(); f++) {
1681 QString s(accList[f]);
1682 if (s.isEmpty() || s[0] == '_' || s[0] == '.') {
1683 accList.removeAt(0);
1684 f--;
1688 AccWindow *au = new AccWindow(this);
1690 connect(au, SIGNAL(updateAccountInfo(AccWindow *, const QString &)), SLOT(onUpdateAccountInfo(AccWindow *, const QString &)));
1691 connect(au, SIGNAL(accountCreated(AccWindow *, const QString &)), SLOT(onAccountCreated(AccWindow *, const QString &)));
1692 connect(au, SIGNAL(accountDeleted(AccWindow *, const QString &)), SLOT(onAccountDeleted(AccWindow *, const QString &)));
1694 au->cbAccount->addItems(accList);
1695 if (mAccName.isEmpty()) au->cbAccount->setCurrentIndex(-1);
1696 else au->cbAccount->setCurrentIndex(accList.indexOf(mAccName));
1698 au->maxIdx = accList.count()-1;
1701 PsycEntity u(mProto->uni());
1702 if (u.isValid()) {
1703 au->leNick->setText(u.nick());
1704 if (u.port() != 0 && u.port() != 4404) au->leHost->setText(QString("%1:%2").arg(u.host()).arg(u.port()));
1705 else au->leHost->setText(u.host());
1706 } else {
1707 au->leNick->setText("");
1708 au->leHost->setText("");
1710 au->lePass->setText(mPassword);
1711 au->cbSecure->setChecked(mProto->secureLogin());
1713 do {
1714 if (au->exec() != 1) break;
1715 PsycEntity u("psyc://"+au->leHost->text()+"/~"+au->leNick->text());
1716 if (!u.isValid()) break;
1718 //FIXME: idiotic w32!
1719 bool sameAcc = (mAccName == au->curAccName) && u.sameUNI(mProto->uni());
1721 if (!sameAcc) {
1722 mProto->logoff();
1723 mProto->clear();
1724 mProto->setUNI(u.uni());
1726 delete mSDB;
1728 mAccName = au->curAccName;
1729 mSDB = new K8SDB(settingsDBFile(mAccName), K8SDB::OpenAlways);
1731 setDefAccount(mAccName);
1734 mSDB->set("/server/host", u.host());
1735 mSDB->set("/server/port", (qint32)u.port());
1736 mSDB->set("/login/nick", u.nick());
1737 mSDB->set("/login/password", K8Str::enPass(au->lePass->text()));
1738 mSDB->set("/login/secure", au->cbSecure->isChecked());
1739 #ifdef USE_SSL
1740 mSDB->set("/login/useSSL", au->cbSSL->isChecked());
1741 #endif
1743 mProto->setSecureLogin(au->cbSecure->isChecked());
1744 mPassword = au->lePass->text();
1746 if (!sameAcc) loadAccount();
1747 else {
1748 mProto->setSecureLogin(au->cbSecure->isChecked());
1749 mProto->setUseSSL(au->cbSSL->isChecked());
1751 } while (0);
1753 delete au;
1757 ///////////////////////////////////////////////////////////////////////////////
1758 void ChatForm::onAuthRequested (const PsycEntity &user) {
1759 if (user.isPlace()) return;
1760 PsycContact *cc = addContact(user.uni());
1761 if (!cc) return;
1762 if (cc->authorized()) {
1763 mProto->sendAuth(user);
1764 return;
1766 //cc->setAuthorized(true);
1767 AuthWindow *au = new AuthWindow(user, this);
1768 au->setWindowTitle("Dyskinesia: "+user.uni()+tr(" requested authorization from you"));
1769 au->edText->setPlainText(user.uni()+tr(" kindly asks for your friendship."));
1770 au->btOk->setFocus();
1771 connect(au, SIGNAL(granted(const PsycEntity &, const QString &)), this, SLOT(onAuthClicked(const PsycEntity &, const QString &)));
1772 au->show();
1776 void ChatForm::onAuthClicked (const PsycEntity &entity, const QString &text) {
1777 Q_UNUSED(text)
1778 mProto->sendAuth(entity);
1779 bool isNew;
1780 PsycContact *cc = addContact(entity.uni(), "", &isNew);
1781 if (cc && (isNew || !cc->authorized())) {
1782 cc->setAuthorized(true);
1783 saveOneContact(cc);
1784 redrawContact(cc);
1786 qDebug() << "auth granted to" << entity.uni() << "text:" << text;
1790 void ChatForm::onAuthGranted (const PsycEntity &user) {
1791 if (user.isPlace()) return;
1792 if (toBoolOpt("/popup/active", true, user.uni())) showInfoPopup(user.uni()+"\ngranted authorization");
1793 bool isNew;
1794 PsycContact *cc = addContact(user.uni(), "", &isNew);
1795 if (!cc) return;
1796 if (isNew || !cc->authorized()) {
1797 cc->setAuthorized(true);
1798 //mProto->sendAuth(user, tr("auth granted"));
1799 saveOneContact(cc);
1800 redrawContact(cc);
1805 void ChatForm::onAuthDenied (const PsycEntity &user) {
1806 if (user.isPlace()) return;
1807 if (toBoolOpt("/popup/active", true, user.uni())) showInfoPopup(user.uni()+"\nrevoked authorization");
1808 PsycContact *cc = findContact(user);
1809 if (!cc || !cc->authorized()) return;
1810 cc->setAuthorized(false);
1811 saveOneContact(cc);
1812 redrawContact(cc);
1816 ///////////////////////////////////////////////////////////////////////////////
1817 void ChatForm::on_edChat_textChanged (void) {
1818 //if (mChBlock || !mChatUser || !mDoTypo) return;
1819 if (mChBlock) return; // don't recurse
1820 if (mChatUser) {
1821 if (!mDoTypo) return;
1822 } else if (!toBoolOptCU("/editor/typography")) return;
1823 QString txt = edChat->toPlainText();
1824 if (txt.isEmpty()) return;
1825 QChar ch = txt.at(0);
1826 if (ch == '/' || ch == ' ' || ch == 13 || ch == 10) return;
1827 int mv = K8Str::fixTypograpy(txt);
1828 if (mv >= 0) {
1829 mChBlock = true;
1830 int pos = edChat->textCursor().position()-mv;
1831 if (pos > txt.length()) pos = txt.length();
1832 if (pos < 0) pos = 0;
1833 edChat->setPlainText(txt);
1834 QTextCursor cur = edChat->textCursor();
1835 cur.setPosition(pos);
1836 edChat->setTextCursor(cur);
1837 mChBlock = false;
1842 void ChatForm::chatAutoComplete () {
1843 if (!mChatUser || !mChatUser->isPlace()) {
1844 mStartingNickCompletion.clear();
1845 mLatestNickCompletion.clear();
1846 return;
1848 QString txt = edChat->toPlainText();
1849 if (txt.isEmpty() || txt[0] == '/') {
1850 mStartingNickCompletion.clear();
1851 mLatestNickCompletion.clear();
1852 return;
1854 QTextCursor tcur = edChat->textCursor();
1855 int pos = tcur.position();
1856 //TODO: write regexp!
1857 QString nick = txt.left(pos);
1858 QRegExp nre("^\\S*:?\\s*");
1859 if (nick.isEmpty() || !nre.exactMatch(nick)) {
1861 mStartingNickCompletion.clear();
1862 mLatestNickCompletion.clear();
1864 return;
1866 nick = nick.trimmed();
1867 if (nick[nick.length()-1] == ':') nick.chop(1);
1868 nick = nick.trimmed();
1869 if (nick.isEmpty()) {
1870 mStartingNickCompletion.clear();
1871 mLatestNickCompletion.clear();
1872 return;
1874 QStringList ppl(mChatUser->persons());
1875 if (ppl.size() < 1) {
1876 mStartingNickCompletion.clear();
1877 mLatestNickCompletion.clear();
1878 return;
1880 // rebuild the list
1881 for (int f = ppl.size()-1; f >= 0; f--) {
1882 QString s(ppl[f]);
1883 PsycEntity u(s);
1884 PsycContact *cc = findContact(s);
1885 if (cc) s = cc->verbatim(); else s = u.nick();
1886 ppl[f] = s;
1888 ppl.sort();
1889 // find the position
1890 if (!mStartingNickCompletion.startsWith(nick) && !nick.startsWith(mStartingNickCompletion)) {
1891 mStartingNickCompletion.clear();
1892 mLatestNickCompletion.clear();
1894 int st = 0, cur = 0;
1895 if (mStartingNickCompletion.isEmpty()) {
1896 // first time
1897 mStartingNickCompletion = nick;
1898 } else {
1899 // continue
1900 //st = (ppl.indexOf(mLatestNickCompletion)+1)%ppl.size();
1901 for (int f = 0; f < ppl.size(); f++) {
1902 if (!ppl[f].compare(mLatestNickCompletion, Qt::CaseInsensitive)) {
1903 st = f; cur = (f+1)%ppl.size();
1904 break;
1908 nick = mStartingNickCompletion;
1909 mLatestNickCompletion.clear();
1910 qDebug() << st << cur << nick;
1911 do {
1912 QString s(ppl[cur]);
1913 qDebug() << s;
1914 if (s.length() >= nick.length() && !s.leftRef(nick.length()).compare(nick, Qt::CaseInsensitive)) {
1915 // match found!
1916 mLatestNickCompletion = s;
1917 nick = s+": ";
1918 qDebug() << "found!" << nick;
1919 txt = edChat->toPlainText();
1920 // shitty code!
1921 int i = nre.indexIn(txt);
1922 if (i >= 0) txt.remove(0, nre.matchedLength());
1924 while (!txt.isEmpty() || txt[0].isSpace()) txt.remove(0, 1);
1925 while (!txt.isEmpty() && !(txt[0].isSpace()) && txt[0] != ':') txt.remove(0, 1);
1926 if (!txt.isEmpty() && txt[0] == ':') txt.remove(0, 1);
1927 if (!txt.isEmpty() || txt[0].isSpace()) nick.chop(1);
1929 txt.prepend(nick);
1930 mChBlock = true;
1931 edChat->setPlainText(txt);
1932 tcur.setPosition(nick.length());
1933 edChat->setTextCursor(tcur);
1934 mChBlock = false;
1935 break;
1937 cur = (cur+1)%ppl.size();
1938 } while (cur != st);
1939 // do nothing
1943 void ChatForm::keyPressEvent (QKeyEvent *event) {
1944 Q_UNUSED(event)
1946 if (event->modifiers() != Qt::CTRL) {
1947 if (event->key() == Qt::Key_Escape) {
1948 if (toBoolOpt("/app/eschides")) hide(); else showMinimized();
1949 return;
1953 if (edChat->hasFocus()) return;
1954 edChat->setFocus();
1955 QCoreApplication::sendEvent(edChat, event);
1956 return;
1962 void ChatForm::showEvent (QShowEvent *event) {
1963 //if (toBoolOptCU("/chat/show/forcescroll", false)) scrollToBottom();
1964 edChat->setFocus();
1965 mUnreadInChat = false;
1966 if (mChatUser) {
1967 mPopMan->removeById("", mChatUser->uni().toLower());
1968 if (mFMan) mFMan->noMessages(mChatUser->uni());
1970 QWidget::showEvent(event);
1971 checkBlink();
1975 void ChatForm::closeEvent (QCloseEvent *event) {
1976 if (!mCloseWaiting) {
1977 mCloseWaiting = true;
1978 storeAppData();
1979 bool h = mProto->isConnected();
1980 setStatus(PsycProto::Offline);
1981 if (h) {
1982 QTimer::singleShot(5000, this, SLOT(close()));
1983 event->ignore();
1984 return;
1987 event->accept();
1991 ///////////////////////////////////////////////////////////////////////////////
1992 void ChatForm::scrollToBottom () {
1993 runChatJS("do{var ___tmp___=window.pageYOffset;window.scrollTo(0,1000000);}while(window.pageYOffset!=___tmp___);");
1997 void ChatForm::translateMessage () {
1998 mChBlock = true;
1999 QString txt = edChat->toPlainText();
2000 if (edChat->textCursor().hasSelection()){
2001 QString selText = edChat->textCursor().selectedText();
2002 // Qt internally uses U+FDD0 and U+FDD1 to mark the beginning and the end of frames.
2003 // They should be seen as non-printable characters, as trying to display them leads
2004 // to a crash caused by a Qt "noBlockInString" assertion.
2005 selText.replace(QChar(0xFDD0), " ");
2006 selText.replace(QChar(0xFDD1), " ");
2007 selText.replace(QChar(0x2028), "\n");
2008 selText = recodeMsg(selText);
2009 txt.replace(edChat->textCursor().selectionStart(), selText.length(), selText);
2010 } else txt = recodeMsg(txt);
2011 edChat->clear();
2012 edChat->insertPlainText(txt);
2013 edChat->ensureCursorVisible();
2014 edChat->setFocus();
2015 mChBlock = false;
2016 mStartingNickCompletion.clear();
2017 mLatestNickCompletion.clear();
2021 ///////////////////////////////////////////////////////////////////////////////
2022 void ChatForm::doSendMessage () {
2023 mStartingNickCompletion.clear();
2024 mLatestNickCompletion.clear();
2026 QString txt(edChat->toPlainText());
2028 if (txt.length() > 1 && txt[0] == '/') {
2029 edChat->clear();
2030 mEngCmds->doCommand(txt);
2031 return;
2034 if (!mProto->isLoggedIn()) return;
2036 txt = txt.trimmed();
2037 edChat->clear();
2038 if (txt.isEmpty()) return;
2039 if (!mChatUser) return; //FIXME: send to main entity as command?
2040 QString act;
2041 QString achr(toStringOpt("/editor/actionstart", QString(), mChatUser->uni()));
2042 if (!achr.isEmpty() && txt.startsWith(achr)) {
2043 // the first line is action
2044 int idx = txt.indexOf('\n');
2045 if (idx < 0) idx = txt.length();
2046 act = txt.left(idx);
2047 act.remove(0, achr.length());
2048 act = act.simplified();
2049 txt.remove(0, idx+1);
2050 txt = txt.trimmed();
2051 if (txt.isEmpty() && act.isEmpty()) return;
2053 mWasSentTo << mChatUser->uni().toLower();
2054 #ifdef USE_OTR
2055 otrSendMessage(mChatUser->uni(), txt, act);
2056 #else
2057 mProto->sendMessage(mChatUser->uni(), txt, act);
2058 #endif
2059 if (!mChatUser->isOTRActive()) {
2060 // no OTR, check 'immediate output mode'
2061 #ifdef USE_OTR
2062 dlogf("NO OTR!");
2063 #endif
2064 if (!toBoolOptCU("/chat/messaging/immediateout", false)) return; // out this when echo comes
2065 } else {
2066 #ifdef USE_OTR
2067 dlogf("OTR active!");
2068 #endif
2069 //qDebug() << txt;
2071 QDateTime now(QDateTime::currentDateTime());
2072 PsycEntity me(mProto->uni());
2073 addMessageToHistory(*mChatUser->entity(), me, txt, act, now, MessageRead);
2074 addMessageToChat(me, txt, act, now);
2078 ///////////////////////////////////////////////////////////////////////////////
2079 QIcon ChatForm::LinuxizeTrayIcon (const QIcon &ico) {
2080 QPixmap ipm(ico.pixmap(16, 16));
2081 QPixmap fpm(ipm.width(), ipm.height());
2082 fpm.fill(Qt::black);
2084 QPainter p(&fpm);
2085 p.drawPixmap(0, 0, ipm);
2086 p.end();
2088 return QIcon(fpm);
2092 QIcon ChatForm::statusIcon (PsycProto::Status st) {
2093 switch (st) {
2094 case PsycProto::Internal: return QIcon(":/icons/status/connecting.png");
2095 case PsycProto::Offline: return action_stoffline->icon();
2096 case PsycProto::Vacation: return action_stvacation->icon();
2097 case PsycProto::Away: return action_staway->icon();
2098 case PsycProto::DND: return action_stdnd->icon();
2099 case PsycProto::Nearby: return action_stnear->icon();
2100 case PsycProto::Busy: return action_stbusy->icon();
2101 case PsycProto::Here: return action_sthere->icon();
2102 case PsycProto::FFC: return action_stffc->icon();
2103 case PsycProto::Realtime: return action_strtime->icon();
2104 default: ;
2106 return action_stoffline->icon();
2110 void ChatForm::setStatus (PsycProto::Status st, bool doSend) {
2111 bool connecting = false;
2112 if (mMyStatus == PsycProto::Offline && st != PsycProto::Offline) {
2113 connecting = true;
2114 if (doSend) mProto->connectToServer();
2116 if (mMyStatus != PsycProto::Offline && st == PsycProto::Offline) {
2117 if (doSend) {
2118 disconnectAllOTR();
2119 if (mProto->isLoggedIn()) storeAppData();
2120 mProto->logoff();
2124 switch (st) {
2125 //case PsycProto::Internal: return QIcon(":/icons/status/connecting.png");
2126 case PsycProto::Offline:
2127 action_stoffline->setChecked(true);
2128 break;
2129 case PsycProto::Vacation:
2130 action_stvacation->setChecked(true);
2131 break;
2132 case PsycProto::Away:
2133 action_staway->setChecked(true);
2134 break;
2135 case PsycProto::DND:
2136 action_stdnd->setChecked(true);
2137 break;
2138 case PsycProto::Nearby:
2139 action_stnear->setChecked(true);
2140 break;
2141 case PsycProto::Busy:
2142 action_stbusy->setChecked(true);
2143 break;
2144 case PsycProto::Here:
2145 action_sthere->setChecked(true);
2146 break;
2147 case PsycProto::FFC:
2148 action_stffc->setChecked(true);
2149 break;
2150 case PsycProto::Realtime:
2151 action_strtime->setChecked(true);
2152 break;
2153 default: ;
2155 if (connecting) btStatus->setIcon(QIcon(":/icons/status/connecting.png"));
2156 else btStatus->setIcon(statusIcon(st));
2158 if (toBoolOpt("/status/trayicon")) {
2159 #ifndef WIN32
2160 mTrayIcon->setIcon(LinuxizeTrayIcon(statusIcon(st)));
2161 #else
2162 mTrayIcon->setIcon(statusIcon(st));
2163 #endif
2166 mMyStatus = st;
2167 if (!doSend) return;
2168 //if (mProto->isLoggedIn() && (mMyStatus != mProto->status() || mMyMood != mProto->mood())) {
2169 mProto->setStatusText(toStringOpt("/status/text"));
2170 if (mProto->isLoggedIn()) {
2171 //if (mMyStatus == mProto->status()) mProto->setStatus(PsycProto::Internal, mProto->statusText(), mMyMood);
2172 mProto->setStatus(mMyStatus, mProto->statusText(), mMyMood);
2173 //mProto->setStatus(mMyStatus, "seven are they!", mMyMood);
2178 void ChatForm::on_action_stoffline_triggered (bool) { setStatus(PsycProto::Offline); }
2179 void ChatForm::on_action_stvacation_triggered (bool) { setStatus(PsycProto::Vacation); }
2180 void ChatForm::on_action_staway_triggered (bool) { setStatus(PsycProto::Away); }
2181 void ChatForm::on_action_stdnd_triggered (bool) { setStatus(PsycProto::DND); }
2182 void ChatForm::on_action_stnear_triggered (bool) { setStatus(PsycProto::Nearby); }
2183 void ChatForm::on_action_stbusy_triggered (bool) { setStatus(PsycProto::Busy); }
2184 void ChatForm::on_action_sthere_triggered (bool) { setStatus(PsycProto::Here); }
2185 void ChatForm::on_action_stffc_triggered (bool) { setStatus(PsycProto::FFC); }
2186 void ChatForm::on_action_strtime_triggered (bool) { setStatus(PsycProto::Realtime); }
2189 ///////////////////////////////////////////////////////////////////////////////
2190 void ChatForm::setMood (PsycProto::Mood md) {
2191 switch (md) {
2192 case PsycProto::Unspecified: action_moodunspecified->setChecked(true); break;
2193 case PsycProto::Dead: action_mooddead->setChecked(true); break;
2194 case PsycProto::Angry: action_moodangry->setChecked(true); break;
2195 case PsycProto::Sad: action_moodsad->setChecked(true); break;
2196 case PsycProto::Moody: action_moodmoody->setChecked(true); break;
2197 case PsycProto::Okay: action_moodokay->setChecked(true); break;
2198 case PsycProto::Good: action_moodgood->setChecked(true); break;
2199 case PsycProto::Happy: action_moodhappy->setChecked(true); break;
2200 case PsycProto::Bright: action_moodbright->setChecked(true); break;
2201 case PsycProto::Nirvana: action_moodnirvana->setChecked(true); break;
2203 mMyMood = md;
2204 if (!mProto->isLoggedIn()) return;
2205 if (mMyStatus != mProto->status()) setStatus(mMyStatus);
2206 else {
2207 mProto->setStatusText(toStringOpt("/status/text"));
2208 if (mMyStatus != PsycProto::Offline) {
2209 //FIXME
2210 mProto->setStatus(PsycProto::Offline, mProto->statusText(), mMyMood);
2211 mProto->setStatus(PsycProto::Here, mProto->statusText(), mMyMood);
2212 if (mMyStatus != PsycProto::Here) mProto->setStatus(mMyStatus, mProto->statusText(), mMyMood);
2218 void ChatForm::on_action_moodunspecified_triggered (bool) { setMood(PsycProto::Unspecified); }
2219 void ChatForm::on_action_mooddead_triggered (bool) { setMood(PsycProto::Dead); }
2220 void ChatForm::on_action_moodangry_triggered (bool) { setMood(PsycProto::Angry); }
2221 void ChatForm::on_action_moodsad_triggered (bool) { setMood(PsycProto::Sad); }
2222 void ChatForm::on_action_moodmoody_triggered (bool) { setMood(PsycProto::Moody); }
2223 void ChatForm::on_action_moodokay_triggered (bool) { setMood(PsycProto::Okay); }
2224 void ChatForm::on_action_moodgood_triggered (bool) { setMood(PsycProto::Good); }
2225 void ChatForm::on_action_moodhappy_triggered (bool) { setMood(PsycProto::Happy); }
2226 void ChatForm::on_action_moodbright_triggered (bool) { setMood(PsycProto::Bright); }
2227 void ChatForm::on_action_moodnirvana_triggered (bool) { setMood(PsycProto::Nirvana); }
2230 ///////////////////////////////////////////////////////////////////////////////
2231 void ChatForm::on_action_showconsole_triggered (bool) {
2232 if (!mConForm) {
2233 mConForm = new ConsoleForm(this);
2234 mConForm->mUni = mProto->uni();
2235 connect(mConForm, SIGNAL(destroyed()), this, SLOT(consoleDestroyed()));
2236 mConForm->cbActive->setChecked(true);
2238 if (toBoolOpt("/pktconsolewindow/saved", false)) {
2239 int x = toIntOpt("/pktconsolewindow/x");
2240 int y = toIntOpt("/pktconsolewindow/y");
2241 int w = toIntOpt("/pktconsolewindow/w");
2242 int h = toIntOpt("/pktconsolewindow/h");
2243 mConForm->setGeometry(x, y, w, h);
2245 QByteArray geo(mSDB->toByteArray("/pktconsolewindow/geometry"));
2246 mConForm->restoreGeometry(geo);
2248 mConForm->cbActive->setChecked(toBoolOpt("/pktconsolewindow/active", true));
2251 mConForm->hide();
2252 mConForm->show();
2253 if (mConForm->isMinimized()) mConForm->showNormal();
2254 mConForm->activateWindow();
2258 void ChatForm::onUserPktSend () {
2259 if (!mProto->isLoggedIn()) return;
2260 PktForm *frm = dynamic_cast<PktForm *>(sender());
2261 if (!frm) return;
2262 QStringList sl(frm->edPacket->toPlainText().trimmed().split("\n"));
2263 //qDebug() << sl;
2264 if (sl.count() > 1) {
2265 PsycPacket pkt;
2266 foreach (const QString &s, sl) {
2267 if (s == ".") break;
2268 pkt << s;
2270 mProto->sendPacket(pkt);
2272 frm->close();
2276 void ChatForm::on_action_sendpacket_triggered (bool) {
2277 PktForm *frm = new PktForm(this);
2278 //connect(frm->btSend, SIGNAL(clicked()), this, SLOT(onUserPktSend()));
2279 connect(frm, SIGNAL(send()), this, SLOT(onUserPktSend()));
2280 PsycPacket pkt("_");
2281 pkt.put("_target", mProto->uni());
2282 pkt.put("_source_identification", mProto->uni());
2283 pkt.endRoute();
2284 if (mChatUser) {
2285 pkt.put("_person", mChatUser->uni());
2287 QString s(pkt.toString());
2288 if (s.rightRef(3) == "\n.\n") s.chop(3);
2289 frm->edPacket->setPlainText(s);
2290 frm->edPacket->moveCursor(QTextCursor::End);
2291 frm->edPacket->ensureCursorVisible();
2292 frm->edPacket->setFocus(Qt::OtherFocusReason);
2293 frm->show();
2297 ///////////////////////////////////////////////////////////////////////////////
2298 bool ChatForm::addMessageToChat (const PsycEntity &from, const QString &msg, const QString &action, const QDateTime &date) {
2299 bool mine = from.sameUNI(mProto->uni());
2301 PsycContact *cc = findContact(from);
2302 QString vn(from.verbatim());
2303 if (cc) vn = cc->verbatim();
2305 QString act(action), text(msg);
2306 if (act.startsWith(" DONTTOUCH|")) {
2307 act.remove(0, 11);
2308 } else {
2309 text = K8Str::fixURLs(msg);
2310 text.replace("\n", "<br />");
2312 act = K8Str::escapeStr(act);
2314 QString js(QString("addChatMessage({")+
2315 "ismine:"+bool2js(mine)+","+
2316 "unixdate:"+QString::number(date.toTime_t())+","+
2317 "uni:"+K8Str::jsStr(from.uni())+","+
2318 "text:"+K8Str::jsStr(text)+","+
2319 "action:"+K8Str::jsStr(act)+
2320 "});"
2323 runChatJS(js);
2324 //qDebug() << "addChatMessage()";
2325 return true;
2329 bool ChatForm::addMessageToHistory (const PsycEntity &dest, const PsycEntity &src, const QString &text,
2330 const QString &action, const QDateTime &time, UnreadFlag isUnread)
2332 //qDebug() << "addMessageToHistory: acc:" << mProto->uni() << "dest:" << dest.uni() << "from:" << src.uni();
2333 if (text.startsWith("?OTR")) return true; // skip OTR messages
2335 QString hfname;
2336 if (isUnread == MessageUnread) {
2337 hfname = settingsUnreadHistoryFile(mAccName, dest.uni());
2338 } else {
2339 hfname = settingsHistoryFile(mAccName, dest.uni());
2341 HistoryFile hf(hfname, mProto->uni());
2342 if (!hf.open(HistoryFile::Write)) return false;
2343 QString uni(src.uni());
2344 HistoryMessage msg;
2345 msg.uni = uni;
2346 msg.date = time;
2347 msg.text = text;
2348 msg.text.replace("\r\n", "\n");
2349 msg.action = action;
2350 msg.action.replace("\r\n", "\n");
2351 msg.type = mProto->uni().compare(uni, Qt::CaseInsensitive) ? HistoryMessage::Incoming : HistoryMessage::Outgoing;
2352 msg.valid = true; // just in case
2353 hf.append(msg);
2354 hf.close();
2355 //if (isUnread != MessageRead) addMessageToHistory(dest, src, text, actuib, time, MessageRead);
2356 return true;
2360 void ChatForm::startChatWith (const QString &aUNI, bool dontRearrange) {
2361 edChat->setFocus();
2362 PsycEntity user(aUNI);
2363 clearChat();
2364 if (!user.isValid()) {
2365 setWindowTitle(DYSKINESIA);
2366 mChatUser = 0;
2367 mSDB->set("/chatwindow/uni", "");
2368 mDoTypo = toBoolOptCU("/editor/typography");
2369 runCListJS("chatStarted('');");
2370 runChatJS("chatStarted('');");
2371 checkBlink();
2372 return;
2374 bool isNew = false;
2375 PsycContact *cc = addContact(aUNI, "", &isNew);
2376 if (!cc) return;
2378 int idx = mTabList.indexOf(cc);
2379 if (dontRearrange) {
2380 if (idx < 0) mTabList << cc;
2381 } else {
2382 if (idx >= 0) mTabList.removeAt(idx);
2383 mTabList.insert(0, cc);
2386 if (isNew && !user.isPlace()) {
2387 cc->setAuthorized(false);
2388 cc->setStatus(PsycProto::Offline);
2389 saveOneContact(cc);
2391 mChatUser = cc;
2392 setWindowTitle(cc->verbatim()+" -- "+DYSKINESIA);
2393 mSDB->set("/chatwindow/uni", cc->uni());
2394 mDoTypo = toBoolOptCU("/editor/typography");
2395 lbChatDest->setText(cc->verbatim()+" ["+cc->uni()+"]");
2397 runCListJS("chatStarted("+K8Str::jsStr(cc->uni())+");");
2398 runChatJS("chatStarted("+K8Str::jsStr(cc->uni())+");");
2400 bool wasHist = false;
2401 int toShow = toIntOptCU("/history/showcount", 3);
2402 if (toShow > 0) {
2403 HistoryFile hf(settingsHistoryFile(mAccName, cc->uni()), mProto->uni());
2404 if (hf.open(HistoryFile::Read)) {
2405 //fprintf(stderr, "!!!\n");
2406 HistoryMessage msg;
2407 toShow = hf.count()-toShow;
2408 if (toShow < 0) toShow = 0;
2409 //fprintf(stderr, " %i\n", toShow);
2410 while (hf.read(toShow++, msg)) {
2411 if (msg.valid) {
2412 //qDebug() << msg.text;
2413 PsycEntity uu(msg.uni);
2414 addMessageToChat(uu, msg.text, msg.action, msg.date);
2415 wasHist = true;
2418 hf.close();
2422 if (cc->messageCount() > 0) {
2423 if (wasHist) runChatJS("addHR();");
2424 QList<MyMessage *>list(cc->messages());
2425 QString uni(cc->uni());
2426 QString nick(cc->nick());
2427 foreach (MyMessage *msg, list) {
2428 //qDebug() << "unread of:" << cc->entity()->uni() << "from:" << msg->user.uni();
2429 addMessageToHistory(*cc->entity(), msg->user, msg->text, msg->action, msg->time, MessageRead);
2430 addMessageToChat(msg->user, msg->text, msg->action, msg->time);
2432 cc->clearMessages();
2433 // remove saved history
2434 HistoryFile hf(settingsUnreadHistoryFile(mAccName, cc->uni()), mProto->uni());
2435 hf.remove();
2438 mFMan->noMessages(cc->uni());
2440 redrawContact(cc);
2441 scrollToBottom();
2442 mPopMan->removeById("", cc->uni().toLower());
2444 //knockAutosave();
2445 mSDB->set("/chatwindow/uni", cc->uni());
2446 checkBlink();
2448 edChat->setPlainText(toStringOptCU("/editor/state/text", ""));
2449 mStartingNickCompletion.clear();
2450 mLatestNickCompletion.clear();
2451 int cpos = toIntOptCU("/editor/state/curpos", -1);
2452 if (cpos < 0) edChat->moveCursor(QTextCursor::End);
2453 else {
2454 QTextCursor cur(edChat->textCursor());
2455 cur.setPosition(cpos);
2456 edChat->setTextCursor(cur);
2458 edChat->ensureCursorVisible();
2462 void ChatForm::markAsRead (const QString &aUNI) {
2463 PsycContact *cc = findContact(aUNI);
2464 if (!cc || cc->messageCount() < 1) return;
2465 QList<MyMessage *>list(cc->messages());
2466 QString uni(cc->uni());
2467 foreach (MyMessage *msg, list) {
2468 //qDebug() << "unread of:" << cc->entity()->uni() << "from:" << msg->user.uni();
2469 addMessageToHistory(*cc->entity(), msg->user, msg->text, msg->action, msg->time, MessageRead);
2471 cc->clearMessages();
2472 // remove saved history
2473 HistoryFile hf(settingsUnreadHistoryFile(mAccName, cc->uni()), mProto->uni());
2474 hf.remove();
2475 mFMan->noMessages(cc->uni());
2476 redrawContact(cc);
2477 checkBlink();
2481 ///////////////////////////////////////////////////////////////////////////////
2482 void ChatForm::onPopupClick (const QString &type, const QString &id, Qt::MouseButton button) {
2483 if (button != Qt::LeftButton) return;
2484 if (isMinimized() || !isVisible()) showNormal();
2485 activateWindow();
2486 if (type != "private" && type != "public" && type != "status") return;
2487 PsycEntity user(id);
2488 if (!user.isValid()) return;
2489 if (!mChatUser || !mChatUser->entity()->sameUNI(user.uni())) startChatWith(user.uni());
2490 mPopMan->removeById("", id);
2494 ///////////////////////////////////////////////////////////////////////////////
2495 void ChatForm::onConnected () {
2496 mWasSentTo.clear();
2500 void ChatForm::onDisconnected () {
2501 foreach (PsycContact *cc, mContactList) {
2502 cc->setStatus(PsycProto::Offline);
2503 if (cc->isPlace()) onPlaceLeaved(*cc->entity()); else otrDisconnect(cc->uni());
2504 mFMan->newStatus(cc->uni(), PsycProto::Offline);
2505 redrawContact(cc);
2507 mMyStatus = PsycProto::Offline;
2508 setStatus(mMyStatus, false);
2509 if (mCloseWaiting) close();
2513 void ChatForm::onNetError (const QString &errStr) {
2514 qDebug() << "NETWORK ERROR:" << errStr;
2515 //QMessageBox::warning(this, tr("PSYC ERROR"), tr("NETWORK ERROR: ")+errStr);
2516 if (mPopupActive) showErrorPopup(tr("NETWORK ERROR: ")+errStr);
2520 // export packet to WebKit JS
2521 void ChatForm::packetToJSAPI (const PsycPacket &pkt) {
2522 // build a dictionary
2523 QString pktS = "psycpkt={";
2524 pktS += K8Str::jsStr("method")+":"+K8Str::jsStr(pkt.method());
2525 pktS += ","+K8Str::jsStr("vars")+":{";
2526 bool needComma = false;
2527 foreach (const QString &n, pkt.routeVars()) {
2528 if (needComma) pktS += ","; else needComma = true;
2529 pktS += K8Str::jsStr(n)+":"+K8Str::jsStr(pkt.get(n));
2531 foreach (const QString &n, pkt.vars()) {
2532 if (needComma) pktS += ","; else needComma = true;
2533 pktS += K8Str::jsStr(n)+":"+K8Str::jsStr(pkt.get(n));
2535 pktS += "},rvars:{";
2536 needComma = false;
2537 foreach (const QString &n, pkt.routeVars()) {
2538 if (needComma) pktS += ","; else needComma = true;
2539 pktS += K8Str::jsStr(n)+":true";
2541 pktS += "},"+K8Str::jsStr("body")+":"+K8Str::jsStr(pkt.body());
2542 pktS += "};";
2544 //qDebug() << pktS;
2546 webCList->page()->mainFrame()->evaluateJavaScript(pktS);
2547 webChat->page()->mainFrame()->evaluateJavaScript(pktS);
2551 void ChatForm::onPacket (const PsycPacket &pkt) {
2552 //Q_UNUSED(pkt)
2553 //qDebug() << ">>>GOT PACKET\n:" << pkt.toString() << "========================";
2554 QString m(pkt.method());
2555 if (m == "_status_storage") {
2556 parseStoredAppData(pkt);
2557 return;
2559 if (m == "_echo_request_store") return;
2561 if (!mChatUser && toBoolOpt("/debug/dumptochat", false)) {
2562 QString s(pkt.toString());
2563 if (s.rightRef(3) == "\n.\n") s.chop(3);
2564 s = K8Str::deHTMLize(s, K8Str::deHTMLnone);
2565 PsycEntity u("psyc://0.0.0.0/~psycman");
2566 addMessageToChat(u, s.trimmed(), "", QDateTime::currentDateTime());
2569 if (mConForm) {
2570 mConForm->addPacket(pkt, false);
2573 // call WebKit JS code which can handle this packet
2574 // WARNING: there is no way to change/cancel packet for now!
2575 // signal/slot thingy is generally bad with such thing
2576 // maybe i should change the packet processing so
2577 // PsycProto first emits signal 'packetpre' or smth.,
2578 // and handlers can change/cancel packet then?
2579 packetToJSAPI(pkt);
2580 runCListJS("onPSYCPacket();");
2581 runChatJS("onPSYCPacket();");
2585 void ChatForm::onPacketSent (const PsycPacket &pkt) {
2586 if (!mConForm) return;
2587 QString m(pkt.method());
2588 if (m == "_request_store" || m == "_request_retrieve") return;
2589 mConForm->addPacket(pkt, true);
2593 void ChatForm::onNeedPassword () {
2594 mProto->sendPassword(mPassword);
2598 void ChatForm::onInvalidPassword () {
2599 //QMessageBox::warning(this, tr("PSYC ERROR"), tr("ERROR: invalid password!"));
2600 if (mPopupActive) showErrorPopup(tr("ERROR: invalid password!"));
2601 setStatus(PsycProto::Offline);
2605 void ChatForm::onLoginComplete () {
2606 mWasSentTo.clear();
2607 if (mServerStore) requestStoredAppData();
2608 PsycProto::Status st = mMyStatus;
2610 if (mMyStatus != PsycProto::Offline) {
2611 mProto->setStatusText(toStringOpt("/status/text"));
2612 mProto->setStatus(PsycProto::Offline, mProto->statusText(), mMyMood);
2613 mProto->setStatus(PsycProto::Here, mProto->statusText(), mMyMood);
2614 //mProto->setStatus(mMyStatus, mProto->statusText(), mMyMood);
2616 setStatus(st);
2618 //mProto->setStatus(st, mProto->statusText(), mProto->mood());
2619 //show(mProto->nick(), mProto->uni(), "connected!");
2620 mProto->requestPeerList();
2621 //storeAppData();
2625 void ChatForm::onMessage (const PsycEntity &place, const PsycEntity &user, const QDateTime &time,
2626 const QString &text, const QString &action, const QString &forPopup)
2628 if (place.isValid() && toBoolOpt("/ignored", false, user.uni().toLower())) return;
2629 bool privMsg;
2630 QString act(K8Str::deHTMLize(action, K8Str::deHTMLnone));
2631 QString textD(K8Str::deHTMLize(text, K8Str::deHTMLnone));
2632 QString actX(act);
2633 if (actX.startsWith(" DONTTOUCH|")) {
2634 actX.remove(0, 11);
2635 actX = actX.simplified();
2638 PsycEntity histDest;
2639 if (place.isValid()) {
2640 histDest = place;
2641 privMsg = false;
2642 } else {
2643 histDest = user;
2644 privMsg = true;
2646 bool toCurrChat = (mChatUser && mChatUser->entity()->sameUNI(histDest.uni()));
2647 PsycContact *cc = 0;
2648 if (!toCurrChat) cc = addContact(histDest); // either place or person
2650 #ifdef USE_OTR
2651 if (privMsg) {
2652 QString realText(otrGotMessage(user.uni(), text));
2653 if (realText.isNull()) return; // internal OTR message
2654 textD = K8Str::deHTMLize(realText, K8Str::deHTMLnone);
2656 #endif
2658 bool showPopup;
2659 UnreadFlag urf;
2660 if (toCurrChat) {
2661 // to chat
2662 //showPopup = false;
2663 urf = MessageRead;
2664 runChatJS("isChatAtBottom();");
2665 showPopup = toBoolOpt("/popup/active", true);
2666 addMessageToChat(user, textD, act, time);
2667 if (isMinimized() || !isActiveWindow() || !isVisible()) {
2668 mUnreadInChat = true;
2669 mFMan->newMessage(mChatUser->uni());
2670 } else {
2671 mUnreadInChat = false;
2672 if (showPopup && toIntOptCU("/popup/mode", 1) != 0) {
2673 showPopup = !mWasAtBottom;
2674 } else showPopup = false;
2675 //qDebug() << showPopup << mWasAtBottom << toIntOptCU("/popup/mode", 1);
2676 mFMan->noMessages(mChatUser->uni());
2678 redrawContact(mChatUser);
2679 } else {
2680 // to unread
2681 showPopup = toBoolOpt("/popup/active", true);
2682 urf = MessageUnread;
2683 if (cc) {
2684 cc->addMessage(user, textD, act, time);
2685 mFMan->newMessage(cc->uni());
2686 redrawContact(cc);
2689 addMessageToHistory(histDest, user, textD, act, time, urf);
2690 // show popup
2691 if (showPopup &&
2692 toBoolOpt("/popup/active", true, histDest.uni()) &&
2693 toBoolOpt("/popup/active", true, user.uni())
2695 QString ptext(forPopup);
2696 if (ptext.isEmpty()) ptext = textD;
2697 if (privMsg) showPrivatePopup(user, ptext, actX);
2698 else showPublicPopup(place, user, ptext, actX);
2700 // update tray
2701 checkBlink();
2705 void ChatForm::onMessageEcho (const PsycEntity &place, const PsycEntity &user, const PsycEntity &target, const QDateTime &time,
2706 const QString &text, const QString &action, const QString &tag)
2708 Q_UNUSED(user)
2709 Q_UNUSED(tag)
2710 //if (!mChatUser) return; //FIXME: should remember this?
2711 if (toBoolOptCU("/chat/messaging/immediateout", false)) return; // already done
2712 PsycEntity me(mProto->uni());
2713 PsycEntity tgt;
2714 QString realText(text);
2715 if (place.isValid()) {
2716 if (!mWasSentTo.contains(place.uni().toLower())) return; // nothing was sent there, history echo
2717 addMessageToHistory(place, me, realText, action, time, MessageRead);
2718 tgt = place;
2719 } else {
2720 //qDebug() << "echo from" << user.uni() << "tag:" << tag;
2722 PsycContact *cc = findContact(user.uni());
2723 if (!cc) dlogf("shit!");
2724 else {
2725 if (!cc->isOTRActive()) dlogf("fuck!");
2726 if (cc && cc->isOTRActive()) return; // OTR echo
2729 if (text.startsWith("?OTR")) return; // OTR message
2730 if (!mWasSentTo.contains(target.uni().toLower())) return; // nothing was sent there, history echo
2732 #ifdef USE_OTR
2733 realText = otrGotMessage(user.uni(), realText);
2734 if (realText.isNull()) return; // internal OTR message
2735 #endif
2737 addMessageToHistory(target, me, realText, action, time, MessageRead);
2738 tgt = target;
2740 if (mChatUser && *mChatUser->entity() == tgt) addMessageToChat(me, realText, action, time);
2744 void ChatForm::onHistoryMessage (const PsycEntity &place, const PsycEntity &user, const QDateTime &time,
2745 const QString &text, const QString &action)
2747 //qDebug() << "history" << place.uni() << user.uni();
2748 bool addMsg = true;
2749 if (place.isValid()) {
2750 PsycEntity hus(place.isValid()?place:user);
2751 HistoryFile hf(settingsHistoryFile(mAccName, hus.uni()), mProto->uni());
2752 if (hf.open(HistoryFile::Read)) {
2753 // walk history backward and look for this message
2754 HistoryMessage msg;
2755 int wlk, cnt;
2756 for (wlk = 64, cnt = hf.count()-1; wlk > 0 && cnt >= 0; --cnt, --wlk) {
2757 if (!hf.read(cnt, msg)) break;
2758 if (!msg.valid) continue;
2759 if (!user.sameUNI(msg.uni)) continue;
2760 if (msg.action == action && msg.text == text) { addMsg = false; break; }
2762 hf.close();
2764 } else {
2765 addMsg = !text.startsWith("?OTR");
2767 if (!addMsg) return; // duplicate found
2768 onMessage(place, user, time, text, action);
2772 void ChatForm::onUserStatusChanged (const PsycEntity &user) {
2773 //qDebug() << "status:" << user.uni() << user.availDegree();
2774 bool showPopup = true;
2775 PsycContact *cc = addContact(user.uni());
2776 if (cc) {
2777 if (user.isXMPP() && !user.channel().isEmpty()) cc->entity()->setChannel(user.channel());
2778 if (user.status() != PsycProto::Offline) {
2779 if (!cc->authorized()) {
2780 cc->setAuthorized(true);
2781 //saveOneContact(cc);
2783 QString st(user.statusText().simplified());
2784 QString ost(cc->statusText());
2785 if (ost.isEmpty() || !st.isEmpty()) cc->setStatusText(st);
2786 cc->setLastSeen(QDateTime::currentDateTime());
2787 saveOneContact(cc);
2788 } else {
2789 if (!cc->isPlace()) otrDisconnect(cc->uni());
2791 //PsycProto::Status ost = cc->status();
2792 cc->setStatus(user.status());
2793 //showPopup = (ost != user.status());
2795 if (showPopup) showStatusPopup(user);
2796 if (cc) {
2797 mFMan->newStatus(cc->uni(), cc->status());
2798 redrawContact(cc);
2803 void ChatForm::onSelfStatusChanged () {
2804 if (!mProto->isLoggedIn()) return;
2805 if ((int)(mProto->status()) < 2) {
2806 if (mProto->status() == PsycProto::Offline) {
2807 showErrorPopup(tr("STATUS CHANGED: offline"));
2809 if (toBoolOpt("/status/trayicon")) mTrayIcon->setIcon(statusIcon(PsycProto::Offline));
2810 return;
2812 //qDebug() << "selfstatus:" << (int)(mProto->availDegree());
2813 setStatus(mProto->status(), false);
2817 void ChatForm::onNotificationType (const PsycEntity &dest) {
2818 //qDebug() << "nt:" << dest.uni() << "t:" << dest.notification();
2819 PsycContact *cc = addContact(dest.uni());
2820 PsycProto::Notification nt = dest.notification();
2821 bool oldAuth = cc->authorized();
2822 if (nt == PsycProto::Offered || nt == PsycProto::Pending) cc->setAuthorized(false);
2823 else cc->setAuthorized(true);
2824 if (oldAuth != cc->authorized()) saveOneContact(cc);
2825 redrawContact(cc);
2829 ///////////////////////////////////////////////////////////////////////////////
2830 void ChatForm::onPlaceEntered (const PsycEntity &place) {
2831 //qDebug() << "entered:" << place.uni();
2832 bool isNew;
2833 PsycContact *cc = addContact(place, "", &isNew);
2834 if (!cc) return;
2835 cc->setStatus(PsycProto::Here);
2836 if (isNew) redrawContact(cc); // allow cl to add it
2837 if (cc->hasPerson(mProto->uni())) return;
2838 //addContact(mProto->uni(), "", 0, TempContact);
2839 cc->addPerson(mProto->uni());
2840 QString js("meEnters("+K8Str::jsStr(place.uni())+");");
2842 runCListJS(js);
2843 redrawContact(cc);
2847 void ChatForm::onPlaceLeaved (const PsycEntity &place) {
2848 //qDebug() << "leaved:" << place.uni();
2849 bool isNew;
2850 PsycContact *cc = addContact(place, "", &isNew);
2851 if (!cc) return;
2852 //qDebug() << " place found/created";
2853 if (isNew) qDebug() << " actually, created";
2854 cc->setStatus(PsycProto::Offline);
2855 if (isNew) redrawContact(cc); // allow cl to add it
2856 //qDebug() << " we are there";
2857 //addContact(mProto->uni(), "", 0, TempContact);
2858 if (cc->hasPerson(mProto->uni())) cc->delPerson(mProto->uni());
2859 //qDebug() << " and now we not";
2861 runCListJS("meLeaves("+K8Str::jsStr(place.uni())+");");
2862 //qDebug() << " meLeaves()";
2863 redrawContact(cc);
2867 void ChatForm::onUserEntersPlace (const PsycEntity &place, const PsycEntity &who) {
2868 //qDebug() << "user entered to:" << place.uni() << who.uni();
2869 bool isNew;
2870 PsycContact *cc = addContact(place, "", &isNew);
2871 if (!cc) return;
2872 if (isNew) redrawContact(cc);
2873 addContact(who.uni(), "", 0, TempContact);
2874 if (!cc->hasPerson(who.uni())) cc->addPerson(who.uni());
2875 cc->setStatus(PsycProto::Here);
2877 runCListJS("userEnters("+K8Str::jsStr(place.uni())+","+K8Str::jsStr(who.uni())+");");
2878 //qDebug() << "userEnters()";
2879 redrawContact(cc);
2883 void ChatForm::onUserLeavesPlace (const PsycEntity &place, const PsycEntity &who) {
2884 //qDebug() << "user parted from:" << place.uni() << who.uni();
2885 bool isNew;
2886 PsycContact *cc = addContact(place, "", &isNew);
2887 if (!cc) return;
2888 if (isNew) redrawContact(cc);
2889 addContact(who.uni(), "", 0, TempContact);
2890 if (cc->hasPerson(who.uni())) cc->delPerson(who.uni());
2892 runCListJS("userLeaves("+K8Str::jsStr(place.uni())+","+K8Str::jsStr(who.uni())+")");
2893 //qDebug() << "userLeaves()";
2894 redrawContact(cc);
2898 void ChatForm::onEnterCustomPlace (const PsycEntity &place) {
2899 if (!place.isValid() || !place.isPlace()) return;
2900 if (!mProto->isLoggedIn()) return;
2901 mProto->enterPlace(place);
2905 void ChatForm::on_action_join_triggered (bool) {
2906 JoinWin *jw = new JoinWin(this);
2907 connect(jw, SIGNAL(enter(const PsycEntity &)), this, SLOT(onEnterCustomPlace(const PsycEntity &)));
2908 PsycEntity me(mProto->uni());
2909 jw->leNick->setText(me.nick());
2910 jw->leHost->setText(me.host()); //FIXME: port!
2911 jw->leRoom->setFocus();
2912 jw->show();
2916 ///////////////////////////////////////////////////////////////////////////////
2917 void ChatForm::onScratchpadUpdated (const QString &spUrl) {
2918 optPrint("scratchpad updated: "+urlStr(spUrl));
2922 void ChatForm::onPlaceWebExam (const PsycEntity &place, const PsycPacket &pkt) {
2923 if (!toBoolOptCU("/web/showexam")) return;
2924 QString txt(
2925 "place <b>"+K8Str::escapeStr(place.uni())+"</b> examined from web<br />"
2926 "page: "+urlStr(pkt.get("_web_page"))+"<br />"
2927 "on: "+urlStr(pkt.get("_web_on"))+"<br />"
2928 "from: "+urlStr(pkt.get("_web_from"))+"<br />"
2929 "referrer: "+urlStr(pkt.get("_web_referrer"))+"<br />"
2930 "browser: "+K8Str::escapeStr(pkt.get("_web_browser"))+"<br />"
2931 "host: "+K8Str::escapeStr(pkt.get("_host_name"))+"<br />"
2933 optPrint(txt);
2937 void ChatForm::onPlaceNews (const PsycEntity &place, const QString &channel, const QString &title, const QString &url) {
2938 QString msg, tmp;
2939 if (!channel.isEmpty()) msg = QString("<b>%1</b>: ").arg(channel);
2940 msg.append(K8Str::escapeStr(title));
2941 tmp = msg;
2942 if (!url.isEmpty()) {
2943 msg.append("\n<a href=\x22");
2944 msg.append(url);
2945 msg.append("\x22>link</a>");
2948 tmp = pkt.get("_project").trimmed();
2949 if (!tmp.isEmpty()) msg += "project: <b>"+K8Str::escapeStr(tmp)+"</b><br />";
2950 msg += "hash: "+K8Str::escapeStr(pkt.get("_hash_commit_long", pkt.get("_hash_commit")));
2951 msg += "<br />commiter: <b>"+K8Str::escapeStr(pkt.get("_nick_editor"))+"</b>";
2952 tmp = pkt.get("_mailto_editor").trimmed();
2953 if (!tmp.isEmpty()) msg += " (mailto: <i>"+K8Str::escapeStr(tmp)+"</i>)";
2954 msg += "<br /><i>"+K8Str::escapeStr(pkt.get("_comment"))+"</i>";
2956 tmp.prepend('\x1');
2957 onMessage(place, place, QDateTime::currentDateTime(), msg, " DONTTOUCH|", tmp);
2961 void ChatForm::onNoticeUpdate (const PsycEntity &place, const PsycPacket &pkt) {
2962 // update message
2963 QString msg, tmp;
2964 tmp = pkt.get("_project").trimmed();
2965 if (!tmp.isEmpty()) msg += "project: <b>"+K8Str::escapeStr(tmp)+"</b><br />";
2966 msg += "hash: "+K8Str::escapeStr(pkt.get("_hash_commit_long", pkt.get("_hash_commit")));
2967 msg += "<br />commiter: <b>"+K8Str::escapeStr(pkt.get("_nick_editor"))+"</b>";
2968 tmp = pkt.get("_mailto_editor").trimmed();
2969 if (!tmp.isEmpty()) msg += " (mailto: <i>"+K8Str::escapeStr(tmp)+"</i>)";
2970 msg += "<br /><i>"+K8Str::escapeStr(pkt.get("_comment"))+"</i>";
2972 onMessage(place, place, QDateTime::currentDateTime(), msg, " DONTTOUCH|", "\x1"+msg);
2976 ///////////////////////////////////////////////////////////////////////////////
2977 void ChatForm::onLinkClicked (const QUrl &url) {
2978 //qDebug() << "link:" << url << "scheme:" << url.scheme().toLower();
2979 QString sc(url.scheme().toLower());
2980 if (sc != "http" && sc != "https" && sc != "ftp") return;
2981 //qDebug() << " " << toIntOptCU("/web/url/opentype") << toStringOptCU("/web/url/browser");
2982 switch (toIntOptCU("/web/url/opentype")) {
2983 case 0: return; // don't open urls
2984 case 1: QDesktopServices::openUrl(url); return; // use system settings
2985 default: ;
2987 // use our settings
2988 QString cmd(toStringOptCU("/web/url/browser"));
2989 if (cmd.isEmpty()) return;
2990 QString addr(url.toString());
2991 //qDebug() << "url:" << addr;
2992 QByteArray ba(addr.toUtf8());
2993 QString surl = "\"";
2994 char buf[16];
2995 for (int f = 0; f < ba.size(); f++) {
2996 unsigned char ch = (unsigned char)ba[f];
2997 /*if (ch < '\x2a' || ch == '\x3b' || ch == '\x3c' || ch == '\x3e' || ch == '\x3f' ||
2998 (ch >= '\x5b' && ch <= '\x5d') || ch == '\x7b' || ch == '\x7d' || ch == '\x60') {*/
2999 switch (ch) {
3000 case '!': case '`': case '$': case '"': case '\\': surl.append('\\'); break;
3001 case '\t': surl.append("\\t"); break;
3002 case '\r': surl.append("\\r"); break;
3003 case '\n': surl.append("\\n"); break;
3004 default: ;
3006 if (ch > ' ' && ch <= '\x7e') surl.append(ch);
3007 else if (ch == ' ' || ch >= '\x7e') { sprintf(buf, "%%%02X", ch); surl.append(buf); }
3009 surl.append('"');
3010 cmd.replace("\"%s\"", "%s");
3011 if (cmd.indexOf("%s") == -1) cmd += " "+surl; else cmd.replace("%s", surl);
3012 //qDebug() << "cmd" << cmd;
3013 QProcess browser;
3014 browser.startDetached(cmd);
3018 ///////////////////////////////////////////////////////////////////////////////
3019 // options
3020 void ChatForm::clearOptionList () {
3021 foreach (Option *o, mOptionList) delete o;
3022 mOptionList.clear();
3026 void ChatForm::clearBindings () {
3027 mComboList->clear();
3028 mEditComboList->clear();
3029 mCurCombos->clear();
3033 void ChatForm::loadOptionList () {
3034 clearOptionList();
3035 bool ok;
3036 QString fs(loadFile(":/data/options.txt", &ok));
3037 if (!ok) return;
3038 QStringList fl(fs.split('\n'));
3039 for (int f = 0; f < fl.count(); ) {
3040 QString s(fl[f++].trimmed());
3041 if (s.isEmpty() || s[0] == '#' || s[0] == ';') continue;
3042 // collect help
3043 QString help;
3044 for (; f < fl.count(); f++) {
3045 QString s(fl[f].trimmed());
3046 if (s.isEmpty()) break;
3047 if (s[0] == '#' || s[0] == ';') continue;
3048 if (!help.isEmpty()) help += '\n';
3049 help += s;
3051 Option *o = new Option(s, help);
3052 if (o->isValid()) {
3053 QString nl(o->name.toLower());
3054 if (mOptionList.contains(nl)) delete mOptionList[nl];
3055 mOptionList[nl] = o;
3056 //qDebug() << "option found:" << o->name;
3057 } else delete o;
3062 bool ChatForm::loadBindings (const QString &fname) {
3063 clearBindings();
3064 bool ok;
3065 QString fs(loadFile(fname, &ok));
3066 if (!ok) return false;
3067 QStringList fl(fs.split('\n'));
3068 for (int f = 0; f < fl.count(); ) {
3069 QString s(fl[f++].trimmed());
3070 if (s.isEmpty() || s[0] == ';') continue;
3071 if (s.length() < 3 || s[0] != '(' || s[s.length()-1] != ')') continue;
3072 s = s.mid(1, s.length()-2);
3073 QStringList cl(K8Str::parseCmdLine(s));
3074 if (cl.length() < 1 || cl.length() > 3) continue;
3075 QString arg; if (cl.length() > 2) arg = cl[2];
3076 if (cl[0] == "global-key-bind") {
3077 mComboList->bind(cl[1], arg);
3078 } else if (cl[0] == "editor-key-bind") {
3079 mEditComboList->bind(cl[1], arg);
3082 mCurCombos->clear();
3083 mCurCombos->appendFrom(mComboList);
3084 if (edChat->hasFocus()) {
3085 mPrevFocusWasEdit = true;
3086 mCurCombos->appendFrom(mEditComboList);
3087 } else mPrevFocusWasEdit = false;
3088 return true;
3092 void ChatForm::setAccDefaultOptions (K8SDB *db) {
3093 foreach (Option *o, mOptionList) {
3094 if (o->scope == Option::OnlyLocal) continue;
3095 if (db->type(o->dbName) == o->type) continue; // option already set
3096 db->set(o->dbName, o->type, o->defVal);
3097 qDebug() << "default for" << o->name << "set to" << o->defVal;
3102 void ChatForm::optPrint (const QString &s) {
3103 runChatJS("addSysMsg("+K8Str::jsStr(s)+");");
3107 QStringList ChatForm::findByUniPart (const QString &up) const {
3108 QStringList res;
3109 if (!up.isEmpty()) {
3110 qDebug() << "findByUniPart:" << up;
3111 foreach (PsycContact *cc, mContactList) {
3112 QString v(cc->verbatim());
3113 qDebug() << "v:" << v << "up:" << up << "contains:" << v.contains(up, Qt::CaseInsensitive);
3114 if (v.contains(up, Qt::CaseInsensitive)) {
3115 if (v.compare(up, Qt::CaseInsensitive) == 0) {
3116 res.clear();
3117 res << cc->uni();
3118 return res;
3120 res << cc->uni();
3124 return res;
3128 PsycContact *ChatForm::findByUniOne (QString up) const {
3129 if (up.isEmpty()) return 0;
3130 bool isPlace = up[0] == '@';
3131 if (isPlace) {
3132 up.remove(0, 1);
3133 if (up.isEmpty()) return 0;
3135 QString fc; int lpos = 100000; PsycContact *res = 0;
3136 foreach (PsycContact *cc, mContactList) {
3137 if (cc->isPlace() != isPlace) continue;
3138 QString v(cc->verbatim());
3139 if (!v.compare(up, Qt::CaseInsensitive)) return cc; // exact!
3140 int i = v.indexOf(up, 0, Qt::CaseInsensitive);
3141 if (i < 0 || i > lpos) continue;
3142 if (i < lpos) {
3143 // absolutely better match
3144 res = cc;
3145 fc = v;
3146 lpos = i;
3147 continue;
3149 // possible better match (the longest is better)
3150 if (!fc.isEmpty() && fc.length() > v.length()) continue;
3151 res = cc;
3152 fc = v;
3153 lpos = i;
3155 return res;
3159 void ChatForm::onFloatyDblClicked (int x, int y, const QString &uni) {
3160 Q_UNUSED(x)
3161 Q_UNUSED(y)
3162 if (isMinimized() || !isVisible()) {
3163 showNormal();
3164 activateWindow();
3166 if (!mChatUser || mChatUser->uni().compare(uni, Qt::CaseInsensitive)) startChatWith(uni);
3170 void ChatForm::refreshBindings () {
3171 mCurCombos->clear();
3172 mCurCombos->appendFrom(mComboList);
3173 if (edChat->hasFocus()) mCurCombos->appendFrom(mEditComboList);
3177 bool ChatForm::processKeyCombo (QKeyEvent *keyEvent) {
3178 QString cmb;
3179 bool chatInFocus = edChat->hasFocus();
3180 if (chatInFocus != mPrevFocusWasEdit) {
3181 mComboList->reset();
3182 mEditComboList->reset();
3183 mPrevFocusWasEdit = chatInFocus;
3184 refreshBindings();
3187 cmb = mCurCombos->process(keyEvent);
3188 if (!cmb.isEmpty()) {
3189 if (cmb[0] != ' ') {
3190 //qDebug() << "combo:" << cmb << mEditComboList->lastCombo(), mEditComboList->lastComboText();
3191 mEngBinds->dispatchBinding(cmb, mCurCombos->lastCombo(), mCurCombos->lastComboText());
3193 return true;
3195 return false;
3199 ///////////////////////////////////////////////////////////////////////////////
3200 void ChatForm::onOptChanged__tray_visible (const QString &uni) {
3201 Q_UNUSED(uni)
3202 if (toBoolOpt("/tray/visible")) {
3203 if (!mTrayIcon->isVisible()) {
3204 mTrayIcon->show();
3205 checkBlink();
3207 } else mTrayIcon->hide();
3211 void ChatForm::onOptChanged__tray_blinktime (const QString &uni) {
3212 Q_UNUSED(uni)
3213 int ot = mBlinkTimer->interval();
3214 int tt = toIntOpt("/tray/blinktime", 500);
3215 if (ot == tt) return;
3216 if (mBlinkTimer->isActive()) {
3217 if (tt < 1) {
3218 if (mTrayMsgNoteActive) {
3219 mBlinkTimer->stop();
3220 mTrayIcon->setIcon(QIcon(MSG_TRAY_ICON));
3221 mBlinkMsg = true;
3223 } else {
3224 mBlinkTimer->stop();
3225 mBlinkTimer->start(tt>10?tt:10);
3227 } else {
3228 if (mTrayMsgNoteActive) mBlinkTimer->start(tt>10?tt:10);
3233 void ChatForm::onOptChanged__tray_notify (const QString &uni) {
3234 Q_UNUSED(uni)
3235 checkBlink();
3239 void ChatForm::onOptChanged__contactlist_onright (const QString &uni) {
3240 Q_UNUSED(uni)
3241 if (toBoolOpt("/contactlist/onright") == mCListOnRight) return;
3242 mCListOnRight = toBoolOpt("/contactlist/onright");
3243 if (mCListOnRight) {
3244 // was on the left
3245 splitter0->addWidget(layoutWidget0);
3246 } else {
3247 // was on the right
3248 splitter0->addWidget(splitter1);
3250 mSDB->set("/chatwindow/splitters/0", splitter0->saveState());
3251 mSDB->set("/chatwindow/splitters/1", splitter1->saveState());
3255 void ChatForm::onOptChanged__contactlist_showalways (const QString &uni) {
3256 PsycContact *cc = findContact(uni);
3257 if (!cc) return;
3258 redrawContact(cc);
3262 void ChatForm::switchTab (bool done) {
3263 if (mTabList.size() < 2) {
3264 mTabSwitchNo = -1;
3265 return;
3267 if (done) {
3268 if (mTabSwitchNo > 0) {
3269 // rearrange
3270 PsycContact *cc = mTabList[mTabSwitchNo];
3271 mTabList.removeAt(mTabSwitchNo);
3272 mTabList.insert(0, cc);
3274 mTabSwitchNo = -1;
3275 return;
3277 // not done
3278 if (mTabSwitchNo < 0) mTabSwitchNo = 0;
3279 int no = (mTabSwitchNo+1)%mTabList.count();
3280 mTabSwitchNo = no;
3281 //qDebug() << "switchTab:" << done << no;
3282 startChatWith(mTabList[no]->uni(), true);
3286 void ChatForm::switchToUnread () {
3287 foreach (PsycContact *cc, mContactList) {
3288 if (cc->isTemp()) continue;
3289 if (!cc->messageCount()) continue;
3290 if (toBoolOpt("/contactlist/skipunreadcycle", false, cc->uni())) continue;
3291 startChatWith(cc->uni());
3292 break;
3297 void ChatForm::clearTabHistory (void) {
3298 if (mTabList.count() > 0) {
3299 PsycContact *cc = mTabList[0];
3301 mTabList.clear();
3302 mTabList << cc;