Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Chatoutput class #44

Closed
wants to merge 3 commits into from

3 participants

@teamikl

This patch implements #43
Summary of changes

  • separate application specific code and component.
  • use "tr" for translation messages
  • separate add message slots for message types.
  • reuse QTextCharFormat/QTextCursor as member.
  • URL auto-link

Implementation notes

Why those methods are slot

as QTextEdit#append is public slot, and it is an easy way to be callable from qtscript.

Why so many add messages slots

Currently, ChatOutput is missing save information that sender, what type of messages, ...
The view shows it with format but that will be lost when saved as plain text.
So it was the prepare step to make message handlers in future. (like model-view for message log)

parse url for autolink

I tried to do that by QSyntaxHighlighter, but QTextCharFormat#setAnchor in syntax-highlighter did not work.
The highlight url was worked, but that did not be clickable.


I did not do in this patch

notes for future todo.

  • API design, not frozen yet. some of slots may possible to be private or inline.
  • Scalability of message log data (no max-lines, or no limit of data size) when log become too much?
  • Extendability of parser for auto-link. its ad-hoc parser, need to be another approach when extend it.
stefanha added some commits
@stefanha stefanha Autoscroll the chat log
When new chat messages are received or entered by the user, the chat log
should scroll.  Currently the chat log is always one line behind and
therefore requires manual scrolling.

Reported-by: Andreas Hajnoczi <andi5432@gmail.com>
Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
216c995
@stefanha stefanha Add URL link support to chatAddLine()
Vote [+1] links are special-cased to append a URL link to the end of a
chat line.  Links could be used for other purposes in the future so it's
nice to support them directly in chatAddLine() and chatAddMessage().

This refactoring also eliminates the Vote [+1] dependency on
chatAddLine() cursor positioning.  It relied on the fact that
chatAddLine() does not include a newline at the end.

Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
1a4ad22
@stefanha

Nice, I like this patch. I left a few small comments and am looking forward to applying the next version.

@teamikl teamikl Separate ChatOutput class from MainWindow
Some changes are added, refactors reuse of QTextCharFormat,
QTextCursor, add message slots, and auto-link.

For more details, Check Issue #43

Signed-off-by: Ikkei Shimomura (tea) <Ikkei.Shimomura@gmail.com>
a43961a
@wahjam wahjam closed this
@wahjam
Owner

Applied, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 13, 2012
  1. @stefanha

    Autoscroll the chat log

    stefanha authored
    When new chat messages are received or entered by the user, the chat log
    should scroll.  Currently the chat log is always one line behind and
    therefore requires manual scrolling.
    
    Reported-by: Andreas Hajnoczi <andi5432@gmail.com>
    Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
  2. @stefanha

    Add URL link support to chatAddLine()

    stefanha authored
    Vote [+1] links are special-cased to append a URL link to the end of a
    chat line.  Links could be used for other purposes in the future so it's
    nice to support them directly in chatAddLine() and chatAddMessage().
    
    This refactoring also eliminates the Vote [+1] dependency on
    chatAddLine() cursor positioning.  It relied on the fact that
    chatAddLine() does not include a newline at the end.
    
    Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
Commits on Apr 14, 2012
  1. @teamikl

    Separate ChatOutput class from MainWindow

    teamikl authored
    Some changes are added, refactors reuse of QTextCharFormat,
    QTextCursor, add message slots, and auto-link.
    
    For more details, Check Issue #43
    
    Signed-off-by: Ikkei Shimomura (tea) <Ikkei.Shimomura@gmail.com>
