blob: 71d46529cfedc4185e55bed2374d5f29a9c98b3e [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
/*
* Copyright (c) 2013, Regents of the University of California
* Alexander Afanasyev
* Zhenkai Zhu
*
* GNU v3.0 license, See the LICENSE file for more information
*
* Author: Zhenkai Zhu <zhenkai@cs.ucla.edu>
* Alexander Afanasyev <alexander.afanasyev@ucla.edu>
*/
#include "chatdialog.h"
#include "settingdialog.h"
#include <QtGui>
#include <QTimer>
#include <QMetaType>
#include <QMessageBox>
#ifndef Q_MOC_RUN
#include <ctime>
#include <iostream>
#include <boost/random/random_device.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#include <stdio.h>
#endif
#define BROADCAST_PREFIX_FOR_SYNC_DEMO "/ndn/broadcast/ChronoChat-0.3"
#define LOCAL_PREFIX_QUERY "/local/ndn/prefix"
#define DEFAULT_LOCAL_PREFIX "/private/local"
// #define CCN_EXEC "/usr/local/bin/ccnpeek"
static const int HELLO_INTERVAL = FRESHNESS * 3 / 4; // seconds
ChatDialog::ChatDialog(QWidget *parent)
: QDialog(parent)
, m_sock(NULL)
, m_lastMsgTime(0)
, m_historyInitialized(false)
, m_joined(false)
{
// have to register this, otherwise
// the signal-slot system won't recognize this type
qRegisterMetaType<std::vector<Sync::MissingDataInfo> >("std::vector<Sync::MissingDataInfo>");
qRegisterMetaType<size_t>("size_t");
setupUi (this);
m_session = time(NULL);
m_scene = new DigestTreeScene(this);
readSettings();
updateLabels();
lineEdit->setFocusPolicy(Qt::StrongFocus);
treeViewer->setScene(m_scene);
treeViewer->hide ();
m_scene->plot("Empty");
QRectF rect = m_scene->itemsBoundingRect();
m_scene->setSceneRect(rect);
// listView->setStyleSheet("QListView { alternate-background-color: white; background: #F0F0F0; color: darkGreen; font: bold large; }");
// listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
// listView->setDragDropMode(QAbstractItemView::NoDragDrop);
// listView->setSelectionMode(QAbstractItemView::NoSelection);
m_rosterModel = new QStringListModel(this);
listView->setModel(m_rosterModel);
// refreshButton->setIcon(QIcon(QPixmap(":images/refresh.png")));
// reapButton->hide();
createActions();
createTrayIcon();
m_timer = new QTimer(this);
connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
// connect(setButton, SIGNAL(pressed()), this, SLOT(buttonPressed()));
connect(treeButton, SIGNAL(pressed()), this, SLOT(treeButtonPressed()));
// connect(reapButton, SIGNAL(pressed()), this, SLOT(summonReaper()));
// connect(refreshButton, SIGNAL(pressed()), this, SLOT(updateLocalPrefix()));
connect(this, SIGNAL(dataReceived(QString, const char *, size_t, bool, bool)), this, SLOT(processData(QString, const char *, size_t, bool, bool)));
connect(this, SIGNAL(treeUpdated(const std::vector<Sync::MissingDataInfo>)), this, SLOT(processTreeUpdate(const std::vector<Sync::MissingDataInfo>)));
connect(m_timer, SIGNAL(timeout()), this, SLOT(replot()));
connect(m_scene, SIGNAL(replot()), this, SLOT(replot()));
connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(showNormal()));
connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
connect(m_scene, SIGNAL(rosterChanged(QStringList)), this, SLOT(updateRosterList(QStringList)));
initializeSync();
}
void
ChatDialog::initializeSync()
{
// create sync socket
if(!m_user.getChatroom().isEmpty() && !m_user.getNick().isEmpty()) {
std::string syncPrefix = BROADCAST_PREFIX_FOR_SYNC_DEMO;
syncPrefix += "/";
syncPrefix += m_user.getChatroom().toStdString();
try
{
m_sock = new Sync::SyncAppSocket(syncPrefix,
bind(&ChatDialog::processTreeUpdateWrapper, this, _1, _2),
bind(&ChatDialog::processRemoveWrapper, this, _1));
//QTimer::singleShot(100, this, SLOT(getLocalPrefix()));
usleep(100000);
if (!getLocalPrefix())
{
// if getLocalPrefix indicates no prefix change
// this sock is going to be used
QTimer::singleShot(600, this, SLOT(sendJoin()));
m_timer->start(FRESHNESS * 1000);
disableTreeDisplay();
QTimer::singleShot(2200, this, SLOT(enableTreeDisplay()));
Sync::CcnxWrapperPtr handle = boost::make_shared<Sync::CcnxWrapper> ();
handle->setInterestFilter(m_user.getPrefix().toStdString(), bind(&ChatDialog::respondHistoryRequest, this, _1));
}
else
{
// this socket is going to be destroyed anyway
// why bother doing the following steps
// the same steps would be performed for another socket
// in settingUpdated
}
}
catch (Sync::CcnxOperationException ex)
{
QMessageBox::critical(this, tr("Chronos"), tr("Canno connect to ccnd.\n Have you started your ccnd?"), QMessageBox::Ok);
std::exit(1);
}
}
}
ChatDialog::~ChatDialog()
{
if (m_sock != NULL)
{
sendLeave();
delete m_sock;
m_sock = NULL;
}
}
void
ChatDialog::sendLeave()
{
SyncDemo::ChatMessage msg;
formControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
sendMsg(msg);
usleep(500000);
m_sock->remove(m_user.getPrefix().toStdString());
usleep(5000);
m_joined = false;
#ifdef __DEBUG
std::cout << "Sync REMOVE signal sent" << std::endl;
#endif
}
void
ChatDialog::replot()
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
m_scene->plot(m_sock->getRootDigest().c_str());
fitView();
}
void
ChatDialog::summonReaper()
{
Sync::SyncLogic &logic = m_sock->getLogic ();
std::map<std::string, bool> branches = logic.getBranchPrefixes();
QMap<QString, DisplayUserPtr> roster = m_scene->getRosterFull();
m_zombieList.clear();
QMapIterator<QString, DisplayUserPtr> it(roster);
std::map<std::string, bool>::iterator mapIt;
while(it.hasNext())
{
it.next();
DisplayUserPtr p = it.value();
if (p != DisplayUserNullPtr)
{
mapIt = branches.find(p->getPrefix().toStdString());
if (mapIt != branches.end())
{
mapIt->second = true;
}
}
}
for (mapIt = branches.begin(); mapIt != branches.end(); ++mapIt)
{
// this is zombie. all active users should have been marked true
if (! mapIt->second)
{
m_zombieList.append(mapIt->first.c_str());
}
}
m_zombieIndex = 0;
// start reaping
reap();
}
void
ChatDialog::reap()
{
if (m_zombieIndex < m_zombieList.size())
{
std::string prefix = m_zombieList.at(m_zombieIndex).toStdString();
m_sock->remove(prefix);
std::cout << "Reaped: prefix = " << prefix << std::endl;
m_zombieIndex++;
// reap again in 10 seconds
QTimer::singleShot(10000, this, SLOT(reap()));
}
}
void
ChatDialog::updateRosterList(QStringList staleUserList)
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
QStringList rosterList = m_scene->getRosterList();
m_rosterModel->setStringList(rosterList);
QString user;
QStringListIterator it(staleUserList);
while(it.hasNext())
{
std::string nick = it.next().toStdString();
if (nick.empty())
continue;
SyncDemo::ChatMessage msg;
formControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
msg.set_from(nick);
appendMessage(msg);
}
}
void
ChatDialog::setVisible(bool visible)
{
minimizeAction->setEnabled(visible);
maximizeAction->setEnabled(!isMaximized());
restoreAction->setEnabled(isMaximized() || !visible);
raise(); // for MacOS
activateWindow(); // for Windows
QDialog::setVisible(visible);
}
void
ChatDialog::closeEvent(QCloseEvent *e)
{
if (trayIcon->isVisible() && !m_minimaniho)
{
QMessageBox::information(this, tr("Chronos"),
tr("The program will keep running in the "
"system tray. To terminate the program"
"choose <b>Quit</b> in the context memu"
"of the system tray entry."));
hide();
e->ignore();
m_minimaniho = true;
writeSettings();
}
}
void
ChatDialog::changeEvent(QEvent *e)
{
switch(e->type())
{
case QEvent::ActivationChange:
if (isActiveWindow())
{
trayIcon->setIcon(QIcon(":/images/icon_small.png"));
}
break;
default:
break;
}
}
void
ChatDialog::appendMessage(const SyncDemo::ChatMessage msg, bool isHistory)
{
boost::recursive_mutex::scoped_lock lock(m_msgMutex);
if (msg.type() == SyncDemo::ChatMessage::CHAT)
{
if (!msg.has_data())
{
return;
}
if (msg.from().empty() || msg.data().empty())
{
return;
}
if (!msg.has_timestamp())
{
return;
}
if (m_history.size() == MAX_HISTORY_ENTRY)
{
m_history.dequeue();
}
m_history.enqueue(msg);
QTextCharFormat nickFormat;
nickFormat.setForeground(Qt::darkGreen);
nickFormat.setFontWeight(QFont::Bold);
nickFormat.setFontUnderline(true);
nickFormat.setUnderlineColor(Qt::gray);
QTextCursor cursor(textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
QTextTableFormat tableFormat;
tableFormat.setBorder(0);
QTextTable *table = cursor.insertTable(1, 2, tableFormat);
QString from = QString("%1 ").arg(msg.from().c_str());
QTextTableCell fromCell = table->cellAt(0, 0);
fromCell.setFormat(nickFormat);
fromCell.firstCursorPosition().insertText(from);
time_t timestamp = msg.timestamp();
printTimeInCell(table, timestamp);
QTextCursor nextCursor(textEdit->textCursor());
nextCursor.movePosition(QTextCursor::End);
table = nextCursor.insertTable(1, 1, tableFormat);
table->cellAt(0, 0).firstCursorPosition().insertText(QString::fromUtf8(msg.data().c_str()));
if (!isHistory)
{
showMessage(from, QString::fromUtf8(msg.data().c_str()));
}
}
if (msg.type() == SyncDemo::ChatMessage::JOIN || msg.type() == SyncDemo::ChatMessage::LEAVE)
{
QTextCharFormat nickFormat;
nickFormat.setForeground(Qt::gray);
nickFormat.setFontWeight(QFont::Bold);
nickFormat.setFontUnderline(true);
nickFormat.setUnderlineColor(Qt::gray);
QTextCursor cursor(textEdit->textCursor());
cursor.movePosition(QTextCursor::End);
QTextTableFormat tableFormat;
tableFormat.setBorder(0);
QTextTable *table = cursor.insertTable(1, 2, tableFormat);
QString action;
if (msg.type() == SyncDemo::ChatMessage::JOIN)
{
action = "enters room";
}
else
{
action = "leaves room";
}
QString from = QString("%1 %2 ").arg(msg.from().c_str()).arg(action);
QTextTableCell fromCell = table->cellAt(0, 0);
fromCell.setFormat(nickFormat);
fromCell.firstCursorPosition().insertText(from);
time_t timestamp = msg.timestamp();
printTimeInCell(table, timestamp);
}
QScrollBar *bar = textEdit->verticalScrollBar();
bar->setValue(bar->maximum());
}
void
ChatDialog::printTimeInCell(QTextTable *table, time_t timestamp)
{
QTextCharFormat timeFormat;
timeFormat.setForeground(Qt::gray);
timeFormat.setFontUnderline(true);
timeFormat.setUnderlineColor(Qt::gray);
QTextTableCell timeCell = table->cellAt(0, 1);
timeCell.setFormat(timeFormat);
timeCell.firstCursorPosition().insertText(formatTime(timestamp));
}
QString
ChatDialog::formatTime(time_t timestamp)
{
struct tm *tm_time = localtime(&timestamp);
int hour = tm_time->tm_hour;
QString amOrPM;
if (hour > 12)
{
hour -= 12;
amOrPM = "PM";
}
else
{
amOrPM = "AM";
if (hour == 0)
{
hour = 12;
}
}
char textTime[12];
sprintf(textTime, "%d:%02d:%02d %s", hour, tm_time->tm_min, tm_time->tm_sec, amOrPM.toStdString().c_str());
return QString(textTime);
}
void
ChatDialog::processTreeUpdateWrapper(const std::vector<Sync::MissingDataInfo> v, Sync::SyncAppSocket *sock)
{
emit treeUpdated(v);
#ifdef __DEBUG
std::cout << "<<< Tree update signal emitted" << std::endl;
#endif
}
void
ChatDialog::processTreeUpdate(const std::vector<Sync::MissingDataInfo> v)
{
#ifdef __DEBUG
std::cout << "<<< processing Tree Update" << std::endl;
#endif
if (v.empty())
{
return;
}
// reflect the changes on digest tree
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
m_scene->processUpdate(v, m_sock->getRootDigest().c_str());
}
int n = v.size();
int totalMissingPackets = 0;
for (int i = 0; i < n; i++)
{
totalMissingPackets += v[i].high.getSeq() - v[i].low.getSeq() + 1;
}
for (int i = 0; i < n; i++)
{
if (totalMissingPackets < 4)
{
for (Sync::SeqNo seq = v[i].low; seq <= v[i].high; ++seq)
{
m_sock->fetchRaw(v[i].prefix, seq, bind(&ChatDialog::processDataWrapper, this, _1, _2, _3), 2);
#ifdef __DEBUG
std::cout << "<<< Fetching " << v[i].prefix << "/" <<seq.getSession() <<"/" << seq.getSeq() << std::endl;
#endif
}
}
else
{
m_sock->fetchRaw(v[i].prefix, v[i].high, bind(&ChatDialog::processDataNoShowWrapper, this, _1, _2, _3), 2);
}
}
// adjust the view
fitView();
}
void
ChatDialog::processDataWrapper(std::string name, const char *buf, size_t len)
{
char *tempBuf = new char[len];
memcpy(tempBuf, buf, len);
emit dataReceived(name.c_str(), tempBuf, len, true, false);
#ifdef __DEBUG
std::cout <<"<<< " << name << " fetched" << std::endl;
#endif
}
void
ChatDialog::processDataNoShowWrapper(std::string name, const char *buf, size_t len)
{
char *tempBuf = new char[len];
memcpy(tempBuf, buf, len);
emit dataReceived(name.c_str(), tempBuf, len, false, false);
if (!m_historyInitialized)
{
fetchHistory(name);
m_historyInitialized = true;
}
}
void
ChatDialog::processDataHistoryWrapper(std::string name, const char *buf, size_t len)
{
char *tempBuf = new char[len];
memcpy(tempBuf, buf, len);
emit dataReceived(name.c_str(), tempBuf, len, true, true);
}
void
ChatDialog::fetchHistory(std::string name)
{
std::string nameWithoutSeq = name.substr(0, name.find_last_of('/'));
std::string prefix = nameWithoutSeq.substr(0, nameWithoutSeq.find_last_of('/'));
prefix += "/history";
Sync::CcnxWrapperPtr handle = boost::make_shared<Sync::CcnxWrapper> ();
QString randomString = getRandomString();
for (int i = 0; i < MAX_HISTORY_ENTRY; i++)
{
QString interest = QString("%1/%2/%3").arg(prefix.c_str()).arg(randomString).arg(i);
handle->sendInterest(interest.toStdString(), bind(&ChatDialog::processDataHistoryWrapper, this, _1, _2, _3));
}
}
void
ChatDialog::respondHistoryRequest(std::string interest)
{
std::string seqStr = interest.substr(interest.find_last_of('/') + 1);
int seq = boost::lexical_cast<int>(seqStr);
if (seq >= 0 && seq < m_history.size())
{
Sync::CcnxWrapperPtr handle = boost::make_shared<Sync::CcnxWrapper> ();
SyncDemo::ChatMessage msg = m_history.at(seq);
size_t size = msg.ByteSize();
char *buf = new char[size];
msg.SerializeToArray(buf, size);
handle->publishRawData(interest, buf, size, 1);
delete buf;
}
}
void
ChatDialog::processData(QString name, const char *buf, size_t len, bool show, bool isHistory)
{
SyncDemo::ChatMessage msg;
bool corrupted = false;
if (!msg.ParseFromArray(buf, len))
{
std::cerr << "Errrrr.. Can not parse msg with name: " << name.toStdString() << ". what is happening?" << std::endl;
// nasty stuff: as a remedy, we'll form some standard msg for inparsable msgs
msg.set_from("inconnu");
msg.set_type(SyncDemo::ChatMessage::OTHER);
corrupted = true;
}
delete [] buf;
buf = NULL;
// display msg received from network
// we have to do so; this function is called by ccnd thread
// so if we call appendMsg directly
// Qt crash as "QObject: Cannot create children for a parent that is in a different thread"
// the "cannonical" way to is use signal-slot
if (show && !corrupted)
{
appendMessage(msg, isHistory);
}
if (!isHistory)
{
// update the tree view
std::string stdStrName = name.toStdString();
std::string stdStrNameWithoutSeq = stdStrName.substr(0, stdStrName.find_last_of('/'));
std::string prefix = stdStrNameWithoutSeq.substr(0, stdStrNameWithoutSeq.find_last_of('/'));
#ifdef __DEBUG
std::cout <<"<<< updating scene for" << prefix << ": " << msg.from() << std::endl;
#endif
if (msg.type() == SyncDemo::ChatMessage::LEAVE)
{
processRemove(prefix.c_str());
}
else
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
m_scene->msgReceived(prefix.c_str(), msg.from().c_str());
}
}
fitView();
}
void
ChatDialog::processRemoveWrapper(std::string prefix)
{
#ifdef __DEBUG
std::cout << "Sync REMOVE signal received for prefix: " << prefix << std::endl;
#endif
//emit removeReceived(prefix.c_str());
}
void
ChatDialog::processRemove(QString prefix)
{
#ifdef __DEBUG
std::cout << "<<< remove node for prefix" << prefix.toStdString() << std::endl;
#endif
bool removed = m_scene->removeNode(prefix);
if (removed)
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
m_scene->plot(m_sock->getRootDigest().c_str());
}
}
void
ChatDialog::formChatMessage(const QString &text, SyncDemo::ChatMessage &msg) {
msg.set_from(m_user.getNick().toStdString());
msg.set_to(m_user.getChatroom().toStdString());
msg.set_data(text.toUtf8().constData());
time_t seconds = time(NULL);
msg.set_timestamp(seconds);
msg.set_type(SyncDemo::ChatMessage::CHAT);
}
void
ChatDialog::formControlMessage(SyncDemo::ChatMessage &msg, SyncDemo::ChatMessage::ChatMessageType type)
{
msg.set_from(m_user.getNick().toStdString());
msg.set_to(m_user.getChatroom().toStdString());
time_t seconds = time(NULL);
msg.set_timestamp(seconds);
msg.set_type(type);
}
static std::string chars("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789");
QString
ChatDialog::getRandomString()
{
std::string randStr;
boost::random::random_device rng;
boost::random::uniform_int_distribution<> index_dist(0, chars.size() - 1);
for (int i = 0; i < 10; i ++)
{
randStr += chars[index_dist(rng)];
}
return randStr.c_str();
}
bool
ChatDialog::getLocalPrefix()
{
// /*
// * this method tries to use ccncat
// * however, it does not work in Mac OS X app bundle
// * it works well in command line though
// */
// std::string cmd = CCN_EXEC;
// cmd += " -c -v ";
// cmd += LOCAL_PREFIX_QUERY;
// QString localPrefix;
// #define MAX_PREFIX_LEN 100
// FILE *fp = popen(cmd.c_str(), "r");
// if (fp != NULL)
// {
// char prefix[MAX_PREFIX_LEN];
// if (fgets(prefix, MAX_PREFIX_LEN, fp) != NULL)
// {
// localPrefix = prefix;
// localPrefix.remove('\n');
// }
// else
// {
// localPrefix = DEFAULT_LOCAL_PREFIX;
// }
// pclose(fp);
// }
// else
// {
// localPrefix = DEFAULT_LOCAL_PREFIX;
// }
// return localPrefix;
std::cerr << "trying to get local prefix" << std::endl;
if (m_sock != NULL)
{
QString originPrefix = QString::fromStdString (m_sock->GetLocalPrefix()).trimmed ();
std::cerr << "got: " << originPrefix.toStdString () << std::endl;
if (originPrefix != "" && m_user.getOriginPrefix () != originPrefix)
{
emit settingUpdated(m_user.getNick (), m_user.getChatroom (), originPrefix);
// prefix updated
return true;
}
}
// prefix not changed
return false;
}
void
ChatDialog::updateLocalPrefix()
{
getLocalPrefix();
}
bool
ChatDialog::readSettings()
{
QSettings settings (QSettings::NativeFormat, QSettings::UserScope, ORGANIZATION, APPLICATION);
QString nick = settings.value("nick", "").toString();
QString chatroom = settings.value("chatroom", "").toString();
// QString originPrefix = settings.value("originPrefix", "").toString();
// Sync::CcnxWrapperPtr wrapper = Sync::CcnxWrapper::Create ();
// QString originPrefix = QString::fromStdString (wrapper->getLocalPrefix());
// Sync::CcnxWrapper::Destroy ();
QString originPrefix = DEFAULT_LOCAL_PREFIX;
m_minimaniho = settings.value("minimaniho", false).toBool();
if (nick == "" || chatroom == "" || originPrefix == "") {
m_user.setOriginPrefix(DEFAULT_LOCAL_PREFIX);
m_user.setChatroom("retreat2012");
QTimer::singleShot(500, this, SLOT(buttonPressed()));
return false;
}
else {
m_user.setNick(nick);
m_user.setChatroom(chatroom);
m_user.setOriginPrefix(originPrefix);
m_user.setPrefix(originPrefix + "/" + chatroom + "/" + getRandomString());
m_scene->setCurrentPrefix(originPrefix + "/" + chatroom + "/" + getRandomString());
return true;
}
// QTimer::singleShot(500, this, SLOT(buttonPressed()));
// return false;
}
void
ChatDialog::writeSettings()
{
QSettings settings (QSettings::NativeFormat, QSettings::UserScope, ORGANIZATION, APPLICATION);
settings.setValue("nick", m_user.getNick());
settings.setValue("chatroom", m_user.getChatroom());
//settings.setValue("originPrefix", m_user.getOriginPrefix());
settings.setValue("minimaniho", m_minimaniho);
}
void
ChatDialog::updateLabels()
{
QString settingDisp = QString("Chatroom: %1").arg(m_user.getChatroom());
infoLabel->setStyleSheet("QLabel {color: #630; font-size: 16px; font: bold \"Verdana\";}");
infoLabel->setText(settingDisp);
QString prefixDisp;
if (m_user.getPrefix().startsWith(DEFAULT_LOCAL_PREFIX))
{
prefixDisp = QString("<Warning: no connection to hub or hub does not support prefix autoconfig.>\n <Prefix = %1>").arg(m_user.getPrefix());
prefixLabel->setStyleSheet("QLabel {color: red; font-size: 12px; font: bold \"Verdana\";}");
}
else
{
prefixDisp = QString("<Prefix = %1>").arg(m_user.getPrefix());
prefixLabel->setStyleSheet("QLabel {color: Green; font-size: 12px; font: bold \"Verdana\";}");
}
prefixLabel->setText(prefixDisp);
}
void
ChatDialog::returnPressed()
{
QString text = lineEdit->text();
if (text.isEmpty())
return;
lineEdit->clear();
if (text.startsWith("boruoboluomi"))
{
summonReaper ();
// reapButton->show();
fitView();
return;
}
if (text.startsWith("minimanihong"))
{
// reapButton->hide();
fitView();
return;
}
SyncDemo::ChatMessage msg;
formChatMessage(text, msg);
appendMessage(msg);
sendMsg(msg);
fitView();
}
void
ChatDialog::sendMsg(SyncDemo::ChatMessage &msg)
{
// send msg
size_t size = msg.ByteSize();
char *buf = new char[size];
msg.SerializeToArray(buf, size);
if (!msg.IsInitialized())
{
std::cerr << "Errrrr.. msg was not probally initialized "<<__FILE__ <<":"<<__LINE__<<". what is happening?" << std::endl;
abort();
}
m_sock->publishRaw(m_user.getPrefix().toStdString(), m_session, buf, size, FRESHNESS);
delete buf;
m_lastMsgTime = time(NULL);
int nextSequence = m_sock->getNextSeq(m_user.getPrefix().toStdString(), m_session);
Sync::MissingDataInfo mdi = {m_user.getPrefix().toStdString(), Sync::SeqNo(0), Sync::SeqNo(nextSequence - 1)};
std::vector<Sync::MissingDataInfo> v;
v.push_back(mdi);
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
m_scene->processUpdate(v, m_sock->getRootDigest().c_str());
m_scene->msgReceived(m_user.getPrefix(), m_user.getNick());
}
}
void
ChatDialog::sendJoin()
{
m_joined = true;
SyncDemo::ChatMessage msg;
formControlMessage(msg, SyncDemo::ChatMessage::JOIN);
sendMsg(msg);
boost::random::random_device rng;
boost::random::uniform_int_distribution<> uniform(1, FRESHNESS / 5 * 1000);
m_randomizedInterval = HELLO_INTERVAL * 1000 + uniform(rng);
QTimer::singleShot(m_randomizedInterval, this, SLOT(sendHello()));
}
void
ChatDialog::sendHello()
{
time_t now = time(NULL);
int elapsed = now - m_lastMsgTime;
if (elapsed >= m_randomizedInterval / 1000)
{
SyncDemo::ChatMessage msg;
formControlMessage(msg, SyncDemo::ChatMessage::HELLO);
sendMsg(msg);
boost::random::random_device rng;
boost::random::uniform_int_distribution<> uniform(1, FRESHNESS / 5 * 1000);
m_randomizedInterval = HELLO_INTERVAL * 1000 + uniform(rng);
QTimer::singleShot(m_randomizedInterval, this, SLOT(sendHello()));
}
else
{
QTimer::singleShot((m_randomizedInterval - elapsed * 1000), this, SLOT(sendHello()));
}
}
void
ChatDialog::buttonPressed()
{
if (m_sock != NULL)
{
Sync::SyncLogic &logic = m_sock->getLogic ();
logic.printState ();
}
SettingDialog dialog(this, m_user.getNick(), m_user.getChatroom(), m_user.getOriginPrefix());
connect(&dialog, SIGNAL(updated(QString, QString, QString)), this, SLOT(settingUpdated(QString, QString, QString)));
dialog.exec();
QTimer::singleShot(100, this, SLOT(checkSetting()));
}
void ChatDialog::treeButtonPressed()
{
if (treeViewer->isVisible())
{
treeViewer->hide();
treeButton->setText("Show ChronoSync Tree");
}
else
{
treeViewer->show();
treeButton->setText("Hide ChronoSync Tree");
}
fitView();
}
void ChatDialog::enableTreeDisplay()
{
treeButton->setEnabled(true);
// treeViewer->show();
// fitView();
}
void ChatDialog::disableTreeDisplay()
{
treeButton->setEnabled(false);
treeViewer->hide();
fitView();
}
void
ChatDialog::checkSetting()
{
if (m_user.getNick().isEmpty() || m_user.getChatroom().isEmpty() || m_user.getOriginPrefix().isEmpty())
{
buttonPressed();
}
}
void
ChatDialog::settingUpdated(QString nick, QString chatroom, QString originPrefix)
{
QString randString = getRandomString();
bool needWrite = false;
bool needFresh = false;
if (!nick.isEmpty() && nick != m_user.getNick()) {
m_user.setNick(nick);
needWrite = true;
}
QString oldPrefix = m_user.getPrefix();
if (!originPrefix.isEmpty() && originPrefix != m_user.getOriginPrefix()) {
m_user.setOriginPrefix(originPrefix);
m_user.setPrefix(originPrefix + "/" + m_user.getChatroom() + "/" + randString);
m_scene->setCurrentPrefix(originPrefix + "/" + m_user.getChatroom() + "/" + randString);
needWrite = true;
needFresh = true;
}
if (!chatroom.isEmpty() && chatroom != m_user.getChatroom()) {
m_user.setChatroom(chatroom);
m_user.setPrefix(m_user.getOriginPrefix() + "/" + chatroom + "/" + randString);
m_scene->setCurrentPrefix(m_user.getOriginPrefix() + "/" + chatroom + "/" + randString);
needWrite = true;
needFresh = true;
}
if (needWrite) {
writeSettings();
updateLabels();
}
if (needFresh && m_sock != NULL)
{
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
m_scene->clearAll();
m_scene->plot("Empty");
}
textEdit->clear();
// keep the new prefix
QString newPrefix = m_user.getPrefix();
// send leave for the old
m_user.setPrefix(oldPrefix);
// there is no point to send leave if we haven't joined yet
if (m_joined)
{
sendLeave();
}
// resume new prefix
m_user.setPrefix(newPrefix);
// Sync::CcnxWrapperPtr handle = Sync::CcnxWrapper::Create();
// handle->clearInterestFilter(oldPrefix.toStdString());
m_history.clear();
m_historyInitialized = false;
delete m_sock;
m_sock = NULL;
std::string syncPrefix = BROADCAST_PREFIX_FOR_SYNC_DEMO;
syncPrefix += "/";
syncPrefix += m_user.getChatroom().toStdString();
try
{
usleep(100000);
m_sock = new Sync::SyncAppSocket(syncPrefix, bind(&ChatDialog::processTreeUpdateWrapper, this, _1, _2), bind(&ChatDialog::processRemoveWrapper, this, _1));
usleep(100000);
Sync::CcnxWrapperPtr handle = boost::make_shared<Sync::CcnxWrapper> ();
handle->setInterestFilter(m_user.getPrefix().toStdString(), bind(&ChatDialog::respondHistoryRequest, this, _1));
QTimer::singleShot(600, this, SLOT(sendJoin()));
m_timer->start(FRESHNESS * 1000);
disableTreeDisplay();
QTimer::singleShot(2200, this, SLOT(enableTreeDisplay()));
}
catch (Sync::CcnxOperationException ex)
{
QMessageBox::critical(this, tr("Chronos"), tr("Canno connect to ccnd.\n Have you started your ccnd?"), QMessageBox::Ok);
std::exit(1);
}
}
else if (needFresh && m_sock == NULL)
{
m_history.clear();
m_historyInitialized = false;
initializeSync();
}
else if (m_sock == NULL)
{
initializeSync();
}
else
{
#ifdef __DEBUG
std::cout << "Just changing nicks, we're good. " << std::endl;
#endif
}
fitView();
}
void
ChatDialog::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason)
{
case QSystemTrayIcon::Trigger:
case QSystemTrayIcon::DoubleClick:
break;
case QSystemTrayIcon::MiddleClick:
// showMessage();
break;
default:;
}
}
void
ChatDialog::showMessage(QString from, QString data)
{
// std::cout <<"Showing Message: " << from.toStdString() << ": " << data.toStdString() << std::endl;
if (!isActiveWindow())
{
trayIcon->showMessage(QString("Chatroom %1 has a new message").arg(m_user.getChatroom()), QString("<%1>: %2").arg(from).arg(data), QSystemTrayIcon::Information, 20000);
trayIcon->setIcon(QIcon(":/images/note.png"));
}
}
void
ChatDialog::messageClicked()
{
this->showMaximized();
}
void
ChatDialog::createActions()
{
minimizeAction = new QAction(tr("Mi&nimize"), this);
connect(minimizeAction, SIGNAL(triggered()), this, SLOT(hide()));
maximizeAction = new QAction(tr("Ma&ximize"), this);
connect(maximizeAction, SIGNAL(triggered()), this, SLOT(showMaximized()));
restoreAction = new QAction(tr("&Restore"), this);
connect(restoreAction, SIGNAL(triggered()), this, SLOT(showNormal()));
settingsAction = new QAction(tr("Settings"), this);
connect (settingsAction, SIGNAL(triggered()), this, SLOT(buttonPressed()));
settingsAction->setMenuRole (QAction::PreferencesRole);
updateLocalPrefixAction = new QAction(tr("Update local prefix"), this);
connect (updateLocalPrefixAction, SIGNAL(triggered()), this, SLOT(updateLocalPrefix()));
quitAction = new QAction(tr("Quit"), this);
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
}
void
ChatDialog::createTrayIcon()
{
trayIconMenu = new QMenu(this);
trayIconMenu->addAction(minimizeAction);
trayIconMenu->addAction(maximizeAction);
trayIconMenu->addAction(restoreAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(settingsAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(updateLocalPrefixAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
trayIcon = new QSystemTrayIcon(this);
trayIcon->setContextMenu(trayIconMenu);
QIcon icon(":/images/icon_small.png");
trayIcon->setIcon(icon);
setWindowIcon(icon);
trayIcon->setToolTip("Chronos System Tray Icon");
trayIcon->setVisible(true);
// // QApplication::getMenu ()->addMenu (trayIconMenu);
// QMenuBar *bar = new QMenuBar ();
// bar->setMenu (trayIconMenu);
// setMenuBar (bar);
}
void
ChatDialog::resizeEvent(QResizeEvent *e)
{
fitView();
}
void
ChatDialog::showEvent(QShowEvent *e)
{
fitView();
}
void
ChatDialog::fitView()
{
boost::recursive_mutex::scoped_lock lock(m_sceneMutex);
QRectF rect = m_scene->itemsBoundingRect();
m_scene->setSceneRect(rect);
treeViewer->fitInView(m_scene->itemsBoundingRect(), Qt::KeepAspectRatio);
}
#if WAF
#include "chatdialog.moc"
#include "chatdialog.cpp.moc"
#endif