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>&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>