This page is out of date. Refresh to see the latest.
View
157 ninjam/qtclient/ChatOutput.cpp
@@ -0,0 +1,157 @@
+/*
+ Copyright (C) 2012 Ikkei Shimomura (tea) <Ikkei.Shimomura@gmail.com>
+
+ Wahjam is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ Wahjam is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Wahjam; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "ChatOutput.h"
+
+#ifndef AUTOLINK_REGEXP
+#define AUTOLINK_REGEXP "https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+"
+#endif
+
+
+ChatOutput::ChatOutput(QWidget *parent)
+ : QTextBrowser(parent), autolinkRegexp(AUTOLINK_REGEXP)
+{
+ setReadOnly(true);
+ setOpenLinks(true);
+ setOpenExternalLinks(true);
+ setUndoRedoEnabled(false);
+
+ normalFormat.setFontWeight(QFont::Normal);
+
+ boldFormat.setFontWeight(QFont::Bold);
+
+ linkFormat.setAnchor(true);
+ linkFormat.setAnchorHref("");
+ linkFormat.setFontWeight(QFont::Bold);
+ linkFormat.setForeground(palette().link());
+ linkFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+
+ cursor = textCursor();
+
+ Q_ASSERT(autolinkRegexp.isValid());
+}
+
+void ChatOutput::addText(const QString &text, const QTextCharFormat &format)
+{
+ cursor.insertText(text, format);
+}
+
+/**
+ * add content with autolink
+ */
+void ChatOutput::addContent(const QString &content, const QTextCharFormat &format)
+{
+ QString text;
+ QString url;
+ int offset = 0;
+ int index = autolinkRegexp.indexIn(content, offset);
+
+ // avoid QString::mid call, content does not have links most cases.
+ if (index == -1) {
+ Q_ASSERT(offset == 0);
+ addText(content, format);
+ return;
+ }
+
+ do {
+ Q_ASSERT(index == -1 || index >= 0);
+
+ // add normal text.
+ // when index was -1, no more links, that add the rest of content.
+ text = content.mid(offset, (index == -1) ? -1 : (index-offset));
+ if (!text.isEmpty()) {
+ addText(text, format);
+ }
+
+ if (index == -1) {
+ break;
+ }
+
+ url = autolinkRegexp.cap(0);
+ if (!url.isEmpty()) {
+ addLink(url, url);
+ }
+
+ // set variables for next iteration.
+ offset = index + autolinkRegexp.matchedLength();
+ index = autolinkRegexp.indexIn(content, offset);
+
+ // NOTE: overflow check ((int + int) >= 0)
+ Q_ASSERT(offset >= 0);
+
+ } while (offset >= 0);
+}
+
+void ChatOutput::addLine(const QString &prefix, const QString &content)
+{
+ // Edit block for Undo/Redo.
+ cursor.beginEditBlock();
+ {
+ cursor.movePosition(QTextCursor::End);
+
+ if (!document()->isEmpty()) {
+ cursor.insertBlock();
+ }
+
+ if (!prefix.isEmpty()) {
+ addText(prefix, boldFormat);
+ }
+
+ if (!content.isEmpty()) {
+ addContent(content, normalFormat);
+ }
+ }
+ cursor.endEditBlock();
+
+ // Autoscroll bottom of chat
+ moveCursor(QTextCursor::End);
+}
+
+void ChatOutput::addLink(const QString &href, const QString &linktext)
+{
+ QTextCharFormat format = linkFormat;
+ format.setAnchorHref(href);
+
+ addText(linktext, format);
+}
+
+void ChatOutput::addMessage(const QString &src, const QString &message)
+{
+ addLine(tr("<%1> ").arg(src), message);
+}
+
+void ChatOutput::addActionMessage(const QString &src, const QString &message)
+{
+ addLine(tr("* %1 ").arg(src), message);
+}
+
+void ChatOutput::addPrivateMessage(const QString &src, const QString &message)
+{
+ addLine(tr("* %1 ").arg(src), message);
+}
+
+void ChatOutput::addInfoMessage(const QString &message)
+{
+ addLine(tr("[INFO] "), message);
+}
+
+void ChatOutput::addErrorMessage(const QString &message)
+{
+ addLine(tr("[ERROR] "), message);
+}
+
View
55 ninjam/qtclient/ChatOutput.h
@@ -0,0 +1,55 @@
+/*
+ Copyright (C) 2012 Ikkei Shimomura (tea) <Ikkei.Shimomura@gmail.com>
+
+ Wahjam is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ Wahjam is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Wahjam; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef _CHATOUTPUT_H_
+#define _CHATOUTPUT_H_
+
+#include <QRegExp>
+#include <QTextBrowser>
+
+
+class ChatOutput : public QTextBrowser
+{
+ Q_OBJECT
+
+public:
+ ChatOutput(QWidget *parent=0);
+
+public slots:
+ void addText(const QString &text, const QTextCharFormat &format);
+ void addContent(const QString &content, const QTextCharFormat &format);
+ void addLine(const QString &prefix, const QString &content);
+ void addLink(const QString &href, const QString &linktext);
+
+ void addMessage(const QString &src, const QString &message);
+ void addActionMessage(const QString &src, const QString &message);
+ void addPrivateMessage(const QString &src, const QString &message);
+ void addInfoMessage(const QString &message);
+ void addErrorMessage(const QString &message);
+
+private:
+ QRegExp autolinkRegexp;
+ QTextCharFormat normalFormat;
+ QTextCharFormat boldFormat;
+ QTextCharFormat linkFormat;
+ QTextCursor cursor;
+
+};
+
+#endif /* _CHATOUTPUT_H_ */
+
View
100 ninjam/qtclient/MainWindow.cpp
@@ -121,10 +121,7 @@ MainWindow::MainWindow(QWidget *parent)
setWindowTitle(tr("Wahjam"));
- chatOutput = new QTextBrowser(this);
- chatOutput->setReadOnly(true);
- chatOutput->setOpenLinks(false);
- chatOutput->setOpenExternalLinks(false);
+ chatOutput = new ChatOutput(this);
chatOutput->connect(chatOutput, SIGNAL(anchorClicked(const QUrl&)),
this, SLOT(ChatLinkClicked(const QUrl&)));
@@ -244,7 +241,7 @@ void MainWindow::setupStatusBar()
void MainWindow::Connect(const QString &host, const QString &user, const QString &pass)
{
if (!setupWorkDir()) {
- chatAddLine("Unable to create work directory.", "");
+ chatOutput->addInfoMessage(tr("Unable to create work directory."));
return;
}
@@ -287,7 +284,7 @@ void MainWindow::Disconnect()
if (!workDirPath.isEmpty() && !keepWorkDir) {
cleanupWorkDir(workDirPath);
- chatAddLine("Disconnected", "");
+ chatOutput->addInfoMessage(tr("Disconnected"));
}
setWindowTitle(tr("Wahjam"));
@@ -468,7 +465,7 @@ void MainWindow::ClientStatusChanged(int newStatus)
statusMessage = tr("Error: connecting failed");
}
- chatAddLine(statusMessage, "");
+ chatOutput->addInfoMessage(statusMessage);
if (newStatus < 0) {
Disconnect();
@@ -493,32 +490,6 @@ void MainWindow::BeatsPerIntervalChanged(int bpi)
}
}
-/* Append line with bold formatted prefix to the chat widget */
-void MainWindow::chatAddLine(const QString &prefix, const QString &content)
-{
- QTextCharFormat defaultFormat;
- QTextCharFormat boldFormat;
- boldFormat.setFontWeight(QFont::Bold);
-
- chatOutput->moveCursor(QTextCursor::End);
- chatOutput->setCurrentCharFormat(boldFormat);
- chatOutput->append(prefix);
- chatOutput->setCurrentCharFormat(defaultFormat);
- chatOutput->insertPlainText(content);
-}
-
-/* Append a message from a given source to the chat widget */
-void MainWindow::chatAddMessage(const QString &src, const QString &msg)
-{
- if (src.isEmpty()) {
- chatAddLine("*** ", msg);
- } else if (msg.startsWith("/me ")) {
- chatAddLine(QString("* %1 ").arg(src), msg.mid(4));
- } else {
- chatAddLine(QString("<%1> ").arg(src), msg);
- }
-}
-
void MainWindow::ChatMessageCallback(char **charparms, int nparms)
{
QString parms[nparms];
@@ -533,21 +504,23 @@ void MainWindow::ChatMessageCallback(char **charparms, int nparms)
if (parms[0] == "TOPIC") {
if (parms[1].isEmpty()) {
if (parms[2].isEmpty()) {
- chatAddLine("No topic is set.", "");
+ chatOutput->addInfoMessage(tr("No topic is set."));
} else {
- chatAddLine(QString("Topic is: "), parms[2]);
+ chatOutput->addInfoMessage(tr("Topic is: %1").arg(parms[2]));
}
} else {
if (parms[2].isEmpty()) {
- chatAddLine(QString("%1 removes topic.").arg(parms[1]), "");
+ chatOutput->addInfoMessage(tr("%1 removes topic.").arg(parms[1]));
} else {
- chatAddLine(QString("%1 sets topic to: ").arg(parms[1]), parms[2]);
+ chatOutput->addInfoMessage(
+ tr("%1 sets topic to: %2").arg(parms[1], parms[2]));
}
}
/* TODO set topic */
} else if (parms[0] == "MSG") {
- chatAddMessage(parms[1], parms[2]);
+ QString href;
+ QString linktext;
// Add +1 vote link
if (parms[1].isEmpty() && parms[2].startsWith("[voting system]")) {
@@ -556,34 +529,33 @@ void MainWindow::ChatMessageCallback(char **charparms, int nparms)
" for (\\d+) (BPI|BPM) \\[each vote expires in \\d+s\\]");
if (re.exactMatch(parms[2]) && re.cap(1) == "1") {
- QString href = QString("send-message:!vote %1 %2").arg(
- re.cap(3).toLower(),re.cap(2));
-
- QTextCharFormat defaultFormat;
- QTextCharFormat linkFormat;
- linkFormat.setAnchor(true);
- linkFormat.setAnchorHref(href);
- linkFormat.setFontWeight(QFont::Bold);
- linkFormat.setForeground(QApplication::palette().link());
- linkFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
-
- QTextCursor cursor = chatOutput->textCursor();
- cursor.movePosition(QTextCursor::End);
- cursor.insertText(" ", defaultFormat);
- cursor.insertText("[+1]", linkFormat);
- cursor.insertText(" ", defaultFormat);
+ href = QString("send-message:!vote %1 %2").arg(
+ re.cap(3).toLower(),re.cap(2));
+ linktext = "[+1]";
+ }
+ }
+
+ if (! href.isEmpty() && ! linktext.isEmpty()) {
+ chatOutput->addMessage(parms[1], parms[2]);
+ chatOutput->addLink(href, linktext);
+ } else {
+ if (parms[2].startsWith("/me ")) {
+ chatOutput->addActionMessage(parms[1], parms[2]);
+ }
+ else {
+ chatOutput->addMessage(parms[1], parms[2]);
}
}
} else if (parms[0] == "PRIVMSG") {
- chatAddLine(QString("* %1 * ").arg(parms[1]), parms[2]);
+ chatOutput->addPrivateMessage(parms[1], parms[2]);
} else if (parms[0] == "JOIN") {
- chatAddLine(QString("%1 has joined the server").arg(parms[1]), "");
+ chatOutput->addInfoMessage(tr("%1 has joined the server").arg(parms[1]));
} else if (parms[0] == "PART") {
- chatAddLine(QString("%1 has left the server").arg(parms[1]), "");
+ chatOutput->addInfoMessage(tr("%1 has left the server").arg(parms[1]));
} else {
- chatOutput->append("Unrecognized command:");
+ chatOutput->addInfoMessage(tr("Unrecognized command:"));
for (i = 0; i < nparms; i++) {
- chatOutput->append(QString("[%1] %2").arg(i).arg(parms[i]));
+ chatOutput->addLine(QString("[%1]").arg(i), parms[i]);
}
}
}
@@ -599,7 +571,9 @@ void MainWindow::ChatInputReturnPressed()
{
QString line = chatInput->text();
chatInput->clear();
- SendChatMessage(line);
+ if (! line.isEmpty()) {
+ SendChatMessage(line);
+ }
}
void MainWindow::SendChatMessage(const QString &line)
@@ -625,10 +599,10 @@ void MainWindow::SendChatMessage(const QString &line)
parm = line.section(' ', 1, 1, QString::SectionSkipEmpty);
msg = line.section(' ', 2, -1, QString::SectionSkipEmpty);
if (msg.isEmpty()) {
- chatAddLine("error: /msg requires a username and a message.", "");
+ chatOutput->addErrorMessage(tr("/msg requires a username and a message."));
return;
}
- chatAddLine(QString("-> *%1* ").arg(parm), msg);
+ chatOutput->addLine(tr("-> *%1* ").arg(parm), msg);
} else {
command = "MSG";
parm = line;
@@ -646,7 +620,7 @@ void MainWindow::SendChatMessage(const QString &line)
clientMutex.unlock();
if (!connected) {
- chatAddLine("error: not connected to a server.", "");
+ chatOutput->addErrorMessage("not connected to a server.");
}
}
View
10 ninjam/qtclient/MainWindow.h
@@ -21,7 +21,6 @@
#include <QMainWindow>
#include <QWidget>
-#include <QTextBrowser>
#include <QLineEdit>
#include <QLabel>
#include <QMutex>
@@ -30,6 +29,7 @@
#include "ChannelTreeWidget.h"
#include "MetronomeBar.h"
+#include "ChatOutput.h"
#include "../njclient.h"
#include "../audiostream.h"
@@ -82,7 +82,7 @@ private slots:
audioStreamer *audio;
QMutex clientMutex;
ClientRunThread *runThread;
- QTextBrowser *chatOutput;
+ ChatOutput *chatOutput;
QLineEdit *chatInput;
ChannelTreeWidget *channelTree;
QAction *connectAction;
@@ -97,8 +97,10 @@ private slots:
bool setupWorkDir();
void cleanupWorkDir(const QString &path);
void OnSamples(float **inbuf, int innch, float **outbuf, int outnch, int len, int srate);
- void chatAddLine(const QString &prefix, const QString &content);
- void chatAddMessage(const QString &src, const QString &msg);
+ void chatAddLine(const QString &prefix, const QString &content,
+ const QString &href = "", const QString &linktext = "");
+ void chatAddMessage(const QString &src, const QString &msg,
+ const QString &href = "", const QString &linktext = "");
static void OnSamplesTrampoline(float **inbuf, int innch, float **outbuf, int outnch, int len, int srate);
static int LicenseCallbackTrampoline(int user32, char *licensetext);
static void ChatMessageCallbackTrampoline(int user32, NJClient *inst, char **parms, int nparms);
View
2  ninjam/qtclient/ServerBrowser.cpp
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 tea <Ikkei.Shimomura@gmail.com>
+ Copyright (C) 2012 Ikkei Shimomura (tea) <Ikkei.Shimomura@gmail.com>
Wahjam is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
View
2  ninjam/qtclient/ServerBrowser.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012 tea <Ikkei.Shimomura@gmail.com>
+ Copyright (C) 2012 Ikkei Shimomura (tea) <Ikkei.Shimomura@gmail.com>
Wahjam is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
View
2  ninjam/qtclient/qtclient.pro
@@ -29,6 +29,7 @@ HEADERS += ChannelTreeWidget.h
HEADERS += PortAudioConfigDialog.h
HEADERS += ServerBrowser.h
HEADERS += MetronomeBar.h
+HEADERS += ChatOutput.h
SOURCES += qtclient.cpp
SOURCES += MainWindow.cpp
@@ -38,6 +39,7 @@ SOURCES += ChannelTreeWidget.cpp
SOURCES += PortAudioConfigDialog.cpp
SOURCES += ServerBrowser.cpp
SOURCES += MetronomeBar.cpp
+SOURCES += ChatOutput.cpp
SOURCES += ../../WDL/jnetlib/asyncdns.cpp
SOURCES += ../../WDL/jnetlib/connection.cpp
SOURCES += ../../WDL/jnetlib/listen.cpp
Something went wrong with that request. Please try again.