On 29/10/13 01:51, Tommaso Cucinotta wrote:
> I was sure to have done that, at least the reading allowed for anyone 
> (gitweb), or perhaps
> this doesn't allow cloning yet ? I'll add "@all" then, but now ssh seems to 
> be hanging, don't
> know why.

Just while we find a way to fix that perms issue (perhaps any other developer 
can issue
that ssh command

  ssh g...@git.lyx.org setperms developers/tommaso/lyx

to update the perms), let's try the old way:

  http://www.lyx.org/trac/attachment/ticket/7964/lyx-xmpp-chat-v3.patch

Bye,

        T.

commit 6c8aaadc
Author: Tommaso Cucinotta <tomm...@lyx.org>
Date:   Wed Oct 16 22:55:40 2013 +0100

    LyX XMPP Chat

diff --git a/config/qt4.m4 b/config/qt4.m4
index 58edd851..691c612f 100644
--- a/config/qt4.m4
+++ b/config/qt4.m4
@@ -37,8 +37,8 @@ AC_DEFUN([QT4_CHECK_COMPILE],
 			fi
 		done
 		qt4_cv_libname=
-		for libname in '-lQtCore -lQtGui' \
-		               '-lQtCore4 -lQtGui4' \
+		for libname in '-lQtCore -lQtGui -lQtNetwork -lQtXml qxmpp' \
+		               '-lQtCore4 -lQtGui4 -lQtNetwork4 -lQtXml4' \
 		               '-framework QtCore -framework QtConcurrent -framework QtWidgets -framework QtGui'\
 		               '-framework QtCore -framework QtGui'
 		do
@@ -182,18 +182,18 @@ AC_DEFUN([QT4_DO_PKG_CONFIG],
 		QT4_CORE_LIB=`$PKG_CONFIG --libs-only-l QtCore`
 		AC_SUBST(QT4_CORE_LIB)
 	fi
-	PKG_CHECK_MODULES(QT4_FRONTEND, QtCore QtGui,,[:])
+	PKG_CHECK_MODULES(QT4_FRONTEND, QtCore QtGui QtNetwork QtXml qxmpp,,[:])
 	if test "$pkg_failed" = "no" ; then
 		QT4_INCLUDES=$QT4_FRONTEND_CFLAGS
 		dnl QT4_LDFLAGS=$QT4_FRONTEND_LIBS
-		QT4_LDFLAGS=`$PKG_CONFIG --libs-only-L QtCore QtGui`
+		QT4_LDFLAGS=`$PKG_CONFIG --libs-only-L QtCore QtGui QtNetwork QtXml qxmpp`
 		AC_SUBST(QT4_INCLUDES)
 		AC_SUBST(QT4_LDFLAGS)
 		QT4_VERSION=`$PKG_CONFIG --modversion QtCore`
 		AC_SUBST(QT4_VERSION)
-		QT4_LIB=`$PKG_CONFIG --libs-only-l QtCore QtGui`
+		QT4_LIB=`$PKG_CONFIG --libs-only-l QtCore QtGui QtNetwork QtXml qxmpp`
 		AC_SUBST(QT4_LIB)
-		dnl LIBS="$LIBS `$PKG_CONFIG --libs-only-other QtCore QtGui`"
+		dnl LIBS="$LIBS `$PKG_CONFIG --libs-only-other QtCore QtGui QtNetwork QtXml qxmpp`"
 	fi
 	PKG_CONFIG_PATH=$save_PKG_CONFIG_PATH
 	dnl Actually, the values of QT4_LIB and QT4_CORE_LIB can be completely
@@ -225,7 +225,7 @@ AC_DEFUN([QT4_DO_MANUAL_CONFIG],
 	QT4_CORE_LDFLAGS=
 	if test -n "$qt4_cv_includes"; then
 		QT4_INCLUDES="-I$qt4_cv_includes"
-		for i in Qt QtCore QtGui QtWidgets QtConcurrent; do
+		for i in Qt QtCore QtGui QtNetwork QtXml qxmpp QtWidgets QtConcurrent; do
 			QT4_INCLUDES="$QT4_INCLUDES -I$qt4_cv_includes/$i"
 		done
 		QT4_CORE_INCLUDES="-I$qt4_cv_includes -I$qt4_cv_includes/QtCore"
diff --git a/lib/Makefile.am b/lib/Makefile.am
index c354bb22..688f82aa 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -354,6 +354,8 @@ dist_images_DATA = \
 	images/all-changes-accept.png \
 	images/all-changes-reject.png \
 	images/amssymb.png \
+	images/available.png \
+	images/away.png \
 	images/banner.png \
 	images/bookmark-goto.png \
 	images/bookmark-goto_0.png \
@@ -424,6 +426,7 @@ dist_images_DATA = \
 	images/dialog-show_vclog.png \
 	images/dialog-toggle_findreplaceadv.png \
 	images/dialog-toggle_toc.png \
+	images/donotdisturb.png \
 	images/down.png \
 	images/editclear.png \
 	images/ert-insert.png \
@@ -473,6 +476,7 @@ dist_images_DATA = \
 	images/nomencl-insert.png \
 	images/note-insert.png \
 	images/note-next.png \
+	images/offline.png \
 	images/paste.png \
 	images/promote.png \
 	images/psnfss1.png \
diff --git a/lib/images/available.png b/lib/images/available.png
new file mode 100644
index 00000000..8b32ddd1
Binary files /dev/null and b/lib/images/available.png differ
diff --git a/lib/images/away.png b/lib/images/away.png
new file mode 100644
index 00000000..61a1eab8
Binary files /dev/null and b/lib/images/away.png differ
diff --git a/lib/images/donotdisturb.png b/lib/images/donotdisturb.png
new file mode 100644
index 00000000..1d6f9ffc
Binary files /dev/null and b/lib/images/donotdisturb.png differ
diff --git a/lib/images/offline.png b/lib/images/offline.png
new file mode 100644
index 00000000..1143dcbf
Binary files /dev/null and b/lib/images/offline.png differ
diff --git a/lib/ui/stdmenus.inc b/lib/ui/stdmenus.inc
index 81d0b1cb..12a74287 100644
--- a/lib/ui/stdmenus.inc
+++ b/lib/ui/stdmenus.inc
@@ -567,6 +567,8 @@ Menuset
 		Item "TeX Information|I" "dialog-show texinfo"
 		Item "Compare...|C" "dialog-show compare"
 		Separator
+		Item "LyX Chat|y" "dialog-show chat"
+		Separator
 # A LOT of applications have Tools->Prefs. Remember this
 # should be rarely used - Edit menu is not a good place to
 # have it.
diff --git a/src/LyXRC.cpp b/src/LyXRC.cpp
index c0a64679..f02af500 100644
--- a/src/LyXRC.cpp
+++ b/src/LyXRC.cpp
@@ -206,6 +206,7 @@ LexerKeyword lyxrcTags[] = {
 	{ "\\use_system_colors", LyXRC::RC_USE_SYSTEM_COLORS },
 	{ "\\use_system_theme_icons", LyXRC::RC_USE_SYSTEM_THEME_ICONS },
 	{ "\\use_tooltip", LyXRC::RC_USE_TOOLTIP },
+	{ "\\user_chat_id", LyXRC::RC_USER_CHAT_ID },
 	{ "\\user_email", LyXRC::RC_USER_EMAIL },
 	{ "\\user_name", LyXRC::RC_USER_NAME },
 	{ "\\view_dvi_paper_option", LyXRC::RC_VIEWDVI_PAPEROPTION },
@@ -1204,6 +1205,9 @@ LyXRC::ReturnValues LyXRC::read(Lexer & lexrc, bool check_format)
 			lexrc >> preview_scale_factor;
 			break;
 
+		case RC_USER_CHAT_ID:
+			lexrc >> user_chat_id;
+			break;
 		case RC_USER_NAME:
 			lexrc >> user_name;
 			break;
@@ -1625,6 +1629,11 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c
 		if (tag != RC_LAST)
 			break;
 
+	case RC_USER_CHAT_ID:
+		os << "\\user_chat_id \"" << user_chat_id << "\"\n";
+		if (tag != RC_LAST)
+			break;
+
 	case RC_USER_NAME:
 		os << "\\user_name \"" << user_name << "\"\n";
 		if (tag != RC_LAST)
@@ -3046,6 +3055,7 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
 	case LyXRC::RC_TEXINPUTS_PREFIX:
 	case LyXRC::RC_THESAURUSDIRPATH:
 	case LyXRC::RC_UIFILE:
+	case LyXRC::RC_USER_CHAT_ID:
 	case LyXRC::RC_USER_EMAIL:
 	case LyXRC::RC_USER_NAME:
 	case LyXRC::RC_USE_CONVERTER_CACHE:
@@ -3508,6 +3518,9 @@ string const LyXRC::getDescription(LyXRCTags tag)
 		str = _("The UI (user interface) file. Can either specify an absolute path, or LyX will look in its global and local ui/ directories.");
 		break;
 
+	case RC_USER_CHAT_ID:
+		break;
+
 	case RC_USER_EMAIL:
 		break;
 
diff --git a/src/LyXRC.h b/src/LyXRC.h
index 94914ccd..49e1394b 100644
--- a/src/LyXRC.h
+++ b/src/LyXRC.h
@@ -178,6 +178,7 @@ public:
 		RC_THESAURUSDIRPATH,
 		RC_UIFILE,
 		RC_USELASTFILEPOS,
+		RC_USER_CHAT_ID,
 		RC_USER_EMAIL,
 		RC_USER_NAME,
 		RC_USE_CONVERTER_CACHE,
@@ -462,6 +463,8 @@ public:
 	bool preview_hashed_labels;
 	///
 	double preview_scale_factor;
+	/// user default chat (xmpp) id
+	std::string user_chat_id;
 	/// user name
 	std::string user_name;
 	/// user email
diff --git a/src/Makefile.am b/src/Makefile.am
index ea5ca423..4e204bff 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -187,7 +187,8 @@ SOURCEFILESCORE = \
 	VCBackend.cpp \
 	version.cpp \
 	VSpace.cpp \
-	WordList.cpp
+	WordList.cpp \
+	moc_BufferView.cpp
 
 HEADERFILESCORE = \
 	Author.h \
@@ -318,7 +319,7 @@ endif
 
 #########################  Qt stuff  ##############################
 
-MOCHEADER = Compare.h
+MOCHEADER = Compare.h BufferView.h
 
 if INSTALL_WINDOWS
 
diff --git a/src/frontends/qt4/GuiBuddies.cpp b/src/frontends/qt4/GuiBuddies.cpp
new file mode 100644
index 00000000..138d1628
--- /dev/null
+++ b/src/frontends/qt4/GuiBuddies.cpp
@@ -0,0 +1,284 @@
+/**
+ * \file GuiBuddies.cpp
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "GuiBuddies.h"
+#include "GuiChat.h"
+#include "LyXRC.h"
+
+#include "GuiApplication.h"
+#include "GuiView.h"
+#include "GuiWorkArea.h"
+#include "qt_helpers.h"
+#include "Language.h"
+#include <QWidget>
+#include <QInputDialog>
+#include <QLineEdit>
+
+#include "FuncRequest.h"
+#include "lyxfind.h"
+
+#include "frontends/alert.h"
+#include "DispatchResult.h"
+
+#include "support/debug.h"
+#include "support/filetools.h"
+#include "support/FileName.h"
+#include "support/gettext.h"
+#include "support/lassert.h"
+#include "support/lstrings.h"
+#include "support/Package.h"
+
+#include <qxmpp/QXmppMessage.h>
+#include <qxmpp/QXmppRosterManager.h>
+
+using namespace std;
+using namespace lyx::support;
+
+namespace lyx {
+namespace frontend {
+
+
+GuiBuddiesWidget::GuiBuddiesWidget(GuiView & view)
+	: QTabWidget(&view), view_(view)
+{
+	setupUi(this);
+
+	connect(buddiesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
+		SLOT(onItemDoubleClicked(QListWidgetItem*)));
+
+	connect(theChatMessenger(), SIGNAL(stateChanged(QXmppClient::State)),
+		this, SLOT(onStateChanged(QXmppClient::State)));
+
+	connect(&theChatMessenger()->rosterManager(),
+		SIGNAL(presenceChanged(const QString&, const QString&)),
+		this, SLOT(onPresenceChanged(const QString&, const QString&)));
+
+	connect(theChatMessenger(), SIGNAL(presenceReceived(const QXmppMessage&)),
+	 	this, SLOT(presenceReceived(const QXmppMessage&)));
+
+	connect(userEdit, SIGNAL(returnPressed()),
+	 	this, SLOT(on_connectPB_clicked()));
+
+	connect(statusCO, SIGNAL(activated(const QString&)),
+	 	this, SLOT(onStateSelected(const QString &)));
+
+	username = toqstr(lyxrc.user_chat_id);
+	// TODO: recover password from somewhere
+	//password = toqstr("secret");
+	userEdit->setText(username);
+
+	if (username.size() > 0 && password.size() > 0)
+	  theChatMessenger()->connectToServer(username, password);
+
+	icon_avail = QIcon::fromTheme("available", getPixmap("images/", "available", "png"));
+	icon_offline = QIcon::fromTheme("offline", getPixmap("images/", "offline", "png"));
+	icon_away = QIcon::fromTheme("away", getPixmap("images/", "away", "png"));
+	icon_dnd = QIcon::fromTheme("donotdisturb", getPixmap("images/", "donotdisturb", "png"));
+}
+
+
+GuiBuddiesWidget::~GuiBuddiesWidget()
+{
+	theChatMessenger()->disconnectFromServer();
+}
+
+
+bool GuiBuddiesWidget::initialiseParams(std::string const & /*params*/)
+{
+	return true;
+}
+
+
+static string availStatusToText(QXmppPresence::AvailableStatusType s) {
+  static const char *texts[] = { "Online", "Away", "Extended Away", "Do Not Disturb", "Buddies", "Invisible" };
+  LASSERT(s <= sizeof(texts) / sizeof(texts[0]), /**/);
+  return texts[s];
+}
+
+
+static string presenceTypeToText(QXmppPresence::Type t) {
+  static const char *texts[] = { "Error", "Available", "Unavailable", "Subscribe", "Subscribed", "Unsubscribe", "Unsubscribed", "Probe" };
+  LASSERT(t <= sizeof(texts) / sizeof(texts[0]), /**/);
+  return texts[t];
+}
+
+
+static QListWidgetItem *findInListWidget(QListWidget *p_list, const QString &s) {
+  for (int i = 0; i < p_list->count(); ++i)
+    if (p_list->item(i)->text() == s)
+      return p_list->item(i);
+  return 0;
+}
+
+
+QIcon GuiBuddiesWidget::presenceToIcon(const QXmppPresence & pres) {
+  if (pres.type() == QXmppPresence::Unavailable)
+    return icon_offline;
+  if (pres.availableStatusType() == QXmppPresence::Online
+      || pres.availableStatusType() == QXmppPresence::Chat)
+    return icon_avail;
+  if (pres.availableStatusType() == QXmppPresence::DND)
+    return icon_dnd;
+  return icon_away;
+}
+
+
+static void dumpPres(const QXmppPresence & pres)
+{
+  lyxerr << "presence=" << availStatusToText(pres.availableStatusType()) << ", type=" << presenceTypeToText(pres.type()) << endl;
+}
+
+
+void GuiBuddiesWidget::onPresenceChanged(const QString& id, const QString &res)
+{
+  QXmppPresence pres = theChatMessenger()->rosterManager().getPresence(id, res);
+  lyxerr << "Friend with id=" << id << ", res=" << res << " ";
+  dumpPres(pres);
+
+  // Ignore subscribe/unsubscribe/probe messages, requesting an action/reaction
+  if (pres.type() != QXmppPresence::Available
+      && pres.type() != QXmppPresence::Unavailable)
+    return;
+
+  if (id == username) {
+    // TBD
+    return;
+  }
+
+  QListWidgetItem *p_item = findInListWidget(buddiesList, id);
+  if (p_item) {
+    p_item->setIcon(presenceToIcon(pres));
+  } else {
+    p_item = new QListWidgetItem(presenceToIcon(pres), id, buddiesList);
+  }
+}
+
+
+void GuiBuddiesWidget::onPresenceReceived(const QXmppPresence& message)
+{
+  QString from = message.from();
+  QString status = message.statusText();
+  lyxerr << from << ": " << status << endl;
+}
+
+
+void GuiBuddiesWidget::on_connectPB_clicked()
+{
+  username = userEdit->text();
+  QString title = QString("Enter Password") + " for " + username;
+  bool ok;
+  password = QInputDialog::getText(this, title, tr("Password: "), QLineEdit::Password, password, &ok);
+  if (ok) {
+    theChatMessenger()->disconnectFromServer();
+    buddiesList->clear();
+    theChatMessenger()->connectToServer(username, password);
+    lyxrc.user_chat_id = fromqstr(username);
+    dispatch(FuncRequest(LFUN_PREFERENCES_SAVE));
+  }
+}
+
+
+void GuiBuddiesWidget::on_disconnectPB_clicked()
+{
+  theChatMessenger()->disconnectFromServer();
+  buddiesList->clear();
+}
+
+
+void GuiBuddiesWidget::onStateChanged(QXmppClient::State state)
+{
+  if (state == QXmppClient::DisconnectedState)
+    statusLabel->setText(tr("Disconnected"));
+  else if (state == QXmppClient::ConnectedState)
+    statusLabel->setText(tr("Connected"));
+  else if (state == QXmppClient::ConnectingState)
+    statusLabel->setText(tr("Connecting..."));
+}
+
+
+void GuiBuddiesWidget::onStateSelected(const QString & qs)
+{
+  QXmppPresence p;
+  string s = fromqstr(qs);
+  if (s == "Available")
+    p.setAvailableStatusType(QXmppPresence::Online);
+  else if (s == "Away")
+    p.setAvailableStatusType(QXmppPresence::Away);
+  else if (s == "Do Not Disturb")
+    p.setAvailableStatusType(QXmppPresence::DND);
+  lyxerr << "Setting ";
+  dumpPres(p);
+  theChatMessenger()->setClientPresence(p);
+}
+
+
+void GuiBuddiesWidget::onItemDoubleClicked(QListWidgetItem *p_item)
+{
+  GuiChat *p_dlg = dynamic_cast<GuiChat *>(view_.findOrBuild("chat-bar", false));
+  if (p_dlg) {
+    GuiChatWidget *p_chat = p_dlg->chatWidget();
+    p_chat->setUserName(fromqstr(username));
+    string destname = fromqstr(p_item->text());
+    p_chat->setDestName(destname);
+    p_chat->ensureChatVisible(destname);
+    view_.currentWorkArea()->stopBlinkingCursor();
+    p_dlg->show();
+    p_dlg->setFocus();
+    //view_.setCurrentWorkArea(p_chat->chat_work_area_);
+  }
+}
+
+
+GuiBuddies::GuiBuddies(GuiView & parent,
+		Qt::DockWidgetArea area, Qt::WindowFlags flags)
+	: DockView(parent, "buddies", qt_("LyX Buddies"),
+		   area, flags)
+{
+	widget_ = new GuiBuddiesWidget(parent);
+	setWidget(widget_);
+	setFocusProxy(widget_);
+	setVisible(true);
+}
+
+
+GuiBuddies::~GuiBuddies()
+{
+	setFocusProxy(0);
+	delete widget_;
+}
+
+
+bool GuiBuddies::initialiseParams(std::string const & params)
+{
+	return widget_->initialiseParams(params);
+}
+
+
+Dialog * createGuiBuddies(GuiView & lv)
+{
+	GuiBuddies * gui = new GuiBuddies(lv, Qt::LeftDockWidgetArea);
+	GuiBuddiesWidget *p_buddies = gui->buddiesWidget();
+	theChatMessenger()->p_buddies = p_buddies;
+
+#ifdef Q_WS_MACX
+	// On Mac show and floating
+	gui->setFloating(true);
+#endif
+	return gui;
+}
+
+
+} // namespace frontend
+} // namespace lyx
+
+
+#include "moc_GuiBuddies.cpp"
diff --git a/src/frontends/qt4/GuiBuddies.h b/src/frontends/qt4/GuiBuddies.h
new file mode 100644
index 00000000..d85f131b
--- /dev/null
+++ b/src/frontends/qt4/GuiBuddies.h
@@ -0,0 +1,105 @@
+// -*- C++ -*-
+/**
+ * \file GuiBuddies.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef QGUIBUDDIES_H
+#define QGUIBUDDIES_H
+
+#undef QT_NO_KEYWORDS 
+#include "GuiWorkArea.h"
+
+#include "DockView.h"
+// This is needed so that ui_BuddiesUi.h can find qt_()
+#include "qt_helpers.h"
+#include "ui_BuddiesUi.h"
+
+#include "BufferView.h"
+#include "Buffer.h"
+#include "LyX.h"
+#include "Text.h"
+#include "lyxfind.h"
+
+#include <QDialog>
+#include <QTabWidget>
+
+#include <string>
+
+#include <qxmpp/QXmppClient.h>
+
+namespace lyx {
+namespace frontend {
+
+class GuiBuddiesWidget : public QTabWidget, public Ui::buddiesPane
+{
+	Q_OBJECT
+
+public:
+	GuiBuddiesWidget(GuiView & view);
+	~GuiBuddiesWidget();
+	bool initialiseParams(std::string const & params);
+
+	QString username;
+	QString password;
+
+public Q_SLOTS:
+	void onStateChanged(QXmppClient::State);
+	void onStateSelected(const QString &);
+
+private:
+	///
+	GuiView & view_;
+	QIcon icon_avail;
+	QIcon icon_offline;
+	QIcon icon_away;
+	QIcon icon_dnd;
+
+	QIcon presenceToIcon(const QXmppPresence & pres);
+
+private Q_SLOTS:
+	void on_connectPB_clicked();
+	void on_disconnectPB_clicked();
+	void onItemDoubleClicked(QListWidgetItem*);
+	void onPresenceChanged(const QString&, const QString&);
+	void onPresenceReceived(const QXmppPresence&);
+};
+
+
+class GuiBuddies : public DockView
+{
+	Q_OBJECT
+public:
+	GuiBuddies(
+		GuiView & parent, ///< the main window where to dock.
+		Qt::DockWidgetArea area = Qt::BottomDockWidgetArea, ///< Position of the dock (and also drawer)
+		Qt::WindowFlags flags = 0);
+
+	~GuiBuddies();
+
+	bool initialiseParams(std::string const &);
+	void clearParams() {}
+	void dispatchParams() {}
+	bool isBufferDependent() const { return false; }
+	bool needBufferOpen() const { return false; }
+
+	/// update
+	void updateView() {}
+
+	GuiBuddiesWidget * buddiesWidget() { return widget_; }
+
+private:
+	/// The encapsulated widget.
+	GuiBuddiesWidget * widget_;
+};
+
+
+} // namespace frontend
+} // namespace lyx
+
+#endif // QGUIBUDDIES_H
diff --git a/src/frontends/qt4/GuiChat.cpp b/src/frontends/qt4/GuiChat.cpp
new file mode 100644
index 00000000..4021ea52
--- /dev/null
+++ b/src/frontends/qt4/GuiChat.cpp
@@ -0,0 +1,278 @@
+/**
+ * \file GuiChat.cpp
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "GuiChat.h"
+#include "GuiBuddies.h"
+
+#include "CutAndPaste.h"
+#include "Lexer.h"
+#include "GuiApplication.h"
+#include "GuiView.h"
+#include "GuiWorkArea.h"
+#include "qt_helpers.h"
+#include "Language.h"
+
+#include "BufferParams.h"
+#include "BufferList.h"
+#include "TextClass.h"
+#include "Cursor.h"
+#include "FuncRequest.h"
+#include "lyxfind.h"
+#include "output_latex.h"
+#include "support/Package.h"
+
+#include "frontends/alert.h"
+
+#include "support/debug.h"
+#include "support/filetools.h"
+#include "support/FileName.h"
+#include "support/gettext.h"
+#include "support/lassert.h"
+#include "support/lstrings.h"
+
+#include <QCloseEvent>
+#include <QLineEdit>
+#include <QMenu>
+
+#include <qxmpp/QXmppMessage.h>
+#include <qxmpp/QXmppRosterManager.h>
+
+using namespace std;
+using namespace lyx::support;
+
+namespace lyx {
+namespace frontend {
+
+
+GuiChatWidget::GuiChatWidget(GuiView & view)
+	: QTabWidget(&view), view_(view)
+{
+	setupUi(this);
+	chat_work_area_->setGuiView(view_);
+	chat_work_area_->init();
+	chat_work_area_->setFrameStyle(QFrame::StyledPanel);
+
+	setFocusProxy(chat_work_area_);
+	chat_work_area_->installEventFilter(this);
+	// We don't want two cursors blinking.
+	chat_work_area_->stopBlinkingCursor();
+
+	//sendPB->setEnabled(false);
+}
+
+
+GuiChatWidget::~GuiChatWidget() {
+}
+
+
+void GuiChatWidget::setUserName(std::string const & name)
+{
+	username = name;
+	fromLE->setText(toqstr(name));
+}
+
+
+void GuiChatWidget::setDestName(std::string const & name)
+{
+	destname = name;
+	destLE->setText(toqstr(name));
+}
+
+
+void GuiChatWidget::onMessageReceived(string const & from, string const & s)
+{
+  printf("Received %lu bytes: %s\n", s.size(), s.c_str());
+  if (view_.currentMainWorkArea() && view_.documentBufferView() && s.size() > 0) {
+    appendToChat(from, from, from_utf8(s));
+    view_.setCurrentWorkArea(chat_work_area_);
+  }
+}
+
+
+bool GuiChatWidget::eventFilter(QObject * obj, QEvent * event)
+{
+	if (event->type() != QEvent::KeyPress
+		  || (obj != chat_work_area_))
+		return QWidget::eventFilter(obj, event);
+
+	QKeyEvent * e = static_cast<QKeyEvent *> (event);
+	switch (e->key()) {
+	case Qt::Key_Escape:
+		if (e->modifiers() == Qt::NoModifier) {
+			hideDialog();
+			return true;
+		}
+		break;
+
+	case Qt::Key_Enter:
+	case Qt::Key_Return: {
+		on_sendPB_clicked();
+		return true;
+		}
+
+	default:
+		break;
+	}
+	// standard event processing
+	return QWidget::eventFilter(obj, event);
+}
+
+
+void GuiChatWidget::hideDialog()
+{
+	dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "chat"));
+}
+
+
+static docstring buffer_to_latex(Buffer & buffer) 
+{
+	OutputParams runparams(&buffer.params().encoding());
+	TexRow texrow;
+	odocstringstream ods;
+	otexstream os(ods, texrow);
+	runparams.nice = true;
+	runparams.flavor = OutputParams::LATEX;
+	runparams.linelen = 80; //lyxrc.plaintext_linelen;
+	// No side effect of file copying and image conversion
+	runparams.dryrun = true;
+	pit_type const endpit = buffer.paragraphs().size();
+	for (pit_type pit = 0; pit != endpit; ++pit) {
+		TeXOnePar(buffer, buffer.text(), pit, os, runparams);
+		LYXERR(Debug::FIND, "searchString up to here: " << ods.str());
+	}
+	return ods.str();
+}
+
+
+void GuiChatWidget::ensureChatVisible(const string & peername)
+{
+	FileName chat_file = theChatMessenger()->chatFileName(peername);
+
+	DispatchResult dr;
+	dispatch(FuncRequest(LFUN_FILE_OPEN, chat_file.absFileName()), dr);
+	dispatch(FuncRequest(LFUN_BUFFER_END), dr);
+}
+
+
+void GuiChatWidget::appendToChat(const string & peername, string const & prefix, docstring const & latex)
+{
+	ensureChatVisible(peername);
+
+	BufferView & bv = view_.currentMainWorkArea()->bufferView();
+	ErrorList & el = bv.buffer().errorList("Parse");
+
+	DispatchResult dr;
+	view_.dispatch(FuncRequest(LFUN_BUFFER_END), dr);
+	view_.dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK), dr);
+
+	Buffer * new_buf = theBufferList().newInternalBuffer(
+		support::FileName::tempName("embedded.internal").absFileName());
+	new_buf->setUnnamed(true);
+
+	new_buf->importString("latex", from_utf8(string("\\textbf{") + prefix + "}: "), el);
+	cap::pasteParagraphList(bv.cursor(), new_buf->paragraphs(),
+				new_buf->params().documentClassPtr(), el);
+
+	if (!new_buf->importString("latex", latex, el))
+		new_buf->importString("txt", latex, el);
+	cap::pasteParagraphList(bv.cursor(), new_buf->paragraphs(),
+				new_buf->params().documentClassPtr(), el);
+	bv.buffer().changed(true);
+	view_.dispatch(FuncRequest(LFUN_BUFFER_WRITE), dr);
+	bv.processUpdateFlags(Update::Force | Update::FitCursor);
+}
+
+
+void GuiChatWidget::on_sendPB_clicked() 
+{
+	if (destname.empty())
+		return;
+	Buffer & msg_buf = chat_work_area_->bufferView().buffer();
+	docstring latex = buffer_to_latex(msg_buf);
+	theChatMessenger()->sendMessage(username, destname, to_utf8(latex));
+	appendToChat(destname, username, latex);
+
+	chat_work_area_->setFocus();
+	dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
+	dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
+	dispatch(FuncRequest(LFUN_CUT));
+}
+
+
+void GuiChatWidget::showEvent(QShowEvent * ev)
+{
+	this->QTabWidget::showEvent(ev);
+	LYXERR(Debug::FIND, "showEvent()" << endl);
+	view_.setCurrentWorkArea(chat_work_area_);
+	LYXERR(Debug::FIND, "Selecting entire chat buffer");
+	dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
+	dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
+}
+
+
+void GuiChatWidget::hideEvent(QHideEvent *ev)
+{
+	LYXERR(Debug::FIND, "hideEvent");
+	this->QTabWidget::hideEvent(ev);
+}
+
+
+bool GuiChatWidget::initialiseParams(std::string const & /*params*/)
+{
+	return true;
+}
+
+
+GuiChat::GuiChat(GuiView & parent,
+		Qt::DockWidgetArea area, Qt::WindowFlags flags)
+	: DockView(parent, "chat", qt_("LyX Chat"),
+		   area, flags)
+{
+	widget_ = new GuiChatWidget(parent);
+	setWidget(widget_);
+	setFocusProxy(widget_);
+}
+
+
+GuiChat::~GuiChat()
+{
+	setFocusProxy(0);
+	delete widget_;
+}
+
+
+bool GuiChat::initialiseParams(std::string const & params)
+{
+	return widget_->initialiseParams(params);
+}
+
+
+Dialog * createGuiChat(GuiView & lv)
+{
+	GuiChat * gui = new GuiChat(lv, Qt::BottomDockWidgetArea);
+	GuiChatWidget *p_chat = gui->chatWidget();
+	theChatMessenger()->p_chat = p_chat;
+
+#ifdef Q_WS_MACX
+	// On Mac show and floating
+	gui->setFloating(true);
+#endif
+
+	return gui;
+}
+
+
+} // namespace frontend
+} // namespace lyx
+
+
+#include "moc_GuiChat.cpp"
diff --git a/src/frontends/qt4/GuiChat.h b/src/frontends/qt4/GuiChat.h
new file mode 100644
index 00000000..724815a9
--- /dev/null
+++ b/src/frontends/qt4/GuiChat.h
@@ -0,0 +1,109 @@
+// -*- C++ -*-
+/**
+ * \file Chat.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef QGUICHAT_H
+#define QGUICHAT_H
+
+#undef QT_NO_KEYWORDS 
+#include "GuiWorkArea.h"
+#include "GuiChatMessenger.h"
+
+#include "DockView.h"
+// This is needed so that ui_ChatUi.h can find qt_()
+#include "qt_helpers.h"
+#include "ui_ChatUi.h"
+
+#include "BufferView.h"
+#include "Buffer.h"
+#include "LyX.h"
+#include "Text.h"
+#include "lyxfind.h"
+
+#include <QDialog>
+
+#include <string>
+
+#include <qxmpp/QXmppClient.h>
+
+namespace lyx {
+namespace frontend {
+
+class GuiChatWidget;
+
+class GuiChatWidget : public QTabWidget, public Ui::ChatUi
+{
+	Q_OBJECT
+
+public:
+	GuiChatWidget(GuiView & view);
+	~GuiChatWidget();
+	bool initialiseParams(std::string const & params);
+	void onMessageReceived(std::string const & from, std::string const & s);
+
+	void setUserName(std::string const & name);
+	void setDestName(std::string const & name);
+
+	void ensureChatVisible(const std::string & peername);
+
+protected:
+	///
+	GuiView & view_;
+	std::string username;
+	std::string destname;
+
+	bool eventFilter(QObject *obj, QEvent *event);
+
+	virtual void showEvent(QShowEvent *ev);
+	virtual void hideEvent(QHideEvent *ev);
+
+	void hideDialog();
+
+	void appendToChat(std::string const & peername, std::string const & prefix, docstring const & latex);
+
+protected Q_SLOTS:
+	void on_sendPB_clicked();
+};
+
+
+class GuiChat : public DockView
+{
+	Q_OBJECT
+public:
+	GuiChat(
+		GuiView & parent, ///< the main window where to dock.
+		Qt::DockWidgetArea area = Qt::BottomDockWidgetArea, ///< Position of the dock (and also drawer)
+		Qt::WindowFlags flags = 0);
+
+	~GuiChat();
+
+	bool initialiseParams(std::string const &);
+	void clearParams() {}
+	void dispatchParams() {}
+	bool isBufferDependent() const { return false; }
+
+	/// update
+	void updateView() {}
+
+	GuiChatWidget * chatWidget() { return widget_; }
+
+protected:
+	virtual bool wantInitialFocus() const { return true; }
+
+private:
+	/// The encapsulated widget.
+	GuiChatWidget * widget_;
+};
+
+
+} // namespace frontend
+} // namespace lyx
+
+#endif // QGUICHAT_H
diff --git a/src/frontends/qt4/GuiChatMessenger.cpp b/src/frontends/qt4/GuiChatMessenger.cpp
new file mode 100644
index 00000000..ba40d301
--- /dev/null
+++ b/src/frontends/qt4/GuiChatMessenger.cpp
@@ -0,0 +1,101 @@
+#include "GuiChatMessenger.h"
+
+#include "GuiChat.h"
+#include "GuiBuddies.h"
+
+#include "qxmpp/QXmppMessage.h"
+#include "qxmpp/QXmppRosterManager.h"
+
+#include <support/debug.h>
+#include "support/lassert.h"
+#include "support/FileName.h"
+#include "support/Package.h"
+#include "support/filetools.h"
+
+namespace lyx {
+namespace frontend {
+
+using namespace std;
+using namespace support;
+
+static GuiChatMessenger *p_messenger = 0;
+
+
+GuiChatMessenger *theChatMessenger()
+{
+	if (!p_messenger)
+		p_messenger = new GuiChatMessenger();
+	return p_messenger;
+}
+
+
+FileName GuiChatMessenger::chatsDir()
+{
+	FileName chats_dir(addPath(package().user_support().absFileName(), "chats"));
+	if (!chats_dir.exists() && !chats_dir.createDirectory(0777)) {
+		lyxerr << "LyX could not create the user chats directory '"
+		       << chats_dir << "'. Chats history will not be kept." << endl;
+	}
+	if (!chats_dir.isDirWritable()) {
+		lyxerr << "LyX could not write to the user chats directory '"
+		       << chats_dir << "'. Chats history will not be kept." << endl;
+	}
+	return chats_dir;
+}
+
+
+FileName GuiChatMessenger::chatFileName(const std::string & peername)
+{
+	return FileName(chats_dir, peername + ".lyx");
+}
+
+
+GuiChatMessenger::GuiChatMessenger()
+  : p_chat(0), p_buddies(0)
+{
+	chats_dir = chatsDir();
+
+	connect(this, SIGNAL(messageReceived(const QXmppMessage&)),
+	 	SLOT(messageReceived(const QXmppMessage&)));
+}
+
+
+void GuiChatMessenger::messageReceived(const QXmppMessage& message)
+{
+  QString from = message.from();
+  QString msg = message.body();
+
+  string from_s = fromqstr(from);
+  size_t pos = from_s.find('/');
+  if (pos != string::npos)
+    from_s = from_s.substr(0, pos);
+  lyxerr << "Received from " << from << " (" << from_s << "): " << msg << endl;
+  if (p_chat)
+    p_chat->onMessageReceived(from_s, fromqstr(msg));
+}
+
+
+static string availStatusToText(QXmppPresence::AvailableStatusType s) {
+  static const char *texts[] = { "Online", "Away", "Extended Away", "Do Not Disturb", "Chat", "Invisible" };
+  LASSERT(s <= sizeof(texts) / sizeof(texts[0]), /**/);
+  return texts[s];
+}
+
+
+static string presenceTypeToText(QXmppPresence::Type t) {
+  static const char *texts[] = { "Error", "Available", "Unavailable", "Subscribe", "Subscribed", "Unsubscribe", "Unsubscribed", "Probe" };
+  LASSERT(t <= sizeof(texts) / sizeof(texts[0]), /**/);
+  return texts[t];
+}
+
+
+void GuiChatMessenger::sendMessage(const std::string & from, const std::string & dest, const std::string & msg)
+{
+  sendPacket(QXmppMessage(toqstr(from), toqstr(dest), toqstr(msg)));
+}
+
+
+} // namespace frontend
+} // namespace lyx
+
+#include "moc_GuiChatMessenger.cpp"
diff --git a/src/frontends/qt4/GuiChatMessenger.h b/src/frontends/qt4/GuiChatMessenger.h
new file mode 100644
index 00000000..f8e2b3c7
--- /dev/null
+++ b/src/frontends/qt4/GuiChatMessenger.h
@@ -0,0 +1,53 @@
+// -*- C++ -*-
+/**
+ * \file Chat.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Tommaso Cucinotta
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef QGUICHATMESSENGER_H
+#define QGUICHATMESSENGER_H
+
+#undef QT_NO_KEYWORDS 
+
+#include <qxmpp/QXmppClient.h>
+#include <string>
+#include "support/FileName.h"
+
+namespace lyx {
+namespace frontend {
+
+class GuiChat;
+class GuiChatWidget;
+class GuiBuddiesWidget;
+class GuiView;
+
+class GuiChatMessenger : public QXmppClient
+{
+	Q_OBJECT
+
+public:
+	GuiChatMessenger();
+	void sendMessage(const std::string & from, const std::string & dest, const std::string & msg);
+
+	support::FileName chatsDir();
+	support::FileName chats_dir;
+	support::FileName chatFileName(const std::string & peername);
+
+	GuiChatWidget *p_chat;
+	GuiBuddiesWidget *p_buddies;
+
+protected Q_SLOTS:
+	void messageReceived(const QXmppMessage&);
+};
+
+extern GuiChatMessenger *theChatMessenger();
+
+} // namespace frontend
+} // namespace lyx
+
+#endif // QGUICHATMESSENGER_H
diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 895a9e14..31fb7e64 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -1728,7 +1728,9 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
 				|| name == "prefs"
 				|| name == "texinfo"
 				|| name == "progress"
-				|| name == "compare";
+				|| name == "compare"
+				|| name == "chat"
+				|| name == "chat-bar";
 		else if (name == "print")
 			enable = doc_buffer->params().isExportable("dvi")
 				&& lyxrc.print_command != "none";
@@ -3934,7 +3936,7 @@ namespace {
 char const * const dialognames[] = {
 
 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
-"citation", "compare", "comparehistory", "document", "errorlist", "ert",
+"chat", "chat-bar", "citation", "compare", "comparehistory", "document", "errorlist", "ert",
 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
 "href", "include", "index", "index_print", "info", "listings", "label", "line",
 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
@@ -4121,6 +4123,8 @@ Dialog * createGuiAbout(GuiView & lv);
 Dialog * createGuiBibtex(GuiView & lv);
 Dialog * createGuiChanges(GuiView & lv);
 Dialog * createGuiCharacter(GuiView & lv);
+Dialog * createGuiChat(GuiView & lv);
+Dialog * createGuiBuddies(GuiView & lv);
 Dialog * createGuiCitation(GuiView & lv);
 Dialog * createGuiCompare(GuiView & lv);
 Dialog * createGuiCompareHistory(GuiView & lv);
@@ -4169,10 +4173,14 @@ Dialog * GuiView::build(string const & name)
 		return createGuiAbout(*this);
 	if (name == "bibtex")
 		return createGuiBibtex(*this);
+	if (name == "chat")
+		return createGuiBuddies(*this);
 	if (name == "changes")
 		return createGuiChanges(*this);
 	if (name == "character")
 		return createGuiCharacter(*this);
+	if (name == "chat-bar")
+		return createGuiChat(*this);
 	if (name == "citation")
 		return createGuiCitation(*this);
 	if (name == "compare")
diff --git a/src/frontends/qt4/GuiView.h b/src/frontends/qt4/GuiView.h
index 9220538f..544bbbd2 100644
--- a/src/frontends/qt4/GuiView.h
+++ b/src/frontends/qt4/GuiView.h
@@ -329,6 +329,8 @@ public:
 	void hideDialog(std::string const & name, Inset * inset);
 	///
 	void disconnectDialog(std::string const & name);
+	///
+	Dialog * findOrBuild(std::string const & name, bool hide_it);
 
 private:
 	/// Saves the layout and geometry of the window
@@ -416,8 +418,6 @@ private:
 	/// Is the dialog currently visible?
 	bool isDialogVisible(std::string const & name) const;
 	///
-	Dialog * findOrBuild(std::string const & name, bool hide_it);
-	///
 	Dialog * build(std::string const & name);
 	///
 	bool reloadBuffer(Buffer & buffer);
diff --git a/src/frontends/qt4/Makefile.am b/src/frontends/qt4/Makefile.am
index fc42e9b9..d00bb690 100644
--- a/src/frontends/qt4/Makefile.am
+++ b/src/frontends/qt4/Makefile.am
@@ -58,6 +58,9 @@ SOURCEFILES = \
 	Action.cpp \
 	BulletsModule.cpp \
 	ButtonController.cpp \
+	GuiChat.cpp \
+	GuiBuddies.cpp \
+	GuiChatMessenger.cpp \
 	CategorizedCombo.cpp \
 	ColorCache.cpp \
 	CustomizedWidgets.cpp \
@@ -177,6 +180,9 @@ NOMOCHEADER = \
 MOCHEADER = \
 	Action.h \
 	BulletsModule.h \
+	GuiChat.h \
+	GuiBuddies.h \
+	GuiChatMessenger.h \
 	CategorizedCombo.h \
 	CustomizedWidgets.h \
 	EmptyTable.h \
@@ -274,6 +280,8 @@ UIFILES = \
 	BulletsUi.ui \
 	ChangesUi.ui \
 	CharacterUi.ui \
+	ChatUi.ui \
+	BuddiesUi.ui \
 	CitationUi.ui \
 	ColorUi.ui \
 	CompareUi.ui \
diff --git a/src/frontends/qt4/ui/ChatUi.ui b/src/frontends/qt4/ui/ChatUi.ui
new file mode 100644
index 00000000..716a8ddc
--- /dev/null
+++ b/src/frontends/qt4/ui/ChatUi.ui
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChatUi</class>
+ <widget class="QWidget" name="ChatUi">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>519</width>
+    <height>253</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>&amp;Chat      From: </string>
+       </property>
+       <property name="buddy">
+        <cstring>chat_work_area_</cstring>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="fromLE"/>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>To: </string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="destLE"/>
+     </item>
+     <item>
+      <widget class="QPushButton" name="sendPB">
+       <property name="text">
+        <string>Send</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="lyx::frontend::EmbeddedWorkArea" name="chat_work_area_" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>200</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>lyx::frontend::EmbeddedWorkArea</class>
+   <extends>QWidget</extends>
+   <header>GuiWorkArea.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

Reply via email to