blob: b660597413d1e6feb893bbbb63907106042a4c60 [file] [log] [blame]
Yingdi Yu348f5ea2014-03-01 14:47:25 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2013, Regents of the University of California
4 * Yingdi Yu
5 *
6 * BSD license, See the LICENSE file for more information
7 *
8 * Author: Yingdi Yu <yingdi@cs.ucla.edu>
9 */
10
11#include <QApplication>
12#include <QMessageBox>
13#include <QDir>
14#include <QTimer>
Yingdi Yu0b0a7362014-08-05 16:31:30 -070015#include "controller.hpp"
Yingdi Yu348f5ea2014-03-01 14:47:25 -080016
17#ifndef Q_MOC_RUN
18#include <boost/filesystem.hpp>
19#include <boost/lexical_cast.hpp>
Yingdi Yufa0b6a02014-04-30 14:26:42 -070020#include <ndn-cxx/util/random.hpp>
Yingdi Yu0b0a7362014-08-05 16:31:30 -070021#include "cryptopp.hpp"
Yingdi Yu348f5ea2014-03-01 14:47:25 -080022#include "config.pb.h"
23#include "endorse-info.pb.h"
24#include "logging.h"
25#endif
26
Yingdi Yueb692ac2015-02-10 18:46:18 -080027INIT_LOGGER("chronochat.Controller");
Yingdi Yu348f5ea2014-03-01 14:47:25 -080028
29Q_DECLARE_METATYPE(ndn::Name)
30Q_DECLARE_METATYPE(ndn::IdentityCertificate)
Yingdi Yueb692ac2015-02-10 18:46:18 -080031Q_DECLARE_METATYPE(chronochat::EndorseInfo)
Yingdi Yu233a9722014-03-07 15:47:09 -080032Q_DECLARE_METATYPE(ndn::Interest)
33Q_DECLARE_METATYPE(size_t)
Yingdi Yueb692ac2015-02-10 18:46:18 -080034Q_DECLARE_METATYPE(chronochat::ChatroomInfo)
35Q_DECLARE_METATYPE(chronochat::Invitation)
Qiuhan Ding43c8e162015-02-02 15:16:48 -080036Q_DECLARE_METATYPE(std::string)
37Q_DECLARE_METATYPE(ndn::Name::Component)
Yingdi Yu348f5ea2014-03-01 14:47:25 -080038
Yingdi Yueb692ac2015-02-10 18:46:18 -080039namespace chronochat {
Yingdi Yu348f5ea2014-03-01 14:47:25 -080040
Yingdi Yu0b0a7362014-08-05 16:31:30 -070041using std::string;
42
Yingdi Yu348f5ea2014-03-01 14:47:25 -080043// constructor & destructor
Yingdi Yu2c9e7712014-10-20 11:55:05 -070044Controller::Controller(QWidget* parent)
Yingdi Yu348f5ea2014-03-01 14:47:25 -080045 : QDialog(parent)
Yingdi Yubaaaec02014-09-02 22:05:32 -070046 , m_localPrefixDetected(false)
Qiuhan Ding43c8e162015-02-02 15:16:48 -080047 , m_settingDialog(new SettingDialog(this))
48 , m_startChatDialog(new StartChatDialog(this))
49 , m_profileEditor(new ProfileEditor(this))
50 , m_invitationDialog(new InvitationDialog(this))
Qiuhan Dingba3e57a2015-01-08 19:07:39 -080051 , m_invitationRequestDialog(new InvitationRequestDialog(this))
Qiuhan Ding43c8e162015-02-02 15:16:48 -080052 , m_contactPanel(new ContactPanel(this))
53 , m_browseContactDialog(new BrowseContactDialog(this))
54 , m_addContactPanel(new AddContactPanel(this))
55 , m_discoveryPanel(new DiscoveryPanel(this))
Yingdi Yu348f5ea2014-03-01 14:47:25 -080056{
57 qRegisterMetaType<ndn::Name>("ndn.Name");
58 qRegisterMetaType<ndn::IdentityCertificate>("ndn.IdentityCertificate");
Yingdi Yueb692ac2015-02-10 18:46:18 -080059 qRegisterMetaType<chronochat::EndorseInfo>("chronochat.EndorseInfo");
Yingdi Yu233a9722014-03-07 15:47:09 -080060 qRegisterMetaType<ndn::Interest>("ndn.Interest");
61 qRegisterMetaType<size_t>("size_t");
Qiuhan Ding43c8e162015-02-02 15:16:48 -080062 qRegisterMetaType<chronochat::ChatroomInfo>("chronochat.Chatroom");
63 qRegisterMetaType<chronochat::Invitation>("chronochat.Invitation");
64 qRegisterMetaType<std::string>("std.string");
65 qRegisterMetaType<ndn::Name::Component>("ndn.Component");
66
Yingdi Yu348f5ea2014-03-01 14:47:25 -080067
68 // Connection to ContactManager
Yingdi Yu2c9e7712014-10-20 11:55:05 -070069 connect(m_backend.getContactManager(), SIGNAL(warning(const QString&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -080070 this, SLOT(onWarning(const QString&)));
71 connect(this, SIGNAL(refreshBrowseContact()),
Yingdi Yu2c9e7712014-10-20 11:55:05 -070072 m_backend.getContactManager(), SLOT(onRefreshBrowseContact()));
73 connect(m_backend.getContactManager(), SIGNAL(contactInfoFetchFailed(const QString&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -080074 this, SLOT(onWarning(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -080075
76 // Connection to SettingDialog
77 connect(this, SIGNAL(identityUpdated(const QString&)),
78 m_settingDialog, SLOT(onIdentityUpdated(const QString&)));
79 connect(m_settingDialog, SIGNAL(identityUpdated(const QString&)),
80 this, SLOT(onIdentityUpdated(const QString&)));
81 connect(m_settingDialog, SIGNAL(nickUpdated(const QString&)),
82 this, SLOT(onNickUpdated(const QString&)));
Yingdi Yubaaaec02014-09-02 22:05:32 -070083 connect(m_settingDialog, SIGNAL(prefixUpdated(const QString&)),
84 this, SLOT(onLocalPrefixConfigured(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -080085
86 // Connection to ProfileEditor
87 connect(this, SIGNAL(closeDBModule()),
88 m_profileEditor, SLOT(onCloseDBModule()));
89 connect(this, SIGNAL(identityUpdated(const QString&)),
90 m_profileEditor, SLOT(onIdentityUpdated(const QString&)));
91 connect(m_profileEditor, SIGNAL(updateProfile()),
Yingdi Yu2c9e7712014-10-20 11:55:05 -070092 m_backend.getContactManager(), SLOT(onUpdateProfile()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -080093
94 // Connection to StartChatDialog
95 connect(m_startChatDialog, SIGNAL(startChatroom(const QString&, bool)),
96 this, SLOT(onStartChatroom(const QString&, bool)));
97
98 // Connection to InvitationDialog
99 connect(m_invitationDialog, SIGNAL(invitationResponded(const ndn::Name&, bool)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700100 &m_backend, SLOT(onInvitationResponded(const ndn::Name&, bool)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800101
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800102 // Connection to InvitationRequestDialog
103 connect(m_invitationRequestDialog, SIGNAL(invitationRequestResponded(const ndn::Name&, bool)),
104 &m_backend, SLOT(onInvitationRequestResponded(const ndn::Name&, bool)));
105
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800106 // Connection to AddContactPanel
107 connect(m_addContactPanel, SIGNAL(fetchInfo(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700108 m_backend.getContactManager(), SLOT(onFetchContactInfo(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800109 connect(m_addContactPanel, SIGNAL(addContact(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700110 m_backend.getContactManager(), SLOT(onAddFetchedContact(const QString&)));
111 connect(m_backend.getContactManager(),
Yingdi Yueb692ac2015-02-10 18:46:18 -0800112 SIGNAL(contactEndorseInfoReady(const chronochat::EndorseInfo&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700113 m_addContactPanel,
Yingdi Yueb692ac2015-02-10 18:46:18 -0800114 SLOT(onContactEndorseInfoReady(const chronochat::EndorseInfo&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800115
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800116 // Connection to BrowseContactDialog
117 connect(m_browseContactDialog, SIGNAL(directAddClicked()),
118 this, SLOT(onDirectAdd()));
119 connect(m_browseContactDialog, SIGNAL(fetchIdCert(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700120 m_backend.getContactManager(), SLOT(onFetchIdCert(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800121 connect(m_browseContactDialog, SIGNAL(addContact(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700122 m_backend.getContactManager(), SLOT(onAddFetchedContactIdCert(const QString&)));
123 connect(m_backend.getContactManager(), SIGNAL(idCertNameListReady(const QStringList&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800124 m_browseContactDialog, SLOT(onIdCertNameListReady(const QStringList&)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700125 connect(m_backend.getContactManager(), SIGNAL(nameListReady(const QStringList&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800126 m_browseContactDialog, SLOT(onNameListReady(const QStringList&)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700127 connect(m_backend.getContactManager(), SIGNAL(idCertReady(const ndn::IdentityCertificate&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800128 m_browseContactDialog, SLOT(onIdCertReady(const ndn::IdentityCertificate&)));
129
130 // Connection to ContactPanel
131 connect(m_contactPanel, SIGNAL(waitForContactList()),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700132 m_backend.getContactManager(), SLOT(onWaitForContactList()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800133 connect(m_contactPanel, SIGNAL(waitForContactInfo(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700134 m_backend.getContactManager(), SLOT(onWaitForContactInfo(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800135 connect(m_contactPanel, SIGNAL(removeContact(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700136 m_backend.getContactManager(), SLOT(onRemoveContact(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800137 connect(m_contactPanel, SIGNAL(updateAlias(const QString&, const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700138 m_backend.getContactManager(), SLOT(onUpdateAlias(const QString&, const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800139 connect(m_contactPanel, SIGNAL(updateIsIntroducer(const QString&, bool)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700140 m_backend.getContactManager(), SLOT(onUpdateIsIntroducer(const QString&, bool)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800141 connect(m_contactPanel, SIGNAL(updateEndorseCertificate(const QString&)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700142 m_backend.getContactManager(), SLOT(onUpdateEndorseCertificate(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800143 connect(m_contactPanel, SIGNAL(warning(const QString&)),
144 this, SLOT(onWarning(const QString&)));
145 connect(this, SIGNAL(closeDBModule()),
146 m_contactPanel, SLOT(onCloseDBModule()));
147 connect(this, SIGNAL(identityUpdated(const QString&)),
148 m_contactPanel, SLOT(onIdentityUpdated(const QString&)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700149 connect(m_backend.getContactManager(), SIGNAL(contactAliasListReady(const QStringList&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800150 m_contactPanel, SLOT(onContactAliasListReady(const QStringList&)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700151 connect(m_backend.getContactManager(), SIGNAL(contactIdListReady(const QStringList&)),
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800152 m_contactPanel, SLOT(onContactIdListReady(const QStringList&)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700153 connect(m_backend.getContactManager(), SIGNAL(contactInfoReady(const QString&, const QString&,
154 const QString&, bool)),
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700155 m_contactPanel, SLOT(onContactInfoReady(const QString&, const QString&,
156 const QString&, bool)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800157
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700158 // Connection to backend thread
159 connect(this, SIGNAL(shutdownBackend()),
160 &m_backend, SLOT(shutdown()));
161 connect(this, SIGNAL(updateLocalPrefix()),
162 &m_backend, SLOT(onUpdateLocalPrefixAction()));
163 connect(this, SIGNAL(identityUpdated(const QString&)),
164 &m_backend, SLOT(onIdentityChanged(const QString&)));
165 connect(this, SIGNAL(addChatroom(QString)),
166 &m_backend, SLOT(addChatroom(QString)));
167 connect(this, SIGNAL(removeChatroom(QString)),
168 &m_backend, SLOT(removeChatroom(QString)));
169
170 // Thread notifications:
171 // on local prefix udpated:
172 connect(&m_backend, SIGNAL(localPrefixUpdated(const QString&)),
173 this, SLOT(onLocalPrefixUpdated(const QString&)));
174 connect(&m_backend, SIGNAL(localPrefixUpdated(const QString&)),
175 m_settingDialog, SLOT(onLocalPrefixUpdated(const QString&)));
176
177 // on invitation validated:
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800178 connect(&m_backend, SIGNAL(invitationValidated(QString, QString, ndn::Name)),
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700179 m_invitationDialog, SLOT(onInvitationReceived(QString, QString, ndn::Name)));
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800180 connect(&m_backend, SIGNAL(startChatroom(const QString&, bool)),
181 this, SLOT(onStartChatroom(const QString&, bool)));
182
183 // on invitation request received
184 connect(&m_backend, SIGNAL(invitationRequestReceived(QString, QString, ndn::Name)),
185 m_invitationRequestDialog, SLOT(onInvitationRequestReceived(QString, QString,
186 ndn::Name)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700187
188 // on invitation accepted:
Yingdi Yueb692ac2015-02-10 18:46:18 -0800189 connect(&m_backend, SIGNAL(startChatroomOnInvitation(chronochat::Invitation, bool)),
190 this, SLOT(onStartChatroom2(chronochat::Invitation, bool)));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700191
192 m_backend.start();
Mengjin Yan391d7242014-08-28 20:51:55 -0700193
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800194 initialize();
195
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800196 m_chatroomDiscoveryBackend
197 = new ChatroomDiscoveryBackend(m_localPrefix,
198 m_identity,
199 this);
200
201 // connect to chatroom discovery back end
202 connect(&m_backend, SIGNAL(localPrefixUpdated(const QString&)),
203 m_chatroomDiscoveryBackend, SLOT(updateRoutingPrefix(const QString&)));
204 connect(this, SIGNAL(localPrefixConfigured(const QString&)),
205 m_chatroomDiscoveryBackend, SLOT(updateRoutingPrefix(const QString&)));
206 connect(this, SIGNAL(newChatroomForDiscovery(Name::Component)),
207 m_chatroomDiscoveryBackend, SLOT(onNewChatroomForDiscovery(Name::Component)));
208 connect(m_chatroomDiscoveryBackend, SIGNAL(chatroomInfoRequest(std::string, bool)),
209 this, SLOT(onChatroomInfoRequest(std::string, bool)));
210 connect(this, SIGNAL(respondChatroomInfoRequest(ChatroomInfo, bool)),
211 m_chatroomDiscoveryBackend, SLOT(onRespondChatroomInfoRequest(ChatroomInfo, bool)));
212 connect(this, SIGNAL(shutdownDiscoveryBackend()),
213 m_chatroomDiscoveryBackend, SLOT(shutdown()));
214 connect(this, SIGNAL(identityUpdated(const QString&)),
215 m_chatroomDiscoveryBackend, SLOT(onIdentityUpdated(const QString&)));
216
217 // connect chatroom discovery back end with front end
218 connect(m_discoveryPanel, SIGNAL(waitForChatroomInfo(const QString&)),
219 m_chatroomDiscoveryBackend, SLOT(onWaitForChatroomInfo(const QString&)));
220 connect(m_discoveryPanel, SIGNAL(warning(const QString&)),
221 this, SLOT(onWarning(const QString&)));
222 connect(this, SIGNAL(identityUpdated(const QString&)),
223 m_discoveryPanel, SLOT(onIdentityUpdated(const QString&)));
224 connect(m_chatroomDiscoveryBackend, SIGNAL(chatroomListReady(const QStringList&)),
225 m_discoveryPanel, SLOT(onChatroomListReady(const QStringList&)));
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800226 connect(m_chatroomDiscoveryBackend, SIGNAL(chatroomInfoReady(const ChatroomInfo&, bool)),
227 m_discoveryPanel, SLOT(onChatroomInfoReady(const ChatroomInfo&, bool)));
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800228 connect(m_discoveryPanel, SIGNAL(startChatroom(const QString&, bool)),
229 this, SLOT(onStartChatroom(const QString&, bool)));
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800230 connect(m_discoveryPanel, SIGNAL(sendInvitationRequest(const QString&, const QString&)),
231 &m_backend, SLOT(onSendInvitationRequest(const QString&, const QString&)));
232 connect(&m_backend, SIGNAL(invitationRequestResult(const std::string&)),
233 m_discoveryPanel, SLOT(onInvitationRequestResult(const std::string&)));
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800234
235 m_chatroomDiscoveryBackend->start();
236
Yingdi Yu233a9722014-03-07 15:47:09 -0800237 createTrayIcon();
238
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700239 emit updateLocalPrefix();
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800240}
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700241
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800242Controller::~Controller()
243{
244 saveConf();
245}
246
247// public methods
248
249
250// private methods
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700251string
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800252Controller::getDBName()
253{
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700254 string dbName("chronos-");
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800255
256 std::stringstream ss;
257 {
258 using namespace CryptoPP;
259
260 SHA256 hash;
261 StringSource(m_identity.wireEncode().wire(), m_identity.wireEncode().size(), true,
262 new HashFilter(hash, new HexEncoder(new FileSink(ss), false)));
263 }
264 dbName.append(ss.str()).append(".db");
265
266 return dbName;
267}
268
269void
270Controller::openDB()
271{
272 m_db = QSqlDatabase::addDatabase("QSQLITE");
273 QString path = (QDir::home().path());
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700274 path.append(QDir::separator())
275 .append(".chronos")
276 .append(QDir::separator())
277 .append(getDBName().c_str());
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800278 m_db.setDatabaseName(path);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800279
Yingdi Yu42125862014-08-07 17:04:28 -0700280 m_db.open();
281
282 // bool ok = m_db.open();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700283 // _LOG_DEBUG("DB opened: " << std::boolalpha << ok );
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800284}
285
286void
287Controller::initialize()
288{
289 loadConf();
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700290
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800291 openDB();
292
293 emit identityUpdated(QString(m_identity.toUri().c_str()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800294}
295
296void
297Controller::loadConf()
298{
299 namespace fs = boost::filesystem;
300
301 fs::path chronosDir = fs::path(getenv("HOME")) / ".chronos";
302 fs::create_directories (chronosDir);
303
304 std::ifstream is((chronosDir / "config").c_str ());
305 ChronoChat::Conf conf;
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700306 if (conf.ParseFromIstream(&is)) {
307 m_identity.clear();
308 m_identity.append(conf.identity());
309 if (conf.has_nick())
310 m_nick = conf.nick();
311 else
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800312 m_nick = m_identity.get(-1).toUri();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700313 }
314 else {
315 m_identity.clear();
316 // TODO: change below to system default;
317 m_identity.append("chronochat-tmp-identity")
318 .append(getRandomString());
319
320 m_nick = m_identity.get(-1).toUri();
321 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800322}
323
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700324void
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800325Controller::saveConf()
326{
327 namespace fs = boost::filesystem;
328
329 fs::path chronosDir = fs::path(getenv("HOME")) / ".chronos";
330 fs::create_directories (chronosDir);
331
332 std::ofstream os((chronosDir / "config").c_str ());
333 ChronoChat::Conf conf;
334 conf.set_identity(m_identity.toUri());
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700335 if (!m_nick.empty())
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800336 conf.set_nick(m_nick);
337 conf.SerializeToOstream(&os);
338
339 os.close();
340}
341
342void
343Controller::createActions()
344{
345 m_startChatroom = new QAction(tr("Start new chat"), this);
346 connect(m_startChatroom, SIGNAL(triggered()), this, SLOT(onStartChatAction()));
347
348 m_settingsAction = new QAction(tr("Settings"), this);
349 connect(m_settingsAction, SIGNAL(triggered()), this, SLOT(onSettingsAction()));
350
351 m_editProfileAction = new QAction(tr("Edit profile"), this);
352 connect(m_editProfileAction, SIGNAL(triggered()), this, SLOT(onProfileEditorAction()));
353
354 m_contactListAction = new QAction(tr("Contact List"), this);
355 connect(m_contactListAction, SIGNAL(triggered()), this, SLOT(onContactListAction()));
356
357 m_addContactAction = new QAction(tr("Add contact"), this);
358 connect(m_addContactAction, SIGNAL(triggered()), this, SLOT(onAddContactAction()));
359
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800360 m_chatroomDiscoveryAction = new QAction(tr("Chatroom Discovery"), this);
361 connect(m_chatroomDiscoveryAction, SIGNAL(triggered()), this, SLOT(onChatroomDiscoveryAction()));
362
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800363 m_updateLocalPrefixAction = new QAction(tr("Update local prefix"), this);
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700364 connect(m_updateLocalPrefixAction, SIGNAL(triggered()),
365 &m_backend, SLOT(onUpdateLocalPrefixAction()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800366
367 m_minimizeAction = new QAction(tr("Mi&nimize"), this);
368 connect(m_minimizeAction, SIGNAL(triggered()), this, SLOT(onMinimizeAction()));
369
370 m_quitAction = new QAction(tr("Quit"), this);
371 connect(m_quitAction, SIGNAL(triggered()), this, SLOT(onQuitAction()));
Mengjin Yan391d7242014-08-28 20:51:55 -0700372
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800373}
374
375void
376Controller::createTrayIcon()
377{
378 createActions();
379
380 m_trayIconMenu = new QMenu(this);
381 m_trayIconMenu->addAction(m_startChatroom);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800382 m_trayIconMenu->addAction(m_chatroomDiscoveryAction);
Mengjin Yan391d7242014-08-28 20:51:55 -0700383
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800384 m_trayIconMenu->addSeparator();
385 m_trayIconMenu->addAction(m_settingsAction);
386 m_trayIconMenu->addAction(m_editProfileAction);
387 m_trayIconMenu->addSeparator();
388 m_trayIconMenu->addAction(m_contactListAction);
389 m_trayIconMenu->addAction(m_addContactAction);
390 m_trayIconMenu->addSeparator();
391 m_trayIconMenu->addAction(m_updateLocalPrefixAction);
392 m_trayIconMenu->addSeparator();
393 m_trayIconMenu->addAction(m_minimizeAction);
394 m_closeMenu = m_trayIconMenu->addMenu("Close chatroom");
395 m_closeMenu->setEnabled(false);
396 m_trayIconMenu->addSeparator();
397 m_trayIconMenu->addAction(m_quitAction);
398
399 m_trayIcon = new QSystemTrayIcon(this);
400 m_trayIcon->setContextMenu(m_trayIconMenu);
401
402 m_trayIcon->setIcon(QIcon(":/images/icon_small.png"));
403 m_trayIcon->setToolTip("ChronoChat System Tray Icon");
404 m_trayIcon->setVisible(true);
405}
406
407void
408Controller::updateMenu()
409{
410 QMenu* menu = new QMenu(this);
411 QMenu* closeMenu = 0;
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700412
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800413 menu->addAction(m_startChatroom);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800414 menu->addAction(m_chatroomDiscoveryAction);
Mengjin Yan391d7242014-08-28 20:51:55 -0700415
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800416 menu->addSeparator();
417 menu->addAction(m_settingsAction);
418 menu->addAction(m_editProfileAction);
419 menu->addSeparator();
Yingdi Yu233a9722014-03-07 15:47:09 -0800420 menu->addAction(m_contactListAction);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800421 menu->addAction(m_addContactAction);
422 menu->addSeparator();
423 {
424 ChatActionList::const_iterator it = m_chatActionList.begin();
425 ChatActionList::const_iterator end = m_chatActionList.end();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700426 if (it != end) {
427 for (; it != end; it++)
428 menu->addAction(it->second);
429 menu->addSeparator();
430 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800431 }
432 menu->addAction(m_updateLocalPrefixAction);
433 menu->addSeparator();
434 menu->addAction(m_minimizeAction);
435 closeMenu = menu->addMenu("Close chatroom");
436 {
437 ChatActionList::const_iterator it = m_closeActionList.begin();
438 ChatActionList::const_iterator end = m_closeActionList.end();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700439 if (it == end)
440 closeMenu->setEnabled(false);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800441 else
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700442 for (; it != end; it++)
443 closeMenu->addAction(it->second);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800444 }
445 menu->addSeparator();
446 menu->addAction(m_quitAction);
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700447
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800448 m_trayIcon->setContextMenu(menu);
449 delete m_trayIconMenu;
450 m_trayIconMenu = menu;
451 m_closeMenu = closeMenu;
452}
453
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700454string
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800455Controller::getRandomString()
456{
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700457 uint32_t r = ndn::random::generateWord32();
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800458 std::stringstream ss;
459 {
460 using namespace CryptoPP;
461 StringSource(reinterpret_cast<uint8_t*>(&r), 4, true,
462 new HexEncoder(new FileSink(ss), false));
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700463
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800464 }
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700465 // for (int i = 0; i < 8; i++)
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800466 // {
467 // uint32_t t = r & mask;
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700468 // if (t < 10)
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800469 // ss << static_cast<char>(t + 0x30);
470 // else
471 // ss << static_cast<char>(t + 0x57);
472 // r = r >> 4;
473 // }
474
475 return ss.str();
476}
477
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800478void
479Controller::addChatDialog(const QString& chatroomName, ChatDialog* chatDialog)
480{
481 m_chatDialogList[chatroomName.toStdString()] = chatDialog;
482 connect(chatDialog, SIGNAL(closeChatDialog(const QString&)),
483 this, SLOT(onRemoveChatDialog(const QString&)));
484 connect(chatDialog, SIGNAL(showChatMessage(const QString&, const QString&, const QString&)),
485 this, SLOT(onShowChatMessage(const QString&, const QString&, const QString&)));
Yingdi Yu233a9722014-03-07 15:47:09 -0800486 connect(chatDialog, SIGNAL(resetIcon()),
487 this, SLOT(onResetIcon()));
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700488 connect(&m_backend, SIGNAL(localPrefixUpdated(const QString&)),
Yingdi Yud45777b2014-10-16 23:54:11 -0700489 chatDialog->getBackend(), SLOT(updateRoutingPrefix(const QString&)));
Yingdi Yubaaaec02014-09-02 22:05:32 -0700490 connect(this, SIGNAL(localPrefixConfigured(const QString&)),
Yingdi Yud45777b2014-10-16 23:54:11 -0700491 chatDialog->getBackend(), SLOT(updateRoutingPrefix(const QString&)));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800492
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800493 // connect chat dialog with discovery backend
494 connect(chatDialog->getBackend(), SIGNAL(addInRoster(ndn::Name, ndn::Name::Component)),
495 m_chatroomDiscoveryBackend, SLOT(onAddInRoster(ndn::Name, ndn::Name::Component)));
496 connect(chatDialog->getBackend(), SIGNAL(eraseInRoster(ndn::Name, ndn::Name::Component)),
497 m_chatroomDiscoveryBackend, SLOT(onEraseInRoster(ndn::Name, ndn::Name::Component)));
498
499
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800500 QAction* chatAction = new QAction(chatroomName, this);
501 m_chatActionList[chatroomName.toStdString()] = chatAction;
Mengjin Yan391d7242014-08-28 20:51:55 -0700502 connect(chatAction, SIGNAL(triggered()),
Yingdi Yue8323b62014-09-02 17:24:15 -0700503 chatDialog, SLOT(onShow()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800504
505 QAction* closeAction = new QAction(chatroomName, this);
506 m_closeActionList[chatroomName.toStdString()] = closeAction;
Yingdi Yud45777b2014-10-16 23:54:11 -0700507 connect(closeAction, SIGNAL(triggered()),
508 chatDialog, SLOT(shutdown()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800509
510 updateMenu();
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800511 emit newChatroomForDiscovery(Name::Component(chatroomName.toStdString()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800512}
513
Mengjin Yan391d7242014-08-28 20:51:55 -0700514void
515Controller::updateDiscoveryList(const ChatroomInfo& info, bool isAdd)
516{
517 emit discoverChatroomChanged(info, isAdd);
518}
519
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800520void
521Controller::onIdentityUpdated(const QString& identity)
522{
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700523 while (!m_chatDialogList.empty()) {
524 ChatDialogList::const_iterator it = m_chatDialogList.begin();
Yingdi Yu1fd4e4a2015-02-02 13:37:50 -0800525 it->second->shutdown();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700526 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800527
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800528 emit closeDBModule();
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700529
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700530 Name identityName(identity.toStdString());
531 m_identity = identityName;
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800532
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700533 QTimer::singleShot(500, this, SLOT(onIdentityUpdatedContinued()));
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800534}
535
536void
537Controller::onIdentityUpdatedContinued()
538{
539 QString connection = m_db.connectionName();
540 // _LOG_DEBUG("connection name: " << connection.toStdString());
541 QSqlDatabase::removeDatabase(connection);
542 m_db.close();
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700543
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800544 openDB();
545
546 emit identityUpdated(QString(m_identity.toUri().c_str()));
547}
548
549void
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800550Controller::onNickUpdated(const QString& nick)
551{
552 m_nick = nick.toStdString();
553}
554
555void
556Controller::onLocalPrefixUpdated(const QString& localPrefix)
557{
Yingdi Yubaaaec02014-09-02 22:05:32 -0700558 QString privateLocalPrefix("/private/local");
559
Yingdi Yubaaaec02014-09-02 22:05:32 -0700560 if (privateLocalPrefix != localPrefix)
561 m_localPrefixDetected = true;
562 else
563 m_localPrefixDetected = false;
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700564
565 m_localPrefix = Name(localPrefix.toStdString());
Yingdi Yubaaaec02014-09-02 22:05:32 -0700566}
567
568void
569Controller::onLocalPrefixConfigured(const QString& prefix)
570{
571 if (!m_localPrefixDetected) {
572 m_localPrefix = Name(prefix.toStdString());
573 emit localPrefixConfigured(prefix);
574 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800575}
576
577void
578Controller::onStartChatAction()
579{
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700580 string chatroom = "chatroom-" + getRandomString();
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800581
582 m_startChatDialog->setChatroom(chatroom);
583 m_startChatDialog->show();
584 m_startChatDialog->raise();
585}
586
587void
588Controller::onSettingsAction()
589{
590 m_settingDialog->setNick(QString(m_nick.c_str()));
591 m_settingDialog->show();
592 m_settingDialog->raise();
593}
594
595void
596Controller::onProfileEditorAction()
597{
598 m_profileEditor->show();
599 m_profileEditor->raise();
600}
601
602void
603Controller::onAddContactAction()
604{
605 emit refreshBrowseContact();
606 m_browseContactDialog->show();
607 m_browseContactDialog->raise();
608}
609
610void
611Controller::onContactListAction()
612{
613 m_contactPanel->show();
614 m_contactPanel->raise();
615}
616
617void
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800618Controller::onChatroomDiscoveryAction()
619{
620 m_discoveryPanel->show();
621 m_discoveryPanel->raise();
622}
623
624void
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800625Controller::onDirectAdd()
626{
627 m_addContactPanel->show();
628 m_addContactPanel->raise();
629}
630
631void
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800632Controller::onMinimizeAction()
633{
634 m_settingDialog->hide();
635 m_startChatDialog->hide();
636 m_profileEditor->hide();
637 m_invitationDialog->hide();
638 m_addContactPanel->hide();
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800639 m_discoveryPanel->hide();
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800640
641 ChatDialogList::iterator it = m_chatDialogList.begin();
642 ChatDialogList::iterator end = m_chatDialogList.end();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700643 for (; it != end; it++)
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800644 it->second->hide();
645}
646
647void
648Controller::onQuitAction()
649{
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700650 while (!m_chatDialogList.empty()) {
651 ChatDialogList::const_iterator it = m_chatDialogList.begin();
Yingdi Yud45777b2014-10-16 23:54:11 -0700652 it->second->shutdown();
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700653 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800654
655 delete m_settingDialog;
656 delete m_startChatDialog;
657 delete m_profileEditor;
658 delete m_invitationDialog;
Yingdi Yuf4aaa8b2014-03-10 11:24:31 -0700659 delete m_browseContactDialog;
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800660 delete m_addContactPanel;
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800661 delete m_discoveryPanel;
662 if (m_chatroomDiscoveryBackend->isRunning()) {
663 emit shutdownDiscoveryBackend();
664 m_chatroomDiscoveryBackend->wait();
665 }
666 delete m_chatroomDiscoveryBackend;
Yingdi Yuf4aaa8b2014-03-10 11:24:31 -0700667
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700668 if (m_backend.isRunning()) {
669 emit shutdownBackend();
670 m_backend.wait();
671 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800672
673 QApplication::quit();
674}
675
676void
677Controller::onStartChatroom(const QString& chatroomName, bool secured)
678{
679 Name chatroomPrefix;
680 chatroomPrefix.append("ndn")
681 .append("broadcast")
682 .append("ChronoChat")
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800683 .append("Chatroom")
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800684 .append(chatroomName.toStdString());
685
686 // check if the chatroom exists
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700687 if (m_chatDialogList.find(chatroomName.toStdString()) != m_chatDialogList.end()) {
688 QMessageBox::information(this, tr("ChronoChat"),
689 tr("You are creating an existing chatroom."
690 "You can check it in the context memu."));
691 return;
692 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800693
Mengjin Yan391d7242014-08-28 20:51:55 -0700694 // TODO: We should create a chatroom specific key/cert
695 //(which should be created in the first half of this method
696 //, but let's use the default one for now.
Yingdi Yud45777b2014-10-16 23:54:11 -0700697 Name chatPrefix;
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800698 chatPrefix.append(m_identity).append("CHRONOCHAT-CHATDATA").append(chatroomName.toStdString());
Yingdi Yud45777b2014-10-16 23:54:11 -0700699
Mengjin Yan391d7242014-08-28 20:51:55 -0700700 ChatDialog* chatDialog
Yingdi Yud45777b2014-10-16 23:54:11 -0700701 = new ChatDialog(chatroomPrefix,
702 chatPrefix,
703 m_localPrefix,
704 chatroomName.toStdString(),
Yingdi Yu45da92a2015-02-02 13:17:03 -0800705 m_nick,
706 true,
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800707 m_identity,
708 this);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800709
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700710 addChatDialog(chatroomName, chatDialog);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800711 chatDialog->show();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700712
713 emit addChatroom(chatroomName);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800714}
715
716void
Yingdi Yueb692ac2015-02-10 18:46:18 -0800717Controller::onStartChatroom2(chronochat::Invitation invitation, bool secured)
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800718{
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700719 QString chatroomName = QString::fromStdString(invitation.getChatroom());
720 onStartChatroom(chatroomName, secured);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800721
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700722 ChatDialogList::iterator it = m_chatDialogList.find(chatroomName.toStdString());
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800723
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700724 BOOST_ASSERT(it != m_chatDialogList.end());
725 it->second->addSyncAnchor(invitation);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800726}
727
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700728
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800729void
730Controller::onShowChatMessage(const QString& chatroomName, const QString& from, const QString& data)
731{
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700732 m_trayIcon->showMessage(QString("Chatroom %1 has a new message").arg(chatroomName),
733 QString("<%1>: %2").arg(from).arg(data),
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800734 QSystemTrayIcon::Information, 20000);
735 m_trayIcon->setIcon(QIcon(":/images/note.png"));
736}
737
738void
Yingdi Yu233a9722014-03-07 15:47:09 -0800739Controller::onResetIcon()
740{
741 m_trayIcon->setIcon(QIcon(":/images/icon_small.png"));
742}
743
744void
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800745Controller::onRemoveChatDialog(const QString& chatroomName)
746{
747 ChatDialogList::iterator it = m_chatDialogList.find(chatroomName.toStdString());
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700748 if (it != m_chatDialogList.end()) {
749 ChatDialog* deletedChat = it->second;
750 if (deletedChat)
751 delete deletedChat;
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800752
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700753 m_chatDialogList.erase(it);
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800754
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700755 QAction* chatAction = m_chatActionList[chatroomName.toStdString()];
756 QAction* closeAction = m_closeActionList[chatroomName.toStdString()];
757 if (chatAction)
758 delete chatAction;
759 if (closeAction)
760 delete closeAction;
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700761
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700762 m_chatActionList.erase(chatroomName.toStdString());
763 m_closeActionList.erase(chatroomName.toStdString());
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700764
Yingdi Yu0b0a7362014-08-05 16:31:30 -0700765 updateMenu();
766 }
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800767}
768
769void
770Controller::onWarning(const QString& msg)
771{
772 QMessageBox::information(this, tr("ChronoChat"), msg);
773}
774
775void
Yingdi Yufa0b6a02014-04-30 14:26:42 -0700776Controller::onError(const QString& msg)
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800777{
778 QMessageBox::critical(this, tr("ChronoChat"), msg, QMessageBox::Ok);
779 exit(1);
780}
781
Yingdi Yu233a9722014-03-07 15:47:09 -0800782void
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800783Controller::onChatroomInfoRequest(std::string chatroomName, bool isManager)
Mengjin Yan391d7242014-08-28 20:51:55 -0700784{
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800785 auto it = m_chatDialogList.find(chatroomName);
786 if (it != m_chatDialogList.end())
787 emit respondChatroomInfoRequest(it->second->getChatroomInfo(), isManager);
Mengjin Yan391d7242014-08-28 20:51:55 -0700788}
789
Yingdi Yueb692ac2015-02-10 18:46:18 -0800790} // namespace chronochat
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800791
792#if WAF
793#include "controller.moc"
Yingdi Yu42125862014-08-07 17:04:28 -0700794// #include "controller.cpp.moc"
Yingdi Yu348f5ea2014-03-01 14:47:25 -0800795#endif