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.
17 #include <dysversion.h>
24 # include <QMessageBox>
28 #include <QApplication>
33 #include <QInputDialog>
35 #include <QMetaMethod>
36 #include <QMessageBox>
43 #include <QTextStream>
48 #include <QWebHitTestResult>
50 #include <QWebSettings>
52 #include "ghotkeyman.h"
55 #include "k8strutils.h"
56 #include "k8ascii85.h"
60 #include "k8history.h"
64 #include "psycproto.h"
66 #include "psyccontact.h"
74 # include "genkeywin.h"
81 #include "eng_commands.h"
82 #include "eng_bindings.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);
113 r = loadFile(settingsPrefsPath()+fn, &ff);
115 if (found) *found = true;
119 r
= loadFile(settingsAppPath()+fn
, &ff
);
121 if (found
) *found
= true;
126 QFile
file(fileName
);
128 if (file
.open(QIODevice::ReadOnly
)) {
130 stream
.setDevice(&file
);
131 stream
.setCodec("UTF-8");
132 res
= stream
.readAll();
134 if (found
) *found
= true;
140 ///////////////////////////////////////////////////////////////////////////////
141 #include "chat_otr.cpp"
144 ///////////////////////////////////////////////////////////////////////////////
145 Option::Option (const QString
&def
, const QString
&aHelp
) {
146 mValid
= parseDef(def
);
151 bool Option::parseDef (const QString
&def
) {
152 QStringList
pd(def
.split(':'));
153 if (pd
.count() < 4) return false;
158 // [4]: scope (can be omited)
161 name
= pd
[0].trimmed();
162 if (name
.isEmpty()) return false;
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
;
170 dbName
= pd
[2].trimmed();
171 if (dbName
.isEmpty()) return false;
173 defVal
= pd
[3].trimmed();
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;
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());
195 if (!pt
.isEmpty() && pt
[0] == '/') pt
.remove(0, 1);
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
);
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
) {
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());
231 default: return false; // don't steal
234 QKeyEvent
*keyEvent
= static_cast<QKeyEvent
*>(event
);
235 if (keyEvent
->key() == Qt::Key_Control
) {
237 if (mCtrlDown
) mForm
->switchTab(true);
243 if (!down
) return false;
245 if (keyEvent
->modifiers() == Qt::ControlModifier
&& keyEvent
->key() == Qt::Key_Tab
) {
246 mForm
->switchTab(false);
247 //mForm->switchTab(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";
262 ///////////////////////////////////////////////////////////////////////////////
263 ChatForm::ChatForm (QWidget
*parent
) : QWidget(parent
),
265 mTrayIcon(new QSystemTrayIcon(QIcon(MAIN_TRAY_ICON
))),
267 mProto(new PsycProto(this)), mPassword(QString("")),
268 mMyStatus(PsycProto::Offline
), mMyMood(PsycProto::Unspecified
),
270 mEFilter(new EnterFilter(this)),
271 mChatUser(0), mSelText(QString()),
273 mPopPos(K8PopupManager::RightBottom
),
274 mCloseWaiting(false), mServerStore(false),
275 mJSAPI(0), mCLJSAPI(0),
277 mBlinkTimer(new QTimer(this)),
278 mBlinkMsg(false), mTrayMsgNoteActive(false), mUnreadInChat(false),
279 mCListOnRight(false),
281 mChatLoaded(false), mCListLoaded(false),
282 mTabDown(false), mTabSwitchNo(-1),
287 mOTRTimer
= new QTimer(this);
288 connect(mOTRTimer
, SIGNAL(timeout()), this, SLOT(onOTRTimer()));
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
);
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()));
318 d
.mkdir(settingsPrefsPath());
320 QString css
= loadFile(":/html/qss/default.qss");
322 if (!css
.isEmpty()) qApp
->setStyleSheet(css
);
325 mConForm = new ConsoleForm(this);
326 connect(mConForm, SIGNAL(destroyed()), this, SLOT(consoleDestroyed()));
327 mConForm->cbActive->setChecked(true);
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)));
399 edChat
->setTabChangesFocus(false);
403 void ChatForm::onGShortcutShow () {
404 if (isMinimized() || !isVisible()) {
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();
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);
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);
447 delete mEditComboList
;
456 void ChatForm::windowDestroyed () {
457 if (mProto
->isLoggedIn()) storeAppData();
458 if (mProto
->isLinked()) mProto
->logoff();
463 void ChatForm::consoleDestroyed () {
468 void ChatForm::recreatePopMan () {
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 () {
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 () {
497 if (mProto
->isLoggedIn()) storeAppData();
498 if (mProto
->isLinked()) mProto
->logoff();
499 delete mSDB
; mSDB
= 0;
501 foreach (PsycContact
*cc
, mContactList
) delete cc
;
502 mContactList
.clear();
508 if (mOTRState
) otrl_userstate_free(mOTRState
);
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"));
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);
536 pt
= K8Popups::escapeStr(msg
.trimmed()); pt
.replace("\n", "<br />");
538 tpl
.setVar("text", pt
);
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;
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());
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();
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"));
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
);
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());
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());
619 showPopup(place
, me
, "error", "errorpopup", msg
, "");
623 void ChatForm::onLoadFinishedChat (bool ok
) {
625 //qDebug() << "onLoadFinishedChat" << ok;
630 void ChatForm::onLoadFinishedCList (bool ok
) {
631 //qDebug() << "onLoadFinishedCList" << ok;
637 void ChatForm::clearChat () {
639 mSelText
= QString();
640 webChat
->load(QUrl("dys://theme/chat.html"));
641 while (!mChatLoaded
) {
642 qApp
->processEvents();
646 lbChatDest
->setText("...");
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
;
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();
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()) {
688 if (toBoolOpt("/tray/middleexit", true) && reason
== QSystemTrayIcon::MiddleClick
) {
694 void ChatForm::checkBlink () {
695 bool doBlink
= mUnreadInChat
;
696 if (mFMan
&& !mUnreadInChat
&& mChatUser
) mFMan
->noMessages(mChatUser
->uni());
699 QString
br("<br />");
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()) {
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
);
729 if (mTrayMsgNoteActive
) {
731 if (toBoolOpt("/status/trayicon")) {
733 mTrayIcon
->setIcon(LinuxizeTrayIcon(statusIcon(mProto
->status())));
735 mTrayIcon
->setIcon(statusIcon(mProto
->status()));
737 } else mTrayIcon
->setIcon(QIcon(MAIN_TRAY_ICON
));
738 mTrayMsgNoteActive
= false;
741 if (!mTrayMsgNoteActive
) {
742 int bt
= toIntOpt("/tray/blinktime", 500);
744 if (bt
< 10) bt
= 10;
745 mBlinkTimer
->start(bt
);
748 if (toBoolOpt("/status/trayicon")) {
750 mTrayIcon
->setIcon(LinuxizeTrayIcon(statusIcon(mProto
->status())));
752 mTrayIcon
->setIcon(statusIcon(mProto
->status()));
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
));
766 //mTrayIcon->setIcon(QIcon(MAIN_TRAY_ICON));
767 if (toBoolOpt("/status/trayicon")) {
769 mTrayIcon
->setIcon(LinuxizeTrayIcon(statusIcon(mProto
->status())));
771 mTrayIcon
->setIcon(statusIcon(mProto
->status()));
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());
785 mProto
->sendPacket(pkt
);
790 * syntax for _request_store:
792 * :_application_pypsyc_window_size>3000x2000
793 * :_application_pypsyc_skin<><>darkstar
794 * :_character_command<><><>%
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());
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());
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
) {
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);
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);
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
);
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", ""));
862 if (u
.isValid()) startChatWith(cw
);
869 ///////////////////////////////////////////////////////////////////////////////
870 static QString
optNameEx (const QString
&name
, const QString
&aUNI
=QString()) {
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
)+");");
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;
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
);
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
);
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
);
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
);
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
);
973 optionChangedSignal(name
, aUNI
);
980 bool ChatForm::toBoolOpt (const QString
&name
, bool def
, const QString
&aUNI
) {
981 if (!mSDB
) return def
;
983 bool res
= mSDB
->toBool(optNameEx(name
, aUNI
), &ok
);
984 if (!ok
&& !aUNI
.isEmpty()) res
= mSDB
->toBool(optNameEx(name
), &ok
);
990 qint32
ChatForm::toIntOpt (const QString
&name
, qint32 def
, const QString
&aUNI
) {
991 if (!mSDB
) return def
;
993 int res
= mSDB
->toInt(optNameEx(name
, aUNI
), &ok
);
994 if (!ok
&& !aUNI
.isEmpty()) res
= mSDB
->toInt(optNameEx(name
), &ok
);
1000 QString
ChatForm::toStringOpt (const QString
&name
, const QString
&def
, const QString
&aUNI
) {
1001 if (!mSDB
) return def
;
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
;
1010 QByteArray
ChatForm::toBAOpt (const QString
&name
, const QByteArray
&def
, const QString
&aUNI
) {
1011 if (!mSDB
) return def
;
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
;
1020 QKeySequence
ChatForm::toKeySequenceOpt (const QString
&name
, const QKeySequence
&def
, const QString
&aUNI
) {
1021 if (!mSDB
) return def
;
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
;
1031 QString
ChatForm::asStringOpt (const QString
&name
, const QString
&aUNI
) {
1032 if (!mSDB
) return QString();
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();
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);
1163 if (mOTRState
) otrl_userstate_free(mOTRState
);
1165 mOTRState
= otrl_userstate_create();
1168 delete mSDB
; mSDB
= 0;
1170 K8SDB
*mainDB
= new K8SDB(settingsMainDBFile(), K8SDB::OpenExisting
);
1171 if (mainDB
->isOpen()) {
1173 QString actAcc
= mainDB
->toString("/active", &ok
);
1174 if (!ok
|| actAcc
.isEmpty()) mAccName
= QString(); else mAccName
= actAcc
;
1178 if (mAccName
.isEmpty()) {
1179 setControlsAccActive(false);
1183 setControlsAccActive(true);
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
));
1211 mProto
->setUNI(uu
.uni());
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");
1223 FILE *fl
= qxfopen(getOTRKeyFile(), true);
1225 otrl_privkey_read_FILEp(mOTRState
, fl
);
1228 fl
= qxfopen(getOTRFingerFile(), true);
1230 otrl_privkey_read_fingerprints_FILEp(mOTRState
, fl
, 0, 0);
1236 if (!loadBindings(settingsDBPath(mAccName
)+"keybinds.txt")) loadBindings(":/data/keybinds.txt");
1237 setAccountShortcuts();
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();
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
);
1255 cc
->addMessage(u
, msg
.text
, msg
.action
, msg
.date
);
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;
1275 if (!ux
.isValid()) continue;
1276 kk
= optNameEx("/clist/", kk
);
1278 QString uni
= mSDB
->toString(kk
+"uni", &ok
);
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
);
1289 cc
->setStatus(PsycProto::Offline
);
1290 if (!verb
.isEmpty()) cc
->setVerbatim(verb
);
1291 bool hid
= mSDB
->toBool(kk
+"hidden", &ok
);
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() << "!!!!!!";
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
);
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
);
1328 void ChatForm::loadAutosaved () {
1329 if (toBoolOpt("/contactlist/onright") != mCListOnRight
) {
1330 mCListOnRight
= toBoolOpt("/contactlist/onright");
1331 if (mCListOnRight
) {
1333 splitter0
->addWidget(layoutWidget0
);
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);
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
);
1362 mTrayIcon
->setIcon(QIcon(MAIN_TRAY_ICON
));
1363 if (toBoolOpt("/tray/visible", true)) mTrayIcon
->show();
1364 else mTrayIcon
->hide();
1368 //startChatWith("");
1373 mProto
->setStatusText(toStringOpt("/status/text"));
1375 startChatWith(toStringOpt("/chatwindow/uni"));
1379 ///////////////////////////////////////////////////////////////////////////////
1380 void ChatForm::saveOneContact (PsycContact
*cc
) {
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()) {
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
) {
1404 mSDB
->set("/chatwindow/splitters/0", splitter0
->saveState());
1408 void ChatForm::on_splitter1_splitterMoved (int pos
, int index
) {
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;
1425 QTextCodec
*codec
= QTextCodec::codecForName("UTF-8");
1427 stream
.setDevice(&file
);
1428 stream
.setCodec(codec
);
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]);
1442 QString
ChatForm::recodeMsg (const QString
&msg
) {
1444 for (int f
= 0; f
< res
.length(); f
++) {
1445 QChar ch
= res
.at(f
);
1446 res
[f
] = mRecodeMap
.value(ch
, ch
);
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
);
1476 if (isNew
) *isNew
= false;
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
;
1486 case SaveContact
: saveOneContact(cc
); break;
1487 case TempContact
: cc
->setTemp(true); break;
1495 void ChatForm::deleteContact (PsycContact
*cc
) {
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
);
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) {
1522 ///////////////////////////////////////////////////////////////////////////////
1523 void ChatForm::onUpdateAccountInfo (AccWindow
*au
, const QString
&text
) {
1524 //qDebug() << "update info for account" << text;
1528 QDir
ppdir(settingsPrefsPath()+text
.toLower());
1533 if (text
.isEmpty() || !ppdir
.exists()) goto clear
;
1536 odb
= mSDB
->pathname();
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"));
1565 au
->cbSSL
->setChecked(db
->toBool("/login/useSSL"));
1567 au
->cbSSL
->setChecked(false);
1568 au
->cbSSL
->setEnabled(false);
1571 au
->btDelAcc
->setEnabled(true);
1575 au
->btDelAcc
->setEnabled(false);
1576 au
->leNick
->setText("");
1577 au
->leHost
->setText("");
1578 au
->lePass
->setText("");
1579 au
->cbSecure
->setChecked(true);
1583 if (!odb
.isEmpty()) {
1584 mSDB
= new K8SDB(odb
, K8SDB::OpenAlways
);
1589 QString
ChatForm::defAccount () {
1591 K8SDB
*mainDB
= new K8SDB(settingsMainDBFile(), K8SDB::OpenExisting
);
1592 if (mainDB
->isOpen()) {
1594 res
= mainDB
->toString("/active", &ok
);
1595 if (!ok
|| res
.isEmpty()) res
= QString();
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
);
1613 void ChatForm::accountSetDefaults (K8SDB
*db
) {
1615 if (!db
|| !db
->isOpen()) return;
1616 setAccDefaultOptions(db
);
1620 //FIXME: check for errors!
1621 void ChatForm::onAccountCreated (AccWindow
*au
, const QString
&text
) {
1623 qDebug() << "account created:" << text
;
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
);
1646 void ChatForm::onAccountDeleted (AccWindow
*au
, const QString
&text
) {
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()+"/");
1657 QString act
= db
->toString("/active", &ok
);
1658 if (ok
&& !act
.compare(text
, Qt::CaseInsensitive
)) db
->remove("/active");
1661 ppdir
.rename(settingsPrefsPath()+text
.toLower(), settingsPrefsPath()+"_"+text
.toLower());
1667 void ChatForm::on_action_accedit_triggered (bool) {
1669 K8SDB *mainDB = new K8SDB(settingsMainDBFile(), K8SDB::OpenExisting);
1670 if (mainDB->isOpen()) {
1672 QString actAcc = mainDB->toString("/active", &ok);
1673 if (!ok || actAcc.isEmpty()) mAccName = QString(); else mAccName = actAcc;
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);
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());
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());
1707 au->leNick->setText("");
1708 au->leHost->setText("");
1710 au->lePass->setText(mPassword);
1711 au->cbSecure->setChecked(mProto->secureLogin());
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());
1724 mProto
->setUNI(u
.uni());
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());
1740 mSDB
->set("/login/useSSL", au
->cbSSL
->isChecked());
1743 mProto
->setSecureLogin(au
->cbSecure
->isChecked());
1744 mPassword
= au
->lePass
->text();
1746 if (!sameAcc
) loadAccount();
1748 mProto
->setSecureLogin(au
->cbSecure
->isChecked());
1749 mProto
->setUseSSL(au
->cbSSL
->isChecked());
1757 ///////////////////////////////////////////////////////////////////////////////
1758 void ChatForm::onAuthRequested (const PsycEntity
&user
) {
1759 if (user
.isPlace()) return;
1760 PsycContact
*cc
= addContact(user
.uni());
1762 if (cc
->authorized()) {
1763 mProto
->sendAuth(user
);
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
&)));
1776 void ChatForm::onAuthClicked (const PsycEntity
&entity
, const QString
&text
) {
1778 mProto
->sendAuth(entity
);
1780 PsycContact
*cc
= addContact(entity
.uni(), "", &isNew
);
1781 if (cc
&& (isNew
|| !cc
->authorized())) {
1782 cc
->setAuthorized(true);
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");
1794 PsycContact
*cc
= addContact(user
.uni(), "", &isNew
);
1796 if (isNew
|| !cc
->authorized()) {
1797 cc
->setAuthorized(true);
1798 //mProto->sendAuth(user, tr("auth granted"));
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);
1816 ///////////////////////////////////////////////////////////////////////////////
1817 void ChatForm::on_edChat_textChanged (void) {
1818 //if (mChBlock || !mChatUser || !mDoTypo) return;
1819 if (mChBlock
) return; // don't recurse
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
);
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
);
1842 void ChatForm::chatAutoComplete () {
1843 if (!mChatUser
|| !mChatUser
->isPlace()) {
1844 mStartingNickCompletion
.clear();
1845 mLatestNickCompletion
.clear();
1848 QString txt
= edChat
->toPlainText();
1849 if (txt
.isEmpty() || txt
[0] == '/') {
1850 mStartingNickCompletion
.clear();
1851 mLatestNickCompletion
.clear();
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();
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();
1874 QStringList
ppl(mChatUser
->persons());
1875 if (ppl
.size() < 1) {
1876 mStartingNickCompletion
.clear();
1877 mLatestNickCompletion
.clear();
1881 for (int f
= ppl
.size()-1; f
>= 0; f
--) {
1884 PsycContact
*cc
= findContact(s
);
1885 if (cc
) s
= cc
->verbatim(); else s
= u
.nick();
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()) {
1897 mStartingNickCompletion
= nick
;
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();
1908 nick
= mStartingNickCompletion
;
1909 mLatestNickCompletion
.clear();
1910 qDebug() << st
<< cur
<< nick
;
1912 QString
s(ppl
[cur
]);
1914 if (s
.length() >= nick
.length() && !s
.leftRef(nick
.length()).compare(nick
, Qt::CaseInsensitive
)) {
1916 mLatestNickCompletion
= s
;
1918 qDebug() << "found!" << nick
;
1919 txt
= edChat
->toPlainText();
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);
1931 edChat
->setPlainText(txt
);
1932 tcur
.setPosition(nick
.length());
1933 edChat
->setTextCursor(tcur
);
1937 cur
= (cur
+1)%ppl
.size();
1938 } while (cur
!= st
);
1943 void ChatForm::keyPressEvent (QKeyEvent
*event
) {
1946 if (event->modifiers() != Qt::CTRL) {
1947 if (event->key() == Qt::Key_Escape) {
1948 if (toBoolOpt("/app/eschides")) hide(); else showMinimized();
1953 if (edChat->hasFocus()) return;
1955 QCoreApplication::sendEvent(edChat, event);
1962 void ChatForm::showEvent (QShowEvent
*event
) {
1963 //if (toBoolOptCU("/chat/show/forcescroll", false)) scrollToBottom();
1965 mUnreadInChat
= false;
1967 mPopMan
->removeById("", mChatUser
->uni().toLower());
1968 if (mFMan
) mFMan
->noMessages(mChatUser
->uni());
1970 QWidget::showEvent(event
);
1975 void ChatForm::closeEvent (QCloseEvent
*event
) {
1976 if (!mCloseWaiting
) {
1977 mCloseWaiting
= true;
1979 bool h
= mProto
->isConnected();
1980 setStatus(PsycProto::Offline
);
1982 QTimer::singleShot(5000, this, SLOT(close()));
1991 ///////////////////////////////////////////////////////////////////////////////
1992 void ChatForm::scrollToBottom () {
1993 runChatJS("do{var ___tmp___=window.pageYOffset;window.scrollTo(0,1000000);}while(window.pageYOffset!=___tmp___);");
1997 void ChatForm::translateMessage () {
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
);
2012 edChat
->insertPlainText(txt
);
2013 edChat
->ensureCursorVisible();
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] == '/') {
2030 mEngCmds
->doCommand(txt
);
2034 if (!mProto
->isLoggedIn()) return;
2036 txt
= txt
.trimmed();
2038 if (txt
.isEmpty()) return;
2039 if (!mChatUser
) return; //FIXME: send to main entity as command?
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();
2055 otrSendMessage(mChatUser
->uni(), txt
, act
);
2057 mProto
->sendMessage(mChatUser
->uni(), txt
, act
);
2059 if (!mChatUser
->isOTRActive()) {
2060 // no OTR, check 'immediate output mode'
2064 if (!toBoolOptCU("/chat/messaging/immediateout", false)) return; // out this when echo comes
2067 dlogf("OTR active!");
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
);
2085 p
.drawPixmap(0, 0, ipm
);
2092 QIcon
ChatForm::statusIcon (PsycProto::Status 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();
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
) {
2114 if (doSend
) mProto
->connectToServer();
2116 if (mMyStatus
!= PsycProto::Offline
&& st
== PsycProto::Offline
) {
2119 if (mProto
->isLoggedIn()) storeAppData();
2125 //case PsycProto::Internal: return QIcon(":/icons/status/connecting.png");
2126 case PsycProto::Offline
:
2127 action_stoffline
->setChecked(true);
2129 case PsycProto::Vacation
:
2130 action_stvacation
->setChecked(true);
2132 case PsycProto::Away
:
2133 action_staway
->setChecked(true);
2135 case PsycProto::DND
:
2136 action_stdnd
->setChecked(true);
2138 case PsycProto::Nearby
:
2139 action_stnear
->setChecked(true);
2141 case PsycProto::Busy
:
2142 action_stbusy
->setChecked(true);
2144 case PsycProto::Here
:
2145 action_sthere
->setChecked(true);
2147 case PsycProto::FFC
:
2148 action_stffc
->setChecked(true);
2150 case PsycProto::Realtime
:
2151 action_strtime
->setChecked(true);
2155 if (connecting
) btStatus
->setIcon(QIcon(":/icons/status/connecting.png"));
2156 else btStatus
->setIcon(statusIcon(st
));
2158 if (toBoolOpt("/status/trayicon")) {
2160 mTrayIcon
->setIcon(LinuxizeTrayIcon(statusIcon(st
)));
2162 mTrayIcon
->setIcon(statusIcon(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
) {
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;
2204 if (!mProto
->isLoggedIn()) return;
2205 if (mMyStatus
!= mProto
->status()) setStatus(mMyStatus
);
2207 mProto
->setStatusText(toStringOpt("/status/text"));
2208 if (mMyStatus
!= PsycProto::Offline
) {
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) {
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));
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());
2262 QStringList
sl(frm
->edPacket
->toPlainText().trimmed().split("\n"));
2264 if (sl
.count() > 1) {
2266 foreach (const QString
&s
, sl
) {
2267 if (s
== ".") break;
2270 mProto
->sendPacket(pkt
);
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());
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
);
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|")) {
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
)+
2324 //qDebug() << "addChatMessage()";
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
2336 if (isUnread
== MessageUnread
) {
2337 hfname
= settingsUnreadHistoryFile(mAccName
, dest
.uni());
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());
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
2355 //if (isUnread != MessageRead) addMessageToHistory(dest, src, text, actuib, time, MessageRead);
2360 void ChatForm::startChatWith (const QString
&aUNI
, bool dontRearrange
) {
2362 PsycEntity
user(aUNI
);
2364 if (!user
.isValid()) {
2365 setWindowTitle(DYSKINESIA
);
2367 mSDB
->set("/chatwindow/uni", "");
2368 mDoTypo
= toBoolOptCU("/editor/typography");
2369 runCListJS("chatStarted('');");
2370 runChatJS("chatStarted('');");
2375 PsycContact
*cc
= addContact(aUNI
, "", &isNew
);
2378 int idx
= mTabList
.indexOf(cc
);
2379 if (dontRearrange
) {
2380 if (idx
< 0) mTabList
<< cc
;
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
);
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);
2403 HistoryFile
hf(settingsHistoryFile(mAccName
, cc
->uni()), mProto
->uni());
2404 if (hf
.open(HistoryFile::Read
)) {
2405 //fprintf(stderr, "!!!\n");
2407 toShow
= hf
.count()-toShow
;
2408 if (toShow
< 0) toShow
= 0;
2409 //fprintf(stderr, " %i\n", toShow);
2410 while (hf
.read(toShow
++, msg
)) {
2412 //qDebug() << msg.text;
2413 PsycEntity
uu(msg
.uni
);
2414 addMessageToChat(uu
, msg
.text
, msg
.action
, msg
.date
);
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());
2438 mFMan
->noMessages(cc
->uni());
2442 mPopMan
->removeById("", cc
->uni().toLower());
2445 mSDB
->set("/chatwindow/uni", cc
->uni());
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
);
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());
2475 mFMan
->noMessages(cc
->uni());
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();
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 () {
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
);
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:{";
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());
2546 webCList
->page()->mainFrame()->evaluateJavaScript(pktS
);
2547 webChat
->page()->mainFrame()->evaluateJavaScript(pktS
);
2551 void ChatForm::onPacket (const PsycPacket
&pkt
) {
2553 //qDebug() << ">>>GOT PACKET\n:" << pkt.toString() << "========================";
2554 QString
m(pkt
.method());
2555 if (m
== "_status_storage") {
2556 parseStoredAppData(pkt
);
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());
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?
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 () {
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);
2618 //mProto->setStatus(st, mProto->statusText(), mProto->mood());
2619 //show(mProto->nick(), mProto->uni(), "connected!");
2620 mProto
->requestPeerList();
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;
2630 QString
act(K8Str::deHTMLize(action
, K8Str::deHTMLnone
));
2631 QString
textD(K8Str::deHTMLize(text
, K8Str::deHTMLnone
));
2633 if (actX
.startsWith(" DONTTOUCH|")) {
2635 actX
= actX
.simplified();
2638 PsycEntity histDest
;
2639 if (place
.isValid()) {
2646 bool toCurrChat
= (mChatUser
&& mChatUser
->entity()->sameUNI(histDest
.uni()));
2647 PsycContact
*cc
= 0;
2648 if (!toCurrChat
) cc
= addContact(histDest
); // either place or person
2652 QString
realText(otrGotMessage(user
.uni(), text
));
2653 if (realText
.isNull()) return; // internal OTR message
2654 textD
= K8Str::deHTMLize(realText
, K8Str::deHTMLnone
);
2662 //showPopup = false;
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());
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
);
2681 showPopup
= toBoolOpt("/popup/active", true);
2682 urf
= MessageUnread
;
2684 cc
->addMessage(user
, textD
, act
, time
);
2685 mFMan
->newMessage(cc
->uni());
2689 addMessageToHistory(histDest
, user
, textD
, act
, time
, urf
);
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
);
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
)
2710 //if (!mChatUser) return; //FIXME: should remember this?
2711 if (toBoolOptCU("/chat/messaging/immediateout", false)) return; // already done
2712 PsycEntity
me(mProto
->uni());
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
);
2720 //qDebug() << "echo from" << user.uni() << "tag:" << tag;
2722 PsycContact *cc = findContact(user.uni());
2723 if (!cc) dlogf("shit!");
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
2733 realText = otrGotMessage(user.uni(), realText);
2734 if (realText.isNull()) return; // internal OTR message
2737 addMessageToHistory(target
, me
, realText
, action
, time
, MessageRead
);
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();
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
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; }
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());
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());
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
);
2797 mFMan
->newStatus(cc
->uni(), cc
->status());
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
));
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
);
2829 ///////////////////////////////////////////////////////////////////////////////
2830 void ChatForm::onPlaceEntered (const PsycEntity
&place
) {
2831 //qDebug() << "entered:" << place.uni();
2833 PsycContact
*cc
= addContact(place
, "", &isNew
);
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())+");");
2847 void ChatForm::onPlaceLeaved (const PsycEntity
&place
) {
2848 //qDebug() << "leaved:" << place.uni();
2850 PsycContact
*cc
= addContact(place
, "", &isNew
);
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()";
2867 void ChatForm::onUserEntersPlace (const PsycEntity
&place
, const PsycEntity
&who
) {
2868 //qDebug() << "user entered to:" << place.uni() << who.uni();
2870 PsycContact
*cc
= addContact(place
, "", &isNew
);
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()";
2883 void ChatForm::onUserLeavesPlace (const PsycEntity
&place
, const PsycEntity
&who
) {
2884 //qDebug() << "user parted from:" << place.uni() << who.uni();
2886 PsycContact
*cc
= addContact(place
, "", &isNew
);
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()";
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();
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;
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 />"
2937 void ChatForm::onPlaceNews (const PsycEntity
&place
, const QString
&channel
, const QString
&title
, const QString
&url
) {
2939 if (!channel
.isEmpty()) msg
= QString("<b>%1</b>: ").arg(channel
);
2940 msg
.append(K8Str::escapeStr(title
));
2942 if (!url
.isEmpty()) {
2943 msg
.append("\n<a href=\x22");
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>";
2957 onMessage(place
, place
, QDateTime::currentDateTime(), msg
, " DONTTOUCH|", tmp
);
2961 void ChatForm::onNoticeUpdate (const PsycEntity
&place
, const PsycPacket
&pkt
) {
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
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
= "\"";
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') {*/
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;
3006 if (ch
> ' ' && ch
<= '\x7e') surl
.append(ch
);
3007 else if (ch
== ' ' || ch
>= '\x7e') { sprintf(buf
, "%%%02X", ch
); surl
.append(buf
); }
3010 cmd
.replace("\"%s\"", "%s");
3011 if (cmd
.indexOf("%s") == -1) cmd
+= " "+surl
; else cmd
.replace("%s", surl
);
3012 //qDebug() << "cmd" << cmd;
3014 browser
.startDetached(cmd
);
3018 ///////////////////////////////////////////////////////////////////////////////
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 () {
3036 QString
fs(loadFile(":/data/options.txt", &ok
));
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;
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';
3051 Option
*o
= new Option(s
, help
);
3053 QString
nl(o
->name
.toLower());
3054 if (mOptionList
.contains(nl
)) delete mOptionList
[nl
];
3055 mOptionList
[nl
] = o
;
3056 //qDebug() << "option found:" << o->name;
3062 bool ChatForm::loadBindings (const QString
&fname
) {
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;
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 {
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) {
3128 PsycContact
*ChatForm::findByUniOne (QString up
) const {
3129 if (up
.isEmpty()) return 0;
3130 bool isPlace
= up
[0] == '@';
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;
3143 // absolutely better match
3149 // possible better match (the longest is better)
3150 if (!fc
.isEmpty() && fc
.length() > v
.length()) continue;
3159 void ChatForm::onFloatyDblClicked (int x
, int y
, const QString
&uni
) {
3162 if (isMinimized() || !isVisible()) {
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
) {
3179 bool chatInFocus
= edChat
->hasFocus();
3180 if (chatInFocus
!= mPrevFocusWasEdit
) {
3181 mComboList
->reset();
3182 mEditComboList
->reset();
3183 mPrevFocusWasEdit
= chatInFocus
;
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());
3199 ///////////////////////////////////////////////////////////////////////////////
3200 void ChatForm::onOptChanged__tray_visible (const QString
&uni
) {
3202 if (toBoolOpt("/tray/visible")) {
3203 if (!mTrayIcon
->isVisible()) {
3207 } else mTrayIcon
->hide();
3211 void ChatForm::onOptChanged__tray_blinktime (const QString
&uni
) {
3213 int ot
= mBlinkTimer
->interval();
3214 int tt
= toIntOpt("/tray/blinktime", 500);
3215 if (ot
== tt
) return;
3216 if (mBlinkTimer
->isActive()) {
3218 if (mTrayMsgNoteActive
) {
3219 mBlinkTimer
->stop();
3220 mTrayIcon
->setIcon(QIcon(MSG_TRAY_ICON
));
3224 mBlinkTimer
->stop();
3225 mBlinkTimer
->start(tt
>10?tt
:10);
3228 if (mTrayMsgNoteActive
) mBlinkTimer
->start(tt
>10?tt
:10);
3233 void ChatForm::onOptChanged__tray_notify (const QString
&uni
) {
3239 void ChatForm::onOptChanged__contactlist_onright (const QString
&uni
) {
3241 if (toBoolOpt("/contactlist/onright") == mCListOnRight
) return;
3242 mCListOnRight
= toBoolOpt("/contactlist/onright");
3243 if (mCListOnRight
) {
3245 splitter0
->addWidget(layoutWidget0
);
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
);
3262 void ChatForm::switchTab (bool done
) {
3263 if (mTabList
.size() < 2) {
3268 if (mTabSwitchNo
> 0) {
3270 PsycContact
*cc
= mTabList
[mTabSwitchNo
];
3271 mTabList
.removeAt(mTabSwitchNo
);
3272 mTabList
.insert(0, cc
);
3278 if (mTabSwitchNo
< 0) mTabSwitchNo
= 0;
3279 int no
= (mTabSwitchNo
+1)%mTabList
.count();
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());
3297 void ChatForm::clearTabHistory (void) {
3298 if (mTabList
.count() > 0) {
3299 PsycContact
*cc
= mTabList
[0];